diff options
24 files changed, 461 insertions, 57 deletions
diff --git a/appinfo/routes.php b/appinfo/routes.php index 60fbd98b..d0816a74 100644 --- a/appinfo/routes.php +++ b/appinfo/routes.php @@ -89,9 +89,9 @@ return [ ['name' => 'Local#globalActorAvatar', 'url' => '/api/v1/global/actor/avatar', 'verb' => 'GET'], ['name' => 'Local#globalAccountsSearch', 'url' => '/api/v1/global/accounts/search', 'verb' => 'GET'], - ['name' => 'Local#search', 'url' => '/api/v1/search', 'verb' => 'GET'], +// ['name' => 'Local#documentsCache', 'url' => '/api/v1/documents/cache', 'verb' => 'POST'], - ['name' => 'Local#documentsCache', 'url' => '/api/v1/documents/cache', 'verb' => 'POST'], + ['name' => 'Local#search', 'url' => '/api/v1/search', 'verb' => 'GET'], ['name' => 'Queue#asyncForRequest', 'url' => CurlService::ASYNC_REQUEST_TOKEN, 'verb' => 'POST'], @@ -48,6 +48,8 @@ use OCA\Social\Interfaces\Activity\UndoInterface; use OCA\Social\Interfaces\Activity\UpdateInterface; use OCA\Social\Interfaces\Actor\PersonInterface; use OCA\Social\Interfaces\IActivityPubInterface; +use OCA\Social\Interfaces\Object\DocumentInterface; +use OCA\Social\Interfaces\Object\ImageInterface; use OCA\Social\Interfaces\Object\AnnounceInterface; use OCA\Social\Interfaces\Object\NoteInterface; use OCA\Social\Model\ActivityPub\ACore; @@ -104,9 +106,15 @@ class AP { /** @var DeleteInterface */ public $deleteInterface; + /** @var DocumentInterface */ + public $documentInterface; + /** @var FollowInterface */ public $followInterface; + /** @var ImageInterface */ + public $imageInterface; + /** @var LikeInterface */ public $likeInterface; @@ -155,7 +163,9 @@ class AP { $ap->blockInterface = \OC::$server->query(BlockInterface::class); $ap->createInterface = \OC::$server->query(CreateInterface::class); $ap->deleteInterface = \OC::$server->query(DeleteInterface::class); + $ap->documentInterface = \OC::$server->query(DocumentInterface::class); $ap->followInterface = \OC::$server->query(FollowInterface::class); + $ap->imageInterface = \OC::$server->query(ImageInterface::class); $ap->likeInterface = \OC::$server->query(LikeInterface::class); $ap->rejectInterface = \OC::$server->query(RejectInterface::class); $ap->removeInterface = \OC::$server->query(RemoveInterface::class); @@ -194,13 +204,6 @@ class AP { $this->getObjectFromData($data, $item, $level); - try { - /** @var Document $icon */ - $icon = $this->getItemFromData($this->getArray('icon', $data, []), $item, $level); - $item->setIcon($icon); - } catch (ItemUnknownException $e) { - } - return $item; } @@ -280,6 +283,10 @@ class AP { $item = new Delete(); break; + case Document::TYPE: + $item = new Document(); + break; + case Follow::TYPE: $item = new Follow(); break; @@ -350,66 +357,74 @@ class AP { public function getInterfaceFromType(string $type): IActivityPubInterface { switch ($type) { case Accept::TYPE: - $service = $this->acceptInterface; + $interface = $this->acceptInterface; break; case Add::TYPE: - $service = $this->addInterface; + $interface = $this->addInterface; break; case Announce::TYPE: - $service = $this->announceInterface; + $interface = $this->announceInterface; break; case Block::TYPE: - $service = $this->blockInterface; + $interface = $this->blockInterface; break; case Create::TYPE: - $service = $this->createInterface; + $interface = $this->createInterface; break; case Delete::TYPE: - $service = $this->deleteInterface; + $interface = $this->deleteInterface; + break; + + case Document::TYPE: + $interface = $this->documentInterface; break; case Follow::TYPE: - $service = $this->followInterface; + $interface = $this->followInterface; + break; + + case Image::TYPE: + $interface = $this->imageInterface; break; case Like::TYPE: - $service = $this->likeInterface; + $interface = $this->likeInterface; break; case Note::TYPE: - $service = $this->noteInterface; + $interface = $this->noteInterface; break; case Person::TYPE: - $service = $this->personInterface; + $interface = $this->personInterface; break; case Reject::TYPE: - $service = $this->rejectInterface; + $interface = $this->rejectInterface; break; case Remove::TYPE: - $service = $this->removeInterface; + $interface = $this->removeInterface; break; case Undo::TYPE: - $service = $this->undoInterface; + $interface = $this->undoInterface; break; case Update::TYPE: - $service = $this->updateInterface; + $interface = $this->updateInterface; break; default: throw new ItemUnknownException(); } - return $service; + return $interface; } } diff --git a/lib/Command/NoteBoost.php b/lib/Command/NoteBoost.php index 477d806b..dd1888ff 100644 --- a/lib/Command/NoteBoost.php +++ b/lib/Command/NoteBoost.php @@ -32,10 +32,6 @@ namespace OCA\Social\Command; use Exception; use OC\Core\Command\Base; -use OCA\Social\Model\ActivityPub\ACore; -use OCA\Social\Model\ActivityPub\Object\Announce; -use OCA\Social\Model\ActivityPub\Stream; -use OCA\Social\Model\Post; use OCA\Social\Service\AccountService; use OCA\Social\Service\ActivityService; use OCA\Social\Service\ConfigService; @@ -45,7 +41,6 @@ use OCA\Social\Service\NoteService; use OCA\Social\Service\PostService; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; -use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; diff --git a/lib/Controller/LocalController.php b/lib/Controller/LocalController.php index b8e31844..bd1978a6 100644 --- a/lib/Controller/LocalController.php +++ b/lib/Controller/LocalController.php @@ -290,6 +290,7 @@ class LocalController extends Controller { /** * @NoAdminRequired + * @NoCSRFRequired * * @param int $since * @param int $limit diff --git a/lib/Controller/NavigationController.php b/lib/Controller/NavigationController.php index f9aba1b2..fd2ae48c 100644 --- a/lib/Controller/NavigationController.php +++ b/lib/Controller/NavigationController.php @@ -36,6 +36,7 @@ use Exception; use OC\User\NoUserException; use OCA\Social\AppInfo\Application; use OCA\Social\Exceptions\AccountAlreadyExistsException; +use OCA\Social\Exceptions\ItemUnknownException; use OCA\Social\Exceptions\SocialAppConfigException; use OCA\Social\Exceptions\UrlCloudException; use OCA\Social\Service\AccountService; @@ -130,6 +131,7 @@ class NavigationController extends Controller { * * @return TemplateResponse * @throws UrlCloudException + * @throws ItemUnknownException */ public function navigate(string $path = ''): TemplateResponse { $data = [ diff --git a/lib/Controller/OStatusController.php b/lib/Controller/OStatusController.php index 182c4a3d..3ad8718d 100644 --- a/lib/Controller/OStatusController.php +++ b/lib/Controller/OStatusController.php @@ -109,7 +109,7 @@ class OStatusController extends Controller { $user = $this->userSession->getUser(); if ($user === null) { - return $this->fail('Failed to retrieve current user'); + throw new Exception('Failed to retrieve current user'); } return new TemplateResponse('social', 'ostatus', [ diff --git a/lib/Db/CacheDocumentsRequest.php b/lib/Db/CacheDocumentsRequest.php index f874feac..b095499a 100644 --- a/lib/Db/CacheDocumentsRequest.php +++ b/lib/Db/CacheDocumentsRequest.php @@ -56,6 +56,7 @@ class CacheDocumentsRequest extends CacheDocumentsRequestBuilder { ->setValue('mime_type', $qb->createNamedParameter($document->getMimeType())) ->setValue('error', $qb->createNamedParameter($document->getError())) ->setValue('local_copy', $qb->createNamedParameter($document->getLocalCopy())) + ->setValue('parent_id', $qb->createNamedParameter($document->getParentId())) ->setValue('public', $qb->createNamedParameter(($document->isPublic()) ? '1' : '0')) ->setValue( 'creation', @@ -69,6 +70,29 @@ class CacheDocumentsRequest extends CacheDocumentsRequestBuilder { /** + * insert cache about an Actor in database. + * + * @param Document $document + */ + public function update(Document $document) { + $qb = $this->getCacheDocumentsInsertSql(); + $qb->set('type', $qb->createNamedParameter($document->getType())) + ->set('url', $qb->createNamedParameter($document->getUrl())) + ->set('media_type', $qb->createNamedParameter($document->getMediaType())) + ->set('mime_type', $qb->createNamedParameter($document->getMimeType())) + ->set('error', $qb->createNamedParameter($document->getError())) + ->set('local_copy', $qb->createNamedParameter($document->getLocalCopy())) + ->set('parent_id', $qb->createNamedParameter($document->getParentId())) + ->set('public', $qb->createNamedParameter(($document->isPublic()) ? '1' : '0')) + ->set( + 'creation', + $qb->createNamedParameter(new DateTime('now'), IQueryBuilder::PARAM_DATE) + ); + $qb->execute(); + } + + + /** * @param Document $document */ public function initCaching(Document $document) { @@ -101,7 +125,7 @@ class CacheDocumentsRequest extends CacheDocumentsRequestBuilder { * @return Document * @throws CacheDocumentDoesNotExistException */ - public function getBySource(string $url) { + public function getByUrl(string $url) { $qb = $this->getCacheDocumentsSelectSql(); $this->limitToUrl($qb, $url); @@ -146,6 +170,24 @@ class CacheDocumentsRequest extends CacheDocumentsRequestBuilder { /** + * @param Document $item + * + * @return bool + */ + public function isDuplicate(Document $item): bool { + $qb = $this->getCacheDocumentsSelectSql(); + $this->limitToUrl($qb, $item->getUrl()); + $this->limitToParentId($qb, $item->getParentId()); + + $cursor = $qb->execute(); + $data = $cursor->fetch(); + $cursor->closeCursor(); + + return ($data !== false); + } + + + /** * @return Document[] * @throws Exception */ @@ -187,5 +229,6 @@ class CacheDocumentsRequest extends CacheDocumentsRequestBuilder { $qb->execute(); } + } diff --git a/lib/Db/CoreRequestBuilder.php b/lib/Db/CoreRequestBuilder.php index d6b25953..ac1956be 100644 --- a/lib/Db/CoreRequestBuilder.php +++ b/lib/Db/CoreRequestBuilder.php @@ -66,6 +66,7 @@ class CoreRequestBuilder { const TABLE_QUEUE_STREAM = 'social_queue_stream'; + /** @var IDBConnection */ protected $dbConnection; @@ -395,6 +396,17 @@ class CoreRequestBuilder { /** + * Limit the request to the parent_id + * + * @param IQueryBuilder $qb + * @param string $parentId + */ + protected function limitToParentId(IQueryBuilder &$qb, string $parentId) { + $this->limitToDBField($qb, 'parent_id', $parentId); + } + + + /** * @param IQueryBuilder $qb * @param int $since * @param int $limit diff --git a/lib/Db/HashtagsRequest.php b/lib/Db/HashtagsRequest.php index 7cf9c167..70f15282 100644 --- a/lib/Db/HashtagsRequest.php +++ b/lib/Db/HashtagsRequest.php @@ -32,11 +32,7 @@ namespace OCA\Social\Db; use daita\MySmallPhpTools\Traits\TArrayTools; -use DateTime; -use OCA\Social\Exceptions\FollowDoesNotExistException; use OCA\Social\Exceptions\HashtagDoesNotExistException; -use OCA\Social\Model\ActivityPub\Activity\Follow; -use OCP\DB\QueryBuilder\IQueryBuilder; /** diff --git a/lib/Db/HashtagsRequestBuilder.php b/lib/Db/HashtagsRequestBuilder.php index b3f1b946..2297d280 100644 --- a/lib/Db/HashtagsRequestBuilder.php +++ b/lib/Db/HashtagsRequestBuilder.php @@ -32,8 +32,6 @@ namespace OCA\Social\Db; use daita\MySmallPhpTools\Traits\TArrayTools; -use OCA\Social\Exceptions\InvalidResourceException; -use OCA\Social\Model\ActivityPub\Activity\Follow; use OCP\DB\QueryBuilder\IQueryBuilder; diff --git a/lib/Db/NotesRequest.php b/lib/Db/NotesRequest.php index db50bd58..4f44415a 100644 --- a/lib/Db/NotesRequest.php +++ b/lib/Db/NotesRequest.php @@ -32,6 +32,7 @@ namespace OCA\Social\Db; use daita\MySmallPhpTools\Model\Cache; use DateTime; +use Doctrine\DBAL\Exception\UniqueConstraintViolationException; use OCA\Social\Exceptions\NoteNotFoundException; use OCA\Social\Model\ActivityPub\ACore; use OCA\Social\Model\ActivityPub\Actor\Person; @@ -69,7 +70,12 @@ class NotesRequest extends NotesRequestBuilder { /** @var Note $stream */ $qb->setValue( 'hashtags', $qb->createNamedParameter(json_encode($stream->getHashtags())) - ); + ) + ->setValue( + 'attachments', $qb->createNamedParameter( + json_encode($stream->getAttachments(), JSON_UNESCAPED_SLASHES) + ) + ); } $qb->execute(); @@ -86,7 +92,10 @@ class NotesRequest extends NotesRequestBuilder { $this->limitToIdString($qb, $stream->getId()); - $qb->execute(); + try { + $qb->execute(); + } catch (UniqueConstraintViolationException $e) { + } } diff --git a/lib/Db/NotesRequestBuilder.php b/lib/Db/NotesRequestBuilder.php index bd9e3a7b..011c478a 100644 --- a/lib/Db/NotesRequestBuilder.php +++ b/lib/Db/NotesRequestBuilder.php @@ -84,7 +84,7 @@ class NotesRequestBuilder extends CoreRequestBuilder { $qb->selectDistinct('sn.id') ->addSelect( 'sn.type', 'sn.to', 'sn.to_array', 'sn.cc', 'sn.bcc', 'sn.content', - 'sn.summary', 'sn.published', 'sn.published_time', 'sn.cache', 'sn.object_id', + 'sn.summary', 'sn.attachments', 'sn.published', 'sn.published_time', 'sn.cache', 'sn.object_id', 'sn.attributed_to', 'sn.in_reply_to', 'sn.source', 'sn.local', 'sn.instances', 'sn.creation' ) diff --git a/lib/Db/StreamQueueRequestBuilder.php b/lib/Db/StreamQueueRequestBuilder.php index f4ac89a5..b66cec1c 100644 --- a/lib/Db/StreamQueueRequestBuilder.php +++ b/lib/Db/StreamQueueRequestBuilder.php @@ -31,7 +31,6 @@ namespace OCA\Social\Db; use daita\MySmallPhpTools\Traits\TArrayTools; -use OCA\Social\Model\RequestQueue; use OCA\Social\Model\StreamQueue; use OCP\DB\QueryBuilder\IQueryBuilder; diff --git a/lib/Interfaces/Actor/PersonInterface.php b/lib/Interfaces/Actor/PersonInterface.php index 25c0bc60..e7b6e9a8 100644 --- a/lib/Interfaces/Actor/PersonInterface.php +++ b/lib/Interfaces/Actor/PersonInterface.php @@ -39,7 +39,6 @@ use OCA\Social\Exceptions\InvalidOriginException; use OCA\Social\Exceptions\ItemNotFoundException; use OCA\Social\Interfaces\IActivityPubInterface; use OCA\Social\Model\ActivityPub\ACore; -use OCA\Social\Model\ActivityPub\Activity\Delete; use OCA\Social\Model\ActivityPub\Activity\Update; use OCA\Social\Model\ActivityPub\Actor\Person; use OCA\Social\Service\ActorService; diff --git a/lib/Interfaces/Object/DocumentInterface.php b/lib/Interfaces/Object/DocumentInterface.php index 1ecb4903..863ac8fe 100644 --- a/lib/Interfaces/Object/DocumentInterface.php +++ b/lib/Interfaces/Object/DocumentInterface.php @@ -32,6 +32,7 @@ namespace OCA\Social\Interfaces\Object; use OCA\Social\Db\CacheDocumentsRequest; +use OCA\Social\Exceptions\CacheDocumentDoesNotExistException; use OCA\Social\Exceptions\ItemNotFoundException; use OCA\Social\Interfaces\IActivityPubInterface; use OCA\Social\Model\ActivityPub\ACore; @@ -43,10 +44,10 @@ class DocumentInterface implements IActivityPubInterface { /** @var CacheDocumentsRequest */ - private $cacheDocumentsRequest; + protected $cacheDocumentsRequest; /** @var MiscService */ - private $miscService; + protected $miscService; /** @@ -100,7 +101,21 @@ class DocumentInterface implements IActivityPubInterface { */ public function save(ACore $item) { /** @var Document $item */ - $this->cacheDocumentsRequest->save($item); + if ($item->getParent()) { + $item->setParentId( + $item->getParent() + ->getId() + ); + } + + try { + $this->cacheDocumentsRequest->getById($item->getId()); + $this->cacheDocumentsRequest->update($item); + } catch (CacheDocumentDoesNotExistException $e) { + if (!$this->cacheDocumentsRequest->isDuplicate($item)) { + $this->cacheDocumentsRequest->save($item); + } + } } diff --git a/lib/Interfaces/Object/ImageInterface.php b/lib/Interfaces/Object/ImageInterface.php new file mode 100644 index 00000000..63b2e666 --- /dev/null +++ b/lib/Interfaces/Object/ImageInterface.php @@ -0,0 +1,109 @@ +<?php +declare(strict_types=1); + + +/** + * Nextcloud - Social Support + * + * This file is licensed under the Affero General Public License version 3 or + * later. See the COPYING file. + * + * @author Maxence Lange <maxence@artificial-owl.com> + * @copyright 2018, Maxence Lange <maxence@artificial-owl.com> + * @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 <http://www.gnu.org/licenses/>. + * + */ + + +namespace OCA\Social\Interfaces\Object; + + +use OCA\Social\Db\CacheDocumentsRequest; +use OCA\Social\Exceptions\ItemNotFoundException; +use OCA\Social\Interfaces\IActivityPubInterface; +use OCA\Social\Model\ActivityPub\ACore; +use OCA\Social\Service\MiscService; + + +class ImageInterface extends DocumentInterface implements IActivityPubInterface { + + + /** + * DocumentInterface constructor. + * + * @param CacheDocumentsRequest $cacheDocumentsRequest + * @param MiscService $miscService + */ + public function __construct( + CacheDocumentsRequest $cacheDocumentsRequest, MiscService $miscService + ) { + parent::__construct($cacheDocumentsRequest, $miscService); + } + + + /** + * @param ACore $activity + * @param ACore $item + */ + public function activity(Acore $activity, ACore $item) { + parent::activity($activity, $item); + } + + + /** + * @param ACore $item + */ + public function processIncomingRequest(ACore $item) { + parent::processIncomingRequest($item); + } + + + /** + * @param ACore $item + */ + public function processResult(ACore $item) { + parent::processResult($item); + } + + + /** + * @param string $id + * + * @return ACore + * @throws ItemNotFoundException + */ + public function getItemById(string $id): ACore { + return parent::getItemById($id); + } + + /** + * @param ACore $item + */ + public function save(ACore $item) { + parent::save($item); + } + + + /** + * @param ACore $item + */ + public function delete(ACore $item) { + parent::delete($item); + } + + +} + diff --git a/lib/Migration/Version0002Date20190119124417.php b/lib/Migration/Version0002Date20190119124417.php new file mode 100644 index 00000000..88713d4e --- /dev/null +++ b/lib/Migration/Version0002Date20190119124417.php @@ -0,0 +1,103 @@ +<?php +declare(strict_types=1); + + + +/** + * Nextcloud - Social Support + * + * This file is licensed under the Affero General Public License version 3 or + * later. See the COPYING file. + * + * @author Maxence Lange <maxence@artificial-owl.com> + * @copyright 2018, Maxence Lange <maxence@artificial-owl.com> + * @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 <http://www.gnu.org/licenses/>. + * + */ + + +namespace OCA\Social\Migration; + + +use Closure; +use Doctrine\DBAL\DBALException; +use Doctrine\DBAL\Schema\SchemaException; +use Doctrine\DBAL\Types\Type; +use OCA\Social\Db\CoreRequestBuilder; +use OCP\DB\ISchemaWrapper; +use OCP\IDBConnection; +use OCP\Migration\IOutput; +use OCP\Migration\SimpleMigrationStep; + + +/** + * Class Version0002Date20190108103942 + * + * @package OCA\Social\Migration + */ +class Version0002Date20190119124417 extends SimpleMigrationStep { + + + /** @var IDBConnection */ + private $connection; + + + /** + * @param IDBConnection $connection + */ + public function __construct(IDBConnection $connection) { + $this->connection = $connection; + } + + + /** + * @param IOutput $output + * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper` + * @param array $options + * + * @return ISchemaWrapper + * @throws SchemaException + * @throws DBALException + */ + public function changeSchema(IOutput $output, Closure $schemaClosure, array $options + ): ISchemaWrapper { + /** @var ISchemaWrapper $schema */ + $schema = $schemaClosure(); + + $table = $schema->getTable(CoreRequestBuilder::TABLE_SERVER_NOTES); + if (!$table->hasColumn('attachments')) { + $table->addColumn('attachments', Type::TEXT, ['notnull' => false]); + } + + $table = $schema->getTable(CoreRequestBuilder::TABLE_CACHE_DOCUMENTS); + if (!$table->hasColumn('parent_id')) { + $table->addColumn('parent_id', Type::STRING, ['notnull' => false, 'length' => 1000]); + } + + return $schema; + } + + + /** + * @param IOutput $output + * @param Closure $schemaClosure The `\Closure` returns a `ISchemaWrapper` + * @param array $options + */ + public function postSchemaChange(IOutput $output, Closure $schemaClosure, array $options) { + } + +} + diff --git a/lib/Model/ActivityPub/Object/Document.php b/lib/Model/ActivityPub/Object/Document.php index 4797acf7..932392c7 100644 --- a/lib/Model/ActivityPub/Object/Document.php +++ b/lib/Model/ActivityPub/Object/Document.php @@ -33,6 +33,7 @@ namespace OCA\Social\Model\ActivityPub\Object; use DateTime; use JsonSerializable; +use OCA\Social\Exceptions\InvalidOriginException; use OCA\Social\Exceptions\UrlCloudException; use OCA\Social\Model\ActivityPub\ACore; @@ -66,6 +67,8 @@ class Document extends ACore implements JsonSerializable { /** @var int */ private $error = 0; + /** @var string */ + private $parentId = ''; /** * Document constructor. @@ -156,6 +159,25 @@ class Document extends ACore implements JsonSerializable { /** + * @return string + */ + public function getParentId(): string { + return $this->parentId; + } + + /** + * @param string $parentId + * + * @return Document + */ + public function setParentId(string $parentId): Document { + $this->parentId = $parentId; + + return $this; + } + + + /** * @return int */ public function getError(): int { @@ -197,6 +219,7 @@ class Document extends ACore implements JsonSerializable { * @param array $data * * @throws UrlCloudException + * @throws InvalidOriginException */ public function import(array $data) { parent::import($data); @@ -205,6 +228,8 @@ class Document extends ACore implements JsonSerializable { if ($this->getId() === '') { $thi |