3 * @copyright Copyright (C) 2010-2023, the Friendica project
5 * @license GNU AGPL version 3 or any later version
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Affero General Public License as
9 * published by the Free Software Foundation, either version 3 of the
10 * License, or (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Affero General Public License for more details.
17 * You should have received a copy of the GNU Affero General Public License
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
22 namespace Friendica\Protocol\Diaspora\Repository;
24 use Friendica\BaseRepository;
25 use Friendica\Database\Database;
26 use Friendica\Database\Definition\DbaDefinition;
27 use Friendica\Model\APContact;
28 use Friendica\Model\Contact;
29 use Friendica\Model\Item;
30 use Friendica\Model\ItemURI;
31 use Friendica\Network\HTTPException;
32 use Friendica\Protocol\Diaspora\Entity;
33 use Friendica\Protocol\Diaspora\Factory;
34 use Friendica\Protocol\WebFingerUri;
35 use Friendica\Util\DateTimeFormat;
36 use Psr\Http\Message\UriInterface;
37 use Psr\Log\LoggerInterface;
39 class DiasporaContact extends BaseRepository
41 const ALWAYS_UPDATE = true;
42 const NEVER_UPDATE = false;
43 const UPDATE_IF_MISSING_OR_OUTDATED = null;
45 protected static $table_name = 'diaspora-contact-view';
47 /** @var Factory\DiasporaContact */
49 /** @var DbaDefinition */
52 public function __construct(DbaDefinition $definition, Database $database, LoggerInterface $logger, Factory\DiasporaContact $factory)
54 parent::__construct($database, $logger, $factory);
56 $this->definition = $definition;
60 * @param array $condition
61 * @param array $params
62 * @return Entity\DiasporaContact
63 * @throws HTTPException\NotFoundException
65 public function selectOne(array $condition, array $params = []): Entity\DiasporaContact
67 return parent::_selectOne($condition, $params);
72 * @return Entity\DiasporaContact
73 * @throws HTTPException\NotFoundException
75 public function selectOneByUriId(int $uriId): Entity\DiasporaContact
77 return $this->selectOne(['uri-id' => $uriId]);
81 * @param UriInterface $uri
82 * @return Entity\DiasporaContact
83 * @throws HTTPException\NotFoundException
85 public function selectOneByUri(UriInterface $uri): Entity\DiasporaContact
88 return $this->selectOne(['url' => (string) $uri]);
89 } catch (HTTPException\NotFoundException $e) {
93 return $this->selectOne(['addr' => (string) $uri]);
94 } catch (HTTPException\NotFoundException $e) {
97 return $this->selectOne(['alias' => (string) $uri]);
101 * @param WebFingerUri $uri
102 * @return Entity\DiasporaContact
103 * @throws HTTPException\NotFoundException
105 public function selectOneByAddr(WebFingerUri $uri): Entity\DiasporaContact
107 return $this->selectOne(['addr' => $uri->getAddr()]);
115 public function existsByUriId(int $uriId): bool
117 return $this->db->exists(self::$table_name, ['uri-id' => $uriId]);
120 public function save(Entity\DiasporaContact $DiasporaContact): Entity\DiasporaContact
122 $uriId = $DiasporaContact->uriId ?? ItemURI::insert(['uri' => $DiasporaContact->url, 'guid' => $DiasporaContact->guid]);
126 'addr' => $DiasporaContact->addr,
127 'alias' => (string)$DiasporaContact->alias,
128 'nick' => $DiasporaContact->nick,
129 'name' => $DiasporaContact->name,
130 'given-name' => $DiasporaContact->givenName,
131 'family-name' => $DiasporaContact->familyName,
132 'photo' => (string)$DiasporaContact->photo,
133 'photo-medium' => (string)$DiasporaContact->photoMedium,
134 'photo-small' => (string)$DiasporaContact->photoSmall,
135 'batch' => (string)$DiasporaContact->batch,
136 'notify' => (string)$DiasporaContact->notify,
137 'poll' => (string)$DiasporaContact->poll,
138 'subscribe' => (string)$DiasporaContact->subscribe,
139 'searchable' => $DiasporaContact->searchable,
140 'pubkey' => $DiasporaContact->pubKey,
141 'gsid' => $DiasporaContact->gsid,
142 'created' => $DiasporaContact->created->format(DateTimeFormat::MYSQL),
143 'updated' => DateTimeFormat::utcNow(),
144 'interacting_count' => $DiasporaContact->interacting_count,
145 'interacted_count' => $DiasporaContact->interacted_count,
146 'post_count' => $DiasporaContact->post_count,
149 // Limit the length on incoming fields
150 $fields = $this->definition->truncateFieldsForTable('diaspora-contact', $fields);
152 $this->db->insert('diaspora-contact', $fields, Database::INSERT_UPDATE);
154 return $this->selectOneByUriId($uriId);
158 * Fetch a Diaspora profile from a given WebFinger address and updates it depending on the mode
160 * @param WebFingerUri $uri Profile address
161 * @param boolean $update true = always update, false = never update, null = update when not found or outdated
162 * @return Entity\DiasporaContact
163 * @throws HTTPException\NotFoundException
165 public function getByAddr(WebFingerUri $uri, ?bool $update = self::UPDATE_IF_MISSING_OR_OUTDATED): Entity\DiasporaContact
167 if ($update !== self::ALWAYS_UPDATE) {
169 $dcontact = $this->selectOneByAddr($uri);
170 if ($update === self::NEVER_UPDATE) {
173 } catch (HTTPException\NotFoundException $e) {
174 if ($update === self::NEVER_UPDATE) {
178 // This is necessary for Contact::getByURL in case the base contact record doesn't need probing,
179 // but we still need the result of a probe to create the missing diaspora-contact record.
180 $update = self::ALWAYS_UPDATE;
184 $contact = Contact::getByURL($uri, $update, ['uri-id']);
185 if (empty($contact['uri-id'])) {
186 throw new HTTPException\NotFoundException('Diaspora profile with URI ' . $uri . ' not found');
189 return self::selectOneByUriId($contact['uri-id']);
193 * Fetch a Diaspora profile from a given profile URL and updates it depending on the mode
195 * @param UriInterface $uri Profile URL
196 * @param boolean $update true = always update, false = never update, null = update when not found or outdated
197 * @return Entity\DiasporaContact
198 * @throws HTTPException\NotFoundException
200 public function getByUrl(UriInterface $uri, ?bool $update = self::UPDATE_IF_MISSING_OR_OUTDATED): Entity\DiasporaContact
202 if ($update !== self::ALWAYS_UPDATE) {
204 $dcontact = $this->selectOneByUriId(ItemURI::getIdByURI($uri));
205 if ($update === self::NEVER_UPDATE) {
208 } catch (HTTPException\NotFoundException $e) {
209 if ($update === self::NEVER_UPDATE) {
213 // This is necessary for Contact::getByURL in case the base contact record doesn't need probing,
214 // but we still need the result of a probe to create the missing diaspora-contact record.
215 $update = self::ALWAYS_UPDATE;
219 $contact = Contact::getByURL($uri, $update, ['uri-id']);
220 if (empty($contact['uri-id'])) {
221 throw new HTTPException\NotFoundException('Diaspora profile with URI ' . $uri . ' not found');
224 return self::selectOneByUriId($contact['uri-id']);
228 * Update or create a diaspora-contact entry via a probe array
230 * @param array $data Probe array
231 * @return Entity\DiasporaContact
234 public function updateFromProbeArray(array $data): Entity\DiasporaContact
236 if (empty($data['url'])) {
237 throw new \InvalidArgumentException('Missing url key in Diaspora probe data array');
240 if (empty($data['guid'])) {
241 throw new \InvalidArgumentException('Missing guid key in Diaspora probe data array');
244 if (empty($data['pubkey'])) {
245 throw new \InvalidArgumentException('Missing pubkey key in Diaspora probe data array');
248 $uriId = ItemURI::insert(['uri' => $data['url'], 'guid' => $data['guid']]);
250 $contact = Contact::getByUriId($uriId, ['id', 'created']);
251 $apcontact = APContact::getByURL($data['url'], false);
252 if (!empty($apcontact)) {
253 $interacting_count = $apcontact['followers_count'];
254 $interacted_count = $apcontact['following_count'];
255 $post_count = $apcontact['statuses_count'];
256 } elseif (!empty($contact['id'])) {
257 $last_interaction = DateTimeFormat::utc('now - 180 days');
259 $interacting_count = $this->db->count('contact-relation', ["`relation-cid` = ? AND NOT `follows` AND `last-interaction` > ?", $contact['id'], $last_interaction]);
260 $interacted_count = $this->db->count('contact-relation', ["`cid` = ? AND NOT `follows` AND `last-interaction` > ?", $contact['id'], $last_interaction]);
261 $post_count = $this->db->count('post', ['author-id' => $contact['id'], 'gravity' => [Item::GRAVITY_PARENT, Item::GRAVITY_COMMENT]]);
264 $DiasporaContact = $this->factory->createfromProbeData(
267 new \DateTime($contact['created'] ?? 'now', new \DateTimeZone('UTC')),
268 $interacting_count ?? 0,
269 $interacted_count ?? 0,
273 $DiasporaContact = $this->save($DiasporaContact);
275 $this->logger->info('Updated diaspora-contact', ['url' => (string) $DiasporaContact->url]);
277 return $DiasporaContact;
281 * get a url (scheme://domain.tld/u/user) from a given contact guid
283 * @param mixed $guid Hexadecimal string guid
285 * @return string the contact url or null
288 public function getUrlByGuid(string $guid): ?string
290 $diasporaContact = $this->db->selectFirst(self::$table_name, ['url'], ['guid' => $guid]);
292 return $diasporaContact['url'] ?? null;