updated plugin ActivityPub
version 1.3.0
This commit is contained in:
parent
96c0ee892f
commit
ed9b10d2ea
@ -3,7 +3,7 @@
|
||||
* Plugin Name: ActivityPub
|
||||
* Plugin URI: https://github.com/pfefferle/wordpress-activitypub/
|
||||
* Description: The ActivityPub protocol is a decentralized social networking protocol based upon the ActivityStreams 2.0 data format.
|
||||
* Version: 1.0.7
|
||||
* Version: 1.3.0
|
||||
* Author: Matthias Pfefferle & Automattic
|
||||
* Author URI: https://automattic.com/
|
||||
* License: MIT
|
||||
@ -66,10 +66,9 @@ function plugin_init() {
|
||||
\add_action( 'init', array( __NAMESPACE__ . '\Migration', 'init' ) );
|
||||
\add_action( 'init', array( __NAMESPACE__ . '\Activitypub', 'init' ) );
|
||||
\add_action( 'init', array( __NAMESPACE__ . '\Activity_Dispatcher', 'init' ) );
|
||||
\add_action( 'init', array( __NAMESPACE__ . '\Collection\Followers', 'init' ) );
|
||||
\add_action( 'init', array( __NAMESPACE__ . '\Handler', 'init' ) );
|
||||
\add_action( 'init', array( __NAMESPACE__ . '\Admin', 'init' ) );
|
||||
\add_action( 'init', array( __NAMESPACE__ . '\Hashtag', 'init' ) );
|
||||
\add_action( 'init', array( __NAMESPACE__ . '\Shortcodes', 'init' ) );
|
||||
\add_action( 'init', array( __NAMESPACE__ . '\Mention', 'init' ) );
|
||||
\add_action( 'init', array( __NAMESPACE__ . '\Health_Check', 'init' ) );
|
||||
\add_action( 'init', array( __NAMESPACE__ . '\Scheduler', 'init' ) );
|
||||
|
@ -1 +1 @@
|
||||
<?php return array('dependencies' => array('wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-data', 'wp-element', 'wp-i18n', 'wp-primitives'), 'version' => '2a185b1c488886051601');
|
||||
<?php return array('dependencies' => array('wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-compose', 'wp-data', 'wp-element', 'wp-i18n', 'wp-primitives'), 'version' => '6aeec6336fd28aa836a7');
|
||||
|
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
.activitypub-follow-me-block-wrapper{width:100%}.activitypub-follow-me-block-wrapper.has-background .activitypub-profile,.activitypub-follow-me-block-wrapper.has-border-color .activitypub-profile{padding-left:1rem;padding-right:1rem}.activitypub-follow-me-block-wrapper .activitypub-profile{align-items:center;display:flex;padding:1rem 0}.activitypub-follow-me-block-wrapper .activitypub-profile .activitypub-profile__avatar{border-radius:50%;height:75px;margin-right:1rem;width:75px}.activitypub-follow-me-block-wrapper .activitypub-profile .activitypub-profile__content{flex:1;min-width:0}.activitypub-follow-me-block-wrapper .activitypub-profile .activitypub-profile__handle,.activitypub-follow-me-block-wrapper .activitypub-profile .activitypub-profile__name{line-height:1.2;margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.activitypub-follow-me-block-wrapper .activitypub-profile .activitypub-profile__name{font-size:1.25em}.activitypub-follow-me-block-wrapper .activitypub-profile .activitypub-profile__follow{align-self:center;background-color:var(--wp--preset--color--black);color:var(--wp--preset--color--white);margin-left:1rem}.activitypub-profile__confirm.components-modal__frame{background-color:#f7f7f7;color:#333}.activitypub-profile__confirm.components-modal__frame .components-modal__header-heading,.activitypub-profile__confirm.components-modal__frame h4{color:#333;letter-spacing:inherit;word-spacing:inherit}.activitypub-follow-me__dialog{max-width:30em}.activitypub-follow-me__dialog h4{line-height:1;margin:0}.activitypub-follow-me__dialog .apmfd__section{margin-bottom:2em}.activitypub-follow-me__dialog .apfmd-description{font-size:var(--wp--preset--font-size--normal,.75rem);margin:.33em 0 1em}.activitypub-follow-me__dialog .apfmd__button-group{display:flex;justify-content:flex-end}.activitypub-follow-me__dialog .apfmd__button-group svg{height:21px;margin-right:.5em;width:21px}.activitypub-follow-me__dialog .apfmd__button-group input{flex:1;padding-left:1em;padding-right:1em}
|
||||
.activitypub-follow-me-block-wrapper{width:100%}.activitypub-follow-me-block-wrapper.has-background .activitypub-profile,.activitypub-follow-me-block-wrapper.has-border-color .activitypub-profile{padding-left:1rem;padding-right:1rem}.activitypub-follow-me-block-wrapper .activitypub-profile{align-items:center;display:flex;padding:1rem 0}.activitypub-follow-me-block-wrapper .activitypub-profile .activitypub-profile__avatar{border-radius:50%;height:75px;margin-right:1rem;width:75px}.activitypub-follow-me-block-wrapper .activitypub-profile .activitypub-profile__content{flex:1;min-width:0}.activitypub-follow-me-block-wrapper .activitypub-profile .activitypub-profile__handle,.activitypub-follow-me-block-wrapper .activitypub-profile .activitypub-profile__name{line-height:1.2;margin:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.activitypub-follow-me-block-wrapper .activitypub-profile .activitypub-profile__name{font-size:1.25em}.activitypub-follow-me-block-wrapper .activitypub-profile .activitypub-profile__follow{align-self:center;background-color:var(--wp--preset--color--black);color:var(--wp--preset--color--white);margin-left:1rem}.activitypub-profile__confirm.components-modal__frame{background-color:#f7f7f7;color:#333}.activitypub-profile__confirm.components-modal__frame .components-modal__header-heading,.activitypub-profile__confirm.components-modal__frame h4{color:#333;letter-spacing:inherit;word-spacing:inherit}.activitypub-follow-me__dialog{max-width:30em}.activitypub-follow-me__dialog h4{line-height:1;margin:0}.activitypub-follow-me__dialog .apmfd__section{margin-bottom:2em}.activitypub-follow-me__dialog .apfmd-description{font-size:var(--wp--preset--font-size--normal,.75rem);margin:.33em 0 1em}.activitypub-follow-me__dialog .apfmd__button-group{align-items:flex-end;display:flex;justify-content:flex-end}.activitypub-follow-me__dialog .apfmd__button-group svg{height:21px;margin-right:.5em;width:21px}.activitypub-follow-me__dialog .apfmd__button-group input{background-color:var(--wp--preset--color--white);border:1px solid var(--wp--preset--color--black);color:var(--wp--preset--color--black);flex:1;padding:6px 12px}
|
||||
|
@ -1 +1 @@
|
||||
<?php return array('dependencies' => array('wp-api-fetch', 'wp-components', 'wp-compose', 'wp-dom-ready', 'wp-element', 'wp-i18n', 'wp-primitives'), 'version' => '17a158ceced1355cc8ea');
|
||||
<?php return array('dependencies' => array('wp-api-fetch', 'wp-components', 'wp-compose', 'wp-dom-ready', 'wp-element', 'wp-i18n', 'wp-primitives'), 'version' => '5b48281e37700a970a66');
|
||||
|
File diff suppressed because one or more lines are too long
@ -1 +1 @@
|
||||
<?php return array('dependencies' => array('react', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-data', 'wp-element', 'wp-i18n', 'wp-primitives', 'wp-url'), 'version' => '1cbd9cbfcbd7fc813429');
|
||||
<?php return array('dependencies' => array('react', 'wp-api-fetch', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-data', 'wp-element', 'wp-i18n', 'wp-primitives', 'wp-url'), 'version' => '59d9702e06860a6d13e4');
|
||||
|
@ -1,3 +1,3 @@
|
||||
(()=>{var e={184:(e,t)=>{var a;!function(){"use strict";var n={}.hasOwnProperty;function l(){for(var e=[],t=0;t<arguments.length;t++){var a=arguments[t];if(a){var r=typeof a;if("string"===r||"number"===r)e.push(a);else if(Array.isArray(a)){if(a.length){var o=l.apply(null,a);o&&e.push(o)}}else if("object"===r){if(a.toString!==Object.prototype.toString&&!a.toString.toString().includes("[native code]")){e.push(a.toString());continue}for(var i in a)n.call(a,i)&&a[i]&&e.push(i)}}}return e.join(" ")}e.exports?(l.default=l,e.exports=l):void 0===(a=function(){return l}.apply(t,[]))||(e.exports=a)}()}},t={};function a(n){var l=t[n];if(void 0!==l)return l.exports;var r=t[n]={exports:{}};return e[n](r,r.exports,a),r.exports}a.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return a.d(t,{a:t}),t},a.d=(e,t)=>{for(var n in t)a.o(t,n)&&!a.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},a.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),(()=>{"use strict";const e=window.wp.blocks,t=window.wp.element,n=window.wp.primitives,l=(0,t.createElement)(n.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24"},(0,t.createElement)(n.Path,{d:"M15.5 9.5a1 1 0 100-2 1 1 0 000 2zm0 1.5a2.5 2.5 0 100-5 2.5 2.5 0 000 5zm-2.25 6v-2a2.75 2.75 0 00-2.75-2.75h-4A2.75 2.75 0 003.75 15v2h1.5v-2c0-.69.56-1.25 1.25-1.25h4c.69 0 1.25.56 1.25 1.25v2h1.5zm7-2v2h-1.5v-2c0-.69-.56-1.25-1.25-1.25H15v-1.5h2.5A2.75 2.75 0 0120.25 15zM9.5 8.5a1 1 0 11-2 0 1 1 0 012 0zm1.5 0a2.5 2.5 0 11-5 0 2.5 2.5 0 015 0z",fillRule:"evenodd"}));function r(){return r=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var a=arguments[t];for(var n in a)Object.prototype.hasOwnProperty.call(a,n)&&(e[n]=a[n])}return e},r.apply(this,arguments)}const o=window.wp.components,i=window.wp.blockEditor,c=window.wp.i18n,s=window.React,p=window.wp.apiFetch;var u=a.n(p);const v=window.wp.url;var m=a(184),b=a.n(m);function w(e){let{active:a,children:n,page:l,pageClick:r,className:o}=e;const i=b()("wp-block activitypub-pager",o,{current:a});return(0,t.createElement)("a",{className:i,onClick:e=>{e.preventDefault(),!a&&r(l)}},n)}const d={outlined:"outlined",minimal:"minimal"};function f(e){let{compact:a,nextLabel:n,page:l,pageClick:r,perPage:o,prevLabel:i,total:c,variant:s=d.outlined}=e;const p=((e,t)=>{let a=[1,e-2,e-1,e,e+1,e+2,t];a.sort(((e,t)=>e-t)),a=a.filter(((e,a,n)=>e>=1&&e<=t&&n.lastIndexOf(e)===a));for(let e=a.length-2;e>=0;e--)a[e]===a[e+1]&&a.splice(e+1,1);return a})(l,Math.ceil(c/o)),u=b()("alignwide wp-block-query-pagination is-content-justification-space-between is-layout-flex wp-block-query-pagination-is-layout-flex",`is-${s}`,{"is-compact":a});return(0,t.createElement)("nav",{className:u},i&&(0,t.createElement)(w,{key:"prev",page:l-1,pageClick:r,active:1===l,"aria-label":i,className:"wp-block-query-pagination-previous block-editor-block-list__block"},i),!a&&(0,t.createElement)("div",{className:"block-editor-block-list__block wp-block wp-block-query-pagination-numbers"},p.map((e=>(0,t.createElement)(w,{key:e,page:e,pageClick:r,active:e===l,className:"page-numbers"},e)))),n&&(0,t.createElement)(w,{key:"next",page:l+1,pageClick:r,active:l===Math.ceil(c/o),"aria-label":n,className:"wp-block-query-pagination-next block-editor-block-list__block"},n))}const{namespace:g}=window._activityPubOptions;function y(e){let{selectedUser:a,per_page:n,order:l,title:o,page:i,setPage:p,className:m="",followLinks:b=!0,followerData:w=!1}=e;const d="site"===a?0:a,[y,k]=(0,s.useState)([]),[E,_]=(0,s.useState)(0),[x,C]=(0,s.useState)(0),[S,O]=function(){const[e,t]=(0,s.useState)(1);return[e,t]}(),N=i||S,P=p||O,L=(0,t.createInterpolateElement)(/* translators: arrow for previous followers link */
|
||||
(0,c.__)("<span>←</span> Less","activitypub"),{span:(0,t.createElement)("span",{class:"wp-block-query-pagination-previous-arrow is-arrow-arrow","aria-hidden":"true"})}),j=(0,t.createInterpolateElement)(/* translators: arrow for next followers link */
|
||||
(0,c.__)("More <span>→</span>","activitypub"),{span:(0,t.createElement)("span",{class:"wp-block-query-pagination-next-arrow is-arrow-arrow","aria-hidden":"true"})}),M=(e,t)=>{k(e),C(t),_(Math.ceil(t/n))};return(0,s.useEffect)((()=>{if(w&&1===N)return M(w.followers,w.total);const e=function(e,t,a,n){const l=`/${g}/users/${e}/followers`,r={per_page:t,order:a,page:n,context:"full"};return(0,v.addQueryArgs)(l,r)}(d,n,l,N);u()({path:e}).then((e=>M(e.orderedItems,e.totalItems))).catch((()=>{}))}),[d,n,l,N,w]),(0,t.createElement)("div",{className:"activitypub-follower-block "+m},(0,t.createElement)("h3",null,o),(0,t.createElement)("ul",null,y&&y.map((e=>(0,t.createElement)("li",{key:e.url},(0,t.createElement)(h,r({},e,{followLinks:b})))))),E>1&&(0,t.createElement)(f,{page:N,perPage:n,total:x,pageClick:P,nextLabel:j,prevLabel:L,compact:"is-style-compact"===m}))}function h(e){let{name:a,icon:n,url:l,preferredUsername:i,followLinks:c=!0}=e;const s=`@${i}`,p={};return c||(p.onClick=e=>e.preventDefault()),(0,t.createElement)(o.ExternalLink,r({className:"activitypub-link",href:l,title:s},p),(0,t.createElement)("img",{width:"40",height:"40",src:n.url,class:"avatar activitypub-avatar"}),(0,t.createElement)("span",{class:"activitypub-actor"},(0,t.createElement)("strong",{className:"activitypub-name"},a),(0,t.createElement)("span",{class:"sep"},"/"),(0,t.createElement)("span",{class:"activitypub-handle"},s)))}const k=window.wp.data,E=window._activityPubOptions?.enabled;(0,e.registerBlockType)("activitypub/followers",{edit:function(e){let{attributes:a,setAttributes:n}=e;const{order:l,per_page:s,selectedUser:p,title:u}=a,v=(0,i.useBlockProps)(),[m,b]=(0,t.useState)(1),w=[{label:(0,c.__)("New to old","activitypub"),value:"desc"},{label:(0,c.__)("Old to new","activitypub"),value:"asc"}],d=function(){const e=E?.users?(0,k.useSelect)((e=>e("core").getUsers({who:"authors"}))):[];return(0,t.useMemo)((()=>{if(!e)return[];const t=E?.site?[{label:(0,c.__)("Whole Site","activitypub"),value:"site"}]:[];return e.reduce(((e,t)=>(e.push({label:t.name,value:`${t.id}`}),e)),t)}),[e])}(),f=e=>t=>{b(1),n({[e]:t})};return(0,t.useEffect)((()=>{d.length&&(d.find((e=>{let{value:t}=e;return t===p}))||n({selectedUser:d[0].value}))}),[p,d]),(0,t.createElement)("div",v,(0,t.createElement)(i.InspectorControls,{key:"setting"},(0,t.createElement)(o.PanelBody,{title:(0,c.__)("Followers Options","activitypub")},(0,t.createElement)(o.TextControl,{label:(0,c.__)("Title","activitypub"),help:(0,c.__)("Title to display above the list of followers. Blank for none.","activitypub"),value:u,onChange:e=>n({title:e})}),d.length>1&&(0,t.createElement)(o.SelectControl,{label:(0,c.__)("Select User","activitypub"),value:p,options:d,onChange:f("selectedUser")}),(0,t.createElement)(o.SelectControl,{label:(0,c.__)("Sort","activitypub"),value:l,options:w,onChange:f("order")}),(0,t.createElement)(o.RangeControl,{label:(0,c.__)("Number of Followers","activitypub"),value:s,onChange:f("per_page"),min:1,max:10}))),(0,t.createElement)(y,r({},a,{page:m,setPage:b,followLinks:!1})))},save:()=>null,icon:l})})()})();
|
||||
(0,c.__)("More <span>→</span>","activitypub"),{span:(0,t.createElement)("span",{class:"wp-block-query-pagination-next-arrow is-arrow-arrow","aria-hidden":"true"})}),M=(e,t)=>{k(e),C(t),_(Math.ceil(t/n))};return(0,s.useEffect)((()=>{if(w&&1===N)return M(w.followers,w.total);const e=function(e,t,a,n){const l=`/${g}/users/${e}/followers`,r={per_page:t,order:a,page:n,context:"full"};return(0,v.addQueryArgs)(l,r)}(d,n,l,N);u()({path:e}).then((e=>M(e.orderedItems,e.totalItems))).catch((()=>{}))}),[d,n,l,N,w]),(0,t.createElement)("div",{className:"activitypub-follower-block "+m},(0,t.createElement)("h3",null,o),(0,t.createElement)("ul",null,y&&y.map((e=>(0,t.createElement)("li",{key:e.url},(0,t.createElement)(h,r({},e,{followLinks:b})))))),E>1&&(0,t.createElement)(f,{page:N,perPage:n,total:x,pageClick:P,nextLabel:j,prevLabel:L,compact:"is-style-compact"===m}))}function h(e){let{name:a,icon:n,url:l,preferredUsername:i,followLinks:c=!0}=e;const s=`@${i}`,p={};return c||(p.onClick=e=>e.preventDefault()),(0,t.createElement)(o.ExternalLink,r({className:"activitypub-link",href:l,title:s},p),(0,t.createElement)("img",{width:"40",height:"40",src:n.url,class:"avatar activitypub-avatar",alt:a}),(0,t.createElement)("span",{class:"activitypub-actor"},(0,t.createElement)("strong",{className:"activitypub-name"},a),(0,t.createElement)("span",{class:"sep"},"/"),(0,t.createElement)("span",{class:"activitypub-handle"},s)))}const k=window.wp.data,E=window._activityPubOptions?.enabled;(0,e.registerBlockType)("activitypub/followers",{edit:function(e){let{attributes:a,setAttributes:n}=e;const{order:l,per_page:s,selectedUser:p,title:u}=a,v=(0,i.useBlockProps)(),[m,b]=(0,t.useState)(1),w=[{label:(0,c.__)("New to old","activitypub"),value:"desc"},{label:(0,c.__)("Old to new","activitypub"),value:"asc"}],d=function(){const e=E?.users?(0,k.useSelect)((e=>e("core").getUsers({who:"authors"}))):[];return(0,t.useMemo)((()=>{if(!e)return[];const t=E?.site?[{label:(0,c.__)("Whole Site","activitypub"),value:"site"}]:[];return e.reduce(((e,t)=>(e.push({label:t.name,value:`${t.id}`}),e)),t)}),[e])}(),f=e=>t=>{b(1),n({[e]:t})};return(0,t.useEffect)((()=>{d.length&&(d.find((e=>{let{value:t}=e;return t===p}))||n({selectedUser:d[0].value}))}),[p,d]),(0,t.createElement)("div",v,(0,t.createElement)(i.InspectorControls,{key:"setting"},(0,t.createElement)(o.PanelBody,{title:(0,c.__)("Followers Options","activitypub")},(0,t.createElement)(o.TextControl,{label:(0,c.__)("Title","activitypub"),help:(0,c.__)("Title to display above the list of followers. Blank for none.","activitypub"),value:u,onChange:e=>n({title:e})}),d.length>1&&(0,t.createElement)(o.SelectControl,{label:(0,c.__)("Select User","activitypub"),value:p,options:d,onChange:f("selectedUser")}),(0,t.createElement)(o.SelectControl,{label:(0,c.__)("Sort","activitypub"),value:l,options:w,onChange:f("order")}),(0,t.createElement)(o.RangeControl,{label:(0,c.__)("Number of Followers","activitypub"),value:s,onChange:f("per_page"),min:1,max:10}))),(0,t.createElement)(y,r({},a,{page:m,setPage:b,followLinks:!1})))},save:()=>null,icon:l})})()})();
|
@ -1 +1 @@
|
||||
<?php return array('dependencies' => array('react', 'wp-api-fetch', 'wp-components', 'wp-dom-ready', 'wp-element', 'wp-i18n', 'wp-url'), 'version' => 'f0e21057f7ec615290d6');
|
||||
<?php return array('dependencies' => array('react', 'wp-api-fetch', 'wp-components', 'wp-dom-ready', 'wp-element', 'wp-i18n', 'wp-url'), 'version' => '04e51e7562fe28b0b2c3');
|
||||
|
@ -1,3 +1,3 @@
|
||||
(()=>{var e,t={189:(e,t,a)=>{"use strict";const r=window.wp.element;function n(){return n=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var a=arguments[t];for(var r in a)Object.prototype.hasOwnProperty.call(a,r)&&(e[r]=a[r])}return e},n.apply(this,arguments)}const l=window.React,o=window.wp.apiFetch;var i=a.n(o);const c=window.wp.url,s=window.wp.i18n;var p=a(184),u=a.n(p);function m(e){let{active:t,children:a,page:n,pageClick:l,className:o}=e;const i=u()("wp-block activitypub-pager",o,{current:t});return(0,r.createElement)("a",{className:i,onClick:e=>{e.preventDefault(),!t&&l(n)}},a)}const v={outlined:"outlined",minimal:"minimal"};function f(e){let{compact:t,nextLabel:a,page:n,pageClick:l,perPage:o,prevLabel:i,total:c,variant:s=v.outlined}=e;const p=((e,t)=>{let a=[1,e-2,e-1,e,e+1,e+2,t];a.sort(((e,t)=>e-t)),a=a.filter(((e,a,r)=>e>=1&&e<=t&&r.lastIndexOf(e)===a));for(let e=a.length-2;e>=0;e--)a[e]===a[e+1]&&a.splice(e+1,1);return a})(n,Math.ceil(c/o)),f=u()("alignwide wp-block-query-pagination is-content-justification-space-between is-layout-flex wp-block-query-pagination-is-layout-flex",`is-${s}`,{"is-compact":t});return(0,r.createElement)("nav",{className:f},i&&(0,r.createElement)(m,{key:"prev",page:n-1,pageClick:l,active:1===n,"aria-label":i,className:"wp-block-query-pagination-previous block-editor-block-list__block"},i),!t&&(0,r.createElement)("div",{className:"block-editor-block-list__block wp-block wp-block-query-pagination-numbers"},p.map((e=>(0,r.createElement)(m,{key:e,page:e,pageClick:l,active:e===n,className:"page-numbers"},e)))),a&&(0,r.createElement)(m,{key:"next",page:n+1,pageClick:l,active:n===Math.ceil(c/o),"aria-label":a,className:"wp-block-query-pagination-next block-editor-block-list__block"},a))}const b=window.wp.components,{namespace:d}=window._activityPubOptions;function w(e){let{selectedUser:t,per_page:a,order:o,title:p,page:u,setPage:m,className:v="",followLinks:b=!0,followerData:w=!1}=e;const y="site"===t?0:t,[k,h]=(0,l.useState)([]),[E,O]=(0,l.useState)(0),[x,_]=(0,l.useState)(0),[N,j]=function(){const[e,t]=(0,l.useState)(1);return[e,t]}(),S=u||N,C=m||j,L=(0,r.createInterpolateElement)(/* translators: arrow for previous followers link */
|
||||
(0,s.__)("<span>←</span> Less","activitypub"),{span:(0,r.createElement)("span",{class:"wp-block-query-pagination-previous-arrow is-arrow-arrow","aria-hidden":"true"})}),q=(0,r.createInterpolateElement)(/* translators: arrow for next followers link */
|
||||
(0,s.__)("More <span>→</span>","activitypub"),{span:(0,r.createElement)("span",{class:"wp-block-query-pagination-next-arrow is-arrow-arrow","aria-hidden":"true"})}),P=(e,t)=>{h(e),_(t),O(Math.ceil(t/a))};return(0,l.useEffect)((()=>{if(w&&1===S)return P(w.followers,w.total);const e=function(e,t,a,r){const n=`/${d}/users/${e}/followers`,l={per_page:t,order:a,page:r,context:"full"};return(0,c.addQueryArgs)(n,l)}(y,a,o,S);i()({path:e}).then((e=>P(e.orderedItems,e.totalItems))).catch((()=>{}))}),[y,a,o,S,w]),(0,r.createElement)("div",{className:"activitypub-follower-block "+v},(0,r.createElement)("h3",null,p),(0,r.createElement)("ul",null,k&&k.map((e=>(0,r.createElement)("li",{key:e.url},(0,r.createElement)(g,n({},e,{followLinks:b})))))),E>1&&(0,r.createElement)(f,{page:S,perPage:a,total:x,pageClick:C,nextLabel:q,prevLabel:L,compact:"is-style-compact"===v}))}function g(e){let{name:t,icon:a,url:l,preferredUsername:o,followLinks:i=!0}=e;const c=`@${o}`,s={};return i||(s.onClick=e=>e.preventDefault()),(0,r.createElement)(b.ExternalLink,n({className:"activitypub-link",href:l,title:c},s),(0,r.createElement)("img",{width:"40",height:"40",src:a.url,class:"avatar activitypub-avatar"}),(0,r.createElement)("span",{class:"activitypub-actor"},(0,r.createElement)("strong",{className:"activitypub-name"},t),(0,r.createElement)("span",{class:"sep"},"/"),(0,r.createElement)("span",{class:"activitypub-handle"},c)))}const y=window.wp.domReady;a.n(y)()((()=>{[].forEach.call(document.querySelectorAll(".activitypub-follower-block"),(e=>{const t=JSON.parse(e.dataset.attrs);(0,r.render)((0,r.createElement)(w,t),e)}))}))},184:(e,t)=>{var a;!function(){"use strict";var r={}.hasOwnProperty;function n(){for(var e=[],t=0;t<arguments.length;t++){var a=arguments[t];if(a){var l=typeof a;if("string"===l||"number"===l)e.push(a);else if(Array.isArray(a)){if(a.length){var o=n.apply(null,a);o&&e.push(o)}}else if("object"===l){if(a.toString!==Object.prototype.toString&&!a.toString.toString().includes("[native code]")){e.push(a.toString());continue}for(var i in a)r.call(a,i)&&a[i]&&e.push(i)}}}return e.join(" ")}e.exports?(n.default=n,e.exports=n):void 0===(a=function(){return n}.apply(t,[]))||(e.exports=a)}()}},a={};function r(e){var n=a[e];if(void 0!==n)return n.exports;var l=a[e]={exports:{}};return t[e](l,l.exports,r),l.exports}r.m=t,e=[],r.O=(t,a,n,l)=>{if(!a){var o=1/0;for(p=0;p<e.length;p++){for(var[a,n,l]=e[p],i=!0,c=0;c<a.length;c++)(!1&l||o>=l)&&Object.keys(r.O).every((e=>r.O[e](a[c])))?a.splice(c--,1):(i=!1,l<o&&(o=l));if(i){e.splice(p--,1);var s=n();void 0!==s&&(t=s)}}return t}l=l||0;for(var p=e.length;p>0&&e[p-1][2]>l;p--)e[p]=e[p-1];e[p]=[a,n,l]},r.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return r.d(t,{a:t}),t},r.d=(e,t)=>{for(var a in t)r.o(t,a)&&!r.o(e,a)&&Object.defineProperty(e,a,{enumerable:!0,get:t[a]})},r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),(()=>{var e={638:0,962:0};r.O.j=t=>0===e[t];var t=(t,a)=>{var n,l,[o,i,c]=a,s=0;if(o.some((t=>0!==e[t]))){for(n in i)r.o(i,n)&&(r.m[n]=i[n]);if(c)var p=c(r)}for(t&&t(a);s<o.length;s++)l=o[s],r.o(e,l)&&e[l]&&e[l][0](),e[l]=0;return r.O(p)},a=globalThis.webpackChunkwordpress_activitypub=globalThis.webpackChunkwordpress_activitypub||[];a.forEach(t.bind(null,0)),a.push=t.bind(null,a.push.bind(a))})();var n=r.O(void 0,[962],(()=>r(189)));n=r.O(n)})();
|
||||
(0,s.__)("More <span>→</span>","activitypub"),{span:(0,r.createElement)("span",{class:"wp-block-query-pagination-next-arrow is-arrow-arrow","aria-hidden":"true"})}),P=(e,t)=>{h(e),_(t),O(Math.ceil(t/a))};return(0,l.useEffect)((()=>{if(w&&1===S)return P(w.followers,w.total);const e=function(e,t,a,r){const n=`/${d}/users/${e}/followers`,l={per_page:t,order:a,page:r,context:"full"};return(0,c.addQueryArgs)(n,l)}(y,a,o,S);i()({path:e}).then((e=>P(e.orderedItems,e.totalItems))).catch((()=>{}))}),[y,a,o,S,w]),(0,r.createElement)("div",{className:"activitypub-follower-block "+v},(0,r.createElement)("h3",null,p),(0,r.createElement)("ul",null,k&&k.map((e=>(0,r.createElement)("li",{key:e.url},(0,r.createElement)(g,n({},e,{followLinks:b})))))),E>1&&(0,r.createElement)(f,{page:S,perPage:a,total:x,pageClick:C,nextLabel:q,prevLabel:L,compact:"is-style-compact"===v}))}function g(e){let{name:t,icon:a,url:l,preferredUsername:o,followLinks:i=!0}=e;const c=`@${o}`,s={};return i||(s.onClick=e=>e.preventDefault()),(0,r.createElement)(b.ExternalLink,n({className:"activitypub-link",href:l,title:c},s),(0,r.createElement)("img",{width:"40",height:"40",src:a.url,class:"avatar activitypub-avatar",alt:t}),(0,r.createElement)("span",{class:"activitypub-actor"},(0,r.createElement)("strong",{className:"activitypub-name"},t),(0,r.createElement)("span",{class:"sep"},"/"),(0,r.createElement)("span",{class:"activitypub-handle"},c)))}const y=window.wp.domReady;a.n(y)()((()=>{[].forEach.call(document.querySelectorAll(".activitypub-follower-block"),(e=>{const t=JSON.parse(e.dataset.attrs);(0,r.render)((0,r.createElement)(w,t),e)}))}))},184:(e,t)=>{var a;!function(){"use strict";var r={}.hasOwnProperty;function n(){for(var e=[],t=0;t<arguments.length;t++){var a=arguments[t];if(a){var l=typeof a;if("string"===l||"number"===l)e.push(a);else if(Array.isArray(a)){if(a.length){var o=n.apply(null,a);o&&e.push(o)}}else if("object"===l){if(a.toString!==Object.prototype.toString&&!a.toString.toString().includes("[native code]")){e.push(a.toString());continue}for(var i in a)r.call(a,i)&&a[i]&&e.push(i)}}}return e.join(" ")}e.exports?(n.default=n,e.exports=n):void 0===(a=function(){return n}.apply(t,[]))||(e.exports=a)}()}},a={};function r(e){var n=a[e];if(void 0!==n)return n.exports;var l=a[e]={exports:{}};return t[e](l,l.exports,r),l.exports}r.m=t,e=[],r.O=(t,a,n,l)=>{if(!a){var o=1/0;for(p=0;p<e.length;p++){for(var[a,n,l]=e[p],i=!0,c=0;c<a.length;c++)(!1&l||o>=l)&&Object.keys(r.O).every((e=>r.O[e](a[c])))?a.splice(c--,1):(i=!1,l<o&&(o=l));if(i){e.splice(p--,1);var s=n();void 0!==s&&(t=s)}}return t}l=l||0;for(var p=e.length;p>0&&e[p-1][2]>l;p--)e[p]=e[p-1];e[p]=[a,n,l]},r.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return r.d(t,{a:t}),t},r.d=(e,t)=>{for(var a in t)r.o(t,a)&&!r.o(e,a)&&Object.defineProperty(e,a,{enumerable:!0,get:t[a]})},r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),(()=>{var e={638:0,962:0};r.O.j=t=>0===e[t];var t=(t,a)=>{var n,l,[o,i,c]=a,s=0;if(o.some((t=>0!==e[t]))){for(n in i)r.o(i,n)&&(r.m[n]=i[n]);if(c)var p=c(r)}for(t&&t(a);s<o.length;s++)l=o[s],r.o(e,l)&&e[l]&&e[l][0](),e[l]=0;return r.O(p)},a=globalThis.webpackChunkwordpress_activitypub=globalThis.webpackChunkwordpress_activitypub||[];a.forEach(t.bind(null,0)),a.push=t.bind(null,a.push.bind(a))})();var n=r.O(void 0,[962],(()=>r(189)));n=r.O(n)})();
|
@ -194,6 +194,12 @@ class Activity extends Base_Object {
|
||||
* @return void
|
||||
*/
|
||||
public function set_object( $object ) {
|
||||
// convert array to object
|
||||
if ( is_array( $object ) ) {
|
||||
$object = Base_Object::init_from_array( $object );
|
||||
}
|
||||
|
||||
// set object
|
||||
$this->set( 'object', $object );
|
||||
|
||||
if ( ! is_object( $object ) ) {
|
||||
|
@ -585,7 +585,7 @@ class Base_Object {
|
||||
|
||||
foreach ( $array as $key => $value ) {
|
||||
$key = camel_to_snake_case( $key );
|
||||
$object->set( $key, $value );
|
||||
call_user_func( array( $object, 'set_' . $key ), $value );
|
||||
}
|
||||
|
||||
return $object;
|
||||
@ -611,7 +611,7 @@ class Base_Object {
|
||||
foreach ( $array as $key => $value ) {
|
||||
if ( $value ) {
|
||||
$key = camel_to_snake_case( $key );
|
||||
$this->set( $key, $value );
|
||||
call_user_func( array( $this, 'set_' . $key ), $value );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,12 @@
|
||||
<?php
|
||||
namespace Activitypub;
|
||||
|
||||
use Exception;
|
||||
use Activitypub\Signature;
|
||||
use Activitypub\Collection\Users;
|
||||
use Activitypub\Collection\Followers;
|
||||
|
||||
use function Activitypub\sanitize_url;
|
||||
|
||||
/**
|
||||
* ActivityPub Class
|
||||
@ -34,6 +38,9 @@ class Activitypub {
|
||||
\add_action( 'after_setup_theme', array( self::class, 'theme_compat' ), 99 );
|
||||
|
||||
\add_action( 'in_plugin_update_message-' . ACTIVITYPUB_PLUGIN_BASENAME, array( self::class, 'plugin_update_message' ) );
|
||||
|
||||
// register several post_types
|
||||
self::register_post_types();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -54,7 +61,6 @@ class Activitypub {
|
||||
*/
|
||||
public static function deactivate() {
|
||||
self::flush_rewrite_rules();
|
||||
|
||||
Scheduler::deregister_schedules();
|
||||
}
|
||||
|
||||
@ -328,4 +334,80 @@ class Activitypub {
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the "Followers" Taxonomy
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function register_post_types() {
|
||||
register_post_type(
|
||||
Followers::POST_TYPE,
|
||||
array(
|
||||
'labels' => array(
|
||||
'name' => _x( 'Followers', 'post_type plural name', 'activitypub' ),
|
||||
'singular_name' => _x( 'Follower', 'post_type single name', 'activitypub' ),
|
||||
),
|
||||
'public' => false,
|
||||
'hierarchical' => false,
|
||||
'rewrite' => false,
|
||||
'query_var' => false,
|
||||
'delete_with_user' => false,
|
||||
'can_export' => true,
|
||||
'supports' => array(),
|
||||
)
|
||||
);
|
||||
|
||||
register_post_meta(
|
||||
Followers::POST_TYPE,
|
||||
'activitypub_inbox',
|
||||
array(
|
||||
'type' => 'string',
|
||||
'single' => true,
|
||||
'sanitize_callback' => 'sanitize_url',
|
||||
)
|
||||
);
|
||||
|
||||
register_post_meta(
|
||||
Followers::POST_TYPE,
|
||||
'activitypub_errors',
|
||||
array(
|
||||
'type' => 'string',
|
||||
'single' => false,
|
||||
'sanitize_callback' => function( $value ) {
|
||||
if ( ! is_string( $value ) ) {
|
||||
throw new Exception( 'Error message is no valid string' );
|
||||
}
|
||||
|
||||
return esc_sql( $value );
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
register_post_meta(
|
||||
Followers::POST_TYPE,
|
||||
'activitypub_user_id',
|
||||
array(
|
||||
'type' => 'string',
|
||||
'single' => false,
|
||||
'sanitize_callback' => function( $value ) {
|
||||
return esc_sql( $value );
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
register_post_meta(
|
||||
Followers::POST_TYPE,
|
||||
'activitypub_actor_json',
|
||||
array(
|
||||
'type' => 'string',
|
||||
'single' => true,
|
||||
'sanitize_callback' => function( $value ) {
|
||||
return sanitize_text_field( $value );
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
do_action( 'activitypub_after_register_post_type' );
|
||||
}
|
||||
}
|
||||
|
33
wp-content/plugins/activitypub/includes/class-handler.php
Normal file
33
wp-content/plugins/activitypub/includes/class-handler.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
namespace Activitypub;
|
||||
|
||||
use Activitypub\Handler\Create;
|
||||
use Activitypub\Handler\Delete;
|
||||
use Activitypub\Handler\Follow;
|
||||
use Activitypub\Handler\Undo;
|
||||
use Activitypub\Handler\Update;
|
||||
|
||||
/**
|
||||
* Handler class.
|
||||
*/
|
||||
class Handler {
|
||||
/**
|
||||
* Initialize the class, registering WordPress hooks
|
||||
*/
|
||||
public static function init() {
|
||||
self::register_handlers();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register handlers.
|
||||
*/
|
||||
public static function register_handlers() {
|
||||
Create::init();
|
||||
Delete::init();
|
||||
Follow::init();
|
||||
Undo::init();
|
||||
Update::init();
|
||||
|
||||
do_action( 'activitypub_register_handlers' );
|
||||
}
|
||||
}
|
@ -43,6 +43,10 @@ class Hashtag {
|
||||
* @return string the filtered post-content
|
||||
*/
|
||||
public static function the_content( $the_content ) {
|
||||
// small protection against execution timeouts: limit to 1 MB
|
||||
if ( mb_strlen( $the_content ) > MB_IN_BYTES ) {
|
||||
return $the_content;
|
||||
}
|
||||
$tag_stack = array();
|
||||
$protected_tags = array(
|
||||
'pre',
|
||||
|
@ -20,7 +20,7 @@ class Http {
|
||||
* @return array|WP_Error The POST Response or an WP_ERROR
|
||||
*/
|
||||
public static function post( $url, $body, $user_id ) {
|
||||
do_action( 'activitypub_pre_http_post', $url, $body, $user_id );
|
||||
\do_action( 'activitypub_pre_http_post', $url, $body, $user_id );
|
||||
|
||||
$date = \gmdate( 'D, d M Y H:i:s T' );
|
||||
$digest = Signature::generate_digest( $body );
|
||||
@ -70,7 +70,7 @@ class Http {
|
||||
* @return array|WP_Error The GET Response or an WP_ERROR
|
||||
*/
|
||||
public static function get( $url ) {
|
||||
do_action( 'activitypub_pre_http_get', $url );
|
||||
\do_action( 'activitypub_pre_http_get', $url );
|
||||
|
||||
$date = \gmdate( 'D, d M Y H:i:s T' );
|
||||
$signature = Signature::generate_signature( Users::APPLICATION_USER_ID, 'get', $url, $date );
|
||||
@ -108,4 +108,24 @@ class Http {
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for URL for Tombstone.
|
||||
*
|
||||
* @param string $url The URL to check.
|
||||
*
|
||||
* @return bool True if the URL is a tombstone.
|
||||
*/
|
||||
public static function is_tombstone( $url ) {
|
||||
\do_action( 'activitypub_pre_http_is_tombstone', $url );
|
||||
|
||||
$response = \wp_safe_remote_get( $url );
|
||||
$code = \wp_remote_retrieve_response_code( $response );
|
||||
|
||||
if ( in_array( (int) $code, array( 404, 410 ), true ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -26,6 +26,10 @@ class Mention {
|
||||
* @return string the filtered post-content
|
||||
*/
|
||||
public static function the_content( $the_content ) {
|
||||
// small protection against execution timeouts: limit to 1 MB
|
||||
if ( mb_strlen( $the_content ) > MB_IN_BYTES ) {
|
||||
return $the_content;
|
||||
}
|
||||
$tag_stack = array();
|
||||
$protected_tags = array(
|
||||
'pre',
|
||||
|
@ -114,6 +114,9 @@ class Migration {
|
||||
if ( version_compare( $version_from_db, '1.0.0', '<' ) ) {
|
||||
self::migrate_from_0_17();
|
||||
}
|
||||
if ( version_compare( $version_from_db, '1.3.0', '<' ) ) {
|
||||
self::migrate_from_1_2_0();
|
||||
}
|
||||
|
||||
update_option( 'activitypub_db_version', self::get_target_version() );
|
||||
|
||||
@ -176,4 +179,22 @@ class Migration {
|
||||
\update_option( 'activitypub_custom_post_content', $content );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the cache after updating to 1.3.0
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function migrate_from_1_2_0() {
|
||||
$user_ids = get_users(
|
||||
array(
|
||||
'fields' => 'ID',
|
||||
'capability__in' => array( 'publish_posts' ),
|
||||
)
|
||||
);
|
||||
|
||||
foreach ( $user_ids as $user_id ) {
|
||||
wp_cache_delete( sprintf( Followers::CACHE_KEY_INBOXES, $user_id ), 'activitypub' );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -114,7 +114,7 @@ class Scheduler {
|
||||
$followers = Followers::get_outdated_followers( $number );
|
||||
|
||||
foreach ( $followers as $follower ) {
|
||||
$meta = get_remote_metadata_by_actor( $follower->get_url(), false );
|
||||
$meta = get_remote_metadata_by_actor( $follower->get_id(), false );
|
||||
|
||||
if ( empty( $meta ) || ! is_array( $meta ) || is_wp_error( $meta ) ) {
|
||||
Followers::add_error( $follower->get__id(), $meta );
|
||||
|
@ -5,14 +5,9 @@ use function Activitypub\esc_hashtag;
|
||||
|
||||
class Shortcodes {
|
||||
/**
|
||||
* Class constructor, registering WordPress then Shortcodes
|
||||
* Register the shortcodes
|
||||
*/
|
||||
public static function init() {
|
||||
// do not load on admin pages
|
||||
if ( is_admin() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
public static function register() {
|
||||
foreach ( get_class_methods( self::class ) as $shortcode ) {
|
||||
if ( 'init' !== $shortcode ) {
|
||||
add_shortcode( 'ap_' . $shortcode, array( self::class, $shortcode ) );
|
||||
@ -20,6 +15,17 @@ class Shortcodes {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister the shortcodes
|
||||
*/
|
||||
public static function unregister() {
|
||||
foreach ( get_class_methods( self::class ) as $shortcode ) {
|
||||
if ( 'init' !== $shortcode ) {
|
||||
remove_shortcode( 'ap_' . $shortcode );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates output for the 'ap_hashtags' shortcode
|
||||
*
|
||||
@ -384,7 +390,8 @@ class Shortcodes {
|
||||
return '';
|
||||
}
|
||||
|
||||
$name = \get_the_author_meta( 'display_name', $item->post_author );
|
||||
$author_id = \get_post_field( 'post_author', $item->ID );
|
||||
$name = \get_the_author_meta( 'display_name', $author_id );
|
||||
|
||||
if ( ! $name ) {
|
||||
return '';
|
||||
@ -409,7 +416,8 @@ class Shortcodes {
|
||||
return '';
|
||||
}
|
||||
|
||||
$url = \get_the_author_meta( 'user_url', $item->post_author );
|
||||
$author_id = \get_post_field( 'post_author', $item->ID );
|
||||
$url = \get_the_author_meta( 'user_url', $author_id );
|
||||
|
||||
if ( ! $url ) {
|
||||
return '';
|
||||
|
@ -4,6 +4,7 @@ namespace Activitypub;
|
||||
use WP_Error;
|
||||
use DateTime;
|
||||
use DateTimeZone;
|
||||
use WP_REST_Request;
|
||||
use Activitypub\Collection\Users;
|
||||
|
||||
/**
|
||||
@ -226,7 +227,7 @@ class Signature {
|
||||
/**
|
||||
* Verifies the http signatures
|
||||
*
|
||||
* @param WP_REQUEST|array $request The request object or $_SERVER array.
|
||||
* @param WP_REST_Request|array $request The request object or $_SERVER array.
|
||||
*
|
||||
* @return mixed A boolean or WP_Error.
|
||||
*/
|
||||
@ -259,7 +260,7 @@ class Signature {
|
||||
}
|
||||
|
||||
if ( ! isset( $headers['signature'] ) ) {
|
||||
return new WP_Error( 'activitypub_signature', __( 'Request not signed', 'activitypub' ), array( 'status' => 403 ) );
|
||||
return new WP_Error( 'activitypub_signature', __( 'Request not signed', 'activitypub' ), array( 'status' => 401 ) );
|
||||
}
|
||||
|
||||
if ( array_key_exists( 'signature', $headers ) ) {
|
||||
@ -269,7 +270,7 @@ class Signature {
|
||||
}
|
||||
|
||||
if ( ! isset( $signature_block ) || ! $signature_block ) {
|
||||
return new WP_Error( 'activitypub_signature', __( 'Incompatible request signature. keyId and signature are required', 'activitypub' ), array( 'status' => 403 ) );
|
||||
return new WP_Error( 'activitypub_signature', __( 'Incompatible request signature. keyId and signature are required', 'activitypub' ), array( 'status' => 401 ) );
|
||||
}
|
||||
|
||||
$signed_headers = $signature_block['headers'];
|
||||
@ -279,12 +280,12 @@ class Signature {
|
||||
|
||||
$signed_data = self::get_signed_data( $signed_headers, $signature_block, $headers );
|
||||
if ( ! $signed_data ) {
|
||||
return new WP_Error( 'activitypub_signature', __( 'Signed request date outside acceptable time window', 'activitypub' ), array( 'status' => 403 ) );
|
||||
return new WP_Error( 'activitypub_signature', __( 'Signed request date outside acceptable time window', 'activitypub' ), array( 'status' => 401 ) );
|
||||
}
|
||||
|
||||
$algorithm = self::get_signature_algorithm( $signature_block );
|
||||
if ( ! $algorithm ) {
|
||||
return new WP_Error( 'activitypub_signature', __( 'Unsupported signature algorithm (only rsa-sha256 and hs2019 are supported)', 'activitypub' ), array( 'status' => 403 ) );
|
||||
return new WP_Error( 'activitypub_signature', __( 'Unsupported signature algorithm (only rsa-sha256 and hs2019 are supported)', 'activitypub' ), array( 'status' => 401 ) );
|
||||
}
|
||||
|
||||
if ( \in_array( 'digest', $signed_headers, true ) && isset( $body ) ) {
|
||||
@ -300,7 +301,7 @@ class Signature {
|
||||
}
|
||||
|
||||
if ( \base64_encode( \hash( $hashalg, $body, true ) ) !== $digest[1] ) { // phpcs:ignore
|
||||
return new WP_Error( 'activitypub_signature', __( 'Invalid Digest header', 'activitypub' ), array( 'status' => 403 ) );
|
||||
return new WP_Error( 'activitypub_signature', __( 'Invalid Digest header', 'activitypub' ), array( 'status' => 401 ) );
|
||||
}
|
||||
}
|
||||
|
||||
@ -313,7 +314,7 @@ class Signature {
|
||||
$verified = \openssl_verify( $signed_data, $signature_block['signature'], $public_key, $algorithm ) > 0;
|
||||
|
||||
if ( ! $verified ) {
|
||||
return new WP_Error( 'activitypub_signature', __( 'Invalid signature', 'activitypub' ), array( 'status' => 403 ) );
|
||||
return new WP_Error( 'activitypub_signature', __( 'Invalid signature', 'activitypub' ), array( 'status' => 401 ) );
|
||||
}
|
||||
return $verified;
|
||||
}
|
||||
@ -323,17 +324,25 @@ class Signature {
|
||||
*
|
||||
* @param string $key_id The URL to the public key.
|
||||
*
|
||||
* @return WP_Error|string The public key.
|
||||
* @return WP_Error|string The public key or WP_Error.
|
||||
*/
|
||||
public static function get_remote_key( $key_id ) { // phpcs:ignore
|
||||
$actor = get_remote_metadata_by_actor( strip_fragment_from_url( $key_id ) ); // phpcs:ignore
|
||||
if ( \is_wp_error( $actor ) ) {
|
||||
return $actor;
|
||||
return new WP_Error(
|
||||
'activitypub_no_remote_profile_found',
|
||||
__( 'No Profile found or Profile not accessible', 'activitypub' ),
|
||||
array( 'status' => 401 )
|
||||
);
|
||||
}
|
||||
if ( isset( $actor['publicKey']['publicKeyPem'] ) ) {
|
||||
return \rtrim( $actor['publicKey']['publicKeyPem'] ); // phpcs:ignore
|
||||
}
|
||||
return new WP_Error( 'activitypub_no_remote_key_found', __( 'No Public-Key found', 'activitypub' ), array( 'status' => 403 ) );
|
||||
return new WP_Error(
|
||||
'activitypub_no_remote_key_found',
|
||||
__( 'No Public-Key found', 'activitypub' ),
|
||||
array( 'status' => 401 )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -41,9 +41,14 @@ class Webfinger {
|
||||
* @return string|WP_Error The URL or WP_Error
|
||||
*/
|
||||
public static function resolve( $resource ) {
|
||||
if ( ! $resource ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( ! preg_match( '/^@?' . ACTIVITYPUB_USERNAME_REGEXP . '$/i', $resource, $m ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$transient_key = 'activitypub_resolve_' . ltrim( $resource, '@' );
|
||||
|
||||
$link = \get_transient( $transient_key );
|
||||
|
@ -2,14 +2,10 @@
|
||||
namespace Activitypub\Collection;
|
||||
|
||||
use WP_Error;
|
||||
use Exception;
|
||||
use WP_Query;
|
||||
use Activitypub\Http;
|
||||
use Activitypub\Webfinger;
|
||||
use Activitypub\Model\Follower;
|
||||
use Activitypub\Collection\Users;
|
||||
use Activitypub\Activity\Activity;
|
||||
use Activitypub\Activity\Base_Object;
|
||||
|
||||
use function Activitypub\is_tombstone;
|
||||
use function Activitypub\get_remote_metadata_by_actor;
|
||||
@ -24,136 +20,6 @@ class Followers {
|
||||
const POST_TYPE = 'ap_follower';
|
||||
const CACHE_KEY_INBOXES = 'follower_inboxes_%s';
|
||||
|
||||
/**
|
||||
* Register WordPress hooks/actions and register Taxonomy
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function init() {
|
||||
// register "followers" post_type
|
||||
self::register_post_type();
|
||||
|
||||
\add_action( 'activitypub_inbox_follow', array( self::class, 'handle_follow_request' ), 10, 2 );
|
||||
\add_action( 'activitypub_inbox_undo', array( self::class, 'handle_undo_request' ), 10, 2 );
|
||||
|
||||
\add_action( 'activitypub_followers_post_follow', array( self::class, 'send_follow_response' ), 10, 4 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the "Followers" Taxonomy
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private static function register_post_type() {
|
||||
register_post_type(
|
||||
self::POST_TYPE,
|
||||
array(
|
||||
'labels' => array(
|
||||
'name' => _x( 'Followers', 'post_type plural name', 'activitypub' ),
|
||||
'singular_name' => _x( 'Follower', 'post_type single name', 'activitypub' ),
|
||||
),
|
||||
'public' => false,
|
||||
'hierarchical' => false,
|
||||
'rewrite' => false,
|
||||
'query_var' => false,
|
||||
'delete_with_user' => false,
|
||||
'can_export' => true,
|
||||
'supports' => array(),
|
||||
)
|
||||
);
|
||||
|
||||
register_post_meta(
|
||||
self::POST_TYPE,
|
||||
'activitypub_inbox',
|
||||
array(
|
||||
'type' => 'string',
|
||||
'single' => true,
|
||||
'sanitize_callback' => array( self::class, 'sanitize_url' ),
|
||||
)
|
||||
);
|
||||
|
||||
register_post_meta(
|
||||
self::POST_TYPE,
|
||||
'activitypub_errors',
|
||||
array(
|
||||
'type' => 'string',
|
||||
'single' => false,
|
||||
'sanitize_callback' => function( $value ) {
|
||||
if ( ! is_string( $value ) ) {
|
||||
throw new Exception( 'Error message is no valid string' );
|
||||
}
|
||||
|
||||
return esc_sql( $value );
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
register_post_meta(
|
||||
self::POST_TYPE,
|
||||
'activitypub_user_id',
|
||||
array(
|
||||
'type' => 'string',
|
||||
'single' => false,
|
||||
'sanitize_callback' => function( $value ) {
|
||||
return esc_sql( $value );
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
register_post_meta(
|
||||
self::POST_TYPE,
|
||||
'activitypub_actor_json',
|
||||
array(
|
||||
'type' => 'string',
|
||||
'single' => true,
|
||||
'sanitize_callback' => function( $value ) {
|
||||
return sanitize_text_field( $value );
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
do_action( 'activitypub_after_register_post_type' );
|
||||
}
|
||||
|
||||
public static function sanitize_url( $value ) {
|
||||
if ( filter_var( $value, FILTER_VALIDATE_URL ) === false ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return esc_url_raw( $value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the "Follow" Request
|
||||
*
|
||||
* @param array $object The JSON "Follow" Activity
|
||||
* @param int $user_id The ID of the ID of the WordPress User
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function handle_follow_request( $object, $user_id ) {
|
||||
// save follower
|
||||
$follower = self::add_follower( $user_id, $object['actor'] );
|
||||
|
||||
do_action( 'activitypub_followers_post_follow', $object['actor'], $object, $user_id, $follower );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle "Unfollow" requests
|
||||
*
|
||||
* @param array $object The JSON "Undo" Activity
|
||||
* @param int $user_id The ID of the ID of the WordPress User
|
||||
*/
|
||||
public static function handle_undo_request( $object, $user_id ) {
|
||||
if (
|
||||
isset( $object['object'] ) &&
|
||||
isset( $object['object']['type'] ) &&
|
||||
'Follow' === $object['object']['type']
|
||||
) {
|
||||
self::remove_follower( $user_id, $object['actor'] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add new Follower
|
||||
*
|
||||
@ -173,8 +39,6 @@ class Followers {
|
||||
return new WP_Error( 'activitypub_invalid_follower', __( 'Invalid Follower', 'activitypub' ), array( 'status' => 400 ) );
|
||||
}
|
||||
|
||||
$error = null;
|
||||
|
||||
$follower = new Follower();
|
||||
$follower->from_array( $meta );
|
||||
|
||||
@ -184,14 +48,10 @@ class Followers {
|
||||
return $id;
|
||||
}
|
||||
|
||||
$meta = get_post_meta( $id, 'activitypub_user_id' );
|
||||
|
||||
if ( $error ) {
|
||||
self::add_error( $id, $error );
|
||||
}
|
||||
$post_meta = get_post_meta( $id, 'activitypub_user_id' );
|
||||
|
||||
// phpcs:ignore WordPress.PHP.StrictInArray.MissingTrueStrict
|
||||
if ( is_array( $meta ) && ! in_array( $user_id, $meta ) ) {
|
||||
if ( is_array( $post_meta ) && ! in_array( $user_id, $post_meta ) ) {
|
||||
add_post_meta( $id, 'activitypub_user_id', $user_id );
|
||||
wp_cache_delete( sprintf( self::CACHE_KEY_INBOXES, $user_id ), 'activitypub' );
|
||||
}
|
||||
@ -220,16 +80,17 @@ class Followers {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a Follower
|
||||
* Get a Follower.
|
||||
*
|
||||
* @param int $user_id The ID of the WordPress User
|
||||
* @param string $actor The Actor URL
|
||||
*
|
||||
* @return \Activitypub\Model\Follower The Follower object
|
||||
* @return \Activitypub\Model\Follower|null The Follower object or null
|
||||
*/
|
||||
public static function get_follower( $user_id, $actor ) {
|
||||
global $wpdb;
|
||||
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
|
||||
$post_id = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT DISTINCT p.ID FROM $wpdb->posts p INNER JOIN $wpdb->postmeta pm ON p.ID = pm.post_id WHERE p.post_type = %s AND pm.meta_key = 'activitypub_user_id' AND pm.meta_value = %d AND p.guid = %s",
|
||||
@ -250,51 +111,29 @@ class Followers {
|
||||
}
|
||||
|
||||
/**
|
||||
* Send Accept response
|
||||
* Get a Follower by Actor indepenent from the User.
|
||||
*
|
||||
* @param string $actor The Actor URL
|
||||
* @param array $object The Activity object
|
||||
* @param int $user_id The ID of the WordPress User
|
||||
* @param Activitypub\Model\Follower $follower The Follower object
|
||||
* @param string $actor The Actor URL.
|
||||
*
|
||||
* @return void
|
||||
* @return \Activitypub\Model\Follower|null The Follower object or null
|
||||
*/
|
||||
public static function send_follow_response( $actor, $object, $user_id, $follower ) {
|
||||
if ( is_wp_error( $follower ) ) {
|
||||
// it is not even possible to send a "Reject" because
|
||||
// we can not get the Remote-Inbox
|
||||
return;
|
||||
}
|
||||
public static function get_follower_by_actor( $actor ) {
|
||||
global $wpdb;
|
||||
|
||||
// only send minimal data
|
||||
$object = array_intersect_key(
|
||||
$object,
|
||||
array_flip(
|
||||
array(
|
||||
'id',
|
||||
'type',
|
||||
'actor',
|
||||
'object',
|
||||
)
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching
|
||||
$post_id = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT ID FROM $wpdb->posts WHERE guid=%s",
|
||||
esc_sql( $actor )
|
||||
)
|
||||
);
|
||||
|
||||
$user = Users::get_by_id( $user_id );
|
||||
if ( $post_id ) {
|
||||
$post = get_post( $post_id );
|
||||
return Follower::init_from_cpt( $post );
|
||||
}
|
||||
|
||||
// get inbox
|
||||
$inbox = $follower->get_shared_inbox();
|
||||
|
||||
// send "Accept" activity
|
||||
$activity = new Activity();
|
||||
$activity->set_type( 'Accept' );
|
||||
$activity->set_object( $object );
|
||||
$activity->set_actor( $user->get_id() );
|
||||
$activity->set_to( $actor );
|
||||
$activity->set_id( $user->get_id() . '#follow-' . \preg_replace( '~^https?://~', '', $actor ) . '-' . \time() );
|
||||
|
||||
$activity = $activity->to_json();
|
||||
|
||||
Http::post( $inbox, $activity, $user_id );
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -360,6 +199,7 @@ class Followers {
|
||||
*/
|
||||
public static function get_all_followers() {
|
||||
$args = array(
|
||||
'nopaging' => true,
|
||||
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
|
||||
'meta_query' => array(
|
||||
'relation' => 'AND',
|
||||
@ -428,6 +268,7 @@ class Followers {
|
||||
// get all Followers of a ID of the WordPress User
|
||||
$posts = new WP_Query(
|
||||
array(
|
||||
'nopaging' => true,
|
||||
'post_type' => self::POST_TYPE,
|
||||
'fields' => 'ids',
|
||||
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
|
||||
|
@ -0,0 +1,235 @@
|
||||
<?php
|
||||
namespace Activitypub\Collection;
|
||||
|
||||
use WP_Error;
|
||||
use WP_Comment_Query;
|
||||
|
||||
use function Activitypub\url_to_commentid;
|
||||
use function Activitypub\object_id_to_comment;
|
||||
use function Activitypub\get_remote_metadata_by_actor;
|
||||
|
||||
/**
|
||||
* ActivityPub Interactions Collection
|
||||
*/
|
||||
class Interactions {
|
||||
/**
|
||||
* Add a comment to a post
|
||||
*
|
||||
* @param array $activity The activity-object
|
||||
*
|
||||
* @return array|false The commentdata or false on failure
|
||||
*/
|
||||
public static function add_comment( $activity ) {
|
||||
if (
|
||||
! isset( $activity['object'] ) ||
|
||||
! isset( $activity['object']['id'] )
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! isset( $activity['object']['inReplyTo'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$in_reply_to = \esc_url_raw( $activity['object']['inReplyTo'] );
|
||||
$comment_post_id = \url_to_postid( $in_reply_to );
|
||||
$parent_comment = object_id_to_comment( $in_reply_to );
|
||||
|
||||
// save only replys and reactions
|
||||
if ( ! $comment_post_id && $parent_comment ) {
|
||||
$comment_post_id = $parent_comment->comment_post_ID;
|
||||
}
|
||||
|
||||
// not a reply to a post or comment
|
||||
if ( ! $comment_post_id ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$meta = get_remote_metadata_by_actor( $activity['actor'] );
|
||||
|
||||
if ( ! $meta || \is_wp_error( $meta ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$commentdata = array(
|
||||
'comment_post_ID' => $comment_post_id,
|
||||
'comment_author' => \esc_attr( $meta['name'] ),
|
||||
'comment_author_url' => \esc_url_raw( $meta['url'] ),
|
||||
'comment_content' => \addslashes( $activity['object']['content'] ),
|
||||
'comment_type' => 'comment',
|
||||
'comment_author_email' => '',
|
||||
'comment_parent' => $parent_comment ? $parent_comment->comment_ID : 0,
|
||||
'comment_meta' => array(
|
||||
'source_id' => \esc_url_raw( $activity['object']['id'] ),
|
||||
'source_url' => \esc_url_raw( $activity['object']['url'] ),
|
||||
'protocol' => 'activitypub',
|
||||
),
|
||||
);
|
||||
|
||||
if ( isset( $meta['icon']['url'] ) ) {
|
||||
$commentdata['comment_meta']['avatar_url'] = \esc_url_raw( $meta['icon']['url'] );
|
||||
}
|
||||
|
||||
// disable flood control
|
||||
\remove_action( 'check_comment_flood', 'check_comment_flood_db', 10 );
|
||||
// do not require email for AP entries
|
||||
\add_filter( 'pre_option_require_name_email', '__return_false' );
|
||||
// No nonce possible for this submission route
|
||||
\add_filter(
|
||||
'akismet_comment_nonce',
|
||||
function() {
|
||||
return 'inactive';
|
||||
}
|
||||
);
|
||||
\add_filter( 'wp_kses_allowed_html', array( self::class, 'allowed_comment_html' ), 10, 2 );
|
||||
|
||||
$comment = \wp_new_comment( $commentdata, true );
|
||||
|
||||
\remove_filter( 'wp_kses_allowed_html', array( self::class, 'allowed_comment_html' ), 10 );
|
||||
\remove_filter( 'pre_option_require_name_email', '__return_false' );
|
||||
// re-add flood control
|
||||
\add_action( 'check_comment_flood', 'check_comment_flood_db', 10, 4 );
|
||||
|
||||
return $comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a comment
|
||||
*
|
||||
* @param array $activity The activity-object
|
||||
*
|
||||
* @return array|false The commentdata or false on failure
|
||||
*/
|
||||
public static function update_comment( $activity ) {
|
||||
$meta = get_remote_metadata_by_actor( $activity['actor'] );
|
||||
|
||||
//Determine comment_ID
|
||||
$object_comment_id = url_to_commentid( \esc_url_raw( $activity['object']['id'] ) );
|
||||
|
||||
if ( ! $object_comment_id ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//found a local comment id
|
||||
$commentdata = \get_comment( $object_comment_id, ARRAY_A );
|
||||
$commentdata['comment_author'] = \esc_attr( $meta['name'] ? $meta['name'] : $meta['preferredUsername'] );
|
||||
$commentdata['comment_content'] = \addslashes( $activity['object']['content'] );
|
||||
if ( isset( $meta['icon']['url'] ) ) {
|
||||
$commentdata['comment_meta']['avatar_url'] = \esc_url_raw( $meta['icon']['url'] );
|
||||
}
|
||||
|
||||
// disable flood control
|
||||
\remove_action( 'check_comment_flood', 'check_comment_flood_db', 10 );
|
||||
// do not require email for AP entries
|
||||
\add_filter( 'pre_option_require_name_email', '__return_false' );
|
||||
// No nonce possible for this submission route
|
||||
\add_filter(
|
||||
'akismet_comment_nonce',
|
||||
function() {
|
||||
return 'inactive';
|
||||
}
|
||||
);
|
||||
\add_filter( 'wp_kses_allowed_html', array( self::class, 'allowed_comment_html' ), 10, 2 );
|
||||
|
||||
$comment = \wp_update_comment( $commentdata, true );
|
||||
|
||||
\remove_filter( 'wp_kses_allowed_html', array( self::class, 'allowed_comment_html' ), 10 );
|
||||
\remove_filter( 'pre_option_require_name_email', '__return_false' );
|
||||
// re-add flood control
|
||||
\add_action( 'check_comment_flood', 'check_comment_flood_db', 10, 4 );
|
||||
|
||||
return $comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get interaction(s) for a given URL/ID.
|
||||
*
|
||||
* @param strin $url The URL/ID to get interactions for.
|
||||
*
|
||||
* @return array The interactions as WP_Comment objects.
|
||||
*/
|
||||
public static function get_interaction_by_id( $url ) {
|
||||
$args = array(
|
||||
'nopaging' => true,
|
||||
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
|
||||
'meta_query' => array(
|
||||
'relation' => 'AND',
|
||||
array(
|
||||
'key' => 'protocol',
|
||||
'value' => 'activitypub',
|
||||
),
|
||||
array(
|
||||
'relation' => 'OR',
|
||||
array(
|
||||
'key' => 'source_url',
|
||||
'value' => $url,
|
||||
),
|
||||
array(
|
||||
'key' => 'source_id',
|
||||
'value' => $url,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$query = new WP_Comment_Query( $args );
|
||||
return $query->comments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get interaction(s) for a given actor.
|
||||
*
|
||||
* @param string $actor The Actor-URL.
|
||||
*
|
||||
* @return array The interactions as WP_Comment objects.
|
||||
*/
|
||||
public static function get_interactions_by_actor( $actor ) {
|
||||
$meta = get_remote_metadata_by_actor( $actor );
|
||||
|
||||
// get URL, because $actor seems to be the ID
|
||||
if ( $meta && ! is_wp_error( $meta ) && isset( $meta['url'] ) ) {
|
||||
$actor = $meta['url'];
|
||||
}
|
||||
|
||||
$args = array(
|
||||
'nopaging' => true,
|
||||
'author_url' => $actor,
|
||||
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
|
||||
'meta_query' => array(
|
||||
array(
|
||||
'key' => 'protocol',
|
||||
'value' => 'activitypub',
|
||||
'compare' => '=',
|
||||
),
|
||||
),
|
||||
);
|
||||
$comment_query = new WP_Comment_Query( $args );
|
||||
return $comment_query->comments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds line breaks to the list of allowed comment tags.
|
||||
*
|
||||
* @param array $allowed_tags Allowed HTML tags.
|
||||
* @param string $context Context.
|
||||
*
|
||||
* @return array Filtered tag list.
|
||||
*/
|
||||
public static function allowed_comment_html( $allowed_tags, $context = '' ) {
|
||||
if ( 'pre_comment_content' !== $context ) {
|
||||
// Do nothing.
|
||||
return $allowed_tags;
|
||||
}
|
||||
|
||||
// Add `p` and `br` to the list of allowed tags.
|
||||
if ( ! array_key_exists( 'br', $allowed_tags ) ) {
|
||||
$allowed_tags['br'] = array();
|
||||
}
|
||||
|
||||
if ( ! array_key_exists( 'p', $allowed_tags ) ) {
|
||||
$allowed_tags['p'] = array();
|
||||
}
|
||||
|
||||
return $allowed_tags;
|
||||
}
|
||||
}
|
@ -35,3 +35,15 @@ if ( ! function_exists( 'get_self_link' ) ) {
|
||||
return esc_url( apply_filters( 'self_link', set_url_scheme( 'http://' . $host['host'] . $path ) ) );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'is_countable' ) ) {
|
||||
/**
|
||||
* Polyfill for `is_countable()` function added in PHP 7.3.
|
||||
*
|
||||
* @param mixed $value The value to check.
|
||||
* @return bool True if `$value` is countable, otherwise false.
|
||||
*/
|
||||
function is_countable( $value ) {
|
||||
return is_array( $value ) || $value instanceof \Countable;
|
||||
}
|
||||
}
|
||||
|
@ -2,9 +2,11 @@
|
||||
namespace Activitypub;
|
||||
|
||||
use WP_Error;
|
||||
use WP_Comment_Query;
|
||||
use Activitypub\Http;
|
||||
use Activitypub\Activity\Activity;
|
||||
use Activitypub\Collection\Followers;
|
||||
use Activitypub\Collection\Users;
|
||||
|
||||
/**
|
||||
* Returns the ActivityPub default JSON-context
|
||||
@ -42,7 +44,7 @@ function get_webfinger_resource( $user_id ) {
|
||||
* @param string $actor The Actor URL.
|
||||
* @param bool $cached If the result should be cached.
|
||||
*
|
||||
* @return array The Actor profile as array
|
||||
* @return array|WP_Error The Actor profile as array or WP_Error on failure.
|
||||
*/
|
||||
function get_remote_metadata_by_actor( $actor, $cached = true ) {
|
||||
$pre = apply_filters( 'pre_get_remote_metadata_by_actor', false, $actor );
|
||||
@ -74,32 +76,25 @@ function get_remote_metadata_by_actor( $actor, $cached = true ) {
|
||||
|
||||
if ( ! \wp_http_validate_url( $actor ) ) {
|
||||
$metadata = new WP_Error( 'activitypub_no_valid_actor_url', \__( 'The "actor" is no valid URL', 'activitypub' ), array( 'status' => 400, 'actor' => $actor ) );
|
||||
\set_transient( $transient_key, $metadata, HOUR_IN_SECONDS ); // Cache the error for a shorter period.
|
||||
return $metadata;
|
||||
}
|
||||
|
||||
$short_timeout = function() {
|
||||
return 3;
|
||||
};
|
||||
add_filter( 'activitypub_remote_get_timeout', $short_timeout );
|
||||
$response = Http::get( $actor );
|
||||
remove_filter( 'activitypub_remote_get_timeout', $short_timeout );
|
||||
|
||||
if ( \is_wp_error( $response ) ) {
|
||||
\set_transient( $transient_key, $response, HOUR_IN_SECONDS ); // Cache the error for a shorter period.
|
||||
return $response;
|
||||
}
|
||||
|
||||
$metadata = \wp_remote_retrieve_body( $response );
|
||||
$metadata = \json_decode( $metadata, true );
|
||||
|
||||
\set_transient( $transient_key, $metadata, WEEK_IN_SECONDS );
|
||||
|
||||
if ( ! $metadata ) {
|
||||
$metadata = new WP_Error( 'activitypub_invalid_json', \__( 'No valid JSON data', 'activitypub' ), array( 'status' => 400, 'actor' => $actor ) );
|
||||
\set_transient( $transient_key, $metadata, HOUR_IN_SECONDS ); // Cache the error for a shorter period.
|
||||
return $metadata;
|
||||
}
|
||||
|
||||
\set_transient( $transient_key, $metadata, WEEK_IN_SECONDS );
|
||||
|
||||
return $metadata;
|
||||
}
|
||||
|
||||
@ -285,6 +280,16 @@ function is_activitypub_request() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if the current post type supports ActivityPub.
|
||||
if ( \is_singular() ) {
|
||||
$queried_object = \get_queried_object();
|
||||
$post_type = \get_post_type( $queried_object );
|
||||
|
||||
if ( ! \post_type_supports( $post_type, 'activitypub' ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// One can trigger an ActivityPub request by adding ?activitypub to the URL.
|
||||
// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.VariableRedeclaration
|
||||
global $wp_query;
|
||||
@ -481,3 +486,215 @@ function is_json( $data ) {
|
||||
function is_blog_public() {
|
||||
return (bool) apply_filters( 'activitypub_is_blog_public', \get_option( 'blog_public', 1 ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize a URL
|
||||
*
|
||||
* @param string $value The URL to sanitize
|
||||
*
|
||||
* @return string|null The sanitized URL or null if invalid
|
||||
*/
|
||||
function sanitize_url( $value ) {
|
||||
if ( filter_var( $value, FILTER_VALIDATE_URL ) === false ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return esc_url_raw( $value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract recipient URLs from Activity object
|
||||
*
|
||||
* @param array $data
|
||||
*
|
||||
* @return array The list of user URLs
|
||||
*/
|
||||
function extract_recipients_from_activity( $data ) {
|
||||
$recipient_items = array();
|
||||
|
||||
foreach ( array( 'to', 'bto', 'cc', 'bcc', 'audience' ) as $i ) {
|
||||
if ( array_key_exists( $i, $data ) ) {
|
||||
if ( is_array( $data[ $i ] ) ) {
|
||||
$recipient = $data[ $i ];
|
||||
} else {
|
||||
$recipient = array( $data[ $i ] );
|
||||
}
|
||||
$recipient_items = array_merge( $recipient_items, $recipient );
|
||||
}
|
||||
|
||||
if ( is_array( $data['object'] ) && array_key_exists( $i, $data['object'] ) ) {
|
||||
if ( is_array( $data['object'][ $i ] ) ) {
|
||||
$recipient = $data['object'][ $i ];
|
||||
} else {
|
||||
$recipient = array( $data['object'][ $i ] );
|
||||
}
|
||||
$recipient_items = array_merge( $recipient_items, $recipient );
|
||||
}
|
||||
}
|
||||
|
||||
$recipients = array();
|
||||
|
||||
// flatten array
|
||||
foreach ( $recipient_items as $recipient ) {
|
||||
if ( is_array( $recipient ) ) {
|
||||
// check if recipient is an object
|
||||
if ( array_key_exists( 'id', $recipient ) ) {
|
||||
$recipients[] = $recipient['id'];
|
||||
}
|
||||
} else {
|
||||
$recipients[] = $recipient;
|
||||
}
|
||||
}
|
||||
|
||||
return array_unique( $recipients );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if passed Activity is Public
|
||||
*
|
||||
* @param array $data The Activity object as array
|
||||
*
|
||||
* @return boolean True if public, false if not
|
||||
*/
|
||||
function is_activity_public( $data ) {
|
||||
$recipients = extract_recipients_from_activity( $data );
|
||||
|
||||
return in_array( 'https://www.w3.org/ns/activitystreams#Public', $recipients, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get active users based on a given duration
|
||||
*
|
||||
* @param int $duration The duration to check in month(s)
|
||||
*
|
||||
* @return int The number of active users
|
||||
*/
|
||||
function get_active_users( $duration = 1 ) {
|
||||
|
||||
$duration = intval( $duration );
|
||||
$transient_key = sprintf( 'monthly_active_users_%d', $duration );
|
||||
$count = get_transient( $transient_key );
|
||||
|
||||
if ( false === $count ) {
|
||||
global $wpdb;
|
||||
$query = "SELECT COUNT( DISTINCT post_author ) FROM {$wpdb->posts} WHERE post_type = 'post' AND post_status = 'publish' AND post_date <= DATE_SUB( NOW(), INTERVAL %d MONTH )";
|
||||
$query = $wpdb->prepare( $query, $duration );
|
||||
$count = $wpdb->get_var( $query ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery
|
||||
|
||||
set_transient( $transient_key, $count, DAY_IN_SECONDS );
|
||||
}
|
||||
|
||||
// if 0 authors where active
|
||||
if ( 0 === $count ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// if single user mode
|
||||
if ( is_single_user() ) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
// if blog user is disabled
|
||||
if ( is_user_disabled( Users::BLOG_USER_ID ) ) {
|
||||
return $count;
|
||||
}
|
||||
|
||||
// also count blog user
|
||||
return $count + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the total number of users
|
||||
*
|
||||
* @return int The total number of users
|
||||
*/
|
||||
function get_total_users() {
|
||||
// if single user mode
|
||||
if ( is_single_user() ) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
$users = \get_users(
|
||||
array(
|
||||
'capability__in' => array( 'publish_posts' ),
|
||||
)
|
||||
);
|
||||
|
||||
if ( is_array( $users ) ) {
|
||||
$users = count( $users );
|
||||
} else {
|
||||
$users = 1;
|
||||
}
|
||||
|
||||
// if blog user is disabled
|
||||
if ( is_user_disabled( Users::BLOG_USER_ID ) ) {
|
||||
return $users;
|
||||
}
|
||||
|
||||
return $users + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Examine a comment ID and look up an existing comment it represents.
|
||||
*
|
||||
* @param string $id ActivityPub object ID (usually a URL) to check.
|
||||
*
|
||||
* @return int|boolean Comment ID, or false on failure.
|
||||
*/
|
||||
function object_id_to_comment( $id ) {
|
||||
$comment_query = new WP_Comment_Query(
|
||||
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
|
||||
)
|
||||
);
|
||||
|
||||
if ( ! $comment_query->comments ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( count( $comment_query->comments ) > 1 ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $comment_query->comments[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify if URL is a local comment,
|
||||
* Or if it is a previously received remote comment
|
||||
* (For threading comments locally)
|
||||
*
|
||||
* @param string $url The URL to check.
|
||||
*
|
||||
* @return int comment_ID or null if not found
|
||||
*/
|
||||
function url_to_commentid( $url ) {
|
||||
if ( ! $url || ! filter_var( $url, FILTER_VALIDATE_URL ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$args = array(
|
||||
// phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_query
|
||||
'meta_query' => array(
|
||||
'relation' => 'OR',
|
||||
array(
|
||||
'key' => 'source_url',
|
||||
'value' => $url,
|
||||
),
|
||||
array(
|
||||
'key' => 'source_id',
|
||||
'value' => $url,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$query = new \WP_Comment_Query();
|
||||
$comments = $query->query( $args );
|
||||
|
||||
if ( $comments && is_array( $comments ) ) {
|
||||
return $comments[0]->comment_ID;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
@ -0,0 +1,61 @@
|
||||
<?php
|
||||
namespace Activitypub\Handler;
|
||||
|
||||
use WP_Error;
|
||||
use Activitypub\Collection\Interactions;
|
||||
|
||||
use function Activitypub\is_activity_public;
|
||||
use function Activitypub\object_id_to_comment;
|
||||
|
||||
/**
|
||||
* Handle Create requests
|
||||
*/
|
||||
class Create {
|
||||
/**
|
||||
* Initialize the class, registering WordPress hooks
|
||||
*/
|
||||
public static function init() {
|
||||
\add_action( 'activitypub_inbox_create', array( self::class, 'handle_create' ), 10, 3 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles "Create" requests
|
||||
*
|
||||
* @param array $array The activity-object
|
||||
* @param int $user_id The id of the local blog-user
|
||||
* @param Activitypub\Activity $object The activity object
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function handle_create( $array, $user_id, $object = null ) {
|
||||
if (
|
||||
! isset( $array['object'] ) ||
|
||||
! isset( $array['object']['id'] )
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check if Activity is public or not
|
||||
if ( ! is_activity_public( $array ) ) {
|
||||
// @todo maybe send email
|
||||
return;
|
||||
}
|
||||
|
||||
$check_dupe = object_id_to_comment( $array['object']['id'] );
|
||||
|
||||
// if comment exists, call update action
|
||||
if ( $check_dupe ) {
|
||||
\do_action( 'activitypub_inbox_update', $array, $user_id, $object );
|
||||
return;
|
||||
}
|
||||
|
||||
$state = Interactions::add_comment( $array );
|
||||
$reaction = null;
|
||||
|
||||
if ( $state && ! \is_wp_error( $reaction ) ) {
|
||||
$reaction = \get_comment( $state );
|
||||
}
|
||||
|
||||
\do_action( 'activitypub_handled_create', $array, $user_id, $state, $reaction );
|
||||
}
|
||||
}
|
165
wp-content/plugins/activitypub/includes/handler/class-delete.php
Normal file
165
wp-content/plugins/activitypub/includes/handler/class-delete.php
Normal file
@ -0,0 +1,165 @@
|
||||
<?php
|
||||
namespace Activitypub\Handler;
|
||||
|
||||
use WP_Error;
|
||||
use WP_REST_Request;
|
||||
use Activitypub\Http;
|
||||
use Activitypub\Collection\Followers;
|
||||
use Activitypub\Collection\Interactions;
|
||||
|
||||
/**
|
||||
* Handles Delete requests.
|
||||
*/
|
||||
class Delete {
|
||||
/**
|
||||
* Initialize the class, registering WordPress hooks
|
||||
*/
|
||||
public static function init() {
|
||||
\add_action( 'activitypub_inbox_delete', array( self::class, 'handle_delete' ), 10, 2 );
|
||||
// defer signature verification for `Delete` requests.
|
||||
\add_filter( 'activitypub_defer_signature_verification', array( self::class, 'defer_signature_verification' ), 10, 2 );
|
||||
// side effect
|
||||
\add_action( 'activitypub_delete_actor_interactions', array( self::class, 'delete_interactions' ), 10, 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles "Delete" requests.
|
||||
*
|
||||
* @param array $activity The delete activity.
|
||||
* @param int $user_id The ID of the user performing the delete activity.
|
||||
*/
|
||||
public static function handle_delete( $activity, $user_id ) {
|
||||
$object_type = isset( $activity['object']['type'] ) ? $activity['object']['type'] : '';
|
||||
|
||||
switch ( $object_type ) {
|
||||
// Actor Types
|
||||
// @see https://www.w3.org/TR/activitystreams-vocabulary/#actor-types
|
||||
case 'Person':
|
||||
case 'Group':
|
||||
case 'Organization':
|
||||
case 'Service':
|
||||
case 'Application':
|
||||
self::maybe_delete_follower( $user_id, $activity );
|
||||
break;
|
||||
// Object and Link Types
|
||||
// @see https://www.w3.org/TR/activitystreams-vocabulary/#object-types
|
||||
case 'Note':
|
||||
case 'Article':
|
||||
case 'Image':
|
||||
case 'Audio':
|
||||
case 'Video':
|
||||
case 'Event':
|
||||
case 'Document':
|
||||
self::maybe_delete_interaction( $activity );
|
||||
break;
|
||||
// Tombstone Type
|
||||
// @see: https://www.w3.org/TR/activitystreams-vocabulary/#dfn-tombstone
|
||||
case 'Tombstone':
|
||||
self::maybe_delete_interaction( $activity );
|
||||
break;
|
||||
// Minimal Activity
|
||||
// @see https://www.w3.org/TR/activitystreams-core/#example-1
|
||||
default:
|
||||
// ignore non Minimal Activities.
|
||||
if ( ! is_string( $activity['object'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check if Object is an Actor.
|
||||
if ( $activity['actor'] === $activity['object'] ) {
|
||||
self::maybe_delete_follower( $activity );
|
||||
self::maybe_delete_interactions( $activity );
|
||||
} else { // assume a interaction otherwise.
|
||||
self::maybe_delete_interaction( $activity );
|
||||
}
|
||||
// maybe handle Delete Activity for other Object Types.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a Follower if Actor-URL is a Tombstone.
|
||||
*
|
||||
* @param array $activity The delete activity.
|
||||
*/
|
||||
public static function maybe_delete_follower( $activity ) {
|
||||
$follower = Followers::get_follower_by_actor( $activity['actor'] );
|
||||
|
||||
// verify if Actor is deleted.
|
||||
if ( $follower && Http::is_tombstone( $activity['actor'] ) ) {
|
||||
$follower->delete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete Reactions if Actor-URL is a Tombstone.
|
||||
*
|
||||
* @param array $activity The delete activity.
|
||||
*/
|
||||
public static function maybe_delete_interactions( $activity ) {
|
||||
// verify if Actor is deleted.
|
||||
if ( Http::is_tombstone( $activity['actor'] ) ) {
|
||||
\wp_schedule_single_event(
|
||||
\time(),
|
||||
'activitypub_delete_actor_interactions',
|
||||
array( $activity['actor'] )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete comments from an Actor.
|
||||
*
|
||||
* @param array $comments The comments to delete.
|
||||
*/
|
||||
public static function delete_interactions( $actor ) {
|
||||
$comments = Interactions::get_interactions_by_actor( $actor );
|
||||
|
||||
if ( is_array( $comments ) ) {
|
||||
foreach ( $comments as $comment ) {
|
||||
wp_delete_comment( $comment->comment_ID );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a Reaction if URL is a Tombstone.
|
||||
*
|
||||
* @param array $activity The delete activity.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function maybe_delete_interaction( $activity ) {
|
||||
if ( is_array( $activity['object'] ) ) {
|
||||
$id = $activity['object']['id'];
|
||||
} else {
|
||||
$id = $activity['object'];
|
||||
}
|
||||
|
||||
$comments = Interactions::get_interaction_by_id( $id );
|
||||
|
||||
if ( $comments && Http::is_tombstone( $id ) ) {
|
||||
foreach ( $comments as $comment ) {
|
||||
wp_delete_comment( $comment->comment_ID, true );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Defer signature verification for `Delete` requests.
|
||||
*
|
||||
* @param bool $defer Whether to defer signature verification.
|
||||
* @param WP_REST_Request $request The request object.
|
||||
*
|
||||
* @return bool Whether to defer signature verification.
|
||||
*/
|
||||
public static function defer_signature_verification( $defer, $request ) {
|
||||
$json = $request->get_json_params();
|
||||
|
||||
if ( isset( $json['type'] ) && 'Delete' === $json['type'] ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
<?php
|
||||
namespace Activitypub\Handler;
|
||||
|
||||
use Activitypub\Http;
|
||||
use Activitypub\Activity\Activity;
|
||||
use Activitypub\Collection\Users;
|
||||
use Activitypub\Collection\Followers;
|
||||
|
||||
/**
|
||||
* Handle Follow requests
|
||||
*/
|
||||
class Follow {
|
||||
/**
|
||||
* Initialize the class, registering WordPress hooks
|
||||
*/
|
||||
public static function init() {
|
||||
\add_action( 'activitypub_inbox_follow', array( self::class, 'handle_follow' ), 10, 2 );
|
||||
\add_action( 'activitypub_followers_post_follow', array( self::class, 'send_follow_response' ), 10, 4 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle "Follow" requests
|
||||
*
|
||||
* @param array $activity The activity object
|
||||
* @param int $user_id The user ID
|
||||
*/
|
||||
public static function handle_follow( $activity, $user_id ) {
|
||||
// save follower
|
||||
$follower = Followers::add_follower( $user_id, $activity['actor'] );
|
||||
|
||||
do_action( 'activitypub_followers_post_follow', $activity['actor'], $activity, $user_id, $follower );
|
||||
}
|
||||
|
||||
/**
|
||||
* Send Accept response
|
||||
*
|
||||
* @param string $actor The Actor URL
|
||||
* @param array $object The Activity object
|
||||
* @param int $user_id The ID of the WordPress User
|
||||
* @param Activitypub\Model\Follower $follower The Follower object
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function send_follow_response( $actor, $object, $user_id, $follower ) {
|
||||
if ( \is_wp_error( $follower ) ) {
|
||||
// it is not even possible to send a "Reject" because
|
||||
// we can not get the Remote-Inbox
|
||||
return;
|
||||
}
|
||||
|
||||
// only send minimal data
|
||||
$object = array_intersect_key(
|
||||
$object,
|
||||
array_flip(
|
||||
array(
|
||||
'id',
|
||||
'type',
|
||||
'actor',
|
||||
'object',
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
$user = Users::get_by_id( $user_id );
|
||||
|
||||
// get inbox
|
||||
$inbox = $follower->get_shared_inbox();
|
||||
|
||||
// send "Accept" activity
|
||||
$activity = new Activity();
|
||||
$activity->set_type( 'Accept' );
|
||||
$activity->set_object( $object );
|
||||
$activity->set_actor( $user->get_id() );
|
||||
$activity->set_to( $actor );
|
||||
$activity->set_id( $user->get_id() . '#follow-' . \preg_replace( '~^https?://~', '', $actor ) . '-' . \time() );
|
||||
|
||||
$activity = $activity->to_json();
|
||||
|
||||
Http::post( $inbox, $activity, $user_id );
|
||||
}
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
namespace Activitypub\Handler;
|
||||
|
||||
use Activitypub\Collection\Followers;
|
||||
|
||||
/**
|
||||
* Handle Undo requests
|
||||
*/
|
||||
class Undo {
|
||||
/**
|
||||
* Initialize the class, registering WordPress hooks
|
||||
*/
|
||||
public static function init() {
|
||||
\add_action( 'activitypub_inbox_undo', array( self::class, 'handle_undo' ), 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle "Unfollow" requests
|
||||
*
|
||||
* @param array $activity The JSON "Undo" Activity
|
||||
* @param int $user_id The ID of the ID of the WordPress User
|
||||
*/
|
||||
public static function handle_undo( $activity, $user_id ) {
|
||||
if (
|
||||
isset( $activity['object']['type'] ) &&
|
||||
'Follow' === $activity['object']['type']
|
||||
) {
|
||||
Followers::remove_follower( $user_id, $activity['actor'] );
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
<?php
|
||||
namespace Activitypub\Handler;
|
||||
|
||||
use WP_Error;
|
||||
use Activitypub\Collection\Interactions;
|
||||
|
||||
use function Activitypub\get_remote_metadata_by_actor;
|
||||
|
||||
/**
|
||||
* Handle Update requests.
|
||||
*/
|
||||
class Update {
|
||||
/**
|
||||
* Initialize the class, registering WordPress hooks
|
||||
*/
|
||||
public static function init() {
|
||||
\add_action( 'activitypub_inbox_update', array( self::class, 'handle_update' ), 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle "Update" requests
|
||||
*
|
||||
* @param array $array The activity-object
|
||||
* @param int $user_id The id of the local blog-user
|
||||
*/
|
||||
public static function handle_update( $array, $user_id ) {
|
||||
$object_type = isset( $array['object']['type'] ) ? $array['object']['type'] : '';
|
||||
|
||||
switch ( $object_type ) {
|
||||
// Actor Types
|
||||
// @see https://www.w3.org/TR/activitystreams-vocabulary/#actor-types
|
||||
case 'Person':
|
||||
case 'Group':
|
||||
case 'Organization':
|
||||
case 'Service':
|
||||
case 'Application':
|
||||
self::update_actor( $array );
|
||||
break;
|
||||
// Object and Link Types
|
||||
// @see https://www.w3.org/TR/activitystreams-vocabulary/#object-types
|
||||
case 'Note':
|
||||
case 'Article':
|
||||
case 'Image':
|
||||
case 'Audio':
|
||||
case 'Video':
|
||||
case 'Event':
|
||||
case 'Document':
|
||||
self::update_interaction( $array, $user_id );
|
||||
break;
|
||||
// Minimal Activity
|
||||
// @see https://www.w3.org/TR/activitystreams-core/#example-1
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an Interaction
|
||||
*
|
||||
* @param array $activity The activity-object
|
||||
* @param int $user_id The id of the local blog-user
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function update_interaction( $activity, $user_id ) {
|
||||
$state = Interactions::update_comment( $activity );
|
||||
$reaction = null;
|
||||
|
||||
if ( $state && ! \is_wp_error( $reaction ) ) {
|
||||
$reaction = \get_comment( $state );
|
||||
}
|
||||
|
||||
\do_action( 'activitypub_handled_update', $activity, $user_id, $state, $reaction );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an Actor
|
||||
*
|
||||
* @param array $activity The activity-object
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function update_actor( $activity ) {
|
||||
// update cache
|
||||
get_remote_metadata_by_actor( $activity['actor'], false );
|
||||
|
||||
// @todo maybe also update all interactions
|
||||
}
|
||||
}
|
@ -11,8 +11,8 @@
|
||||
'<dd>' . \wp_kses( __( 'The post\'s title.', 'activitypub' ), array( 'code' => array() ) ) . '</dd>' .
|
||||
'<dt><code>[ap_content apply_filters="yes"]</code></dt>' .
|
||||
'<dd>' . \wp_kses( __( 'The post\'s content. With <code>apply_filters</code> you can decide if filters (<code>apply_filters( \'the_content\', $content )</code>) should be applied or not (default is <code>yes</code>). The values can be <code>yes</code> or <code>no</code>. <code>apply_filters</code> attribute is optional.', 'activitypub' ), array( 'code' => array() ) ) . '</dd>' .
|
||||
'<dt><code>[ap_excerpt lenght="400"]</code></dt>' .
|
||||
'<dd>' . \wp_kses( __( 'The post\'s excerpt (default 400 chars). <code>length</code> attribute is optional.', 'activitypub' ), array( 'code' => array() ) ) . '</dd>' .
|
||||
'<dt><code>[ap_excerpt length="400"]</code></dt>' .
|
||||
'<dd>' . \wp_kses( __( 'The post\'s excerpt (uses <code>the_excerpt</code> if that is set). If no excerpt is provided, will truncate at <code>length</code> (optional, default = 400).', 'activitypub' ), array( 'code' => array() ) ) . '</dd>' .
|
||||
'<dt><code>[ap_permalink type="url"]</code></dt>' .
|
||||
'<dd>' . \wp_kses( __( 'The post\'s permalink. <code>type</code> can be either: <code>url</code> or <code>html</code> (an <a /> tag). <code>type</code> attribute is optional.', 'activitypub' ), array( 'code' => array() ) ) . '</dd>' .
|
||||
'<dt><code>[ap_shortlink type="url"]</code></dt>' .
|
||||
|
@ -58,10 +58,6 @@ class Application_User extends Blog_User {
|
||||
return null;
|
||||
}
|
||||
|
||||
public function get_featured_tags() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public function get_featured() {
|
||||
return null;
|
||||
}
|
||||
@ -69,4 +65,8 @@ class Application_User extends Blog_User {
|
||||
public function get_moderators() {
|
||||
return null;
|
||||
}
|
||||
|
||||
public function get_indexable() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -315,7 +315,7 @@ class Follower extends Actor {
|
||||
$object->set_id( $post->guid );
|
||||
$object->set_name( $post->post_title );
|
||||
$object->set_summary( $post->post_excerpt );
|
||||
$object->set_published( gmdate( 'Y-m-d H:i:s', strtotime( $post->post_published ) ) );
|
||||
$object->set_published( gmdate( 'Y-m-d H:i:s', strtotime( $post->post_date ) ) );
|
||||
$object->set_updated( gmdate( 'Y-m-d H:i:s', strtotime( $post->post_modified ) ) );
|
||||
|
||||
return $object;
|
||||
|
@ -18,15 +18,6 @@ class User extends Actor {
|
||||
*/
|
||||
protected $_id; // phpcs:ignore PSR2.Classes.PropertyDeclaration.Underscore
|
||||
|
||||
/**
|
||||
* The Featured-Tags.
|
||||
*
|
||||
* @see https://docs.joinmastodon.org/spec/activitypub/#featuredTags
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $featured_tags;
|
||||
|
||||
/**
|
||||
* The Featured-Posts.
|
||||
*
|
||||
@ -235,15 +226,6 @@ class User extends Actor {
|
||||
return get_rest_url_by_path( sprintf( 'users/%d/collections/featured', $this->get__id() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Featured-Tags-API-Endpoint.
|
||||
*
|
||||
* @return string The Featured-Tags-Endpoint.
|
||||
*/
|
||||
public function get_featured_tags() {
|
||||
return get_rest_url_by_path( sprintf( 'users/%d/collections/tags', $this->get__id() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend the User-Output with Attachments.
|
||||
*
|
||||
|
@ -105,7 +105,7 @@ class Collection {
|
||||
'@context' => Activity::CONTEXT,
|
||||
'id' => get_rest_url_by_path( sprintf( 'users/%d/collections/tags', $user->get__id() ) ),
|
||||
'type' => 'Collection',
|
||||
'totalItems' => count( $tags ),
|
||||
'totalItems' => is_countable( $tags ) ? count( $tags ) : 0,
|
||||
'items' => array(),
|
||||
);
|
||||
|
||||
@ -117,7 +117,10 @@ class Collection {
|
||||
);
|
||||
}
|
||||
|
||||
return new WP_REST_Response( $response, 200 );
|
||||
$rest_response = new WP_REST_Response( $response, 200 );
|
||||
$rest_response->header( 'Content-Type', 'application/activity+json; charset=' . get_option( 'blog_charset' ) );
|
||||
|
||||
return $rest_response;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -160,7 +163,7 @@ class Collection {
|
||||
'@context' => Activity::CONTEXT,
|
||||
'id' => get_rest_url_by_path( sprintf( 'users/%d/collections/featured', $user_id ) ),
|
||||
'type' => 'OrderedCollection',
|
||||
'totalItems' => count( $posts ),
|
||||
'totalItems' => is_countable( $posts ) ? count( $posts ) : 0,
|
||||
'orderedItems' => array(),
|
||||
);
|
||||
|
||||
@ -168,7 +171,10 @@ class Collection {
|
||||
$response['orderedItems'][] = Post::transform( $post )->to_object()->to_array();
|
||||
}
|
||||
|
||||
return new WP_REST_Response( $response, 200 );
|
||||
$rest_response = new WP_REST_Response( $response, 200 );
|
||||
$rest_response->header( 'Content-Type', 'application/activity+json; charset=' . get_option( 'blog_charset' ) );
|
||||
|
||||
return $rest_response;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -192,7 +198,10 @@ class Collection {
|
||||
$response['orderedItems'][] = $user->get_url();
|
||||
}
|
||||
|
||||
return new WP_REST_Response( $response, 200 );
|
||||
$rest_response = new WP_REST_Response( $response, 200 );
|
||||
$rest_response->header( 'Content-Type', 'application/activity+json; charset=' . get_option( 'blog_charset' ) );
|
||||
|
||||
return $rest_response;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -103,10 +103,10 @@ class Followers {
|
||||
$data['followers']
|
||||
);
|
||||
|
||||
$response = new WP_REST_Response( $json, 200 );
|
||||
$response->header( 'Content-Type', 'application/activity+json' );
|
||||
$rest_response = new WP_REST_Response( $json, 200 );
|
||||
$rest_response->header( 'Content-Type', 'application/activity+json; charset=' . get_option( 'blog_charset' ) );
|
||||
|
||||
return $response;
|
||||
return $rest_response;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,6 +1,7 @@
|
||||
<?php
|
||||
namespace Activitypub\Rest;
|
||||
|
||||
use WP_REST_Response;
|
||||
use Activitypub\Collection\Users as User_Collection;
|
||||
|
||||
use function Activitypub\is_single_user;
|
||||
@ -74,15 +75,15 @@ class Following {
|
||||
|
||||
$items = apply_filters( 'activitypub_rest_following', array(), $user ); // phpcs:ignore
|
||||
|
||||
$json->totalItems = count( $items ); // phpcs:ignore
|
||||
$json->totalItems = is_countable( $items ) ? count( $items ) : 0; // phpcs:ignore
|
||||
$json->orderedItems = $items; // phpcs:ignore
|
||||
|
||||
$json->first = $json->partOf; // phpcs:ignore
|
||||
|
||||
$response = new \WP_REST_Response( $json, 200 );
|
||||
$response->header( 'Content-Type', 'application/activity+json' );
|
||||
$rest_response = new WP_REST_Response( $json, 200 );
|
||||
$rest_response->header( 'Content-Type', 'application/activity+json; charset=' . get_option( 'blog_charset' ) );
|
||||
|
||||
return $response;
|
||||
return $rest_response;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -11,6 +11,7 @@ use function Activitypub\get_context;
|
||||
use function Activitypub\url_to_authorid;
|
||||
use function Activitypub\get_rest_url_by_path;
|
||||
use function Activitypub\get_remote_metadata_by_actor;
|
||||
use function Activitypub\extract_recipients_from_activity;
|
||||
|
||||
/**
|
||||
* ActivityPub Inbox REST-Class
|
||||
@ -25,8 +26,6 @@ class Inbox {
|
||||
*/
|
||||
public static function init() {
|
||||
self::register_routes();
|
||||
|
||||
\add_action( 'activitypub_inbox_create', array( self::class, 'handle_create' ), 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -109,11 +108,10 @@ class Inbox {
|
||||
*/
|
||||
\do_action( 'activitypub_inbox_post' );
|
||||
|
||||
$response = new WP_REST_Response( $json, 200 );
|
||||
$rest_response = new WP_REST_Response( $json, 200 );
|
||||
$rest_response->header( 'Content-Type', 'application/activity+json; charset=' . get_option( 'blog_charset' ) );
|
||||
|
||||
$response->header( 'Content-Type', 'application/activity+json' );
|
||||
|
||||
return $response;
|
||||
return $rest_response;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -131,14 +129,18 @@ class Inbox {
|
||||
return $user;
|
||||
}
|
||||
|
||||
$data = $request->get_json_params();
|
||||
$type = $request->get_param( 'type' );
|
||||
$type = \strtolower( $type );
|
||||
$data = $request->get_json_params();
|
||||
$activity = Activity::init_from_array( $data );
|
||||
$type = $request->get_param( 'type' );
|
||||
$type = \strtolower( $type );
|
||||
|
||||
\do_action( 'activitypub_inbox', $data, $user->get__id(), $type );
|
||||
\do_action( "activitypub_inbox_{$type}", $data, $user->get__id() );
|
||||
\do_action( 'activitypub_inbox', $data, $user->get__id(), $type, $activity );
|
||||
\do_action( "activitypub_inbox_{$type}", $data, $user->get__id(), $activity );
|
||||
|
||||
return new WP_REST_Response( array(), 202 );
|
||||
$rest_response = new WP_REST_Response( array(), 202 );
|
||||
$rest_response->header( 'Content-Type', 'application/activity+json; charset=' . get_option( 'blog_charset' ) );
|
||||
|
||||
return $rest_response;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -149,16 +151,17 @@ class Inbox {
|
||||
* @return WP_REST_Response
|
||||
*/
|
||||
public static function shared_inbox_post( $request ) {
|
||||
$data = $request->get_json_params();
|
||||
$type = $request->get_param( 'type' );
|
||||
$users = self::extract_recipients( $data );
|
||||
$data = $request->get_json_params();
|
||||
$activity = Activity::init_from_array( $data );
|
||||
$type = $request->get_param( 'type' );
|
||||
$users = self::get_recipients( $data );
|
||||
|
||||
if ( ! $users ) {
|
||||
return new WP_Error(
|
||||
'rest_invalid_param',
|
||||
\__( 'No recipients found', 'activitypub' ),
|
||||
array(
|
||||
'status' => 404,
|
||||
'status' => 400,
|
||||
'params' => array(
|
||||
'to' => \__( 'Please check/validate "to" field', 'activitypub' ),
|
||||
'bto' => \__( 'Please check/validate "bto" field', 'activitypub' ),
|
||||
@ -179,11 +182,14 @@ class Inbox {
|
||||
|
||||
$type = \strtolower( $type );
|
||||
|
||||
\do_action( 'activitypub_inbox', $data, $user->ID, $type );
|
||||
\do_action( "activitypub_inbox_{$type}", $data, $user->ID );
|
||||
\do_action( 'activitypub_inbox', $data, $user->ID, $type, $activity );
|
||||
\do_action( "activitypub_inbox_{$type}", $data, $user->ID, $activity );
|
||||
}
|
||||
|
||||
return new WP_REST_Response( array(), 202 );
|
||||
$rest_response = new WP_REST_Response( array(), 202 );
|
||||
$rest_response->header( 'Content-Type', 'application/activity+json; charset=' . get_option( 'blog_charset' ) );
|
||||
|
||||
return $rest_response;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -231,8 +237,12 @@ class Inbox {
|
||||
$params['actor'] = array(
|
||||
'required' => true,
|
||||
'sanitize_callback' => function( $param, $request, $key ) {
|
||||
if ( ! \is_string( $param ) ) {
|
||||
$param = $param['id'];
|
||||
if ( \is_array( $param ) ) {
|
||||
if ( isset( $param['id'] ) ) {
|
||||
$param = $param['id'];
|
||||
} else {
|
||||
$param = $param['url'];
|
||||
}
|
||||
}
|
||||
return \esc_url_raw( $param );
|
||||
},
|
||||
@ -331,118 +341,6 @@ class Inbox {
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles "Create" requests
|
||||
*
|
||||
* @param array $object The activity-object
|
||||
* @param int $user_id The id of the local blog-user
|
||||
*/
|
||||
public static function handle_create( $object, $user_id ) {
|
||||
$meta = get_remote_metadata_by_actor( $object['actor'] );
|
||||
|
||||
if ( ! isset( $object['object']['inReplyTo'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check if Activity is public or not
|
||||
if ( ! self::is_activity_public( $object ) ) {
|
||||
// @todo maybe send email
|
||||
return;
|
||||
}
|
||||
|
||||
$comment_post_id = \url_to_postid( $object['object']['inReplyTo'] );
|
||||
|
||||
// save only replys and reactions
|
||||
if ( ! $comment_post_id ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$commentdata = array(
|
||||
'comment_post_ID' => $comment_post_id,
|
||||
'comment_author' => \esc_attr( $meta['name'] ),
|
||||
'comment_author_url' => \esc_url_raw( $object['actor'] ),
|
||||
'comment_content' => \wp_filter_kses( $object['object']['content'] ),
|
||||
'comment_type' => 'comment',
|
||||
'comment_author_email' => '',
|
||||
'comment_parent' => 0,
|
||||
'comment_meta' => array(
|
||||
'source_url' => \esc_url_raw( $object['object']['url'] ),
|
||||
'avatar_url' => \esc_url_raw( $meta['icon']['url'] ),
|
||||
'protocol' => 'activitypub',
|
||||
),
|
||||
);
|
||||
|
||||
// disable flood control
|
||||
\remove_action( 'check_comment_flood', 'check_comment_flood_db', 10 );
|
||||
|
||||
// do not require email for AP entries
|
||||
\add_filter( 'pre_option_require_name_email', '__return_false' );
|
||||
|
||||
// No nonce possible for this submission route
|
||||
\add_filter(
|
||||
'akismet_comment_nonce',
|
||||
function() {
|
||||
return 'inactive';
|
||||
}
|
||||
);
|
||||
|
||||
$state = \wp_new_comment( $commentdata, true );
|
||||
|
||||
\remove_filter( 'pre_option_require_name_email', '__return_false' );
|
||||
|
||||
// re-add flood control
|
||||
\add_action( 'check_comment_flood', 'check_comment_flood_db', 10, 4 );
|
||||
|
||||
do_action( 'activitypub_handled_create', $object, $user_id, $state, $commentdata );
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract recipient URLs from Activity object
|
||||
*
|
||||
* @param array $data
|
||||
*
|
||||
* @return array The list of user URLs
|
||||
*/
|
||||
public static function extract_recipients( $data ) {
|
||||
$recipient_items = array();
|
||||
|
||||
foreach ( array( 'to', 'bto', 'cc', 'bcc', 'audience' ) as $i ) {
|
||||
if ( array_key_exists( $i, $data ) ) {
|
||||
if ( is_array( $data[ $i ] ) ) {
|
||||
$recipient = $data[ $i ];
|
||||
} else {
|
||||
$recipient = array( $data[ $i ] );
|
||||
}
|
||||
$recipient_items = array_merge( $recipient_items, $recipient );
|
||||
}
|
||||
|
||||
if ( array_key_exists( $i, $data['object'] ) ) {
|
||||
if ( is_array( $data['object'][ $i ] ) ) {
|
||||
$recipient = $data['object'][ $i ];
|
||||
} else {
|
||||
$recipient = array( $data['object'][ $i ] );
|
||||
}
|
||||
$recipient_items = array_merge( $recipient_items, $recipient );
|
||||
}
|
||||
}
|
||||
|
||||
$recipients = array();
|
||||
|
||||
// flatten array
|
||||
foreach ( $recipient_items as $recipient ) {
|
||||
if ( is_array( $recipient ) ) {
|
||||
// check if recipient is an object
|
||||
if ( array_key_exists( 'id', $recipient ) ) {
|
||||
$recipients[] = $recipient['id'];
|
||||
}
|
||||
} else {
|
||||
$recipients[] = $recipient;
|
||||
}
|
||||
}
|
||||
|
||||
return array_unique( $recipients );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get local user recipients
|
||||
*
|
||||
@ -451,7 +349,7 @@ class Inbox {
|
||||
* @return array The list of local users
|
||||
*/
|
||||
public static function get_recipients( $data ) {
|
||||
$recipients = self::extract_recipients( $data );
|
||||
$recipients = extract_recipients_from_activity( $data );
|
||||
$users = array();
|
||||
|
||||
foreach ( $recipients as $recipient ) {
|
||||
@ -466,16 +364,4 @@ class Inbox {
|
||||
|
||||
return $users;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if passed Activity is Public
|
||||
*
|
||||
* @param array $data
|
||||
* @return boolean
|
||||
*/
|
||||
public static function is_activity_public( $data ) {
|
||||
$recipients = self::extract_recipients( $data );
|
||||
|
||||
return in_array( 'https://www.w3.org/ns/activitystreams#Public', $recipients, true );
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,8 @@ namespace Activitypub\Rest;
|
||||
|
||||
use WP_REST_Response;
|
||||
|
||||
use function Activitypub\get_total_users;
|
||||
use function Activitypub\get_active_users;
|
||||
use function Activitypub\get_rest_url_by_path;
|
||||
|
||||
/**
|
||||
@ -82,24 +84,14 @@ class Nodeinfo {
|
||||
'version' => \get_bloginfo( 'version' ),
|
||||
);
|
||||
|
||||
$users = \get_users(
|
||||
array(
|
||||
'capability__in' => array( 'publish_posts' ),
|
||||
)
|
||||
);
|
||||
|
||||
if ( is_array( $users ) ) {
|
||||
$users = count( $users );
|
||||
} else {
|
||||
$users = 1;
|
||||
}
|
||||
|
||||
$posts = \wp_count_posts();
|
||||
$comments = \wp_count_comments();
|
||||
|
||||
$nodeinfo['usage'] = array(
|
||||
'users' => array(
|
||||
'total' => $users,
|
||||
'total' => get_total_users(),
|
||||
'activeMonth' => get_active_users( '1 month ago' ),
|
||||
'activeHalfyear' => get_active_users( '6 month ago' ),
|
||||
),
|
||||
'localPosts' => (int) $posts->publish,
|
||||
'localComments' => (int) $comments->approved,
|
||||
@ -139,24 +131,14 @@ class Nodeinfo {
|
||||
'version' => \get_bloginfo( 'version' ),
|
||||
);
|
||||
|
||||
$users = \get_users(
|
||||
array(
|
||||
'capability__in' => array( 'publish_posts' ),
|
||||
)
|
||||
);
|
||||
|
||||
if ( is_array( $users ) ) {
|
||||
$users = count( $users );
|
||||
} else {
|
||||
$users = 1;
|
||||
}
|
||||
|
||||
$posts = \wp_count_posts();
|
||||
$comments = \wp_count_comments();
|
||||
|
||||
$nodeinfo['usage'] = array(
|
||||
'users' => array(
|
||||
'total' => (int) $users,
|
||||
'total' => get_total_users(),
|
||||
'activeMonth' => get_active_users( 1 ),
|
||||
'activeHalfyear' => get_active_users( 6 ),
|
||||
),
|
||||
'localPosts' => (int) $posts->publish,
|
||||
'localComments' => (int) $comments->approved,
|
||||
|
@ -123,11 +123,10 @@ class Outbox {
|
||||
*/
|
||||
\do_action( 'activitypub_outbox_post' );
|
||||
|
||||
$response = new WP_REST_Response( $json, 200 );
|
||||
$rest_response = new WP_REST_Response( $json, 200 );
|
||||
$rest_response->header( 'Content-Type', 'application/activity+json; charset=' . get_option( 'blog_charset' ) );
|
||||
|
||||
$response->header( 'Content-Type', 'application/activity+json' );
|
||||
|
||||
return $response;
|
||||
return $rest_response;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -2,6 +2,7 @@
|
||||
namespace Activitypub\Rest;
|
||||
|
||||
use stdClass;
|
||||
use WP_Error;
|
||||
use WP_REST_Response;
|
||||
use Activitypub\Signature;
|
||||
use Activitypub\Model\Application_User;
|
||||
@ -54,11 +55,10 @@ class Server {
|
||||
|
||||
$json = $user->to_array();
|
||||
|
||||
$response = new WP_REST_Response( $json, 200 );
|
||||
$rest_response = new WP_REST_Response( $json, 200 );
|
||||
$rest_response->header( 'Content-Type', 'application/activity+json; charset=' . get_option( 'blog_charset' ) );
|
||||
|
||||
$response->header( 'Content-Type', 'application/activity+json' );
|
||||
|
||||
return $response;
|
||||
return $rest_response;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -74,6 +74,10 @@ class Server {
|
||||
* @return mixed|WP_Error The response, error, or modified response.
|
||||
*/
|
||||
public static function authorize_activitypub_requests( $response, $handler, $request ) {
|
||||
if ( 'HEAD' === $request->get_method() ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$route = $request->get_route();
|
||||
|
||||
// check if it is an activitypub request and exclude webfinger and nodeinfo endpoints
|
||||
@ -85,18 +89,41 @@ class Server {
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter to defer signature verification
|
||||
*
|
||||
* Skip signature verification for debugging purposes or to reduce load for
|
||||
* certain Activity-Types, like "Delete".
|
||||
*
|
||||
* @param bool $defer Whether to defer signature verification.
|
||||
* @param WP_REST_Request $request The request used to generate the response.
|
||||
*
|
||||
* @return bool Whether to defer signature verification.
|
||||
*/
|
||||
$defer = \apply_filters( 'activitypub_defer_signature_verification', false, $request );
|
||||
|
||||
if ( $defer ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
// POST-Requets are always signed
|
||||
if ( 'get' !== \strtolower( $request->get_method() ) ) {
|
||||
if ( 'GET' !== $request->get_method() ) {
|
||||
$verified_request = Signature::verify_http_signature( $request );
|
||||
if ( \is_wp_error( $verified_request ) ) {
|
||||
return $verified_request;
|
||||
return new WP_Error(
|
||||
'activitypub_signature_verification',
|
||||
$verified_request->get_error_message(),
|
||||
array( 'status' => 401 )
|
||||
);
|
||||
}
|
||||
} elseif ( 'get' === \strtolower( $request->get_method() ) ) { // GET-Requests are only signed in secure mode
|
||||
if ( ACTIVITYPUB_AUTHORIZED_FETCH ) {
|
||||
$verified_request = Signature::verify_http_signature( $request );
|
||||
if ( \is_wp_error( $verified_request ) ) {
|
||||
return $verified_request;
|
||||
}
|
||||
} elseif ( 'GET' === $request->get_method() && ACTIVITYPUB_AUTHORIZED_FETCH ) { // GET-Requests are only signed in secure mode
|
||||
$verified_request = Signature::verify_http_signature( $request );
|
||||
if ( \is_wp_error( $verified_request ) ) {
|
||||
return new WP_Error(
|
||||
'activitypub_signature_verification',
|
||||
$verified_request->get_error_message(),
|
||||
array( 'status' => 401 )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -95,10 +95,10 @@ class Users {
|
||||
|
||||
$json = $user->to_array();
|
||||
|
||||
$response = new WP_REST_Response( $json, 200 );
|
||||
$response->header( 'Content-Type', 'application/activity+json' );
|
||||
$rest_response = new WP_REST_Response( $json, 200 );
|
||||
$rest_response->header( 'Content-Type', 'application/activity+json; charset=' . get_option( 'blog_charset' ) );
|
||||
|
||||
return $response;
|
||||
return $rest_response;
|
||||
}
|
||||
|
||||
|
||||
|
@ -113,6 +113,12 @@ class Webfinger {
|
||||
),
|
||||
);
|
||||
|
||||
if ( 'Group' === $user->get_type() ) {
|
||||
$profile['links'][0]['properties'] = array(
|
||||
'https://www.w3.org/ns/activitystreams#type' => 'Group',
|
||||
);
|
||||
}
|
||||
|
||||
return $profile;
|
||||
}
|
||||
}
|
||||
|
@ -30,19 +30,24 @@ class Followers extends WP_List_Table {
|
||||
|
||||
public function get_columns() {
|
||||
return array(
|
||||
'cb' => '<input type="checkbox" />',
|
||||
'avatar' => \__( 'Avatar', 'activitypub' ),
|
||||
'name' => \__( 'Name', 'activitypub' ),
|
||||
'username' => \__( 'Username', 'activitypub' ),
|
||||
'url' => \__( 'URL', 'activitypub' ),
|
||||
'updated' => \__( 'Last updated', 'activitypub' ),
|
||||
//'errors' => \__( 'Errors', 'activitypub' ),
|
||||
//'latest-error' => \__( 'Latest Error Message', 'activitypub' ),
|
||||
'cb' => '<input type="checkbox" />',
|
||||
'avatar' => \__( 'Avatar', 'activitypub' ),
|
||||
'post_title' => \__( 'Name', 'activitypub' ),
|
||||
'username' => \__( 'Username', 'activitypub' ),
|
||||
'url' => \__( 'URL', 'activitypub' ),
|
||||
'published' => \__( 'Followed', 'activitypub' ),
|
||||
'modified' => \__( 'Last updated', 'activitypub' ),
|
||||
);
|
||||
}
|
||||
|
||||
public function get_sortable_columns() {
|
||||
return array();
|
||||
$sortable_columns = array(
|
||||
'post_title' => array( 'post_title', true ),
|
||||
'modified' => array( 'modified', false ),
|
||||
'published' => array( 'published', false ),
|
||||
);
|
||||
|
||||
return $sortable_columns;
|
||||
}
|
||||
|
||||
public function prepare_items() {
|
||||
@ -55,8 +60,32 @@ class Followers extends WP_List_Table {
|
||||
$page_num = $this->get_pagenum();
|
||||
$per_page = 20;
|
||||
|
||||
$followers = FollowerCollection::get_followers( $this->user_id, $per_page, $page_num );
|
||||
$counter = FollowerCollection::count_followers( $this->user_id );
|
||||
$args = array();
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( isset( $_GET['orderby'] ) ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$args['orderby'] = sanitize_text_field( wp_unslash( $_GET['orderby'] ) );
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( isset( $_GET['order'] ) ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$args['order'] = sanitize_text_field( wp_unslash( $_GET['order'] ) );
|
||||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( isset( $_GET['s'] ) && isset( $_REQUEST['_wpnonce'] ) ) {
|
||||
$nonce = sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) );
|
||||
if ( wp_verify_nonce( $nonce, 'bulk-' . $this->_args['plural'] ) ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$args['s'] = sanitize_text_field( wp_unslash( $_GET['s'] ) );
|
||||
}
|
||||
}
|
||||
|
||||
$followers_with_count = FollowerCollection::get_followers_with_count( $this->user_id, $per_page, $page_num, $args );
|
||||
$followers = $followers_with_count['followers'];
|
||||
$counter = $followers_with_count['total'];
|
||||
|
||||
$this->items = array();
|
||||
$this->set_pagination_args(
|
||||
@ -69,14 +98,13 @@ class Followers extends WP_List_Table {
|
||||
|
||||
foreach ( $followers as $follower ) {
|
||||
$item = array(
|
||||
'icon' => esc_attr( $follower->get_icon_url() ),
|
||||
'name' => esc_attr( $follower->get_name() ),
|
||||
'username' => esc_attr( $follower->get_preferred_username() ),
|
||||
'url' => esc_attr( $follower->get_url() ),
|
||||
'identifier' => esc_attr( $follower->get_id() ),
|
||||
'updated' => esc_attr( $follower->get_updated() ),
|
||||
'errors' => $follower->count_errors(),
|
||||
'latest-error' => $follower->get_latest_error_message(),
|
||||
'icon' => esc_attr( $follower->get_icon_url() ),
|
||||
'post_title' => esc_attr( $follower->get_name() ),
|
||||
'username' => esc_attr( $follower->get_preferred_username() ),
|
||||
'url' => esc_attr( $follower->get_url() ),
|
||||
'identifier' => esc_attr( $follower->get_id() ),
|
||||
'published' => esc_attr( $follower->get_published() ),
|
||||
'modified' => esc_attr( $follower->get_updated() ),
|
||||
);
|
||||
|
||||
$this->items[] = $item;
|
||||
@ -116,11 +144,11 @@ class Followers extends WP_List_Table {
|
||||
}
|
||||
|
||||
public function process_action() {
|
||||
if ( ! isset( $_REQUEST['followers'] ) || ! isset( $_REQUEST['_apnonce'] ) ) {
|
||||
if ( ! isset( $_REQUEST['followers'] ) || ! isset( $_REQUEST['_wpnonce'] ) ) {
|
||||
return false;
|
||||
}
|
||||
$nonce = sanitize_text_field( wp_unslash( $_REQUEST['_apnonce'] ) );
|
||||
if ( ! wp_verify_nonce( $nonce, 'activitypub-followers-list' ) ) {
|
||||
$nonce = sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) );
|
||||
if ( ! wp_verify_nonce( $nonce, 'bulk-' . $this->_args['plural'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,7 @@ use WP_Post;
|
||||
use Activitypub\Collection\Users;
|
||||
use Activitypub\Model\Blog_User;
|
||||
use Activitypub\Activity\Base_Object;
|
||||
use Activitypub\Shortcodes;
|
||||
|
||||
use function Activitypub\esc_hashtag;
|
||||
use function Activitypub\is_single_user;
|
||||
@ -81,7 +82,7 @@ class Post {
|
||||
$object->set_content( $this->get_content() );
|
||||
$object->set_content_map(
|
||||
array(
|
||||
\strstr( \get_locale(), '_', true ) => $this->get_content(),
|
||||
$this->get_locale() => $this->get_content(),
|
||||
)
|
||||
);
|
||||
$path = sprintf( 'users/%d/followers', intval( $wp_post->post_author ) );
|
||||
@ -142,78 +143,64 @@ class Post {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Image Attachments for this Post, parsed from blocks.
|
||||
* @param int $max_images The maximum number of images to return.
|
||||
* @param array $image_ids The image IDs to append new IDs to.
|
||||
* Generates all Media Attachments for a Post.
|
||||
*
|
||||
* @return array The image IDs.
|
||||
*/
|
||||
protected function get_block_image_ids( $max_images, $image_ids = [] ) {
|
||||
$blocks = \parse_blocks( $this->wp_post->post_content );
|
||||
return self::get_image_ids_from_blocks( $blocks, $image_ids, $max_images );
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively get image IDs from blocks.
|
||||
* @param array $blocks The blocks to search for image IDs
|
||||
* @param array $image_ids The image IDs to append new IDs to
|
||||
* @param int $max_images The maximum number of images to return.
|
||||
*
|
||||
* @return array The image IDs.
|
||||
*/
|
||||
protected static function get_image_ids_from_blocks( $blocks, $image_ids, $max_images ) {
|
||||
foreach ( $blocks as $block ) {
|
||||
// recurse into inner blocks
|
||||
if ( ! empty( $block['innerBlocks'] ) ) {
|
||||
$image_ids = self::get_image_ids_from_blocks( $block['innerBlocks'], $image_ids, $max_images );
|
||||
}
|
||||
|
||||
switch ( $block['blockName'] ) {
|
||||
case 'core/image':
|
||||
case 'core/cover':
|
||||
if ( ! empty( $block['attrs']['id'] ) ) {
|
||||
$image_ids[] = $block['attrs']['id'];
|
||||
}
|
||||
break;
|
||||
case 'jetpack/slideshow':
|
||||
case 'jetpack/tiled-gallery':
|
||||
if ( ! empty( $block['attrs']['ids'] ) ) {
|
||||
$image_ids = array_merge( $image_ids, $block['attrs']['ids'] );
|
||||
}
|
||||
break;
|
||||
case 'jetpack/image-compare':
|
||||
if ( ! empty( $block['attrs']['beforeImageId'] ) ) {
|
||||
$image_ids[] = $block['attrs']['beforeImageId'];
|
||||
}
|
||||
if ( ! empty( $block['attrs']['afterImageId'] ) ) {
|
||||
$image_ids[] = $block['attrs']['afterImageId'];
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// we could be at or over max, stop unneeded work
|
||||
if ( count( $image_ids ) >= $max_images ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// still need to slice it because one gallery could knock us over the limit
|
||||
return \array_slice( $image_ids, 0, $max_images );
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates all Image Attachments for a Post.
|
||||
*
|
||||
* @return array The Image Attachments.
|
||||
* @return array The Attachments.
|
||||
*/
|
||||
protected function get_attachments() {
|
||||
$max_images = intval( \apply_filters( 'activitypub_max_image_attachments', \get_option( 'activitypub_max_image_attachments', ACTIVITYPUB_MAX_IMAGE_ATTACHMENTS ) ) );
|
||||
// Once upon a time we only supported images, but we now support audio/video as well.
|
||||
// We maintain the image-centric naming for backwards compatibility.
|
||||
$max_media = intval( \apply_filters( 'activitypub_max_image_attachments', \get_option( 'activitypub_max_image_attachments', ACTIVITYPUB_MAX_IMAGE_ATTACHMENTS ) ) );
|
||||
|
||||
$images = array();
|
||||
if ( site_supports_blocks() && \has_blocks( $this->wp_post->post_content ) ) {
|
||||
return $this->get_block_attachments( $max_media );
|
||||
}
|
||||
|
||||
return $this->get_classic_editor_images( $max_media );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get media attachments from blocks. They will be formatted as ActivityPub attachments, not as WP attachments.
|
||||
*
|
||||
* @param int $max_media The maximum number of attachments to return.
|
||||
*
|
||||
* @return array The attachments.
|
||||
*/
|
||||
protected function get_block_attachments( $max_media ) {
|
||||
// max media can't be negative or zero
|
||||
if ( $max_media <= 0 ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$id = $this->wp_post->ID;
|
||||
|
||||
$media_ids = array();
|
||||
|
||||
// list post thumbnail first if this post has one
|
||||
if ( \function_exists( 'has_post_thumbnail' ) && \has_post_thumbnail( $id ) ) {
|
||||
$media_ids[] = \get_post_thumbnail_id( $id );
|
||||
}
|
||||
|
||||
if ( $max_media > 0 ) {
|
||||
$blocks = \parse_blocks( $this->wp_post->post_content );
|
||||
$media_ids = self::get_media_ids_from_blocks( $blocks, $media_ids, $max_media );
|
||||
}
|
||||
|
||||
return \array_filter( \array_map( array( self::class, 'wp_attachment_to_activity_attachment' ), $media_ids ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get image attachments from the classic editor.
|
||||
* Note that audio/video attachments are only supported in the block editor.
|
||||
*
|
||||
* @param int $max_images The maximum number of images to return.
|
||||
*
|
||||
* @return array The attachments.
|
||||
*/
|
||||
protected function get_classic_editor_images( $max_images ) {
|
||||
// max images can't be negative or zero
|
||||
if ( $max_images <= 0 ) {
|
||||
return $images;
|
||||
return array();
|
||||
}
|
||||
|
||||
$id = $this->wp_post->ID;
|
||||
@ -227,68 +214,147 @@ class Post {
|
||||
}
|
||||
|
||||
if ( $max_images > 0 ) {
|
||||
// first try to get images that are actually in the post content
|
||||
if ( site_supports_blocks() && \has_blocks( $this->wp_post->post_content ) ) {
|
||||
$block_image_ids = $this->get_block_image_ids( $max_images, $image_ids );
|
||||
$image_ids = \array_merge( $image_ids, $block_image_ids );
|
||||
} else {
|
||||
// fallback to images attached to the post
|
||||
$query = new \WP_Query(
|
||||
array(
|
||||
'post_parent' => $id,
|
||||
'post_status' => 'inherit',
|
||||
'post_type' => 'attachment',
|
||||
'post_mime_type' => 'image',
|
||||
'order' => 'ASC',
|
||||
'orderby' => 'menu_order ID',
|
||||
'posts_per_page' => $max_images,
|
||||
)
|
||||
);
|
||||
foreach ( $query->get_posts() as $attachment ) {
|
||||
if ( ! \in_array( $attachment->ID, $image_ids, true ) ) {
|
||||
$image_ids[] = $attachment->ID;
|
||||
}
|
||||
$query = new \WP_Query(
|
||||
array(
|
||||
'post_parent' => $id,
|
||||
'post_status' => 'inherit',
|
||||
'post_type' => 'attachment',
|
||||
'post_mime_type' => 'image',
|
||||
'order' => 'ASC',
|
||||
'orderby' => 'menu_order ID',
|
||||
'posts_per_page' => $max_images,
|
||||
)
|
||||
);
|
||||
foreach ( $query->get_posts() as $attachment ) {
|
||||
if ( ! \in_array( $attachment->ID, $image_ids, true ) ) {
|
||||
$image_ids[] = $attachment->ID;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$image_ids = \array_unique( $image_ids );
|
||||
|
||||
// get URLs for each image
|
||||
foreach ( $image_ids as $id ) {
|
||||
$image_size = 'full';
|
||||
return \array_filter( \array_map( array( self::class, 'wp_attachment_to_activity_attachment' ), $image_ids ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the image URL returned for each post.
|
||||
*
|
||||
* @param array|false $thumbnail The image URL, or false if no image is available.
|
||||
* @param int $id The attachment ID.
|
||||
* @param string $image_size The image size to retrieve. Set to 'full' by default.
|
||||
*/
|
||||
$thumbnail = apply_filters(
|
||||
'activitypub_get_image',
|
||||
$this->get_image( $id, $image_size ),
|
||||
$id,
|
||||
$image_size
|
||||
);
|
||||
/**
|
||||
* Recursively get media IDs from blocks.
|
||||
* @param array $blocks The blocks to search for media IDs
|
||||
* @param array $media_ids The media IDs to append new IDs to
|
||||
* @param int $max_media The maximum number of media to return.
|
||||
*
|
||||
* @return array The image IDs.
|
||||
*/
|
||||
protected static function get_media_ids_from_blocks( $blocks, $media_ids, $max_media ) {
|
||||
|
||||
if ( $thumbnail ) {
|
||||
$mimetype = \get_post_mime_type( $id );
|
||||
$alt = \get_post_meta( $id, '_wp_attachment_image_alt', true );
|
||||
$image = array(
|
||||
'type' => 'Image',
|
||||
'url' => $thumbnail[0],
|
||||
'mediaType' => $mimetype,
|
||||
);
|
||||
foreach ( $blocks as $block ) {
|
||||
// recurse into inner blocks
|
||||
if ( ! empty( $block['innerBlocks'] ) ) {
|
||||
$media_ids = self::get_media_ids_from_blocks( $block['innerBlocks'], $media_ids, $max_media );
|
||||
}
|
||||
|
||||
if ( $alt ) {
|
||||
$image['name'] = $alt;
|
||||
}
|
||||
$images[] = $image;
|
||||
switch ( $block['blockName'] ) {
|
||||
case 'core/image':
|
||||
case 'core/cover':
|
||||
case 'core/audio':
|
||||
case 'core/video':
|
||||
case 'videopress/video':
|
||||
if ( ! empty( $block['attrs']['id'] ) ) {
|
||||
$media_ids[] = $block['attrs']['id'];
|
||||
}
|
||||
break;
|
||||
case 'jetpack/slideshow':
|
||||
case 'jetpack/tiled-gallery':
|
||||
if ( ! empty( $block['attrs']['ids'] ) ) {
|
||||
$media_ids = array_merge( $media_ids, $block['attrs']['ids'] );
|
||||
}
|
||||
break;
|
||||
case 'jetpack/image-compare':
|
||||
if ( ! empty( $block['attrs']['beforeImageId'] ) ) {
|
||||
$media_ids[] = $block['attrs']['beforeImageId'];
|
||||
}
|
||||
if ( ! empty( $block['attrs']['afterImageId'] ) ) {
|
||||
$media_ids[] = $block['attrs']['afterImageId'];
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// depupe
|
||||
$media_ids = \array_unique( $media_ids );
|
||||
|
||||
// stop doing unneeded work
|
||||
if ( count( $media_ids ) >= $max_media ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $images;
|
||||
// still need to slice it because one gallery could knock us over the limit
|
||||
return array_slice( $media_ids, 0, $max_media );
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a WordPress Attachment to an ActivityPub Attachment.
|
||||
*
|
||||
* @param int $id The Attachment ID.
|
||||
*
|
||||
* @return array The ActivityPub Attachment.
|
||||
*/
|
||||
public static function wp_attachment_to_activity_attachment( $id ) {
|
||||
$attachment = array();
|
||||
$mime_type = \get_post_mime_type( $id );
|
||||
$mime_type_parts = \explode( '/', $mime_type );
|
||||
// switching on image/audio/video
|
||||
switch ( $mime_type_parts[0] ) {
|
||||
case 'image':
|
||||
$image_size = 'full';
|
||||
|
||||
/**
|
||||
* Filter the image URL returned for each post.
|
||||
*
|
||||
* @param array|false $thumbnail The image URL, or false if no image is available.
|
||||
* @param int $id The attachment ID.
|
||||
* @param string $image_size The image size to retrieve. Set to 'full' by default.
|
||||
*/
|
||||
$thumbnail = apply_filters(
|
||||
'activitypub_get_image',
|
||||
self::get_image( $id, $image_size ),
|
||||
$id,
|
||||
$image_size
|
||||
);
|
||||
|
||||
if ( $thumbnail ) {
|
||||
$alt = \get_post_meta( $id, '_wp_attachment_image_alt', true );
|
||||
$image = array(
|
||||
'type' => 'Image',
|
||||
'url' => $thumbnail[0],
|
||||
'mediaType' => $mime_type,
|
||||
);
|
||||
|
||||
if ( $alt ) {
|
||||
$image['name'] = $alt;
|
||||
}
|
||||
$attachment = $image;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'audio':
|
||||
case 'video':
|
||||
$attachment = array(
|
||||
'type' => 'Document',
|
||||
'mediaType' => $mime_type,
|
||||
'url' => \wp_get_attachment_url( $id ),
|
||||
'name' => \get_the_title( $id ),
|
||||
);
|
||||
$meta = wp_get_attachment_metadata( $id );
|
||||
// height and width for videos
|
||||
if ( isset( $meta['width'] ) && isset( $meta['height'] ) ) {
|
||||
$attachment['width'] = $meta['width'];
|
||||
$attachment['height'] = $meta['height'];
|
||||
}
|
||||
// @todo: add `icon` support for audio/video attachments. Maybe use post thumbnail?
|
||||
break;
|
||||
}
|
||||
|
||||
return \apply_filters( 'activitypub_attachment', $attachment, $id );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -299,7 +365,7 @@ class Post {
|
||||
*
|
||||
* @return array|false Array of image data, or boolean false if no image is available.
|
||||
*/
|
||||
protected function get_image( $id, $image_size = 'full' ) {
|
||||
protected static function get_image( $id, $image_size = 'full' ) {
|
||||
/**
|
||||
* Hook into the image retrieval process. Before image retrieval.
|
||||
*
|
||||
@ -308,7 +374,7 @@ class Post {
|
||||
*/
|
||||
do_action( 'activitypub_get_image_pre', $id, $image_size );
|
||||
|
||||
$thumbnail = \wp_get_attachment_image_src( $id, $image_size );
|
||||
$image = \wp_get_attachment_image_src( $id, $image_size );
|
||||
|
||||
/**
|
||||
* Hook into the image retrieval process. After image retrieval.
|
||||
@ -318,7 +384,7 @@ class Post {
|
||||
*/
|
||||
do_action( 'activitypub_get_image_post', $id, $image_size );
|
||||
|
||||
return $thumbnail;
|
||||
return $image;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -334,6 +400,8 @@ class Post {
|
||||
return \ucfirst( \get_option( 'activitypub_object_type', 'note' ) );
|
||||
}
|
||||
|
||||
// Default to Article.
|
||||
$object_type = 'Article';
|
||||
$post_type = \get_post_type( $this->wp_post );
|
||||
switch ( $post_type ) {
|
||||
case 'post':
|
||||
@ -466,6 +534,8 @@ class Post {
|
||||
$post = $this->wp_post;
|
||||
$content = $this->get_post_content_template();
|
||||
|
||||
// Register our shortcodes just in time.
|
||||
Shortcodes::register();
|
||||
// Fill in the shortcodes.
|
||||
setup_postdata( $post );
|
||||
$content = do_shortcode( $content );
|
||||
@ -477,6 +547,9 @@ class Post {
|
||||
|
||||
$content = \apply_filters( 'activitypub_the_content', $content, $post );
|
||||
|
||||
// Don't need these any more, should never appear in a post.
|
||||
Shortcodes::unregister();
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
@ -509,4 +582,25 @@ class Post {
|
||||
protected function get_mentions() {
|
||||
return apply_filters( 'activitypub_extract_mentions', array(), $this->wp_post->post_content, $this->wp_post );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the locale of the post.
|
||||
*
|
||||
* @return string The locale of the post.
|
||||
*/
|
||||
public function get_locale() {
|
||||
$post_id = $this->wp_post->ID;
|
||||
$lang = \strtolower( \strtok( \get_locale(), '_-' ) );
|
||||
|
||||
/**
|
||||
* Filter the locale of the post.
|
||||
*
|
||||
* @param string $lang The locale of the post.
|
||||
* @param int $post_id The post ID.
|
||||
* @param WP_Post $post The post object.
|
||||
*
|
||||
* @return string The filtered locale of the post.
|
||||
*/
|
||||
return apply_filters( 'activitypub_post_locale', $lang, $post_id, $this->wp_post );
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,9 @@
|
||||
<?php
|
||||
namespace Activitypub\Integration;
|
||||
|
||||
use function Activitypub\get_total_users;
|
||||
use function Activitypub\get_active_users;
|
||||
|
||||
/**
|
||||
* Compatibility with the NodeInfo plugin
|
||||
*
|
||||
@ -31,6 +34,12 @@ class Nodeinfo {
|
||||
$nodeinfo['protocols']['outbound'][] = 'activitypub';
|
||||
}
|
||||
|
||||
$nodeinfo['usage']['users'] = array(
|
||||
'total' => get_total_users(),
|
||||
'activeMonth' => get_active_users( '1 month ago' ),
|
||||
'activeHalfyear' => get_active_users( '6 month ago' ),
|
||||
);
|
||||
|
||||
return $nodeinfo;
|
||||
}
|
||||
|
||||
@ -44,6 +53,12 @@ class Nodeinfo {
|
||||
public static function add_nodeinfo2_discovery( $nodeinfo ) {
|
||||
$nodeinfo['protocols'][] = 'activitypub';
|
||||
|
||||
$nodeinfo['usage']['users'] = array(
|
||||
'total' => get_total_users(),
|
||||
'activeMonth' => get_active_users( '1 month ago' ),
|
||||
'activeHalfyear' => get_active_users( '6 month ago' ),
|
||||
);
|
||||
|
||||
return $nodeinfo;
|
||||
}
|
||||
}
|
||||
|
@ -30,6 +30,10 @@ class Webfinger {
|
||||
public static function add_user_discovery( $array, $resource, $user ) {
|
||||
$user = User_Collection::get_by_id( $user->ID );
|
||||
|
||||
if ( ! $user || is_wp_error( $user ) ) {
|
||||
return $array;
|
||||
}
|
||||
|
||||
$array['links'][] = array(
|
||||
'rel' => 'self',
|
||||
'type' => 'application/activity+json',
|
||||
|
@ -2,8 +2,8 @@
|
||||
Contributors: automattic, pfefferle, mediaformat, mattwiebe, akirk, jeherve, nuriapena, cavalierlife
|
||||
Tags: OStatus, fediverse, activitypub, activitystream
|
||||
Requires at least: 4.7
|
||||
Tested up to: 6.3
|
||||
Stable tag: 1.0.7
|
||||
Tested up to: 6.4
|
||||
Stable tag: 1.3.0
|
||||
Requires PHP: 5.6
|
||||
License: MIT
|
||||
License URI: http://opensource.org/licenses/MIT
|
||||
@ -105,6 +105,54 @@ Where 'blog' is the path to the subdirectory at which your blog resides.
|
||||
|
||||
Project maintained on GitHub at [automattic/wordpress-activitypub](https://github.com/automattic/wordpress-activitypub).
|
||||
|
||||
= 1.3.0 =
|
||||
|
||||
* Added: Threaded-Comments support
|
||||
* Improved: alt text for avatars in Follow Me/Followers blocks
|
||||
* Improved: `Delete`, `Update` and `Follow` Activities
|
||||
* Improved: better/more effective handling of `Delete` Activities
|
||||
* Improved: allow `<p />` and `<br />` for Comments
|
||||
* Fixed: removed default limit of WP_Query to send updates to all Inboxes and not only to the first 10
|
||||
|
||||
= 1.2.0 =
|
||||
|
||||
* Add: Search and order followerer lists
|
||||
* Add: Have a filter to defer signature verification
|
||||
* Improved: "Follow Me" styles for dark themes
|
||||
* Improved: Allow `p` and `br` tags only for AP comments
|
||||
* Fixed: Deduplicate attachments earlier to prevent incorrect max_media
|
||||
|
||||
|
||||
= 1.1.0 =
|
||||
|
||||
* Improved: audio and video attachments are now supported!
|
||||
* Improved: better error messages if remote profile is not accessible
|
||||
* Improved: PHP 8.1 compatibility
|
||||
* Fixed: don't try to parse mentions or hashtags for very large (>1MB) posts to prevent timeouts
|
||||
* Fixed: better handling of ISO-639-1 locale codes
|
||||
* Improved: more reliable [ap_author], props @uk3
|
||||
* Improved: NodeInfo statistics
|
||||
|
||||
= 1.0.10 =
|
||||
|
||||
* Improved: better error messages if remote profile is not accessible
|
||||
|
||||
= 1.0.9 =
|
||||
|
||||
* Fixed: broken following endpoint
|
||||
|
||||
= 1.0.8 =
|
||||
|
||||
* Fixed: blocking of HEAD requests
|
||||
* Fixed: PHP fatal error
|
||||
* Fixed: several typos
|
||||
* Fixed: error codes
|
||||
* Improved: loading of shortcodes
|
||||
* Updated: caching of followers
|
||||
* Updated: Application-User is no longer "indexable"
|
||||
* Updated: more consistent usage of the `application/activity+json` Content-Type
|
||||
* Removed: featured tags endpoint
|
||||
|
||||
= 1.0.7 =
|
||||
|
||||
* Fixed: broken function call
|
||||
|
@ -21,8 +21,8 @@ $followers_template = _n( 'Your blog profile currently has %s follower.', 'Your
|
||||
<input type="hidden" name="tab" value="followers" />
|
||||
<?php
|
||||
$table->prepare_items();
|
||||
$table->search_box( 'Search', 'search' );
|
||||
$table->display();
|
||||
?>
|
||||
<?php wp_nonce_field( 'activitypub-followers-list', '_apnonce' ); ?>
|
||||
</form>
|
||||
</div>
|
||||
|
@ -93,7 +93,7 @@
|
||||
<?php \esc_html_e( 'Excerpt', 'activitypub' ); ?>
|
||||
-
|
||||
<span class="description">
|
||||
<?php \esc_html_e( 'A content summary, shortened to 400 characters and without markup.', 'activitypub' ); ?>
|
||||
<?php \esc_html_e( 'A content summary without markup (truncated if no excerpt is provided).', 'activitypub' ); ?>
|
||||
</span>
|
||||
</label>
|
||||
</p>
|
||||
@ -125,7 +125,7 @@
|
||||
<ul>
|
||||
<li><code>[ap_title]</code> - <?php \esc_html_e( 'The post\'s title.', 'activitypub' ); ?></li>
|
||||
<li><code>[ap_content]</code> - <?php \esc_html_e( 'The post\'s content.', 'activitypub' ); ?></li>
|
||||
<li><code>[ap_excerpt]</code> - <?php \esc_html_e( 'The post\'s excerpt (default 400 chars).', 'activitypub' ); ?></li>
|
||||
<li><code>[ap_excerpt]</code> - <?php \esc_html_e( 'The post\'s excerpt (may be truncated).', 'activitypub' ); ?></li>
|
||||
<li><code>[ap_permalink]</code> - <?php \esc_html_e( 'The post\'s permalink.', 'activitypub' ); ?></li>
|
||||
<li><code>[ap_shortlink]</code> - <?php echo \wp_kses( \__( 'The post\'s shortlink. I can recommend <a href="https://wordpress.org/plugins/hum/" target="_blank">Hum</a>.', 'activitypub' ), 'default' ); ?></li>
|
||||
<li><code>[ap_hashtags]</code> - <?php \esc_html_e( 'The post\'s tags as hashtags.', 'activitypub' ); ?></li>
|
||||
@ -138,7 +138,7 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<?php \esc_html_e( 'Number of images', 'activitypub' ); ?>
|
||||
<?php \esc_html_e( 'Media attachments', 'activitypub' ); ?>
|
||||
</th>
|
||||
<td>
|
||||
<input value="<?php echo esc_attr( \get_option( 'activitypub_max_image_attachments', ACTIVITYPUB_MAX_IMAGE_ATTACHMENTS ) ); ?>" name="activitypub_max_image_attachments" id="activitypub_max_image_attachments" type="number" min="0" />
|
||||
@ -147,13 +147,20 @@
|
||||
echo \wp_kses(
|
||||
\sprintf(
|
||||
// translators:
|
||||
\__( 'The number of images to attach to posts. Default: <code>%s</code>', 'activitypub' ),
|
||||
\__( 'The number of media (images, audio, video) to attach to posts. Default: <code>%s</code>', 'activitypub' ),
|
||||
\esc_html( ACTIVITYPUB_MAX_IMAGE_ATTACHMENTS )
|
||||
),
|
||||
'default'
|
||||
);
|
||||
?>
|
||||
</p>
|
||||
<p class="description">
|
||||
<em>
|
||||
<?php
|
||||
esc_html_e( 'Note: audio and video attachments are only supported from Block Editor.', 'activitypub' );
|
||||
?>
|
||||
</em>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
|
@ -14,8 +14,8 @@ $followers_template = _n( 'Your author profile currently has %s follower.', 'You
|
||||
<input type="hidden" name="page" value="activitypub-followers-list" />
|
||||
<?php
|
||||
$table->prepare_items();
|
||||
$table->search_box( 'Search', 'search' );
|
||||
$table->display();
|
||||
?>
|
||||
<?php wp_nonce_field( 'activitypub-followers-list', '_apnonce' ); ?>
|
||||
</form>
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user