Merge branch 'nomadic' into dev

This commit is contained in:
Mike Macgirvin 2024-07-03 06:46:12 +10:00
commit fad117958a
171 changed files with 9429 additions and 1647 deletions

37
.env Normal file
View file

@ -0,0 +1,37 @@
###> symfony/mailer ###
# MAILER_DSN=null://null
###< symfony/mailer ###
###> symfony/framework-bundle ###
APP_ENV=dev
APP_SECRET=b2e0b4744a825057deefb2b8d6cf7d50
###< symfony/framework-bundle ###
###> doctrine/doctrine-bundle ###
# Format described at https://www.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/configuration.html#connecting-using-a-url
# IMPORTANT: You MUST configure your server version, either here or in config/packages/doctrine.yaml
#
# DATABASE_URL="sqlite:///%kernel.project_dir%/var/data.db"
# DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=8.0.32&charset=utf8mb4"
# DATABASE_URL="mysql://app:!ChangeMe!@127.0.0.1:3306/app?serverVersion=10.11.2-MariaDB&charset=utf8mb4"
DATABASE_URL="postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=16&charset=utf8"
###< doctrine/doctrine-bundle ###
###> nelmio/cors-bundle ###
CORS_ALLOW_ORIGIN='^https?://(localhost|127\.0\.0\.1)(:[0-9]+)?$'
###< nelmio/cors-bundle ###
###> league/oauth2-server-bundle ###
OAUTH_PRIVATE_KEY=%kernel.project_dir%/config/jwt/private.pem
OAUTH_PUBLIC_KEY=%kernel.project_dir%/config/jwt/public.pem
OAUTH_PASSPHRASE=a557f0cd5e33c785227ae08fa111f55a
OAUTH_ENCRYPTION_KEY=0a55b0c0d31a601b7a8cfa5427924fa2
###< league/oauth2-server-bundle ###
###> symfony/messenger ###
# Choose one of the transports below
# MESSENGER_TRANSPORT_DSN=amqp://guest:guest@localhost:5672/%2f/messages
# MESSENGER_TRANSPORT_DSN=redis://localhost:6379/messages
# MESSENGER_TRANSPORT_DSN=doctrine://default?auto_setup=0
###< symfony/messenger ###

6
.env.test Normal file
View file

@ -0,0 +1,6 @@
# define your env variables for the test env here
KERNEL_CLASS='App\Kernel'
APP_SECRET='$ecretf0rt3st'
SYMFONY_DEPRECATIONS_HELPER=999999
PANTHER_APP_ENV=panther
PANTHER_ERROR_SCREENSHOT_DIR=./var/error-screenshots

20
.gitignore vendored
View file

@ -22,6 +22,7 @@ Thumbs.db
__pycache__
## Ignore site specific files and folders
.env*
.htaccess
.htconfig.php
.htstartup.php
@ -85,3 +86,22 @@ vendor/
vendor/**/tests/
vendor/**/Test/
vendor/sabre/*/examples/
###> phpunit/phpunit ###
/phpunit.xml
.phpunit.result.cache
###< phpunit/phpunit ###
###> symfony/framework-bundle ###
/.env.local
/.env.local.php
/.env.*.local
/config/secrets/prod/prod.decrypt.private.php
/public/bundles/
/var/
/vendor/
###< symfony/framework-bundle ###
###> league/oauth2-server-bundle ###
/config/jwt/*.pem
###< league/oauth2-server-bundle ###

21
bin/console Executable file
View file

@ -0,0 +1,21 @@
#!/usr/bin/env php
<?php
use App\Kernel;
use Symfony\Bundle\FrameworkBundle\Console\Application;
if (!is_dir(dirname(__DIR__).'/vendor')) {
throw new LogicException('Dependencies are missing. Try running "composer install".');
}
if (!is_file(dirname(__DIR__).'/vendor/autoload_runtime.php')) {
throw new LogicException('Symfony Runtime is missing. Try running "composer require symfony/runtime".');
}
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
return function (array $context) {
$kernel = new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
return new Application($kernel);
};

19
compose.override.yaml Normal file
View file

@ -0,0 +1,19 @@
version: '3'
services:
###> symfony/mailer ###
mailer:
image: axllent/mailpit
ports:
- "1025"
- "8025"
environment:
MP_SMTP_AUTH_ACCEPT_ANY: 1
MP_SMTP_AUTH_ALLOW_INSECURE: 1
###< symfony/mailer ###
###> doctrine/doctrine-bundle ###
database:
ports:
- "5432"
###< doctrine/doctrine-bundle ###

21
compose.yaml Normal file
View file

@ -0,0 +1,21 @@
version: '3'
services:
###> doctrine/doctrine-bundle ###
database:
image: postgres:${POSTGRES_VERSION:-16}-alpine
environment:
POSTGRES_DB: ${POSTGRES_DB:-app}
# You should definitely change the password in production
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-!ChangeMe!}
POSTGRES_USER: ${POSTGRES_USER:-app}
volumes:
- database_data:/var/lib/postgresql/data:rw
# You may use a bind-mounted host directory instead, so that it is harder to accidentally remove the volume and lose all your data!
# - ./docker/db/data:/var/lib/postgresql/data:rw
###< doctrine/doctrine-bundle ###
volumes:
###> doctrine/doctrine-bundle ###
database_data:
###< doctrine/doctrine-bundle ###

View file

@ -15,6 +15,7 @@
"require": {
"php": ">=8.1",
"ext-curl": "*",
"ext-dom": "*",
"ext-gd": "*",
"ext-mbstring": "*",
"ext-xml": "*",
@ -39,7 +40,7 @@
"ext-json": "*",
"symfony/yaml": "*",
"symfony/uid": "*",
"symfony/mailer": "*",
"symfony/mailer": "^6.4",
"wapmorgan/mp3info": "^0.0.8",
"chillerlan/php-qrcode": "^4.3",
"spomky-labs/otphp": "^10.0",
@ -50,25 +51,70 @@
"league/oauth2-google": "^4.0",
"decomplexity/sendoauth2": "^3.0",
"gregwar/captcha": "^1.2",
"symfony/symfony": "*",
"doctrine/orm": "*",
"doctrine/dbal": "*",
"root23/php-json-canonicalization": "^1.0"
"symfony/flex": "^2.4",
"symfony/asset": "^6.4",
"symfony/monolog-bundle": "^3.10",
"symfony/form": "^6.4",
"symfony/security-bundle": "^6.4",
"symfony/translation": "^6.4",
"symfony/validator": "^6.4",
"doctrine/doctrine-bundle": "^2.12",
"doctrine/doctrine-migrations-bundle": "^3.3",
"symfony/twig-bundle": "^6.4",
"twig/extra-bundle": "^2.12|^3.0",
"twig/twig": "^2.12|^3.0",
"api-platform/core": "^3.2",
"nelmio/cors-bundle": "^2.4",
"symfony/expression-language": "^6.4",
"phpdocumentor/reflection-docblock": "^5.3",
"phpstan/phpdoc-parser": "^1.27",
"symfony/property-access": "^6.4",
"symfony/property-info": "^6.4",
"symfony/serializer": "^6.4",
"league/oauth2-server-bundle": "^0.8.0",
"symfony/messenger": "^6.4",
"symfony/notifier": "^6.4",
"root23/php-json-canonicalization": "^1.0",
"symfony/framework-bundle": "^6.4",
"forensic/feed-parser": "^1.2"
},
"require-dev": {
"phpunit/phpunit": "@stable",
"behat/behat": "*",
"behat/mink-extension": "@stable",
"behat/mink-goutte-driver": "@stable",
"php-mock/php-mock-phpunit": "@stable",
"codeception/codeception": "*",
"codeception/module-phpbrowser": "*",
"codeception/module-asserts": "*"
"codeception/module-asserts": "*",
"symfony/dotenv": "^6.4",
"symfony/maker-bundle": "^1.57",
"doctrine/doctrine-fixtures-bundle": "^3.5",
"symfony/stopwatch": "^6.4",
"symfony/web-profiler-bundle": "^6.4",
"symfony/debug-bundle": "^6.4"
},
"autoload": {
"psr-4": {
"App\\": "src/",
"Include\\": "include/",
"Code\\": "src/"
"Code\\Access\\": "src/Access",
"Code\\ActivityStreams\\": "src/ActivityStreams",
"Code\\Daemon\\": "src/Daemon",
"Code\\Entity\\": "src/Entity",
"Code\\Extend\\": "src/Extend",
"Code\\Identity\\": "src/Identity",
"Code\\Import\\": "src/Import",
"Code\\Lib\\": "src/Lib",
"Code\\Module\\": "src/Module",
"Code\\Nomad\\": "src/Nomad",
"Code\\Photo\\": "src/Photo",
"Code\\Render\\": "src/Render",
"Code\\Storage\\": "src/Storage",
"Code\\Text\\": "src/Text",
"Code\\Thumbs\\": "src/Thumbs",
"Code\\Update\\": "src/Update",
"Code\\Web\\": "src/Web",
"Code\\Widget\\": "src/Widget"
}
},
"autoload-dev": {
@ -76,6 +122,7 @@
"Code\\Tests\\Unit\\": "tests/unit"
}
},
"minimum-stability": "stable",
"config": {
"platform": {
@ -86,5 +133,11 @@
"allow-plugins": {
"symfony/flex": true
}
},
"scripts": {
"auto-scripts": {
"cache:clear": "symfony-cmd",
"assets:install %PUBLIC_DIR%": "symfony-cmd"
}
}
}

8291
composer.lock generated

File diff suppressed because it is too large Load diff

18
config/bundles.php Normal file
View file

@ -0,0 +1,18 @@
<?php
return [
Symfony\Bundle\FrameworkBundle\FrameworkBundle::class => ['all' => true],
Doctrine\Bundle\DoctrineBundle\DoctrineBundle::class => ['all' => true],
Doctrine\Bundle\MigrationsBundle\DoctrineMigrationsBundle::class => ['all' => true],
Symfony\Bundle\TwigBundle\TwigBundle::class => ['all' => true],
Symfony\Bundle\WebProfilerBundle\WebProfilerBundle::class => ['dev' => true, 'test' => true],
Twig\Extra\TwigExtraBundle\TwigExtraBundle::class => ['all' => true],
Symfony\Bundle\SecurityBundle\SecurityBundle::class => ['all' => true],
Symfony\Bundle\MonologBundle\MonologBundle::class => ['all' => true],
Symfony\Bundle\MakerBundle\MakerBundle::class => ['dev' => true],
ApiPlatform\Symfony\Bundle\ApiPlatformBundle::class => ['all' => true],
League\Bundle\OAuth2ServerBundle\LeagueOAuth2ServerBundle::class => ['all' => true],
Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle::class => ['dev' => true, 'test' => true],
Nelmio\CorsBundle\NelmioCorsBundle::class => ['all' => true],
Symfony\Bundle\DebugBundle\DebugBundle::class => ['dev' => true],
];

View file

@ -0,0 +1,18 @@
api_platform:
title: Hello API Platform
version: 1.0.0
formats:
jsonld: ['application/ld+json']
docs_formats:
jsonld: ['application/ld+json']
jsonopenapi: ['application/vnd.openapi+json']
html: ['text/html']
defaults:
stateless: true
cache_headers:
vary: ['Content-Type', 'Authorization', 'Origin']
extra_properties:
standard_put: true
rfc_7807_compliant_errors: true
event_listeners_backward_compatibility_layer: false
keep_legacy_inflector: false

View file

@ -0,0 +1,19 @@
framework:
cache:
# Unique name of your app: used to compute stable namespaces for cache keys.
#prefix_seed: your_vendor_name/app_name
# The "app" cache stores to the filesystem by default.
# The data in this cache should persist between deploys.
# Other options include:
# Redis
#app: cache.adapter.redis
#default_redis_provider: redis://localhost
# APCu (not recommended with heavy random-write workloads as memory fragmentation can cause perf issues)
#app: cache.adapter.apcu
# Namespaced pools use the above "app" backend by default
#pools:
#my.dedicated.cache: null

View file

@ -0,0 +1,5 @@
when@dev:
debug:
# Forwards VarDumper Data clones to a centralized server allowing to inspect dumps on CLI or in your browser.
# See the "server:dump" command to start a new server.
dump_destination: "tcp://%env(VAR_DUMPER_SERVER)%"

View file

@ -0,0 +1,42 @@
doctrine:
dbal:
url: '%env(resolve:DATABASE_URL)%'
# IMPORTANT: You MUST configure your server version,
# either here or in the DATABASE_URL env var (see .env file)
#server_version: '14'
orm:
auto_generate_proxy_classes: true
naming_strategy: doctrine.orm.naming_strategy.underscore_number_aware
auto_mapping: true
mappings:
App:
is_bundle: false
dir: '%kernel.project_dir%/src/Entity'
prefix: 'App\Entity'
alias: App
when@test:
doctrine:
dbal:
# "TEST_TOKEN" is typically set by ParaTest
dbname_suffix: '_test%env(default::TEST_TOKEN)%'
when@prod:
doctrine:
orm:
auto_generate_proxy_classes: false
query_cache_driver:
type: pool
pool: doctrine.system_cache_pool
result_cache_driver:
type: pool
pool: doctrine.result_cache_pool
framework:
cache:
pools:
doctrine.result_cache_pool:
adapter: cache.app
doctrine.system_cache_pool:
adapter: cache.system

View file

@ -0,0 +1,6 @@
doctrine_migrations:
migrations_paths:
# namespace is arbitrary but should be different from App\Migrations
# as migrations classes should NOT be autoloaded
'DoctrineMigrations': '%kernel.project_dir%/migrations'
enable_profiler: false

View file

@ -0,0 +1,24 @@
# see https://symfony.com/doc/current/reference/configuration/framework.html
framework:
secret: '%env(APP_SECRET)%'
#csrf_protection: true
http_method_override: false
# Enables session support. Note that the session will ONLY be started if you read or write from it.
# Remove or comment this section to explicitly disable session support.
session:
handler_id: null
cookie_secure: auto
cookie_samesite: lax
storage_factory_id: session.storage.factory.native
#esi: true
#fragments: true
php_errors:
log: true
when@test:
framework:
test: true
session:
storage_factory_id: session.storage.factory.mock_file

View file

@ -0,0 +1,17 @@
league_oauth2_server:
authorization_server:
private_key: '%env(resolve:OAUTH_PRIVATE_KEY)%'
private_key_passphrase: '%env(resolve:OAUTH_PASSPHRASE)%'
encryption_key: '%env(resolve:OAUTH_ENCRYPTION_KEY)%'
resource_server:
public_key: '%env(resolve:OAUTH_PUBLIC_KEY)%'
scopes:
available: ['email']
default: ['email']
persistence:
doctrine: null
when@test:
league_oauth2_server:
persistence:
in_memory: null

View file

@ -0,0 +1,3 @@
framework:
mailer:
dsn: '%env(MAILER_DSN)%'

View file

@ -0,0 +1,24 @@
framework:
messenger:
failure_transport: failed
transports:
# https://symfony.com/doc/current/messenger.html#transport-configuration
async:
dsn: '%env(MESSENGER_TRANSPORT_DSN)%'
options:
use_notify: true
check_delayed_interval: 60000
retry_strategy:
max_retries: 3
multiplier: 2
failed: 'doctrine://default?queue_name=failed'
# sync: 'sync://'
routing:
Symfony\Component\Mailer\Messenger\SendEmailMessage: async
# Symfony\Component\Notifier\Message\ChatMessage: async
# Symfony\Component\Notifier\Message\SmsMessage: async
# Route your messages to the transports
# 'App\Message\YourMessage': async

View file

@ -0,0 +1,61 @@
monolog:
channels:
- deprecation # Deprecations are logged in the dedicated "deprecation" channel when it exists
when@dev:
monolog:
handlers:
main:
type: stream
path: "%kernel.logs_dir%/%kernel.environment%.log"
level: debug
channels: ["!event"]
# uncomment to get logging in your browser
# you may have to allow bigger header sizes in your Web server configuration
#firephp:
# type: firephp
# level: info
#chromephp:
# type: chromephp
# level: info
console:
type: console
process_psr_3_messages: false
channels: ["!event", "!doctrine", "!console"]
when@test:
monolog:
handlers:
main:
type: fingers_crossed
action_level: error
handler: nested
excluded_http_codes: [404, 405]
channels: ["!event"]
nested:
type: stream
path: "%kernel.logs_dir%/%kernel.environment%.log"
level: debug
when@prod:
monolog:
handlers:
main:
type: fingers_crossed
action_level: error
handler: nested
excluded_http_codes: [404, 405]
buffer_size: 50 # How many messages should be saved? Prevent memory leaks
nested:
type: stream
path: php://stderr
level: debug
formatter: monolog.formatter.json
console:
type: console
process_psr_3_messages: false
channels: ["!event", "!doctrine"]
deprecation:
type: stream
channels: [deprecation]
path: php://stderr

View file

@ -0,0 +1,10 @@
nelmio_cors:
defaults:
origin_regex: true
allow_origin: ['%env(CORS_ALLOW_ORIGIN)%']
allow_methods: ['GET', 'OPTIONS', 'POST', 'PUT', 'PATCH', 'DELETE']
allow_headers: ['Content-Type', 'Authorization']
expose_headers: ['Link']
max_age: 3600
paths:
'^/': null

View file

@ -0,0 +1,16 @@
framework:
notifier:
#chatter_transports:
# slack: '%env(SLACK_DSN)%'
# telegram: '%env(TELEGRAM_DSN)%'
#texter_transports:
# twilio: '%env(TWILIO_DSN)%'
# nexmo: '%env(NEXMO_DSN)%'
channel_policy:
# use chat/slack, chat/telegram, sms/twilio or sms/nexmo
urgent: ['email']
high: ['email']
medium: ['email']
low: ['email']
admin_recipients:
- { email: admin@example.com }

View file

@ -0,0 +1,21 @@
services:
# Register nyholm/psr7 services for autowiring with PSR-17 (HTTP factories)
Psr\Http\Message\RequestFactoryInterface: '@nyholm.psr7.psr17_factory'
Psr\Http\Message\ResponseFactoryInterface: '@nyholm.psr7.psr17_factory'
Psr\Http\Message\ServerRequestFactoryInterface: '@nyholm.psr7.psr17_factory'
Psr\Http\Message\StreamFactoryInterface: '@nyholm.psr7.psr17_factory'
Psr\Http\Message\UploadedFileFactoryInterface: '@nyholm.psr7.psr17_factory'
Psr\Http\Message\UriFactoryInterface: '@nyholm.psr7.psr17_factory'
# Register nyholm/psr7 services for autowiring with HTTPlug factories
Http\Message\MessageFactory: '@nyholm.psr7.httplug_factory'
Http\Message\RequestFactory: '@nyholm.psr7.httplug_factory'
Http\Message\ResponseFactory: '@nyholm.psr7.httplug_factory'
Http\Message\StreamFactory: '@nyholm.psr7.httplug_factory'
Http\Message\UriFactory: '@nyholm.psr7.httplug_factory'
nyholm.psr7.psr17_factory:
class: Nyholm\Psr7\Factory\Psr17Factory
nyholm.psr7.httplug_factory:
class: Nyholm\Psr7\Factory\HttplugFactory

View file

@ -0,0 +1,12 @@
framework:
router:
utf8: true
# Configure how to generate URLs in non-HTTP contexts, such as CLI commands.
# See https://symfony.com/doc/current/routing.html#generating-urls-in-commands
#default_uri: http://localhost
when@prod:
framework:
router:
strict_requirements: null

View file

@ -0,0 +1,48 @@
security:
enable_authenticator_manager: true
# https://symfony.com/doc/current/security.html#registering-the-user-hashing-passwords
password_hashers:
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface: 'auto'
# https://symfony.com/doc/current/security.html#loading-the-user-the-user-provider
providers:
users_in_memory: { memory: null }
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
lazy: true
provider: users_in_memory
# activate different ways to authenticate
# https://symfony.com/doc/current/security.html#the-firewall
# https://symfony.com/doc/current/security/impersonating_user.html
# switch_user: true
api_token:
pattern: ^/api/token$
security: false
api:
pattern: ^/api
security: true
stateless: true
oauth2: true
# Easy way to control access for large sections of your site
# Note: Only the *first* access control that matches will be used
access_control:
- { path: ^/authorize, roles: IS_AUTHENTICATED_REMEMBERED }
- { path: ^/admin, roles: ROLE_ADMIN }
- { path: ^/profile, roles: ROLE_USER }
when@test:
security:
password_hashers:
# By default, password hashers are resource intensive and take time. This is
# important to generate secure password hashes. In tests however, secure hashes
# are not important, waste resources and increase test times. The following
# reduces the work factor to the lowest possible values.
Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface:
algorithm: auto
cost: 4 # Lowest possible value for bcrypt
time_cost: 3 # Lowest possible value for argon
memory_cost: 10 # Lowest possible value for argon

View file

@ -0,0 +1,13 @@
framework:
default_locale: en
translator:
default_path: '%kernel.project_dir%/translations'
fallbacks:
- en
# providers:
# crowdin:
# dsn: '%env(CROWDIN_DSN)%'
# loco:
# dsn: '%env(LOCO_DSN)%'
# lokalise:
# dsn: '%env(LOKALISE_DSN)%'

View file

@ -0,0 +1,6 @@
twig:
default_path: '%kernel.project_dir%/templates'
when@test:
twig:
strict_variables: true

4
config/packages/uid.yaml Normal file
View file

@ -0,0 +1,4 @@
framework:
uid:
default_uuid_version: 7
time_based_uuid_version: 7

View file

@ -0,0 +1,13 @@
framework:
validation:
email_validation_mode: html5
# Enables validator auto-mapping support.
# For instance, basic validation constraints will be inferred from Doctrine's metadata.
#auto_mapping:
# App\Entity\: []
when@test:
framework:
validation:
not_compromised_password: false

View file

@ -0,0 +1,17 @@
when@dev:
web_profiler:
toolbar: true
intercept_redirects: false
framework:
profiler:
only_exceptions: false
collect_serializer_data: true
when@test:
web_profiler:
toolbar: false
intercept_redirects: false
framework:
profiler: { collect: false }

5
config/preload.php Normal file
View file

@ -0,0 +1,5 @@
<?php
if (file_exists(dirname(__DIR__).'/var/cache/prod/App_KernelProdContainer.preload.php')) {
require dirname(__DIR__).'/var/cache/prod/App_KernelProdContainer.preload.php';
}

6
config/routes.yaml Normal file
View file

@ -0,0 +1,6 @@
controllers:
resource: ../src/Controller/
type: attribute
oauth2:
resource: '@LeagueOAuth2ServerBundle/Resources/config/routes.php'
type: php

View file

@ -0,0 +1,4 @@
api_platform:
resource: .
type: api_platform
prefix: /api

View file

@ -0,0 +1,4 @@
when@dev:
_errors:
resource: '@FrameworkBundle/Resources/config/routing/errors.xml'
prefix: /_error

View file

@ -0,0 +1,3 @@
oauth2_server:
resource: '@LeagueOAuth2ServerBundle/Resources/config/routes.php'
type: php

View file

@ -0,0 +1,3 @@
_security_logout:
resource: security.route_loader.logout
type: service

View file

@ -0,0 +1,8 @@
when@dev:
web_profiler_wdt:
resource: '@WebProfilerBundle/Resources/config/routing/wdt.xml'
prefix: /_wdt
web_profiler_profiler:
resource: '@WebProfilerBundle/Resources/config/routing/profiler.xml'
prefix: /_profiler

41
config/services.yaml Normal file
View file

@ -0,0 +1,41 @@
# This file is the entry point to configure your own services.
# Files in the packages/ subdirectory configure your dependencies.
# Put parameters here that don't need to change on each machine where the app is deployed
# https://symfony.com/doc/current/best_practices.html#use-parameters-for-application-configuration
parameters:
services:
# default configuration for services in *this* file
_defaults:
autowire: true # Automatically injects dependencies in your services.
autoconfigure: true # Automatically registers your services as commands, event subscribers, etc.
# makes classes in src/ available to be used as services
# this creates a service per class whose id is the fully-qualified class name
App\:
resource: '../src/'
exclude:
- '../src/DependencyInjection/'
- '../src/Entity/'
- '../src/Kernel.php'
- '../src/Access/'
- '../src/ActivityStreams/'
- '../src/Daemon/'
- '../src/Extend/'
- '../src/Identity/'
- '../src/Import/'
- '../src/Lib/'
- '../src/Module/'
- '../src/Nomad/'
- '../src/Photo/'
- '../src/Render/'
- '../src/Storage/'
- '../src/Text/'
- '../src/Thumbs/'
- '../src/Update/'
- '../src/Web/'
- '../src/Widget/'
# add more service definitions when explicit configuration is needed
# please note that last definitions always *replace* previous ones

View file

@ -3,7 +3,7 @@ AddType audio/ogg .oga
CGIPassAuth On
# don't allow any web access to logfiles, even after rotation/compression
<FilesMatch "\.(out|log|gz)$">
<FilesMatch "\.(out|log|gz|env)$">
<IfModule mod_authz_core.c>
Require all denied
</IfModule>

View file

@ -1101,7 +1101,7 @@ function attach_store($channel, $observer_hash, $options = '', $arr = null)
}
if ($notify) {
$cloudPath = z_root() . '/cloud/' . $channel['channel_address'] . '/' . $r['0']['display_path'];
$cloudPath = Channel::getDidResolver($channel) . '/cloud/' . $channel['channel_address'] . '/' . $r['0']['display_path'];
$object = get_file_activity_object($channel['channel_id'], $r['0']['hash'], $cloudPath);
file_activity($channel['channel_id'], $object, $r['0']['allow_cid'], $r['0']['allow_gid'], $r['0']['deny_cid'], $r['0']['deny_gid'], 'post', $notify);
}
@ -1245,7 +1245,7 @@ function attach_dirlist($channel, $observer, $sort_key = 'display_path', $direct
'shorttext' => (($folder['path']) ? ellipsify($folder['path'], 28) : '/'),
'jstext' => (($folder['path']) ? addslashes($folder['path']) : '/'),
'total' => $folder['total'],
'url' => z_root() . '/' . $folder['location'],
'url' => Channel::getDidResolver($channel) . '/' . $folder['location'],
'urlencode' => urlencode($folder['path']),
'bin2hex' => $folder['folder']
];

View file

@ -303,8 +303,8 @@ function mark_orphan_hubsxchans()
$r = q("update hubloc set hubloc_deleted = 1 where hubloc_deleted = 0
and hubloc_network in ('nomad','zot6') and hubloc_connected != '0001-01-01 00:00:00' and hubloc_connected < %s - interval %s",
db_utcnow(), db_quoteinterval('36 day')
and hubloc_network in ('nomad','zot6') and hubloc_connected != '0001-01-01 00:00:00' and hubloc_connected < %s - %s",
db_utcnow(), db_quoteinterval('36 day', true)
);
$r = q("select hubloc_id, hubloc_hash from hubloc where hubloc_deleted = 1 and hubloc_orphancheck = 0");
@ -589,9 +589,9 @@ function random_profile()
for ($i = 0; $i < $retryrandom; $i++) {
$r = q("select xchan_url, xchan_hash from xchan left join hubloc on hubloc_hash = xchan_hash where
xchan_hidden = 0 and xchan_network in ('nomad','zot6') and xchan_deleted = 0 and hubloc_deleted = 0
and hubloc_connected > %s - interval %s order by $randfunc limit 1",
and hubloc_connected > %s - %s order by $randfunc limit 1",
db_utcnow(),
db_quoteinterval('30 day')
db_quoteinterval('30 day', true)
);
if (!$r) {

View file

@ -23,22 +23,19 @@ use Code\Render\Theme;
function localize_item(&$item)
{
if (activity_match($item['verb'], ACTIVITY_LIKE) || activity_match($item['verb'], ACTIVITY_DISLIKE)
|| $item['verb'] === 'Announce') {
if (! $item['obj']) {
return;
}
if (intval($item['item_thread_top'])) {
return;
}
$obj = ((is_array($item['obj'])) ? $item['obj'] : json_decode($item['obj'], true));
if (! is_array($obj)) {
logger('localize_item: failed to decode object: ' . print_r($item['obj'], true));
return;
}
if (activity_match($item['verb'], ACTIVITY_LIKE) || activity_match($item['verb'], ACTIVITY_DISLIKE)
|| $item['verb'] === 'Announce') {
if (intval($item['item_thread_top'])) {
return;
}
if (isset($obj['actor']) && is_string($obj['actor']) && $obj['actor']) {
$author_link = $obj['actor'];
}
@ -141,9 +138,6 @@ function localize_item(&$item)
$Aname = $item['author']['xchan_name'];
$Alink = $item['author']['xchan_url'];
$obj = json_decode($item['obj'], true);
$Blink = $Bphoto = '';
if ($obj['link']) {
@ -177,8 +171,6 @@ function localize_item(&$item)
$Aname = $item['author']['xchan_name'];
$Alink = $item['author']['xchan_url'];
$obj = json_decode($item['obj'], true);
$Blink = $Bphoto = '';
if ($obj['link']) {
@ -798,7 +790,7 @@ function thread_author_menu($item, $mode = '')
if ($local_channel && $contact) {
$can_dm = perm_is_allowed($local_channel, $item['author_xchan'], 'post_mail') && intval($contact['xchan_type']) !== XCHAN_TYPE_GROUP ;
} elseif ($item['author']['xchan_network'] === 'activitypub') {
} elseif (in_array($item['author']['xchan_network'], ['activitypub', 'apnomadic'])) {
$can_dm = true;
}
if ($can_dm) {
@ -968,6 +960,7 @@ function builtin_activity_puller($item, &$conv_responses)
if ((activity_match($item['verb'], $verb)) && ($item['id'] != $item['parent'])) {
$name = $item['author']['xchan_name'] ?: t('Unknown');
$url = (($item['author_xchan'] && $item['author']['xchan_photo_s'])
? '<a class="dropdown-item" href="' . chanlink_hash($item['author_xchan']) . '">' . '<img class="menu-img-1" src="' . zid($item['author']['xchan_photo_s']) . '" alt="' . urlencode($name) . '" /> ' . $name . ' (' . relative_date($item['created']) .')</a>'
: '<a class="dropdown-item" href="#" class="disabled">' . $name . ' (' . relative_date($item['created']) . ')</a>'
@ -1542,6 +1535,14 @@ function conv_sort($arr, $order)
$narr = [];
foreach ($arr as $item) {
if (is_string($item['obj']) && $item['obj']) {
$local = json_decode($item['obj'], true);
if ($local !== NULL) {
$item['obj'] = $local;
}
}
// perform view filtering if viewer is logged in locally
// This allows blocking and message filters to work on public stream items
// or other channel streams on this site which are not owned by the viewer
@ -1586,10 +1587,9 @@ function conv_sort($arr, $order)
}
}
}
if ($item['obj']) {
$local = json_decode($item['obj'],true);
if (!empty($local['source']) && !empty($local['source']['mediaType']) && !empty($local['source']['content'])) {
$cnt = preg_match_all("/\[share(.*?)portable_id='(.*?)'(.*?)]/ism", $local['source']['content'], $matches, PREG_SET_ORDER);
if (is_array($item['obj'])) {
if (!empty($item['obj']['source']) && !empty($item['obj']['source']['mediaType']) && !empty($item['obj']['source']['content'])) {
$cnt = preg_match_all("/\[share(.*?)portable_id='(.*?)'(.*?)]/ism", $item['obj']['source']['content'], $matches, PREG_SET_ORDER);
if ($cnt) {
foreach ($matches as $match) {
if (LibBlock::fetch_by_entity(local_channel(), $match[2])) {

View file

@ -554,11 +554,11 @@ function update_birthdays()
$r = q(
"SELECT * FROM abook left join xchan on abook_xchan = xchan_hash
WHERE abook_dob > %s + interval %s and abook_dob < %s + interval %s",
WHERE abook_dob > %s + %s and abook_dob < %s + %s",
db_utcnow(),
db_quoteinterval('7 day'),
db_quoteinterval('7 day', true),
db_utcnow(),
db_quoteinterval('14 day')
db_quoteinterval('14 day', true)
);
if ($r) {
foreach ($r as $rr) {

View file

@ -307,9 +307,10 @@ function dbescdate($date)
return DBA::$dba->escape($date);
}
function db_quoteinterval($txt)
function db_quoteinterval($txt, $includeInterval = false)
{
return DBA::$dba->quote_interval($txt);
$returnText = $includeInterval ? 'INTERVAL ' : '';
return $returnText . DBA::$dba->quote_interval($txt);
}
function dbesc_identifier($str)

View file

@ -97,9 +97,13 @@ function format_event_obj($jobject)
$event = [];
if (! is_array($jobject)) {
if (is_array($jobject)) {
$object = $jobject;
}
else {
$object = json_decode($jobject, true);
}
/*******
This is our encoded format

View file

@ -15,10 +15,10 @@ use Code\Daemon\Run;
/**
* @brief Create an array for hubloc table and insert record.
*
* Creates an assoziative array which will be inserted into the hubloc table.
* Creates an associative array which will be inserted into the hubloc table.
*
* @param array $arr An assoziative array with hubloc values
* @return bool|PDOStatement
* @param array $arr An associative array with hubloc values
* @return bool
*/
function hubloc_store_lowlevel($arr)
{

View file

@ -1625,7 +1625,7 @@ function sync_files($channel, $files)
'(request-target)' => 'post ' . $m['path'] . '/' . $att['hash']
];
$headers = HTTPSig::create_sig($headers, $channel['channel_prvkey'], Channel::url($channel), true, 'sha512');
$headers = HTTPSig::create_sig($headers, $channel['channel_prvkey'], Channel::keyId($channel), true, 'sha512');
$x = Url::post($fetch_url . '/' . $att['hash'], $parr, [ 'filep' => $fp, 'headers' => $headers]);
@ -1717,7 +1717,7 @@ function sync_files($channel, $files)
'(request-target)' => 'post ' . $m['path'] . '/' . $att['hash']
];
$headers = HTTPSig::create_sig($headers, $channel['channel_prvkey'], Channel::url($channel), true, 'sha512');
$headers = HTTPSig::create_sig($headers, $channel['channel_prvkey'], Channel::keyId($channel), true, 'sha512');
$x = Url::post($fetch_url . '/' . $att['hash'], $parr, [ 'filep' => $fp, 'headers' => $headers]);

View file

@ -331,7 +331,7 @@ function can_comment_on_post($observer_xchan, $item)
return true;
}
if (isset($item['owner']['xchan_network']) && $item['owner']['xchan_network'] === 'activitypub') {
if (isset($item['owner']['xchan_network']) && in_array($item['owner']['xchan_network'], ['activitypub', 'apnomadic'])) {
return true;
}
break;
@ -3514,8 +3514,8 @@ function item_expire($uid,$days,$comment_days = 7) {
$r = q("SELECT id FROM item
WHERE uid = %d
AND created < %s - INTERVAL %s
AND commented < %s - INTERVAL %s
AND created < %s - %s
AND commented < %s - %s
AND item_retained = 0
AND item_thread_top = 1
AND resource_type = ''
@ -3523,9 +3523,9 @@ function item_expire($uid,$days,$comment_days = 7) {
$sql_extra $item_normal LIMIT $expire_limit ",
intval($uid),
db_utcnow(),
db_quoteinterval(intval($days) . ' DAY'),
db_quoteinterval(intval($days) . ' DAY', true),
db_utcnow(),
db_quoteinterval(intval($comment_days) . ' DAY')
db_quoteinterval(intval($comment_days) . ' DAY', true)
);
if (! $r) {

View file

@ -3802,7 +3802,7 @@ function array2XML($obj, $array)
* @param string $table
* @param array $arr
* @param array $binary_fields - fields which will be cleansed with dbescbin rather than dbesc; this is critical for postgres
* @return bool|PDOStatement
* @return bool
*/
function create_table_from_array($table, $arr, $binary_fields = [])
{

View file

@ -758,7 +758,7 @@ function discover_resource(string $resource, $protocol = '', $verify = true)
}
$results = linksByRel($webfinger['links'], 'self');
if ($results && ((! $protocol) || (strtolower($protocol) === 'activitypub')) ) {
if ($results && ((! $protocol) || (in_array(strtolower($protocol), ['activitypub', 'apnomadic'])))) {
foreach ($results as $link) {
if (isset($link['type']) && isset($link['href'])
&& ($link['type'] === 'application/activity+json' || str_contains($link['type'], 'ld+json'))) {

View file

@ -582,10 +582,6 @@ function check_deliver_permissions($item, $arr, $includeMentions = false)
return($result);
}
/**
* @brief Sets site wide default permissions.
*
@ -598,23 +594,20 @@ function site_default_perms()
$typical = [
'view_stream' => PERMS_PUBLIC,
'search_stream' => PERMS_SPECIFIC,
'deliver_stream'=> PERMS_SPECIFIC,
'view_profile' => PERMS_PUBLIC,
'view_contacts' => PERMS_PUBLIC,
'view_storage' => PERMS_PUBLIC,
'view_pages' => PERMS_PUBLIC,
'view_wiki' => PERMS_PUBLIC,
'send_stream' => PERMS_SPECIFIC,
'hyperdrive' => PERMS_SPECIFIC,
'post_wall' => PERMS_SPECIFIC,
'post_comments' => PERMS_SPECIFIC,
'post_mail' => PERMS_SPECIFIC,
'tag_deliver' => PERMS_SPECIFIC,
'chat' => PERMS_SPECIFIC,
'write_storage' => PERMS_SPECIFIC,
'write_pages' => PERMS_SPECIFIC,
'write_wiki' => PERMS_SPECIFIC,
'republish' => PERMS_SPECIFIC,
'delegate' => PERMS_SPECIFIC,
'post_like' => PERMS_NETWORK
'moderated' => PERMS_SPECIFIC,
];
$global_perms = Permissions::Perms();

View file

@ -9,6 +9,7 @@ use Code\Lib\Apps;
use Code\Lib\Activity;
use Code\Access\AccessControl;
use Code\Access\PermissionLimits;
use Code\Lib\Channel;
use Code\Lib\Time;
use Code\Web\HTTPHeaders;
use Code\Daemon\Run;
@ -266,7 +267,7 @@ function photo_upload($channel, $observer, $args)
'rel' => 'alternate',
'mediaType' => $type,
'summary' => $alt_desc,
'href' => z_root() . '/photo/' . $photo_hash . '-0.' . $ph->getExt(),
'href' => Channel::getDidResolver($channel) . '/photo/' . $photo_hash . '-0.' . $ph->getExt(),
'width' => $width,
'height' => $height
];
@ -290,7 +291,7 @@ function photo_upload($channel, $observer, $args)
'rel' => 'alternate',
'mediaType' => $type,
'summary' => $alt_desc,
'href' => z_root() . '/photo/' . $photo_hash . '-1.' . $ph->getExt(),
'href' => Channel::getDidResolver($channel) . '/photo/' . $photo_hash . '-1.' . $ph->getExt(),
'width' => $ph->getWidth(),
'height' => $ph->getHeight()
];
@ -309,7 +310,7 @@ function photo_upload($channel, $observer, $args)
'rel' => 'alternate',
'mediaType' => $type,
'summary' => $alt_desc,
'href' => z_root() . '/photo/' . $photo_hash . '-2.' . $ph->getExt(),
'href' => Channel::getDidResolver($channel) . '/photo/' . $photo_hash . '-2.' . $ph->getExt(),
'width' => $ph->getWidth(),
'height' => $ph->getHeight()
];
@ -328,7 +329,7 @@ function photo_upload($channel, $observer, $args)
'rel' => 'alternate',
'mediaType' => $type,
'summary' => $alt_desc,
'href' => z_root() . '/photo/' . $photo_hash . '-3.' . $ph->getExt(),
'href' => Channel::getDidResolver($channel) . '/photo/' . $photo_hash . '-3.' . $ph->getExt(),
'width' => $ph->getWidth(),
'height' => $ph->getHeight()
];
@ -356,7 +357,7 @@ function photo_upload($channel, $observer, $args)
'type' => 'Link',
'rel' => 'about',
'mediaType' => 'text/html',
'href' => z_root() . '/photos/' . $channel['channel_address'] . '/image/' . $photo_hash
'href' => Channel::getDidResolver($channel) . '/photos/image/' . $photo_hash
];
$item_hidden = (($visible) ? 0 : 1 );
@ -391,12 +392,12 @@ function photo_upload($channel, $observer, $args)
. "\n\n" . $alt . "\n\n"
. ']' : '[zmg' . $alt . ']');
$author_link = '[zrl=' . z_root() . '/channel/' . $channel['channel_address'] . ']' . $channel['channel_name'] . '[/zrl]';
$author_link = '[zrl=' . Channel::getDidResolver($channel, true) . ']' . $channel['channel_name'] . '[/zrl]';
$photo_link = '[zrl=' . z_root() . '/photos/' . $channel['channel_address'] . '/image/' . $photo_hash . ']' . t('a new photo') . '[/zrl]';
$photo_link = '[zrl=' . Channel::getDidResolver($channel) . '/photos/image/' . $photo_hash . ']' . t('a new photo') . '[/zrl]';
if (array_path_exists('/directory/hash',$args)) {
$album_link = '[zrl=' . z_root() . '/album/' . $channel['channel_address'] . '/' . $args['directory']['hash'] . ']' . ((strlen($album)) ? $album : '/') . '[/zrl]';
$album_link = '[zrl=' . Channel::getDidResolver($channel) . '/album/' . $args['directory']['hash'] . ']' . ((strlen($album)) ? $album : '/') . '[/zrl]';
$activity_format = sprintf(t('%1$s posted %2$s to %3$s', 'photo_upload'), $author_link, $photo_link, $album_link);
}
else {
@ -407,8 +408,8 @@ function photo_upload($channel, $observer, $args)
// If uploaded into a post, this is the text that is returned to the webapp for inclusion in the post.
$obj_body = '[zrl=' . z_root() . '/photos/' . $channel['channel_address'] . '/image/' . $photo_hash . ']'
. $tag . z_root() . "/photo/{$photo_hash}-{$scale}." . $ph->getExt() . '[/zmg]'
$obj_body = '[zrl=' . Channel::getDidResolver($channel) . '/photos/image/' . $photo_hash . ']'
. $tag . Channel::getDidResolver($channel) . "/photo/{$photo_hash}-{$scale}." . $ph->getExt() . '[/zmg]'
. '[/zrl]';
$attribution = (Activity::actorEncode(($visitor) ?: $channel, false));
@ -432,7 +433,7 @@ function photo_upload($channel, $observer, $args)
if ($public) {
$object['to'] = [ ACTIVITY_PUBLIC_INBOX ];
$object['cc'] = [ z_root() . '/followers/' . $channel['channel_address'] ];
$object['cc'] = [ Channel::getDidResolver($channel) . '/followers' ];
} else {
$object['to'] = Activity::map_acl(array_merge($ac, ['item_private' => 1]));
}
@ -440,8 +441,8 @@ function photo_upload($channel, $observer, $args)
$target = [
'type' => 'Collection',
'name' => ((strlen($album)) ? $album : '/'),
'id' => z_root() . '/album/' . $channel['channel_address'] . ((isset($args['folder'])) ? '/' . $args['folder'] : EMPTY_STR),
'attributedTo' => z_root() . '/channel/' . $channel['channel_address'],
'id' => Channel::getDidResolver($channel) . '/album/' . ((isset($args['folder'])) ? '/' . $args['folder'] : EMPTY_STR),
'attributedTo' => Channel::getDidResolver($channel, true),
];
$post_tags = [];
@ -514,7 +515,7 @@ function photo_upload($channel, $observer, $args)
}
} else {
$uuid = new_uuid();
$mid = z_root() . '/item/' . $uuid;
$mid = Channel::getDidResolver($channel) . '/item/' . $uuid;
$object['id'] = $mid;
@ -558,7 +559,7 @@ function photo_upload($channel, $observer, $args)
$arr['term'] = $post_tags;
}
$arr['plink'] = z_root() . '/channel/' . $channel['channel_address'] . '/?f=&mid=' . urlencode($arr['mid']);
$arr['plink'] = $arr['mid'];
if ($lat || $lon) {
$arr['lat'] = floatval($lat);
@ -720,7 +721,7 @@ function photos_albums_list($channel, $observer, $sort_key = 'display_path', $di
'shorttext' => (($album['album']) ? ellipsify($album['album'], 28) : '/'),
'jstext' => (($album['album']) ? addslashes($album['album']) : '/'),
'total' => $album['total'],
'url' => z_root() . '/photos/' . $channel['channel_address'] . '/album/' . $album['folder'],
'url' => Channel::getDidResolver($channel) . '/photos/album/' . $album['folder'],
'urlencode' => urlencode($album['album']),
'bin2hex' => $album['folder']
];
@ -794,7 +795,7 @@ function photos_list_photos($channel, $observer, $album = '')
if ($r) {
for ($x = 0; $x < count($r); $x++) {
$r[$x]['src'] = z_root() . '/photo/' . $r[$x]['resource_id'] . '-' . $r[$x]['imgscale'];
$r[$x]['src'] = Channel::getDidResolver($channel) . '/photo/' . $r[$x]['resource_id'] . '-' . $r[$x]['imgscale'];
}
$ret['success'] = true;
$ret['photos'] = $r;

View file

@ -745,7 +745,7 @@ function get_security_ids($channel_id, $ob_hash)
if ($xchans[0]['xchan_network'] === 'zot6') {
$groups[] = 'zot:' . $rv['channel_hash'];
}
if ($xchans[0]['xchan_network'] === 'activitypub') {
if (in_array($xchans[0]['xchan_network'], ['activitypub', 'apnomadic'])) {
$groups[] = 'activitypub:' . $rv['channel_hash'];
}
}

View file

@ -6,9 +6,49 @@ namespace Code\Web;
* @file index.php
*
* @brief The main entry point to the application.
*
*/
use App\Kernel;
use App\LegacyBridge;
use Symfony\Component\Dotenv\Dotenv;
use Symfony\Component\ErrorHandler\Debug;
use Symfony\Component\HttpFoundation\Request;
require 'vendor/autoload.php';
require_once 'src/Web/WebServer.php';
$server = new WebServer();
$server->run();
(new Dotenv())->bootEnv('.env');
global $kernel;
if ($_SERVER['APP_DEBUG']) {
umask(0000);
Debug::enable();
}
if ($trustedProxies = $_SERVER['TRUSTED_PROXIES'] ?? $_ENV['TRUSTED_PROXIES'] ?? false) {
Request::setTrustedProxies(
explode(',', $trustedProxies),
Request::HEADER_X_FORWARDED_FOR | Request::HEADER_X_FORWARDED_PORT | Request::HEADER_X_FORWARDED_PROTO
);
}
if ($trustedHosts = $_SERVER['TRUSTED_HOSTS'] ?? $_ENV['TRUSTED_HOSTS'] ?? false) {
Request::setTrustedHosts([$trustedHosts]);
}
$kernel = new Kernel($_SERVER['APP_ENV'], (bool) $_SERVER['APP_DEBUG']);
$request = Request::createFromGlobals();
$response = $kernel->handle($request);
if (false === $response->isNotFound()) {
// Symfony successfully handled the route.
$response->send();
} else {
$server = new WebServer();
$server->run($request, $response, __DIR__);
}
$kernel->terminate($request, $response);

View file

@ -18,6 +18,7 @@ $db_user = 'mysqlusername';
$db_pass = 'mysqlpassword';
$db_data = 'mysqldatabasename';
$db_type = 0; // use 1 for postgres, 0 for mysql
$db_vrsn = ''; // Required for doctrine
/*
* Notice: Many of the following settings will be available in the admin panel

0
migrations/.gitignore vendored Normal file
View file

9
public/index.php Normal file
View file

@ -0,0 +1,9 @@
<?php
use App\Kernel;
require_once dirname(__DIR__).'/vendor/autoload_runtime.php';
return function (array $context) {
return new Kernel($context['APP_ENV'], (bool) $context['APP_DEBUG']);
};

View file

@ -49,6 +49,42 @@ class Permissions
return 3;
}
public $permsMap = [
'view_stream' => 'viewStream',
'search_stream' => 'canSearch',
'deliver_stream' => 'deliverStream',
'view_profile' => 'viewProfile',
'view_contacts' => 'viewContacts',
'view_storage' => 'viewFiles',
'post_wall' => 'postHome',
'post_mail' => 'postMail',
'send_stream' => 'sendStream',
'hyperdrive' => 'sendPublic',
'post_comments' => 'canReply',
'write_storage' => 'writeFiles',
'republish' => 'canReAuthor',
'moderated' => 'isModerated',
'delegate' => 'isModerator',
];
function map($permission)
{
return $this->permsMap[$permission] ?? $permission;
}
function unmap($permission)
{
foreach ($this->permsMap as $key => $value) {
if ($permission === $value) {
return $key;
}
}
return $permission;
}
/**
* @brief Return an array with Permissions.
*
@ -298,7 +334,7 @@ class Permissions
$n = [];
if ($p) {
foreach ($p as $k => $v) {
if (intval($v)) {
if ((int)$v) {
$n[] = $k;
}
}

View file

@ -8,6 +8,7 @@ class Actor extends ASObject
public $outbox;
public $followers;
public $following;
public $permissions; /* extension property */
public $endpoints;
public $publicKey;
public $preferredUsername;
@ -23,7 +24,9 @@ class Actor extends ASObject
public $canSearch;
public $indexable;
public $assertionMethod;
public $aliases;
public $gateways;
public $openwebauth;
public $authredirect;
/**
* @return mixed
@ -353,21 +356,73 @@ class Actor extends ASObject
/**
* @return mixed
*/
public function getAliases()
public function getGateways()
{
return $this->aliases;
return $this->gateways;
}
/**
* @param mixed $aliasess
* @param mixed $gateways
* @return Actor
*/
public function setAliases($aliases)
public function setGateways($gateways)
{
$this->aliases = $aliases;
$this->gateways = $gateways;
return $this;
}
/**
* @return mixed
*/
public function getPermissions()
{
return $this->permissions;
}
/**
* @param mixed $permissions
* @return Actor
*/
public function setPermissions($permissions)
{
$this->permissions = $permissions;
return $this;
}
/**
* @return mixed
*/
public function getOpenwebauth()
{
return $this->openwebauth;
}
/**
* @param mixed $openwebauth
* @return Actor
*/
public function setOpenwebauth($openwebauth)
{
$this->openwebauth = $openwebauth;
return $this;
}
/**
* @return mixed
*/
public function getAuthredirect()
{
return $this->authredirect;
}
/**
* @param mixed $authredirect
* @return Actor
*/
public function setAuthredirect($authredirect)
{
$this->authredirect = $authredirect;
return $this;
}
}

View file

@ -4,16 +4,18 @@ namespace Code\ActivityStreams;
class Collection extends ASObject
{
public $totalItems;
public $current;
public $first;
public $last;
public $items;
public int $totalItems;
public string $current;
public string $first;
public string $last;
public array $items;
public mixed $collectionOf;
/**
* @return mixed
* @return int
*/
public function getTotalItems()
public function getTotalItems(): int
{
return $this->totalItems;
}
@ -22,16 +24,16 @@ class Collection extends ASObject
* @param mixed $totalItems
* @return Collection
*/
public function setTotalItems($totalItems)
public function setTotalItems(mixed $totalItems): static
{
$this->totalItems = $totalItems;
return $this;
}
/**
* @return mixed
* @return string
*/
public function getCurrent()
public function getCurrent(): string
{
return $this->current;
}
@ -40,16 +42,16 @@ class Collection extends ASObject
* @param mixed $current
* @return Collection
*/
public function setCurrent($current)
public function setCurrent(mixed $current): static
{
$this->current = $current;
return $this;
}
/**
* @return mixed
* @return string
*/
public function getFirst()
public function getFirst(): string
{
return $this->first;
}
@ -58,16 +60,16 @@ class Collection extends ASObject
* @param mixed $first
* @return Collection
*/
public function setFirst($first)
public function setFirst(mixed $first): static
{
$this->first = $first;
return $this;
}
/**
* @return mixed
* @return string
*/
public function getLast()
public function getLast(): string
{
return $this->last;
}
@ -76,16 +78,16 @@ class Collection extends ASObject
* @param mixed $last
* @return Collection
*/
public function setLast($last)
public function setLast(mixed $last): static
{
$this->last = $last;
return $this;
}
/**
* @return mixed
* @return array
*/
public function getItems()
public function getItems(): array
{
return $this->items;
}
@ -94,11 +96,29 @@ class Collection extends ASObject
* @param mixed $items
* @return Collection
*/
public function setItems($items)
public function setItems(mixed $items): static
{
$this->items = $items;
return $this;
}
/**
* @return mixed
*/
public function getCollectionOf(): mixed
{
return $this->collectionOf;
}
/**
* @param mixed $collectionOf
* @return Collection
*/
public function setCollectionOf(mixed $collectionOf): static
{
$this->collectionOf = $collectionOf;
return $this;
}
}

0
src/ApiResource/.gitignore vendored Normal file
View file

0
src/Controller/.gitignore vendored Normal file
View file

View file

@ -33,9 +33,9 @@ class Checksites implements DaemonInterface
}
$r = q(
"select * from site where site_dead = 0 and site_update < %s - INTERVAL %s and site_type = %d $sql_options ",
"select * from site where site_dead = 0 and site_update < %s - %s and site_type = %d $sql_options ",
db_utcnow(),
db_quoteinterval($days . ' DAY'),
db_quoteinterval($days . ' DAY', true),
intval(SITE_TYPE_ZOT)
);

View file

@ -39,7 +39,7 @@ class Content_importer implements DaemonInterface
'(request-target)' => 'get /api/z/1.0/item/export_page?f=&zap_compat=1&since=' . urlencode($since) . '&until=' . urlencode($until) . '&page=' . $page ,
];
$headers = HTTPSig::create_sig($headers, $channel['channel_prvkey'], Channel::url($channel), true, 'sha512');
$headers = HTTPSig::create_sig($headers, $channel['channel_prvkey'], Channel::keyId($channel), true, 'sha512');
$x = Url::get($hz_server . '/api/z/1.0/item/export_page?f=&zap_compat=1&since=' . urlencode($since) . '&until=' . urlencode($until) . '&page=' . $page, [ 'headers' => $headers ]);

View file

@ -92,9 +92,9 @@ class Cron implements DaemonInterface
// Ensure that every channel pings their directory occasionally.
$interval = floatval(Config::Get('system','delivery_interval', 2));
$r = q(
"select channel_id from channel where channel_dirdate < %s - INTERVAL %s and channel_removed = 0",
"select channel_id from channel where channel_dirdate < %s - %s and channel_removed = 0",
db_utcnow(),
db_quoteinterval('7 DAY')
db_quoteinterval('7 DAY', true)
);
if ($r) {
foreach ($r as $rr) {
@ -162,9 +162,9 @@ class Cron implements DaemonInterface
$r = q(
"select xchan_photo_l, xchan_hash from xchan where xchan_photo_l != '' and xchan_photo_m = ''
and xchan_photo_date < %s - INTERVAL %s",
and xchan_photo_date < %s - %s",
db_utcnow(),
db_quoteinterval('1 DAY')
db_quoteinterval('1 DAY', true)
);
if ($r) {
require_once('include/photo_factory.php');

View file

@ -42,17 +42,17 @@ class Cron_daily implements DaemonInterface
// expire any read notifications over a month old
q(
"delete from notify where seen = 1 and created < %s - INTERVAL %s",
"delete from notify where seen = 1 and created < %s - %s",
db_utcnow(),
db_quoteinterval('60 DAY')
db_quoteinterval('60 DAY', true)
);
// expire any unread notifications over a year old
q(
"delete from notify where seen = 0 and created < %s - INTERVAL %s",
"delete from notify where seen = 0 and created < %s - %s",
db_utcnow(),
db_quoteinterval('1 YEAR')
db_quoteinterval('1 YEAR', true)
);
// expire old delivery reports
@ -63,17 +63,17 @@ class Cron_daily implements DaemonInterface
}
q(
"delete from dreport where dreport_time < %s - INTERVAL %s",
"delete from dreport where dreport_time < %s - %s",
db_utcnow(),
db_quoteinterval($keep_reports . ' DAY')
db_quoteinterval($keep_reports . ' DAY', true)
);
// delete accounts that did not submit email verification within 3 days
$r = q(
"select * from register where password = 'verify' and created < %s - INTERVAL %s",
"select * from register where password = 'verify' and created < %s - %s",
db_utcnow(),
db_quoteinterval('3 DAY')
db_quoteinterval('3 DAY', true)
);
if ($r) {
foreach ($r as $rv) {

View file

@ -35,11 +35,11 @@ class Cron_weekly implements DaemonInterface
$r = q(
"select channel_id from channel where channel_removed = 1 and
channel_deleted > %s - INTERVAL %s and channel_deleted < %s - INTERVAL %s",
channel_deleted > %s - %s and channel_deleted < %s - %s",
db_utcnow(),
db_quoteinterval('21 DAY'),
db_quoteinterval('21 DAY', true),
db_utcnow(),
db_quoteinterval('10 DAY')
db_quoteinterval('10 DAY', true)
);
if ($r) {
foreach ($r as $rv) {
@ -50,9 +50,9 @@ class Cron_weekly implements DaemonInterface
// get rid of ancient poco records
q(
"delete from xlink where xlink_updated < %s - INTERVAL %s and xlink_static = 0 ",
"delete from xlink where xlink_updated < %s - %s and xlink_static = 0 ",
db_utcnow(),
db_quoteinterval('14 DAY')
db_quoteinterval('14 DAY', true)
);
// Check for dead sites

View file

@ -21,9 +21,9 @@ class Expire implements DaemonInterface
// so notifications should have been delivered.
$pending_deletes = q(
"select id from item where item_deleted = 1 and item_pending_remove = 0 and changed < %s - INTERVAL %s",
"select id from item where item_deleted = 1 and item_pending_remove = 0 and changed < %s - %s",
db_utcnow(),
db_quoteinterval('4 DAY')
db_quoteinterval('4 DAY', true)
);
if ($pending_deletes) {
foreach ($pending_deletes as $item) {

View file

@ -37,7 +37,7 @@ class File_importer implements DaemonInterface
'(request-target)' => 'get /api/z/1.0/file/export?f=&zap_compat=1&file_id=' . $attach_id,
];
$headers = HTTPSig::create_sig($headers, $channel['channel_prvkey'], Channel::url($channel), true, 'sha512');
$headers = HTTPSig::create_sig($headers, $channel['channel_prvkey'], Channel::keyId($channel), true, 'sha512');
$x = Url::get($hz_server . '/api/z/1.0/file/export?f=&zap_compat=1&file_id=' . $attach_id, [ 'headers' => $headers ]);
if (! $x['success']) {

View file

@ -34,7 +34,7 @@ class Gprobe implements DaemonInterface
if ($r) {
foreach ($r as $rv) {
if ($rv['hubloc_network'] === 'activitypub') {
if (in_array($rv['hubloc_network'], ['activitypub', 'apnomadic'])) {
$protocols[] = 'activitypub';
continue;
}

View file

@ -38,9 +38,9 @@ class Importdoc implements DaemonInterface
}
// remove old files that weren't updated (indicates they were most likely deleted).
$i = q(
"select * from item where item_type = 5 and edited < %s - INTERVAL %s",
"select * from item where item_type = 5 and edited < %s - %s",
db_utcnow(),
db_quoteinterval('14 DAY')
db_quoteinterval('14 DAY', true)
);
if ($i) {
foreach ($i as $iv) {

View file

@ -220,7 +220,7 @@ class Notifier implements DaemonInterface
$r = q(
"select abook_xchan from abook left join xchan on abook_xchan = xchan_hash
where abook_channel = %d and xchan_network != 'activitypub'",
where abook_channel = %d and not xchan_network in ('activitypub', 'apnomadic')",
intval($item_id)
);

View file

@ -167,10 +167,10 @@ class Onepoll implements DaemonInterface
$r = q(
"SELECT xlink_id from xlink
where xlink_xchan = '%s' and xlink_updated > %s - INTERVAL %s and xlink_static = 0 limit 1",
where xlink_xchan = '%s' and xlink_updated > %s - %s and xlink_static = 0 limit 1",
intval($contact['xchan_hash']),
db_utcnow(),
db_quoteinterval('7 DAY')
db_quoteinterval('7 DAY', true)
);
if (! $r) {
Socgraph::poco_load($contact['xchan_hash'], $contact['xchan_connurl']);

View file

@ -18,27 +18,27 @@ class Queue implements DaemonInterface
// delete all queue items more than 3 days old
// but first mark these sites dead if we haven't heard from them in a month
$oldqItems = q("select outq_posturl from outq where outq_created < %s - INTERVAL %s",
$oldqItems = q("select outq_posturl from outq where outq_created < %s - %s",
db_utcnow(),
db_quoteinterval('3 DAY')
db_quoteinterval('3 DAY', true)
);
if ($oldqItems) {
foreach ($oldqItems as $qItem) {
$h = parse_url($qItem['outq_posturl']);
$site_url = $h['scheme'] . '://' . $h['host'] . (($h['port']) ? ':' . $h['port'] : '');
q("update site set site_dead = 1 where site_dead = 0 and site_url = '%s' and site_update < %s - INTERVAL %s",
q("update site set site_dead = 1 where site_dead = 0 and site_url = '%s' and site_update < %s - %s",
dbesc($site_url),
db_utcnow(),
db_quoteinterval('1 MONTH')
db_quoteinterval('1 MONTH', true)
);
}
logger('Removing ' . count($oldqItems) . ' old queue entries');
}
q("DELETE FROM outq WHERE outq_created < %s - INTERVAL %s",
q("DELETE FROM outq WHERE outq_created < %s - %s",
db_utcnow(),
db_quoteinterval('3 DAY')
db_quoteinterval('3 DAY', true)
);
$deliveries = [];

View file

@ -0,0 +1,17 @@
<?php
namespace App\DataFixtures;
use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Persistence\ObjectManager;
class AppFixtures extends Fixture
{
public function load(ObjectManager $manager): void
{
// $product = new Product();
// $manager->persist($product);
$manager->flush();
}
}

0
src/Entity/.gitignore vendored Normal file
View file

12
src/Kernel.php Normal file
View file

@ -0,0 +1,12 @@
<?php
namespace App;
use Symfony\Bundle\FrameworkBundle\Kernel\MicroKernelTrait;
use Symfony\Component\HttpKernel\Kernel as BaseKernel;
class Kernel extends BaseKernel
{
use MicroKernelTrait;
}

View file

@ -272,7 +272,7 @@ class AccessList
$sql_extra = " and xchan_network in ('nomad','zot6') ";
break;
case '3':
$sql_extra = " and xchan_network = 'activitypub' ";
$sql_extra = " and xchan_network in ('activitypub', 'apnomadic') ";
break;
case '1':
default:
@ -473,7 +473,7 @@ class AccessList
$sql_extra = " and xchan_network in ('nomad','zot6') ";
}
if (str_starts_with($gv, 'activitypub:')) {
$sql_extra = " and xchan_network = 'activitypub' ";
$sql_extra = " and xchan_network in ('activitypub', 'apnomadic') ";
}
$r = q(
"select channel_id from channel where channel_hash = '%s' ",

View file

@ -181,11 +181,11 @@ class Activity
$site_url = unparse_url(['scheme' => $parsed['scheme'], 'host' => $parsed['host'], 'port' => ((array_key_exists('port', $parsed) && intval($parsed['port'])) ? $parsed['port'] : 0)]);
q(
"update site set site_update = '%s' where site_url = '%s' and site_update < %s - INTERVAL %s",
"update site set site_update = '%s' where site_url = '%s' and site_update < %s - %s",
dbesc(Time::convert()),
dbesc($site_url),
db_utcnow(),
db_quoteinterval('1 DAY')
db_quoteinterval('1 DAY', true)
);
// check for a valid signature, but only if this is not an actor object. If it is signed, it must be valid.
@ -290,7 +290,7 @@ class Activity
return false;
}
public static function paged_collection_init($total, $id, $type = 'OrderedCollection', $attributedTo = ''): array
public static function paged_collection_init($total, $id, $type = 'OrderedCollection', $collectionOf = '', $attributedTo = ''): array
{
$ret = [
@ -298,6 +298,9 @@ class Activity
'type' => $type,
'totalItems' => $total,
];
if ($collectionOf) {
$ret['collectionOf'] = $collectionOf;
}
$numpages = $total / App::$pager['itemspage'];
$lastpage = (($numpages > intval($numpages)) ? intval($numpages) + 1 : $numpages);
@ -351,6 +354,7 @@ class Activity
$ret = [
'id' => z_root() . '/' . $id,
'type' => $type,
'collectionOf' => 'Activity',
'totalItems' => $total,
];
if ($attributedTo) {
@ -464,6 +468,7 @@ class Activity
$ret = [
'id' => z_root() . '/' . $id,
'type' => $type . 'Page',
'collectionOf' => 'actor',
];
$numpages = $total / App::$pager['itemspage'];
@ -484,6 +489,7 @@ class Activity
$ret = [
'id' => z_root() . '/' . $id,
'type' => $type,
'collectionOf' => 'actor',
'totalItems' => $total,
];
}
@ -495,7 +501,7 @@ class Activity
if ($items) {
$x = [];
foreach ($items as $i) {
if ($i['xchan_network'] === 'activitypub') {
if (in_array($i['xchan_network'], ['activitypub', 'apnomadic'])) {
$x[] = $i['xchan_hash'];
} else {
$x[] = $i['xchan_url'];
@ -512,12 +518,33 @@ class Activity
return $ret;
}
public static function map_permissions(array $permissions): array
{
$perm = new Permissions();
$returnValue = [];
foreach($permissions as $permission) {
$returnValue[] = 'https://purl.org/nomad#' . $perm->map($permission);
}
return $returnValue;
}
public static function encode_simple_collection($items, $id, $type, $total = 0, $extra = null): array
public static function unmap_permissions(array $permissions): array
{
$perm = new Permissions();
$returnValue = [];
foreach($permissions as $permission) {
$returnValue[] = $perm->unmap(trim('#', strstr($permission, '#')));
}
return $returnValue;
}
public static function encode_simple_collection($items, $id, $type, $total = 0, array $extra = []): array
{
$ret = [
'id' => z_root() . '/' . $id,
'id' => $id,
'type' => $type,
'totalItems' => $total,
];
@ -1081,6 +1108,7 @@ class Activity
}
}
$activity = self::encodeAddressing($activity, $item);
return $activity;
@ -1096,7 +1124,6 @@ class Activity
return $locations;
}
public static function encode_item($item, $activitypub = false)
{
$activity = [];
@ -1187,7 +1214,7 @@ class Activity
}
}
$activity['attributedTo'] = self::actorEncode($item['author'],false);
$activity['attributedTo'] = Channel::getDidResolver($item['author'], true);
if ($item['mid'] === $item['parent_mid']) {
if (in_array($item['comment_policy'], ['self', 'none']) || $item['item_nocomment'] || ($item['comments_closed'] > NULL_DATE && Time::convert('UTC', 'UTC', $item['comments_closed']) <= Time::convert())) {
@ -1195,7 +1222,7 @@ class Activity
} elseif (in_array($item['comment_policy'], ['public', 'authenticated'])) {
$activity['canReply'] = [ACTIVITY_PUBLIC_INBOX];
} elseif (in_array($item['comment_policy'], ['contacts', 'specific'])) {
$activity['canReply'] = [z_root() . '/followers/' . substr($item['author']['xchan_addr'], 0, strpos($item['author']['xchan_addr'], '@'))];
$activity['canReply'] = [Channel::getDidResolver($item['author']) . '/followers'];
}
}
@ -1445,6 +1472,7 @@ class Activity
if (isset($parent_i['to']) && is_array($parent_i['to'])) {
$activity['to'] = array_merge_clean($activity['to'], $parent_i['to']);
}
} elseif ((int)$item['item_private'] === 1) {
if (($audience & AUDIENCE_FOLLOWERS) && $item['item_origin']) {
$activity['to'] = $followers;
@ -1557,7 +1585,7 @@ class Activity
$split = explode(':', $t, 2);
$listChannel = Channel::from_hash($split[1]);
if ($listChannel) {
$ret[] = z_root() . '/followers/' . $listChannel['channel_address'];
$ret[] = Channel::getDidResolver($listChannel) . '/followers' ;
}
else {
$ret[] = z_root() . '/lists/' . $t;
@ -1603,17 +1631,49 @@ class Activity
return array_values(array_unique($ret));
}
public static function portableActor($arr)
{
$output = [];
foreach ($arr as $key => $value) {
if ($key === 'webfinger') {
continue;
}
if (is_array($value)) {
$output[$key] = self::portableActor($value);
} elseif (is_string($value)) {
$output[$key] = str_replace(z_root() . '/.well-known/apgateway/', 'ap://', $value);
} else {
$output[$key] = $value;
}
}
return $output;
}
/**
* @throws UnhandledElementException
*/
public static function actorEncode($p, $extended = true, $activitypub = false)
public static function actorEncode($p, $extended = true, $activitypub = false, $export = false, $legacy = false)
{
$actor = new Actor();
$currhub = false;
$aliases =[];
$gateways =[];
$nomadic = false;
$channel = ((array_key_exists('channel_id', $p)) ? $p : Channel::from_hash($p['xchan_hash']));
if ($channel) {
$nomadic = (int)PConfig::Get($channel['channel_id'], 'system', 'nomadicAP');
if ($legacy) {
$nomadic = false;
}
if ($nomadic && !$extended) {
return Channel::getDidResolver($channel, true);
}
if ($export) {
$nomadic = true;
}
}
if (!$p['xchan_url']) {
return $actor->toArray();
@ -1638,30 +1698,52 @@ class Activity
return $current_url;
}
$c = ((array_key_exists('channel_id', $p)) ? $p : Channel::from_hash($p['xchan_hash']));
$actor->setType('Person');
$auto_follow = false;
if ($c) {
$role = PConfig::Get($c['channel_id'], 'system', 'permissions_role');
if ($channel) {
$role = PConfig::Get($channel['channel_id'], 'system', 'permissions_role');
if (str_contains($role, 'forum')) {
$actor->setType('Group');
}
$auto_follow = intval(PConfig::Get($c['channel_id'],'system','autoperms'));
$nomadic = (int) PConfig::Get($c['channel_id'], 'system', 'nomadicAP');
$auto_follow = intval(PConfig::Get($channel['channel_id'],'system','autoperms'));
}
if ($c) {
$locations = self::nomadic_locations(['author_xchan' => $c['channel_hash']]);
$aka = [];
if ($channel) {
$locations = self::nomadic_locations(['author_xchan' => $channel['channel_hash']]);
$aplocations = self::nomadic_locations(['author_xchan' => Channel::getDid($channel)]);
$aka[] = Channel::getDidResolver($channel, true);
$aka[] = z_root() . '/.well-known/nomad-gateway/' . $channel['xchan_hash'] . '/actor';
$aka[] = z_root() . '/channel/' . $channel['channel_address'];
$gateways[] = z_root();
if ($locations) {
foreach ($locations as $location) {
$aliases[] = $location['hubloc_id_url'];
if ($location['hubloc_url'] === z_root()) {
continue;
}
$actor->setAliases($aliases);
if (! in_array($location['hubloc_url'], $gateways)) {
$gateways[] = $location['hubloc_url'];
}
if (! in_array($location['hubloc_url'] . '/.well-known/nomad-gateway/' . $location['hubloc_hash'] . '/actor', $aka)) {
$aka[] = $location['hubloc_url'] . '/.well-known/nomad-gateway/' . $location['hubloc_hash'] . '/actor';
}
if (! in_array($location['hubloc_id_url'], $aka)) {
$aka[] = $location['hubloc_id_url'];
}
}
foreach ($aplocations as $location) {
if (! in_array($location['hubloc_url'], $gateways)) {
$gateways[] = $location['hubloc_url'];
}
if (! in_array($location['hubloc_id_url'], $aka)) {
$aka[] = $location['hubloc_id_url'];
}
}
$actor->setAlsoKnownAs($aka);
$actor->setGateways($gateways);
}
$actor->setId($nomadic ? Channel::getDidResolver($channel, true) : Channel::url($channel));
$actor->setId($nomadic ? Channel::getDidResolver($c) : Channel::url($c));
} else {
$actor->setId((str_starts_with($p['xchan_hash'], 'http')) ? $p['xchan_hash'] : $current_url);
}
@ -1685,40 +1767,42 @@ class Activity
$actor->setLocation((new Place(['type' => 'Place', 'name' => $p['channel_location']])));
}
$tag = [
['type' => 'Note', 'name' => 'Protocol', 'content' => 'zot6'],
['type' => 'Note', 'name' => 'Protocol', 'content' => 'nomad']
];
$protocols = [ 'nomad', 'zot6'];
$tags = [];
if ($activitypub && get_config('system', 'activitypub', ACTIVITYPUB_ENABLED)) {
if ($c) {
if (get_pconfig($c['channel_id'], 'system', 'activitypub', ACTIVITYPUB_ENABLED)) {
$actor->setInbox($nomadic ? Channel::getDidResolver($c) . '/inbox' : z_root() . '/inbox/' . $c['channel_address']);
$tag[] = ['type' => 'Note', 'name' => 'Protocol', 'content' => 'activitypub'];
if ($channel) {
if (get_pconfig($channel['channel_id'], 'system', 'activitypub', ACTIVITYPUB_ENABLED)) {
$actor->setInbox($nomadic ? Channel::getDidResolver($channel) . '/actor/inbox' : z_root() . '/inbox/' . $channel['channel_address']);
$protocols[] = 'activitypub';
}
$actor->setOutbox($nomadic ? Channel::getDidResolver($c) . '/outbox' : z_root() . '/outbox/' . $c['channel_address']);
$actor->setFollowers($nomadic ? Channel::getDidResolver($c) . '/followers' : z_root() . '/followers/' . $c['channel_address']);
$actor->setFollowing($nomadic ? Channel::getDidResolver($c) . '/following' : z_root() . '/following/' . $c['channel_address']);
$actor->setWebfinger('acct:' . $c['channel_address'] . '@' . App::get_hostname());
$tags[] = ['type' => 'Note', 'name' => 'Protocols', 'content' => implode(',', $protocols)];
$actor->setOutbox($nomadic ? Channel::getDidResolver($channel) . '/actor/outbox' : z_root() . '/outbox/' . $channel['channel_address']);
$actor->setFollowers($nomadic ? Channel::getDidResolver($channel) . '/actor/followers' : z_root() . '/followers/' . $channel['channel_address']);
$actor->setFollowing($nomadic ? Channel::getDidResolver($channel) . '/actor/following' : z_root() . '/following/' . $channel['channel_address']);
$actor->setPermissions($nomadic ? Channel::getDidResolver($channel) . '/actor/permissions' : z_root() . '/permissions/' . $channel['channel_address']);
$actor->setWebfinger('acct:' . $channel['channel_address'] . '@' . App::get_hostname());
$actor->setEndpoints([
'sharedInbox' => z_root() . '/inbox',
'oauthRegistrationEndpoint' => z_root() . '/api/client/register',
'oauthAuthorizationEndpoint' => z_root() . '/authorize',
'oauthTokenEndpoint' => z_root() . '/token',
'searchContent' => z_root() . '/search/' . $c['channel_address'] . '?search={}',
'searchTags' => z_root() . '/search/' . $c['channel_address'] . '?tag={}',
'searchContent' => z_root() . '/search/' . $channel['channel_address'] . '?search={}',
'searchTags' => z_root() . '/search/' . $channel['channel_address'] . '?tag={}',
'openwebauth' => z_root() . '/owa',
'authredirect' => z_root() . '/magic'
]);
$actor->setDiscoverable((bool)((1 - intval($p['xchan_hidden']))));
$searchPerm = PermissionLimits::Get($c['channel_id'], 'search_stream');
$searchPerm = PermissionLimits::Get($channel['channel_id'], 'search_stream');
if ($searchPerm === PERMS_PUBLIC) {
$actor->setCanSearch([ ACTIVITY_PUBLIC_INBOX ]);
$actor->setIndexable(true);
}
elseif (in_array($searchPerm, [ PERMS_SPECIFIC, PERMS_CONTACTS])) {
$actor->setCanSearch([z_root() . '/followers/' . $c['channel_address']]);
$actor->setCanSearch([z_root() . '/followers/' . $channel['channel_address']]);
$actor->setIndexable(false);
}
else {
@ -1733,18 +1817,18 @@ class Activity
$actor->setPublicKey([
'id' => $current_url . '?operation=rsakey',
'owner' => $current_url,
'id' => (($nomadic) ? Channel::getDidResolver($channel, true) : $current_url) . '#rsakey',
'owner' => $nomadic ? Channel::getDidResolver($channel, true) : $current_url,
'signatureAlgorithm' => 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256',
'publicKeyPem' => $p['xchan_pubkey']
]);
$ed25519publicKey = (new Multibase())->publicKey($c['channel_epubkey']);
$ed25519publicKey = (new Multibase())->publicKey($channel['channel_epubkey']);
$actor->setAssertionMethod(new AssertionMethod([
[
'id' => $nomadic ? Channel::getDidResolver($c) . '?operation=ed25519key' : $current_url . '#' . $ed25519publicKey,
'id' => $nomadic ? Channel::getDidResolver($channel, true) . '#ed25519key' : $current_url . '#' . $ed25519publicKey,
'type' => 'Multikey',
'controller' => $nomadic ? Channel::getDidResolver($c) : $current_url,
'controller' => $nomadic ? Channel::getDid($channel) : $current_url,
'publicKeyMultibase' => $ed25519publicKey,
]
]));
@ -1755,7 +1839,7 @@ class Activity
$locations = [];
$nomadicLocations = [];
$locs = Libzot::encode_locations($c);
$locs = Libzot::encode_locations($channel);
if ($locs) {
foreach ($locs as $loc) {
if ($loc['url'] !== z_root()) {
@ -1770,10 +1854,10 @@ class Activity
'id' => $loc->getIdUrl(),
'url' => $loc->getIdUrl(),
'signature' => [
'id' => $loc->getIdUrl() . '?operation=rsakey',
'id' => $loc->getIdUrl() . '#rsakey',
'nonce' => random_string(),
'creator' => $loc->getIdUrl(),
'signature' => base64_encode(Crypto::sign($loc->getIdUrl(), $c['channel_prvkey'])),
'signature' => base64_encode(Crypto::sign($loc->getIdUrl(), $channel['channel_prvkey'])),
],
];
$nomadicLocations[] = $entry;
@ -1795,13 +1879,13 @@ class Activity
// And set the value to the URL of your Mastodon profile.
// Then go back to Mastodon and move your account.
$move_id = PConfig::Get($c['channel_id'],'system','movefrom');
$move_id = PConfig::Get($channel['channel_id'],'system','movefrom');
if ($move_id) {
$actor->setMovedTo(z_root() . '/channel/' . $c['channel_address']);
$actor->setMovedTo(z_root() . '/channel/' . $channel['channel_address']);
$actor->setAlsoKnownAs($move_id);
}
$cp = Channel::get_cover_photo($c['channel_id'], 'array');
$cp = Channel::get_cover_photo($channel['channel_id'], 'array');
if ($cp) {
$actor->setImage([
'type' => 'Image',
@ -1810,10 +1894,10 @@ class Activity
]);
}
// only fill in profile information if the profile is publicly visible
if (perm_is_allowed($c['channel_id'], EMPTY_STR, 'view_profile')) {
if (perm_is_allowed($channel['channel_id'], EMPTY_STR, 'view_profile')) {
$dp = q(
"select * from profile where uid = %d and is_default = 1",
intval($c['channel_id'])
intval($channel['channel_id'])
);
if ($dp) {
if ($dp[0]['about']) {
@ -1846,9 +1930,9 @@ class Activity
foreach ($kw as $k) {
$k = trim($k);
$k = trim($k, '#,');
$tag = $actor->getTag();
$tag[] = ['type' => 'Hashtag', 'id' => z_root() . '/search?tag=' . urlencode($k), 'name' => '#' . urlencode($k)];
$actor->setTag($tag);
$tags = $actor->getTag();
$tags[] = ['type' => 'Hashtag', 'id' => z_root() . '/search?tag=' . urlencode($k), 'name' => '#' . urlencode($k)];
$actor->setTag($tags);
}
}
}
@ -1857,21 +1941,24 @@ class Activity
} else {
$collections = get_xconfig($p['xchan_hash'], 'activitypub', 'collections', []);
if ($collections) {
$actor = array_merge($actor, $collections);
$actor = array_merge($actor->toArray(), $collections);
}
}
} else {
$actor->setPublicKey([
'id' => $current_url,
'owner' => $current_url,
'id' => (($nomadic) ? Channel::getDidResolver($channel, true) : $current_url) . '#rsakey',
'owner' => $nomadic ? Channel::getDidResolver($channel, true) : $current_url,
'signatureAlgorithm' => 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256',
'publicKeyPem' => $p['xchan_pubkey']
]);
$tags[] = ['type' => 'Note', 'name' => 'Protocols', 'content' => implode(',', $protocols)];
}
$actor->setTag($tag);
$actor->setTag($tags);
$arr = ['xchan' => $p, 'encoded' => $actor->toArray(), 'activitypub' => $activitypub];
Hook::call('encode_person', $arr);
return $arr['encoded'];
return $export ? self::portableActor($arr['encoded']) : $arr['encoded'];
}
@ -2133,7 +2220,7 @@ class Activity
return;
}
// From here on out we assume a Follow activity to somebody we have no existing relationship with
// From here on out we assume a Follow activity from somebody we have no existing relationship with
set_abconfig($channel['channel_id'], $person_obj['id'], 'activitypub', 'their_follow_id', $their_follow_id);
set_abconfig($channel['channel_id'], $person_obj['id'], 'activitypub', 'their_follow_type', $act->type);
@ -2280,19 +2367,19 @@ class Activity
}
public static function actor_store($url, $person_obj, $webfinger = null, $force = false)
public static function actor_store($url, $actorRecord, $webfinger = null, $force = false)
{
if (!is_array($person_obj)) {
if (!is_array($actorRecord)) {
return;
}
// logger('person_obj: ' . print_r($person_obj,true));
if (array_key_exists('movedTo', $person_obj) && $person_obj['movedTo'] && !is_array($person_obj['movedTo'])) {
$tgt = self::fetch($person_obj['movedTo']);
if (array_key_exists('movedTo', $actorRecord) && $actorRecord['movedTo'] && !is_array($actorRecord['movedTo'])) {
$tgt = self::fetch($actorRecord['movedTo']);
if (is_array($tgt)) {
self::actor_store($person_obj['movedTo'], $tgt);
ActivityPub::move($person_obj['id'], $tgt);
self::actor_store($actorRecord['movedTo'], $tgt);
ActivityPub::move($actorRecord['id'], $tgt);
}
return;
}
@ -2315,9 +2402,9 @@ class Activity
if ($ap_hubloc) {
// we already have a stored record. Determine if it needs updating.
if ($ap_hubloc['hubloc_updated'] < Time::convert('UTC', 'UTC', ' now - ' . self::ACTOR_CACHE_DAYS . ' days') || $force) {
$person_obj = self::fetch($url);
$actorRecord = self::fetch($url);
// ensure we received something
if (!is_array($person_obj)) {
if (!is_array($actorRecord)) {
return;
}
} else {
@ -2326,8 +2413,8 @@ class Activity
}
if (isset($person_obj['id'])) {
$url = $person_obj['id'];
if (isset($actorRecord['id'])) {
$url = $actorRecord['id'];
}
if (!$url) {
@ -2336,40 +2423,41 @@ class Activity
$actorId = new ActorId($url);
$url = $actorId->getId();
$isDid = $actorId->getType() === ActorId::ACTORID_TYPE_DID;
$isDid = in_array($actorId->getType(), [ActorId::ACTORID_TYPE_DIDKEY, ActorId::ACTORID_TYPE_DIDWEB]);
if ($isDid) {
$aliases = $person_obj['aliases'];
self::updateLocations($url, $actorRecord);
}
// store the actor record in XConfig
XConfig::Set($url, 'system', 'actor_record', $person_obj);
XConfig::Set($url, 'system', 'actor_record', $actorRecord);
$name = unicode_trim(escape_tags($person_obj['name']));
$name = unicode_trim(escape_tags($actorRecord['name']));
if (!$name) {
$name = escape_tags($person_obj['preferredUsername']);
$name = escape_tags($actorRecord['preferredUsername']);
}
if (!$name) {
$name = escape_tags(t('Unknown'));
}
$webfingerAddress = EMPTY_STR;
$username = escape_tags($person_obj['preferredUsername']);
$username = escape_tags($actorRecord['preferredUsername']);
$h = parse_url($url);
if ($h && $h['host']) {
$webfingerAddress = $username . '@' . $h['host'];
}
$icon = self::getIcon($person_obj['icon']);
$icon = self::getIcon($actorRecord['icon']);
$cover_photo = false;
if (isset($person_obj['image'])) {
if (is_string($person_obj['image'])) {
$cover_photo = $person_obj['image'];
if (isset($actorRecord['image'])) {
if (is_string($actorRecord['image'])) {
$cover_photo = $actorRecord['image'];
}
if (isset($person_obj['image']['url'])) {
$ptr = $person_obj['image']['url'];
if (isset($actorRecord['image']['url'])) {
$ptr = $actorRecord['image']['url'];
if (is_string($ptr)) {
$cover_photo = $ptr;
}
@ -2389,22 +2477,22 @@ class Activity
$hidden = false;
// Mastodon style hidden flag
if (array_key_exists('discoverable', $person_obj) && (!intval($person_obj['discoverable']))) {
if (array_key_exists('discoverable', $actorRecord) && (!intval($actorRecord['discoverable']))) {
$hidden = true;
}
// Pleroma style hidden flag
if (array_key_exists('invisible', $person_obj) && (!intval($person_obj['invisible']))) {
if (array_key_exists('invisible', $actorRecord) && (!intval($actorRecord['invisible']))) {
$hidden = true;
}
$links = false;
$profile = false;
if (is_array($person_obj['url'])) {
if (!array_key_exists(0, $person_obj['url'])) {
$links = [$person_obj['url']];
if (is_array($actorRecord['url'])) {
if (!array_key_exists(0, $actorRecord['url'])) {
$links = [$actorRecord['url']];
} else {
$links = $person_obj['url'];
$links = $actorRecord['url'];
}
}
@ -2420,15 +2508,15 @@ class Activity
if (!$profile) {
$profile = $links[0]['href'];
}
} elseif (isset($person_obj['url']) && is_string($person_obj['url'])) {
$profile = $person_obj['url'];
} elseif (isset($actorRecord['url']) && is_string($actorRecord['url'])) {
$profile = $actorRecord['url'];
}
if (!$profile) {
$profile = $url;
}
$inbox = ((array_key_exists('inbox', $person_obj)) ? $person_obj['inbox'] : null);
$inbox = ((array_key_exists('inbox', $actorRecord)) ? $actorRecord['inbox'] : null);
// either an invalid identity or a cached entry of some kind which didn't get caught above
@ -2439,38 +2527,38 @@ class Activity
$collections = [];
$collections['inbox'] = $inbox;
if (array_key_exists('outbox', $person_obj) && is_string($person_obj['outbox'])) {
$collections['outbox'] = $person_obj['outbox'];
if (array_key_exists('outbox', $actorRecord) && is_string($actorRecord['outbox'])) {
$collections['outbox'] = $actorRecord['outbox'];
}
if (array_key_exists('followers', $person_obj) && is_string($person_obj['followers'])) {
$collections['followers'] = $person_obj['followers'];
if (array_key_exists('followers', $actorRecord) && is_string($actorRecord['followers'])) {
$collections['followers'] = $actorRecord['followers'];
}
if (array_key_exists('following', $person_obj) && is_string($person_obj['following'])) {
$collections['following'] = $person_obj['following'];
if (array_key_exists('following', $actorRecord) && is_string($actorRecord['following'])) {
$collections['following'] = $actorRecord['following'];
}
if (array_path_exists('endpoints/sharedInbox', $person_obj) && is_string($person_obj['endpoints']['sharedInbox'])) {
$collections['sharedInbox'] = $person_obj['endpoints']['sharedInbox'];
if (array_path_exists('endpoints/sharedInbox', $actorRecord) && is_string($actorRecord['endpoints']['sharedInbox'])) {
$collections['sharedInbox'] = $actorRecord['endpoints']['sharedInbox'];
}
if (array_path_exists('endpoints/searchContent', $person_obj) && is_string($person_obj['endpoints']['searchContent'])) {
$collections['searchContent'] = $person_obj['endpoints']['searchContent'];
if (array_path_exists('endpoints/searchContent', $actorRecord) && is_string($actorRecord['endpoints']['searchContent'])) {
$collections['searchContent'] = $actorRecord['endpoints']['searchContent'];
}
if (array_path_exists('endpoints/searchTags', $person_obj) && is_string($person_obj['endpoints']['searchTags'])) {
$collections['searchTags'] = $person_obj['endpoints']['searchTags'];
if (array_path_exists('endpoints/searchTags', $actorRecord) && is_string($actorRecord['endpoints']['searchTags'])) {
$collections['searchTags'] = $actorRecord['endpoints']['searchTags'];
}
if (isset($person_obj['publicKey']['publicKeyPem'])) {
if ($person_obj['id'] === $person_obj['publicKey']['owner']) {
$pubkey = $person_obj['publicKey']['publicKeyPem'];
if (isset($actorRecord['publicKey']['publicKeyPem'])) {
if ($actorRecord['id'] === $actorRecord['publicKey']['owner']) {
$pubkey = $actorRecord['publicKey']['publicKeyPem'];
if (str_contains($pubkey, 'RSA ')) {
$pubkey = Keyutils::rsaToPem($pubkey);
}
}
}
$epubkey = self::getEddsaPublicKey($person_obj);
$epubkey = self::getEddsaPublicKey($actorRecord);
$keywords = [];
if (isset($person_obj['tag']) && is_array($person_obj['tag'])) {
foreach ($person_obj['tag'] as $t) {
if (isset($actorRecord['tag']) && is_array($actorRecord['tag'])) {
foreach ($actorRecord['tag'] as $t) {
if (is_array($t) && isset($t['type']) && $t['type'] === 'Hashtag') {
if (isset($t['name'])) {
$tag = escape_tags((str_starts_with($t['name'], '#')) ? substr($t['name'], 1) : $t['name']);
@ -2487,8 +2575,8 @@ class Activity
}
}
$xchan_type = self::get_xchan_type($person_obj['type']);
$about = ((isset($person_obj['summary'])) ? html2bbcode(purify_html($person_obj['summary'])) : EMPTY_STR);
$xchan_type = self::get_xchan_type($actorRecord['type']);
$about = ((isset($actorRecord['summary'])) ? html2bbcode(purify_html($actorRecord['summary'])) : EMPTY_STR);
$p = q(
"select * from xchan where xchan_url = '%s' and xchan_network in ('zot6','nomad') limit 1",
@ -2731,6 +2819,51 @@ class Activity
Run::Summon(['Xchan_photo', bin2hex($icon), bin2hex($url)]);
}
public static function updateLocations($portableId, $actorRecord)
{
$gateways = $actorRecord['gateways'];
if (!is_array($gateways)) {
return;
}
$count = 0;
foreach ($gateways as $gateway) {
$parsedUrl = parse_url($gateway);
$prefix = $gateway . '/.well-known/apgateway/' . $portableId;
$machineName = $actorRecord['preferredUsername'] ?? 'unknown';
$existing = q("select * from hubloc where hubloc_hash = '%s' and hubloc_url = '%s' and hubloc_network = '%s'",
dbesc($portableId),
dbesc($gateway),
dbesc('apnomadic')
);
$record = [
'hubloc_guid' => $portableId,
'hubloc_hash' => $portableId,
'hubloc_id_url' => $prefix . '/actor',
'hubloc_addr' => $machineName . '@' . $parsedUrl['host'],
'hubloc_network' => 'apnomadic',
'hubloc_url' => $gateway,
'hubloc_host' => $parsedUrl['host'],
'hubloc_callback' => $prefix . '/actor/inbox',
'hubloc_updated' => Time::convert(),
'hubloc_primary' => ($count ? 0 : 1)
];
if ($existing) {
$record['hubloc_id'] = $existing[0]['hubloc_id'];
}
hubloc_store_lowlevel($record);
$count ++;
}
}
public static function update_protocols($xchan, $str)
{
$existing = explode(',', get_xconfig($xchan, 'system', 'protocols', EMPTY_STR));
@ -5060,12 +5193,12 @@ class Activity
->setParentMid(str_replace('/conversation/','/item/', $target))
->setThrParent(str_replace('/conversation/','/item/', $target))
->setApproved($object['object']['id'] ?? '')
->setReplyto(z_root() . '/channel/' . $channel['channel_address'])
->setReplyto(Channel::getDidResolver($channel, true))
->setTgtType('Collection')
->setTarget([
'id' => str_replace('/item/','/conversation/', $target),
'type' => 'Collection',
'attributedTo' => z_root() . '/channel/' . $channel['channel_address']
'attributedTo' => Channel::getDidResolver($channel, true)
]
)
);
@ -5097,12 +5230,12 @@ class Activity
->setObjType($object['type'])
->setParentMid(str_replace('/conversation/','/item/', $target))
->setThrParent(str_replace('/conversation/','/item/', $target))
->setReplyto(z_root() . '/channel/' . $channel['channel_address'])
->setReplyto(Channel::getDidResolver($channel, true))
->setTgtType('Collection')
->setTarget([
'id' => str_replace('/item/','/conversation/', $target),
'type' => 'Collection',
'attributedTo' => z_root() . '/channel/' . $channel['channel_address']
'attributedTo' => Channel::getDidResolver($channel, true)
]
)
);
@ -5119,10 +5252,6 @@ class Activity
'https://www.w3.org/ns/did/v1',
'https://w3id.org/security/multikey/v1',
'https://w3id.org/security/data-integrity/v1',
[
'fep' => 'https://w3id.org/fep/ef61#',
'aliases' => 'fep:aliases'
],
self::ap_schema($contextType)
]];
}
@ -5133,6 +5262,11 @@ class Activity
// a limited subset of the entire schema definition for particular activities.
return [
'gateways' => [
'@id' => 'https://w3id.org/fep/ef61/gateways',
'@type' => '@id',
'@container' => '@list'
],
'wf' => 'https://purl.archive.org/socialweb/webfinger',
'xsd' => 'http://www.w3.org/2001/XMLSchema#',
'webfinger' => [
@ -5154,8 +5288,12 @@ class Activity
'directMessage' => 'nomad:directMessage',
'Category' => 'nomad:Category',
'copiedTo' => 'nomad:copiedTo',
'permissions' => 'nomad:permissions',
'searchContent' => 'nomad:searchContent',
'searchTags' => 'nomad:searchTags',
'collectionOf' => 'nomad:collectionOf',
'openwebauth' => 'nomad:openwebauth',
'authredirect' => 'nomad:authredirect',
];
}

View file

@ -381,7 +381,7 @@ class ActivityPub
$jmsg = json_encode($msg, JSON_UNESCAPED_SLASHES);
$r = q("select * from abook left join hubloc on abook_xchan = hubloc_hash
where abook_channel = %d and hubloc_network = 'activitypub'",
where abook_channel = %d and hubloc_network in ('activitypub', 'apnomadic') ",
intval($x['sender']['channel_id'])
);

View file

@ -11,17 +11,28 @@ class ActorId
public const ACTORID_TYPE_UNKNOWN = 0;
public const ACTORID_TYPE_URL = 1;
public const ACTORID_TYPE_DID = 2;
public const ACTORID_TYPE_DIDKEY = 2;
public const ACTORID_TYPE_DIDWEB = 3;
public function __construct($id)
{
$this->id = $id;
if (str_contains($this->id, 'did:ap:key:')) {
$this->type = ActorId::ACTORID_TYPE_DID;
if (str_starts_with($this->id, 'http')) {
$this->id = substr($this->id, strpos($this->id, 'did:ap:key:'));
$this->id = substr($this->id, 0, strpos($this->id, '?') ? strpos($this->id, '?') : null);
$this->id = substr($this->id, 0, strpos($this->id, '#') ? strpos($this->id, '#') : null);
if (str_contains($this->id, 'did:key:')) {
$this->type = ActorId::ACTORID_TYPE_DIDKEY;
if (str_starts_with($this->id, 'http') || str_starts_with($this->id, 'ap://')) {
$this->id = substr($this->id, strpos($this->id, 'did:key:'));
$this->id = substr($this->id, 0, strpos($this->id, '/') ?: null);
$this->id = substr($this->id, 0, strpos($this->id, '?') ?: null);
$this->id = substr($this->id, 0, strpos($this->id, '#') ?: null);
}
}
elseif (str_contains($this->id, 'did:web:')) {
$this->type = ActorId::ACTORID_TYPE_DIDWEB;
if (str_starts_with($this->id, 'http') || str_starts_with($this->id, 'ap://')) {
$this->id = substr($this->id, strpos($this->id, 'did:web:'));
$this->id = substr($this->id, 0, strpos($this->id, '/') ?: null);
$this->id = substr($this->id, 0, strpos($this->id, '?') ?: null);
$this->id = substr($this->id, 0, strpos($this->id, '#') ?: null);
}
}
elseif (str_starts_with($this->id, 'http')) {

View file

@ -1181,10 +1181,10 @@ class Channel
$r = q(
"select * from item where item_wall = 1 and item_deleted = 0 and uid = %d
and created > %s - INTERVAL %s and resource_type = '' order by created",
and created > %s - %s and resource_type = '' order by created",
intval($channel_id),
db_utcnow(),
db_quoteinterval('3 MONTH')
db_quoteinterval('3 MONTH', true)
);
if ($r) {
$ret['item'] = [];
@ -2066,8 +2066,13 @@ class Channel
public static function getDid($channel)
{
if (!empty($channel['xchan_epubkey'])) {
$ed25519publicKey = $channel['xchan_epubkey'];
}
else {
$ed25519publicKey = (new Multibase())->publicKey($channel['channel_epubkey']);
return 'did:ap:key:' . $ed25519publicKey;
}
return 'did:key:' . $ed25519publicKey;
}
public static function getDidActor($channel)
@ -2079,12 +2084,17 @@ class Channel
{
$pubkey = (new Multibase())->publicKey($channel['channel_epubkey']);
$nomadic = PConfig::Get($channel['channel_id'], 'system', 'nomadicAP');
return (($nomadic) ? Channel::getDidResolver($channel) : Channel::url($channel) . '#' . $pubkey);
return (($nomadic) ? Channel::getDidResolver($channel, true) : Channel::url($channel) . '#' . $pubkey);
}
public static function getDidResolver($channel)
public static function getDidResolver($channel, $isActor = false)
{
return z_root() . '/.well-known/apresolver/' . self::getDid($channel);
return z_root() . '/.well-known/apgateway/' . self::getDid($channel) . (($isActor) ? '/actor' : '');
}
public static function getNomadResolver($channel, $isActor = false)
{
return z_root() . '/.well-known/nomad-gateway/' . $channel['channel_hash']. (($isActor) ? '/actor' : '');
}
/**
@ -2610,10 +2620,12 @@ class Channel
}
if ($channel['channel_address'] === App::get_hostname() || intval($channel['channel_system'])) {
return z_root() . '?operation=rsakey';
return z_root() . '#rsakey';
}
$nomadic = PConfig::Get($channel['channel_id'], 'system', 'nomaadicAP');
$url = $nomadic ? self::getDidResolver($channel, true) : z_root() . '/channel/' . $channel['channel_address'];
return (($channel) ? z_root() . '/channel/' . $channel['channel_address'] : z_root()) . '?operation=rsakey';
return $url . '#rsakey';
}
public static function is_group($uid)

View file

@ -168,9 +168,9 @@ class Chatroom
if (intval($x[0]['cr_expire'])) {
$r = q(
"delete from chat where created < %s - INTERVAL %s and chat_room = %d",
"delete from chat where created < %s - %s and chat_room = %d",
db_utcnow(),
db_quoteinterval(intval($x[0]['cr_expire']) . ' MINUTE'),
db_quoteinterval(intval($x[0]['cr_expire']) . ' MINUTE', true),
intval($x[0]['cr_id'])
);
}

View file

@ -95,7 +95,7 @@ class Connect
// ensure there's a valid hubloc for this xchan before proceeding - you cannot connect without it
if (in_array($r['xchan_network'], ['nomad', 'zot6', 'activitypub'])) {
if (in_array($r['xchan_network'], ['nomad', 'zot6', 'activitypub', 'apnomadic'])) {
$h = q(
"select * from hubloc where hubloc_hash = '%s' and hubloc_deleted = 0",
dbesc($r['xchan_hash'])
@ -111,7 +111,7 @@ class Connect
// Check the site table to see if we should have a zot6 hubloc,
// If so, clear the xchan and start fresh
if ($r['xchan_network'] === 'activitypub') {
if (in_array($r['xchan_network'], [ 'activitypub', 'apnomadic'])) {
$m = parse_url($r['xchan_hash']);
unset($m['path']);
$h = unparse_url($m);
@ -177,7 +177,7 @@ class Connect
return $result;
}
if ($r['xchan_network'] === 'activitypub') {
if (in_array($r['xchan_network'], [ 'activitypub', 'apnomadic'])) {
if (!$ap_allowed) {
$result['message'] = t('Protocol not supported');
return $result;

View file

@ -22,7 +22,7 @@ class JcsEddsa2022
'type' => 'DataIntegrityProof',
'cryptosuite' => 'eddsa-jcs-2022',
'created' => Time::convert(format: ISO8601),
'verificationMethod' => Channel::getVerifier($channel),
'verificationMethod' => Channel::getDid($channel),
'proofPurpose' => 'assertionMethod',
];

View file

@ -548,7 +548,7 @@ class Libsync
}
}
if ((!$found) && (!in_array($abook['xchan_network'], ['nomad', 'zot6', 'activitypub']))) {
if ((!$found) && (!in_array($abook['xchan_network'], ['nomad', 'zot6', 'activitypub', 'apnomadic']))) {
// just import the record.
$xc = [];
foreach ($abook as $k => $v) {
@ -979,6 +979,8 @@ class Libsync
if (isset($sender['site']) && isset($sender['site']['url']) && $sender['site']['url'] === $location['url']) {
if (isset($sender['site']['protocol_version']) && intval($sender['site']['protocol_version']) > 10) {
$network = 'nomad';
$owa = $sender['site']['openWebAuth'] ?? '';
$redirect = $sender['site']['authRedirect'] ?? '';
}
}

View file

@ -235,7 +235,7 @@ class Libzot
$h = HTTPSig::create_sig(
$headers,
$channel['channel_prvkey'],
Channel::url($channel),
Channel::keyId($channel),
false,
'sha512',
(($crypto) ? ['key' => $crypto['hubloc_sitekey'], 'algorithm' => self::best_algorithm($crypto['site_crypto'])] : false)
@ -257,7 +257,7 @@ class Libzot
'(request-target)' => 'post ' . get_request_string($url)
];
$h = HTTPSig::create_sig($headers,$channel['channel_prvkey'],Channel::url($channel),false,'sha512',
$h = HTTPSig::create_sig($headers,$channel['channel_prvkey'],Channel::keyId($channel),false,'sha512',
(($crypto) ? [ 'key' => $crypto['hubloc_sitekey'], 'algorithm' => self::best_algorithm($crypto['site_crypto']) ] : false));
}
else {
@ -3037,7 +3037,7 @@ class Libzot
$ret['id'] = $e['xchan_guid'];
$ret['id_sig'] = self::sign($e['xchan_guid'], $e['channel_prvkey']);
$ret['did'] = Channel::getDidResolver($e);
$ret['did'] = Channel::getDid($e);
$primary = new Primary([
'address' => $e['xchan_addr'],
@ -3047,6 +3047,7 @@ class Libzot
'outbox' => z_root() . '/outbox/' . $e['channel_address'],
'followers' => z_root() . '/followers/' . $e['channel_address'],
'following' => z_root() . '/following/' . $e['channel_address'],
'permissions' => z_root() . '/permissions/' . $e['channel_address'],
'searchContent' => z_root() . '/search/' . $e['channel_address'] . '?search={}',
'searchTags' => z_root() . '/search/' . $e['channel_address'] . '?tags={}',
]);
@ -3354,6 +3355,11 @@ class Libzot
return $v;
}
}
foreach ($arr as $v) {
if ($v[$check] === 'apnomadic') {
return $v;
}
}
return $arr[0];
}

View file

@ -289,7 +289,7 @@ class MessageFilter
return false;
}
// Ordering of this check (for falsiness) with relation to the following one (check for truthiness) is important.
// Ordering of this check (for falseness) with relation to the following one (check for truthiness) is important.
if (preg_match('/!(.*?)$/', $s, $matches)) {
$x = ((array_key_exists(trim($matches[1]),$item)) ? $item[trim($matches[1])] : EMPTY_STR);
if (!$x) {

View file

@ -107,7 +107,7 @@ class Share
'term' => substr($this->item['author']['xchan_addr'],0,strpos($this->item['author']['xchan_addr'],'@'))
];
if ($this->item['author']['network'] === 'activitypub') {
if (in_array($this->item['author']['network'], ['activitypub', 'apnomadic'])) {
// for Mastodon compatibility, send back an ActivityPub Announce activity.
// We don't need or want these on our own network as there is no mechanism for providing
// a fair-use defense to copyright claims and frivolous lawsuits.

View file

@ -118,9 +118,9 @@ class Socgraph {
}
}
q(
"delete from xchat where xchat_edited < %s - INTERVAL %s and xchat_xchan = '%s' ",
"delete from xchat where xchat_edited < %s - %s and xchat_xchan = '%s' ",
db_utcnow(),
db_quoteinterval('7 DAY'),
db_quoteinterval('7 DAY', true),
dbesc($xchan)
);
}
@ -150,7 +150,7 @@ class Socgraph {
$profile_url = $url['value'];
continue;
}
if (in_array($url['type'], ['nomad', 'zot6', 'activitypub'])) {
if (in_array($url['type'], ['nomad', 'zot6', 'activitypub', 'apnomadic'])) {
$network = $url['type'];
$address = str_replace('acct:', '', $url['value']);
continue;
@ -166,7 +166,7 @@ class Socgraph {
}
}
if (! in_array($network, ['nomad', 'zot6', 'activitypub'])) {
if (! in_array($network, ['nomad', 'zot6', 'activitypub', 'apnomadic'])) {
continue;
}
@ -186,7 +186,7 @@ class Socgraph {
if (($x !== false) && (! count($x))) {
if ($address) {
if (in_array($network, ['nomad', 'zot6', 'activitypub'])) {
if (in_array($network, ['nomad', 'zot6', 'activitypub', 'apnomadic'])) {
$wf = discover_resource($profile_url, verify: false);
if ($wf) {
$x = q(
@ -241,10 +241,10 @@ class Socgraph {
logger("poco_load: loaded $total entries", LOGGER_DEBUG);
q(
"delete from xlink where xlink_xchan = '%s' and xlink_updated < %s - INTERVAL %s and xlink_static = 0",
"delete from xlink where xlink_xchan = '%s' and xlink_updated < %s - %s and xlink_static = 0",
dbesc($xchan),
db_utcnow(),
db_quoteinterval('7 DAY')
db_quoteinterval('7 DAY', true)
);
}
@ -344,10 +344,10 @@ class Socgraph {
logger("ap_poco_load: loaded $total entries", LOGGER_DEBUG);
q(
"delete from xlink where xlink_xchan = '%s' and xlink_updated < %s - INTERVAL %s and xlink_static = 0",
"delete from xlink where xlink_xchan = '%s' and xlink_updated < %s - %s and xlink_static = 0",
dbesc($xchan),
db_utcnow(),
db_quoteinterval('7 DAY')
db_quoteinterval('7 DAY', true)
);
return true;

View file

@ -70,10 +70,10 @@ class Verify
public static function purge(string $type, string $interval): void
{
q(
"delete from verify where vtype = '%s' and created < ( %s - INTERVAL %s )",
"delete from verify where vtype = '%s' and created < ( %s - %s )",
dbesc($type),
db_utcnow(),
db_quoteinterval($interval)
db_quoteinterval($interval, true)
);
}
}

View file

@ -37,7 +37,7 @@ class Zotfinger
'Host' => $m['host'],
'(request-target)' => 'post ' . get_request_string($resource)
];
$h = HTTPSig::create_sig($headers, $channel['channel_prvkey'], Channel::url($channel), false);
$h = HTTPSig::create_sig($headers, $channel['channel_prvkey'], Channel::keyId($channel), false);
}
else {
$h = ['Accept: ' . $accepts];
@ -125,7 +125,7 @@ class Zotfinger
'Host' => $m['host'],
'(request-target)' => 'get ' . get_request_string($resource)
];
$h = HTTPSig::create_sig($headers, $channel['channel_prvkey'], Channel::url($channel), false);
$h = HTTPSig::create_sig($headers, $channel['channel_prvkey'], Channel::keyId($channel), false);
}
else {
$h = ['Accept: ' . $accepts];

View file

@ -240,7 +240,7 @@ class Activity extends Controller
$observer = App::get_observer();
$parent = $items[0];
$recips = (($parent['owner']['xchan_network'] === 'activitypub') ? get_iconfig($parent['id'], 'activitypub', 'recips', []) : []);
$recips = (in_array($parent['owner']['xchan_network'], ['activitypub', 'apnomadic']) ? get_iconfig($parent['id'], 'activitypub', 'recips', []) : []);
$to = (($recips && array_key_exists('to', $recips) && is_array($recips['to'])) ? $recips['to'] : null);
$nitems = [];
foreach ($items as $i) {
@ -300,7 +300,7 @@ class Activity extends Controller
$ret = json_encode($x, JSON_UNESCAPED_SLASHES);
$headers['Digest'] = HTTPSig::generate_digest_header($ret);
$headers['(request-target)'] = strtolower($_SERVER['REQUEST_METHOD']) . ' ' . $_SERVER['REQUEST_URI'];
$h = HTTPSig::create_sig($headers, $chan['channel_prvkey'], Channel::url($chan));
$h = HTTPSig::create_sig($headers, $chan['channel_prvkey'], Channel::keyId($chan));
HTTPSig::set_headers($h);
echo $ret;
killme();

View file

@ -21,8 +21,6 @@ class Album extends Controller
public function init()
{
if (ActivityStreams::is_as_request()) {
$sigdata = HTTPSig::verify(EMPTY_STR);
if ($sigdata['portable_id'] && $sigdata['header_valid']) {
@ -104,8 +102,9 @@ class Album extends Controller
$collection = fetch_post_tags($collection);
}
$obj = Activity::encode_photo_collection($collection, App::$query_string, 'Collection', true, z_root() . '/channel/' . $channel['channel_address'], count($collection));
$obj = Activity::encode_photo_collection($collection, App::$query_string, 'Collection', true, Channel::getDidResolver($channel, true), count($collection));
$obj['content'] = bbcode($obj['content'], ['export' => true]);
as_return_and_die($obj, $channel);
}
goaway(z_root() . '/photos/' . argv(1) . '/album/' . argv(2));

113
src/Module/Apgateway.php Normal file
View file

@ -0,0 +1,113 @@
<?php
namespace Code\Module;
use App;
use Code\Lib\ActivityStreams;
use Code\Web\Controller;
use Code\Web\Router;
class Apgateway extends Controller
{
protected $module;
public function init()
{
// Concatenate path components starting with argv(1)
// to isolate the DID URL.
$url = null;
for ($index = 1; $index < argc(); $index ++) {
if ($index != 1) {
$url .= '/';
}
$url .= argv($index);
}
// Extract the ed25519 key from the DID URL.
$key = str_replace('did:key:', '', $url);
$key = rtrim($key, '/');
$index = strpos($key, '/');
$key = substr($key, 0, $index ?: null);
// Find a channel on this site which has that ed25519 key.
$query = q("select * from xchan left join channel
on xchan_hash = channel_hash where xchan_epubkey = '%s'",
dbesc($key)
);
if (!($query && isset($query[0]['channel_id']))) {
http_status_exit(404, 'Not found');
}
$mappedPath = $this->mapObject(str_replace('did:key:' . $key, '', rtrim($url, '/')), $query[0]);
$localPath = ltrim($mappedPath, '/');
App::$cmd = $localPath;
App::$argv = explode('/', App::$cmd);
App::$argc = count(App::$argv);
if ((array_key_exists('0', App::$argv)) && strlen(App::$argv[0])) {
if (strpos(App::$argv[0],'.')) {
$_REQUEST['module_format'] = substr(App::$argv[0],strpos(App::$argv[0], '.') + 1);
App::$argv[0] = substr(App::$argv[0], 0, strpos(App::$argv[0], '.'));
}
App::$module = str_replace(".", "_", App::$argv[0]);
App::$module = str_replace("-", "_", App::$module);
if (str_starts_with(App::$module, '_')) {
App::$module = substr(App::$module, 1);
}
}
else {
App::$argc = 1;
App::$argv = ['home'];
App::$module = 'home';
}
header('Link: ' . '<' . $url . '>; rel="alternate"', false);
// recursively call the router.
App::$module_loaded = false;
$router = new Router();
$router->Dispatch(true);
}
protected function mapObject($path, $channel)
{
// lookup abstract paths
$systemPaths = [
'' => '/channel/' . $channel['channel_address'],
'/inbox' => '/inbox/' . $channel['channel_address'],
'/outbox' => '/outbox/' . $channel['channel_address'],
'/followers' => '/followers/' . $channel['channel_address'],
'/following' => '/following/' . $channel['channel_address'],
'/permissions' => '/permissions/' . $channel['channel_address'],
'/actor' => '/channel/' . $channel['channel_address'],
'/actor/inbox' => '/inbox/' . $channel['channel_address'],
'/actor/outbox' => '/outbox/' . $channel['channel_address'],
'/actor/followers' => '/followers/' . $channel['channel_address'],
'/actor/following' => '/following/' . $channel['channel_address'],
'/actor/permissions' => '/permissions/' . $channel['channel_address'],
];
$partialPaths = [
'/files/' => '/cloud/' . $channel['channel_address'],
'/photos/' => '/photos/' . $channel['channel_address'],
'/album/' => '/album/' . $channel['channel_address'],
'/object/' => '/item',
];
foreach ($systemPaths as $index => $localPath) {
if ($path === $index) {
return $localPath;
}
}
foreach ($partialPaths as $index => $localPath) {
if (str_starts_with($path, $index)) {
$suffix = substr($path, strlen($index));
return $localPath . '/' . ltrim($suffix, '/');
}
}
return $path;
}
}

View file

@ -1,88 +0,0 @@
<?php
namespace Code\Module;
use App;
use Code\Lib\ActivityStreams;
use Code\Web\Controller;
class Apresolver extends Controller
{
public function init()
{
if (ActivityStreams::is_as_request()) {
// Concatenate path components starting with argv(1)
// to isolate the DID URL.
$url = null;
for ($index = 1; $index < argc(); $index ++) {
if ($index != 1) {
$url .= '/';
}
$url .= argv($index);
}
// Extract the ed25519 key from the DID URL.
$key = str_replace('did:ap:key:', '', $url);
$key = rtrim($key, '/');
$index = strpos($key, '/');
$key = substr($key, 0, $index ?: null);
// Find a channel on this site which has that ed25519 key.
$query = q("select * from xchan left join channel
on xchan_hash = channel_hash where xchan_epubkey = '%s'",
dbesc($key)
);
if (!($query && isset($query[0]['channel_id']))) {
http_status_exit(404, 'Not found');
}
$mappedPath = $this->mapObject(str_replace('did:ap:key:' . $key, '', rtrim($url, '/')), $query[0]);
$localPath = ltrim($mappedPath, '/');
App::$cmd = $localPath;
$controller = "\\Code\Module\\" . ucfirst(substr($localPath, 0, strpos($localPath, '/')));
$module = new $controller;
App::$argv = explode('/', App::$cmd);
App::$argc = count(App::$argv);
if ((array_key_exists('0', App::$argv)) && strlen(App::$argv[0])) {
if (strpos(App::$argv[0],'.')) {
$_REQUEST['module_format'] = substr(App::$argv[0],strpos(App::$argv[0], '.') + 1);
App::$argv[0] = substr(App::$argv[0], 0, strpos(App::$argv[0], '.'));
}
App::$module = str_replace(".", "_", App::$argv[0]);
App::$module = str_replace("-", "_", App::$module);
if (str_starts_with(App::$module, '_')) {
App::$module = substr(App::$module, 1);
}
}
else {
App::$argc = 1;
App::$argv = ['home'];
App::$module = 'home';
}
header('Link: ' . '<' . $url . '>; rel="alternate"', false);
$module->init();
}
}
protected function mapObject($path, $channel)
{
// lookup abstract paths
$systemPaths = [
'' => '/channel/' . $channel['channel_address'],
'/inbox' => '/inbox/' . $channel['channel_address'],
'/outbox' => '/outbox/' . $channel['channel_address'],
'/followers' => '/followers/' . $channel['channel_address'],
'/following' => '/following/' . $channel['channel_address'],
];
foreach ($systemPaths as $index => $localPath) {
if ($path === $index) {
return $localPath;
}
}
return $path;
}
}

View file

@ -42,6 +42,7 @@ class Channel extends Controller
public $profile_uid = 0;
public $loading = 0;
public $updating = 0;
public $legacy = true;
public function init()
@ -153,8 +154,8 @@ class Channel extends Controller
http_status_exit(403, 'Permission denied');
}
}
as_return_and_die(Activity::actorEncode($channel, true, true), $channel);
$export = !empty($_REQUEST['export']);
as_return_and_die(Activity::actorEncode($channel, extended: true, activitypub: true, export: $export, legacy: $this->legacy), $channel);
}
// handle zot6 channel discovery
@ -199,7 +200,7 @@ class Channel extends Controller
'Digest' => HTTPSig::generate_digest_header($data),
'(request-target)' => strtolower($_SERVER['REQUEST_METHOD']) . ' ' . $_SERVER['REQUEST_URI']
];
$h = HTTPSig::create_sig($headers, $channel['channel_prvkey'], Zlib\Channel::url($channel));
$h = HTTPSig::create_sig($headers, $channel['channel_prvkey'], Zlib\Channel::keyId($channel));
HTTPSig::set_headers($h);
echo $data;
killme();

View file

@ -434,7 +434,7 @@ class Connedit extends Controller
notice(t('Refresh failed - channel is currently unavailable.'));
}
} else {
if ($orig_record['xchan_network'] === 'activitypub') {
if (in_array($orig_record['xchan_network'], ['activitypub', 'apnomadic'])) {
ActivityPub::discover($orig_record['xchan_hash'], true);
}
// if they are on a different network we'll force a refresh of the connection basic info
@ -508,7 +508,7 @@ class Connedit extends Controller
}
if ($cmd === 'drop') {
if ($orig_record['xchan_network'] === 'activitypub') {
if (in_array($orig_record['xchan_network'], ['activitypub', 'apnomadic'])) {
ActivityPub::contact_remove(local_channel(), $orig_record);
}
contact_remove(local_channel(), $orig_record['abook_id'], true);
@ -531,11 +531,31 @@ class Connedit extends Controller
$abook_prev = 0;
$abook_next = 0;
$order_q = 'xchan_name';
if (isset($_REQUEST['order'])) {
switch ($_REQUEST['order']) {
case 'date':
$order_q = 'abook_created desc';
break;
case 'created':
$order_q = 'abook_created';
break;
case 'cmax':
$order_q = 'abook_closeness';
break;
case 'name':
default:
$order_q = 'xchan_name';
break;
}
}
$contact_id = App::$poi['abook_id'];
$contact = App::$poi;
$cn = q(
"SELECT abook_id, xchan_name from abook left join xchan on abook_xchan = xchan_hash where abook_channel = %d and abook_self = 0 and xchan_deleted = 0 order by xchan_name",
"SELECT abook_id, xchan_name from abook left join xchan on abook_xchan = xchan_hash where abook_channel = %d and abook_self = 0 and xchan_deleted = 0 order by $order_q",
intval(local_channel())
);
@ -819,6 +839,7 @@ class Connedit extends Controller
'$section' => $section,
'$sections' => $sections,
'$vcard' => $vcard,
'$order' => $_REQUEST['order'] ?? '',
'$addr_text' => t('This connection\'s primary address is'),
'$loc_text' => t('Available locations:'),
'$locstr' => $locstr,

View file

@ -32,14 +32,16 @@ class Conversation extends Controller
// do we have the item (at all)?
$test = q(
"select * from item where mid = '%s' $item_normal limit 1",
dbesc(z_root() . '/activity/' . $item_id)
"select * from item where mid like '%s' and mid like '%s' $item_normal limit 1",
dbesc(z_root() . '%'),
dbesc('%/activity/' . $item_id)
);
if (!$test) {
$test = q(
"select * from item where mid = '%s' $item_normal limit 1",
dbesc(z_root() . '/item/' . $item_id)
"select * from item where mid like '%s' and mid like '%s' $item_normal limit 1",
dbesc(z_root() . '%'),
dbesc('%/item/' . $item_id)
);
if (!$test) {
http_status_exit(404, 'Not found');
@ -123,7 +125,7 @@ class Conversation extends Controller
$observer = App::get_observer();
$parent = $items[0];
$recips = (($parent['owner']['xchan_network'] === 'activitypub') ? get_iconfig($parent['id'], 'activitypub', 'recips', []) : []);
$recips = (in_array($parent['owner']['xchan_network'], ['activitypub', 'apnomadic']) ? get_iconfig($parent['id'], 'activitypub', 'recips', []) : []);
$to = (($recips && array_key_exists('to', $recips) && is_array($recips['to'])) ? $recips['to'] : null);
$nitems = [];
foreach ($items as $i) {

View file

@ -35,6 +35,7 @@ require_once('include/photos.php');
*/
class Cover_photo extends Controller
{
public $channel;
public function init()
{
@ -42,8 +43,8 @@ class Cover_photo extends Controller
return;
}
$channel = App::get_channel();
Libprofile::load($channel['channel_address']);
$this->$channel = App::get_channel();
Libprofile::load($this->channel['channel_address']);
}
/**
@ -60,8 +61,6 @@ class Cover_photo extends Controller
return;
}
$channel = App::get_channel();
check_form_security_token_redirectOnErr('/cover_photo', 'cover_photo');
if ((array_key_exists('cropfinal', $_POST)) && ($_POST['cropfinal'] == 1)) {
@ -203,17 +202,16 @@ class Cover_photo extends Controller
return;
}
$channel = App::get_channel();
$this->send_cover_photo_activity($channel, $base_image, $profile);
$this->send_cover_photo_activity($this->channel, $base_image, $profile);
// put the url and a copy in the places we look for remote cover photos.
XConfig::Set($channel['channel_hash'], 'system', 'cover_photo', z_root() . '/photo/' . $base_image['resource_id'] . '-7');
Stdio::fcopy('store/' . $channel['channel_address'] . '/' . $p['os_path'] . '-9', Hashpath::path($channel['channel_hash'], 'cache/xp' , 2) . '-9');
XConfig::Set($this->channel['channel_hash'], 'system', 'cover_photo', Channel::getDidResolver($this->channel) . '/photo/' . $base_image['resource_id'] . '-7');
Stdio::fcopy('store/' . $this->channel['channel_address'] . '/' . $p['os_path'] . '-9', Hashpath::path($this->channel['channel_hash'], 'cache/xp' , 2) . '-9');
} else {
notice(t('Unable to process image') . EOL);
}
}
goaway(z_root() . '/channel/' . $channel['channel_address']);
goaway(z_root() . '/channel/' . $this->channel['channel_address']);
}
@ -232,7 +230,7 @@ class Cover_photo extends Controller
}
if ($partial) {
$x = save_chunk($channel, $matches[1], $matches[2], $matches[3]);
$x = save_chunk($this->channel, $matches[1], $matches[2], $matches[3]);
if ($x['partial']) {
header('Range: bytes=0-' . (($x['length']) ? $x['length'] - 1 : 0));
@ -267,7 +265,7 @@ class Cover_photo extends Controller
json_return_and_die(['message' => $hash]);
}
public function send_cover_photo_activity($channel, $photo, $profile)
public function send_cover_photo_activity($photo, $profile)
{
$arr = [];
@ -287,11 +285,11 @@ class Cover_photo extends Controller
$t = t('%1$s updated their %2$s');
}
$ptext = '[zrl=' . z_root() . '/photos/' . $channel['channel_address'] . '/image/' . $photo['resource_id'] . ']' . t('cover photo') . '[/zrl]';
$ptext = '[zrl=' . z_root() . '/photos/' . $this->channel['channel_address'] . '/image/' . $photo['resource_id'] . ']' . t('cover photo') . '[/zrl]';
$ltext = '[zrl=' . z_root() . '/profile/' . $channel['channel_address'] . ']' . '[zmg alt="' . t('cover photo') . '"]' . z_root() . '/photo/' . $photo['resource_id'] . '-8[/zmg][/zrl]';
$ltext = '[zrl=' . z_root() . '/profile/' . $this->channel['channel_address'] . ']' . '[zmg alt="' . t('cover photo') . '"]' . z_root() . '/photo/' . $photo['resource_id'] . '-8[/zmg][/zrl]';
$arr['body'] = sprintf($t, $channel['channel_name'], $ptext) . "\n\n" . $ltext;
$arr['body'] = sprintf($t, $this->channel['channel_name'], $ptext) . "\n\n" . $ltext;
$arr['obj'] = [
'type' => ACTIVITY_OBJ_NOTE,
@ -301,10 +299,10 @@ class Cover_photo extends Controller
'url' => ['type' => 'Link', 'mediaType' => $photo['mimetype'], 'href' => z_root() . '/photo/' . $photo['resource_id'] . '-7'],
'source' => ['content' => $arr['body'], 'mediaType' => 'text/x-multicode'],
'content' => bbcode($arr['body']),
'actor' => Activity::actorEncode($channel, false),
'actor' => Activity::actorEncode($this->channel, false),
];
$acl = new AccessControl($channel);
$acl = new AccessControl($this->channel);
$x = $acl->get();
$arr['allow_cid'] = $x['allow_cid'];
@ -312,11 +310,11 @@ class Cover_photo extends Controller
$arr['deny_cid'] = $x['deny_cid'];
$arr['deny_gid'] = $x['deny_gid'];
$arr['uid'] = $channel['channel_id'];
$arr['aid'] = $channel['channel_account_id'];
$arr['uid'] = $this->channel['channel_id'];
$arr['aid'] = $this->channel['channel_account_id'];
$arr['owner_xchan'] = $channel['channel_hash'];
$arr['author_xchan'] = $channel['channel_hash'];
$arr['owner_xchan'] = $this->channel['channel_hash'];
$arr['author_xchan'] = $this->channel['channel_hash'];
post_activity_item($arr);
}
@ -335,11 +333,9 @@ class Cover_photo extends Controller
if (!local_channel()) {
notice(t('Permission denied.') . EOL);
return;
return '';
}
$channel = App::get_channel();
$newuser = false;
if (argc() == 2 && argv(1) === 'new') {
@ -349,7 +345,7 @@ class Cover_photo extends Controller
if (argv(1) === 'use') {
if (argc() < 3) {
notice(t('Permission denied.') . EOL);
return;
return '';
}
// check_form_security_token_redirectOnErr('/cover_photo', 'cover_photo');
@ -363,7 +359,7 @@ class Cover_photo extends Controller
);
if (!$r) {
notice(t('Photo not available.') . EOL);
return;
return '';
}
$havescale = false;
foreach ($r as $rr) {
@ -379,7 +375,7 @@ class Cover_photo extends Controller
);
if (!$r) {
notice(t('Photo not available.') . EOL);
return;
return '';
}
if (intval($r[0]['os_storage'])) {
@ -412,7 +408,7 @@ class Cover_photo extends Controller
if (!array_key_exists('imagecrop', App::$data)) {
$o = replace_macros(Theme::get_template('cover_photo.tpl'), [
'$user' => App::$channel['channel_address'],
'$user' => $this->channel['channel_address'],
'$info' => t('Your cover photo may be visible to anybody on the internet'),
'$existing' => Channel::get_cover_photo(local_channel(), 'array', PHOTO_RES_COVER_850),
'$lbl_upfile' => t('Upload File:'),
@ -458,7 +454,6 @@ class Cover_photo extends Controller
/* @brief Generate the UI for photo-cropping
*
* @param $a Current application
* @param $ph
* @return void
*

View file

@ -309,7 +309,7 @@ class Directory extends Controller
$ret = json_encode($x, JSON_UNESCAPED_SLASHES);
$headers['Digest'] = HTTPSig::generate_digest_header($ret);
$headers['(request-target)'] = strtolower($_SERVER['REQUEST_METHOD']) . ' ' . $_SERVER['REQUEST_URI'];
$h = HTTPSig::create_sig($headers, $chan['channel_prvkey'], Channel::url($chan));
$h = HTTPSig::create_sig($headers, $chan['channel_prvkey'], Channel::keyId($chan));
HTTPSig::set_headers($h);
echo $ret;
killme();

Some files were not shown because too many files have changed in this diff Show more