Merge remote-tracking branch 'upstream/develop' into item-view

This commit is contained in:
Michael 2021-01-12 21:14:28 +00:00
commit 122ad0af14
875 changed files with 156972 additions and 153019 deletions

60
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,60 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: Bug
assignees: ''
---
<!--
Please fill out this template with all the information you have. The more info you can provide the easier it will be to help you out or fix the problem you are seeing. For trouble with the UI dont forget to add a screenshot or two. Please do your best!
Please note that this template is only for bugs. Please use other templates in case of feature requests or support requests
Lastly, be sure to preview your issue before saving. Thanks!
-->
- [ ] I have searched open and closed issues for duplicates
<!--
You can search all issues here
https://github.com/friendica/friendica/issues?utf8=%E2%9C%93&q=is%3Aissue
Replace [ ] with [X] once you've searched
-->
### Bug Description
<!-- Give an overall summary of the issue. -->
### Steps to Reproduce
<!-- Using bullet points, list the steps that reproduce the bug. -->
1. step one
2. step two
3. step three
Actual Result:
<!-- Describe the details of the buggy behaviour. -->
Expected Result:
<!-- Describe in detail what the correct behavior should be. -->
### Screenshots
<!-- How to take screenshots on all OSes: https://www.take-a-screenshot.org/
You can drag and drop images into this text box. -->
### Platform Info
Friendica Version:
<!-- You can see Friendicas version number at Help -> About this site -> Site/Friendica Version -->
Friendica Source:
PHP version:
SQL version:

5
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1,5 @@
blank_issues_enabled: true
contact_links:
- name: Friendica Community Support
url: https://forum.friendi.ca/
about: Please ask and answer questions here.

View File

@ -0,0 +1,22 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: New Feature
assignees: ''
---
### Is the feature request related to a problem? Please describe.
<!-- A clear and concise description of what the problem is. Like `I'm always frustrated when [...]`
### Describe the feature you'd like
<!-- A clear and concise description of waht you want to happen. -->
### Describe alternatives you've considered
<!-- A clear and concise description of any alternative solutions or feature you've considered. -->
### Additional context

10
.github/ISSUE_TEMPLATE/question.md vendored Normal file
View File

@ -0,0 +1,10 @@
---
name: Question / Support
about: Select this if you have a question
title: ''
labels: Support Request
assignees: ''
---
# For general question about Friendica, please try to find a solution at https://wiki.friendi.ca first.

View File

@ -1,15 +0,0 @@
### Expected behavior
### Actual behavior
### Steps to reproduce the problem
### Friendica version you encountered the problem
see `example.com/friendica` on your Friendica node for the version information.
### Friendica source (git, zip)
### PHP version
### SQL version

101
.github/workflows/php.yml vendored Normal file
View File

@ -0,0 +1,101 @@
name: Testing Friendica
on: [push, pull_request]
jobs:
friendica:
name: Friendica (PHP ${{ matrix.php-versions }})
runs-on: ubuntu-latest
services:
mariadb:
image: mariadb:latest
env:
MYSQL_ALLOW_EMPTY_PASSWORD: true
MYSQL_DATABASE: test
MYSQL_PASSWORD: test
MYSQL_USER: test
ports:
- 3306/tcp
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
redis:
image: redis
ports:
- 6379/tcp
options: --health-cmd="redis-cli ping" --health-interval=10s --health-timeout=5s --health-retries=3
memcached:
image: memcached
ports:
- 11211/tcp
strategy:
fail-fast: false
matrix:
php-versions: ['7.2', '7.3', '7.4']
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup PHP, with composer and extensions
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
tools: pecl, composer:v1
extensions: pdo_mysql, gd, zip, opcache, ctype, pcntl, ldap, apcu, memcached, redis, imagick, memcache
coverage: xdebug
ini-values: apc.enabled=1, apc.enable_cli=1
- name: Start mysql service
run: sudo /etc/init.d/mysql start
- name: Validate composer.json and composer.lock
run: composer validate
- name: Get composer cache directory
id: composercache
run: echo "::set-output name=dir::$(composer config cache-files-dir)"
- name: Cache dependencies
uses: actions/cache@v2
with:
path: ${{ steps.composercache.outputs.dir }}
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
restore-keys: ${{ runner.os }}-composer-
- name: Install dependencies
run: composer install --prefer-dist
- name: Copy default Friendica config
run: cp config/local-sample.config.php config/local.config.php
- name: Verify MariaDB connection
env:
PORT: ${{ job.services.mariadb.ports[3306] }}
run: |
while ! mysqladmin ping -h"127.0.0.1" -P"$PORT" --silent; do
sleep 1
done
- name: Setup MYSQL database
env:
PORT: ${{ job.services.mariadb.ports[3306] }}
run: |
mysql -h"127.0.0.1" -P"$PORT" -utest -ptest test < database.sql
- name: Test with Parallel-lint
run: vendor/bin/parallel-lint --exclude vendor/ --exclude view/asset/ .
- name: Test with phpunit
run: vendor/bin/phpunit --configuration tests/phpunit.xml --coverage-clover clover.xml
env:
MYSQL_HOST: 127.0.0.1
MYSQL_PORT: ${{ job.services.mariadb.ports[3306] }}
MYSQL_DATABASE: test
MYSQL_PASSWORD: test
MYSQL_USER: test
REDIS_PORT: ${{ job.services.redis.ports[6379] }}
REDIS_HOST: 127.0.0.1
MEMCACHED_PORT: ${{ job.services.memcached.ports[11211] }}
MEMCACHE_PORT: ${{ job.services.memcached.ports[11211] }}
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v1
with:
file: clover.xml

4
.gitignore vendored
View File

@ -71,8 +71,8 @@ venv/
/addons
/addon
#ignore .htaccess
.htaccess
#ignore base .htaccess
/.htaccess
#ignore filesystem storage default path
/storage

View File

@ -1,3 +1,6 @@
# This file is meant to be copied to ".htaccess" on Apache-powered web servers.
# The created .htaccess file can be edited manually and will not be overwritten by Friendica updates.
Options -Indexes
AddType application/x-java-archive .jar
AddType audio/ogg .oga

View File

@ -1,31 +0,0 @@
---
language: php
## Friendica officially supports PHP version >= 7.1
php:
- 7.1
- 7.2
- 7.3
services:
- mysql
- redis
- memcached
env:
- MYSQL_HOST=localhost MYSQL_PORT=3306 MYSQL_USERNAME=travis MYSQL_PASSWORD="" MYSQL_DATABASE=test
install:
- composer install
before_script:
- cp config/local-sample.config.php config/local.config.php
- mysql -e 'CREATE DATABASE IF NOT EXISTS test;'
- mysql -utravis test < database.sql
- pecl channel-update pecl.php.net
- pecl config-set preferred_state beta
- phpenv config-add .travis/redis.ini
- phpenv config-add .travis/memcached.ini
script:
- vendor/bin/parallel-lint --exclude vendor/ --exclude view/asset/ .
- vendor/bin/phpunit --configuration tests/phpunit.xml --coverage-clover clover.xml
after_success: bash <(curl -s https://codecov.io/bash)

View File

@ -1,4 +0,0 @@
extension="apcu.so"
apc.enabled = 1
apc.enable_cli = 1

View File

@ -1 +0,0 @@
extension="memcached.so"

View File

@ -1 +0,0 @@
extension="redis.so"

272
CHANGELOG
View File

@ -1,11 +1,269 @@
Version 2020.06 (unreleased)
Version 2021.03 (unreleased)
Friendica Core
Removed the frontend worker [annando]
Friendica Addons
Closed Issues
Version 2021.01 (2021-01-04)
Friendica Core
Added HU translation
Updates to the translations: DE, IT, RU [translation teams]
Updates to the themes (duepuntozero, frio, vier) [annando, MrPetovan, tobiasd, vinzv]
General Code cleanup [annando, MrPetovan, nupplaphil]
Enhanced the handling of permission sets [annando]
Enhanced the usage of system resources when displaying photos and updating contacts [annando]
Enhanced the database structure [annando, Quix0r]
Enhanced the detection of PeerTube servers [annando]
Enhanced the photo cache [annando]
Enhanced the import of old postings which would otherwise not be imported due their age [annando]
Enhanced the delivery process of ActivityPub content [annando]
Enhanced the performance profiler [annando]
Enhanced the background worker [annando]
Enhanced the handling of blocked authors [MrPetovan]
Enhanced the user management in the admin panel [MrPetovan]
Enhanced the process of expiring postings [annando]
Enhanced the un/follow process of contacts [annando]
Enhanced the handling of HTTP requests [nupplaphil]
Enhanced filter possibilities of contacts [annando]
Enhanced language detection of postings [annando]
Enhanced the admin panel [MrPetovan, tobiasd]
Enhanced the contact suggestions [annando]
Enhanced the community page (filter, tags) [annando]
Enhanced the display of the reason why a posting is displayed in a stream [annando]
Enhanced the forum delivery of postings [redmatrix]
Enhanced PHP8 compatibility [annando]
Enhanced the worker_cooldown mechanism [annando]
Added new options to the remote_self feature [annando]
Added API endpoints for accounts and trends [annando]
Added API endpoints for re-sharing of postings [annando]
Added provider fields to the API [annando]
Added the possibility to map $_SERVER variables during installation [nupplaphil]
Added the possibility to filter account types on the network page [annando]
Added missing Mastodon API endpoints as "unsupported" [annando]
Added a watchdog mode to check if the daemon is running [annando]
Added number of group members to the contact widget [annando]
Added endless scrolling in several places [annando]
Added an option to stay local when clicking on a contact profile [annando]
Added support of ActivityPub relays [annando]
Model\User::getAuthenticationInfo is now available for addons [MrPetovan]
Contact details can only edited for mail and feed contacts [annando]
Fixed some problems during the export of user data [annando]
Fixed various problems with the notification system [MrPetovan]
Fixed a problem with emoticon alt-text interpretation [MrPetovan]
Fixed a problem that caused comments on Tweets being distributed via ActivityPub [annando]
Fixed a problem with the auto-completion when composing comments [MrPetovan]
Fixed an ACL problem while poking contacts [MrPetovan]
Fixed a problem with Mastodon emoticons [MrPetovan]
Fixed a parser problem that caused additional <br> tags [annando]
Fixed escaping of several HTML snippets [MrPetovan]
Fixed a problem with fetching objects by URL [annando]
Friendica Addons
Updated to the translations IT, HU [translation teams]
advancedcontentfilter:
Added examples [hoergen]
blackout:
Improved the wording in the admin interface [urbalazs]
catavatar:
Improved the generation of avatars [annando]
ifttt:
Added support for delayed postings [annando]
mailstream:
Improved code structure [nupplaphil]
Fix case-sensitive check by [nupplaphil]
markdown:
Improved parsing [MrPetovan]
newmemberwidget:
Improved addon description [SpencerDub]
langfilter:
Changed the input to use a slider [MrPetovan]
ldapauth:
Reworked the authentication code [MrPetovan]
libravatar:
Fixed a problem with DNS requests [annando]
Improved the list of available avatars [annando]
phpmailer:
Fixed UTF8 encoding problems [MrPetovan]
rendertime:
Added more information about the "other" things that cost time [annando]
showmore:
Improved handling of the HTML structure of postings [MrPetovan]
showmore_dyn:
Improved user settings, language [MrPetovan]
twitter:
Improved logging [annando]
Improved the twitter_post_hook [MrPetovan]
Improved the posts send to twitter [annando]
Improved the remote_self functionality [annando]
Added support for delayed postings [annando]
Fixed a bug with direct re-shares [MrPetovan]
Closed Issues
2803, 4230, 4486, 4494, 5616, 7393, 7697, 8485, 8533, 8605, 8689,
8796, 8896, 8943, 8950, 9042, 9089, 9127, 9142, 9165, 9235, 9236,
9238, 9249, 9264, 9268, 9276, 9281, 9291, 9296, 9305, 9306, 9315,
9326, 9328, 9329, 9337, 9344, 9348, 9363, 9383, 9385, 9407, 9427,
9430, 9432, 9457, 9461, 9464, 9465, 9480, 9486, 9496, 9508, 9518,
9525, 9538, 9549, 9564, 9568, 9573, 9598, 9611, 9622, 9629, 9630,
9633, 9636, 9639, 9641, 9642, 9662, 9672, 9673, 9678, 9682, 9692,
9712
Version 2020.09-1 (2020-09-24)
Friendica Core:
Updates to the translations: RU [translation teams]
Enhanced forum delivery using attached mention tags [redmatrix]
Enhanced code test-ability [nupplaphil]
Enhanced character set detection when parsing URLs [MrPetovan]
Enhanced the Activity Pub relay functionality [annando]
Added phpseclib dependency to replace standalone ASN1 library [nupplaphil]
Fixed a bug generating Message-IDs for notification mails [nupplaphil]
Fixed missing uri-ids in the database [annando]
Fixed a display problem with the new re-shares [annando]
Friendica Addons:
showmore_dyn:
New addon to collapse long post depending on their actual height [wiwie]
nominatim:
Added addon to resolve coordinates with OpenStreetmap [annando]
phpmailer:
Fixed a bug that prevented notification mails being send [nupplaphil]
Closed Issues:
9142, 9264
Version 2020.09 (2020-09-20)
Friendica Core:
Updates to the translations: DE, EN GB, EN US, ES, FR, IT, NL, PL, RU, ZH_CN [translation teams]
Updates to the themes (all) [MrPetovan, tobiasd]
Updates to the documentation [annando, mpanhans, realkinetix, tobiasd]
General code cleanup and refactoring [annando, MrPetovan, nupplaphil]
Enhanced the API [annando]
Enhanced the processing of background jobs [annando]
Enhanced federation of activities [annando, vpzomtrrfrt]
Enhanced the user notifications[annando]
Enhanced database usage [annando, MrPetovan]
Enhanced ActivityPub support for forums [annando]
Enhanced the utilization of the cache [annando, MrPetovan]
Enhanced the performance of the daemon [annando]
Enhanced the communication with the directory servers [annando]
Enhanced the re-sharing of items [annando]
Enhanced sample lighttpd and nginx configs [MrPetovan, tobiasd]
Enhanced the checks for incoming postings using ActivityPub [annando, Roger Meyer]
Enhanced the import of RSS feeds by removing tracking pixels [annando]
Enhanced the speed of the full text search [annando]
Replaced library used for text completion [MrPetovan]
Fixed a problem that prevented recipients of direct messages to be selected [MrPetovan]
Fixed a problem that prevented new email contacts from being added [annando]
Fixed a problem with the console command search [tobiasd]
Fixed a problem during the search for contacts [annando]
Fixed a problem with the JOT of private notes [MrPetovan]
Fixed missing HTML encoding [MrPetovan]
Fixed a layout problem with the frio composer for new postings [MrPetovan]
Fixed some composer notices [nupplaphil]
Fixed a problem for empty preview data when importing feed posts [annando]
Fixed a problem with the pager on search result pages [annando]
Fixed some templates to show the correct un-/follow button for contacts [annando]
Fixed a problem with the generation of the Message-ID of notification emails [nupplaphil]
Added nodeinfo2 support [annando]
Added CSV export and import of blocked servers to the console [tobiasd]
Added new admin debug module for ActivityPub [MrPetovan]
Added the automatic determination of frequency to pull feeds [annando]
Added signed fetching from system users for ActivityPub [annando]
Added the discovery of new peers from contacts [annando]
Added the directory API endpoint [annando]
Added support for signed outbox requests [annando]
Added direction functionality for clarification of posting flow [annando]
Added the ability to set the database version [annando]
Added support for ActivityPub relay server [annando]
By default display of re-sharer information is now flattened [annando]
Removed some unused POCO functionality [annando]
Removed the unused rating functionality [annando]
Removed unneeded network request for local stuff [annando]
Removed some useless info messages [annando]
Reworked some additional features according to a user voting [MrPetovan]
Friendica Addons:
Updates to the translations: DE, EN GB, EN US, IT, NL, RU, ZH_CN [translation teams]
Updates to the docs [SpencerDub]
General code cleanup and maintenance [annando, MrPetovan]
blockbot:
added some "good" bots [annando]
forumdirectory:
fixed some SQL queries [MrPetovan]
phpmailer:
fixed a problem leading to double message ID headers [nupplaphil]
qcomment:
restructured the addon and fixed a bug preventing the addon from working [MrPetovan]
Closed Issues:
2811, 4606, 5742, 5782, 7660, 8676, 8788, 8797, 8798, 8847, 8860,
8874, 8882, 8885, 8906, 8914, 8922, 8928, 8929, 8935, 8940, 8941,
8956, 8958, 8961, 8967, 8989, 8993, 8994, 8995, 8997, 8999, 9000,
9004, 9013, 9015, 9051, 9064, 9065, 9072, 9081, 9090, 9091, 9099,
9107, 9135, 9136, 9137, 9138, 9140, 9142, 9150, 9153, 9154, 9163,
9164, 9172, 9182, 9192, 9193, 9204, 9210, 9229, 9231, 9246
Version 2020.07-1 (2020-09-08)
Friendica Core
Fixed a problem that leaked sensitive information [Roger Meyer, MrPetovan]
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:
@ -61,7 +319,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:
@ -617,7 +875,7 @@ Version 2018.09 (2018-09-23)
Version 2018.05 (2018-06-01)
Friendica Core:
Update to the translations (DE, EN-GB, EN-US, FI, IS, IT, NL, PL, RU, ZN CH) [translation teams]
Update to the documentation [andyhee, annando, fabrixxm, M-arcus, MrPedovan, rudloff, tobiasd]
Update to the documentation [andyhee, annando, fabrixxm, M-arcus, MrPetovan, rudloff, tobiasd]
Enhancements to the DB handling [annando]
Enhancements to the relay system [annando]
Enhancements to the handling of URL that contain unicode characters [annando]
@ -911,7 +1169,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]
@ -1373,7 +1631,7 @@ Version 3.3.1 (2014-11-06)
Set default location to empty for new users. Suppress warning on user creation (issue #1193) (fabrixxm)
Correctly build urls with queries (issue #1190) (fabrixxm)
Optionally use keywords in feed as post tags with "remote self" (annando)
A blacklist of keywords to not use can be defined (annando)
A denylist of keywords to not use can be defined (annando)
"remote self" works also with Friendica and Diaspora contacts (annando)
Show exact post time after 12 hours (FX7)
Optionally redirect from non-SSL to SSL (annando)

View File

@ -9,6 +9,7 @@ Aditoo
AgnesElisa
Albert
Alberto Díaz Tormo
Aleksandr "M.O.Z.G" Dikov
Alex
Alexander An
Alexander Fortin
@ -34,6 +35,7 @@ Athalbert
aweiher
axelt
balderino
Balázs Úr
Beanow
beardyunixer
Beatriz Vital
@ -54,6 +56,7 @@ Chris Case
Christian González
Christian M. Grube
Christian Vogeley
Christian Wiwie
Cohan Robinson
Copiis Praeesse
CrystalStiletto
@ -113,7 +116,6 @@ Hypolite Petovan
Ilmari
ImgBotApp
irhen
Jak
Jakob
Jens Tautenhahn
jensp
@ -121,6 +123,7 @@ Jeroen De Meerleer
jeroenpraat
Joan Bar
JOduMonT
joe slam
Johannes Schwab
John Brazil
Jonatan Nyberg
@ -131,6 +134,8 @@ julia.domagalska
Julio Cova
Karel
Karolina
Kastal András
Keenan Pepper
Keith Fernie
Klaus Weidenbach
Koyu Berteon
@ -141,10 +146,11 @@ Leberwurscht
Leonard Lausen
Lionel Triay
loma-one
loma1
Lorem Ipsum
Ludovic Grossard
Lynn Stephenson
maase2
Magdalena Gazda
Mai Anh Nguyen
Manuel Pérez Monís
Marcin Klessa
@ -153,7 +159,6 @@ Marcus Müller
Marie Olive
Mariusz Pisz
marmor
Marquis_de_Carabas
Martin Schmitt
Mateusz Mikos
Mats Sjöberg
@ -169,9 +174,11 @@ Michal Šupler
Michalina
Mike Macgirvin
miqrogroove
mpanhans
mytbk
nathilia-peirce
Nicola Spanti
nobody
Olaf Conradi
Oliver
Olivier
@ -208,6 +215,7 @@ repat
Ricardo Pereira
Rik 4
RJ Madsen
Roger Meyer
Roland Häder
Rui Andrada
rwa
@ -219,23 +227,22 @@ Samuli Valavuo
Sandro Santilli
Sebastian Egbers
sella
Sennewood
Seth
Silke Meyer
Simon L'nu
Simó Albert i Beltran
softmetz
soko1
SpencerDub
Spencer Dub
St John Karp
Stanislav N.
Steffen K9
StefOfficiel
steve jobs
Sveinn í Felli
Sven Anders
Sylke Vicious
Sylvain Lagacé
szymon.filip
Sérgio Lima
Taekus
Tazman DeVille
@ -263,7 +270,6 @@ U-SOUND\mike
ufic
Ulf Rompe
Unknown
Valvin
Valvin A
Vasudev Kamath
Vasya Novikov

View File

@ -40,3 +40,7 @@ Have a look at the [installation documentation](doc/Install.md) for further info
|*Vier theme, desktop browser. Public timeline view.*|
|![Vier theme in desktop browser](images/screenshots/friendica-vier-community.png?raw=true "Vier theme in desktop browser")
|*Vier theme, desktop browser. Community post displayed.*|
## Endorsements
- [![Awesome Humane Tech](images/humane-tech-badge.svg)](https://github.com/humanetech-community/awesome-humane-tech) On August 12th 2020, Friendica was added to [the curated Awesome Humane Tech directory](https://github.com/humanetech-community/awesome-humane-tech) in [the "Fediverse" category](https://github.com/humanetech-community/awesome-humane-tech#fediverse).

View File

@ -1 +1 @@
2020.06-dev
2021.03-dev

10
bin/.htaccess Normal file
View File

@ -0,0 +1,10 @@
# This file prevents browser access to Friendica command-line scripts on Apache-powered web servers.
# It isn't meant to be edited manually, please check the base Friendica folder for the .htaccess-dist file instead.
<IfModule authz_host_module>
Require all denied
</IfModule>
<IfModule !authz_host_module>
Order Allow,Deny
Deny from all
</IfModule>

View File

@ -51,9 +51,14 @@
*
*/
if (php_sapi_name() !== 'cli') {
header($_SERVER["SERVER_PROTOCOL"] . ' 403 Forbidden');
exit();
}
use Dice\Dice;
use Friendica\App\Mode;
use Friendica\Util\ExAuth;
use Friendica\Security\ExAuth;
use Psr\Log\LoggerInterface;
if (sizeof($_SERVER["argv"]) == 0) {
@ -80,6 +85,7 @@ $dice = $dice->addRule(LoggerInterface::class,['constructParams' => ['auth_ejabb
$appMode = $dice->create(Mode::class);
if ($appMode->isNormal()) {
$oAuth = new ExAuth();
/** @var ExAuth $oAuth */
$oAuth = $dice->create(ExAuth::class);
$oAuth->readStdin();
}

Binary file not shown.

View File

@ -20,6 +20,11 @@
*
*/
if (php_sapi_name() !== 'cli') {
header($_SERVER["SERVER_PROTOCOL"] . ' 403 Forbidden');
exit();
}
use Dice\Dice;
use Psr\Log\LoggerInterface;

View File

@ -23,11 +23,18 @@
* This script was taken from http://php.net/manual/en/function.pcntl-fork.php
*/
if (php_sapi_name() !== 'cli') {
header($_SERVER["SERVER_PROTOCOL"] . ' 403 Forbidden');
exit();
}
use Dice\Dice;
use Friendica\App\Mode;
use Friendica\Core\Logger;
use Friendica\Core\Worker;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Util\DateTimeFormat;
use Psr\Log\LoggerInterface;
// Get options
@ -59,6 +66,8 @@ if (DI::mode()->isInstall()) {
die("Friendica isn't properly installed yet.\n");
}
DI::mode()->setExecutor(Mode::DAEMON);
DI::config()->load();
if (empty(DI::config()->get('system', 'pidfile'))) {
@ -138,34 +147,35 @@ Logger::notice('Starting worker daemon.', ["pid" => $pid]);
if (!$foreground) {
echo "Starting worker daemon.\n";
// Switch over to daemon mode.
if ($pid = pcntl_fork()) {
return; // Parent
}
fclose(STDIN); // Close all of the standard
// Enabling this seem to block a running php process with 100% CPU usage when there is an outpout
// fclose(STDOUT); // file descriptors as we
// fclose(STDERR); // are running as a daemon.
DBA::disconnect();
// Fork a daemon process
$pid = pcntl_fork();
if ($pid == -1) {
echo "Daemon couldn't be forked.\n";
Logger::warning('Could not fork daemon');
exit(1);
} elseif ($pid) {
// The parent process continues here
echo 'Child process started with pid ' . $pid . ".\n";
Logger::notice('Child process started', ['pid' => $pid]);
file_put_contents($pidfile, $pid);
exit(0);
}
// We now are in the child process
register_shutdown_function('shutdown');
// Make the child the main process, detach it from the terminal
if (posix_setsid() < 0) {
return;
}
if ($pid = pcntl_fork()) {
return; // Parent
}
// Closing all existing connections with the outside
fclose(STDIN);
$pid = getmypid();
file_put_contents($pidfile, $pid);
// We lose the database connection upon forking
DBA::reconnect();
// And now connect the database again
DBA::connect();
}
DI::config()->set('system', 'worker_daemon_mode', true);
@ -185,7 +195,12 @@ while (true) {
$do_cron = true;
}
Worker::spawnWorker($do_cron);
if ($do_cron || (!DI::process()->isMaxLoadReached() && Worker::entriesExists() && Worker::isReady())) {
Worker::spawnWorker($do_cron);
} else {
Logger::info('Cool down for 5 seconds', ['pid' => $pid]);
sleep(5);
}
if ($do_cron) {
// We force a reconnect of the database connection.
@ -195,8 +210,9 @@ while (true) {
$last_cron = time();
}
Logger::info("Sleeping", ["pid" => $pid]);
$start = time();
Logger::info("Sleeping", ["pid" => $pid, 'until' => gmdate(DateTimeFormat::MYSQL, $start + $wait_interval)]);
do {
$seconds = (time() - $start);
@ -204,9 +220,14 @@ while (true) {
// Background: After jobs had been started, they often fork many workers.
// To not waste too much time, the sleep period increases.
$arg = (($seconds + 1) / ($wait_interval / 9)) + 1;
$sleep = round(log10($arg) * 1000000, 0);
$sleep = min(1000000, round(log10($arg) * 1000000, 0));
usleep($sleep);
$pid = pcntl_waitpid(-1, $status, WNOHANG);
if ($pid > 0) {
Logger::info('Children quit via pcntl_waitpid', ['pid' => $pid, 'status' => $status]);
}
$timeout = ($seconds >= $wait_interval);
} while (!$timeout && !Worker::IPCJobsExists());

View File

@ -34,7 +34,7 @@ dontinclude = ['root', 'friendica', 'bavatar', 'tony baldwin', 'Taek', 'silke m'
path = os.path.abspath(argv[0].split('bin/dev/make_credits.py')[0])
print('> base directory is assumed to be: '+path)
# a place to store contributors
contributors = ["Andi Stadler", "Ratten", "Vít Šesták 'v6ak'"]
contributors = ["Andi Stadler", "Ratten", "Roger Meyer", "Vít Šesták 'v6ak'"]
# get the contributors
print('> getting contributors to the friendica core repository')
p = subprocess.Popen(['git', 'shortlog', '--no-merges', '-s'],

View File

@ -26,6 +26,10 @@
*
*/
if (php_sapi_name() !== 'cli') {
header($_SERVER["SERVER_PROTOCOL"] . ' 403 Forbidden');
exit();
}
if (($_SERVER["argc"] > 1) && isset($_SERVER["argv"][1])) {
echo $_SERVER["argv"][1];

View File

@ -24,6 +24,11 @@
* Usage: php bin/wait-for-connection {HOST} {PORT} [{TIMEOUT}]
*/
if (php_sapi_name() !== 'cli') {
header($_SERVER["SERVER_PROTOCOL"] . ' 403 Forbidden');
exit();
}
$timeout = 60;
switch ($argc) {
case 4:

View File

@ -21,8 +21,14 @@
* Starts the background processing
*/
if (php_sapi_name() !== 'cli') {
header($_SERVER["SERVER_PROTOCOL"] . ' 403 Forbidden');
exit();
}
use Dice\Dice;
use Friendica\App;
use Friendica\App\Mode;
use Friendica\Core\Update;
use Friendica\Core\Worker;
use Friendica\DI;
@ -53,6 +59,8 @@ $dice = $dice->addRule(LoggerInterface::class,['constructParams' => ['worker']])
DI::init($dice);
$a = DI::app();
DI::mode()->setExecutor(Mode::WORKER);
// Check the database structure and possibly fixes it
Update::check($a->getBasePath(), true, DI::mode());
@ -76,4 +84,4 @@ Worker::processQueue($run_cron);
Worker::unclaimProcess();
Worker::endProcess();
DI::process()->end();

View File

@ -38,7 +38,7 @@ use Friendica\Util\DateTimeFormat;
define('FRIENDICA_PLATFORM', 'Friendica');
define('FRIENDICA_CODENAME', 'Red Hot Poker');
define('FRIENDICA_VERSION', '2020.06-dev');
define('FRIENDICA_VERSION', '2021.03-dev');
define('DFRN_PROTOCOL_VERSION', '2.23');
define('NEW_UPDATE_ROUTINE_VERSION', 1170);
@ -201,6 +201,7 @@ define('PRIORITY_HIGH', 20);
define('PRIORITY_MEDIUM', 30);
define('PRIORITY_LOW', 40);
define('PRIORITY_NEGLIGIBLE', 50);
define('PRIORITIES', [PRIORITY_CRITICAL, PRIORITY_HIGH, PRIORITY_MEDIUM, PRIORITY_LOW, PRIORITY_NEGLIGIBLE]);
/* @}*/
/**
@ -253,10 +254,10 @@ function public_contact()
if (!$public_contact_id && !empty($_SESSION['authenticated'])) {
if (!empty($_SESSION['my_address'])) {
// Local user
$public_contact_id = intval(Contact::getIdForURL($_SESSION['my_address'], 0, true));
$public_contact_id = intval(Contact::getIdForURL($_SESSION['my_address'], 0, false));
} elseif (!empty($_SESSION['visitor_home'])) {
// Remote user
$public_contact_id = intval(Contact::getIdForURL($_SESSION['visitor_home'], 0, true));
$public_contact_id = intval(Contact::getIdForURL($_SESSION['visitor_home'], 0, false));
}
} elseif (empty($_SESSION['authenticated'])) {
$public_contact_id = false;
@ -266,7 +267,7 @@ function public_contact()
}
/**
* Returns contact id of authenticated site visitor or false
* Returns public contact id of authenticated site visitor or false
*
* @return int|bool visitor_id or false
*/
@ -382,38 +383,6 @@ function is_site_admin()
return local_user() && $admin_email && in_array($a->user['email'] ?? '', $adminlist);
}
function explode_querystring($query)
{
$arg_st = strpos($query, '?');
if ($arg_st !== false) {
$base = substr($query, 0, $arg_st);
$arg_st += 1;
} else {
$base = '';
$arg_st = 0;
}
$args = explode('&', substr($query, $arg_st));
foreach ($args as $k => $arg) {
/// @TODO really compare type-safe here?
if ($arg === '') {
unset($args[$k]);
}
}
$args = array_values($args);
if (!$base) {
$base = $args[0];
unset($args[0]);
$args = array_values($args);
}
return [
'base' => $base,
'args' => $args,
];
}
/**
* Returns the complete URL of the current page, e.g.: http(s)://something.com/network
*

View File

@ -34,34 +34,39 @@
"league/html-to-markdown": "^4.8",
"level-2/dice": "^4",
"lightopenid/lightopenid": "dev-master",
"matriphe/iso-639": "^1.2",
"michelf/php-markdown": "^1.7",
"mobiledetect/mobiledetectlib": "^2.8",
"monolog/monolog": "^1.25",
"nikic/fast-route": "^1.3",
"paragonie/hidden-string": "^1.0",
"patrickschur/language-detection": "^3.4",
"pear/console_table": "^1.3",
"pear/text_languagedetect": "1.*",
"phpseclib/phpseclib": "^2.0",
"pragmarx/google2fa": "^5.0",
"pragmarx/recovery": "^0.1.0",
"psr/container": "^1.0",
"seld/cli-prompt": "^1.0",
"smarty/smarty": "^3.1",
"xemlock/htmlpurifier-html5": "^0.1.11",
"fxp/composer-asset-plugin": "^1.4",
"bower-asset/base64": "^1.0",
"bower-asset/chart-js": "^2.8",
"bower-asset/dompurify": "^1.0",
"bower-asset/fork-awesome": "^1.1",
"bower-asset/vue": "^2.6",
"npm-asset/cropperjs": "1.2.2",
"npm-asset/es-jquery-sortable": "^0.9.13",
"npm-asset/fullcalendar": "^3.10",
"npm-asset/imagesloaded": "4.1.4",
"npm-asset/jquery": "^2.0",
"npm-asset/jquery-colorbox": "^1.6",
"npm-asset/jquery-datetimepicker": "^2.5",
"npm-asset/jgrowl": "^1.4",
"npm-asset/moment": "^2.24",
"npm-asset/fullcalendar": "^3.10",
"npm-asset/cropperjs": "1.2.2",
"npm-asset/imagesloaded": "4.1.4",
"npm-asset/typeahead.js": "^0.11.1",
"bower-asset/fork-awesome": "^1.1"
"npm-asset/perfect-scrollbar": "0.6.16",
"npm-asset/textcomplete": "^0.18.2",
"npm-asset/typeahead.js": "^0.11.1"
},
"repositories": [
{
@ -74,14 +79,10 @@
"Friendica\\": "src/",
"Friendica\\Addon\\": "addon/"
},
"psr-0": {
"": "library/"
},
"files": [
"include/conversation.php",
"include/dba.php",
"include/enotify.php",
"include/items.php",
"boot.php"
]
},
@ -91,6 +92,9 @@
}
},
"config": {
"platform": {
"php": "7.0"
},
"autoloader-suffix": "Friendica",
"optimize-autoloader": true,
"preferred-install": "dist",
@ -124,7 +128,7 @@
"mikey179/vfsstream": "^1.6",
"mockery/mockery": "^1.2",
"johnkary/phpunit-speedtrap": "1.1",
"jakub-onderka/php-parallel-lint": "^1.0"
"php-parallel-lint/php-parallel-lint": "^1.2"
},
"scripts": {
"test": "phpunit"

1136
composer.lock generated

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -15,10 +15,13 @@ These endpoints use the [Mastodon API entities](https://docs.joinmastodon.org/en
## Implemented endpoints
- [`GET /api/v1//accounts/:id`](https://docs.joinmastodon.org/methods/accounts/#retrieve-information)
- [`GET /api/v1//accounts/:id/statuses`](https://docs.joinmastodon.org/methods/accounts/#retrieve-information)
- [`GET /api/v1/custom_emojis`](https://docs.joinmastodon.org/methods/instance/custom_emojis/)
- Doesn't return unicode emojis since they aren't using an image URL
- [`GET /api/v1/directory`](https://docs.joinmastodon.org/methods/instance/directory/)
- [`GET /api/v1/follow_requests`](https://docs.joinmastodon.org/methods/accounts/follow_requests#pending-follows)
- Returned IDs are specific to follow requests
- [`POST /api/v1/follow_requests/:id/authorize`](https://docs.joinmastodon.org/methods/accounts/follow_requests#accept-follow)
@ -33,6 +36,8 @@ These endpoints use the [Mastodon API entities](https://docs.joinmastodon.org/en
- [`GET /api/v1/instance`](https://docs.joinmastodon.org/methods/instance#fetch-instance)
- [`GET /api/v1/instance/peers`](https://docs.joinmastodon.org/methods/instance#list-of-connected-domains)
- [`GET /api/v1/timelines/public`](https://docs.joinmastodon.org/methods/timelines/)
- [`GET /api/v1/trends`](https://docs.joinmastodon.org/methods/instance/trends/)
## Non-implemented endpoints

View File

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

View File

@ -466,6 +466,19 @@ Hook data is a `\FastRoute\RouterCollector` object that should be used to add ad
**Notice**: The class whose name is provided in the route handler must be reachable via auto-loader.
### probe_detect
Called before trying to detect the target network of a URL.
If any registered hook function sets the `result` key of the hook data array, it will be returned immediately.
Hook functions should also return immediately if the hook data contains an existing result.
Hook data:
- **uri** (input): the profile URI.
- **network** (input): the target network (can be empty for auto-detection).
- **uid** (input): the user to return the contact data for (can be empty for public contacts).
- **result** (output): Set by the hook function to indicate a successful detection.
## Complete list of hook callbacks
Here is a complete list of all hook callbacks with file locations (as of 24-Sep-2018). Please see the source for details of any hooks not documented above.
@ -505,10 +518,6 @@ Here is a complete list of all hook callbacks with file locations (as of 24-Sep-
Hook::callAll('item_photo_menu', $args);
Hook::callAll('jot_tool', $jotplugins);
### include/items.php
Hook::callAll('page_info_data', $data);
### mod/directory.php
Hook::callAll('directory_item', $arr);
@ -595,10 +604,6 @@ Here is a complete list of all hook callbacks with file locations (as of 24-Sep-
Hook::callAll('post_local_end', $arr);
### mod/lockview.php
Hook::callAll('lockview_content', $item);
### mod/uexport.php
Hook::callAll('uexport_options', $options);
@ -670,6 +675,10 @@ Here is a complete list of all hook callbacks with file locations (as of 24-Sep-
Hook::callAll('register_account', $uid);
Hook::callAll('remove_user', $user);
### src/Module/PermissionTooltip.php
Hook::callAll('lockview_content', $item);
### src/Content/ContactBlock.php
Hook::callAll('contact_block_end', $arr);

View File

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

View File

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

View File

@ -4,49 +4,46 @@ Forums
* [Home](help)
Friendica also lets you create forums and/or celebrity accounts.
Friendica also lets you create community forums and other types of accounts that can function as discussion forums, celebrity accounts, announcement channels, news reflectors, or organization pages, depending on how you want to interact with others. Management of these pages can be delegated to other accounts, or a parent account can be designated to easily toggle multiple identities.
Every page in Friendica has a nickname and these must all be unique.
This applies to all forums, whether they are normal profiles or forum profiles.
Every page in Friendica has a nickname and these must all be unique. This applies to all forums, whether they are normal profiles or forum profiles.
Therefore the first thing you need to do to create a new forum is to register a new account for the forum.
Please note that the site administrator can restrict and/or regulate the registration of new accounts.
If you create a second account on a system and use the same email address or OpenID account as an existing account, you will no longer be able to use the email address (or OpenID) to log in to the account.
You should log in using the account nickname instead.
On the new account, visit the 'Settings' page.
Towards the end of the page are "Advanced Account/Page Type Settings".
Typically you would use "Normal Account" for a normal personal account.
This is the default selection.
Community Forum/Celebrity Accounts provide the ability for people to become friends/fans of the forum without requiring approval.
The exact setting you would use depends on how you wish to interact with people who join the page.
The "Soapbox" setting lets the page owner control all communications.
Everything you post will go out to the forum members, but there will be no opportunity for interaction.
This setting would typically be used for announcements or corporate communications.
The most common setting is the "Community Forum".
This creates a forum page where all members can freely interact.
The "Automatic Friend Account" is typically used for personal profile forums where you wish to automatically approve any friendship/connection requests.
Managing Multiple forums
Managing Accounts
---
We recommend that you create group forums with the same email address and password as your normal account.
If you do this, you will find a new "Manage" tab on the menu bar which lets you toggle identities easily and manage your forums.
You are not required to do this, but the alternative is to log out and log back into the other account to manage alternate forums.
This could get cumbersome if you manage several different forums/identities.
To create a new linked account that can be used as a forum, log in to your normal account and go to Settings > Manage Accounts.
Here you can register additional accounts with new nicknames that will be linked to your primary account.
You may also appoint a delegate to manage your forum.
Do this by visiting the [Delegation Setup Page](settings/delegation).
This will provide you with a list of contacts on this system under "Potential Delegates".
You may appoint a delegate to manage your new account (e.g. forum page).
The Delegates section of Manage Accounts page will provide you with a list of contacts on this instance under "Potential Delegates".
Selecting one or more persons will give them access to manage your forum.
They will be able to edit contacts, profiles, and all content for this account/page.
Please use this facility wisely.
Delegated managers will not be able to alter basic account settings such as passwords or page types and/or remove the account.
Delegated managers will not be able to alter basic account settings, such as passwords or page types, or remove the account.
Additionally, this page is also where you can choose to designate an account as a parent user.
If your primary account is designated as the parent user, you will be able to easily toggle identities and manage your forums or other types of accounts.
Types of Accounts
---
On the new account, visit the Settings > Account page.
Towards the end of the page is a section for "Advanced account types".
Typically you would use "Personal Page - Standard" for a normal personal account with manual approval of “friends” and “followers.”
This is the default selection.
On this page you can change the type of account if desired.
The other subtypes of a Personal Page are “Soapbox” and “Love-all.”
A Soapbox account is an announcement channel that automatically approvals follower requests.
Everything posted by the account will go out to the followers, but there will be no opportunity for interaction.
This setting would typically be used for announcements or corporate communications.
“Love-all” automatically approves contacts as friends.
In addition to Personal Page, there are options for Organization Page, News Page, and Community Forum.
Organization and New Pages automatically approve contact requests as followers.
Community Forum provide the ability for people to become friends/fans of the forum without requiring approval.
This creates a forum page where all members can freely interact.
Posting to Community forums
---

View File

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

View File

@ -35,6 +35,7 @@ Friendica Documentation and Resources
* [Using SSL with Friendica](help/SSL)
* [Config values that can only be set in config/local.config.php](help/Config)
* [Improve Performance](help/Improve-Performance)
* [Migrate](help/Migrate)
* [Administration Tools](help/tools)
**Developer Manual**

View File

@ -33,7 +33,7 @@ The account will expire after 7 days, but you can ask the server admin to keep y
* Apache with mod-rewrite enabled and "Options All" so you can use a local `.htaccess` file
* PHP 7+ (PHP 7.1+ is recommended for performance and official support)
* PHP *command line* access with register_argc_argv set to true in the php.ini file
* Curl, GD, PDO, MySQLi, hash, xml, zip and OpenSSL extensions
* Curl, GD, PDO, mbstrings, MySQLi, hash, xml, zip and OpenSSL extensions
* The POSIX module of PHP needs to be activated (e.g. [RHEL, CentOS](http://www.bigsoft.co.uk/blog/index.php/2014/12/08/posix-php-commands-not-working-under-centos-7) have disabled it)
* some form of email server or email gateway such that PHP mail() works
* MySQL 5.6+ or an equivalent alternative for MySQL (MariaDB, Percona Server etc.)
@ -47,7 +47,6 @@ For alternative server configurations (such as Nginx server and MariaDB database
### Optional
* PHP ImageMagick extension (php-imagick) for animated GIF support.
* [Composer](https://getcomposer.org/) for a git install
## Installation procedure
@ -61,6 +60,8 @@ If this is nothing for you, you might be interested in
### Get Friendica
Download the full archive of the stable release of Friendica core and the addons from [the project homepage](https://friendi.ca/resources/download-files/).
Make sure that the version of the Friendica archive and the addons match.
Unpack the Friendica files into the root of your web server document area.
If you copy the directory tree to your webserver, make sure that you also copy `.htaccess-dist` - as "dot" files are often hidden and aren't normally copied.
@ -72,7 +73,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 +89,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 +436,7 @@ provided by one of our members.
>
> This is obvious as soon as you notice that the friendica-cron uses `proc_open`
> to execute PHP scripts that also use `proc_open`, but it took me quite some time to find that out.
> I hope this saves some time for other people using suhosin with function blacklists.
> I hope this saves some time for other people using suhosin with function blocklists.
### Unable to create all mysql tables on MySQL 5.7.17 or newer

View File

@ -4,9 +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).
Most message passing involves the file include/items.php, which has functions for several feed-related import/export activities.
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).
When a message is posted, all immediate deliveries to all networks are made using include/notifier.php, which chooses how (and to whom) to deliver the message.
This file also invokes the local side of all deliveries including DFRN-notify.

92
doc/Migrate.md Normal file
View File

@ -0,0 +1,92 @@
Migrating to a new server installation
===============
* [Home](help)
## Preparation
### New server
Set up your new server as described [here](Install); follow the installation procedure until you have created a database.
### Heads up to users
Inform your users of an upcoming interruption to your service.
To ensure data consistency, your server needs to be offline during some steps of the migration processes.
You may also find these addons useful for communicating with your users prior to the migration process:
* blackout
* notifyall
### Storage
Check your storage backend with ``bin/console storage list`` in the root folder.
The output should look like this:
````
Sel | Name
-----------------------
| Filesystem
* | Database
````
If you are *not* using ``Database`` run the following commands:
1. ``bin/console storage set Database`` to activate the database backend.
2. ``bin/console storage move`` to initiate moving the stored image files.
This process may take a long time depending on the size of your storage and your server's capacity.
Prior to initiating this process, you may want to check the number of files in the storage with the following command: ``tree -if -I index.html /path/to/storage/``.
### Cleaning up
Before transferring your database, you may want to clean it up; ensure the expiration of database items is set to a reasonable value and activated via the administrator panel.
*Admin* > *Site* > *Performance* > Enable "Clean up database"
After adjusting these settings, the database cleaning up processes will be initiated according to your configured daily cron job.
To review the size of your database, log into MySQL with ``mysql -p`` run the following query:
````
SELECT table_schema AS "Database", SUM(data_length + index_length) / 1024 / 1024 / 1024 AS "Size (GB)" FROM information_schema.TABLES GROUP BY table_schema;
````
You should see an output like this:
````
+--------------------+----------------+
| Database | Size (GB) |
+--------------------+----------------+
| friendica_db | 8.054092407227 |
| [..........] | [...........] |
+--------------------+----------------+
````
Finally, you may also want to optimise your database with the following command: ``mysqloptimize -p friendica-db``
### Going offline
Stop background tasks and put your server in maintenance mode.
1. If you had set up a worker cron job like this ``*/10 * * * * cd /var/www/friendica; /usr/bin/php bin/worker.php`` run ``crontab -e`` and comment out this line. Alternatively if you deploy a worker daemon, disable this instead.
2. Put your server into maintenance mode: ``bin/console maintenance 1 "We are currently upgrading our system and will be back soon."``
## Dumping DB
Export your database: ``mysqldump -p friendica_db > friendica_db-$(date +%Y%m%d).sql`` and possibly compress it.
## Transferring to new server
Transfer your database and a copy of your configuration file ``config/local.config.php.copy`` to your new server installation.
## Restoring your DB
Import your database on your new server: ``mysql -p friendica_db < your-friendica_db-file.sql``
## Completing migration
### Configuration file
Copy your old server's configuration file to ``config/local.config.php``.
Ensure the newly created database credentials are identical to the setting in the configuration file; otherwise update them accordingly.
### Cron job for worker
Set up the required daily cron job.
Run ``crontab -e`` and add the following line according to your system specification
``*/10 * * * * cd /var/www/friendica; /usr/bin/php bin/worker.php``
### DNS settings
Adjust your DNS records by pointing them to your new server.
## Troubleshooting
If you are unable to login to your newly migrated Friendica installation, check your web server's error and access logs and mysql logs for obvious issues.
If still unable to resolve the problem, it's likely an issue with your [installation](Install).
In this case, you may try to an entirely new Friendica installation on your new server, but use a different FQDN and DNS name.
Once you have this up and running, take it offline and purge the database and configuration file and try migrating to this installation.

21
doc/README.md Normal file
View File

@ -0,0 +1,21 @@
# About the docs of the Friendica Project
**Note**: It is expected that some of the links in these files wont work in the Friendica repository as they are supposed to work on an installed Friendica node.
## User and Admin documentation
Every Friendica node has the _current_ version of the user and admin documentation available in the `/help` location.
The documentation is mainly done in English, but the pages can be translated and some are already to German.
If you want to help expanding the documentation or the translation, please register an account at the [Friendica wiki](https://wiki.friendi.ca) where the [texts are maintained](https://wiki.friendi.ca/docs).
The documentation is periodically merged back from there to the _development_ branch of Friendica.
Images that you use in the documentation should be located in the `img` sub-directory of this directory.
Translations are located in sub-directories named after the language codes, e.g. `de`.
Depending on the selected interface language the different translations will be applied, or the `en` original will be used as a fall-back.
## Developers Documentation
We provide a configuration file for [Doxygen](https://www.doxygen.nl/index.html) in the root of the Friendica repository.
With that you should be able to extract some documentation from the source code.
In addition there are some documentation files about the database structure in `doc`db`.

View File

@ -74,7 +74,7 @@ You can chose between the following modes:
##### Invitation based registry
Additionally to the setting in the admin panel, you can decide if registrations are only possible using an invitation code or not.
To enable invitation based registration, you have to set the `invitation_only` setting in the [config/local.config.php](/help/Config) file.
To enable invitation based registration, you have to set the `invitation_only` setting to `true` in the `system` section of the [config/local.config.php](/help/Config) file.
If you want to use this method, the registration policy has to be set to either *open* or *requires approval*.
#### Check Full Names
@ -182,7 +182,7 @@ By default, any (valid) email address is allowed in registrations.
#### Allow Users to set remote_self
If you enable the `Allow Users to set remote_self` users can select Atom feeds from their contact list being their *remote self* in the advanced contact settings.
If you enable the `Allow Users to set remote_self` users can select Atom feeds from their contact list being their *remote self* in the contact settings.
Which means that postings by the remote self are automatically reposted by Friendica in their names.
This feature can be used to let the user mirror e.g. blog postings into their Friendica postings.
@ -240,15 +240,9 @@ This section allows you to configure the background process that is triggered by
The process does check the available system resources before creating a new worker for a task.
Because of this, it may happen that the maximum number of worker processes you allow will not be reached.
If your server setup does not allow you to use the `proc_open` function of PHP, please disable it in this section.
The tasks for the background process have priorities.
To guarantee that important tasks are executed even though the system has a lot of work to do, it is useful to enable the *fastlane*.
Should you not be able to run a cron job on your server, you can also activate the *frontend* worker.
If you have done so, you can call `example.com/worker` (replace example.com with your actual domain name) on a regular basis from an external service.
This will then trigger the execution of the background process.
### Relocate
## Users

View File

@ -36,11 +36,11 @@ The addon tree has to be updated separately like so:
git pull
For both repositories:
The default branch to use is the ``master`` branch, which is the stable version of Friendica.
The default branch to use is the ``stable`` branch, which is the stable version of Friendica.
It is updated about four times a year on a fixed schedule.
If you want to use and test bleeding edge code please checkout the ``develop`` branch.
The new features and fixes will be merged from ``develop`` into ``master`` after a release candidate period before each release.
The new features and fixes will be merged from ``develop`` into ``stable`` after a release candidate period before each release.
Warning: The ``develop`` branch is unstable, and breaks on average once a month for at most 24 hours until a patch is submitted and merged.
Be sure to pull frequently if you choose the ``develop`` branch.

View File

@ -18,9 +18,6 @@ Database Tables
| [event](help/database/db_event) | Events |
| [fcontact](help/database/db_fcontact) | friend suggestion stuff |
| [fsuggest](help/database/db_fsuggest) | friend suggestion stuff |
| [gcign](help/database/db_gcign) | contacts ignored by friend suggestions |
| [gcontact](help/database/db_gcontact) | global contacts |
| [glink](help/database/db_glink) | "friends of friends" linkages derived from poco |
| [group](help/database/db_group) | privacy groups, group info |
| [group_member](help/database/db_group_member) | privacy groups, member info |
| [gserver](help/database/db_gserver) | |

View File

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

View File

@ -1,10 +0,0 @@
Table gcign
===========
| Field | Description | Type | Null | Key | Default | Extra |
| ----- | ------------------------------ | ------- | ---- | --- | ------- | --------------- |
| id | sequential ID | int(11) | NO | PRI | NULL | auto_increment |
| uid | local user.id | int(11) | NO | MUL | 0 | |
| gcid | gcontact.id of ignored contact | int(11) | NO | MUL | 0 | |
Return to [database documentation](help/database)

View File

@ -1,32 +0,0 @@
Table gcontact
==============
| Field |Description | Type | Null | Key | Default | Extra |
|--------------|------------------------------------|------------------|------|-----|---------------------|----------------|
| id | sequential ID | int(10) unsigned | NO | PRI | NULL | auto_increment |
| name | Name that this contact is known by | varchar(255) | NO | | | |
| nick | Nick- and user name of the contact | varchar(255) | NO | | | |
| url | Link to the contacts profile page | varchar(255) | NO | | | |
| nurl | | varchar(255) | NO | MUL | | |
| photo | Link to the profile photo | varchar(255) | NO | | | |
| connect | | varchar(255) | NO | | | |
| created | | datetime | NO | | 0001-01-01 00:00:00 | |
| updated | | datetime | YES | MUL | 0001-01-01 00:00:00 | |
| last_contact | | datetime | YES | | 0001-01-01 00:00:00 | |
| last_failure | | datetime | YES | | 0001-01-01 00:00:00 | |
| location | | varchar(255) | NO | | | |
| about | | text | NO | | NULL | |
| keywords | puplic keywords (interests) | text | NO | | NULL | |
| gender | | varchar(32) | NO | | | |
| birthday | | varchar(32) | NO | | 0001-01-01 | |
| community | 1 if contact is forum account | tinyint(1) | NO | | 0 | |
| hide | 1 = should be hidden from search | tinyint(1) | NO | | 0 | |
| nsfw | 1 = contact posts nsfw content | tinyint(1) | NO | | 0 | |
| network | social network protocol | varchar(255) | NO | | | |
| addr | | varchar(255) | NO | | | |
| notify | | text | NO | | | |
| alias | | varchar(255) | NO | | | |
| generation | | tinyint(3) | NO | | 0 | |
| server_url | baseurl of the contacts server | varchar(255) | NO | | | |
Return to [database documentation](help/database)

View File

@ -1,13 +0,0 @@
Table glink
===========
| Field | Description | Type | Null | Key | Default | Extra |
|---------|------------------|------------------|------|-----|---------------------|----------------|
| id | sequential ID | int(10) unsigned | NO | PRI | NULL | auto_increment |
| cid | | int(11) | NO | MUL | 0 | |
| uid | | int(11) | NO | | 0 | |
| gcid | | int(11) | NO | MUL | 0 | |
| zcid | | int(11) | NO | MUL | 0 | |
| updated | | datetime | NO | | 0001-01-01 00:00:00 | |
Return to [database documentation](help/database)

View File

@ -226,10 +226,6 @@ Eine komplette Liste aller Hook-Callbacks mit den zugehörigen Dateien (am 01-Ap
Hook::callAll('item_photo_menu', $args);
Hook::callAll('jot_tool', $jotplugins);
### include/items.php
Hook::callAll('page_info_data', $data);
### mod/directory.php
Hook::callAll('directory_item', $arr);
@ -316,10 +312,6 @@ Eine komplette Liste aller Hook-Callbacks mit den zugehörigen Dateien (am 01-Ap
Hook::callAll('post_local_end', $arr);
### mod/lockview.php
Hook::callAll('lockview_content', $item);
### mod/uexport.php
Hook::callAll('uexport_options', $options);
@ -426,6 +418,10 @@ Eine komplette Liste aller Hook-Callbacks mit den zugehörigen Dateien (am 01-Ap
Hook::callAll('storage_instance', $data);
### src/Module/PermissionTooltip.php
Hook::callAll('lockview_content', $item);
### src/Worker/Directory.php
Hook::callAll('globaldir_update', $arr);

View File

@ -65,17 +65,17 @@ table.bbcodes > * > tr > th {
<td><a href="http://friendi.ca" target="external-link">Friendica</a></td>
</tr>
<tr>
<td>[img]https://raw.githubusercontent.com/friendica/friendica/master/images/friendica-32.jpg[/img]</td>
<td><img src="https://raw.githubusercontent.com/friendica/friendica/master/images/friendica-32.jpg" alt="Immagine/foto"></td>
<td>[img]https://raw.githubusercontent.com/friendica/friendica/stable/images/friendica-32.jpg[/img]</td>
<td><img src="https://raw.githubusercontent.com/friendica/friendica/stable/images/friendica-32.jpg" alt="Immagine/foto"></td>
</tr>
<tr>
<td>[img=https://raw.githubusercontent.com/friendica/friendica/master/images/friendica-32.jpg]Das Friendica Logo[/img]</td>
<td><img src="https://raw.githubusercontent.com/friendica/friendica/master/images/friendica-32.jpg" alt="Das Friendica Logo"></td>
<td>[img=https://raw.githubusercontent.com/friendica/friendica/stable/images/friendica-32.jpg]Das Friendica Logo[/img]</td>
<td><img src="https://raw.githubusercontent.com/friendica/friendica/stable/images/friendica-32.jpg" alt="Das Friendica Logo"></td>
</tr>
<tr>
<td>[img=64x32]https://raw.githubusercontent.com/friendica/friendica/master/images/friendica-32.jpg[/img]<br>
<td>[img=64x32]https://raw.githubusercontent.com/friendica/friendica/stable/images/friendica-32.jpg[/img]<br>
<br>Note: provided height is simply discarded.</td>
<td><img src="https://raw.githubusercontent.com/friendica/friendica/master/images/friendica-32.jpg" style="width: 64px;"></td>
<td><img src="https://raw.githubusercontent.com/friendica/friendica/stable/images/friendica-32.jpg" style="width: 64px;"></td>
</tr>
<tr>
<td>[size=xx-small]kleiner Text[/size]</td>
@ -482,10 +482,6 @@ Du kannst Videos, Musikdateien und weitere Dinge in Beitr&auml;gen einbinden.
<td>[embed]URL[/embed]</td>
<td>OEmbed rich content einbetten.</td>
</tr>
<tr>
<td>[iframe]URL[/iframe]</td>
<td>General embed, iframe size is limited by the theme size for video players.</td>
</tr>
<tr>
<td>[url]*url*[/url]</td>
<td>Wenn *url* die OEmbed- oder Opengraph-Spezifikationen unterst&uuml;tzt, wird das Objekt eingebettet (z.B. Dokumente von scribd).

View File

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

View File

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

View File

@ -6,9 +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).
Der Großteil der Nachrichtenverarbeitung nutzt die Datei include/items.php, welche Funktionen für verschiedene Feed-bezogene Import-/Exportaktivitäten liefert.
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).
Wenn eine Nachricht veröffentlicht wird, werden alle Übermittlungen an alle Netzwerke mit include/notifier.php durchgeführt, welche entscheidet, wie und an wen die Nachricht geliefert wird.
Diese Datei bindet dabei die lokale Bearbeitung aller Übertragungen ein inkl. dfrn-notify.

View File

@ -172,7 +172,7 @@ Wildcards werden akzeptiert Standardmäßig sind alle gültigen Email-Adressen e
#### Nutzern erlauben das remote_self Flag zu setzen
Webb du die Option `Nutzern erlauben das remote_self Flag zu setzen` aktivierst, können alle Nutzer Atom Feeds in den erweiterten Einstellungen des Kontakts als "Entferntes Konto" markieren.
Webb du die Option `Nutzern erlauben das remote_self Flag zu setzen` aktivierst, können alle Nutzer Atom Feeds in den Kontakteinstellungen als "Entferntes Konto" markieren.
Dadurch werden automatisch alle Beiträge dieser Feeds für diesen Nutzer gespiegelt und an die Kontakte bei Friendica verteilt.
Dieses Feature kann z.B. dafür genutzt werden Blogbeiträge zu spiegeln.
@ -227,15 +227,9 @@ In diesem Abschnitt kann der Hintergrund-Prozess konfiguriert werden.
Bevor ein neuer *Worker* Prozess gestartet wird, überprüft das System, dass die vorhandenen Resourchen ausrechend sind,
Aus diesem Grund kann es sein, dass die maximale Zahl der Hintergrungprozesse nicht erreicht wird.
Sollte die PHP Funktion `proc_open` auf dem Server nicht verfügbar sein, kann die Verwendung durch Friendica hier unterbunden werden.
Die Aufgaben die im Hintergrund erledigt werden, haben Prioritäten zugeteilt.
Um garantieren zu können, das wichtige Prozesse schnellst möglich abgearbeitet werden können, selbst wenn das System gerade stark belastet ist, sollte die *fastlane* aktiviert sein.
Wenn es auf deinem Server nicht möglich ist, einen cron Job zu starten, kannst du den *frontend* Worker einschalten.
Nachdem dies geschehen ist, kannst du `example.com/worker` (tausche example.com mit dem echten Domainnamen aus) aufrufen werden.
Dadurch werden dann die Aufgaben aktiviert, die der cron Job sonst aktivieren würde.
### Umsiedeln
## Nutzer

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -87,7 +87,7 @@ Field parameter:
1. Label for the input box,
2. Current value of the variable,
3. Help text for the input box,
4. if set to "required" modern browser will check that this input box is filled when submitting the form,
4. Should be set to the translation of "Required" to mark this field as required,
5. if set to "autofocus" modern browser will put the cursur into this box once the page is loaded,
6. if set, it will be used for the input type, default is `text` (possible types: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input#%3Cinput%3E_types).
@ -122,7 +122,7 @@ Field parameter:
1. Label for the field,
2. Value for the field, e.g. the old password,
3. Help text for the input field,
4. if set to "required" modern browser will check that this field is filled out,
4. Should be set to the translation of "Required" to mark this field as required,
5. if set to "autofocus" modern browser will put the cursor automatically into this input field.
### field_radio.tpl
@ -176,5 +176,5 @@ Field parameter:
0. Name of the input field,
1. Label for the input box,
2. Current text for the box,
3. Help text for the input box.
4. if set to "required" modern browser will check that this input box is filled when submitting the form,
3. Help text for the input box,
4. Should be set to the translation of "Required" to mark this field as required.

View File

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

View File

@ -27,6 +27,7 @@ The console provides the following commands:
* typo: Checks for parse errors in Friendica files
* postupdate: Execute pending post update scripts (can last days)
* storage: Manage storage backend
* relay: Manage ActivityPub relay servers
Please consult *bin/console help* on the command line interface of your server for details about the commands.

View File

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

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 138 B

After

Width:  |  Height:  |  Size: 111 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 138 B

After

Width:  |  Height:  |  Size: 111 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1021 B

After

Width:  |  Height:  |  Size: 308 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1015 B

After

Width:  |  Height:  |  Size: 407 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 10 KiB

BIN
images/friendica-192.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

BIN
images/friendica-192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 4.5 KiB

BIN
images/friendica-512.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

BIN
images/friendica-512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

View File

@ -1,240 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="96"
height="96"
id="svg2"
version="1.1"
inkscape:version="0.48.0 r9654"
sodipodi:docname="friendica.svg"
inkscape:export-filename="/home/meta/Documents/My random images/friendica.png"
inkscape:export-xdpi="80.552788"
inkscape:export-ydpi="80.552788">
<defs
id="defs4">
<linearGradient
id="highlightgradient">
<stop
id="stop3833"
offset="0"
style="stop-color:#ffffff;stop-opacity:0.74374998;" />
<stop
style="stop-color:#ffffff;stop-opacity:0;"
offset="1"
id="stop3829" />
</linearGradient>
<linearGradient
id="shadowgradient">
<stop
id="stop3833-5"
offset="0"
style="stop-color:#000000;stop-opacity:0.5;" />
<stop
style="stop-color:#818080;stop-opacity:0;"
offset="1"
id="stop3829-9" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#highlightgradient"
id="linearGradient4011"
x1="44.948269"
y1="0"
x2="54.103466"
y2="46.797421"
gradientUnits="userSpaceOnUse"
gradientTransform="scale(1,0.54545455)" />
<linearGradient
inkscape:collect="always"
xlink:href="#shadowgradient"
id="linearGradient4021"
x1="52.016712"
y1="96"
x2="42.867535"
y2="41.837971"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1,0,0,0.5,0,48)" />
<filter
inkscape:collect="always"
id="filter4055"
x="-0.03"
width="1.06"
y="-0.12"
height="1.24">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="1.2"
id="feGaussianBlur4057" />
</filter>
<filter
inkscape:collect="always"
id="filter4059"
x="-0.029877551"
width="1.0597551"
y="-0.122"
height="1.244">
<feGaussianBlur
inkscape:collect="always"
stdDeviation="1.22"
id="feGaussianBlur4061" />
</filter>
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="2.9132799"
inkscape:cx="53.033009"
inkscape:cy="2.8284271"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="true"
width="256px"
inkscape:snap-global="true"
inkscape:window-width="1680"
inkscape:window-height="1010"
inkscape:window-x="194"
inkscape:window-y="0"
inkscape:window-maximized="0">
<inkscape:grid
type="xygrid"
id="grid2985"
empspacing="3"
visible="true"
enabled="true"
snapvisiblegridlinesonly="true"
spacingx="2px"
spacingy="2px" />
</sodipodi:namedview>
<metadata
id="metadata7">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Colors"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-956.3622)"
style="display:inline">
<path
style="fill:#ffc019;fill-opacity:1;stroke:none"
d="M 16,0 C 7.0091019,0.04308252 0,7.0521845 0,16 0,16 0,57.499123 0,80 0,89.120146 7.0091019,96 16,96 L 32,96 32,70 64,70 63.916016,46.068359 32,46.236328 32,26 64,26 64,0 C 64,0 24,0 16,0 z"
transform="translate(0,956.3622)"
id="rect2993"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccsccccccccc" />
<path
style="fill:#1872a2;fill-opacity:1;stroke:none"
d="m 80,1052.3622 c 8.990898,0 16.086165,-6.966 16,-16 0,0 0,-41.4991 0,-64 0.07767,-9.01639 -7.067354,-16 -16,-16 l -16,0 0,26 -32,0 0,22 32,0 0,22 -32,0 0,26 c 0,0 32,0 48,0 z"
id="rect2993-6"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccsccccccccc" />
</g>
<g
style="display:inline"
inkscape:label="Lines as original logo"
id="g3997"
inkscape:groupmode="layer">
<path
sodipodi:nodetypes="cccccccc"
inkscape:connector-curvature="0"
id="path3999"
d="m 64,0 0,26 -32,0 0,22 m 32,0 0,22 -32,0 0,26"
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
<rect
ry="16"
rx="16"
y="0"
x="0"
height="96"
width="96"
id="rect4001"
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
</g>
<g
inkscape:groupmode="layer"
id="layer3"
inkscape:label="Lines with center break"
style="display:none">
<path
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
d="m 64,0 0,26 -32,0 0,22 32,0 0,22 -32,0 0,26"
id="path3926"
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccccccc" />
<rect
style="fill:none;stroke:#000000;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
id="rect3928"
width="96"
height="96"
x="0"
y="0"
rx="16"
ry="16" />
</g>
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="Effects"
style="display:inline">
<rect
style="fill:url(#linearGradient3930);fill-opacity:1;stroke:none"
id="rect3823"
width="96"
height="48.04369"
x="-3.1086245e-15"
y="1.8024861e-14"
ry="15.215644"
rx="15.214664" />
<rect
style="fill:url(#linearGradient3904);fill-opacity:1;stroke:none"
id="rect3823-8"
width="96"
height="47.86721"
x="1.5376101e-14"
y="-96"
ry="15.159752"
rx="15.214664"
transform="scale(1,-1)" />
<rect
style="fill:url(#linearGradient4011);fill-opacity:1;stroke:none;filter:url(#filter4059)"
id="rect4003"
width="98"
height="24"
x="0"
y="0"
rx="15.214664"
ry="8.2994423"
transform="matrix(1.0296115,0,0,1.1963836,-2.901924,-4.7132067)" />
<rect
style="opacity:0.56746030000000003;fill:url(#linearGradient4021);fill-opacity:1;stroke:none;filter:url(#filter4055)"
id="rect4013"
width="96"
height="24"
x="0"
y="72"
rx="14.008356"
ry="12"
transform="matrix(0.9768331,0,0,0.91974646,1.1649641,8.098115)" />
</g>
</svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" id="svg2" width="96" height="96" version="1.1"><defs id="defs4"><linearGradient id="highlightgradient"><stop id="stop3833" offset="0" style="stop-color:#fff;stop-opacity:.74374998"/><stop style="stop-color:#fff;stop-opacity:0" id="stop3829" offset="1"/></linearGradient><linearGradient id="shadowgradient"><stop id="stop3833-5" offset="0" style="stop-color:#000;stop-opacity:.5"/><stop style="stop-color:#818080;stop-opacity:0" id="stop3829-9" offset="1"/></linearGradient><linearGradient id="linearGradient4011" x1="44.948" x2="54.103" y1="0" y2="46.797" gradientTransform="scale(1,0.54545455)" gradientUnits="userSpaceOnUse" xlink:href="#highlightgradient"/><linearGradient id="linearGradient4021" x1="52.017" x2="42.868" y1="96" y2="41.838" gradientTransform="matrix(1,0,0,0.5,0,48)" gradientUnits="userSpaceOnUse" xlink:href="#shadowgradient"/><filter id="filter4055" width="1.06" height="1.24" x="-.03" y="-.12"><feGaussianBlur id="feGaussianBlur4057" stdDeviation="1.2"/></filter><filter id="filter4059" width="1.06" height="1.244" x="-.03" y="-.122"><feGaussianBlur id="feGaussianBlur4061" stdDeviation="1.22"/></filter></defs><metadata id="metadata7"/><g id="layer1" transform="translate(0,-956.3622)" style="display:inline"><path style="fill:#ffc019;fill-opacity:1;stroke:none" id="rect2993" d="M 16,0 C 7.0091019,0.04308252 0,7.0521845 0,16 0,16 0,57.499123 0,80 0,89.120146 7.0091019,96 16,96 L 32,96 32,70 64,70 63.916016,46.068359 32,46.236328 32,26 64,26 64,0 C 64,0 24,0 16,0 z" transform="translate(0,956.3622)"/><path style="fill:#1872a2;fill-opacity:1;stroke:none" id="rect2993-6" d="m 80,1052.3622 c 8.990898,0 16.086165,-6.966 16,-16 0,0 0,-41.4991 0,-64 0.07767,-9.01639 -7.067354,-16 -16,-16 l -16,0 0,26 -32,0 0,22 32,0 0,22 -32,0 0,26 c 0,0 32,0 48,0 z"/></g><g style="display:inline" id="g3997"><path id="path3999" d="m 64,0 0,26 -32,0 0,22 m 32,0 0,22 -32,0 0,26" style="fill:none;stroke:#000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"/><rect id="rect4001" width="96" height="96" x="0" y="0" rx="16" ry="16" style="fill:none;stroke:#000;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"/></g><g id="layer3" style="display:none"><path style="fill:none;stroke:#000;stroke-width:4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" id="path3926" d="m 64,0 0,26 -32,0 0,22 32,0 0,22 -32,0 0,26"/><rect style="fill:none;stroke:#000;stroke-width:4;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" id="rect3928" width="96" height="96" x="0" y="0" rx="16" ry="16"/></g><g id="layer2" style="display:inline"><rect style="fill:url(#linearGradient3930);fill-opacity:1;stroke:none" id="rect3823" width="96" height="48.044" x="0" y="0" rx="15.215" ry="15.216"/><rect style="fill:url(#linearGradient3904);fill-opacity:1;stroke:none" id="rect3823-8" width="96" height="47.867" x="0" y="-96" rx="15.215" ry="15.16" transform="scale(1,-1)"/><rect style="fill:url(#linearGradient4011);fill-opacity:1;stroke:none;filter:url(#filter4059)" id="rect4003" width="98" height="24" x="0" y="0" rx="15.215" ry="8.299" transform="matrix(1.0296115,0,0,1.1963836,-2.901924,-4.7132067)"/><rect style="opacity:.56746030000000003;fill:url(#linearGradient4021);fill-opacity:1;stroke:none;filter:url(#filter4055)" id="rect4013" width="96" height="24" x="0" y="72" rx="14.008" ry="12" transform="matrix(0.9768331,0,0,0.91974646,1.1649641,8.098115)"/></g></svg>

Before

Width:  |  Height:  |  Size: 7.2 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 625 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 47 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1004 B

After

Width:  |  Height:  |  Size: 211 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 145 B

After

Width:  |  Height:  |  Size: 121 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 932 B

After

Width:  |  Height:  |  Size: 229 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 866 B

After

Width:  |  Height:  |  Size: 123 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 912 B

After

Width:  |  Height:  |  Size: 315 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 999 B

After

Width:  |  Height:  |  Size: 207 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 826 B

After

Width:  |  Height:  |  Size: 822 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1003 B

After

Width:  |  Height:  |  Size: 916 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 885 B

After

Width:  |  Height:  |  Size: 868 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 598 B

After

Width:  |  Height:  |  Size: 392 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 577 B

After

Width:  |  Height:  |  Size: 373 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 497 B

After

Width:  |  Height:  |  Size: 297 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 938 B

After

Width:  |  Height:  |  Size: 235 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 924 B

After

Width:  |  Height:  |  Size: 221 B

View File

@ -43,7 +43,8 @@ use Friendica\Model\Notify;
use Friendica\Model\Photo;
use Friendica\Model\User;
use Friendica\Model\UserItem;
use Friendica\Network\FKOAuth1;
use Friendica\Model\Verb;
use Friendica\Security\FKOAuth1;
use Friendica\Network\HTTPException;
use Friendica\Network\HTTPException\BadRequestException;
use Friendica\Network\HTTPException\ExpectationFailedException;
@ -57,6 +58,8 @@ use Friendica\Network\HTTPException\UnauthorizedException;
use Friendica\Object\Image;
use Friendica\Protocol\Activity;
use Friendica\Protocol\Diaspora;
use Friendica\Security\OAuth1\OAuthRequest;
use Friendica\Security\OAuth1\OAuthUtil;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Images;
use Friendica\Util\Network;
@ -64,7 +67,6 @@ use Friendica\Util\Proxy as ProxyUtils;
use Friendica\Util\Strings;
use Friendica\Util\XML;
require_once __DIR__ . '/../mod/share.php';
require_once __DIR__ . '/../mod/item.php';
require_once __DIR__ . '/../mod/wall_upload.php';
@ -263,7 +265,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;
@ -307,22 +312,22 @@ function api_call(App $a, App\Arguments $args = null)
}
$type = "json";
if (strpos($args->getQueryString(), ".xml") > 0) {
if (strpos($args->getCommand(), ".xml") > 0) {
$type = "xml";
}
if (strpos($args->getQueryString(), ".json") > 0) {
if (strpos($args->getCommand(), ".json") > 0) {
$type = "json";
}
if (strpos($args->getQueryString(), ".rss") > 0) {
if (strpos($args->getCommand(), ".rss") > 0) {
$type = "rss";
}
if (strpos($args->getQueryString(), ".atom") > 0) {
if (strpos($args->getCommand(), ".atom") > 0) {
$type = "atom";
}
try {
foreach ($API as $p => $info) {
if (strpos($args->getQueryString(), $p) === 0) {
if (strpos($args->getCommand(), $p) === 0) {
if (!api_check_method($info['method'])) {
throw new MethodNotAllowedException();
}
@ -331,16 +336,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');
@ -380,7 +385,7 @@ function api_call(App $a, App\Arguments $args = null)
}
Logger::warning(API_LOG_PREFIX . 'not implemented', ['module' => 'api', 'action' => 'call', 'query' => DI::args()->getQueryString()]);
throw new NotImplementedException();
throw new NotFoundException();
} catch (HTTPException $e) {
header("HTTP/1.1 {$e->getCode()} {$e->httpdesc}");
return api_error($type, $e, $args);
@ -623,7 +628,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"],
@ -650,8 +655,8 @@ function api_get_user(App $a, $contact_id = null)
'notifications' => false,
'statusnet_profile_url' => $contact["url"],
'uid' => 0,
'cid' => Contact::getIdForURL($contact["url"], api_user(), true),
'pid' => Contact::getIdForURL($contact["url"], 0, true),
'cid' => Contact::getIdForURL($contact["url"], api_user(), false),
'pid' => Contact::getIdForURL($contact["url"], 0, false),
'self' => 0,
'network' => $contact["network"],
];
@ -675,7 +680,7 @@ function api_get_user(App $a, $contact_id = null)
$countfollowers = 0;
$starred = 0;
$pcontact_id = Contact::getIdForURL($uinfo[0]['url'], 0, true);
$pcontact_id = Contact::getIdForURL($uinfo[0]['url'], 0, false);
if (!empty($profile['about'])) {
$description = $profile['about'];
@ -697,7 +702,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"],
@ -727,7 +732,7 @@ function api_get_user(App $a, $contact_id = null)
'statusnet_profile_url' => $uinfo[0]['url'],
'uid' => intval($uinfo[0]['uid']),
'cid' => intval($uinfo[0]['cid']),
'pid' => Contact::getIdForURL($uinfo[0]["url"], 0, true),
'pid' => Contact::getIdForURL($uinfo[0]["url"], 0, false),
'self' => $uinfo[0]['self'],
'network' => $uinfo[0]['network'],
];
@ -1240,7 +1245,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 +1315,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)
@ -1558,7 +1563,7 @@ function api_search($type)
$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']];
@ -1646,7 +1651,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` = ?";
@ -2033,35 +2039,40 @@ function api_statuses_repeat($type)
Logger::log('API: api_statuses_repeat: '.$id);
$fields = ['uri-id', 'body', 'title', 'attach', 'author-name', 'author-link', 'author-avatar', 'guid', 'created', 'plink'];
$fields = ['uri-id', 'network', 'body', 'title', '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'] != "") {
if (strpos($item['body'], "[/share]") !== false) {
$pos = strpos($item['body'], "[share");
$post = substr($item['body'], $pos);
if (in_array($item['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::TWITTER])) {
if (!Item::performActivity($id, 'announce', local_user())) {
throw new InternalServerErrorException();
}
$item_id = $id;
} else {
$post = share_header($item['author-name'], $item['author-link'], $item['author-avatar'], $item['guid'], $item['created'], $item['plink']);
if (strpos($item['body'], "[/share]") !== false) {
$pos = strpos($item['body'], "[share");
$post = substr($item['body'], $pos);
} else {
$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";
if (!empty($item['title'])) {
$post .= '[h3]' . $item['title'] . "[/h3]\n";
}
$post .= $item['body'];
$post .= "[/share]";
}
$_REQUEST['body'] = $post;
$_REQUEST['profile_uid'] = api_user();
$_REQUEST['api_source'] = true;
if (empty($_REQUEST['source'])) {
$_REQUEST["source"] = api_source();
}
$post .= $item['body'];
$post .= "[/share]";
$item_id = item_post($a);
}
$_REQUEST['body'] = $post;
$_REQUEST['attach'] = $item['attach'];
$_REQUEST['profile_uid'] = api_user();
$_REQUEST['api_source'] = true;
if (empty($_REQUEST['source'])) {
$_REQUEST["source"] = api_source();
}
$item_id = item_post($a);
/// @todo Copy tags from the original post to the new one
} else {
throw new ForbiddenException();
}
@ -2152,10 +2163,10 @@ function api_statuses_mentions($type)
// get last network messages
// params
$since_id = $_REQUEST['since_id'] ?? 0;
$max_id = $_REQUEST['max_id'] ?? 0;
$count = $_REQUEST['count'] ?? 20;
$page = $_REQUEST['page'] ?? 1;
$since_id = intval($_REQUEST['since_id'] ?? 0);
$max_id = intval($_REQUEST['max_id'] ?? 0);
$count = intval($_REQUEST['count'] ?? 20);
$page = intval($_REQUEST['page'] ?? 1);
$start = max(0, ($page - 1) * $count);
@ -2228,12 +2239,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;
@ -2254,7 +2260,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) {
@ -2491,10 +2498,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']);
@ -2520,7 +2527,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
@ -3027,7 +3034,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']) {
@ -3304,7 +3311,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` = ?";
@ -3576,96 +3584,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.
*
@ -4161,26 +4079,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]);
@ -4309,7 +4219,7 @@ function api_fr_photo_create_update($type)
$deny_cid = $_REQUEST['deny_cid' ] ?? null;
$allow_gid = $_REQUEST['allow_gid'] ?? null;
$deny_gid = $_REQUEST['deny_gid' ] ?? null;
$visibility = !empty($_REQUEST['visibility']) && $_REQUEST['visibility'] !== "false";
$visibility = !$allow_cid && !$deny_cid && !$allow_gid && !$deny_gid;
// do several checks on input parameters
// we do not allow calls without album string
@ -4457,19 +4367,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");
}
@ -4828,7 +4732,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);
@ -4861,7 +4765,6 @@ function post_photo_item($hash, $allow_cid, $deny_cid, $allow_gid, $deny_gid, $f
$arr['guid'] = System::createUUID();
$arr['uid'] = intval(api_user());
$arr['uri'] = $uri;
$arr['parent-uri'] = $uri;
$arr['type'] = 'photo';
$arr['wall'] = 1;
$arr['resource-id'] = $hash;
@ -4975,8 +4878,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.');
}
@ -4985,7 +4888,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);
@ -5005,10 +4908,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;
@ -5102,8 +5005,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 [];
@ -5155,7 +5057,7 @@ function api_share_as_retweet(&$item)
$reshared_item["share-pre-body"] = $reshared['comment'];
$reshared_item["body"] = $reshared['shared'];
$reshared_item["author-id"] = Contact::getIdForURL($reshared['profile'], 0, true);
$reshared_item["author-id"] = Contact::getIdForURL($reshared['profile'], 0, false);
$reshared_item["author-name"] = $reshared['author'];
$reshared_item["author-link"] = $reshared['profile'];
$reshared_item["author-avatar"] = $reshared['avatar'];
@ -5199,7 +5101,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']);
@ -5374,7 +5276,7 @@ function api_friendica_group_show($type)
// loop through all groups and retrieve all members for adding data in the user array
$grps = [];
foreach ($r as $rr) {
$members = Contact::getByGroupId($rr['id']);
$members = Contact\Group::getById($rr['id']);
$users = [];
if ($type == "xml") {
@ -5699,7 +5601,7 @@ function api_friendica_group_update($type)
}
// remove members
$members = Contact::getByGroupId($gid);
$members = Contact\Group::getById($gid);
foreach ($members as $member) {
$cid = $member['id'];
foreach ($users as $user) {
@ -5813,7 +5715,7 @@ function api_friendica_activity($type)
$id = $_REQUEST['id'] ?? 0;
$res = Item::performActivity($id, $verb);
$res = Item::performActivity($id, $verb, api_user());
if ($res) {
if ($type == "xml") {

View File

@ -28,18 +28,19 @@ use Friendica\Core\Logger;
use Friendica\Core\Protocol;
use Friendica\Core\Renderer;
use Friendica\Core\Session;
use Friendica\Core\Theme;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Contact;
use Friendica\Model\Item;
use Friendica\Model\Profile;
use Friendica\Model\Tag;
use Friendica\Model\Verb;
use Friendica\Object\Post;
use Friendica\Object\Thread;
use Friendica\Protocol\Activity;
use Friendica\Util\Crypto;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Proxy as ProxyUtils;
use Friendica\Util\Strings;
use Friendica\Util\Temporal;
use Friendica\Util\XML;
@ -255,7 +256,7 @@ function localize_item(&$item)
// add zrl's to public images
$photo_pattern = "/\[url=(.*?)\/photos\/(.*?)\/image\/(.*?)\]\[img(.*?)\]h(.*?)\[\/img\]\[\/url\]/is";
if (preg_match($photo_pattern, $item['body'])) {
$photo_replace = '[url=' . Profile::zrl('$1' . '/photos/' . '$2' . '/image/' . '$3' ,true) . '][img' . '$4' . ']h' . '$5' . '[/img][/url]';
$photo_replace = '[url=' . Profile::zrl('$1' . '/photos/' . '$2' . '/image/' . '$3' , true) . '][img' . '$4' . ']h' . '$5' . '[/img][/url]';
$item['body'] = BBCode::pregReplaceInTag($photo_pattern, $photo_replace, 'url', $item['body']);
}
@ -315,7 +316,7 @@ function conv_get_blocklist()
return [];
}
$str_blocked = DI::pConfig()->get(local_user(), 'system', 'blocked');
$str_blocked = str_replace(["\n", "\r"], ",", DI::pConfig()->get(local_user(), 'system', 'blocked'));
if (empty($str_blocked)) {
return [];
}
@ -323,8 +324,7 @@ function conv_get_blocklist()
$blocklist = [];
foreach (explode(',', $str_blocked) as $entry) {
// The 4th parameter guarantees that there always will be a public contact entry
$cid = Contact::getIdForURL(trim($entry), 0, true, ['url' => trim($entry)]);
$cid = Contact::getIdForURL(trim($entry), 0, false);
if (!empty($cid)) {
$blocklist[] = $cid;
}
@ -354,6 +354,13 @@ function conv_get_blocklist()
*/
function conversation(App $a, array $items, $mode, $update, $preview = false, $order = 'commented', $uid = 0)
{
$page = DI::page();
$page->registerFooterScript(Theme::getPathForFile('asset/typeahead.js/dist/typeahead.bundle.js'));
$page->registerFooterScript(Theme::getPathForFile('js/friendica-tagsinput/friendica-tagsinput.js'));
$page->registerStylesheet(Theme::getPathForFile('js/friendica-tagsinput/friendica-tagsinput.css'));
$page->registerStylesheet(Theme::getPathForFile('js/friendica-tagsinput/friendica-tagsinput-typeahead.css'));
$ssl_state = (local_user() ? true : false);
$profile_owner = 0;
@ -376,17 +383,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";
}
@ -435,15 +442,17 @@ function conversation(App $a, array $items, $mode, $update, $preview = false, $o
if (!$update) {
$live_update_div = '<div id="live-community"></div>' . "\r\n"
. "<script> var profile_uid = -1; var netargs = '" . substr(DI::args()->getCommand(), 10)
."/?f='; </script>\r\n";
. '?f='
. (!empty($_GET['no_sharer']) ? '&no_sharer=' . rawurlencode($_GET['no_sharer']) : '')
. "'; </script>\r\n";
}
} elseif ($mode === 'contacts') {
$items = conversation_add_children($items, false, $order, $uid);
$profile_owner = 0;
if (!$update) {
$live_update_div = '<div id="live-contacts"></div>' . "\r\n"
. "<script> var profile_uid = -1; var netargs = '" . substr(DI::args()->getCommand(), 9)
$live_update_div = '<div id="live-contact"></div>' . "\r\n"
. "<script> var profile_uid = -1; var netargs = '" . substr(DI::args()->getCommand(), 8)
."/?f='; </script>\r\n";
}
} elseif ($mode === 'search') {
@ -457,7 +466,7 @@ function conversation(App $a, array $items, $mode, $update, $preview = false, $o
}
$cb = ['items' => $items, 'mode' => $mode, 'update' => $update, 'preview' => $preview];
Hook::callAll('conversation_start',$cb);
Hook::callAll('conversation_start', $cb);
$items = $cb['items'];
@ -491,7 +500,7 @@ function conversation(App $a, array $items, $mode, $update, $preview = false, $o
$writable = false;
}
if (in_array($mode, ['network-new', 'search', 'contact-posts'])) {
if (in_array($mode, ['filed', 'search', 'contact-posts'])) {
/*
* "New Item View" on network page or search page results
@ -512,10 +521,6 @@ function conversation(App $a, array $items, $mode, $update, $preview = false, $o
$threadsid++;
$owner_url = '';
$owner_name = '';
$sparkle = '';
// prevent private email from leaking.
if ($item['network'] === Protocol::MAIL && local_user() != $item['uid']) {
continue;
@ -532,17 +537,17 @@ function conversation(App $a, array $items, $mode, $update, $preview = false, $o
'network' => $item['author-network'], 'url' => $item['author-link']];
$profile_link = Contact::magicLinkByContact($author);
$sparkle = '';
if (strpos($profile_link, 'redir/') === 0) {
$sparkle = ' sparkle';
}
$locate = ['location' => $item['location'], 'coord' => $item['coord'], 'html' => ''];
Hook::callAll('render_location',$locate);
$location = ((strlen($locate['html'])) ? $locate['html'] : render_location_dummy($locate));
Hook::callAll('render_location', $locate);
$location_html = $locate['html'] ?: Strings::escapeHtml($locate['location'] ?: $locate['coord'] ?: '');
localize_item($item);
if ($mode === 'network-new') {
if ($mode === 'filed') {
$dropping = true;
} else {
$dropping = false;
@ -555,21 +560,18 @@ function conversation(App $a, array $items, $mode, $update, $preview = false, $o
'delete' => DI::l10n()->t('Delete'),
];
$star = false;
$isstarred = "unstarred";
$lock = false;
$likebuttons = [
'like' => null,
'dislike' => null,
'share' => null,
'like' => null,
'dislike' => null,
'share' => null,
'announce' => null,
];
if (DI::pConfig()->get(local_user(), 'system', 'hide_dislike')) {
unset($likebuttons['dislike']);
}
$body = Item::prepareBody($item, true, $preview);
$body_html = Item::prepareBody($item, true, $preview);
list($categories, $folders) = DI::contentItem()->determineCategoriesTerms($item);
@ -583,18 +585,22 @@ function conversation(App $a, array $items, $mode, $update, $preview = false, $o
'template' => $tpl,
'id' => ($preview ? 'P0' : $item['id']),
'guid' => ($preview ? 'Q0' : $item['guid']),
'commented' => $item['commented'],
'received' => $item['received'],
'created_date' => $item['created'],
'uriid' => $item['uri-id'],
'network' => $item['network'],
'network_name' => ContactSelector::networkToName($item['author-network'], $item['author-link'], $item['network']),
'network_icon' => ContactSelector::networkToIcon($item['network'], $item['author-link']),
'linktitle' => DI::l10n()->t('View %s\'s profile @ %s', $profile_name, $item['author-link']),
'profile_url' => $profile_link,
'item_photo_menu' => item_photo_menu($item),
'item_photo_menu_html' => item_photo_menu($item),
'name' => $profile_name,
'sparkle' => $sparkle,
'lock' => $lock,
'thumb' => DI::baseUrl()->remove(ProxyUtils::proxifyUrl($item['author-avatar'], false, ProxyUtils::SIZE_THUMB)),
'lock' => false,
'thumb' => DI::baseUrl()->remove($item['author-avatar']),
'title' => $title,
'body' => $body,
'body_html' => $body_html,
'tags' => $tags['tags'],
'hashtags' => $tags['hashtags'],
'mentions' => $tags['mentions'],
@ -605,23 +611,23 @@ function conversation(App $a, array $items, $mode, $update, $preview = false, $o
'has_folders' => ((count($folders)) ? 'true' : ''),
'categories' => $categories,
'folders' => $folders,
'text' => strip_tags($body),
'text' => strip_tags($body_html),
'localtime' => DateTimeFormat::local($item['created'], 'r'),
'ago' => (($item['app']) ? DI::l10n()->t('%s from %s', Temporal::getRelativeDate($item['created']),$item['app']) : Temporal::getRelativeDate($item['created'])),
'location' => $location,
'ago' => (($item['app']) ? DI::l10n()->t('%s from %s', Temporal::getRelativeDate($item['created']), $item['app']) : Temporal::getRelativeDate($item['created'])),
'location_html' => $location_html,
'indent' => '',
'owner_name' => $owner_name,
'owner_url' => $owner_url,
'owner_photo' => DI::baseUrl()->remove(ProxyUtils::proxifyUrl($item['owner-avatar'], false, ProxyUtils::SIZE_THUMB)),
'owner_name' => '',
'owner_url' => '',
'owner_photo' => DI::baseUrl()->remove($item['owner-avatar']),
'plink' => Item::getPlink($item),
'edpost' => false,
'isstarred' => $isstarred,
'star' => $star,
'isstarred' => 'unstarred',
'star' => false,
'drop' => $drop,
'vote' => $likebuttons,
'like' => '',
'dislike' => '',
'comment' => '',
'like_html' => '',
'dislike_html' => '',
'comment_html' => '',
'conv' => (($preview) ? '' : ['href'=> 'display/'.$item['guid'], 'title'=> DI::l10n()->t('View in context')]),
'previewing' => $previewing,
'wait' => DI::l10n()->t('Please wait'),
@ -670,7 +676,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);
}
@ -690,6 +696,7 @@ function conversation(App $a, array $items, $mode, $update, $preview = false, $o
'$live_update' => $live_update_div,
'$remove' => DI::l10n()->t('remove'),
'$mode' => $mode,
'$update' => $update,
'$user' => $a->user,
'$threads' => $threads,
'$dropping' => ($page_dropping ? DI::l10n()->t('Delete Selected Items') : False),
@ -703,44 +710,88 @@ function conversation(App $a, array $items, $mode, $update, $preview = false, $o
*
* @param mixed $thread_items Database statement with thread posts
* @param boolean $pinned Is the item pinned?
* @param array $activity Contact data of the resharer
*
* @return array items with parents and comments
*/
function conversation_fetch_comments($thread_items, $pinned) {
function conversation_fetch_comments($thread_items, bool $pinned, array $activity) {
$comments = [];
$parentlines = [];
$lineno = 0;
$actor = [];
$received = '';
while ($row = Item::fetch($thread_items)) {
if (($row['verb'] == Activity::ANNOUNCE) && !empty($row['contact-uid']) && ($row['received'] > $received) && ($row['thr-parent'] == $row['parent-uri'])) {
$actor = ['link' => $row['author-link'], 'avatar' => $row['author-avatar'], 'name' => $row['author-name']];
$received = $row['received'];
if (!empty($activity)) {
if (($row['gravity'] == GRAVITY_PARENT)) {
$row['post-type'] = Item::PT_ANNOUNCEMENT;
$row = array_merge($row, $activity);
$contact = Contact::getById($activity['causer-id'], ['url', 'name', 'thumb']);
$row['causer-link'] = $contact['url'];
$row['causer-avatar'] = $contact['thumb'];
$row['causer-name'] = $contact['name'];
} elseif (($row['gravity'] == GRAVITY_ACTIVITY) && ($row['verb'] == Activity::ANNOUNCE) &&
($row['author-id'] == $activity['causer-id'])) {
continue;
}
}
if ((($row['gravity'] == GRAVITY_PARENT) && !$row['origin'] && !in_array($row['network'], [Protocol::DIASPORA])) &&
(empty($row['contact-uid']) || !in_array($row['network'], Protocol::NATIVE_SUPPORT))) {
$parentlines[] = $lineno;
}
$name = $row['causer-contact-type'] == Contact::TYPE_RELAY ? $row['causer-link'] : $row['causer-name'];
switch ($row['post-type']) {
case Item::PT_TO:
$row['direction'] = ['direction' => 7, 'title' => DI::l10n()->t('You had been addressed (%s).', 'to')];
break;
case Item::PT_CC:
$row['direction'] = ['direction' => 7, 'title' => DI::l10n()->t('You had been addressed (%s).', 'cc')];
break;
case Item::PT_BTO:
$row['direction'] = ['direction' => 7, 'title' => DI::l10n()->t('You had been addressed (%s).', 'bto')];
break;
case Item::PT_BCC:
$row['direction'] = ['direction' => 7, 'title' => DI::l10n()->t('You had been addressed (%s).', 'bcc')];
break;
case Item::PT_FOLLOWER:
$row['direction'] = ['direction' => 6, 'title' => DI::l10n()->t('You are following %s.', $row['author-name'])];
break;
case Item::PT_TAG:
$row['direction'] = ['direction' => 4, 'title' => DI::l10n()->t('Tagged')];
break;
case Item::PT_ANNOUNCEMENT:
if (!empty($row['causer-id']) && DI::pConfig()->get(local_user(), 'system', 'display_resharer')) {
$row['owner-id'] = $row['causer-id'];
$row['owner-link'] = $row['causer-link'];
$row['owner-avatar'] = $row['causer-avatar'];
$row['owner-name'] = $row['causer-name'];
}
if (($row['gravity'] == GRAVITY_PARENT) && !empty($row['causer-id'])) {
$row['reshared'] = DI::l10n()->t('%s reshared this.', '<a href="'. htmlentities(Contact::magicLinkById($row['causer-id'])) .'">' . htmlentities($name) . '</a>');
}
$row['direction'] = ['direction' => 3, 'title' => (empty($row['causer-id']) ? DI::l10n()->t('Reshared') : DI::l10n()->t('Reshared by %s', $name))];
break;
case Item::PT_COMMENT:
$row['direction'] = ['direction' => 5, 'title' => DI::l10n()->t('%s is participating in this thread.', $row['author-name'])];
break;
case Item::PT_STORED:
$row['direction'] = ['direction' => 8, 'title' => DI::l10n()->t('Stored')];
break;
case Item::PT_GLOBAL:
$row['direction'] = ['direction' => 9, 'title' => DI::l10n()->t('Global')];
break;
case Item::PT_RELAY:
$row['direction'] = ['direction' => 10, 'title' => (empty($row['causer-id']) ? DI::l10n()->t('Relayed') : DI::l10n()->t('Relayed by %s.', $name))];
break;
case Item::PT_FETCHED:
$row['direction'] = ['direction' => 2, 'title' => (empty($row['causer-id']) ? DI::l10n()->t('Fetched') : DI::l10n()->t('Fetched because of %s', $name))];
break;
}
if ($row['gravity'] == GRAVITY_PARENT) {
$row['pinned'] = $pinned;
}
$comments[] = $row;
$lineno++;
}
DBA::close($thread_items);
if (!empty($actor)) {
foreach ($parentlines as $line) {
$comments[$line]['owner-link'] = $actor['link'];
$comments[$line]['owner-avatar'] = $actor['avatar'];
$comments[$line]['owner-name'] = $actor['name'];
}
}
return $comments;
}
@ -759,9 +810,13 @@ 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]];
$params = ['order' => ['gravity', 'uid', 'commented' => true]];
if ($max_comments > 0) {
$params['limit'] = $max_comments;
@ -770,19 +825,23 @@ 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);
if (!empty($parent['thr-parent-id']) && !empty($parent['gravity']) && ($parent['gravity'] == GRAVITY_ACTIVITY)) {
$condition = ["`item`.`parent-uri-id` = ? AND `item`.`uid` IN (0, ?) AND (`vid` != ? OR `vid` IS NULL)",
$parent['thr-parent-id'], $uid, Verb::getID(Activity::FOLLOW)];
if (!empty($parent['author-id'])) {
$activity = ['causer-id' => $parent['author-id']];
foreach (['commented', 'received', 'created'] as $orderfields) {
if (!empty($parent[$orderfields])) {
$activity[$orderfields] = $parent[$orderfields];
}
}
}
} else {
$condition = ["`item`.`parent-uri` = ? AND `item`.`uid` IN (0, ?) AND (`vid` != ? OR `vid` IS NULL)",
$parent['uri'], $uid, Verb::getID(Activity::FOLLOW)];
$activity = [];
}
$items = conversation_fetch_items($parent, $items, $condition, $block_authors, $params, $activity);
}
foreach ($items as $index => $item) {
@ -796,6 +855,32 @@ function conversation_add_children(array $parents, $block_authors, $order, $uid)
return $items;
}
/**
* Fetch conversation items
*
* @param array $parent Parent Item array
* @param array $items Item array
* @param array $condition SQL condition
* @param boolean $block_authors Don't show posts from contacts that are hidden (used on the community page)
* @param array $params SQL parameters
* @param array $activity Contact data of the resharer
* @return array
*/
function conversation_fetch_items(array $parent, array $items, array $condition, bool $block_authors, array $params, array $activity) {
if ($block_authors) {
$condition[0] .= " AND NOT `author`.`hidden`";
}
$thread_items = Item::selectForUser(local_user(), array_merge(Item::DISPLAY_FIELDLIST, ['contact-uid', 'gravity', 'post-type']), $condition, $params);
$comments = conversation_fetch_comments($thread_items, $parent['pinned'] ?? false, $activity);
if (count($comments) != 0) {
$items = array_merge($items, $comments);
}
return $items;
}
function item_photo_menu($item) {
$sub_link = '';
$poke_link = '';
@ -807,7 +892,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;';
}
@ -817,7 +902,7 @@ function item_photo_menu($item) {
$sparkle = (strpos($profile_link, 'redir/') === 0);
$cid = 0;
$pcid = Contact::getIdForURL($item['author-link'], 0, true);
$pcid = Contact::getIdForURL($item['author-link'], 0, false);
$network = '';
$rel = 0;
$condition = ['uid' => local_user(), 'nurl' => Strings::normaliseLink($item['author-link'])];
@ -864,13 +949,17 @@ function item_photo_menu($item) {
DI::l10n()->t('Ignore') => $ignore_link
];
if (!empty($item['language'])) {
$menu[DI::l10n()->t('Languages')] = 'javascript:alert(\'' . Item::getLanguageMessage($item) . '\');';
}
if ($network == Protocol::DFRN) {
$menu[DI::l10n()->t("Poke")] = $poke_link;
}
if ((($cid == 0) || ($rel == Contact::FOLLOWER)) &&
in_array($item['network'], Protocol::FEDERATED)) {
$menu[DI::l10n()->t('Connect/Follow')] = 'follow?url=' . urlencode($item['author-link']);
$menu[DI::l10n()->t('Connect/Follow')] = 'follow?url=' . urlencode($item['author-link']) . '&auto=1';
}
} else {
$menu = [DI::l10n()->t('View Profile') => $item['author-link']];
@ -899,13 +988,14 @@ function item_photo_menu($item) {
*
* Increments the count of each matching activity and adds a link to the author as needed.
*
* @param array $item
* @param array $activity
* @param array &$conv_responses (already created with builtin activity structure)
* @return void
* @throws ImagickException
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
function builtin_activity_puller($item, &$conv_responses) {
function builtin_activity_puller(array $activity, array &$conv_responses)
{
foreach ($conv_responses as $mode => $v) {
$sparkle = '';
@ -932,41 +1022,45 @@ function builtin_activity_puller($item, &$conv_responses) {
return;
}
if (!empty($item['verb']) && DI::activity()->match($item['verb'], $verb) && ($item['id'] != $item['parent'])) {
$author = ['uid' => 0, 'id' => $item['author-id'],
'network' => $item['author-network'], 'url' => $item['author-link']];
if (!empty($activity['verb']) && DI::activity()->match($activity['verb'], $verb) && ($activity['gravity'] != GRAVITY_PARENT)) {
$author = [
'uid' => 0,
'id' => $activity['author-id'],
'network' => $activity['author-network'],
'url' => $activity['author-link']
];
$url = Contact::magicLinkByContact($author);
if (strpos($url, 'redir/') === 0) {
$sparkle = ' class="sparkle" ';
}
$url = '<a href="'. $url . '"'. $sparkle .'>' . htmlentities($item['author-name']) . '</a>';
$link = '<a href="' . $url . '"' . $sparkle . '>' . htmlentities($activity['author-name']) . '</a>';
if (empty($item['thr-parent'])) {
$item['thr-parent'] = $item['parent-uri'];
if (empty($activity['thr-parent'])) {
$activity['thr-parent'] = $activity['parent-uri'];
}
if (!(isset($conv_responses[$mode][$item['thr-parent'] . '-l'])
&& is_array($conv_responses[$mode][$item['thr-parent'] . '-l']))) {
$conv_responses[$mode][$item['thr-parent'] . '-l'] = [];
}
// only list each unique author once
if (in_array($url,$conv_responses[$mode][$item['thr-parent'] . '-l'])) {
// Skip when the causer of the parent is the same than the author of the announce
if (($verb == Activity::ANNOUNCE) && Item::exists(['uri' => $activity['thr-parent'],
'uid' => $activity['uid'], 'causer-id' => $activity['author-id'], 'gravity' => GRAVITY_PARENT])) {
continue;
}
if (!isset($conv_responses[$mode][$item['thr-parent']])) {
$conv_responses[$mode][$item['thr-parent']] = 1;
} else {
$conv_responses[$mode][$item['thr-parent']] ++;
if (!isset($conv_responses[$mode][$activity['thr-parent']])) {
$conv_responses[$mode][$activity['thr-parent']] = [
'links' => [],
'self' => 0,
];
} elseif (in_array($link, $conv_responses[$mode][$activity['thr-parent']]['links'])) {
// only list each unique author once
continue;
}
if (public_contact() == $item['author-id']) {
$conv_responses[$mode][$item['thr-parent'] . '-self'] = 1;
if (public_contact() == $activity['author-id']) {
$conv_responses[$mode][$activity['thr-parent']]['self'] = 1;
}
$conv_responses[$mode][$item['thr-parent'] . '-l'][] = $url;
$conv_responses[$mode][$activity['thr-parent']]['links'][] = $link;
// there can only be one activity verb per item so if we found anything, we can stop looking
return;
@ -975,26 +1069,26 @@ function builtin_activity_puller($item, &$conv_responses) {
}
/**
* Format the vote text for a profile item
* Format the activity text for an item/photo/video
*
* @param int $cnt = number of people who vote the item
* @param array $arr = array of pre-linked names of likers/dislikers
* @param string $type = one of 'like, 'dislike', 'attendyes', 'attendno', 'attendmaybe'
* @param int $id = item id
* @param array $links = array of pre-linked names of actors
* @param string $verb = one of 'like, 'dislike', 'attendyes', 'attendno', 'attendmaybe'
* @param int $id = item id
* @return string formatted text
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
function format_like($cnt, array $arr, $type, $id) {
function format_activity(array $links, $verb, $id) {
$o = '';
$expanded = '';
$phrase = '';
if ($cnt == 1) {
$likers = $arr[0];
$total = count($links);
if ($total == 1) {
$likers = $links[0];
// Phrase if there is only one liker. In other cases it will be uses for the expanded
// list which show all likers
switch ($type) {
switch ($verb) {
case 'like' :
$phrase = DI::l10n()->t('%s likes this.', $likers);
break;
@ -1014,56 +1108,51 @@ function format_like($cnt, array $arr, $type, $id) {
$phrase = DI::l10n()->t('%s reshared this.', $likers);
break;
}
}
if ($cnt > 1) {
$total = count($arr);
} elseif ($total > 1) {
if ($total < MAX_LIKERS) {
$last = DI::l10n()->t('and') . ' ' . $arr[count($arr)-1];
$arr2 = array_slice($arr, 0, -1);
$likers = implode(', ', $arr2) . ' ' . $last;
$likers = implode(', ', array_slice($links, 0, -1));
$likers .= ' ' . DI::l10n()->t('and') . ' ' . $links[count($links)-1];
} else {
$arr = array_slice($arr, 0, MAX_LIKERS - 1);
$likers = implode(', ', $arr);
$likers .= DI::l10n()->t('and %d other people', $total - MAX_LIKERS);
$likers = implode(', ', array_slice($links, 0, MAX_LIKERS - 1));
$likers .= ' ' . DI::l10n()->t('and %d other people', $total - MAX_LIKERS);
}
$spanatts = "class=\"fakelink\" onclick=\"openClose('{$type}list-$id');\"";
$spanatts = "class=\"fakelink\" onclick=\"openClose('{$verb}list-$id');\"";
$explikers = '';
switch ($type) {
switch ($verb) {
case 'like':
$phrase = DI::l10n()->t('<span %1$s>%2$d people</span> like this', $spanatts, $cnt);
$phrase = DI::l10n()->t('<span %1$s>%2$d people</span> like this', $spanatts, $total);
$explikers = DI::l10n()->t('%s like this.', $likers);
break;
case 'dislike':
$phrase = DI::l10n()->t('<span %1$s>%2$d people</span> don\'t like this', $spanatts, $cnt);
$phrase = DI::l10n()->t('<span %1$s>%2$d people</span> don\'t like this', $spanatts, $total);
$explikers = DI::l10n()->t('%s don\'t like this.', $likers);
break;
case 'attendyes':
$phrase = DI::l10n()->t('<span %1$s>%2$d people</span> attend', $spanatts, $cnt);
$phrase = DI::l10n()->t('<span %1$s>%2$d people</span> attend', $spanatts, $total);
$explikers = DI::l10n()->t('%s attend.', $likers);
break;
case 'attendno':
$phrase = DI::l10n()->t('<span %1$s>%2$d people</span> don\'t attend', $spanatts, $cnt);
$phrase = DI::l10n()->t('<span %1$s>%2$d people</span> don\'t attend', $spanatts, $total);
$explikers = DI::l10n()->t('%s don\'t attend.', $likers);
break;
case 'attendmaybe':
$phrase = DI::l10n()->t('<span %1$s>%2$d people</span> attend maybe', $spanatts, $cnt);
$phrase = DI::l10n()->t('<span %1$s>%2$d people</span> attend maybe', $spanatts, $total);
$explikers = DI::l10n()->t('%s attend maybe.', $likers);
break;
case 'announce':
$phrase = DI::l10n()->t('<span %1$s>%2$d people</span> reshared this', $spanatts, $cnt);
$phrase = DI::l10n()->t('<span %1$s>%2$d people</span> reshared this', $spanatts, $total);
$explikers = DI::l10n()->t('%s reshared this.', $likers);
break;
}
$expanded .= "\t" . '<p class="wall-item-' . $type . '-expanded" id="' . $type . 'list-' . $id . '" style="display: none;" >' . $explikers . EOL . '</p>';
$expanded .= "\t" . '<p class="wall-item-' . $verb . '-expanded" id="' . $verb . 'list-' . $id . '" style="display: none;" >' . $explikers . EOL . '</p>';
}
$o .= Renderer::replaceMacros(Renderer::getMarkupTemplate('voting_fakelink.tpl'), [
'$phrase' => $phrase,
'$type' => $type,
'$type' => $verb,
'$id' => $id
]);
$o .= $expanded;
@ -1088,40 +1177,18 @@ function status_editor(App $a, $x, $notes_cid = 0, $popup = false)
'$term' => DI::l10n()->t('Tag term:'),
'$fileas' => DI::l10n()->t('Save to Folder:'),
'$whereareu' => DI::l10n()->t('Where are you right now?'),
'$delitems' => DI::l10n()->t("Delete item\x28s\x29?")
'$delitems' => DI::l10n()->t("Delete item\x28s\x29?"),
'$is_mobile' => DI::mode()->isMobile(),
]);
$jotplugins = '';
Hook::callAll('jot_tool', $jotplugins);
// Private/public post links for the non-JS ACL form
$private_post = 1;
if (!empty($_REQUEST['public'])) {
$private_post = 0;
}
$query_str = DI::args()->getQueryString();
if (strpos($query_str, 'public=1') !== false) {
$query_str = str_replace(['?public=1', '&public=1'], ['', ''], $query_str);
}
/*
* I think $a->query_string may never have ? in it, but I could be wrong
* It looks like it's from the index.php?q=[etc] rewrite that the web
* server does, which converts any ? to &, e.g. suggest&ignore=61 for suggest?ignore=61
*/
if (strpos($query_str, '?') === false) {
$public_post_link = '?public=1';
} else {
$public_post_link = '&public=1';
}
// $tpl = Renderer::replaceMacros($tpl,array('$jotplugins' => $jotplugins));
$tpl = Renderer::getMarkupTemplate("jot.tpl");
$o .= Renderer::replaceMacros($tpl,[
$o .= Renderer::replaceMacros($tpl, [
'$new_post' => DI::l10n()->t('New Post'),
'$return_path' => $query_str,
'$return_path' => DI::args()->getQueryString(),
'$action' => 'item',
'$share' => ($x['button'] ?? '') ?: DI::l10n()->t('Share'),
'$loading' => DI::l10n()->t('Loading...'),
@ -1147,7 +1214,7 @@ function status_editor(App $a, $x, $notes_cid = 0, $popup = false)
'$placeholdercategory' => Feature::isEnabled(local_user(), 'categories') ? DI::l10n()->t("Categories \x28comma-separated list\x29") : '',
'$wait' => DI::l10n()->t('Please wait'),
'$permset' => DI::l10n()->t('Permission settings'),
'$shortpermset' => DI::l10n()->t('permissions'),
'$shortpermset' => DI::l10n()->t('Permissions'),
'$wall' => $notes_cid ? 0 : 1,
'$posttype' => $notes_cid ? Item::PT_PERSONAL_NOTE : Item::PT_ARTICLE,
'$content' => $x['content'] ?? '',
@ -1169,11 +1236,6 @@ function status_editor(App $a, $x, $notes_cid = 0, $popup = false)
// ACL permissions box
'$acl' => $x['acl'],
'$group_perms' => DI::l10n()->t('Post to Groups'),
'$contact_perms' => DI::l10n()->t('Post to Contacts'),
'$private' => DI::l10n()->t('Private post'),
'$is_private' => $private_post,
'$public_link' => $public_post_link,
//jot nav tab (used in some themes)
'$message' => DI::l10n()->t('Message'),
@ -1202,7 +1264,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'];
@ -1350,7 +1412,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;
}
}
@ -1447,13 +1509,3 @@ function sort_thr_commented(array $a, array $b)
{
return strcmp($b['commented'], $a['commented']);
}
function render_location_dummy(array $item) {
if (!empty($item['location']) && !empty($item['location'])) {
return $item['location'];
}
if (!empty($item['coord']) && !empty($item['coord'])) {
return $item['coord'];
}
}

View File

@ -26,6 +26,7 @@ use Friendica\Core\Renderer;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Contact;
use Friendica\Model\Item;
use Friendica\Model\ItemContent;
use Friendica\Model\Notify;
@ -37,10 +38,10 @@ use Friendica\Protocol\Activity;
* Creates a notification entry and possibly sends a mail
*
* @param array $params Array with the elements:
* uid, item, parent, type, otype, verb, event,
* link, subject, body, to_name, to_email, source_name,
* source_link, activity, preamble, notify_flags,
* language, show_in_notification_page
* type, event, otype, activity, verb, uid, cid, origin_cid, item, link,
* source_name, source_mail, source_nick, source_link, source_photo,
* show_in_notification_page
*
* @return bool
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
@ -55,7 +56,7 @@ function notification($params)
}
// Ensure that the important fields are set at any time
$fields = ['notify-flags', 'language', 'username', 'email'];
$fields = ['nickname', 'page-flags', 'notify-flags', 'language', 'username', 'email'];
$user = DBA::selectFirst('user', $fields, ['uid' => $params['uid']]);
if (!DBA::isResult($user)) {
@ -63,14 +64,39 @@ function notification($params)
return false;
}
$params['notify_flags'] = ($params['notify_flags'] ?? '') ?: $user['notify-flags'];
$params['language'] = ($params['language'] ?? '') ?: $user['language'];
$params['to_name'] = ($params['to_name'] ?? '') ?: $user['username'];
$params['to_email'] = ($params['to_email'] ?? '') ?: $user['email'];
// There is no need to create notifications for forum accounts
if (in_array($user['page-flags'], [User::PAGE_FLAGS_COMMUNITY, User::PAGE_FLAGS_PRVGROUP])) {
return false;
}
$nickname = $user['nickname'];
$params['notify_flags'] = $user['notify-flags'];
$params['language'] = $user['language'];
$params['to_name'] = $user['username'];
$params['to_email'] = $user['email'];
// from here on everything is in the recipients language
$l10n = DI::l10n()->withLang($params['language']);
if (!empty($params['cid'])) {
$contact = Contact::getById($params['cid'], ['url', 'name', 'photo']);
if (DBA::isResult($contact)) {
$params['source_link'] = $contact['url'];
$params['source_name'] = $contact['name'];
$params['source_photo'] = $contact['photo'];
}
}
if (!empty($params['origin_cid'])) {
$contact = Contact::getById($params['origin_cid'], ['url', 'name', 'photo']);
if (DBA::isResult($contact)) {
$params['origin_link'] = $contact['url'];
$params['origin_name'] = $contact['name'];
$params['origin_photo'] = $contact['photo'];
}
}
$siteurl = DI::baseUrl()->get(true);
$sitename = DI::config()->get('config', 'sitename');
@ -79,51 +105,23 @@ function notification($params)
$hostname = substr($hostname, 0, strpos($hostname, ':'));
}
$user = User::getById($params['uid'], ['nickname', 'page-flags']);
// There is no need to create notifications for forum accounts
if (!DBA::isResult($user) || in_array($user["page-flags"], [User::PAGE_FLAGS_COMMUNITY, User::PAGE_FLAGS_PRVGROUP])) {
return false;
}
$nickname = $user["nickname"];
// Creates a new email builder for the notification email
$emailBuilder = DI::emailer()->newNotifyMail();
// with $params['show_in_notification_page'] == false, the notification isn't inserted into
// the database, and an email is sent if applicable.
// default, if not specified: true
$show_in_notification_page = isset($params['show_in_notification_page']) ? $params['show_in_notification_page'] : true;
$additional_mail_header = "X-Friendica-Account: <".$nickname."@".$hostname.">\n";
$emailBuilder->setHeader('X-Friendica-Account', '<' . $nickname . '@' . $hostname . '>');
if (array_key_exists('item', $params)) {
$title = $params['item']['title'];
$body = $params['item']['body'];
} else {
$title = $body = '';
}
$title = $params['item']['title'] ?? '';
$body = $params['item']['body'] ?? '';
if (isset($params['item']['id'])) {
$item_id = $params['item']['id'];
} else {
$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;
}
$item_id = $params['item']['id'] ?? 0;
$uri_id = $params['item']['uri-id'] ?? 0;
$parent_id = $params['item']['parent'] ?? 0;
$parent_uri_id = $params['item']['parent-uri-id'] ?? 0;
$epreamble = '';
$preamble = '';
@ -134,8 +132,7 @@ function notification($params)
$itemlink = '';
if ($params['type'] == Notify\Type::MAIL) {
$itemlink = $siteurl.'/message/'.$params['item']['id'];
$params["link"] = $itemlink;
$itemlink = $params['link'];
$subject = $l10n->t('%s New mail received at %s', $subjectPrefix, $sitename);
@ -143,8 +140,11 @@ function notification($params)
$epreamble = $l10n->t('%1$s sent you %2$s.', '[url='.$params['source_link'].']'.$params['source_name'].'[/url]', '[url=' . $itemlink . ']' . $l10n->t('a private message').'[/url]');
$sitelink = $l10n->t('Please visit %s to view and/or reply to your private messages.');
$tsitelink = sprintf($sitelink, $siteurl.'/message/'.$params['item']['id']);
$hsitelink = sprintf($sitelink, '<a href="'.$siteurl.'/message/'.$params['item']['id'].'">'.$sitename.'</a>');
$tsitelink = sprintf($sitelink, $itemlink);
$hsitelink = sprintf($sitelink, '<a href="' . $itemlink . '">' . $sitename . '</a>');
// Mail notifications aren't using the "notify" table entry
$show_in_notification_page = false;
}
if ($params['type'] == Notify\Type::COMMENT || $params['type'] == Notify\Type::TAG_SELF) {
@ -259,13 +259,23 @@ function notification($params)
}
if ($params['type'] == Notify\Type::SHARE) {
$subject = $l10n->t('%s %s shared a new post', $subjectPrefix, $params['source_name']);
if ($params['origin_link'] == $params['source_link']) {
$subject = $l10n->t('%s %s shared a new post', $subjectPrefix, $params['source_name']);
$preamble = $l10n->t('%1$s shared a new post at %2$s', $params['source_name'], $sitename);
$epreamble = $l10n->t('%1$s [url=%2$s]shared a post[/url].',
'[url='.$params['source_link'].']'.$params['source_name'].'[/url]',
$params['link']
);
$preamble = $l10n->t('%1$s shared a new post at %2$s', $params['source_name'], $sitename);
$epreamble = $l10n->t('%1$s [url=%2$s]shared a post[/url].',
'[url='.$params['source_link'].']'.$params['source_name'].'[/url]',
$params['link']
);
} else {
$subject = $l10n->t('%s %s shared a post from %s', $subjectPrefix, $params['source_name'], $params['origin_name']);
$preamble = $l10n->t('%1$s shared a post from %2$s at %3$s', $params['source_name'], $params['origin_name'], $sitename);
$epreamble = $l10n->t('%1$s [url=%2$s]shared a post[/url] from %3$s.',
'[url='.$params['source_link'].']'.$params['source_name'].'[/url]',
$params['link'], '[url='.$params['origin_link'].']'.$params['origin_name'].'[/url]'
);
}
$sitelink = $l10n->t('Please visit %s to view and/or reply to the conversation.');
$tsitelink = sprintf($sitelink, $siteurl);
@ -463,21 +473,35 @@ function notification($params)
$notify_id = 0;
if ($show_in_notification_page) {
$notification = DI::notify()->insert([
$fields = [
'name' => $params['source_name'] ?? '',
'name_cache' => substr(strip_tags(BBCode::convert($params['source_name'] ?? '')), 0, 255),
'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'] ?? '',
]);
];
if (!empty($item_id)) {
$fields['iid'] = $item_id;
}
if (!empty($uri_id)) {
$fields['uri-id'] = $uri_id;
}
if (!empty($parent_id)) {
$fields['parent'] = $parent_id;
}
if (!empty($parent_uri_id)) {
$fields['parent-uri-id'] = $parent_uri_id;
}
$notification = DI::notify()->insert($fields);
// 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]);
@ -494,7 +518,8 @@ function notification($params)
Logger::log('sending notification email');
if (isset($params['parent']) && (intval($params['parent']) != 0)) {
$id_for_parent = $params['parent'] . "@" . $hostname;
$parent = Item::selectFirst(['guid'], ['id' => $params['parent']]);
$message_id = "<" . $parent['guid'] . "@" . gethostname() . ">";
// Is this the first email notification for this parent item and user?
if (!DBA::exists('notify-threads', ['master-parent-item' => $params['parent'], 'receiver-uid' => $params['uid']])) {
@ -505,13 +530,14 @@ function notification($params)
'receiver-uid' => $params['uid'], 'parent-item' => 0];
DBA::insert('notify-threads', $fields);
$additional_mail_header .= "Message-ID: <${id_for_parent}>\n";
$emailBuilder->setHeader('Message-ID', $message_id);
$log_msg = "include/enotify: No previous notification found for this parent:\n" .
" parent: ${params['parent']}\n" . " uid : ${params['uid']}\n";
Logger::log($log_msg, Logger::DEBUG);
} else {
// If not, just "follow" the thread.
$additional_mail_header .= "References: <${id_for_parent}>\nIn-Reply-To: <${id_for_parent}>\n";
$emailBuilder->setHeader('References', $message_id);
$emailBuilder->setHeader('In-Reply-To', $message_id);
Logger::log("There's already a notification for this parent.", Logger::DEBUG);
}
}
@ -530,14 +556,13 @@ function notification($params)
'title' => $title,
'body' => $body,
'subject' => $subject,
'headers' => $additional_mail_header,
'headers' => $emailBuilder->getHeaders(),
];
Hook::callAll('enotify_mail', $datarray);
$builder = DI::emailer()
->newNotifyMail()
->addHeaders($datarray['headers'])
$emailBuilder
->withHeaders($datarray['headers'])
->withRecipient($params['to_email'])
->forUser([
'uid' => $datarray['uid'],
@ -549,13 +574,13 @@ function notification($params)
// If a photo is present, add it to the email
if (!empty($datarray['source_photo'])) {
$builder->withPhoto(
$emailBuilder->withPhoto(
$datarray['source_photo'],
$datarray['source_link'] ?? $sitelink,
$datarray['source_name'] ?? $sitename);
}
$email = $builder->build();
$email = $emailBuilder->build();
// use the Emailer class to send the message
return DI::emailer()->send($email);
@ -589,10 +614,10 @@ function check_user_notification($itemid) {
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
function check_item_notification($itemid, $uid, $notification_type) {
$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];
$fields = ['id', 'uri-id', 'mention', 'parent', 'parent-uri-id', 'thr-parent-id',
'title', 'body', 'author-link', 'author-name', 'author-avatar', 'author-id',
'gravity', 'guid', 'parent-uri', 'uri', 'contact-id', 'network'];
$condition = ['id' => $itemid, 'deleted' => false];
$item = Item::selectFirstForUser($uid, $fields, $condition);
if (!DBA::isResult($item)) {
return false;
@ -600,14 +625,11 @@ function check_item_notification($itemid, $uid, $notification_type) {
// Generate the notification array
$params = [];
$params['otype'] = Notify\ObjectType::ITEM;
$params['uid'] = $uid;
$params['origin_cid'] = $params['cid'] = $item['author-id'];
$params['item'] = $item;
$params['parent'] = $item['parent'];
$params['link'] = DI::baseUrl() . '/display/' . urlencode($item['guid']);
$params['otype'] = 'item';
$params['source_name'] = $item['author-name'];
$params['source_link'] = $item['author-link'];
$params['source_photo'] = $item['author-avatar'];
// Set the activity flags
$params['activity']['explicit_tagged'] = ($notification_type & UserItem::NOTIF_EXPLICIT_TAGGED);
@ -625,6 +647,20 @@ function check_item_notification($itemid, $uid, $notification_type) {
if ($notification_type & UserItem::NOTIF_SHARED) {
$params['type'] = Notify\Type::SHARE;
$params['verb'] = Activity::POST;
// Special treatment for posts that had been shared via "announce"
if ($item['gravity'] == GRAVITY_ACTIVITY) {
$parent_item = Item::selectFirst($fields, ['uri-id' => $item['thr-parent-id'], 'uid' => [$uid, 0]]);
if (DBA::isResult($parent_item)) {
// Don't notify on own entries
if (User::getIdForURL($parent_item['author-link']) == $uid) {
return false;
}
$params['origin_cid'] = $parent_item['author-id'];
$params['item'] = $parent_item;
}
}
} elseif ($notification_type & UserItem::NOTIF_EXPLICIT_TAGGED) {
$params['type'] = Notify\Type::TAG_SELF;
$params['verb'] = Activity::TAG;

View File

@ -1,448 +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/>.
*
*/
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';
function add_page_info_data(array $data, $no_photos = false)
{
Hook::callAll('page_info_data', $data);
if (empty($data['type'])) {
return '';
}
// It maybe is a rich content, but if it does have everything that a link has,
// then treat it that way
if (($data["type"] == "rich") && is_string($data["title"]) &&
is_string($data["text"]) && !empty($data["images"])) {
$data["type"] = "link";
}
$data["title"] = $data["title"] ?? '';
if ((($data["type"] != "link") && ($data["type"] != "video") && ($data["type"] != "photo")) || ($data["title"] == $data["url"])) {
return "";
}
if ($no_photos && ($data["type"] == "photo")) {
return "";
}
// Escape some bad characters
$data["url"] = str_replace(["[", "]"], ["&#91;", "&#93;"], htmlentities($data["url"], ENT_QUOTES, 'UTF-8', false));
$data["title"] = str_replace(["[", "]"], ["&#91;", "&#93;"], htmlentities($data["title"], ENT_QUOTES, 'UTF-8', false));
$text = "[attachment type='".$data["type"]."'";
if (empty($data["text"])) {
$data["text"] = $data["title"];
}
if (empty($data["text"])) {
$data["text"] = $data["url"];
}
if (!empty($data["url"])) {
$text .= " url='".$data["url"]."'";
}
if (!empty($data["title"])) {
$text .= " title='".$data["title"]."'";
}
// Only embedd a picture link when it seems to be a valid picture ("width" is set)
if (!empty($data["images"]) && !empty($data["images"][0]["width"])) {
$preview = str_replace(["[", "]"], ["&#91;", "&#93;"], htmlentities($data["images"][0]["src"], ENT_QUOTES, 'UTF-8', false));
// if the preview picture is larger than 500 pixels then show it in a larger mode
// But only, if the picture isn't higher than large (To prevent huge posts)
if (!DI::config()->get('system', 'always_show_preview') && ($data["images"][0]["width"] >= 500)
&& ($data["images"][0]["width"] >= $data["images"][0]["height"])) {
$text .= " image='".$preview."'";
} else {
$text .= " preview='".$preview."'";
}
}
$text .= "]".$data["text"]."[/attachment]";
$hashtags = "";
if (isset($data["keywords"]) && count($data["keywords"])) {
$hashtags = "\n";
foreach ($data["keywords"] as $keyword) {
/// @TODO make a positive list of allowed characters
$hashtag = str_replace([' ', '+', '/', '.', '#', '@', "'", '"', '', '`', '(', ')', '„', '“'], '', $keyword);
$hashtags .= "#[url=" . DI::baseUrl() . "/search?tag=" . $hashtag . "]" . $hashtag . "[/url] ";
}
}
return "\n".$text.$hashtags;
}
function query_page_info($url, $photo = "", $keywords = false, $keyword_blacklist = "")
{
$data = ParseUrl::getSiteinfoCached($url, true);
if ($photo != "") {
$data["images"][0]["src"] = $photo;
}
Logger::log('fetch page info for ' . $url . ' ' . print_r($data, true), Logger::DEBUG);
if (!$keywords && isset($data["keywords"])) {
unset($data["keywords"]);
}
if (($keyword_blacklist != "") && isset($data["keywords"])) {
$list = explode(", ", $keyword_blacklist);
foreach ($list as $keyword) {
$keyword = trim($keyword);
$index = array_search($keyword, $data["keywords"]);
if ($index !== false) {
unset($data["keywords"][$index]);
}
}
}
return $data;
}
function get_page_keywords($url, $photo = "", $keywords = false, $keyword_blacklist = "")
{
$data = query_page_info($url, $photo, $keywords, $keyword_blacklist);
if (empty($data["keywords"]) || !is_array($data["keywords"])) {
return [];
}
$taglist = [];
foreach ($data['keywords'] as $keyword) {
$hashtag = str_replace([" ", "+", "/", ".", "#", "'"],
["", "", "", "", "", ""], $keyword);
$taglist[] = $hashtag;
}
return $taglist;
}
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;
}
/**
*
* 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
*/
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
}
}

View File

@ -21,6 +21,8 @@
use Dice\Dice;
$start_time = microtime(true);
if (!file_exists(__DIR__ . '/vendor/autoload.php')) {
die('Vendor path not found. Please execute "bin/composer.phar --no-dev install" on the command line in the web root.');
}
@ -34,10 +36,13 @@ $dice = $dice->addRule(Friendica\App\Mode::class, ['call' => [['determineRunMode
$a = \Friendica\DI::app();
\Friendica\DI::mode()->setExecutor(\Friendica\App\Mode::INDEX);
$a->runFrontend(
$dice->create(\Friendica\App\Module::class),
$dice->create(\Friendica\App\Router::class),
$dice->create(\Friendica\Core\PConfig\IPConfig::class),
$dice->create(\Friendica\App\Authentication::class),
$dice->create(\Friendica\App\Page::class)
$dice->create(\Friendica\Security\Authentication::class),
$dice->create(\Friendica\App\Page::class),
$start_time
);

View File

@ -1,169 +0,0 @@
<?php
//-----------------------------------------------------------------------------
// ASNValue class by A.Oliinyk
// contact@pumka.net
//-----------------------------------------------------------------------------
class ASNValue
{
const TAG_INTEGER = 0x02;
const TAG_BITSTRING = 0x03;
const TAG_SEQUENCE = 0x30;
public $Tag;
public $Value;
function __construct($Tag=0x00, $Value='')
{
$this->Tag = $Tag;
$this->Value = $Value;
}
function Encode()
{
//Write type
$result = chr($this->Tag);
//Write size
$size = strlen($this->Value);
if ($size < 127) {
//Write size as is
$result .= chr($size);
}
else {
//Prepare length sequence
$sizeBuf = self::IntToBin($size);
//Write length sequence
$firstByte = 0x80 + strlen($sizeBuf);
$result .= chr($firstByte) . $sizeBuf;
}
//Write value
$result .= $this->Value;
return $result;
}
function Decode(&$Buffer)
{
//Read type
$this->Tag = self::ReadByte($Buffer);
//Read first byte
$firstByte = self::ReadByte($Buffer);
if ($firstByte < 127) {
$size = $firstByte;
}
else if ($firstByte > 127) {
$sizeLen = $firstByte - 0x80;
//Read length sequence
$size = self::BinToInt(self::ReadBytes($Buffer, $sizeLen));
}
else {
throw new Exception("Invalid ASN length value");
}
$this->Value = self::ReadBytes($Buffer, $size);
}
protected static function ReadBytes(&$Buffer, $Length)
{
$result = substr($Buffer, 0, $Length);
$Buffer = substr($Buffer, $Length);
return $result;
}
protected static function ReadByte(&$Buffer)
{
return ord(self::ReadBytes($Buffer, 1));
}
protected static function BinToInt($Bin)
{
$len = strlen($Bin);
$result = 0;
for ($i=0; $i<$len; $i++) {
$curByte = self::ReadByte($Bin);
$result += $curByte << (($len-$i-1)*8);
}
return $result;
}
protected static function IntToBin($Int)
{
$result = '';
do {
$curByte = $Int % 256;
$result .= chr($curByte);
$Int = ($Int - $curByte) / 256;
} while ($Int > 0);
$result = strrev($result);
return $result;
}
function SetIntBuffer($Value)
{
if (strlen($Value) > 1) {
$firstByte = ord($Value[0]);
if ($firstByte & 0x80) { //first bit set
$Value = chr(0x00) . $Value;
}
}
$this->Value = $Value;
}
function GetIntBuffer()
{
$result = $this->Value;
if (ord($result[0]) == 0x00) {
$result = substr($result, 1);
}
return $result;
}
function SetInt($Value)
{
$Value = self::IntToBin($Value);
$this->SetIntBuffer($Value);
}
function GetInt()
{
$result = $this->GetIntBuffer();
$result = self::BinToInt($result);
return $result;
}
function SetSequence($Values)
{
$result = '';
foreach ($Values as $item) {
$result .= $item->Encode();
}
$this->Value = $result;
}
function GetSequence()
{
$result = array();
$seq = $this->Value;
while (strlen($seq)) {
$val = new ASNValue();
$val->Decode($seq);
$result[] = $val;
}
return $result;
}
}

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More