From 5bd77be2444e454c0e72ff7af7f017dae24b89fe Mon Sep 17 00:00:00 2001 From: octospacc Date: Wed, 13 Sep 2023 02:02:54 +0200 Subject: [PATCH] [activitypub] v0.0.2: make posts loadable by url, with text and attached imgs --- .../style/acp_spaccinc_activitypub_main.html | 16 +- .../spaccincphpbb/activitypub/composer.json | 4 +- .../activitypub/config/services.yml | 2 +- .../activitypub/controller/acp_controller.php | 9 +- .../controller/activitypub_controller.php | 269 ++++++++++++++---- .../language/en/info_acp_activitypub.php | 3 +- .../migrations/install_acp_module.php | 5 +- 7 files changed, 244 insertions(+), 64 deletions(-) diff --git a/phpBB/ext/spaccincphpbb/activitypub/adm/style/acp_spaccinc_activitypub_main.html b/phpBB/ext/spaccincphpbb/activitypub/adm/style/acp_spaccinc_activitypub_main.html index d507214..7af363c 100644 --- a/phpBB/ext/spaccincphpbb/activitypub/adm/style/acp_spaccinc_activitypub_main.html +++ b/phpBB/ext/spaccincphpbb/activitypub/adm/style/acp_spaccinc_activitypub_main.html @@ -14,14 +14,16 @@
{{ lang('SETTINGS') }}
-
-
-
+
+
+
- +
diff --git a/phpBB/ext/spaccincphpbb/activitypub/composer.json b/phpBB/ext/spaccincphpbb/activitypub/composer.json index 692ab4e..dd2d2e3 100644 --- a/phpBB/ext/spaccincphpbb/activitypub/composer.json +++ b/phpBB/ext/spaccincphpbb/activitypub/composer.json @@ -3,8 +3,8 @@ "type": "phpbb-extension", "description": "ActivityPub for phpBB", "homepage": "https://gitlab.com/SpaccInc/SpaccCommunityPlatform", - "version": "0.0.2-dev", - "time": "2023-09-09", + "version": "0.0.2", + "time": "2023-09-13", "license": "[to be defined]", "authors": [ { diff --git a/phpBB/ext/spaccincphpbb/activitypub/config/services.yml b/phpBB/ext/spaccincphpbb/activitypub/config/services.yml index 242ef64..cd29c36 100644 --- a/phpBB/ext/spaccincphpbb/activitypub/config/services.yml +++ b/phpBB/ext/spaccincphpbb/activitypub/config/services.yml @@ -3,8 +3,8 @@ services: class: spaccincphpbb\activitypub\controller\activitypub_controller arguments: - '@config' - - '@request' - '@dbal.conn' + - '@request' spaccincphpbb.activitypub.controller.acp: class: spaccincphpbb\activitypub\controller\acp_controller diff --git a/phpBB/ext/spaccincphpbb/activitypub/controller/acp_controller.php b/phpBB/ext/spaccincphpbb/activitypub/controller/acp_controller.php index 2131fbe..8f968ed 100644 --- a/phpBB/ext/spaccincphpbb/activitypub/controller/acp_controller.php +++ b/phpBB/ext/spaccincphpbb/activitypub/controller/acp_controller.php @@ -43,7 +43,8 @@ class acp_controller if (empty($errors)) { - $this->config->set('spaccincphpbb_activitypub_setfederation', $this->request->variable('spaccincphpbb_activitypub_setfederation', 0)); + $this->config->set('spaccinc_activitypub_setfederation', $this->request->variable('spaccinc_activitypub_setfederation', 0)); + //$this->config->set('spaccinc_activitypub_setfederation', $this->request->variable('spaccinc_activitypub_setdomain', '')); $this->log->add('admin', $this->user->data['user_id'], $this->user->ip, 'LOG_ACP_SPACCINC_ACTIVITYPUB_SETTINGS'); trigger_error($this->language->lang('CONFIG_UPDATED') . adm_back_link($this->u_action)); } @@ -53,9 +54,11 @@ class acp_controller $this->template->assign_vars([ 'S_ERROR' => $s_errors, - 'ERROR_MSG' => $s_errors ? implode('
', $errors) : '', + 'ERROR_MSG' => ($s_errors ? implode('
', $errors) : ''), 'U_ACTION' => $this->u_action, - 'SPACCINCPHPBB_ACTIVITYPUB_SETFEDERATION' => (bool)$this->config['spaccincphpbb_activitypub_setfederation'], + 'SYS_SERVER_NAME' => $this->config['server_name'], + 'SPACCINC_ACTIVITYPUB_SETFEDERATION' => (bool)$this->config['spaccinc_activitypub_setfederation'], + 'SPACCINC_ACTIVITYPUB_SETDOMAIN' => $this->config['spaccinc_activitypub_setdomain'], ]); } diff --git a/phpBB/ext/spaccincphpbb/activitypub/controller/activitypub_controller.php b/phpBB/ext/spaccincphpbb/activitypub/controller/activitypub_controller.php index d6d867a..e3962b9 100644 --- a/phpBB/ext/spaccincphpbb/activitypub/controller/activitypub_controller.php +++ b/phpBB/ext/spaccincphpbb/activitypub/controller/activitypub_controller.php @@ -3,48 +3,45 @@ namespace spaccincphpbb\activitypub\controller; use ErrorException; +use DOMDocument; +use DOMXpath; use Symfony\Component\HttpFoundation\Response; class activitypub_controller { protected $config; - protected $request; protected $db; + protected $request; // Quick way to force any PHP warning to be an error that halts execution and makes nothing return, // so that we don't need complex error handling in case of bad queries public function exception_error_handler($severity, $message, $file, $line) { - if ($message !== 'Only variables should be passed by reference') - { + if ($message !== 'Return type of phpbb\datetime::format($format = \'\', $force_absolute = false) should either be compatible with DateTime::format(string $format): string, or the #[\ReturnTypeWillChange] attribute should be used to temporarily suppress the notice' && $message !== 'Only variables should be passed by reference' && !(str_contains($message, ' service is private, getting it from the container is deprecated ') || str_contains($message, 'You should either make the service public, or stop using the container directly and use dependency injection instead.')) + ){ + print $message; throw new ErrorException($message, 0, $severity, $file, $line); } } public function __construct( \phpbb\config\config $config, - \phpbb\request\request $request, \phpbb\db\driver\driver_interface $db, + \phpbb\request\request $request, ){ $this->config = $config; - $this->request = $request; $this->db = $db; + $this->request = $request; - $this->site_name = $this->get_sql_row(' - SELECT config_value - FROM ' . CONFIG_TABLE . " - WHERE config_name = 'sitename' - ")['config_value']; - - $this->server_name = $this->get_sql_row(' - SELECT config_value - FROM ' . CONFIG_TABLE . " - WHERE config_name = 'server_name' - ")['config_value']; - + $this->server_name = $this->config['server_name']; $this->server_addr = ((!empty($this->request->server('HTTPS')) && (strtolower($this->request->server('HTTPS')) == 'on' || $this->request->server('HTTPS') == '1')) ? 'https://' : 'http://') . $this->server_name; } + private function iso_time($time) + { + return gmdate("Y-m-d\TH:i:s\Z", $time); + } + private function get_sql_row($sql) { $result = $this->db->sql_query($sql); @@ -53,9 +50,122 @@ class activitypub_controller return $data; } + private function get_sql_rows($sql) + { + $result = $this->db->sql_query($sql); + $data = $this->db->sql_fetchrowset($result); + $this->db->sql_freeresult($result); + return $data; + } + + private function get_bbcode_flags($data) + { + return + ($data['enable_bbcode'] ? OPTION_FLAG_BBCODE : 0) + + ($data['enable_smilies'] ? OPTION_FLAG_SMILIES : 0) + + ($data['enable_magic_url'] ? OPTION_FLAG_LINKS : 0); + } + + private function make_post_attachments($html) + { + $attachments = []; + + $dom = new DOMDocument; + $dom->loadHTML($html); + $xpath = new DOMXpath($dom); + $imgs = $dom->getElementsByTagName('img'); + $atts = $xpath->query('//div[@class="inline-attachment"]'); + + // TODO: currently this picks up emojis, and must so be fixed + //foreach($imgs as $item){ + // $attachments[] = [ + // 'type' => 'Document', + // 'mediaType' => 'image/*', + // 'url' => $item->getAttribute('src'), + // //'name' => null, + // ]; + //} + unset($item); + + foreach($atts as $item){ + $file_name = explode('', $item->ownerDocument->saveHtml($item))[1])[0]; + + $file_id = $this->get_sql_row(' + SELECT attach_id + FROM ' . ATTACHMENTS_TABLE . " + WHERE real_filename='" . $this->db->sql_escape($file_name) . "' + ")['attach_id']; + + $attachments[] = [ + 'type' => 'Document', + 'mediaType' => 'image/jpeg', + 'url' => $this->server_addr . '/download/file.php?id=' . $file_id, + 'name' => null, + ]; + } + unset($item); + + return $attachments; + } + + private function make_post_object($data, $in_create) + { + $uri_id = $this->server_addr . '/activitypub?&mode=post&post_id=' . $data['post_id']; + $uri_user = $this->server_addr . '/activitypub?&mode=user&user_id=' . $data['poster_id']; + + $post_html = generate_text_for_display($data['post_text'], $data['bbcode_uid'], $data['bbcode_bitfield'], $this->get_bbcode_flags($data)); + $post_time = $this->iso_time($data['post_time']); + // Note: #Public in to and followers in cc for public post, opposite for unlisted! + $post_to = ['https://www.w3.org/ns/activitystreams#Public']; + $post_cc = []; + + $note = [ + 'id' => $uri_id, + 'type' => 'Note', + 'published' => $post_time, + //'updated' => , + 'attributedTo' => $uri_user, + //'inReplyTo' => null, + 'to' => $post_to, + 'cc' => $post_cc, + 'url' => $this->server_addr . '/viewtopic.php?p=' . $data['post_id'] . '#p' . $data['post_id'], + //'mediaType' => 'text/html', + //'summary' => null, + 'content' => $post_html, + //'contentMap' => [ 'it' => '' ], + 'attachment' => $this->make_post_attachments($post_html), + ]; + + if ($in_create) + { + return [ + '@context' => [ + 'https://www.w3.org/ns/activitystreams', + ], + 'id' => $uri_id, + 'type' => 'Create', + 'actor' => $uri_user, + 'published' => $post_time, + //'updated' => , + 'to' => $post_to, + 'cc' => $post_cc, + 'object' => $note, + ]; + } + else + { + $response[] = $note; + return array_merge([ + '@context' => [ + 'https://www.w3.org/ns/activitystreams', + ] + ], $note); + } + } + public function nodeinfo_known() { - if (!$this->config['spaccincphpbb_activitypub_setfederation']) { + if (!$this->config['spaccinc_activitypub_setfederation']) { return; } @@ -74,7 +184,7 @@ class activitypub_controller public function webfinger() { - if (!$this->config['spaccincphpbb_activitypub_setfederation']) { + if (!$this->config['spaccinc_activitypub_setfederation']) { return; } @@ -86,11 +196,6 @@ class activitypub_controller $subject = ''; $href = []; - //if ($resource == '') - //{ - // return new JsonResponse([], 500); - //} - if (str_starts_with($resource, 'acct:')) { $tokens = explode('@', substr($resource, strlen('acct:'))); @@ -107,8 +212,8 @@ class activitypub_controller WHERE username_clean = '" . $this->db->sql_escape($name) . "' "); - $href['self'] = '&mode=user&u=' . $data['user_id']; - $href['profile-page'] = '/memberlist.php?mode=viewprofile&u=' . $data['user_id']; + $href['self'] = '&mode=user&user_id=' . $data['user_id']; + $href['profile-page'] = '/memberlist.php?mode=viewprofile&user_id=' . $data['user_id']; } $response = new Response(json_encode([ @@ -130,7 +235,7 @@ class activitypub_controller public function activitypub() { - if (!$this->config['spaccincphpbb_activitypub_setfederation']) { + if (!$this->config['spaccinc_activitypub_setfederation']) { return; } @@ -139,7 +244,7 @@ class activitypub_controller $uri_id = htmlspecialchars_decode($server_addr . $this->request->server('REQUEST_URI')); $mode = $this->request->variable('mode', ''); - $response = []; + $response = null; switch ($mode) { @@ -173,7 +278,7 @@ class activitypub_controller //], //'openRegistrations' => true, 'metadata' => [ - 'nodeName' => $this->site_name, + 'nodeName' => $this->config['sitename'], ], ]; break; @@ -181,12 +286,12 @@ class activitypub_controller break; case 'user': - $u = $this->request->variable('u', ''); + $user_id = $this->request->variable('user_id', ''); $data = $this->get_sql_row(' SELECT * FROM ' . USERS_TABLE . " - WHERE user_id = '" . $this->db->sql_escape($u) . "' + WHERE user_id = '" . $this->db->sql_escape($user_id) . "' "); $icon_ext = end(explode('.', $data['user_avatar'])); @@ -208,18 +313,18 @@ class activitypub_controller 'https://w3id.org/security/v1', ], 'id' => $uri_id, - 'inbox' => $server_addr . '/activitypub?&mode=inbox&u=' . $u, - 'outbox' => $server_addr . '/activitypub?&mode=outbox&u=' . $u, + 'inbox' => $server_addr . '/activitypub?&mode=inbox&user_id=' . $user_id, + 'outbox' => $server_addr . '/activitypub?&mode=outbox&user_id=' . $user_id, 'endpoints' => [ 'sharedInbox' => $server_addr . '/activitypub&mode=inbox', ], 'type' => 'Person', //'discoverable' => true, //'manuallyApprovesFollowers' => false, - //'published' => $data['user_regdate'], + 'published' => $this->iso_time($data['user_regdate']), 'preferredUsername' => $data['username_clean'], 'name' => $data['username'], - 'url' => $server_addr . '/memberlist.php?mode=viewprofile&u=' . $u, + 'url' => $server_addr . '/memberlist.php?mode=viewprofile&user_id=' . $user_id, 'icon' => [ 'type' => 'Image', 'mediaType' => 'image/' . ($icon_ext === 'jpg' ? 'jpeg' : $icon_ext), @@ -238,27 +343,95 @@ class activitypub_controller break; case 'outbox': - $u = $this->request->variable('u', ''); + $user_id = $this->request->variable('user_id', ''); + $page = $this->request->variable('page', ''); + //$min_id = $this->request->variable('min_id', -1); + //$max_id = $this->request->variable('max_id', -1); - $user_posts = $this->get_sql_row(' + $post_count = (int)$this->get_sql_row(' SELECT user_posts FROM ' . USERS_TABLE . " - WHERE user_id = '" . $this->db->sql_escape($u) . "' + WHERE user_id = '" . $this->db->sql_escape($user_id) . "' ")['user_posts']; - $response = [ - '@context' => 'https://www.w3.org/ns/activitystreams', - 'id' => $uri_id, - 'type' => 'OrderedCollection', - 'totalItems' => (int)$user_posts, - 'first' => $uri_id . '&view=page', - 'last' => $uri_id . '&view=page&min_id=0', - ]; + if ($page === '') + { + $response = [ + '@context' => [ + 'https://www.w3.org/ns/activitystreams', + ], + 'id' => $uri_id, + 'type' => 'OrderedCollection', + 'totalItems' => $post_count, + 'first' => $uri_id . '&page=-1', + 'last' => $uri_id . '&page=0', + ]; + } + else + { + $items = []; + $order = 'DESC'; + $limit = 20; + $offset = 0; + + switch ($page) + { + // Oldest + case '0': + $order = 'DESC'; + break; + + // Newest + case '-1': + // ... + break; + + // Any other page + default: + // ... + break; + } + + $data = $this->get_sql_rows(' + SELECT * + FROM ' . POSTS_TABLE . " + WHERE poster_id = '" . $this->db->sql_escape($user_id) . "' + ORDER BY post_id " . $order . ' + LIMIT ' . $limit . ' + OFFSET ' . $offset + ); + + foreach ($data as &$item) + { + $items[] = $this->make_post_object($item, true); + } + unset($item); + + $response = [ + '@context' => [ + 'https://www.w3.org/ns/activitystreams', + ], + 'id' => $uri_id, + 'type' => 'OrderedCollectionPage', + //'prev' => '', + //'next' => '', + // ... + 'partOf' => $server_addr . '/activitypub?&mode=outbox&user_id=' . $user_id, + //'ordererdItems' => $items, + ]; + } break; - //case 'post': + //case 'thread': // - //break; + + case 'post': + $response = $this->make_post_object($this->get_sql_row(' + SELECT * + FROM ' . POSTS_TABLE . " + WHERE post_id = '" . $this->db->sql_escape($this->request->variable('post_id', '')) . "' + "), false); + break; } $response = new Response(json_encode($response), 200); diff --git a/phpBB/ext/spaccincphpbb/activitypub/language/en/info_acp_activitypub.php b/phpBB/ext/spaccincphpbb/activitypub/language/en/info_acp_activitypub.php index eb6559f..6653a10 100644 --- a/phpBB/ext/spaccincphpbb/activitypub/language/en/info_acp_activitypub.php +++ b/phpBB/ext/spaccincphpbb/activitypub/language/en/info_acp_activitypub.php @@ -14,5 +14,6 @@ $lang = array_merge($lang, [ 'ACP_SPACCINC_ACTIVITYPUB_TITLE' => 'ActivityPub for phpBB Module', 'ACP_SPACCINC_ACTIVITYPUB_SETFEDERATION' => 'Enable ActivityPub federation for this board', 'ACP_SPACCINC_ACTIVITYPUB_SETDOMAIN' => 'Full domain name to use for federation', - 'ACP_SPACCINC_ACTIVITYPUB_SETDOMAIN_INFO' => 'The domain on which federation will be handled. NEVER change this after enabling federation, unless you know the implications.', + 'ACP_SPACCINC_ACTIVITYPUB_SETDOMAIN_INFO' => 'The domain on which federation will be handled. You will never be able to change this later! (unless you know all implications)', + 'ACP_SPACCINC_ACTIVITYPUB_SETDOMAIN_CONFIRM' => 'I understand that the federation domain will not be able to be changed after having set it.' ]); diff --git a/phpBB/ext/spaccincphpbb/activitypub/migrations/install_acp_module.php b/phpBB/ext/spaccincphpbb/activitypub/migrations/install_acp_module.php index f406cb8..15c33eb 100644 --- a/phpBB/ext/spaccincphpbb/activitypub/migrations/install_acp_module.php +++ b/phpBB/ext/spaccincphpbb/activitypub/migrations/install_acp_module.php @@ -6,7 +6,7 @@ class install_acp_module extends \phpbb\db\migration\migration { public function effectively_installed() { - return isset($this->config['spaccincphpbb_activitypub_setfederation']); + return isset($this->config['spaccinc_activitypub_setfederation']); } public static function depends_on() @@ -17,7 +17,8 @@ class install_acp_module extends \phpbb\db\migration\migration public function update_data() { return [ - ['config.add', ['spaccincphpbb_activitypub_setfederation', 0]], + ['config.add', ['spaccinc_activitypub_setfederation', 0]], + ['config.add', ['spaccinc_activitypub_setdomain', '']], ['module.add', [ 'acp',