Compare commits

...

281 Commits

Author SHA1 Message Date
Michael Vogel 0b312ae9b3
Merge pull request #11580 from tobiasd/20220530-lng
DE, FR and HU translation updates
2022-05-30 16:24:25 +02:00
Tobias Diekershoff d7eb78e65e
DE translation updates 2022-05-30 16:04:07 +02:00
Tobias Diekershoff 6ec812208f
update FR translations THX kalon33 2022-05-30 16:04:00 +02:00
Tobias Diekershoff 406b3f14dd
HU translation updated THX Balázs Úr 2022-05-30 16:03:45 +02:00
Michael Vogel ee40d8ea04
Merge pull request #11579 from MrPetovan/bug/11291-user-theme-display
Assume unsettable system.always_my_theme pconfig value is always true
2022-05-29 23:27:21 +02:00
Hypolite Petovan 7f711e266f Assume unsettable system.always_my_theme pconfig value is always true
- This was a logged in user setting so it assumes false for anonymous users
2022-05-29 16:13:49 -04:00
Hypolite Petovan 9160fde8ca
Merge pull request #11578 from annando/issue-11561
Issue 11561: Don't mark photo links as invalid
2022-05-29 15:54:22 -04:00
Hypolite Petovan 220703575c
Merge pull request #11577 from annando/issue-11557
Issue 11557: Update all local contacts
2022-05-29 15:53:21 -04:00
Hypolite Petovan 1e68c90fb2
Merge pull request #11576 from annando/issue-11488
Issue 11488: Don't search for mentions in shared posts
2022-05-29 15:49:46 -04:00
Michael aabb56c70d Issue 11561: Don't mark photo links as invalid 2022-05-29 19:10:00 +00:00
Michael Vogel 47a3d8e6ce
Merge pull request #11519 from MrPetovan/task/11511-console-domain-move
Add relocate console command and fix the missing DB tables for the relocation
2022-05-29 20:42:32 +02:00
Hypolite Petovan 4c5d9e22a8 Propagate signature change of Database::transaction() 2022-05-29 14:23:57 -04:00
Hypolite Petovan 121d26999c Updated main translation file after moving strings 2022-05-29 13:58:33 -04:00
Michael 540b3f892b Issue 11557: Update all local contacts 2022-05-29 17:48:09 +00:00
Hypolite Petovan 1301a53f20 Remove relocation form from Admin Site settings 2022-05-29 13:15:23 -04:00
Hypolite Petovan f1630ebb05 Add relocate console command 2022-05-29 13:13:59 -04:00
Hypolite Petovan 7d09ce86c4 Move relocation feature in its separate class
- Create Database->replaceInTableFields method
2022-05-29 12:11:18 -04:00
Michael 42d3e5c1ce Issue 11488: Don't search for mentions in shared posts 2022-05-29 16:07:53 +00:00
Hypolite Petovan 697b8a6cb8
Merge pull request #11575 from annando/issue-11469
Issue 11469: Respect desktop notification settings
2022-05-29 08:06:32 -04:00
Michael 1dccc31508 Issue 11469: Repect desktop notification settings 2022-05-29 09:20:06 +00:00
Tobias Diekershoff 92bf5213cc
Merge pull request #11573 from annando/no-local
Don't touch local avatars
2022-05-29 10:10:40 +02:00
Michael 02473d8a0e Don't touch local avatars 2022-05-29 06:50:34 +00:00
Michael Vogel 511a51b9a3
Merge pull request #11571 from MrPetovan/bug/11291-user-theme-display
Use the top-level author theme if they're a local user in mod/display
2022-05-29 05:39:54 +02:00
Hypolite Petovan a924716131 Use the top-level author theme if they're a local user in mod/display 2022-05-28 21:53:57 -04:00
Hypolite Petovan faa3f26d3b
Merge pull request #11570 from annando/issue-11470
Issue 11470: Check for removed account
2022-05-28 18:32:21 -04:00
Hypolite Petovan a503d5ac1b
Merge pull request #11569 from annando/issue-11431
Issue 11431: Only show pinned posts on contacts and your own ones
2022-05-28 18:24:37 -04:00
Hypolite Petovan 875d84435c
Merge pull request #11568 from annando/add-timestamp
Add timestamp data to the avatar path
2022-05-28 18:21:19 -04:00
Michael 4ef7f42257 Issue 11470: Check for removed account 2022-05-28 16:21:41 +00:00
Michael 881c4204b4 Issue 11431: Only show pinned posts on contacts and your own ones 2022-05-28 15:55:31 +00:00
Michael ba8088bf3c Add timestamp data to the avatar path 2022-05-28 14:20:48 +00:00
Philipp c7608983dc
Merge pull request #11567 from tobiasd/20220528-hu
HU translation updated THX Balázs Úr
2022-05-28 09:45:25 +02:00
Tobias Diekershoff efa15ab0b8
HU translation updated THX Balázs Úr 2022-05-28 07:09:07 +02:00
Tobias Diekershoff d3624af1b6
Merge pull request #11565 from annando/force-avatar-update
Pass the "force" parameter to the avatar update
2022-05-27 09:52:10 +02:00
Michael b05aa824f7 Don't move mail and feed 2022-05-27 06:32:19 +00:00
Michael 0a43fe857c Pass the "force" parameter to the avatar update 2022-05-27 05:36:07 +00:00
Michael Vogel e793438e0d
Merge pull request #11564 from tobiasd/20220527-de
DE translation updates
2022-05-27 07:12:38 +02:00
Tobias Diekershoff cf91734f4f
DE translation updates 2022-05-27 07:08:48 +02:00
Hypolite Petovan d4631176b2
Merge pull request #11563 from annando/avatar-error
Issue-11560: Fix error when creating avatar cache file
2022-05-26 18:58:49 -04:00
Michael 4475b3ff39 Updated messages.po 2022-05-26 20:01:07 +00:00
Michael 8c901ebd74 Issue-11560: Fix error when creating avatar cache file 2022-05-26 19:06:16 +00:00
Hypolite Petovan 1d29025091
Merge pull request #11559 from tobiasd/20220526-lng
DE, RU, ZH CN translation updates
2022-05-26 10:24:54 -04:00
Tobias Diekershoff cb5b6f345e
update to the zh-cn translation THX shykana 2022-05-26 16:18:11 +02:00
Tobias Diekershoff 314431507d
RU translations THX Alexander An 2022-05-26 16:17:58 +02:00
Tobias Diekershoff 620352d1a2
DE translation updates 2022-05-26 16:17:29 +02:00
Hypolite Petovan 488fdd9791
Merge pull request #11558 from annando/public-contact
Ensure that the public contact exists before adding a user contact
2022-05-25 09:06:13 -04:00
Hypolite Petovan ae3acba231
Merge pull request #11549 from annando/move-to-avatar-cache
Console command to move avatars to the avatar cache
2022-05-25 09:03:12 -04:00
Hypolite Petovan 36ccb25bd2
Merge pull request #11556 from tobiasd/20220525-frhu
FR and HU translation updates
2022-05-25 09:02:27 -04:00
Michael 918c1eeb56 Check for phantom network 2022-05-25 11:46:58 +00:00
Michael f5d69b3b6a Ensure that the public contact exixts before adding a user contact 2022-05-25 11:43:29 +00:00
Michael e8864653d8 Merge remote-tracking branch 'upstream/2022.05-rc' into move-to-avatar-cache 2022-05-25 05:38:50 +00:00
Michael dadebdd061 Change output function 2022-05-25 05:24:31 +00:00
Tobias Diekershoff 737d9e7a58
HU translation updated THX Balázs Úr 2022-05-25 07:23:59 +02:00
Tobias Diekershoff 2a0ab3f0f1
update FR translations THX kalon33 2022-05-25 07:21:04 +02:00
Hypolite Petovan 9804c71b53
Merge pull request #11555 from annando/relay
Improved Relay handling / better handling of receiver responses
2022-05-25 00:22:42 -04:00
Hypolite Petovan 0de50a5118
Merge pull request #11554 from tobiasd/20220525-fr
update FR translations THX  kalon33
2022-05-25 00:20:37 -04:00
Michael Vogel e5e509ea21 Calculate totals 2022-05-24 22:32:30 +02:00
Michael 2fbfc0633a Fixed placeholder 2022-05-24 20:12:39 +00:00
Michael d3709d8497 Delete on invalid 2022-05-24 20:02:54 +00:00
Michael 9da0e637a3 Quit on invalid avatars 2022-05-24 19:47:49 +00:00
Michael 80adad3ad2 Additional method to fetch photo avatars 2022-05-24 19:41:38 +00:00
Michael fb0e31791f updated messages.po 2022-05-24 19:09:36 +00:00
Michael 077c6ece64 Don't use a newline at the end 2022-05-24 17:51:28 +00:00
Michael 2ead69c7a5 Delete invalid photo data 2022-05-24 17:49:29 +00:00
Michael Vogel 0374f0e523 Use correct placeholder 2022-05-24 19:45:02 +02:00
Michael 0ff8ca8053 Trim the rid 2022-05-24 17:39:02 +00:00
Michael Vogel eddfab7122 A space in front of a message looks better 2022-05-24 19:36:48 +02:00
Michael f764ab7429 Improved messages 2022-05-24 17:33:10 +00:00
Michael 74b102b948 Increase the delivery queue counter on drop 2022-05-24 17:28:35 +00:00
Michael bee6ffe968 Support dropping delivery for single message transfer as well 2022-05-24 12:32:04 +00:00
Michael 034d838248 Ignore delivery problems with drops 2022-05-24 12:27:35 +00:00
Michael 8969e83134 Use a better query to fetch the relay actor 2022-05-24 08:06:48 +00:00
Michael e68ff3b3bb Return the actor, not the array 2022-05-24 08:02:55 +00:00
Michael b1a2de5cb5 Resubscribe to relay servers 2022-05-24 07:02:42 +00:00
Michael f16cb13dc7 Fixwd class description 2022-05-24 05:58:14 +00:00
Tobias Diekershoff 9b860f4051
update FR translations THX kalon33 2022-05-24 07:51:23 +02:00
Michael 7b91c4f333 Sorting by "id asc" makes more sense. 2022-05-23 15:31:37 +00:00
Michael 68599c7efa Some more code beauty 2022-05-23 12:44:21 +00:00
Michael 299c4df8f4 Convert to tabs 2022-05-23 12:35:44 +00:00
Michael d658c81107 Code standards 2022-05-23 12:33:25 +00:00
Michael d6242aacf6 Console command to move avatars to the avatar cache 2022-05-23 12:15:30 +00:00
Tobias Diekershoff a1871756ee
Merge pull request #11546 from nupplaphil/bug/catch_image_exception
Fix unsupported animated webp parsing
2022-05-21 18:59:21 +02:00
Philipp 218fc0c20d
Fix unsupported animated webp parsing 2022-05-21 18:51:03 +02:00
Philipp 783b05cbb1
Fix unsupported animated webp parsing 2022-05-21 18:44:03 +02:00
Hypolite Petovan c4ec80e839
Merge pull request #11545 from annando/url-tag
Don't parse tags in links
2022-05-21 08:44:49 -04:00
Michael 087ad25c87 Escape the "share" as well 2022-05-21 11:55:38 +00:00
Michael 54d7a435a3 Don't parse tags in links 2022-05-21 11:46:38 +00:00
Tobias Diekershoff ed350e472f
Merge pull request #11544 from annando/image-proxy
Only use proxied image links with images
2022-05-21 13:40:46 +02:00
Michael 49dab14215 Only use proxied image links with images 2022-05-21 06:58:26 +00:00
Tobias Diekershoff 9e19043c15
Merge pull request #11543 from annando/warning
Avoid warning "Undefined namespace prefix"
2022-05-21 08:18:59 +02:00
Michael 3444c29b0b Avoid warning "Undefined namespace prefix" 2022-05-21 06:04:34 +00:00
Hypolite Petovan 11538376ed
Merge pull request #11539 from annando/worker-split
The worker and the delivery is split into several classes
2022-05-20 23:45:51 -04:00
Hypolite Petovan c374630c65
Merge pull request #11541 from annando/fix-relay
Only deliver own content to the relay (possibly fix relay problems)
2022-05-20 23:42:36 -04:00
Michael e4e8b8cb46 Restoring old status 2022-05-20 22:52:44 +00:00
Michael 5095ce621a Test the license check 2022-05-20 22:43:27 +00:00
Michael Vogel 7857c329cf Only deliver own content to the relay (possibly fix relay problems) 2022-05-20 19:04:03 +02:00
Michael 9ded39eff6 Renamed variable 2022-05-20 15:49:59 +00:00
Hypolite Petovan 8af0af5f35
Merge pull request #11540 from tobiasd/20220520-pl
PL translation update THX strebski
2022-05-20 10:54:07 -04:00
Tobias Diekershoff d858aa86e5
PL translation update THX strebski 2022-05-20 13:59:55 +02:00
Michael 2fc5957abb Spelling error fixed 2022-05-20 05:46:38 +00:00
Michael 653af77e5f Further formatting fixes 2022-05-20 04:52:18 +00:00
Michael 6ce23bd9ca Fixed code structure 2022-05-20 04:42:10 +00:00
Michael dc16e6d471 The worker is split into several classes 2022-05-19 19:24:21 +00:00
Philipp 87e14d9d28
Merge pull request #11533 from annando/notification-cache
Cache notifications
2022-05-19 20:32:30 +02:00
Michael Vogel a13d33e057
Merge pull request #11536 from tobiasd/20220519-de
DE translation updates
2022-05-19 14:11:44 +02:00
Tobias Diekershoff e151e5958f
DE translation updates 2022-05-19 12:49:27 +02:00
Michael 49c47008d1 Convert class calls into DI calls 2022-05-19 09:08:04 +00:00
Michael Vogel 0b8e0a09e3
Merge pull request #11534 from nupplaphil/bug/catch_notfound
Catch exceptions for Worker::AddContact()
2022-05-19 04:45:13 +02:00
Philipp c33611c484
Catch exceptions for Worker::AddContact() 2022-05-18 22:37:17 +02:00
Michael 5aa798b1dc Remove test logging 2022-05-18 20:10:14 +00:00
Michael 536ce232a7 Cache notifications 2022-05-18 20:05:29 +00:00
Tobias Diekershoff 7d958e8804
Merge pull request #11530 from annando/logruntime
Configuration for logging added
2022-05-18 08:17:50 +02:00
Tobias Diekershoff b18880221a
Merge pull request #11531 from annando/display-polls
Improved poll texts for different conditions
2022-05-18 08:16:41 +02:00
Tobias Diekershoff 23e5ae15e3
Merge pull request #11528 from annando/logruntime
Log the execution time
2022-05-18 07:07:55 +02:00
Michael 4622814e5f Configuration for logging added 2022-05-18 03:10:38 +00:00
Michael 2e6e3597e5 unused DI" removed 2022-05-18 02:19:45 +00:00
Michael f6167b4cfd New function to exit the program 2022-05-18 02:13:54 +00:00
Michael 4e9d7df31a Add missing DI 2022-05-17 21:26:35 +00:00
Michael aacaa3c2cf Log the command, not the module 2022-05-17 21:25:01 +00:00
Michael 4016a576d5 Log the execution time 2022-05-17 20:47:23 +00:00
Tobias Diekershoff 6eb70bea16
Merge pull request #11526 from annando/ap-endpoint-cache
Prolonged cache lifetime for endpoints
2022-05-17 17:43:20 +02:00
Michael 0b50dc363b Caching of "noscrape" 2022-05-17 15:39:04 +00:00
Michael c3fd8b39aa Caching for profiles removed 2022-05-17 12:53:31 +00:00
Michael 4e3e9dc763 Cache the profile as well 2022-05-17 12:46:55 +00:00
Michael 75534fa3f3 Fix warning " Trying to access array offset on value of type bool" 2022-05-17 12:38:39 +00:00
Michael 73019284ce Increase cache lifespan / clear cache upon changes 2022-05-17 12:32:25 +00:00
Michael a880d8b982 Merge remote-tracking branch 'upstream/2022.05-rc' into ap-endpoint-cache 2022-05-17 08:18:02 +00:00
Michael 610f8a086f Use cache key constants 2022-05-17 08:17:41 +00:00
Tobias Diekershoff 3493f91801
Merge pull request #11525 from annando/shared-media
Fix: Fetch media in shared posts
2022-05-17 10:11:21 +02:00
Michael 10cdefa232 Fix: Fetch media in shared posts 2022-05-17 07:58:41 +00:00
Tobias Diekershoff e555ea6aad
Merge pull request #11524 from annando/cache-endpoints
AP endpoints are now cached
2022-05-17 07:13:51 +02:00
Michael 1ccf22a496 AP endpoints are now cached 2022-05-17 04:58:54 +00:00
Tobias Diekershoff 9f1cc2fb67
Merge pull request #11523 from annando/preview
Fix: Preview failed because of unknown variables
2022-05-17 06:09:51 +02:00
Michael 3f8da997f4 Fix: Preview failed because of unknown variables 2022-05-17 03:40:38 +00:00
Hypolite Petovan 65516a1d3a
Merge pull request #11522 from annando/performance
Performance: Avoid queries where there is no media or category
2022-05-16 15:00:10 -04:00
Michael Vogel 257ae2ac17
Merge pull request #11521 from tobiasd/20220516-de
DE translation updates
2022-05-16 19:12:36 +02:00
Michael 4daae255d8 Performance: Avoid queries where there is no media or category 2022-05-16 17:06:58 +00:00
Tobias Diekershoff f07422634a
DE translation updates 2022-05-16 18:36:24 +02:00
Michael 62a1cf257e Improved poll texts for different conditions 2022-05-16 06:04:46 +00:00
Tobias Diekershoff 69984ac6bc
Merge pull request #11520 from annando/display-polls
Display poll results
2022-05-16 08:04:08 +02:00
Michael Vogel 7199a91289
Merge pull request #11509 from tobiasd/20220515-credits
regen CREDITS for the 2022.05 release
2022-05-16 07:32:31 +02:00
Michael 2dc9b4eda2 Vote text changed 2022-05-16 05:31:53 +00:00
Michael f277d9cd2f Exclude several folders from translation generation 2022-05-16 05:30:58 +00:00
Michael 1555c82196 Translation updated 2022-05-16 05:07:28 +00:00
Michael a63caefe3d Polls are now displayed 2022-05-16 04:55:15 +00:00
Hypolite Petovan 4829c240e9
Merge pull request #11516 from annando/issue-11472
Issue 11472: Store the "edit" history
2022-05-15 20:24:59 -04:00
Hypolite Petovan 2bde5c4850
Merge pull request #11518 from annando/issue-11504
Issue 11504: Abstract has to be plaintext
2022-05-15 20:19:42 -04:00
Michael 2c511b8a23 Check the edit date before storing history 2022-05-15 21:15:31 +00:00
Michael 440b6d7956 Issue 11504: Abstract has to be plaintext 2022-05-15 21:04:01 +00:00
Michael d77b043d9c Fixing tests 2022-05-15 20:26:14 +00:00
Michael 324cce7a19 Issue 11472: Store the "edit" history 2022-05-15 20:21:56 +00:00
Tobias Diekershoff 204e52ea30
Merge pull request #11515 from annando/issue-11492
Issue 11492: Improvements for contact import
2022-05-15 21:56:36 +02:00
Michael 972f91436c Issue 11492: Improvements for contact import 2022-05-15 18:40:46 +00:00
Tobias Diekershoff 6f70d21e07
Merge pull request #11514 from annando/issue-11508
Issue 11508: Sanitizing date fields for events and polls
2022-05-15 20:07:52 +02:00
Michael 5af7c30026 Issue 11508: Sanitizing date fields for events and polls 2022-05-15 18:00:19 +00:00
Tobias Diekershoff 66004f4d2d regen CREDITS for the 2022.05 release 2022-05-15 13:46:07 +02:00
Tobias Diekershoff 44291a465b
Merge pull request #11506 from annando/featured-worker
Fetch featured posts through a worker
2022-05-15 13:10:19 +02:00
Michael 30b9af10ac Fetch featured posts through a worker 2022-05-15 09:08:35 +00:00
Tobias Diekershoff 036b565a78
Merge pull request #11503 from annando/bulk-delivery
Fix workerqueue entries with wrong priority
2022-05-15 08:22:36 +02:00
Michael f0bfa9a690 Each 5xx error is a server fail 2022-05-14 13:49:01 +00:00
Michael 689b71c696 Added check for server failure 2022-05-14 11:34:00 +00:00
Michael fbdb73cdd6 Improved log levels 2022-05-14 11:18:48 +00:00
Michael 43dbd1c396 Add additional timeout checks 2022-05-14 11:09:59 +00:00
Michael f8d929d94e Ensure to set the network timeout 2022-05-14 09:53:12 +00:00
Michael 962b35a76a Merge remote-tracking branch 'upstream/2022.05-rc' into bulk-delivery 2022-05-14 07:44:25 +00:00
Michael a8839517fe Fix workerqueue entries with wrong priority 2022-05-14 06:36:43 +00:00
Michael 30bcb24af7 Display the runtime 2022-05-14 06:06:38 +00:00
Michael 4b5a743645 Handle timeout 2022-05-14 05:38:01 +00:00
Tobias Diekershoff 74167043a8
Merge pull request #11497 from annando/bulk-delivery
Simplifications for the experimental bulk delivery
2022-05-14 07:34:21 +02:00
Michael 4ef2679ca6 Fix test 2022-05-13 19:37:50 +00:00
Michael fab5ba39ff Failed post deliveries are now deleted via cron 2022-05-13 18:48:13 +00:00
Michael 065d73f860 Merge remote-tracking branch 'upstream/2022.05-rc' into bulk-delivery 2022-05-13 08:53:18 +00:00
Michael 65b86fe0d5 Blanks replaced 2022-05-13 07:44:36 +00:00
Michael 0f0f4bc2c7 New worker job for deliveries without a worker 2022-05-13 07:31:00 +00:00
Tobias Diekershoff bb44ff1528
Merge pull request #11500 from annando/pleroma-issues
Change the featured collection to fix communication issues with Pleroma
2022-05-13 07:58:49 +02:00
Michael a662245c74 We now store the receivers as well 2022-05-13 05:52:05 +00:00
Michael 47cd1edb9f Change the featured collection to fix communication issues with Pleroma 2022-05-13 03:42:04 +00:00
Michael Vogel 201610dfe6 Don't look at the command when archiving an inbox 2022-05-13 04:24:22 +02:00
Michael 2595b5e12f Remove all posts of a given inbox 2022-05-13 02:18:46 +00:00
Michael 2049fbce91 Remove delivery when the inbox is archived 2022-05-13 02:11:02 +00:00
Michael 4f68be82ef Use a simpler worker call 2022-05-12 21:28:57 +00:00
Michael 67a74c15e1 Improve item fetching 2022-05-12 21:10:59 +00:00
Michael Vogel 5409998047
Merge pull request #11498 from tobiasd/20220512-zhcn
update to the zh-cn translation THX gudzpoz
2022-05-12 19:24:36 +02:00
Michael b0b67f1fde Remove failing posts 2022-05-12 12:43:49 +00:00
Tobias Diekershoff dfeb2e9a62
update to the zh-cn translation THX gudzpoz 2022-05-12 14:29:05 +02:00
Tobias Diekershoff ce7bf8524d
update to the zh-cn translation THX gudzpoz 2022-05-12 14:27:03 +02:00
Michael a943dbb420 Introducing the "failed" counter 2022-05-12 06:54:58 +00:00
Michael d5d2892f59 Simplifications for the experimental bulk delivery 2022-05-12 06:17:55 +00:00
Hypolite Petovan f128c00ca5
Merge pull request #11496 from annando/no-local-update
Don't update the local avatar
2022-05-11 14:53:25 -04:00
Michael 3d7ecb4fde Don't update the local avatar 2022-05-11 18:30:59 +00:00
Hypolite Petovan 610b3fc39e
Merge pull request #11495 from annando/warning
Avoid warning "fileperms(): stat failed for ..."
2022-05-11 13:46:06 -04:00
Michael afa57edf8e Avoid warning "fileperms(): stat failed for ..." 2022-05-11 17:28:28 +00:00
Hypolite Petovan fc364df7c2
Merge pull request #11494 from annando/warning
Fix a warning because of an undefined array key
2022-05-11 12:08:07 -04:00
Michael 47ee6fd009 Fix a warning because of an undefined array key 2022-05-11 16:01:37 +00:00
Michael Vogel 449df1a583
Merge pull request #11493 from tobiasd/20220511-frioimportcontactsform
[frio] import contacts form was missing the enctype
2022-05-11 17:59:20 +02:00
Tobias Diekershoff d816f02ab8
[frio] import contacts form was missing the enctype
fixes #11492 hopefully

For uploading files, the enctype for the form has to be set to `multipart/form-data`. Frio was missing this specification, other themes inherit it from the default template.
2022-05-11 17:28:57 +02:00
Hypolite Petovan 7026dd37db
Merge pull request #11490 from annando/issue-11487
Move IDN conversion behind the cleaning
2022-05-11 08:52:02 -04:00
Michael 0f0b649e8d Replace unparse function 2022-05-11 11:06:14 +00:00
Michael 34f594137e Movwe the conversion after the cleaning 2022-05-11 10:56:44 +00:00
Michael ffb8491c3f Merge remote-tracking branch 'upstream/2022.05-rc' into issue-11487 2022-05-11 10:53:43 +00:00
Michael 020ba7a4ed The function is now usable for all formats 2022-05-11 06:58:26 +00:00
Michael 695e3d8b61 Simplifiy the split 2022-05-11 06:55:02 +00:00
Tobias Diekershoff b5129eb4ed
Merge pull request #11489 from annando/issue-11487
Issue 11487: IDN support added
2022-05-11 08:46:15 +02:00
Michael 47808ab0e9 Issue 11487: IDN support added 2022-05-11 06:34:25 +00:00
Hypolite Petovan e374c2e3da
Merge pull request #11486 from annando/avatar-fixes
Fix local avatar, improve speed for fetching cached photos
2022-05-10 14:41:06 -04:00
Michael d9de8b6d2f Remove test logging 2022-05-10 18:37:02 +00:00
Michael fae414fedd Fix local avatar, improve speed for fetching cached photos 2022-05-10 18:18:24 +00:00
Hypolite Petovan 776ce3f63a
Merge pull request #11484 from annando/avatar-logging
Improved logging for avatar storing
2022-05-10 08:03:31 -04:00
Tobias Diekershoff 4be52cdc4e
Merge pull request #11485 from annando/endless-loop
Avoid endless loop when storing posts
2022-05-10 08:44:39 +02:00
Michael 7c3173a0ae Avoid endless loop when storing posts 2022-05-10 06:07:26 +00:00
Michael 945f5c6db2 Improved logging for avatar storing 2022-05-10 06:04:37 +00:00
Hypolite Petovan d195d934be
Merge pull request #11482 from annando/inherit-permissions
Inherit avatar cache file permissions
2022-05-09 23:15:47 -04:00
Michael 22da88b43f Changed log level 2022-05-10 01:14:27 +00:00
Michael 548bf469ca Added logging 2022-05-09 22:36:25 +00:00
Hypolite Petovan c50ca418a5
Merge pull request #11483 from annando/issue-11353
Issue 11353: Suppress the forum sharer
2022-05-09 17:54:31 -04:00
Michael 630d25a24b Issue 11353: Suppress the forum sharer 2022-05-09 20:52:08 +00:00
Michael 34030a736d Use a constant for the avatar base path 2022-05-09 19:16:14 +00:00
Michael 916aa1c9a9 Inherit avatar cache file permissions 2022-05-09 17:36:46 +00:00
Hypolite Petovan e1f32f7f15
Merge pull request #11477 from annando/avatar-file-cache
Cache contact avatars locally as files
2022-05-09 11:13:08 -04:00
Michael cbe4a42906 Use a warning instead 2022-05-09 14:36:41 +00:00
Michael f785026289 Don't return a filename when it wasn't stored 2022-05-09 08:47:02 +00:00
Michael f744dd362d Picture shouldn't be executable 2022-05-09 08:28:23 +00:00
Michael 9d1ff0a4ce Set permissions 2022-05-09 08:20:09 +00:00
Michael 006b7a95f0 More avatar handling isolation 2022-05-09 06:57:47 +00:00
Michael 681d19a3bc Standards and renamed class 2022-05-09 06:31:09 +00:00
Michael 53d064c283 Avatar handling is moved to a separate class 2022-05-09 06:27:46 +00:00
Michael e3692c0105 Replaced check with hardwired path 2022-05-09 04:26:00 +00:00
Hypolite Petovan b9e091eb2f
Merge pull request #11480 from annando/gotosocial
GoToSocial added to the federation admin page
2022-05-08 16:40:32 -04:00
Michael Vogel 6b9f543c4e GoToSocial added to the federation admin page 2022-05-08 20:50:11 +02:00
Michael f220e26f00 Use new function to check for a local avatar cache file 2022-05-08 10:32:29 +00:00
Michael 805dc8e6bd Fix config call 2022-05-08 09:34:30 +00:00
Michael dd422a1e03 Fix contact field name 2022-05-08 08:57:41 +00:00
Michael 7b0187c4c5 Don't overwrite already replaced cache file paths 2022-05-08 08:53:18 +00:00
Michael Vogel ee81c266e1
Merge pull request #11476 from MrPetovan/bug/11445-abstract-code
Escape BBCode tag parsing avoidance tags when processing abstracts
2022-05-08 08:02:41 +02:00
Michael 2d856f48b7 Use the default timeout 2022-05-08 05:51:18 +00:00
Michael a31256412e Cache contact avatars locally as files 2022-05-08 05:37:17 +00:00
Hypolite Petovan 510dacf4df Escape BBCode tag parsing avoidance tags when processing abstracts
- Improve documentation for related methods
2022-05-07 22:19:50 -04:00
Hypolite Petovan 25876099ce Add tests for BBCode::getAbstract and BBCode::stripAbstract methods 2022-05-07 22:19:50 -04:00
Tobias Diekershoff 166f38ef3b
Merge pull request #11474 from annando/issue-11440
Issue 11440: Hashtags are now generated again
2022-05-07 09:31:13 +02:00
Michael 67c3a20c4f Issue 11440: Hashtags are now generated again 2022-05-06 17:03:51 +00:00
Hypolite Petovan 4729fca5d9
Merge pull request #11457 from annando/performance
Improve page performance
2022-05-06 11:11:05 -04:00
Michael Vogel 1e7a55180a
Update src/Content/Conversation.php
Co-authored-by: Hypolite Petovan <hypolite@mrpetovan.com>
2022-05-05 22:40:20 +02:00
Michael 2433fee461 Changes after code review 2022-05-05 12:23:44 +00:00
Michael ab42fd88a4 Changed index for the categories 2022-05-05 08:45:32 +00:00
Michael 2a4b5b4cb4 New index 2022-05-05 08:40:50 +00:00
Michael 4d359b7de1 Move the plink functionality to the right function 2022-05-05 07:57:52 +00:00
Michael d34432c517 Ignore if the worker was added 2022-05-05 06:12:16 +00:00
Michael 2f74a7bca4 Update display cache when the post had been edited 2022-05-04 07:50:14 +00:00
Michael 10b453bfc2 Deprecated function removed 2022-05-04 07:49:37 +00:00
Michael 60532ee7e4 Fill the item cache when storing item 2022-05-04 06:17:34 +00:00
Michael f6218427a4 Use the uri-id to fetch the contact 2022-05-03 21:51:56 +00:00
Tobias Diekershoff ff8e839139
Merge pull request #11468 from urbalazs/urbalazs-da-dk
Add Danish language to list of native language names
2022-05-03 21:26:47 +02:00
Balázs Úr 4171ff7563
Add Danish language to list of native language names 2022-05-03 21:13:47 +02:00
Michael Vogel 9953abb8aa
Merge pull request #11467 from tobiasd/20220503-de
DE translation updates
2022-05-03 18:13:18 +02:00
Tobias Diekershoff a7237ac036
DE translation updates 2022-05-03 15:25:09 +02:00
Michael fdf8002df1 Update database.sql 2022-05-03 08:26:38 +00:00
Michael 4ba28e0199 Merge remote-tracking branch 'upstream/2022.05-rc' into performance 2022-05-03 08:23:09 +00:00
Michael 49ec5e5e6e Check for empty value 2022-05-03 08:20:48 +00:00
Michael e6440471ae Throw an error when the feed is invalid 2022-05-03 08:20:26 +00:00
Michael 690682a37e Only send "accept" headers on some HTTP methods 2022-05-03 08:20:05 +00:00
Michael 632a98965c Fetch contact via uri-id to improve performance 2022-05-03 08:19:35 +00:00
Tobias Diekershoff 8bf806c550
pump version to 2022.05-rc 2022-05-03 07:24:19 +02:00
Michael 3163760343 Added configuration for the bulk delivery 2022-05-02 17:34:40 +00:00
Michael 6e394ac6ff Whitespaces removed 2022-05-02 14:36:21 +00:00
Michael 76789acace Reduce network request by transmitting to shared inboxes 2022-05-02 14:35:57 +00:00
Michael fcb245947e Fetch the receiver list from sharedinbox or personal inbox 2022-05-02 06:10:36 +00:00
Michael 9c2fe81ac6 Transmit via the sharedInbox 2022-05-02 05:53:11 +00:00
Michael c608d85707 Updated database description 2022-05-02 05:17:42 +00:00
Michael a9990db98c Clean the worker queue directly from cron 2022-05-02 05:16:02 +00:00
Michael 40aa67f8be Bulk transmission for AP posts 2022-05-02 05:15:27 +00:00
Michael 13e4144ba6 Use a centralized function to check the priority 2022-05-01 09:29:31 +00:00
Michael f7b6507438 More checks for strange priorities 2022-05-01 08:58:48 +00:00
Michael 19c1b31ab2 Only add valid values to the worker 2022-05-01 07:03:10 +00:00
Michael e19681684b Fix worker priorities 2022-05-01 06:57:29 +00:00
Michael 1f43332a1d Only fetch category when it exists 2022-04-30 06:57:22 +00:00
Michael fb3353d4bd Separate loop to fetch thread parents 2022-04-30 06:19:18 +00:00
Michael fdf70c1047 Make post preview workable 2022-04-29 12:34:10 +00:00
Michael a2452b33eb Add missing fields to collection 2022-04-29 10:13:23 +00:00
Michael 8dc0ab9bb3 Test: Disable magiclink in posts 2022-04-29 09:49:16 +00:00
Michael d3de2497bc Use gsid for the network name 2022-04-29 07:47:24 +00:00
Michael d44641e58c Fetch avatar by id 2022-04-29 07:30:13 +00:00
Michael 1326239576 Reduce the amount of queries 2022-04-29 05:32:12 +00:00
126 changed files with 23087 additions and 19787 deletions

6
.gitignore vendored
View File

@ -85,4 +85,8 @@ venv/
/bin/phpunit
#Ignore cache file
.php_cs.cache
.php_cs.cache
#ignore avatar picture cache path
/avatar

View File

@ -28,9 +28,11 @@ Andy
Andy Hee
Angristan
Anthronaut
Anton
Antron Samurai
Arian - Cazare Muncitori
Asher Pen
atjn
aweiher
axelt
balderino
@ -55,6 +57,7 @@ Carlos Solís
Carsten Pfeiffer
Casper
Cat Gray
chinnux
Chris Case
Christian González
Christian Kalkhoff
@ -90,6 +93,7 @@ effex7
Elena
emilia.krawczyk
Eric Côté
Erich
erik
Erkan Yilmaz
Eugene Veresk
@ -113,6 +117,7 @@ Gidi Kroon
GLComo
greeneyedred
Gregory Smith
gudzpoz
guzzisti
Haakon Meland Eriksen
Hans Meine
@ -138,6 +143,7 @@ Joe Doe
joe slam
Johannes Schwab
John Brazil
John Mortensen
Jonatan Nyberg
Jonny Tischbein
Josef Moravek
@ -216,6 +222,7 @@ Philipp Holzer
Pierre Bernardeau
Pierre Rudloff
Piotr Blonkowski
Piotr Strębski
pokerazor
R C
Rabuzarus
@ -284,6 +291,7 @@ Tobias Diekershoff
Tobias Hößl
Tom
Tom Aurlund
Tom Hu
tomamplius
tomtom84
Tony Baldwin

View File

@ -1 +1 @@
2022.05-dev
2022.05-rc

View File

@ -18,11 +18,13 @@
* 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/>.
*
*/
/**
* Run the worker from a daemon.
*
* 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();
@ -230,7 +232,7 @@ while (true) {
}
$timeout = ($seconds >= $wait_interval);
} while (!$timeout && !Worker::IPCJobsExists());
} while (!$timeout && !Worker\IPC::JobsExists());
if ($timeout) {
$do_cron = true;

View File

@ -58,7 +58,7 @@ case "$MODE" in
OUTFILE="$FULLPATH/../view/lang/C/messages.po"
FINDSTARTDIR="."
# skip addon folder
FINDOPTS="( -path ./addon -or -path ./addons -or -path ./addons-extra -or -path ./tests -or -path ./view/lang -or -path ./view/smarty3 -or -path ./vendor ) -prune -or"
FINDOPTS="( -path ./addon -or -path ./addons -or -path ./addons-extra -or -path ./tests -or -path ./view/lang -or -path ./view/smarty3 -or -path ./vendor -or -path ./local -or -path ./avatar -or -path ./proxy ) -prune -or"
F9KVERSION=$(cat ./VERSION);
echo "Friendica version $F9KVERSION"

View File

@ -31,7 +31,7 @@ use Friendica\Model\Contact;
define('FRIENDICA_PLATFORM', 'Friendica');
define('FRIENDICA_CODENAME', 'Siberian Iris');
define('FRIENDICA_VERSION', '2022.05-dev');
define('FRIENDICA_VERSION', '2022.05-rc');
define('DFRN_PROTOCOL_VERSION', '2.23');
define('NEW_TABLE_STRUCTURE_VERSION', 1288);

View File

@ -1,6 +1,6 @@
-- ------------------------------------------
-- Friendica 2022.05-dev (Siberian Iris)
-- DB_UPDATE_VERSION 1460
-- Friendica 2022.05-rc (Siberian Iris)
-- DB_UPDATE_VERSION 1464
-- ------------------------------------------
@ -712,13 +712,16 @@ CREATE TABLE IF NOT EXISTS `hook` (
--
CREATE TABLE IF NOT EXISTS `inbox-status` (
`url` varbinary(255) NOT NULL COMMENT 'URL of the inbox',
`uri-id` int unsigned COMMENT 'Item-uri id of inbox url',
`created` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'Creation date of this entry',
`success` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'Date of the last successful delivery',
`failure` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'Date of the last failed delivery',
`previous` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'Previous delivery date',
`archive` boolean NOT NULL DEFAULT '0' COMMENT 'Is the inbox archived?',
`shared` boolean NOT NULL DEFAULT '0' COMMENT 'Is it a shared inbox?',
PRIMARY KEY(`url`)
PRIMARY KEY(`url`),
INDEX `uri-id` (`uri-id`),
FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Status of ActivityPub inboxes';
--
@ -1068,8 +1071,8 @@ CREATE TABLE IF NOT EXISTS `post-category` (
`type` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '',
`tid` int unsigned NOT NULL DEFAULT 0 COMMENT '',
PRIMARY KEY(`uri-id`,`uid`,`type`,`tid`),
INDEX `uri-id` (`tid`),
INDEX `uid` (`uid`),
INDEX `tid` (`tid`),
INDEX `uid_uri-id` (`uid`,`uri-id`),
FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
FOREIGN KEY (`uid`) REFERENCES `user` (`uid`) ON UPDATE RESTRICT ON DELETE CASCADE,
FOREIGN KEY (`tid`) REFERENCES `tag` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT
@ -1114,6 +1117,25 @@ CREATE TABLE IF NOT EXISTS `post-content` (
FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Content for all posts';
--
-- TABLE post-delivery
--
CREATE TABLE IF NOT EXISTS `post-delivery` (
`uri-id` int unsigned NOT NULL COMMENT 'Id of the item-uri table entry that contains the item uri',
`inbox-id` int unsigned NOT NULL COMMENT 'Item-uri id of inbox url',
`uid` mediumint unsigned COMMENT 'Delivering user',
`created` datetime DEFAULT '0001-01-01 00:00:00' COMMENT '',
`command` varbinary(32) COMMENT '',
`failed` tinyint DEFAULT 0 COMMENT 'Number of times the delivery has failed',
`receivers` mediumtext COMMENT 'JSON encoded array with the receiving contacts',
PRIMARY KEY(`uri-id`,`inbox-id`),
INDEX `inbox-id_created` (`inbox-id`,`created`),
INDEX `uid` (`uid`),
FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
FOREIGN KEY (`inbox-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
FOREIGN KEY (`uid`) REFERENCES `user` (`uid`) ON UPDATE RESTRICT ON DELETE CASCADE
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Delivery data for posts for the batch processing';
--
-- TABLE post-delivery-data
--
@ -1133,6 +1155,32 @@ CREATE TABLE IF NOT EXISTS `post-delivery-data` (
FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Delivery data for items';
--
-- TABLE post-history
--
CREATE TABLE IF NOT EXISTS `post-history` (
`uri-id` int unsigned NOT NULL COMMENT 'Id of the item-uri table entry that contains the item uri',
`edited` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'Date of edit',
`title` varchar(255) NOT NULL DEFAULT '' COMMENT 'item title',
`content-warning` varchar(255) NOT NULL DEFAULT '' COMMENT '',
`body` mediumtext COMMENT 'item body content',
`raw-body` mediumtext COMMENT 'Body without embedded media links',
`location` varchar(255) NOT NULL DEFAULT '' COMMENT 'text location where this item originated',
`coord` varchar(255) NOT NULL DEFAULT '' COMMENT 'longitude/latitude pair representing location where this item originated',
`language` text COMMENT 'Language information about this post',
`app` varchar(255) NOT NULL DEFAULT '' COMMENT 'application which generated this item',
`rendered-hash` varchar(32) NOT NULL DEFAULT '' COMMENT '',
`rendered-html` mediumtext COMMENT 'item.body converted to html',
`object-type` varchar(100) NOT NULL DEFAULT '' COMMENT 'ActivityStreams object type',
`object` text COMMENT 'JSON encoded object structure unless it is an implied object (normal post)',
`target-type` varchar(100) NOT NULL DEFAULT '' COMMENT 'ActivityStreams target type if applicable (URI)',
`target` text COMMENT 'JSON encoded target structure if used',
`resource-id` varchar(32) NOT NULL DEFAULT '' COMMENT 'Used to link other tables to items, it identifies the linked resource (e.g. photo) and if set must also set resource_type',
`plink` varchar(255) NOT NULL DEFAULT '' COMMENT 'permalink or URL to a displayable copy of the message at its source',
PRIMARY KEY(`uri-id`,`edited`),
FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Post history';
--
-- TABLE post-link
--
@ -1171,6 +1219,7 @@ CREATE TABLE IF NOT EXISTS `post-media` (
`publisher-image` varbinary(255) COMMENT 'Image of the publisher of the media',
PRIMARY KEY(`id`),
UNIQUE INDEX `uri-id-url` (`uri-id`,`url`),
INDEX `uri-id-id` (`uri-id`,`id`),
FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Attached media';
@ -1719,6 +1768,9 @@ CREATE VIEW `post-user-view` AS SELECT
`author`.`network` AS `author-network`,
`author`.`blocked` AS `author-blocked`,
`author`.`hidden` AS `author-hidden`,
`author`.`updated` AS `author-updated`,
`author`.`gsid` AS `author-gsid`,
`author`.`uri-id` AS `author-uri-id`,
`post-user`.`owner-id` AS `owner-id`,
`owner`.`url` AS `owner-link`,
`owner`.`addr` AS `owner-addr`,
@ -1728,6 +1780,7 @@ CREATE VIEW `post-user-view` AS SELECT
`owner`.`network` AS `owner-network`,
`owner`.`blocked` AS `owner-blocked`,
`owner`.`hidden` AS `owner-hidden`,
`owner`.`updated` AS `owner-updated`,
`owner`.`contact-type` AS `owner-contact-type`,
`post-user`.`causer-id` AS `causer-id`,
`causer`.`url` AS `causer-link`,
@ -1763,6 +1816,8 @@ CREATE VIEW `post-user-view` AS SELECT
`post-question`.`multiple` AS `question-multiple`,
`post-question`.`voters` AS `question-voters`,
`post-question`.`end-time` AS `question-end-time`,
EXISTS(SELECT `uri-id` FROM `post-category` WHERE `post-category`.`uri-id` = `post-user`.`uri-id` AND `post-category`.`uid` = `post-user`.`uid`) AS `has-categories`,
EXISTS(SELECT `id` FROM `post-media` WHERE `post-media`.`uri-id` = `post-user`.`uri-id`) AS `has-media`,
`diaspora-interaction`.`interaction` AS `signed_text`,
`parent-item-uri`.`guid` AS `parent-guid`,
`parent-post`.`network` AS `parent-network`,
@ -1884,6 +1939,9 @@ CREATE VIEW `post-thread-user-view` AS SELECT
`author`.`network` AS `author-network`,
`author`.`blocked` AS `author-blocked`,
`author`.`hidden` AS `author-hidden`,
`author`.`updated` AS `author-updated`,
`author`.`gsid` AS `author-gsid`,
`author`.`uri-id` AS `author-uri-id`,
`post-thread-user`.`owner-id` AS `owner-id`,
`owner`.`url` AS `owner-link`,
`owner`.`addr` AS `owner-addr`,
@ -1893,6 +1951,7 @@ CREATE VIEW `post-thread-user-view` AS SELECT
`owner`.`network` AS `owner-network`,
`owner`.`blocked` AS `owner-blocked`,
`owner`.`hidden` AS `owner-hidden`,
`owner`.`updated` AS `owner-updated`,
`owner`.`contact-type` AS `owner-contact-type`,
`post-thread-user`.`causer-id` AS `causer-id`,
`causer`.`url` AS `causer-link`,
@ -1928,6 +1987,8 @@ CREATE VIEW `post-thread-user-view` AS SELECT
`post-question`.`multiple` AS `question-multiple`,
`post-question`.`voters` AS `question-voters`,
`post-question`.`end-time` AS `question-end-time`,
EXISTS(SELECT `uri-id` FROM `post-category` WHERE `post-category`.`uri-id` = `post-thread-user`.`uri-id` AND `post-category`.`uid` = `post-thread-user`.`uid`) AS `has-categories`,
EXISTS(SELECT `id` FROM `post-media` WHERE `post-media`.`uri-id` = `post-thread-user`.`uri-id`) AS `has-media`,
`diaspora-interaction`.`interaction` AS `signed_text`,
`parent-item-uri`.`guid` AS `parent-guid`,
`parent-post`.`network` AS `parent-network`,
@ -2035,6 +2096,9 @@ CREATE VIEW `post-view` AS SELECT
`author`.`network` AS `author-network`,
`author`.`blocked` AS `author-blocked`,
`author`.`hidden` AS `author-hidden`,
`author`.`updated` AS `author-updated`,
`author`.`gsid` AS `author-gsid`,
`author`.`uri-id` AS `author-uri-id`,
`post`.`owner-id` AS `owner-id`,
`owner`.`url` AS `owner-link`,
`owner`.`addr` AS `owner-addr`,
@ -2044,6 +2108,7 @@ CREATE VIEW `post-view` AS SELECT
`owner`.`network` AS `owner-network`,
`owner`.`blocked` AS `owner-blocked`,
`owner`.`hidden` AS `owner-hidden`,
`owner`.`updated` AS `owner-updated`,
`owner`.`contact-type` AS `owner-contact-type`,
`post`.`causer-id` AS `causer-id`,
`causer`.`url` AS `causer-link`,
@ -2059,6 +2124,8 @@ CREATE VIEW `post-view` AS SELECT
`post-question`.`multiple` AS `question-multiple`,
`post-question`.`voters` AS `question-voters`,
`post-question`.`end-time` AS `question-end-time`,
0 AS `has-categories`,
EXISTS(SELECT `id` FROM `post-media` WHERE `post-media`.`uri-id` = `post`.`uri-id`) AS `has-media`,
`diaspora-interaction`.`interaction` AS `signed_text`,
`parent-item-uri`.`guid` AS `parent-guid`,
`parent-post`.`network` AS `parent-network`,
@ -2162,6 +2229,9 @@ CREATE VIEW `post-thread-view` AS SELECT
`author`.`network` AS `author-network`,
`author`.`blocked` AS `author-blocked`,
`author`.`hidden` AS `author-hidden`,
`author`.`updated` AS `author-updated`,
`author`.`gsid` AS `author-gsid`,
`author`.`uri-id` AS `author-uri-id`,
`post-thread`.`owner-id` AS `owner-id`,
`owner`.`url` AS `owner-link`,
`owner`.`addr` AS `owner-addr`,
@ -2171,6 +2241,7 @@ CREATE VIEW `post-thread-view` AS SELECT
`owner`.`network` AS `owner-network`,
`owner`.`blocked` AS `owner-blocked`,
`owner`.`hidden` AS `owner-hidden`,
`owner`.`updated` AS `owner-updated`,
`owner`.`contact-type` AS `owner-contact-type`,
`post-thread`.`causer-id` AS `causer-id`,
`causer`.`url` AS `causer-link`,
@ -2186,6 +2257,8 @@ CREATE VIEW `post-thread-view` AS SELECT
`post-question`.`multiple` AS `question-multiple`,
`post-question`.`voters` AS `question-voters`,
`post-question`.`end-time` AS `question-end-time`,
0 AS `has-categories`,
EXISTS(SELECT `id` FROM `post-media` WHERE `post-media`.`uri-id` = `post-thread`.`uri-id`) AS `has-media`,
`diaspora-interaction`.`interaction` AS `signed_text`,
`parent-item-uri`.`guid` AS `parent-guid`,
`parent-post`.`network` AS `parent-network`,
@ -2234,9 +2307,14 @@ CREATE VIEW `collection-view` AS SELECT
`post-collection`.`type` AS `type`,
`post`.`author-id` AS `cid`,
`post`.`received` AS `received`,
`post`.`created` AS `created`
`post`.`created` AS `created`,
`post-thread`.`commented` AS `commented`,
`post`.`thr-parent-id` AS `thr-parent-id`,
`post`.`author-id` AS `author-id`,
`post`.`gravity` AS `gravity`
FROM `post-collection`
INNER JOIN `post` ON `post-collection`.`uri-id` = `post`.`uri-id`;
INNER JOIN `post` ON `post-collection`.`uri-id` = `post`.`uri-id`
INNER JOIN `post-thread` ON `post-thread`.`uri-id` = `post`.`parent-uri-id`;
--
-- VIEW tag-view

View File

@ -50,7 +50,9 @@ Database Tables
| [post-category](help/database/db_post-category) | post relation to categories |
| [post-collection](help/database/db_post-collection) | Collection of posts |
| [post-content](help/database/db_post-content) | Content for all posts |
| [post-delivery](help/database/db_post-delivery) | Delivery data for posts for the batch processing |
| [post-delivery-data](help/database/db_post-delivery-data) | Delivery data for items |
| [post-history](help/database/db_post-history) | Post history |
| [post-link](help/database/db_post-link) | Post related external links |
| [post-media](help/database/db_post-media) | Attached media |
| [post-question](help/database/db_post-question) | Question |

View File

@ -9,6 +9,7 @@ Fields
| Field | Description | Type | Null | Key | Default | Extra |
| -------- | ------------------------------------ | -------------- | ---- | --- | ------------------- | ----- |
| url | URL of the inbox | varbinary(255) | NO | PRI | NULL | |
| uri-id | Item-uri id of inbox url | int unsigned | YES | | NULL | |
| created | Creation date of this entry | datetime | NO | | 0001-01-01 00:00:00 | |
| success | Date of the last successful delivery | datetime | NO | | 0001-01-01 00:00:00 | |
| failure | Date of the last failed delivery | datetime | NO | | 0001-01-01 00:00:00 | |
@ -22,6 +23,13 @@ Indexes
| Name | Fields |
| ------- | ------ |
| PRIMARY | url |
| uri-id | uri-id |
Foreign Keys
------------
| Field | Target Table | Target Field |
|-------|--------------|--------------|
| uri-id | [item-uri](help/database/db_item-uri) | id |
Return to [database documentation](help/database)

View File

@ -16,11 +16,11 @@ Fields
Indexes
------------
| Name | Fields |
| ------- | ---------------------- |
| PRIMARY | uri-id, uid, type, tid |
| uri-id | tid |
| uid | uid |
| Name | Fields |
| ---------- | ---------------------- |
| PRIMARY | uri-id, uid, type, tid |
| tid | tid |
| uid_uri-id | uid, uri-id |
Foreign Keys
------------

View File

@ -0,0 +1,37 @@
Table post-delivery
===========
Delivery data for posts for the batch processing
Fields
------
| Field | Description | Type | Null | Key | Default | Extra |
| --------- | --------------------------------------------------------- | ------------------ | ---- | --- | ------------------- | ----- |
| uri-id | Id of the item-uri table entry that contains the item uri | int unsigned | NO | PRI | NULL | |
| inbox-id | Item-uri id of inbox url | int unsigned | NO | PRI | NULL | |
| uid | Delivering user | mediumint unsigned | YES | | NULL | |
| created | | datetime | YES | | 0001-01-01 00:00:00 | |
| command | | varbinary(32) | YES | | NULL | |
| failed | Number of times the delivery has failed | tinyint | YES | | 0 | |
| receivers | JSON encoded array with the receiving contacts | mediumtext | YES | | NULL | |
Indexes
------------
| Name | Fields |
| ---------------- | ----------------- |
| PRIMARY | uri-id, inbox-id |
| inbox-id_created | inbox-id, created |
| uid | uid |
Foreign Keys
------------
| Field | Target Table | Target Field |
|-------|--------------|--------------|
| uri-id | [item-uri](help/database/db_item-uri) | id |
| inbox-id | [item-uri](help/database/db_item-uri) | id |
| uid | [user](help/database/db_user) | uid |
Return to [database documentation](help/database)

View File

@ -0,0 +1,44 @@
Table post-history
===========
Post history
Fields
------
| Field | Description | Type | Null | Key | Default | Extra |
| --------------- | ------------------------------------------------------------------------------------------------------------------------- | ------------ | ---- | --- | ------------------- | ----- |
| uri-id | Id of the item-uri table entry that contains the item uri | int unsigned | NO | PRI | NULL | |
| edited | Date of edit | datetime | NO | PRI | 0001-01-01 00:00:00 | |
| title | item title | varchar(255) | NO | | | |
| content-warning | | varchar(255) | NO | | | |
| body | item body content | mediumtext | YES | | NULL | |
| raw-body | Body without embedded media links | mediumtext | YES | | NULL | |
| location | text location where this item originated | varchar(255) | NO | | | |
| coord | longitude/latitude pair representing location where this item originated | varchar(255) | NO | | | |
| language | Language information about this post | text | YES | | NULL | |
| app | application which generated this item | varchar(255) | NO | | | |
| rendered-hash | | varchar(32) | NO | | | |
| rendered-html | item.body converted to html | mediumtext | YES | | NULL | |
| object-type | ActivityStreams object type | varchar(100) | NO | | | |
| object | JSON encoded object structure unless it is an implied object (normal post) | text | YES | | NULL | |
| target-type | ActivityStreams target type if applicable (URI) | varchar(100) | NO | | | |
| target | JSON encoded target structure if used | text | YES | | NULL | |
| resource-id | Used to link other tables to items, it identifies the linked resource (e.g. photo) and if set must also set resource_type | varchar(32) | NO | | | |
| plink | permalink or URL to a displayable copy of the message at its source | varchar(255) | NO | | | |
Indexes
------------
| Name | Fields |
| ------- | -------------- |
| PRIMARY | uri-id, edited |
Foreign Keys
------------
| Field | Target Table | Target Field |
|-------|--------------|--------------|
| uri-id | [item-uri](help/database/db_item-uri) | id |
Return to [database documentation](help/database)

View File

@ -35,6 +35,7 @@ Indexes
| ---------- | ------------------- |
| PRIMARY | id |
| uri-id-url | UNIQUE, uri-id, url |
| uri-id-id | uri-id, id |
Foreign Keys
------------

View File

@ -110,7 +110,13 @@ function display_init(App $a)
$item = $parent ?: $item;
}
DI::page()['aside'] = Widget\VCard::getHTML(display_fetchauthor($item));
$author = display_fetchauthor($item);
if (\Friendica\Util\Network::isLocalLink($author['url'])) {
\Friendica\Model\Profile::load(DI::app(), $author['nick'], false);
} else {
DI::page()['aside'] = Widget\VCard::getHTML($author);
}
}
function display_fetchauthor($item)

View File

@ -39,11 +39,11 @@ use Friendica\Util\Strings;
function fbrowser_content(App $a)
{
if (!local_user()) {
exit();
System::exit();
}
if (DI::args()->getArgc() == 1) {
exit();
System::exit();
}
// Needed to match the correct template in a module that uses a different theme than the user/site/default

View File

@ -621,6 +621,11 @@ function item_post(App $a) {
$datarray["id"] = -1;
$datarray["uri-id"] = -1;
$datarray["author-network"] = Protocol::DFRN;
$datarray["author-updated"] = '';
$datarray["author-gsid"] = 0;
$datarray["author-uri-id"] = ItemURI::getIdByURI($datarray["author-link"]);
$datarray["owner-updated"] = '';
$datarray["has-media"] = false;
$o = DI::conversation()->create([array_merge($contact_record, $datarray)], 'search', false, true);
@ -663,21 +668,17 @@ function item_post(App $a) {
$datarray['uri-id'] = ItemURI::getIdByURI($datarray['uri']);
if ($orig_post) {
// Fill the cache field
// This could be done in Item::update as well - but we have to check for the existance of some fields.
Item::putInCache($datarray);
$fields = [
'title' => $datarray['title'],
'body' => $datarray['body'],
'attach' => $datarray['attach'],
'file' => $datarray['file'],
'rendered-html' => $datarray['rendered-html'],
'rendered-hash' => $datarray['rendered-hash'],
'edited' => DateTimeFormat::utcNow(),
'changed' => DateTimeFormat::utcNow()];
'changed' => DateTimeFormat::utcNow()
];
Item::update($fields, ['id' => $post_id]);
Item::updateDisplayCache($datarray['uri-id']);
if ($return_path) {
DI::baseUrl()->redirect($return_path);

View File

@ -65,7 +65,7 @@ function photos_init(App $a) {
if (DI::args()->getArgc() > 1) {
$owner = User::getOwnerDataByNick(DI::args()->getArgv()[1]);
if (!$owner) {
if (empty($owner) || $owner['account_removed']) {
throw new HTTPException\NotFoundException(DI::l10n()->t('User not found.'));
}
@ -158,7 +158,7 @@ function photos_post(App $a)
if (!$can_post) {
notice(DI::l10n()->t('Permission denied.'));
exit();
System::exit();
}
$owner_record = User::getOwnerDataById($page_owner_uid);
@ -166,7 +166,7 @@ function photos_post(App $a)
if (!$owner_record) {
notice(DI::l10n()->t('Contact information unavailable'));
DI::logger()->info('photos_post: unable to locate contact record for page owner. uid=' . $page_owner_uid);
exit();
System::exit();
}
$aclFormatter = DI::aclFormatter();
@ -1257,14 +1257,12 @@ function photos_content(App $a)
$tags = null;
if (!empty($link_item['id'])) {
$tag_text = Tag::getCSVByURIId($link_item['uri-id']);
$arr = explode(',', $tag_text);
// parse tags and add links
$tag_arr = [];
foreach ($arr as $tag) {
foreach (Tag::getByURIId($link_item['uri-id']) as $tag) {
$tag_arr[] = [
'name' => BBCode::convert($tag),
'removeurl' => '/tagrm/' . $link_item['id'] . '/' . bin2hex($tag)
'name' => $tag['name'],
'removeurl' => '/tagrm/' . $link_item['id'] . '/' . bin2hex($tag['name'])
];
}
$tags = ['title' => DI::l10n()->t('Tags: '), 'tags' => $tag_arr];

View File

@ -22,6 +22,7 @@
use Friendica\App;
use Friendica\Core\Logger;
use Friendica\Core\Protocol;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Contact;
@ -38,7 +39,7 @@ function hub_return($valid, $body)
} else {
throw new \Friendica\Network\HTTPException\NotFoundException();
}
exit();
System::exit();
}
// when receiving an XML feed, always return OK

View File

@ -21,6 +21,7 @@
use Friendica\App;
use Friendica\Core\Logger;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\PushSubscriber;
@ -142,5 +143,5 @@ function pubsubhubbub_init(App $a) {
throw new \Friendica\Network\HTTPException\AcceptedException();
}
exit();
System::exit();
}

View File

@ -23,25 +23,16 @@ use Friendica\App;
use Friendica\BaseModule;
use Friendica\Content\Feature;
use Friendica\Content\Nav;
use Friendica\Core\ACL;
use Friendica\Core\Hook;
use Friendica\Core\Logger;
use Friendica\Core\Renderer;
use Friendica\Core\Worker;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Group;
use Friendica\Model\Item;
use Friendica\Model\Notification;
use Friendica\Model\Profile;
use Friendica\Model\User;
use Friendica\Model\Verb;
use Friendica\Module\BaseSettings;
use Friendica\Module\Security\Login;
use Friendica\Protocol\Activity;
use Friendica\Protocol\Email;
use Friendica\Util\Temporal;
use Friendica\Worker\Delivery;
function settings_init(App $a)
{

View File

@ -21,6 +21,7 @@
use Friendica\App;
use Friendica\Content\Text\BBCode;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Item;
@ -30,7 +31,7 @@ function share_init(App $a) {
$post_id = ((DI::args()->getArgc() > 1) ? intval(DI::args()->getArgv()[1]) : 0);
if (!$post_id || !local_user()) {
exit();
System::exit();
}
$fields = ['private', 'body', 'author-name', 'author-link', 'author-avatar',
@ -38,7 +39,7 @@ function share_init(App $a) {
$item = Post::selectFirst($fields, ['id' => $post_id]);
if (!DBA::isResult($item) || $item['private'] == Item::PRIVATE) {
exit();
System::exit();
}
if (strpos($item['body'], "[/share]") !== false) {
@ -56,5 +57,5 @@ function share_init(App $a) {
}
echo $o;
exit();
System::exit();
}

View File

@ -167,6 +167,5 @@ EOT;
$post = Post::selectFirst(['uri-id', 'uid'], ['id' => $post_id]);
Worker::add(PRIORITY_HIGH, "Notifier", Delivery::POST, $post['uri-id'], $post['uid']);
exit();
System::exit();
}

View File

@ -67,14 +67,14 @@ function wall_attach_post(App $a) {
System::jsonExit(['error' => DI::l10n()->t('Permission denied.')]);
}
notice(DI::l10n()->t('Permission denied.') . EOL );
exit();
System::exit();
}
if (empty($_FILES['userfile'])) {
if ($r_json) {
System::jsonExit(['error' => DI::l10n()->t('Invalid request.')]);
}
exit();
System::exit();
}
$src = $_FILES['userfile']['tmp_name'];
@ -97,7 +97,7 @@ function wall_attach_post(App $a) {
} else {
notice($msg);
}
exit();
System::exit();
}
if ($maxfilesize && $filesize > $maxfilesize) {
@ -108,7 +108,7 @@ function wall_attach_post(App $a) {
} else {
echo $msg . EOL;
}
exit();
System::exit();
}
$newid = Attach::storeFile($src, $page_owner_uid, $filename, '<' . $page_owner_cid . '>');
@ -122,7 +122,7 @@ function wall_attach_post(App $a) {
} else {
echo $msg . EOL;
}
exit();
System::exit();
}
if ($r_json) {
@ -132,7 +132,6 @@ function wall_attach_post(App $a) {
$lf = "\n";
echo $lf . $lf . '[attachment]' . $newid . '[/attachment]' . $lf;
exit();
System::exit();
// NOTREACHED
}

View File

@ -89,14 +89,14 @@ function wall_upload_post(App $a, $desktopmode = true)
System::jsonExit(['error' => DI::l10n()->t('Permission denied.')]);
}
notice(DI::l10n()->t('Permission denied.'));
exit();
System::exit();
}
if (empty($_FILES['userfile']) && empty($_FILES['media'])) {
if ($r_json) {
System::jsonExit(['error' => DI::l10n()->t('Invalid request.')]);
}
exit();
System::exit();
}
$src = '';
@ -148,7 +148,7 @@ function wall_upload_post(App $a, $desktopmode = true)
System::jsonExit(['error' => DI::l10n()->t('Invalid request.')]);
}
notice(DI::l10n()->t('Invalid request.'));
exit();
System::exit();
}
$filetype = Images::getMimeTypeBySource($src, $filename, $filetype);
@ -167,7 +167,7 @@ function wall_upload_post(App $a, $desktopmode = true)
} else {
echo $msg. EOL;
}
exit();
System::exit();
}
$Image->orient($src);
@ -205,7 +205,7 @@ function wall_upload_post(App $a, $desktopmode = true)
} else {
echo $msg. EOL;
}
exit();
System::exit();
}
}
@ -229,7 +229,7 @@ function wall_upload_post(App $a, $desktopmode = true)
} else {
echo $msg. EOL;
}
exit();
System::exit();
}
if ($width > 640 || $height > 640) {
@ -281,6 +281,6 @@ function wall_upload_post(App $a, $desktopmode = true)
}
echo "\n\n" . '[url=' . DI::baseUrl() . '/photos/' . $page_owner_nick . '/image/' . $resource_id . '][img]' . DI::baseUrl() . "/photo/{$resource_id}-{$smallest}.".$Image->getExt()."[/img][/url]\n\n";
exit();
System::exit();
// NOTREACHED
}

View File

@ -472,7 +472,7 @@ class App
// Allow folks to override user themes and always use their own on their own site.
// This works only if the user is on the same server
$user = $this->database->selectFirst('user', ['theme'], ['uid' => $this->profile_owner]);
if ($this->database->isResult($user) && !$this->pConfig->get(local_user(), 'system', 'always_my_theme')) {
if ($this->database->isResult($user) && !local_user()) {
$page_theme = $user['theme'];
}
}
@ -504,7 +504,7 @@ class App
if (!empty($this->profile_owner) && ($this->profile_owner != local_user())) {
// Allow folks to override user themes and always use their own on their own site.
// This works only if the user is on the same server
if (!$this->pConfig->get(local_user(), 'system', 'always_my_theme')) {
if (!local_user()) {
$page_mobile_theme = $this->pConfig->get($this->profile_owner, 'system', 'mobile-theme');
}
}
@ -576,6 +576,7 @@ class App
$this->profiler->set(microtime(true), 'classinit');
$moduleName = $this->args->getModuleName();
$page->setLogging($this->args->getCommand(), $this->args->getMethod());
try {
// Missing DB connection: ERROR
@ -718,6 +719,7 @@ class App
} catch (HTTPException $e) {
(new ModuleHTTPException())->rawContent($e);
}
$page->logRuntime($this->config);
}
/**

View File

@ -30,6 +30,7 @@ use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\PConfig\Capability\IManagePersonalConfigValues;
use Friendica\Core\Hook;
use Friendica\Core\L10n;
use Friendica\Core\Logger;
use Friendica\Core\Renderer;
use Friendica\Core\System;
use Friendica\Core\Theme;
@ -78,14 +79,37 @@ class Page implements ArrayAccess
*/
private $basePath;
private $timestamp = 0;
private $command = '';
private $method = '';
/**
* @param string $basepath The Page basepath
*/
public function __construct(string $basepath)
{
$this->timestamp = microtime(true);
$this->basePath = $basepath;
}
public function setLogging(string $command, string $method)
{
$this->command = $command;
$this->method = $method;
}
public function logRuntime(IManageConfigValues $config)
{
if (in_array($this->command, $config->get('system', 'runtime_ignore'))) {
return;
}
$runtime = number_format(microtime(true) - $this->timestamp, 3);
if ($runtime > $config->get('system', 'runtime_loglimit')) {
Logger::debug('Runtime', ['method' => $this->method, 'command' => $this->command, 'runtime' => $runtime]);
}
}
/**
* Whether a offset exists
*
@ -421,6 +445,9 @@ class Page implements ArrayAccess
{
$moduleName = $args->getModuleName();
$this->command = $moduleName;
$this->method = $args->getMethod();
/* Create the page content.
* Calls all hooks which are including content operations
*

View File

@ -0,0 +1,179 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Console;
use Friendica\App\BaseURL;
use Friendica\Contact\Avatar;
use Friendica\Core\L10n;
use Friendica\Model\Contact;
use Friendica\Model\Photo;
use Friendica\Util\Images;
use Friendica\Object\Image;
use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\Protocol;
/**
* tool to move cached avatars to the avatar file cache.
*/
class MoveToAvatarCache extends \Asika\SimpleConsole\Console
{
protected $helpOptions = ['h', 'help', '?'];
/**
* @var $dba Friendica\Database\Database
*/
private $dba;
/**
* @var $baseurl Friendica\App\BaseURL
*/
private $baseurl;
/**
* @var L10n
*/
private $l10n;
/**
* @var IManageConfigValues
*/
private $config;
protected function getHelp()
{
$help = <<<HELP
console movetoavatarcache - Move all cached avatars to the file based avatar cache
Synopsis
bin/console movetoavatarcache
Description
bin/console movetoavatarcache
Move all cached avatars to the file based avatar cache
Options
-h|--help|-? Show help information
HELP;
return $help;
}
public function __construct(\Friendica\Database\Database $dba, BaseURL $baseurl, L10n $l10n, IManageConfigValues $config, array $argv = null)
{
parent::__construct($argv);
$this->dba = $dba;
$this->baseurl = $baseurl;
$this->l10n = $l10n;
$this->config = $config;
}
protected function doExecute()
{
if (!$this->config->get('system', 'avatar_cache')) {
$this->err($this->l10n->t('The avatar cache needs to be enabled to use this command.'));
return 2;
}
$fields = ['id', 'avatar', 'photo', 'thumb', 'micro', 'uri-id', 'url', 'avatar', 'network'];
$condition = ["NOT `self` AND `avatar` != ? AND `photo` LIKE ? AND `uid` = ? AND `uri-id` != ? AND NOT `uri-id` IS NULL AND NOT `network` IN (?, ?)",
'', $this->baseurl->get() . '/photo/%', 0, 0, Protocol::MAIL, Protocol::FEED];
$count = 0;
$total = $this->dba->count('contact', $condition);
$contacts = $this->dba->select('contact', $fields, $condition, ['order' => ['id']]);
while ($contact = $this->dba->fetch($contacts)) {
if (Contact::isLocal($contact['url'])) {
continue;
}
$this->out(++$count . '/' . $total . "\t" . $contact['id'] . "\t" . $contact['url'] . "\t", false);
$resourceid = Photo::ridFromURI($contact['photo']);
if (empty($resourceid)) {
$this->out($this->l10n->t('no resource in photo %s', $contact['photo']) . ' ', false);
}
$this->storeAvatar($resourceid, $contact, false);
}
$count = 0;
$totals = $this->dba->p("SELECT COUNT(DISTINCT(`resource-id`)) AS `total` FROM `photo` WHERE `contact-id` != ? AND `photo-type` = ?;", 0, Photo::CONTACT_AVATAR);
$total = $this->dba->fetch($totals)['total'] ?? 0;
$photos = $this->dba->p("SELECT `resource-id`, MAX(`contact-id`) AS `contact-id` FROM `photo` WHERE `contact-id` != ? AND `photo-type` = ? GROUP BY `resource-id`;", 0, Photo::CONTACT_AVATAR);
while ($photo = $this->dba->fetch($photos)) {
$contact = Contact::getById($photo['contact-id'], $fields);
if (empty($contact) || in_array($contact['network'], [Protocol::MAIL, Protocol::FEED]) || Contact::isLocal($contact['url'])) {
continue;
}
$this->out(++$count . '/' . $total . "\t" . $contact['id'] . "\t" . $contact['url'] . "\t", false);
$this->storeAvatar($photo['resource-id'], $contact, true);
}
return 0;
}
private function storeAvatar(string $resourceid, array $contact, bool $quit_on_invalid)
{
$valid = !empty($resourceid);
if ($valid) {
$this->out('1', false);
$photo = Photo::selectFirst([], ['resource-id' => $resourceid], ['order' => ['scale']]);
if (empty($photo)) {
$this->out(' ' . $this->l10n->t('no photo with id %s', $resourceid) . ' ', false);
$valid = false;
}
}
if ($valid) {
$this->out('2', false);
$imgdata = Photo::getImageDataForPhoto($photo);
if (empty($imgdata)) {
$this->out(' ' . $this->l10n->t('no image data for photo with id %s', $resourceid) . ' ', false);
$valid = false;
}
}
if ($valid) {
$this->out('3', false);
$image = new Image($imgdata, Images::getMimeTypeByData($imgdata));
if (!$image->isValid()) {
$this->out(' ' . $this->l10n->t('invalid image for id %s', $resourceid) . ' ', false);
$valid = false;
}
}
if ($valid) {
$this->out('4', false);
$fields = Avatar::storeAvatarByImage($contact, $image);
} else {
$fields = ['photo' => '', 'thumb' => '', 'micro' => ''];
}
if ($quit_on_invalid && $fields['photo'] == '') {
$this->out(' ' . $this->l10n->t('Quit on invalid photo %s', $contact['avatar']));
Photo::delete(['resource-id' => $resourceid]);
return;
}
$this->out('5', false);
Contact::update($fields, ['uri-id' => $contact['uri-id']]);
$this->out('6', false);
Photo::delete(['resource-id' => $resourceid]);
$this->out(' ' . $fields['photo']);
}
}

205
src/Console/Relocate.php Normal file
View File

@ -0,0 +1,205 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Console;
use Asika\SimpleConsole\Console;
use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\Worker;
use Friendica\Util\Strings;
use Friendica\Worker\Delivery;
class Relocate extends Console
{
protected $helpOptions = ['h', 'help', '?'];
/**
* @var IManageConfigValues
*/
private $config;
/**
* @var \Friendica\App\BaseURL
*/
private $baseUrl;
/**
* @var \Friendica\Database\Database
*/
private $database;
protected function getHelp()
{
$help = <<<HELP
console relocate - Update the node base URL
Usage
bin/console relocate <new base URL> [-h|--help|-?] [-v]
Description
Warning! Advanced function. Could make this server unreachable.
Change the base URL for this server. Sends relocation message to all the Friendica and Diaspora* contacts of all local users.
This process updates all the database fields that may contain a URL pointing at the current domain, as a result it takes
a while and the node will be in maintenance mode for the whole duration.
Options
-h|--help|-? Show help information
-v Show more debug information.
HELP;
return $help;
}
public function __construct(\Friendica\App\BaseURL $baseUrl, \Friendica\Database\Database $database, IManageConfigValues $config, $argv = null)
{
parent::__construct($argv);
$this->baseUrl = $baseUrl;
$this->database = $database;
$this->config = $config;
}
protected function doExecute()
{
if (count($this->args) == 0) {
$this->out($this->getHelp());
return 0;
}
if (count($this->args) > 1) {
throw new \Asika\SimpleConsole\CommandArgsException('Too many arguments');
}
$new_url = rtrim($this->getArgument(0), '/');
$parsed = @parse_url($new_url);
if (!is_array($parsed) || empty($parsed['host']) || empty($parsed['scheme'])) {
throw new \InvalidArgumentException('Can not parse new base URL. Must have at least <scheme>://<domain>');
}
$this->out(sprintf('Relocation started from %s to %s. Could take a while to complete.', $this->baseUrl->get(true), $this->getArgument(0)));
$old_url = $this->baseUrl->get(true);
// Generate host names for relocation the addresses in the format user@address.tld
$new_host = str_replace('http://', '@', Strings::normaliseLink($new_url));
$old_host = str_replace('http://', '@', Strings::normaliseLink($old_url));
$this->out('Entering maintenance mode');
$this->config->set('system', 'maintenance', true);
$this->config->set('system', 'maintenance_reason', 'Relocating node to ' . $new_url);
try {
if (!$this->database->transaction()) {
throw new \Exception('Unable to start a transaction, please retry later.');
}
// update tables
$this->out('Updating apcontact table fields');
$this->database->replaceInTableFields('apcontact', ['url', 'inbox', 'outbox', 'sharedinbox', 'photo', 'header', 'alias', 'subscribe', 'baseurl'], $old_url, $new_url);
$this->database->replaceInTableFields('apcontact', ['addr'], $old_host, $new_host);
$this->out('Updating contact table fields');
$this->database->replaceInTableFields('contact', ['photo', 'thumb', 'micro', 'url', 'alias', 'request', 'batch', 'notify', 'poll', 'subscribe', 'baseurl', 'confirm', 'poco', 'avatar', 'header'], $old_url, $new_url);
$this->database->replaceInTableFields('contact', ['nurl'], Strings::normaliseLink($old_url), Strings::normaliseLink($new_url));
$this->database->replaceInTableFields('contact', ['addr'], $old_host, $new_host);
$this->out('Updating conv table fields');
$this->database->replaceInTableFields('conv', ['creator', 'recips'], $old_host, $new_host);
$this->out('Updating delayed-post table fields');
$this->database->replaceInTableFields('delayed-post', ['uri'], $old_url, $new_url);
$this->out('Updating endpoint table fields');
$this->database->replaceInTableFields('endpoint', ['url'], $old_url, $new_url);
$this->out('Updating event table fields');
$this->database->replaceInTableFields('event', ['uri'], $old_url, $new_url);
$this->out('Updating fcontact table fields');
$this->database->replaceInTableFields('fcontact', ['url', 'photo', 'request', 'batch', 'poll', 'confirm', 'alias'], $old_url, $new_url);
$this->database->replaceInTableFields('fcontact', ['addr'], $old_host, $new_host);
$this->out('Updating fsuggest table fields');
$this->database->replaceInTableFields('fsuggest', ['url', 'request', 'photo'], $old_url, $new_url);
$this->out('Updating gserver table fields');
$this->database->replaceInTableFields('gserver', ['url'], $old_url, $new_url);
$this->database->replaceInTableFields('gserver', ['nurl'], Strings::normaliseLink($old_url), Strings::normaliseLink($new_url));
$this->out('Updating inbox-status table fields');
$this->database->replaceInTableFields('inbox-status', ['url'], $old_url, $new_url);
$this->out('Updating item-uri table fields');
$this->database->replaceInTableFields('item-uri', ['uri'], $old_url, $new_url);
$this->out('Updating mail table fields');
$this->database->replaceInTableFields('mail', ['from-photo', 'from-url', 'uri', 'thr-parent'], $old_url, $new_url);
$this->database->replaceInTableFields('mail', ['parent-uri'], $old_host, $new_host);
$this->out('Updating notify table fields');
$this->database->replaceInTableFields('notify', ['url', 'photo', 'link', 'msg', 'name_cache', 'msg_cache'], $old_url, $new_url);
$this->out('Updating profile table fields');
$this->database->replaceInTableFields('profile', ['photo', 'thumb'], $old_url, $new_url);
$this->out('Updating post-content table fields');
$this->database->replaceInTableFields('post-content', ['body', 'raw-body', 'rendered-html', 'target', 'plink'], $old_url, $new_url);
$this->database->replaceInTableFields('post-content', ['body', 'raw-body', 'rendered-html', 'target'], $old_host, $new_host);
$this->out('Updating post-history table fields');
$this->database->replaceInTableFields('post-history', ['body', 'raw-body', 'rendered-html', 'target', 'plink'], $old_url, $new_url);
$this->database->replaceInTableFields('post-history', ['body', 'raw-body', 'rendered-html', 'target'], $old_host, $new_host);
$this->out('Updating post-link table fields');
$this->database->replaceInTableFields('post-link', ['url'], $old_url, $new_url);
$this->out('Updating post-media table fields');
$this->database->replaceInTableFields('post-media', ['url', 'preview', 'author-url', 'author-image', 'publisher-url', 'publisher-image'], $old_url, $new_url);
$this->out('Updating tag table fields');
$this->database->replaceInTableFields('tag', ['url'], $old_url, $new_url);
// update config
$this->out('Updating config values');
$this->config->set('system', 'url', $new_url);
$this->baseUrl->saveByURL($new_url);
$this->database->commit();
} catch (\Throwable $e) {
$this->database->rollback();
$this->out('Process aborted with message: ' . $e->getMessage() . ' thrown in ' . $e->getFile() . ':' . $e->getLine());
return 1;
} finally {
$this->out('Leaving maintenance mode');
$this->config->set('system', 'maintenance', false);
$this->config->set('system', 'maintenance_reason', '');
}
// send relocate
$this->out('Schedule relocation messages to remote Friendica and Diaspora hosts');
$users = $this->database->selectToArray('user', ['uid'], ['account_removed' => false, 'account_expired' => false]);
foreach ($users as $user) {
Worker::add(PRIORITY_HIGH, 'Notifier', Delivery::RELOCATION, $user['uid']);
}
return 0;
}
}

265
src/Contact/Avatar.php Normal file
View File

@ -0,0 +1,265 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Contact;
use Friendica\Core\Logger;
use Friendica\DI;
use Friendica\Model\Item;
use Friendica\Network\HTTPClient\Client\HttpClientAccept;
use Friendica\Network\HTTPClient\Client\HttpClientOptions;
use Friendica\Object\Image;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\HTTPSignature;
use Friendica\Util\Images;
use Friendica\Util\Network;
use Friendica\Util\Proxy;
use Friendica\Util\Strings;
/**
* functions for handling contact avatar caching
*/
class Avatar
{
const BASE_PATH = '/avatar/';
/**
* Returns a field array with locally cached avatar pictures
*
* @param array $contact Contact array
* @param string $avatar Link to avatar picture
* @param bool $force force picture update
* @return array
*/
public static function fetchAvatarContact(array $contact, string $avatar, bool $force = false): array
{
$fields = ['avatar' => $avatar, 'avatar-date' => DateTimeFormat::utcNow(), 'photo' => '', 'thumb' => '', 'micro' => ''];
if (!DI::config()->get('system', 'avatar_cache')) {
self::deleteCache($contact);
return $fields;
}
if (Network::isLocalLink($avatar) || empty($avatar)) {
self::deleteCache($contact);
return $fields;
}
if (($avatar != $contact['avatar']) || $force) {
self::deleteCache($contact);
Logger::debug('Avatar file name changed', ['new' => $avatar, 'old' => $contact['avatar']]);
} elseif (self::isCacheFile($contact['photo']) && self::isCacheFile($contact['thumb']) && self::isCacheFile($contact['micro'])) {
$fields['photo'] = $contact['photo'];
$fields['thumb'] = $contact['thumb'];
$fields['micro'] = $contact['micro'];
Logger::debug('Using existing cache files', ['uri-id' => $contact['uri-id'], 'fields' => $fields]);
return $fields;
}
$fetchResult = HTTPSignature::fetchRaw($avatar, 0, [HttpClientOptions::ACCEPT_CONTENT => [HttpClientAccept::IMAGE]]);
$img_str = $fetchResult->getBody();
if (empty($img_str)) {
Logger::debug('Avatar is invalid', ['avatar' => $avatar]);
return $fields;
}
$image = new Image($img_str, Images::getMimeTypeByData($img_str));
if (!$image->isValid()) {
Logger::debug('Avatar picture is invalid', ['avatar' => $avatar]);
return $fields;
}
$filename = self::getFilename($contact['url']);
$timestamp = time();
$fields['photo'] = self::storeAvatarCache($image, $filename, Proxy::PIXEL_SMALL, $timestamp);
$fields['thumb'] = self::storeAvatarCache($image, $filename, Proxy::PIXEL_THUMB, $timestamp);
$fields['micro'] = self::storeAvatarCache($image, $filename, Proxy::PIXEL_MICRO, $timestamp);
Logger::debug('Storing new avatar cache', ['uri-id' => $contact['uri-id'], 'fields' => $fields]);
return $fields;
}
public static function storeAvatarByImage(array $contact, Image $image): array
{
$fields = ['photo' => '', 'thumb' => '', 'micro' => ''];
if (!DI::config()->get('system', 'avatar_cache')) {
self::deleteCache($contact);
return $fields;
}
if (Network::isLocalLink($contact['avatar']) || empty($contact['avatar'])) {
self::deleteCache($contact);
return $fields;
}
$filename = self::getFilename($contact['url']);
$timestamp = time();
$fields['photo'] = self::storeAvatarCache($image, $filename, Proxy::PIXEL_SMALL, $timestamp);
$fields['thumb'] = self::storeAvatarCache($image, $filename, Proxy::PIXEL_THUMB, $timestamp);
$fields['micro'] = self::storeAvatarCache($image, $filename, Proxy::PIXEL_MICRO, $timestamp);
return $fields;
}
private static function getFilename(string $url)
{
$guid = Item::guidFromUri($url, parse_url($url, PHP_URL_HOST));
return substr($guid, 0, 2) . '/' . substr($guid, 3, 2) . '/' . substr($guid, 5, 3) . '/' .
substr($guid, 9, 2) .'/' . substr($guid, 11, 2) . '/' . substr($guid, 13, 4). '/' . substr($guid, 18) . '-';
}
private static function storeAvatarCache(Image $image, string $filename, int $size, int $timestamp): string
{
$image->scaleDown($size);
if (is_null($image) || !$image->isValid()) {
return '';
}
$path = self::BASE_PATH . $filename . $size . '.' . $image->getExt();
$filepath = DI::basePath() . $path;
$dirpath = DI::basePath() . self::BASE_PATH;
DI::profiler()->startRecording('file');
// Fetch the permission and group ownership of the "avatar" path and apply to all files
$dir_perm = fileperms($dirpath) & 0777;
$file_perm = fileperms($dirpath) & 0666;
$group = filegroup($dirpath);
// Check directory permissions of all parts of the path
foreach (explode('/', dirname($filename)) as $part) {
$dirpath .= $part . '/';
if (!file_exists($dirpath)) {
if (!mkdir($dirpath, $dir_perm)) {
Logger::warning('Directory could not be created', ['directory' => $dirpath]);
}
} elseif ((($old_perm = fileperms($dirpath) & 0777) != $dir_perm) && !chmod($dirpath, $dir_perm)) {
Logger::notice('Directory permissions could not be changed', ['directory' => $dirpath, 'old' => $old_perm, 'new' => $dir_perm]);
}
if ((($old_group = filegroup($dirpath)) != $group) && !chgrp($dirpath, $group)) {
Logger::notice('Directory group could not be changed', ['directory' => $dirpath, 'old' => $old_group, 'new' => $group]);
}
}
if (!file_put_contents($filepath, $image->asString())) {
Logger::warning('File could not be created', ['file' => $filepath]);
}
$old_perm = fileperms($filepath) & 0666;
$old_group = filegroup($filepath);
if (($old_perm != $file_perm) && !chmod($filepath, $file_perm)) {
Logger::notice('File permissions could not be changed', ['file' => $filepath, 'old' => $old_perm, 'new' => $file_perm]);
}
if (($old_group != $group) && !chgrp($filepath, $group)) {
Logger::notice('File group could not be changed', ['file' => $filepath, 'old' => $old_group, 'new' => $group]);
}
DI::profiler()->stopRecording();
if (!file_exists($filepath)) {
Logger::warning('Avatar cache file could not be stored', ['file' => $filepath]);
return '';
}
return DI::baseUrl() . $path . '?ts=' . $timestamp;
}
/**
* Check if the avatar cache file is locally stored
*
* @param string $avatar
* @return boolean
*/
private static function isCacheFile(string $avatar): bool
{
return !empty(self::getCacheFile($avatar));
}
/**
* Fetch the name of locally cached avatar pictures
*
* @param string $avatar
* @return string
*/
private static function getCacheFile(string $avatar): string
{
$parts = parse_url($avatar);
if (empty($parts['host']) || ($parts['host'] != DI::baseUrl()->getHostname())) {
return '';
}
$pos = strpos($parts['path'], DI::baseUrl()->getUrlPath() . self::BASE_PATH);
if ($pos !== 0) {
return '';
}
$filename = DI::basePath() . $parts['path'];
DI::profiler()->startRecording('file');
$exists = file_exists($filename);
DI::profiler()->stopRecording();
if (!$exists) {
return '';
}
return $filename;
}
/**
* Delete locally cached avatar pictures of a contact
*
* @param string $avatar
* @return void
*/
public static function deleteCache(array $contact)
{
self::deleteCacheFile($contact['photo']);
self::deleteCacheFile($contact['thumb']);
self::deleteCacheFile($contact['micro']);
}
/**
* Delete a locally cached avatar picture
*
* @param string $avatar
* @return void
*/
private static function deleteCacheFile(string $avatar)
{
$localFile = self::getCacheFile($avatar);
if (!empty($localFile)) {
unlink($localFile);
Logger::debug('Unlink avatar', ['avatar' => $avatar]);
}
}
}

View File

@ -184,7 +184,7 @@ class ContactSelector
* @return string
* @throws \Exception
*/
public static function networkToIcon($network, $profile = "")
public static function networkToIcon($network, $profile = "", $gsid = 0)
{
$nets = [
Protocol::DFRN => 'friendica',
@ -218,7 +218,14 @@ class ContactSelector
$network_icon = str_replace($search, $replace, $network);
if ((in_array($network, Protocol::FEDERATED)) && ($profile != "")) {
$gserver = self::getServerForProfile($profile);
if (!empty($gsid) && !empty(self::$serverdata[$gsid])) {
$gserver = self::$serverdata[$gsid];
} elseif (!empty($gsid)) {
$gserver = DBA::selectFirst('gserver', ['platform', 'network'], ['id' => $gsid]);
self::$serverdata[$gsid] = $gserver;
} else {
$gserver = self::getServerForProfile($profile);
}
if (!empty($gserver['platform'])) {
$network_icon = $platform_icons[strtolower($gserver['platform'])] ?? $network_icon;
}

View File

@ -47,7 +47,6 @@ use Friendica\Protocol\Activity;
use Friendica\Util\Crypto;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Profiler;
use Friendica\Util\Proxy;
use Friendica\Util\Strings;
use Friendica\Util\Temporal;
use Psr\Log\LoggerInterface;
@ -108,6 +107,8 @@ class Conversation
*/
public function builtinActivityPuller(array $activity, array &$conv_responses)
{
$thread_parent = $activity['thr-parent-row'] ?? [];
foreach ($conv_responses as $mode => $v) {
$sparkle = '';
@ -152,9 +153,8 @@ class Conversation
$activity['thr-parent-id'] = $activity['parent-uri-id'];
}
// Skip when the causer of the parent is the same than the author of the announce
if (($verb == Activity::ANNOUNCE) && Post::exists(['uri-id' => $activity['thr-parent-id'],
'uid' => $activity['uid'], 'causer-id' => $activity['author-id'], 'gravity' => [GRAVITY_PARENT, GRAVITY_COMMENT]])) {
// Skip when the causer of the parent is the same as the author of the announce
if (($verb == Activity::ANNOUNCE) && !empty($thread_parent['causer-id'] && ($thread_parent['causer-id'] == $activity['author-id']))) {
continue;
}
@ -441,7 +441,7 @@ class Conversation
$previewing = (($preview) ? ' preview ' : '');
if ($mode === 'network') {
$items = $this->addChildren($items, false, $order, $uid);
$items = $this->addChildren($items, false, $order, $uid, $mode);
if (!$update) {
/*
* The special div is needed for liveUpdate to kick in for this page.
@ -467,7 +467,7 @@ class Conversation
. "'; </script>\r\n";
}
} elseif ($mode === 'profile') {
$items = $this->addChildren($items, false, $order, $uid);
$items = $this->addChildren($items, false, $order, $uid, $mode);
if (!$update) {
$tab = !empty($_GET['tab']) ? trim($_GET['tab']) : 'posts';
@ -484,7 +484,7 @@ class Conversation
}
}
} elseif ($mode === 'notes') {
$items = $this->addChildren($items, false, $order, local_user());
$items = $this->addChildren($items, false, $order, local_user(), $mode);
if (!$update) {
$live_update_div = '<div id="live-notes"></div>' . "\r\n"
@ -492,7 +492,7 @@ class Conversation
. "; var netargs = '/?f='; </script>\r\n";
}
} elseif ($mode === 'display') {
$items = $this->addChildren($items, false, $order, $uid);
$items = $this->addChildren($items, false, $order, $uid, $mode);
if (!$update) {
$live_update_div = '<div id="live-display"></div>' . "\r\n"
@ -500,7 +500,7 @@ class Conversation
. "</script>";
}
} elseif ($mode === 'community') {
$items = $this->addChildren($items, true, $order, $uid);
$items = $this->addChildren($items, true, $order, $uid, $mode);
if (!$update) {
$live_update_div = '<div id="live-community"></div>' . "\r\n"
@ -510,7 +510,7 @@ class Conversation
. "'; </script>\r\n";
}
} elseif ($mode === 'contacts') {
$items = $this->addChildren($items, false, $order, $uid);
$items = $this->addChildren($items, false, $order, $uid, $mode);
if (!$update) {
$live_update_div = '<div id="live-contact"></div>' . "\r\n"
@ -667,15 +667,15 @@ class Conversation
'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']),
'network_name' => ContactSelector::networkToName($item['author-network'], $item['author-link'], $item['network'], $item['author-gsid']),
'network_icon' => ContactSelector::networkToIcon($item['network'], $item['author-link'], $item['author-gsid']),
'linktitle' => $this->l10n->t('View %s\'s profile @ %s', $profile_name, $item['author-link']),
'profile_url' => $profile_link,
'item_photo_menu_html' => $this->item->photoMenu($item, $formSecurityToken),
'name' => $profile_name,
'sparkle' => $sparkle,
'lock' => false,
'thumb' => $this->baseURL->remove(Contact::getAvatarUrlForUrl($item['author-link'], $item['uid'], Proxy::SIZE_THUMB)),
'thumb' => $this->baseURL->remove($this->item->getAuthorAvatar($item)),
'title' => $title,
'body_html' => $body_html,
'tags' => $tags['tags'],
@ -696,7 +696,7 @@ class Conversation
'indent' => '',
'owner_name' => '',
'owner_url' => '',
'owner_photo' => $this->baseURL->remove(Contact::getAvatarUrlForUrl($item['owner-link'], $item['uid'], Proxy::SIZE_THUMB)),
'owner_photo' => $this->baseURL->remove($this->item->getOwnerAvatar($item)),
'plink' => ItemModel::getPlink($item),
'edpost' => false,
'pinned' => $pinned,
@ -809,12 +809,13 @@ class Conversation
/**
* Adds some information (Causer, post reason, direction) to the fetched post row.
*
* @param array $row Post row
* @param array $activity Contact data of the resharer
* @param array $row Post row
* @param array $activity Contact data of the resharer
* @param array $thr_parent Thread parent row
*
* @return array items with parents and comments
*/
private function addRowInformation(array $row, array $activity)
private function addRowInformation(array $row, array $activity, array $thr_parent)
{
$this->profiler->startRecording('rendering');
@ -889,6 +890,8 @@ class Conversation
break;
}
$row['thr-parent-row'] = $thr_parent;
$this->profiler->stopRecording();
return $row;
}
@ -899,15 +902,15 @@ class Conversation
* The system will fetch the comments for the local user whenever possible.
* This behaviour is currently needed to allow commenting on Friendica posts.
*
* @param array $parents Parent items
*
* @param $block_authors
* @param $order
* @param $uid
* @param array $parents Parent items
* @param bool $block_authors
* @param bool $order
* @param int $uid
* @param string $mode
* @return array items with parents and comments
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
private function addChildren(array $parents, $block_authors, $order, $uid)
private function addChildren(array $parents, bool $block_authors, string $order, int $uid, string $mode)
{
$this->profiler->startRecording('rendering');
if (count($parents) > 1) {
@ -916,8 +919,6 @@ class Conversation
$max_comments = $this->config->get('system', 'max_display_comments', 1000);
}
$params = ['order' => ['uri-id' => true, 'uid' => true]];
$activities = [];
$uriids = [];
$commentcounter = [];
@ -951,6 +952,17 @@ class Conversation
$condition = DBA::mergeConditions($condition,
["`uid` IN (0, ?) AND (`vid` != ? OR `vid` IS NULL)", $uid, Verb::getID(Activity::FOLLOW)]);
$thread_parents = Post::select(['uri-id', 'causer-id'], $condition, ['order' => ['uri-id' => false, 'uid']]);
$thr_parent = [];
while ($row = Post::fetch($thread_parents)) {
$thr_parent[$row['uri-id']] = $row;
}
DBA::close($thread_parents);
$params = ['order' => ['uri-id' => true, 'uid' => true]];
$thread_items = Post::selectForUser($uid, array_merge(ItemModel::DISPLAY_FIELDLIST, ['featured', 'contact-uid', 'gravity', 'post-type', 'post-reason']), $condition, $params);
$items = [];
@ -960,6 +972,10 @@ class Conversation
continue;
}
if (($mode != 'contacts') && !$row['origin']) {
$row['featured'] = false;
}
if ($max_comments > 0) {
if (($row['gravity'] == GRAVITY_COMMENT) && (++$commentcounter[$row['parent-uri-id']] > $max_comments)) {
continue;
@ -968,7 +984,8 @@ class Conversation
continue;
}
}
$items[$row['uri-id']] = $this->addRowInformation($row, $activities[$row['uri-id']] ?? []);
$items[$row['uri-id']] = $this->addRowInformation($row, $activities[$row['uri-id']] ?? [], $thr_parent[$row['thr-parent-id']] ?? []);
}
DBA::close($thread_items);

View File

@ -26,16 +26,16 @@ use Friendica\Core\Hook;
use Friendica\Core\L10n;
use Friendica\Core\Logger;
use Friendica\Core\Protocol;
use Friendica\Core\Session;
use Friendica\Database\DBA;
use Friendica\Model\Contact;
use Friendica\Model\Group;
use Friendica\Model\Item as ModelItem;
use Friendica\Model\Photo;
use Friendica\Model\Tag;
use Friendica\Model\Post;
use Friendica\Protocol\Activity;
use Friendica\Util\Profiler;
use Friendica\Util\Strings;
use Friendica\Util\Proxy;
use Friendica\Util\XML;
/**
@ -93,6 +93,10 @@ class Item
$uid = $item['uid'] ?: $uid;
if (empty($item['has-categories'])) {
return [$categories, $folders];
}
foreach (Post\Category::getArrayByURIId($item['uri-id'], $uid, Post\Category::CATEGORY) as $savedFolderName) {
if (!empty($item['author-link'])) {
$url = $item['author-link'] . "?category=" . rawurlencode($savedFolderName);
@ -353,22 +357,6 @@ class Item
}
}
$matches = null;
if (preg_match_all('/@\[url=(.*?)\]/is', $item['body'], $matches, PREG_SET_ORDER)) {
foreach ($matches as $mtch) {
if (!strpos($mtch[1], 'zrl=')) {
$item['body'] = str_replace($mtch[0], '@[url=' . Contact::magicLink($mtch[1]) . ']', $item['body']);
}
}
}
// add sparkle links to appropriate permalinks
// Only create a redirection to a magic link when logged in
if (!empty($item['plink']) && Session::isAuthenticated() && $item['private'] == ModelItem::PRIVATE) {
$author = ['uid' => 0, 'id' => $item['author-id'],
'network' => $item['author-network'], 'url' => $item['author-link']];
$item['plink'] = Contact::magicLinkByContact($author, $item['plink']);
}
$this->profiler->stopRecording();
}
@ -398,7 +386,7 @@ class Item
$pcid = $item['author-id'];
$network = '';
$rel = 0;
$condition = ['uid' => local_user(), 'nurl' => Strings::normaliseLink($item['author-link'])];
$condition = ['uid' => local_user(), 'uri-id' => $item['author-uri-id']];
$contact = DBA::selectFirst('contact', ['id', 'network', 'rel'], $condition);
if (DBA::isResult($contact)) {
$cid = $contact['id'];
@ -577,4 +565,43 @@ class Item
}
return $item;
}
public function getAuthorAvatar(array $item): string
{
if (in_array($item['network'], [Protocol::FEED, Protocol::MAIL])) {
$author_avatar = $item['contact-id'];
$author_updated = '';
$author_thumb = $item['contact-avatar'];
} else {
$author_avatar = $item['author-id'];
$author_updated = $item['author-updated'];
$author_thumb = $item['author-avatar'];
}
if (empty($author_thumb) || Photo::isPhotoURI($author_thumb)) {
$author_thumb = Contact::getAvatarUrlForId($author_avatar, Proxy::SIZE_THUMB, $author_updated);
}
return $author_thumb;
}
public function getOwnerAvatar(array $item): string
{
if (in_array($item['network'], [Protocol::FEED, Protocol::MAIL])) {
$owner_avatar = $item['contact-id'];
$owner_updated = '';
$owner_thumb = $item['contact-avatar'];
} else {
$owner_avatar = $item['owner-id'];
$owner_updated = $item['owner-updated'];
$owner_thumb = $item['owner-avatar'];
}
if (empty($owner_thumb) || Photo::isPhotoURI($owner_thumb)) {
$owner_thumb = Contact::getAvatarUrlForId($owner_avatar, Proxy::SIZE_THUMB, $owner_updated);
}
return $owner_thumb;
}
}

View File

@ -1952,17 +1952,20 @@ class BBCode
* - #[url=<anything>]<term>[/url]
* - [url=<anything>]#<term>[/url]
*/
$text = preg_replace_callback("/(?:#\[url\=[^\[\]]*\]|\[url\=[^\[\]]*\]#)(.*?)\[\/url\]/ism", function($matches) use ($simple_html) {
if ($simple_html == self::ACTIVITYPUB) {
return '<a href="' . DI::baseUrl() . '/search?tag=' . rawurlencode($matches[1])
. '" data-tag="' . XML::escape($matches[1]) . '" rel="tag ugc">#'
. XML::escape($matches[1]) . '</a>';
} else {
return '#<a href="' . DI::baseUrl() . '/search?tag=' . rawurlencode($matches[1])
. '" class="tag" rel="tag" title="' . XML::escape($matches[1]) . '">'
. XML::escape($matches[1]) . '</a>';
}
}, $text);
self::performWithEscapedTags($text, ['url', 'share'], function ($text) use ($simple_html) {
$text = preg_replace_callback("/(?:#\[url\=[^\[\]]*\]|\[url\=[^\[\]]*\]#)(.*?)\[\/url\]/ism", function($matches) use ($simple_html) {
if ($simple_html == self::ACTIVITYPUB) {
return '<a href="' . DI::baseUrl() . '/search?tag=' . rawurlencode($matches[1])
. '" data-tag="' . XML::escape($matches[1]) . '" rel="tag ugc">#'
. XML::escape($matches[1]) . '</a>';
} else {
return '#<a href="' . DI::baseUrl() . '/search?tag=' . rawurlencode($matches[1])
. '" class="tag" rel="tag" title="' . XML::escape($matches[1]) . '">'
. XML::escape($matches[1]) . '</a>';
}
}, $text);
return $text;
});
// We need no target="_blank" rel="noopener noreferrer" for local links
// convert links start with DI::baseUrl() as local link without the target="_blank" rel="noopener noreferrer" attribute
@ -2086,11 +2089,15 @@ class BBCode
* @param string $text The text with BBCode
* @return string The same text - but without "abstract" element
*/
public static function stripAbstract($text)
public static function stripAbstract(string $text): string
{
DI::profiler()->startRecording('rendering');
$text = preg_replace("/[\s|\n]*\[abstract\].*?\[\/abstract\][\s|\n]*/ism", ' ', $text);
$text = preg_replace("/[\s|\n]*\[abstract=.*?\].*?\[\/abstract][\s|\n]*/ism", ' ', $text);
$text = BBCode::performWithEscapedTags($text, ['code', 'noparse', 'nobb', 'pre'], function ($text) {
$text = preg_replace("/[\s|\n]*\[abstract\].*?\[\/abstract\][\s|\n]*/ism", ' ', $text);
$text = preg_replace("/[\s|\n]*\[abstract=.*?\].*?\[\/abstract][\s|\n]*/ism", ' ', $text);
return $text;
});
DI::profiler()->stopRecording();
return $text;
@ -2099,30 +2106,26 @@ class BBCode
/**
* Returns the value of the "abstract" element
*
* @param string $text The text that maybe contains the element
* @param string $text The text that maybe contains the element
* @param string $addon The addon for which the abstract is meant for
* @return string The abstract
*/
public static function getAbstract($text, $addon = '')
public static function getAbstract(string $text, string $addon = ''): string
{
DI::profiler()->startRecording('rendering');
$abstract = '';
$abstracts = [];
$addon = strtolower($addon);
if (preg_match_all("/\[abstract=(.*?)\](.*?)\[\/abstract\]/ism", $text, $results, PREG_SET_ORDER)) {
foreach ($results as $result) {
$abstracts[strtolower($result[1])] = $result[2];
$abstract = BBCode::performWithEscapedTags($text, ['code', 'noparse', 'nobb', 'pre'], function ($text) use ($addon) {
if ($addon && preg_match('#\[abstract=' . preg_quote($addon, '#') . '](.*?)\[/abstract]#ism', $text, $matches)) {
return $matches[1];
}
}
if (isset($abstracts[$addon])) {
$abstract = $abstracts[$addon];
}
if (preg_match("#\[abstract](.*?)\[/abstract]#ism", $text, $matches)) {
return $matches[1];
}
if ($abstract == '' && preg_match("/\[abstract\](.*?)\[\/abstract\]/ism", $text, $result)) {
$abstract = $result[1];
}
return '';
});
DI::profiler()->stopRecording();
return $abstract;
@ -2337,11 +2340,9 @@ class BBCode
* @param array $tagList A list of tag names, e.g ['noparse', 'nobb', 'pre']
* @param callable $callback
* @return string
* @throws Exception
*@see Strings::performWithEscapedBlocks
*
* @see Strings::performWithEscapedBlocks
*/
public static function performWithEscapedTags(string $text, array $tagList, callable $callback)
public static function performWithEscapedTags(string $text, array $tagList, callable $callback): string
{
$tagList = array_map('preg_quote', $tagList);

View File

@ -59,11 +59,13 @@ Commands:
autoinstall Starts automatic installation of friendica based on values from htconfig.php
lock Edit site locks
maintenance Set maintenance mode for this node
movetoavatarcache Move cached avatars to the file based avatar cache
user User management
php2po Generate a messages.po file from a strings.php file
po2php Generate a strings.php file from a messages.po file
typo Checks for parse errors in Friendica files
postupdate Execute pending post update scripts (can last days)
relocate Update node base URL
serverblock Manage blocked servers
storage Manage storage backend
relay Manage ActivityPub relay servers
@ -91,10 +93,12 @@ HELP;
'globalcommunitysilence' => Friendica\Console\GlobalCommunitySilence::class,
'lock' => Friendica\Console\Lock::class,
'maintenance' => Friendica\Console\Maintenance::class,
'movetoavatarcache' => Friendica\Console\MoveToAvatarCache::class,
'php2po' => Friendica\Console\PhpToPo::class,
'postupdate' => Friendica\Console\PostUpdate::class,
'po2php' => Friendica\Console\PoToPhp::class,
'relay' => Friendica\Console\Relay::class,
'relocate' => Friendica\Console\Relocate::class,
'serverblock' => Friendica\Console\ServerBlock::class,
'storage' => Friendica\Console\Storage::class,
'test' => Friendica\Console\Test::class,

View File

@ -40,6 +40,7 @@ class L10n
'bg' => 'Български',
'ca' => 'Català',
'cs' => 'Česky',
'da-dk' => 'Dansk (Danmark)',
'de' => 'Deutsch',
'en-gb' => 'English (United Kingdom)',
'en-us' => 'English (United States)',

View File

@ -295,7 +295,7 @@ class System
DI::apiResponse()->addContent(XML::fromArray(["result" => $result], $xml));
DI::page()->exit(DI::apiResponse()->generate());
exit();
self::exit();
}
/**
@ -315,7 +315,7 @@ class System
DI::apiResponse()->addContent($content);
DI::page()->exit(DI::apiResponse()->generate());
exit();
self::exit();
}
/**
@ -331,7 +331,8 @@ class System
DI::apiResponse()->setType($responce, $content_type);
DI::apiResponse()->addContent($content);
DI::page()->exit(DI::apiResponse()->generate());
exit();
self::exit();
}
public static function jsonError($httpCode, $content, $content_type = 'application/json')
@ -359,6 +360,16 @@ class System
DI::apiResponse()->setType(Response::TYPE_JSON, $content_type);
DI::apiResponse()->addContent(json_encode($content, $options));
DI::page()->exit(DI::apiResponse()->generate());
self::exit();
}
/**
* Exit the program execution.
*/
public static function exit()
{
DI::page()->logRuntime(DI::config());
exit();
}
@ -448,8 +459,7 @@ class System
case 307:
throw new TemporaryRedirectException();
}
exit();
self::exit();
}
/**
@ -522,7 +532,7 @@ class System
echo str_replace("\t", " ", $o);
echo "</section>";
echo "</body></html>\r\n";
exit();
self::exit();
}
/**

View File

@ -21,8 +21,6 @@
namespace Friendica\Core;
use Friendica\App\Mode;
use Friendica\Core;
use Friendica\Core\Worker\Entity\Process;
use Friendica\Database\DBA;
use Friendica\DI;
@ -51,7 +49,6 @@ class Worker
private static $lock_duration = 0;
private static $last_update;
private static $state;
private static $daemon_mode = null;
/** @var Process */
private static $process;
@ -80,7 +77,7 @@ class Worker
$last_cleanup = DI::config()->get('system', 'worker_last_cleaned', 0);
if (time() > ($last_cleanup + 300)) {
DI::config()->set('system', 'worker_last_cleaned', time());
self::killStaleWorkers();
Worker\Cron::killStaleWorkers();
}
// Check if the system is ready
@ -90,7 +87,7 @@ class Worker
// Now we start additional cron processes if we should do so
if ($run_cron) {
self::runCron();
Worker\Cron::run();
}
$last_check = $starttime = time();
@ -98,16 +95,13 @@ class Worker
// We fetch the next queue entry that is about to be executed
while ($r = self::workerProcess()) {
if (self::IPCJobsExists(getmypid())) {
self::IPCDeleteJobState(getmypid());
if (Worker\IPC::JobsExists(getmypid())) {
Worker\IPC::DeleteJobState(getmypid());
}
// Don't refetch when a worker fetches tasks for multiple workers
$refetched = DI::config()->get('system', 'worker_multiple_fetch');
foreach ($r as $entry) {
// Assure that the priority is an integer value
$entry['priority'] = (int)$entry['priority'];
// The work will be done
if (!self::execute($entry)) {
Logger::notice('Process execution failed, quitting.');
@ -152,8 +146,8 @@ class Worker
if (time() > ($starttime + (DI::config()->get('system', 'cron_interval') * 60))) {
Logger::info('Process lifetime reached, respawning.');
self::unclaimProcess($process);
if (self::isDaemonMode()) {
self::IPCSetJobState(true);
if (Worker\Daemon::isMode()) {
Worker\IPC::SetJobState(true);
} else {
self::spawnWorker();
}
@ -162,8 +156,8 @@ class Worker
}
// Cleaning up. Possibly not needed, but it doesn't harm anything.
if (self::isDaemonMode()) {
self::IPCSetJobState(false);
if (Worker\Daemon::isMode()) {
Worker\IPC::SetJobState(false);
}
Logger::info("Couldn't select a workerqueue entry, quitting process", ['pid' => getmypid()]);
}
@ -261,7 +255,7 @@ class Worker
$workerqueue = DBA::selectFirst('workerqueue', ['priority'], $condition, ['order' => ['priority']]);
self::$db_duration += (microtime(true) - $stamp);
if (DBA::isResult($workerqueue)) {
return $workerqueue["priority"];
return $workerqueue['priority'];
} else {
return 0;
}
@ -284,41 +278,41 @@ class Worker
/**
* Checks if the given file is valid to be included
*
* @param mixed $file
* @return bool
* @param mixed $file
* @return bool
*/
private static function validateInclude(&$file)
{
$orig_file = $file;
$file = realpath($file);
if (strpos($file, getcwd()) !== 0) {
return false;
}
$file = str_replace(getcwd() . "/", "", $file, $count);
if ($count != 1) {
return false;
}
if ($orig_file !== $file) {
return false;
}
$valid = false;
if (strpos($file, "include/") === 0) {
$valid = true;
}
if (strpos($file, "addon/") === 0) {
$valid = true;
}
// Simply return flag
return $valid;
}
/**
* Execute a worker entry
*
@ -466,13 +460,13 @@ class Worker
$cooldown = DI::config()->get("system", "worker_cooldown", 0);
if ($cooldown > 0) {
Logger::info('Pre execution cooldown.', ['priority' => $queue["priority"], 'id' => $queue["id"], 'cooldown' => $cooldown]);
Logger::info('Pre execution cooldown.', ['priority' => $queue['priority'], 'id' => $queue["id"], 'cooldown' => $cooldown]);
sleep($cooldown);
}
Logger::enableWorker($funcname);
Logger::info("Process start.", ['priority' => $queue["priority"], 'id' => $queue["id"]]);
Logger::info("Process start.", ['priority' => $queue['priority'], 'id' => $queue["id"]]);
$stamp = (float)microtime(true);
@ -480,11 +474,6 @@ class Worker
// For this reason the variables have to be initialized.
DI::profiler()->reset();
if (!in_array($queue['priority'], PRIORITIES)) {
Logger::warning('Invalid priority', ['queue' => $queue, 'callstack' => System::callstack(20)]);
$queue['priority'] = PRIORITY_MEDIUM;
}
$a->setQueue($queue);
$up_duration = microtime(true) - self::$up_start;
@ -529,21 +518,21 @@ class Worker
self::$lock_duration = 0;
if ($duration > 3600) {
Logger::info('Longer than 1 hour.', ['priority' => $queue["priority"], 'id' => $queue["id"], 'duration' => round($duration/60, 3)]);
Logger::info('Longer than 1 hour.', ['priority' => $queue['priority'], 'id' => $queue["id"], 'duration' => round($duration/60, 3)]);
} elseif ($duration > 600) {
Logger::info('Longer than 10 minutes.', ['priority' => $queue["priority"], 'id' => $queue["id"], 'duration' => round($duration/60, 3)]);
Logger::info('Longer than 10 minutes.', ['priority' => $queue['priority'], 'id' => $queue["id"], 'duration' => round($duration/60, 3)]);
} elseif ($duration > 300) {
Logger::info('Longer than 5 minutes.', ['priority' => $queue["priority"], 'id' => $queue["id"], 'duration' => round($duration/60, 3)]);
Logger::info('Longer than 5 minutes.', ['priority' => $queue['priority'], 'id' => $queue["id"], 'duration' => round($duration/60, 3)]);
} elseif ($duration > 120) {
Logger::info('Longer than 2 minutes.', ['priority' => $queue["priority"], 'id' => $queue["id"], 'duration' => round($duration/60, 3)]);
Logger::info('Longer than 2 minutes.', ['priority' => $queue['priority'], 'id' => $queue["id"], 'duration' => round($duration/60, 3)]);
}
Logger::info('Process done.', ['priority' => $queue["priority"], 'id' => $queue["id"], 'duration' => round($duration, 3)]);
Logger::info('Process done.', ['priority' => $queue['priority'], 'id' => $queue["id"], 'duration' => round($duration, 3)]);
DI::profiler()->saveLog(DI::logger(), "ID " . $queue["id"] . ": " . $funcname);
if ($cooldown > 0) {
Logger::info('Post execution cooldown.', ['priority' => $queue["priority"], 'id' => $queue["id"], 'cooldown' => $cooldown]);
Logger::info('Post execution cooldown.', ['priority' => $queue['priority'], 'id' => $queue["id"], 'cooldown' => $cooldown]);
sleep($cooldown);
}
}
@ -631,87 +620,6 @@ class Worker
return true;
}
/**
* fix the queue entry if the worker process died
*
* @return void
* @throws \Exception
*/
private static function killStaleWorkers()
{
$stamp = (float)microtime(true);
$entries = DBA::select(
'workerqueue',
['id', 'pid', 'executed', 'priority', 'command', 'parameter'],
['NOT `done` AND `pid` != 0'],
['order' => ['priority', 'retrial', 'created']]
);
self::$db_duration += (microtime(true) - $stamp);
while ($entry = DBA::fetch($entries)) {
if (!posix_kill($entry["pid"], 0)) {
$stamp = (float)microtime(true);
DBA::update(
'workerqueue',
['executed' => DBA::NULL_DATETIME, 'pid' => 0],
['id' => $entry["id"]]
);
self::$db_duration += (microtime(true) - $stamp);
self::$db_duration_write += (microtime(true) - $stamp);
} else {
// Kill long running processes
// Check if the priority is in a valid range
if (!in_array($entry["priority"], [PRIORITY_CRITICAL, PRIORITY_HIGH, PRIORITY_MEDIUM, PRIORITY_LOW, PRIORITY_NEGLIGIBLE])) {
$entry["priority"] = PRIORITY_MEDIUM;
}
// Define the maximum durations
$max_duration_defaults = [PRIORITY_CRITICAL => 720, PRIORITY_HIGH => 10, PRIORITY_MEDIUM => 60, PRIORITY_LOW => 180, PRIORITY_NEGLIGIBLE => 720];
$max_duration = $max_duration_defaults[$entry["priority"]];
$argv = json_decode($entry['parameter'], true);
if (!empty($entry['command'])) {
$command = $entry['command'];
} elseif (!empty($argv)) {
$command = array_shift($argv);
} else {
return;
}
$command = basename($command);
// How long is the process already running?
$duration = (time() - strtotime($entry["executed"])) / 60;
if ($duration > $max_duration) {
Logger::notice('Worker process took too much time - killed', ['duration' => number_format($duration, 3), 'max' => $max_duration, 'id' => $entry["id"], 'pid' => $entry["pid"], 'command' => $command]);
posix_kill($entry["pid"], SIGTERM);
// We killed the stale process.
// To avoid a blocking situation we reschedule the process at the beginning of the queue.
// Additionally we are lowering the priority. (But not PRIORITY_CRITICAL)
$new_priority = $entry["priority"];
if ($entry["priority"] == PRIORITY_HIGH) {
$new_priority = PRIORITY_MEDIUM;
} elseif ($entry["priority"] == PRIORITY_MEDIUM) {
$new_priority = PRIORITY_LOW;
} elseif ($entry["priority"] != PRIORITY_CRITICAL) {
$new_priority = PRIORITY_NEGLIGIBLE;
}
$stamp = (float)microtime(true);
DBA::update(
'workerqueue',
['executed' => DBA::NULL_DATETIME, 'created' => DateTimeFormat::utcNow(), 'priority' => $new_priority, 'pid' => 0],
['id' => $entry["id"]]
);
self::$db_duration += (microtime(true) - $stamp);
self::$db_duration_write += (microtime(true) - $stamp);
} else {
Logger::info('Process runtime is okay', ['duration' => number_format($duration, 3), 'max' => $max_duration, 'id' => $entry["id"], 'pid' => $entry["pid"], 'command' => $command]);
}
}
}
DBA::close($entries);
}
/**
* Checks if the number of active workers exceeds the given limits
@ -778,12 +686,12 @@ class Worker
self::$db_duration_stat += (microtime(true) - $stamp);
while ($entry = DBA::fetch($jobs)) {
$stamp = (float)microtime(true);
$running = DBA::count('workerqueue-view', ['priority' => $entry["priority"]]);
$running = DBA::count('workerqueue-view', ['priority' => $entry['priority']]);
self::$db_duration += (microtime(true) - $stamp);
self::$db_duration_stat += (microtime(true) - $stamp);
$idle_workers -= $running;
$waiting_processes += $entry["entries"];
$listitem[$entry["priority"]] = $entry["priority"] . ":" . $running . "/" . $entry["entries"];
$listitem[$entry['priority']] = $entry['priority'] . ":" . $running . "/" . $entry["entries"];
}
DBA::close($jobs);
} else {
@ -795,7 +703,7 @@ class Worker
while ($entry = DBA::fetch($jobs)) {
$idle_workers -= $entry["running"];
$listitem[$entry["priority"]] = $entry["priority"].":".$entry["running"];
$listitem[$entry['priority']] = $entry['priority'].":".$entry["running"];
}
DBA::close($jobs);
}
@ -821,8 +729,8 @@ class Worker
// Are there fewer workers running as possible? Then fork a new one.
if (!DI::config()->get("system", "worker_dont_fork", false) && ($queues > ($active + 1)) && self::entriesExists()) {
Logger::info("There are fewer workers as possible, fork a new worker.", ['active' => $active, 'queues' => $queues]);
if (self::isDaemonMode()) {
self::IPCSetJobState(true);
if (Worker\Daemon::isMode()) {
Worker\IPC::SetJobState(true);
} else {
self::spawnWorker();
}
@ -830,8 +738,8 @@ class Worker
}
// if there are too much worker, we don't spawn a new one.
if (self::isDaemonMode() && ($active > $queues)) {
self::IPCSetJobState(false);
if (Worker\Daemon::isMode() && ($active > $queues)) {
Worker\IPC::SetJobState(false);
}
return $active > $queues;
@ -1122,26 +1030,6 @@ class Worker
self::$db_duration_write += (microtime(true) - $stamp);
}
/**
* Runs the cron processes
*
* @return void
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
private static function runCron()
{
Logger::info('Add cron entries');
// Check for spooled items
self::add(['priority' => PRIORITY_HIGH, 'force_priority' => true], 'SpoolPost');
// Run the cron job that calls all other jobs
self::add(['priority' => PRIORITY_MEDIUM, 'force_priority' => true], 'Cron');
// Cleaning dead processes
self::killStaleWorkers();
}
/**
* Fork a child process
*
@ -1167,11 +1055,11 @@ class Worker
// The parent process continues here
DBA::connect();
self::IPCSetJobState(true, $pid);
Worker\IPC::SetJobState(true, $pid);
Logger::info('Spawned new worker', ['pid' => $pid]);
$cycles = 0;
while (self::IPCJobsExists($pid) && (++$cycles < 100)) {
while (Worker\IPC::JobsExists($pid) && (++$cycles < 100)) {
usleep(10000);
}
@ -1186,7 +1074,7 @@ class Worker
$process = DI::process()->create(getmypid(), basename(__FILE__));
$cycles = 0;
while (!self::IPCJobsExists($process->pid) && (++$cycles < 100)) {
while (!Worker\IPC::JobsExists($process->pid) && (++$cycles < 100)) {
usleep(10000);
}
@ -1196,7 +1084,7 @@ class Worker
self::unclaimProcess($process);
self::IPCSetJobState(false, $process->pid);
Worker\IPC::SetJobState(false, $process->pid);
DI::process()->delete($process);
Logger::info('Worker ended', ['pid' => $process->pid]);
exit();
@ -1211,13 +1099,13 @@ class Worker
*/
public static function spawnWorker($do_cron = false)
{
if (self::isDaemonMode() && DI::config()->get('system', 'worker_fork')) {
if (Worker\Daemon::isMode() && DI::config()->get('system', 'worker_fork')) {
self::forkProcess($do_cron);
} else {
DI::system()->run('bin/worker.php', ['no_cron' => !$do_cron]);
}
if (self::isDaemonMode()) {
self::IPCSetJobState(false);
if (Worker\Daemon::isMode()) {
Worker\IPC::SetJobState(false);
}
}
@ -1287,7 +1175,7 @@ class Worker
$found = DBA::exists('workerqueue', ['command' => $command, 'parameter' => $parameters, 'done' => false]);
$added = 0;
if (!in_array($priority, PRIORITIES)) {
if (!is_int($priority) || !in_array($priority, PRIORITIES)) {
Logger::warning('Invalid priority', ['priority' => $priority, 'command' => $command, 'callstack' => System::callstack(20)]);
$priority = PRIORITY_MEDIUM;
}
@ -1308,11 +1196,11 @@ class Worker
}
// Set the IPC flag to ensure an immediate process execution via daemon
if (self::isDaemonMode()) {
self::IPCSetJobState(true);
if (Worker\Daemon::isMode()) {
Worker\IPC::SetJobState(true);
}
self::checkDaemonState();
Worker\Daemon::checkState();
// Should we quit and wait for the worker to be called as a cronjob?
if ($dont_fork) {
@ -1333,7 +1221,7 @@ class Worker
}
// Quit on daemon mode
if (self::isDaemonMode()) {
if (Worker\Daemon::isMode()) {
return $added;
}
@ -1423,159 +1311,6 @@ class Worker
return true;
}
/**
* Set the flag if some job is waiting
*
* @param boolean $jobs Is there a waiting job?
* @param int $key Key number
* @throws \Exception
*/
public static function IPCSetJobState(bool $jobs, int $key = 0)
{
$stamp = (float)microtime(true);
DBA::replace('worker-ipc', ['jobs' => $jobs, 'key' => $key]);
self::$db_duration += (microtime(true) - $stamp);
self::$db_duration_write += (microtime(true) - $stamp);
}
/**
* Delete a key entry
*
* @param int $key Key number
* @throws \Exception
*/
public static function IPCDeleteJobState(int $key)
{
$stamp = (float)microtime(true);
DBA::delete('worker-ipc', ['key' => $key]);
self::$db_duration += (microtime(true) - $stamp);
self::$db_duration_write += (microtime(true) - $stamp);
}
/**
* Checks if some worker job waits to be executed
*
* @param int $key Key number
* @return bool
* @throws \Exception
*/
public static function IPCJobsExists(int $key = 0)
{
$stamp = (float)microtime(true);
$row = DBA::selectFirst('worker-ipc', ['jobs'], ['key' => $key]);
self::$db_duration += (microtime(true) - $stamp);
// When we don't have a row, no job is running
if (!DBA::isResult($row)) {
return false;
}
return (bool)$row['jobs'];
}
/**
* Checks if the worker is running in the daemon mode.
*
* @return boolean
*/
public static function isDaemonMode()
{
if (!is_null(self::$daemon_mode)) {
return self::$daemon_mode;
}
if (DI::mode()->getExecutor() == Mode::DAEMON) {
return true;
}
$daemon_mode = DI::config()->get('system', 'worker_daemon_mode', false, true);
if ($daemon_mode) {
return $daemon_mode;
}
if (!function_exists('pcntl_fork')) {
self::$daemon_mode = false;
return false;
}
$pidfile = DI::config()->get('system', 'pidfile');
if (empty($pidfile)) {
// No pid file, no daemon
self::$daemon_mode = false;
return false;
}
if (!is_readable($pidfile)) {
// No pid file. We assume that the daemon had been intentionally stopped.
self::$daemon_mode = false;
return false;
}
$pid = intval(file_get_contents($pidfile));
$running = posix_kill($pid, 0);
self::$daemon_mode = $running;
return $running;
}
/**
* Test if the daemon is running. If not, it will be started
*
* @return void
*/
private static function checkDaemonState()
{
if (!DI::config()->get('system', 'daemon_watchdog', false)) {
return;
}
if (!DI::mode()->isNormal()) {
return;
}
// Check every minute if the daemon is running
if (DI::config()->get('system', 'last_daemon_check', 0) + 60 > time()) {
return;
}
DI::config()->set('system', 'last_daemon_check', time());
$pidfile = DI::config()->get('system', 'pidfile');
if (empty($pidfile)) {
// No pid file, no daemon
return;
}
if (!is_readable($pidfile)) {
// No pid file. We assume that the daemon had been intentionally stopped.
return;
}
$pid = intval(file_get_contents($pidfile));
if (posix_kill($pid, 0)) {
Logger::info('Daemon process is running', ['pid' => $pid]);
return;
}
Logger::warning('Daemon process is not running', ['pid' => $pid]);
self::spawnDaemon();
}
/**
* Spawn a new daemon process
*
* @return void
*/
private static function spawnDaemon()
{
Logger::notice('Starting new daemon process');
$command = 'bin/daemon.php';
$a = DI::app();
DI::system()->run($command, ['start']);
Logger::notice('New daemon process started');
}
/**
* Check if the system is inside the defined maintenance window
*

182
src/Core/Worker/Cron.php Normal file
View File

@ -0,0 +1,182 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Core\Worker;
use Friendica\Core\Logger;
use Friendica\Core\Worker;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Post;
use Friendica\Protocol\ActivityPub;
use Friendica\Util\DateTimeFormat;
/**
* Contains the class for jobs that are executed in an interval
*/
class Cron
{
/**
* Runs the cron processes
*
* @return void
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function run()
{
Logger::info('Add cron entries');
// Check for spooled items
Worker::add(['priority' => PRIORITY_HIGH, 'force_priority' => true], 'SpoolPost');
// Run the cron job that calls all other jobs
Worker::add(['priority' => PRIORITY_MEDIUM, 'force_priority' => true], 'Cron');
// Cleaning dead processes
self::killStaleWorkers();
// Remove old entries from the workerqueue
self::cleanWorkerQueue();
// Directly deliver or requeue posts
self::deliverPosts();
}
/**
* fix the queue entry if the worker process died
*
* @return void
* @throws \Exception
*/
public static function killStaleWorkers()
{
$entries = DBA::select(
'workerqueue',
['id', 'pid', 'executed', 'priority', 'command', 'parameter'],
['NOT `done` AND `pid` != 0'],
['order' => ['priority', 'retrial', 'created']]
);
while ($entry = DBA::fetch($entries)) {
if (!posix_kill($entry["pid"], 0)) {
DBA::update('workerqueue', ['executed' => DBA::NULL_DATETIME, 'pid' => 0], ['id' => $entry["id"]]);
} else {
// Kill long running processes
// Define the maximum durations
$max_duration_defaults = [PRIORITY_CRITICAL => 720, PRIORITY_HIGH => 10, PRIORITY_MEDIUM => 60, PRIORITY_LOW => 180, PRIORITY_NEGLIGIBLE => 720];
$max_duration = $max_duration_defaults[$entry['priority']];
$argv = json_decode($entry['parameter'], true);
if (!empty($entry['command'])) {
$command = $entry['command'];
} elseif (!empty($argv)) {
$command = array_shift($argv);
} else {
return;
}
$command = basename($command);
// How long is the process already running?
$duration = (time() - strtotime($entry["executed"])) / 60;
if ($duration > $max_duration) {
Logger::notice('Worker process took too much time - killed', ['duration' => number_format($duration, 3), 'max' => $max_duration, 'id' => $entry["id"], 'pid' => $entry["pid"], 'command' => $command]);
posix_kill($entry["pid"], SIGTERM);
// We killed the stale process.
// To avoid a blocking situation we reschedule the process at the beginning of the queue.
// Additionally we are lowering the priority. (But not PRIORITY_CRITICAL)
$new_priority = $entry['priority'];
if ($entry['priority'] == PRIORITY_HIGH) {
$new_priority = PRIORITY_MEDIUM;
} elseif ($entry['priority'] == PRIORITY_MEDIUM) {
$new_priority = PRIORITY_LOW;
} elseif ($entry['priority'] != PRIORITY_CRITICAL) {
$new_priority = PRIORITY_NEGLIGIBLE;
}
DBA::update('workerqueue', ['executed' => DBA::NULL_DATETIME, 'created' => DateTimeFormat::utcNow(), 'priority' => $new_priority, 'pid' => 0], ['id' => $entry["id"]]
);
} else {
Logger::info('Process runtime is okay', ['duration' => number_format($duration, 3), 'max' => $max_duration, 'id' => $entry["id"], 'pid' => $entry["pid"], 'command' => $command]);
}
}
}
DBA::close($entries);
}
/**
* Remove old entries from the workerqueue
*
* @return void
*/
private static function cleanWorkerQueue()
{
DBA::delete('workerqueue', ["`done` AND `executed` < ?", DateTimeFormat::utc('now - 1 hour')]);
// Optimizing this table only last seconds
if (DI::config()->get('system', 'optimize_tables')) {
// We are acquiring the two locks from the worker to avoid locking problems
if (DI::lock()->acquire(Worker::LOCK_PROCESS, 10)) {
if (DI::lock()->acquire(Worker::LOCK_WORKER, 10)) {
DBA::e("OPTIMIZE TABLE `workerqueue`");
DBA::e("OPTIMIZE TABLE `process`");
DI::lock()->release(Worker::LOCK_WORKER);
}
DI::lock()->release(Worker::LOCK_PROCESS);
}
}
}
/**
* Directly deliver AP messages or requeue them.
*
* This function is placed here as a safeguard. Even when the worker queue is completely blocked, messages will be delivered.
*/
private static function deliverPosts()
{
$deliveries = DBA::p("SELECT `item-uri`.`uri` AS `inbox`, MAX(`failed`) AS `failed` FROM `post-delivery` INNER JOIN `item-uri` ON `item-uri`.`id` = `post-delivery`.`inbox-id` GROUP BY `inbox`");
while ($delivery = DBA::fetch($deliveries)) {
if ($delivery['failed'] == 0) {
$result = ActivityPub\Delivery::deliver($delivery['inbox']);
Logger::info('Directly deliver inbox', ['inbox' => $delivery['inbox'], 'result' => $result['success']]);
continue;
} elseif ($delivery['failed'] < 3) {
$priority = PRIORITY_HIGH;
} elseif ($delivery['failed'] < 6) {
$priority = PRIORITY_MEDIUM;
} elseif ($delivery['failed'] < 8) {
$priority = PRIORITY_LOW;
} else {
$priority = PRIORITY_NEGLIGIBLE;
}
if ($delivery['failed'] >= DI::config()->get('system', 'worker_defer_limit')) {
Logger::info('Removing failed deliveries', ['inbox' => $delivery['inbox'], 'failed' => $delivery['failed']]);
Post\Delivery::removeFailed($delivery['inbox']);
}
if (Worker::add($priority, 'APDelivery', '', 0, $delivery['inbox'], 0)) {
Logger::info('Missing APDelivery worker added for inbox', ['inbox' => $delivery['inbox'], 'failed' => $delivery['failed'], 'priority' => $priority]);
}
}
}
}

135
src/Core/Worker/Daemon.php Normal file
View File

@ -0,0 +1,135 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Core\Worker;
use Friendica\App\Mode;
use Friendica\Core\Logger;
use Friendica\DI;
/**
* Contains the class for the worker background job processing
*/
class Daemon
{
private static $mode = null;
/**
* Checks if the worker is running in the daemon mode.
*
* @return boolean
*/
public static function isMode()
{
if (!is_null(self::$mode)) {
return self::$mode;
}
if (DI::mode()->getExecutor() == Mode::DAEMON) {
return true;
}
$daemon_mode = DI::config()->get('system', 'worker_daemon_mode', false, true);
if ($daemon_mode) {
return $daemon_mode;
}
if (!function_exists('pcntl_fork')) {
self::$mode = false;
return false;
}
$pidfile = DI::config()->get('system', 'pidfile');
if (empty($pidfile)) {
// No pid file, no daemon
self::$mode = false;
return false;
}
if (!is_readable($pidfile)) {
// No pid file. We assume that the daemon had been intentionally stopped.
self::$mode = false;
return false;
}
$pid = intval(file_get_contents($pidfile));
$running = posix_kill($pid, 0);
self::$mode = $running;
return $running;
}
/**
* Test if the daemon is running. If not, it will be started
*
* @return void
*/
public static function checkState()
{
if (!DI::config()->get('system', 'daemon_watchdog', false)) {
return;
}
if (!DI::mode()->isNormal()) {
return;
}
// Check every minute if the daemon is running
if (DI::config()->get('system', 'last_daemon_check', 0) + 60 > time()) {
return;
}
DI::config()->set('system', 'last_daemon_check', time());
$pidfile = DI::config()->get('system', 'pidfile');
if (empty($pidfile)) {
// No pid file, no daemon
return;
}
if (!is_readable($pidfile)) {
// No pid file. We assume that the daemon had been intentionally stopped.
return;
}
$pid = intval(file_get_contents($pidfile));
if (posix_kill($pid, 0)) {
Logger::info('Daemon process is running', ['pid' => $pid]);
return;
}
Logger::warning('Daemon process is not running', ['pid' => $pid]);
self::spawn();
}
/**
* Spawn a new daemon process
*
* @return void
*/
private static function spawn()
{
Logger::notice('Starting new daemon process');
DI::system()->run('bin/daemon.php', ['start']);
Logger::notice('New daemon process started');
}
}

74
src/Core/Worker/IPC.php Normal file
View File

@ -0,0 +1,74 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Core\Worker;
use Friendica\Database\DBA;
/**
* Contains the class for the inter process communication
*/
class IPC
{
/**
* Set the flag if some job is waiting
*
* @param boolean $jobs Is there a waiting job?
* @param int $key Key number
* @throws \Exception
*/
public static function SetJobState(bool $jobs, int $key = 0)
{
$stamp = (float)microtime(true);
DBA::replace('worker-ipc', ['jobs' => $jobs, 'key' => $key]);
}
/**
* Delete a key entry
*
* @param int $key Key number
* @throws \Exception
*/
public static function DeleteJobState(int $key)
{
$stamp = (float)microtime(true);
DBA::delete('worker-ipc', ['key' => $key]);
}
/**
* Checks if some worker job waits to be executed
*
* @param int $key Key number
* @return bool
* @throws \Exception
*/
public static function JobsExists(int $key = 0)
{
$row = DBA::selectFirst('worker-ipc', ['jobs'], ['key' => $key]);
// When we don't have a row, no job is running
if (!DBA::isResult($row)) {
return false;
}
return (bool)$row['jobs'];
}
}

View File

@ -1153,7 +1153,7 @@ class Database
*
* @return boolean Was the command executed successfully?
*/
public function transaction()
public function transaction(): bool
{
if (!$this->performCommit()) {
return false;
@ -1790,4 +1790,32 @@ class Database
{
array_walk($arr, [$this, 'escapeArrayCallback'], $add_quotation);
}
/**
* Replaces a string in the provided fields of the provided table
*
* @param string $table_name
* @param array $fields List of field names in the provided table
* @param string $search
* @param string $replace
* @throws \Exception
*/
public function replaceInTableFields(string $table_name, array $fields, string $search, string $replace)
{
$search = $this->escape($search);
$replace = $this->escape($replace);
$upd = [];
foreach ($fields as $field) {
$field = DBA::quoteIdentifier($field);
$upd[] = "$field = REPLACE($field, '$search', '$replace')";
}
$upds = implode(', ', $upd);
$r = $this->e(sprintf("UPDATE %s SET %s;", $table_name, $upds));
if (!$this->isResult($r)) {
throw new \RuntimeException("Failed updating `$table_name`: " . $this->errorMessage());
}
}
}

View File

@ -21,7 +21,7 @@
namespace Friendica\Model;
use Friendica\App\BaseURL;
use Friendica\Contact\Avatar;
use Friendica\Contact\Introduction\Exception\IntroductionNotFoundException;
use Friendica\Content\Pager;
use Friendica\Content\Text\HTML;
@ -35,6 +35,7 @@ use Friendica\Core\Worker;
use Friendica\Database\Database;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Module\NoScrape;
use Friendica\Network\HTTPException;
use Friendica\Network\Probe;
use Friendica\Protocol\Activity;
@ -682,7 +683,7 @@ class Contact
*/
public static function updateSelfFromUserID($uid, $update_avatar = false)
{
$fields = ['id', 'name', 'nick', 'location', 'about', 'keywords', 'avatar', 'prvkey', 'pubkey', 'manually-approve',
$fields = ['id', 'uri-id', 'name', 'nick', 'location', 'about', 'keywords', 'avatar', 'prvkey', 'pubkey', 'manually-approve',
'xmpp', 'matrix', 'contact-type', 'forum', 'prv', 'avatar-date', 'url', 'nurl', 'unsearchable',
'photo', 'thumb', 'micro', 'header', 'addr', 'request', 'notify', 'poll', 'confirm', 'poco', 'network'];
$self = DBA::selectFirst('contact', $fields, ['uid' => $uid, 'self' => true]);
@ -714,6 +715,7 @@ class Contact
// it seems as if ported accounts can have wrong values, so we make sure that now everything is fine.
$fields['url'] = DI::baseUrl() . '/profile/' . $user['nickname'];
$fields['nurl'] = Strings::normaliseLink($fields['url']);
$fields['uri-id'] = ItemURI::getIdByURI($fields['url']);
$fields['addr'] = $user['nickname'] . '@' . substr(DI::baseUrl(), strpos(DI::baseUrl(), '://') + 3);
$fields['request'] = DI::baseUrl() . '/dfrn_request/' . $user['nickname'];
$fields['notify'] = DI::baseUrl() . '/dfrn_notify/' . $user['nickname'];
@ -771,10 +773,10 @@ class Contact
$fields['updated'] = DateTimeFormat::utcNow();
self::update($fields, ['id' => $self['id']]);
// Update the public contact as well
$fields['prvkey'] = null;
$fields['self'] = false;
self::update($fields, ['uid' => 0, 'nurl' => $self['nurl']]);
// Update the other contacts as well
unset($fields['prvkey']);
$fields['self'] = false;
self::update($fields, ['uri-id' => $self['uri-id'], 'self' => false]);
// Update the profile
$fields = [
@ -797,14 +799,20 @@ class Contact
public static function remove($id)
{
// We want just to make sure that we don't delete our "self" contact
$contact = DBA::selectFirst('contact', ['uid'], ['id' => $id, 'self' => false]);
$contact = DBA::selectFirst('contact', ['uri-id', 'photo', 'thumb', 'micro', 'uid'], ['id' => $id, 'self' => false]);
if (!DBA::isResult($contact)) {
return;
}
self::clearFollowerFollowingEndpointCache($contact['uid']);
// Archive the contact
self::update(['archive' => true, 'network' => Protocol::PHANTOM, 'deleted' => true], ['id' => $id]);
if (!DBA::exists('contact', ['uri-id' => $contact['uri-id'], 'deleted' => false])) {
Avatar::deleteCache($contact);
}
// Delete it in the background
Worker::add(PRIORITY_MEDIUM, 'Contact\Remove', $id);
}
@ -827,8 +835,10 @@ class Contact
}
if (in_array($contact['rel'], [self::SHARING, self::FRIEND])) {
$cdata = Contact::getPublicAndUserContactID($contact['id'], $contact['uid']);
Worker::add(PRIORITY_HIGH, 'Contact\Unfollow', $cdata['public'], $contact['uid']);
$cdata = self::getPublicAndUserContactID($contact['id'], $contact['uid']);
if (!empty($cdata['public'])) {
Worker::add(PRIORITY_HIGH, 'Contact\Unfollow', $cdata['public'], $contact['uid']);
}
}
self::removeSharer($contact);
@ -854,8 +864,10 @@ class Contact
}
if (in_array($contact['rel'], [self::FOLLOWER, self::FRIEND])) {
$cdata = Contact::getPublicAndUserContactID($contact['id'], $contact['uid']);
Worker::add(PRIORITY_HIGH, 'Contact\RevokeFollow', $cdata['public'], $contact['uid']);
$cdata = self::getPublicAndUserContactID($contact['id'], $contact['uid']);
if (!empty($cdata['public'])) {
Worker::add(PRIORITY_HIGH, 'Contact\RevokeFollow', $cdata['public'], $contact['uid']);
}
}
self::removeFollower($contact);
@ -878,19 +890,29 @@ class Contact
throw new \InvalidArgumentException('Unexpected public contact record');
}
$cdata = Contact::getPublicAndUserContactID($contact['id'], $contact['uid']);
$cdata = self::getPublicAndUserContactID($contact['id'], $contact['uid']);
if (in_array($contact['rel'], [self::SHARING, self::FRIEND])) {
if (in_array($contact['rel'], [self::SHARING, self::FRIEND]) && !empty($cdata['public'])) {
Worker::add(PRIORITY_HIGH, 'Contact\Unfollow', $cdata['public'], $contact['uid']);
}
if (in_array($contact['rel'], [self::FOLLOWER, self::FRIEND])) {
if (in_array($contact['rel'], [self::FOLLOWER, self::FRIEND]) && !empty($cdata['public'])) {
Worker::add(PRIORITY_HIGH, 'Contact\RevokeFollow', $cdata['public'], $contact['uid']);
}
self::remove($contact['id']);
}
private static function clearFollowerFollowingEndpointCache(int $uid)
{
if (empty($uid)) {
return;
}
DI::cache()->delete(ActivityPub\Transmitter::CACHEKEY_CONTACTS . 'followers:' . $uid);
DI::cache()->delete(ActivityPub\Transmitter::CACHEKEY_CONTACTS . 'following:' . $uid);
DI::cache()->delete(NoScrape::CACHEKEY . $uid);
}
/**
* Marks a contact for archival after a communication issue delay
@ -1460,7 +1482,7 @@ class Contact
$items = Post::toArray(Post::selectForUser(local_user(), $fields, $condition, $params));
if ($pager->getStart() == 0) {
$cdata = Contact::getPublicAndUserContactID($cid, local_user());
$cdata = self::getPublicAndUserContactID($cid, local_user());
if (!empty($cdata['public'])) {
$pinned = Post\Collection::selectToArrayForContact($cdata['public'], Post\Collection::FEATURED, $fields);
$items = array_merge($items, $pinned);
@ -1473,7 +1495,7 @@ class Contact
$items = Post::toArray(Post::selectForUser(local_user(), $fields, $condition, $params));
if ($pager->getStart() == 0) {
$cdata = Contact::getPublicAndUserContactID($cid, local_user());
$cdata = self::getPublicAndUserContactID($cid, local_user());
if (!empty($cdata['public'])) {
$condition = ["`uri-id` IN (SELECT `uri-id` FROM `collection-view` WHERE `cid` = ? AND `type` = ?)",
$cdata['public'], Post\Collection::FEATURED];
@ -1567,16 +1589,24 @@ class Contact
return;
}
if (Network::isLocalLink($contact['url'])) {
return;
}
if (in_array($contact['network'], [Protocol::FEED, Protocol::MAIL]) || DI::config()->get('system', 'cache_contact_avatar')) {
if (!empty($contact['avatar']) && (empty($contact['photo']) || empty($contact['thumb']) || empty($contact['micro']))) {
Logger::info('Adding avatar cache', ['id' => $cid, 'contact' => $contact]);
self::updateAvatar($cid, $contact['avatar'], true);
return;
}
} elseif (!empty($contact['photo']) || !empty($contact['thumb']) || !empty($contact['micro'])) {
Logger::info('Removing avatar cache', ['id' => $cid, 'contact' => $contact]);
} elseif (Photo::isPhotoURI($contact['photo']) || Photo::isPhotoURI($contact['thumb']) || Photo::isPhotoURI($contact['micro'])) {
Logger::info('Replacing legacy avatar cache', ['id' => $cid, 'contact' => $contact]);
self::updateAvatar($cid, $contact['avatar'], true);
return;
} elseif (DI::config()->get('system', 'avatar_cache') && (empty($contact['photo']) || empty($contact['thumb']) || empty($contact['micro']))) {
Logger::info('Adding avatar cache file', ['id' => $cid, 'contact' => $contact]);
self::updateAvatar($cid, $contact['avatar'], true);
return;
}
}
@ -1593,6 +1623,27 @@ class Contact
private static function getAvatarPath(array $contact, string $size, $no_update = false)
{
$contact = self::checkAvatarCacheByArray($contact, $no_update);
if (DI::config()->get('system', 'avatar_cache')) {
switch ($size) {
case Proxy::SIZE_MICRO:
if (!empty($contact['micro']) && !Photo::isPhotoURI($contact['micro'])) {
return $contact['micro'];
}
break;
case Proxy::SIZE_THUMB:
if (!empty($contact['thumb']) && !Photo::isPhotoURI($contact['thumb'])) {
return $contact['thumb'];
}
break;
case Proxy::SIZE_SMALL:
if (!empty($contact['photo']) && !Photo::isPhotoURI($contact['photo'])) {
return $contact['photo'];
}
break;
}
}
return self::getAvatarUrlForId($contact['id'], $size, $contact['updated'] ?? '');
}
@ -1657,7 +1708,9 @@ class Contact
return $contact;
}
if (!empty($contact['id']) && !empty($contact['avatar'])) {
$local = !empty($contact['url']) && Network::isLocalLink($contact['url']);
if (!$local && !empty($contact['id']) && !empty($contact['avatar'])) {
self::updateAvatar($contact['id'], $contact['avatar'], true);
$new_contact = self::getById($contact['id'], $contact_fields);
@ -1665,6 +1718,8 @@ class Contact
// We only update the cache fields
$contact = array_merge($contact, $new_contact);
}
} elseif ($local && !empty($contact['avatar'])) {
return $contact;
}
/// add the default avatars if the fields aren't filled
@ -1723,7 +1778,7 @@ class Contact
break;
default:
/**
* Use a random picture.
* Use a random picture.
* The service provides random pictures from Unsplash.
* @license https://unsplash.com/license
*/
@ -1799,7 +1854,7 @@ class Contact
{
// We have to fetch the "updated" variable when it wasn't provided
// The parameter can be provided to improve performance
if (empty($updated) || empty($guid)) {
if (empty($updated)) {
$account = DBA::selectFirst('account-user-view', ['updated', 'guid'], ['id' => $cid]);
$updated = $account['updated'] ?? '';
$guid = $account['guid'] ?? '';
@ -1901,7 +1956,7 @@ class Contact
*/
public static function updateAvatar(int $cid, string $avatar, bool $force = false, bool $create_cache = false)
{
$contact = DBA::selectFirst('contact', ['uid', 'avatar', 'photo', 'thumb', 'micro', 'xmpp', 'addr', 'nurl', 'url', 'network'],
$contact = DBA::selectFirst('contact', ['uid', 'avatar', 'photo', 'thumb', 'micro', 'xmpp', 'addr', 'nurl', 'url', 'network', 'uri-id'],
['id' => $cid, 'self' => false]);
if (!DBA::isResult($contact)) {
return;
@ -1942,6 +1997,8 @@ class Contact
}
if (in_array($contact['network'], [Protocol::FEED, Protocol::MAIL]) || $cache_avatar) {
Avatar::deleteCache($contact);
if ($default_avatar && Proxy::isLocalImage($avatar)) {
$fields = ['avatar' => $avatar, 'avatar-date' => DateTimeFormat::utcNow(),
'photo' => $avatar,
@ -1993,9 +2050,8 @@ class Contact
}
} else {
Photo::delete(['uid' => $uid, 'contact-id' => $cid, 'photo-type' => Photo::CONTACT_AVATAR]);
$fields = ['avatar' => $avatar, 'avatar-date' => DateTimeFormat::utcNow(),
'photo' => '', 'thumb' => '', 'micro' => ''];
$update = ($avatar != $contact['avatar'] . $contact['photo'] . $contact['thumb'] . $contact['micro']) || $force;
$fields = Avatar::fetchAvatarContact($contact, $avatar, $force);
$update = ($avatar . $fields['photo'] . $fields['thumb'] . $fields['micro'] != $contact['avatar'] . $contact['photo'] . $contact['thumb'] . $contact['micro']) || $force;
}
if (!$update) {
@ -2274,9 +2330,12 @@ class Contact
if ($uid == 0) {
if ($ret['network'] == Protocol::ACTIVITYPUB) {
ActivityPub\Processor::fetchFeaturedPosts($ret['url']);
$apcontact = APContact::getByURL($ret['url'], false);
if (!empty($apcontact['featured'])) {
Worker::add(PRIORITY_LOW, 'FetchFeaturedPosts', $ret['url']);
}
}
$ret['last-item'] = Probe::getLastUpdate($ret);
Logger::info('Fetched last item', ['id' => $id, 'probed_url' => $ret['url'], 'last-item' => $ret['last-item'], 'callstack' => System::callstack(20)]);
}
@ -2484,6 +2543,11 @@ class Contact
} else {
$probed = true;
$ret = Probe::uri($url, $network, $uid);
// Ensure that the public contact exists
if ($ret['network'] != Protocol::PHANTOM) {
self::getIdForURL($url);
}
}
if (($network != '') && ($ret['network'] != $network)) {
@ -2662,6 +2726,8 @@ class Contact
$contact = DBA::selectFirst('contact', [], ['id' => $cid]);
}
self::clearFollowerFollowingEndpointCache($importer['uid']);
if (!empty($contact)) {
if (!empty($contact['pending'])) {
Logger::info('Pending contact request already exists.', ['url' => $url, 'uid' => $importer['uid']]);
@ -2785,7 +2851,9 @@ class Contact
return;
}
$cdata = Contact::getPublicAndUserContactID($contact['id'], $contact['uid']);
self::clearFollowerFollowingEndpointCache($contact['uid']);
$cdata = self::getPublicAndUserContactID($contact['id'], $contact['uid']);
DI::notification()->deleteForUserByVerb($contact['uid'], Activity::FOLLOW, ['actor-id' => $cdata['public']]);
}
@ -2799,6 +2867,8 @@ class Contact
*/
public static function removeSharer(array $contact)
{
self::clearFollowerFollowingEndpointCache($contact['uid']);
if ($contact['rel'] == self::SHARING || in_array($contact['network'], [Protocol::FEED, Protocol::MAIL])) {
self::remove($contact['id']);
} else {

View File

@ -42,6 +42,7 @@ use Friendica\Util\Map;
use Friendica\Util\Network;
use Friendica\Util\Proxy;
use Friendica\Util\Strings;
use Friendica\Util\Temporal;
use Friendica\Worker\Delivery;
use LanguageDetection\Language;
@ -87,14 +88,16 @@ class Item
'wall', 'private', 'starred', 'origin', 'parent-origin', 'title', 'body', 'language',
'content-warning', 'location', 'coord', 'app', 'rendered-hash', 'rendered-html', 'object',
'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid', 'mention', 'global',
'author-id', 'author-link', 'author-name', 'author-avatar', 'author-network',
'owner-id', 'owner-link', 'owner-name', 'owner-avatar', 'owner-network', 'owner-contact-type',
'author-id', 'author-link', 'author-name', 'author-avatar', 'author-network', 'author-updated', 'author-gsid', 'author-addr', 'author-uri-id',
'owner-id', 'owner-link', 'owner-name', 'owner-avatar', 'owner-network', 'owner-contact-type', 'owner-updated',
'causer-id', 'causer-link', 'causer-name', 'causer-avatar', 'causer-contact-type', 'causer-network',
'contact-id', 'contact-uid', 'contact-link', 'contact-name', 'contact-avatar',
'writable', 'self', 'cid', 'alias',
'event-created', 'event-edited', 'event-start', 'event-finish',
'event-summary', 'event-desc', 'event-location', 'event-type',
'event-nofinish', 'event-ignore', 'event-id',
"question-id", "question-multiple", "question-voters", "question-end-time",
"has-categories", "has-media",
'delivery_queue_count', 'delivery_queue_done', 'delivery_queue_failed'
];
@ -725,7 +728,7 @@ class Item
return GRAVITY_UNKNOWN; // Should not happen
}
public static function insert(array $item, bool $notify = false, bool $post_local = true)
public static function insert(array $item, int $notify = 0, bool $post_local = true)
{
$orig_item = $item;
@ -739,7 +742,7 @@ class Item
$item['protocol'] = Conversation::PARCEL_DIRECT;
$item['direction'] = Conversation::PUSH;
if (in_array($notify, PRIORITIES)) {
if (is_int($notify) && in_array($notify, PRIORITIES)) {
$priority = $notify;
}
} else {
@ -1079,7 +1082,7 @@ class Item
if (in_array($item['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN])) {
$content_warning = BBCode::getAbstract($item['body'], Protocol::ACTIVITYPUB);
if (!empty($content_warning) && empty($item['content-warning'])) {
$item['content-warning'] = $content_warning;
$item['content-warning'] = BBCode::toPlaintext($content_warning);
}
}
@ -1215,9 +1218,30 @@ class Item
Worker::add(['priority' => $priority, 'dont_fork' => true], 'Notifier', $notify_type, (int)$posted_item['uri-id'], (int)$posted_item['uid']);
}
// Fill the cache with the rendered content.
if (in_array($posted_item['gravity'], [GRAVITY_PARENT, GRAVITY_COMMENT]) && ($posted_item['uid'] == 0)) {
self::updateDisplayCache($posted_item['uri-id']);
}
if ($posted_item['origin'] && ($posted_item['uid'] != 0) && in_array($posted_item['gravity'], [GRAVITY_PARENT, GRAVITY_COMMENT])) {
DI::cache()->delete(ActivityPub\Transmitter::CACHEKEY_OUTBOX . $posted_item['uid']);
}
return $post_user_id;
}
/**
* Update the display cache
*
* @param integer $uri_id
* @return void
*/
public static function updateDisplayCache(int $uri_id)
{
$item = Post::selectFirst(self::DISPLAY_FIELDLIST, ['uri-id' => $uri_id]);
self::prepareBody($item, false, false, true);
}
/**
* Change the owner of a parent item if it had been shared by a forum
*
@ -1425,7 +1449,7 @@ class Item
return 0;
}
if ((($item['gravity'] == GRAVITY_COMMENT) || $is_reshare) && !Post::exists(['uri-id' => $item['thr-parent-id'], 'uid' => $uid])) {
if (($uri_id != $item['thr-parent-id']) && (($item['gravity'] == GRAVITY_COMMENT) || $is_reshare) && !Post::exists(['uri-id' => $item['thr-parent-id'], 'uid' => $uid])) {
// Fetch the origin user for the post
$origin_uid = self::GetOriginUidForUriId($item['thr-parent-id'], $uid);
if (is_null($origin_uid)) {
@ -2692,7 +2716,7 @@ class Item
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @todo Remove reference, simply return "rendered-html" and "rendered-hash"
*/
public static function putInCache(&$item)
private static function putInCache(&$item)
{
// Save original body to prevent addons to modify it
$body = $item['body'];
@ -2705,8 +2729,6 @@ class Item
|| $rendered_hash != hash('md5', BBCode::VERSION . '::' . $body)
|| DI::config()->get('system', 'ignore_cache')
) {
self::addRedirToImageTags($item);
$item['rendered-html'] = BBCode::convertForUriId($item['uri-id'], $item['body']);
$item['rendered-hash'] = hash('md5', BBCode::VERSION . '::' . $body);
@ -2731,31 +2753,6 @@ class Item
$item['body'] = $body;
}
/**
* Find any non-embedded images in private items and add redir links to them
*
* @param array &$item The field array of an item row
*/
private static function addRedirToImageTags(array &$item)
{
$app = DI::app();
$matches = [];
$cnt = preg_match_all('|\[img\](http[^\[]*?/photo/[a-fA-F0-9]+?(-[0-9]\.[\w]+?)?)\[\/img\]|', $item['body'], $matches, PREG_SET_ORDER);
if ($cnt) {
foreach ($matches as $mtch) {
if (strpos($mtch[1], '/redir') !== false) {
continue;
}
if ((local_user() == $item['uid']) && ($item['private'] == self::PRIVATE) && ($item['contact-id'] != $app->getContactId()) && ($item['network'] == Protocol::DFRN)) {
$img_url = 'redir/' . $item['contact-id'] . '?url=' . urlencode($mtch[1]);
$item['body'] = str_replace($mtch[0], '[img]' . $img_url . '[/img]', $item['body']);
}
}
}
}
/**
* Given an item array, convert the body element from bbcode to html and add smilie icons.
* If attach is true, also add icons for item attachments.
@ -2763,6 +2760,7 @@ class Item
* @param array $item
* @param boolean $attach
* @param boolean $is_preview
* @param boolean $only_cache
* @return string item body html
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @throws \ImagickException
@ -2771,7 +2769,7 @@ class Item
* @hook prepare_body ('item'=>item array, 'html'=>body string, 'is_preview'=>boolean, 'filter_reasons'=>string array) after first bbcode to html
* @hook prepare_body_final ('item'=>item array, 'html'=>body string) after attach icons and blockquote special case handling (spoiler, author)
*/
public static function prepareBody(array &$item, $attach = false, $is_preview = false)
public static function prepareBody(array &$item, $attach = false, $is_preview = false, $only_cache = false)
{
$a = DI::app();
Hook::callAll('prepare_body_init', $item);
@ -2792,10 +2790,10 @@ class Item
$body = $item['body'] ?? '';
$shared = BBCode::fetchShareAttributes($body);
if (!empty($shared['guid'])) {
$shared_item = Post::selectFirst(['uri-id', 'plink'], ['guid' => $shared['guid']]);
$shared_item = Post::selectFirst(['uri-id', 'plink', 'has-media'], ['guid' => $shared['guid']]);
$shared_uri_id = $shared_item['uri-id'] ?? 0;
$shared_links = [strtolower($shared_item['plink'] ?? '')];
$shared_attachments = Post\Media::splitAttachments($shared_uri_id, $shared['guid']);
$shared_attachments = Post\Media::splitAttachments($shared_uri_id, $shared['guid'], [], $shared_item['has-media'] ?? false);
$shared_links = array_merge($shared_links, array_column($shared_attachments['visual'], 'url'));
$shared_links = array_merge($shared_links, array_column($shared_attachments['link'], 'url'));
$shared_links = array_merge($shared_links, array_column($shared_attachments['additional'], 'url'));
@ -2804,7 +2802,7 @@ class Item
$shared_uri_id = 0;
$shared_links = [];
}
$attachments = Post\Media::splitAttachments($item['uri-id'], $item['guid'] ?? '', $shared_links);
$attachments = Post\Media::splitAttachments($item['uri-id'], $item['guid'] ?? '', $shared_links, $item['has-media']);
$item['body'] = self::replaceVisualAttachments($attachments, $item['body'] ?? '');
$item['body'] = preg_replace("/\s*\[attachment .*?\].*?\[\/attachment\]\s*/ism", "\n", $item['body']);
@ -2812,6 +2810,10 @@ class Item
$item['body'] = $body;
$s = $item["rendered-html"];
if ($only_cache) {
return;
}
// Compile eventual content filter reasons
$filter_reasons = [];
if (!$is_preview && public_contact() != $item['author-id']) {
@ -2857,6 +2859,7 @@ class Item
$s = self::addVisualAttachments($attachments, $item, $s, false);
$s = self::addLinkAttachment($item['uri-id'], $attachments, $body, $s, false, $shared_links);
$s = self::addNonVisualAttachments($attachments, $item, $s, false);
$s = self::addQuestions($item, $s);
// Map.
if (strpos($s, '<div class="map">') !== false && !empty($item['coord'])) {
@ -2927,9 +2930,17 @@ class Item
foreach ($attachments['visual'] as $attachment) {
if (!empty($attachment['preview'])) {
$body = str_replace($attachment['preview'], Post\Media::getPreviewUrlForId($attachment['id'], Proxy::SIZE_LARGE), $body);
$proxy = Post\Media::getPreviewUrlForId($attachment['id'], Proxy::SIZE_LARGE);
$search = ['[img=' . $attachment['preview'] . ']', ']' . $attachment['preview'] . '[/img]'];
$replace = ['[img=' . $proxy . ']', ']' . $proxy . '[/img]'];
$body = str_replace($search, $replace, $body);
} elseif ($attachment['filetype'] == 'image') {
$body = str_replace($attachment['url'], Post\Media::getUrlForId($attachment['id']), $body);
$proxy = Post\Media::getUrlForId($attachment['id']);
$search = ['[img=' . $attachment['url'] . ']', ']' . $attachment['url'] . '[/img]'];
$replace = ['[img=' . $proxy . ']', ']' . $proxy . '[/img]'];
$body = str_replace($search, $replace, $body);
}
}
DI::profiler()->stopRecording();
@ -3182,6 +3193,50 @@ class Item
return $content;
}
private static function addQuestions(array $item, string $content)
{
DI::profiler()->startRecording('rendering');
if (!empty($item['question-id'])) {
$question = [
'id' => $item['question-id'],
'multiple' => $item['question-multiple'],
'voters' => $item['question-voters'],
'endtime' => $item['question-end-time']
];
$options = Post\QuestionOption::getByURIId($item['uri-id']);
foreach ($options as $key => $option) {
$percent = $question['voters'] ? ($option['replies'] / $question['voters'] * 100) : 0;
$options[$key]['percent'] = $percent;
if ($question['voters'] > 0) {
$options[$key]['vote'] = DI::l10n()->t('%s (%d%s, %d votes)', $option['name'], round($percent, 1), '%', $option['replies']);
} else {
$options[$key]['vote'] = DI::l10n()->t('%s (%d votes)', $option['name'], $option['replies']);
}
}
if (!empty($question['voters']) && !empty($question['endtime'])) {
$summary = DI::l10n()->t('%d voters. Poll end: %s', $question['voters'], Temporal::getRelativeDate($question['endtime']));
} elseif (!empty($question['voters'])) {
$summary = DI::l10n()->t('%d voters.', $question['voters']);
} elseif (!empty($question['endtime'])) {
$summary = DI::l10n()->t('Poll end: %s', Temporal::getRelativeDate($question['endtime']));
} else {
$summary = '';
}
$content .= Renderer::replaceMacros(Renderer::getMarkupTemplate('content/question.tpl'), [
'$question' => $question,
'$options' => $options,
'$summary' => $summary,
]);
}
DI::profiler()->stopRecording();
return $content;
}
/**
* get private link for item
*
@ -3205,6 +3260,12 @@ class Item
'orig_title' => DI::l10n()->t('View on separate page'),
];
if (!empty($plink) && ($item['private'] == self::PRIVATE)) {
$author = ['uid' => 0, 'id' => $item['author-id'],
'network' => $item['author-network'], 'url' => $item['author-link']];
$plink = Contact::magicLinkByContact($author, $plink);
}
if (!empty($plink)) {
$ret['href'] = DI::baseUrl()->remove($plink);
$ret['title'] = DI::l10n()->t('Link to source');

View File

@ -704,10 +704,19 @@ class Photo
}
$image_uri = substr($image_uri, strrpos($image_uri, '/') + 1);
$image_uri = substr($image_uri, 0, strpos($image_uri, '-'));
if (!strlen($image_uri)) {
return '';
}
return $image_uri;
return trim($image_uri);
}
/**
* Checks if the given URL is a local photo.
* Since it is meant for time critical occasions, the check is done without any database requests.
*
* @param string $url
* @return boolean
*/
public static function isPhotoURI(string $url): bool
{
return !empty(self::ridFromURI($url));
}
/**

View File

@ -111,6 +111,11 @@ class Category
return array_column($tags, 'name');
}
public static function existsForURIId(int $uri_id, int $uid)
{
return DBA::exists('post-category', ['uri-id' => $uri_id, 'uid' => $uid]);
}
/**
* Generates an array of files or categories of a given uri-id
*

View File

@ -24,6 +24,8 @@ namespace Friendica\Model\Post;
use Friendica\Database\DBA;
use BadMethodCallException;
use Friendica\Database\Database;
use Friendica\DI;
use Friendica\Protocol\ActivityPub;
class Collection
{
@ -34,14 +36,19 @@ class Collection
*
* @param integer $uri_id
* @param integer $type
* @param integer $cache_uid If set to a non zero value, the featured cache is cleared
*/
public static function add(int $uri_id, int $type)
public static function add(int $uri_id, int $type, int $cache_uid = 0)
{
if (empty($uri_id)) {
throw new BadMethodCallException('Empty URI_id');
}
DBA::insert('post-collection', ['uri-id' => $uri_id, 'type' => $type], Database::INSERT_IGNORE);
if (!empty($cache_uid) && ($type == self::FEATURED)) {
DI::cache()->delete(ActivityPub\Transmitter::CACHEKEY_FEATURED . $cache_uid);
}
}
/**
@ -49,14 +56,19 @@ class Collection
*
* @param integer $uri_id
* @param integer $type
* @param integer $cache_uid If set to a non zero value, the featured cache is cleared
*/
public static function remove(int $uri_id, int $type)
public static function remove(int $uri_id, int $type, int $cache_uid = 0)
{
if (empty($uri_id)) {
throw new BadMethodCallException('Empty URI_id');
}
DBA::delete('post-collection', ['uri-id' => $uri_id, 'type' => $type]);
if (!empty($cache_uid) && ($type == self::FEATURED)) {
DI::cache()->delete(ActivityPub\Transmitter::CACHEKEY_FEATURED . $cache_uid);
}
}
/**

100
src/Model/Post/Delivery.php Normal file
View File

@ -0,0 +1,100 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Model\Post;
use Friendica\Database\DBA;
use BadMethodCallException;
use Friendica\Database\Database;
use Friendica\DI;
use Friendica\Model\ItemURI;
class Delivery
{
/**
* Add a post to an inbox
*
* @param integer $uri_id
* @param string $inbox
* @param string $created
* @param array %receivers
*/
public static function add(int $uri_id, int $uid, string $inbox, string $created, string $command, array $receivers)
{
if (empty($uri_id)) {
throw new BadMethodCallException('Empty URI_id');
}
$fields = ['uri-id' => $uri_id, 'uid' => $uid, 'inbox-id' => ItemURI::getIdByURI($inbox),
'created' => $created, 'command' => $command, 'receivers' => json_encode($receivers)];
DBA::insert('post-delivery', $fields, Database::INSERT_IGNORE);
}
/**
* Remove post from an inbox after delivery
*
* @param integer $uri_id
* @param string $inbox
*/
public static function remove(int $uri_id, string $inbox)
{
DBA::delete('post-delivery', ['uri-id' => $uri_id, 'inbox-id' => ItemURI::getIdByURI($inbox)]);
}
/**
* Remove failed posts for an inbox
*
* @param string $inbox
*/
public static function removeFailed(string $inbox)
{
DBA::delete('post-delivery', ["`inbox-id` = ? AND `failed` >= ?", ItemURI::getIdByURI($inbox), DI::config()->get('system', 'worker_defer_limit')]);
}
/**
* Increment "failed" counter for the given inbox and post
*
* @param integer $uri_id
* @param string $inbox
*/
public static function incrementFailed(int $uri_id, string $inbox)
{
return DBA::e('UPDATE `post-delivery` SET `failed` = `failed` + 1 WHERE `uri-id` = ? AND `inbox-id` = ?', $uri_id, ItemURI::getIdByURI($inbox));
}
public static function selectForInbox(string $inbox)
{
$rows = DBA::select('post-delivery', [], ["`inbox-id` = ? AND `failed` < ?", ItemURI::getIdByURI($inbox), DI::config()->get('system', 'worker_defer_limit')], ['order' => ['created']]);
$deliveries = [];
while ($row = DBA::fetch($rows)) {
if (!empty($row['receivers'])) {
$row['receivers'] = json_decode($row['receivers'], true);
} else {
$row['receivers'] = [];
}
$deliveries[] = $row;
}
DBA::close($rows);
return $deliveries;
}
}

View File

@ -0,0 +1,71 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Model\Post;
use Friendica\Core\Logger;
use Friendica\Database\DBA;
use Friendica\Database\Database;
use Friendica\Database\DBStructure;
use Friendica\Model\Post;
class History
{
/**
* Add a post to the history before it is changed
*
* @param integer $uri_id
* @param array $item
*/
public static function add(int $uri_id, array $item)
{
$allfields = DBStructure::definition('', false);
$fields = array_keys($allfields['post-history']['fields']);
$post = Post::selectFirstPost($fields, ['uri-id' => $uri_id]);
if (empty($post)) {
Logger::warning('Post not found', ['uri-id' => $uri_id]);
return;
}
if ($item['edited'] <= $post['edited']) {
Logger::info('New edit date is not newer than the old one', ['uri-id' => $uri_id, 'old' => $post['edited'], 'new' => $item['edited']]);
return;
}
$update = false;
$changed = DBStructure::getFieldsForTable('post-history', $item);
unset($changed['uri-id']);
unset($changed['edited']);
foreach ($changed as $field => $content) {
if ($content != $post[$field]) {
$update = true;
}
}
if ($update) {
DBA::insert('post-history', $post, Database::INSERT_IGNORE);
Logger::info('Added history', ['uri-id' => $uri_id, 'edited' => $post['edited']]);
} else {
Logger::info('No content fields had been changed', ['uri-id' => $uri_id, 'edited' => $post['edited']]);
}
}
}

View File

@ -547,12 +547,17 @@ class Media
* @param int $uri_id
* @param string $guid
* @param array $links list of links that shouldn't be added
* @param bool $has_media
* @return array attachments
*/
public static function splitAttachments(int $uri_id, string $guid = '', array $links = [])
public static function splitAttachments(int $uri_id, string $guid = '', array $links = [], bool $has_media = true)
{
$attachments = ['visual' => [], 'link' => [], 'additional' => []];
if (!$has_media) {
return $attachments;
}
$media = self::getByURIId($uri_id);
if (empty($media)) {
return $attachments;

View File

@ -22,11 +22,9 @@
namespace Friendica\Model\Post;
use \BadMethodCallException;
use Friendica\Core\Protocol;
use Friendica\Database\Database;
use Friendica\Database\DBA;
use Friendica\Database\DBStructure;
use Friendica\Model\Post;
class Thread
{

View File

@ -308,7 +308,7 @@ class UserNotification
return;
}
$notification = (new Notifications\Factory\Notification(DI::baseUrl(), DI::l10n(), DI::localRelationship(), DI::logger()))->createForUser(
$notification = DI::notificationFactory()->createForUser(
$uid,
$item['vid'],
$type,
@ -336,7 +336,7 @@ class UserNotification
*/
public static function insertNotification(int $actor, string $verb, int $uid): bool
{
$notification = (new Notifications\Factory\Notification(DI::baseUrl(), DI::l10n(), DI::localRelationship(), DI::logger()))->createForRelationship(
$notification = DI::notificationFactory()->createForRelationship(
$uid,
$actor,
$verb

View File

@ -220,7 +220,7 @@ class Profile
public static function load(App $a, string $nickname, bool $show_contacts = true)
{
$profile = User::getOwnerDataByNick($nickname);
if (empty($profile)) {
if (empty($profile) || $profile['account_removed']) {
Logger::info('profile error: ' . DI::args()->getQueryString());
return [];
}
@ -235,7 +235,7 @@ class Profile
DI::page()['title'] = $profile['name'] . ' @ ' . DI::config()->get('config', 'sitename');
if (!DI::pConfig()->get(local_user(), 'system', 'always_my_theme')) {
if (!local_user()) {
$a->setCurrentTheme($profile['theme']);
$a->setCurrentMobileTheme(DI::pConfig()->get($a->getProfileOwner(), 'system', 'mobile_theme'));
}
@ -880,23 +880,17 @@ class Profile
*
* Used from within PCSS themes to set theme parameters. If there's a
* profile_uid variable set in App, that is the "page owner" and normally their theme
* settings take precedence; unless a local user sets the "always_my_theme"
* system pconfig, which means they don't want to see anybody else's theme
* settings except their own while on this site.
* settings take precedence; unless a local user is logged in which means they don't
* want to see anybody else's theme settings except their own while on this site.
*
* @param App $a
* @return int user ID
*
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @note Returns local_user instead of user ID if "always_my_theme" is set to true
*/
public static function getThemeUid(App $a)
public static function getThemeUid(App $a): int
{
$uid = !empty($a->getProfileOwner()) ? intval($a->getProfileOwner()) : 0;
if (local_user() && (DI::pConfig()->get(local_user(), 'system', 'always_my_theme') || !$uid)) {
return local_user();
}
return $uid;
return local_user() ?: $a->getProfileOwner();
}
/**

View File

@ -143,17 +143,7 @@ class Subscription
{
$type = \Friendica\Factory\Api\Mastodon\Notification::getType($Notification);
$desktop_notification = !in_array($type, [Notification::TYPE_RESHARE, Notification::TYPE_LIKE]);
if (DI::pConfig()->get($Notification->uid, 'system', 'notify_like') && ($type == Notification::TYPE_LIKE)) {
$desktop_notification = true;
}
if (DI::pConfig()->get($Notification->uid, 'system', 'notify_announce') && ($type == Notification::TYPE_RESHARE)) {
$desktop_notification = true;
}
if ($desktop_notification) {
if (DI::notify()->NotifyOnDesktop($Notification, $type)) {
DI::notify()->createFromNotification($Notification);
}

View File

@ -224,13 +224,16 @@ class Tag
{
$fields = ['name' => substr($name, 0, 96), 'url' => $url];
if (!empty($type)) {
$fields['type'] = $type;
$tag = DBA::selectFirst('tag', ['id', 'type'], $fields);
if (DBA::isResult($tag)) {
if (empty($tag['type']) && !empty($type)) {
DBA::update('tag', ['type' => $type], $fields);
}
return $tag['id'];
}
$tag = DBA::selectFirst('tag', ['id'], $fields);
if (DBA::isResult($tag)) {
return $tag['id'];
if (!empty($type)) {
$fields['type'] = $type;
}
DBA::insert('tag', $fields, Database::INSERT_IGNORE);
@ -273,7 +276,7 @@ class Tag
public static function getFromBody(string $body, string $tags = null)
{
if (is_null($tags)) {
$tags = self::TAG_CHARACTER[self::HASHTAG] . self::TAG_CHARACTER[self::MENTION] . self::TAG_CHARACTER[self::EXCLUSIVE_MENTION];
$tags = self::TAG_CHARACTER[self::HASHTAG] . self::TAG_CHARACTER[self::MENTION] . self::TAG_CHARACTER[self::EXCLUSIVE_MENTION];
}
if (!preg_match_all("/([" . $tags . "])\[url\=([^\[\]]*)\]([^\[\]]*)\[\/url\]/ism", $body, $result, PREG_SET_ORDER)) {
@ -295,16 +298,29 @@ class Tag
{
Logger::info('Check for tags', ['uri-id' => $uriid, 'hash' => $tags, 'callstack' => System::callstack()]);
$result = self::getFromBody($body, $tags);
if (empty($result)) {
return;
if (is_null($tags)) {
$tags = self::TAG_CHARACTER[self::HASHTAG] . self::TAG_CHARACTER[self::MENTION] . self::TAG_CHARACTER[self::EXCLUSIVE_MENTION];
}
Logger::info('Found tags', ['uri-id' => $uriid, 'hash' => $tags, 'result' => $result]);
// Only remove the shared data from "real" reshares
$shared = BBCode::fetchShareAttributes($body);
if (!empty($shared['guid'])) {
if (preg_match("/\s*\[share .*?\](.*?)\[\/share\]\s*/ism", $body, $matches)) {
$share_body = $matches[1];
}
$body = preg_replace("/\s*\[share .*?\].*?\[\/share\]\s*/ism", '', $body);
}
foreach ($result as $tag) {
foreach (self::getFromBody($body, $tags) as $tag) {
self::storeByHash($uriid, $tag[1], $tag[3], $tag[2], $probing);
}
// Search for hashtags in the shared body (but only if hashtags are wanted)
if (!empty($share_body) && (strpos($tags, self::TAG_CHARACTER[self::HASHTAG]) !== false)) {
foreach (self::getFromBody($share_body, self::TAG_CHARACTER[self::HASHTAG]) as $tag) {
self::storeByHash($uriid, $tag[1], $tag[3], $tag[2], $probing);
}
}
}
/**

View File

@ -43,6 +43,7 @@ class Federation extends BaseAdmin
'diaspora' => ['name' => 'Diaspora', 'color' => '#a1a1a1'], // logo is black and white, makes a gray
'funkwhale' => ['name' => 'Funkwhale', 'color' => '#4082B4'], // From the homepage
'gnusocial' => ['name' => 'GNU Social/Statusnet', 'color' => '#a22430'], // dark red from the logo
'gotosocial' => ['name' => 'GoToSocial', 'color' => '#df8958'], // Some color from their mascot
'hometown' => ['name' => 'Hometown', 'color' => '#1f70c1'], // Color from the Patreon page
'hubzilla' => ['name' => 'Hubzilla/Red Matrix', 'color' => '#43488a'], // blue from the logo
'lemmy' => ['name' => 'Lemmy', 'color' => '#00c853'], // Green from the page
@ -154,7 +155,7 @@ class Federation extends BaseAdmin
$versionCounts = self::reformaDiasporaVersions($versionCounts);
} elseif ($platform == 'relay') {
$versionCounts = self::reformatRelayVersions($versionCounts);
} elseif (in_array($platform, ['funkwhale', 'mastodon', 'mobilizon', 'misskey'])) {
} elseif (in_array($platform, ['funkwhale', 'mastodon', 'mobilizon', 'misskey', 'gotosocial'])) {
$versionCounts = self::removeVersionSuffixes($versionCounts);
}

View File

@ -21,6 +21,7 @@
namespace Friendica\Module\Admin;
use Friendica\Core\System;
use Friendica\Module\BaseAdmin;
class PhpInfo extends BaseAdmin
@ -30,6 +31,6 @@ class PhpInfo extends BaseAdmin
self::checkAdminAccess();
phpinfo();
exit();
System::exit();
}
}

View File

@ -22,6 +22,7 @@
namespace Friendica\Module\Admin;
use Friendica\App;
use Friendica\Core\Relocate;
use Friendica\Core\Renderer;
use Friendica\Core\Search;
use Friendica\Core\System;
@ -60,74 +61,6 @@ class Site extends BaseAdmin
return;
}
// relocate
// @TODO This file could benefit from moving this feature away in a Module\Admin\Relocate class for example
if (!empty($_POST['relocate']) && !empty($_POST['relocate_url']) && $_POST['relocate_url'] != "") {
$new_url = $_POST['relocate_url'];
$new_url = rtrim($new_url, "/");
$parsed = @parse_url($new_url);
if (!is_array($parsed) || empty($parsed['host']) || empty($parsed['scheme'])) {
notice(DI::l10n()->t("Can not parse base url. Must have at least <scheme>://<domain>"));
DI::baseUrl()->redirect('admin/site');
}
/* steps:
* replace all "baseurl" to "new_url" in config, profile, term, items and contacts
* send relocate for every local user
* */
$old_url = DI::baseUrl()->get(true);
// Generate host names for relocation the addresses in the format user@address.tld
$new_host = str_replace("http://", "@", Strings::normaliseLink($new_url));
$old_host = str_replace("http://", "@", Strings::normaliseLink($old_url));
function update_table(App $a, $table_name, $fields, $old_url, $new_url)
{
$dbold = DBA::escape($old_url);
$dbnew = DBA::escape($new_url);
$upd = [];
foreach ($fields as $f) {
$upd[] = "`$f` = REPLACE(`$f`, '$dbold', '$dbnew')";
}
$upds = implode(", ", $upd);
$r = DBA::e(sprintf("UPDATE %s SET %s;", $table_name, $upds));
if (!DBA::isResult($r)) {
notice("Failed updating '$table_name': " . DBA::errorMessage());
DI::baseUrl()->redirect('admin/site');
}
}
// update tables
// update profile links in the format "http://server.tld"
update_table($a, "profile", ['photo', 'thumb'], $old_url, $new_url);
update_table($a, "contact", ['photo', 'thumb', 'micro', 'url', 'nurl', 'alias', 'request', 'notify', 'poll', 'confirm', 'poco', 'avatar'], $old_url, $new_url);
update_table($a, "post-content", ['body'], $old_url, $new_url);
// update profile addresses in the format "user@server.tld"
update_table($a, "contact", ['addr'], $old_host, $new_host);
// update config
DI::config()->set('system', 'url', $new_url);
DI::baseUrl()->saveByURL($new_url);
// send relocate
$usersStmt = DBA::select('user', ['uid'], ['account_removed' => false, 'account_expired' => false]);
while ($user = DBA::fetch($usersStmt)) {
Worker::add(PRIORITY_HIGH, 'Notifier', Delivery::RELOCATION, $user['uid']);
}
DBA::close($usersStmt);
info(DI::l10n()->t("Relocation started. Could take a while to complete."));
DI::baseUrl()->redirect('admin/site');
}
// end relocate
$sitename = (!empty($_POST['sitename']) ? trim($_POST['sitename']) : '');
$sender_email = (!empty($_POST['sender_email']) ? trim($_POST['sender_email']) : '');
$banner = (!empty($_POST['banner']) ? trim($_POST['banner']) : false);
@ -512,8 +445,9 @@ class Site extends BaseAdmin
'$no_relay_list' => DI::l10n()->t('The system is not subscribed to any relays at the moment.'),
'$relay_list_title' => DI::l10n()->t('The system is currently subscribed to the following relays:'),
'$relay_list' => Relay::getList(['url']),
'$relocate' => DI::l10n()->t('Relocate Instance'),
'$relocate_warning' => DI::l10n()->t('<strong>Warning!</strong> Advanced function. Could make this server unreachable.'),
'$relocate' => DI::l10n()->t('Relocate Node'),
'$relocate_msg' => DI::l10n()->t('Relocating your node enables you to change the DNS domain of this node and keep all the existing users and posts. This process takes a while and can only be started from the relocate console command like this:'),
'$relocate_cmd' => DI::l10n()->t('(Friendica directory)# bin/console relocate https://newdomain.com'),
'$baseurl' => DI::baseUrl()->get(true),
// name, label, value, help string, extra data...
@ -601,8 +535,6 @@ class Site extends BaseAdmin
'$temppath' => ['temppath', DI::l10n()->t('Temp path'), DI::config()->get('system', 'temppath'), DI::l10n()->t('If you have a restricted system where the webserver can\'t access the system temp path, enter another path here.')],
'$only_tag_search' => ['only_tag_search', DI::l10n()->t('Only search in tags'), DI::config()->get('system', 'only_tag_search'), DI::l10n()->t('On large systems the text search can slow down the system extremely.')],
'$relocate_url' => ['relocate_url', DI::l10n()->t('New base url'), DI::baseUrl()->get(), DI::l10n()->t('Change base url for this server. Sends relocate message to all Friendica and Diaspora* contacts of all users.')],
'$worker_queues' => ['worker_queues', DI::l10n()->t('Maximum number of parallel workers'), DI::config()->get('system', 'worker_queues'), DI::l10n()->t('On shared hosters set this to %d. On larger systems, values of %d are great. Default value is %d.', 5, 20, 10)],
'$worker_fastlane' => ['worker_fastlane', DI::l10n()->t('Enable fastlane'), DI::config()->get('system', 'worker_fastlane'), DI::l10n()->t('When enabed, the fastlane mechanism starts an additional worker if processes with higher priority are blocked by processes of lower priority.')],

View File

@ -46,7 +46,7 @@ class Pin extends BaseApi
DI::mstdnError()->RecordNotFound();
}
Post\Collection::add($this->parameters['id'], Post\Collection::FEATURED);
Post\Collection::add($this->parameters['id'], Post\Collection::FEATURED, $uid);
System::jsonExit(DI::mstdnStatus()->createFromUriId($this->parameters['id'], $uid)->toArray());
}

View File

@ -46,7 +46,7 @@ class Unpin extends BaseApi
DI::mstdnError()->RecordNotFound();
}
Post\Collection::remove($this->parameters['id'], Post\Collection::FEATURED);
Post\Collection::remove($this->parameters['id'], Post\Collection::FEATURED, $uid);
System::jsonExit(DI::mstdnStatus()->createFromUriId($this->parameters['id'], $uid)->toArray());
}

View File

@ -23,6 +23,7 @@ namespace Friendica\Module;
use Friendica\BaseModule;
use Friendica\Core\Logger;
use Friendica\Core\System;
use Friendica\DI;
use Friendica\Model\Attach as MAttach;
@ -72,7 +73,7 @@ class Attach extends BaseModule
}
echo $data;
exit();
System::exit();
// NOTREACHED
}
}

View File

@ -30,6 +30,7 @@ use Friendica\Model;
use Friendica\Network\HTTPException;
use Friendica\Object\Search\ContactResult;
use Friendica\Object\Search\ResultList;
use Friendica\Util\Network;
/**
* Base class for search modules
@ -68,7 +69,7 @@ class BaseSearch extends BaseModule
$header = DI::l10n()->t('People Search - %s', $search);
if (strrpos($search, '@') > 0) {
$results = Search::getContactsFromProbe($search);
$results = Search::getContactsFromProbe(Network::convertToIdn($search));
}
}
@ -78,6 +79,8 @@ class BaseSearch extends BaseModule
$header = DI::l10n()->t('Forum Search - %s', $search);
}
$search = Network::convertToIdn($search);
if (DI::mode()->isMobile()) {
$itemsPerPage = DI::pConfig()->get(local_user(), 'system', 'itemspage_mobile_network',
DI::config()->get('system', 'itemspage_network_mobile'));

View File

@ -24,6 +24,7 @@ namespace Friendica\Module\Contact;
use Friendica\BaseModule;
use Friendica\Core\Renderer;
use Friendica\Core\Session;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Contact;
@ -107,6 +108,6 @@ class Hovercard extends BaseModule
]);
echo $o;
exit();
System::exit();
}
}

View File

@ -22,6 +22,7 @@
namespace Friendica\Module\Debug;
use Friendica\BaseModule;
use Friendica\Core\System;
use Friendica\DI;
use Friendica\Model\Post;
use Friendica\Network\HTTPException;
@ -48,7 +49,7 @@ class ItemBody extends BaseModule
if (!empty($item)) {
if (DI::mode()->isAjax()) {
echo str_replace("\n", '<br />', $item['body']);
exit();
System::exit();
} else {
return str_replace("\n", '<br />', $item['body']);
}

View File

@ -25,6 +25,7 @@ use Friendica\BaseModule;
use Friendica\Core\System;
use Friendica\DI;
use Friendica\Protocol\Feed as ProtocolFeed;
use Friendica\Network\HTTPException;
/**
* Provides public Atom feeds
@ -42,7 +43,7 @@ use Friendica\Protocol\Feed as ProtocolFeed;
*/
class Feed extends BaseModule
{
protected function content(array $request = []): string
protected function rawContent(array $request = [])
{
$last_update = $request['last_update'] ?? '';
$nocache = !empty($request['nocache']) && local_user();
@ -66,6 +67,11 @@ class Feed extends BaseModule
$type = 'posts';
}
System::httpExit(ProtocolFeed::atom($this->parameters['nickname'], $last_update, 10, $type, $nocache, true), Response::TYPE_ATOM);
$feed = ProtocolFeed::atom($this->parameters['nickname'], $last_update, 10, $type, $nocache, true);
if (empty($feed)) {
throw new HTTPException\NotFoundException(DI::l10n()->t('User not found.'));
}
System::httpExit($feed, Response::TYPE_ATOM);
}
}

View File

@ -360,7 +360,7 @@ class Group extends BaseModule
if ($change) {
$tpl = Renderer::getMarkupTemplate('groupeditor.tpl');
echo Renderer::replaceMacros($tpl, $context);
exit();
System::exit();
}
return Renderer::replaceMacros($tpl, $context);

View File

@ -22,6 +22,7 @@
namespace Friendica\Module\HTTPException;
use Friendica\BaseModule;
use Friendica\Core\System;
use Friendica\DI;
use Friendica\Network\HTTPException;
use Psr\Http\Message\ResponseInterface;
@ -47,7 +48,7 @@ class PageNotFound extends BaseModule
$queryString = $this->server['QUERY_STRING'];
// Stupid browser tried to pre-fetch our Javascript img template. Don't log the event or return anything - just quietly exit.
if (!empty($queryString) && preg_match('/{[0-9]}/', $queryString) !== 0) {
exit();
System::exit();
}
if (!empty($queryString) && ($queryString === 'q=internal_error.html') && isset($dreamhost_error_hack)) {

View File

@ -60,9 +60,9 @@ class Pin extends BaseModule
$pinned = !$item['featured'];
if ($pinned) {
Post\Collection::add($item['uri-id'], Post\Collection::FEATURED);
Post\Collection::add($item['uri-id'], Post\Collection::FEATURED, local_user());
} else {
Post\Collection::remove($item['uri-id'], Post\Collection::FEATURED);
Post\Collection::remove($item['uri-id'], Post\Collection::FEATURED, local_user());
}
// See if we've been passed a return path to redirect to

View File

@ -22,6 +22,7 @@
namespace Friendica\Module;
use Friendica\BaseModule;
use Friendica\Core\Cache\Enum\Duration;
use Friendica\Core\Protocol;
use Friendica\Core\System;
use Friendica\Database\DBA;
@ -35,6 +36,8 @@ use Friendica\Model\User;
*/
class NoScrape extends BaseModule
{
const CACHEKEY = 'noscrape:';
protected function rawContent(array $request = [])
{
$a = DI::app();
@ -55,6 +58,12 @@ class NoScrape extends BaseModule
System::jsonError(404, 'Profile not found');
}
$cachekey = self::CACHEKEY . $owner['uid'];
$result = DI::cache()->get($cachekey);
if (!is_null($result)) {
System::jsonExit($result);
}
$json_info = [
'addr' => $owner['addr'],
'nick' => $which,
@ -126,6 +135,8 @@ class NoScrape extends BaseModule
}
}
DI::cache()->set($cachekey, $json_info, Duration::DAY);
System::jsonExit($json_info);
}
}

View File

@ -187,6 +187,9 @@ class Ping extends BaseModule
$owner = User::getOwnerDataById(local_user());
$navNotifications = array_map(function (Entity\Notification $notification) use ($owner) {
if (!DI::notify()->NotifyOnDesktop($notification)) {
return null;
}
if (($notification->type == Post\UserNotification::TYPE_NONE) && in_array($owner['page-flags'], [User::PAGE_FLAGS_NORMAL, User::PAGE_FLAGS_PRVGROUP])) {
return null;
}

View File

@ -23,6 +23,7 @@ namespace Friendica\Module;
use Friendica\BaseModule;
use Friendica\Content;
use Friendica\Core\System;
use Friendica\DI;
use Friendica\Util\Strings;
@ -43,14 +44,14 @@ class Oembed extends BaseModule
if (DI::args()->getArgv()[1] == 'b2h') {
$url = ["", trim(hex2bin($_REQUEST['url']))];
echo Content\OEmbed::replaceCallback($url);
exit();
System::exit();
}
// Unused form: /oembed/h2b?text=...
if (DI::args()->getArgv()[1] == 'h2b') {
$text = trim(hex2bin($_REQUEST['text']));
echo Content\OEmbed::HTML2BBCode($text);
exit();
System::exit();
}
// @TODO: Replace with parameter from router
@ -68,6 +69,6 @@ class Oembed extends BaseModule
echo $j->html;
echo '</body></html>';
}
exit();
System::exit();
}
}

View File

@ -22,6 +22,7 @@
namespace Friendica\Module;
use Friendica\Core\Hook;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\APContact;
@ -164,8 +165,7 @@ class PermissionTooltip extends \Friendica\BaseModule
} else {
echo $o . $receivers;
}
exit();
System::exit();
}
/**

View File

@ -32,6 +32,7 @@ use Friendica\Model\Post;
use Friendica\Model\Profile;
use Friendica\Core\Storage\Type\ExternalResource;
use Friendica\Core\Storage\Type\SystemResource;
use Friendica\Core\System;
use Friendica\Core\Worker;
use Friendica\Model\User;
use Friendica\Network\HTTPClient\Client\HttpClientAccept;
@ -224,7 +225,7 @@ class Photo extends BaseModule
'output' => number_format($output, 3), 'rest' => number_format($rest, 3)]);
}
exit();
System::exit();
}
private static function getPhotoByid(int $id, $type, $customsize)
@ -304,10 +305,12 @@ class Photo extends BaseModule
$photo = MPhoto::selectFirst([], ['resource-id' => $resourceid], ['order' => ['scale']]);
if (!empty($photo)) {
return $photo;
} else {
$url = $contact['avatar'];
}
} else {
$url = $contact['photo'];
}
// We continue with the avatar link when the photo link is invalid
$url = $contact['avatar'];
} elseif (!empty($contact['avatar'])) {
$url = $contact['avatar'];
}

View File

@ -49,7 +49,7 @@ class Profile extends BaseProfile
protected function rawContent(array $request = [])
{
if (ActivityPub::isRequest()) {
$user = DBA::selectFirst('user', ['uid'], ['nickname' => $this->parameters['nickname']]);
$user = DBA::selectFirst('user', ['uid'], ['nickname' => $this->parameters['nickname'], 'account_removed' => false]);
if (DBA::isResult($user)) {
try {
$data = ActivityPub\Transmitter::getProfile($user['uid']);

View File

@ -202,6 +202,6 @@ class Proxy extends BaseModule
header('Expires: ' . gmdate('D, d M Y H:i:s', time() + (31536000)) . ' GMT');
header('Cache-Control: max-age=31536000');
echo $img->asString();
exit();
System::exit();
}
}

View File

@ -22,6 +22,7 @@
namespace Friendica\Module;
use Friendica\BaseModule;
use Friendica\Core\System;
/**
* Return the default robots.txt
@ -44,6 +45,6 @@ class RobotsTxt extends BaseModule
foreach ($allDisalloweds as $disallowed) {
echo 'Disallow: ' . $disallowed . PHP_EOL;
}
exit();
System::exit();
}
}

View File

@ -38,6 +38,7 @@ use Friendica\Model\Post;
use Friendica\Model\Tag;
use Friendica\Module\BaseSearch;
use Friendica\Network\HTTPException;
use Friendica\Util\Network;
class Index extends BaseSearch
{
@ -226,7 +227,8 @@ class Index extends BaseSearch
*/
private static function tryRedirectToProfile(string $search)
{
$isUrl = !empty(parse_url($search, PHP_URL_SCHEME));
$search = Network::convertToIdn($search);
$isUrl = !empty(parse_url($search, PHP_URL_SCHEME));
$isAddr = (bool)preg_match('/^@?([a-z0-9.-_]+@[a-z0-9.-_:]+)$/i', trim($search), $matches);
if (!$isUrl && !$isAddr) {
@ -274,6 +276,8 @@ class Index extends BaseSearch
return;
}
$search = Network::convertToIdn($search);
if (local_user()) {
// Post URL search
$item_id = Item::fetchByLink($search, local_user());

View File

@ -349,7 +349,7 @@ class Account extends BaseSettings
// "http" or "@" to be present in the string.
// All other fields from the row will be ignored
if ((strpos($csvRow[0], '@') !== false) || in_array(parse_url($csvRow[0], PHP_URL_SCHEME), ['http', 'https'])) {
Worker::add(PRIORITY_LOW, 'AddContact', $_SESSION['uid'], $csvRow[0]);
Worker::add(PRIORITY_MEDIUM, 'AddContact', local_user(), $csvRow[0]);
} else {
Logger::notice('Invalid account', ['url' => $csvRow[0]]);
}

View File

@ -23,6 +23,7 @@ namespace Friendica\Module\Settings;
use Friendica\Core\Hook;
use Friendica\Core\Renderer;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\Database\DBStructure;
use Friendica\DI;
@ -112,8 +113,7 @@ class UserExport extends BaseSettings
self::exportContactsAsCSV(local_user());
break;
}
exit();
System::exit();
}
}

View File

@ -22,7 +22,7 @@
namespace Friendica\Module;
use Friendica\BaseModule;
use Friendica\DI;
use Friendica\Core\System;
use Friendica\Util\Strings;
/**
@ -45,7 +45,6 @@ class Theme extends BaseModule
if (file_exists("view/theme/$theme/style.php")) {
require_once "view/theme/$theme/style.php";
}
exit();
System::exit();
}
}

View File

@ -48,6 +48,6 @@ class ThemeDetails extends BaseModule
'credits' => $credits,
]);
}
exit();
System::exit();
}
}

View File

@ -31,7 +31,7 @@ class Network extends NetworkModule
protected function rawContent(array $request = [])
{
if (!isset($_GET['p']) || !isset($_GET['item'])) {
exit();
System::exit();
}
$this->parseRequest($_GET);

View File

@ -25,7 +25,10 @@ use Friendica\App\BaseURL;
use Friendica\BaseFactory;
use Friendica\Capabilities\ICanCreateFromTableRow;
use Friendica\Contact\LocalRelationship\Repository\LocalRelationship;
use Friendica\Content\Text\BBCode;
use Friendica\Content\Text\Plaintext;
use Friendica\Core\Cache\Enum\Duration;
use Friendica\Core\Cache\Capability\ICanCache;
use Friendica\Core\L10n;
use Friendica\Model\Contact;
use Friendica\Model\Post;
@ -43,14 +46,17 @@ class Notification extends BaseFactory implements ICanCreateFromTableRow
private $l10n;
/** @var LocalRelationship */
private $localRelationshipRepo;
/** @var ICanCache */
private $cache;
public function __construct(\Friendica\App\BaseURL $baseUrl, \Friendica\Core\L10n $l10n, \Friendica\Contact\LocalRelationship\Repository\LocalRelationship $localRelationshipRepo, LoggerInterface $logger)
public function __construct(\Friendica\App\BaseURL $baseUrl, \Friendica\Core\L10n $l10n, \Friendica\Contact\LocalRelationship\Repository\LocalRelationship $localRelationshipRepo, LoggerInterface $logger, ICanCache $cache)
{
parent::__construct($logger);
$this->baseUrl = $baseUrl;
$this->l10n = $l10n;
$this->localRelationshipRepo = $localRelationshipRepo;
$this->cache = $cache;
}
public function createFromTableRow(array $row): Entity\Notification
@ -107,6 +113,12 @@ class Notification extends BaseFactory implements ICanCreateFromTableRow
{
$message = [];
$cachekey = 'Notification:' . $Notification->id;
$result = $this->cache->get($cachekey);
if (!is_null($result)) {
return $result;
}
$causer = $author = Contact::getById($Notification->actorId, ['id', 'name', 'url', 'contact-type', 'pending']);
if (empty($causer)) {
$this->logger->info('Causer not found', ['contact' => $Notification->actorId]);
@ -132,7 +144,7 @@ class Notification extends BaseFactory implements ICanCreateFromTableRow
$this->logger->info('Thread is ignored', ['parent-uri-id' => $Notification->parentUriId, 'type' => $Notification->type]);
return $message;
}
if (in_array($Notification->type, [Post\UserNotification::TYPE_THREAD_COMMENT, Post\UserNotification::TYPE_COMMENT_PARTICIPATION, Post\UserNotification::TYPE_ACTIVITY_PARTICIPATION, Post\UserNotification::TYPE_EXPLICIT_TAGGED])) {
$item = Post::selectFirst([], ['uri-id' => $Notification->parentUriId, 'uid' => [0, $Notification->uid]], ['order' => ['uid' => true]]);
if (empty($item)) {
@ -170,11 +182,10 @@ class Notification extends BaseFactory implements ICanCreateFromTableRow
$link = $this->baseUrl . '/display/' . urlencode($link_item['guid']);
$content = Plaintext::getPost($item, 70);
if (!empty($content['text'])) {
$title = '"' . trim(str_replace("\n", " ", $content['text'])) . '"';
} else {
$title = '';
$body = BBCode::toPlaintext($item['body'], false);
$title = Plaintext::shorten($body, 70);
if (!empty($title)) {
$title = '"' . trim(str_replace("\n", " ", $title)) . '"';
}
$this->logger->debug('Got verb and type', ['verb' => $Notification->verb, 'type' => $Notification->type, 'causer' => $causer['id'], 'author' => $author['id'], 'item' => $item['id'], 'uid' => $Notification->uid]);
@ -306,6 +317,7 @@ class Notification extends BaseFactory implements ICanCreateFromTableRow
'[url=' . $link . ']' . $title . '[/url]',
'[url=' . $author['url'] . ']' . $author['name'] . '[/url]');
$message['link'] = $link;
$this->cache->set($cachekey, $message, Duration::HOUR);
} else {
$this->logger->debug('Unhandled notification', ['notification' => $Notification]);
}

View File

@ -23,8 +23,10 @@ namespace Friendica\Navigation\Notifications\Repository;
use Friendica\App\BaseURL;
use Friendica\BaseRepository;
use Friendica\Content\Text\BBCode;
use Friendica\Content\Text\Plaintext;
use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\PConfig\Capability\IManagePersonalConfigValues;
use Friendica\Core\Hook;
use Friendica\Core\L10n;
use Friendica\Core\System;
@ -36,6 +38,7 @@ use Friendica\Navigation\Notifications\Entity;
use Friendica\Navigation\Notifications\Exception;
use Friendica\Navigation\Notifications\Factory;
use Friendica\Network\HTTPException;
use Friendica\Object\Api\Mastodon\Notification;
use Friendica\Protocol\Activity;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Emailer;
@ -58,6 +61,9 @@ class Notify extends BaseRepository
/** @var IManageConfigValues */
protected $config;
/** @var IManagePersonalConfigValues */
private $pConfig;
/** @var Emailer */
protected $emailer;
@ -66,11 +72,12 @@ class Notify extends BaseRepository
protected static $table_name = 'notify';
public function __construct(Database $database, LoggerInterface $logger, L10n $l10n, BaseURL $baseUrl, IManageConfigValues $config, Emailer $emailer, Factory\Notification $notification, Factory\Notify $factory = null)
public function __construct(Database $database, LoggerInterface $logger, L10n $l10n, BaseURL $baseUrl, IManageConfigValues $config, IManagePersonalConfigValues $pConfig, Emailer $emailer, Factory\Notification $notification, Factory\Notify $factory = null)
{
$this->l10n = $l10n;
$this->baseUrl = $baseUrl;
$this->config = $config;
$this->pConfig = $pConfig;
$this->emailer = $emailer;
$this->notification = $notification;
@ -301,11 +308,10 @@ class Notify extends BaseRepository
$item_post_type = Model\Item::postType($item, $l10n);
$content = Plaintext::getPost($item, 70);
if (!empty($content['text'])) {
$title = '"' . trim(str_replace("\n", " ", $content['text'])) . '"';
} else {
$title = '';
$body = BBCode::toPlaintext($item['body'], false);
$title = Plaintext::shorten($body, 70);
if (!empty($title)) {
$title = '"' . trim(str_replace("\n", " ", $title)) . '"';
}
// First go for the general message
@ -651,6 +657,27 @@ class Notify extends BaseRepository
return false;
}
public function NotifyOnDesktop(Entity\Notification $Notification, string $type = null): bool
{
if (is_null($type)) {
$type = \Friendica\Factory\Api\Mastodon\Notification::getType($Notification);
}
if (!in_array($type, [Notification::TYPE_RESHARE, Notification::TYPE_LIKE])) {
return true;
}
if ($this->pConfig->get($Notification->uid, 'system', 'notify_like') && ($type == Notification::TYPE_LIKE)) {
return true;
}
if ($this->pConfig->get($Notification->uid, 'system', 'notify_announce') && ($type == Notification::TYPE_RESHARE)) {
return true;
}
return false;
}
public function createFromNotification(Entity\Notification $Notification)
{
$this->logger->info('Start', ['uid' => $Notification->uid, 'id' => $Notification->id, 'type' => $Notification->type]);
@ -710,11 +737,10 @@ class Notify extends BaseRepository
return false;
}
$content = Plaintext::getPost($item, 70);
if (!empty($content['text'])) {
$title = '"' . trim(str_replace("\n", " ", $content['text'])) . '"';
} else {
$title = $item['title'];
$body = BBCode::toPlaintext($item['body'], false);
$title = Plaintext::shorten($body, 70);
if (!empty($title)) {
$title = '"' . trim(str_replace("\n", " ", $title)) . '"';
}
// Some mail software relies on subject field for threading.

View File

@ -140,7 +140,7 @@ class HttpClient implements ICanSendHttpRequests
}
};
if (empty($conf[HttpClientOptions::HEADERS]['Accept'])) {
if (empty($conf[HttpClientOptions::HEADERS]['Accept']) && in_array($method, ['get', 'head'])) {
$this->logger->info('Accept header was missing, using default.', ['url' => $url, 'callstack' => System::callstack()]);
$conf[HttpClientOptions::HEADERS]['Accept'] = HttpClientAccept::DEFAULT;
}

View File

@ -68,6 +68,8 @@ class Probe
// At first remove leading and trailing junk
$rawUri = trim($rawUri, "@#?:/ \t\n\r\0\x0B");
$rawUri = Network::convertToIdn($rawUri);
$uri = new Uri($rawUri);
if (!$uri->getScheme()) {
return $uri->__toString();
@ -243,49 +245,6 @@ class Probe
return $lrdd;
}
/**
* Perform Webfinger lookup and return DFRN data
*
* Given an email style address, perform webfinger lookup and
* return the resulting DFRN profile URL, or if no DFRN profile URL
* is located, returns an OStatus subscription template (prefixed
* with the string 'stat:' to identify it as on OStatus template).
* If this isn't an email style address just return $webbie.
* Return an empty string if email-style addresses but webfinger fails,
* or if the resultant personal XRD doesn't contain a supported
* subscription/friend-request attribute.
*
* amended 7/9/2011 to return an hcard which could save potentially loading
* a lengthy content page to scrape dfrn attributes
*
* @param string $webbie Address that should be probed
* @param string $hcard_url Link to the hcard - is returned by reference
*
* @return string profile link
* @throws HTTPException\InternalServerErrorException
*/
public static function webfingerDfrn(string $webbie, string &$hcard_url)
{
$profile_link = '';
$links = self::lrdd($webbie);
Logger::debug('Result', ['url' => $webbie, 'links' => $links]);
if (!empty($links) && is_array($links)) {
foreach ($links as $link) {
if ($link['@attributes']['rel'] === ActivityNamespace::DFRN) {
$profile_link = $link['@attributes']['href'];
}
if (($link['@attributes']['rel'] === ActivityNamespace::OSTATUSSUB) && ($profile_link == "")) {
$profile_link = 'stat:'.$link['@attributes']['template'];
}
if ($link['@attributes']['rel'] === 'http://microformats.org/profile/hcard') {
$hcard_url = $link['@attributes']['href'];
}
}
}
return $profile_link;
}
/**
* Check an URI for LRDD data
*

View File

@ -159,15 +159,24 @@ class Image
}
$this->valid = false;
$this->image = @imagecreatefromstring($data);
if ($this->image !== false) {
$this->width = imagesx($this->image);
$this->height = imagesy($this->image);
$this->valid = true;
imagealphablending($this->image, false);
imagesavealpha($this->image, true);
try {
$this->image = @imagecreatefromstring($data);
if ($this->image !== false) {
$this->width = imagesx($this->image);
$this->height = imagesy($this->image);
$this->valid = true;
imagealphablending($this->image, false);
imagesavealpha($this->image, true);
return true;
return true;
}
} catch (\Throwable $error) {
/** @see https://github.com/php/doc-en/commit/d09a881a8e9059d11e756ee59d75bf404d6941ed */
if (strstr($error->getMessage(), "gd-webp cannot allocate temporary buffer")) {
DI::logger()->notice('Image is probably animated and therefore unsupported', ['error' => $error]);
} else {
DI::logger()->warning('Unexpected throwable.', ['error' => $error]);
}
}
return false;

View File

@ -29,10 +29,10 @@ 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\Contact;
use Friendica\Model\Item;
use Friendica\Model\Photo;
use Friendica\Model\Post as PostModel;
use Friendica\Model\Tag;
use Friendica\Model\User;
@ -124,8 +124,8 @@ class Post
/**
* Fetch the privacy of the post
*
* @param array $item
* @return string
* @param array $item
* @return string
*/
private function fetchPrivacy(array $item):string
{
@ -482,7 +482,7 @@ class Post
'profile_url' => $profile_link,
'name' => $profile_name,
'item_photo_menu_html' => DI::contentItem()->photoMenu($item, $formSecurityToken),
'thumb' => DI::baseUrl()->remove(Contact::getAvatarUrlForUrl($item['author-link'], $item['uid'], Proxy::SIZE_THUMB)),
'thumb' => DI::baseUrl()->remove(DI::contentItem()->getAuthorAvatar($item)),
'osparkle' => $osparkle,
'sparkle' => $sparkle,
'title' => $title,
@ -499,7 +499,7 @@ class Post
'shiny' => $shiny,
'owner_self' => $item['author-link'] == Session::get('my_url'),
'owner_url' => $this->getOwnerUrl(),
'owner_photo' => DI::baseUrl()->remove(Contact::getAvatarUrlForUrl($item['owner-link'], $item['uid'], Proxy::SIZE_THUMB)),
'owner_photo' => DI::baseUrl()->remove(DI::contentItem()->getOwnerAvatar($item)),
'owner_name' => $this->getOwnerName(),
'plink' => Item::getPlink($item),
'browsershare' => $browsershare,
@ -529,8 +529,8 @@ class Post
'thread_level' => $thread_level,
'edited' => $edited,
'network' => $item["network"],
'network_name' => ContactSelector::networkToName($item['author-network'], $item['author-link'], $item['network']),
'network_icon' => ContactSelector::networkToIcon($item['network'], $item['author-link']),
'network_name' => ContactSelector::networkToName($item['author-network'], $item['author-link'], $item['network'], $item['author-gsid']),
'network_icon' => ContactSelector::networkToIcon($item['network'], $item['author-link'], $item['author-gsid']),
'received' => $item['received'],
'commented' => $item['commented'],
'created_date' => $item['created'],
@ -898,12 +898,7 @@ class Post
}
$owner = User::getOwnerDataById($a->getLoggedInUserId());
$item = PostModel::selectFirst(['author-addr', 'uri-id', 'network', 'gravity', 'content-warning'], ['id' => $this->getId()]);
if (!DBA::isResult($item) || empty($item['author-addr'])) {
// Should not happen
return '';
}
$item = $this->getData();
if (!empty($item['content-warning']) && Feature::isEnabled(local_user(), 'add_abstract')) {
$text = '[abstract=' . Protocol::ACTIVITYPUB . ']' . $item['content-warning'] . "[/abstract]\n";
@ -967,7 +962,7 @@ class Post
$uid = $conv->getProfileOwner();
$parent_uid = $this->getDataValue('uid');
$contact = Contact::getById($a->getContactId());
$owner = User::getOwnerDataById($a->getLoggedInUserId());
$default_text = $this->getDefaultText();
@ -986,9 +981,9 @@ class Post
'$qcomment' => $qcomment,
'$default' => $default_text,
'$profile_uid' => $uid,
'$mylink' => DI::baseUrl()->remove($contact['url'] ?? ''),
'$mylink' => DI::baseUrl()->remove($owner['url'] ?? ''),
'$mytitle' => DI::l10n()->t('This is you'),
'$myphoto' => DI::baseUrl()->remove($contact['thumb'] ?? ''),
'$myphoto' => DI::baseUrl()->remove($owner['thumb'] ?? ''),
'$comment' => DI::l10n()->t('Comment'),
'$submit' => DI::l10n()->t('Submit'),
'$loading' => DI::l10n()->t('Loading...'),

View File

@ -0,0 +1,222 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Protocol\ActivityPub;
use Friendica\Core\Logger;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Contact;
use Friendica\Model\GServer;
use Friendica\Model\Post;
use Friendica\Protocol\ActivityPub;
use Friendica\Util\HTTPSignature;
use Friendica\Worker\Delivery as WorkerDelivery;
class Delivery
{
/**
* Deliver posts to the given inbox
*
* @param string $inbox
* @return array with the elements "success" and "uri_ids" of the failed posts
*/
public static function deliver(string $inbox): array
{
$uri_ids = [];
$posts = Post\Delivery::selectForInbox($inbox);
$serverfail = false;
foreach ($posts as $post) {
if (!$serverfail) {
$result = self::deliverToInbox($post['command'], 0, $inbox, $post['uid'], $post['receivers'], $post['uri-id']);
if ($result['serverfailure']) {
// In a timeout situation we assume that every delivery to that inbox will time out.
// So we set the flag and try all deliveries at a later time.
Logger::info('Inbox delivery has a server failure', ['inbox' => $inbox]);
$serverfail = true;
}
}
if ($serverfail || (!$result['success'] && !$result['drop'])) {
$uri_ids[] = $post['uri-id'];
}
}
Logger::debug('Inbox delivery done', ['inbox' => $inbox, 'posts' => count($posts), 'failed' => count($uri_ids), 'serverfailure' => $serverfail]);
return ['success' => empty($uri_ids), 'uri_ids' => $uri_ids];
}
/**
* Deliver the given post to the given inbox
*
* @param string $cmd
* @param integer $item_id
* @param string $inbox
* @param integer $uid
* @param array $receivers
* @param integer $uri_id
* @return array
*/
public static function deliverToInbox(string $cmd, int $item_id, string $inbox, int $uid, array $receivers, int $uri_id): array
{
if (empty($item_id) && !empty($uri_id) && !empty($uid)) {
$item = Post::selectFirst(['id', 'parent', 'origin'], ['uri-id' => $uri_id, 'uid' => [$uid, 0]], ['order' => ['uid' => true]]);
if (empty($item['id'])) {
Logger::notice('Item not found, removing delivery', ['uri-id' => $uri_id, 'uid' => $uid, 'cmd' => $cmd, 'inbox' => $inbox]);
Post\Delivery::remove($uri_id, $inbox);
return true;
} else {
$item_id = $item['id'];
}
}
$success = true;
$serverfail = false;
$drop = false;
if ($cmd == WorkerDelivery::MAIL) {
$data = ActivityPub\Transmitter::createActivityFromMail($item_id);
if (!empty($data)) {
$success = HTTPSignature::transmit($data, $inbox, $uid);
}
} elseif ($cmd == WorkerDelivery::SUGGESTION) {
$success = ActivityPub\Transmitter::sendContactSuggestion($uid, $inbox, $item_id);
} elseif ($cmd == WorkerDelivery::RELOCATION) {
// @todo Implementation pending
} elseif ($cmd == WorkerDelivery::POKE) {
// Implementation not planned
} elseif ($cmd == WorkerDelivery::REMOVAL) {
$success = ActivityPub\Transmitter::sendProfileDeletion($uid, $inbox);
} elseif ($cmd == WorkerDelivery::PROFILEUPDATE) {
$success = ActivityPub\Transmitter::sendProfileUpdate($uid, $inbox);
} else {
$data = ActivityPub\Transmitter::createCachedActivityFromItem($item_id);
if (!empty($data)) {
$timestamp = microtime(true);
$response = HTTPSignature::post($data, $inbox, $uid);
$runtime = microtime(true) - $timestamp;
$success = $response->isSuccess();
$serverfail = $response->isTimeout();
if (!$success) {
// 5xx errors are problems on the server. We don't need to continue delivery then.
if (!$serverfail && ($response->getReturnCode() >= 500) && ($response->getReturnCode() <= 599)) {
$serverfail = true;
}
// A 404 means that the inbox doesn't exist. We can stop the delivery here.
if (!$serverfail && ($response->getReturnCode() == 404)) {
$serverfail = true;
}
$xrd_timeout = DI::config()->get('system', 'xrd_timeout');
if (!$serverfail && $xrd_timeout && ($runtime > $xrd_timeout)) {
$serverfail = true;
}
$curl_timeout = DI::config()->get('system', 'curl_timeout');
if (!$serverfail && $curl_timeout && ($runtime > $curl_timeout)) {
$serverfail = true;
}
// Resubscribe to relay server upon client error
if (!$serverfail && ($response->getReturnCode() >= 400) && ($response->getReturnCode() <= 499)) {
$actor = self:: fetchActorForRelayInbox($inbox);
if (!empty($actor)) {
$drop = !ActivityPub\Transmitter::sendRelayFollow($actor);
Logger::notice('Resubscribed to relay', ['url' => $actor, 'success' => !$drop]);
} elseif ($cmd = WorkerDelivery::DELETION) {
// Remote systems not always accept our deletion requests, so we drop them if rejected.
// Situation is: In Friendica we allow the thread owner to delete foreign comments to their thread.
// Most AP systems don't allow this, so they will reject the deletion request.
$drop = true;
}
}
Logger::info('Delivery failed', ['retcode' => $response->getReturnCode(), 'serverfailure' => $serverfail, 'drop' => $drop, 'runtime' => round($runtime, 3), 'uri-id' => $uri_id, 'uid' => $uid, 'item_id' => $item_id, 'cmd' => $cmd, 'inbox' => $inbox]);
}
if ($uri_id) {
if ($success) {
Post\Delivery::remove($uri_id, $inbox);
} else {
Post\Delivery::incrementFailed($uri_id, $inbox);
}
}
}
}
self::setSuccess($receivers, $success);
Logger::debug('Delivered', ['uri-id' => $uri_id, 'uid' => $uid, 'item_id' => $item_id, 'cmd' => $cmd, 'inbox' => $inbox, 'success' => $success, 'serverfailure' => $serverfail, 'drop' => $drop]);
if (($success || $drop) && in_array($cmd, [WorkerDelivery::POST])) {
Post\DeliveryData::incrementQueueDone($uri_id, Post\DeliveryData::ACTIVITYPUB);
}
return ['success' => $success, 'serverfailure' => $serverfail, 'drop' => $drop];
}
/**
* Fetch the actor of the given inbox of an relay server
*
* @param string $inbox
* @return string
*/
private static function fetchActorForRelayInbox(string $inbox): string
{
$apcontact = DBA::selectFirst('apcontact', ['url'], ["`sharedinbox` = ? AND `type` = ? AND `url` IN (SELECT `url` FROM `contact` WHERE `uid` = ? AND `rel` = ?)",
$inbox, 'Application', 0, Contact::FRIEND]);
return $apcontact['url'] ?? '';
}
/**
* mark or unmark the given receivers for archival upon succoess
*
* @param array $receivers
* @param boolean $success
* @return void
*/
private static function setSuccess(array $receivers, bool $success)
{
$gsid = null;
foreach ($receivers as $receiver) {
$contact = Contact::getById($receiver);
if (empty($contact)) {
continue;
}
$gsid = $gsid ?: $contact['gsid'];
if ($success) {
Contact::unmarkForArchival($contact);
} else {
Contact::markForArchival($contact);
}
}
if (!empty($gsid)) {
GServer::setProtocol($gsid, Post\DeliveryData::ACTIVITYPUB);
}
}
}

View File

@ -170,7 +170,7 @@ class Processor
}
if (!empty($activity['question']['end-time'])) {
$question['end-time'] = $activity['question']['end-time'];
$question['end-time'] = DateTimeFormat::utc($activity['question']['end-time']);
}
Post\Question::update($item['uri-id'], $question);
@ -215,6 +215,7 @@ class Processor
return;
}
Post\History::add($item['uri-id'], $item);
Item::update($item, ['uri' => $activity['id']]);
if ($activity['object_type'] == 'as:Event') {
@ -238,8 +239,12 @@ class Processor
$event['edited'] = DateTimeFormat::utc($activity['updated']);
$event['summary'] = HTML::toBBCode($activity['name']);
$event['desc'] = HTML::toBBCode($activity['content']);
$event['start'] = $activity['start-time'];
$event['finish'] = $activity['end-time'];
if (!empty($activity['start-time'])) {
$event['start'] = DateTimeFormat::utc($activity['start-time']);
}
if (!empty($activity['end-time'])) {
$event['finish'] = DateTimeFormat::utc($activity['end-time']);
}
$event['nofinish'] = empty($event['finish']);
$event['location'] = $activity['location'];
@ -558,8 +563,12 @@ class Processor
{
$event['summary'] = HTML::toBBCode($activity['name'] ?: $activity['summary']);
$event['desc'] = HTML::toBBCode($activity['content']);
$event['start'] = $activity['start-time'];
$event['finish'] = $activity['end-time'];
if (!empty($activity['start-time'])) {
$event['start'] = DateTimeFormat::utc($activity['start-time']);
}
if (!empty($activity['end-time'])) {
$event['finish'] = DateTimeFormat::utc($activity['end-time']);
}
$event['nofinish'] = empty($event['finish']);
$event['location'] = $activity['location'];
$event['cid'] = $item['contact-id'];

View File

@ -938,7 +938,7 @@ class Receiver
// Fetch the receivers for the public and the followers collection
if ((($receiver == $followers) || (($receiver == self::PUBLIC_COLLECTION) && !$is_forum)) && !empty($actor)) {
$receivers = self::getReceiverForActor($actor, $tags, $receivers, $follower_target);
$receivers = self::getReceiverForActor($actor, $tags, $receivers, $follower_target, $profile);
continue;
}
@ -1000,33 +1000,46 @@ class Receiver
* @param array $tags
* @param array $receivers
* @param integer $target_type
* @param array $profile
*
* @return array with receivers (user id)
* @throws \Exception
*/
private static function getReceiverForActor($actor, $tags, $receivers, $target_type)
private static function getReceiverForActor($actor, $tags, $receivers, $target_type, $profile)
{
$basecondition = ['rel' => [Contact::SHARING, Contact::FRIEND, Contact::FOLLOWER],
'network' => Protocol::FEDERATED, 'archive' => false, 'pending' => false];
$condition = DBA::mergeConditions($basecondition, ["`nurl` = ? AND `uid` != ?", Strings::normaliseLink($actor), 0]);
$contacts = DBA::select('contact', ['uid', 'rel'], $condition);
while ($contact = DBA::fetch($contacts)) {
if (empty($receivers[$contact['uid']]) && self::isValidReceiverForActor($contact, $tags)) {
$receivers[$contact['uid']] = ['uid' => $contact['uid'], 'type' => $target_type];
if (!empty($profile['uri-id'])) {
$condition = DBA::mergeConditions($basecondition, ["`uri-id` = ? AND `uid` != ?", $profile['uri-id'], 0]);
$contacts = DBA::select('contact', ['uid', 'rel'], $condition);
while ($contact = DBA::fetch($contacts)) {
if (empty($receivers[$contact['uid']]) && self::isValidReceiverForActor($contact, $tags)) {
$receivers[$contact['uid']] = ['uid' => $contact['uid'], 'type' => $target_type];
}
}
}
DBA::close($contacts);
DBA::close($contacts);
} else {
// This part will only be called while post update 1426 wasn't finished
$condition = DBA::mergeConditions($basecondition, ["`nurl` = ? AND `uid` != ?", Strings::normaliseLink($actor), 0]);
$contacts = DBA::select('contact', ['uid', 'rel'], $condition);
while ($contact = DBA::fetch($contacts)) {
if (empty($receivers[$contact['uid']]) && self::isValidReceiverForActor($contact, $tags)) {
$receivers[$contact['uid']] = ['uid' => $contact['uid'], 'type' => $target_type];
}
}
DBA::close($contacts);
// The queries are split because of performance issues
$condition = DBA::mergeConditions($basecondition, ["`alias` IN (?, ?) AND `uid` != ?", Strings::normaliseLink($actor), $actor, 0]);
$contacts = DBA::select('contact', ['uid', 'rel'], $condition);
while ($contact = DBA::fetch($contacts)) {
if (empty($receivers[$contact['uid']]) && self::isValidReceiverForActor($contact, $tags)) {
$receivers[$contact['uid']] = ['uid' => $contact['uid'], 'type' => $target_type];
// The queries are split because of performance issues
$condition = DBA::mergeConditions($basecondition, ["`alias` IN (?, ?) AND `uid` != ?", Strings::normaliseLink($actor), $actor, 0]);
$contacts = DBA::select('contact', ['uid', 'rel'], $condition);
while ($contact = DBA::fetch($contacts)) {
if (empty($receivers[$contact['uid']]) && self::isValidReceiverForActor($contact, $tags)) {
$receivers[$contact['uid']] = ['uid' => $contact['uid'], 'type' => $target_type];
}
}
DBA::close($contacts);
}
DBA::close($contacts);
return $receivers;
}

View File

@ -44,7 +44,6 @@ use Friendica\Protocol\ActivityPub;
use Friendica\Protocol\Relay;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\HTTPSignature;
use Friendica\Util\JsonLD;
use Friendica\Util\LDSignature;
use Friendica\Util\Map;
use Friendica\Util\Network;
@ -59,6 +58,10 @@ use Friendica\Util\XML;
*/
class Transmitter
{
const CACHEKEY_FEATURED = 'transmitter:getFeatured:';
const CACHEKEY_CONTACTS = 'transmitter:getContacts:';
const CACHEKEY_OUTBOX = 'transmitter:getOutbox:';
/**
* Add relay servers to the list of inboxes
*
@ -151,12 +154,21 @@ class Transmitter
* @param string $module The name of the relevant AP endpoint module (followers|following)
* @param integer $page Page number
* @param string $requester URL of the requester
* @param boolean $nocache Wether to bypass caching
*
* @return array of owners
* @throws \Exception
*/
public static function getContacts(array $owner, array $rel, string $module, int $page = null, string $requester = null)
public static function getContacts(array $owner, array $rel, string $module, int $page = null, string $requester = null, $nocache = false)
{
if (empty($page)) {
$cachekey = self::CACHEKEY_CONTACTS . $module . ':'. $owner['uid'];
$result = DI::cache()->get($cachekey);
if (!$nocache && !is_null($result)) {
return $result;
}
}
$parameters = [
'rel' => $rel,
'uid' => $owner['uid'],
@ -192,6 +204,10 @@ class Transmitter
}
if (!$show_contacts) {
if (!empty($cachekey)) {
DI::cache()->set($cachekey, $data, Duration::DAY);
}
return $data;
}
@ -216,6 +232,10 @@ class Transmitter
$data['orderedItems'] = $list;
}
if (!empty($cachekey)) {
DI::cache()->set($cachekey, $data, Duration::DAY);
}
return $data;
}
@ -225,13 +245,22 @@ class Transmitter
* @param array $owner Owner array
* @param integer $page Page number
* @param string $requester URL of requesting account
* @param boolean $nocache Wether to bypass caching
*
* @return array of posts
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @throws \ImagickException
*/
public static function getOutbox(array $owner, int $page = null, string $requester = '')
public static function getOutbox(array $owner, int $page = null, string $requester = '', $nocache = false)
{
if (empty($page)) {
$cachekey = self::CACHEKEY_OUTBOX . $owner['uid'];
$result = DI::cache()->get($cachekey);
if (!$nocache && !is_null($result)) {
return $result;
}
}
$condition = ['private' => [Item::PUBLIC, Item::UNLISTED]];
if (!empty($requester)) {
@ -293,27 +322,42 @@ class Transmitter
$data['orderedItems'] = $list;
}
if (!empty($cachekey)) {
DI::cache()->set($cachekey, $data, Duration::DAY);
}
return $data;
}
/**
* Public posts for the given owner
*
* @param array $owner Owner array
* @param integer $page Page number
* @param array $owner Owner array
* @param integer $page Page number
* @param boolean $nocache Wether to bypass caching
*
* @return array of posts
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @throws \ImagickException
*/
public static function getFeatured(array $owner, int $page = null)
public static function getFeatured(array $owner, int $page = null, $nocache = false)
{
if (empty($page)) {
$cachekey = self::CACHEKEY_FEATURED . $owner['uid'];
$result = DI::cache()->get($cachekey);
if (!$nocache && !is_null($result)) {
return $result;
}
}
$owner_cid = Contact::getIdForURL($owner['url'], 0, false);
$condition = ["`uri-id` IN (SELECT `uri-id` FROM `collection-view` WHERE `cid` = ? AND `type` = ?)",
Contact::getIdForURL($owner['url'], 0, false), Post\Collection::FEATURED];
$owner_cid, Post\Collection::FEATURED];
$condition = DBA::mergeConditions($condition,
['uid' => $owner['uid'],
'author-id' => Contact::getIdForURL($owner['url'], 0, false),
'author-id' => $owner_cid,
'private' => [Item::PUBLIC, Item::UNLISTED],
'gravity' => [GRAVITY_PARENT, GRAVITY_COMMENT],
'network' => Protocol::FEDERATED,
@ -334,30 +378,36 @@ class Transmitter
}
if (empty($page)) {
$data['first'] = DI::baseUrl() . '/featured/' . $owner['nickname'] . '?page=1';
$items = Post::select(['id'], $condition, ['limit' => 20, 'order' => ['created' => true]]);
} else {
$data['type'] = 'OrderedCollectionPage';
$list = [];
$items = Post::select(['id'], $condition, ['limit' => [($page - 1) * 20, 20], 'order' => ['created' => true]]);
while ($item = Post::fetch($items)) {
$activity = self::createActivityFromItem($item['id'], true);
$activity['type'] = $activity['type'] == 'Update' ? 'Create' : $activity['type'];
}
$list = [];
// Only list "Create" activity objects here, no reshares
if (!empty($activity['object']) && ($activity['type'] == 'Create')) {
$list[] = $activity['object'];
}
while ($item = Post::fetch($items)) {
$activity = self::createActivityFromItem($item['id'], true);
$activity['type'] = $activity['type'] == 'Update' ? 'Create' : $activity['type'];
// Only list "Create" activity objects here, no reshares
if (!empty($activity['object']) && ($activity['type'] == 'Create')) {
$list[] = $activity['object'];
}
DBA::close($items);
}
DBA::close($items);
if (count($list) == 20) {
$data['next'] = DI::baseUrl() . '/featured/' . $owner['nickname'] . '?page=' . ($page + 1);
}
if (count($list) == 20) {
$data['next'] = DI::baseUrl() . '/featured/' . $owner['nickname'] . '?page=' . ($page + 1);
}
if (!empty($page)) {
$data['partOf'] = DI::baseUrl() . '/featured/' . $owner['nickname'];
}
$data['orderedItems'] = $list;
$data['orderedItems'] = $list;
if (!empty($cachekey)) {
DI::cache()->set($cachekey, $data, Duration::DAY);
}
return $data;

View File

@ -341,4 +341,15 @@ class Relay
// It should never happen that we arrive here
return [];
}
/**
* Resubscribe to all relay servers
*/
public static function reSubscribe()
{
foreach (self::getList() as $server) {
$success = ActivityPub\Transmitter::sendRelayFollow($server['url']);
Logger::debug('Resubscribed', ['profile' => $server['url'], 'success' => $success]);
}
}
}

View File

@ -27,7 +27,9 @@ use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\APContact;
use Friendica\Model\Contact;
use Friendica\Model\ItemURI;
use Friendica\Model\User;
use Friendica\Network\HTTPClient\Capability\ICanHandleHttpResponses;
use Friendica\Network\HTTPClient\Client\HttpClientAccept;
use Friendica\Network\HTTPClient\Client\HttpClientOptions;
@ -263,21 +265,21 @@ class HTTPSignature
*/
/**
* Transmit given data to a target for a user
* Post given data to a target for a user, returns the result class
*
* @param array $data Data that is about to be send
* @param string $target The URL of the inbox
* @param integer $uid User id of the sender
*
* @return boolean Was the transmission successful?
* @return ICanHandleHttpResponses
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function transmit($data, $target, $uid)
public static function post(array $data, string $target, int $uid): ICanHandleHttpResponses
{
$owner = User::getOwnerDataById($uid);
if (!$owner) {
return;
return null;
}
$content = json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
@ -304,16 +306,32 @@ class HTTPSignature
$headers['Content-Type'] = 'application/activity+json';
$postResult = DI::httpClient()->post($target, $content, $headers);
$postResult = DI::httpClient()->post($target, $content, $headers, DI::config()->get('system', 'curl_timeout'));
$return_code = $postResult->getReturnCode();
Logger::info('Transmit to ' . $target . ' returned ' . $return_code);
$success = ($return_code >= 200) && ($return_code <= 299);
self::setInboxStatus($target, ($return_code >= 200) && ($return_code <= 299));
self::setInboxStatus($target, $success);
return $postResult;
}
return $success;
/**
* Transmit given data to a target for a user
*
* @param array $data Data that is about to be send
* @param string $target The URL of the inbox
* @param integer $uid User id of the sender
*
* @return boolean Was the transmission successful?
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function transmit(array $data, string $target, int $uid): bool
{
$postResult = self::post($data, $target, $uid);
$return_code = $postResult->getReturnCode();
return ($return_code >= 200) && ($return_code <= 299);
}
/**
@ -329,7 +347,7 @@ class HTTPSignature
$status = DBA::selectFirst('inbox-status', [], ['url' => $url]);
if (!DBA::isResult($status)) {
DBA::insert('inbox-status', ['url' => $url, 'created' => $now, 'shared' => $shared], Database::INSERT_IGNORE);
DBA::insert('inbox-status', ['url' => $url, 'uri-id' => ItemURI::getIdByURI($url), 'created' => $now, 'shared' => $shared], Database::INSERT_IGNORE);
$status = DBA::selectFirst('inbox-status', [], ['url' => $url]);
}
@ -369,6 +387,10 @@ class HTTPSignature
$fields['archive'] = false;
}
if (empty($status['uri-id'])) {
$fields['uri-id'] = ItemURI::getIdByURI($url);
}
DBA::update('inbox-status', $fields, ['url' => $url]);
}

View File

@ -24,6 +24,7 @@ namespace Friendica\Util;
use Friendica\Core\Cache\Enum\Duration;
use Friendica\Core\Logger;
use Exception;
use Friendica\Core\System;
use Friendica\DI;
/**
@ -69,7 +70,7 @@ class JsonLD
if ($recursion > 5) {
Logger::error('jsonld bomb detected at: ' . $url);
exit();
System::exit();
}
$result = DI::cache()->get('documentLoader:' . $url);

View File

@ -26,6 +26,7 @@ use Friendica\Core\Logger;
use Friendica\DI;
use Friendica\Model\Contact;
use Friendica\Network\HTTPException\NotModifiedException;
use GuzzleHttp\Psr7\Uri;
class Network
{
@ -436,7 +437,7 @@ class Network
* @param array $parsed URL parts
*
* @return string The glued URL.
* @deprecated since version 2021.12, use a UriInterface object like GuzzleHttp\Psr7\Uri instead
* @deprecated since version 2021.12, use GuzzleHttp\Psr7\Uri::fromParts($parts) instead
*/
public static function unparseURL(array $parsed)
{
@ -462,6 +463,29 @@ class Network
(strlen($fragment) ? "#".$fragment : '');
}
/**
* Convert an URI to an IDN compatible URI
*
* @param string $uri
* @return string
*/
public static function convertToIdn(string $uri): string
{
$parts = parse_url($uri);
if (!empty($parts['scheme']) && !empty($parts['host'])) {
$parts['host'] = idn_to_ascii($parts['host']);
$uri = Uri::fromParts($parts);
} else {
$parts = explode('@', $uri);
if (count($parts) == 2) {
$uri = $parts[0] . '@' . idn_to_ascii($parts[1]);
} else {
$uri = idn_to_ascii($uri);
}
}
return $uri;
}
/**
* Switch the scheme of an url between http and https

View File

@ -485,9 +485,8 @@ class Strings
* @param string $regex
* @param callable $callback
* @return string
* @throws \Exception
*/
public static function performWithEscapedBlocks(string $text, string $regex, callable $callback)
public static function performWithEscapedBlocks(string $text, string $regex, callable $callback): string
{
// Enables nested use
$executionId = random_int(PHP_INT_MAX / 10, PHP_INT_MAX);

View File

@ -460,7 +460,7 @@ class XML
public static function getFirstNodeValue(DOMXPath $xpath, $element, $context = null)
{
$result = $xpath->evaluate($element, $context);
$result = @$xpath->evaluate($element, $context);
if (!is_object($result)) {
return '';
}

View File

@ -23,12 +23,8 @@ namespace Friendica\Worker;
use Friendica\Core\Logger;
use Friendica\Core\Worker;
use Friendica\Model\Contact;
use Friendica\Model\GServer;
use Friendica\Model\Post;
use Friendica\Protocol\ActivityPub;
use Friendica\Util\HTTPSignature;
class APDelivery
{
/**
@ -46,65 +42,44 @@ class APDelivery
public static function execute(string $cmd, int $item_id, string $inbox, int $uid, array $receivers = [], int $uri_id = 0)
{
if (ActivityPub\Transmitter::archivedInbox($inbox)) {
Logger::info('Inbox is archived', ['cmd' => $cmd, 'inbox' => $inbox, 'id' => $item_id, 'uid' => $uid]);
if (in_array($cmd, [Delivery::POST])) {
Logger::info('Inbox is archived', ['cmd' => $cmd, 'inbox' => $inbox, 'id' => $item_id, 'uri-id' => $uri_id, 'uid' => $uid]);
if (empty($uri_id) && !empty($item_id)) {
$item = Post::selectFirst(['uri-id'], ['id' => $item_id]);
Post\DeliveryData::incrementQueueFailed($item['uri-id'] ?? 0);
$uri_id = $item['uri-id'] ?? 0;
}
if (empty($uri_id)) {
$posts = Post\Delivery::selectForInbox($inbox);
$uri_ids = array_column($posts, 'uri-id');
} else {
$uri_ids = [$uri_id];
}
foreach ($uri_ids as $uri_id) {
Post\Delivery::remove($uri_id, $inbox);
Post\DeliveryData::incrementQueueFailed($uri_id);
}
return;
}
Logger::info('Invoked', ['cmd' => $cmd, 'inbox' => $inbox, 'id' => $item_id, 'uri-id' => $uri_id, 'uid' => $uid]);
Logger::debug('Invoked', ['cmd' => $cmd, 'inbox' => $inbox, 'id' => $item_id, 'uri-id' => $uri_id, 'uid' => $uid]);
$success = true;
if ($cmd == Delivery::MAIL) {
$data = ActivityPub\Transmitter::createActivityFromMail($item_id);
if (!empty($data)) {
$success = HTTPSignature::transmit($data, $inbox, $uid);
}
} elseif ($cmd == Delivery::SUGGESTION) {
$success = ActivityPub\Transmitter::sendContactSuggestion($uid, $inbox, $item_id);
} elseif ($cmd == Delivery::RELOCATION) {
// @todo Implementation pending
} elseif ($cmd == Delivery::POKE) {
// Implementation not planned
} elseif ($cmd == Delivery::REMOVAL) {
$success = ActivityPub\Transmitter::sendProfileDeletion($uid, $inbox);
} elseif ($cmd == Delivery::PROFILEUPDATE) {
$success = ActivityPub\Transmitter::sendProfileUpdate($uid, $inbox);
if (empty($uri_id)) {
$result = ActivityPub\Delivery::deliver($inbox);
$success = $result['success'];
$drop = false;
$uri_ids = $result['uri_ids'];
} else {
$data = ActivityPub\Transmitter::createCachedActivityFromItem($item_id);
if (!empty($data)) {
$success = HTTPSignature::transmit($data, $inbox, $uid);
}
$result = ActivityPub\Delivery::deliverToInbox($cmd, $item_id, $inbox, $uid, $receivers, $uri_id);
$success = $result['success'];
$drop = $result['drop'];
$uri_ids = [$uri_id];
}
$gsid = null;
foreach ($receivers as $receiver) {
$contact = Contact::getById($receiver);
if (empty($contact)) {
continue;
if (!$drop && !$success && !Worker::defer() && !empty($uri_ids)) {
foreach ($uri_ids as $uri_id) {
Post\Delivery::remove($uri_id, $inbox);
Post\DeliveryData::incrementQueueFailed($uri_id);
}
$gsid = $gsid ?: $contact['gsid'];
if ($success) {
Contact::unmarkForArchival($contact);
} else {
Contact::markForArchival($contact);
}
}
if (!empty($gsid)) {
GServer::setProtocol($gsid, Post\DeliveryData::ACTIVITYPUB);
}
if (!$success && !Worker::defer() && in_array($cmd, [Delivery::POST])) {
Post\DeliveryData::incrementQueueFailed($uri_id);
} elseif ($success && in_array($cmd, [Delivery::POST])) {
Post\DeliveryData::incrementQueueDone($uri_id, Post\DeliveryData::ACTIVITYPUB);
}
}
}

View File

@ -21,8 +21,10 @@
namespace Friendica\Worker;
use Friendica\Core\Logger;
use Friendica\DI;
use Friendica\Model\Contact;
use Friendica\Network\HTTPException\InternalServerErrorException;
use Friendica\Network\HTTPException\NotFoundException;
class AddContact
{
@ -33,14 +35,22 @@ class AddContact
*/
public static function execute(int $uid, string $url)
{
if ($uid == 0) {
// Adding public contact
$result = Contact::getIdForURL($url);
Logger::info('Added public contact', ['url' => $url, 'result' => $result]);
return;
}
try {
if ($uid == 0) {
// Adding public contact
$result = Contact::getIdForURL($url);
DI::logger()->info('Added public contact', ['url' => $url, 'result' => $result]);
return;
}
$result = Contact::createFromProbeForUser($uid, $url);
Logger::info('Added contact', ['uid' => $uid, 'url' => $url, 'result' => $result]);
$result = Contact::createFromProbeForUser($uid, $url);
DI::logger()->info('Added contact for user', ['uid' => $uid, 'url' => $url, 'result' => $result]);
} catch (InternalServerErrorException $e) {
DI::logger()->warning('Internal server error.', ['exception' => $e, 'uid' => $uid, 'url' => $url]);
} catch (NotFoundException $e) {
DI::logger()->notice('uid not found.', ['exception' => $e, 'uid' => $uid, 'url' => $url]);
} catch (\ImagickException $e) {
DI::logger()->notice('Imagick not found.', ['exception' => $e, 'uid' => $uid, 'url' => $url]);
}
}
}

View File

@ -27,6 +27,7 @@ use Friendica\Core\Worker;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Tag;
use Friendica\Protocol\Relay;
class Cron
{
@ -92,9 +93,6 @@ class Cron
Worker::add(PRIORITY_LOW, 'PullDirectory');
}
// Delete all done workerqueue entries
Worker::add(PRIORITY_LOW, 'CleanWorkerQueue');
// Clear cache entries
Worker::add(PRIORITY_LOW, 'ClearCache');
@ -128,6 +126,9 @@ class Cron
}
DI::config()->set('system', 'last_cron_daily', time());
// Resubscribe to relay servers
Relay::reSubscribe();
}
Logger::notice('end');

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