Merge branch '2020.06-rc' into stable
This commit is contained in:
commit
dc42dbb68a
302 changed files with 78831 additions and 76304 deletions
|
|
@ -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
|
||||
|
|
|
|||
64
CHANGELOG
64
CHANGELOG
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
|||
2020.03
|
||||
2020.06-rc
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -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."
|
||||
|
|
|
|||
26
boot.php
26
boot.php
|
|
@ -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
|
||||
*
|
||||
|
|
|
|||
|
|
@ -92,6 +92,9 @@
|
|||
}
|
||||
},
|
||||
"config": {
|
||||
"platform": {
|
||||
"php": "7.0"
|
||||
},
|
||||
"autoloader-suffix": "Friendica",
|
||||
"optimize-autoloader": true,
|
||||
"preferred-install": "dist",
|
||||
|
|
|
|||
587
composer.lock
generated
587
composer.lock
generated
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
735
database.sql
735
database.sql
|
|
@ -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`;
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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] Spaces[/pre]</li>
|
||||
</ul>
|
||||
</td>
|
||||
<td> Spaces</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>[nosmile] is used to disable smilies on a post by post basis<br>
|
||||
<br>
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ Per Git:
|
|||
cd /var/www/<Pfad zu Deiner friendica-Installation>/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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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
234
include/api.php
234
include/api.php
|
|
@ -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']);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
|
|
|
|||
|
|
@ -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(["[", "]"], ["[", "]"], htmlentities($data["url"], ENT_QUOTES, 'UTF-8', false));
|
||||
$data["title"] = str_replace(["[", "]"], ["[", "]"], 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(["[", "]"], ["[", "]"], 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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
|
|
|
|||
23
mod/cal.php
23
mod/cal.php
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 '';
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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`,
|
||||
|
|
|
|||
104
mod/follow.php
104
mod/follow.php
|
|
@ -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;
|
||||
|
|
|
|||
368
mod/item.php
368
mod/item.php
|
|
@ -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 '';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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."));
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
11
mod/ping.php
11
mod/ping.php
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
302
mod/poco.php
302
mod/poco.php
|
|
@ -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);
|
||||
|
|
|
|||
191
mod/poke.php
191
mod/poke.php
|
|
@ -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;
|
||||
}
|
||||
189
mod/redir.php
189
mod/redir.php
|
|
@ -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.
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
// Doing remote auth with dfrn.
|
||||
if (local_user() && (!empty($contact['dfrn-id']) || !empty($contact['issued-id'])) && empty($contact['pending'])) {
|
||||
$dfrn_id = $orig_id = (($contact['issued-id']) ? $contact['issued-id'] : $contact['dfrn-id']);
|
||||
|
||||
if ($contact['duplex'] && $contact['issued-id']) {
|
||||
$orig_id = $contact['issued-id'];
|
||||
$dfrn_id = '1:' . $orig_id;
|
||||
}
|
||||
if ($contact['duplex'] && $contact['dfrn-id']) {
|
||||
$orig_id = $contact['dfrn-id'];
|
||||
$dfrn_id = '0:' . $orig_id;
|
||||
}
|
||||
|
||||
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()]);
|
||||
$sec = Strings::getRandomHex();
|
||||
|
||||
if (DBA::isResult($contact)) {
|
||||
$cid = $contact['id'];
|
||||
}
|
||||
$fields = ['uid' => local_user(), 'cid' => $cid, 'dfrn_id' => $dfrn_id,
|
||||
'sec' => $sec, 'expire' => time() + 45];
|
||||
DBA::insert('profile_check', $fields);
|
||||
|
||||
if (!empty($a->contact['id']) && $a->contact['id'] == $cid) {
|
||||
// Local user is already authenticated.
|
||||
$target_url = $url ?: $contact_url;
|
||||
Logger::log($contact['name'] . " is already authenticated. Redirecting to " . $target_url, Logger::DEBUG);
|
||||
$a->redirect($target_url);
|
||||
}
|
||||
}
|
||||
Logger::log('mod_redir: ' . $contact['name'] . ' ' . $sec, Logger::DEBUG);
|
||||
|
||||
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);
|
||||
$dest = (!empty($url) ? '&destination_url=' . $url : '');
|
||||
|
||||
// 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.
|
||||
$target_url = $url ?: $contact_url;
|
||||
Logger::log($contact['name'] . " is already authenticated. Redirecting to " . $target_url, Logger::DEBUG);
|
||||
$a->redirect($target_url);
|
||||
}
|
||||
}
|
||||
System::externalRedirect($contact['poll'] . '?dfrn_id=' . $dfrn_id
|
||||
. '&dfrn_version=' . DFRN_PROTOCOL_VERSION . '&type=profile&sec=' . $sec . $dest . $quiet);
|
||||
}
|
||||
|
||||
// Doing remote auth with dfrn.
|
||||
if (local_user() && (!empty($contact['dfrn-id']) || !empty($contact['issued-id'])) && empty($contact['pending'])) {
|
||||
$dfrn_id = $orig_id = (($contact['issued-id']) ? $contact['issued-id'] : $contact['dfrn-id']);
|
||||
|
||||
if ($contact['duplex'] && $contact['issued-id']) {
|
||||
$orig_id = $contact['issued-id'];
|
||||
$dfrn_id = '1:' . $orig_id;
|
||||
}
|
||||
if ($contact['duplex'] && $contact['dfrn-id']) {
|
||||
$orig_id = $contact['dfrn-id'];
|
||||
$dfrn_id = '0:' . $orig_id;
|
||||
}
|
||||
|
||||
$sec = Strings::getRandomHex();
|
||||
|
||||
$fields = ['uid' => local_user(), 'cid' => $cid, 'dfrn_id' => $dfrn_id,
|
||||
'sec' => $sec, 'expire' => time() + 45];
|
||||
DBA::insert('profile_check', $fields);
|
||||
|
||||
Logger::log('mod_redir: ' . $contact['name'] . ' ' . $sec, Logger::DEBUG);
|
||||
|
||||
$dest = (!empty($url) ? '&destination_url=' . $url : '');
|
||||
|
||||
System::externalRedirect($contact['poll'] . '?dfrn_id=' . $dfrn_id
|
||||
. '&dfrn_version=' . DFRN_PROTOCOL_VERSION . '&type=profile&sec=' . $sec . $dest . $quiet);
|
||||
}
|
||||
|
||||
$url = $url ?: $contact_url;
|
||||
if (empty($url)) {
|
||||
throw new \Friendica\Network\HTTPException\BadRequestException(DI::l10n()->t('Bad Request.'));
|
||||
}
|
||||
|
||||
// If we don't have a connected contact, redirect with
|
||||
// the 'zrl' parameter.
|
||||
if (!empty($url)) {
|
||||
$my_profile = Profile::getMyURL();
|
||||
$my_profile = Profile::getMyURL();
|
||||
|
||||
if (!empty($my_profile) && !Strings::compareLink($my_profile, $url)) {
|
||||
$separator = strpos($url, '?') ? '&' : '?';
|
||||
if (!empty($my_profile) && !Strings::compareLink($my_profile, $url)) {
|
||||
$separator = strpos($url, '?') ? '&' : '?';
|
||||
|
||||
$url .= $separator . 'zrl=' . urlencode($my_profile);
|
||||
}
|
||||
|
||||
Logger::log('redirecting to ' . $url, Logger::DEBUG);
|
||||
$a->redirect($url);
|
||||
$url .= $separator . 'zrl=' . urlencode($my_profile);
|
||||
}
|
||||
|
||||
notice(DI::l10n()->t('Contact not found.'));
|
||||
DI::baseUrl()->redirect();
|
||||
Logger::log('redirecting to ' . $url, Logger::DEBUG);
|
||||
$a->redirect($url);
|
||||
}
|
||||
|
||||
function redir_magic($a, $cid, $url)
|
||||
|
|
@ -152,15 +155,10 @@ function redir_magic($a, $cid, $url)
|
|||
$contact = DBA::selectFirst('contact', ['url'], ['id' => $cid]);
|
||||
if (!DBA::isResult($contact)) {
|
||||
Logger::info('Contact not found', ['id' => $cid]);
|
||||
// Shouldn't happen under normal conditions
|
||||
notice(DI::l10n()->t('Contact not found.'));
|
||||
if (!empty($url)) {
|
||||
System::externalRedirect($url);
|
||||
} else {
|
||||
DI::baseUrl()->redirect();
|
||||
}
|
||||
throw new \Friendica\Network\HTTPException\NotFoundException(DI::l10n()->t('Contact not found.'));
|
||||
} else {
|
||||
$contact_url = $contact['url'];
|
||||
redir_check_url($contact_url, $url);
|
||||
$target_url = $url ?: $contact_url;
|
||||
}
|
||||
|
||||
|
|
@ -184,3 +182,24 @@ function redir_magic($a, $cid, $url)
|
|||
Logger::info('No magic for contact', ['contact' => $contact_url]);
|
||||
}
|
||||
}
|
||||
|
||||
function redir_check_url(string $contact_url, string $url)
|
||||
{
|
||||
if (empty($contact_url) || empty($url)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$url_host = parse_url($url, PHP_URL_HOST);
|
||||
if (empty($url_host)) {
|
||||
$url_host = parse_url(DI::baseUrl(), PHP_URL_HOST);
|
||||
}
|
||||
|
||||
$contact_url_host = parse_url($contact_url, PHP_URL_HOST);
|
||||
|
||||
if ($url_host == $contact_url_host) {
|
||||
return;
|
||||
}
|
||||
|
||||
Logger::error('URL check host mismatch', ['contact' => $contact_url, 'url' => $url]);
|
||||
throw new \Friendica\Network\HTTPException\ForbiddenException(DI::l10n()->t('Access denied.'));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ function repair_ostatus_content(App $a) {
|
|||
|
||||
$o .= "<p>".DI::l10n()->t("Keep this window open until done.")."</p>";
|
||||
|
||||
Contact::createFromProbe($uid, $r[0]["url"], true);
|
||||
Contact::createFromProbe($a->user, $r[0]["url"], true);
|
||||
|
||||
DI::page()['htmlhead'] = '<meta http-equiv="refresh" content="1; URL=' . DI::baseUrl() . '/repair_ostatus?counter='.$counter.'">';
|
||||
|
||||
|
|
|
|||
|
|
@ -42,15 +42,11 @@ function salmon_post(App $a, $xml = '') {
|
|||
|
||||
$nick = (($a->argc > 1) ? Strings::escapeTags(trim($a->argv[1])) : '');
|
||||
|
||||
$r = q("SELECT * FROM `user` WHERE `nickname` = '%s' AND `account_expired` = 0 AND `account_removed` = 0 LIMIT 1",
|
||||
DBA::escape($nick)
|
||||
);
|
||||
if (! DBA::isResult($r)) {
|
||||
$importer = DBA::selectFirst('user', [], ['nickname' => $nick, 'account_expired' => false, 'account_removed' => false]);
|
||||
if (! DBA::isResult($importer)) {
|
||||
throw new \Friendica\Network\HTTPException\InternalServerErrorException();
|
||||
}
|
||||
|
||||
$importer = $r[0];
|
||||
|
||||
// parse the xml
|
||||
|
||||
$dom = simplexml_load_string($xml,'SimpleXMLElement',0, ActivityNamespace::SALMON_ME);
|
||||
|
|
@ -83,7 +79,7 @@ function salmon_post(App $a, $xml = '') {
|
|||
// stash away some other stuff for later
|
||||
|
||||
$type = $base->data[0]->attributes()->type[0];
|
||||
$keyhash = $base->sig[0]->attributes()->keyhash[0];
|
||||
$keyhash = $base->sig[0]->attributes()->keyhash[0] ?? '';
|
||||
$encoding = $base->encoding;
|
||||
$alg = $base->alg;
|
||||
|
||||
|
|
@ -124,7 +120,7 @@ function salmon_post(App $a, $xml = '') {
|
|||
$m = Strings::base64UrlDecode($key_info[1]);
|
||||
$e = Strings::base64UrlDecode($key_info[2]);
|
||||
|
||||
Logger::log('key details: ' . print_r($key_info,true), Logger::DEBUG);
|
||||
Logger::info('key details', ['info' => $key_info]);
|
||||
|
||||
$pubkey = Crypto::meToPem($m, $e);
|
||||
|
||||
|
|
@ -175,7 +171,7 @@ function salmon_post(App $a, $xml = '') {
|
|||
Logger::log('Author ' . $author_link . ' unknown to user ' . $importer['uid'] . '.');
|
||||
|
||||
if (DI::pConfig()->get($importer['uid'], 'system', 'ostatus_autofriend')) {
|
||||
$result = Contact::createFromProbe($importer['uid'], $author_link);
|
||||
$result = Contact::createFromProbe($importer, $author_link);
|
||||
|
||||
if ($result['success']) {
|
||||
$r = q("SELECT * FROM `contact` WHERE `network` = '%s' AND ( `url` = '%s' OR `alias` = '%s')
|
||||
|
|
|
|||
108
mod/settings.php
108
mod/settings.php
|
|
@ -183,7 +183,7 @@ function settings_post(App $a)
|
|||
intval($mail_pubmail),
|
||||
intval(local_user())
|
||||
);
|
||||
Logger::log("mail: updating mailaccount. Response: ".print_r($r, true));
|
||||
Logger::notice('updating mailaccount', ['response' => $r]);
|
||||
$r = q("SELECT * FROM `mailacct` WHERE `uid` = %d LIMIT 1",
|
||||
intval(local_user())
|
||||
);
|
||||
|
|
@ -823,44 +823,11 @@ function settings_content(App $a)
|
|||
]);
|
||||
}
|
||||
|
||||
$net_pub_desc = '';
|
||||
if (strlen(DI::config()->get('system', 'directory'))) {
|
||||
$net_pub_desc = ' ' . DI::l10n()->t('Your profile will also be published in the global friendica directories (e.g. <a href="%s">%s</a>).', DI::config()->get('system', 'directory'), DI::config()->get('system', 'directory'));
|
||||
} else {
|
||||
$net_pub_desc = '';
|
||||
}
|
||||
|
||||
$profile_in_net_dir = Renderer::replaceMacros($opt_tpl, [
|
||||
'$field' => ['profile_in_netdirectory', DI::l10n()->t('Allow your profile to be searchable globally?'), $profile['net-publish'], DI::l10n()->t("Activate this setting if you want others to easily find and follow you. Your profile will be searchable on remote systems. This setting also determines whether Friendica will inform search engines that your profile should be indexed or not.") . $net_pub_desc]
|
||||
]);
|
||||
|
||||
$hide_friends = Renderer::replaceMacros($opt_tpl, [
|
||||
'$field' => ['hide-friends', DI::l10n()->t('Hide your contact/friend list from viewers of your profile?'), $profile['hide-friends'], DI::l10n()->t('A list of your contacts is displayed on your profile page. Activate this option to disable the display of your contact list.')],
|
||||
]);
|
||||
|
||||
$hide_wall = Renderer::replaceMacros($opt_tpl, [
|
||||
'$field' => ['hidewall', DI::l10n()->t('Hide your profile details from anonymous viewers?'), $a->user['hidewall'], DI::l10n()->t('Anonymous visitors will only see your profile picture, your display name and the nickname you are using on your profile page. Your public posts and replies will still be accessible by other means.')],
|
||||
]);
|
||||
|
||||
$unlisted = Renderer::replaceMacros($opt_tpl, [
|
||||
'$field' => ['unlisted', DI::l10n()->t('Make public posts unlisted'), DI::pConfig()->get(local_user(), 'system', 'unlisted'), DI::l10n()->t('Your public posts will not appear on the community pages or in search results, nor be sent to relay servers. However they can still appear on public feeds on remote servers.')],
|
||||
]);
|
||||
|
||||
$accessiblephotos = Renderer::replaceMacros($opt_tpl, [
|
||||
'$field' => ['accessible-photos', DI::l10n()->t('Make all posted pictures accessible'), DI::pConfig()->get(local_user(), 'system', 'accessible-photos'), DI::l10n()->t("This option makes every posted picture accessible via the direct link. This is a workaround for the problem that most other networks can't handle permissions on pictures. Non public pictures still won't be visible for the public on your photo albums though.")],
|
||||
]);
|
||||
|
||||
$blockwall = Renderer::replaceMacros($opt_tpl, [
|
||||
'$field' => ['blockwall', DI::l10n()->t('Allow friends to post to your profile page?'), (intval($a->user['blockwall']) ? '0' : '1'), DI::l10n()->t('Your contacts may write posts on your profile wall. These posts will be distributed to your contacts')],
|
||||
]);
|
||||
|
||||
$blocktags = Renderer::replaceMacros($opt_tpl, [
|
||||
'$field' => ['blocktags', DI::l10n()->t('Allow friends to tag your posts?'), (intval($a->user['blocktags']) ? '0' : '1'), DI::l10n()->t('Your contacts can add additional tags to your posts.')],
|
||||
]);
|
||||
|
||||
$unkmail = Renderer::replaceMacros($opt_tpl, [
|
||||
'$field' => ['unkmail', DI::l10n()->t('Permit unknown people to send you private mail?'), $unkmail, DI::l10n()->t('Friendica network users may send you private messages even if they are not in your contact list.')],
|
||||
]);
|
||||
|
||||
$tpl_addr = Renderer::getMarkupTemplate('settings/nick_set.tpl');
|
||||
|
||||
$prof_addr = Renderer::replaceMacros($tpl_addr,[
|
||||
|
|
@ -870,18 +837,6 @@ function settings_content(App $a)
|
|||
|
||||
$stpl = Renderer::getMarkupTemplate('settings/settings.tpl');
|
||||
|
||||
$expire_arr = [
|
||||
'days' => ['expire', DI::l10n()->t("Automatically expire posts after this many days:"), $expire, DI::l10n()->t('If empty, posts will not expire. Expired posts will be deleted')],
|
||||
'label' => DI::l10n()->t('Expiration settings'),
|
||||
'items' => ['expire_items', DI::l10n()->t('Expire posts'), $expire_items, DI::l10n()->t('When activated, posts and comments will be expired.')],
|
||||
'notes' => ['expire_notes', DI::l10n()->t('Expire personal notes'), $expire_notes, DI::l10n()->t('When activated, the personal notes on your profile page will be expired.')],
|
||||
'starred' => ['expire_starred', DI::l10n()->t('Expire starred posts'), $expire_starred, DI::l10n()->t('Starring posts keeps them from being expired. That behaviour is overwritten by this setting.')],
|
||||
'photos' => ['expire_photos', DI::l10n()->t('Expire photos'), $expire_photos, DI::l10n()->t('When activated, photos will be expired.')],
|
||||
'network_only' => ['expire_network_only', DI::l10n()->t('Only expire posts by others'), $expire_network_only, DI::l10n()->t('When activated, your own posts never expire. Then the settings above are only valid for posts you received.')],
|
||||
];
|
||||
|
||||
$group_select = Group::displayGroupSelection(local_user(), $a->user['def_gid']);
|
||||
|
||||
// Private/public post links for the non-JS ACL form
|
||||
$private_post = 1;
|
||||
if (!empty($_REQUEST['public']) && !$_REQUEST['public']) {
|
||||
|
|
@ -932,41 +887,32 @@ function settings_content(App $a)
|
|||
'$defloc' => ['defloc', DI::l10n()->t('Default Post Location:'), $defloc, ''],
|
||||
'$allowloc' => ['allow_location', DI::l10n()->t('Use Browser Location:'), ($a->user['allow_location'] == 1), ''],
|
||||
|
||||
'$h_prv' => DI::l10n()->t('Security and Privacy Settings'),
|
||||
'$visibility' => $profile['net-publish'],
|
||||
'$maxreq' => ['maxreq', DI::l10n()->t('Maximum Friend Requests/Day:'), $maxreq , DI::l10n()->t("\x28to prevent spam abuse\x29")],
|
||||
'$profile_in_dir' => $profile_in_dir,
|
||||
'$profile_in_net_dir' => ['profile_in_netdirectory', DI::l10n()->t('Allow your profile to be searchable globally?'), $profile['net-publish'], DI::l10n()->t("Activate this setting if you want others to easily find and follow you. Your profile will be searchable on remote systems. This setting also determines whether Friendica will inform search engines that your profile should be indexed or not.") . $net_pub_desc],
|
||||
'$hide_friends' => ['hide-friends', DI::l10n()->t('Hide your contact/friend list from viewers of your profile?'), $profile['hide-friends'], DI::l10n()->t('A list of your contacts is displayed on your profile page. Activate this option to disable the display of your contact list.')],
|
||||
'$hide_wall' => ['hidewall', DI::l10n()->t('Hide your profile details from anonymous viewers?'), $a->user['hidewall'], DI::l10n()->t('Anonymous visitors will only see your profile picture, your display name and the nickname you are using on your profile page. Your public posts and replies will still be accessible by other means.')],
|
||||
'$unlisted' => ['unlisted', DI::l10n()->t('Make public posts unlisted'), DI::pConfig()->get(local_user(), 'system', 'unlisted'), DI::l10n()->t('Your public posts will not appear on the community pages or in search results, nor be sent to relay servers. However they can still appear on public feeds on remote servers.')],
|
||||
'$accessiblephotos' => ['accessible-photos', DI::l10n()->t('Make all posted pictures accessible'), DI::pConfig()->get(local_user(), 'system', 'accessible-photos'), DI::l10n()->t("This option makes every posted picture accessible via the direct link. This is a workaround for the problem that most other networks can't handle permissions on pictures. Non public pictures still won't be visible for the public on your photo albums though.")],
|
||||
'$blockwall' => ['blockwall', DI::l10n()->t('Allow friends to post to your profile page?'), (intval($a->user['blockwall']) ? '0' : '1'), DI::l10n()->t('Your contacts may write posts on your profile wall. These posts will be distributed to your contacts')], // array('blockwall', DI::l10n()->t('Allow friends to post to your profile page:'), !$blockwall, ''),
|
||||
'$blocktags' => ['blocktags', DI::l10n()->t('Allow friends to tag your posts?'), (intval($a->user['blocktags']) ? '0' : '1'), DI::l10n()->t('Your contacts can add additional tags to your posts.')], // array('blocktags', DI::l10n()->t('Allow friends to tag your posts:'), !$blocktags, ''),
|
||||
'$unkmail' => ['unkmail', DI::l10n()->t('Permit unknown people to send you private mail?'), $unkmail, DI::l10n()->t('Friendica network users may send you private messages even if they are not in your contact list.')],
|
||||
'$cntunkmail' => ['cntunkmail', DI::l10n()->t('Maximum private messages per day from unknown people:'), $cntunkmail , DI::l10n()->t("\x28to prevent spam abuse\x29")],
|
||||
'$group_select' => Group::displayGroupSelection(local_user(), $a->user['def_gid']),
|
||||
'$permissions' => DI::l10n()->t('Default Post Permissions'),
|
||||
'$aclselect' => ACL::getFullSelectorHTML(DI::page(), $a->user),
|
||||
|
||||
'$h_prv' => DI::l10n()->t('Security and Privacy Settings'),
|
||||
|
||||
'$maxreq' => ['maxreq', DI::l10n()->t('Maximum Friend Requests/Day:'), $maxreq , DI::l10n()->t("\x28to prevent spam abuse\x29")],
|
||||
'$permissions' => DI::l10n()->t('Default Post Permissions'),
|
||||
'$permdesc' => DI::l10n()->t("\x28click to open/close\x29"),
|
||||
'$visibility' => $profile['net-publish'],
|
||||
'$aclselect' => ACL::getFullSelectorHTML(DI::page(), $a->user),
|
||||
'$blockwall'=> $blockwall, // array('blockwall', DI::l10n()->t('Allow friends to post to your profile page:'), !$blockwall, ''),
|
||||
'$blocktags'=> $blocktags, // array('blocktags', DI::l10n()->t('Allow friends to tag your posts:'), !$blocktags, ''),
|
||||
|
||||
// ACL permissions box
|
||||
'$group_perms' => DI::l10n()->t('Show to Groups'),
|
||||
'$contact_perms' => DI::l10n()->t('Show to Contacts'),
|
||||
'$private' => DI::l10n()->t('Default Private Post'),
|
||||
'$public' => DI::l10n()->t('Default Public Post'),
|
||||
'$is_private' => $private_post,
|
||||
'$return_path' => $query_str,
|
||||
'$public_link' => $public_post_link,
|
||||
'$settings_perms' => DI::l10n()->t('Default Permissions for New Posts'),
|
||||
|
||||
'$group_select' => $group_select,
|
||||
|
||||
|
||||
'$expire' => $expire_arr,
|
||||
|
||||
'$profile_in_dir' => $profile_in_dir,
|
||||
'$profile_in_net_dir' => $profile_in_net_dir,
|
||||
'$hide_friends' => $hide_friends,
|
||||
'$hide_wall' => $hide_wall,
|
||||
'$unlisted' => $unlisted,
|
||||
'$accessiblephotos' => $accessiblephotos,
|
||||
'$unkmail' => $unkmail,
|
||||
'$cntunkmail' => ['cntunkmail', DI::l10n()->t('Maximum private messages per day from unknown people:'), $cntunkmail , DI::l10n()->t("\x28to prevent spam abuse\x29")],
|
||||
|
||||
'$expire' => [
|
||||
'label' => DI::l10n()->t('Expiration settings'),
|
||||
'days' => ['expire', DI::l10n()->t("Automatically expire posts after this many days:"), $expire, DI::l10n()->t('If empty, posts will not expire. Expired posts will be deleted')],
|
||||
'items' => ['expire_items', DI::l10n()->t('Expire posts'), $expire_items, DI::l10n()->t('When activated, posts and comments will be expired.')],
|
||||
'notes' => ['expire_notes', DI::l10n()->t('Expire personal notes'), $expire_notes, DI::l10n()->t('When activated, the personal notes on your profile page will be expired.')],
|
||||
'starred' => ['expire_starred', DI::l10n()->t('Expire starred posts'), $expire_starred, DI::l10n()->t('Starring posts keeps them from being expired. That behaviour is overwritten by this setting.')],
|
||||
'photos' => ['expire_photos', DI::l10n()->t('Expire photos'), $expire_photos, DI::l10n()->t('When activated, photos will be expired.')],
|
||||
'network_only' => ['expire_network_only', DI::l10n()->t('Only expire posts by others'), $expire_network_only, DI::l10n()->t('When activated, your own posts never expire. Then the settings above are only valid for posts you received.')],
|
||||
],
|
||||
|
||||
'$h_not' => DI::l10n()->t('Notification Settings'),
|
||||
'$lbl_not' => DI::l10n()->t('Send a notification email when:'),
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@
|
|||
*/
|
||||
|
||||
use Friendica\App;
|
||||
use Friendica\Content\Text\BBCode;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\Model\Item;
|
||||
|
||||
|
|
@ -42,7 +43,7 @@ function share_init(App $a) {
|
|||
$pos = strpos($item['body'], "[share");
|
||||
$o = substr($item['body'], $pos);
|
||||
} else {
|
||||
$o = share_header($item['author-name'], $item['author-link'], $item['author-avatar'], $item['guid'], $item['created'], $item['plink']);
|
||||
$o = BBCode::getShareOpeningTag($item['author-name'], $item['author-link'], $item['author-avatar'], $item['plink'], $item['created'], $item['guid']);
|
||||
|
||||
if ($item['title']) {
|
||||
$o .= '[h3]'.$item['title'].'[/h3]'."\n";
|
||||
|
|
@ -55,22 +56,3 @@ function share_init(App $a) {
|
|||
echo $o;
|
||||
exit();
|
||||
}
|
||||
|
||||
/// @TODO Rewrite to handle over whole record array
|
||||
function share_header($author, $profile, $avatar, $guid, $posted, $link) {
|
||||
$header = "[share author='" . str_replace(["'", "[", "]"], ["'", "[", "]"], $author).
|
||||
"' profile='" . str_replace(["'", "[", "]"], ["'", "[", "]"], $profile).
|
||||
"' avatar='" . str_replace(["'", "[", "]"], ["'", "[", "]"], $avatar);
|
||||
|
||||
if ($guid) {
|
||||
$header .= "' guid='" . str_replace(["'", "[", "]"], ["'", "[", "]"], $guid);
|
||||
}
|
||||
|
||||
if ($posted) {
|
||||
$header .= "' posted='" . str_replace(["'", "[", "]"], ["'", "[", "]"], $posted);
|
||||
}
|
||||
|
||||
$header .= "' link='" . str_replace(["'", "[", "]"], ["'", "[", "]"], $link)."']";
|
||||
|
||||
return $header;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ use Friendica\Core\Worker;
|
|||
use Friendica\Database\DBA;
|
||||
use Friendica\DI;
|
||||
use Friendica\Model\Item;
|
||||
use Friendica\Model\Tag;
|
||||
use Friendica\Protocol\Activity;
|
||||
use Friendica\Util\Strings;
|
||||
use Friendica\Util\XML;
|
||||
|
|
@ -168,47 +169,7 @@ EOT;
|
|||
Item::update(['visible' => true], ['id' => $item['id']]);
|
||||
}
|
||||
|
||||
$term_objtype = ($item['resource-id'] ? TERM_OBJ_PHOTO : TERM_OBJ_POST);
|
||||
|
||||
$t = q("SELECT count(tid) as tcount FROM term WHERE oid=%d AND term='%s'",
|
||||
intval($item['id']),
|
||||
DBA::escape($term)
|
||||
);
|
||||
|
||||
if (!$blocktags && $t[0]['tcount'] == 0) {
|
||||
q("INSERT INTO term (oid, otype, type, term, url, uid) VALUE (%d, %d, %d, '%s', '%s', %d)",
|
||||
intval($item['id']),
|
||||
$term_objtype,
|
||||
TERM_HASHTAG,
|
||||
DBA::escape($term),
|
||||
'',
|
||||
intval($owner_uid)
|
||||
);
|
||||
}
|
||||
|
||||
// if the original post is on this site, update it.
|
||||
$original_item = Item::selectFirst(['tag', 'id', 'uid'], ['origin' => true, 'uri' => $item['uri']]);
|
||||
if (DBA::isResult($original_item)) {
|
||||
$x = q("SELECT `blocktags` FROM `user` WHERE `uid`=%d LIMIT 1",
|
||||
intval($original_item['uid'])
|
||||
);
|
||||
$t = q("SELECT COUNT(`tid`) AS `tcount` FROM `term` WHERE `oid`=%d AND `term`='%s'",
|
||||
intval($original_item['id']),
|
||||
DBA::escape($term)
|
||||
);
|
||||
|
||||
if (DBA::isResult($x) && !$x[0]['blocktags'] && $t[0]['tcount'] == 0){
|
||||
q("INSERT INTO term (`oid`, `otype`, `type`, `term`, `url`, `uid`) VALUE (%d, %d, %d, '%s', '%s', %d)",
|
||||
intval($original_item['id']),
|
||||
$term_objtype,
|
||||
TERM_HASHTAG,
|
||||
DBA::escape($term),
|
||||
'',
|
||||
intval($owner_uid)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Tag::store($item['uri-id'], Tag::HASHTAG, $term);
|
||||
|
||||
$arr['id'] = $post_id;
|
||||
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ use Friendica\Content\Text\BBCode;
|
|||
use Friendica\Database\DBA;
|
||||
use Friendica\DI;
|
||||
use Friendica\Model\Item;
|
||||
use Friendica\Model\Term;
|
||||
use Friendica\Model\Tag;
|
||||
use Friendica\Util\Strings;
|
||||
|
||||
function tagrm_post(App $a)
|
||||
|
|
@ -57,29 +57,24 @@ function tagrm_post(App $a)
|
|||
* @param $tags array
|
||||
* @throws Exception
|
||||
*/
|
||||
function update_tags($item_id, $tags){
|
||||
if (empty($item_id) || empty($tags)){
|
||||
function update_tags($item_id, $tags)
|
||||
{
|
||||
if (empty($item_id) || empty($tags)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$item = Item::selectFirst(['tag'], ['id' => $item_id, 'uid' => local_user()]);
|
||||
$item = Item::selectFirst(['uri-id'], ['id' => $item_id, 'uid' => local_user()]);
|
||||
if (!DBA::isResult($item)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$old_tags = explode(',', $item['tag']);
|
||||
|
||||
foreach ($tags as $new_tag) {
|
||||
foreach ($old_tags as $index => $old_tag) {
|
||||
if (strcmp($old_tag, $new_tag) == 0) {
|
||||
unset($old_tags[$index]);
|
||||
break;
|
||||
if (preg_match_all('/([#@!])\[url\=([^\[\]]*)\]([^\[\]]*)\[\/url\]/ism', $new_tag, $results, PREG_SET_ORDER)) {
|
||||
foreach ($results as $tag) {
|
||||
Tag::removeByHash($item['uri-id'], $tag[1], $tag[3], $tag[2]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$tag_str = implode(',', $old_tags);
|
||||
Term::insertFromTagFieldByItemId($item_id, $tag_str);
|
||||
}
|
||||
|
||||
function tagrm_content(App $a)
|
||||
|
|
@ -102,15 +97,16 @@ function tagrm_content(App $a)
|
|||
// NOTREACHED
|
||||
}
|
||||
|
||||
$item = Item::selectFirst(['tag'], ['id' => $item_id, 'uid' => local_user()]);
|
||||
$item = Item::selectFirst(['uri-id'], ['id' => $item_id, 'uid' => local_user()]);
|
||||
if (!DBA::isResult($item)) {
|
||||
DI::baseUrl()->redirect($_SESSION['photo_return']);
|
||||
}
|
||||
|
||||
$arr = explode(',', $item['tag']);
|
||||
$tag_text = Tag::getCSVByURIId($item['uri-id']);
|
||||
|
||||
$arr = explode(',', $tag_text);
|
||||
|
||||
if (empty($item['tag'])) {
|
||||
if (empty($arr)) {
|
||||
DI::baseUrl()->redirect($_SESSION['photo_return']);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ function videos_init(App $a)
|
|||
'$photo' => $profile['photo'],
|
||||
'$addr' => $profile['addr'] ?? '',
|
||||
'$account_type' => $account_type,
|
||||
'$about' => BBCode::convert($profile['about'] ?? ''),
|
||||
'$about' => BBCode::convert($profile['about']),
|
||||
]);
|
||||
|
||||
// If not there, create 'aside' empty
|
||||
|
|
|
|||
|
|
@ -41,19 +41,13 @@ function wall_upload_post(App $a, $desktopmode = true)
|
|||
Logger::log("wall upload: starting new upload", Logger::DEBUG);
|
||||
|
||||
$r_json = (!empty($_GET['response']) && $_GET['response'] == 'json');
|
||||
$album = (!empty($_GET['album']) ? Strings::escapeTags(trim($_GET['album'])) : '');
|
||||
$album = trim($_GET['album'] ?? '');
|
||||
|
||||
if ($a->argc > 1) {
|
||||
if (empty($_FILES['media'])) {
|
||||
$nick = $a->argv[1];
|
||||
$r = q("SELECT `user`.*, `contact`.`id` FROM `user`
|
||||
INNER JOIN `contact` on `user`.`uid` = `contact`.`uid`
|
||||
WHERE `user`.`nickname` = '%s' AND `user`.`blocked` = 0
|
||||
AND `contact`.`self` = 1 LIMIT 1",
|
||||
DBA::escape($nick)
|
||||
);
|
||||
|
||||
if (!DBA::isResult($r)) {
|
||||
$nick = $a->argv[1];
|
||||
$user = DBA::selectFirst('owner-view', ['id', 'uid', 'nickname', 'page-flags'], ['nickname' => $nick, 'blocked' => false]);
|
||||
if (!DBA::isResult($user)) {
|
||||
if ($r_json) {
|
||||
echo json_encode(['error' => DI::l10n()->t('Invalid request.')]);
|
||||
exit();
|
||||
|
|
@ -62,12 +56,7 @@ function wall_upload_post(App $a, $desktopmode = true)
|
|||
}
|
||||
} else {
|
||||
$user_info = api_get_user($a);
|
||||
$r = q("SELECT `user`.*, `contact`.`id` FROM `user`
|
||||
INNER JOIN `contact` on `user`.`uid` = `contact`.`uid`
|
||||
WHERE `user`.`nickname` = '%s' AND `user`.`blocked` = 0
|
||||
AND `contact`.`self` = 1 LIMIT 1",
|
||||
DBA::escape($user_info['screen_name'])
|
||||
);
|
||||
$user = DBA::selectFirst('owner-view', ['id', 'uid', 'nickname', 'page-flags'], ['nickname' => $user_info['screen_name'], 'blocked' => false]);
|
||||
}
|
||||
} else {
|
||||
if ($r_json) {
|
||||
|
|
@ -83,10 +72,10 @@ function wall_upload_post(App $a, $desktopmode = true)
|
|||
$can_post = false;
|
||||
$visitor = 0;
|
||||
|
||||
$page_owner_uid = $r[0]['uid'];
|
||||
$default_cid = $r[0]['id'];
|
||||
$page_owner_nick = $r[0]['nickname'];
|
||||
$community_page = (($r[0]['page-flags'] == User::PAGE_FLAGS_COMMUNITY) ? true : false);
|
||||
$page_owner_uid = $user['uid'];
|
||||
$default_cid = $user['id'];
|
||||
$page_owner_nick = $user['nickname'];
|
||||
$community_page = (($user['page-flags'] == User::PAGE_FLAGS_COMMUNITY) ? true : false);
|
||||
|
||||
if ((local_user()) && (local_user() == $page_owner_uid)) {
|
||||
$can_post = true;
|
||||
|
|
@ -174,23 +163,7 @@ function wall_upload_post(App $a, $desktopmode = true)
|
|||
exit();
|
||||
}
|
||||
|
||||
// This is a special treatment for picture upload from Twidere
|
||||
if (($filename == "octet-stream") && ($filetype != "")) {
|
||||
$filename = $filetype;
|
||||
$filetype = "";
|
||||
}
|
||||
|
||||
if ($filetype == "") {
|
||||
$filetype = Images::guessType($filename);
|
||||
}
|
||||
|
||||
// If there is a temp name, then do a manual check
|
||||
// This is more reliable than the provided value
|
||||
|
||||
$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, Logger::DEBUG);
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ volumes:
|
|||
|
||||
trigger:
|
||||
branch:
|
||||
# - master
|
||||
# - stable
|
||||
- develop
|
||||
# - "*-rc"
|
||||
# event:
|
||||
|
|
@ -79,7 +79,7 @@ volumes:
|
|||
|
||||
trigger:
|
||||
branch:
|
||||
# - master
|
||||
# - stable
|
||||
- develop
|
||||
# - "*-rc"
|
||||
# event:
|
||||
|
|
@ -122,7 +122,7 @@ volumes:
|
|||
|
||||
trigger:
|
||||
branch:
|
||||
# - master
|
||||
# - stable
|
||||
- develop
|
||||
# - "*-rc"
|
||||
# event:
|
||||
|
|
@ -169,7 +169,7 @@ volumes:
|
|||
|
||||
trigger:
|
||||
branch:
|
||||
# - master
|
||||
# - stable
|
||||
- develop
|
||||
# - "*-rc"
|
||||
# event:
|
||||
|
|
@ -211,7 +211,7 @@ volumes:
|
|||
|
||||
trigger:
|
||||
branch:
|
||||
# - master
|
||||
# - stable
|
||||
- develop
|
||||
# - "*-rc"
|
||||
# event:
|
||||
|
|
@ -253,7 +253,7 @@ volumes:
|
|||
|
||||
trigger:
|
||||
branch:
|
||||
# - master
|
||||
# - stable
|
||||
- develop
|
||||
# - "*-rc"
|
||||
# event:
|
||||
|
|
@ -282,7 +282,7 @@ services:
|
|||
|
||||
trigger:
|
||||
branch:
|
||||
# - master
|
||||
# - stable
|
||||
- develop
|
||||
# - "*-rc"
|
||||
# event:
|
||||
|
|
@ -306,7 +306,7 @@ services:
|
|||
|
||||
trigger:
|
||||
branch:
|
||||
# - master
|
||||
# - stable
|
||||
- develop
|
||||
# - "*-rc"
|
||||
# event:
|
||||
|
|
@ -330,7 +330,7 @@ services:
|
|||
|
||||
trigger:
|
||||
branch:
|
||||
# - master
|
||||
# - stable
|
||||
- develop
|
||||
# - "*-rc"
|
||||
# event:
|
||||
|
|
@ -360,7 +360,7 @@ services:
|
|||
|
||||
trigger:
|
||||
branch:
|
||||
# - master
|
||||
# - stable
|
||||
- develop
|
||||
# - "*-rc"
|
||||
# event:
|
||||
|
|
@ -384,7 +384,7 @@ services:
|
|||
|
||||
trigger:
|
||||
branch:
|
||||
# - master
|
||||
# - stable
|
||||
- develop
|
||||
# - "*-rc"
|
||||
# event:
|
||||
|
|
@ -408,7 +408,7 @@ services:
|
|||
|
||||
trigger:
|
||||
branch:
|
||||
# - master
|
||||
# - stable
|
||||
- develop
|
||||
# - "*-rc"
|
||||
# event:
|
||||
|
|
@ -439,7 +439,7 @@ services:
|
|||
|
||||
trigger:
|
||||
branch:
|
||||
# - master
|
||||
# - stable
|
||||
- develop
|
||||
# - "*-rc"
|
||||
# event:
|
||||
|
|
@ -463,7 +463,7 @@ services:
|
|||
|
||||
trigger:
|
||||
branch:
|
||||
# - master
|
||||
# - stable
|
||||
- develop
|
||||
# - "*-rc"
|
||||
# event:
|
||||
|
|
@ -487,7 +487,7 @@ services:
|
|||
|
||||
trigger:
|
||||
branch:
|
||||
# - master
|
||||
# - stable
|
||||
- develop
|
||||
# - "*-rc"
|
||||
# event:
|
||||
|
|
|
|||
|
|
@ -276,7 +276,7 @@ class Page implements ArrayAccess
|
|||
// If you're just visiting, let javascript take you home
|
||||
if (!empty($_SESSION['visitor_home'])) {
|
||||
$homebase = $_SESSION['visitor_home'];
|
||||
} elseif (local_user()) {
|
||||
} elseif (!empty($app->user['nickname'])) {
|
||||
$homebase = 'profile/' . $app->user['nickname'];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -96,11 +96,11 @@ abstract class BaseModule
|
|||
* Functions used to protect against Cross-Site Request Forgery
|
||||
* The security token has to base on at least one value that an attacker can't know - here it's the session ID and the private key.
|
||||
* In this implementation, a security token is reusable (if the user submits a form, goes back and resubmits the form, maybe with small changes;
|
||||
* or if the security token is used for ajax-calls that happen several times), but only valid for a certain amout of time (3hours).
|
||||
* The "typename" seperates the security tokens of different types of forms. This could be relevant in the following case:
|
||||
* A security token is used to protekt a link from CSRF (e.g. the "delete this profile"-link).
|
||||
* or if the security token is used for ajax-calls that happen several times), but only valid for a certain amount of time (3hours).
|
||||
* The "typename" separates the security tokens of different types of forms. This could be relevant in the following case:
|
||||
* A security token is used to protect a link from CSRF (e.g. the "delete this profile"-link).
|
||||
* If the new page contains by any chance external elements, then the used security token is exposed by the referrer.
|
||||
* Actually, important actions should not be triggered by Links / GET-Requests at all, but somethimes they still are,
|
||||
* Actually, important actions should not be triggered by Links / GET-Requests at all, but sometimes they still are,
|
||||
* so this mechanism brings in some damage control (the attacker would be able to forge a request to a form of this type, but not to forms of other types).
|
||||
*/
|
||||
public static function getFormSecurityToken($typename = '')
|
||||
|
|
@ -108,7 +108,7 @@ abstract class BaseModule
|
|||
$a = DI::app();
|
||||
|
||||
$timestamp = time();
|
||||
$sec_hash = hash('whirlpool', $a->user['guid'] . $a->user['prvkey'] . session_id() . $timestamp . $typename);
|
||||
$sec_hash = hash('whirlpool', ($a->user['guid'] ?? '') . ($a->user['prvkey'] ?? '') . session_id() . $timestamp . $typename);
|
||||
|
||||
return $timestamp . '.' . $sec_hash;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ Commands
|
|||
dryrun Show database update schema queries without running them
|
||||
update Update database schema
|
||||
dumpsql Dump database schema
|
||||
toinnodb Convert all tables from MyISAM to InnoDB
|
||||
toinnodb Convert all tables from MyISAM or InnoDB in the Antelope file format to InnoDB in the Barracuda file format
|
||||
|
||||
Options
|
||||
-h|--help|-? Show help information
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ class GlobalCommunitySilence extends \Asika\SimpleConsole\Console
|
|||
protected function getHelp()
|
||||
{
|
||||
$help = <<<HELP
|
||||
console globalcommunitysilence - Silence remote profile from global community page
|
||||
console globalcommunitysilence - Silence a profile from the global community page
|
||||
Usage
|
||||
bin/console globalcommunitysilence <profile_url> [-h|--help|-?] [-v]
|
||||
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ console user - Modify user settings per console commands.
|
|||
Usage
|
||||
bin/console user password <nickname> [<password>] [-h|--help|-?] [-v]
|
||||
bin/console user add [<name> [<nickname> [<email> [<language>]]]] [-h|--help|-?] [-v]
|
||||
bin/console user delete [<nickname>] [-q] [-h|--help|-?] [-v]
|
||||
bin/console user delete [<nickname>] [-y] [-h|--help|-?] [-v]
|
||||
bin/console user allow [<nickname>] [-h|--help|-?] [-v]
|
||||
bin/console user deny [<nickname>] [-h|--help|-?] [-v]
|
||||
bin/console user block [<nickname>] [-h|--help|-?] [-v]
|
||||
|
|
@ -78,8 +78,8 @@ Description
|
|||
|
||||
Options
|
||||
-h|--help|-? Show help information
|
||||
-v Show more debug information.
|
||||
-q Quiet mode (don't ask for a command).
|
||||
-v Show more debug information
|
||||
-y Non-interactive mode, assume "yes" as answer to the user deletion prompt
|
||||
HELP;
|
||||
return $help;
|
||||
}
|
||||
|
|
@ -304,19 +304,24 @@ HELP;
|
|||
}
|
||||
}
|
||||
|
||||
$user = $this->dba->selectFirst('user', ['uid'], ['nickname' => $nick]);
|
||||
$user = $this->dba->selectFirst('user', ['uid', 'account_removed'], ['nickname' => $nick]);
|
||||
if (empty($user)) {
|
||||
throw new RuntimeException($this->l10n->t('User not found'));
|
||||
}
|
||||
|
||||
if (!$this->getOption('q')) {
|
||||
if (!empty($user['account_removed'])) {
|
||||
$this->out($this->l10n->t('User has already been marked for deletion.'));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!$this->getOption('y')) {
|
||||
$this->out($this->l10n->t('Type "yes" to delete %s', $nick));
|
||||
if (CliPrompt::prompt() !== 'yes') {
|
||||
throw new RuntimeException('Delete abort.');
|
||||
throw new RuntimeException($this->l10n->t('Deletion aborted.'));
|
||||
}
|
||||
}
|
||||
|
||||
return UserModel::remove($user['uid'] ?? -1);
|
||||
return UserModel::remove($user['uid']);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -361,7 +366,7 @@ HELP;
|
|||
$contact['email'],
|
||||
Temporal::getRelativeDate($contact['created']),
|
||||
Temporal::getRelativeDate($contact['login_date']),
|
||||
Temporal::getRelativeDate($contact['lastitem_date']),
|
||||
Temporal::getRelativeDate($contact['last-item']),
|
||||
]);
|
||||
}
|
||||
$this->out($table->getTable());
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ class ForumManager
|
|||
$selected = (($cid == $contact['id']) ? ' forum-selected' : '');
|
||||
|
||||
$entry = [
|
||||
'url' => 'network?cid=' . $contact['id'],
|
||||
'url' => 'network?contactid=' . $contact['id'],
|
||||
'external_url' => Contact::magicLink($contact['url']),
|
||||
'name' => $contact['name'],
|
||||
'cid' => $contact['id'],
|
||||
|
|
|
|||
|
|
@ -21,7 +21,10 @@
|
|||
|
||||
namespace Friendica\Content;
|
||||
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Model\FileTag;
|
||||
use Friendica\Model\Tag;
|
||||
|
||||
/**
|
||||
* A content helper class for displaying items
|
||||
|
|
@ -100,4 +103,136 @@ class Item
|
|||
|
||||
return [$categories, $folders];
|
||||
}
|
||||
|
||||
/**
|
||||
* This function removes the tag $tag from the text $body and replaces it with
|
||||
* the appropriate link.
|
||||
*
|
||||
* @param string $body the text to replace the tag in
|
||||
* @param string $inform a comma-seperated string containing everybody to inform
|
||||
* @param integer $profile_uid the user id to replace the tag for (0 = anyone)
|
||||
* @param string $tag the tag to replace
|
||||
* @param string $network The network of the post
|
||||
*
|
||||
* @return array|bool ['replaced' => $replaced, 'contact' => $contact];
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
public static function replaceTag(&$body, &$inform, $profile_uid, $tag, $network = '')
|
||||
{
|
||||
$replaced = false;
|
||||
|
||||
//is it a person tag?
|
||||
if (Tag::isType($tag, Tag::MENTION, Tag::IMPLICIT_MENTION, Tag::EXCLUSIVE_MENTION)) {
|
||||
$tag_type = substr($tag, 0, 1);
|
||||
//is it already replaced?
|
||||
if (strpos($tag, '[url=')) {
|
||||
// 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]';
|
||||
}
|
||||
}
|
||||
|
||||
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, $profile_uid);
|
||||
} elseif (strpos($name, '@')) {
|
||||
// This function automatically probes when no entry was found
|
||||
$contact = Contact::getDetailsByAddr($name, $profile_uid);
|
||||
} 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 in the current network
|
||||
if (!DBA::isResult($contact) && ($network != '')) {
|
||||
$condition = ["`nick` = ? AND `network` = ? AND `uid` = ?",
|
||||
$name, $network, $profile_uid];
|
||||
$contact = DBA::selectFirst('contact', $fields, $condition);
|
||||
}
|
||||
|
||||
// select someone by attag in the current network
|
||||
if (!DBA::isResult($contact) && ($network != '')) {
|
||||
$condition = ["`attag` = ? AND `network` = ? AND `uid` = ?",
|
||||
$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 in any network
|
||||
if (!DBA::isResult($contact)) {
|
||||
$condition = ["`nick` = ? AND `uid` = ?", $name, $profile_uid];
|
||||
$contact = DBA::selectFirst('contact', $fields, $condition);
|
||||
}
|
||||
|
||||
// select someone by attag in any network
|
||||
if (!DBA::isResult($contact)) {
|
||||
$condition = ["`attag` = ? AND `uid` = ?", $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'];
|
||||
$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);
|
||||
}
|
||||
}
|
||||
|
||||
return ['replaced' => $replaced, 'contact' => $contact];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -171,20 +171,24 @@ class Nav
|
|||
}
|
||||
|
||||
if (local_user()) {
|
||||
// user menu
|
||||
$nav['usermenu'][] = ['profile/' . $a->user['nickname'], DI::l10n()->t('Status'), '', DI::l10n()->t('Your posts and conversations')];
|
||||
$nav['usermenu'][] = ['profile/' . $a->user['nickname'] . '/profile', DI::l10n()->t('Profile'), '', DI::l10n()->t('Your profile page')];
|
||||
$nav['usermenu'][] = ['photos/' . $a->user['nickname'], DI::l10n()->t('Photos'), '', DI::l10n()->t('Your photos')];
|
||||
$nav['usermenu'][] = ['videos/' . $a->user['nickname'], DI::l10n()->t('Videos'), '', DI::l10n()->t('Your videos')];
|
||||
$nav['usermenu'][] = ['events/', DI::l10n()->t('Events'), '', DI::l10n()->t('Your events')];
|
||||
$nav['usermenu'][] = ['notes/', DI::l10n()->t('Personal notes'), '', DI::l10n()->t('Your personal notes')];
|
||||
if (!empty($a->user)) {
|
||||
// user menu
|
||||
$nav['usermenu'][] = ['profile/' . $a->user['nickname'], DI::l10n()->t('Status'), '', DI::l10n()->t('Your posts and conversations')];
|
||||
$nav['usermenu'][] = ['profile/' . $a->user['nickname'] . '/profile', DI::l10n()->t('Profile'), '', DI::l10n()->t('Your profile page')];
|
||||
$nav['usermenu'][] = ['photos/' . $a->user['nickname'], DI::l10n()->t('Photos'), '', DI::l10n()->t('Your photos')];
|
||||
$nav['usermenu'][] = ['videos/' . $a->user['nickname'], DI::l10n()->t('Videos'), '', DI::l10n()->t('Your videos')];
|
||||
$nav['usermenu'][] = ['events/', DI::l10n()->t('Events'), '', DI::l10n()->t('Your events')];
|
||||
$nav['usermenu'][] = ['notes/', DI::l10n()->t('Personal notes'), '', DI::l10n()->t('Your personal notes')];
|
||||
|
||||
// user info
|
||||
$contact = DBA::selectFirst('contact', ['micro'], ['uid' => $a->user['uid'], 'self' => true]);
|
||||
$userinfo = [
|
||||
'icon' => (DBA::isResult($contact) ? DI::baseUrl()->remove($contact['micro']) : 'images/person-48.jpg'),
|
||||
'name' => $a->user['username'],
|
||||
];
|
||||
// user info
|
||||
$contact = DBA::selectFirst('contact', ['micro'], ['uid' => $a->user['uid'], 'self' => true]);
|
||||
$userinfo = [
|
||||
'icon' => (DBA::isResult($contact) ? DI::baseUrl()->remove($contact['micro']) : 'images/person-48.jpg'),
|
||||
'name' => $a->user['username'],
|
||||
];
|
||||
} else {
|
||||
DI::logger()->warning('Empty $a->user for local user', ['local_user' => local_user(), '$a' => $a]);
|
||||
}
|
||||
}
|
||||
|
||||
// "Home" should also take you home from an authenticated remote profile connection
|
||||
|
|
@ -252,7 +256,7 @@ class Nav
|
|||
}
|
||||
|
||||
// The following nav links are only show to logged in users
|
||||
if (local_user()) {
|
||||
if (local_user() && !empty($a->user)) {
|
||||
$nav['network'] = ['network', DI::l10n()->t('Network'), '', DI::l10n()->t('Conversations from your friends')];
|
||||
|
||||
$nav['home'] = ['profile/' . $a->user['nickname'], DI::l10n()->t('Home'), '', DI::l10n()->t('Your posts and conversations')];
|
||||
|
|
|
|||
273
src/Content/PageInfo.php
Normal file
273
src/Content/PageInfo.php
Normal file
|
|
@ -0,0 +1,273 @@
|
|||
<?php
|
||||
/**
|
||||
* @copyright Copyright (C) 2020, Friendica
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Friendica\Content;
|
||||
|
||||
use Friendica\Core\Hook;
|
||||
use Friendica\Core\Logger;
|
||||
use Friendica\DI;
|
||||
use Friendica\Network\HTTPException;
|
||||
use Friendica\Util\ParseUrl;
|
||||
use Friendica\Util\Strings;
|
||||
|
||||
/**
|
||||
* Extracts trailing URLs from post bodies to transform them in enriched attachment tags through Site Info query
|
||||
*/
|
||||
class PageInfo
|
||||
{
|
||||
/**
|
||||
* @param string $body
|
||||
* @param bool $searchNakedUrls
|
||||
* @param bool $no_photos
|
||||
* @return string
|
||||
* @throws HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public static function appendToBody(string $body, bool $searchNakedUrls = false, bool $no_photos = false)
|
||||
{
|
||||
Logger::info('add_page_info_to_body: fetch page info for body', ['body' => $body]);
|
||||
|
||||
$url = self::getRelevantUrlFromBody($body, $searchNakedUrls);
|
||||
if (!$url) {
|
||||
return $body;
|
||||
}
|
||||
|
||||
$footer = self::getFooterFromUrl($url, $no_photos);
|
||||
if (!$footer) {
|
||||
return $body;
|
||||
}
|
||||
|
||||
$body = self::stripTrailingUrlFromBody($body, $url);
|
||||
|
||||
$body .= "\n" . $footer;
|
||||
|
||||
return $body;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
* @param bool $no_photos
|
||||
* @param string $photo
|
||||
* @param bool $keywords
|
||||
* @param string $keyword_denylist
|
||||
* @return string
|
||||
* @throws HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public static function getFooterFromUrl(string $url, bool $no_photos = false, string $photo = '', bool $keywords = false, string $keyword_denylist = '')
|
||||
{
|
||||
$data = self::queryUrl($url, $photo, $keywords, $keyword_denylist);
|
||||
|
||||
return self::getFooterFromData($data, $no_photos);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
* @param bool $no_photos
|
||||
* @return string
|
||||
* @throws HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public static function getFooterFromData(array $data, bool $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(['[', ']'], ['[', ']'], htmlentities($data['url'], ENT_QUOTES, 'UTF-8', false));
|
||||
$data['title'] = str_replace(['[', ']'], ['[', ']'], 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(['[', ']'], ['[', ']'], 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 (!empty($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 $text . $hashtags;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
* @param string $photo
|
||||
* @param bool $keywords
|
||||
* @param string $keyword_denylist
|
||||
* @return array|bool
|
||||
* @throws HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public static function queryUrl(string $url, string $photo = '', bool $keywords = false, string $keyword_denylist = '')
|
||||
{
|
||||
$data = ParseUrl::getSiteinfoCached($url, true);
|
||||
|
||||
if ($photo != '') {
|
||||
$data['images'][0]['src'] = $photo;
|
||||
}
|
||||
|
||||
if (!$keywords) {
|
||||
unset($data['keywords']);
|
||||
} elseif ($keyword_denylist && !empty($data['keywords'])) {
|
||||
$list = explode(', ', $keyword_denylist);
|
||||
|
||||
foreach ($list as $keyword) {
|
||||
$keyword = trim($keyword);
|
||||
|
||||
$index = array_search($keyword, $data['keywords']);
|
||||
if ($index !== false) {
|
||||
unset($data['keywords'][$index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Logger::info('fetch page info for URL', ['url' => $url, 'data' => $data]);
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
* @param string $photo
|
||||
* @param string $keyword_denylist
|
||||
* @return array
|
||||
* @throws HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public static function getTagsFromUrl(string $url, string $photo = '', string $keyword_denylist = '')
|
||||
{
|
||||
$data = self::queryUrl($url, $photo, true, $keyword_denylist);
|
||||
|
||||
if (empty($data['keywords'])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$taglist = [];
|
||||
foreach ($data['keywords'] as $keyword) {
|
||||
$hashtag = str_replace([' ', '+', '/', '.', '#', "'"],
|
||||
['', '', '', '', '', ''], $keyword);
|
||||
|
||||
$taglist[] = $hashtag;
|
||||
}
|
||||
|
||||
return $taglist;
|
||||
}
|
||||
|
||||
/**
|
||||
* Picks a non-hashtag, non-mention, schemeful URL at the end of the provided body string to be converted into Page Info.
|
||||
*
|
||||
* @param string $body
|
||||
* @param bool $searchNakedUrls Whether we should pick a naked URL (outside of BBCode tags) as a last resort
|
||||
* @return string|null
|
||||
*/
|
||||
protected static function getRelevantUrlFromBody(string $body, bool $searchNakedUrls = false)
|
||||
{
|
||||
$URLSearchString = 'https?://[^\[\]]*';
|
||||
|
||||
// Fix for Mastodon where the mentions are in a different format
|
||||
$body = preg_replace("~\[url=($URLSearchString)]([#!@])(.*?)\[/url]~is", '$2[url=$1]$3[/url]', $body);
|
||||
|
||||
preg_match("~(?<![!#@])\[url]($URLSearchString)\[/url]$~is", $body, $matches);
|
||||
|
||||
if (!$matches) {
|
||||
preg_match("~(?<![!#@])\[url=($URLSearchString)].*\[/url]$~is", $body, $matches);
|
||||
}
|
||||
|
||||
if (!$matches && $searchNakedUrls) {
|
||||
preg_match('~(?<=\W|^)(?<![=\]])(https?://.+)$~is', $body, $matches);
|
||||
if ($matches && !Strings::endsWith($body, $matches[1])) {
|
||||
unset($matches);
|
||||
}
|
||||
}
|
||||
|
||||
return $matches[1] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the provided URL from the body if it is at the end of it.
|
||||
* Keep the link label if it isn't the full URL.
|
||||
*
|
||||
* @param string $body
|
||||
* @param string $url
|
||||
* @return string|string[]|null
|
||||
*/
|
||||
protected static function stripTrailingUrlFromBody(string $body, string $url)
|
||||
{
|
||||
$quotedUrl = preg_quote($url, '#');
|
||||
$body = preg_replace("#(?:
|
||||
\[url]$quotedUrl\[/url]|
|
||||
\[url=$quotedUrl]$quotedUrl\[/url]|
|
||||
\[url=$quotedUrl]([^[]*?)\[/url]|
|
||||
$quotedUrl
|
||||
)$#isx", '$1', $body);
|
||||
|
||||
return $body;
|
||||
}
|
||||
}
|
||||
|
|
@ -24,6 +24,8 @@ namespace Friendica\Content\Text;
|
|||
use DOMDocument;
|
||||
use DOMXPath;
|
||||
use Exception;
|
||||
use Friendica\Content\ContactSelector;
|
||||
use Friendica\Content\Item;
|
||||
use Friendica\Content\OEmbed;
|
||||
use Friendica\Content\Smilies;
|
||||
use Friendica\Core\Hook;
|
||||
|
|
@ -35,6 +37,7 @@ use Friendica\DI;
|
|||
use Friendica\Model\Contact;
|
||||
use Friendica\Model\Event;
|
||||
use Friendica\Model\Photo;
|
||||
use Friendica\Model\Tag;
|
||||
use Friendica\Network\Probe;
|
||||
use Friendica\Object\Image;
|
||||
use Friendica\Protocol\Activity;
|
||||
|
|
@ -48,6 +51,15 @@ use Friendica\Util\XML;
|
|||
|
||||
class BBCode
|
||||
{
|
||||
const INTERNAL = 0;
|
||||
const API = 2;
|
||||
const DIASPORA = 3;
|
||||
const CONNECTORS = 4;
|
||||
const OSTATUS = 7;
|
||||
const TWITTER = 8;
|
||||
const BACKLINK = 8;
|
||||
const ACTIVITYPUB = 9;
|
||||
|
||||
/**
|
||||
* Fetches attachment data that were generated the old way
|
||||
*
|
||||
|
|
@ -434,25 +446,36 @@ class BBCode
|
|||
*/
|
||||
public static function toPlaintext($text, $keep_urls = true)
|
||||
{
|
||||
$naked_text = HTML::toPlaintext(BBCode::convert($text, false, 0, true), 0, !$keep_urls);
|
||||
$naked_text = HTML::toPlaintext(self::convert($text, false, 0, true), 0, !$keep_urls);
|
||||
|
||||
return $naked_text;
|
||||
}
|
||||
|
||||
private static function proxyUrl($image, $simplehtml = false)
|
||||
private static function proxyUrl($image, $simplehtml = self::INTERNAL)
|
||||
{
|
||||
// Only send proxied pictures to API and for internal display
|
||||
if (in_array($simplehtml, [false, 2])) {
|
||||
if (in_array($simplehtml, [self::INTERNAL, self::API])) {
|
||||
return ProxyUtils::proxifyUrl($image);
|
||||
} else {
|
||||
return $image;
|
||||
}
|
||||
}
|
||||
|
||||
public static function scaleExternalImages($srctext)
|
||||
/**
|
||||
* This function changing the visual size (not the real size) of images.
|
||||
* The function does not work for pictures with an alternate text description.
|
||||
* This could only be changed by using some new "img" BBCode format.
|
||||
*
|
||||
* @param string $srctext The body with images
|
||||
* @return string The body with possibly scaled images
|
||||
*/
|
||||
public static function scaleExternalImages(string $srctext)
|
||||
{
|
||||
$s = $srctext;
|
||||
|
||||
// Simplify image links
|
||||
$s = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", '[img]$3[/img]', $s);
|
||||
|
||||
$matches = null;
|
||||
$c = preg_match_all('/\[img.*?\](.*?)\[\/img\]/ism', $s, $matches, PREG_SET_ORDER);
|
||||
if ($c) {
|
||||
|
|
@ -464,13 +487,14 @@ class BBCode
|
|||
continue;
|
||||
}
|
||||
|
||||
$i = Network::fetchUrl($mtch[1]);
|
||||
if (!$i) {
|
||||
return $srctext;
|
||||
$curlResult = Network::curl($mtch[1], true);
|
||||
if (!$curlResult->isSuccess()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// guess mimetype from headers or filename
|
||||
$type = Images::guessType($mtch[1], true);
|
||||
$i = $curlResult->getBody();
|
||||
$type = $curlResult->getContentType();
|
||||
$type = Images::getMimeTypeByData($i, $mtch[1], $type);
|
||||
|
||||
if ($i) {
|
||||
$Image = new Image($i, $type);
|
||||
|
|
@ -482,14 +506,14 @@ class BBCode
|
|||
$Image->scaleDown(640);
|
||||
$new_width = $Image->getWidth();
|
||||
$new_height = $Image->getHeight();
|
||||
Logger::log('scale_external_images: ' . $orig_width . '->' . $new_width . 'w ' . $orig_height . '->' . $new_height . 'h' . ' match: ' . $mtch[0], Logger::DEBUG);
|
||||
Logger::info('External images scaled', ['orig_width' => $orig_width, 'new_width' => $new_width, 'orig_height' => $orig_height, 'new_height' => $new_height, 'match' => $mtch[0]]);
|
||||
$s = str_replace(
|
||||
$mtch[0],
|
||||
'[img=' . $new_width . 'x' . $new_height. ']' . $mtch[1] . '[/img]'
|
||||
. "\n",
|
||||
$s
|
||||
);
|
||||
Logger::log('scale_external_images: new string: ' . $s, Logger::DEBUG);
|
||||
Logger::info('New string', ['image' => $s]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -517,7 +541,7 @@ class BBCode
|
|||
// than the maximum, then don't waste time looking for the images
|
||||
if ($maxlen && (strlen($body) > $maxlen)) {
|
||||
|
||||
Logger::log('the total body length exceeds the limit', Logger::DEBUG);
|
||||
Logger::info('the total body length exceeds the limit', ['maxlen' => $maxlen, 'body_len' => strlen($body)]);
|
||||
|
||||
$orig_body = $body;
|
||||
$new_body = '';
|
||||
|
|
@ -537,7 +561,7 @@ class BBCode
|
|||
|
||||
if (($textlen + $img_start) > $maxlen) {
|
||||
if ($textlen < $maxlen) {
|
||||
Logger::log('the limit happens before an embedded image', Logger::DEBUG);
|
||||
Logger::info('the limit happens before an embedded image');
|
||||
$new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
|
||||
$textlen = $maxlen;
|
||||
}
|
||||
|
|
@ -551,7 +575,7 @@ class BBCode
|
|||
|
||||
if (($textlen + $img_end) > $maxlen) {
|
||||
if ($textlen < $maxlen) {
|
||||
Logger::log('the limit happens before the end of a non-embedded image', Logger::DEBUG);
|
||||
Logger::info('the limit happens before the end of a non-embedded image');
|
||||
$new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
|
||||
$textlen = $maxlen;
|
||||
}
|
||||
|
|
@ -574,11 +598,11 @@ class BBCode
|
|||
|
||||
if (($textlen + strlen($orig_body)) > $maxlen) {
|
||||
if ($textlen < $maxlen) {
|
||||
Logger::log('the limit happens after the end of the last image', Logger::DEBUG);
|
||||
Logger::info('the limit happens after the end of the last image');
|
||||
$new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen);
|
||||
}
|
||||
} else {
|
||||
Logger::log('the text size with embedded images extracted did not violate the limit', Logger::DEBUG);
|
||||
Logger::info('the text size with embedded images extracted did not violate the limit');
|
||||
$new_body = $new_body . $orig_body;
|
||||
}
|
||||
|
||||
|
|
@ -593,13 +617,13 @@ class BBCode
|
|||
*
|
||||
* Note: Can produce a [bookmark] tag in the returned string
|
||||
*
|
||||
* @param string $text
|
||||
* @param bool|int $simplehtml
|
||||
* @param bool $tryoembed
|
||||
* @param string $text
|
||||
* @param integer $simplehtml
|
||||
* @param bool $tryoembed
|
||||
* @return string
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
private static function convertAttachment($text, $simplehtml = false, $tryoembed = true)
|
||||
private static function convertAttachment($text, $simplehtml = self::INTERNAL, $tryoembed = true)
|
||||
{
|
||||
$data = self::getAttachmentData($text);
|
||||
if (empty($data) || empty($data['url'])) {
|
||||
|
|
@ -628,7 +652,7 @@ class BBCode
|
|||
} catch (Exception $e) {
|
||||
$data['title'] = ($data['title'] ?? '') ?: $data['url'];
|
||||
|
||||
if ($simplehtml != 4) {
|
||||
if ($simplehtml != self::CONNECTORS) {
|
||||
$return = sprintf('<div class="type-%s">', $data['type']);
|
||||
}
|
||||
|
||||
|
|
@ -655,7 +679,7 @@ class BBCode
|
|||
$return .= sprintf('<sup><a href="%s">%s</a></sup>', $data['url'], parse_url($data['url'], PHP_URL_HOST));
|
||||
}
|
||||
|
||||
if ($simplehtml != 4) {
|
||||
if ($simplehtml != self::CONNECTORS) {
|
||||
$return .= '</div>';
|
||||
}
|
||||
}
|
||||
|
|
@ -954,27 +978,12 @@ class BBCode
|
|||
function ($match) use ($callback) {
|
||||
$attribute_string = $match[2];
|
||||
$attributes = [];
|
||||
foreach (['author', 'profile', 'avatar', 'link', 'posted'] as $field) {
|
||||
foreach (['author', 'profile', 'avatar', 'link', 'posted', 'guid'] as $field) {
|
||||
preg_match("/$field=(['\"])(.+?)\\1/ism", $attribute_string, $matches);
|
||||
$attributes[$field] = html_entity_decode($matches[2] ?? '', ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
// We only call this so that a previously unknown contact can be added.
|
||||
// This is important for the function "Model\Contact::getDetailsByURL()".
|
||||
// This function then can fetch an entry from the contact table.
|
||||
$default['url'] = $attributes['profile'];
|
||||
|
||||
if (!empty($attributes['author'])) {
|
||||
$default['name'] = $attributes['author'];
|
||||
}
|
||||
|
||||
if (!empty($attributes['avatar'])) {
|
||||
$default['photo'] = $attributes['avatar'];
|
||||
}
|
||||
|
||||
Contact::getIdForURL($attributes['profile'], 0, true, $default);
|
||||
|
||||
$author_contact = Contact::getDetailsByURL($attributes['profile']);
|
||||
$author_contact = Contact::getByURL($attributes['profile'], 0, ['url', 'addr', 'name', 'micro'], false);
|
||||
$author_contact['url'] = ($author_contact['url'] ?? $attributes['profile']);
|
||||
$author_contact['addr'] = ($author_contact['addr'] ?? '') ?: Protocol::getAddrFromProfileUrl($attributes['profile']);
|
||||
|
||||
|
|
@ -1013,13 +1022,10 @@ class BBCode
|
|||
$mention = Protocol::formatMention($attributes['profile'], $attributes['author']);
|
||||
|
||||
switch ($simplehtml) {
|
||||
case 1:
|
||||
$text = ($is_quote_share? '<br />' : '') . '<p>' . html_entity_decode('♲ ', ENT_QUOTES, 'UTF-8') . ' <a href="' . $attributes['profile'] . '">' . $mention . '</a>: </p>' . "\n" . '«' . $content . '»';
|
||||
break;
|
||||
case 2:
|
||||
case self::API:
|
||||
$text = ($is_quote_share? '<br />' : '') . '<p>' . html_entity_decode('♲ ', ENT_QUOTES, 'UTF-8') . ' ' . $author_contact['addr'] . ': </p>' . "\n" . $content;
|
||||
break;
|
||||
case 3: // Diaspora
|
||||
case self::DIASPORA:
|
||||
if (stripos(Strings::normaliseLink($attributes['link']), 'http://twitter.com/') === 0) {
|
||||
$text = ($is_quote_share? '<hr />' : '') . '<p><a href="' . $attributes['link'] . '">' . $attributes['link'] . '</a></p>' . "\n";
|
||||
} else {
|
||||
|
|
@ -1037,7 +1043,7 @@ class BBCode
|
|||
}
|
||||
|
||||
break;
|
||||
case 4:
|
||||
case self::CONNECTORS:
|
||||
$headline = '<p><b>' . html_entity_decode('♲ ', ENT_QUOTES, 'UTF-8');
|
||||
$headline .= DI::l10n()->t('<a href="%1$s" target="_blank" rel="noopener noreferrer">%2$s</a> %3$s', $attributes['link'], $mention, $attributes['posted']);
|
||||
$headline .= ':</b></p>' . "\n";
|
||||
|
|
@ -1045,37 +1051,32 @@ class BBCode
|
|||
$text = ($is_quote_share? '<hr />' : '') . $headline . '<blockquote class="shared_content">' . trim($content) . '</blockquote>' . "\n";
|
||||
|
||||
break;
|
||||
case 5:
|
||||
$text = ($is_quote_share? '<br />' : '') . '<p>' . html_entity_decode('♲ ', ENT_QUOTES, 'UTF-8') . ' ' . $author_contact['addr'] . ': </p>' . "\n" . $content;
|
||||
break;
|
||||
case 7: // statusnet/GNU Social
|
||||
case self::OSTATUS:
|
||||
$text = ($is_quote_share? '<br />' : '') . '<p>' . html_entity_decode('♲ ', ENT_QUOTES, 'UTF-8') . ' @' . $author_contact['addr'] . ': ' . $content . '</p>' . "\n";
|
||||
break;
|
||||
case 9: // ActivityPub
|
||||
case self::ACTIVITYPUB:
|
||||
$author = '@<span class="vcard"><a href="' . $author_contact['url'] . '" class="url u-url mention" title="' . $author_contact['addr'] . '"><span class="fn nickname mention">' . $author_contact['addr'] . '</span></a>:</span>';
|
||||
$text = '<div><a href="' . $attributes['link'] . '">' . html_entity_decode('♲', ENT_QUOTES, 'UTF-8') . '</a> ' . $author . '<blockquote>' . $content . '</blockquote></div>' . "\n";
|
||||
break;
|
||||
default:
|
||||
// Transforms quoted tweets in rich attachments to avoid nested tweets
|
||||
if (stripos(Strings::normaliseLink($attributes['link']), 'http://twitter.com/') === 0 && OEmbed::isAllowedURL($attributes['link'])) {
|
||||
try {
|
||||
$text = ($is_quote_share? '<br />' : '') . OEmbed::getHTML($attributes['link']);
|
||||
} catch (Exception $e) {
|
||||
$text = ($is_quote_share? '<br />' : '') . sprintf('[bookmark=%s]%s[/bookmark]', $attributes['link'], $content);
|
||||
}
|
||||
} else {
|
||||
$text = ($is_quote_share? "\n" : '');
|
||||
$text = ($is_quote_share? "\n" : '');
|
||||
|
||||
$tpl = Renderer::getMarkupTemplate('shared_content.tpl');
|
||||
$text .= Renderer::replaceMacros($tpl, [
|
||||
'$profile' => $attributes['profile'],
|
||||
'$avatar' => $attributes['avatar'],
|
||||
'$author' => $attributes['author'],
|
||||
'$link' => $attributes['link'],
|
||||
'$posted' => $attributes['posted'],
|
||||
'$content' => trim($content)
|
||||
]);
|
||||
}
|
||||
$contact = Contact::getByURL($attributes['profile'], 0, ['network'], false);
|
||||
$network = $contact['network'] ?? Protocol::PHANTOM;
|
||||
|
||||
$tpl = Renderer::getMarkupTemplate('shared_content.tpl');
|
||||
$text .= Renderer::replaceMacros($tpl, [
|
||||
'$profile' => $attributes['profile'],
|
||||
'$avatar' => $attributes['avatar'],
|
||||
'$author' => $attributes['author'],
|
||||
'$link' => $attributes['link'],
|
||||
'$link_title' => DI::l10n()->t('link to source'),
|
||||
'$posted' => $attributes['posted'],
|
||||
'$guid' => $attributes['guid'],
|
||||
'$network_name' => ContactSelector::networkToName($network, $attributes['profile']),
|
||||
'$network_icon' => ContactSelector::networkToIcon($network, $attributes['profile']),
|
||||
'$content' => self::setMentions(trim($content), 0, $network),
|
||||
]);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -1246,643 +1247,639 @@ class BBCode
|
|||
* @return string
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public static function convert($text, $try_oembed = true, $simple_html = 0, $for_plaintext = false)
|
||||
public static function convert(string $text = null, $try_oembed = true, $simple_html = self::INTERNAL, $for_plaintext = false)
|
||||
{
|
||||
// Accounting for null default column values
|
||||
if (is_null($text) || $text === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$a = DI::app();
|
||||
|
||||
/*
|
||||
* preg_match_callback function to replace potential Oembed tags with Oembed content
|
||||
*
|
||||
* $match[0] = [tag]$url[/tag] or [tag=$url]$title[/tag]
|
||||
* $match[1] = $url
|
||||
* $match[2] = $title or absent
|
||||
*/
|
||||
$try_oembed_callback = function ($match)
|
||||
{
|
||||
$url = $match[1];
|
||||
$title = $match[2] ?? null;
|
||||
$text = self::performWithEscapedTags($text, ['code'], function ($text) use ($try_oembed, $simple_html, $for_plaintext, $a) {
|
||||
$text = self::performWithEscapedTags($text, ['noparse', 'nobb', 'pre'], function ($text) use ($try_oembed, $simple_html, $for_plaintext, $a) {
|
||||
/*
|
||||
* preg_match_callback function to replace potential Oembed tags with Oembed content
|
||||
*
|
||||
* $match[0] = [tag]$url[/tag] or [tag=$url]$title[/tag]
|
||||
* $match[1] = $url
|
||||
* $match[2] = $title or absent
|
||||
*/
|
||||
$try_oembed_callback = function ($match)
|
||||
{
|
||||
$url = $match[1];
|
||||
$title = $match[2] ?? null;
|
||||
|
||||
try {
|
||||
$return = OEmbed::getHTML($url, $title);
|
||||
} catch (Exception $ex) {
|
||||
$return = $match[0];
|
||||
}
|
||||
|
||||
return $return;
|
||||
};
|
||||
|
||||
// Extracting code blocks before the whitespace processing and the autolinker
|
||||
$codeblocks = [];
|
||||
|
||||
$text = preg_replace_callback("#\[code(?:=([^\]]*))?\](.*?)\[\/code\]#ism",
|
||||
function ($matches) use (&$codeblocks) {
|
||||
$return = '#codeblock-' . count($codeblocks) . '#';
|
||||
if (strpos($matches[2], "\n") !== false) {
|
||||
$codeblocks[] = '<pre><code class="language-' . trim($matches[1]) . '">' . htmlspecialchars(trim($matches[2], "\n\r"), ENT_NOQUOTES, 'UTF-8') . '</code></pre>';
|
||||
} else {
|
||||
$codeblocks[] = '<code>' . htmlspecialchars($matches[2], ENT_NOQUOTES, 'UTF-8') . '</code>';
|
||||
}
|
||||
|
||||
return $return;
|
||||
},
|
||||
$text
|
||||
);
|
||||
|
||||
// Hide all [noparse] contained bbtags by spacefying them
|
||||
// POSSIBLE BUG --> Will the 'preg' functions crash if there's an embedded image?
|
||||
|
||||
$text = preg_replace_callback("/\[noparse\](.*?)\[\/noparse\]/ism", 'self::escapeNoparseCallback', $text);
|
||||
$text = preg_replace_callback("/\[nobb\](.*?)\[\/nobb\]/ism", 'self::escapeNoparseCallback', $text);
|
||||
$text = preg_replace_callback("/\[pre\](.*?)\[\/pre\]/ism", 'self::escapeNoparseCallback', $text);
|
||||
|
||||
// Remove the abstract element. It is a non visible element.
|
||||
$text = self::stripAbstract($text);
|
||||
|
||||
// Move all spaces out of the tags
|
||||
$text = preg_replace("/\[(\w*)\](\s*)/ism", '$2[$1]', $text);
|
||||
$text = preg_replace("/(\s*)\[\/(\w*)\]/ism", '[/$2]$1', $text);
|
||||
|
||||
// Extract the private images which use data urls since preg has issues with
|
||||
// large data sizes. Stash them away while we do bbcode conversion, and then put them back
|
||||
// in after we've done all the regex matching. We cannot use any preg functions to do this.
|
||||
|
||||
$extracted = self::extractImagesFromItemBody($text);
|
||||
$text = $extracted['body'];
|
||||
$saved_image = $extracted['images'];
|
||||
|
||||
// If we find any event code, turn it into an event.
|
||||
// After we're finished processing the bbcode we'll
|
||||
// replace all of the event code with a reformatted version.
|
||||
|
||||
$ev = Event::fromBBCode($text);
|
||||
|
||||
// Replace any html brackets with HTML Entities to prevent executing HTML or script
|
||||
// Don't use strip_tags here because it breaks [url] search by replacing & with amp
|
||||
|
||||
$text = str_replace("<", "<", $text);
|
||||
$text = str_replace(">", ">", $text);
|
||||
|
||||
// remove some newlines before the general conversion
|
||||
$text = preg_replace("/\s?\[share(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism", "[share$1]$2[/share]", $text);
|
||||
$text = preg_replace("/\s?\[quote(.*?)\]\s?(.*?)\s?\[\/quote\]\s?/ism", "[quote$1]$2[/quote]", $text);
|
||||
|
||||
// when the content is meant exporting to other systems then remove the avatar picture since this doesn't really look good on these systems
|
||||
if (!$try_oembed) {
|
||||
$text = preg_replace("/\[share(.*?)avatar\s?=\s?'.*?'\s?(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism", "\n[share$1$2]$3[/share]", $text);
|
||||
}
|
||||
|
||||
// Convert new line chars to html <br /> tags
|
||||
|
||||
// nlbr seems to be hopelessly messed up
|
||||
// $Text = nl2br($Text);
|
||||
|
||||
// We'll emulate it.
|
||||
|
||||
$text = trim($text);
|
||||
$text = str_replace("\r\n", "\n", $text);
|
||||
|
||||
// Remove linefeeds inside of the table elements. See issue #6799
|
||||
$search = ["\n[th]", "[th]\n", " [th]", "\n[/th]", "[/th]\n", "[/th] ",
|
||||
"\n[td]", "[td]\n", " [td]", "\n[/td]", "[/td]\n", "[/td] ",
|
||||
"\n[tr]", "[tr]\n", " [tr]", "[tr] ", "\n[/tr]", "[/tr]\n", " [/tr]", "[/tr] ",
|
||||
"[table]\n", "[table] ", " [table]", "\n[/table]", " [/table]", "[/table] "];
|
||||
$replace = ["[th]", "[th]", "[th]", "[/th]", "[/th]", "[/th]",
|
||||
"[td]", "[td]", "[td]", "[/td]", "[/td]", "[/td]",
|
||||
"[tr]", "[tr]", "[tr]", "[tr]", "[/tr]", "[/tr]", "[/tr]", "[/tr]",
|
||||
"[table]", "[table]", "[table]", "[/table]", "[/table]", "[/table]"];
|
||||
do {
|
||||
$oldtext = $text;
|
||||
$text = str_replace($search, $replace, $text);
|
||||
} while ($oldtext != $text);
|
||||
|
||||
// Replace these here only once
|
||||
$search = ["\n[table]", "[/table]\n"];
|
||||
$replace = ["[table]", "[/table]"];
|
||||
$text = str_replace($search, $replace, $text);
|
||||
|
||||
// removing multiplicated newlines
|
||||
if (DI::config()->get('system', 'remove_multiplicated_lines')) {
|
||||
$search = ["\n\n\n", "\n ", " \n", "[/quote]\n\n", "\n[/quote]", "[/li]\n", "\n[li]", "\n[ul]", "[/ul]\n", "\n\n[share ", "[/attachment]\n",
|
||||
"\n[h1]", "[/h1]\n", "\n[h2]", "[/h2]\n", "\n[h3]", "[/h3]\n", "\n[h4]", "[/h4]\n", "\n[h5]", "[/h5]\n", "\n[h6]", "[/h6]\n"];
|
||||
$replace = ["\n\n", "\n", "\n", "[/quote]\n", "[/quote]", "[/li]", "[li]", "[ul]", "[/ul]", "\n[share ", "[/attachment]",
|
||||
"[h1]", "[/h1]", "[h2]", "[/h2]", "[h3]", "[/h3]", "[h4]", "[/h4]", "[h5]", "[/h5]", "[h6]", "[/h6]"];
|
||||
do {
|
||||
$oldtext = $text;
|
||||
$text = str_replace($search, $replace, $text);
|
||||
} while ($oldtext != $text);
|
||||
}
|
||||
|
||||
/// @todo Have a closer look at the different html modes
|
||||
// Handle attached links or videos
|
||||
if (in_array($simple_html, [9])) {
|
||||
$text = self::removeAttachment($text);
|
||||
} elseif (!in_array($simple_html, [0, 4])) {
|
||||
$text = self::removeAttachment($text, true);
|
||||
} else {
|
||||
$text = self::convertAttachment($text, $simple_html, $try_oembed);
|
||||
}
|
||||
|
||||
// leave open the posibility of [map=something]
|
||||
// this is replaced in Item::prepareBody() which has knowledge of the item location
|
||||
if (strpos($text, '[/map]') !== false) {
|
||||
$text = preg_replace_callback(
|
||||
"/\[map\](.*?)\[\/map\]/ism",
|
||||
function ($match) use ($simple_html) {
|
||||
return str_replace($match[0], '<p class="map">' . Map::byLocation($match[1], $simple_html) . '</p>', $match[0]);
|
||||
},
|
||||
$text
|
||||
);
|
||||
}
|
||||
|
||||
if (strpos($text, '[map=') !== false) {
|
||||
$text = preg_replace_callback(
|
||||
"/\[map=(.*?)\]/ism",
|
||||
function ($match) use ($simple_html) {
|
||||
return str_replace($match[0], '<p class="map">' . Map::byCoordinates(str_replace('/', ' ', $match[1]), $simple_html) . '</p>', $match[0]);
|
||||
},
|
||||
$text
|
||||
);
|
||||
}
|
||||
|
||||
if (strpos($text, '[map]') !== false) {
|
||||
$text = preg_replace("/\[map\]/", '<p class="map"></p>', $text);
|
||||
}
|
||||
|
||||
// Check for headers
|
||||
$text = preg_replace("(\[h1\](.*?)\[\/h1\])ism", '<h1>$1</h1>', $text);
|
||||
$text = preg_replace("(\[h2\](.*?)\[\/h2\])ism", '<h2>$1</h2>', $text);
|
||||
$text = preg_replace("(\[h3\](.*?)\[\/h3\])ism", '<h3>$1</h3>', $text);
|
||||
$text = preg_replace("(\[h4\](.*?)\[\/h4\])ism", '<h4>$1</h4>', $text);
|
||||
$text = preg_replace("(\[h5\](.*?)\[\/h5\])ism", '<h5>$1</h5>', $text);
|
||||
$text = preg_replace("(\[h6\](.*?)\[\/h6\])ism", '<h6>$1</h6>', $text);
|
||||
|
||||
// Check for paragraph
|
||||
$text = preg_replace("(\[p\](.*?)\[\/p\])ism", '<p>$1</p>', $text);
|
||||
|
||||
// Check for bold text
|
||||
$text = preg_replace("(\[b\](.*?)\[\/b\])ism", '<strong>$1</strong>', $text);
|
||||
|
||||
// Check for Italics text
|
||||
$text = preg_replace("(\[i\](.*?)\[\/i\])ism", '<em>$1</em>', $text);
|
||||
|
||||
// Check for Underline text
|
||||
$text = preg_replace("(\[u\](.*?)\[\/u\])ism", '<u>$1</u>', $text);
|
||||
|
||||
// Check for strike-through text
|
||||
$text = preg_replace("(\[s\](.*?)\[\/s\])ism", '<s>$1</s>', $text);
|
||||
|
||||
// Check for over-line text
|
||||
$text = preg_replace("(\[o\](.*?)\[\/o\])ism", '<span class="overline">$1</span>', $text);
|
||||
|
||||
// Check for colored text
|
||||
$text = preg_replace("(\[color=(.*?)\](.*?)\[\/color\])ism", "<span style=\"color: $1;\">$2</span>", $text);
|
||||
|
||||
// Check for sized text
|
||||
// [size=50] --> font-size: 50px (with the unit).
|
||||
if ($simple_html != 3) {
|
||||
$text = preg_replace("(\[size=(\d*?)\](.*?)\[\/size\])ism", "<span style=\"font-size: $1px; line-height: initial;\">$2</span>", $text);
|
||||
$text = preg_replace("(\[size=(.*?)\](.*?)\[\/size\])ism", "<span style=\"font-size: $1; line-height: initial;\">$2</span>", $text);
|
||||
} else {
|
||||
// Issue 2199: Diaspora doesn't interpret the construct above, nor the <small> or <big> element
|
||||
$text = preg_replace("(\[size=(.*?)\](.*?)\[\/size\])ism", "$2", $text);
|
||||
}
|
||||
|
||||
|
||||
// Check for centered text
|
||||
$text = preg_replace("(\[center\](.*?)\[\/center\])ism", "<div style=\"text-align:center;\">$1</div>", $text);
|
||||
|
||||
// Check for list text
|
||||
$text = str_replace("[*]", "<li>", $text);
|
||||
|
||||
// Check for style sheet commands
|
||||
$text = preg_replace_callback(
|
||||
"(\[style=(.*?)\](.*?)\[\/style\])ism",
|
||||
function ($match) {
|
||||
return "<span style=\"" . HTML::sanitizeCSS($match[1]) . ";\">" . $match[2] . "</span>";
|
||||
},
|
||||
$text
|
||||
);
|
||||
|
||||
// Check for CSS classes
|
||||
$text = preg_replace_callback(
|
||||
"(\[class=(.*?)\](.*?)\[\/class\])ism",
|
||||
function ($match) {
|
||||
return "<span class=\"" . HTML::sanitizeCSS($match[1]) . "\">" . $match[2] . "</span>";
|
||||
},
|
||||
$text
|
||||
);
|
||||
|
||||
// handle nested lists
|
||||
$endlessloop = 0;
|
||||
|
||||
while ((((strpos($text, "[/list]") !== false) && (strpos($text, "[list") !== false)) ||
|
||||
((strpos($text, "[/ol]") !== false) && (strpos($text, "[ol]") !== false)) ||
|
||||
((strpos($text, "[/ul]") !== false) && (strpos($text, "[ul]") !== false)) ||
|
||||
((strpos($text, "[/li]") !== false) && (strpos($text, "[li]") !== false))) && (++$endlessloop < 20)) {
|
||||
$text = preg_replace("/\[list\](.*?)\[\/list\]/ism", '<ul class="listbullet" style="list-style-type: circle;">$1</ul>', $text);
|
||||
$text = preg_replace("/\[list=\](.*?)\[\/list\]/ism", '<ul class="listnone" style="list-style-type: none;">$1</ul>', $text);
|
||||
$text = preg_replace("/\[list=1\](.*?)\[\/list\]/ism", '<ul class="listdecimal" style="list-style-type: decimal;">$1</ul>', $text);
|
||||
$text = preg_replace("/\[list=((?-i)i)\](.*?)\[\/list\]/ism", '<ul class="listlowerroman" style="list-style-type: lower-roman;">$2</ul>', $text);
|
||||
$text = preg_replace("/\[list=((?-i)I)\](.*?)\[\/list\]/ism", '<ul class="listupperroman" style="list-style-type: upper-roman;">$2</ul>', $text);
|
||||
$text = preg_replace("/\[list=((?-i)a)\](.*?)\[\/list\]/ism", '<ul class="listloweralpha" style="list-style-type: lower-alpha;">$2</ul>', $text);
|
||||
$text = preg_replace("/\[list=((?-i)A)\](.*?)\[\/list\]/ism", '<ul class="listupperalpha" style="list-style-type: upper-alpha;">$2</ul>', $text);
|
||||
$text = preg_replace("/\[ul\](.*?)\[\/ul\]/ism", '<ul class="listbullet" style="list-style-type: circle;">$1</ul>', $text);
|
||||
$text = preg_replace("/\[ol\](.*?)\[\/ol\]/ism", '<ul class="listdecimal" style="list-style-type: decimal;">$1</ul>', $text);
|
||||
$text = preg_replace("/\[li\](.*?)\[\/li\]/ism", '<li>$1</li>', $text);
|
||||
}
|
||||
|
||||
$text = preg_replace("/\[th\](.*?)\[\/th\]/sm", '<th>$1</th>', $text);
|
||||
$text = preg_replace("/\[td\](.*?)\[\/td\]/sm", '<td>$1</td>', $text);
|
||||
$text = preg_replace("/\[tr\](.*?)\[\/tr\]/sm", '<tr>$1</tr>', $text);
|
||||
$text = preg_replace("/\[table\](.*?)\[\/table\]/sm", '<table>$1</table>', $text);
|
||||
|
||||
$text = preg_replace("/\[table border=1\](.*?)\[\/table\]/sm", '<table border="1" >$1</table>', $text);
|
||||
$text = preg_replace("/\[table border=0\](.*?)\[\/table\]/sm", '<table border="0" >$1</table>', $text);
|
||||
|
||||
$text = str_replace('[hr]', '<hr />', $text);
|
||||
|
||||
if (!$for_plaintext) {
|
||||
$escaped = [];
|
||||
|
||||
// Escaping BBCodes susceptible to contain rogue URL we don'' want the autolinker to catch
|
||||
$text = preg_replace_callback('#\[(url|img|audio|video|youtube|vimeo|share|attachment|iframe|bookmark).+?\[/\1\]#ism',
|
||||
function ($matches) use (&$escaped) {
|
||||
$return = '{escaped-' . count($escaped) . '}';
|
||||
$escaped[] = $matches[0];
|
||||
try {
|
||||
$return = OEmbed::getHTML($url, $title);
|
||||
} catch (Exception $ex) {
|
||||
$return = $match[0];
|
||||
}
|
||||
|
||||
return $return;
|
||||
},
|
||||
$text
|
||||
);
|
||||
|
||||
// Autolinker for isolated URLs
|
||||
$text = preg_replace(Strings::autoLinkRegEx(), '[url]$1[/url]', $text);
|
||||
|
||||
// Restoring escaped blocks
|
||||
$text = preg_replace_callback('/{escaped-([0-9]+)}/iU',
|
||||
function ($matches) use ($escaped) {
|
||||
return $escaped[intval($matches[1])] ?? $matches[0];
|
||||
},
|
||||
$text
|
||||
);
|
||||
}
|
||||
|
||||
// This is actually executed in Item::prepareBody()
|
||||
|
||||
$nosmile = strpos($text, '[nosmile]') !== false;
|
||||
$text = str_replace('[nosmile]', '', $text);
|
||||
|
||||
// Check for font change text
|
||||
$text = preg_replace("/\[font=(.*?)\](.*?)\[\/font\]/sm", "<span style=\"font-family: $1;\">$2</span>", $text);
|
||||
|
||||
// Declare the format for [spoiler] layout
|
||||
$SpoilerLayout = '<details class="spoiler"><summary>' . DI::l10n()->t('Click to open/close') . '</summary>$1</details>';
|
||||
|
||||
// Check for [spoiler] text
|
||||
// handle nested quotes
|
||||
$endlessloop = 0;
|
||||
while ((strpos($text, "[/spoiler]") !== false) && (strpos($text, "[spoiler]") !== false) && (++$endlessloop < 20)) {
|
||||
$text = preg_replace("/\[spoiler\](.*?)\[\/spoiler\]/ism", $SpoilerLayout, $text);
|
||||
}
|
||||
|
||||
// Check for [spoiler=Title] text
|
||||
|
||||
// handle nested quotes
|
||||
$endlessloop = 0;
|
||||
while ((strpos($text, "[/spoiler]")!== false) && (strpos($text, "[spoiler=") !== false) && (++$endlessloop < 20)) {
|
||||
$text = preg_replace("/\[spoiler=[\"\']*(.*?)[\"\']*\](.*?)\[\/spoiler\]/ism",
|
||||
'<details class="spoiler"><summary>$1</summary>$2</details>',
|
||||
$text);
|
||||
}
|
||||
|
||||
// Declare the format for [quote] layout
|
||||
$QuoteLayout = '<blockquote>$1</blockquote>';
|
||||
|
||||
// Check for [quote] text
|
||||
// handle nested quotes
|
||||
$endlessloop = 0;
|
||||
while ((strpos($text, "[/quote]") !== false) && (strpos($text, "[quote]") !== false) && (++$endlessloop < 20)) {
|
||||
$text = preg_replace("/\[quote\](.*?)\[\/quote\]/ism", "$QuoteLayout", $text);
|
||||
}
|
||||
|
||||
// Check for [quote=Author] text
|
||||
|
||||
$t_wrote = DI::l10n()->t('$1 wrote:');
|
||||
|
||||
// handle nested quotes
|
||||
$endlessloop = 0;
|
||||
while ((strpos($text, "[/quote]")!== false) && (strpos($text, "[quote=") !== false) && (++$endlessloop < 20)) {
|
||||
$text = preg_replace("/\[quote=[\"\']*(.*?)[\"\']*\](.*?)\[\/quote\]/ism",
|
||||
"<p><strong class=".'"author"'.">" . $t_wrote . "</strong></p><blockquote>$2</blockquote>",
|
||||
$text);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
// [img=widthxheight]image source[/img]
|
||||
$text = preg_replace_callback(
|
||||
"/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism",
|
||||
function ($matches) use ($simple_html) {
|
||||
if (strpos($matches[3], "data:image/") === 0) {
|
||||
return $matches[0];
|
||||
|
||||
// Remove the abstract element. It is a non visible element.
|
||||
$text = self::stripAbstract($text);
|
||||
|
||||
// Move new lines outside of tags
|
||||
$text = preg_replace("#\[(\w*)](\n*)#ism", '$2[$1]', $text);
|
||||
$text = preg_replace("#(\n*)\[/(\w*)]#ism", '[/$2]$1', $text);
|
||||
|
||||
// Extract the private images which use data urls since preg has issues with
|
||||
// large data sizes. Stash them away while we do bbcode conversion, and then put them back
|
||||
// in after we've done all the regex matching. We cannot use any preg functions to do this.
|
||||
|
||||
$extracted = self::extractImagesFromItemBody($text);
|
||||
$text = $extracted['body'];
|
||||
$saved_image = $extracted['images'];
|
||||
|
||||
// If we find any event code, turn it into an event.
|
||||
// After we're finished processing the bbcode we'll
|
||||
// replace all of the event code with a reformatted version.
|
||||
|
||||
$ev = Event::fromBBCode($text);
|
||||
|
||||
// Replace any html brackets with HTML Entities to prevent executing HTML or script
|
||||
// Don't use strip_tags here because it breaks [url] search by replacing & with amp
|
||||
|
||||
$text = str_replace("<", "<", $text);
|
||||
$text = str_replace(">", ">", $text);
|
||||
|
||||
// remove some newlines before the general conversion
|
||||
$text = preg_replace("/\s?\[share(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism", "[share$1]$2[/share]", $text);
|
||||
$text = preg_replace("/\s?\[quote(.*?)\]\s?(.*?)\s?\[\/quote\]\s?/ism", "[quote$1]$2[/quote]", $text);
|
||||
|
||||
// when the content is meant exporting to other systems then remove the avatar picture since this doesn't really look good on these systems
|
||||
if (!$try_oembed) {
|
||||
$text = preg_replace("/\[share(.*?)avatar\s?=\s?'.*?'\s?(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism", "\n[share$1$2]$3[/share]", $text);
|
||||
}
|
||||
|
||||
$matches[3] = self::proxyUrl($matches[3], $simple_html);
|
||||
return "[img=" . $matches[1] . "x" . $matches[2] . "]" . $matches[3] . "[/img]";
|
||||
},
|
||||
$text
|
||||
);
|
||||
// Convert new line chars to html <br /> tags
|
||||
|
||||
$text = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", '<img src="$3" style="width: $1px;" >', $text);
|
||||
$text = preg_replace("/\[zmg\=([0-9]*)x([0-9]*)\](.*?)\[\/zmg\]/ism", '<img class="zrl" src="$3" style="width: $1px;" >', $text);
|
||||
// nlbr seems to be hopelessly messed up
|
||||
// $Text = nl2br($Text);
|
||||
|
||||
$text = preg_replace_callback("/\[img\=(.*?)\](.*?)\[\/img\]/ism",
|
||||
function ($matches) use ($simple_html) {
|
||||
$matches[1] = self::proxyUrl($matches[1], $simple_html);
|
||||
$matches[2] = htmlspecialchars($matches[2], ENT_COMPAT);
|
||||
return '<img src="' . $matches[1] . '" alt="' . $matches[2] . '" title="' . $matches[2] . '">';
|
||||
},
|
||||
$text);
|
||||
// We'll emulate it.
|
||||
|
||||
// Images
|
||||
// [img]pathtoimage[/img]
|
||||
$text = preg_replace_callback(
|
||||
"/\[img\](.*?)\[\/img\]/ism",
|
||||
function ($matches) use ($simple_html) {
|
||||
if (strpos($matches[1], "data:image/") === 0) {
|
||||
return $matches[0];
|
||||
$text = trim($text);
|
||||
$text = str_replace("\r\n", "\n", $text);
|
||||
|
||||
// Remove linefeeds inside of the table elements. See issue #6799
|
||||
$search = ["\n[th]", "[th]\n", " [th]", "\n[/th]", "[/th]\n", "[/th] ",
|
||||
"\n[td]", "[td]\n", " [td]", "\n[/td]", "[/td]\n", "[/td] ",
|
||||
"\n[tr]", "[tr]\n", " [tr]", "[tr] ", "\n[/tr]", "[/tr]\n", " [/tr]", "[/tr] ",
|
||||
"[table]\n", "[table] ", " [table]", "\n[/table]", " [/table]", "[/table] "];
|
||||
$replace = ["[th]", "[th]", "[th]", "[/th]", "[/th]", "[/th]",
|
||||
"[td]", "[td]", "[td]", "[/td]", "[/td]", "[/td]",
|
||||
"[tr]", "[tr]", "[tr]", "[tr]", "[/tr]", "[/tr]", "[/tr]", "[/tr]",
|
||||
"[table]", "[table]", "[table]", "[/table]", "[/table]", "[/table]"];
|
||||
do {
|
||||
$oldtext = $text;
|
||||
$text = str_replace($search, $replace, $text);
|
||||
} while ($oldtext != $text);
|
||||
|
||||
// Replace these here only once
|
||||
$search = ["\n[table]", "[/table]\n"];
|
||||
$replace = ["[table]", "[/table]"];
|
||||
$text = str_replace($search, $replace, $text);
|
||||
|
||||
// removing multiplicated newlines
|
||||
if (DI::config()->get('system', 'remove_multiplicated_lines')) {
|
||||
$search = ["\n\n\n", "\n ", " \n", "[/quote]\n\n", "\n[/quote]", "[/li]\n", "\n[li]", "\n[ul]", "[/ul]\n", "\n\n[share ", "[/attachment]\n",
|
||||
"\n[h1]", "[/h1]\n", "\n[h2]", "[/h2]\n", "\n[h3]", "[/h3]\n", "\n[h4]", "[/h4]\n", "\n[h5]", "[/h5]\n", "\n[h6]", "[/h6]\n"];
|
||||
$replace = ["\n\n", "\n", "\n", "[/quote]\n", "[/quote]", "[/li]", "[li]", "[ul]", "[/ul]", "\n[share ", "[/attachment]",
|
||||
"[h1]", "[/h1]", "[h2]", "[/h2]", "[h3]", "[/h3]", "[h4]", "[/h4]", "[h5]", "[/h5]", "[h6]", "[/h6]"];
|
||||
do {
|
||||
$oldtext = $text;
|
||||
$text = str_replace($search, $replace, $text);
|
||||
} while ($oldtext != $text);
|
||||
}
|
||||
|
||||
$matches[1] = self::proxyUrl($matches[1], $simple_html);
|
||||
return "[img]" . $matches[1] . "[/img]";
|
||||
},
|
||||
$text
|
||||
);
|
||||
|
||||
$text = preg_replace("/\[img\](.*?)\[\/img\]/ism", '<img src="$1" alt="' . DI::l10n()->t('Image/photo') . '" />', $text);
|
||||
$text = preg_replace("/\[zmg\](.*?)\[\/zmg\]/ism", '<img src="$1" alt="' . DI::l10n()->t('Image/photo') . '" />', $text);
|
||||
|
||||
$text = preg_replace("/\[crypt\](.*?)\[\/crypt\]/ism", '<br/><img src="' .DI::baseUrl() . '/images/lock_icon.gif" alt="' . DI::l10n()->t('Encrypted content') . '" title="' . DI::l10n()->t('Encrypted content') . '" /><br />', $text);
|
||||
$text = preg_replace("/\[crypt(.*?)\](.*?)\[\/crypt\]/ism", '<br/><img src="' .DI::baseUrl() . '/images/lock_icon.gif" alt="' . DI::l10n()->t('Encrypted content') . '" title="' . '$1' . ' ' . DI::l10n()->t('Encrypted content') . '" /><br />', $text);
|
||||
//$Text = preg_replace("/\[crypt=(.*?)\](.*?)\[\/crypt\]/ism", '<br/><img src="' .DI::baseUrl() . '/images/lock_icon.gif" alt="' . DI::l10n()->t('Encrypted content') . '" title="' . '$1' . ' ' . DI::l10n()->t('Encrypted content') . '" /><br />', $Text);
|
||||
|
||||
// Simplify "video" element
|
||||
$text = preg_replace('(\[video.*?\ssrc\s?=\s?([^\s\]]+).*?\].*?\[/video\])ism', '[video]$1[/video]', $text);
|
||||
|
||||
// Try to Oembed
|
||||
if ($try_oembed) {
|
||||
$text = preg_replace("/\[video\](.*?\.(ogg|ogv|oga|ogm|webm|mp4).*?)\[\/video\]/ism", '<video src="$1" controls="controls" width="' . $a->videowidth . '" height="' . $a->videoheight . '" loop="true"><a href="$1">$1</a></video>', $text);
|
||||
$text = preg_replace("/\[audio\](.*?)\[\/audio\]/ism", '<audio src="$1" controls="controls"><a href="$1">$1</a></audio>', $text);
|
||||
|
||||
$text = preg_replace_callback("/\[video\](.*?)\[\/video\]/ism", $try_oembed_callback, $text);
|
||||
$text = preg_replace_callback("/\[audio\](.*?)\[\/audio\]/ism", $try_oembed_callback, $text);
|
||||
} else {
|
||||
$text = preg_replace("/\[video\](.*?)\[\/video\]/ism",
|
||||
'<a href="$1" target="_blank" rel="noopener noreferrer">$1</a>', $text);
|
||||
$text = preg_replace("/\[audio\](.*?)\[\/audio\]/ism",
|
||||
'<a href="$1" target="_blank" rel="noopener noreferrer">$1</a>', $text);
|
||||
}
|
||||
|
||||
// html5 video and audio
|
||||
|
||||
|
||||
if ($try_oembed) {
|
||||
$text = preg_replace("/\[iframe\](.*?)\[\/iframe\]/ism", '<iframe src="$1" width="' . $a->videowidth . '" height="' . $a->videoheight . '"><a href="$1">$1</a></iframe>', $text);
|
||||
} else {
|
||||
$text = preg_replace("/\[iframe\](.*?)\[\/iframe\]/ism", '<a href="$1">$1</a>', $text);
|
||||
}
|
||||
|
||||
// Youtube extensions
|
||||
if ($try_oembed) {
|
||||
$text = preg_replace_callback("/\[youtube\](https?:\/\/www.youtube.com\/watch\?v\=.*?)\[\/youtube\]/ism", $try_oembed_callback, $text);
|
||||
$text = preg_replace_callback("/\[youtube\](www.youtube.com\/watch\?v\=.*?)\[\/youtube\]/ism", $try_oembed_callback, $text);
|
||||
$text = preg_replace_callback("/\[youtube\](https?:\/\/youtu.be\/.*?)\[\/youtube\]/ism", $try_oembed_callback, $text);
|
||||
}
|
||||
|
||||
$text = preg_replace("/\[youtube\]https?:\/\/www.youtube.com\/watch\?v\=(.*?)\[\/youtube\]/ism", '[youtube]$1[/youtube]', $text);
|
||||
$text = preg_replace("/\[youtube\]https?:\/\/www.youtube.com\/embed\/(.*?)\[\/youtube\]/ism", '[youtube]$1[/youtube]', $text);
|
||||
$text = preg_replace("/\[youtube\]https?:\/\/youtu.be\/(.*?)\[\/youtube\]/ism", '[youtube]$1[/youtube]', $text);
|
||||
|
||||
if ($try_oembed) {
|
||||
$text = preg_replace("/\[youtube\]([A-Za-z0-9\-_=]+)(.*?)\[\/youtube\]/ism", '<iframe width="' . $a->videowidth . '" height="' . $a->videoheight . '" src="https://www.youtube.com/embed/$1" frameborder="0" ></iframe>', $text);
|
||||
} else {
|
||||
$text = preg_replace("/\[youtube\]([A-Za-z0-9\-_=]+)(.*?)\[\/youtube\]/ism",
|
||||
'<a href="https://www.youtube.com/watch?v=$1" target="_blank" rel="noopener noreferrer">https://www.youtube.com/watch?v=$1</a>', $text);
|
||||
}
|
||||
|
||||
if ($try_oembed) {
|
||||
$text = preg_replace_callback("/\[vimeo\](https?:\/\/player.vimeo.com\/video\/[0-9]+).*?\[\/vimeo\]/ism", $try_oembed_callback, $text);
|
||||
$text = preg_replace_callback("/\[vimeo\](https?:\/\/vimeo.com\/[0-9]+).*?\[\/vimeo\]/ism", $try_oembed_callback, $text);
|
||||
}
|
||||
|
||||
$text = preg_replace("/\[vimeo\]https?:\/\/player.vimeo.com\/video\/([0-9]+)(.*?)\[\/vimeo\]/ism", '[vimeo]$1[/vimeo]', $text);
|
||||
$text = preg_replace("/\[vimeo\]https?:\/\/vimeo.com\/([0-9]+)(.*?)\[\/vimeo\]/ism", '[vimeo]$1[/vimeo]', $text);
|
||||
|
||||
if ($try_oembed) {
|
||||
$text = preg_replace("/\[vimeo\]([0-9]+)(.*?)\[\/vimeo\]/ism", '<iframe width="' . $a->videowidth . '" height="' . $a->videoheight . '" src="https://player.vimeo.com/video/$1" frameborder="0" ></iframe>', $text);
|
||||
} else {
|
||||
$text = preg_replace("/\[vimeo\]([0-9]+)(.*?)\[\/vimeo\]/ism",
|
||||
'<a href="https://vimeo.com/$1" target="_blank" rel="noopener noreferrer">https://vimeo.com/$1</a>', $text);
|
||||
}
|
||||
|
||||
// oembed tag
|
||||
$text = OEmbed::BBCode2HTML($text);
|
||||
|
||||
// Avoid triple linefeeds through oembed
|
||||
$text = str_replace("<br style='clear:left'></span><br /><br />", "<br style='clear:left'></span><br />", $text);
|
||||
|
||||
// If we found an event earlier, strip out all the event code and replace with a reformatted version.
|
||||
// Replace the event-start section with the entire formatted event. The other bbcode is stripped.
|
||||
// Summary (e.g. title) is required, earlier revisions only required description (in addition to
|
||||
// start which is always required). Allow desc with a missing summary for compatibility.
|
||||
|
||||
if ((!empty($ev['desc']) || !empty($ev['summary'])) && !empty($ev['start'])) {
|
||||
$sub = Event::getHTML($ev, $simple_html);
|
||||
|
||||
$text = preg_replace("/\[event\-summary\](.*?)\[\/event\-summary\]/ism", '', $text);
|
||||
$text = preg_replace("/\[event\-description\](.*?)\[\/event\-description\]/ism", '', $text);
|
||||
$text = preg_replace("/\[event\-start\](.*?)\[\/event\-start\]/ism", $sub, $text);
|
||||
$text = preg_replace("/\[event\-finish\](.*?)\[\/event\-finish\]/ism", '', $text);
|
||||
$text = preg_replace("/\[event\-location\](.*?)\[\/event\-location\]/ism", '', $text);
|
||||
$text = preg_replace("/\[event\-adjust\](.*?)\[\/event\-adjust\]/ism", '', $text);
|
||||
$text = preg_replace("/\[event\-id\](.*?)\[\/event\-id\]/ism", '', $text);
|
||||
}
|
||||
|
||||
// Replace non graphical smilies for external posts
|
||||
if (!$nosmile && !$for_plaintext) {
|
||||
$text = Smilies::replace($text);
|
||||
}
|
||||
|
||||
if (!$for_plaintext) {
|
||||
if (in_array($simple_html, [7, 9])) {
|
||||
$text = preg_replace_callback("/\[url\](.*?)\[\/url\]/ism", 'self::convertUrlForActivityPubCallback', $text);
|
||||
$text = preg_replace_callback("/\[url\=(.*?)\](.*?)\[\/url\]/ism", 'self::convertUrlForActivityPubCallback', $text);
|
||||
}
|
||||
} else {
|
||||
$text = preg_replace("(\[url\](.*?)\[\/url\])ism", " $1 ", $text);
|
||||
$text = preg_replace_callback("&\[url=([^\[\]]*)\]\[img\](.*)\[\/img\]\[\/url\]&Usi", 'self::removePictureLinksCallback', $text);
|
||||
}
|
||||
|
||||
$text = str_replace(["\r","\n"], ['<br />', '<br />'], $text);
|
||||
|
||||
// Remove all hashtag addresses
|
||||
if ($simple_html && !in_array($simple_html, [3, 7, 9])) {
|
||||
$text = preg_replace("/([#@!])\[url\=(.*?)\](.*?)\[\/url\]/ism", '$1$3', $text);
|
||||
} elseif ($simple_html == 3) {
|
||||
// The ! is converted to @ since Diaspora only understands the @
|
||||
$text = preg_replace("/([@!])\[url\=(.*?)\](.*?)\[\/url\]/ism",
|
||||
'@<a href="$2">$3</a>',
|
||||
$text);
|
||||
} elseif (in_array($simple_html, [7, 9])) {
|
||||
$text = preg_replace("/([@!])\[url\=(.*?)\](.*?)\[\/url\]/ism",
|
||||
'$1<span class="vcard"><a href="$2" class="url u-url mention" title="$3"><span class="fn nickname mention">$3</span></a></span>',
|
||||
$text);
|
||||
} elseif (!$simple_html) {
|
||||
$text = preg_replace("/([@!])\[url\=(.*?)\](.*?)\[\/url\]/ism",
|
||||
'$1<a href="$2" class="userinfo mention" title="$3">$3</a>',
|
||||
$text);
|
||||
}
|
||||
|
||||
// Bookmarks in red - will be converted to bookmarks in friendica
|
||||
$text = preg_replace("/#\^\[url\](.*?)\[\/url\]/ism", '[bookmark=$1]$1[/bookmark]', $text);
|
||||
$text = preg_replace("/#\^\[url\=(.*?)\](.*?)\[\/url\]/ism", '[bookmark=$1]$2[/bookmark]', $text);
|
||||
$text = preg_replace("/#\[url\=.*?\]\^\[\/url\]\[url\=(.*?)\](.*?)\[\/url\]/i",
|
||||
"[bookmark=$1]$2[/bookmark]", $text);
|
||||
|
||||
if (in_array($simple_html, [2, 6, 7, 8])) {
|
||||
$text = preg_replace_callback("/([^#@!])\[url\=([^\]]*)\](.*?)\[\/url\]/ism", "self::expandLinksCallback", $text);
|
||||
//$Text = preg_replace("/[^#@!]\[url\=([^\]]*)\](.*?)\[\/url\]/ism", ' $2 [url]$1[/url]', $Text);
|
||||
$text = preg_replace("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/ism", ' $2 [url]$1[/url]',$text);
|
||||
}
|
||||
|
||||
if ($simple_html == 5) {
|
||||
$text = preg_replace("/[^#@!]\[url\=(.*?)\](.*?)\[\/url\]/ism", '[url]$1[/url]', $text);
|
||||
}
|
||||
|
||||
// Perform URL Search
|
||||
if ($try_oembed) {
|
||||
$text = preg_replace_callback("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/ism", $try_oembed_callback, $text);
|
||||
}
|
||||
|
||||
if ($simple_html == 5) {
|
||||
$text = preg_replace("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/ism", '[url]$1[/url]', $text);
|
||||
} else {
|
||||
$text = preg_replace("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/ism", '[url=$1]$2[/url]', $text);
|
||||
}
|
||||
|
||||
// Handle Diaspora posts
|
||||
$text = preg_replace_callback(
|
||||
"&\[url=/?posts/([^\[\]]*)\](.*)\[\/url\]&Usi",
|
||||
function ($match) {
|
||||
return "[url=" . DI::baseUrl() . "/display/" . $match[1] . "]" . $match[2] . "[/url]";
|
||||
}, $text
|
||||
);
|
||||
|
||||
$text = preg_replace_callback(
|
||||
"&\[url=/people\?q\=(.*)\](.*)\[\/url\]&Usi",
|
||||
function ($match) {
|
||||
return "[url=" . DI::baseUrl() . "/search?search=%40" . $match[1] . "]" . $match[2] . "[/url]";
|
||||
}, $text
|
||||
);
|
||||
|
||||
// Server independent link to posts and comments
|
||||
// See issue: https://github.com/diaspora/diaspora_federation/issues/75
|
||||
$expression = "=diaspora://.*?/post/([0-9A-Za-z\-_@.:]{15,254}[0-9A-Za-z])=ism";
|
||||
$text = preg_replace($expression, DI::baseUrl()."/display/$1", $text);
|
||||
|
||||
/* Tag conversion
|
||||
* Supports:
|
||||
* - #[url=<anything>]<term>[/url]
|
||||
* - [url=<anything>]#<term>[/url]
|
||||
*/
|
||||
$text = preg_replace_callback("/(?:#\[url\=[^\[\]]*\]|\[url\=[^\[\]]*\]#)(.*?)\[\/url\]/ism", function($matches) {
|
||||
return '#<a href="'
|
||||
. DI::baseUrl() . '/search?tag=' . rawurlencode($matches[1])
|
||||
. '" class="tag" rel="tag" title="' . XML::escape($matches[1]) . '">'
|
||||
. XML::escape($matches[1])
|
||||
. '</a>';
|
||||
}, $text);
|
||||
|
||||
// We need no target="_blank" rel="noopener noreferrer" for local links
|
||||
// convert links start with DI::baseUrl() as local link without the target="_blank" rel="noopener noreferrer" attribute
|
||||
$escapedBaseUrl = preg_quote(DI::baseUrl(), '/');
|
||||
$text = preg_replace("/\[url\](".$escapedBaseUrl.".*?)\[\/url\]/ism", '<a href="$1">$1</a>', $text);
|
||||
$text = preg_replace("/\[url\=(".$escapedBaseUrl.".*?)\](.*?)\[\/url\]/ism", '<a href="$1">$2</a>', $text);
|
||||
|
||||
$text = preg_replace("/\[url\](.*?)\[\/url\]/ism", '<a href="$1" target="_blank" rel="noopener noreferrer">$1</a>', $text);
|
||||
$text = preg_replace("/\[url\=(.*?)\](.*?)\[\/url\]/ism", '<a href="$1" target="_blank" rel="noopener noreferrer">$2</a>', $text);
|
||||
|
||||
// Red compatibility, though the link can't be authenticated on Friendica
|
||||
$text = preg_replace("/\[zrl\=(.*?)\](.*?)\[\/zrl\]/ism", '<a href="$1" target="_blank" rel="noopener noreferrer">$2</a>', $text);
|
||||
|
||||
|
||||
// we may need to restrict this further if it picks up too many strays
|
||||
// link acct:user@host to a webfinger profile redirector
|
||||
|
||||
$text = preg_replace('/acct:([^@]+)@((?!\-)(?:[a-zA-Z\d\-]{0,62}[a-zA-Z\d]\.){1,126}(?!\d+)[a-zA-Z\d]{1,63})/', '<a href="' . DI::baseUrl() . '/acctlink?addr=$1@$2" target="extlink">acct:$1@$2</a>', $text);
|
||||
|
||||
// Perform MAIL Search
|
||||
$text = preg_replace("/\[mail\](.*?)\[\/mail\]/", '<a href="mailto:$1">$1</a>', $text);
|
||||
$text = preg_replace("/\[mail\=(.*?)\](.*?)\[\/mail\]/", '<a href="mailto:$1">$2</a>', $text);
|
||||
|
||||
// Unhide all [noparse] contained bbtags unspacefying them
|
||||
// and triming the [noparse] tag.
|
||||
|
||||
$text = preg_replace_callback("/\[noparse\](.*?)\[\/noparse\]/ism", 'self::unescapeNoparseCallback', $text);
|
||||
$text = preg_replace_callback("/\[nobb\](.*?)\[\/nobb\]/ism", 'self::unescapeNoparseCallback', $text);
|
||||
$text = preg_replace_callback("/\[pre\](.*?)\[\/pre\]/ism", 'self::unescapeNoparseCallback', $text);
|
||||
|
||||
/// @todo What is the meaning of these lines?
|
||||
$text = preg_replace('/\[\&\;([#a-z0-9]+)\;\]/', '&$1;', $text);
|
||||
$text = preg_replace('/\&\#039\;/', '\'', $text);
|
||||
|
||||
// Currently deactivated, it made problems with " inside of alt texts.
|
||||
//$text = preg_replace('/\"\;/', '"', $text);
|
||||
|
||||
// fix any escaped ampersands that may have been converted into links
|
||||
$text = preg_replace('/\<([^>]*?)(src|href)=(.*?)\&\;(.*?)\>/ism', '<$1$2=$3&$4>', $text);
|
||||
|
||||
// sanitizes src attributes (http and redir URLs for displaying in a web page, cid used for inline images in emails)
|
||||
$allowed_src_protocols = ['//', 'http://', 'https://', 'redir/', 'cid:'];
|
||||
|
||||
array_walk($allowed_src_protocols, function(&$value) { $value = preg_quote($value, '#');});
|
||||
|
||||
$text = preg_replace('#<([^>]*?)(src)="(?!' . implode('|', $allowed_src_protocols) . ')(.*?)"(.*?)>#ism',
|
||||
'<$1$2=""$4 data-original-src="$3" class="invalid-src" title="' . DI::l10n()->t('Invalid source protocol') . '">', $text);
|
||||
|
||||
// sanitize href attributes (only whitelisted protocols URLs)
|
||||
// default value for backward compatibility
|
||||
$allowed_link_protocols = DI::config()->get('system', 'allowed_link_protocols', []);
|
||||
|
||||
// Always allowed protocol even if config isn't set or not including it
|
||||
$allowed_link_protocols[] = '//';
|
||||
$allowed_link_protocols[] = 'http://';
|
||||
$allowed_link_protocols[] = 'https://';
|
||||
$allowed_link_protocols[] = 'redir/';
|
||||
|
||||
array_walk($allowed_link_protocols, function(&$value) { $value = preg_quote($value, '#');});
|
||||
|
||||
$regex = '#<([^>]*?)(href)="(?!' . implode('|', $allowed_link_protocols) . ')(.*?)"(.*?)>#ism';
|
||||
$text = preg_replace($regex, '<$1$2="javascript:void(0)"$4 data-original-href="$3" class="invalid-href" title="' . DI::l10n()->t('Invalid link protocol') . '">', $text);
|
||||
|
||||
// Shared content
|
||||
$text = self::convertShare(
|
||||
$text,
|
||||
function (array $attributes, array $author_contact, $content, $is_quote_share) use ($simple_html) {
|
||||
return self::convertShareCallback($attributes, $author_contact, $content, $is_quote_share, $simple_html);
|
||||
}
|
||||
);
|
||||
|
||||
if ($saved_image) {
|
||||
$text = self::interpolateSavedImagesIntoItemBody($text, $saved_image);
|
||||
}
|
||||
|
||||
// Restore code blocks
|
||||
$text = preg_replace_callback('/#codeblock-([0-9]+)#/iU',
|
||||
function ($matches) use ($codeblocks) {
|
||||
$return = $matches[0];
|
||||
if (isset($codeblocks[intval($matches[1])])) {
|
||||
$return = $codeblocks[$matches[1]];
|
||||
/// @todo Have a closer look at the different html modes
|
||||
// Handle attached links or videos
|
||||
if ($simple_html == self::ACTIVITYPUB) {
|
||||
$text = self::removeAttachment($text);
|
||||
} elseif (!in_array($simple_html, [self::INTERNAL, self::CONNECTORS])) {
|
||||
$text = self::removeAttachment($text, true);
|
||||
} else {
|
||||
$text = self::convertAttachment($text, $simple_html, $try_oembed);
|
||||
}
|
||||
|
||||
// leave open the posibility of [map=something]
|
||||
// this is replaced in Item::prepareBody() which has knowledge of the item location
|
||||
if (strpos($text, '[/map]') !== false) {
|
||||
$text = preg_replace_callback(
|
||||
"/\[map\](.*?)\[\/map\]/ism",
|
||||
function ($match) use ($simple_html) {
|
||||
return str_replace($match[0], '<p class="map">' . Map::byLocation($match[1], $simple_html) . '</p>', $match[0]);
|
||||
},
|
||||
$text
|
||||
);
|
||||
}
|
||||
|
||||
if (strpos($text, '[map=') !== false) {
|
||||
$text = preg_replace_callback(
|
||||
"/\[map=(.*?)\]/ism",
|
||||
function ($match) use ($simple_html) {
|
||||
return str_replace($match[0], '<p class="map">' . Map::byCoordinates(str_replace('/', ' ', $match[1]), $simple_html) . '</p>', $match[0]);
|
||||
},
|
||||
$text
|
||||
);
|
||||
}
|
||||
|
||||
if (strpos($text, '[map]') !== false) {
|
||||
$text = preg_replace("/\[map\]/", '<p class="map"></p>', $text);
|
||||
}
|
||||
|
||||
// Check for headers
|
||||
$text = preg_replace("(\[h1\](.*?)\[\/h1\])ism", '<h1>$1</h1>', $text);
|
||||
$text = preg_replace("(\[h2\](.*?)\[\/h2\])ism", '<h2>$1</h2>', $text);
|
||||
$text = preg_replace("(\[h3\](.*?)\[\/h3\])ism", '<h3>$1</h3>', $text);
|
||||
$text = preg_replace("(\[h4\](.*?)\[\/h4\])ism", '<h4>$1</h4>', $text);
|
||||
$text = preg_replace("(\[h5\](.*?)\[\/h5\])ism", '<h5>$1</h5>', $text);
|
||||
$text = preg_replace("(\[h6\](.*?)\[\/h6\])ism", '<h6>$1</h6>', $text);
|
||||
|
||||
// Check for paragraph
|
||||
$text = preg_replace("(\[p\](.*?)\[\/p\])ism", '<p>$1</p>', $text);
|
||||
|
||||
// Check for bold text
|
||||
$text = preg_replace("(\[b\](.*?)\[\/b\])ism", '<strong>$1</strong>', $text);
|
||||
|
||||
// Check for Italics text
|
||||
$text = preg_replace("(\[i\](.*?)\[\/i\])ism", '<em>$1</em>', $text);
|
||||
|
||||
// Check for Underline text
|
||||
$text = preg_replace("(\[u\](.*?)\[\/u\])ism", '<u>$1</u>', $text);
|
||||
|
||||
// Check for strike-through text
|
||||
$text = preg_replace("(\[s\](.*?)\[\/s\])ism", '<s>$1</s>', $text);
|
||||
|
||||
// Check for over-line text
|
||||
$text = preg_replace("(\[o\](.*?)\[\/o\])ism", '<span class="overline">$1</span>', $text);
|
||||
|
||||
// Check for colored text
|
||||
$text = preg_replace("(\[color=(.*?)\](.*?)\[\/color\])ism", "<span style=\"color: $1;\">$2</span>", $text);
|
||||
|
||||
// Check for sized text
|
||||
// [size=50] --> font-size: 50px (with the unit).
|
||||
if ($simple_html != self::DIASPORA) {
|
||||
$text = preg_replace("(\[size=(\d*?)\](.*?)\[\/size\])ism", "<span style=\"font-size: $1px; line-height: initial;\">$2</span>", $text);
|
||||
$text = preg_replace("(\[size=(.*?)\](.*?)\[\/size\])ism", "<span style=\"font-size: $1; line-height: initial;\">$2</span>", $text);
|
||||
} else {
|
||||
// Issue 2199: Diaspora doesn't interpret the construct above, nor the <small> or <big> element
|
||||
$text = preg_replace("(\[size=(.*?)\](.*?)\[\/size\])ism", "$2", $text);
|
||||
}
|
||||
|
||||
|
||||
// Check for centered text
|
||||
$text = preg_replace("(\[center\](.*?)\[\/center\])ism", "<div style=\"text-align:center;\">$1</div>", $text);
|
||||
|
||||
// Check for list text
|
||||
$text = str_replace("[*]", "<li>", $text);
|
||||
|
||||
// Check for style sheet commands
|
||||
$text = preg_replace_callback(
|
||||
"(\[style=(.*?)\](.*?)\[\/style\])ism",
|
||||
function ($match) {
|
||||
return "<span style=\"" . HTML::sanitizeCSS($match[1]) . ";\">" . $match[2] . "</span>";
|
||||
},
|
||||
$text
|
||||
);
|
||||
|
||||
// Check for CSS classes
|
||||
$text = preg_replace_callback(
|
||||
"(\[class=(.*?)\](.*?)\[\/class\])ism",
|
||||
function ($match) {
|
||||
return "<span class=\"" . HTML::sanitizeCSS($match[1]) . "\">" . $match[2] . "</span>";
|
||||
},
|
||||
$text
|
||||
);
|
||||
|
||||
// handle nested lists
|
||||
$endlessloop = 0;
|
||||
|
||||
while ((((strpos($text, "[/list]") !== false) && (strpos($text, "[list") !== false)) ||
|
||||
((strpos($text, "[/ol]") !== false) && (strpos($text, "[ol]") !== false)) ||
|
||||
((strpos($text, "[/ul]") !== false) && (strpos($text, "[ul]") !== false)) ||
|
||||
((strpos($text, "[/li]") !== false) && (strpos($text, "[li]") !== false))) && (++$endlessloop < 20)) {
|
||||
$text = preg_replace("/\[list\](.*?)\[\/list\]/ism", '<ul class="listbullet" style="list-style-type: circle;">$1</ul>', $text);
|
||||
$text = preg_replace("/\[list=\](.*?)\[\/list\]/ism", '<ul class="listnone" style="list-style-type: none;">$1</ul>', $text);
|
||||
$text = preg_replace("/\[list=1\](.*?)\[\/list\]/ism", '<ul class="listdecimal" style="list-style-type: decimal;">$1</ul>', $text);
|
||||
$text = preg_replace("/\[list=((?-i)i)\](.*?)\[\/list\]/ism", '<ul class="listlowerroman" style="list-style-type: lower-roman;">$2</ul>', $text);
|
||||
$text = preg_replace("/\[list=((?-i)I)\](.*?)\[\/list\]/ism", '<ul class="listupperroman" style="list-style-type: upper-roman;">$2</ul>', $text);
|
||||
$text = preg_replace("/\[list=((?-i)a)\](.*?)\[\/list\]/ism", '<ul class="listloweralpha" style="list-style-type: lower-alpha;">$2</ul>', $text);
|
||||
$text = preg_replace("/\[list=((?-i)A)\](.*?)\[\/list\]/ism", '<ul class="listupperalpha" style="list-style-type: upper-alpha;">$2</ul>', $text);
|
||||
$text = preg_replace("/\[ul\](.*?)\[\/ul\]/ism", '<ul class="listbullet" style="list-style-type: circle;">$1</ul>', $text);
|
||||
$text = preg_replace("/\[ol\](.*?)\[\/ol\]/ism", '<ul class="listdecimal" style="list-style-type: decimal;">$1</ul>', $text);
|
||||
$text = preg_replace("/\[li\](.*?)\[\/li\]/ism", '<li>$1</li>', $text);
|
||||
}
|
||||
|
||||
$text = preg_replace("/\[th\](.*?)\[\/th\]/sm", '<th>$1</th>', $text);
|
||||
$text = preg_replace("/\[td\](.*?)\[\/td\]/sm", '<td>$1</td>', $text);
|
||||
$text = preg_replace("/\[tr\](.*?)\[\/tr\]/sm", '<tr>$1</tr>', $text);
|
||||
$text = preg_replace("/\[table\](.*?)\[\/table\]/sm", '<table>$1</table>', $text);
|
||||
|
||||
$text = preg_replace("/\[table border=1\](.*?)\[\/table\]/sm", '<table border="1" >$1</table>', $text);
|
||||
$text = preg_replace("/\[table border=0\](.*?)\[\/table\]/sm", '<table border="0" >$1</table>', $text);
|
||||
|
||||
$text = str_replace('[hr]', '<hr />', $text);
|
||||
|
||||
if (!$for_plaintext) {
|
||||
$escaped = [];
|
||||
|
||||
// Escaping BBCodes susceptible to contain rogue URL we don'' want the autolinker to catch
|
||||
$text = preg_replace_callback('#\[(url|img|audio|video|youtube|vimeo|share|attachment|iframe|bookmark).+?\[/\1\]#ism',
|
||||
function ($matches) use (&$escaped) {
|
||||
$return = '{escaped-' . count($escaped) . '}';
|
||||
$escaped[] = $matches[0];
|
||||
|
||||
return $return;
|
||||
},
|
||||
$text
|
||||
);
|
||||
|
||||
// Autolinker for isolated URLs
|
||||
$text = preg_replace(Strings::autoLinkRegEx(), '[url]$1[/url]', $text);
|
||||
|
||||
// Restoring escaped blocks
|
||||
$text = preg_replace_callback('/{escaped-([0-9]+)}/iU',
|
||||
function ($matches) use ($escaped) {
|
||||
return $escaped[intval($matches[1])] ?? $matches[0];
|
||||
},
|
||||
$text
|
||||
);
|
||||
}
|
||||
|
||||
// This is actually executed in Item::prepareBody()
|
||||
|
||||
$nosmile = strpos($text, '[nosmile]') !== false;
|
||||
$text = str_replace('[nosmile]', '', $text);
|
||||
|
||||
// Check for font change text
|
||||
$text = preg_replace("/\[font=(.*?)\](.*?)\[\/font\]/sm", "<span style=\"font-family: $1;\">$2</span>", $text);
|
||||
|
||||
// Declare the format for [spoiler] layout
|
||||
$SpoilerLayout = '<details class="spoiler"><summary>' . DI::l10n()->t('Click to open/close') . '</summary>$1</details>';
|
||||
|
||||
// Check for [spoiler] text
|
||||
// handle nested quotes
|
||||
$endlessloop = 0;
|
||||
while ((strpos($text, "[/spoiler]") !== false) && (strpos($text, "[spoiler]") !== false) && (++$endlessloop < 20)) {
|
||||
$text = preg_replace("/\[spoiler\](.*?)\[\/spoiler\]/ism", $SpoilerLayout, $text);
|
||||
}
|
||||
|
||||
// Check for [spoiler=Title] text
|
||||
|
||||
// handle nested quotes
|
||||
$endlessloop = 0;
|
||||
while ((strpos($text, "[/spoiler]")!== false) && (strpos($text, "[spoiler=") !== false) && (++$endlessloop < 20)) {
|
||||
$text = preg_replace("/\[spoiler=[\"\']*(.*?)[\"\']*\](.*?)\[\/spoiler\]/ism",
|
||||
'<details class="spoiler"><summary>$1</summary>$2</details>',
|
||||
$text);
|
||||
}
|
||||
|
||||
// Declare the format for [quote] layout
|
||||
$QuoteLayout = '<blockquote>$1</blockquote>';
|
||||
|
||||
// Check for [quote] text
|
||||
// handle nested quotes
|
||||
$endlessloop = 0;
|
||||
while ((strpos($text, "[/quote]") !== false) && (strpos($text, "[quote]") !== false) && (++$endlessloop < 20)) {
|
||||
$text = preg_replace("/\[quote\](.*?)\[\/quote\]/ism", "$QuoteLayout", $text);
|
||||
}
|
||||
|
||||
// Check for [quote=Author] text
|
||||
|
||||
$t_wrote = DI::l10n()->t('$1 wrote:');
|
||||
|
||||
// handle nested quotes
|
||||
$endlessloop = 0;
|
||||
while ((strpos($text, "[/quote]")!== false) && (strpos($text, "[quote=") !== false) && (++$endlessloop < 20)) {
|
||||
$text = preg_replace("/\[quote=[\"\']*(.*?)[\"\']*\](.*?)\[\/quote\]/ism",
|
||||
"<p><strong class=".'"author"'.">" . $t_wrote . "</strong></p><blockquote>$2</blockquote>",
|
||||
$text);
|
||||
}
|
||||
|
||||
|
||||
// [img=widthxheight]image source[/img]
|
||||
$text = preg_replace_callback(
|
||||
"/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism",
|
||||
function ($matches) use ($simple_html) {
|
||||
if (strpos($matches[3], "data:image/") === 0) {
|
||||
return $matches[0];
|
||||
}
|
||||
|
||||
$matches[3] = self::proxyUrl($matches[3], $simple_html);
|
||||
return "[img=" . $matches[1] . "x" . $matches[2] . "]" . $matches[3] . "[/img]";
|
||||
},
|
||||
$text
|
||||
);
|
||||
|
||||
$text = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", '<img src="$3" style="width: $1px;" >', $text);
|
||||
$text = preg_replace("/\[zmg\=([0-9]*)x([0-9]*)\](.*?)\[\/zmg\]/ism", '<img class="zrl" src="$3" style="width: $1px;" >', $text);
|
||||
|
||||
$text = preg_replace_callback("/\[img\=(.*?)\](.*?)\[\/img\]/ism",
|
||||
function ($matches) use ($simple_html) {
|
||||
$matches[1] = self::proxyUrl($matches[1], $simple_html);
|
||||
$matches[2] = htmlspecialchars($matches[2], ENT_COMPAT);
|
||||
return '<img src="' . $matches[1] . '" alt="' . $matches[2] . '" title="' . $matches[2] . '">';
|
||||
},
|
||||
$text);
|
||||
|
||||
// Images
|
||||
// [img]pathtoimage[/img]
|
||||
$text = preg_replace_callback(
|
||||
"/\[img\](.*?)\[\/img\]/ism",
|
||||
function ($matches) use ($simple_html) {
|
||||
if (strpos($matches[1], "data:image/") === 0) {
|
||||
return $matches[0];
|
||||
}
|
||||
|
||||
$matches[1] = self::proxyUrl($matches[1], $simple_html);
|
||||
return "[img]" . $matches[1] . "[/img]";
|
||||
},
|
||||
$text
|
||||
);
|
||||
|
||||
$text = preg_replace("/\[img\](.*?)\[\/img\]/ism", '<img src="$1" alt="' . DI::l10n()->t('Image/photo') . '" />', $text);
|
||||
$text = preg_replace("/\[zmg\](.*?)\[\/zmg\]/ism", '<img src="$1" alt="' . DI::l10n()->t('Image/photo') . '" />', $text);
|
||||
|
||||
$text = preg_replace("/\[crypt\](.*?)\[\/crypt\]/ism", '<br/><img src="' .DI::baseUrl() . '/images/lock_icon.gif" alt="' . DI::l10n()->t('Encrypted content') . '" title="' . DI::l10n()->t('Encrypted content') . '" /><br />', $text);
|
||||
$text = preg_replace("/\[crypt(.*?)\](.*?)\[\/crypt\]/ism", '<br/><img src="' .DI::baseUrl() . '/images/lock_icon.gif" alt="' . DI::l10n()->t('Encrypted content') . '" title="' . '$1' . ' ' . DI::l10n()->t('Encrypted content') . '" /><br />', $text);
|
||||
//$Text = preg_replace("/\[crypt=(.*?)\](.*?)\[\/crypt\]/ism", '<br/><img src="' .DI::baseUrl() . '/images/lock_icon.gif" alt="' . DI::l10n()->t('Encrypted content') . '" title="' . '$1' . ' ' . DI::l10n()->t('Encrypted content') . '" /><br />', $Text);
|
||||
|
||||
// Simplify "video" element
|
||||
$text = preg_replace('(\[video.*?\ssrc\s?=\s?([^\s\]]+).*?\].*?\[/video\])ism', '[video]$1[/video]', $text);
|
||||
|
||||
// Try to Oembed
|
||||
if ($try_oembed) {
|
||||
$text = preg_replace("/\[video\](.*?\.(ogg|ogv|oga|ogm|webm|mp4).*?)\[\/video\]/ism", '<video src="$1" controls="controls" width="' . $a->videowidth . '" height="' . $a->videoheight . '" loop="true"><a href="$1">$1</a></video>', $text);
|
||||
$text = preg_replace("/\[audio\](.*?)\[\/audio\]/ism", '<audio src="$1" controls="controls"><a href="$1">$1</a></audio>', $text);
|
||||
|
||||
$text = preg_replace_callback("/\[video\](.*?)\[\/video\]/ism", $try_oembed_callback, $text);
|
||||
$text = preg_replace_callback("/\[audio\](.*?)\[\/audio\]/ism", $try_oembed_callback, $text);
|
||||
} else {
|
||||
$text = preg_replace("/\[video\](.*?)\[\/video\]/ism",
|
||||
'<a href="$1" target="_blank" rel="noopener noreferrer">$1</a>', $text);
|
||||
$text = preg_replace("/\[audio\](.*?)\[\/audio\]/ism",
|
||||
'<a href="$1" target="_blank" rel="noopener noreferrer">$1</a>', $text);
|
||||
}
|
||||
|
||||
// html5 video and audio
|
||||
|
||||
|
||||
if ($try_oembed) {
|
||||
$text = preg_replace("/\[iframe\](.*?)\[\/iframe\]/ism", '<iframe src="$1" width="' . $a->videowidth . '" height="' . $a->videoheight . '"><a href="$1">$1</a></iframe>', $text);
|
||||
} else {
|
||||
$text = preg_replace("/\[iframe\](.*?)\[\/iframe\]/ism", '<a href="$1">$1</a>', $text);
|
||||
}
|
||||
|
||||
// Youtube extensions
|
||||
if ($try_oembed) {
|
||||
$text = preg_replace_callback("/\[youtube\](https?:\/\/www.youtube.com\/watch\?v\=.*?)\[\/youtube\]/ism", $try_oembed_callback, $text);
|
||||
$text = preg_replace_callback("/\[youtube\](www.youtube.com\/watch\?v\=.*?)\[\/youtube\]/ism", $try_oembed_callback, $text);
|
||||
$text = preg_replace_callback("/\[youtube\](https?:\/\/youtu.be\/.*?)\[\/youtube\]/ism", $try_oembed_callback, $text);
|
||||
}
|
||||
|
||||
$text = preg_replace("/\[youtube\]https?:\/\/www.youtube.com\/watch\?v\=(.*?)\[\/youtube\]/ism", '[youtube]$1[/youtube]', $text);
|
||||
$text = preg_replace("/\[youtube\]https?:\/\/www.youtube.com\/embed\/(.*?)\[\/youtube\]/ism", '[youtube]$1[/youtube]', $text);
|
||||
$text = preg_replace("/\[youtube\]https?:\/\/youtu.be\/(.*?)\[\/youtube\]/ism", '[youtube]$1[/youtube]', $text);
|
||||
|
||||
if ($try_oembed) {
|
||||
$text = preg_replace("/\[youtube\]([A-Za-z0-9\-_=]+)(.*?)\[\/youtube\]/ism", '<iframe width="' . $a->videowidth . '" height="' . $a->videoheight . '" src="https://www.youtube.com/embed/$1" frameborder="0" ></iframe>', $text);
|
||||
} else {
|
||||
$text = preg_replace("/\[youtube\]([A-Za-z0-9\-_=]+)(.*?)\[\/youtube\]/ism",
|
||||
'<a href="https://www.youtube.com/watch?v=$1" target="_blank" rel="noopener noreferrer">https://www.youtube.com/watch?v=$1</a>', $text);
|
||||
}
|
||||
|
||||
if ($try_oembed) {
|
||||
$text = preg_replace_callback("/\[vimeo\](https?:\/\/player.vimeo.com\/video\/[0-9]+).*?\[\/vimeo\]/ism", $try_oembed_callback, $text);
|
||||
$text = preg_replace_callback("/\[vimeo\](https?:\/\/vimeo.com\/[0-9]+).*?\[\/vimeo\]/ism", $try_oembed_callback, $text);
|
||||
}
|
||||
|
||||
$text = preg_replace("/\[vimeo\]https?:\/\/player.vimeo.com\/video\/([0-9]+)(.*?)\[\/vimeo\]/ism", '[vimeo]$1[/vimeo]', $text);
|
||||
$text = preg_replace("/\[vimeo\]https?:\/\/vimeo.com\/([0-9]+)(.*?)\[\/vimeo\]/ism", '[vimeo]$1[/vimeo]', $text);
|
||||
|
||||
if ($try_oembed) {
|
||||
$text = preg_replace("/\[vimeo\]([0-9]+)(.*?)\[\/vimeo\]/ism", '<iframe width="' . $a->videowidth . '" height="' . $a->videoheight . '" src="https://player.vimeo.com/video/$1" frameborder="0" ></iframe>', $text);
|
||||
} else {
|
||||
$text = preg_replace("/\[vimeo\]([0-9]+)(.*?)\[\/vimeo\]/ism",
|
||||
'<a href="https://vimeo.com/$1" target="_blank" rel="noopener noreferrer">https://vimeo.com/$1</a>', $text);
|
||||
}
|
||||
|
||||
// oembed tag
|
||||
$text = OEmbed::BBCode2HTML($text);
|
||||
|
||||
// Avoid triple linefeeds through oembed
|
||||
$text = str_replace("<br style='clear:left'></span><br /><br />", "<br style='clear:left'></span><br />", $text);
|
||||
|
||||
// If we found an event earlier, strip out all the event code and replace with a reformatted version.
|
||||
// Replace the event-start section with the entire formatted event. The other bbcode is stripped.
|
||||
// Summary (e.g. title) is required, earlier revisions only required description (in addition to
|
||||
// start which is always required). Allow desc with a missing summary for compatibility.
|
||||
|
||||
if ((!empty($ev['desc']) || !empty($ev['summary'])) && !empty($ev['start'])) {
|
||||
$sub = Event::getHTML($ev, $simple_html);
|
||||
|
||||
$text = preg_replace("/\[event\-summary\](.*?)\[\/event\-summary\]/ism", '', $text);
|
||||
$text = preg_replace("/\[event\-description\](.*?)\[\/event\-description\]/ism", '', $text);
|
||||
$text = preg_replace("/\[event\-start\](.*?)\[\/event\-start\]/ism", $sub, $text);
|
||||
$text = preg_replace("/\[event\-finish\](.*?)\[\/event\-finish\]/ism", '', $text);
|
||||
$text = preg_replace("/\[event\-location\](.*?)\[\/event\-location\]/ism", '', $text);
|
||||
$text = preg_replace("/\[event\-adjust\](.*?)\[\/event\-adjust\]/ism", '', $text);
|
||||
$text = preg_replace("/\[event\-id\](.*?)\[\/event\-id\]/ism", '', $text);
|
||||
}
|
||||
|
||||
// Replace non graphical smilies for external posts
|
||||
if (!$nosmile && !$for_plaintext) {
|
||||
$text = Smilies::replace($text);
|
||||
}
|
||||
|
||||
if (!$for_plaintext && DI::config()->get('system', 'big_emojis') && ($simple_html != self::DIASPORA)) {
|
||||
$conv = html_entity_decode(str_replace([' ', "\n", "\r"], '', $text));
|
||||
// Emojis are always 4 byte Unicode characters
|
||||
if (!empty($conv) && (strlen($conv) / mb_strlen($conv) == 4)) {
|
||||
$text = '<span style="font-size: xx-large; line-height: initial;">' . $text . '</span>';
|
||||
}
|
||||
}
|
||||
|
||||
if (!$for_plaintext) {
|
||||
if (in_array($simple_html, [self::OSTATUS, self::ACTIVITYPUB])) {
|
||||
$text = preg_replace_callback("/\[url\](.*?)\[\/url\]/ism", 'self::convertUrlForActivityPubCallback', $text);
|
||||
$text = preg_replace_callback("/\[url\=(.*?)\](.*?)\[\/url\]/ism", 'self::convertUrlForActivityPubCallback', $text);
|
||||
}
|
||||
} else {
|
||||
$text = preg_replace("(\[url\](.*?)\[\/url\])ism", " $1 ", $text);
|
||||
$text = preg_replace_callback("&\[url=([^\[\]]*)\]\[img\](.*)\[\/img\]\[\/url\]&Usi", 'self::removePictureLinksCallback', $text);
|
||||
}
|
||||
|
||||
$text = str_replace(["\r","\n"], ['<br />', '<br />'], $text);
|
||||
|
||||
// Remove all hashtag addresses
|
||||
if ($simple_html && !in_array($simple_html, [self::DIASPORA, self::OSTATUS, self::ACTIVITYPUB])) {
|
||||
$text = preg_replace("/([#@!])\[url\=(.*?)\](.*?)\[\/url\]/ism", '$1$3', $text);
|
||||
} elseif ($simple_html == self::DIASPORA) {
|
||||
// The ! is converted to @ since Diaspora only understands the @
|
||||
$text = preg_replace("/([@!])\[url\=(.*?)\](.*?)\[\/url\]/ism",
|
||||
'@<a href="$2">$3</a>',
|
||||
$text);
|
||||
} elseif (in_array($simple_html, [self::OSTATUS, self::ACTIVITYPUB])) {
|
||||
$text = preg_replace("/([@!])\[url\=(.*?)\](.*?)\[\/url\]/ism",
|
||||
'$1<span class="vcard"><a href="$2" class="url u-url mention" title="$3"><span class="fn nickname mention">$3</span></a></span>',
|
||||
$text);
|
||||
} elseif (!$simple_html) {
|
||||
$text = preg_replace("/([@!])\[url\=(.*?)\](.*?)\[\/url\]/ism",
|
||||
'$1<a href="$2" class="userinfo mention" title="$3">$3</a>',
|
||||
$text);
|
||||
}
|
||||
|
||||
// Bookmarks in red - will be converted to bookmarks in friendica
|
||||
$text = preg_replace("/#\^\[url\](.*?)\[\/url\]/ism", '[bookmark=$1]$1[/bookmark]', $text);
|
||||
$text = preg_replace("/#\^\[url\=(.*?)\](.*?)\[\/url\]/ism", '[bookmark=$1]$2[/bookmark]', $text);
|
||||
$text = preg_replace("/#\[url\=.*?\]\^\[\/url\]\[url\=(.*?)\](.*?)\[\/url\]/i",
|
||||
"[bookmark=$1]$2[/bookmark]", $text);
|
||||
|
||||
if (in_array($simple_html, [self::API, self::OSTATUS, self::TWITTER])) {
|
||||
$text = preg_replace_callback("/([^#@!])\[url\=([^\]]*)\](.*?)\[\/url\]/ism", "self::expandLinksCallback", $text);
|
||||
//$Text = preg_replace("/[^#@!]\[url\=([^\]]*)\](.*?)\[\/url\]/ism", ' $2 [url]$1[/url]', $Text);
|
||||
$text = preg_replace("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/ism", ' $2 [url]$1[/url]',$text);
|
||||
}
|
||||
|
||||
// Perform URL Search
|
||||
if ($try_oembed) {
|
||||
$text = preg_replace_callback("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/ism", $try_oembed_callback, $text);
|
||||
}
|
||||
|
||||
$text = preg_replace("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/ism", '[url=$1]$2[/url]', $text);
|
||||
|
||||
// Handle Diaspora posts
|
||||
$text = preg_replace_callback(
|
||||
"&\[url=/?posts/([^\[\]]*)\](.*)\[\/url\]&Usi",
|
||||
function ($match) {
|
||||
return "[url=" . DI::baseUrl() . "/display/" . $match[1] . "]" . $match[2] . "[/url]";
|
||||
}, $text
|
||||
);
|
||||
|
||||
$text = preg_replace_callback(
|
||||
"&\[url=/people\?q\=(.*)\](.*)\[\/url\]&Usi",
|
||||
function ($match) {
|
||||
return "[url=" . DI::baseUrl() . "/search?search=%40" . $match[1] . "]" . $match[2] . "[/url]";
|
||||
}, $text
|
||||
);
|
||||
|
||||
// Server independent link to posts and comments
|
||||
// See issue: https://github.com/diaspora/diaspora_federation/issues/75
|
||||
$expression = "=diaspora://.*?/post/([0-9A-Za-z\-_@.:]{15,254}[0-9A-Za-z])=ism";
|
||||
$text = preg_replace($expression, DI::baseUrl()."/display/$1", $text);
|
||||
|
||||
/* Tag conversion
|
||||
* Supports:
|
||||
* - #[url=<anything>]<term>[/url]
|
||||
* - [url=<anything>]#<term>[/url]
|
||||
*/
|
||||
$text = preg_replace_callback("/(?:#\[url\=[^\[\]]*\]|\[url\=[^\[\]]*\]#)(.*?)\[\/url\]/ism", function($matches) use ($simple_html) {
|
||||
if ($simple_html == BBCode::ACTIVITYPUB) {
|
||||
return '<a href="' . DI::baseUrl() . '/search?tag=' . rawurlencode($matches[1])
|
||||
. '" data-tag="' . XML::escape($matches[1]) . '" rel="tag ugc">#'
|
||||
. XML::escape($matches[1]) . '</a>';
|
||||
} else {
|
||||
return '#<a href="' . DI::baseUrl() . '/search?tag=' . rawurlencode($matches[1])
|
||||
. '" class="tag" rel="tag" title="' . XML::escape($matches[1]) . '">'
|
||||
. XML::escape($matches[1]) . '</a>';
|
||||
}
|
||||
}, $text);
|
||||
|
||||
// We need no target="_blank" rel="noopener noreferrer" for local links
|
||||
// convert links start with DI::baseUrl() as local link without the target="_blank" rel="noopener noreferrer" attribute
|
||||
$escapedBaseUrl = preg_quote(DI::baseUrl(), '/');
|
||||
$text = preg_replace("/\[url\](".$escapedBaseUrl.".*?)\[\/url\]/ism", '<a href="$1">$1</a>', $text);
|
||||
$text = preg_replace("/\[url\=(".$escapedBaseUrl.".*?)\](.*?)\[\/url\]/ism", '<a href="$1">$2</a>', $text);
|
||||
|
||||
$text = preg_replace("/\[url\](.*?)\[\/url\]/ism", '<a href="$1" target="_blank" rel="noopener noreferrer">$1</a>', $text);
|
||||
$text = preg_replace("/\[url\=(.*?)\](.*?)\[\/url\]/ism", '<a href="$1" target="_blank" rel="noopener noreferrer">$2</a>', $text);
|
||||
|
||||
// Red compatibility, though the link can't be authenticated on Friendica
|
||||
$text = preg_replace("/\[zrl\=(.*?)\](.*?)\[\/zrl\]/ism", '<a href="$1" target="_blank" rel="noopener noreferrer">$2</a>', $text);
|
||||
|
||||
|
||||
// we may need to restrict this further if it picks up too many strays
|
||||
// link acct:user@host to a webfinger profile redirector
|
||||
|
||||
$text = preg_replace('/acct:([^@]+)@((?!\-)(?:[a-zA-Z\d\-]{0,62}[a-zA-Z\d]\.){1,126}(?!\d+)[a-zA-Z\d]{1,63})/', '<a href="' . DI::baseUrl() . '/acctlink?addr=$1@$2" target="extlink">acct:$1@$2</a>', $text);
|
||||
|
||||
// Perform MAIL Search
|
||||
$text = preg_replace("/\[mail\](.*?)\[\/mail\]/", '<a href="mailto:$1">$1</a>', $text);
|
||||
$text = preg_replace("/\[mail\=(.*?)\](.*?)\[\/mail\]/", '<a href="mailto:$1">$2</a>', $text);
|
||||
|
||||
/// @todo What is the meaning of these lines?
|
||||
$text = preg_replace('/\[\&\;([#a-z0-9]+)\;\]/', '&$1;', $text);
|
||||
$text = preg_replace('/\&\#039\;/', '\'', $text);
|
||||
|
||||
// Currently deactivated, it made problems with " inside of alt texts.
|
||||
//$text = preg_replace('/\"\;/', '"', $text);
|
||||
|
||||
// fix any escaped ampersands that may have been converted into links
|
||||
$text = preg_replace('/\<([^>]*?)(src|href)=(.*?)\&\;(.*?)\>/ism', '<$1$2=$3&$4>', $text);
|
||||
|
||||
// sanitizes src attributes (http and redir URLs for displaying in a web page, cid used for inline images in emails)
|
||||
$allowed_src_protocols = ['//', 'http://', 'https://', 'redir/', 'cid:'];
|
||||
|
||||
array_walk($allowed_src_protocols, function(&$value) { $value = preg_quote($value, '#');});
|
||||
|
||||
$text = preg_replace('#<([^>]*?)(src)="(?!' . implode('|', $allowed_src_protocols) . ')(.*?)"(.*?)>#ism',
|
||||
'<$1$2=""$4 data-original-src="$3" class="invalid-src" title="' . DI::l10n()->t('Invalid source protocol') . '">', $text);
|
||||
|
||||
// sanitize href attributes (only allowlisted protocols URLs)
|
||||
// default value for backward compatibility
|
||||
$allowed_link_protocols = DI::config()->get('system', 'allowed_link_protocols', []);
|
||||
|
||||
// Always allowed protocol even if config isn't set or not including it
|
||||
$allowed_link_protocols[] = '//';
|
||||
$allowed_link_protocols[] = 'http://';
|
||||
$allowed_link_protocols[] = 'https://';
|
||||
$allowed_link_protocols[] = 'redir/';
|
||||
|
||||
array_walk($allowed_link_protocols, function(&$value) { $value = preg_quote($value, '#');});
|
||||
|
||||
$regex = '#<([^>]*?)(href)="(?!' . implode('|', $allowed_link_protocols) . ')(.*?)"(.*?)>#ism';
|
||||
$text = preg_replace($regex, '<$1$2="javascript:void(0)"$4 data-original-href="$3" class="invalid-href" title="' . DI::l10n()->t('Invalid link protocol') . '">', $text);
|
||||
|
||||
// Shared content
|
||||
$text = self::convertShare(
|
||||
$text,
|
||||
function (array $attributes, array $author_contact, $content, $is_quote_share) use ($simple_html) {
|
||||
return self::convertShareCallback($attributes, $author_contact, $content, $is_quote_share, $simple_html);
|
||||
}
|
||||
);
|
||||
|
||||
$text = self::interpolateSavedImagesIntoItemBody($text, $saved_image);
|
||||
|
||||
return $text;
|
||||
}); // Escaped noparse, nobb, pre
|
||||
|
||||
// Remove escaping tags
|
||||
$text = preg_replace("/\[noparse\](.*?)\[\/noparse\]/ism", '\1', $text);
|
||||
$text = preg_replace("/\[nobb\](.*?)\[\/nobb\]/ism", '\1', $text);
|
||||
|
||||
// Additionally, [pre] tags preserve spaces
|
||||
$text = preg_replace_callback("/\[pre\](.*?)\[\/pre\]/ism", function ($match) {
|
||||
return str_replace(' ', ' ', $match[1]);
|
||||
}, $text);
|
||||
|
||||
return $text;
|
||||
}); // Escaped code
|
||||
|
||||
$text = preg_replace_callback("#\[code(?:=([^\]]*))?\](.*?)\[\/code\]#ism",
|
||||
function ($matches) {
|
||||
if (strpos($matches[2], "\n") !== false) {
|
||||
$return = '<pre><code class="language-' . trim($matches[1]) . '">' . htmlspecialchars(trim($matches[2], "\n\r"), ENT_NOQUOTES, 'UTF-8') . '</code></pre>';
|
||||
} else {
|
||||
$return = '<code>' . htmlspecialchars($matches[2], ENT_NOQUOTES, 'UTF-8') . '</code>';
|
||||
}
|
||||
|
||||
return $return;
|
||||
},
|
||||
$text
|
||||
|
|
@ -2028,7 +2025,7 @@ class BBCode
|
|||
|
||||
// Convert it to HTML - don't try oembed
|
||||
if ($for_diaspora) {
|
||||
$text = self::convert($text, false, 3);
|
||||
$text = self::convert($text, false, self::DIASPORA);
|
||||
|
||||
// Add all tags that maybe were removed
|
||||
if (preg_match_all("/#\[url\=([$url_search_string]*)\](.*?)\[\/url\]/ism", $original_text, $tags)) {
|
||||
|
|
@ -2042,7 +2039,7 @@ class BBCode
|
|||
$text = $text . " " . $tagline;
|
||||
}
|
||||
} else {
|
||||
$text = self::convert($text, false, 4);
|
||||
$text = self::convert($text, false, self::CONNECTORS);
|
||||
}
|
||||
|
||||
// If a link is followed by a quote then there should be a newline before it
|
||||
|
|
@ -2094,63 +2091,152 @@ class BBCode
|
|||
{
|
||||
$ret = [];
|
||||
|
||||
// Convert hashtag links to hashtags
|
||||
$string = preg_replace('/#\[url\=([^\[\]]*)\](.*?)\[\/url\]/ism', '#$2', $string);
|
||||
BBCode::performWithEscapedTags($string, ['noparse', 'pre', 'code'], function ($string) use (&$ret) {
|
||||
// Convert hashtag links to hashtags
|
||||
$string = preg_replace('/#\[url\=([^\[\]]*)\](.*?)\[\/url\]/ism', '#$2 ', $string);
|
||||
|
||||
// ignore anything in a code block
|
||||
$string = preg_replace('/\[code.*?\].*?\[\/code\]/sm', '', $string);
|
||||
// Force line feeds at bbtags
|
||||
$string = str_replace(['[', ']'], ["\n[", "]\n"], $string);
|
||||
|
||||
// Force line feeds at bbtags
|
||||
$string = str_replace(['[', ']'], ["\n[", "]\n"], $string);
|
||||
// ignore anything in a bbtag
|
||||
$string = preg_replace('/\[(.*?)\]/sm', '', $string);
|
||||
|
||||
// ignore anything in a bbtag
|
||||
$string = preg_replace('/\[(.*?)\]/sm', '', $string);
|
||||
// Match full names against @tags including the space between first and last
|
||||
// We will look these up afterward to see if they are full names or not recognisable.
|
||||
|
||||
// Match full names against @tags including the space between first and last
|
||||
// We will look these up afterward to see if they are full names or not recognisable.
|
||||
if (preg_match_all('/(@[^ \x0D\x0A,:?]+ [^ \x0D\x0A@,:?]+)([ \x0D\x0A@,:?]|$)/', $string, $matches)) {
|
||||
foreach ($matches[1] as $match) {
|
||||
if (strstr($match, ']')) {
|
||||
// we might be inside a bbcode color tag - leave it alone
|
||||
continue;
|
||||
}
|
||||
|
||||
if (preg_match_all('/(@[^ \x0D\x0A,:?]+ [^ \x0D\x0A@,:?]+)([ \x0D\x0A@,:?]|$)/', $string, $matches)) {
|
||||
foreach ($matches[1] as $match) {
|
||||
if (strstr($match, ']')) {
|
||||
// we might be inside a bbcode color tag - leave it alone
|
||||
continue;
|
||||
if (substr($match, -1, 1) === '.') {
|
||||
$ret[] = substr($match, 0, -1);
|
||||
} else {
|
||||
$ret[] = $match;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise pull out single word tags. These can be @nickname, @first_last
|
||||
// and #hash tags.
|
||||
|
||||
if (preg_match_all('/([!#@][^\^ \x0D\x0A,;:?\']*[^\^ \x0D\x0A,;:?!\'.])/', $string, $matches)) {
|
||||
foreach ($matches[1] as $match) {
|
||||
if (strstr($match, ']')) {
|
||||
// we might be inside a bbcode color tag - leave it alone
|
||||
continue;
|
||||
}
|
||||
|
||||
// ignore strictly numeric tags like #1
|
||||
if ((strpos($match, '#') === 0) && ctype_digit(substr($match, 1))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// try not to catch url fragments
|
||||
if (strpos($string, $match) && preg_match('/[a-zA-z0-9\/]/', substr($string, strpos($string, $match) - 1, 1))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (substr($match, -1, 1) === '.') {
|
||||
$ret[] = substr($match, 0, -1);
|
||||
} else {
|
||||
$ret[] = $match;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Otherwise pull out single word tags. These can be @nickname, @first_last
|
||||
// and #hash tags.
|
||||
return array_unique($ret);
|
||||
}
|
||||
|
||||
if (preg_match_all('/([!#@][^\^ \x0D\x0A,;:?]+)([ \x0D\x0A,;:?]|$)/', $string, $matches)) {
|
||||
foreach ($matches[1] as $match) {
|
||||
if (strstr($match, ']')) {
|
||||
// we might be inside a bbcode color tag - leave it alone
|
||||
/**
|
||||
* Perform a custom function on a text after having escaped blocks enclosed in the provided tag list.
|
||||
*
|
||||
* @param string $text
|
||||
* @param array $tagList A list of tag names, e.g ['noparse', 'nobb', 'pre']
|
||||
* @param callable $callback
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*@see Strings::performWithEscapedBlocks
|
||||
*
|
||||
*/
|
||||
public static function performWithEscapedTags(string $text, array $tagList, callable $callback)
|
||||
{
|
||||
$tagList = array_map('preg_quote', $tagList);
|
||||
|
||||
return Strings::performWithEscapedBlocks($text, '#\[(?:' . implode('|', $tagList) . ').*?\[/(?:' . implode('|', $tagList) . ')]#ism', $callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces mentions in the provided message body for the provided user and network if any
|
||||
*
|
||||
* @param $body
|
||||
* @param $profile_uid
|
||||
* @param $network
|
||||
* @return string
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
public static function setMentions($body, $profile_uid = 0, $network = '')
|
||||
{
|
||||
BBCode::performWithEscapedTags($body, ['noparse', 'pre', 'code', 'img'], function ($body) use ($profile_uid, $network) {
|
||||
$tags = BBCode::getTags($body);
|
||||
|
||||
$tagged = [];
|
||||
$inform = '';
|
||||
|
||||
foreach ($tags as $tag) {
|
||||
$tag_type = substr($tag, 0, 1);
|
||||
|
||||
if ($tag_type == Tag::TAG_CHARACTER[Tag::HASHTAG]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (substr($match, -1, 1) === '.') {
|
||||
$match = substr($match,0,-1);
|
||||
/*
|
||||
* If we already tagged 'Robert Johnson', don't try and tag 'Robert'.
|
||||
* Robert Johnson should be first in the $tags array
|
||||
*/
|
||||
foreach ($tagged as $nextTag) {
|
||||
if (stristr($nextTag, $tag . ' ')) {
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
|
||||
// ignore strictly numeric tags like #1
|
||||
if ((strpos($match, '#') === 0) && ctype_digit(substr($match, 1))) {
|
||||
continue;
|
||||
}
|
||||
$success = Item::replaceTag($body, $inform, $profile_uid, $tag, $network);
|
||||
|
||||
// try not to catch url fragments
|
||||
if (strpos($string, $match) && preg_match('/[a-zA-z0-9\/]/', substr($string, strpos($string, $match) - 1, 1))) {
|
||||
continue;
|
||||
if ($success['replaced']) {
|
||||
$tagged[] = $tag;
|
||||
}
|
||||
$ret[] = $match;
|
||||
}
|
||||
|
||||
return $body;
|
||||
});
|
||||
|
||||
return $body;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $author Author display name
|
||||
* @param string $profile Author profile URL
|
||||
* @param string $avatar Author profile picture URL
|
||||
* @param string $link Post source URL
|
||||
* @param string $posted Post created date
|
||||
* @param string|null $guid Post guid (if any)
|
||||
* @return string
|
||||
* @TODO Rewrite to handle over whole record array
|
||||
*/
|
||||
public static function getShareOpeningTag(string $author, string $profile, string $avatar, string $link, string $posted, string $guid = null)
|
||||
{
|
||||
$header = "[share author='" . str_replace(["'", "[", "]"], ["'", "[", "]"], $author) .
|
||||
"' profile='" . str_replace(["'", "[", "]"], ["'", "[", "]"], $profile) .
|
||||
"' avatar='" . str_replace(["'", "[", "]"], ["'", "[", "]"], $avatar) .
|
||||
"' link='" . str_replace(["'", "[", "]"], ["'", "[", "]"], $link) .
|
||||
"' posted='" . str_replace(["'", "[", "]"], ["'", "[", "]"], $posted);
|
||||
|
||||
if ($guid) {
|
||||
$header .= "' guid='" . str_replace(["'", "[", "]"], ["'", "[", "]"], $guid);
|
||||
}
|
||||
|
||||
return $ret;
|
||||
$header .= "']";
|
||||
|
||||
return $header;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ use DOMXPath;
|
|||
use Friendica\Content\Widget\ContactBlock;
|
||||
use Friendica\Core\Hook;
|
||||
use Friendica\Core\Renderer;
|
||||
use Friendica\Core\Search;
|
||||
use Friendica\DI;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Util\Network;
|
||||
|
|
@ -166,252 +167,238 @@ class HTML
|
|||
{
|
||||
$message = str_replace("\r", "", $message);
|
||||
|
||||
// Removing code blocks before the whitespace removal processing below
|
||||
$codeblocks = [];
|
||||
$message = Strings::performWithEscapedBlocks($message, '#<pre><code.*</code></pre>#iUs', function ($message) {
|
||||
$message = str_replace(
|
||||
[
|
||||
"<li><p>",
|
||||
"</p></li>",
|
||||
],
|
||||
[
|
||||
"<li>",
|
||||
"</li>",
|
||||
],
|
||||
$message
|
||||
);
|
||||
|
||||
// remove namespaces
|
||||
$message = preg_replace('=<(\w+):(.+?)>=', '<removeme>', $message);
|
||||
$message = preg_replace('=</(\w+):(.+?)>=', '</removeme>', $message);
|
||||
|
||||
$doc = new DOMDocument();
|
||||
$doc->preserveWhiteSpace = false;
|
||||
|
||||
$message = mb_convert_encoding($message, 'HTML-ENTITIES', "UTF-8");
|
||||
|
||||
@$doc->loadHTML($message, LIBXML_HTML_NODEFDTD);
|
||||
|
||||
XML::deleteNode($doc, 'style');
|
||||
XML::deleteNode($doc, 'head');
|
||||
XML::deleteNode($doc, 'title');
|
||||
XML::deleteNode($doc, 'meta');
|
||||
XML::deleteNode($doc, 'xml');
|
||||
XML::deleteNode($doc, 'removeme');
|
||||
|
||||
$xpath = new DomXPath($doc);
|
||||
$list = $xpath->query("//pre");
|
||||
foreach ($list as $node) {
|
||||
// Ensure to escape unescaped & - they will otherwise raise a warning
|
||||
$safe_value = preg_replace('/&(?!\w+;)/', '&', $node->nodeValue);
|
||||
$node->nodeValue = str_replace("\n", "\r", $safe_value);
|
||||
}
|
||||
|
||||
$message = $doc->saveHTML();
|
||||
$message = str_replace(["\n<", ">\n", "\r", "\n", "\xC3\x82\xC2\xA0"], ["<", ">", "<br />", " ", ""], $message);
|
||||
$message = preg_replace('= [\s]*=i', " ", $message);
|
||||
|
||||
@$doc->loadHTML($message, LIBXML_HTML_NODEFDTD);
|
||||
|
||||
self::tagToBBCode($doc, 'html', [], "", "");
|
||||
self::tagToBBCode($doc, 'body', [], "", "");
|
||||
|
||||
// Outlook-Quote - Variant 1
|
||||
self::tagToBBCode($doc, 'p', ['class' => 'MsoNormal', 'style' => 'margin-left:35.4pt'], '[quote]', '[/quote]');
|
||||
|
||||
// Outlook-Quote - Variant 2
|
||||
self::tagToBBCode(
|
||||
$doc,
|
||||
'div',
|
||||
['style' => 'border:none;border-left:solid blue 1.5pt;padding:0cm 0cm 0cm 4.0pt'],
|
||||
'[quote]',
|
||||
'[/quote]'
|
||||
);
|
||||
|
||||
// MyBB-Stuff
|
||||
self::tagToBBCode($doc, 'span', ['style' => 'text-decoration: underline;'], '[u]', '[/u]');
|
||||
self::tagToBBCode($doc, 'span', ['style' => 'font-style: italic;'], '[i]', '[/i]');
|
||||
self::tagToBBCode($doc, 'span', ['style' => 'font-weight: bold;'], '[b]', '[/b]');
|
||||
|
||||
/* self::node2BBCode($doc, 'font', array('face'=>'/([\w ]+)/', 'size'=>'/(\d+)/', 'color'=>'/(.+)/'), '[font=$1][size=$2][color=$3]', '[/color][/size][/font]');
|
||||
self::node2BBCode($doc, 'font', array('size'=>'/(\d+)/', 'color'=>'/(.+)/'), '[size=$1][color=$2]', '[/color][/size]');
|
||||
self::node2BBCode($doc, 'font', array('face'=>'/([\w ]+)/', 'size'=>'/(.+)/'), '[font=$1][size=$2]', '[/size][/font]');
|
||||
self::node2BBCode($doc, 'font', array('face'=>'/([\w ]+)/', 'color'=>'/(.+)/'), '[font=$1][color=$3]', '[/color][/font]');
|
||||
self::node2BBCode($doc, 'font', array('face'=>'/([\w ]+)/'), '[font=$1]', '[/font]');
|
||||
self::node2BBCode($doc, 'font', array('size'=>'/(\d+)/'), '[size=$1]', '[/size]');
|
||||
self::node2BBCode($doc, 'font', array('color'=>'/(.+)/'), '[color=$1]', '[/color]');
|
||||
*/
|
||||
// Untested
|
||||
//self::node2BBCode($doc, 'span', array('style'=>'/.*font-size:\s*(.+?)[,;].*font-family:\s*(.+?)[,;].*color:\s*(.+?)[,;].*/'), '[size=$1][font=$2][color=$3]', '[/color][/font][/size]');
|
||||
//self::node2BBCode($doc, 'span', array('style'=>'/.*font-size:\s*(\d+)[,;].*/'), '[size=$1]', '[/size]');
|
||||
//self::node2BBCode($doc, 'span', array('style'=>'/.*font-size:\s*(.+?)[,;].*/'), '[size=$1]', '[/size]');
|
||||
|
||||
self::tagToBBCode($doc, 'span', ['style' => '/.*color:\s*(.+?)[,;].*/'], '[color="$1"]', '[/color]');
|
||||
|
||||
//self::node2BBCode($doc, 'span', array('style'=>'/.*font-family:\s*(.+?)[,;].*/'), '[font=$1]', '[/font]');
|
||||
//self::node2BBCode($doc, 'div', array('style'=>'/.*font-family:\s*(.+?)[,;].*font-size:\s*(\d+?)pt.*/'), '[font=$1][size=$2]', '[/size][/font]');
|
||||
//self::node2BBCode($doc, 'div', array('style'=>'/.*font-family:\s*(.+?)[,;].*font-size:\s*(\d+?)px.*/'), '[font=$1][size=$2]', '[/size][/font]');
|
||||
//self::node2BBCode($doc, 'div', array('style'=>'/.*font-family:\s*(.+?)[,;].*/'), '[font=$1]', '[/font]');
|
||||
// Importing the classes - interesting for importing of posts from third party networks that were exported from friendica
|
||||
// Test
|
||||
//self::node2BBCode($doc, 'span', array('class'=>'/([\w ]+)/'), '[class=$1]', '[/class]');
|
||||
self::tagToBBCode($doc, 'span', ['class' => 'type-link'], '[class=type-link]', '[/class]');
|
||||
self::tagToBBCode($doc, 'span', ['class' => 'type-video'], '[class=type-video]', '[/class]');
|
||||
|
||||
self::tagToBBCode($doc, 'strong', [], '[b]', '[/b]');
|
||||
self::tagToBBCode($doc, 'em', [], '[i]', '[/i]');
|
||||
self::tagToBBCode($doc, 'b', [], '[b]', '[/b]');
|
||||
self::tagToBBCode($doc, 'i', [], '[i]', '[/i]');
|
||||
self::tagToBBCode($doc, 'u', [], '[u]', '[/u]');
|
||||
self::tagToBBCode($doc, 's', [], '[s]', '[/s]');
|
||||
self::tagToBBCode($doc, 'del', [], '[s]', '[/s]');
|
||||
self::tagToBBCode($doc, 'strike', [], '[s]', '[/s]');
|
||||
|
||||
self::tagToBBCode($doc, 'big', [], "[size=large]", "[/size]");
|
||||
self::tagToBBCode($doc, 'small', [], "[size=small]", "[/size]");
|
||||
|
||||
self::tagToBBCode($doc, 'blockquote', [], '[quote]', '[/quote]');
|
||||
|
||||
self::tagToBBCode($doc, 'br', [], "\n", '');
|
||||
|
||||
self::tagToBBCode($doc, 'p', ['class' => 'MsoNormal'], "\n", "");
|
||||
self::tagToBBCode($doc, 'div', ['class' => 'MsoNormal'], "\r", "");
|
||||
|
||||
self::tagToBBCode($doc, 'span', [], "", "");
|
||||
|
||||
self::tagToBBCode($doc, 'span', [], "", "");
|
||||
self::tagToBBCode($doc, 'pre', [], "", "");
|
||||
|
||||
self::tagToBBCode($doc, 'div', [], "\r", "\r");
|
||||
self::tagToBBCode($doc, 'p', [], "\n", "\n");
|
||||
|
||||
self::tagToBBCode($doc, 'ul', [], "[list]", "[/list]");
|
||||
self::tagToBBCode($doc, 'ol', [], "[list=1]", "[/list]");
|
||||
self::tagToBBCode($doc, 'li', [], "[*]", "");
|
||||
|
||||
self::tagToBBCode($doc, 'hr', [], "[hr]", "");
|
||||
|
||||
self::tagToBBCode($doc, 'table', [], "[table]", "[/table]");
|
||||
self::tagToBBCode($doc, 'th', [], "[th]", "[/th]");
|
||||
self::tagToBBCode($doc, 'tr', [], "[tr]", "[/tr]");
|
||||
self::tagToBBCode($doc, 'td', [], "[td]", "[/td]");
|
||||
|
||||
self::tagToBBCode($doc, 'h1', [], "[h1]", "[/h1]");
|
||||
self::tagToBBCode($doc, 'h2', [], "[h2]", "[/h2]");
|
||||
self::tagToBBCode($doc, 'h3', [], "[h3]", "[/h3]");
|
||||
self::tagToBBCode($doc, 'h4', [], "[h4]", "[/h4]");
|
||||
self::tagToBBCode($doc, 'h5', [], "[h5]", "[/h5]");
|
||||
self::tagToBBCode($doc, 'h6', [], "[h6]", "[/h6]");
|
||||
|
||||
self::tagToBBCode($doc, 'a', ['href' => '/mailto:(.+)/'], '[mail=$1]', '[/mail]');
|
||||
self::tagToBBCode($doc, 'a', ['href' => '/(.+)/'], '[url=$1]', '[/url]');
|
||||
|
||||
self::tagToBBCode($doc, 'img', ['src' => '/(.+)/', 'alt' => '/(.+)/'], '[img=$1]$2', '[/img]', true);
|
||||
self::tagToBBCode($doc, 'img', ['src' => '/(.+)/', 'width' => '/(\d+)/', 'height' => '/(\d+)/'], '[img=$2x$3]$1', '[/img]', true);
|
||||
self::tagToBBCode($doc, 'img', ['src' => '/(.+)/'], '[img]$1', '[/img]', true);
|
||||
|
||||
|
||||
self::tagToBBCode($doc, 'video', ['src' => '/(.+)/'], '[video]$1', '[/video]', true);
|
||||
self::tagToBBCode($doc, 'audio', ['src' => '/(.+)/'], '[audio]$1', '[/audio]', true);
|
||||
self::tagToBBCode($doc, 'iframe', ['src' => '/(.+)/'], '[iframe]$1', '[/iframe]', true);
|
||||
|
||||
self::tagToBBCode($doc, 'key', [], '[code]', '[/code]');
|
||||
self::tagToBBCode($doc, 'code', [], '[code]', '[/code]');
|
||||
|
||||
$message = $doc->saveHTML();
|
||||
|
||||
// I'm removing something really disturbing
|
||||
// Don't know exactly what it is
|
||||
$message = str_replace(chr(194) . chr(160), ' ', $message);
|
||||
|
||||
$message = str_replace(" ", " ", $message);
|
||||
|
||||
// removing multiple DIVs
|
||||
$message = preg_replace('=\r *\r=i', "\n", $message);
|
||||
$message = str_replace("\r", "\n", $message);
|
||||
|
||||
Hook::callAll('html2bbcode', $message);
|
||||
|
||||
$message = strip_tags($message);
|
||||
|
||||
$message = html_entity_decode($message, ENT_QUOTES, 'UTF-8');
|
||||
|
||||
// remove quotes if they don't make sense
|
||||
$message = preg_replace('=\[/quote\][\s]*\[quote\]=i', "\n", $message);
|
||||
|
||||
$message = preg_replace('=\[quote\]\s*=i', "[quote]", $message);
|
||||
$message = preg_replace('=\s*\[/quote\]=i', "[/quote]", $message);
|
||||
|
||||
do {
|
||||
$oldmessage = $message;
|
||||
$message = str_replace("\n \n", "\n\n", $message);
|
||||
} while ($oldmessage != $message);
|
||||
|
||||
do {
|
||||
$oldmessage = $message;
|
||||
$message = str_replace("\n\n\n", "\n\n", $message);
|
||||
} while ($oldmessage != $message);
|
||||
|
||||
do {
|
||||
$oldmessage = $message;
|
||||
$message = str_replace(
|
||||
[
|
||||
"[/size]\n\n",
|
||||
"\n[hr]",
|
||||
"[hr]\n",
|
||||
"\n[list",
|
||||
"[/list]\n",
|
||||
"\n[/",
|
||||
"[list]\n",
|
||||
"[list=1]\n",
|
||||
"\n[*]"],
|
||||
[
|
||||
"[/size]\n",
|
||||
"[hr]",
|
||||
"[hr]",
|
||||
"[list",
|
||||
"[/list]",
|
||||
"[/",
|
||||
"[list]",
|
||||
"[list=1]",
|
||||
"[*]"],
|
||||
$message
|
||||
);
|
||||
} while ($message != $oldmessage);
|
||||
|
||||
$message = str_replace(
|
||||
['[b][b]', '[/b][/b]', '[i][i]', '[/i][/i]'],
|
||||
['[b]', '[/b]', '[i]', '[/i]'],
|
||||
$message
|
||||
);
|
||||
|
||||
// Handling Yahoo style of mails
|
||||
$message = str_replace('[hr][b]From:[/b]', '[quote][b]From:[/b]', $message);
|
||||
|
||||
return $message;
|
||||
});
|
||||
|
||||
$message = preg_replace_callback(
|
||||
'#<pre><code(?: class="language-([^"]*)")?>(.*)</code></pre>#iUs',
|
||||
function ($matches) use (&$codeblocks) {
|
||||
$return = '[codeblock-' . count($codeblocks) . ']';
|
||||
|
||||
function ($matches) {
|
||||
$prefix = '[code]';
|
||||
if ($matches[1] != '') {
|
||||
$prefix = '[code=' . $matches[1] . ']';
|
||||
}
|
||||
|
||||
$codeblocks[] = $prefix . PHP_EOL . trim($matches[2]) . PHP_EOL . '[/code]';
|
||||
return $return;
|
||||
},
|
||||
$message
|
||||
);
|
||||
|
||||
$message = str_replace(
|
||||
[
|
||||
"<li><p>",
|
||||
"</p></li>",
|
||||
],
|
||||
[
|
||||
"<li>",
|
||||
"</li>",
|
||||
],
|
||||
$message
|
||||
);
|
||||
|
||||
// remove namespaces
|
||||
$message = preg_replace('=<(\w+):(.+?)>=', '<removeme>', $message);
|
||||
$message = preg_replace('=</(\w+):(.+?)>=', '</removeme>', $message);
|
||||
|
||||
$doc = new DOMDocument();
|
||||
$doc->preserveWhiteSpace = false;
|
||||
|
||||
$message = mb_convert_encoding($message, 'HTML-ENTITIES', "UTF-8");
|
||||
|
||||
@$doc->loadHTML($message, LIBXML_HTML_NODEFDTD);
|
||||
|
||||
XML::deleteNode($doc, 'style');
|
||||
XML::deleteNode($doc, 'head');
|
||||
XML::deleteNode($doc, 'title');
|
||||
XML::deleteNode($doc, 'meta');
|
||||
XML::deleteNode($doc, 'xml');
|
||||
XML::deleteNode($doc, 'removeme');
|
||||
|
||||
$xpath = new DomXPath($doc);
|
||||
$list = $xpath->query("//pre");
|
||||
foreach ($list as $node) {
|
||||
// Ensure to escape unescaped & - they will otherwise raise a warning
|
||||
$safe_value = preg_replace('/&(?!\w+;)/', '&', $node->nodeValue);
|
||||
$node->nodeValue = str_replace("\n", "\r", $safe_value);
|
||||
}
|
||||
|
||||
$message = $doc->saveHTML();
|
||||
$message = str_replace(["\n<", ">\n", "\r", "\n", "\xC3\x82\xC2\xA0"], ["<", ">", "<br />", " ", ""], $message);
|
||||
$message = preg_replace('= [\s]*=i', " ", $message);
|
||||
|
||||
@$doc->loadHTML($message, LIBXML_HTML_NODEFDTD);
|
||||
|
||||
self::tagToBBCode($doc, 'html', [], "", "");
|
||||
self::tagToBBCode($doc, 'body', [], "", "");
|
||||
|
||||
// Outlook-Quote - Variant 1
|
||||
self::tagToBBCode($doc, 'p', ['class' => 'MsoNormal', 'style' => 'margin-left:35.4pt'], '[quote]', '[/quote]');
|
||||
|
||||
// Outlook-Quote - Variant 2
|
||||
self::tagToBBCode(
|
||||
$doc,
|
||||
'div',
|
||||
['style' => 'border:none;border-left:solid blue 1.5pt;padding:0cm 0cm 0cm 4.0pt'],
|
||||
'[quote]',
|
||||
'[/quote]'
|
||||
);
|
||||
|
||||
// MyBB-Stuff
|
||||
self::tagToBBCode($doc, 'span', ['style' => 'text-decoration: underline;'], '[u]', '[/u]');
|
||||
self::tagToBBCode($doc, 'span', ['style' => 'font-style: italic;'], '[i]', '[/i]');
|
||||
self::tagToBBCode($doc, 'span', ['style' => 'font-weight: bold;'], '[b]', '[/b]');
|
||||
|
||||
/* self::node2BBCode($doc, 'font', array('face'=>'/([\w ]+)/', 'size'=>'/(\d+)/', 'color'=>'/(.+)/'), '[font=$1][size=$2][color=$3]', '[/color][/size][/font]');
|
||||
self::node2BBCode($doc, 'font', array('size'=>'/(\d+)/', 'color'=>'/(.+)/'), '[size=$1][color=$2]', '[/color][/size]');
|
||||
self::node2BBCode($doc, 'font', array('face'=>'/([\w ]+)/', 'size'=>'/(.+)/'), '[font=$1][size=$2]', '[/size][/font]');
|
||||
self::node2BBCode($doc, 'font', array('face'=>'/([\w ]+)/', 'color'=>'/(.+)/'), '[font=$1][color=$3]', '[/color][/font]');
|
||||
self::node2BBCode($doc, 'font', array('face'=>'/([\w ]+)/'), '[font=$1]', '[/font]');
|
||||
self::node2BBCode($doc, 'font', array('size'=>'/(\d+)/'), '[size=$1]', '[/size]');
|
||||
self::node2BBCode($doc, 'font', array('color'=>'/(.+)/'), '[color=$1]', '[/color]');
|
||||
*/
|
||||
// Untested
|
||||
//self::node2BBCode($doc, 'span', array('style'=>'/.*font-size:\s*(.+?)[,;].*font-family:\s*(.+?)[,;].*color:\s*(.+?)[,;].*/'), '[size=$1][font=$2][color=$3]', '[/color][/font][/size]');
|
||||
//self::node2BBCode($doc, 'span', array('style'=>'/.*font-size:\s*(\d+)[,;].*/'), '[size=$1]', '[/size]');
|
||||
//self::node2BBCode($doc, 'span', array('style'=>'/.*font-size:\s*(.+?)[,;].*/'), '[size=$1]', '[/size]');
|
||||
|
||||
self::tagToBBCode($doc, 'span', ['style' => '/.*color:\s*(.+?)[,;].*/'], '[color="$1"]', '[/color]');
|
||||
|
||||
//self::node2BBCode($doc, 'span', array('style'=>'/.*font-family:\s*(.+?)[,;].*/'), '[font=$1]', '[/font]');
|
||||
//self::node2BBCode($doc, 'div', array('style'=>'/.*font-family:\s*(.+?)[,;].*font-size:\s*(\d+?)pt.*/'), '[font=$1][size=$2]', '[/size][/font]');
|
||||
//self::node2BBCode($doc, 'div', array('style'=>'/.*font-family:\s*(.+?)[,;].*font-size:\s*(\d+?)px.*/'), '[font=$1][size=$2]', '[/size][/font]');
|
||||
//self::node2BBCode($doc, 'div', array('style'=>'/.*font-family:\s*(.+?)[,;].*/'), '[font=$1]', '[/font]');
|
||||
// Importing the classes - interesting for importing of posts from third party networks that were exported from friendica
|
||||
// Test
|
||||
//self::node2BBCode($doc, 'span', array('class'=>'/([\w ]+)/'), '[class=$1]', '[/class]');
|
||||
self::tagToBBCode($doc, 'span', ['class' => 'type-link'], '[class=type-link]', '[/class]');
|
||||
self::tagToBBCode($doc, 'span', ['class' => 'type-video'], '[class=type-video]', '[/class]');
|
||||
|
||||
self::tagToBBCode($doc, 'strong', [], '[b]', '[/b]');
|
||||
self::tagToBBCode($doc, 'em', [], '[i]', '[/i]');
|
||||
self::tagToBBCode($doc, 'b', [], '[b]', '[/b]');
|
||||
self::tagToBBCode($doc, 'i', [], '[i]', '[/i]');
|
||||
self::tagToBBCode($doc, 'u', [], '[u]', '[/u]');
|
||||
self::tagToBBCode($doc, 's', [], '[s]', '[/s]');
|
||||
self::tagToBBCode($doc, 'del', [], '[s]', '[/s]');
|
||||
self::tagToBBCode($doc, 'strike', [], '[s]', '[/s]');
|
||||
|
||||
self::tagToBBCode($doc, 'big', [], "[size=large]", "[/size]");
|
||||
self::tagToBBCode($doc, 'small', [], "[size=small]", "[/size]");
|
||||
|
||||
self::tagToBBCode($doc, 'blockquote', [], '[quote]', '[/quote]');
|
||||
|
||||
self::tagToBBCode($doc, 'br', [], "\n", '');
|
||||
|
||||
self::tagToBBCode($doc, 'p', ['class' => 'MsoNormal'], "\n", "");
|
||||
self::tagToBBCode($doc, 'div', ['class' => 'MsoNormal'], "\r", "");
|
||||
|
||||
self::tagToBBCode($doc, 'span', [], "", "");
|
||||
|
||||
self::tagToBBCode($doc, 'span', [], "", "");
|
||||
self::tagToBBCode($doc, 'pre', [], "", "");
|
||||
|
||||
self::tagToBBCode($doc, 'div', [], "\r", "\r");
|
||||
self::tagToBBCode($doc, 'p', [], "\n", "\n");
|
||||
|
||||
self::tagToBBCode($doc, 'ul', [], "[list]", "[/list]");
|
||||
self::tagToBBCode($doc, 'ol', [], "[list=1]", "[/list]");
|
||||
self::tagToBBCode($doc, 'li', [], "[*]", "");
|
||||
|
||||
self::tagToBBCode($doc, 'hr', [], "[hr]", "");
|
||||
|
||||
self::tagToBBCode($doc, 'table', [], "[table]", "[/table]");
|
||||
self::tagToBBCode($doc, 'th', [], "[th]", "[/th]");
|
||||
self::tagToBBCode($doc, 'tr', [], "[tr]", "[/tr]");
|
||||
self::tagToBBCode($doc, 'td', [], "[td]", "[/td]");
|
||||
|
||||
self::tagToBBCode($doc, 'h1', [], "[h1]", "[/h1]");
|
||||
self::tagToBBCode($doc, 'h2', [], "[h2]", "[/h2]");
|
||||
self::tagToBBCode($doc, 'h3', [], "[h3]", "[/h3]");
|
||||
self::tagToBBCode($doc, 'h4', [], "[h4]", "[/h4]");
|
||||
self::tagToBBCode($doc, 'h5', [], "[h5]", "[/h5]");
|
||||
self::tagToBBCode($doc, 'h6', [], "[h6]", "[/h6]");
|
||||
|
||||
self::tagToBBCode($doc, 'a', ['href' => '/mailto:(.+)/'], '[mail=$1]', '[/mail]');
|
||||
self::tagToBBCode($doc, 'a', ['href' => '/(.+)/'], '[url=$1]', '[/url]');
|
||||
|
||||
self::tagToBBCode($doc, 'img', ['src' => '/(.+)/', 'alt' => '/(.+)/'], '[img=$1]$2', '[/img]', true);
|
||||
self::tagToBBCode($doc, 'img', ['src' => '/(.+)/', 'width' => '/(\d+)/', 'height' => '/(\d+)/'], '[img=$2x$3]$1', '[/img]', true);
|
||||
self::tagToBBCode($doc, 'img', ['src' => '/(.+)/'], '[img]$1', '[/img]', true);
|
||||
|
||||
|
||||
self::tagToBBCode($doc, 'video', ['src' => '/(.+)/'], '[video]$1', '[/video]', true);
|
||||
self::tagToBBCode($doc, 'audio', ['src' => '/(.+)/'], '[audio]$1', '[/audio]', true);
|
||||
self::tagToBBCode($doc, 'iframe', ['src' => '/(.+)/'], '[iframe]$1', '[/iframe]', true);
|
||||
|
||||
self::tagToBBCode($doc, 'key', [], '[code]', '[/code]');
|
||||
self::tagToBBCode($doc, 'code', [], '[code]', '[/code]');
|
||||
|
||||
$message = $doc->saveHTML();
|
||||
|
||||
// I'm removing something really disturbing
|
||||
// Don't know exactly what it is
|
||||
$message = str_replace(chr(194) . chr(160), ' ', $message);
|
||||
|
||||
$message = str_replace(" ", " ", $message);
|
||||
|
||||
// removing multiple DIVs
|
||||
$message = preg_replace('=\r *\r=i', "\n", $message);
|
||||
$message = str_replace("\r", "\n", $message);
|
||||
|
||||
Hook::callAll('html2bbcode', $message);
|
||||
|
||||
$message = strip_tags($message);
|
||||
|
||||
$message = html_entity_decode($message, ENT_QUOTES, 'UTF-8');
|
||||
|
||||
// remove quotes if they don't make sense
|
||||
$message = preg_replace('=\[/quote\][\s]*\[quote\]=i', "\n", $message);
|
||||
|
||||
$message = preg_replace('=\[quote\]\s*=i', "[quote]", $message);
|
||||
$message = preg_replace('=\s*\[/quote\]=i', "[/quote]", $message);
|
||||
|
||||
do {
|
||||
$oldmessage = $message;
|
||||
$message = str_replace("\n \n", "\n\n", $message);
|
||||
} while ($oldmessage != $message);
|
||||
|
||||
do {
|
||||
$oldmessage = $message;
|
||||
$message = str_replace("\n\n\n", "\n\n", $message);
|
||||
} while ($oldmessage != $message);
|
||||
|
||||
do {
|
||||
$oldmessage = $message;
|
||||
$message = str_replace(
|
||||
[
|
||||
"[/size]\n\n",
|
||||
"\n[hr]",
|
||||
"[hr]\n",
|
||||
"\n[list",
|
||||
"[/list]\n",
|
||||
"\n[/",
|
||||
"[list]\n",
|
||||
"[list=1]\n",
|
||||
"\n[*]"],
|
||||
[
|
||||
"[/size]\n",
|
||||
"[hr]",
|
||||
"[hr]",
|
||||
"[list",
|
||||
"[/list]",
|
||||
"[/",
|
||||
"[list]",
|
||||
"[list=1]",
|
||||
"[*]"],
|
||||
$message
|
||||
);
|
||||
} while ($message != $oldmessage);
|
||||
|
||||
$message = str_replace(
|
||||
['[b][b]', '[/b][/b]', '[i][i]', '[/i][/i]'],
|
||||
['[b]', '[/b]', '[i]', '[/i]'],
|
||||
$message
|
||||
);
|
||||
|
||||
// Handling Yahoo style of mails
|
||||
$message = str_replace('[hr][b]From:[/b]', '[quote][b]From:[/b]', $message);
|
||||
|
||||
// Restore code blocks
|
||||
$message = preg_replace_callback(
|
||||
'#\[codeblock-([0-9]+)\]#iU',
|
||||
function ($matches) use ($codeblocks) {
|
||||
$return = '';
|
||||
if (isset($codeblocks[intval($matches[1])])) {
|
||||
$return = $codeblocks[$matches[1]];
|
||||
}
|
||||
return $return;
|
||||
return $prefix . PHP_EOL . trim($matches[2]) . PHP_EOL . '[/code]';
|
||||
},
|
||||
$message
|
||||
);
|
||||
|
|
@ -917,7 +904,7 @@ class HTML
|
|||
'$save_label' => $save_label,
|
||||
'$search_hint' => DI::l10n()->t('@name, !forum, #tags, content'),
|
||||
'$mode' => $mode,
|
||||
'$return_url' => urlencode('search?q=' . urlencode($s)),
|
||||
'$return_url' => urlencode(Search::getSearchPath($s)),
|
||||
];
|
||||
|
||||
if (!$aside) {
|
||||
|
|
|
|||
|
|
@ -35,20 +35,20 @@ class Markdown
|
|||
* compatibility with Diaspora in spite of the Markdown standard.
|
||||
*
|
||||
* @param string $text
|
||||
* @param bool $hardwrap
|
||||
* @param bool $hardwrap Enables line breaks on \n without two trailing spaces
|
||||
* @param string $baseuri Optional. Prepend anchor links with this URL
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function convert($text, $hardwrap = true) {
|
||||
public static function convert($text, $hardwrap = true, $baseuri = null) {
|
||||
$stamp1 = microtime(true);
|
||||
|
||||
$MarkdownParser = new MarkdownParser();
|
||||
$MarkdownParser->code_class_prefix = 'language-';
|
||||
$MarkdownParser->hard_wrap = $hardwrap;
|
||||
$MarkdownParser->hashtag_protection = true;
|
||||
$MarkdownParser->url_filter_func = function ($url) {
|
||||
if (strpos($url, '#') === 0) {
|
||||
$url = ltrim($_SERVER['REQUEST_URI'], '/') . $url;
|
||||
$MarkdownParser->url_filter_func = function ($url) use ($baseuri) {
|
||||
if (!empty($baseuri) && strpos($url, '#') === 0) {
|
||||
$url = ltrim($baseuri, '/') . $url;
|
||||
}
|
||||
return $url;
|
||||
};
|
||||
|
|
@ -122,9 +122,6 @@ class Markdown
|
|||
// protect the recycle symbol from turning into a tag, but without unescaping angles and naked ampersands
|
||||
$s = str_replace('♲', html_entity_decode('♲', ENT_QUOTES, 'UTF-8'), $s);
|
||||
|
||||
// Convert everything that looks like a link to a link
|
||||
$s = preg_replace('/([^\]=]|^)(https?\:\/\/)([a-zA-Z0-9:\/\-?&;.=_~#%$!+,@]+(?<!,))/ism', '$1[url=$2$3]$2$3[/url]', $s);
|
||||
|
||||
//$s = preg_replace("/([^\]\=]|^)(https?\:\/\/)(vimeo|youtu|www\.youtube|soundcloud)([a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", '$1[url=$2$3$4]$2$3$4[/url]',$s);
|
||||
$s = BBCode::pregReplaceInTag('/\[url\=?(.*?)\]https?:\/\/www.youtube.com\/watch\?v\=(.*?)\[\/url\]/ism', '[youtube]$2[/youtube]', 'url', $s);
|
||||
$s = BBCode::pregReplaceInTag('/\[url\=https?:\/\/www.youtube.com\/watch\?v\=(.*?)\].*?\[\/url\]/ism' , '[youtube]$1[/youtube]', 'url', $s);
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
namespace Friendica\Content\Widget;
|
||||
|
||||
use Friendica\Core\Renderer;
|
||||
use Friendica\Core\Search;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\DI;
|
||||
|
||||
|
|
@ -35,32 +36,32 @@ class SavedSearches
|
|||
*/
|
||||
public static function getHTML($return_url, $search = '')
|
||||
{
|
||||
$o = '';
|
||||
|
||||
$saved = [];
|
||||
$saved_searches = DBA::select('search', ['id', 'term'], ['uid' => local_user()]);
|
||||
if (DBA::isResult($saved_searches)) {
|
||||
$saved = [];
|
||||
foreach ($saved_searches as $saved_search) {
|
||||
$saved[] = [
|
||||
'id' => $saved_search['id'],
|
||||
'term' => $saved_search['term'],
|
||||
'encodedterm' => urlencode($saved_search['term']),
|
||||
'delete' => DI::l10n()->t('Remove term'),
|
||||
'selected' => $search == $saved_search['term'],
|
||||
];
|
||||
}
|
||||
while ($saved_search = DBA::fetch($saved_searches)) {
|
||||
$saved[] = [
|
||||
'id' => $saved_search['id'],
|
||||
'term' => $saved_search['term'],
|
||||
'encodedterm' => urlencode($saved_search['term']),
|
||||
'searchpath' => Search::getSearchPath($saved_search['term']),
|
||||
'delete' => DI::l10n()->t('Remove term'),
|
||||
'selected' => $search == $saved_search['term'],
|
||||
];
|
||||
}
|
||||
DBA::close($saved_searches);
|
||||
|
||||
$tpl = Renderer::getMarkupTemplate('widget/saved_searches.tpl');
|
||||
|
||||
$o = Renderer::replaceMacros($tpl, [
|
||||
'$title' => DI::l10n()->t('Saved Searches'),
|
||||
'$add' => '',
|
||||
'$searchbox' => '',
|
||||
'$saved' => $saved,
|
||||
'$return_url' => urlencode($return_url),
|
||||
]);
|
||||
if (empty($saved)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $o;
|
||||
$tpl = Renderer::getMarkupTemplate('widget/saved_searches.tpl');
|
||||
|
||||
return Renderer::replaceMacros($tpl, [
|
||||
'$title' => DI::l10n()->t('Saved Searches'),
|
||||
'$add' => '',
|
||||
'$searchbox' => '',
|
||||
'$saved' => $saved,
|
||||
'$return_url' => urlencode($return_url),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ use Friendica\Core\Renderer;
|
|||
use Friendica\Database\DBA;
|
||||
use Friendica\DI;
|
||||
use Friendica\Model\Item;
|
||||
use Friendica\Model\Tag;
|
||||
|
||||
/**
|
||||
* TagCloud widget
|
||||
|
|
@ -45,7 +46,7 @@ class TagCloud
|
|||
* @return string HTML formatted output.
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public static function getHTML($uid, $count = 0, $owner_id = 0, $flags = '', $type = TERM_HASHTAG)
|
||||
public static function getHTML($uid, $count = 0, $owner_id = 0, $flags = '', $type = Tag::HASHTAG)
|
||||
{
|
||||
$o = '';
|
||||
$r = self::tagadelic($uid, $count, $owner_id, $flags, $type);
|
||||
|
|
@ -84,7 +85,7 @@ class TagCloud
|
|||
* @return array Alphabetical sorted array of used tags of an user.
|
||||
* @throws \Exception
|
||||
*/
|
||||
private static function tagadelic($uid, $count = 0, $owner_id = 0, $flags = '', $type = TERM_HASHTAG)
|
||||
private static function tagadelic($uid, $count = 0, $owner_id = 0, $flags = '', $type = Tag::HASHTAG)
|
||||
{
|
||||
$sql_options = Item::getPermissionsSQLByUserId($uid);
|
||||
$limit = $count ? sprintf('LIMIT %d', intval($count)) : '';
|
||||
|
|
@ -100,16 +101,13 @@ class TagCloud
|
|||
}
|
||||
|
||||
// Fetch tags
|
||||
$tag_stmt = DBA::p("SELECT `term`, COUNT(`term`) AS `total` FROM `term`
|
||||
LEFT JOIN `item` ON `term`.`oid` = `item`.`id`
|
||||
WHERE `term`.`uid` = ? AND `term`.`type` = ?
|
||||
AND `term`.`otype` = ?
|
||||
$tag_stmt = DBA::p("SELECT `name`, COUNT(`name`) AS `total` FROM `tag-search-view`
|
||||
LEFT JOIN `item` ON `tag-search-view`.`uri-id` = `item`.`uri-id`
|
||||
WHERE `tag-search-view`.`uid` = ?
|
||||
AND `item`.`visible` AND NOT `item`.`deleted` AND NOT `item`.`moderated`
|
||||
$sql_options
|
||||
GROUP BY `term` ORDER BY `total` DESC $limit",
|
||||
$uid,
|
||||
$type,
|
||||
TERM_OBJ_POST
|
||||
GROUP BY `name` ORDER BY `total` DESC $limit",
|
||||
$uid
|
||||
);
|
||||
if (!DBA::isResult($tag_stmt)) {
|
||||
return [];
|
||||
|
|
@ -138,7 +136,7 @@ class TagCloud
|
|||
}
|
||||
|
||||
foreach ($arr as $rr) {
|
||||
$tags[$x][0] = $rr['term'];
|
||||
$tags[$x][0] = $rr['name'];
|
||||
$tags[$x][1] = log($rr['total']);
|
||||
$tags[$x][2] = 0;
|
||||
$min = min($min, $tags[$x][1]);
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ namespace Friendica\Content\Widget;
|
|||
|
||||
use Friendica\Core\Renderer;
|
||||
use Friendica\DI;
|
||||
use Friendica\Model\Term;
|
||||
use Friendica\Model\Tag;
|
||||
|
||||
/**
|
||||
* Trending tags aside widget for the community pages, handles both local and global scopes
|
||||
|
|
@ -41,9 +41,9 @@ class TrendingTags
|
|||
public static function getHTML($content = 'global', int $period = 24)
|
||||
{
|
||||
if ($content == 'local') {
|
||||
$tags = Term::getLocalTrendingHashtags($period, 20);
|
||||
$tags = Tag::getLocalTrendingHashtags($period, 20);
|
||||
} else {
|
||||
$tags = Term::getGlobalTrendingHashtags($period, 20);
|
||||
$tags = Tag::getGlobalTrendingHashtags($period, 20);
|
||||
}
|
||||
|
||||
$tpl = Renderer::getMarkupTemplate('widget/trending_tags.tpl');
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ Commands:
|
|||
docbloxerrorchecker Check the file tree for DocBlox errors
|
||||
extract Generate translation string file for the Friendica project (deprecated)
|
||||
globalcommunityblock Block remote profile from interacting with this node
|
||||
globalcommunitysilence Silence remote profile from global community page
|
||||
globalcommunitysilence Silence a profile from the global community page
|
||||
archivecontact Archive a contact when you know that it isn't existing anymore
|
||||
help Show help about a command, e.g (bin/console help config)
|
||||
autoinstall Starts automatic installation of friendica based on values from htconfig.php
|
||||
|
|
|
|||
|
|
@ -259,7 +259,7 @@ class Installer
|
|||
$help = "";
|
||||
if (!$passed) {
|
||||
$help .= DI::l10n()->t('Could not find a command line version of PHP in the web server PATH.') . EOL;
|
||||
$help .= DI::l10n()->t("If you don't have a command line version of PHP installed on your server, you will not be able to run the background processing. See <a href='https://github.com/friendica/friendica/blob/master/doc/Install.md#set-up-the-worker'>'Setup the worker'</a>") . EOL;
|
||||
$help .= DI::l10n()->t("If you don't have a command line version of PHP installed on your server, you will not be able to run the background processing. See <a href='https://github.com/friendica/friendica/blob/stable/doc/Install.md#set-up-the-worker'>'Setup the worker'</a>") . EOL;
|
||||
$help .= EOL . EOL;
|
||||
$tpl = Renderer::getMarkupTemplate('field_input.tpl');
|
||||
/// @todo Separate backend Installer class and presentation layer/view
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@ namespace Friendica\Core;
|
|||
|
||||
use Exception;
|
||||
use Friendica\DI;
|
||||
use Friendica\Render\FriendicaSmarty;
|
||||
use Friendica\Render\ITemplateEngine;
|
||||
use Friendica\Network\HTTPException\InternalServerErrorException;
|
||||
use Friendica\Render\TemplateEngine;
|
||||
|
||||
/**
|
||||
* This class handles Renderer related functions.
|
||||
|
|
@ -66,28 +66,30 @@ class Renderer
|
|||
];
|
||||
|
||||
/**
|
||||
* This is our template processor
|
||||
* Returns the rendered template output from the template string and variables
|
||||
*
|
||||
* @param string|FriendicaSmarty $s The string requiring macro substitution or an instance of FriendicaSmarty
|
||||
* @param array $vars Key value pairs (search => replace)
|
||||
*
|
||||
* @return string substituted string
|
||||
* @throws Exception
|
||||
* @param string $template
|
||||
* @param array $vars
|
||||
* @return string
|
||||
* @throws InternalServerErrorException
|
||||
*/
|
||||
public static function replaceMacros($s, array $vars = [])
|
||||
public static function replaceMacros(string $template, array $vars = [])
|
||||
{
|
||||
$stamp1 = microtime(true);
|
||||
|
||||
// pass $baseurl to all templates if it isn't set
|
||||
$vars = array_merge(['$baseurl' => DI::baseUrl()->get()], $vars);
|
||||
$vars = array_merge(['$baseurl' => DI::baseUrl()->get(), '$APP' => DI::app()], $vars);
|
||||
|
||||
$t = self::getTemplateEngine();
|
||||
|
||||
try {
|
||||
$output = $t->replaceMacros($s, $vars);
|
||||
$output = $t->replaceMacros($template, $vars);
|
||||
} catch (Exception $e) {
|
||||
echo "<pre><b>" . __FUNCTION__ . "</b>: " . $e->getMessage() . "</pre>";
|
||||
exit();
|
||||
DI::logger()->critical($e->getMessage(), ['template' => $template, 'vars' => $vars]);
|
||||
$message = is_site_admin() ?
|
||||
$e->getMessage() :
|
||||
DI::l10n()->t('Friendica can\'t display this page at the moment, please contact the administrator.');
|
||||
throw new InternalServerErrorException($message);
|
||||
}
|
||||
|
||||
DI::profiler()->saveTimestamp($stamp1, "rendering", System::callstack());
|
||||
|
|
@ -98,23 +100,25 @@ class Renderer
|
|||
/**
|
||||
* Load a given template $s
|
||||
*
|
||||
* @param string $s Template to load.
|
||||
* @param string $root Optional.
|
||||
* @param string $file Template to load.
|
||||
* @param string $subDir Subdirectory (Optional)
|
||||
*
|
||||
* @return string template.
|
||||
* @throws Exception
|
||||
* @throws InternalServerErrorException
|
||||
*/
|
||||
public static function getMarkupTemplate($s, $root = '')
|
||||
public static function getMarkupTemplate($file, $subDir = '')
|
||||
{
|
||||
$stamp1 = microtime(true);
|
||||
$a = DI::app();
|
||||
$t = self::getTemplateEngine();
|
||||
|
||||
try {
|
||||
$template = $t->getTemplateFile($s, $root);
|
||||
$template = $t->getTemplateFile($file, $subDir);
|
||||
} catch (Exception $e) {
|
||||
echo "<pre><b>" . __FUNCTION__ . "</b>: " . $e->getMessage() . "</pre>";
|
||||
exit();
|
||||
DI::logger()->critical($e->getMessage(), ['file' => $file, 'subDir' => $subDir]);
|
||||
$message = is_site_admin() ?
|
||||
$e->getMessage() :
|
||||
DI::l10n()->t('Friendica can\'t display this page at the moment, please contact the administrator.');
|
||||
throw new InternalServerErrorException($message);
|
||||
}
|
||||
|
||||
DI::profiler()->saveTimestamp($stamp1, "file", System::callstack());
|
||||
|
|
@ -126,18 +130,22 @@ class Renderer
|
|||
* Register template engine class
|
||||
*
|
||||
* @param string $class
|
||||
* @throws InternalServerErrorException
|
||||
*/
|
||||
public static function registerTemplateEngine($class)
|
||||
{
|
||||
$v = get_class_vars($class);
|
||||
|
||||
if (!empty($v['name']))
|
||||
{
|
||||
if (!empty($v['name'])) {
|
||||
$name = $v['name'];
|
||||
self::$template_engines[$name] = $class;
|
||||
} else {
|
||||
echo "template engine <tt>$class</tt> cannot be registered without a name.\n";
|
||||
die();
|
||||
$admin_message = DI::l10n()->t('template engine cannot be registered without a name.');
|
||||
DI::logger()->critical($admin_message, ['class' => $class]);
|
||||
$message = is_site_admin() ?
|
||||
$admin_message :
|
||||
DI::l10n()->t('Friendica can\'t display this page at the moment, please contact the administrator.');
|
||||
throw new InternalServerErrorException($message);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -147,7 +155,8 @@ class Renderer
|
|||
* If $name is not defined, return engine defined by theme,
|
||||
* or default
|
||||
*
|
||||
* @return ITemplateEngine Template Engine instance
|
||||
* @return TemplateEngine Template Engine instance
|
||||
* @throws InternalServerErrorException
|
||||
*/
|
||||
public static function getTemplateEngine()
|
||||
{
|
||||
|
|
@ -157,15 +166,20 @@ class Renderer
|
|||
if (isset(self::$template_engine_instance[$template_engine])) {
|
||||
return self::$template_engine_instance[$template_engine];
|
||||
} else {
|
||||
$a = DI::app();
|
||||
$class = self::$template_engines[$template_engine];
|
||||
$obj = new $class;
|
||||
$obj = new $class($a->getCurrentTheme(), $a->theme_info);
|
||||
self::$template_engine_instance[$template_engine] = $obj;
|
||||
return $obj;
|
||||
}
|
||||
}
|
||||
|
||||
echo "template engine <tt>$template_engine</tt> is not registered!\n";
|
||||
exit();
|
||||
$admin_message = DI::l10n()->t('template engine is not registered!');
|
||||
DI::logger()->critical($admin_message, ['template_engine' => $template_engine]);
|
||||
$message = is_site_admin() ?
|
||||
$admin_message :
|
||||
DI::l10n()->t('Friendica can\'t display this page at the moment, please contact the administrator.');
|
||||
throw new InternalServerErrorException($message);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ class Search
|
|||
/**
|
||||
* Search in the global directory for occurrences of the search string
|
||||
*
|
||||
* @see https://github.com/friendica/friendica-directory/blob/master/docs/Protocol.md#search
|
||||
* @see https://github.com/friendica/friendica-directory/blob/stable/docs/Protocol.md#search
|
||||
*
|
||||
* @param string $search
|
||||
* @param int $type specific type of searching
|
||||
|
|
@ -142,7 +142,7 @@ class Search
|
|||
$profiles = $results['profiles'] ?? [];
|
||||
|
||||
foreach ($profiles as $profile) {
|
||||
$profile_url = $profile['profile_url'] ?? '';
|
||||
$profile_url = $profile['url'] ?? '';
|
||||
$contactDetails = Contact::getDetailsByURL($profile_url, local_user());
|
||||
|
||||
$result = new ContactResult(
|
||||
|
|
@ -311,4 +311,19 @@ class Search
|
|||
{
|
||||
return DI::config()->get('system', 'directory', self::DEFAULT_DIRECTORY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the search path (either fulltext search or tag search)
|
||||
*
|
||||
* @param string $search
|
||||
* @return string search path
|
||||
*/
|
||||
public static function getSearchPath(string $search)
|
||||
{
|
||||
if (substr($search, 0, 1) == '#') {
|
||||
return 'search?tag=' . urlencode(substr($search, 1));
|
||||
} else {
|
||||
return 'search?q=' . urlencode($search);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,20 +45,22 @@ class System
|
|||
array_shift($trace);
|
||||
|
||||
$callstack = [];
|
||||
$previous = ['class' => '', 'function' => ''];
|
||||
$previous = ['class' => '', 'function' => '', 'database' => false];
|
||||
|
||||
// The ignore list contains all functions that are only wrapper functions
|
||||
$ignore = ['fetchUrl', 'call_user_func_array'];
|
||||
|
||||
while ($func = array_pop($trace)) {
|
||||
if (!empty($func['class'])) {
|
||||
// Don't show multiple calls from the "dba" class to show the essential parts of the callstack
|
||||
if ((($previous['class'] != $func['class']) || ($func['class'] != 'Friendica\Database\DBA')) && ($previous['function'] != 'q')) {
|
||||
// Don't show multiple calls from the Database classes to show the essential parts of the callstack
|
||||
$func['database'] = in_array($func['class'], ['Friendica\Database\DBA', 'Friendica\Database\Database']);
|
||||
if (!$previous['database'] || !$func['database']) {
|
||||
$classparts = explode("\\", $func['class']);
|
||||
$callstack[] = array_pop($classparts).'::'.$func['function'];
|
||||
$previous = $func;
|
||||
}
|
||||
} elseif (!in_array($func['function'], $ignore)) {
|
||||
$func['database'] = ($func['function'] == 'q');
|
||||
$callstack[] = $func['function'];
|
||||
$func['class'] = '';
|
||||
$previous = $func;
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ class Worker
|
|||
|
||||
// At first check the maximum load. We shouldn't continue with a high load
|
||||
if (DI::process()->isMaxLoadReached()) {
|
||||
Logger::log('Pre check: maximum load reached, quitting.', Logger::DEBUG);
|
||||
Logger::info('Pre check: maximum load reached, quitting.');
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -82,25 +82,25 @@ class Worker
|
|||
|
||||
// Count active workers and compare them with a maximum value that depends on the load
|
||||
if (self::tooMuchWorkers()) {
|
||||
Logger::log('Pre check: Active worker limit reached, quitting.', Logger::DEBUG);
|
||||
Logger::info('Pre check: Active worker limit reached, quitting.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Do we have too few memory?
|
||||
if (DI::process()->isMinMemoryReached()) {
|
||||
Logger::log('Pre check: Memory limit reached, quitting.', Logger::DEBUG);
|
||||
Logger::info('Pre check: Memory limit reached, quitting.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Possibly there are too much database connections
|
||||
if (self::maxConnectionsReached()) {
|
||||
Logger::log('Pre check: maximum connections reached, quitting.', Logger::DEBUG);
|
||||
Logger::info('Pre check: maximum connections reached, quitting.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Possibly there are too much database processes that block the system
|
||||
if (DI::process()->isMaxProcessesReached()) {
|
||||
Logger::log('Pre check: maximum processes reached, quitting.', Logger::DEBUG);
|
||||
Logger::info('Pre check: maximum processes reached, quitting.');
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -121,7 +121,7 @@ class Worker
|
|||
|
||||
// The work will be done
|
||||
if (!self::execute($entry)) {
|
||||
Logger::log('Process execution failed, quitting.', Logger::DEBUG);
|
||||
Logger::info('Process execution failed, quitting.');
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -143,14 +143,14 @@ class Worker
|
|||
if (DI::lock()->acquire('worker', 0)) {
|
||||
// Count active workers and compare them with a maximum value that depends on the load
|
||||
if (self::tooMuchWorkers()) {
|
||||
Logger::log('Active worker limit reached, quitting.', Logger::DEBUG);
|
||||
Logger::info('Active worker limit reached, quitting.');
|
||||
DI::lock()->release('worker');
|
||||
return;
|
||||
}
|
||||
|
||||
// Check free memory
|
||||
if (DI::process()->isMinMemoryReached()) {
|
||||
Logger::log('Memory limit reached, quitting.', Logger::DEBUG);
|
||||
Logger::info('Memory limit reached, quitting.');
|
||||
DI::lock()->release('worker');
|
||||
return;
|
||||
}
|
||||
|
|
@ -170,7 +170,7 @@ class Worker
|
|||
if (DI::config()->get('system', 'worker_daemon_mode', false)) {
|
||||
self::IPCSetJobState(false);
|
||||
}
|
||||
Logger::log("Couldn't select a workerqueue entry, quitting process " . getmypid() . ".", Logger::DEBUG);
|
||||
Logger::info("Couldn't select a workerqueue entry, quitting process", ['pid' => getmypid()]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -264,23 +264,27 @@ class Worker
|
|||
|
||||
// Quit when in maintenance
|
||||
if (DI::config()->get('system', 'maintenance', false, true)) {
|
||||
Logger::log("Maintenance mode - quit process ".$mypid, Logger::DEBUG);
|
||||
Logger::info("Maintenance mode - quit process", ['pid' => $mypid]);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Constantly check the number of parallel database processes
|
||||
if (DI::process()->isMaxProcessesReached()) {
|
||||
Logger::log("Max processes reached for process ".$mypid, Logger::DEBUG);
|
||||
Logger::info("Max processes reached for process", ['pid' => $mypid]);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Constantly check the number of available database connections to let the frontend be accessible at any time
|
||||
if (self::maxConnectionsReached()) {
|
||||
Logger::log("Max connection reached for process ".$mypid, Logger::DEBUG);
|
||||
Logger::info("Max connection reached for process", ['pid' => $mypid]);
|
||||
return false;
|
||||
}
|
||||
|
||||
$argv = json_decode($queue["parameter"], true);
|
||||
if (empty($argv)) {
|
||||
Logger::error('Parameter is empty', ['queue' => $queue]);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for existance and validity of the include file
|
||||
$include = $argv[0];
|
||||
|
|
@ -383,8 +387,6 @@ class Worker
|
|||
{
|
||||
$a = DI::app();
|
||||
|
||||
$argc = count($argv);
|
||||
|
||||
Logger::enableWorker($funcname);
|
||||
|
||||
Logger::info("Process start.", ['priority' => $queue["priority"], 'id' => $queue["id"]]);
|
||||
|
|
@ -406,7 +408,7 @@ class Worker
|
|||
if ($method_call) {
|
||||
call_user_func_array(sprintf('Friendica\Worker\%s::execute', $funcname), $argv);
|
||||
} else {
|
||||
$funcname($argv, $argc);
|
||||
$funcname($argv, count($argv));
|
||||
}
|
||||
|
||||
Logger::disableWorker();
|
||||
|
|
@ -504,7 +506,7 @@ class Worker
|
|||
$used = DBA::numRows($r);
|
||||
DBA::close($r);
|
||||
|
||||
Logger::log("Connection usage (user values): ".$used."/".$max, Logger::DEBUG);
|
||||
Logger::info("Connection usage (user values)", ['usage' => $used, 'max' => $max]);
|
||||
|
||||
$level = ($used / $max) * 100;
|
||||
|
||||
|
|
@ -532,7 +534,7 @@ class Worker
|
|||
if ($used == 0) {
|
||||
return false;
|
||||
}
|
||||
Logger::log("Connection usage (system values): ".$used."/".$max, Logger::DEBUG);
|
||||
Logger::info("Connection usage (system values)", ['used' => $used, 'max' => $max]);
|
||||
|
||||
$level = $used / $max * 100;
|
||||
|
||||
|
|
@ -582,6 +584,10 @@ class Worker
|
|||
$max_duration = $max_duration_defaults[$entry["priority"]];
|
||||
|
||||
$argv = json_decode($entry["parameter"], true);
|
||||
if (empty($argv)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$argv[0] = basename($argv[0]);
|
||||
|
||||
// How long is the process already running?
|
||||
|
|
@ -610,10 +616,11 @@ class Worker
|
|||
self::$db_duration += (microtime(true) - $stamp);
|
||||
self::$db_duration_write += (microtime(true) - $stamp);
|
||||
} else {
|
||||
Logger::log("Worker process ".$entry["pid"]." (".substr(json_encode($argv), 0, 50).") now runs for ".round($duration)." of ".$max_duration." allowed minutes. That's okay.", Logger::DEBUG);
|
||||
Logger::info('Process runtime is okay', ['pid' => $entry["pid"], 'duration' => $duration, 'max' => $max_duration, 'command' => substr(json_encode($argv), 0, 50)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
DBA::close($entries);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -684,7 +691,7 @@ class Worker
|
|||
self::$db_duration_stat += (microtime(true) - $stamp);
|
||||
while ($entry = DBA::fetch($jobs)) {
|
||||
$stamp = (float)microtime(true);
|
||||
$processes = DBA::p("SELECT COUNT(*) AS `running` FROM `process` INNER JOIN `workerqueue` ON `workerqueue`.`pid` = `process`.`pid` WHERE NOT `done` AND `priority` = ?", $entry["priority"]);
|
||||
$processes = DBA::p("SELECT COUNT(*) AS `running` FROM `workerqueue-view` WHERE `priority` = ?", $entry["priority"]);
|
||||
self::$db_duration += (microtime(true) - $stamp);
|
||||
self::$db_duration_stat += (microtime(true) - $stamp);
|
||||
if ($process = DBA::fetch($processes)) {
|
||||
|
|
@ -698,7 +705,7 @@ class Worker
|
|||
} else {
|
||||
$waiting_processes = self::totalEntries();
|
||||
$stamp = (float)microtime(true);
|
||||
$jobs = DBA::p("SELECT COUNT(*) AS `running`, `priority` FROM `process` INNER JOIN `workerqueue` ON `workerqueue`.`pid` = `process`.`pid` AND NOT `done` GROUP BY `priority` ORDER BY `priority`");
|
||||
$jobs = DBA::p("SELECT COUNT(*) AS `running`, `priority` FROM `workerqueue-view` GROUP BY `priority` ORDER BY `priority`");
|
||||
self::$db_duration += (microtime(true) - $stamp);
|
||||
self::$db_duration_stat += (microtime(true) - $stamp);
|
||||
|
||||
|
|
@ -720,7 +727,7 @@ class Worker
|
|||
$high_running = self::processWithPriorityActive($top_priority);
|
||||
|
||||
if (!$high_running && ($top_priority > PRIORITY_UNDEFINED) && ($top_priority < PRIORITY_NEGLIGIBLE)) {
|
||||
Logger::log("There are jobs with priority ".$top_priority." waiting but none is executed. Open a fastlane.", Logger::DEBUG);
|
||||
Logger::info("Jobs with a higher priority are waiting but none is executed. Open a fastlane.", ['priority' => $top_priority]);
|
||||
$queues = $active + 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -729,7 +736,7 @@ class Worker
|
|||
|
||||
// Are there fewer workers running as possible? Then fork a new one.
|
||||
if (!DI::config()->get("system", "worker_dont_fork", false) && ($queues > ($active + 1)) && self::entriesExists()) {
|
||||
Logger::log("Active workers: ".$active."/".$queues." Fork a new worker.", Logger::DEBUG);
|
||||
Logger::info("There are fewer workers as possible, fork a new worker.", ['active' => $active, 'queues' => $queues]);
|
||||
if (DI::config()->get('system', 'worker_daemon_mode', false)) {
|
||||
self::IPCSetJobState(true);
|
||||
} else {
|
||||
|
|
@ -839,9 +846,7 @@ class Worker
|
|||
$running = [];
|
||||
$running_total = 0;
|
||||
$stamp = (float)microtime(true);
|
||||
$processes = DBA::p("SELECT COUNT(DISTINCT(`process`.`pid`)) AS `running`, `priority` FROM `process`
|
||||
INNER JOIN `workerqueue` ON `workerqueue`.`pid` = `process`.`pid`
|
||||
WHERE NOT `done` GROUP BY `priority`");
|
||||
$processes = DBA::p("SELECT COUNT(DISTINCT(`pid`)) AS `running`, `priority` FROM `workerqueue-view` GROUP BY `priority`");
|
||||
self::$db_duration += (microtime(true) - $stamp);
|
||||
while ($process = DBA::fetch($processes)) {
|
||||
$running[$process['priority']] = $process['running'];
|
||||
|
|
@ -933,7 +938,7 @@ class Worker
|
|||
/**
|
||||
* Returns the next worker process
|
||||
*
|
||||
* @return string SQL statement
|
||||
* @return array worker processes
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public static function workerProcess()
|
||||
|
|
@ -1028,7 +1033,7 @@ class Worker
|
|||
|
||||
self::runCron();
|
||||
|
||||
Logger::log('Call worker', Logger::DEBUG);
|
||||
Logger::info('Call worker');
|
||||
self::spawnWorker();
|
||||
return;
|
||||
}
|
||||
|
|
@ -1074,7 +1079,7 @@ class Worker
|
|||
*/
|
||||
private static function runCron()
|
||||
{
|
||||
Logger::log('Add cron entries', Logger::DEBUG);
|
||||
Logger::info('Add cron entries');
|
||||
|
||||
// Check for spooled items
|
||||
self::add(['priority' => PRIORITY_HIGH, 'force_priority' => true], 'SpoolPost');
|
||||
|
|
|
|||
16
src/DI.php
16
src/DI.php
|
|
@ -279,6 +279,14 @@ abstract class DI
|
|||
return self::$dice->create(Factory\Api\Mastodon\Relationship::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Factory\Api\Twitter\User
|
||||
*/
|
||||
public static function twitterUser()
|
||||
{
|
||||
return self::$dice->create(Factory\Api\Twitter\User::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Factory\Notification\Notification
|
||||
*/
|
||||
|
|
@ -383,6 +391,14 @@ abstract class DI
|
|||
return self::$dice->create(Util\ACLFormatter::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public static function basePath()
|
||||
{
|
||||
return self::$dice->create('$basepath');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Util\DateTimeFormat
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -648,6 +648,20 @@ class DBA
|
|||
/**
|
||||
* Returns the SQL parameter string built from the provided parameter array
|
||||
*
|
||||
* Expected format for each key:
|
||||
*
|
||||
* group_by:
|
||||
* - list of column names
|
||||
*
|
||||
* order:
|
||||
* - numeric keyed column name => ASC
|
||||
* - associative element with boolean value => DESC (true), ASC (false)
|
||||
* - associative element with string value => 'ASC' or 'DESC' literally
|
||||
*
|
||||
* limit:
|
||||
* - single numeric value => count
|
||||
* - list with two numeric values => offset, count
|
||||
*
|
||||
* @param array $params
|
||||
* @return string
|
||||
*/
|
||||
|
|
@ -665,7 +679,11 @@ class DBA
|
|||
if ($order === 'RAND()') {
|
||||
$order_string .= "RAND(), ";
|
||||
} elseif (!is_int($fields)) {
|
||||
$order_string .= self::quoteIdentifier($fields) . " " . ($order ? "DESC" : "ASC") . ", ";
|
||||
if ($order !== 'DESC' && $order !== 'ASC') {
|
||||
$order = $order ? 'DESC' : 'ASC';
|
||||
}
|
||||
|
||||
$order_string .= self::quoteIdentifier($fields) . " " . $order . ", ";
|
||||
} else {
|
||||
$order_string .= self::quoteIdentifier($order) . ", ";
|
||||
}
|
||||
|
|
@ -741,6 +759,17 @@ class DBA
|
|||
return DI::dba()->processlist();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a database variable
|
||||
*
|
||||
* @param string $name
|
||||
* @return string content
|
||||
*/
|
||||
public static function getVariable(string $name)
|
||||
{
|
||||
return DI::dba()->getVariable($name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if $array is a filled array with at least one entry.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -25,10 +25,10 @@ use Exception;
|
|||
use Friendica\Core\Hook;
|
||||
use Friendica\Core\Logger;
|
||||
use Friendica\DI;
|
||||
use Friendica\Model\Item;
|
||||
use Friendica\Model\User;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
|
||||
require_once __DIR__ . '/../../include/dba.php';
|
||||
|
||||
/**
|
||||
* This class contains functions that doesn't need to know if pdo, mysqli or whatever is used.
|
||||
*/
|
||||
|
|
@ -49,7 +49,7 @@ class DBStructure
|
|||
private static $definition = [];
|
||||
|
||||
/**
|
||||
* Converts all tables from MyISAM to InnoDB
|
||||
* Converts all tables from MyISAM/InnoDB Antelope to InnoDB Barracuda
|
||||
*/
|
||||
public static function convertToInnoDB()
|
||||
{
|
||||
|
|
@ -59,13 +59,19 @@ class DBStructure
|
|||
['engine' => 'MyISAM', 'table_schema' => DBA::databaseName()]
|
||||
);
|
||||
|
||||
$tables = array_merge($tables, DBA::selectToArray(
|
||||
['information_schema' => 'tables'],
|
||||
['table_name'],
|
||||
['engine' => 'InnoDB', 'ROW_FORMAT' => ['COMPACT', 'REDUNDANT'], 'table_schema' => DBA::databaseName()]
|
||||
));
|
||||
|
||||
if (!DBA::isResult($tables)) {
|
||||
echo DI::l10n()->t('There are no tables on MyISAM.') . "\n";
|
||||
echo DI::l10n()->t('There are no tables on MyISAM or InnoDB with the Antelope file format.') . "\n";
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($tables AS $table) {
|
||||
$sql = "ALTER TABLE " . DBA::quoteIdentifier($table['table_name']) . " engine=InnoDB;";
|
||||
$sql = "ALTER TABLE " . DBA::quoteIdentifier($table['table_name']) . " ENGINE=InnoDB ROW_FORMAT=DYNAMIC;";
|
||||
echo $sql . "\n";
|
||||
|
||||
$result = DBA::e($sql);
|
||||
|
|
@ -106,10 +112,12 @@ class DBStructure
|
|||
|
||||
echo "\n";
|
||||
}
|
||||
|
||||
View::printStructure($basePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the database structure definition from the config/dbstructure.config.php file.
|
||||
* Loads the database structure definition from the static/dbstructure.config.php file.
|
||||
* On first pass, defines DB_UPDATE_VERSION constant.
|
||||
*
|
||||
* @see static/dbstructure.config.php
|
||||
|
|
@ -154,11 +162,16 @@ class DBStructure
|
|||
$comment = "";
|
||||
$sql_rows = [];
|
||||
$primary_keys = [];
|
||||
$foreign_keys = [];
|
||||
|
||||
foreach ($structure["fields"] AS $fieldname => $field) {
|
||||
$sql_rows[] = "`" . DBA::escape($fieldname) . "` " . self::FieldCommand($field);
|
||||
if (!empty($field['primary'])) {
|
||||
$primary_keys[] = $fieldname;
|
||||
}
|
||||
if (!empty($field['foreign'])) {
|
||||
$foreign_keys[$fieldname] = $field;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($structure["indexes"])) {
|
||||
|
|
@ -170,6 +183,10 @@ class DBStructure
|
|||
}
|
||||
}
|
||||
|
||||
foreach ($foreign_keys AS $fieldname => $parameters) {
|
||||
$sql_rows[] = self::foreignCommand($name, $fieldname, $parameters);
|
||||
}
|
||||
|
||||
if (isset($structure["engine"])) {
|
||||
$engine = " ENGINE=" . $structure["engine"];
|
||||
}
|
||||
|
|
@ -275,10 +292,18 @@ class DBStructure
|
|||
public static function update($basePath, $verbose, $action, $install = false, array $tables = null, array $definition = null)
|
||||
{
|
||||
if ($action && !$install) {
|
||||
if (self::isUpdating()) {
|
||||
return DI::l10n()->t('Another database update is currently running.');
|
||||
}
|
||||
|
||||
DI::config()->set('system', 'maintenance', 1);
|
||||
DI::config()->set('system', 'maintenance_reason', DI::l10n()->t('%s: Database update', DateTimeFormat::utcNow() . ' ' . date('e')));
|
||||
}
|
||||
|
||||
// ensure that all initial values exist. This test has to be done prior and after the structure check.
|
||||
// Prior is needed if the specific tables already exists - after is needed when they had been created.
|
||||
self::checkInitialValues();
|
||||
|
||||
$errors = '';
|
||||
|
||||
Logger::log('updating structure', Logger::DEBUG);
|
||||
|
|
@ -287,7 +312,7 @@ class DBStructure
|
|||
$database = [];
|
||||
|
||||
if (is_null($tables)) {
|
||||
$tables = q("SHOW TABLES");
|
||||
$tables = DBA::toArray(DBA::p("SHOW TABLES"));
|
||||
}
|
||||
|
||||
if (DBA::isResult($tables)) {
|
||||
|
|
@ -379,6 +404,7 @@ class DBStructure
|
|||
|
||||
// Remove the relation data that is used for the referential integrity
|
||||
unset($parameters['relation']);
|
||||
unset($parameters['foreign']);
|
||||
|
||||
// We change the collation after the indexes had been changed.
|
||||
// This is done to avoid index length problems.
|
||||
|
|
@ -433,9 +459,43 @@ class DBStructure
|
|||
}
|
||||
}
|
||||
|
||||
if (isset($database[$name]["table_status"]["Comment"])) {
|
||||
$existing_foreign_keys = $database[$name]['foreign_keys'];
|
||||
|
||||
// Foreign keys
|
||||
// Compare the field structure field by field
|
||||
foreach ($structure["fields"] AS $fieldname => $parameters) {
|
||||
if (empty($parameters['foreign'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$constraint = self::getConstraintName($name, $fieldname, $parameters);
|
||||
|
||||
unset($existing_foreign_keys[$constraint]);
|
||||
|
||||
if (empty($database[$name]['foreign_keys'][$constraint])) {
|
||||
$sql2 = self::addForeignKey($name, $fieldname, $parameters);
|
||||
|
||||
if ($sql3 == "") {
|
||||
$sql3 = "ALTER" . $ignore . " TABLE `" . $temp_name . "` " . $sql2;
|
||||
} else {
|
||||
$sql3 .= ", " . $sql2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($existing_foreign_keys as $param) {
|
||||
$sql2 = self::dropForeignKey($param['CONSTRAINT_NAME']);
|
||||
|
||||
if ($sql3 == "") {
|
||||
$sql3 = "ALTER" . $ignore . " TABLE `" . $temp_name . "` " . $sql2;
|
||||
} else {
|
||||
$sql3 .= ", " . $sql2;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($database[$name]["table_status"]["TABLE_COMMENT"])) {
|
||||
$structurecomment = $structure["comment"] ?? '';
|
||||
if ($database[$name]["table_status"]["Comment"] != $structurecomment) {
|
||||
if ($database[$name]["table_status"]["TABLE_COMMENT"] != $structurecomment) {
|
||||
$sql2 = "COMMENT = '" . DBA::escape($structurecomment) . "'";
|
||||
|
||||
if ($sql3 == "") {
|
||||
|
|
@ -446,8 +506,8 @@ class DBStructure
|
|||
}
|
||||
}
|
||||
|
||||
if (isset($database[$name]["table_status"]["Engine"]) && isset($structure['engine'])) {
|
||||
if ($database[$name]["table_status"]["Engine"] != $structure['engine']) {
|
||||
if (isset($database[$name]["table_status"]["ENGINE"]) && isset($structure['engine'])) {
|
||||
if ($database[$name]["table_status"]["ENGINE"] != $structure['engine']) {
|
||||
$sql2 = "ENGINE = '" . DBA::escape($structure['engine']) . "'";
|
||||
|
||||
if ($sql3 == "") {
|
||||
|
|
@ -458,8 +518,8 @@ class DBStructure
|
|||
}
|
||||
}
|
||||
|
||||
if (isset($database[$name]["table_status"]["Collation"])) {
|
||||
if ($database[$name]["table_status"]["Collation"] != 'utf8mb4_general_ci') {
|
||||
if (isset($database[$name]["table_status"]["TABLE_COLLATION"])) {
|
||||
if ($database[$name]["table_status"]["TABLE_COLLATION"] != 'utf8mb4_general_ci') {
|
||||
$sql2 = "DEFAULT COLLATE utf8mb4_general_ci";
|
||||
|
||||
if ($sql3 == "") {
|
||||
|
|
@ -588,6 +648,10 @@ class DBStructure
|
|||
}
|
||||
}
|
||||
|
||||
View::create(false, $action);
|
||||
|
||||
self::checkInitialValues();
|
||||
|
||||
if ($action && !$install) {
|
||||
DI::config()->set('system', 'maintenance', 0);
|
||||
DI::config()->set('system', 'maintenance_reason', '');
|
||||
|
|
@ -604,22 +668,36 @@ class DBStructure
|
|||
|
||||
private static function tableStructure($table)
|
||||
{
|
||||
$structures = q("DESCRIBE `%s`", $table);
|
||||
// This query doesn't seem to be executable as a prepared statement
|
||||
$indexes = DBA::toArray(DBA::p("SHOW INDEX FROM " . DBA::quoteIdentifier($table)));
|
||||
|
||||
$full_columns = q("SHOW FULL COLUMNS FROM `%s`", $table);
|
||||
$fields = DBA::selectToArray(['INFORMATION_SCHEMA' => 'COLUMNS'],
|
||||
['COLUMN_NAME', 'COLUMN_TYPE', 'IS_NULLABLE', 'COLUMN_DEFAULT', 'EXTRA',
|
||||
'COLUMN_KEY', 'COLLATION_NAME', 'COLUMN_COMMENT'],
|
||||
["`TABLE_SCHEMA` = ? AND `TABLE_NAME` = ?",
|
||||
DBA::databaseName(), $table]);
|
||||
|
||||
$indexes = q("SHOW INDEX FROM `%s`", $table);
|
||||
$foreign_keys = DBA::selectToArray(['INFORMATION_SCHEMA' => 'KEY_COLUMN_USAGE'],
|
||||
['COLUMN_NAME', 'CONSTRAINT_NAME', 'REFERENCED_TABLE_NAME', 'REFERENCED_COLUMN_NAME'],
|
||||
["`TABLE_SCHEMA` = ? AND `TABLE_NAME` = ? AND `REFERENCED_TABLE_SCHEMA` IS NOT NULL",
|
||||
DBA::databaseName(), $table]);
|
||||
|
||||
$table_status = q("SHOW TABLE STATUS WHERE `name` = '%s'", $table);
|
||||
|
||||
if (DBA::isResult($table_status)) {
|
||||
$table_status = $table_status[0];
|
||||
} else {
|
||||
$table_status = [];
|
||||
}
|
||||
$table_status = DBA::selectFirst(['INFORMATION_SCHEMA' => 'TABLES'],
|
||||
['ENGINE', 'TABLE_COLLATION', 'TABLE_COMMENT'],
|
||||
["`TABLE_SCHEMA` = ? AND `TABLE_NAME` = ?",
|
||||
DBA::databaseName(), $table]);
|
||||
|
||||
$fielddata = [];
|
||||
$indexdata = [];
|
||||
$foreigndata = [];
|
||||
|
||||
if (DBA::isResult($foreign_keys)) {
|
||||
foreach ($foreign_keys as $foreign_key) {
|
||||
$parameters = ['foreign' => [$foreign_key['REFERENCED_TABLE_NAME'] => $foreign_key['REFERENCED_COLUMN_NAME']]];
|
||||
$constraint = self::getConstraintName($table, $foreign_key['COLUMN_NAME'], $parameters);
|
||||
$foreigndata[$constraint] = $foreign_key;
|
||||
}
|
||||
}
|
||||
|
||||
if (DBA::isResult($indexes)) {
|
||||
foreach ($indexes AS $index) {
|
||||
|
|
@ -640,39 +718,39 @@ class DBStructure
|
|||
$indexdata[$index["Key_name"]][] = $column;
|
||||
}
|
||||
}
|
||||
if (DBA::isResult($structures)) {
|
||||
foreach ($structures AS $field) {
|
||||
// Replace the default size values so that we don't have to define them
|
||||
|
||||
$fielddata = [];
|
||||
if (DBA::isResult($fields)) {
|
||||
foreach ($fields AS $field) {
|
||||
$search = ['tinyint(1)', 'tinyint(3) unsigned', 'tinyint(4)', 'smallint(5) unsigned', 'smallint(6)', 'mediumint(8) unsigned', 'mediumint(9)', 'bigint(20)', 'int(10) unsigned', 'int(11)'];
|
||||
$replace = ['boolean', 'tinyint unsigned', 'tinyint', 'smallint unsigned', 'smallint', 'mediumint unsigned', 'mediumint', 'bigint', 'int unsigned', 'int'];
|
||||
$field["Type"] = str_replace($search, $replace, $field["Type"]);
|
||||
$field['COLUMN_TYPE'] = str_replace($search, $replace, $field['COLUMN_TYPE']);
|
||||
|
||||
$fielddata[$field["Field"]]["type"] = $field["Type"];
|
||||
if ($field["Null"] == "NO") {
|
||||
$fielddata[$field["Field"]]["not null"] = true;
|
||||
$fielddata[$field['COLUMN_NAME']]['type'] = $field['COLUMN_TYPE'];
|
||||
|
||||
if ($field['IS_NULLABLE'] == 'NO') {
|
||||
$fielddata[$field['COLUMN_NAME']]['not null'] = true;
|
||||
}
|
||||
|
||||
if (isset($field["Default"])) {
|
||||
$fielddata[$field["Field"]]["default"] = $field["Default"];
|
||||
if (isset($field['COLUMN_DEFAULT']) && ($field['COLUMN_DEFAULT'] != 'NULL')) {
|
||||
$fielddata[$field['COLUMN_NAME']]['default'] = trim($field['COLUMN_DEFAULT'], "'");
|
||||
}
|
||||
|
||||
if ($field["Extra"] != "") {
|
||||
$fielddata[$field["Field"]]["extra"] = $field["Extra"];
|
||||
if (!empty($field['EXTRA'])) {
|
||||
$fielddata[$field['COLUMN_NAME']]['extra'] = $field['EXTRA'];
|
||||
}
|
||||
|
||||
if ($field["Key"] == "PRI") {
|
||||
$fielddata[$field["Field"]]["primary"] = true;
|
||||
if ($field['COLUMN_KEY'] == 'PRI') {
|
||||
$fielddata[$field['COLUMN_NAME']]['primary'] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (DBA::isResult($full_columns)) {
|
||||
foreach ($full_columns AS $column) {
|
||||
$fielddata[$column["Field"]]["Collation"] = $column["Collation"];
|
||||
$fielddata[$column["Field"]]["comment"] = $column["Comment"];
|
||||
|
||||
$fielddata[$field['COLUMN_NAME']]['Collation'] = $field['COLLATION_NAME'];
|
||||
$fielddata[$field['COLUMN_NAME']]['comment'] = $field['COLUMN_COMMENT'];
|
||||
}
|
||||
}
|
||||
|
||||
return ["fields" => $fielddata, "indexes" => $indexdata, "table_status" => $table_status];
|
||||
return ["fields" => $fielddata, "indexes" => $indexdata,
|
||||
"foreign_keys" => $foreigndata, "table_status" => $table_status];
|
||||
}
|
||||
|
||||
private static function dropIndex($indexname)
|
||||
|
|
@ -693,6 +771,45 @@ class DBStructure
|
|||
return ($sql);
|
||||
}
|
||||
|
||||
private static function getConstraintName(string $tablename, string $fieldname, array $parameters)
|
||||
{
|
||||
$foreign_table = array_keys($parameters['foreign'])[0];
|
||||
$foreign_field = array_values($parameters['foreign'])[0];
|
||||
|
||||
return $tablename . "-" . $fieldname. "-" . $foreign_table. "-" . $foreign_field;
|
||||
}
|
||||
|
||||
private static function foreignCommand(string $tablename, string $fieldname, array $parameters) {
|
||||
$foreign_table = array_keys($parameters['foreign'])[0];
|
||||
$foreign_field = array_values($parameters['foreign'])[0];
|
||||
|
||||
$sql = "FOREIGN KEY (`" . $fieldname . "`) REFERENCES `" . $foreign_table . "` (`" . $foreign_field . "`)";
|
||||
|
||||
if (!empty($parameters['foreign']['on update'])) {
|
||||
$sql .= " ON UPDATE " . strtoupper($parameters['foreign']['on update']);
|
||||
} else {
|
||||
$sql .= " ON UPDATE RESTRICT";
|
||||
}
|
||||
|
||||
if (!empty($parameters['foreign']['on delete'])) {
|
||||
$sql .= " ON DELETE " . strtoupper($parameters['foreign']['on delete']);
|
||||
} else {
|
||||
$sql .= " ON DELETE CASCADE";
|
||||
}
|
||||
|
||||
return $sql;
|
||||
}
|
||||
|
||||
private static function addForeignKey(string $tablename, string $fieldname, array $parameters)
|
||||
{
|
||||
return sprintf("ADD %s", self::foreignCommand($tablename, $fieldname, $parameters));
|
||||
}
|
||||
|
||||
private static function dropForeignKey(string $constraint)
|
||||
{
|
||||
return sprintf("DROP FOREIGN KEY `%s`", $constraint);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a GROUP BY clause from a UNIQUE index definition.
|
||||
*
|
||||
|
|
@ -830,6 +947,19 @@ class DBStructure
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a foreign key exists for the given table field
|
||||
*
|
||||
* @param string $table
|
||||
* @param string $field
|
||||
* @return boolean
|
||||
*/
|
||||
public static function existsForeignKeyForField(string $table, string $field)
|
||||
{
|
||||
return DBA::exists(['INFORMATION_SCHEMA' => 'KEY_COLUMN_USAGE'],
|
||||
["`TABLE_SCHEMA` = ? AND `TABLE_NAME` = ? AND `COLUMN_NAME` = ? AND `REFERENCED_TABLE_SCHEMA` IS NOT NULL",
|
||||
DBA::databaseName(), $table, $field]);
|
||||
}
|
||||
/**
|
||||
* Check if a table exists
|
||||
*
|
||||
|
|
@ -868,4 +998,97 @@ class DBStructure
|
|||
$stmtColumns = DBA::p("SHOW COLUMNS FROM `" . $table . "`");
|
||||
return DBA::toArray($stmtColumns);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if initial database values do exist - or create them
|
||||
*/
|
||||
public static function checkInitialValues()
|
||||
{
|
||||
if (self::existsTable('verb') && !DBA::exists('verb', ['id' => 1])) {
|
||||
foreach (Item::ACTIVITIES as $index => $activity) {
|
||||
DBA::insert('verb', ['id' => $index + 1, 'name' => $activity], true);
|
||||
}
|
||||
}
|
||||
|
||||
if (self::existsTable('contact') && !DBA::exists('contact', ['id' => 0])) {
|
||||
DBA::insert('contact', ['nurl' => '']);
|
||||
$lastid = DBA::lastInsertId();
|
||||
if ($lastid != 0) {
|
||||
DBA::update('contact', ['id' => 0], ['id' => $lastid]);
|
||||
}
|
||||
}
|
||||
|
||||
if (self::existsTable('permissionset')) {
|
||||
if (!DBA::exists('permissionset', ['id' => 0])) {
|
||||
DBA::insert('permissionset', ['allow_cid' => '', 'allow_gid' => '', 'deny_cid' => '', 'deny_gid' => '']);
|
||||
$lastid = DBA::lastInsertId();
|
||||
if ($lastid != 0) {
|
||||
DBA::update('permissionset', ['id' => 0], ['id' => $lastid]);
|
||||
}
|
||||
}
|
||||
if (!self::existsForeignKeyForField('item', 'psid')) {
|
||||
$sets = DBA::p("SELECT `psid`, `item`.`uid`, `item`.`private` FROM `item`
|
||||
LEFT JOIN `permissionset` ON `permissionset`.`id` = `item`.`psid`
|
||||
WHERE `permissionset`.`id` IS NULL AND NOT `psid` IS NULL");
|
||||
while ($set = DBA::fetch($sets)) {
|
||||
if (($set['private'] == Item::PRIVATE) && ($set['uid'] != 0)) {
|
||||
$owner = User::getOwnerDataById($set['uid']);
|
||||
if ($owner) {
|
||||
$permission = '<' . $owner['id'] . '>';
|
||||
} else {
|
||||
$permission = '<>';
|
||||
}
|
||||
} else {
|
||||
$permission = '';
|
||||
}
|
||||
$fields = ['id' => $set['psid'], 'uid' => $set['uid'], 'allow_cid' => $permission,
|
||||
'allow_gid' => '', 'deny_cid' => '', 'deny_gid' => ''];
|
||||
DBA::insert('permissionset', $fields);
|
||||
}
|
||||
DBA::close($sets);
|
||||
}
|
||||
}
|
||||
|
||||
if (self::existsTable('tag') && !DBA::exists('tag', ['id' => 0])) {
|
||||
DBA::insert('tag', ['name' => '']);
|
||||
$lastid = DBA::lastInsertId();
|
||||
if ($lastid != 0) {
|
||||
DBA::update('tag', ['id' => 0], ['id' => $lastid]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!self::existsForeignKeyForField('tokens', 'client_id')) {
|
||||
$tokens = DBA::p("SELECT `tokens`.`id` FROM `tokens`
|
||||
LEFT JOIN `clients` ON `clients`.`client_id` = `tokens`.`client_id`
|
||||
WHERE `clients`.`client_id` IS NULL");
|
||||
while ($token = DBA::fetch($tokens)) {
|
||||
DBA::delete('tokens', ['id' => $token['id']]);
|
||||
}
|
||||
DBA::close($tokens);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a database update is currently running
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
private static function isUpdating()
|
||||
{
|
||||
$isUpdate = false;
|
||||
|
||||
$processes = DBA::select(['information_schema' => 'processlist'], ['info'],
|
||||
['db' => DBA::databaseName(), 'command' => ['Query', 'Execute']]);
|
||||
|
||||
while ($process = DBA::fetch($processes)) {
|
||||
$parts = explode(' ', $process['info']);
|
||||
if (in_array(strtolower(array_shift($parts)), ['alter', 'create', 'drop', 'rename'])) {
|
||||
$isUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
DBA::close($processes);
|
||||
|
||||
return $isUpdate;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,8 +21,10 @@
|
|||
|
||||
namespace Friendica\Database;
|
||||
|
||||
use Exception;
|
||||
use Friendica\Core\Config\Cache;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\DI;
|
||||
use Friendica\Network\HTTPException\InternalServerErrorException;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
use Friendica\Util\Profiler;
|
||||
|
|
@ -57,11 +59,13 @@ class Database
|
|||
/** @var PDO|mysqli */
|
||||
protected $connection;
|
||||
protected $driver;
|
||||
private $emulate_prepares = false;
|
||||
private $error = false;
|
||||
private $errorno = 0;
|
||||
private $affected_rows = 0;
|
||||
protected $in_transaction = false;
|
||||
protected $in_retrial = false;
|
||||
protected $testmode = false;
|
||||
private $relation = [];
|
||||
|
||||
public function __construct(Cache $configCache, Profiler $profiler, LoggerInterface $logger, array $server = [])
|
||||
|
|
@ -130,7 +134,10 @@ class Database
|
|||
return false;
|
||||
}
|
||||
|
||||
if (class_exists('\PDO') && in_array('mysql', PDO::getAvailableDrivers())) {
|
||||
$this->emulate_prepares = (bool)$this->configCache->get('database', 'emulate_prepares');
|
||||
$this->pdo_emulate_prepares = (bool)$this->configCache->get('database', 'pdo_emulate_prepares');
|
||||
|
||||
if (!$this->configCache->get('database', 'disable_pdo') && class_exists('\PDO') && in_array('mysql', PDO::getAvailableDrivers())) {
|
||||
$this->driver = 'pdo';
|
||||
$connect = "mysql:host=" . $server . ";dbname=" . $db;
|
||||
|
||||
|
|
@ -144,7 +151,7 @@ class Database
|
|||
|
||||
try {
|
||||
$this->connection = @new PDO($connect, $user, $pass);
|
||||
$this->connection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
|
||||
$this->connection->setAttribute(PDO::ATTR_EMULATE_PREPARES, $this->pdo_emulate_prepares);
|
||||
$this->connected = true;
|
||||
} catch (PDOException $e) {
|
||||
$this->connected = false;
|
||||
|
|
@ -178,6 +185,10 @@ class Database
|
|||
return $this->connected;
|
||||
}
|
||||
|
||||
public function setTestmode(bool $test)
|
||||
{
|
||||
$this->testmode = $test;
|
||||
}
|
||||
/**
|
||||
* Sets the logger for DBA
|
||||
*
|
||||
|
|
@ -308,7 +319,7 @@ class Database
|
|||
}
|
||||
|
||||
$watchlist = explode(',', $this->configCache->get('system', 'db_log_index_watch'));
|
||||
$blacklist = explode(',', $this->configCache->get('system', 'db_log_index_blacklist'));
|
||||
$denylist = explode(',', $this->configCache->get('system', 'db_log_index_denylist'));
|
||||
|
||||
while ($row = $this->fetch($r)) {
|
||||
if ((intval($this->configCache->get('system', 'db_loglimit_index')) > 0)) {
|
||||
|
|
@ -322,7 +333,7 @@ class Database
|
|||
$log = true;
|
||||
}
|
||||
|
||||
if (in_array($row['key'], $blacklist) || ($row['key'] == "")) {
|
||||
if (in_array($row['key'], $denylist) || ($row['key'] == "")) {
|
||||
$log = false;
|
||||
}
|
||||
|
||||
|
|
@ -338,7 +349,7 @@ class Database
|
|||
}
|
||||
|
||||
/**
|
||||
* Removes every not whitelisted character from the identifier string
|
||||
* Removes every not allowlisted character from the identifier string
|
||||
*
|
||||
* @param string $identifier
|
||||
*
|
||||
|
|
@ -428,8 +439,10 @@ class Database
|
|||
{
|
||||
$offset = 0;
|
||||
foreach ($args AS $param => $value) {
|
||||
if (is_int($args[$param]) || is_float($args[$param])) {
|
||||
if (is_int($args[$param]) || is_float($args[$param]) || is_bool($args[$param])) {
|
||||
$replace = intval($args[$param]);
|
||||
} elseif (is_null($args[$param])) {
|
||||
$replace = 'NULL';
|
||||
} else {
|
||||
$replace = "'" . $this->escape($args[$param]) . "'";
|
||||
}
|
||||
|
|
@ -492,6 +505,7 @@ class Database
|
|||
$sql = "/*" . System::callstack() . " */ " . $sql;
|
||||
}
|
||||
|
||||
$is_error = false;
|
||||
$this->error = '';
|
||||
$this->errorno = 0;
|
||||
$this->affected_rows = 0;
|
||||
|
|
@ -515,12 +529,13 @@ class Database
|
|||
switch ($this->driver) {
|
||||
case 'pdo':
|
||||
// If there are no arguments we use "query"
|
||||
if (count($args) == 0) {
|
||||
if (!$retval = $this->connection->query($sql)) {
|
||||
if ($this->emulate_prepares || count($args) == 0) {
|
||||
if (!$retval = $this->connection->query($this->replaceParameters($sql, $args))) {
|
||||
$errorInfo = $this->connection->errorInfo();
|
||||
$this->error = $errorInfo[2];
|
||||
$this->errorno = $errorInfo[1];
|
||||
$retval = false;
|
||||
$is_error = true;
|
||||
break;
|
||||
}
|
||||
$this->affected_rows = $retval->rowCount();
|
||||
|
|
@ -533,6 +548,7 @@ class Database
|
|||
$this->error = $errorInfo[2];
|
||||
$this->errorno = $errorInfo[1];
|
||||
$retval = false;
|
||||
$is_error = true;
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -550,6 +566,7 @@ class Database
|
|||
$this->error = $errorInfo[2];
|
||||
$this->errorno = $errorInfo[1];
|
||||
$retval = false;
|
||||
$is_error = true;
|
||||
} else {
|
||||
$retval = $stmt;
|
||||
$this->affected_rows = $retval->rowCount();
|
||||
|
|
@ -562,12 +579,13 @@ class Database
|
|||
$can_be_prepared = in_array($command, ['select', 'update', 'insert', 'delete']);
|
||||
|
||||
// The fallback routine is called as well when there are no arguments
|
||||
if (!$can_be_prepared || (count($args) == 0)) {
|
||||
if ($this->emulate_prepares || !$can_be_prepared || (count($args) == 0)) {
|
||||
$retval = $this->connection->query($this->replaceParameters($sql, $args));
|
||||
if ($this->connection->errno) {
|
||||
$this->error = $this->connection->error;
|
||||
$this->errorno = $this->connection->errno;
|
||||
$retval = false;
|
||||
$is_error = true;
|
||||
} else {
|
||||
if (isset($retval->num_rows)) {
|
||||
$this->affected_rows = $retval->num_rows;
|
||||
|
|
@ -584,6 +602,7 @@ class Database
|
|||
$this->error = $stmt->error;
|
||||
$this->errorno = $stmt->errno;
|
||||
$retval = false;
|
||||
$is_error = true;
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -611,6 +630,7 @@ class Database
|
|||
$this->error = $this->connection->error;
|
||||
$this->errorno = $this->connection->errno;
|
||||
$retval = false;
|
||||
$is_error = true;
|
||||
} else {
|
||||
$stmt->store_result();
|
||||
$retval = $stmt;
|
||||
|
|
@ -619,15 +639,29 @@ class Database
|
|||
break;
|
||||
}
|
||||
|
||||
// See issue https://github.com/friendica/friendica/issues/8572
|
||||
// Ensure that we always get an error message on an error.
|
||||
if ($is_error && empty($this->errorno)) {
|
||||
$this->errorno = -1;
|
||||
}
|
||||
|
||||
if ($is_error && empty($this->error)) {
|
||||
$this->error = 'Unknown database error';
|
||||
}
|
||||
|
||||
// We are having an own error logging in the function "e"
|
||||
if (($this->errorno != 0) && !$called_from_e) {
|
||||
// We have to preserve the error code, somewhere in the logging it get lost
|
||||
$error = $this->error;
|
||||
$errorno = $this->errorno;
|
||||
|
||||
if ($this->testmode) {
|
||||
throw new Exception(DI::l10n()->t('Database error %d "%s" at "%s"', $errorno, $error, $this->replaceParameters($sql, $args)));
|
||||
}
|
||||
|
||||
$this->logger->error('DB Error', [
|
||||
'code' => $this->errorno,
|
||||
'error' => $this->error,
|
||||
'code' => $errorno,
|
||||
'error' => $error,
|
||||
'callstack' => System::callstack(8),
|
||||
'params' => $this->replaceParameters($sql, $args),
|
||||
]);
|
||||
|
|
@ -638,21 +672,21 @@ class Database
|
|||
// It doesn't make sense to continue when the database connection was lost
|
||||
if ($this->in_retrial) {
|
||||
$this->logger->notice('Giving up retrial because of database error', [
|
||||
'code' => $this->errorno,
|
||||
'error' => $this->error,
|
||||
'code' => $errorno,
|
||||
'error' => $error,
|
||||
]);
|
||||
} else {
|
||||
$this->logger->notice('Couldn\'t reconnect after database error', [
|
||||
'code' => $this->errorno,
|
||||
'error' => $this->error,
|
||||
'code' => $errorno,
|
||||
'error' => $error,
|
||||
]);
|
||||
}
|
||||
exit(1);
|
||||
} else {
|
||||
// We try it again
|
||||
$this->logger->notice('Reconnected after database error', [
|
||||
'code' => $this->errorno,
|
||||
'error' => $this->error,
|
||||
'code' => $errorno,
|
||||
'error' => $error,
|
||||
]);
|
||||
$this->in_retrial = true;
|
||||
$ret = $this->p($sql, $args);
|
||||
|
|
@ -724,9 +758,13 @@ class Database
|
|||
$error = $this->error;
|
||||
$errorno = $this->errorno;
|
||||
|
||||
if ($this->testmode) {
|
||||
throw new Exception(DI::l10n()->t('Database error %d "%s" at "%s"', $errorno, $error, $this->replaceParameters($sql, $params)));
|
||||
}
|
||||
|
||||
$this->logger->error('DB Error', [
|
||||
'code' => $this->errorno,
|
||||
'error' => $this->error,
|
||||
'code' => $errorno,
|
||||
'error' => $error,
|
||||
'callstack' => System::callstack(8),
|
||||
'params' => $this->replaceParameters($sql, $params),
|
||||
]);
|
||||
|
|
@ -735,8 +773,8 @@ class Database
|
|||
// A reconnect like in $this->p could be dangerous with modifications
|
||||
if ($errorno == 2006) {
|
||||
$this->logger->notice('Giving up because of database error', [
|
||||
'code' => $this->errorno,
|
||||
'error' => $this->error,
|
||||
'code' => $errorno,
|
||||
'error' => $error,
|
||||
]);
|
||||
exit(1);
|
||||
}
|
||||
|
|
@ -941,7 +979,7 @@ class Database
|
|||
* @return boolean was the insert successful?
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function insert($table, $param, $on_duplicate_update = false)
|
||||
public function insert($table, array $param, bool $on_duplicate_update = false)
|
||||
{
|
||||
if (empty($table) || empty($param)) {
|
||||
$this->logger->info('Table and fields have to be set');
|
||||
|
|
@ -1009,7 +1047,7 @@ class Database
|
|||
$success = $this->e("LOCK TABLES " . DBA::buildTableString($table) . " WRITE");
|
||||
|
||||
if ($this->driver == 'pdo') {
|
||||
$this->connection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
|
||||
$this->connection->setAttribute(PDO::ATTR_EMULATE_PREPARES, $this->pdo_emulate_prepares);
|
||||
}
|
||||
|
||||
if (!$success) {
|
||||
|
|
@ -1042,7 +1080,7 @@ class Database
|
|||
$success = $this->e("UNLOCK TABLES");
|
||||
|
||||
if ($this->driver == 'pdo') {
|
||||
$this->connection->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
|
||||
$this->connection->setAttribute(PDO::ATTR_EMULATE_PREPARES, $this->pdo_emulate_prepares);
|
||||
$this->e("SET autocommit=1");
|
||||
} else {
|
||||
$this->connection->autocommit(true);
|
||||
|
|
@ -1429,24 +1467,30 @@ class Database
|
|||
/**
|
||||
* Select rows from a table
|
||||
*
|
||||
*
|
||||
* Example:
|
||||
* $table = 'item';
|
||||
* or:
|
||||
* $table = ['schema' => 'table'];
|
||||
* @see DBA::buildTableString()
|
||||
*
|
||||
* $fields = ['id', 'uri', 'uid', 'network'];
|
||||
*
|
||||
* $condition = ['uid' => 1, 'network' => 'dspr', 'blocked' => true];
|
||||
* or:
|
||||
* $condition = ['`uid` = ? AND `network` IN (?, ?)', 1, 'dfrn', 'dspr'];
|
||||
* @see DBA::buildCondition()
|
||||
*
|
||||
* $params = ['order' => ['id', 'received' => true, 'created' => 'ASC'), 'limit' => 10];
|
||||
* @see DBA::buildParameter()
|
||||
*
|
||||
* $data = DBA::select($table, $fields, $condition, $params);
|
||||
*
|
||||
* @param string|array $table Table name or array [schema => table]
|
||||
* @param array $fields Array of selected fields, empty for all
|
||||
* @param array $condition Array of fields for condition
|
||||
* @param array $params Array of several parameters
|
||||
*
|
||||
* @return boolean|object
|
||||
*
|
||||
* Example:
|
||||
* $table = "item";
|
||||
* $fields = array("id", "uri", "uid", "network");
|
||||
*
|
||||
* $condition = array("uid" => 1, "network" => 'dspr');
|
||||
* or:
|
||||
* $condition = array("`uid` = ? AND `network` IN (?, ?)", 1, 'dfrn', 'dspr');
|
||||
*
|
||||
* $params = array("order" => array("id", "received" => true), "limit" => 10);
|
||||
*
|
||||
* $data = DBA::select($table, $fields, $condition, $params);
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function select($table, array $fields = [], array $condition = [], array $params = [])
|
||||
|
|
@ -1640,6 +1684,18 @@ class Database
|
|||
return (["list" => $statelist, "amount" => $processes]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a database variable
|
||||
*
|
||||
* @param string $name
|
||||
* @return string content
|
||||
*/
|
||||
public function getVariable(string $name)
|
||||
{
|
||||
$result = $this->fetchFirst("SHOW GLOBAL VARIABLES WHERE `Variable_name` = ?", $name);
|
||||
return $result['Value'] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if $array is a filled array with at least one entry.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -25,10 +25,15 @@ use Friendica\Core\Logger;
|
|||
use Friendica\Core\Protocol;
|
||||
use Friendica\DI;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Model\GServer;
|
||||
use Friendica\Model\Item;
|
||||
use Friendica\Model\ItemURI;
|
||||
use Friendica\Model\PermissionSet;
|
||||
use Friendica\Model\Post\Category;
|
||||
use Friendica\Model\Tag;
|
||||
use Friendica\Model\UserItem;
|
||||
use Friendica\Model\Verb;
|
||||
use Friendica\Util\Strings;
|
||||
|
||||
/**
|
||||
* These database-intensive post update routines are meant to be executed in the background by the cronjob.
|
||||
|
|
@ -38,6 +43,9 @@ use Friendica\Model\UserItem;
|
|||
*/
|
||||
class PostUpdate
|
||||
{
|
||||
// Needed for the helper function to read from the legacy term table
|
||||
const OBJECT_TYPE_POST = 1;
|
||||
|
||||
/**
|
||||
* Calls the post update functions
|
||||
*/
|
||||
|
|
@ -64,6 +72,30 @@ class PostUpdate
|
|||
if (!self::update1329()) {
|
||||
return false;
|
||||
}
|
||||
if (!self::update1341()) {
|
||||
return false;
|
||||
}
|
||||
if (!self::update1342()) {
|
||||
return false;
|
||||
}
|
||||
if (!self::update1345()) {
|
||||
return false;
|
||||
}
|
||||
if (!self::update1346()) {
|
||||
return false;
|
||||
}
|
||||
if (!self::update1347()) {
|
||||
return false;
|
||||
}
|
||||
if (!self::update1348()) {
|
||||
return false;
|
||||
}
|
||||
if (!self::update1349()) {
|
||||
return false;
|
||||
}
|
||||
if (!self::update1350()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -533,4 +565,489 @@ class PostUpdate
|
|||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill the "tag" table with tags and mentions from the body
|
||||
*
|
||||
* @return bool "true" when the job is done
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
private static function update1341()
|
||||
{
|
||||
// Was the script completed?
|
||||
if (DI::config()->get('system', 'post_update_version') >= 1341) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$id = DI::config()->get('system', 'post_update_version_1341_id', 0);
|
||||
|
||||
Logger::info('Start', ['item' => $id]);
|
||||
|
||||
$rows = 0;
|
||||
|
||||
$items = DBA::p("SELECT `uri-id`,`body` FROM `item-content` WHERE
|
||||
(`body` LIKE ? OR `body` LIKE ? OR `body` LIKE ?) AND `uri-id` >= ?
|
||||
ORDER BY `uri-id` LIMIT 100000", '%#%', '%@%', '%!%', $id);
|
||||
|
||||
if (DBA::errorNo() != 0) {
|
||||
Logger::error('Database error', ['no' => DBA::errorNo(), 'message' => DBA::errorMessage()]);
|
||||
return false;
|
||||
}
|
||||
|
||||
while ($item = DBA::fetch($items)) {
|
||||
Tag::storeFromBody($item['uri-id'], $item['body'], '#!@', false);
|
||||
$id = $item['uri-id'];
|
||||
++$rows;
|
||||
if ($rows % 1000 == 0) {
|
||||
DI::config()->set('system', 'post_update_version_1341_id', $id);
|
||||
}
|
||||
}
|
||||
DBA::close($items);
|
||||
|
||||
DI::config()->set('system', 'post_update_version_1341_id', $id);
|
||||
|
||||
Logger::info('Processed', ['rows' => $rows, 'last' => $id]);
|
||||
|
||||
// When there are less than 1,000 items processed this means that we reached the end
|
||||
// The other entries will then be processed with the regular functionality
|
||||
if ($rows < 1000) {
|
||||
DI::config()->set('system', 'post_update_version', 1341);
|
||||
Logger::info('Done');
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill the "tag" table with tags and mentions from the "term" table
|
||||
*
|
||||
* @return bool "true" when the job is done
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
private static function update1342()
|
||||
{
|
||||
// Was the script completed?
|
||||
if (DI::config()->get('system', 'post_update_version') >= 1342) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$id = DI::config()->get('system', 'post_update_version_1342_id', 0);
|
||||
|
||||
Logger::info('Start', ['item' => $id]);
|
||||
|
||||
$rows = 0;
|
||||
|
||||
$terms = DBA::p("SELECT `term`.`tid`, `item`.`uri-id`, `term`.`type`, `term`.`term`, `term`.`url`, `item-content`.`body`
|
||||
FROM `term`
|
||||
INNER JOIN `item` ON `item`.`id` = `term`.`oid`
|
||||
INNER JOIN `item-content` ON `item-content`.`uri-id` = `item`.`uri-id`
|
||||
WHERE term.type IN (?, ?, ?, ?) AND `tid` >= ? ORDER BY `tid` LIMIT 100000",
|
||||
Tag::HASHTAG, Tag::MENTION, Tag::EXCLUSIVE_MENTION, Tag::IMPLICIT_MENTION, $id);
|
||||
|
||||
if (DBA::errorNo() != 0) {
|
||||
Logger::error('Database error', ['no' => DBA::errorNo(), 'message' => DBA::errorMessage()]);
|
||||
return false;
|
||||
}
|
||||
|
||||
while ($term = DBA::fetch($terms)) {
|
||||
if (($term['type'] == Tag::MENTION) && !empty($term['url']) && !strstr($term['body'], $term['url'])) {
|
||||
$condition = ['nurl' => Strings::normaliseLink($term['url']), 'uid' => 0, 'deleted' => false];
|
||||
$contact = DBA::selectFirst('contact', ['url', 'alias'], $condition, ['order' => ['id']]);
|
||||
if (!DBA::isResult($contact)) {
|
||||
$ssl_url = str_replace('http://', 'https://', $term['url']);
|
||||
$condition = ['`alias` IN (?, ?, ?) AND `uid` = ? AND NOT `deleted`', $term['url'], Strings::normaliseLink($term['url']), $ssl_url, 0];
|
||||
$contact = DBA::selectFirst('contact', ['url', 'alias'], $condition, ['order' => ['id']]);
|
||||
}
|
||||
|
||||
if (DBA::isResult($contact) && (!strstr($term['body'], $contact['url']) && (empty($contact['alias']) || !strstr($term['body'], $contact['alias'])))) {
|
||||
$term['type'] = Tag::IMPLICIT_MENTION;
|
||||
}
|
||||
}
|
||||
|
||||
Tag::store($term['uri-id'], $term['type'], $term['term'], $term['url'], false);
|
||||
|
||||
$id = $term['tid'];
|
||||
++$rows;
|
||||
if ($rows % 1000 == 0) {
|
||||
DI::config()->set('system', 'post_update_version_1342_id', $id);
|
||||
}
|
||||
}
|
||||
DBA::close($terms);
|
||||
|
||||
DI::config()->set('system', 'post_update_version_1342_id', $id);
|
||||
|
||||
Logger::info('Processed', ['rows' => $rows, 'last' => $id]);
|
||||
|
||||
// When there are less than 1,000 items processed this means that we reached the end
|
||||
// The other entries will then be processed with the regular functionality
|
||||
if ($rows < 1000) {
|
||||
DI::config()->set('system', 'post_update_version', 1342);
|
||||
Logger::info('Done');
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill the "post-delivery-data" table with data from the "item-delivery-data" table
|
||||
*
|
||||
* @return bool "true" when the job is done
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
private static function update1345()
|
||||
{
|
||||
// Was the script completed?
|
||||
if (DI::config()->get('system', 'post_update_version') >= 1345) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$id = DI::config()->get('system', 'post_update_version_1345_id', 0);
|
||||
|
||||
Logger::info('Start', ['item' => $id]);
|
||||
|
||||
$rows = 0;
|
||||
|
||||
$deliveries = DBA::p("SELECT `uri-id`, `iid`, `item-delivery-data`.`postopts`, `item-delivery-data`.`inform`,
|
||||
`queue_count`, `queue_done`, `activitypub`, `dfrn`, `diaspora`, `ostatus`, `legacy_dfrn`, `queue_failed`
|
||||
FROM `item-delivery-data`
|
||||
INNER JOIN `item` ON `item`.`id` = `item-delivery-data`.`iid`
|
||||
WHERE `iid` >= ? ORDER BY `iid` LIMIT 10000", $id);
|
||||
|
||||
if (DBA::errorNo() != 0) {
|
||||
Logger::error('Database error', ['no' => DBA::errorNo(), 'message' => DBA::errorMessage()]);
|
||||
return false;
|
||||
}
|
||||
|
||||
while ($delivery = DBA::fetch($deliveries)) {
|
||||
$id = $delivery['iid'];
|
||||
unset($delivery['iid']);
|
||||
DBA::insert('post-delivery-data', $delivery, true);
|
||||
++$rows;
|
||||
}
|
||||
DBA::close($deliveries);
|
||||
|
||||
DI::config()->set('system', 'post_update_version_1345_id', $id);
|
||||
|
||||
Logger::info('Processed', ['rows' => $rows, 'last' => $id]);
|
||||
|
||||
// When there are less than 100 items processed this means that we reached the end
|
||||
// The other entries will then be processed with the regular functionality
|
||||
if ($rows < 100) {
|
||||
DI::config()->set('system', 'post_update_version', 1345);
|
||||
Logger::info('Done');
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the legacy item.file field string from an item ID.
|
||||
* Includes only file and category terms.
|
||||
*
|
||||
* @param int $item_id
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
private static function fileTextFromItemId($item_id)
|
||||
{
|
||||
$file_text = '';
|
||||
|
||||
$condition = ['otype' => self::OBJECT_TYPE_POST, 'oid' => $item_id, 'type' => [Category::FILE, Category::CATEGORY]];
|
||||
$tags = DBA::selectToArray('term', ['type', 'term', 'url'], $condition);
|
||||
foreach ($tags as $tag) {
|
||||
if ($tag['type'] == Category::CATEGORY) {
|
||||
$file_text .= '<' . $tag['term'] . '>';
|
||||
} else {
|
||||
$file_text .= '[' . $tag['term'] . ']';
|
||||
}
|
||||
}
|
||||
|
||||
return $file_text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill the "tag" table with tags and mentions from the "term" table
|
||||
*
|
||||
* @return bool "true" when the job is done
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
private static function update1346()
|
||||
{
|
||||
// Was the script completed?
|
||||
if (DI::config()->get('system', 'post_update_version') >= 1346) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$id = DI::config()->get('system', 'post_update_version_1346_id', 0);
|
||||
|
||||
Logger::info('Start', ['item' => $id]);
|
||||
|
||||
$rows = 0;
|
||||
|
||||
$terms = DBA::select('term', ['oid'],
|
||||
["`type` IN (?, ?) AND `oid` >= ?", Category::CATEGORY, Category::FILE, $id],
|
||||
['order' => ['oid'], 'limit' => 1000, 'group_by' => ['oid']]);
|
||||
|
||||
if (DBA::errorNo() != 0) {
|
||||
Logger::error('Database error', ['no' => DBA::errorNo(), 'message' => DBA::errorMessage()]);
|
||||
return false;
|
||||
}
|
||||
|
||||
while ($term = DBA::fetch($terms)) {
|
||||
$item = Item::selectFirst(['uri-id', 'uid'], ['id' => $term['oid']]);
|
||||
if (!DBA::isResult($item)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$file = self::fileTextFromItemId($term['oid']);
|
||||
if (!empty($file)) {
|
||||
Category::storeTextByURIId($item['uri-id'], $item['uid'], $file);
|
||||
}
|
||||
|
||||
$id = $term['oid'];
|
||||
++$rows;
|
||||
if ($rows % 100 == 0) {
|
||||
DI::config()->set('system', 'post_update_version_1346_id', $id);
|
||||
}
|
||||
}
|
||||
DBA::close($terms);
|
||||
|
||||
DI::config()->set('system', 'post_update_version_1346_id', $id);
|
||||
|
||||
Logger::info('Processed', ['rows' => $rows, 'last' => $id]);
|
||||
|
||||
// When there are less than 10 items processed this means that we reached the end
|
||||
// The other entries will then be processed with the regular functionality
|
||||
if ($rows < 10) {
|
||||
DI::config()->set('system', 'post_update_version', 1346);
|
||||
Logger::info('Done');
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* update the "vid" (verb) field in the item table
|
||||
*
|
||||
* @return bool "true" when the job is done
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
private static function update1347()
|
||||
{
|
||||
// Was the script completed?
|
||||
if (DI::config()->get("system", "post_update_version") >= 1347) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$id = DI::config()->get("system", "post_update_version_1347_id", 0);
|
||||
|
||||
Logger::info('Start', ['item' => $id]);
|
||||
|
||||
$start_id = $id;
|
||||
$rows = 0;
|
||||
|
||||
$items = DBA::p("SELECT `item`.`id`, `item`.`verb` AS `item-verb`, `item-content`.`verb`, `item-activity`.`activity`
|
||||
FROM `item` LEFT JOIN `item-content` ON `item-content`.`uri-id` = `item`.`uri-id`
|
||||
LEFT JOIN `item-activity` ON `item-activity`.`uri-id` = `item`.`uri-id` AND `item`.`gravity` = ?
|
||||
WHERE `item`.`id` >= ? AND `item`.`vid` IS NULL ORDER BY `item`.`id` LIMIT 10000", GRAVITY_ACTIVITY, $id);
|
||||
|
||||
if (DBA::errorNo() != 0) {
|
||||
Logger::error('Database error', ['no' => DBA::errorNo(), 'message' => DBA::errorMessage()]);
|
||||
return false;
|
||||
}
|
||||
|
||||
while ($item = DBA::fetch($items)) {
|
||||
$id = $item['id'];
|
||||
$verb = $item['item-verb'];
|
||||
if (empty($verb)) {
|
||||
$verb = $item['verb'];
|
||||
}
|
||||
if (empty($verb) && is_int($item['activity'])) {
|
||||
$verb = Item::ACTIVITIES[$item['activity']];
|
||||
}
|
||||
if (empty($verb)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
DBA::update('item', ['vid' => Verb::getID($verb)], ['id' => $item['id']]);
|
||||
++$rows;
|
||||
}
|
||||
DBA::close($items);
|
||||
|
||||
DI::config()->set("system", "post_update_version_1347_id", $id);
|
||||
|
||||
Logger::info('Processed', ['rows' => $rows, 'last' => $id]);
|
||||
|
||||
if ($start_id == $id) {
|
||||
DI::config()->set("system", "post_update_version", 1347);
|
||||
Logger::info('Done');
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* update the "gsid" (global server id) field in the contact table
|
||||
*
|
||||
* @return bool "true" when the job is done
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
private static function update1348()
|
||||
{
|
||||
// Was the script completed?
|
||||
if (DI::config()->get("system", "post_update_version") >= 1348) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$id = DI::config()->get("system", "post_update_version_1348_id", 0);
|
||||
|
||||
Logger::info('Start', ['contact' => $id]);
|
||||
|
||||
$start_id = $id;
|
||||
$rows = 0;
|
||||
$condition = ["`id` > ? AND `gsid` IS NULL AND `baseurl` != '' AND NOT `baseurl` IS NULL", $id];
|
||||
$params = ['order' => ['id'], 'limit' => 10000];
|
||||
$contacts = DBA::select('contact', ['id', 'baseurl'], $condition, $params);
|
||||
|
||||
if (DBA::errorNo() != 0) {
|
||||
Logger::error('Database error', ['no' => DBA::errorNo(), 'message' => DBA::errorMessage()]);
|
||||
return false;
|
||||
}
|
||||
|
||||
while ($contact = DBA::fetch($contacts)) {
|
||||
$id = $contact['id'];
|
||||
|
||||
DBA::update('contact',
|
||||
['gsid' => GServer::getID($contact['baseurl'], true), 'baseurl' => GServer::cleanURL($contact['baseurl'])],
|
||||
['id' => $contact['id']]);
|
||||
|
||||
++$rows;
|
||||
}
|
||||
DBA::close($contacts);
|
||||
|
||||
DI::config()->set("system", "post_update_version_1348_id", $id);
|
||||
|
||||
Logger::info('Processed', ['rows' => $rows, 'last' => $id]);
|
||||
|
||||
if ($start_id == $id) {
|
||||
DI::config()->set("system", "post_update_version", 1348);
|
||||
Logger::info('Done');
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* update the "gsid" (global server id) field in the apcontact table
|
||||
*
|
||||
* @return bool "true" when the job is done
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
private static function update1349()
|
||||
{
|
||||
// Was the script completed?
|
||||
if (DI::config()->get("system", "post_update_version") >= 1349) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$id = DI::config()->get("system", "post_update_version_1349_id", '');
|
||||
|
||||
Logger::info('Start', ['apcontact' => $id]);
|
||||
|
||||
$start_id = $id;
|
||||
$rows = 0;
|
||||
$condition = ["`url` > ? AND `gsid` IS NULL AND `baseurl` != '' AND NOT `baseurl` IS NULL", $id];
|
||||
$params = ['order' => ['url'], 'limit' => 10000];
|
||||
$apcontacts = DBA::select('apcontact', ['url', 'baseurl'], $condition, $params);
|
||||
|
||||
if (DBA::errorNo() != 0) {
|
||||
Logger::error('Database error', ['no' => DBA::errorNo(), 'message' => DBA::errorMessage()]);
|
||||
return false;
|
||||
}
|
||||
|
||||
while ($apcontact = DBA::fetch($apcontacts)) {
|
||||
$id = $apcontact['url'];
|
||||
|
||||
DBA::update('apcontact',
|
||||
['gsid' => GServer::getID($apcontact['baseurl'], true), 'baseurl' => GServer::cleanURL($apcontact['baseurl'])],
|
||||
['url' => $apcontact['url']]);
|
||||
|
||||
++$rows;
|
||||
}
|
||||
DBA::close($apcontacts);
|
||||
|
||||
DI::config()->set("system", "post_update_version_1349_id", $id);
|
||||
|
||||
Logger::info('Processed', ['rows' => $rows, 'last' => $id]);
|
||||
|
||||
if ($start_id == $id) {
|
||||
DI::config()->set("system", "post_update_version", 1349);
|
||||
Logger::info('Done');
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* update the "gsid" (global server id) field in the gcontact table
|
||||
*
|
||||
* @return bool "true" when the job is done
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
private static function update1350()
|
||||
{
|
||||
// Was the script completed?
|
||||
if (DI::config()->get("system", "post_update_version") >= 1350) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$id = DI::config()->get("system", "post_update_version_1350_id", 0);
|
||||
|
||||
Logger::info('Start', ['gcontact' => $id]);
|
||||
|
||||
$start_id = $id;
|
||||
$rows = 0;
|
||||
$condition = ["`id` > ? AND `gsid` IS NULL AND `server_url` != '' AND NOT `server_url` IS NULL", $id];
|
||||
$params = ['order' => ['id'], 'limit' => 10000];
|
||||
$gcontacts = DBA::select('gcontact', ['id', 'server_url'], $condition, $params);
|
||||
|
||||
if (DBA::errorNo() != 0) {
|
||||
Logger::error('Database error', ['no' => DBA::errorNo(), 'message' => DBA::errorMessage()]);
|
||||
return false;
|
||||
}
|
||||
|
||||
while ($gcontact = DBA::fetch($gcontacts)) {
|
||||
$id = $gcontact['id'];
|
||||
|
||||
DBA::update('gcontact',
|
||||
['gsid' => GServer::getID($gcontact['server_url'], true), 'server_url' => GServer::cleanURL($gcontact['server_url'])],
|
||||
['id' => $gcontact['id']]);
|
||||
|
||||
++$rows;
|
||||
}
|
||||
DBA::close($gcontacts);
|
||||
|
||||
DI::config()->set("system", "post_update_version_1350_id", $id);
|
||||
|
||||
Logger::info('Processed', ['rows' => $rows, 'last' => $id]);
|
||||
|
||||
if ($start_id == $id) {
|
||||
DI::config()->set("system", "post_update_version", 1350);
|
||||
Logger::info('Done');
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
137
src/Database/View.php
Normal file
137
src/Database/View.php
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
<?php
|
||||
/**
|
||||
* @copyright Copyright (C) 2020, Friendica
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Friendica\Database;
|
||||
|
||||
use Exception;
|
||||
use Friendica\Core\Hook;
|
||||
use Friendica\DI;
|
||||
|
||||
class View
|
||||
{
|
||||
/**
|
||||
* view definition loaded from static/dbview.config.php
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private static $definition = [];
|
||||
|
||||
/**
|
||||
* Loads the database structure definition from the static/dbview.config.php file.
|
||||
* On first pass, defines DB_UPDATE_VERSION constant.
|
||||
*
|
||||
* @see static/dbview.config.php
|
||||
* @param boolean $with_addons_structure Whether to tack on addons additional tables
|
||||
* @param string $basePath The base path of this application
|
||||
* @return array
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function definition($basePath = '', $with_addons_structure = true)
|
||||
{
|
||||
if (!self::$definition) {
|
||||
if (empty($basePath)) {
|
||||
$basePath = DI::app()->getBasePath();
|
||||
}
|
||||
|
||||
$filename = $basePath . '/static/dbview.config.php';
|
||||
|
||||
if (!is_readable($filename)) {
|
||||
throw new Exception('Missing database view config file static/dbview.config.php');
|
||||
}
|
||||
|
||||
$definition = require $filename;
|
||||
|
||||
if (!$definition) {
|
||||
throw new Exception('Corrupted database view config file static/dbview.config.php');
|
||||
}
|
||||
|
||||
self::$definition = $definition;
|
||||
} else {
|
||||
$definition = self::$definition;
|
||||
}
|
||||
|
||||
if ($with_addons_structure) {
|
||||
Hook::callAll('dbview_definition', $definition);
|
||||
}
|
||||
|
||||
return $definition;
|
||||
}
|
||||
|
||||
public static function create(bool $verbose, bool $action)
|
||||
{
|
||||
$definition = self::definition();
|
||||
|
||||
foreach ($definition as $name => $structure) {
|
||||
self::createview($name, $structure, $verbose, $action);
|
||||
}
|
||||
}
|
||||
|
||||
public static function printStructure($basePath)
|
||||
{
|
||||
$database = self::definition($basePath, false);
|
||||
|
||||
foreach ($database AS $name => $structure) {
|
||||
echo "--\n";
|
||||
echo "-- VIEW $name\n";
|
||||
echo "--\n";
|
||||
self::createView($name, $structure, true, false);
|
||||
|
||||
echo "\n";
|
||||
}
|
||||
}
|
||||
|
||||
private static function createview($name, $structure, $verbose, $action)
|
||||
{
|
||||
$r = true;
|
||||
|
||||
$sql_rows = [];
|
||||
foreach ($structure["fields"] AS $fieldname => $origin) {
|
||||
if (is_string($origin)) {
|
||||
$sql_rows[] = $origin . " AS `" . DBA::escape($fieldname) . "`";
|
||||
} elseif (is_array($origin) && (sizeof($origin) == 2)) {
|
||||
$sql_rows[] = "`" . DBA::escape($origin[0]) . "`.`" . DBA::escape($origin[1]) . "` AS `" . DBA::escape($fieldname) . "`";
|
||||
}
|
||||
}
|
||||
|
||||
$sql = sprintf("DROP VIEW IF EXISTS `%s`", DBA::escape($name));
|
||||
|
||||
if ($verbose) {
|
||||
echo $sql . ";\n";
|
||||
}
|
||||
|
||||
if ($action) {
|
||||
DBA::e($sql);
|
||||
}
|
||||
|
||||
$sql = sprintf("CREATE VIEW `%s` AS SELECT \n\t", DBA::escape($name)) .
|
||||
implode(",\n\t", $sql_rows) . "\n\t" . $structure['query'];
|
||||
|
||||
if ($verbose) {
|
||||
echo $sql . ";\n";
|
||||
}
|
||||
|
||||
if ($action) {
|
||||
$r = DBA::e($sql);
|
||||
}
|
||||
|
||||
return $r;
|
||||
}
|
||||
}
|
||||
|
|
@ -37,7 +37,7 @@ class Field extends BaseFactory
|
|||
*/
|
||||
public function createFromProfileField(ProfileField $profileField)
|
||||
{
|
||||
return new \Friendica\Api\Entity\Mastodon\Field($profileField->label, BBCode::convert($profileField->value, false, 9));
|
||||
return new \Friendica\Api\Entity\Mastodon\Field($profileField->label, BBCode::convert($profileField->value, false, BBCode::ACTIVITYPUB));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
55
src/Factory/Api/Twitter/User.php
Normal file
55
src/Factory/Api/Twitter/User.php
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
/**
|
||||
* @copyright Copyright (C) 2020, Friendica
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Friendica\Factory\Api\Twitter;
|
||||
|
||||
use Friendica\BaseFactory;
|
||||
use Friendica\Model\APContact;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Network\HTTPException;
|
||||
|
||||
class User extends BaseFactory
|
||||
{
|
||||
/**
|
||||
* @param int $contactId
|
||||
* @param int $uid Public contact (=0) or owner user id
|
||||
* @param bool $skip_status
|
||||
* @param bool $include_user_entities
|
||||
* @return \Friendica\Object\Api\Twitter\User
|
||||
* @throws HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
public function createFromContactId(int $contactId, $uid = 0, $skip_status = false, $include_user_entities = true)
|
||||
{
|
||||
$cdata = Contact::getPublicAndUserContacID($contactId, $uid);
|
||||
if (!empty($cdata)) {
|
||||
$publicContact = Contact::getById($cdata['public']);
|
||||
$userContact = Contact::getById($cdata['user']);
|
||||
} else {
|
||||
$publicContact = Contact::getById($contactId);
|
||||
$userContact = [];
|
||||
}
|
||||
|
||||
$apcontact = APContact::getByURL($publicContact['url'], false);
|
||||
|
||||
return new \Friendica\Object\Api\Twitter\User($publicContact, $apcontact, $userContact, $skip_status, $include_user_entities);
|
||||
}
|
||||
}
|
||||
|
|
@ -95,11 +95,11 @@ class Notification extends BaseFactory
|
|||
$item['author-avatar'] = $item['contact-avatar'];
|
||||
}
|
||||
|
||||
$item['label'] = (($item['id'] == $item['parent']) ? 'post' : 'comment');
|
||||
$item['label'] = (($item['gravity'] == GRAVITY_PARENT) ? 'post' : 'comment');
|
||||
$item['link'] = $this->baseUrl->get(true) . '/display/' . $item['parent-guid'];
|
||||
$item['image'] = Proxy::proxifyUrl($item['author-avatar'], false, Proxy::SIZE_MICRO);
|
||||
$item['url'] = $item['author-link'];
|
||||
$item['text'] = (($item['id'] == $item['parent'])
|
||||
$item['text'] = (($item['gravity'] == GRAVITY_PARENT)
|
||||
? $this->l10n->t("%s created a new post", $item['author-name'])
|
||||
: $this->l10n->t("%s commented on %s's post", $item['author-name'], $item['parent-author-name']));
|
||||
$item['when'] = DateTimeFormat::local($item['created'], 'r');
|
||||
|
|
@ -272,7 +272,7 @@ class Notification extends BaseFactory
|
|||
}
|
||||
|
||||
$fields = ['id', 'parent', 'verb', 'author-name', 'unseen', 'author-link', 'author-avatar', 'contact-avatar',
|
||||
'network', 'created', 'object', 'parent-author-name', 'parent-author-link', 'parent-guid'];
|
||||
'network', 'created', 'object', 'parent-author-name', 'parent-author-link', 'parent-guid', 'gravity'];
|
||||
$params = ['order' => ['received' => true], 'limit' => [$start, $limit]];
|
||||
|
||||
$formattedNotifications = [];
|
||||
|
|
@ -313,7 +313,7 @@ class Notification extends BaseFactory
|
|||
}
|
||||
|
||||
$fields = ['id', 'parent', 'verb', 'author-name', 'unseen', 'author-link', 'author-avatar', 'contact-avatar',
|
||||
'network', 'created', 'object', 'parent-author-name', 'parent-author-link', 'parent-guid'];
|
||||
'network', 'created', 'object', 'parent-author-name', 'parent-author-link', 'parent-guid', 'gravity'];
|
||||
$params = ['order' => ['received' => true], 'limit' => [$start, $limit]];
|
||||
|
||||
$formattedNotifications = [];
|
||||
|
|
@ -350,7 +350,7 @@ class Notification extends BaseFactory
|
|||
}
|
||||
|
||||
$fields = ['id', 'parent', 'verb', 'author-name', 'unseen', 'author-link', 'author-avatar', 'contact-avatar',
|
||||
'network', 'created', 'object', 'parent-author-name', 'parent-author-link', 'parent-guid'];
|
||||
'network', 'created', 'object', 'parent-author-name', 'parent-author-link', 'parent-guid', 'gravity'];
|
||||
$params = ['order' => ['received' => true], 'limit' => [$start, $limit]];
|
||||
|
||||
$formattedNotifications = [];
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@ use Friendica\Content\Text\HTML;
|
|||
use Friendica\Core\Logger;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\DI;
|
||||
use Friendica\Network\Probe;
|
||||
use Friendica\Protocol\ActivityNamespace;
|
||||
use Friendica\Protocol\ActivityPub;
|
||||
use Friendica\Util\Crypto;
|
||||
use Friendica\Util\Network;
|
||||
|
|
@ -35,56 +37,55 @@ use Friendica\Util\Strings;
|
|||
class APContact
|
||||
{
|
||||
/**
|
||||
* Resolves the profile url from the address by using webfinger
|
||||
* Fetch webfinger data
|
||||
*
|
||||
* @param string $addr profile address (user@domain.tld)
|
||||
* @param string $url profile URL. When set then we return "true" when this profile url can be found at the address
|
||||
* @return string|boolean url
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
* @param string $addr Address
|
||||
* @return array webfinger data
|
||||
*/
|
||||
private static function addrToUrl($addr, $url = null)
|
||||
public static function fetchWebfingerData(string $addr)
|
||||
{
|
||||
$addr_parts = explode('@', $addr);
|
||||
if (count($addr_parts) != 2) {
|
||||
return false;
|
||||
return [];
|
||||
}
|
||||
|
||||
$xrd_timeout = DI::config()->get('system', 'xrd_timeout');
|
||||
|
||||
$webfinger = 'https://' . $addr_parts[1] . '/.well-known/webfinger?resource=acct:' . urlencode($addr);
|
||||
|
||||
$curlResult = Network::curl($webfinger, false, ['timeout' => $xrd_timeout, 'accept_content' => 'application/jrd+json,application/json']);
|
||||
if (!$curlResult->isSuccess() || empty($curlResult->getBody())) {
|
||||
$webfinger = Strings::normaliseLink($webfinger);
|
||||
|
||||
$curlResult = Network::curl($webfinger, false, ['timeout' => $xrd_timeout, 'accept_content' => 'application/jrd+json,application/json']);
|
||||
|
||||
if (!$curlResult->isSuccess() || empty($curlResult->getBody())) {
|
||||
return false;
|
||||
$data = ['addr' => $addr];
|
||||
$template = 'https://' . $addr_parts[1] . '/.well-known/webfinger?resource=acct:' . urlencode($addr);
|
||||
$webfinger = Probe::webfinger(str_replace('{uri}', urlencode($addr), $template), 'application/jrd+json');
|
||||
if (empty($webfinger['links'])) {
|
||||
$template = 'http://' . $addr_parts[1] . '/.well-known/webfinger?resource=acct:' . urlencode($addr);
|
||||
$webfinger = Probe::webfinger(str_replace('{uri}', urlencode($addr), $template), 'application/jrd+json');
|
||||
if (empty($webfinger['links'])) {
|
||||
return [];
|
||||
}
|
||||
$data['baseurl'] = 'http://' . $addr_parts[1];
|
||||
} else {
|
||||
$data['baseurl'] = 'https://' . $addr_parts[1];
|
||||
}
|
||||
|
||||
$data = json_decode($curlResult->getBody(), true);
|
||||
|
||||
if (empty($data['links'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($data['links'] as $link) {
|
||||
if (!empty($url) && !empty($link['href']) && ($link['href'] == $url)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (empty($link['href']) || empty($link['rel']) || empty($link['type'])) {
|
||||
foreach ($webfinger['links'] as $link) {
|
||||
if (empty($link['rel'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (empty($url) && ($link['rel'] == 'self') && ($link['type'] == 'application/activity+json')) {
|
||||
return $link['href'];
|
||||
if (!empty($link['template']) && ($link['rel'] == ActivityNamespace::OSTATUSSUB)) {
|
||||
$data['subscribe'] = $link['template'];
|
||||
}
|
||||
|
||||
if (!empty($link['href']) && !empty($link['type']) && ($link['rel'] == 'self') && ($link['type'] == 'application/activity+json')) {
|
||||
$data['url'] = $link['href'];
|
||||
}
|
||||
|
||||
if (!empty($link['href']) && !empty($link['type']) && ($link['rel'] == 'http://webfinger.net/rel/profile-page') && ($link['type'] == 'text/html')) {
|
||||
$data['alias'] = $link['href'];
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
if (!empty($data['url']) && !empty($data['alias']) && ($data['url'] == $data['alias'])) {
|
||||
unset($data['alias']);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -133,11 +134,15 @@ class APContact
|
|||
}
|
||||
}
|
||||
|
||||
if (empty(parse_url($url, PHP_URL_SCHEME))) {
|
||||
$url = self::addrToUrl($url);
|
||||
if (empty($url)) {
|
||||
$apcontact = [];
|
||||
|
||||
$webfinger = empty(parse_url($url, PHP_URL_SCHEME));
|
||||
if ($webfinger) {
|
||||
$apcontact = self::fetchWebfingerData($url);
|
||||
if (empty($apcontact['url'])) {
|
||||
return $fetched_contact;
|
||||
}
|
||||
$url = $apcontact['url'];
|
||||
}
|
||||
|
||||
$data = ActivityPub::fetchContent($url);
|
||||
|
|
@ -151,7 +156,6 @@ class APContact
|
|||
return $fetched_contact;
|
||||
}
|
||||
|
||||
$apcontact = [];
|
||||
$apcontact['url'] = $compacted['@id'];
|
||||
$apcontact['uuid'] = JsonLD::fetchElement($compacted, 'diaspora:guid', '@value');
|
||||
$apcontact['type'] = str_replace('as:', '', JsonLD::fetchElement($compacted, '@type'));
|
||||
|
|
@ -182,9 +186,11 @@ class APContact
|
|||
$apcontact['photo'] = JsonLD::fetchElement($compacted['as:icon'], 'as:url', '@id');
|
||||
}
|
||||
|
||||
$apcontact['alias'] = JsonLD::fetchElement($compacted, 'as:url', '@id');
|
||||
if (is_array($apcontact['alias'])) {
|
||||
$apcontact['alias'] = JsonLD::fetchElement($compacted['as:url'], 'as:href', '@id');
|
||||
if (empty($apcontact['alias'])) {
|
||||
$apcontact['alias'] = JsonLD::fetchElement($compacted, 'as:url', '@id');
|
||||
if (is_array($apcontact['alias'])) {
|
||||
$apcontact['alias'] = JsonLD::fetchElement($compacted['as:url'], 'as:href', '@id');
|
||||
}
|
||||
}
|
||||
|
||||
// Quit if none of the basic values are set
|
||||
|
|
@ -201,10 +207,12 @@ class APContact
|
|||
unset($parts['scheme']);
|
||||
unset($parts['path']);
|
||||
|
||||
if (!empty($apcontact['nick'])) {
|
||||
$apcontact['addr'] = $apcontact['nick'] . '@' . str_replace('//', '', Network::unparseURL($parts));
|
||||
} else {
|
||||
$apcontact['addr'] = '';
|
||||
if (empty($apcontact['addr'])) {
|
||||
if (!empty($apcontact['nick'])) {
|
||||
$apcontact['addr'] = $apcontact['nick'] . '@' . str_replace('//', '', Network::unparseURL($parts));
|
||||
} else {
|
||||
$apcontact['addr'] = '';
|
||||
}
|
||||
}
|
||||
|
||||
$apcontact['pubkey'] = null;
|
||||
|
|
@ -276,21 +284,38 @@ class APContact
|
|||
}
|
||||
}
|
||||
|
||||
$parts = parse_url($apcontact['url']);
|
||||
unset($parts['path']);
|
||||
$baseurl = Network::unparseURL($parts);
|
||||
if (!$webfinger && !empty($apcontact['addr'])) {
|
||||
$data = self::fetchWebfingerData($apcontact['addr']);
|
||||
if (!empty($data)) {
|
||||
$apcontact['baseurl'] = $data['baseurl'];
|
||||
|
||||
// Check if the address is resolvable or the profile url is identical with the base url of the system
|
||||
if (self::addrToUrl($apcontact['addr'], $apcontact['url']) || Strings::compareLink($apcontact['url'], $baseurl)) {
|
||||
$apcontact['baseurl'] = $baseurl;
|
||||
} else {
|
||||
$apcontact['addr'] = null;
|
||||
if (empty($apcontact['alias']) && !empty($data['alias'])) {
|
||||
$apcontact['alias'] = $data['alias'];
|
||||
}
|
||||
if (!empty($data['subscribe'])) {
|
||||
$apcontact['subscribe'] = $data['subscribe'];
|
||||
}
|
||||
} else {
|
||||
$apcontact['addr'] = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($apcontact['baseurl'])) {
|
||||
$apcontact['baseurl'] = null;
|
||||
}
|
||||
|
||||
if (empty($apcontact['subscribe'])) {
|
||||
$apcontact['subscribe'] = null;
|
||||
}
|
||||
|
||||
if (!empty($apcontact['baseurl']) && empty($fetched_contact['gsid'])) {
|
||||
$apcontact['gsid'] = GServer::getID($apcontact['baseurl']);
|
||||
} elseif (!empty($fetched_contact['gsid'])) {
|
||||
$apcontact['gsid'] = $fetched_contact['gsid'];
|
||||
} else {
|
||||
$apcontact['gsid'] = null;
|
||||
}
|
||||
|
||||
if ($apcontact['url'] == $apcontact['alias']) {
|
||||
$apcontact['alias'] = null;
|
||||
}
|
||||
|
|
@ -304,7 +329,7 @@ class APContact
|
|||
DBA::delete('apcontact', ['url' => $url]);
|
||||
}
|
||||
|
||||
Logger::log('Updated profile for ' . $url, Logger::DEBUG);
|
||||
Logger::info('Updated profile', ['url' => $url]);
|
||||
|
||||
return $apcontact;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -159,7 +159,7 @@ class Attach
|
|||
*/
|
||||
public static function getData($item)
|
||||
{
|
||||
$backendClass = DI::storageManager()->getByName($photo['backend-class'] ?? '');
|
||||
$backendClass = DI::storageManager()->getByName($item['backend-class'] ?? '');
|
||||
if ($backendClass === null) {
|
||||
// legacy data storage in 'data' column
|
||||
$i = self::selectFirst(['data'], ['id' => $item['id']]);
|
||||
|
|
|
|||
|
|
@ -190,6 +190,44 @@ class Contact
|
|||
return DBA::selectFirst('contact', $fields, ['id' => $id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches a contact by a given url
|
||||
*
|
||||
* @param string $url profile url
|
||||
* @param integer $uid User ID of the contact
|
||||
* @param array $fields Field list
|
||||
* @param boolean $update true = always update, false = never update, null = update when not found or outdated
|
||||
* @return array contact array
|
||||
*/
|
||||
public static function getByURL(string $url, int $uid = 0, array $fields = [], $update = null)
|
||||
{
|
||||
if ($update || is_null($update)) {
|
||||
$cid = self::getIdForURL($url, $uid, !($update ?? false));
|
||||
if (empty($cid)) {
|
||||
return [];
|
||||
}
|
||||
return self::getById($cid, $fields);
|
||||
}
|
||||
|
||||
// We first try the nurl (http://server.tld/nick), most common case
|
||||
$options = ['order' => ['id']];
|
||||
$contact = DBA::selectFirst('contact', $fields, ['nurl' => Strings::normaliseLink($url), 'uid' => $uid, 'deleted' => false], $options);
|
||||
|
||||
// Then the addr (nick@server.tld)
|
||||
if (!DBA::isResult($contact)) {
|
||||
$contact = DBA::selectFirst('contact', $fields, ['addr' => str_replace('acct:', '', $url), 'uid' => $uid, 'deleted' => false], $options);
|
||||
}
|
||||
|
||||
// Then the alias (which could be anything)
|
||||
if (!DBA::isResult($contact)) {
|
||||
// The link could be provided as http although we stored it as https
|
||||
$ssl_url = str_replace('http://', 'https://', $url);
|
||||
$condition = ['`alias` IN (?, ?, ?) AND `uid` = ? AND NOT `deleted`', $url, Strings::normaliseLink($url), $ssl_url, $uid];
|
||||
$contact = DBA::selectFirst('contact', $fields, $condition, $options);
|
||||
}
|
||||
return $contact;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests if the given contact is a follower
|
||||
*
|
||||
|
|
@ -843,11 +881,12 @@ class Contact
|
|||
// create an unfollow slap
|
||||
$item = [];
|
||||
$item['verb'] = Activity::O_UNFOLLOW;
|
||||
$item['gravity'] = GRAVITY_ACTIVITY;
|
||||
$item['follow'] = $contact["url"];
|
||||
$item['body'] = '';
|
||||
$item['title'] = '';
|
||||
$item['guid'] = '';
|
||||
$item['tag'] = '';
|
||||
$item['uri-id'] = 0;
|
||||
$item['attach'] = '';
|
||||
$slap = OStatus::salmon($item, $user);
|
||||
|
||||
|
|
@ -887,10 +926,10 @@ class Contact
|
|||
return;
|
||||
}
|
||||
} elseif (!isset($contact['url'])) {
|
||||
Logger::log('Empty contact: ' . json_encode($contact) . ' - ' . System::callstack(20), Logger::DEBUG);
|
||||
Logger::info('Empty contact', ['contact' => $contact, 'callstack' => System::callstack(20)]);
|
||||
}
|
||||
|
||||
Logger::log('Contact '.$contact['id'].' is marked for archival', Logger::DEBUG);
|
||||
Logger::info('Contact is marked for archival', ['id' => $contact['id']]);
|
||||
|
||||
// Contact already archived or "self" contact? => nothing to do
|
||||
if ($contact['archive'] || $contact['self']) {
|
||||
|
|
@ -949,7 +988,7 @@ class Contact
|
|||
return;
|
||||
}
|
||||
|
||||
Logger::log('Contact '.$contact['id'].' is marked as vital again', Logger::DEBUG);
|
||||
Logger::info('Contact is marked as vital again', ['id' => $contact['id']]);
|
||||
|
||||
if (!isset($contact['url']) && !empty($contact['id'])) {
|
||||
$fields = ['id', 'url', 'batch'];
|
||||
|
|
@ -1038,7 +1077,6 @@ class Contact
|
|||
}
|
||||
|
||||
if (DBA::isResult($r)) {
|
||||
$authoritativeResult = true;
|
||||
// If there is more than one entry we filter out the connector networks
|
||||
if (count($r) > 1) {
|
||||
foreach ($r as $id => $result) {
|
||||
|
|
@ -1049,7 +1087,10 @@ class Contact
|
|||
}
|
||||
|
||||
$profile = array_shift($r);
|
||||
}
|
||||
|
||||
if (!empty($profile)) {
|
||||
$authoritativeResult = true;
|
||||
// "bd" always contains the upcoming birthday of a contact.
|
||||
// "birthday" might contain the birthday including the year of birth.
|
||||
if ($profile["birthday"] > DBA::NULL_DATE) {
|
||||
|
|
@ -1168,7 +1209,7 @@ class Contact
|
|||
if (!DBA::isResult($r)) {
|
||||
$data = Probe::uri($addr);
|
||||
|
||||
$profile = self::getDetailsByURL($data['url'], $uid);
|
||||
$profile = self::getDetailsByURL($data['url'], $uid, $data);
|
||||
} else {
|
||||
$profile = $r[0];
|
||||
}
|
||||
|
|
@ -1235,7 +1276,7 @@ class Contact
|
|||
}
|
||||
|
||||
if (($contact['network'] == Protocol::DFRN) && !$contact['self'] && empty($contact['pending'])) {
|
||||
$poke_link = DI::baseUrl() . '/poke/?c=' . $contact['id'];
|
||||
$poke_link = 'contact/' . $contact['id'] . '/poke';
|
||||
}
|
||||
|
||||
$contact_url = DI::baseUrl() . '/contact/' . $contact['id'];
|
||||
|
|
@ -1450,7 +1491,7 @@ class Contact
|
|||
*/
|
||||
public static function getIdForURL($url, $uid = 0, $no_update = false, $default = [], $in_loop = false)
|
||||
{
|
||||
Logger::log("Get contact data for url " . $url . " and user " . $uid . " - " . System::callstack(), Logger::DEBUG);
|
||||
Logger::info('Get contact data', ['url' => $url, 'user' => $uid]);
|
||||
|
||||
$contact_id = 0;
|
||||
|
||||
|
|
@ -1458,26 +1499,9 @@ class Contact
|
|||
return 0;
|
||||
}
|
||||
|
||||
/// @todo Verify if we can't use Contact::getDetailsByUrl instead of the following
|
||||
// We first try the nurl (http://server.tld/nick), most common case
|
||||
$fields = ['id', 'avatar', 'updated', 'network'];
|
||||
$options = ['order' => ['id']];
|
||||
$contact = DBA::selectFirst('contact', $fields, ['nurl' => Strings::normaliseLink($url), 'uid' => $uid, 'deleted' => false], $options);
|
||||
$contact = self::getByURL($url, $uid, ['id', 'avatar', 'updated', 'network'], false);
|
||||
|
||||
// Then the addr (nick@server.tld)
|
||||
if (!DBA::isResult($contact)) {
|
||||
$contact = DBA::selectFirst('contact', $fields, ['addr' => str_replace('acct:', '', $url), 'uid' => $uid, 'deleted' => false], $options);
|
||||
}
|
||||
|
||||
// Then the alias (which could be anything)
|
||||
if (!DBA::isResult($contact)) {
|
||||
// The link could be provided as http although we stored it as https
|
||||
$ssl_url = str_replace('http://', 'https://', $url);
|
||||
$condition = ['`alias` IN (?, ?, ?) AND `uid` = ? AND NOT `deleted`', $url, Strings::normaliseLink($url), $ssl_url, $uid];
|
||||
$contact = DBA::selectFirst('contact', $fields, $condition, $options);
|
||||
}
|
||||
|
||||
if (DBA::isResult($contact)) {
|
||||
if (!empty($contact)) {
|
||||
$contact_id = $contact["id"];
|
||||
$update_contact = false;
|
||||
|
||||
|
|
@ -1530,10 +1554,6 @@ class Contact
|
|||
|
||||
if (empty($data)) {
|
||||
$data = Probe::uri($url, "", $uid);
|
||||
// Ensure that there is a gserver entry
|
||||
if (!empty($data['baseurl']) && ($data['network'] != Protocol::PHANTOM)) {
|
||||
GServer::check($data['baseurl']);
|
||||
}
|
||||
}
|
||||
|
||||
// Take the default values when probing failed
|
||||
|
|
@ -1546,7 +1566,15 @@ class Contact
|
|||
return 0;
|
||||
}
|
||||
|
||||
if (!$contact_id && !empty($data['alias']) && ($data['alias'] != $url) && !$in_loop) {
|
||||
if (!empty($data['baseurl'])) {
|
||||
$data['baseurl'] = GServer::cleanURL($data['baseurl']);
|
||||
}
|
||||
|
||||
if (!empty($data['baseurl']) && empty($data['gsid'])) {
|
||||
$data['gsid'] = GServer::getID($data['baseurl']);
|
||||
}
|
||||
|
||||
if (!$contact_id && !empty($data['alias']) && ($data['alias'] != $data['url']) && !$in_loop) {
|
||||
$contact_id = self::getIdForURL($data["alias"], $uid, true, $default, true);
|
||||
}
|
||||
|
||||
|
|
@ -1575,6 +1603,7 @@ class Contact
|
|||
'confirm' => $data['confirm'] ?? '',
|
||||
'poco' => $data['poco'] ?? '',
|
||||
'baseurl' => $data['baseurl'] ?? '',
|
||||
'gsid' => $data['gsid'] ?? null,
|
||||
'name-date' => DateTimeFormat::utcNow(),
|
||||
'uri-date' => DateTimeFormat::utcNow(),
|
||||
'avatar-date' => DateTimeFormat::utcNow(),
|
||||
|
|
@ -1627,7 +1656,7 @@ class Contact
|
|||
}
|
||||
}
|
||||
} else {
|
||||
$fields = ['url', 'nurl', 'addr', 'alias', 'name', 'nick', 'keywords', 'location', 'about', 'avatar-date', 'baseurl'];
|
||||
$fields = ['url', 'nurl', 'addr', 'alias', 'name', 'nick', 'keywords', 'location', 'about', 'avatar-date', 'baseurl', 'gsid'];
|
||||
$contact = DBA::selectFirst('contact', $fields, ['id' => $contact_id]);
|
||||
|
||||
// This condition should always be true
|
||||
|
|
@ -1641,7 +1670,7 @@ class Contact
|
|||
'updated' => DateTimeFormat::utcNow()
|
||||
];
|
||||
|
||||
$fields = ['addr', 'alias', 'name', 'nick', 'keywords', 'location', 'about', 'baseurl'];
|
||||
$fields = ['addr', 'alias', 'name', 'nick', 'keywords', 'location', 'about', 'baseurl', 'gsid'];
|
||||
|
||||
foreach ($fields as $field) {
|
||||
$updated[$field] = ($data[$field] ?? '') ?: $contact[$field];
|
||||
|
|
@ -1756,7 +1785,6 @@ class Contact
|
|||
* Returns posts from a given contact url
|
||||
*
|
||||
* @param string $contact_url Contact URL
|
||||
*
|
||||
* @param bool $thread_mode
|
||||
* @param int $update
|
||||
* @return string posts in HTML
|
||||
|
|
@ -1764,9 +1792,21 @@ class Contact
|
|||
*/
|
||||
public static function getPostsFromUrl($contact_url, $thread_mode = false, $update = 0)
|
||||
{
|
||||
$a = DI::app();
|
||||
return self::getPostsFromId(self::getIdForURL($contact_url), $thread_mode, $update);
|
||||
}
|
||||
|
||||
$cid = self::getIdForURL($contact_url);
|
||||
/**
|
||||
* Returns posts from a given contact id
|
||||
*
|
||||
* @param integer $cid
|
||||
* @param bool $thread_mode
|
||||
* @param integer $update
|
||||
* @return string posts in HTML
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function getPostsFromId($cid, $thread_mode = false, $update = 0)
|
||||
{
|
||||
$a = DI::app();
|
||||
|
||||
$contact = DBA::selectFirst('contact', ['contact-type', 'network'], ['id' => $cid]);
|
||||
if (!DBA::isResult($contact)) {
|
||||
|
|
@ -2057,6 +2097,7 @@ class Contact
|
|||
|
||||
Worker::add(PRIORITY_HIGH, 'MergeContact', $first, $duplicate['id'], $uid);
|
||||
}
|
||||
DBA::close($duplicates);
|
||||
Logger::info('Duplicates handled', ['uid' => $uid, 'nurl' => $nurl]);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -2079,9 +2120,9 @@ class Contact
|
|||
// These fields aren't updated by this routine:
|
||||
// 'xmpp', 'sensitive'
|
||||
|
||||
$fields = ['uid', 'avatar', 'name', 'nick', 'location', 'keywords', 'about',
|
||||
$fields = ['uid', 'avatar', 'name', 'nick', 'location', 'keywords', 'about', 'subscribe',
|
||||
'unsearchable', 'url', 'addr', 'batch', 'notify', 'poll', 'request', 'confirm', 'poco',
|
||||
'network', 'alias', 'baseurl', 'forum', 'prv', 'contact-type', 'pubkey'];
|
||||
'network', 'alias', 'baseurl', 'gsid', 'forum', 'prv', 'contact-type', 'pubkey'];
|
||||
$contact = DBA::selectFirst('contact', $fields, ['id' => $id]);
|
||||
if (!DBA::isResult($contact)) {
|
||||
return false;
|
||||
|
|
@ -2255,20 +2296,20 @@ class Contact
|
|||
* $return['message'] error text if success is false.
|
||||
*
|
||||
* Takes a $uid and a url/handle and adds a new contact
|
||||
* @param int $uid
|
||||
* @param string $url
|
||||
*
|
||||
* @param array $user The user the contact should be created for
|
||||
* @param string $url The profile URL of the contact
|
||||
* @param bool $interactive
|
||||
* @param string $network
|
||||
* @return array
|
||||
* @throws HTTPException\InternalServerErrorException
|
||||
* @throws HTTPException\NotFoundException
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
public static function createFromProbe($uid, $url, $interactive = false, $network = '')
|
||||
public static function createFromProbe(array $user, $url, $interactive = false, $network = '')
|
||||
{
|
||||
$result = ['cid' => -1, 'success' => false, 'message' => ''];
|
||||
|
||||
$a = DI::app();
|
||||
|
||||
// remove ajax junk, e.g. Twitter
|
||||
$url = str_replace('/#!/', '/', $url);
|
||||
|
||||
|
|
@ -2299,7 +2340,7 @@ class Contact
|
|||
if (!empty($arr['contact']['name'])) {
|
||||
$ret = $arr['contact'];
|
||||
} else {
|
||||
$ret = Probe::uri($url, $network, $uid, false);
|
||||
$ret = Probe::uri($url, $network, $user['uid'], false);
|
||||
}
|
||||
|
||||
if (($network != '') && ($ret['network'] != $network)) {
|
||||
|
|
@ -2311,21 +2352,21 @@ class Contact
|
|||
// the poll url is more reliable than the profile url, as we may have
|
||||
// indirect links or webfinger links
|
||||
|
||||
$condition = ['uid' => $uid, 'poll' => [$ret['poll'], Strings::normaliseLink($ret['poll'])], 'network' => $ret['network'], 'pending' => false];
|
||||
$condition = ['uid' => $user['uid'], 'poll' => [$ret['poll'], Strings::normaliseLink($ret['poll'])], 'network' => $ret['network'], 'pending' => false];
|
||||
$contact = DBA::selectFirst('contact', ['id', 'rel'], $condition);
|
||||
if (!DBA::isResult($contact)) {
|
||||
$condition = ['uid' => $uid, 'nurl' => Strings::normaliseLink($url), 'network' => $ret['network'], 'pending' => false];
|
||||
$condition = ['uid' => $user['uid'], 'nurl' => Strings::normaliseLink($ret['url']), 'network' => $ret['network'], 'pending' => false];
|
||||
$contact = DBA::selectFirst('contact', ['id', 'rel'], $condition);
|
||||
}
|
||||
|
||||
$protocol = self::getProtocol($url, $ret['network']);
|
||||
$protocol = self::getProtocol($ret['url'], $ret['network']);
|
||||
|
||||
if (($protocol === Protocol::DFRN) && !DBA::isResult($contact)) {
|
||||
if ($interactive) {
|
||||
if (strlen(DI::baseUrl()->getUrlPath())) {
|
||||
$myaddr = bin2hex(DI::baseUrl() . '/profile/' . $a->user['nickname']);
|
||||
$myaddr = bin2hex(DI::baseUrl() . '/profile/' . $user['nickname']);
|
||||
} else {
|
||||
$myaddr = bin2hex($a->user['nickname'] . '@' . DI::baseUrl()->getHostname());
|
||||
$myaddr = bin2hex($user['nickname'] . '@' . DI::baseUrl()->getHostname());
|
||||
}
|
||||
|
||||
DI::baseUrl()->redirect($ret['request'] . "&addr=$myaddr");
|
||||
|
|
@ -2355,7 +2396,7 @@ class Contact
|
|||
if (empty($ret['url'])) {
|
||||
$result['message'] .= DI::l10n()->t('No browser URL could be matched to this address.') . EOL;
|
||||
}
|
||||
if (strpos($url, '@') !== false) {
|
||||
if (strpos($ret['url'], '@') !== false) {
|
||||
$result['message'] .= DI::l10n()->t('Unable to match @-style Identity Address with a known protocol or email contact.') . EOL;
|
||||
$result['message'] .= DI::l10n()->t('Use mailto: in front of address to force email check.') . EOL;
|
||||
}
|
||||
|
|
@ -2379,7 +2420,7 @@ class Contact
|
|||
|
||||
$pending = false;
|
||||
if ($protocol == Protocol::ACTIVITYPUB) {
|
||||
$apcontact = APContact::getByURL($url, false);
|
||||
$apcontact = APContact::getByURL($ret['url'], false);
|
||||
if (isset($apcontact['manually-approve'])) {
|
||||
$pending = (bool)$apcontact['manually-approve'];
|
||||
}
|
||||
|
|
@ -2400,7 +2441,7 @@ class Contact
|
|||
|
||||
// create contact record
|
||||
self::insert([
|
||||
'uid' => $uid,
|
||||
'uid' => $user['uid'],
|
||||
'created' => DateTimeFormat::utcNow(),
|
||||
'url' => $ret['url'],
|
||||
'nurl' => Strings::normaliseLink($ret['url']),
|
||||
|
|
@ -2414,6 +2455,7 @@ class Contact
|
|||
'nick' => $ret['nick'],
|
||||
'network' => $ret['network'],
|
||||
'baseurl' => $ret['baseurl'],
|
||||
'gsid' => $ret['gsid'] ?? null,
|
||||
'protocol' => $protocol,
|
||||
'pubkey' => $ret['pubkey'],
|
||||
'rel' => $new_relation,
|
||||
|
|
@ -2427,7 +2469,7 @@ class Contact
|
|||
]);
|
||||
}
|
||||
|
||||
$contact = DBA::selectFirst('contact', [], ['url' => $ret['url'], 'network' => $ret['network'], 'uid' => $uid]);
|
||||
$contact = DBA::selectFirst('contact', [], ['url' => $ret['url'], 'network' => $ret['network'], 'uid' => $user['uid']]);
|
||||
if (!DBA::isResult($contact)) {
|
||||
$result['message'] .= DI::l10n()->t('Unable to retrieve contact information.') . EOL;
|
||||
return $result;
|
||||
|
|
@ -2436,27 +2478,28 @@ class Contact
|
|||
$contact_id = $contact['id'];
|
||||
$result['cid'] = $contact_id;
|
||||
|
||||
Group::addMember(User::getDefaultGroup($uid, $contact["network"]), $contact_id);
|
||||
Group::addMember(User::getDefaultGroup($user['uid'], $contact["network"]), $contact_id);
|
||||
|
||||
// Update the avatar
|
||||
self::updateAvatar($ret['photo'], $uid, $contact_id);
|
||||
self::updateAvatar($ret['photo'], $user['uid'], $contact_id);
|
||||
|
||||
// pull feed and consume it, which should subscribe to the hub.
|
||||
|
||||
Worker::add(PRIORITY_HIGH, "OnePoll", $contact_id, "force");
|
||||
|
||||
$owner = User::getOwnerDataById($uid);
|
||||
$owner = User::getOwnerDataById($user['uid']);
|
||||
|
||||
if (DBA::isResult($owner)) {
|
||||
if (in_array($protocol, [Protocol::OSTATUS, Protocol::DFRN])) {
|
||||
// create a follow slap
|
||||
$item = [];
|
||||
$item['verb'] = Activity::FOLLOW;
|
||||
$item['gravity'] = GRAVITY_ACTIVITY;
|
||||
$item['follow'] = $contact["url"];
|
||||
$item['body'] = '';
|
||||
$item['title'] = '';
|
||||
$item['guid'] = '';
|
||||
$item['tag'] = '';
|
||||
$item['uri-id'] = 0;
|
||||
$item['attach'] = '';
|
||||
|
||||
$slap = OStatus::salmon($item, $owner);
|
||||
|
|
@ -2465,7 +2508,7 @@ class Contact
|
|||
Salmon::slapper($owner, $contact['notify'], $slap);
|
||||
}
|
||||
} elseif ($protocol == Protocol::DIASPORA) {
|
||||
$ret = Diaspora::sendShare($a->user, $contact);
|
||||
$ret = Diaspora::sendShare($owner, $contact);
|
||||
Logger::log('share returns: ' . $ret);
|
||||
} elseif ($protocol == Protocol::ACTIVITYPUB) {
|
||||
$activity_id = ActivityPub\Transmitter::activityIDFromContact($contact_id);
|
||||
|
|
@ -2474,7 +2517,7 @@ class Contact
|
|||
return false;
|
||||
}
|
||||
|
||||
$ret = ActivityPub\Transmitter::sendActivity('Follow', $contact['url'], $uid, $activity_id);
|
||||
$ret = ActivityPub\Transmitter::sendActivity('Follow', $contact['url'], $user['uid'], $activity_id);
|
||||
Logger::log('Follow returns: ' . $ret);
|
||||
}
|
||||
}
|
||||
|
|
@ -2659,7 +2702,7 @@ class Contact
|
|||
}
|
||||
} elseif (DBA::isResult($user) && in_array($user['page-flags'], [User::PAGE_FLAGS_SOAPBOX, User::PAGE_FLAGS_FREELOVE, User::PAGE_FLAGS_COMMUNITY])) {
|
||||
if (($user['page-flags'] == User::PAGE_FLAGS_FREELOVE) && ($network != Protocol::DIASPORA)) {
|
||||
self::createFromProbe($importer['uid'], $url, false, $network);
|
||||
self::createFromProbe($importer, $url, false, $network);
|
||||
}
|
||||
|
||||
$condition = ['uid' => $importer['uid'], 'url' => $url, 'pending' => true];
|
||||
|
|
@ -2732,6 +2775,7 @@ class Contact
|
|||
);
|
||||
}
|
||||
}
|
||||
DBA::close($contacts);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ namespace Friendica\Model;
|
|||
use Friendica\Content\Text\BBCode;
|
||||
use Friendica\Core\Hook;
|
||||
use Friendica\Core\Logger;
|
||||
use Friendica\Core\Protocol;
|
||||
use Friendica\Core\Renderer;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Database\DBA;
|
||||
|
|
@ -370,6 +371,7 @@ class Event
|
|||
$item_arr['origin'] = $event['cid'] === 0 ? 1 : 0;
|
||||
$item_arr['body'] = self::getBBCode($event);
|
||||
$item_arr['event-id'] = $event['id'];
|
||||
$item_arr['network'] = Protocol::DFRN;
|
||||
|
||||
$item_arr['object'] = '<object><type>' . XML::escape(Activity\ObjectType::EVENT) . '</type><title></title><id>' . XML::escape($event['uri']) . '</id>';
|
||||
$item_arr['object'] .= '<content>' . XML::escape(self::getBBCode($event)) . '</content>';
|
||||
|
|
@ -611,14 +613,12 @@ class Event
|
|||
|
||||
$title = BBCode::convert(Strings::escapeHtml($event['summary']));
|
||||
if (!$title) {
|
||||
list($title, $_trash) = explode("<br", BBCode::convert(Strings::escapeHtml($event['desc'])), 2);
|
||||
list($title, $_trash) = explode("<br", BBCode::convert(Strings::escapeHtml($event['desc'])), BBCode::API);
|
||||
}
|
||||
|
||||
$author_link = $event['author-link'];
|
||||
$plink = $event['plink'];
|
||||
|
||||
$event['author-link'] = Contact::magicLink($author_link);
|
||||
$event['plink'] = Contact::magicLink($author_link, $plink);
|
||||
|
||||
$html = self::getHTML($event);
|
||||
$event['summary'] = BBCode::convert(Strings::escapeHtml($event['summary']));
|
||||
|
|
@ -638,7 +638,7 @@ class Event
|
|||
'is_first' => $is_first,
|
||||
'item' => $event,
|
||||
'html' => $html,
|
||||
'plink' => [$event['plink'], DI::l10n()->t('link to source'), '', ''],
|
||||
'plink' => Item::getPlink($event),
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ namespace Friendica\Model;
|
|||
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\DI;
|
||||
use Friendica\Model\Post\Category;
|
||||
|
||||
/**
|
||||
* This class handles FileTag related functions
|
||||
|
|
@ -195,11 +196,11 @@ class FileTag
|
|||
if ($type == 'file') {
|
||||
$lbracket = '[';
|
||||
$rbracket = ']';
|
||||
$termtype = TERM_FILE;
|
||||
$termtype = Category::FILE;
|
||||
} else {
|
||||
$lbracket = '<';
|
||||
$rbracket = '>';
|
||||
$termtype = TERM_CATEGORY;
|
||||
$termtype = Category::CATEGORY;
|
||||
}
|
||||
|
||||
$filetags_updated = $saved;
|
||||
|
|
@ -223,13 +224,7 @@ class FileTag
|
|||
}
|
||||
|
||||
foreach ($deleted_tags as $key => $tag) {
|
||||
$r = q("SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d",
|
||||
DBA::escape($tag),
|
||||
intval(Term::OBJECT_TYPE_POST),
|
||||
intval($termtype),
|
||||
intval($uid));
|
||||
|
||||
if (DBA::isResult($r)) {
|
||||
if (DBA::exists('category-view', ['name' => $tag, 'type' => $termtype, 'uid' => $uid])) {
|
||||
unset($deleted_tags[$key]);
|
||||
} else {
|
||||
$filetags_updated = str_replace($lbracket . self::encode($tag) . $rbracket, '', $filetags_updated);
|
||||
|
|
@ -302,10 +297,10 @@ class FileTag
|
|||
|
||||
if ($cat == true) {
|
||||
$pattern = '<' . self::encode($file) . '>';
|
||||
$termtype = Term::CATEGORY;
|
||||
$termtype = Category::CATEGORY;
|
||||
} else {
|
||||
$pattern = '[' . self::encode($file) . ']';
|
||||
$termtype = Term::FILE;
|
||||
$termtype = Category::FILE;
|
||||
}
|
||||
|
||||
$item = Item::selectFirst(['file'], ['id' => $item_id, 'uid' => $uid]);
|
||||
|
|
@ -318,14 +313,7 @@ class FileTag
|
|||
|
||||
Item::update($fields, ['id' => $item_id]);
|
||||
|
||||
$r = q("SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d",
|
||||
DBA::escape($file),
|
||||
intval(Term::OBJECT_TYPE_POST),
|
||||
intval($termtype),
|
||||
intval($uid)
|
||||
);
|
||||
|
||||
if (!DBA::isResult($r)) {
|
||||
if (!DBA::exists('category-view', ['name' => $file, 'type' => $termtype, 'uid' => $uid])) {
|
||||
$saved = DI::pConfig()->get($uid, 'system', 'filetags');
|
||||
DI::pConfig()->set($uid, 'system', 'filetags', str_replace($pattern, '', $saved));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -113,6 +113,7 @@ class GContact
|
|||
|
||||
$gcontacts[] = Contact::getDetailsByURL($result['nurl'], local_user());
|
||||
}
|
||||
DBA::close($results);
|
||||
return $gcontacts;
|
||||
}
|
||||
|
||||
|
|
@ -229,8 +230,6 @@ class GContact
|
|||
throw new Exception('Probing for URL ' . $gcontact['url'] . ' failed');
|
||||
}
|
||||
|
||||
$orig_profile = $gcontact['url'];
|
||||
|
||||
$gcontact['server_url'] = $data['baseurl'];
|
||||
|
||||
$gcontact = array_merge($gcontact, $data);
|
||||
|
|
@ -563,6 +562,7 @@ class GContact
|
|||
PortableContact::loadWorker(0, 0, 0, $base);
|
||||
}
|
||||
}
|
||||
DBA::close($contacts);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -609,8 +609,6 @@ class GContact
|
|||
*/
|
||||
public static function getId($contact)
|
||||
{
|
||||
$gcontact_id = 0;
|
||||
|
||||
if (empty($contact['network'])) {
|
||||
Logger::notice('Empty network', ['url' => $contact['url'], 'callstack' => System::callstack()]);
|
||||
return false;
|
||||
|
|
@ -625,42 +623,37 @@ class GContact
|
|||
$contact['network'] = Protocol::OSTATUS;
|
||||
}
|
||||
|
||||
// All new contacts are hidden by default
|
||||
if (!isset($contact['hide'])) {
|
||||
$contact['hide'] = true;
|
||||
}
|
||||
|
||||
// Remove unwanted parts from the contact url (e.g. '?zrl=...')
|
||||
if (in_array($contact['network'], Protocol::FEDERATED)) {
|
||||
$contact['url'] = self::cleanContactUrl($contact['url']);
|
||||
}
|
||||
|
||||
DBA::lock('gcontact');
|
||||
$fields = ['id', 'last_contact', 'last_failure', 'network'];
|
||||
$gcnt = DBA::selectFirst('gcontact', $fields, ['nurl' => Strings::normaliseLink($contact['url'])]);
|
||||
if (DBA::isResult($gcnt)) {
|
||||
$gcontact_id = $gcnt['id'];
|
||||
} else {
|
||||
$contact['location'] = $contact['location'] ?? '';
|
||||
$contact['about'] = $contact['about'] ?? '';
|
||||
$contact['generation'] = $contact['generation'] ?? 0;
|
||||
|
||||
$fields = ['name' => $contact['name'], 'nick' => $contact['nick'] ?? '', 'addr' => $contact['addr'] ?? '', 'network' => $contact['network'],
|
||||
'url' => $contact['url'], 'nurl' => Strings::normaliseLink($contact['url']), 'photo' => $contact['photo'],
|
||||
'created' => DateTimeFormat::utcNow(), 'updated' => DateTimeFormat::utcNow(), 'location' => $contact['location'],
|
||||
'about' => $contact['about'], 'hide' => $contact['hide'], 'generation' => $contact['generation']];
|
||||
|
||||
DBA::insert('gcontact', $fields);
|
||||
|
||||
$condition = ['nurl' => Strings::normaliseLink($contact['url'])];
|
||||
$cnt = DBA::selectFirst('gcontact', ['id', 'network'], $condition, ['order' => ['id']]);
|
||||
if (DBA::isResult($cnt)) {
|
||||
$gcontact_id = $cnt['id'];
|
||||
}
|
||||
$condition = ['nurl' => Strings::normaliseLink($contact['url'])];
|
||||
$gcontact = DBA::selectFirst('gcontact', ['id'], $condition, ['order' => ['id']]);
|
||||
if (DBA::isResult($gcontact)) {
|
||||
return $gcontact['id'];
|
||||
}
|
||||
DBA::unlock();
|
||||
|
||||
return $gcontact_id;
|
||||
$contact['location'] = $contact['location'] ?? '';
|
||||
$contact['about'] = $contact['about'] ?? '';
|
||||
$contact['generation'] = $contact['generation'] ?? 0;
|
||||
$contact['hide'] = $contact['hide'] ?? true;
|
||||
|
||||
$fields = ['name' => $contact['name'], 'nick' => $contact['nick'] ?? '', 'addr' => $contact['addr'] ?? '', 'network' => $contact['network'],
|
||||
'url' => $contact['url'], 'nurl' => Strings::normaliseLink($contact['url']), 'photo' => $contact['photo'],
|
||||
'created' => DateTimeFormat::utcNow(), 'updated' => DateTimeFormat::utcNow(), 'location' => $contact['location'],
|
||||
'about' => $contact['about'], 'hide' => $contact['hide'], 'generation' => $contact['generation']];
|
||||
|
||||
DBA::insert('gcontact', $fields);
|
||||
|
||||
// We intentionally aren't using lastInsertId here. There is a chance for duplicates.
|
||||
$gcontact = DBA::selectFirst('gcontact', ['id'], $condition, ['order' => ['id']]);
|
||||
if (!DBA::isResult($gcontact)) {
|
||||
Logger::info('GContact creation failed', $fields);
|
||||
// Shouldn't happen
|
||||
return 0;
|
||||
}
|
||||
return $gcontact['id'];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -688,7 +681,7 @@ class GContact
|
|||
}
|
||||
|
||||
$public_contact = DBA::selectFirst('gcontact', [
|
||||
'name', 'nick', 'photo', 'location', 'about', 'addr', 'generation', 'birthday', 'keywords',
|
||||
'name', 'nick', 'photo', 'location', 'about', 'addr', 'generation', 'birthday', 'keywords', 'gsid',
|
||||
'contact-type', 'hide', 'nsfw', 'network', 'alias', 'notify', 'server_url', 'connect', 'updated', 'url'
|
||||
], ['id' => $gcontact_id]);
|
||||
|
||||
|
|
@ -750,6 +743,10 @@ class GContact
|
|||
$contact['server_url'] = Strings::normaliseLink($contact['server_url']);
|
||||
}
|
||||
|
||||
if (!empty($contact['server_url']) && empty($contact['gsid'])) {
|
||||
$contact['gsid'] = GServer::getID($contact['server_url']);
|
||||
}
|
||||
|
||||
if (empty($contact['addr']) && !empty($contact['server_url']) && !empty($contact['nick'])) {
|
||||
$hostname = str_replace('http://', '', $contact['server_url']);
|
||||
$contact['addr'] = $contact['nick'] . '@' . $hostname;
|
||||
|
|
@ -789,7 +786,8 @@ class GContact
|
|||
'notify' => $contact['notify'], 'url' => $contact['url'],
|
||||
'location' => $contact['location'], 'about' => $contact['about'],
|
||||
'generation' => $contact['generation'], 'updated' => $contact['updated'],
|
||||
'server_url' => $contact['server_url'], 'connect' => $contact['connect']
|
||||
'server_url' => $contact['server_url'], 'connect' => $contact['connect'],
|
||||
'gsid' => $contact['gsid']
|
||||
];
|
||||
|
||||
DBA::update('gcontact', $updated, $condition, $fields);
|
||||
|
|
@ -1014,7 +1012,7 @@ class GContact
|
|||
$fields = ['name', 'nick', 'url', 'nurl', 'location', 'about', 'keywords',
|
||||
'bd', 'contact-type', 'network', 'addr', 'notify', 'alias', 'archive', 'term-date',
|
||||
'created', 'updated', 'avatar', 'success_update', 'failure_update', 'forum', 'prv',
|
||||
'baseurl', 'sensitive', 'unsearchable'];
|
||||
'baseurl', 'gsid', 'sensitive', 'unsearchable'];
|
||||
|
||||
$contact = DBA::selectFirst('contact', $fields, array_merge($condition, ['uid' => 0, 'network' => Protocol::FEDERATED]));
|
||||
if (!DBA::isResult($contact)) {
|
||||
|
|
@ -1024,7 +1022,7 @@ class GContact
|
|||
$fields = ['name', 'nick', 'url', 'nurl', 'location', 'about', 'keywords', 'generation',
|
||||
'birthday', 'contact-type', 'network', 'addr', 'notify', 'alias', 'archived', 'archive_date',
|
||||
'created', 'updated', 'photo', 'last_contact', 'last_failure', 'community', 'connect',
|
||||
'server_url', 'nsfw', 'hide', 'id'];
|
||||
'server_url', 'gsid', 'nsfw', 'hide', 'id'];
|
||||
|
||||
$old_gcontact = DBA::selectFirst('gcontact', $fields, ['nurl' => $contact['nurl']]);
|
||||
$do_insert = !DBA::isResult($old_gcontact);
|
||||
|
|
@ -1035,7 +1033,7 @@ class GContact
|
|||
$gcontact = [];
|
||||
|
||||
// These fields are identical in both contact and gcontact
|
||||
$fields = ['name', 'nick', 'url', 'nurl', 'location', 'about', 'keywords',
|
||||
$fields = ['name', 'nick', 'url', 'nurl', 'location', 'about', 'keywords', 'gsid',
|
||||
'contact-type', 'network', 'addr', 'notify', 'alias', 'created', 'updated'];
|
||||
|
||||
foreach ($fields as $field) {
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ use Friendica\Util\DateTimeFormat;
|
|||
use Friendica\Util\Strings;
|
||||
use Friendica\Util\XML;
|
||||
use Friendica\Core\Logger;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Protocol\PortableContact;
|
||||
use Friendica\Protocol\Diaspora;
|
||||
use Friendica\Network\Probe;
|
||||
|
|
@ -47,6 +48,57 @@ class GServer
|
|||
const DT_NONE = 0;
|
||||
const DT_POCO = 1;
|
||||
const DT_MASTODON = 2;
|
||||
|
||||
// Methods to detect server types
|
||||
|
||||
// Non endpoint specific methods
|
||||
const DETECT_MANUAL = 0;
|
||||
const DETECT_HEADER = 1;
|
||||
const DETECT_BODY = 2;
|
||||
|
||||
// Implementation specific endpoints
|
||||
const DETECT_FRIENDIKA = 10;
|
||||
const DETECT_FRIENDICA = 11;
|
||||
const DETECT_STATUSNET = 12;
|
||||
const DETECT_GNUSOCIAL = 13;
|
||||
const DETECT_CONFIG_JSON = 14; // Statusnet, GNU Social, Older Hubzilla/Redmatrix
|
||||
const DETECT_SITEINFO_JSON = 15; // Newer Hubzilla
|
||||
const DETECT_MASTODON_API = 16;
|
||||
const DETECT_STATUS_PHP = 17; // Nextcloud
|
||||
|
||||
// Standardized endpoints
|
||||
const DETECT_STATISTICS_JSON = 100;
|
||||
const DETECT_NODEINFO_1 = 101;
|
||||
const DETECT_NODEINFO_2 = 102;
|
||||
|
||||
/**
|
||||
* Get the ID for the given server URL
|
||||
*
|
||||
* @param string $url
|
||||
* @param boolean $no_check Don't check if the server hadn't been found
|
||||
* @return int gserver id
|
||||
*/
|
||||
public static function getID(string $url, bool $no_check = false)
|
||||
{
|
||||
if (empty($url)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$url = self::cleanURL($url);
|
||||
|
||||
$gserver = DBA::selectFirst('gserver', ['id'], ['nurl' => Strings::normaliseLink($url)]);
|
||||
if (DBA::isResult($gserver)) {
|
||||
Logger::info('Got ID for URL', ['id' => $gserver['id'], 'url' => $url, 'callstack' => System::callstack(20)]);
|
||||
return $gserver['id'];
|
||||
}
|
||||
|
||||
if ($no_check || !self::check($url)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return self::getID($url, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given server is reachable
|
||||
*
|
||||
|
|
@ -128,17 +180,16 @@ class GServer
|
|||
/**
|
||||
* Checks the state of the given server.
|
||||
*
|
||||
* @param string $server_url URL of the given server
|
||||
* @param string $network Network value that is used, when detection failed
|
||||
* @param boolean $force Force an update.
|
||||
* @param string $server_url URL of the given server
|
||||
* @param string $network Network value that is used, when detection failed
|
||||
* @param boolean $force Force an update.
|
||||
* @param boolean $only_nodeinfo Only use nodeinfo for server detection
|
||||
*
|
||||
* @return boolean 'true' if server seems vital
|
||||
*/
|
||||
public static function check(string $server_url, string $network = '', bool $force = false)
|
||||
public static function check(string $server_url, string $network = '', bool $force = false, bool $only_nodeinfo = false)
|
||||
{
|
||||
// Unify the server address
|
||||
$server_url = trim($server_url, '/');
|
||||
$server_url = str_replace('/index.php', '', $server_url);
|
||||
$server_url = self::cleanURL($server_url);
|
||||
|
||||
if ($server_url == '') {
|
||||
return false;
|
||||
|
|
@ -174,32 +225,82 @@ class GServer
|
|||
Logger::info('Server is unknown. Start discovery.', ['Server' => $server_url]);
|
||||
}
|
||||
|
||||
return self::detect($server_url, $network);
|
||||
return self::detect($server_url, $network, $only_nodeinfo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set failed server status
|
||||
*
|
||||
* @param string $url
|
||||
*/
|
||||
private static function setFailure(string $url)
|
||||
{
|
||||
if (DBA::exists('gserver', ['nurl' => Strings::normaliseLink($url)])) {
|
||||
DBA::update('gserver', ['last_failure' => DateTimeFormat::utcNow(), 'detection-method' => null],
|
||||
['nurl' => Strings::normaliseLink($url)]);
|
||||
Logger::info('Set failed status for existing server', ['url' => $url]);
|
||||
return;
|
||||
}
|
||||
DBA::insert('gserver', ['url' => $url, 'nurl' => Strings::normaliseLink($url),
|
||||
'network' => Protocol::PHANTOM, 'created' => DateTimeFormat::utcNow(),
|
||||
'last_failure' => DateTimeFormat::utcNow()]);
|
||||
Logger::info('Set failed status for new server', ['url' => $url]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove unwanted content from the given URL
|
||||
*
|
||||
* @param string $url
|
||||
* @return string cleaned URL
|
||||
*/
|
||||
public static function cleanURL(string $url)
|
||||
{
|
||||
$url = trim($url, '/');
|
||||
$url = str_replace('/index.php', '', $url);
|
||||
|
||||
$urlparts = parse_url($url);
|
||||
unset($urlparts['user']);
|
||||
unset($urlparts['pass']);
|
||||
unset($urlparts['query']);
|
||||
unset($urlparts['fragment']);
|
||||
return Network::unparseURL($urlparts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the base URL
|
||||
*
|
||||
* @param string $url
|
||||
* @return string base URL
|
||||
*/
|
||||
private static function getBaseURL(string $url)
|
||||
{
|
||||
$urlparts = parse_url(self::cleanURL($url));
|
||||
unset($urlparts['path']);
|
||||
return Network::unparseURL($urlparts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect server data (type, protocol, version number, ...)
|
||||
* The detected data is then updated or inserted in the gserver table.
|
||||
*
|
||||
* @param string $url URL of the given server
|
||||
* @param string $network Network value that is used, when detection failed
|
||||
* @param string $url URL of the given server
|
||||
* @param string $network Network value that is used, when detection failed
|
||||
* @param boolean $only_nodeinfo Only use nodeinfo for server detection
|
||||
*
|
||||
* @return boolean 'true' if server could be detected
|
||||
*/
|
||||
public static function detect(string $url, string $network = '')
|
||||
public static function detect(string $url, string $network = '', bool $only_nodeinfo = false)
|
||||
{
|
||||
Logger::info('Detect server type', ['server' => $url]);
|
||||
$serverdata = [];
|
||||
$serverdata = ['detection-method' => self::DETECT_MANUAL];
|
||||
|
||||
$original_url = $url;
|
||||
|
||||
// Remove URL content that is not supposed to exist for a server url
|
||||
$urlparts = parse_url($url);
|
||||
unset($urlparts['user']);
|
||||
unset($urlparts['pass']);
|
||||
unset($urlparts['query']);
|
||||
unset($urlparts['fragment']);
|
||||
$url = Network::unparseURL($urlparts);
|
||||
$url = self::cleanURL($url);
|
||||
|
||||
// Get base URL
|
||||
$baseurl = self::getBaseURL($url);
|
||||
|
||||
// If the URL missmatches, then we mark the old entry as failure
|
||||
if ($url != $original_url) {
|
||||
|
|
@ -210,11 +311,16 @@ class GServer
|
|||
$xrd_timeout = DI::config()->get('system', 'xrd_timeout');
|
||||
$curlResult = Network::curl($url . '/.well-known/nodeinfo', false, ['timeout' => $xrd_timeout]);
|
||||
if ($curlResult->isTimeout()) {
|
||||
DBA::update('gserver', ['last_failure' => DateTimeFormat::utcNow()], ['nurl' => Strings::normaliseLink($url)]);
|
||||
self::setFailure($url);
|
||||
return false;
|
||||
}
|
||||
|
||||
$nodeinfo = self::fetchNodeinfo($url, $curlResult);
|
||||
if ($only_nodeinfo && empty($nodeinfo)) {
|
||||
Logger::info('Invalid nodeinfo in nodeinfo-mode, server is marked as failure', ['url' => $url]);
|
||||
self::setFailure($url);
|
||||
return false;
|
||||
}
|
||||
|
||||
// When nodeinfo isn't present, we use the older 'statistics.json' endpoint
|
||||
if (empty($nodeinfo)) {
|
||||
|
|
@ -224,18 +330,53 @@ class GServer
|
|||
// If that didn't work out well, we use some protocol specific endpoints
|
||||
// For Friendica and Zot based networks we have to dive deeper to reveal more details
|
||||
if (empty($nodeinfo['network']) || in_array($nodeinfo['network'], [Protocol::DFRN, Protocol::ZOT])) {
|
||||
if (!empty($nodeinfo['detection-method'])) {
|
||||
$serverdata['detection-method'] = $nodeinfo['detection-method'];
|
||||
}
|
||||
|
||||
// Fetch the landing page, possibly it reveals some data
|
||||
if (empty($nodeinfo['network'])) {
|
||||
$curlResult = Network::curl($url, false, ['timeout' => $xrd_timeout]);
|
||||
if ($baseurl == $url) {
|
||||
$basedata = $serverdata;
|
||||
} else {
|
||||
$basedata = ['detection-method' => self::DETECT_MANUAL];
|
||||
}
|
||||
|
||||
$curlResult = Network::curl($baseurl, false, ['timeout' => $xrd_timeout]);
|
||||
if ($curlResult->isSuccess()) {
|
||||
$serverdata = self::analyseRootHeader($curlResult, $serverdata);
|
||||
$serverdata = self::analyseRootBody($curlResult, $serverdata, $url);
|
||||
$basedata = self::analyseRootHeader($curlResult, $basedata);
|
||||
$basedata = self::analyseRootBody($curlResult, $basedata, $baseurl);
|
||||
}
|
||||
|
||||
if (!$curlResult->isSuccess() || empty($curlResult->getBody()) || self::invalidBody($curlResult->getBody())) {
|
||||
DBA::update('gserver', ['last_failure' => DateTimeFormat::utcNow()], ['nurl' => Strings::normaliseLink($url)]);
|
||||
self::setFailure($url);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($baseurl == $url) {
|
||||
$serverdata = $basedata;
|
||||
} else {
|
||||
// When the base path doesn't seem to contain a social network we try the complete path.
|
||||
// Most detectable system have to be installed in the root directory.
|
||||
// We checked the base to avoid false positives.
|
||||
$curlResult = Network::curl($url, false, ['timeout' => $xrd_timeout]);
|
||||
if ($curlResult->isSuccess()) {
|
||||
$urldata = self::analyseRootHeader($curlResult, $serverdata);
|
||||
$urldata = self::analyseRootBody($curlResult, $urldata, $url);
|
||||
|
||||
$comparebase = $basedata;
|
||||
unset($comparebase['info']);
|
||||
unset($comparebase['site_name']);
|
||||
$compareurl = $urldata;
|
||||
unset($compareurl['info']);
|
||||
unset($compareurl['site_name']);
|
||||
|
||||
// We assume that no one will install the identical system in the root and a subfolder
|
||||
if (!empty(array_diff($comparebase, $compareurl))) {
|
||||
$serverdata = $urldata;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($serverdata['network']) || ($serverdata['network'] == Protocol::ACTIVITYPUB)) {
|
||||
|
|
@ -246,7 +387,7 @@ class GServer
|
|||
// With this check we don't have to waste time and ressources for dead systems.
|
||||
// Also this hopefully prevents us from receiving abuse messages.
|
||||
if (empty($serverdata['network']) && !self::validHostMeta($url)) {
|
||||
DBA::update('gserver', ['last_failure' => DateTimeFormat::utcNow()], ['nurl' => Strings::normaliseLink($url)]);
|
||||
self::setFailure($url);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -271,6 +412,8 @@ class GServer
|
|||
if (empty($serverdata['network'])) {
|
||||
$serverdata = self::detectGNUSocial($url, $serverdata);
|
||||
}
|
||||
|
||||
$serverdata = array_merge($nodeinfo, $serverdata);
|
||||
} else {
|
||||
$serverdata = $nodeinfo;
|
||||
}
|
||||
|
|
@ -301,12 +444,7 @@ class GServer
|
|||
$registeredUsers = 1;
|
||||
}
|
||||
|
||||
if ($serverdata['network'] != Protocol::PHANTOM) {
|
||||
$gcontacts = DBA::count('gcontact', ['server_url' => [$url, $serverdata['nurl']]]);
|
||||
$apcontacts = DBA::count('apcontact', ['baseurl' => [$url, $serverdata['nurl']]]);
|
||||
$contacts = DBA::count('contact', ['uid' => 0, 'baseurl' => [$url, $serverdata['nurl']]]);
|
||||
$serverdata['registered-users'] = max($gcontacts, $apcontacts, $contacts, $registeredUsers);
|
||||
} else {
|
||||
if ($serverdata['network'] == Protocol::PHANTOM) {
|
||||
$serverdata['registered-users'] = $registeredUsers;
|
||||
$serverdata = self::detectNetworkViaContacts($url, $serverdata);
|
||||
}
|
||||
|
|
@ -317,6 +455,7 @@ class GServer
|
|||
if (!DBA::isResult($gserver)) {
|
||||
$serverdata['created'] = DateTimeFormat::utcNow();
|
||||
$ret = DBA::insert('gserver', $serverdata);
|
||||
$id = DBA::lastInsertId();
|
||||
} else {
|
||||
// Don't override the network with 'unknown' when there had been a valid entry before
|
||||
if (($serverdata['network'] == Protocol::PHANTOM) && !empty($gserver['network'])) {
|
||||
|
|
@ -324,11 +463,26 @@ class GServer
|
|||
}
|
||||
|
||||
$ret = DBA::update('gserver', $serverdata, ['nurl' => $serverdata['nurl']]);
|
||||
$gserver = DBA::selectFirst('gserver', ['id'], ['nurl' => $serverdata['nurl']]);
|
||||
if (DBA::isResult($gserver)) {
|
||||
$id = $gserver['id'];
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($serverdata['network']) && !empty($id) && ($serverdata['network'] != Protocol::PHANTOM)) {
|
||||
$gcontacts = DBA::count('gcontact', ['gsid' => $id]);
|
||||
$apcontacts = DBA::count('apcontact', ['gsid' => $id]);
|
||||
$contacts = DBA::count('contact', ['uid' => 0, 'gsid' => $id]);
|
||||
$max_users = max($gcontacts, $apcontacts, $contacts, $registeredUsers);
|
||||
if ($max_users > $registeredUsers) {
|
||||
Logger::info('Update registered users', ['id' => $id, 'url' => $serverdata['nurl'], 'registered-users' => $max_users]);
|
||||
DBA::update('gserver', ['registered-users' => $max_users], ['id' => $id]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($serverdata['network']) && in_array($serverdata['network'], [Protocol::DFRN, Protocol::DIASPORA])) {
|
||||
self::discoverRelay($url);
|
||||
}
|
||||
self::discoverRelay($url);
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
|
@ -353,6 +507,15 @@ class GServer
|
|||
return;
|
||||
}
|
||||
|
||||
// Sanitize incoming data, see https://github.com/friendica/friendica/issues/8565
|
||||
$data['subscribe'] = (bool)$data['subscribe'] ?? false;
|
||||
|
||||
if (!$data['subscribe'] || empty($data['scope']) || !in_array(strtolower($data['scope']), ['all', 'tags'])) {
|
||||
$data['scope'] = '';
|
||||
$data['subscribe'] = false;
|
||||
$data['tags'] = [];
|
||||
}
|
||||
|
||||
$gserver = DBA::selectFirst('gserver', ['id', 'relay-subscribe', 'relay-scope'], ['nurl' => Strings::normaliseLink($server_url)]);
|
||||
if (!DBA::isResult($gserver)) {
|
||||
return;
|
||||
|
|
@ -425,7 +588,7 @@ class GServer
|
|||
return [];
|
||||
}
|
||||
|
||||
$serverdata = [];
|
||||
$serverdata = ['detection-method' => self::DETECT_STATISTICS_JSON];
|
||||
|
||||
if (!empty($data['version'])) {
|
||||
$serverdata['version'] = $data['version'];
|
||||
|
|
@ -472,6 +635,10 @@ class GServer
|
|||
*/
|
||||
private static function fetchNodeinfo(string $url, CurlResult $curlResult)
|
||||
{
|
||||
if (!$curlResult->isSuccess()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$nodeinfo = json_decode($curlResult->getBody(), true);
|
||||
|
||||
if (!is_array($nodeinfo) || empty($nodeinfo['links'])) {
|
||||
|
|
@ -522,7 +689,6 @@ class GServer
|
|||
private static function parseNodeinfo1(string $nodeinfo_url)
|
||||
{
|
||||
$curlResult = Network::curl($nodeinfo_url);
|
||||
|
||||
if (!$curlResult->isSuccess()) {
|
||||
return [];
|
||||
}
|
||||
|
|
@ -533,9 +699,8 @@ class GServer
|
|||
return [];
|
||||
}
|
||||
|
||||
$server = [];
|
||||
|
||||
$server['register_policy'] = Register::CLOSED;
|
||||
$server = ['detection-method' => self::DETECT_NODEINFO_1,
|
||||
'register_policy' => Register::CLOSED];
|
||||
|
||||
if (!empty($nodeinfo['openRegistrations'])) {
|
||||
$server['register_policy'] = Register::OPEN;
|
||||
|
|
@ -610,9 +775,8 @@ class GServer
|
|||
return [];
|
||||
}
|
||||
|
||||
$server = [];
|
||||
|
||||
$server['register_policy'] = Register::CLOSED;
|
||||
$server = ['detection-method' => self::DETECT_NODEINFO_2,
|
||||
'register_policy' => Register::CLOSED];
|
||||
|
||||
if (!empty($nodeinfo['openRegistrations'])) {
|
||||
$server['register_policy'] = Register::OPEN;
|
||||
|
|
@ -687,6 +851,10 @@ class GServer
|
|||
return $serverdata;
|
||||
}
|
||||
|
||||
if (in_array($serverdata['detection-method'], [self::DETECT_HEADER, self::DETECT_BODY, self::DETECT_MANUAL])) {
|
||||
$serverdata['detection-method'] = self::DETECT_SITEINFO_JSON;
|
||||
}
|
||||
|
||||
if (!empty($data['url'])) {
|
||||
$serverdata['platform'] = strtolower($data['platform']);
|
||||
$serverdata['version'] = $data['version'];
|
||||
|
|
@ -747,7 +915,7 @@ class GServer
|
|||
return false;
|
||||
}
|
||||
|
||||
$xrd = XML::parseString($curlResult->getBody(), false);
|
||||
$xrd = XML::parseString($curlResult->getBody());
|
||||
if (!is_object($xrd)) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -796,13 +964,13 @@ class GServer
|
|||
DBA::close($gcontacts);
|
||||
|
||||
$apcontacts = DBA::select('apcontact', ['url'], ['baseurl' => [$url, $serverdata['nurl']]]);
|
||||
while ($gcontact = DBA::fetch($gcontacts)) {
|
||||
while ($apcontact = DBA::fetch($apcontacts)) {
|
||||
$contacts[Strings::normaliseLink($apcontact['url'])] = $apcontact['url'];
|
||||
}
|
||||
DBA::close($apcontacts);
|
||||
|
||||
$pcontacts = DBA::select('contact', ['url', 'nurl'], ['uid' => 0, 'baseurl' => [$url, $serverdata['nurl']]]);
|
||||
while ($gcontact = DBA::fetch($gcontacts)) {
|
||||
while ($pcontact = DBA::fetch($pcontacts)) {
|
||||
$contacts[$pcontact['nurl']] = $pcontact['url'];
|
||||
}
|
||||
DBA::close($pcontacts);
|
||||
|
|
@ -896,7 +1064,6 @@ class GServer
|
|||
private static function detectNextcloud(string $url, array $serverdata)
|
||||
{
|
||||
$curlResult = Network::curl($url . '/status.php');
|
||||
|
||||
if (!$curlResult->isSuccess() || ($curlResult->getBody() == '')) {
|
||||
return $serverdata;
|
||||
}
|
||||
|
|
@ -910,6 +1077,10 @@ class GServer
|
|||
$serverdata['platform'] = 'nextcloud';
|
||||
$serverdata['version'] = $data['version'];
|
||||
$serverdata['network'] = Protocol::ACTIVITYPUB;
|
||||
|
||||
if (in_array($serverdata['detection-method'], [self::DETECT_HEADER, self::DETECT_BODY, self::DETECT_MANUAL])) {
|
||||
$serverdata['detection-method'] = self::DETECT_STATUS_PHP;
|
||||
}
|
||||
}
|
||||
|
||||
return $serverdata;
|
||||
|
|
@ -926,7 +1097,6 @@ class GServer
|
|||
private static function detectMastodonAlikes(string $url, array $serverdata)
|
||||
{
|
||||
$curlResult = Network::curl($url . '/api/v1/instance');
|
||||
|
||||
if (!$curlResult->isSuccess() || ($curlResult->getBody() == '')) {
|
||||
return $serverdata;
|
||||
}
|
||||
|
|
@ -936,6 +1106,10 @@ class GServer
|
|||
return $serverdata;
|
||||
}
|
||||
|
||||
if (in_array($serverdata['detection-method'], [self::DETECT_HEADER, self::DETECT_BODY, self::DETECT_MANUAL])) {
|
||||
$serverdata['detection-method'] = self::DETECT_MASTODON_API;
|
||||
}
|
||||
|
||||
if (!empty($data['version'])) {
|
||||
$serverdata['platform'] = 'mastodon';
|
||||
$serverdata['version'] = $data['version'] ?? '';
|
||||
|
|
@ -993,7 +1167,7 @@ class GServer
|
|||
}
|
||||
|
||||
$data = json_decode($curlResult->getBody(), true);
|
||||
if (empty($data)) {
|
||||
if (empty($data) || empty($data['site'])) {
|
||||
return $serverdata;
|
||||
}
|
||||
|
||||
|
|
@ -1041,11 +1215,16 @@ class GServer
|
|||
}
|
||||
|
||||
if (!$closed && !$private and $inviteonly) {
|
||||
$register_policy = Register::APPROVE;
|
||||
$serverdata['register_policy'] = Register::APPROVE;
|
||||
} elseif (!$closed && !$private) {
|
||||
$register_policy = Register::OPEN;
|
||||
$serverdata['register_policy'] = Register::OPEN;
|
||||
} else {
|
||||
$register_policy = Register::CLOSED;
|
||||
$serverdata['register_policy'] = Register::CLOSED;
|
||||
}
|
||||
|
||||
if (!empty($serverdata['network']) && in_array($serverdata['detection-method'],
|
||||
[self::DETECT_HEADER, self::DETECT_BODY, self::DETECT_MANUAL])) {
|
||||
$serverdata['detection-method'] = self::DETECT_CONFIG_JSON;
|
||||
}
|
||||
|
||||
return $serverdata;
|
||||
|
|
@ -1089,6 +1268,11 @@ class GServer
|
|||
$serverdata['version'] = str_replace(["\r", "\n", "\t"], '', $serverdata['version']);
|
||||
$serverdata['version'] = trim($serverdata['version'], '"');
|
||||
$serverdata['network'] = Protocol::OSTATUS;
|
||||
|
||||
if (in_array($serverdata['detection-method'], [self::DETECT_HEADER, self::DETECT_BODY, self::DETECT_MANUAL])) {
|
||||
$serverdata['detection-method'] = self::DETECT_GNUSOCIAL;
|
||||
}
|
||||
|
||||
return $serverdata;
|
||||
}
|
||||
|
||||
|
|
@ -1110,6 +1294,10 @@ class GServer
|
|||
$serverdata['platform'] = 'statusnet';
|
||||
$serverdata['network'] = Protocol::OSTATUS;
|
||||
}
|
||||
|
||||
if (in_array($serverdata['detection-method'], [self::DETECT_HEADER, self::DETECT_BODY, self::DETECT_MANUAL])) {
|
||||
$serverdata['detection-method'] = self::DETECT_STATUSNET;
|
||||
}
|
||||
}
|
||||
|
||||
return $serverdata;
|
||||
|
|
@ -1128,6 +1316,11 @@ class GServer
|
|||
$curlResult = Network::curl($url . '/friendica/json');
|
||||
if (!$curlResult->isSuccess()) {
|
||||
$curlResult = Network::curl($url . '/friendika/json');
|
||||
$friendika = true;
|
||||
$platform = 'Friendika';
|
||||
} else {
|
||||
$friendika = false;
|
||||
$platform = 'Friendica';
|
||||
}
|
||||
|
||||
if (!$curlResult->isSuccess()) {
|
||||
|
|
@ -1139,6 +1332,10 @@ class GServer
|
|||
return $serverdata;
|
||||
}
|
||||
|
||||
if (in_array($serverdata['detection-method'], [self::DETECT_HEADER, self::DETECT_BODY, self::DETECT_MANUAL])) {
|
||||
$serverdata['detection-method'] = $friendika ? self::DETECT_FRIENDIKA : self::DETECT_FRIENDICA;
|
||||
}
|
||||
|
||||
$serverdata['network'] = Protocol::DFRN;
|
||||
$serverdata['version'] = $data['version'];
|
||||
|
||||
|
|
@ -1174,7 +1371,7 @@ class GServer
|
|||
break;
|
||||
}
|
||||
|
||||
$serverdata['platform'] = strtolower($data['platform'] ?? '');
|
||||
$serverdata['platform'] = strtolower($data['platform'] ?? $platform);
|
||||
|
||||
return $serverdata;
|
||||
}
|
||||
|
|
@ -1222,7 +1419,8 @@ class GServer
|
|||
$serverdata['info'] = $attr['content'];
|
||||
}
|
||||
|
||||
if ($attr['name'] == 'application-name') {
|
||||
if (in_array($attr['name'], ['application-name', 'al:android:app_name', 'al:ios:app_name',
|
||||
'twitter:app:name:googleplay', 'twitter:app:name:iphone', 'twitter:app:name:ipad'])) {
|
||||
$serverdata['platform'] = strtolower($attr['content']);
|
||||
if (in_array($attr['content'], ['Misskey', 'Write.as'])) {
|
||||
$serverdata['network'] = Protocol::ACTIVITYPUB;
|
||||
|
|
@ -1243,6 +1441,10 @@ class GServer
|
|||
} else {
|
||||
$serverdata['network'] = Protocol::FEED;
|
||||
}
|
||||
|
||||
if ($serverdata['detection-method'] == self::DETECT_MANUAL) {
|
||||
$serverdata['detection-method'] = self::DETECT_BODY;
|
||||
}
|
||||
}
|
||||
if (in_array($version_part[0], ['Friendika', 'Friendica'])) {
|
||||
$serverdata['platform'] = strtolower($version_part[0]);
|
||||
|
|
@ -1298,6 +1500,10 @@ class GServer
|
|||
}
|
||||
}
|
||||
|
||||
if (!empty($serverdata['network']) && ($serverdata['detection-method'] == self::DETECT_MANUAL)) {
|
||||
$serverdata['detection-method'] = self::DETECT_BODY;
|
||||
}
|
||||
|
||||
return $serverdata;
|
||||
}
|
||||
|
||||
|
|
@ -1313,16 +1519,23 @@ class GServer
|
|||
{
|
||||
if ($curlResult->getHeader('server') == 'Mastodon') {
|
||||
$serverdata['platform'] = 'mastodon';
|
||||
$serverdata['network'] = $network = Protocol::ACTIVITYPUB;
|
||||
$serverdata['network'] = Protocol::ACTIVITYPUB;
|
||||
} elseif ($curlResult->inHeader('x-diaspora-version')) {
|
||||
$serverdata['platform'] = 'diaspora';
|
||||
$serverdata['network'] = $network = Protocol::DIASPORA;
|
||||
$serverdata['network'] = Protocol::DIASPORA;
|
||||
$serverdata['version'] = $curlResult->getHeader('x-diaspora-version');
|
||||
} elseif ($curlResult->inHeader('x-friendica-version')) {
|
||||
$serverdata['platform'] = 'friendica';
|
||||
$serverdata['network'] = $network = Protocol::DFRN;
|
||||
$serverdata['network'] = Protocol::DFRN;
|
||||
$serverdata['version'] = $curlResult->getHeader('x-friendica-version');
|
||||
} else {
|
||||
return $serverdata;
|
||||
}
|
||||
|
||||
if ($serverdata['detection-method'] == self::DETECT_MANUAL) {
|
||||
$serverdata['detection-method'] = self::DETECT_HEADER;
|
||||
}
|
||||
|
||||
return $serverdata;
|
||||
}
|
||||
|
||||
|
|
@ -1414,14 +1627,18 @@ class GServer
|
|||
}
|
||||
|
||||
// Discover federated servers
|
||||
$curlResult = Network::fetchUrl("http://the-federation.info/pods.json");
|
||||
|
||||
if (!empty($curlResult)) {
|
||||
$servers = json_decode($curlResult, true);
|
||||
|
||||
if (!empty($servers['pods'])) {
|
||||
foreach ($servers['pods'] as $server) {
|
||||
Worker::add(PRIORITY_LOW, 'UpdateGServer', 'https://' . $server['host']);
|
||||
$protocols = ['activitypub', 'diaspora', 'dfrn', 'ostatus'];
|
||||
foreach ($protocols as $protocol) {
|
||||
$query = '{nodes(protocol:"' . $protocol . '"){host}}';
|
||||
$curlResult = Network::fetchUrl('https://the-federation.info/graphql?query=' . urlencode($query));
|
||||
if (!empty($curlResult)) {
|
||||
$data = json_decode($curlResult, true);
|
||||
if (!empty($data['data']['nodes'])) {
|
||||
foreach ($data['data']['nodes'] as $server) {
|
||||
// Using "only_nodeinfo" since servers that are listed on that page should always have it.
|
||||
echo $server['host']."\n";
|
||||
Worker::add(PRIORITY_LOW, 'UpdateGServer', 'https://' . $server['host'], true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1433,7 +1650,6 @@ class GServer
|
|||
$api = 'https://instances.social/api/1.0/instances/list?count=0';
|
||||
$header = ['Authorization: Bearer '.$accesstoken];
|
||||
$curlResult = Network::curl($api, false, ['headers' => $header]);
|
||||
|
||||
if ($curlResult->isSuccess()) {
|
||||
$servers = json_decode($curlResult->getBody(), true);
|
||||
|
||||
|
|
|
|||
1585
src/Model/Item.php
1585
src/Model/Item.php
|
|
@ -32,16 +32,15 @@ use Friendica\Core\System;
|
|||
use Friendica\Core\Worker;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\DI;
|
||||
use Friendica\Model\Post\Category;
|
||||
use Friendica\Protocol\Activity;
|
||||
use Friendica\Protocol\ActivityPub;
|
||||
use Friendica\Protocol\Diaspora;
|
||||
use Friendica\Protocol\OStatus;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
use Friendica\Util\Map;
|
||||
use Friendica\Util\Network;
|
||||
use Friendica\Util\Security;
|
||||
use Friendica\Util\Strings;
|
||||
use Friendica\Util\XML;
|
||||
use Friendica\Worker\Delivery;
|
||||
use Text_LanguageDetect;
|
||||
use Friendica\Repository\PermissionSet as RepPermissionSet;
|
||||
|
|
@ -61,7 +60,7 @@ class Item
|
|||
|
||||
// Field list that is used to display the items
|
||||
const DISPLAY_FIELDLIST = [
|
||||
'uid', 'id', 'parent', 'uri', 'thr-parent', 'parent-uri', 'guid', 'network', 'gravity',
|
||||
'uid', 'id', 'parent', 'uri-id', 'uri', 'thr-parent', 'parent-uri', 'guid', 'network', 'gravity',
|
||||
'commented', 'created', 'edited', 'received', 'verb', 'object-type', 'postopts', 'plink',
|
||||
'wall', 'private', 'starred', 'origin', 'title', 'body', 'file', 'attach', 'language',
|
||||
'content-warning', 'location', 'coord', 'app', 'rendered-hash', 'rendered-html', 'object',
|
||||
|
|
@ -77,10 +76,10 @@ class Item
|
|||
];
|
||||
|
||||
// Field list that is used to deliver items via the protocols
|
||||
const DELIVER_FIELDLIST = ['uid', 'id', 'parent', 'uri', 'thr-parent', 'parent-uri', 'guid',
|
||||
const DELIVER_FIELDLIST = ['uid', 'id', 'parent', 'uri-id', 'uri', 'thr-parent', 'parent-uri', 'guid',
|
||||
'parent-guid', 'created', 'edited', 'verb', 'object-type', 'object', 'target',
|
||||
'private', 'title', 'body', 'location', 'coord', 'app',
|
||||
'attach', 'tag', 'deleted', 'extid', 'post-type',
|
||||
'attach', 'deleted', 'extid', 'post-type', 'gravity',
|
||||
'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid',
|
||||
'author-id', 'author-link', 'owner-link', 'contact-uid',
|
||||
'signed_text', 'signature', 'signer', 'network'];
|
||||
|
|
@ -94,10 +93,11 @@ class Item
|
|||
const CONTENT_FIELDLIST = ['language'];
|
||||
|
||||
// All fields in the item table
|
||||
const ITEM_FIELDLIST = ['id', 'uid', 'parent', 'uri', 'parent-uri', 'thr-parent', 'guid',
|
||||
'contact-id', 'type', 'wall', 'gravity', 'extid', 'icid', 'iaid', 'psid',
|
||||
const ITEM_FIELDLIST = ['id', 'uid', 'parent', 'uri', 'parent-uri', 'thr-parent',
|
||||
'guid', 'uri-id', 'parent-uri-id', 'thr-parent-id', 'vid',
|
||||
'contact-id', 'type', 'wall', 'gravity', 'extid', 'icid', 'psid',
|
||||
'created', 'edited', 'commented', 'received', 'changed', 'verb',
|
||||
'postopts', 'plink', 'resource-id', 'event-id', 'tag', 'attach', 'inform',
|
||||
'postopts', 'plink', 'resource-id', 'event-id', 'attach', 'inform',
|
||||
'file', 'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid', 'post-type',
|
||||
'private', 'pubmail', 'moderated', 'visible', 'starred', 'bookmark',
|
||||
'unseen', 'deleted', 'origin', 'forum_mode', 'mention', 'global', 'network',
|
||||
|
|
@ -106,8 +106,8 @@ class Item
|
|||
'author-id', 'author-link', 'author-name', 'author-avatar', 'author-network',
|
||||
'owner-id', 'owner-link', 'owner-name', 'owner-avatar'];
|
||||
|
||||
// List of all verbs that don't need additional content data.
|
||||
// Never reorder or remove entries from this list. Just add new ones at the end, if needed.
|
||||
// The item-activity table only stores the index and needs this array to know the matching activity.
|
||||
const ACTIVITIES = [
|
||||
Activity::LIKE, Activity::DISLIKE,
|
||||
Activity::ATTEND, Activity::ATTENDNO, Activity::ATTENDMAYBE,
|
||||
|
|
@ -203,38 +203,6 @@ class Item
|
|||
return self::selectThreadForUser($uid, $selected, $condition, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* returns an activity index from an activity string
|
||||
*
|
||||
* @param string $activity activity string
|
||||
* @return integer Activity index
|
||||
*/
|
||||
public static function activityToIndex($activity)
|
||||
{
|
||||
$index = array_search($activity, self::ACTIVITIES);
|
||||
|
||||
if (is_bool($index)) {
|
||||
$index = -1;
|
||||
}
|
||||
|
||||
return $index;
|
||||
}
|
||||
|
||||
/**
|
||||
* returns an activity string from an activity index
|
||||
*
|
||||
* @param integer $index activity index
|
||||
* @return string Activity string
|
||||
*/
|
||||
private static function indexToActivity($index)
|
||||
{
|
||||
if (is_null($index) || !array_key_exists($index, self::ACTIVITIES)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return self::ACTIVITIES[$index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a single item row
|
||||
*
|
||||
|
|
@ -282,7 +250,7 @@ class Item
|
|||
|
||||
// Fetch data from the item-content table whenever there is content there
|
||||
if (self::isLegacyMode()) {
|
||||
$legacy_fields = array_merge(ItemDeliveryData::LEGACY_FIELD_LIST, self::MIXED_CONTENT_FIELDLIST);
|
||||
$legacy_fields = array_merge(Post\DeliveryData::LEGACY_FIELD_LIST, self::MIXED_CONTENT_FIELDLIST);
|
||||
foreach ($legacy_fields as $field) {
|
||||
if (empty($row[$field]) && !empty($row['internal-item-' . $field])) {
|
||||
$row[$field] = $row['internal-item-' . $field];
|
||||
|
|
@ -291,39 +259,40 @@ class Item
|
|||
}
|
||||
}
|
||||
|
||||
if (!empty($row['internal-iaid']) && array_key_exists('verb', $row)) {
|
||||
$row['verb'] = self::indexToActivity($row['internal-activity']);
|
||||
if (array_key_exists('title', $row)) {
|
||||
$row['title'] = '';
|
||||
if (array_key_exists('verb', $row)) {
|
||||
if (!is_null($row['internal-verb'])) {
|
||||
$row['verb'] = $row['internal-verb'];
|
||||
}
|
||||
if (array_key_exists('body', $row)) {
|
||||
$row['body'] = $row['verb'];
|
||||
}
|
||||
if (array_key_exists('object', $row)) {
|
||||
$row['object'] = '';
|
||||
}
|
||||
if (array_key_exists('object-type', $row)) {
|
||||
$row['object-type'] = Activity\ObjectType::NOTE;
|
||||
}
|
||||
} elseif (array_key_exists('verb', $row) && in_array($row['verb'], ['', Activity::POST, Activity::SHARE])) {
|
||||
// Posts don't have a target - but having tags or files.
|
||||
// We safe some performance by building tag and file strings only here.
|
||||
// We remove the target since they aren't used for this type.
|
||||
// In mail posts we do store some mail header data in the object.
|
||||
if (array_key_exists('target', $row)) {
|
||||
$row['target'] = '';
|
||||
|
||||
if (in_array($row['verb'], self::ACTIVITIES)) {
|
||||
if (array_key_exists('title', $row)) {
|
||||
$row['title'] = '';
|
||||
}
|
||||
if (array_key_exists('body', $row)) {
|
||||
$row['body'] = $row['verb'];
|
||||
}
|
||||
if (array_key_exists('object', $row)) {
|
||||
$row['object'] = '';
|
||||
}
|
||||
if (array_key_exists('object-type', $row)) {
|
||||
$row['object-type'] = Activity\ObjectType::NOTE;
|
||||
}
|
||||
} elseif (in_array($row['verb'], ['', Activity::POST, Activity::SHARE])) {
|
||||
// Posts don't have a target - but having tags or files.
|
||||
if (array_key_exists('target', $row)) {
|
||||
$row['target'] = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (array_key_exists('vid', $row) && is_null($row['vid']) && !empty($row['verb'])) {
|
||||
$row['vid'] = Verb::getID($row['verb']);
|
||||
}
|
||||
|
||||
if (!array_key_exists('verb', $row) || in_array($row['verb'], ['', Activity::POST, Activity::SHARE])) {
|
||||
// Build the tag string out of the term entries
|
||||
if (array_key_exists('tag', $row) && empty($row['tag'])) {
|
||||
$row['tag'] = Term::tagTextFromItemId($row['internal-iid']);
|
||||
}
|
||||
|
||||
// Build the file string out of the term entries
|
||||
if (array_key_exists('file', $row) && empty($row['file'])) {
|
||||
$row['file'] = Term::fileTextFromItemId($row['internal-iid']);
|
||||
$row['file'] = Category::getTextByURIId($row['internal-uri-id'], $row['internal-uid']);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -342,20 +311,16 @@ class Item
|
|||
}
|
||||
}
|
||||
|
||||
if (array_key_exists('signed_text', $row) && array_key_exists('interaction', $row) && !is_null($row['interaction'])) {
|
||||
$row['signed_text'] = $row['interaction'];
|
||||
}
|
||||
|
||||
if (array_key_exists('ignored', $row) && array_key_exists('internal-user-ignored', $row) && !is_null($row['internal-user-ignored'])) {
|
||||
$row['ignored'] = $row['internal-user-ignored'];
|
||||
}
|
||||
|
||||
// Remove internal fields
|
||||
unset($row['internal-activity']);
|
||||
unset($row['internal-network']);
|
||||
unset($row['internal-iid']);
|
||||
unset($row['internal-uri-id']);
|
||||
unset($row['internal-uid']);
|
||||
unset($row['internal-psid']);
|
||||
unset($row['internal-iaid']);
|
||||
unset($row['internal-verb']);
|
||||
unset($row['internal-user-ignored']);
|
||||
unset($row['interaction']);
|
||||
|
||||
|
|
@ -672,24 +637,26 @@ class Item
|
|||
{
|
||||
$fields = [];
|
||||
|
||||
$fields['item'] = ['id', 'uid', 'parent', 'uri', 'parent-uri', 'thr-parent', 'guid',
|
||||
$fields['item'] = ['id', 'uid', 'parent', 'uri', 'parent-uri', 'thr-parent',
|
||||
'guid', 'uri-id', 'parent-uri-id', 'thr-parent-id', 'vid',
|
||||
'contact-id', 'owner-id', 'author-id', 'type', 'wall', 'gravity', 'extid',
|
||||
'created', 'edited', 'commented', 'received', 'changed', 'psid',
|
||||
'resource-id', 'event-id', 'tag', 'attach', 'post-type', 'file',
|
||||
'resource-id', 'event-id', 'attach', 'post-type', 'file',
|
||||
'private', 'pubmail', 'moderated', 'visible', 'starred', 'bookmark',
|
||||
'unseen', 'deleted', 'origin', 'forum_mode', 'mention', 'global',
|
||||
'id' => 'item_id', 'network', 'icid', 'iaid', 'id' => 'internal-iid',
|
||||
'network' => 'internal-network', 'iaid' => 'internal-iaid', 'psid' => 'internal-psid'];
|
||||
'id' => 'item_id', 'network', 'icid',
|
||||
'uri-id' => 'internal-uri-id', 'uid' => 'internal-uid',
|
||||
'network' => 'internal-network', 'psid' => 'internal-psid'];
|
||||
|
||||
if ($usermode) {
|
||||
$fields['user-item'] = ['pinned', 'notification-type', 'ignored' => 'internal-user-ignored'];
|
||||
}
|
||||
|
||||
$fields['item-activity'] = ['activity', 'activity' => 'internal-activity'];
|
||||
|
||||
$fields['item-content'] = array_merge(self::CONTENT_FIELDLIST, self::MIXED_CONTENT_FIELDLIST);
|
||||
|
||||
$fields['item-delivery-data'] = array_merge(ItemDeliveryData::LEGACY_FIELD_LIST, ItemDeliveryData::FIELD_LIST);
|
||||
$fields['post-delivery-data'] = array_merge(Post\DeliveryData::LEGACY_FIELD_LIST, Post\DeliveryData::FIELD_LIST);
|
||||
|
||||
$fields['verb'] = ['name' => 'internal-verb'];
|
||||
|
||||
$fields['permissionset'] = ['allow_cid', 'allow_gid', 'deny_cid', 'deny_gid'];
|
||||
|
||||
|
|
@ -705,7 +672,8 @@ class Item
|
|||
|
||||
$fields['parent-item'] = ['guid' => 'parent-guid', 'network' => 'parent-network'];
|
||||
|
||||
$fields['parent-item-author'] = ['url' => 'parent-author-link', 'name' => 'parent-author-name'];
|
||||
$fields['parent-item-author'] = ['url' => 'parent-author-link', 'name' => 'parent-author-name',
|
||||
'network' => 'parent-author-network'];
|
||||
|
||||
$fields['event'] = ['created' => 'event-created', 'edited' => 'event-edited',
|
||||
'start' => 'event-start','finish' => 'event-finish',
|
||||
|
|
@ -714,9 +682,7 @@ class Item
|
|||
'nofinish' => 'event-nofinish','adjust' => 'event-adjust',
|
||||
'ignore' => 'event-ignore', 'id' => 'event-id'];
|
||||
|
||||
$fields['sign'] = ['signed_text', 'signature', 'signer'];
|
||||
|
||||
$fields['diaspora-interaction'] = ['interaction'];
|
||||
$fields['diaspora-interaction'] = ['interaction', 'interaction' => 'signed_text'];
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
|
@ -801,36 +767,32 @@ class Item
|
|||
$joins .= " LEFT JOIN `event` ON `event-id` = `event`.`id`";
|
||||
}
|
||||
|
||||
if (strpos($sql_commands, "`sign`.") !== false) {
|
||||
$joins .= " LEFT JOIN `sign` ON `sign`.`iid` = `item`.`id`";
|
||||
}
|
||||
|
||||
if (strpos($sql_commands, "`diaspora-interaction`.") !== false) {
|
||||
$joins .= " LEFT JOIN `diaspora-interaction` ON `diaspora-interaction`.`uri-id` = `item`.`uri-id`";
|
||||
}
|
||||
|
||||
if (strpos($sql_commands, "`item-activity`.") !== false) {
|
||||
$joins .= " LEFT JOIN `item-activity` ON `item-activity`.`uri-id` = `item`.`uri-id`";
|
||||
}
|
||||
|
||||
if (strpos($sql_commands, "`item-content`.") !== false) {
|
||||
$joins .= " LEFT JOIN `item-content` ON `item-content`.`uri-id` = `item`.`uri-id`";
|
||||
}
|
||||
|
||||
if (strpos($sql_commands, "`item-delivery-data`.") !== false) {
|
||||
$joins .= " LEFT JOIN `item-delivery-data` ON `item-delivery-data`.`iid` = `item`.`id`";
|
||||
if (strpos($sql_commands, "`post-delivery-data`.") !== false) {
|
||||
$joins .= " LEFT JOIN `post-delivery-data` ON `post-delivery-data`.`uri-id` = `item`.`uri-id` AND `item`.`origin`";
|
||||
}
|
||||
|
||||
if (strpos($sql_commands, "`verb`.") !== false) {
|
||||
$joins .= " LEFT JOIN `verb` ON `verb`.`id` = `item`.`vid`";
|
||||
}
|
||||
|
||||
if (strpos($sql_commands, "`permissionset`.") !== false) {
|
||||
$joins .= " LEFT JOIN `permissionset` ON `permissionset`.`id` = `item`.`psid`";
|
||||
}
|
||||
|
||||
if ((strpos($sql_commands, "`parent-item`.") !== false) || (strpos($sql_commands, "`parent-author`.") !== false)) {
|
||||
if ((strpos($sql_commands, "`parent-item`.") !== false) || (strpos($sql_commands, "`parent-item-author`.") !== false)) {
|
||||
$joins .= " STRAIGHT_JOIN `item` AS `parent-item` ON `parent-item`.`id` = `item`.`parent`";
|
||||
}
|
||||
|
||||
if (strpos($sql_commands, "`parent-item-author`.") !== false) {
|
||||
$joins .= " STRAIGHT_JOIN `contact` AS `parent-item-author` ON `parent-item-author`.`id` = `parent-item`.`author-id`";
|
||||
if (strpos($sql_commands, "`parent-item-author`.") !== false) {
|
||||
$joins .= " STRAIGHT_JOIN `contact` AS `parent-item-author` ON `parent-item-author`.`id` = `parent-item`.`author-id`";
|
||||
}
|
||||
}
|
||||
|
||||
return $joins;
|
||||
|
|
@ -847,22 +809,18 @@ class Item
|
|||
private static function constructSelectFields(array $fields, array $selected)
|
||||
{
|
||||
if (!empty($selected)) {
|
||||
$selected = array_merge($selected, ['internal-iid', 'internal-psid', 'internal-iaid', 'internal-network']);
|
||||
$selected = array_merge($selected, ['internal-uri-id', 'internal-uid', 'internal-psid', 'internal-network']);
|
||||
}
|
||||
|
||||
if (in_array('verb', $selected)) {
|
||||
$selected[] = 'internal-activity';
|
||||
$selected = array_merge($selected, ['internal-verb']);
|
||||
}
|
||||
|
||||
if (in_array('ignored', $selected)) {
|
||||
$selected[] = 'internal-user-ignored';
|
||||
}
|
||||
|
||||
if (in_array('signed_text', $selected)) {
|
||||
$selected[] = 'interaction';
|
||||
}
|
||||
|
||||
$legacy_fields = array_merge(ItemDeliveryData::LEGACY_FIELD_LIST, self::MIXED_CONTENT_FIELDLIST);
|
||||
$legacy_fields = array_merge(Post\DeliveryData::LEGACY_FIELD_LIST, self::MIXED_CONTENT_FIELDLIST);
|
||||
|
||||
$selection = [];
|
||||
foreach ($fields as $table => $table_fields) {
|
||||
|
|
@ -934,7 +892,7 @@ class Item
|
|||
// We cannot simply expand the condition to check for origin entries
|
||||
// The condition needn't to be a simple array but could be a complex condition.
|
||||
// And we have to execute this query before the update to ensure to fetch the same data.
|
||||
$items = DBA::select('item', ['id', 'origin', 'uri', 'uri-id', 'iaid', 'icid', 'tag', 'file'], $condition);
|
||||
$items = DBA::select('item', ['id', 'origin', 'uri', 'uri-id', 'icid', 'uid', 'file'], $condition);
|
||||
|
||||
$content_fields = [];
|
||||
foreach (array_merge(self::CONTENT_FIELDLIST, self::MIXED_CONTENT_FIELDLIST) as $field) {
|
||||
|
|
@ -948,7 +906,7 @@ class Item
|
|||
}
|
||||
}
|
||||
|
||||
$delivery_data = ItemDeliveryData::extractFields($fields);
|
||||
$delivery_data = Post\DeliveryData::extractFields($fields);
|
||||
|
||||
$clear_fields = ['bookmark', 'type', 'author-name', 'author-avatar', 'author-link', 'owner-name', 'owner-avatar', 'owner-link', 'postopts', 'inform'];
|
||||
foreach ($clear_fields as $field) {
|
||||
|
|
@ -957,13 +915,6 @@ class Item
|
|||
}
|
||||
}
|
||||
|
||||
if (array_key_exists('tag', $fields)) {
|
||||
$tags = $fields['tag'];
|
||||
$fields['tag'] = null;
|
||||
} else {
|
||||
$tags = null;
|
||||
}
|
||||
|
||||
if (array_key_exists('file', $fields)) {
|
||||
$files = $fields['file'];
|
||||
$fields['file'] = null;
|
||||
|
|
@ -971,6 +922,10 @@ class Item
|
|||
$files = null;
|
||||
}
|
||||
|
||||
if (!empty($content_fields['verb'])) {
|
||||
$fields['vid'] = Verb::getID($content_fields['verb']);
|
||||
}
|
||||
|
||||
if (!empty($fields)) {
|
||||
$success = DBA::update('item', $fields, $condition);
|
||||
|
||||
|
|
@ -987,34 +942,7 @@ class Item
|
|||
$notify_items = [];
|
||||
|
||||
while ($item = DBA::fetch($items)) {
|
||||
if (!empty($item['iaid']) || (!empty($content_fields['verb']) && (self::activityToIndex($content_fields['verb']) >= 0))) {
|
||||
self::updateActivity($content_fields, ['uri-id' => $item['uri-id']]);
|
||||
|
||||
if (empty($item['iaid'])) {
|
||||
$item_activity = DBA::selectFirst('item-activity', ['id'], ['uri-id' => $item['uri-id']]);
|
||||
if (DBA::isResult($item_activity)) {
|
||||
$item_fields = ['iaid' => $item_activity['id'], 'icid' => null];
|
||||
foreach (self::MIXED_CONTENT_FIELDLIST as $field) {
|
||||
if (self::isLegacyMode()) {
|
||||
$item_fields[$field] = null;
|
||||
} else {
|
||||
unset($item_fields[$field]);
|
||||
}
|
||||
}
|
||||
DBA::update('item', $item_fields, ['id' => $item['id']]);
|
||||
|
||||
if (!empty($item['icid']) && !DBA::exists('item', ['icid' => $item['icid']])) {
|
||||
DBA::delete('item-content', ['id' => $item['icid']]);
|
||||
}
|
||||
}
|
||||
} elseif (!empty($item['icid'])) {
|
||||
DBA::update('item', ['icid' => null], ['id' => $item['id']]);
|
||||
|
||||
if (!DBA::exists('item', ['icid' => $item['icid']])) {
|
||||
DBA::delete('item-content', ['id' => $item['icid']]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (empty($content_fields['verb']) || !in_array($content_fields['verb'], self::ACTIVITIES)) {
|
||||
self::updateContent($content_fields, ['uri-id' => $item['uri-id']]);
|
||||
|
||||
if (empty($item['icid'])) {
|
||||
|
|
@ -1022,12 +950,10 @@ class Item
|
|||
if (DBA::isResult($item_content)) {
|
||||
$item_fields = ['icid' => $item_content['id']];
|
||||
// Clear all fields in the item table that have a content in the item-content table
|
||||
foreach ($item_content as $field => $content) {
|
||||
if (in_array($field, self::MIXED_CONTENT_FIELDLIST) && !empty($item_content[$field])) {
|
||||
if (self::isLegacyMode()) {
|
||||
if (self::isLegacyMode()) {
|
||||
foreach ($item_content as $field => $content) {
|
||||
if (in_array($field, self::MIXED_CONTENT_FIELDLIST) && !empty($content)) {
|
||||
$item_fields[$field] = null;
|
||||
} else {
|
||||
unset($item_fields[$field]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1036,21 +962,14 @@ class Item
|
|||
}
|
||||
}
|
||||
|
||||
if (!is_null($tags)) {
|
||||
Term::insertFromTagFieldByItemId($item['id'], $tags);
|
||||
if (!empty($item['tag'])) {
|
||||
DBA::update('item', ['tag' => ''], ['id' => $item['id']]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_null($files)) {
|
||||
Term::insertFromFileFieldByItemId($item['id'], $files);
|
||||
Category::storeTextByURIId($item['uri-id'], $item['uid'], $files);
|
||||
if (!empty($item['file'])) {
|
||||
DBA::update('item', ['file' => ''], ['id' => $item['id']]);
|
||||
}
|
||||
}
|
||||
|
||||
ItemDeliveryData::update($item['id'], $delivery_data);
|
||||
Post\DeliveryData::update($item['uri-id'], $delivery_data);
|
||||
|
||||
self::updateThread($item['id']);
|
||||
|
||||
|
|
@ -1130,10 +1049,10 @@ class Item
|
|||
{
|
||||
Logger::info('Mark item for deletion by id', ['id' => $item_id, 'callstack' => System::callstack()]);
|
||||
// locate item to be deleted
|
||||
$fields = ['id', 'uri', 'uid', 'parent', 'parent-uri', 'origin',
|
||||
$fields = ['id', 'uri', 'uri-id', 'uid', 'parent', 'parent-uri', 'origin',
|
||||
'deleted', 'file', 'resource-id', 'event-id', 'attach',
|
||||
'verb', 'object-type', 'object', 'target', 'contact-id',
|
||||
'icid', 'iaid', 'psid'];
|
||||
'icid', 'psid', 'gravity'];
|
||||
$item = self::selectFirst($fields, ['id' => $item_id]);
|
||||
if (!DBA::isResult($item)) {
|
||||
Logger::info('Item not found.', ['id' => $item_id]);
|
||||
|
|
@ -1152,7 +1071,7 @@ class Item
|
|||
|
||||
// clean up categories and tags so they don't end up as orphans
|
||||
|
||||
$matches = false;
|
||||
$matches = [];
|
||||
$cnt = preg_match_all('/<(.*?)>/', $item['file'], $matches, PREG_SET_ORDER);
|
||||
|
||||
if ($cnt) {
|
||||
|
|
@ -1161,7 +1080,7 @@ class Item
|
|||
}
|
||||
}
|
||||
|
||||
$matches = false;
|
||||
$matches = [];
|
||||
|
||||
$cnt = preg_match_all('/\[(.*?)\]/', $item['file'], $matches, PREG_SET_ORDER);
|
||||
|
||||
|
|
@ -1196,9 +1115,6 @@ class Item
|
|||
}
|
||||
}
|
||||
|
||||
// Delete tags that had been attached to other items
|
||||
self::deleteTagsFromItem($item);
|
||||
|
||||
// Delete notifications
|
||||
DBA::delete('notify', ['iid' => $item['id'], 'uid' => $item['uid']]);
|
||||
|
||||
|
|
@ -1206,17 +1122,14 @@ class Item
|
|||
$item_fields = ['deleted' => true, 'edited' => DateTimeFormat::utcNow(), 'changed' => DateTimeFormat::utcNow()];
|
||||
DBA::update('item', $item_fields, ['id' => $item['id']]);
|
||||
|
||||
Term::insertFromTagFieldByItemId($item['id'], '');
|
||||
Term::insertFromFileFieldByItemId($item['id'], '');
|
||||
Category::storeTextByURIId($item['uri-id'], $item['uid'], '');
|
||||
self::deleteThread($item['id'], $item['parent-uri']);
|
||||
|
||||
if (!self::exists(["`uri` = ? AND `uid` != 0 AND NOT `deleted`", $item['uri']])) {
|
||||
self::markForDeletion(['uri' => $item['uri'], 'uid' => 0, 'deleted' => false], $priority);
|
||||
}
|
||||
|
||||
ItemDeliveryData::delete($item['id']);
|
||||
|
||||
// We don't delete the item-activity here, since we need some of the data for ActivityPub
|
||||
Post\DeliveryData::delete($item['uri-id']);
|
||||
|
||||
if (!empty($item['icid']) && !self::exists(['icid' => $item['icid'], 'deleted' => false])) {
|
||||
DBA::delete('item-content', ['id' => $item['icid']], ['cascade' => false]);
|
||||
|
|
@ -1230,7 +1143,7 @@ class Item
|
|||
//}
|
||||
|
||||
// If it's the parent of a comment thread, kill all the kids
|
||||
if ($item['id'] == $item['parent']) {
|
||||
if ($item['gravity'] == GRAVITY_PARENT) {
|
||||
self::markForDeletion(['parent' => $item['parent'], 'deleted' => false], $priority);
|
||||
}
|
||||
|
||||
|
|
@ -1255,43 +1168,6 @@ class Item
|
|||
return true;
|
||||
}
|
||||
|
||||
private static function deleteTagsFromItem($item)
|
||||
{
|
||||
if (($item["verb"] != Activity::TAG) || ($item["object-type"] != Activity\ObjectType::TAGTERM)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$xo = XML::parseString($item["object"], false);
|
||||
$xt = XML::parseString($item["target"], false);
|
||||
|
||||
if ($xt->type != Activity\ObjectType::NOTE) {
|
||||
return;
|
||||
}
|
||||
|
||||
$i = self::selectFirst(['id', 'contact-id', 'tag'], ['uri' => $xt->id, 'uid' => $item['uid']]);
|
||||
if (!DBA::isResult($i)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// For tags, the owner cannot remove the tag on the author's copy of the post.
|
||||
$owner_remove = ($item["contact-id"] == $i["contact-id"]);
|
||||
$author_copy = $item["origin"];
|
||||
|
||||
if (($owner_remove && $author_copy) || !$owner_remove) {
|
||||
return;
|
||||
}
|
||||
|
||||
$tags = explode(',', $i["tag"]);
|
||||
$newtags = [];
|
||||
if (count($tags)) {
|
||||
foreach ($tags as $tag) {
|
||||
if (trim($tag) !== trim($xo->body)) {
|
||||
$newtags[] = trim($tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
self::update(['tag' => implode(',', $newtags)], ['id' => $i["id"]]);
|
||||
}
|
||||
|
||||
private static function guid($item, $notify)
|
||||
{
|
||||
|
|
@ -1402,7 +1278,299 @@ class Item
|
|||
}
|
||||
}
|
||||
|
||||
public static function insert($item, $force_parent = false, $notify = false, $dontcache = false)
|
||||
/**
|
||||
* Check if the item array is a duplicate
|
||||
*
|
||||
* @param array $item
|
||||
* @return boolean is it a duplicate?
|
||||
*/
|
||||
private static function isDuplicate(array $item)
|
||||
{
|
||||
// Checking if there is already an item with the same guid
|
||||
$condition = ['guid' => $item['guid'], 'network' => $item['network'], 'uid' => $item['uid']];
|
||||
if (self::exists($condition)) {
|
||||
Logger::notice('Found already existing item', [
|
||||
'guid' => $item['guid'],
|
||||
'uid' => $item['uid'],
|
||||
'network' => $item['network']
|
||||
]);
|
||||
return true;
|
||||
}
|
||||
|
||||
$condition = ["`uri` = ? AND `network` IN (?, ?) AND `uid` = ?",
|
||||
$item['uri'], $item['network'], Protocol::DFRN, $item['uid']];
|
||||
if (self::exists($condition)) {
|
||||
Logger::notice('duplicated item with the same uri found.', $item);
|
||||
return true;
|
||||
}
|
||||
|
||||
// On Friendica and Diaspora the GUID is unique
|
||||
if (in_array($item['network'], [Protocol::DFRN, Protocol::DIASPORA])) {
|
||||
$condition = ['guid' => $item['guid'], 'uid' => $item['uid']];
|
||||
if (self::exists($condition)) {
|
||||
Logger::notice('duplicated item with the same guid found.', $item);
|
||||
return true;
|
||||
}
|
||||
} elseif ($item['network'] == Protocol::OSTATUS) {
|
||||
// Check for an existing post with the same content. There seems to be a problem with OStatus.
|
||||
$condition = ["`body` = ? AND `network` = ? AND `created` = ? AND `contact-id` = ? AND `uid` = ?",
|
||||
$item['body'], $item['network'], $item['created'], $item['contact-id'], $item['uid']];
|
||||
if (self::exists($condition)) {
|
||||
Logger::notice('duplicated item with the same body found.', $item);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Check for already added items.
|
||||
* There is a timing issue here that sometimes creates double postings.
|
||||
* An unique index would help - but the limitations of MySQL (maximum size of index values) prevent this.
|
||||
*/
|
||||
if (($item['uid'] == 0) && self::exists(['uri' => trim($item['uri']), 'uid' => 0])) {
|
||||
Logger::notice('Global item already stored.', ['uri' => $item['uri'], 'network' => $item['network']]);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the item array is valid
|
||||
*
|
||||
* @param array $item
|
||||
* @return boolean item is valid
|
||||
*/
|
||||
private static function isValid(array $item)
|
||||
{
|
||||
// When there is no content then we don't post it
|
||||
if ($item['body'].$item['title'] == '') {
|
||||
Logger::notice('No body, no title.');
|
||||
return false;
|
||||
}
|
||||
|
||||
// check for create date and expire time
|
||||
$expire_interval = DI::config()->get('system', 'dbclean-expire-days', 0);
|
||||
|
||||
$user = DBA::selectFirst('user', ['expire'], ['uid' => $item['uid']]);
|
||||
if (DBA::isResult($user) && ($user['expire'] > 0) && (($user['expire'] < $expire_interval) || ($expire_interval == 0))) {
|
||||
$expire_interval = $user['expire'];
|
||||
}
|
||||
|
||||
if (($expire_interval > 0) && !empty($item['created'])) {
|
||||
$expire_date = time() - ($expire_interval * 86400);
|
||||
$created_date = strtotime($item['created']);
|
||||
if ($created_date < $expire_date) {
|
||||
Logger::notice('Item created before expiration interval.', [
|
||||
'created' => date('c', $created_date),
|
||||
'expired' => date('c', $expire_date),
|
||||
'$item' => $item
|
||||
]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (Contact::isBlocked($item['author-id'])) {
|
||||
Logger::notice('Author is blocked node-wide', ['author-link' => $item['author-link'], 'item-uri' => $item['uri']]);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!empty($item['author-link']) && Network::isUrlBlocked($item['author-link'])) {
|
||||
Logger::notice('Author server is blocked', ['author-link' => $item['author-link'], 'item-uri' => $item['uri']]);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!empty($item['uid']) && Contact::isBlockedByUser($item['author-id'], $item['uid'])) {
|
||||
Logger::notice('Author is blocked by user', ['author-link' => $item['author-link'], 'uid' => $item['uid'], 'item-uri' => $item['uri']]);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Contact::isBlocked($item['owner-id'])) {
|
||||
Logger::notice('Owner is blocked node-wide', ['owner-link' => $item['owner-link'], 'item-uri' => $item['uri']]);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!empty($item['owner-link']) && Network::isUrlBlocked($item['owner-link'])) {
|
||||
Logger::notice('Owner server is blocked', ['owner-link' => $item['owner-link'], 'item-uri' => $item['uri']]);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!empty($item['uid']) && Contact::isBlockedByUser($item['owner-id'], $item['uid'])) {
|
||||
Logger::notice('Owner is blocked by user', ['owner-link' => $item['owner-link'], 'uid' => $item['uid'], 'item-uri' => $item['uri']]);
|
||||
return false;
|
||||
}
|
||||
|
||||
// The causer is set during a thread completion, for example because of a reshare. It countains the responsible actor.
|
||||
if (!empty($item['uid']) && !empty($item['causer-id']) && Contact::isBlockedByUser($item['causer-id'], $item['uid'])) {
|
||||
Logger::notice('Causer is blocked by user', ['causer-link' => $item['causer-link'], 'uid' => $item['uid'], 'item-uri' => $item['uri']]);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!empty($item['uid']) && !empty($item['causer-id']) && ($item['parent-uri'] == $item['uri']) && Contact::isIgnoredByUser($item['causer-id'], $item['uid'])) {
|
||||
Logger::notice('Causer is ignored by user', ['causer-link' => $item['causer-link'], 'uid' => $item['uid'], 'item-uri' => $item['uri']]);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($item['verb'] == Activity::FOLLOW) {
|
||||
if (!$item['origin'] && ($item['author-id'] == Contact::getPublicIdByUserId($item['uid']))) {
|
||||
// Our own follow request can be relayed to us. We don't store it to avoid notification chaos.
|
||||
Logger::info("Follow: Don't store not origin follow request", ['parent-uri' => $item['parent-uri']]);
|
||||
return false;
|
||||
}
|
||||
|
||||
$condition = ['verb' => Activity::FOLLOW, 'uid' => $item['uid'],
|
||||
'parent-uri' => $item['parent-uri'], 'author-id' => $item['author-id']];
|
||||
if (self::exists($condition)) {
|
||||
// It happens that we receive multiple follow requests by the same author - we only store one.
|
||||
Logger::info('Follow: Found existing follow request from author', ['author-id' => $item['author-id'], 'parent-uri' => $item['parent-uri']]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the id of the given item array if it has been stored before
|
||||
*
|
||||
* @param array $item
|
||||
* @return integer item id
|
||||
*/
|
||||
private static function getDuplicateID(array $item)
|
||||
{
|
||||
if (empty($item['network']) || in_array($item['network'], Protocol::FEDERATED)) {
|
||||
$condition = ["`uri` = ? AND `uid` = ? AND `network` IN (?, ?, ?, ?)",
|
||||
trim($item['uri']), $item['uid'],
|
||||
Protocol::ACTIVITYPUB, Protocol::DIASPORA, Protocol::DFRN, Protocol::OSTATUS];
|
||||
$existing = self::selectFirst(['id', 'network'], $condition);
|
||||
if (DBA::isResult($existing)) {
|
||||
// We only log the entries with a different user id than 0. Otherwise we would have too many false positives
|
||||
if ($item['uid'] != 0) {
|
||||
Logger::notice('Item already existed for user', [
|
||||
'uri' => $item['uri'],
|
||||
'uid' => $item['uid'],
|
||||
'network' => $item['network'],
|
||||
'existing_id' => $existing["id"],
|
||||
'existing_network' => $existing["network"]
|
||||
]);
|
||||
}
|
||||
|
||||
return $existing["id"];
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch parent data for the given item array
|
||||
*
|
||||
* @param array $item
|
||||
* @return array item array with parent data
|
||||
*/
|
||||
private static function getParentData(array $item)
|
||||
{
|
||||
// find the parent and snarf the item id and ACLs
|
||||
// and anything else we need to inherit
|
||||
|
||||
$fields = ['uri', 'parent-uri', 'id', 'deleted',
|
||||
'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid',
|
||||
'wall', 'private', 'forum_mode', 'origin', 'author-id'];
|
||||
$condition = ['uri' => $item['parent-uri'], 'uid' => $item['uid']];
|
||||
$params = ['order' => ['id' => false]];
|
||||
$parent = self::selectFirst($fields, $condition, $params);
|
||||
|
||||
if (!DBA::isResult($parent)) {
|
||||
Logger::info('item parent was not found - ignoring item', ['parent-uri' => $item['parent-uri'], 'uid' => $item['uid']]);
|
||||
return [];
|
||||
} else {
|
||||
// is the new message multi-level threaded?
|
||||
// even though we don't support it now, preserve the info
|
||||
// and re-attach to the conversation parent.
|
||||
if ($parent['uri'] != $parent['parent-uri']) {
|
||||
$item['parent-uri'] = $parent['parent-uri'];
|
||||
|
||||
$condition = ['uri' => $item['parent-uri'],
|
||||
'parent-uri' => $item['parent-uri'],
|
||||
'uid' => $item['uid']];
|
||||
$params = ['order' => ['id' => false]];
|
||||
$toplevel_parent = self::selectFirst($fields, $condition, $params);
|
||||
|
||||
if (DBA::isResult($toplevel_parent)) {
|
||||
$parent = $toplevel_parent;
|
||||
}
|
||||
}
|
||||
|
||||
$item['parent'] = $parent['id'];
|
||||
$item["deleted"] = $parent['deleted'];
|
||||
$item["allow_cid"] = $parent['allow_cid'];
|
||||
$item['allow_gid'] = $parent['allow_gid'];
|
||||
$item['deny_cid'] = $parent['deny_cid'];
|
||||
$item['deny_gid'] = $parent['deny_gid'];
|
||||
$item['parent_origin'] = $parent['origin'];
|
||||
|
||||
// Don't federate received participation messages
|
||||
if ($item['verb'] != Activity::FOLLOW) {
|
||||
$item['wall'] = $parent['wall'];
|
||||
} else {
|
||||
$item['wall'] = false;
|
||||
}
|
||||
|
||||
/*
|
||||
* If the parent is private, force privacy for the entire conversation
|
||||
* This differs from the above settings as it subtly allows comments from
|
||||
* email correspondents to be private even if the overall thread is not.
|
||||
*/
|
||||
if ($parent['private']) {
|
||||
$item['private'] = $parent['private'];
|
||||
}
|
||||
|
||||
/*
|
||||
* Edge case. We host a public forum that was originally posted to privately.
|
||||
* The original author commented, but as this is a comment, the permissions
|
||||
* weren't fixed up so it will still show the comment as private unless we fix it here.
|
||||
*/
|
||||
if ((intval($parent['forum_mode']) == 1) && ($parent['private'] != self::PUBLIC)) {
|
||||
$item['private'] = self::PUBLIC;
|
||||
}
|
||||
|
||||
// If its a post that originated here then tag the thread as "mention"
|
||||
if ($item['origin'] && $item['uid']) {
|
||||
DBA::update('thread', ['mention' => true], ['iid' => $item['parent']]);
|
||||
Logger::info('tagged thread as mention', ['parent' => $item['parent'], 'uid' => $item['uid']]);
|
||||
}
|
||||
|
||||
// Update the contact relations
|
||||
if ($item['author-id'] != $parent['author-id']) {
|
||||
DBA::update('contact-relation', ['last-interaction' => $item['created']], ['cid' => $parent['author-id'], 'relation-cid' => $item['author-id']], true);
|
||||
}
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the gravity for the given item array
|
||||
*
|
||||
* @param array $item
|
||||
* @return integer gravity
|
||||
*/
|
||||
private static function getGravity(array $item)
|
||||
{
|
||||
$activity = DI::activity();
|
||||
|
||||
if (isset($item['gravity'])) {
|
||||
return intval($item['gravity']);
|
||||
} elseif ($item['parent-uri'] === $item['uri']) {
|
||||
return GRAVITY_PARENT;
|
||||
} elseif ($activity->match($item['verb'], Activity::POST)) {
|
||||
return GRAVITY_COMMENT;
|
||||
} elseif ($activity->match($item['verb'], Activity::FOLLOW)) {
|
||||
return GRAVITY_ACTIVITY;
|
||||
}
|
||||
Logger::info('Unknown gravity for verb', ['verb' => $item['verb']]);
|
||||
return GRAVITY_UNKNOWN; // Should not happen
|
||||
}
|
||||
|
||||
public static function insert($item, $notify = false, $dontcache = false)
|
||||
{
|
||||
$orig_item = $item;
|
||||
|
||||
|
|
@ -1422,6 +1590,8 @@ class Item
|
|||
$item['network'] = trim(($item['network'] ?? '') ?: Protocol::PHANTOM);
|
||||
}
|
||||
|
||||
$uid = intval($item['uid']);
|
||||
|
||||
$item['guid'] = self::guid($item, $notify);
|
||||
$item['uri'] = substr(Strings::escapeTags(trim(($item['uri'] ?? '') ?: self::newURI($item['uid'], $item['guid']))), 0, 255);
|
||||
|
||||
|
|
@ -1431,100 +1601,24 @@ class Item
|
|||
// Store conversation data
|
||||
$item = Conversation::insert($item);
|
||||
|
||||
/*
|
||||
* If a Diaspora signature structure was passed in, pull it out of the
|
||||
* item array and set it aside for later storage.
|
||||
*/
|
||||
|
||||
$dsprsig = null;
|
||||
if (isset($item['dsprsig'])) {
|
||||
$encoded_signature = $item['dsprsig'];
|
||||
$dsprsig = json_decode(base64_decode($item['dsprsig']));
|
||||
unset($item['dsprsig']);
|
||||
}
|
||||
|
||||
$diaspora_signed_text = '';
|
||||
if (isset($item['diaspora_signed_text'])) {
|
||||
$diaspora_signed_text = $item['diaspora_signed_text'];
|
||||
unset($item['diaspora_signed_text']);
|
||||
}
|
||||
|
||||
// Converting the plink
|
||||
/// @TODO Check if this is really still needed
|
||||
if ($item['network'] == Protocol::OSTATUS) {
|
||||
if (isset($item['plink'])) {
|
||||
$item['plink'] = OStatus::convertHref($item['plink']);
|
||||
} elseif (isset($item['uri'])) {
|
||||
$item['plink'] = OStatus::convertHref($item['uri']);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($item['thr-parent'])) {
|
||||
$item['parent-uri'] = $item['thr-parent'];
|
||||
}
|
||||
|
||||
$activity = DI::activity();
|
||||
|
||||
if (isset($item['gravity'])) {
|
||||
$item['gravity'] = intval($item['gravity']);
|
||||
} elseif ($item['parent-uri'] === $item['uri']) {
|
||||
$item['gravity'] = GRAVITY_PARENT;
|
||||
} elseif ($activity->match($item['verb'], Activity::POST)) {
|
||||
$item['gravity'] = GRAVITY_COMMENT;
|
||||
} elseif ($activity->match($item['verb'], Activity::FOLLOW)) {
|
||||
$item['gravity'] = GRAVITY_ACTIVITY;
|
||||
} else {
|
||||
$item['gravity'] = GRAVITY_UNKNOWN; // Should not happen
|
||||
Logger::log('Unknown gravity for verb: ' . $item['verb'], Logger::DEBUG);
|
||||
}
|
||||
|
||||
$uid = intval($item['uid']);
|
||||
|
||||
// check for create date and expire time
|
||||
$expire_interval = DI::config()->get('system', 'dbclean-expire-days', 0);
|
||||
|
||||
$user = DBA::selectFirst('user', ['expire'], ['uid' => $uid]);
|
||||
if (DBA::isResult($user) && ($user['expire'] > 0) && (($user['expire'] < $expire_interval) || ($expire_interval == 0))) {
|
||||
$expire_interval = $user['expire'];
|
||||
}
|
||||
|
||||
if (($expire_interval > 0) && !empty($item['created'])) {
|
||||
$expire_date = time() - ($expire_interval * 86400);
|
||||
$created_date = strtotime($item['created']);
|
||||
if ($created_date < $expire_date) {
|
||||
Logger::notice('Item created before expiration interval.', [
|
||||
'created' => date('c', $created_date),
|
||||
'expired' => date('c', $expire_date),
|
||||
'$item' => $item
|
||||
]);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Do we already have this item?
|
||||
* We have to check several networks since Friendica posts could be repeated
|
||||
* via OStatus (maybe Diasporsa as well)
|
||||
*/
|
||||
if (empty($item['network']) || in_array($item['network'], Protocol::FEDERATED)) {
|
||||
$condition = ["`uri` = ? AND `uid` = ? AND `network` IN (?, ?, ?, ?)",
|
||||
trim($item['uri']), $item['uid'],
|
||||
Protocol::ACTIVITYPUB, Protocol::DIASPORA, Protocol::DFRN, Protocol::OSTATUS];
|
||||
$existing = self::selectFirst(['id', 'network'], $condition);
|
||||
if (DBA::isResult($existing)) {
|
||||
// We only log the entries with a different user id than 0. Otherwise we would have too many false positives
|
||||
if ($uid != 0) {
|
||||
Logger::notice('Item already existed for user', [
|
||||
'uri' => $item['uri'],
|
||||
'uid' => $uid,
|
||||
'network' => $item['network'],
|
||||
'existing_id' => $existing["id"],
|
||||
'existing_network' => $existing["network"]
|
||||
]);
|
||||
}
|
||||
$duplicate = self::getDuplicateID($item);
|
||||
if ($duplicate) {
|
||||
return $duplicate;
|
||||
}
|
||||
|
||||
return $existing["id"];
|
||||
}
|
||||
// Additional duplicate checks
|
||||
/// @todo Check why the first duplication check returns the item number and the second a 0
|
||||
if (self::isDuplicate($item)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$item['wall'] = intval($item['wall'] ?? 0);
|
||||
|
|
@ -1559,7 +1653,6 @@ class Item
|
|||
$item['deny_gid'] = trim($item['deny_gid'] ?? '');
|
||||
$item['private'] = intval($item['private'] ?? self::PUBLIC);
|
||||
$item['body'] = trim($item['body'] ?? '');
|
||||
$item['tag'] = trim($item['tag'] ?? '');
|
||||
$item['attach'] = trim($item['attach'] ?? '');
|
||||
$item['app'] = trim($item['app'] ?? '');
|
||||
$item['origin'] = intval($item['origin'] ?? 0);
|
||||
|
|
@ -1569,14 +1662,6 @@ class Item
|
|||
$item['inform'] = trim($item['inform'] ?? '');
|
||||
$item['file'] = trim($item['file'] ?? '');
|
||||
|
||||
// When there is no content then we don't post it
|
||||
if ($item['body'].$item['title'] == '') {
|
||||
Logger::notice('No body, no title.');
|
||||
return 0;
|
||||
}
|
||||
|
||||
self::addLanguageToItemArray($item);
|
||||
|
||||
// Items cannot be stored before they happen ...
|
||||
if ($item['created'] > DateTimeFormat::utcNow()) {
|
||||
$item['created'] = DateTimeFormat::utcNow();
|
||||
|
|
@ -1589,234 +1674,59 @@ class Item
|
|||
|
||||
$item['plink'] = ($item['plink'] ?? '') ?: DI::baseUrl() . '/display/' . urlencode($item['guid']);
|
||||
|
||||
$item['language'] = self::getLanguage($item);
|
||||
|
||||
$item['gravity'] = self::getGravity($item);
|
||||
|
||||
$default = ['url' => $item['author-link'], 'name' => $item['author-name'],
|
||||
'photo' => $item['author-avatar'], 'network' => $item['network']];
|
||||
|
||||
$item['author-id'] = ($item['author-id'] ?? 0) ?: Contact::getIdForURL($item['author-link'], 0, false, $default);
|
||||
|
||||
if (Contact::isBlocked($item['author-id'])) {
|
||||
Logger::notice('Author is blocked node-wide', ['author-link' => $item['author-link'], 'item-uri' => $item['uri']]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!empty($item['author-link']) && Network::isUrlBlocked($item['author-link'])) {
|
||||
Logger::notice('Author server is blocked', ['author-link' => $item['author-link'], 'item-uri' => $item['uri']]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!empty($uid) && Contact::isBlockedByUser($item['author-id'], $uid)) {
|
||||
Logger::notice('Author is blocked by user', ['author-link' => $item['author-link'], 'uid' => $uid, 'item-uri' => $item['uri']]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
$default = ['url' => $item['owner-link'], 'name' => $item['owner-name'],
|
||||
'photo' => $item['owner-avatar'], 'network' => $item['network']];
|
||||
|
||||
$item['owner-id'] = ($item['owner-id'] ?? 0) ?: Contact::getIdForURL($item['owner-link'], 0, false, $default);
|
||||
|
||||
if (Contact::isBlocked($item['owner-id'])) {
|
||||
Logger::notice('Owner is blocked node-wide', ['owner-link' => $item['owner-link'], 'item-uri' => $item['uri']]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!empty($item['owner-link']) && Network::isUrlBlocked($item['owner-link'])) {
|
||||
Logger::notice('Owner server is blocked', ['owner-link' => $item['owner-link'], 'item-uri' => $item['uri']]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!empty($uid) && Contact::isBlockedByUser($item['owner-id'], $uid)) {
|
||||
Logger::notice('Owner is blocked by user', ['owner-link' => $item['owner-link'], 'uid' => $uid, 'item-uri' => $item['uri']]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// The causer is set during a thread completion, for example because of a reshare. It countains the responsible actor.
|
||||
if (!empty($uid) && !empty($item['causer-id']) && Contact::isBlockedByUser($item['causer-id'], $uid)) {
|
||||
Logger::notice('Causer is blocked by user', ['causer-link' => $item['causer-link'], 'uid' => $uid, 'item-uri' => $item['uri']]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!empty($uid) && !empty($item['causer-id']) && ($item['parent-uri'] == $item['uri']) && Contact::isIgnoredByUser($item['causer-id'], $uid)) {
|
||||
Logger::notice('Causer is ignored by user', ['causer-link' => $item['causer-link'], 'uid' => $uid, 'item-uri' => $item['uri']]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// We don't store the causer, we only have it here for the checks above
|
||||
unset($item['causer-id']);
|
||||
unset($item['causer-link']);
|
||||
|
||||
// The contact-id should be set before "self::insert" was called - but there seems to be issues sometimes
|
||||
$item["contact-id"] = self::contactId($item);
|
||||
|
||||
if ($item['network'] == Protocol::PHANTOM) {
|
||||
$item['network'] = Protocol::DFRN;
|
||||
Logger::notice('Missing network, setting to {network}.', [
|
||||
'uri' => $item["uri"],
|
||||
'network' => $item['network'],
|
||||
'callstack' => System::callstack()
|
||||
]);
|
||||
}
|
||||
|
||||
// Checking if there is already an item with the same guid
|
||||
$condition = ['guid' => $item['guid'], 'network' => $item['network'], 'uid' => $item['uid']];
|
||||
if (self::exists($condition)) {
|
||||
Logger::notice('Found already existing item', [
|
||||
'guid' => $item['guid'],
|
||||
'uid' => $item['uid'],
|
||||
'network' => $item['network']
|
||||
]);
|
||||
if (!self::isValid($item)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ($item['verb'] == Activity::FOLLOW) {
|
||||
if (!$item['origin'] && ($item['author-id'] == Contact::getPublicIdByUserId($uid))) {
|
||||
// Our own follow request can be relayed to us. We don't store it to avoid notification chaos.
|
||||
Logger::log("Follow: Don't store not origin follow request from us for " . $item['parent-uri'], Logger::DEBUG);
|
||||
return 0;
|
||||
}
|
||||
// We don't store the causer, we only have it here for the checks in the function above
|
||||
unset($item['causer-id']);
|
||||
unset($item['causer-link']);
|
||||
|
||||
$condition = ['verb' => Activity::FOLLOW, 'uid' => $item['uid'],
|
||||
'parent-uri' => $item['parent-uri'], 'author-id' => $item['author-id']];
|
||||
if (self::exists($condition)) {
|
||||
// It happens that we receive multiple follow requests by the same author - we only store one.
|
||||
Logger::log('Follow: Found existing follow request from author ' . $item['author-id'] . ' for ' . $item['parent-uri'], Logger::DEBUG);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
// We don't store these fields anymore in the item table
|
||||
unset($item['author-link']);
|
||||
unset($item['author-name']);
|
||||
unset($item['author-avatar']);
|
||||
unset($item['author-network']);
|
||||
|
||||
// Check for hashtags in the body and repair or add hashtag links
|
||||
self::setHashtags($item);
|
||||
unset($item['owner-link']);
|
||||
unset($item['owner-name']);
|
||||
unset($item['owner-avatar']);
|
||||
|
||||
$item['thr-parent'] = $item['parent-uri'];
|
||||
|
||||
$notify_type = Delivery::POST;
|
||||
$allow_cid = '';
|
||||
$allow_gid = '';
|
||||
$deny_cid = '';
|
||||
$deny_gid = '';
|
||||
|
||||
if ($item['parent-uri'] === $item['uri']) {
|
||||
$parent_id = 0;
|
||||
$parent_deleted = 0;
|
||||
$allow_cid = $item['allow_cid'];
|
||||
$allow_gid = $item['allow_gid'];
|
||||
$deny_cid = $item['deny_cid'];
|
||||
$deny_gid = $item['deny_gid'];
|
||||
} else {
|
||||
// find the parent and snarf the item id and ACLs
|
||||
// and anything else we need to inherit
|
||||
|
||||
$fields = ['uri', 'parent-uri', 'id', 'deleted',
|
||||
'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid',
|
||||
'wall', 'private', 'forum_mode', 'origin', 'author-id'];
|
||||
$condition = ['uri' => $item['parent-uri'], 'uid' => $item['uid']];
|
||||
$params = ['order' => ['id' => false]];
|
||||
$parent = self::selectFirst($fields, $condition, $params);
|
||||
|
||||
if (DBA::isResult($parent)) {
|
||||
// is the new message multi-level threaded?
|
||||
// even though we don't support it now, preserve the info
|
||||
// and re-attach to the conversation parent.
|
||||
|
||||
if ($parent['uri'] != $parent['parent-uri']) {
|
||||
$item['parent-uri'] = $parent['parent-uri'];
|
||||
|
||||
$condition = ['uri' => $item['parent-uri'],
|
||||
'parent-uri' => $item['parent-uri'],
|
||||
'uid' => $item['uid']];
|
||||
$params = ['order' => ['id' => false]];
|
||||
$toplevel_parent = self::selectFirst($fields, $condition, $params);
|
||||
|
||||
if (DBA::isResult($toplevel_parent)) {
|
||||
$parent = $toplevel_parent;
|
||||
}
|
||||
}
|
||||
|
||||
$parent_id = $parent['id'];
|
||||
$parent_deleted = $parent['deleted'];
|
||||
$allow_cid = $parent['allow_cid'];
|
||||
$allow_gid = $parent['allow_gid'];
|
||||
$deny_cid = $parent['deny_cid'];
|
||||
$deny_gid = $parent['deny_gid'];
|
||||
$item['wall'] = $parent['wall'];
|
||||
|
||||
/*
|
||||
* If the parent is private, force privacy for the entire conversation
|
||||
* This differs from the above settings as it subtly allows comments from
|
||||
* email correspondents to be private even if the overall thread is not.
|
||||
*/
|
||||
if ($parent['private']) {
|
||||
$item['private'] = $parent['private'];
|
||||
}
|
||||
|
||||
/*
|
||||
* Edge case. We host a public forum that was originally posted to privately.
|
||||
* The original author commented, but as this is a comment, the permissions
|
||||
* weren't fixed up so it will still show the comment as private unless we fix it here.
|
||||
*/
|
||||
if ((intval($parent['forum_mode']) == 1) && ($parent['private'] != self::PUBLIC)) {
|
||||
$item['private'] = self::PUBLIC;
|
||||
}
|
||||
|
||||
// If its a post that originated here then tag the thread as "mention"
|
||||
if ($item['origin'] && $item['uid']) {
|
||||
DBA::update('thread', ['mention' => true], ['iid' => $parent_id]);
|
||||
Logger::log('tagged thread ' . $parent_id . ' as mention for user ' . $item['uid'], Logger::DEBUG);
|
||||
}
|
||||
|
||||
// Update the contact relations
|
||||
if ($item['author-id'] != $parent['author-id']) {
|
||||
DBA::update('contact-relation', ['last-interaction' => $item['created']], ['cid' => $parent['author-id'], 'relation-cid' => $item['author-id']], true);
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* Allow one to see reply tweets from status.net even when
|
||||
* we don't have or can't see the original post.
|
||||
*/
|
||||
if ($force_parent) {
|
||||
Logger::log('$force_parent=true, reply converted to top-level post.');
|
||||
$parent_id = 0;
|
||||
$item['parent-uri'] = $item['uri'];
|
||||
$item['gravity'] = GRAVITY_PARENT;
|
||||
} else {
|
||||
Logger::log('item parent '.$item['parent-uri'].' for '.$item['uid'].' was not found - ignoring item');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$parent_deleted = 0;
|
||||
if ($item['parent-uri'] != $item['uri']) {
|
||||
$item = self::getParentData($item);
|
||||
if (empty($item)) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (stristr($item['verb'], Activity::POKE)) {
|
||||
$notify_type = Delivery::POKE;
|
||||
$parent_id = $item['parent'];
|
||||
unset($item['parent']);
|
||||
$parent_origin = $item['parent_origin'];
|
||||
unset($item['parent_origin']);
|
||||
} else {
|
||||
$parent_id = 0;
|
||||
$parent_origin = $item['origin'];
|
||||
}
|
||||
|
||||
$item['parent-uri-id'] = ItemURI::getIdByURI($item['parent-uri']);
|
||||
$item['thr-parent-id'] = ItemURI::getIdByURI($item['thr-parent']);
|
||||
|
||||
$condition = ["`uri` = ? AND `network` IN (?, ?) AND `uid` = ?",
|
||||
$item['uri'], $item['network'], Protocol::DFRN, $item['uid']];
|
||||
if (self::exists($condition)) {
|
||||
Logger::log('duplicated item with the same uri found. '.print_r($item,true));
|
||||
return 0;
|
||||
}
|
||||
|
||||
// On Friendica and Diaspora the GUID is unique
|
||||
if (in_array($item['network'], [Protocol::DFRN, Protocol::DIASPORA])) {
|
||||
$condition = ['guid' => $item['guid'], 'uid' => $item['uid']];
|
||||
if (self::exists($condition)) {
|
||||
Logger::log('duplicated item with the same guid found. '.print_r($item,true));
|
||||
return 0;
|
||||
}
|
||||
} elseif ($item['network'] == Protocol::OSTATUS) {
|
||||
// Check for an existing post with the same content. There seems to be a problem with OStatus.
|
||||
$condition = ["`body` = ? AND `network` = ? AND `created` = ? AND `contact-id` = ? AND `uid` = ?",
|
||||
$item['body'], $item['network'], $item['created'], $item['contact-id'], $item['uid']];
|
||||
if (self::exists($condition)) {
|
||||
Logger::log('duplicated item with the same body found. '.print_r($item,true));
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Is this item available in the global items (with uid=0)?
|
||||
if ($item["uid"] == 0) {
|
||||
$item["global"] = true;
|
||||
|
|
@ -1828,22 +1738,10 @@ class Item
|
|||
}
|
||||
|
||||
// ACL settings
|
||||
if (strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid)) {
|
||||
$private = self::PRIVATE;
|
||||
} else {
|
||||
$private = $item['private'];
|
||||
if (!empty($item["allow_cid"] . $item["allow_gid"] . $item["deny_cid"] . $item["deny_gid"])) {
|
||||
$item["private"] = self::PRIVATE;
|
||||
}
|
||||
|
||||
$item["allow_cid"] = $allow_cid;
|
||||
$item["allow_gid"] = $allow_gid;
|
||||
$item["deny_cid"] = $deny_cid;
|
||||
$item["deny_gid"] = $deny_gid;
|
||||
$item["private"] = $private;
|
||||
$item["deleted"] = $parent_deleted;
|
||||
|
||||
// Fill the cache field
|
||||
self::putInCache($item);
|
||||
|
||||
if ($notify) {
|
||||
$item['edit'] = false;
|
||||
$item['parent'] = $parent_id;
|
||||
|
|
@ -1854,41 +1752,13 @@ class Item
|
|||
Hook::callAll('post_remote', $item);
|
||||
}
|
||||
|
||||
// This array field is used to trigger some automatic reactions
|
||||
// It is mainly used in the "post_local" hook.
|
||||
unset($item['api_source']);
|
||||
|
||||
if (!empty($item['cancel'])) {
|
||||
Logger::log('post cancelled by addon.');
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check for already added items.
|
||||
* There is a timing issue here that sometimes creates double postings.
|
||||
* An unique index would help - but the limitations of MySQL (maximum size of index values) prevent this.
|
||||
*/
|
||||
if ($item["uid"] == 0) {
|
||||
if (self::exists(['uri' => trim($item['uri']), 'uid' => 0])) {
|
||||
Logger::log('Global item already stored. URI: '.$item['uri'].' on network '.$item['network'], Logger::DEBUG);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
Logger::log('' . print_r($item,true), Logger::DATA);
|
||||
|
||||
if (array_key_exists('tag', $item)) {
|
||||
$tags = $item['tag'];
|
||||
unset($item['tag']);
|
||||
} else {
|
||||
$tags = '';
|
||||
}
|
||||
|
||||
if (array_key_exists('file', $item)) {
|
||||
$files = $item['file'];
|
||||
unset($item['file']);
|
||||
} else {
|
||||
$files = '';
|
||||
if (empty($item['vid']) && !empty($item['verb'])) {
|
||||
$item['vid'] = Verb::getID($item['verb']);
|
||||
}
|
||||
|
||||
// Creates or assigns the permission set
|
||||
|
|
@ -1900,34 +1770,75 @@ class Item
|
|||
$item['deny_gid']
|
||||
);
|
||||
|
||||
$item['allow_cid'] = null;
|
||||
$item['allow_gid'] = null;
|
||||
$item['deny_cid'] = null;
|
||||
$item['deny_gid'] = null;
|
||||
unset($item['allow_cid']);
|
||||
unset($item['allow_gid']);
|
||||
unset($item['deny_cid']);
|
||||
unset($item['deny_gid']);
|
||||
|
||||
// We are doing this outside of the transaction to avoid timing problems
|
||||
if (!self::insertActivity($item)) {
|
||||
self::insertContent($item);
|
||||
// This array field is used to trigger some automatic reactions
|
||||
// It is mainly used in the "post_local" hook.
|
||||
unset($item['api_source']);
|
||||
|
||||
|
||||
// Check for hashtags in the body and repair or add hashtag links
|
||||
$item['body'] = self::setHashtags($item['body']);
|
||||
|
||||
// Fill the cache field
|
||||
self::putInCache($item);
|
||||
|
||||
if (stristr($item['verb'], Activity::POKE)) {
|
||||
$notify_type = Delivery::POKE;
|
||||
} else {
|
||||
$notify_type = Delivery::POST;
|
||||
}
|
||||
|
||||
$delivery_data = ItemDeliveryData::extractFields($item);
|
||||
|
||||
unset($item['postopts']);
|
||||
unset($item['inform']);
|
||||
|
||||
// These fields aren't stored anymore in the item table, they are fetched upon request
|
||||
unset($item['author-link']);
|
||||
unset($item['author-name']);
|
||||
unset($item['author-avatar']);
|
||||
unset($item['author-network']);
|
||||
|
||||
unset($item['owner-link']);
|
||||
unset($item['owner-name']);
|
||||
unset($item['owner-avatar']);
|
||||
|
||||
$like_no_comment = DI::config()->get('system', 'like_no_comment');
|
||||
|
||||
DBA::transaction();
|
||||
|
||||
if (!in_array($item['verb'], self::ACTIVITIES)) {
|
||||
$item['icid'] = self::insertContent($item);
|
||||
}
|
||||
|
||||
$body = $item['body'];
|
||||
|
||||
// We just remove everything that is content
|
||||
foreach (array_merge(self::CONTENT_FIELDLIST, self::MIXED_CONTENT_FIELDLIST) as $field) {
|
||||
unset($item[$field]);
|
||||
}
|
||||
|
||||
unset($item['activity']);
|
||||
|
||||
// Filling item related side tables
|
||||
|
||||
// Diaspora signature
|
||||
if (!empty($item['diaspora_signed_text'])) {
|
||||
DBA::insert('diaspora-interaction', ['uri-id' => $item['uri-id'], 'interaction' => $item['diaspora_signed_text']], true);
|
||||
}
|
||||
|
||||
unset($item['diaspora_signed_text']);
|
||||
|
||||
// Attached file links
|
||||
if (!empty($item['file'])) {
|
||||
Category::storeTextByURIId($item['uri-id'], $item['uid'], $item['file']);
|
||||
}
|
||||
|
||||
unset($item['file']);
|
||||
|
||||
// Delivery relevant data
|
||||
$delivery_data = Post\DeliveryData::extractFields($item);
|
||||
unset($item['postopts']);
|
||||
unset($item['inform']);
|
||||
|
||||
if (!empty($item['origin']) || !empty($item['wall']) || !empty($delivery_data['postopts']) || !empty($delivery_data['inform'])) {
|
||||
Post\DeliveryData::insert($item['uri-id'], $delivery_data);
|
||||
}
|
||||
|
||||
// Store tags from the body if this hadn't been handled previously in the protocol classes
|
||||
if (!Tag::existsForPost($item['uri-id'])) {
|
||||
Tag::storeFromBody($item['uri-id'], $body);
|
||||
}
|
||||
|
||||
$ret = DBA::insert('item', $item);
|
||||
|
||||
// When the item was successfully stored we fetch the ID of the item.
|
||||
|
|
@ -1957,7 +1868,7 @@ class Item
|
|||
// There are duplicates. We delete our just created entry.
|
||||
Logger::info('Delete duplicated item', ['id' => $current_post, 'uri' => $item['uri'], 'uid' => $item['uid'], 'guid' => $item['guid']]);
|
||||
|
||||
// Yes, we could do a rollback here - but we are having many users with MyISAM.
|
||||
// Yes, we could do a rollback here - but we possibly are still having users with MyISAM.
|
||||
DBA::delete('item', ['id' => $current_post]);
|
||||
DBA::commit();
|
||||
return 0;
|
||||
|
|
@ -1988,53 +1899,13 @@ class Item
|
|||
DBA::update('item', ['changed' => DateTimeFormat::utcNow()], ['id' => $parent_id]);
|
||||
}
|
||||
|
||||
if ($dsprsig) {
|
||||
/*
|
||||
* Friendica servers lower than 3.4.3-2 had double encoded the signature ...
|
||||
* We can check for this condition when we decode and encode the stuff again.
|
||||
*/
|
||||
if (base64_encode(base64_decode(base64_decode($dsprsig->signature))) == base64_decode($dsprsig->signature)) {
|
||||
$dsprsig->signature = base64_decode($dsprsig->signature);
|
||||
Logger::log("Repaired double encoded signature from handle ".$dsprsig->signer, Logger::DEBUG);
|
||||
}
|
||||
|
||||
if (!empty($dsprsig->signed_text) && empty($dsprsig->signature) && empty($dsprsig->signer)) {
|
||||
DBA::insert('diaspora-interaction', ['uri-id' => $item['uri-id'], 'interaction' => $dsprsig->signed_text], true);
|
||||
} else {
|
||||
// The other fields are used by very old Friendica servers, so we currently store them differently
|
||||
DBA::insert('sign', ['iid' => $current_post, 'signed_text' => $dsprsig->signed_text,
|
||||
'signature' => $dsprsig->signature, 'signer' => $dsprsig->signer]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($diaspora_signed_text)) {
|
||||
DBA::insert('diaspora-interaction', ['uri-id' => $item['uri-id'], 'interaction' => $diaspora_signed_text], true);
|
||||
}
|
||||
|
||||
if ($item['parent-uri'] === $item['uri']) {
|
||||
self::addThread($current_post);
|
||||
} else {
|
||||
self::updateThread($parent_id);
|
||||
}
|
||||
|
||||
if (!empty($item['origin']) || !empty($item['wall']) || !empty($delivery_data['postopts']) || !empty($delivery_data['inform'])) {
|
||||
ItemDeliveryData::insert($current_post, $delivery_data);
|
||||
}
|
||||
|
||||
DBA::commit();
|
||||
|
||||
/*
|
||||
* Due to deadlock issues with the "term" table we are doing these steps after the commit.
|
||||
* This is not perfect - but a workable solution until we found the reason for the problem.
|
||||
*/
|
||||
if (!empty($tags)) {
|
||||
Term::insertFromTagFieldByItemId($current_post, $tags);
|
||||
}
|
||||
|
||||
if (!empty($files)) {
|
||||
Term::insertFromFileFieldByItemId($current_post, $files);
|
||||
}
|
||||
|
||||
// In that function we check if this is a forum post. Additionally we delete the item under certain circumstances
|
||||
if (self::tagDeliver($item['uid'], $current_post)) {
|
||||
// Get the user information for the logging
|
||||
|
|
@ -2069,7 +1940,19 @@ class Item
|
|||
|
||||
check_user_notification($current_post);
|
||||
|
||||
if ($notify || ($item['visible'] && ((!empty($parent) && $parent['origin']) || $item['origin']))) {
|
||||
$transmit = $notify || ($item['visible'] && ($parent_origin || $item['origin']));
|
||||
|
||||
if ($transmit) {
|
||||
$transmit_item = Item::selectFirst(['verb', 'origin'], ['id' => $item['id']]);
|
||||
// Don't relay participation messages
|
||||
if (($transmit_item['verb'] == Activity::FOLLOW) &&
|
||||
(!$transmit_item['origin'] || ($item['author-id'] != Contact::getPublicIdByUserId($uid)))) {
|
||||
Logger::info('Participation messages will not be relayed', ['item' => $item['id'], 'uri' => $item['uri'], 'verb' => $transmit_item['verb']]);
|
||||
$transmit = false;
|
||||
}
|
||||
}
|
||||
|
||||
if ($transmit) {
|
||||
Worker::add(['priority' => $priority, 'dont_fork' => true], 'Notifier', $notify_type, $current_post);
|
||||
}
|
||||
|
||||
|
|
@ -2080,116 +1963,45 @@ class Item
|
|||
* Insert a new item content entry
|
||||
*
|
||||
* @param array $item The item fields that are to be inserted
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
private static function insertActivity(&$item)
|
||||
{
|
||||
$activity_index = self::activityToIndex($item['verb']);
|
||||
|
||||
if ($activity_index < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$fields = ['activity' => $activity_index, 'uri-hash' => (string)$item['uri-id'], 'uri-id' => $item['uri-id']];
|
||||
|
||||
// We just remove everything that is content
|
||||
foreach (array_merge(self::CONTENT_FIELDLIST, self::MIXED_CONTENT_FIELDLIST) as $field) {
|
||||
unset($item[$field]);
|
||||
}
|
||||
|
||||
// To avoid timing problems, we are using locks.
|
||||
$locked = DI::lock()->acquire('item_insert_activity');
|
||||
if (!$locked) {
|
||||
Logger::log("Couldn't acquire lock for URI " . $item['uri'] . " - proceeding anyway.");
|
||||
}
|
||||
|
||||
// Do we already have this content?
|
||||
$item_activity = DBA::selectFirst('item-activity', ['id'], ['uri-id' => $item['uri-id']]);
|
||||
if (DBA::isResult($item_activity)) {
|
||||
$item['iaid'] = $item_activity['id'];
|
||||
Logger::log('Fetched activity for URI ' . $item['uri'] . ' (' . $item['iaid'] . ')');
|
||||
} elseif (DBA::insert('item-activity', $fields)) {
|
||||
$item['iaid'] = DBA::lastInsertId();
|
||||
Logger::log('Inserted activity for URI ' . $item['uri'] . ' (' . $item['iaid'] . ')');
|
||||
} else {
|
||||
// This shouldn't happen.
|
||||
Logger::log('Could not insert activity for URI ' . $item['uri'] . ' - should not happen');
|
||||
DI::lock()->release('item_insert_activity');
|
||||
return false;
|
||||
}
|
||||
if ($locked) {
|
||||
DI::lock()->release('item_insert_activity');
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a new item content entry
|
||||
*
|
||||
* @param array $item The item fields that are to be inserted
|
||||
* @throws \Exception
|
||||
*/
|
||||
private static function insertContent(&$item)
|
||||
private static function insertContent(array $item)
|
||||
{
|
||||
$fields = ['uri-plink-hash' => (string)$item['uri-id'], 'uri-id' => $item['uri-id']];
|
||||
|
||||
foreach (array_merge(self::CONTENT_FIELDLIST, self::MIXED_CONTENT_FIELDLIST) as $field) {
|
||||
if (isset($item[$field])) {
|
||||
$fields[$field] = $item[$field];
|
||||
unset($item[$field]);
|
||||
}
|
||||
}
|
||||
|
||||
// To avoid timing problems, we are using locks.
|
||||
$locked = DI::lock()->acquire('item_insert_content');
|
||||
if (!$locked) {
|
||||
Logger::log("Couldn't acquire lock for URI " . $item['uri'] . " - proceeding anyway.");
|
||||
}
|
||||
|
||||
// Do we already have this content?
|
||||
$item_content = DBA::selectFirst('item-content', ['id'], ['uri-id' => $item['uri-id']]);
|
||||
if (DBA::isResult($item_content)) {
|
||||
$item['icid'] = $item_content['id'];
|
||||
Logger::log('Fetched content for URI ' . $item['uri'] . ' (' . $item['icid'] . ')');
|
||||
} elseif (DBA::insert('item-content', $fields)) {
|
||||
$item['icid'] = DBA::lastInsertId();
|
||||
Logger::log('Inserted content for URI ' . $item['uri'] . ' (' . $item['icid'] . ')');
|
||||
} else {
|
||||
// This shouldn't happen.
|
||||
Logger::log('Could not insert content for URI ' . $item['uri'] . ' - should not happen');
|
||||
}
|
||||
if ($locked) {
|
||||
DI::lock()->release('item_insert_content');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update existing item content entries
|
||||
*
|
||||
* @param array $item The item fields that are to be changed
|
||||
* @param array $condition The condition for finding the item content entries
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
private static function updateActivity($item, $condition)
|
||||
{
|
||||
if (empty($item['verb'])) {
|
||||
return false;
|
||||
}
|
||||
$activity_index = self::activityToIndex($item['verb']);
|
||||
|
||||
if ($activity_index < 0) {
|
||||
return false;
|
||||
$icid = $item_content['id'];
|
||||
Logger::info('Content found', ['icid' => $icid, 'uri' => $item['uri']]);
|
||||
return $icid;
|
||||
}
|
||||
|
||||
$fields = ['activity' => $activity_index];
|
||||
DBA::insert('item-content', $fields, true);
|
||||
$icid = DBA::lastInsertId();
|
||||
if ($icid != 0) {
|
||||
Logger::info('Content inserted', ['icid' => $icid, 'uri' => $item['uri']]);
|
||||
return $icid;
|
||||
}
|
||||
|
||||
Logger::log('Update activity for ' . json_encode($condition));
|
||||
// Possibly there can be timing issues. Then the same content could be inserted multiple times.
|
||||
// Due to the indexes this doesn't happen, but "lastInsertId" will be empty in these situations.
|
||||
// So we have to fetch the id manually. This is no bug and there is no data loss.
|
||||
$item_content = DBA::selectFirst('item-content', ['id'], ['uri-id' => $item['uri-id']]);
|
||||
if (DBA::isResult($item_content)) {
|
||||
$icid = $item_content['id'];
|
||||
Logger::notice('Content inserted with empty lastInsertId', ['icid' => $icid, 'uri' => $item['uri']]);
|
||||
return $icid;
|
||||
}
|
||||
|
||||
DBA::update('item-activity', $fields, $condition, true);
|
||||
|
||||
return true;
|
||||
// This shouldn't happen.
|
||||
Logger::error("Content wasn't inserted", $item);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -2210,14 +2022,11 @@ class Item
|
|||
}
|
||||
|
||||
if (empty($fields)) {
|
||||
// when there are no fields at all, just use the condition
|
||||
// This is to ensure that we always store content.
|
||||
$fields = $condition;
|
||||
return;
|
||||
}
|
||||
|
||||
Logger::log('Update content for ' . json_encode($condition));
|
||||
|
||||
DBA::update('item-content', $fields, $condition, true);
|
||||
Logger::info('Updated content', ['condition' => $condition]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -2353,12 +2162,12 @@ class Item
|
|||
}
|
||||
}
|
||||
|
||||
$distributed = self::insert($item, false, $notify, true);
|
||||
$distributed = self::insert($item, $notify, true);
|
||||
|
||||
if (!$distributed) {
|
||||
Logger::log("Distributed public item " . $itemid . " for user " . $uid . " wasn't stored", Logger::DEBUG);
|
||||
Logger::info("Distributed public item wasn't stored", ['id' => $itemid, 'user' => $uid]);
|
||||
} else {
|
||||
Logger::log("Distributed public item " . $itemid . " for user " . $uid . " with id " . $distributed, Logger::DEBUG);
|
||||
Logger::info('Distributed public item was stored', ['id' => $itemid, 'user' => $uid, 'stored' => $distributed]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2420,9 +2229,9 @@ class Item
|
|||
$item['contact-id'] = $item['author-id'];
|
||||
}
|
||||
|
||||
$public_shadow = self::insert($item, false, false, true);
|
||||
$public_shadow = self::insert($item, false, true);
|
||||
|
||||
Logger::log("Stored public shadow for thread ".$itemid." under id ".$public_shadow, Logger::DEBUG);
|
||||
Logger::info('Stored public shadow', ['thread' => $itemid, 'id' => $public_shadow]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2442,7 +2251,7 @@ class Item
|
|||
}
|
||||
|
||||
// Is it a toplevel post?
|
||||
if ($item['id'] == $item['parent']) {
|
||||
if ($item['gravity'] == GRAVITY_PARENT) {
|
||||
self::addShadow($itemid);
|
||||
return;
|
||||
}
|
||||
|
|
@ -2478,9 +2287,9 @@ class Item
|
|||
unset($item['inform']);
|
||||
$item['contact-id'] = Contact::getIdForURL($item['author-link']);
|
||||
|
||||
$public_shadow = self::insert($item, false, false, true);
|
||||
$public_shadow = self::insert($item, false, true);
|
||||
|
||||
Logger::log("Stored public shadow for comment ".$item['uri']." under id ".$public_shadow, Logger::DEBUG);
|
||||
Logger::info('Stored public shadow', ['uri' => $item['uri'], 'id' => $public_shadow]);
|
||||
|
||||
// If this was a comment to a Diaspora post we don't get our comment back.
|
||||
// This means that we have to distribute the comment by ourselves.
|
||||
|
|
@ -2493,20 +2302,22 @@ class Item
|
|||
* Adds a language specification in a "language" element of given $arr.
|
||||
* Expects "body" element to exist in $arr.
|
||||
*
|
||||
* @param $item
|
||||
* @param array $item
|
||||
* @return string detected language
|
||||
* @throws \Text_LanguageDetect_Exception
|
||||
*/
|
||||
private static function addLanguageToItemArray(&$item)
|
||||
private static function getLanguage(array $item)
|
||||
{
|
||||
$naked_body = BBCode::toPlaintext($item['body'], false);
|
||||
|
||||
$ld = new Text_LanguageDetect();
|
||||
$ld->setNameMode(2);
|
||||
$languages = $ld->detect($naked_body, 3);
|
||||
|
||||
if (is_array($languages)) {
|
||||
$item['language'] = json_encode($languages);
|
||||
return json_encode($languages);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -2579,7 +2390,8 @@ class Item
|
|||
Contact::unmarkForArchival($contact);
|
||||
}
|
||||
|
||||
$update = (($arr['private'] != self::PRIVATE) && ((($arr['author-link'] ?? '') === ($arr['owner-link'] ?? '')) || ($arr["parent-uri"] === $arr["uri"])));
|
||||
/// @todo On private posts we could obfuscate the date
|
||||
$update = ($arr['private'] != self::PRIVATE);
|
||||
|
||||
// Is it a forum? Then we don't care about the rules from above
|
||||
if (!$update && in_array($arr["network"], [Protocol::ACTIVITYPUB, Protocol::DFRN]) && ($arr["parent-uri"] === $arr["uri"])) {
|
||||
|
|
@ -2589,8 +2401,15 @@ class Item
|
|||
}
|
||||
|
||||
if ($update) {
|
||||
DBA::update('contact', ['success_update' => $arr['received'], 'last-item' => $arr['received']],
|
||||
['id' => $arr['contact-id']]);
|
||||
// The "self" contact id is used (for example in the connectors) when the contact is unknown
|
||||
// So we have to ensure to only update the last item when it had been our own post,
|
||||
// or it had been done by a "regular" contact.
|
||||
if (!empty($arr['wall'])) {
|
||||
$condition = ['id' => $arr['contact-id']];
|
||||
} else {
|
||||
$condition = ['id' => $arr['contact-id'], 'self' => false];
|
||||
}
|
||||
DBA::update('contact', ['success_update' => $arr['received'], 'last-item' => $arr['received']], $condition);
|
||||
}
|
||||
// Now do the same for the system wide contacts with uid=0
|
||||
if ($arr['private'] != self::PRIVATE) {
|
||||
|
|
@ -2604,91 +2423,69 @@ class Item
|
|||
}
|
||||
}
|
||||
|
||||
public static function setHashtags(&$item)
|
||||
public static function setHashtags($body)
|
||||
{
|
||||
$tags = BBCode::getTags($item["body"]);
|
||||
$body = BBCode::performWithEscapedTags($body, ['noparse', 'pre', 'code'], function ($body) {
|
||||
$tags = BBCode::getTags($body);
|
||||
|
||||
// No hashtags?
|
||||
if (!count($tags)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// What happens in [code], stays in [code]!
|
||||
// escape the # and the [
|
||||
// hint: we will also get in trouble with #tags, when we want markdown in posts -> ### Headline 3
|
||||
$item["body"] = preg_replace_callback("/\[code(.*?)\](.*?)\[\/code\]/ism",
|
||||
function ($match) {
|
||||
// we truly ESCape all # and [ to prevent gettin weird tags in [code] blocks
|
||||
$find = ['#', '['];
|
||||
$replace = [chr(27).'sharp', chr(27).'leftsquarebracket'];
|
||||
return ("[code" . $match[1] . "]" . str_replace($find, $replace, $match[2]) . "[/code]");
|
||||
}, $item["body"]);
|
||||
|
||||
// This sorting is important when there are hashtags that are part of other hashtags
|
||||
// Otherwise there could be problems with hashtags like #test and #test2
|
||||
rsort($tags);
|
||||
|
||||
$URLSearchString = "^\[\]";
|
||||
|
||||
// All hashtags should point to the home server if "local_tags" is activated
|
||||
if (DI::config()->get('system', 'local_tags')) {
|
||||
$item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
|
||||
"#[url=".DI::baseUrl()."/search?tag=$2]$2[/url]", $item["body"]);
|
||||
|
||||
$item["tag"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
|
||||
"#[url=".DI::baseUrl()."/search?tag=$2]$2[/url]", $item["tag"]);
|
||||
}
|
||||
|
||||
// mask hashtags inside of url, bookmarks and attachments to avoid urls in urls
|
||||
$item["body"] = preg_replace_callback("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
|
||||
function ($match) {
|
||||
return ("[url=" . str_replace("#", "#", $match[1]) . "]" . str_replace("#", "#", $match[2]) . "[/url]");
|
||||
}, $item["body"]);
|
||||
|
||||
$item["body"] = preg_replace_callback("/\[bookmark\=([$URLSearchString]*)\](.*?)\[\/bookmark\]/ism",
|
||||
function ($match) {
|
||||
return ("[bookmark=" . str_replace("#", "#", $match[1]) . "]" . str_replace("#", "#", $match[2]) . "[/bookmark]");
|
||||
}, $item["body"]);
|
||||
|
||||
$item["body"] = preg_replace_callback("/\[attachment (.*)\](.*?)\[\/attachment\]/ism",
|
||||
function ($match) {
|
||||
return ("[attachment " . str_replace("#", "#", $match[1]) . "]" . $match[2] . "[/attachment]");
|
||||
}, $item["body"]);
|
||||
|
||||
// Repair recursive urls
|
||||
$item["body"] = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
|
||||
"#$2", $item["body"]);
|
||||
|
||||
foreach ($tags as $tag) {
|
||||
if ((strpos($tag, '#') !== 0) || strpos($tag, '[url=') || strlen($tag) < 2 || $tag[1] == '#') {
|
||||
continue;
|
||||
// No hashtags?
|
||||
if (!count($tags)) {
|
||||
return $body;
|
||||
}
|
||||
|
||||
$basetag = str_replace('_',' ',substr($tag,1));
|
||||
$newtag = '#[url=' . DI::baseUrl() . '/search?tag=' . $basetag . ']' . $basetag . '[/url]';
|
||||
// This sorting is important when there are hashtags that are part of other hashtags
|
||||
// Otherwise there could be problems with hashtags like #test and #test2
|
||||
// Because of this we are sorting from the longest to the shortest tag.
|
||||
usort($tags, function ($a, $b) {
|
||||
return strlen($b) <=> strlen($a);
|
||||
});
|
||||
|
||||
$item["body"] = str_replace($tag, $newtag, $item["body"]);
|
||||
$URLSearchString = "^\[\]";
|
||||
|
||||
if (!stristr($item["tag"], "/search?tag=" . $basetag . "]" . $basetag . "[/url]")) {
|
||||
if (strlen($item["tag"])) {
|
||||
$item["tag"] = ',' . $item["tag"];
|
||||
// All hashtags should point to the home server if "local_tags" is activated
|
||||
if (DI::config()->get('system', 'local_tags')) {
|
||||
$body = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
|
||||
"#[url=" . DI::baseUrl() . "/search?tag=$2]$2[/url]", $body);
|
||||
}
|
||||
|
||||
// mask hashtags inside of url, bookmarks and attachments to avoid urls in urls
|
||||
$body = preg_replace_callback("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
|
||||
function ($match) {
|
||||
return ("[url=" . str_replace("#", "#", $match[1]) . "]" . str_replace("#", "#", $match[2]) . "[/url]");
|
||||
}, $body);
|
||||
|
||||
$body = preg_replace_callback("/\[bookmark\=([$URLSearchString]*)\](.*?)\[\/bookmark\]/ism",
|
||||
function ($match) {
|
||||
return ("[bookmark=" . str_replace("#", "#", $match[1]) . "]" . str_replace("#", "#", $match[2]) . "[/bookmark]");
|
||||
}, $body);
|
||||
|
||||
$body = preg_replace_callback("/\[attachment (.*)\](.*?)\[\/attachment\]/ism",
|
||||
function ($match) {
|
||||
return ("[attachment " . str_replace("#", "#", $match[1]) . "]" . $match[2] . "[/attachment]");
|
||||
}, $body);
|
||||
|
||||
// Repair recursive urls
|
||||
$body = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
|
||||
"#$2", $body);
|
||||
|
||||
foreach ($tags as $tag) {
|
||||
if ((strpos($tag, '#') !== 0) || strpos($tag, '[url=') || strlen($tag) < 2 || $tag[1] == '#') {
|
||||
continue;
|
||||
}
|
||||
$item["tag"] = $newtag . $item["tag"];
|
||||
|
||||
$basetag = str_replace('_', ' ', substr($tag, 1));
|
||||
$newtag = '#[url=' . DI::baseUrl() . '/search?tag=' . $basetag . ']' . $basetag . '[/url]';
|
||||
|
||||
$body = str_replace($tag, $newtag, $body);
|
||||
}
|
||||
}
|
||||
|
||||
// Convert back the masked hashtags
|
||||
$item["body"] = str_replace("#", "#", $item["body"]);
|
||||
// Convert back the masked hashtags
|
||||
$body = str_replace("#", "#", $body);
|
||||
|
||||
// Remember! What happens in [code], stays in [code]
|
||||
// roleback the # and [
|
||||
$item["body"] = preg_replace_callback("/\[code(.*?)\](.*?)\[\/code\]/ism",
|
||||
function ($match) {
|
||||
// we truly unESCape all sharp and leftsquarebracket
|
||||
$find = [chr(27).'sharp', chr(27).'leftsquarebracket'];
|
||||
$replace = ['#', '['];
|
||||
return ("[code" . $match[1] . "]" . str_replace($find, $replace, $match[2]) . "[/code]");
|
||||
}, $item["body"]);
|
||||
return $body;
|
||||
});
|
||||
|
||||
return $body;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -2737,7 +2534,7 @@ class Item
|
|||
|
||||
if (!$mention) {
|
||||
if (($community_page || $prvgroup) &&
|
||||
!$item['wall'] && !$item['origin'] && ($item['id'] == $item['parent'])) {
|
||||
!$item['wall'] && !$item['origin'] && ($item['gravity'] == GRAVITY_PARENT)) {
|
||||
Logger::info('Delete private group/communiy top-level item without mention', ['id' => $item_id, 'guid'=> $item['guid']]);
|
||||
DBA::delete('item', ['id' => $item_id]);
|
||||
return true;
|
||||
|
|
@ -2803,29 +2600,29 @@ class Item
|
|||
|
||||
// Prevent the forwarding of posts that are forwarded
|
||||
if (!empty($datarray["extid"]) && ($datarray["extid"] == Protocol::DFRN)) {
|
||||
Logger::log('Already forwarded', Logger::DEBUG);
|
||||
Logger::info('Already forwarded');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Prevent to forward already forwarded posts
|
||||
if ($datarray["app"] == DI::baseUrl()->getHostname()) {
|
||||
Logger::log('Already forwarded (second test)', Logger::DEBUG);
|
||||
Logger::info('Already forwarded (second test)');
|
||||
return false;
|
||||
}
|
||||
|
||||
// Only forward posts
|
||||
if ($datarray["verb"] != Activity::POST) {
|
||||
Logger::log('No post', Logger::DEBUG);
|
||||
Logger::info('No post');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (($contact['network'] != Protocol::FEED) && ($datarray['private'] == self::PRIVATE)) {
|
||||
Logger::log('Not public', Logger::DEBUG);
|
||||
Logger::info('Not public');
|
||||
return false;
|
||||
}
|
||||
|
||||
$datarray2 = $datarray;
|
||||
Logger::log('remote-self start - Contact '.$contact['url'].' - '.$contact['remote_self'].' Item '.print_r($datarray, true), Logger::DEBUG);
|
||||
Logger::info('remote-self start', ['contact' => $contact['url'], 'remote_self'=> $contact['remote_self'], 'item' => $datarray]);
|
||||
if ($contact['remote_self'] == 2) {
|
||||
$self = DBA::selectFirst('contact', ['id', 'name', 'url', 'thumb'],
|
||||
['uid' => $contact['uid'], 'self' => true]);
|
||||
|
|
@ -2863,8 +2660,8 @@ class Item
|
|||
|
||||
if ($contact['network'] != Protocol::FEED) {
|
||||
// Store the original post
|
||||
$result = self::insert($datarray2, false, false);
|
||||
Logger::log('remote-self post original item - Contact '.$contact['url'].' return '.$result.' Item '.print_r($datarray2, true), Logger::DEBUG);
|
||||
$result = self::insert($datarray2);
|
||||
Logger::info('remote-self post original item', ['contact' => $contact['url'], 'result'=> $result, 'item' => $datarray2]);
|
||||
} else {
|
||||
$datarray["app"] = "Feed";
|
||||
$result = true;
|
||||
|
|
@ -2896,7 +2693,7 @@ class Item
|
|||
return $s;
|
||||
}
|
||||
|
||||
Logger::log('check for photos', Logger::DEBUG);
|
||||
Logger::info('check for photos');
|
||||
$site = substr(DI::baseUrl(), strpos(DI::baseUrl(), '://'));
|
||||
|
||||
$orig_body = $s;
|
||||
|
|
@ -2910,7 +2707,7 @@ class Item
|
|||
$img_st_close++; // make it point to AFTER the closing bracket
|
||||
$image = substr($orig_body, $img_start + $img_st_close, $img_len);
|
||||
|
||||
Logger::log('found photo ' . $image, Logger::DEBUG);
|
||||
Logger::info('found photo', ['image' => $image]);
|
||||
|
||||
if (stristr($image, $site . '/photo/')) {
|
||||
// Only embed locally hosted photos
|
||||
|
|
@ -2949,7 +2746,7 @@ class Item
|
|||
$photo_img = Photo::getImageForPhoto($photo);
|
||||
// If a custom width and height were specified, apply before embedding
|
||||
if (preg_match("/\[img\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) {
|
||||
Logger::log('scaling photo', Logger::DEBUG);
|
||||
Logger::info('scaling photo');
|
||||
|
||||
$width = intval($match[1]);
|
||||
$height = intval($match[2]);
|
||||
|
|
@ -2960,9 +2757,9 @@ class Item
|
|||
$data = $photo_img->asString();
|
||||
$type = $photo_img->getType();
|
||||
|
||||
Logger::log('replacing photo', Logger::DEBUG);
|
||||
Logger::info('replacing photo');
|
||||
$image = 'data:' . $type . ';base64,' . base64_encode($data);
|
||||
Logger::log('replaced: ' . $image, Logger::DATA);
|
||||
Logger::debug('replaced', ['image' => $image]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3032,37 +2829,13 @@ class Item
|
|||
return $recipients;
|
||||
}
|
||||
|
||||
public static function getFeedTags($item)
|
||||
{
|
||||
$ret = [];
|
||||
$matches = false;
|
||||
$cnt = preg_match_all('|\#\[url\=(.*?)\](.*?)\[\/url\]|', $item['tag'], $matches);
|
||||
if ($cnt) {
|
||||
for ($x = 0; $x < $cnt; $x ++) {
|
||||
if ($matches[1][$x]) {
|
||||
$ret[$matches[2][$x]] = ['#', $matches[1][$x], $matches[2][$x]];
|
||||
}
|
||||
}
|
||||
}
|
||||
$matches = false;
|
||||
$cnt = preg_match_all('|\@\[url\=(.*?)\](.*?)\[\/url\]|', $item['tag'], $matches);
|
||||
if ($cnt) {
|
||||
for ($x = 0; $x < $cnt; $x ++) {
|
||||
if ($matches[1][$x]) {
|
||||
$ret[] = ['@', $matches[1][$x], $matches[2][$x]];
|
||||
}
|
||||
}
|
||||
}
|
||||
return $ret;
|
||||
}
|
||||
|
||||
public static function expire($uid, $days, $network = "", $force = false)
|
||||
{
|
||||
if (!$uid || ($days < 1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$condition = ["`uid` = ? AND NOT `deleted` AND `id` = `parent` AND `gravity` = ?",
|
||||
$condition = ["`uid` = ? AND NOT `deleted` AND `gravity` = ?",
|
||||
$uid, GRAVITY_PARENT];
|
||||
|
||||
/*
|
||||
|
|
@ -3163,39 +2936,6 @@ class Item
|
|||
return false;
|
||||
}
|
||||
|
||||
switch ($verb) {
|
||||
case 'like':
|
||||
case 'unlike':
|
||||
$activity = Activity::LIKE;
|
||||
break;
|
||||
case 'dislike':
|
||||
case 'undislike':
|
||||
$activity = Activity::DISLIKE;
|
||||
break;
|
||||
case 'attendyes':
|
||||
case 'unattendyes':
|
||||
$activity = Activity::ATTEND;
|
||||
break;
|
||||
case 'attendno':
|
||||
case 'unattendno':
|
||||
$activity = Activity::ATTENDNO;
|
||||
break;
|
||||
case 'attendmaybe':
|
||||
case 'unattendmaybe':
|
||||
$activity = Activity::ATTENDMAYBE;
|
||||
break;
|
||||
case 'follow':
|
||||
case 'unfollow':
|
||||
$activity = Activity::FOLLOW;
|
||||
break;
|
||||
default:
|
||||
Logger::log('like: unknown verb ' . $verb . ' for item ' . $item_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Enable activity toggling instead of on/off
|
||||
$event_verb_flag = $activity === Activity::ATTEND || $activity === Activity::ATTENDNO || $activity === Activity::ATTENDMAYBE;
|
||||
|
||||
Logger::log('like: verb ' . $verb . ' item ' . $item_id);
|
||||
|
||||
$item = self::selectFirst(self::ITEM_FIELDLIST, ['`id` = ? OR `uri` = ?', $item_id, $item_id]);
|
||||
|
|
@ -3244,37 +2984,95 @@ class Item
|
|||
}
|
||||
}
|
||||
|
||||
$activity = null;
|
||||
switch ($verb) {
|
||||
case 'like':
|
||||
case 'unlike':
|
||||
$activity = Activity::LIKE;
|
||||
break;
|
||||
case 'dislike':
|
||||
case 'undislike':
|
||||
$activity = Activity::DISLIKE;
|
||||
break;
|
||||
case 'attendyes':
|
||||
case 'unattendyes':
|
||||
$activity = Activity::ATTEND;
|
||||
break;
|
||||
case 'attendno':
|
||||
case 'unattendno':
|
||||
$activity = Activity::ATTENDNO;
|
||||
break;
|
||||
case 'attendmaybe':
|
||||
case 'unattendmaybe':
|
||||
$activity = Activity::ATTENDMAYBE;
|
||||
break;
|
||||
case 'follow':
|
||||
case 'unfollow':
|
||||
$activity = Activity::FOLLOW;
|
||||
break;
|
||||
default:
|
||||
Logger::log('like: unknown verb ' . $verb . ' for item ' . $item_id);
|
||||
return false;
|
||||
}
|
||||
|
||||
$mode = Strings::startsWith($verb, 'un') ? 'delete' : 'create';
|
||||
|
||||
// Enable activity toggling instead of on/off
|
||||
$event_verb_flag = $activity === Activity::ATTEND || $activity === Activity::ATTENDNO || $activity === Activity::ATTENDMAYBE;
|
||||
|
||||
// Look for an existing verb row
|
||||
// event participation are essentially radio toggles. If you make a subsequent choice,
|
||||
// we need to eradicate your first choice.
|
||||
// Event participation activities are mutually exclusive, only one of them can exist at all times.
|
||||
if ($event_verb_flag) {
|
||||
$verbs = [Activity::ATTEND, Activity::ATTENDNO, Activity::ATTENDMAYBE];
|
||||
|
||||
// Translate to the index based activity index
|
||||
$activities = [];
|
||||
$vids = [];
|
||||
foreach ($verbs as $verb) {
|
||||
$activities[] = self::activityToIndex($verb);
|
||||
$vids[] = Verb::getID($verb);
|
||||
}
|
||||
} else {
|
||||
$activities = self::activityToIndex($activity);
|
||||
$vids = Verb::getID($activity);
|
||||
}
|
||||
|
||||
$condition = ['activity' => $activities, 'deleted' => false, 'gravity' => GRAVITY_ACTIVITY,
|
||||
$condition = ['vid' => $vids, 'deleted' => false, 'gravity' => GRAVITY_ACTIVITY,
|
||||
'author-id' => $author_id, 'uid' => $item['uid'], 'thr-parent' => $item_uri];
|
||||
|
||||
$like_item = self::selectFirst(['id', 'guid', 'verb'], $condition);
|
||||
|
||||
// If it exists, mark it as deleted
|
||||
if (DBA::isResult($like_item)) {
|
||||
self::markForDeletionById($like_item['id']);
|
||||
/**
|
||||
* Truth table for existing activities
|
||||
*
|
||||
* | Inputs || Outputs |
|
||||
* |----------------------------||-------------------|
|
||||
* | Mode | Event | Same verb || Delete? | Return? |
|
||||
* |--------|-------|-----------||---------|---------|
|
||||
* | create | Yes | Yes || No | Yes |
|
||||
* | create | Yes | No || Yes | No |
|
||||
* | create | No | Yes || No | Yes |
|
||||
* | create | No | No || N/A† |
|
||||
* | delete | Yes | Yes || Yes | N/A‡ |
|
||||
* | delete | Yes | No || No | N/A‡ |
|
||||
* | delete | No | Yes || Yes | N/A‡ |
|
||||
* | delete | No | No || N/A† |
|
||||
* |--------|-------|-----------||---------|---------|
|
||||
* | A | B | C || A xor C | !B or C |
|
||||
*
|
||||
* † Can't happen: It's impossible to find an existing non-event activity without
|
||||
* the same verb because we are only looking for this single verb.
|
||||
*
|
||||
* ‡ The "mode = delete" is returning early whether an existing activity was found or not.
|
||||
*/
|
||||
if ($mode == 'create' xor $like_item['verb'] == $activity) {
|
||||
self::markForDeletionById($like_item['id']);
|
||||
}
|
||||
|
||||
if (!$event_verb_flag || $like_item['verb'] == $activity) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Verb is "un-something", just trying to delete existing entries
|
||||
if (strpos($verb, 'un') === 0) {
|
||||
// No need to go further if we aren't creating anything
|
||||
if ($mode == 'delete') {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -3327,7 +3125,7 @@ class Item
|
|||
private static function addThread($itemid, $onlyshadow = false)
|
||||
{
|
||||
$fields = ['uid', 'created', 'edited', 'commented', 'received', 'changed', 'wall', 'private', 'pubmail',
|
||||
'moderated', 'visible', 'starred', 'contact-id', 'post-type',
|
||||
'moderated', 'visible', 'starred', 'contact-id', 'post-type', 'uri-id',
|
||||
'deleted', 'origin', 'forum_mode', 'mention', 'network', 'author-id', 'owner-id'];
|
||||
$condition = ["`id` = ? AND (`parent` = ? OR `parent` = 0)", $itemid, $itemid];
|
||||
$item = self::selectFirst($fields, $condition);
|
||||
|
|
@ -3341,14 +3139,14 @@ class Item
|
|||
if (!$onlyshadow) {
|
||||
$result = DBA::insert('thread', $item);
|
||||
|
||||
Logger::log("Add thread for item ".$itemid." - ".print_r($result, true), Logger::DEBUG);
|
||||
Logger::info('Add thread', ['item' => $itemid, 'result' => $result]);
|
||||
}
|
||||
}
|
||||
|
||||
private static function updateThread($itemid, $setmention = false)
|
||||
{
|
||||
$fields = ['uid', 'guid', 'created', 'edited', 'commented', 'received', 'changed', 'post-type',
|
||||
'wall', 'private', 'pubmail', 'moderated', 'visible', 'starred', 'contact-id',
|
||||
'wall', 'private', 'pubmail', 'moderated', 'visible', 'starred', 'contact-id', 'uri-id',
|
||||
'deleted', 'origin', 'forum_mode', 'network', 'author-id', 'owner-id'];
|
||||
$condition = ["`id` = ? AND (`parent` = ? OR `parent` = 0)", $itemid, $itemid];
|
||||
|
||||
|
|
@ -3371,20 +3169,20 @@ class Item
|
|||
|
||||
$result = DBA::update('thread', $fields, ['iid' => $itemid]);
|
||||
|
||||
Logger::log("Update thread for item ".$itemid." - guid ".$item["guid"]." - ".(int)$result, Logger::DEBUG);
|
||||
Logger::info('Update thread', ['item' => $itemid, 'guid' => $item["guid"], 'result' => $result]);
|
||||
}
|
||||
|
||||
private static function deleteThread($itemid, $itemuri = "")
|
||||
{
|
||||
$item = DBA::selectFirst('thread', ['uid'], ['iid' => $itemid]);
|
||||
if (!DBA::isResult($item)) {
|
||||
Logger::log('No thread found for id '.$itemid, Logger::DEBUG);
|
||||
Logger::info('No thread found', ['id' => $itemid]);
|
||||
return;
|
||||
}
|
||||
|
||||
$result = DBA::delete('thread', ['iid' => $itemid], ['cascade' => false]);
|
||||
|
||||
Logger::log("deleteThread: Deleted thread for item ".$itemid." - ".print_r($result, true), Logger::DEBUG);
|
||||
Logger::info('Deleted thread', ['item' => $itemid, 'result' => $result]);
|
||||
|
||||
if ($itemuri != "") {
|
||||
$condition = ["`uri` = ? AND NOT `deleted` AND NOT (`uid` IN (?, 0))", $itemuri, $item["uid"]];
|
||||
|
|
@ -3444,9 +3242,9 @@ class Item
|
|||
return DI::l10n()->t('event');
|
||||
} elseif (!empty($item['resource-id'])) {
|
||||
return DI::l10n()->t('photo');
|
||||
} elseif (!empty($item['verb']) && $item['verb'] !== Activity::POST) {
|
||||
} elseif ($item['gravity'] == GRAVITY_ACTIVITY) {
|
||||
return DI::l10n()->t('activity');
|
||||
} elseif ($item['id'] != $item['parent']) {
|
||||
} elseif ($item['gravity'] == GRAVITY_COMMENT) {
|
||||
return DI::l10n()->t('comment');
|
||||
}
|
||||
|
||||
|
|
@ -3563,7 +3361,7 @@ class Item
|
|||
return $ev;
|
||||
}
|
||||
|
||||
$tags = Term::populateTagsFromItem($item);
|
||||
$tags = Tag::populateFromItem($item);
|
||||
|
||||
$item['tags'] = $tags['tags'];
|
||||
$item['hashtags'] = $tags['hashtags'];
|
||||
|
|
@ -3691,9 +3489,7 @@ class Item
|
|||
*/
|
||||
public static function getPlink($item)
|
||||
{
|
||||
$a = DI::app();
|
||||
|
||||
if ($a->user['nickname'] != "") {
|
||||
if (local_user()) {
|
||||
$ret = [
|
||||
'href' => "display/" . $item['guid'],
|
||||
'orig' => "display/" . $item['guid'],
|
||||
|
|
@ -3705,7 +3501,6 @@ class Item
|
|||
$ret["href"] = DI::baseUrl()->remove($item['plink']);
|
||||
$ret["title"] = DI::l10n()->t('link to source');
|
||||
}
|
||||
|
||||
} elseif (!empty($item['plink']) && ($item['private'] != self::PRIVATE)) {
|
||||
$ret = [
|
||||
'href' => $item['plink'],
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
namespace Friendica\Model;
|
||||
|
||||
use Friendica\Content\Text;
|
||||
use Friendica\Content\Text\BBCode;
|
||||
use Friendica\Core\Protocol;
|
||||
use Friendica\DI;
|
||||
|
||||
|
|
@ -41,7 +42,7 @@ class ItemContent
|
|||
* @see \Friendica\Content\Text\BBCode::getAttachedData
|
||||
*
|
||||
*/
|
||||
public static function getPlaintextPost($item, $limit = 0, $includedlinks = false, $htmlmode = 2, $target_network = '')
|
||||
public static function getPlaintextPost($item, $limit = 0, $includedlinks = false, $htmlmode = BBCode::API, $target_network = '')
|
||||
{
|
||||
// Remove hashtags
|
||||
$URLSearchString = '^\[\]';
|
||||
|
|
@ -79,11 +80,11 @@ class ItemContent
|
|||
}
|
||||
} else {// Try to guess the correct target network
|
||||
switch ($htmlmode) {
|
||||
case 8:
|
||||
case BBCode::TWITTER:
|
||||
$abstract = Text\BBCode::getAbstract($item['body'], Protocol::TWITTER);
|
||||
break;
|
||||
|
||||
case 7:
|
||||
case BBCode::OSTATUS:
|
||||
$abstract = Text\BBCode::getAbstract($item['body'], Protocol::STATUSNET);
|
||||
break;
|
||||
|
||||
|
|
@ -139,8 +140,8 @@ class ItemContent
|
|||
$msg = trim(str_replace($link, '', $msg));
|
||||
} elseif (($limit == 0) || ($pos < $limit)) {
|
||||
// The limit has to be increased since it will be shortened - but not now
|
||||
// Only do it with Twitter (htmlmode = 8)
|
||||
if (($limit > 0) && (strlen($link) > 23) && ($htmlmode == 8)) {
|
||||
// Only do it with Twitter
|
||||
if (($limit > 0) && (strlen($link) > 23) && ($htmlmode == BBCode::TWITTER)) {
|
||||
$limit = $limit - 23 + strlen($link);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -42,13 +42,17 @@ class ItemURI
|
|||
DBA::insert('item-uri', $fields, true);
|
||||
}
|
||||
|
||||
$itemuri = DBA::selectFirst('item-uri', ['id'], ['uri' => $uri]);
|
||||
$itemuri = DBA::selectFirst('item-uri', ['id', 'guid'], ['uri' => $uri]);
|
||||
|
||||
if (!DBA::isResult($itemuri)) {
|
||||
// This shouldn't happen
|
||||
return null;
|
||||
}
|
||||
|
||||
if (empty($itemuri['guid']) && !empty($fields['guid'])) {
|
||||
DBA::update('item-uri', ['guid' => $fields['guid']], ['id' => $itemuri['id']]);
|
||||
}
|
||||
|
||||
return $itemuri['id'];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ class Mail
|
|||
'to_email' => $user['email'],
|
||||
'uid' => $user['uid'],
|
||||
'item' => $msg,
|
||||
'parent' => 0,
|
||||
'parent' => $msg['id'],
|
||||
'source_name' => $msg['from-name'],
|
||||
'source_link' => $msg['from-url'],
|
||||
'source_photo' => $msg['from-photo'],
|
||||
|
|
@ -268,7 +268,6 @@ class Mail
|
|||
$uri = Item::newURI(local_user(), $guid);
|
||||
|
||||
$me = Probe::uri($replyto);
|
||||
|
||||
if (!$me['name']) {
|
||||
return -2;
|
||||
}
|
||||
|
|
@ -277,10 +276,7 @@ class Mail
|
|||
|
||||
$recip_handle = $recipient['nickname'] . '@' . substr(DI::baseUrl(), strpos(DI::baseUrl(), '://') + 3);
|
||||
|
||||
$sender_nick = basename($replyto);
|
||||
$sender_host = substr($replyto, strpos($replyto, '://') + 3);
|
||||
$sender_host = substr($sender_host, 0, strpos($sender_host, '/'));
|
||||
$sender_handle = $sender_nick . '@' . $sender_host;
|
||||
$sender_handle = $me['addr'];
|
||||
|
||||
$handles = $recip_handle . ';' . $sender_handle;
|
||||
|
||||
|
|
@ -313,7 +309,7 @@ class Mail
|
|||
'reply' => 0,
|
||||
'replied' => 0,
|
||||
'uri' => $uri,
|
||||
'parent-uri' => $replyto,
|
||||
'parent-uri' => $me['url'],
|
||||
'created' => DateTimeFormat::utcNow(),
|
||||
'unknown' => 1
|
||||
]
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ class Notify extends BaseModel
|
|||
private function setNameCache()
|
||||
{
|
||||
try {
|
||||
$this->name_cache = strip_tags(BBCode::convert($this->source_name ?? ''));
|
||||
$this->name_cache = strip_tags(BBCode::convert($this->source_name));
|
||||
} catch (InternalServerErrorException $e) {
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -432,9 +432,7 @@ class Photo
|
|||
return false;
|
||||
}
|
||||
|
||||
if (empty($type)) {
|
||||
$type = Images::guessType($image_url, true);
|
||||
}
|
||||
$type = Images::getMimeTypeByData($img_str, $image_url, $type);
|
||||
|
||||
$Image = new Image($img_str, $type);
|
||||
if ($Image->isValid()) {
|
||||
|
|
|
|||
117
src/Model/Post/Category.php
Normal file
117
src/Model/Post/Category.php
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
<?php
|
||||
/**
|
||||
* @copyright Copyright (C) 2020, Friendica
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Friendica\Model\Post;
|
||||
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\Model\Item;
|
||||
use Friendica\Model\Tag;
|
||||
|
||||
/**
|
||||
* Class Category
|
||||
*
|
||||
* This Model class handles category table interactions.
|
||||
* This tables stores user-applied categories related to posts.
|
||||
*/
|
||||
class Category
|
||||
{
|
||||
const UNKNOWN = 0;
|
||||
const CATEGORY = 3;
|
||||
const FILE = 5;
|
||||
|
||||
/**
|
||||
* Generates the legacy item.file field string from an item ID.
|
||||
* Includes only file and category terms.
|
||||
*
|
||||
* @param int $item_id
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function getTextByURIId(int $uri_id, int $uid)
|
||||
{
|
||||
$file_text = '';
|
||||
|
||||
$tags = DBA::selectToArray('category-view', ['type', 'name'], ['uri-id' => $uri_id, 'uid' => $uid]);
|
||||
foreach ($tags as $tag) {
|
||||
if ($tag['type'] == self::CATEGORY) {
|
||||
$file_text .= '<' . $tag['name'] . '>';
|
||||
} else {
|
||||
$file_text .= '[' . $tag['name'] . ']';
|
||||
}
|
||||
}
|
||||
|
||||
return $file_text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts new terms for the provided item ID based on the legacy item.file field BBCode content.
|
||||
* Deletes all previous file terms for the same item ID.
|
||||
*
|
||||
* @param integer $item_id item id
|
||||
* @param $files
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function storeTextByURIId(int $uri_id, int $uid, string $files)
|
||||
{
|
||||
$message = Item::selectFirst(['deleted'], ['uri-id' => $uri_id, 'uid' => $uid]);
|
||||
if (DBA::isResult($message)) {
|
||||
// Clean up all tags
|
||||
DBA::delete('post-category', ['uri-id' => $uri_id, 'uid' => $uid]);
|
||||
|
||||
if ($message['deleted']) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (preg_match_all("/\[(.*?)\]/ism", $files, $result)) {
|
||||
foreach ($result[1] as $file) {
|
||||
$tagid = Tag::getID($file);
|
||||
if (empty($tagid)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
DBA::insert('post-category', [
|
||||
'uri-id' => $uri_id,
|
||||
'uid' => $uid,
|
||||
'type' => self::FILE,
|
||||
'tid' => $tagid
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
if (preg_match_all("/\<(.*?)\>/ism", $files, $result)) {
|
||||
foreach ($result[1] as $file) {
|
||||
$tagid = Tag::getID($file);
|
||||
if (empty($tagid)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
DBA::insert('post-category', [
|
||||
'uri-id' => $uri_id,
|
||||
'uid' => $uid,
|
||||
'type' => self::CATEGORY,
|
||||
'tid' => $tagid
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -19,12 +19,12 @@
|
|||
*
|
||||
*/
|
||||
|
||||
namespace Friendica\Model;
|
||||
namespace Friendica\Model\Post;
|
||||
|
||||
use Friendica\Database\DBA;
|
||||
use \BadMethodCallException;
|
||||
|
||||
class ItemDeliveryData
|
||||
class DeliveryData
|
||||
{
|
||||
const LEGACY_FIELD_LIST = [
|
||||
// Legacy fields moved from item table
|
||||
|
|
@ -55,7 +55,7 @@ class ItemDeliveryData
|
|||
public static function extractFields(array &$fields)
|
||||
{
|
||||
$delivery_data = [];
|
||||
foreach (array_merge(ItemDeliveryData::FIELD_LIST, ItemDeliveryData::LEGACY_FIELD_LIST) as $key => $field) {
|
||||
foreach (array_merge(self::FIELD_LIST, self::LEGACY_FIELD_LIST) as $key => $field) {
|
||||
if (is_int($key) && isset($fields[$field])) {
|
||||
// Legacy field moved from item table
|
||||
$delivery_data[$field] = $fields[$field];
|
||||
|
|
@ -71,16 +71,16 @@ class ItemDeliveryData
|
|||
}
|
||||
|
||||
/**
|
||||
* Increments the queue_done for the given item ID.
|
||||
* Increments the queue_done for the given URI ID.
|
||||
*
|
||||
* Avoids racing condition between multiple delivery threads.
|
||||
*
|
||||
* @param integer $item_id
|
||||
* @param integer $uri_id
|
||||
* @param integer $protocol
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function incrementQueueDone($item_id, $protocol = 0)
|
||||
public static function incrementQueueDone(int $uri_id, int $protocol = 0)
|
||||
{
|
||||
$sql = '';
|
||||
|
||||
|
|
@ -102,69 +102,69 @@ class ItemDeliveryData
|
|||
break;
|
||||
}
|
||||
|
||||
return DBA::e('UPDATE `item-delivery-data` SET `queue_done` = `queue_done` + 1' . $sql . ' WHERE `iid` = ?', $item_id);
|
||||
return DBA::e('UPDATE `post-delivery-data` SET `queue_done` = `queue_done` + 1' . $sql . ' WHERE `uri-id` = ?', $uri_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments the queue_failed for the given item ID.
|
||||
* Increments the queue_failed for the given URI ID.
|
||||
*
|
||||
* Avoids racing condition between multiple delivery threads.
|
||||
*
|
||||
* @param integer $item_id
|
||||
* @param integer $uri_id
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function incrementQueueFailed($item_id)
|
||||
public static function incrementQueueFailed(int $uri_id)
|
||||
{
|
||||
return DBA::e('UPDATE `item-delivery-data` SET `queue_failed` = `queue_failed` + 1 WHERE `iid` = ?', $item_id);
|
||||
return DBA::e('UPDATE `post-delivery-data` SET `queue_failed` = `queue_failed` + 1 WHERE `uri-id` = ?', $uri_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Increments the queue_count for the given item ID.
|
||||
* Increments the queue_count for the given URI ID.
|
||||
*
|
||||
* @param integer $item_id
|
||||
* @param integer $uri_id
|
||||
* @param integer $increment
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function incrementQueueCount(int $item_id, int $increment = 1)
|
||||
public static function incrementQueueCount(int $uri_id, int $increment = 1)
|
||||
{
|
||||
return DBA::e('UPDATE `item-delivery-data` SET `queue_count` = `queue_count` + ? WHERE `iid` = ?', $increment, $item_id);
|
||||
return DBA::e('UPDATE `post-delivery-data` SET `queue_count` = `queue_count` + ? WHERE `uri-id` = ?', $increment, $uri_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a new item delivery data entry
|
||||
* Insert a new URI delivery data entry
|
||||
*
|
||||
* @param integer $item_id
|
||||
* @param integer $uri_id
|
||||
* @param array $fields
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function insert($item_id, array $fields)
|
||||
public static function insert(int $uri_id, array $fields)
|
||||
{
|
||||
if (empty($item_id)) {
|
||||
throw new BadMethodCallException('Empty item_id');
|
||||
if (empty($uri_id)) {
|
||||
throw new BadMethodCallException('Empty URI_id');
|
||||
}
|
||||
|
||||
$fields['iid'] = $item_id;
|
||||
$fields['uri-id'] = $uri_id;
|
||||
|
||||
return DBA::insert('item-delivery-data', $fields);
|
||||
return DBA::insert('post-delivery-data', $fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update/Insert item delivery data
|
||||
* Update/Insert URI delivery data
|
||||
*
|
||||
* If you want to update queue_done, please use incrementQueueDone instead.
|
||||
*
|
||||
* @param integer $item_id
|
||||
* @param integer $uri_id
|
||||
* @param array $fields
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function update($item_id, array $fields)
|
||||
public static function update(int $uri_id, array $fields)
|
||||
{
|
||||
if (empty($item_id)) {
|
||||
throw new BadMethodCallException('Empty item_id');
|
||||
if (empty($uri_id)) {
|
||||
throw new BadMethodCallException('Empty URI_id');
|
||||
}
|
||||
|
||||
if (empty($fields)) {
|
||||
|
|
@ -172,22 +172,22 @@ class ItemDeliveryData
|
|||
return true;
|
||||
}
|
||||
|
||||
return DBA::update('item-delivery-data', $fields, ['iid' => $item_id], true);
|
||||
return DBA::update('post-delivery-data', $fields, ['uri-id' => $uri_id], true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete item delivery data
|
||||
* Delete URI delivery data
|
||||
*
|
||||
* @param integer $item_id
|
||||
* @param integer $uri_id
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function delete($item_id)
|
||||
public static function delete(int $uri_id)
|
||||
{
|
||||
if (empty($item_id)) {
|
||||
throw new BadMethodCallException('Empty item_id');
|
||||
if (empty($uri_id)) {
|
||||
throw new BadMethodCallException('Empty URI_id');
|
||||
}
|
||||
|
||||
return DBA::delete('item-delivery-data', ['iid' => $item_id]);
|
||||
return DBA::delete('post-delivery-data', ['uri-id' => $uri_id]);
|
||||
}
|
||||
}
|
||||
|
|
@ -85,7 +85,7 @@ class Process
|
|||
self::deleteByPid($process['pid']);
|
||||
}
|
||||
}
|
||||
|
||||
DBA::close($processes);
|
||||
DBA::commit();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,6 @@ use Friendica\Content\Widget\ContactBlock;
|
|||
use Friendica\Core\Cache\Duration;
|
||||
use Friendica\Core\Hook;
|
||||
use Friendica\Core\Logger;
|
||||
use Friendica\Network\Probe;
|
||||
use Friendica\Core\Protocol;
|
||||
use Friendica\Core\Renderer;
|
||||
use Friendica\Core\Session;
|
||||
|
|
@ -234,19 +233,7 @@ class Profile
|
|||
*/
|
||||
public static function getByNickname($nickname, $uid = 0)
|
||||
{
|
||||
$profile = DBA::fetchFirst(
|
||||
"SELECT `contact`.`id` AS `contact_id`, `contact`.`photo` AS `contact_photo`,
|
||||
`contact`.`thumb` AS `contact_thumb`, `contact`.`micro` AS `contact_micro`,
|
||||
`profile`.*,
|
||||
`contact`.`avatar-date` AS picdate, `contact`.`addr`, `contact`.`url`, `user`.*
|
||||
FROM `profile`
|
||||
INNER JOIN `contact` on `contact`.`uid` = `profile`.`uid` AND `contact`.`self`
|
||||
INNER JOIN `user` ON `profile`.`uid` = `user`.`uid`
|
||||
WHERE `user`.`nickname` = ? AND `profile`.`uid` = ? LIMIT 1",
|
||||
$nickname,
|
||||
intval($uid)
|
||||
);
|
||||
|
||||
$profile = DBA::selectFirst('owner-view', [], ['nickname' => $nickname, 'uid' => $uid]);
|
||||
return $profile;
|
||||
}
|
||||
|
||||
|
|
@ -271,7 +258,7 @@ class Profile
|
|||
* @hooks 'profile_sidebar'
|
||||
* array $arr
|
||||
*/
|
||||
private static function sidebar(App $a, $profile, $block = 0, $show_connect = true)
|
||||
private static function sidebar(App $a, array $profile, $block = 0, $show_connect = true)
|
||||
{
|
||||
$o = '';
|
||||
$location = false;
|
||||
|
|
@ -279,7 +266,8 @@ class Profile
|
|||
// This function can also use contact information in $profile
|
||||
$is_contact = !empty($profile['cid']);
|
||||
|
||||
if (!is_array($profile) && !count($profile)) {
|
||||
if (empty($profile['nickname'])) {
|
||||
Logger::warning('Received profile with no nickname', ['profile' => $profile, 'callstack' => System::callstack(10)]);
|
||||
return $o;
|
||||
}
|
||||
|
||||
|
|
@ -304,8 +292,6 @@ class Profile
|
|||
$subscribe_feed_link = null;
|
||||
$wallmessage_link = null;
|
||||
|
||||
|
||||
|
||||
$visitor_contact = [];
|
||||
if (!empty($profile['uid']) && self::getMyURL()) {
|
||||
$visitor_contact = Contact::selectFirst(['rel'], ['uid' => $profile['uid'], 'nurl' => Strings::normaliseLink(self::getMyURL())]);
|
||||
|
|
@ -399,9 +385,9 @@ class Profile
|
|||
'fullname' => $profile['name'],
|
||||
'firstname' => $firstname,
|
||||
'lastname' => $lastname,
|
||||
'photo300' => $profile['contact_photo'] ?? '',
|
||||
'photo100' => $profile['contact_thumb'] ?? '',
|
||||
'photo50' => $profile['contact_micro'] ?? '',
|
||||
'photo300' => $profile['photo'] ?? '',
|
||||
'photo100' => $profile['thumb'] ?? '',
|
||||
'photo50' => $profile['micro'] ?? '',
|
||||
];
|
||||
} else {
|
||||
$diaspora = false;
|
||||
|
|
@ -410,18 +396,15 @@ class Profile
|
|||
$contact_block = '';
|
||||
$updated = '';
|
||||
$contact_count = 0;
|
||||
|
||||
if (!empty($profile['last-item'])) {
|
||||
$updated = date('c', strtotime($profile['last-item']));
|
||||
}
|
||||
|
||||
if (!$block) {
|
||||
$contact_block = ContactBlock::getHTML($a->profile);
|
||||
|
||||
if (is_array($a->profile) && !$a->profile['hide-friends']) {
|
||||
$r = q(
|
||||
"SELECT `gcontact`.`updated` FROM `contact` INNER JOIN `gcontact` WHERE `gcontact`.`nurl` = `contact`.`nurl` AND `self` AND `uid` = %d LIMIT 1",
|
||||
intval($a->profile['uid'])
|
||||
);
|
||||
if (DBA::isResult($r)) {
|
||||
$updated = date('c', strtotime($r[0]['updated']));
|
||||
}
|
||||
|
||||
$contact_count = DBA::count('contact', [
|
||||
'uid' => $profile['uid'],
|
||||
'self' => false,
|
||||
|
|
@ -616,7 +599,7 @@ class Profile
|
|||
|
||||
while ($rr = DBA::fetch($s)) {
|
||||
$condition = ['parent-uri' => $rr['uri'], 'uid' => $rr['uid'], 'author-id' => public_contact(),
|
||||
'activity' => [Item::activityToIndex( Activity::ATTEND), Item::activityToIndex(Activity::ATTENDMAYBE)],
|
||||
'vid' => [Verb::getID(Activity::ATTEND), Verb::getID(Activity::ATTENDMAYBE)],
|
||||
'visible' => true, 'deleted' => false];
|
||||
if (!Item::exists($condition)) {
|
||||
continue;
|
||||
|
|
@ -787,7 +770,7 @@ class Profile
|
|||
$_SESSION['visitor_handle'] = $visitor['addr'];
|
||||
$_SESSION['visitor_home'] = $visitor['url'];
|
||||
$_SESSION['my_url'] = $visitor['url'];
|
||||
$_SESSION['remote_comment'] = Probe::getRemoteFollowLink($visitor['url']);
|
||||
$_SESSION['remote_comment'] = $visitor['subscribe'];
|
||||
|
||||
Session::setVisitorsContacts();
|
||||
|
||||
|
|
@ -902,87 +885,37 @@ class Profile
|
|||
*/
|
||||
public static function searchProfiles($start = 0, $count = 100, $search = null)
|
||||
{
|
||||
$publish = (DI::config()->get('system', 'publish_all') ? '' : "`publish` = 1");
|
||||
$total = 0;
|
||||
|
||||
if (!empty($search)) {
|
||||
$publish = (DI::config()->get('system', 'publish_all') ? '' : "AND `publish` ");
|
||||
$searchTerm = '%' . $search . '%';
|
||||
$cnt = DBA::fetchFirst("SELECT COUNT(*) AS `total`
|
||||
FROM `profile`
|
||||
LEFT JOIN `user` ON `user`.`uid` = `profile`.`uid`
|
||||
WHERE $publish AND NOT `user`.`blocked` AND NOT `user`.`account_removed`
|
||||
AND ((`profile`.`name` LIKE ?) OR
|
||||
(`user`.`nickname` LIKE ?) OR
|
||||
(`profile`.`about` LIKE ?) OR
|
||||
(`profile`.`locality` LIKE ?) OR
|
||||
(`profile`.`region` LIKE ?) OR
|
||||
(`profile`.`country-name` LIKE ?) OR
|
||||
(`profile`.`pub_keywords` LIKE ?) OR
|
||||
(`profile`.`prv_keywords` LIKE ?))",
|
||||
$searchTerm, $searchTerm, $searchTerm, $searchTerm, $searchTerm, $searchTerm, $searchTerm, $searchTerm,
|
||||
$searchTerm, $searchTerm, $searchTerm, $searchTerm, $searchTerm, $searchTerm, $searchTerm);
|
||||
$condition = ["NOT `blocked` AND NOT `account_removed`
|
||||
$publish
|
||||
AND ((`name` LIKE ?) OR
|
||||
(`nickname` LIKE ?) OR
|
||||
(`about` LIKE ?) OR
|
||||
(`locality` LIKE ?) OR
|
||||
(`region` LIKE ?) OR
|
||||
(`country-name` LIKE ?) OR
|
||||
(`pub_keywords` LIKE ?) OR
|
||||
(`prv_keywords` LIKE ?))",
|
||||
$searchTerm, $searchTerm, $searchTerm, $searchTerm,
|
||||
$searchTerm, $searchTerm, $searchTerm, $searchTerm];
|
||||
} else {
|
||||
$cnt = DBA::fetchFirst("SELECT COUNT(*) AS `total`
|
||||
FROM `profile`
|
||||
LEFT JOIN `user` ON `user`.`uid` = `profile`.`uid`
|
||||
WHERE $publish AND NOT `user`.`blocked` AND NOT `user`.`account_removed`");
|
||||
}
|
||||
|
||||
if (DBA::isResult($cnt)) {
|
||||
$total = $cnt['total'];
|
||||
}
|
||||
|
||||
$order = " ORDER BY `name` ASC ";
|
||||
$profiles = [];
|
||||
|
||||
// If nothing found, don't try to select details
|
||||
if ($total > 0) {
|
||||
if (!empty($search)) {
|
||||
$searchTerm = '%' . $search . '%';
|
||||
|
||||
$profiles = DBA::p("SELECT `profile`.*, `profile`.`uid` AS `profile_uid`, `user`.`nickname`, `user`.`timezone` , `user`.`page-flags`,
|
||||
`contact`.`addr`, `contact`.`url` AS `profile_url`
|
||||
FROM `profile`
|
||||
LEFT JOIN `user` ON `user`.`uid` = `profile`.`uid`
|
||||
LEFT JOIN `contact` ON `contact`.`uid` = `user`.`uid`
|
||||
WHERE $publish AND NOT `user`.`blocked` AND NOT `user`.`account_removed` AND `contact`.`self`
|
||||
AND ((`profile`.`name` LIKE ?) OR
|
||||
(`user`.`nickname` LIKE ?) OR
|
||||
(`profile`.`about` LIKE ?) OR
|
||||
(`profile`.`locality` LIKE ?) OR
|
||||
(`profile`.`region` LIKE ?) OR
|
||||
(`profile`.`country-name` LIKE ?) OR
|
||||
(`profile`.`pub_keywords` LIKE ?) OR
|
||||
(`profile`.`prv_keywords` LIKE ?))
|
||||
$order LIMIT ?,?",
|
||||
$searchTerm, $searchTerm, $searchTerm, $searchTerm, $searchTerm, $searchTerm, $searchTerm, $searchTerm,
|
||||
$searchTerm, $searchTerm, $searchTerm, $searchTerm, $searchTerm, $searchTerm, $searchTerm,
|
||||
$start, $count
|
||||
);
|
||||
} else {
|
||||
$profiles = DBA::p("SELECT `profile`.*, `profile`.`uid` AS `profile_uid`, `user`.`nickname`, `user`.`timezone` , `user`.`page-flags`,
|
||||
`contact`.`addr`, `contact`.`url` AS `profile_url`
|
||||
FROM `profile`
|
||||
LEFT JOIN `user` ON `user`.`uid` = `profile`.`uid`
|
||||
LEFT JOIN `contact` ON `contact`.`uid` = `user`.`uid`
|
||||
WHERE $publish AND NOT `user`.`blocked` AND NOT `user`.`account_removed` AND `contact`.`self`
|
||||
$order LIMIT ?,?",
|
||||
$start, $count
|
||||
);
|
||||
$condition = ['blocked' => false, 'account_removed' => false];
|
||||
if (!DI::config()->get('system', 'publish_all')) {
|
||||
$condition['publish'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (DBA::isResult($profiles) && $total > 0) {
|
||||
return [
|
||||
'total' => $total,
|
||||
'entries' => DBA::toArray($profiles),
|
||||
];
|
||||
$total = DBA::count('owner-view', $condition);
|
||||
|
||||
// If nothing found, don't try to select details
|
||||
if ($total > 0) {
|
||||
$profiles = DBA::selectToArray('owner-view', [], $condition, ['order' => ['name'], 'limit' => [$start, $count]]);
|
||||
} else {
|
||||
return [
|
||||
'total' => $total,
|
||||
'entries' => [],
|
||||
];
|
||||
$profiles = [];
|
||||
}
|
||||
|
||||
return ['total' => $total, 'entries' => $profiles];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,15 +42,7 @@ class Register
|
|||
*/
|
||||
public static function getPending($start = 0, $count = Pager::ITEMS_PER_PAGE)
|
||||
{
|
||||
$stmt = DBA::p(
|
||||
"SELECT `register`.*, `contact`.`name`, `contact`.`url`, `contact`.`micro`, `user`.`email`, `contact`.`nick`
|
||||
FROM `register`
|
||||
INNER JOIN `contact` ON `register`.`uid` = `contact`.`uid`
|
||||
INNER JOIN `user` ON `register`.`uid` = `user`.`uid`
|
||||
LIMIT ?, ?", $start, $count
|
||||
);
|
||||
|
||||
return DBA::toArray($stmt);
|
||||
return DBA::selectToArray('pending-view', [], [], ['limit' => [$start, $count]]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -64,14 +56,7 @@ class Register
|
|||
*/
|
||||
public static function getPendingForUser(int $uid)
|
||||
{
|
||||
return DBA::fetchFirst(
|
||||
"SELECT `register`.*, `contact`.`name`, `contact`.`url`, `contact`.`micro`, `user`.`email`
|
||||
FROM `register`
|
||||
INNER JOIN `contact` ON `register`.`uid` = `contact`.`uid`
|
||||
INNER JOIN `user` ON `register`.`uid` = `user`.`uid`
|
||||
WHERE `register`.uid = ?",
|
||||
$uid
|
||||
);
|
||||
return DBA::selectFirst('pending-view', [], ['uid' => $uid, 'self' => true]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -82,13 +67,7 @@ class Register
|
|||
*/
|
||||
public static function getPendingCount()
|
||||
{
|
||||
$register = DBA::fetchFirst(
|
||||
"SELECT COUNT(*) AS `count`
|
||||
FROM `register`
|
||||
INNER JOIN `contact` ON `register`.`uid` = `contact`.`uid` AND `contact`.`self`"
|
||||
);
|
||||
|
||||
return $register['count'];
|
||||
return DBA::count('pending-view', ['self' => true]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ class Search
|
|||
while ($term = DBA::fetch($termsStmt)) {
|
||||
$tags[] = trim($term['term'], '#');
|
||||
}
|
||||
|
||||
DBA::close($termsStmt);
|
||||
return $tags;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
540
src/Model/Tag.php
Normal file
540
src/Model/Tag.php
Normal file
|
|
@ -0,0 +1,540 @@
|
|||
<?php
|
||||
/**
|
||||
* @copyright Copyright (C) 2020, Friendica
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Friendica\Model;
|
||||
|
||||
use Friendica\Content\Text\BBCode;
|
||||
use Friendica\Core\Cache\Duration;
|
||||
use Friendica\Core\Logger;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\DI;
|
||||
use Friendica\Util\Strings;
|
||||
|
||||
/**
|
||||
* Class Tag
|
||||
*
|
||||
* This Model class handles tag table interactions.
|
||||
* This tables stores relevant tags related to posts, like hashtags and mentions.
|
||||
*/
|
||||
class Tag
|
||||
{
|
||||
const UNKNOWN = 0;
|
||||
const HASHTAG = 1;
|
||||
const MENTION = 2;
|
||||
/**
|
||||
* An implicit mention is a mention in a comment body that is redundant with the threading information.
|
||||
*/
|
||||
const IMPLICIT_MENTION = 8;
|
||||
/**
|
||||
* An exclusive mention transfers the ownership of the post to the target account, usually a forum.
|
||||
*/
|
||||
const EXCLUSIVE_MENTION = 9;
|
||||
|
||||
const TAG_CHARACTER = [
|
||||
self::HASHTAG => '#',
|
||||
self::MENTION => '@',
|
||||
self::IMPLICIT_MENTION => '%',
|
||||
self::EXCLUSIVE_MENTION => '!',
|
||||
];
|
||||
|
||||
/**
|
||||
* Store tag/mention elements
|
||||
*
|
||||
* @param integer $uriid
|
||||
* @param integer $type
|
||||
* @param string $name
|
||||
* @param string $url
|
||||
* @param boolean $probing
|
||||
*/
|
||||
public static function store(int $uriid, int $type, string $name, string $url = '', $probing = true)
|
||||
{
|
||||
if ($type == self::HASHTAG) {
|
||||
// Remove some common "garbarge" from tags
|
||||
$name = trim($name, "\x00..\x20\xFF#!@,;.:'/?!^°$%".'"');
|
||||
|
||||
$tags = explode(self::TAG_CHARACTER[self::HASHTAG], $name);
|
||||
if (count($tags) > 1) {
|
||||
foreach ($tags as $tag) {
|
||||
self::store($uriid, $type, $tag, $url, $probing);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$cid = 0;
|
||||
$tagid = 0;
|
||||
|
||||
if (in_array($type, [self::MENTION, self::EXCLUSIVE_MENTION, self::IMPLICIT_MENTION])) {
|
||||
if (empty($url)) {
|
||||
// No mention without a contact url
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$probing) {
|
||||
$condition = ['nurl' => Strings::normaliseLink($url), 'uid' => 0, 'deleted' => false];
|
||||
$contact = DBA::selectFirst('contact', ['id'], $condition, ['order' => ['id']]);
|
||||
if (DBA::isResult($contact)) {
|
||||
$cid = $contact['id'];
|
||||
Logger::info('Got id for contact url', ['cid' => $cid, 'url' => $url]);
|
||||
}
|
||||
|
||||
if (empty($cid)) {
|
||||
$ssl_url = str_replace('http://', 'https://', $url);
|
||||
$condition = ['`alias` IN (?, ?, ?) AND `uid` = ? AND NOT `deleted`', $url, Strings::normaliseLink($url), $ssl_url, 0];
|
||||
$contact = DBA::selectFirst('contact', ['id'], $condition, ['order' => ['id']]);
|
||||
if (DBA::isResult($contact)) {
|
||||
$cid = $contact['id'];
|
||||
Logger::info('Got id for contact alias', ['cid' => $cid, 'url' => $url]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$cid = Contact::getIdForURL($url, 0, true);
|
||||
Logger::info('Got id by probing', ['cid' => $cid, 'url' => $url]);
|
||||
}
|
||||
|
||||
if (empty($cid)) {
|
||||
// The contact wasn't found in the system (most likely some dead account)
|
||||
// We ensure that we only store a single entry by overwriting the previous name
|
||||
Logger::info('Contact not found, updating tag', ['url' => $url, 'name' => $name]);
|
||||
DBA::update('tag', ['name' => substr($name, 0, 96)], ['url' => $url]);
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($cid)) {
|
||||
if (($type != self::HASHTAG) && !empty($url) && ($url != $name)) {
|
||||
$url = strtolower($url);
|
||||
} else {
|
||||
$url = '';
|
||||
}
|
||||
|
||||
$tagid = self::getID($name, $url);
|
||||
if (empty($tagid)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$fields = ['uri-id' => $uriid, 'type' => $type, 'tid' => $tagid, 'cid' => $cid];
|
||||
|
||||
if (in_array($type, [self::MENTION, self::EXCLUSIVE_MENTION, self::IMPLICIT_MENTION])) {
|
||||
$condition = $fields;
|
||||
$condition['type'] = [self::MENTION, self::EXCLUSIVE_MENTION, self::IMPLICIT_MENTION];
|
||||
if (DBA::exists('post-tag', $condition)) {
|
||||
Logger::info('Tag already exists', $fields);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
DBA::insert('post-tag', $fields, true);
|
||||
|
||||
Logger::info('Stored tag/mention', ['uri-id' => $uriid, 'tag-id' => $tagid, 'contact-id' => $cid, 'name' => $name, 'type' => $type, 'callstack' => System::callstack(8)]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a tag id for a given tag name and url
|
||||
*
|
||||
* @param string $name
|
||||
* @param string $url
|
||||
* @return void
|
||||
*/
|
||||
public static function getID(string $name, string $url = '')
|
||||
{
|
||||
$fields = ['name' => substr($name, 0, 96), 'url' => $url];
|
||||
|
||||
$tag = DBA::selectFirst('tag', ['id'], $fields);
|
||||
if (DBA::isResult($tag)) {
|
||||
return $tag['id'];
|
||||
}
|
||||
|
||||
DBA::insert('tag', $fields, true);
|
||||
$tid = DBA::lastInsertId();
|
||||
if (!empty($tid)) {
|
||||
return $tid;
|
||||
}
|
||||
|
||||
Logger::error('No tag id created', $fields);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store tag/mention elements
|
||||
*
|
||||
* @param integer $uriid
|
||||
* @param string $hash
|
||||
* @param string $name
|
||||
* @param string $url
|
||||
* @param boolean $probing
|
||||
*/
|
||||
public static function storeByHash(int $uriid, string $hash, string $name, string $url = '', $probing = true)
|
||||
{
|
||||
$type = self::getTypeForHash($hash);
|
||||
if ($type == self::UNKNOWN) {
|
||||
return;
|
||||
}
|
||||
|
||||
self::store($uriid, $type, $name, $url, $probing);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store tags and mentions from the body
|
||||
*
|
||||
* @param integer $uriid URI-Id
|
||||
* @param string $body Body of the post
|
||||
* @param string $tags Accepted tags
|
||||
* @param boolean $probing Perform a probing for contacts, adding them if needed
|
||||
*/
|
||||
public static function storeFromBody(int $uriid, string $body, string $tags = null, $probing = true)
|
||||
{
|
||||
if (is_null($tags)) {
|
||||
$tags = self::TAG_CHARACTER[self::HASHTAG] . self::TAG_CHARACTER[self::MENTION] . self::TAG_CHARACTER[self::EXCLUSIVE_MENTION];
|
||||
}
|
||||
|
||||
Logger::info('Check for tags', ['uri-id' => $uriid, 'hash' => $tags, 'callstack' => System::callstack()]);
|
||||
|
||||
if (!preg_match_all("/([" . $tags . "])\[url\=([^\[\]]*)\]([^\[\]]*)\[\/url\]/ism", $body, $result, PREG_SET_ORDER)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Logger::info('Found tags', ['uri-id' => $uriid, 'hash' => $tags, 'result' => $result]);
|
||||
|
||||
foreach ($result as $tag) {
|
||||
self::storeByHash($uriid, $tag[1], $tag[3], $tag[2], $probing);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store raw tags (not encapsulated in links) from the body
|
||||
* This function is needed in the intermediate phase.
|
||||
* Later we can call item::setHashtags in advance to have all tags converted.
|
||||
*
|
||||
* @param integer $uriid URI-Id
|
||||
* @param string $body Body of the post
|
||||
*/
|
||||
public static function storeRawTagsFromBody(int $uriid, string $body)
|
||||
{
|
||||
Logger::info('Check for tags', ['uri-id' => $uriid, 'callstack' => System::callstack()]);
|
||||
|
||||
$result = BBCode::getTags($body);
|
||||
if (empty($result)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Logger::info('Found tags', ['uri-id' => $uriid, 'result' => $result]);
|
||||
|
||||
foreach ($result as $tag) {
|
||||
if (substr($tag, 0, 1) != self::TAG_CHARACTER[self::HASHTAG]) {
|
||||
continue;
|
||||
}
|
||||
self::storeByHash($uriid, substr($tag, 0, 1), substr($tag, 1));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for stored hashtags and mentions for the given post
|
||||
*
|
||||
* @param integer $uriid
|
||||
* @return bool
|
||||
*/
|
||||
public static function existsForPost(int $uriid)
|
||||
{
|
||||
return DBA::exists('post-tag', ['uri-id' => $uriid, 'type' => [self::HASHTAG, self::MENTION, self::IMPLICIT_MENTION, self::EXCLUSIVE_MENTION]]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove tag/mention
|
||||
*
|
||||
* @param integer $uriid
|
||||
* @param integer $type
|
||||
* @param string $name
|
||||
* @param string $url
|
||||
*/
|
||||
public static function remove(int $uriid, int $type, string $name, string $url = '')
|
||||
{
|
||||
$condition = ['uri-id' => $uriid, 'type' => $type, 'url' => $url];
|
||||
if ($type == self::HASHTAG) {
|
||||
$condition['name'] = $name;
|
||||
}
|
||||
|
||||
$tag = DBA::selectFirst('tag-view', ['tid', 'cid'], $condition);
|
||||
if (!DBA::isResult($tag)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Logger::info('Removing tag/mention', ['uri-id' => $uriid, 'tid' => $tag['tid'], 'name' => $name, 'url' => $url, 'callstack' => System::callstack(8)]);
|
||||
DBA::delete('post-tag', ['uri-id' => $uriid, 'type' => $type, 'tid' => $tag['tid'], 'cid' => $tag['cid']]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove tag/mention
|
||||
*
|
||||
* @param integer $uriid
|
||||
* @param string $hash
|
||||
* @param string $name
|
||||
* @param string $url
|
||||
*/
|
||||
public static function removeByHash(int $uriid, string $hash, string $name, string $url = '')
|
||||
{
|
||||
$type = self::getTypeForHash($hash);
|
||||
if ($type == self::UNKNOWN) {
|
||||
return;
|
||||
}
|
||||
|
||||
self::remove($uriid, $type, $name, $url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the type for the given hash
|
||||
*
|
||||
* @param string $hash
|
||||
* @return integer type
|
||||
*/
|
||||
private static function getTypeForHash(string $hash)
|
||||
{
|
||||
if ($hash == self::TAG_CHARACTER[self::MENTION]) {
|
||||
return self::MENTION;
|
||||
} elseif ($hash == self::TAG_CHARACTER[self::EXCLUSIVE_MENTION]) {
|
||||
return self::EXCLUSIVE_MENTION;
|
||||
} elseif ($hash == self::TAG_CHARACTER[self::IMPLICIT_MENTION]) {
|
||||
return self::IMPLICIT_MENTION;
|
||||
} elseif ($hash == self::TAG_CHARACTER[self::HASHTAG]) {
|
||||
return self::HASHTAG;
|
||||
} else {
|
||||
return self::UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create implicit mentions for a given post
|
||||
*
|
||||
* @param integer $uri_id
|
||||
* @param integer $parent_uri_id
|
||||
*/
|
||||
public static function createImplicitMentions(int $uri_id, int $parent_uri_id)
|
||||
{
|
||||
// Always mention the direct parent author
|
||||
$parent = Item::selectFirst(['author-link', 'author-name'], ['uri-id' => $parent_uri_id]);
|
||||
self::store($uri_id, self::IMPLICIT_MENTION, $parent['author-name'], $parent['author-link']);
|
||||
|
||||
if (DI::config()->get('system', 'disable_implicit_mentions')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$tags = DBA::select('tag-view', ['name', 'url'], ['uri-id' => $parent_uri_id]);
|
||||
while ($tag = DBA::fetch($tags)) {
|
||||
self::store($uri_id, self::IMPLICIT_MENTION, $tag['name'], $tag['url']);
|
||||
}
|
||||
DBA::close($tags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the terms from the provided type(s) associated with the provided item ID.
|
||||
*
|
||||
* @param int $item_id
|
||||
* @param int|array $type
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function getByURIId(int $uri_id, array $type = [self::HASHTAG, self::MENTION, self::IMPLICIT_MENTION, self::EXCLUSIVE_MENTION])
|
||||
{
|
||||
$condition = ['uri-id' => $uri_id, 'type' => $type];
|
||||
return DBA::selectToArray('tag-view', ['type', 'name', 'url'], $condition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a string with all tags and mentions
|
||||
*
|
||||
* @param integer $uri_id
|
||||
* @param array $type
|
||||
* @return string tags and mentions
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function getCSVByURIId(int $uri_id, array $type = [self::HASHTAG, self::MENTION, self::IMPLICIT_MENTION, self::EXCLUSIVE_MENTION])
|
||||
{
|
||||
$tag_list = [];
|
||||
$tags = self::getByURIId($uri_id, $type);
|
||||
foreach ($tags as $tag) {
|
||||
$tag_list[] = self::TAG_CHARACTER[$tag['type']] . '[url=' . $tag['url'] . ']' . $tag['name'] . '[/url]';
|
||||
}
|
||||
|
||||
return implode(',', $tag_list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts an item's tags into mentions, hashtags and other tags. Generate personalized URLs by user and modify the
|
||||
* provided item's body with them.
|
||||
*
|
||||
* @param array $item
|
||||
* @return array
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
public static function populateFromItem(&$item)
|
||||
{
|
||||
$return = [
|
||||
'tags' => [],
|
||||
'hashtags' => [],
|
||||
'mentions' => [],
|
||||
'implicit_mentions' => [],
|
||||
];
|
||||
|
||||
$searchpath = DI::baseUrl() . "/search?tag=";
|
||||
|
||||
$taglist = DBA::select('tag-view', ['type', 'name', 'url'],
|
||||
['uri-id' => $item['uri-id'], 'type' => [self::HASHTAG, self::MENTION, self::EXCLUSIVE_MENTION, self::IMPLICIT_MENTION]]);
|
||||
while ($tag = DBA::fetch($taglist)) {
|
||||
if ($tag['url'] == '') {
|
||||
$tag['url'] = $searchpath . rawurlencode($tag['name']);
|
||||
}
|
||||
|
||||
$orig_tag = $tag['url'];
|
||||
|
||||
$prefix = self::TAG_CHARACTER[$tag['type']];
|
||||
switch($tag['type']) {
|
||||
case self::HASHTAG:
|
||||
if ($orig_tag != $tag['url']) {
|
||||
$item['body'] = str_replace($orig_tag, $tag['url'], $item['body']);
|
||||
}
|
||||
|
||||
$return['hashtags'][] = $prefix . '<a href="' . $tag['url'] . '" target="_blank" rel="noopener noreferrer">' . htmlspecialchars($tag['name']) . '</a>';
|
||||
$return['tags'][] = $prefix . '<a href="' . $tag['url'] . '" target="_blank" rel="noopener noreferrer">' . htmlspecialchars($tag['name']) . '</a>';
|
||||
break;
|
||||
case self::MENTION:
|
||||
case self::EXCLUSIVE_MENTION:
|
||||
$tag['url'] = Contact::magicLink($tag['url']);
|
||||
$return['mentions'][] = $prefix . '<a href="' . $tag['url'] . '" target="_blank" rel="noopener noreferrer">' . htmlspecialchars($tag['name']) . '</a>';
|
||||
$return['tags'][] = $prefix . '<a href="' . $tag['url'] . '" target="_blank" rel="noopener noreferrer">' . htmlspecialchars($tag['name']) . '</a>';
|
||||
break;
|
||||
case self::IMPLICIT_MENTION:
|
||||
$return['implicit_mentions'][] = $prefix . $tag['name'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
DBA::close($taglist);
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search posts for given tag
|
||||
*
|
||||
* @param string $search
|
||||
* @param integer $uid
|
||||
* @param integer $start
|
||||
* @param integer $limit
|
||||
* @return array with URI-ID
|
||||
*/
|
||||
public static function getURIIdListByTag(string $search, int $uid = 0, int $start = 0, int $limit = 100)
|
||||
{
|
||||
$condition = ["`name` = ? AND (NOT `private` OR (`private` AND `uid` = ?))", $search, $uid];
|
||||
$params = [
|
||||
'order' => ['uri-id' => true],
|
||||
'group_by' => ['uri-id'],
|
||||
'limit' => [$start, $limit]
|
||||
];
|
||||
|
||||
$tags = DBA::select('tag-search-view', ['uri-id'], $condition, $params);
|
||||
|
||||
$uriids = [];
|
||||
while ($tag = DBA::fetch($tags)) {
|
||||
$uriids[] = $tag['uri-id'];
|
||||
}
|
||||
DBA::close($tags);
|
||||
|
||||
return $uriids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of the most frequent global hashtags over the given period
|
||||
*
|
||||
* @param int $period Period in hours to consider posts
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function getGlobalTrendingHashtags(int $period, $limit = 10)
|
||||
{
|
||||
$tags = DI::cache()->get('global_trending_tags');
|
||||
|
||||
if (empty($tags)) {
|
||||
$tagsStmt = DBA::p("SELECT `name` AS `term`, COUNT(*) AS `score`
|
||||
FROM `tag-search-view`
|
||||
WHERE `private` = ? AND `received` > DATE_SUB(NOW(), INTERVAL ? HOUR)
|
||||
GROUP BY `term` ORDER BY `score` DESC LIMIT ?",
|
||||
Item::PUBLIC, $period, $limit);
|
||||
|
||||
if (DBA::isResult($tagsStmt)) {
|
||||
$tags = DBA::toArray($tagsStmt);
|
||||
DI::cache()->set('global_trending_tags', $tags, Duration::HOUR);
|
||||
}
|
||||
}
|
||||
|
||||
return $tags ?: [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of the most frequent local hashtags over the given period
|
||||
*
|
||||
* @param int $period Period in hours to consider posts
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function getLocalTrendingHashtags(int $period, $limit = 10)
|
||||
{
|
||||
$tags = DI::cache()->get('local_trending_tags');
|
||||
|
||||
if (empty($tags)) {
|
||||
$tagsStmt = DBA::p("SELECT `name` AS `term`, COUNT(*) AS `score`
|
||||
FROM `tag-search-view`
|
||||
WHERE `private` = ? AND `wall` AND `origin` AND `received` > DATE_SUB(NOW(), INTERVAL ? HOUR)
|
||||
GROUP BY `term` ORDER BY `score` DESC LIMIT ?",
|
||||
Item::PUBLIC, $period, $limit);
|
||||
|
||||
if (DBA::isResult($tagsStmt)) {
|
||||
$tags = DBA::toArray($tagsStmt);
|
||||
DI::cache()->set('local_trending_tags', $tags, Duration::HOUR);
|
||||
}
|
||||
}
|
||||
|
||||
return $tags ?: [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the provided tag is of one of the provided term types.
|
||||
*
|
||||
* @param string $tag
|
||||
* @param int ...$types
|
||||
* @return bool
|
||||
*/
|
||||
public static function isType($tag, ...$types)
|
||||
{
|
||||
$tag_chars = [];
|
||||
foreach ($types as $type) {
|
||||
if (array_key_exists($type, self::TAG_CHARACTER)) {
|
||||
$tag_chars[] = self::TAG_CHARACTER[$type];
|
||||
}
|
||||
}
|
||||
|
||||
return Strings::startsWithChars($tag, $tag_chars);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,519 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* @copyright Copyright (C) 2020, Friendica
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Friendica\Model;
|
||||
|
||||
use Friendica\Core\Cache\Duration;
|
||||
use Friendica\Core\Logger;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\DI;
|
||||
use Friendica\Util\Strings;
|
||||
|
||||
/**
|
||||
* Class Term
|
||||
*
|
||||
* This Model class handles term table interactions.
|
||||
* This tables stores relevant terms related to posts, photos and searches, like hashtags, mentions and
|
||||
* user-applied categories.
|
||||
*/
|
||||
class Term
|
||||
{
|
||||
const UNKNOWN = 0;
|
||||
const HASHTAG = 1;
|
||||
const MENTION = 2;
|
||||
const CATEGORY = 3;
|
||||
const PCATEGORY = 4;
|
||||
const FILE = 5;
|
||||
const SAVEDSEARCH = 6;
|
||||
const CONVERSATION = 7;
|
||||
/**
|
||||
* An implicit mention is a mention in a comment body that is redundant with the threading information.
|
||||
*/
|
||||
const IMPLICIT_MENTION = 8;
|
||||
/**
|
||||
* An exclusive mention transfers the ownership of the post to the target account, usually a forum.
|
||||
*/
|
||||
const EXCLUSIVE_MENTION = 9;
|
||||
|
||||
const TAG_CHARACTER = [
|
||||
self::HASHTAG => '#',
|
||||
self::MENTION => '@',
|
||||
self::IMPLICIT_MENTION => '%',
|
||||
self::EXCLUSIVE_MENTION => '!',
|
||||
];
|
||||
|
||||
const OBJECT_TYPE_POST = 1;
|
||||
const OBJECT_TYPE_PHOTO = 2;
|
||||
|
||||
/**
|
||||
* Returns a list of the most frequent global hashtags over the given period
|
||||
*
|
||||
* @param int $period Period in hours to consider posts
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function getGlobalTrendingHashtags(int $period, $limit = 10)
|
||||
{
|
||||
$tags = DI::cache()->get('global_trending_tags');
|
||||
|
||||
if (!$tags) {
|
||||
$tagsStmt = DBA::p("SELECT t.`term`, COUNT(*) AS `score`
|
||||
FROM `term` t
|
||||
JOIN `item` i ON i.`id` = t.`oid` AND i.`uid` = t.`uid`
|
||||
JOIN `thread` ON `thread`.`iid` = i.`id`
|
||||
WHERE `thread`.`visible`
|
||||
AND NOT `thread`.`deleted`
|
||||
AND NOT `thread`.`moderated`
|
||||
AND `thread`.`private` = ?
|
||||
AND t.`uid` = 0
|
||||
AND t.`otype` = ?
|
||||
AND t.`type` = ?
|
||||
AND t.`term` != ''
|
||||
AND i.`received` > DATE_SUB(NOW(), INTERVAL ? HOUR)
|
||||
GROUP BY `term`
|
||||
ORDER BY `score` DESC
|
||||
LIMIT ?",
|
||||
Item::PUBLIC,
|
||||
Term::OBJECT_TYPE_POST,
|
||||
Term::HASHTAG,
|
||||
$period,
|
||||
$limit
|
||||
);
|
||||
|
||||
if (DBA::isResult($tagsStmt)) {
|
||||
$tags = DBA::toArray($tagsStmt);
|
||||
DI::cache()->set('global_trending_tags', $tags, Duration::HOUR);
|
||||
}
|
||||
}
|
||||
|
||||
return $tags ?: [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of the most frequent local hashtags over the given period
|
||||
*
|
||||
* @param int $period Period in hours to consider posts
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function getLocalTrendingHashtags(int $period, $limit = 10)
|
||||
{
|
||||
$tags = DI::cache()->get('local_trending_tags');
|
||||
|
||||
if (!$tags) {
|
||||
$tagsStmt = DBA::p("SELECT t.`term`, COUNT(*) AS `score`
|
||||
FROM `term` t
|
||||
JOIN `item` i ON i.`id` = t.`oid` AND i.`uid` = t.`uid`
|
||||
JOIN `thread` ON `thread`.`iid` = i.`id`
|
||||
WHERE `thread`.`visible`
|
||||
AND NOT `thread`.`deleted`
|
||||
AND NOT `thread`.`moderated`
|
||||
AND `thread`.`private` = ?
|
||||
AND `thread`.`wall`
|
||||
AND `thread`.`origin`
|
||||
AND t.`otype` = ?
|
||||
AND t.`type` = ?
|
||||
AND t.`term` != ''
|
||||
AND i.`received` > DATE_SUB(NOW(), INTERVAL ? HOUR)
|
||||
GROUP BY `term`
|
||||
ORDER BY `score` DESC
|
||||
LIMIT ?",
|
||||
Item::PUBLIC,
|
||||
Term::OBJECT_TYPE_POST,
|
||||
Term::HASHTAG,
|
||||
$period,
|
||||
$limit
|
||||
);
|
||||
|
||||
if (DBA::isResult($tagsStmt)) {
|
||||
$tags = DBA::toArray($tagsStmt);
|
||||
DI::cache()->set('local_trending_tags', $tags, Duration::HOUR);
|
||||
}
|
||||
}
|
||||
|
||||
return $tags ?: [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the legacy item.tag field comma-separated BBCode string from an item ID.
|
||||
* Includes only hashtags, implicit and explicit mentions.
|
||||
*
|
||||
* @param int $item_id
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function tagTextFromItemId($item_id)
|
||||
{
|
||||
$tag_list = [];
|
||||
$tags = self::tagArrayFromItemId($item_id, [self::HASHTAG, self::MENTION, self::IMPLICIT_MENTION]);
|
||||
foreach ($tags as $tag) {
|
||||
$tag_list[] = self::TAG_CHARACTER[$tag['type']] . '[url=' . $tag['url'] . ']' . $tag['term'] . '[/url]';
|
||||
}
|
||||
|
||||
return implode(',', $tag_list);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the terms from the provided type(s) associated with the provided item ID.
|
||||
*
|
||||
* @param int $item_id
|
||||
* @param int|array $type
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function tagArrayFromItemId($item_id, $type = [self::HASHTAG, self::MENTION])
|
||||
{
|
||||
$condition = ['otype' => self::OBJECT_TYPE_POST, 'oid' => $item_id, 'type' => $type];
|
||||
$tags = DBA::select('term', ['type', 'term', 'url'], $condition);
|
||||
if (!DBA::isResult($tags)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return DBA::toArray($tags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the legacy item.file field string from an item ID.
|
||||
* Includes only file and category terms.
|
||||
*
|
||||
* @param int $item_id
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function fileTextFromItemId($item_id)
|
||||
{
|
||||
$file_text = '';
|
||||
$tags = self::tagArrayFromItemId($item_id, [self::FILE, self::CATEGORY]);
|
||||
foreach ($tags as $tag) {
|
||||
if ($tag['type'] == self::CATEGORY) {
|
||||
$file_text .= '<' . $tag['term'] . '>';
|
||||
} else {
|
||||
$file_text .= '[' . $tag['term'] . ']';
|
||||
}
|
||||
}
|
||||
|
||||
return $file_text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts new terms for the provided item ID based on the legacy item.tag field BBCode content.
|
||||
* Deletes all previous tag terms for the same item ID.
|
||||
* Sets both the item.mention and thread.mentions field flags if a mention concerning the item UID is found.
|
||||
*
|
||||
* @param int $item_id
|
||||
* @param string $tag_str
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public static function insertFromTagFieldByItemId($item_id, $tag_str)
|
||||
{
|
||||
$profile_base = DI::baseUrl();
|
||||
$profile_data = parse_url($profile_base);
|
||||
$profile_path = $profile_data['path'] ?? '';
|
||||
$profile_base_friendica = $profile_data['host'] . $profile_path . '/profile/';
|
||||
$profile_base_diaspora = $profile_data['host'] . $profile_path . '/u/';
|
||||
|
||||
$fields = ['guid', 'uid', 'id', 'edited', 'deleted', 'created', 'received', 'title', 'body', 'parent'];
|
||||
$item = Item::selectFirst($fields, ['id' => $item_id]);
|
||||
if (!DBA::isResult($item)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$item['tag'] = $tag_str;
|
||||
|
||||
// Clean up all tags
|
||||
self::deleteByItemId($item_id);
|
||||
|
||||
if ($item['deleted']) {
|
||||
return;
|
||||
}
|
||||
|
||||
$taglist = explode(',', $item['tag']);
|
||||
|
||||
$tags_string = '';
|
||||
foreach ($taglist as $tag) {
|
||||
if (Strings::startsWith($tag, self::TAG_CHARACTER)) {
|
||||
$tags_string .= ' ' . trim($tag);
|
||||
} else {
|
||||
$tags_string .= ' #' . trim($tag);
|
||||
}
|
||||
}
|
||||
|
||||
$data = ' ' . $item['title'] . ' ' . $item['body'] . ' ' . $tags_string . ' ';
|
||||
|
||||
// ignore anything in a code block
|
||||
$data = preg_replace('/\[code\](.*?)\[\/code\]/sm', '', $data);
|
||||
|
||||
$tags = [];
|
||||
|
||||
$pattern = '/\W\#([^\[].*?)[\s\'".,:;\?!\[\]\/]/ism';
|
||||
if (preg_match_all($pattern, $data, $matches)) {
|
||||
foreach ($matches[1] as $match) {
|
||||
$tags['#' . $match] = '';
|
||||
}
|
||||
}
|
||||
|
||||
$pattern = '/\W([\#@!%])\[url\=(.*?)\](.*?)\[\/url\]/ism';
|
||||
if (preg_match_all($pattern, $data, $matches, PREG_SET_ORDER)) {
|
||||
foreach ($matches as $match) {
|
||||
|
||||
if (in_array($match[1], [
|
||||
self::TAG_CHARACTER[self::MENTION],
|
||||
self::TAG_CHARACTER[self::IMPLICIT_MENTION],
|
||||
self::TAG_CHARACTER[self::EXCLUSIVE_MENTION]
|
||||
])) {
|
||||
$contact = Contact::getDetailsByURL($match[2], 0);
|
||||
if (!empty($contact['addr'])) {
|
||||
$match[3] = $contact['addr'];
|
||||
}
|
||||
|
||||
if (!empty($contact['url'])) {
|
||||
$match[2] = $contact['url'];
|
||||
}
|
||||
}
|
||||
|
||||
$tags[$match[2]] = $match[1] . trim($match[3], ',.:;[]/\"?!');
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($tags as $link => $tag) {
|
||||
if (self::isType($tag, self::HASHTAG)) {
|
||||
// try to ignore #039 or #1 or anything like that
|
||||
if (ctype_digit(substr(trim($tag), 1))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// try to ignore html hex escapes, e.g. #x2317
|
||||
if ((substr(trim($tag), 1, 1) == 'x' || substr(trim($tag), 1, 1) == 'X') && ctype_digit(substr(trim($tag), 2))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$type = self::HASHTAG;
|
||||
$term = substr($tag, 1);
|
||||
$link = '';
|
||||
} elseif (self::isType($tag, self::MENTION, self::EXCLUSIVE_MENTION, self::IMPLICIT_MENTION)) {
|
||||
if (self::isType($tag, self::MENTION, self::EXCLUSIVE_MENTION)) {
|
||||
$type = self::MENTION;
|
||||
} else {
|
||||
$type = self::IMPLICIT_MENTION;
|
||||
}
|
||||
|
||||
$contact = Contact::getDetailsByURL($link, 0);
|
||||
if (!empty($contact['name'])) {
|
||||
$term = $contact['name'];
|
||||
} else {
|
||||
$term = substr($tag, 1);
|
||||
}
|
||||
} else { // This shouldn't happen
|
||||
$type = self::HASHTAG;
|
||||
$term = $tag;
|
||||
$link = '';
|
||||
|
||||
Logger::notice('Unknown term type', ['tag' => $tag]);
|
||||
}
|
||||
|
||||
if (DBA::exists('term', ['uid' => $item['uid'], 'otype' => self::OBJECT_TYPE_POST, 'oid' => $item_id, 'term' => $term, 'type' => $type])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($item['uid'] == 0) {
|
||||
$global = true;
|
||||
DBA::update('term', ['global' => true], ['otype' => self::OBJECT_TYPE_POST, 'guid' => $item['guid']]);
|
||||
} else {
|
||||
$global = DBA::exists('term', ['uid' => 0, 'otype' => self::OBJECT_TYPE_POST, 'guid' => $item['guid']]);
|
||||
}
|
||||
|
||||
DBA::insert('term', [
|
||||
'uid' => $item['uid'],
|
||||
'oid' => $item_id,
|
||||
'otype' => self::OBJECT_TYPE_POST,
|
||||
'type' => $type,
|
||||
'term' => substr($term, 0, 255),
|
||||
'url' => $link,
|
||||
'guid' => $item['guid'],
|
||||
'created' => $item['created'],
|
||||
'received' => $item['received'],
|
||||
'global' => $global
|
||||
]);
|
||||
|
||||
// Search for mentions
|
||||
if (self::isType($tag, self::MENTION, self::EXCLUSIVE_MENTION)
|
||||
&& (
|
||||
strpos($link, $profile_base_friendica) !== false
|
||||
|| strpos($link, $profile_base_diaspora) !== false
|
||||
)
|
||||
) {
|
||||
$users_stmt = DBA::p("SELECT `uid` FROM `contact` WHERE self AND (`url` = ? OR `nurl` = ?)", $link, $link);
|
||||
$users = DBA::toArray($users_stmt);
|
||||
foreach ($users AS $user) {
|
||||
if ($user['uid'] == $item['uid']) {
|
||||
/// @todo This function is called from Item::update - so we mustn't call that function here
|
||||
DBA::update('item', ['mention' => true], ['id' => $item_id]);
|
||||
DBA::update('thread', ['mention' => true], ['iid' => $item['parent']]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts new terms for the provided item ID based on the legacy item.file field BBCode content.
|
||||
* Deletes all previous file terms for the same item ID.
|
||||
*
|
||||
* @param integer $item_id item id
|
||||
* @param $files
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function insertFromFileFieldByItemId($item_id, $files)
|
||||
{
|
||||
$message = Item::selectFirst(['uid', 'deleted'], ['id' => $item_id]);
|
||||
if (!DBA::isResult($message)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clean up all tags
|
||||
DBA::delete('term', ['otype' => self::OBJECT_TYPE_POST, 'oid' => $item_id, 'type' => [self::FILE, self::CATEGORY]]);
|
||||
|
||||
if ($message["deleted"]) {
|
||||
return;
|
||||
}
|
||||
|
||||
$message['file'] = $files;
|
||||
|
||||
if (preg_match_all("/\[(.*?)\]/ism", $message["file"], $files)) {
|
||||
foreach ($files[1] as $file) {
|
||||
DBA::insert('term', [
|
||||
'uid' => $message["uid"],
|
||||
'oid' => $item_id,
|
||||
'otype' => self::OBJECT_TYPE_POST,
|
||||
'type' => self::FILE,
|
||||
'term' => $file
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
if (preg_match_all("/\<(.*?)\>/ism", $message["file"], $files)) {
|
||||
foreach ($files[1] as $file) {
|
||||
DBA::insert('term', [
|
||||
'uid' => $message["uid"],
|
||||
'oid' => $item_id,
|
||||
'otype' => self::OBJECT_TYPE_POST,
|
||||
'type' => self::CATEGORY,
|
||||
'term' => $file
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts an item's tags into mentions, hashtags and other tags. Generate personalized URLs by user and modify the
|
||||
* provided item's body with them.
|
||||
*
|
||||
* @param array $item
|
||||
* @return array
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
public static function populateTagsFromItem(&$item)
|
||||
{
|
||||
$return = [
|
||||
'tags' => [],
|
||||
'hashtags' => [],
|
||||
'mentions' => [],
|
||||
'implicit_mentions' => [],
|
||||
];
|
||||
|
||||
$searchpath = DI::baseUrl() . "/search?tag=";
|
||||
|
||||
$taglist = DBA::select(
|
||||
'term',
|
||||
['type', 'term', 'url'],
|
||||
['otype' => self::OBJECT_TYPE_POST, 'oid' => $item['id'], 'type' => [self::HASHTAG, self::MENTION, self::IMPLICIT_MENTION]],
|
||||
['order' => ['tid']]
|
||||
);
|
||||
while ($tag = DBA::fetch($taglist)) {
|
||||
if ($tag['url'] == '') {
|
||||
$tag['url'] = $searchpath . rawurlencode($tag['term']);
|
||||
}
|
||||
|
||||
$orig_tag = $tag['url'];
|
||||
|
||||
$prefix = self::TAG_CHARACTER[$tag['type']];
|
||||
switch($tag['type']) {
|
||||
case self::HASHTAG:
|
||||
if ($orig_tag != $tag['url']) {
|
||||
$item['body'] = str_replace($orig_tag, $tag['url'], $item['body']);
|
||||
}
|
||||
|
||||
$return['hashtags'][] = $prefix . '<a href="' . $tag['url'] . '" target="_blank" rel="noopener noreferrer">' . htmlspecialchars($tag['term']) . '</a>';
|
||||
$return['tags'][] = $prefix . '<a href="' . $tag['url'] . '" target="_blank" rel="noopener noreferrer">' . htmlspecialchars($tag['term']) . '</a>';
|
||||
break;
|
||||
case self::MENTION:
|
||||
$tag['url'] = Contact::magicLink($tag['url']);
|
||||
$return['mentions'][] = $prefix . '<a href="' . $tag['url'] . '" target="_blank" rel="noopener noreferrer">' . htmlspecialchars($tag['term']) . '</a>';
|
||||
$return['tags'][] = $prefix . '<a href="' . $tag['url'] . '" target="_blank" rel="noopener noreferrer">' . htmlspecialchars($tag['term']) . '</a>';
|
||||
break;
|
||||
case self::IMPLICIT_MENTION:
|
||||
$return['implicit_mentions'][] = $prefix . $tag['term'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
DBA::close($taglist);
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete tags of the specific type(s) from an item
|
||||
*
|
||||
* @param int $item_id
|
||||
* @param int|array $type
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function deleteByItemId($item_id, $type = [self::HASHTAG, self::MENTION, self::IMPLICIT_MENTION])
|
||||
{
|
||||
if (empty($item_id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Clean up all tags
|
||||
DBA::delete('term', ['otype' => self::OBJECT_TYPE_POST, 'oid' => $item_id, 'type' => $type]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the provided tag is of one of the provided term types.
|
||||
*
|
||||
* @param string $tag
|
||||
* @param int ...$types
|
||||
* @return bool
|
||||
*/
|
||||
public static function isType($tag, ...$types)
|
||||
{
|
||||
$tag_chars = [];
|
||||
foreach ($types as $type) {
|
||||
if (array_key_exists($type, self::TAG_CHARACTER)) {
|
||||
$tag_chars[] = self::TAG_CHARACTER[$type];
|
||||
}
|
||||
}
|
||||
|
||||
return Strings::startsWith($tag, $tag_chars);
|
||||
}
|
||||
}
|
||||
|
|
@ -195,66 +195,50 @@ class User
|
|||
*/
|
||||
public static function getOwnerDataById($uid, $check_valid = true)
|
||||
{
|
||||
$r = DBA::fetchFirst(
|
||||
"SELECT
|
||||
`contact`.*,
|
||||
`user`.`prvkey` AS `uprvkey`,
|
||||
`user`.`timezone`,
|
||||
`user`.`nickname`,
|
||||
`user`.`sprvkey`,
|
||||
`user`.`spubkey`,
|
||||
`user`.`page-flags`,
|
||||
`user`.`account-type`,
|
||||
`user`.`prvnets`,
|
||||
`user`.`account_removed`,
|
||||
`user`.`hidewall`
|
||||
FROM `contact`
|
||||
INNER JOIN `user`
|
||||
ON `user`.`uid` = `contact`.`uid`
|
||||
WHERE `contact`.`uid` = ?
|
||||
AND `contact`.`self`
|
||||
LIMIT 1",
|
||||
$uid
|
||||
);
|
||||
if (!DBA::isResult($r)) {
|
||||
return false;
|
||||
$owner = DBA::selectFirst('owner-view', [], ['uid' => $uid]);
|
||||
if (!DBA::isResult($owner)) {
|
||||
if (!DBA::exists('user', ['uid' => $uid]) || !$check_valid) {
|
||||
return false;
|
||||
}
|
||||
Contact::createSelfFromUserId($uid);
|
||||
$owner = self::getOwnerDataById($uid, false);
|
||||
}
|
||||
|
||||
if (empty($r['nickname'])) {
|
||||
if (empty($owner['nickname'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$check_valid) {
|
||||
return $r;
|
||||
return $owner;
|
||||
}
|
||||
|
||||
// Check if the returned data is valid, otherwise fix it. See issue #6122
|
||||
|
||||
// Check for correct url and normalised nurl
|
||||
$url = DI::baseUrl() . '/profile/' . $r['nickname'];
|
||||
$repair = ($r['url'] != $url) || ($r['nurl'] != Strings::normaliseLink($r['url']));
|
||||
$url = DI::baseUrl() . '/profile/' . $owner['nickname'];
|
||||
$repair = ($owner['url'] != $url) || ($owner['nurl'] != Strings::normaliseLink($owner['url']));
|
||||
|
||||
if (!$repair) {
|
||||
// Check if "addr" is present and correct
|
||||
$addr = $r['nickname'] . '@' . substr(DI::baseUrl(), strpos(DI::baseUrl(), '://') + 3);
|
||||
$repair = ($addr != $r['addr']);
|
||||
$addr = $owner['nickname'] . '@' . substr(DI::baseUrl(), strpos(DI::baseUrl(), '://') + 3);
|
||||
$repair = ($addr != $owner['addr']);
|
||||
}
|
||||
|
||||
if (!$repair) {
|
||||
// Check if the avatar field is filled and the photo directs to the correct path
|
||||
$avatar = Photo::selectFirst(['resource-id'], ['uid' => $uid, 'profile' => true]);
|
||||
if (DBA::isResult($avatar)) {
|
||||
$repair = empty($r['avatar']) || !strpos($r['photo'], $avatar['resource-id']);
|
||||
$repair = empty($owner['avatar']) || !strpos($owner['photo'], $avatar['resource-id']);
|
||||
}
|
||||
}
|
||||
|
||||
if ($repair) {
|
||||
Contact::updateSelfFromUserID($uid);
|
||||
// Return the corrected data and avoid a loop
|
||||
$r = self::getOwnerDataById($uid, false);
|
||||
$owner = self::getOwnerDataById($uid, false);
|
||||
}
|
||||
|
||||
return $r;
|
||||
return $owner;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -839,9 +823,16 @@ class User
|
|||
$photo_failure = false;
|
||||
|
||||
$filename = basename($photo);
|
||||
$img_str = Network::fetchUrl($photo, true);
|
||||
// guess mimetype from headers or filename
|
||||
$type = Images::guessType($photo, true);
|
||||
$curlResult = Network::curl($photo, true);
|
||||
if ($curlResult->isSuccess()) {
|
||||
$img_str = $curlResult->getBody();
|
||||
$type = $curlResult->getContentType();
|
||||
} else {
|
||||
$img_str = '';
|
||||
$type = '';
|
||||
}
|
||||
|
||||
$type = Images::getMimeTypeByData($img_str, $photo, $type);
|
||||
|
||||
$Image = new Image($img_str, $type);
|
||||
if ($Image->isValid()) {
|
||||
|
|
@ -1171,7 +1162,7 @@ class User
|
|||
// unique), so it cannot be re-registered in the future.
|
||||
DBA::insert('userd', ['username' => $user['nickname']]);
|
||||
|
||||
// The user and related data will be deleted in "cron_expire_and_remove_users" (cronjobs.php)
|
||||
// The user and related data will be deleted in Friendica\Worker\CronJobs::expireAndRemoveUsers()
|
||||
DBA::update('user', ['account_removed' => true, 'account_expires_on' => DateTimeFormat::utc('now + 7 day')], ['uid' => $uid]);
|
||||
Worker::add(PRIORITY_HIGH, 'Notifier', Delivery::REMOVAL, $uid);
|
||||
|
||||
|
|
@ -1283,17 +1274,10 @@ class User
|
|||
'active_users_monthly' => 0,
|
||||
];
|
||||
|
||||
$userStmt = DBA::p("SELECT `user`.`uid`, `user`.`login_date`, `contact`.`last-item`
|
||||
FROM `user`
|
||||
INNER JOIN `contact` ON `contact`.`uid` = `user`.`uid` AND `contact`.`self`
|
||||
WHERE `user`.`verified`
|
||||
AND `user`.`login_date` > ?
|
||||
AND NOT `user`.`blocked`
|
||||
AND NOT `user`.`account_removed`
|
||||
AND NOT `user`.`account_expired`",
|
||||
DBA::NULL_DATETIME
|
||||
);
|
||||
|
||||
$userStmt = DBA::select('owner-view', ['uid', 'login_date', 'last-item'],
|
||||
["`verified` AND `login_date` > ? AND NOT `blocked`
|
||||
AND NOT `account_removed` AND NOT `account_expired`",
|
||||
DBA::NULL_DATETIME]);
|
||||
if (!DBA::isResult($userStmt)) {
|
||||
return $statistics;
|
||||
}
|
||||
|
|
@ -1314,6 +1298,7 @@ class User
|
|||
$statistics['active_users_monthly']++;
|
||||
}
|
||||
}
|
||||
DBA::close($userStmt);
|
||||
|
||||
return $statistics;
|
||||
}
|
||||
|
|
@ -1325,39 +1310,28 @@ class User
|
|||
* @param int $count Count of the items per page (Default is @see Pager::ITEMS_PER_PAGE)
|
||||
* @param string $type The type of users, which should get (all, bocked, removed)
|
||||
* @param string $order Order of the user list (Default is 'contact.name')
|
||||
* @param string $order_direction Order direction (Default is ASC)
|
||||
* @param bool $descending Order direction (Default is ascending)
|
||||
*
|
||||
* @return array The list of the users
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getList($start = 0, $count = Pager::ITEMS_PER_PAGE, $type = 'all', $order = 'contact.name', $order_direction = '+')
|
||||
public static function getList($start = 0, $count = Pager::ITEMS_PER_PAGE, $type = 'all', $order = 'name', bool $descending = false)
|
||||
{
|
||||
$sql_order = '`' . str_replace('.', '`.`', $order) . '`';
|
||||
$sql_order_direction = ($order_direction === '+') ? 'ASC' : 'DESC';
|
||||
|
||||
$param = ['limit' => [$start, $count], 'order' => [$order => $descending]];
|
||||
$condition = [];
|
||||
switch ($type) {
|
||||
case 'active':
|
||||
$sql_extra = 'AND `user`.`blocked` = 0';
|
||||
$condition['account_removed'] = false;
|
||||
$condition['blocked'] = false;
|
||||
break;
|
||||
case 'blocked':
|
||||
$sql_extra = 'AND `user`.`blocked` = 1';
|
||||
$condition['blocked'] = true;
|
||||
break;
|
||||
case 'removed':
|
||||
$sql_extra = 'AND `user`.`account_removed` = 1';
|
||||
break;
|
||||
case 'all':
|
||||
default:
|
||||
$sql_extra = '';
|
||||
$condition['account_removed'] = true;
|
||||
break;
|
||||
}
|
||||
|
||||
$usersStmt = DBA::p("SELECT `user`.*, `contact`.`name`, `contact`.`url`, `contact`.`micro`, `user`.`account_expired`, `contact`.`last-item` AS `lastitem_date`, `contact`.`nick`, `contact`.`created`
|
||||
FROM `user`
|
||||
INNER JOIN `contact` ON `contact`.`uid` = `user`.`uid` AND `contact`.`self`
|
||||
WHERE `user`.`verified` $sql_extra
|
||||
ORDER BY $sql_order $sql_order_direction LIMIT ?, ?", $start, $count
|
||||
);
|
||||
|
||||
return DBA::toArray($usersStmt);
|
||||
return DBA::selectToArray('owner-view', [], $condition, $param);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ use Friendica\Core\Hook;
|
|||
use Friendica\Database\DBA;
|
||||
use Friendica\DI;
|
||||
use Friendica\Util\Strings;
|
||||
use Friendica\Model\Tag;
|
||||
|
||||
class UserItem
|
||||
{
|
||||
|
|
@ -49,7 +50,7 @@ class UserItem
|
|||
*/
|
||||
public static function setNotification(int $iid)
|
||||
{
|
||||
$fields = ['id', 'uid', 'body', 'parent', 'gravity', 'tag', 'contact-id', 'thr-parent', 'parent-uri', 'author-id'];
|
||||
$fields = ['id', 'uri-id', 'uid', 'body', 'parent', 'gravity', 'tag', 'contact-id', 'thr-parent', 'parent-uri', 'author-id'];
|
||||
$item = Item::selectFirst($fields, ['id' => $iid, 'origin' => false]);
|
||||
if (!DBA::isResult($item)) {
|
||||
return;
|
||||
|
|
@ -74,7 +75,7 @@ class UserItem
|
|||
private static function setNotificationForUser(array $item, int $uid)
|
||||
{
|
||||
$thread = Item::selectFirstThreadForUser($uid, ['ignored'], ['iid' => $item['parent'], 'deleted' => false]);
|
||||
if ($thread['ignored']) {
|
||||
if (!empty($thread['ignored'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -206,13 +207,14 @@ class UserItem
|
|||
}
|
||||
|
||||
// Or the contact is a mentioned forum
|
||||
$tags = DBA::select('term', ['url'], ['otype' => TERM_OBJ_POST, 'oid' => $item['id'], 'type' => TERM_MENTION, 'uid' => $uid]);
|
||||
$tags = DBA::select('tag-view', ['url'], ['uri-id' => $item['uri-id'], 'type' => [Tag::MENTION, Tag::EXCLUSIVE_MENTION]]);
|
||||
while ($tag = DBA::fetch($tags)) {
|
||||
$condition = ['nurl' => Strings::normaliseLink($tag['url']), 'uid' => $uid, 'notify_new_posts' => true, 'contact-type' => Contact::TYPE_COMMUNITY];
|
||||
if (DBA::exists('contact', $condition)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
DBA::close($tags);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
@ -225,9 +227,10 @@ class UserItem
|
|||
*/
|
||||
private static function checkImplicitMention(array $item, array $profiles)
|
||||
{
|
||||
foreach ($profiles AS $profile) {
|
||||
if (strpos($item['tag'], '=' . $profile.']') || strpos($item['body'], '=' . $profile . ']')) {
|
||||
if (strpos($item['body'], $profile) === false) {
|
||||
$mentions = Tag::getByURIId($item['uri-id'], [Tag::IMPLICIT_MENTION]);
|
||||
foreach ($mentions as $mention) {
|
||||
foreach ($profiles as $profile) {
|
||||
if (Strings::compareLink($profile, $mention['url'])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -244,9 +247,10 @@ class UserItem
|
|||
*/
|
||||
private static function checkExplicitMention(array $item, array $profiles)
|
||||
{
|
||||
foreach ($profiles AS $profile) {
|
||||
if (strpos($item['tag'], '=' . $profile.']') || strpos($item['body'], '=' . $profile . ']')) {
|
||||
if (!(strpos($item['body'], $profile) === false)) {
|
||||
$mentions = Tag::getByURIId($item['uri-id'], [Tag::MENTION, Tag::EXCLUSIVE_MENTION]);
|
||||
foreach ($mentions as $mention) {
|
||||
foreach ($profiles as $profile) {
|
||||
if (Strings::compareLink($profile, $mention['url'])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
71
src/Model/Verb.php
Normal file
71
src/Model/Verb.php
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
/**
|
||||
* @copyright Copyright (C) 2020, Friendica
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Friendica\Model;
|
||||
|
||||
use Friendica\Database\DBA;
|
||||
|
||||
class Verb
|
||||
{
|
||||
/**
|
||||
* Insert a verb record and return its id
|
||||
*
|
||||
* @param string $verb
|
||||
*
|
||||
* @return integer verb id
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function getID(string $verb)
|
||||
{
|
||||
if (empty($verb)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$verb_record = DBA::selectFirst('verb', ['id'], ['name' => $verb]);
|
||||
if (DBA::isResult($verb_record)) {
|
||||
return $verb_record['id'];
|
||||
}
|
||||
|
||||
DBA::insert('verb', ['name' => $verb], true);
|
||||
|
||||
return DBA::lastInsertId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return verb name for the given ID
|
||||
*
|
||||
* @param integer $id
|
||||
* @return string verb
|
||||
*/
|
||||
public static function getByID(int $id)
|
||||
{
|
||||
if (empty($id)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$verb_record = DBA::selectFirst('verb', ['name'], ['id' => $id]);
|
||||
if (!DBA::isResult($verb_record)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $verb_record['name'];
|
||||
}
|
||||
}
|
||||
|
|
@ -36,7 +36,6 @@ class Acctlink extends BaseModule
|
|||
|
||||
if ($addr) {
|
||||
$url = Probe::uri($addr)['url'] ?? '';
|
||||
|
||||
if ($url) {
|
||||
System::externalRedirect($url);
|
||||
exit();
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ class Server extends BaseAdmin
|
|||
return Renderer::replaceMacros($t, [
|
||||
'$title' => DI::l10n()->t('Administration'),
|
||||
'$page' => DI::l10n()->t('Server Domain Pattern Blocklist'),
|
||||
'$intro' => DI::l10n()->t('This page can be used to define a blacklist of server domain patterns from the federated network that are not allowed to interact with your node. For each domain pattern you should also provide the reason why you block it.'),
|
||||
'$intro' => DI::l10n()->t('This page can be used to define a blocklist of server domain patterns from the federated network that are not allowed to interact with your node. For each domain pattern you should also provide the reason why you block it.'),
|
||||
'$public' => DI::l10n()->t('The list of blocked server domain patterns will be made publically available on the <a href="/friendica">/friendica</a> page so that your users and people investigating communication problems can find the reason easily.'),
|
||||
'$syntax' => DI::l10n()->t('<p>The server domain pattern syntax is case-insensitive shell wildcard, comprising the following special characters:</p>
|
||||
<ul>
|
||||
|
|
|
|||
|
|
@ -102,6 +102,7 @@ class DBSync extends BaseAdmin
|
|||
$failed[] = $upd;
|
||||
}
|
||||
}
|
||||
DBA::close($configStmt);
|
||||
|
||||
if (!count($failed)) {
|
||||
$o = Renderer::replaceMacros(Renderer::getMarkupTemplate('admin/dbsync/structure_check.tpl'), [
|
||||
|
|
|
|||
|
|
@ -64,15 +64,14 @@ class Features extends BaseAdmin
|
|||
{
|
||||
parent::content($parameters);
|
||||
|
||||
$arr = [];
|
||||
$features = Feature::get(false);
|
||||
$features = [];
|
||||
|
||||
foreach ($features as $fname => $fdata) {
|
||||
$arr[$fname] = [];
|
||||
$arr[$fname][0] = $fdata[0];
|
||||
foreach (Feature::get(false) as $fname => $fdata) {
|
||||
$features[$fname] = [];
|
||||
$features[$fname][0] = $fdata[0];
|
||||
foreach (array_slice($fdata, 1) as $f) {
|
||||
$set = DI::config()->get('feature', $f[0], $f[3]);
|
||||
$arr[$fname][1][] = [
|
||||
$features[$fname][1][] = [
|
||||
['feature_' . $f[0], $f[1], $set, $f[2]],
|
||||
['featurelock_' . $f[0], DI::l10n()->t('Lock feature %s', $f[1]), $f[4], '']
|
||||
];
|
||||
|
|
@ -82,9 +81,10 @@ class Features extends BaseAdmin
|
|||
$tpl = Renderer::getMarkupTemplate('admin/features.tpl');
|
||||
$o = Renderer::replaceMacros($tpl, [
|
||||
'$form_security_token' => parent::getFormSecurityToken("admin_manage_features"),
|
||||
'$title' => DI::l10n()->t('Manage Additional Features'),
|
||||
'$features' => $arr,
|
||||
'$submit' => DI::l10n()->t('Save Settings'),
|
||||
'$baseurl' => DI::baseUrl()->get(true),
|
||||
'$title' => DI::l10n()->t('Manage Additional Features'),
|
||||
'$features' => $features,
|
||||
'$submit' => DI::l10n()->t('Save Settings'),
|
||||
]);
|
||||
|
||||
return $o;
|
||||
|
|
|
|||
|
|
@ -33,29 +33,23 @@ class Source extends BaseAdmin
|
|||
{
|
||||
parent::content($parameters);
|
||||
|
||||
$a = DI::app();
|
||||
|
||||
$guid = null;
|
||||
// @TODO: Replace with parameter from router
|
||||
if (!empty($a->argv[3])) {
|
||||
$guid = $a->argv[3];
|
||||
}
|
||||
|
||||
$guid = $_REQUEST['guid'] ?? $guid;
|
||||
$guid = basename($_REQUEST['guid'] ?? '') ?: $parameters['guid'];
|
||||
|
||||
$source = '';
|
||||
$item_uri = '';
|
||||
$item_id = '';
|
||||
$terms = [];
|
||||
if (!empty($guid)) {
|
||||
$item = Model\Item::selectFirst(['id', 'guid', 'uri'], ['guid' => $guid]);
|
||||
$item = Model\Item::selectFirst(['id', 'uri-id', 'guid', 'uri'], ['guid' => $guid]);
|
||||
|
||||
$conversation = Model\Conversation::getByItemUri($item['uri']);
|
||||
if ($item) {
|
||||
$conversation = Model\Conversation::getByItemUri($item['uri']);
|
||||
|
||||
$item_id = $item['id'];
|
||||
$item_uri = $item['uri'];
|
||||
$source = $conversation['source'];
|
||||
$terms = Model\Term::tagArrayFromItemId($item['id'], [Model\Term::HASHTAG, Model\Term::MENTION, Model\Term::IMPLICIT_MENTION]);
|
||||
$item_id = $item['id'];
|
||||
$item_uri = $item['uri'];
|
||||
$source = $conversation['source'];
|
||||
$terms = Model\Tag::getByURIId($item['uri-id'], [Model\Tag::HASHTAG, Model\Tag::MENTION, Model\Tag::IMPLICIT_MENTION]);
|
||||
}
|
||||
}
|
||||
|
||||
$tpl = Renderer::getMarkupTemplate('admin/item/source.tpl');
|
||||
|
|
|
|||
|
|
@ -103,7 +103,6 @@ class Site extends BaseAdmin
|
|||
// update tables
|
||||
// update profile links in the format "http://server.tld"
|
||||
update_table($a, "profile", ['photo', 'thumb'], $old_url, $new_url);
|
||||
update_table($a, "term", ['url'], $old_url, $new_url);
|
||||
update_table($a, "contact", ['photo', 'thumb', 'micro', 'url', 'nurl', 'alias', 'request', 'notify', 'poll', 'confirm', 'poco', 'avatar'], $old_url, $new_url);
|
||||
update_table($a, "gcontact", ['url', 'nurl', 'photo', 'server_url', 'notify', 'alias'], $old_url, $new_url);
|
||||
update_table($a, "item", ['owner-link', 'author-link', 'body', 'plink', 'tag'], $old_url, $new_url);
|
||||
|
|
@ -121,6 +120,7 @@ class Site extends BaseAdmin
|
|||
while ($user = DBA::fetch($usersStmt)) {
|
||||
Worker::add(PRIORITY_HIGH, 'Notifier', Delivery::RELOCATION, $user['uid']);
|
||||
}
|
||||
DBA::close($usersStmt);
|
||||
|
||||
info("Relocation started. Could take a while to complete.");
|
||||
|
||||
|
|
@ -199,6 +199,7 @@ class Site extends BaseAdmin
|
|||
$itemcache = (!empty($_POST['itemcache']) ? Strings::escapeTags(trim($_POST['itemcache'])) : '');
|
||||
$itemcache_duration = (!empty($_POST['itemcache_duration']) ? intval($_POST['itemcache_duration']) : 0);
|
||||
$max_comments = (!empty($_POST['max_comments']) ? intval($_POST['max_comments']) : 0);
|
||||
$max_display_comments = (!empty($_POST['max_display_comments']) ? intval($_POST['max_display_comments']) : 0);
|
||||
$temppath = (!empty($_POST['temppath']) ? Strings::escapeTags(trim($_POST['temppath'])) : '');
|
||||
$singleuser = (!empty($_POST['singleuser']) ? Strings::escapeTags(trim($_POST['singleuser'])) : '');
|
||||
$proxy_disabled = !empty($_POST['proxy_disabled']);
|
||||
|
|
@ -407,6 +408,7 @@ class Site extends BaseAdmin
|
|||
DI::config()->set('system', 'itemcache', $itemcache);
|
||||
DI::config()->set('system', 'itemcache_duration', $itemcache_duration);
|
||||
DI::config()->set('system', 'max_comments', $max_comments);
|
||||
DI::config()->set('system', 'max_display_comments', $max_display_comments);
|
||||
|
||||
if ($temppath != '') {
|
||||
$temppath = BasePath::getRealPath($temppath);
|
||||
|
|
@ -546,7 +548,7 @@ class Site extends BaseAdmin
|
|||
|
||||
$check_git_version_choices = [
|
||||
'none' => DI::l10n()->t('Don\'t check'),
|
||||
'master' => DI::l10n()->t('check the stable version'),
|
||||
'stable' => DI::l10n()->t('check the stable version'),
|
||||
'develop' => DI::l10n()->t('check the development version')
|
||||
];
|
||||
|
||||
|
|
@ -613,7 +615,7 @@ class Site extends BaseAdmin
|
|||
'$worker_title' => DI::l10n()->t('Worker'),
|
||||
'$relay_title' => DI::l10n()->t('Message Relay'),
|
||||
'$relocate' => DI::l10n()->t('Relocate Instance'),
|
||||
'$relocate_warning' => DI::l10n()->t('Warning! Advanced function. Could make this server unreachable.'),
|
||||
'$relocate_warning' => DI::l10n()->t('<strong>Warning!</strong> Advanced function. Could make this server unreachable.'),
|
||||
'$baseurl' => DI::baseUrl()->get(true),
|
||||
|
||||
// name, label, value, help string, extra data...
|
||||
|
|
@ -695,6 +697,7 @@ class Site extends BaseAdmin
|
|||
'$itemcache' => ['itemcache', DI::l10n()->t('Path to item cache'), DI::config()->get('system', 'itemcache'), DI::l10n()->t('The item caches buffers generated bbcode and external images.')],
|
||||
'$itemcache_duration' => ['itemcache_duration', DI::l10n()->t('Cache duration in seconds'), DI::config()->get('system', 'itemcache_duration'), DI::l10n()->t('How long should the cache files be hold? Default value is 86400 seconds (One day). To disable the item cache, set the value to -1.')],
|
||||
'$max_comments' => ['max_comments', DI::l10n()->t('Maximum numbers of comments per post'), DI::config()->get('system', 'max_comments'), DI::l10n()->t('How much comments should be shown for each post? Default value is 100.')],
|
||||
'$max_display_comments' => ['max_display_comments', DI::l10n()->t('Maximum numbers of comments per post on the display page'), DI::config()->get('system', 'max_display_comments'), DI::l10n()->t('How many comments should be shown on the single view for each post? Default value is 1000.')],
|
||||
'$temppath' => ['temppath', DI::l10n()->t('Temp path'), DI::config()->get('system', 'temppath'), DI::l10n()->t('If you have a restricted system where the webserver can\'t access the system temp path, enter another path here.')],
|
||||
'$proxy_disabled' => ['proxy_disabled', DI::l10n()->t('Disable picture proxy'), DI::config()->get('system', 'proxy_disabled'), DI::l10n()->t('The picture proxy increases performance and privacy. It shouldn\'t be used on systems with very low bandwidth.')],
|
||||
'$only_tag_search' => ['only_tag_search', DI::l10n()->t('Only search in tags'), DI::config()->get('system', 'only_tag_search'), DI::l10n()->t('On large systems the text search can slow down the system extremely.')],
|
||||
|
|
|
|||
|
|
@ -31,7 +31,9 @@ use Friendica\Database\DBStructure;
|
|||
use Friendica\DI;
|
||||
use Friendica\Model\Register;
|
||||
use Friendica\Module\BaseAdmin;
|
||||
use Friendica\Module\Update\Profile;
|
||||
use Friendica\Network\HTTPException\InternalServerErrorException;
|
||||
use Friendica\Render\FriendicaSmarty;
|
||||
use Friendica\Util\ConfigFileLoader;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
use Friendica\Util\Network;
|
||||
|
|
@ -46,12 +48,35 @@ class Summary extends BaseAdmin
|
|||
|
||||
// are there MyISAM tables in the DB? If so, trigger a warning message
|
||||
$warningtext = [];
|
||||
|
||||
$templateEngine = Renderer::getTemplateEngine();
|
||||
$errors = [];
|
||||
$templateEngine->testInstall($errors);
|
||||
foreach ($errors as $error) {
|
||||
$warningtext[] = DI::l10n()->t('Template engine (%s) error: %s', $templateEngine::$name, $error);
|
||||
}
|
||||
|
||||
if (DBA::count(['information_schema' => 'tables'], ['engine' => 'myisam', 'table_schema' => DBA::databaseName()])) {
|
||||
$warningtext[] = DI::l10n()->t('Your DB still runs with MyISAM tables. You should change the engine type to InnoDB. As Friendica will use InnoDB only features in the future, you should change this! See <a href="%s">here</a> for a guide that may be helpful converting the table engines. You may also use the command <tt>php bin/console.php dbstructure toinnodb</tt> of your Friendica installation for an automatic conversion.<br />', 'https://dev.mysql.com/doc/refman/5.7/en/converting-tables-to-innodb.html');
|
||||
}
|
||||
|
||||
// Check if github.com/friendica/master/VERSION is higher then
|
||||
// the local version of Friendica. Check is opt-in, source may be master or devel branch
|
||||
// are there InnoDB tables in Antelope in the DB? If so, trigger a warning message
|
||||
if (DBA::count(['information_schema' => 'tables'], ['ENGINE' => 'InnoDB', 'ROW_FORMAT' => ['COMPACT', 'REDUNDANT'], 'table_schema' => DBA::databaseName()])) {
|
||||
$warningtext[] = DI::l10n()->t('Your DB still runs with InnoDB tables in the Antelope file format. You should change the file format to Barracuda. Friendica is using features that are not provided by the Antelope format. See <a href="%s">here</a> for a guide that may be helpful converting the table engines. You may also use the command <tt>php bin/console.php dbstructure toinnodb</tt> of your Friendica installation for an automatic conversion.<br />', 'https://dev.mysql.com/doc/refman/5.7/en/innodb-file-format.html');
|
||||
}
|
||||
|
||||
// Avoid the database error 1615 "Prepared statement needs to be re-prepared", see https://github.com/friendica/friendica/issues/8550
|
||||
$table_definition_cache = DBA::getVariable('table_definition_cache');
|
||||
$table_open_cache = DBA::getVariable('table_open_cache');
|
||||
if (!empty($table_definition_cache) && !empty($table_open_cache)) {
|
||||
$suggested_definition_cache = min(400 + round($table_open_cache / 2, 1), 2000);
|
||||
if ($suggested_definition_cache > $table_definition_cache) {
|
||||
$warningtext[] = DI::l10n()->t('Your table_definition_cache is too low (%d). This can lead to the database error "Prepared statement needs to be re-prepared". Please set it at least to %d (or -1 for autosizing). See <a href="%s">here</a> for more information.<br />', $table_definition_cache, $suggested_definition_cache, 'https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_table_definition_cache');
|
||||
}
|
||||
}
|
||||
|
||||
// Check if github.com/friendica/stable/VERSION is higher then
|
||||
// the local version of Friendica. Check is opt-in, source may be stable or develop branch
|
||||
if (DI::config()->get('system', 'check_new_version_url', 'none') != 'none') {
|
||||
$gitversion = DI::config()->get('system', 'git_friendica_version');
|
||||
if (version_compare(FRIENDICA_VERSION, $gitversion) < 0) {
|
||||
|
|
@ -121,7 +146,6 @@ class Summary extends BaseAdmin
|
|||
throw new InternalServerErrorException('Stream is null.');
|
||||
}
|
||||
}
|
||||
|
||||
} catch (\Throwable $exception) {
|
||||
$warningtext[] = DI::l10n()->t('The debug logfile \'%s\' is not usable. No logging possible (error: \'%s\')', $file, $exception->getMessage());
|
||||
}
|
||||
|
|
@ -178,7 +202,7 @@ class Summary extends BaseAdmin
|
|||
}
|
||||
DBA::close($pageFlagsCountStmt);
|
||||
|
||||
Logger::log('accounts: ' . print_r($accounts, true), Logger::DATA);
|
||||
Logger::debug('accounts', ['accounts' => $accounts]);
|
||||
|
||||
$pending = Register::getPendingCount();
|
||||
|
||||
|
|
|
|||
|
|
@ -157,15 +157,15 @@ class Users extends BaseAdmin
|
|||
$pager = new Pager(DI::l10n(), DI::args()->getQueryString(), 100);
|
||||
|
||||
$valid_orders = [
|
||||
'contact.name',
|
||||
'user.email',
|
||||
'user.register_date',
|
||||
'user.login_date',
|
||||
'lastitem_date',
|
||||
'user.page-flags'
|
||||
'name',
|
||||
'email',
|
||||
'register_date',
|
||||
'login_date',
|
||||
'last-item',
|
||||
'page-flags'
|
||||
];
|
||||
|
||||
$order = 'contact.name';
|
||||
$order = 'name';
|
||||
$order_direction = '+';
|
||||
if (!empty($_GET['o'])) {
|
||||
$new_order = $_GET['o'];
|
||||
|
|
@ -179,7 +179,7 @@ class Users extends BaseAdmin
|
|||
}
|
||||
}
|
||||
|
||||
$users = User::getList($pager->getStart(), $pager->getItemsPerPage(), 'all', $order, $order_direction);
|
||||
$users = User::getList($pager->getStart(), $pager->getItemsPerPage(), 'all', $order, ($order_direction == '-'));
|
||||
|
||||
$adminlist = explode(',', str_replace(' ', '', DI::config()->get('config', 'admin_email')));
|
||||
$_setup_users = function ($e) use ($adminlist) {
|
||||
|
|
@ -206,7 +206,7 @@ class Users extends BaseAdmin
|
|||
|
||||
$e['register_date'] = Temporal::getRelativeDate($e['register_date']);
|
||||
$e['login_date'] = Temporal::getRelativeDate($e['login_date']);
|
||||
$e['lastitem_date'] = Temporal::getRelativeDate($e['lastitem_date']);
|
||||
$e['lastitem_date'] = Temporal::getRelativeDate($e['last-item']);
|
||||
$e['is_admin'] = in_array($e['email'], $adminlist);
|
||||
$e['is_deletable'] = (intval($e['uid']) != local_user());
|
||||
$e['deleted'] = ($e['account_removed'] ? Temporal::getRelativeDate($e['account_expires_on']) : False);
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ class Show extends BaseApi
|
|||
foreach ($profileFields as $profileField) {
|
||||
$custom_fields[] = [
|
||||
'label' => $profileField->label,
|
||||
'value' => BBCode::convert($profileField->value, false, 2),
|
||||
'value' => BBCode::convert($profileField->value, false, BBCode::API),
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
|||
223
src/Module/Api/Twitter/ContactEndpoint.php
Normal file
223
src/Module/Api/Twitter/ContactEndpoint.php
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
<?php
|
||||
/**
|
||||
* @copyright Copyright (C) 2020, Friendica
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Friendica\Module\Api\Twitter;
|
||||
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\DI;
|
||||
use Friendica\Model\Profile;
|
||||
use Friendica\Model\User;
|
||||
use Friendica\Module\BaseApi;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Network\HTTPException;
|
||||
use Friendica\Util\Strings;
|
||||
|
||||
abstract class ContactEndpoint extends BaseApi
|
||||
{
|
||||
const DEFAULT_COUNT = 20;
|
||||
const MAX_COUNT = 200;
|
||||
|
||||
public static function init(array $parameters = [])
|
||||
{
|
||||
parent::init($parameters);
|
||||
|
||||
if (!self::login()) {
|
||||
throw new HTTPException\UnauthorizedException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the uid from the contact_id + screen_name parameters
|
||||
*
|
||||
* @param int|null $contact_id
|
||||
* @param string $screen_name
|
||||
* @return int
|
||||
* @throws HTTPException\NotFoundException
|
||||
*/
|
||||
protected static function getUid(int $contact_id = null, string $screen_name = null)
|
||||
{
|
||||
$uid = self::$current_user_id;
|
||||
|
||||
if ($contact_id || $screen_name) {
|
||||
// screen_name trumps user_id when both are provided
|
||||
if (!$screen_name) {
|
||||
$contact = Contact::getById($contact_id, ['nick', 'url']);
|
||||
// We don't have the followers of remote accounts so we check for locality
|
||||
if (empty($contact) || !Strings::startsWith($contact['url'], DI::baseUrl()->get())) {
|
||||
throw new HTTPException\NotFoundException(DI::l10n()->t('Contact not found'));
|
||||
}
|
||||
|
||||
$screen_name = $contact['nick'];
|
||||
}
|
||||
|
||||
$user = User::getByNickname($screen_name, ['uid']);
|
||||
if (empty($user)) {
|
||||
throw new HTTPException\NotFoundException(DI::l10n()->t('User not found'));
|
||||
}
|
||||
|
||||
$uid = $user['uid'];
|
||||
}
|
||||
|
||||
return $uid;
|
||||
}
|
||||
|
||||
/**
|
||||
* This methods expands the contact ids into full user objects in an existing result set.
|
||||
*
|
||||
* @param mixed $rel A relationship constant or a list of them
|
||||
* @param int $uid The local user id we query the contacts from
|
||||
* @param int $cursor
|
||||
* @param int $count
|
||||
* @param bool $skip_status
|
||||
* @param bool $include_user_entities
|
||||
* @return array
|
||||
* @throws HTTPException\InternalServerErrorException
|
||||
* @throws HTTPException\NotFoundException
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
protected static function list($rel, int $uid, int $cursor = -1, int $count = self::DEFAULT_COUNT, bool $skip_status = false, bool $include_user_entities = true)
|
||||
{
|
||||
$return = self::ids($rel, $uid, $cursor, $count);
|
||||
|
||||
$users = [];
|
||||
foreach ($return['ids'] as $contactId) {
|
||||
$users[] = DI::twitterUser()->createFromContactId($contactId, $uid, $skip_status, $include_user_entities);
|
||||
}
|
||||
|
||||
unset($return['ids']);
|
||||
$return['users'] = $users;
|
||||
|
||||
$return = [
|
||||
'users' => $users,
|
||||
'next_cursor' => $return['next_cursor'],
|
||||
'next_cursor_str' => $return['next_cursor_str'],
|
||||
'previous_cursor' => $return['previous_cursor'],
|
||||
'previous_cursor_str' => $return['previous_cursor_str'],
|
||||
'total_count' => $return['total_count'],
|
||||
];
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed $rel A relationship constant or a list of them
|
||||
* @param int $uid The local user id we query the contacts from
|
||||
* @param int $cursor
|
||||
* @param int $count
|
||||
* @param bool $stringify_ids
|
||||
* @return array
|
||||
* @throws HTTPException\NotFoundException
|
||||
*/
|
||||
protected static function ids($rel, int $uid, int $cursor = -1, int $count = self::DEFAULT_COUNT, bool $stringify_ids = false)
|
||||
{
|
||||
$hide_friends = false;
|
||||
if ($uid != self::$current_user_id) {
|
||||
$profile = Profile::getByUID($uid);
|
||||
if (empty($profile)) {
|
||||
throw new HTTPException\NotFoundException(DI::l10n()->t('Profile not found'));
|
||||
}
|
||||
|
||||
$hide_friends = (bool)$profile['hide-friends'];
|
||||
}
|
||||
|
||||
$ids = [];
|
||||
$next_cursor = 0;
|
||||
$previous_cursor = 0;
|
||||
$total_count = 0;
|
||||
if (!$hide_friends) {
|
||||
$condition = DBA::collapseCondition([
|
||||
'rel' => $rel,
|
||||
'uid' => $uid,
|
||||
'self' => false,
|
||||
'deleted' => false,
|
||||
'hidden' => false,
|
||||
'archive' => false,
|
||||
'pending' => false
|
||||
]);
|
||||
|
||||
$total_count = DBA::count('contact', $condition);
|
||||
|
||||
if ($cursor !== -1) {
|
||||
if ($cursor > 0) {
|
||||
$condition[0] .= " AND `id` > ?";
|
||||
$condition[] = $cursor;
|
||||
} else {
|
||||
$condition[0] .= " AND `id` < ?";
|
||||
$condition[] = -$cursor;
|
||||
}
|
||||
}
|
||||
|
||||
$contacts = Contact::selectToArray(['id'], $condition, ['limit' => $count, 'order' => ['id']]);
|
||||
|
||||
// Contains user-specific contact ids
|
||||
$ids = array_column($contacts, 'id');
|
||||
|
||||
// Cursor is on the user-specific contact id since it's the sort field
|
||||
if (count($ids)) {
|
||||
$previous_cursor = -$ids[0];
|
||||
$next_cursor = $ids[count($ids) -1];
|
||||
}
|
||||
|
||||
// No next page
|
||||
if ($total_count <= count($contacts) || count($contacts) < $count) {
|
||||
$next_cursor = 0;
|
||||
}
|
||||
// End of results
|
||||
if ($cursor < 0 && count($contacts) === 0) {
|
||||
$next_cursor = -1;
|
||||
}
|
||||
|
||||
// No previous page
|
||||
if ($cursor === -1) {
|
||||
$previous_cursor = 0;
|
||||
}
|
||||
|
||||
if ($cursor > 0 && count($contacts) === 0) {
|
||||
$previous_cursor = -$cursor;
|
||||
}
|
||||
|
||||
if ($cursor < 0 && count($contacts) === 0) {
|
||||
$next_cursor = -1;
|
||||
}
|
||||
|
||||
// Conversion to public contact ids
|
||||
array_walk($ids, function (&$contactId) use ($uid, $stringify_ids) {
|
||||
$cdata = Contact::getPublicAndUserContacID($contactId, $uid);
|
||||
if ($stringify_ids) {
|
||||
$contactId = (string)$cdata['public'];
|
||||
} else {
|
||||
$contactId = (int)$cdata['public'];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
$return = [
|
||||
'ids' => $ids,
|
||||
'next_cursor' => $next_cursor,
|
||||
'next_cursor_str' => (string)$next_cursor,
|
||||
'previous_cursor' => $previous_cursor,
|
||||
'previous_cursor_str' => (string)$previous_cursor,
|
||||
'total_count' => $total_count,
|
||||
];
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
||||
58
src/Module/Api/Twitter/FollowersIds.php
Normal file
58
src/Module/Api/Twitter/FollowersIds.php
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
<?php
|
||||
/**
|
||||
* @copyright Copyright (C) 2020, Friendica
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Friendica\Module\Api\Twitter;
|
||||
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Model\Contact;
|
||||
|
||||
/**
|
||||
* @see https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-followers-ids
|
||||
*/
|
||||
class FollowersIds extends ContactEndpoint
|
||||
{
|
||||
public static function rawContent(array $parameters = [])
|
||||
{
|
||||
// Expected value for user_id parameter: public/user contact id
|
||||
$contact_id = filter_input(INPUT_GET, 'user_id' , FILTER_VALIDATE_INT);
|
||||
$screen_name = filter_input(INPUT_GET, 'screen_name');
|
||||
$cursor = filter_input(INPUT_GET, 'cursor' , FILTER_VALIDATE_INT);
|
||||
$stringify_ids = filter_input(INPUT_GET, 'stringify_ids', FILTER_VALIDATE_BOOLEAN);
|
||||
$count = filter_input(INPUT_GET, 'count' , FILTER_VALIDATE_INT, ['options' => [
|
||||
'default' => self::DEFAULT_COUNT,
|
||||
'min_range' => 1,
|
||||
'max_range' => self::MAX_COUNT,
|
||||
]]);
|
||||
// Friendica-specific
|
||||
$since_id = filter_input(INPUT_GET, 'since_id' , FILTER_VALIDATE_INT);
|
||||
$max_id = filter_input(INPUT_GET, 'max_id' , FILTER_VALIDATE_INT, ['options' => [
|
||||
'default' => 1,
|
||||
]]);
|
||||
|
||||
System::jsonExit(self::ids(
|
||||
[Contact::FOLLOWER, Contact::FRIEND],
|
||||
self::getUid($contact_id, $screen_name),
|
||||
$cursor ?? $since_id ?? - $max_id,
|
||||
$count,
|
||||
$stringify_ids
|
||||
));
|
||||
}
|
||||
}
|
||||
62
src/Module/Api/Twitter/FollowersList.php
Normal file
62
src/Module/Api/Twitter/FollowersList.php
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
/**
|
||||
* @copyright Copyright (C) 2020, Friendica
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Friendica\Module\Api\Twitter;
|
||||
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Model\Contact;
|
||||
|
||||
/**
|
||||
* @see https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-followers-list
|
||||
*/
|
||||
class FollowersList extends ContactEndpoint
|
||||
{
|
||||
public static function rawContent(array $parameters = [])
|
||||
{
|
||||
// Expected value for user_id parameter: public/user contact id
|
||||
$contact_id = filter_input(INPUT_GET, 'user_id' , FILTER_VALIDATE_INT);
|
||||
$screen_name = filter_input(INPUT_GET, 'screen_name');
|
||||
$cursor = filter_input(INPUT_GET, 'cursor' , FILTER_VALIDATE_INT);
|
||||
$count = filter_input(INPUT_GET, 'count' , FILTER_VALIDATE_INT, ['options' => [
|
||||
'default' => self::DEFAULT_COUNT,
|
||||
'min_range' => 1,
|
||||
'max_range' => self::MAX_COUNT,
|
||||
]]);
|
||||
$skip_status = filter_input(INPUT_GET, 'skip_status' , FILTER_VALIDATE_BOOLEAN);
|
||||
$include_user_entities = filter_input(INPUT_GET, 'include_user_entities', FILTER_VALIDATE_BOOLEAN);
|
||||
|
||||
// Friendica-specific
|
||||
$since_id = filter_input(INPUT_GET, 'since_id' , FILTER_VALIDATE_INT);
|
||||
$max_id = filter_input(INPUT_GET, 'max_id' , FILTER_VALIDATE_INT, ['options' => [
|
||||
'default' => 1,
|
||||
]]);
|
||||
|
||||
|
||||
System::jsonExit(self::list(
|
||||
[Contact::FOLLOWER, Contact::FRIEND],
|
||||
self::getUid($contact_id, $screen_name),
|
||||
$cursor ?? $since_id ?? - $max_id,
|
||||
$count,
|
||||
$skip_status,
|
||||
$include_user_entities
|
||||
));
|
||||
}
|
||||
}
|
||||
58
src/Module/Api/Twitter/FriendsIds.php
Normal file
58
src/Module/Api/Twitter/FriendsIds.php
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
<?php
|
||||
/**
|
||||
* @copyright Copyright (C) 2020, Friendica
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Friendica\Module\Api\Twitter;
|
||||
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Model\Contact;
|
||||
|
||||
/**
|
||||
* @see https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friends-ids
|
||||
*/
|
||||
class FriendsIds extends ContactEndpoint
|
||||
{
|
||||
public static function rawContent(array $parameters = [])
|
||||
{
|
||||
// Expected value for user_id parameter: public/user contact id
|
||||
$contact_id = filter_input(INPUT_GET, 'user_id' , FILTER_VALIDATE_INT);
|
||||
$screen_name = filter_input(INPUT_GET, 'screen_name');
|
||||
$cursor = filter_input(INPUT_GET, 'cursor' , FILTER_VALIDATE_INT);
|
||||
$stringify_ids = filter_input(INPUT_GET, 'stringify_ids', FILTER_VALIDATE_BOOLEAN);
|
||||
$count = filter_input(INPUT_GET, 'count' , FILTER_VALIDATE_INT, ['options' => [
|
||||
'default' => self::DEFAULT_COUNT,
|
||||
'min_range' => 1,
|
||||
'max_range' => self::MAX_COUNT,
|
||||
]]);
|
||||
// Friendica-specific
|
||||
$since_id = filter_input(INPUT_GET, 'since_id' , FILTER_VALIDATE_INT);
|
||||
$max_id = filter_input(INPUT_GET, 'max_id' , FILTER_VALIDATE_INT, ['options' => [
|
||||
'default' => 1,
|
||||
]]);
|
||||
|
||||
System::jsonExit(self::ids(
|
||||
[Contact::SHARING, Contact::FRIEND],
|
||||
self::getUid($contact_id, $screen_name),
|
||||
$cursor ?? $since_id ?? - $max_id,
|
||||
$count,
|
||||
$stringify_ids
|
||||
));
|
||||
}
|
||||
}
|
||||
61
src/Module/Api/Twitter/FriendsList.php
Normal file
61
src/Module/Api/Twitter/FriendsList.php
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
/**
|
||||
* @copyright Copyright (C) 2020, Friendica
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Friendica\Module\Api\Twitter;
|
||||
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Model\Contact;
|
||||
|
||||
/**
|
||||
* @see https://developer.twitter.com/en/docs/accounts-and-users/follow-search-get-users/api-reference/get-friends-list
|
||||
*/
|
||||
class FriendsList extends ContactEndpoint
|
||||
{
|
||||
public static function rawContent(array $parameters = [])
|
||||
{
|
||||
// Expected value for user_id parameter: public/user contact id
|
||||
$contact_id = filter_input(INPUT_GET, 'user_id' , FILTER_VALIDATE_INT);
|
||||
$screen_name = filter_input(INPUT_GET, 'screen_name');
|
||||
$cursor = filter_input(INPUT_GET, 'cursor' , FILTER_VALIDATE_INT);
|
||||
$count = filter_input(INPUT_GET, 'count' , FILTER_VALIDATE_INT, ['options' => [
|
||||
'default' => self::DEFAULT_COUNT,
|
||||
'min_range' => 1,
|
||||
'max_range' => self::MAX_COUNT,
|
||||
]]);
|
||||
$skip_status = filter_input(INPUT_GET, 'skip_status' , FILTER_VALIDATE_BOOLEAN);
|
||||
$include_user_entities = filter_input(INPUT_GET, 'include_user_entities', FILTER_VALIDATE_BOOLEAN);
|
||||
|
||||
// Friendica-specific
|
||||
$since_id = filter_input(INPUT_GET, 'since_id' , FILTER_VALIDATE_INT);
|
||||
$max_id = filter_input(INPUT_GET, 'max_id' , FILTER_VALIDATE_INT, ['options' => [
|
||||
'default' => 1,
|
||||
]]);
|
||||
|
||||
System::jsonExit(self::list(
|
||||
[Contact::SHARING, Contact::FRIEND],
|
||||
self::getUid($contact_id, $screen_name),
|
||||
$cursor ?? $since_id ?? - $max_id,
|
||||
$count,
|
||||
$skip_status,
|
||||
$include_user_entities
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
@ -76,7 +76,7 @@ abstract class BaseAdmin extends BaseModule
|
|||
}
|
||||
|
||||
if (!empty($_SESSION['submanage'])) {
|
||||
throw new ForbiddenException(DI::l10n()->t('Submanaged account can\'t access the administation pages. Please log back in as the master account.'));
|
||||
throw new ForbiddenException(DI::l10n()->t('Submanaged account can\'t access the administation pages. Please log back in as the main account.'));
|
||||
}
|
||||
|
||||
// Header stuff
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ class Contact extends BaseModule
|
|||
|
||||
$fetch_further_information = intval($_POST['fetch_further_information'] ?? 0);
|
||||
|
||||
$ffi_keyword_blacklist = Strings::escapeHtml(trim($_POST['ffi_keyword_blacklist'] ?? ''));
|
||||
$ffi_keyword_denylist = Strings::escapeHtml(trim($_POST['ffi_keyword_denylist'] ?? ''));
|
||||
|
||||
$priority = intval($_POST['poll'] ?? 0);
|
||||
if ($priority > 5 || $priority < 0) {
|
||||
|
|
@ -140,7 +140,7 @@ class Contact extends BaseModule
|
|||
'hidden' => $hidden,
|
||||
'notify_new_posts' => $notify,
|
||||
'fetch_further_information' => $fetch_further_information,
|
||||
'ffi_keyword_blacklist' => $ffi_keyword_blacklist],
|
||||
'ffi_keyword_denylist' => $ffi_keyword_denylist],
|
||||
['id' => $contact_id, 'uid' => local_user()]
|
||||
);
|
||||
|
||||
|
|
@ -167,10 +167,9 @@ class Contact extends BaseModule
|
|||
return;
|
||||
}
|
||||
|
||||
$uid = $contact['uid'];
|
||||
|
||||
if ($contact['network'] == Protocol::OSTATUS) {
|
||||
$result = Model\Contact::createFromProbe($uid, $contact['url'], false, $contact['network']);
|
||||
$user = Model\User::getById($contact['uid']);
|
||||
$result = Model\Contact::createFromProbe($user, $contact['url'], false, $contact['network']);
|
||||
|
||||
if ($result['success']) {
|
||||
DBA::update('contact', ['subhub' => 1], ['id' => $contact_id]);
|
||||
|
|
@ -613,7 +612,7 @@ class Contact extends BaseModule
|
|||
'$hidden' => ['hidden', DI::l10n()->t('Hide this contact from others'), ($contact['hidden'] == 1), DI::l10n()->t('Replies/likes to your public posts <strong>may</strong> still be visible')],
|
||||
'$notify' => ['notify', DI::l10n()->t('Notification for new posts'), ($contact['notify_new_posts'] == 1), DI::l10n()->t('Send a notification of every new post of this contact')],
|
||||
'$fetch_further_information' => $fetch_further_information,
|
||||
'$ffi_keyword_blacklist' => ['ffi_keyword_blacklist', DI::l10n()->t('Blacklisted keywords'), $contact['ffi_keyword_blacklist'], DI::l10n()->t('Comma separated list of keywords that should not be converted to hashtags, when "Fetch information and keywords" is selected')],
|
||||
'$ffi_keyword_denylist' => ['ffi_keyword_denylist', DI::l10n()->t('Keyword Deny List'), $contact['ffi_keyword_denylist'], DI::l10n()->t('Comma separated list of keywords that should not be converted to hashtags, when "Fetch information and keywords" is selected')],
|
||||
'$photo' => $contact['photo'],
|
||||
'$name' => $contact['name'],
|
||||
'$dir_icon' => $dir_icon,
|
||||
|
|
@ -715,15 +714,14 @@ class Contact extends BaseModule
|
|||
$sql_values[] = $group;
|
||||
}
|
||||
|
||||
$sql_extra .= Widget::unavailableNetworks();
|
||||
|
||||
$total = 0;
|
||||
$stmt = DBA::p("SELECT COUNT(*) AS `total`
|
||||
FROM `contact`
|
||||
WHERE `uid` = ?
|
||||
AND `self` = 0
|
||||
AND NOT `deleted`
|
||||
$sql_extra",
|
||||
$sql_extra
|
||||
" . Widget::unavailableNetworks(),
|
||||
$sql_values
|
||||
);
|
||||
if (DBA::isResult($stmt)) {
|
||||
|
|
@ -976,7 +974,12 @@ class Contact extends BaseModule
|
|||
$profiledata = Model\Contact::getDetailsByURL($contact['url']);
|
||||
|
||||
Model\Profile::load($a, '', $profiledata, true);
|
||||
$o .= Model\Contact::getPostsFromUrl($contact['url'], true, $update);
|
||||
|
||||
if ($contact['uid'] == 0) {
|
||||
$o .= Model\Contact::getPostsFromId($contact['id'], true, $update);
|
||||
} else {
|
||||
$o .= Model\Contact::getPostsFromUrl($contact['url'], true, $update);
|
||||
}
|
||||
}
|
||||
|
||||
return $o;
|
||||
|
|
@ -998,7 +1001,12 @@ class Contact extends BaseModule
|
|||
}
|
||||
|
||||
Model\Profile::load($a, '', $profiledata, true);
|
||||
$o .= Model\Contact::getPostsFromUrl($contact['url']);
|
||||
|
||||
if ($contact['uid'] == 0) {
|
||||
$o .= Model\Contact::getPostsFromId($contact['id']);
|
||||
} else {
|
||||
$o .= Model\Contact::getPostsFromUrl($contact['url']);
|
||||
}
|
||||
}
|
||||
|
||||
return $o;
|
||||
|
|
|
|||
164
src/Module/Contact/Poke.php
Normal file
164
src/Module/Contact/Poke.php
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
<?php
|
||||
|
||||
namespace Friendica\Module\Contact;
|
||||
|
||||
use Friendica\BaseModule;
|
||||
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;
|
||||
use Friendica\Network\HTTPException;
|
||||
use Friendica\Protocol\Activity;
|
||||
use Friendica\Util\XML;
|
||||
|
||||
class Poke extends BaseModule
|
||||
{
|
||||
public static function post(array $parameters = [])
|
||||
{
|
||||
if (!local_user() || empty($parameters['id'])) {
|
||||
return self::postReturn(false);
|
||||
}
|
||||
|
||||
$uid = local_user();
|
||||
|
||||
if (empty($_POST['verb'])) {
|
||||
return self::postReturn(false);
|
||||
}
|
||||
|
||||
$verb = $_POST['verb'];
|
||||
|
||||
$verbs = DI::l10n()->getPokeVerbs();
|
||||
if (!array_key_exists($verb, $verbs)) {
|
||||
return self::postReturn(false);
|
||||
}
|
||||
|
||||
$activity = Activity::POKE . '#' . urlencode($verbs[$verb][0]);
|
||||
|
||||
$contact_id = intval($parameters['id']);
|
||||
if (!$contact_id) {
|
||||
return self::postReturn(false);
|
||||
}
|
||||
|
||||
Logger::info('verb ' . $verb . ' contact ' . $contact_id);
|
||||
|
||||
$contact = DBA::selectFirst('contact', ['id', 'name'], ['id' => $parameters['id'], 'uid' => local_user()]);
|
||||
if (!DBA::isResult($contact)) {
|
||||
return self::postReturn(false);
|
||||
}
|
||||
|
||||
$a = DI::app();
|
||||
|
||||
$private = (!empty($_GET['private']) ? intval($_GET['private']) : Model\Item::PUBLIC);
|
||||
|
||||
$allow_cid = ($private ? '<' . $contact['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']);
|
||||
|
||||
$actor = $a->contact;
|
||||
|
||||
$uri = Model\Item::newURI($uid);
|
||||
|
||||
$arr = [];
|
||||
|
||||
$arr['guid'] = System::createUUID();
|
||||
$arr['uid'] = $uid;
|
||||
$arr['uri'] = $uri;
|
||||
$arr['parent-uri'] = $uri;
|
||||
$arr['wall'] = 1;
|
||||
$arr['contact-id'] = $actor['id'];
|
||||
$arr['owner-name'] = $actor['name'];
|
||||
$arr['owner-link'] = $actor['url'];
|
||||
$arr['owner-avatar'] = $actor['thumb'];
|
||||
$arr['author-name'] = $actor['name'];
|
||||
$arr['author-link'] = $actor['url'];
|
||||
$arr['author-avatar'] = $actor['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=' . $actor['url'] . ']' . $actor['name'] . '[/url]' . ' ' . $verbs[$verb][2] . ' ' . '[url=' . $contact['url'] . ']' . $contact['name'] . '[/url]';
|
||||
|
||||
$arr['object'] = '<object><type>' . Activity\ObjectType::PERSON . '</type><title>' . XML::escape($contact['name']) . '</title><id>' . XML::escape($contact['url']) . '</id>';
|
||||
$arr['object'] .= '<link>' . XML::escape('<link rel="alternate" type="text/html" href="' . $contact['url'] . '" />') . "\n";
|
||||
|
||||
$arr['object'] .= XML::escape('<link rel="photo" type="image/jpeg" href="' . $contact['photo'] . '" />') . "\n";
|
||||
$arr['object'] .= '</link></object>' . "\n";
|
||||
|
||||
$result = Model\Item::insert($arr);
|
||||
|
||||
Hook::callAll('post_local_end', $arr);
|
||||
|
||||
return self::postReturn($result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Since post() is called before rawContent(), we need to be able to return a JSON response in post() directly.
|
||||
*
|
||||
* @param bool $success
|
||||
* @return bool
|
||||
*/
|
||||
private static function postReturn(bool $success)
|
||||
{
|
||||
if ($success) {
|
||||
info(DI::l10n()->t('Poke successfully sent.'));
|
||||
} else {
|
||||
notice(DI::l10n()->t('Error while sending poke, please retry.'));
|
||||
}
|
||||
|
||||
if (DI::mode()->isAjax()) {
|
||||
System::jsonExit(['success' => $success]);
|
||||
}
|
||||
|
||||
return $success;
|
||||
}
|
||||
|
||||
public static function content(array $parameters = [])
|
||||
{
|
||||
if (!local_user()) {
|
||||
throw new HTTPException\UnauthorizedException(DI::l10n()->t('You must be logged in to use this module.'));
|
||||
}
|
||||
|
||||
if (empty($parameters['id'])) {
|
||||
throw new HTTPException\BadRequestException();
|
||||
}
|
||||
|
||||
$contact = DBA::selectFirst('contact', ['id', 'url', 'name'], ['id' => $parameters['id'], 'uid' => local_user()]);
|
||||
if (!DBA::isResult($contact)) {
|
||||
throw new HTTPException\NotFoundException();
|
||||
}
|
||||
|
||||
Model\Profile::load(DI::app(), '', Model\Contact::getDetailsByURL($contact["url"]));
|
||||
|
||||
$verbs = [];
|
||||
foreach (DI::l10n()->getPokeVerbs() as $verb => $translations) {
|
||||
if ($translations[1] !== 'NOTRANSLATION') {
|
||||
$verbs[$verb] = $translations[1];
|
||||
}
|
||||
}
|
||||
|
||||
$tpl = Renderer::getMarkupTemplate('contact/poke.tpl');
|
||||
$o = Renderer::replaceMacros($tpl,[
|
||||
'$title' => DI::l10n()->t('Poke/Prod'),
|
||||
'$desc' => DI::l10n()->t('poke, prod or do other things to somebody'),
|
||||
'$id' => $contact['id'],
|
||||
'$verb' => ['verb', DI::l10n()->t('Choose what you wish to do to recipient'), '', '', $verbs],
|
||||
'$private' => ['private', DI::l10n()->t('Make this post private')],
|
||||
'$loading' => DI::l10n()->t('Loading...'),
|
||||
'$submit' => DI::l10n()->t('Submit'),
|
||||
|
||||
]);
|
||||
|
||||
return $o;
|
||||
}
|
||||
}
|
||||
|
|
@ -22,10 +22,12 @@
|
|||
namespace Friendica\Module\Debug;
|
||||
|
||||
use Friendica\BaseModule;
|
||||
use Friendica\Content\PageInfo;
|
||||
use Friendica\Content\Text;
|
||||
use Friendica\Core\Renderer;
|
||||
use Friendica\DI;
|
||||
use Friendica\Model\Item;
|
||||
use Friendica\Model\Tag;
|
||||
use Friendica\Util\XML;
|
||||
|
||||
/**
|
||||
|
|
@ -101,19 +103,31 @@ class Babel extends BaseModule
|
|||
'content' => visible_whitespace($bbcode4)
|
||||
];
|
||||
|
||||
$item = [
|
||||
'body' => $bbcode,
|
||||
'tag' => '',
|
||||
];
|
||||
$tags = Text\BBCode::getTags($bbcode);
|
||||
|
||||
Item::setHashtags($item);
|
||||
$body = Item::setHashtags($bbcode);
|
||||
$results[] = [
|
||||
'title' => DI::l10n()->t('Item Body'),
|
||||
'content' => visible_whitespace($item['body'])
|
||||
'content' => visible_whitespace($body)
|
||||
];
|
||||
$results[] = [
|
||||
'title' => DI::l10n()->t('Item Tags'),
|
||||
'content' => $item['tag']
|
||||
'content' => visible_whitespace(var_export($tags, true)),
|
||||
];
|
||||
|
||||
$body2 = PageInfo::appendToBody($bbcode, true);
|
||||
$results[] = [
|
||||
'title' => DI::l10n()->t('PageInfo::appendToBody'),
|
||||
'content' => visible_whitespace($body2)
|
||||
];
|
||||
$html3 = Text\BBCode::convert($body2);
|
||||
$results[] = [
|
||||
'title' => DI::l10n()->t('PageInfo::appendToBody => BBCode::convert (raw HTML)'),
|
||||
'content' => visible_whitespace($html3)
|
||||
];
|
||||
$results[] = [
|
||||
'title' => DI::l10n()->t('PageInfo::appendToBody => BBCode::convert'),
|
||||
'content' => $html3
|
||||
];
|
||||
break;
|
||||
case 'diaspora':
|
||||
|
|
@ -125,9 +139,7 @@ class Babel extends BaseModule
|
|||
|
||||
$markdown = XML::unescape($diaspora);
|
||||
case 'markdown':
|
||||
if (!isset($markdown)) {
|
||||
$markdown = trim($_REQUEST['text']);
|
||||
}
|
||||
$markdown = $markdown ?? trim($_REQUEST['text']);
|
||||
|
||||
$results[] = [
|
||||
'title' => DI::l10n()->t('Source input (Markdown)'),
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ class Fetch extends BaseModule
|
|||
if (empty($item)) {
|
||||
$condition = ['guid' => $guid, 'network' => [Protocol::DFRN, Protocol::DIASPORA]];
|
||||
$item = Item::selectFirst(['author-link'], $condition);
|
||||
if (empty($item)) {
|
||||
if (!empty($item["author-link"])) {
|
||||
$parts = parse_url($item["author-link"]);
|
||||
if (empty($parts["scheme"]) || empty($parts["host"])) {
|
||||
throw new HTTPException\InternalServerErrorException();
|
||||
|
|
|
|||
|
|
@ -148,7 +148,7 @@ class Receive extends BaseModule
|
|||
}
|
||||
|
||||
self::$logger->info('Diaspora: Post decoded.');
|
||||
self::$logger->debug('Diaspora: Decoded message.', ['msg' => print_r($msg, true)]);
|
||||
self::$logger->debug('Diaspora: Decoded message.', ['msg' => $msg]);
|
||||
|
||||
if (!is_array($msg)) {
|
||||
throw new HTTPException\InternalServerErrorException('Message is not an array.');
|
||||
|
|
|
|||
|
|
@ -120,9 +120,9 @@ class Directory extends BaseModule
|
|||
*/
|
||||
public static function formatEntry(array $contact, $photo_size = 'photo')
|
||||
{
|
||||
$itemurl = (($contact['addr'] != "") ? $contact['addr'] : $contact['profile_url']);
|
||||
$itemurl = (($contact['addr'] != "") ? $contact['addr'] : $contact['url']);
|
||||
|
||||
$profile_link = $contact['profile_url'];
|
||||
$profile_link = $contact['url'];
|
||||
|
||||
$about = (($contact['about']) ? $contact['about'] . '<br />' : '');
|
||||
|
||||
|
|
|
|||
|
|
@ -22,8 +22,8 @@
|
|||
namespace Friendica\Module;
|
||||
|
||||
use Friendica\BaseModule;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\DI;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Model\User;
|
||||
use Friendica\Protocol\ActivityPub;
|
||||
|
||||
|
|
@ -49,7 +49,7 @@ class Followers extends BaseModule
|
|||
|
||||
$page = $_REQUEST['page'] ?? null;
|
||||
|
||||
$followers = ActivityPub\Transmitter::getFollowers($owner, $page);
|
||||
$followers = ActivityPub\Transmitter::getContacts($owner, [Contact::FOLLOWER, Contact::FRIEND], 'followers', $page);
|
||||
|
||||
header('Content-Type: application/activity+json');
|
||||
echo json_encode($followers);
|
||||
|
|
|
|||
|
|
@ -22,8 +22,8 @@
|
|||
namespace Friendica\Module;
|
||||
|
||||
use Friendica\BaseModule;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\DI;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Model\User;
|
||||
use Friendica\Protocol\ActivityPub;
|
||||
|
||||
|
|
@ -49,10 +49,10 @@ class Following extends BaseModule
|
|||
|
||||
$page = $_REQUEST['page'] ?? null;
|
||||
|
||||
$Following = ActivityPub\Transmitter::getFollowing($owner, $page);
|
||||
$following = ActivityPub\Transmitter::getContacts($owner, [Contact::SHARING, Contact::FRIEND], 'following', $page);
|
||||
|
||||
header('Content-Type: application/activity+json');
|
||||
echo json_encode($Following);
|
||||
echo json_encode($following);
|
||||
exit();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@ use Friendica\Util\Strings;
|
|||
*/
|
||||
class Hashtag extends BaseModule
|
||||
{
|
||||
|
||||
public static function content(array $parameters = [])
|
||||
{
|
||||
$result = [];
|
||||
|
|
@ -41,12 +40,9 @@ class Hashtag extends BaseModule
|
|||
System::jsonExit($result);
|
||||
}
|
||||
|
||||
$taglist = DBA::p("SELECT DISTINCT(`term`) FROM `term` WHERE `term` LIKE ? AND `type` = ? ORDER BY `term`",
|
||||
$t . '%',
|
||||
intval(TERM_HASHTAG)
|
||||
);
|
||||
$taglist = DBA::select('tag', ['name'], ["`name` LIKE ?", $t . "%"], ['order' => ['name'], 'limit' => 100]);
|
||||
while ($tag = DBA::fetch($taglist)) {
|
||||
$result[] = ['text' => $tag['term']];
|
||||
$result[] = ['text' => $tag['name']];
|
||||
}
|
||||
DBA::close($taglist);
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ use Friendica\Core\Session;
|
|||
use Friendica\DI;
|
||||
use Friendica\Model\Profile;
|
||||
use Friendica\Model\User;
|
||||
use Friendica\Network\HTTPException\NotFoundException;
|
||||
use Friendica\Network\HTTPException;
|
||||
|
||||
/**
|
||||
* Loads a profile for the HoverCard view
|
||||
|
|
@ -44,11 +44,15 @@ class HoverCard extends BaseModule
|
|||
// Show the profile hovercard
|
||||
$nickname = $parameters['profile'];
|
||||
} else {
|
||||
throw new NotFoundException(DI::l10n()->t('No profile'));
|
||||
throw new HTTPException\NotFoundException(DI::l10n()->t('No profile'));
|
||||
}
|
||||
|
||||
Profile::load($a, $nickname);
|
||||
|
||||
if (empty($a->profile)) {
|
||||
throw new HTTPException\NotFoundException(DI::l10n()->t('User not found.'));
|
||||
}
|
||||
|
||||
$page = DI::page();
|
||||
|
||||
if (!empty($a->profile['page-flags']) && ($a->profile['page-flags'] == User::PAGE_FLAGS_COMMUNITY)) {
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
namespace Friendica\Module;
|
||||
|
||||
use Friendica\BaseModule;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\DI;
|
||||
use Friendica\Model\Item;
|
||||
use Friendica\Core\Session;
|
||||
|
|
@ -68,5 +69,7 @@ class Like extends BaseModule
|
|||
|
||||
DI::baseUrl()->redirect($returnPath . $rand);
|
||||
}
|
||||
|
||||
System::jsonExit(['status' => 'OK']);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,9 +42,9 @@ class Magic extends BaseModule
|
|||
{
|
||||
$a = DI::app();
|
||||
$ret = ['success' => false, 'url' => '', 'message' => ''];
|
||||
Logger::log('magic mdule: invoked', Logger::DEBUG);
|
||||
Logger::info('magic mdule: invoked');
|
||||
|
||||
Logger::log('args: ' . print_r($_REQUEST, true), Logger::DATA);
|
||||
Logger::debug('args', ['request' => $_REQUEST]);
|
||||
|
||||
$addr = $_REQUEST['addr'] ?? '';
|
||||
$dest = $_REQUEST['dest'] ?? '';
|
||||
|
|
@ -73,7 +73,7 @@ class Magic extends BaseModule
|
|||
return $ret;
|
||||
}
|
||||
|
||||
Logger::log('Contact is already authenticated', Logger::DEBUG);
|
||||
Logger::info('Contact is already authenticated');
|
||||
System::externalRedirect($dest);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -46,14 +46,17 @@ class NoScrape extends BaseModule
|
|||
$which = $parameters['nick'];
|
||||
} elseif (local_user() && isset($parameters['profile']) && DI::args()->get(2) == 'view') {
|
||||
// view infos about a known profile (needs a login)
|
||||
$which = $a->user['nickname'];
|
||||
$which = $a->user['nickname'];
|
||||
} else {
|
||||
System::jsonError(403, 'Authentication required');
|
||||
exit();
|
||||
}
|
||||
|
||||
Profile::load($a, $which);
|
||||
|
||||
if (empty($a->profile['uid'])) {
|
||||
System::jsonError(404, 'Profile not found');
|
||||
}
|
||||
|
||||
$json_info = [
|
||||
'addr' => $a->profile['addr'],
|
||||
'nick' => $which,
|
||||
|
|
@ -85,22 +88,11 @@ class NoScrape extends BaseModule
|
|||
$json_info['tags'] = $keywords;
|
||||
$json_info['language'] = $a->profile['language'];
|
||||
|
||||
if (!($a->profile['hide-friends'] ?? false)) {
|
||||
$stmt = DBA::p(
|
||||
"SELECT `gcontact`.`updated`
|
||||
FROM `contact`
|
||||
INNER JOIN `gcontact`
|
||||
WHERE `gcontact`.`nurl` = `contact`.`nurl`
|
||||
AND `self`
|
||||
AND `uid` = ?
|
||||
LIMIT 1",
|
||||
intval($a->profile['uid'])
|
||||
);
|
||||
if ($gcontact = DBA::fetch($stmt)) {
|
||||
$json_info["updated"] = date("c", strtotime($gcontact['updated']));
|
||||
}
|
||||
DBA::close($stmt);
|
||||
if (!empty($a->profile['last-item'])) {
|
||||
$json_info['updated'] = date("c", strtotime($a->profile['last-item']));
|
||||
}
|
||||
|
||||
if (!($a->profile['hide-friends'] ?? false)) {
|
||||
$json_info['contacts'] = DBA::count('contact',
|
||||
[
|
||||
'uid' => $a->profile['uid'],
|
||||
|
|
|
|||
|
|
@ -22,10 +22,13 @@
|
|||
namespace Friendica\Module;
|
||||
|
||||
use Friendica\BaseModule;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\DI;
|
||||
use Friendica\Model\Item;
|
||||
use Friendica\Network\HTTPException;
|
||||
use Friendica\Protocol\ActivityPub;
|
||||
use Friendica\Util\Network;
|
||||
|
||||
/**
|
||||
* ActivityPub Objects
|
||||
|
|
@ -34,10 +37,8 @@ class Objects extends BaseModule
|
|||
{
|
||||
public static function rawContent(array $parameters = [])
|
||||
{
|
||||
$a = DI::app();
|
||||
|
||||
if (empty($a->argv[1])) {
|
||||
throw new \Friendica\Network\HTTPException\NotFoundException();
|
||||
if (empty($parameters['guid'])) {
|
||||
throw new HTTPException\BadRequestException();
|
||||
}
|
||||
|
||||
if (!ActivityPub::isRequest()) {
|
||||
|
|
@ -47,31 +48,50 @@ class Objects extends BaseModule
|
|||
/// @todo Add Authentication to enable fetching of non public content
|
||||
// $requester = HTTPSignature::getSigner('', $_SERVER);
|
||||
|
||||
// At first we try the original post with that guid
|
||||
// @TODO: Replace with parameter from router
|
||||
$item = Item::selectFirst(['id'], ['guid' => $a->argv[1], 'origin' => true, 'private' => [item::PUBLIC, Item::UNLISTED]]);
|
||||
if (!DBA::isResult($item)) {
|
||||
// If no original post could be found, it could possibly be a forum post, there we remove the "origin" field.
|
||||
// @TODO: Replace with parameter from router
|
||||
$item = Item::selectFirst(['id', 'author-link'], ['guid' => $a->argv[1], 'private' => [item::PUBLIC, Item::UNLISTED]]);
|
||||
if (!DBA::isResult($item) || !strstr($item['author-link'], DI::baseUrl()->get())) {
|
||||
throw new \Friendica\Network\HTTPException\NotFoundException();
|
||||
$item = Item::selectFirst(
|
||||
['id', 'origin', 'author-link', 'changed'],
|
||||
[
|
||||
'guid' => $parameters['guid'],
|
||||
'private' => [Item::PUBLIC, Item::UNLISTED]
|
||||
],
|
||||
['order' => ['origin' => true]]
|
||||
);
|
||||
// Valid items are original post or posted from this node (including in the case of a forum)
|
||||
if (!DBA::isResult($item) || !$item['origin'] && (parse_url($item['author-link'], PHP_URL_HOST) != parse_url(DI::baseUrl()->get(), PHP_URL_HOST))) {
|
||||
throw new HTTPException\NotFoundException();
|
||||
}
|
||||
|
||||
$etag = md5($parameters['guid'] . '-' . $item['changed']);
|
||||
$last_modified = $item['changed'];
|
||||
Network::checkEtagModified($etag, $last_modified);
|
||||
|
||||
if (empty($parameters['activity'])) {
|
||||
$activity = ActivityPub\Transmitter::createActivityFromItem($item['id'], true);
|
||||
$activity['type'] = $activity['type'] == 'Update' ? 'Create' : $activity['type'];
|
||||
|
||||
// Only display "Create" activity objects here, no reshares or anything else
|
||||
if (empty($activity['object']) || ($activity['type'] != 'Create')) {
|
||||
throw new HTTPException\NotFoundException();
|
||||
}
|
||||
|
||||
$data = ['@context' => ActivityPub::CONTEXT];
|
||||
$data = array_merge($data, $activity['object']);
|
||||
} elseif (in_array($parameters['activity'], ['Create', 'Announce', 'Update',
|
||||
'Like', 'Dislike', 'Accept', 'Reject', 'TentativeAccept', 'Follow', 'Add'])) {
|
||||
$data = ActivityPub\Transmitter::createActivityFromItem($item['id']);
|
||||
if (empty($data)) {
|
||||
throw new HTTPException\NotFoundException();
|
||||
}
|
||||
if ($parameters['activity'] != 'Create') {
|
||||
$data['type'] = $parameters['activity'];
|
||||
$data['id'] = str_replace('/Create', '/' . $parameters['activity'], $data['id']);
|
||||
}
|
||||
} else {
|
||||
throw new HTTPException\NotFoundException();
|
||||
}
|
||||
|
||||
$activity = ActivityPub\Transmitter::createActivityFromItem($item['id'], true);
|
||||
$activity['type'] = $activity['type'] == 'Update' ? 'Create' : $activity['type'];
|
||||
|
||||
// Only display "Create" activity objects here, no reshares or anything else
|
||||
if (empty($activity['object']) || ($activity['type'] != 'Create')) {
|
||||
throw new \Friendica\Network\HTTPException\NotFoundException();
|
||||
}
|
||||
|
||||
$data = ['@context' => ActivityPub::CONTEXT];
|
||||
$data = array_merge($data, $activity['object']);
|
||||
|
||||
header('Content-Type: application/activity+json');
|
||||
echo json_encode($data);
|
||||
exit();
|
||||
// Relaxed CORS header for public items
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
System::jsonExit($data, 'application/activity+json');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,8 +76,7 @@ class Owa extends BaseModule
|
|||
$verified = HTTPSignature::verifyMagic($contact['pubkey']);
|
||||
|
||||
if ($verified && $verified['header_signed'] && $verified['header_valid']) {
|
||||
Logger::log('OWA header: ' . print_r($verified, true), Logger::DATA);
|
||||
Logger::log('OWA success: ' . $contact['addr'], Logger::DATA);
|
||||
Logger::debug('OWA header', ['addr' => $contact['addr'], 'data' => $verified]);
|
||||
|
||||
$ret['success'] = true;
|
||||
$token = Strings::getRandomHex(32);
|
||||
|
|
@ -94,10 +93,10 @@ class Owa extends BaseModule
|
|||
openssl_public_encrypt($token, $result, $contact['pubkey']);
|
||||
$ret['encrypted_token'] = Strings::base64UrlEncode($result);
|
||||
} else {
|
||||
Logger::log('OWA fail: ' . $contact['id'] . ' ' . $contact['addr'] . ' ' . $contact['url'], Logger::DEBUG);
|
||||
Logger::info('OWA fail', ['id' => $contact['id'], 'addr' => $contact['addr'], 'url' => $contact['url']]);
|
||||
}
|
||||
} else {
|
||||
Logger::log('Contact not found: ' . $handle, Logger::DEBUG);
|
||||
Logger::info('Contact not found', ['handle' => $handle]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ use Friendica\Database\DBA;
|
|||
use Friendica\DI;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Model\Profile as ProfileModel;
|
||||
use Friendica\Model\Term;
|
||||
use Friendica\Model\Tag;
|
||||
use Friendica\Model\User;
|
||||
use Friendica\Module\BaseProfile;
|
||||
use Friendica\Module\Security\Login;
|
||||
|
|
@ -54,6 +54,8 @@ class Profile extends BaseProfile
|
|||
// The function returns an empty array when the account is removed, expired or blocked
|
||||
$data = ActivityPub\Transmitter::getProfile($user['uid']);
|
||||
if (!empty($data)) {
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
header('Cache-Control: max-age=23200, stale-while-revalidate=23200');
|
||||
System::jsonExit($data, 'application/activity+json');
|
||||
}
|
||||
}
|
||||
|
|
@ -182,7 +184,7 @@ class Profile extends BaseProfile
|
|||
foreach (explode(',', $a->profile['pub_keywords']) as $tag_label) {
|
||||
$tags[] = [
|
||||
'url' => '/search?tag=' . $tag_label,
|
||||
'label' => Term::TAG_CHARACTER[Term::HASHTAG] . $tag_label,
|
||||
'label' => Tag::TAG_CHARACTER[Tag::HASHTAG] . $tag_label,
|
||||
];
|
||||
}
|
||||
|
||||
|
|
@ -297,10 +299,10 @@ class Profile extends BaseProfile
|
|||
$htmlhead .= '<meta content="noindex, noarchive" name="robots" />' . "\n";
|
||||
}
|
||||
|
||||
$htmlhead .= '<link rel="alternate" type="application/atom+xml" href="' . $baseUrl . '/dfrn_poll/' . $nickname . '" title="DFRN: ' . DI::l10n()->t('%s\'s timeline', $profile['username']) . '"/>' . "\n";
|
||||
$htmlhead .= '<link rel="alternate" type="application/atom+xml" href="' . $baseUrl . '/feed/' . $nickname . '/" title="' . DI::l10n()->t('%s\'s posts', $profile['username']) . '"/>' . "\n";
|
||||
$htmlhead .= '<link rel="alternate" type="application/atom+xml" href="' . $baseUrl . '/feed/' . $nickname . '/comments" title="' . DI::l10n()->t('%s\'s comments', $profile['username']) . '"/>' . "\n";
|
||||
$htmlhead .= '<link rel="alternate" type="application/atom+xml" href="' . $baseUrl . '/feed/' . $nickname . '/activity" title="' . DI::l10n()->t('%s\'s timeline', $profile['username']) . '"/>' . "\n";
|
||||
$htmlhead .= '<link rel="alternate" type="application/atom+xml" href="' . $baseUrl . '/dfrn_poll/' . $nickname . '" title="DFRN: ' . DI::l10n()->t('%s\'s timeline', $profile['name']) . '"/>' . "\n";
|
||||
$htmlhead .= '<link rel="alternate" type="application/atom+xml" href="' . $baseUrl . '/feed/' . $nickname . '/" title="' . DI::l10n()->t('%s\'s posts', $profile['name']) . '"/>' . "\n";
|
||||
$htmlhead .= '<link rel="alternate" type="application/atom+xml" href="' . $baseUrl . '/feed/' . $nickname . '/comments" title="' . DI::l10n()->t('%s\'s comments', $profile['name']) . '"/>' . "\n";
|
||||
$htmlhead .= '<link rel="alternate" type="application/atom+xml" href="' . $baseUrl . '/feed/' . $nickname . '/activity" title="' . DI::l10n()->t('%s\'s timeline', $profile['name']) . '"/>' . "\n";
|
||||
$uri = urlencode('acct:' . $profile['nickname'] . '@' . $baseUrl->getHostname() . ($baseUrl->getUrlPath() ? '/' . $baseUrl->getUrlPath() : ''));
|
||||
$htmlhead .= '<link rel="lrdd" type="application/xrd+xml" href="' . $baseUrl . '/xrd/?uri=' . $uri . '" />' . "\n";
|
||||
header('Link: <' . $baseUrl . '/xrd/?uri=' . $uri . '>; rel="lrdd"; type="application/xrd+xml"', false);
|
||||
|
|
|
|||
|
|
@ -29,10 +29,12 @@ use Friendica\Core\Session;
|
|||
use Friendica\Database\DBA;
|
||||
use Friendica\DI;
|
||||
use Friendica\Model\Item;
|
||||
use Friendica\Model\Post\Category;
|
||||
use Friendica\Model\Profile as ProfileModel;
|
||||
use Friendica\Model\User;
|
||||
use Friendica\Module\BaseProfile;
|
||||
use Friendica\Module\Security\Login;
|
||||
use Friendica\Network\HTTPException;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
use Friendica\Util\Security;
|
||||
use Friendica\Util\Strings;
|
||||
|
|
@ -48,14 +50,18 @@ class Status extends BaseProfile
|
|||
|
||||
ProfileModel::load($a, $parameters['nickname']);
|
||||
|
||||
if (empty($a->profile)) {
|
||||
throw new HTTPException\NotFoundException(DI::l10n()->t('User not found.'));
|
||||
}
|
||||
|
||||
if (!$a->profile['net-publish']) {
|
||||
DI::page()['htmlhead'] .= '<meta content="noindex, noarchive" name="robots" />' . "\n";
|
||||
}
|
||||
|
||||
DI::page()['htmlhead'] .= '<link rel="alternate" type="application/atom+xml" href="' . DI::baseUrl() . '/dfrn_poll/' . $parameters['nickname'] . '" title="DFRN: ' . DI::l10n()->t('%s\'s timeline', $a->profile['username']) . '"/>' . "\n";
|
||||
DI::page()['htmlhead'] .= '<link rel="alternate" type="application/atom+xml" href="' . DI::baseUrl() . '/feed/' . $parameters['nickname'] . '/" title="' . DI::l10n()->t('%s\'s posts', $a->profile['username']) . '"/>' . "\n";
|
||||
DI::page()['htmlhead'] .= '<link rel="alternate" type="application/atom+xml" href="' . DI::baseUrl() . '/feed/' . $parameters['nickname'] . '/comments" title="' . DI::l10n()->t('%s\'s comments', $a->profile['username']) . '"/>' . "\n";
|
||||
DI::page()['htmlhead'] .= '<link rel="alternate" type="application/atom+xml" href="' . DI::baseUrl() . '/feed/' . $parameters['nickname'] . '/activity" title="' . DI::l10n()->t('%s\'s timeline', $a->profile['username']) . '"/>' . "\n";
|
||||
DI::page()['htmlhead'] .= '<link rel="alternate" type="application/atom+xml" href="' . DI::baseUrl() . '/dfrn_poll/' . $parameters['nickname'] . '" title="DFRN: ' . DI::l10n()->t('%s\'s timeline', $a->profile['name']) . '"/>' . "\n";
|
||||
DI::page()['htmlhead'] .= '<link rel="alternate" type="application/atom+xml" href="' . DI::baseUrl() . '/feed/' . $parameters['nickname'] . '/" title="' . DI::l10n()->t('%s\'s posts', $a->profile['name']) . '"/>' . "\n";
|
||||
DI::page()['htmlhead'] .= '<link rel="alternate" type="application/atom+xml" href="' . DI::baseUrl() . '/feed/' . $parameters['nickname'] . '/comments" title="' . DI::l10n()->t('%s\'s comments', $a->profile['name']) . '"/>' . "\n";
|
||||
DI::page()['htmlhead'] .= '<link rel="alternate" type="application/atom+xml" href="' . DI::baseUrl() . '/feed/' . $parameters['nickname'] . '/activity" title="' . DI::l10n()->t('%s\'s timeline', $a->profile['name']) . '"/>' . "\n";
|
||||
|
||||
$category = $datequery = $datequery2 = '';
|
||||
|
||||
|
|
@ -141,13 +147,13 @@ class Status extends BaseProfile
|
|||
$sql_post_table = "";
|
||||
|
||||
if (!empty($category)) {
|
||||
$sql_post_table = sprintf("INNER JOIN (SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d ORDER BY `tid` DESC) AS `term` ON `item`.`id` = `term`.`oid` ",
|
||||
DBA::escape(Strings::protectSprintf($category)), intval(TERM_OBJ_POST), intval(TERM_CATEGORY), intval($a->profile['uid']));
|
||||
$sql_post_table = sprintf("INNER JOIN (SELECT `uri-id` FROM `category-view` WHERE `name` = '%s' AND `type` = %d AND `uid` = %d ORDER BY `uri-id` DESC) AS `category` ON `item`.`uri-id` = `category`.`uri-id` ",
|
||||
DBA::escape(Strings::protectSprintf($category)), intval(Category::CATEGORY), intval($a->profile['uid']));
|
||||
}
|
||||
|
||||
if (!empty($hashtags)) {
|
||||
$sql_post_table .= sprintf("INNER JOIN (SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d ORDER BY `tid` DESC) AS `term` ON `item`.`id` = `term`.`oid` ",
|
||||
DBA::escape(Strings::protectSprintf($hashtags)), intval(TERM_OBJ_POST), intval(TERM_HASHTAG), intval($a->profile['uid']));
|
||||
$sql_post_table .= sprintf("INNER JOIN (SELECT `uri-id` FROM `tag-search-view` WHERE `name` = '%s' AND `uid` = %d ORDER BY `uri-id` DESC) AS `tag-search` ON `item`.`uri-id` = `tag-search`.`uri-id` ",
|
||||
DBA::escape(Strings::protectSprintf($hashtags)), intval($a->profile['uid']));
|
||||
}
|
||||
|
||||
if (!empty($datequery)) {
|
||||
|
|
@ -161,7 +167,7 @@ class Status extends BaseProfile
|
|||
// If not then we can improve the performance with an additional condition
|
||||
$condition = ['uid' => $a->profile['uid'], 'page-flags' => [User::PAGE_FLAGS_COMMUNITY, User::PAGE_FLAGS_PRVGROUP]];
|
||||
if (!DBA::exists('user', $condition)) {
|
||||
$sql_extra3 = sprintf(" AND `thread`.`contact-id` = %d ", intval(intval($a->profile['contact_id'])));
|
||||
$sql_extra3 = sprintf(" AND `thread`.`contact-id` = %d ", intval(intval($a->profile['id'])));
|
||||
} else {
|
||||
$sql_extra3 = "";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,17 +67,14 @@ class RemoteFollow extends BaseModule
|
|||
return;
|
||||
}
|
||||
|
||||
// Fetch link for the "remote follow" functionality of the given profile
|
||||
$follow_link_template = Probe::getRemoteFollowLink($url);
|
||||
|
||||
if (empty($follow_link_template)) {
|
||||
if (empty($data['subscribe'])) {
|
||||
notice(DI::l10n()->t("Remote subscription can't be done for your network. Please subscribe directly on your system."));
|
||||
return;
|
||||
}
|
||||
|
||||
Logger::notice('Remote request', ['url' => $url, 'follow' => $a->profile['url'], 'remote' => $follow_link_template]);
|
||||
Logger::notice('Remote request', ['url' => $url, 'follow' => $a->profile['url'], 'remote' => $data['subscribe']]);
|
||||
|
||||
// Substitute our user's feed URL into $follow_link_template
|
||||
// Substitute our user's feed URL into $data['subscribe']
|
||||
// Send the subscriber home to subscribe
|
||||
// Diaspora needs the uri in the format user@domain.tld
|
||||
if ($data['network'] == Protocol::DIASPORA) {
|
||||
|
|
@ -86,7 +83,7 @@ class RemoteFollow extends BaseModule
|
|||
$uri = urlencode($a->profile['url']);
|
||||
}
|
||||
|
||||
$follow_link = str_replace('{uri}', $uri, $follow_link_template);
|
||||
$follow_link = str_replace('{uri}', $uri, $data['subscribe']);
|
||||
System::externalRedirect($follow_link);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -79,6 +79,11 @@ class Acl extends BaseModule
|
|||
|
||||
$contacts = [];
|
||||
foreach ($r as $g) {
|
||||
if (empty($g['name'])) {
|
||||
DI::logger()->warning('Wrong result item from Search::searchGlobalContact', ['$g' => $g, '$search' => $search, '$mode' => $mode, '$page' => $page]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$contacts[] = [
|
||||
'photo' => ProxyUtils::proxifyUrl($g['photo'], false, ProxyUtils::SIZE_MICRO),
|
||||
'name' => htmlspecialchars($g['name']),
|
||||
|
|
|
|||
|
|
@ -28,12 +28,13 @@ use Friendica\Content\Widget;
|
|||
use Friendica\Core\Cache\Duration;
|
||||
use Friendica\Core\Logger;
|
||||
use Friendica\Core\Renderer;
|
||||
use Friendica\Core\Search;
|
||||
use Friendica\Core\Session;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\DI;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Model\Item;
|
||||
use Friendica\Model\Term;
|
||||
use Friendica\Model\Tag;
|
||||
use Friendica\Module\BaseSearch;
|
||||
use Friendica\Network\HTTPException;
|
||||
use Friendica\Util\Strings;
|
||||
|
|
@ -80,7 +81,7 @@ class Index extends BaseSearch
|
|||
}
|
||||
|
||||
if (local_user()) {
|
||||
DI::page()['aside'] .= Widget\SavedSearches::getHTML('search?q=' . urlencode($search), $search);
|
||||
DI::page()['aside'] .= Widget\SavedSearches::getHTML(Search::getSearchPath($search), $search);
|
||||
}
|
||||
|
||||
Nav::setSelected('search');
|
||||
|
|
@ -149,28 +150,11 @@ class Index extends BaseSearch
|
|||
|
||||
if ($tag) {
|
||||
Logger::info('Start tag search.', ['q' => $search]);
|
||||
$uriids = Tag::getURIIdListByTag($search, local_user(), $pager->getStart(), $pager->getItemsPerPage());
|
||||
|
||||
$condition = [
|
||||
"(`uid` = 0 OR (`uid` = ? AND NOT `global`))
|
||||
AND `otype` = ? AND `type` = ? AND `term` = ?",
|
||||
local_user(), Term::OBJECT_TYPE_POST, Term::HASHTAG, $search
|
||||
];
|
||||
$params = [
|
||||
'order' => ['received' => true],
|
||||
'limit' => [$pager->getStart(), $pager->getItemsPerPage()]
|
||||
];
|
||||
$terms = DBA::select('term', ['oid'], $condition, $params);
|
||||
|
||||
$itemids = [];
|
||||
while ($term = DBA::fetch($terms)) {
|
||||
$itemids[] = $term['oid'];
|
||||
}
|
||||
|
||||
DBA::close($terms);
|
||||
|
||||
if (!empty($itemids)) {
|
||||
$params = ['order' => ['id' => true]];
|
||||
$items = Item::selectForUser(local_user(), [], ['id' => $itemids], $params);
|
||||
if (!empty($uriids)) {
|
||||
$params = ['order' => ['id' => true], 'group_by' => ['uri-id']];
|
||||
$items = Item::selectForUser(local_user(), [], ['uri-id' => $uriids], $params);
|
||||
$r = Item::inArray($items);
|
||||
} else {
|
||||
$r = [];
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
namespace Friendica\Module\Search;
|
||||
|
||||
use Friendica\BaseModule;
|
||||
use Friendica\Core\Search;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\DI;
|
||||
use Friendica\Util\Strings;
|
||||
|
|
@ -33,7 +34,7 @@ class Saved extends BaseModule
|
|||
$action = DI::args()->get(2, 'none');
|
||||
$search = Strings::escapeTags(trim(rawurldecode($_GET['term'] ?? '')));
|
||||
|
||||
$return_url = $_GET['return_url'] ?? 'search?q=' . urlencode($search);
|
||||
$return_url = $_GET['return_url'] ?? Search::getSearchPath($search);
|
||||
|
||||
if (local_user() && $search) {
|
||||
switch ($action) {
|
||||
|
|
|
|||
|
|
@ -125,6 +125,7 @@ class Delegation extends BaseSettings
|
|||
while ($contact = DBA::fetch($contacts)) {
|
||||
$nicknames[] = $contact['nick'];
|
||||
}
|
||||
DBA::close($contacts);
|
||||
|
||||
// get user records for all potential page delegates who are not already delegates or managers
|
||||
$potentialDelegateUsers = DBA::selectToArray('user', ['uid', 'username', 'nickname'], ['nickname' => $nicknames]);
|
||||
|
|
|
|||
|
|
@ -52,9 +52,8 @@ class Index extends BaseSettings
|
|||
$filename = basename($_FILES['userfile']['name']);
|
||||
$filesize = intval($_FILES['userfile']['size']);
|
||||
$filetype = $_FILES['userfile']['type'];
|
||||
if ($filetype == '') {
|
||||
$filetype = Images::guessType($filename);
|
||||
}
|
||||
|
||||
$filetype = Images::getMimeTypeBySource($src, $filename, $filetype);
|
||||
|
||||
$maximagesize = DI::config()->get('system', 'maximagesize', 0);
|
||||
|
||||
|
|
|
|||
|
|
@ -85,6 +85,11 @@ class Xrd extends BaseModule
|
|||
|
||||
$owner = User::getOwnerDataById($user['uid']);
|
||||
|
||||
if (empty($owner)) {
|
||||
DI::logger()->warning('No owner data for user id', ['uri' => $uri, 'name' => $name, 'user' => $user]);
|
||||
throw new \Friendica\Network\HTTPException\NotFoundException();
|
||||
}
|
||||
|
||||
$alias = str_replace('/profile/', '/~', $owner['url']);
|
||||
|
||||
$avatar = Photo::selectFirst(['type'], ['uid' => $owner['uid'], 'profile' => true]);
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
namespace Friendica\Network;
|
||||
|
||||
use Friendica\Core\Logger;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Network\HTTPException\InternalServerErrorException;
|
||||
use Friendica\Util\Network;
|
||||
|
||||
|
|
@ -130,7 +131,7 @@ class CurlResult
|
|||
$this->errorNumber = $errorNumber;
|
||||
$this->error = $error;
|
||||
|
||||
Logger::log($url . ': ' . $this->returnCode . " " . $result, Logger::DATA);
|
||||
Logger::debug('construct', ['url' => $url, 'returncode' => $this->returnCode, 'result' => $result]);
|
||||
|
||||
$this->parseBodyHeader($result);
|
||||
$this->checkSuccess();
|
||||
|
|
@ -166,8 +167,8 @@ class CurlResult
|
|||
}
|
||||
|
||||
if (!$this->isSuccess) {
|
||||
Logger::log('error: ' . $this->url . ': ' . $this->returnCode . ' - ' . $this->error, Logger::INFO);
|
||||
Logger::log('debug: ' . print_r($this->info, true), Logger::DATA);
|
||||
Logger::notice('http error', ['url' => $this->url, 'code' => $this->returnCode, 'error' => $this->error, 'callstack' => System::callstack(20)]);
|
||||
Logger::debug('debug', ['info' => $this->info]);
|
||||
}
|
||||
|
||||
if (!$this->isSuccess && $this->errorNumber == CURLE_OPERATION_TIMEDOUT) {
|
||||
|
|
|
|||
|
|
@ -51,12 +51,12 @@ class FKOAuth1 extends OAuthServer
|
|||
*/
|
||||
public function loginUser($uid)
|
||||
{
|
||||
Logger::log("FKOAuth1::loginUser $uid");
|
||||
Logger::notice("FKOAuth1::loginUser $uid");
|
||||
$a = DI::app();
|
||||
$record = DBA::selectFirst('user', [], ['uid' => $uid, 'blocked' => 0, 'account_expired' => 0, 'account_removed' => 0, 'verified' => 1]);
|
||||
|
||||
if (!DBA::isResult($record)) {
|
||||
Logger::log('FKOAuth1::loginUser failure: ' . print_r($_SERVER, true), Logger::DEBUG);
|
||||
Logger::info('FKOAuth1::loginUser failure', ['server' => $_SERVER]);
|
||||
header('HTTP/1.0 401 Unauthorized');
|
||||
die('This api requires login');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,13 +24,15 @@ namespace Friendica\Network;
|
|||
use DOMDocument;
|
||||
use DomXPath;
|
||||
use Friendica\Core\Cache\Duration;
|
||||
use Friendica\Core\Hook;
|
||||
use Friendica\Core\Logger;
|
||||
use Friendica\Core\Protocol;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\DI;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Model\GServer;
|
||||
use Friendica\Model\Profile;
|
||||
use Friendica\Model\User;
|
||||
use Friendica\Protocol\ActivityNamespace;
|
||||
use Friendica\Protocol\ActivityPub;
|
||||
use Friendica\Protocol\Email;
|
||||
|
|
@ -45,6 +47,8 @@ use Friendica\Util\XML;
|
|||
*/
|
||||
class Probe
|
||||
{
|
||||
const WEBFINGER = '/.well-known/webfinger?resource={uri}';
|
||||
|
||||
private static $baseurl;
|
||||
private static $istimeout;
|
||||
|
||||
|
|
@ -84,16 +88,22 @@ class Probe
|
|||
{
|
||||
$fields = ["name", "nick", "guid", "url", "addr", "alias", "photo", "account-type",
|
||||
"community", "keywords", "location", "about", "hide",
|
||||
"batch", "notify", "poll", "request", "confirm", "poco",
|
||||
"batch", "notify", "poll", "request", "confirm", "subscribe", "poco",
|
||||
"following", "followers", "inbox", "outbox", "sharedinbox",
|
||||
"priority", "network", "pubkey", "baseurl"];
|
||||
"priority", "network", "pubkey", "baseurl", "gsid"];
|
||||
|
||||
$newdata = [];
|
||||
foreach ($fields as $field) {
|
||||
if (isset($data[$field])) {
|
||||
$newdata[$field] = $data[$field];
|
||||
} else {
|
||||
if (in_array($field, ["gsid", "hide", "account-type"])) {
|
||||
$newdata[$field] = (int)$data[$field];
|
||||
} else {
|
||||
$newdata[$field] = $data[$field];
|
||||
}
|
||||
} elseif ($field != "gsid") {
|
||||
$newdata[$field] = "";
|
||||
} else {
|
||||
$newdata[$field] = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -160,46 +170,46 @@ class Probe
|
|||
$ssl_connection_error = ($curlResult->getErrorNumber() == CURLE_COULDNT_CONNECT) || ($curlResult->getReturnCode() == 0);
|
||||
if ($curlResult->isSuccess()) {
|
||||
$xml = $curlResult->getBody();
|
||||
$xrd = XML::parseString($xml, false);
|
||||
$xrd = XML::parseString($xml, true);
|
||||
if (!empty($url)) {
|
||||
$host_url = 'https://' . $host;
|
||||
} else {
|
||||
$host_url = $host;
|
||||
}
|
||||
} elseif ($curlResult->isTimeout()) {
|
||||
Logger::info('Probing timeout', ['url' => $ssl_url], Logger::DEBUG);
|
||||
Logger::info('Probing timeout', ['url' => $ssl_url]);
|
||||
self::$istimeout = true;
|
||||
return false;
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!is_object($xrd) && !empty($url)) {
|
||||
$curlResult = Network::curl($url, false, ['timeout' => $xrd_timeout, 'accept_content' => 'application/xrd+xml']);
|
||||
$connection_error = ($curlResult->getErrorNumber() == CURLE_COULDNT_CONNECT) || ($curlResult->getReturnCode() == 0);
|
||||
if ($curlResult->isTimeout()) {
|
||||
Logger::info('Probing timeout', ['url' => $url], Logger::DEBUG);
|
||||
Logger::info('Probing timeout', ['url' => $url]);
|
||||
self::$istimeout = true;
|
||||
return false;
|
||||
return [];
|
||||
} elseif ($connection_error && $ssl_connection_error) {
|
||||
self::$istimeout = true;
|
||||
return false;
|
||||
return [];
|
||||
}
|
||||
|
||||
$xml = $curlResult->getBody();
|
||||
$xrd = XML::parseString($xml, false);
|
||||
$xrd = XML::parseString($xml, true);
|
||||
$host_url = 'http://'.$host;
|
||||
}
|
||||
if (!is_object($xrd)) {
|
||||
Logger::log("No xrd object found for ".$host, Logger::DEBUG);
|
||||
Logger::info('No xrd object found', ['host' => $host]);
|
||||
return [];
|
||||
}
|
||||
|
||||
$links = XML::elementToArray($xrd);
|
||||
if (!isset($links["xrd"]["link"])) {
|
||||
Logger::log("No xrd data found for ".$host, Logger::DEBUG);
|
||||
Logger::info('No xrd data found', ['host' => $host]);
|
||||
return [];
|
||||
}
|
||||
|
||||
$lrdd = ['application/jrd+json' => $host_url . '/.well-known/webfinger?resource={uri}'];
|
||||
$lrdd = [];
|
||||
|
||||
foreach ($links["xrd"]["link"] as $value => $link) {
|
||||
if (!empty($link["@attributes"])) {
|
||||
|
|
@ -219,7 +229,7 @@ class Probe
|
|||
|
||||
self::$baseurl = $host_url;
|
||||
|
||||
Logger::log("Probing successful for ".$host, Logger::DEBUG);
|
||||
Logger::info('Probing successful', ['host' => $host]);
|
||||
|
||||
return $lrdd;
|
||||
}
|
||||
|
|
@ -250,7 +260,7 @@ class Probe
|
|||
$profile_link = '';
|
||||
|
||||
$links = self::lrdd($webbie);
|
||||
Logger::log('webfingerDfrn: '.$webbie.':'.print_r($links, true), Logger::DATA);
|
||||
Logger::debug('Result', ['url' => $webbie, 'links' => $links]);
|
||||
if (!empty($links) && is_array($links)) {
|
||||
foreach ($links as $link) {
|
||||
if ($link['@attributes']['rel'] === ActivityNamespace::DFRN) {
|
||||
|
|
@ -267,110 +277,34 @@ class Probe
|
|||
return $profile_link;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the link for the remote follow page for a given profile link
|
||||
*
|
||||
* @param sting $profile
|
||||
* @return string Remote follow page link
|
||||
*/
|
||||
public static function getRemoteFollowLink(string $profile)
|
||||
{
|
||||
$follow_link = '';
|
||||
|
||||
$links = self::lrdd($profile);
|
||||
|
||||
if (!empty($links) && is_array($links)) {
|
||||
foreach ($links as $link) {
|
||||
if ($link['@attributes']['rel'] === ActivityNamespace::OSTATUSSUB) {
|
||||
$follow_link = $link['@attributes']['template'];
|
||||
}
|
||||
}
|
||||
}
|
||||
return $follow_link;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check an URI for LRDD data
|
||||
*
|
||||
* @param string $uri Address that should be probed
|
||||
* @param string $uri Address that should be probed
|
||||
*
|
||||
* @return array uri data
|
||||
* @throws HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public static function lrdd($uri)
|
||||
public static function lrdd(string $uri)
|
||||
{
|
||||
$lrdd = self::hostMeta($uri);
|
||||
$webfinger = null;
|
||||
|
||||
if (is_bool($lrdd)) {
|
||||
$data = self::getWebfingerArray($uri);
|
||||
if (empty($data)) {
|
||||
return [];
|
||||
}
|
||||
$webfinger = $data['webfinger'];
|
||||
|
||||
if (!$lrdd) {
|
||||
$parts = @parse_url($uri);
|
||||
if (!$parts || empty($parts["host"]) || empty($parts["path"])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$host = $parts['scheme'] . '://' . $parts["host"];
|
||||
if (!empty($parts["port"])) {
|
||||
$host .= ':'.$parts["port"];
|
||||
}
|
||||
|
||||
$path_parts = explode("/", trim($parts["path"], "/"));
|
||||
|
||||
$nick = array_pop($path_parts);
|
||||
|
||||
do {
|
||||
$lrdd = self::hostMeta($host);
|
||||
$host .= "/".array_shift($path_parts);
|
||||
} while (!$lrdd && (sizeof($path_parts) > 0));
|
||||
}
|
||||
|
||||
if (!$lrdd) {
|
||||
Logger::log("No lrdd data found for ".$uri, Logger::DEBUG);
|
||||
if (empty($webfinger["links"])) {
|
||||
Logger::info('No webfinger links found', ['uri' => $uri]);
|
||||
return [];
|
||||
}
|
||||
|
||||
foreach ($lrdd as $type => $template) {
|
||||
if ($webfinger) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$path = str_replace('{uri}', urlencode($uri), $template);
|
||||
$webfinger = self::webfinger($path, $type);
|
||||
|
||||
if (!$webfinger && (strstr($uri, "@"))) {
|
||||
$path = str_replace('{uri}', urlencode("acct:".$uri), $template);
|
||||
$webfinger = self::webfinger($path, $type);
|
||||
}
|
||||
|
||||
// Special treatment for Mastodon
|
||||
// Problem is that Mastodon uses an URL format like http://domain.tld/@nick
|
||||
// But the webfinger for this format fails.
|
||||
if (!$webfinger && !empty($nick)) {
|
||||
// Mastodon uses a "@" as prefix for usernames in their url format
|
||||
$nick = ltrim($nick, '@');
|
||||
|
||||
$addr = $nick."@".$host;
|
||||
|
||||
$path = str_replace('{uri}', urlencode("acct:".$addr), $template);
|
||||
$webfinger = self::webfinger($path, $type);
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_array($webfinger["links"])) {
|
||||
Logger::log("No webfinger links found for ".$uri, Logger::DEBUG);
|
||||
return false;
|
||||
}
|
||||
|
||||
$data = [];
|
||||
|
||||
foreach ($webfinger["links"] as $link) {
|
||||
$data[] = ["@attributes" => $link];
|
||||
}
|
||||
|
||||
if (is_array($webfinger["aliases"])) {
|
||||
if (!empty($webfinger["aliases"]) && is_array($webfinger["aliases"])) {
|
||||
foreach ($webfinger["aliases"] as $alias) {
|
||||
$data[] = ["@attributes" =>
|
||||
["rel" => "alias",
|
||||
|
|
@ -395,8 +329,9 @@ class Probe
|
|||
*/
|
||||
public static function uri($uri, $network = '', $uid = -1, $cache = true)
|
||||
{
|
||||
$cachekey = 'Probe::uri:' . $network . ':' . $uri;
|
||||
if ($cache) {
|
||||
$result = DI::cache()->get('Probe::uri:' . $network . ':' . $uri);
|
||||
$result = DI::cache()->get($cachekey);
|
||||
if (!is_null($result)) {
|
||||
return $result;
|
||||
}
|
||||
|
|
@ -406,19 +341,19 @@ class Probe
|
|||
$uid = local_user();
|
||||
}
|
||||
|
||||
if (empty($network) || ($network == Protocol::ACTIVITYPUB)) {
|
||||
$ap_profile = ActivityPub::probeProfile($uri);
|
||||
} else {
|
||||
$ap_profile = [];
|
||||
}
|
||||
|
||||
self::$istimeout = false;
|
||||
|
||||
if ($network != Protocol::ACTIVITYPUB) {
|
||||
$data = self::detect($uri, $network, $uid);
|
||||
} else {
|
||||
$data = null;
|
||||
}
|
||||
|
||||
// When the previous detection process had got a time out
|
||||
// we could falsely detect a Friendica profile as AP profile.
|
||||
if (!self::$istimeout) {
|
||||
$ap_profile = ActivityPub::probeProfile($uri);
|
||||
|
||||
$data = self::detect($uri, $network, $uid, $ap_profile);
|
||||
if (!is_array($data)) {
|
||||
$data = [];
|
||||
}
|
||||
if (empty($data) || (!empty($ap_profile) && empty($network) && (($data['network'] ?? '') != Protocol::DFRN))) {
|
||||
$data = $ap_profile;
|
||||
} elseif (!empty($ap_profile)) {
|
||||
|
|
@ -426,16 +361,14 @@ class Probe
|
|||
$data = array_merge($ap_profile, $data);
|
||||
}
|
||||
} else {
|
||||
Logger::notice('Time out detected. AP will not be probed.', ['uri' => $uri]);
|
||||
$data = $ap_profile;
|
||||
}
|
||||
|
||||
if (!isset($data['url'])) {
|
||||
$data['url'] = $uri;
|
||||
}
|
||||
|
||||
if (!empty($data['photo']) && !empty($data['baseurl'])) {
|
||||
$data['baseurl'] = Network::getUrlMatch(Strings::normaliseLink($data['baseurl']), Strings::normaliseLink($data['photo']));
|
||||
} elseif (empty($data['photo'])) {
|
||||
if (empty($data['photo'])) {
|
||||
$data['photo'] = DI::baseUrl() . '/images/person-300.jpg';
|
||||
}
|
||||
|
||||
|
|
@ -457,8 +390,8 @@ class Probe
|
|||
}
|
||||
}
|
||||
|
||||
if (!empty(self::$baseurl)) {
|
||||
$data['baseurl'] = self::$baseurl;
|
||||
if (!empty($data['baseurl']) && empty($data['gsid'])) {
|
||||
$data['gsid'] = GServer::getID($data['baseurl']);
|
||||
}
|
||||
|
||||
if (empty($data['network'])) {
|
||||
|
|
@ -478,7 +411,7 @@ class Probe
|
|||
|
||||
// Only store into the cache if the value seems to be valid
|
||||
if (!in_array($data['network'], [Protocol::PHANTOM, Protocol::MAIL])) {
|
||||
DI::cache()->set('Probe::uri:' . $network . ':' . $uri, $data, Duration::DAY);
|
||||
DI::cache()->set($cachekey, $data, Duration::DAY);
|
||||
}
|
||||
|
||||
return $data;
|
||||
|
|
@ -549,96 +482,219 @@ class Probe
|
|||
}
|
||||
|
||||
/**
|
||||
* Checks if a profile url should be OStatus but only provides partial information
|
||||
* Fetch the "subscribe" and add it to the result
|
||||
*
|
||||
* @param array $webfinger Webfinger data
|
||||
* @param string $lrdd Path template for webfinger request
|
||||
* @param string $type type
|
||||
*
|
||||
* @return array fixed webfinger data
|
||||
* @throws HTTPException\InternalServerErrorException
|
||||
* @param array $result
|
||||
* @param array $webfinger
|
||||
* @return array result
|
||||
*/
|
||||
private static function fixOStatus($webfinger, $lrdd, $type)
|
||||
private static function getSubscribeLink(array $result, array $webfinger)
|
||||
{
|
||||
if (empty($webfinger['links']) || empty($webfinger['subject'])) {
|
||||
return $webfinger;
|
||||
if (empty($webfinger['links'])) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$is_ostatus = false;
|
||||
$has_key = false;
|
||||
|
||||
foreach ($webfinger['links'] as $link) {
|
||||
if ($link['rel'] == ActivityNamespace::OSTATUSSUB) {
|
||||
$is_ostatus = true;
|
||||
}
|
||||
if ($link['rel'] == 'magic-public-key') {
|
||||
$has_key = true;
|
||||
if (!empty($link['template']) && ($link['rel'] === ActivityNamespace::OSTATUSSUB)) {
|
||||
$result['subscribe'] = $link['template'];
|
||||
}
|
||||
}
|
||||
|
||||
if (!$is_ostatus || $has_key) {
|
||||
return $webfinger;
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get webfinger data from a given URI
|
||||
*
|
||||
* @param string $uri
|
||||
* @return array Webfinger array
|
||||
*/
|
||||
private static function getWebfingerArray(string $uri)
|
||||
{
|
||||
$parts = parse_url($uri);
|
||||
|
||||
if (!empty($parts['scheme']) && !empty($parts['host'])) {
|
||||
$host = $parts['host'];
|
||||
if (!empty($parts['port'])) {
|
||||
$host .= ':'.$parts['port'];
|
||||
}
|
||||
|
||||
$baseurl = $parts['scheme'] . '://' . $host;
|
||||
|
||||
$nick = '';
|
||||
$addr = '';
|
||||
|
||||
$path_parts = explode("/", trim($parts['path'] ?? '', "/"));
|
||||
if (!empty($path_parts)) {
|
||||
$nick = ltrim(end($path_parts), '@');
|
||||
// When the last part of the URI is numeric then it is most likely an ID and not a nick name
|
||||
if (!is_numeric($nick)) {
|
||||
$addr = $nick."@".$host;
|
||||
} else {
|
||||
$nick = '';
|
||||
}
|
||||
}
|
||||
|
||||
$webfinger = self::getWebfinger($parts['scheme'] . '://' . $host . self::WEBFINGER, 'application/jrd+json', $uri, $addr);
|
||||
if (empty($webfinger)) {
|
||||
$lrdd = self::hostMeta($host);
|
||||
}
|
||||
|
||||
if (empty($webfinger) && empty($lrdd)) {
|
||||
while (empty($lrdd) && empty($webfinger) && (sizeof($path_parts) > 1)) {
|
||||
$host .= "/".array_shift($path_parts);
|
||||
$baseurl = $parts['scheme'] . '://' . $host;
|
||||
|
||||
if (!empty($nick)) {
|
||||
$addr = $nick."@".$host;
|
||||
}
|
||||
|
||||
$webfinger = self::getWebfinger($parts['scheme'] . '://' . $host . self::WEBFINGER, 'application/jrd+json', $uri, $addr);
|
||||
if (empty($webfinger)) {
|
||||
$lrdd = self::hostMeta($host);
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($lrdd) && empty($webfinger)) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
} elseif (strstr($uri, '@')) {
|
||||
// Remove "acct:" from the URI
|
||||
$uri = str_replace('acct:', '', $uri);
|
||||
|
||||
$host = substr($uri, strpos($uri, '@') + 1);
|
||||
$nick = substr($uri, 0, strpos($uri, '@'));
|
||||
$addr = $uri;
|
||||
|
||||
$webfinger = self::getWebfinger('https://' . $host . self::WEBFINGER, 'application/jrd+json', $uri, $addr);
|
||||
if (self::$istimeout) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (empty($webfinger)) {
|
||||
$webfinger = self::getWebfinger('http://' . $host . self::WEBFINGER, 'application/jrd+json', $uri, $addr);
|
||||
if (self::$istimeout) {
|
||||
return [];
|
||||
}
|
||||
} else {
|
||||
$baseurl = 'https://' . $host;
|
||||
}
|
||||
|
||||
if (empty($webfinger)) {
|
||||
$lrdd = self::hostMeta($host);
|
||||
if (self::$istimeout) {
|
||||
return [];
|
||||
}
|
||||
$baseurl = self::$baseurl;
|
||||
} else {
|
||||
$baseurl = 'http://' . $host;
|
||||
}
|
||||
} else {
|
||||
Logger::info('URI was not detectable', ['uri' => $uri]);
|
||||
return [];
|
||||
}
|
||||
|
||||
$url = Network::switchScheme($webfinger['subject']);
|
||||
$path = str_replace('{uri}', urlencode($url), $lrdd);
|
||||
$webfinger2 = self::webfinger($path, $type);
|
||||
if (empty($webfinger)) {
|
||||
foreach ($lrdd as $type => $template) {
|
||||
if ($webfinger) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Is the new webfinger detectable as OStatus?
|
||||
if (self::ostatus($webfinger2, true)) {
|
||||
$webfinger = $webfinger2;
|
||||
$webfinger = self::getWebfinger($template, $type, $uri, $addr);
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($webfinger)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if ($webfinger['detected'] == $addr) {
|
||||
$webfinger['nick'] = $nick;
|
||||
$webfinger['addr'] = $addr;
|
||||
}
|
||||
|
||||
$webfinger['baseurl'] = $baseurl;
|
||||
|
||||
return $webfinger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform network request for webfinger data
|
||||
*
|
||||
* @param string $template
|
||||
* @param string $type
|
||||
* @param string $uri
|
||||
* @param string $addr
|
||||
* @return array webfinger results
|
||||
*/
|
||||
private static function getWebfinger(string $template, string $type, string $uri, string $addr)
|
||||
{
|
||||
// First try the address because this is the primary purpose of webfinger
|
||||
if (!empty($addr)) {
|
||||
$detected = $addr;
|
||||
$path = str_replace('{uri}', urlencode("acct:" . $addr), $template);
|
||||
$webfinger = self::webfinger($path, $type);
|
||||
if (self::$istimeout) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// Then try the URI
|
||||
if (empty($webfinger) && $uri != $addr) {
|
||||
$detected = $uri;
|
||||
$path = str_replace('{uri}', urlencode($uri), $template);
|
||||
$webfinger = self::webfinger($path, $type);
|
||||
if (self::$istimeout) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($webfinger)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return ['webfinger' => $webfinger, 'detected' => $detected];
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch information (protocol endpoints and user information) about a given uri
|
||||
*
|
||||
* This function is only called by the "uri" function that adds caching and rearranging of data.
|
||||
*
|
||||
* @param string $uri Address that should be probed
|
||||
* @param string $network Test for this specific network
|
||||
* @param integer $uid User ID for the probe (only used for mails)
|
||||
* @param string $uri Address that should be probed
|
||||
* @param string $network Test for this specific network
|
||||
* @param integer $uid User ID for the probe (only used for mails)
|
||||
* @param array $ap_profile Previously probed AP profile
|
||||
*
|
||||
* @return array uri data
|
||||
* @throws HTTPException\InternalServerErrorException
|
||||
*/
|
||||
private static function detect($uri, $network, $uid)
|
||||
private static function detect(string $uri, string $network, int $uid, array $ap_profile)
|
||||
{
|
||||
$hookData = [
|
||||
'uri' => $uri,
|
||||
'network' => $network,
|
||||
'uid' => $uid,
|
||||
'result' => [],
|
||||
];
|
||||
|
||||
Hook::callAll('probe_detect', $hookData);
|
||||
|
||||
if ($hookData['result']) {
|
||||
if (!is_array($hookData['result'])) {
|
||||
return [];
|
||||
} else {
|
||||
return $hookData['result'];
|
||||
}
|
||||
}
|
||||
|
||||
$parts = parse_url($uri);
|
||||
|
||||
if (!empty($parts["scheme"]) && !empty($parts["host"])) {
|
||||
$host = $parts["host"];
|
||||
if (!empty($parts["port"])) {
|
||||
$host .= ':'.$parts["port"];
|
||||
}
|
||||
|
||||
if ($host == 'twitter.com') {
|
||||
if (!empty($parts['scheme']) && !empty($parts['host'])) {
|
||||
if (in_array($parts['host'], ['twitter.com', 'mobile.twitter.com'])) {
|
||||
return self::twitter($uri);
|
||||
}
|
||||
$lrdd = self::hostMeta($host);
|
||||
|
||||
if (is_bool($lrdd)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$path_parts = explode("/", trim($parts['path'] ?? '', "/"));
|
||||
|
||||
while (!$lrdd && (sizeof($path_parts) > 1)) {
|
||||
$host .= "/".array_shift($path_parts);
|
||||
$lrdd = self::hostMeta($host);
|
||||
}
|
||||
if (!$lrdd) {
|
||||
Logger::log('No XRD data was found for '.$uri, Logger::DEBUG);
|
||||
return self::feed($uri);
|
||||
}
|
||||
$nick = array_pop($path_parts);
|
||||
|
||||
// Mastodon uses a "@" as prefix for usernames in their url format
|
||||
$nick = ltrim($nick, '@');
|
||||
|
||||
$addr = $nick."@".$host;
|
||||
} elseif (strstr($uri, '@')) {
|
||||
// If the URI starts with "mailto:" then jump directly to the mail detection
|
||||
if (strpos($uri, 'mailto:') !== false) {
|
||||
|
|
@ -649,73 +705,36 @@ class Probe
|
|||
if ($network == Protocol::MAIL) {
|
||||
return self::mail($uri, $uid);
|
||||
}
|
||||
// Remove "acct:" from the URI
|
||||
$uri = str_replace('acct:', '', $uri);
|
||||
|
||||
$host = substr($uri, strpos($uri, '@') + 1);
|
||||
$nick = substr($uri, 0, strpos($uri, '@'));
|
||||
|
||||
if (strpos($uri, '@twitter.com')) {
|
||||
if (Strings::endsWith($uri, '@twitter.com')
|
||||
|| Strings::endsWith($uri, '@mobile.twitter.com')
|
||||
) {
|
||||
return self::twitter($uri);
|
||||
}
|
||||
$lrdd = self::hostMeta($host);
|
||||
} else {
|
||||
Logger::info('URI was not detectable', ['uri' => $uri]);
|
||||
return [];
|
||||
}
|
||||
|
||||
if (is_bool($lrdd)) {
|
||||
Logger::info('Probing start', ['uri' => $uri]);
|
||||
|
||||
$data = self::getWebfingerArray($uri);
|
||||
if (empty($data)) {
|
||||
if (!empty($parts['scheme'])) {
|
||||
return self::feed($uri);
|
||||
} elseif (!empty($uid)) {
|
||||
return self::mail($uri, $uid);
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!$lrdd) {
|
||||
Logger::log('No XRD data was found for '.$uri, Logger::DEBUG);
|
||||
return self::mail($uri, $uid);
|
||||
}
|
||||
$addr = $uri;
|
||||
} else {
|
||||
Logger::log("Uri ".$uri." was not detectable", Logger::DEBUG);
|
||||
return false;
|
||||
}
|
||||
|
||||
$webfinger = false;
|
||||
$webfinger = $data['webfinger'];
|
||||
$nick = $data['nick'] ?? '';
|
||||
$addr = $data['addr'] ?? '';
|
||||
$baseurl = $data['baseurl'] ?? '';
|
||||
|
||||
/// @todo Do we need the prefix "acct:" or "acct://"?
|
||||
|
||||
foreach ($lrdd as $type => $template) {
|
||||
if ($webfinger) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// At first try it with the given uri
|
||||
$path = str_replace('{uri}', urlencode($uri), $template);
|
||||
$webfinger = self::webfinger($path, $type);
|
||||
|
||||
// Fix possible problems with GNU Social probing to wrong scheme
|
||||
$webfinger = self::fixOStatus($webfinger, $template, $type);
|
||||
|
||||
// We cannot be sure that the detected address was correct, so we don't use the values
|
||||
if ($webfinger && ($uri != $addr)) {
|
||||
$nick = "";
|
||||
$addr = "";
|
||||
}
|
||||
|
||||
// Try webfinger with the address (user@domain.tld)
|
||||
if (!$webfinger) {
|
||||
$path = str_replace('{uri}', urlencode($addr), $template);
|
||||
$webfinger = self::webfinger($path, $type);
|
||||
}
|
||||
|
||||
// Mastodon needs to have it with "acct:"
|
||||
if (!$webfinger) {
|
||||
$path = str_replace('{uri}', urlencode("acct:".$addr), $template);
|
||||
$webfinger = self::webfinger($path, $type);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$webfinger) {
|
||||
return self::feed($uri);
|
||||
}
|
||||
|
||||
$result = false;
|
||||
|
||||
Logger::log("Probing ".$uri, Logger::DEBUG);
|
||||
$result = [];
|
||||
|
||||
if (in_array($network, ["", Protocol::DFRN])) {
|
||||
$result = self::dfrn($webfinger);
|
||||
|
|
@ -727,12 +746,12 @@ class Probe
|
|||
$result = self::ostatus($webfinger);
|
||||
}
|
||||
if (in_array($network, ['', Protocol::ZOT])) {
|
||||
$result = self::zot($webfinger, $result);
|
||||
$result = self::zot($webfinger, $result, $baseurl);
|
||||
}
|
||||
if ((!$result && ($network == "")) || ($network == Protocol::PUMPIO)) {
|
||||
$result = self::pumpio($webfinger, $addr);
|
||||
}
|
||||
if ((!$result && ($network == "")) || ($network == Protocol::FEED)) {
|
||||
if (empty($result['network']) && empty($ap_profile['network']) || ($network == Protocol::FEED)) {
|
||||
$result = self::feed($uri);
|
||||
} else {
|
||||
// We overwrite the detected nick with our try if the previois routines hadn't detected it.
|
||||
|
|
@ -746,22 +765,22 @@ class Probe
|
|||
}
|
||||
}
|
||||
|
||||
$result = self::getSubscribeLink($result, $webfinger);
|
||||
|
||||
if (empty($result["network"])) {
|
||||
$result["network"] = Protocol::PHANTOM;
|
||||
}
|
||||
|
||||
if (empty($result['baseurl']) && !empty($baseurl)) {
|
||||
$result['baseurl'] = $baseurl;
|
||||
}
|
||||
|
||||
if (empty($result["url"])) {
|
||||
$result["url"] = $uri;
|
||||
}
|
||||
|
||||
Logger::log($uri." is ".$result["network"], Logger::DEBUG);
|
||||
Logger::info('Probing done', ['uri' => $uri, 'network' => $result["network"]]);
|
||||
|
||||
if (empty($result["baseurl"]) && ($result["network"] != Protocol::PHANTOM)) {
|
||||
$pos = strpos($result["url"], $host);
|
||||
if ($pos) {
|
||||
$result["baseurl"] = substr($result["url"], 0, $pos).$host;
|
||||
}
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
|
@ -774,7 +793,7 @@ class Probe
|
|||
* @return array Zot data
|
||||
* @throws HTTPException\InternalServerErrorException
|
||||
*/
|
||||
private static function zot($webfinger, $data)
|
||||
private static function zot($webfinger, $data, $baseurl)
|
||||
{
|
||||
if (!empty($webfinger["aliases"]) && is_array($webfinger["aliases"])) {
|
||||
foreach ($webfinger["aliases"] as $alias) {
|
||||
|
|
@ -795,12 +814,12 @@ class Probe
|
|||
}
|
||||
}
|
||||
|
||||
if (empty($zot_url) && !empty($data['addr']) && !empty(self::$baseurl)) {
|
||||
$condition = ['nurl' => Strings::normaliseLink(self::$baseurl), 'platform' => ['hubzilla']];
|
||||
if (empty($zot_url) && !empty($data['addr']) && !empty($baseurl)) {
|
||||
$condition = ['nurl' => Strings::normaliseLink($baseurl), 'platform' => ['hubzilla']];
|
||||
if (!DBA::exists('gserver', $condition)) {
|
||||
return $data;
|
||||
}
|
||||
$zot_url = self::$baseurl . '/.well-known/zot-info?address=' . $data['addr'];
|
||||
$zot_url = $baseurl . '/.well-known/zot-info?address=' . $data['addr'];
|
||||
}
|
||||
|
||||
if (empty($zot_url)) {
|
||||
|
|
@ -873,7 +892,7 @@ class Probe
|
|||
}
|
||||
if (!empty($json['public_forum'])) {
|
||||
$data['community'] = $json['public_forum'];
|
||||
$data['account-type'] = Contact::PAGE_COMMUNITY;
|
||||
$data['account-type'] = User::PAGE_FLAGS_COMMUNITY;
|
||||
}
|
||||
|
||||
if (!empty($json['profile'])) {
|
||||
|
|
@ -915,37 +934,37 @@ class Probe
|
|||
* @return array webfinger data
|
||||
* @throws HTTPException\InternalServerErrorException
|
||||
*/
|
||||
private static function webfinger($url, $type)
|
||||
public static function webfinger($url, $type)
|
||||
{
|
||||
$xrd_timeout = DI::config()->get('system', 'xrd_timeout', 20);
|
||||
|
||||
$curlResult = Network::curl($url, false, ['timeout' => $xrd_timeout, 'accept_content' => $type]);
|
||||
if ($curlResult->isTimeout()) {
|
||||
self::$istimeout = true;
|
||||
return false;
|
||||
return [];
|
||||
}
|
||||
$data = $curlResult->getBody();
|
||||
|
||||
$webfinger = json_decode($data, true);
|
||||
if (is_array($webfinger)) {
|
||||
if (!empty($webfinger)) {
|
||||
if (!isset($webfinger["links"])) {
|
||||
Logger::log("No json webfinger links for ".$url, Logger::DEBUG);
|
||||
return false;
|
||||
Logger::info('No json webfinger links', ['url' => $url]);
|
||||
return [];
|
||||
}
|
||||
return $webfinger;
|
||||
}
|
||||
|
||||
// If it is not JSON, maybe it is XML
|
||||
$xrd = XML::parseString($data, false);
|
||||
$xrd = XML::parseString($data, true);
|
||||
if (!is_object($xrd)) {
|
||||
Logger::log("No webfinger data retrievable for ".$url, Logger::DEBUG);
|
||||
return false;
|
||||
Logger::info('No webfinger data retrievable', ['url' => $url]);
|
||||
return [];
|
||||
}
|
||||
|
||||
$xrd_arr = XML::elementToArray($xrd);
|
||||
if (!isset($xrd_arr["xrd"]["link"])) {
|
||||
Logger::log("No XML webfinger links for ".$url, Logger::DEBUG);
|
||||
return false;
|
||||
Logger::info('No XML webfinger links', ['url' => $url]);
|
||||
return [];
|
||||
}
|
||||
|
||||
$webfinger = [];
|
||||
|
|
@ -991,18 +1010,18 @@ class Probe
|
|||
$curlResult = Network::curl($noscrape_url);
|
||||
if ($curlResult->isTimeout()) {
|
||||
self::$istimeout = true;
|
||||
return false;
|
||||
return [];
|
||||
}
|
||||
$content = $curlResult->getBody();
|
||||
if (!$content) {
|
||||
Logger::log("Empty body for ".$noscrape_url, Logger::DEBUG);
|
||||
return false;
|
||||
Logger::info('Empty body', ['url' => $noscrape_url]);
|
||||
return [];
|
||||
}
|
||||
|
||||
$json = json_decode($content, true);
|
||||
if (!is_array($json)) {
|
||||
Logger::log("No json data for ".$noscrape_url, Logger::DEBUG);
|
||||
return false;
|
||||
Logger::info('No json data', ['url' => $noscrape_url]);
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!empty($json["fn"])) {
|
||||
|
|
@ -1115,7 +1134,7 @@ class Probe
|
|||
{
|
||||
$data = [];
|
||||
|
||||
Logger::log("Check profile ".$profile_link, Logger::DEBUG);
|
||||
Logger::info('Check profile', ['link' => $profile_link]);
|
||||
|
||||
// Fetch data via noscrape - this is faster
|
||||
$noscrape_url = str_replace(["/hcard/", "/profile/"], "/noscrape/", $profile_link);
|
||||
|
|
@ -1149,7 +1168,7 @@ class Probe
|
|||
$prof_data["fn"] = $data['name'] ?? null;
|
||||
$prof_data["key"] = $data['pubkey'] ?? null;
|
||||
|
||||
Logger::log("Result for profile ".$profile_link.": ".print_r($prof_data, true), Logger::DEBUG);
|
||||
Logger::debug('Result', ['link' => $profile_link, 'data' => $prof_data]);
|
||||
|
||||
return $prof_data;
|
||||
}
|
||||
|
|
@ -1212,7 +1231,7 @@ class Probe
|
|||
}
|
||||
|
||||
if (!isset($data["network"]) || ($hcard_url == "")) {
|
||||
return false;
|
||||
return [];
|
||||
}
|
||||
|
||||
// Fetch data via noscrape - this is faster
|
||||
|
|
@ -1249,23 +1268,23 @@ class Probe
|
|||
$curlResult = Network::curl($hcard_url);
|
||||
if ($curlResult->isTimeout()) {
|
||||
self::$istimeout = true;
|
||||
return false;
|
||||
return [];
|
||||
}
|
||||
$content = $curlResult->getBody();
|
||||
if (!$content) {
|
||||
return false;
|
||||
return [];
|
||||
}
|
||||
|
||||
$doc = new DOMDocument();
|
||||
if (!@$doc->loadHTML($content)) {
|
||||
return false;
|
||||
return [];
|
||||
}
|
||||
|
||||
$xpath = new DomXPath($doc);
|
||||
|
||||
$vcards = $xpath->query("//div[contains(concat(' ', @class, ' '), ' vcard ')]");
|
||||
if (!is_object($vcards)) {
|
||||
return false;
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!isset($data["baseurl"])) {
|
||||
|
|
@ -1403,7 +1422,7 @@ class Probe
|
|||
}
|
||||
|
||||
if (empty($data["url"]) || empty($hcard_url)) {
|
||||
return false;
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!empty($webfinger["aliases"]) && is_array($webfinger["aliases"])) {
|
||||
|
|
@ -1424,7 +1443,7 @@ class Probe
|
|||
$data = self::pollHcard($hcard_url, $data);
|
||||
|
||||
if (!$data) {
|
||||
return false;
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!empty($data["url"])
|
||||
|
|
@ -1444,7 +1463,7 @@ class Probe
|
|||
$data["notify"] = $data["baseurl"] . "/receive/users/" . $data["guid"];
|
||||
$data["batch"] = $data["baseurl"] . "/receive/public";
|
||||
} else {
|
||||
return false;
|
||||
return [];
|
||||
}
|
||||
|
||||
return $data;
|
||||
|
|
@ -1477,7 +1496,7 @@ class Probe
|
|||
$data["addr"] = str_replace('acct:', '', $webfinger["subject"]);
|
||||
}
|
||||
|
||||
if (is_array($webfinger["links"])) {
|
||||
if (!empty($webfinger["links"])) {
|
||||
// The array is reversed to take into account the order of preference for same-rel links
|
||||
// See: https://tools.ietf.org/html/rfc7033#section-4.4.4
|
||||
foreach (array_reverse($webfinger["links"]) as $link) {
|
||||
|
|
@ -1485,7 +1504,7 @@ class Probe
|
|||
&& (($link["type"] ?? "") == "text/html")
|
||||
&& ($link["href"] != "")
|
||||
) {
|
||||
$data["url"] = $link["href"];
|
||||
$data["url"] = $data["alias"] = $link["href"];
|
||||
} elseif (($link["rel"] == "salmon") && !empty($link["href"])) {
|
||||
$data["notify"] = $link["href"];
|
||||
} elseif (($link["rel"] == ActivityNamespace::FEED) && !empty($link["href"])) {
|
||||
|
|
@ -1503,7 +1522,7 @@ class Probe
|
|||
$curlResult = Network::curl($pubkey);
|
||||
if ($curlResult->isTimeout()) {
|
||||
self::$istimeout = true;
|
||||
return false;
|
||||
return $short ? false : [];
|
||||
}
|
||||
$pubkey = $curlResult->getBody();
|
||||
}
|
||||
|
|
@ -1525,7 +1544,7 @@ class Probe
|
|||
) {
|
||||
$data["network"] = Protocol::OSTATUS;
|
||||
} else {
|
||||
return false;
|
||||
return $short ? false : [];
|
||||
}
|
||||
|
||||
if ($short) {
|
||||
|
|
@ -1536,12 +1555,12 @@ class Probe
|
|||
$curlResult = Network::curl($data["poll"]);
|
||||
if ($curlResult->isTimeout()) {
|
||||
self::$istimeout = true;
|
||||
return false;
|
||||
return [];
|
||||
}
|
||||
$feed = $curlResult->getBody();
|
||||
$feed_data = Feed::import($feed);
|
||||
if (!$feed_data) {
|
||||
return false;
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!empty($feed_data["header"]["author-name"])) {
|
||||
|
|
@ -1568,8 +1587,7 @@ class Probe
|
|||
$data["url"] = $feed_data["header"]["author-link"];
|
||||
}
|
||||
|
||||
if (($data['poll'] == $data['url']) && ($data["alias"] != '')) {
|
||||
$data['url'] = $data["alias"];
|
||||
if ($data["url"] == $data["alias"]) {
|
||||
$data["alias"] = '';
|
||||
}
|
||||
|
||||
|
|
@ -1588,12 +1606,12 @@ class Probe
|
|||
{
|
||||
$curlResult = Network::curl($profile_link);
|
||||
if (!$curlResult->isSuccess()) {
|
||||
return false;
|
||||
return [];
|
||||
}
|
||||
|
||||
$doc = new DOMDocument();
|
||||
if (!@$doc->loadHTML($curlResult->getBody())) {
|
||||
return false;
|
||||
return [];
|
||||
}
|
||||
|
||||
$xpath = new DomXPath($doc);
|
||||
|
|
@ -1674,13 +1692,13 @@ class Probe
|
|||
|
||||
$data["network"] = Protocol::PUMPIO;
|
||||
} else {
|
||||
return false;
|
||||
return [];
|
||||
}
|
||||
|
||||
$profile_data = self::pumpioProfileData($data["url"]);
|
||||
|
||||
if (!$profile_data) {
|
||||
return false;
|
||||
return [];
|
||||
}
|
||||
|
||||
$data = array_merge($data, $profile_data);
|
||||
|
|
@ -1704,9 +1722,9 @@ class Probe
|
|||
*/
|
||||
private static function twitter($uri)
|
||||
{
|
||||
if (preg_match('=(.*)@twitter.com=i', $uri, $matches)) {
|
||||
if (preg_match('=([^@]+)@(?:mobile\.)?twitter\.com$=i', $uri, $matches)) {
|
||||
$nick = $matches[1];
|
||||
} elseif (preg_match('=https?://twitter.com/(.*)=i', $uri, $matches)) {
|
||||
} elseif (preg_match('=^https?://(?:mobile\.)?twitter\.com/(.+)=i', $uri, $matches)) {
|
||||
$nick = $matches[1];
|
||||
} else {
|
||||
return [];
|
||||
|
|
@ -1719,87 +1737,91 @@ class Probe
|
|||
$data['network'] = Protocol::TWITTER;
|
||||
$data['baseurl'] = 'https://twitter.com';
|
||||
|
||||
$curlResult = Network::curl($data['url'], false);
|
||||
if (!$curlResult->isSuccess()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$body = $curlResult->getBody();
|
||||
$doc = new DOMDocument();
|
||||
@$doc->loadHTML($body);
|
||||
$xpath = new DOMXPath($doc);
|
||||
|
||||
$list = $xpath->query('//img[@class]');
|
||||
foreach ($list as $node) {
|
||||
$img_attr = [];
|
||||
if ($node->attributes->length) {
|
||||
foreach ($node->attributes as $attribute) {
|
||||
$img_attr[$attribute->name] = $attribute->value;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($img_attr['class'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (strpos($img_attr['class'], 'ProfileAvatar-image') !== false) {
|
||||
if (!empty($img_attr['src'])) {
|
||||
$data['photo'] = $img_attr['src'];
|
||||
}
|
||||
if (!empty($img_attr['alt'])) {
|
||||
$data['name'] = $img_attr['alt'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check page for feed link
|
||||
* Checks HTML page for RSS feed link
|
||||
*
|
||||
* @param string $url Page link
|
||||
*
|
||||
* @return string feed link
|
||||
* @param string $url Page link
|
||||
* @param string $body Page body string
|
||||
* @return string|false Feed link or false if body was invalid HTML document
|
||||
*/
|
||||
private static function getFeedLink($url)
|
||||
public static function getFeedLink(string $url, string $body)
|
||||
{
|
||||
$curlResult = Network::curl($url);
|
||||
if (!$curlResult->isSuccess()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$doc = new DOMDocument();
|
||||
if (!@$doc->loadHTML($curlResult->getBody())) {
|
||||
if (!@$doc->loadHTML($body)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$xpath = new DomXPath($doc);
|
||||
$xpath = new DOMXPath($doc);
|
||||
|
||||
//$feeds = $xpath->query("/html/head/link[@type='application/rss+xml']");
|
||||
$feeds = $xpath->query("/html/head/link[@type='application/rss+xml' and @rel='alternate']");
|
||||
if (!is_object($feeds)) {
|
||||
return false;
|
||||
$feedUrl = $xpath->evaluate('string(/html/head/link[@type="application/rss+xml" and @rel="alternate"]/@href)');
|
||||
|
||||
$feedUrl = $feedUrl ? self::ensureAbsoluteLinkFromHTMLDoc($feedUrl, $url, $xpath) : '';
|
||||
|
||||
return $feedUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an absolute URL in the context of a HTML document retrieved from the provided URL.
|
||||
*
|
||||
* Loosely based on RFC 1808
|
||||
*
|
||||
* @see https://tools.ietf.org/html/rfc1808
|
||||
*
|
||||
* @param string $href The potential relative href found in the HTML document
|
||||
* @param string $base The HTML document URL
|
||||
* @param DOMXPath $xpath The HTML document XPath
|
||||
* @return string
|
||||
*/
|
||||
private static function ensureAbsoluteLinkFromHTMLDoc(string $href, string $base, DOMXPath $xpath)
|
||||
{
|
||||
if (filter_var($href, FILTER_VALIDATE_URL)) {
|
||||
return $href;
|
||||
}
|
||||
|
||||
if ($feeds->length == 0) {
|
||||
return false;
|
||||
}
|
||||
$base = $xpath->evaluate('string(/html/head/base/@href)') ?: $base;
|
||||
|
||||
$feed_url = "";
|
||||
$baseParts = parse_url($base);
|
||||
|
||||
foreach ($feeds as $feed) {
|
||||
$attr = [];
|
||||
foreach ($feed->attributes as $attribute) {
|
||||
$attr[$attribute->name] = trim($attribute->value);
|
||||
// Naked domain case (scheme://basehost)
|
||||
$path = $baseParts['path'] ?? '/';
|
||||
|
||||
// Remove the filename part of the path if it exists (/base/path/file)
|
||||
$path = implode('/', array_slice(explode('/', $path), 0, -1));
|
||||
|
||||
$hrefParts = parse_url($href);
|
||||
|
||||
// Root path case (/path) including relative scheme case (//host/path)
|
||||
if ($hrefParts['path'] && $hrefParts['path'][0] == '/') {
|
||||
$path = $hrefParts['path'];
|
||||
} else {
|
||||
$path = $path . '/' . $hrefParts['path'];
|
||||
|
||||
// Resolve arbitrary relative path
|
||||
// Lifted from https://www.php.net/manual/en/function.realpath.php#84012
|
||||
$parts = array_filter(explode('/', $path), 'strlen');
|
||||
$absolutes = array();
|
||||
foreach ($parts as $part) {
|
||||
if ('.' == $part) continue;
|
||||
if ('..' == $part) {
|
||||
array_pop($absolutes);
|
||||
} else {
|
||||
$absolutes[] = $part;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($feed_url) && !empty($attr['href'])) {
|
||||
$feed_url = $attr["href"];
|
||||
}
|
||||
$path = '/' . implode('/', $absolutes);
|
||||
}
|
||||
|
||||
return $feed_url;
|
||||
// Relative scheme case (//host/path)
|
||||
$baseParts['host'] = $hrefParts['host'] ?? $baseParts['host'];
|
||||
$baseParts['path'] = $path;
|
||||
unset($baseParts['query']);
|
||||
unset($baseParts['fragment']);
|
||||
|
||||
return Network::unparseURL($baseParts);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1816,20 +1838,20 @@ class Probe
|
|||
$curlResult = Network::curl($url);
|
||||
if ($curlResult->isTimeout()) {
|
||||
self::$istimeout = true;
|
||||
return false;
|
||||
return [];
|
||||
}
|
||||
$feed = $curlResult->getBody();
|
||||
$feed_data = Feed::import($feed);
|
||||
|
||||
if (!$feed_data) {
|
||||
if (!$probe) {
|
||||
return false;
|
||||
return [];
|
||||
}
|
||||
|
||||
$feed_url = self::getFeedLink($url);
|
||||
$feed_url = self::getFeedLink($url, $feed);
|
||||
|
||||
if (!$feed_url) {
|
||||
return false;
|
||||
return [];
|
||||
}
|
||||
|
||||
return self::feed($feed_url, false);
|
||||
|
|
@ -1854,12 +1876,6 @@ class Probe
|
|||
$data["url"] = $url;
|
||||
$data["poll"] = $url;
|
||||
|
||||
if (!empty($feed_data["header"]["author-link"])) {
|
||||
$data["baseurl"] = $feed_data["header"]["author-link"];
|
||||
} else {
|
||||
$data["baseurl"] = $data["url"];
|
||||
}
|
||||
|
||||
$data["network"] = Protocol::FEED;
|
||||
|
||||
return $data;
|
||||
|
|
@ -1877,11 +1893,11 @@ class Probe
|
|||
private static function mail($uri, $uid)
|
||||
{
|
||||
if (!Network::isEmailDomainValid($uri)) {
|
||||
return false;
|
||||
return [];
|
||||
}
|
||||
|
||||
if ($uid == 0) {
|
||||
return false;
|
||||
return [];
|
||||
}
|
||||
|
||||
$user = DBA::selectFirst('user', ['prvkey'], ['uid' => $uid]);
|
||||
|
|
@ -1891,7 +1907,7 @@ class Probe
|
|||
$mailacct = DBA::selectFirst('mailacct', $fields, $condition);
|
||||
|
||||
if (!DBA::isResult($user) || !DBA::isResult($mailacct)) {
|
||||
return false;
|
||||
return [];
|
||||
}
|
||||
|
||||
$mailbox = Email::constructMailboxName($mailacct);
|
||||
|
|
@ -1899,14 +1915,14 @@ class Probe
|
|||
openssl_private_decrypt(hex2bin($mailacct['pass']), $password, $user['prvkey']);
|
||||
$mbox = Email::connect($mailbox, $mailacct['user'], $password);
|
||||
if (!$mbox) {
|
||||
return false;
|
||||
return [];
|
||||
}
|
||||
|
||||
$msgs = Email::poll($mbox, $uri);
|
||||
Logger::log('searching '.$uri.', '.count($msgs).' messages found.', Logger::DEBUG);
|
||||
Logger::info('Messages found', ['uri' => $uri, 'count' => count($msgs)]);
|
||||
|
||||
if (!count($msgs)) {
|
||||
return false;
|
||||
return [];
|
||||
}
|
||||
|
||||
$phost = substr($uri, strpos($uri, '@') + 1);
|
||||
|
|
@ -1986,7 +2002,7 @@ class Probe
|
|||
|
||||
$fixed = $scheme.$host.$port.$path.$query.$fragment;
|
||||
|
||||
Logger::log('Base: '.$base.' - Avatar: '.$avatar.' - Fixed: '.$fixed, Logger::DATA);
|
||||
Logger::debug('Avatar fixed', ['base' => $base, 'avatar' => $avatar, 'fixed' => $fixed]);
|
||||
|
||||
return $fixed;
|
||||
}
|
||||
|
|
|
|||
153
src/Object/Api/Twitter/User.php
Normal file
153
src/Object/Api/Twitter/User.php
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
<?php
|
||||
/**
|
||||
* @copyright Copyright (C) 2020, Friendica
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Friendica\Object\Api\Twitter;
|
||||
|
||||
use Friendica\BaseEntity;
|
||||
use Friendica\Content\ContactSelector;
|
||||
use Friendica\Content\Text\BBCode;
|
||||
|
||||
/**
|
||||
* @see https://developer.twitter.com/en/docs/tweets/data-dictionary/overview/user-object
|
||||
*/
|
||||
class User extends BaseEntity
|
||||
{
|
||||
/** @var int */
|
||||
protected $id;
|
||||
/** @var string */
|
||||
protected $id_str;
|
||||
/** @var string */
|
||||
protected $name;
|
||||
/** @var string */
|
||||
protected $screen_name;
|
||||
/** @var string|null */
|
||||
protected $location;
|
||||
/** @var array */
|
||||
protected $derived;
|
||||
/** @var string|null */
|
||||
protected $url;
|
||||
/** @var array */
|
||||
protected $entities;
|
||||
/** @var string|null */
|
||||
protected $description;
|
||||
/** @var bool */
|
||||
protected $protected;
|
||||
/** @var bool */
|
||||
protected $verified;
|
||||
/** @var int */
|
||||
protected $followers_count;
|
||||
/** @var int */
|
||||
protected $friends_count;
|
||||
/** @var int */
|
||||
protected $listed_count;
|
||||
/** @var int */
|
||||
protected $favourites_count;
|
||||
/** @var int */
|
||||
protected $statuses_count;
|
||||
/** @var string */
|
||||
protected $created_at;
|
||||
/** @var string */
|
||||
protected $profile_banner_url;
|
||||
/** @var string */
|
||||
protected $profile_image_url_https;
|
||||
/** @var bool */
|
||||
protected $default_profile;
|
||||
/** @var bool */
|
||||
protected $default_profile_image;
|
||||
/** @var Status */
|
||||
protected $status;
|
||||
/** @var array */
|
||||
protected $withheld_in_countries;
|
||||
/** @var string */
|
||||
protected $withheld_scope;
|
||||
|
||||
/**
|
||||
* @param array $publicContact Full contact table record with uid = 0
|
||||
* @param array $apcontact Optional full apcontact table record
|
||||
* @param array $userContact Optional full contact table record with uid != 0
|
||||
* @param bool $skip_status Whether to remove the last status property, currently unused
|
||||
* @param bool $include_user_entities Whether to add the entities property
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public function __construct(array $publicContact, array $apcontact = [], array $userContact = [], $skip_status = false, $include_user_entities = true)
|
||||
{
|
||||
$this->id = $publicContact['id'];
|
||||
$this->id_str = (string) $publicContact['id'];
|
||||
$this->name = $publicContact['name'];
|
||||
$this->screen_name = $publicContact['nick'] ?: $publicContact['name'];
|
||||
$this->location = $publicContact['location'] ?:
|
||||
ContactSelector::networkToName($publicContact['network'], $publicContact['url'], $publicContact['protocol']);
|
||||
$this->derived = [];
|
||||
$this->url = $publicContact['url'];
|
||||
// No entities needed since we don't perform any shortening in the URL or description
|
||||
$this->entities = [
|
||||
'url' => ['urls' => []],
|
||||
'description' => ['urls' => []],
|
||||
];
|
||||
if (!$include_user_entities) {
|
||||
unset($this->entities);
|
||||
}
|
||||
$this->description = BBCode::toPlaintext($publicContact['about']);
|
||||
$this->profile_image_url_https = $userContact['avatar'] ?? $publicContact['avatar'];
|
||||
$this->protected = false;
|
||||
$this->followers_count = $apcontact['followers_count'] ?? 0;
|
||||
$this->friends_count = $apcontact['following_count'] ?? 0;
|
||||
$this->listed_count = 0;
|
||||
$this->created_at = api_date($publicContact['created']);
|
||||
$this->favourites_count = 0;
|
||||
$this->verified = false;
|
||||
$this->statuses_count = $apcontact['statuses_count'] ?? 0;
|
||||
$this->profile_banner_url = '';
|
||||
$this->default_profile = false;
|
||||
$this->default_profile_image = false;
|
||||
|
||||
// @TODO Replace skip_status parameter with an optional Status parameter
|
||||
unset($this->status);
|
||||
|
||||
// Unused optional fields
|
||||
unset($this->withheld_in_countries);
|
||||
unset($this->withheld_scope);
|
||||
|
||||
// Deprecated
|
||||
$this->profile_image_url = $userContact['avatar'] ?? $publicContact['avatar'];
|
||||
$this->profile_image_url_profile_size = $publicContact['thumb'];
|
||||
$this->profile_image_url_large = $publicContact['photo'];
|
||||
$this->utc_offset = 0;
|
||||
$this->time_zone = 'UTC';
|
||||
$this->geo_enabled = false;
|
||||
$this->lang = null;
|
||||
$this->contributors_enabled = false;
|
||||
$this->is_translator = false;
|
||||
$this->is_translation_enabled = false;
|
||||
$this->following = false;
|
||||
$this->follow_request_sent = false;
|
||||
$this->statusnet_blocking = false;
|
||||
$this->notifications = false;
|
||||
|
||||
// Friendica-specific
|
||||
$this->uid = $userContact['uid'] ?? 0;
|
||||
$this->cid = $userContact['id'] ?? 0;
|
||||
$this->pid = $publicContact['id'];
|
||||
$this->self = $userContact['self'] ?? false;
|
||||
$this->network = $publicContact['network'];
|
||||
$this->statusnet_profile_url = $publicContact['url'];
|
||||
}
|
||||
}
|
||||
|
|
@ -456,7 +456,6 @@ class Image
|
|||
break;
|
||||
}
|
||||
|
||||
// Logger::log('exif: ' . print_r($exif,true));
|
||||
return $exif;
|
||||
}
|
||||
|
||||
|
|
@ -708,22 +707,6 @@ class Image
|
|||
return Images::getFormatsMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Guess image mimetype from filename or from Content-Type header
|
||||
*
|
||||
* @param string $filename Image filename
|
||||
* @param boolean $fromcurl Check Content-Type header from curl request
|
||||
* @param string $header passed headers to take into account
|
||||
*
|
||||
* @return string|null
|
||||
* @throws Exception
|
||||
* @deprecated in version 2019.12 please use Util\Images::guessType() instead.
|
||||
*/
|
||||
public static function guessType($filename, $fromcurl = false, $header = '')
|
||||
{
|
||||
return Images::guessType($filename, $fromcurl, $header);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $url url
|
||||
* @return array
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ use Friendica\Database\DBA;
|
|||
use Friendica\DI;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Model\Item;
|
||||
use Friendica\Model\Term;
|
||||
use Friendica\Model\Tag;
|
||||
use Friendica\Model\User;
|
||||
use Friendica\Protocol\Activity;
|
||||
use Friendica\Util\Crypto;
|
||||
|
|
@ -214,7 +214,7 @@ class Post
|
|||
$pinned = DI::l10n()->t('pinned item');
|
||||
}
|
||||
|
||||
if ($origin && ($item['id'] != $item['parent']) && ($item['network'] == Protocol::ACTIVITYPUB)) {
|
||||
if ($origin && ($item['gravity'] != GRAVITY_PARENT) && ($item['network'] == Protocol::ACTIVITYPUB)) {
|
||||
// ActivityPub doesn't allow removal of remote comments
|
||||
$delete = DI::l10n()->t('Delete locally');
|
||||
} else {
|
||||
|
|
@ -380,8 +380,11 @@ class Post
|
|||
}
|
||||
|
||||
// Disable features that aren't available in several networks
|
||||
if ($buttons["dislike"] && !in_array($item["network"], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA])) {
|
||||
$buttons["dislike"] = false;
|
||||
if (!in_array($item["network"], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA])) {
|
||||
if ($buttons["dislike"]) {
|
||||
$buttons["dislike"] = false;
|
||||
}
|
||||
|
||||
$isevent = false;
|
||||
$tagger = '';
|
||||
}
|
||||
|
|
@ -390,7 +393,7 @@ class Post
|
|||
$buttons["like"] = false;
|
||||
}
|
||||
|
||||
$tags = Term::populateTagsFromItem($item);
|
||||
$tags = Tag::populateFromItem($item);
|
||||
|
||||
$ago = Temporal::getRelativeDate($item['created']);
|
||||
$ago_received = Temporal::getRelativeDate($item['received']);
|
||||
|
|
@ -860,7 +863,7 @@ class Post
|
|||
return '';
|
||||
}
|
||||
|
||||
$item = Item::selectFirst(['author-addr'], ['id' => $this->getId()]);
|
||||
$item = Item::selectFirst(['author-addr', 'uri-id'], ['id' => $this->getId()]);
|
||||
if (!DBA::isResult($item) || empty($item['author-addr'])) {
|
||||
// Should not happen
|
||||
return '';
|
||||
|
|
@ -872,7 +875,7 @@ class Post
|
|||
$text = '';
|
||||
}
|
||||
|
||||
$terms = Term::tagArrayFromItemId($this->getId(), [Term::MENTION, Term::IMPLICIT_MENTION]);
|
||||
$terms = Tag::getByURIId($item['uri-id'], [Tag::MENTION, Tag::IMPLICIT_MENTION, Tag::EXCLUSIVE_MENTION]);
|
||||
foreach ($terms as $term) {
|
||||
$profile = Contact::getDetailsByURL($term['url']);
|
||||
if (!empty($profile['addr']) && ((($profile['contact-type'] ?? '') ?: Contact::TYPE_UNKNOWN) != Contact::TYPE_COMMUNITY) &&
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ class ActivityPub
|
|||
{
|
||||
$apcontact = APContact::getByURL($url, $update);
|
||||
if (empty($apcontact)) {
|
||||
return false;
|
||||
return [];
|
||||
}
|
||||
|
||||
$profile = ['network' => Protocol::ACTIVITYPUB];
|
||||
|
|
@ -170,7 +170,9 @@ class ActivityPub
|
|||
$profile['notify'] = $apcontact['inbox'];
|
||||
$profile['poll'] = $apcontact['outbox'];
|
||||
$profile['pubkey'] = $apcontact['pubkey'];
|
||||
$profile['subscribe'] = $apcontact['subscribe'];
|
||||
$profile['baseurl'] = $apcontact['baseurl'];
|
||||
$profile['gsid'] = $apcontact['gsid'];
|
||||
|
||||
// Remove all "null" fields
|
||||
foreach ($profile as $field => $content) {
|
||||
|
|
|
|||
|
|
@ -32,8 +32,9 @@ use Friendica\Model\Contact;
|
|||
use Friendica\Model\Conversation;
|
||||
use Friendica\Model\Event;
|
||||
use Friendica\Model\Item;
|
||||
use Friendica\Model\ItemURI;
|
||||
use Friendica\Model\Mail;
|
||||
use Friendica\Model\Term;
|
||||
use Friendica\Model\Tag;
|
||||
use Friendica\Model\User;
|
||||
use Friendica\Protocol\Activity;
|
||||
use Friendica\Protocol\ActivityPub;
|
||||
|
|
@ -78,35 +79,6 @@ class Processor
|
|||
return $body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a string with tags for a given tag array
|
||||
*
|
||||
* @param array $tags
|
||||
* @param boolean $sensitive
|
||||
* @return string with tags
|
||||
*/
|
||||
private static function constructTagString(array $tags = null, $sensitive = false)
|
||||
{
|
||||
if (empty($tags)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$tag_text = '';
|
||||
foreach ($tags as $tag) {
|
||||
if (in_array($tag['type'] ?? '', ['Mention', 'Hashtag'])) {
|
||||
if (!empty($tag_text)) {
|
||||
$tag_text .= ',';
|
||||
}
|
||||
|
||||
$tag_text .= substr($tag['name'], 0, 1) . '[url=' . $tag['href'] . ']' . substr($tag['name'], 1) . '[/url]';
|
||||
}
|
||||
}
|
||||
|
||||
/// @todo add nsfw for $sensitive
|
||||
|
||||
return $tag_text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add attachment data to the item array
|
||||
*
|
||||
|
|
@ -122,39 +94,54 @@ class Processor
|
|||
}
|
||||
|
||||
foreach ($activity['attachments'] as $attach) {
|
||||
$filetype = strtolower(substr($attach['mediaType'], 0, strpos($attach['mediaType'], '/')));
|
||||
if ($filetype == 'image') {
|
||||
if (!empty($activity['source']) && strpos($activity['source'], $attach['url'])) {
|
||||
continue;
|
||||
}
|
||||
switch ($attach['type']) {
|
||||
case 'link':
|
||||
// Only one [attachment] tag is allowed
|
||||
$existingAttachmentPos = strpos($item['body'], '[attachment');
|
||||
if ($existingAttachmentPos !== false) {
|
||||
$linkTitle = $attach['title'] ?: $attach['url'];
|
||||
// Additional link attachments are prepended before the existing [attachment] tag
|
||||
$item['body'] = substr_replace($item['body'], "\n[bookmark=" . $attach['url'] . ']' . $linkTitle . "[/bookmark]\n", $existingAttachmentPos, 0);
|
||||
} else {
|
||||
// Strip the link preview URL from the end of the body if any
|
||||
$quotedUrl = preg_quote($attach['url'], '#');
|
||||
$item['body'] = preg_replace("#\s*(?:\[bookmark={$quotedUrl}].+?\[/bookmark]|\[url={$quotedUrl}].+?\[/url]|\[url]{$quotedUrl}\[/url]|{$quotedUrl})\s*$#", '', $item['body']);
|
||||
$item['body'] .= "\n[attachment type='link' url='" . $attach['url'] . "' title='" . htmlspecialchars($attach['title'] ?? '', ENT_QUOTES) . "' image='" . ($attach['image'] ?? '') . "']" . ($attach['desc'] ?? '') . '[/attachment]';
|
||||
}
|
||||
break;
|
||||
default:
|
||||
$filetype = strtolower(substr($attach['mediaType'], 0, strpos($attach['mediaType'], '/')));
|
||||
if ($filetype == 'image') {
|
||||
if (!empty($activity['source']) && strpos($activity['source'], $attach['url'])) {
|
||||
continue 2;
|
||||
}
|
||||
|
||||
if (empty($attach['name'])) {
|
||||
$item['body'] .= "\n[img]" . $attach['url'] . '[/img]';
|
||||
} else {
|
||||
$item['body'] .= "\n[img=" . $attach['url'] . ']' . $attach['name'] . '[/img]';
|
||||
}
|
||||
} elseif ($filetype == 'audio') {
|
||||
if (!empty($activity['source']) && strpos($activity['source'], $attach['url'])) {
|
||||
continue;
|
||||
}
|
||||
if (empty($attach['name'])) {
|
||||
$item['body'] .= "\n[img]" . $attach['url'] . '[/img]';
|
||||
} else {
|
||||
$item['body'] .= "\n[img=" . $attach['url'] . ']' . $attach['name'] . '[/img]';
|
||||
}
|
||||
} elseif ($filetype == 'audio') {
|
||||
if (!empty($activity['source']) && strpos($activity['source'], $attach['url'])) {
|
||||
continue 2;
|
||||
}
|
||||
|
||||
$item['body'] .= "\n[audio]" . $attach['url'] . '[/audio]';
|
||||
} elseif ($filetype == 'video') {
|
||||
if (!empty($activity['source']) && strpos($activity['source'], $attach['url'])) {
|
||||
continue;
|
||||
}
|
||||
$item['body'] .= "\n[audio]" . $attach['url'] . '[/audio]';
|
||||
} elseif ($filetype == 'video') {
|
||||
if (!empty($activity['source']) && strpos($activity['source'], $attach['url'])) {
|
||||
continue 2;
|
||||
}
|
||||
|
||||
$item['body'] .= "\n[video]" . $attach['url'] . '[/video]';
|
||||
} else {
|
||||
if (!empty($item["attach"])) {
|
||||
$item["attach"] .= ',';
|
||||
} else {
|
||||
$item["attach"] = '';
|
||||
}
|
||||
if (!isset($attach['length'])) {
|
||||
$attach['length'] = "0";
|
||||
}
|
||||
$item["attach"] .= '[attach]href="'.$attach['url'].'" length="'.$attach['length'].'" type="'.$attach['mediaType'].'" title="'.($attach['name'] ?? '') .'"[/attach]';
|
||||
$item['body'] .= "\n[video]" . $attach['url'] . '[/video]';
|
||||
} else {
|
||||
if (!empty($item["attach"])) {
|
||||
$item["attach"] .= ',';
|
||||
} else {
|
||||
$item["attach"] = '';
|
||||
}
|
||||
|
||||
$item["attach"] .= '[attach]href="' . $attach['url'] . '" length="' . ($attach['length'] ?? '0') . '" type="' . $attach['mediaType'] . '" title="' . ($attach['name'] ?? '') . '"[/attach]';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -169,9 +156,10 @@ class Processor
|
|||
*/
|
||||
public static function updateItem($activity)
|
||||
{
|
||||
$item = Item::selectFirst(['uri', 'thr-parent', 'gravity'], ['uri' => $activity['id']]);
|
||||
$item = Item::selectFirst(['uri', 'uri-id', 'thr-parent', 'gravity'], ['uri' => $activity['id']]);
|
||||
if (!DBA::isResult($item)) {
|
||||
Logger::warning('Unknown item', ['uri' => $activity['id']]);
|
||||
Logger::warning('No existing item, item will be created', ['uri' => $activity['id']]);
|
||||
self::createItem($activity);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -231,7 +219,7 @@ class Processor
|
|||
{
|
||||
$owner = Contact::getIdForURL($activity['actor']);
|
||||
|
||||
Logger::log('Deleting item ' . $activity['object_id'] . ' from ' . $owner, Logger::DEBUG);
|
||||
Logger::info('Deleting item', ['object' => $activity['object_id'], 'owner' => $owner]);
|
||||
Item::markForDeletion(['uri' => $activity['object_id'], 'owner-id' => $owner]);
|
||||
}
|
||||
|
||||
|
|
@ -249,7 +237,7 @@ class Processor
|
|||
}
|
||||
|
||||
foreach ($activity['receiver'] as $receiver) {
|
||||
$item = Item::selectFirst(['id', 'tag', 'origin', 'author-link'], ['uri' => $activity['target_id'], 'uid' => $receiver]);
|
||||
$item = Item::selectFirst(['id', 'uri-id', 'tag', 'origin', 'author-link'], ['uri' => $activity['target_id'], 'uid' => $receiver]);
|
||||
if (!DBA::isResult($item)) {
|
||||
// We don't fetch missing content for this purpose
|
||||
continue;
|
||||
|
|
@ -260,15 +248,8 @@ class Processor
|
|||
continue;
|
||||
}
|
||||
|
||||
// To-Do:
|
||||
// - Check if "blocktag" is set
|
||||
// - Check if actor is a contact
|
||||
|
||||
if (!stristr($item['tag'], trim($activity['object_content']))) {
|
||||
$tag = $item['tag'] . (strlen($item['tag']) ? ',' : '') . '#[url=' . $activity['object_id'] . ']'. $activity['object_content'] . '[/url]';
|
||||
Item::update(['tag' => $tag], ['id' => $item['id']]);
|
||||
Logger::info('Tagged item', ['id' => $item['id'], 'tag' => $activity['object_content'], 'uri' => $activity['target_id'], 'actor' => $activity['actor']]);
|
||||
}
|
||||
Tag::store($item['uri-id'], Tag::HASHTAG, $activity['object_content'], $activity['object_id']);
|
||||
Logger::info('Tagged item', ['id' => $item['id'], 'tag' => $activity['object_content'], 'uri' => $activity['target_id'], 'actor' => $activity['actor']]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -355,7 +336,7 @@ class Processor
|
|||
}
|
||||
|
||||
$event_id = Event::store($event);
|
||||
Logger::log('Event '.$event_id.' was stored', Logger::DEBUG);
|
||||
Logger::info('Event was stored', ['id' => $event_id]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -383,30 +364,29 @@ class Processor
|
|||
|
||||
if (empty($activity['directmessage']) && ($item['thr-parent'] != $item['uri']) && ($item['gravity'] == GRAVITY_COMMENT)) {
|
||||
$item_private = !in_array(0, $activity['item_receiver']);
|
||||
$parent = Item::selectFirst(['id', 'private', 'author-link', 'alias'], ['uri' => $item['thr-parent']]);
|
||||
$parent = Item::selectFirst(['id', 'uri-id', 'private', 'author-link', 'alias'], ['uri' => $item['thr-parent']]);
|
||||
if (!DBA::isResult($parent)) {
|
||||
Logger::warning('Unknown parent item.', ['uri' => $item['thr-parent']]);
|
||||
return false;
|
||||
}
|
||||
if ($item_private && ($parent['private'] == Item::PRIVATE)) {
|
||||
if ($item_private && ($parent['private'] != Item::PRIVATE)) {
|
||||
Logger::warning('Item is private but the parent is not. Dropping.', ['item-uri' => $item['uri'], 'thr-parent' => $item['thr-parent']]);
|
||||
return false;
|
||||
}
|
||||
|
||||
$potential_implicit_mentions = self::getImplicitMentionList($parent);
|
||||
$content = self::removeImplicitMentionsFromBody($content, $potential_implicit_mentions);
|
||||
$activity['tags'] = self::convertImplicitMentionsInTags($activity['tags'], $potential_implicit_mentions);
|
||||
$content = self::removeImplicitMentionsFromBody($content, $parent);
|
||||
}
|
||||
$item['content-warning'] = HTML::toBBCode($activity['summary']);
|
||||
$item['body'] = $content;
|
||||
}
|
||||
|
||||
$item['tag'] = self::constructTagString($activity['tags'], $activity['sensitive']);
|
||||
self::storeFromBody($item);
|
||||
self::storeTags($item['uri-id'], $activity['tags']);
|
||||
|
||||
$item['location'] = $activity['location'];
|
||||
|
||||
if (!empty($item['latitude']) && !empty($item['longitude'])) {
|
||||
$item['coord'] = $item['latitude'] . ' ' . $item['longitude'];
|
||||
if (!empty($activity['latitude']) && !empty($activity['longitude'])) {
|
||||
$item['coord'] = $activity['latitude'] . ' ' . $activity['longitude'];
|
||||
}
|
||||
|
||||
$item['app'] = $activity['generator'];
|
||||
|
|
@ -414,6 +394,19 @@ class Processor
|
|||
return $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store hashtags and mentions
|
||||
*
|
||||
* @param array $item
|
||||
*/
|
||||
private static function storeFromBody(array $item)
|
||||
{
|
||||
// Make sure to delete all existing tags (can happen when called via the update functionality)
|
||||
DBA::delete('post-tag', ['uri-id' => $item['uri-id']]);
|
||||
|
||||
Tag::storeFromBody($item['uri-id'], $item['body'], '@!');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a GUID out of an URL
|
||||
*
|
||||
|
|
@ -494,7 +487,10 @@ class Processor
|
|||
|
||||
$item['created'] = DateTimeFormat::utc($activity['published']);
|
||||
$item['edited'] = DateTimeFormat::utc($activity['updated']);
|
||||
$item['guid'] = $activity['diaspora:guid'] ?: $activity['sc:identifier'] ?: self::getGUIDByURL($item['uri']);
|
||||
$guid = $activity['sc:identifier'] ?: self::getGUIDByURL($item['uri']);
|
||||
$item['guid'] = $activity['diaspora:guid'] ?: $guid;
|
||||
|
||||
$item['uri-id'] = ItemURI::insert(['uri' => $item['uri'], 'guid' => $item['guid']]);
|
||||
|
||||
$item = self::processContent($activity, $item);
|
||||
if (empty($item)) {
|
||||
|
|
@ -565,12 +561,56 @@ class Processor
|
|||
$author = APContact::getByURL($item['owner-link'], false);
|
||||
// We send automatic follow requests for reshared messages. (We don't need though for forum posts)
|
||||
if ($author['type'] != 'Group') {
|
||||
Logger::log('Send follow request for ' . $item['uri'] . ' (' . $stored . ') to ' . $item['author-link'], Logger::DEBUG);
|
||||
Logger::info('Send follow request', ['uri' => $item['uri'], 'stored' => $stored, 'to' => $item['author-link']]);
|
||||
ActivityPub\Transmitter::sendFollowObject($item['uri'], $item['author-link']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Store tags and mentions into the tag table
|
||||
*
|
||||
* @param integer $uriid
|
||||
* @param array $tags
|
||||
*/
|
||||
private static function storeTags(int $uriid, array $tags = null)
|
||||
{
|
||||
foreach ($tags as $tag) {
|
||||
if (empty($tag['name']) || empty($tag['type']) || !in_array($tag['type'], ['Mention', 'Hashtag'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$hash = substr($tag['name'], 0, 1);
|
||||
|
||||
if ($tag['type'] == 'Mention') {
|
||||
if (in_array($hash, [Tag::TAG_CHARACTER[Tag::MENTION],
|
||||
Tag::TAG_CHARACTER[Tag::EXCLUSIVE_MENTION],
|
||||
Tag::TAG_CHARACTER[Tag::IMPLICIT_MENTION]])) {
|
||||
$tag['name'] = substr($tag['name'], 1);
|
||||
}
|
||||
$type = Tag::IMPLICIT_MENTION;
|
||||
|
||||
if (!empty($tag['href'])) {
|
||||
$apcontact = APContact::getByURL($tag['href']);
|
||||
if (!empty($apcontact['name']) || !empty($apcontact['nick'])) {
|
||||
$tag['name'] = $apcontact['name'] ?: $apcontact['nick'];
|
||||
}
|
||||
}
|
||||
} elseif ($tag['type'] == 'Hashtag') {
|
||||
if ($hash == Tag::TAG_CHARACTER[Tag::HASHTAG]) {
|
||||
$tag['name'] = substr($tag['name'], 1);
|
||||
}
|
||||
$type = Tag::HASHTAG;
|
||||
}
|
||||
|
||||
if (empty($tag['name'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Tag::store($uriid, $type, $tag['name'], $tag['href']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an mail post
|
||||
*
|
||||
|
|
@ -620,7 +660,7 @@ class Processor
|
|||
$title = $matches[3];
|
||||
}
|
||||
|
||||
$title = trim(HTML::toPlaintext(BBCode::convert($title, false, 2, true), 0));
|
||||
$title = trim(HTML::toPlaintext(BBCode::convert($title, false, BBCode::API, true), 0));
|
||||
|
||||
if (strlen($title) > 20) {
|
||||
$title = substr($title, 0, 20) . '...';
|
||||
|
|
@ -737,7 +777,7 @@ class Processor
|
|||
|
||||
$result = Contact::addRelationship($owner, $contact, $item, false, $note);
|
||||
if ($result === true) {
|
||||
ActivityPub\Transmitter::sendContactAccept($item['author-link'], $item['author-id'], $owner['uid']);
|
||||
ActivityPub\Transmitter::sendContactAccept($item['author-link'], $activity['id'], $owner['uid']);
|
||||
}
|
||||
|
||||
$cid = Contact::getIdForURL($activity['actor'], $uid);
|
||||
|
|
@ -764,7 +804,7 @@ class Processor
|
|||
return;
|
||||
}
|
||||
|
||||
Logger::log('Updating profile for ' . $activity['object_id'], Logger::DEBUG);
|
||||
Logger::info('Updating profile', ['object' => $activity['object_id']]);
|
||||
Contact::updateFromProbeByURL($activity['object_id'], true);
|
||||
}
|
||||
|
||||
|
|
@ -777,12 +817,12 @@ class Processor
|
|||
public static function deletePerson($activity)
|
||||
{
|
||||
if (empty($activity['object_id']) || empty($activity['actor'])) {
|
||||
Logger::log('Empty object id or actor.', Logger::DEBUG);
|
||||
Logger::info('Empty object id or actor.');
|
||||
return;
|
||||
}
|
||||
|
||||
if ($activity['object_id'] != $activity['actor']) {
|
||||
Logger::log('Object id does not match actor.', Logger::DEBUG);
|
||||
Logger::info('Object id does not match actor.');
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -792,7 +832,7 @@ class Processor
|
|||
}
|
||||
DBA::close($contacts);
|
||||
|
||||
Logger::log('Deleted contact ' . $activity['object_id'], Logger::DEBUG);
|
||||
Logger::info('Deleted contact', ['object' => $activity['object_id']]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -811,7 +851,7 @@ class Processor
|
|||
|
||||
$cid = Contact::getIdForURL($activity['actor'], $uid);
|
||||
if (empty($cid)) {
|
||||
Logger::log('No contact found for ' . $activity['actor'], Logger::DEBUG);
|
||||
Logger::info('No contact found', ['actor' => $activity['actor']]);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -826,7 +866,7 @@ class Processor
|
|||
|
||||
$condition = ['id' => $cid];
|
||||
DBA::update('contact', $fields, $condition);
|
||||
Logger::log('Accept contact request from contact ' . $cid . ' for user ' . $uid, Logger::DEBUG);
|
||||
Logger::info('Accept contact request', ['contact' => $cid, 'user' => $uid]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -845,7 +885,7 @@ class Processor
|
|||
|
||||
$cid = Contact::getIdForURL($activity['actor'], $uid);
|
||||
if (empty($cid)) {
|
||||
Logger::log('No contact found for ' . $activity['actor'], Logger::DEBUG);
|
||||
Logger::info('No contact found', ['actor' => $activity['actor']]);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -853,9 +893,9 @@ class Processor
|
|||
|
||||
if (DBA::exists('contact', ['id' => $cid, 'rel' => Contact::SHARING])) {
|
||||
Contact::remove($cid);
|
||||
Logger::log('Rejected contact request from contact ' . $cid . ' for user ' . $uid . ' - contact had been removed.', Logger::DEBUG);
|
||||
Logger::info('Rejected contact request - contact removed', ['contact' => $cid, 'user' => $uid]);
|
||||
} else {
|
||||
Logger::log('Rejected contact request from contact ' . $cid . ' for user ' . $uid . '.', Logger::DEBUG);
|
||||
Logger::info('Rejected contact request', ['contact' => $cid, 'user' => $uid]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -902,7 +942,7 @@ class Processor
|
|||
|
||||
$cid = Contact::getIdForURL($activity['actor'], $uid);
|
||||
if (empty($cid)) {
|
||||
Logger::log('No contact found for ' . $activity['actor'], Logger::DEBUG);
|
||||
Logger::info('No contact found', ['actor' => $activity['actor']]);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -914,7 +954,7 @@ class Processor
|
|||
}
|
||||
|
||||
Contact::removeFollower($owner, $contact);
|
||||
Logger::log('Undo following request from contact ' . $cid . ' for user ' . $uid, Logger::DEBUG);
|
||||
Logger::info('Undo following request', ['contact' => $cid, 'user' => $uid]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -945,16 +985,12 @@ class Processor
|
|||
*/
|
||||
private static function getImplicitMentionList(array $parent)
|
||||
{
|
||||
if (DI::config()->get('system', 'disable_implicit_mentions')) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$parent_terms = Term::tagArrayFromItemId($parent['id'], [Term::MENTION, Term::IMPLICIT_MENTION]);
|
||||
$parent_terms = Tag::getByURIId($parent['uri-id'], [Tag::MENTION, Tag::IMPLICIT_MENTION, Tag::EXCLUSIVE_MENTION]);
|
||||
|
||||
$parent_author = Contact::getDetailsByURL($parent['author-link'], 0);
|
||||
|
||||
$implicit_mentions = [];
|
||||
if (empty($parent_author)) {
|
||||
if (empty($parent_author['url'])) {
|
||||
Logger::notice('Author public contact unknown.', ['author-link' => $parent['author-link'], 'item-id' => $parent['id']]);
|
||||
} else {
|
||||
$implicit_mentions[] = $parent_author['url'];
|
||||
|
|
@ -968,7 +1004,7 @@ class Processor
|
|||
|
||||
foreach ($parent_terms as $term) {
|
||||
$contact = Contact::getDetailsByURL($term['url'], 0);
|
||||
if (!empty($contact)) {
|
||||
if (!empty($contact['url'])) {
|
||||
$implicit_mentions[] = $contact['url'];
|
||||
$implicit_mentions[] = $contact['nurl'];
|
||||
$implicit_mentions[] = $contact['alias'];
|
||||
|
|
@ -982,15 +1018,17 @@ class Processor
|
|||
* Strips from the body prepended implicit mentions
|
||||
*
|
||||
* @param string $body
|
||||
* @param array $potential_mentions
|
||||
* @param array $parent
|
||||
* @return string
|
||||
*/
|
||||
private static function removeImplicitMentionsFromBody($body, array $potential_mentions)
|
||||
private static function removeImplicitMentionsFromBody(string $body, array $parent)
|
||||
{
|
||||
if (DI::config()->get('system', 'disable_implicit_mentions')) {
|
||||
return $body;
|
||||
}
|
||||
|
||||
$potential_mentions = self::getImplicitMentionList($parent);
|
||||
|
||||
$kept_mentions = [];
|
||||
|
||||
// Extract one prepended mention at a time from the body
|
||||
|
|
@ -1007,24 +1045,4 @@ class Processor
|
|||
|
||||
return implode('', $kept_mentions);
|
||||
}
|
||||
|
||||
private static function convertImplicitMentionsInTags($activity_tags, array $potential_mentions)
|
||||
{
|
||||
if (DI::config()->get('system', 'disable_implicit_mentions')) {
|
||||
return $activity_tags;
|
||||
}
|
||||
|
||||
foreach ($activity_tags as $index => $tag) {
|
||||
if (in_array($tag['href'], $potential_mentions)) {
|
||||
$activity_tags[$index]['name'] = preg_replace(
|
||||
'/' . preg_quote(Term::TAG_CHARACTER[Term::MENTION], '/') . '/',
|
||||
Term::TAG_CHARACTER[Term::IMPLICIT_MENTION],
|
||||
$activity_tags[$index]['name'],
|
||||
1
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $activity_tags;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
namespace Friendica\Protocol\ActivityPub;
|
||||
|
||||
use Friendica\Content\Text\BBCode;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\Content\Text\HTML;
|
||||
use Friendica\Content\Text\Markdown;
|
||||
|
|
@ -273,7 +274,7 @@ class Receiver
|
|||
$object_data['object_type'] = JsonLD::fetchElement($activity['as:object'], '@type');
|
||||
|
||||
// An Undo is done on the object of an object, so we need that type as well
|
||||
if ($type == 'as:Undo') {
|
||||
if (($type == 'as:Undo') && !empty($object_data['object_object'])) {
|
||||
$object_data['object_object_type'] = self::fetchObjectType([], $object_data['object_object'], $uid);
|
||||
}
|
||||
}
|
||||
|
|
@ -820,14 +821,10 @@ class Receiver
|
|||
*
|
||||
* @return array with tags in a simplified format
|
||||
*/
|
||||
private static function processTags($tags)
|
||||
private static function processTags(array $tags)
|
||||
{
|
||||
$taglist = [];
|
||||
|
||||
if (empty($tags)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
foreach ($tags as $tag) {
|
||||
if (empty($tag)) {
|
||||
continue;
|
||||
|
|
@ -853,17 +850,13 @@ class Receiver
|
|||
/**
|
||||
* Convert emojis from JSON-LD format into a simplified format
|
||||
*
|
||||
* @param $emojis
|
||||
* @param array $emojis
|
||||
* @return array with emojis in a simplified format
|
||||
*/
|
||||
private static function processEmojis($emojis)
|
||||
private static function processEmojis(array $emojis)
|
||||
{
|
||||
$emojilist = [];
|
||||
|
||||
if (empty($emojis)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
foreach ($emojis as $emoji) {
|
||||
if (empty($emoji) || (JsonLD::fetchElement($emoji, '@type') != 'toot:Emoji') || empty($emoji['as:icon'])) {
|
||||
continue;
|
||||
|
|
@ -875,6 +868,7 @@ class Receiver
|
|||
|
||||
$emojilist[] = $element;
|
||||
}
|
||||
|
||||
return $emojilist;
|
||||
}
|
||||
|
||||
|
|
@ -885,24 +879,62 @@ class Receiver
|
|||
*
|
||||
* @return array with attachmants in a simplified format
|
||||
*/
|
||||
private static function processAttachments($attachments)
|
||||
private static function processAttachments(array $attachments)
|
||||
{
|
||||
$attachlist = [];
|
||||
|
||||
if (empty($attachments)) {
|
||||
return [];
|
||||
}
|
||||
// Removes empty values
|
||||
$attachments = array_filter($attachments);
|
||||
|
||||
foreach ($attachments as $attachment) {
|
||||
if (empty($attachment)) {
|
||||
continue;
|
||||
}
|
||||
switch (JsonLD::fetchElement($attachment, '@type')) {
|
||||
case 'as:Page':
|
||||
$pageUrl = null;
|
||||
$pageImage = null;
|
||||
|
||||
$attachlist[] = ['type' => str_replace('as:', '', JsonLD::fetchElement($attachment, '@type')),
|
||||
'mediaType' => JsonLD::fetchElement($attachment, 'as:mediaType', '@value'),
|
||||
'name' => JsonLD::fetchElement($attachment, 'as:name', '@value'),
|
||||
'url' => JsonLD::fetchElement($attachment, 'as:url', '@id')];
|
||||
$urls = JsonLD::fetchElementArray($attachment, 'as:url');
|
||||
foreach ($urls as $url) {
|
||||
// Single scalar URL case
|
||||
if (is_string($url)) {
|
||||
$pageUrl = $url;
|
||||
continue;
|
||||
}
|
||||
|
||||
$href = JsonLD::fetchElement($url, 'as:href', '@id');
|
||||
$mediaType = JsonLD::fetchElement($url, 'as:mediaType', '@value');
|
||||
if (Strings::startsWith($mediaType, 'image')) {
|
||||
$pageImage = $href;
|
||||
} else {
|
||||
$pageUrl = $href;
|
||||
}
|
||||
}
|
||||
|
||||
$attachlist[] = [
|
||||
'type' => 'link',
|
||||
'title' => JsonLD::fetchElement($attachment, 'as:name', '@value'),
|
||||
'desc' => JsonLD::fetchElement($attachment, 'as:summary', '@value'),
|
||||
'url' => $pageUrl,
|
||||
'image' => $pageImage,
|
||||
];
|
||||
break;
|
||||
case 'as:Link':
|
||||
$attachlist[] = [
|
||||
'type' => str_replace('as:', '', JsonLD::fetchElement($attachment, '@type')),
|
||||
'mediaType' => JsonLD::fetchElement($attachment, 'as:mediaType', '@value'),
|
||||
'name' => JsonLD::fetchElement($attachment, 'as:name', '@value'),
|
||||
'url' => JsonLD::fetchElement($attachment, 'as:href', '@id')
|
||||
];
|
||||
break;
|
||||
default:
|
||||
$attachlist[] = [
|
||||
'type' => str_replace('as:', '', JsonLD::fetchElement($attachment, '@type')),
|
||||
'mediaType' => JsonLD::fetchElement($attachment, 'as:mediaType', '@value'),
|
||||
'name' => JsonLD::fetchElement($attachment, 'as:name', '@value'),
|
||||
'url' => JsonLD::fetchElement($attachment, 'as:url', '@id')
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $attachlist;
|
||||
}
|
||||
|
||||
|
|
@ -1056,6 +1088,16 @@ class Receiver
|
|||
$actor = JsonLD::fetchElement($object, 'as:actor', '@id');
|
||||
}
|
||||
|
||||
$location = JsonLD::fetchElement($object, 'as:location', 'as:name', '@type', 'as:Place');
|
||||
$location = JsonLD::fetchElement($location, 'location', '@value');
|
||||
if ($location) {
|
||||
// Some AP software allow formatted text in post location, so we run all the text converters we have to boil
|
||||
// down to HTML and then finally format to plaintext.
|
||||
$location = Markdown::convert($location);
|
||||
$location = BBCode::convert($location);
|
||||
$location = HTML::toPlaintext($location);
|
||||
}
|
||||
|
||||
$object_data['sc:identifier'] = JsonLD::fetchElement($object, 'sc:identifier', '@value');
|
||||
$object_data['diaspora:guid'] = JsonLD::fetchElement($object, 'diaspora:guid', '@value');
|
||||
$object_data['diaspora:comment'] = JsonLD::fetchElement($object, 'diaspora:comment', '@value');
|
||||
|
|
@ -1070,15 +1112,14 @@ class Receiver
|
|||
$object_data = self::getSource($object, $object_data);
|
||||
$object_data['start-time'] = JsonLD::fetchElement($object, 'as:startTime', '@value');
|
||||
$object_data['end-time'] = JsonLD::fetchElement($object, 'as:endTime', '@value');
|
||||
$object_data['location'] = JsonLD::fetchElement($object, 'as:location', 'as:name', '@type', 'as:Place');
|
||||
$object_data['location'] = JsonLD::fetchElement($object_data, 'location', '@value');
|
||||
$object_data['location'] = $location;
|
||||
$object_data['latitude'] = JsonLD::fetchElement($object, 'as:location', 'as:latitude', '@type', 'as:Place');
|
||||
$object_data['latitude'] = JsonLD::fetchElement($object_data, 'latitude', '@value');
|
||||
$object_data['longitude'] = JsonLD::fetchElement($object, 'as:location', 'as:longitude', '@type', 'as:Place');
|
||||
$object_data['longitude'] = JsonLD::fetchElement($object_data, 'longitude', '@value');
|
||||
$object_data['attachments'] = self::processAttachments(JsonLD::fetchElementArray($object, 'as:attachment'));
|
||||
$object_data['tags'] = self::processTags(JsonLD::fetchElementArray($object, 'as:tag'));
|
||||
$object_data['emojis'] = self::processEmojis(JsonLD::fetchElementArray($object, 'as:tag', 'toot:Emoji'));
|
||||
$object_data['attachments'] = self::processAttachments(JsonLD::fetchElementArray($object, 'as:attachment') ?? []);
|
||||
$object_data['tags'] = self::processTags(JsonLD::fetchElementArray($object, 'as:tag') ?? []);
|
||||
$object_data['emojis'] = self::processEmojis(JsonLD::fetchElementArray($object, 'as:tag', 'toot:Emoji') ?? []);
|
||||
$object_data['generator'] = JsonLD::fetchElement($object, 'as:generator', 'as:name', '@type', 'as:Application');
|
||||
$object_data['generator'] = JsonLD::fetchElement($object_data, 'generator', '@value');
|
||||
$object_data['alternate-url'] = JsonLD::fetchElement($object, 'as:url', '@id');
|
||||
|
|
|
|||
|
|
@ -34,9 +34,10 @@ use Friendica\Model\APContact;
|
|||
use Friendica\Model\Contact;
|
||||
use Friendica\Model\Conversation;
|
||||
use Friendica\Model\Item;
|
||||
use Friendica\Model\ItemURI;
|
||||
use Friendica\Model\Profile;
|
||||
use Friendica\Model\Photo;
|
||||
use Friendica\Model\Term;
|
||||
use Friendica\Model\Tag;
|
||||
use Friendica\Model\User;
|
||||
use Friendica\Protocol\Activity;
|
||||
use Friendica\Protocol\ActivityPub;
|
||||
|
|
@ -61,24 +62,42 @@ require_once 'mod/share.php';
|
|||
class Transmitter
|
||||
{
|
||||
/**
|
||||
* collects the lost of followers of the given owner
|
||||
* Collects a list of contacts of the given owner
|
||||
*
|
||||
* @param array $owner Owner array
|
||||
* @param integer $page Page number
|
||||
* @param array $owner Owner array
|
||||
* @param int|array $rel The relevant value(s) contact.rel should match
|
||||
* @param string $module The name of the relevant AP endpoint module (followers|following)
|
||||
* @param integer $page Page number
|
||||
*
|
||||
* @return array of owners
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function getFollowers($owner, $page = null)
|
||||
public static function getContacts($owner, $rel, $module, $page = null)
|
||||
{
|
||||
$condition = ['rel' => [Contact::FOLLOWER, Contact::FRIEND], 'network' => Protocol::FEDERATED, 'uid' => $owner['uid'],
|
||||
'self' => false, 'deleted' => false, 'hidden' => false, 'archive' => false, 'pending' => false];
|
||||
$count = DBA::count('contact', $condition);
|
||||
$parameters = [
|
||||
'rel' => $rel,
|
||||
'uid' => $owner['uid'],
|
||||
'self' => false,
|
||||
'deleted' => false,
|
||||
'hidden' => false,
|
||||
'archive' => false,
|
||||
'pending' => false
|
||||
];
|
||||
$condition = DBA::buildCondition($parameters);
|
||||
|
||||
$sql = "SELECT COUNT(*) as `count`
|
||||
FROM `contact`
|
||||
JOIN `apcontact` ON `apcontact`.`url` = `contact`.`url`
|
||||
" . $condition;
|
||||
|
||||
$contacts = DBA::fetchFirst($sql, ...$parameters);
|
||||
|
||||
$modulePath = '/' . $module . '/';
|
||||
|
||||
$data = ['@context' => ActivityPub::CONTEXT];
|
||||
$data['id'] = DI::baseUrl() . '/followers/' . $owner['nickname'];
|
||||
$data['id'] = DI::baseUrl() . $modulePath . $owner['nickname'];
|
||||
$data['type'] = 'OrderedCollection';
|
||||
$data['totalItems'] = $count;
|
||||
$data['totalItems'] = $contacts['count'];
|
||||
|
||||
// When we hide our friends we will only show the pure number but don't allow more.
|
||||
$profile = Profile::getByUID($owner['uid']);
|
||||
|
|
@ -87,70 +106,31 @@ class Transmitter
|
|||
}
|
||||
|
||||
if (empty($page)) {
|
||||
$data['first'] = DI::baseUrl() . '/followers/' . $owner['nickname'] . '?page=1';
|
||||
$data['first'] = DI::baseUrl() . $modulePath . $owner['nickname'] . '?page=1';
|
||||
} else {
|
||||
$data['type'] = 'OrderedCollectionPage';
|
||||
$list = [];
|
||||
|
||||
$contacts = DBA::select('contact', ['url'], $condition, ['limit' => [($page - 1) * 100, 100]]);
|
||||
$sql = "SELECT `contact`.`url`
|
||||
FROM `contact`
|
||||
JOIN `apcontact` ON `apcontact`.`url` = `contact`.`url`
|
||||
" . $condition . "
|
||||
LIMIT ?, ?";
|
||||
|
||||
$parameters[] = ($page - 1) * 100;
|
||||
$parameters[] = 100;
|
||||
|
||||
$contacts = DBA::p($sql, ...$parameters);
|
||||
while ($contact = DBA::fetch($contacts)) {
|
||||
$list[] = $contact['url'];
|
||||
}
|
||||
DBA::close($contacts);
|
||||
|
||||
if (!empty($list)) {
|
||||
$data['next'] = DI::baseUrl() . '/followers/' . $owner['nickname'] . '?page=' . ($page + 1);
|
||||
$data['next'] = DI::baseUrl() . $modulePath . $owner['nickname'] . '?page=' . ($page + 1);
|
||||
}
|
||||
|
||||
$data['partOf'] = DI::baseUrl() . '/followers/' . $owner['nickname'];
|
||||
|
||||
$data['orderedItems'] = $list;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create list of following contacts
|
||||
*
|
||||
* @param array $owner Owner array
|
||||
* @param integer $page Page numbe
|
||||
*
|
||||
* @return array of following contacts
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public static function getFollowing($owner, $page = null)
|
||||
{
|
||||
$condition = ['rel' => [Contact::SHARING, Contact::FRIEND], 'network' => Protocol::FEDERATED, 'uid' => $owner['uid'],
|
||||
'self' => false, 'deleted' => false, 'hidden' => false, 'archive' => false, 'pending' => false];
|
||||
$count = DBA::count('contact', $condition);
|
||||
|
||||
$data = ['@context' => ActivityPub::CONTEXT];
|
||||
$data['id'] = DI::baseUrl() . '/following/' . $owner['nickname'];
|
||||
$data['type'] = 'OrderedCollection';
|
||||
$data['totalItems'] = $count;
|
||||
|
||||
// When we hide our friends we will only show the pure number but don't allow more.
|
||||
$profile = Profile::getByUID($owner['uid']);
|
||||
if (!empty($profile['hide-friends'])) {
|
||||
return $data;
|
||||
}
|
||||
|
||||
if (empty($page)) {
|
||||
$data['first'] = DI::baseUrl() . '/following/' . $owner['nickname'] . '?page=1';
|
||||
} else {
|
||||
$data['type'] = 'OrderedCollectionPage';
|
||||
$list = [];
|
||||
|
||||
$contacts = DBA::select('contact', ['url'], $condition, ['limit' => [($page - 1) * 100, 100]]);
|
||||
while ($contact = DBA::fetch($contacts)) {
|
||||
$list[] = $contact['url'];
|
||||
}
|
||||
|
||||
if (!empty($list)) {
|
||||
$data['next'] = DI::baseUrl() . '/following/' . $owner['nickname'] . '?page=' . ($page + 1);
|
||||
}
|
||||
|
||||
$data['partOf'] = DI::baseUrl() . '/following/' . $owner['nickname'];
|
||||
$data['partOf'] = DI::baseUrl() . $modulePath . $owner['nickname'];
|
||||
|
||||
$data['orderedItems'] = $list;
|
||||
}
|
||||
|
|
@ -405,7 +385,7 @@ class Transmitter
|
|||
$actor_profile = APContact::getByURL($item['author-link']);
|
||||
}
|
||||
|
||||
$terms = Term::tagArrayFromItemId($item['id'], [Term::MENTION, Term::IMPLICIT_MENTION]);
|
||||
$terms = Tag::getByURIId($item['uri-id'], [Tag::MENTION, Tag::IMPLICIT_MENTION, Tag::EXCLUSIVE_MENTION]);
|
||||
|
||||
if ($item['private'] != Item::PRIVATE) {
|
||||
// Directly mention the original author upon a quoted reshare.
|
||||
|
|
@ -478,7 +458,7 @@ class Transmitter
|
|||
$data['to'][] = $profile['url'];
|
||||
} else {
|
||||
$data['cc'][] = $profile['url'];
|
||||
if (($item['private'] != Item::PRIVATE) && $item['private'] && !empty($actor_profile['followers'])) {
|
||||
if (($item['private'] != Item::PRIVATE) && !empty($actor_profile['followers'])) {
|
||||
$data['cc'][] = $actor_profile['followers'];
|
||||
}
|
||||
}
|
||||
|
|
@ -659,7 +639,7 @@ class Transmitter
|
|||
continue;
|
||||
}
|
||||
|
||||
if ($receiver == $item_profile['followers']) {
|
||||
if ($item_profile && $receiver == $item_profile['followers']) {
|
||||
$inboxes = array_merge($inboxes, self::fetchTargetInboxesforUser($uid, $personal));
|
||||
} else {
|
||||
if (Contact::isLocal($receiver)) {
|
||||
|
|
@ -699,6 +679,8 @@ class Transmitter
|
|||
return [];
|
||||
}
|
||||
|
||||
$mail['uri-id'] = ItemURI::insert(['uri' => $mail['uri'], 'guid' => $mail['guid']]);
|
||||
|
||||
$reply = DBA::selectFirst('mail', ['uri'], ['parent-uri' => $mail['parent-uri'], 'reply' => false]);
|
||||
|
||||
// Making the post more compatible for Mastodon by:
|
||||
|
|
@ -747,7 +729,7 @@ class Transmitter
|
|||
$data = [];
|
||||
}
|
||||
|
||||
$data['id'] = $mail['uri'] . '#Create';
|
||||
$data['id'] = $mail['uri'] . '/Create';
|
||||
$data['type'] = 'Create';
|
||||
$data['actor'] = $mail['author-link'];
|
||||
$data['published'] = DateTimeFormat::utc($mail['created'] . '+00:00', DateTimeFormat::ATOM);
|
||||
|
|
@ -852,7 +834,7 @@ class Transmitter
|
|||
}
|
||||
}
|
||||
|
||||
$data = ActivityPub\Transmitter::createActivityFromItem($item_id);
|
||||
$data = self::createActivityFromItem($item_id);
|
||||
|
||||
DI::cache()->set($cachekey, $data, Duration::QUARTER_HOUR);
|
||||
return $data;
|
||||
|
|
@ -881,8 +863,8 @@ class Transmitter
|
|||
$type = 'Announce';
|
||||
|
||||
// Disguise forum posts as reshares. Will later be converted to a real announce
|
||||
$item['body'] = share_header($item['author-name'], $item['author-link'], $item['author-avatar'],
|
||||
$item['guid'], $item['created'], $item['plink']) . $item['body'] . '[/share]';
|
||||
$item['body'] = BBCode::getShareOpeningTag($item['author-name'], $item['author-link'], $item['author-avatar'],
|
||||
$item['plink'], $item['created'], $item['guid']) . $item['body'] . '[/share]';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -891,8 +873,21 @@ class Transmitter
|
|||
$conversation = DBA::selectFirst('conversation', ['source'], $condition);
|
||||
if (DBA::isResult($conversation)) {
|
||||
$data = json_decode($conversation['source'], true);
|
||||
if (!empty($data)) {
|
||||
return $data;
|
||||
if (!empty($data['type'])) {
|
||||
if (in_array($data['type'], ['Create', 'Update'])) {
|
||||
if ($object_mode) {
|
||||
unset($data['@context']);
|
||||
unset($data['signature']);
|
||||
}
|
||||
return $data;
|
||||
} elseif (in_array('as:' . $data['type'], Receiver::CONTENT_TYPES)) {
|
||||
if (!empty($data['@context'])) {
|
||||
$context = $data['@context'];
|
||||
unset($data['@context']);
|
||||
}
|
||||
unset($data['actor']);
|
||||
$object = $data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -900,7 +895,7 @@ class Transmitter
|
|||
}
|
||||
|
||||
if (!$object_mode) {
|
||||
$data = ['@context' => ActivityPub::CONTEXT];
|
||||
$data = ['@context' => $context ?? ActivityPub::CONTEXT];
|
||||
|
||||
if ($item['deleted'] && ($item['gravity'] == GRAVITY_ACTIVITY)) {
|
||||
$type = 'Undo';
|
||||
|
|
@ -911,7 +906,7 @@ class Transmitter
|
|||
$data = [];
|
||||
}
|
||||
|
||||
$data['id'] = $item['uri'] . '#' . $type;
|
||||
$data['id'] = $item['uri'] . '/' . $type;
|
||||
$data['type'] = $type;
|
||||
|
||||
if (Item::isForumPost($item) && ($type != 'Announce')) {
|
||||
|
|
@ -927,7 +922,7 @@ class Transmitter
|
|||
$data = array_merge($data, self::createPermissionBlockForItem($item, false));
|
||||
|
||||
if (in_array($data['type'], ['Create', 'Update', 'Delete'])) {
|
||||
$data['object'] = self::createNote($item);
|
||||
$data['object'] = $object ?? self::createNote($item);
|
||||
} elseif ($data['type'] == 'Add') {
|
||||
$data = self::createAddTag($item, $data);
|
||||
} elseif ($data['type'] == 'Announce') {
|
||||
|
|
@ -1007,12 +1002,12 @@ class Transmitter
|
|||
{
|
||||
$tags = [];
|
||||
|
||||
$terms = Term::tagArrayFromItemId($item['id'], [Term::HASHTAG, Term::MENTION, Term::IMPLICIT_MENTION]);
|
||||
$terms = Tag::getByURIId($item['uri-id'], [Tag::HASHTAG, Tag::MENTION, Tag::IMPLICIT_MENTION, Tag::EXCLUSIVE_MENTION]);
|
||||
foreach ($terms as $term) {
|
||||
if ($term['type'] == Term::HASHTAG) {
|
||||
$url = DI::baseUrl() . '/search?tag=' . urlencode($term['term']);
|
||||
$tags[] = ['type' => 'Hashtag', 'href' => $url, 'name' => '#' . $term['term']];
|
||||
} elseif ($term['type'] == Term::MENTION || $term['type'] == Term::IMPLICIT_MENTION) {
|
||||
if ($term['type'] == Tag::HASHTAG) {
|
||||
$url = DI::baseUrl() . '/search?tag=' . urlencode($term['name']);
|
||||
$tags[] = ['type' => 'Hashtag', 'href' => $url, 'name' => '#' . $term['name']];
|
||||
} else {
|
||||
$contact = Contact::getDetailsByURL($term['url']);
|
||||
if (!empty($contact['addr'])) {
|
||||
$mention = '@' . $contact['addr'];
|
||||
|
|
@ -1211,15 +1206,14 @@ class Transmitter
|
|||
/**
|
||||
* Returns if the post contains sensitive content ("nsfw")
|
||||
*
|
||||
* @param integer $item_id
|
||||
* @param integer $uri_id
|
||||
*
|
||||
* @return boolean
|
||||
* @throws \Exception
|
||||
*/
|
||||
private static function isSensitive($item_id)
|
||||
private static function isSensitive($uri_id)
|
||||
{
|
||||
$condition = ['otype' => TERM_OBJ_POST, 'oid' => $item_id, 'type' => TERM_HASHTAG, 'term' => 'nsfw'];
|
||||
return DBA::exists('term', $condition);
|
||||
return DBA::exists('tag-view', ['uri-id' => $uri_id, 'name' => 'nsfw']);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1234,7 +1228,7 @@ class Transmitter
|
|||
{
|
||||
$event = [];
|
||||
$event['name'] = $item['event-summary'];
|
||||
$event['content'] = BBCode::convert($item['event-desc'], false, 9);
|
||||
$event['content'] = BBCode::convert($item['event-desc'], false, BBCode::ACTIVITYPUB);
|
||||
$event['startTime'] = DateTimeFormat::utc($item['event-start'] . '+00:00', DateTimeFormat::ATOM);
|
||||
|
||||
if (!$item['event-nofinish']) {
|
||||
|
|
@ -1301,7 +1295,7 @@ class Transmitter
|
|||
|
||||
$data['url'] = $item['plink'];
|
||||
$data['attributedTo'] = $item['author-link'];
|
||||
$data['sensitive'] = self::isSensitive($item['id']);
|
||||
$data['sensitive'] = self::isSensitive($item['uri-id']);
|
||||
$data['context'] = self::fetchContextURLForItem($item);
|
||||
|
||||
if (!empty($item['title'])) {
|
||||
|
|
@ -1313,7 +1307,7 @@ class Transmitter
|
|||
$body = $item['body'];
|
||||
|
||||
if (empty($item['uid']) || !Feature::isEnabled($item['uid'], 'explicit_mentions')) {
|
||||
$body = self::prependMentions($body, $permission_block);
|
||||
$body = self::prependMentions($body, $item['uri-id']);
|
||||
}
|
||||
|
||||
if ($type == 'Note') {
|
||||
|
|
@ -1328,7 +1322,7 @@ class Transmitter
|
|||
$regexp = "/[@!]\[url\=([^\[\]]*)\].*?\[\/url\]/ism";
|
||||
$body = preg_replace_callback($regexp, ['self', 'mentionCallback'], $body);
|
||||
|
||||
$data['content'] = BBCode::convert($body, false, 9);
|
||||
$data['content'] = BBCode::convert($body, false, BBCode::ACTIVITYPUB);
|
||||
}
|
||||
|
||||
// The regular "content" field does contain a minimized HTML. This is done since systems like
|
||||
|
|
@ -1406,8 +1400,8 @@ class Transmitter
|
|||
*/
|
||||
private static function createAddTag($item, $data)
|
||||
{
|
||||
$object = XML::parseString($item['object'], false);
|
||||
$target = XML::parseString($item["target"], false);
|
||||
$object = XML::parseString($item['object']);
|
||||
$target = XML::parseString($item["target"]);
|
||||
|
||||
$data['diaspora:guid'] = $item['guid'];
|
||||
$data['actor'] = $item['author-link'];
|
||||
|
|
@ -1862,22 +1856,18 @@ class Transmitter
|
|||
HTTPSignature::transmit($signed, $profile['inbox'], $uid);
|
||||
}
|
||||
|
||||
private static function prependMentions($body, array $permission_block)
|
||||
private static function prependMentions($body, int $uriid)
|
||||
{
|
||||
if (DI::config()->get('system', 'disable_implicit_mentions')) {
|
||||
return $body;
|
||||
}
|
||||
|
||||
$mentions = [];
|
||||
|
||||
foreach ($permission_block['to'] as $profile_url) {
|
||||
$profile = Contact::getDetailsByURL($profile_url);
|
||||
foreach (Tag::getByURIId($uriid, [Tag::IMPLICIT_MENTION]) as $tag) {
|
||||
$profile = Contact::getDetailsByURL($tag['url']);
|
||||
if (!empty($profile['addr'])
|
||||
&& $profile['contact-type'] != Contact::TYPE_COMMUNITY
|
||||
&& !strstr($body, $profile['addr'])
|
||||
&& !strstr($body, $profile_url)
|
||||
&& !strstr($body, $tag['url'])
|
||||
) {
|
||||
$mentions[] = '@[url=' . $profile_url . ']' . $profile['nick'] . '[/url]';
|
||||
$mentions[] = '@[url=' . $tag['url'] . ']' . $profile['nick'] . '[/url]';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,9 +24,7 @@ namespace Friendica\Protocol;
|
|||
use DOMDocument;
|
||||
use DOMXPath;
|
||||
use Friendica\App\BaseURL;
|
||||
use Friendica\Content\OEmbed;
|
||||
use Friendica\Content\Text\BBCode;
|
||||
use Friendica\Content\Text\HTML;
|
||||
use Friendica\Core\Hook;
|
||||
use Friendica\Core\Logger;
|
||||
use Friendica\Core\Protocol;
|
||||
|
|
@ -37,10 +35,13 @@ use Friendica\Model\Conversation;
|
|||
use Friendica\Model\Event;
|
||||
use Friendica\Model\GContact;
|
||||
use Friendica\Model\Item;
|
||||
use Friendica\Model\ItemURI;
|
||||
use Friendica\Model\Mail;
|
||||
use Friendica\Model\Notify\Type;
|
||||
use Friendica\Model\PermissionSet;
|
||||
use Friendica\Model\Post\Category;
|
||||
use Friendica\Model\Profile;
|
||||
use Friendica\Model\Tag;
|
||||
use Friendica\Model\User;
|
||||
use Friendica\Network\Probe;
|
||||
use Friendica\Util\Crypto;
|
||||
|
|
@ -49,8 +50,6 @@ use Friendica\Util\Images;
|
|||
use Friendica\Util\Network;
|
||||
use Friendica\Util\Strings;
|
||||
use Friendica\Util\XML;
|
||||
use HTMLPurifier;
|
||||
use HTMLPurifier_Config;
|
||||
|
||||
/**
|
||||
* This class contain functions to create and send DFRN XML files
|
||||
|
|
@ -184,19 +183,12 @@ class DFRN
|
|||
|
||||
$sql_extra = sprintf(" AND `item`.`private` != %s ", Item::PRIVATE);
|
||||
|
||||
$r = q(
|
||||
"SELECT `contact`.*, `user`.`nickname`, `user`.`timezone`, `user`.`page-flags`, `user`.`account-type`
|
||||
FROM `contact` INNER JOIN `user` ON `user`.`uid` = `contact`.`uid`
|
||||
WHERE `contact`.`self` AND `user`.`nickname` = '%s' LIMIT 1",
|
||||
DBA::escape($owner_nick)
|
||||
);
|
||||
|
||||
if (! DBA::isResult($r)) {
|
||||
$owner = DBA::selectFirst('owner-view', [], ['nickname' => $owner_nick]);
|
||||
if (!DBA::isResult($owner)) {
|
||||
Logger::log(sprintf('No contact found for nickname=%d', $owner_nick), Logger::WARNING);
|
||||
exit();
|
||||
}
|
||||
|
||||
$owner = $r[0];
|
||||
$owner_id = $owner['uid'];
|
||||
|
||||
$sql_post_table = "";
|
||||
|
|
@ -249,13 +241,8 @@ class DFRN
|
|||
}
|
||||
|
||||
if (isset($category)) {
|
||||
$sql_post_table = sprintf(
|
||||
"INNER JOIN (SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d ORDER BY `tid` DESC) AS `term` ON `item`.`id` = `term`.`oid` ",
|
||||
DBA::escape(Strings::protectSprintf($category)),
|
||||
intval(TERM_OBJ_POST),
|
||||
intval(TERM_CATEGORY),
|
||||
intval($owner_id)
|
||||
);
|
||||
$sql_post_table = sprintf("INNER JOIN (SELECT `uri-id` FROM `category-view` WHERE `name` = '%s' AND `type` = %d AND `uid` = %d ORDER BY `uri-id` DESC) AS `category` ON `item`.`uri-id` = `category`.`uri-id` ",
|
||||
DBA::escape(Strings::protectSprintf($category)), intval(Category::CATEGORY), intval($owner_id));
|
||||
}
|
||||
|
||||
if ($public_feed && ! $converse) {
|
||||
|
|
@ -685,18 +672,10 @@ class DFRN
|
|||
}
|
||||
|
||||
// Only show contact details when we are allowed to
|
||||
$r = q(
|
||||
"SELECT `profile`.`about`, `profile`.`name`, `profile`.`homepage`, `user`.`nickname`,
|
||||
`user`.`timezone`, `profile`.`locality`, `profile`.`region`, `profile`.`country-name`,
|
||||
`profile`.`pub_keywords`, `profile`.`xmpp`, `profile`.`dob`
|
||||
FROM `profile`
|
||||
INNER JOIN `user` ON `user`.`uid` = `profile`.`uid`
|
||||
WHERE NOT `user`.`hidewall` AND `user`.`uid` = %d",
|
||||
intval($owner['uid'])
|
||||
);
|
||||
if (DBA::isResult($r)) {
|
||||
$profile = $r[0];
|
||||
|
||||
$profile = DBA::selectFirst('owner-view',
|
||||
['about', 'name', 'homepage', 'nickname', 'timezone', 'locality', 'region', 'country-name', 'pub_keywords', 'xmpp', 'dob'],
|
||||
['uid' => $owner['uid'], 'hidewall' => false]);
|
||||
if (DBA::isResult($profile)) {
|
||||
XML::addElement($doc, $author, "poco:displayName", $profile["name"]);
|
||||
XML::addElement($doc, $author, "poco:updated", $namdate);
|
||||
|
||||
|
|
@ -821,7 +800,7 @@ class DFRN
|
|||
if ($activity) {
|
||||
$entry = $doc->createElement($element);
|
||||
|
||||
$r = XML::parseString($activity, false);
|
||||
$r = XML::parseString($activity);
|
||||
if (!$r) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -847,7 +826,7 @@ class DFRN
|
|||
$r->link = preg_replace('/\<link(.*?)\"\>/', '<link$1"/>', $r->link);
|
||||
|
||||
// XML does need a single element as root element so we add a dummy element here
|
||||
$data = XML::parseString("<dummy>" . $r->link . "</dummy>", false);
|
||||
$data = XML::parseString("<dummy>" . $r->link . "</dummy>");
|
||||
if (is_object($data)) {
|
||||
foreach ($data->link as $link) {
|
||||
$attributes = [];
|
||||
|
|
@ -972,7 +951,7 @@ class DFRN
|
|||
$htmlbody = "[b]" . $item['title'] . "[/b]\n\n" . $htmlbody;
|
||||
}
|
||||
|
||||
$htmlbody = BBCode::convert($htmlbody, false, 7);
|
||||
$htmlbody = BBCode::convert($htmlbody, false, BBCode::OSTATUS);
|
||||
}
|
||||
|
||||
$author = self::addEntryAuthor($doc, "author", $item["author-link"], $item);
|
||||
|
|
@ -981,7 +960,7 @@ class DFRN
|
|||
$dfrnowner = self::addEntryAuthor($doc, "dfrn:owner", $item["owner-link"], $item);
|
||||
$entry->appendChild($dfrnowner);
|
||||
|
||||
if (($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
|
||||
if ($item['gravity'] != GRAVITY_PARENT) {
|
||||
$parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
|
||||
$parent = Item::selectFirst(['guid', 'plink'], ['uri' => $parent_item, 'uid' => $item['uid']]);
|
||||
$attributes = ["ref" => $parent_item, "type" => "text/html",
|
||||
|
|
@ -1072,7 +1051,7 @@ class DFRN
|
|||
// The signed text contains the content in Markdown, the sender handle and the signatur for the content
|
||||
// It is needed for relayed comments to Diaspora.
|
||||
if ($item['signed_text']) {
|
||||
$sign = base64_encode(json_encode(['signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer']]));
|
||||
$sign = base64_encode(json_encode(['signed_text' => $item['signed_text'],'signature' => '','signer' => '']));
|
||||
XML::addElement($doc, $entry, "dfrn:diaspora_signature", $sign);
|
||||
}
|
||||
|
||||
|
|
@ -1080,7 +1059,7 @@ class DFRN
|
|||
|
||||
if ($item['object-type'] != "") {
|
||||
XML::addElement($doc, $entry, "activity:object-type", $item['object-type']);
|
||||
} elseif ($item['id'] == $item['parent']) {
|
||||
} elseif ($item['gravity'] == GRAVITY_PARENT) {
|
||||
XML::addElement($doc, $entry, "activity:object-type", Activity\ObjectType::NOTE);
|
||||
} else {
|
||||
XML::addElement($doc, $entry, "activity:object-type", Activity\ObjectType::COMMENT);
|
||||
|
|
@ -1096,21 +1075,15 @@ class DFRN
|
|||
$entry->appendChild($actarg);
|
||||
}
|
||||
|
||||
$tags = Item::getFeedTags($item);
|
||||
$tags = Tag::getByURIId($item['uri-id']);
|
||||
|
||||
/// @TODO Combine this with similar below if() block?
|
||||
if (count($tags)) {
|
||||
foreach ($tags as $t) {
|
||||
if (($type != 'html') || ($t[0] != "@")) {
|
||||
XML::addElement($doc, $entry, "category", "", ["scheme" => "X-DFRN:".$t[0].":".$t[1], "term" => $t[2]]);
|
||||
foreach ($tags as $tag) {
|
||||
if (($type != 'html') || ($tag['type'] == Tag::HASHTAG)) {
|
||||
XML::addElement($doc, $entry, "category", "", ["scheme" => "X-DFRN:" . Tag::TAG_CHARACTER[$tag['type']] . ":" . $tag['url'], "term" => $tag['name']]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (count($tags)) {
|
||||
foreach ($tags as $t) {
|
||||
if ($t[0] == "@") {
|
||||
$mentioned[$t[1]] = $t[1];
|
||||
if ($tag['type'] != Tag::HASHTAG) {
|
||||
$mentioned[$tag['url']] = $tag['url'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1368,7 +1341,7 @@ class DFRN
|
|||
}
|
||||
|
||||
|
||||
Logger::log('dfrn_deliver: ' . "SENDING: " . print_r($postvars, true), Logger::DATA);
|
||||
Logger::debug('dfrn_deliver', ['post' => $postvars]);
|
||||
|
||||
$postResult = Network::post($contact['notify'], $postvars);
|
||||
|
||||
|
|
@ -1587,7 +1560,7 @@ class DFRN
|
|||
if (DBA::isResult($contact_old) && !$onlyfetch) {
|
||||
Logger::log("Check if contact details for contact " . $contact_old["id"] . " (" . $contact_old["nick"] . ") have to be updated.", Logger::DEBUG);
|
||||
|
||||
$poco = ["url" => $contact_old["url"]];
|
||||
$poco = ["url" => $contact_old["url"], "network" => $contact_old["network"]];
|
||||
|
||||
// When was the last change to name or uri?
|
||||
$name_element = $xpath->query($element . "/atom:name", $context)->item(0);
|
||||
|
|
@ -2023,7 +1996,7 @@ class DFRN
|
|||
}
|
||||
|
||||
$fields = ['title' => $item['title'] ?? '', 'body' => $item['body'] ?? '',
|
||||
'tag' => $item['tag'] ?? '', 'changed' => DateTimeFormat::utcNow(),
|
||||
'changed' => DateTimeFormat::utcNow(),
|
||||
'edited' => DateTimeFormat::utc($item["edited"])];
|
||||
|
||||
$condition = ["`uri` = ? AND `uid` IN (0, ?)", $item["uri"], $importer["importer_uid"]];
|
||||
|
|
@ -2117,7 +2090,7 @@ class DFRN
|
|||
if (!$verb) {
|
||||
return;
|
||||
}
|
||||
$xo = XML::parseString($item["object"], false);
|
||||
$xo = XML::parseString($item["object"]);
|
||||
|
||||
if (($xo->type == Activity\ObjectType::PERSON) && ($xo->id)) {
|
||||
// somebody was poked/prodded. Was it me?
|
||||
|
|
@ -2137,7 +2110,7 @@ class DFRN
|
|||
$author = DBA::selectFirst('contact', ['name', 'thumb', 'url'], ['id' => $item['author-id']]);
|
||||
|
||||
$parent = Item::selectFirst(['id'], ['uri' => $item['parent-uri'], 'uid' => $importer["importer_uid"]]);
|
||||
$item["parent"] = $parent['id'];
|
||||
$item['parent'] = $parent['id'];
|
||||
|
||||
// send a notification
|
||||
notification(
|
||||
|
|
@ -2156,7 +2129,7 @@ class DFRN
|
|||
"verb" => $item["verb"],
|
||||
"otype" => "person",
|
||||
"activity" => $verb,
|
||||
"parent" => $item["parent"]]
|
||||
"parent" => $item['parent']]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -2240,11 +2213,11 @@ class DFRN
|
|||
}
|
||||
|
||||
if (($item["verb"] == Activity::TAG) && ($item["object-type"] == Activity\ObjectType::TAGTERM)) {
|
||||
$xo = XML::parseString($item["object"], false);
|
||||
$xt = XML::parseString($item["target"], false);
|
||||
$xo = XML::parseString($item["object"]);
|
||||
$xt = XML::parseString($item["target"]);
|
||||
|
||||
if ($xt->type == Activity\ObjectType::NOTE) {
|
||||
$item_tag = Item::selectFirst(['id', 'tag'], ['uri' => $xt->id, 'uid' => $importer["importer_uid"]]);
|
||||
$item_tag = Item::selectFirst(['id', 'uri-id', 'tag'], ['uri' => $xt->id, 'uid' => $importer["importer_uid"]]);
|
||||
|
||||
if (!DBA::isResult($item_tag)) {
|
||||
Logger::log("Query failed to execute, no result returned in " . __FUNCTION__);
|
||||
|
|
@ -2253,10 +2226,7 @@ class DFRN
|
|||
|
||||
// extract tag, if not duplicate, add to parent item
|
||||
if ($xo->content) {
|
||||
if (!stristr($item_tag["tag"], trim($xo->content))) {
|
||||
$tag = $item_tag["tag"] . (strlen($item_tag["tag"]) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]';
|
||||
Item::update(['tag' => $tag], ['id' => $item_tag["id"]]);
|
||||
}
|
||||
Tag::store($item_tag['uri-id'], Tag::HASHTAG, $xo->content);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2407,10 +2377,18 @@ class DFRN
|
|||
|
||||
$item["guid"] = XML::getFirstNodeValue($xpath, "dfrn:diaspora_guid/text()", $entry);
|
||||
|
||||
$item['uri-id'] = ItemURI::insert(['uri' => $item['uri'], 'guid' => $item['guid']]);
|
||||
|
||||
Tag::storeFromBody($item['uri-id'], $item["body"]);
|
||||
|
||||
// We store the data from "dfrn:diaspora_signature" in a different table, this is done in "Item::insert"
|
||||
$dsprsig = XML::unescape(XML::getFirstNodeValue($xpath, "dfrn:diaspora_signature/text()", $entry));
|
||||
if ($dsprsig != "") {
|
||||
$item["dsprsig"] = $dsprsig;
|
||||
$signature = json_decode(base64_decode($dsprsig));
|
||||
// We don't store the old style signatures anymore that also contained the "signature" and "signer"
|
||||
if (!empty($signature->signed_text) && empty($signature->signature) && empty($signature->signer)) {
|
||||
$item["diaspora_signed_text"] = $signature->signed_text;
|
||||
}
|
||||
}
|
||||
|
||||
$item["verb"] = XML::getFirstNodeValue($xpath, "activity:verb/text()", $entry);
|
||||
|
|
@ -2423,7 +2401,7 @@ class DFRN
|
|||
$item["object"] = self::transformActivity($xpath, $object, "object");
|
||||
|
||||
if (trim($item["object"]) != "") {
|
||||
$r = XML::parseString($item["object"], false);
|
||||
$r = XML::parseString($item["object"]);
|
||||
if (isset($r->type)) {
|
||||
$item["object-type"] = $r->type;
|
||||
}
|
||||
|
|
@ -2450,16 +2428,9 @@ class DFRN
|
|||
if (($term != "") && ($scheme != "")) {
|
||||
$parts = explode(":", $scheme);
|
||||
if ((count($parts) >= 4) && (array_shift($parts) == "X-DFRN")) {
|
||||
$termhash = array_shift($parts);
|
||||
$termurl = implode(":", $parts);
|
||||
|
||||
if (!empty($item["tag"])) {
|
||||
$item["tag"] .= ",";
|
||||
} else {
|
||||
$item["tag"] = "";
|
||||
}
|
||||
|
||||
$item["tag"] .= $termhash . "[url=" . $termurl . "]" . $term . "[/url]";
|
||||
$termurl = array_pop($parts);
|
||||
$termurl = array_pop($parts) . $termurl;
|
||||
Tag::store($item['uri-id'], Tag::IMPLICIT_MENTION, $term, $termurl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2618,7 +2589,7 @@ class DFRN
|
|||
// Turn this into a wall post.
|
||||
$notify = Item::isRemoteSelf($importer, $item);
|
||||
|
||||
$posted_id = Item::insert($item, false, $notify);
|
||||
$posted_id = Item::insert($item, $notify);
|
||||
|
||||
if ($notify) {
|
||||
$posted_id = $notify;
|
||||
|
|
@ -2663,7 +2634,7 @@ class DFRN
|
|||
}
|
||||
|
||||
$condition = ['uri' => $uri, 'uid' => $importer["importer_uid"]];
|
||||
$item = Item::selectFirst(['id', 'parent', 'contact-id', 'file', 'deleted'], $condition);
|
||||
$item = Item::selectFirst(['id', 'parent', 'contact-id', 'file', 'deleted', 'gravity'], $condition);
|
||||
if (!DBA::isResult($item)) {
|
||||
Logger::log("Item with uri " . $uri . " for user " . $importer["importer_uid"] . " wasn't found.", Logger::DEBUG);
|
||||
return;
|
||||
|
|
@ -2675,13 +2646,13 @@ class DFRN
|
|||
}
|
||||
|
||||
// When it is a starting post it has to belong to the person that wants to delete it
|
||||
if (($item['id'] == $item['parent']) && ($item['contact-id'] != $importer["id"])) {
|
||||
if (($item['gravity'] == GRAVITY_PARENT) && ($item['contact-id'] != $importer["id"])) {
|
||||
Logger::log("Item with uri " . $uri . " don't belong to contact " . $importer["id"] . " - ignoring deletion.", Logger::DEBUG);
|
||||
return;
|
||||
}
|
||||
|
||||
// Comments can be deleted by the thread owner or comment owner
|
||||
if (($item['id'] != $item['parent']) && ($item['contact-id'] != $importer["id"])) {
|
||||
if (($item['gravity'] != GRAVITY_PARENT) && ($item['contact-id'] != $importer["id"])) {
|
||||
$condition = ['id' => $item['parent'], 'contact-id' => $importer["id"]];
|
||||
if (!Item::exists($condition)) {
|
||||
Logger::log("Item with uri " . $uri . " wasn't found or mustn't be deleted by contact " . $importer["id"] . " - ignoring deletion.", Logger::DEBUG);
|
||||
|
|
|
|||
|
|
@ -35,9 +35,10 @@ use Friendica\Model\Contact;
|
|||
use Friendica\Model\Conversation;
|
||||
use Friendica\Model\GContact;
|
||||
use Friendica\Model\Item;
|
||||
use Friendica\Model\ItemDeliveryData;
|
||||
use Friendica\Model\ItemURI;
|
||||
use Friendica\Model\Mail;
|
||||
use Friendica\Model\Profile;
|
||||
use Friendica\Model\Post;
|
||||
use Friendica\Model\Tag;
|
||||
use Friendica\Model\User;
|
||||
use Friendica\Network\Probe;
|
||||
use Friendica\Util\Crypto;
|
||||
|
|
@ -111,7 +112,7 @@ class Diaspora
|
|||
|
||||
if (DI::config()->get("system", "relay_directly", false)) {
|
||||
// We distribute our stuff based on the parent to ensure that the thread will be complete
|
||||
$parent = Item::selectFirst(['parent'], ['id' => $item_id]);
|
||||
$parent = Item::selectFirst(['uri-id'], ['id' => $item_id]);
|
||||
if (!DBA::isResult($parent)) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -121,14 +122,15 @@ class Diaspora
|
|||
while ($server = DBA::fetch($servers)) {
|
||||
$serverlist[$server['url']] = $server['url'];
|
||||
}
|
||||
DBA::close($servers);
|
||||
|
||||
// All tags of the current post
|
||||
$condition = ['otype' => TERM_OBJ_POST, 'type' => TERM_HASHTAG, 'oid' => $parent['parent']];
|
||||
$tags = DBA::select('term', ['term'], $condition);
|
||||
$tags = DBA::select('tag-view', ['name'], ['uri-id' => $parent['uri-id'], 'type' => Tag::HASHTAG]);
|
||||
$taglist = [];
|
||||
while ($tag = DBA::fetch($tags)) {
|
||||
$taglist[] = $tag['term'];
|
||||
$taglist[] = $tag['name'];
|
||||
}
|
||||
DBA::close($tags);
|
||||
|
||||
// All servers who wants content with this tag
|
||||
$tagserverlist = [];
|
||||
|
|
@ -137,6 +139,7 @@ class Diaspora
|
|||
while ($server = DBA::fetch($tagserver)) {
|
||||
$tagserverlist[] = $server['gserver-id'];
|
||||
}
|
||||
DBA::close($tagserver);
|
||||
}
|
||||
|
||||
// All adresses with the given id
|
||||
|
|
@ -145,6 +148,7 @@ class Diaspora
|
|||
while ($server = DBA::fetch($servers)) {
|
||||
$serverlist[$server['url']] = $server['url'];
|
||||
}
|
||||
DBA::close($servers);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -248,34 +252,29 @@ class Diaspora
|
|||
* One of the parameters is a contact array.
|
||||
* This is done to avoid duplicates.
|
||||
*
|
||||
* @param integer $thread The id of the thread
|
||||
* @param array $contacts The previously fetched contacts
|
||||
* @param array $item Item that is about to be delivered
|
||||
* @param array $contacts The previously fetched contacts
|
||||
*
|
||||
* @return array of relay servers
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function participantsForThread($thread, array $contacts)
|
||||
public static function participantsForThread(array $item, array $contacts)
|
||||
{
|
||||
$r = DBA::p("SELECT `contact`.`batch`, `contact`.`id`, `contact`.`url`, `contact`.`name`, `contact`.`network`, `contact`.`protocol`,
|
||||
`fcontact`.`batch` AS `fbatch`, `fcontact`.`network` AS `fnetwork` FROM `participation`
|
||||
INNER JOIN `contact` ON `contact`.`id` = `participation`.`cid`
|
||||
INNER JOIN `fcontact` ON `fcontact`.`id` = `participation`.`fid`
|
||||
WHERE `participation`.`iid` = ? AND NOT `contact`.`archive`", $thread);
|
||||
if (!in_array($item['private'], [Item::PUBLIC, Item::UNLISTED]) || in_array($item["verb"], [Activity::FOLLOW, Activity::TAG])) {
|
||||
Logger::info('Item is private or a participation request. It will not be relayed', ['guid' => $item['guid'], 'private' => $item['private'], 'verb' => $item['verb']]);
|
||||
return $contacts;
|
||||
}
|
||||
|
||||
while ($contact = DBA::fetch($r)) {
|
||||
if (!empty($contact['fnetwork'])) {
|
||||
$contact['network'] = $contact['fnetwork'];
|
||||
$items = Item::select(['author-id', 'author-link', 'parent-author-link', 'parent-guid', 'guid'],
|
||||
['parent' => $item['parent'], 'gravity' => [GRAVITY_COMMENT, GRAVITY_ACTIVITY]]);
|
||||
while ($item = DBA::fetch($items)) {
|
||||
$contact = DBA::selectFirst('contact', ['id', 'url', 'name', 'protocol', 'batch', 'network'],
|
||||
['id' => $item['author-id']]);
|
||||
if (!DBA::isResult($contact) || empty($contact['batch']) ||
|
||||
($contact['network'] != Protocol::DIASPORA) ||
|
||||
Strings::compareLink($item['parent-author-link'], $item['author-link'])) {
|
||||
continue;
|
||||
}
|
||||
unset($contact['fnetwork']);
|
||||
|
||||
if (empty($contact['protocol'])) {
|
||||
$contact['protocol'] = $contact['network'];
|
||||
}
|
||||
|
||||
if (empty($contact['batch']) && !empty($contact['fbatch'])) {
|
||||
$contact['batch'] = $contact['fbatch'];
|
||||
}
|
||||
unset($contact['fbatch']);
|
||||
|
||||
$exists = false;
|
||||
foreach ($contacts as $entry) {
|
||||
|
|
@ -285,45 +284,15 @@ class Diaspora
|
|||
}
|
||||
|
||||
if (!$exists) {
|
||||
Logger::info('Add participant to receiver list', ['parent' => $item['parent-guid'], 'item' => $item['guid'], 'participant' => $contact['url']]);
|
||||
$contacts[] = $contact;
|
||||
}
|
||||
}
|
||||
DBA::close($r);
|
||||
DBA::close($items);
|
||||
|
||||
return $contacts;
|
||||
}
|
||||
|
||||
/**
|
||||
* repairs a signature that was double encoded
|
||||
*
|
||||
* The function is unused at the moment. It was copied from the old implementation.
|
||||
*
|
||||
* @param string $signature The signature
|
||||
* @param string $handle The handle of the signature owner
|
||||
* @param integer $level This value is only set inside this function to avoid endless loops
|
||||
*
|
||||
* @return string the repaired signature
|
||||
* @throws \Exception
|
||||
*/
|
||||
private static function repairSignature($signature, $handle = "", $level = 1)
|
||||
{
|
||||
if ($signature == "") {
|
||||
return ($signature);
|
||||
}
|
||||
|
||||
if (base64_encode(base64_decode(base64_decode($signature))) == base64_decode($signature)) {
|
||||
$signature = base64_decode($signature);
|
||||
Logger::log("Repaired double encoded signature from Diaspora/Hubzilla handle ".$handle." - level ".$level, Logger::DEBUG);
|
||||
|
||||
// Do a recursive call to be able to fix even multiple levels
|
||||
if ($level < 10) {
|
||||
$signature = self::repairSignature($signature, $handle, ++$level);
|
||||
}
|
||||
}
|
||||
|
||||
return($signature);
|
||||
}
|
||||
|
||||
/**
|
||||
* verify the envelope and return the verified data
|
||||
*
|
||||
|
|
@ -335,7 +304,7 @@ class Diaspora
|
|||
*/
|
||||
private static function verifyMagicEnvelope($envelope)
|
||||
{
|
||||
$basedom = XML::parseString($envelope);
|
||||
$basedom = XML::parseString($envelope, true);
|
||||
|
||||
if (!is_object($basedom)) {
|
||||
Logger::log("Envelope is no XML file");
|
||||
|
|
@ -461,7 +430,7 @@ class Diaspora
|
|||
$xml = $raw;
|
||||
}
|
||||
|
||||
$basedom = XML::parseString($xml);
|
||||
$basedom = XML::parseString($xml, true);
|
||||
|
||||
if (!is_object($basedom)) {
|
||||
Logger::log('Received data does not seem to be an XML. Discarding. '.$xml);
|
||||
|
|
@ -542,7 +511,7 @@ class Diaspora
|
|||
$basedom = XML::parseString($xml);
|
||||
|
||||
if (!is_object($basedom)) {
|
||||
Logger::log("XML is not parseable.");
|
||||
Logger::notice('XML is not parseable.');
|
||||
return false;
|
||||
}
|
||||
$children = $basedom->children('https://joindiaspora.com/protocol');
|
||||
|
|
@ -556,7 +525,7 @@ class Diaspora
|
|||
} else {
|
||||
// This happens with posts from a relais
|
||||
if (empty($privKey)) {
|
||||
Logger::log("This is no private post in the old format", Logger::DEBUG);
|
||||
Logger::info('This is no private post in the old format');
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -575,7 +544,7 @@ class Diaspora
|
|||
|
||||
$decrypted = self::aesDecrypt($outer_key, $outer_iv, $ciphertext);
|
||||
|
||||
Logger::log('decrypted: '.$decrypted, Logger::DEBUG);
|
||||
Logger::info('decrypted', ['data' => $decrypted]);
|
||||
$idom = XML::parseString($decrypted);
|
||||
|
||||
$inner_iv = base64_decode($idom->iv);
|
||||
|
|
@ -724,7 +693,7 @@ class Diaspora
|
|||
|
||||
$type = $fields->getName();
|
||||
|
||||
Logger::log("Received message type ".$type." from ".$sender." for user ".$importer["uid"], Logger::DEBUG);
|
||||
Logger::info('Received message', ['type' => $type, 'sender' => $sender, 'user' => $importer["uid"]]);
|
||||
|
||||
switch ($type) {
|
||||
case "account_migration":
|
||||
|
|
@ -816,7 +785,7 @@ class Diaspora
|
|||
$data = XML::parseString($msg["message"]);
|
||||
|
||||
if (!is_object($data)) {
|
||||
Logger::log("No valid XML ".$msg["message"], Logger::DEBUG);
|
||||
Logger::info('No valid XML', ['message' => $msg['message']]);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -928,7 +897,7 @@ class Diaspora
|
|||
if (isset($parent_author_signature)) {
|
||||
$key = self::key($msg["author"]);
|
||||
if (empty($key)) {
|
||||
Logger::log("No key found for parent author ".$msg["author"], Logger::DEBUG);
|
||||
Logger::info('No key found for parent', ['author' => $msg["author"]]);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -940,7 +909,7 @@ class Diaspora
|
|||
|
||||
$key = self::key($fields->author);
|
||||
if (empty($key)) {
|
||||
Logger::log("No key found for author ".$fields->author, Logger::DEBUG);
|
||||
Logger::info('No key found', ['author' => $fields->author]);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -994,7 +963,7 @@ class Diaspora
|
|||
}
|
||||
|
||||
if (DBA::isResult($person)) {
|
||||
Logger::debug("In cache " . print_r($person, true));
|
||||
Logger::debug('In cache', ['person' => $person]);
|
||||
|
||||
if (is_null($update)) {
|
||||
// update record occasionally so it doesn't get stale
|
||||
|
|
@ -1108,7 +1077,7 @@ class Diaspora
|
|||
*/
|
||||
public static function urlFromContactGuid($fcontact_guid)
|
||||
{
|
||||
Logger::log("fcontact guid is ".$fcontact_guid, Logger::DEBUG);
|
||||
Logger::info('fcontact', ['guid' => $fcontact_guid]);
|
||||
|
||||
$r = q(
|
||||
"SELECT `url` FROM `fcontact` WHERE `url` != '' AND `network` = '%s' AND `guid` = '%s'",
|
||||
|
|
@ -1517,7 +1486,7 @@ class Diaspora
|
|||
private static function parentItem($uid, $guid, $author, array $contact)
|
||||
{
|
||||
$fields = ['id', 'parent', 'body', 'wall', 'uri', 'guid', 'private', 'origin',
|
||||
'author-name', 'author-link', 'author-avatar',
|
||||
'author-name', 'author-link', 'author-avatar', 'gravity',
|
||||
'owner-name', 'owner-link', 'owner-avatar'];
|
||||
$condition = ['uid' => $uid, 'guid' => $guid];
|
||||
$item = Item::selectFirst($fields, $condition);
|
||||
|
|
@ -1730,6 +1699,7 @@ class Diaspora
|
|||
while ($contact = DBA::fetch($contacts)) {
|
||||
Contact::remove($contact["id"]);
|
||||
}
|
||||
DBA::close($contacts);
|
||||
|
||||
DBA::delete('gcontact', ['addr' => $author]);
|
||||
|
||||
|
|
@ -1807,6 +1777,40 @@ class Diaspora
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the mentions in the tag table
|
||||
*
|
||||
* @param integer $uriid
|
||||
* @param string $text
|
||||
*/
|
||||
private static function storeMentions(int $uriid, string $text)
|
||||
{
|
||||
preg_match_all('/([@!]){(?:([^}]+?); ?)?([^} ]+)}/', $text, $matches, PREG_SET_ORDER);
|
||||
if (empty($matches)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Matching values for the preg match
|
||||
* [1] = mention type (@ or !)
|
||||
* [2] = name (optional)
|
||||
* [3] = profile URL
|
||||
*/
|
||||
|
||||
foreach ($matches as $match) {
|
||||
if (empty($match)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$person = self::personByHandle($match[3]);
|
||||
if (empty($person)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Tag::storeByHash($uriid, $match[1], $person['name'] ?: $person['nick'], $person['url']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes an incoming comment
|
||||
*
|
||||
|
|
@ -1877,6 +1881,7 @@ class Diaspora
|
|||
|
||||
$datarray["guid"] = $guid;
|
||||
$datarray["uri"] = self::getUriFromGuid($author, $guid);
|
||||
$datarray['uri-id'] = ItemURI::insert(['uri' => $datarray['uri'], 'guid' => $datarray['guid']]);
|
||||
|
||||
$datarray["verb"] = Activity::POST;
|
||||
$datarray["gravity"] = GRAVITY_COMMENT;
|
||||
|
|
@ -1899,6 +1904,9 @@ class Diaspora
|
|||
|
||||
$datarray["body"] = self::replacePeopleGuid($body, $person["url"]);
|
||||
|
||||
self::storeMentions($datarray['uri-id'], $text);
|
||||
Tag::storeRawTagsFromBody($datarray['uri-id'], $datarray["body"]);
|
||||
|
||||
self::fetchGuid($datarray);
|
||||
|
||||
// If we are the origin of the parent we store the original data.
|
||||
|
|
@ -2125,8 +2133,8 @@ class Diaspora
|
|||
$datarray["changed"] = $datarray["created"] = $datarray["edited"] = DateTimeFormat::utcNow();
|
||||
|
||||
// like on comments have the comment as parent. So we need to fetch the toplevel parent
|
||||
if ($parent_item["id"] != $parent_item["parent"]) {
|
||||
$toplevel = Item::selectFirst(['origin'], ['id' => $parent_item["parent"]]);
|
||||
if ($parent_item['gravity'] != GRAVITY_PARENT) {
|
||||
$toplevel = Item::selectFirst(['origin'], ['id' => $parent_item['parent']]);
|
||||
$origin = $toplevel["origin"];
|
||||
} else {
|
||||
$origin = $parent_item["origin"];
|
||||
|
|
@ -2221,18 +2229,36 @@ class Diaspora
|
|||
* @param array $importer Array of the importer user
|
||||
* @param object $data The message object
|
||||
*
|
||||
* @return bool always true
|
||||
* @return bool success
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
private static function receiveParticipation(array $importer, $data)
|
||||
{
|
||||
$author = strtolower(Strings::escapeTags(XML::unescape($data->author)));
|
||||
$guid = Strings::escapeTags(XML::unescape($data->guid));
|
||||
$parent_guid = Strings::escapeTags(XML::unescape($data->parent_guid));
|
||||
|
||||
$contact_id = Contact::getIdForURL($author);
|
||||
if (!$contact_id) {
|
||||
Logger::log('Contact not found: '.$author);
|
||||
$contact = self::allowedContactByHandle($importer, $author, true);
|
||||
if (!$contact) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (self::messageExists($importer["uid"], $guid)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$parent_item = self::parentItem($importer["uid"], $parent_guid, $author, $contact);
|
||||
if (!$parent_item) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!$parent_item['origin']) {
|
||||
Logger::info('Not our origin. Participation is ignored', ['parent_guid' => $parent_guid, 'guid' => $guid, 'author' => $author]);
|
||||
}
|
||||
|
||||
if (!in_array($parent_item['private'], [Item::PUBLIC, Item::UNLISTED])) {
|
||||
Logger::info('Item is not public, participation is ignored', ['parent_guid' => $parent_guid, 'guid' => $guid, 'author' => $author]);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -2242,36 +2268,60 @@ class Diaspora
|
|||
return false;
|
||||
}
|
||||
|
||||
$item = Item::selectFirst(['id'], ['guid' => $parent_guid, 'origin' => true, 'private' => [Item::PUBLIC, Item::UNLISTED]]);
|
||||
if (!DBA::isResult($item)) {
|
||||
Logger::log('Item not found, no origin or private: '.$parent_guid);
|
||||
return false;
|
||||
}
|
||||
$author_contact = self::authorContactByUrl($contact, $person, $importer["uid"]);
|
||||
|
||||
$author_parts = explode('@', $author);
|
||||
if (isset($author_parts[1])) {
|
||||
$server = $author_parts[1];
|
||||
} else {
|
||||
// Should never happen
|
||||
$server = $author;
|
||||
}
|
||||
// Store participation
|
||||
$datarray = [];
|
||||
|
||||
Logger::log('Received participation for ID: '.$item['id'].' - Contact: '.$contact_id.' - Server: '.$server, Logger::DEBUG);
|
||||
$datarray["protocol"] = Conversation::PARCEL_DIASPORA;
|
||||
|
||||
if (!DBA::exists('participation', ['iid' => $item['id'], 'server' => $server])) {
|
||||
DBA::insert('participation', ['iid' => $item['id'], 'cid' => $contact_id, 'fid' => $person['id'], 'server' => $server]);
|
||||
}
|
||||
$datarray["uid"] = $importer["uid"];
|
||||
$datarray["contact-id"] = $author_contact["cid"];
|
||||
$datarray["network"] = $author_contact["network"];
|
||||
|
||||
$datarray["owner-link"] = $datarray["author-link"] = $person["url"];
|
||||
$datarray["owner-id"] = $datarray["author-id"] = Contact::getIdForURL($person["url"], 0);
|
||||
|
||||
$datarray["guid"] = $guid;
|
||||
$datarray["uri"] = self::getUriFromGuid($author, $guid);
|
||||
|
||||
$datarray["verb"] = Activity::FOLLOW;
|
||||
$datarray["gravity"] = GRAVITY_ACTIVITY;
|
||||
$datarray["parent-uri"] = $parent_item["uri"];
|
||||
|
||||
$datarray["object-type"] = Activity\ObjectType::NOTE;
|
||||
|
||||
$datarray["body"] = Activity::FOLLOW;
|
||||
|
||||
// Diaspora doesn't provide a date for a participation
|
||||
$datarray["changed"] = $datarray["created"] = $datarray["edited"] = DateTimeFormat::utcNow();
|
||||
|
||||
$message_id = Item::insert($datarray);
|
||||
|
||||
Logger::info('Participation stored', ['id' => $message_id, 'guid' => $guid, 'parent_guid' => $parent_guid, 'author' => $author]);
|
||||
|
||||
// Send all existing comments and likes to the requesting server
|
||||
$comments = Item::select(['id', 'parent', 'verb', 'self'], ['parent' => $item['id']]);
|
||||
$comments = Item::select(['id', 'uri-id', 'parent-author-network', 'author-network', 'verb'],
|
||||
['parent' => $parent_item['id'], 'gravity' => [GRAVITY_COMMENT, GRAVITY_ACTIVITY]]);
|
||||
while ($comment = Item::fetch($comments)) {
|
||||
if ($comment['id'] == $comment['parent']) {
|
||||
if (in_array($comment['verb'], [Activity::FOLLOW, Activity::TAG])) {
|
||||
Logger::info('participation messages are not relayed', ['item' => $comment['id']]);
|
||||
continue;
|
||||
}
|
||||
|
||||
Logger::info('Deliver participation', ['item' => $comment['id'], 'contact' => $contact_id]);
|
||||
if (Worker::add(PRIORITY_HIGH, 'Delivery', Delivery::POST, $comment['id'], $contact_id)) {
|
||||
ItemDeliveryData::incrementQueueCount($comment['id'], 1);
|
||||
if ($comment['author-network'] == Protocol::ACTIVITYPUB) {
|
||||
Logger::info('Comments from ActivityPub authors are not relayed', ['item' => $comment['id']]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($comment['parent-author-network'] == Protocol::ACTIVITYPUB) {
|
||||
Logger::info('Comments to comments from ActivityPub authors are not relayed', ['item' => $comment['id']]);
|
||||
continue;
|
||||
}
|
||||
|
||||
Logger::info('Deliver participation', ['item' => $comment['id'], 'contact' => $author_contact["cid"]]);
|
||||
if (Worker::add(PRIORITY_HIGH, 'Delivery', Delivery::POST, $comment['id'], $author_contact["cid"])) {
|
||||
Post\DeliveryData::incrementQueueCount($comment['uri-id'], 1);
|
||||
}
|
||||
}
|
||||
DBA::close($comments);
|
||||
|
|
@ -2552,8 +2602,8 @@ class Diaspora
|
|||
}
|
||||
|
||||
// Do we already have this item?
|
||||
$fields = ['body', 'title', 'attach', 'tag', 'app', 'created', 'object-type', 'uri', 'guid',
|
||||
'author-name', 'author-link', 'author-avatar'];
|
||||
$fields = ['body', 'title', 'attach', 'app', 'created', 'object-type', 'uri', 'guid',
|
||||
'author-name', 'author-link', 'author-avatar', 'plink'];
|
||||
$condition = ['guid' => $guid, 'visible' => true, 'deleted' => false, 'private' => [Item::PUBLIC, Item::UNLISTED]];
|
||||
$item = Item::selectFirst($fields, $condition);
|
||||
|
||||
|
|
@ -2596,8 +2646,8 @@ class Diaspora
|
|||
}
|
||||
|
||||
if ($stored) {
|
||||
$fields = ['body', 'title', 'attach', 'tag', 'app', 'created', 'object-type', 'uri', 'guid',
|
||||
'author-name', 'author-link', 'author-avatar'];
|
||||
$fields = ['body', 'title', 'attach', 'app', 'created', 'object-type', 'uri', 'guid',
|
||||
'author-name', 'author-link', 'author-avatar', 'plink'];
|
||||
$condition = ['guid' => $guid, 'visible' => true, 'deleted' => false, 'private' => [Item::PUBLIC, Item::UNLISTED]];
|
||||
$item = Item::selectFirst($fields, $condition);
|
||||
|
||||
|
|
@ -2699,8 +2749,6 @@ class Diaspora
|
|||
return false;
|
||||
}
|
||||
|
||||
$orig_url = DI::baseUrl()."/display/".$original_item["guid"];
|
||||
|
||||
$datarray = [];
|
||||
|
||||
$datarray["uid"] = $importer["uid"];
|
||||
|
|
@ -2715,6 +2763,7 @@ class Diaspora
|
|||
|
||||
$datarray["guid"] = $guid;
|
||||
$datarray["uri"] = $datarray["parent-uri"] = self::getUriFromGuid($author, $guid);
|
||||
$datarray['uri-id'] = ItemURI::insert(['uri' => $datarray['uri'], 'guid' => $datarray['guid']]);
|
||||
|
||||
$datarray["verb"] = Activity::POST;
|
||||
$datarray["gravity"] = GRAVITY_PARENT;
|
||||
|
|
@ -2722,13 +2771,15 @@ class Diaspora
|
|||
$datarray["protocol"] = Conversation::PARCEL_DIASPORA;
|
||||
$datarray["source"] = $xml;
|
||||
|
||||
$prefix = share_header(
|
||||
/// @todo Copy tag data from original post
|
||||
|
||||
$prefix = BBCode::getShareOpeningTag(
|
||||
$original_item["author-name"],
|
||||
$original_item["author-link"],
|
||||
$original_item["author-avatar"],
|
||||
$original_item["guid"],
|
||||
$original_item["plink"],
|
||||
$original_item["created"],
|
||||
$orig_url
|
||||
$original_item["guid"]
|
||||
);
|
||||
|
||||
if (!empty($original_item['title'])) {
|
||||
|
|
@ -2737,7 +2788,8 @@ class Diaspora
|
|||
|
||||
$datarray["body"] = $prefix.$original_item["body"]."[/share]";
|
||||
|
||||
$datarray["tag"] = $original_item["tag"];
|
||||
Tag::storeFromBody($datarray['uri-id'], $datarray["body"]);
|
||||
|
||||
$datarray["attach"] = $original_item["attach"];
|
||||
$datarray["app"] = $original_item["app"];
|
||||
|
||||
|
|
@ -2817,7 +2869,7 @@ class Diaspora
|
|||
}
|
||||
|
||||
// Fetch the parent item
|
||||
$parent = Item::selectFirst(['author-link'], ['id' => $item["parent"]]);
|
||||
$parent = Item::selectFirst(['author-link'], ['id' => $item['parent']]);
|
||||
|
||||
// Only delete it if the parent author really fits
|
||||
if (!Strings::compareLink($parent["author-link"], $contact["url"]) && !Strings::compareLink($item["author-link"], $contact["url"])) {
|
||||
|
|
@ -2827,7 +2879,7 @@ class Diaspora
|
|||
|
||||
Item::markForDeletion(['id' => $item['id']]);
|
||||
|
||||
Logger::log("Deleted target ".$target_guid." (".$item["id"].") from user ".$item["uid"]." parent: ".$item["parent"], Logger::DEBUG);
|
||||
Logger::log("Deleted target ".$target_guid." (".$item["id"].") from user ".$item["uid"]." parent: ".$item['parent'], Logger::DEBUG);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
|
@ -2958,6 +3010,7 @@ class Diaspora
|
|||
|
||||
$datarray["guid"] = $guid;
|
||||
$datarray["uri"] = $datarray["parent-uri"] = self::getUriFromGuid($author, $guid);
|
||||
$datarray['uri-id'] = ItemURI::insert(['uri' => $datarray['uri'], 'guid' => $datarray['guid']]);
|
||||
|
||||
$datarray["verb"] = Activity::POST;
|
||||
$datarray["gravity"] = GRAVITY_PARENT;
|
||||
|
|
@ -2967,6 +3020,9 @@ class Diaspora
|
|||
|
||||
$datarray["body"] = self::replacePeopleGuid($body, $contact["url"]);
|
||||
|
||||
self::storeMentions($datarray['uri-id'], $text);
|
||||
Tag::storeRawTagsFromBody($datarray['uri-id'], $datarray["body"]);
|
||||
|
||||
if ($provider_display_name != "") {
|
||||
$datarray["app"] = $provider_display_name;
|
||||
}
|
||||
|
|
@ -3061,7 +3117,9 @@ class Diaspora
|
|||
$json = json_encode(["iv" => $b_iv, "key" => $b_aes_key]);
|
||||
|
||||
$encrypted_key_bundle = "";
|
||||
openssl_public_encrypt($json, $encrypted_key_bundle, $pubkey);
|
||||
if (!@openssl_public_encrypt($json, $encrypted_key_bundle, $pubkey)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$json_object = json_encode(
|
||||
["aes_key" => base64_encode($encrypted_key_bundle),
|
||||
|
|
@ -3336,7 +3394,7 @@ class Diaspora
|
|||
"profile" => $profile,
|
||||
"signature" => $signature];
|
||||
|
||||
Logger::log("Send account migration ".print_r($message, true), Logger::DEBUG);
|
||||
Logger::info('Send account migration', ['msg' => $message]);
|
||||
|
||||
return self::buildAndTransmit($owner, $contact, "account_migration", $message);
|
||||
}
|
||||
|
|
@ -3380,7 +3438,7 @@ class Diaspora
|
|||
"following" => "true",
|
||||
"sharing" => "true"];
|
||||
|
||||
Logger::log("Send share ".print_r($message, true), Logger::DEBUG);
|
||||
Logger::info('Send share', ['msg' => $message]);
|
||||
|
||||
return self::buildAndTransmit($owner, $contact, "contact", $message);
|
||||
}
|
||||
|
|
@ -3401,7 +3459,7 @@ class Diaspora
|
|||
"following" => "false",
|
||||
"sharing" => "false"];
|
||||
|
||||
Logger::log("Send unshare ".print_r($message, true), Logger::DEBUG);
|
||||
Logger::info('Send unshare', ['msg' => $message]);
|
||||
|
||||
return self::buildAndTransmit($owner, $contact, "contact", $message);
|
||||
}
|
||||
|
|
@ -3596,8 +3654,8 @@ class Diaspora
|
|||
|
||||
if ($item['author-link'] != $item['owner-link']) {
|
||||
require_once 'mod/share.php';
|
||||
$body = share_header($item['author-name'], $item['author-link'], $item['author-avatar'],
|
||||
"", $item['created'], $item['plink']) . $body . '[/share]';
|
||||
$body = BBCode::getShareOpeningTag($item['author-name'], $item['author-link'], $item['author-avatar'],
|
||||
$item['plink'], $item['created']) . $body . '[/share]';
|
||||
}
|
||||
|
||||
// convert to markdown
|
||||
|
|
@ -3790,9 +3848,9 @@ class Diaspora
|
|||
return $result;
|
||||
}
|
||||
|
||||
$toplevel_item = Item::selectFirst(['guid', 'author-id', 'author-link'], ['id' => $item["parent"], 'parent' => $item["parent"]]);
|
||||
$toplevel_item = Item::selectFirst(['guid', 'author-id', 'author-link'], ['id' => $item['parent'], 'parent' => $item['parent']]);
|
||||
if (!DBA::isResult($toplevel_item)) {
|
||||
Logger::error('Missing parent conversation item', ['parent' => $item["parent"]]);
|
||||
Logger::error('Missing parent conversation item', ['parent' => $item['parent']]);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -3941,35 +3999,29 @@ class Diaspora
|
|||
|
||||
Logger::log("Got relayable data ".$type." for item ".$item["guid"]." (".$item["id"].")", Logger::DEBUG);
|
||||
|
||||
// Old way - is used by the internal Friendica functions
|
||||
/// @todo Change all signatur storing functions to the new format
|
||||
if ($item['signed_text'] && $item['signature'] && $item['signer']) {
|
||||
$message = self::messageFromSignature($item);
|
||||
} else {// New way
|
||||
$msg = json_decode($item['signed_text'], true);
|
||||
$msg = json_decode($item['signed_text'], true);
|
||||
|
||||
$message = [];
|
||||
if (is_array($msg)) {
|
||||
foreach ($msg as $field => $data) {
|
||||
if (!$item["deleted"]) {
|
||||
if ($field == "diaspora_handle") {
|
||||
$field = "author";
|
||||
}
|
||||
if ($field == "target_type") {
|
||||
$field = "parent_type";
|
||||
}
|
||||
$message = [];
|
||||
if (is_array($msg)) {
|
||||
foreach ($msg as $field => $data) {
|
||||
if (!$item["deleted"]) {
|
||||
if ($field == "diaspora_handle") {
|
||||
$field = "author";
|
||||
}
|
||||
if ($field == "target_type") {
|
||||
$field = "parent_type";
|
||||
}
|
||||
|
||||
$message[$field] = $data;
|
||||
}
|
||||
} else {
|
||||
Logger::log("Signature text for item ".$item["guid"]." (".$item["id"].") couldn't be extracted: ".$item['signed_text'], Logger::DEBUG);
|
||||
|
||||
$message[$field] = $data;
|
||||
}
|
||||
} else {
|
||||
Logger::log("Signature text for item ".$item["guid"]." (".$item["id"].") couldn't be extracted: ".$item['signed_text'], Logger::DEBUG);
|
||||
}
|
||||
|
||||
$message["parent_author_signature"] = self::signature($owner, $message);
|
||||
|
||||
Logger::log("Relayed data ".print_r($message, true), Logger::DEBUG);
|
||||
Logger::info('Relayed data', ['msg' => $message]);
|
||||
|
||||
return self::buildAndTransmit($owner, $contact, $type, $message, $public_batch, $item["guid"]);
|
||||
}
|
||||
|
|
@ -3992,7 +4044,7 @@ class Diaspora
|
|||
|
||||
$msg_type = "retraction";
|
||||
|
||||
if ($item['id'] == $item['parent']) {
|
||||
if ($item['gravity'] == GRAVITY_PARENT) {
|
||||
$target_type = "Post";
|
||||
} elseif (in_array($item["verb"], [Activity::LIKE, Activity::DISLIKE])) {
|
||||
$target_type = "Like";
|
||||
|
|
@ -4004,7 +4056,7 @@ class Diaspora
|
|||
"target_guid" => $item['guid'],
|
||||
"target_type" => $target_type];
|
||||
|
||||
Logger::log("Got message ".print_r($message, true), Logger::DEBUG);
|
||||
Logger::info('Got message', ['msg' => $message]);
|
||||
|
||||
return self::buildAndTransmit($owner, $contact, $msg_type, $message, $public_batch, $item["guid"]);
|
||||
}
|
||||
|
|
@ -4126,20 +4178,11 @@ class Diaspora
|
|||
*/
|
||||
private static function createProfileData($uid)
|
||||
{
|
||||
$r = q(
|
||||
"SELECT `profile`.`uid` AS `profile_uid`, `profile`.* , `user`.*, `user`.`prvkey` AS `uprvkey`, `contact`.`addr`
|
||||
FROM `profile`
|
||||
INNER JOIN `user` ON `profile`.`uid` = `user`.`uid`
|
||||
INNER JOIN `contact` ON `profile`.`uid` = `contact`.`uid`
|
||||
WHERE `user`.`uid` = %d AND `contact`.`self` LIMIT 1",
|
||||
intval($uid)
|
||||
);
|
||||
|
||||
if (!$r) {
|
||||
$profile = DBA::selectFirst('owner-view', ['uid', 'addr', 'name', 'location', 'net-publish', 'dob', 'about', 'pub_keywords'], ['uid' => $uid]);
|
||||
if (!DBA::isResult($profile)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$profile = $r[0];
|
||||
$handle = $profile["addr"];
|
||||
|
||||
$split_name = self::splitName($profile['name']);
|
||||
|
|
@ -4168,7 +4211,7 @@ class Diaspora
|
|||
|
||||
$about = BBCode::toMarkdown($profile['about']);
|
||||
|
||||
$location = Profile::formatLocation($profile);
|
||||
$location = $profile['location'];
|
||||
$tags = '';
|
||||
if ($profile['pub_keywords']) {
|
||||
$kw = str_replace(',', ' ', $profile['pub_keywords']);
|
||||
|
|
@ -4254,7 +4297,7 @@ class Diaspora
|
|||
{
|
||||
$owner = User::getOwnerDataById($uid);
|
||||
if (empty($owner)) {
|
||||
Logger::log("No owner post, so not storing signature", Logger::DEBUG);
|
||||
Logger::info('No owner post, so not storing signature');
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -4285,7 +4328,7 @@ class Diaspora
|
|||
{
|
||||
$owner = User::getOwnerDataById($uid);
|
||||
if (empty($owner)) {
|
||||
Logger::log("No owner post, so not storing signature", Logger::DEBUG);
|
||||
Logger::info('No owner post, so not storing signature');
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ use Friendica\Core\Protocol;
|
|||
use Friendica\Database\DBA;
|
||||
use Friendica\DI;
|
||||
use Friendica\Model\Item;
|
||||
use Friendica\Model\Tag;
|
||||
use Friendica\Util\Network;
|
||||
use Friendica\Util\ParseUrl;
|
||||
use Friendica\Util\XML;
|
||||
|
|
@ -36,7 +37,67 @@ use Friendica\Util\XML;
|
|||
/**
|
||||
* This class contain functions to import feeds (RSS/RDF/Atom)
|
||||
*/
|
||||
class Feed {
|
||||
class Feed
|
||||
{
|
||||
/**
|
||||
* consume - 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
|
||||
*/
|
||||
public static function consume($xml, array $importer, array $contact, &$hub)
|
||||
{
|
||||
if ($contact['network'] === Protocol::OSTATUS) {
|
||||
Logger::info('Consume OStatus messages');
|
||||
OStatus::import($xml, $importer, $contact, $hub);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($contact['network'] === Protocol::FEED) {
|
||||
Logger::info('Consume feeds');
|
||||
self::import($xml, $importer, $contact);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($contact['network'] === Protocol::DFRN) {
|
||||
Logger::info('Consume DFRN messages');
|
||||
$dfrn_importer = DFRN::getImporter($contact['id'], $importer['uid']);
|
||||
if (!empty($dfrn_importer)) {
|
||||
Logger::info('Now import the DFRN feed');
|
||||
DFRN::import($xml, $dfrn_importer, true);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a RSS/RDF/Atom feed and create an item entry for it
|
||||
*
|
||||
|
|
@ -276,6 +337,10 @@ class Feed {
|
|||
$item["uri"] = $item["plink"];
|
||||
}
|
||||
|
||||
// Add the base path if missing
|
||||
$item["uri"] = Network::addBasePath($item["uri"], $basepath);
|
||||
$item["plink"] = Network::addBasePath($item["plink"], $basepath);
|
||||
|
||||
$orig_plink = $item["plink"];
|
||||
|
||||
$item["plink"] = Network::finalUrl($item["plink"]);
|
||||
|
|
@ -384,16 +449,10 @@ class Feed {
|
|||
$item["attach"] .= '[attach]href="' . $href . '" length="' . $length . '" type="' . $type . '"[/attach]';
|
||||
}
|
||||
|
||||
$tags = '';
|
||||
$taglist = [];
|
||||
$categories = $xpath->query("category", $entry);
|
||||
foreach ($categories AS $category) {
|
||||
$hashtag = $category->nodeValue;
|
||||
if ($tags != '') {
|
||||
$tags .= ', ';
|
||||
}
|
||||
|
||||
$taglink = "#[url=" . DI::baseUrl() . "/search?tag=" . $hashtag . "]" . $hashtag . "[/url]";
|
||||
$tags .= $taglink;
|
||||
$taglist[] = $category->nodeValue;
|
||||
}
|
||||
|
||||
$body = trim(XML::getFirstNodeValue($xpath, 'atom:content/text()', $entry));
|
||||
|
|
@ -473,8 +532,8 @@ class Feed {
|
|||
|
||||
// We always strip the title since it will be added in the page information
|
||||
$item["title"] = "";
|
||||
$item["body"] = $item["body"] . add_page_info($item["plink"], false, $preview, ($contact["fetch_further_information"] == 2), $contact["ffi_keyword_blacklist"]);
|
||||
$item["tag"] = add_page_keywords($item["plink"], $preview, ($contact["fetch_further_information"] == 2), $contact["ffi_keyword_blacklist"]);
|
||||
$item["body"] = $item["body"] . add_page_info($item["plink"], false, $preview, ($contact["fetch_further_information"] == 2), $contact["ffi_keyword_denylist"] ?? '');
|
||||
$taglist = get_page_keywords($item["plink"], $preview, ($contact["fetch_further_information"] == 2), $contact["ffi_keyword_denylist"]);
|
||||
$item["object-type"] = Activity\ObjectType::BOOKMARK;
|
||||
unset($item["attach"]);
|
||||
} else {
|
||||
|
|
@ -483,13 +542,12 @@ class Feed {
|
|||
}
|
||||
|
||||
if (!empty($contact["fetch_further_information"]) && ($contact["fetch_further_information"] == 3)) {
|
||||
if (!empty($tags)) {
|
||||
$item["tag"] = $tags;
|
||||
} else {
|
||||
// @todo $preview is never set in this case, is it intended? - @MrPetovan 2018-02-13
|
||||
$item["tag"] = add_page_keywords($item["plink"], $preview, true, $contact["ffi_keyword_blacklist"]);
|
||||
if (empty($taglist)) {
|
||||
$taglist = get_page_keywords($item["plink"], $preview, true, $contact["ffi_keyword_denylist"]);
|
||||
}
|
||||
$item["body"] .= "\n" . $item['tag'];
|
||||
$item["body"] .= "\n" . self::tagToString($taglist);
|
||||
} else {
|
||||
$taglist = [];
|
||||
}
|
||||
|
||||
// Add the link to the original feed entry if not present in feed
|
||||
|
|
@ -502,7 +560,7 @@ class Feed {
|
|||
$items[] = $item;
|
||||
break;
|
||||
} else {
|
||||
Logger::info("Stored feed: " . print_r($item, true));
|
||||
Logger::info('Stored feed', ['item' => $item]);
|
||||
|
||||
$notify = Item::isRemoteSelf($contact, $item);
|
||||
|
||||
|
|
@ -517,15 +575,43 @@ class Feed {
|
|||
$notify = PRIORITY_MEDIUM;
|
||||
}
|
||||
|
||||
$id = Item::insert($item, false, $notify);
|
||||
$id = Item::insert($item, $notify);
|
||||
|
||||
Logger::info("Feed for contact " . $contact["url"] . " stored under id " . $id);
|
||||
|
||||
if (!empty($id) && !empty($taglist)) {
|
||||
$feeditem = Item::selectFirst(['uri-id'], ['id' => $id]);
|
||||
foreach ($taglist as $tag) {
|
||||
Tag::store($feeditem['uri-id'], Tag::HASHTAG, $tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ["header" => $author, "items" => $items];
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a tag array to a tag string
|
||||
*
|
||||
* @param array $tags
|
||||
* @return string tag string
|
||||
*/
|
||||
private static function tagToString(array $tags)
|
||||
{
|
||||
$tagstr = '';
|
||||
|
||||
foreach ($tags as $tag) {
|
||||
if ($tagstr != "") {
|
||||
$tagstr .= ", ";
|
||||
}
|
||||
|
||||
$tagstr .= "#[url=" . DI::baseUrl() . "/search?tag=" . urlencode($tag) . "]" . $tag . "[/url]";
|
||||
}
|
||||
|
||||
return $tagstr;
|
||||
}
|
||||
|
||||
private static function titleIsBody($title, $body)
|
||||
{
|
||||
$title = strip_tags($title);
|
||||
|
|
|
|||
|
|
@ -35,6 +35,8 @@ use Friendica\Model\Contact;
|
|||
use Friendica\Model\Conversation;
|
||||
use Friendica\Model\GContact;
|
||||
use Friendica\Model\Item;
|
||||
use Friendica\Model\ItemURI;
|
||||
use Friendica\Model\Tag;
|
||||
use Friendica\Model\User;
|
||||
use Friendica\Network\Probe;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
|
|
@ -437,6 +439,7 @@ class OStatus
|
|||
$item = array_merge($header, $author);
|
||||
|
||||
$item["uri"] = XML::getFirstNodeValue($xpath, 'atom:id/text()', $entry);
|
||||
$item['uri-id'] = ItemURI::insert(['uri' => $item['uri']]);
|
||||
|
||||
$item["verb"] = XML::getFirstNodeValue($xpath, 'activity:verb/text()', $entry);
|
||||
|
||||
|
|
@ -652,14 +655,8 @@ class OStatus
|
|||
foreach ($categories as $category) {
|
||||
foreach ($category->attributes as $attributes) {
|
||||
if ($attributes->name == 'term') {
|
||||
$term = $attributes->textContent;
|
||||
if (!empty($item['tag'])) {
|
||||
$item['tag'] .= ',';
|
||||
} else {
|
||||
$item['tag'] = '';
|
||||
}
|
||||
|
||||
$item['tag'] .= '#[url=' . DI::baseUrl() . '/search?tag=' . $term . ']' . $term . '[/url]';
|
||||
// Store the hashtag
|
||||
Tag::store($item['uri-id'], Tag::HASHTAG, $attributes->textContent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -703,6 +700,8 @@ class OStatus
|
|||
$item["body"] = add_page_info_to_body($item["body"]);
|
||||
}
|
||||
|
||||
Tag::storeFromBody($item['uri-id'], $item['body']);
|
||||
|
||||
// Mastodon Content Warning
|
||||
if (($item["verb"] == Activity::POST) && $xpath->evaluate('boolean(atom:summary)', $entry)) {
|
||||
$clear_text = XML::getFirstNodeValue($xpath, 'atom:summary/text()', $entry);
|
||||
|
|
@ -1002,7 +1001,7 @@ class OStatus
|
|||
|
||||
// Even more worse workaround for GNU Social ;-)
|
||||
if ($xml == '') {
|
||||
$related_guess = OStatus::convertHref($related_uri);
|
||||
$related_guess = self::convertHref($related_uri);
|
||||
$curlResult = Network::curl(str_replace('/notice/', '/api/statuses/show/', $related_guess).'.atom');
|
||||
|
||||
if ($curlResult->isSuccess()) {
|
||||
|
|
@ -1176,7 +1175,7 @@ class OStatus
|
|||
*
|
||||
* @return string URL in the format http(s)://....
|
||||
*/
|
||||
public static function convertHref($href)
|
||||
private static function convertHref($href)
|
||||
{
|
||||
$elements = explode(":", $href);
|
||||
|
||||
|
|
@ -1272,14 +1271,16 @@ class OStatus
|
|||
$root = $doc->createElementNS(ActivityNamespace::ATOM1, 'feed');
|
||||
$doc->appendChild($root);
|
||||
|
||||
$root->setAttribute("xmlns:thr", ActivityNamespace::THREAD);
|
||||
$root->setAttribute("xmlns:georss", ActivityNamespace::GEORSS);
|
||||
$root->setAttribute("xmlns:activity", ActivityNamespace::ACTIVITY);
|
||||
$root->setAttribute("xmlns:media", ActivityNamespace::MEDIA);
|
||||
$root->setAttribute("xmlns:poco", ActivityNamespace::POCO);
|
||||
$root->setAttribute("xmlns:ostatus", ActivityNamespace::OSTATUS);
|
||||
$root->setAttribute("xmlns:statusnet", ActivityNamespace::STATUSNET);
|
||||
$root->setAttribute("xmlns:mastodon", ActivityNamespace::MASTODON);
|
||||
if (!$feed_mode) {
|
||||
$root->setAttribute("xmlns:thr", ActivityNamespace::THREAD);
|
||||
$root->setAttribute("xmlns:georss", ActivityNamespace::GEORSS);
|
||||
$root->setAttribute("xmlns:activity", ActivityNamespace::ACTIVITY);
|
||||
$root->setAttribute("xmlns:media", ActivityNamespace::MEDIA);
|
||||
$root->setAttribute("xmlns:poco", ActivityNamespace::POCO);
|
||||
$root->setAttribute("xmlns:ostatus", ActivityNamespace::OSTATUS);
|
||||
$root->setAttribute("xmlns:statusnet", ActivityNamespace::STATUSNET);
|
||||
$root->setAttribute("xmlns:mastodon", ActivityNamespace::MASTODON);
|
||||
}
|
||||
|
||||
$title = '';
|
||||
$selfUri = '/feed/' . $owner["nick"] . '/';
|
||||
|
|
@ -1309,7 +1310,7 @@ class OStatus
|
|||
XML::addElement($doc, $root, "logo", $owner["photo"]);
|
||||
XML::addElement($doc, $root, "updated", DateTimeFormat::utcNow(DateTimeFormat::ATOM));
|
||||
|
||||
$author = self::addAuthor($doc, $owner);
|
||||
$author = self::addAuthor($doc, $owner, true, $feed_mode);
|
||||
$root->appendChild($author);
|
||||
|
||||
$attributes = ["href" => $owner["url"], "rel" => "alternate", "type" => "text/html"];
|
||||
|
|
@ -1323,14 +1324,16 @@ class OStatus
|
|||
|
||||
self::hublinks($doc, $root, $owner["nick"]);
|
||||
|
||||
$attributes = ["href" => DI::baseUrl() . "/salmon/" . $owner["nick"], "rel" => "salmon"];
|
||||
XML::addElement($doc, $root, "link", "", $attributes);
|
||||
if (!$feed_mode) {
|
||||
$attributes = ["href" => DI::baseUrl() . "/salmon/" . $owner["nick"], "rel" => "salmon"];
|
||||
XML::addElement($doc, $root, "link", "", $attributes);
|
||||
|
||||
$attributes = ["href" => DI::baseUrl() . "/salmon/" . $owner["nick"], "rel" => "http://salmon-protocol.org/ns/salmon-replies"];
|
||||
XML::addElement($doc, $root, "link", "", $attributes);
|
||||
$attributes = ["href" => DI::baseUrl() . "/salmon/" . $owner["nick"], "rel" => "http://salmon-protocol.org/ns/salmon-replies"];
|
||||
XML::addElement($doc, $root, "link", "", $attributes);
|
||||
|
||||
$attributes = ["href" => DI::baseUrl() . "/salmon/" . $owner["nick"], "rel" => "http://salmon-protocol.org/ns/salmon-mention"];
|
||||
XML::addElement($doc, $root, "link", "", $attributes);
|
||||
$attributes = ["href" => DI::baseUrl() . "/salmon/" . $owner["nick"], "rel" => "http://salmon-protocol.org/ns/salmon-mention"];
|
||||
XML::addElement($doc, $root, "link", "", $attributes);
|
||||
}
|
||||
|
||||
$attributes = ["href" => DI::baseUrl() . $selfUri, "rel" => "self", "type" => "application/atom+xml"];
|
||||
XML::addElement($doc, $root, "link", "", $attributes);
|
||||
|
|
@ -1390,7 +1393,7 @@ class OStatus
|
|||
$attributes = ["rel" => "enclosure",
|
||||
"href" => $siteinfo["url"],
|
||||
"type" => "text/html; charset=UTF-8",
|
||||
"length" => "",
|
||||
"length" => "0",
|
||||
"title" => ($siteinfo["title"] ?? '') ?: $siteinfo["url"],
|
||||
];
|
||||
XML::addElement($doc, $root, "link", "", $attributes);
|
||||
|
|
@ -1439,74 +1442,79 @@ class OStatus
|
|||
* @param DOMDocument $doc XML document
|
||||
* @param array $owner Contact data of the poster
|
||||
* @param bool $show_profile Whether to show profile
|
||||
* @param bool $feed_mode Behave like a regular feed for users if true
|
||||
*
|
||||
* @return \DOMElement author element
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
private static function addAuthor(DOMDocument $doc, array $owner, $show_profile = true)
|
||||
private static function addAuthor(DOMDocument $doc, array $owner, $show_profile = true, $feed_mode = false)
|
||||
{
|
||||
$profile = DBA::selectFirst('profile', ['homepage', 'publish'], ['uid' => $owner['uid']]);
|
||||
$author = $doc->createElement("author");
|
||||
XML::addElement($doc, $author, "id", $owner["url"]);
|
||||
if ($owner['account-type'] == User::ACCOUNT_TYPE_COMMUNITY) {
|
||||
XML::addElement($doc, $author, "activity:object-type", Activity\ObjectType::GROUP);
|
||||
} else {
|
||||
XML::addElement($doc, $author, "activity:object-type", Activity\ObjectType::PERSON);
|
||||
if (!$feed_mode) {
|
||||
XML::addElement($doc, $author, "id", $owner["url"]);
|
||||
if ($owner['account-type'] == User::ACCOUNT_TYPE_COMMUNITY) {
|
||||
XML::addElement($doc, $author, "activity:object-type", Activity\ObjectType::GROUP);
|
||||
} else {
|
||||
XML::addElement($doc, $author, "activity:object-type", Activity\ObjectType::PERSON);
|
||||
}
|
||||
}
|
||||
XML::addElement($doc, $author, "uri", $owner["url"]);
|
||||
XML::addElement($doc, $author, "name", $owner["nick"]);
|
||||
XML::addElement($doc, $author, "email", $owner["addr"]);
|
||||
if ($show_profile) {
|
||||
XML::addElement($doc, $author, "summary", BBCode::convert($owner["about"], false, 7));
|
||||
if ($show_profile && !$feed_mode) {
|
||||
XML::addElement($doc, $author, "summary", BBCode::convert($owner["about"], false, BBCode::OSTATUS));
|
||||
}
|
||||
|
||||
$attributes = ["rel" => "alternate", "type" => "text/html", "href" => $owner["url"]];
|
||||
XML::addElement($doc, $author, "link", "", $attributes);
|
||||
if (!$feed_mode) {
|
||||
$attributes = ["rel" => "alternate", "type" => "text/html", "href" => $owner["url"]];
|
||||
XML::addElement($doc, $author, "link", "", $attributes);
|
||||
|
||||
$attributes = [
|
||||
"rel" => "avatar",
|
||||
"type" => "image/jpeg", // To-Do?
|
||||
"media:width" => 300,
|
||||
"media:height" => 300,
|
||||
"href" => $owner["photo"]];
|
||||
XML::addElement($doc, $author, "link", "", $attributes);
|
||||
|
||||
if (isset($owner["thumb"])) {
|
||||
$attributes = [
|
||||
"rel" => "avatar",
|
||||
"type" => "image/jpeg", // To-Do?
|
||||
"media:width" => 80,
|
||||
"media:height" => 80,
|
||||
"href" => $owner["thumb"]];
|
||||
"media:width" => 300,
|
||||
"media:height" => 300,
|
||||
"href" => $owner["photo"]];
|
||||
XML::addElement($doc, $author, "link", "", $attributes);
|
||||
}
|
||||
|
||||
XML::addElement($doc, $author, "poco:preferredUsername", $owner["nick"]);
|
||||
XML::addElement($doc, $author, "poco:displayName", $owner["name"]);
|
||||
if ($show_profile) {
|
||||
XML::addElement($doc, $author, "poco:note", BBCode::convert($owner["about"], false, 7));
|
||||
|
||||
if (trim($owner["location"]) != "") {
|
||||
$element = $doc->createElement("poco:address");
|
||||
XML::addElement($doc, $element, "poco:formatted", $owner["location"]);
|
||||
$author->appendChild($element);
|
||||
}
|
||||
}
|
||||
|
||||
if (DBA::isResult($profile) && !$show_profile) {
|
||||
if (trim($profile["homepage"]) != "") {
|
||||
$urls = $doc->createElement("poco:urls");
|
||||
XML::addElement($doc, $urls, "poco:type", "homepage");
|
||||
XML::addElement($doc, $urls, "poco:value", $profile["homepage"]);
|
||||
XML::addElement($doc, $urls, "poco:primary", "true");
|
||||
$author->appendChild($urls);
|
||||
if (isset($owner["thumb"])) {
|
||||
$attributes = [
|
||||
"rel" => "avatar",
|
||||
"type" => "image/jpeg", // To-Do?
|
||||
"media:width" => 80,
|
||||
"media:height" => 80,
|
||||
"href" => $owner["thumb"]];
|
||||
XML::addElement($doc, $author, "link", "", $attributes);
|
||||
}
|
||||
|
||||
XML::addElement($doc, $author, "followers", "", ["url" => DI::baseUrl() . "/profile/" . $owner["nick"] . "/contacts/followers"]);
|
||||
XML::addElement($doc, $author, "statusnet:profile_info", "", ["local_id" => $owner["uid"]]);
|
||||
XML::addElement($doc, $author, "poco:preferredUsername", $owner["nick"]);
|
||||
XML::addElement($doc, $author, "poco:displayName", $owner["name"]);
|
||||
if ($show_profile) {
|
||||
XML::addElement($doc, $author, "poco:note", BBCode::convert($owner["about"], false, BBCode::OSTATUS));
|
||||
|
||||
if ($profile["publish"]) {
|
||||
XML::addElement($doc, $author, "mastodon:scope", "public");
|
||||
if (trim($owner["location"]) != "") {
|
||||
$element = $doc->createElement("poco:address");
|
||||
XML::addElement($doc, $element, "poco:formatted", $owner["location"]);
|
||||
$author->appendChild($element);
|
||||
}
|
||||
}
|
||||
|
||||
if (DBA::isResult($profile) && !$show_profile) {
|
||||
if (trim($profile["homepage"]) != "") {
|
||||
$urls = $doc->createElement("poco:urls");
|
||||
XML::addElement($doc, $urls, "poco:type", "homepage");
|
||||
XML::addElement($doc, $urls, "poco:value", $profile["homepage"]);
|
||||
XML::addElement($doc, $urls, "poco:primary", "true");
|
||||
$author->appendChild($urls);
|
||||
}
|
||||
|
||||
XML::addElement($doc, $author, "followers", "", ["url" => DI::baseUrl() . "/profile/" . $owner["nick"] . "/contacts/followers"]);
|
||||
XML::addElement($doc, $author, "statusnet:profile_info", "", ["local_id" => $owner["uid"]]);
|
||||
|
||||
if ($profile["publish"]) {
|
||||
XML::addElement($doc, $author, "mastodon:scope", "public");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1570,7 +1578,7 @@ class OStatus
|
|||
|
||||
$repeated_guid = self::getResharedGuid($item);
|
||||
if ($repeated_guid != "") {
|
||||
$xml = self::reshareEntry($doc, $item, $owner, $repeated_guid, $toplevel);
|
||||
$xml = self::reshareEntry($doc, $item, $owner, $repeated_guid, $toplevel, $feed_mode);
|
||||
}
|
||||
|
||||
if ($xml) {
|
||||
|
|
@ -1669,14 +1677,15 @@ class OStatus
|
|||
* @param array $owner Contact data of the poster
|
||||
* @param string $repeated_guid guid
|
||||
* @param bool $toplevel Is it for en entry element (false) or a feed entry (true)?
|
||||
* @param bool $feed_mode Behave like a regular feed for users if true
|
||||
*
|
||||
* @return bool Entry element
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
private static function reshareEntry(DOMDocument $doc, array $item, array $owner, $repeated_guid, $toplevel)
|
||||
private static function reshareEntry(DOMDocument $doc, array $item, array $owner, $repeated_guid, $toplevel, $feed_mode = false)
|
||||
{
|
||||
if (($item["id"] != $item["parent"]) && (Strings::normaliseLink($item["author-link"]) != Strings::normaliseLink($owner["url"]))) {
|
||||
if (($item['gravity'] != GRAVITY_PARENT) && (Strings::normaliseLink($item["author-link"]) != Strings::normaliseLink($owner["url"]))) {
|
||||
Logger::log("OStatus entry is from author ".$owner["url"]." - not from ".$item["author-link"].". Quitting.", Logger::DEBUG);
|
||||
}
|
||||
|
||||
|
|
@ -1693,36 +1702,38 @@ class OStatus
|
|||
|
||||
$title = $owner["nick"]." repeated a notice by ".$contact["nick"];
|
||||
|
||||
self::entryContent($doc, $entry, $item, $owner, $title, Activity::SHARE, false);
|
||||
self::entryContent($doc, $entry, $item, $owner, $title, Activity::SHARE, false, $feed_mode);
|
||||
|
||||
$as_object = $doc->createElement("activity:object");
|
||||
if (!$feed_mode) {
|
||||
$as_object = $doc->createElement("activity:object");
|
||||
|
||||
XML::addElement($doc, $as_object, "activity:object-type", ActivityNamespace::ACTIVITY_SCHEMA . "activity");
|
||||
XML::addElement($doc, $as_object, "activity:object-type", ActivityNamespace::ACTIVITY_SCHEMA . "activity");
|
||||
|
||||
self::entryContent($doc, $as_object, $repeated_item, $owner, "", "", false);
|
||||
self::entryContent($doc, $as_object, $repeated_item, $owner, "", "", false);
|
||||
|
||||
$author = self::addAuthor($doc, $contact, false);
|
||||
$as_object->appendChild($author);
|
||||
$author = self::addAuthor($doc, $contact, false);
|
||||
$as_object->appendChild($author);
|
||||
|
||||
$as_object2 = $doc->createElement("activity:object");
|
||||
$as_object2 = $doc->createElement("activity:object");
|
||||
|
||||
XML::addElement($doc, $as_object2, "activity:object-type", self::constructObjecttype($repeated_item));
|
||||
XML::addElement($doc, $as_object2, "activity:object-type", self::constructObjecttype($repeated_item));
|
||||
|
||||
$title = sprintf("New comment by %s", $contact["nick"]);
|
||||
$title = sprintf("New comment by %s", $contact["nick"]);
|
||||
|
||||
self::entryContent($doc, $as_object2, $repeated_item, $owner, $title);
|
||||
self::entryContent($doc, $as_object2, $repeated_item, $owner, $title);
|
||||
|
||||
$as_object->appendChild($as_object2);
|
||||
$as_object->appendChild($as_object2);
|
||||
|
||||
self::entryFooter($doc, $as_object, $item, $owner, false);
|
||||
self::entryFooter($doc, $as_object, $item, $owner, false);
|
||||
|
||||
$source = self::sourceEntry($doc, $contact);
|
||||
$source = self::sourceEntry($doc, $contact);
|
||||
|
||||
$as_object->appendChild($source);
|
||||
$as_object->appendChild($source);
|
||||
|
||||
$entry->appendChild($as_object);
|
||||
$entry->appendChild($as_object);
|
||||
}
|
||||
|
||||
self::entryFooter($doc, $entry, $item, $owner);
|
||||
self::entryFooter($doc, $entry, $item, $owner, true, $feed_mode);
|
||||
|
||||
return $entry;
|
||||
}
|
||||
|
|
@ -1741,7 +1752,7 @@ class OStatus
|
|||
*/
|
||||
private static function likeEntry(DOMDocument $doc, array $item, array $owner, $toplevel)
|
||||
{
|
||||
if (($item["id"] != $item["parent"]) && (Strings::normaliseLink($item["author-link"]) != Strings::normaliseLink($owner["url"]))) {
|
||||
if (($item['gravity'] != GRAVITY_PARENT) && (Strings::normaliseLink($item["author-link"]) != Strings::normaliseLink($owner["url"]))) {
|
||||
Logger::log("OStatus entry is from author ".$owner["url"]." - not from ".$item["author-link"].". Quitting.", Logger::DEBUG);
|
||||
}
|
||||
|
||||
|
|
@ -1825,16 +1836,17 @@ class OStatus
|
|||
*/
|
||||
private static function followEntry(DOMDocument $doc, array $item, array $owner, $toplevel)
|
||||
{
|
||||
$item["id"] = $item["parent"] = 0;
|
||||
$item["id"] = $item['parent'] = 0;
|
||||
$item["created"] = $item["edited"] = date("c");
|
||||
$item["private"] = Item::PRIVATE;
|
||||
|
||||
$contact = Probe::uri($item['follow']);
|
||||
$item['follow'] = $contact['url'];
|
||||
|
||||
if ($contact['alias'] == '') {
|
||||
$contact['alias'] = $contact["url"];
|
||||
} else {
|
||||
if ($contact['alias']) {
|
||||
$item['follow'] = $contact['alias'];
|
||||
} else {
|
||||
$contact['alias'] = $contact['url'];
|
||||
}
|
||||
|
||||
$condition = ['uid' => $owner['uid'], 'nurl' => Strings::normaliseLink($contact["url"])];
|
||||
|
|
@ -1890,13 +1902,13 @@ class OStatus
|
|||
*/
|
||||
private static function noteEntry(DOMDocument $doc, array $item, array $owner, $toplevel, $feed_mode)
|
||||
{
|
||||
if (($item["id"] != $item["parent"]) && (Strings::normaliseLink($item["author-link"]) != Strings::normaliseLink($owner["url"]))) {
|
||||
if (($item['gravity'] != GRAVITY_PARENT) && (Strings::normaliseLink($item["author-link"]) != Strings::normaliseLink($owner["url"]))) {
|
||||
Logger::log("OStatus entry is from author ".$owner["url"]." - not from ".$item["author-link"].". Quitting.", Logger::DEBUG);
|
||||
}
|
||||
|
||||
if (!$toplevel) {
|
||||
if (!empty($item['title'])) {
|
||||
$title = BBCode::convert($item['title'], false, 7);
|
||||
$title = BBCode::convert($item['title'], false, BBCode::OSTATUS);
|
||||
} else {
|
||||
$title = sprintf("New note by %s", $owner["nick"]);
|
||||
}
|
||||
|
|
@ -1906,7 +1918,9 @@ class OStatus
|
|||
|
||||
$entry = self::entryHeader($doc, $owner, $item, $toplevel);
|
||||
|
||||
XML::addElement($doc, $entry, "activity:object-type", Activity\ObjectType::NOTE);
|
||||
if (!$feed_mode) {
|
||||
XML::addElement($doc, $entry, "activity:object-type", Activity\ObjectType::NOTE);
|
||||
}
|
||||
|
||||
self::entryContent($doc, $entry, $item, $owner, $title, '', true, $feed_mode);
|
||||
|
||||
|
|
@ -1985,7 +1999,7 @@ class OStatus
|
|||
$body = "[b]".$item['title']."[/b]\n\n".$body;
|
||||
}
|
||||
|
||||
$body = BBCode::convert($body, false, 7);
|
||||
$body = BBCode::convert($body, false, BBCode::OSTATUS);
|
||||
|
||||
XML::addElement($doc, $entry, "content", $body, ["type" => "html"]);
|
||||
|
||||
|
|
@ -2021,8 +2035,8 @@ class OStatus
|
|||
{
|
||||
$mentioned = [];
|
||||
|
||||
if (($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
|
||||
$parent = Item::selectFirst(['guid', 'author-link', 'owner-link'], ['id' => $item["parent"]]);
|
||||
if ($item['gravity'] != GRAVITY_PARENT) {
|
||||
$parent = Item::selectFirst(['guid', 'author-link', 'owner-link'], ['id' => $item['parent']]);
|
||||
$parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
|
||||
|
||||
$thrparent = Item::selectFirst(['guid', 'author-link', 'owner-link', 'plink'], ['uid' => $owner["uid"], 'uri' => $parent_item]);
|
||||
|
|
@ -2048,7 +2062,7 @@ class OStatus
|
|||
XML::addElement($doc, $entry, "link", "", $attributes);
|
||||
}
|
||||
|
||||
if (!$feed_mode && (intval($item["parent"]) > 0)) {
|
||||
if (!$feed_mode && (intval($item['parent']) > 0)) {
|
||||
$conversation_href = $conversation_uri = str_replace('/objects/', '/context/', $item['parent-uri']);
|
||||
|
||||
if (isset($parent_item)) {
|
||||
|
|
@ -2063,83 +2077,79 @@ class OStatus
|
|||
}
|
||||
}
|
||||
|
||||
XML::addElement($doc, $entry, "link", "", ["rel" => "ostatus:conversation", "href" => $conversation_href]);
|
||||
if (!$feed_mode) {
|
||||
XML::addElement($doc, $entry, "link", "", ["rel" => "ostatus:conversation", "href" => $conversation_href]);
|
||||
|
||||
$attributes = [
|
||||
"href" => $conversation_href,
|
||||
"local_id" => $item["parent"],
|
||||
"ref" => $conversation_uri];
|
||||
$attributes = [
|
||||
"href" => $conversation_href,
|
||||
"local_id" => $item['parent'],
|
||||
"ref" => $conversation_uri];
|
||||
|
||||
XML::addElement($doc, $entry, "ostatus:conversation", $conversation_uri, $attributes);
|
||||
}
|
||||
|
||||
$tags = item::getFeedTags($item);
|
||||
|
||||
if (count($tags)) {
|
||||
foreach ($tags as $t) {
|
||||
if ($t[0] == "@") {
|
||||
$mentioned[$t[1]] = $t[1];
|
||||
}
|
||||
XML::addElement($doc, $entry, "ostatus:conversation", $conversation_uri, $attributes);
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure that mentions are accepted (GNU Social has problems with mixing HTTP and HTTPS)
|
||||
$newmentions = [];
|
||||
foreach ($mentioned as $mention) {
|
||||
$newmentions[str_replace("http://", "https://", $mention)] = str_replace("http://", "https://", $mention);
|
||||
$newmentions[str_replace("https://", "http://", $mention)] = str_replace("https://", "http://", $mention);
|
||||
// uri-id isn't present for follow entry pseudo-items
|
||||
$tags = Tag::getByURIId($item['uri-id'] ?? 0);
|
||||
foreach ($tags as $tag) {
|
||||
$mentioned[$tag['url']] = $tag['url'];
|
||||
}
|
||||
$mentioned = $newmentions;
|
||||
|
||||
foreach ($mentioned as $mention) {
|
||||
$condition = ['uid' => $owner['uid'], 'nurl' => Strings::normaliseLink($mention)];
|
||||
$contact = DBA::selectFirst('contact', ['forum', 'prv', 'self', 'contact-type'], $condition);
|
||||
if ($contact["forum"] || $contact["prv"] || ($owner['contact-type'] == Contact::TYPE_COMMUNITY) ||
|
||||
($contact['self'] && ($owner['account-type'] == User::ACCOUNT_TYPE_COMMUNITY))) {
|
||||
XML::addElement($doc, $entry, "link", "",
|
||||
[
|
||||
"rel" => "mentioned",
|
||||
"ostatus:object-type" => Activity\ObjectType::GROUP,
|
||||
"href" => $mention]
|
||||
);
|
||||
} else {
|
||||
XML::addElement($doc, $entry, "link", "",
|
||||
[
|
||||
"rel" => "mentioned",
|
||||
"ostatus:object-type" => Activity\ObjectType::PERSON,
|
||||
"href" => $mention]
|
||||
);
|
||||
if (!$feed_mode) {
|
||||
// Make sure that mentions are accepted (GNU Social has problems with mixing HTTP and HTTPS)
|
||||
$newmentions = [];
|
||||
foreach ($mentioned as $mention) {
|
||||
$newmentions[str_replace("http://", "https://", $mention)] = str_replace("http://", "https://", $mention);
|
||||
$newmentions[str_replace("https://", "http://", $mention)] = str_replace("https://", "http://", $mention);
|
||||
}
|
||||
$mentioned = $newmentions;
|
||||
|
||||
foreach ($mentioned as $mention) {
|
||||
$contact = Contact::getByURL($mention, 0, ['contact-type']);
|
||||
if (!empty($contact) && ($contact['contact-type'] == Contact::TYPE_COMMUNITY)) {
|
||||
XML::addElement($doc, $entry, "link", "",
|
||||
[
|
||||
"rel" => "mentioned",
|
||||
"ostatus:object-type" => Activity\ObjectType::GROUP,
|
||||
"href" => $mention]
|
||||
);
|
||||
} else {
|
||||
XML::addElement($doc, $entry, "link", "",
|
||||
[
|
||||
"rel" => "mentioned",
|
||||
"ostatus:object-type" => Activity\ObjectType::PERSON,
|
||||
"href" => $mention]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ($owner['account-type'] == User::ACCOUNT_TYPE_COMMUNITY) {
|
||||
XML::addElement($doc, $entry, "link", "", [
|
||||
"rel" => "mentioned",
|
||||
"ostatus:object-type" => "http://activitystrea.ms/schema/1.0/group",
|
||||
"href" => $owner['url']
|
||||
]);
|
||||
}
|
||||
|
||||
if ($item['private'] != Item::PRIVATE) {
|
||||
XML::addElement($doc, $entry, "link", "", ["rel" => "ostatus:attention",
|
||||
"href" => "http://activityschema.org/collection/public"]);
|
||||
XML::addElement($doc, $entry, "link", "", ["rel" => "mentioned",
|
||||
"ostatus:object-type" => "http://activitystrea.ms/schema/1.0/collection",
|
||||
"href" => "http://activityschema.org/collection/public"]);
|
||||
XML::addElement($doc, $entry, "mastodon:scope", "public");
|
||||
}
|
||||
}
|
||||
|
||||
if ($owner['account-type'] == User::ACCOUNT_TYPE_COMMUNITY) {
|
||||
XML::addElement($doc, $entry, "link", "", [
|
||||
"rel" => "mentioned",
|
||||
"ostatus:object-type" => "http://activitystrea.ms/schema/1.0/group",
|
||||
"href" => $owner['url']
|
||||
]);
|
||||
}
|
||||
|
||||
if (($item['private'] != Item::PRIVATE) && !$feed_mode) {
|
||||
XML::addElement($doc, $entry, "link", "", ["rel" => "ostatus:attention",
|
||||
"href" => "http://activityschema.org/collection/public"]);
|
||||
XML::addElement($doc, $entry, "link", "", ["rel" => "mentioned",
|
||||
"ostatus:object-type" => "http://activitystrea.ms/schema/1.0/collection",
|
||||
"href" => "http://activityschema.org/collection/public"]);
|
||||
XML::addElement($doc, $entry, "mastodon:scope", "public");
|
||||
}
|
||||
|
||||
if (count($tags)) {
|
||||
foreach ($tags as $t) {
|
||||
if ($t[0] != "@") {
|
||||
XML::addElement($doc, $entry, "category", "", ["term" => $t[2]]);
|
||||
}
|
||||
foreach ($tags as $tag) {
|
||||
if ($tag['type'] == Tag::HASHTAG) {
|
||||
XML::addElement($doc, $entry, "category", "", ["term" => $tag['name']]);
|
||||
}
|
||||
}
|
||||
|
||||
self::getAttachment($doc, $entry, $item);
|
||||
|
||||
if ($complete && ($item["id"] > 0)) {
|
||||
if (!$feed_mode && $complete && ($item["id"] > 0)) {
|
||||
$app = $item["app"];
|
||||
if ($app == "") {
|
||||
$app = "web";
|
||||
|
|
@ -2208,7 +2218,7 @@ class OStatus
|
|||
$last_update = 'now -30 days';
|
||||
}
|
||||
|
||||
$check_date = DateTimeFormat::utc($last_update);
|
||||
$check_date = $feed_mode ? '' : DateTimeFormat::utc($last_update);
|
||||
$authorid = Contact::getIdForURL($owner["url"], 0, true);
|
||||
|
||||
$condition = ["`uid` = ? AND `received` > ? AND NOT `deleted`
|
||||
|
|
@ -2246,6 +2256,10 @@ class OStatus
|
|||
$item['body'] .= '🍼';
|
||||
}
|
||||
|
||||
if (in_array($item["verb"], [Activity::FOLLOW, Activity::O_UNFOLLOW, Activity::LIKE])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$entry = self::entry($doc, $item, $owner, false, $feed_mode);
|
||||
$root->appendChild($entry);
|
||||
|
||||
|
|
|
|||
|
|
@ -116,7 +116,7 @@ class PortableContact
|
|||
|
||||
$j = json_decode($s, true);
|
||||
|
||||
Logger::log('load: json: ' . print_r($j, true), Logger::DATA);
|
||||
Logger::debug('load', ['json' => $j]);
|
||||
|
||||
if (!isset($j['entry'])) {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ class Salmon
|
|||
}
|
||||
|
||||
|
||||
Logger::log('Key located: ' . print_r($ret, true));
|
||||
Logger::notice('Key located', ['ret' => $ret]);
|
||||
|
||||
if (count($ret) == 1) {
|
||||
// We only found one one key so we don't care if the hash matches.
|
||||
|
|
@ -111,13 +111,13 @@ class Salmon
|
|||
{
|
||||
// does contact have a salmon endpoint?
|
||||
|
||||
if (! strlen($url)) {
|
||||
if (!strlen($url)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $owner['sprvkey']) {
|
||||
if (!$owner['sprvkey']) {
|
||||
Logger::log(sprintf("user '%s' (%d) does not have a salmon private key. Send failed.",
|
||||
$owner['username'], $owner['uid']));
|
||||
$owner['name'], $owner['uid']));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@
|
|||
|
||||
namespace Friendica\Render;
|
||||
|
||||
use Friendica\DI;
|
||||
use Smarty;
|
||||
use Friendica\Core\Renderer;
|
||||
|
||||
|
|
@ -34,26 +33,23 @@ class FriendicaSmarty extends Smarty
|
|||
|
||||
public $filename;
|
||||
|
||||
function __construct()
|
||||
function __construct(string $theme, array $theme_info)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$a = DI::app();
|
||||
$theme = $a->getCurrentTheme();
|
||||
|
||||
// setTemplateDir can be set to an array, which Smarty will parse in order.
|
||||
// The order is thus very important here
|
||||
$template_dirs = ['theme' => "view/theme/$theme/" . self::SMARTY3_TEMPLATE_FOLDER . "/"];
|
||||
if (!empty($a->theme_info['extends'])) {
|
||||
$template_dirs = $template_dirs + ['extends' => "view/theme/" . $a->theme_info["extends"] . "/" . self::SMARTY3_TEMPLATE_FOLDER . "/"];
|
||||
if (!empty($theme_info['extends'])) {
|
||||
$template_dirs = $template_dirs + ['extends' => "view/theme/" . $theme_info["extends"] . "/" . self::SMARTY3_TEMPLATE_FOLDER . "/"];
|
||||
}
|
||||
|
||||
$template_dirs = $template_dirs + ['base' => "view/" . self::SMARTY3_TEMPLATE_FOLDER . "/"];
|
||||
$this->setTemplateDir($template_dirs);
|
||||
|
||||
$this->setCompileDir('view/smarty3/compiled/');
|
||||
$this->setConfigDir('view/smarty3/config/');
|
||||
$this->setCacheDir('view/smarty3/cache/');
|
||||
$this->setConfigDir('view/smarty3/');
|
||||
$this->setCacheDir('view/smarty3/');
|
||||
|
||||
$this->left_delimiter = Renderer::getTemplateLeftDelimiter('smarty3');
|
||||
$this->right_delimiter = Renderer::getTemplateRightDelimiter('smarty3');
|
||||
|
|
@ -63,13 +59,4 @@ class FriendicaSmarty extends Smarty
|
|||
// Don't report errors so verbosely
|
||||
$this->error_reporting = E_ALL & ~E_NOTICE;
|
||||
}
|
||||
|
||||
function parsed($template = '')
|
||||
{
|
||||
if ($template) {
|
||||
return $this->fetch('string:' . $template);
|
||||
}
|
||||
return $this->fetch('file:' . $this->filename);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,76 +23,105 @@ namespace Friendica\Render;
|
|||
|
||||
use Friendica\Core\Hook;
|
||||
use Friendica\DI;
|
||||
use Friendica\Network\HTTPException\InternalServerErrorException;
|
||||
use Friendica\Util\Strings;
|
||||
|
||||
/**
|
||||
* Smarty implementation of the Friendica template engine interface
|
||||
* Smarty implementation of the Friendica template abstraction
|
||||
*/
|
||||
class FriendicaSmartyEngine implements ITemplateEngine
|
||||
final class FriendicaSmartyEngine extends TemplateEngine
|
||||
{
|
||||
static $name = "smarty3";
|
||||
|
||||
public function __construct()
|
||||
const FILE_PREFIX = 'file:';
|
||||
const STRING_PREFIX = 'string:';
|
||||
|
||||
/** @var FriendicaSmarty */
|
||||
private $smarty;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function __construct(string $theme, array $theme_info)
|
||||
{
|
||||
if (!is_writable(__DIR__ . '/../../view/smarty3/')) {
|
||||
echo "<b>ERROR:</b> folder <tt>view/smarty3/</tt> must be writable by webserver.";
|
||||
exit();
|
||||
$this->theme = $theme;
|
||||
$this->theme_info = $theme_info;
|
||||
$this->smarty = new FriendicaSmarty($this->theme, $this->theme_info);
|
||||
|
||||
if (!is_writable(DI::basePath() . '/view/smarty3')) {
|
||||
$admin_message = DI::l10n()->t('The folder view/smarty3/ must be writable by webserver.');
|
||||
DI::logger()->critical($admin_message);
|
||||
$message = is_site_admin() ?
|
||||
$admin_message :
|
||||
DI::l10n()->t('Friendica can\'t display this page at the moment, please contact the administrator.');
|
||||
throw new InternalServerErrorException($message);
|
||||
}
|
||||
}
|
||||
|
||||
// ITemplateEngine interface
|
||||
public function replaceMacros($s, $r)
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function testInstall(array &$errors = null)
|
||||
{
|
||||
$template = '';
|
||||
if (gettype($s) === 'string') {
|
||||
$template = $s;
|
||||
$s = new FriendicaSmarty();
|
||||
}
|
||||
$this->smarty->testInstall($errors);
|
||||
}
|
||||
|
||||
$r['$APP'] = DI::app();
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function replaceMacros(string $template, array $vars)
|
||||
{
|
||||
if (!Strings::startsWith($template, self::FILE_PREFIX)) {
|
||||
$template = self::STRING_PREFIX . $template;
|
||||
}
|
||||
|
||||
// "middleware": inject variables into templates
|
||||
$arr = [
|
||||
"template" => basename($s->filename),
|
||||
"vars" => $r
|
||||
'template' => basename($this->smarty->filename),
|
||||
'vars' => $vars
|
||||
];
|
||||
Hook::callAll("template_vars", $arr);
|
||||
$r = $arr['vars'];
|
||||
Hook::callAll('template_vars', $arr);
|
||||
$vars = $arr['vars'];
|
||||
|
||||
foreach ($r as $key => $value) {
|
||||
$this->smarty->clearAllAssign();
|
||||
|
||||
foreach ($vars as $key => $value) {
|
||||
if ($key[0] === '$') {
|
||||
$key = substr($key, 1);
|
||||
}
|
||||
|
||||
$s->assign($key, $value);
|
||||
$this->smarty->assign($key, $value);
|
||||
}
|
||||
return $s->parsed($template);
|
||||
|
||||
return $this->smarty->fetch($template);
|
||||
}
|
||||
|
||||
public function getTemplateFile($file, $root = '')
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function getTemplateFile(string $file, string $subDir = '')
|
||||
{
|
||||
$a = DI::app();
|
||||
$template = new FriendicaSmarty();
|
||||
|
||||
// Make sure $root ends with a slash /
|
||||
if ($root !== '' && substr($root, -1, 1) !== '/') {
|
||||
$root = $root . '/';
|
||||
if ($subDir !== '' && substr($subDir, -1, 1) !== '/') {
|
||||
$subDir = $subDir . '/';
|
||||
}
|
||||
|
||||
$theme = $a->getCurrentTheme();
|
||||
$filename = $template::SMARTY3_TEMPLATE_FOLDER . '/' . $file;
|
||||
$root = DI::basePath() . '/' . $subDir;
|
||||
|
||||
if (file_exists("{$root}view/theme/$theme/$filename")) {
|
||||
$template_file = "{$root}view/theme/$theme/$filename";
|
||||
} elseif (!empty($a->theme_info['extends']) && file_exists(sprintf('%sview/theme/%s}/%s', $root, $a->theme_info['extends'], $filename))) {
|
||||
$template_file = sprintf('%sview/theme/%s}/%s', $root, $a->theme_info['extends'], $filename);
|
||||
$filename = $this->smarty::SMARTY3_TEMPLATE_FOLDER . '/' . $file;
|
||||
|
||||
if (file_exists("{$root}view/theme/$this->theme/$filename")) {
|
||||
$template_file = "{$root}view/theme/$this->theme/$filename";
|
||||
} elseif (!empty($this->theme_info['extends']) && file_exists(sprintf('%sview/theme/%s}/%s', $root, $this->theme_info['extends'], $filename))) {
|
||||
$template_file = sprintf('%sview/theme/%s}/%s', $root, $this->theme_info['extends'], $filename);
|
||||
} elseif (file_exists("{$root}/$filename")) {
|
||||
$template_file = "{$root}/$filename";
|
||||
} else {
|
||||
$template_file = "{$root}view/$filename";
|
||||
}
|
||||
|
||||
$template->filename = $template_file;
|
||||
$this->smarty->filename = $template_file;
|
||||
|
||||
return $template;
|
||||
return self::FILE_PREFIX . $template_file;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
68
src/Render/TemplateEngine.php
Normal file
68
src/Render/TemplateEngine.php
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
/**
|
||||
* @copyright Copyright (C) 2020, Friendica
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Friendica\Render;
|
||||
|
||||
/**
|
||||
* Interface for template engines
|
||||
*/
|
||||
abstract class TemplateEngine
|
||||
{
|
||||
/** @var string */
|
||||
static $name;
|
||||
|
||||
/** @var string */
|
||||
protected $theme;
|
||||
/** @var array */
|
||||
protected $theme_info;
|
||||
|
||||
/**
|
||||
* @param string $theme The current theme name
|
||||
* @param array $theme_info The current theme info array
|
||||
*/
|
||||
abstract public function __construct(string $theme, array $theme_info);
|
||||
|
||||
/**
|
||||
* Checks the template engine is correctly installed and configured and reports error messages in the provided
|
||||
* parameter or displays them directly if it's null.
|
||||
*
|
||||
* @param array|null $errors
|
||||
*/
|
||||
abstract public function testInstall(array &$errors = null);
|
||||
|
||||
/**
|
||||
* Returns the rendered template output from the template string and variables
|
||||
*
|
||||
* @param string $template
|
||||
* @param array $vars
|
||||
* @return string
|
||||
*/
|
||||
abstract public function replaceMacros(string $template, array $vars);
|
||||
|
||||
/**
|
||||
* Returns the template string from a file path and an optional sub-directory from the project root
|
||||
*
|
||||
* @param string $file
|
||||
* @param string $subDir
|
||||
* @return mixed
|
||||
*/
|
||||
abstract public function getTemplateFile(string $file, string $subDir = '');
|
||||
}
|
||||
|
|
@ -393,7 +393,7 @@ class Crypto
|
|||
// log the offending call so we can track it down
|
||||
if (!openssl_public_encrypt($key, $k, $pubkey)) {
|
||||
$x = debug_backtrace();
|
||||
Logger::log('RSA failed. ' . print_r($x[0], true));
|
||||
Logger::notice('RSA failed', ['trace' => $x[0]]);
|
||||
}
|
||||
|
||||
$result['alg'] = $alg;
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ class Emailer
|
|||
$this->l10n = $defaultLang;
|
||||
|
||||
$this->siteEmailAddress = $this->config->get('config', 'sender_email');
|
||||
if (empty($sysEmailAddress)) {
|
||||
if (empty($this->siteEmailAddress)) {
|
||||
$hostname = $this->baseUrl->getHostname();
|
||||
if (strpos($hostname, ':')) {
|
||||
$hostname = substr($hostname, 0, strpos($hostname, ':'));
|
||||
|
|
|
|||
|
|
@ -534,6 +534,14 @@ class HTTPSignature
|
|||
|
||||
$algorithm = null;
|
||||
|
||||
// Wildcard value where signing algorithm should be derived from keyId
|
||||
// @see https://tools.ietf.org/html/draft-ietf-httpbis-message-signatures-00#section-4.1
|
||||
// Defaulting to SHA256 as it seems to be the prevalent implementation
|
||||
// @see https://arewehs2019yet.vpzom.click
|
||||
if ($sig_block['algorithm'] === 'hs2019') {
|
||||
$algorithm = 'sha256';
|
||||
}
|
||||
|
||||
if ($sig_block['algorithm'] === 'rsa-sha256') {
|
||||
$algorithm = 'sha256';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ namespace Friendica\Util;
|
|||
use Friendica\Core\Logger;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\DI;
|
||||
use Imagick;
|
||||
|
||||
/**
|
||||
* Image utilities
|
||||
|
|
@ -74,61 +73,79 @@ class Images
|
|||
}
|
||||
|
||||
/**
|
||||
* Guess image mimetype from filename or from Content-Type header
|
||||
* Fetch image mimetype from the image data or guessing from the file name
|
||||
*
|
||||
* @param string $filename Image filename
|
||||
* @param boolean $fromcurl Check Content-Type header from curl request
|
||||
* @param string $header passed headers to take into account
|
||||
* @param string $image_data Image data
|
||||
* @param string $filename File name (for guessing the type via the extension)
|
||||
* @param string $mime default mime type
|
||||
*
|
||||
* @return string|null
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function guessType($filename, $fromcurl = false, $header = '')
|
||||
public static function getMimeTypeByData(string $image_data, string $filename = '', string $mime = '')
|
||||
{
|
||||
Logger::info('Image: guessType: ' . $filename . ($fromcurl ? ' from curl headers' : ''));
|
||||
$type = null;
|
||||
if ($fromcurl) {
|
||||
$headers = [];
|
||||
$h = explode("\n", $header);
|
||||
foreach ($h as $l) {
|
||||
$data = array_map("trim", explode(":", trim($l), 2));
|
||||
if (count($data) > 1) {
|
||||
list($k, $v) = $data;
|
||||
$headers[$k] = $v;
|
||||
}
|
||||
}
|
||||
|
||||
if (array_key_exists('Content-Type', $headers)) {
|
||||
$type = $headers['Content-Type'];
|
||||
}
|
||||
if (substr($mime, 0, 6) == 'image/') {
|
||||
Logger::info('Using default mime type', ['filename' => $filename, 'mime' => $mime]);
|
||||
return $mime;
|
||||
}
|
||||
|
||||
if (is_null($type)) {
|
||||
// Guessing from extension? Isn't that... dangerous?
|
||||
if (class_exists('Imagick') && file_exists($filename) && is_readable($filename)) {
|
||||
/**
|
||||
* Well, this not much better,
|
||||
* but at least it comes from the data inside the image,
|
||||
* we won't be tricked by a manipulated extension
|
||||
*/
|
||||
$image = new Imagick($filename);
|
||||
$type = $image->getImageMimeType();
|
||||
} else {
|
||||
$ext = pathinfo($filename, PATHINFO_EXTENSION);
|
||||
$types = self::supportedTypes();
|
||||
$type = 'image/jpeg';
|
||||
foreach ($types as $m => $e) {
|
||||
if ($ext == $e) {
|
||||
$type = $m;
|
||||
}
|
||||
}
|
||||
}
|
||||
$image = @getimagesizefromstring($image_data);
|
||||
if (!empty($image['mime'])) {
|
||||
Logger::info('Mime type detected via data', ['filename' => $filename, 'default' => $mime, 'mime' => $image['mime']]);
|
||||
return $image['mime'];
|
||||
}
|
||||
|
||||
Logger::info('Image: guessType: type=' . $type);
|
||||
return $type;
|
||||
return self::guessTypeByExtension($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch image mimetype from the image data or guessing from the file name
|
||||
*
|
||||
* @param string $sourcefile Source file of the image
|
||||
* @param string $filename File name (for guessing the type via the extension)
|
||||
* @param string $mime default mime type
|
||||
*
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function getMimeTypeBySource(string $sourcefile, string $filename = '', string $mime = '')
|
||||
{
|
||||
if (substr($mime, 0, 6) == 'image/') {
|
||||
Logger::info('Using default mime type', ['filename' => $filename, 'mime' => $mime]);
|
||||
return $mime;
|
||||
}
|
||||
|
||||
$image = @getimagesize($sourcefile);
|
||||
if (!empty($image['mime'])) {
|
||||
Logger::info('Mime type detected via file', ['filename' => $filename, 'default' => $mime, 'image' => $image]);
|
||||
return $image['mime'];
|
||||
}
|
||||
|
||||
return self::guessTypeByExtension($filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Guess image mimetype from the filename
|
||||
*
|
||||
* @param string $filename Image filename
|
||||
*
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function guessTypeByExtension(string $filename)
|
||||
{
|
||||
$ext = pathinfo(parse_url($filename, PHP_URL_PATH), PATHINFO_EXTENSION);
|
||||
$types = self::supportedTypes();
|
||||
$type = 'image/jpeg';
|
||||
foreach ($types as $m => $e) {
|
||||
if ($ext == $e) {
|
||||
$type = $m;
|
||||
}
|
||||
}
|
||||
|
||||
Logger::info('Mime type guessed via extension', ['filename' => $filename, 'type' => $type]);
|
||||
return $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
|
|
|
|||
|
|
@ -173,12 +173,8 @@ class JsonLD
|
|||
*
|
||||
* @return array fetched element
|
||||
*/
|
||||
public static function fetchElementArray($array, $element, $key = '@id')
|
||||
public static function fetchElementArray($array, $element, $key = null)
|
||||
{
|
||||
if (empty($array)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!isset($array[$element])) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -191,12 +187,10 @@ class JsonLD
|
|||
$elements = [];
|
||||
|
||||
foreach ($array[$element] as $entry) {
|
||||
if (!is_array($entry)) {
|
||||
if (!is_array($entry) || (is_null($key) && is_array($entry))) {
|
||||
$elements[] = $entry;
|
||||
} elseif (isset($entry[$key])) {
|
||||
} elseif (!is_null($key) && isset($entry[$key])) {
|
||||
$elements[] = $entry[$key];
|
||||
} elseif (!empty($entry) || !is_array($entry)) {
|
||||
$elements[] = $entry;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -625,6 +625,26 @@ class Network
|
|||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a missing base path (scheme and host) to a given url
|
||||
*
|
||||
* @param string $url
|
||||
* @param string $basepath
|
||||
* @return string url
|
||||
*/
|
||||
public static function addBasePath(string $url, string $basepath)
|
||||
{
|
||||
if (!empty(parse_url($url, PHP_URL_SCHEME)) || empty(parse_url($basepath, PHP_URL_SCHEME)) || empty($url) || empty(parse_url($url))) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
$base = ['scheme' => parse_url($basepath, PHP_URL_SCHEME),
|
||||
'host' => parse_url($basepath, PHP_URL_HOST)];
|
||||
|
||||
$parts = array_merge($base, parse_url('/' . ltrim($url, '/')));
|
||||
return self::unparseURL($parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the original URL of the provided URL
|
||||
*
|
||||
|
|
@ -910,4 +930,46 @@ class Network
|
|||
|
||||
return self::unparseURL($parsed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates ETag and Last-Modified response headers and checks them against
|
||||
* If-None-Match and If-Modified-Since request headers if present.
|
||||
*
|
||||
* Blocking function, sends 304 headers and exits if check passes.
|
||||
*
|
||||
* @param string $etag The page etag
|
||||
* @param string $last_modified The page last modification UTC date
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function checkEtagModified(string $etag, string $last_modified)
|
||||
{
|
||||
$last_modified = DateTimeFormat::utc($last_modified, 'D, d M Y H:i:s') . ' GMT';
|
||||
|
||||
/**
|
||||
* @see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.26
|
||||
*/
|
||||
$if_none_match = filter_input(INPUT_SERVER, 'HTTP_IF_NONE_MATCH');
|
||||
$if_modified_since = filter_input(INPUT_SERVER, 'HTTP_IF_MODIFIED_SINCE');
|
||||
$flag_not_modified = null;
|
||||
if ($if_none_match) {
|
||||
$result = [];
|
||||
preg_match('/^(?:W\/")?([^"]+)"?$/i', $etag, $result);
|
||||
$etagTrimmed = $result[1];
|
||||
// Lazy exact ETag match, could check weak/strong ETags
|
||||
$flag_not_modified = $if_none_match == '*' || strpos($if_none_match, $etagTrimmed) !== false;
|
||||
}
|
||||
|
||||
if ($if_modified_since && (!$if_none_match || $flag_not_modified)) {
|
||||
// Lazy exact Last-Modified match, could check If-Modified-Since validity
|
||||
$flag_not_modified = $if_modified_since == $last_modified;
|
||||
}
|
||||
|
||||
header('Etag: ' . $etag);
|
||||
header('Last-Modified: ' . $last_modified);
|
||||
|
||||
if ($flag_not_modified) {
|
||||
header("HTTP/1.1 304 Not Modified");
|
||||
exit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -369,13 +369,43 @@ class Strings
|
|||
* @param array $chars
|
||||
* @return bool
|
||||
*/
|
||||
public static function startsWith($string, array $chars)
|
||||
public static function startsWithChars($string, array $chars)
|
||||
{
|
||||
$return = in_array(substr(trim($string), 0, 1), $chars);
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the first string starts with the second
|
||||
*
|
||||
* @see http://maettig.com/code/php/php-performance-benchmarks.php#startswith
|
||||
* @param string $string
|
||||
* @param string $start
|
||||
* @return bool
|
||||
*/
|
||||
public static function startsWith(string $string, string $start)
|
||||
{
|
||||
$return = substr_compare($string, $start, 0, strlen($start)) === 0;
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the first string ends with the second
|
||||
*
|
||||
* @see http://maettig.com/code/php/php-performance-benchmarks.php#endswith
|
||||
* @param string $string
|
||||
* @param string $end
|
||||
* @return bool
|
||||
*/
|
||||
public static function endsWith(string $string, string $end)
|
||||
{
|
||||
$return = substr_compare($string, $end, -strlen($end)) === 0;
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the regular expression string to match URLs in a given text
|
||||
*
|
||||
|
|
@ -420,4 +450,90 @@ class Strings
|
|||
|
||||
return $pathItem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Multi-byte safe implementation of substr_replace where $start and $length are character offset and count rather
|
||||
* than byte offset and counts.
|
||||
*
|
||||
* Depends on mbstring, use default encoding.
|
||||
*
|
||||
* @param string $string
|
||||
* @param string $replacement
|
||||
* @param int $start
|
||||
* @param int|null $length
|
||||
* @return string
|
||||
* @see substr_replace()
|
||||
*/
|
||||
public static function substringReplace(string $string, string $replacement, int $start, int $length = null)
|
||||
{
|
||||
$string_length = mb_strlen($string);
|
||||
|
||||
$length = $length ?? $string_length;
|
||||
|
||||
if ($start < 0) {
|
||||
$start = max(0, $string_length + $start);
|
||||
} else if ($start > $string_length) {
|
||||
$start = $string_length;
|
||||
}
|
||||
|
||||
if ($length < 0) {
|
||||
$length = max(0, $string_length - $start + $length);
|
||||
} else if ($length > $string_length) {
|
||||
$length = $string_length;
|
||||
}
|
||||
|
||||
if (($start + $length) > $string_length) {
|
||||
$length = $string_length - $start;
|
||||
}
|
||||
|
||||
return mb_substr($string, 0, $start) . $replacement . mb_substr($string, $start + $length, $string_length - $start - $length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a custom function on a text after having escaped blocks matched by the provided regular expressions.
|
||||
* Only full matches are used, capturing group are ignored.
|
||||
*
|
||||
* To change the provided text, the callback function needs to return it and this function will return the modified
|
||||
* version as well after having restored the escaped blocks.
|
||||
*
|
||||
* @param string $text
|
||||
* @param string $regex
|
||||
* @param callable $callback
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function performWithEscapedBlocks(string $text, string $regex, callable $callback)
|
||||
{
|
||||
// Enables nested use
|
||||
$executionId = random_int(PHP_INT_MAX / 10, PHP_INT_MAX);
|
||||
|
||||
$blocks = [];
|
||||
|
||||
$text = preg_replace_callback($regex,
|
||||
function ($matches) use ($executionId, &$blocks) {
|
||||
$return = '«block-' . $executionId . '-' . count($blocks) . '»';
|
||||
|
||||
$blocks[] = $matches[0];
|
||||
|
||||
return $return;
|
||||
},
|
||||
$text
|
||||
);
|
||||
|
||||
$text = $callback($text) ?? '';
|
||||
|
||||
// Restore code blocks
|
||||
$text = preg_replace_callback('/«block-' . $executionId . '-([0-9]+)»/iU',
|
||||
function ($matches) use ($blocks) {
|
||||
$return = $matches[0];
|
||||
if (isset($blocks[intval($matches[1])])) {
|
||||
$return = $blocks[$matches[1]];
|
||||
}
|
||||
return $return;
|
||||
},
|
||||
$text
|
||||
);
|
||||
|
||||
return $text;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -433,18 +433,26 @@ class XML
|
|||
}
|
||||
}
|
||||
|
||||
public static function parseString($s, $strict = true)
|
||||
/**
|
||||
* Parse XML string
|
||||
*
|
||||
* @param string $s
|
||||
* @param boolean $suppress_log
|
||||
* @return Object
|
||||
*/
|
||||
public static function parseString(string $s, bool $suppress_log = false)
|
||||
{
|
||||
// the "strict" parameter is deactivated
|
||||
libxml_use_internal_errors(true);
|
||||
|
||||
$x = @simplexml_load_string($s);
|
||||
if (!$x) {
|
||||
Logger::error('Error(s) while parsing XML string.', ['callstack' => System::callstack()]);
|
||||
foreach (libxml_get_errors() as $err) {
|
||||
Logger::info('libxml error', ['code' => $err->code, 'position' => $err->line . ":" . $err->column, 'message' => $err->message]);
|
||||
if (!$suppress_log) {
|
||||
Logger::error('Error(s) while parsing XML string.', ['callstack' => System::callstack()]);
|
||||
foreach (libxml_get_errors() as $err) {
|
||||
Logger::info('libxml error', ['code' => $err->code, 'position' => $err->line . ":" . $err->column, 'message' => $err->message]);
|
||||
}
|
||||
Logger::debug('Erroring XML string', ['xml' => $s]);
|
||||
}
|
||||
Logger::debug('Erroring XML string', ['xml' => $s]);
|
||||
libxml_clear_errors();
|
||||
}
|
||||
return $x;
|
||||
|
|
|
|||
|
|
@ -23,7 +23,8 @@ namespace Friendica\Worker;
|
|||
|
||||
use Friendica\Core\Logger;
|
||||
use Friendica\Core\Worker;
|
||||
use Friendica\Model\ItemDeliveryData;
|
||||
use Friendica\Model\Item;
|
||||
use Friendica\Model\Post;
|
||||
use Friendica\Protocol\ActivityPub;
|
||||
use Friendica\Util\HTTPSignature;
|
||||
|
||||
|
|
@ -67,10 +68,14 @@ class APDelivery
|
|||
}
|
||||
}
|
||||
|
||||
// This should never fail and is temporariy (until the move to the "post" structure)
|
||||
$item = Item::selectFirst(['uri-id'], ['id' => $target_id]);
|
||||
$uriid = $item['uri-id'] ?? 0;
|
||||
|
||||
if (!$success && !Worker::defer() && in_array($cmd, [Delivery::POST])) {
|
||||
ItemDeliveryData::incrementQueueFailed($target_id);
|
||||
Post\DeliveryData::incrementQueueFailed($uriid);
|
||||
} elseif ($success && in_array($cmd, [Delivery::POST])) {
|
||||
ItemDeliveryData::incrementQueueDone($target_id, ItemDeliveryData::ACTIVITYPUB);
|
||||
Post\DeliveryData::incrementQueueDone($uriid, Post\DeliveryData::ACTIVITYPUB);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ namespace Friendica\Worker;
|
|||
|
||||
use Friendica\Core\Logger;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Model\User;
|
||||
|
||||
class AddContact
|
||||
{
|
||||
|
|
@ -33,7 +34,11 @@ class AddContact
|
|||
*/
|
||||
public static function execute(int $uid, string $url)
|
||||
{
|
||||
$result = Contact::createFromProbe($uid, $url, '', false);
|
||||
$user = User::getById($uid);
|
||||
if (empty($user)) {
|
||||
return;
|
||||
}
|
||||
$result = Contact::createFromProbe($user, $url, '', false);
|
||||
Logger::info('Added contact', ['uid' => $uid, 'url' => $url, 'result' => $result]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ use Friendica\Util\Network;
|
|||
* Check the git repository VERSION file and save the version to the DB
|
||||
*
|
||||
* Checking the upstream version is optional (opt-in) and can be done to either
|
||||
* the master or the develop branch in the repository.
|
||||
* the stable or the develop branch in the repository.
|
||||
*/
|
||||
class CheckVersion
|
||||
{
|
||||
|
|
@ -42,7 +42,8 @@ class CheckVersion
|
|||
|
||||
switch ($checkurl) {
|
||||
case 'master':
|
||||
$checked_url = 'https://raw.githubusercontent.com/friendica/friendica/master/VERSION';
|
||||
case 'stable':
|
||||
$checked_url = 'https://raw.githubusercontent.com/friendica/friendica/stable/VERSION';
|
||||
break;
|
||||
case 'develop':
|
||||
$checked_url = 'https://raw.githubusercontent.com/friendica/friendica/develop/VERSION';
|
||||
|
|
|
|||
|
|
@ -128,6 +128,7 @@ class CronJobs
|
|||
while ($user = DBA::fetch($users)) {
|
||||
User::remove($user['uid']);
|
||||
}
|
||||
DBA::close($users);
|
||||
|
||||
// delete user records for recently removed accounts
|
||||
$users = DBA::select('user', ['uid'], ["`account_removed` AND `account_expires_on` < UTC_TIMESTAMP() "]);
|
||||
|
|
@ -140,6 +141,7 @@ class CronJobs
|
|||
|
||||
DBA::delete('user', ['uid' => $user['uid']]);
|
||||
}
|
||||
DBA::close($users);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ class DBClean {
|
|||
* 3: Orphaned data from thread table.
|
||||
* 4: Orphaned data from notify table.
|
||||
* 5: Orphaned data from notify-threads table.
|
||||
* 6: Orphaned data from sign table.
|
||||
* 6: Legacy functionality (removed)
|
||||
* 7: Orphaned data from term table.
|
||||
* 8: Expired threads.
|
||||
* 9: Old global item entries from expired threads.
|
||||
|
|
@ -224,57 +224,11 @@ class DBClean {
|
|||
DI::config()->set('system', 'finished-dbclean-5', true);
|
||||
}
|
||||
} elseif ($stage == 6) {
|
||||
$last_id = DI::config()->get('system', 'dbclean-last-id-6', 0);
|
||||
|
||||
Logger::log("Deleting orphaned data from sign table. Last ID: ".$last_id);
|
||||
$r = DBA::p("SELECT `iid`, `id` FROM `sign`
|
||||
WHERE NOT EXISTS (SELECT `id` FROM `item` WHERE `item`.`id` = `sign`.`iid`) AND `id` >= ?
|
||||
ORDER BY `id` LIMIT ?", $last_id, $limit);
|
||||
$count = DBA::numRows($r);
|
||||
if ($count > 0) {
|
||||
Logger::log("found sign orphans: ".$count);
|
||||
while ($orphan = DBA::fetch($r)) {
|
||||
$last_id = $orphan["id"];
|
||||
DBA::delete('sign', ['iid' => $orphan["iid"]]);
|
||||
}
|
||||
Worker::add(PRIORITY_MEDIUM, 'DBClean', 6, $last_id);
|
||||
} else {
|
||||
Logger::log("No sign orphans found");
|
||||
}
|
||||
DBA::close($r);
|
||||
Logger::log("Done deleting ".$count." orphaned data from sign table. Last ID: ".$last_id);
|
||||
|
||||
DI::config()->set('system', 'dbclean-last-id-6', $last_id);
|
||||
|
||||
if ($count < $limit) {
|
||||
DI::config()->set('system', 'finished-dbclean-6', true);
|
||||
}
|
||||
// The legacy functionality had been removed
|
||||
DI::config()->set('system', 'finished-dbclean-6', true);
|
||||
} elseif ($stage == 7) {
|
||||
$last_id = DI::config()->get('system', 'dbclean-last-id-7', 0);
|
||||
|
||||
Logger::log("Deleting orphaned data from term table. Last ID: ".$last_id);
|
||||
$r = DBA::p("SELECT `oid`, `tid` FROM `term`
|
||||
WHERE NOT EXISTS (SELECT `id` FROM `item` WHERE `item`.`id` = `term`.`oid`) AND `tid` >= ?
|
||||
ORDER BY `tid` LIMIT ?", $last_id, $limit);
|
||||
$count = DBA::numRows($r);
|
||||
if ($count > 0) {
|
||||
Logger::log("found term orphans: ".$count);
|
||||
while ($orphan = DBA::fetch($r)) {
|
||||
$last_id = $orphan["tid"];
|
||||
DBA::delete('term', ['oid' => $orphan["oid"]]);
|
||||
}
|
||||
Worker::add(PRIORITY_MEDIUM, 'DBClean', 7, $last_id);
|
||||
} else {
|
||||
Logger::log("No term orphans found");
|
||||
}
|
||||
DBA::close($r);
|
||||
Logger::log("Done deleting ".$count." orphaned data from term table. Last ID: ".$last_id);
|
||||
|
||||
DI::config()->set('system', 'dbclean-last-id-7', $last_id);
|
||||
|
||||
if ($count < $limit) {
|
||||
DI::config()->set('system', 'finished-dbclean-7', true);
|
||||
}
|
||||
// The legacy functionality had been removed
|
||||
DI::config()->set('system', 'finished-dbclean-7', true);
|
||||
} elseif ($stage == 8) {
|
||||
if ($days <= 0) {
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -58,14 +58,12 @@ class Delivery
|
|||
if ($cmd == self::MAIL) {
|
||||
$target_item = DBA::selectFirst('mail', [], ['id' => $target_id]);
|
||||
if (!DBA::isResult($target_item)) {
|
||||
self::setFailedQueue($cmd, $target_id);
|
||||
return;
|
||||
}
|
||||
$uid = $target_item['uid'];
|
||||
} elseif ($cmd == self::SUGGESTION) {
|
||||
$target_item = DBA::selectFirst('fsuggest', [], ['id' => $target_id]);
|
||||
if (!DBA::isResult($target_item)) {
|
||||
self::setFailedQueue($cmd, $target_id);
|
||||
return;
|
||||
}
|
||||
$uid = $target_item['uid'];
|
||||
|
|
@ -75,7 +73,6 @@ class Delivery
|
|||
} else {
|
||||
$item = Model\Item::selectFirst(['parent'], ['id' => $target_id]);
|
||||
if (!DBA::isResult($item) || empty($item['parent'])) {
|
||||
self::setFailedQueue($cmd, $target_id);
|
||||
return;
|
||||
}
|
||||
$parent_id = intval($item['parent']);
|
||||
|
|
@ -97,13 +94,12 @@ class Delivery
|
|||
|
||||
if (empty($target_item)) {
|
||||
Logger::log('Item ' . $target_id . "wasn't found. Quitting here.");
|
||||
self::setFailedQueue($cmd, $target_id);
|
||||
return;
|
||||
}
|
||||
|
||||
if (empty($parent)) {
|
||||
Logger::log('Parent ' . $parent_id . ' for item ' . $target_id . "wasn't found. Quitting here.");
|
||||
self::setFailedQueue($cmd, $target_id);
|
||||
self::setFailedQueue($cmd, $target_item);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -113,7 +109,7 @@ class Delivery
|
|||
$uid = $target_item['uid'];
|
||||
} else {
|
||||
Logger::log('Only public users for item ' . $target_id, Logger::DEBUG);
|
||||
self::setFailedQueue($cmd, $target_id);
|
||||
self::setFailedQueue($cmd, $target_item);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -127,7 +123,7 @@ class Delivery
|
|||
|
||||
if (!empty($contact_id) && Model\Contact::isArchived($contact_id)) {
|
||||
Logger::info('Contact is archived', ['id' => $contact_id, 'cmd' => $cmd, 'item' => $target_item['id']]);
|
||||
self::setFailedQueue($cmd, $target_id);
|
||||
self::setFailedQueue($cmd, $target_item);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -187,7 +183,7 @@ class Delivery
|
|||
|
||||
$owner = Model\User::getOwnerDataById($uid);
|
||||
if (!DBA::isResult($owner)) {
|
||||
self::setFailedQueue($cmd, $target_id);
|
||||
self::setFailedQueue($cmd, $target_item);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -196,12 +192,12 @@ class Delivery
|
|||
['id' => $contact_id, 'blocked' => false, 'pending' => false, 'self' => false]
|
||||
);
|
||||
if (!DBA::isResult($contact)) {
|
||||
self::setFailedQueue($cmd, $target_id);
|
||||
self::setFailedQueue($cmd, $target_item);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Network::isUrlBlocked($contact['url'])) {
|
||||
self::setFailedQueue($cmd, $target_id);
|
||||
self::setFailedQueue($cmd, $target_item);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -242,16 +238,16 @@ class Delivery
|
|||
/**
|
||||
* Increased the "failed" counter in the item delivery data
|
||||
*
|
||||
* @param string $cmd Command
|
||||
* @param integer $id Item id
|
||||
* @param string $cmd Command
|
||||
* @param array $item Item array
|
||||
*/
|
||||
private static function setFailedQueue(string $cmd, int $id)
|
||||
private static function setFailedQueue(string $cmd, array $item)
|
||||
{
|
||||
if (!in_array($cmd, [Delivery::POST, Delivery::POKE])) {
|
||||
return;
|
||||
}
|
||||
|
||||
Model\ItemDeliveryData::incrementQueueFailed($id);
|
||||
Model\Post\DeliveryData::incrementQueueFailed($item['uri-id'] ?? $item['id']);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -335,13 +331,13 @@ class Delivery
|
|||
DFRN::import($atom, $target_importer);
|
||||
|
||||
if (in_array($cmd, [Delivery::POST, Delivery::POKE])) {
|
||||
Model\ItemDeliveryData::incrementQueueDone($target_item['id'], Model\ItemDeliveryData::DFRN);
|
||||
Model\Post\DeliveryData::incrementQueueDone($target_item['uri-id'], Model\Post\DeliveryData::DFRN);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$protocol = Model\ItemDeliveryData::DFRN;
|
||||
$protocol = Model\Post\DeliveryData::DFRN;
|
||||
|
||||
// We don't have a relationship with contacts on a public post.
|
||||
// Se we transmit with the new method and via Diaspora as a fallback
|
||||
|
|
@ -357,9 +353,9 @@ class Delivery
|
|||
|
||||
if (in_array($cmd, [Delivery::POST, Delivery::POKE])) {
|
||||
if (($deliver_status >= 200) && ($deliver_status <= 299)) {
|
||||
Model\ItemDeliveryData::incrementQueueDone($target_item['id'], $protocol);
|
||||
Model\Post\DeliveryData::incrementQueueDone($target_item['uri-id'], $protocol);
|
||||
} else {
|
||||
Model\ItemDeliveryData::incrementQueueFailed($target_item['id']);
|
||||
Model\Post\DeliveryData::incrementQueueFailed($target_item['uri-id']);
|
||||
}
|
||||
}
|
||||
return;
|
||||
|
|
@ -376,11 +372,11 @@ class Delivery
|
|||
if ($deliver_status < 200) {
|
||||
// Legacy DFRN
|
||||
$deliver_status = DFRN::deliver($owner, $contact, $atom);
|
||||
$protocol = Model\ItemDeliveryData::LEGACY_DFRN;
|
||||
$protocol = Model\Post\DeliveryData::LEGACY_DFRN;
|
||||
}
|
||||
} else {
|
||||
$deliver_status = DFRN::deliver($owner, $contact, $atom);
|
||||
$protocol = Model\ItemDeliveryData::LEGACY_DFRN;
|
||||
$protocol = Model\Post\DeliveryData::LEGACY_DFRN;
|
||||
}
|
||||
|
||||
Logger::info('DFRN Delivery', ['cmd' => $cmd, 'url' => $contact['url'], 'guid' => ($target_item['guid'] ?? '') ?: $target_item['id'], 'return' => $deliver_status]);
|
||||
|
|
@ -390,7 +386,7 @@ class Delivery
|
|||
Model\Contact::unmarkForArchival($contact);
|
||||
|
||||
if (in_array($cmd, [Delivery::POST, Delivery::POKE])) {
|
||||
Model\ItemDeliveryData::incrementQueueDone($target_item['id'], $protocol);
|
||||
Model\Post\DeliveryData::incrementQueueDone($target_item['uri-id'], $protocol);
|
||||
}
|
||||
} else {
|
||||
// The message could not be delivered. We mark the contact as "dead"
|
||||
|
|
@ -398,7 +394,7 @@ class Delivery
|
|||
|
||||
Logger::info('Delivery failed: defer message', ['id' => ($target_item['guid'] ?? '') ?: $target_item['id']]);
|
||||
if (!Worker::defer() && in_array($cmd, [Delivery::POST, Delivery::POKE])) {
|
||||
Model\ItemDeliveryData::incrementQueueFailed($target_item['id']);
|
||||
Model\Post\DeliveryData::incrementQueueFailed($target_item['uri-id']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -475,7 +471,7 @@ class Delivery
|
|||
Model\Contact::unmarkForArchival($contact);
|
||||
|
||||
if (in_array($cmd, [Delivery::POST, Delivery::POKE])) {
|
||||
Model\ItemDeliveryData::incrementQueueDone($target_item['id'], Model\ItemDeliveryData::DIASPORA);
|
||||
Model\Post\DeliveryData::incrementQueueDone($target_item['uri-id'], Model\Post\DeliveryData::DIASPORA);
|
||||
}
|
||||
} else {
|
||||
// The message could not be delivered. We mark the contact as "dead"
|
||||
|
|
@ -490,10 +486,10 @@ class Delivery
|
|||
Logger::info('Delivery failed: defer message', ['id' => ($target_item['guid'] ?? '') ?: $target_item['id']]);
|
||||
// defer message for redelivery
|
||||
if (!Worker::defer() && in_array($cmd, [Delivery::POST, Delivery::POKE])) {
|
||||
Model\ItemDeliveryData::incrementQueueFailed($target_item['id']);
|
||||
Model\Post\DeliveryData::incrementQueueFailed($target_item['uri-id']);
|
||||
}
|
||||
} elseif (in_array($cmd, [Delivery::POST, Delivery::POKE])) {
|
||||
Model\ItemDeliveryData::incrementQueueFailed($target_item['id']);
|
||||
Model\Post\DeliveryData::incrementQueueFailed($target_item['uri-id']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -564,7 +560,8 @@ class Delivery
|
|||
$headers = 'From: ' . Email::encodeHeader($local_user['username'],'UTF-8') . ' <' . $local_user['email'] . '>' . "\n";
|
||||
}
|
||||
} else {
|
||||
$headers = 'From: '. Email::encodeHeader($local_user['username'], 'UTF-8') . ' <noreply@' . DI::baseUrl()->getHostname() . '>' . "\n";
|
||||
$sender = DI::config()->get('config', 'sender_email', 'noreply@' . DI::baseUrl()->getHostname());
|
||||
$headers = 'From: '. Email::encodeHeader($local_user['username'], 'UTF-8') . ' <' . $sender . '>' . "\n";
|
||||
}
|
||||
|
||||
$headers .= 'Message-Id: <' . Email::iri2msgid($target_item['uri']) . '>' . "\n";
|
||||
|
|
@ -602,7 +599,7 @@ class Delivery
|
|||
|
||||
Email::send($addr, $subject, $headers, $target_item);
|
||||
|
||||
Model\ItemDeliveryData::incrementQueueDone($target_item['id'], Model\ItemDeliveryData::MAIL);
|
||||
Model\Post\DeliveryData::incrementQueueDone($target_item['uri-id'], Model\Post\DeliveryData::MAIL);
|
||||
|
||||
Logger::info('Delivered via mail', ['guid' => $target_item['guid'], 'to' => $addr, 'subject' => $subject]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,16 +61,10 @@ class Directory
|
|||
}
|
||||
|
||||
private static function updateAll() {
|
||||
$r = q("SELECT `url` FROM `contact`
|
||||
INNER JOIN `profile` ON `profile`.`uid` = `contact`.`uid`
|
||||
INNER JOIN `user` ON `user`.`uid` = `contact`.`uid`
|
||||
WHERE `contact`.`self` AND `profile`.`net-publish` AND
|
||||
NOT `user`.`account_expired` AND `user`.`verified`");
|
||||
|
||||
if (DBA::isResult($r)) {
|
||||
foreach ($r AS $user) {
|
||||
Worker::add(PRIORITY_LOW, 'Directory', $user['url']);
|
||||
}
|
||||
$users = DBA::select('owner-view', ['url'], ['net-publish' => true, 'account_expired' => false, 'verified' => true]);
|
||||
while ($user = DBA::fetch($users)) {
|
||||
Worker::add(PRIORITY_LOW, 'Directory', $user['url']);
|
||||
}
|
||||
DBA::close($users);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,11 +52,6 @@ class Expire
|
|||
|
||||
// Normally we shouldn't have orphaned data at all.
|
||||
// If we do have some, then we have to check why.
|
||||
Logger::log('Deleting orphaned item activities - start', Logger::DEBUG);
|
||||
$condition = ["NOT EXISTS (SELECT `iaid` FROM `item` WHERE `item`.`iaid` = `item-activity`.`id`)"];
|
||||
DBA::delete('item-activity', $condition);
|
||||
Logger::log('Orphaned item activities deleted: ' . DBA::affectedRows(), Logger::DEBUG);
|
||||
|
||||
Logger::log('Deleting orphaned item content - start', Logger::DEBUG);
|
||||
$condition = ["NOT EXISTS (SELECT `icid` FROM `item` WHERE `item`.`icid` = `item-content`.`id`)"];
|
||||
DBA::delete('item-content', $condition);
|
||||
|
|
|
|||
|
|
@ -26,31 +26,41 @@ use Friendica\Database\DBA;
|
|||
|
||||
class MergeContact
|
||||
{
|
||||
public static function execute($first, $dup_id, $uid)
|
||||
/**
|
||||
* Replace all occurences of the given contact id and replace it
|
||||
*
|
||||
* @param integer $new_cid
|
||||
* @param integer $old_cid
|
||||
* @param integer $uid
|
||||
*/
|
||||
public static function execute(int $new_cid, int $old_cid, int $uid)
|
||||
{
|
||||
if (empty($first) || empty($dup_id) || ($first == $dup_id)) {
|
||||
if (empty($new_cid) || empty($old_cid) || ($new_cid == $old_cid)) {
|
||||
// Invalid request
|
||||
return;
|
||||
}
|
||||
|
||||
Logger::info('Handling duplicate', ['search' => $dup_id, 'replace' => $first]);
|
||||
Logger::info('Handling duplicate', ['search' => $old_cid, 'replace' => $new_cid]);
|
||||
|
||||
// Search and replace
|
||||
DBA::update('item', ['contact-id' => $first], ['contact-id' => $dup_id]);
|
||||
DBA::update('thread', ['contact-id' => $first], ['contact-id' => $dup_id]);
|
||||
DBA::update('mail', ['contact-id' => $first], ['contact-id' => $dup_id]);
|
||||
DBA::update('photo', ['contact-id' => $first], ['contact-id' => $dup_id]);
|
||||
DBA::update('event', ['cid' => $first], ['cid' => $dup_id]);
|
||||
DBA::update('item', ['contact-id' => $new_cid], ['contact-id' => $old_cid]);
|
||||
DBA::update('thread', ['contact-id' => $new_cid], ['contact-id' => $old_cid]);
|
||||
DBA::update('mail', ['contact-id' => $new_cid], ['contact-id' => $old_cid]);
|
||||
DBA::update('photo', ['contact-id' => $new_cid], ['contact-id' => $old_cid]);
|
||||
DBA::update('event', ['cid' => $new_cid], ['cid' => $old_cid]);
|
||||
|
||||
// These fields only contain public contact entries (uid = 0)
|
||||
if ($uid == 0) {
|
||||
DBA::update('item', ['author-id' => $first], ['author-id' => $dup_id]);
|
||||
DBA::update('item', ['owner-id' => $first], ['owner-id' => $dup_id]);
|
||||
DBA::update('thread', ['author-id' => $first], ['author-id' => $dup_id]);
|
||||
DBA::update('thread', ['owner-id' => $first], ['owner-id' => $dup_id]);
|
||||
DBA::update('post-tag', ['cid' => $new_cid], ['cid' => $old_cid]);
|
||||
DBA::update('item', ['author-id' => $new_cid], ['author-id' => $old_cid]);
|
||||
DBA::update('item', ['owner-id' => $new_cid], ['owner-id' => $old_cid]);
|
||||
DBA::update('thread', ['author-id' => $new_cid], ['author-id' => $old_cid]);
|
||||
DBA::update('thread', ['owner-id' => $new_cid], ['owner-id' => $old_cid]);
|
||||
} else {
|
||||
/// @todo Check if some other data needs to be adjusted as well, possibly the "rel" status?
|
||||
}
|
||||
|
||||
// Remove the duplicate
|
||||
DBA::delete('contact', ['id' => $dup_id]);
|
||||
DBA::delete('contact', ['id' => $old_cid]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,8 +32,9 @@ use Friendica\Model\Contact;
|
|||
use Friendica\Model\Conversation;
|
||||
use Friendica\Model\Group;
|
||||
use Friendica\Model\Item;
|
||||
use Friendica\Model\ItemDeliveryData;
|
||||
use Friendica\Model\Post;
|
||||
use Friendica\Model\PushSubscriber;
|
||||
use Friendica\Model\Tag;
|
||||
use Friendica\Model\User;
|
||||
use Friendica\Network\Probe;
|
||||
use Friendica\Protocol\ActivityPub;
|
||||
|
|
@ -165,7 +166,7 @@ class Notifier
|
|||
if (!empty($target_item) && !empty($items)) {
|
||||
$parent = $items[0];
|
||||
|
||||
$fields = ['network', 'author-id', 'author-link', 'owner-id'];
|
||||
$fields = ['network', 'author-id', 'author-link', 'author-network', 'owner-id'];
|
||||
$condition = ['uri' => $target_item["thr-parent"], 'uid' => $target_item["uid"]];
|
||||
$thr_parent = Item::selectFirst($fields, $condition);
|
||||
if (empty($thr_parent)) {
|
||||
|
|
@ -367,16 +368,11 @@ class Notifier
|
|||
}
|
||||
|
||||
// Send a salmon notification to every person we mentioned in the post
|
||||
$arr = explode(',',$target_item['tag']);
|
||||
foreach ($arr as $x) {
|
||||
//Logger::log('Checking tag '.$x, Logger::DEBUG);
|
||||
$matches = null;
|
||||
if (preg_match('/@\[url=([^\]]*)\]/',$x,$matches)) {
|
||||
$probed_contact = Probe::uri($matches[1]);
|
||||
if ($probed_contact["notify"] != "") {
|
||||
Logger::log('Notify mentioned user '.$probed_contact["url"].': '.$probed_contact["notify"]);
|
||||
$url_recipients[$probed_contact["notify"]] = $probed_contact["notify"];
|
||||
}
|
||||
foreach (Tag::getByURIId($target_item['uri-id'], [Tag::MENTION, Tag::EXCLUSIVE_MENTION, Tag::IMPLICIT_MENTION]) as $tag) {
|
||||
$probed_contact = Probe::uri($tag['url']);
|
||||
if ($probed_contact["notify"] != "") {
|
||||
Logger::log('Notify mentioned user '.$probed_contact["url"].': '.$probed_contact["notify"]);
|
||||
$url_recipients[$probed_contact["notify"]] = $probed_contact["notify"];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -441,7 +437,7 @@ class Notifier
|
|||
|
||||
// Fetch the participation list
|
||||
// The function will ensure that there are no duplicates
|
||||
$relay_list = Diaspora::participantsForThread($target_id, $relay_list);
|
||||
$relay_list = Diaspora::participantsForThread($target_item, $relay_list);
|
||||
|
||||
// Add the relay to the list, avoid duplicates.
|
||||
// Don't send community posts to the relay. Forum posts via the Diaspora protocol are looking ugly.
|
||||
|
|
@ -465,17 +461,17 @@ class Notifier
|
|||
}
|
||||
|
||||
if (!empty($rr['addr']) && ($rr['network'] == Protocol::ACTIVITYPUB) && !DBA::exists('fcontact', ['addr' => $rr['addr']])) {
|
||||
Logger::info('Contact is AP omly', ['target' => $target_id, 'contact' => $rr['url']]);
|
||||
Logger::info('Contact is AP omly, so skip delivery via legacy DFRN/Diaspora', ['target' => $target_id, 'contact' => $rr['url']]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!empty($rr['id']) && Contact::isArchived($rr['id'])) {
|
||||
Logger::info('Contact is archived', ['target' => $target_id, 'contact' => $rr['url']]);
|
||||
Logger::info('Contact is archived, so skip delivery', ['target' => $target_id, 'contact' => $rr['url']]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (self::isRemovalActivity($cmd, $owner, $rr['network'])) {
|
||||
Logger::log('Skipping dropping for ' . $rr['url'] . ' since the network supports account removal commands.', Logger::DEBUG);
|
||||
Logger::info('Contact does no supports account removal commands, so skip delivery', ['target' => $target_id, 'contact' => $rr['url']]);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -484,6 +480,11 @@ class Notifier
|
|||
continue;
|
||||
}
|
||||
|
||||
if (self::skipActivityPubForDiaspora($rr, $target_item, $thr_parent)) {
|
||||
Logger::info('Contact is from Diaspora, but the replied author is from ActivityPub, so skip delivery via Diaspora', ['id' => $target_id, 'url' => $rr['url']]);
|
||||
continue;
|
||||
}
|
||||
|
||||
$conversants[] = $rr['id'];
|
||||
|
||||
Logger::info('Public delivery', ['target' => $target_id, 'guid' => $target_item["guid"], 'to' => $rr]);
|
||||
|
|
@ -515,17 +516,17 @@ class Notifier
|
|||
}
|
||||
|
||||
if (!empty($contact['addr']) && ($contact['network'] == Protocol::ACTIVITYPUB) && !DBA::exists('fcontact', ['addr' => $contact['addr']])) {
|
||||
Logger::info('Contact is AP omly', ['target' => $target_id, 'contact' => $contact['url']]);
|
||||
Logger::info('Contact is AP omly, so skip delivery via legacy DFRN/Diaspora', ['target' => $target_id, 'contact' => $contact['url']]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!empty($contact['id']) && Contact::isArchived($contact['id'])) {
|
||||
Logger::info('Contact is archived', ['target' => $target_id, 'contact' => $contact['url']]);
|
||||
Logger::info('Contact is archived, so skip delivery', ['target' => $target_id, 'contact' => $contact['url']]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (self::isRemovalActivity($cmd, $owner, $contact['network'])) {
|
||||
Logger::log('Skipping dropping for ' . $contact['url'] . ' since the network supports account removal commands.', Logger::DEBUG);
|
||||
Logger::info('Contact does no supports account removal commands, so skip delivery', ['target' => $target_id, 'contact' => $contact['url']]);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -534,6 +535,11 @@ class Notifier
|
|||
continue;
|
||||
}
|
||||
|
||||
if (self::skipActivityPubForDiaspora($contact, $target_item, $thr_parent)) {
|
||||
Logger::info('Contact is from Diaspora, but the replied author is from ActivityPub, so skip delivery via Diaspora', ['id' => $target_id, 'url' => $rr['url']]);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Don't deliver to Diaspora if it already had been done as batch delivery
|
||||
if (($contact['network'] == Protocol::DIASPORA) && $batch_delivery) {
|
||||
Logger::log('Already delivered id ' . $target_id . ' via batch to ' . json_encode($contact), Logger::DEBUG);
|
||||
|
|
@ -573,7 +579,7 @@ class Notifier
|
|||
/// @TODO Redeliver/queue these items on failure, though there is no contact record
|
||||
$delivery_queue_count++;
|
||||
Salmon::slapper($owner, $url, $slap);
|
||||
ItemDeliveryData::incrementQueueDone($target_id, ItemDeliveryData::OSTATUS);
|
||||
Post\DeliveryData::incrementQueueDone($target_item['uri-id'], Post\DeliveryData::OSTATUS);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -595,17 +601,46 @@ class Notifier
|
|||
// Workaround for pure connector posts
|
||||
if (in_array($cmd, [Delivery::POST, Delivery::POKE])) {
|
||||
if ($delivery_queue_count == 0) {
|
||||
ItemDeliveryData::incrementQueueDone($target_item['id']);
|
||||
Post\DeliveryData::incrementQueueDone($target_item['uri-id']);
|
||||
$delivery_queue_count = 1;
|
||||
}
|
||||
|
||||
ItemDeliveryData::incrementQueueCount($target_item['id'], $delivery_queue_count);
|
||||
Post\DeliveryData::incrementQueueCount($target_item['uri-id'], $delivery_queue_count);
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current delivery shouldn't be transported to Diaspora.
|
||||
* This is done for posts from AP authors or posts that are comments to AP authors.
|
||||
*
|
||||
* @param array $contact Receiver of the post
|
||||
* @param array $item The post
|
||||
* @param array $thr_parent The thread parent
|
||||
* @return bool
|
||||
*/
|
||||
private static function skipActivityPubForDiaspora(array $contact, array $item, array $thr_parent)
|
||||
{
|
||||
// No skipping needs to be done when delivery isn't done to Diaspora
|
||||
if ($contact['network'] != Protocol::DIASPORA) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip the delivery to Diaspora if the item is from an ActivityPub author
|
||||
if ($item['author-network'] == Protocol::ACTIVITYPUB) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Skip the delivery to Diaspora if the thread parent is from an ActivityPub author
|
||||
if ($thr_parent['author-network'] == Protocol::ACTIVITYPUB) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current delivery process needs to be transported via DFRN.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -207,7 +207,7 @@ class OnePoll
|
|||
continue;
|
||||
}
|
||||
|
||||
subscribe_to_hub($h, $importer, $contact, $hubmode);
|
||||
self::subscribeToHub($h, $importer, $contact, $hubmode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -704,4 +704,56 @@ class OnePoll
|
|||
Logger::log("Mail: closing connection for ".$mailconf['user']);
|
||||
imap_close($mbox);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
* @param array $importer
|
||||
* @param array $contact
|
||||
* @param string $hubmode
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
private static function subscribeToHub(string $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;
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,59 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* @copyright Copyright (C) 2020, Friendica
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Friendica\Worker;
|
||||
|
||||
use Friendica\Core\Logger;
|
||||
use Friendica\Database\DBA;
|
||||
|
||||
class TagUpdate
|
||||
{
|
||||
public static function execute()
|
||||
{
|
||||
$messages = DBA::p("SELECT `oid`,`item`.`guid`, `item`.`created`, `item`.`received` FROM `term` INNER JOIN `item` ON `item`.`id`=`term`.`oid` WHERE `term`.`otype` = 1 AND `term`.`guid` = ''");
|
||||
|
||||
Logger::log('fetched messages: ' . DBA::numRows($messages));
|
||||
while ($message = DBA::fetch($messages)) {
|
||||
if ($message['uid'] == 0) {
|
||||
$global = true;
|
||||
|
||||
DBA::update('term', ['global' => true], ['otype' => TERM_OBJ_POST, 'guid' => $message['guid']]);
|
||||
} else {
|
||||
$global = (DBA::count('term', ['uid' => 0, 'otype' => TERM_OBJ_POST, 'guid' => $message['guid']]) > 0);
|
||||
}
|
||||
|
||||
$fields = ['guid' => $message['guid'], 'created' => $message['created'],
|
||||
'received' => $message['received'], 'global' => $global];
|
||||
DBA::update('term', $fields, ['otype' => TERM_OBJ_POST, 'oid' => $message['oid']]);
|
||||
}
|
||||
|
||||
DBA::close($messages);
|
||||
|
||||
$messages = DBA::select('item', ['guid'], ['uid' => 0]);
|
||||
|
||||
Logger::log('fetched messages: ' . DBA::numRows($messages));
|
||||
while ($message = DBA::fetch($messages)) {
|
||||
DBA::update('item', ['global' => true], ['guid' => $message['guid']]);
|
||||
}
|
||||
|
||||
DBA::close($messages);
|
||||
}
|
||||
}
|
||||
|
|
@ -96,5 +96,6 @@ class UpdateGContacts
|
|||
return;
|
||||
}
|
||||
}
|
||||
DBA::close($contacts);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,9 +29,10 @@ class UpdateGServer
|
|||
{
|
||||
/**
|
||||
* Update the given server
|
||||
* @param string $server_url Server URL
|
||||
* @param string $server_url Server URL
|
||||
* @param boolean $only_nodeinfo Only use nodeinfo for server detection
|
||||
*/
|
||||
public static function execute($server_url)
|
||||
public static function execute(string $server_url, bool $only_nodeinfo = false)
|
||||
{
|
||||
if (empty($server_url)) {
|
||||
return;
|
||||
|
|
@ -42,7 +43,7 @@ class UpdateGServer
|
|||
return;
|
||||
}
|
||||
|
||||
$ret = GServer::check($server_url);
|
||||
$ret = GServer::check($server_url, '', false, $only_nodeinfo);
|
||||
Logger::info('Updated gserver', ['url' => $server_url, 'result' => $ret]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,5 +52,6 @@ class UpdateGServers
|
|||
return;
|
||||
}
|
||||
}
|
||||
DBA::close($gservers);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@
|
|||
* {"default" => "<default value>",}
|
||||
* {"default" => NULL_DATE,} (for datetime fields)
|
||||
* {"primary" => "1",}
|
||||
* {"relation" => ["<foreign key table name>" => "<foreign key field name>"],}
|
||||
* {"foreign|relation" => ["<foreign key table name>" => "<foreign key field name>"],}
|
||||
* "comment" => "Description of the fields"
|
||||
* ],
|
||||
* ...
|
||||
|
|
@ -44,6 +44,9 @@
|
|||
* ],
|
||||
* ],
|
||||
*
|
||||
* Whenever possible prefer "foreign" before "relation" with the foreign keys.
|
||||
* "foreign" adds true foreign keys on the database level, while "relation" simulates this behaviour.
|
||||
*
|
||||
* If you need to make any change, make sure to increment the DB_UPDATE_VERSION constant value below.
|
||||
*
|
||||
*/
|
||||
|
|
@ -51,10 +54,195 @@
|
|||
use Friendica\Database\DBA;
|
||||
|
||||
if (!defined('DB_UPDATE_VERSION')) {
|
||||
define('DB_UPDATE_VERSION', 1338);
|
||||
define('DB_UPDATE_VERSION', 1355);
|
||||
}
|
||||
|
||||
return [
|
||||
// Side tables
|
||||
"gserver" => [
|
||||
"comment" => "Global servers",
|
||||
"fields" => [
|
||||
"id" => ["type" => "int unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1", "comment" => "sequential ID"],
|
||||
"url" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""],
|
||||
"nurl" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""],
|
||||
"version" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""],
|
||||
"site_name" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""],
|
||||
"info" => ["type" => "text", "comment" => ""],
|
||||
"register_policy" => ["type" => "tinyint", "not null" => "1", "default" => "0", "comment" => ""],
|
||||
"registered-users" => ["type" => "int unsigned", "not null" => "1", "default" => "0", "comment" => "Number of registered users"],
|
||||
"directory-type" => ["type" => "tinyint", "default" => "0", "comment" => "Type of directory service (Poco, Mastodon)"],
|
||||
"poco" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""],
|
||||
"noscrape" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""],
|
||||
"network" => ["type" => "char(4)", "not null" => "1", "default" => "", "comment" => ""],
|
||||
"platform" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""],
|
||||
"relay-subscribe" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "Has the server subscribed to the relay system"],
|
||||
"relay-scope" => ["type" => "varchar(10)", "not null" => "1", "default" => "", "comment" => "The scope of messages that the server wants to get"],
|
||||
"detection-method" => ["type" => "tinyint unsigned", "comment" => "Method that had been used to detect that server"],
|
||||
"created" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => ""],
|
||||
"last_poco_query" => ["type" => "datetime", "default" => DBA::NULL_DATETIME, "comment" => ""],
|
||||
"last_contact" => ["type" => "datetime", "default" => DBA::NULL_DATETIME, "comment" => ""],
|
||||
"last_failure" => ["type" => "datetime", "default" => DBA::NULL_DATETIME, "comment" => ""],
|
||||
],
|
||||
"indexes" => [
|
||||
"PRIMARY" => ["id"],
|
||||
"nurl" => ["UNIQUE", "nurl(190)"],
|
||||
]
|
||||
],
|
||||
"clients" => [
|
||||
"comment" => "OAuth usage",
|
||||
"fields" => [
|
||||
"client_id" => ["type" => "varchar(20)", "not null" => "1", "primary" => "1", "comment" => ""],
|
||||
"pw" => ["type" => "varchar(20)", "not null" => "1", "default" => "", "comment" => ""],
|
||||
"redirect_uri" => ["type" => "varchar(200)", "not null" => "1", "default" => "", "comment" => ""],
|
||||
"name" => ["type" => "text", "comment" => ""],
|
||||
"icon" => ["type" => "text", "comment" => ""],
|
||||
"uid" => ["type" => "mediumint unsigned", "not null" => "1", "default" => "0", "relation" => ["user" => "uid"], "comment" => "User id"],
|
||||
],
|
||||
"indexes" => [
|
||||
"PRIMARY" => ["client_id"],
|
||||
]
|
||||
],
|
||||
"contact" => [
|
||||
"comment" => "contact table",
|
||||
"fields" => [
|
||||
"id" => ["type" => "int unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1", "comment" => "sequential ID"],
|
||||
"uid" => ["type" => "mediumint unsigned", "not null" => "1", "default" => "0", "relation" => ["user" => "uid"], "comment" => "Owner User id"],
|
||||
"created" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => ""],
|
||||
"updated" => ["type" => "datetime", "default" => DBA::NULL_DATETIME, "comment" => "Date of last contact update"],
|
||||
"self" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "1 if the contact is the user him/her self"],
|
||||
"remote_self" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => ""],
|
||||
"rel" => ["type" => "tinyint unsigned", "not null" => "1", "default" => "0", "comment" => "The kind of the relation between the user and the contact"],
|
||||
"duplex" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => ""],
|
||||
"network" => ["type" => "char(4)", "not null" => "1", "default" => "", "comment" => "Network of the contact"],
|
||||
"protocol" => ["type" => "char(4)", "not null" => "1", "default" => "", "comment" => "Protocol of the contact"],
|
||||
"name" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => "Name that this contact is known by"],
|
||||
"nick" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => "Nick- and user name of the contact"],
|
||||
"location" => ["type" => "varchar(255)", "default" => "", "comment" => ""],
|
||||
"about" => ["type" => "text", "comment" => ""],
|
||||
"keywords" => ["type" => "text", "comment" => "public keywords (interests) of the contact"],
|
||||
"gender" => ["type" => "varchar(32)", "not null" => "1", "default" => "", "comment" => "Deprecated"],
|
||||
"xmpp" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""],
|
||||
"attag" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""],
|
||||
"avatar" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""],
|
||||
"photo" => ["type" => "varchar(255)", "default" => "", "comment" => "Link to the profile photo of the contact"],
|
||||
"thumb" => ["type" => "varchar(255)", "default" => "", "comment" => "Link to the profile photo (thumb size)"],
|
||||
"micro" => ["type" => "varchar(255)", "default" => "", "comment" => "Link to the profile photo (micro size)"],
|
||||
"site-pubkey" => ["type" => "text", "comment" => ""],
|
||||
"issued-id" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""],
|
||||
"dfrn-id" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""],
|
||||
"url" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""],
|
||||
"nurl" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""],
|
||||
"addr" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""],
|
||||
"alias" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""],
|
||||
"pubkey" => ["type" => "text", "comment" => "RSA public key 4096 bit"],
|
||||
"prvkey" => ["type" => "text", "comment" => "RSA private key 4096 bit"],
|
||||
"batch" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""],
|
||||
"request" => ["type" => "varchar(255)", "comment" => ""],
|
||||
"notify" => ["type" => "varchar(255)", "comment" => ""],
|
||||
"poll" => ["type" => "varchar(255)", "comment" => ""],
|
||||
"confirm" => ["type" => "varchar(255)", "comment" => ""],
|
||||
"subscribe" => ["type" => "varchar(255)", "comment" => ""],
|
||||
"poco" => ["type" => "varchar(255)", "comment" => ""],
|
||||
"aes_allow" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => ""],
|
||||
"ret-aes" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => ""],
|
||||
"usehub" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => ""],
|
||||
"subhub" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => ""],
|
||||
"hub-verify" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""],
|
||||
"last-update" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => "Date of the last try to update the contact info"],
|
||||
"success_update" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => "Date of the last successful contact update"],
|
||||
"failure_update" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => "Date of the last failed update"],
|
||||
"name-date" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => ""],
|
||||
"uri-date" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => ""],
|
||||
"avatar-date" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => ""],
|
||||
"term-date" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => ""],
|
||||
"last-item" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => "date of the last post"],
|
||||
"priority" => ["type" => "tinyint unsigned", "not null" => "1", "default" => "0", "comment" => ""],
|
||||
"blocked" => ["type" => "boolean", "not null" => "1", "default" => "1", "comment" => "Node-wide block status"],
|
||||
"block_reason" => ["type" => "text", "comment" => "Node-wide block reason"],
|
||||
"readonly" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "posts of the contact are readonly"],
|
||||
"writable" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => ""],
|
||||
"forum" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "contact is a forum"],
|
||||
"prv" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "contact is a private group"],
|
||||
"contact-type" => ["type" => "tinyint", "not null" => "1", "default" => "0", "comment" => ""],
|
||||
"hidden" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => ""],
|
||||
"archive" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => ""],
|
||||
"pending" => ["type" => "boolean", "not null" => "1", "default" => "1", "comment" => ""],
|
||||
"deleted" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "Contact has been deleted"],
|
||||
"rating" => ["type" => "tinyint", "not null" => "1", "default" => "0", "comment" => ""],
|
||||
"unsearchable" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "Contact prefers to not be searchable"],
|
||||
"sensitive" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "Contact posts sensitive content"],
|
||||
"baseurl" => ["type" => "varchar(255)", "default" => "", "comment" => "baseurl of the contact"],
|
||||
"gsid" => ["type" => "int unsigned", "foreign" => ["gserver" => "id", "on delete" => "restrict"], "comment" => "Global Server ID"],
|
||||
"reason" => ["type" => "text", "comment" => ""],
|
||||
"closeness" => ["type" => "tinyint unsigned", "not null" => "1", "default" => "99", "comment" => ""],
|
||||
"info" => ["type" => "mediumtext", "comment" => ""],
|
||||
"profile-id" => ["type" => "int unsigned", "comment" => "Deprecated"],
|
||||
"bdyear" => ["type" => "varchar(4)", "not null" => "1", "default" => "", "comment" => ""],
|
||||
"bd" => ["type" => "date", "not null" => "1", "default" => DBA::NULL_DATE, "comment" => ""],
|
||||
"notify_new_posts" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => ""],
|
||||
"fetch_further_information" => ["type" => "tinyint unsigned", "not null" => "1", "default" => "0", "comment" => ""],
|
||||
"ffi_keyword_denylist" => ["type" => "text", "comment" => ""],
|
||||
],
|
||||
"indexes" => [
|
||||
"PRIMARY" => ["id"],
|
||||
"uid_name" => ["uid", "name(190)"],
|
||||
"self_uid" => ["self", "uid"],
|
||||
"alias_uid" => ["alias(32)", "uid"],
|
||||
"pending_uid" => ["pending", "uid"],
|
||||
"blocked_uid" => ["blocked", "uid"],
|
||||
"uid_rel_network_poll" => ["uid", "rel", "network", "poll(64)", "archive"],
|
||||
"uid_network_batch" => ["uid", "network", "batch(64)"],
|
||||
"addr_uid" => ["addr(32)", "uid"],
|
||||
"nurl_uid" => ["nurl(32)", "uid"],
|
||||
"nick_uid" => ["nick(32)", "uid"],
|
||||
"attag_uid" => ["attag(32)", "uid"],
|
||||
"dfrn-id" => ["dfrn-id(64)"],
|
||||
"issued-id" => ["issued-id(64)"],
|
||||
"gsid" => ["gsid"]
|
||||
]
|
||||
],
|
||||
"item-uri" => [
|
||||
"comment" => "URI and GUID for items",
|
||||
"fields" => [
|
||||
"id" => ["type" => "int unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1"],
|
||||
"uri" => ["type" => "varbinary(255)", "not null" => "1", "comment" => "URI of an item"],
|
||||
"guid" => ["type" => "varbinary(255)", "comment" => "A unique identifier for an item"]
|
||||
],
|
||||
"indexes" => [
|
||||
"PRIMARY" => ["id"],
|
||||
"uri" => ["UNIQUE", "uri"],
|
||||
"guid" => ["guid"]
|
||||
]
|
||||
],
|
||||
"permissionset" => [
|
||||
"comment" => "",
|
||||
"fields" => [
|
||||
"id" => ["type" => "int unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1", "comment" => "sequential ID"],
|
||||
"uid" => ["type" => "mediumint unsigned", "not null" => "1", "default" => "0", "relation" => ["user" => "uid"], "comment" => "Owner id of this permission set"],
|
||||
"allow_cid" => ["type" => "mediumtext", "comment" => "Access Control - list of allowed contact.id '<19><78>'"],
|
||||
"allow_gid" => ["type" => "mediumtext", "comment" => "Access Control - list of allowed groups"],
|
||||
"deny_cid" => ["type" => "mediumtext", "comment" => "Access Control - list of denied contact.id"],
|
||||
"deny_gid" => ["type" => "mediumtext", "comment" => "Access Control - list of denied groups"],
|
||||
],
|
||||
"indexes" => [
|
||||
"PRIMARY" => ["id"],
|
||||
"uid_allow_cid_allow_gid_deny_cid_deny_gid" => ["allow_cid(50)", "allow_gid(30)", "deny_cid(50)", "deny_gid(30)"],
|
||||
]
|
||||
],
|
||||
"tag" => [
|
||||
"comment" => "tags and mentions",
|
||||
"fields" => [
|
||||
"id" => ["type" => "int unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1", "comment" => ""],
|
||||
"name" => ["type" => "varchar(96)", "not null" => "1", "default" => "", "comment" => ""],
|
||||
"url" => ["type" => "varbinary(255)", "not null" => "1", "default" => "", "comment" => ""]
|
||||
],
|
||||
"indexes" => [
|
||||
"PRIMARY" => ["id"],
|
||||
"type_name_url" => ["UNIQUE", "name", "url"],
|
||||
"url" => ["url"]
|
||||
]
|
||||
],
|
||||
// Main tables
|
||||
"2fa_app_specific_password" => [
|
||||
"comment" => "Two-factor app-specific _password",
|
||||
"fields" => [
|
||||
|
|
@ -117,7 +305,9 @@ return [
|
|||
"addr" => ["type" => "varchar(255)", "comment" => ""],
|
||||
"alias" => ["type" => "varchar(255)", "comment" => ""],
|
||||
"pubkey" => ["type" => "text", "comment" => ""],
|
||||
"subscribe" => ["type" => "varchar(255)", "comment" => ""],
|
||||
"baseurl" => ["type" => "varchar(255)", "comment" => "baseurl of the ap contact"],
|
||||
"gsid" => ["type" => "int unsigned", "foreign" => ["gserver" => "id", "on delete" => "restrict"], "comment" => "Global Server ID"],
|
||||
"generator" => ["type" => "varchar(255)", "comment" => "Name of the contact's system"],
|
||||
"following_count" => ["type" => "int unsigned", "default" => 0, "comment" => "Number of following contacts"],
|
||||
"followers_count" => ["type" => "int unsigned", "default" => 0, "comment" => "Number of followers"],
|
||||
|
|
@ -128,7 +318,8 @@ return [
|
|||
"PRIMARY" => ["url"],
|
||||
"addr" => ["addr(32)"],
|
||||
"alias" => ["alias(190)"],
|
||||
"url" => ["followers(190)"]
|
||||
"followers" => ["followers(190)"],
|
||||
"gsid" => ["gsid"]
|
||||
]
|
||||
],
|
||||
"attach" => [
|
||||
|
|
@ -158,7 +349,7 @@ return [
|
|||
"comment" => "OAuth usage",
|
||||
"fields" => [
|
||||
"id" => ["type" => "varchar(40)", "not null" => "1", "primary" => "1", "comment" => ""],
|
||||
"client_id" => ["type" => "varchar(20)", "not null" => "1", "default" => "", "relation" => ["clients" => "client_id"],
|
||||
"client_id" => ["type" => "varchar(20)", "not null" => "1", "default" => "", "foreign" => ["clients" => "client_id"],
|
||||
"comment" => ""],
|
||||
"redirect_uri" => ["type" => "varchar(200)", "not null" => "1", "default" => "", "comment" => ""],
|
||||
"expires" => ["type" => "int", "not null" => "1", "default" => "0", "comment" => ""],
|
||||
|
|
@ -166,6 +357,7 @@ return [
|
|||
],
|
||||
"indexes" => [
|
||||
"PRIMARY" => ["id"],
|
||||
"client_id" => ["client_id"]
|
||||
]
|
||||
],
|
||||
"cache" => [
|
||||
|
|
@ -195,20 +387,6 @@ return [
|
|||
"PRIMARY" => ["id"],
|
||||
]
|
||||
],
|
||||
"clients" => [
|
||||
"comment" => "OAuth usage",
|
||||
"fields" => [
|
||||
"client_id" => ["type" => "varchar(20)", "not null" => "1", "primary" => "1", "comment" => ""],
|
||||
"pw" => ["type" => "varchar(20)", "not null" => "1", "default" => "", "comment" => ""],
|
||||
"redirect_uri" => ["type" => "varchar(200)", "not null" => "1", "default" => "", "comment" => ""],
|
||||
"name" => ["type" => "text", "comment" => ""],
|
||||
"icon" => ["type" => "text", "comment" => ""],
|
||||
"uid" => ["type" => "mediumint unsigned", "not null" => "1", "default" => "0", "relation" => ["user" => "uid"], "comment" => "User id"],
|
||||
],
|
||||
"indexes" => [
|
||||
"PRIMARY" => ["client_id"],
|
||||
]
|
||||
],
|
||||
"config" => [
|
||||
"comment" => "main configuration storage",
|
||||
"fields" => [
|
||||
|
|
@ -222,101 +400,6 @@ return [
|
|||
"cat_k" => ["UNIQUE", "cat", "k"],
|
||||
]
|
||||
],
|
||||
"contact" => [
|
||||
"comment" => "contact table",
|
||||
"fields" => [
|
||||
"id" => ["type" => "int unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1", "comment" => "sequential ID"],
|
||||
"uid" => ["type" => "mediumint unsigned", "not null" => "1", "default" => "0", "relation" => ["user" => "uid"], "comment" => "Owner User id"],
|
||||
"created" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => ""],
|
||||
"updated" => ["type" => "datetime", "default" => DBA::NULL_DATETIME, "comment" => "Date of last contact update"],
|
||||
"self" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "1 if the contact is the user him/her self"],
|
||||
"remote_self" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => ""],
|
||||
"rel" => ["type" => "tinyint unsigned", "not null" => "1", "default" => "0", "comment" => "The kind of the relation between the user and the contact"],
|
||||
"duplex" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => ""],
|
||||
"network" => ["type" => "char(4)", "not null" => "1", "default" => "", "comment" => "Network of the contact"],
|
||||
"protocol" => ["type" => "char(4)", "not null" => "1", "default" => "", "comment" => "Protocol of the contact"],
|
||||
"name" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => "Name that this contact is known by"],
|
||||
"nick" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => "Nick- and user name of the contact"],
|
||||
"location" => ["type" => "varchar(255)", "default" => "", "comment" => ""],
|
||||
"about" => ["type" => "text", "comment" => ""],
|
||||
"keywords" => ["type" => "text", "comment" => "public keywords (interests) of the contact"],
|
||||
"gender" => ["type" => "varchar(32)", "not null" => "1", "default" => "", "comment" => "Deprecated"],
|
||||
"xmpp" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""],
|
||||
"attag" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""],
|
||||
"avatar" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""],
|
||||
"photo" => ["type" => "varchar(255)", "default" => "", "comment" => "Link to the profile photo of the contact"],
|
||||
"thumb" => ["type" => "varchar(255)", "default" => "", "comment" => "Link to the profile photo (thumb size)"],
|
||||
"micro" => ["type" => "varchar(255)", "default" => "", "comment" => "Link to the profile photo (micro size)"],
|
||||
"site-pubkey" => ["type" => "text", "comment" => ""],
|
||||
"issued-id" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""],
|
||||
"dfrn-id" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""],
|
||||
"url" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""],
|
||||
"nurl" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""],
|
||||
"addr" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""],
|
||||
"alias" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""],
|
||||
"pubkey" => ["type" => "text", "comment" => "RSA public key 4096 bit"],
|
||||
"prvkey" => ["type" => "text", "comment" => "RSA private key 4096 bit"],
|
||||
"batch" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""],
|
||||
"request" => ["type" => "varchar(255)", "comment" => ""],
|
||||
"notify" => ["type" => "varchar(255)", "comment" => ""],
|
||||
"poll" => ["type" => "varchar(255)", "comment" => ""],
|
||||
"confirm" => ["type" => "varchar(255)", "comment" => ""],
|
||||
"poco" => ["type" => "varchar(255)", "comment" => ""],
|
||||
"aes_allow" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => ""],
|
||||
"ret-aes" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => ""],
|
||||
"usehub" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => ""],
|
||||
"subhub" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => ""],
|
||||
"hub-verify" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""],
|
||||
"last-update" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => "Date of the last try to update the contact info"],
|
||||
"success_update" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => "Date of the last successful contact update"],
|
||||
"failure_update" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => "Date of the last failed update"],
|
||||
"name-date" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => ""],
|
||||
"uri-date" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => ""],
|
||||
"avatar-date" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => ""],
|
||||
"term-date" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => ""],
|
||||
"last-item" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => "date of the last post"],
|
||||
"priority" => ["type" => "tinyint unsigned", "not null" => "1", "default" => "0", "comment" => ""],
|
||||
"blocked" => ["type" => "boolean", "not null" => "1", "default" => "1", "comment" => "Node-wide block status"],
|
||||
"block_reason" => ["type" => "text", "comment" => "Node-wide block reason"],
|
||||
"readonly" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "posts of the contact are readonly"],
|
||||
"writable" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => ""],
|
||||
"forum" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "contact is a forum"],
|
||||
"prv" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "contact is a private group"],
|
||||
"contact-type" => ["type" => "tinyint", "not null" => "1", "default" => "0", "comment" => ""],
|
||||
"hidden" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => ""],
|
||||
"archive" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => ""],
|
||||
"pending" => ["type" => "boolean", "not null" => "1", "default" => "1", "comment" => ""],
|
||||
"deleted" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "Contact has been deleted"],
|
||||
"rating" => ["type" => "tinyint", "not null" => "1", "default" => "0", "comment" => ""],
|
||||
"unsearchable" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "Contact prefers to not be searchable"],
|
||||
"sensitive" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "Contact posts sensitive content"],
|
||||
"baseurl" => ["type" => "varchar(255)", "default" => "", "comment" => "baseurl of the contact"],
|
||||
"reason" => ["type" => "text", "comment" => ""],
|
||||
"closeness" => ["type" => "tinyint unsigned", "not null" => "1", "default" => "99", "comment" => ""],
|
||||
"info" => ["type" => "mediumtext", "comment" => ""],
|
||||
"profile-id" => ["type" => "int unsigned", "comment" => "Deprecated"],
|
||||
"bdyear" => ["type" => "varchar(4)", "not null" => "1", "default" => "", "comment" => ""],
|
||||
"bd" => ["type" => "date", "not null" => "1", "default" => DBA::NULL_DATE, "comment" => ""],
|
||||
"notify_new_posts" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => ""],
|
||||
"fetch_further_information" => ["type" => "tinyint unsigned", "not null" => "1", "default" => "0", "comment" => ""],
|
||||
"ffi_keyword_blacklist" => ["type" => "text", "comment" => ""],
|
||||
],
|
||||
"indexes" => [
|
||||
"PRIMARY" => ["id"],
|
||||
"uid_name" => ["uid", "name(190)"],
|
||||
"self_uid" => ["self", "uid"],
|
||||
"alias_uid" => ["alias(32)", "uid"],
|
||||
"pending_uid" => ["pending", "uid"],
|
||||
"blocked_uid" => ["blocked", "uid"],
|
||||
"uid_rel_network_poll" => ["uid", "rel", "network", "poll(64)", "archive"],
|
||||
"uid_network_batch" => ["uid", "network", "batch(64)"],
|
||||
"addr_uid" => ["addr(32)", "uid"],
|
||||
"nurl_uid" => ["nurl(32)", "uid"],
|
||||
"nick_uid" => ["nick(32)", "uid"],
|
||||
"dfrn-id" => ["dfrn-id(64)"],
|
||||
"issued-id" => ["issued-id(64)"],
|
||||
]
|
||||
],
|
||||
"contact-relation" => [
|
||||
"comment" => "Contact relations",
|
||||
"fields" => [
|
||||
|
|
@ -367,7 +450,7 @@ return [
|
|||
"diaspora-interaction" => [
|
||||
"comment" => "Signed Diaspora Interaction",
|
||||
"fields" => [
|
||||
"uri-id" => ["type" => "int unsigned", "not null" => "1", "primary" => "1", "relation" => ["item-uri" => "id"], "comment" => "Id of the item-uri table entry that contains the item uri"],
|
||||
"uri-id" => ["type" => "int unsigned", "not null" => "1", "primary" => "1", "foreign" => ["item-uri" => "id"], "comment" => "Id of the item-uri table entry that contains the item uri"],
|
||||
"interaction" => ["type" => "mediumtext", "comment" => "The Diaspora interaction"]
|
||||
],
|
||||
"indexes" => [
|
||||
|
|
@ -492,6 +575,7 @@ return [
|
|||
"alias" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""],
|
||||
"generation" => ["type" => "tinyint unsigned", "not null" => "1", "default" => "0", "comment" => ""],
|
||||
"server_url" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => "baseurl of the contacts server"],
|
||||
"gsid" => ["type" => "int unsigned", "foreign" => ["gserver" => "id", "on delete" => "restrict"], "comment" => "Global Server ID"],
|
||||
],
|
||||
"indexes" => [
|
||||
"PRIMARY" => ["id"],
|
||||
|
|
@ -501,6 +585,7 @@ return [
|
|||
"addr" => ["addr(64)"],
|
||||
"hide_network_updated" => ["hide", "network", "updated"],
|
||||
"updated" => ["updated"],
|
||||
"gsid" => ["gsid"]
|
||||
]
|
||||
],
|
||||
"gfollower" => [
|
||||
|
|
@ -558,34 +643,6 @@ return [
|
|||
"gid_contactid" => ["UNIQUE", "gid", "contact-id"],
|
||||
]
|
||||
],
|
||||
"gserver" => [
|
||||
"comment" => "Global servers",
|
||||
"fields" => [
|
||||
"id" => ["type" => "int unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1", "comment" => "sequential ID"],
|
||||
"url" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""],
|
||||
"nurl" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""],
|
||||
"version" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""],
|
||||
"site_name" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""],
|
||||
"info" => ["type" => "text", "comment" => ""],
|
||||
"register_policy" => ["type" => "tinyint", "not null" => "1", "default" => "0", "comment" => ""],
|
||||
"registered-users" => ["type" => "int unsigned", "not null" => "1", "default" => "0", "comment" => "Number of registered users"],
|
||||
"directory-type" => ["type" => "tinyint", "default" => "0", "comment" => "Type of directory service (Poco, Mastodon)"],
|
||||
"poco" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""],
|
||||
"noscrape" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""],
|
||||
"network" => ["type" => "char(4)", "not null" => "1", "default" => "", "comment" => ""],
|
||||
"platform" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""],
|
||||
"relay-subscribe" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "Has the server subscribed to the relay system"],
|
||||
"relay-scope" => ["type" => "varchar(10)", "not null" => "1", "default" => "", "comment" => "The scope of messages that the server wants to get"],
|
||||
"created" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => ""],
|
||||
"last_poco_query" => ["type" => "datetime", "default" => DBA::NULL_DATETIME, "comment" => ""],
|
||||
"last_contact" => ["type" => "datetime", "default" => DBA::NULL_DATETIME, "comment" => ""],
|
||||
"last_failure" => ["type" => "datetime", "default" => DBA::NULL_DATETIME, "comment" => ""],
|
||||
],
|
||||
"indexes" => [
|
||||
"PRIMARY" => ["id"],
|
||||
"nurl" => ["UNIQUE", "nurl(190)"],
|
||||
]
|
||||
],
|
||||
"gserver-tag" => [
|
||||
"comment" => "Tags that the server has subscribed",
|
||||
"fields" => [
|
||||
|
|
@ -652,13 +709,13 @@ return [
|
|||
"id" => ["type" => "int unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1", "relation" => ["thread" => "iid"]],
|
||||
"guid" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => "A unique identifier for this item"],
|
||||
"uri" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""],
|
||||
"uri-id" => ["type" => "int unsigned", "relation" => ["item-uri" => "id"], "comment" => "Id of the item-uri table entry that contains the item uri"],
|
||||
"uri-id" => ["type" => "int unsigned", "foreign" => ["item-uri" => "id"], "comment" => "Id of the item-uri table entry that contains the item uri"],
|
||||
"uri-hash" => ["type" => "varchar(80)", "not null" => "1", "default" => "", "comment" => "RIPEMD-128 hash from uri"],
|
||||
"parent" => ["type" => "int unsigned", "not null" => "1", "default" => "0", "relation" => ["item" => "id"], "comment" => "item.id of the parent to this item if it is a reply of some form; otherwise this must be set to the id of this item"],
|
||||
"parent-uri" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => "uri of the parent to this item"],
|
||||
"parent-uri-id" => ["type" => "int unsigned", "relation" => ["item-uri" => "id"], "comment" => "Id of the item-uri table that contains the parent uri"],
|
||||
"parent-uri-id" => ["type" => "int unsigned", "foreign" => ["item-uri" => "id"], "comment" => "Id of the item-uri table that contains the parent uri"],
|
||||
"thr-parent" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => "If the parent of this item is not the top-level item in the conversation, the uri of the immediate parent; otherwise set to parent-uri"],
|
||||
"thr-parent-id" => ["type" => "int unsigned", "relation" => ["item-uri" => "id"], "comment" => "Id of the item-uri table that contains the thread parent uri"],
|
||||
"thr-parent-id" => ["type" => "int unsigned", "foreign" => ["item-uri" => "id"], "comment" => "Id of the item-uri table that contains the thread parent uri"],
|
||||
"created" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => "Creation timestamp."],
|
||||
"edited" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => "Date of last edit (default is created)"],
|
||||
"commented" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => "Date of last comment/reply to this item"],
|
||||
|
|
@ -670,6 +727,7 @@ return [
|
|||
"author-id" => ["type" => "int unsigned", "not null" => "1", "default" => "0", "relation" => ["contact" => "id"], "comment" => "Link to the contact table with uid=0 of the author of this item"],
|
||||
"icid" => ["type" => "int unsigned", "relation" => ["item-content" => "id"], "comment" => "Id of the item-content table entry that contains the whole item content"],
|
||||
"iaid" => ["type" => "int unsigned", "relation" => ["item-activity" => "id"], "comment" => "Id of the item-activity table entry that contains the activity data"],
|
||||
"vid" => ["type" => "smallint unsigned", "relation" => ["verb" => "id"], "comment" => "Id of the verb table entry that contains the activity verbs"],
|
||||
"extid" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""],
|
||||
"post-type" => ["type" => "tinyint unsigned", "not null" => "1", "default" => "0", "comment" => "Post type (personal note, bookmark, ...)"],
|
||||
"global" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => ""],
|
||||
|
|
@ -687,7 +745,7 @@ return [
|
|||
"unseen" => ["type" => "boolean", "not null" => "1", "default" => "1", "comment" => "item has not been seen"],
|
||||
"mention" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "The owner of this item was mentioned in it"],
|
||||
"forum_mode" => ["type" => "tinyint unsigned", "not null" => "1", "default" => "0", "comment" => ""],
|
||||
"psid" => ["type" => "int unsigned", "relation" => ["permissionset" => "id"], "comment" => "ID of the permission set of this post"],
|
||||
"psid" => ["type" => "int unsigned", "foreign" => ["permissionset" => "id", "on delete" => "restrict"], "comment" => "ID of the permission set of this post"],
|
||||
// It has to be decided whether these fields belong to the user or the structure
|
||||
"resource-id" => ["type" => "varchar(32)", "not null" => "1", "default" => "", "comment" => "Used to link other tables to items, it identifies the linked resource (e.g. photo) and if set must also set resource_type"],
|
||||
"event-id" => ["type" => "int unsigned", "not null" => "1", "default" => "0", "relation" => ["event" => "id"], "comment" => "Used to link to the event.id"],
|
||||
|
|
@ -754,6 +812,9 @@ return [
|
|||
"icid" => ["icid"],
|
||||
"iaid" => ["iaid"],
|
||||
"psid_wall" => ["psid", "wall"],
|
||||
"uri-id" => ["uri-id"],
|
||||
"parent-uri-id" => ["parent-uri-id"],
|
||||
"thr-parent-id" => ["thr-parent-id"],
|
||||
]
|
||||
],
|
||||
"item-activity" => [
|
||||
|
|
@ -761,7 +822,7 @@ return [
|
|||
"fields" => [
|
||||
"id" => ["type" => "int unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1"],
|
||||
"uri" => ["type" => "varchar(255)", "comment" => ""],
|
||||
"uri-id" => ["type" => "int unsigned", "relation" => ["item-uri" => "id"], "comment" => "Id of the item-uri table entry that contains the item uri"],
|
||||
"uri-id" => ["type" => "int unsigned", "foreign" => ["item-uri" => "id"], "comment" => "Id of the item-uri table entry that contains the item uri"],
|
||||
"uri-hash" => ["type" => "varchar(80)", "not null" => "1", "default" => "", "comment" => "RIPEMD-128 hash from uri"],
|
||||
"activity" => ["type" => "smallint unsigned", "not null" => "1", "default" => "0", "comment" => ""]
|
||||
],
|
||||
|
|
@ -777,7 +838,7 @@ return [
|
|||
"fields" => [
|
||||
"id" => ["type" => "int unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1"],
|
||||
"uri" => ["type" => "varchar(255)", "comment" => ""],
|
||||
"uri-id" => ["type" => "int unsigned", "relation" => ["item-uri" => "id"], "comment" => "Id of the item-uri table entry that contains the item uri"],
|
||||
"uri-id" => ["type" => "int unsigned", "foreign" => ["item-uri" => "id"], "comment" => "Id of the item-uri table entry that contains the item uri"],
|
||||
"uri-plink-hash" => ["type" => "varchar(80)", "not null" => "1", "default" => "", "comment" => "RIPEMD-128 hash from uri"],
|
||||
"title" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => "item title"],
|
||||
"content-warning" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""],
|
||||
|
|
@ -803,38 +864,6 @@ return [
|
|||
"uri-id" => ["uri-id"]
|
||||
]
|
||||
],
|
||||
"item-delivery-data" => [
|
||||
"comment" => "Delivery data for items",
|
||||
"fields" => [
|
||||
"iid" => ["type" => "int unsigned", "not null" => "1", "primary" => "1", "relation" => ["item" => "id"], "comment" => "Item id"],
|
||||
"postopts" => ["type" => "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" => ["type" => "mediumtext", "comment" => "Additional receivers of the linked item"],
|
||||
"queue_count" => ["type" => "mediumint", "not null" => "1", "default" => "0", "comment" => "Initial number of delivery recipients, used as item.delivery_queue_count"],
|
||||
"queue_done" => ["type" => "mediumint", "not null" => "1", "default" => "0", "comment" => "Number of successful deliveries, used as item.delivery_queue_done"],
|
||||
"queue_failed" => ["type" => "mediumint", "not null" => "1", "default" => "0", "comment" => "Number of unsuccessful deliveries, used as item.delivery_queue_failed"],
|
||||
"activitypub" => ["type" => "mediumint", "not null" => "1", "default" => "0", "comment" => "Number of successful deliveries via ActivityPub"],
|
||||
"dfrn" => ["type" => "mediumint", "not null" => "1", "default" => "0", "comment" => "Number of successful deliveries via DFRN"],
|
||||
"legacy_dfrn" => ["type" => "mediumint", "not null" => "1", "default" => "0", "comment" => "Number of successful deliveries via legacy DFRN"],
|
||||
"diaspora" => ["type" => "mediumint", "not null" => "1", "default" => "0", "comment" => "Number of successful deliveries via Diaspora"],
|
||||
"ostatus" => ["type" => "mediumint", "not null" => "1", "default" => "0", "comment" => "Number of successful deliveries via OStatus"],
|
||||
],
|
||||
"indexes" => [
|
||||
"PRIMARY" => ["iid"],
|
||||
]
|
||||
],
|
||||
"item-uri" => [
|
||||
"comment" => "URI and GUID for items",
|
||||
"fields" => [
|
||||
"id" => ["type" => "int unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1"],
|
||||
"uri" => ["type" => "varbinary(255)", "not null" => "1", "comment" => "URI of an item"],
|
||||
"guid" => ["type" => "varbinary(255)", "comment" => "A unique identifier for an item"]
|
||||
],
|
||||
"indexes" => [
|
||||
"PRIMARY" => ["id"],
|
||||
"uri" => ["UNIQUE", "uri"],
|
||||
"guid" => ["guid"]
|
||||
]
|
||||
],
|
||||
"locks" => [
|
||||
"comment" => "",
|
||||
"fields" => [
|
||||
|
|
@ -926,6 +955,8 @@ return [
|
|||
"link" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""],
|
||||
"iid" => ["type" => "int unsigned", "not null" => "1", "default" => "0", "relation" => ["item" => "id"], "comment" => "item.id"],
|
||||
"parent" => ["type" => "int unsigned", "not null" => "1", "default" => "0", "relation" => ["item" => "id"], "comment" => ""],
|
||||
"uri-id" => ["type" => "int unsigned", "relation" => ["item-uri" => "id"], "comment" => "Item-uri id of the related post"],
|
||||
"parent-uri-id" => ["type" => "int unsigned", "relation" => ["item-uri" => "id"], "comment" => "Item-uri id of the parent of the related post"],
|
||||
"seen" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => ""],
|
||||
"verb" => ["type" => "varchar(100)", "not null" => "1", "default" => "", "comment" => ""],
|
||||
"otype" => ["type" => "varchar(10)", "not null" => "1", "default" => "", "comment" => ""],
|
||||
|
|
@ -944,14 +975,15 @@ return [
|
|||
"fields" => [
|
||||
"id" => ["type" => "int unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1", "comment" => "sequential ID"],
|
||||
"notify-id" => ["type" => "int unsigned", "not null" => "1", "default" => "0", "relation" => ["notify" => "id"], "comment" => ""],
|
||||
"master-parent-item" => ["type" => "int unsigned", "not null" => "1", "default" => "0", "relation" => ["item" => "id"],
|
||||
"comment" => ""],
|
||||
"master-parent-item" => ["type" => "int unsigned", "not null" => "1", "default" => "0", "relation" => ["item" => "id"], "comment" => ""],
|
||||
"master-parent-uri-id" => ["type" => "int unsigned", "relation" => ["item-uri" => "id"], "comment" => "Item-uri id of the parent of the related post"],
|
||||
"parent-item" => ["type" => "int unsigned", "not null" => "1", "default" => "0", "comment" => ""],
|
||||
"receiver-uid" => ["type" => "mediumint unsigned", "not null" => "1", "default" => "0", "relation" => ["user" => "uid"],
|
||||
"comment" => "User id"],
|
||||
],
|
||||
"indexes" => [
|
||||
"PRIMARY" => ["id"],
|
||||
"master-parent-uri-id" => ["master-parent-uri-id"],
|
||||
]
|
||||
],
|
||||
"oembed" => [
|
||||
|
|
@ -1023,21 +1055,6 @@ return [
|
|||
"uid_cat_k" => ["UNIQUE", "uid", "cat", "k"],
|
||||
]
|
||||
],
|
||||
"permissionset" => [
|
||||
"comment" => "",
|
||||
"fields" => [
|
||||
"id" => ["type" => "int unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1", "comment" => "sequential ID"],
|
||||
"uid" => ["type" => "mediumint unsigned", "not null" => "1", "default" => "0", "relation" => ["user" => "uid"], "comment" => "Owner id of this permission set"],
|
||||
"allow_cid" => ["type" => "mediumtext", "comment" => "Access Control - list of allowed contact.id '<19><78>'"],
|
||||
"allow_gid" => ["type" => "mediumtext", "comment" => "Access Control - list of allowed groups"],
|
||||
"deny_cid" => ["type" => "mediumtext", "comment" => "Access Control - list of denied contact.id"],
|
||||
"deny_gid" => ["type" => "mediumtext", "comment" => "Access Control - list of denied groups"],
|
||||
],
|
||||
"indexes" => [
|
||||
"PRIMARY" => ["id"],
|
||||
"uid_allow_cid_allow_gid_deny_cid_deny_gid" => ["allow_cid(50)", "allow_gid(30)", "deny_cid(50)", "deny_gid(30)"],
|
||||
]
|
||||
],
|
||||
"photo" => [
|
||||
"comment" => "photo storage",
|
||||
"fields" => [
|
||||
|
|
@ -1111,6 +1128,52 @@ return [
|
|||
"poll_id" => ["poll_id"],
|
||||
]
|
||||
],
|
||||
"post-category" => [
|
||||
"comment" => "post relation to categories",
|
||||
"fields" => [
|
||||
"uri-id" => ["type" => "int unsigned", "not null" => "1", "primary" => "1", "foreign" => ["item-uri" => "id"], "comment" => "Id of the item-uri table entry that contains the item uri"],
|
||||
"uid" => ["type" => "mediumint unsigned", "not null" => "1", "default" => "0", "primary" => "1", "relation" => ["user" => "uid"], "comment" => "User id"],
|
||||
"type" => ["type" => "tinyint unsigned", "not null" => "1", "default" => "0", "primary" => "1", "comment" => ""],
|
||||
"tid" => ["type" => "int unsigned", "not null" => "1", "default" => "0", "primary" => "1", "foreign" => ["tag" => "id", "on delete" => "restrict"], "comment" => ""],
|
||||
],
|
||||
"indexes" => [
|
||||
"PRIMARY" => ["uri-id", "uid", "type", "tid"],
|
||||
"uri-id" => ["tid"]
|
||||
]
|
||||
],
|
||||
"post-delivery-data" => [
|
||||
"comment" => "Delivery data for items",
|
||||
"fields" => [
|
||||
"uri-id" => ["type" => "int unsigned", "not null" => "1", "primary" => "1", "foreign" => ["item-uri" => "id"], "comment" => "Id of the item-uri table entry that contains the item uri"],
|
||||
"postopts" => ["type" => "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" => ["type" => "mediumtext", "comment" => "Additional receivers of the linked item"],
|
||||
"queue_count" => ["type" => "mediumint", "not null" => "1", "default" => "0", "comment" => "Initial number of delivery recipients, used as item.delivery_queue_count"],
|
||||
"queue_done" => ["type" => "mediumint", "not null" => "1", "default" => "0", "comment" => "Number of successful deliveries, used as item.delivery_queue_done"],
|
||||
"queue_failed" => ["type" => "mediumint", "not null" => "1", "default" => "0", "comment" => "Number of unsuccessful deliveries, used as item.delivery_queue_failed"],
|
||||
"activitypub" => ["type" => "mediumint", "not null" => "1", "default" => "0", "comment" => "Number of successful deliveries via ActivityPub"],
|
||||
"dfrn" => ["type" => "mediumint", "not null" => "1", "default" => "0", "comment" => "Number of successful deliveries via DFRN"],
|
||||
"legacy_dfrn" => ["type" => "mediumint", "not null" => "1", "default" => "0", "comment" => "Number of successful deliveries via legacy DFRN"],
|
||||
"diaspora" => ["type" => "mediumint", "not null" => "1", "default" => "0", "comment" => "Number of successful deliveries via Diaspora"],
|
||||
"ostatus" => ["type" => "mediumint", "not null" => "1", "default" => "0", "comment" => "Number of successful deliveries via OStatus"],
|
||||
],
|
||||
"indexes" => [
|
||||
"PRIMARY" => ["uri-id"],
|
||||
]
|
||||
],
|
||||
"post-tag" => [
|
||||
"comment" => "post relation to tags",
|
||||
"fields" => [
|
||||
"uri-id" => ["type" => "int unsigned", "not null" => "1", "primary" => "1", "foreign" => ["item-uri" => "id"], "comment" => "Id of the item-uri table entry that contains the item uri"],
|
||||
"type" => ["type" => "tinyint unsigned", "not null" => "1", "default" => "0", "primary" => "1", "comment" => ""],
|
||||
"tid" => ["type" => "int unsigned", "not null" => "1", "default" => "0", "primary" => "1", "foreign" => ["tag" => "id", "on delete" => "restrict"], "comment" => ""],
|
||||
"cid" => ["type" => "int unsigned", "not null" => "1", "default" => "0", "primary" => "1", "foreign" => ["contact" => "id", "on delete" => "restrict"], "comment" => "Contact id of the mentioned public contact"],
|
||||
],
|
||||
"indexes" => [
|
||||
"PRIMARY" => ["uri-id", "type", "tid", "cid"],
|
||||
"tid" => ["tid"],
|
||||
"cid" => ["cid"]
|
||||
]
|
||||
],
|
||||
"process" => [
|
||||
"comment" => "Currently running system processes",
|
||||
"fields" => [
|
||||
|
|
@ -1195,7 +1258,7 @@ return [
|
|||
"id" => ["type" => "int unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1", "comment" => "sequential ID"],
|
||||
"uid" => ["type" => "mediumint unsigned", "not null" => "1", "default" => "0", "relation" => ["user" => "uid"], "comment" => "Owner user id"],
|
||||
"order" => ["type" => "mediumint unsigned", "not null" => "1", "default" => "1", "comment" => "Field ordering per user"],
|
||||
"psid" => ["type" => "int unsigned", "relation" => ["permissionset" => "id"], "comment" => "ID of the permission set of this profile field - 0 = public"],
|
||||
"psid" => ["type" => "int unsigned", "foreign" => ["permissionset" => "id", "on delete" => "restrict"], "comment" => "ID of the permission set of this profile field - 0 = public"],
|
||||
"label" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => "Label of the field"],
|
||||
"value" => ["type" => "text", "comment" => "Value of the field"],
|
||||
"created" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => "creation time"],
|
||||
|
|
@ -1268,42 +1331,14 @@ return [
|
|||
"expire" => ["expire"],
|
||||
]
|
||||
],
|
||||
"sign" => [
|
||||
"comment" => "Diaspora signatures",
|
||||
"storage" => [
|
||||
"comment" => "Data stored by Database storage backend",
|
||||
"fields" => [
|
||||
"id" => ["type" => "int unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1", "comment" => "sequential ID"],
|
||||
"iid" => ["type" => "int unsigned", "not null" => "1", "default" => "0", "relation" => ["item" => "id"], "comment" => "item.id"],
|
||||
"signed_text" => ["type" => "mediumtext", "comment" => ""],
|
||||
"signature" => ["type" => "text", "comment" => ""],
|
||||
"signer" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""],
|
||||
"id" => ["type" => "int unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1", "comment" => "Auto incremented image data id"],
|
||||
"data" => ["type" => "longblob", "not null" => "1", "comment" => "file data"]
|
||||
],
|
||||
"indexes" => [
|
||||
"PRIMARY" => ["id"],
|
||||
"iid" => ["UNIQUE", "iid"],
|
||||
]
|
||||
],
|
||||
"term" => [
|
||||
"comment" => "item taxonomy (categories, tags, etc.) table",
|
||||
"fields" => [
|
||||
"tid" => ["type" => "int unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1", "comment" => ""],
|
||||
"oid" => ["type" => "int unsigned", "not null" => "1", "default" => "0", "relation" => ["item" => "id"], "comment" => ""],
|
||||
"otype" => ["type" => "tinyint unsigned", "not null" => "1", "default" => "0", "comment" => ""],
|
||||
"type" => ["type" => "tinyint unsigned", "not null" => "1", "default" => "0", "comment" => ""],
|
||||
"term" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""],
|
||||
"url" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""],
|
||||
"guid" => ["type" => "varchar(255)", "not null" => "1", "default" => "", "comment" => ""],
|
||||
"created" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => ""],
|
||||
"received" => ["type" => "datetime", "not null" => "1", "default" => DBA::NULL_DATETIME, "comment" => ""],
|
||||
"global" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => ""],
|
||||
"uid" => ["type" => "mediumint unsigned", "not null" => "1", "default" => "0", "relation" => ["user" => "uid"], "comment" => "User id"],
|
||||
],
|
||||
"indexes" => [
|
||||
"PRIMARY" => ["tid"],
|
||||
"term_type" => ["term(64)", "type"],
|
||||
"oid_otype_type_term" => ["oid", "otype", "type", "term(32)"],
|
||||
"uid_otype_type_term_global_created" => ["uid", "otype", "type", "term(32)", "global", "created"],
|
||||
"uid_otype_type_url" => ["uid", "otype", "type", "url(64)"],
|
||||
"guid" => ["guid(64)"],
|
||||
"PRIMARY" => ["id"]
|
||||
]
|
||||
],
|
||||
"thread" => [
|
||||
|
|
@ -1311,6 +1346,7 @@ return [
|
|||
"fields" => [
|
||||
"iid" => ["type" => "int unsigned", "not null" => "1", "default" => "0", "primary" => "1", "relation" => ["item" => "id"],
|
||||
"comment" => "sequential ID"],
|
||||
"uri-id" => ["type" => "int unsigned", "foreign" => ["item-uri" => "id"], "comment" => "Id of the item-uri table entry that contains the item uri"],
|
||||
"uid" => ["type" => "mediumint unsigned", "not null" => "1", "default" => "0", "relation" => ["user" => "uid"], "comment" => "User id"],
|
||||
"contact-id" => ["type" => "int unsigned", "not null" => "1", "default" => "0", "relation" => ["contact" => "id"], "comment" => ""],
|
||||
"owner-id" => ["type" => "int unsigned", "not null" => "1", "default" => "0", "relation" => ["contact" => "id"], "comment" => "Item owner"],
|
||||
|
|
@ -1349,6 +1385,7 @@ return [
|
|||
"uid_commented" => ["uid", "commented"],
|
||||
"uid_wall_received" => ["uid", "wall", "received"],
|
||||
"private_wall_origin_commented" => ["private", "wall", "origin", "commented"],
|
||||
"uri-id" => ["uri-id"],
|
||||
]
|
||||
],
|
||||
"tokens" => [
|
||||
|
|
@ -1356,13 +1393,14 @@ return [
|
|||
"fields" => [
|
||||
"id" => ["type" => "varchar(40)", "not null" => "1", "primary" => "1", "comment" => ""],
|
||||
"secret" => ["type" => "text", "comment" => ""],
|
||||
"client_id" => ["type" => "varchar(20)", "not null" => "1", "default" => "", "relation" => ["clients" => "client_id"]],
|
||||
"client_id" => ["type" => "varchar(20)", "not null" => "1", "default" => "", "foreign" => ["clients" => "client_id"]],
|
||||
"expires" => ["type" => "int", "not null" => "1", "default" => "0", "comment" => ""],
|
||||
"scope" => ["type" => "varchar(200)", "not null" => "1", "default" => "", "comment" => ""],
|
||||
"uid" => ["type" => "mediumint unsigned", "not null" => "1", "default" => "0", "relation" => ["user" => "uid"], "comment" => "User id"],
|
||||
],
|
||||
"indexes" => [
|
||||
"PRIMARY" => ["id"],
|
||||
"client_id" => ["client_id"]
|
||||
]
|
||||
],
|
||||
"user" => [
|
||||
|
|
@ -1460,6 +1498,16 @@ return [
|
|||
"iid_uid" => ["iid", "uid"]
|
||||
]
|
||||
],
|
||||
"verb" => [
|
||||
"comment" => "Activity Verbs",
|
||||
"fields" => [
|
||||
"id" => ["type" => "smallint unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1"],
|
||||
"name" => ["type" => "varchar(100)", "not null" => "1", "default" => "", "comment" => ""]
|
||||
],
|
||||
"indexes" => [
|
||||
"PRIMARY" => ["id"]
|
||||
]
|
||||
],
|
||||
"worker-ipc" => [
|
||||
"comment" => "Inter process communication between the frontend and the worker",
|
||||
"fields" => [
|
||||
|
|
@ -1494,15 +1542,4 @@ return [
|
|||
"done_pid_priority_created" => ["done", "pid", "priority", "created"]
|
||||
]
|
||||
],
|
||||
"storage" => [
|
||||
"comment" => "Data stored by Database storage backend",
|
||||
"fields" => [
|
||||
"id" => ["type" => "int unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1", "comment" => "Auto incremented image data id"],
|
||||
"data" => ["type" => "longblob", "not null" => "1", "comment" => "file data"]
|
||||
],
|
||||
"indexes" => [
|
||||
"PRIMARY" => ["id"]
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
|
|
|
|||
253
static/dbview.config.php
Executable file
253
static/dbview.config.php
Executable file
|
|
@ -0,0 +1,253 @@
|
|||
<?php
|
||||
/**
|
||||
* @copyright Copyright (C) 2020, Friendica
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Main view structure configuration file.
|
||||
*
|
||||
* Here are described all the view Friendica needs to work.
|
||||
*
|
||||
* Syntax (braces indicate optionale values):
|
||||
* "<view name>" => [
|
||||
* "fields" => [
|
||||
* "<field name>" => ["table", "field"],
|
||||
* "<field name>" => "SQL expression",
|
||||
* ...
|
||||
* ],
|
||||
* "query" => "FROM `table` INNER JOIN `other-table` ..."
|
||||
* ],
|
||||
* ],
|
||||
*
|
||||
* If you need to make any change, make sure to increment the DB_UPDATE_VERSION constant value in dbstructure.config.php.
|
||||
*
|
||||
*/
|
||||
|
||||
return [
|
||||
"category-view" => [
|
||||
"fields" => [
|
||||
"uri-id" => ["post-category", "uri-id"],
|
||||
"uid" => ["post-category", "uid"],
|
||||
"uri" => ["item-uri", "uri"],
|
||||
"guid" => ["item-uri", "guid"],
|
||||
"type" => ["post-category", "type"],
|
||||
"tid" => ["post-category", "tid"],
|
||||
"name" => ["tag", "name"],
|
||||
"url" => ["tag", "url"],
|
||||
],
|
||||
"query" => "FROM `post-category`
|
||||
INNER JOIN `item-uri` ON `item-uri`.id = `post-category`.`uri-id`
|
||||
LEFT JOIN `tag` ON `post-category`.`tid` = `tag`.`id`"
|
||||
],
|
||||
"tag-view" => [
|
||||
"fields" => [
|
||||
"uri-id" => ["post-tag", "uri-id"],
|
||||
"uri" => ["item-uri", "uri"],
|
||||
"guid" => ["item-uri", "guid"],
|
||||
"type" => ["post-tag", "type"],
|
||||
"tid" => ["post-tag", "tid"],
|
||||
"cid" => ["post-tag", "cid"],
|
||||
"name" => "CASE `cid` WHEN 0 THEN `tag`.`name` ELSE `contact`.`name` END",
|
||||
"url" => "CASE `cid` WHEN 0 THEN `tag`.`url` ELSE `contact`.`url` END",
|
||||
],
|
||||
"query" => "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`"
|
||||
],
|
||||
"owner-view" => [
|
||||
"fields" => [
|
||||
"id" => ["contact", "id"],
|
||||
"uid" => ["contact", "uid"],
|
||||
"created" => ["contact", "created"],
|
||||
"updated" => ["contact", "updated"],
|
||||
"self" => ["contact", "self"],
|
||||
"remote_self" => ["contact", "remote_self"],
|
||||
"rel" => ["contact", "rel"],
|
||||
"duplex" => ["contact", "duplex"],
|
||||
"network" => ["contact", "network"],
|
||||
"protocol" => ["contact", "protocol"],
|
||||
"name" => ["contact", "name"],
|
||||
"nick" => ["contact", "nick"],
|
||||
"location" => ["contact", "location"],
|
||||
"about" => ["contact", "about"],
|
||||
"keywords" => ["contact", "keywords"],
|
||||
"gender" => ["contact", "gender"],
|
||||
"xmpp" => ["contact", "xmpp"],
|
||||
"attag" => ["contact", "attag"],
|
||||
"avatar" => ["contact", "avatar"],
|
||||
"photo" => ["contact", "photo"],
|
||||
"thumb" => ["contact", "thumb"],
|
||||
"micro" => ["contact", "micro"],
|
||||
"site-pubkey" => ["contact", "site-pubkey"],
|
||||
"issued-id" => ["contact", "issued-id"],
|
||||
"dfrn-id" => ["contact", "dfrn-id"],
|
||||
"url" => ["contact", "url"],
|
||||
"nurl" => ["contact", "nurl"],
|
||||
"addr" => ["contact", "addr"],
|
||||
"alias" => ["contact", "alias"],
|
||||
"pubkey" => ["contact", "pubkey"],
|
||||
"prvkey" => ["contact", "prvkey"],
|
||||
"batch" => ["contact", "batch"],
|
||||
"request" => ["contact", "request"],
|
||||
"notify" => ["contact", "notify"],
|
||||
"poll" => ["contact", "poll"],
|
||||
"confirm" => ["contact", "confirm"],
|
||||
"poco" => ["contact", "poco"],
|
||||
"aes_allow" => ["contact", "aes_allow"],
|
||||
"ret-aes" => ["contact", "ret-aes"],
|
||||
"usehub" => ["contact", "usehub"],
|
||||
"subhub" => ["contact", "subhub"],
|
||||
"hub-verify" => ["contact", "hub-verify"],
|
||||
"last-update" => ["contact", "last-update"],
|
||||
"success_update" => ["contact", "success_update"],
|
||||
"failure_update" => ["contact", "failure_update"],
|
||||
"name-date" => ["contact", "name-date"],
|
||||
"uri-date" => ["contact", "uri-date"],
|
||||
"avatar-date" => ["contact", "avatar-date"],
|
||||
"picdate" => ["contact", "avatar-date"], /// @todo Replaces all uses of "picdate" with "avatar-date"
|
||||
"term-date" => ["contact", "term-date"],
|
||||
"last-item" => ["contact", "last-item"],
|
||||
"priority" => ["contact", "priority"],
|
||||
"blocked" => ["contact", "blocked"], /// @todo Check if "blocked" from contact or from the users table
|
||||
"block_reason" => ["contact", "block_reason"],
|
||||
"readonly" => ["contact", "readonly"],
|
||||
"writable" => ["contact", "writable"],
|
||||
"forum" => ["contact", "forum"],
|
||||
"prv" => ["contact", "prv"],
|
||||
"contact-type" => ["contact", "contact-type"],
|
||||
"hidden" => ["contact", "hidden"],
|
||||
"archive" => ["contact", "archive"],
|
||||
"pending" => ["contact", "pending"],
|
||||
"deleted" => ["contact", "deleted"],
|
||||
"rating" => ["contact", "rating"],
|
||||
"unsearchable" => ["contact", "unsearchable"],
|
||||
"sensitive" => ["contact", "sensitive"],
|
||||
"baseurl" => ["contact", "baseurl"],
|
||||
"reason" => ["contact", "reason"],
|
||||
"closeness" => ["contact", "closeness"],
|
||||
"info" => ["contact", "info"],
|
||||
"profile-id" => ["contact", "profile-id"],
|
||||
"bdyear" => ["contact", "bdyear"],
|
||||
"bd" => ["contact", "bd"],
|
||||
"notify_new_posts" => ["contact", "notify_new_posts"],
|
||||
"fetch_further_information" => ["contact", "fetch_further_information"],
|
||||
"ffi_keyword_denylist" => ["contact", "ffi_keyword_denylist"],
|
||||
"parent-uid" => ["user", "parent-uid"],
|
||||
"guid" => ["user", "guid"],
|
||||
"nickname" => ["user", "nickname"], /// @todo Replaces all uses of "nickname" with "nick"
|
||||
"email" => ["user", "email"],
|
||||
"openid" => ["user", "openid"],
|
||||
"timezone" => ["user", "timezone"],
|
||||
"language" => ["user", "language"],
|
||||
"register_date" => ["user", "register_date"],
|
||||
"login_date" => ["user", "login_date"],
|
||||
"default-location" => ["user", "default-location"],
|
||||
"allow_location" => ["user", "allow_location"],
|
||||
"theme" => ["user", "theme"],
|
||||
"upubkey" => ["user", "pubkey"],
|
||||
"uprvkey" => ["user", "prvkey"],
|
||||
"sprvkey" => ["user", "sprvkey"],
|
||||
"spubkey" => ["user", "spubkey"],
|
||||
"verified" => ["user", "verified"],
|
||||
"blockwall" => ["user", "blockwall"],
|
||||
"hidewall" => ["user", "hidewall"],
|
||||
"blocktags" => ["user", "blocktags"],
|
||||
"unkmail" => ["user", "unkmail"],
|
||||
"cntunkmail" => ["user", "cntunkmail"],
|
||||
"notify-flags" => ["user", "notify-flags"],
|
||||
"page-flags" => ["user", "page-flags"],
|
||||
"account-type" => ["user", "account-type"],
|
||||
"prvnets" => ["user", "prvnets"],
|
||||
"maxreq" => ["user", "maxreq"],
|
||||
"expire" => ["user", "expire"],
|
||||
"account_removed" => ["user", "account_removed"],
|
||||
"account_expired" => ["user", "account_expired"],
|
||||
"account_expires_on" => ["user", "account_expires_on"],
|
||||
"expire_notification_sent" => ["user", "expire_notification_sent"],
|
||||
"def_gid" => ["user", "def_gid"],
|
||||
"allow_cid" => ["user", "allow_cid"],
|
||||
"allow_gid" => ["user", "allow_gid"],
|
||||
"deny_cid" => ["user", "deny_cid"],
|
||||
"deny_gid" => ["user", "deny_gid"],
|
||||
"openidserver" => ["user", "openidserver"],
|
||||
"publish" => ["profile", "publish"],
|
||||
"net-publish" => ["profile", "net-publish"],
|
||||
"hide-friends" => ["profile", "hide-friends"],
|
||||
"prv_keywords" => ["profile", "prv_keywords"],
|
||||
"pub_keywords" => ["profile", "pub_keywords"],
|
||||
"address" => ["profile", "address"],
|
||||
"locality" => ["profile", "locality"],
|
||||
"region" => ["profile", "region"],
|
||||
"postal-code" => ["profile", "postal-code"],
|
||||
"country-name" => ["profile", "country-name"],
|
||||
"homepage" => ["profile", "homepage"],
|
||||
"dob" => ["profile", "dob"],
|
||||
],
|
||||
"query" => "FROM `user`
|
||||
INNER JOIN `contact` ON `contact`.`uid` = `user`.`uid` AND `contact`.`self`
|
||||
INNER JOIN `profile` ON `profile`.`uid` = `user`.`uid`"
|
||||
],
|
||||
"pending-view" => [
|
||||
"fields" => [
|
||||
"id" => ["register", "id"],
|
||||
"hash" => ["register", "hash"],
|
||||
"created" => ["register", "created"],
|
||||
"uid" => ["register", "uid"],
|
||||
"password" => ["register", "password"],
|
||||
"language" => ["register", "language"],
|
||||
"note" => ["register", "note"],
|
||||
"self" => ["contact", "self"],
|
||||
"name" => ["contact", "name"],
|
||||
"url" => ["contact", "url"],
|
||||
"micro" => ["contact", "micro"],
|
||||
"email" => ["user", "email"],
|
||||
"nick" => ["contact", "nick"],
|
||||
],
|
||||
"query" => "FROM `register`
|
||||
INNER JOIN `contact` ON `register`.`uid` = `contact`.`uid`
|
||||
INNER JOIN `user` ON `register`.`uid` = `user`.`uid`"
|
||||
],
|
||||
"tag-search-view" => [
|
||||
"fields" => [
|
||||
"uri-id" => ["post-tag", "uri-id"],
|
||||
"iid" => ["item", "id"],
|
||||
"uri" => ["item", "uri"],
|
||||
"guid" => ["item", "guid"],
|
||||
"uid" => ["item", "uid"],
|
||||
"private" => ["item", "private"],
|
||||
"wall" => ["item", "wall"],
|
||||
"origin" => ["item", "origin"],
|
||||
"gravity" => ["item", "gravity"],
|
||||
"received" => ["item", "received"],
|
||||
"name" => ["tag", "name"],
|
||||
],
|
||||
"query" => "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"
|
||||
],
|
||||
"workerqueue-view" => [
|
||||
"fields" => [
|
||||
"pid" => ["process", "pid"],
|
||||
"priority" => ["workerqueue", "priority"],
|
||||
],
|
||||
"query" => "FROM `process`
|
||||
INNER JOIN `workerqueue` ON `workerqueue`.`pid` = `process`.`pid`
|
||||
WHERE NOT `workerqueue`.`done`"
|
||||
],
|
||||
];
|
||||
|
||||
|
|
@ -45,8 +45,23 @@ return [
|
|||
'database' => '',
|
||||
|
||||
// charset (String)
|
||||
// Database connexion charset. Changing this value will likely corrupt special characters.
|
||||
// Database connection charset. Changing this value will likely corrupt special characters.
|
||||
'charset' => 'utf8mb4',
|
||||
|
||||
// emulate_prepares (Boolean) (Experimental)
|
||||
// If enabled, prepared statements will be emulated.
|
||||
// In combination with MySQLi this will cast all return values to strings.
|
||||
'emulate_prepares' => false,
|
||||
|
||||
// pdo_emulate_prepares (Boolean) (Experimental)
|
||||
// If enabled, the builtin emulation for prepared statements is used.
|
||||
// Due to limitations of that emulation (all return values are casted as strings)
|
||||
// this will most likely cause issues and should not be used on production systems.
|
||||
'pdo_emulate_prepares' => false,
|
||||
|
||||
// disable_pdo (Boolean)
|
||||
// PDO is used by default (if available). Otherwise MySQLi will be used.
|
||||
'disable_pdo' => false,
|
||||
],
|
||||
'config' => [
|
||||
// admin_email (Comma-separated list)
|
||||
|
|
@ -88,6 +103,10 @@ return [
|
|||
// chose "Remember me" when logging in is considered logged out.
|
||||
'auth_cookie_lifetime' => 7,
|
||||
|
||||
// big_emojis (Boolean)
|
||||
// Display "Emoji Only" posts in big.
|
||||
'big_emojis' => false,
|
||||
|
||||
// block_local_dir (Boolean)
|
||||
// Deny public access to the local user directory.
|
||||
'block_local_dir' => false,
|
||||
|
|
@ -124,9 +143,9 @@ return [
|
|||
// Watchlist of indexes to watch.
|
||||
'db_log_index_watch' => '',
|
||||
|
||||
// db_log_index_blacklist (Comma-separated list)
|
||||
// Blacklist of indexes that shouldn't be watched.
|
||||
'db_log_index_blacklist' => '',
|
||||
// db_log_index_denylist (Comma-separated list)
|
||||
// Deny list of indexes that shouldn't be watched.
|
||||
'db_log_index_denylist' => '',
|
||||
|
||||
// db_loglimit (Integer)
|
||||
// If a database call lasts longer than this value in seconds it is logged.
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue