Merge branch '2020.06-rc' into stable

This commit is contained in:
Tobias Diekershoff 2020-07-12 20:46:59 +02:00
commit dc42dbb68a
302 changed files with 78831 additions and 76304 deletions

View file

@ -8,7 +8,7 @@ php:
services:
- mysql
- redis-server
- redis
- memcached
env:
- MYSQL_HOST=localhost MYSQL_PORT=3306 MYSQL_USERNAME=travis MYSQL_PASSWORD="" MYSQL_DATABASE=test

View file

@ -1,3 +1,61 @@
Version 2020.07 (2020-07-12)
Friendica Core:
Update to the translations: DE, EN GB, EN US, FR, ET, NL, PL, RU, ZH-CN [translation teams]
Updates to the themes (frio, vier) [MrPetovan]
Updated the shipped composer version, and the dependency list [annando, MrPetovan, tobiasd]
Updates to the documentation [MrPetovan]
General code refactoring and enhancements [AlfredSK, annando, MrPetovan]
Replace charged terms with "allowlist", "denylist" and "blocklist" [MrPetovan]
Enhanced the comment distribution in threads that involve diaspora*, AP and DFRN actors [annando]
Enhanced the profile probing mechanism [annando, MrPetovan]
Enhanced the post update process of the database [annando]
Enhanced the database performance [annando]
Enhanced ActivityPub attachment handling [MrPetovan]
Enhanced security of redirections [annando]
Enhanced database performance [annando]
Enhanced the handling of BBCode [pre] tags [MrPetovan]
Enhanced Markdown to BBCode conversion [MrPetovan]
Enhanced the speed of the network page [annando]
Fixed a problem recognising logins via the API [MrPetovan]
Fixed a problem with handling local diaspora* URLs [MrPetovan]
Fixed a problem with implicit mentions [annando]
Fixed a problem with the password reset token security [lynn-stephenson]
Fixed a problem with receiving non-public posts via ActivityPub [annando]
Fixed a problem with the photo endpoint of the API [MrPetovan]
Fixed a problem with pressing the ESC key in the frio-theme [MrPetovan]
Fixed a problem with the display if post categories [annando]
Fixed a problem with validation of feeds [annando]
Fixed a problem that prevented AP activities being fetched sometimes [annando]
Renamed the -q option of the console user delete command to -y [MrPetovan]
Added notification count to page title [MrPetovan]
Added handling of relative URLs during feed detection [MrPetovan]
Added entities [nupplaphil]
Friendica Addons:
Update to the translations (EN GB, NB NO, NL, PL, RU, ZH CN) [translation teams]
blockbot:
The list of accepted user agents was enhanced [annando]
Diaspora*:
Enhanced conntector settings [MrPetovan]
PHP Mailer SMTP:
Updated phpmailer version [dependabot]
showmore_dyn:
New addon to collapse long post depending on their actual height [wiwie]
twitter:
Enhaceed the handling of mobile twitter URLs [annando]
Enhanced the handling of quoted tweets [MrPetovan]
added HTML error code handling [MrPetovan]
various:
enhancements to the probe mechanism [MrPetovan]
Closed Issues:
3084, 3884, 8287, 8314, 8374, 8400, 8425, 8432, 8458, 8470, 8477,
8482, 8488, 8489, 8490, 8493, 8495, 8498, 8511, 8517, 8523, 8527,
8551, 8553, 8560, 8564, 8565, 8568, 8578, 8586, 8593, 8606, 8610,
8612, 8626, 8664, 8672, 8683, 8685, 8691, 8694, 8702, 8709, 8714,
8717, 8722, 8726, 8732, 8736, 8743, 8744, 8746, 8756, 8766, 8769,
8781, 8800, 8807, 8808, 8827, 8829, 8836, 8844, 8846, 8857, 8866
Version 2020.03 "Red Hot Poker" (2020-03-30)
Friendica Core:
Updates to the translations (DE, FR, JA, NL, PL, RU, ZH-CN) [translation teams]
@ -52,7 +110,7 @@ Version 2020.03 "Red Hot Poker" (2020-03-30)
Update to the translations (CS, DE, FR, PL, RU, ZH-CN) [translation teams]
General code refactoring and enhancements [AndyHee, annando, MrPetovan, nupplaphil]
blockbot:
Ensure that good agents are whitelisted [valvin1]
Ensure that good agents are allowlisted [valvin1]
markdown:
Addon to use Markdown while composing a posting was added [annando]
showmore:
@ -902,7 +960,7 @@ Version 3.5.3 (2017-10-05)
Updates to the documentation [tobiasd]
Code revision and refactoring [Hypolite]
pumpio, twitter bridges adopted to new background mechanism [annando]
Leistungsschutzrecht has a new source list, and a whitelist [annando]
Leistungsschutzrecht has a new source list, and an allowlist [annando]
retriever marked unsupported due to unwanted side-effects [annando]
Unicode emoji added [annando]
Enhancement to the general content filter [annando]
@ -1364,7 +1422,7 @@ Version 3.3.1 (2014-11-06)
Set default location to empty for new users. Suppress warning on user creation (issue #1193) (fabrixxm)
Correctly build urls with queries (issue #1190) (fabrixxm)
Optionally use keywords in feed as post tags with "remote self" (annando)
A blacklist of keywords to not use can be defined (annando)
A denylist of keywords to not use can be defined (annando)
"remote self" works also with Friendica and Diaspora contacts (annando)
Show exact post time after 12 hours (FX7)
Optionally redirect from non-SSL to SSL (annando)

View file

@ -9,6 +9,7 @@ Aditoo
AgnesElisa
Albert
Alberto Díaz Tormo
Aleksandr "M.O.Z.G" Dikov
Alex
Alexander An
Alexander Fortin
@ -131,6 +132,7 @@ julia.domagalska
Julio Cova
Karel
Karolina
Keenan Pepper
Keith Fernie
Klaus Weidenbach
Koyu Berteon
@ -141,8 +143,10 @@ Leberwurscht
Leonard Lausen
Lionel Triay
loma-one
loma1
Lorem Ipsum
Ludovic Grossard
Lynn Stephenson
maase2
Magdalena Gazda
Mai Anh Nguyen
@ -231,6 +235,7 @@ St John Karp
Stanislav N.
Steffen K9
StefOfficiel
steve jobs
Sveinn í Felli
Sven Anders
Sylke Vicious

View file

@ -1 +1 @@
2020.03
2020.06-rc

Binary file not shown.

View file

@ -32,7 +32,7 @@ case "$MODE" in
mkdir -p "$FULLPATH/../addon/$ADDONNAME/lang/C"
OUTFILE="$FULLPATH/../addon/$ADDONNAME/lang/C/messages.po"
FINDSTARTDIR="."
FINDOPTS=
FINDOPTS="-path ./vendor -prune -or"
;;
'single')
FULLPATH=$PWD
@ -40,7 +40,7 @@ case "$MODE" in
mkdir -p "$FULLPATH/lang/C"
OUTFILE="$FULLPATH/lang/C/messages.po"
FINDSTARTDIR="."
FINDOPTS=
FINDOPTS="-path ./vendor -prune -or"
echo "Extract strings for single addon '$ADDONNAME'"
;;
'default')
@ -48,7 +48,7 @@ case "$MODE" in
OUTFILE="$FULLPATH/../view/lang/C/messages.po"
FINDSTARTDIR="."
# skip addon folder
FINDOPTS="( -wholename */addon -or -wholename */addons -or -wholename */addons-extra -or -wholename */smarty3 ) -prune -o"
FINDOPTS="( -path ./addon -or -path ./addons -or -path ./addons-extra -or -path ./tests -or -path ./view/lang -or -path ./view/smarty3 -or -path ./vendor ) -prune -or"
F9KVERSION=$(cat ./VERSION);
echo "Friendica version $F9KVERSION"
@ -58,44 +58,54 @@ esac
KEYWORDS="-k -kt -ktt:1,2"
echo "extract strings to $OUTFILE.."
echo "Extract strings to $OUTFILE.."
rm "$OUTFILE"; touch "$OUTFILE"
for f in $(find "$FINDSTARTDIR" $FINDOPTS -name "*.php" -type f)
find_result=$(find "$FINDSTARTDIR" $FINDOPTS -name "*.php" -type f)
total_files=$(wc -l <<< "${find_result}")
for file in $find_result
do
if [ ! -d "$f" ]
((count++))
echo -ne " \r"
echo -ne "Reading file $count/$total_files..."
# On Windows, find still outputs the name of pruned folders
if [ ! -d "$file" ]
then
xgettext $KEYWORDS -j -o "$OUTFILE" --from-code=UTF-8 "$f"
xgettext $KEYWORDS -j -o "$OUTFILE" --from-code=UTF-8 "$file" || exit 1
sed -i "s/CHARSET/UTF-8/g" "$OUTFILE"
fi
done
echo -ne "\n"
echo "setup base info.."
case "$MODE" in
echo "Interpolate metadata.."
sed -i "s/^\"Plural-Forms.*$//g" "$OUTFILE"
case "$MODE" in
'addon'|'single')
sed -i "s/SOME DESCRIPTIVE TITLE./ADDON $ADDONNAME/g" "$OUTFILE"
sed -i "s/YEAR THE PACKAGE'S COPYRIGHT HOLDER//g" "$OUTFILE"
sed -i "s/FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.//g" "$OUTFILE"
sed -i "s/PACKAGE VERSION//g" "$OUTFILE"
sed -i "s/PACKAGE/Friendica $ADDONNAME addon/g" "$OUTFILE"
sed -i "s/CHARSET/UTF-8/g" "$OUTFILE"
sed -i "s/^\"Plural-Forms.*$//g" "$OUTFILE"
;;
'default')
sed -i "s/SOME DESCRIPTIVE TITLE./FRIENDICA Distributed Social Network/g" "$OUTFILE"
sed -i "s/YEAR THE PACKAGE'S COPYRIGHT HOLDER/2010, 2011, 2012, 2013 the Friendica Project/g" "$OUTFILE"
sed -i "s/YEAR THE PACKAGE'S COPYRIGHT HOLDER/2010-$(date +%Y) the Friendica Project/g" "$OUTFILE"
sed -i "s/FIRST AUTHOR <EMAIL@ADDRESS>, YEAR./Mike Macgirvin, 2010/g" "$OUTFILE"
sed -i "s/PACKAGE VERSION/$F9KVERSION/g" "$OUTFILE"
sed -i "s/PACKAGE/Friendica/g" "$OUTFILE"
sed -i "s/CHARSET/UTF-8/g" "$OUTFILE"
sed -i "s/^\"Plural-Forms.*$//g" "$OUTFILE"
;;
esac
if [ "" != "$1" -a "$MODE" == "default" ]
then
UPDATEFILE="$(readlink -f ${FULLPATH}/$1)"
echo "merging new strings to $UPDATEFILE.."
echo "Merging new strings to $UPDATEFILE.."
msgmerge -U $OUTFILE $UPDATEFILE
fi
echo "done."
echo "Done."

View file

@ -33,13 +33,12 @@ use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Contact;
use Friendica\Model\Notify;
use Friendica\Model\Term;
use Friendica\Util\BasePath;
use Friendica\Util\DateTimeFormat;
define('FRIENDICA_PLATFORM', 'Friendica');
define('FRIENDICA_CODENAME', 'Red Hot Poker');
define('FRIENDICA_VERSION', '2020.03');
define('FRIENDICA_VERSION', '2020.06-rc');
define('DFRN_PROTOCOL_VERSION', '2.23');
define('NEW_UPDATE_ROUTINE_VERSION', 1170);
@ -178,29 +177,6 @@ define('NOTIFY_SHARE', Notify\Type::SHARE);
define('NOTIFY_SYSTEM', Notify\Type::SYSTEM);
/* @}*/
/** @deprecated since 2019.03, use Term::UNKNOWN instead */
define('TERM_UNKNOWN', Term::UNKNOWN);
/** @deprecated since 2019.03, use Term::HASHTAG instead */
define('TERM_HASHTAG', Term::HASHTAG);
/** @deprecated since 2019.03, use Term::MENTION instead */
define('TERM_MENTION', Term::MENTION);
/** @deprecated since 2019.03, use Term::CATEGORY instead */
define('TERM_CATEGORY', Term::CATEGORY);
/** @deprecated since 2019.03, use Term::PCATEGORY instead */
define('TERM_PCATEGORY', Term::PCATEGORY);
/** @deprecated since 2019.03, use Term::FILE instead */
define('TERM_FILE', Term::FILE);
/** @deprecated since 2019.03, use Term::SAVEDSEARCH instead */
define('TERM_SAVEDSEARCH', Term::SAVEDSEARCH);
/** @deprecated since 2019.03, use Term::CONVERSATION instead */
define('TERM_CONVERSATION', Term::CONVERSATION);
/** @deprecated since 2019.03, use Term::OBJECT_TYPE_POST instead */
define('TERM_OBJ_POST', Term::OBJECT_TYPE_POST);
/** @deprecated since 2019.03, use Term::OBJECT_TYPE_PHOTO instead */
define('TERM_OBJ_PHOTO', Term::OBJECT_TYPE_PHOTO);
/**
* @name Gravity
*

View file

@ -92,6 +92,9 @@
}
},
"config": {
"platform": {
"php": "7.0"
},
"autoloader-suffix": "Friendica",
"optimize-autoloader": true,
"preferred-install": "dist",

587
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "b3a7490d8f103ef40431848a26fcc2a6",
"content-hash": "ded67f7e680a122d0cd3512c2738be97",
"packages": [
{
"name": "asika/simple-console",
@ -87,16 +87,16 @@
},
{
"name": "bower-asset/Chart-js",
"version": "v2.8.0",
"version": "v2.9.3",
"source": {
"type": "git",
"url": "https://github.com/chartjs/Chart.js.git",
"reference": "947d8a7ccfbfc76dd9d384ea75436fa4a7aeefb1"
"reference": "06f73dc3590084b2c464bf08189c7aee2b6b92d2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/chartjs/Chart.js/zipball/947d8a7ccfbfc76dd9d384ea75436fa4a7aeefb1",
"reference": "947d8a7ccfbfc76dd9d384ea75436fa4a7aeefb1",
"url": "https://api.github.com/repos/chartjs/Chart.js/zipball/06f73dc3590084b2c464bf08189c7aee2b6b92d2",
"reference": "06f73dc3590084b2c464bf08189c7aee2b6b92d2",
"shasum": ""
},
"type": "bower-asset-library",
@ -115,20 +115,20 @@
"MIT"
],
"description": "Simple HTML5 charts using the canvas element.",
"time": "2019-03-14T13:03:00+00:00"
"time": "2019-11-14T18:37:30+00:00"
},
{
"name": "bower-asset/base64",
"version": "1.0.2",
"version": "1.1.0",
"source": {
"type": "git",
"url": "https://github.com/davidchambers/Base64.js.git",
"reference": "10f0e9990dab0a73009fc106ff2b88102a0a13cf"
"reference": "660b299aa4854843fd35d42b30eda9273125b9da"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/davidchambers/Base64.js/zipball/10f0e9990dab0a73009fc106ff2b88102a0a13cf",
"reference": "10f0e9990dab0a73009fc106ff2b88102a0a13cf",
"url": "https://api.github.com/repos/davidchambers/Base64.js/zipball/660b299aa4854843fd35d42b30eda9273125b9da",
"reference": "660b299aa4854843fd35d42b30eda9273125b9da",
"shasum": ""
},
"type": "bower-asset-library",
@ -146,7 +146,7 @@
"WTFPL"
],
"description": "Base64 encoding and decoding",
"time": "2019-02-12T17:19:36+00:00"
"time": "2019-11-02T20:07:47+00:00"
},
{
"name": "bower-asset/dompurify",
@ -239,16 +239,16 @@
},
{
"name": "bower-asset/vue",
"version": "v2.6.10",
"version": "v2.6.11",
"source": {
"type": "git",
"url": "https://github.com/vuejs/vue.git",
"reference": "e90cc60c4718a69e2c919275a999b7370141f3bf"
"reference": "ec78fc8b6d03e59da669be1adf4b4b5abf670a34"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/vuejs/vue/zipball/e90cc60c4718a69e2c919275a999b7370141f3bf",
"reference": "e90cc60c4718a69e2c919275a999b7370141f3bf",
"url": "https://api.github.com/repos/vuejs/vue/zipball/ec78fc8b6d03e59da669be1adf4b4b5abf670a34",
"reference": "ec78fc8b6d03e59da669be1adf4b4b5abf670a34",
"shasum": ""
},
"type": "bower-asset-library"
@ -394,21 +394,24 @@
},
{
"name": "ezyang/htmlpurifier",
"version": "v4.7.0",
"version": "v4.12.0",
"source": {
"type": "git",
"url": "https://github.com/ezyang/htmlpurifier.git",
"reference": "ae1828d955112356f7677c465f94f7deb7d27a40"
"reference": "a617e55bc62a87eec73bd456d146d134ad716f03"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/ae1828d955112356f7677c465f94f7deb7d27a40",
"reference": "ae1828d955112356f7677c465f94f7deb7d27a40",
"url": "https://api.github.com/repos/ezyang/htmlpurifier/zipball/a617e55bc62a87eec73bd456d146d134ad716f03",
"reference": "a617e55bc62a87eec73bd456d146d134ad716f03",
"shasum": ""
},
"require": {
"php": ">=5.2"
},
"require-dev": {
"simpletest/simpletest": "dev-master#72de02a7b80c6bb8864ef9bf66d41d2f58f826bd"
},
"type": "library",
"autoload": {
"psr-0": {
@ -420,7 +423,7 @@
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL"
"LGPL-2.1-or-later"
],
"authors": [
{
@ -434,7 +437,7 @@
"keywords": [
"html"
],
"time": "2015-08-05T01:03:42+00:00"
"time": "2019-10-28T03:44:26+00:00"
},
{
"name": "friendica/json-ld",
@ -541,27 +544,29 @@
},
{
"name": "guzzlehttp/guzzle",
"version": "6.3.3",
"version": "6.5.5",
"source": {
"type": "git",
"url": "https://github.com/guzzle/guzzle.git",
"reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba"
"reference": "9d4290de1cfd701f38099ef7e183b64b4b7b0c5e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/407b0cb880ace85c9b63c5f9551db498cb2d50ba",
"reference": "407b0cb880ace85c9b63c5f9551db498cb2d50ba",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/9d4290de1cfd701f38099ef7e183b64b4b7b0c5e",
"reference": "9d4290de1cfd701f38099ef7e183b64b4b7b0c5e",
"shasum": ""
},
"require": {
"ext-json": "*",
"guzzlehttp/promises": "^1.0",
"guzzlehttp/psr7": "^1.4",
"php": ">=5.5"
"guzzlehttp/psr7": "^1.6.1",
"php": ">=5.5",
"symfony/polyfill-intl-idn": "^1.17.0"
},
"require-dev": {
"ext-curl": "*",
"phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.4 || ^7.0",
"psr/log": "^1.0"
"psr/log": "^1.1"
},
"suggest": {
"psr/log": "Required for using the Log middleware"
@ -569,16 +574,16 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "6.3-dev"
"dev-master": "6.5-dev"
}
},
"autoload": {
"files": [
"src/functions_include.php"
],
"psr-4": {
"GuzzleHttp\\": "src/"
}
},
"files": [
"src/functions_include.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
@ -602,7 +607,7 @@
"rest",
"web service"
],
"time": "2018-04-22T15:46:56+00:00"
"time": "2020-06-16T21:01:06+00:00"
},
{
"name": "guzzlehttp/promises",
@ -792,16 +797,16 @@
},
{
"name": "level-2/dice",
"version": "4.0.1",
"version": "4.0.2",
"source": {
"type": "git",
"url": "https://github.com/Level-2/Dice.git",
"reference": "e631f110f0520294fec902814c61cac26566023c"
"reference": "b9336d9200d0165c31e982374dc5d8d2552807bc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Level-2/Dice/zipball/e631f110f0520294fec902814c61cac26566023c",
"reference": "e631f110f0520294fec902814c61cac26566023c",
"url": "https://api.github.com/repos/Level-2/Dice/zipball/b9336d9200d0165c31e982374dc5d8d2552807bc",
"reference": "b9336d9200d0165c31e982374dc5d8d2552807bc",
"shasum": ""
},
"require": {
@ -834,7 +839,7 @@
"di",
"ioc"
],
"time": "2019-05-01T12:55:36+00:00"
"time": "2020-01-28T13:47:49+00:00"
},
{
"name": "lightopenid/lightopenid",
@ -871,21 +876,24 @@
},
{
"name": "michelf/php-markdown",
"version": "1.8.0",
"version": "1.9.0",
"source": {
"type": "git",
"url": "https://github.com/michelf/php-markdown.git",
"reference": "01ab082b355bf188d907b9929cd99b2923053495"
"reference": "c83178d49e372ca967d1a8c77ae4e051b3a3c75c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/michelf/php-markdown/zipball/01ab082b355bf188d907b9929cd99b2923053495",
"reference": "01ab082b355bf188d907b9929cd99b2923053495",
"url": "https://api.github.com/repos/michelf/php-markdown/zipball/c83178d49e372ca967d1a8c77ae4e051b3a3c75c",
"reference": "c83178d49e372ca967d1a8c77ae4e051b3a3c75c",
"shasum": ""
},
"require": {
"php": ">=5.3.0"
},
"require-dev": {
"phpunit/phpunit": ">=4.3 <5.8"
},
"type": "library",
"autoload": {
"psr-4": {
@ -913,7 +921,7 @@
"keywords": [
"markdown"
],
"time": "2018-01-15T00:49:33+00:00"
"time": "2019-12-02T02:32:27+00:00"
},
{
"name": "mobiledetect/mobiledetectlib",
@ -969,16 +977,16 @@
},
{
"name": "monolog/monolog",
"version": "1.25.1",
"version": "1.25.4",
"source": {
"type": "git",
"url": "https://github.com/Seldaek/monolog.git",
"reference": "70e65a5470a42cfec1a7da00d30edb6e617e8dcf"
"reference": "3022efff205e2448b560c833c6fbbf91c3139168"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Seldaek/monolog/zipball/70e65a5470a42cfec1a7da00d30edb6e617e8dcf",
"reference": "70e65a5470a42cfec1a7da00d30edb6e617e8dcf",
"url": "https://api.github.com/repos/Seldaek/monolog/zipball/3022efff205e2448b560c833c6fbbf91c3139168",
"reference": "3022efff205e2448b560c833c6fbbf91c3139168",
"shasum": ""
},
"require": {
@ -992,11 +1000,10 @@
"aws/aws-sdk-php": "^2.4.9 || ^3.0",
"doctrine/couchdb": "~1.0@dev",
"graylog2/gelf-php": "~1.0",
"jakub-onderka/php-parallel-lint": "0.9",
"php-amqplib/php-amqplib": "~2.4",
"php-console/php-console": "^3.1.3",
"php-parallel-lint/php-parallel-lint": "^1.0",
"phpunit/phpunit": "~4.5",
"phpunit/phpunit-mock-objects": "2.3.0",
"ruflin/elastica": ">=0.90 <3.0",
"sentry/sentry": "^0.13",
"swiftmailer/swiftmailer": "^5.3|^6.0"
@ -1043,7 +1050,7 @@
"logging",
"psr-3"
],
"time": "2019-09-06T13:49:17+00:00"
"time": "2020-05-22T07:31:27+00:00"
},
{
"name": "nikic/fast-route",
@ -1271,11 +1278,11 @@
},
{
"name": "npm-asset/fullcalendar",
"version": "3.10.1",
"version": "3.10.2",
"dist": {
"type": "tar",
"url": "https://registry.npmjs.org/fullcalendar/-/fullcalendar-3.10.1.tgz",
"shasum": "cca3f9a2656a7e978a3f3facb7f35934a91185db"
"url": "https://registry.npmjs.org/fullcalendar/-/fullcalendar-3.10.2.tgz",
"shasum": "9b1ba84bb02803621b761d1bba91a4f18affafb7"
},
"type": "npm-asset-library",
"extra": {
@ -1313,7 +1320,7 @@
"full-sized",
"jquery-plugin"
],
"time": "2019-08-10T16:05:46+00:00"
"time": "2020-05-19T03:44:55+00:00"
},
{
"name": "npm-asset/imagesloaded",
@ -1647,11 +1654,11 @@
},
{
"name": "npm-asset/moment",
"version": "2.24.0",
"version": "2.26.0",
"dist": {
"type": "tar",
"url": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz",
"shasum": "0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b"
"url": "https://registry.npmjs.org/moment/-/moment-2.26.0.tgz",
"shasum": "5e1f82c6bafca6e83e808b30c8705eed0dcbd39a"
},
"type": "npm-asset-library",
"extra": {
@ -1665,8 +1672,12 @@
"url": "git+https://github.com/moment/moment.git"
},
"npm-asset-scripts": {
"typescript-test": "tsc --project typing-tests",
"ts3.1-typescript-test": "cross-env node_modules/typescript3/bin/tsc --project ts3.1-typing-tests",
"typescript-test": "cross-env node_modules/typescript/bin/tsc --project typing-tests",
"test": "grunt test",
"eslint": "eslint Gruntfile.js tasks src",
"prettier-check": "prettier --check Gruntfile.js tasks src",
"prettier-fmt": "prettier --write Gruntfile.js tasks src",
"coverage": "nyc npm test && nyc report",
"coveralls": "nyc npm test && nyc report --reporter=text-lcov | coveralls"
},
@ -1709,7 +1720,7 @@
}
],
"description": "Parse, validate, manipulate, and display dates",
"homepage": "http://momentjs.com",
"homepage": "https://momentjs.com",
"keywords": [
"date",
"ender",
@ -1721,7 +1732,65 @@
"time",
"validate"
],
"time": "2019-01-21T21:10:34+00:00"
"time": "2020-05-20T06:46:22+00:00"
},
{
"name": "npm-asset/perfect-scrollbar",
"version": "0.6.16",
"dist": {
"type": "tar",
"url": "https://registry.npmjs.org/perfect-scrollbar/-/perfect-scrollbar-0.6.16.tgz",
"shasum": "b1d61a5245cf3962bb9a8407a3fc669d923212fc"
},
"type": "npm-asset-library",
"extra": {
"npm-asset-bugs": {
"url": "https://github.com/noraesae/perfect-scrollbar/issues"
},
"npm-asset-files": [
"dist",
"src",
"index.js",
"jquery.js",
"perfect-scrollbar.d.ts"
],
"npm-asset-main": "./index.js",
"npm-asset-directories": [],
"npm-asset-repository": {
"type": "git",
"url": "git+https://github.com/noraesae/perfect-scrollbar.git"
},
"npm-asset-scripts": {
"test": "gulp",
"before-deploy": "gulp && gulp compress",
"release": "rm -rf dist && gulp && npm publish"
},
"npm-asset-engines": {
"node": ">= 0.12.0"
}
},
"license": [
"MIT"
],
"authors": [
{
"name": "Hyunje Jun",
"email": "me@noraesae.net"
},
{
"name": "Hyunje Jun",
"email": "me@noraesae.net"
}
],
"description": "Minimalistic but perfect custom scrollbar plugin",
"homepage": "https://github.com/noraesae/perfect-scrollbar#readme",
"keywords": [
"frontend",
"jquery-plugin",
"scroll",
"scrollbar"
],
"time": "2017-01-10T01:03:05+00:00"
},
{
"name": "npm-asset/perfect-scrollbar",
@ -1783,16 +1852,16 @@
},
{
"name": "npm-asset/php-date-formatter",
"version": "v1.3.5",
"version": "v1.3.6",
"source": {
"type": "git",
"url": "https://github.com/kartik-v/php-date-formatter.git",
"reference": "d842e1c4e6a8d6108017b726321c305bb5ae4fb5"
"reference": "514a53660b0d69439236fd3cbc3f41512adb00a0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/kartik-v/php-date-formatter/zipball/d842e1c4e6a8d6108017b726321c305bb5ae4fb5",
"reference": "d842e1c4e6a8d6108017b726321c305bb5ae4fb5",
"url": "https://api.github.com/repos/kartik-v/php-date-formatter/zipball/514a53660b0d69439236fd3cbc3f41512adb00a0",
"reference": "514a53660b0d69439236fd3cbc3f41512adb00a0",
"shasum": ""
},
"type": "npm-asset-library",
@ -1817,7 +1886,7 @@
],
"description": "A Javascript datetime formatting and manipulation library using PHP date-time formats.",
"homepage": "https://github.com/kartik-v/php-date-formatter",
"time": "2018-07-13T06:56:46+00:00"
"time": "2020-04-14T10:16:32+00:00"
},
{
"name": "npm-asset/typeahead.js",
@ -1873,16 +1942,16 @@
},
{
"name": "paragonie/certainty",
"version": "v2.5.0",
"version": "v2.6.1",
"source": {
"type": "git",
"url": "https://github.com/paragonie/certainty.git",
"reference": "cc39b91595e577fdff6128d7ce787892bd117274"
"reference": "b0068bc1e5605bd2ebe1ba906f2426d5df123944"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/paragonie/certainty/zipball/cc39b91595e577fdff6128d7ce787892bd117274",
"reference": "cc39b91595e577fdff6128d7ce787892bd117274",
"url": "https://api.github.com/repos/paragonie/certainty/zipball/b0068bc1e5605bd2ebe1ba906f2426d5df123944",
"reference": "b0068bc1e5605bd2ebe1ba906f2426d5df123944",
"shasum": ""
},
"require": {
@ -1891,7 +1960,7 @@
"guzzlehttp/guzzle": "^6",
"paragonie/constant_time_encoding": "^1|^2",
"paragonie/sodium_compat": "^1.11",
"php": "^5.5|^7"
"php": "^5.5|^7|^8"
},
"require-dev": {
"composer/composer": "^1",
@ -1931,28 +2000,28 @@
"ssl",
"tls"
],
"time": "2019-09-27T22:26:33+00:00"
"time": "2020-01-02T00:55:01+00:00"
},
{
"name": "paragonie/constant_time_encoding",
"version": "v2.2.3",
"version": "v2.3.0",
"source": {
"type": "git",
"url": "https://github.com/paragonie/constant_time_encoding.git",
"reference": "55af0dc01992b4d0da7f6372e2eac097bbbaffdb"
"reference": "47a1cedd2e4d52688eb8c96469c05ebc8fd28fa2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/55af0dc01992b4d0da7f6372e2eac097bbbaffdb",
"reference": "55af0dc01992b4d0da7f6372e2eac097bbbaffdb",
"url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/47a1cedd2e4d52688eb8c96469c05ebc8fd28fa2",
"reference": "47a1cedd2e4d52688eb8c96469c05ebc8fd28fa2",
"shasum": ""
},
"require": {
"php": "^7"
"php": "^7|^8"
},
"require-dev": {
"phpunit/phpunit": "^6|^7",
"vimeo/psalm": "^1|^2"
"vimeo/psalm": "^1|^2|^3"
},
"type": "library",
"autoload": {
@ -1993,7 +2062,7 @@
"hex2bin",
"rfc4648"
],
"time": "2019-01-03T20:26:31+00:00"
"time": "2019-11-06T19:20:29+00:00"
},
{
"name": "paragonie/hidden-string",
@ -2091,16 +2160,16 @@
},
{
"name": "paragonie/sodium_compat",
"version": "v1.11.1",
"version": "v1.13.0",
"source": {
"type": "git",
"url": "https://github.com/paragonie/sodium_compat.git",
"reference": "a9f968bc99485f85f9303a8524c3485a7e87bc15"
"reference": "bbade402cbe84c69b718120911506a3aa2bae653"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/a9f968bc99485f85f9303a8524c3485a7e87bc15",
"reference": "a9f968bc99485f85f9303a8524c3485a7e87bc15",
"url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/bbade402cbe84c69b718120911506a3aa2bae653",
"reference": "bbade402cbe84c69b718120911506a3aa2bae653",
"shasum": ""
},
"require": {
@ -2108,7 +2177,7 @@
"php": "^5.2.4|^5.3|^5.4|^5.5|^5.6|^7|^8"
},
"require-dev": {
"phpunit/phpunit": "^3|^4|^5"
"phpunit/phpunit": "^3|^4|^5|^6|^7"
},
"suggest": {
"ext-libsodium": "PHP < 7.0: Better performance, password hashing (Argon2i), secure memory management (memzero), and better security.",
@ -2169,7 +2238,7 @@
"secret-key cryptography",
"side-channel resistant"
],
"time": "2019-09-12T12:05:58+00:00"
"time": "2020-03-20T21:48:09+00:00"
},
{
"name": "pear/console_table",
@ -2228,20 +2297,20 @@
},
{
"name": "pear/text_languagedetect",
"version": "v1.0.0",
"version": "v1.0.1",
"source": {
"type": "git",
"url": "https://github.com/pear/Text_LanguageDetect.git",
"reference": "bb9ff6f4970f686fac59081e916b456021fe7ba6"
"reference": "9e253f26cef9a9066f53f200cc3e0684018cb5b5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/pear/Text_LanguageDetect/zipball/bb9ff6f4970f686fac59081e916b456021fe7ba6",
"reference": "bb9ff6f4970f686fac59081e916b456021fe7ba6",
"url": "https://api.github.com/repos/pear/Text_LanguageDetect/zipball/9e253f26cef9a9066f53f200cc3e0684018cb5b5",
"reference": "9e253f26cef9a9066f53f200cc3e0684018cb5b5",
"shasum": ""
},
"require-dev": {
"phpunit/phpunit": "*"
"phpunit/phpunit": "8.*|9.*"
},
"suggest": {
"ext-mbstring": "May require the mbstring PHP extension"
@ -2268,7 +2337,7 @@
],
"description": "Identify human languages from text samples",
"homepage": "http://pear.php.net/package/Text_LanguageDetect",
"time": "2017-03-02T16:14:08+00:00"
"time": "2020-05-17T12:19:40+00:00"
},
{
"name": "pragmarx/google2fa",
@ -2600,16 +2669,16 @@
},
{
"name": "psr/log",
"version": "1.1.0",
"version": "1.1.3",
"source": {
"type": "git",
"url": "https://github.com/php-fig/log.git",
"reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd"
"reference": "0f73288fd15629204f9d42b7055f72dacbe811fc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/log/zipball/6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd",
"reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd",
"url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc",
"reference": "0f73288fd15629204f9d42b7055f72dacbe811fc",
"shasum": ""
},
"require": {
@ -2618,7 +2687,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
"dev-master": "1.1.x-dev"
}
},
"autoload": {
@ -2643,7 +2712,7 @@
"psr",
"psr-3"
],
"time": "2018-11-20T15:27:04+00:00"
"time": "2020-03-23T09:12:05+00:00"
},
{
"name": "ralouphie/getallheaders",
@ -2735,21 +2804,25 @@
},
{
"name": "smarty/smarty",
"version": "v3.1.33",
"version": "v3.1.36",
"source": {
"type": "git",
"url": "https://github.com/smarty-php/smarty.git",
"reference": "dd55b23121e55a3b4f1af90a707a6c4e5969530f"
"reference": "fd148f7ade295014fff77f89ee3d5b20d9d55451"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/smarty-php/smarty/zipball/dd55b23121e55a3b4f1af90a707a6c4e5969530f",
"reference": "dd55b23121e55a3b4f1af90a707a6c4e5969530f",
"url": "https://api.github.com/repos/smarty-php/smarty/zipball/fd148f7ade295014fff77f89ee3d5b20d9d55451",
"reference": "fd148f7ade295014fff77f89ee3d5b20d9d55451",
"shasum": ""
},
"require": {
"php": ">=5.2"
},
"require-dev": {
"phpunit/phpunit": "6.4.1",
"smarty/smarty-lexer": "^3.1"
},
"type": "library",
"extra": {
"branch-alias": {
@ -2757,8 +2830,8 @@
}
},
"autoload": {
"files": [
"libs/bootstrap.php"
"classmap": [
"libs/"
]
},
"notification-url": "https://packagist.org/downloads/",
@ -2784,20 +2857,141 @@
"keywords": [
"templating"
],
"time": "2018-09-12T20:54:16+00:00"
"time": "2020-04-14T14:44:26+00:00"
},
{
"name": "symfony/polyfill-php56",
"version": "v1.12.0",
"name": "symfony/polyfill-intl-idn",
"version": "v1.17.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php56.git",
"reference": "0e3b212e96a51338639d8ce175c046d7729c3403"
"url": "https://github.com/symfony/polyfill-intl-idn.git",
"reference": "3bff59ea7047e925be6b7f2059d60af31bb46d6a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/0e3b212e96a51338639d8ce175c046d7729c3403",
"reference": "0e3b212e96a51338639d8ce175c046d7729c3403",
"url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/3bff59ea7047e925be6b7f2059d60af31bb46d6a",
"reference": "3bff59ea7047e925be6b7f2059d60af31bb46d6a",
"shasum": ""
},
"require": {
"php": ">=5.3.3",
"symfony/polyfill-mbstring": "^1.3",
"symfony/polyfill-php72": "^1.10"
},
"suggest": {
"ext-intl": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.17-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Intl\\Idn\\": ""
},
"files": [
"bootstrap.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Laurent Bassin",
"email": "laurent@bassin.info"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"idn",
"intl",
"polyfill",
"portable",
"shim"
],
"time": "2020-05-12T16:47:27+00:00"
},
{
"name": "symfony/polyfill-mbstring",
"version": "v1.17.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-mbstring.git",
"reference": "fa79b11539418b02fc5e1897267673ba2c19419c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fa79b11539418b02fc5e1897267673ba2c19419c",
"reference": "fa79b11539418b02fc5e1897267673ba2c19419c",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"suggest": {
"ext-mbstring": "For best performance"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.17-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Mbstring\\": ""
},
"files": [
"bootstrap.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill for the Mbstring extension",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"mbstring",
"polyfill",
"portable",
"shim"
],
"time": "2020-05-12T16:47:27+00:00"
},
{
"name": "symfony/polyfill-php56",
"version": "v1.17.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php56.git",
"reference": "e3c8c138280cdfe4b81488441555583aa1984e23"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/e3c8c138280cdfe4b81488441555583aa1984e23",
"reference": "e3c8c138280cdfe4b81488441555583aa1984e23",
"shasum": ""
},
"require": {
@ -2807,7 +3001,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.12-dev"
"dev-master": "1.17-dev"
}
},
"autoload": {
@ -2840,20 +3034,20 @@
"portable",
"shim"
],
"time": "2019-08-06T08:03:45+00:00"
"time": "2020-05-12T16:47:27+00:00"
},
{
"name": "symfony/polyfill-util",
"version": "v1.12.0",
"name": "symfony/polyfill-php72",
"version": "v1.17.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-util.git",
"reference": "4317de1386717b4c22caed7725350a8887ab205c"
"url": "https://github.com/symfony/polyfill-php72.git",
"reference": "f048e612a3905f34931127360bdd2def19a5e582"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-util/zipball/4317de1386717b4c22caed7725350a8887ab205c",
"reference": "4317de1386717b4c22caed7725350a8887ab205c",
"url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/f048e612a3905f34931127360bdd2def19a5e582",
"reference": "f048e612a3905f34931127360bdd2def19a5e582",
"shasum": ""
},
"require": {
@ -2862,7 +3056,62 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.12-dev"
"dev-master": "1.17-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Php72\\": ""
},
"files": [
"bootstrap.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"polyfill",
"portable",
"shim"
],
"time": "2020-05-12T16:47:27+00:00"
},
{
"name": "symfony/polyfill-util",
"version": "v1.17.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-util.git",
"reference": "4afb4110fc037752cf0ce9869f9ab8162c4e20d7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-util/zipball/4afb4110fc037752cf0ce9869f9ab8162c4e20d7",
"reference": "4afb4110fc037752cf0ce9869f9ab8162c4e20d7",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.17-dev"
}
},
"autoload": {
@ -2892,7 +3141,7 @@
"polyfill",
"shim"
],
"time": "2019-08-06T08:03:45+00:00"
"time": "2020-05-12T16:14:59+00:00"
}
],
"packages-dev": [
@ -3097,16 +3346,16 @@
},
{
"name": "mikey179/vfsstream",
"version": "v1.6.7",
"version": "v1.6.8",
"source": {
"type": "git",
"url": "https://github.com/bovigo/vfsStream.git",
"reference": "2b544ac3a21bcc4dde5d90c4ae8d06f4319055fb"
"reference": "231c73783ebb7dd9ec77916c10037eff5a2b6efe"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/bovigo/vfsStream/zipball/2b544ac3a21bcc4dde5d90c4ae8d06f4319055fb",
"reference": "2b544ac3a21bcc4dde5d90c4ae8d06f4319055fb",
"url": "https://api.github.com/repos/bovigo/vfsStream/zipball/231c73783ebb7dd9ec77916c10037eff5a2b6efe",
"reference": "231c73783ebb7dd9ec77916c10037eff5a2b6efe",
"shasum": ""
},
"require": {
@ -3139,20 +3388,20 @@
],
"description": "Virtual file system to mock the real file system in unit tests.",
"homepage": "http://vfs.bovigo.org/",
"time": "2019-08-01T01:38:37+00:00"
"time": "2019-10-30T15:31:00+00:00"
},
{
"name": "mockery/mockery",
"version": "1.2.3",
"version": "1.3.1",
"source": {
"type": "git",
"url": "https://github.com/mockery/mockery.git",
"reference": "4eff936d83eb809bde2c57a3cea0ee9643769031"
"reference": "f69bbde7d7a75d6b2862d9ca8fab1cd28014b4be"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/mockery/mockery/zipball/4eff936d83eb809bde2c57a3cea0ee9643769031",
"reference": "4eff936d83eb809bde2c57a3cea0ee9643769031",
"url": "https://api.github.com/repos/mockery/mockery/zipball/f69bbde7d7a75d6b2862d9ca8fab1cd28014b4be",
"reference": "f69bbde7d7a75d6b2862d9ca8fab1cd28014b4be",
"shasum": ""
},
"require": {
@ -3166,7 +3415,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
"dev-master": "1.3.x-dev"
}
},
"autoload": {
@ -3204,7 +3453,7 @@
"test double",
"testing"
],
"time": "2019-08-07T15:01:07+00:00"
"time": "2019-12-26T09:49:15+00:00"
},
{
"name": "myclabs/deep-copy",
@ -3399,33 +3648,33 @@
},
{
"name": "phpspec/prophecy",
"version": "1.8.1",
"version": "v1.10.3",
"source": {
"type": "git",
"url": "https://github.com/phpspec/prophecy.git",
"reference": "1927e75f4ed19131ec9bcc3b002e07fb1173ee76"
"reference": "451c3cd1418cf640de218914901e51b064abb093"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpspec/prophecy/zipball/1927e75f4ed19131ec9bcc3b002e07fb1173ee76",
"reference": "1927e75f4ed19131ec9bcc3b002e07fb1173ee76",
"url": "https://api.github.com/repos/phpspec/prophecy/zipball/451c3cd1418cf640de218914901e51b064abb093",
"reference": "451c3cd1418cf640de218914901e51b064abb093",
"shasum": ""
},
"require": {
"doctrine/instantiator": "^1.0.2",
"php": "^5.3|^7.0",
"phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0",
"sebastian/comparator": "^1.1|^2.0|^3.0",
"sebastian/recursion-context": "^1.0|^2.0|^3.0"
"phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0",
"sebastian/comparator": "^1.2.3|^2.0|^3.0|^4.0",
"sebastian/recursion-context": "^1.0|^2.0|^3.0|^4.0"
},
"require-dev": {
"phpspec/phpspec": "^2.5|^3.2",
"phpspec/phpspec": "^2.5 || ^3.2",
"phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.8.x-dev"
"dev-master": "1.10.x-dev"
}
},
"autoload": {
@ -3458,7 +3707,7 @@
"spy",
"stub"
],
"time": "2019-06-13T12:50:23+00:00"
"time": "2020-03-05T15:02:03+00:00"
},
{
"name": "phpunit/php-code-coverage",
@ -4366,16 +4615,16 @@
},
{
"name": "symfony/polyfill-ctype",
"version": "v1.12.0",
"version": "v1.17.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-ctype.git",
"reference": "550ebaac289296ce228a706d0867afc34687e3f4"
"reference": "e94c8b1bbe2bc77507a1056cdb06451c75b427f9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/550ebaac289296ce228a706d0867afc34687e3f4",
"reference": "550ebaac289296ce228a706d0867afc34687e3f4",
"url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e94c8b1bbe2bc77507a1056cdb06451c75b427f9",
"reference": "e94c8b1bbe2bc77507a1056cdb06451c75b427f9",
"shasum": ""
},
"require": {
@ -4387,7 +4636,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.12-dev"
"dev-master": "1.17-dev"
}
},
"autoload": {
@ -4420,31 +4669,27 @@
"polyfill",
"portable"
],
"time": "2019-08-06T08:03:45+00:00"
"time": "2020-05-12T16:14:59+00:00"
},
{
"name": "symfony/yaml",
"version": "v3.4.32",
"version": "v3.3.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
"reference": "768f817446da74a776a31eea335540f9dcb53942"
"reference": "ddc23324e6cfe066f3dd34a37ff494fa80b617ed"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/768f817446da74a776a31eea335540f9dcb53942",
"reference": "768f817446da74a776a31eea335540f9dcb53942",
"url": "https://api.github.com/repos/symfony/yaml/zipball/ddc23324e6cfe066f3dd34a37ff494fa80b617ed",
"reference": "ddc23324e6cfe066f3dd34a37ff494fa80b617ed",
"shasum": ""
},
"require": {
"php": "^5.5.9|>=7.0.8",
"symfony/polyfill-ctype": "~1.8"
},
"conflict": {
"symfony/console": "<3.4"
"php": ">=5.5.9"
},
"require-dev": {
"symfony/console": "~3.4|~4.0"
"symfony/console": "~2.8|~3.0"
},
"suggest": {
"symfony/console": "For validating YAML files using the lint command"
@ -4452,7 +4697,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.4-dev"
"dev-master": "3.3-dev"
}
},
"autoload": {
@ -4479,35 +4724,34 @@
],
"description": "Symfony Yaml Component",
"homepage": "https://symfony.com",
"time": "2019-09-10T10:38:46+00:00"
"time": "2017-07-23T12:43:26+00:00"
},
{
"name": "webmozart/assert",
"version": "1.5.0",
"version": "1.9.0",
"source": {
"type": "git",
"url": "https://github.com/webmozart/assert.git",
"reference": "88e6d84706d09a236046d686bbea96f07b3a34f4"
"reference": "9dc4f203e36f2b486149058bade43c851dd97451"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/webmozart/assert/zipball/88e6d84706d09a236046d686bbea96f07b3a34f4",
"reference": "88e6d84706d09a236046d686bbea96f07b3a34f4",
"url": "https://api.github.com/repos/webmozart/assert/zipball/9dc4f203e36f2b486149058bade43c851dd97451",
"reference": "9dc4f203e36f2b486149058bade43c851dd97451",
"shasum": ""
},
"require": {
"php": "^5.3.3 || ^7.0",
"symfony/polyfill-ctype": "^1.8"
},
"conflict": {
"phpstan/phpstan": "<0.12.20",
"vimeo/psalm": "<3.9.1"
},
"require-dev": {
"phpunit/phpunit": "^4.8.36 || ^7.5.13"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.3-dev"
}
},
"autoload": {
"psr-4": {
"Webmozart\\Assert\\": "src/"
@ -4529,7 +4773,7 @@
"check",
"validate"
],
"time": "2019-08-24T08:43:50+00:00"
"time": "2020-06-16T10:16:42+00:00"
}
],
"aliases": [],
@ -4554,5 +4798,8 @@
"ext-simplexml": "*",
"ext-xml": "*"
},
"platform-dev": []
"platform-dev": [],
"platform-overrides": {
"php": "7.0"
}
}

View file

@ -1,9 +1,186 @@
-- ------------------------------------------
-- Friendica 2020.03-rc (Dalmatian Bellflower)
-- DB_UPDATE_VERSION 1338
-- Friendica 2020.06-dev (Red Hot Poker)
-- DB_UPDATE_VERSION 1353
-- ------------------------------------------
--
-- TABLE gserver
--
CREATE TABLE IF NOT EXISTS `gserver` (
`id` int unsigned NOT NULL auto_increment COMMENT 'sequential ID',
`url` varchar(255) NOT NULL DEFAULT '' COMMENT '',
`nurl` varchar(255) NOT NULL DEFAULT '' COMMENT '',
`version` varchar(255) NOT NULL DEFAULT '' COMMENT '',
`site_name` varchar(255) NOT NULL DEFAULT '' COMMENT '',
`info` text COMMENT '',
`register_policy` tinyint NOT NULL DEFAULT 0 COMMENT '',
`registered-users` int unsigned NOT NULL DEFAULT 0 COMMENT 'Number of registered users',
`directory-type` tinyint DEFAULT 0 COMMENT 'Type of directory service (Poco, Mastodon)',
`poco` varchar(255) NOT NULL DEFAULT '' COMMENT '',
`noscrape` varchar(255) NOT NULL DEFAULT '' COMMENT '',
`network` char(4) NOT NULL DEFAULT '' COMMENT '',
`platform` varchar(255) NOT NULL DEFAULT '' COMMENT '',
`relay-subscribe` boolean NOT NULL DEFAULT '0' COMMENT 'Has the server subscribed to the relay system',
`relay-scope` varchar(10) NOT NULL DEFAULT '' COMMENT 'The scope of messages that the server wants to get',
`detection-method` tinyint unsigned COMMENT 'Method that had been used to detect that server',
`created` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT '',
`last_poco_query` datetime DEFAULT '0001-01-01 00:00:00' COMMENT '',
`last_contact` datetime DEFAULT '0001-01-01 00:00:00' COMMENT '',
`last_failure` datetime DEFAULT '0001-01-01 00:00:00' COMMENT '',
PRIMARY KEY(`id`),
UNIQUE INDEX `nurl` (`nurl`(190))
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Global servers';
--
-- TABLE clients
--
CREATE TABLE IF NOT EXISTS `clients` (
`client_id` varchar(20) NOT NULL COMMENT '',
`pw` varchar(20) NOT NULL DEFAULT '' COMMENT '',
`redirect_uri` varchar(200) NOT NULL DEFAULT '' COMMENT '',
`name` text COMMENT '',
`icon` text COMMENT '',
`uid` mediumint unsigned NOT NULL DEFAULT 0 COMMENT 'User id',
PRIMARY KEY(`client_id`)
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='OAuth usage';
--
-- TABLE contact
--
CREATE TABLE IF NOT EXISTS `contact` (
`id` int unsigned NOT NULL auto_increment COMMENT 'sequential ID',
`uid` mediumint unsigned NOT NULL DEFAULT 0 COMMENT 'Owner User id',
`created` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT '',
`updated` datetime DEFAULT '0001-01-01 00:00:00' COMMENT 'Date of last contact update',
`self` boolean NOT NULL DEFAULT '0' COMMENT '1 if the contact is the user him/her self',
`remote_self` boolean NOT NULL DEFAULT '0' COMMENT '',
`rel` tinyint unsigned NOT NULL DEFAULT 0 COMMENT 'The kind of the relation between the user and the contact',
`duplex` boolean NOT NULL DEFAULT '0' COMMENT '',
`network` char(4) NOT NULL DEFAULT '' COMMENT 'Network of the contact',
`protocol` char(4) NOT NULL DEFAULT '' COMMENT 'Protocol of the contact',
`name` varchar(255) NOT NULL DEFAULT '' COMMENT 'Name that this contact is known by',
`nick` varchar(255) NOT NULL DEFAULT '' COMMENT 'Nick- and user name of the contact',
`location` varchar(255) DEFAULT '' COMMENT '',
`about` text COMMENT '',
`keywords` text COMMENT 'public keywords (interests) of the contact',
`gender` varchar(32) NOT NULL DEFAULT '' COMMENT 'Deprecated',
`xmpp` varchar(255) NOT NULL DEFAULT '' COMMENT '',
`attag` varchar(255) NOT NULL DEFAULT '' COMMENT '',
`avatar` varchar(255) NOT NULL DEFAULT '' COMMENT '',
`photo` varchar(255) DEFAULT '' COMMENT 'Link to the profile photo of the contact',
`thumb` varchar(255) DEFAULT '' COMMENT 'Link to the profile photo (thumb size)',
`micro` varchar(255) DEFAULT '' COMMENT 'Link to the profile photo (micro size)',
`site-pubkey` text COMMENT '',
`issued-id` varchar(255) NOT NULL DEFAULT '' COMMENT '',
`dfrn-id` varchar(255) NOT NULL DEFAULT '' COMMENT '',
`url` varchar(255) NOT NULL DEFAULT '' COMMENT '',
`nurl` varchar(255) NOT NULL DEFAULT '' COMMENT '',
`addr` varchar(255) NOT NULL DEFAULT '' COMMENT '',
`alias` varchar(255) NOT NULL DEFAULT '' COMMENT '',
`pubkey` text COMMENT 'RSA public key 4096 bit',
`prvkey` text COMMENT 'RSA private key 4096 bit',
`batch` varchar(255) NOT NULL DEFAULT '' COMMENT '',
`request` varchar(255) COMMENT '',
`notify` varchar(255) COMMENT '',
`poll` varchar(255) COMMENT '',
`confirm` varchar(255) COMMENT '',
`subscribe` varchar(255) COMMENT '',
`poco` varchar(255) COMMENT '',
`aes_allow` boolean NOT NULL DEFAULT '0' COMMENT '',
`ret-aes` boolean NOT NULL DEFAULT '0' COMMENT '',
`usehub` boolean NOT NULL DEFAULT '0' COMMENT '',
`subhub` boolean NOT NULL DEFAULT '0' COMMENT '',
`hub-verify` varchar(255) NOT NULL DEFAULT '' COMMENT '',
`last-update` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'Date of the last try to update the contact info',
`success_update` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'Date of the last successful contact update',
`failure_update` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'Date of the last failed update',
`name-date` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT '',
`uri-date` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT '',
`avatar-date` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT '',
`term-date` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT '',
`last-item` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'date of the last post',
`priority` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '',
`blocked` boolean NOT NULL DEFAULT '1' COMMENT 'Node-wide block status',
`block_reason` text COMMENT 'Node-wide block reason',
`readonly` boolean NOT NULL DEFAULT '0' COMMENT 'posts of the contact are readonly',
`writable` boolean NOT NULL DEFAULT '0' COMMENT '',
`forum` boolean NOT NULL DEFAULT '0' COMMENT 'contact is a forum',
`prv` boolean NOT NULL DEFAULT '0' COMMENT 'contact is a private group',
`contact-type` tinyint NOT NULL DEFAULT 0 COMMENT '',
`hidden` boolean NOT NULL DEFAULT '0' COMMENT '',
`archive` boolean NOT NULL DEFAULT '0' COMMENT '',
`pending` boolean NOT NULL DEFAULT '1' COMMENT '',
`deleted` boolean NOT NULL DEFAULT '0' COMMENT 'Contact has been deleted',
`rating` tinyint NOT NULL DEFAULT 0 COMMENT '',
`unsearchable` boolean NOT NULL DEFAULT '0' COMMENT 'Contact prefers to not be searchable',
`sensitive` boolean NOT NULL DEFAULT '0' COMMENT 'Contact posts sensitive content',
`baseurl` varchar(255) DEFAULT '' COMMENT 'baseurl of the contact',
`gsid` int unsigned COMMENT 'Global Server ID',
`reason` text COMMENT '',
`closeness` tinyint unsigned NOT NULL DEFAULT 99 COMMENT '',
`info` mediumtext COMMENT '',
`profile-id` int unsigned COMMENT 'Deprecated',
`bdyear` varchar(4) NOT NULL DEFAULT '' COMMENT '',
`bd` date NOT NULL DEFAULT '0001-01-01' COMMENT '',
`notify_new_posts` boolean NOT NULL DEFAULT '0' COMMENT '',
`fetch_further_information` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '',
`ffi_keyword_denylist` text COMMENT '',
PRIMARY KEY(`id`),
INDEX `uid_name` (`uid`,`name`(190)),
INDEX `self_uid` (`self`,`uid`),
INDEX `alias_uid` (`alias`(32),`uid`),
INDEX `pending_uid` (`pending`,`uid`),
INDEX `blocked_uid` (`blocked`,`uid`),
INDEX `uid_rel_network_poll` (`uid`,`rel`,`network`,`poll`(64),`archive`),
INDEX `uid_network_batch` (`uid`,`network`,`batch`(64)),
INDEX `addr_uid` (`addr`(32),`uid`),
INDEX `nurl_uid` (`nurl`(32),`uid`),
INDEX `nick_uid` (`nick`(32),`uid`),
INDEX `dfrn-id` (`dfrn-id`(64)),
INDEX `issued-id` (`issued-id`(64)),
INDEX `gsid` (`gsid`),
FOREIGN KEY (`gsid`) REFERENCES `gserver` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='contact table';
--
-- TABLE item-uri
--
CREATE TABLE IF NOT EXISTS `item-uri` (
`id` int unsigned NOT NULL auto_increment,
`uri` varbinary(255) NOT NULL COMMENT 'URI of an item',
`guid` varbinary(255) COMMENT 'A unique identifier for an item',
PRIMARY KEY(`id`),
UNIQUE INDEX `uri` (`uri`),
INDEX `guid` (`guid`)
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='URI and GUID for items';
--
-- TABLE permissionset
--
CREATE TABLE IF NOT EXISTS `permissionset` (
`id` int unsigned NOT NULL auto_increment COMMENT 'sequential ID',
`uid` mediumint unsigned NOT NULL DEFAULT 0 COMMENT 'Owner id of this permission set',
`allow_cid` mediumtext COMMENT 'Access Control - list of allowed contact.id \'<19><78>\'',
`allow_gid` mediumtext COMMENT 'Access Control - list of allowed groups',
`deny_cid` mediumtext COMMENT 'Access Control - list of denied contact.id',
`deny_gid` mediumtext COMMENT 'Access Control - list of denied groups',
PRIMARY KEY(`id`),
INDEX `uid_allow_cid_allow_gid_deny_cid_deny_gid` (`allow_cid`(50),`allow_gid`(30),`deny_cid`(50),`deny_gid`(30))
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='';
--
-- TABLE tag
--
CREATE TABLE IF NOT EXISTS `tag` (
`id` int unsigned NOT NULL auto_increment COMMENT '',
`name` varchar(96) NOT NULL DEFAULT '' COMMENT '',
`url` varbinary(255) NOT NULL DEFAULT '' COMMENT '',
PRIMARY KEY(`id`),
UNIQUE INDEX `type_name_url` (`name`,`url`),
INDEX `url` (`url`)
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='tags and mentions';
--
-- TABLE 2fa_app_specific_password
--
@ -64,7 +241,9 @@ CREATE TABLE IF NOT EXISTS `apcontact` (
`addr` varchar(255) COMMENT '',
`alias` varchar(255) COMMENT '',
`pubkey` text COMMENT '',
`subscribe` varchar(255) COMMENT '',
`baseurl` varchar(255) COMMENT 'baseurl of the ap contact',
`gsid` int unsigned COMMENT 'Global Server ID',
`generator` varchar(255) COMMENT 'Name of the contact\'s system',
`following_count` int unsigned DEFAULT 0 COMMENT 'Number of following contacts',
`followers_count` int unsigned DEFAULT 0 COMMENT 'Number of followers',
@ -73,7 +252,9 @@ CREATE TABLE IF NOT EXISTS `apcontact` (
PRIMARY KEY(`url`),
INDEX `addr` (`addr`(32)),
INDEX `alias` (`alias`(190)),
INDEX `url` (`followers`(190))
INDEX `followers` (`followers`(190)),
INDEX `gsid` (`gsid`),
FOREIGN KEY (`gsid`) REFERENCES `gserver` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='ActivityPub compatible contacts - used in the ActivityPub implementation';
--
@ -107,7 +288,9 @@ CREATE TABLE IF NOT EXISTS `auth_codes` (
`redirect_uri` varchar(200) NOT NULL DEFAULT '' COMMENT '',
`expires` int NOT NULL DEFAULT 0 COMMENT '',
`scope` varchar(250) NOT NULL DEFAULT '' COMMENT '',
PRIMARY KEY(`id`)
PRIMARY KEY(`id`),
INDEX `client_id` (`client_id`),
FOREIGN KEY (`client_id`) REFERENCES `clients` (`client_id`) ON UPDATE RESTRICT ON DELETE CASCADE
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='OAuth usage';
--
@ -135,19 +318,6 @@ CREATE TABLE IF NOT EXISTS `challenge` (
PRIMARY KEY(`id`)
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='';
--
-- TABLE clients
--
CREATE TABLE IF NOT EXISTS `clients` (
`client_id` varchar(20) NOT NULL COMMENT '',
`pw` varchar(20) NOT NULL DEFAULT '' COMMENT '',
`redirect_uri` varchar(200) NOT NULL DEFAULT '' COMMENT '',
`name` text COMMENT '',
`icon` text COMMENT '',
`uid` mediumint unsigned NOT NULL DEFAULT 0 COMMENT 'User id',
PRIMARY KEY(`client_id`)
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='OAuth usage';
--
-- TABLE config
--
@ -160,100 +330,6 @@ CREATE TABLE IF NOT EXISTS `config` (
UNIQUE INDEX `cat_k` (`cat`,`k`)
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='main configuration storage';
--
-- TABLE contact
--
CREATE TABLE IF NOT EXISTS `contact` (
`id` int unsigned NOT NULL auto_increment COMMENT 'sequential ID',
`uid` mediumint unsigned NOT NULL DEFAULT 0 COMMENT 'Owner User id',
`created` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT '',
`updated` datetime DEFAULT '0001-01-01 00:00:00' COMMENT 'Date of last contact update',
`self` boolean NOT NULL DEFAULT '0' COMMENT '1 if the contact is the user him/her self',
`remote_self` boolean NOT NULL DEFAULT '0' COMMENT '',
`rel` tinyint unsigned NOT NULL DEFAULT 0 COMMENT 'The kind of the relation between the user and the contact',
`duplex` boolean NOT NULL DEFAULT '0' COMMENT '',
`network` char(4) NOT NULL DEFAULT '' COMMENT 'Network of the contact',
`protocol` char(4) NOT NULL DEFAULT '' COMMENT 'Protocol of the contact',
`name` varchar(255) NOT NULL DEFAULT '' COMMENT 'Name that this contact is known by',
`nick` varchar(255) NOT NULL DEFAULT '' COMMENT 'Nick- and user name of the contact',
`location` varchar(255) DEFAULT '' COMMENT '',
`about` text COMMENT '',
`keywords` text COMMENT 'public keywords (interests) of the contact',
`gender` varchar(32) NOT NULL DEFAULT '' COMMENT 'Deprecated',
`xmpp` varchar(255) NOT NULL DEFAULT '' COMMENT '',
`attag` varchar(255) NOT NULL DEFAULT '' COMMENT '',
`avatar` varchar(255) NOT NULL DEFAULT '' COMMENT '',
`photo` varchar(255) DEFAULT '' COMMENT 'Link to the profile photo of the contact',
`thumb` varchar(255) DEFAULT '' COMMENT 'Link to the profile photo (thumb size)',
`micro` varchar(255) DEFAULT '' COMMENT 'Link to the profile photo (micro size)',
`site-pubkey` text COMMENT '',
`issued-id` varchar(255) NOT NULL DEFAULT '' COMMENT '',
`dfrn-id` varchar(255) NOT NULL DEFAULT '' COMMENT '',
`url` varchar(255) NOT NULL DEFAULT '' COMMENT '',
`nurl` varchar(255) NOT NULL DEFAULT '' COMMENT '',
`addr` varchar(255) NOT NULL DEFAULT '' COMMENT '',
`alias` varchar(255) NOT NULL DEFAULT '' COMMENT '',
`pubkey` text COMMENT 'RSA public key 4096 bit',
`prvkey` text COMMENT 'RSA private key 4096 bit',
`batch` varchar(255) NOT NULL DEFAULT '' COMMENT '',
`request` varchar(255) COMMENT '',
`notify` varchar(255) COMMENT '',
`poll` varchar(255) COMMENT '',
`confirm` varchar(255) COMMENT '',
`poco` varchar(255) COMMENT '',
`aes_allow` boolean NOT NULL DEFAULT '0' COMMENT '',
`ret-aes` boolean NOT NULL DEFAULT '0' COMMENT '',
`usehub` boolean NOT NULL DEFAULT '0' COMMENT '',
`subhub` boolean NOT NULL DEFAULT '0' COMMENT '',
`hub-verify` varchar(255) NOT NULL DEFAULT '' COMMENT '',
`last-update` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'Date of the last try to update the contact info',
`success_update` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'Date of the last successful contact update',
`failure_update` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'Date of the last failed update',
`name-date` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT '',
`uri-date` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT '',
`avatar-date` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT '',
`term-date` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT '',
`last-item` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'date of the last post',
`priority` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '',
`blocked` boolean NOT NULL DEFAULT '1' COMMENT 'Node-wide block status',
`block_reason` text COMMENT 'Node-wide block reason',
`readonly` boolean NOT NULL DEFAULT '0' COMMENT 'posts of the contact are readonly',
`writable` boolean NOT NULL DEFAULT '0' COMMENT '',
`forum` boolean NOT NULL DEFAULT '0' COMMENT 'contact is a forum',
`prv` boolean NOT NULL DEFAULT '0' COMMENT 'contact is a private group',
`contact-type` tinyint NOT NULL DEFAULT 0 COMMENT '',
`hidden` boolean NOT NULL DEFAULT '0' COMMENT '',
`archive` boolean NOT NULL DEFAULT '0' COMMENT '',
`pending` boolean NOT NULL DEFAULT '1' COMMENT '',
`deleted` boolean NOT NULL DEFAULT '0' COMMENT 'Contact has been deleted',
`rating` tinyint NOT NULL DEFAULT 0 COMMENT '',
`unsearchable` boolean NOT NULL DEFAULT '0' COMMENT 'Contact prefers to not be searchable',
`sensitive` boolean NOT NULL DEFAULT '0' COMMENT 'Contact posts sensitive content',
`baseurl` varchar(255) DEFAULT '' COMMENT 'baseurl of the contact',
`reason` text COMMENT '',
`closeness` tinyint unsigned NOT NULL DEFAULT 99 COMMENT '',
`info` mediumtext COMMENT '',
`profile-id` int unsigned COMMENT 'Deprecated',
`bdyear` varchar(4) NOT NULL DEFAULT '' COMMENT '',
`bd` date NOT NULL DEFAULT '0001-01-01' COMMENT '',
`notify_new_posts` boolean NOT NULL DEFAULT '0' COMMENT '',
`fetch_further_information` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '',
`ffi_keyword_blacklist` text COMMENT '',
PRIMARY KEY(`id`),
INDEX `uid_name` (`uid`,`name`(190)),
INDEX `self_uid` (`self`,`uid`),
INDEX `alias_uid` (`alias`(32),`uid`),
INDEX `pending_uid` (`pending`,`uid`),
INDEX `blocked_uid` (`blocked`,`uid`),
INDEX `uid_rel_network_poll` (`uid`,`rel`,`network`,`poll`(64),`archive`),
INDEX `uid_network_batch` (`uid`,`network`,`batch`(64)),
INDEX `addr_uid` (`addr`(32),`uid`),
INDEX `nurl_uid` (`nurl`(32),`uid`),
INDEX `nick_uid` (`nick`(32),`uid`),
INDEX `dfrn-id` (`dfrn-id`(64)),
INDEX `issued-id` (`issued-id`(64))
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='contact table';
--
-- TABLE contact-relation
--
@ -304,7 +380,8 @@ CREATE TABLE IF NOT EXISTS `conversation` (
CREATE TABLE IF NOT EXISTS `diaspora-interaction` (
`uri-id` int unsigned NOT NULL COMMENT 'Id of the item-uri table entry that contains the item uri',
`interaction` mediumtext COMMENT 'The Diaspora interaction',
PRIMARY KEY(`uri-id`)
PRIMARY KEY(`uri-id`),
FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Signed Diaspora Interaction';
--
@ -422,13 +499,16 @@ CREATE TABLE IF NOT EXISTS `gcontact` (
`alias` varchar(255) NOT NULL DEFAULT '' COMMENT '',
`generation` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '',
`server_url` varchar(255) NOT NULL DEFAULT '' COMMENT 'baseurl of the contacts server',
`gsid` int unsigned COMMENT 'Global Server ID',
PRIMARY KEY(`id`),
UNIQUE INDEX `nurl` (`nurl`(190)),
INDEX `name` (`name`(64)),
INDEX `nick` (`nick`(32)),
INDEX `addr` (`addr`(64)),
INDEX `hide_network_updated` (`hide`,`network`,`updated`),
INDEX `updated` (`updated`)
INDEX `updated` (`updated`),
INDEX `gsid` (`gsid`),
FOREIGN KEY (`gsid`) REFERENCES `gserver` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='global contacts';
--
@ -482,33 +562,6 @@ CREATE TABLE IF NOT EXISTS `group_member` (
UNIQUE INDEX `gid_contactid` (`gid`,`contact-id`)
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='privacy groups, member info';
--
-- TABLE gserver
--
CREATE TABLE IF NOT EXISTS `gserver` (
`id` int unsigned NOT NULL auto_increment COMMENT 'sequential ID',
`url` varchar(255) NOT NULL DEFAULT '' COMMENT '',
`nurl` varchar(255) NOT NULL DEFAULT '' COMMENT '',
`version` varchar(255) NOT NULL DEFAULT '' COMMENT '',
`site_name` varchar(255) NOT NULL DEFAULT '' COMMENT '',
`info` text COMMENT '',
`register_policy` tinyint NOT NULL DEFAULT 0 COMMENT '',
`registered-users` int unsigned NOT NULL DEFAULT 0 COMMENT 'Number of registered users',
`directory-type` tinyint DEFAULT 0 COMMENT 'Type of directory service (Poco, Mastodon)',
`poco` varchar(255) NOT NULL DEFAULT '' COMMENT '',
`noscrape` varchar(255) NOT NULL DEFAULT '' COMMENT '',
`network` char(4) NOT NULL DEFAULT '' COMMENT '',
`platform` varchar(255) NOT NULL DEFAULT '' COMMENT '',
`relay-subscribe` boolean NOT NULL DEFAULT '0' COMMENT 'Has the server subscribed to the relay system',
`relay-scope` varchar(10) NOT NULL DEFAULT '' COMMENT 'The scope of messages that the server wants to get',
`created` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT '',
`last_poco_query` datetime DEFAULT '0001-01-01 00:00:00' COMMENT '',
`last_contact` datetime DEFAULT '0001-01-01 00:00:00' COMMENT '',
`last_failure` datetime DEFAULT '0001-01-01 00:00:00' COMMENT '',
PRIMARY KEY(`id`),
UNIQUE INDEX `nurl` (`nurl`(190))
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Global servers';
--
-- TABLE gserver-tag
--
@ -589,6 +642,7 @@ CREATE TABLE IF NOT EXISTS `item` (
`author-id` int unsigned NOT NULL DEFAULT 0 COMMENT 'Link to the contact table with uid=0 of the author of this item',
`icid` int unsigned COMMENT 'Id of the item-content table entry that contains the whole item content',
`iaid` int unsigned COMMENT 'Id of the item-activity table entry that contains the activity data',
`vid` smallint unsigned COMMENT 'Id of the verb table entry that contains the activity verbs',
`extid` varchar(255) NOT NULL DEFAULT '' COMMENT '',
`post-type` tinyint unsigned NOT NULL DEFAULT 0 COMMENT 'Post type (personal note, bookmark, ...)',
`global` boolean NOT NULL DEFAULT '0' COMMENT '',
@ -666,7 +720,14 @@ CREATE TABLE IF NOT EXISTS `item` (
INDEX `uid_eventid` (`uid`,`event-id`),
INDEX `icid` (`icid`),
INDEX `iaid` (`iaid`),
INDEX `psid_wall` (`psid`,`wall`)
INDEX `psid_wall` (`psid`,`wall`),
INDEX `uri-id` (`uri-id`),
INDEX `parent-uri-id` (`parent-uri-id`),
INDEX `thr-parent-id` (`thr-parent-id`),
FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
FOREIGN KEY (`parent-uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
FOREIGN KEY (`thr-parent-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
FOREIGN KEY (`psid`) REFERENCES `permissionset` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Structure for all posts';
--
@ -681,7 +742,8 @@ CREATE TABLE IF NOT EXISTS `item-activity` (
PRIMARY KEY(`id`),
UNIQUE INDEX `uri-hash` (`uri-hash`),
INDEX `uri` (`uri`(191)),
INDEX `uri-id` (`uri-id`)
INDEX `uri-id` (`uri-id`),
FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Activities for items';
--
@ -711,39 +773,10 @@ CREATE TABLE IF NOT EXISTS `item-content` (
UNIQUE INDEX `uri-plink-hash` (`uri-plink-hash`),
INDEX `uri` (`uri`(191)),
INDEX `plink` (`plink`(191)),
INDEX `uri-id` (`uri-id`)
INDEX `uri-id` (`uri-id`),
FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Content for all posts';
--
-- TABLE item-delivery-data
--
CREATE TABLE IF NOT EXISTS `item-delivery-data` (
`iid` int unsigned NOT NULL COMMENT 'Item id',
`postopts` text COMMENT 'External post connectors add their network name to this comma-separated string to identify that they should be delivered to these networks during delivery',
`inform` mediumtext COMMENT 'Additional receivers of the linked item',
`queue_count` mediumint NOT NULL DEFAULT 0 COMMENT 'Initial number of delivery recipients, used as item.delivery_queue_count',
`queue_done` mediumint NOT NULL DEFAULT 0 COMMENT 'Number of successful deliveries, used as item.delivery_queue_done',
`queue_failed` mediumint NOT NULL DEFAULT 0 COMMENT 'Number of unsuccessful deliveries, used as item.delivery_queue_failed',
`activitypub` mediumint NOT NULL DEFAULT 0 COMMENT 'Number of successful deliveries via ActivityPub',
`dfrn` mediumint NOT NULL DEFAULT 0 COMMENT 'Number of successful deliveries via DFRN',
`legacy_dfrn` mediumint NOT NULL DEFAULT 0 COMMENT 'Number of successful deliveries via legacy DFRN',
`diaspora` mediumint NOT NULL DEFAULT 0 COMMENT 'Number of successful deliveries via Diaspora',
`ostatus` mediumint NOT NULL DEFAULT 0 COMMENT 'Number of successful deliveries via OStatus',
PRIMARY KEY(`iid`)
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Delivery data for items';
--
-- TABLE item-uri
--
CREATE TABLE IF NOT EXISTS `item-uri` (
`id` int unsigned NOT NULL auto_increment,
`uri` varbinary(255) NOT NULL COMMENT 'URI of an item',
`guid` varbinary(255) COMMENT 'A unique identifier for an item',
PRIMARY KEY(`id`),
UNIQUE INDEX `uri` (`uri`),
INDEX `guid` (`guid`)
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='URI and GUID for items';
--
-- TABLE locks
--
@ -832,6 +865,8 @@ CREATE TABLE IF NOT EXISTS `notify` (
`link` varchar(255) NOT NULL DEFAULT '' COMMENT '',
`iid` int unsigned NOT NULL DEFAULT 0 COMMENT 'item.id',
`parent` int unsigned NOT NULL DEFAULT 0 COMMENT '',
`uri-id` int unsigned COMMENT 'Item-uri id of the related post',
`parent-uri-id` int unsigned COMMENT 'Item-uri id of the parent of the related post',
`seen` boolean NOT NULL DEFAULT '0' COMMENT '',
`verb` varchar(100) NOT NULL DEFAULT '' COMMENT '',
`otype` varchar(10) NOT NULL DEFAULT '' COMMENT '',
@ -850,9 +885,11 @@ CREATE TABLE IF NOT EXISTS `notify-threads` (
`id` int unsigned NOT NULL auto_increment COMMENT 'sequential ID',
`notify-id` int unsigned NOT NULL DEFAULT 0 COMMENT '',
`master-parent-item` int unsigned NOT NULL DEFAULT 0 COMMENT '',
`master-parent-uri-id` int unsigned COMMENT 'Item-uri id of the parent of the related post',
`parent-item` int unsigned NOT NULL DEFAULT 0 COMMENT '',
`receiver-uid` mediumint unsigned NOT NULL DEFAULT 0 COMMENT 'User id',
PRIMARY KEY(`id`)
PRIMARY KEY(`id`),
INDEX `master-parent-uri-id` (`master-parent-uri-id`)
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='';
--
@ -919,20 +956,6 @@ CREATE TABLE IF NOT EXISTS `pconfig` (
UNIQUE INDEX `uid_cat_k` (`uid`,`cat`,`k`)
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='personal (per user) configuration storage';
--
-- TABLE permissionset
--
CREATE TABLE IF NOT EXISTS `permissionset` (
`id` int unsigned NOT NULL auto_increment COMMENT 'sequential ID',
`uid` mediumint unsigned NOT NULL DEFAULT 0 COMMENT 'Owner id of this permission set',
`allow_cid` mediumtext COMMENT 'Access Control - list of allowed contact.id \'<19><78>\'',
`allow_gid` mediumtext COMMENT 'Access Control - list of allowed groups',
`deny_cid` mediumtext COMMENT 'Access Control - list of denied contact.id',
`deny_gid` mediumtext COMMENT 'Access Control - list of denied groups',
PRIMARY KEY(`id`),
INDEX `uid_allow_cid_allow_gid_deny_cid_deny_gid` (`allow_cid`(50),`allow_gid`(30),`deny_cid`(50),`deny_gid`(30))
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='';
--
-- TABLE photo
--
@ -1003,6 +1026,55 @@ CREATE TABLE IF NOT EXISTS `poll_result` (
INDEX `poll_id` (`poll_id`)
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='data for polls - currently unused';
--
-- TABLE post-category
--
CREATE TABLE IF NOT EXISTS `post-category` (
`uri-id` int unsigned NOT NULL COMMENT 'Id of the item-uri table entry that contains the item uri',
`uid` mediumint unsigned NOT NULL DEFAULT 0 COMMENT 'User id',
`type` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '',
`tid` int unsigned NOT NULL DEFAULT 0 COMMENT '',
PRIMARY KEY(`uri-id`,`uid`,`type`,`tid`),
INDEX `uri-id` (`tid`),
FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
FOREIGN KEY (`tid`) REFERENCES `tag` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='post relation to categories';
--
-- TABLE post-delivery-data
--
CREATE TABLE IF NOT EXISTS `post-delivery-data` (
`uri-id` int unsigned NOT NULL COMMENT 'Id of the item-uri table entry that contains the item uri',
`postopts` text COMMENT 'External post connectors add their network name to this comma-separated string to identify that they should be delivered to these networks during delivery',
`inform` mediumtext COMMENT 'Additional receivers of the linked item',
`queue_count` mediumint NOT NULL DEFAULT 0 COMMENT 'Initial number of delivery recipients, used as item.delivery_queue_count',
`queue_done` mediumint NOT NULL DEFAULT 0 COMMENT 'Number of successful deliveries, used as item.delivery_queue_done',
`queue_failed` mediumint NOT NULL DEFAULT 0 COMMENT 'Number of unsuccessful deliveries, used as item.delivery_queue_failed',
`activitypub` mediumint NOT NULL DEFAULT 0 COMMENT 'Number of successful deliveries via ActivityPub',
`dfrn` mediumint NOT NULL DEFAULT 0 COMMENT 'Number of successful deliveries via DFRN',
`legacy_dfrn` mediumint NOT NULL DEFAULT 0 COMMENT 'Number of successful deliveries via legacy DFRN',
`diaspora` mediumint NOT NULL DEFAULT 0 COMMENT 'Number of successful deliveries via Diaspora',
`ostatus` mediumint NOT NULL DEFAULT 0 COMMENT 'Number of successful deliveries via OStatus',
PRIMARY KEY(`uri-id`),
FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Delivery data for items';
--
-- TABLE post-tag
--
CREATE TABLE IF NOT EXISTS `post-tag` (
`uri-id` int unsigned NOT NULL COMMENT 'Id of the item-uri table entry that contains the item uri',
`type` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '',
`tid` int unsigned NOT NULL DEFAULT 0 COMMENT '',
`cid` int unsigned NOT NULL DEFAULT 0 COMMENT 'Contact id of the mentioned public contact',
PRIMARY KEY(`uri-id`,`type`,`tid`,`cid`),
INDEX `tid` (`tid`),
INDEX `cid` (`cid`),
FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
FOREIGN KEY (`tid`) REFERENCES `tag` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT,
FOREIGN KEY (`cid`) REFERENCES `contact` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='post relation to tags';
--
-- TABLE process
--
@ -1093,7 +1165,8 @@ CREATE TABLE IF NOT EXISTS `profile_field` (
PRIMARY KEY(`id`),
INDEX `uid` (`uid`),
INDEX `order` (`order`),
INDEX `psid` (`psid`)
INDEX `psid` (`psid`),
FOREIGN KEY (`psid`) REFERENCES `permissionset` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Custom profile fields';
--
@ -1153,46 +1226,20 @@ CREATE TABLE IF NOT EXISTS `session` (
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='web session storage';
--
-- TABLE sign
-- TABLE storage
--
CREATE TABLE IF NOT EXISTS `sign` (
`id` int unsigned NOT NULL auto_increment COMMENT 'sequential ID',
`iid` int unsigned NOT NULL DEFAULT 0 COMMENT 'item.id',
`signed_text` mediumtext COMMENT '',
`signature` text COMMENT '',
`signer` varchar(255) NOT NULL DEFAULT '' COMMENT '',
PRIMARY KEY(`id`),
UNIQUE INDEX `iid` (`iid`)
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Diaspora signatures';
--
-- TABLE term
--
CREATE TABLE IF NOT EXISTS `term` (
`tid` int unsigned NOT NULL auto_increment COMMENT '',
`oid` int unsigned NOT NULL DEFAULT 0 COMMENT '',
`otype` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '',
`type` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '',
`term` varchar(255) NOT NULL DEFAULT '' COMMENT '',
`url` varchar(255) NOT NULL DEFAULT '' COMMENT '',
`guid` varchar(255) NOT NULL DEFAULT '' COMMENT '',
`created` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT '',
`received` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT '',
`global` boolean NOT NULL DEFAULT '0' COMMENT '',
`uid` mediumint unsigned NOT NULL DEFAULT 0 COMMENT 'User id',
PRIMARY KEY(`tid`),
INDEX `term_type` (`term`(64),`type`),
INDEX `oid_otype_type_term` (`oid`,`otype`,`type`,`term`(32)),
INDEX `uid_otype_type_term_global_created` (`uid`,`otype`,`type`,`term`(32),`global`,`created`),
INDEX `uid_otype_type_url` (`uid`,`otype`,`type`,`url`(64)),
INDEX `guid` (`guid`(64))
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='item taxonomy (categories, tags, etc.) table';
CREATE TABLE IF NOT EXISTS `storage` (
`id` int unsigned NOT NULL auto_increment COMMENT 'Auto incremented image data id',
`data` longblob NOT NULL COMMENT 'file data',
PRIMARY KEY(`id`)
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Data stored by Database storage backend';
--
-- TABLE thread
--
CREATE TABLE IF NOT EXISTS `thread` (
`iid` int unsigned NOT NULL DEFAULT 0 COMMENT 'sequential ID',
`uri-id` int unsigned COMMENT 'Id of the item-uri table entry that contains the item uri',
`uid` mediumint unsigned NOT NULL DEFAULT 0 COMMENT 'User id',
`contact-id` int unsigned NOT NULL DEFAULT 0 COMMENT '',
`owner-id` int unsigned NOT NULL DEFAULT 0 COMMENT 'Item owner',
@ -1228,7 +1275,9 @@ CREATE TABLE IF NOT EXISTS `thread` (
INDEX `uid_received` (`uid`,`received`),
INDEX `uid_commented` (`uid`,`commented`),
INDEX `uid_wall_received` (`uid`,`wall`,`received`),
INDEX `private_wall_origin_commented` (`private`,`wall`,`origin`,`commented`)
INDEX `private_wall_origin_commented` (`private`,`wall`,`origin`,`commented`),
INDEX `uri-id` (`uri-id`),
FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Thread related data';
--
@ -1241,7 +1290,9 @@ CREATE TABLE IF NOT EXISTS `tokens` (
`expires` int NOT NULL DEFAULT 0 COMMENT '',
`scope` varchar(200) NOT NULL DEFAULT '' COMMENT '',
`uid` mediumint unsigned NOT NULL DEFAULT 0 COMMENT 'User id',
PRIMARY KEY(`id`)
PRIMARY KEY(`id`),
INDEX `client_id` (`client_id`),
FOREIGN KEY (`client_id`) REFERENCES `clients` (`client_id`) ON UPDATE RESTRICT ON DELETE CASCADE
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='OAuth usage';
--
@ -1334,6 +1385,15 @@ CREATE TABLE IF NOT EXISTS `user-item` (
INDEX `iid_uid` (`iid`,`uid`)
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='User specific item data';
--
-- TABLE verb
--
CREATE TABLE IF NOT EXISTS `verb` (
`id` smallint unsigned NOT NULL auto_increment,
`name` varchar(100) NOT NULL DEFAULT '' COMMENT '',
PRIMARY KEY(`id`)
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Activity Verbs';
--
-- TABLE worker-ipc
--
@ -1366,12 +1426,227 @@ CREATE TABLE IF NOT EXISTS `workerqueue` (
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Background tasks queue entries';
--
-- TABLE storage
-- VIEW category-view
--
CREATE TABLE IF NOT EXISTS `storage` (
`id` int unsigned NOT NULL auto_increment COMMENT 'Auto incremented image data id',
`data` longblob NOT NULL COMMENT 'file data',
PRIMARY KEY(`id`)
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Data stored by Database storage backend';
DROP VIEW IF EXISTS `category-view`;
CREATE VIEW `category-view` AS SELECT
`post-category`.`uri-id` AS `uri-id`,
`post-category`.`uid` AS `uid`,
`item-uri`.`uri` AS `uri`,
`item-uri`.`guid` AS `guid`,
`post-category`.`type` AS `type`,
`post-category`.`tid` AS `tid`,
`tag`.`name` AS `name`,
`tag`.`url` AS `url`
FROM `post-category`
INNER JOIN `item-uri` ON `item-uri`.id = `post-category`.`uri-id`
LEFT JOIN `tag` ON `post-category`.`tid` = `tag`.`id`;
--
-- VIEW tag-view
--
DROP VIEW IF EXISTS `tag-view`;
CREATE VIEW `tag-view` AS SELECT
`post-tag`.`uri-id` AS `uri-id`,
`item-uri`.`uri` AS `uri`,
`item-uri`.`guid` AS `guid`,
`post-tag`.`type` AS `type`,
`post-tag`.`tid` AS `tid`,
`post-tag`.`cid` AS `cid`,
CASE `cid` WHEN 0 THEN `tag`.`name` ELSE `contact`.`name` END AS `name`,
CASE `cid` WHEN 0 THEN `tag`.`url` ELSE `contact`.`url` END AS `url`
FROM `post-tag`
INNER JOIN `item-uri` ON `item-uri`.id = `post-tag`.`uri-id`
LEFT JOIN `tag` ON `post-tag`.`tid` = `tag`.`id`
LEFT JOIN `contact` ON `post-tag`.`cid` = `contact`.`id`;
--
-- VIEW owner-view
--
DROP VIEW IF EXISTS `owner-view`;
CREATE VIEW `owner-view` AS SELECT
`contact`.`id` AS `id`,
`contact`.`uid` AS `uid`,
`contact`.`created` AS `created`,
`contact`.`updated` AS `updated`,
`contact`.`self` AS `self`,
`contact`.`remote_self` AS `remote_self`,
`contact`.`rel` AS `rel`,
`contact`.`duplex` AS `duplex`,
`contact`.`network` AS `network`,
`contact`.`protocol` AS `protocol`,
`contact`.`name` AS `name`,
`contact`.`nick` AS `nick`,
`contact`.`location` AS `location`,
`contact`.`about` AS `about`,
`contact`.`keywords` AS `keywords`,
`contact`.`gender` AS `gender`,
`contact`.`xmpp` AS `xmpp`,
`contact`.`attag` AS `attag`,
`contact`.`avatar` AS `avatar`,
`contact`.`photo` AS `photo`,
`contact`.`thumb` AS `thumb`,
`contact`.`micro` AS `micro`,
`contact`.`site-pubkey` AS `site-pubkey`,
`contact`.`issued-id` AS `issued-id`,
`contact`.`dfrn-id` AS `dfrn-id`,
`contact`.`url` AS `url`,
`contact`.`nurl` AS `nurl`,
`contact`.`addr` AS `addr`,
`contact`.`alias` AS `alias`,
`contact`.`pubkey` AS `pubkey`,
`contact`.`prvkey` AS `prvkey`,
`contact`.`batch` AS `batch`,
`contact`.`request` AS `request`,
`contact`.`notify` AS `notify`,
`contact`.`poll` AS `poll`,
`contact`.`confirm` AS `confirm`,
`contact`.`poco` AS `poco`,
`contact`.`aes_allow` AS `aes_allow`,
`contact`.`ret-aes` AS `ret-aes`,
`contact`.`usehub` AS `usehub`,
`contact`.`subhub` AS `subhub`,
`contact`.`hub-verify` AS `hub-verify`,
`contact`.`last-update` AS `last-update`,
`contact`.`success_update` AS `success_update`,
`contact`.`failure_update` AS `failure_update`,
`contact`.`name-date` AS `name-date`,
`contact`.`uri-date` AS `uri-date`,
`contact`.`avatar-date` AS `avatar-date`,
`contact`.`avatar-date` AS `picdate`,
`contact`.`term-date` AS `term-date`,
`contact`.`last-item` AS `last-item`,
`contact`.`priority` AS `priority`,
`contact`.`blocked` AS `blocked`,
`contact`.`block_reason` AS `block_reason`,
`contact`.`readonly` AS `readonly`,
`contact`.`writable` AS `writable`,
`contact`.`forum` AS `forum`,
`contact`.`prv` AS `prv`,
`contact`.`contact-type` AS `contact-type`,
`contact`.`hidden` AS `hidden`,
`contact`.`archive` AS `archive`,
`contact`.`pending` AS `pending`,
`contact`.`deleted` AS `deleted`,
`contact`.`rating` AS `rating`,
`contact`.`unsearchable` AS `unsearchable`,
`contact`.`sensitive` AS `sensitive`,
`contact`.`baseurl` AS `baseurl`,
`contact`.`reason` AS `reason`,
`contact`.`closeness` AS `closeness`,
`contact`.`info` AS `info`,
`contact`.`profile-id` AS `profile-id`,
`contact`.`bdyear` AS `bdyear`,
`contact`.`bd` AS `bd`,
`contact`.`notify_new_posts` AS `notify_new_posts`,
`contact`.`fetch_further_information` AS `fetch_further_information`,
`contact`.`ffi_keyword_denylist` AS `ffi_keyword_denylist`,
`user`.`parent-uid` AS `parent-uid`,
`user`.`guid` AS `guid`,
`user`.`nickname` AS `nickname`,
`user`.`email` AS `email`,
`user`.`openid` AS `openid`,
`user`.`timezone` AS `timezone`,
`user`.`language` AS `language`,
`user`.`register_date` AS `register_date`,
`user`.`login_date` AS `login_date`,
`user`.`default-location` AS `default-location`,
`user`.`allow_location` AS `allow_location`,
`user`.`theme` AS `theme`,
`user`.`pubkey` AS `upubkey`,
`user`.`prvkey` AS `uprvkey`,
`user`.`sprvkey` AS `sprvkey`,
`user`.`spubkey` AS `spubkey`,
`user`.`verified` AS `verified`,
`user`.`blockwall` AS `blockwall`,
`user`.`hidewall` AS `hidewall`,
`user`.`blocktags` AS `blocktags`,
`user`.`unkmail` AS `unkmail`,
`user`.`cntunkmail` AS `cntunkmail`,
`user`.`notify-flags` AS `notify-flags`,
`user`.`page-flags` AS `page-flags`,
`user`.`account-type` AS `account-type`,
`user`.`prvnets` AS `prvnets`,
`user`.`maxreq` AS `maxreq`,
`user`.`expire` AS `expire`,
`user`.`account_removed` AS `account_removed`,
`user`.`account_expired` AS `account_expired`,
`user`.`account_expires_on` AS `account_expires_on`,
`user`.`expire_notification_sent` AS `expire_notification_sent`,
`user`.`def_gid` AS `def_gid`,
`user`.`allow_cid` AS `allow_cid`,
`user`.`allow_gid` AS `allow_gid`,
`user`.`deny_cid` AS `deny_cid`,
`user`.`deny_gid` AS `deny_gid`,
`user`.`openidserver` AS `openidserver`,
`profile`.`publish` AS `publish`,
`profile`.`net-publish` AS `net-publish`,
`profile`.`hide-friends` AS `hide-friends`,
`profile`.`prv_keywords` AS `prv_keywords`,
`profile`.`pub_keywords` AS `pub_keywords`,
`profile`.`address` AS `address`,
`profile`.`locality` AS `locality`,
`profile`.`region` AS `region`,
`profile`.`postal-code` AS `postal-code`,
`profile`.`country-name` AS `country-name`,
`profile`.`homepage` AS `homepage`,
`profile`.`dob` AS `dob`
FROM `user`
INNER JOIN `contact` ON `contact`.`uid` = `user`.`uid` AND `contact`.`self`
INNER JOIN `profile` ON `profile`.`uid` = `user`.`uid`;
--
-- VIEW pending-view
--
DROP VIEW IF EXISTS `pending-view`;
CREATE VIEW `pending-view` AS SELECT
`register`.`id` AS `id`,
`register`.`hash` AS `hash`,
`register`.`created` AS `created`,
`register`.`uid` AS `uid`,
`register`.`password` AS `password`,
`register`.`language` AS `language`,
`register`.`note` AS `note`,
`contact`.`self` AS `self`,
`contact`.`name` AS `name`,
`contact`.`url` AS `url`,
`contact`.`micro` AS `micro`,
`user`.`email` AS `email`,
`contact`.`nick` AS `nick`
FROM `register`
INNER JOIN `contact` ON `register`.`uid` = `contact`.`uid`
INNER JOIN `user` ON `register`.`uid` = `user`.`uid`;
--
-- VIEW tag-search-view
--
DROP VIEW IF EXISTS `tag-search-view`;
CREATE VIEW `tag-search-view` AS SELECT
`post-tag`.`uri-id` AS `uri-id`,
`item`.`id` AS `iid`,
`item`.`uri` AS `uri`,
`item`.`guid` AS `guid`,
`item`.`uid` AS `uid`,
`item`.`private` AS `private`,
`item`.`wall` AS `wall`,
`item`.`origin` AS `origin`,
`item`.`gravity` AS `gravity`,
`item`.`received` AS `received`,
`tag`.`name` AS `name`
FROM `post-tag`
INNER JOIN `tag` ON `tag`.`id` = `post-tag`.`tid`
INNER JOIN `item` ON `item`.`uri-id` = `post-tag`.`uri-id`
WHERE `post-tag`.`type` = 1;
--
-- VIEW workerqueue-view
--
DROP VIEW IF EXISTS `workerqueue-view`;
CREATE VIEW `workerqueue-view` AS SELECT
`process`.`pid` AS `pid`,
`workerqueue`.`priority` AS `priority`
FROM `process`
INNER JOIN `workerqueue` ON `workerqueue`.`pid` = `process`.`pid`
WHERE NOT `workerqueue`.`done`;

View file

@ -152,19 +152,29 @@ These endpoints use the [Friendica API entities](help/API-Entities).
- [GET api/friendships/incoming](https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friendships-incoming)
- Unsupported parameters
- `stringify_ids`
- [GET api/followers/ids](https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-followers-ids)
- Unsupported parameters:
- `user_id`: Relationships aren't returned for other users than self
- `screen_name`: Relationships aren't returned for other users than self
- [GET api/friends/ids](https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friends-ids)
- Unsupported parameters:
- `user_id`: Relationships aren't returned for other users than self
- `screen_name`: Relationships aren't returned for other users than self
- - [GET api/followers/ids](https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-followers-ids)
- [GET api/followers/list](https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-followers-list)
- [GET api/friends/ids](https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friends-ids)
- [GET api/friends/list](https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friends-list)
- Additional parameters:
- `since_id`: You can use the `next_cursor` value to load the next page.
- `max_id`: You can use the inverse of the `previous_cursor` value to load the previous page.
- Unsupported parameter:
- `skip_status`: No status is returned even if it isn't set to true.
- Caveats:
- `cursor` trumps `since_id` trumps `max_id` if any combination is provided.
- `user_id` must be the ID of a contact associated with a local user account.
- `screen_name` must be associated with a local user account.
- `screen_name` trumps `user_id` if both are provided (undocumented Twitter behavior).
- Will succeed but return an empty array for users hiding their contact lists.
- [POST api/friendships/destroy](https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/post-friendships-destroy)
## Non-implemented endpoints
- [GET oauth/authenticate](https://developer.twitter.com/en/docs/basics/authentication/api-reference/authenticate)
@ -188,8 +198,6 @@ These endpoints use the [Friendica API entities](help/API-Entities).
- [POST lists/subscribers/destroy](https://developer.twitter.com/en/docs/accounts-and-users/create-manage-lists/api-reference/post-lists-subscribers-destroy)
- [GET followers/list](https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-followers-list)
- [GET friends/list](https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friends-list)
- [GET friendships/lookup](https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friendships-lookup)
- [GET friendships/no_retweets/ids](https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friendships-no_retweets-ids)
- [GET friendships/outgoing](https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friendships-outgoing)

View file

@ -466,6 +466,19 @@ Hook data is a `\FastRoute\RouterCollector` object that should be used to add ad
**Notice**: The class whose name is provided in the route handler must be reachable via auto-loader.
### probe_detect
Called before trying to detect the target network of a URL.
If any registered hook function sets the `result` key of the hook data array, it will be returned immediately.
Hook functions should also return immediately if the hook data contains an existing result.
Hook data:
- **uri** (input): the profile URI.
- **network** (input): the target network (can be empty for auto-detection).
- **uid** (input): the user to return the contact data for (can be empty for public contacts).
- **result** (output): Set by the hook function to indicate a successful detection.
## Complete list of hook callbacks
Here is a complete list of all hook callbacks with file locations (as of 24-Sep-2018). Please see the source for details of any hooks not documented above.

View file

@ -65,17 +65,17 @@ table.bbcodes > * > tr > th {
<td><a href="http://friendi.ca" target="external-link">Friendica</a></td>
</tr>
<tr>
<td>[img]https://raw.githubusercontent.com/friendica/friendica/master/images/friendica-32.jpg[/img]</td>
<td><img src="https://raw.githubusercontent.com/friendica/friendica/master/images/friendica-32.jpg" alt="Immagine/foto"></td>
<td>[img]https://raw.githubusercontent.com/friendica/friendica/stable/images/friendica-32.jpg[/img]</td>
<td><img src="https://raw.githubusercontent.com/friendica/friendica/stable/images/friendica-32.jpg" alt="Immagine/foto"></td>
</tr>
<tr>
<td>[img=https://raw.githubusercontent.com/friendica/friendica/master/images/friendica-32.jpg]The Friendica Logo[/img]</td>
<td><img src="https://raw.githubusercontent.com/friendica/friendica/master/images/friendica-32.jpg" alt="The Friendica Logo"></td>
<td>[img=https://raw.githubusercontent.com/friendica/friendica/stable/images/friendica-32.jpg]The Friendica Logo[/img]</td>
<td><img src="https://raw.githubusercontent.com/friendica/friendica/stable/images/friendica-32.jpg" alt="The Friendica Logo"></td>
</tr>
<tr>
<td>[img=64x32]https://raw.githubusercontent.com/friendica/friendica/master/images/friendica-32.jpg[/img]<br>
<td>[img=64x32]https://raw.githubusercontent.com/friendica/friendica/stable/images/friendica-32.jpg[/img]<br>
<br>Note: provided height is simply discarded.</td>
<td><img src="https://raw.githubusercontent.com/friendica/friendica/master/images/friendica-32.jpg" style="width: 64px;"></td>
<td><img src="https://raw.githubusercontent.com/friendica/friendica/stable/images/friendica-32.jpg" style="width: 64px;"></td>
</tr>
<tr>
<td>[size=xx-small]small text[/size]</td>
@ -613,15 +613,34 @@ On Mastodon this field is used for the content warning.
<th>Result</th>
</tr>
<tr>
<td>If you need to put literal bbcode in a message, [noparse], [nobb] or [pre] are used to escape bbcode:
<td>If you need to put literal BBCode in a message, [noparse], [nobb] or [pre] blocks prevent BBCode conversion:
<ul>
<li>[noparse][b]bold[/b][/noparse]</li>
<li>[nobb][b]bold[/b][/nobb]</li>
<li>[pre][b]bold[/b][/pre]</li>
</ul>
Note: [code] has priority over [noparse], [nobb] and [pre] which makes them display as BBCode tags in code blocks instead of being removed.
[code] blocks inside [noparse] will still be converted to a code block.
</td>
<td>[b]bold[/b]</td>
</tr>
<tr>
<td>Additionally, [noparse] and [pre] blocks prevent mention and hashtag conversion to links:
<ul>
<li>[noparse]@user@domain.tld #hashtag[/noparse]</li>
<li>[pre]@user@domain.tld #hashtag[/pre]</li>
</ul>
</td>
<td>@user@domain.tld #hashtag</td>
</tr>
<tr>
<td>Additionally, [pre] blocks preserve spaces:
<ul>
<li>[pre]&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Spaces[/pre]</li>
</ul>
</td>
<td>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Spaces</td>
</tr>
<tr>
<td>[nosmile] is used to disable smilies on a post by post basis<br>
<br>

View file

@ -43,7 +43,7 @@ At first you have to get the current version. You can either pull it from [Githu
$> cd /var/www/virtual/YOURSPACE/html/addon; git pull
Or you can download a tar archive here: [jappixmini.tgz](https://github.com/friendica/friendica-addons/blob/master/jappixmini.tgz) (click at „view raw“).
Or you can download a tar archive here: [jappixmini.tgz](https://github.com/friendica/friendica-addons/blob/stable/jappixmini.tgz) (click at „view raw“).
Just unpack the file and rename the directory to „jappixmini“.
Next, upload this directory and the .tgz-file into your addon directory of your friendica installation.

View file

@ -22,7 +22,7 @@ Our Git Branches
There are two relevant branches in the main repo on GitHub:
1. master: This branch contains stable releases only.
1. stable: This branch contains stable releases only.
2. develop: This branch contains the latest code.
This is what you want to work with.
@ -43,7 +43,7 @@ Release branches
A release branch is created when the develop branch contains all features it should have.
A release branch is used for a few things.
1. It allows last-minute bug fixing before the release goes to master branch.
1. It allows last-minute bug fixing before the release goes to stable branch.
2. It allows meta-data changes (README, CHANGELOG, etc.) for version bumps and documentation changes.
3. It makes sure the develop branch can receive new features that are **not** part of this release.

View file

@ -72,7 +72,7 @@ This makes the software much easier to update.
The Linux commands to clone the repository into a directory "mywebsite" would be
git clone https://github.com/friendica/friendica.git -b master mywebsite
git clone https://github.com/friendica/friendica.git -b stable mywebsite
cd mywebsite
bin/composer.phar install --no-dev
@ -88,7 +88,7 @@ Get the addons by going into your website folder.
Clone the addon repository (separately):
git clone https://github.com/friendica/friendica-addons.git -b master addon
git clone https://github.com/friendica/friendica-addons.git -b stable addon
If you want to use the development version of Friendica you can switch to the develop branch in the repository by running
@ -435,7 +435,7 @@ provided by one of our members.
>
> This is obvious as soon as you notice that the friendica-cron uses `proc_open`
> to execute PHP scripts that also use `proc_open`, but it took me quite some time to find that out.
> I hope this saves some time for other people using suhosin with function blacklists.
> I hope this saves some time for other people using suhosin with function blocklists.
### Unable to create all mysql tables on MySQL 5.7.17 or newer

View file

@ -4,7 +4,7 @@ Friendica Message Flow
This page documents some of the details of how messages get from one person to another in the Friendica network.
There are multiple paths, using multiple protocols and message formats.
Those attempting to understand these message flows should become familiar with (at the minimum) the [DFRN protocol document](https://github.com/friendica/friendica/blob/master/spec/dfrn2.pdf) and the message passing elements of the OStatus stack (salmon and Pubsubhubbub).
Those attempting to understand these message flows should become familiar with (at the minimum) the [DFRN protocol document](https://github.com/friendica/friendica/blob/stable/spec/dfrn2.pdf) and the message passing elements of the OStatus stack (salmon and Pubsubhubbub).
Most message passing involves the file include/items.php, which has functions for several feed-related import/export activities.

View file

@ -74,7 +74,7 @@ You can chose between the following modes:
##### Invitation based registry
Additionally to the setting in the admin panel, you can decide if registrations are only possible using an invitation code or not.
To enable invitation based registration, you have to set the `invitation_only` setting in the [config/local.config.php](/help/Config) file.
To enable invitation based registration, you have to set the `invitation_only` setting to `true` in the `system` section of the [config/local.config.php](/help/Config) file.
If you want to use this method, the registration policy has to be set to either *open* or *requires approval*.
#### Check Full Names

View file

@ -8,7 +8,13 @@ Updating Friendica
If you installed Friendica in the ``path/to/friendica`` folder:
1. Unpack the new Friendica archive in ``path/to/friendica_new``.
2. Copy ``config/local.config.php``, ``photo/`` and ``proxy/`` from ``path/to/friendica`` to ``path/to/friendica_new``.
2. Copy the following items from ``path/to/friendica`` to ``path/to/friendica_new``:
* ``config/local.config.php``
* ``proxy/``
The following items only need to be copied if they are located inside your friendica path:
* your storage folder as set in **Admin -> Site -> File Upload -> Storage base path**
* your item cache as set in **Admin -> Site -> Performance -> Path to item cache**
* your temp folder as set in **Admin -> Site -> Advanced -> Temp path**
3. Rename the ``path/to/friendica`` folder to ``path/to/friendica_old``.
4. Rename the ``path/to/friendica_new`` folder to ``path/to/friendica``.
5. Check your site. Note: it may go into maintenance mode to update the database schema.
@ -30,11 +36,11 @@ The addon tree has to be updated separately like so:
git pull
For both repositories:
The default branch to use is the ``master`` branch, which is the stable version of Friendica.
The default branch to use is the ``stable`` branch, which is the stable version of Friendica.
It is updated about four times a year on a fixed schedule.
If you want to use and test bleeding edge code please checkout the ``develop`` branch.
The new features and fixes will be merged from ``develop`` into ``master`` after a release candidate period before each release.
The new features and fixes will be merged from ``develop`` into ``stable`` after a release candidate period before each release.
Warning: The ``develop`` branch is unstable, and breaks on average once a month for at most 24 hours until a patch is submitted and merged.
Be sure to pull frequently if you choose the ``develop`` branch.

View file

@ -67,6 +67,6 @@ Table contact
| bd | | date | NO | | 0001-01-01 | |
| notify_new_posts | | tinyint(1) | NO | | 0 | |
| fetch_further_information | | tinyint(1) | NO | | 0 | |
| ffi_keyword_blacklist | | mediumtext | NO | | NULL | |
| ffi_keyword_denylist | | mediumtext | NO | | NULL | |
Return to [database documentation](help/database)

View file

@ -65,17 +65,17 @@ table.bbcodes > * > tr > th {
<td><a href="http://friendi.ca" target="external-link">Friendica</a></td>
</tr>
<tr>
<td>[img]https://raw.githubusercontent.com/friendica/friendica/master/images/friendica-32.jpg[/img]</td>
<td><img src="https://raw.githubusercontent.com/friendica/friendica/master/images/friendica-32.jpg" alt="Immagine/foto"></td>
<td>[img]https://raw.githubusercontent.com/friendica/friendica/stable/images/friendica-32.jpg[/img]</td>
<td><img src="https://raw.githubusercontent.com/friendica/friendica/stable/images/friendica-32.jpg" alt="Immagine/foto"></td>
</tr>
<tr>
<td>[img=https://raw.githubusercontent.com/friendica/friendica/master/images/friendica-32.jpg]Das Friendica Logo[/img]</td>
<td><img src="https://raw.githubusercontent.com/friendica/friendica/master/images/friendica-32.jpg" alt="Das Friendica Logo"></td>
<td>[img=https://raw.githubusercontent.com/friendica/friendica/stable/images/friendica-32.jpg]Das Friendica Logo[/img]</td>
<td><img src="https://raw.githubusercontent.com/friendica/friendica/stable/images/friendica-32.jpg" alt="Das Friendica Logo"></td>
</tr>
<tr>
<td>[img=64x32]https://raw.githubusercontent.com/friendica/friendica/master/images/friendica-32.jpg[/img]<br>
<td>[img=64x32]https://raw.githubusercontent.com/friendica/friendica/stable/images/friendica-32.jpg[/img]<br>
<br>Note: provided height is simply discarded.</td>
<td><img src="https://raw.githubusercontent.com/friendica/friendica/master/images/friendica-32.jpg" style="width: 64px;"></td>
<td><img src="https://raw.githubusercontent.com/friendica/friendica/stable/images/friendica-32.jpg" style="width: 64px;"></td>
</tr>
<tr>
<td>[size=xx-small]kleiner Text[/size]</td>

View file

@ -49,7 +49,7 @@ Per Git:
cd /var/www/&lt;Pfad zu Deiner friendica-Installation&gt;/addon; git pull
</p>
oder als normaler Download von hier: https://github.com/friendica/friendica-addons/blob/master/jappixmini.tgz (auf „view raw“ klicken)
oder als normaler Download von hier: https://github.com/friendica/friendica-addons/blob/stable/jappixmini.tgz (auf „view raw“ klicken)
Entpacke diese Datei (ggf. den entpackten Ordner in „jappixmini“ umbenennen) und lade sowohl den entpackten Ordner komplett als auch die .tgz Datei in den Addon Ordner Deiner Friendica Installation hoch.

View file

@ -55,7 +55,7 @@ Wenn du die Möglichkeit hierzu hast, empfehlen wir dir "git" zu nutzen, um die
Das macht die Aktualisierung wesentlich einfacher.
Der Linux-Code, mit dem man die Dateien direkt in ein Verzeichnis wie "meinewebseite" kopiert, ist
git clone https://github.com/friendica/friendica.git -b master mywebsite
git clone https://github.com/friendica/friendica.git -b stable mywebsite
cd mywebsite
bin/composer.phar install
@ -70,7 +70,7 @@ Falls Addons installiert werden sollen: Gehe in den Friendica-Ordner
Und die Addon Repository klonst:
git clone https://github.com/friendica/friendica-addons.git -b master addon
git clone https://github.com/friendica/friendica-addons.git -b stable addon
Um das Addon-Verzeichnis aktuell zu halten, solltest du in diesem Pfad ein "git pull"-Befehl eintragen

View file

@ -6,7 +6,7 @@ Friendica Nachrichtenfluss
Diese Seite soll einige Infos darüber dokumentieren, wie Nachrichten innerhalb von Friendica von einer Person zur anderen übertragen werden.
Es gibt verschiedene Pfade, die verschiedene Protokolle und Nachrichtenformate nutzen.
Diejenigen, die den Nachrichtenfluss genauer verstehen wollen, sollten sich mindestens mit dem DFRN-Protokoll ([Dokument mit den DFRN Spezifikationen](https://github.com/friendica/friendica/blob/master/spec/dfrn2.pdf)) und den Elementen zur Nachrichtenverarbeitung des OStatus Stack informieren (salmon und Pubsubhubbub).
Diejenigen, die den Nachrichtenfluss genauer verstehen wollen, sollten sich mindestens mit dem DFRN-Protokoll ([Dokument mit den DFRN Spezifikationen](https://github.com/friendica/friendica/blob/stable/spec/dfrn2.pdf)) und den Elementen zur Nachrichtenverarbeitung des OStatus Stack informieren (salmon und Pubsubhubbub).
Der Großteil der Nachrichtenverarbeitung nutzt die Datei include/items.php, welche Funktionen für verschiedene Feed-bezogene Import-/Exportaktivitäten liefert.

View file

@ -3,7 +3,7 @@
* [Home](help)
To change the look of friendica you have to touch the themes.
The current default theme is [Vier](https://github.com/friendica/friendica/tree/master/view/theme/vier) but there are numerous others.
The current default theme is [Vier](https://github.com/friendica/friendica/tree/stable/view/theme/vier) but there are numerous others.
Have a look at [friendica-themes.com](http://friendica-themes.com) for an overview of the existing themes.
In case none of them suits your needs, there are several ways to change a theme.

View file

@ -8,7 +8,7 @@ Friendica translations
The Friendica translation process is based on `gettext` PO files.
Basic worflow:
1. `xgettext` is used to collect translation strings across the project in the master PO file located in `view/lang/C/messages.po`.
1. `xgettext` is used to collect translation strings across the project in the authoritative PO file located in `view/lang/C/messages.po`.
2. This file makes translations strings available at [the Transifex Friendica page](https://www.transifex.com/Friendica/friendica/dashboard/).
3. The translation itself is done at Transifex by volunteers.
4. The resulting PO files by languages are manually updated in `view/lang/<language>/messages.po`.

File diff suppressed because one or more lines are too long

View file

@ -43,6 +43,7 @@ use Friendica\Model\Notify;
use Friendica\Model\Photo;
use Friendica\Model\User;
use Friendica\Model\UserItem;
use Friendica\Model\Verb;
use Friendica\Network\FKOAuth1;
use Friendica\Network\HTTPException;
use Friendica\Network\HTTPException\BadRequestException;
@ -263,7 +264,10 @@ function api_login(App $a)
throw new UnauthorizedException("This API requires login");
}
DI::auth()->setForUser($a, $record);
// Don't refresh the login date more often than twice a day to spare database writes
$login_refresh = strcmp(DateTimeFormat::utc('now - 12 hours'), $record['login_date']) > 0;
DI::auth()->setForUser($a, $record, false, false, $login_refresh);
$_SESSION["allow_api"] = true;
@ -331,16 +335,16 @@ function api_call(App $a, App\Arguments $args = null)
if (!empty($info['auth']) && api_user() === false) {
api_login($a);
Logger::info(API_LOG_PREFIX . 'username {username}', ['module' => 'api', 'action' => 'call', 'username' => $a->user['username']]);
}
Logger::info(API_LOG_PREFIX . 'username {username}', ['module' => 'api', 'action' => 'call', 'username' => $a->user['username']]);
Logger::debug(API_LOG_PREFIX . 'parameters', ['module' => 'api', 'action' => 'call', 'parameters' => $_REQUEST]);
$stamp = microtime(true);
$return = call_user_func($info['func'], $type);
$duration = floatval(microtime(true) - $stamp);
Logger::info(API_LOG_PREFIX . 'username {username}', ['module' => 'api', 'action' => 'call', 'username' => $a->user['username'], 'duration' => round($duration, 2)]);
Logger::info(API_LOG_PREFIX . 'duration {duration}', ['module' => 'api', 'action' => 'call', 'duration' => round($duration, 2)]);
DI::profiler()->saveLog(DI::logger(), API_LOG_PREFIX . 'performance');
@ -623,7 +627,7 @@ function api_get_user(App $a, $contact_id = null)
'name' => $contact["name"],
'screen_name' => (($contact['nick']) ? $contact['nick'] : $contact['name']),
'location' => ($contact["location"] != "") ? $contact["location"] : ContactSelector::networkToName($contact['network'], $contact['url'], $contact['protocol']),
'description' => BBCode::toPlaintext($contact["about"]),
'description' => BBCode::toPlaintext($contact["about"] ?? ''),
'profile_image_url' => $contact["micro"],
'profile_image_url_https' => $contact["micro"],
'profile_image_url_profile_size' => $contact["thumb"],
@ -697,7 +701,7 @@ function api_get_user(App $a, $contact_id = null)
'name' => (($uinfo[0]['name']) ? $uinfo[0]['name'] : $uinfo[0]['nick']),
'screen_name' => (($uinfo[0]['nick']) ? $uinfo[0]['nick'] : $uinfo[0]['name']),
'location' => $location,
'description' => BBCode::toPlaintext($description),
'description' => BBCode::toPlaintext($description ?? ''),
'profile_image_url' => $uinfo[0]['micro'],
'profile_image_url_https' => $uinfo[0]['micro'],
'profile_image_url_profile_size' => $uinfo[0]["thumb"],
@ -1240,7 +1244,7 @@ function api_media_upload()
"image_type" => $media["type"],
"friendica_preview_url" => $media["preview"]];
Logger::log("Media uploaded: " . print_r($returndata, true), Logger::DEBUG);
Logger::info('Media uploaded', ['return' => $returndata]);
return ["media" => $returndata];
}
@ -1310,7 +1314,7 @@ api_register_func('api/media/metadata/create', 'api_media_metadata_create', true
/**
* @param string $type Return format (atom, rss, xml, json)
* @param int $item_id
* @return string
* @return array|string
* @throws Exception
*/
function api_status_show($type, $item_id)
@ -1538,34 +1542,27 @@ function api_search($type)
$params = ['order' => ['id' => true], 'limit' => [$start, $count]];
if (preg_match('/^#(\w+)$/', $searchTerm, $matches) === 1 && isset($matches[1])) {
$searchTerm = $matches[1];
$condition = ["`oid` > ?
AND (`uid` = 0 OR (`uid` = ? AND NOT `global`))
AND `otype` = ? AND `type` = ? AND `term` = ?",
$since_id, local_user(), TERM_OBJ_POST, TERM_HASHTAG, $searchTerm];
if ($max_id > 0) {
$condition[0] .= ' AND `oid` <= ?';
$condition[] = $max_id;
$condition = ["`iid` > ? AND `name` = ? AND (NOT `private` OR (`private` AND `uid` = ?))", $since_id, $searchTerm, local_user()];
$tags = DBA::select('tag-search-view', ['uri-id'], $condition);
$uriids = [];
while ($tag = DBA::fetch($tags)) {
$uriids[] = $tag['uri-id'];
}
$terms = DBA::select('term', ['oid'], $condition, []);
$itemIds = [];
while ($term = DBA::fetch($terms)) {
$itemIds[] = $term['oid'];
}
DBA::close($terms);
DBA::close($tags);
if (empty($itemIds)) {
if (empty($uriids)) {
return api_format_data('statuses', $type, $data);
}
$preCondition = ['`id` IN (' . implode(', ', $itemIds) . ')'];
$condition = ['uri-id' => $uriids];
if ($exclude_replies) {
$preCondition[] = '`id` = `parent`';
$condition['gravity'] = GRAVITY_PARENT;
}
$condition = [implode(' AND ', $preCondition)];
$params['group_by'] = ['uri-id'];
} else {
$condition = ["`id` > ?
" . ($exclude_replies ? " AND `id` = `parent` " : ' ') . "
" . ($exclude_replies ? " AND `gravity` = " . GRAVITY_PARENT : ' ') . "
AND (`uid` = 0 OR (`uid` = ? AND NOT `global`))
AND `body` LIKE CONCAT('%',?,'%')",
$since_id, api_user(), $_REQUEST['q']];
@ -1653,7 +1650,8 @@ function api_statuses_home_timeline($type)
$condition[] = $max_id;
}
if ($exclude_replies) {
$condition[0] .= ' AND `item`.`parent` = `item`.`id`';
$condition[0] .= ' AND `item`.`gravity` = ?';
$condition[] = GRAVITY_PARENT;
}
if ($conversation_id > 0) {
$condition[0] .= " AND `item`.`parent` = ?";
@ -2040,7 +2038,7 @@ function api_statuses_repeat($type)
Logger::log('API: api_statuses_repeat: '.$id);
$fields = ['body', 'title', 'attach', 'tag', 'author-name', 'author-link', 'author-avatar', 'guid', 'created', 'plink'];
$fields = ['uri-id', 'body', 'title', 'attach', 'author-name', 'author-link', 'author-avatar', 'guid', 'created', 'plink'];
$item = Item::selectFirst($fields, ['id' => $id, 'private' => [Item::PUBLIC, Item::UNLISTED]]);
if (DBA::isResult($item) && $item['body'] != "") {
@ -2048,7 +2046,7 @@ function api_statuses_repeat($type)
$pos = strpos($item['body'], "[share");
$post = substr($item['body'], $pos);
} else {
$post = share_header($item['author-name'], $item['author-link'], $item['author-avatar'], $item['guid'], $item['created'], $item['plink']);
$post = BBCode::getShareOpeningTag($item['author-name'], $item['author-link'], $item['author-avatar'], $item['plink'], $item['created'], $item['guid']);
if (!empty($item['title'])) {
$post .= '[h3]' . $item['title'] . "[/h3]\n";
@ -2058,7 +2056,6 @@ function api_statuses_repeat($type)
$post .= "[/share]";
}
$_REQUEST['body'] = $post;
$_REQUEST['tag'] = $item['tag'];
$_REQUEST['attach'] = $item['attach'];
$_REQUEST['profile_uid'] = api_user();
$_REQUEST['api_source'] = true;
@ -2068,6 +2065,8 @@ function api_statuses_repeat($type)
}
$item_id = item_post($a);
/// @todo Copy tags from the original post to the new one
} else {
throw new ForbiddenException();
}
@ -2234,12 +2233,7 @@ function api_statuses_user_timeline($type)
throw new ForbiddenException();
}
Logger::log(
"api_statuses_user_timeline: api_user: ". api_user() .
"\nuser_info: ".print_r($user_info, true) .
"\n_REQUEST: ".print_r($_REQUEST, true),
Logger::DEBUG
);
Logger::info('api_statuses_user_timeline', ['api_user' => api_user(), 'user_info' => $user_info, '_REQUEST' => $_REQUEST]);
$since_id = $_REQUEST['since_id'] ?? 0;
$max_id = $_REQUEST['max_id'] ?? 0;
@ -2260,7 +2254,8 @@ function api_statuses_user_timeline($type)
}
if ($exclude_replies) {
$condition[0] .= ' AND `item`.`parent` = `item`.`id`';
$condition[0] .= ' AND `item`.`gravity` = ?';
$condition[] = GRAVITY_PARENT;
}
if ($conversation_id > 0) {
@ -2497,10 +2492,10 @@ function api_format_messages($item, $recipient, $sender)
if ($_GET['getText'] == 'html') {
$ret['text'] = BBCode::convert($item['body'], false);
} elseif ($_GET['getText'] == 'plain') {
$ret['text'] = trim(HTML::toPlaintext(BBCode::convert(api_clean_plain_items($item['body']), false, 2, true), 0));
$ret['text'] = trim(HTML::toPlaintext(BBCode::convert(api_clean_plain_items($item['body']), false, BBCode::API, true), 0));
}
} else {
$ret['text'] = $item['title'] . "\n" . HTML::toPlaintext(BBCode::convert(api_clean_plain_items($item['body']), false, 2, true), 0);
$ret['text'] = $item['title'] . "\n" . HTML::toPlaintext(BBCode::convert(api_clean_plain_items($item['body']), false, BBCode::API, true), 0);
}
if (!empty($_GET['getUserObjects']) && $_GET['getUserObjects'] == 'false') {
unset($ret['sender']);
@ -2526,7 +2521,7 @@ function api_convert_item($item)
$attachments = api_get_attachments($body);
// Workaround for ostatus messages where the title is identically to the body
$html = BBCode::convert(api_clean_plain_items($body), false, 2, true);
$html = BBCode::convert(api_clean_plain_items($body), false, BBCode::API, true);
$statusbody = trim(HTML::toPlaintext($html, 0));
// handle data: images
@ -3033,7 +3028,7 @@ function api_format_item($item, $type = "json", $status_user = null, $author_use
$retweeted_item = [];
$quoted_item = [];
if ($item["id"] == $item["parent"]) {
if ($item['gravity'] == GRAVITY_PARENT) {
$body = $item['body'];
$retweeted_item = api_share_as_retweet($item);
if ($body != $item['body']) {
@ -3310,7 +3305,8 @@ function api_lists_statuses($type)
$condition[] = $max_id;
}
if ($exclude_replies > 0) {
$condition[0] .= ' AND `item`.`parent` = `item`.`id`';
$condition[0] .= ' AND `item`.`gravity` = ?';
$condition[] = GRAVITY_PARENT;
}
if ($conversation_id > 0) {
$condition[0] .= " AND `item`.`parent` = ?";
@ -3582,96 +3578,6 @@ function api_statusnet_version($type)
api_register_func('api/gnusocial/version', 'api_statusnet_version', false);
api_register_func('api/statusnet/version', 'api_statusnet_version', false);
/**
*
* @param string $type Return type (atom, rss, xml, json)
*
* @param int $rel A contact relationship constant
* @return array|string|void
* @throws BadRequestException
* @throws ForbiddenException
* @throws ImagickException
* @throws InternalServerErrorException
* @throws UnauthorizedException
* @todo use api_format_data() to return data
*/
function api_ff_ids($type, int $rel)
{
if (!api_user()) {
throw new ForbiddenException();
}
$a = DI::app();
api_get_user($a);
$stringify_ids = $_REQUEST['stringify_ids'] ?? false;
$contacts = DBA::p("SELECT `pcontact`.`id`
FROM `contact`
INNER JOIN `contact` AS `pcontact`
ON `contact`.`nurl` = `pcontact`.`nurl`
AND `pcontact`.`uid` = 0
WHERE `contact`.`uid` = ?
AND NOT `contact`.`self`
AND `contact`.`rel` IN (?, ?)",
api_user(),
$rel,
Contact::FRIEND
);
$ids = [];
foreach (DBA::toArray($contacts) as $contact) {
if ($stringify_ids) {
$ids[] = $contact['id'];
} else {
$ids[] = intval($contact['id']);
}
}
return api_format_data('ids', $type, ['id' => $ids]);
}
/**
* Returns the ID of every user the user is following.
*
* @param string $type Return type (atom, rss, xml, json)
*
* @return array|string
* @throws BadRequestException
* @throws ForbiddenException
* @throws ImagickException
* @throws InternalServerErrorException
* @throws UnauthorizedException
* @see https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friends-ids
*/
function api_friends_ids($type)
{
return api_ff_ids($type, Contact::SHARING);
}
/**
* Returns the ID of every user following the user.
*
* @param string $type Return type (atom, rss, xml, json)
*
* @return array|string
* @throws BadRequestException
* @throws ForbiddenException
* @throws ImagickException
* @throws InternalServerErrorException
* @throws UnauthorizedException
* @see https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-followers-ids
*/
function api_followers_ids($type)
{
return api_ff_ids($type, Contact::FOLLOWER);
}
/// @TODO move to top of file or somewhere better
api_register_func('api/friends/ids', 'api_friends_ids', true);
api_register_func('api/followers/ids', 'api_followers_ids', true);
/**
* Sends a new direct message.
*
@ -4167,26 +4073,18 @@ function api_fr_photoalbum_delete($type)
throw new BadRequestException("no albumname specified");
}
// check if album is existing
$r = q(
"SELECT DISTINCT `resource-id` FROM `photo` WHERE `uid` = %d AND `album` = '%s'",
intval(api_user()),
DBA::escape($album)
);
if (!DBA::isResult($r)) {
$photos = DBA::selectToArray('photo', ['resource-id'], ['uid' => api_user(), 'album' => $album], ['group_by' => ['resource-id']]);
if (!DBA::isResult($photos)) {
throw new BadRequestException("album not available");
}
$resourceIds = array_column($photos, 'resource-id');
// function for setting the items to "deleted = 1" which ensures that comments, likes etc. are not shown anymore
// to the user and the contacts of the users (drop_items() performs the federation of the deletion to other networks
foreach ($r as $rr) {
$condition = ['uid' => local_user(), 'resource-id' => $rr['resource-id'], 'type' => 'photo'];
$photo_item = Item::selectFirstForUser(local_user(), ['id'], $condition);
if (!DBA::isResult($photo_item)) {
throw new InternalServerErrorException("problem with deleting items occured");
}
Item::deleteForUser(['id' => $photo_item['id']], api_user());
}
$condition = ['uid' => api_user(), 'resource-id' => $resourceIds, 'type' => 'photo'];
Item::deleteForUser($condition, api_user());
// now let's delete all photos from the album
$result = Photo::delete(['uid' => api_user(), 'album' => $album]);
@ -4463,19 +4361,13 @@ function api_fr_photo_delete($type)
// return success of deletion or error message
if ($result) {
// retrieve the id of the parent element (the photo element)
$condition = ['uid' => local_user(), 'resource-id' => $photo_id, 'type' => 'photo'];
$photo_item = Item::selectFirstForUser(local_user(), ['id'], $condition);
if (!DBA::isResult($photo_item)) {
throw new InternalServerErrorException("problem with deleting items occured");
}
// function for setting the items to "deleted = 1" which ensures that comments, likes etc. are not shown anymore
// to the user and the contacts of the users (drop_items() do all the necessary magic to avoid orphans in database and federate deletion)
Item::deleteForUser(['id' => $photo_item['id']], api_user());
$condition = ['uid' => api_user(), 'resource-id' => $photo_id, 'type' => 'photo'];
Item::deleteForUser($condition, api_user());
$answer = ['result' => 'deleted', 'message' => 'photo with id `' . $photo_id . '` has been deleted from server.'];
return api_format_data("photo_delete", $type, ['$result' => $answer]);
$result = ['result' => 'deleted', 'message' => 'photo with id `' . $photo_id . '` has been deleted from server.'];
return api_format_data("photo_delete", $type, ['$result' => $result]);
} else {
throw new InternalServerErrorException("unknown error on deleting photo from database table");
}
@ -4734,13 +4626,8 @@ function save_media_to_database($mediatype, $media, $type, $album, $allow_cid, $
}
}
if ($filetype == "") {
$filetype = Images::guessType($filename);
}
$imagedata = @getimagesize($src);
if ($imagedata) {
$filetype = $imagedata['mime'];
}
$filetype = Images::getMimeTypeBySource($src, $filename, $filetype);
Logger::log(
"File upload src: " . $src . " - filename: " . $filename .
" - size: " . $filesize . " - type: " . $filetype,
@ -4839,7 +4726,7 @@ function save_media_to_database($mediatype, $media, $type, $album, $allow_cid, $
Logger::log("photo upload: new profile image upload ended", Logger::DEBUG);
}
if (isset($r) && $r) {
if (!empty($r)) {
// create entry in 'item'-table on new uploads to enable users to comment/like/dislike the photo
if ($photo_id == null && $mediatype == "photo") {
post_photo_item($resource_id, $allow_cid, $deny_cid, $allow_gid, $deny_gid, $filetype, $visibility);
@ -4986,8 +4873,8 @@ function prepare_photo_data($type, $scale, $photo_id)
}
// retrieve item element for getting activities (like, dislike etc.) related to photo
$condition = ['uid' => local_user(), 'resource-id' => $photo_id, 'type' => 'photo'];
$item = Item::selectFirstForUser(local_user(), ['id'], $condition);
$condition = ['uid' => api_user(), 'resource-id' => $photo_id, 'type' => 'photo'];
$item = Item::selectFirst(['id', 'uid', 'uri', 'parent', 'allow_cid', 'deny_cid', 'allow_gid', 'deny_gid'], $condition);
if (!DBA::isResult($item)) {
throw new NotFoundException('Photo-related item not found.');
}
@ -4996,7 +4883,7 @@ function prepare_photo_data($type, $scale, $photo_id)
// retrieve comments on photo
$condition = ["`parent` = ? AND `uid` = ? AND (`gravity` IN (?, ?) OR `type`='photo')",
$item[0]['parent'], api_user(), GRAVITY_PARENT, GRAVITY_COMMENT];
$item['parent'], api_user(), GRAVITY_PARENT, GRAVITY_COMMENT];
$statuses = Item::selectForUser(api_user(), [], $condition);
@ -5016,10 +4903,10 @@ function prepare_photo_data($type, $scale, $photo_id)
$data['photo']['friendica_comments'] = $comments;
// include info if rights on photo and rights on item are mismatching
$rights_mismatch = $data['photo']['allow_cid'] != $item[0]['allow_cid'] ||
$data['photo']['deny_cid'] != $item[0]['deny_cid'] ||
$data['photo']['allow_gid'] != $item[0]['allow_gid'] ||
$data['photo']['deny_cid'] != $item[0]['deny_cid'];
$rights_mismatch = $data['photo']['allow_cid'] != $item['allow_cid'] ||
$data['photo']['deny_cid'] != $item['deny_cid'] ||
$data['photo']['allow_gid'] != $item['allow_gid'] ||
$data['photo']['deny_gid'] != $item['deny_gid'];
$data['photo']['rights_mismatch'] = $rights_mismatch;
return $data;
@ -5113,8 +5000,7 @@ function api_get_announce($item)
}
$fields = ['author-id', 'author-name', 'author-link', 'author-avatar'];
$activity = Item::activityToIndex(Activity::ANNOUNCE);
$condition = ['parent-uri' => $item['uri'], 'gravity' => GRAVITY_ACTIVITY, 'uid' => [0, $item['uid']], 'activity' => $activity];
$condition = ['parent-uri' => $item['uri'], 'gravity' => GRAVITY_ACTIVITY, 'uid' => [0, $item['uid']], 'vid' => Verb::getID(Activity::ANNOUNCE)];
$announce = Item::selectFirstForUser($item['uid'], $fields, $condition, ['order' => ['received' => true]]);
if (!DBA::isResult($announce)) {
return [];
@ -5210,7 +5096,7 @@ function api_in_reply_to($item)
$in_reply_to['user_id_str'] = null;
$in_reply_to['screen_name'] = null;
if (($item['thr-parent'] != $item['uri']) && (intval($item['parent']) != intval($item['id']))) {
if (($item['thr-parent'] != $item['uri']) && ($item['gravity'] != GRAVITY_PARENT)) {
$parent = Item::selectFirst(['id'], ['uid' => $item['uid'], 'uri' => $item['thr-parent']]);
if (DBA::isResult($parent)) {
$in_reply_to['status_id'] = intval($parent['id']);

View file

@ -22,7 +22,6 @@
use Friendica\App;
use Friendica\Content\ContactSelector;
use Friendica\Content\Feature;
use Friendica\Content\Pager;
use Friendica\Content\Text\BBCode;
use Friendica\Core\Hook;
use Friendica\Core\Logger;
@ -34,7 +33,8 @@ use Friendica\DI;
use Friendica\Model\Contact;
use Friendica\Model\Item;
use Friendica\Model\Profile;
use Friendica\Model\Term;
use Friendica\Model\Tag;
use Friendica\Model\Verb;
use Friendica\Object\Post;
use Friendica\Object\Thread;
use Friendica\Protocol\Activity;
@ -144,222 +144,106 @@ function localize_item(&$item)
$item['body'] = item_redir_and_replace_images($extracted['body'], $extracted['images'], $item['contact-id']);
}
/*
heluecht 2018-06-19: from my point of view this whole code part is useless.
It just renders the body message of technical posts (Like, dislike, ...).
But: The body isn't visible at all. So we do this stuff just because we can.
Even if these messages were visible, this would only mean that something went wrong.
During the further steps of the database restructuring I would like to address this issue.
*/
$activity = DI::activity();
$xmlhead = "<" . "?xml version='1.0' encoding='UTF-8' ?" . ">";
if ($activity->match($item['verb'], Activity::LIKE)
|| $activity->match($item['verb'], Activity::DISLIKE)
|| $activity->match($item['verb'], Activity::ATTEND)
|| $activity->match($item['verb'], Activity::ATTENDNO)
|| $activity->match($item['verb'], Activity::ATTENDMAYBE)) {
$fields = ['author-link', 'author-name', 'verb', 'object-type', 'resource-id', 'body', 'plink'];
$obj = Item::selectFirst($fields, ['uri' => $item['parent-uri']]);
if (!DBA::isResult($obj)) {
return;
}
$author = '[url=' . $item['author-link'] . ']' . $item['author-name'] . '[/url]';
$objauthor = '[url=' . $obj['author-link'] . ']' . $obj['author-name'] . '[/url]';
switch ($obj['verb']) {
case Activity::POST:
switch ($obj['object-type']) {
case Activity\ObjectType::EVENT:
$post_type = DI::l10n()->t('event');
break;
default:
$post_type = DI::l10n()->t('status');
}
break;
default:
if ($obj['resource-id']) {
$post_type = DI::l10n()->t('photo');
$m = [];
preg_match("/\[url=([^]]*)\]/", $obj['body'], $m);
$rr['plink'] = $m[1];
} else {
$post_type = DI::l10n()->t('status');
}
}
$plink = '[url=' . $obj['plink'] . ']' . $post_type . '[/url]';
$bodyverb = '';
if ($activity->match($item['verb'], Activity::LIKE)) {
$bodyverb = DI::l10n()->t('%1$s likes %2$s\'s %3$s');
} elseif ($activity->match($item['verb'], Activity::DISLIKE)) {
$bodyverb = DI::l10n()->t('%1$s doesn\'t like %2$s\'s %3$s');
} elseif ($activity->match($item['verb'], Activity::ATTEND)) {
$bodyverb = DI::l10n()->t('%1$s attends %2$s\'s %3$s');
} elseif ($activity->match($item['verb'], Activity::ATTENDNO)) {
$bodyverb = DI::l10n()->t('%1$s doesn\'t attend %2$s\'s %3$s');
} elseif ($activity->match($item['verb'], Activity::ATTENDMAYBE)) {
$bodyverb = DI::l10n()->t('%1$s attends maybe %2$s\'s %3$s');
}
$item['body'] = sprintf($bodyverb, $author, $objauthor, $plink);
}
if ($activity->match($item['verb'], Activity::FRIEND)) {
if ($item['object-type']=="" || $item['object-type']!== Activity\ObjectType::PERSON) return;
$Aname = $item['author-name'];
$Alink = $item['author-link'];
$xmlhead="<"."?xml version='1.0' encoding='UTF-8' ?".">";
$obj = XML::parseString($xmlhead.$item['object']);
$links = XML::parseString($xmlhead."<links>".XML::unescape($obj->link)."</links>");
$Bname = $obj->title;
$Blink = "";
$Bphoto = "";
foreach ($links->link as $l) {
$atts = $l->attributes();
switch ($atts['rel']) {
case "alternate": $Blink = $atts['href']; break;
case "photo": $Bphoto = $atts['href']; break;
}
}
$A = '[url=' . Contact::magicLink($Alink) . ']' . $Aname . '[/url]';
$B = '[url=' . Contact::magicLink($Blink) . ']' . $Bname . '[/url]';
if ($Bphoto != "") {
$Bphoto = '[url=' . Contact::magicLink($Blink) . '][img]' . $Bphoto . '[/img][/url]';
}
$item['body'] = DI::l10n()->t('%1$s is now friends with %2$s', $A, $B)."\n\n\n".$Bphoto;
}
if (stristr($item['verb'], Activity::POKE)) {
$verb = urldecode(substr($item['verb'],strpos($item['verb'],'#')+1));
if (!$verb) {
return;
}
if ($item['object-type']=="" || $item['object-type']!== Activity\ObjectType::PERSON) {
return;
}
$Aname = $item['author-name'];
$Alink = $item['author-link'];
/// @todo The following functionality needs to be cleaned up.
if (!empty($item['verb'])) {
$activity = DI::activity();
$xmlhead = "<" . "?xml version='1.0' encoding='UTF-8' ?" . ">";
$obj = XML::parseString($xmlhead.$item['object']);
$Bname = $obj->title;
$Blink = $obj->id;
$Bphoto = "";
foreach ($obj->link as $l) {
$atts = $l->attributes();
switch ($atts['rel']) {
case "alternate": $Blink = $atts['href'];
case "photo": $Bphoto = $atts['href'];
if (stristr($item['verb'], Activity::POKE)) {
$verb = urldecode(substr($item['verb'], strpos($item['verb'],'#') + 1));
if (!$verb) {
return;
}
if ($item['object-type'] == "" || $item['object-type'] !== Activity\ObjectType::PERSON) {
return;
}
$Aname = $item['author-name'];
$Alink = $item['author-link'];
$obj = XML::parseString($xmlhead . $item['object']);
$Bname = $obj->title;
$Blink = $obj->id;
$Bphoto = "";
foreach ($obj->link as $l) {
$atts = $l->attributes();
switch ($atts['rel']) {
case "alternate": $Blink = $atts['href'];
case "photo": $Bphoto = $atts['href'];
}
}
$A = '[url=' . Contact::magicLink($Alink) . ']' . $Aname . '[/url]';
$B = '[url=' . Contact::magicLink($Blink) . ']' . $Bname . '[/url]';
if ($Bphoto != "") {
$Bphoto = '[url=' . Contact::magicLink($Blink) . '][img=80x80]' . $Bphoto . '[/img][/url]';
}
/*
* we can't have a translation string with three positions but no distinguishable text
* So here is the translate string.
*/
$txt = DI::l10n()->t('%1$s poked %2$s');
// now translate the verb
$poked_t = trim(sprintf($txt, '', ''));
$txt = str_replace($poked_t, DI::l10n()->t($verb), $txt);
// then do the sprintf on the translation string
$item['body'] = sprintf($txt, $A, $B) . "\n\n\n" . $Bphoto;
}
$A = '[url=' . Contact::magicLink($Alink) . ']' . $Aname . '[/url]';
$B = '[url=' . Contact::magicLink($Blink) . ']' . $Bname . '[/url]';
if ($Bphoto != "") {
$Bphoto = '[url=' . Contact::magicLink($Blink) . '][img=80x80]' . $Bphoto . '[/img][/url]';
}
if ($activity->match($item['verb'], Activity::TAG)) {
$fields = ['author-id', 'author-link', 'author-name', 'author-network',
'verb', 'object-type', 'resource-id', 'body', 'plink'];
$obj = Item::selectFirst($fields, ['uri' => $item['parent-uri']]);
if (!DBA::isResult($obj)) {
return;
}
/*
* we can't have a translation string with three positions but no distinguishable text
* So here is the translate string.
*/
$txt = DI::l10n()->t('%1$s poked %2$s');
$author_arr = ['uid' => 0, 'id' => $item['author-id'],
'network' => $item['author-network'], 'url' => $item['author-link']];
$author = '[url=' . Contact::magicLinkByContact($author_arr) . ']' . $item['author-name'] . '[/url]';
// now translate the verb
$poked_t = trim(sprintf($txt, "", ""));
$txt = str_replace($poked_t, DI::l10n()->t($verb), $txt);
$author_arr = ['uid' => 0, 'id' => $obj['author-id'],
'network' => $obj['author-network'], 'url' => $obj['author-link']];
$objauthor = '[url=' . Contact::magicLinkByContact($author_arr) . ']' . $obj['author-name'] . '[/url]';
// then do the sprintf on the translation string
$item['body'] = sprintf($txt, $A, $B). "\n\n\n" . $Bphoto;
}
if ($activity->match($item['verb'], Activity::TAG)) {
$fields = ['author-id', 'author-link', 'author-name', 'author-network',
'verb', 'object-type', 'resource-id', 'body', 'plink'];
$obj = Item::selectFirst($fields, ['uri' => $item['parent-uri']]);
if (!DBA::isResult($obj)) {
return;
}
$author_arr = ['uid' => 0, 'id' => $item['author-id'],
'network' => $item['author-network'], 'url' => $item['author-link']];
$author = '[url=' . Contact::magicLinkByContact($author_arr) . ']' . $item['author-name'] . '[/url]';
$author_arr = ['uid' => 0, 'id' => $obj['author-id'],
'network' => $obj['author-network'], 'url' => $obj['author-link']];
$objauthor = '[url=' . Contact::magicLinkByContact($author_arr) . ']' . $obj['author-name'] . '[/url]';
switch ($obj['verb']) {
case Activity::POST:
switch ($obj['object-type']) {
case Activity\ObjectType::EVENT:
$post_type = DI::l10n()->t('event');
break;
default:
switch ($obj['verb']) {
case Activity::POST:
switch ($obj['object-type']) {
case Activity\ObjectType::EVENT:
$post_type = DI::l10n()->t('event');
break;
default:
$post_type = DI::l10n()->t('status');
}
break;
default:
if ($obj['resource-id']) {
$post_type = DI::l10n()->t('photo');
$m=[]; preg_match("/\[url=([^]]*)\]/", $obj['body'], $m);
$rr['plink'] = $m[1];
} else {
$post_type = DI::l10n()->t('status');
}
break;
default:
if ($obj['resource-id']) {
$post_type = DI::l10n()->t('photo');
$m=[]; preg_match("/\[url=([^]]*)\]/", $obj['body'], $m);
$rr['plink'] = $m[1];
} else {
$post_type = DI::l10n()->t('status');
}
// Let's break everthing ... ;-)
break;
}
$plink = '[url=' . $obj['plink'] . ']' . $post_type . '[/url]';
$parsedobj = XML::parseString($xmlhead.$item['object']);
$tag = sprintf('#[url=%s]%s[/url]', $parsedobj->id, $parsedobj->content);
$item['body'] = DI::l10n()->t('%1$s tagged %2$s\'s %3$s with %4$s', $author, $objauthor, $plink, $tag);
}
if ($activity->match($item['verb'], Activity::FAVORITE)) {
if ($item['object-type'] == "") {
return;
}
$Aname = $item['author-name'];
$Alink = $item['author-link'];
$xmlhead = "<" . "?xml version='1.0' encoding='UTF-8' ?" . ">";
$obj = XML::parseString($xmlhead.$item['object']);
if (strlen($obj->id)) {
$fields = ['author-link', 'author-name', 'plink'];
$target = Item::selectFirst($fields, ['uri' => $obj->id, 'uid' => $item['uid']]);
if (DBA::isResult($target) && $target['plink']) {
$Bname = $target['author-name'];
$Blink = $target['author-link'];
$A = '[url=' . Contact::magicLink($Alink) . ']' . $Aname . '[/url]';
$B = '[url=' . Contact::magicLink($Blink) . ']' . $Bname . '[/url]';
$P = '[url=' . $target['plink'] . ']' . DI::l10n()->t('post/item') . '[/url]';
$item['body'] = DI::l10n()->t('%1$s marked %2$s\'s %3$s as favorite', $A, $B, $P)."\n";
}
// Let's break everthing ... ;-)
break;
}
$plink = '[url=' . $obj['plink'] . ']' . $post_type . '[/url]';
$parsedobj = XML::parseString($xmlhead . $item['object']);
$tag = sprintf('#[url=%s]%s[/url]', $parsedobj->id, $parsedobj->content);
$item['body'] = DI::l10n()->t('%1$s tagged %2$s\'s %3$s with %4$s', $author, $objauthor, $plink, $tag);
}
}
$matches = null;
if (preg_match_all('/@\[url=(.*?)\]/is', $item['body'], $matches, PREG_SET_ORDER)) {
foreach ($matches as $mtch) {
@ -493,17 +377,17 @@ function conversation(App $a, array $items, $mode, $update, $preview = false, $o
. "<script> var profile_uid = " . $_SESSION['uid']
. "; var netargs = '" . substr(DI::args()->getCommand(), 8)
. '?f='
. (!empty($_GET['cid']) ? '&cid=' . rawurlencode($_GET['cid']) : '')
. (!empty($_GET['search']) ? '&search=' . rawurlencode($_GET['search']) : '')
. (!empty($_GET['star']) ? '&star=' . rawurlencode($_GET['star']) : '')
. (!empty($_GET['order']) ? '&order=' . rawurlencode($_GET['order']) : '')
. (!empty($_GET['bmark']) ? '&bmark=' . rawurlencode($_GET['bmark']) : '')
. (!empty($_GET['liked']) ? '&liked=' . rawurlencode($_GET['liked']) : '')
. (!empty($_GET['conv']) ? '&conv=' . rawurlencode($_GET['conv']) : '')
. (!empty($_GET['nets']) ? '&nets=' . rawurlencode($_GET['nets']) : '')
. (!empty($_GET['cmin']) ? '&cmin=' . rawurlencode($_GET['cmin']) : '')
. (!empty($_GET['cmax']) ? '&cmax=' . rawurlencode($_GET['cmax']) : '')
. (!empty($_GET['file']) ? '&file=' . rawurlencode($_GET['file']) : '')
. (!empty($_GET['contactid']) ? '&contactid=' . rawurlencode($_GET['contactid']) : '')
. (!empty($_GET['search']) ? '&search=' . rawurlencode($_GET['search']) : '')
. (!empty($_GET['star']) ? '&star=' . rawurlencode($_GET['star']) : '')
. (!empty($_GET['order']) ? '&order=' . rawurlencode($_GET['order']) : '')
. (!empty($_GET['bmark']) ? '&bmark=' . rawurlencode($_GET['bmark']) : '')
. (!empty($_GET['liked']) ? '&liked=' . rawurlencode($_GET['liked']) : '')
. (!empty($_GET['conv']) ? '&conv=' . rawurlencode($_GET['conv']) : '')
. (!empty($_GET['nets']) ? '&nets=' . rawurlencode($_GET['nets']) : '')
. (!empty($_GET['cmin']) ? '&cmin=' . rawurlencode($_GET['cmin']) : '')
. (!empty($_GET['cmax']) ? '&cmax=' . rawurlencode($_GET['cmax']) : '')
. (!empty($_GET['file']) ? '&file=' . rawurlencode($_GET['file']) : '')
. "'; </script>\r\n";
}
@ -643,7 +527,7 @@ function conversation(App $a, array $items, $mode, $update, $preview = false, $o
$profile_name = $item['author-link'];
}
$tags = Term::populateTagsFromItem($item);
$tags = Tag::populateFromItem($item);
$author = ['uid' => 0, 'id' => $item['author-id'],
'network' => $item['author-network'], 'url' => $item['author-link']];
@ -787,7 +671,7 @@ function conversation(App $a, array $items, $mode, $update, $preview = false, $o
$item['pagedrop'] = $page_dropping;
if ($item['id'] == $item['parent']) {
if ($item['gravity'] == GRAVITY_PARENT) {
$item_object = new Post($item);
$conv->addParent($item_object);
}
@ -876,7 +760,11 @@ function conversation_fetch_comments($thread_items, $pinned) {
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
function conversation_add_children(array $parents, $block_authors, $order, $uid) {
$max_comments = DI::config()->get('system', 'max_comments', 100);
if (count($parents) > 1) {
$max_comments = DI::config()->get('system', 'max_comments', 100);
} else {
$max_comments = DI::config()->get('system', 'max_display_comments', 1000);
}
$params = ['order' => ['uid', 'commented' => true]];
@ -887,19 +775,9 @@ function conversation_add_children(array $parents, $block_authors, $order, $uid)
$items = [];
foreach ($parents AS $parent) {
$condition = ["`item`.`parent-uri` = ? AND `item`.`uid` IN (0, ?) ",
$parent['uri'], $uid];
if ($block_authors) {
$condition[0] .= "AND NOT `author`.`hidden`";
}
$thread_items = Item::selectForUser(local_user(), array_merge(Item::DISPLAY_FIELDLIST, ['contact-uid', 'gravity']), $condition, $params);
$comments = conversation_fetch_comments($thread_items, $parent['pinned'] ?? false);
if (count($comments) != 0) {
$items = array_merge($items, $comments);
}
$condition = ["`item`.`parent-uri` = ? AND `item`.`uid` IN (0, ?) AND (`vid` != ? OR `vid` IS NULL)",
$parent['uri'], $uid, Verb::getID(Activity::FOLLOW)];
$items = conversation_fetch_items($parent, $items, $condition, $block_authors, $params);
}
foreach ($items as $index => $item) {
@ -913,6 +791,31 @@ function conversation_add_children(array $parents, $block_authors, $order, $uid)
return $items;
}
/**
* Fetch conversation items
*
* @param array $parent
* @param array $items
* @param array $condition
* @param boolean $block_authors
* @param array $params
* @return array
*/
function conversation_fetch_items(array $parent, array $items, array $condition, bool $block_authors, array $params) {
if ($block_authors) {
$condition[0] .= " AND NOT `author`.`hidden`";
}
$thread_items = Item::selectForUser(local_user(), array_merge(Item::DISPLAY_FIELDLIST, ['contact-uid', 'gravity']), $condition, $params);
$comments = conversation_fetch_comments($thread_items, $parent['pinned'] ?? false);
if (count($comments) != 0) {
$items = array_merge($items, $comments);
}
return $items;
}
function item_photo_menu($item) {
$sub_link = '';
$poke_link = '';
@ -924,7 +827,7 @@ function item_photo_menu($item) {
$block_link = '';
$ignore_link = '';
if (local_user() && local_user() == $item['uid'] && $item['parent'] == $item['id'] && !$item['self']) {
if (local_user() && local_user() == $item['uid'] && $item['gravity'] == GRAVITY_PARENT && !$item['self']) {
$sub_link = 'javascript:dosubthread(' . $item['id'] . '); return false;';
}
@ -953,15 +856,15 @@ function item_photo_menu($item) {
if (!empty($pcid)) {
$contact_url = 'contact/' . $pcid;
$posts_link = 'contact/' . $pcid . '/posts';
$block_link = 'contact/' . $pcid . '/block';
$ignore_link = 'contact/' . $pcid . '/ignore';
$posts_link = $contact_url . '/posts';
$block_link = $contact_url . '/block';
$ignore_link = $contact_url . '/ignore';
}
if ($cid && !$item['self']) {
$poke_link = 'poke?c=' . $cid;
$contact_url = 'contact/' . $cid;
$posts_link = 'contact/' . $cid . '/posts';
$poke_link = $contact_url . '/poke';
$posts_link = $contact_url . '/posts';
if (in_array($network, [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA])) {
$pm_url = 'message/new/' . $cid;
@ -1049,7 +952,7 @@ function builtin_activity_puller($item, &$conv_responses) {
return;
}
if (!empty($item['verb']) && DI::activity()->match($item['verb'], $verb) && ($item['id'] != $item['parent'])) {
if (!empty($item['verb']) && DI::activity()->match($item['verb'], $verb) && ($item['gravity'] != GRAVITY_PARENT)) {
$author = ['uid' => 0, 'id' => $item['author-id'],
'network' => $item['author-network'], 'url' => $item['author-link']];
$url = Contact::magicLinkByContact($author);
@ -1295,6 +1198,8 @@ function status_editor(App $a, $x, $notes_cid = 0, $popup = false)
//jot nav tab (used in some themes)
'$message' => DI::l10n()->t('Message'),
'$browser' => DI::l10n()->t('Browser'),
'$compose_link_title' => DI::l10n()->t('Open Compose page'),
]);
@ -1317,7 +1222,7 @@ function get_item_children(array &$item_list, array $parent, $recursive = true)
{
$children = [];
foreach ($item_list as $i => $item) {
if ($item['id'] != $item['parent']) {
if ($item['gravity'] != GRAVITY_PARENT) {
if ($recursive) {
// Fallback to parent-uri if thr-parent is not set
$thr_parent = $item['thr-parent'];
@ -1465,7 +1370,7 @@ function conv_sort(array $item_list, $order)
// Extract the top level items
foreach ($item_array as $item) {
if ($item['id'] == $item['parent']) {
if ($item['gravity'] == GRAVITY_PARENT) {
$parents[] = $item;
}
}

View file

@ -107,12 +107,24 @@ function notification($params)
$item_id = 0;
}
if (isset($params['item']['uri-id'])) {
$uri_id = $params['item']['uri-id'];
} else {
$uri_id = 0;
}
if (isset($params['parent'])) {
$parent_id = $params['parent'];
} else {
$parent_id = 0;
}
if (isset($params['item']['parent-uri-id'])) {
$parent_uri_id = $params['item']['parent-uri-id'];
} else {
$parent_uri_id = 0;
}
$epreamble = '';
$preamble = '';
$subject = '';
@ -452,19 +464,26 @@ function notification($params)
if ($show_in_notification_page) {
$notification = DI::notify()->insert([
'name' => $params['source_name'] ?? '',
'name_cache' => substr(strip_tags(BBCode::convert($params['source_name'] ?? '')), 0, 255),
'url' => $params['source_link'] ?? '',
'photo' => $params['source_photo'] ?? '',
'link' => $itemlink ?? '',
'uid' => $params['uid'] ?? 0,
'iid' => $item_id ?? 0,
'parent' => $parent_id ?? 0,
'type' => $params['type'] ?? '',
'verb' => $params['verb'] ?? '',
'otype' => $params['otype'] ?? '',
'name' => $params['source_name'] ?? '',
'name_cache' => substr(strip_tags(BBCode::convert($params['source_name'])), 0, 255),
'url' => $params['source_link'] ?? '',
'photo' => $params['source_photo'] ?? '',
'link' => $itemlink ?? '',
'uid' => $params['uid'] ?? 0,
'iid' => $item_id,
'uri-id' => $uri_id,
'parent' => $parent_id,
'parent-uri-id' => $parent_uri_id,
'type' => $params['type'] ?? '',
'verb' => $params['verb'] ?? '',
'otype' => $params['otype'] ?? '',
]);
// Notification insertion can be intercepted by an addon registering the 'enotify_store' hook
if (!$notification) {
return false;
}
$notification->msg = Renderer::replaceMacros($epreamble, ['$itemlink' => $notification->link]);
DI::notify()->update($notification);
@ -486,8 +505,9 @@ function notification($params)
if (!DBA::exists('notify-threads', ['master-parent-item' => $params['parent'], 'receiver-uid' => $params['uid']])) {
Logger::log("notify_id:" . intval($notify_id) . ", parent: " . intval($params['parent']) . "uid: " . intval($params['uid']), Logger::DEBUG);
$fields = ['notify-id' => $notify_id, 'master-parent-item' => $params['parent'],
'receiver-uid' => $params['uid'], 'parent-item' => 0];
$fields = ['notify-id' => $notify_id, 'master-parent-item' => $params['parent'],
'master-parent-uri-id' => $parent_uri_id,
'receiver-uid' => $params['uid'], 'parent-item' => 0];
DBA::insert('notify-threads', $fields);
$additional_mail_header .= "Message-ID: <${id_for_parent}>\n";
@ -574,7 +594,7 @@ function check_user_notification($itemid) {
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
function check_item_notification($itemid, $uid, $notification_type) {
$fields = ['id', 'mention', 'tag', 'parent', 'title', 'body',
$fields = ['id', 'uri-id', 'mention', 'parent', 'parent-uri-id', 'title', 'body',
'author-link', 'author-name', 'author-avatar', 'author-id',
'guid', 'parent-uri', 'uri', 'contact-id', 'network'];
$condition = ['id' => $itemid, 'gravity' => [GRAVITY_PARENT, GRAVITY_COMMENT], 'deleted' => false];

View file

@ -19,433 +19,56 @@
*
*/
use Friendica\Core\Hook;
use Friendica\Core\Logger;
use Friendica\Core\Protocol;
use Friendica\Core\Renderer;
use Friendica\Core\Session;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Item;
use Friendica\Protocol\DFRN;
use Friendica\Protocol\Feed;
use Friendica\Protocol\OStatus;
use Friendica\Util\Network;
use Friendica\Util\ParseUrl;
use Friendica\Util\Strings;
require_once __DIR__ . '/../mod/share.php';
/**
* @deprecated since 2020.06
* @see \Friendica\Content\PageInfo::getFooterFromData
*/
function add_page_info_data(array $data, $no_photos = false)
{
Hook::callAll('page_info_data', $data);
if (empty($data['type'])) {
return '';
}
// It maybe is a rich content, but if it does have everything that a link has,
// then treat it that way
if (($data["type"] == "rich") && is_string($data["title"]) &&
is_string($data["text"]) && !empty($data["images"])) {
$data["type"] = "link";
}
$data["title"] = $data["title"] ?? '';
if ((($data["type"] != "link") && ($data["type"] != "video") && ($data["type"] != "photo")) || ($data["title"] == $data["url"])) {
return "";
}
if ($no_photos && ($data["type"] == "photo")) {
return "";
}
// Escape some bad characters
$data["url"] = str_replace(["[", "]"], ["&#91;", "&#93;"], htmlentities($data["url"], ENT_QUOTES, 'UTF-8', false));
$data["title"] = str_replace(["[", "]"], ["&#91;", "&#93;"], htmlentities($data["title"], ENT_QUOTES, 'UTF-8', false));
$text = "[attachment type='".$data["type"]."'";
if (empty($data["text"])) {
$data["text"] = $data["title"];
}
if (empty($data["text"])) {
$data["text"] = $data["url"];
}
if (!empty($data["url"])) {
$text .= " url='".$data["url"]."'";
}
if (!empty($data["title"])) {
$text .= " title='".$data["title"]."'";
}
// Only embedd a picture link when it seems to be a valid picture ("width" is set)
if (!empty($data["images"]) && !empty($data["images"][0]["width"])) {
$preview = str_replace(["[", "]"], ["&#91;", "&#93;"], htmlentities($data["images"][0]["src"], ENT_QUOTES, 'UTF-8', false));
// if the preview picture is larger than 500 pixels then show it in a larger mode
// But only, if the picture isn't higher than large (To prevent huge posts)
if (!DI::config()->get('system', 'always_show_preview') && ($data["images"][0]["width"] >= 500)
&& ($data["images"][0]["width"] >= $data["images"][0]["height"])) {
$text .= " image='".$preview."'";
} else {
$text .= " preview='".$preview."'";
}
}
$text .= "]".$data["text"]."[/attachment]";
$hashtags = "";
if (isset($data["keywords"]) && count($data["keywords"])) {
$hashtags = "\n";
foreach ($data["keywords"] as $keyword) {
/// @TODO make a positive list of allowed characters
$hashtag = str_replace([' ', '+', '/', '.', '#', '@', "'", '"', '', '`', '(', ')', '„', '“'], '', $keyword);
$hashtags .= "#[url=" . DI::baseUrl() . "/search?tag=" . $hashtag . "]" . $hashtag . "[/url] ";
}
}
return "\n".$text.$hashtags;
}
function query_page_info($url, $photo = "", $keywords = false, $keyword_blacklist = "")
{
$data = ParseUrl::getSiteinfoCached($url, true);
if ($photo != "") {
$data["images"][0]["src"] = $photo;
}
Logger::log('fetch page info for ' . $url . ' ' . print_r($data, true), Logger::DEBUG);
if (!$keywords && isset($data["keywords"])) {
unset($data["keywords"]);
}
if (($keyword_blacklist != "") && isset($data["keywords"])) {
$list = explode(", ", $keyword_blacklist);
foreach ($list as $keyword) {
$keyword = trim($keyword);
$index = array_search($keyword, $data["keywords"]);
if ($index !== false) {
unset($data["keywords"][$index]);
}
}
}
return $data;
}
function add_page_keywords($url, $photo = "", $keywords = false, $keyword_blacklist = "")
{
$data = query_page_info($url, $photo, $keywords, $keyword_blacklist);
$tags = "";
if (isset($data["keywords"]) && count($data["keywords"])) {
foreach ($data["keywords"] as $keyword) {
$hashtag = str_replace([" ", "+", "/", ".", "#", "'"],
["", "", "", "", "", ""], $keyword);
if ($tags != "") {
$tags .= ", ";
}
$tags .= "#[url=" . DI::baseUrl() . "/search?tag=" . $hashtag . "]" . $hashtag . "[/url]";
}
}
return $tags;
}
function add_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "")
{
$data = query_page_info($url, $photo, $keywords, $keyword_blacklist);
$text = '';
if (is_array($data)) {
$text = add_page_info_data($data, $no_photos);
}
return $text;
}
function add_page_info_to_body($body, $texturl = false, $no_photos = false)
{
Logger::log('add_page_info_to_body: fetch page info for body ' . $body, Logger::DEBUG);
$URLSearchString = "^\[\]";
// Fix for Mastodon where the mentions are in a different format
$body = preg_replace("/\[url\=([$URLSearchString]*)\]([#!@])(.*?)\[\/url\]/ism",
'$2[url=$1]$3[/url]', $body);
// Adding these spaces is a quick hack due to my problems with regular expressions :)
preg_match("/[^!#@]\[url\]([$URLSearchString]*)\[\/url\]/ism", " " . $body, $matches);
if (!$matches) {
preg_match("/[^!#@]\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", " " . $body, $matches);
}
// Convert urls without bbcode elements
if (!$matches && $texturl) {
preg_match("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", " ".$body, $matches);
// Yeah, a hack. I really hate regular expressions :)
if ($matches) {
$matches[1] = $matches[2];
}
}
if ($matches) {
$footer = add_page_info($matches[1], $no_photos);
}
// Remove the link from the body if the link is attached at the end of the post
if (isset($footer) && (trim($footer) != "") && (strpos($footer, $matches[1]))) {
$removedlink = trim(str_replace($matches[1], "", $body));
if (($removedlink == "") || strstr($body, $removedlink)) {
$body = $removedlink;
}
$removedlink = preg_replace("/\[url\=" . preg_quote($matches[1], '/') . "\](.*?)\[\/url\]/ism", '', $body);
if (($removedlink == "") || strstr($body, $removedlink)) {
$body = $removedlink;
}
}
// Add the page information to the bottom
if (isset($footer) && (trim($footer) != "")) {
$body .= $footer;
}
return $body;
return "\n" . \Friendica\Content\PageInfo::getFooterFromData($data, $no_photos);
}
/**
*
* consume_feed - process atom feed and update anything/everything we might need to update
*
* $xml = the (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds.
*
* $importer = the contact_record (joined to user_record) of the local user who owns this relationship.
* It is this person's stuff that is going to be updated.
* $contact = the person who is sending us stuff. If not set, we MAY be processing a "follow" activity
* from an external network and MAY create an appropriate contact record. Otherwise, we MUST
* have a contact record.
* $hub = should we find a hub declation in the feed, pass it back to our calling process, who might (or
* might not) try and subscribe to it.
* $datedir sorts in reverse order
* $pass - by default ($pass = 0) we cannot guarantee that a parent item has been
* imported prior to its children being seen in the stream unless we are certain
* of how the feed is arranged/ordered.
* With $pass = 1, we only pull parent items out of the stream.
* With $pass = 2, we only pull children (comments/likes).
*
* So running this twice, first with pass 1 and then with pass 2 will do the right
* thing regardless of feed ordering. This won't be adequate in a fully-threaded
* model where comments can have sub-threads. That would require some massive sorting
* to get all the feed items into a mostly linear ordering, and might still require
* recursion.
*
* @param $xml
* @param array $importer
* @param array $contact
* @param $hub
* @throws ImagickException
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @deprecated since 2020.06
* @see \Friendica\Content\PageInfo::queryUrl
*/
function query_page_info($url, $photo = "", $keywords = false, $keyword_denylist = "")
{
return \Friendica\Content\PageInfo::queryUrl($url, $photo, $keywords, $keyword_denylist);
}
/**
* @deprecated since 2020.06
* @see \Friendica\Content\PageInfo::getTagsFromUrl()
*/
function get_page_keywords($url, $photo = "", $keywords = false, $keyword_denylist = "")
{
return $keywords ? \Friendica\Content\PageInfo::getTagsFromUrl($url, $photo, $keyword_denylist) : [];
}
/**
* @deprecated since 2020.06
* @see \Friendica\Content\PageInfo::getFooterFromUrl
*/
function add_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_denylist = "")
{
return "\n" . \Friendica\Content\PageInfo::getFooterFromUrl($url, $no_photos, $photo, $keywords, $keyword_denylist);
}
/**
* @deprecated since 2020.06
* @see \Friendica\Content\PageInfo::appendToBody
*/
function add_page_info_to_body($body, $texturl = false, $no_photos = false)
{
return \Friendica\Content\PageInfo::appendToBody($body, $texturl, $no_photos);
}
/**
* @deprecated since 2020.06
* @see \Friendica\Protocol\Feed::consume
*/
function consume_feed($xml, array $importer, array $contact, &$hub)
{
if ($contact['network'] === Protocol::OSTATUS) {
Logger::log("Consume OStatus messages ", Logger::DEBUG);
OStatus::import($xml, $importer, $contact, $hub);
return;
}
if ($contact['network'] === Protocol::FEED) {
Logger::log("Consume feeds", Logger::DEBUG);
Feed::import($xml, $importer, $contact);
return;
}
if ($contact['network'] === Protocol::DFRN) {
Logger::log("Consume DFRN messages", Logger::DEBUG);
$dfrn_importer = DFRN::getImporter($contact["id"], $importer["uid"]);
if (!empty($dfrn_importer)) {
Logger::log("Now import the DFRN feed");
DFRN::import($xml, $dfrn_importer, true);
return;
}
}
}
function subscribe_to_hub($url, array $importer, array $contact, $hubmode = 'subscribe')
{
/*
* Diaspora has different message-ids in feeds than they do
* through the direct Diaspora protocol. If we try and use
* the feed, we'll get duplicates. So don't.
*/
if ($contact['network'] === Protocol::DIASPORA) {
return;
}
// Without an importer we don't have a user id - so we quit
if (empty($importer)) {
return;
}
$user = DBA::selectFirst('user', ['nickname'], ['uid' => $importer['uid']]);
// No user, no nickname, we quit
if (!DBA::isResult($user)) {
return;
}
$push_url = DI::baseUrl() . '/pubsub/' . $user['nickname'] . '/' . $contact['id'];
// Use a single verify token, even if multiple hubs
$verify_token = ((strlen($contact['hub-verify'])) ? $contact['hub-verify'] : Strings::getRandomHex());
$params= 'hub.mode=' . $hubmode . '&hub.callback=' . urlencode($push_url) . '&hub.topic=' . urlencode($contact['poll']) . '&hub.verify=async&hub.verify_token=' . $verify_token;
Logger::log('subscribe_to_hub: ' . $hubmode . ' ' . $contact['name'] . ' to hub ' . $url . ' endpoint: ' . $push_url . ' with verifier ' . $verify_token);
if (!strlen($contact['hub-verify']) || ($contact['hub-verify'] != $verify_token)) {
DBA::update('contact', ['hub-verify' => $verify_token], ['id' => $contact['id']]);
}
$postResult = Network::post($url, $params);
Logger::log('subscribe_to_hub: returns: ' . $postResult->getReturnCode(), Logger::DEBUG);
return;
}
function drop_items(array $items)
{
$uid = 0;
if (!Session::isAuthenticated()) {
return;
}
if (!empty($items)) {
foreach ($items as $item) {
$owner = Item::deleteForUser(['id' => $item], local_user());
if ($owner && !$uid) {
$uid = $owner;
}
}
}
}
function drop_item($id, $return = '')
{
$a = DI::app();
// locate item to be deleted
$fields = ['id', 'uid', 'guid', 'contact-id', 'deleted', 'gravity', 'parent'];
$item = Item::selectFirstForUser(local_user(), $fields, ['id' => $id]);
if (!DBA::isResult($item)) {
notice(DI::l10n()->t('Item not found.') . EOL);
DI::baseUrl()->redirect('network');
}
if ($item['deleted']) {
return 0;
}
$contact_id = 0;
// check if logged in user is either the author or owner of this item
if (Session::getRemoteContactID($item['uid']) == $item['contact-id']) {
$contact_id = $item['contact-id'];
}
if ((local_user() == $item['uid']) || $contact_id) {
// Check if we should do HTML-based delete confirmation
if (!empty($_REQUEST['confirm'])) {
// <form> can't take arguments in its "action" parameter
// so add any arguments as hidden inputs
$query = explode_querystring(DI::args()->getQueryString());
$inputs = [];
foreach ($query['args'] as $arg) {
if (strpos($arg, 'confirm=') === false) {
$arg_parts = explode('=', $arg);
$inputs[] = ['name' => $arg_parts[0], 'value' => $arg_parts[1]];
}
}
return Renderer::replaceMacros(Renderer::getMarkupTemplate('confirm.tpl'), [
'$method' => 'get',
'$message' => DI::l10n()->t('Do you really want to delete this item?'),
'$extra_inputs' => $inputs,
'$confirm' => DI::l10n()->t('Yes'),
'$confirm_url' => $query['base'],
'$confirm_name' => 'confirmed',
'$cancel' => DI::l10n()->t('Cancel'),
]);
}
// Now check how the user responded to the confirmation query
if (!empty($_REQUEST['canceled'])) {
DI::baseUrl()->redirect('display/' . $item['guid']);
}
$is_comment = ($item['gravity'] == GRAVITY_COMMENT) ? true : false;
$parentitem = null;
if (!empty($item['parent'])){
$fields = ['guid'];
$parentitem = Item::selectFirstForUser(local_user(), $fields, ['id' => $item['parent']]);
}
// delete the item
Item::deleteForUser(['id' => $item['id']], local_user());
$return_url = hex2bin($return);
// removes update_* from return_url to ignore Ajax refresh
$return_url = str_replace("update_", "", $return_url);
// Check if delete a comment
if ($is_comment) {
// Return to parent guid
if (!empty($parentitem)) {
DI::baseUrl()->redirect('display/' . $parentitem['guid']);
//NOTREACHED
}
// In case something goes wrong
else {
DI::baseUrl()->redirect('network');
//NOTREACHED
}
}
else {
// if unknown location or deleting top level post called from display
if (empty($return_url) || strpos($return_url, 'display') !== false) {
DI::baseUrl()->redirect('network');
//NOTREACHED
} else {
DI::baseUrl()->redirect($return_url);
//NOTREACHED
}
}
} else {
notice(DI::l10n()->t('Permission denied.') . EOL);
DI::baseUrl()->redirect('display/' . $item['guid']);
//NOTREACHED
}
\Friendica\Protocol\Feed::consume($xml, $importer, $contact, $hub);
}

View file

@ -96,7 +96,7 @@ abstract class OAuthSignatureMethod
* @param OAuthToken $token
* @return string
*/
abstract public function build_signature(OAuthRequest $request, OAuthConsumer $consumer, OAuthToken $token);
abstract public function build_signature(OAuthRequest $request, OAuthConsumer $consumer, OAuthToken $token = null);
/**
* Verifies that a given signature is correct
@ -107,7 +107,7 @@ abstract class OAuthSignatureMethod
* @param string $signature
* @return bool
*/
public function check_signature($request, $consumer, $token, $signature)
public function check_signature(OAuthRequest $request, OAuthConsumer $consumer, $signature, OAuthToken $token = null)
{
$built = $this->build_signature($request, $consumer, $token);
return ($built == $signature);
@ -134,7 +134,7 @@ class OAuthSignatureMethod_HMAC_SHA1 extends OAuthSignatureMethod
* @param OAuthToken $token
* @return string
*/
public function build_signature(OAuthRequest $request, OAuthConsumer $consumer, OAuthToken $token)
public function build_signature(OAuthRequest $request, OAuthConsumer $consumer, OAuthToken $token = null)
{
$base_string = $request->get_signature_base_string();
$request->base_string = $base_string;
@ -179,7 +179,7 @@ class OAuthSignatureMethod_PLAINTEXT extends OAuthSignatureMethod
* @param $token
* @return string
*/
public function build_signature(OAuthRequest $request, OAuthConsumer $consumer, OAuthToken $token)
public function build_signature(OAuthRequest $request, OAuthConsumer $consumer, OAuthToken $token = null)
{
$key_parts = array(
$consumer->secret,
@ -223,7 +223,7 @@ abstract class OAuthSignatureMethod_RSA_SHA1 extends OAuthSignatureMethod
// Either way should return a string representation of the certificate
protected abstract function fetch_private_cert(&$request);
public function build_signature(OAuthRequest $request, OAuthConsumer $consumer, OAuthToken $token)
public function build_signature(OAuthRequest $request, OAuthConsumer $consumer, OAuthToken $token = null)
{
$base_string = $request->get_signature_base_string();
$request->base_string = $base_string;
@ -243,7 +243,7 @@ abstract class OAuthSignatureMethod_RSA_SHA1 extends OAuthSignatureMethod
return base64_encode($signature);
}
public function check_signature($request, $consumer, $token, $signature)
public function check_signature(OAuthRequest $request, OAuthConsumer $consumer, $signature, OAuthToken $token = null)
{
$decoded_sig = base64_decode($signature);
@ -358,7 +358,7 @@ class OAuthRequest
* @param array|null $parameters
* @return OAuthRequest
*/
public static function from_consumer_and_token(OAuthConsumer $consumer, OAuthToken $token, $http_method, $http_url, array $parameters = NULL)
public static function from_consumer_and_token(OAuthConsumer $consumer, $http_method, $http_url, array $parameters = null, OAuthToken $token = null)
{
@$parameters or $parameters = array();
$defaults = array(
@ -788,11 +788,10 @@ class OAuthServer
$valid_sig = $signature_method->check_signature(
$request,
$consumer,
$token,
$signature
$signature,
$token
);
if (!$valid_sig) {
throw new OAuthException("Invalid signature");
}

View file

@ -37,17 +37,18 @@ use Friendica\Model\Event;
use Friendica\Model\Item;
use Friendica\Model\Profile;
use Friendica\Module\BaseProfile;
use Friendica\Network\HTTPException;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Temporal;
function cal_init(App $a)
{
if (DI::config()->get('system', 'block_public') && !Session::isAuthenticated()) {
throw new \Friendica\Network\HTTPException\ForbiddenException(DI::l10n()->t('Access denied.'));
throw new HTTPException\ForbiddenException(DI::l10n()->t('Access denied.'));
}
if ($a->argc < 2) {
throw new \Friendica\Network\HTTPException\ForbiddenException(DI::l10n()->t('Access denied.'));
throw new HTTPException\ForbiddenException(DI::l10n()->t('Access denied.'));
}
Nav::setSelected('events');
@ -55,7 +56,7 @@ function cal_init(App $a)
$nick = $a->argv[1];
$user = DBA::selectFirst('user', [], ['nickname' => $nick, 'blocked' => false]);
if (!DBA::isResult($user)) {
throw new \Friendica\Network\HTTPException\NotFoundException();
throw new HTTPException\NotFoundException();
}
$a->data['user'] = $user;
@ -67,18 +68,22 @@ function cal_init(App $a)
return;
}
$profile = Profile::getByNickname($nick, $a->profile_uid);
$a->profile = Profile::getByNickname($nick, $a->profile_uid);
$account_type = Contact::getAccountType($profile);
if (empty($a->profile)) {
throw new HTTPException\NotFoundException(DI::l10n()->t('User not found.'));
}
$account_type = Contact::getAccountType($a->profile);
$tpl = Renderer::getMarkupTemplate('widget/vcard.tpl');
$vcard_widget = Renderer::replaceMacros($tpl, [
'$name' => $profile['name'],
'$photo' => $profile['photo'],
'$addr' => $profile['addr'] ?: '',
'$name' => $a->profile['name'],
'$photo' => $a->profile['photo'],
'$addr' => $a->profile['addr'] ?: '',
'$account_type' => $account_type,
'$about' => BBCode::convert($profile['about'] ?: ''),
'$about' => BBCode::convert($a->profile['about']),
]);
$cal_widget = Widget\CalendarExport::getHTML();

View file

@ -27,9 +27,9 @@
* 2. We may be the target or other side of the conversation to scenario 1, and will
* interact with that process on our own user's behalf.
*
* @see PDF with dfrn specs: https://github.com/friendica/friendica/blob/master/spec/dfrn2.pdf
* @see PDF with dfrn specs: https://github.com/friendica/friendica/blob/stable/spec/dfrn2.pdf
* You also find a graphic which describes the confirmation process at
* https://github.com/friendica/friendica/blob/master/spec/dfrn2_contact_confirmation.png
* https://github.com/friendica/friendica/blob/stable/spec/dfrn2_contact_confirmation.png
*/
use Friendica\App;
@ -214,7 +214,7 @@ function dfrn_confirm_post(App $a, $handsfree = null)
$params['page'] = 2;
}
Logger::log('Confirm: posting data to ' . $dfrn_confirm . ': ' . print_r($params, true), Logger::DATA);
Logger::debug('Confirm: posting data', ['confirm' => $dfrn_confirm, 'parameter' => $params]);
/*
*
@ -372,9 +372,9 @@ function dfrn_confirm_post(App $a, $handsfree = null)
$forum = (($page == 1) ? 1 : 0);
$prv = (($page == 2) ? 1 : 0);
Logger::log('dfrn_confirm: requestee contacted: ' . $node);
Logger::notice('requestee contacted', ['node' => $node]);
Logger::log('dfrn_confirm: request: POST=' . print_r($_POST, true), Logger::DATA);
Logger::debug('request', ['POST' => $_POST]);
// If $aes_key is set, both of these items require unpacking from the hex transport encoding.

View file

@ -19,7 +19,7 @@
*
* The dfrn notify endpoint
*
* @see PDF with dfrn specs: https://github.com/friendica/friendica/blob/master/spec/dfrn2.pdf
* @see PDF with dfrn specs: https://github.com/friendica/friendica/blob/stable/spec/dfrn2.pdf
*/
use Friendica\App;

View file

@ -379,7 +379,7 @@ function dfrn_poll_post(App $a)
// NOTREACHED
} else {
// Update the writable flag if it changed
Logger::log('dfrn_poll: post request feed: ' . print_r($_POST, true), Logger::DATA);
Logger::debug('post request feed', ['post' => $_POST]);
if ($dfrn_version >= 2.21) {
if ($perm === 'rw') {
$writable = 1;
@ -521,7 +521,7 @@ function dfrn_poll_content(App $a)
if (strlen($s) && strstr($s, '<?xml')) {
$xml = XML::parseString($s);
Logger::log('dfrn_poll: profile: parsed xml: ' . print_r($xml, true), Logger::DATA);
Logger::debug(' profile: parsed', ['xml' => $xml]);
Logger::log('dfrn_poll: secure profile: challenge: ' . $xml->challenge . ' expecting ' . $hash);
Logger::log('dfrn_poll: secure profile: sec: ' . $xml->sec . ' expecting ' . $sec);

View file

@ -19,9 +19,9 @@
*
*Handles communication associated with the issuance of friend requests.
*
* @see PDF with dfrn specs: https://github.com/friendica/friendica/blob/master/spec/dfrn2.pdf
* @see PDF with dfrn specs: https://github.com/friendica/friendica/blob/stable/spec/dfrn2.pdf
* You also find a graphic which describes the confirmation process at
* https://github.com/friendica/friendica/blob/master/spec/dfrn2_contact_request.png
* https://github.com/friendica/friendica/blob/stable/spec/dfrn2_contact_request.png
*/
use Friendica\App;
@ -297,8 +297,8 @@ function dfrn_request_post(App $a)
$data = Probe::uri($url);
$network = $data["network"];
// Canonicalise email-style profile locator
$url = Probe::webfingerDfrn($url, $hcard);
// Canonicalize email-style profile locator
$url = Probe::webfingerDfrn($data['url'], $hcard);
if (substr($url, 0, 5) === 'stat:') {
// Every time we detect the remote subscription we define this as OStatus.

View file

@ -42,7 +42,7 @@ use Friendica\Util\Strings;
function display_init(App $a)
{
if (ActivityPub::isRequest()) {
Objects::rawContent();
Objects::rawContent(['guid' => $a->argv[1] ?? null]);
}
if (DI::config()->get('system', 'block_public') && !Session::isAuthenticated()) {
@ -54,7 +54,7 @@ function display_init(App $a)
$item = null;
$item_user = local_user();
$fields = ['id', 'parent', 'author-id', 'body', 'uid', 'guid'];
$fields = ['id', 'parent', 'author-id', 'body', 'uid', 'guid', 'gravity'];
// If there is only one parameter, then check if this parameter could be a guid
if ($a->argc == 2) {
@ -101,12 +101,12 @@ function display_init(App $a)
}
if (!empty($_SERVER['HTTP_ACCEPT']) && strstr($_SERVER['HTTP_ACCEPT'], 'application/atom+xml')) {
Logger::log('Directly serving XML for id '.$item["id"], Logger::DEBUG);
displayShowFeed($item["id"], false);
Logger::log('Directly serving XML for id '.$item['id'], Logger::DEBUG);
displayShowFeed($item['id'], false);
}
if ($item["id"] != $item["parent"]) {
$parent = Item::selectFirstForUser($item_user, $fields, ['id' => $item["parent"]]);
if ($item['gravity'] != GRAVITY_PARENT) {
$parent = Item::selectFirstForUser($item_user, $fields, ['id' => $item['parent']]);
$item = $parent ?: $item;
}
@ -116,11 +116,7 @@ function display_init(App $a)
$nickname = str_replace(Strings::normaliseLink(DI::baseUrl()) . '/profile/', '', Strings::normaliseLink($profiledata['url']));
if (!empty($a->user['nickname']) && $nickname != $a->user['nickname']) {
$profile = DBA::fetchFirst("SELECT `profile`.* , `contact`.`avatar-date` AS picdate, `user`.* FROM `profile`
INNER JOIN `contact` on `contact`.`uid` = `profile`.`uid` INNER JOIN `user` ON `profile`.`uid` = `user`.`uid`
WHERE `user`.`nickname` = ? AND `contact`.`self` LIMIT 1",
$nickname
);
$profile = DBA::selectFirst('owner-view', [], ['nickname' => $nickname]);
if (DBA::isResult($profile)) {
$profiledata = $profile;
}
@ -187,6 +183,8 @@ function display_content(App $a, $update = false, $update_uid = 0)
$item = null;
$force = (bool)($_REQUEST['force'] ?? false);
if ($update) {
$item_id = $_REQUEST['item_id'];
$item = Item::selectFirst(['uid', 'parent', 'parent-uri'], ['id' => $item_id]);
@ -209,8 +207,8 @@ function display_content(App $a, $update = false, $update_uid = 0)
$condition = ['guid' => $a->argv[1], 'uid' => local_user()];
$item = Item::selectFirstForUser(local_user(), $fields, $condition);
if (DBA::isResult($item)) {
$item_id = $item["id"];
$item_parent = $item["parent"];
$item_id = $item['id'];
$item_parent = $item['parent'];
$item_parent_uri = $item['parent-uri'];
}
}
@ -218,8 +216,8 @@ function display_content(App $a, $update = false, $update_uid = 0)
if (($item_parent == 0) && remote_user()) {
$item = Item::selectFirst($fields, ['guid' => $a->argv[1], 'private' => Item::PRIVATE, 'origin' => true]);
if (DBA::isResult($item) && Contact::isFollower(remote_user(), $item['uid'])) {
$item_id = $item["id"];
$item_parent = $item["parent"];
$item_id = $item['id'];
$item_parent = $item['parent'];
$item_parent_uri = $item['parent-uri'];
}
}
@ -228,8 +226,8 @@ function display_content(App $a, $update = false, $update_uid = 0)
$condition = ['private' => [Item::PUBLIC, Item::UNLISTED], 'guid' => $a->argv[1], 'uid' => 0];
$item = Item::selectFirstForUser(local_user(), $fields, $condition);
if (DBA::isResult($item)) {
$item_id = $item["id"];
$item_parent = $item["parent"];
$item_id = $item['id'];
$item_parent = $item['parent'];
$item_parent_uri = $item['parent-uri'];
}
}
@ -285,7 +283,7 @@ function display_content(App $a, $update = false, $update_uid = 0)
}
// We need the editor here to be able to reshare an item.
if ($is_owner) {
if ($is_owner && !$update) {
$x = [
'is_owner' => true,
'allow_location' => $a->user['allow_location'],
@ -308,7 +306,7 @@ function display_content(App $a, $update = false, $update_uid = 0)
$unseen = false;
}
if ($update && !$unseen) {
if ($update && !$unseen && !$force) {
return '';
}

View file

@ -132,6 +132,8 @@ function editpost_content(App $a)
'$message' => DI::l10n()->t('Message'),
'$browser' => DI::l10n()->t('Browser'),
'$shortpermset' => DI::l10n()->t('permissions'),
'$compose_link_title' => DI::l10n()->t('Open Compose page'),
]);
return $o;

View file

@ -66,7 +66,7 @@ function events_init(App $a)
function events_post(App $a)
{
Logger::log('post: ' . print_r($_REQUEST, true), Logger::DATA);
Logger::debug('post', ['request' => $_REQUEST]);
if (!local_user()) {
return;

View file

@ -39,31 +39,26 @@ function fbrowser_content(App $a)
switch ($a->argv[1]) {
case "image":
$path = [["", DI::l10n()->t("Photos")]];
$path = ['' => DI::l10n()->t('Photos')];
$albums = false;
$sql_extra = "";
$sql_extra2 = " ORDER BY created DESC LIMIT 0, 10";
if ($a->argc==2) {
$albums = q("SELECT distinct(`album`) AS `album` FROM `photo` WHERE `uid` = %d AND `album` != '%s' AND `album` != '%s' ",
$photos = q("SELECT distinct(`album`) AS `album` FROM `photo` WHERE `uid` = %d AND `album` != '%s' AND `album` != '%s' ",
intval(local_user()),
DBA::escape('Contact Photos'),
DBA::escape(DI::l10n()->t('Contact Photos'))
);
function _map_folder1($el)
{
return [bin2hex($el['album']),$el['album']];
};
$albums = array_map("_map_folder1", $albums);
$albums = array_column($photos, 'album');
}
if ($a->argc == 3) {
$album = hex2bin($a->argv[2]);
$album = $a->argv[2];
$sql_extra = sprintf("AND `album` = '%s' ", DBA::escape($album));
$sql_extra2 = "";
$path[] = [$a->argv[2], $album];
$path[$album] = $album;
}
$r = q("SELECT `resource-id`, ANY_VALUE(`id`) AS `id`, ANY_VALUE(`filename`) AS `filename`, ANY_VALUE(`type`) AS `type`,

View file

@ -28,6 +28,7 @@ use Friendica\Model\Profile;
use Friendica\Model\Item;
use Friendica\Network\Probe;
use Friendica\Database\DBA;
use Friendica\Model\User;
use Friendica\Util\Strings;
function follow_post(App $a)
@ -40,7 +41,6 @@ function follow_post(App $a)
DI::baseUrl()->redirect('contact');
}
$uid = local_user();
$url = Probe::cleanURI($_REQUEST['url']);
$return_path = 'follow?url=' . urlencode($url);
@ -48,7 +48,7 @@ function follow_post(App $a)
// This is just a precaution if maybe this page is called somewhere directly via POST
$_SESSION['fastlane'] = $url;
$result = Contact::createFromProbe($uid, $url, true);
$result = Contact::createFromProbe($a->user, $url, true);
if ($result['success'] == false) {
// Possibly it is a remote item and not an account
@ -95,88 +95,63 @@ function follow_content(App $a)
$submit = DI::l10n()->t('Submit Request');
// Don't try to add a pending contact
$r = q("SELECT `pending` FROM `contact` WHERE `uid` = %d AND ((`rel` != %d) OR (`network` = '%s')) AND
(`nurl` = '%s' OR `alias` = '%s' OR `alias` = '%s') AND
`network` != '%s' LIMIT 1",
intval(local_user()), DBA::escape(Contact::FOLLOWER), DBA::escape(Protocol::DFRN), DBA::escape(Strings::normaliseLink($url)),
DBA::escape(Strings::normaliseLink($url)), DBA::escape($url), DBA::escape(Protocol::STATUSNET));
$user_contact = DBA::selectFirst('contact', ['pending'], ["`uid` = ? AND ((`rel` != ?) OR (`network` = ?)) AND
(`nurl` = ? OR `alias` = ? OR `alias` = ?) AND `network` != ?",
$uid, Contact::FOLLOWER, Protocol::DFRN, Strings::normaliseLink($url),
Strings::normaliseLink($url), $url, Protocol::STATUSNET]);
if ($r) {
if ($r[0]['pending']) {
if (DBA::isResult($user_contact)) {
if ($user_contact['pending']) {
notice(DI::l10n()->t('You already added this contact.'));
$submit = '';
//$a->internalRedirect($_SESSION['return_path']);
// NOTREACHED
}
}
$ret = Probe::uri($url);
$protocol = Contact::getProtocol($ret['url'], $ret['network']);
if (($protocol == Protocol::DIASPORA) && !DI::config()->get('system', 'diaspora_enabled')) {
notice(DI::l10n()->t("Diaspora support isn't enabled. Contact can't be added."));
$submit = '';
//$a->internalRedirect($_SESSION['return_path']);
// NOTREACHED
}
if (($protocol == Protocol::OSTATUS) && DI::config()->get('system', 'ostatus_disabled')) {
notice(DI::l10n()->t("OStatus support is disabled. Contact can't be added."));
$submit = '';
//$a->internalRedirect($_SESSION['return_path']);
// NOTREACHED
}
if ($protocol == Protocol::PHANTOM) {
$contact = Contact::getByURL($url, 0, [], true);
if (empty($contact)) {
// Possibly it is a remote item and not an account
follow_remote_item($url);
notice(DI::l10n()->t("The network type couldn't be detected. Contact can't be added."));
$submit = '';
//$a->internalRedirect($_SESSION['return_path']);
// NOTREACHED
$contact = ['url' => $url, 'network' => Protocol::PHANTOM, 'name' => $url, 'keywords' => ''];
}
$protocol = Contact::getProtocol($contact['url'], $contact['network']);
if (($protocol == Protocol::DIASPORA) && !DI::config()->get('system', 'diaspora_enabled')) {
notice(DI::l10n()->t("Diaspora support isn't enabled. Contact can't be added."));
$submit = '';
}
if (($protocol == Protocol::OSTATUS) && DI::config()->get('system', 'ostatus_disabled')) {
notice(DI::l10n()->t("OStatus support is disabled. Contact can't be added."));
$submit = '';
}
if ($protocol == Protocol::MAIL) {
$ret['url'] = $ret['addr'];
$contact['url'] = $contact['addr'];
}
if (($protocol === Protocol::DFRN) && !DBA::isResult($r)) {
$request = $ret['request'];
if (($protocol === Protocol::DFRN) && !DBA::isResult($contact)) {
$request = $contact['request'];
$tpl = Renderer::getMarkupTemplate('dfrn_request.tpl');
} else {
$request = DI::baseUrl() . '/follow';
$tpl = Renderer::getMarkupTemplate('auto_request.tpl');
}
$r = q("SELECT `url` FROM `contact` WHERE `uid` = %d AND `self` LIMIT 1", intval($uid));
if (!$r) {
$owner = User::getOwnerDataById($uid);
if (empty($owner)) {
notice(DI::l10n()->t('Permission denied.'));
DI::baseUrl()->redirect($return_path);
// NOTREACHED
}
$myaddr = $r[0]['url'];
$gcontact_id = 0;
$myaddr = $owner['url'];
// Makes the connection request for friendica contacts easier
$_SESSION['fastlane'] = $ret['url'];
$r = q("SELECT `id`, `location`, `about`, `keywords` FROM `gcontact` WHERE `nurl` = '%s'",
Strings::normaliseLink($ret['url']));
if (!$r) {
$r = [['location' => '', 'about' => '', 'keywords' => '']];
} else {
$gcontact_id = $r[0]['id'];
}
if ($protocol === Protocol::DIASPORA) {
$r[0]['location'] = '';
$r[0]['about'] = '';
}
$_SESSION['fastlane'] = $contact['url'];
$o = Renderer::replaceMacros($tpl, [
'$header' => DI::l10n()->t('Connect/Follow'),
@ -188,30 +163,27 @@ function follow_content(App $a)
'$cancel' => DI::l10n()->t('Cancel'),
'$request' => $request,
'$name' => $ret['name'],
'$url' => $ret['url'],
'$zrl' => Profile::zrl($ret['url']),
'$name' => $contact['name'],
'$url' => $contact['url'],
'$zrl' => Profile::zrl($contact['url']),
'$myaddr' => $myaddr,
'$keywords' => $r[0]['keywords'],
'$keywords' => $contact['keywords'],
'$does_know_you' => ['knowyou', DI::l10n()->t('%s knows you', $ret['name'])],
'$does_know_you' => ['knowyou', DI::l10n()->t('%s knows you', $contact['name'])],
'$addnote_field' => ['dfrn-request-message', DI::l10n()->t('Add a personal note:')],
]);
DI::page()['aside'] = '';
$profiledata = Contact::getDetailsByURL($ret['url']);
if ($profiledata) {
Profile::load($a, '', $profiledata, false);
}
if ($protocol != Protocol::PHANTOM) {
Profile::load($a, '', $contact, false);
if ($gcontact_id <> 0) {
$o .= Renderer::replaceMacros(Renderer::getMarkupTemplate('section_title.tpl'),
['$title' => DI::l10n()->t('Status Messages and Posts')]
);
// Show last public posts
$o .= Contact::getPostsFromUrl($ret['url']);
$o .= Contact::getPostsFromUrl($contact['url']);
}
return $o;

View file

@ -29,11 +29,12 @@
*/
use Friendica\App;
use Friendica\Content\Pager;
use Friendica\Content\Item as ItemHelper;
use Friendica\Content\Text\BBCode;
use Friendica\Core\Hook;
use Friendica\Core\Logger;
use Friendica\Core\Protocol;
use Friendica\Core\Renderer;
use Friendica\Core\Session;
use Friendica\Core\System;
use Friendica\Core\Worker;
@ -46,7 +47,7 @@ use Friendica\Model\FileTag;
use Friendica\Model\Item;
use Friendica\Model\Notify\Type;
use Friendica\Model\Photo;
use Friendica\Model\Term;
use Friendica\Model\Tag;
use Friendica\Network\HTTPException;
use Friendica\Object\EMail\ItemCCEMail;
use Friendica\Protocol\Activity;
@ -67,7 +68,10 @@ function item_post(App $a) {
if (!empty($_REQUEST['dropitems'])) {
$arr_drop = explode(',', $_REQUEST['dropitems']);
drop_items($arr_drop);
foreach ($arr_drop as $item) {
Item::deleteForUser(['id' => $item], $uid);
}
$json = ['success' => 1];
System::jsonExit($json);
}
@ -101,14 +105,9 @@ function item_post(App $a) {
$toplevel_item_id = intval($_REQUEST['parent'] ?? 0);
$thr_parent_uri = trim($_REQUEST['parent_uri'] ?? '');
$thread_parent_id = 0;
$thread_parent_contact = null;
$toplevel_item = null;
$parent_user = null;
$parent_contact = null;
$objecttype = null;
$profile_uid = ($_REQUEST['profile_uid'] ?? 0) ?: local_user();
$posttype = ($_REQUEST['post_type'] ?? '') ?: Item::PT_ARTICLE;
@ -123,11 +122,9 @@ function item_post(App $a) {
// if this isn't the top-level parent of the conversation, find it
if (DBA::isResult($toplevel_item)) {
// The URI and the contact is taken from the direct parent which needn't to be the top parent
$thread_parent_id = $toplevel_item['id'];
$thr_parent_uri = $toplevel_item['uri'];
$thread_parent_contact = Contact::getDetailsByURL($toplevel_item["author-link"]);
if ($toplevel_item['id'] != $toplevel_item['parent']) {
if ($toplevel_item['gravity'] != GRAVITY_PARENT) {
$toplevel_item = Item::selectFirst([], ['id' => $toplevel_item['parent']]);
}
}
@ -253,7 +250,7 @@ function item_post(App $a) {
$verb = $orig_post['verb'];
$objecttype = $orig_post['object-type'];
$app = $orig_post['app'];
$categories = $orig_post['file'];
$categories = $orig_post['file'] ?? '';
$title = Strings::escapeTags(trim($_REQUEST['title']));
$body = trim($body);
$private = $orig_post['private'];
@ -370,74 +367,63 @@ function item_post(App $a) {
// get contact info for owner
if ($profile_uid == local_user() || $allow_comment) {
$contact_record = $author;
$contact_record = $author ?: [];
} else {
$contact_record = DBA::selectFirst('contact', [], ['uid' => $profile_uid, 'self' => true]);
$contact_record = DBA::selectFirst('contact', [], ['uid' => $profile_uid, 'self' => true]) ?: [];
}
// Look for any tags and linkify them
$str_tags = '';
$inform = '';
$tags = BBCode::getTags($body);
if ($thread_parent_id && !\Friendica\Content\Feature::isEnabled($uid, 'explicit_mentions')) {
$tags = item_add_implicit_mentions($tags, $thread_parent_contact, $thread_parent_id);
}
$tagged = [];
$private_forum = false;
$private_id = null;
$only_to_forum = false;
$forum_contact = [];
if (count($tags)) {
$body = BBCode::performWithEscapedTags($body, ['noparse', 'pre', 'code', 'img'], function ($body) use ($profile_uid, $network, $str_contact_allow, &$inform, &$private_forum, &$private_id, &$only_to_forum, &$forum_contact) {
$tags = BBCode::getTags($body);
$tagged = [];
foreach ($tags as $tag) {
$tag_type = substr($tag, 0, 1);
if ($tag_type == Term::TAG_CHARACTER[Term::HASHTAG]) {
if ($tag_type == Tag::TAG_CHARACTER[Tag::HASHTAG]) {
continue;
}
/*
* If we already tagged 'Robert Johnson', don't try and tag 'Robert'.
/* If we already tagged 'Robert Johnson', don't try and tag 'Robert'.
* Robert Johnson should be first in the $tags array
*/
$fullnametagged = false;
/// @TODO $tagged is initialized above if () block and is not filled, maybe old-lost code?
foreach ($tagged as $nextTag) {
if (stristr($nextTag, $tag . ' ')) {
$fullnametagged = true;
break;
continue 2;
}
}
if ($fullnametagged) {
continue;
}
$success = handle_tag($body, $inform, $str_tags, local_user() ? local_user() : $profile_uid, $tag, $network);
$success = ItemHelper::replaceTag($body, $inform, local_user() ? local_user() : $profile_uid, $tag, $network);
if ($success['replaced']) {
$tagged[] = $tag;
}
// When the forum is private or the forum is addressed with a "!" make the post private
if (is_array($success['contact']) && (!empty($success['contact']['prv']) || ($tag_type == Term::TAG_CHARACTER[Term::EXCLUSIVE_MENTION]))) {
if (!empty($success['contact']['prv']) || ($tag_type == Tag::TAG_CHARACTER[Tag::EXCLUSIVE_MENTION])) {
$private_forum = $success['contact']['prv'];
$only_to_forum = ($tag_type == Term::TAG_CHARACTER[Term::EXCLUSIVE_MENTION]);
$only_to_forum = ($tag_type == Tag::TAG_CHARACTER[Tag::EXCLUSIVE_MENTION]);
$private_id = $success['contact']['id'];
$forum_contact = $success['contact'];
} elseif (is_array($success['contact']) && !empty($success['contact']['forum']) &&
($str_contact_allow == '<' . $success['contact']['id'] . '>')) {
} elseif (!empty($success['contact']['forum']) && ($str_contact_allow == '<' . $success['contact']['id'] . '>')) {
$private_forum = false;
$only_to_forum = true;
$private_id = $success['contact']['id'];
$forum_contact = $success['contact'];
}
}
}
return $body;
});
$original_contact_id = $contact_id;
if (!$toplevel_item_id && count($forum_contact) && ($private_forum || $only_to_forum)) {
if (!$toplevel_item_id && !empty($forum_contact) && ($private_forum || $only_to_forum)) {
// we tagged a forum in a top level post. Now we change the post
$private = $private_forum;
@ -578,9 +564,9 @@ function item_post(App $a) {
$datarray['gravity'] = $gravity;
$datarray['network'] = $network;
$datarray['contact-id'] = $contact_id;
$datarray['owner-name'] = $contact_record['name'];
$datarray['owner-link'] = $contact_record['url'];
$datarray['owner-avatar'] = $contact_record['thumb'];
$datarray['owner-name'] = $contact_record['name'] ?? '';
$datarray['owner-link'] = $contact_record['url'] ?? '';
$datarray['owner-avatar'] = $contact_record['thumb'] ?? '';
$datarray['owner-id'] = Contact::getIdForURL($datarray['owner-link']);
$datarray['author-name'] = $author['name'];
$datarray['author-link'] = $author['url'];
@ -599,7 +585,6 @@ function item_post(App $a) {
$datarray['app'] = $app;
$datarray['location'] = $location;
$datarray['coord'] = $coord;
$datarray['tag'] = $str_tags;
$datarray['file'] = $categories;
$datarray['inform'] = $inform;
$datarray['verb'] = $verb;
@ -656,7 +641,7 @@ function item_post(App $a) {
// Check for hashtags in the body and repair or add hashtag links
if ($preview || $orig_post) {
Item::setHashtags($datarray);
$datarray['body'] = Item::setHashtags($datarray['body']);
}
// preview mode - prepare the body for display and send it via json
@ -664,6 +649,7 @@ function item_post(App $a) {
// We set the datarray ID to -1 because in preview mode the dataray
// doesn't have an ID.
$datarray["id"] = -1;
$datarray["uri-id"] = -1;
$datarray["item_id"] = -1;
$datarray["author-network"] = Protocol::DFRN;
@ -696,7 +682,6 @@ function item_post(App $a) {
$fields = [
'title' => $datarray['title'],
'body' => $datarray['body'],
'tag' => $datarray['tag'],
'attach' => $datarray['attach'],
'file' => $datarray['file'],
'rendered-html' => $datarray['rendered-html'],
@ -750,12 +735,18 @@ function item_post(App $a) {
throw new HTTPException\InternalServerErrorException(DI::l10n()->t('Item couldn\'t be fetched.'));
}
Tag::storeFromBody($datarray['uri-id'], $datarray['body']);
if (!\Friendica\Content\Feature::isEnabled($uid, 'explicit_mentions') && ($datarray['gravity'] == GRAVITY_COMMENT)) {
Tag::createImplicitMentions($datarray['uri-id'], $datarray['thr-parent-id']);
}
// update filetags in pconfig
FileTag::updatePconfig($uid, $categories_old, $categories_new, 'category');
// These notifications are sent if someone else is commenting other your wall
if ($toplevel_item_id) {
if ($contact_record != $author) {
if ($contact_record != $author) {
if ($toplevel_item_id) {
notification([
'type' => Type::COMMENT,
'notify_flags' => $user['notify-flags'],
@ -773,9 +764,7 @@ function item_post(App $a) {
'parent' => $toplevel_item_id,
'parent_uri' => $toplevel_item['uri']
]);
}
} else {
if (($contact_record != $author) && !count($forum_contact)) {
} elseif (empty($forum_contact)) {
notification([
'type' => Type::WALL,
'notify_flags' => $user['notify-flags'],
@ -863,7 +852,9 @@ function item_content(App $a)
if (($a->argc >= 3) && ($a->argv[1] === 'drop') && intval($a->argv[2])) {
if (DI::mode()->isAjax()) {
$o = Item::deleteForUser(['id' => $a->argv[2]], local_user());
Item::deleteForUser(['id' => $a->argv[2]], local_user());
// ajax return: [<item id>, 0 (no perm) | <owner id>]
System::jsonExit([intval($a->argv[2]), local_user()]);
} else {
if (!empty($a->argv[3])) {
$o = drop_item($a->argv[2], $a->argv[3]);
@ -872,203 +863,110 @@ function item_content(App $a)
$o = drop_item($a->argv[2]);
}
}
if (DI::mode()->isAjax()) {
// ajax return: [<item id>, 0 (no perm) | <owner id>]
System::jsonExit([intval($a->argv[2]), intval($o)]);
}
}
return $o;
}
/**
* This function removes the tag $tag from the text $body and replaces it with
* the appropriate link.
*
* @param App $a
* @param string $body the text to replace the tag in
* @param string $inform a comma-seperated string containing everybody to inform
* @param string $str_tags string to add the tag to
* @param integer $profile_uid
* @param string $tag the tag to replace
* @param string $network The network of the post
*
* @return array|bool ['replaced' => $replaced, 'contact' => $contact];
* @throws ImagickException
* @param int $id
* @param string $return
* @return string
* @throws HTTPException\InternalServerErrorException
*/
function handle_tag(&$body, &$inform, &$str_tags, $profile_uid, $tag, $network = "")
function drop_item(int $id, string $return = '')
{
$replaced = false;
$r = null;
// locate item to be deleted
$fields = ['id', 'uid', 'guid', 'contact-id', 'deleted', 'gravity', 'parent'];
$item = Item::selectFirstForUser(local_user(), $fields, ['id' => $id]);
//is it a person tag?
if (Term::isType($tag, Term::MENTION, Term::IMPLICIT_MENTION, Term::EXCLUSIVE_MENTION)) {
$tag_type = substr($tag, 0, 1);
//is it already replaced?
if (strpos($tag, '[url=')) {
//append tag to str_tags
if (!stristr($str_tags, $tag)) {
if (strlen($str_tags)) {
$str_tags .= ',';
}
$str_tags .= $tag;
}
// Checking for the alias that is used for OStatus
$pattern = "/[@!]\[url\=(.*?)\](.*?)\[\/url\]/ism";
if (preg_match($pattern, $tag, $matches)) {
$data = Contact::getDetailsByURL($matches[1]);
if ($data["alias"] != "") {
$newtag = '@[url=' . $data["alias"] . ']' . $data["nick"] . '[/url]';
if (!stripos($str_tags, '[url=' . $data["alias"] . ']')) {
if (strlen($str_tags)) {
$str_tags .= ',';
}
$str_tags .= $newtag;
}
}
}
return $replaced;
}
//get the person's name
$name = substr($tag, 1);
// Sometimes the tag detection doesn't seem to work right
// This is some workaround
$nameparts = explode(" ", $name);
$name = $nameparts[0];
// Try to detect the contact in various ways
if (strpos($name, 'http://')) {
// At first we have to ensure that the contact exists
Contact::getIdForURL($name);
// Now we should have something
$contact = Contact::getDetailsByURL($name);
} elseif (strpos($name, '@')) {
// This function automatically probes when no entry was found
$contact = Contact::getDetailsByAddr($name);
} else {
$contact = false;
$fields = ['id', 'url', 'nick', 'name', 'alias', 'network', 'forum', 'prv'];
if (strrpos($name, '+')) {
// Is it in format @nick+number?
$tagcid = intval(substr($name, strrpos($name, '+') + 1));
$contact = DBA::selectFirst('contact', $fields, ['id' => $tagcid, 'uid' => $profile_uid]);
}
// select someone by nick or attag in the current network
if (!DBA::isResult($contact) && ($network != "")) {
$condition = ["(`nick` = ? OR `attag` = ?) AND `network` = ? AND `uid` = ?",
$name, $name, $network, $profile_uid];
$contact = DBA::selectFirst('contact', $fields, $condition);
}
//select someone by name in the current network
if (!DBA::isResult($contact) && ($network != "")) {
$condition = ['name' => $name, 'network' => $network, 'uid' => $profile_uid];
$contact = DBA::selectFirst('contact', $fields, $condition);
}
// select someone by nick or attag in any network
if (!DBA::isResult($contact)) {
$condition = ["(`nick` = ? OR `attag` = ?) AND `uid` = ?", $name, $name, $profile_uid];
$contact = DBA::selectFirst('contact', $fields, $condition);
}
// select someone by name in any network
if (!DBA::isResult($contact)) {
$condition = ['name' => $name, 'uid' => $profile_uid];
$contact = DBA::selectFirst('contact', $fields, $condition);
}
}
// Check if $contact has been successfully loaded
if (DBA::isResult($contact)) {
if (strlen($inform) && (isset($contact["notify"]) || isset($contact["id"]))) {
$inform .= ',';
}
if (isset($contact["id"])) {
$inform .= 'cid:' . $contact["id"];
} elseif (isset($contact["notify"])) {
$inform .= $contact["notify"];
}
$profile = $contact["url"];
$alias = $contact["alias"];
$newname = ($contact["name"] ?? '') ?: $contact["nick"];
}
//if there is an url for this persons profile
if (isset($profile) && ($newname != "")) {
$replaced = true;
// create profile link
$profile = str_replace(',', '%2c', $profile);
$newtag = $tag_type.'[url=' . $profile . ']' . $newname . '[/url]';
$body = str_replace($tag_type . $name, $newtag, $body);
// append tag to str_tags
if (!stristr($str_tags, $newtag)) {
if (strlen($str_tags)) {
$str_tags .= ',';
}
$str_tags .= $newtag;
}
/*
* Status.Net seems to require the numeric ID URL in a mention if the person isn't
* subscribed to you. But the nickname URL is OK if they are. Grrr. We'll tag both.
*/
if (!empty($alias)) {
$newtag = '@[url=' . $alias . ']' . $newname . '[/url]';
if (!stripos($str_tags, '[url=' . $alias . ']')) {
if (strlen($str_tags)) {
$str_tags .= ',';
}
$str_tags .= $newtag;
}
}
}
if (!DBA::isResult($item)) {
notice(DI::l10n()->t('Item not found.') . EOL);
DI::baseUrl()->redirect('network');
}
return ['replaced' => $replaced, 'contact' => $contact];
}
if ($item['deleted']) {
return '';
}
function item_add_implicit_mentions(array $tags, array $thread_parent_contact, $thread_parent_id)
{
if (DI::config()->get('system', 'disable_implicit_mentions')) {
// Add a tag if the parent contact is from ActivityPub or OStatus (This will notify them)
if (in_array($thread_parent_contact['network'], [Protocol::OSTATUS, Protocol::ACTIVITYPUB])) {
$contact = Term::TAG_CHARACTER[Term::MENTION] . '[url=' . $thread_parent_contact['url'] . ']' . $thread_parent_contact['nick'] . '[/url]';
if (!stripos(implode($tags), '[url=' . $thread_parent_contact['url'] . ']')) {
$tags[] = $contact;
$contact_id = 0;
// check if logged in user is either the author or owner of this item
if (Session::getRemoteContactID($item['uid']) == $item['contact-id']) {
$contact_id = $item['contact-id'];
}
if ((local_user() == $item['uid']) || $contact_id) {
// Check if we should do HTML-based delete confirmation
if (!empty($_REQUEST['confirm'])) {
// <form> can't take arguments in its "action" parameter
// so add any arguments as hidden inputs
$query = explode_querystring(DI::args()->getQueryString());
$inputs = [];
foreach ($query['args'] as $arg) {
if (strpos($arg, 'confirm=') === false) {
$arg_parts = explode('=', $arg);
$inputs[] = ['name' => $arg_parts[0], 'value' => $arg_parts[1]];
}
}
return Renderer::replaceMacros(Renderer::getMarkupTemplate('confirm.tpl'), [
'$method' => 'get',
'$message' => DI::l10n()->t('Do you really want to delete this item?'),
'$extra_inputs' => $inputs,
'$confirm' => DI::l10n()->t('Yes'),
'$confirm_url' => $query['base'],
'$confirm_name' => 'confirmed',
'$cancel' => DI::l10n()->t('Cancel'),
]);
}
// Now check how the user responded to the confirmation query
if (!empty($_REQUEST['canceled'])) {
DI::baseUrl()->redirect('display/' . $item['guid']);
}
$is_comment = $item['gravity'] == GRAVITY_COMMENT;
$parentitem = null;
if (!empty($item['parent'])) {
$fields = ['guid'];
$parentitem = Item::selectFirstForUser(local_user(), $fields, ['id' => $item['parent']]);
}
// delete the item
Item::deleteForUser(['id' => $item['id']], local_user());
$return_url = hex2bin($return);
// removes update_* from return_url to ignore Ajax refresh
$return_url = str_replace("update_", "", $return_url);
// Check if delete a comment
if ($is_comment) {
// Return to parent guid
if (!empty($parentitem)) {
DI::baseUrl()->redirect('display/' . $parentitem['guid']);
//NOTREACHED
} // In case something goes wrong
else {
DI::baseUrl()->redirect('network');
//NOTREACHED
}
} else {
// if unknown location or deleting top level post called from display
if (empty($return_url) || strpos($return_url, 'display') !== false) {
DI::baseUrl()->redirect('network');
//NOTREACHED
} else {
DI::baseUrl()->redirect($return_url);
//NOTREACHED
}
}
} else {
$implicit_mentions = [
$thread_parent_contact['url'] => $thread_parent_contact['nick']
];
$parent_terms = Term::tagArrayFromItemId($thread_parent_id, [Term::MENTION, Term::IMPLICIT_MENTION]);
foreach ($parent_terms as $parent_term) {
$implicit_mentions[$parent_term['url']] = $parent_term['term'];
}
foreach ($implicit_mentions as $url => $label) {
if ($url != \Friendica\Model\Profile::getMyURL() && !stripos(implode($tags), '[url=' . $url . ']')) {
$tags[] = Term::TAG_CHARACTER[Term::IMPLICIT_MENTION] . '[url=' . $url . ']' . $label . '[/url]';
}
}
notice(DI::l10n()->t('Permission denied.'));
DI::baseUrl()->redirect('display/' . $item['guid']);
//NOTREACHED
}
return $tags;
return '';
}

View file

@ -41,10 +41,10 @@ function lostpass_post(App $a)
DI::baseUrl()->redirect();
}
$pwdreset_token = Strings::getRandomName(12) . random_int(1000, 9999);
$pwdreset_token = Strings::getRandomHex(32);
$fields = [
'pwdreset' => $pwdreset_token,
'pwdreset' => hash('sha256', $pwdreset_token),
'pwdreset_time' => DateTimeFormat::utcNow()
];
$result = DBA::update('user', $fields, ['uid' => $user['uid']]);
@ -95,7 +95,7 @@ function lostpass_content(App $a)
if ($a->argc > 1) {
$pwdreset_token = $a->argv[1];
$user = DBA::selectFirst('user', ['uid', 'username', 'nickname', 'email', 'pwdreset_time', 'language'], ['pwdreset' => $pwdreset_token]);
$user = DBA::selectFirst('user', ['uid', 'username', 'nickname', 'email', 'pwdreset_time', 'language'], ['pwdreset' => hash('sha256', $pwdreset_token)]);
if (!DBA::isResult($user)) {
notice(DI::l10n()->t("Request could not be verified. \x28You may have previously submitted it.\x29 Password reset failed."));

View file

@ -352,13 +352,7 @@ function message_content(App $a)
$messages = DBA::toArray($messages_stmt);
DBA::update('mail', ['seen' => 1], ['parent-uri' => $message['parent-uri'], 'uid' => local_user()]);
if ($message['convid']) {
// Clear Diaspora private message notifications
DBA::update('notify', ['seen' => 1], ['type' => Type::MAIL, 'parent' => $message['convid'], 'uid' => local_user()]);
}
// Clear DFRN private message notifications
DBA::update('notify', ['seen' => 1], ['type' => Type::MAIL, 'parent' => $message['parent-uri'], 'uid' => local_user()]);
DBA::update('notify', ['seen' => 1], ['type' => Type::MAIL, 'parent' => $message['id'], 'uid' => local_user()]);
} else {
$messages = false;
}

View file

@ -68,7 +68,7 @@ function msearch_post(App $a)
$perpage
);
while($search_result = DBA::fetch($search_stmt)) {
while ($search_result = DBA::fetch($search_stmt)) {
$results[] = [
'name' => $search_result['name'],
'url' => DI::baseUrl() . '/profile/' . $search_result['nickname'],
@ -77,6 +77,8 @@ function msearch_post(App $a)
];
}
DBA::close($search_stmt);
$output = ['total' => $total, 'items_page' => $perpage, 'page' => $page, 'results' => $results];
echo json_encode($output);

View file

@ -37,8 +37,8 @@ use Friendica\DI;
use Friendica\Model\Contact;
use Friendica\Model\Group;
use Friendica\Model\Item;
use Friendica\Model\Post\Category;
use Friendica\Model\Profile;
use Friendica\Model\Term;
use Friendica\Module\Security\Login;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Proxy as ProxyUtils;
@ -58,8 +58,8 @@ function network_init(App $a)
$group_id = (($a->argc > 1 && is_numeric($a->argv[1])) ? intval($a->argv[1]) : 0);
$cid = 0;
if (!empty($_GET['cid'])) {
$cid = $_GET['cid'];
if (!empty($_GET['contactid'])) {
$cid = $_GET['contactid'];
$_GET['nets'] = '';
$group_id = 0;
}
@ -379,25 +379,25 @@ function networkFlatView(App $a, $update = 0)
networkPager($a, $pager, $update);
$item_params = ['order' => ['id' => true]];
if (strlen($file)) {
$term_condition = ["`term` = ? AND `otype` = ? AND `type` = ? AND `uid` = ?",
$file, Term::OBJECT_TYPE_POST, Term::FILE, local_user()];
$term_params = ['order' => ['tid' => true], 'limit' => [$pager->getStart(), $pager->getItemsPerPage()]];
$result = DBA::select('term', ['oid'], $term_condition, $term_params);
$item_params = ['order' => ['uri-id' => true]];
$term_condition = ['name' => $file, 'type' => Category::FILE, 'uid' => local_user()];
$term_params = ['order' => ['uri-id' => true], 'limit' => [$pager->getStart(), $pager->getItemsPerPage()]];
$result = DBA::select('category-view', ['uri-id'], $term_condition, $term_params);
$posts = [];
while ($term = DBA::fetch($result)) {
$posts[] = $term['oid'];
$posts[] = $term['uri-id'];
}
DBA::close($result);
if (count($posts) == 0) {
return '';
}
$item_condition = ['uid' => local_user(), 'id' => $posts];
$item_condition = ['uid' => local_user(), 'uri-id' => $posts];
} else {
$item_params = ['order' => ['id' => true]];
$item_condition = ['uid' => local_user()];
$item_params['limit'] = [$pager->getStart(), $pager->getItemsPerPage()];
@ -466,12 +466,12 @@ function networkThreadedView(App $a, $update, $parent)
$o = '';
$cid = intval($_GET['cid'] ?? 0);
$star = intval($_GET['star'] ?? 0);
$bmark = intval($_GET['bmark'] ?? 0);
$conv = intval($_GET['conv'] ?? 0);
$cid = intval($_GET['contactid'] ?? 0);
$star = intval($_GET['star'] ?? 0);
$bmark = intval($_GET['bmark'] ?? 0);
$conv = intval($_GET['conv'] ?? 0);
$order = Strings::escapeTags(($_GET['order'] ?? '') ?: 'activity');
$nets = $_GET['nets'] ?? '';
$nets = $_GET['nets'] ?? '';
$allowedCids = [];
if ($cid) {
@ -709,7 +709,7 @@ function networkThreadedView(App $a, $update, $parent)
}
if ($order === 'post') {
// Only show toplevel posts when updating posts in this order mode
$sql_extra4 .= " AND `item`.`id` = `item`.`parent`";
$sql_extra4 .= " AND `item`.`gravity` = " . GRAVITY_PARENT;
}
}
@ -786,15 +786,21 @@ function networkThreadedView(App $a, $update, $parent)
$top_limit = DateTimeFormat::utcNow();
}
// Handle bad performance situations when the distance between top and bottom is too high
// See issue https://github.com/friendica/friendica/issues/8619
if (strtotime($top_limit) - strtotime($bottom_limit) > 86400) {
// Set the bottom limit to one day in the past at maximum
$bottom_limit = DateTimeFormat::utc(date('c', strtotime($top_limit) - 86400));
}
$items = DBA::p("SELECT `item`.`parent-uri` AS `uri`, 0 AS `item_id`, `item`.$ordering AS `order_date`, `author`.`url` AS `author-link` FROM `item`
STRAIGHT_JOIN (SELECT `oid` FROM `term` WHERE `term` IN
(SELECT SUBSTR(`term`, 2) FROM `search` WHERE `uid` = ? AND `term` LIKE '#%') AND `otype` = ? AND `type` = ? AND `uid` = 0) AS `term`
ON `item`.`id` = `term`.`oid`
STRAIGHT_JOIN (SELECT `uri-id` FROM `tag-search-view` WHERE `name` IN
(SELECT SUBSTR(`term`, 2) FROM `search` WHERE `uid` = ? AND `term` LIKE '#%') AND `uid` = 0) AS `tag-search`
ON `item`.`uri-id` = `tag-search`.`uri-id`
STRAIGHT_JOIN `contact` AS `author` ON `author`.`id` = `item`.`author-id`
WHERE `item`.`uid` = 0 AND `item`.$ordering < ? AND `item`.$ordering > ? AND `item`.`gravity` = ?
AND NOT `author`.`hidden` AND NOT `author`.`blocked`" . $sql_tag_nets,
local_user(), TERM_OBJ_POST, TERM_HASHTAG,
$top_limit, $bottom_limit, GRAVITY_PARENT);
local_user(), $top_limit, $bottom_limit, GRAVITY_PARENT);
$data = DBA::toArray($items);
@ -892,8 +898,8 @@ function network_tabs(App $a)
$cmd = DI::args()->getCommand();
$def_param = [];
if (!empty($_GET['cid'])) {
$def_param['cid'] = $_GET['cid'];
if (!empty($_GET['contactid'])) {
$def_param['contactid'] = $_GET['contactid'];
}
// tabs

View file

@ -48,7 +48,6 @@ function ostatus_subscribe_content(App $a)
}
$contact = Probe::uri($_REQUEST['url']);
if (!$contact) {
DI::pConfig()->delete($uid, 'ostatus', 'legacy_contact');
return $o . DI::l10n()->t('Couldn\'t fetch information for contact.');
@ -91,7 +90,7 @@ function ostatus_subscribe_content(App $a)
$probed = Probe::uri($url);
if ($probed['network'] == Protocol::OSTATUS) {
$result = Contact::createFromProbe($uid, $url, true, Protocol::OSTATUS);
$result = Contact::createFromProbe($a->user, $probed['url'], true, Protocol::OSTATUS);
if ($result['success']) {
$o .= ' - ' . DI::l10n()->t('success');
} else {

View file

@ -36,6 +36,7 @@ use Friendica\Model\Contact;
use Friendica\Model\Item;
use Friendica\Model\Photo;
use Friendica\Model\Profile;
use Friendica\Model\Tag;
use Friendica\Model\User;
use Friendica\Module\BaseProfile;
use Friendica\Network\Probe;
@ -81,7 +82,7 @@ function photos_init(App $a) {
'$photo' => $profile['photo'],
'$addr' => $profile['addr'] ?? '',
'$account_type' => $account_type,
'$about' => BBCode::convert($profile['about'] ?? ''),
'$about' => BBCode::convert($profile['about']),
]);
$albums = Photo::getAlbums($a->data['user']['uid']);
@ -309,7 +310,7 @@ function photos_post(App $a)
$desc = !empty($_POST['desc']) ? Strings::escapeTags(trim($_POST['desc'])) : '';
$rawtags = !empty($_POST['newtag']) ? Strings::escapeTags(trim($_POST['newtag'])) : '';
$item_id = !empty($_POST['item_id']) ? intval($_POST['item_id']) : 0;
$albname = !empty($_POST['albname']) ? Strings::escapeTags(trim($_POST['albname'])) : '';
$albname = !empty($_POST['albname']) ? trim($_POST['albname']) : '';
$origaname = !empty($_POST['origaname']) ? Strings::escapeTags(trim($_POST['origaname'])) : '';
$aclFormatter = DI::aclFormatter();
@ -421,16 +422,14 @@ function photos_post(App $a)
}
if ($item_id) {
$item = Item::selectFirst(['tag', 'inform'], ['id' => $item_id, 'uid' => $page_owner_uid]);
$item = Item::selectFirst(['tag', 'inform', 'uri-id'], ['id' => $item_id, 'uid' => $page_owner_uid]);
if (DBA::isResult($item)) {
$old_tag = $item['tag'];
$old_inform = $item['inform'];
}
}
if (strlen($rawtags)) {
$str_tags = '';
$inform = '';
// if the new tag doesn't have a namespace specifier (@foo or #foo) give it a hashtag
@ -510,38 +509,33 @@ function photos_post(App $a)
if ($profile) {
if (!empty($contact)) {
$taginfo[] = [$newname, $profile, $notify, $contact, '@[url=' . str_replace(',', '%2c', $profile) . ']' . $newname . '[/url]'];
$taginfo[] = [$newname, $profile, $notify, $contact];
} else {
$taginfo[] = [$newname, $profile, $notify, null, $str_tags .= '@[url=' . $profile . ']' . $newname . '[/url]'];
}
if (strlen($str_tags)) {
$str_tags .= ',';
$taginfo[] = [$newname, $profile, $notify, null];
}
$profile = str_replace(',', '%2c', $profile);
$str_tags .= '@[url=' . $profile . ']' . $newname . '[/url]';
if (!empty($item['uri-id'])) {
Tag::store($item['uri-id'], Tag::MENTION, $newname, $profile);
}
}
} elseif (strpos($tag, '#') === 0) {
$tagname = substr($tag, 1);
$str_tags .= '#[url=' . DI::baseUrl() . "/search?tag=" . $tagname . ']' . $tagname . '[/url],';
if (!empty($item['uri-id'])) {
Tag::store($item['uri-id'], Tag::HASHTAG, $tagname);
}
}
}
}
$newtag = $old_tag ?? '';
if (strlen($newtag) && strlen($str_tags)) {
$newtag .= ',';
}
$newtag .= $str_tags;
$newinform = $old_inform ?? '';
if (strlen($newinform) && strlen($inform)) {
$newinform .= ',';
}
$newinform .= $inform;
$fields = ['tag' => $newtag, 'inform' => $newinform, 'edited' => DateTimeFormat::utcNow(), 'changed' => DateTimeFormat::utcNow()];
$fields = ['inform' => $newinform, 'edited' => DateTimeFormat::utcNow(), 'changed' => DateTimeFormat::utcNow()];
$condition = ['id' => $item_id];
Item::update($fields, $condition);
@ -585,7 +579,6 @@ function photos_post(App $a)
$arr['gravity'] = GRAVITY_PARENT;
$arr['object-type'] = Activity\ObjectType::PERSON;
$arr['target-type'] = Activity\ObjectType::IMAGE;
$arr['tag'] = $tagged[4];
$arr['inform'] = $tagged[2];
$arr['origin'] = 1;
$arr['body'] = DI::l10n()->t('%1$s was tagged in %2$s by %3$s', '[url=' . $tagged[1] . ']' . $tagged[0] . '[/url]', '[url=' . DI::baseUrl() . '/photos/' . $owner_record['nickname'] . '/image/' . $photo['resource-id'] . ']' . DI::l10n()->t('a photo') . '[/url]', '[url=' . $owner_record['url'] . ']' . $owner_record['name'] . '[/url]') ;
@ -615,10 +608,10 @@ function photos_post(App $a)
Hook::callAll('photo_post_init', $_POST);
// Determine the album to use
$album = !empty($_REQUEST['album']) ? Strings::escapeTags(trim($_REQUEST['album'])) : '';
$newalbum = !empty($_REQUEST['newalbum']) ? Strings::escapeTags(trim($_REQUEST['newalbum'])) : '';
$album = trim($_REQUEST['album'] ?? '');
$newalbum = trim($_REQUEST['newalbum'] ?? '');
Logger::log('mod/photos.php: photos_post(): album= ' . $album . ' newalbum= ' . $newalbum , Logger::DEBUG);
Logger::info('album= ' . $album . ' newalbum= ' . $newalbum);
if (!strlen($album)) {
if (strlen($newalbum)) {
@ -706,9 +699,7 @@ function photos_post(App $a)
return;
}
if ($type == "") {
$type = Images::guessType($filename);
}
$type = Images::getMimeTypeBySource($src, $filename, $type);
Logger::log('photos: upload: received file: ' . $filename . ' as ' . $src . ' ('. $type . ') ' . $filesize . ' bytes', Logger::DEBUG);
@ -787,7 +778,7 @@ function photos_post(App $a)
// Create item container
$lat = $lon = null;
if ($exif && $exif['GPS'] && Feature::isEnabled($page_owner_uid, 'photo_location')) {
if (!empty($exif['GPS']) && Feature::isEnabled($page_owner_uid, 'photo_location')) {
$lat = Photo::getGps($exif['GPS']['GPSLatitude'], $exif['GPS']['GPSLatitudeRef']);
$lon = Photo::getGps($exif['GPS']['GPSLongitude'], $exif['GPS']['GPSLongitudeRef']);
}
@ -1296,7 +1287,7 @@ function photos_content(App $a)
}
if (!empty($link_item['parent']) && !empty($link_item['uid'])) {
$condition = ["`parent` = ? AND `parent` != `id`", $link_item['parent']];
$condition = ["`parent` = ? AND `gravity` != ?", $link_item['parent'], GRAVITY_PARENT];
$total = DBA::count('item', $condition);
$pager = new Pager(DI::l10n(), DI::args()->getQueryString());
@ -1316,8 +1307,9 @@ function photos_content(App $a)
$tags = null;
if (!empty($link_item['id']) && !empty($link_item['tag'])) {
$arr = explode(',', $link_item['tag']);
if (!empty($link_item['id'])) {
$tag_text = Tag::getCSVByURIId($link_item['uri-id']);
$arr = explode(',', $tag_text);
// parse tags and add links
$tag_arr = [];
foreach ($arr as $tag) {
@ -1464,7 +1456,7 @@ function photos_content(App $a)
if (($activity->match($item['verb'], Activity::LIKE) ||
$activity->match($item['verb'], Activity::DISLIKE)) &&
($item['id'] != $item['parent'])) {
($item['gravity'] != GRAVITY_PARENT)) {
continue;
}

View file

@ -30,6 +30,8 @@ use Friendica\Model\Contact;
use Friendica\Model\Group;
use Friendica\Model\Item;
use Friendica\Model\Notify\Type;
use Friendica\Model\Verb;
use Friendica\Protocol\Activity;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Temporal;
use Friendica\Util\Proxy as ProxyUtils;
@ -134,9 +136,10 @@ function ping_init(App $a)
$notifs = ping_get_notifications(local_user());
$condition = ["`unseen` AND `uid` = ? AND `contact-id` != ?", local_user(), local_user()];
$condition = ["`unseen` AND `uid` = ? AND `contact-id` != ? AND (`vid` != ? OR `vid` IS NULL)",
local_user(), local_user(), Verb::getID(Activity::FOLLOW)];
$fields = ['id', 'parent', 'verb', 'author-name', 'unseen', 'author-link', 'author-avatar', 'contact-avatar',
'network', 'created', 'object', 'parent-author-name', 'parent-author-link', 'parent-guid', 'wall'];
'network', 'created', 'object', 'parent-author-name', 'parent-author-link', 'parent-guid', 'wall', 'activity'];
$params = ['order' => ['received' => true]];
$items = Item::selectForUser(local_user(), $fields, $condition, $params);
@ -464,13 +467,13 @@ function ping_get_notifications($uid)
if ($notification["visible"]
&& !$notification["deleted"]
&& empty($result[$notification["parent"]])
&& empty($result[$notification['parent']])
) {
// Should we condense the notifications or show them all?
if (DI::pConfig()->get(local_user(), 'system', 'detailed_notif')) {
$result[$notification["id"]] = $notification;
} else {
$result[$notification["parent"]] = $notification;
$result[$notification['parent']] = $notification;
}
}
}

View file

@ -81,10 +81,7 @@ function poco_init(App $a) {
}
if (!$system_mode && !$global) {
$user = DBA::fetchFirst("SELECT `user`.`uid`, `user`.`nickname` FROM `user`
INNER JOIN `profile` ON `user`.`uid` = `profile`.`uid`
WHERE `user`.`nickname` = ? AND NOT `profile`.`hide-friends`",
$nickname);
$user = DBA::selectFirst('owner-view', ['uid', 'nickname'], ['nickname' => $nickname, 'hide-friends' => false]);
if (!DBA::isResult($user)) {
throw new \Friendica\Network\HTTPException\NotFoundException();
}
@ -147,16 +144,7 @@ function poco_init(App $a) {
);
} elseif ($system_mode) {
Logger::log("Start system mode query", Logger::DEBUG);
$contacts = q("SELECT `contact`.*, `profile`.`about` AS `pabout`, `profile`.`locality` AS `plocation`, `profile`.`pub_keywords`,
`profile`.`address` AS `paddress`, `profile`.`region` AS `pregion`,
`profile`.`postal-code` AS `ppostalcode`, `profile`.`country-name` AS `pcountry`, `user`.`account-type`
FROM `contact` INNER JOIN `profile` ON `profile`.`uid` = `contact`.`uid`
INNER JOIN `user` ON `user`.`uid` = `contact`.`uid`
WHERE `self` = 1 AND `profile`.`net-publish`
LIMIT %d, %d",
intval($startIndex),
intval($itemsPerPage)
);
$contacts = DBA::selectToArray('owner-view', [], ['net-publish' => true], ['limit' => [$startIndex, $itemsPerPage]]);
} else {
Logger::log("Start query for user " . $user['nickname'], Logger::DEBUG);
$contacts = q("SELECT * FROM `contact` WHERE `uid` = %d AND `blocked` = 0 AND `pending` = 0 AND `hidden` = 0 AND `archive` = 0
@ -216,164 +204,142 @@ function poco_init(App $a) {
}
}
if (is_array($contacts)) {
if (DBA::isResult($contacts)) {
foreach ($contacts as $contact) {
if (!isset($contact['updated'])) {
$contact['updated'] = '';
}
if (!is_array($contacts)) {
throw new \Friendica\Network\HTTPException\InternalServerErrorException();
}
if (! isset($contact['generation'])) {
if ($global) {
$contact['generation'] = 3;
} elseif ($system_mode) {
$contact['generation'] = 1;
} else {
$contact['generation'] = 2;
}
}
if (($contact['about'] == "") && isset($contact['pabout'])) {
$contact['about'] = $contact['pabout'];
}
if ($contact['location'] == "") {
if (isset($contact['plocation'])) {
$contact['location'] = $contact['plocation'];
}
if (isset($contact['pregion']) && ( $contact['pregion'] != "")) {
if ($contact['location'] != "") {
$contact['location'] .= ", ";
}
$contact['location'] .= $contact['pregion'];
}
if (isset($contact['pcountry']) && ( $contact['pcountry'] != "")) {
if ($contact['location'] != "") {
$contact['location'] .= ", ";
}
$contact['location'] .= $contact['pcountry'];
}
}
if (($contact['keywords'] == "") && isset($contact['pub_keywords'])) {
$contact['keywords'] = $contact['pub_keywords'];
}
if (isset($contact['account-type'])) {
$contact['contact-type'] = $contact['account-type'];
}
$about = DI::cache()->get("about:" . $contact['updated'] . ":" . $contact['nurl']);
if (is_null($about)) {
$about = BBCode::convert($contact['about'], false);
DI::cache()->set("about:" . $contact['updated'] . ":" . $contact['nurl'], $about);
}
// Non connected persons can only see the keywords of a Diaspora account
if ($contact['network'] == Protocol::DIASPORA) {
$contact['location'] = "";
$about = "";
}
$entry = [];
if ($fields_ret['id']) {
$entry['id'] = (int)$contact['id'];
}
if ($fields_ret['displayName']) {
$entry['displayName'] = $contact['name'];
}
if ($fields_ret['aboutMe']) {
$entry['aboutMe'] = $about;
}
if ($fields_ret['currentLocation']) {
$entry['currentLocation'] = $contact['location'];
}
if ($fields_ret['generation']) {
$entry['generation'] = (int)$contact['generation'];
}
if ($fields_ret['urls']) {
$entry['urls'] = [['value' => $contact['url'], 'type' => 'profile']];
if ($contact['addr'] && ($contact['network'] !== Protocol::MAIL)) {
$entry['urls'][] = ['value' => 'acct:' . $contact['addr'], 'type' => 'webfinger'];
}
}
if ($fields_ret['preferredUsername']) {
$entry['preferredUsername'] = $contact['nick'];
}
if ($fields_ret['updated']) {
if (! $global) {
$entry['updated'] = $contact['success_update'];
if ($contact['name-date'] > $entry['updated']) {
$entry['updated'] = $contact['name-date'];
}
if ($contact['uri-date'] > $entry['updated']) {
$entry['updated'] = $contact['uri-date'];
}
if ($contact['avatar-date'] > $entry['updated']) {
$entry['updated'] = $contact['avatar-date'];
}
} else {
$entry['updated'] = $contact['updated'];
}
$entry['updated'] = date("c", strtotime($entry['updated']));
}
if ($fields_ret['photos']) {
$entry['photos'] = [['value' => $contact['photo'], 'type' => 'profile']];
}
if ($fields_ret['network']) {
$entry['network'] = $contact['network'];
if ($entry['network'] == Protocol::STATUSNET) {
$entry['network'] = Protocol::OSTATUS;
}
if (($entry['network'] == "") && ($contact['self'])) {
$entry['network'] = Protocol::DFRN;
}
}
if ($fields_ret['tags']) {
$tags = str_replace(",", " ", $contact['keywords']);
$tags = explode(" ", $tags);
$cleaned = [];
foreach ($tags as $tag) {
$tag = trim(strtolower($tag));
if ($tag != "") {
$cleaned[] = $tag;
}
}
$entry['tags'] = [$cleaned];
}
if ($fields_ret['address']) {
$entry['address'] = [];
// Deactivated. It just reveals too much data. (Although its from the default profile)
//if (isset($rr['paddress']))
// $entry['address']['streetAddress'] = $rr['paddress'];
if (isset($contact['plocation'])) {
$entry['address']['locality'] = $contact['plocation'];
}
if (isset($contact['pregion'])) {
$entry['address']['region'] = $contact['pregion'];
}
// See above
//if (isset($rr['ppostalcode']))
// $entry['address']['postalCode'] = $rr['ppostalcode'];
if (isset($contact['pcountry'])) {
$entry['address']['country'] = $contact['pcountry'];
}
}
if ($fields_ret['contactType']) {
$entry['contactType'] = intval($contact['contact-type']);
}
$ret['entry'][] = $entry;
if (DBA::isResult($contacts)) {
foreach ($contacts as $contact) {
if (!isset($contact['updated'])) {
$contact['updated'] = '';
}
} else {
$ret['entry'][] = [];
if (! isset($contact['generation'])) {
if ($global) {
$contact['generation'] = 3;
} elseif ($system_mode) {
$contact['generation'] = 1;
} else {
$contact['generation'] = 2;
}
}
if (($contact['keywords'] == "") && isset($contact['pub_keywords'])) {
$contact['keywords'] = $contact['pub_keywords'];
}
if (isset($contact['account-type'])) {
$contact['contact-type'] = $contact['account-type'];
}
$about = DI::cache()->get("about:" . $contact['updated'] . ":" . $contact['nurl']);
if (is_null($about)) {
$about = BBCode::convert($contact['about'], false);
DI::cache()->set("about:" . $contact['updated'] . ":" . $contact['nurl'], $about);
}
// Non connected persons can only see the keywords of a Diaspora account
if ($contact['network'] == Protocol::DIASPORA) {
$contact['location'] = "";
$about = "";
}
$entry = [];
if ($fields_ret['id']) {
$entry['id'] = (int)$contact['id'];
}
if ($fields_ret['displayName']) {
$entry['displayName'] = $contact['name'];
}
if ($fields_ret['aboutMe']) {
$entry['aboutMe'] = $about;
}
if ($fields_ret['currentLocation']) {
$entry['currentLocation'] = $contact['location'];
}
if ($fields_ret['generation']) {
$entry['generation'] = (int)$contact['generation'];
}
if ($fields_ret['urls']) {
$entry['urls'] = [['value' => $contact['url'], 'type' => 'profile']];
if ($contact['addr'] && ($contact['network'] !== Protocol::MAIL)) {
$entry['urls'][] = ['value' => 'acct:' . $contact['addr'], 'type' => 'webfinger'];
}
}
if ($fields_ret['preferredUsername']) {
$entry['preferredUsername'] = $contact['nick'];
}
if ($fields_ret['updated']) {
if (! $global) {
$entry['updated'] = $contact['success_update'];
if ($contact['name-date'] > $entry['updated']) {
$entry['updated'] = $contact['name-date'];
}
if ($contact['uri-date'] > $entry['updated']) {
$entry['updated'] = $contact['uri-date'];
}
if ($contact['avatar-date'] > $entry['updated']) {
$entry['updated'] = $contact['avatar-date'];
}
} else {
$entry['updated'] = $contact['updated'];
}
$entry['updated'] = date("c", strtotime($entry['updated']));
}
if ($fields_ret['photos']) {
$entry['photos'] = [['value' => $contact['photo'], 'type' => 'profile']];
}
if ($fields_ret['network']) {
$entry['network'] = $contact['network'];
if ($entry['network'] == Protocol::STATUSNET) {
$entry['network'] = Protocol::OSTATUS;
}
if (($entry['network'] == "") && ($contact['self'])) {
$entry['network'] = Protocol::DFRN;
}
}
if ($fields_ret['tags']) {
$tags = str_replace(",", " ", $contact['keywords']);
$tags = explode(" ", $tags);
$cleaned = [];
foreach ($tags as $tag) {
$tag = trim(strtolower($tag));
if ($tag != "") {
$cleaned[] = $tag;
}
}
$entry['tags'] = [$cleaned];
}
if ($fields_ret['address']) {
$entry['address'] = [];
// Deactivated. It just reveals too much data. (Although its from the default profile)
//if (isset($rr['address']))
// $entry['address']['streetAddress'] = $rr['address'];
if (isset($contact['locality'])) {
$entry['address']['locality'] = $contact['locality'];
}
if (isset($contact['region'])) {
$entry['address']['region'] = $contact['region'];
}
// See above
//if (isset($rr['postal-code']))
// $entry['address']['postalCode'] = $rr['postal-code'];
if (isset($contact['country'])) {
$entry['address']['country'] = $contact['country'];
}
}
if ($fields_ret['contactType']) {
$entry['contactType'] = intval($contact['contact-type']);
}
$ret['entry'][] = $entry;
}
} else {
throw new \Friendica\Network\HTTPException\InternalServerErrorException();
$ret['entry'][] = [];
}
Logger::log("End of poco", Logger::DEBUG);

View file

@ -1,191 +0,0 @@
<?php
/**
* Poke, prod, finger, or otherwise do unspeakable things to somebody - who must be a connection in your address book
* This function can be invoked with the required arguments (verb and cid and private and possibly parent) silently via ajax or
* other web request. You must be logged in and connected to a profile.
* If the required arguments aren't present, we'll display a simple form to choose a recipient and a verb.
* parent is a special argument which let's you attach this activity as a comment to an existing conversation, which
* may have started with somebody else poking (etc.) somebody, but this isn't necessary. This can be used in the more pokes
* addon version to have entire conversations where Alice poked Bob, Bob fingered Alice, Alice hugged Bob, etc.
*
* private creates a private conversation with the recipient. Otherwise your profile's default post privacy is used.
*
* @file mod/poke.php
*/
use Friendica\App;
use Friendica\Core\Hook;
use Friendica\Core\Logger;
use Friendica\Core\Renderer;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Item;
use Friendica\Protocol\Activity;
use Friendica\Util\Strings;
use Friendica\Util\XML;
function poke_init(App $a)
{
if (!local_user()) {
return;
}
$uid = local_user();
if (empty($_GET['verb'])) {
return;
}
$verb = Strings::escapeTags(trim($_GET['verb']));
$verbs = DI::l10n()->getPokeVerbs();
if (!array_key_exists($verb, $verbs)) {
return;
}
$activity = Activity::POKE . '#' . urlencode($verbs[$verb][0]);
$contact_id = intval($_GET['cid']);
if (!$contact_id) {
return;
}
$parent = (!empty($_GET['parent']) ? intval($_GET['parent']) : 0);
Logger::log('poke: verb ' . $verb . ' contact ' . $contact_id, Logger::DEBUG);
$r = q("SELECT * FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
intval($contact_id),
intval($uid)
);
if (!DBA::isResult($r)) {
Logger::log('poke: no contact ' . $contact_id);
return;
}
$target = $r[0];
if ($parent) {
$fields = ['uri', 'private', 'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid'];
$condition = ['id' => $parent, 'parent' => $parent, 'uid' => $uid];
$item = Item::selectFirst($fields, $condition);
if (DBA::isResult($item)) {
$parent_uri = $item['uri'];
$private = $item['private'];
$allow_cid = $item['allow_cid'];
$allow_gid = $item['allow_gid'];
$deny_cid = $item['deny_cid'];
$deny_gid = $item['deny_gid'];
}
} else {
$private = (!empty($_GET['private']) ? intval($_GET['private']) : Item::PUBLIC);
$allow_cid = ($private ? '<' . $target['id']. '>' : $a->user['allow_cid']);
$allow_gid = ($private ? '' : $a->user['allow_gid']);
$deny_cid = ($private ? '' : $a->user['deny_cid']);
$deny_gid = ($private ? '' : $a->user['deny_gid']);
}
$poster = $a->contact;
$uri = Item::newURI($uid);
$arr = [];
$arr['guid'] = System::createUUID();
$arr['uid'] = $uid;
$arr['uri'] = $uri;
$arr['parent-uri'] = (!empty($parent_uri) ? $parent_uri : $uri);
$arr['wall'] = 1;
$arr['contact-id'] = $poster['id'];
$arr['owner-name'] = $poster['name'];
$arr['owner-link'] = $poster['url'];
$arr['owner-avatar'] = $poster['thumb'];
$arr['author-name'] = $poster['name'];
$arr['author-link'] = $poster['url'];
$arr['author-avatar'] = $poster['thumb'];
$arr['title'] = '';
$arr['allow_cid'] = $allow_cid;
$arr['allow_gid'] = $allow_gid;
$arr['deny_cid'] = $deny_cid;
$arr['deny_gid'] = $deny_gid;
$arr['visible'] = 1;
$arr['verb'] = $activity;
$arr['private'] = $private;
$arr['object-type'] = Activity\ObjectType::PERSON;
$arr['origin'] = 1;
$arr['body'] = '[url=' . $poster['url'] . ']' . $poster['name'] . '[/url]' . ' ' . DI::l10n()->t($verbs[$verb][0]) . ' ' . '[url=' . $target['url'] . ']' . $target['name'] . '[/url]';
$arr['object'] = '<object><type>' . Activity\ObjectType::PERSON . '</type><title>' . $target['name'] . '</title><id>' . $target['url'] . '</id>';
$arr['object'] .= '<link>' . XML::escape('<link rel="alternate" type="text/html" href="' . $target['url'] . '" />' . "\n");
$arr['object'] .= XML::escape('<link rel="photo" type="image/jpeg" href="' . $target['photo'] . '" />' . "\n");
$arr['object'] .= '</link></object>' . "\n";
Item::insert($arr);
Hook::callAll('post_local_end', $arr);
return;
}
function poke_content(App $a)
{
if (!local_user()) {
notice(DI::l10n()->t('Permission denied.') . EOL);
return;
}
if (empty($_GET['c'])) {
return;
}
$contact = DBA::selectFirst('contact', ['id', 'name'], ['id' => $_GET['c'], 'uid' => local_user()]);
if (!DBA::isResult($contact)) {
return;
}
$name = $contact['name'];
$id = $contact['id'];
$head_tpl = Renderer::getMarkupTemplate('poke_head.tpl');
DI::page()['htmlhead'] .= Renderer::replaceMacros($head_tpl,[
'$baseurl' => DI::baseUrl()->get(true),
]);
$parent = (!empty($_GET['parent']) ? intval($_GET['parent']) : '0');
$verbs = DI::l10n()->getPokeVerbs();
$shortlist = [];
foreach ($verbs as $k => $v) {
if ($v[1] !== 'NOTRANSLATION') {
$shortlist[] = [$k, $v[1]];
}
}
$tpl = Renderer::getMarkupTemplate('poke_content.tpl');
$o = Renderer::replaceMacros($tpl,[
'$title' => DI::l10n()->t('Poke/Prod'),
'$desc' => DI::l10n()->t('poke, prod or do other things to somebody'),
'$clabel' => DI::l10n()->t('Recipient'),
'$choice' => DI::l10n()->t('Choose what you wish to do to recipient'),
'$verbs' => $shortlist,
'$parent' => $parent,
'$prv_desc' => DI::l10n()->t('Make this post private'),
'$submit' => DI::l10n()->t('Submit'),
'$name' => $name,
'$id' => $id
]);
return $o;
}

View file

@ -31,6 +31,9 @@ use Friendica\Util\Network;
use Friendica\Util\Strings;
function redir_init(App $a) {
if (!Session::isAuthenticated()) {
throw new \Friendica\Network\HTTPException\ForbiddenException(DI::l10n()->t('Access denied.'));
}
$url = $_GET['url'] ?? '';
$quiet = !empty($_GET['quiet']) ? '&quiet=1' : '';
@ -44,102 +47,102 @@ function redir_init(App $a) {
// Try magic auth before the legacy stuff
redir_magic($a, $cid, $url);
if (!empty($cid)) {
$fields = ['id', 'uid', 'nurl', 'url', 'addr', 'name', 'network', 'poll', 'issued-id', 'dfrn-id', 'duplex', 'pending'];
$contact = DBA::selectFirst('contact', $fields, ['id' => $cid, 'uid' => [0, local_user()]]);
if (!DBA::isResult($contact)) {
notice(DI::l10n()->t('Contact not found.'));
DI::baseUrl()->redirect();
if (empty($cid)) {
throw new \Friendica\Network\HTTPException\BadRequestException(DI::l10n()->t('Bad Request.'));
}
$fields = ['id', 'uid', 'nurl', 'url', 'addr', 'name', 'network', 'poll', 'issued-id', 'dfrn-id', 'duplex', 'pending'];
$contact = DBA::selectFirst('contact', $fields, ['id' => $cid, 'uid' => [0, local_user()]]);
if (!DBA::isResult($contact)) {
throw new \Friendica\Network\HTTPException\NotFoundException(DI::l10n()->t('Contact not found.'));
}
$contact_url = $contact['url'];
if (!empty($a->contact['id']) && $a->contact['id'] == $cid) {
// Local user is already authenticated.
redir_check_url($contact_url, $url);
$a->redirect($url ?: $contact_url);
}
if ($contact['uid'] == 0 && local_user()) {
// Let's have a look if there is an established connection
// between the public contact we have found and the local user.
$contact = DBA::selectFirst('contact', $fields, ['nurl' => $contact['nurl'], 'uid' => local_user()]);
if (DBA::isResult($contact)) {
$cid = $contact['id'];
}
$contact_url = $contact['url'];
if (!empty($a->contact['id']) && $a->contact['id'] == $cid) {
// Local user is already authenticated.
redir_check_url($contact_url, $url);
$target_url = $url ?: $contact_url;
Logger::log($contact['name'] . " is already authenticated. Redirecting to " . $target_url, Logger::DEBUG);
$a->redirect($target_url);
}
}
if (!Session::isAuthenticated() // Visitors (not logged in or not remotes) can't authenticate.
|| (!empty($a->contact['id']) && $a->contact['id'] == $cid)) // Local user is already authenticated.
{
$a->redirect($url ?: $contact_url);
if (remote_user()) {
$host = substr(DI::baseUrl()->getUrlPath() . (DI::baseUrl()->getUrlPath() ? '/' . DI::baseUrl()->getUrlPath() : ''), strpos(DI::baseUrl()->getUrlPath(), '://') + 3);
$remotehost = substr($contact['addr'], strpos($contact['addr'], '@') + 1);
// On a local instance we have to check if the local user has already authenticated
// with the local contact. Otherwise the local user would ask the local contact
// for authentification everytime he/she is visiting a profile page of the local
// contact.
if (($host == $remotehost) && (Session::getRemoteContactID(Session::get('visitor_visiting')) == Session::get('visitor_id'))) {
// Remote user is already authenticated.