installed plugin Event Bridge for ActivityPub
version 1.1.0
@ -0,0 +1,24 @@
|
||||
name: Plugin asset/readme update on WordPress.org
|
||||
|
||||
on: workflow_dispatch
|
||||
|
||||
jobs:
|
||||
trunk:
|
||||
name: Push assets to trunk on WordPress.org
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: https://code.forgejo.org/actions/checkout@v4
|
||||
|
||||
- name: Install and cache rsync
|
||||
uses: https://github.com/awalsh128/cache-apt-pkgs-action@latest
|
||||
with:
|
||||
packages: rsync subversion
|
||||
version: 1.0
|
||||
|
||||
- name: WordPress.org plugin asset/readme update
|
||||
uses: https://github.com/10up/action-wordpress-plugin-asset-update@stable
|
||||
env:
|
||||
SVN_PASSWORD: ${{ secrets.SVN_PASSWORD }}
|
||||
SVN_USERNAME: ${{ secrets.SVN_USERNAME }}
|
||||
SLUG: event-bridge-for-activitypub
|
@ -0,0 +1,26 @@
|
||||
name: Deploy to WordPress.org
|
||||
|
||||
on:
|
||||
push:
|
||||
tags: '[0-9]+.[0-9]+.[0-9]+'
|
||||
|
||||
jobs:
|
||||
tag:
|
||||
name: Deploy tagged release on WordPress.org
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: https://code.forgejo.org/actions/checkout@v4
|
||||
|
||||
- name: Install and cache rsync and subversion
|
||||
uses: https://github.com/awalsh128/cache-apt-pkgs-action@latest
|
||||
with:
|
||||
packages: rsync subversion
|
||||
version: 1.0
|
||||
|
||||
- name: WordPress Plugin Deploy
|
||||
uses: https://github.com/10up/action-wordpress-plugin-deploy@stable
|
||||
env:
|
||||
SVN_PASSWORD: ${{ secrets.SVN_PASSWORD }}
|
||||
SVN_USERNAME: ${{ secrets.SVN_USERNAME }}
|
||||
SLUG: event-bridge-for-activitypub
|
@ -0,0 +1,61 @@
|
||||
name: PHP Code Checker
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
phpcs:
|
||||
name: PHP Code Checker
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
php-versions: ['8.1']
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: https://code.forgejo.org/actions/checkout@v4
|
||||
|
||||
- name: Cache Composer
|
||||
id: cache-composer
|
||||
uses: https://code.forgejo.org/actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
./vendor/
|
||||
key: cache-composer-3
|
||||
|
||||
- name: Setup cache environment for PHP Extensions
|
||||
id: php-extensions-cache
|
||||
uses: https://github.com/shivammathur/cache-extensions@v1
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
extensions: dom, iconv, json, libxml, zip
|
||||
key: php-extensions-cache-v1
|
||||
|
||||
- name: Cache PHP extensions
|
||||
uses: https://code.forgejo.org/actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.php-extensions-cache.outputs.dir }}
|
||||
key: ${{ steps.php-extensions-cache.outputs.key }}
|
||||
restore-keys: ${{ steps.php-extensions-cache.outputs.key }}
|
||||
|
||||
- name: Setup PHP
|
||||
uses: https://github.com/shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
extensions: dom, iconv, json, libxml, zip
|
||||
coverage: none
|
||||
tools: composer, cs2pr
|
||||
env:
|
||||
runner: self-hosted
|
||||
GITHUB_TOKEN: ${{ env.COMPOSER_TOKEN }}
|
||||
|
||||
- name: Install Composer dependencies for PHP
|
||||
if: steps.cache-composer.outputs.cache-hit != 'true'
|
||||
uses: ramsey/composer-install@v3
|
||||
env:
|
||||
COMPOSER_AUTH: '{"github-oauth": {"github.com": "${{ secrets.COMPOSER_TOKEN }}"}}'
|
||||
|
||||
- name: Run PHP_CodeSniffer
|
||||
run: ./vendor/bin/phpcs
|
@ -0,0 +1,87 @@
|
||||
name: PHPStan Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
WP_TESTS_DIR: /workspace/wordpress-test-lib
|
||||
WP_CORE_DIR: /workspace/wordpress
|
||||
|
||||
jobs:
|
||||
phpstan:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
php-version: ['7.4','8.1','8.4']
|
||||
name: PHPStan for WordPress ${{ matrix.php-version }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: https://code.forgejo.org/actions/checkout@v4
|
||||
|
||||
- name: Cache WordPress Setup and installed plugins (Event plugins and ActivityPub plugin).
|
||||
id: cache-wordpress
|
||||
uses: https://code.forgejo.org/actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
${{ env.WP_CORE_DIR }}
|
||||
${{ env.WP_TESTS_DIR }}
|
||||
key: cache-phpstan-wordpress-6.7-activitypub-5.1.0-2
|
||||
|
||||
- name: Cache Composer
|
||||
id: cache-composer
|
||||
uses: https://code.forgejo.org/actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
./vendor/
|
||||
key: cache-composer-3
|
||||
|
||||
- name: Setup cache environment for PHP Extensions
|
||||
id: php-extensions-cache
|
||||
uses: https://github.com/shivammathur/cache-extensions@v1
|
||||
with:
|
||||
php-version: ${{ matrix.php-version }}
|
||||
extensions: dom, iconv, json, libxml, zip
|
||||
key: php-extensions-cache-v1
|
||||
|
||||
- name: Cache PHP extensions
|
||||
uses: https://code.forgejo.org/actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.php-extensions-cache.outputs.dir }}
|
||||
key: ${{ steps.php-extensions-cache.outputs.key }}
|
||||
restore-keys: ${{ steps.php-extensions-cache.outputs.key }}
|
||||
|
||||
- name: Setup PHP
|
||||
uses: https://github.com/shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php-version }}
|
||||
extensions: dom, iconv, json, libxml, zip
|
||||
coverage: none
|
||||
tools: composer, cs2pr
|
||||
env:
|
||||
runner: self-hosted
|
||||
GITHUB_TOKEN: ${{ env.COMPOSER_TOKEN }}
|
||||
|
||||
- name: Install Composer dependencies for PHP
|
||||
if: steps.cache-composer.outputs.cache-hit != 'true'
|
||||
uses: ramsey/composer-install@v3
|
||||
env:
|
||||
COMPOSER_AUTH: '{"github-oauth": {"github.com": "${{ secrets.COMPOSER_TOKEN }}"}}'
|
||||
|
||||
- name: Log debug information
|
||||
run: |
|
||||
git --version
|
||||
php --version
|
||||
composer --version
|
||||
|
||||
- name: Install third party plugins, so that PHP Stan finds that functions
|
||||
if: steps.cache-wordpress.outputs.cache-hit != 'true'
|
||||
run: bash bin/install-wp-tests.sh wordpress_test root root 127.0.0.1 latest true false false true
|
||||
|
||||
- name: Running PHPStan Analyze
|
||||
if: ${{ success() || failure() }}
|
||||
run: |
|
||||
./vendor/bin/phpstan --version
|
||||
./vendor/bin/phpstan analyze -vv --memory-limit=2G --error-format=checkstyle | cs2pr
|
@ -0,0 +1,147 @@
|
||||
name: PHPUnit
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
WP_TESTS_DIR: /workspace/wordpress-test-lib
|
||||
WP_CORE_DIR: /workspace/wordpress
|
||||
|
||||
jobs:
|
||||
phpunit:
|
||||
runs-on: ubuntu-latest
|
||||
services:
|
||||
mysql:
|
||||
image: mariadb
|
||||
env:
|
||||
MYSQL_ROOT_PASSWORD: root
|
||||
strategy:
|
||||
matrix:
|
||||
php-version: ['7.4', '8.1', '8.3', '8.4']
|
||||
wordpress-version: ['6.7', '6.8-RC3']
|
||||
name: "PHPUnit: PHP ${{ matrix.php-version }} - WordPress ${{ matrix.wordpress-version }}"
|
||||
continue-on-error: true
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: https://code.forgejo.org/actions/checkout@v4
|
||||
|
||||
- name: Cache WordPress Setup and installed plugins (Event plugins and ActivityPub plugin).
|
||||
id: cache-wordpress
|
||||
uses: https://code.forgejo.org/actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
${{ env.WP_CORE_DIR }}
|
||||
${{ env.WP_TESTS_DIR }}
|
||||
key: cache-phpunit-wordpress-${{ matrix.wordpress-version }}-activitypub-5.7.0
|
||||
|
||||
- name: Cache Composer
|
||||
id: cache-composer
|
||||
uses: https://code.forgejo.org/actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
./vendor/
|
||||
key: cache-composer-3
|
||||
|
||||
- name: Setup cache environment for PHP Extensions
|
||||
id: php-extensions-cache
|
||||
uses: https://github.com/shivammathur/cache-extensions@v1
|
||||
with:
|
||||
php-version: ${{ matrix.php-version }}
|
||||
extensions: dom, iconv, json, libxml, zip
|
||||
key: php-extensions-cache-v1
|
||||
|
||||
- name: Cache PHP extensions
|
||||
uses: https://code.forgejo.org/actions/cache@v4
|
||||
with:
|
||||
path: ${{ steps.php-extensions-cache.outputs.dir }}
|
||||
key: ${{ steps.php-extensions-cache.outputs.key }}
|
||||
restore-keys: ${{ steps.php-extensions-cache.outputs.key }}
|
||||
|
||||
- name: Setup PHP
|
||||
uses: https://github.com/shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php-version }}
|
||||
extensions: dom, iconv, json, libxml, zip
|
||||
coverage: none
|
||||
tools: composer, cs2pr
|
||||
env:
|
||||
runner: self-hosted
|
||||
GITHUB_TOKEN: ${{ secrets.COMPOSER_TOKEN }}
|
||||
|
||||
- name: Install Composer dependencies for PHP
|
||||
if: steps.cache-composer.outputs.cache-hit != 'true'
|
||||
uses: ramsey/composer-install@v3
|
||||
env:
|
||||
COMPOSER_AUTH: '{"github-oauth": {"github.com": "${{ secrets.COMPOSER_TOKEN }}"}}'
|
||||
|
||||
- name: Install and cache mysqladmin needed to initialize the test database
|
||||
uses: https://github.com/awalsh128/cache-apt-pkgs-action@latest
|
||||
with:
|
||||
packages: mysql-client
|
||||
version: 1.0
|
||||
|
||||
- name: Setup Test Environment
|
||||
if: steps.cache-wordpress.outputs.cache-hit != 'true'
|
||||
run: bash bin/install-wp-tests.sh wordpress_test root root 127.0.0.1 ${{ matrix.wordpress-version }} false false false false
|
||||
|
||||
- name: Initialize WordPress test database
|
||||
if: steps.cache-wordpress.outputs.cache-hit != 'false'
|
||||
run: bash bin/install-wp-tests.sh wordpress_test root root 127.0.0.1 ${{ matrix.wordpress-version }} false true true true
|
||||
|
||||
- name: Run General Tests
|
||||
run: cd /workspace/Event-Federation/wordpress-event-bridge-for-activitypub/ && ./vendor/bin/phpunit
|
||||
env:
|
||||
PHP_VERSION: ${{ matrix.php-version }}
|
||||
|
||||
- name: Run Integration tests for The Events Calendar
|
||||
run: cd /workspace/Event-Federation/wordpress-event-bridge-for-activitypub/ && ./vendor/bin/phpunit --filter=the_events_calendar
|
||||
env:
|
||||
PHP_VERSION: ${{ matrix.php-version }}
|
||||
|
||||
- name: Run Integration tests for VS Event List
|
||||
run: cd /workspace/Event-Federation/wordpress-event-bridge-for-activitypub/ && ./vendor/bin/phpunit --filter=vs_event_list
|
||||
env:
|
||||
PHP_VERSION: ${{ matrix.php-version }}
|
||||
|
||||
- name: Run Integration tests for GatherPress
|
||||
run: cd /workspace/Event-Federation/wordpress-event-bridge-for-activitypub/ && ./vendor/bin/phpunit --filter=gatherpress
|
||||
env:
|
||||
PHP_VERSION: ${{ matrix.php-version }}
|
||||
|
||||
- name: Run Integration tests for Events Manager
|
||||
run: cd /workspace/Event-Federation/wordpress-event-bridge-for-activitypub/ && ./vendor/bin/phpunit --filter=events_manager
|
||||
env:
|
||||
PHP_VERSION: ${{ matrix.php-version }}
|
||||
|
||||
- name: Run Integration tests for WP Event Manager
|
||||
run: cd /workspace/Event-Federation/wordpress-event-bridge-for-activitypub/ && ./vendor/bin/phpunit --filter=wp_event_manager
|
||||
env:
|
||||
PHP_VERSION: ${{ matrix.php-version }}
|
||||
|
||||
- name: Run Integration tests for Eventin (WP Event Solution)
|
||||
run: cd /workspace/Event-Federation/wordpress-event-bridge-for-activitypub/ && ./vendor/bin/phpunit --filter=eventin
|
||||
env:
|
||||
PHP_VERSION: ${{ matrix.php-version }}
|
||||
|
||||
- name: Run Integration tests for Modern Events Calendar Lite
|
||||
run: cd /workspace/Event-Federation/wordpress-event-bridge-for-activitypub/ && ./vendor/bin/phpunit --filter=modern_events_calendar_lite
|
||||
env:
|
||||
PHP_VERSION: ${{ matrix.php-version }}
|
||||
|
||||
- name: Run Integration tests for EventPrime
|
||||
run: cd /workspace/Event-Federation/wordpress-event-bridge-for-activitypub/ && ./vendor/bin/phpunit --filter=eventprime
|
||||
env:
|
||||
PHP_VERSION: ${{ matrix.php-version }}
|
||||
|
||||
- name: Run Integration tests for Event Organiser
|
||||
run: cd /workspace/Event-Federation/wordpress-event-bridge-for-activitypub/ && ./vendor/bin/phpunit --filter=event_organiser
|
||||
env:
|
||||
PHP_VERSION: ${{ matrix.php-version }}
|
||||
|
||||
- name: Run Integration tests for EventON – Events Calendar
|
||||
run: cd /workspace/Event-Federation/wordpress-event-bridge-for-activitypub/ && ./vendor/bin/phpunit --filter=eventon
|
||||
env:
|
||||
PHP_VERSION: ${{ matrix.php-version }}
|
@ -0,0 +1,17 @@
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?$'
|
||||
|
||||
jobs:
|
||||
upload-release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: https://code.forgejo.org/actions/checkout@v4
|
||||
|
||||
- uses: https://code.forgejo.org/actions/forgejo-release@v2
|
||||
with:
|
||||
direction: upload
|
||||
url: https://codeberg.org
|
||||
release-notes: ${{ TAG }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
26
wp-content/plugins/event-bridge-for-activitypub/.gitattributes
vendored
Normal file
@ -0,0 +1,26 @@
|
||||
.distignore export-ignore
|
||||
.forgejo export-ignore
|
||||
.gitattributes export-ignore
|
||||
.gitignore export-ignore
|
||||
.wordpress-org export-ignore
|
||||
.wp-env.json export-ignore
|
||||
bin export-ignore
|
||||
CODE_OF_CONDUCT.md export-ignore
|
||||
composer.json export-ignore
|
||||
composer.lock export-ignore
|
||||
docker-compose.yml export-ignore
|
||||
Dockerfile export-ignore
|
||||
docs export-ignore
|
||||
FEDERATION.md export-ignore
|
||||
Gruntfile.js export-ignore
|
||||
node_modules export-ignore
|
||||
package.json export-ignore
|
||||
package-lock.json export-ignore
|
||||
phpcs.xml export-ignore
|
||||
phpstan.neon export-ignore
|
||||
phpstan.neon.dist export-ignore
|
||||
phpunit.xml export-ignore
|
||||
README.md export-ignore
|
||||
src export-ignore
|
||||
tests export-ignore
|
||||
vendor export-ignore
|
48
wp-content/plugins/event-bridge-for-activitypub/CHANGELOG.md
Normal file
@ -0,0 +1,48 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [1.1.0] - 2025-04-12
|
||||
|
||||
### Added
|
||||
|
||||
* Basic support for Starter Kits
|
||||
|
||||
### Fixed
|
||||
|
||||
* Fixed: Uncatched error in following process (issue #145)
|
||||
* Fixed: Compatibility with ActivityPub plugin version 5.7.0
|
||||
|
||||
## [1.0.0] - 2025-02-11
|
||||
|
||||
### Changed
|
||||
|
||||
* This plugin now depends on ActivityPub plugin version greater than 5.1.0
|
||||
|
||||
### Added
|
||||
|
||||
* Support for the EventPrime event plugin
|
||||
* Event self-announce feature at configurable time before event starts
|
||||
* Blueprint (Preview via WordPress Playground)
|
||||
* Event Sources feature: cache and list events from remote ActivityPub profiles on your site
|
||||
* Custom ActivityPub preview
|
||||
* Admin setting to enforce sending summary of events as plain text
|
||||
|
||||
### Fixed
|
||||
|
||||
* The Events Calendar date times when using the Gutenberg editor.
|
||||
* Improved admin UI for event-category mapping
|
||||
|
||||
## [0.3.5] - 2025-01-03
|
||||
|
||||
### Fixed
|
||||
|
||||
* Images of Acknowledgements in Admin UI
|
||||
|
||||
## [0.3.4] - 2024-12-21
|
||||
|
||||
* Initial release on WordPress.org
|
||||
|
@ -0,0 +1,44 @@
|
||||
# Contributing
|
||||
|
||||
Thank you for considering contributing to Event Bridge For ActivityPub WordPress Plugin 💜
|
||||
|
||||
You can contribute in the following ways:
|
||||
|
||||
- Finding and reporting bugs
|
||||
- Translating the plugin into various languages
|
||||
- Contributing code by fixing bugs or implementing features
|
||||
- Improving the documentation
|
||||
- Proposing ideas for new features
|
||||
|
||||
## Bug reports
|
||||
|
||||
Bug reports and feature suggestions must use descriptive and concise titles and be submitted to [Codeberg Issues](https://codeberg.org/Event-Federation/wordpress-event-bridge-for-activitypub/issues) or the [WordPress.org Support Forum](https://wordpress.org/support/plugin/event-bridge-for-activitypub/). Please use the search function to make sure that you are not submitting duplicates, and that a similar report or request has not already been resolved or rejected.
|
||||
|
||||
## Translations
|
||||
|
||||
You can submit translations via [translate.WordPress.org](https://translate.wordpress.org/projects/wp-plugins/event-bridge-for-activitypub/). These changes are merged by WordPress itself to each plugin installation.
|
||||
|
||||
## Pull requests
|
||||
|
||||
**Please use clean, concise titles for your pull requests.** Unless the pull request is about refactoring code or other internal tasks, assume that the person reading the pull request title is not a programmer or WordPress developer, but instead a WordPress user, and **try to describe your change or fix from their perspective**. We use commit squashing, so the final commit in the main branch will carry the title of the pull request, and commits from the main branch are fed into the changelog. The changelog is separated into [keepachangelog.com categories](https://keepachangelog.com/en/1.0.0/), and while that spec does not prescribe how the entries ought to be named, for easier sorting, start your pull request titles using one of the verbs "Add", "Change", "Deprecate", "Remove", or "Fix" (present tense).
|
||||
|
||||
Example:
|
||||
|
||||
| Not ideal | Better |
|
||||
| ------------------------------------ | ------------------------------------------------------------- |
|
||||
| Fixed NoMethodError in RemovalWorker | Fix nil error when removing statuses caused by race condition |
|
||||
|
||||
It is not always possible to phrase every change in such a manner, but it is desired.
|
||||
|
||||
**The smaller the set of changes in the pull request is, the quicker it can be reviewed and merged.** Splitting tasks into multiple smaller pull requests is often preferable.
|
||||
|
||||
**Pull requests that do not pass automated checks may not be reviewed**. In particular, you need to keep in mind:
|
||||
|
||||
- Unit and integration test (PHPUnit)
|
||||
- Code style rules (PHP_CodeSniffer)
|
||||
- [WordPress plugin guidelines](https://developer.wordpress.org/plugins/wordpress-org/detailed-plugin-guidelines/)
|
||||
|
||||
## Documentation
|
||||
|
||||
- Documentation for users is currently in the README.md and readme.txt files and the admin pages of the plugin (see the `.template/` folder), although this can change if it could be advantageous.
|
||||
- Documentation for developers is the the projects [Wiki](https://codeberg.org/Event-Federation/wordpress-event-bridge-for-activitypub/wiki).
|
235
wp-content/plugins/event-bridge-for-activitypub/LICENSE
Normal file
@ -0,0 +1,235 @@
|
||||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU Affero General Public License is a free, copyleft license for software and other kinds of works, specifically designed to ensure cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, our General Public Licenses are intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things.
|
||||
|
||||
Developers that use our General Public Licenses protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License which gives you legal permission to copy, distribute and/or modify the software.
|
||||
|
||||
A secondary benefit of defending all users' freedom is that improvements made in alternate versions of the program, if they receive widespread use, become available for other developers to incorporate. Many developers of free software are heartened and encouraged by the resulting cooperation. However, in the case of software used on network servers, this result may fail to come about. The GNU General Public License permits making a modified version and letting the public access it on a server without ever releasing its source code to the public.
|
||||
|
||||
The GNU Affero General Public License is designed specifically to ensure that, in such cases, the modified source code becomes available to the community. It requires the operator of a network server to provide the source code of the modified version running there to the users of that server. Therefore, public use of a modified version, on a publicly accessible server, gives the public access to the source code of the modified version.
|
||||
|
||||
An older license, called the Affero General Public License and published by Affero, was designed to accomplish similar goals. This is a different license, not a version of the Affero GPL, but Affero has released a new version of the Affero GPL which permits relicensing under this license.
|
||||
|
||||
The precise terms and conditions for copying, distribution and modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
"Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the Program, your modified version must prominently offer all users interacting with it remotely through a computer network (if your version supports such interaction) an opportunity to receive the Corresponding Source of your version by providing access to the Corresponding Source from a network server at no charge, through some standard or customary means of facilitating copying of software. This Corresponding Source shall include the Corresponding Source for any work covered by version 3 of the GNU General Public License that is incorporated pursuant to the following paragraph.
|
||||
|
||||
Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the work with which it is combined will remain governed by version 3 of the GNU General Public License.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of the GNU Affero General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU Affero General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU Affero General Public License, you may choose any version ever published by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future versions of the GNU Affero General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
test
|
||||
Copyright (C) 2023 linos
|
||||
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If your software can interact with users remotely through a computer network, you should also make sure that it provides a way for users to get its source. For example, if your program is a web application, its interface could display a "Source" link that leads users to an archive of the code. There are many ways you could offer source, and different solutions will be better for different programs; see section 13 for the specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU AGPL, see <http://www.gnu.org/licenses/>.
|
@ -0,0 +1,236 @@
|
||||
.settings_page_event-bridge-for-activitypub #wpcontent {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.event-bridge-for-activitypub-settings-page .box {
|
||||
border: 1px solid #c3c4c7;
|
||||
background-color: #fff;
|
||||
padding: 1em 1.5em;
|
||||
margin-bottom: 1.5em;
|
||||
}
|
||||
|
||||
.event-bridge-for-activitypub-settings-page .logo-center {
|
||||
width: 25%;
|
||||
margin: 10px 5% 10px 5%;
|
||||
}
|
||||
|
||||
.event-bridge-for-activitypub-settings-page .box ul.event-bridge-for-activitypub-list {
|
||||
margin-left: 0.6em;
|
||||
}
|
||||
|
||||
.event-bridge-for-activitypub-settings-page .box pre {
|
||||
padding: 1rem;
|
||||
min-height: 200px;
|
||||
box-shadow: none;
|
||||
border-radius: 15px;
|
||||
border: 1px solid #dfe0e2;
|
||||
background-color: #f7f7f7;
|
||||
}
|
||||
|
||||
.event-bridge-for-activitypub-settings td {
|
||||
padding-top: 20px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.event-bridge-for-activitypub-settings-header {
|
||||
text-align: center;
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
.event-bridge-for-activitypub-settings-title-section {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
clear: both;
|
||||
padding-top: 8px;
|
||||
}
|
||||
|
||||
.event-bridge-for-activitypub-settings-tabs-wrapper {
|
||||
display: -ms-inline-grid;
|
||||
-ms-grid-columns: auto auto auto auto;
|
||||
vertical-align: top;
|
||||
display: inline-grid;
|
||||
grid-template-columns: auto auto auto auto;
|
||||
}
|
||||
|
||||
.event-bridge-for-activitypub-settings-tab.active {
|
||||
box-shadow: inset 0 -3px #3582c4;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.event-bridge-for-activitypub-settings-tab {
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
padding: .5rem 1rem 1rem;
|
||||
margin: 0 1rem;
|
||||
transition: box-shadow .5s ease-in-out;
|
||||
}
|
||||
|
||||
.event-bridge-for-activitypub-settings .box h3 {
|
||||
font-size: 1.15em;
|
||||
margin-bottom: 0em;
|
||||
}
|
||||
|
||||
#event_bridge_for_activitypub_initially_activated {
|
||||
display: hidden;
|
||||
}
|
||||
|
||||
/* Accordions for admin pages */
|
||||
.event-bridge-for-activitypub-settings-accordion {
|
||||
border: 1px solid #c3c4c7;
|
||||
}
|
||||
|
||||
.event-bridge-for-activitypub-settings-accordion-heading {
|
||||
margin: 0;
|
||||
border-top: 1px solid #c3c4c7;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
font-weight: 600;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.event-bridge-for-activitypub-settings-accordion-heading:first-child {
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.event-bridge-for-activitypub-settings-accordion-panel {
|
||||
margin: 0;
|
||||
padding: 1em 1.5em;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.event-bridge-for-activitypub-settings-accordion-trigger {
|
||||
background: #fff;
|
||||
border: 0;
|
||||
color: #2c3338;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
font-weight: 400;
|
||||
margin: 0;
|
||||
padding: 1em 3.5em 1em 1.5em;
|
||||
min-height: 46px;
|
||||
position: relative;
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
-webkit-user-select: auto;
|
||||
user-select: auto;
|
||||
}
|
||||
|
||||
.event-bridge-for-activitypub-settings-accordion-trigger {
|
||||
color: #2c3338;
|
||||
cursor: pointer;
|
||||
font-weight: 400;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.event-bridge-for-activitypub-settings-accordion-trigger .title {
|
||||
pointer-events: none;
|
||||
font-weight: 600;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.event-bridge-for-activitypub-settings-accordion-trigger .icon,
|
||||
.event-bridge-for-activitypub-settings-accordion-viewed .icon {
|
||||
border: solid #50575e medium;
|
||||
border-width: 0 2px 2px 0;
|
||||
height: .5rem;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
right: 1.5em;
|
||||
top: 50%;
|
||||
transform: translateY(-70%) rotate(45deg);
|
||||
width: .5rem;
|
||||
}
|
||||
|
||||
.event-bridge-for-activitypub-settings-accordion-trigger[aria-expanded="true"] .icon {
|
||||
transform: translateY(-30%) rotate(-135deg);
|
||||
}
|
||||
|
||||
.event-bridge-for-activitypub-settings-accordion-trigger:active,
|
||||
.event-bridge-for-activitypub-settings-accordion-trigger:hover {
|
||||
background: #f6f7f7;
|
||||
}
|
||||
|
||||
.event-bridge-for-activitypub-settings-accordion-trigger:focus {
|
||||
color: #1d2327;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
outline-offset: -1px;
|
||||
outline: 2px solid #2271b1;
|
||||
background-color: #f6f7f7;
|
||||
}
|
||||
|
||||
.event-bridge-for-activitypub-settings-inline-icon {
|
||||
width: 1.5em;
|
||||
height: 1.5em;
|
||||
vertical-align: middle;
|
||||
margin: 0 0.3em;
|
||||
}
|
||||
|
||||
code.event-bridge-for-activitypub-settings-example-url {
|
||||
display: block;
|
||||
background: rgb(28, 29, 33);
|
||||
padding: 8px;
|
||||
margin: 10px 0px 10px 0;
|
||||
border-radius: 7px;
|
||||
color: #d5d5d6;
|
||||
overflow-x: auto;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
#event_bridge_for_activitypub_summary_type_custom-details {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#event_bridge_for_activitypub_summary_type_custom-details > details {
|
||||
padding: 0.5em;
|
||||
}
|
||||
|
||||
.event_bridge_for_activitypub-list {
|
||||
list-style: disc;
|
||||
padding-left: 22px;
|
||||
}
|
||||
|
||||
.event_bridge_for_activitypub-admin-table-container {
|
||||
padding-left: 22px;
|
||||
}
|
||||
|
||||
.event_bridge_for_activitypub-admin-table-top > h2 {
|
||||
display: inline-block;
|
||||
font-size: 23px;
|
||||
font-weight: 400;
|
||||
margin: 0 10px 0 2px;
|
||||
padding: 9px 0 4px;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.event_bridge_for_activitypub-admin-table-top > a {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.settings_page_event-bridge-for-activitypub .notice-warning {
|
||||
background: #fff;
|
||||
border: 1px solid #c3c4c7;
|
||||
border-left-width: 4px;
|
||||
box-shadow: 0 1px 1px rgba(0,0,0,.04);
|
||||
margin: 5px 15px 2px;
|
||||
padding: 1px 12px;
|
||||
border-left-color: #dba617;
|
||||
}
|
||||
|
||||
.event-bridge-for-activitypub-settings .select-cell::before {
|
||||
content: "\21D2";
|
||||
margin-right: 8px;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
ul.event-bridge-for-activitypub-syntax-list {
|
||||
list-style: disc;
|
||||
}
|
||||
|
||||
ul.event-bridge-for-activitypub-syntax-list > li {
|
||||
margin: 6px 6px 6px 20px;
|
||||
}
|
@ -0,0 +1,121 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
version="1.1"
|
||||
id="svg2"
|
||||
xml:space="preserve"
|
||||
width="1600.5095"
|
||||
height="502.77777"
|
||||
viewBox="0 0 480.15286 150.83333"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"><metadata
|
||||
id="metadata8"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs6"><linearGradient
|
||||
id="linearGradient1220"><stop
|
||||
id="stop1216"
|
||||
offset="0"
|
||||
style="stop-color:#98bf00;stop-opacity:1;" /><stop
|
||||
id="stop1218"
|
||||
offset="1"
|
||||
style="stop-color:#98bf00;stop-opacity:0.51" /></linearGradient><linearGradient
|
||||
x1="0"
|
||||
y1="0"
|
||||
x2="1"
|
||||
y2="0"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(-139.45511,-135.52185,-135.52185,139.45511,177.4727,131.75308)"
|
||||
spreadMethod="pad"
|
||||
id="linearGradient28"><stop
|
||||
style="stop-opacity:1;stop-color:#00afbc"
|
||||
offset="0"
|
||||
id="stop24" /><stop
|
||||
style="stop-opacity:1;stop-color:#205374"
|
||||
offset="1"
|
||||
id="stop26" /></linearGradient><clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath38"><path
|
||||
d="M 0,127.984 H 415.474 V 0 H 0 Z"
|
||||
id="path36" /></clipPath><linearGradient
|
||||
xlink:href="#linearGradient1220"
|
||||
id="linearGradient947"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="14.915152"
|
||||
y1="14.167241"
|
||||
x2="214.11908"
|
||||
y2="111.76186"
|
||||
gradientTransform="matrix(4.4444443,0,0,-4.4444443,-33.008887,535.8)" /><clipPath
|
||||
clipPathUnits="userSpaceOnUse"
|
||||
id="clipPath38-9"><path
|
||||
d="M 0,127.984 H 415.474 V 0 H 0 Z"
|
||||
id="path36-1" /></clipPath></defs><g
|
||||
id="g10"
|
||||
transform="matrix(1.3333333,0,0,-1.3333333,-9.9026662,160.74)"><g
|
||||
id="g40"
|
||||
transform="translate(175.9982,95.8645)" /><g
|
||||
id="g44"
|
||||
transform="translate(152.1193,64.9934)" />
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<g
|
||||
id="NGI0Entrust"><title
|
||||
id="title12661">NGI Zero Entrust</title><path
|
||||
id="path7692"
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0.999999"
|
||||
d="m 133.10651,96.933602 c -6.67899,0 -12.68988,-1.41201 -18.02988,-4.23501 -5.344,-2.822 -9.51678,-6.73803 -12.52178,-11.74702 -3.004994,-5.008 -4.507906,-10.66967 -4.507906,-16.982669 0,-6.314995 1.502912,-11.974991 4.507906,-16.983985 3.005,-5.008995 7.14794,-8.924024 12.42993,-11.747021 5.282,-2.823998 11.23084,-4.23501 17.84883,-4.23501 4.613,0 9.19693,0.698875 13.75093,2.094873 0.045,0.014 0.0912,0.02819 0.13623,0.04219 7.10399,2.201999 11.88413,8.859686 11.88413,16.29668 v 9.047022 c 0,3.581996 -2.90333,6.485889 -6.48633,6.485889 h -0.50581 c -0.064,0 -0.12704,-0.0077 -0.19204,-0.0097 -0.064,0.002 -0.12704,0.0097 -0.19204,0.0097 h -7.28306 c -3.92899,0 -7.35908,-2.964914 -7.61308,-6.884912 -0.278,-4.295996 3.12428,-7.86709 7.36128,-7.86709 0.776,0 1.34293,-0.753702 1.11093,-1.493702 -0.65799,-2.087998 -2.34102,-3.751009 -4.54702,-4.333008 -2.07399,-0.546999 -4.27598,-0.820898 -6.60498,-0.820898 -4.00699,0 -7.57381,0.864972 -10.6998,2.594971 -3.127,1.729999 -5.5704,4.143993 -7.3314,7.23999 -1.761,3.095997 -2.64067,6.617018 -2.64067,10.564014 0,4.005996 0.87967,7.557666 2.64067,10.653656 1.761,3.097 4.2191,5.49317 7.3771,7.19517 3.156,1.698 6.76804,2.54883 10.83604,2.54883 4.68099,0 8.8649,-1.26899 12.5499,-3.80699 2.341,-1.61199 5.52423,-1.58761 7.75723,0.17139 3.47999,2.741 3.2889,8.04495 -0.31509,10.45196 -1.7,1.13599 -3.53807,2.11163 -5.51206,2.92763 -4.553,1.881 -9.62316,2.82305 -15.20816,2.82305 z m -93.706345,-1.09248 c -4.022996,0 -7.284815,-3.26081 -7.284815,-7.28482 v -49.17612 c 0,-4.022993 3.261819,-7.284815 7.284815,-7.284815 4.023996,0 7.284814,3.261822 7.284814,7.284815 V 62.34029 c 0,2.842996 3.564362,4.118722 5.36836,1.921728 L 76.282148,34.757135 c 1.383999,-1.685 3.450155,-2.661768 5.631153,-2.661768 h 1.380761 c 4.023997,0 7.286133,3.261822 7.286133,7.284815 v 49.17612 c 0,4.02401 -3.262136,7.28482 -7.286133,7.28482 -4.023995,0 -7.284815,-3.26081 -7.284815,-7.28482 V 65.615095 c 0,-2.844997 -3.568118,-4.119773 -5.370117,-1.917774 L 46.503925,93.172322 c -1.382997,1.69 -3.45199,2.6688 -5.635987,2.6688 z m 136.597415,-4.4e-4 c -4.074,0 -7.37578,-3.30178 -7.37578,-7.37578 V 39.472027 c 0,-4.073996 3.30178,-7.37622 7.37578,-7.37622 4.074,0 7.37622,3.302224 7.37622,7.37622 v 48.992875 c 0,4.074 -3.30222,7.37578 -7.37622,7.37578 z" /><path
|
||||
id="path30"
|
||||
style="fill:url(#linearGradient947);fill-opacity:1;stroke:none;stroke-width:4.44444"
|
||||
d="M 79.115234 30 C 52.097457 30 30 52.101902 30 79.115234 L 30 423.66211 C 30 450.67989 52.097457 472.77734 79.115234 472.77734 L 812.60352 472.77734 C 839.61685 472.77734 861.7207 450.67544 861.7207 423.66211 L 861.7207 342.50586 C 861.7207 333.51919 865.28844 324.89711 871.64844 318.53711 L 912.07617 278.11133 C 923.36506 266.82688 923.33313 248.52428 912.01758 237.27539 L 871.7207 197.19922 C 865.3207 190.83922 861.7207 182.18238 861.7207 173.16016 L 861.7207 79.115234 C 861.7207 52.101902 839.61685 30 812.60352 30 L 79.115234 30 z M 558.57812 104.87891 C 583.40035 104.87891 605.93437 109.06578 626.16992 117.42578 C 634.94325 121.05245 643.11241 125.38861 650.66797 130.4375 C 666.68575 141.13528 667.53503 164.7084 652.06836 176.89062 C 642.14392 184.7084 627.99624 184.81679 617.5918 177.65234 C 601.21402 166.37234 582.6189 160.73242 561.81445 160.73242 C 543.73445 160.73242 527.68096 164.51388 513.6543 172.06055 C 499.61874 179.62499 488.69385 190.27462 480.86719 204.03906 C 473.04052 217.79906 469.13086 233.58423 469.13086 251.38867 C 469.13086 268.93089 473.04052 284.57984 480.86719 298.33984 C 488.69385 312.09984 499.55339 322.82869 513.45117 330.51758 C 527.3445 338.20647 543.19697 342.05078 561.00586 342.05078 C 571.35697 342.05078 581.14355 340.83345 590.36133 338.40234 C 600.16577 335.81568 607.64587 328.42453 610.57031 319.14453 C 611.60142 315.85564 609.0817 312.50586 605.63281 312.50586 C 586.8017 312.50586 571.68046 296.63435 572.91602 277.54102 C 574.0449 260.11879 589.28973 246.94141 606.75195 246.94141 L 639.12109 246.94141 C 639.40998 246.94141 639.69016 246.97549 639.97461 246.98438 C 640.2635 246.97549 640.54368 246.94141 640.82812 246.94141 L 643.07617 246.94141 C 659.00062 246.94141 671.9043 259.84758 671.9043 275.76758 L 671.9043 315.97656 C 671.9043 349.0299 650.65927 378.61958 619.08594 388.40625 C 618.88594 388.46847 618.68047 388.53153 618.48047 388.59375 C 598.24047 394.79819 577.86746 397.9043 557.36523 397.9043 C 527.9519 397.9043 501.51266 391.63314 478.03711 379.08203 C 454.56155 366.53536 436.14852 349.13527 422.79297 326.87305 C 409.43741 304.61083 402.75781 279.45534 402.75781 251.38867 C 402.75781 223.33089 409.43741 198.16793 422.79297 175.91016 C 436.14852 153.64793 454.6942 136.24339 478.44531 123.70117 C 502.17865 111.15451 528.89368 104.87891 558.57812 104.87891 z M 142.10547 109.73438 L 148.62891 109.73438 C 158.33557 109.73438 167.53107 114.08459 173.67773 121.5957 L 280.94531 252.5957 C 288.9542 262.38237 304.8125 256.71671 304.8125 244.07227 L 304.8125 142.11133 C 304.8125 124.22688 319.30501 109.73438 337.18945 109.73438 C 355.0739 109.73438 369.57227 124.22688 369.57227 142.11133 L 369.57227 360.67188 C 369.57227 378.55187 355.0739 393.04883 337.18945 393.04883 L 331.05273 393.04883 C 321.3594 393.04883 312.1765 388.70764 306.02539 381.21875 L 198.3418 250.08594 C 190.32402 240.32149 174.48242 245.9914 174.48242 258.62695 L 174.48242 360.67188 C 174.48242 378.55187 159.98991 393.04883 142.10547 393.04883 C 124.22547 393.04883 109.72852 378.55187 109.72852 360.67188 L 109.72852 142.11133 C 109.72852 124.22688 124.22547 109.73438 142.10547 109.73438 z M 749.20508 109.73633 C 767.31174 109.73633 781.98828 124.41091 781.98828 142.51758 L 781.98828 360.26367 C 781.98828 378.37034 767.31174 393.04688 749.20508 393.04688 C 731.09841 393.04688 716.42383 378.37034 716.42383 360.26367 L 716.42383 142.51758 C 716.42383 124.41091 731.09841 109.73633 749.20508 109.73633 z "
|
||||
transform="matrix(0.22500001,0,0,-0.22500001,7.4269998,120.555)" /><g
|
||||
aria-label="Z E R O"
|
||||
transform="scale(1,-1)"
|
||||
id="text56"
|
||||
style="font-weight:600;font-size:31.76px;font-family:'Montserrat SemiBold';-inkscape-font-specification:Montserrat-SemiBold;fill:#6f9aa8"><path
|
||||
d="m 261.75384,-85.665085 -13.08512,15.97528 h 13.498 v 3.4936 H 243.206 v -2.76312 l 13.08512,-15.97528 h -12.8628 v -3.4936 h 18.32552 z"
|
||||
id="path12603" /><path
|
||||
d="m 278.84063,-75.787725 v 6.12968 h 12.5452 v 3.46184 h -16.674 v -22.232 h 16.22936 v 3.46184 h -12.10056 v 5.78032 h 10.73488 v 3.39832 z"
|
||||
id="path12605" /><path
|
||||
d="m 323.74919,-66.196205 h -4.4464 l -4.54168,-6.5108 q -0.28584,0.03176 -0.85752,0.03176 h -5.01808 v 6.47904 h -4.1288 v -22.232 h 9.14688 q 2.89016,0 5.01808,0.9528 2.15968,0.9528 3.30304,2.73136 1.14336,1.77856 1.14336,4.22408 0,2.50904 -1.23864,4.31936 -1.20688,1.81032 -3.4936,2.6996 z m -4.54168,-14.32376 q 0,-2.12792 -1.39744,-3.27128 -1.39744,-1.14336 -4.09704,-1.14336 h -4.82752 v 8.86104 h 4.82752 q 2.6996,0 4.09704,-1.14336 1.39744,-1.17512 1.39744,-3.30304 z"
|
||||
id="path12607" /><path
|
||||
d="m 347.12448,-65.878605 q -3.39832,0 -6.12968,-1.46096 -2.73136,-1.49272 -4.2876,-4.09704 -1.55624,-2.63608 -1.55624,-5.8756 0,-3.23952 1.55624,-5.84384 1.55624,-2.63608 4.2876,-4.09704 2.73136,-1.49272 6.12968,-1.49272 3.39832,0 6.12968,1.49272 2.73136,1.46096 4.2876,4.06528 1.55624,2.60432 1.55624,5.8756 0,3.27128 -1.55624,5.8756 -1.55624,2.60432 -4.2876,4.09704 -2.73136,1.46096 -6.12968,1.46096 z m 0,-3.62064 q 2.2232,0 4.00176,-0.98456 1.77856,-1.01632 2.79488,-2.79488 1.01632,-1.81032 1.01632,-4.03352 0,-2.2232 -1.01632,-4.00176 -1.01632,-1.81032 -2.79488,-2.79488 -1.77856,-1.01632 -4.00176,-1.01632 -2.2232,0 -4.00176,1.01632 -1.77856,0.98456 -2.79488,2.79488 -1.01632,1.77856 -1.01632,4.00176 0,2.2232 1.01632,4.03352 1.01632,1.77856 2.79488,2.79488 1.77856,0.98456 4.00176,0.98456 z"
|
||||
id="path12609" /></g><g
|
||||
aria-label="ENTRUST"
|
||||
transform="scale(0.99994801,-1.000052)"
|
||||
id="Entrust"
|
||||
style="font-weight:bold;font-size:20.009px;font-family:'Montserrat SemiBold';-inkscape-font-specification:'Montserrat SemiBold, Bold';letter-spacing:3.55932px;fill:#6f9aa8;stroke-width:0.999947"><path
|
||||
d="m 245.81989,-41.935548 v 3.861737 h 7.90356 v 2.180981 h -10.50473 v -14.0063 h 10.2246 v 2.180981 h -7.62343 v 3.641638 h 6.76304 v 2.140963 z"
|
||||
id="path12612" /><path
|
||||
d="m 270.04847,-40.414864 v -9.484266 h 2.58116 v 14.0063 h -2.14096 l -7.72347,-9.484266 v 9.484266 h -2.58117 v -14.0063 h 2.14097 z"
|
||||
id="path12614" /><path
|
||||
d="m 285.39308,-35.89283 h -2.60117 v -11.80531 h -4.64209 v -2.20099 h 11.88535 v 2.20099 h -4.64209 z"
|
||||
id="path12616" /><path
|
||||
d="m 307.52074,-35.89283 h -2.80126 l -2.86129,-4.101845 q -0.18008,0.02001 -0.54024,0.02001 h -3.16142 v 4.081836 h -2.60117 v -14.0063 h 5.76259 q 1.82082,0 3.16142,0.60027 1.36061,0.60027 2.08094,1.720774 0.72032,1.120504 0.72032,2.661197 0,1.580711 -0.78035,2.721224 -0.76034,1.140513 -2.20099,1.700765 z m -2.86129,-9.024059 q 0,-1.340603 -0.88039,-2.060927 -0.8804,-0.720324 -2.58116,-0.720324 h -3.04137 v 5.582511 h 3.04137 q 1.70076,0 2.58116,-0.720324 0.88039,-0.740333 0.88039,-2.080936 z"
|
||||
id="path12618" /><path
|
||||
d="m 319.76395,-35.69274 q -2.90131,0 -4.52204,-1.620729 -1.62073,-1.640738 -1.62073,-4.682106 v -7.903555 h 2.60117 v 7.80351 q 0,4.121854 3.5616,4.121854 3.5416,0 3.5416,-4.121854 v -7.80351 h 2.56115 v 7.903555 q 0,3.041368 -1.62073,4.682106 -1.60072,1.620729 -4.50202,1.620729 z"
|
||||
id="path12620" /><path
|
||||
d="m 337.4296,-35.69274 q -1.62073,0 -3.14141,-0.460207 -1.50068,-0.460207 -2.38107,-1.220549 l 0.9004,-2.020909 q 0.86039,0.680306 2.10095,1.120504 1.26056,0.420189 2.52113,0.420189 1.5607,0 2.32105,-0.500225 0.78035,-0.500225 0.78035,-1.320594 0,-0.60027 -0.4402,-0.980441 -0.42019,-0.40018 -1.08049,-0.620279 -0.66029,-0.220099 -1.80081,-0.500225 -1.60072,-0.380171 -2.60117,-0.760342 -0.98044,-0.380171 -1.70076,-1.180531 -0.70032,-0.820369 -0.70032,-2.20099 0,-1.160522 0.62028,-2.100945 0.64029,-0.960432 1.90086,-1.520684 1.28057,-0.560252 3.1214,-0.560252 1.28058,0 2.52113,0.320144 1.24056,0.320144 2.14097,0.920414 l -0.82037,2.020909 q -0.92042,-0.540243 -1.92087,-0.820369 -1.00045,-0.280126 -1.94087,-0.280126 -1.54069,0 -2.30103,0.520234 -0.74034,0.520234 -0.74034,1.380621 0,0.60027 0.42019,0.980441 0.4402,0.380171 1.1005,0.60027 0.66029,0.220099 1.80081,0.500225 1.5607,0.360162 2.56115,0.760342 1.00045,0.380171 1.70076,1.180531 0.72033,0.80036 0.72033,2.160972 0,1.160522 -0.64029,2.100945 -0.62028,0.940423 -1.90085,1.500675 -1.28058,0.560252 -3.12141,0.560252 z"
|
||||
id="path12622" /><path
|
||||
d="m 354.47498,-35.89283 h -2.60117 v -11.80531 h -4.64209 v -2.20099 h 11.88535 v 2.20099 h -4.64209 z"
|
||||
id="path12624" /></g></g>
|
||||
|
||||
|
||||
|
||||
<text
|
||||
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:20.01px;font-family:'Montserrat SemiBold';-inkscape-font-specification:'Montserrat SemiBold, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#6f9aa8;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1"
|
||||
id="text2843"
|
||||
x="240.16206"
|
||||
y="-35.894695"
|
||||
transform="scale(1,-1)"><tspan
|
||||
id="tspan2841"
|
||||
x="240.16206"
|
||||
y="-35.894695" /></text></g></svg>
|
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 16 KiB |
@ -0,0 +1,288 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
width="130"
|
||||
height="130"
|
||||
viewBox="0 0 34.395832 34.395832"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="0.92.1 r15371"
|
||||
sodipodi:docname="ActivityPub-logo-symbol.svg">
|
||||
<title
|
||||
id="title4590">ActivityPub logo</title>
|
||||
<defs
|
||||
id="defs2">
|
||||
<linearGradient
|
||||
id="AP-4-0"
|
||||
osb:paint="solid">
|
||||
<stop
|
||||
style="stop-color:#5e5e5e;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop5660" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient5640"
|
||||
osb:paint="solid">
|
||||
<stop
|
||||
style="stop-color:#000000;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop5638" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient5634"
|
||||
osb:paint="solid">
|
||||
<stop
|
||||
style="stop-color:#000000;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop5632" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="linearGradient5628"
|
||||
osb:paint="solid">
|
||||
<stop
|
||||
style="stop-color:#000000;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop5626" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="AP-3-7"
|
||||
osb:paint="solid">
|
||||
<stop
|
||||
style="stop-color:#c678c5;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop5498" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="AP-2-3"
|
||||
osb:paint="solid">
|
||||
<stop
|
||||
style="stop-color:#6d6d6d;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop5230" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="AP1-5"
|
||||
osb:paint="solid">
|
||||
<stop
|
||||
style="stop-color:#f1007e;stop-opacity:1;"
|
||||
offset="0"
|
||||
id="stop5212" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#AP-3-7"
|
||||
id="linearGradient5749"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="3319.292"
|
||||
y1="-1291.2802"
|
||||
x2="3344.3645"
|
||||
y2="-1291.2802" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#AP1-5"
|
||||
id="linearGradient7297-7"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="3241.6836"
|
||||
y1="-1355.4329"
|
||||
x2="3254.9529"
|
||||
y2="-1355.4329" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#AP-2-3"
|
||||
id="linearGradient7303-7"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="3225.7603"
|
||||
y1="-1355.4329"
|
||||
x2="3239.0295"
|
||||
y2="-1355.4329" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#AP1-5"
|
||||
id="linearGradient8308"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="3241.6836"
|
||||
y1="-1355.4329"
|
||||
x2="3254.9529"
|
||||
y2="-1355.4329" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#AP1-5"
|
||||
id="linearGradient8310"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="3241.6836"
|
||||
y1="-1355.4329"
|
||||
x2="3254.9529"
|
||||
y2="-1355.4329" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#AP1-5"
|
||||
id="linearGradient8312"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="3241.6836"
|
||||
y1="-1355.4329"
|
||||
x2="3254.9529"
|
||||
y2="-1355.4329" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#AP-2-3"
|
||||
id="linearGradient8314"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
x1="3225.7603"
|
||||
y1="-1355.4329"
|
||||
x2="3239.0295"
|
||||
y2="-1355.4329"
|
||||
gradientTransform="matrix(3.7000834,0,0,3.7000834,-11935.582,4544.6634)" />
|
||||
<linearGradient
|
||||
inkscape:collect="always"
|
||||
xlink:href="#AP-2-3"
|
||||
id="linearGradient5188"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="matrix(0.42732603,0,0,0.42732603,-1363.3009,454.91899)"
|
||||
x1="3269.126"
|
||||
y1="-1354.6217"
|
||||
x2="3322.1943"
|
||||
y2="-1354.6217" />
|
||||
</defs>
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="0.14509804"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="0.70710678"
|
||||
inkscape:cx="-195.34129"
|
||||
inkscape:cy="-120.65903"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
inkscape:snap-global="true"
|
||||
showguides="false"
|
||||
inkscape:guide-bbox="true"
|
||||
showborder="true"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:showpageshadow="false"
|
||||
borderlayer="false"
|
||||
units="px">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid4572"
|
||||
enabled="false"
|
||||
originx="7.1437514"
|
||||
originy="-404.28382" />
|
||||
<inkscape:grid
|
||||
type="axonomgrid"
|
||||
id="grid4574"
|
||||
units="mm"
|
||||
empspacing="12"
|
||||
originx="7.1437514"
|
||||
originy="-404.28382"
|
||||
enabled="false" />
|
||||
<sodipodi:guide
|
||||
position="3278.981,1256.5057"
|
||||
orientation="0,1"
|
||||
id="guide5059"
|
||||
inkscape:locked="false" />
|
||||
<sodipodi:guide
|
||||
position="3278.981,1238.2495"
|
||||
orientation="0,1"
|
||||
id="guide5061"
|
||||
inkscape:locked="false" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title>ActivityPub logo</dc:title>
|
||||
<cc:license
|
||||
rdf:resource="http://creativecommons.org/publicdomain/zero/1.0/" />
|
||||
<dc:date>2017-04-15</dc:date>
|
||||
<dc:creator>
|
||||
<cc:Agent>
|
||||
<dc:title>Robert Martinez</dc:title>
|
||||
</cc:Agent>
|
||||
</dc:creator>
|
||||
<dc:subject>
|
||||
<rdf:Bag>
|
||||
<rdf:li>ActivityPub</rdf:li>
|
||||
</rdf:Bag>
|
||||
</dc:subject>
|
||||
</cc:Work>
|
||||
<cc:License
|
||||
rdf:about="http://creativecommons.org/publicdomain/zero/1.0/">
|
||||
<cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#Reproduction" />
|
||||
<cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#Distribution" />
|
||||
<cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
|
||||
</cc:License>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
style="opacity:1"
|
||||
transform="translate(7.1437516,141.67967)">
|
||||
<path
|
||||
style="fill:#000000;stroke-width:0.26458335"
|
||||
d=""
|
||||
id="path5497"
|
||||
inkscape:connector-curvature="0" />
|
||||
<g
|
||||
id="g5197"
|
||||
transform="translate(-4.2352716,0.01824528)">
|
||||
<g
|
||||
id="g5132-90"
|
||||
style="fill:url(#linearGradient7297-7);fill-opacity:1"
|
||||
transform="matrix(0.9789804,0,0,0.9789804,-3157.9561,1202.4422)">
|
||||
<g
|
||||
transform="matrix(0.2553682,0,0,0.2553682,2615.9213,-1125.3113)"
|
||||
id="g5080-78"
|
||||
style="fill:url(#linearGradient8312);fill-opacity:1">
|
||||
<path
|
||||
inkscape:connector-curvature="0"
|
||||
id="path5404-0-0"
|
||||
d="m 2450.431,-937.13662 51.9615,30 v 12 l -51.9615,30 v -12 l 41.5693,-24 -41.5692,-24 z"
|
||||
style="fill:url(#linearGradient8308);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
sodipodi:nodetypes="cccccccc" />
|
||||
<path
|
||||
sodipodi:nodetypes="cccc"
|
||||
style="fill:url(#linearGradient8310);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="m 2450.431,-913.13662 20.7847,12 -20.7847,12 z"
|
||||
id="path5406-6-3"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</g>
|
||||
<g
|
||||
id="g5127-1"
|
||||
style="fill:url(#linearGradient7303-7);fill-opacity:1"
|
||||
transform="matrix(0.9789804,0,0,0.9789804,-3157.9561,1202.4422)">
|
||||
<path
|
||||
id="path5467-2-0"
|
||||
transform="matrix(0.27026418,0,0,0.27026418,3225.7603,-1228.2597)"
|
||||
d="M 49.097656,-504.56641 0,-476.2207 v 11.33789 l 39.277344,-22.67578 v 45.35351 l 9.820312,5.66992 z m -19.638672,34.01563 -19.6406246,11.33789 19.6406246,11.33789 z"
|
||||
style="fill:url(#linearGradient8314);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:0.25000042px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
inkscape:connector-curvature="0" />
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 8.8 KiB |
@ -0,0 +1,17 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="196.52mm" height="196.52mm" viewBox="0 0 196.52 196.52">
|
||||
<path fill="#a730b8" d="M47.9242 72.7966a18.2278 18.2278 0 0 1-7.7959 7.7597l42.7984 42.9653 10.3182-5.2291zm56.4524 56.6704-10.3182 5.2291 21.686 21.7708a18.2278 18.2278 0 0 1 7.7975-7.7608z"/>
|
||||
<path fill="#5496be" d="M129.6645 102.0765l1.7865 11.4272 27.4149-13.8942a18.2278 18.2278 0 0 1-4.9719-9.8124zm-14.0658 7.1282-57.2891 29.0339a18.2278 18.2278 0 0 1 4.9728 9.8133l54.1027-27.4194z"/>
|
||||
<path fill="#ce3d1a" d="M69.5312 91.6539l8.1618 8.1933 29.269-57.1387a18.2278 18.2278 0 0 1-9.787-5.0219zm-7.1897 14.0363-14.0022 27.3353a18.2278 18.2278 0 0 1 9.786 5.0214l12.3775-24.1639z"/>
|
||||
<path fill="#d0188f" d="M39.8906 80.6763a18.2278 18.2278 0 0 1-10.8655 1.7198l8.1762 52.2981a18.2278 18.2278 0 0 1 10.8645-1.7198z"/>
|
||||
<path fill="#5b36e9" d="M63.3259 148.3109a18.2278 18.2278 0 0 1-1.7322 10.8629l52.2893 8.3907a18.2278 18.2278 0 0 1 1.7322-10.8629z"/>
|
||||
<path fill="#30b873" d="M134.9148 146.9182a18.2278 18.2278 0 0 1 9.788 5.0224l24.1345-47.117a18.2278 18.2278 0 0 1-9.7875-5.0229z"/>
|
||||
<path fill="#ebe305" d="M126.1329 33.1603a18.2278 18.2278 0 0 1-7.7975 7.7608l37.3765 37.5207a18.2278 18.2278 0 0 1 7.7969-7.7608z"/>
|
||||
<path fill="#f47601" d="M44.7704 51.6279a18.2278 18.2278 0 0 1 4.9723 9.8123l47.2478-23.9453a18.2278 18.2278 0 0 1-4.9718-9.8113z"/>
|
||||
<path fill="#57c115" d="M118.2491 40.9645a18.2278 18.2278 0 0 1-10.8511 1.8123l4.1853 26.8 11.42 1.8324zm-4.2333 44.1927 9.8955 63.3631a18.2278 18.2278 0 0 1 10.88-1.6278l-9.355-59.9035z"/>
|
||||
<path fill="#dbb210" d="M49.7763 61.6412a18.2278 18.2278 0 0 1-1.694 10.8686l26.8206 4.3077 5.2715-10.2945zm45.9677 7.382-5.272 10.2955 63.3713 10.1777a18.2278 18.2278 0 0 1 1.7606-10.8593z"/>
|
||||
<path fill="#ffca00" d="M93.4385 23.8419a1 1 0 1 0 33.0924 1.8025 1 1 0 1 0-33.0924-1.8025"/>
|
||||
<path fill="#64ff00" d="M155.314 85.957a1 1 0 1 0 33.0923 1.8025 1 1 0 1 0-33.0923-1.8025"/>
|
||||
<path fill="#00a3ff" d="M115.3466 163.9824a1 1 0 1 0 33.0923 1.8025 1 1 0 1 0-33.0923-1.8025"/>
|
||||
<path fill="#9500ff" d="M28.7698 150.0898a1 1 0 1 0 33.0923 1.8025 1 1 0 1 0-33.0923-1.8025"/>
|
||||
<path fill="#ff0000" d="M15.2298 63.4781a1 1 0 1 0 33.0923 1.8025 1 1 0 1 0-33.0923-1.8025"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 3.5 KiB |
@ -0,0 +1,10 @@
|
||||
<svg width="75" height="79" viewBox="0 0 75 79" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M73.8393 17.4898C72.6973 9.00165 65.2994 2.31235 56.5296 1.01614C55.05 0.797115 49.4441 0 36.4582 0H36.3612C23.3717 0 20.585 0.797115 19.1054 1.01614C10.5798 2.27644 2.79399 8.28712 0.904997 16.8758C-0.00358524 21.1056 -0.100549 25.7949 0.0682394 30.0965C0.308852 36.2651 0.355538 42.423 0.91577 48.5665C1.30307 52.6474 1.97872 56.6957 2.93763 60.6812C4.73325 68.042 12.0019 74.1676 19.1233 76.6666C26.7478 79.2728 34.9474 79.7055 42.8039 77.9162C43.6682 77.7151 44.5217 77.4817 45.3645 77.216C47.275 76.6092 49.5123 75.9305 51.1571 74.7385C51.1797 74.7217 51.1982 74.7001 51.2112 74.6753C51.2243 74.6504 51.2316 74.6229 51.2325 74.5948V68.6416C51.2321 68.6154 51.2259 68.5896 51.2142 68.5661C51.2025 68.5426 51.1858 68.522 51.1651 68.5058C51.1444 68.4896 51.1204 68.4783 51.0948 68.4726C51.0692 68.4669 51.0426 68.467 51.0171 68.4729C45.9835 69.675 40.8254 70.2777 35.6502 70.2682C26.7439 70.2682 24.3486 66.042 23.6626 64.2826C23.1113 62.762 22.7612 61.1759 22.6212 59.5646C22.6197 59.5375 22.6247 59.5105 22.6357 59.4857C22.6466 59.4609 22.6633 59.4391 22.6843 59.422C22.7053 59.4048 22.73 59.3929 22.7565 59.3871C22.783 59.3813 22.8104 59.3818 22.8367 59.3886C27.7864 60.5826 32.8604 61.1853 37.9522 61.1839C39.1768 61.1839 40.3978 61.1839 41.6224 61.1516C46.7435 61.008 52.1411 60.7459 57.1796 59.7621C57.3053 59.7369 57.431 59.7154 57.5387 59.6831C65.4861 58.157 73.0493 53.3672 73.8178 41.2381C73.8465 40.7606 73.9184 36.2364 73.9184 35.7409C73.9219 34.0569 74.4606 23.7949 73.8393 17.4898Z" fill="url(#paint0_linear_549_34)"/>
|
||||
<path d="M61.2484 27.0263V48.114H52.8916V27.6475C52.8916 23.3388 51.096 21.1413 47.4437 21.1413C43.4287 21.1413 41.4177 23.7409 41.4177 28.8755V40.0782H33.1111V28.8755C33.1111 23.7409 31.0965 21.1413 27.0815 21.1413C23.4507 21.1413 21.6371 23.3388 21.6371 27.6475V48.114H13.2839V27.0263C13.2839 22.7176 14.384 19.2946 16.5843 16.7572C18.8539 14.2258 21.8311 12.926 25.5264 12.926C29.8036 12.926 33.0357 14.5705 35.1905 17.8559L37.2698 21.346L39.3527 17.8559C41.5074 14.5705 44.7395 12.926 49.0095 12.926C52.7013 12.926 55.6784 14.2258 57.9553 16.7572C60.1531 19.2922 61.2508 22.7152 61.2484 27.0263Z" fill="white"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_549_34" x1="37.0692" y1="0" x2="37.0692" y2="79" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#6364FF"/>
|
||||
<stop offset="1" stop-color="#563ACC"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 14 KiB |
@ -0,0 +1,34 @@
|
||||
jQuery( function( $ ) {
|
||||
// Accordion handling in various areas.
|
||||
$( '.event-bridge-for-activitypub-settings-accordion' ).on( 'click', '.event-bridge-for-activitypub-settings-accordion-trigger', function() {
|
||||
var isExpanded = ( 'true' === $( this ).attr( 'aria-expanded' ) );
|
||||
|
||||
if ( isExpanded ) {
|
||||
$( this ).attr( 'aria-expanded', 'false' );
|
||||
$( '#' + $( this ).attr( 'aria-controls' ) ).attr( 'hidden', true );
|
||||
} else {
|
||||
$( this ).attr( 'aria-expanded', 'true' );
|
||||
$( '#' + $( this ).attr( 'aria-controls' ) ).attr( 'hidden', false );
|
||||
}
|
||||
} );
|
||||
|
||||
// Function to toggle visibility of custom details based on selected radio button.
|
||||
function toggleCustomDetailsForSummary() {
|
||||
if ($("#event_bridge_for_activitypub_summary_type_custom").is(':checked')) {
|
||||
$("#event_bridge_for_activitypub_summary_type_custom-details").show();
|
||||
} else {
|
||||
$("#event_bridge_for_activitypub_summary_type_custom-details").hide();
|
||||
}
|
||||
}
|
||||
|
||||
// Run the toggle function on page load.
|
||||
$(document).ready(function() {
|
||||
toggleCustomDetailsForSummary(); // Set the correct state on load.
|
||||
|
||||
// Listen for changes on the radio buttons
|
||||
$("input[name=event_bridge_for_activitypub_summary_type]").change(function() {
|
||||
toggleCustomDetailsForSummary(); // Update visibility on change.
|
||||
});
|
||||
});
|
||||
|
||||
} );
|
@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "reminder",
|
||||
"title": "Reminder Plugin: not a block, but block.json is very useful.",
|
||||
"category": "widgets",
|
||||
"icon": "admin-comments",
|
||||
"keywords": [],
|
||||
"editorScript": "file:./plugin.js"
|
||||
}
|
@ -0,0 +1 @@
|
||||
<?php return array('dependencies' => array('react-jsx-runtime', 'wp-components', 'wp-core-data', 'wp-data', 'wp-editor', 'wp-i18n', 'wp-plugins'), 'version' => 'd491284dfb7e5078a777');
|
@ -0,0 +1 @@
|
||||
(()=>{"use strict";const e=window.wp.editor,t=window.wp.plugins,i=window.wp.components,n=window.wp.data,a=window.wp.coreData,r=window.wp.i18n,d=window.ReactJSXRuntime,p=activityPubEventBridge.reminderTypeGap;(0,t.registerPlugin)("event-bridge-for-activitypub-reminder",{render:()=>{const t=(0,n.useSelect)((e=>e("core/editor").getCurrentPostType()),[]),[_,b]=(0,a.useEntityProp)("postType",t,"meta"),u=_?.event_bridge_for_activitypub_reminder_time_gap?_?.event_bridge_for_activitypub_reminder_time_gap:p;return(0,d.jsx)(e.PluginDocumentSettingPanel,{name:"activitypub",title:(0,r.__)("Send reminder before event's start","activitypub"),children:(0,d.jsx)(i.SelectControl,{label:(0,r.__)("Time gap","activitypub"),value:u,options:[{label:(0,r.__)("Disabled","event-bridge-for-activitypub"),value:0},{label:(0,r.__)("6 hours","event-bridge-for-activitypub"),value:21600},{label:(0,r.__)("1 day","event-bridge-for-activitypub"),value:86400},{label:(0,r.__)("3 days","event-bridge-for-activitypub"),value:259200},{label:(0,r.__)("1 week","event-bridge-for-activitypub"),value:604800}],onChange:e=>{b({..._,event_bridge_for_activitypub_reminder_time_gap:e})},__nextHasNoMarginBottom:!0})})}})})();
|
@ -0,0 +1,39 @@
|
||||
<?php
|
||||
/**
|
||||
* Plugin Name: Event Bridge for ActivityPub
|
||||
* Description: Integrating popular event plugins with the ActivityPub plugin. The development of this plugin was funded through the <strong>NGI0 Entrust Fund</strong>, a fund established by <strong>NLnet</strong>.
|
||||
* Plugin URI: https://event-federation.eu/
|
||||
* Version: 1.1.0
|
||||
* Author: André Menrath
|
||||
* Author URI: https://graz.social/@linos
|
||||
* Text Domain: event-bridge-for-activitypub
|
||||
* License: AGPL-3.0-or-later
|
||||
* License URI: https://www.gnu.org/licenses/agpl-3.0.html
|
||||
* Requires PHP: 7.4
|
||||
* Requires Plugins: activitypub
|
||||
*
|
||||
* Requires at least ActivityPub plugin with version >= 5.6.1. ActivityPub plugin tested up to: 5.7.0.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
define( 'EVENT_BRIDGE_FOR_ACTIVITYPUB_PLUGIN_DIR', plugin_dir_path( __FILE__ ) );
|
||||
define( 'EVENT_BRIDGE_FOR_ACTIVITYPUB_PLUGIN_BASENAME', plugin_basename( __FILE__ ) );
|
||||
define( 'EVENT_BRIDGE_FOR_ACTIVITYPUB_PLUGIN_FILE', plugin_dir_path( __FILE__ ) . '/' . basename( __FILE__ ) );
|
||||
define( 'EVENT_BRIDGE_FOR_ACTIVITYPUB_PLUGIN_URL', plugin_dir_url( __FILE__ ) );
|
||||
define( 'EVENT_BRIDGE_FOR_ACTIVITYPUB_PLUGIN_VERSION', current( get_file_data( __FILE__, array( 'Version' ), 'plugin' ) ) );
|
||||
define( 'EVENT_BRIDGE_FOR_ACTIVITYPUB_DOMAIN', 'event-bridge-for-activitypub' );
|
||||
define( 'EVENT_BRIDGE_FOR_ACTIVITYPUB_ACTIVITYPUB_PLUGIN_MIN_VERSION', '5.6.1' );
|
||||
define( 'EVENT_BRIDGE_FOR_ACTIVITYPUB_SUMMARY_TEMPLATE', "[ap_start_time]\n[ap_end_time]\n[ap_location]\n\n[ap_content]" );
|
||||
define( 'EVENT_BRIDGE_FOR_ACTIVITYPUB_DEFAULT_SUMMARY_TYPE', 'preset' );
|
||||
|
||||
// Include and register the autoloader class for automatic loading of plugin classes.
|
||||
require_once EVENT_BRIDGE_FOR_ACTIVITYPUB_PLUGIN_DIR . '/includes/class-autoloader.php';
|
||||
Event_Bridge_For_ActivityPub\Autoloader::register();
|
||||
|
||||
// Initialize the plugin.
|
||||
Event_Bridge_For_ActivityPub\Setup::get_instance();
|
@ -0,0 +1,35 @@
|
||||
<?php
|
||||
/**
|
||||
* Class responsible for registering handlers for incoming activities to the ActivityPub plugin.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Handler\Accept;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Handler\Update;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Handler\Create;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Handler\Delete;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Handler\Undo;
|
||||
|
||||
/**
|
||||
* Class responsible for registering handlers for incoming activities to the ActivityPub plugin.
|
||||
*/
|
||||
class Handler {
|
||||
/**
|
||||
* Register all ActivityPub handlers.
|
||||
*/
|
||||
public static function register_handlers() {
|
||||
Accept::init();
|
||||
Update::init();
|
||||
Create::init();
|
||||
Delete::init();
|
||||
Undo::init();
|
||||
}
|
||||
}
|
@ -0,0 +1,542 @@
|
||||
<?php
|
||||
/**
|
||||
* ActivityPub Event Sources (=Followed Actors) Collection.
|
||||
*
|
||||
* The Event Sources are nothing else than follows in the ActivityPub world.
|
||||
* However, this plugins currently only listens to Event object being created,
|
||||
* updated or deleted by a follow.
|
||||
*
|
||||
* For the ActivityPub `Follow` the Blog-Actor from the ActivityPub plugin is used.
|
||||
*
|
||||
* This class is responsible for defining a custom post type in WordPress along
|
||||
* with post-meta fields and methods to easily manage event sources. This includes
|
||||
* handling side effects, like when an event source is added a follow request is sent
|
||||
* or adding them to the `follow` collection o the blog-actor profile.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Collection;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Activitypub\Model\Blog;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Model\Event_Source;
|
||||
use WP_Error;
|
||||
use WP_Query;
|
||||
|
||||
use function Activitypub\is_tombstone;
|
||||
use function Activitypub\get_remote_metadata_by_actor;
|
||||
|
||||
/**
|
||||
* ActivityPub Event Sources (=Followed Actors) Collection.
|
||||
*
|
||||
* The Event Sources are nothing else than follows in the ActivityPub world.
|
||||
* However, this plugins currently only listens to Event object being created,
|
||||
* updated or deleted by a follow.
|
||||
*
|
||||
* For the ActivityPub `Follow` the Blog-Actor from the ActivityPub plugin is used.
|
||||
*
|
||||
* This class is responsible for defining a custom post type in WordPress along
|
||||
* with post-meta fields and methods to easily manage event sources. This includes
|
||||
* handling side effects, like when an event source is added a follow request is sent
|
||||
* or adding them to the `follow` collection o the blog-actor profile.
|
||||
*/
|
||||
class Event_Sources {
|
||||
/**
|
||||
* The custom post type.
|
||||
*/
|
||||
const POST_TYPE = 'ap_event_source';
|
||||
|
||||
/**
|
||||
* Init.
|
||||
*/
|
||||
public static function init() {
|
||||
self::register_post_type();
|
||||
\add_action( 'event_bridge_for_activitypub_follow', array( self::class, 'activitypub_follow_actor' ), 10, 1 );
|
||||
\add_action( 'event_bridge_for_activitypub_unfollow', array( self::class, 'activitypub_unfollow_actor' ), 10, 1 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the post type used to store the external event sources (i.e., followed ActivityPub actors).
|
||||
*/
|
||||
public static function register_post_type() {
|
||||
register_post_type(
|
||||
self::POST_TYPE,
|
||||
array(
|
||||
'labels' => array(
|
||||
'name' => _x( 'Event Sources', 'post_type plural name', 'event-bridge-for-activitypub' ),
|
||||
'singular_name' => _x( 'Event Source', 'post_type single name', 'event-bridge-for-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_actor_id',
|
||||
array(
|
||||
'type' => 'string',
|
||||
'single' => true,
|
||||
'sanitize_callback' => '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_actor_json',
|
||||
array(
|
||||
'type' => 'string',
|
||||
'single' => true,
|
||||
'sanitize_callback' => function ( $value ) {
|
||||
return sanitize_text_field( $value );
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
\register_post_meta(
|
||||
self::POST_TYPE,
|
||||
'_activitypub_inbox',
|
||||
array(
|
||||
'type' => 'string',
|
||||
'single' => true,
|
||||
'sanitize_callback' => 'sanitize_url',
|
||||
)
|
||||
);
|
||||
|
||||
\register_post_meta(
|
||||
self::POST_TYPE,
|
||||
'_event_bridge_for_activitypub_utilize_announces',
|
||||
array(
|
||||
'type' => 'string',
|
||||
'single' => true,
|
||||
'sanitize_callback' => function ( $value ) {
|
||||
if ( 'same_origin' === $value ) {
|
||||
return 'same_origin';
|
||||
}
|
||||
return '';
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
\register_post_meta(
|
||||
self::POST_TYPE,
|
||||
'_event_bridge_for_activitypub_accept_of_follow',
|
||||
array(
|
||||
'type' => 'string',
|
||||
'single' => true,
|
||||
'sanitize_callback' => 'sanitize_url',
|
||||
)
|
||||
);
|
||||
|
||||
\register_post_meta(
|
||||
self::POST_TYPE,
|
||||
'_event_bridge_for_activitypub_event_count',
|
||||
array(
|
||||
'type' => 'integer',
|
||||
'single' => true,
|
||||
'sanitize_callback' => 'absint',
|
||||
'default' => '0',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add new Event Source.
|
||||
*
|
||||
* @param string $actor The Actor URL/ID.
|
||||
*
|
||||
* @return Event_Source|WP_Error|null The Followed (WP_Post array) or an WP_Error.
|
||||
*/
|
||||
public static function add_event_source( $actor ) {
|
||||
$meta = get_remote_metadata_by_actor( $actor );
|
||||
|
||||
if ( is_tombstone( $meta ) ) {
|
||||
return $meta;
|
||||
}
|
||||
|
||||
if ( \is_wp_error( $meta ) ) {
|
||||
return $meta;
|
||||
}
|
||||
|
||||
if ( empty( $meta ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$event_source = new Event_Source();
|
||||
$event_source->from_array( $meta );
|
||||
|
||||
$post_id = $event_source->save();
|
||||
|
||||
if ( \is_wp_error( $post_id ) ) {
|
||||
return $post_id;
|
||||
}
|
||||
|
||||
self::delete_event_source_transients();
|
||||
|
||||
self::queue_follow_actor( $actor );
|
||||
|
||||
return $event_source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compose the ActivityPub ID of a follow request.
|
||||
*
|
||||
* @param string $follower_id The ActivityPub ID of the actor that followers the other one.
|
||||
* @param string $followed_id The ActivityPub ID of the followed actor.
|
||||
* @return string The `Follow` ID.
|
||||
*/
|
||||
public static function compose_follow_id( $follower_id, $followed_id ) {
|
||||
return $follower_id . '#follow-' . \preg_replace( '~^https?://~', '', $followed_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all transients related to the event sources.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function delete_event_source_transients(): void {
|
||||
\delete_transient( 'event_bridge_for_activitypub_event_sources' );
|
||||
\delete_transient( 'event_bridge_for_activitypub_event_sources_hosts' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether an attachment is set as a featured image of any post.
|
||||
*
|
||||
* @param string|int $attachment_id The numeric post ID of the attachment.
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_attachment_featured_image( $attachment_id ): bool {
|
||||
if ( ! is_numeric( $attachment_id ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Query posts with the given attachment ID as their featured image.
|
||||
$args = array(
|
||||
'post_type' => 'any',
|
||||
'meta_query' => array(
|
||||
array(
|
||||
'key' => '_thumbnail_id',
|
||||
'value' => $attachment_id,
|
||||
'compare' => '=',
|
||||
),
|
||||
),
|
||||
'fields' => 'ids', // Only retrieve post IDs for performance.
|
||||
'numberposts' => 1, // We only need one match to confirm.
|
||||
);
|
||||
|
||||
$posts = \get_posts( $args );
|
||||
|
||||
return ! empty( $posts );
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all posts of an event source.
|
||||
*
|
||||
* @param int $event_source_post_id The WordPress Post ID of the event source.
|
||||
* @return void
|
||||
*/
|
||||
public static function delete_events_by_event_source( $event_source_post_id ): void {
|
||||
global $wpdb;
|
||||
$results = $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
"SELECT post_id FROM $wpdb->postmeta WHERE meta_key = %s AND meta_value = %s",
|
||||
'_event_bridge_for_activitypub_event_source',
|
||||
absint( $event_source_post_id )
|
||||
)
|
||||
);
|
||||
|
||||
// If no matching posts are found, return early.
|
||||
if ( empty( $results ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Loop through the posts and delete them permanently.
|
||||
foreach ( $results as $result ) {
|
||||
// Check if the post has a thumbnail.
|
||||
$thumbnail_id = \get_post_thumbnail_id( $result->post_id );
|
||||
|
||||
if ( $thumbnail_id ) {
|
||||
// Remove the thumbnail from the post.
|
||||
\delete_post_thumbnail( $result->post_id );
|
||||
|
||||
// Delete the attachment (and its files) from the media library.
|
||||
if ( self::is_attachment_featured_image( $thumbnail_id ) ) {
|
||||
\wp_delete_attachment( $thumbnail_id, true );
|
||||
}
|
||||
}
|
||||
|
||||
\wp_delete_post( $result->post_id, true );
|
||||
}
|
||||
|
||||
// Clean up the query.
|
||||
\wp_reset_postdata();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an Event Source (=Followed ActivityPub actor).
|
||||
*
|
||||
* @param int|string $event_source_post_id The Events Sources Post ID or ActivityPub ID.
|
||||
*
|
||||
* @return void Post data on success, false or null on failure.
|
||||
*/
|
||||
public static function remove_event_source( $event_source_post_id ): void {
|
||||
$event_source = Event_Source::get_by_id( $event_source_post_id );
|
||||
|
||||
if ( ! $event_source ) {
|
||||
return;
|
||||
}
|
||||
|
||||
self::delete_events_by_event_source( $event_source->get__id() );
|
||||
|
||||
self::delete_event_source_transients();
|
||||
|
||||
// Temporary hack.
|
||||
$post = \get_post( $event_source->get__id() );
|
||||
$post->post_status = 'draft';
|
||||
|
||||
if ( $post instanceof \WP_Post ) {
|
||||
$post = \get_object_vars( $post );
|
||||
$post = \wp_slash( $post );
|
||||
$post = \wp_update_post( $post );
|
||||
}
|
||||
|
||||
self::queue_unfollow_actor( $event_source->get_id() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all Event-Sources.
|
||||
*
|
||||
* It returns associative arrays, where the keys are the WordPress post IDs and the values are the ActivityPub IDs.
|
||||
* For example:
|
||||
* array(
|
||||
* 15 => 'https://remote.example/actors/event_organizer1',
|
||||
* 19 => 'https://remote2.example/users/johnmastodon',
|
||||
* )
|
||||
*
|
||||
* @return array List of `Event Sources`: <WP_Post-ID> => <ActivityPub-ID>
|
||||
*/
|
||||
public static function get_event_sources(): array {
|
||||
$event_sources = get_transient( 'event_bridge_for_activitypub_event_sources' );
|
||||
|
||||
if ( $event_sources && is_array( $event_sources ) ) {
|
||||
return $event_sources;
|
||||
}
|
||||
|
||||
$event_sources = self::get_event_sources_with_count()['actors'];
|
||||
|
||||
set_transient( 'event_bridge_for_activitypub_event_sources', $event_sources );
|
||||
|
||||
return $event_sources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Event Sources along with a total count for pagination purposes.
|
||||
*
|
||||
* @param int $number Maximum number of results to return.
|
||||
* @param int $page Page number.
|
||||
* @param array $args The WP_Query arguments.
|
||||
*
|
||||
* @return array {
|
||||
* Data about the event sources.
|
||||
*
|
||||
* @type array $actors List of `Event Sources`: <WP_Post-ID> => <ActivityPub-ID>
|
||||
* @type int $total Total number of followers.
|
||||
* }
|
||||
*/
|
||||
public static function get_event_sources_with_count( $number = -1, $page = null, $args = array() ): array {
|
||||
$defaults = array(
|
||||
'post_type' => self::POST_TYPE,
|
||||
'posts_per_page' => $number,
|
||||
'paged' => $page,
|
||||
'orderby' => 'ID',
|
||||
'order' => 'DESC',
|
||||
'post_status' => array( 'publish', 'pending', 'draft', 'auto-draft', 'future', 'private', 'inherit' ),
|
||||
);
|
||||
|
||||
$args = wp_parse_args( $args, $defaults );
|
||||
$query = new WP_Query( $args );
|
||||
$total = $query->found_posts;
|
||||
$actors = array();
|
||||
|
||||
foreach ( $query->get_posts() as $post ) {
|
||||
$actors[ $post->ID ] = get_post_meta( $post->ID, '_activitypub_actor_id', true );
|
||||
}
|
||||
|
||||
$event_sources = compact( 'actors', 'total' );
|
||||
|
||||
return $event_sources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue a hook to run async.
|
||||
*
|
||||
* @param string $hook The hook name.
|
||||
* @param array $args The arguments to pass to the hook.
|
||||
* @param string $unqueue_hook Optional a hook to unschedule before queuing.
|
||||
* @return void|bool|WP_Error Whether the hook was queued.
|
||||
*/
|
||||
public static function queue( $hook, $args, $unqueue_hook = null ) {
|
||||
if ( $unqueue_hook ) {
|
||||
$hook_timestamp = \wp_next_scheduled( $unqueue_hook, $args );
|
||||
if ( $hook_timestamp ) {
|
||||
\wp_unschedule_event( $hook_timestamp, $unqueue_hook, $args );
|
||||
}
|
||||
}
|
||||
|
||||
if ( \wp_next_scheduled( $hook, $args ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
return \wp_schedule_single_event( \time(), $hook, $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare to follow an ActivityPub actor via a scheduled event.
|
||||
*
|
||||
* @param string $actor The ActivityPub actor.
|
||||
*
|
||||
* @return bool Whether the event was queued.
|
||||
*/
|
||||
public static function queue_follow_actor( $actor ): bool {
|
||||
$queued = self::queue(
|
||||
'event_bridge_for_activitypub_follow',
|
||||
array( $actor ),
|
||||
'event_bridge_for_activitypub_unfollow'
|
||||
);
|
||||
|
||||
if ( \is_wp_error( $queued ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Following this actor has already been queued.
|
||||
if ( null === $queued ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $queued;
|
||||
}
|
||||
|
||||
/**
|
||||
* Follow an ActivityPub actor via the Blog user.
|
||||
*
|
||||
* @param string $actor_id The ID/URL of the Actor.
|
||||
*/
|
||||
public static function activitypub_follow_actor( $actor_id ) {
|
||||
$actor = Event_Source::get_by_id( $actor_id );
|
||||
|
||||
if ( ! $actor ) {
|
||||
return $actor;
|
||||
}
|
||||
|
||||
$inbox = $actor->get_shared_inbox();
|
||||
$to = $actor->get_id();
|
||||
|
||||
$from_actor = new Blog();
|
||||
|
||||
$activity = new \Activitypub\Activity\Activity();
|
||||
$activity->set_type( 'Follow' );
|
||||
$activity->set_to( null );
|
||||
$activity->set_cc( null );
|
||||
$activity->set_actor( $from_actor->get_id() );
|
||||
$activity->set_object( $to );
|
||||
$activity->set_id( self::compose_follow_id( $from_actor->get_id(), $to ) );
|
||||
$activity = $activity->to_json();
|
||||
\Activitypub\safe_remote_post( $inbox, $activity, \Activitypub\Collection\Actors::BLOG_USER_ID );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare to unfollow an actor via a scheduled event.
|
||||
*
|
||||
* @param string $actor The ActivityPub actor ID.
|
||||
*
|
||||
* @return bool|void|WP_Error Whether the event was queued.
|
||||
*/
|
||||
public static function queue_unfollow_actor( $actor ) {
|
||||
$queued = self::queue(
|
||||
'event_bridge_for_activitypub_unfollow',
|
||||
array( $actor ),
|
||||
'event_bridge_for_activitypub_follow'
|
||||
);
|
||||
|
||||
if ( \is_wp_error( $queued ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Following this actor has already been queued.
|
||||
if ( null === $queued ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $queued;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unfollow an ActivityPub actor.
|
||||
*
|
||||
* @param string $actor The ActivityPub ID of the actor to unfollow.
|
||||
* @return void
|
||||
*/
|
||||
public static function activitypub_unfollow_actor( $actor ): void {
|
||||
$actor = Event_Source::get_by_id( $actor );
|
||||
|
||||
if ( ! $actor ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$inbox = $actor->get_shared_inbox();
|
||||
$to = $actor->get_id();
|
||||
|
||||
$from_actor = new Blog();
|
||||
|
||||
if ( ! $inbox ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$activity = new \Activitypub\Activity\Activity();
|
||||
$activity->set_type( 'Undo' );
|
||||
$activity->set_to( null );
|
||||
$activity->set_cc( null );
|
||||
$activity->set_actor( $from_actor->get_id() );
|
||||
$activity->set_object(
|
||||
array(
|
||||
'type' => 'Follow',
|
||||
'actor' => $actor,
|
||||
'object' => $to,
|
||||
'id' => self::compose_follow_id( $from_actor->get_id(), $to ),
|
||||
)
|
||||
);
|
||||
$activity->set_id( $from_actor->get_id() . '#unfollow-' . \preg_replace( '~^https?://~', '', $to ) );
|
||||
$activity = $activity->to_json();
|
||||
\Activitypub\safe_remote_post( $inbox, $activity, \Activitypub\Collection\Actors::BLOG_USER_ID );
|
||||
|
||||
$actor->delete();
|
||||
|
||||
self::delete_event_source_transients();
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
<?php
|
||||
/**
|
||||
* Accept handler file.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later */
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Handler;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Activitypub\Collection\Actors;
|
||||
use Activitypub\Model\Blog;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Model\Event_Source;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Collection\Event_Sources as Event_Sources_Collection;
|
||||
|
||||
use function Activitypub\object_to_uri;
|
||||
|
||||
/**
|
||||
* Handle Accept requests.
|
||||
*/
|
||||
class Accept {
|
||||
/**
|
||||
* Initialize the class, registering the handler for incoming `Accept` activities to the ActivityPub plugin.
|
||||
*/
|
||||
public static function init(): void {
|
||||
\add_action(
|
||||
'activitypub_inbox_accept',
|
||||
array( self::class, 'handle_accept' ),
|
||||
15,
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle incoming "Accept" activities.
|
||||
*
|
||||
* @param array $activity The activity-object.
|
||||
* @param int $user_id The id of the local blog-user.
|
||||
*/
|
||||
public static function handle_accept( $activity, $user_id ): void {
|
||||
// We only process activities that are target to the blog actor.
|
||||
if ( Actors::BLOG_USER_ID !== $user_id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check that we are actually following/or have a pending follow request this actor.
|
||||
$event_source_post_id = Event_Source::get_post_id_by_activitypub_id( $activity['actor'] );
|
||||
if ( ! $event_source_post_id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// This is what the ID of the follow request would look like.
|
||||
$application = new Blog();
|
||||
$follow_id = Event_Sources_Collection::compose_follow_id( $application->get_id(), $activity['actor'] );
|
||||
|
||||
// Check if the object of the `Accept` is indeed the `Follow` request we sent to that actor.
|
||||
if ( object_to_uri( $activity['object'] ) !== $follow_id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Save the accept status of the follow request to the event source post.
|
||||
\update_post_meta( $event_source_post_id, '_event_bridge_for_activitypub_accept_of_follow', $activity['id'] );
|
||||
\wp_update_post(
|
||||
array(
|
||||
'ID' => $event_source_post_id,
|
||||
'post_status' => 'publish',
|
||||
)
|
||||
);
|
||||
|
||||
// Trigger the backfilling of events from this actor.
|
||||
\do_action( 'event_bridge_for_activitypub_backfill_events', $event_source_post_id );
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
<?php
|
||||
/**
|
||||
* Create handler file.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Handler;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Activitypub\Collection\Actors;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Model\Event_Source;
|
||||
use Event_Bridge_For_ActivityPub\Event_Sources;
|
||||
use Event_Bridge_For_ActivityPub\Setup;
|
||||
|
||||
use function Activitypub\is_activity_public;
|
||||
|
||||
/**
|
||||
* Handle Create requests.
|
||||
*/
|
||||
class Create {
|
||||
/**
|
||||
* Initialize the class, registering the handler for incoming `Create` activities to the ActivityPub plugin.
|
||||
*/
|
||||
public static function init(): void {
|
||||
\add_action(
|
||||
'activitypub_inbox_create',
|
||||
array( self::class, 'handle_create' ),
|
||||
15,
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle incoming "Create" activities.
|
||||
*
|
||||
* @param array $activity The activity-object.
|
||||
* @param int $user_id The id of the local blog-user.
|
||||
*/
|
||||
public static function handle_create( $activity, $user_id ): void {
|
||||
// We only process activities that are target to the blog actor.
|
||||
if ( Actors::BLOG_USER_ID !== $user_id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if Activity is public or not.
|
||||
if ( ! is_activity_public( $activity ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if an object is set and it is an object of type `Event`.
|
||||
if ( ! isset( $activity['object']['type'] ) || 'Event' !== $activity['object']['type'] ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check that we are actually following/or have a pending follow request this actor.
|
||||
$event_source_post_id = Event_Source::get_post_id_by_activitypub_id( $activity['actor'] );
|
||||
if ( ! $event_source_post_id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( Event_Sources::is_time_passed( $activity['object']['startTime'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$transmogrifier = Setup::get_transmogrifier();
|
||||
|
||||
if ( ! $transmogrifier ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$transmogrifier::save( $activity['object'], $event_source_post_id );
|
||||
}
|
||||
}
|
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
/**
|
||||
* Delete handler file.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Handler;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Activitypub\Collection\Actors;
|
||||
use Event_Bridge_For_ActivityPub\Event_Sources;
|
||||
use Event_Bridge_For_ActivityPub\Setup;
|
||||
|
||||
use function Activitypub\object_to_uri;
|
||||
|
||||
/**
|
||||
* Handle Delete requests.
|
||||
*/
|
||||
class Delete {
|
||||
/**
|
||||
* Initialize the class, registering the handler for incoming `Delete` activities to the ActivityPub plugin.
|
||||
*/
|
||||
public static function init() {
|
||||
\add_action(
|
||||
'activitypub_inbox_delete',
|
||||
array( self::class, 'handle_delete' ),
|
||||
15,
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle "Follow" requests.
|
||||
*
|
||||
* @param array $activity The activity-object.
|
||||
* @param int $user_id The id of the local blog-user.
|
||||
*/
|
||||
public static function handle_delete( $activity, $user_id ): void {
|
||||
// We only process activities that are target to the application user.
|
||||
if ( Actors::BLOG_USER_ID !== $user_id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check that we are actually following this actor.
|
||||
if ( ! Event_Sources::actor_is_event_source( $activity['actor'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$id = object_to_uri( $activity['object'] );
|
||||
|
||||
$transmogrifier = Setup::get_transmogrifier();
|
||||
|
||||
if ( ! $transmogrifier ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$transmogrifier::delete( $id );
|
||||
}
|
||||
}
|
@ -0,0 +1,159 @@
|
||||
<?php
|
||||
/**
|
||||
* Join handler file.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Handler;
|
||||
|
||||
use Activitypub\Activity\Activity;
|
||||
use Activitypub\Collection\Actors;
|
||||
use Activitypub\Activity\Actor;
|
||||
use Activitypub\Http;
|
||||
use Activitypub\Transformer\Factory;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Event\Event as Event_Transformer;
|
||||
|
||||
use function Activitypub\get_remote_metadata_by_actor;
|
||||
use function Activitypub\object_to_uri;
|
||||
|
||||
/**
|
||||
* Handle Join requests.
|
||||
*/
|
||||
class Join {
|
||||
/**
|
||||
* Initialize the class, registering WordPress hooks.
|
||||
*/
|
||||
public static function init() {
|
||||
\add_action(
|
||||
'activitypub_register_handlers',
|
||||
array( self::class, 'register_join_handler' )
|
||||
);
|
||||
|
||||
\add_action(
|
||||
'event_bridge_for_activitypub_ignore_join',
|
||||
array( self::class, 'send_ignore_response' ),
|
||||
10,
|
||||
3
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the join handler to the ActivityPub plugin.
|
||||
*/
|
||||
public static function register_join_handler() {
|
||||
\add_action(
|
||||
'activitypub_inbox_join',
|
||||
array( self::class, 'handle_join' )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle ActivityPub "Join" requests.
|
||||
*
|
||||
* @param array $activity The activity object.
|
||||
*/
|
||||
public static function handle_join( $activity ) {
|
||||
$actor = get_remote_metadata_by_actor( object_to_uri( $activity['actor'] ) );
|
||||
|
||||
// If we cannot fetch the actor, we cannot continue.
|
||||
if ( \is_wp_error( $actor ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// This should be already validated, but just to be sure.
|
||||
if ( ! array_key_exists( 'object', $activity ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the WordPress Post ID, via the ActivityPub ID.
|
||||
$post_id = self::get_post_id_by_activitypub_id( \sanitize_url( object_to_uri( $activity['object'] ) ) );
|
||||
|
||||
if ( ! $post_id ) {
|
||||
// No post is found for this URL/ID.
|
||||
return;
|
||||
}
|
||||
|
||||
// Check whether the target object/post is an event post.
|
||||
|
||||
$transformer = Factory::get_transformer( get_post( $post_id ) );
|
||||
|
||||
if ( ! $transformer instanceof Event_Transformer ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Pass over to Event plugin specific handler if implemented here. Until then just send an ignore.
|
||||
do_action(
|
||||
'event_bridge_for_activitypub_ignore_join',
|
||||
$transformer->get_actor_object()->get_id(), // Gets the WordPress user that "owns" the object by ActivityPub means.
|
||||
$activity['id'],
|
||||
Actor::init_from_array( $actor )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send "Ignore" response.
|
||||
*
|
||||
* @param string $actor The actors ActivityPub ID which sends the response.
|
||||
* @param string $ignored The ID of the Activity that gets ignored.
|
||||
* @param Actor|mixed $to The target actor.
|
||||
*/
|
||||
public static function send_ignore_response( $actor, $ignored, $to ) {
|
||||
// Get actor object that owns the object that was targeted by the ignored activity.
|
||||
$actor = Actors::get_by_resource( $actor );
|
||||
|
||||
if ( \is_wp_error( $to ) || \is_wp_error( $actor ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get inbox.
|
||||
$inbox = $to->get_inbox();
|
||||
|
||||
if ( ! $inbox ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Send "Ignore" activity.
|
||||
$activity = new Activity();
|
||||
$activity->set_type( 'Ignore' );
|
||||
$activity->set_object( \esc_url_raw( $ignored ) );
|
||||
$activity->set_actor( $actor->get_id() );
|
||||
$activity->set_to( $to->get_id() );
|
||||
$activity->set_id( $actor->get_id() . '#ignore-' . \preg_replace( '~^https?://~', '', $ignored ) );
|
||||
$activity->set_sensitive( null );
|
||||
|
||||
// @phpstan-ignore-next-line
|
||||
Http::post( $inbox, $activity->to_json(), $actor->get__id() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the WordPress Post ID by the ActivityPub ID.
|
||||
*
|
||||
* @param string $activitypub_id The ActivityPub objects ID.
|
||||
* @return int The WordPress post ID.
|
||||
*/
|
||||
private static function get_post_id_by_activitypub_id( $activitypub_id ) {
|
||||
// Parse the URL and extract its components.
|
||||
$parsed_url = wp_parse_url( $activitypub_id );
|
||||
$home_url = \trailingslashit( \home_url() );
|
||||
|
||||
// Ensure the base URL matches the home URL.
|
||||
if ( \trailingslashit( "{$parsed_url['scheme']}://{$parsed_url['host']}" ) !== $home_url ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Check for a valid query string and parse it.
|
||||
if ( isset( $parsed_url['query'] ) ) {
|
||||
parse_str( $parsed_url['query'], $query_vars );
|
||||
|
||||
// Ensure the only parameter is 'p'.
|
||||
if ( count( $query_vars ) === 1 && isset( $query_vars['p'] ) ) {
|
||||
return intval( $query_vars['p'] ); // Return the post ID.
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: legacy ActivityPub plugin (before version 3.0.0) used pretty permalinks as `id`.
|
||||
return \url_to_postid( $activitypub_id );
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
/**
|
||||
* Undo handler file.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later */
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Handler;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Activitypub\Collection\Actors;
|
||||
use Event_Bridge_For_ActivityPub\Event_Sources;
|
||||
|
||||
use function Activitypub\object_to_uri;
|
||||
|
||||
/**
|
||||
* Handle Uno requests.
|
||||
*/
|
||||
class Undo {
|
||||
/**
|
||||
* Initialize the class, registering the handler for incoming `Uno` activities to the ActivityPub plugin.
|
||||
*/
|
||||
public static function init(): void {
|
||||
\add_action(
|
||||
'activitypub_inbox_undo',
|
||||
array( self::class, 'handle_undo' ),
|
||||
15,
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle incoming "Undo" activities.
|
||||
*
|
||||
* @param array $activity The activity-object.
|
||||
* @param int $user_id The id of the local blog-user.
|
||||
*/
|
||||
public static function handle_undo( $activity, $user_id ): void {
|
||||
// We only process activities that are target to the blog actor.
|
||||
if ( Actors::BLOG_USER_ID !== $user_id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check that we are actually following/or have a pending follow request for this actor.
|
||||
if ( ! Event_Sources::actor_is_event_source( $activity['actor'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$accept_id = \sanitize_url( object_to_uri( $activity['object'] ) );
|
||||
|
||||
global $wpdb;
|
||||
|
||||
$results = $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
"SELECT post_id FROM $wpdb->postmeta WHERE meta_key = %s AND meta_value = %s",
|
||||
'_event_bridge_for_activitypub_accept_of_follow',
|
||||
$accept_id
|
||||
)
|
||||
);
|
||||
|
||||
// If no event source with that accept ID is found return.
|
||||
if ( empty( $results ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$post_id = reset( $results )->post_id;
|
||||
|
||||
\delete_post_meta( $post_id, '_event_bridge_for_activitypub_accept_of_follow' );
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
<?php
|
||||
/**
|
||||
* Update handler file.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Handler;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Activitypub\Collection\Actors;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Model\Event_Source;
|
||||
use Event_Bridge_For_ActivityPub\Event_Sources;
|
||||
use Event_Bridge_For_ActivityPub\Setup;
|
||||
|
||||
use function Activitypub\is_activity_public;
|
||||
|
||||
/**
|
||||
* Handle Update requests.
|
||||
*/
|
||||
class Update {
|
||||
/**
|
||||
* Initialize the class, registering the handler for incoming `Update` activities to the ActivityPub plugin.
|
||||
*/
|
||||
public static function init(): void {
|
||||
\add_action(
|
||||
'activitypub_inbox_update',
|
||||
array( self::class, 'handle_update' ),
|
||||
15,
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle incoming "Update" activities..
|
||||
*
|
||||
* @param array $activity The activity-object.
|
||||
* @param int $user_id The id of the local blog-user.
|
||||
*/
|
||||
public static function handle_update( $activity, $user_id ): void {
|
||||
// We only process activities that are target to the application user.
|
||||
if ( Actors::BLOG_USER_ID !== $user_id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if Activity is public or not.
|
||||
if ( ! is_activity_public( $activity ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if an object is set and it is an object of type `Event`.
|
||||
if ( ! isset( $activity['object']['type'] ) || 'Event' !== $activity['object']['type'] ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check that we are actually following/or have a pending follow request this actor.
|
||||
$event_source_post_id = Event_Source::get_post_id_by_activitypub_id( $activity['actor'] );
|
||||
if ( ! $event_source_post_id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( Event_Sources::is_time_passed( $activity['object']['startTime'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$transmogrifier = Setup::get_transmogrifier();
|
||||
|
||||
if ( ! $transmogrifier ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$transmogrifier::save( $activity['object'], $event_source_post_id );
|
||||
}
|
||||
}
|
@ -0,0 +1,371 @@
|
||||
<?php
|
||||
/**
|
||||
* Event-Source (=ActivityPub Actor that is followed) model.
|
||||
*
|
||||
* This class holds methods needed for relating an ActivityPub actor
|
||||
* that is followed with the custom post type structure how it is
|
||||
* stored within WordPress.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Model;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Activitypub\Activity\Actor;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Collection\Event_Sources;
|
||||
use WP_Error;
|
||||
use WP_Post;
|
||||
|
||||
/**
|
||||
* Event-Source (=ActivityPub Actor that is followed) model.
|
||||
*
|
||||
* This class holds methods needed for relating an ActivityPub actor
|
||||
* that is followed with the custom post type structure how it is
|
||||
* stored within WordPress.
|
||||
*
|
||||
* @method ?string get_published()
|
||||
* @method string get_id()
|
||||
* @method ?string get_name()
|
||||
* @method ?string get_updated()
|
||||
* @method int get__id()
|
||||
* @method ?string get_status()
|
||||
* @method ?string get_summary()
|
||||
* @method Event_Source set_published(string $published)
|
||||
* @method Event_Source set_id(string $id)
|
||||
* @method Event_Source set_name(string $name)
|
||||
* @method Event_Source set_updated(string $updated)
|
||||
* @method Event_Source set__id(int $id)
|
||||
* @method Event_Source set_status(string $status)
|
||||
* @method Event_Source set_summary(string $summary)
|
||||
* @method ?string get_inbox()
|
||||
* @method string|array get_icon()
|
||||
* @method array get_endpoints()
|
||||
*/
|
||||
class Event_Source extends Actor {
|
||||
const ACTIVITYPUB_USER_HANDLE_REGEXP = '(?:([A-Za-z0-9_.-]+)@((?:[A-Za-z0-9_-]+\.)+[A-Za-z]+))';
|
||||
|
||||
/**
|
||||
* The WordPress Post ID which stores the event source.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $_id; // phpcs:ignore PSR2.Classes.PropertyDeclaration.Underscore
|
||||
|
||||
/**
|
||||
* The WordPress post status of the post which stores the event source.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $status; // phpcs:ignore PSR2.Classes.PropertyDeclaration.Underscore
|
||||
|
||||
/**
|
||||
* Get the Icon URL (Avatar).
|
||||
*
|
||||
* @return string The URL to the Avatar.
|
||||
*/
|
||||
public function get_icon_url(): string {
|
||||
$icon = $this->get_icon();
|
||||
|
||||
if ( is_string( $icon ) ) {
|
||||
return $icon;
|
||||
}
|
||||
|
||||
if ( isset( $icon['url'] ) && is_string( $icon['url'] ) ) {
|
||||
return $icon['url'];
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the Post-IDs of all events cached by this event source.
|
||||
*/
|
||||
public static function get_cached_events(): array {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for URL attribute.
|
||||
*
|
||||
* @return string The URL.
|
||||
*/
|
||||
public function get_url() {
|
||||
if ( $this->url ) {
|
||||
return $this->url;
|
||||
}
|
||||
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the outbox.
|
||||
*
|
||||
* @return ?string The outbox URL.
|
||||
*/
|
||||
public function get_outbox() {
|
||||
if ( $this->outbox ) {
|
||||
return $this->outbox;
|
||||
}
|
||||
|
||||
$actor_json = \get_post_meta( $this->get__id(), '_activitypub_actor_json', true );
|
||||
|
||||
if ( ! $actor_json ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$actor = \json_decode( $actor_json, true );
|
||||
|
||||
if ( ! isset( $actor['outbox'] ) ) {
|
||||
\do_action( 'event_bridge_for_activitypub_write_log', array( "[ACTIVITYPUB] Did not find outbox URL for actor {$actor}" ) );
|
||||
return null;
|
||||
}
|
||||
|
||||
return $actor['outbox'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Event Source Post ID by the ActivityPub ID.
|
||||
*
|
||||
* @param string $activitypub_actor_id The ActivityPub actor ID.
|
||||
* @return int|false The Event Sources Post ID, if a WordPress Post representing it is found, false otherwise.
|
||||
*/
|
||||
public static function get_post_id_by_activitypub_id( $activitypub_actor_id ) {
|
||||
$event_sources = Event_Sources::get_event_sources();
|
||||
|
||||
return array_search( $activitypub_actor_id, $event_sources, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Event Source by the ActivityPub ID or WordPress Post ID.
|
||||
*
|
||||
* @param int|string $event_source_id The ActivityPub actor ID as string or the Post ID as int of the Event Source.
|
||||
* @return ?Event_Source The Event Sources if it exists, false otherwise.
|
||||
*/
|
||||
public static function get_by_id( $event_source_id ): ?Event_Source {
|
||||
$post_id = is_integer( $event_source_id ) ? $event_source_id : self::get_post_id_by_activitypub_id( $event_source_id );
|
||||
|
||||
if ( ! $post_id ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get Custom Post.
|
||||
$event_source_post = \get_post( $post_id );
|
||||
|
||||
if ( ! $event_source_post ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Init From Custom Post.
|
||||
$event_source = self::init_from_cpt( $event_source_post );
|
||||
|
||||
if ( ! $event_source ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $event_source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a Custom-Post-Type input to an \Event_Bridge_For_ActivityPub\ActivityPub\Model\Event_Source.
|
||||
*
|
||||
* @param \WP_Post $post The post object.
|
||||
* @return ?Event_Source
|
||||
*/
|
||||
public static function init_from_cpt( $post ): ?Event_Source {
|
||||
if ( Event_Sources::POST_TYPE !== $post->post_type ) {
|
||||
return null;
|
||||
}
|
||||
$actor_json = \get_post_meta( $post->ID, '_activitypub_actor_json', true );
|
||||
$object = static::init_from_json( $actor_json );
|
||||
|
||||
if ( \is_wp_error( $object ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$object->set__id( $post->ID );
|
||||
$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_date ) ) );
|
||||
$object->set_updated( gmdate( 'Y-m-d H:i:s', strtotime( $post->post_modified ) ) );
|
||||
$object->set_status( $post->post_status );
|
||||
$thumbnail_id = \get_post_thumbnail_id( $post );
|
||||
if ( $thumbnail_id ) {
|
||||
$object->set_icon(
|
||||
array(
|
||||
'type' => 'Image',
|
||||
'url' => \wp_get_attachment_image_url( $thumbnail_id, 'thumbnail', true ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! $object instanceof Event_Source ) { // To make phpstan happy.
|
||||
return null;
|
||||
}
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the current Event Source ActivityPub actor object.
|
||||
*
|
||||
* @return boolean True if the verification was successful.
|
||||
*/
|
||||
public function is_valid(): bool {
|
||||
// The minimum required attributes.
|
||||
$required_attributes = array(
|
||||
'id',
|
||||
'preferredUsername',
|
||||
'inbox',
|
||||
'publicKey',
|
||||
'publicKeyPem',
|
||||
);
|
||||
|
||||
foreach ( $required_attributes as $attribute ) {
|
||||
if ( ! $this->get( $attribute ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the post meta.
|
||||
*/
|
||||
protected function get_post_meta_input() {
|
||||
$meta_input = array();
|
||||
$meta_input['_activitypub_inbox'] = \sanitize_url( $this->get_shared_inbox() );
|
||||
$meta_input['_activitypub_actor_json'] = $this->to_json();
|
||||
$meta_input['_activitypub_actor_id'] = $this->get_id();
|
||||
|
||||
return $meta_input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the shared inbox, with a fallback to the inbox.
|
||||
*
|
||||
* @return string|null The URL to the shared inbox, the inbox or null.
|
||||
*/
|
||||
public function get_shared_inbox() {
|
||||
if ( ! empty( $this->get_endpoints()['sharedInbox'] ) ) {
|
||||
return $this->get_endpoints()['sharedInbox'];
|
||||
} elseif ( ! empty( $this->get_inbox() ) ) {
|
||||
return $this->get_inbox();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the current Event Source object to Database within custom post type.
|
||||
*
|
||||
* @return int|WP_Error The post ID or an WP_Error.
|
||||
*/
|
||||
public function save() {
|
||||
Event_Sources::delete_event_source_transients();
|
||||
|
||||
if ( ! $this->is_valid() ) {
|
||||
return new WP_Error( 'activitypub_invalid_follower', __( 'Invalid Follower', 'event-bridge-for-activitypub' ), array( 'status' => 400 ) );
|
||||
}
|
||||
|
||||
if ( ! $this->get__id() ) {
|
||||
global $wpdb;
|
||||
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
|
||||
$post_id = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT post_id FROM $wpdb->postmeta WHERE meta_key='_activitypub_actor_id' AND meta_value=%s",
|
||||
esc_sql( $this->get_id() )
|
||||
)
|
||||
);
|
||||
|
||||
if ( $post_id ) {
|
||||
$post = get_post( $post_id );
|
||||
$this->set__id( $post->ID );
|
||||
}
|
||||
}
|
||||
|
||||
$post_id = $this->get__id();
|
||||
|
||||
$args = array(
|
||||
'ID' => $post_id,
|
||||
'post_title' => wp_strip_all_tags( sanitize_text_field( $this->get_name() ) ),
|
||||
'post_author' => 0,
|
||||
'post_type' => Event_Sources::POST_TYPE,
|
||||
'post_name' => esc_url_raw( $this->get_id() ),
|
||||
'post_excerpt' => sanitize_text_field( wp_kses( $this->get_summary(), 'user_description' ) ),
|
||||
'post_status' => 'pending',
|
||||
'meta_input' => $this->get_post_meta_input(),
|
||||
);
|
||||
|
||||
if ( ! empty( $post_id ) ) {
|
||||
// If this is an update, prevent the "added" date from being overwritten by the current date.
|
||||
$post = get_post( $post_id );
|
||||
$args['post_date'] = $post->post_date;
|
||||
$args['post_date_gmt'] = $post->post_date_gmt;
|
||||
}
|
||||
|
||||
$post_id = \wp_insert_post( $args );
|
||||
$this->_id = $post_id;
|
||||
|
||||
// Abort if inserting or updating the post didn't work.
|
||||
|
||||
// @phpstan-ignore-next-line
|
||||
if ( is_wp_error( $post_id ) || 0 === $post_id ) {
|
||||
return $post_id;
|
||||
}
|
||||
|
||||
// Delete old icon.
|
||||
// Check if the post has a thumbnail.
|
||||
$thumbnail_id = get_post_thumbnail_id( $post_id );
|
||||
|
||||
if ( $thumbnail_id ) {
|
||||
// Remove the thumbnail from the post.
|
||||
delete_post_thumbnail( $post_id );
|
||||
|
||||
// Delete the attachment (and its files) from the media library.
|
||||
wp_delete_attachment( $thumbnail_id, true );
|
||||
}
|
||||
|
||||
// Set new icon.
|
||||
$icon = $this->get_icon();
|
||||
|
||||
if ( isset( $icon['url'] ) ) {
|
||||
$image = media_sideload_image( sanitize_url( $icon['url'] ), $post_id, null, 'id' );
|
||||
}
|
||||
if ( isset( $image ) && ! is_wp_error( $image ) ) {
|
||||
set_post_thumbnail( $post_id, $image );
|
||||
}
|
||||
|
||||
return $post_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an Event Source and it's profile image.
|
||||
*/
|
||||
public function delete() {
|
||||
$post_id = $this->get__id();
|
||||
|
||||
if ( ! $post_id ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$thumbnail_id = get_post_thumbnail_id( $post_id );
|
||||
|
||||
if ( $thumbnail_id ) {
|
||||
wp_delete_attachment( $thumbnail_id, true );
|
||||
}
|
||||
|
||||
$result = wp_delete_post( $post_id, false ) ?? false;
|
||||
|
||||
if ( $result ) {
|
||||
Event_Sources::delete_event_source_transients();
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
@ -0,0 +1,107 @@
|
||||
<?php
|
||||
/**
|
||||
* Event Post scheduler class file.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Scheduler;
|
||||
|
||||
use Event_Bridge_For_ActivityPub\Setup;
|
||||
|
||||
use function Activitypub\add_to_outbox;
|
||||
|
||||
/**
|
||||
* Event Post scheduler class.
|
||||
*/
|
||||
class Event {
|
||||
/**
|
||||
* Initialize the class, registering WordPress hooks.
|
||||
*/
|
||||
public static function init() {
|
||||
\add_action( 'transition_post_status', array( self::class, 'maybe_schedule_event_post_activity' ), 50, 3 );
|
||||
\add_action( 'event_bridge_for_activitypub_add_event_post_to_outbox', array( self::class, 'add_event_post_to_outbox' ), 10, 3 );
|
||||
\add_filter( 'activitypub_is_post_disabled', array( self::class, 'is_post_disabled_for_the_activitypub_plugin' ), 50, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevent the ActivityPub plugin from dealing with event posts.
|
||||
*
|
||||
* @param bool $disabled Whether the post is already marked as disabled.
|
||||
* @param \WP_Post $post The WordPress post.
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_post_disabled_for_the_activitypub_plugin( $disabled, $post ): bool {
|
||||
if ( $disabled ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( Setup::get_instance()->is_event_post_type_of_active_event_plugin( $post->post_type ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule Activities.
|
||||
*
|
||||
* @param string $new_status New post status.
|
||||
* @param string $old_status Old post status.
|
||||
* @param \WP_Post $post Post object.
|
||||
*/
|
||||
public static function maybe_schedule_event_post_activity( $new_status, $old_status, $post ): void {
|
||||
if ( defined( 'WP_IMPORTING' ) && WP_IMPORTING ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! Setup::get_instance()->is_event_post_type_of_active_event_plugin( $post->post_type ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( Setup::is_post_disabled( $post ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch ( $new_status ) {
|
||||
case 'publish':
|
||||
$type = ( 'publish' === $old_status ) ? 'Update' : 'Create';
|
||||
break;
|
||||
|
||||
case 'draft':
|
||||
$type = ( 'publish' === $old_status ) ? 'Update' : false;
|
||||
break;
|
||||
|
||||
case 'trash':
|
||||
$type = 'Delete';
|
||||
break;
|
||||
|
||||
default:
|
||||
$type = false;
|
||||
}
|
||||
|
||||
// Do not send Activities if `$type` is not set or unknown.
|
||||
if ( empty( $type ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$hook = 'event_bridge_for_activitypub_add_event_post_to_outbox';
|
||||
$args = array( $post, $type, $post->post_author );
|
||||
|
||||
if ( false === \wp_next_scheduled( $hook, $args ) ) {
|
||||
\wp_schedule_single_event( \time() + 10, $hook, $args );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an event post to the outbox.
|
||||
*
|
||||
* @param \WP_Post $post The WP_Post object to add to the outbox.
|
||||
* @param string $activity_type The type of the Activity.
|
||||
* @param integer $user_id The User-ID.
|
||||
*/
|
||||
public static function add_event_post_to_outbox( $post, $activity_type, $user_id ): void {
|
||||
$post = get_post( $post->ID );
|
||||
add_to_outbox( $post, $activity_type, $user_id );
|
||||
}
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
<?php
|
||||
/**
|
||||
* ActivityPub Transformer for the plugin Event Organiser.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Event;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Activitypub\Activity\Extended_Object\Place;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Event\Event as Base_Event_Transformer;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Place\Event_Organiser as Event_Organiser_Location_Transformer;
|
||||
use WP_Post;
|
||||
|
||||
/**
|
||||
* ActivityPub Transformer for Event Organiser.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class Event_Organiser extends Base_Event_Transformer {
|
||||
/**
|
||||
* The events occurances.
|
||||
*
|
||||
* @var ?array
|
||||
*/
|
||||
protected $schedule;
|
||||
|
||||
/**
|
||||
* Extended constructor.
|
||||
*
|
||||
* The item is overridden with a the item with filters. This object
|
||||
* also contains attributes specific to the Event organiser plugin like the
|
||||
* occurrence id.
|
||||
*
|
||||
* @param WP_Post $item The WordPress object.
|
||||
* @param string $wp_taxonomy The taxonomy slug of the event post type.
|
||||
*/
|
||||
public function __construct( $item, $wp_taxonomy ) {
|
||||
parent::__construct( $item, $wp_taxonomy );
|
||||
$this->schedule = \eo_get_event_schedule( $item->ID );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the end time from the event object.
|
||||
*/
|
||||
public function get_end_time(): string {
|
||||
return $this->schedule['end']->format( 'Y-m-d\TH:i:sP' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the start time from the event object.
|
||||
*/
|
||||
public function get_start_time(): string {
|
||||
return $this->schedule['start']->format( 'Y-m-d\TH:i:sP' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get location from the event object.
|
||||
*/
|
||||
public function get_location(): ?Place {
|
||||
$venue = \get_the_terms( $this->item->ID, 'event-venue' );
|
||||
|
||||
if ( empty( $venue ) || is_wp_error( $venue ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$venue = array_pop( $venue );
|
||||
|
||||
$location_transformer = new Event_Organiser_Location_Transformer( $venue );
|
||||
$location = $location_transformer->to_object();
|
||||
|
||||
return $location;
|
||||
}
|
||||
}
|
@ -0,0 +1,609 @@
|
||||
<?php
|
||||
/**
|
||||
* Replace the default ActivityPub Transformer
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Event;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Activitypub\Activity\Extended_Object\Event as Event_Object;
|
||||
use Activitypub\Activity\Extended_Object\Place;
|
||||
use Activitypub\Shortcodes;
|
||||
use Activitypub\Transformer\Post;
|
||||
use DateTime;
|
||||
use WP_Comment;
|
||||
use WP_Post;
|
||||
|
||||
/**
|
||||
* Base transformer for WordPress event post types to ActivityPub events.
|
||||
*
|
||||
* Everything that transforming several WordPress post types that represent events
|
||||
* have in common, as well as sane defaults for events should be defined here.
|
||||
*
|
||||
* BeforeFirstRelease:
|
||||
* [ ] remove link at the end of the content.
|
||||
* [ ] add organizer.
|
||||
* [ ] do add Cancelled reason in the content.
|
||||
*/
|
||||
abstract class Event extends Post {
|
||||
/**
|
||||
* The WordPress event taxonomy.
|
||||
*
|
||||
* @var ?string
|
||||
*/
|
||||
protected $wp_taxonomy;
|
||||
|
||||
/**
|
||||
* Returns the ActivityStreams 2.0 Object-Type for an Event.
|
||||
*
|
||||
* @see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-event
|
||||
*
|
||||
* @return string The Event Object-Type.
|
||||
*/
|
||||
protected function get_type(): string {
|
||||
return 'Event';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a sane default for whether comments are enabled.
|
||||
*/
|
||||
protected function get_comments_enabled(): ?bool {
|
||||
return \comments_open( $this->item );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a hardcoded template for the content.
|
||||
*
|
||||
* This actually disabled templates for the content.
|
||||
* Maybe this independent templates for events will be added later.
|
||||
*/
|
||||
protected function get_post_content_template(): string {
|
||||
return '[ap_content]';
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend the construction of the Post Transformer to also set the according taxonomy of the event post type.
|
||||
*
|
||||
* @param \WP_Post $item The WordPress post object (event).
|
||||
* @param string $wp_taxonomy The taxonomy slug of the event post type.
|
||||
*/
|
||||
public function __construct( $item, $wp_taxonomy = 'category' ) {
|
||||
parent::__construct( $item );
|
||||
$this->wp_taxonomy = $wp_taxonomy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the join mode.
|
||||
*
|
||||
* Currently we don't handle joins, we always mark events as external.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_join_mode(): ?string {
|
||||
return 'external';
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the external participation url.
|
||||
*
|
||||
* Currently we don't handle joins, we always mark events as external.
|
||||
* We just link back to the events HTML representation on our WordPress site.
|
||||
*
|
||||
* @return ?string The external participation URL.
|
||||
*/
|
||||
public function get_external_participation_url(): ?string {
|
||||
return 'external' === $this->get_join_mode() ? $this->get_url() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the event category, via the mapping setting.
|
||||
*
|
||||
* @return ?string
|
||||
*/
|
||||
public function get_category(): ?string {
|
||||
if ( is_null( $this->wp_taxonomy ) ) {
|
||||
return null;
|
||||
}
|
||||
$current_category_mapping = \get_option( 'event_bridge_for_activitypub_event_category_mappings', array() );
|
||||
$terms = \get_the_terms( $this->item, $this->wp_taxonomy );
|
||||
|
||||
// Check if the event has a category set and if that category has a specific mapping return that one.
|
||||
if ( ! is_wp_error( $terms ) && $terms && array_key_exists( $terms[0]->slug, $current_category_mapping ) ) {
|
||||
return sanitize_text_field( $current_category_mapping[ $terms[0]->slug ] );
|
||||
} else {
|
||||
// Return the default event category.
|
||||
return sanitize_text_field( \get_option( 'event_bridge_for_activitypub_default_event_category', 'MEETING' ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the excerpt text (may be HTML). Used for constructing the summary.
|
||||
*
|
||||
* @return ?string
|
||||
*/
|
||||
protected function retrieve_excerpt(): ?string {
|
||||
if ( $this->item->post_excerpt ) {
|
||||
return $this->item->post_excerpt;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the start time.
|
||||
*
|
||||
* This is mandatory and must be implemented in the final event transformer class.
|
||||
*/
|
||||
abstract public function get_start_time(): string;
|
||||
|
||||
/**
|
||||
* Get the end time.
|
||||
*
|
||||
* This is not mandatory and therefore just return null by default.
|
||||
*/
|
||||
public function get_end_time(): ?string {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a default for the location.
|
||||
*
|
||||
* This should be overridden in the actual event transformer.
|
||||
*
|
||||
* @return array|Place|null
|
||||
*/
|
||||
public function get_location() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default value for the event status.
|
||||
*/
|
||||
public function get_status(): ?string {
|
||||
return 'CONFIRMED';
|
||||
}
|
||||
|
||||
/**
|
||||
* Compose a human readable formatted start time.
|
||||
*/
|
||||
protected function format_start_time(): string {
|
||||
return $this->format_time( $this->get_start_time() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Compose a human readable formatted end time.
|
||||
*/
|
||||
protected function format_end_time(): string {
|
||||
return $this->format_time( $this->get_end_time() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Compose a human readable formatted time.
|
||||
*
|
||||
* @param ?string $time The time which needs to be formatted.
|
||||
*/
|
||||
protected static function format_time( $time ) {
|
||||
if ( is_null( $time ) ) {
|
||||
return '';
|
||||
}
|
||||
$start_datetime = new DateTime( $time );
|
||||
$start_timestamp = $start_datetime->getTimestamp();
|
||||
$datetime_format = get_option( 'date_format' ) . ' ' . get_option( 'time_format' );
|
||||
return \wp_date( $datetime_format, $start_timestamp );
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates output for the 'ap_start_time' shortcode.
|
||||
*
|
||||
* @param ?array $atts The shortcode's attributes.
|
||||
* @return string The formatted start date and time of the event.
|
||||
*/
|
||||
public function shortcode_start_time( $atts ) {
|
||||
$start_timestamp = $this->get_start_time();
|
||||
return $this->generate_time_output( $start_timestamp, $atts, '🗓️', __( 'Start', 'event-bridge-for-activitypub' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates output for the 'ap_end_time' shortcode.
|
||||
*
|
||||
* @param ?array $atts The shortcode's attributes.
|
||||
* @return string The formatted end date and time of the event.
|
||||
*/
|
||||
public function shortcode_end_time( $atts ) {
|
||||
$end_timestamp = $this->get_end_time();
|
||||
return $this->generate_time_output( $end_timestamp, $atts, '⏳', __( 'End', 'event-bridge-for-activitypub' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the formatted time output for a shortcode.
|
||||
*
|
||||
* @param string|null $timestamp The timestamp for the event time.
|
||||
* @param array $atts The shortcode attributes.
|
||||
* @param string $icon The icon to display.
|
||||
* @param string $label The label to display (e.g., 'Start', 'End').
|
||||
* @return string The formatted date and time, or an empty string if the timestamp is invalid.
|
||||
*/
|
||||
private function generate_time_output( $timestamp, $atts, $icon, $label ): string {
|
||||
if ( ! $timestamp ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$args = shortcode_atts(
|
||||
array(
|
||||
'icon' => 'true',
|
||||
'label' => 'true',
|
||||
),
|
||||
$atts
|
||||
);
|
||||
|
||||
$args['icon'] = filter_var( $args['icon'], FILTER_VALIDATE_BOOLEAN );
|
||||
$args['label'] = filter_var( $args['label'], FILTER_VALIDATE_BOOLEAN );
|
||||
|
||||
$output = array();
|
||||
|
||||
if ( $args['icon'] ) {
|
||||
$output[] = $icon;
|
||||
}
|
||||
|
||||
if ( $args['label'] ) {
|
||||
$output[] = $label . ':';
|
||||
}
|
||||
|
||||
$output[] = self::format_time( $timestamp );
|
||||
|
||||
return implode( ' ', $output );
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates output for the 'ap_location' shortcode.
|
||||
*
|
||||
* @param ?array $atts The shortcode's attributes.
|
||||
* @return string The formatted location/address of the event.
|
||||
*/
|
||||
public function shortcode_location( $atts ) {
|
||||
$args = shortcode_atts(
|
||||
array(
|
||||
'icon' => 'true',
|
||||
'label' => 'true',
|
||||
'country' => 'true',
|
||||
'zip' => 'true',
|
||||
'city' => 'true',
|
||||
'street' => 'true',
|
||||
),
|
||||
$atts,
|
||||
'ap_location'
|
||||
);
|
||||
|
||||
// Convert attributes to booleans.
|
||||
$args = array_map(
|
||||
function ( $value ) {
|
||||
return filter_var( $value, FILTER_VALIDATE_BOOLEAN );
|
||||
},
|
||||
$args
|
||||
);
|
||||
|
||||
$location = $this->get_location();
|
||||
if ( ! $location ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$output = array();
|
||||
|
||||
if ( is_array( $location ) && isset( $location['type'] ) && 'VirtualLocation' === $location['type'] ) {
|
||||
if ( $args['icon'] ) {
|
||||
$output[] = '🔗';
|
||||
}
|
||||
if ( $args['label'] && isset( $location['name'] ) ) {
|
||||
$output[] = $location['name'] . ':';
|
||||
}
|
||||
} else {
|
||||
if ( $args['icon'] ) {
|
||||
$output[] = '📍';
|
||||
}
|
||||
|
||||
if ( $args['label'] ) {
|
||||
$output[] = esc_html__( 'Location', 'event-bridge-for-activitypub' ) . ':';
|
||||
}
|
||||
}
|
||||
|
||||
$output[] = $this->get_formatted_address( true, $args );
|
||||
|
||||
// Join output array into a single string with spaces and return.
|
||||
return implode( ' ', array_filter( $output ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the address based on provided arguments.
|
||||
*
|
||||
* @param mixed $address The address data, either as a string or an array.
|
||||
* @param array $args The arguments for which components to include.
|
||||
* @return string The formatted address.
|
||||
*/
|
||||
protected static function format_address( $address, $args = array() ) {
|
||||
if ( is_string( $address ) ) {
|
||||
return esc_html( $address );
|
||||
}
|
||||
|
||||
if ( empty( $args ) ) {
|
||||
$args = array(
|
||||
'icon' => 'true',
|
||||
'title' => 'true',
|
||||
'country' => 'true',
|
||||
'zip' => 'true',
|
||||
'city' => 'true',
|
||||
'street' => 'true',
|
||||
);
|
||||
}
|
||||
|
||||
if ( is_array( $address ) ) {
|
||||
$address_parts = array();
|
||||
|
||||
$components = array(
|
||||
'street' => 'streetAddress',
|
||||
'zip' => 'postalCode',
|
||||
'city' => 'addressLocality',
|
||||
'country' => 'addressCountry',
|
||||
);
|
||||
|
||||
foreach ( $components as $arg_key => $address_key ) {
|
||||
if ( $args[ $arg_key ] && ! empty( $address[ $address_key ] ) ) {
|
||||
$address_parts[] = esc_html( $address[ $address_key ] );
|
||||
}
|
||||
}
|
||||
|
||||
return implode( ', ', $address_parts );
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the category using the translation.
|
||||
*/
|
||||
protected function format_categories(): string {
|
||||
if ( is_null( $this->wp_taxonomy ) ) {
|
||||
return '';
|
||||
}
|
||||
$categories = array();
|
||||
|
||||
// Add the federated category string.
|
||||
require_once EVENT_BRIDGE_FOR_ACTIVITYPUB_PLUGIN_DIR . '/includes/event-categories.php';
|
||||
$federated_category = $this->get_category();
|
||||
if ( array_key_exists( $federated_category, EVENT_BRIDGE_FOR_ACTIVITYPUB_EVENT_CATEGORIES ) ) {
|
||||
$categories[] = EVENT_BRIDGE_FOR_ACTIVITYPUB_EVENT_CATEGORIES[ $federated_category ];
|
||||
}
|
||||
|
||||
// Add all category terms.
|
||||
$terms = \get_the_terms( $this->item, $this->wp_taxonomy );
|
||||
if ( $terms && ! is_wp_error( $terms ) ) {
|
||||
foreach ( $terms as $term ) {
|
||||
$categories[] = $term->name;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $categories ) ) {
|
||||
return implode( ' · ', array_unique( $categories ) );
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the shortcodes.
|
||||
*/
|
||||
public function register_shortcodes() {
|
||||
Shortcodes::register();
|
||||
foreach ( array( 'location', 'start_time', 'end_time' ) as $shortcode ) {
|
||||
\add_shortcode( 'ap_' . $shortcode, array( $this, 'shortcode_' . $shortcode ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the shortcodes.
|
||||
*/
|
||||
public function unregister_shortcodes() {
|
||||
Shortcodes::unregister();
|
||||
foreach ( array( 'location', 'start_time', 'end_time' ) as $shortcode ) {
|
||||
\remove_shortcode( 'ap_' . $shortcode );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the summary.
|
||||
*/
|
||||
public function get_summary(): ?string {
|
||||
if ( 'preset' === get_option( 'event_bridge_for_activitypub_summary_type', 'preset' ) ) {
|
||||
$summary = EVENT_BRIDGE_FOR_ACTIVITYPUB_SUMMARY_TEMPLATE;
|
||||
} else {
|
||||
$summary = $this->get_event_summary_template();
|
||||
}
|
||||
|
||||
// It seems that shortcodes are only applied to published posts.
|
||||
if ( is_preview() ) {
|
||||
$this->item->post_status = 'publish';
|
||||
}
|
||||
|
||||
// Register our shortcodes just in time.
|
||||
|
||||
$this->register_shortcodes();
|
||||
|
||||
// Fill in the shortcodes.
|
||||
\setup_postdata( $this->item );
|
||||
Shortcodes::register();
|
||||
$summary = \do_shortcode( $summary );
|
||||
\wp_reset_postdata();
|
||||
|
||||
$summary = \wpautop( $summary );
|
||||
$summary = \preg_replace( '/[\n\r\t]/', '', $summary );
|
||||
$summary = \trim( $summary );
|
||||
|
||||
$summary = \apply_filters( 'event_bridge_for_activitypub_the_summary', $summary, $this->item );
|
||||
|
||||
// Unregister the shortcodes.
|
||||
$this->unregister_shortcodes();
|
||||
|
||||
if ( 'plain' === get_option( 'event_bridge_for_activitypub_summary_format', 'html' ) ) {
|
||||
$summary = self::strip_html_preserve_linebreaks( $summary );
|
||||
}
|
||||
|
||||
return $summary;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip all HTML but preverse some line breaks.
|
||||
*
|
||||
* @param mixed $content The HTML input.
|
||||
* @return string
|
||||
*/
|
||||
private static function strip_html_preserve_linebreaks( $content ): string {
|
||||
// Replace <br> with newlines.
|
||||
$content = preg_replace( '/<br\s*\/?>/i', "\n", $content );
|
||||
|
||||
// Replace closing </p> followed by <p> with double newlines (preserve paragraph breaks).
|
||||
$content = preg_replace( '/<\/p>\s*<p>/', "\n\n", $content );
|
||||
|
||||
// Preserve list structure.
|
||||
$content = preg_replace( '/<\/ul>/i', "\n", $content );
|
||||
$content = preg_replace( '/<li>/i', '- ', $content );
|
||||
$content = preg_replace( '/<\/li>/i', "\n", $content );
|
||||
|
||||
// Remove all remaining HTML tags.
|
||||
$content = wp_strip_all_tags( $content );
|
||||
|
||||
// Normalize excessive newlines (more than 2 in a row to just 2).
|
||||
$content = preg_replace( "/\n{3,}/", "\n\n", $content );
|
||||
|
||||
// Trim trailing newlines.
|
||||
return trim( $content );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the address as a string.
|
||||
*
|
||||
* @param bool $include_location_name Whether to include the locations name.
|
||||
* @param array $args The arguments forwarded to format_address.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_formatted_address( $include_location_name = false, $args = array() ) {
|
||||
$location = $this->get_location();
|
||||
|
||||
if ( $location instanceof Place ) {
|
||||
$location_name = $location->get_name();
|
||||
$foramted_address = self::format_address( $location->get_address(), $args );
|
||||
|
||||
$loaction_parts = array();
|
||||
|
||||
if ( $location_name ) {
|
||||
$location_parts[] = $location_name;
|
||||
}
|
||||
|
||||
if ( $foramted_address ) {
|
||||
$location_parts[] = $foramted_address;
|
||||
}
|
||||
|
||||
if ( ! empty( $location_parts ) ) {
|
||||
return implode( ', ', $location_parts );
|
||||
}
|
||||
} elseif ( is_array( $location ) && isset( $location['type'] ) && 'VirtualLocation' === $location['type'] ) {
|
||||
if ( isset( $location['url'] ) ) {
|
||||
return $location['url'];
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the template to use to generate the summary of the ActivityStreams representation of an event post.
|
||||
*
|
||||
* @return string The Template.
|
||||
*/
|
||||
protected function get_event_summary_template() {
|
||||
$summary = \get_option( 'event_bridge_for_activitypub_custom_summary', EVENT_BRIDGE_FOR_ACTIVITYPUB_SUMMARY_TEMPLATE );
|
||||
$template = $summary ?? EVENT_BRIDGE_FOR_ACTIVITYPUB_SUMMARY_TEMPLATE;
|
||||
|
||||
return apply_filters( 'event_bridge_for_activitypub_summary_template', $template, $this->item );
|
||||
}
|
||||
|
||||
/**
|
||||
* By default set the timezone of the WordPress site.
|
||||
*
|
||||
* This is likely to be overwritten by the actual transformer.
|
||||
*
|
||||
* @return string The timezone string of the site.
|
||||
*/
|
||||
public function get_timezone(): string {
|
||||
return \wp_timezone_string();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the permalink shortcode from a WordPress template.
|
||||
*
|
||||
* This used for the summary template, because the summary usually gets,
|
||||
* used when converting a object, where the URL is usually appended anyway.
|
||||
*
|
||||
* @param string $template The template string.
|
||||
* @param WP_Post|WP_Comment $item The item which was used to select the template.
|
||||
*/
|
||||
public static function remove_ap_permalink_from_template( $template, $item ) {
|
||||
|
||||
// we could override the template here, to get out custom template from an option.
|
||||
|
||||
if ( 'event' === $item->post_type ) {
|
||||
$template = str_replace( '[ap_permalink]', '', $template );
|
||||
$template = str_replace( '[ap_permalink type="html"]', '', $template );
|
||||
}
|
||||
|
||||
return $template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic function that converts an WP-Event object to an ActivityPub-Event object.
|
||||
*
|
||||
* @return Event_object|\WP_Error
|
||||
*/
|
||||
public function to_object() {
|
||||
$activitypub_object = new Event_Object();
|
||||
$activitypub_object = $this->transform_object_properties( $activitypub_object );
|
||||
|
||||
// @phpstan-ignore-next-line
|
||||
$activitypub_object->set_comments_enabled( $this->get_comments_enabled() );
|
||||
|
||||
if ( \is_wp_error( $activitypub_object ) ) {
|
||||
return $activitypub_object;
|
||||
}
|
||||
|
||||
// maybe move the following logic (till end of the function) into getter functions.
|
||||
|
||||
$published = \strtotime( $this->item->post_date_gmt );
|
||||
|
||||
$activitypub_object->set_published( \gmdate( 'Y-m-d\TH:i:s\Z', $published ) );
|
||||
|
||||
$updated = \strtotime( $this->item->post_modified_gmt );
|
||||
|
||||
if ( $updated > $published ) {
|
||||
$activitypub_object->set_updated( \gmdate( 'Y-m-d\TH:i:s\Z', $updated ) );
|
||||
}
|
||||
|
||||
$activitypub_object->set_content_map(
|
||||
array(
|
||||
$this->get_locale() => $this->get_content(),
|
||||
)
|
||||
);
|
||||
|
||||
$activitypub_object->set_to(
|
||||
array(
|
||||
'https://www.w3.org/ns/activitystreams#Public',
|
||||
$this->get_actor_object()->get_followers(),
|
||||
)
|
||||
);
|
||||
|
||||
// @phpstan-ignore-next-line
|
||||
return $activitypub_object;
|
||||
}
|
||||
}
|
@ -0,0 +1,166 @@
|
||||
<?php
|
||||
/**
|
||||
* ActivityPub Transformer for Events managed with Eventin.
|
||||
*
|
||||
* @link https://support.themewinter.com/docs/plugins/docs-category/eventin/
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Event;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Activitypub\Activity\Extended_Object\Place;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Event\Event;
|
||||
use Etn\Core\Event\Event_Model;
|
||||
|
||||
use function Activitypub\esc_hashtag;
|
||||
|
||||
/**
|
||||
* ActivityPub Transformer for Events managed with Eventin.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class Eventin extends Event {
|
||||
|
||||
/**
|
||||
* Holds the EM_Event object.
|
||||
*
|
||||
* @var Event_Model
|
||||
*/
|
||||
protected $event_model;
|
||||
|
||||
/**
|
||||
* Extend the constructor, to also set the Event Model.
|
||||
*
|
||||
* This is a special class object form The Events Calendar which
|
||||
* has a lot of useful functions, we make use of our getter functions.
|
||||
*
|
||||
* @param \WP_Post $item The WordPress object.
|
||||
* @param string $wp_taxonomy The taxonomy slug of the event post type.
|
||||
*/
|
||||
public function __construct( $item, $wp_taxonomy ) {
|
||||
parent::__construct( $item, $wp_taxonomy );
|
||||
$this->event_model = new Event_Model( $this->item->ID );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the end time from the event object.
|
||||
*/
|
||||
public function get_start_time(): string {
|
||||
return \gmdate( 'Y-m-d\TH:i:s\Z', strtotime( $this->event_model->get_start_datetime() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the end time from the event object.
|
||||
*/
|
||||
public function get_end_time(): string {
|
||||
return \gmdate( 'Y-m-d\TH:i:s\Z', strtotime( $this->event_model->get_end_datetime() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the timezone of the event.
|
||||
*/
|
||||
public function get_timezone(): string {
|
||||
return $this->event_model->get_timezone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether the event is online.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function get_is_online(): bool {
|
||||
return 'online' === $this->event_model->__get( 'event_type' ) ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maybe add online link to attachments.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_attachment(): array {
|
||||
$attachment = parent::get_attachment();
|
||||
|
||||
$location = (array) $this->event_model->__get( 'location' );
|
||||
|
||||
// @phpstan-ignore-next-line
|
||||
if ( array_key_exists( 'integration', $location ) && array_key_exists( $location['integration'], $location ) ) {
|
||||
$online_link = array(
|
||||
'type' => 'Link',
|
||||
'mediaType' => 'text/html',
|
||||
'name' => $location[ $location['integration'] ],
|
||||
'href' => $location[ $location['integration'] ],
|
||||
);
|
||||
$attachment[] = $online_link;
|
||||
}
|
||||
return $attachment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compose the events tags.
|
||||
*/
|
||||
public function get_tag(): ?array {
|
||||
// The parent tag function also fetches the mentions.
|
||||
$tags = parent::get_tag();
|
||||
|
||||
$post_tags = \wp_get_post_terms( $this->item->ID, 'etn_tags' );
|
||||
$post_categories = \wp_get_post_terms( $this->item->ID, 'etn_category' );
|
||||
|
||||
if ( ! is_wp_error( $post_tags ) && $post_tags ) {
|
||||
foreach ( $post_tags as $term ) {
|
||||
$tag = array(
|
||||
'type' => 'Hashtag',
|
||||
'href' => \esc_url( \get_tag_link( $term->term_id ) ),
|
||||
'name' => esc_hashtag( $term->name ),
|
||||
);
|
||||
$tags[] = $tag;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! is_wp_error( $post_categories ) && $post_categories ) {
|
||||
foreach ( $post_categories as $term ) {
|
||||
$tag = array(
|
||||
'type' => 'Hashtag',
|
||||
'href' => \esc_url( \get_tag_link( $term->term_id ) ),
|
||||
'name' => esc_hashtag( $term->name ),
|
||||
);
|
||||
$tags[] = $tag;
|
||||
}
|
||||
}
|
||||
|
||||
if ( empty( $tags ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the location.
|
||||
*
|
||||
* @return ?Place
|
||||
*/
|
||||
public function get_location(): ?Place {
|
||||
$location = (array) $this->event_model->__get( 'location' );
|
||||
|
||||
// @phpstan-ignore-next-line
|
||||
if ( ! array_key_exists( 'address', $location ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// @phpstan-ignore-next-line
|
||||
$place = new Place();
|
||||
|
||||
$address = $location['address'];
|
||||
|
||||
$place->set_name( $address );
|
||||
$place->set_address( $address );
|
||||
$place->set_sensitive( null );
|
||||
|
||||
return $place;
|
||||
}
|
||||
}
|
@ -0,0 +1,186 @@
|
||||
<?php
|
||||
/**
|
||||
* Class file for the ActivityPub Transformer for events the WordPress plugin EventON – Events Calendar.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Event;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Activitypub\Activity\Extended_Object\Place;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Event\Event as Event_Transformer;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Place\EventOn as EventOn_Place_Transformer;
|
||||
|
||||
/**
|
||||
* Class for the ActivityPub Transformer for events the WordPress plugin EventON – Events Calendar.
|
||||
*
|
||||
* This transformer tries a different principle: The setters are chainable.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class EventOn extends Event_Transformer {
|
||||
/**
|
||||
* The location meta for all locations.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $tax_meta;
|
||||
|
||||
/**
|
||||
* Extend the construction of the Post Transformer to also set the according taxonomy of the event post type.
|
||||
*
|
||||
* @param \WP_Post $item The WordPress post object (event).
|
||||
* @param string $wp_taxonomy The taxonomy slug of the event post type.
|
||||
*/
|
||||
public function __construct( $item, $wp_taxonomy = 'category' ) {
|
||||
parent::__construct( $item );
|
||||
$this->wp_taxonomy = $wp_taxonomy;
|
||||
|
||||
$this->tax_meta = \get_option( 'evo_tax_meta' );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get content.
|
||||
*/
|
||||
public function get_content(): string {
|
||||
$subtitle = \get_post_meta( $this->item->ID, 'evcal_subtitle', true );
|
||||
|
||||
$content = $subtitle ? $subtitle . '<br>' : '';
|
||||
$content = $content . parent::get_content();
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the event location(s).
|
||||
*
|
||||
* @return array|null The Place.
|
||||
*/
|
||||
public function get_location() {
|
||||
$location = array();
|
||||
|
||||
$terms = \get_the_terms( $this->item->ID, 'event_location' );
|
||||
|
||||
// The terms may both contain virtual and physical Locations.
|
||||
if ( ! empty( $terms ) ) {
|
||||
foreach ( $terms as $term ) {
|
||||
$location_transformer = new EventOn_Place_Transformer( $term );
|
||||
$location[] = $location_transformer->to_object()->to_array( false );
|
||||
}
|
||||
}
|
||||
|
||||
// Virtual Locations can also be directly int the post meta, not in terms!
|
||||
$virtual_url = \get_post_meta( $this->item->ID, '_vir_url', true );
|
||||
$virtual_type = \get_post_meta( $this->item->ID, '_vir_type', true );
|
||||
|
||||
if ( $virtual_url ) {
|
||||
$virtual_location = array(
|
||||
'type' => 'VirtualLocation',
|
||||
'url' => (string) $virtual_url,
|
||||
);
|
||||
if ( $virtual_type ) {
|
||||
$virtual_location['name'] = (string) $virtual_type;
|
||||
}
|
||||
$location[] = $virtual_location;
|
||||
}
|
||||
|
||||
// If we only have one location, send object directy, not in array.
|
||||
if ( 1 === count( $location ) ) {
|
||||
$location = reset( $location );
|
||||
} elseif ( empty( $location ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $location;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the end time from the events metadata.
|
||||
*/
|
||||
public function get_end_time(): ?string {
|
||||
$end_time = \get_post_meta( $this->item->ID, '_unix_end_ev', true );
|
||||
$timezone = \get_post_meta( $this->item->ID, '_evo_tz', true );
|
||||
$timezone = $timezone ? new \DateTimeZone( $timezone ) : null;
|
||||
|
||||
if ( is_null( $end_time ) || empty( $end_time ) ) {
|
||||
return null;
|
||||
}
|
||||
return \wp_date( 'Y-m-d\TH:i:sP', (int) $end_time, $timezone );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get timezone
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_timezone(): string {
|
||||
$timezone = \get_post_meta( $this->item->ID, '_evo_tz', true );
|
||||
|
||||
return $timezone ?? \wp_timezone_string();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the end time from the events metadata.
|
||||
*/
|
||||
public function get_start_time(): string {
|
||||
$start_time = \get_post_meta( $this->item->ID, '_unix_start_ev', true );
|
||||
$timezone = \get_post_meta( $this->item->ID, '_evo_tz', true );
|
||||
$timezone = $timezone ? new \DateTimeZone( $timezone ) : null;
|
||||
|
||||
return \wp_date( 'Y-m-d\TH:i:sP', (int) $start_time, $timezone );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the event link from the events metadata.
|
||||
*
|
||||
* @return ?array Associated array of an ActivityStreams Link object with the events URL.
|
||||
*/
|
||||
private function get_event_link(): ?array {
|
||||
$event_link = \get_post_meta( $this->item->ID, 'event-link', true );
|
||||
$event_link_label = \get_post_meta( $this->item->ID, 'event-link-label', true ) ?? 'Event Link';
|
||||
if ( $event_link ) {
|
||||
return array(
|
||||
'type' => 'Link',
|
||||
'name' => $event_link_label,
|
||||
'href' => \esc_url( $event_link ),
|
||||
'mediaType' => 'text/html',
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides/extends the get_attachments function to also add the event Link.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_attachment(): array {
|
||||
$attachments = parent::get_attachment();
|
||||
if ( count( $attachments ) ) {
|
||||
$attachments[0]['type'] = 'Document';
|
||||
$attachments[0]['name'] = 'Banner';
|
||||
}
|
||||
$event_link = $this->get_event_link();
|
||||
if ( $event_link ) {
|
||||
$attachments[] = $event_link;
|
||||
}
|
||||
return $attachments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the excerpt text (may be HTML). Used for constructing the summary.
|
||||
*
|
||||
* @return ?string
|
||||
*/
|
||||
protected function retrieve_excerpt(): ?string {
|
||||
if ( \get_post_meta( $this->item->ID, 'event-summary', true ) ) {
|
||||
return \get_post_meta( $this->item->ID, 'event-summary', true );
|
||||
} else {
|
||||
return parent::retrieve_excerpt();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
/**
|
||||
* ActivityPub Transformer for the plugin EventPrime.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Event;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Activitypub\Activity\Extended_Object\Place;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Event\Event as Base_Event_Transformer;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Place\EventPrime as EventPrime_Location_Transformer;
|
||||
|
||||
/**
|
||||
* ActivityPub Transformer for VS Event
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class EventPrime extends Base_Event_Transformer {
|
||||
/**
|
||||
* Get the end time from the event object.
|
||||
*/
|
||||
public function get_end_time(): ?string {
|
||||
$timestamp = \get_post_meta( $this->wp_object->ID, 'em_end_date', true );
|
||||
if ( $timestamp ) {
|
||||
return \gmdate( 'Y-m-d\TH:i:s\Z', $timestamp );
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the end time from the event object.
|
||||
*/
|
||||
public function get_start_time(): string {
|
||||
$timestamp = \get_post_meta( $this->wp_object->ID, 'em_start_date', true );
|
||||
if ( $timestamp ) {
|
||||
return \gmdate( 'Y-m-d\TH:i:s\Z', $timestamp );
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get location from the event object.
|
||||
*/
|
||||
public function get_location(): ?Place {
|
||||
$venue_term_id = \get_post_meta( $this->item->ID, 'em_venue', true );
|
||||
if ( ! $venue_term_id ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$venue = \get_the_terms( $this->item->ID, 'em_venue' );
|
||||
|
||||
if ( empty( $venue ) || is_wp_error( $venue ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$venue = array_pop( $venue );
|
||||
|
||||
$location_transformer = new EventPrime_Location_Transformer( $venue );
|
||||
$location = $location_transformer->to_object();
|
||||
|
||||
return $location;
|
||||
}
|
||||
}
|
@ -0,0 +1,248 @@
|
||||
<?php
|
||||
/**
|
||||
* ActivityPub Transformer for the plugin Very Simple Event List.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Event;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Activitypub\Activity\Extended_Object\Place;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Event\Event as Event_Transformer;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Place\Events_Manager as Events_Manager_Place_Transformer;
|
||||
use DateTime;
|
||||
use DateTimeZone;
|
||||
use EM_Event;
|
||||
|
||||
use function Activitypub\esc_hashtag;
|
||||
|
||||
/**
|
||||
* ActivityPub Transformer for events from the WordPress plugin 'Events Manager'
|
||||
*
|
||||
* @see https://wordpress.org/plugins/events-manager/
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class Events_Manager extends Event_Transformer {
|
||||
|
||||
/**
|
||||
* Holds the EM_Event object.
|
||||
*
|
||||
* @var EM_Event
|
||||
*/
|
||||
protected $em_event;
|
||||
|
||||
/**
|
||||
* Extend the constructor, to also set the Events Manager objects.
|
||||
*
|
||||
* This is a special class object form The Events Calendar which
|
||||
* has a lot of useful functions, we make use of our getter functions.
|
||||
*
|
||||
* @param \WP_Post $item The WordPress object.
|
||||
* @param string $wp_taxonomy The taxonomy slug of the event post type.
|
||||
*/
|
||||
public function __construct( $item, $wp_taxonomy ) {
|
||||
parent::__construct( $item, $wp_taxonomy );
|
||||
$this->em_event = new EM_Event( $this->item->ID, 'post_id' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the even is online
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function is_online(): bool {
|
||||
return \EM_Event_Locations\Event_Locations::is_enabled( 'url' ) && 'url' === $this->em_event->event_location_type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the event location.
|
||||
*
|
||||
* @return array|Place|null The Place.
|
||||
*/
|
||||
public function get_location() {
|
||||
if ( $this->is_online() ) {
|
||||
if ( property_exists( $this->em_event->event_location, 'data' ) ) {
|
||||
$event_location = $this->em_event->event_location->data;
|
||||
} else {
|
||||
$event_location = array();
|
||||
}
|
||||
|
||||
$event_link_url = isset( $event_location['url'] ) ? $event_location['url'] : null;
|
||||
$event_link_text = isset( $event_location['text'] ) ? $event_location['text'] : esc_html__( 'Link', 'event-bridge-for-activitypub' );
|
||||
|
||||
if ( empty( $event_link_url ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return array(
|
||||
'type' => 'VirtualLocation',
|
||||
'url' => \esc_url( $event_link_url ),
|
||||
'name' => \esc_html( $event_link_text ),
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! \EM_Locations::is_enabled() ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$em_location = $this->em_event->get_location();
|
||||
|
||||
if ( ! isset( $em_location->post_id ) || ! $em_location->post_id ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$location_transformer = new Events_Manager_Place_Transformer( get_post( $em_location->post_id ) );
|
||||
$full_location_object = false;
|
||||
$location = $location_transformer->to_object( $full_location_object );
|
||||
return $location;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the end time from the events metadata.
|
||||
*/
|
||||
public function get_end_time(): ?string {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the end time from the events metadata.
|
||||
*/
|
||||
public function get_start_time(): string {
|
||||
$date_string = $this->em_event->event_start_date;
|
||||
$time_string = $this->em_event->event_start_time;
|
||||
$timezone_string = $this->em_event->event_timezone;
|
||||
|
||||
// Create a DateTime object with the given date, time, and timezone.
|
||||
$datetime = new DateTime( $date_string . ' ' . $time_string, new DateTimeZone( $timezone_string ) );
|
||||
|
||||
// Set the timezone for proper formatting.
|
||||
$datetime->setTimezone( new DateTimeZone( 'UTC' ) );
|
||||
|
||||
// Format the DateTime object as 'Y-m-d\TH:i:s\Z'.
|
||||
$formatted_date = $datetime->format( 'Y-m-d\TH:i:s\Z' );
|
||||
return $formatted_date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the maximum attendee capacity.
|
||||
*
|
||||
* @return ?int
|
||||
*/
|
||||
public function get_maximum_attendee_capacity() {
|
||||
return $this->em_event->event_spaces;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the remaining attendee capacity
|
||||
*
|
||||
* @return ?int
|
||||
*/
|
||||
public function get_remaining_attendee_capacity(): ?int {
|
||||
$em_bookings_count = $this->get_participant_count();
|
||||
$max_bookings = $this->em_event->event_spaces;
|
||||
|
||||
if ( $max_bookings && $em_bookings_count ) {
|
||||
return $this->em_event->event_spaces - $em_bookings_count;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current participant count.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_participant_count(): int {
|
||||
$em_bookings = $this->em_event->get_bookings()->get_bookings();
|
||||
return count( $em_bookings->bookings );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the event link as an ActivityPub Link object, but as an associative array.
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
private function get_event_link_attachment(): ?array {
|
||||
if ( $this->is_online() ) {
|
||||
if ( property_exists( $this->em_event->event_location, 'data' ) ) {
|
||||
$event_location = $this->em_event->event_location->data;
|
||||
} else {
|
||||
$event_location = array();
|
||||
}
|
||||
|
||||
$event_link_url = isset( $event_location['url'] ) ? $event_location['url'] : null;
|
||||
$event_link_text = isset( $event_location['text'] ) ? $event_location['text'] : __( 'Link', 'event-bridge-for-activitypub' );
|
||||
|
||||
if ( empty( $event_link_url ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return array(
|
||||
'type' => 'Link',
|
||||
'name' => \esc_html( $event_link_text ),
|
||||
'href' => \esc_url( $event_link_url ),
|
||||
'mediaType' => 'text/html',
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides/extends the get_attachments function to also add the event Link.
|
||||
*/
|
||||
protected function get_attachment(): array {
|
||||
// Get attachments via parent function.
|
||||
$attachments = parent::get_attachment();
|
||||
|
||||
// The first attachment is the featured image, make sure it is compatible with Mobilizon.
|
||||
if ( count( $attachments ) ) {
|
||||
$attachments[0]['type'] = 'Document';
|
||||
$attachments[0]['name'] = 'Banner';
|
||||
}
|
||||
|
||||
$event_link_attachment = $this->get_event_link_attachment();
|
||||
|
||||
if ( $event_link_attachment ) {
|
||||
$attachments[] = $event_link_attachment;
|
||||
}
|
||||
return $attachments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compose the events tags.
|
||||
*/
|
||||
public function get_tag(): array {
|
||||
// The parent tag function also fetches the mentions.
|
||||
$tags = parent::get_tag();
|
||||
|
||||
$post_tags = \wp_get_post_terms( $this->item->ID, 'event-tags' );
|
||||
|
||||
if ( $post_tags ) {
|
||||
foreach ( $post_tags as $post_tag ) {
|
||||
$tag = array(
|
||||
'type' => 'Hashtag',
|
||||
'href' => \esc_url( \get_tag_link( $post_tag->term_id ) ),
|
||||
'name' => esc_hashtag( $post_tag->name ),
|
||||
);
|
||||
$tags[] = $tag;
|
||||
}
|
||||
}
|
||||
return $tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the events title/name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_name(): string {
|
||||
return $this->em_event->event_name;
|
||||
}
|
||||
}
|
@ -0,0 +1,156 @@
|
||||
<?php
|
||||
/**
|
||||
* ActivityPub Transformer for the GatherPress event plugin.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Event;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Activitypub\Activity\Extended_Object\Event as Event_Object;
|
||||
use Activitypub\Activity\Extended_Object\Place;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Event\Event;
|
||||
use GatherPress\Core\Event as GatherPress_Event;
|
||||
|
||||
/**
|
||||
* ActivityPub Transformer for VS Event
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class GatherPress extends Event {
|
||||
|
||||
/**
|
||||
* The current GatherPress Event object.
|
||||
*
|
||||
* @var GatherPress_Event
|
||||
*/
|
||||
protected $gp_event;
|
||||
|
||||
/**
|
||||
* The current GatherPress Venue.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $gp_venue;
|
||||
|
||||
/**
|
||||
* Extend the constructor, to also set the GatherPress objects.
|
||||
*
|
||||
* This is a special class object form The Events Calendar which
|
||||
* has a lot of useful functions, we make use of our getter functions.
|
||||
*
|
||||
* @param \WP_Post $item The WordPress object.
|
||||
* @param string $wp_taxonomy The taxonomy slug of the event post type.
|
||||
*/
|
||||
public function __construct( $item, $wp_taxonomy ) {
|
||||
parent::__construct( $item, $wp_taxonomy );
|
||||
$this->gp_event = new GatherPress_Event( $item->ID );
|
||||
$this->gp_venue = $this->gp_event->get_venue_information();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the event location.
|
||||
*
|
||||
* @return ?Place The place objector null if not place set.
|
||||
*/
|
||||
public function get_location(): ?Place {
|
||||
$address = $this->gp_venue['full_address'];
|
||||
if ( $address ) {
|
||||
$place = new Place();
|
||||
$place->set_type( 'Place' );
|
||||
$place->set_name( $address );
|
||||
$place->set_address( $address );
|
||||
return $place;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the end time from the event object.
|
||||
*/
|
||||
public function get_end_time(): string {
|
||||
return $this->gp_event->get_datetime_end( 'Y-m-d\TH:i:s\Z' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the end time from the event object.
|
||||
*/
|
||||
public function get_start_time(): string {
|
||||
return $this->gp_event->get_datetime_start( 'Y-m-d\TH:i:s\Z' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the event link from the events metadata.
|
||||
*/
|
||||
private function get_event_link(): array {
|
||||
|
||||
$event_link = get_post_meta( $this->item->ID, 'event-link', true );
|
||||
if ( $event_link ) {
|
||||
return array(
|
||||
'type' => 'Link',
|
||||
'name' => 'Website',
|
||||
'href' => \esc_url( $event_link ),
|
||||
'mediaType' => 'text/html',
|
||||
);
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides/extends the get_attachments function to also add the event Link.
|
||||
*/
|
||||
protected function get_attachment(): array {
|
||||
$attachments = parent::get_attachment();
|
||||
if ( count( $attachments ) ) {
|
||||
$attachments[0]['type'] = 'Document';
|
||||
$attachments[0]['name'] = 'Banner';
|
||||
}
|
||||
$event_link = $this->get_event_link();
|
||||
if ( $event_link ) {
|
||||
$attachments[] = $this->get_event_link();
|
||||
}
|
||||
return $attachments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevents gatherpress blocks from being rendered for the content.
|
||||
*
|
||||
* @param mixed $block_content The blocks content.
|
||||
* @param mixed $block The block.
|
||||
*/
|
||||
public static function filter_gatherpress_blocks( $block_content, $block ) {
|
||||
// Check if the block name starts with 'gatherpress'.
|
||||
if ( isset( $block['blockName'] ) && 0 === strpos( $block['blockName'], 'gatherpress/' ) ) {
|
||||
return ''; // Skip rendering this block.
|
||||
}
|
||||
|
||||
return $block_content; // Return the content for other blocks.
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the filter for preventing the rendering off gatherpress blocks just in time.
|
||||
*
|
||||
* @return Event_Object
|
||||
*/
|
||||
public function to_object(): Event_Object {
|
||||
add_filter( 'render_block', array( self::class, 'filter_gatherpress_blocks' ), 10, 2 );
|
||||
$activitypub_object = parent::to_object();
|
||||
remove_filter( 'render_block', array( self::class, 'filter_gatherpress_blocks' ) );
|
||||
return $activitypub_object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether the event is online.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function get_is_online(): bool {
|
||||
return $this->gp_event->maybe_get_online_event_link() ? true : false;
|
||||
}
|
||||
}
|
@ -0,0 +1,127 @@
|
||||
<?php
|
||||
/**
|
||||
* ActivityPub Tribe Transformer
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Event;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Activitypub\Activity\Extended_Object\Place;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Event\Event;
|
||||
|
||||
use MEC;
|
||||
use MEC\Events\Event as MEC_Event;
|
||||
use MEC_main;
|
||||
|
||||
/**
|
||||
* ActivityPub Tribe Transformer
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class Modern_Events_Calendar_Lite extends Event {
|
||||
/**
|
||||
* The MEC Event object.
|
||||
*
|
||||
* @var MEC_Event|null
|
||||
*/
|
||||
protected $mec_event;
|
||||
|
||||
/**
|
||||
* The MEC main instance.
|
||||
*
|
||||
* @var MEC_main|null
|
||||
*/
|
||||
protected $mec_main;
|
||||
|
||||
/**
|
||||
* Extend the constructor, to also set the tribe object.
|
||||
*
|
||||
* This is a special class object form The Events Calendar which
|
||||
* has a lot of useful functions, we make use of our getter functions.
|
||||
*
|
||||
* @param \WP_Post $item The WordPress object.
|
||||
* @param string $wp_taxonomy The taxonomy slug of the event post type.
|
||||
*/
|
||||
public function __construct( $item, $wp_taxonomy ) {
|
||||
parent::__construct( $item, $wp_taxonomy );
|
||||
$this->mec_main = MEC::getInstance( 'app.libraries.main' );
|
||||
$this->mec_event = new MEC_Event( $item );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the content without the plugins rendered shortcodes.
|
||||
*/
|
||||
public function get_content(): string {
|
||||
$content = wpautop( $this->item->post_content );
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the end time from the event object.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_start_time(): string {
|
||||
return \gmdate( 'Y-m-d\TH:i:s\Z', $this->mec_event->get_datetime()['start']['timestamp'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the end time from the event object.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_end_time(): string {
|
||||
return \gmdate( 'Y-m-d\TH:i:s\Z', $this->mec_event->get_datetime()['end']['timestamp'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the location.
|
||||
*
|
||||
* @return ?Place
|
||||
*/
|
||||
public function get_location(): ?Place {
|
||||
$location_id = $this->mec_main->get_master_location_id( $this->mec_event->ID );
|
||||
|
||||
if ( ! $location_id ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$data = $this->mec_main->get_location_data( $location_id );
|
||||
|
||||
$location = new Place();
|
||||
$location->set_sensitive( null );
|
||||
|
||||
if ( ! empty( $data['address'] ) ) {
|
||||
$location->set_address( $data['address'] );
|
||||
}
|
||||
if ( ! empty( $data['name'] ) ) {
|
||||
$location->set_name( $data['name'] );
|
||||
}
|
||||
if ( ! empty( $data['longitude'] ) ) {
|
||||
$location->set_longitude( $data['longitude'] );
|
||||
}
|
||||
if ( ! empty( $data['latitude'] ) ) {
|
||||
$location->set_latitude( $data['latitude'] );
|
||||
}
|
||||
|
||||
return $location;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the location.
|
||||
*/
|
||||
public function get_timezone(): string {
|
||||
$timezone = get_post_meta( $this->item->ID, 'mec_timezone', true );
|
||||
|
||||
if ( 'global' === $timezone ) {
|
||||
return parent::get_timezone();
|
||||
}
|
||||
|
||||
return $timezone;
|
||||
}
|
||||
}
|
@ -0,0 +1,203 @@
|
||||
<?php
|
||||
/**
|
||||
* ActivityPub Tribe Transformer
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Event;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Activitypub\Activity\Extended_Object\Event as Event_Object;
|
||||
use Activitypub\Activity\Extended_Object\Place;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Place\The_Events_Calendar as The_Events_Calendar_Location;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Event\Event;
|
||||
use WP_Post;
|
||||
|
||||
use function Activitypub\esc_hashtag;
|
||||
|
||||
/**
|
||||
* ActivityPub Tribe Transformer
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class The_Events_Calendar extends Event {
|
||||
|
||||
/**
|
||||
* The Tribe Event object.
|
||||
*
|
||||
* @var WP_Post|null
|
||||
*
|
||||
* @property string $timezone
|
||||
* @property string $event_status
|
||||
* @property \Tribe\Events\Collections\Lazy_Post_Collection|WP_Post $venues
|
||||
* @property string $start_date
|
||||
*/
|
||||
protected $tribe_event;
|
||||
|
||||
/**
|
||||
* Extend the constructor, to also set the tribe object.
|
||||
*
|
||||
* This is a special class object form The Events Calendar which
|
||||
* has a lot of useful functions, we make use of our getter functions.
|
||||
*
|
||||
* @param WP_Post $item The WordPress object.
|
||||
* @param string $wp_taxonomy The taxonomy slug of the event post type.
|
||||
*/
|
||||
public function __construct( $item, $wp_taxonomy ) {
|
||||
parent::__construct( $item, $wp_taxonomy );
|
||||
$this->tribe_event = \tribe_get_event( $item );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the tags, including also the set categories from The Events Calendar.
|
||||
*
|
||||
* @return array The array if tags,
|
||||
*/
|
||||
public function get_tag(): array {
|
||||
$tags = array();
|
||||
$category_ids = tribe_get_event_cat_ids();
|
||||
if ( $category_ids ) {
|
||||
foreach ( $category_ids as $category_id ) {
|
||||
$term = \get_term( $category_id );
|
||||
$tag = array(
|
||||
'type' => 'Hashtag',
|
||||
'href' => \esc_url( \get_term_link( $term ) ),
|
||||
'name' => esc_hashtag( $term->name ),
|
||||
);
|
||||
$tags[] = $tag;
|
||||
}
|
||||
}
|
||||
$tags = array_merge( $tags, parent::get_tag() );
|
||||
|
||||
return $tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the end time from the event object.
|
||||
*/
|
||||
public function get_end_time(): string {
|
||||
$utc_time = get_post_meta( $this->tribe_event->ID, '_EventEndDateUTC', true );
|
||||
$timezone = new \DateTimeZone( $this->get_timezone() );
|
||||
$time = new \DateTime( $utc_time );
|
||||
$time->setTimezone( $timezone );
|
||||
return $time->format( 'Y-m-d\TH:i:sP' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the end time from the event object.
|
||||
*/
|
||||
public function get_start_time(): string {
|
||||
$utc_time = get_post_meta( $this->tribe_event->ID, '_EventStartDateUTC', true );
|
||||
$timezone = new \DateTimeZone( $this->get_timezone() );
|
||||
$time = new \DateTime( $utc_time );
|
||||
$time->setTimezone( $timezone );
|
||||
return $time->format( 'Y-m-d\TH:i:sP' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the timezone of the event.
|
||||
*
|
||||
* @return string The timezone string of the site.
|
||||
*/
|
||||
public function get_timezone(): string {
|
||||
// @phpstan-ignore-next-line
|
||||
$timezone = $this->tribe_event->timezone;
|
||||
|
||||
if ( ! $timezone || ! is_string( $timezone ) ) {
|
||||
return parent::get_timezone();
|
||||
}
|
||||
|
||||
return $timezone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get status of the tribe event
|
||||
*
|
||||
* @return string status of the event
|
||||
*/
|
||||
public function get_status(): string {
|
||||
// @phpstan-ignore-next-line
|
||||
$event_status = $this->tribe_event->event_status;
|
||||
|
||||
if ( 'canceled' === $event_status ) {
|
||||
return 'CANCELLED';
|
||||
}
|
||||
|
||||
if ( 'postponed' === $event_status ) {
|
||||
return 'CANCELLED'; // This will be reflected in the cancelled reason.
|
||||
}
|
||||
|
||||
return 'CONFIRMED';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if the comments are enabled for the current event.
|
||||
*/
|
||||
public function get_comments_enabled(): bool {
|
||||
return ( 'open' === $this->tribe_event->comment_status ) ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the event is an online event.
|
||||
*/
|
||||
public function get_is_online(): bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the event location.
|
||||
*
|
||||
* @return ?Place The place/venue if one is set.
|
||||
*/
|
||||
public function get_location(): ?Place {
|
||||
// Get short handle for the venues.
|
||||
|
||||
if ( ! \tribe_has_venue( $this->tribe_event->ID ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$venue_id = \tribe_get_venue_id( $this->tribe_event->ID );
|
||||
$post = \get_post( $venue_id );
|
||||
|
||||
if ( ! $post ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$location_transformer = new The_Events_Calendar_Location( $post );
|
||||
$full_location_object = false;
|
||||
$location = $location_transformer->to_object( $full_location_object );
|
||||
return $location;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the filter for preventing the rendering off The Events Calendar blocks just in time.
|
||||
*
|
||||
* @return Event_Object
|
||||
*/
|
||||
public function to_object(): Event_Object {
|
||||
add_filter( 'render_block', array( self::class, 'filter_tribe_blocks' ), 10, 2 );
|
||||
$activitypub_object = parent::to_object();
|
||||
remove_filter( 'render_block', array( self::class, 'filter_tribe_blocks' ) );
|
||||
return $activitypub_object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prevents The Events Calendar blocks from being rendered for the content.
|
||||
*
|
||||
* @param mixed $block_content The blocks content.
|
||||
* @param mixed $block The block.
|
||||
*/
|
||||
public static function filter_tribe_blocks( $block_content, $block ) {
|
||||
// Check if the block name starts with 'tribe' and is not an exception.
|
||||
if ( isset( $block['blockName'] ) && 0 === strpos( $block['blockName'], 'tribe/' ) ) {
|
||||
return ''; // Skip rendering this block.
|
||||
}
|
||||
|
||||
return $block_content; // Return the content for other blocks.
|
||||
}
|
||||
}
|
@ -0,0 +1,115 @@
|
||||
<?php
|
||||
/**
|
||||
* ActivityPub Transformer for the plugin Very Simple Event List.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Event;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Activitypub\Activity\Extended_Object\Place;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Event\Event as Event_Transformer;
|
||||
|
||||
/**
|
||||
* ActivityPub Transformer for VS Event.
|
||||
*
|
||||
* This transformer tries a different principle: The setters are chainable.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class VS_Event_List extends Event_Transformer {
|
||||
|
||||
/**
|
||||
* Get the event location.
|
||||
*
|
||||
* @return Place The Place.
|
||||
*/
|
||||
public function get_location(): ?Place {
|
||||
$address = \get_post_meta( $this->item->ID, 'event-location', true );
|
||||
if ( $address ) {
|
||||
$place = new Place();
|
||||
$place->set_type( 'Place' );
|
||||
$place->set_name( $address );
|
||||
$place->set_address( $address );
|
||||
return $place;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the end time from the events metadata.
|
||||
*/
|
||||
public function get_end_time(): ?string {
|
||||
if ( 'yes' === \get_post_meta( $this->item->ID, 'event-hide-end-time', true ) ) {
|
||||
return null;
|
||||
}
|
||||
$end_time = \get_post_meta( $this->item->ID, 'event-date', true );
|
||||
if ( is_null( $end_time ) || empty( $end_time ) || 'no' === $end_time ) {
|
||||
return null;
|
||||
}
|
||||
return \gmdate( 'Y-m-d\TH:i:s\Z', (int) $end_time );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the end time from the events metadata.
|
||||
*/
|
||||
public function get_start_time(): string {
|
||||
$start_time = \get_post_meta( $this->item->ID, 'event-start-date', true );
|
||||
return \gmdate( 'Y-m-d\TH:i:s\Z', (int) $start_time );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the event link from the events metadata.
|
||||
*
|
||||
* @return ?array Associated array of an ActivityStreams Link object with the events URL.
|
||||
*/
|
||||
private function get_event_link(): ?array {
|
||||
$event_link = \get_post_meta( $this->item->ID, 'event-link', true );
|
||||
$event_link_label = \get_post_meta( $this->item->ID, 'event-link-label', true ) ?? 'Event Link';
|
||||
if ( $event_link ) {
|
||||
return array(
|
||||
'type' => 'Link',
|
||||
'name' => $event_link_label,
|
||||
'href' => \esc_url( $event_link ),
|
||||
'mediaType' => 'text/html',
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides/extends the get_attachments function to also add the event Link.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_attachment(): array {
|
||||
$attachments = parent::get_attachment();
|
||||
if ( count( $attachments ) ) {
|
||||
$attachments[0]['type'] = 'Document';
|
||||
$attachments[0]['name'] = 'Banner';
|
||||
}
|
||||
$event_link = $this->get_event_link();
|
||||
if ( $event_link ) {
|
||||
$attachments[] = $event_link;
|
||||
}
|
||||
return $attachments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the excerpt text (may be HTML). Used for constructing the summary.
|
||||
*
|
||||
* @return ?string
|
||||
*/
|
||||
protected function retrieve_excerpt(): ?string {
|
||||
if ( \get_post_meta( $this->item->ID, 'event-summary', true ) ) {
|
||||
return \get_post_meta( $this->item->ID, 'event-summary', true );
|
||||
} else {
|
||||
return parent::retrieve_excerpt();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,162 @@
|
||||
<?php
|
||||
/**
|
||||
* ActivityPub Transformer for the plugin Very Simple Event List.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Event;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Activitypub\Activity\Extended_Object\Place;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Event\Event as Event_Transformer;
|
||||
use DateTime;
|
||||
use DateTimeZone;
|
||||
|
||||
/**
|
||||
* ActivityPub Transformer for events from the WordPress plugin 'Events Manager'
|
||||
*
|
||||
* @see https://wordpress.org/plugins/events-manager/
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class WP_Event_Manager extends Event_Transformer {
|
||||
/**
|
||||
* Returns whether the even is online
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function get_is_online(): bool {
|
||||
$is_online_text = get_post_meta( $this->item->ID, '_event_online', true );
|
||||
$is_online = false;
|
||||
// Radio buttons.
|
||||
if ( 'yes' === $is_online_text ) {
|
||||
$is_online = true;
|
||||
}
|
||||
// Checkbox.
|
||||
if ( '1' === $is_online_text ) {
|
||||
$is_online = true;
|
||||
}
|
||||
return $is_online;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the event location.
|
||||
*
|
||||
* @return ?Place The Place.
|
||||
*/
|
||||
public function get_location(): ?Place {
|
||||
$location_name = get_post_meta( $this->item->ID, '_event_location', true );
|
||||
|
||||
if ( $location_name ) {
|
||||
$location = new Place();
|
||||
$location->set_name( $location_name );
|
||||
$location->set_sensitive( null );
|
||||
$location->set_address( $location_name );
|
||||
|
||||
return $location;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the end time from the events metadata.
|
||||
*
|
||||
* @return ?string The events end-datetime if is set, null otherwise.
|
||||
*/
|
||||
public function get_end_time(): ?string {
|
||||
$end_date = get_post_meta( $this->item->ID, '_event_end_date', true );
|
||||
if ( ! $end_date ) {
|
||||
return null;
|
||||
}
|
||||
$timezone = new DateTimeZone( $this->get_timezone() );
|
||||
|
||||
if ( is_numeric( $end_date ) ) {
|
||||
$end_date = '@' . $end_date;
|
||||
}
|
||||
|
||||
$end_datetime = new DateTime( $end_date, $timezone );
|
||||
|
||||
return $end_datetime->format( 'Y-m-d\TH:i:sP' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get timezone.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_timezone(): string {
|
||||
$time_zone = get_post_meta( $this->item->ID, '_event_timezone', true );
|
||||
if ( $time_zone ) {
|
||||
return $time_zone;
|
||||
}
|
||||
return parent::get_timezone();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the end time from the events metadata.
|
||||
*/
|
||||
public function get_start_time(): string {
|
||||
$start_date = get_post_meta( $this->item->ID, '_event_start_date', true );
|
||||
$timezone = new DateTimeZone( $this->get_timezone() );
|
||||
|
||||
if ( is_numeric( $start_date ) ) {
|
||||
$start_date = '@' . $start_date;
|
||||
}
|
||||
|
||||
$start_datetime = new DateTime( $start_date, $timezone );
|
||||
|
||||
return $start_datetime->format( 'Y-m-d\TH:i:sP' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the event link as an ActivityPub Link object, but as an associative array.
|
||||
*
|
||||
* @return ?array
|
||||
*/
|
||||
private function get_event_link_attachment(): ?array {
|
||||
$event_link_url = get_post_meta( $this->item->ID, '_event_video_url', true );
|
||||
|
||||
if ( str_starts_with( $event_link_url, 'http' ) ) {
|
||||
return array(
|
||||
'type' => 'Link',
|
||||
'name' => \esc_html__( 'Video URL', 'event-bridge-for-activitypub' ),
|
||||
'href' => \esc_url( $event_link_url ),
|
||||
'mediaType' => 'text/html',
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides/extends the get_attachments function to also add the event Link.
|
||||
*/
|
||||
protected function get_attachment() {
|
||||
// Get attachments via parent function.
|
||||
$attachments = parent::get_attachment();
|
||||
|
||||
// The first attachment is the featured image, make sure it is compatible with Mobilizon.
|
||||
if ( count( $attachments ) ) {
|
||||
$attachments[0]['type'] = 'Document';
|
||||
$attachments[0]['name'] = 'Banner';
|
||||
}
|
||||
|
||||
if ( $this->get_event_link_attachment() ) {
|
||||
$attachments[] = $this->get_event_link_attachment();
|
||||
}
|
||||
return $attachments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the events title/name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_name(): string {
|
||||
return $this->item->post_title;
|
||||
}
|
||||
}
|
@ -0,0 +1,146 @@
|
||||
<?php
|
||||
/**
|
||||
* Class file for the ActivityPub transformer of the venues of The Events Calendar to `as:Place`.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Place;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Activitypub\Activity\Extended_Object\Place as Place_Object;
|
||||
use Activitypub\Transformer\Post;
|
||||
|
||||
/**
|
||||
* Class for the ActivityPub transformer of the venues of The Events Calendar to `as:Place`.
|
||||
*
|
||||
* @method array|string get_address()
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
abstract class Base_Post_Place extends Post {
|
||||
/**
|
||||
* Set the type of the object.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_type(): string {
|
||||
return 'Place';
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the type of the object.
|
||||
*
|
||||
* @return ?array
|
||||
*/
|
||||
public function get_replies() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the type of the object.
|
||||
*
|
||||
* @return ?string
|
||||
*/
|
||||
public function get_sensitive() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Null content to prevent registering and unregistering ActivityPub shortcodes in parent function.
|
||||
*
|
||||
* @return ?string
|
||||
*/
|
||||
public function get_content() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Completely remove attachments.
|
||||
*
|
||||
* @return ?array
|
||||
*/
|
||||
public function get_attachment() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Completely remove summary.
|
||||
*
|
||||
* @return ?string
|
||||
*/
|
||||
public function get_summary() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Completely remove tag.
|
||||
*
|
||||
* @return ?array
|
||||
*/
|
||||
public function get_tag() {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Completely media type.
|
||||
*
|
||||
* @return ?string
|
||||
*/
|
||||
public function get_media_type() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic function that converts an WordPress location object to an ActivityPub-Place object.
|
||||
*
|
||||
* @param bool $full_object bool Return an object with all properties set, or a minimal one as used within an `as:Event`s location.
|
||||
* @return Place_Object|\WP_Error
|
||||
*/
|
||||
public function to_object( $full_object = true ) {
|
||||
$activitypub_object = new Place_Object();
|
||||
$activitypub_object = $this->transform_object_properties( $activitypub_object );
|
||||
|
||||
if ( \is_wp_error( $activitypub_object ) ) {
|
||||
return $activitypub_object;
|
||||
}
|
||||
|
||||
if ( ! empty( $activitypub_object->get_content() ) ) {
|
||||
$activitypub_object->set_content_map(
|
||||
array(
|
||||
$this->get_locale() => $this->get_content(),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$updated = \strtotime( $this->item->post_modified_gmt );
|
||||
|
||||
$activitypub_object->set_updated( \gmdate( 'Y-m-d\TH:i:s\Z', $updated ) );
|
||||
|
||||
if ( $full_object ) {
|
||||
$published = \strtotime( $this->item->post_date_gmt );
|
||||
|
||||
$activitypub_object->set_published( \gmdate( 'Y-m-d\TH:i:s\Z', $published ) );
|
||||
|
||||
$activitypub_object->set_to(
|
||||
array(
|
||||
'https://www.w3.org/ns/activitystreams#Public',
|
||||
$this->get_actor_object()->get_followers(),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$address = $this->get_address();
|
||||
|
||||
if ( $address ) {
|
||||
$activitypub_object->set_address( $address );
|
||||
}
|
||||
|
||||
// @phpstan-ignore-next-line
|
||||
return $activitypub_object;
|
||||
}
|
||||
}
|
@ -0,0 +1,165 @@
|
||||
<?php
|
||||
/**
|
||||
* Class file a base `Place` transformer where the place/location/venue is stored in a WordPress term.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Place;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Activitypub\Activity\Extended_Object\Place as Place_Object;
|
||||
use Activitypub\Transformer\Base;
|
||||
|
||||
/**
|
||||
* Class for a base `Place` transformer where the place/location/venue is stored in a WordPress term.
|
||||
*
|
||||
* @method array|string get_address()
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
abstract class Base_Term_Place extends Base {
|
||||
/**
|
||||
* Set the type of the object.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_type(): string {
|
||||
return 'Place';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the WordPress term ID of the current Transformers item.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get__id() {
|
||||
return $this->item->term_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ActivityPub ID of the term.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_url() {
|
||||
return \get_term_link( $this->item );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the most unique, resolvable "ID" there currently is for a WordPress term.
|
||||
*
|
||||
* @return string The "ID"
|
||||
*/
|
||||
public function get_id() {
|
||||
/**
|
||||
* The first approach was to use the normal query from WordPress, but it contains the slug, which might be edited.
|
||||
*
|
||||
* \add_query_arg( $this->item->taxonomy, $this->item->slug, \trailingslashit( \home_url() ) );
|
||||
*
|
||||
* As https://github.com/Automattic/wordpress-activitypub/pull/1272 got merged, now we can definy a real ID.
|
||||
*/
|
||||
return \add_query_arg( 'term_id', $this->item->term_id, \trailingslashit( \home_url() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Use Term description as ActivityPub content.
|
||||
*
|
||||
* @return mixed|string
|
||||
*/
|
||||
public function get_content() {
|
||||
return $this->item->description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name for the ActivityPub Item which is the title of the term.
|
||||
*
|
||||
* @return string|null The title or null if the object type is `note`.
|
||||
*/
|
||||
protected function get_name() {
|
||||
if ( isset( $this->item->name ) && $this->item instanceof \WP_Term ) {
|
||||
return \wp_strip_all_tags(
|
||||
\html_entity_decode(
|
||||
$this->item->name
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic function that converts an WordPress location object to an ActivityPub-Place object.
|
||||
*
|
||||
* @return Place_Object|\WP_Error
|
||||
*/
|
||||
public function to_object() {
|
||||
$activitypub_object = new Place_Object();
|
||||
|
||||
$activitypub_object->set_type( $this->get_type() );
|
||||
$activitypub_object->set_id( $this->get_id() );
|
||||
$activitypub_object->set_name( $this->get_name() );
|
||||
$activitypub_object->set_url( $this->get_url() );
|
||||
$activitypub_object->set_content( $this->get_content() );
|
||||
$activitypub_object->set_sensitive( $this->get_sensitive() );
|
||||
|
||||
$address = $this->get_address();
|
||||
|
||||
if ( $address ) {
|
||||
$activitypub_object->set_address( $address );
|
||||
}
|
||||
|
||||
return $activitypub_object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't set a media type on Place per default.
|
||||
*
|
||||
* @return null
|
||||
* @phpstan-ignore-next-line
|
||||
*/
|
||||
public function get_media_type() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't set sensitive per default.
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
public function get_sensitive() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't support replies for Place per default.
|
||||
*
|
||||
* @return null
|
||||
*/
|
||||
public function get_replies() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't support tags for Place per default.
|
||||
*
|
||||
* @return null
|
||||
* @phpstan-ignore-next-line
|
||||
*/
|
||||
protected function get_tag() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Don't set attrbuted to per default.
|
||||
*
|
||||
* @return null The attributed to.
|
||||
* @phpstan-ignore-next-line
|
||||
*/
|
||||
protected function get_attributed_to() {
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
<?php
|
||||
/**
|
||||
* Class file for the ActivityPub transformer of the venues of The Events Calendar to `as:Place`.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Place;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
/**
|
||||
* Class for the ActivityPub transformer of the venues of The Events Calendar to `as:Place`.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class Event_Organiser extends Base_Term_Place {
|
||||
/**
|
||||
* Get the longitute.
|
||||
*
|
||||
* @return float|null
|
||||
*/
|
||||
public function get_longitude() {
|
||||
$longitude = \eo_get_venue_lng( $this->item->ID );
|
||||
return 0.0 !== $longitude ? $longitude : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the latitude.
|
||||
*
|
||||
* @return float|null
|
||||
*/
|
||||
public function get_latitude() {
|
||||
$latitude = \eo_get_venue_lat( $this->item->ID );
|
||||
return 0.0 !== $latitude ? $latitude : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the description of the venue as the ActivityPub content.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function get_content() {
|
||||
$description = \eo_get_venue_description( $this->item->term_id );
|
||||
|
||||
if ( empty( $description ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $description;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the events address.
|
||||
*
|
||||
* @return ?array The place/venue if one is set, or null if no valid address data exists.
|
||||
*/
|
||||
public function get_address(): ?array {
|
||||
$address = \eo_get_venue_address( $this->item->term_id );
|
||||
|
||||
// Map the values to a schema.org PostalAddress.
|
||||
$postal_address = array(
|
||||
'streetAddress' => isset( $address['address'] ) ? $address['address'] : null,
|
||||
'postalCode' => isset( $address['address'] ) ? $address['postcode'] : null,
|
||||
'addressRegion' => isset( $address['address'] ) ? $address['state'] : null,
|
||||
'addressLocality' => isset( $address['address'] ) ? $address['city'] : null,
|
||||
'addressCountry' => isset( $address['address'] ) ? $address['country'] : null,
|
||||
);
|
||||
|
||||
// Filter out empty values.
|
||||
foreach ( $postal_address as $key => $value ) {
|
||||
if ( empty( $value ) ) {
|
||||
unset( $postal_address[ $key ] );
|
||||
}
|
||||
}
|
||||
|
||||
// If no valid address data remains, return null.
|
||||
if ( empty( $postal_address ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Add the type.
|
||||
$postal_address['type'] = 'PostalAddress';
|
||||
|
||||
return $postal_address;
|
||||
}
|
||||
}
|
@ -0,0 +1,197 @@
|
||||
<?php
|
||||
/**
|
||||
* Class file for the ActivityPub transformer of the venues of The Events Calendar to `as:Place`.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Place;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Activitypub\Activity\Extended_Object\Place as Place_Object;
|
||||
|
||||
/**
|
||||
* Class for the ActivityPub transformer of the venues of The Events Calendar to `as:Place`.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class EventOn extends Base_Term_Place {
|
||||
/**
|
||||
* The location meta for all locations.
|
||||
*
|
||||
* @var ?array
|
||||
*/
|
||||
protected $tax_meta = null;
|
||||
|
||||
/**
|
||||
* Extend the construction to get the taxonomy meta for this term from options.
|
||||
*
|
||||
* @param \WP_Term $item The WordPress post object (event).
|
||||
*/
|
||||
public function __construct( $item ) {
|
||||
parent::__construct( $item );
|
||||
|
||||
$evo_tax_meta = \get_option( 'evo_tax_meta' );
|
||||
if ( isset( $evo_tax_meta['event_location'][ $item->term_id ] ) ) {
|
||||
$this->tax_meta = $evo_tax_meta['event_location'][ $item->term_id ];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic function that converts an WordPress location object to an ActivityPub-Place object.
|
||||
*
|
||||
* @return Place_Object|\WP_Error
|
||||
*/
|
||||
public function to_object() {
|
||||
$object = parent::to_object();
|
||||
|
||||
if ( \is_wp_error( $object ) ) {
|
||||
return $object;
|
||||
}
|
||||
|
||||
$object->set_longitude( $this->get_longitude() );
|
||||
$object->set_latitude( $this->get_latitude() );
|
||||
|
||||
return $object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the type, either Place or VirtualLocation, both is stored in the same taxonomy.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_type(): string {
|
||||
if ( $this->is_virtual_location() ) {
|
||||
return 'VirtualLocation';
|
||||
}
|
||||
return 'Place';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the longitute.
|
||||
*
|
||||
* @return float|null
|
||||
*/
|
||||
public function get_longitude(): ?float {
|
||||
$longitude = null;
|
||||
|
||||
if ( isset( $this->tax_meta['location_lon'] ) ) {
|
||||
$longitude = $this->tax_meta['location_lon'];
|
||||
}
|
||||
|
||||
return $longitude ? (float) $longitude : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the latitude.
|
||||
*
|
||||
* @return float|null
|
||||
*/
|
||||
public function get_latitude(): ?float {
|
||||
$latitude = null;
|
||||
|
||||
if ( isset( $this->tax_meta['location_lat'] ) ) {
|
||||
$latitude = $this->tax_meta['location_lat'];
|
||||
}
|
||||
|
||||
return $latitude ? (float) $latitude : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the events address.
|
||||
*
|
||||
* @return ?array The place/venue if one is set, or null if no valid address data exists.
|
||||
*/
|
||||
public function get_address(): ?array {
|
||||
|
||||
if ( $this->is_virtual_location() ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Map the values to a schema.org PostalAddress.
|
||||
$postal_address = array(
|
||||
'streetAddress' => isset( $this->tax_meta['location_address'] ) ? (string) $this->tax_meta['location_address'] : null,
|
||||
'addressRegion' => isset( $this->tax_meta['location_state'] ) ? (string) $this->tax_meta['location_state'] : null,
|
||||
'addressCountry' => isset( $this->tax_meta['location_country'] ) ? (string) $this->tax_meta['location_country'] : null,
|
||||
);
|
||||
|
||||
if ( isset( $this->tax_meta['location_city'] ) ) {
|
||||
$locality_and_postal_code = $this->parse_city_for_postal_code( $this->tax_meta['location_city'] );
|
||||
$postal_address['addressLocality'] = (string) $locality_and_postal_code['addressLocality'];
|
||||
$postal_address['postalCode'] = (string) $locality_and_postal_code['postalCode'];
|
||||
}
|
||||
|
||||
// Filter out empty values.
|
||||
foreach ( $postal_address as $key => $value ) {
|
||||
if ( empty( $value ) ) {
|
||||
unset( $postal_address[ $key ] );
|
||||
}
|
||||
}
|
||||
|
||||
// If no valid address data remains, return null.
|
||||
if ( empty( $postal_address ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Add the type.
|
||||
$postal_address = array_merge(
|
||||
array(
|
||||
'type' => 'PostalAddress',
|
||||
),
|
||||
$postal_address
|
||||
);
|
||||
|
||||
return $postal_address;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this term represents a virtual location.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function is_virtual_location(): bool {
|
||||
if ( isset( $this->tax_meta['location_type'] ) && 'virtual' === $this->tax_meta['location_type'] ) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a string whether it contains a postal code and seperates both.
|
||||
*
|
||||
* @param string $input The input string of the locality which might contain the postal code too.
|
||||
* @return array{addressLocality: string, postalCode: string}
|
||||
*/
|
||||
private function parse_city_for_postal_code( $input ): array {
|
||||
$input = trim( $input );
|
||||
|
||||
if ( empty( $input ) ) {
|
||||
return array(
|
||||
'addressLocality' => '',
|
||||
'postalCode' => '',
|
||||
);
|
||||
}
|
||||
|
||||
$parts = explode( ' ', $input );
|
||||
$postal_code = '';
|
||||
$locality = array();
|
||||
|
||||
foreach ( $parts as $part ) {
|
||||
if ( preg_match( '/^\d{4,5}$/', $part ) ) {
|
||||
// Match postal codes (assuming 4-5 digit codes).
|
||||
$postal_code = $part;
|
||||
} else {
|
||||
// Assume everything else is part of the name of the city.
|
||||
$locality[] = $part;
|
||||
}
|
||||
}
|
||||
|
||||
return array(
|
||||
'addressLocality' => implode( ' ', $locality ),
|
||||
'postalCode' => $postal_code,
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
<?php
|
||||
/**
|
||||
* Class file for the ActivityPub transformer of the venues of The Events Calendar to `as:Place`.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Place;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
/**
|
||||
* Class for the ActivityPub transformer of the venues of The Events Calendar to `as:Place`.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class EventPrime extends Base_Term_Place {
|
||||
/**
|
||||
* Get URL
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function get_url() {
|
||||
$ep = new \Eventprime_Basic_Functions();
|
||||
$url = $ep->ep_get_custom_page_url( 'venues_page', $this->item->term_id, 'venue', 'term' );
|
||||
|
||||
if ( \is_wp_error( $url ) ) {
|
||||
return null;
|
||||
}
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the best "ID" we currently have.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function get_id() {
|
||||
return $this->get_url();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the event location.
|
||||
*
|
||||
* @return array|string|null The place/venue if one is set.
|
||||
*/
|
||||
public function get_address() {
|
||||
$address = \get_term_meta( $this->item->term_id, 'em_address', true );
|
||||
$display_address = \get_term_meta( $this->item->term_id, 'em_display_address_on_frontend', true );
|
||||
|
||||
if ( $address && $display_address ) {
|
||||
return $address;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
<?php
|
||||
/**
|
||||
* Class file for the ActivityPub transformer of the venues of The Events Calendar to `as:Place`.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Place;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Place\Base_Post_Place;
|
||||
|
||||
/**
|
||||
* Class for the ActivityPub transformer of the venues of The Events Calendar to `as:Place`.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class Events_Manager extends Base_Post_Place {
|
||||
/**
|
||||
* The EM Location object.
|
||||
*
|
||||
* @var ?\EM_Location
|
||||
*/
|
||||
protected $em_location;
|
||||
|
||||
/**
|
||||
* Set the EM Location object on construction.
|
||||
*
|
||||
* @param \WP_Post $post The WordPress post object of the EM Location.
|
||||
*/
|
||||
public function __construct( $post ) {
|
||||
parent::__construct( $post );
|
||||
// We check for WP_Post to indicate that this might change in the future, to also e.g. allow for locations stored in terms.
|
||||
|
||||
// @phpstan-ignore-next-line
|
||||
if ( $post instanceof \WP_Post && EM_POST_TYPE_LOCATION === $post->post_type ) {
|
||||
$this->em_location = em_get_location( $post );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the location.
|
||||
*
|
||||
* @return ?string
|
||||
*/
|
||||
public function get_name(): ?string {
|
||||
if ( isset( $this->em_location->location_name ) ) {
|
||||
return \wp_strip_all_tags(
|
||||
\html_entity_decode(
|
||||
$this->em_location->location_name
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the event location.
|
||||
*
|
||||
* @return ?array The place/venue if one is set.
|
||||
*/
|
||||
public function get_address(): ?array {
|
||||
$postal_address = array();
|
||||
|
||||
if ( isset( $this->em_location->location_country ) && $this->em_location->location_country ) {
|
||||
$postal_address['addressCountry'] = $this->em_location->location_country;
|
||||
}
|
||||
|
||||
if ( isset( $this->em_location->location_town ) && $this->em_location->location_town ) {
|
||||
$postal_address['addressLocality'] = $this->em_location->location_town;
|
||||
}
|
||||
|
||||
if ( isset( $this->em_location->location_address ) && $this->em_location->location_address ) {
|
||||
$postal_address['streetAddress'] = $this->em_location->location_address;
|
||||
}
|
||||
|
||||
if ( isset( $this->em_location->location_state ) && $this->em_location->location_state ) {
|
||||
$postal_address['addressRegion'] = $this->em_location->location_state;
|
||||
}
|
||||
|
||||
if ( isset( $this->em_location->location_postcode ) && $this->em_location->location_postcode ) {
|
||||
$postal_address['postalCode'] = $this->em_location->location_postcode;
|
||||
}
|
||||
|
||||
if ( ! empty( $postal_address ) ) {
|
||||
return array_merge( array( 'type' => 'PostalAddress' ), $postal_address );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
<?php
|
||||
/**
|
||||
* Class file for the ActivityPub transformer of the venues of The Events Calendar to `as:Place`.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Place;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Place\Base_Post_Place;
|
||||
|
||||
/**
|
||||
* Class for the ActivityPub transformer of the venues of The Events Calendar to `as:Place`.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class The_Events_Calendar extends Base_Post_Place {
|
||||
/**
|
||||
* Get the event location.
|
||||
*
|
||||
* @return ?array The place/venue if one is set.
|
||||
*/
|
||||
public function get_address(): ?array {
|
||||
$postal_address = array();
|
||||
|
||||
$country = \tribe_get_country( $this->item->ID );
|
||||
if ( $country ) {
|
||||
$postal_address['addressCountry'] = $country;
|
||||
}
|
||||
|
||||
$city = \tribe_get_city( $this->item->ID );
|
||||
if ( $city ) {
|
||||
$postal_address['addressLocality'] = $city;
|
||||
}
|
||||
|
||||
$province = \tribe_get_province( $this->item->ID );
|
||||
if ( $province ) {
|
||||
$postal_address['addressRegion'] = $province;
|
||||
}
|
||||
|
||||
$zip = \tribe_get_zip( $this->item->ID );
|
||||
if ( $zip ) {
|
||||
$postal_address['postalCode'] = $zip;
|
||||
}
|
||||
|
||||
$address = \tribe_get_address( $this->item->ID );
|
||||
if ( $city ) {
|
||||
$postal_address['streetAddress'] = $address;
|
||||
}
|
||||
|
||||
if ( empty( $postal_address ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$postal_address = array_merge( array( 'type' => 'PostalAddress' ), $postal_address );
|
||||
|
||||
return $postal_address;
|
||||
}
|
||||
}
|
@ -0,0 +1,378 @@
|
||||
<?php
|
||||
/**
|
||||
* Base class with common functions for transforming an ActivityPub Event object to a WordPress object.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Transmogrifier;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Activitypub\Activity\Extended_Object\Event;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Collection\Event_Sources;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transmogrifier\Helper\Sanitizer;
|
||||
use WP_Error;
|
||||
use WP_Post;
|
||||
|
||||
/**
|
||||
* Base class with common functions for transforming an ActivityPub Event object to a WordPress object.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
abstract class Base {
|
||||
/**
|
||||
* Internal function to actually save the event.
|
||||
*
|
||||
* @param Event $activitypub_event The ActivityPub event as associative array.
|
||||
* @param int $event_source_post_id The Post ID of the Event Source that owns the outbox.
|
||||
*
|
||||
* @return false|int Post-ID on success, false on failure.
|
||||
*/
|
||||
abstract protected static function save_event( $activitypub_event, $event_source_post_id );
|
||||
|
||||
/**
|
||||
* Save the ActivityPub event object within WordPress.
|
||||
*
|
||||
* @param array $activitypub_event The ActivityPub event as associative array.
|
||||
* @param int $event_source_post_id The Post ID of the Event Source that owns the outbox.
|
||||
*/
|
||||
public static function save( $activitypub_event, $event_source_post_id ): void {
|
||||
// Sanitize the incoming event and set only the properties used by the transmogrifier classes.
|
||||
$activitypub_event = Sanitizer::init_and_sanitize_event_object_from_array( $activitypub_event );
|
||||
|
||||
if ( is_wp_error( $activitypub_event ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Pass the saving to the actual Transmogrifier implementation.
|
||||
$post_id = static::save_event( $activitypub_event, $event_source_post_id );
|
||||
|
||||
// Post processing: Logging and marking the imported event's origin.
|
||||
$event_activitypub_id = $activitypub_event->get_id();
|
||||
$event_source_activitypub_id = \get_the_guid( $event_source_post_id );
|
||||
|
||||
if ( $post_id ) {
|
||||
\do_action(
|
||||
'event_bridge_for_activitypub_write_log',
|
||||
array( "[ACTIVITYPUB] Processed incoming event {$event_activitypub_id} from {$event_source_activitypub_id}" )
|
||||
);
|
||||
// Use post meta to remember who we received this event from.
|
||||
\update_post_meta( $post_id, '_event_bridge_for_activitypub_event_source', absint( $event_source_post_id ) );
|
||||
\update_post_meta( $post_id, 'activitypub_content_visibility', defined( 'ACTIVITYPUB_CONTENT_VISIBILITY_LOCAL' ) ? constant( 'ACTIVITYPUB_CONTENT_VISIBILITY_LOCAL' ) : '' );
|
||||
} else {
|
||||
\do_action(
|
||||
'event_bridge_for_activitypub_write_log',
|
||||
array( "[ACTIVITYPUB] Failed processing incoming event {$event_activitypub_id} from {$event_source_activitypub_id}" )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a local event in WordPress that is a cached remote one.
|
||||
*
|
||||
* @param string $activitypub_event_id The ActivityPub events ID.
|
||||
* @return bool|WP_Post|null|WP_Error
|
||||
*/
|
||||
public static function delete( $activitypub_event_id ) {
|
||||
$post_id = static::get_post_id_from_activitypub_id( $activitypub_event_id );
|
||||
|
||||
if ( ! $post_id ) {
|
||||
\do_action(
|
||||
'event_bridge_for_activitypub_write_log',
|
||||
array( "[ACTIVITYPUB] Received delete for event that is not cached locally {$activitypub_event_id}" )
|
||||
);
|
||||
return new WP_Error(
|
||||
'event_bridge_for_activitypub_remote_event_not_found',
|
||||
\__( 'Remote event not found in cache', 'event-bridge-for-activitypub' ),
|
||||
array( 'status' => 404 )
|
||||
);
|
||||
}
|
||||
|
||||
$thumbnail_id = get_post_thumbnail_id( $post_id );
|
||||
|
||||
if ( $thumbnail_id && ! Event_Sources::is_attachment_featured_image( $thumbnail_id ) ) {
|
||||
wp_delete_attachment( $thumbnail_id, true );
|
||||
}
|
||||
|
||||
$result = wp_delete_post( $post_id, true );
|
||||
|
||||
if ( $result ) {
|
||||
\do_action( 'event_bridge_for_activitypub_write_log', array( "[ACTIVITYPUB] Deleted cached event {$activitypub_event_id}" ) );
|
||||
} else {
|
||||
\do_action( 'event_bridge_for_activitypub_write_log', array( "[ACTIVITYPUB] Failed deleting cached event {$activitypub_event_id}" ) );
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format an ActivityStreams xds:datetime to WordPress GMT format.
|
||||
*
|
||||
* @param string $time_string The ActivityStreams xds:datetime (may include offset).
|
||||
* @return string The GMT string in format 'Y-m-d H:i:s'.
|
||||
*/
|
||||
protected static function format_time_string_to_wordpress_gmt( $time_string ): string {
|
||||
$datetime = new \DateTime( $time_string );
|
||||
$datetime->setTimezone( new \DateTimeZone( 'GMT' ) );
|
||||
return $datetime->format( 'Y-m-d H:i:s' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get WordPress post by ActivityPub object ID using the guid.
|
||||
*
|
||||
* @param string $activitypub_id The ActivityPub object ID.
|
||||
* @return int The WordPress Post ID, 0 if not post with that ActivityPub object ID (by guid) is found.
|
||||
*/
|
||||
protected static function get_post_id_from_activitypub_id( $activitypub_id ): int {
|
||||
global $wpdb;
|
||||
return (int) $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT ID FROM $wpdb->posts WHERE guid=%s",
|
||||
esc_sql( $activitypub_id ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the image URL and alt-text of an ActivityPub object.
|
||||
*
|
||||
* @param mixed $data The ActivityPub object as ann associative array.
|
||||
* @return array Array containing the images URL and alt-text.
|
||||
*/
|
||||
private static function extract_image_alt_and_url( $data ): array {
|
||||
$image = array(
|
||||
'url' => null,
|
||||
'alt' => null,
|
||||
);
|
||||
|
||||
// Check whether it is already simple.
|
||||
if ( ! $data || is_string( $data ) ) {
|
||||
$image['url'] = $data;
|
||||
return $image;
|
||||
}
|
||||
|
||||
if ( ! isset( $data['type'] ) ) {
|
||||
return $image;
|
||||
}
|
||||
|
||||
if ( ! in_array( $data['type'], array( 'Document', 'Image' ), true ) ) {
|
||||
return $image;
|
||||
}
|
||||
|
||||
if ( isset( $data['url'] ) ) {
|
||||
$image['url'] = $data['url'];
|
||||
} elseif ( isset( $data['id'] ) ) {
|
||||
$image['id'] = $data['id'];
|
||||
}
|
||||
|
||||
if ( isset( $data['name'] ) ) {
|
||||
$image['alt'] = $data['name'];
|
||||
}
|
||||
|
||||
return $image;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URL of the featured image.
|
||||
*
|
||||
* @param Event $event The ActivityPub event object.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected static function get_featured_image( $event ): array {
|
||||
// Search for the featured image in the image property.
|
||||
$image = $event->get_image();
|
||||
|
||||
if ( $image ) {
|
||||
return self::extract_image_alt_and_url( $image );
|
||||
}
|
||||
|
||||
// Fallback attachment.
|
||||
$attachment = $event->get_attachment();
|
||||
|
||||
// If attachment is an array get the first fitting one.
|
||||
if ( is_array( $attachment ) && ! empty( $attachment ) ) {
|
||||
$supported_types = array( 'Image', 'Document' );
|
||||
$match = null;
|
||||
|
||||
foreach ( $attachment as $item ) {
|
||||
if ( in_array( $item['type'], $supported_types, true ) ) {
|
||||
$match = $item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$attachment = $match;
|
||||
}
|
||||
|
||||
return self::extract_image_alt_and_url( $attachment );
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an image URL return an attachment ID. Image will be side-loaded into the media library if it doesn't exist.
|
||||
*
|
||||
* Forked from https://gist.github.com/kingkool68/a66d2df7835a8869625282faa78b489a.
|
||||
*
|
||||
* @param int $post_id The post ID where the image will be set as featured image.
|
||||
* @param string $url The image URL to maybe sideload.
|
||||
* @uses media_sideload_image
|
||||
* @return string|int|WP_Error
|
||||
*/
|
||||
protected static function maybe_sideload_image( $post_id, $url = '' ) {
|
||||
global $wpdb;
|
||||
|
||||
// Include necessary WordPress file for media handling.
|
||||
if ( ! function_exists( 'media_sideload_image' ) ) {
|
||||
// @phpstan-ignore-next-line
|
||||
require_once ABSPATH . 'wp-admin/includes/media.php';
|
||||
// @phpstan-ignore-next-line
|
||||
require_once ABSPATH . 'wp-admin/includes/file.php';
|
||||
// @phpstan-ignore-next-line
|
||||
require_once ABSPATH . 'wp-admin/includes/image.php';
|
||||
}
|
||||
|
||||
// Check to see if the URL has already been fetched, if so return the attachment ID.
|
||||
$attachment_id = $wpdb->get_var(
|
||||
$wpdb->prepare( "SELECT `post_id` FROM {$wpdb->postmeta} WHERE `meta_key` = '_source_url' AND `meta_value` = %s", $url )
|
||||
);
|
||||
if ( ! empty( $attachment_id ) ) {
|
||||
return $attachment_id;
|
||||
}
|
||||
|
||||
$attachment_id = $wpdb->get_var(
|
||||
$wpdb->prepare( "SELECT `ID` FROM {$wpdb->posts} WHERE guid=%s", $url )
|
||||
);
|
||||
if ( ! empty( $attachment_id ) ) {
|
||||
return $attachment_id;
|
||||
}
|
||||
|
||||
// If the URL doesn't exist, sideload it to the media library.
|
||||
return media_sideload_image( $url, $post_id, $url, 'id' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sideload an image_url set it as featured image and add the alt-text.
|
||||
*
|
||||
* @param int $post_id The post ID where the image will be set as featured image.
|
||||
* @param string $image_url The image URL.
|
||||
* @param string $alt_text The alt-text of the image.
|
||||
* @return int|WP_Error The attachment ID
|
||||
*/
|
||||
protected static function set_featured_image_with_alt( $post_id, $image_url, $alt_text = '' ) {
|
||||
// Maybe sideload the image or get the Attachment ID of an existing one.
|
||||
$image_id = self::maybe_sideload_image( $post_id, $image_url );
|
||||
|
||||
if ( \is_wp_error( $image_id ) ) {
|
||||
// Handle the error.
|
||||
return $image_id;
|
||||
}
|
||||
|
||||
// Set the image as the featured image for the post.
|
||||
\set_post_thumbnail( $post_id, $image_id );
|
||||
|
||||
// Update the alt text.
|
||||
if ( ! empty( $alt_text ) ) {
|
||||
\update_post_meta( $image_id, '_wp_attachment_image_alt', $alt_text );
|
||||
}
|
||||
|
||||
return $image_id; // Return the attachment ID for further use if needed.
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a PostalAddress to a string.
|
||||
*
|
||||
* @link https://schema.org/PostalAddress
|
||||
*
|
||||
* @param array $postal_address The PostalAddress as an associative array.
|
||||
* @return string
|
||||
*/
|
||||
private static function postal_address_to_string( $postal_address ): string {
|
||||
if ( ! isset( $postal_address['type'] ) || 'PostalAddress' !== $postal_address['type'] ) {
|
||||
_doing_it_wrong(
|
||||
__METHOD__,
|
||||
'The parameter postal_address must be an associate array like schema.org/PostalAddress.',
|
||||
esc_html( EVENT_BRIDGE_FOR_ACTIVITYPUB_PLUGIN_VERSION )
|
||||
);
|
||||
}
|
||||
|
||||
$address = array();
|
||||
$known_attributes = array(
|
||||
'streetAddress',
|
||||
'postalCode',
|
||||
'addressLocality',
|
||||
'addressState',
|
||||
'addressCountry',
|
||||
);
|
||||
|
||||
foreach ( $known_attributes as $attribute ) {
|
||||
if ( isset( $postal_address[ $attribute ] ) && is_string( $postal_address[ $attribute ] ) ) {
|
||||
$address[] = $postal_address[ $attribute ];
|
||||
}
|
||||
}
|
||||
|
||||
$address_string = implode( ' ,', $address );
|
||||
|
||||
return $address_string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an address to a string.
|
||||
*
|
||||
* @param mixed $address The address as an object, string or associative array.
|
||||
* @return string
|
||||
*/
|
||||
protected static function address_to_string( $address ): string {
|
||||
if ( is_string( $address ) ) {
|
||||
return $address;
|
||||
}
|
||||
|
||||
if ( is_object( $address ) ) {
|
||||
$address = (array) $address;
|
||||
}
|
||||
|
||||
if ( ! is_array( $address ) || ! isset( $address['type'] ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if ( 'PostalAddress' === $address['type'] ) {
|
||||
return self::postal_address_to_string( $address );
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of revisions to keep.
|
||||
*
|
||||
* @return int The number of revisions to keep.
|
||||
*/
|
||||
public static function revisions_to_keep(): int {
|
||||
return 5;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the URL of the online event link.
|
||||
*
|
||||
* @param Event $event The ActivityPub event object.
|
||||
*
|
||||
* @return ?string
|
||||
*/
|
||||
protected static function get_online_event_link_from_attachments( $event ): ?string {
|
||||
$attachments = $event->get_attachment();
|
||||
|
||||
if ( ! is_array( $attachments ) || empty( $attachments ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ( $attachments as $attachment ) {
|
||||
if ( array_key_exists( 'type', $attachment ) && 'Link' === $attachment['type'] && isset( $attachment['href'] ) ) {
|
||||
return $attachment['href'];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,207 @@
|
||||
<?php
|
||||
/**
|
||||
* ActivityPub Transmogrify for the GatherPress event plugin.
|
||||
*
|
||||
* Handles converting incoming external ActivityPub events to GatherPress Events.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Transmogrifier;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Activitypub\Activity\Extended_Object\Event;
|
||||
use Activitypub\Activity\Extended_Object\Place;
|
||||
use DateTime;
|
||||
use Event_Bridge_For_ActivityPub\Integrations\GatherPress as IntegrationsGatherPress;
|
||||
use GatherPress\Core\Event as GatherPress_Event;
|
||||
|
||||
/**
|
||||
* ActivityPub Transmogrifier for the GatherPress event plugin.
|
||||
*
|
||||
* Handles converting incoming external ActivityPub events to GatherPress Events.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class GatherPress extends Base {
|
||||
/**
|
||||
* Add tags to post.
|
||||
*
|
||||
* @param Event $event The ActivityPub event object.
|
||||
* @param int $post_id The post ID.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function add_tags_to_post( $event, $post_id ) {
|
||||
$tags_array = $event->get_tag();
|
||||
|
||||
// Ensure the input is valid.
|
||||
if ( empty( $tags_array ) || ! is_array( $tags_array ) || ! $post_id ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extract and process tag names.
|
||||
$tag_names = array();
|
||||
foreach ( $tags_array as $tag ) {
|
||||
if ( isset( $tag['name'] ) && 'Hashtag' === $tag['type'] ) {
|
||||
$tag_names[] = ltrim( $tag['name'], '#' ); // Remove the '#' from the name.
|
||||
}
|
||||
}
|
||||
|
||||
// Add the tags as terms to the post.
|
||||
if ( ! empty( $tag_names ) ) {
|
||||
\wp_set_object_terms( $post_id, $tag_names, IntegrationsGatherPress::get_event_category_taxonomy(), true );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add venue.
|
||||
*
|
||||
* @param Event $activitypub_event The ActivityPub event object.
|
||||
* @param int $post_id The post ID.
|
||||
*/
|
||||
private static function add_venue( $activitypub_event, $post_id ) {
|
||||
$location = $activitypub_event->get_location();
|
||||
|
||||
if ( ! $location ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $location instanceof Place ) {
|
||||
$location = $location->to_array();
|
||||
}
|
||||
|
||||
if ( ! is_array( $location ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! isset( $location['name'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback for Gancio instances.
|
||||
if ( 'online' === $location['name'] ) {
|
||||
$online_event_link = self::get_online_event_link_from_attachments( $activitypub_event );
|
||||
if ( ! $online_event_link ) {
|
||||
return;
|
||||
}
|
||||
\update_post_meta( $post_id, 'gatherpress_online_event_link', \sanitize_url( $online_event_link ) );
|
||||
\wp_set_object_terms( $post_id, 'online-event', '_gatherpress_venue', false );
|
||||
return;
|
||||
}
|
||||
|
||||
$venue_instance = \GatherPress\Core\Venue::get_instance();
|
||||
$venue_name = \sanitize_title( $location['name'] );
|
||||
$venue_slug = $venue_instance->get_venue_term_slug( $venue_name );
|
||||
$venue_post = $venue_instance->get_venue_post_from_term_slug( $venue_slug );
|
||||
|
||||
if ( ! $venue_post ) {
|
||||
$venue_id = \wp_insert_post(
|
||||
array(
|
||||
'post_title' => sanitize_text_field( $location['name'] ),
|
||||
'post_type' => 'gatherpress_venue',
|
||||
'post_status' => 'publish',
|
||||
)
|
||||
);
|
||||
} else {
|
||||
$venue_id = $venue_post->ID;
|
||||
}
|
||||
|
||||
$venue_information = array();
|
||||
|
||||
$address_string = isset( $location['address'] ) ? self::address_to_string( $location['address'] ) : '';
|
||||
|
||||
$venue_information['fullAddress'] = $address_string;
|
||||
$venue_information['phone_number'] = '';
|
||||
$venue_information['website'] = '';
|
||||
$venue_information['permalink'] = '';
|
||||
|
||||
$venue_json = \wp_json_encode( $venue_information );
|
||||
|
||||
\update_post_meta( $venue_id, 'gatherpress_venue_information', $venue_json );
|
||||
|
||||
\wp_set_object_terms( $post_id, $venue_slug, '_gatherpress_venue', false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the ActivityPub event object as GatherPress Event.
|
||||
*
|
||||
* @param Event $activitypub_event The ActivityPub event object.
|
||||
* @param int $event_source_post_id The Post ID of the Event Source that owns the outbox.
|
||||
*
|
||||
* @return false|int
|
||||
*/
|
||||
protected static function save_event( $activitypub_event, $event_source_post_id ) {
|
||||
// Limit this as a safety measure.
|
||||
\add_filter( 'wp_revisions_to_keep', array( self::class, 'revisions_to_keep' ) );
|
||||
|
||||
$post_id = self::get_post_id_from_activitypub_id( $activitypub_event->get_id() );
|
||||
|
||||
$args = array(
|
||||
'post_title' => sanitize_text_field( $activitypub_event->get_name() ),
|
||||
'post_type' => 'gatherpress_event',
|
||||
'post_content' => wp_kses_post( $activitypub_event->get_content() ?? '' ) . '<!-- wp:gatherpress/venue /-->',
|
||||
'post_excerpt' => wp_kses_post( $activitypub_event->get_summary() ?? '' ),
|
||||
'post_status' => 'publish',
|
||||
'guid' => sanitize_url( $activitypub_event->get_id() ),
|
||||
);
|
||||
|
||||
if ( $activitypub_event->get_published() ) {
|
||||
$post_date = self::format_time_string_to_wordpress_gmt( $activitypub_event->get_published() );
|
||||
$args['post_date'] = $post_date;
|
||||
$args['post_date_gmt'] = $post_date;
|
||||
}
|
||||
|
||||
if ( $post_id ) {
|
||||
// Update existing GatherPress event post.
|
||||
$args['ID'] = $post_id;
|
||||
\wp_update_post( $args );
|
||||
} else {
|
||||
// Insert new GatherPress event post.
|
||||
$post_id = \wp_insert_post( $args );
|
||||
}
|
||||
|
||||
// @phpstan-ignore-next-line
|
||||
if ( ! $post_id || \is_wp_error( $post_id ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Insert the dates.
|
||||
$gatherpress_event = new GatherPress_Event( $post_id );
|
||||
$start_time = $activitypub_event->get_start_time();
|
||||
$end_time = $activitypub_event->get_end_time();
|
||||
if ( ! $end_time ) {
|
||||
$end_time = new DateTime( $start_time );
|
||||
$end_time->modify( '+1 hour' );
|
||||
$end_time = $end_time->format( 'Y-m-d H:i:s' );
|
||||
}
|
||||
$params = array(
|
||||
'datetime_start' => $start_time,
|
||||
'datetime_end' => $end_time,
|
||||
'timezone' => $activitypub_event->get_timezone(),
|
||||
);
|
||||
// Sanitization of the params is done in the save_datetimes function just in time.
|
||||
$gatherpress_event->save_datetimes( $params );
|
||||
|
||||
// Insert featured image.
|
||||
$image = self::get_featured_image( $activitypub_event );
|
||||
self::set_featured_image_with_alt( $post_id, $image['url'], $image['alt'] );
|
||||
|
||||
// Add hashtags.
|
||||
self::add_tags_to_post( $activitypub_event, $post_id );
|
||||
|
||||
// Add venue.
|
||||
self::add_venue( $activitypub_event, $post_id );
|
||||
|
||||
// Limit this as a safety measure.
|
||||
\remove_filter( 'wp_revisions_to_keep', array( self::class, 'revisions_to_keep' ) );
|
||||
|
||||
return $post_id;
|
||||
}
|
||||
}
|
@ -0,0 +1,371 @@
|
||||
<?php
|
||||
/**
|
||||
* ActivityPub Transmogrify for the The Events Calendar event plugin.
|
||||
*
|
||||
* Handles converting incoming external ActivityPub events to The Events Calendar Events.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Transmogrifier;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Activitypub\Activity\Extended_Object\Event;
|
||||
use Activitypub\Activity\Extended_Object\Place;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Model\Event_Source;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transmogrifier\Helper\The_Events_Calendar_Event_Repository;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transmogrifier\Helper\The_Events_Calendar_Venue_Repository;
|
||||
|
||||
use function Activitypub\object_to_uri;
|
||||
|
||||
/**
|
||||
* ActivityPub Transmogrifier for the GatherPress event plugin.
|
||||
*
|
||||
* Handles converting incoming external ActivityPub events to GatherPress Events.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class The_Events_Calendar extends Base {
|
||||
/**
|
||||
* Save the ActivityPub event object as GatherPress Event.
|
||||
*
|
||||
* @param Event $activitypub_event The ActivityPub event as associative array.
|
||||
* @param int $event_source_post_id The Post ID of the Event Source that owns the outbox.
|
||||
*
|
||||
* @return false|int
|
||||
*/
|
||||
protected static function save_event( $activitypub_event, $event_source_post_id ) {
|
||||
// Limit the number of saved post revisions as a safety measure.
|
||||
add_filter( 'wp_revisions_to_keep', array( self::class, 'revisions_to_keep' ) );
|
||||
|
||||
$post_id = self::get_post_id_from_activitypub_id( $activitypub_event->get_id() );
|
||||
$duration = self::get_duration( $activitypub_event );
|
||||
$venue_id = self::add_venue( $activitypub_event, $event_source_post_id );
|
||||
$organizer_id = self::add_organizer( $activitypub_event );
|
||||
|
||||
$args = array(
|
||||
'title' => $activitypub_event->get_name(),
|
||||
'content' => $activitypub_event->get_content() ?? '',
|
||||
'start_date' => gmdate( 'Y-m-d H:i:s', strtotime( $activitypub_event->get_start_time() ) ),
|
||||
'duration' => $duration,
|
||||
'status' => 'publish',
|
||||
'guid' => $activitypub_event->get_id(),
|
||||
);
|
||||
|
||||
if ( $venue_id ) {
|
||||
$args['venue'] = $venue_id;
|
||||
$args['VenueID'] = $venue_id;
|
||||
}
|
||||
|
||||
if ( $organizer_id ) {
|
||||
$args['organizer'] = $organizer_id;
|
||||
$args['OrganizerID'] = $organizer_id;
|
||||
}
|
||||
|
||||
if ( $activitypub_event->get_published() ) {
|
||||
$post_date = self::format_time_string_to_wordpress_gmt( $activitypub_event->get_published() );
|
||||
$args['post_date'] = $post_date;
|
||||
$args['post_date_gmt'] = $post_date;
|
||||
}
|
||||
|
||||
$tribe_event = new The_Events_Calendar_Event_Repository();
|
||||
|
||||
if ( $post_id ) {
|
||||
$post = $tribe_event->where( 'id', $post_id )->set_args( $args )->save();
|
||||
} else {
|
||||
$post = $tribe_event->set_args( $args )->create();
|
||||
}
|
||||
|
||||
if ( $post instanceof \WP_Post ) {
|
||||
$post_id = $post->ID;
|
||||
}
|
||||
|
||||
if ( ! $post_id ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Insert featured image.
|
||||
$image = self::get_featured_image( $activitypub_event );
|
||||
if ( isset( $image['url'] ) ) {
|
||||
self::set_featured_image_with_alt( $post_id, $image['url'], $image['alt'] );
|
||||
}
|
||||
|
||||
// Add tags.
|
||||
self::add_tags_to_post( $activitypub_event, $post_id );
|
||||
|
||||
// Remove revision limit.
|
||||
remove_filter( 'wp_revisions_to_keep', array( self::class, 'revisions_to_keep' ) );
|
||||
|
||||
return $post_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map an ActivityStreams Place to the Events Calendar venue.
|
||||
*
|
||||
* @param array|Place $location An ActivityPub location as an associative array or Place object.
|
||||
* @link https://www.w3.org/TR/activitystreams-vocabulary/#dfn-place
|
||||
* @return array
|
||||
*/
|
||||
private static function get_venue_args( $location ): array {
|
||||
$args = array(
|
||||
'venue' => $location['name'],
|
||||
'status' => 'publish',
|
||||
);
|
||||
|
||||
if ( $location instanceof Place ) {
|
||||
$location = $location->to_array();
|
||||
}
|
||||
|
||||
if ( ! isset( $location['address'] ) ) {
|
||||
return $args;
|
||||
}
|
||||
|
||||
if ( is_array( $location['address'] ) ) {
|
||||
$mapping = array(
|
||||
'streetAddress' => 'address',
|
||||
'postalCode' => 'zip',
|
||||
'addressLocality' => 'city',
|
||||
'addressState' => 'state',
|
||||
'addressCountry' => 'country',
|
||||
'url' => 'website',
|
||||
);
|
||||
|
||||
foreach ( $mapping as $postal_address_key => $venue_key ) {
|
||||
if ( isset( $location['address'][ $postal_address_key ] ) ) {
|
||||
$args[ $venue_key ] = $location['address'][ $postal_address_key ];
|
||||
}
|
||||
}
|
||||
} elseif ( is_string( $location['address'] ) ) {
|
||||
// Use the address field for a solely text address.
|
||||
$args['address'] = $location['address'];
|
||||
}
|
||||
|
||||
if ( isset( $location['id'] ) ) {
|
||||
$args['guid'] = $location['id'];
|
||||
}
|
||||
|
||||
return $args;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add venue.
|
||||
*
|
||||
* @param Event $activitypub_event The ActivityPub event object.
|
||||
* @param int $event_source_post_id The WordPress Post ID of the event source.
|
||||
*
|
||||
* @return ?int $post_id The venues post ID.
|
||||
*/
|
||||
private static function add_venue( $activitypub_event, $event_source_post_id ): ?int {
|
||||
$location = $activitypub_event->get_location();
|
||||
|
||||
// Make sure we have a valid location in the right format.
|
||||
if ( ! $location ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( $location instanceof Place ) {
|
||||
$location = $location->to_array();
|
||||
}
|
||||
|
||||
if ( ! is_array( $location ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( ! isset( $location['name'] ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Fallback for Gancio instances.
|
||||
if ( 'online' === $location['name'] ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$tribe_venue = new The_Events_Calendar_Venue_Repository();
|
||||
|
||||
// If the venue already exists try to find it's post id.
|
||||
$post_id = null;
|
||||
|
||||
// Search if we already got this venue/place in our database.
|
||||
if ( isset( $location['id'] ) ) {
|
||||
global $wpdb;
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery
|
||||
$post_id = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT ID FROM $wpdb->posts WHERE guid=%s AND post_type=%s",
|
||||
$location['id'],
|
||||
\Tribe__Events__Venue::POSTTYPE
|
||||
)
|
||||
);
|
||||
if ( $post_id ) {
|
||||
$post_id = (int) $post_id;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $post_id ) {
|
||||
// Try to find a match by searching.
|
||||
$results = $tribe_venue->search( $location['name'] )->all();
|
||||
|
||||
foreach ( $results as $potential_matching_post_id ) {
|
||||
// @phpstan-ignore-next-line
|
||||
if ( $potential_matching_post_id instanceof \WP_Post ) {
|
||||
$potential_matching_post_id = $potential_matching_post_id->ID;
|
||||
}
|
||||
// Only accept a match for the venue/location if it was received by the same actor.
|
||||
if ( \get_post_meta( $potential_matching_post_id, '_event_bridge_for_activitypub_event_source', true ) === $event_source_post_id ) {
|
||||
$post_id = $potential_matching_post_id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( $post_id ) {
|
||||
// Update if we found a match.
|
||||
$result = $tribe_venue->where( 'id', $post_id )->set_args( self::get_venue_args( $location ) )->save();
|
||||
if ( array_key_exists( $post_id, $result ) && $result[ $post_id ] ) {
|
||||
return $post_id;
|
||||
}
|
||||
} else {
|
||||
// Create a new venue.
|
||||
$post = $tribe_venue->set_args( self::get_venue_args( $location ) )->create();
|
||||
if ( $post ) {
|
||||
$post_id = $post->ID;
|
||||
update_post_meta( $post_id, '_event_bridge_for_activitypub_event_source', $event_source_post_id );
|
||||
}
|
||||
}
|
||||
|
||||
return $post_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add organizer.
|
||||
*
|
||||
* @param Event $activitypub_event The ActivityPub event object.
|
||||
*
|
||||
* @return int|bool $post_id The organizers post ID.
|
||||
*/
|
||||
private static function add_organizer( $activitypub_event ) {
|
||||
// This might likely change, because of FEP-8a8e.
|
||||
$actor = $activitypub_event->get_attributed_to();
|
||||
|
||||
if ( is_null( $actor ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$actor_id = object_to_uri( $actor );
|
||||
$event_source = Event_Source::get_by_id( $actor_id );
|
||||
|
||||
// As long as we do not support announces, we expect the attributedTo to be an existing event source.
|
||||
if ( ! $event_source ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Prepare arguments for inserting/updating the organizer post.
|
||||
$args = array(
|
||||
'organizer' => $event_source->get_name(),
|
||||
'description' => $event_source->get_summary(),
|
||||
'website' => $event_source->get_url(),
|
||||
'excerpt' => $event_source->get_summary(),
|
||||
'post_parent' => $event_source->get__id(), // Maybe just use post meta too here.
|
||||
);
|
||||
|
||||
if ( $event_source->get_published() ) {
|
||||
$post_date = self::format_time_string_to_wordpress_gmt( $event_source->get_published() );
|
||||
$args['post_date'] = $post_date;
|
||||
$args['post_date_gmt'] = $post_date;
|
||||
}
|
||||
|
||||
// Get organizer if it is already present.
|
||||
$children = \get_children(
|
||||
array(
|
||||
'post_parent' => $event_source->get__id(),
|
||||
'post_type' => \Tribe__Events__Organizer::POSTTYPE,
|
||||
),
|
||||
);
|
||||
|
||||
if ( count( $children ) ) {
|
||||
// Update organizer post.
|
||||
$child = array_pop( $children );
|
||||
$tribe_organizer_post_ids = \tribe_organizers()->where( 'id', $child->ID )->set_args( $args )->save();
|
||||
|
||||
// Fallback to delete duplicates.
|
||||
foreach ( $children as $to_delete ) {
|
||||
\wp_delete_post( $to_delete->ID, true );
|
||||
}
|
||||
|
||||
// If updating failed return.
|
||||
if ( 1 !== count( $tribe_organizer_post_ids ) || ! reset( $tribe_organizer_post_ids ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$tribe_organizer_post_id = array_key_first( $tribe_organizer_post_ids );
|
||||
} else {
|
||||
// Create new organizer post.
|
||||
$tribe_organizer_post = \tribe_organizers()->set_args( $args )->create();
|
||||
|
||||
if ( ! $tribe_organizer_post ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$tribe_organizer_post_id = $tribe_organizer_post->ID;
|
||||
|
||||
// Make a relationship between the event source WP_Post and the organizer WP_Post.
|
||||
\update_post_meta( $tribe_organizer_post_id, '_event_bridge_for_activitypub_event_source', true );
|
||||
}
|
||||
|
||||
// Add the thumbnail of the event source to the organizer.
|
||||
if ( \get_post_thumbnail_id( $event_source->get__id() ) ) {
|
||||
\set_post_thumbnail( $tribe_organizer_post_id, \get_post_thumbnail_id( $event_source->get__id() ) );
|
||||
}
|
||||
|
||||
return $tribe_organizer_post_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add tags to post.
|
||||
*
|
||||
* @param Event $activitypub_event The ActivityPub event object.
|
||||
* @param int $post_id The post ID.
|
||||
*/
|
||||
private static function add_tags_to_post( $activitypub_event, $post_id ): bool {
|
||||
$tags_array = $activitypub_event->get_tag();
|
||||
|
||||
// Ensure the input is valid.
|
||||
if ( empty( $tags_array ) || ! is_array( $tags_array ) || ! $post_id ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extract and process tag names.
|
||||
$tag_names = array();
|
||||
foreach ( $tags_array as $tag ) {
|
||||
if ( isset( $tag['name'] ) && 'Hashtag' === $tag['type'] ) {
|
||||
$tag_names[] = ltrim( $tag['name'], '#' ); // Remove the '#' from the name.
|
||||
}
|
||||
}
|
||||
|
||||
// Add the tags as terms to the post.
|
||||
if ( ! empty( $tag_names ) ) {
|
||||
\wp_set_object_terms( $post_id, $tag_names, 'post_tag', true );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the events duration in seconds.
|
||||
*
|
||||
* @param Event $activitypub_event The ActivityPub event object.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private static function get_duration( $activitypub_event ): int {
|
||||
$end_time = $activitypub_event->get_end_time();
|
||||
if ( ! $end_time ) {
|
||||
return 2 * HOUR_IN_SECONDS;
|
||||
}
|
||||
return abs( strtotime( $end_time ) - strtotime( $activitypub_event->get_start_time() ) );
|
||||
}
|
||||
}
|
@ -0,0 +1,175 @@
|
||||
<?php
|
||||
/**
|
||||
* ActivityPub Transmogrifier for the VS Event List event plugin.
|
||||
*
|
||||
* Handles converting incoming external ActivityPub events to events of VS Event List.
|
||||
*
|
||||
* @link https://wordpress.org/plugins/very-simple-event-list/
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Transmogrifier;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Activitypub\Activity\Extended_Object\Event;
|
||||
use Activitypub\Activity\Extended_Object\Place;
|
||||
use Event_Bridge_For_ActivityPub\Integrations\VS_Event_List as IntegrationsVS_Event_List;
|
||||
|
||||
/**
|
||||
* ActivityPub Transmogrifier for the VS Event List event plugin.
|
||||
*
|
||||
* Handles converting incoming external ActivityPub events to events of VS Event List.
|
||||
*
|
||||
* @link https://wordpress.org/plugins/very-simple-event-list/
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class VS_Event_List extends Base {
|
||||
/**
|
||||
* Extract location and address as string.
|
||||
*
|
||||
* @param mixed $location The ActivityStreams location.
|
||||
* @return string The location and address formatted as a single string.
|
||||
*/
|
||||
private static function get_location_as_string( $location ): string {
|
||||
$location_string = '';
|
||||
|
||||
if ( $location instanceof Place ) {
|
||||
$location = $location->to_array();
|
||||
}
|
||||
|
||||
// Return empty string when location is not an associative array.
|
||||
if ( ! is_array( $location ) || 0 === count( $location ) ) {
|
||||
return $location_string;
|
||||
}
|
||||
|
||||
if ( ! isset( $location['type'] ) || 'Place' !== $location['type'] ) {
|
||||
return $location_string;
|
||||
}
|
||||
|
||||
// Add name of the location.
|
||||
if ( isset( $location['name'] ) ) {
|
||||
$location_string .= $location['name'];
|
||||
}
|
||||
|
||||
// Add delimiter between name and address if both are set.
|
||||
if ( isset( $location['name'] ) && isset( $location['address'] ) ) {
|
||||
$location_string .= ' – ';
|
||||
}
|
||||
|
||||
// Add address.
|
||||
if ( isset( $location['address'] ) ) {
|
||||
$location_string .= self::address_to_string( $location['address'] );
|
||||
}
|
||||
return $location_string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add tags to post.
|
||||
*
|
||||
* @param Event $activitypub_event The ActivityPub event object.
|
||||
* @param int $post_id The post ID.
|
||||
*/
|
||||
private static function add_tags_to_post( $activitypub_event, $post_id ) {
|
||||
$tags_array = $activitypub_event->get_tag();
|
||||
|
||||
// Ensure the input is valid.
|
||||
if ( empty( $tags_array ) || ! is_array( $tags_array ) || ! $post_id ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extract and process tag names.
|
||||
$tag_names = array();
|
||||
foreach ( $tags_array as $tag ) {
|
||||
if ( isset( $tag['name'] ) && 'Hashtag' === $tag['type'] ) {
|
||||
$tag_names[] = ltrim( $tag['name'], '#' ); // Remove the '#' from the name.
|
||||
}
|
||||
}
|
||||
|
||||
// Add the tags as terms to the post.
|
||||
if ( ! empty( $tag_names ) ) {
|
||||
\wp_set_object_terms( $post_id, $tag_names, IntegrationsVS_Event_List::get_event_category_taxonomy(), true );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save the ActivityPub event object as VS Event List event.
|
||||
*
|
||||
* @param Event $activitypub_event The ActivityPub event object.
|
||||
* @param int $event_source_post_id The Post ID of the Event Source that owns the outbox.
|
||||
*
|
||||
* @return false|int
|
||||
*/
|
||||
protected static function save_event( $activitypub_event, $event_source_post_id ) {
|
||||
// Limit this as a safety measure.
|
||||
\add_filter( 'wp_revisions_to_keep', array( self::class, 'revisions_to_keep' ) );
|
||||
|
||||
$post_id = self::get_post_id_from_activitypub_id( $activitypub_event->get_id() );
|
||||
|
||||
$args = array(
|
||||
'post_title' => $activitypub_event->get_name(),
|
||||
'post_type' => \Event_Bridge_For_ActivityPub\Integrations\VS_Event_List::get_post_type(),
|
||||
'post_content' => $activitypub_event->get_content() ?? '',
|
||||
'post_excerpt' => $activitypub_event->get_summary() ?? '',
|
||||
'post_status' => 'publish',
|
||||
'guid' => $activitypub_event->get_id(),
|
||||
'meta_input' => array(
|
||||
'event-start-date' => \strtotime( $activitypub_event->get_start_time() ),
|
||||
'event-link' => $activitypub_event->get_url() ?? $activitypub_event->get_id(),
|
||||
'event-link-label' => \sanitize_text_field( __( 'Original Website', 'event-bridge-for-activitypub' ) ),
|
||||
'event-link-target' => 'yes', // Open in new window.
|
||||
'event-link-title' => 'no', // Whether to redirect event title to original source.
|
||||
'event-link-image' => 'no', // Whether to redirect events featured image to original source.
|
||||
),
|
||||
);
|
||||
|
||||
if ( $activitypub_event->get_published() ) {
|
||||
$post_date = self::format_time_string_to_wordpress_gmt( $activitypub_event->get_published() );
|
||||
$args['post_date'] = $post_date;
|
||||
$args['post_date_gmt'] = $post_date;
|
||||
}
|
||||
|
||||
// Add end time.
|
||||
$end_time = $activitypub_event->get_end_time();
|
||||
if ( $end_time ) {
|
||||
$args['meta_input']['event-date'] = \strtotime( $end_time );
|
||||
}
|
||||
|
||||
// Maybe add location.
|
||||
$location = self::get_location_as_string( $activitypub_event->get_location() );
|
||||
if ( $location ) {
|
||||
$args['meta_input']['event-location'] = $location;
|
||||
}
|
||||
|
||||
if ( $post_id ) {
|
||||
// Update existing event post.
|
||||
$args['ID'] = $post_id;
|
||||
$post_id = \wp_update_post( $args );
|
||||
} else {
|
||||
// Insert new event post.
|
||||
$post_id = \wp_insert_post( $args );
|
||||
}
|
||||
|
||||
// @phpstan-ignore-next-line
|
||||
if ( 0 === $post_id || \is_wp_error( $post_id ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Insert featured image.
|
||||
$image = self::get_featured_image( $activitypub_event );
|
||||
self::set_featured_image_with_alt( $post_id, $image['url'], $image['alt'] );
|
||||
|
||||
// Add hashtags.
|
||||
self::add_tags_to_post( $activitypub_event, $post_id );
|
||||
|
||||
// Limit this as a safety measure.
|
||||
\remove_filter( 'wp_revisions_to_keep', array( self::class, 'revisions_to_keep' ) );
|
||||
|
||||
return $post_id;
|
||||
}
|
||||
}
|
@ -0,0 +1,241 @@
|
||||
<?php
|
||||
/**
|
||||
* Collection of functions that sanitize an incoming event.
|
||||
*
|
||||
* We do a lot of duck-typing. We just discard/ignore attributes/properties we do not know.
|
||||
* Replacing this with defining a schema and using rest_sanitize_value_from_schema is a future goal.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Transmogrifier\Helper;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Activitypub\Activity\Extended_Object\Event;
|
||||
use Activitypub\Activity\Extended_Object\Place;
|
||||
use WP_Error;
|
||||
|
||||
use function Activitypub\object_to_uri;
|
||||
|
||||
/**
|
||||
* Collection of functions that sanitize an incoming event.
|
||||
*
|
||||
* We do a lot of duck-typing. We just discard/ignore attributes/properties we do not know.
|
||||
* Replacing this with defining a schema and using rest_sanitize_value_from_schema is a future goal.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
class Sanitizer {
|
||||
/**
|
||||
* Convert input array to an Event.
|
||||
*
|
||||
* @param mixed $data The object array.
|
||||
*
|
||||
* @return Event|WP_Error An Object built from the input array or WP_Error when it's not an array.
|
||||
*/
|
||||
public static function init_and_sanitize_event_object_from_array( $data ) {
|
||||
if ( ! is_array( $data ) ) {
|
||||
return new WP_Error( 'invalid_array', __( 'Invalid array', 'event-bridge-for-activitypub' ), array( 'status' => 404 ) );
|
||||
}
|
||||
|
||||
$event = new Event();
|
||||
|
||||
// Straightforward sanitization of all attributes we possible make use of.
|
||||
if ( isset( $data['content'] ) ) {
|
||||
$event->set_content( \wp_kses_post( $data['content'] ) );
|
||||
}
|
||||
|
||||
if ( isset( $data['summary'] ) ) {
|
||||
$event->set_summary( \wp_kses_post( $data['summary'] ) );
|
||||
}
|
||||
|
||||
if ( isset( $data['name'] ) ) {
|
||||
$event->set_name( \sanitize_text_field( $data['name'] ) );
|
||||
}
|
||||
|
||||
if ( isset( $data['startTime'] ) ) {
|
||||
$event->set_start_time( \sanitize_text_field( $data['startTime'] ) );
|
||||
}
|
||||
|
||||
if ( isset( $data['endTime'] ) ) {
|
||||
$event->set_end_time( \sanitize_text_field( $data['endTime'] ) );
|
||||
}
|
||||
|
||||
if ( isset( $data['published'] ) ) {
|
||||
$event->set_published( \sanitize_text_field( $data['published'] ) );
|
||||
}
|
||||
|
||||
if ( isset( $data['id'] ) ) {
|
||||
$event->set_id( \sanitize_url( $data['id'] ) );
|
||||
}
|
||||
|
||||
if ( isset( $data['url'] ) ) {
|
||||
$event->set_url( \sanitize_url( $data['url'] ) );
|
||||
}
|
||||
|
||||
if ( isset( $data['attributedTo'] ) ) {
|
||||
$event->set_attributed_to( self::sanitize_attributed_to( $data['attributedTo'] ) );
|
||||
}
|
||||
|
||||
if ( isset( $data['location'] ) ) {
|
||||
$event->set_location( self::sanitize_place_object_from_array( $data['location'] ) );
|
||||
}
|
||||
|
||||
if ( isset( $data['attachment'] ) ) {
|
||||
$event->set_attachment( self::sanitize_attachment( $data['attachment'] ) );
|
||||
}
|
||||
|
||||
if ( isset( $data['tag'] ) ) {
|
||||
$event->set_tag( self::sanitize_attachment( $data['tag'] ) );
|
||||
}
|
||||
|
||||
return $event;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize attributedTo.
|
||||
*
|
||||
* Currently only multiple attributedTo's are not supported.
|
||||
*
|
||||
* @param mixed $data The object array.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function sanitize_attributed_to( $data ): string {
|
||||
if ( is_array( $data ) && self::array_is_list( $data ) ) {
|
||||
$data = reset( $data );
|
||||
}
|
||||
|
||||
return object_to_uri( $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize attachments.
|
||||
*
|
||||
* @param mixed $data The object array.
|
||||
*
|
||||
* @return ?array
|
||||
*/
|
||||
private static function sanitize_attachment( $data ): ?array {
|
||||
if ( ! is_array( $data ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( ! self::array_is_list( $data ) ) {
|
||||
$data = array( $data );
|
||||
}
|
||||
|
||||
$attachment = array();
|
||||
|
||||
foreach ( $data as $item ) {
|
||||
$sanitized_item = array();
|
||||
|
||||
// Straightforward sanitization of all attributes we possible make use of.
|
||||
if ( isset( $item['name'] ) ) {
|
||||
$sanitized_item['name'] = \sanitize_text_field( $item['name'] );
|
||||
}
|
||||
if ( isset( $item['url'] ) ) {
|
||||
$sanitized_item['url'] = \sanitize_url( $item['url'] );
|
||||
}
|
||||
if ( isset( $item['id'] ) ) {
|
||||
$sanitized_item['id'] = \sanitize_url( $item['id'] );
|
||||
}
|
||||
if ( isset( $item['type'] ) ) {
|
||||
$sanitized_item['type'] = \sanitize_text_field( $item['type'] );
|
||||
}
|
||||
if ( isset( $item['href'] ) ) {
|
||||
$sanitized_item['href'] = \sanitize_text_field( $item['href'] );
|
||||
}
|
||||
|
||||
if ( isset( $sanitized_item['url'] ) || isset( $sanitized_item['href'] ) || isset( $sanitized_item['name'] ) ) {
|
||||
$attachment[] = $sanitized_item;
|
||||
}
|
||||
}
|
||||
|
||||
return $attachment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fallback for PHP version prior to 8.1 for array_is_list.
|
||||
*
|
||||
* @param array $arr The array to check.
|
||||
* @return bool
|
||||
*/
|
||||
private static function array_is_list( $arr ) {
|
||||
if ( ! function_exists( 'array_is_list' ) ) {
|
||||
if ( array() === $arr ) {
|
||||
return true;
|
||||
}
|
||||
return array_keys( $arr ) === range( 0, count( $arr ) - 1 );
|
||||
}
|
||||
return array_is_list( $arr );
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert input array to an Location.
|
||||
*
|
||||
* @param mixed $data The object array.
|
||||
*
|
||||
* @return ?Place An Object built from the input array or null.
|
||||
*/
|
||||
private static function sanitize_place_object_from_array( $data ): ?Place {
|
||||
if ( ! is_array( $data ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// If the array is a list, work with the first item.
|
||||
if ( array_key_exists( 0, $data ) ) {
|
||||
$data = $data[0];
|
||||
}
|
||||
|
||||
$place = new Place();
|
||||
|
||||
if ( isset( $data['name'] ) ) {
|
||||
$place->set_name( \sanitize_text_field( $data['name'] ) );
|
||||
}
|
||||
|
||||
if ( isset( $data['id'] ) ) {
|
||||
$place->set_id( \sanitize_url( $data['id'] ) );
|
||||
}
|
||||
|
||||
if ( isset( $data['url'] ) ) {
|
||||
$place->set_url( \sanitize_url( $data['url'] ) );
|
||||
}
|
||||
|
||||
if ( isset( $data['address'] ) ) {
|
||||
if ( is_string( $data['address'] ) ) {
|
||||
$place->set_address( \sanitize_text_field( $data['address'] ) );
|
||||
}
|
||||
if ( is_array( $data['address'] ) && isset( $data['address']['type'] ) && 'PostalAddress' === $data['address']['type'] ) {
|
||||
$address = array();
|
||||
if ( isset( $data['address']['streetAddress'] ) ) {
|
||||
$address['streetAddress'] = \sanitize_text_field( $data['address']['streetAddress'] );
|
||||
}
|
||||
if ( isset( $data['address']['postalCode'] ) ) {
|
||||
$address['postalCode'] = \sanitize_text_field( $data['address']['postalCode'] );
|
||||
}
|
||||
if ( isset( $data['address']['addressLocality'] ) ) {
|
||||
$address['addressLocality'] = \sanitize_text_field( $data['address']['addressLocality'] );
|
||||
}
|
||||
if ( isset( $data['address']['addressState'] ) ) {
|
||||
$address['addressState'] = \sanitize_text_field( $data['address']['addressState'] );
|
||||
}
|
||||
if ( isset( $data['address']['addressCountry'] ) ) {
|
||||
$address['addressCountry'] = \sanitize_text_field( $data['address']['addressCountry'] );
|
||||
}
|
||||
if ( isset( $data['address']['url'] ) ) {
|
||||
$address['url'] = \sanitize_url( $data['address']['url'] );
|
||||
}
|
||||
$place->set_address( $address );
|
||||
}
|
||||
}
|
||||
|
||||
return $place;
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
/**
|
||||
* Extending the Tribe Events API to allow setting of the guid.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Transmogrifier\Helper;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
/**
|
||||
* Extending the Tribe Events API to allow setting of the guid.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class The_Events_Calendar_Event_Repository extends \Tribe__Events__Repositories__Event {
|
||||
/**
|
||||
* Override diff: allow setting of guid.
|
||||
*
|
||||
* @var array An array of keys that cannot be updated on this repository.
|
||||
*/
|
||||
protected static $blocked_keys = array(
|
||||
'ID',
|
||||
'post_type',
|
||||
'comment_count',
|
||||
);
|
||||
|
||||
/**
|
||||
* Whether the current key can be updated by this repository or not.
|
||||
*
|
||||
* @since 4.7.19
|
||||
*
|
||||
* @param string $key The key.
|
||||
* @return bool
|
||||
*/
|
||||
protected function can_be_updated( $key ): bool {
|
||||
return ! in_array( $key, self::$blocked_keys, true );
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
/**
|
||||
* Extending the Organizer Venue API to allow setting of the guid.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Transmogrifier\Helper;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
/**
|
||||
* Extending the Organizer Venue API to allow setting of the guid.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class The_Events_Calendar_Organizer_Repository extends \Tribe__Events__Repositories__Organizer {
|
||||
/**
|
||||
* Override diff: allow setting of guid.
|
||||
*
|
||||
* @var array An array of keys that cannot be updated on this repository.
|
||||
*/
|
||||
protected static $blocked_keys = array(
|
||||
'ID',
|
||||
'post_type',
|
||||
'comment_count',
|
||||
);
|
||||
|
||||
/**
|
||||
* Whether the current key can be updated by this repository or not.
|
||||
*
|
||||
* @since 4.7.19
|
||||
*
|
||||
* @param string $key The key.
|
||||
* @return bool
|
||||
*/
|
||||
protected function can_be_updated( $key ): bool {
|
||||
return ! in_array( $key, self::$blocked_keys, true );
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
/**
|
||||
* Extending the Tribe Venue API to allow setting of the guid.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\ActivityPub\Transmogrifier\Helper;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
/**
|
||||
* Extending the Tribe Venue API to allow setting of the guid.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class The_Events_Calendar_Venue_Repository extends \Tribe__Events__Repositories__Venue {
|
||||
/**
|
||||
* Override diff: allow setting of guid.
|
||||
*
|
||||
* @var array An array of keys that cannot be updated on this repository.
|
||||
*/
|
||||
protected static $blocked_keys = array(
|
||||
'ID',
|
||||
'post_type',
|
||||
'comment_count',
|
||||
);
|
||||
|
||||
/**
|
||||
* Whether the current key can be updated by this repository or not.
|
||||
*
|
||||
* @since 4.7.19
|
||||
*
|
||||
* @param string $key The key.
|
||||
* @return bool
|
||||
*/
|
||||
protected function can_be_updated( $key ): bool {
|
||||
return ! in_array( $key, self::$blocked_keys, true );
|
||||
}
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
<?php
|
||||
/**
|
||||
* Class responsible for Event Plugin related admin notices.
|
||||
*
|
||||
* Notices for guiding to proper configuration of ActivityPub with event plugins.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\Admin;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Event_Bridge_For_ActivityPub\Integrations\Event_Plugin_Integration;
|
||||
|
||||
/**
|
||||
* Class responsible for Event Plugin related admin notices.
|
||||
*
|
||||
* Notices for guiding to proper configuration of ActivityPub with event plugins.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class Event_Plugin_Admin_Notices {
|
||||
/**
|
||||
* Information about the event plugin.
|
||||
*
|
||||
* @var Event_Plugin_Integration
|
||||
*/
|
||||
protected $event_plugin;
|
||||
|
||||
/**
|
||||
* Adds admin notices to an active supported event plugin.
|
||||
*
|
||||
* @param Event_Plugin_Integration $event_plugin Class that has implements functions to handle a certain supported activate event plugin.
|
||||
*/
|
||||
public function __construct( $event_plugin ) {
|
||||
$this->event_plugin = $event_plugin;
|
||||
if ( $this->event_post_type_is_not_activitypub_enabled() ) {
|
||||
add_action( 'admin_notices', array( $this, 'admin_notice_activitypub_not_enabled_for_post_type' ), 10, 1 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if ActivityPub is enabled for the custom post type of the event plugin.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function event_post_type_is_not_activitypub_enabled(): bool {
|
||||
return ! in_array( $this->event_plugin::get_post_type(), get_option( 'activitypub_support_post_types', array() ), true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Display the admin notices for the plugins.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function admin_notice_activitypub_not_enabled_for_post_type(): void {
|
||||
if ( $this->event_plugin::is_plugin_page() ) {
|
||||
$this->do_admin_notice_post_type_not_activitypub_enabled();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Print admin notice that the current post type is not enabled in the ActivityPub plugin.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function do_admin_notice_post_type_not_activitypub_enabled(): void {
|
||||
$all_plugins = get_plugins();
|
||||
$event_plugin_file = $this->event_plugin::get_relative_plugin_file();
|
||||
if ( isset( $all_plugins[ $event_plugin_file ]['Name'] ) ) {
|
||||
$event_plugin_name = $all_plugins[ $event_plugin_file ]['Name'];
|
||||
} elseif ( isset( get_mu_plugins()[ $event_plugin_file ]['Name'] ) ) {
|
||||
$event_plugin_name = get_mu_plugins()[ $event_plugin_file ]['Name'];
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
$activitypub_plugin_data = get_plugin_data( ACTIVITYPUB_PLUGIN_FILE );
|
||||
$notice = sprintf(
|
||||
/* translators: 1: the name of the event plugin a admin notice is shown. 2: The name of the ActivityPub plugin. */
|
||||
_x(
|
||||
'You have installed the <i>%1$s</i> plugin, but the event post type of the plugin <i>%2$s</i> is <b>not enabled</b> in the <a href="%3$s">%1$s settings</a>.',
|
||||
'admin notice',
|
||||
'event-bridge-for-activitypub'
|
||||
),
|
||||
esc_html( $activitypub_plugin_data['Name'] ),
|
||||
esc_html( $event_plugin_name ),
|
||||
admin_url( 'options-general.php?page=activitypub&tab=settings' )
|
||||
);
|
||||
$allowed_html = array(
|
||||
'a' => array(
|
||||
'href' => true,
|
||||
'title' => true,
|
||||
),
|
||||
'b' => array(),
|
||||
'i' => array(),
|
||||
);
|
||||
echo '<div class="notice notice-warning is-dismissible"><p>' . \wp_kses( $notice, $allowed_html ) . '</p></div>';
|
||||
}
|
||||
}
|
@ -0,0 +1,152 @@
|
||||
<?php
|
||||
/**
|
||||
* Class responsible for general admin notices.
|
||||
*
|
||||
* Notices for guiding to proper configuration of this plugin.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\Admin;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
/**
|
||||
* Class responsible for general admin notices.
|
||||
*
|
||||
* Notices for guiding to proper configuration of this plugin.
|
||||
* - ActivityPub plugin not installed and activated
|
||||
* - No supported Event Plugin installed and activated
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class General_Admin_Notices {
|
||||
/**
|
||||
* URL of the ActivityPub plugin. Needed when the ActivityPub plugin is not installed.
|
||||
*/
|
||||
const ACTIVITYPUB_PLUGIN_URL = 'https://wordpress.org/plugins/activitypub';
|
||||
|
||||
const EVENT_BRIDGE_FOR_ACTIVITYPUB_SUPPORTED_EVENT_PLUGINS_URL = 'https://wordpress.org/plugins/event-bridge-for-activitypub/#installation';
|
||||
|
||||
/**
|
||||
* Allowed HTML for admin notices.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
const ALLOWED_HTML = array(
|
||||
'a' => array(
|
||||
'href' => true,
|
||||
'title' => true,
|
||||
'target' => true,
|
||||
),
|
||||
'br',
|
||||
'i',
|
||||
);
|
||||
|
||||
/**
|
||||
* Admin notice when the ActivityPub plugin is not enabled.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_admin_notice_activitypub_plugin_not_enabled(): string {
|
||||
return sprintf(
|
||||
/* translators: 1: An URL that points to the ActivityPub plugin. */
|
||||
_x(
|
||||
'For the Event Bridge for ActivityPub to work, you will need to install and activate the <a href="%1$s">ActivityPub</a> plugin.',
|
||||
'admin notice',
|
||||
'event-bridge-for-activitypub'
|
||||
),
|
||||
esc_html( self::ACTIVITYPUB_PLUGIN_URL )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin notice when the ActivityPub plugin version is too old.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_admin_notice_activitypub_plugin_version_too_old(): string {
|
||||
return sprintf(
|
||||
/* translators: 1: The name of the ActivityPub plugin. 2: The minimum required version number of the ActivityPub plugin. */
|
||||
_x(
|
||||
'Please upgrade your <a href="%1$s">ActivityPub</a> plugin. At least version %2$s is required for the Event Bridge for ActivityPub to work.',
|
||||
'admin notice',
|
||||
'event-bridge-for-activitypub'
|
||||
),
|
||||
esc_html( self::ACTIVITYPUB_PLUGIN_URL ),
|
||||
esc_html( EVENT_BRIDGE_FOR_ACTIVITYPUB_ACTIVITYPUB_PLUGIN_MIN_VERSION )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Warning that no supported event plugin can be found.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_admin_notice_no_supported_event_plugin_active(): string {
|
||||
return sprintf(
|
||||
/* translators: 1: An URL to the list of supported event plugins. */
|
||||
_x(
|
||||
'The Plugin <i>Event Bridge for ActivityPub</i> is of no use, because you do not have installed and activated a supported Event Plugin.
|
||||
<br> For a list of supported Event Plugins see <a href="%1$s" target="_blank">here</a>.',
|
||||
'admin notice',
|
||||
'event-bridge-for-activitypub'
|
||||
),
|
||||
esc_url( self::EVENT_BRIDGE_FOR_ACTIVITYPUB_SUPPORTED_EVENT_PLUGINS_URL )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Warning to fix status issues first.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_admin_notice_status_not_ok(): string {
|
||||
return sprintf(
|
||||
/* translators: 1: An URL to the list of supported event plugins. */
|
||||
_x(
|
||||
'The Plugin <i>Event Bridge for ActivityPub</i> is of no use, because you do not have installed and activated a supported Event Plugin.
|
||||
<br> For a list of supported Event Plugins see <a href="%1$s">here</a>.',
|
||||
'admin notice',
|
||||
'event-bridge-for-activitypub'
|
||||
),
|
||||
esc_html( self::EVENT_BRIDGE_FOR_ACTIVITYPUB_SUPPORTED_EVENT_PLUGINS_URL )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Warning if the plugin is Active and the ActivityPub plugin is not.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function activitypub_plugin_not_enabled(): void {
|
||||
$notice = self::get_admin_notice_activitypub_plugin_not_enabled();
|
||||
// @phpstan-ignore-next-line
|
||||
echo '<div class="notice notice-warning"><p>' . \wp_kses( $notice, self::ALLOWED_HTML ) . '</p></div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Warning if the plugin is Active and the ActivityPub plugins version is too old.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function activitypub_plugin_version_too_old(): void {
|
||||
$notice = self::get_admin_notice_activitypub_plugin_version_too_old();
|
||||
// @phpstan-ignore-next-line
|
||||
echo '<div class="notice notice-warning"><p>' . \wp_kses( $notice, self::ALLOWED_HTML ) . '</p></div>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Warning when no supported Even Plugin is installed and active.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function no_supported_event_plugin_active(): void {
|
||||
$notice = self::get_admin_notice_no_supported_event_plugin_active();
|
||||
// @phpstan-ignore-next-line
|
||||
echo '<div class="notice notice-warning"><p>' . \wp_kses( $notice, self::ALLOWED_HTML ) . '</p></div>';
|
||||
}
|
||||
}
|
@ -0,0 +1,211 @@
|
||||
<?php
|
||||
/**
|
||||
* Health_Check class.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\Admin;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Activitypub\Transformer\Factory as Transformer_Factory;
|
||||
use Event_Bridge_For_ActivityPub\Integrations\Event_Plugin_Integration;
|
||||
use Event_Bridge_For_ActivityPub\Setup;
|
||||
use WP_Post;
|
||||
use WP_Query;
|
||||
|
||||
/**
|
||||
* ActivityPub Health_Check Class.
|
||||
*/
|
||||
class Health_Check {
|
||||
/**
|
||||
* Initialize health checks.
|
||||
*/
|
||||
public static function init() {
|
||||
\add_filter( 'site_status_tests', array( self::class, 'add_tests' ) );
|
||||
\add_filter( 'debug_information', array( self::class, 'add_debug_information' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add tests to the Site Health Check.
|
||||
*
|
||||
* @param array $tests The test array.
|
||||
*
|
||||
* @return array The filtered test array.
|
||||
*/
|
||||
public static function add_tests( $tests ): array {
|
||||
$tests['direct']['event_bridge_for_activitypub_test'] = array(
|
||||
'label' => __( 'ActivityPub Event Transformer Test', 'event-bridge-for-activitypub' ),
|
||||
'test' => array( self::class, 'test_event_transformation' ),
|
||||
);
|
||||
|
||||
return $tests;
|
||||
}
|
||||
|
||||
/**
|
||||
* The the transformation of the most recent event posts.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function test_event_transformation(): array {
|
||||
$result = array(
|
||||
'label' => \__( 'Transformation of Events to a valid ActivityStreams representation.', 'event-bridge-for-activitypub' ),
|
||||
'status' => 'good',
|
||||
'badge' => array(
|
||||
'label' => \__( 'Event Bridge for ActivityPub', 'event-bridge-for-activitypub' ),
|
||||
'color' => 'green',
|
||||
),
|
||||
'description' => \sprintf(
|
||||
'<p>%s</p>',
|
||||
\__( 'The transformation to ActivityPub of your most recent events was successful.', 'event-bridge-for-activitypub' )
|
||||
),
|
||||
'actions' => '',
|
||||
'test' => 'test_event_transformation',
|
||||
);
|
||||
|
||||
$check = self::transform_most_recent_event_posts();
|
||||
|
||||
if ( true === $check ) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$result['status'] = 'critical';
|
||||
$result['label'] = \__( 'One or more of your most recent events failed to transform to ActivityPub', 'event-bridge-for-activitypub' );
|
||||
$result['badge']['color'] = 'red';
|
||||
$result['description'] = \sprintf(
|
||||
'<p>%s</p>',
|
||||
\__( 'The transformation to ActivityPub of your most recent events was not successful.', 'event-bridge-for-activitypub' )
|
||||
);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if right transformer gets applied.
|
||||
*
|
||||
* @param Event_Plugin_Integration $event_plugin The event plugin definition.
|
||||
*
|
||||
* @return bool True if the check passed.
|
||||
*/
|
||||
public static function test_if_event_transformer_is_used( $event_plugin ): bool {
|
||||
if ( ! Setup::get_instance()->is_activitypub_plugin_active() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get a (random) event post.
|
||||
$event_posts = self::get_most_recent_event_posts( $event_plugin->get_post_type(), 1 );
|
||||
|
||||
// If no post is found, we can not do this test.
|
||||
if ( isset( $event_posts[0] ) || empty( $event_posts ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Call the transformer Factory.
|
||||
$transformer = Transformer_Factory::get_transformer( \get_post( $event_posts[0] ) );
|
||||
// Check that we got the right transformer.
|
||||
$desired_transformer_class = $event_plugin::get_activitypub_event_transformer( $event_posts[0] );
|
||||
if ( $transformer instanceof $desired_transformer_class ) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the most recently published event posts of a certain event post type.
|
||||
*
|
||||
* @param ?string $event_post_type The post type of the events.
|
||||
* @param ?int $number_of_posts The maximum number of events to return.
|
||||
*
|
||||
* @return \WP_Post[] Array of event posts, or false if none are found.
|
||||
*/
|
||||
public static function get_most_recent_event_posts( $event_post_type = null, $number_of_posts = 5 ): array {
|
||||
if ( ! Setup::get_instance()->is_activitypub_plugin_active() ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
if ( ! $event_post_type ) {
|
||||
$active_event_plugins = Setup::get_instance()->get_active_event_plugins();
|
||||
$active_event_plugin = reset( $active_event_plugins );
|
||||
if ( ! $active_event_plugin ) {
|
||||
return array();
|
||||
}
|
||||
$event_post_type = $active_event_plugin->get_post_type();
|
||||
}
|
||||
|
||||
$args = array(
|
||||
'numberposts' => $number_of_posts,
|
||||
'category' => 0,
|
||||
'orderby' => 'date',
|
||||
'order' => 'DESC',
|
||||
'include' => array(),
|
||||
'exclude' => array(),
|
||||
'meta_query' => array(
|
||||
'relation' => 'OR',
|
||||
array(
|
||||
'key' => '_event_bridge_for_activitypub_event_source',
|
||||
'compare' => 'NOT EXISTS',
|
||||
),
|
||||
array(
|
||||
'key' => '_event_bridge_for_activitypub_event_source',
|
||||
'value' => '',
|
||||
'compare' => '=',
|
||||
),
|
||||
),
|
||||
'post_type' => $event_post_type,
|
||||
'suppress_filters' => true,
|
||||
);
|
||||
|
||||
$query = new WP_Query();
|
||||
return $query->query( $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform the most recent event posts.
|
||||
*/
|
||||
public static function transform_most_recent_event_posts(): bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves information like name and version from active event plugins.
|
||||
*/
|
||||
private static function get_info_about_active_event_plugins(): array {
|
||||
$active_event_plugins = Setup::get_instance()->get_active_event_plugins();
|
||||
$info = array();
|
||||
foreach ( $active_event_plugins as $active_event_plugin ) {
|
||||
$event_plugin_file = $active_event_plugin->get_relative_plugin_file();
|
||||
$event_plugin_data = \get_plugin_data( $event_plugin_file );
|
||||
|
||||
$info[] = array(
|
||||
'event_plugin_name' => $event_plugin_data['Name'],
|
||||
'event_plugin_version' => $event_plugin_data['Version'],
|
||||
'event_plugin_file' => $event_plugin_file,
|
||||
);
|
||||
}
|
||||
return $info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Static function for generating site debug data when required.
|
||||
*
|
||||
* @param array $info The debug information to be added to the core information page.
|
||||
* @return array The extended information.
|
||||
*/
|
||||
public static function add_debug_information( $info ): array {
|
||||
$info['event_bridge_for_activitypub'] = array(
|
||||
'label' => __( 'Event Bridge for ActivityPub', 'event-bridge-for-activitypub' ),
|
||||
'fields' => array(
|
||||
'plugin_version' => array(
|
||||
'label' => __( 'Plugin Version', 'event-bridge-for-activitypub' ),
|
||||
'value' => EVENT_BRIDGE_FOR_ACTIVITYPUB_PLUGIN_VERSION,
|
||||
'private' => true,
|
||||
),
|
||||
'active_event_plugins' => self::get_info_about_active_event_plugins(),
|
||||
),
|
||||
);
|
||||
|
||||
return $info;
|
||||
}
|
||||
}
|
@ -0,0 +1,280 @@
|
||||
<?php
|
||||
/**
|
||||
* General settings class.
|
||||
*
|
||||
* This file contains the General class definition, which handles the "General" settings
|
||||
* page for the Event Bridge for ActivityPub Plugin, providing options for configuring various general settings.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\Admin;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Activitypub\Webfinger;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Model\Event_Source;
|
||||
use Event_Bridge_For_ActivityPub\Event_Sources;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Collection\Event_Sources as Event_Source_Collection;
|
||||
use Event_Bridge_For_ActivityPub\Integrations\Event_Plugin_Integration;
|
||||
use Event_Bridge_For_ActivityPub\Integrations\Feature_Event_Sources;
|
||||
use Event_Bridge_For_ActivityPub\Setup;
|
||||
|
||||
/**
|
||||
* Class responsible for the Event Bridge for ActivityPub related Settings.
|
||||
*
|
||||
* Class which handles the "General" settings page for the Event Bridge for ActivityPub Plugin,
|
||||
* providing options for configuring various general settings.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class Settings_Page {
|
||||
const STATIC = 'Event_Bridge_For_ActivityPub\Admin\Settings_Page';
|
||||
|
||||
const SETTINGS_SLUG = 'event-bridge-for-activitypub';
|
||||
|
||||
/**
|
||||
* Init settings pages.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function init() {
|
||||
\add_filter( 'activitypub_admin_settings_tabs', array( self::class, 'add_settings_tab' ) );
|
||||
\add_action(
|
||||
'admin_init',
|
||||
array( self::class, 'maybe_add_event_source' ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a custom tab to the ActivityPub settings.
|
||||
*
|
||||
* @param array $tabs The existing tabs array.
|
||||
* @return array The modified tabs array.
|
||||
*/
|
||||
public static function add_settings_tab( $tabs ): array {
|
||||
$tabs['event-bridge-for-activitypub'] = array(
|
||||
'label' => __( 'Event Bridge', 'event-bridge-for-activitypub' ),
|
||||
'template' => EVENT_BRIDGE_FOR_ACTIVITYPUB_PLUGIN_DIR . 'templates/settings/tab.php',
|
||||
);
|
||||
|
||||
return $tabs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the current request wants to add an event source (ActivityPub follow) and passed on to actual handler.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function maybe_add_event_source() {
|
||||
if ( ! isset( $_POST['event_bridge_for_activitypub_add_event_source'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check and verify request and check capabilities.
|
||||
if ( ! \wp_verify_nonce( sanitize_key( $_REQUEST['_wpnonce'] ), 'event-bridge-for-activitypub_add-event-source-options' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! \current_user_can( 'manage_options' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$event_source = \sanitize_text_field( $_POST['event_bridge_for_activitypub_add_event_source'] );
|
||||
|
||||
$actor_url = false;
|
||||
$url = \wp_parse_url( $event_source );
|
||||
|
||||
$error_message = \esc_html__( 'Failed to add Event Source', 'event-bridge-for-activitypub' );
|
||||
|
||||
// Check if URL is a Collection or a single Actor.
|
||||
$maybe_collection = \wp_safe_remote_get( $event_source );
|
||||
|
||||
if ( ! \is_wp_error( $maybe_collection ) ) {
|
||||
$maybe_collection = \json_decode( \wp_remote_retrieve_body( $maybe_collection ), true );
|
||||
}
|
||||
|
||||
$event_sources = array();
|
||||
|
||||
if ( isset( $maybe_collection['type'] ) && in_array( $maybe_collection['type'], array( 'Collection', 'OrderedCollection' ), true ) ) {
|
||||
// Return only the IDs of the items in the collection.
|
||||
$event_sources = \wp_list_pluck( $maybe_collection['items'], 'id' );
|
||||
} else {
|
||||
$event_sources[] = $event_source;
|
||||
}
|
||||
|
||||
// Iterate over all event sources and add them to the collection.
|
||||
foreach ( $event_sources as $event_source ) {
|
||||
$url = \wp_parse_url( $event_source );
|
||||
|
||||
if ( isset( $url['path'], $url['host'], $url['scheme'] ) ) {
|
||||
$actor_url = \sanitize_url( $event_source );
|
||||
} elseif ( preg_match( '/^@?' . Event_Source::ACTIVITYPUB_USER_HANDLE_REGEXP . '$/i', $event_source ) ) {
|
||||
$actor_url = Webfinger::resolve( $event_source );
|
||||
if ( \is_wp_error( $actor_url ) ) {
|
||||
\add_settings_error(
|
||||
'event-bridge-for-activitypub_add-event-source',
|
||||
'event_bridge_for_activitypub_cannot_follow_actor',
|
||||
$error_message . ': ' . esc_html__( 'Cannot find an ActivityPub actor for this user handle via Webfinger.', 'event-bridge-for-activitypub' ),
|
||||
'error'
|
||||
);
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
if ( ! isset( $url['path'] ) && isset( $url['host'] ) ) {
|
||||
$actor_url = Event_Sources::get_application_actor( $url['host'] );
|
||||
} elseif ( self::is_domain( $event_source ) ) {
|
||||
$actor_url = Event_Sources::get_application_actor( $event_source );
|
||||
}
|
||||
if ( ! $actor_url ) {
|
||||
\add_settings_error(
|
||||
'event-bridge-for-activitypub_add-event-source',
|
||||
'event_bridge_for_activitypub_cannot_follow_actor',
|
||||
$error_message . ': ' . \esc_html__( 'Unable to identify the ActivityPub relay actor to follow for this domain.', 'event-bridge-for-activitypub' ),
|
||||
'error'
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $actor_url ) {
|
||||
\add_settings_error(
|
||||
'event-bridge-for-activitypub_add-event-source',
|
||||
'event_bridge_for_activitypub_cannot_follow_actor',
|
||||
$error_message . ': ' . \esc_html__( 'ActivityPub actor does not exist.', 'event-bridge-for-activitypub' ),
|
||||
'error'
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Don't proceed if on the same host!
|
||||
if ( \wp_parse_url( \home_url(), PHP_URL_HOST ) === \wp_parse_url( $actor_url, PHP_URL_HOST ) ) {
|
||||
\add_settings_error(
|
||||
'event-bridge-for-activitypub_add-event-source',
|
||||
'event_bridge_for_activitypub_cannot_follow_actor',
|
||||
$error_message . ': ' . \esc_html__( 'Cannot follow own actor on own domain.', 'event-bridge-for-activitypub' ),
|
||||
'error'
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
Event_Source_Collection::add_event_source( $actor_url );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a string is a valid domain name.
|
||||
*
|
||||
* @param string $domain The input string which might be a domain.
|
||||
* @return bool
|
||||
*/
|
||||
private static function is_domain( $domain ): bool {
|
||||
$pattern = '/^(?!\-)(?:(?:[a-zA-Z\d](?:[a-zA-Z\d\-]{0,61}[a-zA-Z\d])?)\.)+(?!\d+$)[a-zA-Z\d]{2,63}$/';
|
||||
return 1 === preg_match( $pattern, $domain );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds Link to the settings page in the plugin page.
|
||||
* It's called via apply_filter('plugin_action_links_' . PLUGIN_NAME).
|
||||
*
|
||||
* @param array $links Already added links.
|
||||
*
|
||||
* @return array Original links but with link to setting page added.
|
||||
*/
|
||||
public static function settings_link( $links ): array {
|
||||
$links[] = \sprintf(
|
||||
'<a href="%1s">%2s</a>',
|
||||
\add_query_arg( 'tab', 'event-bridge-for-activitypub', \menu_page_url( 'activitypub', false ) ),
|
||||
\__( 'Settings', 'event-bridge-for-activitypub' )
|
||||
);
|
||||
|
||||
return $links;
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive the event categories (terms) used by the event plugin.
|
||||
*
|
||||
* @param Event_Plugin_Integration $event_plugin Contains info about a certain event plugin.
|
||||
*
|
||||
* @return array An array of Terms.
|
||||
*/
|
||||
private static function get_event_terms( $event_plugin ): array {
|
||||
$taxonomy = $event_plugin::get_event_category_taxonomy();
|
||||
if ( $taxonomy ) {
|
||||
$event_terms = get_terms(
|
||||
array(
|
||||
'taxonomy' => $taxonomy,
|
||||
'hide_empty' => true,
|
||||
)
|
||||
);
|
||||
return ! is_wp_error( $event_terms ) ? $event_terms : array();
|
||||
} else {
|
||||
return array();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Preparing the data and loading the template for the settings page.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function do_settings_page(): void {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( empty( $_GET['subpage'] ) ) {
|
||||
$tab = 'welcome';
|
||||
} else {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$tab = \sanitize_key( $_GET['subpage'] );
|
||||
}
|
||||
|
||||
// Fallback to always re-scan active event plugins, when user visits admin area of this plugin.
|
||||
$plugin_setup = Setup::get_instance();
|
||||
$plugin_setup->redetect_active_event_plugins();
|
||||
$event_plugins = $plugin_setup->get_active_event_plugins();
|
||||
|
||||
switch ( $tab ) {
|
||||
case 'settings':
|
||||
$event_terms = array();
|
||||
|
||||
foreach ( $event_plugins as $event_plugin_integration ) {
|
||||
$event_terms = array_merge( $event_terms, self::get_event_terms( $event_plugin_integration ) );
|
||||
}
|
||||
|
||||
$args = array(
|
||||
'slug' => self::SETTINGS_SLUG,
|
||||
'event_terms' => $event_terms,
|
||||
);
|
||||
|
||||
\load_template( EVENT_BRIDGE_FOR_ACTIVITYPUB_PLUGIN_DIR . 'templates/settings/subpages/settings.php', true, $args );
|
||||
break;
|
||||
case 'event-sources':
|
||||
$supports_event_sources = array();
|
||||
|
||||
foreach ( $event_plugins as $event_plugin_integration ) {
|
||||
if ( is_a( $event_plugin_integration, Feature_Event_Sources::class ) ) {
|
||||
$class_name = get_class( $event_plugin_integration );
|
||||
$supports_event_sources[ $class_name ] = $event_plugin_integration::get_plugin_name();
|
||||
}
|
||||
}
|
||||
|
||||
$args = array(
|
||||
'supports_event_sources' => $supports_event_sources,
|
||||
);
|
||||
|
||||
\wp_enqueue_script( 'thickbox' );
|
||||
\wp_enqueue_style( 'thickbox' );
|
||||
\load_template( EVENT_BRIDGE_FOR_ACTIVITYPUB_PLUGIN_DIR . 'templates/settings/subpages/event-sources.php', true, $args );
|
||||
break;
|
||||
case 'welcome':
|
||||
default:
|
||||
\wp_enqueue_script( 'plugin-install' );
|
||||
\add_thickbox();
|
||||
\wp_enqueue_script( 'updates' );
|
||||
|
||||
\load_template( EVENT_BRIDGE_FOR_ACTIVITYPUB_PLUGIN_DIR . 'templates/settings/subpages/welcome.php', true );
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
<?php
|
||||
/**
|
||||
* Class responsible for User Interface additions in the Admin UI.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\Admin;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Model\Event_Source;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Collection\Event_Sources as Event_Sources_Collection;
|
||||
use Event_Bridge_For_ActivityPub\Event_Sources;
|
||||
|
||||
/**
|
||||
* Class responsible for Event Plugin related admin notices.
|
||||
*
|
||||
* Notices for guiding to proper configuration of ActivityPub with event plugins.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class User_Interface {
|
||||
/**
|
||||
* Init.
|
||||
*/
|
||||
public static function init() {
|
||||
\add_filter( 'page_row_actions', array( self::class, 'row_actions' ), 10, 2 );
|
||||
\add_filter( 'post_row_actions', array( self::class, 'row_actions' ), 10, 2 );
|
||||
\add_filter( 'map_meta_cap', array( self::class, 'disable_editing_for_external_events' ), 10, 4 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an column that shows the origin of an external event.
|
||||
*
|
||||
* @param array $columns The current columns.
|
||||
* @return array
|
||||
*/
|
||||
public static function add_origin_column( $columns ) {
|
||||
// Add a new column after the title column.
|
||||
$columns['activitypub_origin'] = __( 'ActivityPub origin', 'event-bridge-for-activitypub' );
|
||||
return $columns;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a "⁂ Preview" link to the row actions.
|
||||
*
|
||||
* @param array $actions The existing actions.
|
||||
* @param \WP_Post $post The post object.
|
||||
*
|
||||
* @return array The modified actions.
|
||||
*/
|
||||
public static function row_actions( $actions, $post ): array {
|
||||
// check if the post is enabled for ActivityPub.
|
||||
if ( ! Event_Sources::is_cached_external_post( $post ) ) {
|
||||
return $actions;
|
||||
}
|
||||
|
||||
$url = $post->guid;
|
||||
|
||||
$parent = get_post_parent();
|
||||
|
||||
if ( $parent && Event_Sources_Collection::POST_TYPE === $parent->post_type ) {
|
||||
$url = \get_post_meta( $parent->ID, '_activitypub_actor_id', true );
|
||||
}
|
||||
|
||||
$actions['view_origin'] = sprintf(
|
||||
'<a href="%s" target="_blank">⁂ %s</a>',
|
||||
\esc_url( $url ),
|
||||
\esc_html__( 'Open original page', 'event-bridge-for-activitypub' )
|
||||
);
|
||||
|
||||
return $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modify the user capabilities so that nobody can edit external events.
|
||||
*
|
||||
* @param array $caps Concerned user's capabilities.
|
||||
* @param mixed $cap Required primitive capabilities for the requested capability.
|
||||
* @param array $user_id The WordPress user ID.
|
||||
* @param array $args Additional args.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function disable_editing_for_external_events( $caps, $cap, $user_id, $args ) {
|
||||
if ( 'edit_post' === $cap && isset( $args[0] ) ) {
|
||||
$post_id = $args[0];
|
||||
$post = get_post( $post_id );
|
||||
if ( $post && Event_Sources::is_cached_external_post( $post ) ) {
|
||||
// Deny editing by returning 'do_not_allow'.
|
||||
return array( 'do_not_allow' );
|
||||
}
|
||||
}
|
||||
return $caps;
|
||||
}
|
||||
}
|
@ -0,0 +1,67 @@
|
||||
<?php
|
||||
/**
|
||||
* Class responsible for autoloading Event Bridge for ActivityPub class files.
|
||||
*
|
||||
* The Autoloader class is responsible for automatically loading class files as needed
|
||||
* to ensure a clean and organized codebase. It maps class names to their corresponding
|
||||
* file locations within the GatherPress plugin.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
/**
|
||||
* Class Autoloader.
|
||||
*
|
||||
* This class is responsible for automatic loading of classes and namespaces.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class Autoloader {
|
||||
/**
|
||||
* Register method for autoloader.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function register(): void {
|
||||
spl_autoload_register(
|
||||
function ( $full_class ) {
|
||||
$base_dir = EVENT_BRIDGE_FOR_ACTIVITYPUB_PLUGIN_DIR . '/includes/';
|
||||
$base = 'Event_Bridge_For_ActivityPub\\';
|
||||
|
||||
if ( strncmp( $full_class, $base, strlen( $base ) ) === 0 ) {
|
||||
$maybe_uppercase = str_replace( $base, '', $full_class );
|
||||
$class = strtolower( $maybe_uppercase );
|
||||
// All classes should be capitalized. If this is instead looking for a lowercase method, we ignore that.
|
||||
if ( $maybe_uppercase === $class ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( false !== strpos( $class, '\\' ) ) {
|
||||
$parts = explode( '\\', $class );
|
||||
$class = array_pop( $parts );
|
||||
$sub_dir = strtr( implode( '/', $parts ), '_', '-' );
|
||||
$base_dir = $base_dir . $sub_dir . '/';
|
||||
}
|
||||
|
||||
$filename = 'class-' . strtr( $class, '_', '-' );
|
||||
$file = $base_dir . $filename . '.php';
|
||||
|
||||
if ( file_exists( $file ) && is_readable( $file ) ) {
|
||||
require_once $file;
|
||||
} else {
|
||||
\wp_die( sprintf( esc_html( 'Required class not found or not readable: %s' ), esc_html( $full_class ) ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
<?php
|
||||
/**
|
||||
* Class file for Debug Class.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
/**
|
||||
* Debug Class.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
class Debug {
|
||||
/**
|
||||
* Initialize the class, registering WordPress hooks.
|
||||
*/
|
||||
public static function init() {
|
||||
if ( defined( 'WP_DEBUG_LOG' ) && constant( 'WP_DEBUG_LOG' ) ) {
|
||||
\add_action( 'event_bridge_for_activitypub_write_log', array( self::class, 'write_log' ), 10, 1 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a log entry.
|
||||
*
|
||||
* @param mixed $log The log entry.
|
||||
*/
|
||||
public static function write_log( $log ) {
|
||||
// phpcs:ignore WordPress.PHP.DevelopmentFunctions
|
||||
\error_log( \print_r( $log, true ) );
|
||||
}
|
||||
}
|
@ -0,0 +1,514 @@
|
||||
<?php
|
||||
/**
|
||||
* Class for handling and saving the ActivityPub event sources (i.e. follows).
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Activitypub\Model\Blog;
|
||||
use DateTime;
|
||||
use DateTimeZone;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Collection\Event_Sources as Event_Sources_Collection;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Handler;
|
||||
use Event_Bridge_For_ActivityPub\Admin\User_Interface;
|
||||
use Event_Bridge_For_ActivityPub\Integrations\Feature_Event_Sources;
|
||||
use WP_Error;
|
||||
use WP_Post;
|
||||
use WP_REST_Request;
|
||||
|
||||
use function Activitypub\is_activitypub_request;
|
||||
|
||||
/**
|
||||
* Class for handling and saving the ActivityPub event sources (i.e. follows).
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
*/
|
||||
class Event_Sources {
|
||||
/**
|
||||
* Init.
|
||||
*/
|
||||
public static function init() {
|
||||
// Register the Event Sources Collection which takes care of managing the event sources.
|
||||
\add_action( 'init', array( Event_Sources_Collection::class, 'init' ) );
|
||||
|
||||
// Allow wp_safe_redirect to all followed event sources hosts.
|
||||
\add_filter( 'allowed_redirect_hosts', array( self::class, 'add_event_sources_hosts_to_allowed_redirect_hosts' ) );
|
||||
|
||||
// Register handlers for incoming activities to the ActivityPub plugin, e.g. incoming `Event` objects.
|
||||
\add_action( 'activitypub_register_handlers', array( Handler::class, 'register_handlers' ) );
|
||||
|
||||
// Add validation filter, so that only plausible activities reach the handlers above.
|
||||
\add_filter(
|
||||
'activitypub_validate_object',
|
||||
array( self::class, 'validate_event_object' ),
|
||||
12,
|
||||
3
|
||||
);
|
||||
\add_filter(
|
||||
'activitypub_validate_object',
|
||||
array( self::class, 'validate_activity' ),
|
||||
13,
|
||||
3
|
||||
);
|
||||
|
||||
// Apply modifications to the UI, e.g. disable editing of remote event posts.
|
||||
\add_action( 'init', array( User_Interface::class, 'init' ) );
|
||||
|
||||
// Register post meta to the event plugins post types needed for easier handling of this feature.
|
||||
\add_action( 'init', array( self::class, 'register_post_meta' ) );
|
||||
|
||||
// Register filters that prevent cached remote events from being federated again.
|
||||
\add_filter( 'activitypub_is_post_disabled', array( self::class, 'is_post_disabled_for_activitypub' ), 99, 2 );
|
||||
\add_filter( 'template_include', array( self::class, 'redirect_activitypub_requests_for_cached_external_events' ), 100 );
|
||||
|
||||
// Register daily schedule to cleanup cached remote events that have ended.
|
||||
\add_action( 'event_bridge_for_activitypub_event_sources_clear_cache', array( self::class, 'clear_cache' ) );
|
||||
if ( ! \wp_next_scheduled( 'event_bridge_for_activitypub_event_sources_clear_cache' ) ) {
|
||||
\wp_schedule_event( time(), 'daily', 'event_bridge_for_activitypub_event_sources_clear_cache' );
|
||||
}
|
||||
|
||||
// Add the actors followed by the event sources feature to the `follow` collection of the used ActivityPub actor.
|
||||
\add_filter( 'activitypub_rest_following', array( self::class, 'add_event_sources_to_follow_collection' ), 10, 2 );
|
||||
|
||||
// Add action for backfilling the events.
|
||||
Outbox_Parser::init();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Register post meta.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function register_post_meta() {
|
||||
$setup = Setup::get_instance();
|
||||
|
||||
foreach ( $setup->get_active_event_plugins() as $event_plugin_integration ) {
|
||||
if ( ! is_a( $event_plugin_integration, Feature_Event_Sources::class ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$post_type = $event_plugin_integration::get_post_type();
|
||||
self::register_post_meta_event_bridge_for_activitypub_event_source( $post_type );
|
||||
|
||||
$post_type = $event_plugin_integration::get_place_post_type();
|
||||
if ( $post_type ) {
|
||||
self::register_post_meta_event_bridge_for_activitypub_event_source( $post_type );
|
||||
}
|
||||
|
||||
$post_type = $event_plugin_integration::get_organizer_post_type();
|
||||
if ( $post_type ) {
|
||||
self::register_post_meta_event_bridge_for_activitypub_event_source( $post_type );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register post meta _event_bridge_for_activitypub_event_source for a given post type.
|
||||
*
|
||||
* @param string $post_type The post type to register the meta for.
|
||||
* @return void
|
||||
*/
|
||||
private static function register_post_meta_event_bridge_for_activitypub_event_source( $post_type ) {
|
||||
\register_post_meta(
|
||||
$post_type,
|
||||
'_event_bridge_for_activitypub_event_source',
|
||||
array(
|
||||
'type' => 'integer',
|
||||
'single' => true,
|
||||
'sanitize_callback' => 'absint',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Application actor via FEP-2677.
|
||||
*
|
||||
* @param string $domain The domain without scheme.
|
||||
* @return bool|string The URL/ID of the application actor, false if not found.
|
||||
*/
|
||||
public static function get_application_actor( $domain ) {
|
||||
$result = wp_remote_get( 'https://' . $domain . '/.well-known/nodeinfo' );
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$body = wp_remote_retrieve_body( $result );
|
||||
|
||||
$nodeinfo = json_decode( $body, true );
|
||||
|
||||
// Check if 'links' exists and is an array.
|
||||
if ( isset( $nodeinfo['links'] ) && is_array( $nodeinfo['links'] ) ) {
|
||||
foreach ( $nodeinfo['links'] as $link ) {
|
||||
// Check if this link matches the application actor rel.
|
||||
if ( isset( $link['rel'] ) && 'https://www.w3.org/ns/activitystreams#Application' === $link['rel'] ) {
|
||||
if ( is_string( $link['href'] ) ) {
|
||||
return $link['href'];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return false if no application actor is found.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter that cached external posts are not scheduled via the ActivityPub plugin.
|
||||
*
|
||||
* Posts that are actually just external events are treated as cache. They are displayed in
|
||||
* the frontend HTML view and redirected via ActivityPub request, but we do not own them.
|
||||
*
|
||||
* @param bool $disabled If it is disabled already by others (the upstream ActivityPub plugin).
|
||||
* @param WP_Post $post The WordPress post object.
|
||||
* @return bool False if the post is not disabled for federation via ActivityPub.
|
||||
*/
|
||||
public static function is_post_disabled_for_activitypub( $disabled, $post = null ): bool {
|
||||
if ( $disabled ) {
|
||||
return $disabled;
|
||||
}
|
||||
return self::is_cached_external_post( $post );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether a WP post is a cached external event.
|
||||
*
|
||||
* @param WP_Post|int $post The WordPress post object or post ID.
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_cached_external_post( $post ): bool {
|
||||
$post_id = $post instanceof WP_Post ? $post->ID : $post;
|
||||
|
||||
if ( \get_post_meta( $post_id, '_event_bridge_for_activitypub_event_source', true ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the ActivityPub template for EventPrime.
|
||||
*
|
||||
* @param string $template The path to the template object.
|
||||
* @return string The new path to the JSON template.
|
||||
*/
|
||||
public static function redirect_activitypub_requests_for_cached_external_events( $template ) {
|
||||
if ( defined( 'REST_REQUEST' ) && REST_REQUEST ) {
|
||||
return $template;
|
||||
}
|
||||
|
||||
if ( ! is_activitypub_request() ) {
|
||||
return $template;
|
||||
}
|
||||
|
||||
if ( ! \is_singular() ) {
|
||||
return $template;
|
||||
}
|
||||
|
||||
$post = \get_post( \get_queried_object_id() );
|
||||
|
||||
if ( self::is_cached_external_post( $post ) ) {
|
||||
\wp_safe_redirect( $post->guid, 301 );
|
||||
exit;
|
||||
}
|
||||
|
||||
return $template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete old cached events that took place in the past.
|
||||
*/
|
||||
public static function clear_cache() {
|
||||
// Get the event plugin integration that is used.
|
||||
$event_plugin_integration = Setup::get_event_plugin_integration_used_for_event_sources_feature();
|
||||
|
||||
if ( ! $event_plugin_integration ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$cache_retention_period = get_option( 'event_bridge_for_activitypub_event_source_cache_retention', WEEK_IN_SECONDS );
|
||||
|
||||
$ended_before_time = gmdate( 'Y-m-d H:i:s', time() - $cache_retention_period );
|
||||
|
||||
$past_event_ids = $event_plugin_integration::get_cached_remote_events( $ended_before_time );
|
||||
|
||||
foreach ( $past_event_ids as $post_id ) {
|
||||
if ( has_post_thumbnail( $post_id ) ) {
|
||||
$attachment_id = get_post_thumbnail_id( $post_id );
|
||||
wp_delete_attachment( $attachment_id, true );
|
||||
}
|
||||
wp_delete_post( $post_id, true );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the Blog Authors to the following list of the Blog Actor
|
||||
* if Blog not in single mode.
|
||||
*
|
||||
* @param array $follow_list The array of following urls.
|
||||
* @param mixed $user The user object, a subtype of \Activitypub\Model\User.
|
||||
*
|
||||
* @return array The array of following urls.
|
||||
*/
|
||||
public static function add_event_sources_to_follow_collection( $follow_list, $user ): array {
|
||||
if ( ! $user instanceof Blog ) {
|
||||
return $follow_list;
|
||||
}
|
||||
|
||||
$event_sources_activitypub_ids = array_values( Event_Sources_Collection::get_event_sources() );
|
||||
|
||||
return array_merge( $follow_list, $event_sources_activitypub_ids );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array will all unique hosts of all Event-Sources.
|
||||
*
|
||||
* @return array A list with all unique hosts of all Event Sources' ActivityPub IDs.
|
||||
*/
|
||||
public static function get_event_sources_hosts() {
|
||||
$hosts = get_transient( 'event_bridge_for_activitypub_event_sources_hosts' );
|
||||
|
||||
if ( $hosts ) {
|
||||
return $hosts;
|
||||
}
|
||||
|
||||
$event_sources = Event_Sources_Collection::get_event_sources();
|
||||
|
||||
$hosts = array();
|
||||
foreach ( $event_sources as $actor ) {
|
||||
$url = wp_parse_url( $actor );
|
||||
if ( isset( $url['host'] ) ) {
|
||||
$hosts[] = $url['host'];
|
||||
}
|
||||
}
|
||||
|
||||
$hosts = array_unique( $hosts );
|
||||
|
||||
set_transient( 'event_bridge_for_activitypub_event_sources_hosts', $hosts );
|
||||
|
||||
return $hosts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add Event Sources hosts to allowed hosts used by safe redirect.
|
||||
*
|
||||
* @param array $hosts The hosts before the filter.
|
||||
* @return array
|
||||
*/
|
||||
public static function add_event_sources_hosts_to_allowed_redirect_hosts( $hosts ) {
|
||||
$event_sources_hosts = self::get_event_sources_hosts();
|
||||
return array_merge( $hosts, $event_sources_hosts );
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark incoming accept activities as valid.
|
||||
*
|
||||
* @param bool $valid The validation state.
|
||||
* @param string $param The object parameter.
|
||||
* @param WP_REST_Request $request The request object.
|
||||
*
|
||||
* @return bool|WP_Error The validation state: true if valid, false if not.
|
||||
*/
|
||||
public static function validate_activity( $valid, $param, $request ) {
|
||||
if ( $valid ) {
|
||||
return $valid;
|
||||
}
|
||||
$json_params = $request->get_json_params();
|
||||
|
||||
if ( isset( $json_params['object']['type'] ) && in_array( $json_params['object']['type'], array( 'Accept', 'Undo' ), true ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the event object.
|
||||
*
|
||||
* @param bool $valid The validation state.
|
||||
* @param string $param The object parameter.
|
||||
* @param WP_REST_Request $request The request object.
|
||||
*
|
||||
* @return bool|WP_Error The validation state: true if valid, false if not.
|
||||
*/
|
||||
public static function validate_event_object( $valid, $param, $request ) {
|
||||
$json_params = $request->get_json_params();
|
||||
|
||||
// Check if we should continue with the validation.
|
||||
if ( isset( $json_params['object']['type'] ) && 'Event' === $json_params['object']['type'] ) {
|
||||
$valid = true;
|
||||
} else {
|
||||
return $valid;
|
||||
}
|
||||
|
||||
if ( empty( $json_params['type'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( empty( $json_params['actor'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! in_array( $json_params['type'], array( 'Create', 'Update', 'Delete', 'Announce' ), true ) ) {
|
||||
return $valid;
|
||||
}
|
||||
|
||||
if ( ! self::is_valid_activitypub_event_object( $json_params['object'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! self::same_host( $json_params['actor'], $json_params['id'], $json_params['object']['id'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if all provided URLs belong to the same origin (host).
|
||||
*
|
||||
* @param string ...$urls List of URLs to compare.
|
||||
* @return bool True if all URLs have the same host, false otherwise.
|
||||
*/
|
||||
public static function same_host( ...$urls ) {
|
||||
if ( empty( $urls ) ) {
|
||||
return false; // No URLs given, can't compare hosts.
|
||||
}
|
||||
|
||||
$first = \wp_parse_url( array_shift( $urls ) );
|
||||
if ( ! isset( $first['host'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$first_host = $first['host'];
|
||||
|
||||
foreach ( $urls as $url ) {
|
||||
$result = \wp_parse_url( $url );
|
||||
if ( ! isset( $result['host'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( $result['host'] !== $first_host ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if the object is a valid ActivityPub event.
|
||||
*
|
||||
* @param mixed $event_object The (event) object as an associative array.
|
||||
* @return bool True if the object is an valid ActivityPub Event, false if not.
|
||||
*/
|
||||
public static function is_valid_activitypub_event_object( $event_object ): bool {
|
||||
if ( ! is_array( $event_object ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$required = array(
|
||||
'id',
|
||||
'startTime',
|
||||
'name',
|
||||
);
|
||||
|
||||
if ( array_intersect( $required, array_keys( $event_object ) ) !== $required ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! self::is_valid_activitypub_time_string( $event_object['startTime'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! self::is_valid_activitypub_id( $event_object['id'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate an ActivityPub ID.
|
||||
*
|
||||
* @link https://www.w3.org/TR/activitypub/#obj-id
|
||||
*
|
||||
* @param string $id The ID to validate.
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_valid_activitypub_id( $id ) {
|
||||
return \sanitize_url( $id ) ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a time string if it is according to the ActivityPub specification.
|
||||
*
|
||||
* @link https://www.w3.org/TR/activitystreams-core/#dates
|
||||
*
|
||||
* @param string $time_string The xsd:datetime string.
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_valid_activitypub_time_string( string $time_string ): bool {
|
||||
// Regular expression based on AS2 rules.
|
||||
return 1 === preg_match( '/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2})?(\.\d+)?(Z|[+-]\d{2}:\d{2})$/', $time_string );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a given DateTime is already passed.
|
||||
*
|
||||
* @param string|DateTime $time The ActivityPub like time string or DateTime object.
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_time_passed( $time ) {
|
||||
if ( ! $time instanceof DateTime ) {
|
||||
// Create a DateTime object from the ActivityPub time string.
|
||||
$time = new DateTime( $time, new DateTimeZone( 'UTC' ) );
|
||||
}
|
||||
|
||||
// Get the current time in UTC.
|
||||
$current_time = new DateTime( 'now', new DateTimeZone( 'UTC' ) );
|
||||
|
||||
// Compare the event time with the current time.
|
||||
return $time < $current_time;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether an Event is an ongoing or future event.
|
||||
*
|
||||
* @param array $event_object The ActivityPub Event as an associative array.
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_ongoing_or_future_event( $event_object ) {
|
||||
if ( isset( $event_object['endTime'] ) ) {
|
||||
$time = $event_object['endTime'];
|
||||
} else {
|
||||
$time = new DateTime( $event_object['startTime'], new DateTimeZone( 'UTC' ) );
|
||||
$time->modify( '+3 hours' );
|
||||
}
|
||||
return ! self::is_time_passed( $time );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that an ActivityPub actor is an event source (i.e. it is followed by the ActivityPub blog actor).
|
||||
*
|
||||
* @param string $actor_id The actor ID.
|
||||
* @return bool True if the ActivityPub actor ID is followed, false otherwise.
|
||||
*/
|
||||
public static function actor_is_event_source( $actor_id ) {
|
||||
$event_sources = Event_Sources_Collection::get_event_sources();
|
||||
if ( in_array( $actor_id, $event_sources, true ) ) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,282 @@
|
||||
<?php
|
||||
/**
|
||||
* Class file for parsing an ActivityPub outbox for Events.
|
||||
*
|
||||
* The main external entry function is `backfill_events`.
|
||||
* The function `import_events_from_outbox` is used for delaying the parsing via schedules.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Activitypub\Http;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Model\Event_Source;
|
||||
use WP_Error;
|
||||
|
||||
use function Activitypub\object_to_uri;
|
||||
|
||||
/**
|
||||
* Class for parsing an ActivityPub outbox for Events.
|
||||
*
|
||||
* The main external entry function is `backfill_events`.
|
||||
* The function `import_events_from_outbox` is used for delaying the parsing via schedules.
|
||||
*/
|
||||
class Outbox_Parser {
|
||||
/**
|
||||
* Maximum number of events to backfill per actor.
|
||||
*/
|
||||
const MAX_EVENTS_TO_IMPORT = 20;
|
||||
|
||||
/**
|
||||
* Init actions.
|
||||
*/
|
||||
public static function init(): void {
|
||||
// Add action for backfilling the events.
|
||||
\add_action( 'event_bridge_for_activitypub_backfill_events', array( self::class, 'backfill_events' ), 10, 1 );
|
||||
\add_action( 'event_bridge_for_activitypub_import_events_from_outbox', array( self::class, 'import_events_from_outbox' ), 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the backfilling of events via the outbox of an ActivityPub actor.
|
||||
*
|
||||
* @param int $event_source_post_id The Post ID of Event Source we want to backfill the events for.
|
||||
* @return void
|
||||
*/
|
||||
public static function backfill_events( $event_source_post_id ): void {
|
||||
$event_source = Event_Source::get_by_id( $event_source_post_id );
|
||||
|
||||
if ( ! $event_source ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$outbox_url = $event_source->get_outbox();
|
||||
|
||||
if ( ! $outbox_url ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Schedule the import of events via the outbox.
|
||||
self::queue_importing_from_outbox( $outbox_url, $event_source->get__id(), 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Import events from an outbox: OrderedCollection or OrderedCollectionPage.
|
||||
*
|
||||
* @param string $url The url of the current page or outbox.
|
||||
* @param int $event_source_post_id The Post ID of the Event Source that owns the outbox.
|
||||
* @return void
|
||||
*/
|
||||
public static function import_events_from_outbox( $url, $event_source_post_id ) {
|
||||
$setup = Setup::get_instance();
|
||||
if ( ! $setup->is_activitypub_plugin_active() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$outbox = self::fetch_outbox( $url );
|
||||
|
||||
if ( ! $outbox ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$current_count = self::get_import_count( $event_source_post_id );
|
||||
|
||||
if ( $current_count >= self::MAX_EVENTS_TO_IMPORT ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Process orderedItems if they exist (non-paginated outbox).
|
||||
if ( isset( $outbox['orderedItems'] ) && is_array( $outbox['orderedItems'] ) ) {
|
||||
$current_count += self::import_events_from_items(
|
||||
$outbox['orderedItems'],
|
||||
$event_source_post_id,
|
||||
self::MAX_EVENTS_TO_IMPORT - $current_count
|
||||
);
|
||||
}
|
||||
|
||||
self::update_import_count( $event_source_post_id, $current_count );
|
||||
|
||||
// If the count is already exceeded abort here.
|
||||
if ( $current_count >= self::MAX_EVENTS_TO_IMPORT ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get next page and if it exists schedule the import of next page.
|
||||
$pagination_url = self::get_pagination_url( $outbox );
|
||||
|
||||
if ( $pagination_url ) {
|
||||
self::queue_importing_from_outbox( $pagination_url, $event_source_post_id );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an Activity is of type Update or Create.
|
||||
*
|
||||
* @param array $activity The Activity as associative array.
|
||||
* @return bool
|
||||
*/
|
||||
private static function is_create_or_update_activity( $activity ) {
|
||||
if ( ! isset( $activity['type'] ) ) {
|
||||
return false;
|
||||
}
|
||||
if ( in_array( $activity['type'], array( 'Update', 'Create' ), true ) ) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses items from an Collection, OrderedCollection, CollectionPage or OrderedCollectionPage.
|
||||
*
|
||||
* @param array $items The items as an associative array.
|
||||
* @param int $max_items The maximum number of items to parse.
|
||||
* @return array Parsed events from the collection.
|
||||
*/
|
||||
private static function parse_outbox_items_for_events( $items, $max_items ) {
|
||||
$parsed_events = array();
|
||||
|
||||
foreach ( $items as $activity ) {
|
||||
// Abort if we have exceeded the maximal events to return.
|
||||
if ( $max_items > 0 && count( $parsed_events ) >= $max_items ) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Check if it is a create or update Activity.
|
||||
if ( ! self::is_create_or_update_activity( $activity ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If no object is set we cannot process anything.
|
||||
if ( ! isset( $activity['object'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if the Event object meets the minimum requirements and is valid.
|
||||
$is_valid_event_object = Event_Sources::is_valid_activitypub_event_object( $activity['object'] );
|
||||
if ( ! $is_valid_event_object ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if the event is in the future or ongoing.
|
||||
if ( Event_Sources::is_ongoing_or_future_event( $activity['object'] ) ) {
|
||||
$parsed_events[] = $activity['object'];
|
||||
}
|
||||
}
|
||||
|
||||
return $parsed_events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Import events from the items of an outbox.
|
||||
*
|
||||
* @param array $items The items/orderedItems as an associative array.
|
||||
* @param int $event_source_post_id The Post ID of the Event Source that owns the outbox.
|
||||
* @param int $limit The limit of how many events to save locally.
|
||||
* @return int The number of saved events (at least attempted).
|
||||
*/
|
||||
private static function import_events_from_items( $items, $event_source_post_id, $limit = -1 ): int {
|
||||
$events = self::parse_outbox_items_for_events( $items, $limit );
|
||||
|
||||
$transmogrifier = Setup::get_transmogrifier();
|
||||
|
||||
if ( ! $transmogrifier ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$imported_count = 0;
|
||||
|
||||
foreach ( $events as $event ) {
|
||||
$transmogrifier::save( $event, $event_source_post_id );
|
||||
++$imported_count;
|
||||
if ( $limit > 0 && $imported_count >= $limit ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $imported_count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule the import of events from an outbox OrderedCollection or OrderedCollectionPage.
|
||||
*
|
||||
* @param string $url The url of the current page or outbox.
|
||||
* @param int $event_source_post_id The Post ID of the Event Source that owns the outbox.
|
||||
* @param int $delay The delay of the current time in seconds.
|
||||
* @return bool
|
||||
*/
|
||||
private static function queue_importing_from_outbox( $url, $event_source_post_id, $delay = 10 ): bool {
|
||||
$hook = 'event_bridge_for_activitypub_import_events_from_outbox';
|
||||
$args = array( $url, $event_source_post_id );
|
||||
|
||||
if ( \wp_next_scheduled( $hook, $args ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return \wp_schedule_single_event( \time() + $delay, $hook, $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current import count for the actor.
|
||||
*
|
||||
* @param int $event_source_post_id The Post ID of the Event Source that owns the outbox.
|
||||
* @return int The current count of imported events.
|
||||
*/
|
||||
private static function get_import_count( $event_source_post_id ): int {
|
||||
return (int) \get_post_meta( $event_source_post_id, '_event_bridge_for_activitypub_event_count', true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the import count for an event source..
|
||||
*
|
||||
* @param int $event_source_post_id The Post ID of the Event Source that owns the outbox.
|
||||
* @param int $count The new count of imported events.
|
||||
* @return void
|
||||
*/
|
||||
private static function update_import_count( $event_source_post_id, $count ) {
|
||||
\update_post_meta( $event_source_post_id, '_event_bridge_for_activitypub_event_count', $count );
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the outbox from the given URL.
|
||||
*
|
||||
* @param string $url The URL of the outbox.
|
||||
* @return array|null The decoded outbox data, or null if fetching fails.
|
||||
*/
|
||||
private static function fetch_outbox( $url ) {
|
||||
$response = Http::get( $url );
|
||||
|
||||
if ( \is_wp_error( $response ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$outbox = \wp_remote_retrieve_body( $response );
|
||||
$outbox = \json_decode( $outbox, true );
|
||||
|
||||
return ( is_array( $outbox ) && isset( $outbox['type'] ) && isset( $outbox['id'] ) ) ? $outbox : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the pagination URL from the outbox.
|
||||
*
|
||||
* @param array $outbox The outbox data.
|
||||
* @return string|null The pagination URL, or null if not found.
|
||||
*/
|
||||
private static function get_pagination_url( $outbox ) {
|
||||
// If we are on a collection page simply use the next key.
|
||||
if ( 'OrderedCollectionPage' === $outbox['type'] && ! empty( $outbox['next'] ) && is_string( $outbox['next'] ) ) {
|
||||
return $outbox['next'];
|
||||
}
|
||||
|
||||
// If we still have the ordered collection itself.
|
||||
if ( isset( $outbox['type'] ) && 'OrderedCollection' === $outbox['type'] && isset( $outbox['first'] ) ) {
|
||||
return object_to_uri( $outbox['first'] );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
<?php
|
||||
/**
|
||||
* Class file initializing the custom ActivityPub preview.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @license AGPL-3.0-or-later
|
||||
* @since 1.0.0
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
/**
|
||||
* Class for initializing the custom ActivityPub preview(s).
|
||||
*/
|
||||
class Preview {
|
||||
/**
|
||||
* Init functions to hook into the ActivityPub plugin.
|
||||
*/
|
||||
public static function init() {
|
||||
\add_filter( 'activitypub_preview_template', array( self::class, 'maybe_apply_event_preview_template' ), 10, 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Maybe apply a custom preview template if the post type is an event post type of a supported event plugin.
|
||||
*
|
||||
* @return string The full path for the ActivityPub preview template to use.
|
||||
*/
|
||||
public static function maybe_apply_event_preview_template() {
|
||||
$event_post_types = Setup::get_instance()->get_active_event_plugins_post_types();
|
||||
|
||||
if ( in_array( \get_post_type(), $event_post_types, true ) ) {
|
||||
return EVENT_BRIDGE_FOR_ACTIVITYPUB_PLUGIN_DIR . '/templates/event-preview.php';
|
||||
}
|
||||
|
||||
return ACTIVITYPUB_PLUGIN_DIR . '/templates/post-preview.php';
|
||||
}
|
||||
}
|
@ -0,0 +1,192 @@
|
||||
<?php
|
||||
/**
|
||||
* Class file for Event Reminders.
|
||||
*
|
||||
* Automatic announcing or sending of reminders before the events start time.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @license AGPL-3.0-or-later
|
||||
* @since 1.0.0
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Activitypub\Transformer\Factory as Transformer_Factory;
|
||||
use Event_Bridge_For_ActivityPub\Setup;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Event\Event as Event_Transformer;
|
||||
use DateTime;
|
||||
use WP_Post;
|
||||
|
||||
use function ActivityPub\add_to_outbox;
|
||||
use function Activitypub\is_user_disabled;
|
||||
|
||||
/**
|
||||
* Adds automatic announcing or sending of reminders before the events start time.
|
||||
*/
|
||||
class Reminder {
|
||||
/**
|
||||
* Initialize the class, registering WordPress hooks.
|
||||
*/
|
||||
public static function init() {
|
||||
// Post transitions.
|
||||
\add_action( 'transition_post_status', array( self::class, 'maybe_schedule_event_reminder' ), 33, 3 );
|
||||
\add_action( 'delete_post', array( self::class, 'unschedule_event_reminder' ), 33, 1 );
|
||||
|
||||
// Send an event reminder.
|
||||
\add_action( 'event_bridge_for_activitypub_send_event_reminder', array( self::class, 'send_event_reminder' ), 10, 1 );
|
||||
|
||||
// Load the block which allows overriding the reminder time for an individual event in the post settings.
|
||||
\add_action( 'enqueue_block_editor_assets', array( self::class, 'enqueue_editor_assets' ) );
|
||||
|
||||
// Register the post-meta which stores per-event overrides of the side-wide default of the reminder time gap.
|
||||
\add_action( 'init', array( self::class, 'register_postmeta' ), 11 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register post meta for controlling whether and when a reminder is scheduled for an individual event.
|
||||
*/
|
||||
public static function register_postmeta() {
|
||||
$ap_post_types = \get_post_types_by_support( 'activitypub' );
|
||||
foreach ( $ap_post_types as $post_type ) {
|
||||
\register_post_meta(
|
||||
$post_type,
|
||||
'event_bridge_for_activitypub_reminder_time_gap',
|
||||
array(
|
||||
'show_in_rest' => true,
|
||||
'single' => true,
|
||||
'type' => 'integer',
|
||||
'sanitize_callback' => 'absint',
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue the block editor assets.
|
||||
*/
|
||||
public static function enqueue_editor_assets() {
|
||||
// Check for our supported post types.
|
||||
$current_screen = \get_current_screen();
|
||||
$event_post_types = Setup::get_instance()->get_active_event_plugins_post_types();
|
||||
if ( ! $current_screen || ! in_array( $current_screen->post_type, $event_post_types, true ) ) {
|
||||
return;
|
||||
}
|
||||
$asset_data = include EVENT_BRIDGE_FOR_ACTIVITYPUB_PLUGIN_DIR . 'build/reminder/plugin.asset.php';
|
||||
$plugin_url = plugins_url( 'build/reminder/plugin.js', EVENT_BRIDGE_FOR_ACTIVITYPUB_PLUGIN_FILE );
|
||||
\wp_enqueue_script( 'event-bridge-for-activitypub-reminder', $plugin_url, $asset_data['dependencies'], $asset_data['version'], true );
|
||||
|
||||
// Pass the the default site wide time gap option to the settings block on the events edit page.
|
||||
\wp_localize_script(
|
||||
'event-bridge-for-activitypub-reminder',
|
||||
'activityPubEventBridge',
|
||||
array(
|
||||
'reminderTypeGap' => \get_option( 'event_bridge_for_activitypub_reminder_time_gap', 0 ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule Activities.
|
||||
*
|
||||
* @param string $new_status New post status.
|
||||
* @param string $old_status Old post status.
|
||||
* @param ?WP_Post $post Post object.
|
||||
*/
|
||||
public static function maybe_schedule_event_reminder( $new_status, $old_status, $post ): void {
|
||||
if ( ! $post instanceof WP_Post ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// At first always unschedule the reminder for this event, it will be added again, in case.
|
||||
self::unschedule_event_reminder( $post->ID );
|
||||
|
||||
// Do not set reminders if post is password protected.
|
||||
if ( \post_password_required( $post ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only schedule an reminder for event post types.
|
||||
if ( ! Setup::get_instance()->is_event_post_type_of_active_event_plugin( $post->post_type ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Do not schedule a reminder if the event is not published.
|
||||
if ( 'publish' !== $new_status ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// See if a reminder time gap is set for the event individually in the events post-meta.
|
||||
$reminder_time_gap = (int) get_post_meta( $post->ID, 'event_bridge_for_activitypub_reminder_time_gap', true );
|
||||
|
||||
// If not fallback to the global reminder time gap.
|
||||
if ( ! $reminder_time_gap ) {
|
||||
$reminder_time_gap = \get_option( 'event_bridge_for_activitypub_reminder_time_gap', 0 );
|
||||
}
|
||||
|
||||
// Any non positive integer means that this feature is not active for this event post.
|
||||
if ( 0 === $reminder_time_gap || ! is_int( $reminder_time_gap ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get start time of the event.
|
||||
$event_transformer = Transformer_Factory::get_transformer( $post );
|
||||
|
||||
if ( \is_wp_error( $event_transformer ) || ! $event_transformer instanceof Event_Transformer ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$start_time = $event_transformer->get_start_time();
|
||||
$start_datetime = new DateTime( $start_time );
|
||||
$start_timestamp = $start_datetime->getTimestamp();
|
||||
|
||||
// Get the time when the reminder of the event's start should be sent.
|
||||
$schedule_time = $start_timestamp - $reminder_time_gap;
|
||||
|
||||
// If the reminder time has already passed "now" skip it.
|
||||
if ( $schedule_time < \time() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// All checks passed: schedule a single event which will trigger the sending of the reminder for this event post.
|
||||
\wp_schedule_single_event( $schedule_time, 'event_bridge_for_activitypub_send_event_reminder', array( $post->ID ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Unschedule the event reminder.
|
||||
*
|
||||
* @param int $post_id The WordPress post ID of the event post.
|
||||
*/
|
||||
public static function unschedule_event_reminder( $post_id ): void {
|
||||
\wp_clear_scheduled_hook( 'event_bridge_for_activitypub_send_event_reminder', array( $post_id ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a reminder for an event post.
|
||||
*
|
||||
* This currently sends an Announce activity.
|
||||
*
|
||||
* @param int $post_id The WordPress post ID of the event post.
|
||||
*/
|
||||
public static function send_event_reminder( $post_id ) {
|
||||
$post = \get_post( $post_id );
|
||||
|
||||
$transformer = Transformer_Factory::get_transformer( $post );
|
||||
|
||||
if ( \is_wp_error( $transformer ) || ! $transformer instanceof Event_Transformer ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$actor = $transformer->get_actor_object();
|
||||
$user_id = $actor->get__id();
|
||||
|
||||
if ( $user_id > 0 && is_user_disabled( $user_id ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add announce of the event to outbox.
|
||||
add_to_outbox( $post, 'Announce', $user_id );
|
||||
}
|
||||
}
|
@ -0,0 +1,261 @@
|
||||
<?php
|
||||
/**
|
||||
* General settings class.
|
||||
*
|
||||
* This file contains the General class definition, which handles the "General" settings
|
||||
* page for the Event Bridge for ActivityPub Plugin, providing options for configuring various general settings.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub;
|
||||
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Collection\Event_Sources;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Event_Bridge_For_ActivityPub\Integrations\Feature_Event_Sources;
|
||||
|
||||
/**
|
||||
* Class responsible for the ActivityPui Event Extension related Settings.
|
||||
*
|
||||
* Class responsible for the ActivityPui Event Extension related Settings.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class Settings {
|
||||
const SETTINGS_SLUG = 'event-bridge-for-activitypub';
|
||||
|
||||
/**
|
||||
* The default ActivityPub event category.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const DEFAULT_EVENT_CATEGORY = 'MEETING';
|
||||
|
||||
/**
|
||||
* Register the settings for the Event Bridge for ActivityPub plugin.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function register_settings(): void {
|
||||
\register_setting(
|
||||
'event-bridge-for-activitypub',
|
||||
'event_bridge_for_activitypub_default_event_category',
|
||||
array(
|
||||
'type' => 'string',
|
||||
'description' => \__( 'Default standardized federated event category.', 'event-bridge-for-activitypub' ),
|
||||
'show_in_rest' => true,
|
||||
'default' => self::DEFAULT_EVENT_CATEGORY,
|
||||
'sanitize_callback' => array( self::class, 'sanitize_mapped_event_category' ),
|
||||
)
|
||||
);
|
||||
|
||||
\register_setting(
|
||||
'event-bridge-for-activitypub',
|
||||
'event_bridge_for_activitypub_event_category_mappings',
|
||||
array(
|
||||
'type' => 'array',
|
||||
'description' => \__( 'Define your own custom post template', 'event-bridge-for-activitypub' ),
|
||||
'default' => array(),
|
||||
'sanitize_callback' => array( self::class, 'sanitize_event_category_mappings' ),
|
||||
)
|
||||
);
|
||||
|
||||
\register_setting(
|
||||
'event-bridge-for-activitypub',
|
||||
'event_bridge_for_activitypub_reminder_time_gap',
|
||||
array(
|
||||
'type' => 'array',
|
||||
'description' => \__( 'Time gap in seconds when a reminder is triggered that the event is about to start.', 'event-bridge-for-activitypub' ),
|
||||
'default' => 0, // Zero leads to this feature being deactivated.
|
||||
'sanitize_callback' => 'absint',
|
||||
)
|
||||
);
|
||||
|
||||
\register_setting(
|
||||
'event-bridge-for-activitypub',
|
||||
'event_bridge_for_activitypub_initially_activated',
|
||||
array(
|
||||
'type' => 'boolean',
|
||||
'description' => \__( 'Whether the plugin just got activated for the first time.', 'event-bridge-for-activitypub' ),
|
||||
'default' => 1,
|
||||
)
|
||||
);
|
||||
|
||||
\register_setting(
|
||||
'event-bridge-for-activitypub',
|
||||
'event_bridge_for_activitypub_summary_type',
|
||||
array(
|
||||
'type' => 'string',
|
||||
'description' => \__( 'Summary type to use for ActivityStreams', 'event-bridge-for-activitypub' ),
|
||||
'show_in_rest' => true,
|
||||
'default' => 'preset',
|
||||
)
|
||||
);
|
||||
|
||||
\register_setting(
|
||||
'event-bridge-for-activitypub',
|
||||
'event_bridge_for_activitypub_summary_format',
|
||||
array(
|
||||
'type' => 'string',
|
||||
'description' => \__( 'Summary format to use for ActivityStreams', 'event-bridge-for-activitypub' ),
|
||||
'show_in_rest' => true,
|
||||
'default' => 'html',
|
||||
)
|
||||
);
|
||||
|
||||
\register_setting(
|
||||
'event-bridge-for-activitypub',
|
||||
'event_bridge_for_activitypub_custom_summary',
|
||||
array(
|
||||
'type' => 'string',
|
||||
'description' => \__( 'Define your own custom summary template for events', 'event-bridge-for-activitypub' ),
|
||||
'show_in_rest' => true,
|
||||
'default' => EVENT_BRIDGE_FOR_ACTIVITYPUB_SUMMARY_TEMPLATE,
|
||||
)
|
||||
);
|
||||
|
||||
\register_setting(
|
||||
'event-bridge-for-activitypub_event-sources',
|
||||
'event_bridge_for_activitypub_event_sources_active',
|
||||
array(
|
||||
'type' => 'boolean',
|
||||
'show_in_rest' => true,
|
||||
'description' => \__( 'Whether the event sources feature is activated.', 'event-bridge-for-activitypub' ),
|
||||
'default' => 0,
|
||||
'sanitize_callback' => array( self::class, 'sanitize_event_sources_feature_active' ),
|
||||
)
|
||||
);
|
||||
|
||||
\register_setting(
|
||||
'event-bridge-for-activitypub_event-sources',
|
||||
'event_bridge_for_activitypub_event_source_cache_retention',
|
||||
array(
|
||||
'type' => 'integer',
|
||||
'show_in_rest' => true,
|
||||
'description' => \__( 'The cache retention period for external event sources.', 'event-bridge-for-activitypub' ),
|
||||
'default' => WEEK_IN_SECONDS,
|
||||
'sanitize_callback' => 'absint',
|
||||
)
|
||||
);
|
||||
|
||||
\register_setting(
|
||||
'event-bridge-for-activitypub_event-sources',
|
||||
'event_bridge_for_activitypub_integration_used_for_event_sources_feature',
|
||||
array(
|
||||
'type' => 'string',
|
||||
'description' => \__( 'Define which plugin/integration is used for the event sources feature', 'event-bridge-for-activitypub' ),
|
||||
'default' => array(),
|
||||
'sanitize_callback' => array( self::class, 'sanitize_event_plugin_integration_used_for_event_sources' ),
|
||||
)
|
||||
);
|
||||
|
||||
\register_setting(
|
||||
'event-bridge-for-activitypub_add-event-source',
|
||||
'event_bridge_for_activitypub_add_event_source',
|
||||
array(
|
||||
'type' => 'array',
|
||||
'description' => \__( 'Dummy setting for adding event sources', 'event-bridge-for-activitypub' ),
|
||||
'default' => '',
|
||||
'sanitize_callback' => 'sanitize_text_field',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Do not allow the event sources feature to get deactivated, when event sources are still followed.
|
||||
*
|
||||
* @param mixed $value The optios value.
|
||||
*/
|
||||
public static function sanitize_event_sources_feature_active( $value ) {
|
||||
$count = count( Event_Sources::get_event_sources() );
|
||||
|
||||
$value = (bool) $value;
|
||||
|
||||
if ( 0 === $count ) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if ( ! $value ) {
|
||||
\add_settings_error(
|
||||
'event-bridge-for-activitypub_event-sources',
|
||||
'event_bridge_for_activitypub_cannot_disable_event_sources',
|
||||
__( 'It is not possible to disable the Event Sources feature while you are still having active followed Event Sources.', 'event-bridge-for-activitypub' ),
|
||||
'error'
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize the option which event plugin.
|
||||
*
|
||||
* @param mixed $event_plugin_integration The setting.
|
||||
* @return string
|
||||
*/
|
||||
public static function sanitize_event_plugin_integration_used_for_event_sources( $event_plugin_integration ): string {
|
||||
if ( ! is_string( $event_plugin_integration ) ) {
|
||||
return '';
|
||||
}
|
||||
$setup = Setup::get_instance();
|
||||
$active_event_plugins = $setup->get_active_event_plugins();
|
||||
|
||||
$valid_options = array();
|
||||
foreach ( $active_event_plugins as $active_event_plugin ) {
|
||||
if ( $active_event_plugin instanceof Feature_Event_Sources ) {
|
||||
$valid_options[] = get_class( $active_event_plugin );
|
||||
}
|
||||
}
|
||||
if ( in_array( $event_plugin_integration, $valid_options, true ) ) {
|
||||
return $event_plugin_integration;
|
||||
}
|
||||
return Setup::get_default_integration_class_name_used_for_event_sources_feature();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize the target ActivityPub Event category.
|
||||
*
|
||||
* @param string $event_category The ActivityPUb event category.
|
||||
*/
|
||||
public static function sanitize_mapped_event_category( $event_category ): string {
|
||||
return self::is_allowed_event_category( $event_category ) ? $event_category : self::DEFAULT_EVENT_CATEGORY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitization function for the event category mapping setting.
|
||||
*
|
||||
* Currently only the default event categories are allowed to be target of a mapping.
|
||||
*
|
||||
* @param array $event_category_mappings The settings value.
|
||||
*
|
||||
* @return array An array that contains only valid mapping pairs.
|
||||
*/
|
||||
public static function sanitize_event_category_mappings( $event_category_mappings ): array {
|
||||
if ( empty( $event_category_mappings ) ) {
|
||||
return array();
|
||||
}
|
||||
foreach ( $event_category_mappings as $taxonomy_slug => $event_category ) {
|
||||
if ( ! self::is_allowed_event_category( $event_category ) ) {
|
||||
unset( $event_category_mappings[ $taxonomy_slug ] );
|
||||
}
|
||||
}
|
||||
return $event_category_mappings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given event category is allowed to be target of a mapping.
|
||||
*
|
||||
* @param string $event_category The event category to check.
|
||||
*
|
||||
* @return bool True if allowed, false otherwise.
|
||||
*/
|
||||
private static function is_allowed_event_category( $event_category ): bool {
|
||||
require_once EVENT_BRIDGE_FOR_ACTIVITYPUB_PLUGIN_DIR . '/includes/event-categories.php';
|
||||
$allowed_event_categories = array_keys( EVENT_BRIDGE_FOR_ACTIVITYPUB_EVENT_CATEGORIES );
|
||||
return in_array( $event_category, $allowed_event_categories, true );
|
||||
}
|
||||
}
|
@ -0,0 +1,692 @@
|
||||
<?php
|
||||
/**
|
||||
* Class responsible for initializing Event Bridge for ActivityPub.
|
||||
*
|
||||
* The setup class provides function for checking if this plugin should be activated.
|
||||
* It detects supported event plugins and provides all setup hooks and filters.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Collection\Event_Sources as Event_Sources_Collection;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Handler\Join as Join_Handler;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Scheduler\Event as Event_Scheduler;
|
||||
use Event_Bridge_For_ActivityPub\Admin\Event_Plugin_Admin_Notices;
|
||||
use Event_Bridge_For_ActivityPub\Admin\General_Admin_Notices;
|
||||
use Event_Bridge_For_ActivityPub\Admin\Health_Check;
|
||||
use Event_Bridge_For_ActivityPub\Admin\Settings_Page;
|
||||
use Event_Bridge_For_ActivityPub\Integrations\Event_Plugin_Integration;
|
||||
use Event_Bridge_For_ActivityPub\Integrations\Feature_Event_Sources;
|
||||
use Event_Bridge_For_ActivityPub\Reminder;
|
||||
|
||||
use function Activitypub\is_user_type_disabled;
|
||||
|
||||
// @phpstan-ignore-next-line
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
|
||||
/**
|
||||
* Class Setup.
|
||||
|
||||
* This class is responsible for initializing Event Bridge for ActivityPub.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class Setup {
|
||||
/**
|
||||
* Keep the information whether the ActivityPub plugin is active.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected $activitypub_plugin_is_active = false;
|
||||
|
||||
/**
|
||||
* Keep the current version of the current ActivityPub plugin.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $activitypub_plugin_version = '';
|
||||
|
||||
/**
|
||||
* Holds an array of the currently activated supported event plugins.
|
||||
*
|
||||
* @var Event_Plugin_Integration[]
|
||||
*/
|
||||
protected $active_event_plugins = array();
|
||||
|
||||
/**
|
||||
* Constructor for the Setup class.
|
||||
*
|
||||
* Initializes and sets up various components of the plugin.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
protected function __construct() {
|
||||
// Detect the presence/active-status and version of the ActivityPub plugin.
|
||||
$this->activitypub_plugin_is_active = defined( 'ACTIVITYPUB_PLUGIN_VERSION' ) || \is_plugin_active( 'activitypub/activitypub.php' );
|
||||
$this->activitypub_plugin_version = self::get_activitypub_plugin_version();
|
||||
|
||||
// Register main action that load the Event Bridge For ActivityPub.
|
||||
\add_action( 'plugins_loaded', array( $this, 'setup_hooks' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* The single instance of the class.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @var ?self The instance of the class.
|
||||
*/
|
||||
private static $instance = null;
|
||||
|
||||
/**
|
||||
* Get the instance of the Singleton class.
|
||||
*
|
||||
* If an instance does not exist, it creates one; otherwise, it returns the existing instance.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @return self The instance of the class.
|
||||
*/
|
||||
public static function get_instance(): self {
|
||||
if ( null === self::$instance ) {
|
||||
self::$instance = new self();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter function for whether the ActivityPub plugin is active.
|
||||
*
|
||||
* @return bool True when the ActivityPub plugin is active.
|
||||
*/
|
||||
public function is_activitypub_plugin_active(): bool {
|
||||
return $this->activitypub_plugin_is_active;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current version of the ActivityPub plugin.
|
||||
*
|
||||
* @return string The semantic Version.
|
||||
*/
|
||||
private static function get_activitypub_plugin_version(): string {
|
||||
if ( defined( 'ACTIVITYPUB_PLUGIN_VERSION' ) ) {
|
||||
return constant( 'ACTIVITYPUB_PLUGIN_VERSION' );
|
||||
}
|
||||
return '0.0.0';
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter function for the active event plugins.
|
||||
*
|
||||
* @return Event_Plugin_Integration[]
|
||||
*/
|
||||
public function get_active_event_plugins(): array {
|
||||
return $this->active_event_plugins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter function for the active event plugins event post types.
|
||||
*
|
||||
* @return array List of event post types of the active event plugins.
|
||||
*/
|
||||
public function get_active_event_plugins_post_types(): array {
|
||||
$post_types = array();
|
||||
foreach ( $this->active_event_plugins as $event_plugin ) {
|
||||
$post_types[] = $event_plugin->get_post_type();
|
||||
}
|
||||
|
||||
return $post_types;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function to check whether a post type is an event post type of an active event plugin.
|
||||
*
|
||||
* @param string $post_type The post type.
|
||||
*
|
||||
* @return bool True if it is an event post type.
|
||||
*/
|
||||
public function is_event_post_type_of_active_event_plugin( $post_type ): bool {
|
||||
foreach ( $this->active_event_plugins as $event_plugin ) {
|
||||
if ( $post_type === $event_plugin->get_post_type() ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Holds all the full class names for the supported event plugins.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private const EVENT_PLUGIN_INTEGRATIONS = array(
|
||||
\Event_Bridge_For_ActivityPub\Integrations\Events_Manager::class,
|
||||
\Event_Bridge_For_ActivityPub\Integrations\GatherPress::class,
|
||||
\Event_Bridge_For_ActivityPub\Integrations\The_Events_Calendar::class,
|
||||
\Event_Bridge_For_ActivityPub\Integrations\VS_Event_List::class,
|
||||
\Event_Bridge_For_ActivityPub\Integrations\WP_Event_Manager::class,
|
||||
\Event_Bridge_For_ActivityPub\Integrations\Eventin::class,
|
||||
\Event_Bridge_For_ActivityPub\Integrations\Modern_Events_Calendar_Lite::class,
|
||||
\Event_Bridge_For_ActivityPub\Integrations\Event_Organiser::class,
|
||||
\Event_Bridge_For_ActivityPub\Integrations\EventPrime::class,
|
||||
\Event_Bridge_For_ActivityPub\Integrations\EventOn::class,
|
||||
);
|
||||
|
||||
/**
|
||||
* Force the re-scan for active event plugins without using the cached transient.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function redetect_active_event_plugins(): void {
|
||||
if ( ! $this->activitypub_plugin_is_active ) {
|
||||
return;
|
||||
}
|
||||
\delete_transient( 'event_bridge_for_activitypub_active_event_plugins' );
|
||||
|
||||
$this->detect_active_event_plugins();
|
||||
}
|
||||
|
||||
/**
|
||||
* Function that checks for supported activated event plugins.
|
||||
*
|
||||
* @return array List of supported event plugins as keys from the SUPPORTED_EVENT_PLUGINS const.
|
||||
*/
|
||||
public function detect_active_event_plugins(): array {
|
||||
// Detection will fail in case the ActivityPub plugin is not active.
|
||||
if ( ! $this->activitypub_plugin_is_active ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$active_event_plugins = \get_transient( 'event_bridge_for_activitypub_active_event_plugins' );
|
||||
|
||||
if ( $active_event_plugins ) {
|
||||
$this->active_event_plugins = $active_event_plugins;
|
||||
return $active_event_plugins;
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'get_plugins' ) ) {
|
||||
// @phpstan-ignore-next-line
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
}
|
||||
|
||||
$all_plugins = array_merge( \get_plugins(), \get_mu_plugins() );
|
||||
|
||||
$active_event_plugins = array();
|
||||
foreach ( self::EVENT_PLUGIN_INTEGRATIONS as $event_plugin_integration ) {
|
||||
// Get the filename of the main plugin file of the event plugin (relative to the plugin dir).
|
||||
$event_plugin_file = $event_plugin_integration::get_relative_plugin_file();
|
||||
|
||||
// Check if plugin is present on disk and is activated.
|
||||
if ( array_key_exists( $event_plugin_file, $all_plugins ) && \is_plugin_active( $event_plugin_file ) ) {
|
||||
$active_event_plugins[ $event_plugin_file ] = new $event_plugin_integration();
|
||||
}
|
||||
}
|
||||
\set_transient( 'event_bridge_for_activitypub_active_event_plugins', $active_event_plugins );
|
||||
$this->active_event_plugins = $active_event_plugins;
|
||||
return $active_event_plugins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function that checks which event plugins support the event sources feature.
|
||||
*
|
||||
* @return array List of supported event plugins as keys from the SUPPORTED_EVENT_PLUGINS const.
|
||||
*/
|
||||
public static function detect_event_plugins_supporting_event_sources(): array {
|
||||
$plugins_supporting_event_sources = array();
|
||||
|
||||
foreach ( self::EVENT_PLUGIN_INTEGRATIONS as $event_plugin_integration ) {
|
||||
if ( is_a( $event_plugin_integration, Feature_Event_Sources::class, true ) ) {
|
||||
$plugins_supporting_event_sources[] = new $event_plugin_integration();
|
||||
}
|
||||
}
|
||||
return $plugins_supporting_event_sources;
|
||||
}
|
||||
|
||||
/**
|
||||
* Main setup function of the plugin "Event Bridge For ActivityPub".
|
||||
*
|
||||
* This method adds hooks for different purposes as needed.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setup_hooks(): void {
|
||||
// Detect active supported event plugins.
|
||||
$this->detect_active_event_plugins();
|
||||
|
||||
// Register hook that runs when this plugin gets activated.
|
||||
\register_activation_hook( EVENT_BRIDGE_FOR_ACTIVITYPUB_PLUGIN_FILE, array( $this, 'activate' ) );
|
||||
|
||||
// Register listeners whenever any plugin gets activated or deactivated to maybe update the transient of active event plugins.
|
||||
\add_action( 'activated_plugin', array( $this, 'redetect_active_event_plugins' ) );
|
||||
\add_action( 'deactivated_plugin', array( $this, 'redetect_active_event_plugins' ) );
|
||||
|
||||
// Add hook that takes care of all notices in the Admin UI.
|
||||
\add_action( 'admin_init', array( $this, 'do_admin_notices' ) );
|
||||
|
||||
// Add hook that registers all settings of this plugin to WordPress.
|
||||
\add_action( 'admin_init', array( Settings::class, 'register_settings' ) );
|
||||
|
||||
// Add hook that loads CSS and JavaScript files for the Admin UI.
|
||||
\add_action( 'admin_enqueue_scripts', array( self::class, 'enqueue_styles' ) );
|
||||
|
||||
// Register the settings page of this plugin as a Tab in the ActivityPub plugins settings.
|
||||
Settings_Page::init();
|
||||
|
||||
// Add settings link in the Plugin overview Page.
|
||||
\add_filter(
|
||||
'plugin_action_links_' . EVENT_BRIDGE_FOR_ACTIVITYPUB_PLUGIN_BASENAME,
|
||||
array( Settings_Page::class, 'settings_link' )
|
||||
);
|
||||
|
||||
// If we don't have any active event plugins, or the ActivityPub plugin is not enabled, abort here.
|
||||
if ( empty( $this->active_event_plugins ) || ! $this->activitypub_plugin_is_active ) {
|
||||
self::shut_down();
|
||||
return;
|
||||
}
|
||||
|
||||
// Register health checks and status reports to the WordPress status report site.
|
||||
\add_action( 'init', array( Health_Check::class, 'init' ) );
|
||||
|
||||
// Check if the minimum required version of the ActivityPub plugin is installed, if not abort.
|
||||
|
||||
if ( ! version_compare( $this->activitypub_plugin_version, EVENT_BRIDGE_FOR_ACTIVITYPUB_ACTIVITYPUB_PLUGIN_MIN_VERSION, '>=' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Register our own deplayed event scheduler because of upstream race-condition bug.
|
||||
// See https://github.com/Automattic/wordpress-activitypub/issues/1269 for more information.
|
||||
if ( version_compare( $this->activitypub_plugin_version, '7.0.0', '<' ) ) {
|
||||
Event_Scheduler::init();
|
||||
}
|
||||
|
||||
// Register the event reminders.
|
||||
\add_action( 'init', array( Reminder::class, 'init' ) );
|
||||
|
||||
// Initialize the handling of "Join" activities.
|
||||
Join_Handler::init();
|
||||
|
||||
// If the Event-Sources feature is enabled and all requirements are met, initialize it.
|
||||
if ( ! is_user_type_disabled( 'blog' ) && \get_option( 'event_bridge_for_activitypub_event_sources_active' ) ) {
|
||||
Event_Sources::init();
|
||||
}
|
||||
|
||||
// Initialize writing of debug logs.
|
||||
Debug::init();
|
||||
|
||||
$this->register_plugin_specific_hooks();
|
||||
|
||||
// Most importantly: register the ActivityPub transformers for events to the ActivityPub plugin.
|
||||
\add_filter( 'activitypub_transformer', array( $this, 'register_activitypub_transformer' ), 10, 3 );
|
||||
|
||||
// Apply custom ActivityPub previews for events.
|
||||
\add_action( 'init', array( Preview::class, 'init' ) );
|
||||
|
||||
$this->maybe_register_term_activitypub_ids();
|
||||
}
|
||||
|
||||
/**
|
||||
* Temporary hack to register custom actions and hooks, only needed by exceptional event plugins.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function register_plugin_specific_hooks(): void {
|
||||
if ( array_key_exists( \Event_Bridge_For_ActivityPub\Integrations\EventPrime::get_relative_plugin_file(), $this->active_event_plugins ) ) {
|
||||
\Event_Bridge_For_ActivityPub\Integrations\EventPrime::init();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Shut down the plugin.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function shut_down(): void {
|
||||
// Delete all transients.
|
||||
Event_Sources_Collection::delete_event_source_transients();
|
||||
\delete_transient( 'event_bridge_for_activitypub_active_event_plugins' );
|
||||
|
||||
// Unschedule all crons.
|
||||
\wp_unschedule_hook( 'event_bridge_for_activitypub_event_sources_clear_cache' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the CSS for the admin pages.
|
||||
*
|
||||
* @param string $hook_suffix The suffix of the hook.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function enqueue_styles( $hook_suffix ): void {
|
||||
if ( 'settings_page_activitypub' !== $hook_suffix ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we're on your custom tab.
|
||||
$current_tab = isset( $_GET['tab'] ) ? \sanitize_key( $_GET['tab'] ) : 'welcome'; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( 'event-bridge-for-activitypub' === $current_tab ) {
|
||||
\wp_enqueue_style(
|
||||
'event-bridge-for-activitypub-admin-styles',
|
||||
plugins_url(
|
||||
'assets/css/event-bridge-for-activitypub-admin.css',
|
||||
EVENT_BRIDGE_FOR_ACTIVITYPUB_PLUGIN_FILE
|
||||
),
|
||||
array(),
|
||||
EVENT_BRIDGE_FOR_ACTIVITYPUB_PLUGIN_VERSION
|
||||
);
|
||||
\wp_enqueue_script(
|
||||
'event-bridge-for-activitypub-admin-script',
|
||||
plugins_url(
|
||||
'assets/js/event-bridge-for-activitypub-admin.js',
|
||||
EVENT_BRIDGE_FOR_ACTIVITYPUB_PLUGIN_FILE
|
||||
),
|
||||
array( 'jquery' ),
|
||||
EVENT_BRIDGE_FOR_ACTIVITYPUB_PLUGIN_VERSION,
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires the initialization of admin notices.
|
||||
*/
|
||||
public function do_admin_notices(): void {
|
||||
foreach ( $this->active_event_plugins as $event_plugin ) {
|
||||
new Event_Plugin_Admin_Notices( $event_plugin );
|
||||
}
|
||||
// Check if any general admin notices are needed and add actions to insert the needed admin notices.
|
||||
if ( ! $this->activitypub_plugin_is_active ) {
|
||||
// The ActivityPub plugin is not active.
|
||||
\add_action( 'admin_notices', array( General_Admin_Notices::class, 'activitypub_plugin_not_enabled' ), 10, 0 );
|
||||
return;
|
||||
}
|
||||
if ( ! version_compare( $this->activitypub_plugin_version, EVENT_BRIDGE_FOR_ACTIVITYPUB_ACTIVITYPUB_PLUGIN_MIN_VERSION, '>=' ) ) {
|
||||
// The ActivityPub plugin is too old.
|
||||
\add_action( 'admin_notices', array( General_Admin_Notices::class, 'activitypub_plugin_version_too_old' ), 10, 0 );
|
||||
return;
|
||||
}
|
||||
if ( empty( $this->active_event_plugins ) ) {
|
||||
// No supported Event Plugin is active.
|
||||
\add_action( 'admin_notices', array( General_Admin_Notices::class, 'no_supported_event_plugin_active' ), 10, 0 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the custom transformers for the events and locations of several WordPress event plugins.
|
||||
*
|
||||
* @param \Activitypub\Transformer\Base $transformer The transformer to use.
|
||||
* @param mixed $data The data to transform.
|
||||
* @param string $object_class The class of the object to transform.
|
||||
*
|
||||
* @return \Activitypub\Transformer\Base|null|\WP_Error
|
||||
*/
|
||||
public function register_activitypub_transformer( $transformer, $data, $object_class ) {
|
||||
// If the current WordPress object is not a post (e.g., a WP_Comment), don't change the transformer.
|
||||
if ( 'WP_Post' === $object_class ) {
|
||||
// Get the transformer for a specific event plugins event or location post type.
|
||||
foreach ( $this->active_event_plugins as $event_plugin ) {
|
||||
// Check if we have an event.
|
||||
if ( $data->post_type === $event_plugin->get_post_type() ) {
|
||||
if ( ! self::is_post_disabled( $data ) ) {
|
||||
return $event_plugin::get_activitypub_event_transformer( $data );
|
||||
} else {
|
||||
return new \WP_Error( 'invalid_object', __( 'Invalid object', 'event-bridge-for-activitypub' ) );
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we have a location.
|
||||
if ( $data->post_type === $event_plugin->get_place_post_type() ) {
|
||||
if ( ! self::is_post_disabled( $data ) ) {
|
||||
return $event_plugin::get_activitypub_place_transformer( $data );
|
||||
} else {
|
||||
return new \WP_Error( 'invalid_object', __( 'Invalid object', 'event-bridge-for-activitypub' ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
} elseif ( 'WP_Term' === $object_class ) {
|
||||
foreach ( $this->active_event_plugins as $event_plugin ) {
|
||||
if ( $data->taxonomy === $event_plugin->get_place_taxonomy() ) {
|
||||
return $event_plugin::get_activitypub_place_transformer( $data );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return the default transformer.
|
||||
return $transformer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a post of a post type that is managed by this plugin is disabled for ActivityPub.
|
||||
*
|
||||
* This function checks the visibility of the post and whether it is private or has a password.
|
||||
*
|
||||
* @param mixed $post The post object or ID.
|
||||
*
|
||||
* @return boolean True if the post is disabled, false otherwise.
|
||||
*/
|
||||
public static function is_post_disabled( $post ): bool {
|
||||
$post = \get_post( $post );
|
||||
$disabled = false;
|
||||
|
||||
if ( ! $post ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$visibility = \get_post_meta( $post->ID, 'activitypub_content_visibility', true );
|
||||
|
||||
if (
|
||||
ACTIVITYPUB_CONTENT_VISIBILITY_LOCAL === $visibility ||
|
||||
ACTIVITYPUB_CONTENT_VISIBILITY_PRIVATE === $visibility ||
|
||||
'private' === $post->post_status ||
|
||||
! empty( $post->post_password )
|
||||
) {
|
||||
$disabled = true;
|
||||
}
|
||||
|
||||
return $disabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Activates ActivityPub support for all active event plugins event post-types.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function activate_activitypub_support_for_active_event_plugins(): void {
|
||||
// If someone installs this plugin, we simply enable ActivityPub support for all currently active event post types.
|
||||
$activitypub_supported_post_types = get_option( 'activitypub_support_post_types', array() );
|
||||
foreach ( $this->active_event_plugins as $event_plugin ) {
|
||||
if ( ! in_array( $event_plugin->get_post_type(), $activitypub_supported_post_types, true ) ) {
|
||||
$activitypub_supported_post_types[] = $event_plugin->get_post_type();
|
||||
add_post_type_support( $event_plugin->get_post_type(), 'activitypub' );
|
||||
}
|
||||
}
|
||||
\update_option( 'activitypub_support_post_types', $activitypub_supported_post_types );
|
||||
}
|
||||
|
||||
/**
|
||||
* Activates the Event Bridge for ActivityPub plugin.
|
||||
*
|
||||
* This method handles the activation of the Event Bridge for ActivityPub plugin.
|
||||
*
|
||||
* @since 1.0.0
|
||||
* @see register_activation_hook()
|
||||
* @return void
|
||||
*/
|
||||
public function activate(): void {
|
||||
$this->redetect_active_event_plugins();
|
||||
// Don't allow plugin activation, when the ActivityPub plugin is not activated yet.
|
||||
if ( ! $this->activitypub_plugin_is_active ) {
|
||||
\deactivate_plugins( plugin_basename( EVENT_BRIDGE_FOR_ACTIVITYPUB_PLUGIN_FILE ) );
|
||||
$notice = General_Admin_Notices::get_admin_notice_activitypub_plugin_not_enabled();
|
||||
\wp_die(
|
||||
// @phpstan-ignore-next-line
|
||||
wp_kses( $notice, General_Admin_Notices::ALLOWED_HTML ),
|
||||
'Plugin dependency check',
|
||||
array( 'back_link' => true ),
|
||||
);
|
||||
}
|
||||
|
||||
if ( empty( $this->active_event_plugins ) ) {
|
||||
\deactivate_plugins( plugin_basename( EVENT_BRIDGE_FOR_ACTIVITYPUB_PLUGIN_FILE ) );
|
||||
$notice = General_Admin_Notices::get_admin_notice_no_supported_event_plugin_active();
|
||||
\wp_die(
|
||||
// @phpstan-ignore-next-line
|
||||
wp_kses( $notice, General_Admin_Notices::ALLOWED_HTML ),
|
||||
'Plugin dependency check',
|
||||
array( 'back_link' => true ),
|
||||
);
|
||||
}
|
||||
|
||||
self::activate_activitypub_support_for_active_event_plugins();
|
||||
}
|
||||
|
||||
/**
|
||||
* Maybe (depending on active event plugins) make it possible to querly event terms by `?term_id=<term_id>`.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function maybe_register_term_activitypub_ids(): void {
|
||||
$register_term_query_var = false;
|
||||
|
||||
foreach ( $this->active_event_plugins as $event_plugin ) {
|
||||
if ( $event_plugin::get_place_taxonomy() ) {
|
||||
$register_term_query_var = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $register_term_query_var ) {
|
||||
\add_filter( 'query_vars', array( self::class, 'add_term_query_var' ) );
|
||||
\add_filter( 'activitypub_queried_object', array( $this, 'maybe_detect_event_plugins_location_term' ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the 'activitypub' query for term variable so WordPress won't mangle it.
|
||||
*
|
||||
* @param array $vars The query variables.
|
||||
*
|
||||
* @return array The query variables.
|
||||
*/
|
||||
public static function add_term_query_var( $vars ) {
|
||||
$vars[] = 'term_id';
|
||||
|
||||
return $vars;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the queried object.
|
||||
*
|
||||
* @param \WP_Term|\WP_Post_Type|\WP_Post|\WP_User|\WP_Comment|null $queried_object The queried object.
|
||||
*/
|
||||
public function maybe_detect_event_plugins_location_term( $queried_object ) {
|
||||
if ( $queried_object ) {
|
||||
return $queried_object;
|
||||
}
|
||||
|
||||
$term_id = \get_query_var( 'term_id' );
|
||||
|
||||
if ( $term_id ) {
|
||||
$queried_object = \get_term( $term_id );
|
||||
}
|
||||
|
||||
if ( $queried_object instanceof \WP_Term && $this->is_place_taxonomy_of_active_event_plugin( $queried_object->taxonomy ) ) {
|
||||
return $queried_object;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a taxonomy is an active event plugins location taxonomy.
|
||||
*
|
||||
* @param string $taxonomy The taxonomy.
|
||||
* @return boolean
|
||||
*/
|
||||
private function is_place_taxonomy_of_active_event_plugin( $taxonomy ): bool {
|
||||
foreach ( $this->active_event_plugins as $event_plugin ) {
|
||||
if ( $event_plugin::get_place_taxonomy() === $taxonomy ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the event plugin integration class name used for the event sources feature.
|
||||
*
|
||||
* @return ?string The class name of the event plugin integration class.
|
||||
*/
|
||||
public static function get_event_plugin_integration_used_for_event_sources_feature(): ?string {
|
||||
// Get plugin option.
|
||||
$event_plugin_integration = get_option(
|
||||
'event_bridge_for_activitypub_integration_used_for_event_sources_feature',
|
||||
self::get_default_integration_class_name_used_for_event_sources_feature()
|
||||
);
|
||||
|
||||
// Exit if event sources are not active or no plugin is specified.
|
||||
if ( empty( $event_plugin_integration ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Validate if setting is actual existing class.
|
||||
if ( ! class_exists( $event_plugin_integration ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $event_plugin_integration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the transmogrifier class.
|
||||
*
|
||||
* Retrieves the appropriate transmogrifier class based on the active event plugins and settings.
|
||||
*
|
||||
* @return ?string The transmogrifier class name or null if not available.
|
||||
*/
|
||||
public static function get_transmogrifier(): ?string {
|
||||
$event_plugin_integration = self::get_event_plugin_integration_used_for_event_sources_feature();
|
||||
|
||||
if ( ! $event_plugin_integration ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Validate if get_transformer method exists in event plugin integration.
|
||||
if ( ! method_exists( $event_plugin_integration, 'get_transmogrifier' ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$transmogrifier = $event_plugin_integration::get_transmogrifier();
|
||||
|
||||
return $transmogrifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full class name of the first event plugin integration that is active and supports the event source feature.
|
||||
*
|
||||
* @return string The full class name of the event plugin integration.
|
||||
*/
|
||||
public static function get_default_integration_class_name_used_for_event_sources_feature(): string {
|
||||
$setup = self::get_instance();
|
||||
|
||||
$event_plugin_integrations = $setup->get_active_event_plugins();
|
||||
foreach ( $event_plugin_integrations as $event_plugin_integration ) {
|
||||
if ( $event_plugin_integration instanceof Feature_Event_Sources ) {
|
||||
return get_class( $event_plugin_integration );
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
/**
|
||||
* File responsible for defining the event category strings.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
define(
|
||||
'EVENT_BRIDGE_FOR_ACTIVITYPUB_EVENT_CATEGORIES',
|
||||
array(
|
||||
'ARTS' => __( 'Arts', 'event-bridge-for-activitypub' ),
|
||||
'BOOK_CLUBS' => __( 'Book clubs', 'event-bridge-for-activitypub' ),
|
||||
'BUSINESS' => __( 'Business', 'event-bridge-for-activitypub' ),
|
||||
'CAUSES' => __( 'Causes', 'event-bridge-for-activitypub' ),
|
||||
'COMEDY' => __( 'Comedy', 'event-bridge-for-activitypub' ),
|
||||
'CRAFTS' => __( 'Crafts', 'event-bridge-for-activitypub' ),
|
||||
'FOOD_DRINK' => __( 'Food & Drink', 'event-bridge-for-activitypub' ),
|
||||
'HEALTH' => __( 'Health', 'event-bridge-for-activitypub' ),
|
||||
'MUSIC' => __( 'Music', 'event-bridge-for-activitypub' ),
|
||||
'AUTO_BOAT_AIR' => __( 'Auto, boat and air', 'event-bridge-for-activitypub' ),
|
||||
'COMMUNITY' => __( 'Community', 'event-bridge-for-activitypub' ),
|
||||
'FAMILY_EDUCATION' => __( 'Family & Education', 'event-bridge-for-activitypub' ),
|
||||
'FASHION_BEAUTY' => __( 'Fashion & Beauty', 'event-bridge-for-activitypub' ),
|
||||
'FILM_MEDIA' => __( 'Film & Media', 'event-bridge-for-activitypub' ),
|
||||
'GAMES' => __( 'Games', 'event-bridge-for-activitypub' ),
|
||||
'LANGUAGE_CULTURE' => __( 'Language & Culture', 'event-bridge-for-activitypub' ),
|
||||
'LEARNING' => __( 'Learning', 'event-bridge-for-activitypub' ),
|
||||
'LGBTQ' => __( 'LGBTQ', 'event-bridge-for-activitypub' ),
|
||||
'MOVEMENTS_POLITICS' => __( 'Movements and politics', 'event-bridge-for-activitypub' ),
|
||||
'NETWORKING' => __( 'Networking', 'event-bridge-for-activitypub' ),
|
||||
'PARTY' => __( 'Party', 'event-bridge-for-activitypub' ),
|
||||
'PERFORMING_VISUAL_ARTS' => __( 'Performing & Visual Arts', 'event-bridge-for-activitypub' ),
|
||||
'PETS' => __( 'Pets', 'event-bridge-for-activitypub' ),
|
||||
'PHOTOGRAPHY' => __( 'Photography', 'event-bridge-for-activitypub' ),
|
||||
'OUTDOORS_ADVENTURE' => __( 'Outdoors & Adventure', 'event-bridge-for-activitypub' ),
|
||||
'SPIRITUALITY_RELIGION_BELIEFS' => __( 'Spirituality, Religion & Beliefs', 'event-bridge-for-activitypub' ),
|
||||
'SCIENCE_TECH' => __( 'Science & Tech', 'event-bridge-for-activitypub' ),
|
||||
'SPORTS' => __( 'Sports', 'event-bridge-for-activitypub' ),
|
||||
'THEATRE' => __( 'Theatre', 'event-bridge-for-activitypub' ),
|
||||
'MEETING' => __( 'Meeting', 'event-bridge-for-activitypub' ), // Default value in federation.
|
||||
'DEFAULT' => __( 'Default', 'event-bridge-for-activitypub' ), // Internal default for overrides.
|
||||
),
|
||||
);
|
@ -0,0 +1,109 @@
|
||||
<?php
|
||||
/**
|
||||
* Event Organiser.
|
||||
*
|
||||
* Defines all the necessary meta information and methods for the integration
|
||||
* of the WordPress "Event Organiser" plugin.
|
||||
*
|
||||
* @link https://wordpress.org/plugins/event-organiser/
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\Integrations;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Activitypub\Query;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Event\Event_Organiser as Event_Organiser_Transformer;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Place\Event_Organiser as Event_Organiser_Place_Transformer;
|
||||
|
||||
/**
|
||||
* Event Organiser.
|
||||
*
|
||||
* Defines all the necessary meta information and methods for the integration
|
||||
* of the WordPress "Event Organiser" plugin.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class Event_Organiser extends Event_Plugin_Integration {
|
||||
/**
|
||||
* Returns the full plugin file.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_relative_plugin_file(): string {
|
||||
return 'event-organiser/event-organiser.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the event post type of the plugin.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_post_type(): string {
|
||||
return 'event';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the IDs of the admin pages of the plugin.
|
||||
*
|
||||
* @return array The settings page urls.
|
||||
*/
|
||||
public static function get_settings_pages(): array {
|
||||
return array( 'event-organiser' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the taxonomy used for the plugin's event categories.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_event_category_taxonomy(): string {
|
||||
return 'event-category';
|
||||
}
|
||||
|
||||
/**
|
||||
* In case an event plugin uses a custom taxonomy for storing locations/venues return it here.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_place_taxonomy() {
|
||||
return 'event-venue';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ActivityPub transformer for a Event_Organiser event post.
|
||||
*
|
||||
* @param \WP_Post $post The WordPress post object of the Event.
|
||||
* @return Event_Organiser_Transformer
|
||||
*/
|
||||
public static function get_activitypub_event_transformer( $post ): Event_Organiser_Transformer {
|
||||
return new Event_Organiser_Transformer( $post, self::get_event_category_taxonomy() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ActivityPub transformer for a Event_Organiser event venue which is stored in a taxonomy.
|
||||
*
|
||||
* @param \WP_Term $term The WordPress Term/Taxonomy of the venue.
|
||||
* @return Event_Organiser_Place_Transformer
|
||||
*/
|
||||
public static function get_activitypub_place_transformer( $term ): Event_Organiser_Place_Transformer {
|
||||
if ( Query::get_instance()->is_activitypub_request() && defined( 'EVENT_ORGANISER_DIR' ) ) {
|
||||
$class_path = constant( EVENT_ORGANISER_DIR ) . 'includes/class-eo-theme-compatability.php';
|
||||
|
||||
if ( file_exists( $class_path ) ) {
|
||||
require_once $class_path;
|
||||
|
||||
// Remove the theme filter which is not needed in ActivityStreams.
|
||||
$eo = \EO_Theme_Compatabilty::get_instance();
|
||||
if ( $eo instanceof \EO_Theme_Compatabilty ) {
|
||||
$eo->remove_filter( 'template_include', PHP_INT_MAX - 1 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new Event_Organiser_Place_Transformer( $term );
|
||||
}
|
||||
}
|
@ -0,0 +1,132 @@
|
||||
<?php
|
||||
/**
|
||||
* Abstract base class for a basic integration of a WordPress event plugin.
|
||||
*
|
||||
* Basic information and methods that each supported event needs for this plugin to work.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\Integrations;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Event\Event as ActivityPub_Event_Transformer;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Place\Base_Post_Place;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Place\Base_Term_Place;
|
||||
use WP_Post;
|
||||
|
||||
require_once EVENT_BRIDGE_FOR_ACTIVITYPUB_PLUGIN_DIR . 'includes/integrations/interface-feature-event-sources.php';
|
||||
|
||||
/**
|
||||
* Abstract base class for a basic integration of a WordPress event plugin.
|
||||
*
|
||||
* Basic information and methods that each supported event needs for this plugin to work.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
abstract class Event_Plugin_Integration {
|
||||
/**
|
||||
* Returns the plugin file relative to the plugins dir.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public static function get_relative_plugin_file(): string;
|
||||
|
||||
/**
|
||||
* Returns the event post type of the plugin.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public static function get_post_type(): string;
|
||||
|
||||
/**
|
||||
* Returns the taxonomy used for the plugin's event categories.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public static function get_event_category_taxonomy(): string;
|
||||
|
||||
/**
|
||||
* Returns the Activitypub transformer for events of the event plugins event post type.
|
||||
*
|
||||
* @param WP_Post $post The WordPress post object of the Event.
|
||||
* @return ActivityPub_Event_Transformer
|
||||
*/
|
||||
abstract public static function get_activitypub_event_transformer( $post ): ActivityPub_Event_Transformer;
|
||||
|
||||
/**
|
||||
* In case an event plugin uses a custom post type for the locations/venues return it here.
|
||||
*
|
||||
* @return ?string
|
||||
*/
|
||||
public static function get_place_post_type() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* In case an event plugin uses a custom taxonomy for storing locations/venues return it here.
|
||||
*
|
||||
* @return ?string
|
||||
*/
|
||||
public static function get_place_taxonomy() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Activitypub transformer for places of the event plugins location post type.
|
||||
*
|
||||
* @param WP_Post $post The WordPress post object of the Event.
|
||||
* @return Base_Post_Place|Base_Term_Place|null
|
||||
*/
|
||||
public static function get_activitypub_place_transformer( $post ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* In case an event plugin used a custom post type for organizers return it here.
|
||||
*
|
||||
* @return ?string
|
||||
*/
|
||||
public static function get_organizer_post_type() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the IDs of the admin pages of the plugin.
|
||||
*
|
||||
* @return array The IDs of one or several admin/settings pages.
|
||||
*/
|
||||
public static function get_settings_pages(): array {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the plugins name from the main plugin-file's top-level-file-comment.
|
||||
*/
|
||||
public static function get_plugin_name(): string {
|
||||
$all_plugins = array_merge( get_plugins(), get_mu_plugins() );
|
||||
if ( isset( $all_plugins[ static::get_relative_plugin_file() ]['Name'] ) ) {
|
||||
return $all_plugins[ static::get_relative_plugin_file() ]['Name'];
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects whether the current screen is a admin page of the event plugin.
|
||||
*/
|
||||
public static function is_plugin_page(): bool {
|
||||
// Get the current page.
|
||||
$screen = get_current_screen();
|
||||
|
||||
// Check if we are on a edit page for the event, or on the settings page of the event plugin.
|
||||
$is_event_plugins_edit_page = 'edit' === $screen->base && static::get_post_type() === $screen->post_type;
|
||||
$is_event_plugins_settings_page = in_array( $screen->id, static::get_settings_pages(), true );
|
||||
|
||||
return $is_event_plugins_edit_page || $is_event_plugins_settings_page;
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
<?php
|
||||
/**
|
||||
* Eventin.
|
||||
*
|
||||
* Defines all the necessary meta information and methods for the integration of the
|
||||
* WordPress plugin "Eventin".
|
||||
*
|
||||
* @link https://wordpress.org/plugins/eventin/
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\Integrations;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Event\Eventin as Eventin_Transformer;
|
||||
|
||||
/**
|
||||
* Eventin.
|
||||
*
|
||||
* Defines all the necessary meta information and methods for the integration of the
|
||||
* WordPress plugin "Eventin".
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class Eventin extends Event_Plugin_Integration {
|
||||
/**
|
||||
* Returns the full plugin file.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_relative_plugin_file(): string {
|
||||
return 'wp-event-solution/eventin.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the event post type of the plugin.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_post_type(): string {
|
||||
return 'etn';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the IDs of the admin pages of the plugin.
|
||||
*
|
||||
* @return array The settings page url.
|
||||
*/
|
||||
public static function get_settings_pages(): array {
|
||||
return array( 'eventin' ); // Base always is wp-admin/admin.php?page=eventin.
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the taxonomy used for the plugin's event categories.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_event_category_taxonomy(): string {
|
||||
return 'etn_category';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ActivityPub transformer for a Eventin event post.
|
||||
*
|
||||
* @param \WP_Post $post The WordPress post object of the Event.
|
||||
* @return Eventin_Transformer
|
||||
*/
|
||||
public static function get_activitypub_event_transformer( $post ): Eventin_Transformer {
|
||||
return new Eventin_Transformer( $post, self::get_event_category_taxonomy() );
|
||||
}
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
<?php
|
||||
/**
|
||||
* EventON – Events Calendar
|
||||
*
|
||||
* Defines all the necessary meta information for the integration of the WordPress event plugin
|
||||
* "EventON – Events Calendar".
|
||||
*
|
||||
* @link https://wordpress.org/plugins/eventon-lite
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\Integrations;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Event\EventOn as EventOn_Event_Transformer;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Place\EventOn as EventOn_Location_Transformer;
|
||||
|
||||
/**
|
||||
* EventON – Events Calendar
|
||||
*
|
||||
* Defines all the necessary meta information for the integration of the WordPress event plugin
|
||||
* "EventON – Events Calendar".
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class EventOn extends Event_Plugin_Integration {
|
||||
/**
|
||||
* Returns the full plugin file.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_relative_plugin_file(): string {
|
||||
return 'eventon-lite/eventon.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the event post type of the plugin.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_post_type(): string {
|
||||
return 'ajde_events';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the IDs of the admin pages of the plugin.
|
||||
*
|
||||
* @return array The settings page urls.
|
||||
*/
|
||||
public static function get_settings_pages(): array {
|
||||
return array( 'admin.php?page=eventon' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the taxonomy used for the plugin's event categories.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_event_category_taxonomy(): string {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ActivityPub transformer for a VS_Event_List event post.
|
||||
*
|
||||
* @param \WP_Post $post The WordPress post object of the Event.
|
||||
* @return EventOn_Event_Transformer
|
||||
*/
|
||||
public static function get_activitypub_event_transformer( $post ): EventOn_Event_Transformer {
|
||||
return new EventOn_Event_Transformer( $post, self::get_event_category_taxonomy() );
|
||||
}
|
||||
|
||||
/**
|
||||
* In case an event plugin uses a custom taxonomy for storing locations/venues return it here.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_place_taxonomy() {
|
||||
return 'event_location';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ActivityPub transformer for a Event_Organiser event venue which is stored in a taxonomy.
|
||||
*
|
||||
* @param \WP_Term $term The WordPress Term/Taxonomy of the venue.
|
||||
* @return EventOn_Location_Transformer
|
||||
*/
|
||||
public static function get_activitypub_place_transformer( $term ): EventOn_Location_Transformer {
|
||||
return new EventOn_Location_Transformer( $term );
|
||||
}
|
||||
}
|
@ -0,0 +1,171 @@
|
||||
<?php
|
||||
/**
|
||||
* EventPrime – Events Calendar, Bookings and Tickets
|
||||
*
|
||||
* @link https://wordpress.org/plugins/eventprime-event-calendar-management/
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\Integrations;
|
||||
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Event\EventPrime as EventPrime_Event_Transformer;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Place\EventPrime as EventPrime_Place_Transformer;
|
||||
use Eventprime_Basic_Functions;
|
||||
|
||||
use function Activitypub\is_activitypub_request;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
/**
|
||||
* This class defines which information is necessary for the EventPrime event plugin.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class EventPrime extends Event_Plugin_Integration {
|
||||
/**
|
||||
* Add filter for the template inclusion.
|
||||
*/
|
||||
public static function init() {
|
||||
// Forcefully enable 'activitypub' post type support for EventPrime, because it is not public and cannot be done in the admin UI.
|
||||
\add_post_type_support( self::get_post_type(), 'activitypub' );
|
||||
\add_filter( 'activitypub_transformer', array( self::class, 'register_activitypub_transformer' ), 10, 3 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the full plugin file.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_relative_plugin_file(): string {
|
||||
return 'eventprime-event-calendar-management/event-prime.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the event post type of the plugin.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_post_type(): string {
|
||||
return 'em_event';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the taxonomy used for storing venues.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_place_taxonomy(): string {
|
||||
return 'em_venue';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the IDs of the admin pages of the plugin.
|
||||
*
|
||||
* @return array The settings page urls.
|
||||
*/
|
||||
public static function get_settings_pages(): array {
|
||||
return array( 'ep-settings' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ActivityPub transformer.
|
||||
*
|
||||
* @param \WP_Post $post The WordPress post object of the Event.
|
||||
* @return EventPrime_Event_Transformer
|
||||
*/
|
||||
public static function get_activitypub_event_transformer( $post ): EventPrime_Event_Transformer {
|
||||
return new EventPrime_Event_Transformer( $post, self::get_event_category_taxonomy() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the taxonomy used for the plugin's event categories.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_event_category_taxonomy(): string {
|
||||
return 'em_event_type';
|
||||
}
|
||||
|
||||
/**
|
||||
* Maybe use the custom transformer for the EventPrime.
|
||||
*
|
||||
* @param mixed $transformer The transformer to use.
|
||||
* @param mixed $data The data to transform.
|
||||
* @param string $object_class The class of the object to transform.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function register_activitypub_transformer( $transformer, $data, $object_class ) {
|
||||
if ( 'WP_Post' !== $object_class ) {
|
||||
return $transformer;
|
||||
}
|
||||
|
||||
$object_type = self::post_contains_eventprime_object( $data );
|
||||
|
||||
if ( 'event' === $object_type ) {
|
||||
$post = get_post( self::get_object_id( $object_type ) );
|
||||
if ( $post && self::get_post_type() === $post->post_type ) {
|
||||
return new EventPrime_Event_Transformer( $post );
|
||||
}
|
||||
}
|
||||
|
||||
if ( 'venue' === $object_type ) {
|
||||
$term = get_term( self::get_object_id( $object_type ) );
|
||||
if ( $term && self::get_place_taxonomy() === $term->taxonomy ) {
|
||||
return new EventPrime_Place_Transformer( $term );
|
||||
}
|
||||
}
|
||||
|
||||
return $transformer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the current post is actually just a shortcode Wrapper linking to an EventPrime event.
|
||||
*
|
||||
* @param \WP_Post $post The WordPress post object.
|
||||
* @return string|bool
|
||||
*/
|
||||
private static function post_contains_eventprime_object( $post ) {
|
||||
if ( 'page' !== $post->post_type ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( '[em_event]' === $post->post_content || '[em_events]' === $post->post_content ) {
|
||||
return 'event';
|
||||
}
|
||||
|
||||
if ( '[em_sites]' === $post->post_content ) {
|
||||
return 'venue';
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the post id for events and term id for venues for an EventPrime event query.
|
||||
*
|
||||
* @param string $type 'event' or 'venue'.
|
||||
* @return bool|int The post ID, or term ID if found, false otherwise.
|
||||
*/
|
||||
private static function get_object_id( $type = 'event' ) {
|
||||
if ( ! in_array( $type, array( 'venue', 'event' ), true ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$event = get_query_var( $type );
|
||||
if ( ! $event ) {
|
||||
if ( ! empty( filter_input( INPUT_GET, $type, FILTER_SANITIZE_FULL_SPECIAL_CHARS ) ) ) {
|
||||
$event = rtrim( filter_input( INPUT_GET, $type, FILTER_SANITIZE_FULL_SPECIAL_CHARS ), '/\\' );
|
||||
}
|
||||
}
|
||||
|
||||
if ( $event ) {
|
||||
$ep_basic_functions = new Eventprime_Basic_Functions();
|
||||
return $ep_basic_functions->ep_get_id_by_slug( $event, "em_{$type}" );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
<?php
|
||||
/**
|
||||
* Events Manager.
|
||||
*
|
||||
* Defines all the necessary meta information and methods for the integration of the
|
||||
* WordPress plugin "Events Manager".
|
||||
*
|
||||
* @link https://wordpress.org/plugins/events-manager/
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\Integrations;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Event\Events_Manager as Events_Manager_Event_Transformer;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Place\Events_Manager as Events_Manager_Place_Transformer;
|
||||
|
||||
|
||||
/**
|
||||
* Events Manager.
|
||||
*
|
||||
* Defines all the necessary meta information and methods for the integration of the
|
||||
* WordPress plugin "Events Manager".
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class Events_Manager extends Event_Plugin_Integration {
|
||||
/**
|
||||
* Returns the full plugin file.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_relative_plugin_file(): string {
|
||||
return 'events-manager/events-manager.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the event post type of the plugin.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_post_type(): string {
|
||||
return defined( 'EM_POST_TYPE_EVENT' ) ? constant( 'EM_POST_TYPE_EVENT' ) : 'event';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the place post type of the plugin.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_place_post_type(): string {
|
||||
return defined( 'EM_POST_TYPE_LOCATION' ) ? constant( 'EM_POST_TYPE_LOCATION' ) : 'location';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Activitypub transformer for places of the event plugins location post type.
|
||||
*
|
||||
* @param \WP_Post $post The WordPress post object of the Event.
|
||||
* @return Events_Manager_Place_Transformer
|
||||
*/
|
||||
public static function get_activitypub_place_transformer( $post ): Events_Manager_Place_Transformer {
|
||||
return new Events_Manager_Place_Transformer( $post );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the IDs of the admin pages of the plugin.
|
||||
*
|
||||
* @return array The settings page urls.
|
||||
*/
|
||||
public static function get_settings_page(): array {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the taxonomy used for the plugin's event categories.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_event_category_taxonomy(): string {
|
||||
return defined( 'EM_TAXONOMY_CATEGORY' ) ? constant( 'EM_TAXONOMY_CATEGORY' ) : 'event-categories';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ActivityPub transformer for a Events_Manager event post.
|
||||
*
|
||||
* @param \WP_Post $post The WordPress post object of the Event.
|
||||
* @return Events_Manager_Event_Transformer
|
||||
*/
|
||||
public static function get_activitypub_event_transformer( $post ): Events_Manager_Event_Transformer {
|
||||
return new Events_Manager_Event_Transformer( $post, self::get_event_category_taxonomy() );
|
||||
}
|
||||
}
|
@ -0,0 +1,143 @@
|
||||
<?php
|
||||
/**
|
||||
* GatherPress.
|
||||
*
|
||||
* Defines all the necessary meta information and methods for the integration
|
||||
* of the WordPress event plugin "GatherPress".
|
||||
*
|
||||
* @link https://wordpress.org/plugins/gatherpress/
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\Integrations;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Event\GatherPress as GatherPress_Transformer;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transmogrifier\GatherPress as GatherPress_Transmogrifier;
|
||||
|
||||
/**
|
||||
* GatherPress.
|
||||
*
|
||||
* Defines all the necessary meta information and methods for the integration
|
||||
* of the WordPress event plugin "GatherPress".
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class GatherPress extends Event_Plugin_Integration implements Feature_Event_Sources {
|
||||
/**
|
||||
* Returns the full plugin file.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_relative_plugin_file(): string {
|
||||
return 'gatherpress/gatherpress.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the event post type of the plugin.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_post_type(): string {
|
||||
return class_exists( '\GatherPress\Core\Event' ) ? \GatherPress\Core\Event::POST_TYPE : 'gatherpress_event';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the IDs of the admin pages of the plugin.
|
||||
*
|
||||
* @return array The settings page urls.
|
||||
*/
|
||||
public static function get_settings_pages(): array {
|
||||
return array( class_exists( '\GatherPress\Core\Utility' ) ? \GatherPress\Core\Utility::prefix_key( 'general' ) : 'gatherpress_general' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the taxonomy used for the plugin's event categories.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_event_category_taxonomy(): string {
|
||||
return class_exists( '\GatherPress\Core\Topic' ) ? \GatherPress\Core\Topic::TAXONOMY : 'gatherpress_topic';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ActivityPub transformer for a GatherPress event post.
|
||||
*
|
||||
* @param \WP_Post $post The WordPress post object of the Event.
|
||||
* @return GatherPress_Transformer
|
||||
*/
|
||||
public static function get_activitypub_event_transformer( $post ): GatherPress_Transformer {
|
||||
return new GatherPress_Transformer( $post, self::get_event_category_taxonomy() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Transmogrifier for GatherPress.
|
||||
*/
|
||||
public static function get_transmogrifier(): string {
|
||||
return GatherPress_Transmogrifier::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of Post IDs of events that have ended.
|
||||
*
|
||||
* @param int $ends_before_time Filter: only get events that ended before that datetime as unix-time.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_cached_remote_events( $ends_before_time ): array {
|
||||
global $wpdb;
|
||||
|
||||
$ends_before_time_string = gmdate( 'Y-m-d H:i:s', $ends_before_time );
|
||||
|
||||
$results = $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
"SELECT DISTINCT {$wpdb->prefix}posts.ID
|
||||
FROM {$wpdb->prefix}posts
|
||||
LEFT JOIN {$wpdb->prefix}gatherpress_events
|
||||
ON {$wpdb->prefix}posts.ID = {$wpdb->prefix}gatherpress_events.post_id
|
||||
LEFT JOIN {$wpdb->prefix}postmeta
|
||||
ON {$wpdb->prefix}posts.ID = {$wpdb->prefix}postmeta.post_id
|
||||
WHERE {$wpdb->prefix}posts.post_type = 'gatherpress_event'
|
||||
AND {$wpdb->prefix}posts.post_status = 'publish'
|
||||
AND {$wpdb->prefix}gatherpress_events.datetime_end_gmt <= %s
|
||||
AND {$wpdb->prefix}postmeta.meta_key = '_event_bridge_for_activitypub_event_source'
|
||||
",
|
||||
$ends_before_time_string
|
||||
),
|
||||
ARRAY_N
|
||||
);
|
||||
|
||||
$post_ids = array_column( $results, 0 );
|
||||
|
||||
return $post_ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Init function: force displaying online event link for federated events.
|
||||
*/
|
||||
public static function init(): void {
|
||||
\add_filter(
|
||||
'gatherpress_force_online_event_link',
|
||||
function ( $force_online_event_link ) {
|
||||
// Get the current post object.
|
||||
$post = get_post();
|
||||
|
||||
// Check if we are in a valid context and the post type is 'gatherpress'.
|
||||
if ( $post && 'gatherpress_event' === $post->post_type ) {
|
||||
// Add your custom logic here to decide whether to force the link.
|
||||
// For example, force it only if a specific meta field exists.
|
||||
if ( get_post_meta( $post->ID, '_event_bridge_for_activitypub_event_source', true ) ) {
|
||||
return true; // Force the online event link.
|
||||
}
|
||||
}
|
||||
|
||||
return $force_online_event_link; // Default behavior.
|
||||
},
|
||||
10,
|
||||
1
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
<?php
|
||||
/**
|
||||
* Modern Events Calendar (Lite)
|
||||
*
|
||||
* Defines all the necessary meta information for the integration of the
|
||||
* WordPress plugin "Modern Events Calendar (Lite)".
|
||||
*
|
||||
* @link https://webnus.net/modern-events-calendar/
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\Integrations;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Event\Modern_Events_Calendar_Lite as Modern_Events_Calendar_Lite_Transformer;
|
||||
|
||||
/**
|
||||
* Modern Events Calendar (Lite)
|
||||
*
|
||||
* Defines all the necessary meta information for the integration of the
|
||||
* WordPress plugin "Modern Events Calendar (Lite)".
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class Modern_Events_Calendar_Lite extends Event_Plugin_Integration {
|
||||
/**
|
||||
* Returns the full plugin file.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_relative_plugin_file(): string {
|
||||
return 'modern-events-calendar-lite/modern-events-calendar-lite.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the event post type of the plugin.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_post_type(): string {
|
||||
// See MEC_feature_events->get_main_post_type().
|
||||
return 'mec-events';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the IDs of the admin pages of the plugin.
|
||||
*
|
||||
* @return array The settings page urls.
|
||||
*/
|
||||
public static function get_settings_pages(): array {
|
||||
return array( 'MEC-settings', 'MEC-support', 'MEC-ix', 'MEC-wizard', 'MEC-addons', 'mec-intro' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the taxonomy used for the plugin's event categories.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_event_category_taxonomy(): string {
|
||||
return 'mec_category';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ActivityPub transformer for a Modern_Events_Calendar_Lite event post.
|
||||
*
|
||||
* @param \WP_Post $post The WordPress post object of the Event.
|
||||
* @return Modern_Events_Calendar_Lite_Transformer
|
||||
*/
|
||||
public static function get_activitypub_event_transformer( $post ): Modern_Events_Calendar_Lite_Transformer {
|
||||
return new Modern_Events_Calendar_Lite_Transformer( $post, self::get_event_category_taxonomy() );
|
||||
}
|
||||
}
|
@ -0,0 +1,155 @@
|
||||
<?php
|
||||
/**
|
||||
* The Events Calendar.
|
||||
*
|
||||
* Defines all the necessary meta information for the integration of the
|
||||
* WordPress plugin "The Events Calendar".
|
||||
*
|
||||
* @link https://wordpress.org/plugins/the-events-calendar/
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\Integrations;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Event\The_Events_Calendar as The_Events_Calendar_Event_Transformer;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Place\The_Events_Calendar as The_Events_Calendar_Place_Transformer;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transmogrifier\The_Events_Calendar as The_Events_Calendar_Transmogrifier;
|
||||
|
||||
/**
|
||||
* The Events Calendar.
|
||||
*
|
||||
* Defines all the necessary meta information for the integration of the
|
||||
* WordPress plugin "The Events Calendar".
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class The_Events_Calendar extends Event_Plugin_Integration implements Feature_Event_Sources {
|
||||
/**
|
||||
* Returns the full plugin file.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_relative_plugin_file(): string {
|
||||
return 'the-events-calendar/the-events-calendar.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the event post type of the plugin.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_post_type(): string {
|
||||
return class_exists( '\Tribe__Events__Main' ) ? \Tribe__Events__Main::POSTTYPE : 'tribe_event';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the taxonomy used for the plugin's event categories.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_event_category_taxonomy(): string {
|
||||
return class_exists( '\Tribe__Events__Main' ) ? \Tribe__Events__Main::TAXONOMY : 'tribe_events_cat';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ActivityPub transformer for a The_Events_Calendar event post.
|
||||
*
|
||||
* @param \WP_Post $post The WordPress post object of the Event.
|
||||
* @return The_Events_Calendar_Event_Transformer
|
||||
*/
|
||||
public static function get_activitypub_event_transformer( $post ): The_Events_Calendar_Event_Transformer {
|
||||
return new The_Events_Calendar_Event_Transformer( $post, self::get_event_category_taxonomy() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the location/venue post type used by tribe.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_place_post_type(): string {
|
||||
return class_exists( '\Tribe__Events__Venue' ) ? \Tribe__Events__Venue::POSTTYPE : 'tribe_venue';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the organizers post type used by tribe.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_organizer_post_type(): string {
|
||||
return class_exists( '\Tribe__Events__Organizer' ) ? \Tribe__Events__Organizer::POSTTYPE : 'tribe_organizer';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ActivityPub transformer for a The_Events_Calendar venue post.
|
||||
*
|
||||
* @param \WP_Post $post The WordPress post object of the venue.
|
||||
* @return The_Events_Calendar_Place_Transformer
|
||||
*/
|
||||
public static function get_activitypub_place_transformer( $post ): The_Events_Calendar_Place_Transformer {
|
||||
return new The_Events_Calendar_Place_Transformer( $post );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the IDs of the admin pages of the plugin.
|
||||
*
|
||||
* @return array The settings page urls.
|
||||
*/
|
||||
public static function get_settings_pages(): array {
|
||||
if ( class_exists( '\Tribe\Events\Admin\Settings' ) ) {
|
||||
$page = \Tribe\Events\Admin\Settings::$settings_page_id;
|
||||
} else {
|
||||
$page = 'tec-events-settings';
|
||||
}
|
||||
return array( $page );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Transmogrifier for The_Events_Calendar.
|
||||
*/
|
||||
public static function get_transmogrifier(): string {
|
||||
return The_Events_Calendar_Transmogrifier::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of Post IDs of events that have ended.
|
||||
*
|
||||
* @param int $ends_before_time Filter to only get events that ended before that datetime as unix-time.
|
||||
*
|
||||
* @return array<int>
|
||||
*/
|
||||
public static function get_cached_remote_events( $ends_before_time ): array {
|
||||
add_filter(
|
||||
'tribe_repository_events_apply_modifier_schema_entry',
|
||||
array( self::class, 'add_is_activitypub_remote_cached_to_query' ),
|
||||
10,
|
||||
1
|
||||
);
|
||||
|
||||
$events = tribe_events()->where( 'ends_before', $ends_before_time )->get_ids();
|
||||
|
||||
remove_filter(
|
||||
'tribe_repository_events_apply_modifier_schema_entry',
|
||||
array( self::class, 'add_is_activitypub_remote_cached_to_query' )
|
||||
);
|
||||
|
||||
return $events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Only show remote cached ActivityPub events in Tribe query.
|
||||
*
|
||||
* @param array $schema_entry The current schema entry.
|
||||
* @return array The modified schema entry.
|
||||
*/
|
||||
public static function add_is_activitypub_remote_cached_to_query( $schema_entry ) {
|
||||
$schema_entry['meta_query']['is-remote-cached'] = array(
|
||||
'key' => '_event_bridge_for_activitypub_event_source',
|
||||
'compare' => 'EXISTS',
|
||||
);
|
||||
return $schema_entry;
|
||||
}
|
||||
}
|
@ -0,0 +1,117 @@
|
||||
<?php
|
||||
/**
|
||||
* VS Events LIst.
|
||||
*
|
||||
* Defines all the necessary meta information for the integration of the WordPress event plugin
|
||||
* "Very Simple Events List".
|
||||
*
|
||||
* @link https://wordpress.org/plugins/very-simple-event-list/
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\Integrations;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Event\VS_Event_List as VS_Event_List_Transformer;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transmogrifier\VS_Event_List as VS_Event_List_Transmogrifier;
|
||||
use WP_Query;
|
||||
|
||||
/**
|
||||
* VS Events LIst.
|
||||
*
|
||||
* Defines all the necessary meta information for the integration of the WordPress event plugin
|
||||
* "Very Simple Events List".
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class VS_Event_List extends Event_Plugin_Integration implements Feature_Event_Sources {
|
||||
/**
|
||||
* Returns the full plugin file.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_relative_plugin_file(): string {
|
||||
return 'very-simple-event-list/vsel.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the event post type of the plugin.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_post_type(): string {
|
||||
return 'event';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the IDs of the admin pages of the plugin.
|
||||
*
|
||||
* @return array The settings page urls.
|
||||
*/
|
||||
public static function get_settings_pages(): array {
|
||||
return array( 'settings_page_vsel' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the taxonomy used for the plugin's event categories.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_event_category_taxonomy(): string {
|
||||
return 'event_cat';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ActivityPub transformer for a VS_Event_List event post.
|
||||
*
|
||||
* @param \WP_Post $post The WordPress post object of the Event.
|
||||
* @return VS_Event_List_Transformer
|
||||
*/
|
||||
public static function get_activitypub_event_transformer( $post ): VS_Event_List_Transformer {
|
||||
return new VS_Event_List_Transformer( $post, self::get_event_category_taxonomy() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Transmogrifier for The_Events_Calendar.
|
||||
*/
|
||||
public static function get_transmogrifier(): string {
|
||||
return VS_Event_List_Transmogrifier::class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of Post IDs of events that have ended.
|
||||
*
|
||||
* @param int $ends_before_time Filter to only get events that ended before that datetime as unix-time.
|
||||
*
|
||||
* @return array<int>
|
||||
*/
|
||||
public static function get_cached_remote_events( $ends_before_time ): array {
|
||||
$args = array(
|
||||
'post_type' => 'event',
|
||||
'posts_per_page' => -1,
|
||||
'fields' => 'ids',
|
||||
'meta_query' => array(
|
||||
'relation' => 'AND',
|
||||
array(
|
||||
'key' => '_event_bridge_for_activitypub_event_source',
|
||||
'compare' => 'EXISTS',
|
||||
),
|
||||
array(
|
||||
'key' => 'event-date',
|
||||
'value' => $ends_before_time,
|
||||
'type' => 'NUMERIC',
|
||||
'compare' => '<',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$query = new WP_Query( $args );
|
||||
|
||||
$post_ids = $query->posts;
|
||||
|
||||
return $post_ids;
|
||||
}
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
/**
|
||||
* WP Event Manager.
|
||||
*
|
||||
* Defines all the necessary meta information for the Integration of the
|
||||
* WordPress event plugin "WP Event Manager".
|
||||
*
|
||||
* @link https://de.wordpress.org/plugins/wp-event-manager
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\Integrations;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Transformer\Event\WP_Event_Manager as WP_Event_Manager_Transformer;
|
||||
|
||||
/**
|
||||
* Interface for a supported event plugin.
|
||||
*
|
||||
* This interface defines which information is necessary for a supported event plugin.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
final class WP_Event_Manager extends Event_Plugin_Integration {
|
||||
/**
|
||||
* Returns the full plugin file.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_relative_plugin_file(): string {
|
||||
return 'wp-event-manager/wp-event-manager.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the event post type of the plugin.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_post_type(): string {
|
||||
return 'event_listing';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the IDs of the admin pages of the plugin.
|
||||
*
|
||||
* @return array The settings page urls.
|
||||
*/
|
||||
public static function get_settings_pages(): array {
|
||||
return array( 'event-manager-settings' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the taxonomy used for the plugin's event categories.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_event_category_taxonomy(): string {
|
||||
return 'event_listing_category';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ActivityPub transformer for a WP_Event_Manager event post.
|
||||
*
|
||||
* @param \WP_Post $post The WordPress post object of the Event.
|
||||
* @return WP_Event_Manager_Transformer
|
||||
*/
|
||||
public static function get_activitypub_event_transformer( $post ): WP_Event_Manager_Transformer {
|
||||
return new WP_Event_Manager_Transformer( $post, self::get_event_category_taxonomy() );
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
/**
|
||||
* Interface for defining Methods needed for the Event Sources feature.
|
||||
*
|
||||
* The Event Sources feature is about following other ActivityPub actors and
|
||||
* importing their events. That means treating them as cache and listing them.
|
||||
* Events should be deleted some time after they have ended.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\Integrations;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
/**
|
||||
* Interface for an event plugin integration that supports the Event Sources feature.
|
||||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
interface Feature_Event_Sources {
|
||||
/**
|
||||
* Returns the full class name of the transmogrifier.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_transmogrifier(): string;
|
||||
|
||||
/**
|
||||
* Retrieves a list of post IDs for cached remote events that have ended.
|
||||
*
|
||||
* Filters the events to include only those that ended before the specified timestamp.
|
||||
*
|
||||
* @param int $ends_before_time Unix timestamp. Only events ending before this time will be included.
|
||||
*
|
||||
* @return int[] List of post IDs for events that match the criteria.
|
||||
*/
|
||||
public static function get_cached_remote_events( $ends_before_time ): array;
|
||||
}
|
@ -0,0 +1,243 @@
|
||||
<?php
|
||||
/**
|
||||
* Event Sources Table-Class file.
|
||||
*
|
||||
* This table display the event sources (=followed ActivityPub actors) that are used for
|
||||
* importing (caching and displaying) remote events to the WordPress site.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
namespace Event_Bridge_For_ActivityPub\Table;
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use WP_List_Table;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Collection\Event_Sources as Event_Sources_Collection;
|
||||
use Event_Bridge_For_ActivityPub\ActivityPub\Model\Event_Source;
|
||||
|
||||
if ( ! \class_exists( '\WP_List_Table' ) ) {
|
||||
// @phpstan-ignore-next-line
|
||||
require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Event Sources Table-Class.
|
||||
*/
|
||||
class Event_Sources extends WP_List_Table {
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
parent::__construct(
|
||||
array(
|
||||
'singular' => \__( 'Event Source', 'event-bridge-for-activitypub' ),
|
||||
'plural' => \__( 'Event Sources', 'event-bridge-for-activitypub' ),
|
||||
'ajax' => true,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get columns.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_columns(): array {
|
||||
return array(
|
||||
'cb' => '<input type="checkbox" />',
|
||||
'icon' => \__( 'Icon', 'event-bridge-for-activitypub' ),
|
||||
'name' => \__( 'Name', 'event-bridge-for-activitypub' ),
|
||||
'accepted' => \__( 'Follow', 'event-bridge-for-activitypub' ),
|
||||
'url' => \__( 'URL', 'event-bridge-for-activitypub' ),
|
||||
'published' => \__( 'Followed', 'event-bridge-for-activitypub' ),
|
||||
'modified' => \__( 'Last updated', 'event-bridge-for-activitypub' ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns sortable columns.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_sortable_columns(): array {
|
||||
return array(
|
||||
'name' => array( 'name', true ),
|
||||
'modified' => array( 'modified', false ),
|
||||
'published' => array( 'published', false ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare items.
|
||||
*/
|
||||
public function prepare_items(): void {
|
||||
$columns = $this->get_columns();
|
||||
$hidden = array();
|
||||
|
||||
$this->process_action();
|
||||
$this->_column_headers = array( $columns, $hidden, $this->get_sortable_columns() );
|
||||
|
||||
$page_num = $this->get_pagenum();
|
||||
$per_page = 20;
|
||||
|
||||
$args = array();
|
||||
|
||||
// phpcs:disable WordPress.Security.NonceVerification.Recommended
|
||||
if ( isset( $_GET['orderby'] ) ) {
|
||||
$args['orderby'] = sanitize_text_field( wp_unslash( $_GET['orderby'] ) );
|
||||
}
|
||||
|
||||
if ( isset( $_GET['order'] ) ) {
|
||||
$args['order'] = sanitize_text_field( wp_unslash( $_GET['order'] ) );
|
||||
}
|
||||
|
||||
if ( isset( $_GET['s'] ) && isset( $_REQUEST['_wpnonce'] ) ) {
|
||||
$nonce = sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) );
|
||||
if ( wp_verify_nonce( $nonce, 'bulk-' . $this->_args['plural'] ) ) {
|
||||
$args['s'] = sanitize_text_field( wp_unslash( $_GET['s'] ) );
|
||||
}
|
||||
}
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Recommended
|
||||
|
||||
$event_sources = Event_Sources_Collection::get_event_sources_with_count( $per_page, $page_num, $args );
|
||||
$total_count = $event_sources['total'];
|
||||
|
||||
$this->items = array();
|
||||
$this->set_pagination_args(
|
||||
array(
|
||||
'total_items' => $total_count,
|
||||
'total_pages' => (int) ceil( $total_count / $per_page ),
|
||||
'per_page' => $per_page,
|
||||
)
|
||||
);
|
||||
|
||||
foreach ( $event_sources['actors'] as $event_source_post_id => $event_source_activitypub_id ) {
|
||||
$event_source = Event_Source::get_by_id( (int) $event_source_post_id );
|
||||
|
||||
if ( ! $event_source || ! in_array( $event_source->get_status(), array( 'publish', 'pending' ), true ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$item = array(
|
||||
'icon' => esc_attr( $event_source->get_icon_url() ),
|
||||
'name' => esc_attr( $event_source->get_name() ),
|
||||
'url' => esc_attr( $event_source_activitypub_id ),
|
||||
'accepted' => esc_attr( get_post_meta( $event_source->get__id(), '_event_bridge_for_activitypub_accept_of_follow', true ) ),
|
||||
'identifier' => esc_attr( $event_source_post_id ),
|
||||
'published' => esc_attr( $event_source->get_published() ),
|
||||
'modified' => esc_attr( $event_source->get_updated() ),
|
||||
);
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns bulk actions.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_bulk_actions(): array {
|
||||
return array(
|
||||
'delete' => __( 'Delete', 'event-bridge-for-activitypub' ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Column default.
|
||||
*
|
||||
* @param array $item Item.
|
||||
* @param string $column_name Column name.
|
||||
* @return string
|
||||
*/
|
||||
public function column_default( $item, $column_name ) {
|
||||
if ( ! array_key_exists( $column_name, $item ) ) {
|
||||
return __( 'None', 'event-bridge-for-activitypub' );
|
||||
}
|
||||
return $item[ $column_name ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Column avatar.
|
||||
*
|
||||
* @param array $item Item.
|
||||
* @return string
|
||||
*/
|
||||
public function column_icon( $item ): string {
|
||||
return sprintf(
|
||||
'<img src="%s" width="25px;" />',
|
||||
$item['icon']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Column url.
|
||||
*
|
||||
* @param array $item Item.
|
||||
* @return string
|
||||
*/
|
||||
public function column_url( $item ): string {
|
||||
return sprintf(
|
||||
'<a href="%s" target="_blank">%s</a>',
|
||||
esc_url( $item['url'] ),
|
||||
$item['url']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Column cb.
|
||||
*
|
||||
* @param array $item Item.
|
||||
* @return string
|
||||
*/
|
||||
public function column_cb( $item ): string {
|
||||
return sprintf( '<input type="checkbox" name="event_sources[]" value="%s" />', esc_attr( $item['identifier'] ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Column action.
|
||||
*
|
||||
* @param array $item Item.
|
||||
* @return string
|
||||
*/
|
||||
public function column_accepted( $item ): string {
|
||||
if ( $item['accepted'] ) {
|
||||
return esc_html__( 'Accepted', 'event-bridge-for-activitypub' );
|
||||
} else {
|
||||
return esc_html__( 'Pending', 'event-bridge-for-activitypub' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process action.
|
||||
*/
|
||||
public function process_action(): void {
|
||||
if ( ! isset( $_REQUEST['event_sources'] ) || ! isset( $_REQUEST['_wpnonce'] ) ) {
|
||||
return;
|
||||
}
|
||||
$nonce = sanitize_text_field( wp_unslash( $_REQUEST['_wpnonce'] ) );
|
||||
if ( ! wp_verify_nonce( $nonce, 'bulk-' . $this->_args['plural'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$event_sources = $_REQUEST['event_sources']; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput
|
||||
|
||||
if ( ! is_array( $event_sources ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( 'delete' === $this->current_action() ) {
|
||||
foreach ( $event_sources as $event_source ) {
|
||||
Event_Sources_Collection::remove_event_source( absint( $event_source ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
157
wp-content/plugins/event-bridge-for-activitypub/readme.txt
Normal file
@ -0,0 +1,157 @@
|
||||
=== Event Bridge for ActivityPub ===
|
||||
Contributors: andremenrath, pfefferle
|
||||
Tags: events, fediverse, activitypub, calendar
|
||||
Requires at least: 6.5
|
||||
Tested up to: 6.8
|
||||
Stable tag: 1.1.0
|
||||
Requires PHP: 7.4
|
||||
License: AGPL-3.0-or-later
|
||||
License URI: https://www.gnu.org/licenses/agpl-3.0.html
|
||||
Integrating popular event plugins with the ActivityPub plugin.
|
||||
|
||||
== Description ==
|
||||
|
||||
Make your events more discoverable, expand your reach effortlessly while being independent of other (commercial) platforms, and be a part of the growing decentralized web (the Fediverse). With the Event Bridge for ActivityPub Plugin for WordPress, your events can be automatically followed, aggregated and displayed across decentralized platforms like [Mastodon](https://joinmastodon.org) or [Gancio](https://gancio.org), without any extra work. Forget the hassle of managing multiple social media accounts just to keep your audience informed.
|
||||
|
||||
This plugin is not an event managing plugin but an add-on to popular event plugins. It extends their functionality to fully support the [ActivityPub plugin](https://wordpress.org/plugins/activitypub/). With the ActivityPub plugin people can follow your website directly and engage with your events just as they would on social media: liking, boosting and even commenting if you enable it. You retain full ownership of your content. By integrating into your existing setup, it ensures no extra work is needed while enhancing your events' visibility across the web.
|
||||
|
||||
= Supported Event Plugins =
|
||||
|
||||
Full support (including importing events from the Fediverse):
|
||||
|
||||
* [The Events Calendar](https://de.wordpress.org/plugins/the-events-calendar/)
|
||||
* [VS Event List](https://de.wordpress.org/plugins/very-simple-event-list/)
|
||||
* [GatherPress](https://gatherpress.org/)
|
||||
|
||||
Basic support (outgoing events):
|
||||
|
||||
* [Events Manager](https://de.wordpress.org/plugins/events-manager/)
|
||||
* [WP Event Manager](https://de.wordpress.org/plugins/wp-event-manager/)
|
||||
* [Eventin](https://de.wordpress.org/plugins/wp-event-solution/)
|
||||
* [Modern Events Calendar Lite](https://webnus.net/modern-events-calendar/)
|
||||
* [Event Organiser](https://wordpress.org/plugins/event-organiser/)
|
||||
* [EventPrime – Events Calendar, Bookings and Tickets](https://wordpress.org/plugins/eventprime-event-calendar-management/)
|
||||
* [EventON – Events Calendar](https://wordpress.org/plugins/eventon-lite/)
|
||||
|
||||
= How It Works =
|
||||
|
||||
With the Event Bridge for ActivityPub WordPress plugin, sharing your events is effortless and automatic! Once you create an event on your WordPress site, it is seamlessly shared across the decentralized web using the ActivityPub protocol.
|
||||
|
||||
[vimeo https://vimeo.com/1043105544 ]
|
||||
|
||||
Your events can be automatically delivered to platforms that fully support events, such as [Mobilizon](https://joinmobilizon.org/), [Gancio](https://gancio.org), [Friendica](https://friendi.ca), [Hubzilla](https://hubzilla.org), and [Pleroma](https://pleroma.social/). These platforms create public event calendars by pulling in events from various sources, including your website. Any updates you make to your events are synced across these platforms—so you only need to manage your events on your own site, with no extra work required.
|
||||
|
||||
[vimeo https://vimeo.com/1043104445 ]
|
||||
|
||||
Even platforms that don't yet fully support events, like [Mastodon](https://joinmastodon.org), will still receive a detailed, well-composed summary of your event. The Event Federation plugin ensures that users from those platforms are provided with all important information about an event.
|
||||
|
||||
= Features for Your WordPress Events and the Fediverse =
|
||||
|
||||
**ActivityPub-Enabled Event Sharing:** Your WordPress events are now compatible with the Fediverse, using the ActivityStreams format. This means your events can be easily discovered and followed by users on platforms like Mastodon and other ActivityPub-compatible services.
|
||||
|
||||
**Automatic Event Summaries:** When your event is shared on the Fediverse, platforms like Mastodon that don't fully support events will display a brief HTML summary of key details — such as the event's title, start time, and location. This ensures that even if someone can't view the full event on their platform, they still get the important info at a glance, with a link to your WordPress event page. Advanced users can create custom summaries via a set of shortcodes.
|
||||
|
||||
**Improved Event Discoverability:** Your custom event categories are mapped to a set of default categories used in the Fediverse, helping your events reach a wider audience. This improves the chances that users searching for similar events on other platforms will find yours.
|
||||
|
||||
**Event Reminders for Your Followers:** Often, events are planned well in advance. To keep your followers informed right in time, you can set up reminders that are supposed to trigger the events showing up in their timelines right before the event starts. At the moment this reminder is implemented as a self-boost of your original event post. While this feature may behave differently across various platforms, we are working on a more robust solution that will let you schedule dedicated reminder notes that appear in all followers' timelines.
|
||||
|
||||
**External Event Sources:** This functionality is only available for a subset of the supported event plugins. It enables your WordPress site to act as a hub for displaying events from other ActivityPub profiles, aggregating them into a cohesive calendar view.
|
||||
|
||||
== Installation ==
|
||||
|
||||
This plugin depends on the [ActivityPub plugin](https://wordpress.org/plugins/activitypub/). Additionally, you need to use one of the supported event Plugins.
|
||||
|
||||
= Supported Event Plugins =
|
||||
|
||||
Full support (including importing events from the Fediverse):
|
||||
|
||||
* [The Events Calendar](https://de.wordpress.org/plugins/the-events-calendar/)
|
||||
* [VS Event List](https://de.wordpress.org/plugins/very-simple-event-list/)
|
||||
* [GatherPress](https://gatherpress.org/)
|
||||
|
||||
Basic support (outgoing events):
|
||||
|
||||
* [Events Manager](https://de.wordpress.org/plugins/events-manager/)
|
||||
* [WP Event Manager](https://de.wordpress.org/plugins/wp-event-manager/)
|
||||
* [Eventin](https://de.wordpress.org/plugins/wp-event-solution/)
|
||||
* [Modern Events Calendar Lite](https://webnus.net/modern-events-calendar/)
|
||||
* [Event Organiser](https://wordpress.org/plugins/event-organiser/)
|
||||
* [EventPrime – Events Calendar, Bookings and Tickets](https://wordpress.org/plugins/eventprime-event-calendar-management/)
|
||||
|
||||
= Configuration =
|
||||
|
||||
If you're new to the [ActivityPub plugin](https://wordpress.org/plugins/activitypub/), it's recommended to spend a few minutes reading through its documentation to familiarize yourself with its setup and functionality.
|
||||
|
||||
== Frequently Asked Questions ==
|
||||
|
||||
= Do I need to install another event plugin to use the Event Federation Plugin? =
|
||||
|
||||
Yes, this plugin works as an add-on and requires both the ActivityPub plugin and a supported event plugin such as The Events Calendar, VS Event List, or Events Manager to manage your events. It just fills the missing gap between event plugins and the [ActivityPub plugin](https://wordpress.org/plugins/activitypub/).
|
||||
|
||||
= What platforms can follow my events? =
|
||||
|
||||
Your events can be followed on platforms that support ActivityPub like [Mobilizon](https://joinmobilizon.org/), [Gancio](https://gancio.org), [Friendica](https://friendi.ca), [Hubzilla](https://hubzilla.org), and [Pleroma](https://pleroma.social/). Even other applications like [Mastodon](https://joinmastodon.org), which don't fully support events yet, will display all important information about the events.
|
||||
|
||||
= Why does Mastodon not show any updates? ==
|
||||
|
||||
Mastodon does not yet handle updates of `Event` objects. See the related tracking issue [#31114](https://github.com/mastodon/mastodon/issues/31114).
|
||||
|
||||
= How much extra work is required to maintain my events across the decentralized Web? =
|
||||
|
||||
None! Once the plugin is set up, your events are automatically sent to all connected platforms or account that follow you (your Website). Any updates you make to your events are synced without additional effort.
|
||||
|
||||
= Can I still use social media to promote my events? =
|
||||
|
||||
Yes, you can still use traditional social media if you wish. However, this plugin helps reduce reliance on commercial platforms by connecting your events to the decentralized Fediverse.
|
||||
|
||||
= Will this plugin work if I don't use the ActivityPub plugin? =
|
||||
|
||||
No, the Event Federation Plugin depends on the [ActivityPub plugin](https://wordpress.org/plugins/activitypub/) to deliver your events across decentralized platforms, so it's essential to have it installed and configured.
|
||||
|
||||
= My event plugin is not supported, what can I do? =
|
||||
|
||||
If you know about coding have a look at the documentation of how to add your plugin or open an [issue](https://codeberg.org/Event-Federation/wordpress-event-bridge-for-activitypub/issues), if we can spare some free hours we might add it.
|
||||
|
||||
= What if I experience problems? =
|
||||
|
||||
We're always interested in your feedback. Feel free to reach out to us via [E-Mail](https://event-federation.eu/contact/) or create an [issue](https://codeberg.org/Event-Federation/wordpress-event-bridge-for-activitypub/issues).
|
||||
|
||||
== Acknowledgement ==
|
||||
|
||||
The development of this WordPress plugin was funded through the [NGI0 Entrust](https://NLnet.nl/entrust) Fund, a fund established by [NLnet](https://nlnet.nl) with financial support from the European Commission's [Next Generation Internet](https://ngi.eu) programme, under the aegis of [Communications Networks, Content and Technology](https://commission.europa.eu/about-european-commission/departments-and-executive-agencies/communications-networks-content-and-technology_en) under grant agreement number 101069594.
|
||||
|
||||
== Changelog ==
|
||||
|
||||
= [1.1.0] - 2025-04-12 =
|
||||
|
||||
* Added: Basic support for Starter Kits
|
||||
* Fixed: Uncatched error in following process (issue #145)
|
||||
* Fixed: Compatibility with ActivityPub plugin version 5.7.0
|
||||
|
||||
= [1.0.0] - 2025-02-11 =
|
||||
|
||||
* Added: Support for the EventPrime event plugin
|
||||
* Added: Event self-announce feature at configurable time before event starts
|
||||
* Added: Blueprint (Preview via WordPress Playground)
|
||||
* Added: Event Sources feature: cache and list events from remote ActivityPub profiles on your site
|
||||
* Added: Custom ActivityPub preview
|
||||
* Added: Admin setting to enfore sending summary of events as plain text
|
||||
* Changed: Now depends on ActivityPub plugin version greater than 5.1.0
|
||||
* Fixed: The Events Calendar date times when using the Gutenberg editor
|
||||
* Fixed: Improved admin UI for event-category mapping
|
||||
|
||||
= [0.3.5] - 2025-01-03 =
|
||||
|
||||
* Fixed: Images of Acknowledgements in Admin UI
|
||||
|
||||
= [0.3.4] - 2024-12-21 =
|
||||
|
||||
* Initial release on https://wordpress.org/
|
||||
|
||||
== Contributing ==
|
||||
|
||||
This plugin is free software, and contributions of all kinds are welcome! Whether it's reporting issues, submitting improvements, or suggesting new features, your input helps make the plugin better for everyone.
|
||||
|
||||
Please review our [Contribution Guidelines](https://codeberg.org/Event-Federation/wordpress-event-bridge-for-activitypub/src/branch/main/CONTRIBUTING.md) to get started. The source code is hosted on [Codeberg](https://codeberg.org/Event-Federation/wordpress-event-bridge-for-activitypub), where you can open issues and submit pull requests.
|
||||
|
||||
Thank you for contributing!
|
@ -0,0 +1,45 @@
|
||||
<?php
|
||||
/**
|
||||
* Template for the header and navigation of the admin pages.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
/* @var array $args Template arguments. */
|
||||
$args = wp_parse_args(
|
||||
$args,
|
||||
array(
|
||||
'welcome' => '',
|
||||
'settings' => '',
|
||||
'event-sources' => '',
|
||||
)
|
||||
);
|
||||
?>
|
||||
|
||||
<div class="event-bridge-for-activitypub-settings-header">
|
||||
<!-- <div class="event-bridge-for-activitypub-settings-title-section">
|
||||
<h1><?php \esc_html_e( 'Event Bridge for ActivityPub', 'event-bridge-for-activitypub' ); ?></h1>
|
||||
</div> -->
|
||||
|
||||
<nav class="event-bridge-for-activitypub-settings-tabs-wrapper" aria-label="<?php \esc_attr_e( 'Tertiary menu', 'event-bridge-for-activitypub' ); ?>">
|
||||
<a href="<?php echo \esc_url( admin_url( 'options-general.php?page=activitypub&tab=event-bridge-for-activitypub' ) ); ?>" class="event-bridge-for-activitypub-settings-tab <?php echo \esc_attr( $args['welcome'] ); ?>">
|
||||
<?php \esc_html_e( 'Overview & Status', 'event-bridge-for-activitypub' ); ?>
|
||||
</a>
|
||||
|
||||
<a href="<?php echo \esc_url( admin_url( 'options-general.php?page=activitypub&tab=event-bridge-for-activitypub&subpage=settings' ) ); ?>" class="event-bridge-for-activitypub-settings-tab <?php echo \esc_attr( $args['settings'] ); ?>">
|
||||
<?php \esc_html_e( 'Event Bridge Settings', 'event-bridge-for-activitypub' ); ?>
|
||||
</a>
|
||||
|
||||
<a href="<?php echo \esc_url( admin_url( 'options-general.php?page=activitypub&tab=event-bridge-for-activitypub&subpage=event-sources' ) ); ?>" class="event-bridge-for-activitypub-settings-tab <?php echo \esc_attr( $args['event-sources'] ); ?>">
|
||||
<?php \esc_html_e( 'Federated Event Sources', 'event-bridge-for-activitypub' ); ?>
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
|
||||
<?php
|
||||
\Event_Bridge_For_ActivityPub\Admin\Settings_Page::do_settings_page();
|
@ -0,0 +1,236 @@
|
||||
<?php
|
||||
/**
|
||||
* Event Sources management page for the ActivityPub Event Bridge.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Event_Bridge_For_ActivityPub\Setup;
|
||||
|
||||
\load_template(
|
||||
__DIR__ . '/../menu.php',
|
||||
true,
|
||||
array(
|
||||
'event-sources' => 'active',
|
||||
)
|
||||
);
|
||||
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$activitypub_plugin_is_active = Setup::get_instance()->is_activitypub_plugin_active();
|
||||
|
||||
\get_option( 'event_bridge_for_activitypub_event_sources_active', false );
|
||||
|
||||
if ( ! isset( $args ) || ! array_key_exists( 'supports_event_sources', $args ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$event_plugins_supporting_event_sources = $args['supports_event_sources'];
|
||||
|
||||
$event_sources_active = \get_option( 'event_bridge_for_activitypub_event_sources_active', false );
|
||||
$cache_retention_period = \get_option( 'event_bridge_for_activitypub_event_source_cache_retention', DAY_IN_SECONDS );
|
||||
|
||||
?>
|
||||
<?php if ( $activitypub_plugin_is_active ) { ?>
|
||||
<div class="activitypub-settings hide-if-no-js">
|
||||
<form method="post" action="options.php">
|
||||
<?php \settings_fields( 'event-bridge-for-activitypub_event-sources' ); ?>
|
||||
<div class="box">
|
||||
<h2><?php \esc_html_e( 'Event Sources', 'event-bridge-for-activitypub' ); ?></h2>
|
||||
<p id="event-sources-description"><?php esc_html_e( 'This feature allows your WordPress site to retrieve and display events from external sources via ActivityPub. Once enabled, you can add any ActivityPub account as a source of events by following that profile. These events will be cached on your site and seamlessly integrated into your existing event calendar, creating a unified view of events from both internal and external sources.', 'event-bridge-for-activitypub' ); ?></p>
|
||||
<?php
|
||||
if ( ! \Activitypub\is_user_type_disabled( 'blog' ) && count( $event_plugins_supporting_event_sources ) ) {
|
||||
?>
|
||||
<table class="form-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<label for="event_bridge_for_activitypub_event_sources_active"><?php \esc_html_e( 'Enable External Event Sources', 'event-bridge-for-activitypub' ); ?></label>
|
||||
</th>
|
||||
<td>
|
||||
<input
|
||||
type="checkbox"
|
||||
name="event_bridge_for_activitypub_event_sources_active"
|
||||
id="event_bridge_for_activitypub_event_sources_active"
|
||||
aria-describedby="event-sources-description"
|
||||
value="1"
|
||||
<?php echo \checked( $event_sources_active ); ?>
|
||||
>
|
||||
</td>
|
||||
</tr>
|
||||
<?php
|
||||
if ( $event_sources_active ) {
|
||||
?>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<label for="event_bridge_for_activitypub_integration_used_for_event_sources_feature"><?php \esc_html_e( 'Event Plugin', 'event-bridge-for-activitypub' ); ?></label>
|
||||
</th>
|
||||
<td>
|
||||
<select
|
||||
name="event_bridge_for_activitypub_integration_used_for_event_sources_feature"
|
||||
id="event_bridge_for_activitypub_integration_used_for_event_sources_feature"
|
||||
value="gatherpress"
|
||||
aria-describedby="event-sources-used-plugin-description"
|
||||
>
|
||||
<?php
|
||||
foreach ( $event_plugins_supporting_event_sources as $event_plugin_class_name => $event_plugin_name ) {
|
||||
echo '<option value="' . esc_attr( $event_plugin_class_name ) . '" ' . selected( $event_plugin_class_name, Setup::get_event_plugin_integration_used_for_event_sources_feature(), true ) . '>' . esc_attr( $event_plugin_name ) . '</option>';
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
<p id="event-sources-used-plugin-description"><?php esc_html_e( 'In case you have multiple event plugins installed you might choose which event plugin is utilized.', 'event-bridge-for-activitypub' ); ?></p>
|
||||
</td>
|
||||
<tr>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<label for="event_bridge_for_activitypub_event_source_cache"><?php \esc_html_e( 'Retention Period for External Events', 'event-bridge-for-activitypub' ); ?></label>
|
||||
</th>
|
||||
<td>
|
||||
<select
|
||||
name="event_bridge_for_activitypub_event_source_cache_retention"
|
||||
id="event_bridge_for_activitypub_event_source_cache_retention"
|
||||
value="0"
|
||||
aria-describedby="event_bridge_for_activitypub_event-sources-cache-clear-time-frame"
|
||||
>
|
||||
<?php
|
||||
$choices = array(
|
||||
0 => __( 'Immediately', 'event-bridge-for-activitypub' ),
|
||||
DAY_IN_SECONDS => __( 'One Day', 'event-bridge-for-activitypub' ),
|
||||
WEEK_IN_SECONDS => __( 'One Week', 'event-bridge-for-activitypub' ),
|
||||
MONTH_IN_SECONDS => __( 'One Month', 'event-bridge-for-activitypub' ),
|
||||
YEAR_IN_SECONDS => __( 'One Year', 'event-bridge-for-activitypub' ),
|
||||
);
|
||||
foreach ( $choices as $time => $string ) {
|
||||
echo '<option value="' . \esc_attr( $time ) . '" ' . \selected( $cache_retention_period, $time, true ) . '>' . \esc_attr( $string ) . '</option>';
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
<p id="event_bridge_for_activitypub_event-sources-cache-clear-time-frame"><?php esc_html_e( 'External events from your event sources will be automatically removed from your site after the selected time period has passed since the event ended. Choose a time frame that works best for your needs.', 'event-bridge-for-activitypub' ); ?></p>
|
||||
</td>
|
||||
<tr>
|
||||
<?php
|
||||
}
|
||||
?>
|
||||
<tbody>
|
||||
</table>
|
||||
<?php
|
||||
} elseif ( ! \Activitypub\is_user_type_disabled( 'blog' ) ) {
|
||||
?>
|
||||
<div class="notice-warning"><p><?php esc_html_e( 'You do not have an Event Plugin installed that supports this feature.', 'event-bridge-for-activitypub' ); ?></p></div>
|
||||
<p><?php \esc_html_e( 'The following Event Plugins are supported:', 'event-bridge-for-activitypub' ); ?></p>
|
||||
<?php
|
||||
$plugins_supporting_event_sources = Setup::detect_event_plugins_supporting_event_sources();
|
||||
echo '<ul class="event_bridge_for_activitypub-list">';
|
||||
foreach ( $plugins_supporting_event_sources as $event_plugin ) {
|
||||
echo '<li>' . esc_attr( $event_plugin->get_plugin_name() ) . '</li>';
|
||||
}
|
||||
echo '</ul>';
|
||||
} else {
|
||||
$activitypub_plugin_data = \get_plugin_data( ACTIVITYPUB_PLUGIN_FILE );
|
||||
|
||||
$notice = sprintf(
|
||||
/* translators: 1: The name of the ActivityPub plugin. */
|
||||
_x(
|
||||
'In order to use this feature your have to enable the Blog-Actor in the the <a href="%1$s">%2$s settings</a>.',
|
||||
'admin notice',
|
||||
'event-bridge-for-activitypub'
|
||||
),
|
||||
\admin_url( 'options-general.php?page=activitypub&tab=event-bridge-for-activitypub&subpage=settings' ),
|
||||
\esc_html( $activitypub_plugin_data['Name'] )
|
||||
);
|
||||
|
||||
$allowed_html = array(
|
||||
'a' => array(
|
||||
'href' => true,
|
||||
'title' => true,
|
||||
),
|
||||
);
|
||||
echo '<div class="notice-warning"><p>' . \wp_kses( $notice, $allowed_html ) . '</p></div>';
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
<?php \submit_button(); ?>
|
||||
</form>
|
||||
</div>
|
||||
<div class="wrap event_bridge_for_activitypub-admin-table-container">
|
||||
<br>
|
||||
<?php
|
||||
if ( \get_option( 'event_bridge_for_activitypub_event_sources_active', false ) ) {
|
||||
?>
|
||||
<!-- ThickBox content (hidden initially) -->
|
||||
<div id="Event_Bridge_For_ActivityPub_add_new_source" style="display:none;">
|
||||
<h2><?php \esc_html_e( 'Add a Trusted Event Source', 'event-bridge-for-activitypub' ); ?></h2>
|
||||
<ul class="event-bridge-for-activitypub-syntax-list" id="event_bridge_for_activitypub_add_event_source_description">
|
||||
<?php \esc_html_e( 'Use one of the following syntax:', 'event-bridge-for-activitypub' ); ?>
|
||||
<li>
|
||||
<?php \esc_html_e( 'Enter a Fediverse user handle', 'event-bridge-for-activitypub' ); ?> ( <?php esc_html_e( 'e.g.', 'event-bridge-for-activitypub' ); ?> <code>@username@example.social</code>)
|
||||
</li>
|
||||
<li>
|
||||
<?php \esc_html_e( 'ActivityPub account URL or ID', 'event-bridge-for-activitypub' ); ?> ( <?php esc_html_e( 'e.g.', 'event-bridge-for-activitypub' ); ?> <code>https://example.social/user/username</code>)
|
||||
</li>
|
||||
<li>
|
||||
<?php \esc_html_e( 'The domain or URL of a Gancio instance', 'event-bridge-for-activitypub' ); ?> ( <?php esc_html_e( 'e.g.', 'event-bridge-for-activitypub' ); ?> <code>https://demo.gancio.org</code>)
|
||||
</li>
|
||||
</ul>
|
||||
<div class="notice notice-info inline">
|
||||
<p>
|
||||
ℹ️
|
||||
<?php
|
||||
$number_of_imports = \Event_Bridge_For_ActivityPub\Outbox_Parser::MAX_EVENTS_TO_IMPORT;
|
||||
$notice = sprintf(
|
||||
/* translators: 1: The maximum number of imported events. */
|
||||
__( 'To ensure a smooth start, up to %d upcoming events from this source will be automatically imported soon after adding it.', 'event-bridge-for-activitypub' ),
|
||||
$number_of_imports
|
||||
);
|
||||
echo esc_html( $notice );
|
||||
?>
|
||||
</p>
|
||||
</div>
|
||||
<form method="post" action="options.php">
|
||||
<?php \settings_fields( 'event-bridge-for-activitypub_add-event-source' ); ?>
|
||||
<label for="event_bridge_for_activitypub_add_event_source">
|
||||
<p>
|
||||
<?php \esc_html_e( 'Event Source (handle, URL, or instance)', 'event-bridge-for-activitypub' ); ?>:
|
||||
</p>
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
style="width: 100%"
|
||||
name="event_bridge_for_activitypub_add_event_source"
|
||||
id="event_bridge_for_activitypub_add_event_source"
|
||||
aria-describedby="event_bridge_for_activitypub_add_event_source_description"
|
||||
placeholder="@username@example.social or https://example.social/user/username">
|
||||
<?php \submit_button( __( 'Follow Event Source', 'event-bridge-for-activitypub' ) ); ?>
|
||||
</form>
|
||||
</div>
|
||||
<div class="wrap activitypub-followers-page">
|
||||
<!-- Table title with add new button like on post edit pages -->
|
||||
<div class="event_bridge_for_activitypub-admin-table-top">
|
||||
<h2 class="wp-heading-inline"> <?php esc_html_e( 'Manage Event Sources', 'event-bridge-for-activitypub' ); ?> </h2>
|
||||
<!-- Button that triggers ThickBox -->
|
||||
<a href="#TB_inline?width=600&height=400&inlineId=Event_Bridge_For_ActivityPub_add_new_source" class="thickbox page-title-action">
|
||||
<?php \esc_html_e( 'Add Event Source', 'event-bridge-for-activitypub' ); ?>
|
||||
</a>
|
||||
</div>
|
||||
<form method="get">
|
||||
<input type="hidden" name="page" value="activitypub" />
|
||||
<input type="hidden" name="tab" value="event-bridge-for-activitypub" />
|
||||
<input type="hidden" name="subpage" value="event-sources" />
|
||||
<?php
|
||||
$table = new \Event_Bridge_For_ActivityPub\Table\Event_Sources();
|
||||
$table->prepare_items();
|
||||
$table->search_box( 'Search', 'search' );
|
||||
$table->display();
|
||||
?>
|
||||
</form>
|
||||
</div>
|
||||
<?php } ?>
|
||||
</div>
|
||||
<?php } ?>
|
@ -0,0 +1,229 @@
|
||||
<?php
|
||||
/**
|
||||
* Template for Event Bridge for ActivityPub settings page.
|
||||
*
|
||||
* This template is used to display and manage settings for the Event Bridge for ActivityPub plugin.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*
|
||||
* @param array $args An array of arguments for the settings page.
|
||||
*/
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Activitypub\Activity\Extended_Object\Event;
|
||||
use Event_Bridge_For_ActivityPub\Setup;
|
||||
|
||||
\load_template(
|
||||
__DIR__ . '/../menu.php',
|
||||
true,
|
||||
array(
|
||||
'settings' => 'active',
|
||||
)
|
||||
);
|
||||
|
||||
$activitypub_plugin_is_active = Setup::get_instance()->is_activitypub_plugin_active();
|
||||
|
||||
if ( ! isset( $args ) || ! array_key_exists( 'event_terms', $args ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! current_user_can( 'manage_options' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$event_terms = $args['event_terms'];
|
||||
|
||||
require_once EVENT_BRIDGE_FOR_ACTIVITYPUB_PLUGIN_DIR . '/includes/event-categories.php';
|
||||
|
||||
$selected_default_event_category = \get_option( 'event_bridge_for_activitypub_default_event_category', 'MEETING' );
|
||||
$current_category_mapping = \get_option( 'event_bridge_for_activitypub_event_category_mappings', array() );
|
||||
$reminder_time_gap = \get_option( 'event_bridge_for_activitypub_reminder_time_gap', 0 );
|
||||
|
||||
$reminder_time_gap_choices = array(
|
||||
0 => __( 'Disabled', 'event-bridge-for-activitypub' ),
|
||||
HOUR_IN_SECONDS * 6 => __( '6 hours', 'event-bridge-for-activitypub' ),
|
||||
DAY_IN_SECONDS => __( '1 day', 'event-bridge-for-activitypub' ),
|
||||
DAY_IN_SECONDS * 3 => __( '3 days', 'event-bridge-for-activitypub' ),
|
||||
WEEK_IN_SECONDS => __( '1 week', 'event-bridge-for-activitypub' ),
|
||||
);
|
||||
|
||||
if ( \get_option( 'event_bridge_for_activitypub_initially_activated' ) ) {
|
||||
\update_option( 'event_bridge_for_activitypub_initially_activated', '' );
|
||||
}
|
||||
?>
|
||||
|
||||
<div class="activitypub-settings hide-if-no-js">
|
||||
<form method="post" action="options.php">
|
||||
<?php \settings_fields( 'event-bridge-for-activitypub' ); ?>
|
||||
<div class="box">
|
||||
<h2> <?php esc_html_e( 'Event Summary', 'event-bridge-for-activitypub' ); ?> </h2>
|
||||
<p><?php esc_html_e( 'Many Fediverse applications (e.g., Mastodon) don\'t fully support events, instead they will show a summary text along with the events title and the URL to your Website.', 'event-bridge-for-activitypub' ); ?></p>
|
||||
<table class="form-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<label for="event_bridge_for_activitypub_summary_format_type"> <?php esc_html_e( 'Event Summary Text', 'event-bridge-for-activitypub' ); ?></label>
|
||||
</th>
|
||||
<td>
|
||||
<p>
|
||||
<label for="event_bridge_for_activitypub_summary_type_preset">
|
||||
<input type="radio" name="event_bridge_for_activitypub_summary_type" id="event_bridge_for_activitypub_summary_type_preset" value="preset" <?php echo \checked( 'preset', \get_option( 'event_bridge_for_activitypub_summary_type', EVENT_BRIDGE_FOR_ACTIVITYPUB_DEFAULT_SUMMARY_TYPE ) ); ?> />
|
||||
<?php \esc_html_e( 'Automatic (default)', 'event-bridge-for-activitypub' ); ?>
|
||||
-
|
||||
<span class="description">
|
||||
<?php \esc_html_e( 'Let the plugin compose a summary for you. ', 'event-bridge-for-activitypub' ); ?>
|
||||
</span>
|
||||
</label>
|
||||
</p>
|
||||
<p>
|
||||
<label for="event_bridge_for_activitypub_summary_type_custom">
|
||||
<input type="radio" name="event_bridge_for_activitypub_summary_type" id="event_bridge_for_activitypub_summary_type_custom" value="custom" <?php echo \checked( 'custom', \get_option( 'event_bridge_for_activitypub_summary_type', EVENT_BRIDGE_FOR_ACTIVITYPUB_DEFAULT_SUMMARY_TYPE ) ); ?> />
|
||||
<?php \esc_html_e( 'Custom', 'event-bridge-for-activitypub' ); ?>
|
||||
-
|
||||
<span class="description">
|
||||
<?php \esc_html_e( 'For advanced users: compose your custom summary via shortcodes.', 'event-bridge-for-activitypub' ); ?>
|
||||
</span>
|
||||
</label>
|
||||
</p>
|
||||
<div id="event_bridge_for_activitypub_summary_type_custom-details">
|
||||
<textarea name="event_bridge_for_activitypub_custom_summary" id="event_bridge_for_activitypub_custom_summary" rows="10" cols="50" class="large-text"><?php echo esc_textarea( wp_kses( \get_option( 'event_bridge_for_activitypub_custom_summary', EVENT_BRIDGE_FOR_ACTIVITYPUB_SUMMARY_TEMPLATE ), 'post' ) ); ?></textarea>
|
||||
<details>
|
||||
<summary><?php esc_html_e( 'See a list Template Tags available for the summary.', 'event-bridge-for-activitypub' ); ?></summary>
|
||||
<div class="description">
|
||||
<dl>
|
||||
<dt><code>[ap_start_time icon="true" label="true"]</code><dt>
|
||||
<dd><?php \esc_html_e( 'The events title.', 'event-bridge-for-activitypub' ); ?></dd>
|
||||
<dt><code>[ap_end_time icon="true" label="true"]</code><dt>
|
||||
<dd><?php \esc_html_e( 'The events content.', 'event-bridge-for-activitypub' ); ?></dd>
|
||||
<dt><code>[ap_location icon="true" label="true"]</code><dt>
|
||||
<dd><?php \esc_html_e( 'The events location.', 'event-bridge-for-activitypub' ); ?></dd>
|
||||
<dt><code>[ap_hashtags]</code><dt>
|
||||
<dd><?php \esc_html_e( 'The events tags as hashtags.', 'event-bridge-for-activitypub' ); ?></dd>
|
||||
<dt><code>[ap_excerpt]</code><dt>
|
||||
<dd><?php \esc_html_e( 'The events excerpt (may be truncated).', 'event-bridge-for-activitypub' ); ?></dd>
|
||||
<dt><code>[ap_content]</code><dt>
|
||||
<dd><?php \esc_html_e( 'The events description.', 'event-bridge-for-activitypub' ); ?></dd>
|
||||
</dl>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<label for="event_bridge_for_activitypub_summary_format"> <?php esc_html_e( 'Enforce plain text in summary', 'event-bridge-for-activitypub' ); ?></label>
|
||||
</th>
|
||||
<td>
|
||||
<p>
|
||||
<input type="checkbox" aria-describedby="event_bridge_for_activitypub_summary_format_description" name="event_bridge_for_activitypub_summary_format" id="event_bridge_for_activitypub_summary_format" value="plain" <?php echo \checked( 'plain', \get_option( 'event_bridge_for_activitypub_summary_format', EVENT_BRIDGE_FOR_ACTIVITYPUB_DEFAULT_SUMMARY_TYPE ) ); ?> />
|
||||
<span id="event_bridge_for_activitypub_summary_format_description">
|
||||
<?php
|
||||
$allowed_html = array(
|
||||
'code' => array(),
|
||||
);
|
||||
echo \wp_kses( __( 'Many Fediverse applications, including Mastodon before version 4.3.0, do not render summaries as HTML. Enable this option to send the summary as plain text for better compatibility (e.g., <code><ul><li>Item 1</li></ul></code> will be sent as \'Item 1\' without formatting).', 'event-bridge-for-activitypub' ), $allowed_html );
|
||||
?>
|
||||
</span>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="box">
|
||||
<h2> <?php esc_html_e( 'ActivityPub Event Category', 'event-bridge-for-activitypub' ); ?> </h2>
|
||||
<p id="event_bridge_for_activitypub_default_event_category_desc"> <?php esc_html_e( 'To help visitors find events more easily, the community created a set of basic event categories. Please select the category that best matches the majority of the events you organize.', 'event-bridge-for-activitypub' ); ?> </p>
|
||||
<table class="form-table">
|
||||
<tr>
|
||||
<th scope="row">
|
||||
<label for="event_bridge_for_activitypub_default_event_category"> <?php esc_html_e( 'Default Federated Event Category', 'event-bridge-for-activitypub' ); ?></label>
|
||||
</th>
|
||||
<td>
|
||||
<select aria-describedby="event_bridge_for_activitypub_default_event_category_desc" id="event_bridge_for_activitypub_default_event_category" name="event_bridge_for_activitypub_default_event_category">';
|
||||
<?php
|
||||
foreach ( EVENT_BRIDGE_FOR_ACTIVITYPUB_EVENT_CATEGORIES as $value => $label ) {
|
||||
echo '<option value="' . esc_attr( $value ) . '" ' . selected( $selected_default_event_category, $value, false ) . '>' . esc_html( $label ) . '</option>';
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<?php if ( ! empty( $event_terms ) ) : ?>
|
||||
<h3> <?php esc_html_e( 'Fine-grained Event Category Settings', 'event-bridge-for-activitypub' ); ?> </h3>
|
||||
<p id="event_bridge_for_activitypub_event_category_mapping_desc"> <?php esc_html_e( 'For any event category you have created on your WordPress site you can choose an event category which will be used in federation. This option lets you override the default selection above. ', 'event-bridge-for-activitypub' ); ?> </p>
|
||||
<table class="form-table">
|
||||
<tr>
|
||||
<th> <?php esc_html_e( 'Event category on your site', 'event-bridge-for-activitypub' ); ?> </th>
|
||||
<th> <?php esc_html_e( 'Fediverse event category', 'event-bridge-for-activitypub' ); ?> </th>
|
||||
</tr>
|
||||
<?php foreach ( $event_terms as $event_term ) { ?>
|
||||
<tr>
|
||||
<td scope="row">
|
||||
<label for="event_bridge_for_activitypub_event_category_mapping_<?php echo esc_attr( $event_term->slug ); ?>"">
|
||||
<?php echo esc_html( $event_term->name ); ?> </td>
|
||||
</label>
|
||||
<td class="select-cell">
|
||||
<select aria-describedby="event_bridge_for_activitypub_event_category_mapping_desc" id="event_bridge_for_activitypub_event_category_mapping_<?php echo esc_attr( $event_term->slug ); ?>" name="event_bridge_for_activitypub_event_category_mappings[<?php echo esc_attr( $event_term->slug ); ?>]">
|
||||
<?php
|
||||
$current_mapping_is_set = false;
|
||||
if ( ! empty( $current_category_mapping ) ) {
|
||||
$current_mapping_is_set = array_key_exists( $event_term->slug, $current_category_mapping );
|
||||
}
|
||||
if ( $current_mapping_is_set ) {
|
||||
$mapping = $current_category_mapping[ $event_term->slug ];
|
||||
} else {
|
||||
$mapping = 'DEFAULT';
|
||||
}
|
||||
if ( 'DEFAULT' === $mapping ) {
|
||||
echo '<option value="' . esc_attr( $mapping ) . '"> -- ' . esc_html( EVENT_BRIDGE_FOR_ACTIVITYPUB_EVENT_CATEGORIES[ $mapping ] ) . ' -- </option>';
|
||||
} else {
|
||||
echo '<option value="' . esc_attr( $mapping ) . '">' . esc_html( EVENT_BRIDGE_FOR_ACTIVITYPUB_EVENT_CATEGORIES[ $mapping ] ) . '</option>';
|
||||
}
|
||||
echo '<option value="DEFAULT" ' . selected( $selected_default_event_category, 'DEFAULT', false ) . '> -- ' . esc_html__( 'Default', 'event-bridge-for-activitypub' ) . ' -- </option>';
|
||||
foreach ( Event::DEFAULT_EVENT_CATEGORIES as $event_category ) {
|
||||
echo '<option value="' . esc_attr( $event_category ) . '" ' . selected( $mappings[ $event_term->slug ] ?? '', $event_category, false ) . '>' . esc_html( EVENT_BRIDGE_FOR_ACTIVITYPUB_EVENT_CATEGORIES[ $event_category ] ) . '</option>';
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
</table>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="box">
|
||||
<h2> <?php esc_html_e( 'Send reminder before event starts', 'event-bridge-for-activitypub' ); ?> </h2>
|
||||
<p> <?php esc_html_e( 'Specify a time interval before the event starts to trigger a reminder. This reminder automatically boosts the event, making it reappear in users\' timelines at the defined time before the event to increase visibility just before the event begins.', 'event-bridge-for-activitypub' ); ?> </p>
|
||||
<table class="form-table">
|
||||
<tr>
|
||||
<label for="event_bridge_for_activitypub_reminder_time_gap">
|
||||
<th scope="row"> <?php esc_html_e( 'Default Time Gap for Reminders', 'event-bridge-for-activitypub' ); ?> </th>
|
||||
</label>
|
||||
<td>
|
||||
<select id="event_bridge_for_activitypub_reminder_time_gap" name="event_bridge_for_activitypub_reminder_time_gap">';
|
||||
<?php
|
||||
foreach ( $reminder_time_gap_choices as $value => $label ) {
|
||||
echo '<option value="' . esc_attr( $value ) . '" ' . selected( $reminder_time_gap, $value, false ) . '>' . esc_html( $label ) . '</option>';
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
<br><br>
|
||||
<?php esc_html_e( 'This default value can be overridden for each event. Note that override is only available in the User Interface if you use the Gutenberg editor.', 'event-bridge-for-activitypub' ); ?>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<!-- This disables the setup wizard. -->
|
||||
<div class="hidden" aria-hidden="true">
|
||||
<input type="checkbox" id="event_bridge_for_activitypub_initially_activated" name="event_bridge_for_activitypub_initially_activated"/>
|
||||
</div>
|
||||
<?php \submit_button(); ?>
|
||||
</form>
|
||||
</div>
|
@ -0,0 +1,250 @@
|
||||
<?php
|
||||
/**
|
||||
* Status/Welcome page for the Event Bridge for ActivityPub admin interface.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
use Event_Bridge_For_ActivityPub\Setup;
|
||||
use Event_Bridge_For_ActivityPub\Admin\General_Admin_Notices;
|
||||
use Event_Bridge_For_ActivityPub\Admin\Settings_Page;
|
||||
use Event_Bridge_For_ActivityPub\Admin\Health_Check;
|
||||
|
||||
\load_template(
|
||||
__DIR__ . '/../menu.php',
|
||||
true,
|
||||
array(
|
||||
'welcome' => 'active',
|
||||
)
|
||||
);
|
||||
|
||||
$active_event_plugins = Setup::get_instance()->get_active_event_plugins();
|
||||
$activitypub_plugin_is_active = Setup::get_instance()->is_activitypub_plugin_active();
|
||||
$event_bridge_for_activitypub_status_ok = true;
|
||||
$example_event_post = Health_Check::get_most_recent_event_posts();
|
||||
|
||||
if ( empty( $example_event_post ) ) {
|
||||
$example_event_post = 'https://yoursite.com/events/event-name';
|
||||
$example_event_post_is_dummy = true;
|
||||
} else {
|
||||
$example_event_post = \get_permalink( $example_event_post[0] );
|
||||
$example_event_post_is_dummy = false;
|
||||
}
|
||||
|
||||
global $wp_filesystem;
|
||||
WP_Filesystem();
|
||||
|
||||
?>
|
||||
|
||||
<div class="activitypub-settings event-bridge-for-activitypub-settings event-bridge-for-activitypub-settings-page hide-if-no-js">
|
||||
<div class="box">
|
||||
<h2><?php \esc_html_e( 'Status', 'event-bridge-for-activitypub' ); ?></h2>
|
||||
<p><?php \esc_html_e( 'The Event Bridge for ActivityPub detected the following (activated) event plugins:', 'event-bridge-for-activitypub' ); ?></p>
|
||||
<?php
|
||||
if ( ! $activitypub_plugin_is_active ) {
|
||||
$notice = General_Admin_Notices::get_admin_notice_activitypub_plugin_not_enabled();
|
||||
echo '<p>⚠' . \wp_kses( $notice, General_Admin_Notices::ALLOWED_HTML ) . '</p>';
|
||||
} elseif ( empty( $active_event_plugins ) ) {
|
||||
$notice = General_Admin_Notices::get_admin_notice_no_supported_event_plugin_active();
|
||||
echo '<p>⚠' . \wp_kses( $notice, General_Admin_Notices::ALLOWED_HTML ) . '</p>';
|
||||
}
|
||||
?>
|
||||
<?php foreach ( $active_event_plugins as $active_event_plugin ) { ?>
|
||||
<h3><?php echo esc_html( $active_event_plugin->get_plugin_name() ); ?>:</h3>
|
||||
<ul class="event-bridge-for-activitypub-list">
|
||||
<li>
|
||||
<?php
|
||||
if ( in_array( $active_event_plugin::get_post_type(), get_option( 'activitypub_support_post_types', array() ), true ) ) {
|
||||
echo '✅ ';
|
||||
$status_message_post_type_enabled = sprintf(
|
||||
/* translators: 1: the name of the event plugin a admin notice is shown. 2: The name of the ActivityPub plugin. */
|
||||
_x(
|
||||
'The ActivityPub support for the event post type of the plugin <i>%2$s</i> is enabled in the <a href="%3$s">%1$s settings</a>.',
|
||||
'admin notice',
|
||||
'event-bridge-for-activitypub'
|
||||
),
|
||||
esc_html( get_plugin_data( ACTIVITYPUB_PLUGIN_FILE )['Name'] ),
|
||||
esc_html( $active_event_plugin->get_plugin_name() ),
|
||||
admin_url( 'options-general.php?page=activitypub&tab=event-bridge-for-activitypub&subpage=settings' )
|
||||
);
|
||||
} else {
|
||||
$event_bridge_for_activitypub_status_ok = false;
|
||||
echo '❌ ';
|
||||
$status_message_post_type_enabled = sprintf(
|
||||
/* translators: 1: the name of the event plugin a admin notice is shown. 2: The name of the ActivityPub plugin. */
|
||||
_x(
|
||||
'The post type for events of the plugin <i>%2$s</i> is <b>not enabled</b> in the <a href="%3$s">%1$s settings</a>.',
|
||||
'admin notice',
|
||||
'event-bridge-for-activitypub'
|
||||
),
|
||||
esc_html( get_plugin_data( ACTIVITYPUB_PLUGIN_FILE )['Name'] ),
|
||||
esc_html( $active_event_plugin->get_plugin_name() ),
|
||||
admin_url( 'options-general.php?page=activitypub&tab=event-bridge-for-activitypub&subpage=settings' )
|
||||
);
|
||||
}
|
||||
$allowed_html = array(
|
||||
'a' => array(
|
||||
'href' => true,
|
||||
'title' => true,
|
||||
),
|
||||
'b' => array(),
|
||||
'i' => array(),
|
||||
);
|
||||
echo \wp_kses( $status_message_post_type_enabled, $allowed_html );
|
||||
?>
|
||||
</li>
|
||||
<li>
|
||||
<?php
|
||||
if ( Health_Check::test_if_event_transformer_is_used( $active_event_plugin ) ) {
|
||||
echo '✅ ';
|
||||
esc_html_e( 'The Event Bridge for ActivityPub successfully registered to the ActivityPub plugin.', 'event-bridge-for-activitypub' );
|
||||
} else {
|
||||
$event_bridge_for_activitypub_status_ok = false;
|
||||
echo '❌ ';
|
||||
esc_html_e( 'The Event Bridge for ActivityPub could not register to the ActivityPub plugin.', 'event-bridge-for-activitypub' );
|
||||
}
|
||||
?>
|
||||
</li>
|
||||
</ul>
|
||||
<?php } ?>
|
||||
</div>
|
||||
|
||||
<?php if ( get_option( 'event_bridge_for_activitypub_initially_activated', true ) ) : ?>
|
||||
<a href="<?php echo esc_url( admin_url( 'options-general.php?page=' . Settings_Page::SETTINGS_SLUG ) . '&tab=settings' ); ?>" role="button">
|
||||
<button class="button button-primary">
|
||||
<strong>→</strong> <?php \esc_html_e( 'Continue your setup', 'event-bridge-for-activitypub' ); ?>
|
||||
</button>
|
||||
</a>
|
||||
|
||||
<?php else : ?>
|
||||
|
||||
<div class="box">
|
||||
<h2><?php \esc_html_e( 'How to Check if It\'s Working', 'event-bridge-for-activitypub' ); ?></h2>
|
||||
<?php
|
||||
if ( ! $event_bridge_for_activitypub_status_ok ) {
|
||||
echo '<div class="notice-warning"><p>' . \esc_html__( 'Please fix the status issues above first.', 'event-bridge-for-activitypub' ) . '</p></div>';
|
||||
}
|
||||
?>
|
||||
<p><?php esc_html_e( 'Most of the magic happens behind the scenes, but here is how you can verify that your events are ready to be discovered:', 'event-bridge-for-activitypub' ); ?></p>
|
||||
<div class="event-bridge-for-activitypub-settings-accordion">
|
||||
<h4 class="event-bridge-for-activitypub-settings-accordion-heading">
|
||||
<button aria-expanded="false" class="event-bridge-for-activitypub-settings-accordion-trigger" aria-controls="event-bridge-for-activitypub-help-accordion-mastodon" type="button">
|
||||
<span class="title">
|
||||
1.
|
||||
<img src="<?php echo esc_url( plugins_url( '/assets/img/mastodon.svg', EVENT_BRIDGE_FOR_ACTIVITYPUB_PLUGIN_FILE ) ); ?>" alt="Mastodon Icon" class="event-bridge-for-activitypub-settings-inline-icon">
|
||||
<?php \esc_html_e( 'Using Your Mastodon Account', 'event-bridge-for-activitypub' ); ?>
|
||||
</span>
|
||||
<span class="icon"></span>
|
||||
</button>
|
||||
</h4>
|
||||
<div id="event-bridge-for-activitypub-help-accordion-mastodon" class="event-bridge-for-activitypub-settings-accordion-panel" hidden="hidden">
|
||||
<div class="notice notice-warning inline"><p>ℹ <?php \esc_html_e( 'Note that Mastodon can receive ActivityPub Event objects but does not yet support updating them. This means that if Mastodon has already received an event, it will always display the first version it encountered.', 'event-bridge-for-activitypub' ); ?> <?php esc_html_e( 'See the related tracking issue:', 'event-bridge-for-activitypub' ); ?> <a href="https://github.com/mastodon/mastodon/issues/31114" target="_blank">https://github.com/mastodon/mastodon/issues/31114</a></p></div>
|
||||
<ol class="event-bridge-for-activitypub-settings-numbered-list">
|
||||
<li><?php \esc_html_e( 'Log into your Mastodon account.', 'event-bridge-for-activitypub' ); ?></li>
|
||||
<li>
|
||||
<?php \esc_html_e( 'In the search bar, type or copy the full URL of one of your event pages. For example:', 'event-bridge-for-activitypub' ); ?>
|
||||
<code class="event-bridge-for-activitypub-settings-example-url"><?php echo \esc_url( $example_event_post ); ?></code>
|
||||
</li>
|
||||
<li><?php \esc_html_e( 'If everything is set up correctly, you\'ll see a post representing your event. It should include the event\'s image, title, and a brief description.', 'event-bridge-for-activitypub' ); ?></li>
|
||||
</ol>
|
||||
</div>
|
||||
<h4 class="event-bridge-for-activitypub-settings-accordion-heading">
|
||||
<button aria-expanded="false" class="event-bridge-for-activitypub-settings-accordion-trigger" aria-controls="event-bridge-for-activitypub-help-accordion-mobilizon" type="button">
|
||||
<span class="title">
|
||||
2.
|
||||
<img src="<?php echo esc_url( plugins_url( '/assets/img/mobilizon.svg', EVENT_BRIDGE_FOR_ACTIVITYPUB_PLUGIN_FILE ) ); ?>" alt="Mastodon Icon" class="event-bridge-for-activitypub-settings-inline-icon">
|
||||
<?php \esc_html_e( 'Using Your Mobilizon Account', 'event-bridge-for-activitypub' ); ?>
|
||||
</span>
|
||||
<span class="icon"></span>
|
||||
</button>
|
||||
</h4>
|
||||
<div id="event-bridge-for-activitypub-help-accordion-mobilizon" class="event-bridge-for-activitypub-settings-accordion-panel" hidden="hidden">
|
||||
<div class="notice notice-error inline"><p>⚠️ <?php \esc_html_e( 'Note that Mobilizon has significant interoperability issues (at least up to version 5.1).', 'event-bridge-for-activitypub' ); ?> <?php esc_html_e( 'See the related tracking issue:', 'event-bridge-for-activitypub' ); ?> <a href="https://framagit.org/framasoft/mobilizon/-/issues/1669" target="_blank">https://framagit.org/framasoft/mobilizon/-/issues/1669</a></p></div>
|
||||
<!-- <ol class="event-bridge-for-activitypub-settings-numbered-list">
|
||||
<li><?php \esc_html_e( 'Log into your Mobilizon account.', 'event-bridge-for-activitypub' ); ?></li>
|
||||
<li>
|
||||
<?php \esc_html_e( 'In the search bar, type or copy the full URL of one of your event pages. For example:', 'event-bridge-for-activitypub' ); ?>
|
||||
<code class="event-bridge-for-activitypub-settings-example-url"><?php echo \esc_url( $example_event_post ); ?></code>
|
||||
</li>
|
||||
<li><?php \esc_html_e( 'If everything is set up correctly, you\'ll see a full representation of your WordPress event. This will include the event\'s banner image, title, complete description, start and end times, categories, tags, and whether it\'s an online event.', 'event-bridge-for-activitypub' ); ?></li>
|
||||
</ol> -->
|
||||
</div>
|
||||
<h4 class="event-bridge-for-activitypub-settings-accordion-heading">
|
||||
<button aria-expanded="false" class="event-bridge-for-activitypub-settings-accordion-trigger" aria-controls="event-bridge-for-activitypub-help-accordion-fediverse" type="button">
|
||||
<span class="title">
|
||||
3.
|
||||
<img src="<?php echo esc_url( plugins_url( '/assets/img/fediverse.svg', EVENT_BRIDGE_FOR_ACTIVITYPUB_PLUGIN_FILE ) ); ?>" alt="Mastodon Icon" class="event-bridge-for-activitypub-settings-inline-icon">
|
||||
<?php \esc_html_e( 'Using Any Other Fediverse Application', 'event-bridge-for-activitypub' ); ?>
|
||||
</span>
|
||||
<span class="icon"></span>
|
||||
</button>
|
||||
</h4>
|
||||
<div id="event-bridge-for-activitypub-help-accordion-fediverse" class="event-bridge-for-activitypub-settings-accordion-panel" hidden="hidden">
|
||||
<p><?php \esc_html_e( 'Of course any other application in the Fediverse should work as well. Most applications support importing external content via searching for the contents full URL.', 'event-bridge-for-activitypub' ); ?></p>
|
||||
<ol class="event-bridge-for-activitypub-settings-numbered-list">
|
||||
<li><?php \esc_html_e( 'Log into your account on any Fediverse app.', 'event-bridge-for-activitypub' ); ?></li>
|
||||
<li>
|
||||
<?php \esc_html_e( 'In the search bar, type or copy the full URL of one of your event pages. For example:', 'event-bridge-for-activitypub' ); ?>
|
||||
<code class="event-bridge-for-activitypub-settings-example-url"><?php echo \esc_url( $example_event_post ); ?></code>
|
||||
</li>
|
||||
<li><?php \esc_html_e( 'If the application which your are using natively supports ActivityPub events, you should see a representation of your WordPress event. If your application is supports receiving ActivityPub events you will get a post which summarizes the event. Keep in mind that some apps may not support events at all.', 'event-bridge-for-activitypub' ); ?></li>
|
||||
</ol>
|
||||
</div>
|
||||
<h4 class="event-bridge-for-activitypub-settings-accordion-heading">
|
||||
<button aria-expanded="false" class="event-bridge-for-activitypub-settings-accordion-trigger" aria-controls="event-bridge-for-activitypub-help-accordion-advanced" type="button">
|
||||
<span class="title">
|
||||
4.
|
||||
<img src="<?php echo esc_url( plugins_url( '/assets/img/activitypub.svg', EVENT_BRIDGE_FOR_ACTIVITYPUB_PLUGIN_FILE ) ); ?>" alt="Mastodon Icon" class="event-bridge-for-activitypub-settings-inline-icon">
|
||||
<?php \esc_html_e( 'Advanced: Verifying the ActivityStreams JSON', 'event-bridge-for-activitypub' ); ?></span>
|
||||
<span class="icon"></span>
|
||||
</button>
|
||||
</h4>
|
||||
<div id="event-bridge-for-activitypub-help-accordion-advanced" class="event-bridge-for-activitypub-settings-accordion-panel" hidden="hidden">
|
||||
<p>
|
||||
<?php
|
||||
// Assume $event_url contains the dynamic URL, and '?activitypub' is appended to it.
|
||||
$activitypub_url = esc_url( $example_event_post . '?activitypub' );
|
||||
|
||||
// Prepare the activitypub part wrapped in a <code> element.
|
||||
$activitypub_query = '<nobr><code>' . esc_html( '?activitypub' ) . '</code></nobr>';
|
||||
|
||||
$activitypub_url_html = '<a href="' . esc_url( $activitypub_url ) . '" target="_blank">' . esc_html( $activitypub_url ) . '</a>';
|
||||
|
||||
// Translator comment to explain the placeholder.
|
||||
/* translators: %1$s is the <code>?activitypub</code> string, and %2$s is the full URL of an example event */
|
||||
$raw_string = sprintf( __( 'For more technical users, you can inspect how your event is converted into an ActivityPub object. Simply append %1$s to the end of any single event pages URL to view the raw ActivityStreams JSON data (e.g., %2$s).', 'event-bridge-for-activitypub' ), $activitypub_query, $activitypub_url_html );
|
||||
|
||||
// Allowed HTML tags in the string (only <code> and <a>).
|
||||
$allowed_html = array(
|
||||
'a' => array(
|
||||
'href' => array(),
|
||||
'target' => array(),
|
||||
),
|
||||
'nobr' => array(),
|
||||
'code' => array(),
|
||||
);
|
||||
|
||||
// Output the formatted string with the allowed HTML elements.
|
||||
echo wp_kses( $raw_string, $allowed_html );
|
||||
?>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="box">
|
||||
<h2><?php \esc_html_e( 'Acknowledgement', 'event-bridge-for-activitypub' ); ?></h2>
|
||||
<p><a href="https://NLnet.nl"><img src="<?php echo esc_url( plugins_url( '/assets/img/acknowledgement-NLnet.svg', EVENT_BRIDGE_FOR_ACTIVITYPUB_PLUGIN_FILE ) ); ?>" alt="Logo NLnet: abstract logo of four people seen from above" class="logo-center"></a> <a href="https://NLnet.nl/NGI0"><img src="<?php echo esc_url( plugins_url( '/assets/img/acknowledgement-NGI0Entrust.svg', EVENT_BRIDGE_FOR_ACTIVITYPUB_PLUGIN_FILE ) ); ?>" alt="Logo NGI Zero: letterlogo shaped like a tag" class="logo-center"> </a></p>
|
||||
<p>The development of this plugin was funded through the <a href="https://NLnet.nl/entrust">NGI0 Entrust</a> Fund, a fund established by <a href="https://nlnet.nl">NLnet</a> with financial support from the European Commission's <a href="https://ngi.eu">Next Generation Internet</a> programme, under the aegis of <a href="https://commission.europa.eu/about-european-commission/departments-and-executive-agencies/communications-networks-content-and-technology_en">DG Communications Networks, Content and Technology</a> under grant agreement N<sup>o</sup> 101069594.</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
@ -0,0 +1,13 @@
|
||||
<?php
|
||||
/**
|
||||
* Template for the header and navigation of the admin pages.
|
||||
*
|
||||
* @package Event_Bridge_For_ActivityPub
|
||||
* @since 1.0.0
|
||||
* @license AGPL-3.0-or-later
|
||||
*/
|
||||
|
||||
// Exit if accessed directly.
|
||||
defined( 'ABSPATH' ) || exit; // @codeCoverageIgnore
|
||||
|
||||
\Event_Bridge_For_ActivityPub\Admin\Settings_Page::do_settings_page();
|