updated plugin ActivityPub version 9.1.0
This commit is contained in:
@ -3,7 +3,7 @@
|
||||
* Plugin Name: ActivityPub
|
||||
* Plugin URI: https://github.com/Automattic/wordpress-activitypub
|
||||
* Description: The ActivityPub protocol is a decentralized social networking protocol based upon the ActivityStreams 2.0 data format.
|
||||
* Version: 8.3.0
|
||||
* Version: 9.1.0
|
||||
* Author: Matthias Pfefferle & Automattic
|
||||
* Author URI: https://automattic.com/
|
||||
* License: MIT
|
||||
@ -17,7 +17,7 @@
|
||||
|
||||
namespace Activitypub;
|
||||
|
||||
\define( 'ACTIVITYPUB_PLUGIN_VERSION', '8.3.0' );
|
||||
\define( 'ACTIVITYPUB_PLUGIN_VERSION', '9.1.0' );
|
||||
|
||||
// Plugin related constants.
|
||||
\define( 'ACTIVITYPUB_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
|
||||
@ -65,6 +65,7 @@ function rest_init() {
|
||||
( new Rest\Interaction_Controller() )->register_routes();
|
||||
( new Rest\Moderators_Controller() )->register_routes();
|
||||
if ( \get_option( 'activitypub_api', false ) ) {
|
||||
( new Rest\Actor_Autocomplete_Controller() )->register_routes();
|
||||
( new Rest\OAuth\Authorization_Controller() )->register_routes();
|
||||
( new Rest\OAuth\Clients_Controller() )->register_routes();
|
||||
( new Rest\OAuth\Token_Controller() )->register_routes();
|
||||
@ -87,7 +88,9 @@ function rest_init() {
|
||||
*/
|
||||
function plugin_init() {
|
||||
\add_action( 'init', array( __NAMESPACE__ . '\Activitypub', 'init' ) );
|
||||
\add_action( 'init', array( __NAMESPACE__ . '\Application', 'init' ) );
|
||||
\add_action( 'init', array( __NAMESPACE__ . '\Avatars', 'init' ) );
|
||||
\add_action( 'init', array( __NAMESPACE__ . '\Blurhash', 'init' ) );
|
||||
\add_action( 'init', array( __NAMESPACE__ . '\Cache', 'init' ) );
|
||||
\add_action( 'init', array( __NAMESPACE__ . '\Comment', 'init' ) );
|
||||
\add_action( 'init', array( __NAMESPACE__ . '\Dispatcher', 'init' ) );
|
||||
|
||||
@ -17,4 +17,33 @@ jQuery( function( $ ) {
|
||||
$( '.activate-now' ).removeClass( 'thickbox open-plugin-details-modal' );
|
||||
}, 1200 );
|
||||
} );
|
||||
|
||||
/*
|
||||
* Lazy-load embedded iframes (e.g. the Fediverse intro video) only once their
|
||||
* container becomes visible. Help tab panels are rendered hidden, and players
|
||||
* like PeerTube abort with an "invalid width" error when initialized inside a
|
||||
* zero-width container. Deferring the src until the panel is shown avoids that.
|
||||
*/
|
||||
var lazyIframes = document.querySelectorAll( 'iframe[data-src]' );
|
||||
|
||||
if ( lazyIframes.length && 'IntersectionObserver' in window ) {
|
||||
var iframeObserver = new IntersectionObserver( function( entries, observer ) {
|
||||
entries.forEach( function( entry ) {
|
||||
if ( entry.isIntersecting ) {
|
||||
// Set once, then stop observing so it never reloads.
|
||||
entry.target.src = entry.target.dataset.src;
|
||||
observer.unobserve( entry.target );
|
||||
}
|
||||
} );
|
||||
} );
|
||||
|
||||
lazyIframes.forEach( function( iframe ) {
|
||||
iframeObserver.observe( iframe );
|
||||
} );
|
||||
} else {
|
||||
// IntersectionObserver unsupported: load immediately as a fallback.
|
||||
lazyIframes.forEach( function( iframe ) {
|
||||
iframe.src = iframe.dataset.src;
|
||||
} );
|
||||
}
|
||||
} );
|
||||
|
||||
@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Toggle the custom distribution-mode fields based on the selected radio.
|
||||
*
|
||||
* The custom batch-size / pause inputs are only relevant when the
|
||||
* "custom" preset is active. They are rendered visible by default so the
|
||||
* form remains usable when JavaScript is disabled; this script collapses
|
||||
* them on load when another mode is selected, and toggles them when the
|
||||
* radio changes.
|
||||
*/
|
||||
|
||||
( function() {
|
||||
const radios = document.querySelectorAll( 'input[name="activitypub_distribution_mode"]' );
|
||||
const fields = document.getElementById( 'activitypub-custom-distribution-fields' );
|
||||
|
||||
if ( ! fields || ! radios.length ) {
|
||||
return;
|
||||
}
|
||||
|
||||
function updateVisibility( value ) {
|
||||
fields.style.display = 'custom' === value ? '' : 'none';
|
||||
}
|
||||
|
||||
const initial = document.querySelector( 'input[name="activitypub_distribution_mode"]:checked' );
|
||||
updateVisibility( initial ? initial.value : 'default' );
|
||||
|
||||
radios.forEach( function( radio ) {
|
||||
radio.addEventListener( 'change', function() {
|
||||
updateVisibility( this.value );
|
||||
} );
|
||||
} );
|
||||
}() );
|
||||
@ -7,5 +7,9 @@
|
||||
"description": "Manage ActivityPub settings, followers, and federation from the WordPress admin.",
|
||||
"icon": "smartphone",
|
||||
"textdomain": "activitypub",
|
||||
"editorScript": "file:./index.js"
|
||||
"viewScriptModule": [
|
||||
"file:./loader.js",
|
||||
"file:./routes/feed/content.js",
|
||||
"file:./routes/feed/route.js"
|
||||
]
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
||||
"use strict";(globalThis.webpackChunkwordpress_activitypub=globalThis.webpackChunkwordpress_activitypub||[]).push([[843],{4831(s,t,p){p.r(t),p.d(t,{route:()=>r});const r={inspector:({search:s})=>!!s.postId}}}]);
|
||||
@ -1 +0,0 @@
|
||||
<?php return array('dependencies' => array('react', 'react-dom', 'react-jsx-runtime', 'wp-commands', 'wp-components', 'wp-compose', 'wp-core-data', 'wp-data', 'wp-data-controls', 'wp-date', 'wp-dom', 'wp-element', 'wp-hooks', 'wp-html-entities', 'wp-i18n', 'wp-keyboard-shortcuts', 'wp-keycodes', 'wp-notices', 'wp-preferences', 'wp-primitives', 'wp-url'), 'version' => '3b7e2c6f3b9b959e939d');
|
||||
File diff suppressed because one or more lines are too long
@ -0,0 +1 @@
|
||||
<?php return array('dependencies' => array('@wordpress/boot'), 'version' => '628716c963d567f5237c', 'type' => 'module');
|
||||
1
wp-content/plugins/activitypub/build/app/loader.js
Normal file
1
wp-content/plugins/activitypub/build/app/loader.js
Normal file
@ -0,0 +1 @@
|
||||
import{initSinglePage as t}from"@wordpress/boot";const e=()=>{const{mountId:e,routes:n}=(()=>{const t=document.getElementById("wp-script-module-data-@activitypub/app");if(!t?.textContent)return{};try{return JSON.parse(t.textContent)}catch{return{}}})();e&&Array.isArray(n)&&t({mountId:e,routes:n})};"loading"===document.readyState?document.addEventListener("DOMContentLoaded",e,{once:!0}):e();
|
||||
@ -0,0 +1 @@
|
||||
<?php return array('dependencies' => array('@wordpress/route'), 'version' => '3f559ba6405dafacccda', 'type' => 'module');
|
||||
192
wp-content/plugins/activitypub/build/app/routes/feed/content.js
Normal file
192
wp-content/plugins/activitypub/build/app/routes/feed/content.js
Normal file
File diff suppressed because one or more lines are too long
@ -0,0 +1 @@
|
||||
<?php return array('dependencies' => array(), 'version' => '2342cc8363fb9d82cc64', 'type' => 'module');
|
||||
@ -0,0 +1 @@
|
||||
const t=window.wp.i18n,i={title:()=>(0,t.__)("Social Web","activitypub"),inspector:({search:t})=>!!t.postId};export{i as route};
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,10 +0,0 @@
|
||||
.themed-surface{background:#fff;border-radius:0;display:flex;flex:1;flex-direction:column;min-height:0;overflow:hidden}@media(min-width:782px){.themed-surface{border-radius:var(--wpds-border-radius-large,8px)}}
|
||||
.panel{display:flex;flex:1;flex-direction:column;margin:0;min-height:0}@media(min-width:782px){.panel{margin:var(--wpds-spacing-40,16px) 0 var(--wpds-spacing-40,16px) var(--wpds-spacing-40,16px)}}
|
||||
.site-icon{align-items:center;display:flex;justify-content:center}.site-icon__image{aspect-ratio:1/1;background:#333;height:100%;-o-object-fit:cover;object-fit:cover;width:100%}.site-icon__icon{fill:currentcolor}
|
||||
.site-hub{align-items:center;display:flex;gap:var(--wpds-spacing-20,8px);height:var(--wpds-spacing-160,64px);justify-content:space-between;margin-left:var(--wpds-spacing-30,12px)}.site-hub__actions{flex-shrink:0}.site-hub__icon-container{flex-shrink:0;height:var(--wpds-spacing-160,64px);width:var(--wpds-spacing-160,64px)}.site-hub__icon-container.has-transparent-background .site-hub__icon-container{background:transparent}.site-hub__icon-button.components-button{align-items:center;background:#1e1e1e;border-radius:0;color:#fff;display:flex;height:var(--wpds-spacing-160,64px);justify-content:center;overflow:hidden;padding:0;position:relative;view-transition-name:toggle;width:var(--wpds-spacing-160,64px)}.site-hub__icon-button.components-button:active,.site-hub__icon-button.components-button:hover{color:#fff}.site-hub__icon-button.components-button:focus,.site-hub__icon-button.components-button:focus-visible{box-shadow:0 0 0 3px #1e1e1e,0 0 0 6px var(--wp-admin-theme-color);outline:4px solid transparent;outline-offset:4px}.site-hub__icon-button.components-button:before{border-radius:var(--wpds-border-radius-large,8px);bottom:calc(var(--wpds-spacing-20, 8px) + var(--wpds-border-width-focus, 1px));box-shadow:none;content:"";display:block;right:calc(var(--wpds-spacing-40, 16px) + var(--wpds-border-width-focus, 1px));position:absolute;left:calc(var(--wpds-spacing-20, 8px) + var(--wpds-border-width-focus, 1px));top:calc(var(--wpds-spacing-20, 8px) + var(--wpds-border-width-focus, 1px))}@media not (prefers-reduced-motion){.site-hub__icon-button.components-button:before{transition:box-shadow .1s ease}}.site-hub__icon-button.components-button .site-icon{height:var(--wpds-spacing-160,64px);width:var(--wpds-spacing-160,64px)}.site-hub__icon-button.components-button .site-icon__icon{height:100%;width:100%}.site-hub__title .components-button{color:#e0e0e0;display:block;flex-grow:1;font-size:var(--wpds-font-size-medium,15px);font-weight:var(--wpds-font-weight-medium,500);margin-right:-4px;overflow:hidden;padding-left:var(--wpds-spacing-40,16px);position:relative;text-decoration:none;text-overflow:ellipsis;white-space:nowrap}.site-hub__title .components-button:active,.site-hub__title .components-button:focus,.site-hub__title .components-button:hover{color:#e0e0e0}.site-hub__title .components-button:focus{box-shadow:none;outline:none}.site-hub__title .components-button:focus-visible{box-shadow:0 0 0 var(--wp-admin-border-width-focus) #1e1e1e,0 0 0 calc(var(--wp-admin-border-width-focus)*2) var(--wp-admin-theme-color);outline:2px solid transparent;outline-offset:2px}.site-hub__title .components-button:after{content:"↗";font-weight:400;opacity:0;position:absolute;left:0}@media not (prefers-reduced-motion){.site-hub__title .components-button:after{transition:opacity .1s linear}}.site-hub__title .components-button:active:after,.site-hub__title .components-button:focus:after,.site-hub__title .components-button:hover:after{opacity:1}.site-hub__command-button{color:#e0e0e0}.site-hub__command-button:active svg,.site-hub__command-button:hover svg{fill:#f0f0f0}.site-hub-mobile{align-items:center;background:#1e1e1e;border-bottom:1px solid hsla(0,0%,100%,.1);display:flex;flex-shrink:0;height:56px;justify-content:space-between;padding:0 var(--wpds-spacing-30,12px)}.site-hub-mobile__title{color:#fff;font-size:var(--wpds-font-size-medium,16px);font-weight:var(--wpds-font-weight-medium,500)}.site-hub-mobile__button.components-button{color:#e0e0e0}.site-hub-mobile__button.components-button:focus:not(:disabled),.site-hub-mobile__button.components-button:hover:not(:disabled){color:#fff}.site-hub-mobile__button.components-button:focus-visible{box-shadow:0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color);outline:2px solid transparent}
|
||||
.activitypub-avatar{background-color:#f0f0f0;border-radius:50%;flex-shrink:0;height:var(--wpds-spacing-120,48px);-o-object-fit:cover;object-fit:cover;width:var(--wpds-spacing-120,48px)}
|
||||
.actor-switcher{align-items:center;color:#e0e0e0;display:flex;min-width:0;padding-right:2px;text-decoration:none;transition:color .2s ease}.actor-switcher:hover:not(:disabled){color:#fff}.actor-switcher__avatar{border-radius:50%;flex-shrink:0;height:32px;width:32px}.actor-switcher__name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||
.popular-tags{background:transparent!important;border-bottom:none!important;border-right:none!important;border-left:none!important;border-top:1px solid hsla(0,0%,100%,.1);box-shadow:none!important;padding:0 var(--wpds-spacing-30,12px) var(--wpds-spacing-30,12px)}.popular-tags__title{color:#949494;font-size:11px;font-weight:500;letter-spacing:.5px;margin:var(--wpds-spacing-30,12px) 0 var(--wpds-spacing-20,8px) 0;padding:0 var(--wpds-spacing-20,8px);text-transform:uppercase}.popular-tags__loading{color:#ccc;font-size:13px;padding:var(--wpds-spacing-20,8px)}.popular-tags .components-menu-group{background:transparent!important;border:none!important;box-shadow:none!important;margin:0;padding:0}.popular-tags .menu-item{align-items:center;color:#e0e0e0;display:flex;gap:var(--wpds-spacing-20,8px)}.popular-tags .menu-item:hover{background-color:hsla(0,0%,100%,.1);color:#fff}.popular-tags .menu-item.is-pressed{background-color:hsla(0,0%,100%,.15);color:#fff}.popular-tags .menu-item svg{flex-shrink:0}
|
||||
.sidebar{background-color:#1e1e1e;color:#ccc;display:flex;flex:1;flex-direction:column}.sidebar .nav{flex:1;overflow-y:auto;padding:0 var(--wpds-spacing-30,12px)}.sidebar .sidebar-navigation__icon-title{background:#1e1e1e;margin-bottom:var(--wpds-spacing-20,8px);padding-bottom:var(--wpds-spacing-20,8px);padding-top:var(--wpds-spacing-120,48px);position:sticky;top:0}.sidebar .sidebar-navigation__button{color:#e0e0e0;flex-shrink:0}.sidebar .sidebar-navigation__button:focus:not(:disabled){box-shadow:none;outline:none}.sidebar .sidebar-navigation__button:focus-visible:not(:disabled){box-shadow:0 0 0 var(--wpds-border-width-focus,1.5px) var(--wp-admin-theme-color);outline:3px solid transparent}.sidebar .sidebar-navigation__button:focus,.sidebar .sidebar-navigation__button:focus-visible,.sidebar .sidebar-navigation__button:hover:not(:disabled,[aria-disabled=true]),.sidebar .sidebar-navigation__button:not(:disabled,[aria-disabled=true]):active,.sidebar .sidebar-navigation__button[aria-expanded=true]{color:#f0f0f0}.sidebar .sidebar-navigation__title{color:#e0e0e0;flex-grow:1;line-height:var(--wpds-spacing-80,32px);overflow-wrap:break-word}.sidebar .sidebar-description{margin:0 0 var(--wpds-spacing-80,32px) 0;padding:0 var(--wpds-spacing-40,16px)}.sidebar .menu-item{color:#e0e0e0;transition:none!important}.sidebar .menu-item:focus{box-shadow:none!important;outline:none!important}.sidebar .menu-item:focus-visible{box-shadow:0 0 0 var(--wpds-border-width-focus,1.5px) var(--wp-admin-theme-color)!important;outline:3px solid transparent!important}.sidebar .menu-item:hover{color:#fff!important}.sidebar .menu-item:active,.sidebar .menu-item:hover{background-color:hsla(0,0%,100%,.1)!important}.sidebar .menu-item.is-selected,.sidebar .menu-item[aria-pressed=true]{background-color:hsla(0,0%,100%,.15)!important;color:#fff!important}.sidebar .menu-item.is-selected:active,.sidebar .menu-item.is-selected:hover,.sidebar .menu-item[aria-pressed=true]:active,.sidebar .menu-item[aria-pressed=true]:hover{background-color:hsla(0,0%,100%,.15)!important}.sidebar .menu-item svg{margin-left:var(--wpds-spacing-10,8px)}.sidebar .object-types-menu{margin-top:var(--wpds-spacing-10,4px)}.sidebar .object-types{padding:var(--wpds-spacing-20,8px) var(--wpds-spacing-30,12px)}.sidebar .object-types__loading{color:#ccc;font-size:13px}.sidebar .footer{border-top:1px solid hsla(0,0%,100%,.1);padding:var(--wpds-spacing-40,16px)}.sidebar .footer>.components-h-stack{padding:2px 0}.sidebar .footer .footer-settings-button{color:#e0e0e0;flex-shrink:0}.sidebar .footer .footer-settings-button:hover:not(:disabled){color:#fff}
|
||||
#activitypub-app-root>div{height:100%}.app-layout{background-color:#1e1e1e;display:flex;flex-direction:column;height:100%;isolation:isolate;overflow:hidden;width:100%}.sidebar-backdrop{background-color:rgba(0,0,0,.5);cursor:pointer;inset:0;position:fixed;z-index:100002}.app-content{background-color:#1e1e1e;display:flex;flex-grow:1;height:100%;overflow:hidden;width:100%}.app-content.is-mobile,.sidebar-region{flex-direction:column}.sidebar-region{display:flex;flex-shrink:0;height:100%;width:300px}.sidebar-region.is-mobile{background-color:#1e1e1e;bottom:0;box-shadow:-2px 0 8px rgba(0,0,0,.3);right:0;max-width:85vw;position:fixed;top:0;width:300px;z-index:100003}.inspector-region,.stage-region{bottom:0;display:flex;flex-direction:column;height:calc(100vh - 56px);right:0;overflow:hidden;position:fixed;left:0;top:56px;width:100vw}@media(min-width:782px){.inspector-region,.stage-region{bottom:auto;height:auto;right:auto;position:static;left:auto;top:auto;width:auto}}.stage-region{z-index:2}@media(min-width:782px){.stage-region{flex:1 1 auto;min-width:0;z-index:auto}}.inspector-region{z-index:3}@media(min-width:782px){.inspector-region{flex:0 0 var(--sw-inspector-width,380px);z-index:auto}}.components-snackbar-list{bottom:20px;right:20px;position:fixed;z-index:100000}@media(max-width:782px){.components-snackbar-list{bottom:10px;right:10px;left:10px}}
|
||||
:root{--sw-sidebar-width:300px;--sw-inspector-width:50%}.activitypub-app-layout{bottom:0;right:0;position:fixed;left:0;top:0}#wpfooter{display:none}
|
||||
@ -1,10 +0,0 @@
|
||||
.themed-surface{background:#fff;border-radius:0;display:flex;flex:1;flex-direction:column;min-height:0;overflow:hidden}@media(min-width:782px){.themed-surface{border-radius:var(--wpds-border-radius-large,8px)}}
|
||||
.panel{display:flex;flex:1;flex-direction:column;margin:0;min-height:0}@media(min-width:782px){.panel{margin:var(--wpds-spacing-40,16px) var(--wpds-spacing-40,16px) var(--wpds-spacing-40,16px) 0}}
|
||||
.site-icon{align-items:center;display:flex;justify-content:center}.site-icon__image{aspect-ratio:1/1;background:#333;height:100%;-o-object-fit:cover;object-fit:cover;width:100%}.site-icon__icon{fill:currentcolor}
|
||||
.site-hub{align-items:center;display:flex;gap:var(--wpds-spacing-20,8px);height:var(--wpds-spacing-160,64px);justify-content:space-between;margin-right:var(--wpds-spacing-30,12px)}.site-hub__actions{flex-shrink:0}.site-hub__icon-container{flex-shrink:0;height:var(--wpds-spacing-160,64px);width:var(--wpds-spacing-160,64px)}.site-hub__icon-container.has-transparent-background .site-hub__icon-container{background:transparent}.site-hub__icon-button.components-button{align-items:center;background:#1e1e1e;border-radius:0;color:#fff;display:flex;height:var(--wpds-spacing-160,64px);justify-content:center;overflow:hidden;padding:0;position:relative;view-transition-name:toggle;width:var(--wpds-spacing-160,64px)}.site-hub__icon-button.components-button:active,.site-hub__icon-button.components-button:hover{color:#fff}.site-hub__icon-button.components-button:focus,.site-hub__icon-button.components-button:focus-visible{box-shadow:0 0 0 3px #1e1e1e,0 0 0 6px var(--wp-admin-theme-color);outline:4px solid transparent;outline-offset:4px}.site-hub__icon-button.components-button:before{border-radius:var(--wpds-border-radius-large,8px);bottom:calc(var(--wpds-spacing-20, 8px) + var(--wpds-border-width-focus, 1px));box-shadow:none;content:"";display:block;left:calc(var(--wpds-spacing-40, 16px) + var(--wpds-border-width-focus, 1px));position:absolute;right:calc(var(--wpds-spacing-20, 8px) + var(--wpds-border-width-focus, 1px));top:calc(var(--wpds-spacing-20, 8px) + var(--wpds-border-width-focus, 1px))}@media not (prefers-reduced-motion){.site-hub__icon-button.components-button:before{transition:box-shadow .1s ease}}.site-hub__icon-button.components-button .site-icon{height:var(--wpds-spacing-160,64px);width:var(--wpds-spacing-160,64px)}.site-hub__icon-button.components-button .site-icon__icon{height:100%;width:100%}.site-hub__title .components-button{color:#e0e0e0;display:block;flex-grow:1;font-size:var(--wpds-font-size-medium,15px);font-weight:var(--wpds-font-weight-medium,500);margin-left:-4px;overflow:hidden;padding-right:var(--wpds-spacing-40,16px);position:relative;text-decoration:none;text-overflow:ellipsis;white-space:nowrap}.site-hub__title .components-button:active,.site-hub__title .components-button:focus,.site-hub__title .components-button:hover{color:#e0e0e0}.site-hub__title .components-button:focus{box-shadow:none;outline:none}.site-hub__title .components-button:focus-visible{box-shadow:0 0 0 var(--wp-admin-border-width-focus) #1e1e1e,0 0 0 calc(var(--wp-admin-border-width-focus)*2) var(--wp-admin-theme-color);outline:2px solid transparent;outline-offset:2px}.site-hub__title .components-button:after{content:"↗";font-weight:400;opacity:0;position:absolute;right:0}@media not (prefers-reduced-motion){.site-hub__title .components-button:after{transition:opacity .1s linear}}.site-hub__title .components-button:active:after,.site-hub__title .components-button:focus:after,.site-hub__title .components-button:hover:after{opacity:1}.site-hub__command-button{color:#e0e0e0}.site-hub__command-button:active svg,.site-hub__command-button:hover svg{fill:#f0f0f0}.site-hub-mobile{align-items:center;background:#1e1e1e;border-bottom:1px solid hsla(0,0%,100%,.1);display:flex;flex-shrink:0;height:56px;justify-content:space-between;padding:0 var(--wpds-spacing-30,12px)}.site-hub-mobile__title{color:#fff;font-size:var(--wpds-font-size-medium,16px);font-weight:var(--wpds-font-weight-medium,500)}.site-hub-mobile__button.components-button{color:#e0e0e0}.site-hub-mobile__button.components-button:focus:not(:disabled),.site-hub-mobile__button.components-button:hover:not(:disabled){color:#fff}.site-hub-mobile__button.components-button:focus-visible{box-shadow:0 0 0 var(--wp-admin-border-width-focus) var(--wp-admin-theme-color);outline:2px solid transparent}
|
||||
.activitypub-avatar{background-color:#f0f0f0;border-radius:50%;flex-shrink:0;height:var(--wpds-spacing-120,48px);-o-object-fit:cover;object-fit:cover;width:var(--wpds-spacing-120,48px)}
|
||||
.actor-switcher{align-items:center;color:#e0e0e0;display:flex;min-width:0;padding-left:2px;text-decoration:none;transition:color .2s ease}.actor-switcher:hover:not(:disabled){color:#fff}.actor-switcher__avatar{border-radius:50%;flex-shrink:0;height:32px;width:32px}.actor-switcher__name{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||
.popular-tags{background:transparent!important;border-bottom:none!important;border-left:none!important;border-right:none!important;border-top:1px solid hsla(0,0%,100%,.1);box-shadow:none!important;padding:0 var(--wpds-spacing-30,12px) var(--wpds-spacing-30,12px)}.popular-tags__title{color:#949494;font-size:11px;font-weight:500;letter-spacing:.5px;margin:var(--wpds-spacing-30,12px) 0 var(--wpds-spacing-20,8px) 0;padding:0 var(--wpds-spacing-20,8px);text-transform:uppercase}.popular-tags__loading{color:#ccc;font-size:13px;padding:var(--wpds-spacing-20,8px)}.popular-tags .components-menu-group{background:transparent!important;border:none!important;box-shadow:none!important;margin:0;padding:0}.popular-tags .menu-item{align-items:center;color:#e0e0e0;display:flex;gap:var(--wpds-spacing-20,8px)}.popular-tags .menu-item:hover{background-color:hsla(0,0%,100%,.1);color:#fff}.popular-tags .menu-item.is-pressed{background-color:hsla(0,0%,100%,.15);color:#fff}.popular-tags .menu-item svg{flex-shrink:0}
|
||||
.sidebar{background-color:#1e1e1e;color:#ccc;display:flex;flex:1;flex-direction:column}.sidebar .nav{flex:1;overflow-y:auto;padding:0 var(--wpds-spacing-30,12px)}.sidebar .sidebar-navigation__icon-title{background:#1e1e1e;margin-bottom:var(--wpds-spacing-20,8px);padding-bottom:var(--wpds-spacing-20,8px);padding-top:var(--wpds-spacing-120,48px);position:sticky;top:0}.sidebar .sidebar-navigation__button{color:#e0e0e0;flex-shrink:0}.sidebar .sidebar-navigation__button:focus:not(:disabled){box-shadow:none;outline:none}.sidebar .sidebar-navigation__button:focus-visible:not(:disabled){box-shadow:0 0 0 var(--wpds-border-width-focus,1.5px) var(--wp-admin-theme-color);outline:3px solid transparent}.sidebar .sidebar-navigation__button:focus,.sidebar .sidebar-navigation__button:focus-visible,.sidebar .sidebar-navigation__button:hover:not(:disabled,[aria-disabled=true]),.sidebar .sidebar-navigation__button:not(:disabled,[aria-disabled=true]):active,.sidebar .sidebar-navigation__button[aria-expanded=true]{color:#f0f0f0}.sidebar .sidebar-navigation__title{color:#e0e0e0;flex-grow:1;line-height:var(--wpds-spacing-80,32px);overflow-wrap:break-word}.sidebar .sidebar-description{margin:0 0 var(--wpds-spacing-80,32px) 0;padding:0 var(--wpds-spacing-40,16px)}.sidebar .menu-item{color:#e0e0e0;transition:none!important}.sidebar .menu-item:focus{box-shadow:none!important;outline:none!important}.sidebar .menu-item:focus-visible{box-shadow:0 0 0 var(--wpds-border-width-focus,1.5px) var(--wp-admin-theme-color)!important;outline:3px solid transparent!important}.sidebar .menu-item:hover{color:#fff!important}.sidebar .menu-item:active,.sidebar .menu-item:hover{background-color:hsla(0,0%,100%,.1)!important}.sidebar .menu-item.is-selected,.sidebar .menu-item[aria-pressed=true]{background-color:hsla(0,0%,100%,.15)!important;color:#fff!important}.sidebar .menu-item.is-selected:active,.sidebar .menu-item.is-selected:hover,.sidebar .menu-item[aria-pressed=true]:active,.sidebar .menu-item[aria-pressed=true]:hover{background-color:hsla(0,0%,100%,.15)!important}.sidebar .menu-item svg{margin-right:var(--wpds-spacing-10,8px)}.sidebar .object-types-menu{margin-top:var(--wpds-spacing-10,4px)}.sidebar .object-types{padding:var(--wpds-spacing-20,8px) var(--wpds-spacing-30,12px)}.sidebar .object-types__loading{color:#ccc;font-size:13px}.sidebar .footer{border-top:1px solid hsla(0,0%,100%,.1);padding:var(--wpds-spacing-40,16px)}.sidebar .footer>.components-h-stack{padding:2px 0}.sidebar .footer .footer-settings-button{color:#e0e0e0;flex-shrink:0}.sidebar .footer .footer-settings-button:hover:not(:disabled){color:#fff}
|
||||
#activitypub-app-root>div{height:100%}.app-layout{background-color:#1e1e1e;display:flex;flex-direction:column;height:100%;isolation:isolate;overflow:hidden;width:100%}.sidebar-backdrop{background-color:rgba(0,0,0,.5);cursor:pointer;inset:0;position:fixed;z-index:100002}.app-content{background-color:#1e1e1e;display:flex;flex-grow:1;height:100%;overflow:hidden;width:100%}.app-content.is-mobile,.sidebar-region{flex-direction:column}.sidebar-region{display:flex;flex-shrink:0;height:100%;width:300px}.sidebar-region.is-mobile{background-color:#1e1e1e;bottom:0;box-shadow:2px 0 8px rgba(0,0,0,.3);left:0;max-width:85vw;position:fixed;top:0;width:300px;z-index:100003}.inspector-region,.stage-region{bottom:0;display:flex;flex-direction:column;height:calc(100vh - 56px);left:0;overflow:hidden;position:fixed;right:0;top:56px;width:100vw}@media(min-width:782px){.inspector-region,.stage-region{bottom:auto;height:auto;left:auto;position:static;right:auto;top:auto;width:auto}}.stage-region{z-index:2}@media(min-width:782px){.stage-region{flex:1 1 auto;min-width:0;z-index:auto}}.inspector-region{z-index:3}@media(min-width:782px){.inspector-region{flex:0 0 var(--sw-inspector-width,380px);z-index:auto}}.components-snackbar-list{bottom:20px;left:20px;position:fixed;z-index:100000}@media(max-width:782px){.components-snackbar-list{bottom:10px;left:10px;right:10px}}
|
||||
:root{--sw-sidebar-width:300px;--sw-inspector-width:50%}.activitypub-app-layout{bottom:0;left:0;position:fixed;right:0;top:0}#wpfooter{display:none}
|
||||
@ -1 +0,0 @@
|
||||
<?php return array('dependencies' => array(), 'version' => '574f62b8c694f733dfd4');
|
||||
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
||||
<?php return array('dependencies' => array(), 'version' => '753d276871f3f5b80028');
|
||||
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
<?php return array('dependencies' => array('react-jsx-runtime', 'wp-api-fetch', 'wp-components', 'wp-compose', 'wp-core-data', 'wp-data', 'wp-element', 'wp-i18n'), 'version' => '018706e2c6de03ab04d1');
|
||||
<?php return array('dependencies' => array('react-jsx-runtime', 'wp-api-fetch', 'wp-components', 'wp-compose', 'wp-core-data', 'wp-data', 'wp-element', 'wp-i18n'), 'version' => 'ce9100d5903e06fbd90c');
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
<?php return array('dependencies' => array('react-jsx-runtime', 'wp-components', 'wp-core-data', 'wp-data', 'wp-edit-post', 'wp-editor', 'wp-element', 'wp-i18n', 'wp-plugins', 'wp-primitives', 'wp-url'), 'version' => 'a87121d13575d9f2d232');
|
||||
<?php return array('dependencies' => array('react-jsx-runtime', 'wp-components', 'wp-core-data', 'wp-data', 'wp-edit-post', 'wp-editor', 'wp-element', 'wp-i18n', 'wp-notices', 'wp-plugins', 'wp-primitives', 'wp-url'), 'version' => '274eb3cd8d33a8c91dd5');
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -2,7 +2,7 @@
|
||||
"$schema": "https://schemas.wp.org/trunk/block.json",
|
||||
"name": "activitypub/extra-fields",
|
||||
"apiVersion": 3,
|
||||
"version": "8.3.0",
|
||||
"version": "9.1.0",
|
||||
"title": "Fediverse Extra Fields",
|
||||
"category": "widgets",
|
||||
"description": "Display extra fields from Fediverse user profiles.",
|
||||
|
||||
@ -1 +1 @@
|
||||
<?php return array('dependencies' => array('react-jsx-runtime', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-data', 'wp-dom', 'wp-element', 'wp-i18n'), 'version' => 'c22c615ef19e14341c00');
|
||||
<?php return array('dependencies' => array('react-jsx-runtime', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-data', 'wp-dom', 'wp-element', 'wp-i18n'), 'version' => '0f6193ad9d1a0f8e03bb');
|
||||
|
||||
@ -1,2 +1,2 @@
|
||||
(()=>{"use strict";var e,t={6755(e,t,i){const r=window.wp.blocks,s=window.wp.i18n,n=window.wp.blockEditor,l=window.wp.components,a=window.wp.dom,o=window.wp.data,c=window.wp.element,d=window.wp.apiFetch;var u=i.n(d);function p(){return window._activityPubOptions||{}}const h=window.ReactJSXRuntime,b=JSON.parse('{"UU":"activitypub/extra-fields"}');(0,r.registerBlockType)(b.UU,{edit:function({attributes:e,setAttributes:t,context:i}){const{selectedUser:r,maxFields:d}=e,{postId:b,postType:v}=i??{},[y,f]=(0,c.useState)([]),[x,g]=(0,c.useState)(!1),[w,m]=(0,c.useState)(null),_=(0,o.useSelect)(e=>{if(b&&v){const t=e("core");if(t){const e=t.getEditedEntityRecord?.("postType",v,b)??null;if(e?.author)return e.author;const i=t.getEntityRecord?.("postType",v,b)??null;if(i?.author)return i.author}}const t=e("core/editor");return t&&t.getCurrentPostAttribute?t.getCurrentPostAttribute("author"):null},[b,v]),j="blog"===r?0:"inherit"===r?_||null:r,{namespace:k="activitypub/1.0",profileUrls:U={}}=p(),F="blog"===r?U.blog:U.user,C=(0,n.useBlockProps)({className:"activitypub-extra-fields-block-wrapper"}),O=function({withInherit:e=!1}){const{enabled:t,namespace:i}=p(),[r,n]=(0,c.useState)(!1),{fetchedUsers:l,isLoadingUsers:a}=(0,o.useSelect)(e=>{const{getUsers:i,getIsResolving:r}=e("core");return{fetchedUsers:t?.users?i({capabilities:"activitypub"}):null,isLoadingUsers:!!t?.users&&r("getUsers",[{capabilities:"activitypub"}])}},[t?.users]),d=(0,o.useSelect)(e=>l||a?null:e("core").getCurrentUser(),[l,a]);(0,c.useEffect)(()=>{!l&&!a&&d&&i&&u()({path:`/${i}/actors/${d.id}`,method:"HEAD",headers:{Accept:"application/activity+json"},parse:!1}).then(()=>n(!0)).catch(()=>n(!1))},[l,a,d,i]);const h=(0,c.useMemo)(()=>l||(d&&r?[{id:d.id,name:d.name}]:[]),[l,d,r]);return(0,c.useMemo)(()=>{if(!h.length)return[];const i=[];return t?.blog&&l&&i.push({label:(0,s.__)("Blog","activitypub"),value:"blog"}),e&&t?.users&&l&&i.push({label:(0,s.__)("Dynamic User","activitypub"),value:"inherit"}),h.reduce((e,t)=>(e.push({label:t.name,value:`${t.id}`}),e),i)},[h,t?.blog,t?.users,l,e])}({withInherit:!0});(0,c.useEffect)(()=>{null!==j?(g(!0),m(null),u()({path:`/${k}/actors/${j}`,headers:{Accept:"application/activity+json"}}).then(e=>{const t=(e.attachment||[]).filter(e=>"PropertyValue"===e.type);f(t),g(!1)}).catch(e=>{m(e.message),g(!1)})):f([])},[j,k]);const E=d>0?y.slice(0,d):y,S=(()=>{const t=e.className?.includes("is-style-cards");if(!t)return{};const i=e.style||{},r=e.backgroundColor,s=i.color?.background;return r?{backgroundColor:`var(--wp--preset--color--${r})`}:s?{backgroundColor:s}:{}})(),P=(0,h.jsx)(n.InspectorControls,{children:(0,h.jsxs)(l.PanelBody,{title:(0,s.__)("Settings","activitypub"),initialOpen:!0,children:[(0,h.jsx)(l.SelectControl,{label:(0,s.__)("User","activitypub"),value:r,options:O,onChange:e=>t({selectedUser:e})}),(0,h.jsx)(l.RangeControl,{label:(0,s.__)("Maximum Fields","activitypub"),value:d,onChange:e=>t({maxFields:e}),min:0,max:20,help:(0,s.__)("Limit the number of fields displayed. 0 = show all.","activitypub")})]})});return"inherit"!==r||_?x?(0,h.jsx)("div",{...C,children:(0,h.jsx)(l.Placeholder,{label:(0,s.__)("Fediverse Extra Fields","activitypub"),children:(0,h.jsx)(l.Spinner,{})})}):w?(0,h.jsx)("div",{...C,children:(0,h.jsx)(l.Placeholder,{label:(0,s.__)("Fediverse Extra Fields","activitypub"),children:(0,h.jsx)("p",{children:(0,s.sprintf)(/* translators: %s: Error message */ /* translators: %s: Error message */
|
||||
(0,s.__)("Error loading extra fields: %s","activitypub"),w)})})}):0===E.length?(0,h.jsxs)(h.Fragment,{children:[P,(0,h.jsx)("div",{...C,children:(0,h.jsx)(l.Placeholder,{label:(0,s.__)("Fediverse Extra Fields","activitypub"),children:(0,h.jsxs)("p",{children:[(0,s.__)("No extra fields found.","activitypub")," ",F&&(0,h.jsx)(l.Button,{variant:"link",onClick:()=>{window.location.href=F},children:(0,s.__)("Add fields in your profile settings","activitypub")})]})})})]}):(0,h.jsxs)(h.Fragment,{children:[P,(0,h.jsx)("div",{...C,children:(0,h.jsx)("dl",{className:"activitypub-extra-fields",children:E.map(e=>(0,h.jsxs)("div",{className:"activitypub-extra-field",style:S,children:[(0,h.jsx)("dt",{children:e.name}),(0,h.jsx)("dd",{dangerouslySetInnerHTML:{__html:(0,a.safeHTML)(e.value)}})]},`${e.name}-${e.value}`))})})]}):(0,h.jsxs)(h.Fragment,{children:[P,(0,h.jsx)("div",{...C,children:(0,h.jsx)(l.Placeholder,{label:(0,s.__)("Fediverse Extra Fields","activitypub"),children:(0,h.jsx)("p",{children:(0,s.__)("This block will display extra fields based on the post author when published.","activitypub")})})})]})},save:()=>null})}},i={};function r(e){var s=i[e];if(void 0!==s)return s.exports;var n=i[e]={exports:{}};return t[e](n,n.exports,r),n.exports}r.m=t,e=[],r.O=(t,i,s,n)=>{if(!i){var l=1/0;for(d=0;d<e.length;d++){for(var[i,s,n]=e[d],a=!0,o=0;o<i.length;o++)(!1&n||l>=n)&&Object.keys(r.O).every(e=>r.O[e](i[o]))?i.splice(o--,1):(a=!1,n<l&&(l=n));if(a){e.splice(d--,1);var c=s();void 0!==c&&(t=c)}}return t}n=n||0;for(var d=e.length;d>0&&e[d-1][2]>n;d--)e[d]=e[d-1];e[d]=[i,s,n]},r.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return r.d(t,{a:t}),t},r.d=(e,t)=>{for(var i in t)r.o(t,i)&&!r.o(e,i)&&Object.defineProperty(e,i,{enumerable:!0,get:t[i]})},r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),(()=>{var e={914:0,678:0};r.O.j=t=>0===e[t];var t=(t,i)=>{var s,n,[l,a,o]=i,c=0;if(l.some(t=>0!==e[t])){for(s in a)r.o(a,s)&&(r.m[s]=a[s]);if(o)var d=o(r)}for(t&&t(i);c<l.length;c++)n=l[c],r.o(e,n)&&e[n]&&e[n][0](),e[n]=0;return r.O(d)},i=globalThis.webpackChunkwordpress_activitypub=globalThis.webpackChunkwordpress_activitypub||[];i.forEach(t.bind(null,0)),i.push=t.bind(null,i.push.bind(i))})();var s=r.O(void 0,[678],()=>r(6755));s=r.O(s)})();
|
||||
(()=>{"use strict";var e={755(e,t,i){const r=window.wp.blocks,s=window.wp.i18n,n=window.wp.blockEditor,l=window.wp.components,a=window.wp.dom,o=window.wp.data,c=window.wp.element,d=window.wp.apiFetch;var u=i.n(d);function p(){return window._activityPubOptions||{}}const h=window.ReactJSXRuntime,b=JSON.parse('{"UU":"activitypub/extra-fields"}');(0,r.registerBlockType)(b.UU,{edit:function({attributes:e,setAttributes:t,context:i}){const{selectedUser:r,maxFields:d}=e,{postId:b,postType:v}=i??{},[f,y]=(0,c.useState)([]),[x,g]=(0,c.useState)(!1),[m,w]=(0,c.useState)(null),_=(0,o.useSelect)(e=>{if(b&&v){const t=e("core");if(t){const e=t.getEditedEntityRecord?.("postType",v,b)??null;if(e?.author)return e.author;const i=t.getEntityRecord?.("postType",v,b)??null;if(i?.author)return i.author}}const t=e("core/editor");return t&&t.getCurrentPostAttribute?t.getCurrentPostAttribute("author"):null},[b,v]),j="blog"===r?0:"inherit"===r?_||null:r,{namespace:O="activitypub/1.0",profileUrls:U={}}=p(),k="blog"===r?U.blog:U.user,F=(0,n.useBlockProps)({className:"activitypub-extra-fields-block-wrapper"}),C=function({withInherit:e=!1}){const{enabled:t,namespace:i}=p(),[r,n]=(0,c.useState)(!1),{fetchedUsers:l,isLoadingUsers:a}=(0,o.useSelect)(e=>{const{getUsers:i,getIsResolving:r}=e("core");return{fetchedUsers:t?.users?i({capabilities:"activitypub"}):null,isLoadingUsers:!!t?.users&&r("getUsers",[{capabilities:"activitypub"}])}},[t?.users]),d=(0,o.useSelect)(e=>l||a?null:e("core").getCurrentUser(),[l,a]);(0,c.useEffect)(()=>{!l&&!a&&d&&i&&u()({path:`/${i}/actors/${d.id}`,method:"HEAD",headers:{Accept:"application/activity+json"},parse:!1}).then(()=>n(!0)).catch(()=>n(!1))},[l,a,d,i]);const h=(0,c.useMemo)(()=>l||(d&&r?[{id:d.id,name:d.name}]:[]),[l,d,r]);return(0,c.useMemo)(()=>{if(!h.length)return[];const i=[];return t?.blog&&l&&i.push({label:(0,s.__)("Blog","activitypub"),value:"blog"}),e&&t?.users&&l&&i.push({label:(0,s.__)("Dynamic User","activitypub"),value:"inherit"}),h.reduce((e,t)=>(e.push({label:t.name,value:`${t.id}`}),e),i)},[h,t?.blog,t?.users,l,e])}({withInherit:!0});(0,c.useEffect)(()=>{null!==j?(g(!0),w(null),u()({path:`/${O}/actors/${j}`,headers:{Accept:"application/activity+json"}}).then(e=>{const t=(e.attachment||[]).filter(e=>"PropertyValue"===e.type);y(t),g(!1)}).catch(e=>{w(e.message),g(!1)})):y([])},[j,O]);const E=d>0?f.slice(0,d):f,P=(()=>{const t=e.className?.includes("is-style-cards");if(!t)return{};const i=e.style||{},r=e.backgroundColor,s=i.color?.background;return r?{backgroundColor:`var(--wp--preset--color--${r})`}:s?{backgroundColor:s}:{}})(),S=(0,h.jsx)(n.InspectorControls,{children:(0,h.jsxs)(l.PanelBody,{title:(0,s.__)("Settings","activitypub"),initialOpen:!0,children:[(0,h.jsx)(l.SelectControl,{label:(0,s.__)("User","activitypub"),value:r,options:C,onChange:e=>t({selectedUser:e})}),(0,h.jsx)(l.RangeControl,{label:(0,s.__)("Maximum Fields","activitypub"),value:d,onChange:e=>t({maxFields:e}),min:0,max:20,help:(0,s.__)("Limit the number of fields displayed. 0 = show all.","activitypub")})]})});return"inherit"!==r||_?x?(0,h.jsx)("div",{...F,children:(0,h.jsx)(l.Placeholder,{label:(0,s.__)("Fediverse Extra Fields","activitypub"),children:(0,h.jsx)(l.Spinner,{})})}):m?(0,h.jsx)("div",{...F,children:(0,h.jsx)(l.Placeholder,{label:(0,s.__)("Fediverse Extra Fields","activitypub"),children:(0,h.jsx)("p",{children:(0,s.sprintf)(/* translators: %s: Error message */ /* translators: %s: Error message */
|
||||
(0,s.__)("Error loading extra fields: %s","activitypub"),m)})})}):0===E.length?(0,h.jsxs)(h.Fragment,{children:[S,(0,h.jsx)("div",{...F,children:(0,h.jsx)(l.Placeholder,{label:(0,s.__)("Fediverse Extra Fields","activitypub"),children:(0,h.jsxs)("p",{children:[(0,s.__)("No extra fields found.","activitypub")," ",k&&(0,h.jsx)(l.Button,{variant:"link",onClick:()=>{window.location.href=k},children:(0,s.__)("Add fields in your profile settings","activitypub")})]})})})]}):(0,h.jsxs)(h.Fragment,{children:[S,(0,h.jsx)("div",{...F,children:(0,h.jsx)("dl",{className:"activitypub-extra-fields",children:E.map(e=>(0,h.jsxs)("div",{className:"activitypub-extra-field",style:P,children:[(0,h.jsx)("dt",{children:e.name}),(0,h.jsx)("dd",{dangerouslySetInnerHTML:{__html:(0,a.safeHTML)(e.value)}})]},`${e.name}-${e.value}`))})})]}):(0,h.jsxs)(h.Fragment,{children:[S,(0,h.jsx)("div",{...F,children:(0,h.jsx)(l.Placeholder,{label:(0,s.__)("Fediverse Extra Fields","activitypub"),children:(0,h.jsx)("p",{children:(0,s.__)("This block will display extra fields based on the post author when published.","activitypub")})})})]})},save:()=>null})}};const t={};function i(r){const s=t[r];if(void 0!==s)return s.exports;const n=t[r]={exports:{}};return e[r](n,n.exports,i),n.exports}i.m=e,(()=>{const e=[];i.O=(t,r,s,n)=>{if(r){n=n||0;for(var l=e.length;l>0&&e[l-1][2]>n;l--)e[l]=e[l-1];return void(e[l]=[r,s,n])}let a=1/0;for(l=0;l<e.length;l++){let[r,s,n]=e[l],c=!0;for(var o=0;o<r.length;o++)(!1&n||a>=n)&&Object.keys(i.O).every(e=>i.O[e](r[o]))?r.splice(o--,1):(c=!1,n<a&&(a=n));if(c){e.splice(l--,1);const i=s();void 0!==i&&(t=i)}}return t}})(),i.n=e=>{const t=e&&e.__esModule?()=>e.default:()=>e;return i.d(t,{a:t}),t},i.d=(e,t)=>{if(Array.isArray(t))for(var r=0;r<t.length;){var s=t[r++],n=t[r++];i.o(e,s)?0===n&&r++:0===n?Object.defineProperty(e,s,{enumerable:!0,value:t[r++]}):Object.defineProperty(e,s,{enumerable:!0,get:n})}else for(var s in t)i.o(t,s)&&!i.o(e,s)&&Object.defineProperty(e,s,{enumerable:!0,get:t[s]})},i.o=(e,t)=>Object.hasOwn(e,t),(()=>{const e={914:0,678:0};i.O.j=t=>0===e[t];const t=(t,r)=>{let[s,n,l]=r;var a,o,c=0;if(s.some(t=>0!==e[t])){for(a in n)i.o(n,a)&&(i.m[a]=n[a]);if(l)var d=l(i)}for(t&&t(r);c<s.length;c++)o=s[c],i.o(e,o)&&e[o]&&e[o][0](),e[o]=0;return i.O(d)},r=globalThis.webpackChunkwordpress_activitypub||=[];r.forEach(t.bind(null,0)),r.push=t.bind(null,r.push.bind(r))})();let r=i.O(void 0,[678],()=>i(755));r=i.O(r)})();
|
||||
@ -2,7 +2,7 @@
|
||||
"$schema": "https://schemas.wp.org/trunk/block.json",
|
||||
"name": "activitypub/follow-me",
|
||||
"apiVersion": 3,
|
||||
"version": "8.3.0",
|
||||
"version": "9.1.0",
|
||||
"title": "Follow me on the Fediverse",
|
||||
"category": "widgets",
|
||||
"description": "Display your Fediverse profile so that visitors can follow you.",
|
||||
|
||||
@ -1 +1 @@
|
||||
<?php return array('dependencies' => array('react-jsx-runtime', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-core-data', 'wp-data', 'wp-dom', 'wp-element', 'wp-i18n', 'wp-primitives'), 'version' => 'a7a6cb736043a8fe087f');
|
||||
<?php return array('dependencies' => array('react-jsx-runtime', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-core-data', 'wp-data', 'wp-dom', 'wp-element', 'wp-i18n', 'wp-primitives'), 'version' => 'd9b2734e3ace0db0581c');
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -2,7 +2,7 @@
|
||||
"$schema": "https://schemas.wp.org/trunk/block.json",
|
||||
"name": "activitypub/followers",
|
||||
"apiVersion": 3,
|
||||
"version": "8.3.0",
|
||||
"version": "9.1.0",
|
||||
"title": "Fediverse Followers",
|
||||
"category": "widgets",
|
||||
"description": "Display your followers from the Fediverse on your website.",
|
||||
|
||||
@ -1 +1 @@
|
||||
<?php return array('dependencies' => array('react-jsx-runtime', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-core-data', 'wp-data', 'wp-element', 'wp-i18n', 'wp-primitives', 'wp-url'), 'version' => 'da88cfc334f994481a92');
|
||||
<?php return array('dependencies' => array('react-jsx-runtime', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-core-data', 'wp-data', 'wp-element', 'wp-i18n', 'wp-primitives', 'wp-url'), 'version' => '176eb7cf33243aa43546');
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -2,7 +2,7 @@
|
||||
"$schema": "https://schemas.wp.org/trunk/block.json",
|
||||
"name": "activitypub/following",
|
||||
"apiVersion": 3,
|
||||
"version": "8.3.0",
|
||||
"version": "9.1.0",
|
||||
"title": "Fediverse Following",
|
||||
"category": "widgets",
|
||||
"description": "Display the accounts you follow in the Fediverse on your website.",
|
||||
|
||||
@ -1 +1 @@
|
||||
<?php return array('dependencies' => array('react-jsx-runtime', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-core-data', 'wp-data', 'wp-element', 'wp-i18n', 'wp-primitives', 'wp-url'), 'version' => '579447d958b5f1081d10');
|
||||
<?php return array('dependencies' => array('react-jsx-runtime', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-core-data', 'wp-data', 'wp-element', 'wp-i18n', 'wp-primitives', 'wp-url'), 'version' => '1930abe82fb42731ebb4');
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -2,7 +2,7 @@
|
||||
"$schema": "https://schemas.wp.org/trunk/block.json",
|
||||
"name": "activitypub/posts-and-replies",
|
||||
"apiVersion": 3,
|
||||
"version": "unreleased",
|
||||
"version": "9.1.0",
|
||||
"title": "Posts and Replies",
|
||||
"category": "widgets",
|
||||
"description": "Display a tab bar to filter between posts only and posts with replies on author archives.",
|
||||
|
||||
@ -1 +1 @@
|
||||
<?php return array('dependencies' => array('react-jsx-runtime', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-i18n'), 'version' => 'c8dfeeece9537c9d6183');
|
||||
<?php return array('dependencies' => array('react-jsx-runtime', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-i18n'), 'version' => '1547e16f5dd8e2ae2d88');
|
||||
|
||||
@ -1 +1 @@
|
||||
(()=>{"use strict";var e,t={5879(){const e=window.wp.blocks,t=window.wp.blockEditor,i=window.wp.components,s=window.wp.i18n,r=window.ReactJSXRuntime,o=JSON.parse('{"$schema":"https://schemas.wp.org/trunk/block.json","name":"activitypub/posts-and-replies","apiVersion":3,"version":"unreleased","title":"Posts and Replies","category":"widgets","description":"Display a tab bar to filter between posts only and posts with replies on author archives.","textdomain":"activitypub","icon":"admin-post","keywords":["fediverse","activitypub","posts","replies","tabs"],"supports":{"html":false},"editorScript":"file:./index.js","style":["file:./style-index.css"],"render":"file:./render.php"}');(0,e.registerBlockType)(o,{edit:function(){const e=(0,t.useBlockProps)();return(0,r.jsx)("div",{...e,children:(0,r.jsx)(i.Placeholder,{icon:"admin-post",label:(0,s.__)("Posts and Replies","activitypub"),instructions:(0,s.__)('Displays a tab bar to filter between "Posts" (excluding replies) and "Posts & Replies" on author archives. Place above a Query Loop block with "Inherit query from template" enabled.',"activitypub")})})},save:()=>null})}},i={};function s(e){var r=i[e];if(void 0!==r)return r.exports;var o=i[e]={exports:{}};return t[e](o,o.exports,s),o.exports}s.m=t,e=[],s.O=(t,i,r,o)=>{if(!i){var n=1/0;for(c=0;c<e.length;c++){for(var[i,r,o]=e[c],a=!0,p=0;p<i.length;p++)(!1&o||n>=o)&&Object.keys(s.O).every(e=>s.O[e](i[p]))?i.splice(p--,1):(a=!1,o<n&&(n=o));if(a){e.splice(c--,1);var l=r();void 0!==l&&(t=l)}}return t}o=o||0;for(var c=e.length;c>0&&e[c-1][2]>o;c--)e[c]=e[c-1];e[c]=[i,r,o]},s.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),(()=>{var e={642:0,6:0};s.O.j=t=>0===e[t];var t=(t,i)=>{var r,o,[n,a,p]=i,l=0;if(n.some(t=>0!==e[t])){for(r in a)s.o(a,r)&&(s.m[r]=a[r]);if(p)var c=p(s)}for(t&&t(i);l<n.length;l++)o=n[l],s.o(e,o)&&e[o]&&e[o][0](),e[o]=0;return s.O(c)},i=globalThis.webpackChunkwordpress_activitypub=globalThis.webpackChunkwordpress_activitypub||[];i.forEach(t.bind(null,0)),i.push=t.bind(null,i.push.bind(i))})();var r=s.O(void 0,[6],()=>s(5879));r=s.O(r)})();
|
||||
(()=>{"use strict";var e={879(){const e=window.wp.blocks,t=window.wp.blockEditor,s=window.wp.components,i=window.wp.i18n,o=window.ReactJSXRuntime,n=JSON.parse('{"$schema":"https://schemas.wp.org/trunk/block.json","name":"activitypub/posts-and-replies","apiVersion":3,"version":"9.0.1","title":"Posts and Replies","category":"widgets","description":"Display a tab bar to filter between posts only and posts with replies on author archives.","textdomain":"activitypub","icon":"admin-post","keywords":["fediverse","activitypub","posts","replies","tabs"],"supports":{"html":false},"editorScript":"file:./index.js","style":["file:./style-index.css"],"render":"file:./render.php"}');(0,e.registerBlockType)(n,{edit:function(){const e=(0,t.useBlockProps)();return(0,o.jsx)("div",{...e,children:(0,o.jsx)(s.Placeholder,{icon:"admin-post",label:(0,i.__)("Posts and Replies","activitypub"),instructions:(0,i.__)('Displays a tab bar to filter between "Posts" (excluding replies) and "Posts & Replies" on author archives. Place above a Query Loop block with "Inherit query from template" enabled.',"activitypub")})})},save:()=>null})}};const t={};function s(i){const o=t[i];if(void 0!==o)return o.exports;const n=t[i]={exports:{}};return e[i](n,n.exports,s),n.exports}s.m=e,(()=>{const e=[];s.O=(t,i,o,n)=>{if(i){n=n||0;for(var r=e.length;r>0&&e[r-1][2]>n;r--)e[r]=e[r-1];return void(e[r]=[i,o,n])}let l=1/0;for(r=0;r<e.length;r++){let[i,o,n]=e[r],p=!0;for(var a=0;a<i.length;a++)(!1&n||l>=n)&&Object.keys(s.O).every(e=>s.O[e](i[a]))?i.splice(a--,1):(p=!1,n<l&&(l=n));if(p){e.splice(r--,1);const s=o();void 0!==s&&(t=s)}}return t}})(),s.o=(e,t)=>Object.hasOwn(e,t),(()=>{const e={642:0,6:0};s.O.j=t=>0===e[t];const t=(t,i)=>{let[o,n,r]=i;var l,a,p=0;if(o.some(t=>0!==e[t])){for(l in n)s.o(n,l)&&(s.m[l]=n[l]);if(r)var c=r(s)}for(t&&t(i);p<o.length;p++)a=o[p],s.o(e,a)&&e[a]&&e[a][0](),e[a]=0;return s.O(c)},i=globalThis.webpackChunkwordpress_activitypub||=[];i.forEach(t.bind(null,0)),i.push=t.bind(null,i.push.bind(i))})();let i=s.O(void 0,[6],()=>s(879));i=s.O(i)})();
|
||||
@ -2,7 +2,7 @@
|
||||
"$schema": "https://schemas.wp.org/trunk/block.json",
|
||||
"name": "activitypub/reactions",
|
||||
"apiVersion": 3,
|
||||
"version": "8.3.0",
|
||||
"version": "9.1.0",
|
||||
"title": "Fediverse Reactions",
|
||||
"category": "widgets",
|
||||
"icon": "heart",
|
||||
|
||||
@ -1 +1 @@
|
||||
<?php return array('dependencies' => array('react-jsx-runtime', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-data', 'wp-element', 'wp-i18n'), 'version' => '1e13568c338d5be171c1');
|
||||
<?php return array('dependencies' => array('react-jsx-runtime', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-data', 'wp-element', 'wp-i18n'), 'version' => '6306c831521d83d3a536');
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
(()=>{"use strict";var e,t={2091(e,t,s){const i=window.wp.blocks,r=window.wp.blockEditor,a=window.ReactJSXRuntime,n=[{attributes:{},supports:{html:!1,align:!0,layout:{default:{type:"constrained",orientation:"vertical",justifyContent:"center"}}},save:()=>(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(r.InnerBlocks.Content,{}),(0,a.jsx)("div",{className:"activitypub-reactions-block"})]})},{attributes:{title:{type:"string",default:"Fediverse reactions"}},supports:{html:!1,align:!0,layout:{default:{type:"constrained",orientation:"vertical",justifyContent:"center"}}},save:()=>null,isEligible:({title:e})=>!!e,migrate:({title:e,...t})=>[t,[(0,i.createBlock)("core/heading",{content:e,level:6})]]}];function o(e){var t,s,i="";if("string"==typeof e||"number"==typeof e)i+=e;else if("object"==typeof e)if(Array.isArray(e)){var r=e.length;for(t=0;t<r;t++)e[t]&&(s=o(e[t]))&&(i&&(i+=" "),i+=s)}else for(s in e)e[s]&&(i&&(i+=" "),i+=s);return i}const l=function(){for(var e,t,s=0,i="",r=arguments.length;s<r;s++)(e=arguments[s])&&(t=o(e))&&(i&&(i+=" "),i+=t);return i},c=window.wp.components,p=window.wp.i18n,u=window.wp.data,d=window.wp.element,f=window.wp.apiFetch;var v=s.n(f);function y(){return window._activityPubOptions||{}}const m={like:(0,p.__)("Like","activitypub"),likes:(0,p.__)("Like","activitypub"),repost:(0,p.__)("Repost","activitypub"),reposts:(0,p.__)("Repost","activitypub"),quote:(0,p.__)("Quote","activitypub"),quotes:(0,p.__)("Quote","activitypub")},h=({reactions:e,displayStyle:t})=>{const{defaultAvatarUrl:s}=y();return"compact"===t?null:(0,a.jsx)("ul",{className:"reaction-avatars",children:e.map((e,t)=>{const i=["reaction-avatar"].filter(Boolean).join(" "),r=e.avatar||s;return(0,a.jsx)("li",{children:(0,a.jsx)("a",{href:e.url,target:"_blank",rel:"noopener noreferrer",children:(0,a.jsx)("img",{src:r,alt:e.name,className:i,width:"32",height:"32",onError:e=>{e.target.src=s}})})},t)})})},b=({reactions:e,displayStyle:t})=>{const{defaultAvatarUrl:s}=y();return(0,a.jsx)("ul",{className:"reactions-list",children:e.map((e,i)=>{const r=e.avatar||s;return(0,a.jsx)("li",{className:"reaction-item",children:(0,a.jsxs)("a",{href:e.url,className:"reaction-item",target:"_blank",rel:"noopener noreferrer",children:["facepile"===t&&(0,a.jsx)("img",{src:r,alt:e.name,width:"32",height:"32",onError:e=>{e.target.src=s}}),(0,a.jsx)("span",{className:"reaction-name",children:e.name})]})},i)})})},w=({items:e,label:t,displayStyle:s,showActions:i,actionLabel:r})=>{const[n,o]=(0,d.useState)(!1),[l,p]=(0,d.useState)(null),u=(0,d.useRef)(null),f=e.slice(0,20);return(0,a.jsxs)("div",{className:"reaction-group",ref:u,children:[i&&r&&(0,a.jsx)("button",{className:"reaction-action-button wp-element-button",type:"button",disabled:!0,children:r}),(0,a.jsx)(h,{reactions:f,displayStyle:s}),(0,a.jsx)(c.Button,{ref:p,className:"reaction-label is-link",onClick:()=>o(!n),"aria-expanded":n,children:t}),n&&l&&(0,a.jsx)(c.Popover,{anchor:l,onClose:()=>o(!1),className:"activitypub-popover",children:(0,a.jsx)(b,{reactions:e,displayStyle:s})})]})};function g({postId:e=null,reactions:t=null,fallbackReactions:s=null,displayStyle:i="facepile",showActions:r=!1}){const{namespace:n}=y(),[o,l]=(0,d.useState)(t),[c,p]=(0,d.useState)(!t),u=(0,d.useCallback)(()=>{s&&l(s),p(!1)},[s]);return(0,d.useEffect)(()=>{if(t)return l(t),void p(!1);e&&"number"==typeof e?(p(!0),v()({path:`/${n}/posts/${e}/reactions`}).then(e=>{const t=Object.values(e).some(e=>e.items?.length>0);l(!t&&s?s:e),p(!1)}).catch(u)):u()},[e,t,s,n,u]),c?null:o&&Object.values(o).some(e=>e.items?.length>0)?(0,a.jsx)("div",{className:"activitypub-reactions",children:Object.entries(o).map(([e,t])=>t.items?.length?(0,a.jsx)(w,{items:t.items,label:t.label,displayStyle:i,showActions:r,actionLabel:m[e]},e):null)}):null}const x=(e,t,s,i)=>Array.from({length:e},(e,r)=>({name:`${t} ${r+1}`,url:"#",avatar:`data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Ccircle cx='32' cy='32' r='32' fill='%23${i[r%i.length]}'/%3E%3Ctext x='32' y='38' font-family='sans-serif' font-size='24' fill='white' text-anchor='middle'%3E${String.fromCharCode(s+r)}%3C/text%3E%3C/svg%3E`})),j=["FF6B6B","4ECDC4","45B7D1","96CEB4","D4A5A5","9B59B6","3498DB","E67E22"],k={likes:{label:(0,p.sprintf)(/* translators: %d: Number of likes */ /* translators: %d: Number of likes */
|
||||
(0,p._x)("%d likes","number of likes","activitypub"),9),items:x(9,"User",65,j)},reposts:{label:(0,p.sprintf)(/* translators: %d: Number of reposts */ /* translators: %d: Number of reposts */
|
||||
(0,p._x)("%d reposts","number of reposts","activitypub"),6),items:x(6,"Reposter",82,j)},quotes:{label:(0,p.sprintf)(/* translators: %d: Number of quotes */ /* translators: %d: Number of quotes */
|
||||
(0,p._x)("%d quotes","number of quotes","activitypub"),7),items:x(7,"Quoter",81,j)}},_=JSON.parse('{"$schema":"https://schemas.wp.org/trunk/block.json","name":"activitypub/reactions","apiVersion":3,"version":"8.2.1","title":"Fediverse Reactions","category":"widgets","icon":"heart","keywords":["fediverse","activitypub","likes","reposts"],"description":"Display Fediverse likes and reposts for your posts.","example":{"attributes":{"className":"is-style-facepile"}},"styles":[{"name":"facepile","label":"Facepile","isDefault":true},{"name":"compact","label":"Compact"}],"attributes":{"displayStyle":{"type":"string","default":"facepile"},"showActions":{"type":"boolean","default":false}},"supports":{"align":["wide","full"],"color":{"gradients":true},"__experimentalBorder":{"radius":true,"width":true,"color":true,"style":true},"html":false,"interactivity":true,"shadow":true,"typography":{"fontSize":true,"__experimentalDefaultControls":{"fontSize":true}}},"blockHooks":{"core/post-content":"after"},"textdomain":"activitypub","editorScript":"file:./index.js","style":"file:./style-index.css","viewStyle":"file:./view.css","viewScriptModule":"file:./view.js","viewScript":"wp-api-fetch","render":"file:./render.php"}');(0,i.registerBlockType)(_,{deprecated:n,edit:function({attributes:e,setAttributes:t}){const{className:s="",displayStyle:i="facepile",showActions:n=!1}=e,o=(0,r.useBlockProps)(),{getCurrentPostId:f}=(0,u.select)("core/editor"),{showAvatars:v=!0}=y(),m=(0,d.useRef)(!1);(0,d.useEffect)(()=>{if(m.current)return;m.current=!0;const e=s?.includes("is-style-");if(!e){const e=v?"facepile":"compact";t({className:l(s,`is-style-${e}`),displayStyle:e})}},[s,v,t]);const h=s?.includes("is-style-compact")?"compact":"facepile";(0,d.useEffect)(()=>{h!==i&&t({displayStyle:h})},[h,i,t]);const b=[["core/heading",{level:6,placeholder:(0,p.__)("Fediverse Reactions","activitypub"),content:(0,p.__)("Fediverse Reactions","activitypub")}]];return(0,a.jsxs)(a.Fragment,{children:[(0,a.jsx)(r.InspectorControls,{children:(0,a.jsx)(c.PanelBody,{title:(0,p.__)("Action Buttons","activitypub"),children:(0,a.jsx)(c.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,p.__)("Show action buttons","activitypub"),help:(0,p.__)("Display Like and Boost buttons so visitors can interact from their Fediverse server.","activitypub"),checked:n,onChange:e=>t({showActions:e})})})}),(0,a.jsxs)("div",{...o,children:[(0,a.jsx)(r.InnerBlocks,{template:b,allowedBlocks:["core/heading"],templateLock:"all",renderAppender:!1}),(0,a.jsx)(g,{postId:f(),fallbackReactions:k,displayStyle:i,showActions:n})]})]})},save:function(){return(0,a.jsx)("div",{...r.useBlockProps.save(),children:(0,a.jsx)(r.InnerBlocks.Content,{})})}})}},s={};function i(e){var r=s[e];if(void 0!==r)return r.exports;var a=s[e]={exports:{}};return t[e](a,a.exports,i),a.exports}i.m=t,e=[],i.O=(t,s,r,a)=>{if(!s){var n=1/0;for(p=0;p<e.length;p++){for(var[s,r,a]=e[p],o=!0,l=0;l<s.length;l++)(!1&a||n>=a)&&Object.keys(i.O).every(e=>i.O[e](s[l]))?s.splice(l--,1):(o=!1,a<n&&(n=a));if(o){e.splice(p--,1);var c=r();void 0!==c&&(t=c)}}return t}a=a||0;for(var p=e.length;p>0&&e[p-1][2]>a;p--)e[p]=e[p-1];e[p]=[s,r,a]},i.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return i.d(t,{a:t}),t},i.d=(e,t)=>{for(var s in t)i.o(t,s)&&!i.o(e,s)&&Object.defineProperty(e,s,{enumerable:!0,get:t[s]})},i.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),(()=>{var e={608:0,104:0};i.O.j=t=>0===e[t];var t=(t,s)=>{var r,a,[n,o,l]=s,c=0;if(n.some(t=>0!==e[t])){for(r in o)i.o(o,r)&&(i.m[r]=o[r]);if(l)var p=l(i)}for(t&&t(s);c<n.length;c++)a=n[c],i.o(e,a)&&e[a]&&e[a][0](),e[a]=0;return i.O(p)},s=globalThis.webpackChunkwordpress_activitypub=globalThis.webpackChunkwordpress_activitypub||[];s.forEach(t.bind(null,0)),s.push=t.bind(null,s.push.bind(s))})();var r=i.O(void 0,[104],()=>i(2091));r=i.O(r)})();
|
||||
(()=>{"use strict";var e={91(e,t,s){const i=window.wp.blocks,r=window.wp.blockEditor,n=window.ReactJSXRuntime,a=[{attributes:{},supports:{html:!1,align:!0,layout:{default:{type:"constrained",orientation:"vertical",justifyContent:"center"}}},save:()=>(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(r.InnerBlocks.Content,{}),(0,n.jsx)("div",{className:"activitypub-reactions-block"})]})},{attributes:{title:{type:"string",default:"Fediverse reactions"}},supports:{html:!1,align:!0,layout:{default:{type:"constrained",orientation:"vertical",justifyContent:"center"}}},save:()=>null,isEligible:({title:e})=>!!e,migrate:({title:e,...t})=>[t,[(0,i.createBlock)("core/heading",{content:e,level:6})]]}];function o(e){var t,s,i="";if("string"==typeof e||"number"==typeof e)i+=e;else if("object"==typeof e)if(Array.isArray(e)){var r=e.length;for(t=0;t<r;t++)e[t]&&(s=o(e[t]))&&(i&&(i+=" "),i+=s)}else for(s in e)e[s]&&(i&&(i+=" "),i+=s);return i}const l=function(){for(var e,t,s=0,i="",r=arguments.length;s<r;s++)(e=arguments[s])&&(t=o(e))&&(i&&(i+=" "),i+=t);return i},c=window.wp.components,u=window.wp.i18n,p=window.wp.data,d=window.wp.element,f=window.wp.apiFetch;var y=s.n(f);function v(){return window._activityPubOptions||{}}const m={like:(0,u.__)("Like","activitypub"),likes:(0,u.__)("Like","activitypub"),repost:(0,u.__)("Repost","activitypub"),reposts:(0,u.__)("Repost","activitypub"),quote:(0,u.__)("Quote","activitypub"),quotes:(0,u.__)("Quote","activitypub")},h=({reactions:e,displayStyle:t})=>{const{defaultAvatarUrl:s}=v();return"compact"===t?null:(0,n.jsx)("ul",{className:"reaction-avatars",children:e.map((e,t)=>{const i=["reaction-avatar"].filter(Boolean).join(" "),r=e.avatar||s;return(0,n.jsx)("li",{children:(0,n.jsx)("a",{href:e.url,target:"_blank",rel:"noopener noreferrer",children:(0,n.jsx)("img",{src:r,alt:e.name,className:i,width:"32",height:"32",onError:e=>{e.target.src=s}})})},t)})})},b=({reactions:e,displayStyle:t})=>{const{defaultAvatarUrl:s}=v();return(0,n.jsx)("ul",{className:"reactions-list",children:e.map((e,i)=>{const r=e.avatar||s;return(0,n.jsx)("li",{className:"reaction-item",children:(0,n.jsxs)("a",{href:e.url,className:"reaction-item",target:"_blank",rel:"noopener noreferrer",children:["facepile"===t&&(0,n.jsx)("img",{src:r,alt:e.name,width:"32",height:"32",onError:e=>{e.target.src=s}}),(0,n.jsx)("span",{className:"reaction-name",children:e.name})]})},i)})})},w=({items:e,label:t,displayStyle:s,showActions:i,actionLabel:r})=>{const[a,o]=(0,d.useState)(!1),[l,u]=(0,d.useState)(null),p=(0,d.useRef)(null),f=e.slice(0,20);return(0,n.jsxs)("div",{className:"reaction-group",ref:p,children:[i&&r&&(0,n.jsx)("button",{className:"reaction-action-button wp-element-button",type:"button",disabled:!0,children:r}),(0,n.jsx)(h,{reactions:f,displayStyle:s}),(0,n.jsx)(c.Button,{ref:u,className:"reaction-label is-link",onClick:()=>o(!a),"aria-expanded":a,children:t}),a&&l&&(0,n.jsx)(c.Popover,{anchor:l,onClose:()=>o(!1),className:"activitypub-popover",children:(0,n.jsx)(b,{reactions:e,displayStyle:s})})]})};function g({postId:e=null,reactions:t=null,fallbackReactions:s=null,displayStyle:i="facepile",showActions:r=!1}){const{namespace:a}=v(),[o,l]=(0,d.useState)(t),[c,u]=(0,d.useState)(!t),p=(0,d.useCallback)(()=>{s&&l(s),u(!1)},[s]);return(0,d.useEffect)(()=>{if(t)return l(t),void u(!1);e&&"number"==typeof e?(u(!0),y()({path:`/${a}/posts/${e}/reactions`}).then(e=>{const t=Object.values(e).some(e=>e.items?.length>0);l(!t&&s?s:e),u(!1)}).catch(p)):p()},[e,t,s,a,p]),c?null:o&&Object.values(o).some(e=>e.items?.length>0)?(0,n.jsx)("div",{className:"activitypub-reactions",children:Object.entries(o).map(([e,t])=>t.items?.length?(0,n.jsx)(w,{items:t.items,label:t.label,displayStyle:i,showActions:r,actionLabel:m[e]},e):null)}):null}const x=(e,t,s,i)=>Array.from({length:e},(e,r)=>({name:`${t} ${r+1}`,url:"#",avatar:`data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 64 64'%3E%3Ccircle cx='32' cy='32' r='32' fill='%23${i[r%i.length]}'/%3E%3Ctext x='32' y='38' font-family='sans-serif' font-size='24' fill='white' text-anchor='middle'%3E${String.fromCharCode(s+r)}%3C/text%3E%3C/svg%3E`})),j=["FF6B6B","4ECDC4","45B7D1","96CEB4","D4A5A5","9B59B6","3498DB","E67E22"],k={likes:{label:(0,u.sprintf)(/* translators: %d: Number of likes */ /* translators: %d: Number of likes */
|
||||
(0,u._x)("%d likes","number of likes","activitypub"),9),items:x(9,"User",65,j)},reposts:{label:(0,u.sprintf)(/* translators: %d: Number of reposts */ /* translators: %d: Number of reposts */
|
||||
(0,u._x)("%d reposts","number of reposts","activitypub"),6),items:x(6,"Reposter",82,j)},quotes:{label:(0,u.sprintf)(/* translators: %d: Number of quotes */ /* translators: %d: Number of quotes */
|
||||
(0,u._x)("%d quotes","number of quotes","activitypub"),7),items:x(7,"Quoter",81,j)}},_=JSON.parse('{"$schema":"https://schemas.wp.org/trunk/block.json","name":"activitypub/reactions","apiVersion":3,"version":"9.0.1","title":"Fediverse Reactions","category":"widgets","icon":"heart","keywords":["fediverse","activitypub","likes","reposts"],"description":"Display Fediverse likes and reposts for your posts.","example":{"attributes":{"className":"is-style-facepile"}},"styles":[{"name":"facepile","label":"Facepile","isDefault":true},{"name":"compact","label":"Compact"}],"attributes":{"displayStyle":{"type":"string","default":"facepile"},"showActions":{"type":"boolean","default":false}},"supports":{"align":["wide","full"],"color":{"gradients":true},"__experimentalBorder":{"radius":true,"width":true,"color":true,"style":true},"html":false,"interactivity":true,"shadow":true,"typography":{"fontSize":true,"__experimentalDefaultControls":{"fontSize":true}}},"blockHooks":{"core/post-content":"after"},"textdomain":"activitypub","editorScript":"file:./index.js","style":"file:./style-index.css","viewStyle":"file:./view.css","viewScriptModule":"file:./view.js","viewScript":"wp-api-fetch","render":"file:./render.php"}');(0,i.registerBlockType)(_,{deprecated:a,edit:function({attributes:e,setAttributes:t}){const{className:s="",displayStyle:i="facepile",showActions:a=!1}=e,o=(0,r.useBlockProps)(),{getCurrentPostId:f}=(0,p.select)("core/editor"),{showAvatars:y=!0}=v(),m=(0,d.useRef)(!1);(0,d.useEffect)(()=>{if(m.current)return;m.current=!0;const e=s?.includes("is-style-");if(!e){const e=y?"facepile":"compact";t({className:l(s,`is-style-${e}`),displayStyle:e})}},[s,y,t]);const h=s?.includes("is-style-compact")?"compact":"facepile";(0,d.useEffect)(()=>{h!==i&&t({displayStyle:h})},[h,i,t]);const b=[["core/heading",{level:6,placeholder:(0,u.__)("Fediverse Reactions","activitypub"),content:(0,u.__)("Fediverse Reactions","activitypub")}]];return(0,n.jsxs)(n.Fragment,{children:[(0,n.jsx)(r.InspectorControls,{children:(0,n.jsx)(c.PanelBody,{title:(0,u.__)("Action Buttons","activitypub"),children:(0,n.jsx)(c.ToggleControl,{__nextHasNoMarginBottom:!0,label:(0,u.__)("Show action buttons","activitypub"),help:(0,u.__)("Display Like and Boost buttons so visitors can interact from their Fediverse server.","activitypub"),checked:a,onChange:e=>t({showActions:e})})})}),(0,n.jsxs)("div",{...o,children:[(0,n.jsx)(r.InnerBlocks,{template:b,allowedBlocks:["core/heading"],templateLock:"all",renderAppender:!1}),(0,n.jsx)(g,{postId:f(),fallbackReactions:k,displayStyle:i,showActions:a})]})]})},save:function(){return(0,n.jsx)("div",{...r.useBlockProps.save(),children:(0,n.jsx)(r.InnerBlocks.Content,{})})}})}};const t={};function s(i){const r=t[i];if(void 0!==r)return r.exports;const n=t[i]={exports:{}};return e[i](n,n.exports,s),n.exports}s.m=e,(()=>{const e=[];s.O=(t,i,r,n)=>{if(i){n=n||0;for(var a=e.length;a>0&&e[a-1][2]>n;a--)e[a]=e[a-1];return void(e[a]=[i,r,n])}let o=1/0;for(a=0;a<e.length;a++){let[i,r,n]=e[a],c=!0;for(var l=0;l<i.length;l++)(!1&n||o>=n)&&Object.keys(s.O).every(e=>s.O[e](i[l]))?i.splice(l--,1):(c=!1,n<o&&(o=n));if(c){e.splice(a--,1);const s=r();void 0!==s&&(t=s)}}return t}})(),s.n=e=>{const t=e&&e.__esModule?()=>e.default:()=>e;return s.d(t,{a:t}),t},s.d=(e,t)=>{if(Array.isArray(t))for(var i=0;i<t.length;){var r=t[i++],n=t[i++];s.o(e,r)?0===n&&i++:0===n?Object.defineProperty(e,r,{enumerable:!0,value:t[i++]}):Object.defineProperty(e,r,{enumerable:!0,get:n})}else for(var r in t)s.o(t,r)&&!s.o(e,r)&&Object.defineProperty(e,r,{enumerable:!0,get:t[r]})},s.o=(e,t)=>Object.hasOwn(e,t),(()=>{const e={608:0,104:0};s.O.j=t=>0===e[t];const t=(t,i)=>{let[r,n,a]=i;var o,l,c=0;if(r.some(t=>0!==e[t])){for(o in n)s.o(n,o)&&(s.m[o]=n[o]);if(a)var u=a(s)}for(t&&t(i);c<r.length;c++)l=r[c],s.o(e,l)&&e[l]&&e[l][0](),e[l]=0;return s.O(u)},i=globalThis.webpackChunkwordpress_activitypub||=[];i.forEach(t.bind(null,0)),i.push=t.bind(null,i.push.bind(i))})();let i=s.O(void 0,[104],()=>s(91));i=s.O(i)})();
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -2,7 +2,7 @@
|
||||
"$schema": "https://schemas.wp.org/trunk/block.json",
|
||||
"name": "activitypub/remote-reply",
|
||||
"apiVersion": 3,
|
||||
"version": "8.3.0",
|
||||
"version": "9.1.0",
|
||||
"title": "Reply on the Fediverse",
|
||||
"category": "widgets",
|
||||
"description": "Allow visitors to reply to your posts from their Fediverse account.",
|
||||
|
||||
@ -1 +1 @@
|
||||
body.modal-open{overflow:hidden}.activitypub-modal__overlay{--activitypub-modal-background-color:var(--wp--preset--color--base,var(--wp--preset--color--background,#fff));--activitypub-modal-border-color:var(--wp--preset--color--contrast-3,var(--wp--preset--color--light-gray,#e2e4e7));--activitypub-modal-radius:var(--wp--custom--border--radius--medium,8px);--activitypub-modal-shadow:var(--wp--preset--shadow--natural,0 5px 15px rgba(0,0,0,.3));--activitypub-modal-compact-shadow:var(--wp--preset--shadow--natural,0 2px 8px rgba(0,0,0,.1));--activitypub-modal-text-color:var(--wp--preset--color--contrast,var(--wp--preset--color--foreground,inherit));align-items:center;background-color:rgba(0,0,0,.5);bottom:0;color:var(--activitypub-modal-text-color);display:flex;justify-content:center;left:0;margin:0!important;max-width:none!important;padding:1rem;position:fixed;right:0;top:0;z-index:100000}.activitypub-modal__overlay.compact{align-items:flex-start;background-color:transparent;bottom:auto;justify-content:flex-start;left:auto;padding:0;position:absolute;right:auto;top:auto;z-index:100}.activitypub-modal__overlay[hidden]{display:none}.activitypub-modal__frame{animation:activitypub-modal-appear .2s ease-out;background-color:var(--activitypub-modal-background-color);border:1px solid var(--activitypub-modal-border-color);border-radius:var(--activitypub-modal-radius);box-shadow:var(--activitypub-modal-shadow);box-sizing:border-box;color:var(--wp--preset--color--contrast,#1e1e1e);display:flex;flex-direction:column;font-size:inherit;max-height:calc(100vh - 2rem);max-width:660px;overflow:hidden;width:100%}.compact .activitypub-modal__frame{box-shadow:var(--activitypub-modal-compact-shadow);max-height:300px;max-width:-moz-min-content;max-width:min-content;min-width:250px;width:auto}.activitypub-modal__header{align-items:center;border-bottom:1px solid var(--activitypub-modal-border-color);display:flex;flex-shrink:0;justify-content:space-between;padding:2rem 2rem 1.5rem}.compact .activitypub-modal__header{display:none}.activitypub-modal__header .activitypub-modal__close{align-items:center;background:transparent;border:none;border-radius:var(--activitypub-modal-radius);color:inherit;cursor:pointer;display:flex;justify-content:center;padding:.5rem;width:auto}.activitypub-modal__header .activitypub-modal__close:active{border:none;padding:.5rem}.activitypub-modal__title{font-size:130%;font-weight:600;line-height:1.4;margin:0!important}.activitypub-modal__content{overflow-y:auto}.activitypub-dialog__section{border-bottom:1px solid var(--activitypub-modal-border-color,var(--wp--preset--color--light-gray,#f0f0f0));padding:1.5rem 2rem}.activitypub-dialog__section:last-child{border-bottom:none;padding-bottom:2rem}.activitypub-dialog__section h4{font-size:110%;margin-bottom:.5rem;margin-top:0}.activitypub-dialog__description{color:inherit;margin-bottom:1rem}.activitypub-dialog__button-group{align-items:center;display:flex;gap:.5rem;margin-bottom:.5rem;width:100%}.activitypub-dialog__button-group input[type]{background-color:var(--activitypub-modal-background-color,#fff)!important;border:1px solid var(--activitypub-modal-border-color,#949494)!important;border-radius:0;box-sizing:border-box;color:inherit!important;flex:1;font-family:inherit;font-size:1em;line-height:1;margin:0;min-width:0;padding:calc(.667em + 2px)!important}.activitypub-dialog__button-group input[type]::-moz-placeholder{opacity:.5}.activitypub-dialog__button-group input[type]::placeholder{opacity:.5}.activitypub-dialog__button-group input[type][aria-invalid=true]{border-color:var(--wp--preset--color--vivid-red,#b32d2e)!important}.activitypub-dialog__button-group button{background-color:var(--wp--preset--color--contrast,#1e1e1e);border:none;box-sizing:border-box;color:var(--wp--preset--color--base,#fff);cursor:pointer;font-family:inherit;font-size:1em;line-height:1;min-width:22.5%;padding:calc(.667em + 2px) 1.5em;width:auto}.activitypub-dialog__error{color:var(--wp--preset--color--vivid-red,#b32d2e);font-size:90%;margin-top:.5rem}.activitypub-dialog__remember{font-size:90%;margin-top:1rem}.activitypub-dialog__remember label{align-items:center;display:flex;gap:.5rem}.activitypub-dialog__remember input[type=checkbox]{margin:0;position:relative;top:0}@keyframes activitypub-modal-appear{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.activitypub-remote-reply{margin-top:24px}.activitypub-remote-reply .activitypub-remote-profile{align-items:center;display:flex;gap:1rem}.activitypub-remote-reply .activitypub-remote-profile__close{align-items:center;border-radius:50%;display:inline-flex;height:24px;justify-content:center;line-height:1;padding:0;width:24px}.activitypub-remote-reply .activitypub-remote-profile__link,.activitypub-remote-reply__link{cursor:pointer;display:inline-block;margin:0!important}.activitypub-remote-profile[hidden],.activitypub-remote-reply__link[hidden]{display:none!important}
|
||||
body.modal-open{overflow:hidden}.activitypub-modal__overlay{--activitypub-modal-background-color:var(--wp--preset--color--base,var(--wp--preset--color--background,#fff));--activitypub-modal-border-color:var(--wp--preset--color--contrast-3,var(--wp--preset--color--light-gray,#e2e4e7));--activitypub-modal-radius:var(--wp--custom--border--radius--medium,8px);--activitypub-modal-shadow:var(--wp--preset--shadow--natural,0 5px 15px rgba(0,0,0,.3));--activitypub-modal-compact-shadow:var(--wp--preset--shadow--natural,0 2px 8px rgba(0,0,0,.1));--activitypub-modal-text-color:var(--wp--preset--color--contrast,var(--wp--preset--color--foreground,inherit));align-items:center;background-color:rgba(0,0,0,.5);bottom:0;color:var(--activitypub-modal-text-color);display:flex;justify-content:center;left:0;margin:0!important;max-width:none!important;padding:1rem;position:fixed;right:0;top:0;z-index:100000}.activitypub-modal__overlay.compact{align-items:flex-start;background-color:transparent;bottom:auto;justify-content:flex-start;left:auto;padding:0;position:absolute;right:auto;top:auto;z-index:100}.activitypub-modal__overlay[hidden]{display:none}.activitypub-modal__frame{animation:activitypub-modal-appear .2s ease-out;background-color:var(--activitypub-modal-background-color);border:1px solid var(--activitypub-modal-border-color);border-radius:var(--activitypub-modal-radius);box-shadow:var(--activitypub-modal-shadow);box-sizing:border-box;color:var(--wp--preset--color--contrast,#1e1e1e);display:flex;flex-direction:column;font-size:inherit;max-height:calc(100vh - 2rem);max-width:660px;overflow:hidden;width:100%}.compact .activitypub-modal__frame{box-shadow:var(--activitypub-modal-compact-shadow);max-height:300px;max-width:min-content;min-width:250px;width:auto}.activitypub-modal__header{align-items:center;border-bottom:1px solid var(--activitypub-modal-border-color);display:flex;flex-shrink:0;justify-content:space-between;padding:2rem 2rem 1.5rem}.compact .activitypub-modal__header{display:none}.activitypub-modal__header .activitypub-modal__close{align-items:center;background:transparent;border:none;border-radius:var(--activitypub-modal-radius);color:inherit;cursor:pointer;display:flex;justify-content:center;padding:.5rem;width:auto}.activitypub-modal__header .activitypub-modal__close:active{border:none;padding:.5rem}.activitypub-modal__title{font-size:130%;font-weight:600;line-height:1.4;margin:0!important}.activitypub-modal__content{overflow-y:auto}.activitypub-dialog__section{border-bottom:1px solid var(--activitypub-modal-border-color,var(--wp--preset--color--light-gray,#f0f0f0));padding:1.5rem 2rem}.activitypub-dialog__section:last-child{border-bottom:none;padding-bottom:2rem}.activitypub-dialog__section h4{font-size:110%;margin-bottom:.5rem;margin-top:0}.activitypub-dialog__description{color:inherit;margin-bottom:1rem}.activitypub-dialog__button-group{align-items:center;display:flex;gap:.5rem;margin-bottom:.5rem;width:100%}.activitypub-dialog__button-group input[type]{background-color:var(--activitypub-modal-background-color,#fff)!important;border:1px solid var(--activitypub-modal-border-color,#949494)!important;border-radius:0;box-sizing:border-box;color:inherit!important;flex:1;font-family:inherit;font-size:1em;line-height:1;margin:0;min-width:0;padding:calc(.667em + 2px)!important}.activitypub-dialog__button-group input[type]::placeholder{opacity:.5}.activitypub-dialog__button-group input[type][aria-invalid=true]{border-color:var(--wp--preset--color--vivid-red,#b32d2e)!important}.activitypub-dialog__button-group button{background-color:var(--wp--preset--color--contrast,#1e1e1e);border:none;box-sizing:border-box;color:var(--wp--preset--color--base,#fff);cursor:pointer;font-family:inherit;font-size:1em;line-height:1;min-width:22.5%;padding:calc(.667em + 2px) 1.5em;width:auto}.activitypub-dialog__error{color:var(--wp--preset--color--vivid-red,#b32d2e);font-size:90%;margin-top:.5rem}.activitypub-dialog__remember{font-size:90%;margin-top:1rem}.activitypub-dialog__remember label{align-items:center;display:flex;gap:.5rem}.activitypub-dialog__remember input[type=checkbox]{margin:0;position:relative;top:0}@keyframes activitypub-modal-appear{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}.activitypub-remote-reply{margin-top:24px}.activitypub-remote-reply .activitypub-remote-profile{align-items:center;display:flex;gap:1rem}.activitypub-remote-reply .activitypub-remote-profile__close{align-items:center;border-radius:50%;display:inline-flex;height:24px;justify-content:center;line-height:1;padding:0;width:24px}.activitypub-remote-reply .activitypub-remote-profile__link,.activitypub-remote-reply__link{cursor:pointer;display:inline-block;margin:0!important}.activitypub-remote-profile[hidden],.activitypub-remote-reply__link[hidden]{display:none!important}
|
||||
|
||||
@ -1 +1 @@
|
||||
<?php return array('dependencies' => array('@wordpress/interactivity'), 'version' => '283e9946a5209ff839e1', 'type' => 'module');
|
||||
<?php return array('dependencies' => array('@wordpress/interactivity'), 'version' => '773aabd18fa5b71fb3f3', 'type' => 'module');
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -2,7 +2,7 @@
|
||||
"$schema": "https://schemas.wp.org/trunk/block.json",
|
||||
"apiVersion": 3,
|
||||
"name": "activitypub/reply",
|
||||
"version": "8.3.0",
|
||||
"version": "9.1.0",
|
||||
"title": "Federated Reply",
|
||||
"category": "widgets",
|
||||
"icon": "admin-comments",
|
||||
|
||||
@ -1 +1 @@
|
||||
.activitypub-embed-container{min-height:100px;pointer-events:none;position:relative;-webkit-user-select:none;-moz-user-select:none;user-select:none}.activitypub-embed-loading{align-items:center;display:flex;justify-content:center}.activitypub-embed-container .wp-block-embed{pointer-events:none!important}.activitypub-embed-preview,.activitypub-embed-preview iframe{pointer-events:none}.activitypub-reply-display{margin:1em 0}.activitypub-reply-display p{margin:0}.activitypub-reply-display a{color:#2271b1;text-decoration:none}.activitypub-reply-display a:hover{color:#135e96;text-decoration:underline}.activitypub-embed{background:#fff;border:1px solid #e6e6e6;border-radius:12px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif;max-width:100%;padding:0}.activitypub-reply-block .activitypub-embed{margin:1em 0}.activitypub-embed-header{align-items:center;display:flex;gap:10px;padding:15px}.activitypub-embed-header img{border-radius:50%;height:48px;width:48px}.activitypub-embed-header-text{flex-grow:1}.activitypub-embed-header-text h2{color:#000;font-size:15px;font-weight:600;margin:0;padding:0}.activitypub-embed-header-text .ap-account{color:#687684;font-size:14px;text-decoration:none}.activitypub-embed-content{padding:0 15px 15px}.activitypub-embed-content .ap-title{color:#000;font-size:23px;font-weight:600;margin:0 0 10px;padding:0}.activitypub-embed-content .ap-subtitle{color:#000;font-size:15px;margin:0 0 15px}.activitypub-embed-content .ap-preview{border:1px solid #e6e6e6;border-radius:8px;box-sizing:border-box;display:grid;gap:2px;grid-template-columns:1fr 1fr;grid-template-rows:1fr 1fr;margin:1em 0 0;min-height:64px;overflow:hidden;position:relative;width:100%}.activitypub-embed-content .ap-preview img{border:0;box-sizing:border-box;display:block;height:100%;-o-object-fit:cover;object-fit:cover;overflow:hidden;position:relative;width:100%}.activitypub-embed-content .ap-preview audio,.activitypub-embed-content .ap-preview video{display:block;grid-column:1/span 2;max-width:100%}.activitypub-embed-content .ap-preview audio{width:100%}.activitypub-embed-content .ap-preview.layout-1{grid-template-columns:1fr;grid-template-rows:1fr}.activitypub-embed-content .ap-preview.layout-2{aspect-ratio:auto;grid-template-rows:1fr;height:auto}.activitypub-embed-content .ap-preview.layout-3>img:first-child{grid-row:span 2}.activitypub-embed-content .ap-preview-text{padding:15px}.activitypub-embed-meta{border-top:1px solid #e6e6e6;color:#687684;display:flex;font-size:13px;gap:15px;padding:15px}.activitypub-embed-meta .ap-stat{align-items:center;display:flex;gap:5px}@media only screen and (max-width:399px){.activitypub-embed-meta span.ap-stat{display:none!important}}.activitypub-embed-meta a.ap-stat{color:inherit;text-decoration:none}.activitypub-embed-meta strong{color:#000;font-weight:600}.activitypub-embed-meta .ap-stat-label{color:#687684}.wp-block-activitypub-reply .components-spinner{height:12px;margin-bottom:0;margin-top:0;width:12px}
|
||||
.activitypub-embed-container{min-height:100px;pointer-events:none;position:relative;-webkit-user-select:none;user-select:none}.activitypub-embed-loading{align-items:center;display:flex;justify-content:center}.activitypub-embed-container .wp-block-embed{pointer-events:none!important}.activitypub-embed-preview,.activitypub-embed-preview iframe{pointer-events:none}.activitypub-reply-display{margin:1em 0}.activitypub-reply-display p{margin:0}.activitypub-reply-display a{color:#2271b1;text-decoration:none}.activitypub-reply-display a:hover{color:#135e96;text-decoration:underline}.activitypub-embed{background:#fff;border:1px solid #e6e6e6;border-radius:12px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif;max-width:100%;padding:0}.activitypub-reply-block .activitypub-embed{margin:1em 0}.activitypub-embed-header{align-items:center;display:flex;gap:10px;padding:15px}.activitypub-embed-header img{border-radius:50%;height:48px;width:48px}.activitypub-embed-header-text{flex-grow:1}.activitypub-embed-header-text h2{color:#000;font-size:15px;font-weight:600;margin:0;padding:0}.activitypub-embed-header-text .ap-account{color:#687684;font-size:14px;text-decoration:none}.activitypub-embed-content{padding:0 15px 15px}.activitypub-embed-content .ap-title{color:#000;font-size:23px;font-weight:600;margin:0 0 10px;padding:0}.activitypub-embed-content .ap-subtitle{color:#000;font-size:15px;margin:0 0 15px}.activitypub-embed-content .ap-preview{border:1px solid #e6e6e6;border-radius:8px;box-sizing:border-box;display:grid;gap:2px;grid-template-columns:1fr 1fr;grid-template-rows:1fr 1fr;margin:1em 0 0;min-height:64px;overflow:hidden;position:relative;width:100%}.activitypub-embed-content .ap-preview img{border:0;box-sizing:border-box;display:block;height:100%;object-fit:cover;overflow:hidden;position:relative;width:100%}.activitypub-embed-content .ap-preview audio,.activitypub-embed-content .ap-preview video{display:block;grid-column:1/span 2;max-width:100%}.activitypub-embed-content .ap-preview audio{width:100%}.activitypub-embed-content .ap-preview.layout-1{grid-template-columns:1fr;grid-template-rows:1fr}.activitypub-embed-content .ap-preview.layout-2{aspect-ratio:auto;grid-template-rows:1fr;height:auto}.activitypub-embed-content .ap-preview.layout-3>img:first-child{grid-row:span 2}.activitypub-embed-content .ap-preview-text{padding:15px}.activitypub-embed-meta{border-top:1px solid #e6e6e6;color:#687684;display:flex;font-size:13px;gap:15px;padding:15px}.activitypub-embed-meta .ap-stat{align-items:center;display:flex;gap:5px}@media only screen and (max-width:399px){.activitypub-embed-meta span.ap-stat{display:none!important}}.activitypub-embed-meta a.ap-stat{color:inherit;text-decoration:none}.activitypub-embed-meta strong{color:#000;font-weight:600}.activitypub-embed-meta .ap-stat-label{color:#687684}.wp-block-activitypub-reply .components-spinner{height:12px;margin-bottom:0;margin-top:0;width:12px}
|
||||
|
||||
@ -1 +1 @@
|
||||
<?php return array('dependencies' => array('react-jsx-runtime', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-data', 'wp-element', 'wp-i18n', 'wp-primitives', 'wp-url'), 'version' => '9528bc906518de9ee951');
|
||||
<?php return array('dependencies' => array('react-jsx-runtime', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-data', 'wp-element', 'wp-i18n', 'wp-primitives', 'wp-url'), 'version' => 'fbe17065130aaabefa31');
|
||||
|
||||
@ -1 +1 @@
|
||||
.activitypub-embed-container{min-height:100px;pointer-events:none;position:relative;-webkit-user-select:none;-moz-user-select:none;user-select:none}.activitypub-embed-loading{align-items:center;display:flex;justify-content:center}.activitypub-embed-container .wp-block-embed{pointer-events:none!important}.activitypub-embed-preview,.activitypub-embed-preview iframe{pointer-events:none}.activitypub-reply-display{margin:1em 0}.activitypub-reply-display p{margin:0}.activitypub-reply-display a{color:#2271b1;text-decoration:none}.activitypub-reply-display a:hover{color:#135e96;text-decoration:underline}.activitypub-embed{background:#fff;border:1px solid #e6e6e6;border-radius:12px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif;max-width:100%;padding:0}.activitypub-reply-block .activitypub-embed{margin:1em 0}.activitypub-embed-header{align-items:center;display:flex;gap:10px;padding:15px}.activitypub-embed-header img{border-radius:50%;height:48px;width:48px}.activitypub-embed-header-text{flex-grow:1}.activitypub-embed-header-text h2{color:#000;font-size:15px;font-weight:600;margin:0;padding:0}.activitypub-embed-header-text .ap-account{color:#687684;font-size:14px;text-decoration:none}.activitypub-embed-content{padding:0 15px 15px}.activitypub-embed-content .ap-title{color:#000;font-size:23px;font-weight:600;margin:0 0 10px;padding:0}.activitypub-embed-content .ap-subtitle{color:#000;font-size:15px;margin:0 0 15px}.activitypub-embed-content .ap-preview{border:1px solid #e6e6e6;border-radius:8px;box-sizing:border-box;display:grid;gap:2px;grid-template-columns:1fr 1fr;grid-template-rows:1fr 1fr;margin:1em 0 0;min-height:64px;overflow:hidden;position:relative;width:100%}.activitypub-embed-content .ap-preview img{border:0;box-sizing:border-box;display:block;height:100%;-o-object-fit:cover;object-fit:cover;overflow:hidden;position:relative;width:100%}.activitypub-embed-content .ap-preview audio,.activitypub-embed-content .ap-preview video{display:block;grid-column:1/span 2;max-width:100%}.activitypub-embed-content .ap-preview audio{width:100%}.activitypub-embed-content .ap-preview.layout-1{grid-template-columns:1fr;grid-template-rows:1fr}.activitypub-embed-content .ap-preview.layout-2{aspect-ratio:auto;grid-template-rows:1fr;height:auto}.activitypub-embed-content .ap-preview.layout-3>img:first-child{grid-row:span 2}.activitypub-embed-content .ap-preview-text{padding:15px}.activitypub-embed-meta{border-top:1px solid #e6e6e6;color:#687684;display:flex;font-size:13px;gap:15px;padding:15px}.activitypub-embed-meta .ap-stat{align-items:center;display:flex;gap:5px}@media only screen and (max-width:399px){.activitypub-embed-meta span.ap-stat{display:none!important}}.activitypub-embed-meta a.ap-stat{color:inherit;text-decoration:none}.activitypub-embed-meta strong{color:#000;font-weight:600}.activitypub-embed-meta .ap-stat-label{color:#687684}.wp-block-activitypub-reply .components-spinner{height:12px;margin-bottom:0;margin-top:0;width:12px}
|
||||
.activitypub-embed-container{min-height:100px;pointer-events:none;position:relative;-webkit-user-select:none;user-select:none}.activitypub-embed-loading{align-items:center;display:flex;justify-content:center}.activitypub-embed-container .wp-block-embed{pointer-events:none!important}.activitypub-embed-preview,.activitypub-embed-preview iframe{pointer-events:none}.activitypub-reply-display{margin:1em 0}.activitypub-reply-display p{margin:0}.activitypub-reply-display a{color:#2271b1;text-decoration:none}.activitypub-reply-display a:hover{color:#135e96;text-decoration:underline}.activitypub-embed{background:#fff;border:1px solid #e6e6e6;border-radius:12px;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif;max-width:100%;padding:0}.activitypub-reply-block .activitypub-embed{margin:1em 0}.activitypub-embed-header{align-items:center;display:flex;gap:10px;padding:15px}.activitypub-embed-header img{border-radius:50%;height:48px;width:48px}.activitypub-embed-header-text{flex-grow:1}.activitypub-embed-header-text h2{color:#000;font-size:15px;font-weight:600;margin:0;padding:0}.activitypub-embed-header-text .ap-account{color:#687684;font-size:14px;text-decoration:none}.activitypub-embed-content{padding:0 15px 15px}.activitypub-embed-content .ap-title{color:#000;font-size:23px;font-weight:600;margin:0 0 10px;padding:0}.activitypub-embed-content .ap-subtitle{color:#000;font-size:15px;margin:0 0 15px}.activitypub-embed-content .ap-preview{border:1px solid #e6e6e6;border-radius:8px;box-sizing:border-box;display:grid;gap:2px;grid-template-columns:1fr 1fr;grid-template-rows:1fr 1fr;margin:1em 0 0;min-height:64px;overflow:hidden;position:relative;width:100%}.activitypub-embed-content .ap-preview img{border:0;box-sizing:border-box;display:block;height:100%;object-fit:cover;overflow:hidden;position:relative;width:100%}.activitypub-embed-content .ap-preview audio,.activitypub-embed-content .ap-preview video{display:block;grid-column:1/span 2;max-width:100%}.activitypub-embed-content .ap-preview audio{width:100%}.activitypub-embed-content .ap-preview.layout-1{grid-template-columns:1fr;grid-template-rows:1fr}.activitypub-embed-content .ap-preview.layout-2{aspect-ratio:auto;grid-template-rows:1fr;height:auto}.activitypub-embed-content .ap-preview.layout-3>img:first-child{grid-row:span 2}.activitypub-embed-content .ap-preview-text{padding:15px}.activitypub-embed-meta{border-top:1px solid #e6e6e6;color:#687684;display:flex;font-size:13px;gap:15px;padding:15px}.activitypub-embed-meta .ap-stat{align-items:center;display:flex;gap:5px}@media only screen and (max-width:399px){.activitypub-embed-meta span.ap-stat{display:none!important}}.activitypub-embed-meta a.ap-stat{color:inherit;text-decoration:none}.activitypub-embed-meta strong{color:#000;font-weight:600}.activitypub-embed-meta .ap-stat-label{color:#687684}.wp-block-activitypub-reply .components-spinner{height:12px;margin-bottom:0;margin-top:0;width:12px}
|
||||
|
||||
@ -1 +1 @@
|
||||
(()=>{"use strict";var e={n:t=>{var o=t&&t.__esModule?()=>t.default:()=>t;return e.d(o,{a:o}),o},d:(t,o)=>{for(var r in o)e.o(o,r)&&!e.o(t,r)&&Object.defineProperty(t,r,{enumerable:!0,get:o[r]})},o:(e,t)=>Object.prototype.hasOwnProperty.call(e,t)};const t=window.wp.blocks,o=window.wp.primitives,r=window.ReactJSXRuntime;var i=(0,r.jsx)(o.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:(0,r.jsx)(o.Path,{d:"M6.68822 10.625L6.24878 11.0649L5.5 11.8145L5.5 5.5L12.5 5.5V8L14 6.5V5C14 4.44772 13.5523 4 13 4H5C4.44772 4 4 4.44771 4 5V13.5247C4 13.8173 4.16123 14.086 4.41935 14.2237C4.72711 14.3878 5.10601 14.3313 5.35252 14.0845L7.31 12.125H8.375L9.875 10.625H7.31H6.68822ZM14.5605 10.4983L11.6701 13.75H16.9975C17.9963 13.75 18.7796 14.1104 19.3553 14.7048C19.9095 15.2771 20.2299 16.0224 20.4224 16.7443C20.7645 18.0276 20.7543 19.4618 20.7487 20.2544C20.7481 20.345 20.7475 20.4272 20.7475 20.4999L19.2475 20.5001C19.2475 20.4191 19.248 20.3319 19.2484 20.2394V20.2394C19.2526 19.4274 19.259 18.2035 18.973 17.1307C18.8156 16.5401 18.586 16.0666 18.2778 15.7483C17.9909 15.4521 17.5991 15.25 16.9975 15.25H11.8106L14.5303 17.9697L13.4696 19.0303L8.96956 14.5303L13.4394 9.50171L14.5605 10.4983Z"})});const c=window.wp.blockEditor,l=window.wp.components,n=window.wp.i18n,s=window.wp.element,a=window.wp.compose,d=window.wp.data,u=window.wp.apiFetch;var p=e.n(u);const b=window.wp.url,w={default:(0,n.__)("Enter the URL of a post from the Fediverse (Mastodon, Pixelfed, etc.) that you want to reply to.","activitypub"),checking:()=>(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(l.Spinner,{})," "+(0,n.__)("Checking URL…","activitypub")]}),valid:(0,n.__)("The author will be notified of your response.","activitypub"),error:(0,n.__)("This site doesn’t have ActivityPub enabled and won’t receive your reply.","activitypub")};(0,t.registerBlockType)("activitypub/reply",{edit:function({attributes:e,setAttributes:o,clientId:i,isSelected:u}){const{url:y="",embedPost:v=!1}=e,[m,h]=(0,s.useState)(w.default),[f,k]=(0,s.useState)(!1),[_,C]=(0,s.useState)(!1),x=(0,s.useRef)(),{insertAfterBlock:L,removeBlock:g,replaceInnerBlocks:P}=(0,d.useDispatch)("core/block-editor"),j=v&&!_&&f,B=(0,c.useInnerBlocksProps)({className:"activitypub-embed-container"},{allowedBlocks:["core/embed"],template:y&&j?[["core/embed",{url:y}]]:[],templateLock:"all"});(0,s.useEffect)(()=>{P(i,y&&j?[(0,t.createBlock)("core/embed",{url:y})]:[])},[y,j,i,P]),(0,s.useEffect)(()=>{h(y?_?w.checking():f?w.valid:w.error:w.default)},[y,_,f]);const S=()=>{setTimeout(()=>x.current?.focus(),50)},V=(0,s.useCallback)(async e=>{if(e)try{C(!0),new URL(e);try{const t=await p()({path:(0,b.addQueryArgs)("/oembed/1.0/proxy",{url:e,activitypub:!0})});t&&t.provider_name?(o({embedPost:!0,isValidActivityPub:!0}),k(!0)):(o({isValidActivityPub:!1}),k(!1))}catch(e){console.log("Could not fetch embed:",e),o({isValidActivityPub:!1}),k(!1)}}catch(e){o({isValidActivityPub:!1}),k(!1)}finally{C(!1)}else k(!1)},[o,k,C]),A=(0,a.useDebounce)(V,250);return(0,s.useEffect)(()=>{y&&A(y)},[y,A]),(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(c.InspectorControls,{children:(0,r.jsx)(l.PanelBody,{title:(0,n.__)("Settings","activitypub"),children:(0,r.jsx)(l.ToggleControl,{label:(0,n.__)("Embed Post","activitypub"),checked:!!v,onChange:e=>o({embedPost:e}),disabled:!f,help:(0,n.__)("Show embedded content from the URL.","activitypub")})})}),(0,r.jsxs)("div",{onClick:S,...(0,c.useBlockProps)(),children:[u&&(0,r.jsx)(l.TextControl,{label:(0,n.__)("Your post is a reply to the following URL","activitypub"),value:y,onChange:e=>o({url:e}),help:m,onKeyDown:e=>{"Enter"===e.key&&L(i),!y&&["Backspace","Delete"].includes(e.key)&&g(i)},ref:x,__next40pxDefaultSize:!0}),j&&(0,r.jsx)("div",{...B}),y&&!j&&!u&&(0,r.jsx)("div",{className:"activitypub-reply-block-editor__preview",contentEditable:!1,onClick:S,style:{cursor:"pointer"},children:(0,r.jsx)("a",{href:y,className:"u-in-reply-to",target:"_blank",rel:"noreferrer",children:"↬"+y.replace(/^https?:\/\//,"")})})]})]})},save:()=>null,icon:i,transforms:{from:[{type:"block",blocks:["core/embed"],transform:e=>(0,t.createBlock)("activitypub/reply",{url:e.url||"",embedPost:!0})}],to:[{type:"block",blocks:["core/embed"],transform:e=>(0,t.createBlock)("core/embed",{url:e.url||""})}]}})})();
|
||||
(()=>{"use strict";const e={n:t=>{const o=t&&t.__esModule?()=>t.default:()=>t;return e.d(o,{a:o}),o},d:(t,o)=>{if(Array.isArray(o))for(var r=0;r<o.length;){var i=o[r++],l=o[r++];e.o(t,i)?0===l&&r++:0===l?Object.defineProperty(t,i,{enumerable:!0,value:o[r++]}):Object.defineProperty(t,i,{enumerable:!0,get:l})}else for(var i in o)e.o(o,i)&&!e.o(t,i)&&Object.defineProperty(t,i,{enumerable:!0,get:o[i]})},o:(e,t)=>Object.hasOwn(e,t)},t=window.wp.blocks,o=window.wp.primitives,r=window.ReactJSXRuntime;var i=(0,r.jsx)(o.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor",children:(0,r.jsx)(o.Path,{d:"M6.68822 10.625L6.24878 11.0649L5.5 11.8145L5.5 5.5L12.5 5.5V8L14 6.5V5C14 4.44772 13.5523 4 13 4H5C4.44772 4 4 4.44771 4 5V13.5247C4 13.8173 4.16123 14.086 4.41935 14.2237C4.72711 14.3878 5.10601 14.3313 5.35252 14.0845L7.31 12.125H8.375L9.875 10.625H7.31H6.68822ZM14.5605 10.4983L11.6701 13.75H16.9975C17.9963 13.75 18.7796 14.1104 19.3553 14.7048C19.9095 15.2771 20.2299 16.0224 20.4224 16.7443C20.7645 18.0276 20.7543 19.4618 20.7487 20.2544C20.7481 20.345 20.7475 20.4272 20.7475 20.4999L19.2475 20.5001C19.2475 20.4191 19.248 20.3319 19.2484 20.2394V20.2394C19.2526 19.4274 19.259 18.2035 18.973 17.1307C18.8156 16.5401 18.586 16.0666 18.2778 15.7483C17.9909 15.4521 17.5991 15.25 16.9975 15.25H11.8106L14.5303 17.9697L13.4696 19.0303L8.96956 14.5303L13.4394 9.50171L14.5605 10.4983Z"})});const l=window.wp.blockEditor,c=window.wp.components,n=window.wp.i18n,s=window.wp.element,a=window.wp.compose,d=window.wp.data,u=window.wp.apiFetch;var p=e.n(u);const b=window.wp.url,w={default:(0,n.__)("Enter the URL of a post from the Fediverse (Mastodon, Pixelfed, etc.) that you want to reply to.","activitypub"),checking:()=>(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(c.Spinner,{})," "+(0,n.__)("Checking URL…","activitypub")]}),valid:(0,n.__)("The author will be notified of your response.","activitypub"),error:(0,n.__)("This site doesn’t have ActivityPub enabled and won’t receive your reply.","activitypub")};(0,t.registerBlockType)("activitypub/reply",{edit:function({attributes:e,setAttributes:o,clientId:i,isSelected:u}){const{url:y="",embedPost:m=!1}=e,[v,f]=(0,s.useState)(w.default),[h,k]=(0,s.useState)(!1),[_,C]=(0,s.useState)(!1),g=(0,s.useRef)(),{insertAfterBlock:x,removeBlock:L,replaceInnerBlocks:P}=(0,d.useDispatch)("core/block-editor"),j=m&&!_&&h,B=(0,l.useInnerBlocksProps)({className:"activitypub-embed-container"},{allowedBlocks:["core/embed"],template:y&&j?[["core/embed",{url:y}]]:[],templateLock:"all"});(0,s.useEffect)(()=>{P(i,y&&j?[(0,t.createBlock)("core/embed",{url:y})]:[])},[y,j,i,P]),(0,s.useEffect)(()=>{f(y?_?w.checking():h?w.valid:w.error:w.default)},[y,_,h]);const A=()=>{setTimeout(()=>g.current?.focus(),50)},S=(0,s.useCallback)(async e=>{if(e)try{C(!0),new URL(e);try{const t=await p()({path:(0,b.addQueryArgs)("/oembed/1.0/proxy",{url:e,activitypub:!0})});t&&t.provider_name?(o({embedPost:!0,isValidActivityPub:!0}),k(!0)):(o({isValidActivityPub:!1}),k(!1))}catch(e){console.log("Could not fetch embed:",e),o({isValidActivityPub:!1}),k(!1)}}catch(e){o({isValidActivityPub:!1}),k(!1)}finally{C(!1)}else k(!1)},[o,k,C]),V=(0,a.useDebounce)(S,250);return(0,s.useEffect)(()=>{y&&V(y)},[y,V]),(0,r.jsxs)(r.Fragment,{children:[(0,r.jsx)(l.InspectorControls,{children:(0,r.jsx)(c.PanelBody,{title:(0,n.__)("Settings","activitypub"),children:(0,r.jsx)(c.ToggleControl,{label:(0,n.__)("Embed Post","activitypub"),checked:!!m,onChange:e=>o({embedPost:e}),disabled:!h,help:(0,n.__)("Show embedded content from the URL.","activitypub")})})}),(0,r.jsxs)("div",{onClick:A,...(0,l.useBlockProps)(),children:[u&&(0,r.jsx)(c.TextControl,{label:(0,n.__)("Your post is a reply to the following URL","activitypub"),value:y,onChange:e=>o({url:e}),help:v,onKeyDown:e=>{"Enter"===e.key&&x(i),!y&&["Backspace","Delete"].includes(e.key)&&L(i)},ref:g,__next40pxDefaultSize:!0}),j&&(0,r.jsx)("div",{...B}),y&&!j&&!u&&(0,r.jsx)("div",{className:"activitypub-reply-block-editor__preview",contentEditable:!1,onClick:A,style:{cursor:"pointer"},children:(0,r.jsx)("a",{href:y,className:"u-in-reply-to",target:"_blank",rel:"noreferrer",children:"↬"+y.replace(/^https?:\/\//,"")})})]})]})},save:()=>null,icon:i,transforms:{from:[{type:"block",blocks:["core/embed"],transform:e=>(0,t.createBlock)("activitypub/reply",{url:e.url||"",embedPost:!0})}],to:[{type:"block",blocks:["core/embed"],transform:e=>(0,t.createBlock)("core/embed",{url:e.url||""})}]}})})();
|
||||
@ -2,7 +2,7 @@
|
||||
"$schema": "https://schemas.wp.org/trunk/block.json",
|
||||
"name": "activitypub/stats",
|
||||
"apiVersion": 3,
|
||||
"version": "unreleased",
|
||||
"version": "9.1.0",
|
||||
"title": "ActivityPub Stats",
|
||||
"category": "widgets",
|
||||
"description": "Display your annual Fediverse stats as a shareable card.",
|
||||
|
||||
@ -1 +1 @@
|
||||
<?php return array('dependencies' => array('react-jsx-runtime', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-data', 'wp-element', 'wp-i18n', 'wp-server-side-render'), 'version' => '7042ce0f90bb9fbbfc2a');
|
||||
<?php return array('dependencies' => array('react-jsx-runtime', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-data', 'wp-element', 'wp-i18n', 'wp-server-side-render'), 'version' => 'c3b06abb1351ff45fc38');
|
||||
|
||||
@ -1 +1 @@
|
||||
(()=>{"use strict";var e,t={1868(e,t,i){const s=window.wp.blocks,r=window.wp.serverSideRender;var n=i.n(r);const a=window.wp.components,o=window.wp.blockEditor,l=window.wp.i18n,c=window.wp.element,u=window.wp.apiFetch;var p=i.n(u);const d=window.wp.data;const v=window.ReactJSXRuntime,b=(new Date).getFullYear();function h(){const e=[];for(let t=b;t>=b-5;t--)e.push({label:String(t),value:String(t)});return e}const g=JSON.parse('{"UU":"activitypub/stats"}');(0,s.registerBlockType)(g.UU,{edit:function({attributes:e,setAttributes:t}){const{selectedUser:i,year:s}=e,r=(0,o.useBlockProps)(),u=function({withInherit:e=!1}){const{enabled:t,namespace:i}=window._activityPubOptions||{},[s,r]=(0,c.useState)(!1),{fetchedUsers:n,isLoadingUsers:a}=(0,d.useSelect)(e=>{const{getUsers:i,getIsResolving:s}=e("core");return{fetchedUsers:t?.users?i({capabilities:"activitypub"}):null,isLoadingUsers:!!t?.users&&s("getUsers",[{capabilities:"activitypub"}])}},[t?.users]),o=(0,d.useSelect)(e=>n||a?null:e("core").getCurrentUser(),[n,a]);(0,c.useEffect)(()=>{!n&&!a&&o&&i&&p()({path:`/${i}/actors/${o.id}`,method:"HEAD",headers:{Accept:"application/activity+json"},parse:!1}).then(()=>r(!0)).catch(()=>r(!1))},[n,a,o,i]);const u=(0,c.useMemo)(()=>n||(o&&s?[{id:o.id,name:o.name}]:[]),[n,o,s]);return(0,c.useMemo)(()=>{if(!u.length)return[];const i=[];return t?.blog&&n&&i.push({label:(0,l.__)("Blog","activitypub"),value:"blog"}),e&&t?.users&&n&&i.push({label:(0,l.__)("Dynamic User","activitypub"),value:"inherit"}),u.reduce((e,t)=>(e.push({label:t.name,value:`${t.id}`}),e),i)},[u,t?.blog,t?.users,n,e])}({}),[g,w]=(0,c.useState)(!1);(0,c.useEffect)(()=>{!i&&u.length&&t({selectedUser:u[0].value})},[u]);const y=s||b-1,[f,_]=(0,c.useState)(""),m=(0,c.useCallback)(()=>{if(!i)return;const e=function(e,t){const i=window._activityPubOptions?.statsImageUrlEndpoint||"";if(!i)return"";const s=e&&"blog"!==e?e:0;return i.replace("{user_id}",s).replace("{year}",t)}(i,y);e&&p()({url:e}).then(e=>_(e.url||"")).catch(()=>_(""))},[i,y]);return(0,c.useEffect)(()=>{m()},[m]),(0,v.jsxs)("div",{...r,children:[(0,v.jsxs)(o.InspectorControls,{children:[(0,v.jsxs)(a.PanelBody,{title:(0,l.__)("Settings","activitypub"),children:[u.length>1&&(0,v.jsx)(a.SelectControl,{label:(0,l.__)("Select User","activitypub"),value:i,options:u,onChange:e=>t({selectedUser:e})}),(0,v.jsx)(a.SelectControl,{label:(0,l.__)("Year","activitypub"),value:String(y),options:h(),onChange:e=>t({year:parseInt(e,10)})})]}),f&&(0,v.jsxs)(a.PanelBody,{title:(0,l.__)("Share Image","activitypub"),initialOpen:!1,children:[(0,v.jsx)("p",{className:"description",children:(0,l.__)("Use this URL to share your stats as an image on social media.","activitypub")}),(0,v.jsx)(a.TextControl,{label:(0,l.__)("Share image URL","activitypub"),hideLabelFromVision:!0,__nextHasNoMarginBottom:!0,value:f,readOnly:!0,onClick:e=>e.target.select()}),(0,v.jsxs)("div",{style:{display:"flex",gap:"8px",alignItems:"center"},children:[(0,v.jsx)(a.Button,{variant:"secondary",onClick:()=>{navigator.clipboard.writeText(f).then(()=>{w(!0),setTimeout(()=>w(!1),2e3)})},children:g?(0,l.__)("Copied!","activitypub"):(0,l.__)("Copy URL","activitypub")}),(0,v.jsx)(a.ExternalLink,{href:f,children:(0,l.__)("Preview","activitypub")})]})]})]}),(0,v.jsx)(a.Disabled,{children:(0,v.jsx)(n(),{block:"activitypub/stats",attributes:{...e,year:y}})})]})}})}},i={};function s(e){var r=i[e];if(void 0!==r)return r.exports;var n=i[e]={exports:{}};return t[e](n,n.exports,s),n.exports}s.m=t,e=[],s.O=(t,i,r,n)=>{if(!i){var a=1/0;for(u=0;u<e.length;u++){for(var[i,r,n]=e[u],o=!0,l=0;l<i.length;l++)(!1&n||a>=n)&&Object.keys(s.O).every(e=>s.O[e](i[l]))?i.splice(l--,1):(o=!1,n<a&&(a=n));if(o){e.splice(u--,1);var c=r();void 0!==c&&(t=c)}}return t}n=n||0;for(var u=e.length;u>0&&e[u-1][2]>n;u--)e[u]=e[u-1];e[u]=[i,r,n]},s.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return s.d(t,{a:t}),t},s.d=(e,t)=>{for(var i in t)s.o(t,i)&&!s.o(e,i)&&Object.defineProperty(e,i,{enumerable:!0,get:t[i]})},s.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),(()=>{var e={85:0,721:0};s.O.j=t=>0===e[t];var t=(t,i)=>{var r,n,[a,o,l]=i,c=0;if(a.some(t=>0!==e[t])){for(r in o)s.o(o,r)&&(s.m[r]=o[r]);if(l)var u=l(s)}for(t&&t(i);c<a.length;c++)n=a[c],s.o(e,n)&&e[n]&&e[n][0](),e[n]=0;return s.O(u)},i=globalThis.webpackChunkwordpress_activitypub=globalThis.webpackChunkwordpress_activitypub||[];i.forEach(t.bind(null,0)),i.push=t.bind(null,i.push.bind(i))})();var r=s.O(void 0,[721],()=>s(1868));r=s.O(r)})();
|
||||
(()=>{"use strict";var e={868(e,t,n){const r=window.wp.blocks,i=window.wp.serverSideRender;var s=n.n(i);const a=window.wp.components,o=window.wp.blockEditor,l=window.wp.i18n,c=window.wp.element,u=window.wp.apiFetch;var p=n.n(u);const d=window.wp.data;const b=window.ReactJSXRuntime,v=(new Date).getFullYear();function h(){const e=[];for(let t=v;t>=v-5;t--)e.push({label:String(t),value:String(t)});return e}const g=JSON.parse('{"UU":"activitypub/stats"}');(0,r.registerBlockType)(g.UU,{edit:function({attributes:e,setAttributes:t}){const{selectedUser:n,year:r}=e,i=(0,o.useBlockProps)(),u=function({withInherit:e=!1}){const{enabled:t,namespace:n}=window._activityPubOptions||{},[r,i]=(0,c.useState)(!1),{fetchedUsers:s,isLoadingUsers:a}=(0,d.useSelect)(e=>{const{getUsers:n,getIsResolving:r}=e("core");return{fetchedUsers:t?.users?n({capabilities:"activitypub"}):null,isLoadingUsers:!!t?.users&&r("getUsers",[{capabilities:"activitypub"}])}},[t?.users]),o=(0,d.useSelect)(e=>s||a?null:e("core").getCurrentUser(),[s,a]);(0,c.useEffect)(()=>{!s&&!a&&o&&n&&p()({path:`/${n}/actors/${o.id}`,method:"HEAD",headers:{Accept:"application/activity+json"},parse:!1}).then(()=>i(!0)).catch(()=>i(!1))},[s,a,o,n]);const u=(0,c.useMemo)(()=>s||(o&&r?[{id:o.id,name:o.name}]:[]),[s,o,r]);return(0,c.useMemo)(()=>{if(!u.length)return[];const n=[];return t?.blog&&s&&n.push({label:(0,l.__)("Blog","activitypub"),value:"blog"}),e&&t?.users&&s&&n.push({label:(0,l.__)("Dynamic User","activitypub"),value:"inherit"}),u.reduce((e,t)=>(e.push({label:t.name,value:`${t.id}`}),e),n)},[u,t?.blog,t?.users,s,e])}({}),[g,y]=(0,c.useState)(!1);(0,c.useEffect)(()=>{!n&&u.length&&t({selectedUser:u[0].value})},[u]);const f=r||v-1,[w,_]=(0,c.useState)(""),m=(0,c.useCallback)(()=>{if(!n)return;const e=function(e,t){const n=window._activityPubOptions?.statsImageUrlEndpoint||"";if(!n)return"";const r=e&&"blog"!==e?e:0;return n.replace("{user_id}",r).replace("{year}",t)}(n,f);e&&p()({url:e}).then(e=>_(e.url||"")).catch(()=>_(""))},[n,f]);return(0,c.useEffect)(()=>{m()},[m]),(0,b.jsxs)("div",{...i,children:[(0,b.jsxs)(o.InspectorControls,{children:[(0,b.jsxs)(a.PanelBody,{title:(0,l.__)("Settings","activitypub"),children:[u.length>1&&(0,b.jsx)(a.SelectControl,{label:(0,l.__)("Select User","activitypub"),value:n,options:u,onChange:e=>t({selectedUser:e})}),(0,b.jsx)(a.SelectControl,{label:(0,l.__)("Year","activitypub"),value:String(f),options:h(),onChange:e=>t({year:parseInt(e,10)})})]}),w&&(0,b.jsxs)(a.PanelBody,{title:(0,l.__)("Share Image","activitypub"),initialOpen:!1,children:[(0,b.jsx)("p",{className:"description",children:(0,l.__)("Use this URL to share your stats as an image on social media.","activitypub")}),(0,b.jsx)(a.TextControl,{label:(0,l.__)("Share image URL","activitypub"),hideLabelFromVision:!0,__nextHasNoMarginBottom:!0,value:w,readOnly:!0,onClick:e=>e.target.select()}),(0,b.jsxs)("div",{style:{display:"flex",gap:"8px",alignItems:"center"},children:[(0,b.jsx)(a.Button,{variant:"secondary",onClick:()=>{navigator.clipboard.writeText(w).then(()=>{y(!0),setTimeout(()=>y(!1),2e3)})},children:g?(0,l.__)("Copied!","activitypub"):(0,l.__)("Copy URL","activitypub")}),(0,b.jsx)(a.ExternalLink,{href:w,rel:"external noreferrer noopener",children:(0,l.__)("Preview","activitypub")})]})]})]}),(0,b.jsx)(a.Disabled,{children:(0,b.jsx)(s(),{block:"activitypub/stats",attributes:{...e,year:f}})})]})}})}};const t={};function n(r){const i=t[r];if(void 0!==i)return i.exports;const s=t[r]={exports:{}};return e[r](s,s.exports,n),s.exports}n.m=e,(()=>{const e=[];n.O=(t,r,i,s)=>{if(r){s=s||0;for(var a=e.length;a>0&&e[a-1][2]>s;a--)e[a]=e[a-1];return void(e[a]=[r,i,s])}let o=1/0;for(a=0;a<e.length;a++){let[r,i,s]=e[a],c=!0;for(var l=0;l<r.length;l++)(!1&s||o>=s)&&Object.keys(n.O).every(e=>n.O[e](r[l]))?r.splice(l--,1):(c=!1,s<o&&(o=s));if(c){e.splice(a--,1);const n=i();void 0!==n&&(t=n)}}return t}})(),n.n=e=>{const t=e&&e.__esModule?()=>e.default:()=>e;return n.d(t,{a:t}),t},n.d=(e,t)=>{if(Array.isArray(t))for(var r=0;r<t.length;){var i=t[r++],s=t[r++];n.o(e,i)?0===s&&r++:0===s?Object.defineProperty(e,i,{enumerable:!0,value:t[r++]}):Object.defineProperty(e,i,{enumerable:!0,get:s})}else for(var i in t)n.o(t,i)&&!n.o(e,i)&&Object.defineProperty(e,i,{enumerable:!0,get:t[i]})},n.o=(e,t)=>Object.hasOwn(e,t),(()=>{const e={85:0,721:0};n.O.j=t=>0===e[t];const t=(t,r)=>{let[i,s,a]=r;var o,l,c=0;if(i.some(t=>0!==e[t])){for(o in s)n.o(s,o)&&(n.m[o]=s[o]);if(a)var u=a(n)}for(t&&t(r);c<i.length;c++)l=i[c],n.o(e,l)&&e[l]&&e[l][0](),e[l]=0;return n.O(u)},r=globalThis.webpackChunkwordpress_activitypub||=[];r.forEach(t.bind(null,0)),r.push=t.bind(null,r.push.bind(r))})();let r=n.O(void 0,[721],()=>n(868));r=n.O(r)})();
|
||||
@ -65,13 +65,9 @@ $change_sign = $followers_net_change >= 0 ? '+' : '';
|
||||
// Get actor webfinger for the card header.
|
||||
$actor = Actors::get_by_id( $user_id );
|
||||
|
||||
if ( \is_wp_error( $actor ) ) {
|
||||
// Fall back to direct model instantiation for blog/application actors.
|
||||
if ( Actors::BLOG_USER_ID === $user_id ) {
|
||||
$actor = new \Activitypub\Model\Blog();
|
||||
} elseif ( Actors::APPLICATION_USER_ID === $user_id ) {
|
||||
$actor = new \Activitypub\Model\Application();
|
||||
}
|
||||
if ( \is_wp_error( $actor ) && Actors::BLOG_USER_ID === $user_id ) {
|
||||
// Fall back to direct model instantiation for the blog actor.
|
||||
$actor = new \Activitypub\Model\Blog();
|
||||
}
|
||||
|
||||
$actor_webfinger = ! \is_wp_error( $actor ) ? $actor->get_webfinger() : '';
|
||||
|
||||
@ -38,8 +38,10 @@ class Activity extends Base_Object {
|
||||
const JSON_LD_CONTEXT = array(
|
||||
'https://www.w3.org/ns/activitystreams',
|
||||
array(
|
||||
'toot' => 'http://joinmastodon.org/ns#',
|
||||
'QuoteRequest' => 'toot:QuoteRequest',
|
||||
'toot' => 'http://joinmastodon.org/ns#',
|
||||
'QuoteRequest' => 'toot:QuoteRequest',
|
||||
'blurhash' => 'toot:blurhash',
|
||||
'FeatureRequest' => 'https://w3id.org/fep/7aa9#FeatureRequest',
|
||||
),
|
||||
);
|
||||
|
||||
@ -70,6 +72,7 @@ class Activity extends Base_Object {
|
||||
'Move',
|
||||
'Offer',
|
||||
'QuoteRequest', // @see https://codeberg.org/fediverse/fep/src/branch/main/fep/044f/fep-044f.md
|
||||
'FeatureRequest', // @see https://w3id.org/fep/7aa9 (FEP-7aa9 draft)
|
||||
'Read',
|
||||
'Reject',
|
||||
'Remove',
|
||||
@ -188,9 +191,9 @@ class Activity extends Base_Object {
|
||||
$object = $data;
|
||||
|
||||
// Convert array to appropriate object type.
|
||||
if ( is_array( $data ) ) {
|
||||
if ( array_is_list( $data ) ) {
|
||||
$object = array_map( array( $this, 'maybe_convert_to_object' ), $data );
|
||||
if ( \is_array( $data ) ) {
|
||||
if ( \array_is_list( $data ) ) {
|
||||
$object = \array_map( array( $this, 'maybe_convert_to_object' ), $data );
|
||||
} else {
|
||||
$object = $this->maybe_convert_to_object( $data );
|
||||
}
|
||||
@ -207,14 +210,14 @@ class Activity extends Base_Object {
|
||||
$object = $this->get_object();
|
||||
|
||||
// Check if `$object` is a URL and use it to generate an ID then.
|
||||
if ( is_string( $object ) && filter_var( $object, FILTER_VALIDATE_URL ) && ! $this->get_id() ) {
|
||||
$this->set( 'id', $object . '#activity-' . strtolower( $this->get_type() ) . '-' . time() );
|
||||
if ( \is_string( $object ) && \filter_var( $object, FILTER_VALIDATE_URL ) && ! $this->get_id() ) {
|
||||
$this->set( 'id', $object . '#activity-' . \strtolower( $this->get_type() ) . '-' . \time() );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if `$object` is an object and copy some properties otherwise do nothing.
|
||||
if ( ! is_object( $object ) ) {
|
||||
if ( ! \is_object( $object ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -246,15 +249,15 @@ class Activity extends Base_Object {
|
||||
}
|
||||
|
||||
if ( $object->get_id() && ! $this->get_id() ) {
|
||||
$id = strtok( $object->get_id(), '#' );
|
||||
$id = \strtok( $object->get_id(), '#' );
|
||||
if ( $object->get_updated() ) {
|
||||
$updated = $object->get_updated();
|
||||
} elseif ( $object->get_published() ) {
|
||||
$updated = $object->get_published();
|
||||
} else {
|
||||
$updated = time();
|
||||
$updated = \time();
|
||||
}
|
||||
$this->set( 'id', $id . '#activity-' . strtolower( $this->get_type() ) . '-' . $updated );
|
||||
$this->set( 'id', $id . '#activity-' . \strtolower( $this->get_type() ) . '-' . $updated );
|
||||
}
|
||||
}
|
||||
|
||||
@ -265,7 +268,7 @@ class Activity extends Base_Object {
|
||||
*/
|
||||
public function get_json_ld_context() {
|
||||
if ( \is_object( $this->object ) ) {
|
||||
$class = get_class( $this->object );
|
||||
$class = \get_class( $this->object );
|
||||
if ( $class && $class::JSON_LD_CONTEXT ) {
|
||||
// Without php 5.6 support this could be just: 'return $this->object::JSON_LD_CONTEXT;'.
|
||||
return $class::JSON_LD_CONTEXT;
|
||||
@ -283,17 +286,17 @@ class Activity extends Base_Object {
|
||||
* @return Activity|Actor|Base_Object|Generic_Object|string|\WP_Error|null The converted object or original data.
|
||||
*/
|
||||
private function maybe_convert_to_object( $data ) {
|
||||
if ( ! is_array( $data ) ) {
|
||||
if ( ! \is_array( $data ) ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
$type = $data['type'] ?? null;
|
||||
|
||||
if ( in_array( $type, self::TYPES, true ) ) {
|
||||
if ( \in_array( $type, self::TYPES, true ) ) {
|
||||
$object = self::init_from_array( $data );
|
||||
} elseif ( in_array( $type, Actor::TYPES, true ) ) {
|
||||
} elseif ( \in_array( $type, Actor::TYPES, true ) ) {
|
||||
$object = Actor::init_from_array( $data );
|
||||
} elseif ( in_array( $type, Base_Object::TYPES, true ) ) {
|
||||
} elseif ( \in_array( $type, Base_Object::TYPES, true ) ) {
|
||||
switch ( $type ) {
|
||||
case 'Event':
|
||||
$object = Event::init_from_array( $data );
|
||||
|
||||
@ -74,6 +74,7 @@ class Actor extends Base_Object {
|
||||
'toot' => 'http://joinmastodon.org/ns#',
|
||||
'lemmy' => 'https://join-lemmy.org/ns#',
|
||||
'litepub' => 'http://litepub.social/ns#',
|
||||
'gts' => 'https://gotosocial.org/ns#',
|
||||
'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers',
|
||||
'PropertyValue' => 'schema:PropertyValue',
|
||||
'value' => 'schema:value',
|
||||
@ -107,6 +108,18 @@ class Actor extends Base_Object {
|
||||
'@type' => '@id',
|
||||
'@container' => '@list',
|
||||
),
|
||||
'interactionPolicy' => array(
|
||||
'@id' => 'gts:interactionPolicy',
|
||||
'@type' => '@id',
|
||||
),
|
||||
'canFeature' => array(
|
||||
'@id' => 'https://w3id.org/fep/7aa9#canFeature',
|
||||
'@type' => '@id',
|
||||
),
|
||||
'automaticApproval' => array(
|
||||
'@id' => 'gts:automaticApproval',
|
||||
'@type' => '@id',
|
||||
),
|
||||
'postingRestrictedToMods' => 'lemmy:postingRestrictedToMods',
|
||||
'discoverable' => 'toot:discoverable',
|
||||
'indexable' => 'toot:indexable',
|
||||
|
||||
@ -162,6 +162,8 @@ class Base_Object extends Generic_Object {
|
||||
'@id' => 'gts:always',
|
||||
'@type' => '@id',
|
||||
),
|
||||
'toot' => 'http://joinmastodon.org/ns#',
|
||||
'blurhash' => 'toot:blurhash',
|
||||
),
|
||||
);
|
||||
|
||||
@ -634,7 +636,7 @@ class Base_Object extends Generic_Object {
|
||||
*/
|
||||
public function get( $key ) {
|
||||
if ( ! $this->has( $key ) ) {
|
||||
return new \WP_Error( 'invalid_key', __( 'Invalid key', 'activitypub' ), array( 'status' => 404 ) );
|
||||
return new \WP_Error( 'invalid_key', \__( 'Invalid key', 'activitypub' ), array( 'status' => 404 ) );
|
||||
}
|
||||
|
||||
return parent::get( $key );
|
||||
@ -650,7 +652,7 @@ class Base_Object extends Generic_Object {
|
||||
*/
|
||||
public function set( $key, $value ) {
|
||||
if ( ! $this->has( $key ) ) {
|
||||
return new \WP_Error( 'invalid_key', __( 'Invalid key', 'activitypub' ), array( 'status' => 404 ) );
|
||||
return new \WP_Error( 'invalid_key', \__( 'Invalid key', 'activitypub' ), array( 'status' => 404 ) );
|
||||
}
|
||||
|
||||
return parent::set( $key, $value );
|
||||
@ -666,7 +668,7 @@ class Base_Object extends Generic_Object {
|
||||
*/
|
||||
public function add( $key, $value ) {
|
||||
if ( ! $this->has( $key ) ) {
|
||||
return new \WP_Error( 'invalid_key', __( 'Invalid key', 'activitypub' ), array( 'status' => 404 ) );
|
||||
return new \WP_Error( 'invalid_key', \__( 'Invalid key', 'activitypub' ), array( 'status' => 404 ) );
|
||||
}
|
||||
|
||||
return parent::add( $key, $value );
|
||||
|
||||
@ -99,7 +99,7 @@ class Generic_Object {
|
||||
* @return mixed The value.
|
||||
*/
|
||||
public function get( $key ) {
|
||||
return call_user_func( array( $this, 'get_' . $key ) );
|
||||
return \call_user_func( array( $this, 'get_' . $key ) );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -133,19 +133,19 @@ class Generic_Object {
|
||||
$this->$key = array();
|
||||
}
|
||||
|
||||
if ( is_string( $this->$key ) ) {
|
||||
if ( \is_string( $this->$key ) ) {
|
||||
$this->$key = array( $this->$key );
|
||||
}
|
||||
|
||||
$attributes = $this->$key;
|
||||
|
||||
if ( is_array( $value ) ) {
|
||||
$attributes = array_merge( $attributes, $value );
|
||||
if ( \is_array( $value ) ) {
|
||||
$attributes = \array_merge( $attributes, $value );
|
||||
} else {
|
||||
$attributes[] = $value;
|
||||
}
|
||||
|
||||
$this->$key = array_unique( $attributes );
|
||||
$this->$key = \array_unique( $attributes );
|
||||
|
||||
return $this->$key;
|
||||
}
|
||||
@ -158,7 +158,7 @@ class Generic_Object {
|
||||
* @return boolean True if the object has the key.
|
||||
*/
|
||||
public function has( $key ) {
|
||||
return property_exists( $this, $key );
|
||||
return \property_exists( $this, $key );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -171,8 +171,8 @@ class Generic_Object {
|
||||
public static function init_from_json( $json ) {
|
||||
$array = \json_decode( $json, true );
|
||||
|
||||
if ( ! is_array( $array ) ) {
|
||||
return new \WP_Error( 'invalid_json', __( 'Invalid JSON', 'activitypub' ), array( 'status' => 400 ) );
|
||||
if ( ! \is_array( $array ) ) {
|
||||
return new \WP_Error( 'invalid_json', \__( 'Invalid JSON', 'activitypub' ), array( 'status' => 400 ) );
|
||||
}
|
||||
|
||||
return self::init_from_array( $array );
|
||||
@ -186,8 +186,8 @@ class Generic_Object {
|
||||
* @return static|\WP_Error An Object built from the input array or WP_Error when it's not an array.
|
||||
*/
|
||||
public static function init_from_array( $data ) {
|
||||
if ( ! is_array( $data ) ) {
|
||||
return new \WP_Error( 'invalid_array', __( 'Invalid array', 'activitypub' ), array( 'status' => 400 ) );
|
||||
if ( ! \is_array( $data ) ) {
|
||||
return new \WP_Error( 'invalid_array', \__( 'Invalid array', 'activitypub' ), array( 'status' => 400 ) );
|
||||
}
|
||||
|
||||
$object = new static();
|
||||
@ -209,7 +209,7 @@ class Generic_Object {
|
||||
$key = camel_to_snake_case( $key );
|
||||
}
|
||||
|
||||
call_user_func( array( $this, 'set_' . $key ), $value );
|
||||
\call_user_func( array( $this, 'set_' . $key ), $value );
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -244,7 +244,7 @@ class Generic_Object {
|
||||
*/
|
||||
public function to_array( $include_json_ld_context = true, $include_blind_audience = false ) {
|
||||
$array = array();
|
||||
$vars = get_object_vars( $this );
|
||||
$vars = \get_object_vars( $this );
|
||||
|
||||
foreach ( $vars as $key => $value ) {
|
||||
if ( \is_wp_error( $value ) ) {
|
||||
@ -252,20 +252,20 @@ class Generic_Object {
|
||||
}
|
||||
|
||||
// Ignore all _prefixed keys.
|
||||
if ( '_' === substr( $key, 0, 1 ) ) {
|
||||
if ( '_' === \substr( $key, 0, 1 ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If value is empty, try to get it from a getter.
|
||||
if ( ! $value ) {
|
||||
$value = call_user_func( array( $this, 'get_' . $key ) );
|
||||
$value = \call_user_func( array( $this, 'get_' . $key ) );
|
||||
}
|
||||
|
||||
if ( is_object( $value ) ) {
|
||||
if ( \is_object( $value ) ) {
|
||||
$value = $value->to_array( false, $include_blind_audience );
|
||||
}
|
||||
|
||||
if ( is_array( $value ) && $this->is_namespaced( $key ) ) {
|
||||
if ( \is_array( $value ) && $this->is_namespaced( $key ) ) {
|
||||
foreach ( $value as $sub_key => $sub_value ) {
|
||||
$array[ snake_to_camel_case( $key ) . ':' . snake_to_camel_case( $sub_key ) ] = $sub_value;
|
||||
}
|
||||
@ -276,11 +276,11 @@ class Generic_Object {
|
||||
|
||||
if ( $include_json_ld_context ) {
|
||||
// Get JsonLD context and move it to '@context' at the top.
|
||||
$array = array_merge( array( '@context' => $this->get_json_ld_context() ), $array );
|
||||
$array = \array_merge( array( '@context' => $this->get_json_ld_context() ), $array );
|
||||
}
|
||||
|
||||
$class = new \ReflectionClass( $this );
|
||||
$class = strtolower( $class->getShortName() );
|
||||
$class = \strtolower( $class->getShortName() );
|
||||
|
||||
/**
|
||||
* Filter the array of the ActivityPub object.
|
||||
@ -371,7 +371,7 @@ class Generic_Object {
|
||||
$namespaces = array();
|
||||
|
||||
foreach ( static::JSON_LD_CONTEXT as $context ) {
|
||||
if ( is_array( $context ) ) {
|
||||
if ( \is_array( $context ) ) {
|
||||
$namespaces = \array_merge( $namespaces, $context );
|
||||
}
|
||||
}
|
||||
|
||||
@ -301,10 +301,10 @@ class Event extends Base_Object {
|
||||
* @return Event
|
||||
*/
|
||||
public function set_timezone( $timezone ) {
|
||||
if ( in_array( $timezone, timezone_identifiers_list(), true ) ) {
|
||||
if ( \in_array( $timezone, \timezone_identifiers_list(), true ) ) {
|
||||
$this->timezone = $timezone;
|
||||
} else {
|
||||
$this->timezone = wp_timezone_string();
|
||||
$this->timezone = \wp_timezone_string();
|
||||
}
|
||||
|
||||
return $this;
|
||||
@ -318,11 +318,11 @@ class Event extends Base_Object {
|
||||
* @return Event
|
||||
*/
|
||||
public function set_replies_moderation_option( $type ) {
|
||||
if ( in_array( $type, self::REPLIES_MODERATION_OPTION_TYPES, true ) ) {
|
||||
if ( \in_array( $type, self::REPLIES_MODERATION_OPTION_TYPES, true ) ) {
|
||||
$this->replies_moderation_option = $type;
|
||||
$this->comments_enabled = ( 'allow_all' === $type ) ? true : false;
|
||||
} else {
|
||||
_doing_it_wrong(
|
||||
\_doing_it_wrong(
|
||||
__METHOD__,
|
||||
'The replies moderation option must be either allow_all or closed.',
|
||||
'<version_placeholder>'
|
||||
@ -340,11 +340,11 @@ class Event extends Base_Object {
|
||||
* @return Event
|
||||
*/
|
||||
public function set_comments_enabled( $comments_enabled ) {
|
||||
if ( is_bool( $comments_enabled ) ) {
|
||||
if ( \is_bool( $comments_enabled ) ) {
|
||||
$this->comments_enabled = $comments_enabled;
|
||||
$this->replies_moderation_option = $comments_enabled ? 'allow_all' : 'closed';
|
||||
} else {
|
||||
_doing_it_wrong(
|
||||
\_doing_it_wrong(
|
||||
__METHOD__,
|
||||
'The commentsEnabled must be boolean.',
|
||||
'<version_placeholder>'
|
||||
@ -362,10 +362,10 @@ class Event extends Base_Object {
|
||||
* @return Event
|
||||
*/
|
||||
public function set_status( $status ) {
|
||||
if ( in_array( $status, self::ICAL_EVENT_STATUS_TYPES, true ) ) {
|
||||
if ( \in_array( $status, self::ICAL_EVENT_STATUS_TYPES, true ) ) {
|
||||
$this->status = $status;
|
||||
} else {
|
||||
_doing_it_wrong(
|
||||
\_doing_it_wrong(
|
||||
__METHOD__,
|
||||
'The status of the event must be a VEVENT iCal status.',
|
||||
'<version_placeholder>'
|
||||
@ -387,7 +387,7 @@ class Event extends Base_Object {
|
||||
*/
|
||||
public function set_category( $category, $mobilizon_compatibility = true ) {
|
||||
if ( $mobilizon_compatibility ) {
|
||||
$this->category = in_array( $category, self::DEFAULT_EVENT_CATEGORIES, true ) ? $category : 'MEETING';
|
||||
$this->category = \in_array( $category, self::DEFAULT_EVENT_CATEGORIES, true ) ? $category : 'MEETING';
|
||||
} else {
|
||||
$this->category = $category;
|
||||
}
|
||||
@ -405,7 +405,7 @@ class Event extends Base_Object {
|
||||
* @return Event
|
||||
*/
|
||||
public function set_external_participation_url( $url ) {
|
||||
if ( preg_match( '/^https?:\/\/.*/i', $url ) ) {
|
||||
if ( \preg_match( '/^https?:\/\/.*/i', $url ) ) {
|
||||
$this->external_participation_url = $url;
|
||||
$this->join_mode = 'external';
|
||||
}
|
||||
|
||||
@ -0,0 +1,74 @@
|
||||
<?php
|
||||
/**
|
||||
* Feature_Authorization is an implementation of the FeatureAuthorization activity type,
|
||||
* as defined in FEP-7aa9 (https://w3id.org/fep/7aa9).
|
||||
*
|
||||
* This class represents a FeatureAuthorization activity for ActivityPub implementations.
|
||||
*
|
||||
* @package Activitypub
|
||||
*/
|
||||
|
||||
namespace Activitypub\Activity\Extended_Object;
|
||||
|
||||
use Activitypub\Activity\Base_Object;
|
||||
|
||||
/**
|
||||
* Class representing a FeatureAuthorization activity.
|
||||
*
|
||||
* @see https://w3id.org/fep/7aa9
|
||||
*
|
||||
* @since 9.0.0
|
||||
*
|
||||
* @method Base_Object|string|array|null get_interacting_object() Gets the interacting object property of the object.
|
||||
* @method Base_Object|string|array|null get_interaction_target() Gets the interaction target property of the object.
|
||||
*
|
||||
* @method Feature_Authorization set_interacting_object( string|array|Base_Object|null $data ) Sets the interacting object property of the object.
|
||||
* @method Feature_Authorization set_interaction_target( string|array|Base_Object|null $data ) Sets the interaction target property of the object.
|
||||
*/
|
||||
class Feature_Authorization extends Base_Object {
|
||||
/**
|
||||
* The JSON-LD context for the object.
|
||||
*
|
||||
* Intentionally minimal: stamps are always served standalone at their own
|
||||
* URL, so we ship only the vocabulary the stamp document itself uses.
|
||||
* Mirrors the Quote_Authorization (FEP-044f) approach.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
const JSON_LD_CONTEXT = array(
|
||||
'https://www.w3.org/ns/activitystreams',
|
||||
array(
|
||||
'FeatureAuthorization' => 'https://w3id.org/fep/7aa9#FeatureAuthorization',
|
||||
'gts' => 'https://gotosocial.org/ns#',
|
||||
'interactingObject' => array(
|
||||
'@id' => 'gts:interactingObject',
|
||||
'@type' => '@id',
|
||||
),
|
||||
'interactionTarget' => array(
|
||||
'@id' => 'gts:interactionTarget',
|
||||
'@type' => '@id',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
/**
|
||||
* The type of the object.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $type = 'FeatureAuthorization';
|
||||
|
||||
/**
|
||||
* The object that is being interacted with.
|
||||
*
|
||||
* @var Base_Object|string|array|null
|
||||
*/
|
||||
protected $interacting_object;
|
||||
|
||||
/**
|
||||
* The target of the interaction.
|
||||
*
|
||||
* @var Base_Object|string|array|null
|
||||
*/
|
||||
protected $interaction_target;
|
||||
}
|
||||
@ -106,10 +106,10 @@ class Place extends Base_Object {
|
||||
* @param array|string $address The address of the place.
|
||||
*/
|
||||
public function set_address( $address ) {
|
||||
if ( is_string( $address ) || is_array( $address ) ) {
|
||||
if ( \is_string( $address ) || \is_array( $address ) ) {
|
||||
$this->address = $address;
|
||||
} else {
|
||||
_doing_it_wrong(
|
||||
\_doing_it_wrong(
|
||||
__METHOD__,
|
||||
'The address must be either a string or an array like schema.org/PostalAddress.',
|
||||
'<version_placeholder>'
|
||||
|
||||
@ -568,7 +568,7 @@ abstract class File {
|
||||
// Method 4: Ensure file extension matches MIME type.
|
||||
$ext = \pathinfo( $file_path, PATHINFO_EXTENSION );
|
||||
|
||||
if ( strtolower( $ext ) !== $expected_ext ) {
|
||||
if ( \strtolower( $ext ) !== $expected_ext ) {
|
||||
$new_path = \preg_replace( '/\.[^.]+$/', '.' . $expected_ext, $file_path );
|
||||
if ( empty( $new_path ) || $new_path === $file_path ) {
|
||||
$new_path = $file_path . '.' . $expected_ext;
|
||||
|
||||
@ -9,7 +9,6 @@
|
||||
namespace Activitypub\Cache;
|
||||
|
||||
use Activitypub\Collection\Actors;
|
||||
use Activitypub\Model\Application;
|
||||
use Activitypub\Model\Blog;
|
||||
use Activitypub\Statistics;
|
||||
|
||||
@ -208,16 +207,16 @@ class Stats_Image extends File {
|
||||
|
||||
$actor = Actors::get_by_id( $user_id );
|
||||
|
||||
if ( \is_wp_error( $actor ) ) {
|
||||
if ( Actors::BLOG_USER_ID === $user_id ) {
|
||||
$actor = new Blog();
|
||||
} elseif ( Actors::APPLICATION_USER_ID === $user_id ) {
|
||||
$actor = new Application();
|
||||
}
|
||||
if ( \is_wp_error( $actor ) && Actors::BLOG_USER_ID === $user_id ) {
|
||||
$actor = new Blog();
|
||||
}
|
||||
|
||||
$actor_webfinger = ! \is_wp_error( $actor ) ? $actor->get_webfinger() : '';
|
||||
$site_name = \get_bloginfo( 'name' );
|
||||
if ( ! \is_wp_error( $actor ) ) {
|
||||
$actor_webfinger = $actor->get_webfinger();
|
||||
} else {
|
||||
$actor_webfinger = '';
|
||||
}
|
||||
$site_name = \get_bloginfo( 'name' );
|
||||
|
||||
if ( ! \function_exists( 'wp_tempnam' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/file.php';
|
||||
|
||||
@ -131,7 +131,7 @@ class Activitypub {
|
||||
* @deprecated 7.5.0 Use {@see Router::add_rewrite_rules()}.
|
||||
*/
|
||||
public static function add_rewrite_rules() {
|
||||
_deprecated_function( __FUNCTION__, '7.5.0', '\Activitypub\Router::add_rewrite_rules()' );
|
||||
\_deprecated_function( __FUNCTION__, '7.5.0', '\Activitypub\Router::add_rewrite_rules()' );
|
||||
|
||||
Router::add_rewrite_rules();
|
||||
}
|
||||
@ -142,9 +142,9 @@ class Activitypub {
|
||||
public static function theme_compat() {
|
||||
// We assume that you want to use Post-Formats when enabling the setting.
|
||||
if ( 'wordpress-post-format' === \get_option( 'activitypub_object_type', ACTIVITYPUB_DEFAULT_OBJECT_TYPE ) ) {
|
||||
if ( ! get_theme_support( 'post-formats' ) ) {
|
||||
if ( ! \get_theme_support( 'post-formats' ) ) {
|
||||
// Add support for the Aside, Gallery Post Formats...
|
||||
add_theme_support(
|
||||
\add_theme_support(
|
||||
'post-formats',
|
||||
array(
|
||||
'gallery',
|
||||
@ -230,7 +230,7 @@ class Activitypub {
|
||||
'single' => true,
|
||||
'default' => '',
|
||||
'sanitize_callback' => static function ( $value ) {
|
||||
return wp_kses( $value, 'user_description' );
|
||||
return \wp_kses( $value, 'user_description' );
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
318
wp-content/plugins/activitypub/includes/class-application.php
Normal file
318
wp-content/plugins/activitypub/includes/class-application.php
Normal file
@ -0,0 +1,318 @@
|
||||
<?php
|
||||
/**
|
||||
* Application class file.
|
||||
*
|
||||
* @package Activitypub
|
||||
*/
|
||||
|
||||
namespace Activitypub;
|
||||
|
||||
/**
|
||||
* ActivityPub Application Class.
|
||||
*
|
||||
* The Application is not a real actor in the plugin's internal sense —
|
||||
* it cannot be followed, addressed, or interacted with. It exists only as:
|
||||
* 1. A JSON-LD document at /wp-json/activitypub/1.0/application
|
||||
* 2. A signing identity for outbound HTTP GET requests
|
||||
*
|
||||
* This class provides static utility methods for the Application actor,
|
||||
* primarily key management for HTTP Signatures.
|
||||
*
|
||||
* @since 9.1.0
|
||||
*/
|
||||
class Application {
|
||||
/**
|
||||
* The option key for the Application key pair.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const KEYPAIR_OPTION_KEY = 'activitypub_application_keypair';
|
||||
|
||||
/**
|
||||
* The preferred username for the Application actor.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const USERNAME = 'application';
|
||||
|
||||
/**
|
||||
* Initialize the class, registering WordPress hooks.
|
||||
*
|
||||
* @since 9.1.0
|
||||
*/
|
||||
public static function init() {
|
||||
/*
|
||||
* Priority 2: must run after Integration\Webfinger::add_pseudo_user_discovery (priority 1),
|
||||
* which returns WP_Error for 'application' since it is not in the Actors collection.
|
||||
*/
|
||||
\add_filter( 'webfinger_data', array( self::class, 'add_webfinger_discovery' ), 2, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* WebFinger discovery filter callback.
|
||||
*
|
||||
* @since 9.1.0
|
||||
*
|
||||
* @param array $jrd The jrd array.
|
||||
* @param string $uri The WebFinger resource.
|
||||
*
|
||||
* @return array The jrd array or Application WebFinger data.
|
||||
*/
|
||||
public static function add_webfinger_discovery( $jrd, $uri ) {
|
||||
/*
|
||||
* Respect a profile already resolved at an earlier priority — for example
|
||||
* on sites whose blog identifier was set to "application" before the name
|
||||
* was reserved for the Application actor.
|
||||
*/
|
||||
if ( $jrd && ! \is_wp_error( $jrd ) ) {
|
||||
return $jrd;
|
||||
}
|
||||
|
||||
$data = self::get_webfinger_data( $uri );
|
||||
|
||||
if ( $data ) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
return $jrd;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Application actor ID (URL).
|
||||
*
|
||||
* @since 9.1.0
|
||||
*
|
||||
* @return string The Application ID.
|
||||
*/
|
||||
public static function get_id() {
|
||||
return get_rest_url_by_path( 'application' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the pretty URL for the Application actor.
|
||||
*
|
||||
* @since 9.1.0
|
||||
*
|
||||
* @return string The Application URL.
|
||||
*/
|
||||
public static function get_url() {
|
||||
return self::get_id();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the WebFinger identifier for the Application.
|
||||
*
|
||||
* @since 9.1.0
|
||||
*
|
||||
* @return string The WebFinger identifier (e.g. application@example.com).
|
||||
*/
|
||||
public static function get_webfinger() {
|
||||
return self::USERNAME . '@' . home_host();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the icon for the Application.
|
||||
*
|
||||
* @since 9.1.0
|
||||
*
|
||||
* @return string[] The icon array with 'type' and 'url'.
|
||||
*/
|
||||
public static function get_icon() {
|
||||
return site_icon();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the published date of the Application.
|
||||
*
|
||||
* @since 9.1.0
|
||||
*
|
||||
* @return string The published date in RFC3339 format.
|
||||
*/
|
||||
public static function get_published() {
|
||||
$first_post = new \WP_Query(
|
||||
array(
|
||||
'orderby' => 'date',
|
||||
'order' => 'ASC',
|
||||
'posts_per_page' => 1,
|
||||
'no_found_rows' => true,
|
||||
'ignore_sticky_posts' => true,
|
||||
'update_post_meta_cache' => false,
|
||||
'update_post_term_cache' => false,
|
||||
)
|
||||
);
|
||||
|
||||
$time = false;
|
||||
|
||||
if ( ! empty( $first_post->posts[0] ) ) {
|
||||
$time = \strtotime( $first_post->posts[0]->post_date_gmt );
|
||||
}
|
||||
|
||||
if ( false === $time ) {
|
||||
$time = \time();
|
||||
}
|
||||
|
||||
return \gmdate( ACTIVITYPUB_DATE_TIME_RFC3339, $time );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key ID for HTTP signatures.
|
||||
*
|
||||
* @since 9.1.0
|
||||
*
|
||||
* @return string The key ID.
|
||||
*/
|
||||
public static function get_key_id() {
|
||||
return self::get_id() . '#main-key';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the public key PEM for the Application.
|
||||
*
|
||||
* @since 9.1.0
|
||||
*
|
||||
* @return string|null The public key PEM.
|
||||
*/
|
||||
public static function get_public_key() {
|
||||
$key_pair = self::get_keypair();
|
||||
return $key_pair['public_key'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the private key for the Application.
|
||||
*
|
||||
* @since 9.1.0
|
||||
*
|
||||
* @return string|null The private key.
|
||||
*/
|
||||
public static function get_private_key() {
|
||||
$key_pair = self::get_keypair();
|
||||
return $key_pair['private_key'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key pair for the Application.
|
||||
*
|
||||
* @since 9.1.0
|
||||
*
|
||||
* @return array The key pair with 'public_key' and 'private_key'.
|
||||
*/
|
||||
public static function get_keypair() {
|
||||
return Signature::get_key_pair(
|
||||
self::KEYPAIR_OPTION_KEY,
|
||||
function () {
|
||||
return self::check_legacy_key_pair();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for legacy key pair options.
|
||||
*
|
||||
* @since 9.1.0
|
||||
*
|
||||
* @return array|false The key pair or false.
|
||||
*/
|
||||
private static function check_legacy_key_pair() {
|
||||
/*
|
||||
* Generic actor key pair option (array form) used for the former application
|
||||
* user (ID -1). Checked here so the key survives even if get_keypair() runs
|
||||
* before migrate_application_keypair_option() has had a chance to rename it.
|
||||
*/
|
||||
$key_pair = \get_option( 'activitypub_keypair_for_-1' );
|
||||
|
||||
if ( \is_array( $key_pair ) && ! empty( $key_pair['public_key'] ) && ! empty( $key_pair['private_key'] ) ) {
|
||||
return array(
|
||||
'private_key' => $key_pair['private_key'],
|
||||
'public_key' => $key_pair['public_key'],
|
||||
);
|
||||
}
|
||||
|
||||
// Even older separate key options.
|
||||
$public_key = \get_option( 'activitypub_application_user_public_key' );
|
||||
$private_key = \get_option( 'activitypub_application_user_private_key' );
|
||||
|
||||
if ( ! empty( $public_key ) && \is_string( $public_key ) && ! empty( $private_key ) && \is_string( $private_key ) ) {
|
||||
return array(
|
||||
'private_key' => $private_key,
|
||||
'public_key' => $public_key,
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the URI matches the Application actor and return WebFinger data.
|
||||
*
|
||||
* @since 9.1.0
|
||||
*
|
||||
* Handles the following URI formats:
|
||||
* - acct:application@example.com / application@example.com
|
||||
* - http(s)://example.com/@application
|
||||
* - http(s)://example.com/wp-json/activitypub/1.0/application
|
||||
*
|
||||
* @param string $uri The WebFinger resource URI.
|
||||
*
|
||||
* @return array|false The WebFinger profile data or false if not the Application.
|
||||
*/
|
||||
public static function get_webfinger_data( $uri ) {
|
||||
if ( ! self::is_application_resource( $uri ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$application_id = self::get_id();
|
||||
|
||||
return array(
|
||||
'subject' => sprintf( 'acct:%s', self::get_webfinger() ),
|
||||
'aliases' => array( $application_id ),
|
||||
'links' => array(
|
||||
array(
|
||||
'rel' => 'self',
|
||||
'type' => 'application/activity+json',
|
||||
'href' => $application_id,
|
||||
'properties' => array(
|
||||
'https://www.w3.org/ns/activitystreams#type' => 'Application',
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a URI refers to the Application actor.
|
||||
*
|
||||
* @since 9.1.0
|
||||
*
|
||||
* @param string $uri The URI to check.
|
||||
*
|
||||
* @return bool True if the URI refers to the Application.
|
||||
*/
|
||||
public static function is_application_resource( $uri ) {
|
||||
$identifier_and_host = Webfinger::get_identifier_and_host( $uri );
|
||||
|
||||
if ( \is_wp_error( $identifier_and_host ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
list( $identifier, $host ) = $identifier_and_host;
|
||||
|
||||
// The resource must point at this site, or at its pre-migration host.
|
||||
$host = normalize_host( $host );
|
||||
if ( normalize_host( home_host() ) !== $host && normalize_host( \get_option( 'activitypub_old_host' ) ) !== $host ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// URL forms: the REST actor ID or the pretty /@application profile path.
|
||||
if ( false !== \strpos( $identifier, '://' ) ) {
|
||||
$identifier = normalize_url( $identifier );
|
||||
|
||||
return normalize_url( self::get_id() ) === $identifier
|
||||
|| normalize_url( \home_url( '/@' . self::USERNAME ) ) === $identifier;
|
||||
}
|
||||
|
||||
// acct form: application@host.
|
||||
$username = \strstr( \str_replace( 'acct:', '', $identifier ), '@', true );
|
||||
|
||||
return self::USERNAME === $username;
|
||||
}
|
||||
}
|
||||
@ -49,7 +49,7 @@ class Attachments {
|
||||
// First, import inline images from the post content.
|
||||
$inline_mappings = self::import_inline_images( $post_id, $author_id );
|
||||
|
||||
if ( empty( $attachments ) || ! is_array( $attachments ) ) {
|
||||
if ( empty( $attachments ) || ! \is_array( $attachments ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
@ -114,7 +114,7 @@ class Attachments {
|
||||
}
|
||||
|
||||
// Find all img tags in the content.
|
||||
preg_match_all( '/<img[^>]+src=["\']([^"\']+)["\'][^>]*>/i', $post->post_content, $matches );
|
||||
\preg_match_all( '/<img[^>]+src=["\']([^"\']+)["\'][^>]*>/i', $post->post_content, $matches );
|
||||
|
||||
if ( empty( $matches[1] ) ) {
|
||||
return array();
|
||||
@ -172,7 +172,7 @@ class Attachments {
|
||||
$attachment = \get_object_vars( $attachment );
|
||||
}
|
||||
|
||||
if ( ! is_array( $attachment ) || empty( $attachment['url'] ) ) {
|
||||
if ( ! \is_array( $attachment ) || empty( $attachment['url'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -207,7 +207,7 @@ class Attachments {
|
||||
|
||||
$filesystem = new \WP_Filesystem_Direct( null );
|
||||
|
||||
$is_local = ! preg_match( '#^https?://#i', $attachment_data['url'] );
|
||||
$is_local = ! \preg_match( '#^https?://#i', $attachment_data['url'] );
|
||||
|
||||
if ( $is_local ) {
|
||||
// Validate local path is within allowed directories to prevent file disclosure.
|
||||
@ -219,7 +219,7 @@ class Attachments {
|
||||
// Read local file from disk.
|
||||
if ( ! $filesystem->exists( $attachment_data['url'] ) ) {
|
||||
/* translators: %s: file path */
|
||||
return new \WP_Error( 'file_not_found', sprintf( \__( 'File not found: %s', 'activitypub' ), $attachment_data['url'] ) );
|
||||
return new \WP_Error( 'file_not_found', \sprintf( \__( 'File not found: %s', 'activitypub' ), $attachment_data['url'] ) );
|
||||
}
|
||||
|
||||
// Copy to temp file so media_handle_sideload doesn't move the original.
|
||||
@ -279,7 +279,7 @@ class Attachments {
|
||||
// Add alt text for images.
|
||||
if ( ! empty( $attachment_data['name'] ) ) {
|
||||
$original_mime = $attachment_data['mediaType'] ?? '';
|
||||
if ( 'image' === strtok( $original_mime, '/' ) ) {
|
||||
if ( 'image' === \strtok( $original_mime, '/' ) ) {
|
||||
$post_data['meta_input']['_wp_attachment_image_alt'] = $attachment_data['name'];
|
||||
}
|
||||
}
|
||||
@ -459,7 +459,7 @@ class Attachments {
|
||||
}
|
||||
|
||||
$media = self::generate_media_markup( $attachment_ids );
|
||||
$separator = empty( trim( $post->post_content ) ) ? '' : "\n\n";
|
||||
$separator = empty( \trim( $post->post_content ) ) ? '' : "\n\n";
|
||||
|
||||
\wp_update_post(
|
||||
array(
|
||||
@ -498,11 +498,11 @@ class Attachments {
|
||||
}
|
||||
|
||||
// Default to block markup.
|
||||
$type = strtok( \get_post_mime_type( $attachment_ids[0] ), '/' );
|
||||
$type = \strtok( \get_post_mime_type( $attachment_ids[0] ), '/' );
|
||||
|
||||
// Single video or audio file.
|
||||
if ( 1 === \count( $attachment_ids ) && ( 'video' === $type || 'audio' === $type ) ) {
|
||||
return sprintf(
|
||||
return \sprintf(
|
||||
'<!-- wp:%1$s {"id":"%2$s"} --><figure class="wp-block-%1$s"><%1$s controls src="%3$s"></%1$s></figure><!-- /wp:%1$s -->',
|
||||
\esc_attr( $type ),
|
||||
\esc_attr( $attachment_ids[0] ),
|
||||
@ -640,7 +640,7 @@ class Attachments {
|
||||
}
|
||||
|
||||
$media = self::generate_files_markup( $files );
|
||||
$separator = empty( trim( $content ) ) ? '' : "\n\n";
|
||||
$separator = empty( \trim( $content ) ) ? '' : "\n\n";
|
||||
|
||||
self::update_object_content( $object_id, $object_type, $content . $separator . $media );
|
||||
}
|
||||
@ -683,11 +683,11 @@ class Attachments {
|
||||
}
|
||||
|
||||
// Default to block markup.
|
||||
$type = strtok( $files[0]['mime_type'], '/' );
|
||||
$type = \strtok( $files[0]['mime_type'], '/' );
|
||||
|
||||
// Single video or audio file.
|
||||
if ( 1 === \count( $files ) && ( 'video' === $type || 'audio' === $type ) ) {
|
||||
return sprintf(
|
||||
return \sprintf(
|
||||
'<!-- wp:%1$s --><figure class="wp-block-%1$s"><%1$s controls src="%2$s"></%1$s></figure><!-- /wp:%1$s -->',
|
||||
\esc_attr( $type ),
|
||||
\esc_url( $files[0]['url'] )
|
||||
|
||||
@ -9,6 +9,9 @@ namespace Activitypub;
|
||||
|
||||
use Activitypub\Cache\Stats_Image;
|
||||
use Activitypub\Collection\Actors;
|
||||
use Activitypub\Collection\Followers;
|
||||
use Activitypub\Collection\Following;
|
||||
use Activitypub\Collection\Remote_Actors;
|
||||
|
||||
/**
|
||||
* Block class.
|
||||
@ -105,22 +108,22 @@ class Blocks {
|
||||
'noteLength' => ACTIVITYPUB_NOTE_LENGTH,
|
||||
'statsImageUrlEndpoint' => Stats_Image::is_available() ? \get_rest_url( null, ACTIVITYPUB_REST_NAMESPACE . '/stats/image-url/{user_id}/{year}' ) : '',
|
||||
);
|
||||
wp_localize_script( 'wp-editor', '_activityPubOptions', $data );
|
||||
\wp_localize_script( 'wp-editor', '_activityPubOptions', $data );
|
||||
|
||||
// Check for our supported post types.
|
||||
$current_screen = \get_current_screen();
|
||||
$ap_post_types = \get_post_types_by_support( 'activitypub' );
|
||||
if ( ! $current_screen || ! in_array( $current_screen->post_type, $ap_post_types, true ) ) {
|
||||
if ( ! $current_screen || ! \in_array( $current_screen->post_type, $ap_post_types, true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$asset_data = include ACTIVITYPUB_PLUGIN_DIR . 'build/editor-plugin/plugin.asset.php';
|
||||
$plugin_url = plugins_url( 'build/editor-plugin/plugin.js', ACTIVITYPUB_PLUGIN_FILE );
|
||||
wp_enqueue_script( 'activitypub-block-editor', $plugin_url, $asset_data['dependencies'], $asset_data['version'], true );
|
||||
$plugin_url = \plugins_url( 'build/editor-plugin/plugin.js', ACTIVITYPUB_PLUGIN_FILE );
|
||||
\wp_enqueue_script( 'activitypub-block-editor', $plugin_url, $asset_data['dependencies'], $asset_data['version'], true );
|
||||
|
||||
$asset_data = include ACTIVITYPUB_PLUGIN_DIR . 'build/pre-publish-panel/plugin.asset.php';
|
||||
$plugin_url = plugins_url( 'build/pre-publish-panel/plugin.js', ACTIVITYPUB_PLUGIN_FILE );
|
||||
wp_enqueue_script( 'activitypub-pre-publish-panel', $plugin_url, $asset_data['dependencies'], $asset_data['version'], true );
|
||||
$plugin_url = \plugins_url( 'build/pre-publish-panel/plugin.js', ACTIVITYPUB_PLUGIN_FILE );
|
||||
\wp_enqueue_script( 'activitypub-pre-publish-panel', $plugin_url, $asset_data['dependencies'], $asset_data['version'], true );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -134,8 +137,8 @@ class Blocks {
|
||||
}
|
||||
|
||||
$asset_data = include ACTIVITYPUB_PLUGIN_DIR . 'build/reply-intent/plugin.asset.php';
|
||||
$plugin_url = plugins_url( 'build/reply-intent/plugin.js', ACTIVITYPUB_PLUGIN_FILE );
|
||||
wp_enqueue_script( 'activitypub-reply-intent', $plugin_url, $asset_data['dependencies'], $asset_data['version'], true );
|
||||
$plugin_url = \plugins_url( 'build/reply-intent/plugin.js', ACTIVITYPUB_PLUGIN_FILE );
|
||||
\wp_enqueue_script( 'activitypub-reply-intent', $plugin_url, $asset_data['dependencies'], $asset_data['version'], true );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -289,7 +292,7 @@ class Blocks {
|
||||
*/
|
||||
public static function register_rest_fields() {
|
||||
// Register the post_count field for Follow Me block.
|
||||
register_rest_field(
|
||||
\register_rest_field(
|
||||
'user',
|
||||
'post_count',
|
||||
array(
|
||||
@ -302,7 +305,7 @@ class Blocks {
|
||||
* @return int The number of published posts.
|
||||
*/
|
||||
'get_callback' => static function ( $response, $field_name, $request ) {
|
||||
return (int) count_user_posts( $request->get_param( 'id' ), 'post', true );
|
||||
return (int) \count_user_posts( $request->get_param( 'id' ), 'post', true );
|
||||
},
|
||||
'schema' => array(
|
||||
'description' => 'Number of published posts',
|
||||
@ -320,8 +323,8 @@ class Blocks {
|
||||
* @return int|null The user ID, or null if the 'inherit' string is not supported in this context.
|
||||
*/
|
||||
public static function get_user_id( $user_string ) {
|
||||
if ( is_numeric( $user_string ) ) {
|
||||
return absint( $user_string );
|
||||
if ( \is_numeric( $user_string ) ) {
|
||||
return \absint( $user_string );
|
||||
}
|
||||
|
||||
// If the user string is 'blog', return the Blog User ID.
|
||||
@ -335,30 +338,30 @@ class Blocks {
|
||||
}
|
||||
|
||||
// For a homepage/front page, if the Blog User is active, use it.
|
||||
if ( ( is_front_page() || is_home() ) && ! is_user_type_disabled( 'blog' ) ) {
|
||||
if ( ( \is_front_page() || \is_home() ) && ! is_user_type_disabled( 'blog' ) ) {
|
||||
return Actors::BLOG_USER_ID;
|
||||
}
|
||||
|
||||
// If we're in a loop, use the post author.
|
||||
$author_id = get_the_author_meta( 'ID' );
|
||||
$author_id = \get_the_author_meta( 'ID' );
|
||||
if ( $author_id ) {
|
||||
return $author_id;
|
||||
}
|
||||
|
||||
// For other pages, the queried object will clue us in.
|
||||
$queried_object = get_queried_object();
|
||||
$queried_object = \get_queried_object();
|
||||
if ( ! $queried_object ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// If we're on a user archive page, use that user's ID.
|
||||
if ( is_a( $queried_object, 'WP_User' ) ) {
|
||||
if ( \is_a( $queried_object, 'WP_User' ) ) {
|
||||
return $queried_object->ID;
|
||||
}
|
||||
|
||||
// For a single post, use the post author's ID.
|
||||
if ( is_a( $queried_object, 'WP_Post' ) ) {
|
||||
return get_the_author_meta( 'ID' );
|
||||
if ( \is_a( $queried_object, 'WP_Post' ) ) {
|
||||
return \get_the_author_meta( 'ID' );
|
||||
}
|
||||
|
||||
// We won't properly account for some conditions, like tag archives.
|
||||
@ -381,12 +384,12 @@ class Blocks {
|
||||
}
|
||||
|
||||
$attributes = \wp_parse_args( $attributes );
|
||||
$block_name = 'followers' === $endpoint ? __( 'Followers', 'activitypub' ) : __( 'Following', 'activitypub' );
|
||||
$block_name = 'followers' === $endpoint ? \__( 'Followers', 'activitypub' ) : \__( 'Following', 'activitypub' );
|
||||
|
||||
if ( empty( $content ) ) {
|
||||
// Fallback for v1.0.0 blocks.
|
||||
/* translators: %s: Block type (Followers or Following) */
|
||||
$_title = $attributes['title'] ?? \sprintf( __( 'Fediverse %s', 'activitypub' ), $block_name );
|
||||
$_title = $attributes['title'] ?? \sprintf( \__( 'Fediverse %s', 'activitypub' ), $block_name );
|
||||
$content = '<h3 class="wp-block-heading">' . \esc_html( $_title ) . '</h3>';
|
||||
unset( $attributes['title'], $attributes['className'] );
|
||||
} else {
|
||||
@ -415,17 +418,17 @@ class Blocks {
|
||||
|
||||
// Query the appropriate collection.
|
||||
if ( 'followers' === $endpoint ) {
|
||||
$data = \Activitypub\Collection\Followers::query( $user_id, $_per_page );
|
||||
$data = Followers::query( $user_id, $_per_page );
|
||||
$items = $data['followers'];
|
||||
} else {
|
||||
$data = \Activitypub\Collection\Following::query( $user_id, $_per_page );
|
||||
$data = Following::query( $user_id, $_per_page );
|
||||
$items = $data['following'];
|
||||
}
|
||||
|
||||
// Prepare items data for the Interactivity API context.
|
||||
$prepared_items = \array_map(
|
||||
static function ( $item ) {
|
||||
$actor = \Activitypub\Collection\Remote_Actors::get_actor( $item );
|
||||
$actor = Remote_Actors::get_actor( $item );
|
||||
|
||||
// Restrict URLs to http/https schemes to prevent XSS via javascript: URIs.
|
||||
$url = object_to_uri( $actor->get_url() ) ?: $actor->get_id();
|
||||
@ -474,7 +477,7 @@ class Blocks {
|
||||
);
|
||||
|
||||
/* translators: %s: Block type (Followers or Following) */
|
||||
$nav_label = \sprintf( __( '%s navigation', 'activitypub' ), $block_name );
|
||||
$nav_label = \sprintf( \__( '%s navigation', 'activitypub' ), $block_name );
|
||||
|
||||
\ob_start();
|
||||
?>
|
||||
@ -512,8 +515,8 @@ class Blocks {
|
||||
}
|
||||
|
||||
$url = $attrs['url'];
|
||||
$shortcode = trim( $content );
|
||||
$name = trim( $shortcode, ':' );
|
||||
$shortcode = \trim( $content );
|
||||
$name = \trim( $shortcode, ':' );
|
||||
|
||||
/**
|
||||
* Filters a remote media URL for caching.
|
||||
@ -674,11 +677,26 @@ class Blocks {
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* In feed contexts (RSS, Atom, and anything else WordPress treats as a feed) the styled
|
||||
* embed card depends on plugin CSS that isn't loaded, so it degrades to an unreadable
|
||||
* wall of text. Substitute the same simplified mention link the federation path uses,
|
||||
* and if the remote lookup fails fall through to the plain `<a class="u-in-reply-to">`
|
||||
* link below so the feed item still surfaces *some* indication that it's a reply.
|
||||
*/
|
||||
if ( \is_feed() ) {
|
||||
$mention = self::generate_reply_link( '', array( 'attrs' => $attrs ) );
|
||||
if ( ! empty( $mention ) ) {
|
||||
return $mention;
|
||||
}
|
||||
$attrs['embedPost'] = false;
|
||||
}
|
||||
|
||||
$show_embed = isset( $attrs['embedPost'] ) && $attrs['embedPost'];
|
||||
|
||||
$wrapper_attrs = get_block_wrapper_attributes(
|
||||
$wrapper_attrs = \get_block_wrapper_attributes(
|
||||
array(
|
||||
'aria-label' => __( 'Reply', 'activitypub' ),
|
||||
'aria-label' => \__( 'Reply', 'activitypub' ),
|
||||
'class' => 'activitypub-reply-block',
|
||||
'data-in-reply-to' => $attrs['url'],
|
||||
)
|
||||
@ -691,7 +709,7 @@ class Blocks {
|
||||
if ( $show_embed ) {
|
||||
// Use the theme's content width or a reasonable default to avoid narrow embeds.
|
||||
$embed_width = ! empty( $GLOBALS['content_width'] ) ? $GLOBALS['content_width'] : 600;
|
||||
$embed = wp_oembed_get( $attrs['url'], array( 'width' => $embed_width ) );
|
||||
$embed = \wp_oembed_get( $attrs['url'], array( 'width' => $embed_width ) );
|
||||
if ( $embed ) {
|
||||
$html .= $embed;
|
||||
\wp_enqueue_script( 'wp-embed' );
|
||||
@ -700,12 +718,12 @@ class Blocks {
|
||||
|
||||
// Show the link if embed is not requested or if embed failed.
|
||||
if ( ! $show_embed || ! $embed ) {
|
||||
$html .= sprintf(
|
||||
$html .= \sprintf(
|
||||
'<p><a title="%2$s" aria-label="%2$s" href="%1$s" class="u-in-reply-to" target="_blank">%3$s</a></p>',
|
||||
esc_url( $attrs['url'] ),
|
||||
esc_attr__( 'This post is a response to the referenced content.', 'activitypub' ),
|
||||
\esc_url( $attrs['url'] ),
|
||||
\esc_attr__( 'This post is a response to the referenced content.', 'activitypub' ),
|
||||
// translators: %s is the URL of the post being replied to.
|
||||
sprintf( __( '↬%s', 'activitypub' ), \str_replace( array( 'https://', 'http://' ), '', esc_url( $attrs['url'] ) ) )
|
||||
\sprintf( \__( '↬%s', 'activitypub' ), \str_replace( array( 'https://', 'http://' ), '', \esc_url( $attrs['url'] ) ) )
|
||||
);
|
||||
}
|
||||
|
||||
@ -817,7 +835,7 @@ class Blocks {
|
||||
'show_pagination' => true,
|
||||
'total' => 0,
|
||||
'per_page' => 10,
|
||||
'nav_label' => __( 'Actor navigation', 'activitypub' ),
|
||||
'nav_label' => \__( 'Actor navigation', 'activitypub' ),
|
||||
);
|
||||
|
||||
$args = \wp_parse_args( $args, $defaults );
|
||||
@ -1040,7 +1058,7 @@ class Blocks {
|
||||
'<p class="ap-reply-mention"><a rel="mention ugc" href="%1$s" title="%2$s">%3$s</a></p>',
|
||||
\esc_url( $url ),
|
||||
\esc_attr( $webfinger ),
|
||||
\esc_html( '@' . strtok( $webfinger, '@' ) )
|
||||
\esc_html( '@' . \strtok( $webfinger, '@' ) )
|
||||
);
|
||||
}
|
||||
|
||||
@ -1146,7 +1164,7 @@ class Blocks {
|
||||
if ( ! isset( $block['attrs']['url'] ) ) {
|
||||
return $block_content;
|
||||
}
|
||||
return '<p><a href="' . esc_url( $block['attrs']['url'] ) . '">' . $block['attrs']['url'] . '</a></p>';
|
||||
return '<p><a href="' . \esc_url( $block['attrs']['url'] ) . '">' . $block['attrs']['url'] . '</a></p>';
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1289,7 +1307,7 @@ class Blocks {
|
||||
$query_post_type = $query->get( 'post_type' );
|
||||
if ( ! empty( $query_post_type ) && 'any' !== $query_post_type ) {
|
||||
$query_post_types = (array) $query_post_type;
|
||||
if ( ! array_intersect( $query_post_types, \get_post_types_by_support( 'activitypub' ) ) ) {
|
||||
if ( ! \array_intersect( $query_post_types, \get_post_types_by_support( 'activitypub' ) ) ) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,187 @@
|
||||
<?php
|
||||
/**
|
||||
* Blurhash encoder.
|
||||
*
|
||||
* @package Activitypub
|
||||
*/
|
||||
|
||||
namespace Activitypub;
|
||||
|
||||
/**
|
||||
* Encodes pixel data into a Blurhash placeholder string.
|
||||
*
|
||||
* A first-party port of the public Blurhash encode algorithm
|
||||
* (https://github.com/woltapp/blurhash, MIT). Adapted from
|
||||
* Automattic/FOSSE (https://github.com/Automattic/fosse), which used the
|
||||
* kornrunner/blurhash library; this replaces that runtime dependency so
|
||||
* the plugin ships Blurhash support out of the box.
|
||||
*
|
||||
* @since 9.0.0
|
||||
*/
|
||||
class Blurhash_Encoder {
|
||||
|
||||
/**
|
||||
* Base83 alphabet defined by the Blurhash spec.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const ALPHABET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~';
|
||||
|
||||
/**
|
||||
* Encode a pixel array into a Blurhash string.
|
||||
*
|
||||
* @param array $pixels Row-major `[r,g,b][][]` array (0-255 per channel), indexed `[$y][$x]`.
|
||||
* @param int $components_x Horizontal component count (1-9).
|
||||
* @param int $components_y Vertical component count (1-9).
|
||||
* @return string Blurhash string, or '' on invalid input.
|
||||
*/
|
||||
public static function encode( $pixels, $components_x, $components_y ) {
|
||||
$components_x = (int) $components_x;
|
||||
$components_y = (int) $components_y;
|
||||
|
||||
if ( $components_x < 1 || $components_x > 9 || $components_y < 1 || $components_y > 9 ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$height = \count( $pixels );
|
||||
if ( $height < 1 || ! isset( $pixels[0] ) || ! \is_array( $pixels[0] ) ) {
|
||||
return '';
|
||||
}
|
||||
$width = \count( $pixels[0] );
|
||||
if ( $width < 1 ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$factors = array();
|
||||
for ( $y = 0; $y < $components_y; $y++ ) {
|
||||
for ( $x = 0; $x < $components_x; $x++ ) {
|
||||
$normalisation = ( 0 === $x && 0 === $y ) ? 1.0 : 2.0;
|
||||
$r = 0.0;
|
||||
$g = 0.0;
|
||||
$b = 0.0;
|
||||
for ( $i = 0; $i < $width; $i++ ) {
|
||||
for ( $j = 0; $j < $height; $j++ ) {
|
||||
$basis = $normalisation
|
||||
* \cos( \pi() * $x * $i / $width )
|
||||
* \cos( \pi() * $y * $j / $height );
|
||||
$pixel = $pixels[ $j ][ $i ];
|
||||
$r += $basis * self::srgb_to_linear( (int) $pixel[0] );
|
||||
$g += $basis * self::srgb_to_linear( (int) $pixel[1] );
|
||||
$b += $basis * self::srgb_to_linear( (int) $pixel[2] );
|
||||
}
|
||||
}
|
||||
$scale = 1.0 / ( $width * $height );
|
||||
$factors[] = array( $r * $scale, $g * $scale, $b * $scale );
|
||||
}
|
||||
}
|
||||
|
||||
$dc = $factors[0];
|
||||
$ac = \array_slice( $factors, 1 );
|
||||
|
||||
$hash = self::encode83( ( $components_x - 1 ) + ( $components_y - 1 ) * 9, 1 );
|
||||
$max_value = 1.0;
|
||||
|
||||
if ( \count( $ac ) > 0 ) {
|
||||
$actual_max = 0.0;
|
||||
foreach ( $ac as $factor ) {
|
||||
$actual_max = \max( $actual_max, \abs( $factor[0] ), \abs( $factor[1] ), \abs( $factor[2] ) );
|
||||
}
|
||||
$quantised_max = (int) \max( 0, \min( 82, \floor( $actual_max * 166 - 0.5 ) ) );
|
||||
$max_value = ( $quantised_max + 1 ) / 166;
|
||||
$hash .= self::encode83( $quantised_max, 1 );
|
||||
} else {
|
||||
$hash .= self::encode83( 0, 1 );
|
||||
}
|
||||
|
||||
$hash .= self::encode83( self::encode_dc( $dc ), 4 );
|
||||
foreach ( $ac as $factor ) {
|
||||
$hash .= self::encode83( self::encode_ac( $factor, $max_value ), 2 );
|
||||
}
|
||||
|
||||
return $hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode an integer to a fixed-length base83 string.
|
||||
*
|
||||
* @param int $value Value.
|
||||
* @param int $length Output length.
|
||||
* @return string
|
||||
*/
|
||||
private static function encode83( $value, $length ) {
|
||||
$value = (int) $value;
|
||||
$result = '';
|
||||
for ( $i = 1; $i <= $length; $i++ ) {
|
||||
$digit = (int) ( $value / ( 83 ** ( $length - $i ) ) ) % 83;
|
||||
$result .= self::ALPHABET[ $digit ];
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an sRGB 0-255 channel to linear 0-1.
|
||||
*
|
||||
* @param int $value Channel value.
|
||||
* @return float
|
||||
*/
|
||||
private static function srgb_to_linear( $value ) {
|
||||
$v = $value / 255.0;
|
||||
if ( $v <= 0.04045 ) {
|
||||
return $v / 12.92;
|
||||
}
|
||||
return \pow( ( $v + 0.055 ) / 1.055, 2.4 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a linear 0-1 channel to sRGB 0-255.
|
||||
*
|
||||
* @param float $value Linear value.
|
||||
* @return int
|
||||
*/
|
||||
private static function linear_to_srgb( $value ) {
|
||||
$v = \max( 0.0, \min( 1.0, $value ) );
|
||||
if ( $v <= 0.0031308 ) {
|
||||
return (int) ( $v * 12.92 * 255 + 0.5 );
|
||||
}
|
||||
return (int) ( ( 1.055 * \pow( $v, 1 / 2.4 ) - 0.055 ) * 255 + 0.5 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode the DC (average color) factor.
|
||||
*
|
||||
* @param array $factor `[r,g,b]` linear floats.
|
||||
* @return int
|
||||
*/
|
||||
private static function encode_dc( $factor ) {
|
||||
$r = self::linear_to_srgb( $factor[0] );
|
||||
$g = self::linear_to_srgb( $factor[1] );
|
||||
$b = self::linear_to_srgb( $factor[2] );
|
||||
return ( $r << 16 ) + ( $g << 8 ) + $b;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode an AC factor against the maximum value.
|
||||
*
|
||||
* @param array $factor `[r,g,b]` linear floats.
|
||||
* @param float $max_value Quantisation maximum.
|
||||
* @return int
|
||||
*/
|
||||
private static function encode_ac( $factor, $max_value ) {
|
||||
$quant_r = (int) \max( 0, \min( 18, \floor( self::sign_pow( $factor[0] / $max_value, 0.5 ) * 9 + 9.5 ) ) );
|
||||
$quant_g = (int) \max( 0, \min( 18, \floor( self::sign_pow( $factor[1] / $max_value, 0.5 ) * 9 + 9.5 ) ) );
|
||||
$quant_b = (int) \max( 0, \min( 18, \floor( self::sign_pow( $factor[2] / $max_value, 0.5 ) * 9 + 9.5 ) ) );
|
||||
return $quant_r * 19 * 19 + $quant_g * 19 + $quant_b;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sign-preserving power.
|
||||
*
|
||||
* @param float $value Base.
|
||||
* @param float $exp Exponent.
|
||||
* @return float
|
||||
*/
|
||||
private static function sign_pow( $value, $exp ) {
|
||||
$result = \pow( \abs( $value ), $exp );
|
||||
return $value < 0 ? -$result : $result;
|
||||
}
|
||||
}
|
||||
745
wp-content/plugins/activitypub/includes/class-blurhash.php
Normal file
745
wp-content/plugins/activitypub/includes/class-blurhash.php
Normal file
@ -0,0 +1,745 @@
|
||||
<?php
|
||||
/**
|
||||
* Blurhash encoder + storage for image attachments.
|
||||
*
|
||||
* @package Activitypub
|
||||
*/
|
||||
|
||||
namespace Activitypub;
|
||||
|
||||
/**
|
||||
* Compute, store, and inject Blurhash placeholder strings for image
|
||||
* attachments so federated ActivityPub posts emit
|
||||
* `attachment[].blurhash` — the colored-blur preview that Pixelfed,
|
||||
* Mastodon, and other fediverse clients paint while the full image
|
||||
* is still loading. Without it, federated WP photos sit on a grey
|
||||
* placeholder where native uploads paint instantly; with it, the
|
||||
* loading state matches native.
|
||||
*
|
||||
* Encoding runs at upload time via `wp_generate_attachment_metadata`,
|
||||
* deferred to cron so the upload UI returns immediately. The hash
|
||||
* is persisted as attachment postmeta ({@see self::META_KEY}) and
|
||||
* read back by the `activitypub_attachment` projector — zero
|
||||
* per-publish CPU cost, federation hot path stays cheap.
|
||||
*
|
||||
* Pure no-op when GD isn't available: the encoder needs an
|
||||
* `[r,g,b][][]` pixel array, and GD's `imagecreatefrom*` family is
|
||||
* the only host-portable way to build one. Sites running Imagick-only
|
||||
* just don't get blurhash placeholders — attachments still federate,
|
||||
* minus the field. Same posture for any other failure (unreadable
|
||||
* file, encoder exception, deleted attachment): never blocks the
|
||||
* upload, never blocks federation.
|
||||
*
|
||||
* Called from `activitypub.php` on `init`.
|
||||
* Adapted from Automattic/FOSSE (https://github.com/Automattic/fosse).
|
||||
*
|
||||
* @since 9.0.0
|
||||
*/
|
||||
class Blurhash {
|
||||
|
||||
/**
|
||||
* Postmeta key holding the encoded blurhash for an attachment.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const META_KEY = '_activitypub_blurhash';
|
||||
|
||||
/**
|
||||
* Cron hook fired for each attachment that needs a hash computed.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const CRON_HOOK = 'activitypub_blurhash_compute';
|
||||
|
||||
/**
|
||||
* DCT component count passed to the encoder as BOTH the X and
|
||||
* Y dimensions — kept as a single number because the two
|
||||
* dimensions are always equal in this encoder and splitting
|
||||
* them implies a tuning surface that doesn't exist. Wolt's
|
||||
* reference recommends 4–5 for landscape and lower for
|
||||
* portrait; 4 is the middle ground Mastodon also defaults to.
|
||||
* Hash length grows with the product, so 4×4 keeps the encoded
|
||||
* string short.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private const COMPONENTS = 4;
|
||||
|
||||
/**
|
||||
* Upper bound on stored blurhash string length, in characters.
|
||||
* A 4×4 component grid produces a 30-character hash; the
|
||||
* theoretical max for the 9×9 component grid the spec supports
|
||||
* is 99 characters. We cap a little above that as a defense
|
||||
* against postmeta poisoning — anyone with `edit_post_meta` on
|
||||
* an attachment could otherwise write arbitrary bytes that we
|
||||
* would then federate straight into the AP envelope.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private const MAX_HASH_LENGTH = 128;
|
||||
|
||||
/**
|
||||
* Image size used as the encoder's source. Blurhash encoding is
|
||||
* O(N) over pixel count and the output is a few low-frequency DCT
|
||||
* coefficients — feeding it a 12-megapixel original would burn
|
||||
* CPU producing a hash that's perceptually identical to the one
|
||||
* computed off the ~150px thumbnail. WP's `thumbnail` size is the
|
||||
* smallest variant guaranteed to exist for every uploaded image.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private const ENCODE_SIZE = 'thumbnail';
|
||||
|
||||
/**
|
||||
* Hard upper bound on the longest edge fed to the encoder. Even
|
||||
* when {@see self::resolve_encode_path()} returns the original
|
||||
* (no intermediate, fallback path, misconfigured thumbnail size),
|
||||
* the GD image is downscaled to this max edge before the per-pixel
|
||||
* array is built. Keeps the PHP array allocation bounded — without
|
||||
* this cap, a 4000×3000 original would build a 12M-cell nested
|
||||
* array (~960 MB) and OOM the cron worker.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private const MAX_ENCODE_EDGE = 64;
|
||||
|
||||
/**
|
||||
* Hard upper bound on the byte size of the source file fed into
|
||||
* GD. Defends against pathological cases where {@see self::resolve_encode_path()}
|
||||
* resolves to a huge original (or, via filterable
|
||||
* `image_get_intermediate_size`, a path that's not actually an
|
||||
* image at all). 8 MiB comfortably accommodates any realistic
|
||||
* web-photo upload at full quality.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private const MAX_ENCODE_BYTES = 8388608;
|
||||
|
||||
/**
|
||||
* Hard upper bound on the DECODED pixel count (width × height) of
|
||||
* the source image. `imagecreatefromstring()` fully decodes the
|
||||
* compressed bytes into an uncompressed GD bitmap BEFORE
|
||||
* {@see self::encode_from_attachment()} downscales to
|
||||
* {@see self::MAX_ENCODE_EDGE}, so a small, highly compressible
|
||||
* source (e.g. a flat-color PNG declaring 30000×30000) slips past
|
||||
* the {@see self::MAX_ENCODE_BYTES} byte cap yet forces a
|
||||
* multi-gigabyte allocation — an uncatchable OOM that kills the
|
||||
* cron worker or aborts a CLI backfill mid-run. We read the
|
||||
* declared dimensions with `getimagesizefromstring()` first and
|
||||
* skip (`encode_from_attachment()` returns `false`, which callers
|
||||
* treat as "we don't encode this", not a failure) when the
|
||||
* product exceeds this cap. 50 megapixels comfortably covers any
|
||||
* realistic camera/phone upload while rejecting decompression bombs.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private const MAX_ENCODE_PIXELS = 50000000;
|
||||
|
||||
/**
|
||||
* Raster MIME types GD can decode via `imagecreatefromstring`.
|
||||
* Used as the early gate that splits "we don't encode this"
|
||||
* (skip silently) from "encoder failed" (emit warning, count
|
||||
* against exit status). SVG/XML, ICO, and other formats either
|
||||
* GD can't read or aren't raster get filtered out before the
|
||||
* encoder runs.
|
||||
*
|
||||
* @var array<int, string>
|
||||
*/
|
||||
private const ENCODABLE_MIME_TYPES = array(
|
||||
'image/jpeg',
|
||||
'image/png',
|
||||
'image/gif',
|
||||
'image/webp',
|
||||
'image/avif',
|
||||
'image/bmp',
|
||||
);
|
||||
|
||||
/**
|
||||
* Register all hooks. Called from `activitypub.php` on `init`.
|
||||
*/
|
||||
public static function init(): void {
|
||||
\add_filter( 'wp_generate_attachment_metadata', array( self::class, 'schedule_encode' ), 10, 2 );
|
||||
\add_action( self::CRON_HOOK, array( self::class, 'run_encode' ), 10, 1 );
|
||||
\add_filter( 'activitypub_attachment', array( self::class, 'inject_blurhash' ), 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the stored blurhash for an attachment, or null when no
|
||||
* usable value is stored. Empty/whitespace/non-string values are
|
||||
* treated as absent, AND values that fail
|
||||
* {@see self::is_well_formed_hash()} are too — so postmeta
|
||||
* poisoning (or an old encoder bug that wrote junk) doesn't
|
||||
* leak into the federation envelope AND doesn't permanently
|
||||
* stick the cron `run_encode` short-circuit (which keys off
|
||||
* this returning non-null). Net effect: a malformed row
|
||||
* self-heals on the next `wp_generate_attachment_metadata`
|
||||
* cycle because `get()` reports absent, `run_encode` proceeds,
|
||||
* and `set()` overwrites the malformed value.
|
||||
*
|
||||
* @param int $attachment_id Attachment post ID.
|
||||
* @return string|null
|
||||
*/
|
||||
public static function get( int $attachment_id ): ?string {
|
||||
$value = \get_post_meta( $attachment_id, self::META_KEY, true );
|
||||
if ( ! \is_string( $value ) ) {
|
||||
return null;
|
||||
}
|
||||
$value = \trim( $value );
|
||||
if ( '' === $value ) {
|
||||
return null;
|
||||
}
|
||||
return self::is_well_formed_hash( $value ) ? $value : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Persist a computed blurhash on the attachment.
|
||||
*
|
||||
* @param int $attachment_id Attachment post ID.
|
||||
* @param string $hash Encoded blurhash string.
|
||||
*/
|
||||
public static function set( int $attachment_id, string $hash ): void {
|
||||
\update_post_meta( $attachment_id, self::META_KEY, $hash );
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete any stored blurhash for an attachment. Used by the
|
||||
* upload/regen invalidation path ({@see self::schedule_encode()}) so a
|
||||
* replaced image re-encodes against its latest bytes.
|
||||
*
|
||||
* @param int $attachment_id Attachment post ID.
|
||||
*/
|
||||
public static function delete( int $attachment_id ): void {
|
||||
\delete_post_meta( $attachment_id, self::META_KEY );
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute (synchronously) the blurhash for an attachment by
|
||||
* loading the configured size's file through GD and feeding the
|
||||
* pixel array to the encoder. Never throws, never warns. Used by
|
||||
* both the cron handler and the WP-CLI backfill.
|
||||
*
|
||||
* Three-state return so callers can route outcomes to the right
|
||||
* bucket: a string is success; `false` means the source is
|
||||
* deliberately outside encode policy and must be skipped silently —
|
||||
* a non-encodable attachment (non-raster mime, a deleted or
|
||||
* nonexistent attachment ID, or a format the host GD build can't
|
||||
* decode), an unavailable encoder, or a declared pixel count over
|
||||
* {@see self::MAX_ENCODE_PIXELS}; `null` is an unexpected failure
|
||||
* (the file behind an otherwise-encodable attachment is missing,
|
||||
* unreadable, or corrupt, or GD/the encoder errored) that callers
|
||||
* surface for monitoring. No native return type because union types
|
||||
* require PHP 8.0 and the plugin supports 7.4.
|
||||
*
|
||||
* @param int $attachment_id Attachment post ID.
|
||||
* @return string|false|null Hash on success, false on policy skip, null on failure.
|
||||
*/
|
||||
public static function encode_from_attachment( int $attachment_id ) {
|
||||
/*
|
||||
* Predictable unencodability (non-raster mime, missing/deleted
|
||||
* attachment, host GD that can't decode the format, or no GD at
|
||||
* all) is a policy skip, not a failure: `false` routes direct
|
||||
* callers to the silent bucket instead of the diagnostic one.
|
||||
*/
|
||||
if ( ! self::is_encodable_attachment( $attachment_id ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! self::is_encoder_runnable() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$path = self::resolve_encode_path( $attachment_id );
|
||||
if ( null === $path ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Fast-fail before reading bytes. A pathological source
|
||||
// (huge original, non-image file slipped through a filterable
|
||||
// metadata path) gets rejected without allocating PHP memory
|
||||
// for the read.
|
||||
$size = @\filesize( $path ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- stat failure returns false and we handle.
|
||||
if ( false === $size || $size < 1 || $size > self::MAX_ENCODE_BYTES ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents, WordPress.PHP.NoSilencedErrors.Discouraged -- local absolute path read; corrupt/missing file returns false and we handle.
|
||||
$bytes = @\file_get_contents( $path );
|
||||
if ( false === $bytes || '' === $bytes ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* Decode-bomb guard. `imagecreatefromstring()` fully decodes
|
||||
* the compressed bytes into an uncompressed bitmap before we
|
||||
* get a chance to downscale, so a small but highly
|
||||
* compressible source declaring huge dimensions (e.g. a
|
||||
* flat-color 30000×30000 PNG) would force a multi-gigabyte
|
||||
* allocation and OOM the worker — uncatchable, so we can't
|
||||
* recover with the try/catch below. Read the declared
|
||||
* dimensions cheaply first and skip (silent, not an error)
|
||||
* anything past the megapixel cap.
|
||||
*/
|
||||
// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- malformed header returns false and we handle.
|
||||
$dimensions = @\getimagesizefromstring( $bytes );
|
||||
if ( false === $dimensions || ! isset( $dimensions[0] ) || ! isset( $dimensions[1] ) ) {
|
||||
return null;
|
||||
}
|
||||
$declared_width = (int) $dimensions[0];
|
||||
$declared_height = (int) $dimensions[1];
|
||||
if ( $declared_width < 1 || $declared_height < 1 ) {
|
||||
return null;
|
||||
}
|
||||
if ( $declared_width * $declared_height > self::MAX_ENCODE_PIXELS ) {
|
||||
/*
|
||||
* Policy skip, not a failure: `false` routes the caller
|
||||
* to the same silent bucket as a non-raster mime, so a
|
||||
* permanently over-cap source doesn't error_log on every
|
||||
* cron run or hold the CLI backfill exit code nonzero
|
||||
* forever.
|
||||
*/
|
||||
return false;
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged -- corrupt image returns false and we handle.
|
||||
$original = @\imagecreatefromstring( $bytes );
|
||||
if ( false === $original ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* $scaled holds a second GD resource created by imagescale when
|
||||
* the image exceeds MAX_ENCODE_EDGE, $canvas a third one
|
||||
* created for the transparency flattening composite. Both are
|
||||
* kept separate from $original so all of them can be destroyed
|
||||
* in finally regardless of which code path ran.
|
||||
*/
|
||||
$scaled = null;
|
||||
$canvas = null;
|
||||
|
||||
try {
|
||||
$width = \imagesx( $original );
|
||||
$height = \imagesy( $original );
|
||||
if ( $width < 1 || $height < 1 ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// The image we will actually read pixels from — either the
|
||||
// original or a downscaled copy.
|
||||
$src_image = $original;
|
||||
|
||||
// Defensive downscale before the per-pixel loop. The
|
||||
// nested array grows quadratically with edge length, so
|
||||
// any source larger than MAX_ENCODE_EDGE gets sampled
|
||||
// down — perceptually identical output, bounded memory.
|
||||
//
|
||||
// Scale by the LONGER edge so portrait images
|
||||
// (`height > width`) don't get upscaled by a
|
||||
// fixed-width call to `imagescale`. Target dimensions
|
||||
// are computed explicitly so both edges land at or
|
||||
// below the cap. If `imagescale` fails we bail rather
|
||||
// than fall through to the per-pixel loop with the
|
||||
// original (oversized) GD image.
|
||||
if ( $width > self::MAX_ENCODE_EDGE || $height > self::MAX_ENCODE_EDGE ) {
|
||||
if ( $width >= $height ) {
|
||||
$target_width = self::MAX_ENCODE_EDGE;
|
||||
$target_height = (int) \max( 1, \round( $height * ( self::MAX_ENCODE_EDGE / $width ) ) );
|
||||
} else {
|
||||
$target_height = self::MAX_ENCODE_EDGE;
|
||||
$target_width = (int) \max( 1, \round( $width * ( self::MAX_ENCODE_EDGE / $height ) ) );
|
||||
}
|
||||
$scaled = \imagescale( $original, $target_width, $target_height );
|
||||
if ( false === $scaled ) {
|
||||
return null;
|
||||
}
|
||||
$src_image = $scaled;
|
||||
$width = $target_width;
|
||||
$height = $target_height;
|
||||
}
|
||||
|
||||
/*
|
||||
* Flatten transparency against a white background before
|
||||
* sampling. `imagecolorsforindex()` reports the raw RGB of
|
||||
* a transparent pixel (usually 0,0,0 → black) while
|
||||
* discarding alpha, so a transparent PNG/GIF/WebP logo or
|
||||
* sticker would otherwise encode to a near-black blurhash.
|
||||
* Compositing onto an opaque white canvas yields the color
|
||||
* a viewer actually sees over a typical light surface.
|
||||
* Best-effort: if any GD call fails we keep sampling the
|
||||
* un-flattened image rather than bail.
|
||||
*/
|
||||
$canvas = \imagecreatetruecolor( $width, $height );
|
||||
if ( false !== $canvas ) {
|
||||
$white = \imagecolorallocate( $canvas, 255, 255, 255 );
|
||||
if ( false !== $white ) {
|
||||
\imagefilledrectangle( $canvas, 0, 0, $width - 1, $height - 1, $white );
|
||||
\imagealphablending( $canvas, true );
|
||||
if ( \imagecopy( $canvas, $src_image, 0, 0, 0, 0, $width, $height ) ) {
|
||||
$src_image = $canvas;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$pixels = array();
|
||||
for ( $y = 0; $y < $height; $y++ ) {
|
||||
$row = array();
|
||||
for ( $x = 0; $x < $width; $x++ ) {
|
||||
$index = \imagecolorat( $src_image, $x, $y );
|
||||
$colors = \imagecolorsforindex( $src_image, $index );
|
||||
$row[] = array( $colors['red'], $colors['green'], $colors['blue'] );
|
||||
}
|
||||
$pixels[] = $row;
|
||||
}
|
||||
|
||||
$hash = Blurhash_Encoder::encode( $pixels, self::COMPONENTS, self::COMPONENTS );
|
||||
return \is_string( $hash ) && '' !== $hash ? $hash : null;
|
||||
} catch ( \Throwable $e ) {
|
||||
return null;
|
||||
} finally {
|
||||
// Free GD resources. On PHP 7.4 GD images are plain
|
||||
// resources that persist until script end; the CLI
|
||||
// backfill processes many images in one process and
|
||||
// would leak all of them without explicit cleanup.
|
||||
\imagedestroy( $original );
|
||||
if ( null !== $scaled && false !== $scaled ) {
|
||||
\imagedestroy( $scaled );
|
||||
}
|
||||
if ( null !== $canvas && false !== $canvas ) {
|
||||
\imagedestroy( $canvas );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the absolute filesystem path of the size we use as
|
||||
* encoder input, falling back to the original when the
|
||||
* intermediate doesn't exist (small uploads that core didn't
|
||||
* generate downscales for).
|
||||
*
|
||||
* @param int $attachment_id Attachment post ID.
|
||||
* @return string|null Absolute file path, or null when nothing readable resolved.
|
||||
*/
|
||||
private static function resolve_encode_path( int $attachment_id ): ?string {
|
||||
$upload = \wp_upload_dir();
|
||||
$basedir_real = ( \is_array( $upload ) && ! empty( $upload['basedir'] ) )
|
||||
? \realpath( $upload['basedir'] )
|
||||
: false;
|
||||
|
||||
$sized = \image_get_intermediate_size( $attachment_id, self::ENCODE_SIZE );
|
||||
if ( \is_array( $sized ) && ! empty( $sized['path'] ) && false !== $basedir_real ) {
|
||||
$candidate = \trailingslashit( $upload['basedir'] ) . $sized['path'];
|
||||
$resolved = self::contain_under_basedir( $candidate, $basedir_real );
|
||||
if ( null !== $resolved ) {
|
||||
return $resolved;
|
||||
}
|
||||
}
|
||||
|
||||
$attached = \get_attached_file( $attachment_id );
|
||||
if ( \is_string( $attached ) && '' !== $attached ) {
|
||||
// The fallback can return paths outside the uploads dir
|
||||
// (e.g. shared media), so containment is best-effort: we
|
||||
// accept readable real paths but skip the basedir prefix
|
||||
// check, while still rejecting unreadable / non-existent
|
||||
// targets.
|
||||
$resolved = \realpath( $attached );
|
||||
if ( false !== $resolved && \is_readable( $resolved ) ) {
|
||||
return $resolved;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Realpath-resolve `$candidate` and verify it sits under
|
||||
* `$basedir_real`. Returns the resolved path on success, null on
|
||||
* any failure (unresolvable, traversal outside the basedir,
|
||||
* unreadable). Defends the encoder against filterable
|
||||
* `image_get_intermediate_size` returning a `path` that contains
|
||||
* `..` segments or an absolute symlink target outside uploads.
|
||||
*
|
||||
* @param string $candidate Path to resolve.
|
||||
* @param string $basedir_real Already-resolved (realpath) basedir.
|
||||
* @return string|null
|
||||
*/
|
||||
private static function contain_under_basedir( string $candidate, string $basedir_real ): ?string {
|
||||
$resolved = \realpath( $candidate );
|
||||
if ( false === $resolved ) {
|
||||
return null;
|
||||
}
|
||||
$prefix = \rtrim( $basedir_real, \DIRECTORY_SEPARATOR ) . \DIRECTORY_SEPARATOR;
|
||||
if ( 0 !== \strpos( $resolved, $prefix ) ) {
|
||||
return null;
|
||||
}
|
||||
return \is_readable( $resolved ) ? $resolved : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* `wp_generate_attachment_metadata` filter callback. Schedules
|
||||
* a single-event cron run to compute the blurhash for image
|
||||
* attachments. The filter return value is the metadata unchanged
|
||||
* — we use the hook purely as an "image is ready" notifier.
|
||||
*
|
||||
* @param array $metadata Attachment metadata as built by WP.
|
||||
* @param int $attachment_id Attachment post ID.
|
||||
* @return array
|
||||
*/
|
||||
public static function schedule_encode( $metadata, $attachment_id ) {
|
||||
$attachment_id = (int) $attachment_id;
|
||||
if ( $attachment_id < 1 || ! self::is_encodable_attachment( $attachment_id ) ) {
|
||||
return $metadata;
|
||||
}
|
||||
|
||||
// Skip both the invalidation AND the cron enqueue when the
|
||||
// encoder can't actually run on this host. Without the gate,
|
||||
// a metadata regen on a GD-less site would wipe a previously
|
||||
// stored hash (computed on a different host, restored from
|
||||
// backup, etc.) and queue a cron event guaranteed to fail.
|
||||
if ( ! self::is_encoder_runnable() ) {
|
||||
return $metadata;
|
||||
}
|
||||
|
||||
// Invalidate any prior hash so cron will re-encode against
|
||||
// the latest bytes. `wp_generate_attachment_metadata` fires
|
||||
// on initial upload AND on media-replace / crop / regen, so
|
||||
// without this delete a replaced image would keep federating
|
||||
// the placeholder for its prior bytes.
|
||||
self::delete( $attachment_id );
|
||||
|
||||
self::schedule( $attachment_id );
|
||||
return $metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether an attachment is something the encoder will attempt on this host.
|
||||
* True for `wp_attachment_is_image` attachments whose mime is in
|
||||
* {@see self::ENCODABLE_MIME_TYPES} AND that this GD build can actually
|
||||
* decode; false for SVG, non-image media, deleted/nonexistent IDs, and
|
||||
* formats this GD build lacks support for (e.g. WebP/AVIF/BMP on a
|
||||
* stripped-down GD). Shared gate used by the upload-scheduling path, the
|
||||
* CLI backfill (to count "we don't encode this" as a skip rather than a
|
||||
* failure), and the encoder itself.
|
||||
*
|
||||
* The GD-capability check matters because `schedule_encode()` invalidates
|
||||
* any prior hash before queueing the cron encode: without it, a metadata
|
||||
* regen on a host that can't decode the format would wipe a previously
|
||||
* good hash (e.g. migrated from a host with broader GD support) and queue
|
||||
* a cron event guaranteed to fail.
|
||||
*
|
||||
* @param int $attachment_id Attachment post ID.
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_encodable_attachment( int $attachment_id ): bool {
|
||||
if ( $attachment_id < 1 || ! \wp_attachment_is_image( $attachment_id ) ) {
|
||||
return false;
|
||||
}
|
||||
$mime = \get_post_mime_type( $attachment_id );
|
||||
return \is_string( $mime )
|
||||
&& \in_array( $mime, self::ENCODABLE_MIME_TYPES, true )
|
||||
&& self::host_can_decode( $mime );
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether this GD build can decode the given image mime type.
|
||||
*
|
||||
* GD support for WebP, AVIF, and BMP is build-dependent, so a mime being
|
||||
* in {@see self::ENCODABLE_MIME_TYPES} is necessary but not sufficient.
|
||||
* `imagetypes()` reports what the running GD can actually handle. The
|
||||
* `IMG_*` flags for WebP/AVIF/BMP are not defined on every PHP version
|
||||
* (`IMG_AVIF` is PHP 8.1+), so guard each with `defined()`.
|
||||
*
|
||||
* @param string $mime The attachment mime type.
|
||||
* @return bool
|
||||
*/
|
||||
private static function host_can_decode( $mime ) {
|
||||
if ( ! \function_exists( 'imagetypes' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$supported = \imagetypes();
|
||||
|
||||
switch ( $mime ) {
|
||||
case 'image/jpeg':
|
||||
return (bool) ( $supported & IMG_JPG );
|
||||
case 'image/png':
|
||||
return (bool) ( $supported & IMG_PNG );
|
||||
case 'image/gif':
|
||||
return (bool) ( $supported & IMG_GIF );
|
||||
case 'image/webp':
|
||||
return \defined( 'IMG_WEBP' ) && (bool) ( $supported & IMG_WEBP );
|
||||
case 'image/avif':
|
||||
return \defined( 'IMG_AVIF' ) && (bool) ( $supported & IMG_AVIF );
|
||||
case 'image/bmp':
|
||||
return \defined( 'IMG_BMP' ) && (bool) ( $supported & IMG_BMP );
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the host has the GD primitives the encoder needs to
|
||||
* actually run. Public so the WP-CLI backfill can fail-fast with
|
||||
* one clear error message rather than emit a warning per
|
||||
* attachment, and so the upload/cron paths can short-circuit
|
||||
* without leaving cron noise behind on a GD-less host.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_encoder_runnable(): bool {
|
||||
return \function_exists( 'imagecreatefromstring' )
|
||||
&& \function_exists( 'imagecreatetruecolor' )
|
||||
&& \function_exists( 'imagescale' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue (or skip, if already queued) a cron event to compute
|
||||
* the blurhash for an attachment. Internal helper for
|
||||
* {@see self::schedule_encode()} — callers should go through
|
||||
* that filter callback so the metadata-regen invalidation pass
|
||||
* stays load-bearing.
|
||||
*
|
||||
* @param int $attachment_id Attachment post ID.
|
||||
*/
|
||||
private static function schedule( int $attachment_id ): void {
|
||||
if ( $attachment_id < 1 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Rely on WP's own duplicate-event guard inside
|
||||
* `wp_schedule_single_event` (rejects matching args within
|
||||
* the 10-minute window) rather than running an explicit
|
||||
* `wp_next_scheduled` check first. The explicit check did
|
||||
* nothing the underlying scheduler doesn't already do and
|
||||
* added a needless read against the autoloaded cron option
|
||||
* on every attachment metadata regen.
|
||||
*
|
||||
* Defer one minute rather than firing at `time()`. This
|
||||
* callback runs inside the `wp_generate_attachment_metadata`
|
||||
* filter, BEFORE `wp_update_attachment_metadata()` commits the
|
||||
* sizes array. A concurrent wp-cron tick could otherwise run
|
||||
* `run_encode()` before that commit lands, find no thumbnail
|
||||
* intermediate yet, and fall back to encoding the full-size
|
||||
* original. The one-minute delay lets the metadata write
|
||||
* settle first; single-event dedup semantics are unchanged.
|
||||
*/
|
||||
\wp_schedule_single_event( \time() + MINUTE_IN_SECONDS, self::CRON_HOOK, array( $attachment_id ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Cron callback: compute and store the blurhash. No-op when a
|
||||
* hash is already stored; callers wanting a re-encode delete
|
||||
* the postmeta first ({@see self::delete()}) — that's what
|
||||
* {@see self::schedule_encode()} does before scheduling, so the
|
||||
* media-replace path always recomputes.
|
||||
*
|
||||
* Emits `activitypub_blurhash_encode_failed` (action) and an
|
||||
* `error_log` line when the encoder returns null without a
|
||||
* pre-existing stored hash, so transient failures (NFS hiccup,
|
||||
* S3 lag, GD blip) leave a signal for monitoring instead of
|
||||
* silently never producing a placeholder.
|
||||
*
|
||||
* @param int $attachment_id Attachment post ID.
|
||||
*/
|
||||
public static function run_encode( int $attachment_id ): void {
|
||||
$attachment_id = (int) $attachment_id;
|
||||
if ( $attachment_id < 1 ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Predictable unencodability (system-wide GD missing,
|
||||
// non-raster mime, deleted attachment) is silent — logging
|
||||
// per-event would spam diagnostics on every scheduled run
|
||||
// even though the reason is global. Only unexpected failures
|
||||
// (encoder exception, missing file, decode failure) below
|
||||
// fire the diagnostic action.
|
||||
if ( ! self::is_encoder_runnable() || ! self::is_encodable_attachment( $attachment_id ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( null !== self::get( $attachment_id ) ) {
|
||||
return;
|
||||
}
|
||||
$hash = self::encode_from_attachment( $attachment_id );
|
||||
if ( \is_string( $hash ) ) {
|
||||
self::set( $attachment_id, $hash );
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* `false` is a policy skip (source over the decode-bomb
|
||||
* dimension cap): deliberate, deterministic, and global to
|
||||
* the source bytes — logging it would re-introduce the
|
||||
* per-run noise this bucket exists to avoid. Only `null`
|
||||
* (unexpected failure) falls through to diagnostics.
|
||||
*/
|
||||
if ( false === $hash ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Silent-failure guard — surface the gap so an operator can
|
||||
// investigate (or wire monitoring against the action).
|
||||
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- intentional plugin diagnostics; cron path only.
|
||||
\error_log( "[activitypub:blurhash] encode failed for attachment {$attachment_id}; placeholder will be absent until backfill." );
|
||||
|
||||
/**
|
||||
* Fires when the cron-deferred encoder fails to produce a
|
||||
* hash for an attachment. Monitoring integrations can hook
|
||||
* this to count blurhash-encode failures over time.
|
||||
*
|
||||
* @param int $attachment_id The attachment ID that failed to encode.
|
||||
*/
|
||||
\do_action( 'activitypub_blurhash_encode_failed', $attachment_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* `activitypub_attachment` filter callback. Injects `blurhash`
|
||||
* into image attachment arrays when a usable postmeta value is
|
||||
* stored. No-op on anything else (non-image attachments, missing
|
||||
* meta, malformed meta, malformed arrays) so non-photo federation
|
||||
* paths are untouched. Sanitization is enforced inside
|
||||
* {@see self::get()} — anyone with `edit_post_meta` on an
|
||||
* attachment could otherwise rewrite `_activitypub_blurhash` to bytes
|
||||
* that break `wp_json_encode` and drop the entire AP envelope.
|
||||
*
|
||||
* @param mixed $attachment The attachment array as built by bundled AP.
|
||||
* @param mixed $attachment_id The attachment post ID (mixed because the upstream filter is loosely typed).
|
||||
* @return mixed
|
||||
*/
|
||||
public static function inject_blurhash( $attachment, $attachment_id ) {
|
||||
if ( ! \is_array( $attachment ) ) {
|
||||
return $attachment;
|
||||
}
|
||||
if ( 'Image' !== ( $attachment['type'] ?? '' ) ) {
|
||||
return $attachment;
|
||||
}
|
||||
$hash = self::get( (int) $attachment_id );
|
||||
if ( null === $hash ) {
|
||||
return $attachment;
|
||||
}
|
||||
$attachment['blurhash'] = $hash;
|
||||
return $attachment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a stored hash against the blurhash spec's character
|
||||
* set and our length bound. Treat any out-of-bounds value as
|
||||
* absent rather than coerce — preserves the "no blurhash is
|
||||
* better than a wrong blurhash" posture the rest of the class
|
||||
* follows. The base83 alphabet is defined by the spec at
|
||||
* {@link https://github.com/woltapp/blurhash/blob/master/Algorithm.md#base-83}.
|
||||
*
|
||||
* @param string $hash Candidate hash string.
|
||||
* @return bool
|
||||
*/
|
||||
private static function is_well_formed_hash( string $hash ): bool {
|
||||
$length = \strlen( $hash );
|
||||
if ( $length < 6 || $length > self::MAX_HASH_LENGTH ) {
|
||||
return false;
|
||||
}
|
||||
// Base83 alphabet — same character set the encoder library
|
||||
// emits, conservative enough to reject any byte sequence
|
||||
// that would surprise a downstream JSON encoder or client
|
||||
// decoder.
|
||||
return 1 === \preg_match( '/\A[0-9A-Za-z#$%*+,\-.:;=?@\[\]\^_{|}~]+\z/', $hash );
|
||||
}
|
||||
}
|
||||
@ -7,6 +7,19 @@
|
||||
|
||||
namespace Activitypub;
|
||||
|
||||
use Activitypub\Cli\Actor_Command;
|
||||
use Activitypub\Cli\Blurhash_Command;
|
||||
use Activitypub\Cli\Cache_Command;
|
||||
use Activitypub\Cli\Command;
|
||||
use Activitypub\Cli\Comment_Command;
|
||||
use Activitypub\Cli\Fetch_Command;
|
||||
use Activitypub\Cli\Follow_Command;
|
||||
use Activitypub\Cli\Move_Command;
|
||||
use Activitypub\Cli\Outbox_Command;
|
||||
use Activitypub\Cli\Post_Command;
|
||||
use Activitypub\Cli\Self_Destruct_Command;
|
||||
use Activitypub\Cli\Stats_Command;
|
||||
|
||||
/**
|
||||
* ActivityPub CLI command registry.
|
||||
*
|
||||
@ -33,12 +46,13 @@ class Cli {
|
||||
* - wp activitypub follow <remote_user>
|
||||
* - wp activitypub stats <collect|compile|send>
|
||||
* - wp activitypub fetch <url>
|
||||
* - wp activitypub blurhash backfill [--dry-run] [--limit=<n>] [--force]
|
||||
*/
|
||||
public static function register() {
|
||||
// Register parent command with version subcommand.
|
||||
\WP_CLI::add_command(
|
||||
'activitypub',
|
||||
'\Activitypub\Cli\Command',
|
||||
Command::class,
|
||||
array(
|
||||
'shortdesc' => 'Manage ActivityPub plugin functionality and federation.',
|
||||
)
|
||||
@ -46,7 +60,7 @@ class Cli {
|
||||
|
||||
\WP_CLI::add_command(
|
||||
'activitypub post',
|
||||
'\Activitypub\Cli\Post_Command',
|
||||
Post_Command::class,
|
||||
array(
|
||||
'shortdesc' => 'Manage ActivityPub posts (delete or update).',
|
||||
)
|
||||
@ -54,7 +68,7 @@ class Cli {
|
||||
|
||||
\WP_CLI::add_command(
|
||||
'activitypub comment',
|
||||
'\Activitypub\Cli\Comment_Command',
|
||||
Comment_Command::class,
|
||||
array(
|
||||
'shortdesc' => 'Manage ActivityPub comments (delete or update).',
|
||||
)
|
||||
@ -62,7 +76,7 @@ class Cli {
|
||||
|
||||
\WP_CLI::add_command(
|
||||
'activitypub actor',
|
||||
'\Activitypub\Cli\Actor_Command',
|
||||
Actor_Command::class,
|
||||
array(
|
||||
'shortdesc' => 'Manage ActivityPub actors (delete or update).',
|
||||
)
|
||||
@ -70,7 +84,7 @@ class Cli {
|
||||
|
||||
\WP_CLI::add_command(
|
||||
'activitypub outbox',
|
||||
'\Activitypub\Cli\Outbox_Command',
|
||||
Outbox_Command::class,
|
||||
array(
|
||||
'shortdesc' => 'Manage ActivityPub outbox items (undo or reschedule).',
|
||||
)
|
||||
@ -78,7 +92,7 @@ class Cli {
|
||||
|
||||
\WP_CLI::add_command(
|
||||
'activitypub self-destruct',
|
||||
'\Activitypub\Cli\Self_Destruct_Command',
|
||||
Self_Destruct_Command::class,
|
||||
array(
|
||||
'shortdesc' => 'Remove the entire blog from the Fediverse.',
|
||||
)
|
||||
@ -86,7 +100,7 @@ class Cli {
|
||||
|
||||
\WP_CLI::add_command(
|
||||
'activitypub move',
|
||||
'\Activitypub\Cli\Move_Command',
|
||||
Move_Command::class,
|
||||
array(
|
||||
'shortdesc' => 'Move the blog to a new URL.',
|
||||
)
|
||||
@ -94,7 +108,7 @@ class Cli {
|
||||
|
||||
\WP_CLI::add_command(
|
||||
'activitypub follow',
|
||||
'\Activitypub\Cli\Follow_Command',
|
||||
Follow_Command::class,
|
||||
array(
|
||||
'shortdesc' => 'Follow a remote ActivityPub user.',
|
||||
)
|
||||
@ -102,7 +116,7 @@ class Cli {
|
||||
|
||||
\WP_CLI::add_command(
|
||||
'activitypub cache',
|
||||
'\Activitypub\Cli\Cache_Command',
|
||||
Cache_Command::class,
|
||||
array(
|
||||
'shortdesc' => 'Manage remote media cache (clear or show status).',
|
||||
)
|
||||
@ -110,7 +124,7 @@ class Cli {
|
||||
|
||||
\WP_CLI::add_command(
|
||||
'activitypub fetch',
|
||||
'\Activitypub\Cli\Fetch_Command',
|
||||
Fetch_Command::class,
|
||||
array(
|
||||
'shortdesc' => 'Fetch a remote URL with a signed ActivityPub request.',
|
||||
)
|
||||
@ -118,10 +132,18 @@ class Cli {
|
||||
|
||||
\WP_CLI::add_command(
|
||||
'activitypub stats',
|
||||
'\Activitypub\Cli\Stats_Command',
|
||||
Stats_Command::class,
|
||||
array(
|
||||
'shortdesc' => 'Manage ActivityPub statistics (collect, compile or send).',
|
||||
)
|
||||
);
|
||||
|
||||
\WP_CLI::add_command(
|
||||
'activitypub blurhash',
|
||||
Blurhash_Command::class,
|
||||
array(
|
||||
'shortdesc' => 'Backfill Blurhash placeholders for image attachments.',
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -113,7 +113,7 @@ class Comment {
|
||||
if ( \is_user_logged_in() ) {
|
||||
$author = \esc_html( $comment->comment_author );
|
||||
|
||||
$message = sprintf(
|
||||
$message = \sprintf(
|
||||
/* translators: %s: comment author name */
|
||||
\__( '%s is on the Fediverse. To reply to them, ask your administrator to enable ActivityPub for your account.', 'activitypub' ),
|
||||
$author
|
||||
@ -121,7 +121,7 @@ class Comment {
|
||||
|
||||
// Add link to users page if current user can edit users.
|
||||
if ( \current_user_can( 'edit_users' ) ) {
|
||||
$message = sprintf(
|
||||
$message = \sprintf(
|
||||
/* translators: 1: comment author name, 2: URL to the users management page */
|
||||
\__( '%1$s is on the Fediverse. To reply to them, <a href="%2$s">enable ActivityPub for your account</a>.', 'activitypub' ),
|
||||
$author,
|
||||
@ -129,7 +129,7 @@ class Comment {
|
||||
);
|
||||
}
|
||||
|
||||
$warning = sprintf(
|
||||
$warning = \sprintf(
|
||||
'<p class="activitypub-reply-warning"><em>%s</em></p>',
|
||||
\wp_kses( $message, array( 'a' => array( 'href' => array() ) ) )
|
||||
);
|
||||
@ -178,7 +178,7 @@ class Comment {
|
||||
return true;
|
||||
}
|
||||
|
||||
$current_user = get_current_user_id();
|
||||
$current_user = \get_current_user_id();
|
||||
|
||||
if ( ! $current_user ) {
|
||||
return false;
|
||||
@ -299,6 +299,17 @@ class Comment {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Do not federate brand-new comments on a post that is not federated itself
|
||||
* (e.g. a private post, a post switched to local visibility, or a non-ActivityPub
|
||||
* post type). This prevents leaking replies on content the post type's read rules
|
||||
* would otherwise protect. Comments that were already sent are allowed through so
|
||||
* their Update and Delete activities can still federate (and tear down remote copies).
|
||||
*/
|
||||
if ( ! self::was_sent( $comment ) && ! is_post_federated( $comment->comment_post_ID ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// It is a comment to the post and can be federated.
|
||||
if ( empty( $comment->comment_parent ) ) {
|
||||
return true;
|
||||
@ -313,20 +324,31 @@ class Comment {
|
||||
/**
|
||||
* Examine a comment ID and look up an existing comment it represents.
|
||||
*
|
||||
* @param string $id ActivityPub object ID (usually a URL) to check.
|
||||
* @since 9.1.0 Added the `$args` parameter.
|
||||
*
|
||||
* @param string $id ActivityPub object ID (usually a URL) to check.
|
||||
* @param array $args Optional. Additional WP_Comment_Query arguments. Pass `array( 'status' => 'any' )`
|
||||
* to also match comments in spam or trash, which the default status excludes.
|
||||
*
|
||||
* @return \WP_Comment|false Comment object, or false on failure.
|
||||
*/
|
||||
public static function object_id_to_comment( $id ) {
|
||||
$comment_query = new \WP_Comment_Query(
|
||||
public static function object_id_to_comment( $id, $args = array() ) {
|
||||
$args = \wp_parse_args(
|
||||
$args,
|
||||
array(
|
||||
'meta_key' => 'source_id', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
|
||||
'meta_value' => $id, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value
|
||||
'orderby' => 'comment_date',
|
||||
'order' => 'DESC',
|
||||
'number' => 1,
|
||||
'orderby' => 'comment_date',
|
||||
'order' => 'DESC',
|
||||
)
|
||||
);
|
||||
|
||||
// Force the lookup key and full comment objects, so callers cannot break the return contract.
|
||||
$args['fields'] = 'all';
|
||||
$args['meta_key'] = 'source_id'; // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
|
||||
$args['meta_value'] = $id; // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value
|
||||
|
||||
$comment_query = new \WP_Comment_Query( $args );
|
||||
|
||||
if ( ! $comment_query->comments ) {
|
||||
return false;
|
||||
}
|
||||
@ -382,7 +404,7 @@ class Comment {
|
||||
$query = new \WP_Comment_Query();
|
||||
$comments = $query->query( $args );
|
||||
|
||||
if ( $comments && is_array( $comments ) ) {
|
||||
if ( $comments && \is_array( $comments ) ) {
|
||||
return $comments[0]->comment_ID;
|
||||
}
|
||||
|
||||
@ -400,7 +422,7 @@ class Comment {
|
||||
*/
|
||||
public static function comment_class( $classes, $css_class, $comment_id ) {
|
||||
// Check if ActivityPub comment.
|
||||
if ( 'activitypub' === get_comment_meta( $comment_id, 'protocol', true ) ) {
|
||||
if ( 'activitypub' === \get_comment_meta( $comment_id, 'protocol', true ) ) {
|
||||
$classes[] = 'activitypub-comment';
|
||||
}
|
||||
|
||||
@ -431,9 +453,9 @@ class Comment {
|
||||
$where .= $wpdb->prepare( ' AND comment_type = %s', $comment_type );
|
||||
} else {
|
||||
$comment_types = \array_map( 'esc_sql', $comment_types );
|
||||
$placeholders = implode( ', ', array_fill( 0, count( $comment_types ), '%s' ) );
|
||||
$placeholders = \implode( ', ', \array_fill( 0, \count( $comment_types ), '%s' ) );
|
||||
// phpcs:ignore WordPress.DB.PreparedSQLPlaceholders.ReplacementsWrongNumber, WordPress.DB.PreparedSQL.NotPrepared
|
||||
$where .= $wpdb->prepare( sprintf( ' AND comment_type NOT IN (%s)', $placeholders ), ...$comment_types );
|
||||
$where .= $wpdb->prepare( \sprintf( ' AND comment_type NOT IN (%s)', $placeholders ), ...$comment_types );
|
||||
}
|
||||
|
||||
return $where;
|
||||
@ -565,7 +587,7 @@ class Comment {
|
||||
$comment_types = self::get_comment_types();
|
||||
|
||||
foreach ( $comment_types as $comment_type ) {
|
||||
if ( in_array( $activity_type, $comment_type['activity_types'], true ) ) {
|
||||
if ( \in_array( $activity_type, $comment_type['activity_types'], true ) ) {
|
||||
return $comment_type;
|
||||
}
|
||||
}
|
||||
@ -606,13 +628,13 @@ class Comment {
|
||||
* @return array The registered custom comment type slugs.
|
||||
*/
|
||||
public static function get_comment_type_slugs() {
|
||||
if ( ! did_action( 'init' ) ) {
|
||||
_doing_it_wrong( __METHOD__, 'This function should not be called before the init action has run. Comment types are only available after init.', '7.5.0' );
|
||||
if ( ! \did_action( 'init' ) ) {
|
||||
\_doing_it_wrong( __METHOD__, 'This function should not be called before the init action has run. Comment types are only available after init.', '7.5.0' );
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
return array_keys( self::get_comment_types() );
|
||||
return \array_keys( self::get_comment_types() );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -628,14 +650,14 @@ class Comment {
|
||||
* @return array The comment type.
|
||||
*/
|
||||
public static function get_comment_type( $type ) {
|
||||
$type = strtolower( $type );
|
||||
$type = sanitize_key( $type );
|
||||
$type = \strtolower( $type );
|
||||
$type = \sanitize_key( $type );
|
||||
|
||||
$comment_types = self::get_comment_types();
|
||||
$type_array = array();
|
||||
|
||||
// Check array keys.
|
||||
if ( in_array( $type, array_keys( $comment_types ), true ) ) {
|
||||
if ( \in_array( $type, \array_keys( $comment_types ), true ) ) {
|
||||
$type_array = $comment_types[ $type ];
|
||||
}
|
||||
|
||||
@ -644,7 +666,7 @@ class Comment {
|
||||
*
|
||||
* @param array $type_array The comment type.
|
||||
*/
|
||||
return apply_filters( "activitypub_comment_type_{$type}", $type_array );
|
||||
return \apply_filters( "activitypub_comment_type_{$type}", $type_array );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -670,7 +692,7 @@ class Comment {
|
||||
* @param mixed $value The value of the attribute.
|
||||
* @param string $type The comment type.
|
||||
*/
|
||||
return apply_filters( "activitypub_comment_type_{$attr}", $value, $type );
|
||||
return \apply_filters( "activitypub_comment_type_{$attr}", $value, $type );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -680,57 +702,57 @@ class Comment {
|
||||
register_comment_type(
|
||||
'repost',
|
||||
array(
|
||||
'label' => __( 'Reposts', 'activitypub' ),
|
||||
'singular' => __( 'Repost', 'activitypub' ),
|
||||
'label' => \__( 'Reposts', 'activitypub' ),
|
||||
'singular' => \__( 'Repost', 'activitypub' ),
|
||||
'description' => 'A repost (or Announce) is when a post appears in the timeline because someone else shared it, while still showing the original author as the source.',
|
||||
'icon' => '♻️',
|
||||
'class' => 'p-repost',
|
||||
'type' => 'repost',
|
||||
'collection' => 'reposts',
|
||||
'activity_types' => array( 'announce' ),
|
||||
'excerpt' => html_entity_decode( \__( '… reposted this!', 'activitypub' ) ),
|
||||
'excerpt' => \html_entity_decode( \__( '… reposted this!', 'activitypub' ) ),
|
||||
/* translators: %d: Number of reposts */
|
||||
'count_single' => _x( '%d repost', 'number of reposts', 'activitypub' ),
|
||||
'count_single' => \_x( '%d repost', 'number of reposts', 'activitypub' ),
|
||||
/* translators: %d: Number of reposts */
|
||||
'count_plural' => _x( '%d reposts', 'number of reposts', 'activitypub' ),
|
||||
'count_plural' => \_x( '%d reposts', 'number of reposts', 'activitypub' ),
|
||||
)
|
||||
);
|
||||
|
||||
register_comment_type(
|
||||
'like',
|
||||
array(
|
||||
'label' => __( 'Likes', 'activitypub' ),
|
||||
'singular' => __( 'Like', 'activitypub' ),
|
||||
'label' => \__( 'Likes', 'activitypub' ),
|
||||
'singular' => \__( 'Like', 'activitypub' ),
|
||||
'description' => 'A like is a small positive reaction that shows appreciation for a post without sharing it further.',
|
||||
'icon' => '👍',
|
||||
'class' => 'p-like',
|
||||
'type' => 'like',
|
||||
'collection' => 'likes',
|
||||
'activity_types' => array( 'like' ),
|
||||
'excerpt' => html_entity_decode( \__( '… liked this!', 'activitypub' ) ),
|
||||
'excerpt' => \html_entity_decode( \__( '… liked this!', 'activitypub' ) ),
|
||||
/* translators: %d: Number of likes */
|
||||
'count_single' => _x( '%d like', 'number of likes', 'activitypub' ),
|
||||
'count_single' => \_x( '%d like', 'number of likes', 'activitypub' ),
|
||||
/* translators: %d: Number of likes */
|
||||
'count_plural' => _x( '%d likes', 'number of likes', 'activitypub' ),
|
||||
'count_plural' => \_x( '%d likes', 'number of likes', 'activitypub' ),
|
||||
)
|
||||
);
|
||||
|
||||
register_comment_type(
|
||||
'quote',
|
||||
array(
|
||||
'label' => __( 'Quotes', 'activitypub' ),
|
||||
'singular' => __( 'Quote', 'activitypub' ),
|
||||
'label' => \__( 'Quotes', 'activitypub' ),
|
||||
'singular' => \__( 'Quote', 'activitypub' ),
|
||||
'description' => 'A quote is when a post is shared along with an added comment, so the original post appears together with the sharer’s own words.',
|
||||
'icon' => '❞',
|
||||
'class' => 'p-quote',
|
||||
'type' => 'quote',
|
||||
'collection' => 'quotes',
|
||||
'activity_types' => array( 'quote' ),
|
||||
'excerpt' => html_entity_decode( \__( '… quoted this!', 'activitypub' ) ),
|
||||
'excerpt' => \html_entity_decode( \__( '… quoted this!', 'activitypub' ) ),
|
||||
/* translators: %d: Number of quotes */
|
||||
'count_single' => _x( '%d quote', 'number of quotes', 'activitypub' ),
|
||||
'count_single' => \_x( '%d quote', 'number of quotes', 'activitypub' ),
|
||||
/* translators: %d: Number of quotes */
|
||||
'count_plural' => _x( '%d quotes', 'number of quotes', 'activitypub' ),
|
||||
'count_plural' => \_x( '%d quotes', 'number of quotes', 'activitypub' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
@ -744,9 +766,9 @@ class Comment {
|
||||
*/
|
||||
public static function get_avatar_comment_types( $types ) {
|
||||
$comment_types = self::get_comment_type_slugs();
|
||||
$types = array_merge( $types, $comment_types );
|
||||
$types = \array_merge( $types, $comment_types );
|
||||
|
||||
return array_unique( $types );
|
||||
return \array_unique( $types );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -764,7 +786,7 @@ class Comment {
|
||||
}
|
||||
|
||||
// Do not exclude likes and reposts on ActivityPub requests.
|
||||
if ( defined( 'ACTIVITYPUB_REQUEST' ) && ACTIVITYPUB_REQUEST ) {
|
||||
if ( \defined( 'ACTIVITYPUB_REQUEST' ) && ACTIVITYPUB_REQUEST ) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -922,7 +944,7 @@ class Comment {
|
||||
*/
|
||||
public static function pre_wp_update_comment_count_now( $new_count, $old_count, $post_id ) {
|
||||
if ( null === $new_count ) {
|
||||
$excluded_types = array_filter( self::get_comment_type_slugs(), array( self::class, 'is_comment_type_enabled' ) );
|
||||
$excluded_types = \array_filter( self::get_comment_type_slugs(), array( self::class, 'is_comment_type_enabled' ) );
|
||||
|
||||
if ( ! empty( $excluded_types ) ) {
|
||||
/*
|
||||
@ -946,12 +968,12 @@ class Comment {
|
||||
* @param int $post_id The post ID.
|
||||
*/
|
||||
$excluded_types = \apply_filters( 'activitypub_excluded_comment_types', $excluded_types, $post_id );
|
||||
$excluded_types = array_unique( array_filter( $excluded_types ) );
|
||||
$excluded_types = \array_unique( \array_filter( $excluded_types ) );
|
||||
|
||||
global $wpdb;
|
||||
|
||||
// phpcs:ignore WordPress.DB
|
||||
$new_count = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->comments WHERE comment_post_ID = %d AND comment_approved = '1' AND comment_type NOT IN ('" . implode( "','", $excluded_types ) . "')", $post_id ) );
|
||||
$new_count = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $wpdb->comments WHERE comment_post_ID = %d AND comment_approved = '1' AND comment_type NOT IN ('" . \implode( "','", $excluded_types ) . "')", $post_id ) );
|
||||
}
|
||||
}
|
||||
|
||||
@ -965,7 +987,7 @@ class Comment {
|
||||
* @return bool True if the comment type is enabled.
|
||||
*/
|
||||
public static function is_comment_type_enabled( $comment_type ) {
|
||||
return '1' === get_option( "activitypub_allow_{$comment_type}s", '1' );
|
||||
return '1' === \get_option( "activitypub_allow_{$comment_type}s", '1' );
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -57,7 +57,7 @@ class Dispatcher {
|
||||
*
|
||||
* @param int $batch_size The batch size. Default ACTIVITYPUB_OUTBOX_PROCESSING_BATCH_SIZE.
|
||||
*/
|
||||
return apply_filters( 'activitypub_dispatcher_batch_size', ACTIVITYPUB_OUTBOX_PROCESSING_BATCH_SIZE );
|
||||
return \apply_filters( 'activitypub_dispatcher_batch_size', ACTIVITYPUB_OUTBOX_PROCESSING_BATCH_SIZE );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -71,7 +71,7 @@ class Dispatcher {
|
||||
*
|
||||
* @param int $retry_max_attempts The maximum number of retry attempts. Default ACTIVITYPUB_OUTBOX_RETRY_MAX_ATTEMPTS.
|
||||
*/
|
||||
return apply_filters( 'activitypub_dispatcher_retry_max_attempts', 3 );
|
||||
return \apply_filters( 'activitypub_dispatcher_retry_max_attempts', 3 );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -89,7 +89,7 @@ class Dispatcher {
|
||||
*
|
||||
* @param int $retry_delay_unit The retry delay unit in seconds. Default ACTIVITYPUB_OUTBOX_RETRY_DELAY_UNIT.
|
||||
*/
|
||||
return apply_filters( 'activitypub_dispatcher_retry_delay', HOUR_IN_SECONDS );
|
||||
return \apply_filters( 'activitypub_dispatcher_retry_delay', HOUR_IN_SECONDS );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -105,7 +105,7 @@ class Dispatcher {
|
||||
*
|
||||
* @param int[] $retry_error_codes The error codes. Default array( 408, 429, 500, 502, 503, 504 ).
|
||||
*/
|
||||
return apply_filters( 'activitypub_dispatcher_retry_error_codes', ACTIVITYPUB_RETRY_ERROR_CODES );
|
||||
return \apply_filters( 'activitypub_dispatcher_retry_error_codes', ACTIVITYPUB_RETRY_ERROR_CODES );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -183,7 +183,7 @@ class Dispatcher {
|
||||
self::schedule_retry( $retries, $outbox_item_id );
|
||||
}
|
||||
|
||||
if ( is_countable( $inboxes ) && count( $inboxes ) < $batch_size ) {
|
||||
if ( \is_countable( $inboxes ) && \count( $inboxes ) < $batch_size ) {
|
||||
\delete_post_meta( $outbox_item_id, '_activitypub_outbox_offset' );
|
||||
|
||||
/**
|
||||
@ -280,7 +280,7 @@ class Dispatcher {
|
||||
$result = safe_remote_post( $inbox, $json, $outbox_item->post_author );
|
||||
}
|
||||
|
||||
if ( \is_wp_error( $result ) && in_array( $result->get_error_code(), self::get_retry_error_codes(), true ) ) {
|
||||
if ( \is_wp_error( $result ) && \in_array( $result->get_error_code(), self::get_retry_error_codes(), true ) ) {
|
||||
$retries[] = $inbox;
|
||||
}
|
||||
|
||||
@ -309,7 +309,7 @@ class Dispatcher {
|
||||
private static function send_to_local_inbox( $inbox_url, $json ) {
|
||||
// Parse the inbox URL to extract the REST route.
|
||||
$path = \wp_parse_url( $inbox_url, PHP_URL_PATH ) ?? '';
|
||||
$rest_route = \preg_replace( '#^/' . preg_quote( \rest_get_url_prefix(), '#' ) . '#', '', $path );
|
||||
$rest_route = \preg_replace( '#^/' . \preg_quote( \rest_get_url_prefix(), '#' ) . '#', '', $path );
|
||||
|
||||
// Create a REST request.
|
||||
$request = new \WP_REST_Request( 'POST', $rest_route );
|
||||
@ -369,8 +369,8 @@ class Dispatcher {
|
||||
* @param int $actor_id The actor ID.
|
||||
* @param Activity $activity The ActivityPub Activity.
|
||||
*/
|
||||
$inboxes = apply_filters( 'activitypub_additional_inboxes', array(), $actor_id, $activity );
|
||||
$inboxes = array_unique( $inboxes );
|
||||
$inboxes = \apply_filters( 'activitypub_additional_inboxes', array(), $actor_id, $activity );
|
||||
$inboxes = \array_unique( $inboxes );
|
||||
|
||||
$retries = self::send_to_inboxes( $inboxes, $outbox_item->ID );
|
||||
|
||||
@ -393,15 +393,15 @@ class Dispatcher {
|
||||
$cc = $activity->get_cc() ?? array();
|
||||
$to = $activity->get_to() ?? array();
|
||||
|
||||
$audience = array_merge( $cc, $to );
|
||||
$audience = \array_merge( $cc, $to );
|
||||
|
||||
// Remove "public placeholder" from the audience.
|
||||
$audience = array_diff( $audience, ACTIVITYPUB_PUBLIC_AUDIENCE_IDENTIFIERS );
|
||||
$audience = \array_diff( $audience, ACTIVITYPUB_PUBLIC_AUDIENCE_IDENTIFIERS );
|
||||
|
||||
if ( $audience ) {
|
||||
$mentioned_inboxes = Mention::get_inboxes( $audience );
|
||||
|
||||
return array_merge( $inboxes, $mentioned_inboxes );
|
||||
return \array_merge( $inboxes, $mentioned_inboxes );
|
||||
}
|
||||
|
||||
return $inboxes;
|
||||
@ -423,7 +423,7 @@ class Dispatcher {
|
||||
return $inboxes;
|
||||
}
|
||||
|
||||
if ( ! is_array( $in_reply_to ) ) {
|
||||
if ( ! \is_array( $in_reply_to ) ) {
|
||||
$in_reply_to = array( $in_reply_to );
|
||||
}
|
||||
|
||||
@ -475,20 +475,20 @@ class Dispatcher {
|
||||
$bcc = (array) ( $activity->get_bcc() ?? array() );
|
||||
$bto = (array) ( $activity->get_bto() ?? array() );
|
||||
|
||||
$audience = array_merge( $cc, $to, $bcc, $bto );
|
||||
$audience = \array_merge( $cc, $to, $bcc, $bto );
|
||||
|
||||
$send = (
|
||||
// Check if activity is public.
|
||||
is_activity_public( $activity ) ||
|
||||
// ...or check if follower endpoint is set.
|
||||
in_array( $actor->get_followers(), $audience, true )
|
||||
\in_array( $actor->get_followers(), $audience, true )
|
||||
);
|
||||
|
||||
if ( $send ) {
|
||||
$followers = Followers::get_inboxes_for_activity( $activity->to_json(), $outbox_item->post_author );
|
||||
|
||||
// Only send if there are followers to send to.
|
||||
$send = ! is_countable( $followers ) || 0 < count( $followers );
|
||||
$send = ! \is_countable( $followers ) || 0 < \count( $followers );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -499,7 +499,7 @@ class Dispatcher {
|
||||
* @param int $actor_id The actor ID.
|
||||
* @param \WP_Post $outbox_item The WordPress object.
|
||||
*/
|
||||
return apply_filters( 'activitypub_send_activity_to_followers', $send, $activity, $outbox_item->post_author, $outbox_item );
|
||||
return \apply_filters( 'activitypub_send_activity_to_followers', $send, $activity, $outbox_item->post_author, $outbox_item );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -523,7 +523,7 @@ class Dispatcher {
|
||||
return $inboxes;
|
||||
}
|
||||
|
||||
return array_merge( $inboxes, $relays );
|
||||
return \array_merge( $inboxes, $relays );
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -56,7 +56,7 @@ class Embed {
|
||||
// If we don't have an avatar URL, but we have an author URL, try to fetch it.
|
||||
if ( ! $avatar_url && $author_url ) {
|
||||
$author = Http::get_remote_object( $author_url );
|
||||
if ( is_wp_error( $author ) ) {
|
||||
if ( \is_wp_error( $author ) ) {
|
||||
$author = array();
|
||||
} else {
|
||||
$avatar_url = $author['icon']['url'] ?? '';
|
||||
@ -78,7 +78,7 @@ class Embed {
|
||||
|
||||
$title = $activity_object['name'] ?? '';
|
||||
$content = $activity_object['content'] ?? '';
|
||||
$published = isset( $activity_object['published'] ) ? gmdate( get_option( 'date_format' ) . ', ' . get_option( 'time_format' ), strtotime( $activity_object['published'] ) ) : '';
|
||||
$published = isset( $activity_object['published'] ) ? \gmdate( \get_option( 'date_format' ) . ', ' . \get_option( 'time_format' ), \strtotime( $activity_object['published'] ) ) : '';
|
||||
$boosts = isset( $activity_object['shares']['totalItems'] ) ? (int) $activity_object['shares']['totalItems'] : null;
|
||||
$favorites = isset( $activity_object['likes']['totalItems'] ) ? (int) $activity_object['likes']['totalItems'] : null;
|
||||
|
||||
@ -95,7 +95,7 @@ class Embed {
|
||||
);
|
||||
} elseif ( isset( $activity_object['attachment'] ) ) {
|
||||
foreach ( $activity_object['attachment'] as $attachment ) {
|
||||
$type = isset( $attachment['mediaType'] ) ? strtok( $attachment['mediaType'], '/' ) : strtolower( $attachment['type'] );
|
||||
$type = isset( $attachment['mediaType'] ) ? \strtok( $attachment['mediaType'], '/' ) : \strtolower( $attachment['type'] );
|
||||
|
||||
switch ( $type ) {
|
||||
case 'image':
|
||||
@ -112,8 +112,8 @@ class Embed {
|
||||
$images = \array_slice( $images, 0, 4 );
|
||||
}
|
||||
|
||||
ob_start();
|
||||
load_template(
|
||||
\ob_start();
|
||||
\load_template(
|
||||
ACTIVITYPUB_PLUGIN_DIR . 'templates/embed.php',
|
||||
false,
|
||||
array(
|
||||
@ -137,11 +137,11 @@ class Embed {
|
||||
// Grab the CSS.
|
||||
$css = \file_get_contents( ACTIVITYPUB_PLUGIN_DIR . 'assets/css/activitypub-embed.css' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents
|
||||
// We embed CSS directly because this may be in an iframe.
|
||||
printf( '<style>%s</style>', $css ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
\printf( '<style>%s</style>', $css ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
|
||||
}
|
||||
|
||||
// A little light whitespace cleanup.
|
||||
return preg_replace( '/\s+/', ' ', ob_get_clean() );
|
||||
return \preg_replace( '/\s+/', ' ', \ob_get_clean() );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -261,7 +261,7 @@ class Embed {
|
||||
return $response;
|
||||
}
|
||||
|
||||
if ( ( is_wp_error( $response ) && 'oembed_invalid_url' === $response->get_error_code() ) || empty( $response->html ) ) {
|
||||
if ( ( \is_wp_error( $response ) && 'oembed_invalid_url' === $response->get_error_code() ) || empty( $response->html ) ) {
|
||||
$url = $request->get_param( 'url' );
|
||||
$html = self::get_html( $url );
|
||||
|
||||
@ -274,12 +274,12 @@ class Embed {
|
||||
);
|
||||
|
||||
/** This filter is documented in wp-includes/class-wp-oembed.php */
|
||||
$data->html = apply_filters( 'oembed_result', $data->html, $url, $args );
|
||||
$data->html = \apply_filters( 'oembed_result', $data->html, $url, $args );
|
||||
|
||||
/** This filter is documented in wp-includes/class-wp-oembed-controller.php */
|
||||
$ttl = apply_filters( 'rest_oembed_ttl', DAY_IN_SECONDS, $url, $args );
|
||||
$ttl = \apply_filters( 'rest_oembed_ttl', DAY_IN_SECONDS, $url, $args );
|
||||
|
||||
set_transient( 'oembed_' . md5( serialize( $args ) ), $data, $ttl ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
|
||||
\set_transient( 'oembed_' . \md5( \serialize( $args ) ), $data, $ttl ); // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.serialize_serialize
|
||||
|
||||
$response = new \WP_REST_Response( $data );
|
||||
}
|
||||
|
||||
@ -38,8 +38,8 @@ class Event_Stream {
|
||||
* @param int $user_id The user ID.
|
||||
*/
|
||||
public static function signal_outbox( $outbox_activity_id, $activity, $user_id ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
$signal_key = sprintf( 'activitypub_sse_signal_%s_outbox', $user_id );
|
||||
\set_transient( $signal_key, time(), HOUR_IN_SECONDS );
|
||||
$signal_key = \sprintf( 'activitypub_sse_signal_%s_outbox', $user_id );
|
||||
\set_transient( $signal_key, \time(), HOUR_IN_SECONDS );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -54,8 +54,8 @@ class Event_Stream {
|
||||
}
|
||||
|
||||
foreach ( $user_ids as $user_id ) {
|
||||
$signal_key = sprintf( 'activitypub_sse_signal_%s_inbox', $user_id );
|
||||
\set_transient( $signal_key, time(), HOUR_IN_SECONDS );
|
||||
$signal_key = \sprintf( 'activitypub_sse_signal_%s_inbox', $user_id );
|
||||
\set_transient( $signal_key, \time(), HOUR_IN_SECONDS );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,6 +28,7 @@ class Handler {
|
||||
Handler\Collection_Sync::init();
|
||||
Handler\Create::init();
|
||||
Handler\Delete::init();
|
||||
Handler\Feature_Request::init();
|
||||
Handler\Follow::init();
|
||||
Handler\Like::init();
|
||||
Handler\Move::init();
|
||||
@ -41,7 +42,7 @@ class Handler {
|
||||
*
|
||||
* @since 1.3.0
|
||||
*/
|
||||
do_action( 'activitypub_register_handlers' );
|
||||
\do_action( 'activitypub_register_handlers' );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -65,6 +66,6 @@ class Handler {
|
||||
*
|
||||
* @since 8.1.0
|
||||
*/
|
||||
do_action( 'activitypub_register_outbox_handlers' );
|
||||
\do_action( 'activitypub_register_outbox_handlers' );
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,7 +57,7 @@ class Hashtag {
|
||||
|
||||
// Check if the (custom) post supports tags.
|
||||
$taxonomies = \get_object_taxonomies( $post );
|
||||
if ( ! in_array( 'post_tag', $taxonomies, true ) ) {
|
||||
if ( ! \in_array( 'post_tag', $taxonomies, true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -150,7 +150,7 @@ class Hashtag {
|
||||
|
||||
if ( $tag_object ) {
|
||||
$link = \get_term_link( $tag_object, 'post_tag' );
|
||||
return \sprintf( '<a rel="tag" class="hashtag u-tag u-category" href="%s">#%s</a>', esc_url( $link ), $tag );
|
||||
return \sprintf( '<a rel="tag" class="hashtag u-tag u-category" href="%s">#%s</a>', \esc_url( $link ), $tag );
|
||||
}
|
||||
|
||||
return '#' . $tag;
|
||||
|
||||
@ -18,9 +18,9 @@ class Http {
|
||||
/**
|
||||
* Send a POST Request with the needed HTTP Headers
|
||||
*
|
||||
* @param string $url The URL endpoint.
|
||||
* @param string $body The Post Body.
|
||||
* @param int $user_id The WordPress User-ID.
|
||||
* @param string $url The URL endpoint.
|
||||
* @param string $body The Post Body.
|
||||
* @param int|null $user_id The WordPress User-ID, or null to sign with the Application key.
|
||||
*
|
||||
* @return array|\WP_Error The POST Response or an WP_Error.
|
||||
*/
|
||||
@ -28,9 +28,9 @@ class Http {
|
||||
/**
|
||||
* Fires before an HTTP POST request is made.
|
||||
*
|
||||
* @param string $url The URL endpoint.
|
||||
* @param string $body The POST body.
|
||||
* @param int $user_id The WordPress User ID.
|
||||
* @param string $url The URL endpoint.
|
||||
* @param string $body The POST body.
|
||||
* @param int|null $user_id The WordPress User ID, or null when signing with the Application key.
|
||||
*/
|
||||
\do_action( 'activitypub_pre_http_post', $url, $body, $user_id );
|
||||
|
||||
@ -49,6 +49,12 @@ class Http {
|
||||
*/
|
||||
$timeout = \apply_filters( 'activitypub_remote_post_timeout', 10 );
|
||||
|
||||
/*
|
||||
* Get the private key for signing the request. If a user ID is provided,
|
||||
* get the user's private key; otherwise, use the application's private key.
|
||||
*/
|
||||
$private_key = null === $user_id ? Application::get_private_key() : Actors::get_private_key( $user_id );
|
||||
|
||||
$args = array(
|
||||
'timeout' => $timeout,
|
||||
'limit_response_size' => 1048576,
|
||||
@ -61,7 +67,7 @@ class Http {
|
||||
),
|
||||
'body' => $body,
|
||||
'key_id' => \json_decode( $body )->actor . '#main-key',
|
||||
'private_key' => Actors::get_private_key( $user_id ),
|
||||
'private_key' => $private_key,
|
||||
'user_id' => $user_id,
|
||||
);
|
||||
|
||||
@ -71,7 +77,7 @@ class Http {
|
||||
if ( $code >= 400 ) {
|
||||
$response = new \WP_Error(
|
||||
$code,
|
||||
__( 'Failed HTTP Request', 'activitypub' ),
|
||||
\__( 'Failed HTTP Request', 'activitypub' ),
|
||||
array(
|
||||
'status' => $code,
|
||||
'response' => $response,
|
||||
@ -85,7 +91,7 @@ class Http {
|
||||
* @param array|\WP_Error $response The response of the remote POST request.
|
||||
* @param string $url The URL endpoint.
|
||||
* @param string $body The Post Body.
|
||||
* @param int $user_id The WordPress User-ID.
|
||||
* @param int|null $user_id The WordPress User-ID, or null when signing with the Application key.
|
||||
*/
|
||||
\do_action( 'activitypub_safe_remote_post_response', $response, $url, $body, $user_id );
|
||||
|
||||
@ -98,7 +104,8 @@ class Http {
|
||||
* @param string $url The URL endpoint.
|
||||
* @param array $args Optional. Additional arguments to customize the request.
|
||||
* - 'headers': Array of headers to override defaults.
|
||||
* @param bool|int $cached Optional. Whether to return cached results, or cache duration. Default false.
|
||||
* @param bool|int $cached Optional. Whether to cache the response, or the cache duration in seconds for
|
||||
* successful responses. Failed responses use a fixed short backoff duration. Default false.
|
||||
*
|
||||
* @return array|\WP_Error The GET Response or a WP_Error.
|
||||
*/
|
||||
@ -165,8 +172,8 @@ class Http {
|
||||
'Content-Type' => 'application/activity+json',
|
||||
'Date' => \gmdate( 'D, d M Y H:i:s T' ),
|
||||
),
|
||||
'key_id' => Actors::get_by_id( Actors::APPLICATION_USER_ID )->get_id() . '#main-key',
|
||||
'private_key' => Actors::get_private_key( Actors::APPLICATION_USER_ID ),
|
||||
'key_id' => Application::get_key_id(),
|
||||
'private_key' => Application::get_private_key(),
|
||||
);
|
||||
|
||||
$args = \wp_parse_args( $args, $defaults );
|
||||
@ -176,23 +183,33 @@ class Http {
|
||||
$code = \wp_remote_retrieve_response_code( $response );
|
||||
|
||||
if ( \is_wp_error( $response ) || $code >= 400 ) {
|
||||
// Capture the served-from URL before $response is replaced by the error below.
|
||||
$effective_url = \is_wp_error( $response ) ? '' : self::effective_url( $response );
|
||||
|
||||
if ( ! $code ) {
|
||||
$code = 0;
|
||||
}
|
||||
$response = new \WP_Error( $code, __( 'Failed HTTP Request', 'activitypub' ), array( 'status' => $code ) );
|
||||
$response = new \WP_Error( $code, \__( 'Failed HTTP Request', 'activitypub' ), array( 'status' => $code ) );
|
||||
|
||||
/*
|
||||
* Always cache errors to prevent repeated timeout waits.
|
||||
* Cache errors to prevent repeated timeout waits, but never one reached via a
|
||||
* cross-host redirect: cached under the requested URL's key, a one-off open
|
||||
* redirect on the requested host would let a transient 4xx be replayed as a
|
||||
* repeatable federation outage (the same caching concern as the success path
|
||||
* below, just with a WP_Error instead of a document).
|
||||
*
|
||||
* - Retriable errors (timeouts, 5xx): 1 minute (server may recover quickly).
|
||||
* - Other errors (4xx): 15 minutes (client errors are more permanent).
|
||||
*/
|
||||
if ( \in_array( $code, ACTIVITYPUB_RETRY_ERROR_CODES, true ) || 0 === $code ) {
|
||||
$cache_duration = MINUTE_IN_SECONDS;
|
||||
} else {
|
||||
$cache_duration = 15 * MINUTE_IN_SECONDS;
|
||||
}
|
||||
if ( $cached && ( ! $effective_url || is_same_host( $url, $effective_url ) ) ) {
|
||||
if ( \in_array( $code, ACTIVITYPUB_RETRY_ERROR_CODES, true ) || 0 === $code ) {
|
||||
$cache_duration = MINUTE_IN_SECONDS;
|
||||
} else {
|
||||
$cache_duration = 15 * MINUTE_IN_SECONDS;
|
||||
}
|
||||
|
||||
\set_transient( $transient_key, $response, $cache_duration );
|
||||
\set_transient( $transient_key, $response, $cache_duration );
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
@ -205,12 +222,26 @@ class Http {
|
||||
*/
|
||||
\do_action( 'activitypub_safe_remote_get_response', $response, $url );
|
||||
|
||||
// Always cache successful responses.
|
||||
$cache_duration = $cached;
|
||||
if ( ! is_int( $cache_duration ) ) {
|
||||
$cache_duration = HOUR_IN_SECONDS;
|
||||
/*
|
||||
* Never persist a response that was redirected to a different host. The cache
|
||||
* is keyed on the requested URL, so caching cross-origin content under that key
|
||||
* would let a one-off open redirect on the requested host durably associate the
|
||||
* other host's document with that URL — a later lookup (e.g. a public-key fetch)
|
||||
* would then return it. Same-host redirects (e.g. http→https) stay cacheable.
|
||||
*/
|
||||
$effective_url = self::effective_url( $response );
|
||||
if ( $effective_url && ! is_same_host( $url, $effective_url ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
// Cache successful responses when caching is requested.
|
||||
if ( $cached ) {
|
||||
$cache_duration = $cached;
|
||||
if ( ! \is_int( $cache_duration ) ) {
|
||||
$cache_duration = HOUR_IN_SECONDS;
|
||||
}
|
||||
\set_transient( $transient_key, $response, $cache_duration );
|
||||
}
|
||||
\set_transient( $transient_key, $response, $cache_duration );
|
||||
|
||||
return $response;
|
||||
}
|
||||
@ -223,7 +254,7 @@ class Http {
|
||||
* @return bool True if the URL is a tombstone.
|
||||
*/
|
||||
public static function is_tombstone( $url ) {
|
||||
_deprecated_function( __METHOD__, '7.3.0', 'Activitypub\Tombstone::exists_remote' );
|
||||
\_deprecated_function( __METHOD__, '7.3.0', 'Activitypub\Tombstone::exists_remote' );
|
||||
|
||||
return Tombstone::exists_remote( $url );
|
||||
}
|
||||
@ -242,6 +273,15 @@ class Http {
|
||||
/**
|
||||
* Requests the Data from the Object-URL or Object-Array.
|
||||
*
|
||||
* Fetched objects are self-confirmed before they are returned, the same way
|
||||
* Mastodon's `JsonLdHelper#fetch_resource` works: an object is trusted only when
|
||||
* its own `id` is the URL it was actually served from (after any redirects). If
|
||||
* the document served at the requested URL declares a different `id`, that id is
|
||||
* dereferenced from its own host and accepted only when it self-confirms. This
|
||||
* makes every caller safe to cache the result under its `id` — one host can never
|
||||
* serve an object (and its public key) under another host's id — without each
|
||||
* caller having to re-check the origin itself.
|
||||
*
|
||||
* @param array|string $url_or_object The Object or the Object URL.
|
||||
* @param bool $cached Optional. Whether the result should be cached. Default true.
|
||||
*
|
||||
@ -251,10 +291,13 @@ class Http {
|
||||
/**
|
||||
* Filters the preemptive return value of a remote object request.
|
||||
*
|
||||
* This is an explicit in-process override (used for caching and tests), not
|
||||
* untrusted network data, so it is returned as-is without self-confirmation.
|
||||
*
|
||||
* @param array|string|null $response The response.
|
||||
* @param array|string|null $url_or_object The Object or the Object URL.
|
||||
*/
|
||||
$response = apply_filters( 'activitypub_pre_http_get_remote_object', null, $url_or_object );
|
||||
$response = \apply_filters( 'activitypub_pre_http_get_remote_object', null, $url_or_object );
|
||||
if ( null !== $response ) {
|
||||
return $response;
|
||||
}
|
||||
@ -280,6 +323,59 @@ class Http {
|
||||
return $url;
|
||||
}
|
||||
|
||||
$final_url = '';
|
||||
$object = self::fetch_object( $url, $cached, $final_url );
|
||||
|
||||
if ( \is_wp_error( $object ) ) {
|
||||
return $object;
|
||||
}
|
||||
|
||||
// Trust the document when it is served under its own id (after redirects).
|
||||
if ( id_matches_url( $object, $final_url ) ) {
|
||||
return $object;
|
||||
}
|
||||
|
||||
$declared_id = isset( $object['id'] ) && \is_string( $object['id'] ) ? $object['id'] : '';
|
||||
|
||||
/*
|
||||
* An id-less object cannot be cached under an id, so it cannot be written
|
||||
* under another id in an id-keyed cache. Return the document as served.
|
||||
*/
|
||||
if ( '' === $declared_id ) {
|
||||
return $object;
|
||||
}
|
||||
|
||||
// Re-fetch the declared id from its own host and require it to self-confirm. One hop only.
|
||||
$object = self::fetch_object( $declared_id, $cached, $final_url );
|
||||
|
||||
if ( \is_wp_error( $object ) ) {
|
||||
return $object;
|
||||
}
|
||||
|
||||
if ( ! id_matches_url( $object, $final_url ) ) {
|
||||
return new \WP_Error(
|
||||
'activitypub_object_id_mismatch',
|
||||
\__( 'The object id does not match the URL it was served from', 'activitypub' ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
}
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch and JSON-decode a single remote document.
|
||||
*
|
||||
* @param string $url The URL to fetch. Must already be resolved (not a WebFinger acct).
|
||||
* @param bool $cached Whether the result may be served from and written to cache.
|
||||
* @param string $final_url Filled by reference with the URL the document was served from,
|
||||
* after following any redirects.
|
||||
*
|
||||
* @return array|\WP_Error The decoded document, or WP_Error on failure.
|
||||
*/
|
||||
private static function fetch_object( $url, $cached, &$final_url ) {
|
||||
$final_url = $url;
|
||||
|
||||
if ( ! \wp_http_validate_url( $url ) ) {
|
||||
return new \WP_Error(
|
||||
'activitypub_no_valid_object_url',
|
||||
@ -297,8 +393,12 @@ class Http {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$data = \wp_remote_retrieve_body( $response );
|
||||
$data = \json_decode( $data, true );
|
||||
$effective_url = self::effective_url( $response );
|
||||
if ( $effective_url ) {
|
||||
$final_url = $effective_url;
|
||||
}
|
||||
|
||||
$data = \json_decode( \wp_remote_retrieve_body( $response ), true );
|
||||
|
||||
if ( ! $data ) {
|
||||
return new \WP_Error(
|
||||
@ -313,4 +413,36 @@ class Http {
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the effective URL a response was served from, after redirects.
|
||||
*
|
||||
* WordPress follows redirects transparently and exposes the final URL only on
|
||||
* the underlying Requests response object. Returns an empty string when the URL
|
||||
* cannot be determined, so callers fall back to the URL they requested.
|
||||
*
|
||||
* SECURITY: the redirect protections that build on this (the self-confirmation in
|
||||
* get_remote_object() and the cross-host cache skip in get()) fail OPEN when this
|
||||
* returns an empty string — self-confirmation then compares against the requested
|
||||
* URL, which a redirect could have bounced away from. This relies on the internal
|
||||
* `http_response` → Requests response `url` shape; if a future WordPress release
|
||||
* changes it, re-verify that this still returns the post-redirect URL.
|
||||
*
|
||||
* @param array $response A `wp_remote_get()` response array.
|
||||
*
|
||||
* @return string The final URL, or an empty string when unavailable.
|
||||
*/
|
||||
private static function effective_url( $response ) {
|
||||
if ( empty( $response['http_response'] ) || ! \is_object( $response['http_response'] ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$requests_response = $response['http_response']->get_response_object();
|
||||
|
||||
if ( ! \is_object( $requests_response ) || empty( $requests_response->url ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return (string) $requests_response->url;
|
||||
}
|
||||
}
|
||||
|
||||
@ -60,10 +60,10 @@ class Link {
|
||||
* @return string The final string.
|
||||
*/
|
||||
public static function replace_with_links( $result ) {
|
||||
if ( 'www.' === substr( $result[0], 0, 4 ) ) {
|
||||
if ( 'www.' === \substr( $result[0], 0, 4 ) ) {
|
||||
$result[0] = 'https://' . $result[0];
|
||||
}
|
||||
$parsed_url = \wp_parse_url( html_entity_decode( $result[0] ) );
|
||||
$parsed_url = \wp_parse_url( \html_entity_decode( $result[0] ) );
|
||||
if ( ! $parsed_url || empty( $parsed_url['host'] ) ) {
|
||||
return $result[0];
|
||||
}
|
||||
@ -84,8 +84,8 @@ class Link {
|
||||
}
|
||||
|
||||
$text_url = $parsed_url['host'];
|
||||
if ( 'www.' === substr( $text_url, 0, 4 ) ) {
|
||||
$text_url = substr( $text_url, 4 );
|
||||
if ( 'www.' === \substr( $text_url, 0, 4 ) ) {
|
||||
$text_url = \substr( $text_url, 4 );
|
||||
$invisible_prefix .= 'www.';
|
||||
}
|
||||
if ( ! empty( $parsed_url['port'] ) ) {
|
||||
@ -114,16 +114,16 @@ class Link {
|
||||
*
|
||||
* @param string $rel The rel attribute string. Default 'nofollow noopener noreferrer'.
|
||||
*/
|
||||
$rel = apply_filters( 'activitypub_link_rel', 'nofollow noopener noreferrer' );
|
||||
$rel = \apply_filters( 'activitypub_link_rel', 'nofollow noopener noreferrer' );
|
||||
|
||||
return \sprintf(
|
||||
'<a href="%s" target="_blank" rel="%s" translate="no"><span class="invisible">%s</span><span class="%s">%s</span><span class="invisible">%s</span></a>',
|
||||
esc_url( $result[0] ),
|
||||
\esc_url( $result[0] ),
|
||||
$rel,
|
||||
esc_html( $invisible_prefix ),
|
||||
\esc_html( $invisible_prefix ),
|
||||
$display_class,
|
||||
esc_html( $display ),
|
||||
esc_html( $invisible_suffix )
|
||||
\esc_html( $display ),
|
||||
\esc_html( $invisible_suffix )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -59,7 +59,7 @@ class Mailer {
|
||||
$post = \get_post( $comment->comment_post_ID );
|
||||
|
||||
/* translators: 1: Blog name, 2: Like or Repost, 3: Post title */
|
||||
return \sprintf( \esc_html__( '[%1$s] %2$s: %3$s', 'activitypub' ), \esc_html( get_option( 'blogname' ) ), \esc_html( $singular ), \esc_html( $post->post_title ) );
|
||||
return \sprintf( \esc_html__( '[%1$s] %2$s: %3$s', 'activitypub' ), \esc_html( \get_option( 'blogname' ) ), \esc_html( $singular ), \esc_html( $post->post_title ) );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -101,7 +101,7 @@ class Mailer {
|
||||
if ( 0 === (int) $comment->comment_parent ) {
|
||||
$notify_message = \sprintf(
|
||||
/* translators: 1: Comment type, 2: Post title */
|
||||
\html_entity_decode( esc_html__( 'New %1$s on your post “%2$s”.', 'activitypub' ) ),
|
||||
\html_entity_decode( \esc_html__( 'New %1$s on your post “%2$s”.', 'activitypub' ) ),
|
||||
\esc_html( $comment_type['singular'] ),
|
||||
\esc_html( $post->post_title )
|
||||
) . PHP_EOL . PHP_EOL;
|
||||
@ -110,7 +110,7 @@ class Mailer {
|
||||
$parent_comment = \get_comment( $comment->comment_parent );
|
||||
$notify_message = \sprintf(
|
||||
/* translators: 1: Comment type, 2: Post title, 3: Parent comment author */
|
||||
\html_entity_decode( esc_html__( 'New %1$s on your post “%2$s” in reply to %3$s’s comment.', 'activitypub' ) ),
|
||||
\html_entity_decode( \esc_html__( 'New %1$s on your post “%2$s” in reply to %3$s’s comment.', 'activitypub' ) ),
|
||||
\esc_html( $comment_type['singular'] ),
|
||||
\esc_html( $post->post_title ),
|
||||
\esc_html( $parent_comment->comment_author )
|
||||
@ -120,7 +120,19 @@ class Mailer {
|
||||
/* translators: 1: Website name, 2: Website IP address, 3: Website hostname. */
|
||||
$notify_message .= \sprintf( \esc_html__( 'From: %1$s (IP address: %2$s, %3$s)', 'activitypub' ), \esc_html( $comment->comment_author ), \esc_html( $comment->comment_author_IP ), \esc_html( $comment_author_domain ) ) . "\r\n";
|
||||
/* translators: Reaction author URL. */
|
||||
$notify_message .= \sprintf( \esc_html__( 'URL: %s', 'activitypub' ), \esc_url( $comment->comment_author_url ) ) . "\r\n\r\n";
|
||||
$notify_message .= \sprintf( \esc_html__( 'URL: %s', 'activitypub' ), \esc_url( $comment->comment_author_url ) ) . "\r\n";
|
||||
|
||||
// For quotes, link to the quoting post itself so the author can review and respond.
|
||||
if ( 'quote' === $comment->comment_type ) {
|
||||
$quote_url = Comment::get_source_url( $comment->comment_ID );
|
||||
|
||||
if ( $quote_url ) {
|
||||
/* translators: Quoting post URL. */
|
||||
$notify_message .= \sprintf( \esc_html__( 'Quoting post: %s', 'activitypub' ), \esc_url( $quote_url ) ) . "\r\n";
|
||||
}
|
||||
}
|
||||
|
||||
$notify_message .= "\r\n";
|
||||
/* translators: Comment type label */
|
||||
$notify_message .= \sprintf( \esc_html__( 'You can see all %s on this post here:', 'activitypub' ), \esc_html( $comment_type['label'] ) ) . "\r\n";
|
||||
$notify_message .= \get_permalink( $comment->comment_post_ID ) . '#' . \esc_attr( $comment_type['type'] ) . "\r\n\r\n";
|
||||
@ -144,11 +156,6 @@ class Mailer {
|
||||
// Extract the user ID (follows are always for a single user).
|
||||
$user_id = \is_array( $user_ids ) ? \reset( $user_ids ) : $user_ids;
|
||||
|
||||
// Do not send notifications to the Application user.
|
||||
if ( Actors::APPLICATION_USER_ID === $user_id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $user_id > Actors::BLOG_USER_ID ) {
|
||||
if ( ! \get_user_option( 'activitypub_mailer_new_follower', $user_id ) ) {
|
||||
return;
|
||||
@ -180,7 +187,7 @@ class Mailer {
|
||||
$actor['summary'] = Emoji::replace_for_actor( $actor['summary'], $actor['url'] );
|
||||
}
|
||||
|
||||
$template_args = array_merge(
|
||||
$template_args = \array_merge(
|
||||
$actor,
|
||||
array(
|
||||
'admin_url' => $admin_url,
|
||||
@ -305,7 +312,7 @@ class Mailer {
|
||||
$alt_function = static function ( $mailer ) use ( $actor, $activity ) {
|
||||
$content = \html_entity_decode(
|
||||
\wp_strip_all_tags(
|
||||
str_replace( '</p>', PHP_EOL . PHP_EOL, $activity['object']['content'] )
|
||||
\str_replace( '</p>', PHP_EOL . PHP_EOL, $activity['object']['content'] )
|
||||
),
|
||||
ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401
|
||||
);
|
||||
@ -345,8 +352,8 @@ class Mailer {
|
||||
}
|
||||
|
||||
$recipients = array();
|
||||
$mentions = wp_list_filter( (array) $activity['object']['tag'], array( 'type' => 'Mention' ) );
|
||||
$mentions = array_map( '\Activitypub\object_to_uri', $mentions );
|
||||
$mentions = \wp_list_filter( (array) $activity['object']['tag'], array( 'type' => 'Mention' ) );
|
||||
$mentions = \array_map( '\Activitypub\object_to_uri', $mentions );
|
||||
foreach ( (array) $user_ids as $user_id ) {
|
||||
$actor = Actors::get_by_id( $user_id );
|
||||
if ( \is_wp_error( $actor ) ) {
|
||||
@ -405,7 +412,7 @@ class Mailer {
|
||||
$alt_function = static function ( $mailer ) use ( $actor, $activity ) {
|
||||
$content = \html_entity_decode(
|
||||
\wp_strip_all_tags(
|
||||
str_replace( '</p>', PHP_EOL . PHP_EOL, $activity['object']['content'] )
|
||||
\str_replace( '</p>', PHP_EOL . PHP_EOL, $activity['object']['content'] )
|
||||
),
|
||||
ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401
|
||||
);
|
||||
|
||||
@ -80,7 +80,7 @@ class Mention {
|
||||
$username = $actor->get_preferred_username() ?: $actor->get_name() ?: Sanitize::webfinger( $result[0] );
|
||||
$url = object_to_uri( $actor->get_url() ?: $actor->get_id() );
|
||||
|
||||
return \sprintf( '<a rel="mention" class="u-url mention" href="%1$s">@%2$s</a>', esc_url( $url ), esc_html( $username ) );
|
||||
return \sprintf( '<a rel="mention" class="u-url mention" href="%1$s">@%2$s</a>', \esc_url( $url ), \esc_html( $username ) );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -96,7 +96,7 @@ class Mention {
|
||||
foreach ( $mentioned as $actor ) {
|
||||
$inbox = self::get_inbox_by_mentioned_actor( $actor );
|
||||
|
||||
if ( ! is_wp_error( $inbox ) && $inbox ) {
|
||||
if ( ! \is_wp_error( $inbox ) && $inbox ) {
|
||||
$inboxes[] = $inbox;
|
||||
}
|
||||
}
|
||||
@ -141,7 +141,7 @@ class Mention {
|
||||
\preg_match_all( '/@' . ACTIVITYPUB_USERNAME_REGEXP . '/i', $post_content, $matches );
|
||||
foreach ( $matches[0] as $match ) {
|
||||
$link = Webfinger::resolve( $match );
|
||||
if ( ! is_wp_error( $link ) ) {
|
||||
if ( ! \is_wp_error( $link ) ) {
|
||||
$mentions[ $match ] = $link;
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,6 +11,7 @@ use Activitypub\Collection\Actors;
|
||||
use Activitypub\Collection\Extra_Fields;
|
||||
use Activitypub\Collection\Followers;
|
||||
use Activitypub\Collection\Following;
|
||||
use Activitypub\Collection\Inbox;
|
||||
use Activitypub\Collection\Outbox;
|
||||
use Activitypub\Collection\Remote_Actors;
|
||||
use Activitypub\Transformer\Factory;
|
||||
@ -43,7 +44,7 @@ class Migration {
|
||||
* @return string The current version.
|
||||
*/
|
||||
public static function get_version() {
|
||||
return get_option( 'activitypub_db_version', 0 );
|
||||
return \get_option( 'activitypub_db_version', 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -185,15 +186,15 @@ class Migration {
|
||||
\wp_schedule_single_event( \time(), 'activitypub_upgrade', array( 'update_actor_json_storage' ) );
|
||||
}
|
||||
if ( \version_compare( $version_from_db, '7.0.0', '<' ) ) {
|
||||
wp_unschedule_hook( 'activitypub_update_followers' );
|
||||
wp_unschedule_hook( 'activitypub_cleanup_followers' );
|
||||
\wp_unschedule_hook( 'activitypub_update_followers' );
|
||||
\wp_unschedule_hook( 'activitypub_cleanup_followers' );
|
||||
|
||||
if ( ! \wp_next_scheduled( 'activitypub_update_remote_actors' ) ) {
|
||||
\wp_schedule_event( time(), 'hourly', 'activitypub_update_remote_actors' );
|
||||
\wp_schedule_event( \time(), 'hourly', 'activitypub_update_remote_actors' );
|
||||
}
|
||||
|
||||
if ( ! \wp_next_scheduled( 'activitypub_cleanup_remote_actors' ) ) {
|
||||
\wp_schedule_event( time(), 'daily', 'activitypub_cleanup_remote_actors' );
|
||||
\wp_schedule_event( \time(), 'daily', 'activitypub_cleanup_remote_actors' );
|
||||
}
|
||||
}
|
||||
if ( \version_compare( $version_from_db, '7.3.0', '<' ) ) {
|
||||
@ -213,11 +214,16 @@ class Migration {
|
||||
// Backfill historical statistics data (delay + jitter to avoid load spikes on hosts running many sites).
|
||||
\wp_schedule_single_event( \time() + HOUR_IN_SECONDS + \wp_rand( 0, 6 * HOUR_IN_SECONDS ), 'activitypub_backfill_statistics' );
|
||||
}
|
||||
|
||||
if ( \version_compare( $version_from_db, '8.3.0', '<' ) ) {
|
||||
if ( ! \wp_next_scheduled( 'activitypub_tombstone_migrate' ) ) {
|
||||
\wp_schedule_single_event( \time() + MINUTE_IN_SECONDS, 'activitypub_tombstone_migrate' );
|
||||
}
|
||||
}
|
||||
if ( \version_compare( $version_from_db, '9.1.0', '<' ) ) {
|
||||
self::migrate_application_keypair_option();
|
||||
self::delete_application_outbox_items();
|
||||
}
|
||||
|
||||
/*
|
||||
* Defer the flush to late in the `init` cycle (priority 20). Migration::init
|
||||
@ -297,8 +303,8 @@ class Migration {
|
||||
*/
|
||||
public static function migrate_from_0_17() {
|
||||
// Migrate followers.
|
||||
foreach ( get_users( array( 'fields' => 'ID' ) ) as $user_id ) {
|
||||
$followers = get_user_meta( $user_id, 'activitypub_followers', true );
|
||||
foreach ( \get_users( array( 'fields' => 'ID' ) ) as $user_id ) {
|
||||
$followers = \get_user_meta( $user_id, 'activitypub_followers', true );
|
||||
|
||||
if ( $followers ) {
|
||||
foreach ( $followers as $actor ) {
|
||||
@ -320,7 +326,7 @@ class Migration {
|
||||
);
|
||||
|
||||
foreach ( $user_ids as $user_id ) {
|
||||
wp_cache_delete( sprintf( Followers::CACHE_KEY_INBOXES, $user_id ), 'activitypub' );
|
||||
\wp_cache_delete( \sprintf( Followers::CACHE_KEY_INBOXES, $user_id ), 'activitypub' );
|
||||
}
|
||||
}
|
||||
|
||||
@ -328,13 +334,13 @@ class Migration {
|
||||
* Unschedule Hooks after updating to 2.0.0.
|
||||
*/
|
||||
private static function migrate_from_2_0_0() {
|
||||
wp_clear_scheduled_hook( 'activitypub_send_post_activity' );
|
||||
wp_clear_scheduled_hook( 'activitypub_send_update_activity' );
|
||||
wp_clear_scheduled_hook( 'activitypub_send_delete_activity' );
|
||||
\wp_clear_scheduled_hook( 'activitypub_send_post_activity' );
|
||||
\wp_clear_scheduled_hook( 'activitypub_send_update_activity' );
|
||||
\wp_clear_scheduled_hook( 'activitypub_send_delete_activity' );
|
||||
|
||||
wp_unschedule_hook( 'activitypub_send_post_activity' );
|
||||
wp_unschedule_hook( 'activitypub_send_update_activity' );
|
||||
wp_unschedule_hook( 'activitypub_send_delete_activity' );
|
||||
\wp_unschedule_hook( 'activitypub_send_post_activity' );
|
||||
\wp_unschedule_hook( 'activitypub_send_update_activity' );
|
||||
\wp_unschedule_hook( 'activitypub_send_delete_activity' );
|
||||
|
||||
$object_type = \get_option( 'activitypub_object_type', ACTIVITYPUB_DEFAULT_OBJECT_TYPE );
|
||||
if ( 'article' === $object_type ) {
|
||||
@ -355,7 +361,7 @@ class Migration {
|
||||
* Rename DB fields.
|
||||
*/
|
||||
private static function migrate_from_2_6_0() {
|
||||
wp_cache_flush();
|
||||
\wp_cache_flush();
|
||||
|
||||
self::update_usermeta_key( 'activitypub_user_description', 'activitypub_description' );
|
||||
|
||||
@ -371,7 +377,7 @@ class Migration {
|
||||
$latest_post_id = 0;
|
||||
|
||||
// Get the ID of the latest blog post and save it to the options table.
|
||||
$latest_post = get_posts(
|
||||
$latest_post = \get_posts(
|
||||
array(
|
||||
'numberposts' => 1,
|
||||
'orderby' => 'ID',
|
||||
@ -484,7 +490,7 @@ class Migration {
|
||||
$wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE post_type = %s", Remote_Actors::POST_TYPE )
|
||||
);
|
||||
foreach ( $followers as $id ) {
|
||||
clean_post_cache( $id );
|
||||
\clean_post_cache( $id );
|
||||
}
|
||||
}
|
||||
|
||||
@ -502,7 +508,7 @@ class Migration {
|
||||
|
||||
Comment::register_comment_types();
|
||||
$comment_types = Comment::get_comment_type_slugs();
|
||||
$type_inclusion = "AND comment_type IN ('" . implode( "','", $comment_types ) . "')";
|
||||
$type_inclusion = "AND comment_type IN ('" . \implode( "','", $comment_types ) . "')";
|
||||
|
||||
// Get and process this batch.
|
||||
$post_ids = $wpdb->get_col( // phpcs:ignore WordPress.DB
|
||||
@ -518,7 +524,7 @@ class Migration {
|
||||
\wp_update_comment_count_now( $post_id );
|
||||
}
|
||||
|
||||
if ( count( $post_ids ) === $batch_size ) {
|
||||
if ( \count( $post_ids ) === $batch_size ) {
|
||||
// Schedule next batch.
|
||||
return array( $batch_size, $offset + $batch_size );
|
||||
}
|
||||
@ -562,7 +568,7 @@ class Migration {
|
||||
}
|
||||
}
|
||||
|
||||
if ( count( $posts ) === $batch_size ) {
|
||||
if ( \count( $posts ) === $batch_size ) {
|
||||
return array(
|
||||
'batch_size' => $batch_size,
|
||||
'offset' => $offset + $batch_size,
|
||||
@ -599,7 +605,7 @@ class Migration {
|
||||
self::add_to_outbox( $comment, 'Create', $comment->user_id );
|
||||
}
|
||||
|
||||
if ( count( $comments ) === $batch_size ) {
|
||||
if ( \count( $comments ) === $batch_size ) {
|
||||
return array(
|
||||
'batch_size' => $batch_size,
|
||||
'offset' => $offset + $batch_size,
|
||||
@ -695,7 +701,7 @@ class Migration {
|
||||
);
|
||||
}
|
||||
|
||||
if ( count( $comments ) === $batch_size ) {
|
||||
if ( \count( $comments ) === $batch_size ) {
|
||||
return array(
|
||||
'batch_size' => $batch_size,
|
||||
'offset' => $offset + $batch_size,
|
||||
@ -1029,7 +1035,7 @@ class Migration {
|
||||
$wpdb->postmeta,
|
||||
array(
|
||||
'meta_key' => '_activitypub_following', // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
|
||||
'meta_value' => Actors::APPLICATION_USER_ID, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value
|
||||
'meta_value' => -1, // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value
|
||||
)
|
||||
);
|
||||
}
|
||||
@ -1083,7 +1089,7 @@ class Migration {
|
||||
$inbox_ids = $wpdb->get_col(
|
||||
$wpdb->prepare(
|
||||
"SELECT ID FROM {$wpdb->posts} WHERE post_type = %s",
|
||||
\Activitypub\Collection\Inbox::POST_TYPE
|
||||
Inbox::POST_TYPE
|
||||
)
|
||||
);
|
||||
|
||||
@ -1271,7 +1277,7 @@ class Migration {
|
||||
}
|
||||
|
||||
// Return batch info if there are more comments to process.
|
||||
if ( count( $comments ) === $batch_size ) {
|
||||
if ( \count( $comments ) === $batch_size ) {
|
||||
return array(
|
||||
'batch_size' => $batch_size,
|
||||
);
|
||||
@ -1321,7 +1327,7 @@ class Migration {
|
||||
}
|
||||
|
||||
// Return batch info if there are more actors to process.
|
||||
if ( count( $actors ) === $batch_size ) {
|
||||
if ( \count( $actors ) === $batch_size ) {
|
||||
return array(
|
||||
'batch_size' => $batch_size,
|
||||
'offset' => $offset + $batch_size,
|
||||
@ -1330,4 +1336,62 @@ class Migration {
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate the Application key pair option from the old name to the new name.
|
||||
*
|
||||
* Renames `activitypub_keypair_for_-1` to `activitypub_application_keypair`.
|
||||
* Older separate key options (activitypub_application_user_public_key /
|
||||
* activitypub_application_user_private_key) are migrated lazily on first read.
|
||||
*
|
||||
* @since 9.1.0
|
||||
*/
|
||||
public static function migrate_application_keypair_option() {
|
||||
self::update_options_key( 'activitypub_keypair_for_-1', Application::KEYPAIR_OPTION_KEY );
|
||||
|
||||
// The raw rename bypasses the options API, so drop only the two stale option caches (plus the autoload bucket) instead of flushing everything.
|
||||
\wp_cache_delete( 'activitypub_keypair_for_-1', 'options' );
|
||||
\wp_cache_delete( Application::KEYPAIR_OPTION_KEY, 'options' );
|
||||
\wp_cache_delete( 'alloptions', 'options' );
|
||||
|
||||
/*
|
||||
* If an early Application::get_keypair() read already created the destination
|
||||
* option, the rename above is a no-op blocked by the unique `option_name`,
|
||||
* leaving the legacy row behind. Drop it once the destination is in place.
|
||||
*/
|
||||
if ( false !== \get_option( Application::KEYPAIR_OPTION_KEY, false ) ) {
|
||||
\delete_option( 'activitypub_keypair_for_-1' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete outbox items that belonged to the Application actor.
|
||||
*
|
||||
* The Application used to queue Reject activities through the Outbox as user
|
||||
* ID -1. It no longer dispatches activities, so any pending items are
|
||||
* undeliverable and are removed.
|
||||
*
|
||||
* @since 9.1.0
|
||||
*/
|
||||
public static function delete_application_outbox_items() {
|
||||
$items = \get_posts(
|
||||
array(
|
||||
'post_type' => Outbox::POST_TYPE,
|
||||
'post_status' => 'any',
|
||||
'nopaging' => true,
|
||||
'fields' => 'ids',
|
||||
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
|
||||
'meta_query' => array(
|
||||
array(
|
||||
'key' => '_activitypub_activity_actor',
|
||||
'value' => 'application',
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
foreach ( $items as $item_id ) {
|
||||
\wp_delete_post( $item_id, true );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -233,7 +233,7 @@ class Moderation {
|
||||
* @param array $values Array of values to block.
|
||||
*/
|
||||
public static function add_site_blocks( $type, $values ) {
|
||||
if ( ! in_array( $type, array( self::TYPE_DOMAIN, self::TYPE_KEYWORD ), true ) ) {
|
||||
if ( ! \in_array( $type, array( self::TYPE_DOMAIN, self::TYPE_KEYWORD ), true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -252,7 +252,7 @@ class Moderation {
|
||||
}
|
||||
|
||||
$existing = \get_option( self::OPTION_KEYS[ $type ], array() );
|
||||
\update_option( self::OPTION_KEYS[ $type ], array_unique( array_merge( $existing, $values ) ) );
|
||||
\update_option( self::OPTION_KEYS[ $type ], \array_unique( \array_merge( $existing, $values ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -29,13 +29,13 @@ class Move {
|
||||
*
|
||||
* @param bool $domain_moves_enabled Whether domain moves are enabled.
|
||||
*/
|
||||
$domain_moves_enabled = apply_filters( 'activitypub_enable_primary_domain_moves', false );
|
||||
$domain_moves_enabled = \apply_filters( 'activitypub_enable_primary_domain_moves', false );
|
||||
|
||||
if ( $domain_moves_enabled ) {
|
||||
// Add the filter to change the domain.
|
||||
\add_filter( 'update_option_home', array( self::class, 'change_domain' ), 10, 2 );
|
||||
|
||||
if ( get_option( 'activitypub_old_host' ) ) {
|
||||
if ( \get_option( 'activitypub_old_host' ) ) {
|
||||
\add_action( 'activitypub_construct_model_actor', array( self::class, 'maybe_initiate_old_user' ) );
|
||||
\add_action( 'activitypub_pre_send_to_inboxes', array( self::class, 'pre_send_to_inboxes' ) );
|
||||
|
||||
@ -99,8 +99,8 @@ class Move {
|
||||
|
||||
// Check if the `Move` Activity is valid.
|
||||
$also_known_as = $target_actor->get_also_known_as() ?? array();
|
||||
if ( ! in_array( $from, $also_known_as, true ) ) {
|
||||
return new \WP_Error( 'invalid_target', __( 'Invalid target', 'activitypub' ) );
|
||||
if ( ! \in_array( $from, $also_known_as, true ) ) {
|
||||
return new \WP_Error( 'invalid_target', \__( 'Invalid target', 'activitypub' ) );
|
||||
}
|
||||
|
||||
$activity = new Activity();
|
||||
@ -218,7 +218,7 @@ class Move {
|
||||
$actor_id = $actor->get_id();
|
||||
|
||||
// Replace the new host with the old host in the actor ID.
|
||||
$old_actor_id = str_replace( $to_host, $from_host, $actor_id );
|
||||
$old_actor_id = \str_replace( $to_host, $from_host, $actor_id );
|
||||
|
||||
// Call Move::internally for this actor.
|
||||
$result = self::internally( $old_actor_id, $actor_id );
|
||||
@ -236,7 +236,7 @@ class Move {
|
||||
continue;
|
||||
}
|
||||
|
||||
$json = str_replace( $to_host, $from_host, $actor->to_json() );
|
||||
$json = \str_replace( $to_host, $from_host, $actor->to_json() );
|
||||
|
||||
// Save the current actor data after migration.
|
||||
if ( $actor instanceof Blog ) {
|
||||
@ -284,7 +284,7 @@ class Move {
|
||||
* @param string $json The ActivityPub Activity JSON.
|
||||
*/
|
||||
public static function pre_send_to_inboxes( $json ) {
|
||||
$json = json_decode( $json, true );
|
||||
$json = \json_decode( $json, true );
|
||||
|
||||
if ( 'Move' !== $json['type'] ) {
|
||||
return;
|
||||
|
||||
@ -27,6 +27,10 @@ class Options {
|
||||
\add_filter( 'pre_option_activitypub_following_ui', array( self::class, 'pre_option_activitypub_following_ui' ) );
|
||||
\add_filter( 'pre_option_activitypub_create_posts', array( self::class, 'pre_option_activitypub_create_posts' ) );
|
||||
|
||||
\add_filter( 'pre_option_activitypub_distribution_mode', array( self::class, 'pre_option_activitypub_distribution_mode' ) );
|
||||
\add_filter( 'activitypub_dispatcher_batch_size', array( self::class, 'filter_dispatcher_batch_size' ) );
|
||||
\add_filter( 'activitypub_scheduler_async_batch_pause', array( self::class, 'filter_scheduler_batch_pause' ), 10, 2 );
|
||||
|
||||
\add_filter( 'pre_option_activitypub_allow_likes', array( self::class, 'maybe_disable_interactions' ) );
|
||||
\add_filter( 'pre_option_activitypub_allow_replies', array( self::class, 'maybe_disable_interactions' ) );
|
||||
|
||||
@ -201,6 +205,24 @@ class Options {
|
||||
)
|
||||
);
|
||||
|
||||
\register_setting(
|
||||
'activitypub',
|
||||
'activitypub_default_feature_policy',
|
||||
array(
|
||||
'type' => 'string',
|
||||
'description' => 'Default policy for who can include this site\'s actors in featured collections (FEP-7aa9).',
|
||||
'default' => ACTIVITYPUB_INTERACTION_POLICY_ME,
|
||||
'sanitize_callback' => static function ( $value ) {
|
||||
$allowed = array(
|
||||
ACTIVITYPUB_INTERACTION_POLICY_ANYONE,
|
||||
ACTIVITYPUB_INTERACTION_POLICY_FOLLOWERS,
|
||||
ACTIVITYPUB_INTERACTION_POLICY_ME,
|
||||
);
|
||||
return \in_array( $value, $allowed, true ) ? $value : ACTIVITYPUB_INTERACTION_POLICY_ME;
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
\register_setting(
|
||||
'activitypub',
|
||||
'activitypub_relays',
|
||||
@ -371,6 +393,45 @@ class Options {
|
||||
)
|
||||
);
|
||||
|
||||
$default_distribution = self::get_distribution_preset_values()['default'];
|
||||
|
||||
\register_setting(
|
||||
'activitypub_advanced',
|
||||
'activitypub_distribution_mode',
|
||||
array(
|
||||
'type' => 'string',
|
||||
'description' => \__( 'Distribution mode for federation delivery.', 'activitypub' ),
|
||||
'default' => 'default',
|
||||
'sanitize_callback' => array( self::class, 'sanitize_distribution_mode' ),
|
||||
)
|
||||
);
|
||||
|
||||
\register_setting(
|
||||
'activitypub_advanced',
|
||||
'activitypub_custom_batch_size',
|
||||
array(
|
||||
'type' => 'integer',
|
||||
'description' => \__( 'Custom batch size for federation delivery.', 'activitypub' ),
|
||||
'default' => $default_distribution['batch_size'],
|
||||
'sanitize_callback' => static function ( $value ) {
|
||||
return \min( 500, \max( 1, \absint( $value ) ) );
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
\register_setting(
|
||||
'activitypub_advanced',
|
||||
'activitypub_custom_batch_pause',
|
||||
array(
|
||||
'type' => 'integer',
|
||||
'description' => \__( 'Custom pause in seconds between batches.', 'activitypub' ),
|
||||
'default' => $default_distribution['pause'],
|
||||
'sanitize_callback' => static function ( $value ) {
|
||||
return \min( 3600, \absint( $value ) );
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
/*
|
||||
* Options Group: activitypub_blog
|
||||
*/
|
||||
@ -673,6 +734,249 @@ class Options {
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-get option filter for the Distribution Mode.
|
||||
*
|
||||
* @since 9.0.0
|
||||
*
|
||||
* @param string|false $pre The pre-get option value.
|
||||
*
|
||||
* @return string|false The distribution mode or false if it should not be filtered.
|
||||
*/
|
||||
public static function pre_option_activitypub_distribution_mode( $pre ) {
|
||||
return self::resolve_distribution_mode( $pre, ACTIVITYPUB_DISTRIBUTION_MODE );
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the distribution mode is locked to a valid preset by the
|
||||
* `ACTIVITYPUB_DISTRIBUTION_MODE` constant.
|
||||
*
|
||||
* Returns true only when the constant is set to a key recognized by
|
||||
* `get_distribution_preset_values()`. Invalid constant values fall back
|
||||
* to `'default'` at runtime (see `resolve_distribution_mode()`) but the
|
||||
* UI stays visible so admins can spot the misconfiguration.
|
||||
*
|
||||
* @since 9.0.0
|
||||
*
|
||||
* @return bool True when the constant pins the mode to a valid preset.
|
||||
*/
|
||||
public static function is_distribution_mode_locked() {
|
||||
if ( false === ACTIVITYPUB_DISTRIBUTION_MODE ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return \in_array( ACTIVITYPUB_DISTRIBUTION_MODE, \array_keys( self::get_distribution_preset_values() ), true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the distribution mode against the wp-config constant.
|
||||
*
|
||||
* Extracted from `pre_option_activitypub_distribution_mode()` so the
|
||||
* constant-lock path can be exercised from tests without redefining
|
||||
* the real constant.
|
||||
*
|
||||
* Only preset modes are honored via the constant. The 'custom' mode
|
||||
* is excluded because its batch size and pause values are still read
|
||||
* from the database, which would defeat the purpose of locking the
|
||||
* mode via wp-config.php.
|
||||
*
|
||||
* @since 9.0.0
|
||||
*
|
||||
* @param string|false $pre The pre-get option value.
|
||||
* @param mixed $constant_value The value of `ACTIVITYPUB_DISTRIBUTION_MODE`.
|
||||
*
|
||||
* @return string|false Mode if locked, `$pre` otherwise.
|
||||
*/
|
||||
public static function resolve_distribution_mode( $pre, $constant_value ) {
|
||||
if ( false === $constant_value ) {
|
||||
return $pre;
|
||||
}
|
||||
|
||||
$allowed = \array_keys( self::get_distribution_preset_values() );
|
||||
|
||||
if ( \in_array( $constant_value, $allowed, true ) ) {
|
||||
return $constant_value;
|
||||
}
|
||||
|
||||
\_doing_it_wrong(
|
||||
__METHOD__,
|
||||
\sprintf(
|
||||
/* translators: %s: invalid constant value */
|
||||
\esc_html__( 'ACTIVITYPUB_DISTRIBUTION_MODE value %s is not a valid preset; falling back to default.', 'activitypub' ),
|
||||
\esc_html( (string) $constant_value )
|
||||
),
|
||||
'9.0.0'
|
||||
);
|
||||
|
||||
return 'default';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the raw batch_size/pause values for each distribution preset.
|
||||
*
|
||||
* Single source of truth for the preset values, used in the hot path
|
||||
* (get_distribution_params, sanitize_distribution_mode, resolve_distribution_mode)
|
||||
* to avoid running translation calls just to check keys or numbers.
|
||||
*
|
||||
* @since 9.0.0
|
||||
*
|
||||
* @return array Associative array of mode => { batch_size, pause }.
|
||||
*/
|
||||
private static function get_distribution_preset_values() {
|
||||
return array(
|
||||
'default' => array(
|
||||
'batch_size' => 100,
|
||||
'pause' => 15,
|
||||
),
|
||||
'balanced' => array(
|
||||
'batch_size' => 50,
|
||||
'pause' => 30,
|
||||
),
|
||||
'eco' => array(
|
||||
'batch_size' => 20,
|
||||
'pause' => 30,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the available distribution mode presets with UI labels.
|
||||
*
|
||||
* Decorates `get_distribution_preset_values()` with translated labels
|
||||
* and descriptions for use in the admin settings page.
|
||||
*
|
||||
* @since 9.0.0
|
||||
*
|
||||
* @return array Associative array of mode => { batch_size, pause, label, description }.
|
||||
*/
|
||||
public static function get_distribution_modes() {
|
||||
$modes = self::get_distribution_preset_values();
|
||||
|
||||
$modes['default']['label'] = \__( 'Default', 'activitypub' );
|
||||
$modes['default']['description'] = \sprintf(
|
||||
/* translators: 1: batch size, 2: pause in seconds */
|
||||
\__( 'Deliver activities as fast as possible (<code>%1$d</code> per batch, <code>%2$ds</code> pause).', 'activitypub' ),
|
||||
$modes['default']['batch_size'],
|
||||
$modes['default']['pause']
|
||||
);
|
||||
$modes['balanced']['label'] = \__( 'Balanced', 'activitypub' );
|
||||
$modes['balanced']['description'] = \sprintf(
|
||||
/* translators: 1: batch size, 2: pause in seconds */
|
||||
\__( 'Moderate pace with reasonable pauses between batches (<code>%1$d</code> per batch, <code>%2$ds</code> pause).', 'activitypub' ),
|
||||
$modes['balanced']['batch_size'],
|
||||
$modes['balanced']['pause']
|
||||
);
|
||||
$modes['eco']['label'] = \__( 'Eco Mode', 'activitypub' );
|
||||
$modes['eco']['description'] = \sprintf(
|
||||
/* translators: 1: batch size, 2: pause in seconds */
|
||||
\__( 'Gentle on server resources, ideal for shared hosting (<code>%1$d</code> per batch, <code>%2$ds</code> pause).', 'activitypub' ),
|
||||
$modes['eco']['batch_size'],
|
||||
$modes['eco']['pause']
|
||||
);
|
||||
|
||||
return $modes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize the distribution mode option.
|
||||
*
|
||||
* Restricts the stored value to a known preset (from
|
||||
* `get_distribution_modes()`) or `'custom'`. Anything else
|
||||
* falls back to `'default'`.
|
||||
*
|
||||
* @since 9.0.0
|
||||
*
|
||||
* @param string $value The submitted option value.
|
||||
*
|
||||
* @return string A valid distribution mode key.
|
||||
*/
|
||||
public static function sanitize_distribution_mode( $value ) {
|
||||
$allowed = \array_merge( \array_keys( self::get_distribution_preset_values() ), array( 'custom' ) );
|
||||
|
||||
return \in_array( $value, $allowed, true ) ? $value : 'default';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get distribution parameters for the current mode.
|
||||
*
|
||||
* @since 9.0.0
|
||||
*
|
||||
* @return array { mode: string, batch_size: int, pause: int }
|
||||
*/
|
||||
public static function get_distribution_params() {
|
||||
$mode = \get_option( 'activitypub_distribution_mode', 'default' );
|
||||
$modes = self::get_distribution_preset_values();
|
||||
|
||||
if ( isset( $modes[ $mode ] ) ) {
|
||||
return array(
|
||||
'mode' => $mode,
|
||||
'batch_size' => $modes[ $mode ]['batch_size'],
|
||||
'pause' => $modes[ $mode ]['pause'],
|
||||
);
|
||||
}
|
||||
|
||||
// Custom mode reads its values from dedicated options; any other
|
||||
// unrecognized mode falls back to the default preset so callers
|
||||
// always receive a valid configuration.
|
||||
if ( 'custom' !== $mode ) {
|
||||
return array(
|
||||
'mode' => 'default',
|
||||
'batch_size' => $modes['default']['batch_size'],
|
||||
'pause' => $modes['default']['pause'],
|
||||
);
|
||||
}
|
||||
|
||||
$default_params = $modes['default'];
|
||||
|
||||
return array(
|
||||
'mode' => 'custom',
|
||||
'batch_size' => \max( 1, \absint( \get_option( 'activitypub_custom_batch_size', $default_params['batch_size'] ) ) ),
|
||||
'pause' => \absint( \get_option( 'activitypub_custom_batch_pause', $default_params['pause'] ) ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the dispatcher batch size based on distribution mode.
|
||||
*
|
||||
* In `'default'` mode the upstream value is passed through so the
|
||||
* `ACTIVITYPUB_OUTBOX_PROCESSING_BATCH_SIZE` constant and other filters
|
||||
* still win; any explicit mode imposes its own batch size.
|
||||
*
|
||||
* @since 9.0.0
|
||||
*
|
||||
* @param int $batch_size The default batch size.
|
||||
*
|
||||
* @return int The batch size for the current distribution mode.
|
||||
*/
|
||||
public static function filter_dispatcher_batch_size( $batch_size ) {
|
||||
$params = self::get_distribution_params();
|
||||
|
||||
return 'default' === $params['mode'] ? $batch_size : $params['batch_size'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the scheduler batch pause based on distribution mode.
|
||||
*
|
||||
* Only delivery batches (`activitypub_send_activity`) are affected. Every
|
||||
* mode imposes its own delivery pause: `'default'` is the fast preset, which
|
||||
* is intentionally shorter than the generic async-batch baseline, so it does
|
||||
* not pass the upstream value through.
|
||||
*
|
||||
* @since 9.0.0
|
||||
*
|
||||
* @param int $pause The default pause in seconds.
|
||||
* @param string|false|null $hook The async batch hook being scheduled.
|
||||
*
|
||||
* @return int The pause for the current distribution mode.
|
||||
*/
|
||||
public static function filter_scheduler_batch_pause( $pause, $hook = null ) {
|
||||
if ( 'activitypub_send_activity' !== $hook ) {
|
||||
return $pause;
|
||||
}
|
||||
|
||||
return self::get_distribution_params()['pause'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize purge day values.
|
||||
*
|
||||
|
||||
@ -519,10 +519,10 @@ class Post_Types {
|
||||
'single' => true,
|
||||
'description' => 'Allowed redirect URIs for this client.',
|
||||
'sanitize_callback' => static function ( $value ) {
|
||||
if ( ! is_array( $value ) ) {
|
||||
if ( ! \is_array( $value ) ) {
|
||||
return array();
|
||||
}
|
||||
return array_map( array( Sanitize::class, 'redirect_uri' ), $value );
|
||||
return \array_map( array( Sanitize::class, 'redirect_uri' ), $value );
|
||||
},
|
||||
)
|
||||
);
|
||||
@ -574,6 +574,10 @@ class Post_Types {
|
||||
\register_post_type(
|
||||
Tombstone::POST_TYPE,
|
||||
array(
|
||||
'labels' => array(
|
||||
'name' => \_x( 'Tombstones', 'post_type plural name', 'activitypub' ),
|
||||
'singular_name' => \_x( 'Tombstone', 'post_type single name', 'activitypub' ),
|
||||
),
|
||||
'public' => false,
|
||||
'publicly_queryable' => false,
|
||||
'show_ui' => false,
|
||||
@ -741,8 +745,8 @@ class Post_Types {
|
||||
return array(
|
||||
'username' => $actor->get_preferred_username(),
|
||||
'name' => $actor->get_name() ?? $actor->get_preferred_username(),
|
||||
'icon' => object_to_uri( $actor->get_icon() ),
|
||||
'url' => object_to_uri( $actor->get_url() ?? $actor->get_id() ),
|
||||
'icon' => \sanitize_url( object_to_uri( $actor->get_icon() ) ?? '' ),
|
||||
'url' => \sanitize_url( object_to_uri( $actor->get_url() ?? $actor->get_id() ) ?? '' ),
|
||||
'webfinger' => Remote_Actors::get_acct( $response['id'] ),
|
||||
'identifier' => $actor->get_id(),
|
||||
);
|
||||
@ -829,8 +833,8 @@ class Post_Types {
|
||||
return array(
|
||||
'username' => $actor->get_preferred_username(),
|
||||
'name' => $actor->get_name() ?? $actor->get_preferred_username(),
|
||||
'icon' => object_to_uri( $actor->get_icon() ),
|
||||
'url' => object_to_uri( $actor->get_url() ?? $actor->get_id() ),
|
||||
'icon' => \sanitize_url( object_to_uri( $actor->get_icon() ) ?? '' ),
|
||||
'url' => \sanitize_url( object_to_uri( $actor->get_url() ?? $actor->get_id() ) ?? '' ),
|
||||
'webfinger' => Remote_Actors::get_acct( $id ),
|
||||
'identifier' => $actor->get_id(),
|
||||
);
|
||||
@ -852,7 +856,7 @@ class Post_Types {
|
||||
'rest_' . Remote_Posts::POST_TYPE . '_collection_params',
|
||||
function ( $params ) {
|
||||
$params['user_id'] = array(
|
||||
'description' => __( 'Filter posts by user ID (0 for site/blog actor).', 'activitypub' ),
|
||||
'description' => \__( 'Filter posts by user ID (0 for site/blog actor).', 'activitypub' ),
|
||||
'type' => 'integer',
|
||||
'sanitize_callback' => 'absint',
|
||||
);
|
||||
@ -942,7 +946,7 @@ class Post_Types {
|
||||
*/
|
||||
public static function register_object_type_user_param( $params ) {
|
||||
$params['user_id'] = array(
|
||||
'description' => __( 'Filter terms to those with posts from this user ID.', 'activitypub' ),
|
||||
'description' => \__( 'Filter terms to those with posts from this user ID.', 'activitypub' ),
|
||||
'type' => 'integer',
|
||||
'sanitize_callback' => 'absint',
|
||||
);
|
||||
@ -1010,7 +1014,7 @@ class Post_Types {
|
||||
);
|
||||
|
||||
if ( isset( $post_metas[ $meta_key ] ) && $post_metas[ $meta_key ] === (string) $meta_value ) {
|
||||
if ( 'update_post_metadata' === current_action() ) {
|
||||
if ( 'update_post_metadata' === \current_action() ) {
|
||||
\delete_post_meta( $object_id, $meta_key );
|
||||
}
|
||||
|
||||
|
||||
@ -7,9 +7,11 @@
|
||||
|
||||
namespace Activitypub;
|
||||
|
||||
use Activitypub\Activity\Extended_Object\Feature_Authorization;
|
||||
use Activitypub\Activity\Extended_Object\Quote_Authorization;
|
||||
use Activitypub\Collection\Actors;
|
||||
use Activitypub\Collection\Outbox;
|
||||
use Activitypub\Handler\Feature_Request;
|
||||
use Activitypub\Transformer\Factory;
|
||||
|
||||
/**
|
||||
@ -139,8 +141,15 @@ class Query {
|
||||
private function prepare_activitypub_data() {
|
||||
$queried_object = $this->get_queried_object();
|
||||
|
||||
if ( $queried_object instanceof \WP_Post && \get_query_var( 'stamp' ) ) {
|
||||
return $this->maybe_get_stamp();
|
||||
if ( \get_query_var( 'stamp' ) ) {
|
||||
if ( $queried_object instanceof \WP_Post ) {
|
||||
return $this->maybe_get_stamp();
|
||||
}
|
||||
|
||||
// Note: the blog actor's `actor` query var is '0', which is falsy but valid.
|
||||
if ( $queried_object instanceof \WP_User || '' !== \get_query_var( 'actor' ) ) {
|
||||
return $this->maybe_get_actor_stamp();
|
||||
}
|
||||
}
|
||||
|
||||
// Check for Outbox Activity.
|
||||
@ -220,17 +229,16 @@ class Query {
|
||||
*
|
||||
* @param \WP_Term|\WP_Post_Type|\WP_Post|\WP_User|\WP_Comment|null $queried_object The queried object.
|
||||
*/
|
||||
return apply_filters( 'activitypub_queried_object', $queried_object );
|
||||
return \apply_filters( 'activitypub_queried_object', $queried_object );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the virtual object.
|
||||
*
|
||||
* Virtual objects are objects that are not stored in the database, but are created on the fly.
|
||||
* The plugins currently supports two virtual objects: The Blog-Actor and the Application-Actor.
|
||||
* The plugin currently supports one virtual object: The Blog-Actor.
|
||||
*
|
||||
* @see \Activitypub\Model\Blog
|
||||
* @see \Activitypub\Model\Application
|
||||
*
|
||||
* @return object|null The virtual object.
|
||||
*/
|
||||
@ -243,7 +251,7 @@ class Query {
|
||||
|
||||
$author_id = url_to_authorid( $url );
|
||||
|
||||
if ( ! is_numeric( $author_id ) ) {
|
||||
if ( ! \is_numeric( $author_id ) ) {
|
||||
$author_id = $url;
|
||||
}
|
||||
|
||||
@ -403,8 +411,13 @@ class Query {
|
||||
|
||||
$post = $this->get_queried_object();
|
||||
|
||||
// Ensure the meta belongs to the queried post to prevent arbitrary meta disclosure.
|
||||
if ( (int) $meta->post_id !== $post->ID ) {
|
||||
/*
|
||||
* Only quote-authorization meta may be reflected as a stamp, and only for the queried
|
||||
* post. Checking the post id alone would still let an unauthenticated request read any
|
||||
* of that post's meta rows (e.g. _edit_lock or private custom fields) by guessing a
|
||||
* meta_id, so the meta key is verified too.
|
||||
*/
|
||||
if ( '_activitypub_quoted_by' !== $meta->meta_key || (int) $meta->post_id !== $post->ID ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -433,4 +446,69 @@ class Query {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maybe get a FeatureAuthorization object from an actor-scoped stamp.
|
||||
*
|
||||
* Resolves URLs of the form `?actor=USER_ID&stamp=STAMP_ID` against the
|
||||
* actor's stamp store, see {@see Feature_Request::get_stamp()}. Ownership
|
||||
* is enforced by resolving the stamp scoped to the queried actor, which
|
||||
* includes the blog actor (`actor=0`).
|
||||
*
|
||||
* @return bool True if a FeatureAuthorization was prepared, false otherwise.
|
||||
*/
|
||||
private function maybe_get_actor_stamp() {
|
||||
$stamp_id = (int) \get_query_var( 'stamp' );
|
||||
$actor_var = \get_query_var( 'actor' );
|
||||
|
||||
if ( ! $stamp_id ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( '' === $actor_var ) {
|
||||
$queried = $this->get_queried_object();
|
||||
if ( ! $queried instanceof \WP_User ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$actor_id = (int) $queried->ID;
|
||||
} else {
|
||||
// Values like '0e1' or '1.5' pass is_numeric() but cast to 0/1 and alias
|
||||
// an actor, so require a plain decimal integer before casting.
|
||||
if ( ! \ctype_digit( (string) $actor_var ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$actor_id = (int) $actor_var;
|
||||
}
|
||||
|
||||
$instrument = Feature_Request::get_stamp( $actor_id, $stamp_id );
|
||||
if ( null === $instrument ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$actor = Actors::get_by_id( $actor_id );
|
||||
if ( \is_wp_error( $actor ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$stamp_url = \add_query_arg(
|
||||
array(
|
||||
'actor' => $actor_id,
|
||||
'stamp' => $stamp_id,
|
||||
),
|
||||
\home_url( '/' )
|
||||
);
|
||||
|
||||
$authorization = new Feature_Authorization();
|
||||
$authorization->set_id( $stamp_url );
|
||||
$authorization->set_attributed_to( $actor->get_id() );
|
||||
$authorization->set_interacting_object( $instrument );
|
||||
$authorization->set_interaction_target( $actor->get_id() );
|
||||
|
||||
$this->activitypub_object = $authorization;
|
||||
$this->activitypub_object_id = $authorization->get_id();
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -41,7 +41,7 @@ class Relay {
|
||||
// Only relay if: successfully handled, Blog actor is recipient, activity is public, and in single-user mode.
|
||||
if (
|
||||
! $success ||
|
||||
! in_array( Actors::BLOG_USER_ID, (array) $user_ids, true ) ||
|
||||
! \in_array( Actors::BLOG_USER_ID, (array) $user_ids, true ) ||
|
||||
! is_activity_public( $activity ) ||
|
||||
! is_single_user()
|
||||
) {
|
||||
@ -53,7 +53,7 @@ class Relay {
|
||||
$announce->set_type( 'Announce' );
|
||||
$announce->set_actor( Actors::BLOG_USER_ID );
|
||||
$announce->set_object( $activity );
|
||||
$announce->set_published( gmdate( ACTIVITYPUB_DATE_TIME_RFC3339 ) );
|
||||
$announce->set_published( \gmdate( ACTIVITYPUB_DATE_TIME_RFC3339 ) );
|
||||
|
||||
// Add to outbox for distribution. The outbox will generate the ID.
|
||||
Outbox::add( $announce, Actors::BLOG_USER_ID );
|
||||
|
||||
@ -71,6 +71,8 @@ class Router {
|
||||
'top'
|
||||
);
|
||||
|
||||
// Must precede the generic @username rule, which would otherwise match "application" as an actor username.
|
||||
\add_rewrite_rule( '^@application\/?$', 'index.php?rest_route=/' . ACTIVITYPUB_REST_NAMESPACE . '/application', 'top' );
|
||||
\add_rewrite_rule( '^@([\w\-\.]+)\/?$', 'index.php?actor=$matches[1]', 'top' );
|
||||
\add_rewrite_endpoint( 'activitypub', EP_AUTHORS | EP_PERMALINK | EP_PAGES );
|
||||
}
|
||||
@ -107,8 +109,31 @@ class Router {
|
||||
}
|
||||
|
||||
$activitypub_object = Query::get_instance()->get_activitypub_object();
|
||||
$queried_object = Query::get_instance()->get_queried_object();
|
||||
|
||||
if ( Tombstone::exists_local( Query::get_instance()->get_request_url() ) ) {
|
||||
/*
|
||||
* Serve the Tombstone for a deleted object — but not while an authorized
|
||||
* user is previewing it. During an editor `?preview=true` request,
|
||||
* `is_post_publicly_queryable()` treats a draft or pending post as
|
||||
* queryable only for a user who can edit it, so the author can still use
|
||||
* the Fediverse Preview on a post they just soft-deleted (which is
|
||||
* otherwise already in the tombstone registry).
|
||||
*
|
||||
* The bypass is scoped to that preview request on purpose. A normal
|
||||
* ActivityPub fetch (no `preview`) of a tombstoned URL always gets the
|
||||
* Tombstone — even if the URL now resolves to a fresh public post because
|
||||
* its slug was reused — since remote servers were told that id is gone.
|
||||
* The legitimate restore path clears the registry entry itself, via
|
||||
* `Create::maybe_unbury()` when the re-publish Create is queued.
|
||||
*/
|
||||
$is_authorized_preview = \get_query_var( 'preview' )
|
||||
&& $queried_object instanceof \WP_Post
|
||||
&& is_post_publicly_queryable( $queried_object );
|
||||
|
||||
if (
|
||||
Tombstone::exists_local( Query::get_instance()->get_request_url() )
|
||||
&& ! $is_authorized_preview
|
||||
) {
|
||||
// Set 410 Gone for permanently deleted posts, 200 OK for soft-deleted.
|
||||
if ( ! $activitypub_object ) {
|
||||
\status_header( 410 );
|
||||
@ -117,18 +142,36 @@ class Router {
|
||||
return ACTIVITYPUB_PLUGIN_DIR . 'templates/tombstone-json.php';
|
||||
}
|
||||
|
||||
/*
|
||||
* Refuse to expose the content-negotiated representation of a post
|
||||
* that is no longer publicly queryable (non-public status, AP
|
||||
* visibility flipped, post-type support removed, etc.). The
|
||||
* lifecycle gate in `is_post_disabled()` intentionally lets such
|
||||
* posts through the federation pipeline so a Delete can fire, but
|
||||
* that escape hatch must not leak into front-end rendering during
|
||||
* the window between status change and Delete delivery.
|
||||
*/
|
||||
if (
|
||||
$activitypub_object &&
|
||||
$queried_object instanceof \WP_Post &&
|
||||
'ap_outbox' !== $queried_object->post_type &&
|
||||
! is_post_publicly_queryable( $queried_object )
|
||||
) {
|
||||
return $template;
|
||||
}
|
||||
|
||||
$activitypub_template = false;
|
||||
|
||||
if ( $activitypub_object ) {
|
||||
if ( \get_query_var( 'preview' ) ) {
|
||||
\define( 'ACTIVITYPUB_PREVIEW', true );
|
||||
\defined( 'ACTIVITYPUB_PREVIEW' ) || \define( 'ACTIVITYPUB_PREVIEW', true );
|
||||
|
||||
/**
|
||||
* Filter the template used for the ActivityPub preview.
|
||||
*
|
||||
* @param string $activitypub_template Absolute path to the template file.
|
||||
*/
|
||||
$activitypub_template = apply_filters( 'activitypub_preview_template', ACTIVITYPUB_PLUGIN_DIR . '/templates/post-preview.php' );
|
||||
$activitypub_template = \apply_filters( 'activitypub_preview_template', ACTIVITYPUB_PLUGIN_DIR . '/templates/post-preview.php' );
|
||||
} else {
|
||||
$activitypub_template = ACTIVITYPUB_PLUGIN_DIR . 'templates/activitypub-json.php';
|
||||
}
|
||||
@ -191,7 +234,7 @@ class Router {
|
||||
}
|
||||
|
||||
if ( ! \headers_sent() ) {
|
||||
\header( 'Link: <' . esc_url( $id ) . '>; title="ActivityPub (JSON)"; rel="alternate"; type="application/activity+json"', false );
|
||||
\header( 'Link: <' . \esc_url( $id ) . '>; title="ActivityPub (JSON)"; rel="alternate"; type="application/activity+json"', false );
|
||||
|
||||
if ( \get_option( 'activitypub_vary_header', '1' ) ) {
|
||||
// Send Vary header for Accept header.
|
||||
@ -202,7 +245,7 @@ class Router {
|
||||
\add_action(
|
||||
'wp_head',
|
||||
static function () use ( $id ) {
|
||||
echo PHP_EOL . '<link rel="alternate" title="ActivityPub (JSON)" type="application/activity+json" href="' . esc_url( $id ) . '" />' . PHP_EOL;
|
||||
echo PHP_EOL . '<link rel="alternate" title="ActivityPub (JSON)" type="application/activity+json" href="' . \esc_url( $id ) . '" />' . PHP_EOL;
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -216,7 +259,7 @@ class Router {
|
||||
* @return string $redirect_url The possibly-unslashed redirect URL.
|
||||
*/
|
||||
public static function no_trailing_redirect( $redirect_url, $requested_url ) {
|
||||
if ( get_query_var( 'actor' ) ) {
|
||||
if ( \get_query_var( 'actor' ) ) {
|
||||
return $requested_url;
|
||||
}
|
||||
|
||||
@ -246,7 +289,7 @@ class Router {
|
||||
unset( $query_params['activitypub'] );
|
||||
unset( $query_params['stamp'] );
|
||||
|
||||
if ( 1 !== count( $query_params ) ) {
|
||||
if ( 1 !== \count( $query_params ) ) {
|
||||
return $redirect_url;
|
||||
}
|
||||
|
||||
@ -286,12 +329,20 @@ class Router {
|
||||
return;
|
||||
}
|
||||
|
||||
\wp_safe_redirect( get_comment_link( $comment ) );
|
||||
\wp_safe_redirect( \get_comment_link( $comment ) );
|
||||
exit;
|
||||
}
|
||||
|
||||
$actor = \get_query_var( 'actor', null );
|
||||
if ( $actor ) {
|
||||
/*
|
||||
* Skip the actor branch when this looks like an actor-scoped FEP-7aa9
|
||||
* stamp URL: numeric `actor` paired with a `stamp`. Those resolve to a
|
||||
* FeatureAuthorization via Activitypub\Query, not via the username
|
||||
* lookup which would 404 the numeric ID. Non-numeric actors fall
|
||||
* through to the regular Mastodon-style profile lookup.
|
||||
*/
|
||||
$actor = \get_query_var( 'actor', null );
|
||||
$is_stamp_url = $actor && \get_query_var( 'stamp' ) && \ctype_digit( (string) $actor );
|
||||
if ( $actor && ! $is_stamp_url ) {
|
||||
$actor = Actors::get_by_username( $actor );
|
||||
if ( ! $actor || \is_wp_error( $actor ) ) {
|
||||
$wp_query->set_404();
|
||||
@ -325,7 +376,7 @@ class Router {
|
||||
*/
|
||||
$supported_taxonomies = \apply_filters( 'activitypub_supported_taxonomies', array( 'category', 'post_tag' ) );
|
||||
|
||||
if ( ! in_array( $term->taxonomy, $supported_taxonomies, true ) ) {
|
||||
if ( ! \in_array( $term->taxonomy, $supported_taxonomies, true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user