]> git.mxchange.org Git - friendica.git/commitdiff
Merge remote-tracking branch 'upstream/develop' into item-lock
authorMichael <heluecht@pirati.ca>
Thu, 29 Oct 2020 21:07:24 +0000 (21:07 +0000)
committerMichael <heluecht@pirati.ca>
Thu, 29 Oct 2020 21:07:24 +0000 (21:07 +0000)
36 files changed:
.github/workflows/php.yml
database.sql
doc/Settings.md
doc/de/Settings.md
src/Content/PageInfo.php
src/Content/Text/BBCode.php
src/Core/Process.php
src/DI.php
src/Factory/Api/Mastodon/Account.php
src/Factory/Api/Mastodon/Field.php
src/Factory/Api/Mastodon/Mention.php [new file with mode: 0644]
src/Factory/Api/Mastodon/Status.php
src/Factory/Api/Mastodon/Tag.php [new file with mode: 0644]
src/Model/Item.php
src/Model/Post/Media.php [new file with mode: 0644]
src/Module/Admin/Site.php
src/Module/Contact.php
src/Module/Contact/Advanced.php
src/Module/Conversation/Network.php
src/Object/Api/Mastodon/Account.php
src/Object/Api/Mastodon/Card.php [new file with mode: 0644]
src/Object/Api/Mastodon/Mention.php [new file with mode: 0644]
src/Object/Api/Mastodon/Status.php
src/Object/Api/Mastodon/Status/Counts.php
src/Object/Api/Mastodon/Status/UserAttributes.php [new file with mode: 0644]
src/Object/Api/Mastodon/Tag.php [new file with mode: 0644]
src/Protocol/ActivityPub/Processor.php
src/Protocol/ActivityPub/Receiver.php
src/Protocol/Diaspora.php
src/Worker/CleanItemUri.php
static/dbstructure.config.php
view/templates/contact/advanced.tpl
view/templates/contact_edit.tpl
view/theme/frio/templates/contact/advanced.tpl
view/theme/frio/templates/contact_edit.tpl
view/theme/vier/templates/contact_edit.tpl

index cf1ad136700b776465a018a985fcb412e5689a63..91a8b60747aa4b3443ce63b675b6d39ceff820ec 100644 (file)
@@ -37,7 +37,7 @@ jobs:
         uses: shivammathur/setup-php@v2
         with:
           php-version: ${{ matrix.php-versions }}
-          tools: pecl
+          tools: pecl, composer:v1
           extensions: pdo_mysql, gd, zip, opcache, ctype, pcntl, ldap, apcu, memcached, redis, imagick, memcache
           coverage: xdebug
           ini-values: apc.enabled=1, apc.enable_cli=1
index 1e439213d4b46c27b6dfdd63954e22a8dc20239e..d3e75e753e03d619db62898de691c49cdf7b84cf 100644 (file)
@@ -1,6 +1,6 @@
 -- ------------------------------------------
 -- Friendica 2020.12-dev (Red Hot Poker)
--- DB_UPDATE_VERSION 1370
+-- DB_UPDATE_VERSION 1372
 -- ------------------------------------------
 
 
@@ -775,6 +775,7 @@ CREATE TABLE IF NOT EXISTS `item-content` (
        `title` varchar(255) NOT NULL DEFAULT '' COMMENT 'item title',
        `content-warning` varchar(255) NOT NULL DEFAULT '' COMMENT '',
        `body` mediumtext COMMENT 'item body content',
+       `raw-body` mediumtext COMMENT 'Body without embedded media links',
        `location` varchar(255) NOT NULL DEFAULT '' COMMENT 'text location where this item originated',
        `coord` varchar(255) NOT NULL DEFAULT '' COMMENT 'longitude/latitude pair representing location where this item originated',
        `language` text COMMENT 'Language information about this post',
@@ -1064,6 +1065,27 @@ CREATE TABLE IF NOT EXISTS `post-delivery-data` (
        FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE
 ) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Delivery data for items';
 
+--
+-- TABLE post-media
+--
+CREATE TABLE IF NOT EXISTS `post-media` (
+       `id` int unsigned NOT NULL auto_increment COMMENT 'sequential ID',
+       `uri-id` int unsigned NOT NULL COMMENT 'Id of the item-uri table entry that contains the item uri',
+       `url` varbinary(511) NOT NULL COMMENT 'Media URL',
+       `type` tinyint unsigned NOT NULL DEFAULT 0 COMMENT 'Media type',
+       `mimetype` varchar(60) COMMENT '',
+       `height` smallint unsigned COMMENT 'Height of the media',
+       `width` smallint unsigned COMMENT 'Width of the media',
+       `size` int unsigned COMMENT 'Media size',
+       `preview` varbinary(255) COMMENT 'Preview URL',
+       `preview-height` smallint unsigned COMMENT 'Height of the preview picture',
+       `preview-width` smallint unsigned COMMENT 'Width of the preview picture',
+       `description` text COMMENT '',
+        PRIMARY KEY(`id`),
+        UNIQUE INDEX `uri-id-url` (`uri-id`,`url`),
+       FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE
+) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Attached media';
+
 --
 -- TABLE post-tag
 --
@@ -1390,7 +1412,7 @@ CREATE TABLE IF NOT EXISTS `workerqueue` (
         PRIMARY KEY(`id`),
         INDEX `done_parameter` (`done`,`parameter`(64)),
         INDEX `done_executed` (`done`,`executed`),
-        INDEX `done_priority_created` (`done`,`priority`,`created`),
+        INDEX `done_priority_retrial_created` (`done`,`priority`,`retrial`,`created`),
         INDEX `done_priority_next_try` (`done`,`priority`,`next_try`),
         INDEX `done_pid_next_try` (`done`,`pid`,`next_try`),
         INDEX `done_pid_retrial` (`done`,`pid`,`retrial`),
index effba6a81e81459212112989a8172ebbe729245b..67a5af832ec1496749b5f3e556bd7d4d61e0f127 100644 (file)
@@ -182,7 +182,7 @@ By default, any (valid) email address is allowed in registrations.
 
 #### Allow Users to set remote_self
 
-If you enable the `Allow Users to set remote_self` users can select Atom feeds from their contact list being their *remote self* in the advanced contact settings.
+If you enable the `Allow Users to set remote_self` users can select Atom feeds from their contact list being their *remote self* in the contact settings.
 Which means that postings by the remote self are automatically reposted by Friendica in their names.
 
 This feature can be used to let the user mirror e.g. blog postings into their Friendica postings.
index abb38175701338ca60983ea150d0156582cf3243..33023fd7126903d9b05a061033c50f66c55f04fd 100644 (file)
@@ -172,7 +172,7 @@ Wildcards werden akzeptiert Standardmäßig sind alle gültigen Email-Adressen e
 
 #### Nutzern erlauben das remote_self Flag zu setzen
 
-Webb du die Option `Nutzern erlauben das remote_self Flag zu setzen` aktivierst, können alle Nutzer Atom Feeds in den erweiterten Einstellungen des Kontakts als "Entferntes Konto" markieren.
+Webb du die Option `Nutzern erlauben das remote_self Flag zu setzen` aktivierst, können alle Nutzer Atom Feeds in den Kontakteinstellungen als "Entferntes Konto" markieren.
 Dadurch werden automatisch alle Beiträge dieser Feeds für diesen Nutzer gespiegelt und an die Kontakte bei Friendica verteilt.
 
 Dieses Feature kann z.B. dafür genutzt werden Blogbeiträge zu spiegeln.
index 39bd35f73af68511db77c60a441c2005305247ce..786385c3ef7d4a841efb372650e31ce07cf91fb3 100644 (file)
@@ -292,7 +292,7 @@ class PageInfo
                        $quotedUrl
                )$#isx", function ($match) use ($url) {
                        // Stripping URLs with no label
-                       if (!isset($match[1])) {
+                       if (empty($match[1])) {
                                return '';
                        }
 
index e241abadc89b6efb9b83a0271354a84575c36dac..1229337294c61bc919832884120582dd3b912156 100644 (file)
@@ -146,13 +146,15 @@ class BBCode
        public static function getAttachmentData($body)
        {
                $data = [
-                       'type'        => '',
-                       'text'        => '',
-                       'after'       => '',
-                       'image'       => null,
-                       'url'         => '',
-                       'title'       => '',
-                       'description' => '',
+                       'type'          => '',
+                       'text'          => '',
+                       'after'         => '',
+                       'image'         => null,
+                       'url'           => '',
+                       'provider_name' => '',
+                       'provider_url'  => '',
+                       'title'         => '',
+                       'description'   => '',
                ];
 
                if (!preg_match("/(.*)\[attachment(.*?)\](.*?)\[\/attachment\](.*)/ism", $body, $match)) {
@@ -253,6 +255,16 @@ class BBCode
 
                $data['after'] = trim($match[4]);
 
+               $parts = parse_url($data['url']);
+               if (!empty($parts['scheme']) && !empty($parts['host'])) {
+                       $data['provider_name'] = $parts['host'];
+                       $data['provider_url'] = $parts['scheme'] . '://' . $parts['host'];
+
+                       if (!empty($parts['port'])) {
+                               $data['provider_url'] .= ':' . $parts['port'];
+                       }
+               }
+
                return $data;
        }
 
index f8958f102c3ceda81f9f619bb857baad43b1bea2..06653cbe3161400351a744a890c6a4863b77004e 100644 (file)
@@ -225,6 +225,7 @@ class Process
        public function run($command, $args)
        {
                if (!function_exists('proc_open')) {
+                       $this->logger->notice('"proc_open" not available - quitting');
                        return;
                }
 
@@ -242,6 +243,7 @@ class Process
                }
 
                if ($this->isMinMemoryReached()) {
+                       $this->logger->notice('Memory limit reached - quitting');
                        return;
                }
 
@@ -251,9 +253,11 @@ class Process
                        $resource = proc_open($cmdline . ' &', [], $foo, $this->basePath);
                }
                if (!is_resource($resource)) {
-                       $this->logger->debug('We got no resource for command.', ['cmd' => $cmdline]);
+                       $this->logger->notice('We got no resource for command.', ['command' => $cmdline]);
                        return;
                }
                proc_close($resource);
+
+               $this->logger->info('Executed "proc_open"', ['command' => $cmdline, 'callstack' => System::callstack(10)]);
        }
 }
index 73f9b7811d37abf5e6f38752e5c2f599ebb4980c..e259c271a00ac4ef6dc82d13b4e21246b76d89eb 100644 (file)
@@ -279,6 +279,22 @@ abstract class DI
                return self::$dice->create(Factory\Api\Mastodon\Status::class);
        }
 
+       /**
+        * @return Factory\Api\Mastodon\Mention
+        */
+       public static function mstdnMention()
+       {
+               return self::$dice->create(Factory\Api\Mastodon\Mention::class);
+       }
+
+       /**
+        * @return Factory\Api\Mastodon\Tag
+        */
+       public static function mstdnTag()
+       {
+               return self::$dice->create(Factory\Api\Mastodon\Tag::class);
+       }
+
        /**
         * @return Factory\Api\Twitter\User
         */
index b0c31c09b4cca8756da254487455cddffbda2706..a2bd550a73b4c92d5d08a99015fe91818eac485e 100644 (file)
@@ -69,7 +69,15 @@ class Account extends BaseFactory
 
                $apcontact = APContact::getByURL($publicContact['url'], false);
 
-               return new \Friendica\Object\Api\Mastodon\Account($this->baseUrl, $publicContact, new Fields(), $apcontact, $userContact);
+               $self_contact = Contact::selectFirst(['uid'], ['nurl' => $publicContact['nurl'], 'self' => true]);
+               if (!empty($self_contact['uid'])) {
+                       $profileFields = $this->profileField->select(['uid' => $self_contact['uid'], 'psid' => PermissionSet::PUBLIC]);
+                       $fields        = $this->mstdnField->createFromProfileFields($profileFields);
+               } else {
+                       $fields = new Fields();
+               }
+
+               return new \Friendica\Object\Api\Mastodon\Account($this->baseUrl, $publicContact, $fields, $apcontact, $userContact);
        }
 
        /**
index fdf0a4ef6da478ca0cc523b54503b4e334b37fb2..d357ee2fa5678008dddca6d8567af338958a19a4 100644 (file)
@@ -37,7 +37,7 @@ class Field extends BaseFactory
         */
        public function createFromProfileField(ProfileField $profileField)
        {
-               return new \Friendica\Api\Entity\Mastodon\Field($profileField->label, BBCode::convert($profileField->value, false, BBCode::ACTIVITYPUB));
+               return new \Friendica\Object\Api\Mastodon\Field($profileField->label, BBCode::convert($profileField->value, false, BBCode::ACTIVITYPUB));
        }
 
        /**
diff --git a/src/Factory/Api/Mastodon/Mention.php b/src/Factory/Api/Mastodon/Mention.php
new file mode 100644 (file)
index 0000000..5ab82d7
--- /dev/null
@@ -0,0 +1,67 @@
+<?php
+/**
+ * @copyright Copyright (C) 2020, Friendica
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Factory\Api\Mastodon;
+
+use Friendica\App\BaseURL;
+use Friendica\BaseFactory;
+use Friendica\Model\Contact;
+use Friendica\Model\Tag;
+use Friendica\Network\HTTPException;
+use Friendica\Repository\ProfileField;
+use Psr\Log\LoggerInterface;
+
+class Mention extends BaseFactory
+{
+       /** @var BaseURL */
+       protected $baseUrl;
+       /** @var ProfileField */
+       protected $profileField;
+       /** @var Field */
+       protected $mstdnField;
+
+       public function __construct(LoggerInterface $logger, BaseURL $baseURL, ProfileField $profileField, Field $mstdnField)
+       {
+               parent::__construct($logger);
+
+               $this->baseUrl = $baseURL;
+               $this->profileField = $profileField;
+               $this->mstdnField = $mstdnField;
+       }
+
+       /**
+        * @param int $uriId Uri-ID of the item
+        * @return array
+        * @throws HTTPException\InternalServerErrorException
+        * @throws \ImagickException
+        */
+       public function createFromUriId(int $uriId)
+       {
+               $mentions = [];
+               $tags = Tag::getByURIId($uriId, [Tag::MENTION, Tag::EXCLUSIVE_MENTION, Tag::IMPLICIT_MENTION]);
+               foreach ($tags as $tag) {
+                       $contact = Contact::getByURL($tag['url'], false);
+                       $mention = new \Friendica\Object\Api\Mastodon\Mention($this->baseUrl, $tag, $contact);
+                       $mentions[] = $mention->toArray();
+               }
+               return $mentions;
+       }
+}
index c31c211a59ec7bf03b168885bdab8e8d24a4fd75..b4ef0a0f7fa568cda0694911488ee8a825cf8ef6 100644 (file)
@@ -23,6 +23,7 @@ namespace Friendica\Factory\Api\Mastodon;
 
 use Friendica\App\BaseURL;
 use Friendica\BaseFactory;
+use Friendica\Content\Text\BBCode;
 use Friendica\Database\DBA;
 use Friendica\DI;
 use Friendica\Model\Item;
@@ -68,6 +69,22 @@ class Status extends BaseFactory
                        DBA::count('item', ['thr-parent-id' => $uriId, 'uid' => $uid, 'gravity' => GRAVITY_ACTIVITY, 'vid' => Verb::getID(Activity::LIKE)])
                );
 
-               return new \Friendica\Object\Api\Mastodon\Status($item, $account, $counts);
+               $userAttributes = new \Friendica\Object\Api\Mastodon\Status\UserAttributes(
+                       DBA::exists('item', ['thr-parent-id' => $uriId, 'uid' => $uid, 'origin' => true, 'gravity' => GRAVITY_ACTIVITY, 'vid' => Verb::getID(Activity::LIKE)]),
+                       DBA::exists('item', ['thr-parent-id' => $uriId, 'uid' => $uid, 'origin' => true, 'gravity' => GRAVITY_ACTIVITY, 'vid' => Verb::getID(Activity::ANNOUNCE)]),
+                       DBA::exists('thread', ['iid' => $item['id'], 'uid' => $item['uid'], 'ignored' => true]),
+                       (bool)$item['starred'],
+                       DBA::exists('user-item', ['iid' => $item['id'], 'uid' => $item['uid'], 'pinned' => true])
+               );
+
+               $sensitive = DBA::exists('tag-view', ['uri-id' => $uriId, 'name' => 'nsfw']);
+               $application = new \Friendica\Object\Api\Mastodon\Application($item['app'] ?? '');
+               $mentions = DI::mstdnMention()->createFromUriId($uriId);
+               $tags = DI::mstdnTag()->createFromUriId($uriId);
+
+               $attachment = BBCode::getAttachmentData($item['body']);
+               $card = new \Friendica\Object\Api\Mastodon\Card($attachment);
+
+               return new \Friendica\Object\Api\Mastodon\Status($item, $account, $counts, $userAttributes, $sensitive, $application, $mentions, $tags, $card);
        }
 }
diff --git a/src/Factory/Api/Mastodon/Tag.php b/src/Factory/Api/Mastodon/Tag.php
new file mode 100644 (file)
index 0000000..9b81e6d
--- /dev/null
@@ -0,0 +1,65 @@
+<?php
+/**
+ * @copyright Copyright (C) 2020, Friendica
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Factory\Api\Mastodon;
+
+use Friendica\App\BaseURL;
+use Friendica\BaseFactory;
+use Friendica\Model\Tag as TagModel;
+use Friendica\Network\HTTPException;
+use Friendica\Repository\ProfileField;
+use Psr\Log\LoggerInterface;
+
+class Tag extends BaseFactory
+{
+       /** @var BaseURL */
+       protected $baseUrl;
+       /** @var ProfileField */
+       protected $profileField;
+       /** @var Field */
+       protected $mstdnField;
+
+       public function __construct(LoggerInterface $logger, BaseURL $baseURL, ProfileField $profileField, Field $mstdnField)
+       {
+               parent::__construct($logger);
+
+               $this->baseUrl = $baseURL;
+               $this->profileField = $profileField;
+               $this->mstdnField = $mstdnField;
+       }
+
+       /**
+        * @param int $uriId Uri-ID of the item
+        * @return array
+        * @throws HTTPException\InternalServerErrorException
+        * @throws \ImagickException
+        */
+       public function createFromUriId(int $uriId)
+       {
+               $hashtags = [];
+               $tags = TagModel::getByURIId($uriId, [TagModel::HASHTAG]);
+               foreach ($tags as $tag) {
+                       $hashtag = new \Friendica\Object\Api\Mastodon\Tag($this->baseUrl, $tag);
+                       $hashtags[] = $hashtag->toArray();
+               }
+               return $hashtags;
+       }
+}
index d5958312f30679f84d6f98c5c64a5ec290fa6e6e..2be63d3989e01635774a3db6b46fd9515ff20831 100644 (file)
@@ -106,7 +106,7 @@ class Item
                        'object-type', 'object', 'target-type', 'target', 'plink'];
 
        // Field list for "item-content" table that is not present in the "item" table
-       const CONTENT_FIELDLIST = ['language'];
+       const CONTENT_FIELDLIST = ['language', 'raw-body'];
 
        // All fields in the item table
        const ITEM_FIELDLIST = ['id', 'uid', 'parent', 'uri', 'parent-uri', 'thr-parent',
@@ -1680,6 +1680,7 @@ class Item
                $item['deny_gid']      = trim($item['deny_gid'] ?? '');
                $item['private']       = intval($item['private'] ?? self::PUBLIC);
                $item['body']          = trim($item['body'] ?? '');
+               $item['raw-body']      = trim($item['raw-body'] ?? $item['body']);
                $item['attach']        = trim($item['attach'] ?? '');
                $item['app']           = trim($item['app'] ?? '');
                $item['origin']        = intval($item['origin'] ?? 0);
@@ -1818,6 +1819,10 @@ class Item
                        self::setOwnerforResharedItem($item);
                }
 
+               // Remove all media attachments from the body and store them in the post-media table
+               $item['raw-body'] = Post\Media::insertFromBody($item['uri-id'], $item['raw-body']);
+               $item['raw-body'] = self::setHashtags($item['raw-body']);
+
                // Check for hashtags in the body and repair or add hashtag links
                $item['body'] = self::setHashtags($item['body']);
 
diff --git a/src/Model/Post/Media.php b/src/Model/Post/Media.php
new file mode 100644 (file)
index 0000000..ec3bf96
--- /dev/null
@@ -0,0 +1,171 @@
+<?php
+/**
+ * @copyright Copyright (C) 2020, Friendica
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Model\Post;
+
+use Friendica\Core\Logger;
+use Friendica\Core\System;
+use Friendica\Database\DBA;
+use Friendica\Util\Images;
+
+/**
+ * Class Media
+ *
+ * This Model class handles media interactions.
+ * This tables stores medias (images, videos, audio files) related to posts.
+ */
+class Media
+{
+       const UNKNOWN = 0;
+       const IMAGE   = 1;
+       const VIDEO   = 2;
+       const AUDIO   = 3;
+       const TORRENT = 16;
+
+       /**
+        * Insert a post-media record
+        *
+        * @param array $media
+        * @return void
+        */
+       public static function insert(array $media)
+       {
+               if (empty($media['url']) || empty($media['uri-id'])) {
+                       return;
+               }
+
+               if (DBA::exists('post-media', ['uri-id' => $media['uri-id'], 'url' => $media['url']])) {
+                       Logger::info('Media already exists', ['uri-id' => $media['uri-id'], 'url' => $media['url'], 'callstack' => System::callstack()]);
+                       return;
+               }
+
+               $fields = ['type', 'mimetype', 'height', 'width', 'size', 'preview', 'preview-height', 'preview-width', 'description'];
+               foreach ($fields as $field) {
+                       if (empty($media[$field])) {
+                               unset($media[$field]);
+                       }
+               }
+
+               if ($media['type'] == self::IMAGE) {
+                       $imagedata = Images::getInfoFromURLCached($media['url']);
+                       if (!empty($imagedata)) {
+                               $media['mimetype'] = $imagedata['mime'];
+                               $media['size'] = $imagedata['size'];
+                               $media['width'] = $imagedata[0];
+                               $media['height'] = $imagedata[1];
+                       }
+                       if (!empty($media['preview'])) {
+                               $imagedata = Images::getInfoFromURLCached($media['preview']);
+                               if (!empty($imagedata)) {
+                                       $media['preview-width'] = $imagedata[0];
+                                       $media['preview-height'] = $imagedata[1];
+                               }
+                       }
+               }
+
+               $result = DBA::insert('post-media', $media, true);
+               Logger::info('Stored media', ['result' => $result, 'media' => $media, 'callstack' => System::callstack()]);
+       }
+
+       /**
+        * Tests for path patterns that are usef for picture links in Friendica
+        *
+        * @param string $page    Link to the image page
+        * @param string $preview Preview picture
+        * @return boolean
+        */
+       private static function isPictureLink(string $page, string $preview)
+       {
+               return preg_match('#/photos/.*/image/#ism', $page) && preg_match('#/photo/.*-1\.#ism', $preview);
+       }
+
+       /**
+        * Add media links and remove them from the body
+        *
+        * @param integer $uriid
+        * @param string $body
+        * @return string Body without media links
+        */
+       public static function insertFromBody(int $uriid, string $body)
+       {
+               // Simplify image codes
+               $body = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", '[img]$3[/img]', $body);
+
+               $attachments = [];
+               if (preg_match_all("#\[url=([^\]]+?)\]\s*\[img=([^\[\]]*)\]([^\[\]]*)\[\/img\]\s*\[/url\]#ism", $body, $pictures, PREG_SET_ORDER)) {
+                       foreach ($pictures as $picture) {
+                               if (!self::isPictureLink($picture[1], $picture[2])) {
+                                       continue;
+                               }
+                               $body = str_replace($picture[0], '', $body);
+                               $image = str_replace('-1.', '-0.', $picture[2]);
+                               $attachments[] = ['uri-id' => $uriid, 'type' => self::IMAGE, 'url' => $image,
+                                       'preview' => $picture[2], 'description' => $picture[3]];
+                       }
+               }
+
+               if (preg_match_all("/\[img=([^\[\]]*)\]([^\[\]]*)\[\/img\]/Usi", $body, $pictures, PREG_SET_ORDER)) {
+                       foreach ($pictures as $picture) {
+                               $body = str_replace($picture[0], '', $body);
+                               $attachments[] = ['uri-id' => $uriid, 'type' => self::IMAGE, 'url' => $picture[1], 'description' => $picture[2]];
+                       }
+               }
+
+               if (preg_match_all("#\[url=([^\]]+?)\]\s*\[img\]([^\[]+?)\[/img\]\s*\[/url\]#ism", $body, $pictures, PREG_SET_ORDER)) {
+                       foreach ($pictures as $picture) {
+                               if (!self::isPictureLink($picture[1], $picture[2])) {
+                                       continue;
+                               }
+                               $body = str_replace($picture[0], '', $body);
+                               $image = str_replace('-1.', '-0.', $picture[2]);
+                               $attachments[] = ['uri-id' => $uriid, 'type' => self::IMAGE, 'url' => $image,
+                                       'preview' => $picture[2], 'description' => null];
+                       }
+               }
+
+               if (preg_match_all("/\[img\]([^\[\]]*)\[\/img\]/ism", $body, $pictures, PREG_SET_ORDER)) {
+                       foreach ($pictures as $picture) {
+                               $body = str_replace($picture[0], '', $body);
+                               $attachments[] = ['uri-id' => $uriid, 'type' => self::IMAGE, 'url' => $picture[1]];
+                       }
+               }
+
+               if (preg_match_all("/\[audio\]([^\[\]]*)\[\/audio\]/ism", $body, $audios, PREG_SET_ORDER)) {
+                       foreach ($audios as $audio) {
+                               $body = str_replace($audio[0], '', $body);
+                               $attachments[] = ['uri-id' => $uriid, 'type' => self::AUDIO, 'url' => $audio[1]];
+                       }
+               }
+
+               if (preg_match_all("/\[video\]([^\[\]]*)\[\/video\]/ism", $body, $videos, PREG_SET_ORDER)) {
+                       foreach ($videos as $video) {
+                               $body = str_replace($video[0], '', $body);
+                               $attachments[] = ['uri-id' => $uriid, 'type' => self::VIDEO, 'url' => $video[1]];
+                       }
+               }
+
+               foreach ($attachments as $attachment) {
+                       self::insert($attachment);
+               }
+
+               return trim($body);
+       }
+}
index ea2b5604e33a087b70de24b93a86e0744157d748..a5ca196214c6a5391c02c972e0c4005eaa90800c 100644 (file)
@@ -417,7 +417,11 @@ class Site extends BaseAdmin
                DI::config()->set('system', 'only_tag_search'  , $only_tag_search);
 
                DI::config()->set('system', 'worker_queues'    , $worker_queues);
-               DI::config()->set('system', 'worker_dont_fork' , $worker_dont_fork);
+
+               if (function_exists('proc_open')) {
+                       DI::config()->set('system', 'worker_dont_fork', $worker_dont_fork);
+               }
+
                DI::config()->set('system', 'worker_fastlane'  , $worker_fastlane);
                DI::config()->set('system', 'frontend_worker'  , $worker_frontend);
 
@@ -578,6 +582,14 @@ class Site extends BaseAdmin
                        }
                }
 
+               if (function_exists('proc_open')) {
+                       $worker_dont_fork = DI::config()->get('system', 'worker_dont_fork');
+                       $worker_dont_fork_disabled = '';
+               } else {
+                       $worker_dont_fork = true;
+                       $worker_dont_fork_disabled = 'disabled';
+               }
+
                $t = Renderer::getMarkupTemplate('admin/site.tpl');
                return Renderer::replaceMacros($t, [
                        '$title'             => DI::l10n()->t('Administration'),
@@ -689,7 +701,7 @@ class Site extends BaseAdmin
                        '$rino'                   => ['rino', DI::l10n()->t('RINO Encryption'), intval(DI::config()->get('system', 'rino_encrypt')), DI::l10n()->t('Encryption layer between nodes.'), [0 => DI::l10n()->t('Disabled'), 1 => DI::l10n()->t('Enabled')]],
 
                        '$worker_queues'          => ['worker_queues', DI::l10n()->t('Maximum number of parallel workers'), DI::config()->get('system', 'worker_queues'), DI::l10n()->t('On shared hosters set this to %d. On larger systems, values of %d are great. Default value is %d.', 5, 20, 10)],
-                       '$worker_dont_fork'       => ['worker_dont_fork', DI::l10n()->t('Don\'t use "proc_open" with the worker'), DI::config()->get('system', 'worker_dont_fork'), DI::l10n()->t('Enable this if your system doesn\'t allow the use of "proc_open". This can happen on shared hosters. If this is enabled you should increase the frequency of worker calls in your crontab.')],
+                       '$worker_dont_fork'       => ['worker_dont_fork', DI::l10n()->t('Don\'t use "proc_open" with the worker'), $worker_dont_fork, DI::l10n()->t('Enable this if your system doesn\'t allow the use of "proc_open". This can happen on shared hosters. If this is enabled you should increase the frequency of worker calls in your crontab.'), $worker_dont_fork_disabled],
                        '$worker_fastlane'        => ['worker_fastlane', DI::l10n()->t('Enable fastlane'), DI::config()->get('system', 'worker_fastlane'), DI::l10n()->t('When enabed, the fastlane mechanism starts an additional worker if processes with higher priority are blocked by processes of lower priority.')],
                        '$worker_frontend'        => ['worker_frontend', DI::l10n()->t('Enable frontend worker'), DI::config()->get('system', 'frontend_worker'), DI::l10n()->t('When enabled the Worker process is triggered when backend access is performed (e.g. messages being delivered). On smaller sites you might want to call %s/worker on a regular basis via an external cron job. You should only enable this option if you cannot utilize cron/scheduled jobs on your server.', DI::baseUrl()->get())],
 
index 87fff3e963bdad502a17481d137b107936042232..08256bcf566093dda03f3b6535b6706352c225f7 100644 (file)
@@ -21,7 +21,6 @@
 
 namespace Friendica\Module;
 
-use Friendica\App;
 use Friendica\BaseModule;
 use Friendica\Content\ContactSelector;
 use Friendica\Content\Nav;
@@ -132,6 +131,8 @@ class Contact extends BaseModule
 
                $fetch_further_information = intval($_POST['fetch_further_information'] ?? 0);
 
+               $remote_self = $_POST['remote_self'] ?? false;
+
                $ffi_keyword_denylist = Strings::escapeHtml(trim($_POST['ffi_keyword_denylist'] ?? ''));
 
                $priority = intval($_POST['poll'] ?? 0);
@@ -147,6 +148,7 @@ class Contact extends BaseModule
                        'hidden'     => $hidden,
                        'notify_new_posts' => $notify,
                        'fetch_further_information' => $fetch_further_information,
+                       'remote_self' => $remote_self,
                        'ffi_keyword_denylist'     => $ffi_keyword_denylist],
                        ['id' => $contact_id, 'uid' => local_user()]
                );
@@ -555,6 +557,18 @@ class Contact extends BaseModule
                                ];
                        }
 
+                       // Disable remote self for everything except feeds.
+                       // There is an issue when you repeat an item from maybe twitter and you got comments from friendica and twitter
+                       // Problem is, you couldn't reply to both networks.
+                       $allow_remote_self = in_array($contact['network'], [Protocol::FEED, Protocol::DFRN, Protocol::DIASPORA, Protocol::TWITTER])
+                               && DI::config()->get('system', 'allow_users_remote_self');
+
+                       if ($contact['network'] == Protocol::FEED) {
+                               $remote_self_options = ['0' => DI::l10n()->t('No mirroring'), '1' => DI::l10n()->t('Mirror as forwarded posting'), '2' => DI::l10n()->t('Mirror as my own posting')];
+                       } else {
+                               $remote_self_options = ['0' => DI::l10n()->t('No mirroring'), '2' => DI::l10n()->t('Mirror as my own posting')];
+                       }
+
                        $poll_interval = null;
                        if ((($contact['network'] == Protocol::FEED) && !DI::config()->get('system', 'adjust_poll_frequency')) || ($contact['network']== Protocol::MAIL)) {
                                $poll_interval = ContactSelector::pollInterval($contact['priority'], !$poll_enabled);
@@ -629,6 +643,13 @@ class Contact extends BaseModule
                                '$contact_status' => DI::l10n()->t('Status'),
                                '$contact_settings_label' => $contact_settings_label,
                                '$contact_profile_label' => DI::l10n()->t('Profile'),
+                               '$allow_remote_self' => $allow_remote_self,
+                               '$remote_self'       => ['remote_self',
+                                       DI::l10n()->t('Mirror postings from this contact'),
+                                       $contact['remote_self'],
+                                       DI::l10n()->t('Mark this contact as remote_self, this will cause friendica to repost new entries from this contact.'),
+                                       $remote_self_options
+                               ],      
                        ]);
 
                        $arr = ['contact' => $contact, 'output' => $o];
@@ -916,7 +937,7 @@ class Contact extends BaseModule
                        ],
                ];
 
-               if ($cid != $pcid) {
+               if (!empty($contact['network']) && in_array($contact['network'], [Protocol::FEED, Protocol::MAIL]) && ($cid != $pcid)) {
                        $tabs[] = ['label' => DI::l10n()->t('Advanced'),
                                'url'   => 'contact/' . $cid . '/advanced/',
                                'sel'   => (($active_tab == self::TAB_ADVANCED) ? 'active' : ''),
index 1c2595dc24a29b3e482e5494ba98175dabdedaaa..91536a8889555317b00b6e9281f08b7e2f896849 100644 (file)
@@ -63,7 +63,6 @@ class Advanced extends BaseModule
                $poll        = $_POST['poll'] ?? '';
                $attag       = $_POST['attag'] ?? '';
                $photo       = $_POST['photo'] ?? '';
-               $remote_self = $_POST['remote_self'] ?? false;
                $nurl        = Strings::normaliseLink($url);
 
                $r = DI::dba()->update(
@@ -79,7 +78,6 @@ class Advanced extends BaseModule
                                'notify'      => $notify,
                                'poll'        => $poll,
                                'attag'       => $attag,
-                               'remote_self' => $remote_self,
                        ],
                        ['id' => $contact['id'], 'uid' => local_user()]
                );
@@ -113,18 +111,6 @@ class Advanced extends BaseModule
 
                $returnaddr = "contact/$cid";
 
-               // Disable remote self for everything except feeds.
-               // There is an issue when you repeat an item from maybe twitter and you got comments from friendica and twitter
-               // Problem is, you couldn't reply to both networks.
-               $allow_remote_self = in_array($contact['network'], [Protocol::FEED, Protocol::DFRN, Protocol::DIASPORA, Protocol::TWITTER])
-                                    && DI::config()->get('system', 'allow_users_remote_self');
-
-               if ($contact['network'] == Protocol::FEED) {
-                       $remote_self_options = ['0' => DI::l10n()->t('No mirroring'), '1' => DI::l10n()->t('Mirror as forwarded posting'), '2' => DI::l10n()->t('Mirror as my own posting')];
-               } else {
-                       $remote_self_options = ['0' => DI::l10n()->t('No mirroring'), '2' => DI::l10n()->t('Mirror as my own posting')];
-               }
-
                // This data is fetched automatically for most networks.
                // Editing does only makes sense for mail and feed contacts.
                if (!in_array($contact['network'], [Protocol::FEED, Protocol::MAIL])) {
@@ -146,14 +132,6 @@ class Advanced extends BaseModule
                        '$udprofilenow'      => DI::l10n()->t('Refetch contact data'),
                        '$contact_id'        => $contact['id'],
                        '$lbl_submit'        => DI::l10n()->t('Submit'),
-                       '$label_remote_self' => DI::l10n()->t('Remote Self'),
-                       '$allow_remote_self' => $allow_remote_self,
-                       '$remote_self'       => ['remote_self',
-                               DI::l10n()->t('Mirror postings from this contact'),
-                               $contact['remote_self'],
-                               DI::l10n()->t('Mark this contact as remote_self, this will cause friendica to repost new entries from this contact.'),
-                               $remote_self_options
-                       ],
 
                        '$name'    => ['name', DI::l10n()->t('Name'), $contact['name'], '', '', $readonly],
                        '$nick'    => ['nick', DI::l10n()->t('Account Nickname'), $contact['nick'], '', '', $readonly],
index abe836f27a56f9809daca7f8ec699cde31505b95..886ce25931717e689097e5d6cca50adf8bfc6a33 100644 (file)
@@ -283,30 +283,32 @@ class Network extends BaseModule
 
                self::$forumContactId = $parameters['contact_id'] ?? 0;
 
-               self::$selectedTab = Session::get('network-tab', '');
+               self::$selectedTab = Session::get('network-tab', DI::pConfig()->get(local_user(), 'network.view', 'selected_tab', ''));
+
+               self::$order = 'commented';
 
                if (!empty($get['star'])) {
                        self::$selectedTab = 'star';
+                       self::$order = 'received';
                }
 
                if (!empty($get['mention'])) {
                        self::$selectedTab = 'mention';
+                       self::$order = 'received';
                }
 
                if (!empty($get['order'])) {
                        self::$selectedTab = $get['order'];
+                       self::$order = $get['order'];
                }
 
-               Session::set('network-tab', self::$selectedTab);
-
                self::$star    = intval($get['star'] ?? 0);
                self::$mention = intval($get['mention'] ?? 0);
-               self::$order   = $get['order'] ?? Session::get('network-order', 'commented');
 
                self::$selectedTab = self::$selectedTab ?? self::$order;
 
                Session::set('network-tab', self::$selectedTab);
-               Session::set('network-order', self::$order);
+               DI::pConfig()->set(local_user(), 'network.view', 'selected_tab', self::$selectedTab);
 
                self::$accountTypeString = $get['accounttype'] ?? $parameters['accounttype'] ?? '';
                self::$accountType = User::getAccountTypeByString(self::$accountTypeString);
@@ -331,15 +333,15 @@ class Network extends BaseModule
                        case 'received':
                                self::$max_id = $get['last_received'] ?? self::$max_id;
                                break;
-                       case 'commented':
-                               self::$max_id = $get['last_commented'] ?? self::$max_id;
-                               break;
                        case 'created':
                                self::$max_id = $get['last_created'] ?? self::$max_id;
                                break;
                        case 'uriid':
                                self::$max_id = $get['last_uriid'] ?? self::$max_id;
                                break;
+                       default:
+                               self::$order = 'commented';
+                               self::$max_id = $get['last_commented'] ?? self::$max_id;
                }
        }
 
index 5bd2743b9e0cb9acb0e724c95dec11682a989d11..587c6ce6d4e3ad5fcaaefcad0b566ff31c4da52c 100644 (file)
@@ -99,7 +99,7 @@ class Account extends BaseEntity
                                $publicContact['nick'] :
                                $publicContact['addr'];
                $this->display_name    = $publicContact['name'];
-               $this->locked          = $publicContact['manually-approve'] ?? !empty($apcontact['manually-approve']);
+               $this->locked          = (bool)$publicContact['manually-approve'] ?? !empty($apcontact['manually-approve']);
                $this->bot             = ($publicContact['contact-type'] == Contact::TYPE_NEWS);
                $this->discoverable    = !$publicContact['unsearchable'];
                $this->group           = ($publicContact['contact-type'] == Contact::TYPE_COMMUNITY);
@@ -132,4 +132,20 @@ class Account extends BaseEntity
                $this->fields          = $fields->getArrayCopy();
 
        }
+
+       /**
+        * Returns the current entity as an array
+        *
+        * @return array
+        */
+       public function toArray()
+       {
+               $account = parent::toArray();
+
+               if (empty($account['moved'])) {
+                       unset($account['moved']);
+               }
+
+               return $account;
+       }
 }
diff --git a/src/Object/Api/Mastodon/Card.php b/src/Object/Api/Mastodon/Card.php
new file mode 100644 (file)
index 0000000..2f46e47
--- /dev/null
@@ -0,0 +1,78 @@
+<?php
+/**
+ * @copyright Copyright (C) 2020, Friendica
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Object\Api\Mastodon;
+
+use Friendica\BaseEntity;
+
+/**
+ * Class Card
+ *
+ * @see https://docs.joinmastodon.org/entities/card
+ */
+class Card extends BaseEntity
+{
+       /** @var string */
+       protected $url;
+       /** @var string */
+       protected $title;
+       /** @var string */
+       protected $description;
+       /** @var string */
+       protected $type;
+       /** @var string */
+       protected $provider_name;
+       /** @var string */
+       protected $provider_url;
+       /** @var string */
+       protected $image;
+
+       /**
+        * Creates a card record from an attachment array.
+        *
+        * @param array   $attachment Attachment record
+        * @throws \Friendica\Network\HTTPException\InternalServerErrorException
+        */
+       public function __construct(array $attachment)
+       {
+               $this->url           = $attachment['url'] ?? '';
+               $this->title         = $attachment['title'] ?? '';
+               $this->description   = $attachment['description'] ?? '';
+               $this->type          = $attachment['type'] ?? '';
+               $this->image         = $attachment['image'] ?? '';
+               $this->provider_name = $attachment['provider_name'] ?? '';
+               $this->provider_url  = $attachment['provider_url'] ?? '';
+       }
+
+       /**
+        * Returns the current entity as an array
+        *
+        * @return array
+        */
+       public function toArray()
+       {
+               if (empty($this->url)) {
+                       return null;
+               }
+
+               return parent::toArray();
+       }
+}
diff --git a/src/Object/Api/Mastodon/Mention.php b/src/Object/Api/Mastodon/Mention.php
new file mode 100644 (file)
index 0000000..22e623e
--- /dev/null
@@ -0,0 +1,66 @@
+<?php
+/**
+ * @copyright Copyright (C) 2020, Friendica
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Object\Api\Mastodon;
+
+use Friendica\App\BaseURL;
+use Friendica\BaseEntity;
+
+/**
+ * Class Mention
+ *
+ * @see https://docs.joinmastodon.org/entities/mention
+ */
+class Mention extends BaseEntity
+{
+       /** @var string */
+       protected $id;
+       /** @var string */
+       protected $username;
+       /** @var string */
+       protected $url = null;
+       /** @var string */
+       protected $acct = null;
+
+       /**
+        * Creates a mention record from an tag-view record.
+        *
+        * @param BaseURL $baseUrl
+        * @param array   $tag     tag-view record
+        * @param array   $contact contact table record
+        * @throws \Friendica\Network\HTTPException\InternalServerErrorException
+        */
+       public function __construct(BaseURL $baseUrl, array $tag, array $contact)
+       {
+               $this->id       = $contact['id'] ?? 0;
+               $this->username = $tag['name'];
+               $this->url      = $tag['url'];
+
+               if (!empty($contact)) {
+                       $this->acct =
+                               strpos($contact['url'], $baseUrl->get() . '/') === 0 ?
+                                       $contact['nick'] :
+                                       $contact['addr'];
+               } else {
+                       $this->acct = '';
+               }
+       }
+}
index aa26fa1986aaa2a7fcd85977c46d772019084674..2d2beb583f64d36cfe0158c27d9f1f866ec24dbd 100644 (file)
@@ -24,6 +24,7 @@ namespace Friendica\Object\Api\Mastodon;
 use Friendica\BaseEntity;
 use Friendica\Content\Text\BBCode;
 use Friendica\Object\Api\Mastodon\Status\Counts;
+use Friendica\Object\Api\Mastodon\Status\UserAttributes;
 use Friendica\Util\DateTimeFormat;
 
 /**
@@ -96,7 +97,7 @@ class Status extends BaseEntity
         * @param array   $item
         * @throws \Friendica\Network\HTTPException\InternalServerErrorException
         */
-       public function __construct(array $item, Account $account, Counts $counts)
+       public function __construct(array $item, Account $account, Counts $counts, UserAttributes $userAttributes, bool $sensitive, Application $application, array $mentions, array $tags, Card $card)
        {
                $this->id         = (string)$item['uri-id'];
                $this->created_at = DateTimeFormat::utc($item['created'], DateTimeFormat::ATOM);
@@ -106,32 +107,54 @@ class Status extends BaseEntity
                        $this->in_reply_to_account_id = (string)$item['parent-author-id'];
                }
 
-               $this->sensitive = false;
+               $this->sensitive = $sensitive;
                $this->spoiler_text = $item['title'];
 
                $visibility = ['public', 'private', 'unlisted'];
                $this->visibility = $visibility[$item['private']];
 
-               $this->language = null;
+               $languages = json_decode($item['language'], true);
+               $this->language = is_array($languages) ? array_key_first($languages) : null;
+
                $this->uri = $item['uri'];
                $this->url = $item['plink'] ?? null;
                $this->replies_count = $counts->replies;
                $this->reblogs_count = $counts->reblogs;
                $this->favourites_count = $counts->favourites;
-               $this->favourited = false;
-               $this->reblogged = false;
-               $this->muted = false;
-               $this->bookmarked = false;
-               $this->pinned = false;
+               $this->favourited = $userAttributes->favourited;
+               $this->reblogged = $userAttributes->reblogged;
+               $this->muted = $userAttributes->muted;
+               $this->bookmarked = $userAttributes->bookmarked;
+               $this->pinned = $userAttributes->pinned;
                $this->content = BBCode::convert($item['body'], false);
-               $this->reblog = null;
-               $this->application = null;
+               $this->reblog = null; /// @todo
+               $this->application = $application->toArray();
                $this->account = $account->toArray();
-               $this->media_attachments = [];
-               $this->mentions = [];
-               $this->tags = [];
+               $this->media_attachments = []; /// @todo
+               $this->mentions = $mentions;
+               $this->tags = $tags;
                $this->emojis = [];
-               $this->card = null;
+               $this->card = $card->toArray();
                $this->poll = null;
        }
+
+       /**
+        * Returns the current entity as an array
+        *
+        * @return array
+        */
+       public function toArray()
+       {
+               $status = parent::toArray();
+
+               if (!$status['pinned']) {
+                       unset($status['pinned']);
+               }
+
+               if (empty($status['application']['name'])) {
+                       unset($status['application']);
+               }
+
+               return $status;
+       }
 }
index 2c446c36c7e017eec7558a4b0346d33aa7d5eb80..a0af517dfb4258553c2f1ba26419093620851b94 100644 (file)
@@ -51,6 +51,6 @@ class Counts
        }
 
        public function __get($name) {
-        return $this->$name;
-    }
+               return $this->$name;
+       }
 }
diff --git a/src/Object/Api/Mastodon/Status/UserAttributes.php b/src/Object/Api/Mastodon/Status/UserAttributes.php
new file mode 100644 (file)
index 0000000..c1201c9
--- /dev/null
@@ -0,0 +1,64 @@
+<?php
+/**
+ * @copyright Copyright (C) 2020, Friendica
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Object\Api\Mastodon\Status;
+
+/**
+ * Class UserAttributes
+ *
+ * @see https://docs.joinmastodon.org/entities/status
+ */
+class UserAttributes
+{
+       /** @var bool */
+       protected $favourited;
+       /** @var bool */
+       protected $reblogged;
+       /** @var bool */
+       protected $muted;
+       /** @var bool */
+       protected $bookmarked;
+       /** @var bool */
+       protected $pinned;
+
+       /**
+        * Creates a authorized user attributes object
+        *
+        * @param bool $favourited
+        * @param bool $reblogged
+        * @param bool $muted
+        * @param bool $bookmarked
+        * @param bool $pinned
+        * @throws \Friendica\Network\HTTPException\InternalServerErrorException
+        */
+       public function __construct(bool $favourited, bool $reblogged, bool $muted, bool $bookmarked, bool $pinned)
+       {
+               $this->favourited = $favourited;
+               $this->reblogged = $reblogged;
+               $this->muted = $muted;
+               $this->bookmarked = $bookmarked;
+               $this->pinned = $pinned;
+       }
+
+       public function __get($name) {
+               return $this->$name;
+       }
+}
diff --git a/src/Object/Api/Mastodon/Tag.php b/src/Object/Api/Mastodon/Tag.php
new file mode 100644 (file)
index 0000000..1e74eae
--- /dev/null
@@ -0,0 +1,51 @@
+<?php
+/**
+ * @copyright Copyright (C) 2020, Friendica
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Object\Api\Mastodon;
+
+use Friendica\App\BaseURL;
+use Friendica\BaseEntity;
+
+/**
+ * Class Tag
+ *
+ * @see https://docs.joinmastodon.org/entities/tag
+ */
+class Tag extends BaseEntity
+{
+       /** @var string */
+       protected $name;
+       /** @var string */
+       protected $url = null;
+
+       /**
+        * Creates a hashtag record from an tag-view record.
+        *
+        * @param BaseURL $baseUrl
+        * @param array   $tag     tag-view record
+        * @throws \Friendica\Network\HTTPException\InternalServerErrorException
+        */
+       public function __construct(BaseURL $baseUrl, array $tag)
+       {
+               $this->name = strtolower($tag['name']);
+               $this->url  = $baseUrl . '/search?tag=' . urlencode($this->name);
+       }
+}
index c7310d9eb8d1f824038a617858e55337ee2f48ff..63732b0e15a4a66048c34045678e3ce99d2ce30d 100644 (file)
@@ -37,6 +37,7 @@ use Friendica\Model\ItemURI;
 use Friendica\Model\Mail;
 use Friendica\Model\Tag;
 use Friendica\Model\User;
+use Friendica\Model\Post;
 use Friendica\Protocol\Activity;
 use Friendica\Protocol\ActivityPub;
 use Friendica\Protocol\Relay;
@@ -81,6 +82,45 @@ class Processor
                return $body;
        }
 
+       /**
+        * Store attached media files in the post-media table
+        *
+        * @param int $uriid
+        * @param array $attachment
+        * @return void
+        */
+       private static function storeAttachmentAsMedia(int $uriid, array $attachment)
+       {
+               if (empty($attachment['url'])) {
+                       return;
+               }
+
+               $data = ['uri-id' => $uriid];
+
+               $filetype = strtolower(substr($attachment['mediaType'], 0, strpos($attachment['mediaType'], '/')));
+               if ($filetype == 'image') {
+                       $data['type'] = Post\Media::IMAGE;
+               } elseif ($filetype == 'video') {
+                       $data['type'] = Post\Media::VIDEO;
+               } elseif ($filetype == 'audio') {
+                       $data['type'] = Post\Media::AUDIO;
+               } elseif (in_array($attachment['mediaType'], ['application/x-bittorrent', 'application/x-bittorrent;x-scheme-handler/magnet'])) {
+                       $data['type'] = Post\Media::TORRENT;
+               } else {
+                       Logger::info('Unknown type', ['attachment' => $attachment]);
+                       return;
+               }
+
+               $data['url'] = $attachment['url'];
+               $data['mimetype'] = $attachment['mediaType'];
+               $data['height'] = $attachment['height'] ?? null;
+               $data['size'] = $attachment['size'] ?? null;
+               $data['preview'] = $attachment['image'] ?? null;
+               $data['description'] = $attachment['name'] ?? null;
+
+               Post\Media::insert($data);
+       }
+
        /**
         * Add attachment data to the item array
         *
@@ -95,6 +135,8 @@ class Processor
                        return $item;
                }
 
+               $item['attach'] = '';
+
                foreach ($activity['attachments'] as $attach) {
                        switch ($attach['type']) {
                                case 'link':
@@ -110,6 +152,8 @@ class Processor
                                        $item['body'] = PageInfo::appendDataToBody($item['body'], $data);
                                        break;
                                default:
+                                       self::storeAttachmentAsMedia($item['uri-id'], $attach);
+
                                        $filetype = strtolower(substr($attach['mediaType'], 0, strpos($attach['mediaType'], '/')));
                                        if ($filetype == 'image') {
                                                if (!empty($activity['source']) && strpos($activity['source'], $attach['url'])) {
@@ -146,13 +190,13 @@ class Processor
 
                                                $item['body'] .= "\n[video]" . $attach['url'] . '[/video]';
                                        } else {
-                                               if (!empty($item["attach"])) {
-                                                       $item["attach"] .= ',';
+                                               if (!empty($item['attach'])) {
+                                                       $item['attach'] .= ',';
                                                } else {
-                                                       $item["attach"] = '';
+                                                       $item['attach'] = '';
                                                }
 
-                                               $item["attach"] .= '[attach]href="' . $attach['url'] . '" length="' . ($attach['length'] ?? '0') . '" type="' . $attach['mediaType'] . '" title="' . ($attach['name'] ?? '') . '"[/attach]';
+                                               $item['attach'] .= '[attach]href="' . $attach['url'] . '" length="' . ($attach['length'] ?? '0') . '" type="' . $attach['mediaType'] . '" title="' . ($attach['name'] ?? '') . '"[/attach]';
                                        }
                        }
                }
@@ -180,6 +224,9 @@ class Processor
                $item['edited'] = DateTimeFormat::utc($activity['updated']);
 
                $item = self::processContent($activity, $item);
+
+               $item = self::constructAttachList($activity, $item);
+
                if (empty($item)) {
                        return;
                }
@@ -403,17 +450,18 @@ class Processor
        {
                $item['title'] = HTML::toBBCode($activity['name']);
 
-               if (!empty($activity['source'])) {
-                       $item['body'] = $activity['source'];
-               } else {
-                       $content = HTML::toBBCode($activity['content']);
+               $content = HTML::toBBCode($activity['content']);
 
-                       if (!empty($activity['emojis'])) {
-                               $content = self::replaceEmojis($content, $activity['emojis']);
-                       }
+               if (!empty($activity['emojis'])) {
+                       $content = self::replaceEmojis($content, $activity['emojis']);
+               }
 
-                       $content = self::convertMentions($content);
+               $content = self::convertMentions($content);
 
+               if (!empty($activity['source'])) {
+                       $item['body'] = $activity['source'];
+                       $item['raw-body'] = $content;
+               } else {
                        if (empty($activity['directmessage']) && ($item['thr-parent'] != $item['uri']) && ($item['gravity'] == GRAVITY_COMMENT)) {
                                $item_private = !in_array(0, $activity['item_receiver']);
                                $parent = Item::selectFirst(['id', 'uri-id', 'private', 'author-link', 'alias'], ['uri' => $item['thr-parent']]);
@@ -429,7 +477,7 @@ class Processor
                                $content = self::removeImplicitMentionsFromBody($content, $parent);
                        }
                        $item['content-warning'] = HTML::toBBCode($activity['summary']);
-                       $item['body'] = $content;
+                       $item['raw-body'] = $item['body'] = $content;
                }
 
                self::storeFromBody($item);
index 69d24a7abbc2f5019e1bf8b7df5788f3b06d2df3..31a2dcb0b8dee0859cb05cd6f8a9e8ff3cc4528e 100644 (file)
@@ -1231,24 +1231,36 @@ class Receiver
                        $filetype = strtolower(substr($mediatype, 0, strpos($mediatype, '/')));
 
                        if ($filetype == 'audio') {
-                               $attachments[$filetype] = ['type' => $mediatype, 'url' => $href];
+                               $attachments[$filetype] = ['type' => $mediatype, 'url' => $href, 'height' => null, 'size' => null];
                        } elseif ($filetype == 'video') {
                                $height = (int)JsonLD::fetchElement($url, 'as:height', '@value');
+                               $size = (int)JsonLD::fetchElement($url, 'pt:size', '@value');
 
-                               // We save bandwidth by using a moderate height
+                               // We save bandwidth by using a moderate height (alt least 480 pixel height)
                                // Peertube normally uses these heights: 240, 360, 480, 720, 1080
                                if (!empty($attachments[$filetype]['height']) &&
-                                       (($height > 480) || $height < $attachments[$filetype]['height'])) {
+                                       ($height > $attachments[$filetype]['height']) && ($attachments[$filetype]['height'] >= 480)) {
                                        continue;
                                }
 
-                               $attachments[$filetype] = ['type' => $mediatype, 'url' => $href, 'height' => $height];
+                               $attachments[$filetype] = ['type' => $mediatype, 'url' => $href, 'height' => $height, 'size' => $size];
+                       } elseif (in_array($mediatype, ['application/x-bittorrent', 'application/x-bittorrent;x-scheme-handler/magnet'])) {
+                               $height = (int)JsonLD::fetchElement($url, 'as:height', '@value');
+
+                               // For Torrent links we always store the highest resolution
+                               if (!empty($attachments[$mediatype]['height']) && ($height < $attachments[$mediatype]['height'])) {
+                                       continue;
+                               }
+
+                               $attachments[$mediatype] = ['type' => $mediatype, 'url' => $href, 'height' => $height, 'size' => null];
                        }
                }
 
                foreach ($attachments as $type => $attachment) {
                        $object_data['attachments'][] = ['type' => $type,
                                'mediaType' => $attachment['type'],
+                               'height' => $attachment['height'],
+                               'size' => $attachment['size'],
                                'name' => '',
                                'url' => $attachment['url']];
                }
index fd668b1f3062755103630af9837da3eb7d053947..c9a02a4fce28ec903b96f178ac513ba15d29d590 100644 (file)
@@ -2810,6 +2810,26 @@ class Diaspora
                return Relay::isSolicitedPost($tags, $body, $contact['id'], $url, Protocol::DIASPORA);
        }
 
+       /**
+        * Store an attached photo in the post-media table
+        *
+        * @param int $uriid
+        * @param object $photo
+        * @return void
+        */
+       private static function storePhotoAsMedia(int $uriid, $photo)
+       {
+               $data = [];
+               $data['uri-id'] = $uriid;
+               $data['type'] = Post\Media::IMAGE;
+               $data['url'] = XML::unescape($photo->remote_photo_path) . XML::unescape($photo->remote_photo_name);
+               $data['height'] = (int)XML::unescape($photo->height ?? 0);
+               $data['width'] = (int)XML::unescape($photo->width ?? 0);
+               $data['description'] = XML::unescape($photo->text ?? '');
+
+               Post\Media::insert($data);
+       }
+
        /**
         * Receives status messages
         *
@@ -2847,13 +2867,18 @@ class Diaspora
                        }
                }
 
-               $body = Markdown::toBBCode($text);
+               $raw_body = $body = Markdown::toBBCode($text);
 
                $datarray = [];
 
+               $datarray["guid"] = $guid;
+               $datarray["uri"] = $datarray["parent-uri"] = self::getUriFromGuid($author, $guid);
+               $datarray['uri-id'] = ItemURI::insert(['uri' => $datarray['uri'], 'guid' => $datarray['guid']]);
+
                // Attach embedded pictures to the body
                if ($data->photo) {
                        foreach ($data->photo as $photo) {
+                               self::storePhotoAsMedia($datarray['uri-id'], $photo);
                                $body = "[img]".XML::unescape($photo->remote_photo_path).
                                        XML::unescape($photo->remote_photo_name)."[/img]\n".$body;
                        }
@@ -2887,10 +2912,6 @@ class Diaspora
                $datarray["owner-link"] = $datarray["author-link"];
                $datarray["owner-id"] = $datarray["author-id"];
 
-               $datarray["guid"] = $guid;
-               $datarray["uri"] = $datarray["parent-uri"] = self::getUriFromGuid($author, $guid);
-               $datarray['uri-id'] = ItemURI::insert(['uri' => $datarray['uri'], 'guid' => $datarray['guid']]);
-
                $datarray["verb"] = Activity::POST;
                $datarray["gravity"] = GRAVITY_PARENT;
 
@@ -2904,6 +2925,7 @@ class Diaspora
                }
 
                $datarray["body"] = self::replacePeopleGuid($body, $contact["url"]);
+               $datarray["raw-body"] = self::replacePeopleGuid($raw_body, $contact["url"]);
 
                self::storeMentions($datarray['uri-id'], $text);
                Tag::storeRawTagsFromBody($datarray['uri-id'], $datarray["body"]);
index 0f24e549f77c39043248cc3d0e6ceec4d7067890..d7a99153f69efaddc14427490ffb916ec968697d 100644 (file)
@@ -31,9 +31,19 @@ class CleanItemUri
         */
        public static function execute()
        {
-               $ret = DBA::e("DELETE FROM `item-uri` WHERE NOT `id` IN (SELECT `uri-id` FROM `item`)
+               // We have to avoid deleting newly created "item-uri" entries.
+               // So we fetch a post that had been stored yesterday and only delete older ones.
+               $item = DBA::selectFirst('item', ['uri-id'], ["`uid` = ? AND `received` < UTC_TIMESTAMP() - INTERVAL ? DAY", 0, 1],
+                       ['order' => ['received' => true]]);
+               if (empty($item['uri-id'])) {
+                       Logger::warning('No item with uri-id found - we better quit here');
+                       return;
+               }
+               Logger::notice('Start deleting orphaned URI-ID', ['last-id' => $item['uri-id']]);
+               $ret = DBA::e("DELETE FROM `item-uri` WHERE `id` < ?
+                       AND NOT `id` IN (SELECT `uri-id` FROM `item`)
                        AND NOT `id` IN (SELECT `parent-uri-id` FROM `item`)
-                       AND NOT `id` IN (SELECT `thr-parent-id` FROM `item`)");
+                       AND NOT `id` IN (SELECT `thr-parent-id` FROM `item`)", $item['uri-id']);
                Logger::notice('Orphaned URI-ID entries removed', ['result' => $ret, 'rows' => DBA::affectedRows()]);
        }
 }
index 3322616785b07a5cea7a619a09cf6d18397b3a9c..a3b94e0b27f69325398fc3007fe5e84527d4e98e 100755 (executable)
@@ -54,7 +54,7 @@
 use Friendica\Database\DBA;
 
 if (!defined('DB_UPDATE_VERSION')) {
-       define('DB_UPDATE_VERSION', 1371);
+       define('DB_UPDATE_VERSION', 1372);
 }
 
 return [
@@ -843,6 +843,7 @@ return [
                        "title" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => "item title"],
                        "content-warning" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""],
                        "body" => ["type" => "mediumtext", "comment" => "item body content"],
+                       "raw-body" => ["type" => "mediumtext", "comment" => "Body without embedded media links"],
                        "location" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => "text location where this item originated"],
                        "coord" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => "longitude/latitude pair representing location where this item originated"],
                        "language" => ["type" => "text", "comment" => "Language information about this post"],
@@ -1133,6 +1134,27 @@ return [
                        "PRIMARY" => ["uri-id"],
                ]
        ],
+       "post-media" => [
+               "comment" => "Attached media",
+               "fields" => [
+                       "id" => ["type" => "int unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1", "comment" => "sequential ID"],
+                       "uri-id" => ["type" => "int unsigned", "not null" => "1", "foreign" => ["item-uri" => "id"], "comment" => "Id of the item-uri table entry that contains the item uri"],
+                       "url" => ["type" => "varbinary(511)", "not null" => "1", "comment" => "Media URL"],
+                       "type" => ["type" => "tinyint unsigned", "not null" => "1", "default" => "0", "comment" => "Media type"],
+                       "mimetype" => ["type" => "varchar(60)", "comment" => ""],
+                       "height" => ["type" => "smallint unsigned", "comment" => "Height of the media"],
+                       "width" => ["type" => "smallint unsigned", "comment" => "Width of the media"],
+                       "size" => ["type" => "int unsigned", "comment" => "Media size"],
+                       "preview" => ["type" => "varbinary(255)", "comment" => "Preview URL"],
+                       "preview-height" => ["type" => "smallint unsigned", "comment" => "Height of the preview picture"],
+                       "preview-width" => ["type" => "smallint unsigned", "comment" => "Width of the preview picture"],
+                       "description" => ["type" => "text", "comment" => ""],
+               ],
+               "indexes" => [
+                       "PRIMARY" => ["id"],
+                       "uri-id-url" => ["UNIQUE", "uri-id", "url"],
+               ]
+       ],
        "post-tag" => [
                "comment" => "post relation to tags",
                "fields" => [
index 6c6065f1cc2e01702d6ac3c731ce6604fd350ba5..487f27a0eb0fc3b7a0c3685e6686110cb3e88e35 100644 (file)
 
        {{include file="field_input.tpl" field=$photo}}
 
-
-       {{if $allow_remote_self eq 1}}
-       <h4>{{$label_remote_self}}</h4>
-       {{include file="field_select.tpl" field=$remote_self}}
-       {{/if}}
-
        <input type="submit" name="submit" value="{{$lbl_submit}}" />
 
 </form>
index 1dc9e531f911e320f6d03f600e5f7c8dc24fa609..9d93494a1b24c13065424f497405d236c2d5df01 100644 (file)
                                                {{include file="field_select.tpl" field=$fetch_further_information}}
                                                {{if $fetch_further_information.2 == 2 || $fetch_further_information.2 == 3}} {{include file="field_textarea.tpl" field=$ffi_keyword_denylist}} {{/if}}
                                        {{/if}}
+                                       {{if $allow_remote_self}}
+                                               {{include file="field_select.tpl" field=$remote_self}}
+                                       {{/if}}
+
                                        {{include file="field_checkbox.tpl" field=$hidden}}
 
                                <div id="contact-edit-info-wrapper">
index 30a2c4bea029ca15ccd1d6842ee74731da34c573..6dff44a755c011ce06fac2951e9ee914d2f2ca4d 100644 (file)
 
                {{include file="field_input.tpl" field=$photo}}
 
-
-               {{if $allow_remote_self eq 1}}
-               <h4>{{$label_remote_self}}</h4>
-               {{include file="field_select.tpl" field=$remote_self}}
-               {{/if}}
-
                <div class="pull-right settings-submit-wrapper" >
                        <button type="submit" name="submit" class="btn btn-primary" value="{{$lbl_submit}}">{{$lbl_submit}}</button>
                </div>
index 68ca758a230b2ef61f75d1a91d312bb64bfa3fe7..776e8f8aff411346bdcefba60f9ef366dbb0c517 100644 (file)
                                                                        {{include file="field_select.tpl" field=$fetch_further_information}}
                                                                        {{if $fetch_further_information.2 == 2 || $fetch_further_information.2 == 3}} {{include file="field_textarea.tpl" field=$ffi_keyword_denylist}} {{/if}}
                                                                {{/if}}
+                                                               {{if $allow_remote_self}}
+                                                                       {{include file="field_select.tpl" field=$remote_self}}
+                                                               {{/if}}
+
                                                                {{include file="field_checkbox.tpl" field=$hidden}}
 
                                                                <div class="pull-right settings-submit-wrapper" >
index 3f8315c835bf29415e786dfe20285acd512559ea..e4e9ec44fb8ca10a7117dca113f857a9f01d47af 100644 (file)
                                                {{include file="field_select.tpl" field=$fetch_further_information}}
                                                {{if $fetch_further_information.2 == 2 || $fetch_further_information.2 == 3}} {{include file="field_textarea.tpl" field=$ffi_keyword_denylist}} {{/if}}
                                        {{/if}}
+                                       {{if $allow_remote_self}}
+                                               {{include file="field_select.tpl" field=$remote_self}}
+                                       {{/if}}
+
                                        {{include file="field_checkbox.tpl" field=$hidden}}
 
                                <div id="contact-edit-info-wrapper">