diff options
author | Maxence Lange <maxence@artificial-owl.com> | 2019-05-16 16:46:06 -0100 |
---|---|---|
committer | Maxence Lange <maxence@artificial-owl.com> | 2019-05-16 16:46:06 -0100 |
commit | d16537b403a0ee29f6c4d5d99c9e8cadd7a201e3 (patch) | |
tree | f920c293f47a15947e94e59503e3fa3ef475eb51 /lib/Db/StreamRequestBuilder.php | |
parent | 7d5ea91b58fce9e95b8e524ab8a6fed5858c2794 (diff) |
Notes -> Stream
Signed-off-by: Maxence Lange <maxence@artificial-owl.com>
Diffstat (limited to 'lib/Db/StreamRequestBuilder.php')
-rw-r--r-- | lib/Db/StreamRequestBuilder.php | 396 |
1 files changed, 396 insertions, 0 deletions
diff --git a/lib/Db/StreamRequestBuilder.php b/lib/Db/StreamRequestBuilder.php new file mode 100644 index 00000000..254d3ca0 --- /dev/null +++ b/lib/Db/StreamRequestBuilder.php @@ -0,0 +1,396 @@ +<?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\Db; + + +use daita\MySmallPhpTools\Traits\TArrayTools; +use Doctrine\DBAL\Query\QueryBuilder; +use OCA\Social\Exceptions\InvalidResourceException; +use OCA\Social\Model\ActivityPub\ACore; +use OCA\Social\Model\ActivityPub\Actor\Person; +use OCA\Social\Model\ActivityPub\Object\Note; +use OCA\Social\Model\ActivityPub\Stream; +use OCA\Social\Model\InstancePath; +use OCP\DB\QueryBuilder\ICompositeExpression; +use OCP\DB\QueryBuilder\IQueryBuilder; + + +/** + * Class StreamRequestBuilder + * + * @package OCA\Social\Db + */ +class StreamRequestBuilder extends CoreRequestBuilder { + + + use TArrayTools; + + + /** + * Base of the Sql Insert request + * + * @return IQueryBuilder + */ + protected function getStreamInsertSql(): IQueryBuilder { + $qb = $this->dbConnection->getQueryBuilder(); + $qb->insert(self::TABLE_STREAMS); + + return $qb; + } + + + /** + * Base of the Sql Update request + * + * @return IQueryBuilder + */ + protected function getStreamUpdateSql(): IQueryBuilder { + $qb = $this->dbConnection->getQueryBuilder(); + $qb->update(self::TABLE_STREAMS); + + return $qb; + } + + + /** + * Base of the Sql Select request for Shares + * + * @return IQueryBuilder + */ + protected function getStreamSelectSql(): IQueryBuilder { + $qb = $this->dbConnection->getQueryBuilder(); + + /** @noinspection PhpMethodParametersCountMismatchInspection */ + $qb->selectDistinct('s.id') + ->addSelect( + 's.type', 's.to', 's.to_array', 's.cc', 's.bcc', 's.content', + 's.summary', 's.attachments', 's.published', 's.published_time', 's.cache', + 's.object_id', + 's.attributed_to', 's.in_reply_to', 's.source', 's.local', 's.instances', + 's.creation' + ) + ->from(self::TABLE_STREAMS, 's'); + + $this->defaultSelectAlias = 's'; + + return $qb; + } + + + /** + * Base of the Sql Select request for Shares + * + * @return IQueryBuilder + */ + protected function countNotesSelectSql(): IQueryBuilder { + $qb = $this->dbConnection->getQueryBuilder(); + $qb->selectAlias($qb->createFunction('COUNT(*)'), 'count') + ->from(self::TABLE_STREAMS, 's'); + + $this->limitToType($qb, Note::TYPE); + + $this->defaultSelectAlias = 's'; + + return $qb; + } + + + /** + * Base of the Sql Delete request + * + * @return IQueryBuilder + */ + protected function getStreamDeleteSql(): IQueryBuilder { + $qb = $this->dbConnection->getQueryBuilder(); + $qb->delete(self::TABLE_STREAMS); + + return $qb; + } + + + /** + * @param IQueryBuilder $qb + */ + protected function limitToViewer(IQueryBuilder $qb) { + $actor = $this->viewer; + + $on = $this->exprJoinFollowing($qb, $actor, false); + $on->add($this->exprLimitToRecipient($qb, ACore::CONTEXT_PUBLIC, false)); + $on->add($this->exprLimitToRecipient($qb, $actor->getId(), true)); + $qb->join($this->defaultSelectAlias, CoreRequestBuilder::TABLE_FOLLOWS, 'f', $on); + } + + + /** + * @param IQueryBuilder $qb + * @param Person $actor + */ + protected function joinFollowing(IQueryBuilder $qb, Person $actor) { + if ($qb->getType() !== QueryBuilder::SELECT) { + return; + } + + $on = $this->exprJoinFollowing($qb, $actor); + $qb->join($this->defaultSelectAlias, CoreRequestBuilder::TABLE_FOLLOWS, 'f', $on); + } + + + /** + * @param IQueryBuilder $qb + * @param Person $actor + * @param bool $followers + * + * @return ICompositeExpression + */ + protected function exprJoinFollowing(IQueryBuilder $qb, Person $actor, bool $followers = true) { + $expr = $qb->expr(); + $func = $qb->func(); + $pf = $this->defaultSelectAlias . '.'; + + $on = $expr->orX(); + if ($followers) { + $on->add($this->exprLimitToRecipient($qb, $actor->getFollowers(), false)); + } + + // list of possible recipient as a follower (to, to_array, cc, ...) + $recipientFields = $expr->orX(); + $recipientFields->add($expr->eq($func->lower($pf . 'to'), $func->lower('f.follow_id'))); + $recipientFields->add($this->exprFieldWithinJsonFormat($qb, 'to_array', 'f.follow_id')); + $recipientFields->add($this->exprFieldWithinJsonFormat($qb, 'cc', 'f.follow_id')); + $recipientFields->add($this->exprFieldWithinJsonFormat($qb, 'bcc', 'f.follow_id')); + + // all possible follow, but linked by followers (actor_id) and accepted follow + $crossFollows = $expr->andX(); + $crossFollows->add($recipientFields); + $crossFollows->add($this->exprLimitToDBField($qb, 'actor_id', $actor->getId(), false, 'f')); + $crossFollows->add($this->exprLimitToDBFieldInt($qb, 'accepted', 1, 'f')); + $on->add($crossFollows); + + return $on; + } + + + /** + * @param IQueryBuilder $qb + * @param string $field + * @param string $fieldRight + * @param string $alias + * + * @return string + */ + protected function exprFieldWithinJsonFormat( + IQueryBuilder $qb, string $field, string $fieldRight, string $alias = '' + ) { + $func = $qb->func(); + $expr = $qb->expr(); + + if ($alias === '') { + $alias = $this->defaultSelectAlias; + } + + $concat = $func->concat( + $qb->createNamedParameter('%"'), + $func->concat($fieldRight, $qb->createNamedParameter('"%')) + ); + + return $expr->iLike($alias . '.' . $field, $concat); + } + + + /** + * @param IQueryBuilder $qb + * @param string $field + * @param string $value + * + * @return string + */ + protected function exprValueWithinJsonFormat(IQueryBuilder $qb, string $field, string $value + ): string { + $dbConn = $this->dbConnection; + $expr = $qb->expr(); + + return $expr->iLike( + $field, + $qb->createNamedParameter('%"' . $dbConn->escapeLikeParameter($value) . '"%') + ); + } + + + /** + * @param IQueryBuilder $qb + * @param string $field + * @param string $value + * + * @return string + */ + protected function exprValueNotWithinJsonFormat(IQueryBuilder $qb, string $field, string $value + ): string { + $dbConn = $this->dbConnection; + $expr = $qb->expr(); + $func = $qb->func(); + + return $expr->notLike( + $func->lower($field), + $qb->createNamedParameter( + '%"' . $func->lower($dbConn->escapeLikeParameter($value)) . '"%' + ) + ); + } + + + /** + * @param IQueryBuilder $qb + * @param string $recipient + * @param bool $asAuthor + * @param array $type + */ + protected function limitToRecipient( + IQueryBuilder &$qb, string $recipient, bool $asAuthor = false, array $type = [] + ) { + $qb->andWhere($this->exprLimitToRecipient($qb, $recipient, $asAuthor, $type)); + } + + + /** + * @param IQueryBuilder $qb + * @param string $recipient + * @param bool $asAuthor + * @param array $type + * + * @return ICompositeExpression + */ + protected function exprLimitToRecipient( + IQueryBuilder &$qb, string $recipient, bool $asAuthor = false, array $type = [] + ): ICompositeExpression { + + $expr = $qb->expr(); + $limit = $expr->orX(); + + if ($asAuthor === true) { + $func = $qb->func(); + $limit->add( + $expr->eq( + $func->lower('attributed_to'), + $func->lower($qb->createNamedParameter($recipient)) + ) + ); + } + + if ($type === []) { + $type = ['to', 'cc', 'bcc']; + } + + $this->addLimitToRecipient($qb, $limit, $type, $recipient); + + return $limit; + } + + + /** + * @param IQueryBuilder $qb + * @param ICompositeExpression $limit + * @param array $type + * @param string $to + */ + private function addLimitToRecipient( + IQueryBuilder $qb, ICompositeExpression &$limit, array $type, string $to + ) { + + $expr = $qb->expr(); + if (in_array('to', $type)) { + $limit->add($expr->eq('to', $qb->createNamedParameter($to))); + $limit->add($this->exprValueWithinJsonFormat($qb, 'to_array', $to)); + } + + if (in_array('cc', $type)) { + $limit->add($this->exprValueWithinJsonFormat($qb, 'cc', $to)); + } + + if (in_array('bcc', $type)) { + $limit->add($this->exprValueWithinJsonFormat($qb, 'bcc', $to)); + } + } + + + /** + * @param IQueryBuilder $qb + * @param string $recipient + */ + protected function filterToRecipient(IQueryBuilder &$qb, string $recipient) { + + $expr = $qb->expr(); + $filter = $expr->andX(); + + $filter->add($expr->neq('to', $qb->createNamedParameter($recipient))); + $filter->add($this->exprValueNotWithinJsonFormat($qb, 'to_array', $recipient)); + $filter->add($this->exprValueNotWithinJsonFormat($qb, 'cc', $recipient)); + $filter->add($this->exprValueNotWithinJsonFormat($qb, 'bcc', $recipient)); + + $qb->andWhere($filter); +// return $filter; + } + + + /** + * @param array $data + * + * @return Stream + */ + protected function parseStreamSelectSql($data): Stream { + $item = new Stream(); + $item->importFromDatabase($data); + + $instances = json_decode($this->get('instances', $data, '[]'), true); + if (is_array($instances)) { + foreach ($instances as $instance) { + $instancePath = new InstancePath(); + $instancePath->import($instance); + $item->addInstancePath($instancePath); + } + } + + try { + $actor = $this->parseCacheActorsLeftJoin($data); + $item->setCompleteDetails(true); + $item->setActor($actor); + } catch (InvalidResourceException $e) { + } + + try { + $action = $this->parseStreamActionsLeftJoin($data); + $item->setAction($action); + } catch (InvalidResourceException $e) { + } + + return $item; + } + +} + |