mirror of
https://github.com/friendica/friendica
synced 2025-04-29 19:44:22 +02:00
Merge remote-tracking branch 'upstream/develop' into sanitize-gcontact
This commit is contained in:
commit
f1e7d97b8c
1219 changed files with 241816 additions and 178090 deletions
|
@ -7,14 +7,15 @@
|
|||
namespace Friendica\Model;
|
||||
|
||||
use Friendica\BaseObject;
|
||||
use Friendica\Content\Text\HTML;
|
||||
use Friendica\Core\Logger;
|
||||
use Friendica\Core\Config;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\Protocol\ActivityPub;
|
||||
use Friendica\Util\Network;
|
||||
use Friendica\Util\JsonLD;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
use Friendica\Content\Text\HTML;
|
||||
|
||||
require_once 'boot.php';
|
||||
use Friendica\Util\Strings;
|
||||
|
||||
class APContact extends BaseObject
|
||||
{
|
||||
|
@ -22,20 +23,30 @@ class APContact extends BaseObject
|
|||
* Resolves the profile url from the address by using webfinger
|
||||
*
|
||||
* @param string $addr profile address (user@domain.tld)
|
||||
* @return string url
|
||||
* @param string $url profile URL. When set then we return "true" when this profile url can be found at the address
|
||||
* @return string|boolean url
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
private static function addrToUrl($addr)
|
||||
private static function addrToUrl($addr, $url = null)
|
||||
{
|
||||
$addr_parts = explode('@', $addr);
|
||||
if (count($addr_parts) != 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$xrd_timeout = Config::get('system', 'xrd_timeout');
|
||||
|
||||
$webfinger = 'https://' . $addr_parts[1] . '/.well-known/webfinger?resource=acct:' . urlencode($addr);
|
||||
|
||||
$curlResult = Network::curl($webfinger, false, $redirects, ['accept_content' => 'application/jrd+json,application/json']);
|
||||
$curlResult = Network::curl($webfinger, false, ['timeout' => $xrd_timeout, 'accept_content' => 'application/jrd+json,application/json']);
|
||||
if (!$curlResult->isSuccess() || empty($curlResult->getBody())) {
|
||||
return false;
|
||||
$webfinger = Strings::normaliseLink($webfinger);
|
||||
|
||||
$curlResult = Network::curl($webfinger, false, ['timeout' => $xrd_timeout, 'accept_content' => 'application/jrd+json,application/json']);
|
||||
|
||||
if (!$curlResult->isSuccess() || empty($curlResult->getBody())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$data = json_decode($curlResult->getBody(), true);
|
||||
|
@ -45,11 +56,15 @@ class APContact extends BaseObject
|
|||
}
|
||||
|
||||
foreach ($data['links'] as $link) {
|
||||
if (!empty($url) && !empty($link['href']) && ($link['href'] == $url)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (empty($link['href']) || empty($link['rel']) || empty($link['type'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (($link['rel'] == 'self') && ($link['type'] == 'application/activity+json')) {
|
||||
if (empty($url) && ($link['rel'] == 'self') && ($link['type'] == 'application/activity+json')) {
|
||||
return $link['href'];
|
||||
}
|
||||
}
|
||||
|
@ -61,8 +76,10 @@ class APContact extends BaseObject
|
|||
* Fetches a profile from a given url
|
||||
*
|
||||
* @param string $url profile url
|
||||
* @param boolean $update true = always update, false = never update, null = update when not found
|
||||
* @param boolean $update true = always update, false = never update, null = update when not found or outdated
|
||||
* @return array profile array
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
public static function getByURL($url, $update = null)
|
||||
{
|
||||
|
@ -70,66 +87,83 @@ class APContact extends BaseObject
|
|||
return false;
|
||||
}
|
||||
|
||||
$fetched_contact = false;
|
||||
|
||||
if (empty($update)) {
|
||||
if (is_null($update)) {
|
||||
$ref_update = DateTimeFormat::utc('now - 1 month');
|
||||
} else {
|
||||
$ref_update = DBA::NULL_DATETIME;
|
||||
}
|
||||
|
||||
$apcontact = DBA::selectFirst('apcontact', [], ['url' => $url]);
|
||||
if (DBA::isResult($apcontact)) {
|
||||
return $apcontact;
|
||||
if (!DBA::isResult($apcontact)) {
|
||||
$apcontact = DBA::selectFirst('apcontact', [], ['alias' => $url]);
|
||||
}
|
||||
|
||||
$apcontact = DBA::selectFirst('apcontact', [], ['alias' => $url]);
|
||||
if (DBA::isResult($apcontact)) {
|
||||
return $apcontact;
|
||||
if (!DBA::isResult($apcontact)) {
|
||||
$apcontact = DBA::selectFirst('apcontact', [], ['addr' => $url]);
|
||||
}
|
||||
|
||||
$apcontact = DBA::selectFirst('apcontact', [], ['addr' => $url]);
|
||||
if (DBA::isResult($apcontact)) {
|
||||
if (DBA::isResult($apcontact) && ($apcontact['updated'] > $ref_update) && !empty($apcontact['pubkey'])) {
|
||||
return $apcontact;
|
||||
}
|
||||
|
||||
if (!is_null($update)) {
|
||||
return false;
|
||||
return DBA::isResult($apcontact) ? $apcontact : false;
|
||||
}
|
||||
|
||||
if (DBA::isResult($apcontact)) {
|
||||
$fetched_contact = $apcontact;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty(parse_url($url, PHP_URL_SCHEME))) {
|
||||
$url = self::addrToUrl($url);
|
||||
if (empty($url)) {
|
||||
return false;
|
||||
return $fetched_contact;
|
||||
}
|
||||
}
|
||||
|
||||
$data = ActivityPub::fetchContent($url);
|
||||
if (empty($data)) {
|
||||
return false;
|
||||
return $fetched_contact;
|
||||
}
|
||||
|
||||
$compacted = JsonLD::compact($data);
|
||||
|
||||
if (empty($compacted['@id'])) {
|
||||
return $fetched_contact;
|
||||
}
|
||||
|
||||
$apcontact = [];
|
||||
$apcontact['url'] = $compacted['@id'];
|
||||
$apcontact['uuid'] = JsonLD::fetchElement($compacted, 'diaspora:guid');
|
||||
$apcontact['uuid'] = JsonLD::fetchElement($compacted, 'diaspora:guid', '@value');
|
||||
$apcontact['type'] = str_replace('as:', '', JsonLD::fetchElement($compacted, '@type'));
|
||||
$apcontact['following'] = JsonLD::fetchElement($compacted, 'as:following', '@id');
|
||||
$apcontact['followers'] = JsonLD::fetchElement($compacted, 'as:followers', '@id');
|
||||
$apcontact['inbox'] = JsonLD::fetchElement($compacted, 'ldp:inbox', '@id');
|
||||
self::unarchiveInbox($apcontact['inbox'], false);
|
||||
|
||||
$apcontact['outbox'] = JsonLD::fetchElement($compacted, 'as:outbox', '@id');
|
||||
|
||||
$apcontact['sharedinbox'] = '';
|
||||
if (!empty($compacted['as:endpoints'])) {
|
||||
$apcontact['sharedinbox'] = JsonLD::fetchElement($compacted['as:endpoints'], 'as:sharedInbox', '@id');
|
||||
self::unarchiveInbox($apcontact['sharedinbox'], true);
|
||||
}
|
||||
|
||||
$apcontact['nick'] = JsonLD::fetchElement($compacted, 'as:preferredUsername');
|
||||
$apcontact['name'] = JsonLD::fetchElement($compacted, 'as:name');
|
||||
$apcontact['nick'] = JsonLD::fetchElement($compacted, 'as:preferredUsername', '@value') ?? '';
|
||||
$apcontact['name'] = JsonLD::fetchElement($compacted, 'as:name', '@value');
|
||||
|
||||
if (empty($apcontact['name'])) {
|
||||
$apcontact['name'] = $apcontact['nick'];
|
||||
}
|
||||
|
||||
$apcontact['about'] = HTML::toBBCode(JsonLD::fetchElement($compacted, 'as:summary'));
|
||||
$apcontact['about'] = HTML::toBBCode(JsonLD::fetchElement($compacted, 'as:summary', '@value'));
|
||||
|
||||
$apcontact['photo'] = JsonLD::fetchElement($compacted, 'as:icon', '@id');
|
||||
if (is_array($apcontact['photo'])) {
|
||||
if (is_array($apcontact['photo']) || !empty($compacted['as:icon']['as:url']['@id'])) {
|
||||
$apcontact['photo'] = JsonLD::fetchElement($compacted['as:icon'], 'as:url', '@id');
|
||||
}
|
||||
|
||||
|
@ -138,22 +172,41 @@ class APContact extends BaseObject
|
|||
$apcontact['alias'] = JsonLD::fetchElement($compacted['as:url'], 'as:href', '@id');
|
||||
}
|
||||
|
||||
if (empty($apcontact['url']) || empty($apcontact['inbox'])) {
|
||||
return false;
|
||||
// Quit if none of the basic values are set
|
||||
if (empty($apcontact['url']) || empty($apcontact['inbox']) || empty($apcontact['type'])) {
|
||||
return $fetched_contact;
|
||||
}
|
||||
|
||||
// Quit if this doesn't seem to be an account at all
|
||||
if (!in_array($apcontact['type'], ActivityPub::ACCOUNT_TYPES)) {
|
||||
return $fetched_contact;
|
||||
}
|
||||
|
||||
$parts = parse_url($apcontact['url']);
|
||||
unset($parts['scheme']);
|
||||
unset($parts['path']);
|
||||
$apcontact['addr'] = $apcontact['nick'] . '@' . str_replace('//', '', Network::unparseURL($parts));
|
||||
|
||||
$apcontact['pubkey'] = trim(JsonLD::fetchElement($compacted, 'w3id:publicKey', 'w3id:publicKeyPem'));
|
||||
if (!empty($apcontact['nick'])) {
|
||||
$apcontact['addr'] = $apcontact['nick'] . '@' . str_replace('//', '', Network::unparseURL($parts));
|
||||
} else {
|
||||
$apcontact['addr'] = '';
|
||||
}
|
||||
|
||||
if (!empty($compacted['w3id:publicKey'])) {
|
||||
$apcontact['pubkey'] = trim(JsonLD::fetchElement($compacted['w3id:publicKey'], 'w3id:publicKeyPem', '@value'));
|
||||
}
|
||||
|
||||
$apcontact['manually-approve'] = (int)JsonLD::fetchElement($compacted, 'as:manuallyApprovesFollowers');
|
||||
|
||||
if (!empty($compacted['as:generator'])) {
|
||||
$apcontact['baseurl'] = JsonLD::fetchElement($compacted['as:generator'], 'as:url', '@id');
|
||||
$apcontact['generator'] = JsonLD::fetchElement($compacted['as:generator'], 'as:name', '@value');
|
||||
}
|
||||
|
||||
// To-Do
|
||||
// manuallyApprovesFollowers
|
||||
|
||||
// Unhandled
|
||||
// @context, tag, attachment, image, nomadicLocations, signature, following, followers, featured, movedTo, liked
|
||||
// tag, attachment, image, nomadicLocations, signature, featured, movedTo, liked
|
||||
|
||||
// Unhandled from Misskey
|
||||
// sharedInbox, isCat
|
||||
|
@ -161,13 +214,18 @@ class APContact extends BaseObject
|
|||
// Unhandled from Kroeg
|
||||
// kroeg:blocks, updated
|
||||
|
||||
// Check if the address is resolvable
|
||||
if (self::addrToUrl($apcontact['addr']) == $apcontact['url']) {
|
||||
$parts = parse_url($apcontact['url']);
|
||||
unset($parts['path']);
|
||||
$apcontact['baseurl'] = Network::unparseURL($parts);
|
||||
$parts = parse_url($apcontact['url']);
|
||||
unset($parts['path']);
|
||||
$baseurl = Network::unparseURL($parts);
|
||||
|
||||
// Check if the address is resolvable or the profile url is identical with the base url of the system
|
||||
if (self::addrToUrl($apcontact['addr'], $apcontact['url']) || Strings::compareLink($apcontact['url'], $baseurl)) {
|
||||
$apcontact['baseurl'] = $baseurl;
|
||||
} else {
|
||||
$apcontact['addr'] = null;
|
||||
}
|
||||
|
||||
if (empty($apcontact['baseurl'])) {
|
||||
$apcontact['baseurl'] = null;
|
||||
}
|
||||
|
||||
|
@ -179,21 +237,36 @@ class APContact extends BaseObject
|
|||
|
||||
DBA::update('apcontact', $apcontact, ['url' => $url], true);
|
||||
|
||||
// Update some data in the contact table with various ways to catch them all
|
||||
$contact_fields = ['name' => $apcontact['name'], 'about' => $apcontact['about']];
|
||||
DBA::update('contact', $contact_fields, ['nurl' => normalise_link($url)]);
|
||||
|
||||
$contacts = DBA::select('contact', ['uid', 'id'], ['nurl' => normalise_link($url)]);
|
||||
while ($contact = DBA::fetch($contacts)) {
|
||||
Contact::updateAvatar($apcontact['photo'], $contact['uid'], $contact['id']);
|
||||
// We delete the old entry when the URL is changed
|
||||
if (($url != $apcontact['url']) && DBA::exists('apcontact', ['url' => $url]) && DBA::exists('apcontact', ['url' => $apcontact['url']])) {
|
||||
DBA::delete('apcontact', ['url' => $url]);
|
||||
}
|
||||
DBA::close($contacts);
|
||||
|
||||
// Update the gcontact table
|
||||
DBA::update('gcontact', $contact_fields, ['nurl' => normalise_link($url)]);
|
||||
|
||||
logger('Updated profile for ' . $url, LOGGER_DEBUG);
|
||||
Logger::log('Updated profile for ' . $url, Logger::DEBUG);
|
||||
|
||||
return $apcontact;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unarchive inboxes
|
||||
*
|
||||
* @param string $url inbox url
|
||||
*/
|
||||
private static function unarchiveInbox($url, $shared)
|
||||
{
|
||||
if (empty($url)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$now = DateTimeFormat::utcNow();
|
||||
|
||||
$fields = ['archive' => false, 'success' => $now, 'shared' => $shared];
|
||||
|
||||
if (!DBA::exists('inbox-status', ['url' => $url])) {
|
||||
$fields = array_merge($fields, ['url' => $url, 'created' => $now]);
|
||||
DBA::insert('inbox-status', $fields);
|
||||
} else {
|
||||
DBA::update('inbox-status', $fields, ['url' => $url]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
311
src/Model/Attach.php
Normal file
311
src/Model/Attach.php
Normal file
|
@ -0,0 +1,311 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file src/Model/Attach.php
|
||||
* @brief This file contains the Attach class for database interface
|
||||
*/
|
||||
namespace Friendica\Model;
|
||||
|
||||
use Friendica\BaseObject;
|
||||
use Friendica\Core\StorageManager;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\Database\DBStructure;
|
||||
use Friendica\Model\Storage\IStorage;
|
||||
use Friendica\Object\Image;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
use Friendica\Util\Mimetype;
|
||||
use Friendica\Util\Security;
|
||||
|
||||
/**
|
||||
* Class to handle attach dabatase table
|
||||
*/
|
||||
class Attach extends BaseObject
|
||||
{
|
||||
|
||||
/**
|
||||
* @brief Return a list of fields that are associated with the attach table
|
||||
*
|
||||
* @return array field list
|
||||
* @throws \Exception
|
||||
*/
|
||||
private static function getFields()
|
||||
{
|
||||
$allfields = DBStructure::definition(self::getApp()->getBasePath(), false);
|
||||
$fields = array_keys($allfields['attach']['fields']);
|
||||
array_splice($fields, array_search('data', $fields), 1);
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Select rows from the attach table and return them as array
|
||||
*
|
||||
* @param array $fields Array of selected fields, empty for all
|
||||
* @param array $conditions Array of fields for conditions
|
||||
* @param array $params Array of several parameters
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws \Exception
|
||||
* @see \Friendica\Database\DBA::selectToArray
|
||||
*/
|
||||
public static function selectToArray(array $fields = [], array $conditions = [], array $params = [])
|
||||
{
|
||||
if (empty($fields)) {
|
||||
$fields = self::getFields();
|
||||
}
|
||||
|
||||
return DBA::selectToArray('attach', $fields, $conditions, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Retrieve a single record from the attach table
|
||||
*
|
||||
* @param array $fields Array of selected fields, empty for all
|
||||
* @param array $conditions Array of fields for conditions
|
||||
* @param array $params Array of several parameters
|
||||
*
|
||||
* @return bool|array
|
||||
*
|
||||
* @throws \Exception
|
||||
* @see \Friendica\Database\DBA::select
|
||||
*/
|
||||
public static function selectFirst(array $fields = [], array $conditions = [], array $params = [])
|
||||
{
|
||||
if (empty($fields)) {
|
||||
$fields = self::getFields();
|
||||
}
|
||||
|
||||
return DBA::selectFirst('attach', $fields, $conditions, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if attachment with given conditions exists
|
||||
*
|
||||
* @param array $conditions Array of extra conditions
|
||||
*
|
||||
* @return boolean
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function exists(array $conditions)
|
||||
{
|
||||
return DBA::exists('attach', $conditions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Retrive a single record given the ID
|
||||
*
|
||||
* @param int $id Row id of the record
|
||||
*
|
||||
* @return bool|array
|
||||
*
|
||||
* @throws \Exception
|
||||
* @see \Friendica\Database\DBA::select
|
||||
*/
|
||||
public static function getById($id)
|
||||
{
|
||||
return self::selectFirst([], ['id' => $id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Retrive a single record given the ID
|
||||
*
|
||||
* @param int $id Row id of the record
|
||||
*
|
||||
* @return bool|array
|
||||
*
|
||||
* @throws \Exception
|
||||
* @see \Friendica\Database\DBA::select
|
||||
*/
|
||||
public static function getByIdWithPermission($id)
|
||||
{
|
||||
$r = self::selectFirst(['uid'], ['id' => $id]);
|
||||
if ($r === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$sql_acl = Security::getPermissionsSQLByUserId($r['uid']);
|
||||
|
||||
$conditions = [
|
||||
'`id` = ?' . $sql_acl,
|
||||
$id
|
||||
];
|
||||
|
||||
$item = self::selectFirst([], $conditions);
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get file data for given row id. null if row id does not exist
|
||||
*
|
||||
* @param array $item Attachment data. Needs at least 'id', 'backend-class', 'backend-ref'
|
||||
*
|
||||
* @return string file data
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function getData($item)
|
||||
{
|
||||
if ($item['backend-class'] == '') {
|
||||
// legacy data storage in 'data' column
|
||||
$i = self::selectFirst(['data'], ['id' => $item['id']]);
|
||||
if ($i === false) {
|
||||
return null;
|
||||
}
|
||||
return $i['data'];
|
||||
} else {
|
||||
$backendClass = $item['backend-class'];
|
||||
$backendRef = $item['backend-ref'];
|
||||
return $backendClass::get($backendRef);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Store new file metadata in db and binary in default backend
|
||||
*
|
||||
* @param string $data Binary data
|
||||
* @param integer $uid User ID
|
||||
* @param string $filename Filename
|
||||
* @param string $filetype Mimetype. optional, default = ''
|
||||
* @param integer $filesize File size in bytes. optional, default = null
|
||||
* @param string $allow_cid Permissions, allowed contacts. optional, default = ''
|
||||
* @param string $allow_gid Permissions, allowed groups. optional, default = ''
|
||||
* @param string $deny_cid Permissions, denied contacts.optional, default = ''
|
||||
* @param string $deny_gid Permissions, denied greoup.optional, default = ''
|
||||
*
|
||||
* @return boolean/integer Row id on success, False on errors
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public static function store($data, $uid, $filename, $filetype = '' , $filesize = null, $allow_cid = '', $allow_gid = '', $deny_cid = '', $deny_gid = '')
|
||||
{
|
||||
if ($filetype === '') {
|
||||
$filetype = Mimetype::getContentType($filename);
|
||||
}
|
||||
|
||||
if (is_null($filesize)) {
|
||||
$filesize = strlen($data);
|
||||
}
|
||||
|
||||
/** @var IStorage $backend_class */
|
||||
$backend_class = StorageManager::getBackend();
|
||||
$backend_ref = '';
|
||||
if ($backend_class !== '') {
|
||||
$backend_ref = $backend_class::put($data);
|
||||
$data = '';
|
||||
}
|
||||
|
||||
$hash = System::createGUID(64);
|
||||
$created = DateTimeFormat::utcNow();
|
||||
|
||||
$fields = [
|
||||
'uid' => $uid,
|
||||
'hash' => $hash,
|
||||
'filename' => $filename,
|
||||
'filetype' => $filetype,
|
||||
'filesize' => $filesize,
|
||||
'data' => $data,
|
||||
'created' => $created,
|
||||
'edited' => $created,
|
||||
'allow_cid' => $allow_cid,
|
||||
'allow_gid' => $allow_gid,
|
||||
'deny_cid' => $deny_cid,
|
||||
'deny_gid' => $deny_gid,
|
||||
'backend-class' => $backend_class,
|
||||
'backend-ref' => $backend_ref
|
||||
];
|
||||
|
||||
$r = DBA::insert('attach', $fields);
|
||||
if ($r === true) {
|
||||
return DBA::lastInsertId();
|
||||
}
|
||||
return $r;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Store new file metadata in db and binary in default backend from existing file
|
||||
*
|
||||
* @param $src
|
||||
* @param $uid
|
||||
* @param string $filename
|
||||
* @param string $allow_cid
|
||||
* @param string $allow_gid
|
||||
* @param string $deny_cid
|
||||
* @param string $deny_gid
|
||||
* @return boolean True on success
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public static function storeFile($src, $uid, $filename = '', $allow_cid = '', $allow_gid = '', $deny_cid = '', $deny_gid = '')
|
||||
{
|
||||
if ($filename === '') {
|
||||
$filename = basename($src);
|
||||
}
|
||||
|
||||
$data = @file_get_contents($src);
|
||||
|
||||
return self::store($data, $uid, $filename, '', null, $allow_cid, $allow_gid, $deny_cid, $deny_gid);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Update an attached file
|
||||
*
|
||||
* @param array $fields Contains the fields that are updated
|
||||
* @param array $conditions Condition array with the key values
|
||||
* @param Image $img Image data to update. Optional, default null.
|
||||
* @param array|boolean $old_fields Array with the old field values that are about to be replaced (true = update on duplicate)
|
||||
*
|
||||
* @return boolean Was the update successful?
|
||||
*
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
* @see \Friendica\Database\DBA::update
|
||||
*/
|
||||
public static function update($fields, $conditions, Image $img = null, array $old_fields = [])
|
||||
{
|
||||
if (!is_null($img)) {
|
||||
// get items to update
|
||||
$items = self::selectToArray(['backend-class','backend-ref'], $conditions);
|
||||
|
||||
foreach($items as $item) {
|
||||
/** @var IStorage $backend_class */
|
||||
$backend_class = (string)$item['backend-class'];
|
||||
if ($backend_class !== '') {
|
||||
$fields['backend-ref'] = $backend_class::put($img->asString(), $item['backend-ref']);
|
||||
} else {
|
||||
$fields['data'] = $img->asString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$fields['edited'] = DateTimeFormat::utcNow();
|
||||
|
||||
return DBA::update('attach', $fields, $conditions, $old_fields);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Delete info from table and data from storage
|
||||
*
|
||||
* @param array $conditions Field condition(s)
|
||||
* @param array $options Options array, Optional
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @throws \Exception
|
||||
* @see \Friendica\Database\DBA::delete
|
||||
*/
|
||||
public static function delete(array $conditions, array $options = [])
|
||||
{
|
||||
// get items to delete data info
|
||||
$items = self::selectToArray(['backend-class','backend-ref'], $conditions);
|
||||
|
||||
foreach($items as $item) {
|
||||
/** @var IStorage $backend_class */
|
||||
$backend_class = (string)$item['backend-class'];
|
||||
if ($backend_class !== '') {
|
||||
$backend_class::delete($item['backend-ref']);
|
||||
}
|
||||
}
|
||||
|
||||
return DBA::delete('attach', $conditions, $options);
|
||||
}
|
||||
}
|
131
src/Model/Config/Config.php
Normal file
131
src/Model/Config/Config.php
Normal file
|
@ -0,0 +1,131 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Model\Config;
|
||||
|
||||
|
||||
/**
|
||||
* The Config model backend, which is using the general DB-model backend for configs
|
||||
*/
|
||||
class Config extends DbaConfig
|
||||
{
|
||||
/**
|
||||
* Loads all configuration values and returns the loaded category as an array.
|
||||
*
|
||||
* @param string|null $cat The category of the configuration values to load
|
||||
*
|
||||
* @return array The config array
|
||||
*
|
||||
* @throws \Exception In case DB calls are invalid
|
||||
*/
|
||||
public function load(string $cat = null)
|
||||
{
|
||||
$return = [];
|
||||
|
||||
if (empty($cat)) {
|
||||
$configs = $this->dba->select('config', ['cat', 'v', 'k']);
|
||||
} else {
|
||||
$configs = $this->dba->select('config', ['cat', 'v', 'k'], ['cat' => $cat]);
|
||||
}
|
||||
|
||||
while ($config = $this->dba->fetch($configs)) {
|
||||
|
||||
$key = $config['k'];
|
||||
$value = $this->toConfigValue($config['v']);
|
||||
|
||||
// just save it in case it is set
|
||||
if (isset($value)) {
|
||||
$return[$config['cat']][$key] = $value;
|
||||
}
|
||||
}
|
||||
$this->dba->close($configs);
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a particular, system-wide config variable out of the DB with the
|
||||
* given category name ($cat) and a key ($key).
|
||||
*
|
||||
* Note: Boolean variables are defined as 0/1 in the database
|
||||
*
|
||||
* @param string $cat The category of the configuration value
|
||||
* @param string $key The configuration key to query
|
||||
*
|
||||
* @return array|string|null Stored value or null if it does not exist
|
||||
*
|
||||
* @throws \Exception In case DB calls are invalid
|
||||
*/
|
||||
public function get(string $cat, string $key)
|
||||
{
|
||||
if (!$this->isConnected()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$config = $this->dba->selectFirst('config', ['v'], ['cat' => $cat, 'k' => $key]);
|
||||
if ($this->dba->isResult($config)) {
|
||||
$value = $this->toConfigValue($config['v']);
|
||||
|
||||
// just return it in case it is set
|
||||
if (isset($value)) {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores a config value ($value) in the category ($cat) under the key ($key).
|
||||
*
|
||||
* Note: Please do not store booleans - convert to 0/1 integer values!
|
||||
*
|
||||
* @param string $cat The category of the configuration value
|
||||
* @param string $key The configuration key to set
|
||||
* @param mixed $value The value to store
|
||||
*
|
||||
* @return bool Operation success
|
||||
*
|
||||
* @throws \Exception In case DB calls are invalid
|
||||
*/
|
||||
public function set(string $cat, string $key, $value)
|
||||
{
|
||||
if (!$this->isConnected()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// We store our setting values in a string variable.
|
||||
// So we have to do the conversion here so that the compare below works.
|
||||
// The exception are array values.
|
||||
$compare_value = (!is_array($value) ? (string)$value : $value);
|
||||
$stored_value = $this->get($cat, $key);
|
||||
|
||||
if (isset($stored_value) && ($stored_value === $compare_value)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$dbvalue = $this->toDbValue($value);
|
||||
|
||||
$result = $this->dba->update('config', ['v' => $dbvalue], ['cat' => $cat, 'k' => $key], true);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the configured value from the database.
|
||||
*
|
||||
* @param string $cat The category of the configuration value
|
||||
* @param string $key The configuration key to delete
|
||||
*
|
||||
* @return bool Operation success
|
||||
*
|
||||
* @throws \Exception In case DB calls are invalid
|
||||
*/
|
||||
public function delete(string $cat, string $key)
|
||||
{
|
||||
if (!$this->isConnected()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->dba->delete('config', ['cat' => $cat, 'k' => $key]);
|
||||
}
|
||||
}
|
86
src/Model/Config/DbaConfig.php
Normal file
86
src/Model/Config/DbaConfig.php
Normal file
|
@ -0,0 +1,86 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Model\Config;
|
||||
|
||||
use Friendica\Database\Database;
|
||||
|
||||
/**
|
||||
* The DB-based model of (P-)Config values
|
||||
* Encapsulates db-calls in case of config queries
|
||||
*/
|
||||
abstract class DbaConfig
|
||||
{
|
||||
/** @var Database */
|
||||
protected $dba;
|
||||
|
||||
/**
|
||||
* @param Database $dba The database connection of this model
|
||||
*/
|
||||
public function __construct(Database $dba)
|
||||
{
|
||||
$this->dba = $dba;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the model is currently connected
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isConnected()
|
||||
{
|
||||
return $this->dba->isConnected();
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a DB value to a config value
|
||||
* - null = The db-value isn't set
|
||||
* - bool = The db-value is either '0' or '1'
|
||||
* - array = The db-value is a serialized array
|
||||
* - string = The db-value is a string
|
||||
*
|
||||
* Keep in mind that there aren't any numeric/integer config values in the database
|
||||
*
|
||||
* @param null|string $value
|
||||
*
|
||||
* @return null|array|string
|
||||
*/
|
||||
protected function toConfigValue($value)
|
||||
{
|
||||
if (!isset($value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch (true) {
|
||||
// manage array value
|
||||
case preg_match("|^a:[0-9]+:{.*}$|s", $value):
|
||||
return unserialize($value);
|
||||
|
||||
default:
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a config value to a DB value (string)
|
||||
*
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function toDbValue($value)
|
||||
{
|
||||
// if not set, save an empty string
|
||||
if (!isset($value)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
switch (true) {
|
||||
// manage arrays
|
||||
case is_array($value):
|
||||
return serialize($value);
|
||||
|
||||
default:
|
||||
return (string)$value;
|
||||
}
|
||||
}
|
||||
}
|
135
src/Model/Config/PConfig.php
Normal file
135
src/Model/Config/PConfig.php
Normal file
|
@ -0,0 +1,135 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Model\Config;
|
||||
|
||||
|
||||
/**
|
||||
* The Config model backend for users, which is using the general DB-model backend for user-configs
|
||||
*/
|
||||
class PConfig extends DbaConfig
|
||||
{
|
||||
/**
|
||||
* Loads all configuration values and returns the loaded category as an array.
|
||||
*
|
||||
* @param int $uid The id of the user to load
|
||||
* @param string|null $cat The category of the configuration values to load
|
||||
*
|
||||
* @return array The config array
|
||||
*
|
||||
* @throws \Exception In case DB calls are invalid
|
||||
*/
|
||||
public function load(int $uid, string $cat = null)
|
||||
{
|
||||
$return = [];
|
||||
|
||||
if (empty($cat)) {
|
||||
$configs = $this->dba->select('pconfig', ['cat', 'v', 'k'], ['uid' => $uid]);
|
||||
} else {
|
||||
$configs = $this->dba->select('pconfig', ['cat', 'v', 'k'], ['cat' => $cat, 'uid' => $uid]);
|
||||
}
|
||||
|
||||
while ($config = $this->dba->fetch($configs)) {
|
||||
$key = $config['k'];
|
||||
$value = $this->toConfigValue($config['v']);
|
||||
|
||||
// just save it in case it is set
|
||||
if (isset($value)) {
|
||||
$return[$config['cat']][$key] = $value;
|
||||
}
|
||||
}
|
||||
$this->dba->close($configs);
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a particular user config variable out of the DB with the
|
||||
* given category name ($cat) and a key ($key).
|
||||
*
|
||||
* Note: Boolean variables are defined as 0/1 in the database
|
||||
*
|
||||
* @param int $uid The id of the user to load
|
||||
* @param string $cat The category of the configuration value
|
||||
* @param string $key The configuration key to query
|
||||
*
|
||||
* @return array|string|null Stored value or null if it does not exist
|
||||
*
|
||||
* @throws \Exception In case DB calls are invalid
|
||||
*/
|
||||
public function get(int $uid, string $cat, string $key)
|
||||
{
|
||||
if (!$this->isConnected()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$config = $this->dba->selectFirst('pconfig', ['v'], ['uid' => $uid, 'cat' => $cat, 'k' => $key]);
|
||||
if ($this->dba->isResult($config)) {
|
||||
$value = $this->toConfigValue($config['v']);
|
||||
|
||||
// just return it in case it is set
|
||||
if (isset($value)) {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores a config value ($value) in the category ($cat) under the key ($key) for a
|
||||
* given user ($uid).
|
||||
*
|
||||
* Note: Please do not store booleans - convert to 0/1 integer values!
|
||||
*
|
||||
* @param int $uid The id of the user to load
|
||||
* @param string $cat The category of the configuration value
|
||||
* @param string $key The configuration key to set
|
||||
* @param mixed $value The value to store
|
||||
*
|
||||
* @return bool Operation success
|
||||
*
|
||||
* @throws \Exception In case DB calls are invalid
|
||||
*/
|
||||
public function set(int $uid, string $cat, string $key, $value)
|
||||
{
|
||||
if (!$this->isConnected()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// We store our setting values in a string variable.
|
||||
// So we have to do the conversion here so that the compare below works.
|
||||
// The exception are array values.
|
||||
$compare_value = (!is_array($value) ? (string)$value : $value);
|
||||
$stored_value = $this->get($uid, $cat, $key);
|
||||
|
||||
if (isset($stored_value) && ($stored_value === $compare_value)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$dbvalue = $this->toDbValue($value);
|
||||
|
||||
$result = $this->dba->update('pconfig', ['v' => $dbvalue], ['uid' => $uid, 'cat' => $cat, 'k' => $key], true);
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the configured value of the given user.
|
||||
*
|
||||
* @param int $uid The id of the user to load
|
||||
* @param string $cat The category of the configuration value
|
||||
* @param string $key The configuration key to delete
|
||||
*
|
||||
* @return bool Operation success
|
||||
*
|
||||
* @throws \Exception In case DB calls are invalid
|
||||
*/
|
||||
public function delete(int $uid, string $cat, string $key)
|
||||
{
|
||||
if (!$this->isConnected()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->dba->delete('pconfig', ['uid' => $uid, 'cat' => $cat, 'k' => $key]);
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -5,12 +5,11 @@
|
|||
|
||||
namespace Friendica\Model;
|
||||
|
||||
use Friendica\Core\Logger;
|
||||
use Friendica\Core\Protocol;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
|
||||
require_once "include/dba.php";
|
||||
|
||||
class Conversation
|
||||
{
|
||||
/*
|
||||
|
@ -36,6 +35,7 @@ class Conversation
|
|||
*
|
||||
* @param array $arr Item array with conversation data
|
||||
* @return array Item array with removed conversation data
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function insert(array $arr)
|
||||
{
|
||||
|
@ -77,18 +77,18 @@ class Conversation
|
|||
}
|
||||
// Update structure data all the time but the source only when its from a better protocol.
|
||||
if (empty($conversation['source']) || (!empty($old_conv['source']) &&
|
||||
($old_conv['protocol'] < defaults($conversation, 'protocol', PARCEL_UNKNOWN)))) {
|
||||
($old_conv['protocol'] < defaults($conversation, 'protocol', self::PARCEL_UNKNOWN)))) {
|
||||
unset($conversation['protocol']);
|
||||
unset($conversation['source']);
|
||||
}
|
||||
if (!DBA::update('conversation', $conversation, ['item-uri' => $conversation['item-uri']], $old_conv)) {
|
||||
logger('Conversation: update for ' . $conversation['item-uri'] . ' from ' . $old_conv['protocol'] . ' to ' . $conversation['protocol'] . ' failed',
|
||||
LOGGER_DEBUG);
|
||||
Logger::log('Conversation: update for ' . $conversation['item-uri'] . ' from ' . $old_conv['protocol'] . ' to ' . $conversation['protocol'] . ' failed',
|
||||
Logger::DEBUG);
|
||||
}
|
||||
} else {
|
||||
if (!DBA::insert('conversation', $conversation, true)) {
|
||||
logger('Conversation: insert for ' . $conversation['item-uri'] . ' (protocol ' . $conversation['protocol'] . ') failed',
|
||||
LOGGER_DEBUG);
|
||||
Logger::log('Conversation: insert for ' . $conversation['item-uri'] . ' (protocol ' . $conversation['protocol'] . ') failed',
|
||||
Logger::DEBUG);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,18 +7,17 @@ namespace Friendica\Model;
|
|||
|
||||
use Friendica\BaseObject;
|
||||
use Friendica\Content\Text\BBCode;
|
||||
use Friendica\Core\Addon;
|
||||
use Friendica\Core\Hook;
|
||||
use Friendica\Core\L10n;
|
||||
use Friendica\Core\Logger;
|
||||
use Friendica\Core\PConfig;
|
||||
use Friendica\Core\Renderer;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
use Friendica\Util\Map;
|
||||
|
||||
require_once 'boot.php';
|
||||
require_once 'include/dba.php';
|
||||
require_once 'include/items.php';
|
||||
use Friendica\Util\Strings;
|
||||
use Friendica\Util\XML;
|
||||
|
||||
/**
|
||||
* @brief functions for interacting with the event database table
|
||||
|
@ -34,13 +33,13 @@ class Event extends BaseObject
|
|||
|
||||
$bd_format = L10n::t('l F d, Y \@ g:i A'); // Friday January 18, 2011 @ 8 AM.
|
||||
|
||||
$event_start = day_translate(
|
||||
$event_start = L10n::getDay(
|
||||
!empty($event['adjust']) ?
|
||||
DateTimeFormat::local($event['start'], $bd_format) : DateTimeFormat::utc($event['start'], $bd_format)
|
||||
);
|
||||
|
||||
if (!empty($event['finish'])) {
|
||||
$event_end = day_translate(
|
||||
$event_end = L10n::getDay(
|
||||
!empty($event['adjust']) ?
|
||||
DateTimeFormat::local($event['finish'], $bd_format) : DateTimeFormat::utc($event['finish'], $bd_format)
|
||||
);
|
||||
|
@ -49,12 +48,14 @@ class Event extends BaseObject
|
|||
}
|
||||
|
||||
if ($simple) {
|
||||
$o = '';
|
||||
|
||||
if (!empty($event['summary'])) {
|
||||
$o = "<h3>" . BBCode::convert($event['summary'], false, $simple) . "</h3>";
|
||||
$o .= "<h3>" . BBCode::convert(Strings::escapeHtml($event['summary']), false, $simple) . "</h3>";
|
||||
}
|
||||
|
||||
if (!empty($event['desc'])) {
|
||||
$o .= "<div>" . BBCode::convert($event['desc'], false, $simple) . "</div>";
|
||||
$o .= "<div>" . BBCode::convert(Strings::escapeHtml($event['desc']), false, $simple) . "</div>";
|
||||
}
|
||||
|
||||
$o .= "<h4>" . L10n::t('Starts:') . "</h4><p>" . $event_start . "</p>";
|
||||
|
@ -64,7 +65,7 @@ class Event extends BaseObject
|
|||
}
|
||||
|
||||
if (!empty($event['location'])) {
|
||||
$o .= "<h4>" . L10n::t('Location:') . "</h4><p>" . BBCode::convert($event['location'], false, $simple) . "</p>";
|
||||
$o .= "<h4>" . L10n::t('Location:') . "</h4><p>" . BBCode::convert(Strings::escapeHtml($event['location']), false, $simple) . "</p>";
|
||||
}
|
||||
|
||||
return $o;
|
||||
|
@ -72,7 +73,7 @@ class Event extends BaseObject
|
|||
|
||||
$o = '<div class="vevent">' . "\r\n";
|
||||
|
||||
$o .= '<div class="summary event-summary">' . BBCode::convert($event['summary'], false, $simple) . '</div>' . "\r\n";
|
||||
$o .= '<div class="summary event-summary">' . BBCode::convert(Strings::escapeHtml($event['summary']), false, $simple) . '</div>' . "\r\n";
|
||||
|
||||
$o .= '<div class="event-start"><span class="event-label">' . L10n::t('Starts:') . '</span> <span class="dtstart" title="'
|
||||
. DateTimeFormat::utc($event['start'], (!empty($event['adjust']) ? DateTimeFormat::ATOM : 'Y-m-d\TH:i:s'))
|
||||
|
@ -87,12 +88,12 @@ class Event extends BaseObject
|
|||
}
|
||||
|
||||
if (!empty($event['desc'])) {
|
||||
$o .= '<div class="description event-description">' . BBCode::convert($event['desc'], false, $simple) . '</div>' . "\r\n";
|
||||
$o .= '<div class="description event-description">' . BBCode::convert(Strings::escapeHtml($event['desc']), false, $simple) . '</div>' . "\r\n";
|
||||
}
|
||||
|
||||
if (!empty($event['location'])) {
|
||||
$o .= '<div class="event-location"><span class="event-label">' . L10n::t('Location:') . '</span> <span class="location">'
|
||||
. BBCode::convert($event['location'], false, $simple)
|
||||
. BBCode::convert(Strings::escapeHtml($event['location']), false, $simple)
|
||||
. '</span></div>' . "\r\n";
|
||||
|
||||
// Include a map of the location if the [map] BBCode is used.
|
||||
|
@ -149,6 +150,7 @@ class Event extends BaseObject
|
|||
* @brief Extract bbcode formatted event data from a string.
|
||||
*
|
||||
* @params: string $s The string which should be parsed for event data.
|
||||
* @param $text
|
||||
* @return array The array with the event information.
|
||||
*/
|
||||
public static function fromBBCode($text)
|
||||
|
@ -216,6 +218,7 @@ class Event extends BaseObject
|
|||
*
|
||||
* @param int $event_id Event ID.
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function delete($event_id)
|
||||
{
|
||||
|
@ -223,8 +226,8 @@ class Event extends BaseObject
|
|||
return;
|
||||
}
|
||||
|
||||
DBA::delete('event', ['id' => $event_id]);
|
||||
logger("Deleted event ".$event_id, LOGGER_DEBUG);
|
||||
DBA::delete('event', ['id' => $event_id], ['cascade' => false]);
|
||||
Logger::log("Deleted event ".$event_id, Logger::DEBUG);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -234,16 +237,16 @@ class Event extends BaseObject
|
|||
*
|
||||
* @param array $arr Array with event data.
|
||||
* @return int The new event id.
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public static function store($arr)
|
||||
{
|
||||
$a = self::getApp();
|
||||
|
||||
$event = [];
|
||||
$event['id'] = intval(defaults($arr, 'id' , 0));
|
||||
$event['uid'] = intval(defaults($arr, 'uid' , 0));
|
||||
$event['cid'] = intval(defaults($arr, 'cid' , 0));
|
||||
$event['uri'] = defaults($arr, 'uri' , Item::newURI($event['uid']));
|
||||
$event['guid'] = defaults($arr, 'guid' , System::createUUID());
|
||||
$event['uri'] = defaults($arr, 'uri' , Item::newURI($event['uid'], $event['guid']));
|
||||
$event['type'] = defaults($arr, 'type' , 'event');
|
||||
$event['summary'] = defaults($arr, 'summary' , '');
|
||||
$event['desc'] = defaults($arr, 'desc' , '');
|
||||
|
@ -300,8 +303,8 @@ class Event extends BaseObject
|
|||
|
||||
$item = Item::selectFirst(['id'], ['event-id' => $event['id'], 'uid' => $event['uid']]);
|
||||
if (DBA::isResult($item)) {
|
||||
$object = '<object><type>' . xmlify(ACTIVITY_OBJ_EVENT) . '</type><title></title><id>' . xmlify($event['uri']) . '</id>';
|
||||
$object .= '<content>' . xmlify(self::getBBCode($event)) . '</content>';
|
||||
$object = '<object><type>' . XML::escape(ACTIVITY_OBJ_EVENT) . '</type><title></title><id>' . XML::escape($event['uri']) . '</id>';
|
||||
$object .= '<content>' . XML::escape(self::getBBCode($event)) . '</content>';
|
||||
$object .= '</object>' . "\n";
|
||||
|
||||
$fields = ['body' => self::getBBCode($event), 'object' => $object, 'edited' => $event['edited']];
|
||||
|
@ -312,52 +315,55 @@ class Event extends BaseObject
|
|||
$item_id = 0;
|
||||
}
|
||||
|
||||
Addon::callHooks('event_updated', $event['id']);
|
||||
Hook::callAll('event_updated', $event['id']);
|
||||
} else {
|
||||
$event['guid'] = defaults($arr, 'guid', System::createUUID());
|
||||
|
||||
// New event. Store it.
|
||||
DBA::insert('event', $event);
|
||||
|
||||
$event['id'] = DBA::lastInsertId();
|
||||
$item_id = 0;
|
||||
|
||||
$item_arr = [];
|
||||
// Don't create an item for birthday events
|
||||
if ($event['type'] == 'event') {
|
||||
$event['id'] = DBA::lastInsertId();
|
||||
|
||||
$item_arr['uid'] = $event['uid'];
|
||||
$item_arr['contact-id'] = $event['cid'];
|
||||
$item_arr['uri'] = $event['uri'];
|
||||
$item_arr['parent-uri'] = $event['uri'];
|
||||
$item_arr['guid'] = $event['guid'];
|
||||
$item_arr['plink'] = defaults($arr, 'plink', '');
|
||||
$item_arr['post-type'] = Item::PT_EVENT;
|
||||
$item_arr['wall'] = $event['cid'] ? 0 : 1;
|
||||
$item_arr['contact-id'] = $contact['id'];
|
||||
$item_arr['owner-name'] = $contact['name'];
|
||||
$item_arr['owner-link'] = $contact['url'];
|
||||
$item_arr['owner-avatar'] = $contact['thumb'];
|
||||
$item_arr['author-name'] = $contact['name'];
|
||||
$item_arr['author-link'] = $contact['url'];
|
||||
$item_arr['author-avatar'] = $contact['thumb'];
|
||||
$item_arr['title'] = '';
|
||||
$item_arr['allow_cid'] = $event['allow_cid'];
|
||||
$item_arr['allow_gid'] = $event['allow_gid'];
|
||||
$item_arr['deny_cid'] = $event['deny_cid'];
|
||||
$item_arr['deny_gid'] = $event['deny_gid'];
|
||||
$item_arr['private'] = $private;
|
||||
$item_arr['visible'] = 1;
|
||||
$item_arr['verb'] = ACTIVITY_POST;
|
||||
$item_arr['object-type'] = ACTIVITY_OBJ_EVENT;
|
||||
$item_arr['origin'] = $event['cid'] === 0 ? 1 : 0;
|
||||
$item_arr['body'] = self::getBBCode($event);
|
||||
$item_arr['event-id'] = $event['id'];
|
||||
$item_arr = [];
|
||||
|
||||
$item_arr['object'] = '<object><type>' . xmlify(ACTIVITY_OBJ_EVENT) . '</type><title></title><id>' . xmlify($event['uri']) . '</id>';
|
||||
$item_arr['object'] .= '<content>' . xmlify(self::getBBCode($event)) . '</content>';
|
||||
$item_arr['object'] .= '</object>' . "\n";
|
||||
$item_arr['uid'] = $event['uid'];
|
||||
$item_arr['contact-id'] = $event['cid'];
|
||||
$item_arr['uri'] = $event['uri'];
|
||||
$item_arr['parent-uri'] = $event['uri'];
|
||||
$item_arr['guid'] = $event['guid'];
|
||||
$item_arr['plink'] = defaults($arr, 'plink', '');
|
||||
$item_arr['post-type'] = Item::PT_EVENT;
|
||||
$item_arr['wall'] = $event['cid'] ? 0 : 1;
|
||||
$item_arr['contact-id'] = $contact['id'];
|
||||
$item_arr['owner-name'] = $contact['name'];
|
||||
$item_arr['owner-link'] = $contact['url'];
|
||||
$item_arr['owner-avatar'] = $contact['thumb'];
|
||||
$item_arr['author-name'] = $contact['name'];
|
||||
$item_arr['author-link'] = $contact['url'];
|
||||
$item_arr['author-avatar'] = $contact['thumb'];
|
||||
$item_arr['title'] = '';
|
||||
$item_arr['allow_cid'] = $event['allow_cid'];
|
||||
$item_arr['allow_gid'] = $event['allow_gid'];
|
||||
$item_arr['deny_cid'] = $event['deny_cid'];
|
||||
$item_arr['deny_gid'] = $event['deny_gid'];
|
||||
$item_arr['private'] = $private;
|
||||
$item_arr['visible'] = 1;
|
||||
$item_arr['verb'] = ACTIVITY_POST;
|
||||
$item_arr['object-type'] = ACTIVITY_OBJ_EVENT;
|
||||
$item_arr['origin'] = $event['cid'] === 0 ? 1 : 0;
|
||||
$item_arr['body'] = self::getBBCode($event);
|
||||
$item_arr['event-id'] = $event['id'];
|
||||
|
||||
$item_id = Item::insert($item_arr);
|
||||
$item_arr['object'] = '<object><type>' . XML::escape(ACTIVITY_OBJ_EVENT) . '</type><title></title><id>' . XML::escape($event['uri']) . '</id>';
|
||||
$item_arr['object'] .= '<content>' . XML::escape(self::getBBCode($event)) . '</content>';
|
||||
$item_arr['object'] .= '</object>' . "\n";
|
||||
|
||||
Addon::callHooks("event_created", $event['id']);
|
||||
$item_id = Item::insert($item_arr);
|
||||
}
|
||||
|
||||
Hook::callAll("event_created", $event['id']);
|
||||
}
|
||||
|
||||
return $item_id;
|
||||
|
@ -367,6 +373,7 @@ class Event extends BaseObject
|
|||
* @brief Create an array with translation strings used for events.
|
||||
*
|
||||
* @return array Array with translations strings.
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public static function getStrings()
|
||||
{
|
||||
|
@ -410,7 +417,6 @@ class Event extends BaseObject
|
|||
"February" => L10n::t("February"),
|
||||
"March" => L10n::t("March"),
|
||||
"April" => L10n::t("April"),
|
||||
"May" => L10n::t("May"),
|
||||
"June" => L10n::t("June"),
|
||||
"July" => L10n::t("July"),
|
||||
"August" => L10n::t("August"),
|
||||
|
@ -463,6 +469,7 @@ class Event extends BaseObject
|
|||
* @param int $event_id The ID of the event in the event table
|
||||
* @param string $sql_extra
|
||||
* @return array Query result
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function getListById($owner_uid, $event_id, $sql_extra = '')
|
||||
{
|
||||
|
@ -491,17 +498,18 @@ class Event extends BaseObject
|
|||
/**
|
||||
* @brief Get all events in a specific time frame.
|
||||
*
|
||||
* @param int $owner_uid The User ID of the owner of the events.
|
||||
* @param array $event_params An associative array with
|
||||
* int 'ignore' =>
|
||||
* string 'start' => Start time of the timeframe.
|
||||
* string 'finish' => Finish time of the timeframe.
|
||||
* string 'adjust_start' =>
|
||||
* string 'adjust_finish' =>
|
||||
* @param int $owner_uid The User ID of the owner of the events.
|
||||
* @param array $event_params An associative array with
|
||||
* int 'ignore' =>
|
||||
* string 'start' => Start time of the timeframe.
|
||||
* string 'finish' => Finish time of the timeframe.
|
||||
* string 'adjust_start' =>
|
||||
* string 'adjust_finish' =>
|
||||
*
|
||||
* @param string $sql_extra Additional sql conditions (e.g. permission request).
|
||||
* @param string $sql_extra Additional sql conditions (e.g. permission request).
|
||||
*
|
||||
* @return array Query results.
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function getListByDate($owner_uid, $event_params, $sql_extra = '')
|
||||
{
|
||||
|
@ -542,6 +550,8 @@ class Event extends BaseObject
|
|||
*
|
||||
* @param array $event_result Event query array.
|
||||
* @return array Event array for the template.
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
public static function prepareListForTemplate(array $event_result)
|
||||
{
|
||||
|
@ -561,7 +571,7 @@ class Event extends BaseObject
|
|||
$start = $event['adjust'] ? DateTimeFormat::local($event['start'], 'c') : DateTimeFormat::utc($event['start'], 'c');
|
||||
$j = $event['adjust'] ? DateTimeFormat::local($event['start'], 'j') : DateTimeFormat::utc($event['start'], 'j');
|
||||
$day = $event['adjust'] ? DateTimeFormat::local($event['start'], $fmt) : DateTimeFormat::utc($event['start'], $fmt);
|
||||
$day = day_translate($day);
|
||||
$day = L10n::getDay($day);
|
||||
|
||||
if ($event['nofinish']) {
|
||||
$end = null;
|
||||
|
@ -584,10 +594,9 @@ class Event extends BaseObject
|
|||
$drop = [System::baseUrl() . '/events/drop/' . $event['id'] , L10n::t('Delete event') , '', ''];
|
||||
}
|
||||
|
||||
$title = strip_tags(html_entity_decode(BBCode::convert($event['summary']), ENT_QUOTES, 'UTF-8'));
|
||||
$title = BBCode::convert(Strings::escapeHtml($event['summary']));
|
||||
if (!$title) {
|
||||
list($title, $_trash) = explode("<br", BBCode::convert($event['desc']), 2);
|
||||
$title = strip_tags(html_entity_decode($title, ENT_QUOTES, 'UTF-8'));
|
||||
list($title, $_trash) = explode("<br", BBCode::convert(Strings::escapeHtml($event['desc'])), 2);
|
||||
}
|
||||
|
||||
$author_link = $event['author-link'];
|
||||
|
@ -597,8 +606,9 @@ class Event extends BaseObject
|
|||
$event['plink'] = Contact::magicLink($author_link, $plink);
|
||||
|
||||
$html = self::getHTML($event);
|
||||
$event['desc'] = BBCode::convert($event['desc']);
|
||||
$event['location'] = BBCode::convert($event['location']);
|
||||
$event['summary'] = BBCode::convert(Strings::escapeHtml($event['summary']));
|
||||
$event['desc'] = BBCode::convert(Strings::escapeHtml($event['desc']));
|
||||
$event['location'] = BBCode::convert(Strings::escapeHtml($event['location']));
|
||||
$event_list[] = [
|
||||
'id' => $event['id'],
|
||||
'start' => $start,
|
||||
|
@ -623,25 +633,27 @@ class Event extends BaseObject
|
|||
/**
|
||||
* @brief Format event to export format (ical/csv).
|
||||
*
|
||||
* @param array $events Query result for events.
|
||||
* @param string $format The output format (ical/csv).
|
||||
* @param string $timezone The timezone of the user (not implemented yet).
|
||||
* @param array $events Query result for events.
|
||||
* @param string $format The output format (ical/csv).
|
||||
*
|
||||
* @param $timezone
|
||||
* @return string Content according to selected export format.
|
||||
*
|
||||
* @todo Implement timezone support
|
||||
* @todo Implement timezone support
|
||||
*/
|
||||
private static function formatListForExport(array $events, $format, $timezone)
|
||||
private static function formatListForExport(array $events, $format)
|
||||
{
|
||||
$o = '';
|
||||
|
||||
if (!count($events)) {
|
||||
return '';
|
||||
return $o;
|
||||
}
|
||||
|
||||
switch ($format) {
|
||||
// Format the exported data as a CSV file.
|
||||
case "csv":
|
||||
header("Content-type: text/csv");
|
||||
$o = '"Subject", "Start Date", "Start Time", "Description", "End Date", "End Time", "Location"' . PHP_EOL;
|
||||
$o .= '"Subject", "Start Date", "Start Time", "Description", "End Date", "End Time", "Location"' . PHP_EOL;
|
||||
|
||||
foreach ($events as $event) {
|
||||
/// @todo The time / date entries don't include any information about the
|
||||
|
@ -737,6 +749,7 @@ class Event extends BaseObject
|
|||
* @param int $uid The user ID.
|
||||
*
|
||||
* @return array Query results.
|
||||
* @throws \Exception
|
||||
*/
|
||||
private static function getListByUserId($uid = 0)
|
||||
{
|
||||
|
@ -767,33 +780,29 @@ class Event extends BaseObject
|
|||
|
||||
/**
|
||||
*
|
||||
* @param int $uid The user ID.
|
||||
* @param int $uid The user ID.
|
||||
* @param string $format Output format (ical/csv).
|
||||
* @return array With the results:
|
||||
* bool 'success' => True if the processing was successful,<br>
|
||||
* string 'format' => The output format,<br>
|
||||
* string 'extension' => The file extension of the output format,<br>
|
||||
* string 'content' => The formatted output content.<br>
|
||||
* bool 'success' => True if the processing was successful,<br>
|
||||
* string 'format' => The output format,<br>
|
||||
* string 'extension' => The file extension of the output format,<br>
|
||||
* string 'content' => The formatted output content.<br>
|
||||
*
|
||||
* @throws \Exception
|
||||
* @todo Respect authenticated users with events_by_uid().
|
||||
*/
|
||||
public static function exportListByUserId($uid, $format = 'ical')
|
||||
{
|
||||
$process = false;
|
||||
|
||||
$user = DBA::selectFirst('user', ['timezone'], ['uid' => $uid]);
|
||||
if (DBA::isResult($user)) {
|
||||
$timezone = $user['timezone'];
|
||||
}
|
||||
|
||||
// Get all events which are owned by a uid (respects permissions).
|
||||
$events = self::getListByUserId($uid);
|
||||
|
||||
// We have the events that are available for the requestor.
|
||||
// Now format the output according to the requested format.
|
||||
$res = self::formatListForExport($events, $format, $timezone);
|
||||
$res = self::formatListForExport($events, $format);
|
||||
|
||||
// If there are results the precess was successfull.
|
||||
// If there are results the precess was successful.
|
||||
if (!empty($res)) {
|
||||
$process = true;
|
||||
}
|
||||
|
@ -827,6 +836,8 @@ class Event extends BaseObject
|
|||
*
|
||||
* @param array $item Array with item and event data.
|
||||
* @return string HTML output.
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
public static function getItemHTML(array $item) {
|
||||
$same_date = false;
|
||||
|
@ -838,14 +849,14 @@ class Event extends BaseObject
|
|||
$tformat = L10n::t('g:i A'); // 8:01 AM.
|
||||
|
||||
// Convert the time to different formats.
|
||||
$dtstart_dt = day_translate(
|
||||
$dtstart_dt = L10n::getDay(
|
||||
$item['event-adjust'] ?
|
||||
DateTimeFormat::local($item['event-start'], $dformat)
|
||||
: DateTimeFormat::utc($item['event-start'], $dformat)
|
||||
);
|
||||
$dtstart_title = DateTimeFormat::utc($item['event-start'], $item['event-adjust'] ? DateTimeFormat::ATOM : 'Y-m-d\TH:i:s');
|
||||
// Format: Jan till Dec.
|
||||
$month_short = day_short_translate(
|
||||
$month_short = L10n::getDayShort(
|
||||
$item['event-adjust'] ?
|
||||
DateTimeFormat::local($item['event-start'], 'M')
|
||||
: DateTimeFormat::utc($item['event-start'], 'M')
|
||||
|
@ -857,7 +868,7 @@ class Event extends BaseObject
|
|||
$start_time = $item['event-adjust'] ?
|
||||
DateTimeFormat::local($item['event-start'], $tformat)
|
||||
: DateTimeFormat::utc($item['event-start'], $tformat);
|
||||
$start_short = day_short_translate(
|
||||
$start_short = L10n::getDayShort(
|
||||
$item['event-adjust'] ?
|
||||
DateTimeFormat::local($item['event-start'], $dformat_short)
|
||||
: DateTimeFormat::utc($item['event-start'], $dformat_short)
|
||||
|
@ -866,13 +877,13 @@ class Event extends BaseObject
|
|||
// If the option 'nofinisch' isn't set, we need to format the finish date/time.
|
||||
if (!$item['event-nofinish']) {
|
||||
$finish = true;
|
||||
$dtend_dt = day_translate(
|
||||
$dtend_dt = L10n::getDay(
|
||||
$item['event-adjust'] ?
|
||||
DateTimeFormat::local($item['event-finish'], $dformat)
|
||||
: DateTimeFormat::utc($item['event-finish'], $dformat)
|
||||
);
|
||||
$dtend_title = DateTimeFormat::utc($item['event-finish'], $item['event-adjust'] ? DateTimeFormat::ATOM : 'Y-m-d\TH:i:s');
|
||||
$end_short = day_short_translate(
|
||||
$end_short = L10n::getDayShort(
|
||||
$item['event-adjust'] ?
|
||||
DateTimeFormat::local($item['event-finish'], $dformat_short)
|
||||
: DateTimeFormat::utc($item['event-finish'], $dformat_short)
|
||||
|
@ -897,8 +908,8 @@ class Event extends BaseObject
|
|||
// Construct the profile link (magic-auth).
|
||||
$profile_link = Contact::magicLinkById($item['author-id']);
|
||||
|
||||
$tpl = get_markup_template('event_stream_item.tpl');
|
||||
$return = replace_macros($tpl, [
|
||||
$tpl = Renderer::getMarkupTemplate('event_stream_item.tpl');
|
||||
$return = Renderer::replaceMacros($tpl, [
|
||||
'$id' => $item['event-id'],
|
||||
'$title' => prepare_text($item['event-summary']),
|
||||
'$dtstart_label' => L10n::t('Starts:'),
|
||||
|
@ -941,6 +952,7 @@ class Event extends BaseObject
|
|||
* 'name' => The name of the location,<br>
|
||||
* 'address' => The address of the location,<br>
|
||||
* 'coordinates' => Latitude and longitude (e.g. '48.864716,2.349014').<br>
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
private static function locationToArray($s = '') {
|
||||
if ($s == '') {
|
||||
|
@ -978,4 +990,48 @@ class Event extends BaseObject
|
|||
|
||||
return $location;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Add new birthday event for this person
|
||||
*
|
||||
* @param array $contact Contact array, expects: id, uid, url, name
|
||||
* @param string $birthday Birthday of the contact
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function createBirthday($contact, $birthday)
|
||||
{
|
||||
// Check for duplicates
|
||||
$condition = [
|
||||
'uid' => $contact['uid'],
|
||||
'cid' => $contact['id'],
|
||||
'start' => DateTimeFormat::utc($birthday),
|
||||
'type' => 'birthday'
|
||||
];
|
||||
if (DBA::exists('event', $condition)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Add new birthday event for this person
|
||||
*
|
||||
* summary is just a readable placeholder in case the event is shared
|
||||
* with others. We will replace it during presentation to our $importer
|
||||
* to contain a sparkle link and perhaps a photo.
|
||||
*/
|
||||
$values = [
|
||||
'uid' => $contact['uid'],
|
||||
'cid' => $contact['id'],
|
||||
'start' => DateTimeFormat::utc($birthday),
|
||||
'finish' => DateTimeFormat::utc($birthday . ' + 1 day '),
|
||||
'summary' => L10n::t('%s\'s birthday', $contact['name']),
|
||||
'desc' => L10n::t('Happy Birthday %s', ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]'),
|
||||
'type' => 'birthday',
|
||||
'adjust' => 0
|
||||
];
|
||||
|
||||
self::store($values);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
320
src/Model/FileTag.php
Normal file
320
src/Model/FileTag.php
Normal file
|
@ -0,0 +1,320 @@
|
|||
<?php
|
||||
/**
|
||||
* @file src/Model/FileTag.php
|
||||
*/
|
||||
|
||||
namespace Friendica\Model;
|
||||
|
||||
use Friendica\Core\L10n;
|
||||
use Friendica\Core\PConfig;
|
||||
use Friendica\Database\DBA;
|
||||
|
||||
/**
|
||||
* @brief This class handles FileTag related functions
|
||||
*
|
||||
* post categories and "save to file" use the same item.file table for storage.
|
||||
* We will differentiate the different uses by wrapping categories in angle brackets
|
||||
* and save to file categories in square brackets.
|
||||
* To do this we need to escape these characters if they appear in our tag.
|
||||
*/
|
||||
class FileTag
|
||||
{
|
||||
/**
|
||||
* @brief URL encode <, >, left and right brackets
|
||||
*
|
||||
* @param string $s String to be URL encoded.
|
||||
*
|
||||
* @return string The URL encoded string.
|
||||
*/
|
||||
public static function encode($s)
|
||||
{
|
||||
return str_replace(['<', '>', '[', ']'], ['%3c', '%3e', '%5b', '%5d'], $s);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief URL decode <, >, left and right brackets
|
||||
*
|
||||
* @param string $s The URL encoded string to be decoded
|
||||
*
|
||||
* @return string The decoded string.
|
||||
*/
|
||||
public static function decode($s)
|
||||
{
|
||||
return str_replace(['%3c', '%3e', '%5b', '%5d'], ['<', '>', '[', ']'], $s);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Query files for tag
|
||||
*
|
||||
* @param string $table The table to be queired.
|
||||
* @param string $s The search term
|
||||
* @param string $type Optional file type.
|
||||
*
|
||||
* @return string Query string.
|
||||
*/
|
||||
public static function fileQuery($table, $s, $type = 'file')
|
||||
{
|
||||
if ($type == 'file') {
|
||||
$str = preg_quote('[' . str_replace('%', '%%', self::encode($s)) . ']');
|
||||
} else {
|
||||
$str = preg_quote('<' . str_replace('%', '%%', self::encode($s)) . '>');
|
||||
}
|
||||
|
||||
return " AND " . (($table) ? DBA::escape($table) . '.' : '') . "file regexp '" . DBA::escape($str) . "' ";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get file tags from array
|
||||
*
|
||||
* ex. given [music,video] return <music><video> or [music][video]
|
||||
*
|
||||
* @param array $array A list of tags.
|
||||
* @param string $type Optional file type.
|
||||
*
|
||||
* @return string A list of file tags.
|
||||
*/
|
||||
public static function arrayToFile(array $array, string $type = 'file')
|
||||
{
|
||||
$tag_list = '';
|
||||
if ($type == 'file') {
|
||||
$lbracket = '[';
|
||||
$rbracket = ']';
|
||||
} else {
|
||||
$lbracket = '<';
|
||||
$rbracket = '>';
|
||||
}
|
||||
|
||||
foreach ($array as $item) {
|
||||
if (strlen($item)) {
|
||||
$tag_list .= $lbracket . self::encode(trim($item)) . $rbracket;
|
||||
}
|
||||
}
|
||||
|
||||
return $tag_list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tag list from file tags
|
||||
*
|
||||
* ex. given <music><video>[friends], return [music,video] or [friends]
|
||||
*
|
||||
* @param string $file File tags
|
||||
* @param string $type Optional file type.
|
||||
*
|
||||
* @return array List of tag names.
|
||||
*/
|
||||
public static function fileToArray(string $file, string $type = 'file')
|
||||
{
|
||||
$matches = [];
|
||||
$return = [];
|
||||
|
||||
if ($type == 'file') {
|
||||
$cnt = preg_match_all('/\[(.*?)\]/', $file, $matches, PREG_SET_ORDER);
|
||||
} else {
|
||||
$cnt = preg_match_all('/<(.*?)>/', $file, $matches, PREG_SET_ORDER);
|
||||
}
|
||||
|
||||
if ($cnt) {
|
||||
foreach ($matches as $match) {
|
||||
$return[] = self::decode($match[1]);
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get file tags from list
|
||||
*
|
||||
* ex. given music,video return <music><video> or [music][video]
|
||||
* @param string $list A comma delimited list of tags.
|
||||
* @param string $type Optional file type.
|
||||
*
|
||||
* @return string A list of file tags.
|
||||
* @deprecated since 2019.06 use arrayToFile() instead
|
||||
*/
|
||||
public static function listToFile(string $list, string $type = 'file')
|
||||
{
|
||||
$list_array = explode(',', $list);
|
||||
|
||||
return self::arrayToFile($list_array, $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get list from file tags
|
||||
*
|
||||
* ex. given <music><video>[friends], return music,video or friends
|
||||
* @param string $file File tags
|
||||
* @param string $type Optional file type.
|
||||
*
|
||||
* @return string Comma delimited list of tag names.
|
||||
* @deprecated since 2019.06 use fileToArray() instead
|
||||
*/
|
||||
public static function fileToList(string $file, $type = 'file')
|
||||
{
|
||||
return implode(',', self::fileToArray($file, $type));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Update file tags in PConfig
|
||||
*
|
||||
* @param int $uid Unique Identity.
|
||||
* @param string $file_old Categories previously associated with an item
|
||||
* @param string $file_new New list of categories for an item
|
||||
* @param string $type Optional file type.
|
||||
*
|
||||
* @return boolean A value indicating success or failure.
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function updatePconfig(int $uid, string $file_old, string $file_new, string $type = 'file')
|
||||
{
|
||||
if (!intval($uid)) {
|
||||
return false;
|
||||
} elseif ($file_old == $file_new) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$saved = PConfig::get($uid, 'system', 'filetags');
|
||||
|
||||
if (strlen($saved)) {
|
||||
if ($type == 'file') {
|
||||
$lbracket = '[';
|
||||
$rbracket = ']';
|
||||
$termtype = TERM_FILE;
|
||||
} else {
|
||||
$lbracket = '<';
|
||||
$rbracket = '>';
|
||||
$termtype = TERM_CATEGORY;
|
||||
}
|
||||
|
||||
$filetags_updated = $saved;
|
||||
|
||||
// check for new tags to be added as filetags in pconfig
|
||||
$new_tags = [];
|
||||
foreach (self::fileToArray($file_new, $type) as $tag) {
|
||||
if (!stristr($saved, $lbracket . self::encode($tag) . $rbracket)) {
|
||||
$new_tags[] = $tag;
|
||||
}
|
||||
}
|
||||
|
||||
$filetags_updated .= self::arrayToFile($new_tags, $type);
|
||||
|
||||
// check for deleted tags to be removed from filetags in pconfig
|
||||
$deleted_tags = [];
|
||||
foreach (self::fileToArray($file_old, $type) as $tag) {
|
||||
if (!stristr($file_new, $lbracket . self::encode($tag) . $rbracket)) {
|
||||
$deleted_tags[] = $tag;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($deleted_tags as $key => $tag) {
|
||||
$r = q("SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d",
|
||||
DBA::escape($tag),
|
||||
intval(Term::OBJECT_TYPE_POST),
|
||||
intval($termtype),
|
||||
intval($uid));
|
||||
|
||||
if (DBA::isResult($r)) {
|
||||
unset($deleted_tags[$key]);
|
||||
} else {
|
||||
$filetags_updated = str_replace($lbracket . self::encode($tag) . $rbracket, '', $filetags_updated);
|
||||
}
|
||||
}
|
||||
|
||||
if ($saved != $filetags_updated) {
|
||||
PConfig::set($uid, 'system', 'filetags', $filetags_updated);
|
||||
}
|
||||
|
||||
return true;
|
||||
} elseif (strlen($file_new)) {
|
||||
PConfig::set($uid, 'system', 'filetags', $file_new);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Add tag to file
|
||||
*
|
||||
* @param int $uid Unique identity.
|
||||
* @param int $item_id Item identity.
|
||||
* @param string $file File tag.
|
||||
*
|
||||
* @return boolean A value indicating success or failure.
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public static function saveFile($uid, $item_id, $file)
|
||||
{
|
||||
if (!intval($uid)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$item = Item::selectFirst(['file'], ['id' => $item_id, 'uid' => $uid]);
|
||||
if (DBA::isResult($item)) {
|
||||
if (!stristr($item['file'], '[' . self::encode($file) . ']')) {
|
||||
$fields = ['file' => $item['file'] . '[' . self::encode($file) . ']'];
|
||||
Item::update($fields, ['id' => $item_id]);
|
||||
}
|
||||
|
||||
$saved = PConfig::get($uid, 'system', 'filetags');
|
||||
|
||||
if (!strlen($saved) || !stristr($saved, '[' . self::encode($file) . ']')) {
|
||||
PConfig::set($uid, 'system', 'filetags', $saved . '[' . self::encode($file) . ']');
|
||||
}
|
||||
|
||||
info(L10n::t('Item filed'));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Remove tag from file
|
||||
*
|
||||
* @param int $uid Unique identity.
|
||||
* @param int $item_id Item identity.
|
||||
* @param string $file File tag.
|
||||
* @param boolean $cat Optional value indicating the term type (i.e. Category or File)
|
||||
*
|
||||
* @return boolean A value indicating success or failure.
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public static function unsaveFile($uid, $item_id, $file, $cat = false)
|
||||
{
|
||||
if (!intval($uid)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($cat == true) {
|
||||
$pattern = '<' . self::encode($file) . '>';
|
||||
$termtype = Term::CATEGORY;
|
||||
} else {
|
||||
$pattern = '[' . self::encode($file) . ']';
|
||||
$termtype = Term::FILE;
|
||||
}
|
||||
|
||||
$item = Item::selectFirst(['file'], ['id' => $item_id, 'uid' => $uid]);
|
||||
|
||||
if (!DBA::isResult($item)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$fields = ['file' => str_replace($pattern, '', $item['file'])];
|
||||
|
||||
Item::update($fields, ['id' => $item_id]);
|
||||
|
||||
$r = q("SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d",
|
||||
DBA::escape($file),
|
||||
intval(Term::OBJECT_TYPE_POST),
|
||||
intval($termtype),
|
||||
intval($uid)
|
||||
);
|
||||
|
||||
if (!DBA::isResult($r)) {
|
||||
$saved = PConfig::get($uid, 'system', 'filetags');
|
||||
PConfig::set($uid, 'system', 'filetags', str_replace($pattern, '', $saved));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -6,18 +6,21 @@
|
|||
*/
|
||||
namespace Friendica\Model;
|
||||
|
||||
use DOMDocument;
|
||||
use DOMXPath;
|
||||
use Exception;
|
||||
use Friendica\Core\Config;
|
||||
use Friendica\Core\Logger;
|
||||
use Friendica\Core\Protocol;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Core\Worker;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\Network\Probe;
|
||||
use Friendica\Protocol\ActivityPub;
|
||||
use Friendica\Protocol\PortableContact;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
use Friendica\Util\Network;
|
||||
|
||||
require_once 'include/dba.php';
|
||||
use Friendica\Util\Strings;
|
||||
|
||||
/**
|
||||
* @brief This class handles GlobalContact related functions
|
||||
|
@ -31,6 +34,7 @@ class GContact
|
|||
* @param string $mode Search mode (e.g. "community")
|
||||
*
|
||||
* @return array with search results
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public static function searchByName($search, $mode = '')
|
||||
{
|
||||
|
@ -91,6 +95,7 @@ class GContact
|
|||
* @param integer $cid Contact ID
|
||||
* @param integer $zcid Global Contact ID
|
||||
* @return void
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function link($gcid, $uid = 0, $cid = 0, $zcid = 0)
|
||||
{
|
||||
|
@ -105,16 +110,16 @@ class GContact
|
|||
/**
|
||||
* @brief Sanitize the given gcontact data
|
||||
*
|
||||
* @param array $gcontact array with gcontact data
|
||||
* @throw Exception
|
||||
*
|
||||
* Generation:
|
||||
* 0: No definition
|
||||
* 1: Profiles on this server
|
||||
* 2: Contacts of profiles on this server
|
||||
* 3: Contacts of contacts of profiles on this server
|
||||
* 4: ...
|
||||
*
|
||||
* @param array $gcontact array with gcontact data
|
||||
* @return array $gcontact
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function sanitize($gcontact)
|
||||
{
|
||||
|
@ -140,14 +145,14 @@ class GContact
|
|||
}
|
||||
|
||||
// Assure that there are no parameter fragments in the profile url
|
||||
if (in_array($gcontact['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS, ''])) {
|
||||
if (empty($gcontact['*network']) || in_array($gcontact["network"], Protocol::FEDERATED)) {
|
||||
$gcontact['url'] = self::cleanContactUrl($gcontact['url']);
|
||||
}
|
||||
|
||||
$alternate = PortableContact::alternateOStatusUrl($gcontact['url']);
|
||||
|
||||
// The global contacts should contain the original picture, not the cached one
|
||||
if (($gcontact['generation'] != 1) && stristr(normalise_link($gcontact['photo']), normalise_link(System::baseUrl().'/photo/'))) {
|
||||
if (($gcontact['generation'] != 1) && stristr(Strings::normaliseLink($gcontact['photo']), Strings::normaliseLink(System::baseUrl() . '/photo/'))) {
|
||||
$gcontact['photo'] = '';
|
||||
}
|
||||
|
||||
|
@ -155,7 +160,7 @@ class GContact
|
|||
$gcontact['network'] = '';
|
||||
|
||||
$condition = ["`uid` = 0 AND `nurl` = ? AND `network` != '' AND `network` != ?",
|
||||
normalise_link($gcontact['url']), Protocol::STATUSNET];
|
||||
Strings::normaliseLink($gcontact['url']), Protocol::STATUSNET];
|
||||
$contact = DBA::selectFirst('contact', ['network'], $condition);
|
||||
if (DBA::isResult($contact)) {
|
||||
$gcontact['network'] = $contact['network'];
|
||||
|
@ -163,7 +168,7 @@ class GContact
|
|||
|
||||
if (($gcontact['network'] == '') || ($gcontact['network'] == Protocol::OSTATUS)) {
|
||||
$condition = ["`uid` = 0 AND `alias` IN (?, ?) AND `network` != '' AND `network` != ?",
|
||||
$gcontact['url'], normalise_link($gcontact['url']), Protocol::STATUSNET];
|
||||
$gcontact['url'], Strings::normaliseLink($gcontact['url']), Protocol::STATUSNET];
|
||||
$contact = DBA::selectFirst('contact', ['network'], $condition);
|
||||
if (DBA::isResult($contact)) {
|
||||
$gcontact['network'] = $contact['network'];
|
||||
|
@ -172,7 +177,7 @@ class GContact
|
|||
}
|
||||
|
||||
$fields = ['network', 'updated', 'server_url', 'url', 'addr'];
|
||||
$gcnt = DBA::selectFirst('gcontact', $fields, ['nurl' => normalise_link($gcontact['url'])]);
|
||||
$gcnt = DBA::selectFirst('gcontact', $fields, ['nurl' => Strings::normaliseLink($gcontact['url'])]);
|
||||
if (DBA::isResult($gcnt)) {
|
||||
if (!isset($gcontact['network']) && ($gcnt['network'] != Protocol::STATUSNET)) {
|
||||
$gcontact['network'] = $gcnt['network'];
|
||||
|
@ -180,7 +185,7 @@ class GContact
|
|||
if ($gcontact['updated'] <= DBA::NULL_DATETIME) {
|
||||
$gcontact['updated'] = $gcnt['updated'];
|
||||
}
|
||||
if (!isset($gcontact['server_url']) && (normalise_link($gcnt['server_url']) != normalise_link($gcnt['url']))) {
|
||||
if (!isset($gcontact['server_url']) && (Strings::normaliseLink($gcnt['server_url']) != Strings::normaliseLink($gcnt['url']))) {
|
||||
$gcontact['server_url'] = $gcnt['server_url'];
|
||||
}
|
||||
if (!isset($gcontact['addr'])) {
|
||||
|
@ -189,7 +194,7 @@ class GContact
|
|||
}
|
||||
|
||||
if ((!isset($gcontact['network']) || !isset($gcontact['name']) || !isset($gcontact['addr']) || !isset($gcontact['photo']) || !isset($gcontact['server_url']) || $alternate)
|
||||
&& PortableContact::reachable($gcontact['url'], $gcontact['server_url'], $gcontact['network'], false)
|
||||
&& GServer::reachable($gcontact['url'], $gcontact['server_url'], $gcontact['network'], false)
|
||||
) {
|
||||
$data = Probe::uri($gcontact['url']);
|
||||
|
||||
|
@ -205,8 +210,8 @@ class GContact
|
|||
|
||||
if ($alternate && ($gcontact['network'] == Protocol::OSTATUS)) {
|
||||
// Delete the old entry - if it exists
|
||||
if (DBA::exists('gcontact', ['nurl' => normalise_link($orig_profile)])) {
|
||||
DBA::delete('gcontact', ['nurl' => normalise_link($orig_profile)]);
|
||||
if (DBA::exists('gcontact', ['nurl' => Strings::normaliseLink($orig_profile)])) {
|
||||
DBA::delete('gcontact', ['nurl' => Strings::normaliseLink($orig_profile)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -215,13 +220,13 @@ class GContact
|
|||
throw new Exception('No name and photo for URL '.$gcontact['url']);
|
||||
}
|
||||
|
||||
if (!in_array($gcontact['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::OSTATUS, Protocol::DIASPORA])) {
|
||||
if (!in_array($gcontact['network'], Protocol::FEDERATED)) {
|
||||
throw new Exception('No federated network ('.$gcontact['network'].') detected for URL '.$gcontact['url']);
|
||||
}
|
||||
|
||||
if (empty($gcontact['server_url'])) {
|
||||
// We check the server url to be sure that it is a real one
|
||||
$server_url = PortableContact::detectServer($gcontact['url']);
|
||||
$server_url = Contact::getBasepath($gcontact['url']);
|
||||
|
||||
// We are now sure that it is a correct URL. So we use it in the future
|
||||
if ($server_url != '') {
|
||||
|
@ -230,7 +235,7 @@ class GContact
|
|||
}
|
||||
|
||||
// The server URL doesn't seem to be valid, so we don't store it.
|
||||
if (!PortableContact::checkServer($gcontact['server_url'], $gcontact['network'])) {
|
||||
if (!GServer::check($gcontact['server_url'], $gcontact['network'])) {
|
||||
$gcontact['server_url'] = '';
|
||||
}
|
||||
|
||||
|
@ -241,6 +246,7 @@ class GContact
|
|||
* @param integer $uid id
|
||||
* @param integer $cid id
|
||||
* @return integer
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function countCommonFriends($uid, $cid)
|
||||
{
|
||||
|
@ -257,7 +263,7 @@ class GContact
|
|||
intval($cid)
|
||||
);
|
||||
|
||||
// logger("countCommonFriends: $uid $cid {$r[0]['total']}");
|
||||
// Logger::log("countCommonFriends: $uid $cid {$r[0]['total']}");
|
||||
if (DBA::isResult($r)) {
|
||||
return $r[0]['total'];
|
||||
}
|
||||
|
@ -268,6 +274,7 @@ class GContact
|
|||
* @param integer $uid id
|
||||
* @param integer $zcid zcid
|
||||
* @return integer
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function countCommonFriendsZcid($uid, $zcid)
|
||||
{
|
||||
|
@ -294,6 +301,7 @@ class GContact
|
|||
* @param integer $limit optional, default 9999
|
||||
* @param boolean $shuffle optional, default false
|
||||
* @return object
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function commonFriends($uid, $cid, $start = 0, $limit = 9999, $shuffle = false)
|
||||
{
|
||||
|
@ -332,6 +340,7 @@ class GContact
|
|||
* @param integer $limit optional, default 9999
|
||||
* @param boolean $shuffle optional, default false
|
||||
* @return object
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function commonFriendsZcid($uid, $zcid, $start = 0, $limit = 9999, $shuffle = false)
|
||||
{
|
||||
|
@ -361,6 +370,7 @@ class GContact
|
|||
* @param integer $uid user
|
||||
* @param integer $cid cid
|
||||
* @return integer
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function countAllFriends($uid, $cid)
|
||||
{
|
||||
|
@ -386,6 +396,7 @@ class GContact
|
|||
* @param integer $start optional, default 0
|
||||
* @param integer $limit optional, default 80
|
||||
* @return array
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function allFriends($uid, $cid, $start = 0, $limit = 80)
|
||||
{
|
||||
|
@ -409,10 +420,11 @@ class GContact
|
|||
}
|
||||
|
||||
/**
|
||||
* @param object $uid user
|
||||
* @param int $uid user
|
||||
* @param integer $start optional, default 0
|
||||
* @param integer $limit optional, default 80
|
||||
* @return array
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public static function suggestionQuery($uid, $start = 0, $limit = 80)
|
||||
{
|
||||
|
@ -429,7 +441,7 @@ class GContact
|
|||
// return $list;
|
||||
//}
|
||||
|
||||
$network = [Protocol::DFRN];
|
||||
$network = [Protocol::DFRN, Protocol::ACTIVITYPUB];
|
||||
|
||||
if (Config::get('system', 'diaspora_enabled')) {
|
||||
$network[] = Protocol::DIASPORA;
|
||||
|
@ -450,7 +462,7 @@ class GContact
|
|||
where uid = %d and not gcontact.nurl in ( select nurl from contact where uid = %d )
|
||||
AND NOT `gcontact`.`name` IN (SELECT `name` FROM `contact` WHERE `uid` = %d)
|
||||
AND NOT `gcontact`.`id` IN (SELECT `gcid` FROM `gcign` WHERE `uid` = %d)
|
||||
AND `gcontact`.`updated` >= '%s'
|
||||
AND `gcontact`.`updated` >= '%s' AND NOT `gcontact`.`hide`
|
||||
AND `gcontact`.`last_contact` >= `gcontact`.`last_failure`
|
||||
AND `gcontact`.`network` IN (%s)
|
||||
GROUP BY `glink`.`gcid` ORDER BY `gcontact`.`updated` DESC,`total` DESC LIMIT %d, %d",
|
||||
|
@ -516,11 +528,10 @@ class GContact
|
|||
|
||||
/**
|
||||
* @return void
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public static function updateSuggestions()
|
||||
{
|
||||
$a = get_app();
|
||||
|
||||
$done = [];
|
||||
|
||||
/// @TODO Check if it is really neccessary to poll the own server
|
||||
|
@ -534,7 +545,7 @@ class GContact
|
|||
$j = json_decode($x);
|
||||
if (!empty($j->entries)) {
|
||||
foreach ($j->entries as $entry) {
|
||||
PortableContact::checkServer($entry->url);
|
||||
GServer::check($entry->url);
|
||||
|
||||
$url = $entry->url . '/poco';
|
||||
if (!in_array($url, $done)) {
|
||||
|
@ -569,6 +580,7 @@ class GContact
|
|||
* @param string $url Contact url
|
||||
*
|
||||
* @return string Contact url with the wanted parts
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function cleanContactUrl($url)
|
||||
{
|
||||
|
@ -589,7 +601,7 @@ class GContact
|
|||
}
|
||||
|
||||
if ($new_url != $url) {
|
||||
logger("Cleaned contact url ".$url." to ".$new_url." - Called by: ".System::callstack(), LOGGER_DEBUG);
|
||||
Logger::log("Cleaned contact url ".$url." to ".$new_url." - Called by: ".System::callstack(), Logger::DEBUG);
|
||||
}
|
||||
|
||||
return $new_url;
|
||||
|
@ -600,13 +612,15 @@ class GContact
|
|||
*
|
||||
* @param array $contact contact array (called by reference)
|
||||
* @return void
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
public static function fixAlternateContactAddress(&$contact)
|
||||
{
|
||||
if (($contact["network"] == Protocol::OSTATUS) && PortableContact::alternateOStatusUrl($contact["url"])) {
|
||||
$data = Probe::uri($contact["url"]);
|
||||
if ($contact["network"] == Protocol::OSTATUS) {
|
||||
logger("Fix primary url from ".$contact["url"]." to ".$data["url"]." - Called by: ".System::callstack(), LOGGER_DEBUG);
|
||||
Logger::log("Fix primary url from ".$contact["url"]." to ".$data["url"]." - Called by: ".System::callstack(), Logger::DEBUG);
|
||||
$contact["url"] = $data["url"];
|
||||
$contact["addr"] = $data["addr"];
|
||||
$contact["alias"] = $data["alias"];
|
||||
|
@ -621,6 +635,8 @@ class GContact
|
|||
* @param array $contact contact array
|
||||
*
|
||||
* @return bool|int Returns false if not found, integer if contact was found
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
public static function getId($contact)
|
||||
{
|
||||
|
@ -630,12 +646,12 @@ class GContact
|
|||
$last_contact_str = '';
|
||||
|
||||
if (empty($contact["network"])) {
|
||||
logger("Empty network for contact url ".$contact["url"]." - Called by: ".System::callstack(), LOGGER_DEBUG);
|
||||
Logger::log("Empty network for contact url ".$contact["url"]." - Called by: ".System::callstack(), Logger::DEBUG);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (in_array($contact["network"], [Protocol::PHANTOM])) {
|
||||
logger("Invalid network for contact url ".$contact["url"]." - Called by: ".System::callstack(), LOGGER_DEBUG);
|
||||
Logger::log("Invalid network for contact url ".$contact["url"]." - Called by: ".System::callstack(), Logger::DEBUG);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -652,13 +668,13 @@ class GContact
|
|||
self::fixAlternateContactAddress($contact);
|
||||
|
||||
// Remove unwanted parts from the contact url (e.g. "?zrl=...")
|
||||
if (in_array($contact["network"], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS])) {
|
||||
if (in_array($contact["network"], Protocol::FEDERATED)) {
|
||||
$contact["url"] = self::cleanContactUrl($contact["url"]);
|
||||
}
|
||||
|
||||
DBA::lock('gcontact');
|
||||
$fields = ['id', 'last_contact', 'last_failure', 'network'];
|
||||
$gcnt = DBA::selectFirst('gcontact', $fields, ['nurl' => normalise_link($contact["url"])]);
|
||||
$gcnt = DBA::selectFirst('gcontact', $fields, ['nurl' => Strings::normaliseLink($contact["url"])]);
|
||||
if (DBA::isResult($gcnt)) {
|
||||
$gcontact_id = $gcnt["id"];
|
||||
|
||||
|
@ -683,7 +699,7 @@ class GContact
|
|||
DBA::escape($contact["addr"]),
|
||||
DBA::escape($contact["network"]),
|
||||
DBA::escape($contact["url"]),
|
||||
DBA::escape(normalise_link($contact["url"])),
|
||||
DBA::escape(Strings::normaliseLink($contact["url"])),
|
||||
DBA::escape($contact["photo"]),
|
||||
DBA::escape(DateTimeFormat::utcNow()),
|
||||
DBA::escape(DateTimeFormat::utcNow()),
|
||||
|
@ -693,7 +709,7 @@ class GContact
|
|||
intval($contact["generation"])
|
||||
);
|
||||
|
||||
$condition = ['nurl' => normalise_link($contact["url"])];
|
||||
$condition = ['nurl' => Strings::normaliseLink($contact["url"])];
|
||||
$cnt = DBA::selectFirst('gcontact', ['id', 'network'], $condition, ['order' => ['id']]);
|
||||
if (DBA::isResult($cnt)) {
|
||||
$gcontact_id = $cnt["id"];
|
||||
|
@ -703,7 +719,7 @@ class GContact
|
|||
DBA::unlock();
|
||||
|
||||
if ($doprobing) {
|
||||
logger("Last Contact: ". $last_contact_str." - Last Failure: ".$last_failure_str." - Checking: ".$contact["url"], LOGGER_DEBUG);
|
||||
Logger::log("Last Contact: ". $last_contact_str." - Last Failure: ".$last_failure_str." - Checking: ".$contact["url"], Logger::DEBUG);
|
||||
Worker::add(PRIORITY_LOW, 'GProbe', $contact["url"]);
|
||||
}
|
||||
|
||||
|
@ -716,6 +732,8 @@ class GContact
|
|||
* @param array $contact contact array
|
||||
*
|
||||
* @return bool|int Returns false if not found, integer if contact was found
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
public static function update($contact)
|
||||
{
|
||||
|
@ -732,174 +750,434 @@ class GContact
|
|||
return false;
|
||||
}
|
||||
|
||||
$public_contact = q(
|
||||
"SELECT `name`, `nick`, `photo`, `location`, `about`, `addr`, `generation`, `birthday`, `gender`, `keywords`,
|
||||
`contact-type`, `hide`, `nsfw`, `network`, `alias`, `notify`, `server_url`, `connect`, `updated`, `url`
|
||||
FROM `gcontact` WHERE `id` = %d LIMIT 1",
|
||||
intval($gcontact_id)
|
||||
);
|
||||
$public_contact = DBA::selectFirst('gcontact', [
|
||||
'name', 'nick', 'photo', 'location', 'about', 'addr', 'generation', 'birthday', 'gender', 'keywords',
|
||||
'contact-type', 'hide', 'nsfw', 'network', 'alias', 'notify', 'server_url', 'connect', 'updated', 'url'
|
||||
], ['id' => $gcontact_id]);
|
||||
|
||||
if (!DBA::isResult($public_contact)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get all field names
|
||||
$fields = [];
|
||||
foreach ($public_contact[0] as $field => $data) {
|
||||
foreach ($public_contact as $field => $data) {
|
||||
$fields[$field] = $data;
|
||||
}
|
||||
|
||||
unset($fields["url"]);
|
||||
unset($fields["updated"]);
|
||||
unset($fields["hide"]);
|
||||
unset($fields['url']);
|
||||
unset($fields['updated']);
|
||||
unset($fields['hide']);
|
||||
|
||||
// Bugfix: We had an error in the storing of keywords which lead to the "0"
|
||||
// This value is still transmitted via poco.
|
||||
if (!empty($contact["keywords"]) && ($contact["keywords"] == "0")) {
|
||||
unset($contact["keywords"]);
|
||||
if (isset($contact['keywords']) && ($contact['keywords'] == '0')) {
|
||||
unset($contact['keywords']);
|
||||
}
|
||||
|
||||
if (!empty($public_contact[0]["keywords"]) && ($public_contact[0]["keywords"] == "0")) {
|
||||
$public_contact[0]["keywords"] = "";
|
||||
if (isset($public_contact['keywords']) && ($public_contact['keywords'] == '0')) {
|
||||
$public_contact['keywords'] = '';
|
||||
}
|
||||
|
||||
// assign all unassigned fields from the database entry
|
||||
foreach ($fields as $field => $data) {
|
||||
if (!isset($contact[$field]) || ($contact[$field] == "")) {
|
||||
$contact[$field] = $public_contact[0][$field];
|
||||
if (empty($contact[$field])) {
|
||||
$contact[$field] = $public_contact[$field];
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($contact["hide"])) {
|
||||
$contact["hide"] = $public_contact[0]["hide"];
|
||||
if (!isset($contact['hide'])) {
|
||||
$contact['hide'] = $public_contact['hide'];
|
||||
}
|
||||
|
||||
$fields["hide"] = $public_contact[0]["hide"];
|
||||
$fields['hide'] = $public_contact['hide'];
|
||||
|
||||
if ($contact["network"] == Protocol::STATUSNET) {
|
||||
$contact["network"] = Protocol::OSTATUS;
|
||||
if ($contact['network'] == Protocol::STATUSNET) {
|
||||
$contact['network'] = Protocol::OSTATUS;
|
||||
}
|
||||
|
||||
// Replace alternate OStatus user format with the primary one
|
||||
self::fixAlternateContactAddress($contact);
|
||||
|
||||
if (!isset($contact["updated"])) {
|
||||
$contact["updated"] = DateTimeFormat::utcNow();
|
||||
if (!isset($contact['updated'])) {
|
||||
$contact['updated'] = DateTimeFormat::utcNow();
|
||||
}
|
||||
|
||||
if ($contact["network"] == Protocol::TWITTER) {
|
||||
$contact["server_url"] = 'http://twitter.com';
|
||||
if ($contact['network'] == Protocol::TWITTER) {
|
||||
$contact['server_url'] = 'http://twitter.com';
|
||||
}
|
||||
|
||||
if ($contact["server_url"] == "") {
|
||||
$data = Probe::uri($contact["url"]);
|
||||
if ($data["network"] != Protocol::PHANTOM) {
|
||||
$contact["server_url"] = $data['baseurl'];
|
||||
if (empty($contact['server_url'])) {
|
||||
$data = Probe::uri($contact['url']);
|
||||
if ($data['network'] != Protocol::PHANTOM) {
|
||||
$contact['server_url'] = $data['baseurl'];
|
||||
}
|
||||
} else {
|
||||
$contact["server_url"] = normalise_link($contact["server_url"]);
|
||||
$contact['server_url'] = Strings::normaliseLink($contact['server_url']);
|
||||
}
|
||||
|
||||
if (($contact["addr"] == "") && ($contact["server_url"] != "") && ($contact["nick"] != "")) {
|
||||
$hostname = str_replace("http://", "", $contact["server_url"]);
|
||||
$contact["addr"] = $contact["nick"]."@".$hostname;
|
||||
if (empty($contact['addr']) && !empty($contact['server_url']) && !empty($contact['nick'])) {
|
||||
$hostname = str_replace('http://', '', $contact['server_url']);
|
||||
$contact['addr'] = $contact['nick'] . '@' . $hostname;
|
||||
}
|
||||
|
||||
// Check if any field changed
|
||||
$update = false;
|
||||
unset($fields["generation"]);
|
||||
unset($fields['generation']);
|
||||
|
||||
if ((($contact["generation"] > 0) && ($contact["generation"] <= $public_contact[0]["generation"])) || ($public_contact[0]["generation"] == 0)) {
|
||||
if ((($contact['generation'] > 0) && ($contact['generation'] <= $public_contact['generation'])) || ($public_contact['generation'] == 0)) {
|
||||
foreach ($fields as $field => $data) {
|
||||
if ($contact[$field] != $public_contact[0][$field]) {
|
||||
logger("Difference for contact ".$contact["url"]." in field '".$field."'. New value: '".$contact[$field]."', old value '".$public_contact[0][$field]."'", LOGGER_DEBUG);
|
||||
if ($contact[$field] != $public_contact[$field]) {
|
||||
Logger::debug('Difference found.', ['contact' => $contact["url"], 'field' => $field, 'new' => $contact[$field], 'old' => $public_contact[$field]]);
|
||||
$update = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($contact["generation"] < $public_contact[0]["generation"]) {
|
||||
logger("Difference for contact ".$contact["url"]." in field 'generation'. new value: '".$contact["generation"]."', old value '".$public_contact[0]["generation"]."'", LOGGER_DEBUG);
|
||||
if ($contact['generation'] < $public_contact['generation']) {
|
||||
Logger::debug('Difference found.', ['contact' => $contact["url"], 'field' => 'generation', 'new' => $contact['generation'], 'old' => $public_contact['generation']]);
|
||||
$update = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($update) {
|
||||
logger("Update gcontact for ".$contact["url"], LOGGER_DEBUG);
|
||||
Logger::debug('Update gcontact.', ['contact' => $contact['url']]);
|
||||
$condition = ['`nurl` = ? AND (`generation` = 0 OR `generation` >= ?)',
|
||||
normalise_link($contact["url"]), $contact["generation"]];
|
||||
Strings::normaliseLink($contact["url"]), $contact["generation"]];
|
||||
$contact["updated"] = DateTimeFormat::utc($contact["updated"]);
|
||||
|
||||
$updated = ['photo' => $contact['photo'], 'name' => $contact['name'],
|
||||
'nick' => $contact['nick'], 'addr' => $contact['addr'],
|
||||
'network' => $contact['network'], 'birthday' => $contact['birthday'],
|
||||
'gender' => $contact['gender'], 'keywords' => $contact['keywords'],
|
||||
'hide' => $contact['hide'], 'nsfw' => $contact['nsfw'],
|
||||
'contact-type' => $contact['contact-type'], 'alias' => $contact['alias'],
|
||||
'notify' => $contact['notify'], 'url' => $contact['url'],
|
||||
'location' => $contact['location'], 'about' => $contact['about'],
|
||||
'generation' => $contact['generation'], 'updated' => $contact['updated'],
|
||||
'server_url' => $contact['server_url'], 'connect' => $contact['connect']];
|
||||
$updated = [
|
||||
'photo' => $contact['photo'], 'name' => $contact['name'],
|
||||
'nick' => $contact['nick'], 'addr' => $contact['addr'],
|
||||
'network' => $contact['network'], 'birthday' => $contact['birthday'],
|
||||
'gender' => $contact['gender'], 'keywords' => $contact['keywords'],
|
||||
'hide' => $contact['hide'], 'nsfw' => $contact['nsfw'],
|
||||
'contact-type' => $contact['contact-type'], 'alias' => $contact['alias'],
|
||||
'notify' => $contact['notify'], 'url' => $contact['url'],
|
||||
'location' => $contact['location'], 'about' => $contact['about'],
|
||||
'generation' => $contact['generation'], 'updated' => $contact['updated'],
|
||||
'server_url' => $contact['server_url'], 'connect' => $contact['connect']
|
||||
];
|
||||
|
||||
DBA::update('gcontact', $updated, $condition, $fields);
|
||||
|
||||
// Now update the contact entry with the user id "0" as well.
|
||||
// This is used for the shadow copies of public items.
|
||||
/// @todo Check if we really should do this.
|
||||
// The quality of the gcontact table is mostly lower than the public contact
|
||||
$public_contact = DBA::selectFirst('contact', ['id'], ['nurl' => normalise_link($contact["url"]), 'uid' => 0]);
|
||||
if (DBA::isResult($public_contact)) {
|
||||
logger("Update public contact ".$public_contact["id"], LOGGER_DEBUG);
|
||||
|
||||
Contact::updateAvatar($contact["photo"], 0, $public_contact["id"]);
|
||||
|
||||
$fields = ['name', 'nick', 'addr',
|
||||
'network', 'bd', 'gender',
|
||||
'keywords', 'alias', 'contact-type',
|
||||
'url', 'location', 'about'];
|
||||
$old_contact = DBA::selectFirst('contact', $fields, ['id' => $public_contact["id"]]);
|
||||
|
||||
// Update it with the current values
|
||||
$fields = ['name' => $contact['name'], 'nick' => $contact['nick'],
|
||||
'addr' => $contact['addr'], 'network' => $contact['network'],
|
||||
'bd' => $contact['birthday'], 'gender' => $contact['gender'],
|
||||
'keywords' => $contact['keywords'], 'alias' => $contact['alias'],
|
||||
'contact-type' => $contact['contact-type'], 'url' => $contact['url'],
|
||||
'location' => $contact['location'], 'about' => $contact['about']];
|
||||
|
||||
// Don't update the birthday field if not set or invalid
|
||||
if (empty($contact['birthday']) || ($contact['birthday'] < '0001-01-01')) {
|
||||
unset($fields['bd']);
|
||||
}
|
||||
|
||||
|
||||
DBA::update('contact', $fields, ['id' => $public_contact["id"]], $old_contact);
|
||||
}
|
||||
}
|
||||
|
||||
return $gcontact_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the last date that the contact had posted something
|
||||
*
|
||||
* @param string $data Probing result
|
||||
* @param bool $force force updating
|
||||
*/
|
||||
public static function setLastUpdate(array $data, bool $force = false)
|
||||
{
|
||||
// Fetch the global contact
|
||||
$gcontact = DBA::selectFirst('gcontact', ['created', 'updated', 'last_contact', 'last_failure'],
|
||||
['nurl' => Strings::normaliseLink($data['url'])]);
|
||||
if (!DBA::isResult($gcontact)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$force && !PortableContact::updateNeeded($gcontact['created'], $gcontact['updated'], $gcontact['last_failure'], $gcontact['last_contact'])) {
|
||||
Logger::info("Don't update profile", ['url' => $data['url'], 'updated' => $gcontact['updated']]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (self::updateFromNoScrape($data)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// When the profile doesn't have got a feed, then we exit here
|
||||
if (empty($data['poll'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($data['network'] == Protocol::ACTIVITYPUB) {
|
||||
self::updateFromOutbox($data['poll'], $data);
|
||||
} else {
|
||||
self::updateFromFeed($data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a global contact via the "noscrape" endpoint
|
||||
*
|
||||
* @param string $data Probing result
|
||||
*
|
||||
* @return bool 'true' if update was successful or the server was unreachable
|
||||
*/
|
||||
private static function updateFromNoScrape(array $data)
|
||||
{
|
||||
// Check the 'noscrape' endpoint when it is a Friendica server
|
||||
$gserver = DBA::selectFirst('gserver', ['noscrape'], ["`nurl` = ? AND `noscrape` != ''",
|
||||
Strings::normaliseLink($data['baseurl'])]);
|
||||
if (!DBA::isResult($gserver)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$curlResult = Network::curl($gserver['noscrape'] . '/' . $data['nick']);
|
||||
|
||||
if ($curlResult->isSuccess() && !empty($curlResult->getBody())) {
|
||||
$noscrape = json_decode($curlResult->getBody(), true);
|
||||
if (!empty($noscrape)) {
|
||||
$noscrape['updated'] = DateTimeFormat::utc($noscrape['updated'], DateTimeFormat::MYSQL);
|
||||
$fields = ['last_contact' => DateTimeFormat::utcNow(), 'updated' => $noscrape['updated']];
|
||||
DBA::update('gcontact', $fields, ['nurl' => Strings::normaliseLink($data['url'])]);
|
||||
return true;
|
||||
}
|
||||
} elseif ($curlResult->isTimeout()) {
|
||||
// On a timeout return the existing value, but mark the contact as failure
|
||||
$fields = ['last_failure' => DateTimeFormat::utcNow()];
|
||||
DBA::update('gcontact', $fields, ['nurl' => Strings::normaliseLink($data['url'])]);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a global contact via an ActivityPub Outbox
|
||||
*
|
||||
* @param string $data Probing result
|
||||
*/
|
||||
private static function updateFromOutbox(string $feed, array $data)
|
||||
{
|
||||
$outbox = ActivityPub::fetchContent($feed);
|
||||
if (empty($outbox)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!empty($outbox['orderedItems'])) {
|
||||
$items = $outbox['orderedItems'];
|
||||
} elseif (!empty($outbox['first']['orderedItems'])) {
|
||||
$items = $outbox['first']['orderedItems'];
|
||||
} elseif (!empty($outbox['first'])) {
|
||||
self::updateFromOutbox($outbox['first'], $data);
|
||||
return;
|
||||
} else {
|
||||
$items = [];
|
||||
}
|
||||
|
||||
$last_updated = '';
|
||||
|
||||
foreach ($items as $activity) {
|
||||
if ($last_updated < $activity['published']) {
|
||||
$last_updated = $activity['published'];
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($last_updated)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$fields = ['last_contact' => DateTimeFormat::utcNow(), 'updated' => $last_updated];
|
||||
DBA::update('gcontact', $fields, ['nurl' => Strings::normaliseLink($data['url'])]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a global contact via an XML feed
|
||||
*
|
||||
* @param string $data Probing result
|
||||
*/
|
||||
private static function updateFromFeed(array $data)
|
||||
{
|
||||
// Search for the newest entry in the feed
|
||||
$curlResult = Network::curl($data['poll']);
|
||||
if (!$curlResult->isSuccess()) {
|
||||
$fields = ['last_failure' => DateTimeFormat::utcNow()];
|
||||
DBA::update('gcontact', $fields, ['nurl' => Strings::normaliseLink($profile)]);
|
||||
|
||||
Logger::info("Profile wasn't reachable (no feed)", ['url' => $data['url']]);
|
||||
return;
|
||||
}
|
||||
|
||||
$doc = new DOMDocument();
|
||||
@$doc->loadXML($curlResult->getBody());
|
||||
|
||||
$xpath = new DOMXPath($doc);
|
||||
$xpath->registerNamespace('atom', 'http://www.w3.org/2005/Atom');
|
||||
|
||||
$entries = $xpath->query('/atom:feed/atom:entry');
|
||||
|
||||
$last_updated = '';
|
||||
|
||||
foreach ($entries as $entry) {
|
||||
$published_item = $xpath->query('atom:published/text()', $entry)->item(0);
|
||||
$updated_item = $xpath->query('atom:updated/text()' , $entry)->item(0);
|
||||
$published = !empty($published_item->nodeValue) ? DateTimeFormat::utc($published_item->nodeValue) : null;
|
||||
$updated = !empty($updated_item->nodeValue) ? DateTimeFormat::utc($updated_item->nodeValue) : null;
|
||||
|
||||
if (empty($published) || empty($updated)) {
|
||||
Logger::notice('Invalid entry for XPath.', ['entry' => $entry, 'url' => $data['url']]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($last_updated < $published) {
|
||||
$last_updated = $published;
|
||||
}
|
||||
|
||||
if ($last_updated < $updated) {
|
||||
$last_updated = $updated;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($last_updated)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$fields = ['last_contact' => DateTimeFormat::utcNow(), 'updated' => $last_updated];
|
||||
DBA::update('gcontact', $fields, ['nurl' => Strings::normaliseLink($data['url'])]);
|
||||
}
|
||||
/**
|
||||
* @brief Updates the gcontact entry from a given public contact id
|
||||
*
|
||||
* @param integer $cid contact id
|
||||
* @return void
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
public static function updateFromPublicContactID($cid)
|
||||
{
|
||||
self::updateFromPublicContact(['id' => $cid]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Updates the gcontact entry from a given public contact url
|
||||
*
|
||||
* @param string $url contact url
|
||||
* @return integer gcontact id
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
public static function updateFromPublicContactURL($url)
|
||||
{
|
||||
return self::updateFromPublicContact(['nurl' => Strings::normaliseLink($url)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Helper function for updateFromPublicContactID and updateFromPublicContactURL
|
||||
*
|
||||
* @param array $condition contact condition
|
||||
* @return integer gcontact id
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
private static function updateFromPublicContact($condition)
|
||||
{
|
||||
$fields = ['name', 'nick', 'url', 'nurl', 'location', 'about', 'keywords', 'gender',
|
||||
'bd', 'contact-type', 'network', 'addr', 'notify', 'alias', 'archive', 'term-date',
|
||||
'created', 'updated', 'avatar', 'success_update', 'failure_update', 'forum', 'prv',
|
||||
'baseurl', 'sensitive', 'unsearchable'];
|
||||
|
||||
$contact = DBA::selectFirst('contact', $fields, array_merge($condition, ['uid' => 0, 'network' => Protocol::FEDERATED]));
|
||||
if (!DBA::isResult($contact)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$fields = ['name', 'nick', 'url', 'nurl', 'location', 'about', 'keywords', 'gender', 'generation',
|
||||
'birthday', 'contact-type', 'network', 'addr', 'notify', 'alias', 'archived', 'archive_date',
|
||||
'created', 'updated', 'photo', 'last_contact', 'last_failure', 'community', 'connect',
|
||||
'server_url', 'nsfw', 'hide', 'id'];
|
||||
|
||||
$old_gcontact = DBA::selectFirst('gcontact', $fields, ['nurl' => $contact['nurl']]);
|
||||
$do_insert = !DBA::isResult($old_gcontact);
|
||||
if ($do_insert) {
|
||||
$old_gcontact = [];
|
||||
}
|
||||
|
||||
$gcontact = [];
|
||||
|
||||
// These fields are identical in both contact and gcontact
|
||||
$fields = ['name', 'nick', 'url', 'nurl', 'location', 'about', 'keywords', 'gender',
|
||||
'contact-type', 'network', 'addr', 'notify', 'alias', 'created', 'updated'];
|
||||
|
||||
foreach ($fields as $field) {
|
||||
$gcontact[$field] = $contact[$field];
|
||||
}
|
||||
|
||||
// These fields are having different names but the same content
|
||||
$gcontact['server_url'] = $contact['baseurl'] ?? ''; // "baseurl" can be null, "server_url" not
|
||||
$gcontact['nsfw'] = $contact['sensitive'];
|
||||
$gcontact['hide'] = $contact['unsearchable'];
|
||||
$gcontact['archived'] = $contact['archive'];
|
||||
$gcontact['archive_date'] = $contact['term-date'];
|
||||
$gcontact['birthday'] = $contact['bd'];
|
||||
$gcontact['photo'] = $contact['avatar'];
|
||||
$gcontact['last_contact'] = $contact['success_update'];
|
||||
$gcontact['last_failure'] = $contact['failure_update'];
|
||||
$gcontact['community'] = ($contact['forum'] || $contact['prv']);
|
||||
|
||||
foreach (['last_contact', 'last_failure', 'updated'] as $field) {
|
||||
if (!empty($old_gcontact[$field]) && ($old_gcontact[$field] >= $gcontact[$field])) {
|
||||
unset($gcontact[$field]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$gcontact['archived']) {
|
||||
$gcontact['archive_date'] = DBA::NULL_DATETIME;
|
||||
}
|
||||
|
||||
if (!empty($old_gcontact['created']) && ($old_gcontact['created'] > DBA::NULL_DATETIME)
|
||||
&& ($old_gcontact['created'] <= $gcontact['created'])) {
|
||||
unset($gcontact['created']);
|
||||
}
|
||||
|
||||
if (empty($gcontact['birthday']) && ($gcontact['birthday'] <= DBA::NULL_DATETIME)) {
|
||||
unset($gcontact['birthday']);
|
||||
}
|
||||
|
||||
if (empty($old_gcontact['generation']) || ($old_gcontact['generation'] > 2)) {
|
||||
$gcontact['generation'] = 2; // We fetched the data directly from the other server
|
||||
}
|
||||
|
||||
if (!$do_insert) {
|
||||
DBA::update('gcontact', $gcontact, ['nurl' => $contact['nurl']], $old_gcontact);
|
||||
return $old_gcontact['id'];
|
||||
} elseif (!$gcontact['archived']) {
|
||||
DBA::insert('gcontact', $gcontact);
|
||||
return DBA::lastInsertId();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Updates the gcontact entry from probe
|
||||
*
|
||||
* @param string $url profile link
|
||||
* @return void
|
||||
* @param string $url profile link
|
||||
* @param boolean $force Optional forcing of network probing (otherwise we use the cached data)
|
||||
*
|
||||
* @return boolean 'true' when contact had been updated
|
||||
*
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
public static function updateFromProbe($url)
|
||||
public static function updateFromProbe($url, $force = false)
|
||||
{
|
||||
$data = Probe::uri($url);
|
||||
$data = Probe::uri($url, $force);
|
||||
|
||||
if (in_array($data["network"], [Protocol::PHANTOM])) {
|
||||
logger("Invalid network for contact url ".$data["url"]." - Called by: ".System::callstack(), LOGGER_DEBUG);
|
||||
return;
|
||||
$fields = ['last_failure' => DateTimeFormat::utcNow()];
|
||||
DBA::update('gcontact', $fields, ['nurl' => Strings::normaliseLink($url)]);
|
||||
Logger::info('Invalid network for contact', ['url' => $data['url'], 'callstack' => System::callstack()]);
|
||||
return false;
|
||||
}
|
||||
|
||||
$data["server_url"] = $data["baseurl"];
|
||||
|
||||
self::update($data);
|
||||
|
||||
// Set the date of the latest post
|
||||
self::setLastUpdate($data, $force);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Update the gcontact entry for a given user id
|
||||
*
|
||||
* @param int $uid User ID
|
||||
* @return void
|
||||
* @return bool
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
public static function updateForUser($uid)
|
||||
{
|
||||
|
@ -917,7 +1195,7 @@ class GContact
|
|||
);
|
||||
|
||||
if (!DBA::isResult($r)) {
|
||||
logger('Cannot find user with uid=' . $uid, LOGGER_INFO);
|
||||
Logger::log('Cannot find user with uid=' . $uid, Logger::INFO);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -950,11 +1228,13 @@ class GContact
|
|||
* If the "Statistics" addon is enabled (See http://gstools.org/ for details) we query user data with this.
|
||||
*
|
||||
* @param string $server Server address
|
||||
* @return void
|
||||
* @return bool
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
public static function fetchGsUsers($server)
|
||||
{
|
||||
logger("Fetching users from GNU Social server ".$server, LOGGER_DEBUG);
|
||||
Logger::log("Fetching users from GNU Social server ".$server, Logger::DEBUG);
|
||||
|
||||
$url = $server."/main/statistics";
|
||||
|
||||
|
@ -965,8 +1245,8 @@ class GContact
|
|||
|
||||
$statistics = json_decode($curlResult->getBody());
|
||||
|
||||
if (!empty($statistics->config)) {
|
||||
if ($statistics->config->instance_with_ssl) {
|
||||
if (!empty($statistics->config->instance_address)) {
|
||||
if (!empty($statistics->config->instance_with_ssl)) {
|
||||
$server = "https://";
|
||||
} else {
|
||||
$server = "http://";
|
||||
|
@ -975,8 +1255,8 @@ class GContact
|
|||
$server .= $statistics->config->instance_address;
|
||||
|
||||
$hostname = $statistics->config->instance_address;
|
||||
} elseif (!empty($statistics)) {
|
||||
if ($statistics->instance_with_ssl) {
|
||||
} elseif (!empty($statistics->instance_address)) {
|
||||
if (!empty($statistics->instance_with_ssl)) {
|
||||
$server = "https://";
|
||||
} else {
|
||||
$server = "http://";
|
||||
|
@ -1010,6 +1290,8 @@ class GContact
|
|||
/**
|
||||
* @brief Asking GNU Social server on a regular base for their user data
|
||||
* @return void
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
public static function discoverGsUsers()
|
||||
{
|
||||
|
@ -1017,11 +1299,16 @@ class GContact
|
|||
|
||||
$last_update = date("c", time() - (60 * 60 * 24 * $requery_days));
|
||||
|
||||
$r = q(
|
||||
"SELECT `nurl`, `url` FROM `gserver` WHERE `last_contact` >= `last_failure` AND `network` = '%s' AND `last_poco_query` < '%s' ORDER BY RAND() LIMIT 5",
|
||||
DBA::escape(Protocol::OSTATUS),
|
||||
DBA::escape($last_update)
|
||||
);
|
||||
$r = DBA::select('gserver', ['nurl', 'url'], [
|
||||
'`network` = ?
|
||||
AND `last_contact` >= `last_failure`
|
||||
AND `last_poco_query` < ?',
|
||||
Protocol::OSTATUS,
|
||||
$last_update
|
||||
], [
|
||||
'limit' => 5,
|
||||
'order' => ['RAND()']
|
||||
]);
|
||||
|
||||
if (!DBA::isResult($r)) {
|
||||
return;
|
||||
|
@ -1034,20 +1321,23 @@ class GContact
|
|||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* Returns a random, global contact of the current node
|
||||
*
|
||||
* @return string The profile URL
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getRandomUrl()
|
||||
{
|
||||
$r = q(
|
||||
"SELECT `url` FROM `gcontact` WHERE `network` = '%s'
|
||||
AND `last_contact` >= `last_failure`
|
||||
AND `updated` > UTC_TIMESTAMP - INTERVAL 1 MONTH
|
||||
ORDER BY rand() LIMIT 1",
|
||||
DBA::escape(Protocol::DFRN)
|
||||
);
|
||||
$r = DBA::selectFirst('gcontact', ['url'], [
|
||||
'`network` = ?
|
||||
AND `last_contact` >= `last_failure`
|
||||
AND `updated` > ?',
|
||||
Protocol::DFRN,
|
||||
DateTimeFormat::utc('now - 1 month'),
|
||||
], ['order' => ['RAND()']]);
|
||||
|
||||
if (DBA::isResult($r)) {
|
||||
return dirname($r[0]['url']);
|
||||
return $r['url'];
|
||||
}
|
||||
|
||||
return '';
|
||||
|
|
1187
src/Model/GServer.php
Normal file
1187
src/Model/GServer.php
Normal file
File diff suppressed because it is too large
Load diff
|
@ -2,36 +2,68 @@
|
|||
/**
|
||||
* @file src/Model/Group.php
|
||||
*/
|
||||
|
||||
namespace Friendica\Model;
|
||||
|
||||
use Friendica\BaseModule;
|
||||
use Friendica\BaseObject;
|
||||
use Friendica\Core\L10n;
|
||||
use Friendica\Core\Logger;
|
||||
use Friendica\Core\Protocol;
|
||||
use Friendica\Core\Renderer;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\Util\Security;
|
||||
|
||||
require_once 'boot.php';
|
||||
require_once 'include/dba.php';
|
||||
require_once 'include/text.php';
|
||||
|
||||
/**
|
||||
* @brief functions for interacting with the group database table
|
||||
*/
|
||||
class Group extends BaseObject
|
||||
{
|
||||
const FOLLOWERS = '~';
|
||||
const MUTUALS = '&';
|
||||
|
||||
public static function getByUserId($uid, $includesDeleted = false)
|
||||
{
|
||||
$conditions = ['uid' => $uid];
|
||||
|
||||
if (!$includesDeleted) {
|
||||
$conditions['deleted'] = false;
|
||||
}
|
||||
|
||||
return DBA::selectToArray('group', [], $conditions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $group_id
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function exists($group_id, $uid = null)
|
||||
{
|
||||
$condition = ['id' => $group_id, 'deleted' => false];
|
||||
|
||||
if (isset($uid)) {
|
||||
$condition = [
|
||||
'uid' => $uid
|
||||
];
|
||||
}
|
||||
|
||||
return DBA::exists('group', $condition);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Create a new contact group
|
||||
*
|
||||
* Note: If we found a deleted group with the same name, we restore it
|
||||
*
|
||||
* @param int $uid
|
||||
* @param int $uid
|
||||
* @param string $name
|
||||
* @return boolean
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function create($uid, $name)
|
||||
{
|
||||
$return = false;
|
||||
if (x($uid) && x($name)) {
|
||||
if (!empty($uid) && !empty($name)) {
|
||||
$gid = self::getIdByName($uid, $name); // check for dupes
|
||||
if ($gid !== false) {
|
||||
// This could be a problem.
|
||||
|
@ -58,10 +90,11 @@ class Group extends BaseObject
|
|||
/**
|
||||
* Update group information.
|
||||
*
|
||||
* @param int $id Group ID
|
||||
* @param string $name Group name
|
||||
* @param int $id Group ID
|
||||
* @param string $name Group name
|
||||
*
|
||||
* @return bool Was the update successful?
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function update($id, $name)
|
||||
{
|
||||
|
@ -73,17 +106,17 @@ class Group extends BaseObject
|
|||
*
|
||||
* @param int $cid
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function getIdsByContactId($cid)
|
||||
{
|
||||
$condition = ['contact-id' => $cid];
|
||||
$stmt = DBA::select('group_member', ['gid'], $condition);
|
||||
|
||||
$return = [];
|
||||
|
||||
$stmt = DBA::select('group_member', ['gid'], ['contact-id' => $cid]);
|
||||
while ($group = DBA::fetch($stmt)) {
|
||||
$return[] = $group['gid'];
|
||||
}
|
||||
DBA::close($stmt);
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
@ -94,9 +127,10 @@ class Group extends BaseObject
|
|||
* Count unread items of each groups of the local user
|
||||
*
|
||||
* @return array
|
||||
* 'id' => group id
|
||||
* 'name' => group name
|
||||
* 'count' => counted unseen group items
|
||||
* 'id' => group id
|
||||
* 'name' => group name
|
||||
* 'count' => counted unseen group items
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function countUnseen()
|
||||
{
|
||||
|
@ -123,9 +157,10 @@ class Group extends BaseObject
|
|||
*
|
||||
* Returns false if no group has been found.
|
||||
*
|
||||
* @param int $uid
|
||||
* @param int $uid
|
||||
* @param string $name
|
||||
* @return int|boolean
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function getIdByName($uid, $name)
|
||||
{
|
||||
|
@ -146,9 +181,11 @@ class Group extends BaseObject
|
|||
*
|
||||
* @param int $gid
|
||||
* @return boolean
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function remove($gid) {
|
||||
if (! $gid) {
|
||||
public static function remove($gid)
|
||||
{
|
||||
if (!$gid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -190,17 +227,19 @@ class Group extends BaseObject
|
|||
}
|
||||
|
||||
/**
|
||||
* @brief Mark a group as deleted based on its name
|
||||
* @brief Mark a group as deleted based on its name
|
||||
*
|
||||
* @deprecated Use Group::remove instead
|
||||
*
|
||||
* @param int $uid
|
||||
* @param int $uid
|
||||
* @param string $name
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
* @deprecated Use Group::remove instead
|
||||
*
|
||||
*/
|
||||
public static function removeByName($uid, $name) {
|
||||
public static function removeByName($uid, $name)
|
||||
{
|
||||
$return = false;
|
||||
if (x($uid) && x($name)) {
|
||||
if (!empty($uid) && !empty($name)) {
|
||||
$gid = self::getIdByName($uid, $name);
|
||||
|
||||
$return = self::remove($gid);
|
||||
|
@ -215,6 +254,7 @@ class Group extends BaseObject
|
|||
* @param int $gid
|
||||
* @param int $cid
|
||||
* @return boolean
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function addMember($gid, $cid)
|
||||
{
|
||||
|
@ -239,6 +279,7 @@ class Group extends BaseObject
|
|||
* @param int $gid
|
||||
* @param int $cid
|
||||
* @return boolean
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function removeMember($gid, $cid)
|
||||
{
|
||||
|
@ -252,14 +293,15 @@ class Group extends BaseObject
|
|||
}
|
||||
|
||||
/**
|
||||
* @brief Removes a contact from a group based on its name
|
||||
* @brief Removes a contact from a group based on its name
|
||||
*
|
||||
* @param int $uid
|
||||
* @param string $name
|
||||
* @param int $cid
|
||||
* @return boolean
|
||||
* @throws \Exception
|
||||
* @deprecated Use Group::removeMember instead
|
||||
*
|
||||
* @param int $uid
|
||||
* @param string $name
|
||||
* @param int $cid
|
||||
* @return boolean
|
||||
*/
|
||||
public static function removeMemberByName($uid, $name, $cid)
|
||||
{
|
||||
|
@ -273,22 +315,55 @@ class Group extends BaseObject
|
|||
/**
|
||||
* @brief Returns the combined list of contact ids from a group id list
|
||||
*
|
||||
* @param array $group_ids
|
||||
* @param int $uid
|
||||
* @param array $group_ids
|
||||
* @param boolean $check_dead
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function expand($group_ids, $check_dead = false)
|
||||
public static function expand($uid, array $group_ids, $check_dead = false)
|
||||
{
|
||||
if (!is_array($group_ids) || !count($group_ids)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$stmt = DBA::select('group_member', ['contact-id'], ['gid' => $group_ids]);
|
||||
|
||||
$return = [];
|
||||
while($group_member = DBA::fetch($stmt)) {
|
||||
|
||||
$key = array_search(self::FOLLOWERS, $group_ids);
|
||||
if ($key !== false) {
|
||||
$followers = Contact::selectToArray(['id'], [
|
||||
'uid' => $uid,
|
||||
'rel' => [Contact::FOLLOWER, Contact::FRIEND],
|
||||
'network' => Protocol::SUPPORT_PRIVATE,
|
||||
]);
|
||||
|
||||
foreach ($followers as $follower) {
|
||||
$return[] = $follower['id'];
|
||||
}
|
||||
|
||||
unset($group_ids[$key]);
|
||||
}
|
||||
|
||||
$key = array_search(self::MUTUALS, $group_ids);
|
||||
if ($key !== false) {
|
||||
$mutuals = Contact::selectToArray(['id'], [
|
||||
'uid' => $uid,
|
||||
'rel' => [Contact::FRIEND],
|
||||
'network' => Protocol::SUPPORT_PRIVATE,
|
||||
]);
|
||||
|
||||
foreach ($mutuals as $mutual) {
|
||||
$return[] = $mutual['id'];
|
||||
}
|
||||
|
||||
unset($group_ids[$key]);
|
||||
}
|
||||
|
||||
$stmt = DBA::select('group_member', ['contact-id'], ['gid' => $group_ids]);
|
||||
while ($group_member = DBA::fetch($stmt)) {
|
||||
$return[] = $group_member['contact-id'];
|
||||
}
|
||||
DBA::close($stmt);
|
||||
|
||||
if ($check_dead) {
|
||||
Contact::pruneUnavailable($return);
|
||||
|
@ -300,17 +375,14 @@ class Group extends BaseObject
|
|||
/**
|
||||
* @brief Returns a templated group selection list
|
||||
*
|
||||
* @param int $uid
|
||||
* @param int $gid An optional pre-selected group
|
||||
* @param int $uid
|
||||
* @param int $gid An optional pre-selected group
|
||||
* @param string $label An optional label of the list
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function displayGroupSelection($uid, $gid = 0, $label = '')
|
||||
{
|
||||
$o = '';
|
||||
|
||||
$stmt = DBA::select('group', [], ['deleted' => 0, 'uid' => $uid], ['order' => ['name']]);
|
||||
|
||||
$display_groups = [
|
||||
[
|
||||
'name' => '',
|
||||
|
@ -318,6 +390,8 @@ class Group extends BaseObject
|
|||
'selected' => ''
|
||||
]
|
||||
];
|
||||
|
||||
$stmt = DBA::select('group', [], ['deleted' => 0, 'uid' => $uid], ['order' => ['name']]);
|
||||
while ($group = DBA::fetch($stmt)) {
|
||||
$display_groups[] = [
|
||||
'name' => $group['name'],
|
||||
|
@ -325,13 +399,15 @@ class Group extends BaseObject
|
|||
'selected' => $gid == $group['id'] ? 'true' : ''
|
||||
];
|
||||
}
|
||||
logger('groups: ' . print_r($display_groups, true));
|
||||
DBA::close($stmt);
|
||||
|
||||
Logger::info('Got groups', $display_groups);
|
||||
|
||||
if ($label == '') {
|
||||
$label = L10n::t('Default privacy group for new contacts');
|
||||
}
|
||||
|
||||
$o = replace_macros(get_markup_template('group_selection.tpl'), [
|
||||
$o = Renderer::replaceMacros(Renderer::getMarkupTemplate('group_selection.tpl'), [
|
||||
'$label' => $label,
|
||||
'$groups' => $display_groups
|
||||
]);
|
||||
|
@ -344,17 +420,16 @@ class Group extends BaseObject
|
|||
* @param string $every
|
||||
* @param string $each
|
||||
* @param string $editmode
|
||||
* 'standard' => include link 'Edit groups'
|
||||
* 'extended' => include link 'Create new group'
|
||||
* 'full' => include link 'Create new group' and provide for each group a link to edit this group
|
||||
* @param int $group_id
|
||||
* @param int $cid
|
||||
* 'standard' => include link 'Edit groups'
|
||||
* 'extended' => include link 'Create new group'
|
||||
* 'full' => include link 'Create new group' and provide for each group a link to edit this group
|
||||
* @param string $group_id
|
||||
* @param int $cid
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function sidebarWidget($every = 'contact', $each = 'group', $editmode = 'standard', $group_id = '', $cid = 0)
|
||||
{
|
||||
$o = '';
|
||||
|
||||
if (!local_user()) {
|
||||
return '';
|
||||
}
|
||||
|
@ -368,13 +443,12 @@ class Group extends BaseObject
|
|||
]
|
||||
];
|
||||
|
||||
$stmt = DBA::select('group', [], ['deleted' => 0, 'uid' => local_user()], ['order' => ['name']]);
|
||||
|
||||
$member_of = [];
|
||||
if ($cid) {
|
||||
$member_of = self::getIdsByContactId($cid);
|
||||
}
|
||||
|
||||
$stmt = DBA::select('group', [], ['deleted' => 0, 'uid' => local_user()], ['order' => ['name']]);
|
||||
while ($group = DBA::fetch($stmt)) {
|
||||
$selected = (($group_id == $group['id']) ? ' group-selected' : '');
|
||||
|
||||
|
@ -397,9 +471,15 @@ class Group extends BaseObject
|
|||
'ismember' => in_array($group['id'], $member_of),
|
||||
];
|
||||
}
|
||||
DBA::close($stmt);
|
||||
|
||||
$tpl = get_markup_template('group_side.tpl');
|
||||
$o = replace_macros($tpl, [
|
||||
// Don't show the groups on the network page when there is only one
|
||||
if ((count($display_groups) <= 2) && ($each == 'network')) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$tpl = Renderer::getMarkupTemplate('group_side.tpl');
|
||||
$o = Renderer::replaceMacros($tpl, [
|
||||
'$add' => L10n::t('add'),
|
||||
'$title' => L10n::t('Groups'),
|
||||
'$groups' => $display_groups,
|
||||
|
@ -414,7 +494,6 @@ class Group extends BaseObject
|
|||
'$form_security_token' => BaseModule::getFormSecurityToken('group_edit'),
|
||||
]);
|
||||
|
||||
|
||||
return $o;
|
||||
}
|
||||
}
|
||||
|
|
1314
src/Model/Item.php
1314
src/Model/Item.php
File diff suppressed because it is too large
Load diff
|
@ -11,10 +11,6 @@ use Friendica\Content\Text;
|
|||
use Friendica\Core\PConfig;
|
||||
use Friendica\Core\Protocol;
|
||||
|
||||
require_once 'boot.php';
|
||||
require_once 'include/items.php';
|
||||
require_once 'include/text.php';
|
||||
|
||||
class ItemContent extends BaseObject
|
||||
{
|
||||
/**
|
||||
|
@ -26,9 +22,10 @@ class ItemContent extends BaseObject
|
|||
* @param int $htmlmode This controls the behavior of the BBCode conversion
|
||||
* @param string $target_network Name of the network where the post should go to.
|
||||
*
|
||||
* @see \Friendica\Content\Text\BBCode::getAttachedData
|
||||
*
|
||||
* @return array Same array structure than \Friendica\Content\Text\BBCode::getAttachedData
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
* @see \Friendica\Content\Text\BBCode::getAttachedData
|
||||
*
|
||||
*/
|
||||
public static function getPlaintextPost($item, $limit = 0, $includedlinks = false, $htmlmode = 2, $target_network = '')
|
||||
{
|
||||
|
|
177
src/Model/ItemDeliveryData.php
Normal file
177
src/Model/ItemDeliveryData.php
Normal file
|
@ -0,0 +1,177 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file src/Model/ItemDeliveryData.php
|
||||
*/
|
||||
|
||||
namespace Friendica\Model;
|
||||
|
||||
use Friendica\Database\DBA;
|
||||
use \BadMethodCallException;
|
||||
|
||||
class ItemDeliveryData
|
||||
{
|
||||
const LEGACY_FIELD_LIST = [
|
||||
// Legacy fields moved from item table
|
||||
'postopts',
|
||||
'inform',
|
||||
];
|
||||
|
||||
const FIELD_LIST = [
|
||||
// New delivery fields with virtual field name in item fields
|
||||
'queue_count' => 'delivery_queue_count',
|
||||
'queue_done' => 'delivery_queue_done',
|
||||
'queue_failed' => 'delivery_queue_failed',
|
||||
];
|
||||
|
||||
const ACTIVITYPUB = 1;
|
||||
const DFRN = 2;
|
||||
const LEGACY_DFRN = 3;
|
||||
const DIASPORA = 4;
|
||||
const OSTATUS = 5;
|
||||
|
||||
/**
|
||||
* Extract delivery data from the provided item fields
|
||||
*
|
||||
* @param array $fields
|
||||
* @return array
|
||||
*/
|
||||
public static function extractFields(array &$fields)
|
||||
{
|
||||
$delivery_data = [];
|
||||
foreach (array_merge(ItemDeliveryData::FIELD_LIST, ItemDeliveryData::LEGACY_FIELD_LIST) as $key => $field) {
|
||||
if (is_int($key) && isset($fields[$field])) {
|
||||
// Legacy field moved from item table
|
||||
$delivery_data[$field] = $fields[$field];
|
||||
$fields[$field] = null;
|
||||
} elseif (isset($fields[$field])) {
|
||||
// New delivery field with virtual field name in item fields
|
||||
$delivery_data[$key] = $fields[$field];
|
||||
unset($fields[$field]);
|
||||
}
|
||||
}
|
||||
|
||||
return $delivery_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments the queue_done for the given item ID.
|
||||
*
|
||||
* Avoids racing condition between multiple delivery threads.
|
||||
*
|
||||
* @param integer $item_id
|
||||
* @param integer $protocol
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function incrementQueueDone($item_id, $protocol = 0)
|
||||
{
|
||||
$sql = '';
|
||||
|
||||
switch ($protocol) {
|
||||
case self::ACTIVITYPUB:
|
||||
$sql = ", `activitypub` = `activitypub` + 1";
|
||||
break;
|
||||
case self::DFRN:
|
||||
$sql = ", `dfrn` = `dfrn` + 1";
|
||||
break;
|
||||
case self::LEGACY_DFRN:
|
||||
$sql = ", `legacy_dfrn` = `legacy_dfrn` + 1";
|
||||
break;
|
||||
case self::DIASPORA:
|
||||
$sql = ", `diaspora` = `diaspora` + 1";
|
||||
break;
|
||||
case self::OSTATUS:
|
||||
$sql = ", `ostatus` = `ostatus` + 1";
|
||||
break;
|
||||
}
|
||||
|
||||
return DBA::e('UPDATE `item-delivery-data` SET `queue_done` = `queue_done` + 1' . $sql . ' WHERE `iid` = ?', $item_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments the queue_failed for the given item ID.
|
||||
*
|
||||
* Avoids racing condition between multiple delivery threads.
|
||||
*
|
||||
* @param integer $item_id
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function incrementQueueFailed($item_id)
|
||||
{
|
||||
return DBA::e('UPDATE `item-delivery-data` SET `queue_failed` = `queue_failed` + 1 WHERE `iid` = ?', $item_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments the queue_count for the given item ID.
|
||||
*
|
||||
* @param integer $item_id
|
||||
* @param integer $increment
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function incrementQueueCount(int $item_id, int $increment = 1)
|
||||
{
|
||||
return DBA::e('UPDATE `item-delivery-data` SET `queue_count` = `queue_count` + ? WHERE `iid` = ?', $increment, $item_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a new item delivery data entry
|
||||
*
|
||||
* @param integer $item_id
|
||||
* @param array $fields
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function insert($item_id, array $fields)
|
||||
{
|
||||
if (empty($item_id)) {
|
||||
throw new BadMethodCallException('Empty item_id');
|
||||
}
|
||||
|
||||
$fields['iid'] = $item_id;
|
||||
|
||||
return DBA::insert('item-delivery-data', $fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update/Insert item delivery data
|
||||
*
|
||||
* If you want to update queue_done, please use incrementQueueDone instead.
|
||||
*
|
||||
* @param integer $item_id
|
||||
* @param array $fields
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function update($item_id, array $fields)
|
||||
{
|
||||
if (empty($item_id)) {
|
||||
throw new BadMethodCallException('Empty item_id');
|
||||
}
|
||||
|
||||
if (empty($fields)) {
|
||||
// Nothing to do, update successful
|
||||
return true;
|
||||
}
|
||||
|
||||
return DBA::update('item-delivery-data', $fields, ['iid' => $item_id], true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete item delivery data
|
||||
*
|
||||
* @param integer $item_id
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function delete($item_id)
|
||||
{
|
||||
if (empty($item_id)) {
|
||||
throw new BadMethodCallException('Empty item_id');
|
||||
}
|
||||
|
||||
return DBA::delete('item-delivery-data', ['iid' => $item_id]);
|
||||
}
|
||||
}
|
|
@ -9,8 +9,6 @@ namespace Friendica\Model;
|
|||
use Friendica\BaseObject;
|
||||
use Friendica\Database\DBA;
|
||||
|
||||
require_once 'boot.php';
|
||||
|
||||
class ItemURI extends BaseObject
|
||||
{
|
||||
/**
|
||||
|
@ -19,14 +17,18 @@ class ItemURI extends BaseObject
|
|||
* @param array $fields Item-uri fields
|
||||
*
|
||||
* @return integer item-uri id
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function insert($fields)
|
||||
{
|
||||
if (!DBA::exists('item-uri', ['uri' => $fields['uri']])) {
|
||||
// If the URI gets too long we only take the first parts and hope for best
|
||||
$uri = substr($fields['uri'], 0, 255);
|
||||
|
||||
if (!DBA::exists('item-uri', ['uri' => $uri])) {
|
||||
DBA::insert('item-uri', $fields, true);
|
||||
}
|
||||
|
||||
$itemuri = DBA::selectFirst('item-uri', ['id'], ['uri' => $fields['uri']]);
|
||||
$itemuri = DBA::selectFirst('item-uri', ['id'], ['uri' => $uri]);
|
||||
|
||||
if (!DBA::isResult($itemuri)) {
|
||||
// This shouldn't happen
|
||||
|
@ -42,9 +44,13 @@ class ItemURI extends BaseObject
|
|||
* @param string $uri
|
||||
*
|
||||
* @return integer item-uri id
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function getIdByURI($uri)
|
||||
{
|
||||
// If the URI gets too long we only take the first parts and hope for best
|
||||
$uri = substr($uri, 0, 255);
|
||||
|
||||
$itemuri = DBA::selectFirst('item-uri', ['id'], ['uri' => $uri]);
|
||||
|
||||
if (!DBA::isResult($itemuri)) {
|
||||
|
|
|
@ -6,19 +6,91 @@
|
|||
namespace Friendica\Model;
|
||||
|
||||
use Friendica\Core\L10n;
|
||||
use Friendica\Core\Logger;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Core\Worker;
|
||||
use Friendica\Model\Item;
|
||||
use Friendica\Model\Photo;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\Network\Probe;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
|
||||
require_once 'include/dba.php';
|
||||
use Friendica\Worker\Delivery;
|
||||
|
||||
/**
|
||||
* Class to handle private messages
|
||||
*/
|
||||
class Mail
|
||||
{
|
||||
/**
|
||||
* Insert received private message
|
||||
*
|
||||
* @param array $msg
|
||||
* @return int|boolean Message ID or false on error
|
||||
*/
|
||||
public static function insert($msg)
|
||||
{
|
||||
$user = User::getById($msg['uid']);
|
||||
|
||||
if (!isset($msg['reply'])) {
|
||||
$msg['reply'] = DBA::exists('mail', ['parent-uri' => $msg['parent-uri']]);
|
||||
}
|
||||
|
||||
if (empty($msg['convid'])) {
|
||||
$mail = DBA::selectFirst('mail', ['convid'], ["`convid` != 0 AND `parent-uri` = ?", $msg['parent-uri']]);
|
||||
if (DBA::isResult($mail)) {
|
||||
$msg['convid'] = $mail['convid'];
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($msg['guid'])) {
|
||||
$host = parse_url($msg['from-url'], PHP_URL_HOST);
|
||||
$msg['guid'] = Item::guidFromUri($msg['uri'], $host);
|
||||
}
|
||||
|
||||
$msg['created'] = (!empty($msg['created']) ? DateTimeFormat::utc($msg['created']) : DateTimeFormat::utcNow());
|
||||
|
||||
DBA::lock('mail');
|
||||
|
||||
if (DBA::exists('mail', ['uri' => $msg['uri'], 'uid' => $msg['uid']])) {
|
||||
DBA::unlock();
|
||||
Logger::info('duplicate message already delivered.');
|
||||
return false;
|
||||
}
|
||||
|
||||
DBA::insert('mail', $msg);
|
||||
|
||||
$msg['id'] = DBA::lastInsertId();
|
||||
|
||||
DBA::unlock();
|
||||
|
||||
if (!empty($msg['convid'])) {
|
||||
DBA::update('conv', ['updated' => DateTimeFormat::utcNow()], ['id' => $msg['convid']]);
|
||||
}
|
||||
|
||||
// send notifications.
|
||||
$notif_params = [
|
||||
'type' => NOTIFY_MAIL,
|
||||
'notify_flags' => $user['notify-flags'],
|
||||
'language' => $user['language'],
|
||||
'to_name' => $user['username'],
|
||||
'to_email' => $user['email'],
|
||||
'uid' => $user['uid'],
|
||||
'item' => $msg,
|
||||
'parent' => 0,
|
||||
'source_name' => $msg['from-name'],
|
||||
'source_link' => $msg['from-url'],
|
||||
'source_photo' => $msg['from-photo'],
|
||||
'verb' => ACTIVITY_POST,
|
||||
'otype' => 'mail'
|
||||
];
|
||||
|
||||
notification($notif_params);
|
||||
|
||||
Logger::info('Mail is processed, notification was sent.', ['id' => $msg['id'], 'uri' => $msg['uri']]);
|
||||
|
||||
return $msg['id'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Send private message
|
||||
*
|
||||
|
@ -26,10 +98,12 @@ class Mail
|
|||
* @param string $body message body, default empty
|
||||
* @param string $subject message subject, default empty
|
||||
* @param string $replyto reply to
|
||||
* @return int
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public static function send($recipient = 0, $body = '', $subject = '', $replyto = '')
|
||||
{
|
||||
$a = get_app();
|
||||
$a = \get_app();
|
||||
|
||||
if (!$recipient) {
|
||||
return -1;
|
||||
|
@ -46,8 +120,10 @@ class Mail
|
|||
return -2;
|
||||
}
|
||||
|
||||
Photo::setPermissionFromBody($body, local_user(), $me['id'], '<' . $contact['id'] . '>', '', '', '');
|
||||
|
||||
$guid = System::createUUID();
|
||||
$uri = 'urn:X-dfrn:' . System::baseUrl() . ':' . local_user() . ':' . $guid;
|
||||
$uri = Item::newURI(local_user(), $guid);
|
||||
|
||||
$convid = 0;
|
||||
$reply = false;
|
||||
|
@ -87,7 +163,7 @@ class Mail
|
|||
}
|
||||
|
||||
if (!$convid) {
|
||||
logger('send message: conversation not found.');
|
||||
Logger::log('send message: conversation not found.');
|
||||
return -4;
|
||||
}
|
||||
|
||||
|
@ -142,13 +218,13 @@ class Mail
|
|||
}
|
||||
$image_uri = substr($image, strrpos($image, '/') + 1);
|
||||
$image_uri = substr($image_uri, 0, strpos($image_uri, '-'));
|
||||
DBA::update('photo', ['allow-cid' => '<' . $recipient . '>'], ['resource-id' => $image_uri, 'album' => 'Wall Photos', 'uid' => local_user()]);
|
||||
Photo::update(['allow-cid' => '<' . $recipient . '>'], ['resource-id' => $image_uri, 'album' => 'Wall Photos', 'uid' => local_user()]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($post_id) {
|
||||
Worker::add(PRIORITY_HIGH, "Notifier", "mail", $post_id);
|
||||
Worker::add(PRIORITY_HIGH, "Notifier", Delivery::MAIL, $post_id);
|
||||
return intval($post_id);
|
||||
} else {
|
||||
return -3;
|
||||
|
@ -156,12 +232,15 @@ class Mail
|
|||
}
|
||||
|
||||
/**
|
||||
* @param string $recipient recipient, default empty
|
||||
* @param array $recipient recipient, default empty
|
||||
* @param string $body message body, default empty
|
||||
* @param string $subject message subject, default empty
|
||||
* @param string $replyto reply to, default empty
|
||||
* @return int
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
public static function sendWall($recipient = '', $body = '', $subject = '', $replyto = '')
|
||||
public static function sendWall(array $recipient = [], $body = '', $subject = '', $replyto = '')
|
||||
{
|
||||
if (!$recipient) {
|
||||
return -1;
|
||||
|
@ -172,7 +251,7 @@ class Mail
|
|||
}
|
||||
|
||||
$guid = System::createUUID();
|
||||
$uri = 'urn:X-dfrn:' . System::baseUrl() . ':' . local_user() . ':' . $guid;
|
||||
$uri = Item::newURI(local_user(), $guid);
|
||||
|
||||
$me = Probe::uri($replyto);
|
||||
|
||||
|
@ -200,7 +279,7 @@ class Mail
|
|||
}
|
||||
|
||||
if (!$convid) {
|
||||
logger('send message: conversation not found.');
|
||||
Logger::log('send message: conversation not found.');
|
||||
return -4;
|
||||
}
|
||||
|
||||
|
|
64
src/Model/Nodeinfo.php
Normal file
64
src/Model/Nodeinfo.php
Normal file
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Model;
|
||||
|
||||
use Friendica\BaseObject;
|
||||
use Friendica\Core\Addon;
|
||||
use Friendica\Database\DBA;
|
||||
|
||||
/**
|
||||
* Model interaction for the nodeinfo
|
||||
*/
|
||||
class Nodeinfo extends BaseObject
|
||||
{
|
||||
/**
|
||||
* Updates the info about the current node
|
||||
*
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public static function update()
|
||||
{
|
||||
$app = self::getApp();
|
||||
$config = $app->getConfig();
|
||||
$logger = $app->getLogger();
|
||||
|
||||
// If the addon 'statistics_json' is enabled then disable it and activate nodeinfo.
|
||||
if (Addon::isEnabled('statistics_json')) {
|
||||
$config->set('system', 'nodeinfo', true);
|
||||
|
||||
$addon = 'statistics_json';
|
||||
$addons = $config->get('system', 'addon');
|
||||
|
||||
if ($addons) {
|
||||
$addons_arr = explode(',', str_replace(' ', '', $addons));
|
||||
|
||||
$idx = array_search($addon, $addons_arr);
|
||||
if ($idx !== false) {
|
||||
unset($addons_arr[$idx]);
|
||||
Addon::uninstall($addon);
|
||||
$config->set('system', 'addon', implode(', ', $addons_arr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($config->get('system', 'nodeinfo'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
$userStats = User::getStatistics();
|
||||
|
||||
$config->set('nodeinfo', 'total_users', $userStats['total_users']);
|
||||
$config->set('nodeinfo', 'active_users_halfyear', $userStats['active_users_halfyear']);
|
||||
$config->set('nodeinfo', 'active_users_monthly', $userStats['active_users_monthly']);
|
||||
|
||||
$logger->debug('user statistics', $userStats);
|
||||
|
||||
$local_posts = DBA::count('thread', ["`wall` AND NOT `deleted` AND `uid` != 0"]);
|
||||
$config->set('nodeinfo', 'local_posts', $local_posts);
|
||||
$logger->debug('thread statistics', ['local_posts' => $local_posts]);
|
||||
|
||||
$local_comments = DBA::count('item', ["`origin` AND `id` != `parent` AND NOT `deleted` AND `uid` != 0"]);
|
||||
$config->set('nodeinfo', 'local_comments', $local_comments);
|
||||
$logger->debug('item statistics', ['local_comments' => $local_comments]);
|
||||
}
|
||||
}
|
|
@ -16,12 +16,13 @@ class OpenWebAuthToken
|
|||
/**
|
||||
* Create an entry in the 'openwebauth-token' table.
|
||||
*
|
||||
* @param string $type Verify type.
|
||||
* @param int $uid The user ID.
|
||||
* @param string $type Verify type.
|
||||
* @param int $uid The user ID.
|
||||
* @param string $token
|
||||
* @param string $meta
|
||||
*
|
||||
* @return boolean
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function create($type, $uid, $token, $meta)
|
||||
{
|
||||
|
@ -38,11 +39,12 @@ class OpenWebAuthToken
|
|||
/**
|
||||
* Get the "meta" field of an entry in the openwebauth-token table.
|
||||
*
|
||||
* @param string $type Verify type.
|
||||
* @param int $uid The user ID.
|
||||
* @param string $type Verify type.
|
||||
* @param int $uid The user ID.
|
||||
* @param string $token
|
||||
*
|
||||
* @return string|boolean The meta enry or false if not found.
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function getMeta($type, $uid, $token)
|
||||
{
|
||||
|
@ -62,6 +64,7 @@ class OpenWebAuthToken
|
|||
*
|
||||
* @param string $type Verify type.
|
||||
* @param string $interval SQL compatible time interval
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function purge($type, $interval)
|
||||
{
|
||||
|
|
|
@ -7,8 +7,6 @@ namespace Friendica\Model;
|
|||
use Friendica\BaseObject;
|
||||
use Friendica\Database\DBA;
|
||||
|
||||
require_once 'include/dba.php';
|
||||
|
||||
/**
|
||||
* @brief functions for interacting with the permission set of an object (item, photo, event, ...)
|
||||
*/
|
||||
|
@ -18,7 +16,8 @@ class PermissionSet extends BaseObject
|
|||
* Fetch the id of a given permission set. Generate a new one when needed
|
||||
*
|
||||
* @param array $postarray The array from an item, picture or event post
|
||||
* @return id
|
||||
* @return int id
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function fetchIDForPost(&$postarray)
|
||||
{
|
||||
|
@ -68,20 +67,20 @@ class PermissionSet extends BaseObject
|
|||
*
|
||||
* @param integer $uid User id whom the items belong
|
||||
* @param integer $contact_id Contact id of the visitor
|
||||
* @param array $groups Possibly previously fetched group ids for that contact
|
||||
*
|
||||
* @return array of permission set ids.
|
||||
* @throws \Exception
|
||||
*/
|
||||
|
||||
static public function get($uid, $contact_id, $groups = null)
|
||||
static public function get($uid, $contact_id)
|
||||
{
|
||||
if (empty($groups) && DBA::exists('contact', ['id' => $contact_id, 'uid' => $uid, 'blocked' => false])) {
|
||||
if (DBA::exists('contact', ['id' => $contact_id, 'uid' => $uid, 'blocked' => false])) {
|
||||
$groups = Group::getIdsByContactId($contact_id);
|
||||
}
|
||||
|
||||
if (empty($groups) || !is_array($groups)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$group_str = '<<>>'; // should be impossible to match
|
||||
|
||||
foreach ($groups as $g) {
|
||||
|
@ -90,11 +89,9 @@ class PermissionSet extends BaseObject
|
|||
|
||||
$contact_str = '<' . $contact_id . '>';
|
||||
|
||||
$condition = ["`uid` = ? AND (`allow_cid` = '' OR`allow_cid` REGEXP ?)
|
||||
AND (`deny_cid` = '' OR NOT `deny_cid` REGEXP ?)
|
||||
AND (`allow_gid` = '' OR `allow_gid` REGEXP ?)
|
||||
AND (`deny_gid` = '' OR NOT `deny_gid` REGEXP ?)",
|
||||
$uid, $contact_str, $contact_str, $group_str, $group_str];
|
||||
$condition = ["`uid` = ? AND (NOT (`deny_cid` REGEXP ? OR deny_gid REGEXP ?)
|
||||
AND (allow_cid REGEXP ? OR allow_gid REGEXP ? OR (allow_cid = '' AND allow_gid = '')))",
|
||||
$uid, $contact_str, $group_str, $contact_str, $group_str];
|
||||
|
||||
$ret = DBA::select('permissionset', ['id'], $condition);
|
||||
$set = [];
|
||||
|
|
|
@ -6,99 +6,405 @@
|
|||
*/
|
||||
namespace Friendica\Model;
|
||||
|
||||
use Friendica\BaseObject;
|
||||
use Friendica\Core\Cache;
|
||||
use Friendica\Core\Config;
|
||||
use Friendica\Core\L10n;
|
||||
use Friendica\Core\Logger;
|
||||
use Friendica\Core\StorageManager;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\Database\DBStructure;
|
||||
use Friendica\Model\Storage\IStorage;
|
||||
use Friendica\Object\Image;
|
||||
use Friendica\Protocol\DFRN;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
use Friendica\Util\Network;
|
||||
use Friendica\Util\Security;
|
||||
use Friendica\Util\Strings;
|
||||
|
||||
require_once 'include/dba.php';
|
||||
require_once "include/dba.php";
|
||||
|
||||
/**
|
||||
* Class to handle photo dabatase table
|
||||
*/
|
||||
class Photo
|
||||
class Photo extends BaseObject
|
||||
{
|
||||
/**
|
||||
* @param Image $Image image
|
||||
* @param integer $uid uid
|
||||
* @param integer $cid cid
|
||||
* @param integer $rid rid
|
||||
* @param string $filename filename
|
||||
* @param string $album album name
|
||||
* @param integer $scale scale
|
||||
* @param integer $profile optional, default = 0
|
||||
* @param string $allow_cid optional, default = ''
|
||||
* @param string $allow_gid optional, default = ''
|
||||
* @param string $deny_cid optional, default = ''
|
||||
* @param string $deny_gid optional, default = ''
|
||||
* @param string $desc optional, default = ''
|
||||
* @return object
|
||||
* @brief Select rows from the photo table and returns them as array
|
||||
*
|
||||
* @param array $fields Array of selected fields, empty for all
|
||||
* @param array $conditions Array of fields for conditions
|
||||
* @param array $params Array of several parameters
|
||||
*
|
||||
* @return boolean|array
|
||||
*
|
||||
* @throws \Exception
|
||||
* @see \Friendica\Database\DBA::selectToArray
|
||||
*/
|
||||
public static function store(Image $Image, $uid, $cid, $rid, $filename, $album, $scale, $profile = 0, $allow_cid = '', $allow_gid = '', $deny_cid = '', $deny_gid = '', $desc = '')
|
||||
public static function selectToArray(array $fields = [], array $conditions = [], array $params = [])
|
||||
{
|
||||
$photo = DBA::selectFirst('photo', ['guid'], ["`resource-id` = ? AND `guid` != ?", $rid, '']);
|
||||
if (empty($fields)) {
|
||||
$fields = self::getFields();
|
||||
}
|
||||
|
||||
return DBA::selectToArray('photo', $fields, $conditions, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Retrieve a single record from the photo table
|
||||
*
|
||||
* @param array $fields Array of selected fields, empty for all
|
||||
* @param array $conditions Array of fields for conditions
|
||||
* @param array $params Array of several parameters
|
||||
*
|
||||
* @return bool|array
|
||||
*
|
||||
* @throws \Exception
|
||||
* @see \Friendica\Database\DBA::select
|
||||
*/
|
||||
public static function selectFirst(array $fields = [], array $conditions = [], array $params = [])
|
||||
{
|
||||
if (empty($fields)) {
|
||||
$fields = self::getFields();
|
||||
}
|
||||
|
||||
return DBA::selectFirst("photo", $fields, $conditions, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get photos for user id
|
||||
*
|
||||
* @param integer $uid User id
|
||||
* @param string $resourceid Rescource ID of the photo
|
||||
* @param array $conditions Array of fields for conditions
|
||||
* @param array $params Array of several parameters
|
||||
*
|
||||
* @return bool|array
|
||||
*
|
||||
* @throws \Exception
|
||||
* @see \Friendica\Database\DBA::select
|
||||
*/
|
||||
public static function getPhotosForUser($uid, $resourceid, array $conditions = [], array $params = [])
|
||||
{
|
||||
$conditions["resource-id"] = $resourceid;
|
||||
$conditions["uid"] = $uid;
|
||||
|
||||
return self::selectToArray([], $conditions, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get a photo for user id
|
||||
*
|
||||
* @param integer $uid User id
|
||||
* @param string $resourceid Rescource ID of the photo
|
||||
* @param integer $scale Scale of the photo. Defaults to 0
|
||||
* @param array $conditions Array of fields for conditions
|
||||
* @param array $params Array of several parameters
|
||||
*
|
||||
* @return bool|array
|
||||
*
|
||||
* @throws \Exception
|
||||
* @see \Friendica\Database\DBA::select
|
||||
*/
|
||||
public static function getPhotoForUser($uid, $resourceid, $scale = 0, array $conditions = [], array $params = [])
|
||||
{
|
||||
$conditions["resource-id"] = $resourceid;
|
||||
$conditions["uid"] = $uid;
|
||||
$conditions["scale"] = $scale;
|
||||
|
||||
return self::selectFirst([], $conditions, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get a single photo given resource id and scale
|
||||
*
|
||||
* This method checks for permissions. Returns associative array
|
||||
* on success, "no sign" image info, if user has no permission,
|
||||
* false if photo does not exists
|
||||
*
|
||||
* @param string $resourceid Rescource ID of the photo
|
||||
* @param integer $scale Scale of the photo. Defaults to 0
|
||||
*
|
||||
* @return boolean|array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function getPhoto($resourceid, $scale = 0)
|
||||
{
|
||||
$r = self::selectFirst(["uid"], ["resource-id" => $resourceid]);
|
||||
if (!DBA::isResult($r)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$uid = $r["uid"];
|
||||
|
||||
$sql_acl = Security::getPermissionsSQLByUserId($uid);
|
||||
|
||||
$conditions = ["`resource-id` = ? AND `scale` <= ? " . $sql_acl, $resourceid, $scale];
|
||||
$params = ["order" => ["scale" => true]];
|
||||
$photo = self::selectFirst([], $conditions, $params);
|
||||
|
||||
return $photo;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Check if photo with given conditions exists
|
||||
*
|
||||
* @param array $conditions Array of extra conditions
|
||||
*
|
||||
* @return boolean
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function exists(array $conditions)
|
||||
{
|
||||
return DBA::exists("photo", $conditions);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Get Image object for given row id. null if row id does not exist
|
||||
*
|
||||
* @param array $photo Photo data. Needs at least 'id', 'type', 'backend-class', 'backend-ref'
|
||||
*
|
||||
* @return \Friendica\Object\Image
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
public static function getImageForPhoto(array $photo)
|
||||
{
|
||||
$data = "";
|
||||
|
||||
if ($photo["backend-class"] == "") {
|
||||
// legacy data storage in "data" column
|
||||
$i = self::selectFirst(["data"], ["id" => $photo["id"]]);
|
||||
if ($i === false) {
|
||||
return null;
|
||||
}
|
||||
$data = $i["data"];
|
||||
} else {
|
||||
$backendClass = $photo["backend-class"];
|
||||
$backendRef = $photo["backend-ref"];
|
||||
$data = $backendClass::get($backendRef);
|
||||
}
|
||||
|
||||
if ($data === "") {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Image($data, $photo["type"]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return a list of fields that are associated with the photo table
|
||||
*
|
||||
* @return array field list
|
||||
* @throws \Exception
|
||||
*/
|
||||
private static function getFields()
|
||||
{
|
||||
$allfields = DBStructure::definition(self::getApp()->getBasePath(), false);
|
||||
$fields = array_keys($allfields["photo"]["fields"]);
|
||||
array_splice($fields, array_search("data", $fields), 1);
|
||||
return $fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Construct a photo array for a system resource image
|
||||
*
|
||||
* @param string $filename Image file name relative to code root
|
||||
* @param string $mimetype Image mime type. Defaults to "image/jpeg"
|
||||
*
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function createPhotoForSystemResource($filename, $mimetype = "image/jpeg")
|
||||
{
|
||||
$fields = self::getFields();
|
||||
$values = array_fill(0, count($fields), "");
|
||||
|
||||
$photo = array_combine($fields, $values);
|
||||
$photo["backend-class"] = Storage\SystemResource::class;
|
||||
$photo["backend-ref"] = $filename;
|
||||
$photo["type"] = $mimetype;
|
||||
$photo["cacheable"] = false;
|
||||
|
||||
return $photo;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief store photo metadata in db and binary in default backend
|
||||
*
|
||||
* @param Image $Image Image object with data
|
||||
* @param integer $uid User ID
|
||||
* @param integer $cid Contact ID
|
||||
* @param integer $rid Resource ID
|
||||
* @param string $filename Filename
|
||||
* @param string $album Album name
|
||||
* @param integer $scale Scale
|
||||
* @param integer $profile Is a profile image? optional, default = 0
|
||||
* @param string $allow_cid Permissions, allowed contacts. optional, default = ""
|
||||
* @param string $allow_gid Permissions, allowed groups. optional, default = ""
|
||||
* @param string $deny_cid Permissions, denied contacts.optional, default = ""
|
||||
* @param string $deny_gid Permissions, denied greoup.optional, default = ""
|
||||
* @param string $desc Photo caption. optional, default = ""
|
||||
*
|
||||
* @return boolean True on success
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public static function store(Image $Image, $uid, $cid, $rid, $filename, $album, $scale, $profile = 0, $allow_cid = "", $allow_gid = "", $deny_cid = "", $deny_gid = "", $desc = "")
|
||||
{
|
||||
$photo = self::selectFirst(["guid"], ["`resource-id` = ? AND `guid` != ?", $rid, ""]);
|
||||
if (DBA::isResult($photo)) {
|
||||
$guid = $photo['guid'];
|
||||
$guid = $photo["guid"];
|
||||
} else {
|
||||
$guid = System::createGUID();
|
||||
}
|
||||
|
||||
$existing_photo = DBA::selectFirst('photo', ['id'], ['resource-id' => $rid, 'uid' => $uid, 'contact-id' => $cid, 'scale' => $scale]);
|
||||
$existing_photo = self::selectFirst(["id", "created", "backend-class", "backend-ref"], ["resource-id" => $rid, "uid" => $uid, "contact-id" => $cid, "scale" => $scale]);
|
||||
$created = DateTimeFormat::utcNow();
|
||||
if (DBA::isResult($existing_photo)) {
|
||||
$created = $existing_photo["created"];
|
||||
}
|
||||
|
||||
// Get defined storage backend.
|
||||
// if no storage backend, we use old "data" column in photo table.
|
||||
// if is an existing photo, reuse same backend
|
||||
$data = "";
|
||||
$backend_ref = "";
|
||||
|
||||
/** @var IStorage $backend_class */
|
||||
if (DBA::isResult($existing_photo)) {
|
||||
$backend_ref = (string)$existing_photo["backend-ref"];
|
||||
$backend_class = (string)$existing_photo["backend-class"];
|
||||
} else {
|
||||
$backend_class = StorageManager::getBackend();
|
||||
}
|
||||
|
||||
if ($backend_class === "") {
|
||||
$data = $Image->asString();
|
||||
} else {
|
||||
$backend_ref = $backend_class::put($Image->asString(), $backend_ref);
|
||||
}
|
||||
|
||||
|
||||
$fields = [
|
||||
'uid' => $uid,
|
||||
'contact-id' => $cid,
|
||||
'guid' => $guid,
|
||||
'resource-id' => $rid,
|
||||
'created' => DateTimeFormat::utcNow(),
|
||||
'edited' => DateTimeFormat::utcNow(),
|
||||
'filename' => basename($filename),
|
||||
'type' => $Image->getType(),
|
||||
'album' => $album,
|
||||
'height' => $Image->getHeight(),
|
||||
'width' => $Image->getWidth(),
|
||||
'datasize' => strlen($Image->asString()),
|
||||
'data' => $Image->asString(),
|
||||
'scale' => $scale,
|
||||
'profile' => $profile,
|
||||
'allow_cid' => $allow_cid,
|
||||
'allow_gid' => $allow_gid,
|
||||
'deny_cid' => $deny_cid,
|
||||
'deny_gid' => $deny_gid,
|
||||
'desc' => $desc
|
||||
"uid" => $uid,
|
||||
"contact-id" => $cid,
|
||||
"guid" => $guid,
|
||||
"resource-id" => $rid,
|
||||
"created" => $created,
|
||||
"edited" => DateTimeFormat::utcNow(),
|
||||
"filename" => basename($filename),
|
||||
"type" => $Image->getType(),
|
||||
"album" => $album,
|
||||
"height" => $Image->getHeight(),
|
||||
"width" => $Image->getWidth(),
|
||||
"datasize" => strlen($Image->asString()),
|
||||
"data" => $data,
|
||||
"scale" => $scale,
|
||||
"profile" => $profile,
|
||||
"allow_cid" => $allow_cid,
|
||||
"allow_gid" => $allow_gid,
|
||||
"deny_cid" => $deny_cid,
|
||||
"deny_gid" => $deny_gid,
|
||||
"desc" => $desc,
|
||||
"backend-class" => $backend_class,
|
||||
"backend-ref" => $backend_ref
|
||||
];
|
||||
|
||||
if (DBA::isResult($existing_photo)) {
|
||||
$r = DBA::update('photo', $fields, ['id' => $existing_photo['id']]);
|
||||
$r = DBA::update("photo", $fields, ["id" => $existing_photo["id"]]);
|
||||
} else {
|
||||
$r = DBA::insert('photo', $fields);
|
||||
$r = DBA::insert("photo", $fields);
|
||||
}
|
||||
|
||||
return $r;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Delete info from table and data from storage
|
||||
*
|
||||
* @param array $conditions Field condition(s)
|
||||
* @param array $options Options array, Optional
|
||||
*
|
||||
* @return boolean
|
||||
*
|
||||
* @throws \Exception
|
||||
* @see \Friendica\Database\DBA::delete
|
||||
*/
|
||||
public static function delete(array $conditions, array $options = [])
|
||||
{
|
||||
// get photo to delete data info
|
||||
$photos = self::selectToArray(['backend-class', 'backend-ref'], $conditions);
|
||||
|
||||
foreach($photos as $photo) {
|
||||
/** @var IStorage $backend_class */
|
||||
$backend_class = (string)$photo["backend-class"];
|
||||
if ($backend_class !== "") {
|
||||
$backend_class::delete($photo["backend-ref"]);
|
||||
}
|
||||
}
|
||||
|
||||
return DBA::delete("photo", $conditions, $options);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Update a photo
|
||||
*
|
||||
* @param array $fields Contains the fields that are updated
|
||||
* @param array $conditions Condition array with the key values
|
||||
* @param Image $img Image to update. Optional, default null.
|
||||
* @param array|boolean $old_fields Array with the old field values that are about to be replaced (true = update on duplicate)
|
||||
*
|
||||
* @return boolean Was the update successfull?
|
||||
*
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
* @see \Friendica\Database\DBA::update
|
||||
*/
|
||||
public static function update($fields, $conditions, Image $img = null, array $old_fields = [])
|
||||
{
|
||||
if (!is_null($img)) {
|
||||
// get photo to update
|
||||
$photos = self::selectToArray(['backend-class', 'backend-ref'], $conditions);
|
||||
|
||||
foreach($photos as $photo) {
|
||||
/** @var IStorage $backend_class */
|
||||
$backend_class = (string)$photo["backend-class"];
|
||||
if ($backend_class !== "") {
|
||||
$fields["backend-ref"] = $backend_class::put($img->asString(), $photo["backend-ref"]);
|
||||
} else {
|
||||
$fields["data"] = $img->asString();
|
||||
}
|
||||
}
|
||||
$fields['updated'] = DateTimeFormat::utcNow();
|
||||
}
|
||||
|
||||
$fields['edited'] = DateTimeFormat::utcNow();
|
||||
|
||||
return DBA::update("photo", $fields, $conditions, $old_fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $image_url Remote URL
|
||||
* @param integer $uid user id
|
||||
* @param integer $cid contact id
|
||||
* @param boolean $quit_on_error optional, default false
|
||||
* @return array
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
public static function importProfilePhoto($image_url, $uid, $cid, $quit_on_error = false)
|
||||
{
|
||||
$thumb = '';
|
||||
$micro = '';
|
||||
$thumb = "";
|
||||
$micro = "";
|
||||
|
||||
$photo = DBA::selectFirst(
|
||||
'photo', ['resource-id'], ['uid' => $uid, 'contact-id' => $cid, 'scale' => 4, 'album' => 'Contact Photos']
|
||||
"photo", ["resource-id"], ["uid" => $uid, "contact-id" => $cid, "scale" => 4, "album" => "Contact Photos"]
|
||||
);
|
||||
if (x($photo['resource-id'])) {
|
||||
$hash = $photo['resource-id'];
|
||||
if (!empty($photo['resource-id'])) {
|
||||
$hash = $photo["resource-id"];
|
||||
} else {
|
||||
$hash = self::newResource();
|
||||
}
|
||||
|
@ -106,18 +412,27 @@ class Photo
|
|||
$photo_failure = false;
|
||||
|
||||
$filename = basename($image_url);
|
||||
$img_str = Network::fetchUrl($image_url, true);
|
||||
if (!empty($image_url)) {
|
||||
$ret = Network::curl($image_url, true);
|
||||
$img_str = $ret->getBody();
|
||||
$type = $ret->getContentType();
|
||||
} else {
|
||||
$img_str = '';
|
||||
}
|
||||
|
||||
if ($quit_on_error && ($img_str == "")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$type = Image::guessType($image_url, true);
|
||||
if (empty($type)) {
|
||||
$type = Image::guessType($image_url, true);
|
||||
}
|
||||
|
||||
$Image = new Image($img_str, $type);
|
||||
if ($Image->isValid()) {
|
||||
$Image->scaleToSquare(300);
|
||||
|
||||
$r = self::store($Image, $uid, $cid, $hash, $filename, 'Contact Photos', 4);
|
||||
$r = self::store($Image, $uid, $cid, $hash, $filename, "Contact Photos", 4);
|
||||
|
||||
if ($r === false) {
|
||||
$photo_failure = true;
|
||||
|
@ -125,7 +440,7 @@ class Photo
|
|||
|
||||
$Image->scaleDown(80);
|
||||
|
||||
$r = self::store($Image, $uid, $cid, $hash, $filename, 'Contact Photos', 5);
|
||||
$r = self::store($Image, $uid, $cid, $hash, $filename, "Contact Photos", 5);
|
||||
|
||||
if ($r === false) {
|
||||
$photo_failure = true;
|
||||
|
@ -133,32 +448,32 @@ class Photo
|
|||
|
||||
$Image->scaleDown(48);
|
||||
|
||||
$r = self::store($Image, $uid, $cid, $hash, $filename, 'Contact Photos', 6);
|
||||
$r = self::store($Image, $uid, $cid, $hash, $filename, "Contact Photos", 6);
|
||||
|
||||
if ($r === false) {
|
||||
$photo_failure = true;
|
||||
}
|
||||
|
||||
$suffix = '?ts=' . time();
|
||||
$suffix = "?ts=" . time();
|
||||
|
||||
$image_url = System::baseUrl() . '/photo/' . $hash . '-4.' . $Image->getExt() . $suffix;
|
||||
$thumb = System::baseUrl() . '/photo/' . $hash . '-5.' . $Image->getExt() . $suffix;
|
||||
$micro = System::baseUrl() . '/photo/' . $hash . '-6.' . $Image->getExt() . $suffix;
|
||||
$image_url = System::baseUrl() . "/photo/" . $hash . "-4." . $Image->getExt() . $suffix;
|
||||
$thumb = System::baseUrl() . "/photo/" . $hash . "-5." . $Image->getExt() . $suffix;
|
||||
$micro = System::baseUrl() . "/photo/" . $hash . "-6." . $Image->getExt() . $suffix;
|
||||
|
||||
// Remove the cached photo
|
||||
$a = get_app();
|
||||
$a = \get_app();
|
||||
$basepath = $a->getBasePath();
|
||||
|
||||
if (is_dir($basepath . "/photo")) {
|
||||
$filename = $basepath . '/photo/' . $hash . '-4.' . $Image->getExt();
|
||||
$filename = $basepath . "/photo/" . $hash . "-4." . $Image->getExt();
|
||||
if (file_exists($filename)) {
|
||||
unlink($filename);
|
||||
}
|
||||
$filename = $basepath . '/photo/' . $hash . '-5.' . $Image->getExt();
|
||||
$filename = $basepath . "/photo/" . $hash . "-5." . $Image->getExt();
|
||||
if (file_exists($filename)) {
|
||||
unlink($filename);
|
||||
}
|
||||
$filename = $basepath . '/photo/' . $hash . '-6.' . $Image->getExt();
|
||||
$filename = $basepath . "/photo/" . $hash . "-6." . $Image->getExt();
|
||||
if (file_exists($filename)) {
|
||||
unlink($filename);
|
||||
}
|
||||
|
@ -172,16 +487,16 @@ class Photo
|
|||
}
|
||||
|
||||
if ($photo_failure) {
|
||||
$image_url = System::baseUrl() . '/images/person-300.jpg';
|
||||
$thumb = System::baseUrl() . '/images/person-80.jpg';
|
||||
$micro = System::baseUrl() . '/images/person-48.jpg';
|
||||
$image_url = System::baseUrl() . "/images/person-300.jpg";
|
||||
$thumb = System::baseUrl() . "/images/person-80.jpg";
|
||||
$micro = System::baseUrl() . "/images/person-48.jpg";
|
||||
}
|
||||
|
||||
return [$image_url, $thumb, $micro];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $exifCoord coordinate
|
||||
* @param array $exifCoord coordinate
|
||||
* @param string $hemi hemi
|
||||
* @return float
|
||||
*/
|
||||
|
@ -191,7 +506,7 @@ class Photo
|
|||
$minutes = count($exifCoord) > 1 ? self::gps2Num($exifCoord[1]) : 0;
|
||||
$seconds = count($exifCoord) > 2 ? self::gps2Num($exifCoord[2]) : 0;
|
||||
|
||||
$flip = ($hemi == 'W' || $hemi == 'S') ? -1 : 1;
|
||||
$flip = ($hemi == "W" || $hemi == "S") ? -1 : 1;
|
||||
|
||||
return floatval($flip * ($degrees + ($minutes / 60) + ($seconds / 3600)));
|
||||
}
|
||||
|
@ -202,7 +517,7 @@ class Photo
|
|||
*/
|
||||
private static function gps2Num($coordPart)
|
||||
{
|
||||
$parts = explode('/', $coordPart);
|
||||
$parts = explode("/", $coordPart);
|
||||
|
||||
if (count($parts) <= 0) {
|
||||
return 0;
|
||||
|
@ -224,6 +539,7 @@ class Photo
|
|||
* @param bool $update Update the cache
|
||||
*
|
||||
* @return array Returns array of the photo albums
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public static function getAlbums($uid, $update = false)
|
||||
{
|
||||
|
@ -232,7 +548,7 @@ class Photo
|
|||
$key = "photo_albums:".$uid.":".local_user().":".remote_user();
|
||||
$albums = Cache::get($key);
|
||||
if (is_null($albums) || $update) {
|
||||
if (!Config::get('system', 'no_count', false)) {
|
||||
if (!Config::get("system", "no_count", false)) {
|
||||
/// @todo This query needs to be renewed. It is really slow
|
||||
// At this time we just store the data in the cache
|
||||
$albums = q("SELECT COUNT(DISTINCT `resource-id`) AS `total`, `album`, ANY_VALUE(`created`) AS `created`
|
||||
|
@ -240,8 +556,8 @@ class Photo
|
|||
WHERE `uid` = %d AND `album` != '%s' AND `album` != '%s' $sql_extra
|
||||
GROUP BY `album` ORDER BY `created` DESC",
|
||||
intval($uid),
|
||||
DBA::escape('Contact Photos'),
|
||||
DBA::escape(L10n::t('Contact Photos'))
|
||||
DBA::escape("Contact Photos"),
|
||||
DBA::escape(L10n::t("Contact Photos"))
|
||||
);
|
||||
} else {
|
||||
// This query doesn't do the count and is much faster
|
||||
|
@ -249,8 +565,8 @@ class Photo
|
|||
FROM `photo` USE INDEX (`uid_album_scale_created`)
|
||||
WHERE `uid` = %d AND `album` != '%s' AND `album` != '%s' $sql_extra",
|
||||
intval($uid),
|
||||
DBA::escape('Contact Photos'),
|
||||
DBA::escape(L10n::t('Contact Photos'))
|
||||
DBA::escape("Contact Photos"),
|
||||
DBA::escape(L10n::t("Contact Photos"))
|
||||
);
|
||||
}
|
||||
Cache::set($key, $albums, Cache::DAY);
|
||||
|
@ -261,6 +577,7 @@ class Photo
|
|||
/**
|
||||
* @param int $uid User id of the photos
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function clearAlbumCache($uid)
|
||||
{
|
||||
|
@ -272,9 +589,130 @@ class Photo
|
|||
* Generate a unique photo ID.
|
||||
*
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function newResource()
|
||||
{
|
||||
return system::createGUID(32, false);
|
||||
return System::createGUID(32, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes photo permissions that had been embedded in a post
|
||||
*
|
||||
* @todo This function currently does have some flaws:
|
||||
* - Sharing a post with a forum will create a photo that only the forum can see.
|
||||
* - Sharing a photo again that been shared non public before doesn't alter the permissions.
|
||||
*
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function setPermissionFromBody($body, $uid, $original_contact_id, $str_contact_allow, $str_group_allow, $str_contact_deny, $str_group_deny)
|
||||
{
|
||||
// Simplify image codes
|
||||
$img_body = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", '[img]$3[/img]', $body);
|
||||
$img_body = preg_replace("/\[img\=(.*?)\](.*?)\[\/img\]/ism", '[img]$1[/img]', $img_body);
|
||||
|
||||
// Search for images
|
||||
if (!preg_match_all("/\[img\](.*?)\[\/img\]/", $img_body, $match)) {
|
||||
return false;
|
||||
}
|
||||
$images = $match[1];
|
||||
if (empty($images)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($images as $image) {
|
||||
if (!stristr($image, System::baseUrl() . '/photo/')) {
|
||||
continue;
|
||||
}
|
||||
$image_uri = substr($image,strrpos($image,'/') + 1);
|
||||
$image_uri = substr($image_uri,0, strpos($image_uri,'-'));
|
||||
if (!strlen($image_uri)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ensure to only modify photos that you own
|
||||
$srch = '<' . intval($original_contact_id) . '>';
|
||||
|
||||
$condition = [
|
||||
'allow_cid' => $srch, 'allow_gid' => '', 'deny_cid' => '', 'deny_gid' => '',
|
||||
'resource-id' => $image_uri, 'uid' => $uid
|
||||
];
|
||||
if (!Photo::exists($condition)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/// @todo Check if $str_contact_allow does contain a public forum. Then set the permissions to public.
|
||||
|
||||
$fields = ['allow_cid' => $str_contact_allow, 'allow_gid' => $str_group_allow,
|
||||
'deny_cid' => $str_contact_deny, 'deny_gid' => $str_group_deny];
|
||||
$condition = ['resource-id' => $image_uri, 'uid' => $uid];
|
||||
Logger::info('Set permissions', ['condition' => $condition, 'permissions' => $fields]);
|
||||
Photo::update($fields, $condition);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strips known picture extensions from picture links
|
||||
*
|
||||
* @param string $name Picture link
|
||||
* @return string stripped picture link
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function stripExtension($name)
|
||||
{
|
||||
$name = str_replace([".jpg", ".png", ".gif"], ["", "", ""], $name);
|
||||
foreach (Image::supportedTypes() as $m => $e) {
|
||||
$name = str_replace("." . $e, "", $name);
|
||||
}
|
||||
return $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the GUID from picture links
|
||||
*
|
||||
* @param string $name Picture link
|
||||
* @return string GUID
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function getGUID($name)
|
||||
{
|
||||
$a = \get_app();
|
||||
$base = $a->getBaseURL();
|
||||
|
||||
$guid = str_replace([Strings::normaliseLink($base), '/photo/'], '', Strings::normaliseLink($name));
|
||||
|
||||
$guid = self::stripExtension($guid);
|
||||
if (substr($guid, -2, 1) != "-") {
|
||||
return '';
|
||||
}
|
||||
|
||||
$scale = intval(substr($guid, -1, 1));
|
||||
if (!is_numeric($scale)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$guid = substr($guid, 0, -2);
|
||||
return $guid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if the picture link points to a locally stored picture
|
||||
*
|
||||
* @param string $name Picture link
|
||||
* @return boolean
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function isLocal($name)
|
||||
{
|
||||
$guid = self::getGUID($name);
|
||||
|
||||
if (empty($guid)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return DBA::exists('photo', ['resource-id' => $guid]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,8 +8,6 @@ use Friendica\BaseObject;
|
|||
use Friendica\Database\DBA;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
|
||||
require_once 'include/dba.php';
|
||||
|
||||
/**
|
||||
* @brief functions for interacting with a process
|
||||
*/
|
||||
|
@ -21,6 +19,7 @@ class Process extends BaseObject
|
|||
* @param string $command
|
||||
* @param string $pid
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function insert($command, $pid = null)
|
||||
{
|
||||
|
@ -46,6 +45,7 @@ class Process extends BaseObject
|
|||
*
|
||||
* @param string $pid
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function deleteByPid($pid = null)
|
||||
{
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -4,19 +4,19 @@
|
|||
*/
|
||||
namespace Friendica\Model;
|
||||
|
||||
use Friendica\Core\Logger;
|
||||
use Friendica\Core\Worker;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
|
||||
require_once 'include/dba.php';
|
||||
|
||||
class PushSubscriber
|
||||
{
|
||||
/**
|
||||
* @brief Send subscription notifications for the given user
|
||||
*
|
||||
* @param integer $uid User ID
|
||||
* @param string $priority Priority for push workers
|
||||
* @param integer $uid User ID
|
||||
* @param int $default_priority
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public static function publishFeed($uid, $default_priority = PRIORITY_HIGH)
|
||||
{
|
||||
|
@ -29,7 +29,8 @@ class PushSubscriber
|
|||
/**
|
||||
* @brief start workers to transmit the feed data
|
||||
*
|
||||
* @param string $priority Priority for push workers
|
||||
* @param int $default_priority
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public static function requeue($default_priority = PRIORITY_HIGH)
|
||||
{
|
||||
|
@ -45,7 +46,7 @@ class PushSubscriber
|
|||
$priority = $default_priority;
|
||||
}
|
||||
|
||||
logger('Publish feed to ' . $subscriber['callback_url'] . ' for ' . $subscriber['nickname'] . ' with priority ' . $priority, LOGGER_DEBUG);
|
||||
Logger::log('Publish feed to ' . $subscriber['callback_url'] . ' for ' . $subscriber['nickname'] . ' with priority ' . $priority, Logger::DEBUG);
|
||||
Worker::add($priority, 'PubSubPublish', (int)$subscriber['id']);
|
||||
}
|
||||
|
||||
|
@ -61,6 +62,7 @@ class PushSubscriber
|
|||
* @param string $hub_callback Callback address
|
||||
* @param string $hub_topic Feed topic
|
||||
* @param string $hub_secret Subscription secret
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function renew($uid, $nick, $subscribe, $hub_callback, $hub_topic, $hub_secret)
|
||||
{
|
||||
|
@ -88,9 +90,9 @@ class PushSubscriber
|
|||
'secret' => $hub_secret];
|
||||
DBA::insert('push_subscriber', $fields);
|
||||
|
||||
logger("Successfully subscribed [$hub_callback] for $nick");
|
||||
Logger::log("Successfully subscribed [$hub_callback] for $nick");
|
||||
} else {
|
||||
logger("Successfully unsubscribed [$hub_callback] for $nick");
|
||||
Logger::log("Successfully unsubscribed [$hub_callback] for $nick");
|
||||
// we do nothing here, since the row was already deleted
|
||||
}
|
||||
}
|
||||
|
@ -99,6 +101,7 @@ class PushSubscriber
|
|||
* @brief Delay the push subscriber
|
||||
*
|
||||
* @param integer $id Subscriber ID
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function delay($id)
|
||||
{
|
||||
|
@ -115,10 +118,10 @@ class PushSubscriber
|
|||
|
||||
if ($days > 60) {
|
||||
DBA::update('push_subscriber', ['push' => -1, 'next_try' => DBA::NULL_DATETIME], ['id' => $id]);
|
||||
logger('Delivery error: Subscription ' . $subscriber['callback_url'] . ' for ' . $subscriber['nickname'] . ' is marked as ended.', LOGGER_DEBUG);
|
||||
Logger::log('Delivery error: Subscription ' . $subscriber['callback_url'] . ' for ' . $subscriber['nickname'] . ' is marked as ended.', Logger::DEBUG);
|
||||
} else {
|
||||
DBA::update('push_subscriber', ['push' => 0, 'next_try' => DBA::NULL_DATETIME], ['id' => $id]);
|
||||
logger('Delivery error: Giving up ' . $subscriber['callback_url'] . ' for ' . $subscriber['nickname'] . ' for now.', LOGGER_DEBUG);
|
||||
Logger::log('Delivery error: Giving up ' . $subscriber['callback_url'] . ' for ' . $subscriber['nickname'] . ' for now.', Logger::DEBUG);
|
||||
}
|
||||
} else {
|
||||
// Calculate the delay until the next trial
|
||||
|
@ -128,7 +131,7 @@ class PushSubscriber
|
|||
$retrial = $retrial + 1;
|
||||
|
||||
DBA::update('push_subscriber', ['push' => $retrial, 'next_try' => $next], ['id' => $id]);
|
||||
logger('Delivery error: Next try (' . $retrial . ') ' . $subscriber['callback_url'] . ' for ' . $subscriber['nickname'] . ' at ' . $next, LOGGER_DEBUG);
|
||||
Logger::log('Delivery error: Next try (' . $retrial . ') ' . $subscriber['callback_url'] . ' for ' . $subscriber['nickname'] . ' at ' . $next, Logger::DEBUG);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -136,7 +139,8 @@ class PushSubscriber
|
|||
* @brief Reset the push subscriber
|
||||
*
|
||||
* @param integer $id Subscriber ID
|
||||
* @param date $last_update Date of last transmitted item
|
||||
* @param string $last_update Date of last transmitted item
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function reset($id, $last_update)
|
||||
{
|
||||
|
@ -148,6 +152,6 @@ class PushSubscriber
|
|||
// set last_update to the 'created' date of the last item, and reset push=0
|
||||
$fields = ['push' => 0, 'next_try' => DBA::NULL_DATETIME, 'last_update' => $last_update];
|
||||
DBA::update('push_subscriber', $fields, ['id' => $id]);
|
||||
logger('Subscriber ' . $subscriber['callback_url'] . ' for ' . $subscriber['nickname'] . ' is marked as vital', LOGGER_DEBUG);
|
||||
Logger::log('Subscriber ' . $subscriber['callback_url'] . ' for ' . $subscriber['nickname'] . ' is marked as vital', Logger::DEBUG);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,122 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* @file src/Model/Queue.php
|
||||
*/
|
||||
namespace Friendica\Model;
|
||||
|
||||
use Friendica\Core\Config;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
|
||||
require_once 'include/dba.php';
|
||||
|
||||
class Queue
|
||||
{
|
||||
/**
|
||||
* @param string $id id
|
||||
*/
|
||||
public static function updateTime($id)
|
||||
{
|
||||
logger('queue: requeue item ' . $id);
|
||||
$queue = DBA::selectFirst('queue', ['retrial'], ['id' => $id]);
|
||||
if (!DBA::isResult($queue)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$retrial = $queue['retrial'];
|
||||
|
||||
if ($retrial > 14) {
|
||||
self::removeItem($id);
|
||||
}
|
||||
|
||||
// Calculate the delay until the next trial
|
||||
$delay = (($retrial + 3) ** 4) + (rand(1, 30) * ($retrial + 1));
|
||||
$next = DateTimeFormat::utc('now + ' . $delay . ' seconds');
|
||||
|
||||
DBA::update('queue', ['last' => DateTimeFormat::utcNow(), 'retrial' => $retrial + 1, 'next' => $next], ['id' => $id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $id id
|
||||
*/
|
||||
public static function removeItem($id)
|
||||
{
|
||||
logger('queue: remove queue item ' . $id);
|
||||
DBA::delete('queue', ['id' => $id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Checks if the communication with a given contact had problems recently
|
||||
*
|
||||
* @param int $cid Contact id
|
||||
*
|
||||
* @return bool The communication with this contact has currently problems
|
||||
*/
|
||||
public static function wasDelayed($cid)
|
||||
{
|
||||
// Are there queue entries that were recently added?
|
||||
$r = q("SELECT `id` FROM `queue` WHERE `cid` = %d
|
||||
AND `last` > UTC_TIMESTAMP() - INTERVAL 15 MINUTE LIMIT 1",
|
||||
intval($cid)
|
||||
);
|
||||
|
||||
$was_delayed = DBA::isResult($r);
|
||||
|
||||
// We set "term-date" to a current date if the communication has problems.
|
||||
// If the communication works again we reset this value.
|
||||
if ($was_delayed) {
|
||||
$r = q("SELECT `term-date` FROM `contact` WHERE `id` = %d AND `term-date` <= '1000-01-01' LIMIT 1",
|
||||
intval($cid)
|
||||
);
|
||||
$was_delayed = !DBA::isResult($r);
|
||||
}
|
||||
|
||||
return $was_delayed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $cid cid
|
||||
* @param string $network network
|
||||
* @param string $msg message
|
||||
* @param boolean $batch batch, default false
|
||||
*/
|
||||
public static function add($cid, $network, $msg, $batch = false, $guid = '')
|
||||
{
|
||||
|
||||
$max_queue = Config::get('system', 'max_contact_queue');
|
||||
if ($max_queue < 1) {
|
||||
$max_queue = 500;
|
||||
}
|
||||
|
||||
$batch_queue = Config::get('system', 'max_batch_queue');
|
||||
if ($batch_queue < 1) {
|
||||
$batch_queue = 1000;
|
||||
}
|
||||
|
||||
$r = q("SELECT COUNT(*) AS `total` FROM `queue` INNER JOIN `contact` ON `queue`.`cid` = `contact`.`id`
|
||||
WHERE `queue`.`cid` = %d AND `contact`.`self` = 0 ",
|
||||
intval($cid)
|
||||
);
|
||||
|
||||
if (DBA::isResult($r)) {
|
||||
if ($batch && ($r[0]['total'] > $batch_queue)) {
|
||||
logger('too many queued items for batch server ' . $cid . ' - discarding message');
|
||||
return;
|
||||
} elseif ((! $batch) && ($r[0]['total'] > $max_queue)) {
|
||||
logger('too many queued items for contact ' . $cid . ' - discarding message');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
DBA::insert('queue', [
|
||||
'cid' => $cid,
|
||||
'network' => $network,
|
||||
'guid' => $guid,
|
||||
'created' => DateTimeFormat::utcNow(),
|
||||
'last' => DateTimeFormat::utcNow(),
|
||||
'content' => $msg,
|
||||
'batch' =>($batch) ? 1 : 0
|
||||
]);
|
||||
logger('Added item ' . $guid . ' for ' . $cid);
|
||||
}
|
||||
}
|
5
src/Model/README.md
Normal file
5
src/Model/README.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
## Friendica\Model
|
||||
|
||||
Models are the glue between the business logic of the app and the datastore(s).
|
||||
|
||||
In the namespace Model should only be static classes that interact with the DB with the same name as a database table.
|
|
@ -3,10 +3,12 @@
|
|||
/**
|
||||
* @file src/Model/Register.php
|
||||
*/
|
||||
|
||||
namespace Friendica\Model;
|
||||
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
use Friendica\Util\Strings;
|
||||
|
||||
/**
|
||||
* Class interacting with the register database table
|
||||
|
@ -19,11 +21,12 @@ class Register
|
|||
* Return the list of pending registrations
|
||||
*
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function getPending()
|
||||
{
|
||||
$stmt = DBA::p(
|
||||
"SELECT `register`.*, `contact`.`name`, `user`.`email`
|
||||
"SELECT `register`.*, `contact`.`name`, `contact`.`url`, `contact`.`micro`, `user`.`email`
|
||||
FROM `register`
|
||||
INNER JOIN `contact` ON `register`.`uid` = `contact`.`uid`
|
||||
INNER JOIN `user` ON `register`.`uid` = `user`.`uid`"
|
||||
|
@ -36,6 +39,7 @@ class Register
|
|||
* Returns the pending registration count
|
||||
*
|
||||
* @return int
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function getPendingCount()
|
||||
{
|
||||
|
@ -53,6 +57,7 @@ class Register
|
|||
*
|
||||
* @param string $hash
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function getByHash($hash)
|
||||
{
|
||||
|
@ -62,8 +67,9 @@ class Register
|
|||
/**
|
||||
* Returns true if a register record exists with the provided hash
|
||||
*
|
||||
* @param string $hash
|
||||
* @param string $hash
|
||||
* @return boolean
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function existsByHash($hash)
|
||||
{
|
||||
|
@ -74,10 +80,11 @@ class Register
|
|||
* Creates a register record for an invitation and returns the auto-generated code for it
|
||||
*
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function createForInvitation()
|
||||
{
|
||||
$code = autoname(8) . srand(1000, 9999);
|
||||
$code = Strings::getRandomName(8) . random_int(1000, 9999);
|
||||
|
||||
$fields = [
|
||||
'hash' => $code,
|
||||
|
@ -97,10 +104,11 @@ class Register
|
|||
* @param string $language The registration language
|
||||
* @param string $note An additional message from the user
|
||||
* @return boolean
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function createForApproval($uid, $language, $note = '')
|
||||
{
|
||||
$hash = random_string();
|
||||
$hash = Strings::getRandomHex();
|
||||
|
||||
if (!User::exists($uid)) {
|
||||
return false;
|
||||
|
@ -121,8 +129,9 @@ class Register
|
|||
/**
|
||||
* Deletes a register record by the provided hash and returns the success of the database deletion
|
||||
*
|
||||
* @param string $hash
|
||||
* @param string $hash
|
||||
* @return boolean
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function deleteByHash($hash)
|
||||
{
|
||||
|
|
32
src/Model/Search.php
Normal file
32
src/Model/Search.php
Normal file
|
@ -0,0 +1,32 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Model;
|
||||
|
||||
use Friendica\BaseObject;
|
||||
use Friendica\Database\DBA;
|
||||
|
||||
/**
|
||||
* Model for DB specific logic for the search entity
|
||||
*/
|
||||
class Search extends BaseObject
|
||||
{
|
||||
/**
|
||||
* Returns the list of user defined tags (e.g. #Friendica)
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function getUserTags()
|
||||
{
|
||||
$termsStmt = DBA::p("SELECT DISTINCT(`term`) FROM `search`");
|
||||
|
||||
$tags = [];
|
||||
|
||||
while ($term = DBA::fetch($termsStmt)) {
|
||||
$tags[] = trim($term['term'], '#');
|
||||
}
|
||||
|
||||
return $tags;
|
||||
}
|
||||
}
|
63
src/Model/Storage/Database.php
Normal file
63
src/Model/Storage/Database.php
Normal file
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
/**
|
||||
* @file src/Model/Storage/Filesystem.php
|
||||
* @brief Storage backend system
|
||||
*/
|
||||
|
||||
namespace Friendica\Model\Storage;
|
||||
|
||||
use Friendica\Core\Logger;
|
||||
use Friendica\Core\L10n;
|
||||
use Friendica\Database\DBA;
|
||||
|
||||
/**
|
||||
* @brief Database based storage system
|
||||
*
|
||||
* This class manage data stored in database table.
|
||||
*/
|
||||
class Database implements IStorage
|
||||
{
|
||||
public static function get($ref)
|
||||
{
|
||||
$r = DBA::selectFirst('storage', ['data'], ['id' => $ref]);
|
||||
if (!DBA::isResult($r)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $r['data'];
|
||||
}
|
||||
|
||||
public static function put($data, $ref = '')
|
||||
{
|
||||
if ($ref !== '') {
|
||||
$r = DBA::update('storage', ['data' => $data], ['id' => $ref]);
|
||||
if ($r === false) {
|
||||
Logger::log('Failed to update data with id ' . $ref . ': ' . DBA::errorNo() . ' : ' . DBA::errorMessage());
|
||||
throw new StorageException(L10n::t('Database storage failed to update %s', $ref));
|
||||
}
|
||||
return $ref;
|
||||
} else {
|
||||
$r = DBA::insert('storage', ['data' => $data]);
|
||||
if ($r === false) {
|
||||
Logger::log('Failed to insert data: ' . DBA::errorNo() . ' : ' . DBA::errorMessage());
|
||||
throw new StorageException(L10n::t('Database storage failed to insert data'));
|
||||
}
|
||||
return DBA::lastInsertId();
|
||||
}
|
||||
}
|
||||
|
||||
public static function delete($ref)
|
||||
{
|
||||
return DBA::delete('storage', ['id' => $ref]);
|
||||
}
|
||||
|
||||
public static function getOptions()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public static function saveOptions($data)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
145
src/Model/Storage/Filesystem.php
Normal file
145
src/Model/Storage/Filesystem.php
Normal file
|
@ -0,0 +1,145 @@
|
|||
<?php
|
||||
/**
|
||||
* @file src/Model/Storage/Filesystem.php
|
||||
* @brief Storage backend system
|
||||
*/
|
||||
|
||||
namespace Friendica\Model\Storage;
|
||||
|
||||
use Friendica\Core\Config;
|
||||
use Friendica\Core\L10n;
|
||||
use Friendica\Core\Logger;
|
||||
use Friendica\Util\Strings;
|
||||
|
||||
/**
|
||||
* @brief Filesystem based storage backend
|
||||
*
|
||||
* This class manage data on filesystem.
|
||||
* Base folder for storage is set in storage.filesystem_path.
|
||||
* Best would be for storage folder to be outside webserver folder, we are using a
|
||||
* folder relative to code tree root as default to ease things for users in shared hostings.
|
||||
* Each new resource gets a value as reference and is saved in a
|
||||
* folder tree stucture created from that value.
|
||||
*/
|
||||
class Filesystem implements IStorage
|
||||
{
|
||||
// Default base folder
|
||||
const DEFAULT_BASE_FOLDER = 'storage';
|
||||
|
||||
private static function getBasePath()
|
||||
{
|
||||
$path = Config::get('storage', 'filesystem_path', self::DEFAULT_BASE_FOLDER);
|
||||
return rtrim($path, '/');
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Split data ref and return file path
|
||||
* @param string $ref Data reference
|
||||
* @return string
|
||||
*/
|
||||
private static function pathForRef($ref)
|
||||
{
|
||||
$base = self::getBasePath();
|
||||
$fold1 = substr($ref, 0, 2);
|
||||
$fold2 = substr($ref, 2, 2);
|
||||
$file = substr($ref, 4);
|
||||
|
||||
return implode('/', [$base, $fold1, $fold2, $file]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Create dirctory tree to store file, with .htaccess and index.html files
|
||||
* @param string $file Path and filename
|
||||
* @throws StorageException
|
||||
*/
|
||||
private static function createFoldersForFile($file)
|
||||
{
|
||||
$path = dirname($file);
|
||||
|
||||
if (!is_dir($path)) {
|
||||
if (!mkdir($path, 0770, true)) {
|
||||
Logger::log('Failed to create dirs ' . $path);
|
||||
throw new StorageException(L10n::t('Filesystem storage failed to create "%s". Check you write permissions.', $path));
|
||||
}
|
||||
}
|
||||
|
||||
$base = self::getBasePath();
|
||||
|
||||
while ($path !== $base) {
|
||||
if (!is_file($path . '/index.html')) {
|
||||
file_put_contents($path . '/index.html', '');
|
||||
}
|
||||
chmod($path . '/index.html', 0660);
|
||||
chmod($path, 0770);
|
||||
$path = dirname($path);
|
||||
}
|
||||
if (!is_file($path . '/index.html')) {
|
||||
file_put_contents($path . '/index.html', '');
|
||||
chmod($path . '/index.html', 0660);
|
||||
}
|
||||
}
|
||||
|
||||
public static function get($ref)
|
||||
{
|
||||
$file = self::pathForRef($ref);
|
||||
if (!is_file($file)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return file_get_contents($file);
|
||||
}
|
||||
|
||||
public static function put($data, $ref = '')
|
||||
{
|
||||
if ($ref === '') {
|
||||
$ref = Strings::getRandomHex();
|
||||
}
|
||||
$file = self::pathForRef($ref);
|
||||
|
||||
self::createFoldersForFile($file);
|
||||
|
||||
$r = file_put_contents($file, $data);
|
||||
if ($r === FALSE) {
|
||||
Logger::log('Failed to write data to ' . $file);
|
||||
throw new StorageException(L10n::t('Filesystem storage failed to save data to "%s". Check your write permissions', $file));
|
||||
}
|
||||
chmod($file, 0660);
|
||||
return $ref;
|
||||
}
|
||||
|
||||
public static function delete($ref)
|
||||
{
|
||||
$file = self::pathForRef($ref);
|
||||
// return true if file doesn't exists. we want to delete it: success with zero work!
|
||||
if (!is_file($file)) {
|
||||
return true;
|
||||
}
|
||||
return unlink($file);
|
||||
}
|
||||
|
||||
public static function getOptions()
|
||||
{
|
||||
return [
|
||||
'storagepath' => [
|
||||
'input',
|
||||
L10n::t('Storage base path'),
|
||||
self::getBasePath(),
|
||||
L10n::t('Folder where uploaded files are saved. For maximum security, This should be a path outside web server folder tree')
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
public static function saveOptions($data)
|
||||
{
|
||||
$storagepath = defaults($data, 'storagepath', '');
|
||||
if ($storagepath === '' || !is_dir($storagepath)) {
|
||||
return [
|
||||
'storagepath' => L10n::t('Enter a valid existing folder')
|
||||
];
|
||||
};
|
||||
Config::set('storage', 'filesystem_path', $storagepath);
|
||||
return [];
|
||||
}
|
||||
|
||||
}
|
89
src/Model/Storage/IStorage.php
Normal file
89
src/Model/Storage/IStorage.php
Normal file
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
/**
|
||||
* @file src/Model/Storage/IStorage.php
|
||||
* @brief Storage backend system
|
||||
*/
|
||||
|
||||
namespace Friendica\Model\Storage;
|
||||
|
||||
/**
|
||||
* @brief Interface for storage backends
|
||||
*/
|
||||
interface IStorage
|
||||
{
|
||||
/**
|
||||
* @brief Get data from backend
|
||||
* @param string $ref Data reference
|
||||
* @return string
|
||||
*/
|
||||
public static function get($ref);
|
||||
|
||||
/**
|
||||
* @brief Put data in backend as $ref. If $ref is not defined a new reference is created.
|
||||
* @param string $data Data to save
|
||||
* @param string $ref Data referece. Optional.
|
||||
* @return string Saved data referece
|
||||
*/
|
||||
public static function put($data, $ref = "");
|
||||
|
||||
/**
|
||||
* @brief Remove data from backend
|
||||
* @param string $ref Data referece
|
||||
* @return boolean True on success
|
||||
*/
|
||||
public static function delete($ref);
|
||||
|
||||
/**
|
||||
* @brief Get info about storage options
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* This method return an array with informations about storage options
|
||||
* from which the form presented to the user is build.
|
||||
*
|
||||
* The returned array is:
|
||||
*
|
||||
* [
|
||||
* 'option1name' => [ ..info.. ],
|
||||
* 'option2name' => [ ..info.. ],
|
||||
* ...
|
||||
* ]
|
||||
*
|
||||
* An empty array can be returned if backend doesn't have any options
|
||||
*
|
||||
* The info array for each option MUST be as follows:
|
||||
*
|
||||
* [
|
||||
* 'type', // define the field used in form, and the type of data.
|
||||
* // one of 'checkbox', 'combobox', 'custom', 'datetime',
|
||||
* // 'input', 'intcheckbox', 'password', 'radio', 'richtext'
|
||||
* // 'select', 'select_raw', 'textarea', 'yesno'
|
||||
*
|
||||
* 'label', // Translatable label of the field
|
||||
* 'value', // Current value
|
||||
* 'help text', // Translatable description for the field
|
||||
* extra data // Optional. Depends on 'type':
|
||||
* // select: array [ value => label ] of choices
|
||||
* // intcheckbox: value of input element
|
||||
* // select_raw: prebuild html string of < option > tags
|
||||
* // yesno: array [ 'label no', 'label yes']
|
||||
* ]
|
||||
*
|
||||
* See https://github.com/friendica/friendica/wiki/Quick-Template-Guide
|
||||
*/
|
||||
public static function getOptions();
|
||||
|
||||
/**
|
||||
* @brief Validate and save options
|
||||
*
|
||||
* @param array $data Array [optionname => value] to be saved
|
||||
*
|
||||
* @return array Validation errors: [optionname => error message]
|
||||
*
|
||||
* Return array must be empty if no error.
|
||||
*/
|
||||
public static function saveOptions($data);
|
||||
|
||||
}
|
||||
|
||||
|
14
src/Model/Storage/StorageException.php
Normal file
14
src/Model/Storage/StorageException.php
Normal file
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
/**
|
||||
* @file src/Model/Storage/StorageException.php
|
||||
* @brief Storage backend system
|
||||
*/
|
||||
|
||||
namespace Friendica\Model\Storage;
|
||||
|
||||
/**
|
||||
* @brief Storage Exception
|
||||
*/
|
||||
class StorageException extends \Exception
|
||||
{
|
||||
}
|
55
src/Model/Storage/SystemResource.php
Normal file
55
src/Model/Storage/SystemResource.php
Normal file
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
/**
|
||||
* @file src/Model/Storage/SystemStorage.php
|
||||
* @brief Storage backend system
|
||||
*/
|
||||
|
||||
namespace Friendica\Model\Storage;
|
||||
|
||||
use \BadMethodCallException;
|
||||
|
||||
/**
|
||||
* @brief System resource storage class
|
||||
*
|
||||
* This class is used to load system resources, like images.
|
||||
* Is not intended to be selectable by admins as default storage class.
|
||||
*/
|
||||
class SystemResource implements IStorage
|
||||
{
|
||||
// Valid folders to look for resources
|
||||
const VALID_FOLDERS = ["images"];
|
||||
|
||||
public static function get($filename)
|
||||
{
|
||||
$folder = dirname($filename);
|
||||
if (!in_array($folder, self::VALID_FOLDERS)) {
|
||||
return "";
|
||||
}
|
||||
if (!file_exists($filename)) {
|
||||
return "";
|
||||
}
|
||||
return file_get_contents($filename);
|
||||
}
|
||||
|
||||
|
||||
public static function put($data, $filename = "")
|
||||
{
|
||||
throw new BadMethodCallException();
|
||||
}
|
||||
|
||||
public static function delete($filename)
|
||||
{
|
||||
throw new BadMethodCallException();
|
||||
}
|
||||
|
||||
public static function getOptions()
|
||||
{
|
||||
return [];
|
||||
}
|
||||
|
||||
public static function saveOptions($data)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
|
@ -1,41 +1,171 @@
|
|||
<?php
|
||||
/**
|
||||
* @file src/Model/Term
|
||||
* @file src/Model/Term.php
|
||||
*/
|
||||
namespace Friendica\Model;
|
||||
|
||||
use Friendica\Core\Cache;
|
||||
use Friendica\Core\Logger;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\Util\Strings;
|
||||
|
||||
require_once 'boot.php';
|
||||
require_once 'include/conversation.php';
|
||||
require_once 'include/dba.php';
|
||||
|
||||
/**
|
||||
* Class Term
|
||||
*
|
||||
* This Model class handles term table interactions.
|
||||
* This tables stores relevant terms related to posts, photos and searches, like hashtags, mentions and
|
||||
* user-applied categories.
|
||||
*
|
||||
* @package Friendica\Model
|
||||
*/
|
||||
class Term
|
||||
{
|
||||
public static function tagTextFromItemId($itemid)
|
||||
{
|
||||
$tag_text = '';
|
||||
$condition = ['otype' => TERM_OBJ_POST, 'oid' => $itemid, 'type' => [TERM_HASHTAG, TERM_MENTION]];
|
||||
$tags = DBA::select('term', [], $condition);
|
||||
while ($tag = DBA::fetch($tags)) {
|
||||
if ($tag_text != '') {
|
||||
$tag_text .= ',';
|
||||
}
|
||||
const UNKNOWN = 0;
|
||||
const HASHTAG = 1;
|
||||
const MENTION = 2;
|
||||
const CATEGORY = 3;
|
||||
const PCATEGORY = 4;
|
||||
const FILE = 5;
|
||||
const SAVEDSEARCH = 6;
|
||||
const CONVERSATION = 7;
|
||||
/**
|
||||
* An implicit mention is a mention in a comment body that is redundant with the threading information.
|
||||
*/
|
||||
const IMPLICIT_MENTION = 8;
|
||||
/**
|
||||
* An exclusive mention transfers the ownership of the post to the target account, usually a forum.
|
||||
*/
|
||||
const EXCLUSIVE_MENTION = 9;
|
||||
|
||||
if ($tag['type'] == 1) {
|
||||
$tag_text .= '#';
|
||||
} else {
|
||||
$tag_text .= '@';
|
||||
const TAG_CHARACTER = [
|
||||
self::HASHTAG => '#',
|
||||
self::MENTION => '@',
|
||||
self::IMPLICIT_MENTION => '%',
|
||||
self::EXCLUSIVE_MENTION => '!',
|
||||
];
|
||||
|
||||
const OBJECT_TYPE_POST = 1;
|
||||
const OBJECT_TYPE_PHOTO = 2;
|
||||
|
||||
/**
|
||||
* Returns a list of the most frequent global hashtags over the given period
|
||||
*
|
||||
* @param int $period Period in hours to consider posts
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function getGlobalTrendingHashtags(int $period, $limit = 10)
|
||||
{
|
||||
$tags = Cache::get('global_trending_tags');
|
||||
|
||||
if (!$tags) {
|
||||
$tagsStmt = DBA::p("SELECT t.`term`, COUNT(*) AS `score`
|
||||
FROM `term` t
|
||||
JOIN `item` i ON i.`id` = t.`oid` AND i.`uid` = t.`uid`
|
||||
JOIN `thread` ON `thread`.`iid` = i.`id`
|
||||
WHERE `thread`.`visible`
|
||||
AND NOT `thread`.`deleted`
|
||||
AND NOT `thread`.`moderated`
|
||||
AND NOT `thread`.`private`
|
||||
AND t.`uid` = 0
|
||||
AND t.`otype` = ?
|
||||
AND t.`type` = ?
|
||||
AND t.`term` != ''
|
||||
AND i.`received` > DATE_SUB(NOW(), INTERVAL ? HOUR)
|
||||
GROUP BY `term`
|
||||
ORDER BY `score` DESC
|
||||
LIMIT ?",
|
||||
Term::OBJECT_TYPE_POST,
|
||||
Term::HASHTAG,
|
||||
$period,
|
||||
$limit
|
||||
);
|
||||
|
||||
if (DBA::isResult($tagsStmt)) {
|
||||
$tags = DBA::toArray($tagsStmt);
|
||||
Cache::set('global_trending_tags', $tags, Cache::HOUR);
|
||||
}
|
||||
$tag_text .= '[url=' . $tag['url'] . ']' . $tag['term'] . '[/url]';
|
||||
}
|
||||
return $tag_text;
|
||||
|
||||
return $tags ?: [];
|
||||
}
|
||||
|
||||
public static function tagArrayFromItemId($itemid, $type = [TERM_HASHTAG, TERM_MENTION])
|
||||
/**
|
||||
* Returns a list of the most frequent local hashtags over the given period
|
||||
*
|
||||
* @param int $period Period in hours to consider posts
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function getLocalTrendingHashtags(int $period, $limit = 10)
|
||||
{
|
||||
$condition = ['otype' => TERM_OBJ_POST, 'oid' => $itemid, 'type' => $type];
|
||||
$tags = Cache::get('local_trending_tags');
|
||||
|
||||
if (!$tags) {
|
||||
$tagsStmt = DBA::p("SELECT t.`term`, COUNT(*) AS `score`
|
||||
FROM `term` t
|
||||
JOIN `item` i ON i.`id` = t.`oid` AND i.`uid` = t.`uid`
|
||||
JOIN `thread` ON `thread`.`iid` = i.`id`
|
||||
JOIN `user` ON `user`.`uid` = `thread`.`uid` AND NOT `user`.`hidewall`
|
||||
WHERE `thread`.`visible`
|
||||
AND NOT `thread`.`deleted`
|
||||
AND NOT `thread`.`moderated`
|
||||
AND NOT `thread`.`private`
|
||||
AND `thread`.`wall`
|
||||
AND `thread`.`origin`
|
||||
AND t.`otype` = ?
|
||||
AND t.`type` = ?
|
||||
AND t.`term` != ''
|
||||
AND i.`received` > DATE_SUB(NOW(), INTERVAL ? HOUR)
|
||||
GROUP BY `term`
|
||||
ORDER BY `score` DESC
|
||||
LIMIT ?",
|
||||
Term::OBJECT_TYPE_POST,
|
||||
Term::HASHTAG,
|
||||
$period,
|
||||
$limit
|
||||
);
|
||||
|
||||
if (DBA::isResult($tagsStmt)) {
|
||||
$tags = DBA::toArray($tagsStmt);
|
||||
Cache::set('local_trending_tags', $tags, Cache::HOUR);
|
||||
}
|
||||
}
|
||||
|
||||
return $tags ?: [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the legacy item.tag field comma-separated BBCode string from an item ID.
|
||||
* Includes only hashtags, implicit and explicit mentions.
|
||||
*
|
||||
* @param int $item_id
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function tagTextFromItemId($item_id)
|
||||
{
|
||||
$tag_list = [];
|
||||
$tags = self::tagArrayFromItemId($item_id, [self::HASHTAG, self::MENTION, self::IMPLICIT_MENTION]);
|
||||
foreach ($tags as $tag) {
|
||||
$tag_list[] = self::TAG_CHARACTER[$tag['type']] . '[url=' . $tag['url'] . ']' . $tag['term'] . '[/url]';
|
||||
}
|
||||
|
||||
return implode(',', $tag_list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the terms from the provided type(s) associated with the provided item ID.
|
||||
*
|
||||
* @param int $item_id
|
||||
* @param int|array $type
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function tagArrayFromItemId($item_id, $type = [self::HASHTAG, self::MENTION])
|
||||
{
|
||||
$condition = ['otype' => self::OBJECT_TYPE_POST, 'oid' => $item_id, 'type' => $type];
|
||||
$tags = DBA::select('term', ['type', 'term', 'url'], $condition);
|
||||
if (!DBA::isResult($tags)) {
|
||||
return [];
|
||||
|
@ -44,22 +174,39 @@ class Term
|
|||
return DBA::toArray($tags);
|
||||
}
|
||||
|
||||
public static function fileTextFromItemId($itemid)
|
||||
/**
|
||||
* Generates the legacy item.file field string from an item ID.
|
||||
* Includes only file and category terms.
|
||||
*
|
||||
* @param int $item_id
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function fileTextFromItemId($item_id)
|
||||
{
|
||||
$file_text = '';
|
||||
$condition = ['otype' => TERM_OBJ_POST, 'oid' => $itemid, 'type' => [TERM_FILE, TERM_CATEGORY]];
|
||||
$tags = DBA::select('term', [], $condition);
|
||||
while ($tag = DBA::fetch($tags)) {
|
||||
if ($tag['type'] == TERM_CATEGORY) {
|
||||
$tags = self::tagArrayFromItemId($item_id, [self::FILE, self::CATEGORY]);
|
||||
foreach ($tags as $tag) {
|
||||
if ($tag['type'] == self::CATEGORY) {
|
||||
$file_text .= '<' . $tag['term'] . '>';
|
||||
} else {
|
||||
$file_text .= '[' . $tag['term'] . ']';
|
||||
}
|
||||
}
|
||||
|
||||
return $file_text;
|
||||
}
|
||||
|
||||
public static function insertFromTagFieldByItemId($itemid, $tags)
|
||||
/**
|
||||
* Inserts new terms for the provided item ID based on the legacy item.tag field BBCode content.
|
||||
* Deletes all previous tag terms for the same item ID.
|
||||
* Sets both the item.mention and thread.mentions field flags if a mention concerning the item UID is found.
|
||||
*
|
||||
* @param int $item_id
|
||||
* @param string $tag_str
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public static function insertFromTagFieldByItemId($item_id, $tag_str)
|
||||
{
|
||||
$profile_base = System::baseUrl();
|
||||
$profile_data = parse_url($profile_base);
|
||||
|
@ -68,32 +215,32 @@ class Term
|
|||
$profile_base_diaspora = $profile_data['host'] . $profile_path . '/u/';
|
||||
|
||||
$fields = ['guid', 'uid', 'id', 'edited', 'deleted', 'created', 'received', 'title', 'body', 'parent'];
|
||||
$message = Item::selectFirst($fields, ['id' => $itemid]);
|
||||
if (!DBA::isResult($message)) {
|
||||
$item = Item::selectFirst($fields, ['id' => $item_id]);
|
||||
if (!DBA::isResult($item)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$message['tag'] = $tags;
|
||||
$item['tag'] = $tag_str;
|
||||
|
||||
// Clean up all tags
|
||||
self::deleteByItemId($itemid);
|
||||
self::deleteByItemId($item_id);
|
||||
|
||||
if ($message['deleted']) {
|
||||
if ($item['deleted']) {
|
||||
return;
|
||||
}
|
||||
|
||||
$taglist = explode(',', $message['tag']);
|
||||
$taglist = explode(',', $item['tag']);
|
||||
|
||||
$tags_string = '';
|
||||
foreach ($taglist as $tag) {
|
||||
if ((substr(trim($tag), 0, 1) == '#') || (substr(trim($tag), 0, 1) == '@')) {
|
||||
if (Strings::startsWith($tag, self::TAG_CHARACTER)) {
|
||||
$tags_string .= ' ' . trim($tag);
|
||||
} else {
|
||||
$tags_string .= ' #' . trim($tag);
|
||||
}
|
||||
}
|
||||
|
||||
$data = ' ' . $message['title'] . ' ' . $message['body'] . ' ' . $tags_string . ' ';
|
||||
$data = ' ' . $item['title'] . ' ' . $item['body'] . ' ' . $tags_string . ' ';
|
||||
|
||||
// ignore anything in a code block
|
||||
$data = preg_replace('/\[code\](.*?)\[\/code\]/sm', '', $data);
|
||||
|
@ -107,11 +254,15 @@ class Term
|
|||
}
|
||||
}
|
||||
|
||||
$pattern = '/\W([\#@])\[url\=(.*?)\](.*?)\[\/url\]/ism';
|
||||
$pattern = '/\W([\#@!%])\[url\=(.*?)\](.*?)\[\/url\]/ism';
|
||||
if (preg_match_all($pattern, $data, $matches, PREG_SET_ORDER)) {
|
||||
foreach ($matches as $match) {
|
||||
|
||||
if ($match[1] == '@') {
|
||||
if (in_array($match[1], [
|
||||
self::TAG_CHARACTER[self::MENTION],
|
||||
self::TAG_CHARACTER[self::IMPLICIT_MENTION],
|
||||
self::TAG_CHARACTER[self::EXCLUSIVE_MENTION]
|
||||
])) {
|
||||
$contact = Contact::getDetailsByURL($match[2], 0);
|
||||
if (!empty($contact['addr'])) {
|
||||
$match[3] = $contact['addr'];
|
||||
|
@ -122,12 +273,12 @@ class Term
|
|||
}
|
||||
}
|
||||
|
||||
$tags[$match[1] . trim($match[3], ',.:;[]/\"?!')] = $match[2];
|
||||
$tags[$match[2]] = $match[1] . trim($match[3], ',.:;[]/\"?!');
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($tags as $tag => $link) {
|
||||
if (substr(trim($tag), 0, 1) == '#') {
|
||||
foreach ($tags as $link => $tag) {
|
||||
if (self::isType($tag, self::HASHTAG)) {
|
||||
// try to ignore #039 or #1 or anything like that
|
||||
if (ctype_digit(substr(trim($tag), 1))) {
|
||||
continue;
|
||||
|
@ -138,10 +289,15 @@ class Term
|
|||
continue;
|
||||
}
|
||||
|
||||
$type = TERM_HASHTAG;
|
||||
$type = self::HASHTAG;
|
||||
$term = substr($tag, 1);
|
||||
} elseif (substr(trim($tag), 0, 1) == '@') {
|
||||
$type = TERM_MENTION;
|
||||
$link = '';
|
||||
} elseif (self::isType($tag, self::MENTION, self::EXCLUSIVE_MENTION, self::IMPLICIT_MENTION)) {
|
||||
if (self::isType($tag, self::MENTION, self::EXCLUSIVE_MENTION)) {
|
||||
$type = self::MENTION;
|
||||
} else {
|
||||
$type = self::IMPLICIT_MENTION;
|
||||
}
|
||||
|
||||
$contact = Contact::getDetailsByURL($link, 0);
|
||||
if (!empty($contact['name'])) {
|
||||
|
@ -150,42 +306,51 @@ class Term
|
|||
$term = substr($tag, 1);
|
||||
}
|
||||
} else { // This shouldn't happen
|
||||
$type = TERM_HASHTAG;
|
||||
$type = self::HASHTAG;
|
||||
$term = $tag;
|
||||
$link = '';
|
||||
|
||||
Logger::notice('Unknown term type', ['tag' => $tag]);
|
||||
}
|
||||
|
||||
if (DBA::exists('term', ['uid' => $message['uid'], 'otype' => TERM_OBJ_POST, 'oid' => $itemid, 'url' => $link])) {
|
||||
if (DBA::exists('term', ['uid' => $item['uid'], 'otype' => self::OBJECT_TYPE_POST, 'oid' => $item_id, 'term' => $term, 'type' => $type])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($message['uid'] == 0) {
|
||||
if ($item['uid'] == 0) {
|
||||
$global = true;
|
||||
DBA::update('term', ['global' => true], ['otype' => TERM_OBJ_POST, 'guid' => $message['guid']]);
|
||||
DBA::update('term', ['global' => true], ['otype' => self::OBJECT_TYPE_POST, 'guid' => $item['guid']]);
|
||||
} else {
|
||||
$global = DBA::exists('term', ['uid' => 0, 'otype' => TERM_OBJ_POST, 'guid' => $message['guid']]);
|
||||
$global = DBA::exists('term', ['uid' => 0, 'otype' => self::OBJECT_TYPE_POST, 'guid' => $item['guid']]);
|
||||
}
|
||||
|
||||
DBA::insert('term', [
|
||||
'uid' => $message['uid'],
|
||||
'oid' => $itemid,
|
||||
'otype' => TERM_OBJ_POST,
|
||||
'uid' => $item['uid'],
|
||||
'oid' => $item_id,
|
||||
'otype' => self::OBJECT_TYPE_POST,
|
||||
'type' => $type,
|
||||
'term' => $term,
|
||||
'url' => $link,
|
||||
'guid' => $message['guid'],
|
||||
'created' => $message['created'],
|
||||
'received' => $message['received'],
|
||||
'guid' => $item['guid'],
|
||||
'created' => $item['created'],
|
||||
'received' => $item['received'],
|
||||
'global' => $global
|
||||
]);
|
||||
|
||||
// Search for mentions
|
||||
if ((substr($tag, 0, 1) == '@') && (strpos($link, $profile_base_friendica) || strpos($link, $profile_base_diaspora))) {
|
||||
$users = q("SELECT `uid` FROM `contact` WHERE self AND (`url` = '%s' OR `nurl` = '%s')", $link, $link);
|
||||
if (self::isType($tag, self::MENTION, self::EXCLUSIVE_MENTION)
|
||||
&& (
|
||||
strpos($link, $profile_base_friendica) !== false
|
||||
|| strpos($link, $profile_base_diaspora) !== false
|
||||
)
|
||||
) {
|
||||
$users_stmt = DBA::p("SELECT `uid` FROM `contact` WHERE self AND (`url` = ? OR `nurl` = ?)", $link, $link);
|
||||
$users = DBA::toArray($users_stmt);
|
||||
foreach ($users AS $user) {
|
||||
if ($user['uid'] == $message['uid']) {
|
||||
/// @todo This function is called frim Item::update - so we mustn't call that function here
|
||||
DBA::update('item', ['mention' => true], ['id' => $itemid]);
|
||||
DBA::update('thread', ['mention' => true], ['iid' => $message['parent']]);
|
||||
if ($user['uid'] == $item['uid']) {
|
||||
/// @todo This function is called from Item::update - so we mustn't call that function here
|
||||
DBA::update('item', ['mention' => true], ['id' => $item_id]);
|
||||
DBA::update('thread', ['mention' => true], ['iid' => $item['parent']]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -193,18 +358,23 @@ class Term
|
|||
}
|
||||
|
||||
/**
|
||||
* @param integer $itemid item id
|
||||
* Inserts new terms for the provided item ID based on the legacy item.file field BBCode content.
|
||||
* Deletes all previous file terms for the same item ID.
|
||||
*
|
||||
* @param integer $item_id item id
|
||||
* @param $files
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function insertFromFileFieldByItemId($itemid, $files)
|
||||
public static function insertFromFileFieldByItemId($item_id, $files)
|
||||
{
|
||||
$message = Item::selectFirst(['uid', 'deleted'], ['id' => $itemid]);
|
||||
$message = Item::selectFirst(['uid', 'deleted'], ['id' => $item_id]);
|
||||
if (!DBA::isResult($message)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clean up all tags
|
||||
DBA::delete('term', ['otype' => TERM_OBJ_POST, 'oid' => $itemid, 'type' => [TERM_FILE, TERM_CATEGORY]]);
|
||||
DBA::delete('term', ['otype' => self::OBJECT_TYPE_POST, 'oid' => $item_id, 'type' => [self::FILE, self::CATEGORY]]);
|
||||
|
||||
if ($message["deleted"]) {
|
||||
return;
|
||||
|
@ -216,9 +386,9 @@ class Term
|
|||
foreach ($files[1] as $file) {
|
||||
DBA::insert('term', [
|
||||
'uid' => $message["uid"],
|
||||
'oid' => $itemid,
|
||||
'otype' => TERM_OBJ_POST,
|
||||
'type' => TERM_FILE,
|
||||
'oid' => $item_id,
|
||||
'otype' => self::OBJECT_TYPE_POST,
|
||||
'type' => self::FILE,
|
||||
'term' => $file
|
||||
]);
|
||||
}
|
||||
|
@ -228,9 +398,9 @@ class Term
|
|||
foreach ($files[1] as $file) {
|
||||
DBA::insert('term', [
|
||||
'uid' => $message["uid"],
|
||||
'oid' => $itemid,
|
||||
'otype' => TERM_OBJ_POST,
|
||||
'type' => TERM_CATEGORY,
|
||||
'oid' => $item_id,
|
||||
'otype' => self::OBJECT_TYPE_POST,
|
||||
'type' => self::CATEGORY,
|
||||
'term' => $file
|
||||
]);
|
||||
}
|
||||
|
@ -243,6 +413,8 @@ class Term
|
|||
*
|
||||
* @param array $item
|
||||
* @return array
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
public static function populateTagsFromItem(&$item)
|
||||
{
|
||||
|
@ -250,6 +422,7 @@ class Term
|
|||
'tags' => [],
|
||||
'hashtags' => [],
|
||||
'mentions' => [],
|
||||
'implicit_mentions' => [],
|
||||
];
|
||||
|
||||
$searchpath = System::baseUrl() . "/search?tag=";
|
||||
|
@ -257,34 +430,35 @@ class Term
|
|||
$taglist = DBA::select(
|
||||
'term',
|
||||
['type', 'term', 'url'],
|
||||
["`otype` = ? AND `oid` = ? AND `type` IN (?, ?)", TERM_OBJ_POST, $item['id'], TERM_HASHTAG, TERM_MENTION],
|
||||
['otype' => self::OBJECT_TYPE_POST, 'oid' => $item['id'], 'type' => [self::HASHTAG, self::MENTION, self::IMPLICIT_MENTION]],
|
||||
['order' => ['tid']]
|
||||
);
|
||||
|
||||
while ($tag = DBA::fetch($taglist)) {
|
||||
if ($tag["url"] == "") {
|
||||
$tag["url"] = $searchpath . $tag["term"];
|
||||
if ($tag['url'] == '') {
|
||||
$tag['url'] = $searchpath . rawurlencode($tag['term']);
|
||||
}
|
||||
|
||||
$orig_tag = $tag["url"];
|
||||
$orig_tag = $tag['url'];
|
||||
|
||||
$author = ['uid' => 0, 'id' => $item['author-id'],
|
||||
'network' => $item['author-network'], 'url' => $item['author-link']];
|
||||
$tag["url"] = Contact::magicLinkByContact($author, $tag['url']);
|
||||
$prefix = self::TAG_CHARACTER[$tag['type']];
|
||||
switch($tag['type']) {
|
||||
case self::HASHTAG:
|
||||
if ($orig_tag != $tag['url']) {
|
||||
$item['body'] = str_replace($orig_tag, $tag['url'], $item['body']);
|
||||
}
|
||||
|
||||
if ($tag["type"] == TERM_HASHTAG) {
|
||||
if ($orig_tag != $tag["url"]) {
|
||||
$item['body'] = str_replace($orig_tag, $tag["url"], $item['body']);
|
||||
}
|
||||
|
||||
$return['hashtags'][] = "#<a href=\"" . $tag["url"] . "\" target=\"_blank\">" . $tag["term"] . "</a>";
|
||||
$prefix = "#";
|
||||
} elseif ($tag["type"] == TERM_MENTION) {
|
||||
$return['mentions'][] = "@<a href=\"" . $tag["url"] . "\" target=\"_blank\">" . $tag["term"] . "</a>";
|
||||
$prefix = "@";
|
||||
$return['hashtags'][] = $prefix . '<a href="' . $tag['url'] . '" target="_blank">' . $tag['term'] . '</a>';
|
||||
$return['tags'][] = $prefix . '<a href="' . $tag['url'] . '" target="_blank">' . $tag['term'] . '</a>';
|
||||
break;
|
||||
case self::MENTION:
|
||||
$tag['url'] = Contact::magicLink($tag['url']);
|
||||
$return['mentions'][] = $prefix . '<a href="' . $tag['url'] . '" target="_blank">' . $tag['term'] . '</a>';
|
||||
$return['tags'][] = $prefix . '<a href="' . $tag['url'] . '" target="_blank">' . $tag['term'] . '</a>';
|
||||
break;
|
||||
case self::IMPLICIT_MENTION:
|
||||
$return['implicit_mentions'][] = $prefix . $tag['term'];
|
||||
break;
|
||||
}
|
||||
|
||||
$return['tags'][] = $prefix . "<a href=\"" . $tag["url"] . "\" target=\"_blank\">" . $tag["term"] . "</a>";
|
||||
}
|
||||
DBA::close($taglist);
|
||||
|
||||
|
@ -292,18 +466,38 @@ class Term
|
|||
}
|
||||
|
||||
/**
|
||||
* Delete all tags from an item
|
||||
* @param int itemid - choose from which item the tags will be removed
|
||||
* @param array type - items type. default is [TERM_HASHTAG, TERM_MENTION]
|
||||
* Delete tags of the specific type(s) from an item
|
||||
*
|
||||
* @param int $item_id
|
||||
* @param int|array $type
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function deleteByItemId($itemid, $type = [TERM_HASHTAG, TERM_MENTION])
|
||||
public static function deleteByItemId($item_id, $type = [self::HASHTAG, self::MENTION, self::IMPLICIT_MENTION])
|
||||
{
|
||||
if (empty($itemid)) {
|
||||
if (empty($item_id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clean up all tags
|
||||
DBA::delete('term', ['otype' => TERM_OBJ_POST, 'oid' => $itemid, 'type' => $type]);
|
||||
DBA::delete('term', ['otype' => self::OBJECT_TYPE_POST, 'oid' => $item_id, 'type' => $type]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the provided tag is of one of the provided term types.
|
||||
*
|
||||
* @param string $tag
|
||||
* @param int ...$types
|
||||
* @return bool
|
||||
*/
|
||||
public static function isType($tag, ...$types)
|
||||
{
|
||||
$tag_chars = [];
|
||||
foreach ($types as $type) {
|
||||
if (array_key_exists($type, self::TAG_CHARACTER)) {
|
||||
$tag_chars[] = self::TAG_CHARACTER[$type];
|
||||
}
|
||||
}
|
||||
|
||||
return Strings::startsWith($tag, $tag_chars);
|
||||
}
|
||||
}
|
||||
|
|
137
src/Model/TwoFactor/AppSpecificPassword.php
Normal file
137
src/Model/TwoFactor/AppSpecificPassword.php
Normal file
|
@ -0,0 +1,137 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Model\TwoFactor;
|
||||
|
||||
use Friendica\BaseObject;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\Model\User;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
use Friendica\Util\Temporal;
|
||||
use PragmaRX\Random\Random;
|
||||
|
||||
/**
|
||||
* Manages users' two-factor recovery hashed_passwords in the 2fa_app_specific_passwords table
|
||||
*
|
||||
* @package Friendica\Model
|
||||
*/
|
||||
class AppSpecificPassword extends BaseObject
|
||||
{
|
||||
public static function countForUser($uid)
|
||||
{
|
||||
return DBA::count('2fa_app_specific_password', ['uid' => $uid]);
|
||||
}
|
||||
|
||||
public static function checkDuplicateForUser($uid, $description)
|
||||
{
|
||||
return DBA::exists('2fa_app_specific_password', ['uid' => $uid, 'description' => $description]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the provided hashed_password is available to use for login by the provided user
|
||||
*
|
||||
* @param int $uid User ID
|
||||
* @param string $plaintextPassword
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function authenticateUser($uid, $plaintextPassword)
|
||||
{
|
||||
$appSpecificPasswords = self::getListForUser($uid);
|
||||
|
||||
$return = false;
|
||||
|
||||
foreach ($appSpecificPasswords as $appSpecificPassword) {
|
||||
if (password_verify($plaintextPassword, $appSpecificPassword['hashed_password'])) {
|
||||
$fields = ['last_used' => DateTimeFormat::utcNow()];
|
||||
if (password_needs_rehash($appSpecificPassword['hashed_password'], PASSWORD_DEFAULT)) {
|
||||
$fields['hashed_password'] = User::hashPassword($plaintextPassword);
|
||||
}
|
||||
|
||||
self::update($appSpecificPassword['id'], $fields);
|
||||
|
||||
$return |= true;
|
||||
}
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a complete list of all recovery hashed_passwords for the provided user, including the used status
|
||||
*
|
||||
* @param int $uid User ID
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function getListForUser($uid)
|
||||
{
|
||||
$appSpecificPasswordsStmt = DBA::select('2fa_app_specific_password', ['id', 'description', 'hashed_password', 'last_used'], ['uid' => $uid]);
|
||||
|
||||
$appSpecificPasswords = DBA::toArray($appSpecificPasswordsStmt);
|
||||
|
||||
array_walk($appSpecificPasswords, function (&$value) {
|
||||
$value['ago'] = Temporal::getRelativeDate($value['last_used']);
|
||||
});
|
||||
|
||||
return $appSpecificPasswords;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a new app specific password for the provided user and hashes it in the database.
|
||||
*
|
||||
* @param int $uid User ID
|
||||
* @param string $description Password description
|
||||
* @return array The new app-specific password data structure with the plaintext password added
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function generateForUser(int $uid, $description)
|
||||
{
|
||||
$Random = (new Random())->size(40);
|
||||
|
||||
$plaintextPassword = $Random->get();
|
||||
|
||||
$generated = DateTimeFormat::utcNow();
|
||||
|
||||
$fields = [
|
||||
'uid' => $uid,
|
||||
'description' => $description,
|
||||
'hashed_password' => User::hashPassword($plaintextPassword),
|
||||
'generated' => $generated,
|
||||
];
|
||||
|
||||
DBA::insert('2fa_app_specific_password', $fields);
|
||||
|
||||
$fields['id'] = DBA::lastInsertId();
|
||||
$fields['plaintext_password'] = $plaintextPassword;
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
private static function update($appSpecificPasswordId, $fields)
|
||||
{
|
||||
return DBA::update('2fa_app_specific_password', $fields, ['id' => $appSpecificPasswordId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all the recovery hashed_passwords for the provided user.
|
||||
*
|
||||
* @param int $uid User ID
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function deleteAllForUser(int $uid)
|
||||
{
|
||||
return DBA::delete('2fa_app_specific_password', ['uid' => $uid]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $uid
|
||||
* @param int $app_specific_password_id
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function deleteForUser(int $uid, int $app_specific_password_id)
|
||||
{
|
||||
return DBA::delete('2fa_app_specific_password', ['id' => $app_specific_password_id, 'uid' => $uid]);
|
||||
}
|
||||
}
|
125
src/Model/TwoFactor/RecoveryCode.php
Normal file
125
src/Model/TwoFactor/RecoveryCode.php
Normal file
|
@ -0,0 +1,125 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Model\TwoFactor;
|
||||
|
||||
use Friendica\BaseObject;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
use PragmaRX\Random\Random;
|
||||
use PragmaRX\Recovery\Recovery;
|
||||
|
||||
/**
|
||||
* Manages users' two-factor recovery codes in the 2fa_recovery_codes table
|
||||
*
|
||||
* @package Friendica\Model
|
||||
*/
|
||||
class RecoveryCode extends BaseObject
|
||||
{
|
||||
/**
|
||||
* Returns the number of code the provided users can still use to replace a TOTP code
|
||||
*
|
||||
* @param int $uid User ID
|
||||
* @return int
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function countValidForUser($uid)
|
||||
{
|
||||
return DBA::count('2fa_recovery_codes', ['uid' => $uid, 'used' => null]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the provided code is available to use for login by the provided user
|
||||
*
|
||||
* @param int $uid User ID
|
||||
* @param string $code
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function existsForUser($uid, $code)
|
||||
{
|
||||
return DBA::exists('2fa_recovery_codes', ['uid' => $uid, 'code' => $code, 'used' => null]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a complete list of all recovery codes for the provided user, including the used status
|
||||
*
|
||||
* @param int $uid User ID
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function getListForUser($uid)
|
||||
{
|
||||
$codesStmt = DBA::select('2fa_recovery_codes', ['code', 'used'], ['uid' => $uid]);
|
||||
|
||||
return DBA::toArray($codesStmt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the provided code as used for the provided user.
|
||||
* Returns false if the code doesn't exist for the user or it has been used already.
|
||||
*
|
||||
* @param int $uid User ID
|
||||
* @param string $code
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function markUsedForUser($uid, $code)
|
||||
{
|
||||
DBA::update('2fa_recovery_codes', ['used' => DateTimeFormat::utcNow()], ['uid' => $uid, 'code' => $code, 'used' => null]);
|
||||
|
||||
return DBA::affectedRows() > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a fresh set of recovery codes for the provided user.
|
||||
* Generates 12 codes constituted of 2 blocks of 6 characters separated by a dash.
|
||||
*
|
||||
* @param int $uid User ID
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function generateForUser($uid)
|
||||
{
|
||||
$Random = (new Random())->pattern('[a-z0-9]');
|
||||
|
||||
$RecoveryGenerator = new Recovery($Random);
|
||||
|
||||
$codes = $RecoveryGenerator
|
||||
->setCount(12)
|
||||
->setBlocks(2)
|
||||
->setChars(6)
|
||||
->lowercase(true)
|
||||
->toArray();
|
||||
|
||||
$generated = DateTimeFormat::utcNow();
|
||||
foreach ($codes as $code) {
|
||||
DBA::insert('2fa_recovery_codes', [
|
||||
'uid' => $uid,
|
||||
'code' => $code,
|
||||
'generated' => $generated
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all the recovery codes for the provided user.
|
||||
*
|
||||
* @param int $uid User ID
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function deleteForUser($uid)
|
||||
{
|
||||
DBA::delete('2fa_recovery_codes', ['uid' => $uid]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the existing recovery codes for the provided user by a freshly generated set.
|
||||
*
|
||||
* @param int $uid User ID
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function regenerateForUser($uid)
|
||||
{
|
||||
self::deleteForUser($uid);
|
||||
self::generateForUser($uid);
|
||||
}
|
||||
}
|
|
@ -1,40 +1,93 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file src/Model/User.php
|
||||
* @brief This file includes the User class with user related database functions
|
||||
*/
|
||||
|
||||
namespace Friendica\Model;
|
||||
|
||||
use DivineOmega\PasswordExposed;
|
||||
use Exception;
|
||||
use Friendica\Core\Addon;
|
||||
use Friendica\Core\Config;
|
||||
use Friendica\Core\Hook;
|
||||
use Friendica\Core\L10n;
|
||||
use Friendica\Core\Logger;
|
||||
use Friendica\Core\PConfig;
|
||||
use Friendica\Core\Protocol;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Core\Worker;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\Model\Photo;
|
||||
use Friendica\Model\TwoFactor\AppSpecificPassword;
|
||||
use Friendica\Object\Image;
|
||||
use Friendica\Util\Crypto;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
use Friendica\Util\Network;
|
||||
use Friendica\Util\Strings;
|
||||
use Friendica\Worker\Delivery;
|
||||
use LightOpenID;
|
||||
|
||||
require_once 'boot.php';
|
||||
require_once 'include/dba.php';
|
||||
require_once 'include/enotify.php';
|
||||
require_once 'include/text.php';
|
||||
/**
|
||||
* @brief This class handles User related functions
|
||||
*/
|
||||
class User
|
||||
{
|
||||
/**
|
||||
* Page/profile types
|
||||
*
|
||||
* PAGE_FLAGS_NORMAL is a typical personal profile account
|
||||
* PAGE_FLAGS_SOAPBOX automatically approves all friend requests as Contact::SHARING, (readonly)
|
||||
* PAGE_FLAGS_COMMUNITY automatically approves all friend requests as Contact::SHARING, but with
|
||||
* write access to wall and comments (no email and not included in page owner's ACL lists)
|
||||
* PAGE_FLAGS_FREELOVE automatically approves all friend requests as full friends (Contact::FRIEND).
|
||||
*
|
||||
* @{
|
||||
*/
|
||||
const PAGE_FLAGS_NORMAL = 0;
|
||||
const PAGE_FLAGS_SOAPBOX = 1;
|
||||
const PAGE_FLAGS_COMMUNITY = 2;
|
||||
const PAGE_FLAGS_FREELOVE = 3;
|
||||
const PAGE_FLAGS_BLOG = 4;
|
||||
const PAGE_FLAGS_PRVGROUP = 5;
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Account types
|
||||
*
|
||||
* ACCOUNT_TYPE_PERSON - the account belongs to a person
|
||||
* Associated page types: PAGE_FLAGS_NORMAL, PAGE_FLAGS_SOAPBOX, PAGE_FLAGS_FREELOVE
|
||||
*
|
||||
* ACCOUNT_TYPE_ORGANISATION - the account belongs to an organisation
|
||||
* Associated page type: PAGE_FLAGS_SOAPBOX
|
||||
*
|
||||
* ACCOUNT_TYPE_NEWS - the account is a news reflector
|
||||
* Associated page type: PAGE_FLAGS_SOAPBOX
|
||||
*
|
||||
* ACCOUNT_TYPE_COMMUNITY - the account is community forum
|
||||
* Associated page types: PAGE_COMMUNITY, PAGE_FLAGS_PRVGROUP
|
||||
*
|
||||
* ACCOUNT_TYPE_RELAY - the account is a relay
|
||||
* This will only be assigned to contacts, not to user accounts
|
||||
* @{
|
||||
*/
|
||||
const ACCOUNT_TYPE_PERSON = 0;
|
||||
const ACCOUNT_TYPE_ORGANISATION = 1;
|
||||
const ACCOUNT_TYPE_NEWS = 2;
|
||||
const ACCOUNT_TYPE_COMMUNITY = 3;
|
||||
const ACCOUNT_TYPE_RELAY = 4;
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
||||
/**
|
||||
* Returns true if a user record exists with the provided id
|
||||
*
|
||||
* @param integer $uid
|
||||
* @return boolean
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function exists($uid)
|
||||
{
|
||||
|
@ -43,11 +96,24 @@ class User
|
|||
|
||||
/**
|
||||
* @param integer $uid
|
||||
* @param array $fields
|
||||
* @return array|boolean User record if it exists, false otherwise
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getById($uid)
|
||||
public static function getById($uid, array $fields = [])
|
||||
{
|
||||
return DBA::selectFirst('user', [], ['uid' => $uid]);
|
||||
return DBA::selectFirst('user', $fields, ['uid' => $uid]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $nickname
|
||||
* @param array $fields
|
||||
* @return array|boolean User record if it exists, false otherwise
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getByNickname($nickname, array $fields = [])
|
||||
{
|
||||
return DBA::selectFirst('user', $fields, ['nickname' => $nickname]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -56,10 +122,11 @@ class User
|
|||
* @param string $url
|
||||
*
|
||||
* @return integer user id
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getIdForURL($url)
|
||||
{
|
||||
$self = DBA::selectFirst('contact', ['uid'], ['nurl' => normalise_link($url), 'self' => true]);
|
||||
$self = DBA::selectFirst('contact', ['uid'], ['nurl' => Strings::normaliseLink($url), 'self' => true]);
|
||||
if (!DBA::isResult($self)) {
|
||||
return false;
|
||||
} else {
|
||||
|
@ -67,14 +134,33 @@ class User
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a user based on its email
|
||||
*
|
||||
* @param string $email
|
||||
* @param array $fields
|
||||
*
|
||||
* @return array|boolean User record if it exists, false otherwise
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getByEmail($email, array $fields = [])
|
||||
{
|
||||
return DBA::selectFirst('user', $fields, ['email' => $email]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get owner data by user id
|
||||
*
|
||||
* @param int $uid
|
||||
* @param boolean $check_valid Test if data is invalid and correct it
|
||||
* @return boolean|array
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getOwnerDataById($uid) {
|
||||
$r = DBA::fetchFirst("SELECT
|
||||
public static function getOwnerDataById($uid, $check_valid = true)
|
||||
{
|
||||
$r = DBA::fetchFirst(
|
||||
"SELECT
|
||||
`contact`.*,
|
||||
`user`.`prvkey` AS `uprvkey`,
|
||||
`user`.`timezone`,
|
||||
|
@ -83,7 +169,8 @@ class User
|
|||
`user`.`spubkey`,
|
||||
`user`.`page-flags`,
|
||||
`user`.`account-type`,
|
||||
`user`.`prvnets`
|
||||
`user`.`prvnets`,
|
||||
`user`.`account_removed`
|
||||
FROM `contact`
|
||||
INNER JOIN `user`
|
||||
ON `user`.`uid` = `contact`.`uid`
|
||||
|
@ -95,6 +182,41 @@ class User
|
|||
if (!DBA::isResult($r)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (empty($r['nickname'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$check_valid) {
|
||||
return $r;
|
||||
}
|
||||
|
||||
// Check if the returned data is valid, otherwise fix it. See issue #6122
|
||||
|
||||
// Check for correct url and normalised nurl
|
||||
$url = System::baseUrl() . '/profile/' . $r['nickname'];
|
||||
$repair = ($r['url'] != $url) || ($r['nurl'] != Strings::normaliseLink($r['url']));
|
||||
|
||||
if (!$repair) {
|
||||
// Check if "addr" is present and correct
|
||||
$addr = $r['nickname'] . '@' . substr(System::baseUrl(), strpos(System::baseUrl(), '://') + 3);
|
||||
$repair = ($addr != $r['addr']);
|
||||
}
|
||||
|
||||
if (!$repair) {
|
||||
// Check if the avatar field is filled and the photo directs to the correct path
|
||||
$avatar = Photo::selectFirst(['resource-id'], ['uid' => $uid, 'profile' => true]);
|
||||
if (DBA::isResult($avatar)) {
|
||||
$repair = empty($r['avatar']) || !strpos($r['photo'], $avatar['resource-id']);
|
||||
}
|
||||
}
|
||||
|
||||
if ($repair) {
|
||||
Contact::updateSelfFromUserID($uid);
|
||||
// Return the corrected data and avoid a loop
|
||||
$r = self::getOwnerDataById($uid, false);
|
||||
}
|
||||
|
||||
return $r;
|
||||
}
|
||||
|
||||
|
@ -103,6 +225,7 @@ class User
|
|||
*
|
||||
* @param int $nick
|
||||
* @return boolean|array
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getOwnerDataByNick($nick)
|
||||
{
|
||||
|
@ -122,6 +245,7 @@ class User
|
|||
* @param string $network network name
|
||||
*
|
||||
* @return int group id
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public static function getDefaultGroup($uid, $network = '')
|
||||
{
|
||||
|
@ -148,17 +272,18 @@ class User
|
|||
/**
|
||||
* Authenticate a user with a clear text password
|
||||
*
|
||||
* @brief Authenticate a user with a clear text password
|
||||
* @param mixed $user_info
|
||||
* @brief Authenticate a user with a clear text password
|
||||
* @param mixed $user_info
|
||||
* @param string $password
|
||||
* @param bool $third_party
|
||||
* @return int|boolean
|
||||
* @deprecated since version 3.6
|
||||
* @see User::getIdFromPasswordAuthentication()
|
||||
* @see User::getIdFromPasswordAuthentication()
|
||||
*/
|
||||
public static function authenticate($user_info, $password)
|
||||
public static function authenticate($user_info, $password, $third_party = false)
|
||||
{
|
||||
try {
|
||||
return self::getIdFromPasswordAuthentication($user_info, $password);
|
||||
return self::getIdFromPasswordAuthentication($user_info, $password, $third_party);
|
||||
} catch (Exception $ex) {
|
||||
return false;
|
||||
}
|
||||
|
@ -168,19 +293,25 @@ class User
|
|||
* Returns the user id associated with a successful password authentication
|
||||
*
|
||||
* @brief Authenticate a user with a clear text password
|
||||
* @param mixed $user_info
|
||||
* @param mixed $user_info
|
||||
* @param string $password
|
||||
* @param bool $third_party
|
||||
* @return int User Id if authentication is successful
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getIdFromPasswordAuthentication($user_info, $password)
|
||||
public static function getIdFromPasswordAuthentication($user_info, $password, $third_party = false)
|
||||
{
|
||||
$user = self::getAuthenticationInfo($user_info);
|
||||
|
||||
if (strpos($user['password'], '$') === false) {
|
||||
if ($third_party && PConfig::get($user['uid'], '2fa', 'verified')) {
|
||||
// Third-party apps can't verify two-factor authentication, we use app-specific passwords instead
|
||||
if (AppSpecificPassword::authenticateUser($user['uid'], $password)) {
|
||||
return $user['uid'];
|
||||
}
|
||||
} elseif (strpos($user['password'], '$') === false) {
|
||||
//Legacy hash that has not been replaced by a new hash yet
|
||||
if (self::hashPasswordLegacy($password) === $user['password']) {
|
||||
self::updatePassword($user['uid'], $password);
|
||||
self::updatePasswordHashed($user['uid'], self::hashPassword($password));
|
||||
|
||||
return $user['uid'];
|
||||
}
|
||||
|
@ -188,14 +319,14 @@ class User
|
|||
//Legacy hash that has been double-hashed and not replaced by a new hash yet
|
||||
//Warning: `legacy_password` is not necessary in sync with the content of `password`
|
||||
if (password_verify(self::hashPasswordLegacy($password), $user['password'])) {
|
||||
self::updatePassword($user['uid'], $password);
|
||||
self::updatePasswordHashed($user['uid'], self::hashPassword($password));
|
||||
|
||||
return $user['uid'];
|
||||
}
|
||||
} elseif (password_verify($password, $user['password'])) {
|
||||
//New password hash
|
||||
if (password_needs_rehash($user['password'], PASSWORD_DEFAULT)) {
|
||||
self::updatePassword($user['uid'], $password);
|
||||
self::updatePasswordHashed($user['uid'], self::hashPassword($password));
|
||||
}
|
||||
|
||||
return $user['uid'];
|
||||
|
@ -228,7 +359,8 @@ class User
|
|||
$user = $user_info;
|
||||
}
|
||||
|
||||
if (!isset($user['uid'])
|
||||
if (
|
||||
!isset($user['uid'])
|
||||
|| !isset($user['password'])
|
||||
|| !isset($user['legacy_password'])
|
||||
) {
|
||||
|
@ -236,7 +368,9 @@ class User
|
|||
}
|
||||
} elseif (is_int($user_info) || is_string($user_info)) {
|
||||
if (is_int($user_info)) {
|
||||
$user = DBA::selectFirst('user', ['uid', 'password', 'legacy_password'],
|
||||
$user = DBA::selectFirst(
|
||||
'user',
|
||||
['uid', 'password', 'legacy_password'],
|
||||
[
|
||||
'uid' => $user_info,
|
||||
'blocked' => 0,
|
||||
|
@ -247,9 +381,11 @@ class User
|
|||
);
|
||||
} else {
|
||||
$fields = ['uid', 'password', 'legacy_password'];
|
||||
$condition = ["(`email` = ? OR `username` = ? OR `nickname` = ?)
|
||||
$condition = [
|
||||
"(`email` = ? OR `username` = ? OR `nickname` = ?)
|
||||
AND NOT `blocked` AND NOT `account_expired` AND NOT `account_removed` AND `verified`",
|
||||
$user_info, $user_info, $user_info];
|
||||
$user_info, $user_info, $user_info
|
||||
];
|
||||
$user = DBA::selectFirst('user', $fields, $condition);
|
||||
}
|
||||
|
||||
|
@ -268,7 +404,7 @@ class User
|
|||
*/
|
||||
public static function generateNewPassword()
|
||||
{
|
||||
return autoname(6) . mt_rand(100, 9999);
|
||||
return ucfirst(Strings::getRandomName(8)) . random_int(1000, 9999);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -276,6 +412,7 @@ class User
|
|||
*
|
||||
* @param string $password
|
||||
* @return bool
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function isPasswordExposed($password)
|
||||
{
|
||||
|
@ -284,9 +421,20 @@ class User
|
|||
'cacheDirectory' => get_temppath() . '/password-exposed-cache/',
|
||||
]);
|
||||
|
||||
$PasswordExposedCHecker = new PasswordExposed\PasswordExposedChecker(null, $cache);
|
||||
try {
|
||||
$passwordExposedChecker = new PasswordExposed\PasswordExposedChecker(null, $cache);
|
||||
|
||||
return $PasswordExposedCHecker->passwordExposed($password) === PasswordExposed\PasswordStatus::EXPOSED;
|
||||
return $passwordExposedChecker->passwordExposed($password) === PasswordExposed\PasswordStatus::EXPOSED;
|
||||
} catch (\Exception $e) {
|
||||
Logger::error('Password Exposed Exception: ' . $e->getMessage(), [
|
||||
'code' => $e->getCode(),
|
||||
'file' => $e->getFile(),
|
||||
'line' => $e->getLine(),
|
||||
'trace' => $e->getTraceAsString()
|
||||
]);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -305,6 +453,7 @@ class User
|
|||
*
|
||||
* @param string $password
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function hashPassword($password)
|
||||
{
|
||||
|
@ -321,9 +470,26 @@ class User
|
|||
* @param int $uid
|
||||
* @param string $password
|
||||
* @return bool
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function updatePassword($uid, $password)
|
||||
{
|
||||
$password = trim($password);
|
||||
|
||||
if (empty($password)) {
|
||||
throw new Exception(L10n::t('Empty passwords are not allowed.'));
|
||||
}
|
||||
|
||||
if (!Config::get('system', 'disable_password_exposed', false) && self::isPasswordExposed($password)) {
|
||||
throw new Exception(L10n::t('The new password has been exposed in a public data dump, please choose another.'));
|
||||
}
|
||||
|
||||
$allowed_characters = '!"#$%&\'()*+,-./;<=>?@[\]^_`{|}~';
|
||||
|
||||
if (!preg_match('/^[a-z0-9' . preg_quote($allowed_characters, '/') . ']+$/i', $password)) {
|
||||
throw new Exception(L10n::t('The password can\'t contain accentuated letters, white spaces or colons (:)'));
|
||||
}
|
||||
|
||||
return self::updatePasswordHashed($uid, self::hashPassword($password));
|
||||
}
|
||||
|
||||
|
@ -334,6 +500,7 @@ class User
|
|||
* @param int $uid
|
||||
* @param string $pasword_hashed
|
||||
* @return bool
|
||||
* @throws Exception
|
||||
*/
|
||||
private static function updatePasswordHashed($uid, $pasword_hashed)
|
||||
{
|
||||
|
@ -355,6 +522,7 @@ class User
|
|||
*
|
||||
* @param string $nickname The nickname that should be checked
|
||||
* @return boolean True is the nickname is blocked on the node
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public static function isNicknameBlocked($nickname)
|
||||
{
|
||||
|
@ -388,33 +556,35 @@ class User
|
|||
* - Create self-contact
|
||||
* - Create profile image
|
||||
*
|
||||
* @param array $data
|
||||
* @return string
|
||||
* @throw Exception
|
||||
* @param array $data
|
||||
* @return array
|
||||
* @throws \ErrorException
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function create(array $data)
|
||||
{
|
||||
$a = get_app();
|
||||
$a = \get_app();
|
||||
$return = ['user' => null, 'password' => ''];
|
||||
|
||||
$using_invites = Config::get('system', 'invitation_only');
|
||||
$num_invites = Config::get('system', 'number_invites');
|
||||
|
||||
$invite_id = !empty($data['invite_id']) ? notags(trim($data['invite_id'])) : '';
|
||||
$username = !empty($data['username']) ? notags(trim($data['username'])) : '';
|
||||
$nickname = !empty($data['nickname']) ? notags(trim($data['nickname'])) : '';
|
||||
$email = !empty($data['email']) ? notags(trim($data['email'])) : '';
|
||||
$openid_url = !empty($data['openid_url']) ? notags(trim($data['openid_url'])) : '';
|
||||
$photo = !empty($data['photo']) ? notags(trim($data['photo'])) : '';
|
||||
$invite_id = !empty($data['invite_id']) ? Strings::escapeTags(trim($data['invite_id'])) : '';
|
||||
$username = !empty($data['username']) ? Strings::escapeTags(trim($data['username'])) : '';
|
||||
$nickname = !empty($data['nickname']) ? Strings::escapeTags(trim($data['nickname'])) : '';
|
||||
$email = !empty($data['email']) ? Strings::escapeTags(trim($data['email'])) : '';
|
||||
$openid_url = !empty($data['openid_url']) ? Strings::escapeTags(trim($data['openid_url'])) : '';
|
||||
$photo = !empty($data['photo']) ? Strings::escapeTags(trim($data['photo'])) : '';
|
||||
$password = !empty($data['password']) ? trim($data['password']) : '';
|
||||
$password1 = !empty($data['password1']) ? trim($data['password1']) : '';
|
||||
$confirm = !empty($data['confirm']) ? trim($data['confirm']) : '';
|
||||
$blocked = !empty($data['blocked']) ? intval($data['blocked']) : 0;
|
||||
$verified = !empty($data['verified']) ? intval($data['verified']) : 0;
|
||||
$language = !empty($data['language']) ? notags(trim($data['language'])) : 'en';
|
||||
$blocked = !empty($data['blocked']);
|
||||
$verified = !empty($data['verified']);
|
||||
$language = !empty($data['language']) ? Strings::escapeTags(trim($data['language'])) : 'en';
|
||||
|
||||
$publish = !empty($data['profile_publish_reg']) && intval($data['profile_publish_reg']) ? 1 : 0;
|
||||
$netpublish = strlen(Config::get('system', 'directory')) ? $publish : 0;
|
||||
$publish = !empty($data['profile_publish_reg']);
|
||||
$netpublish = $publish && Config::get('system', 'directory');
|
||||
|
||||
if ($password1 != $confirm) {
|
||||
throw new Exception(L10n::t('Passwords do not match. Password unchanged.'));
|
||||
|
@ -461,8 +631,6 @@ class User
|
|||
$openid_url = '';
|
||||
}
|
||||
|
||||
$err = '';
|
||||
|
||||
// collapse multiple spaces in name
|
||||
$username = preg_replace('/ +/', ' ', $username);
|
||||
|
||||
|
@ -470,7 +638,7 @@ class User
|
|||
$username_max_length = max(1, min(64, intval(Config::get('system', 'username_max_length', 48))));
|
||||
|
||||
if ($username_min_length > $username_max_length) {
|
||||
logger(L10n::t('system.username_min_length (%s) and system.username_max_length (%s) are excluding each other, swapping values.', $username_min_length, $username_max_length), LOGGER_WARNING);
|
||||
Logger::log(L10n::t('system.username_min_length (%s) and system.username_max_length (%s) are excluding each other, swapping values.', $username_min_length, $username_max_length), Logger::WARNING);
|
||||
$tmp = $username_min_length;
|
||||
$username_min_length = $username_max_length;
|
||||
$username_max_length = $tmp;
|
||||
|
@ -497,7 +665,7 @@ class User
|
|||
throw new Exception(L10n::t('Your email domain is not among those allowed on this site.'));
|
||||
}
|
||||
|
||||
if (!valid_email($email) || !Network::isEmailDomainValid($email)) {
|
||||
if (!filter_var($email, FILTER_VALIDATE_EMAIL) || !Network::isEmailDomainValid($email)) {
|
||||
throw new Exception(L10n::t('Not a valid email address.'));
|
||||
}
|
||||
if (self::isNicknameBlocked($nickname)) {
|
||||
|
@ -524,7 +692,8 @@ class User
|
|||
}
|
||||
|
||||
// Check existing and deleted accounts for this nickname.
|
||||
if (DBA::exists('user', ['nickname' => $nickname])
|
||||
if (
|
||||
DBA::exists('user', ['nickname' => $nickname])
|
||||
|| DBA::exists('userd', ['username' => $nickname])
|
||||
) {
|
||||
throw new Exception(L10n::t('Nickname is already registered. Please choose another.'));
|
||||
|
@ -669,12 +838,12 @@ class User
|
|||
}
|
||||
|
||||
if (!$photo_failure) {
|
||||
DBA::update('photo', ['profile' => 1], ['resource-id' => $hash]);
|
||||
Photo::update(['profile' => 1], ['resource-id' => $hash]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Addon::callHooks('register_account', $uid);
|
||||
Hook::callAll('register_account', $uid);
|
||||
|
||||
$return['user'] = $user;
|
||||
return $return;
|
||||
|
@ -688,10 +857,12 @@ class User
|
|||
* @param string $siteurl
|
||||
* @param string $password Plaintext password
|
||||
* @return NULL|boolean from notification() and email() inherited
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public static function sendRegisterPendingEmail($user, $sitename, $siteurl, $password)
|
||||
{
|
||||
$body = deindent(L10n::t('
|
||||
$body = Strings::deindent(L10n::t(
|
||||
'
|
||||
Dear %1$s,
|
||||
Thank you for registering at %2$s. Your account is pending for approval by the administrator.
|
||||
|
||||
|
@ -701,7 +872,11 @@ class User
|
|||
Login Name: %4$s
|
||||
Password: %5$s
|
||||
',
|
||||
$user['username'], $sitename, $siteurl, $user['nickname'], $password
|
||||
$user['username'],
|
||||
$sitename,
|
||||
$siteurl,
|
||||
$user['nickname'],
|
||||
$password
|
||||
));
|
||||
|
||||
return notification([
|
||||
|
@ -723,16 +898,20 @@ class User
|
|||
* @param string $siteurl
|
||||
* @param string $password Plaintext password
|
||||
* @return NULL|boolean from notification() and email() inherited
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public static function sendRegisterOpenEmail($user, $sitename, $siteurl, $password)
|
||||
{
|
||||
$preamble = deindent(L10n::t('
|
||||
Dear %1$s,
|
||||
$preamble = Strings::deindent(L10n::t(
|
||||
'
|
||||
Dear %1$s,
|
||||
Thank you for registering at %2$s. Your account has been created.
|
||||
',
|
||||
$preamble, $user['username'], $sitename
|
||||
',
|
||||
$user['username'],
|
||||
$sitename
|
||||
));
|
||||
$body = deindent(L10n::t('
|
||||
$body = Strings::deindent(L10n::t(
|
||||
'
|
||||
The login details are as follows:
|
||||
|
||||
Site Location: %3$s
|
||||
|
@ -759,7 +938,11 @@ class User
|
|||
If you ever want to delete your account, you can do so at %3$s/removeme
|
||||
|
||||
Thank you and welcome to %2$s.',
|
||||
$user['email'], $sitename, $siteurl, $user['username'], $password
|
||||
$user['nickname'],
|
||||
$sitename,
|
||||
$siteurl,
|
||||
$user['username'],
|
||||
$password
|
||||
));
|
||||
|
||||
return notification([
|
||||
|
@ -775,41 +958,166 @@ class User
|
|||
|
||||
/**
|
||||
* @param object $uid user to remove
|
||||
* @return void
|
||||
* @return bool
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public static function remove($uid)
|
||||
{
|
||||
if (!$uid) {
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
$a = get_app();
|
||||
|
||||
logger('Removing user: ' . $uid);
|
||||
Logger::log('Removing user: ' . $uid);
|
||||
|
||||
$user = DBA::selectFirst('user', [], ['uid' => $uid]);
|
||||
|
||||
Addon::callHooks('remove_user', $user);
|
||||
Hook::callAll('remove_user', $user);
|
||||
|
||||
// save username (actually the nickname as it is guaranteed
|
||||
// unique), so it cannot be re-registered in the future.
|
||||
DBA::insert('userd', ['username' => $user['nickname']]);
|
||||
|
||||
// The user and related data will be deleted in "cron_expire_and_remove_users" (cronjobs.php)
|
||||
DBA::update('user', ['account_removed' => true, 'account_expires_on' => DateTimeFormat::utc(DateTimeFormat::utcNow() . " + 7 day")], ['uid' => $uid]);
|
||||
Worker::add(PRIORITY_HIGH, "Notifier", "removeme", $uid);
|
||||
DBA::update('user', ['account_removed' => true, 'account_expires_on' => DateTimeFormat::utc('now + 7 day')], ['uid' => $uid]);
|
||||
Worker::add(PRIORITY_HIGH, 'Notifier', Delivery::REMOVAL, $uid);
|
||||
|
||||
// Send an update to the directory
|
||||
$self = DBA::selectFirst('contact', ['url'], ['uid' => $uid, 'self' => true]);
|
||||
Worker::add(PRIORITY_LOW, "Directory", $self['url']);
|
||||
Worker::add(PRIORITY_LOW, 'Directory', $self['url']);
|
||||
|
||||
// Remove the user relevant data
|
||||
Worker::add(PRIORITY_LOW, "RemoveUser", $uid);
|
||||
Worker::add(PRIORITY_NEGLIGIBLE, 'RemoveUser', $uid);
|
||||
|
||||
if ($uid == local_user()) {
|
||||
unset($_SESSION['authenticated']);
|
||||
unset($_SESSION['uid']);
|
||||
$a->internalRedirect();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all identities to a user
|
||||
*
|
||||
* @param int $uid The user id
|
||||
* @return array All identities for this user
|
||||
*
|
||||
* Example for a return:
|
||||
* [
|
||||
* [
|
||||
* 'uid' => 1,
|
||||
* 'username' => 'maxmuster',
|
||||
* 'nickname' => 'Max Mustermann'
|
||||
* ],
|
||||
* [
|
||||
* 'uid' => 2,
|
||||
* 'username' => 'johndoe',
|
||||
* 'nickname' => 'John Doe'
|
||||
* ]
|
||||
* ]
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function identities($uid)
|
||||
{
|
||||
$identities = [];
|
||||
|
||||
$user = DBA::selectFirst('user', ['uid', 'nickname', 'username', 'parent-uid'], ['uid' => $uid]);
|
||||
if (!DBA::isResult($user)) {
|
||||
return $identities;
|
||||
}
|
||||
|
||||
if ($user['parent-uid'] == 0) {
|
||||
// First add our own entry
|
||||
$identities = [[
|
||||
'uid' => $user['uid'],
|
||||
'username' => $user['username'],
|
||||
'nickname' => $user['nickname']
|
||||
]];
|
||||
|
||||
// Then add all the children
|
||||
$r = DBA::select(
|
||||
'user',
|
||||
['uid', 'username', 'nickname'],
|
||||
['parent-uid' => $user['uid'], 'account_removed' => false]
|
||||
);
|
||||
if (DBA::isResult($r)) {
|
||||
$identities = array_merge($identities, DBA::toArray($r));
|
||||
}
|
||||
} else {
|
||||
// First entry is our parent
|
||||
$r = DBA::select(
|
||||
'user',
|
||||
['uid', 'username', 'nickname'],
|
||||
['uid' => $user['parent-uid'], 'account_removed' => false]
|
||||
);
|
||||
if (DBA::isResult($r)) {
|
||||
$identities = DBA::toArray($r);
|
||||
}
|
||||
|
||||
// Then add all siblings
|
||||
$r = DBA::select(
|
||||
'user',
|
||||
['uid', 'username', 'nickname'],
|
||||
['parent-uid' => $user['parent-uid'], 'account_removed' => false]
|
||||
);
|
||||
if (DBA::isResult($r)) {
|
||||
$identities = array_merge($identities, DBA::toArray($r));
|
||||
}
|
||||
}
|
||||
|
||||
$r = DBA::p(
|
||||
"SELECT `user`.`uid`, `user`.`username`, `user`.`nickname`
|
||||
FROM `manage`
|
||||
INNER JOIN `user` ON `manage`.`mid` = `user`.`uid`
|
||||
WHERE `user`.`account_removed` = 0 AND `manage`.`uid` = ?",
|
||||
$user['uid']
|
||||
);
|
||||
if (DBA::isResult($r)) {
|
||||
$identities = array_merge($identities, DBA::toArray($r));
|
||||
}
|
||||
|
||||
return $identities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns statistical information about the current users of this node
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getStatistics()
|
||||
{
|
||||
$statistics = [
|
||||
'total_users' => 0,
|
||||
'active_users_halfyear' => 0,
|
||||
'active_users_monthly' => 0,
|
||||
];
|
||||
|
||||
$userStmt = DBA::p("SELECT `user`.`uid`, `user`.`login_date`, `contact`.`last-item`
|
||||
FROM `user`
|
||||
INNER JOIN `profile` ON `profile`.`uid` = `user`.`uid` AND `profile`.`is-default`
|
||||
INNER JOIN `contact` ON `contact`.`uid` = `user`.`uid` AND `contact`.`self`
|
||||
WHERE (`profile`.`publish` OR `profile`.`net-publish`) AND `user`.`verified`
|
||||
AND NOT `user`.`blocked` AND NOT `user`.`account_removed`
|
||||
AND NOT `user`.`account_expired`");
|
||||
|
||||
if (!DBA::isResult($userStmt)) {
|
||||
return $statistics;
|
||||
}
|
||||
|
||||
$halfyear = time() - (180 * 24 * 60 * 60);
|
||||
$month = time() - (30 * 24 * 60 * 60);
|
||||
|
||||
while ($user = DBA::fetch($userStmt)) {
|
||||
$statistics['total_users']++;
|
||||
|
||||
if ((strtotime($user['login_date']) > $halfyear) || (strtotime($user['last-item']) > $halfyear)
|
||||
) {
|
||||
$statistics['active_users_halfyear']++;
|
||||
}
|
||||
|
||||
if ((strtotime($user['login_date']) > $month) || (strtotime($user['last-item']) > $month)
|
||||
) {
|
||||
$statistics['active_users_monthly']++;
|
||||
}
|
||||
}
|
||||
|
||||
return $statistics;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue