Compare commits

..

363 Commits
dev ... 10.4.2

Author SHA1 Message Date
Mario
44a0311798 Merge branch 'master' of https://framagit.org/hubzilla/core 2025-08-08 07:51:42 +00:00
Mario
2ac06fef9a version 10.4.2 2025-08-08 07:51:12 +00:00
Mario
962b4e5b4c fix php error
(cherry picked from commit eeef6cfd00)

Co-authored-by: Mario <mario@mariovavti.com>
2025-08-07 14:57:11 +00:00
Mario
91f1f8ed7c version 10.4.1 2025-07-31 08:46:43 +00:00
Mario
82817b4532 tagadelic: look up toplevels only
(cherry picked from commit 3eeaca4aab)

Co-authored-by: Mario <mario@mariovavti.com>
2025-07-22 10:34:19 +00:00
Mario
a3dd4a4d3d revert cleanup template
(cherry picked from commit c383e6af80)

Co-authored-by: Mario <mario@mariovavti.com>
2025-07-22 10:22:38 +00:00
Mario
517e14cc38 cleanup template
(cherry picked from commit eb3dfb2488)

Co-authored-by: Mario <mario@mariovavti.com>
2025-07-22 10:17:05 +00:00
Mario
70e8648d91 the item_wall directive is not required here since we are looking for parent items which we own - remove it since it will make the DB engine use the wrong index in certain cases. also remove duplication of item_type directive when looking up articles
(cherry picked from commit ae8e9bc353)

Co-authored-by: Mario <mario@mariovavti.com>
2025-07-22 09:33:34 +00:00
Mario
ee7b982358 Merge branch '10.4RC' 2025-07-15 06:40:43 +00:00
Mario
2740f36259 version 10.4 2025-07-15 06:40:19 +00:00
Mario
ff16810e76 Merge branch 'dev' into 10.4RC 2025-07-15 06:35:47 +00:00
Mario
da1e0b965b Merge branch 'dev' into 10.4RC 2025-07-13 19:43:01 +00:00
Mario
24316bbb03 Merge branch 'dev' into 10.4RC 2025-07-13 10:32:55 +00:00
Mario
052341d24a Merge branch 'dev' into 10.4RC 2025-07-13 09:49:29 +00:00
Mario
0244d9423a RC3 2025-07-10 12:02:15 +00:00
Mario
042c98cf2d Merge branch 'dev' into 10.4RC 2025-07-10 12:00:53 +00:00
Mario
48ef490f50 Merge branch 'dev' into 10.4RC 2025-07-10 11:40:58 +00:00
Mario
37f0074674 Merge branch 'dev' into 10.4RC 2025-06-26 08:58:56 +00:00
Mario
7bd4217dd8 Merge branch 'dev' into 10.4RC 2025-06-26 08:12:28 +00:00
Mario Vavti
890a5942d6 Merge branch 'dev' into 10.4RC 2025-06-25 17:43:52 +02:00
Mario Vavti
7ecbce691d RC2 2025-06-25 17:35:58 +02:00
Mario Vavti
8c182c9d68 version 10.2.3 2025-04-11 21:30:03 +02:00
Mario
384eea79dc changelog
(cherry picked from commit 6068c0e8c2)

Co-authored-by: Mario <mario@mariovavti.com>
2025-04-11 19:28:46 +00:00
Mario Vavti
f8bc755c20 fix bougus merge to master 2025-04-11 21:13:40 +02:00
Mario Vavti
aa38e8360a version 10.2.2 2025-04-11 10:45:43 +02:00
Mario
fdf6680b29 changelog
(cherry picked from commit b79c19c66d)

Co-authored-by: Mario <mario@mariovavti.com>
2025-04-11 08:44:37 +00:00
Mario
8999a96ef0 cleanup deprecated forum queries and possibly improved performance 2025-04-11 10:23:46 +02:00
Mario
b5d2236a58 do not return success if Libzot::fetch() did not return anything useful and remove redundant parameter $hub
(cherry picked from commit 3550609d29)

Co-authored-by: Mario <mario@mariovavti.com>
2025-04-11 07:48:28 +00:00
Mario
91e59d3c4f make sure we have an encoded activity before we go on building the packet, do not json_encode() a possibly empty item.target
(cherry picked from commit fe8ae2f88e)

Co-authored-by: Mario <mario@mariovavti.com>
2025-04-11 07:45:24 +00:00
Mario
a6ff444e19 fix some plink for forum posts and comments
(cherry picked from commit a5a2d80c50)

Co-authored-by: Mario <mario@mariovavti.com>
2025-04-11 07:42:50 +00:00
Mario
29d4edb285 fix an obscure delivering issue which incidentally also fixes a delivering issue that required an ugly hack to work around
(cherry picked from commit 7c79ec9626)

Co-authored-by: Mario <mario@mariovavti.com>
2025-04-10 13:04:41 +00:00
Mario
295d5f06dc lazy load profile images for likes and remove zid function for the profile image src attribute
(cherry picked from commit 30d552255e)

Co-authored-by: Mario <mario@mariovavti.com>
2025-03-28 13:31:47 +00:00
Mario
362be52be0 version 10.2.1 2025-03-18 08:10:15 +00:00
Mario
20f5e654ad changelog
(cherry picked from commit 34b2bdcf2c)

Co-authored-by: Mario <mario@mariovavti.com>
2025-03-18 08:09:03 +00:00
Mario
8c38ee8208 fix sse_bs query 2025-03-18 08:00:37 +00:00
Mario
2c1c12825d we require the HTTP signature to be set in the HTTP_AUTHORIZATION field somewhere down the line (probably in Web/HTTPSig). Make sure it is there.
(cherry picked from commit 5188b9cef5)

Co-authored-by: Mario <mario@mariovavti.com>
2025-03-18 07:58:14 +00:00
Mario
bbcf7e9aa1 Merge branch '10.2RC' 2025-03-17 19:39:51 +00:00
Mario
45a2ebc662 version 10.2 2025-03-17 19:33:32 +00:00
Mario
aece22aee6 Merge branch 'dev' into 10.2RC 2025-03-17 19:32:36 +00:00
Mario
7c0f98a513 Merge branch 'dev' into 10.2RC 2025-03-17 19:30:39 +00:00
Mario
9369085835 Merge branch 'dev' into 10.2RC 2025-03-17 19:20:55 +00:00
Mario
00b3039b6e Merge branch 'dev' into 10.2RC 2025-03-16 11:16:31 +00:00
Mario
bcfdb73001 Merge branch 'dev' into 10.2RC 2025-03-14 10:52:17 +00:00
Mario
991815469f Merge branch 'dev' into 10.2RC 2025-03-14 10:49:50 +00:00
Mario
a0c98e070e Merge branch 'dev' into 10.2RC 2025-03-09 16:14:46 +00:00
Mario
4ba470318c Merge branch 'dev' into 10.2RC 2025-03-09 10:09:47 +00:00
Mario
8d45fd36a1 css fix 2025-03-07 09:01:48 +00:00
Mario
6ed2301a94 bump version 2025-03-07 08:54:59 +00:00
Mario
a0eb701503 Merge branch 'dev' into 10.2RC 2025-03-07 08:54:06 +00:00
Mario
13002af4c3 version 10.2RC1 2025-03-06 10:00:21 +00:00
Mario
ee988edc83 make sure attachment is an array
(cherry picked from commit 2b4b409c01)

Co-authored-by: Mario <mario@mariovavti.com>
2025-03-03 08:56:30 +00:00
Mario
7579749adc Merge branch 'debianinstall' into 'master'
Debianinstall

See merge request hubzilla/core!2179
2025-03-03 08:40:29 +00:00
OJ Random
cf408c3fac Debianinstall 2025-03-03 08:40:29 +00:00
Mario Vavti
6c8da6ce36 version 10.0.8 2025-02-01 12:30:15 +01:00
Mario
e19a050b92 changelog
(cherry picked from commit a13bf6a3fe)

Co-authored-by: Mario Vavti <mario@mariovavti.com>
2025-02-01 11:28:04 +00:00
Mario
546c4fcad2 calling fetch_post_tags() will add duplicate items here
(cherry picked from commit 0a17b83578)

Co-authored-by: Mario Vavti <mario@mariovavti.com>
2025-02-01 00:03:50 +00:00
Mario
2782b6d724 possible sql performance improvements
(cherry picked from commit dc076a4c00)

Co-authored-by: Mario Vavti <mario@mariovavti.com>
2025-01-30 12:10:45 +00:00
Mario
324b281813 fix php warniing in case tgt_type is not set
(cherry picked from commit 03d1f3383e)

Co-authored-by: Mario Vavti <mario@mariovavti.com>
2025-01-24 12:16:33 +00:00
Mario
69a23c604d return if $furl is empty for some reason
(cherry picked from commit 174469970a)

Co-authored-by: Mario Vavti <mario@mariovavti.com>
2025-01-24 11:44:34 +00:00
Mario Vavti
9b13055dfe version 10.0.7 2025-01-22 13:08:42 +01:00
Mario
06afd8a375 changelog
(cherry picked from commit 27efb887d2)

Co-authored-by: Mario Vavti <mario@mariovavti.com>
2025-01-22 11:24:32 +00:00
Mario
6f027544d6 make sure to provide uid to drop_item() where applicable and check ownership when imporing feed items
(cherry picked from commit ccd52584a4)

Co-authored-by: Mario Vavti <mario@mariovavti.com>
2025-01-22 11:22:15 +00:00
Mario
ebab5ff281 use bin2hex() instead of btoa() to create an id for the toast. btoa() will not work with nonascii chars.
(cherry picked from commit 0165f44063)

Co-authored-by: Mario Vavti <mario@mariovavti.com>
2025-01-15 12:00:01 +00:00
Mario
5fb6e5d6f7 fix regression in notifications filter
(cherry picked from commit 8fc0e41beb)

Co-authored-by: Mario Vavti <mario@mariovavti.com>
2025-01-06 09:15:59 +00:00
Mario Vavti
5a84ffdcda version 10.0.6 2025-01-05 13:31:28 +01:00
Mario
ad993645be changelog
(cherry picked from commit 392cb020aa)

Co-authored-by: Mario Vavti <mario@mariovavti.com>
2025-01-05 12:30:32 +00:00
Mario
0701cde239 dismiss entries where primary location data is not complete for some reason
(cherry picked from commit 884b612ffc)

Co-authored-by: Mario Vavti <mario@mariovavti.com>
2025-01-05 11:56:51 +00:00
Mario
a136c288d5 cleanup outdated doc entries just once after the docs were imported to the db
(cherry picked from commit ae0d138d2a)

Co-authored-by: Mario Vavti <mario@mariovavti.com>
2025-01-05 11:56:09 +00:00
Mario
1624a2620a remove redundant arguments
(cherry picked from commit 48ef4744ac)

Co-authored-by: Mario Vavti <mario@mariovavti.com>
2025-01-04 10:20:40 +00:00
Mario Vavti
f4769d0f04 version 10.0.5 2024-12-29 17:36:28 +01:00
Mario Vavti
c8e30a00e2 Merge branch 'dev' 2024-12-29 17:35:44 +01:00
Mario Vavti
51745d3652 Merge branch 'dev' 2024-12-29 17:18:04 +01:00
Mario
762d402dea Merge branch 'dev' 2024-12-26 09:00:33 +00:00
Mario
b05a440f03 version 10.0.4 2024-12-26 08:58:46 +00:00
Mario
9bb4988eda Merge branch 'dev' 2024-12-26 08:55:36 +00:00
Mario
49cb73c8c7 version 10.0.3 2024-12-26 08:04:05 +00:00
Mario
2a00bd9a28 Merge branch 'dev' 2024-12-26 08:03:26 +00:00
Mario
5813b65aed hotfix release version 10.0.2 2024-12-25 12:03:43 +00:00
Mario
f8acd1d3a5 Merge branch 'dev' 2024-12-25 12:02:32 +00:00
Mario
632721da4e version 10.0.1 2024-12-22 08:51:48 +00:00
Mario
f3a702e927 Merge branch 'dev' 2024-12-22 08:51:15 +00:00
Mario
c55662510d fetch missing commit from dev 2024-12-22 08:31:04 +00:00
Mario
f01e917fff Merge branch 'dev' 2024-12-22 08:26:26 +00:00
Mario
b80cba4e68 Merge branch '10.0RC' 2024-12-21 18:49:06 +00:00
Mario
f2b28f5bbb version 10.0 2024-12-21 18:46:40 +00:00
Mario
4aeb4963a4 Merge branch 'dev' into 10.0RC 2024-12-21 18:44:04 +00:00
Mario
cf66f245b4 RC2 2024-12-21 12:34:05 +00:00
Mario
91baa52b8b Merge branch 'saiwal-master-patch-76831' into 'master'
Update chat.tpl to correct expand and contract icons from forkawesome to bootstrap icons.

See merge request hubzilla/core!2171
2024-11-19 21:08:32 +00:00
Saiwal K
c626985448 Update chat.tpl to correct expand and contract icons from forkawesome to bootstrap icons. 2024-11-17 19:59:31 +00:00
Mario
586cda5155 fix some conent import issues
(cherry picked from commit c9a8914b07)

Co-authored-by: Mario Vavti <mario@mariovavti.com>
2024-11-14 13:30:07 +00:00
Mario
513cd3b99a fix click area in dropdown add/remove reaction
(cherry picked from commit 8268ed1ba7)

Co-authored-by: Mario <mario@mariovavti.com>

(cherry picked from commit ce9d67f7b1)

Co-authored-by: Mario <mario@mariovavti.com>
2024-11-11 20:00:36 +00:00
Mario
f05b8bd87d Do not filter deleted hublocs in xchan_query because it will result in empty profile info in conversations if the hubloc was deleted. Deleting a hublocation does not neccesarily delete its content and the author could appear again from another location.
(cherry picked from commit 4e6696b049)

Co-authored-by: Mario Vavti <mario@mariovavti.com>
2024-11-09 20:12:21 +00:00
Mario
e466c73407 fix allday not set if a rsource is provided
(cherry picked from commit 17183cc5dc)

Co-authored-by: Mario <mario@mariovavti.com>
2024-11-07 14:31:18 +00:00
Mario
e5aae2af0f fix allday variable not set when clicking an event
(cherry picked from commit 939e5d9ca5)

Co-authored-by: Mario <mario@mariovavti.com>
2024-11-06 16:17:18 +00:00
Mario
173c4d242d version 9.4.4 2024-11-06 13:06:05 +00:00
Mario
76b8c36f7c changelog
(cherry picked from commit 12c88c06d8)

Co-authored-by: Mario <mario@mariovavti.com>
2024-11-06 13:05:18 +00:00
Mario
f35352090e Update translations for Norwegian Bokmål
(cherry picked from commit 35a05073f2)

Co-authored-by: Harald Eilertsen <haraldei@anduin.net>
2024-11-06 12:30:37 +00:00
Mario
e5db47e0d5 Module\Thing: Don't try to escape field that's not present
The `profile_assign` field is only present if the multiple profiles
feature is enabled.


(cherry picked from commit 472484dde0)

Co-authored-by: Harald Eilertsen <haraldei@anduin.net>
2024-11-06 12:30:16 +00:00
Mario
6a52e502aa Module\Thing: Don't use $_REQUEST superglobal.
Replaces all occurences with $_POST or $_GET instead.


(cherry picked from commit 5c3bdbd1e0)

Co-authored-by: Harald Eilertsen <haraldei@anduin.net>
2024-11-06 12:29:46 +00:00
Mario
6a866fe904 Move Norwegian translations from nb-no to nb
(cherry picked from commit ec02453d37)

Co-authored-by: Harald Eilertsen <haraldei@anduin.net>
2024-11-06 12:28:51 +00:00
Mario
d9d239bf3a only zot6 hublocs have a sitekey so only query such hublocs. This will safe alternative network hublocs from being marked deleted allthough they are intact
(cherry picked from commit 7a3f2c1ba9)

Co-authored-by: Mario <mario@mariovavti.com>
2024-11-06 11:16:46 +00:00
Mario
429d15f009 correctly hide modals and remove unneeded toString() conversion since we are not dealing with string objects here
(cherry picked from commit 9f473fc204)

Co-authored-by: Mario <mario@mariovavti.com>
2024-11-06 11:16:20 +00:00
Mario
2b44be58c3 explicit check for channel_address
(cherry picked from commit e20327d267)

Co-authored-by: Mario <mario@mariovavti.com>
2024-11-03 11:02:35 +00:00
Mario
c44db397ff Zotlabs\Module\Setup: Fix deprecation.
Using `self` in callables has been deprecated, so change to proper fully
qualified class name.


(cherry picked from commit 8ab3ad6531)

Co-authored-by: Harald Eilertsen <haraldei@anduin.net>
2024-11-03 11:02:04 +00:00
Mario
a385fdff37 Zotlabs\Web\HttpMeta: Declare and init properties.
The $ogproperty was not declared, which triggered a warning in PHP 8.2.

Also fixed the initialization of the properties, and removed the now
superfluous constructor.


(cherry picked from commit bf008465ad)

Co-authored-by: Harald Eilertsen <haraldei@anduin.net>
2024-11-03 11:01:50 +00:00
Mario
20aacb82c6 tests: Declare private property $output.
Uncovered by PHP 8.2 because dynamic properties are deprecated.


(cherry picked from commit a29a1c768d)

Co-authored-by: Harald Eilertsen <haraldei@anduin.net>
2024-11-03 11:01:38 +00:00
Mario
0d17d8dad9 tests: Fix typo in UnitTestCase.
Uncovered by PHP 8.2 because dynamic properties are deprecated.


(cherry picked from commit 90bc987ea7)

Co-authored-by: Harald Eilertsen <haraldei@anduin.net>
2024-11-03 11:01:21 +00:00
Mario
2ab0118c13 Fix missing CSRF checks in admin/account_edit
(cherry picked from commit 38c947590e)

342d94c3 tpl: Fix warnings in templates.
bccaeb1e tests: Update Module\TestCase to support POST requests
f627e55b tests: Update account fixtures with fixed account_level.
ee62aff4 Module\Admin\Account_edit: Add missing CSRF checks.

Co-authored-by: Harald Eilertsen <haraldei@anduin.net>
2024-11-03 10:59:41 +00:00
Mario
30419bdbf6 add ocap tokens to all media files and attachments
(cherry picked from commit 2f0a47e583)

Co-authored-by: Mario <mario@mariovavti.com>
2024-11-03 10:59:16 +00:00
Mario
c958cc6f90 URL escape zid param in head.tpl
This should fix issue #1877 fully.


(cherry picked from commit 065f85bab1)

Co-authored-by: Harald Eilertsen <haraldei@anduin.net>
2024-11-01 09:03:21 +00:00
Mario
38ac60e618 make sure the objects published date has the correct time format
(cherry picked from commit 160c40b580)

Co-authored-by: Mario <mario@mariovavti.com>
2024-10-31 11:12:54 +00:00
Mario
23a19ecf1f escape the zid parameter - issue #1877
(cherry picked from commit 0207c02420)

Co-authored-by: Mario <mario@mariovavti.com>
2024-10-30 09:18:23 +00:00
Mario
8b75f50f23 fix en tos page
(cherry picked from commit 0e50b1d10c)

Co-authored-by: Mario Vavti <mario@mariovavti.com>
2024-10-27 12:57:02 +00:00
Mario
436b1673cf make sure we select only sys channel items and remove dupes checking (this will be checked in item_store() anyway)
(cherry picked from commit e530476e6c)

Co-authored-by: Mario <mario@mariovavti.com>
2024-10-23 12:08:03 +00:00
Mario
96210f5ecc fix edit button not clickable if below right aside
(cherry picked from commit 1411eafa9b)

Co-authored-by: Mario <mario@mariovavti.com>
2024-10-18 19:14:04 +00:00
Mario
9e9e8efb2d fix tags rendering in the editor
(cherry picked from commit f4495fd441)

Co-authored-by: Mario <mario@mariovavti.com>
2024-10-17 09:04:25 +00:00
Mario
e9dc4b553b Fix regex to detect URLs in cleanup_bbcode.
This fixes the issue where the text after the URL would be included in
the link if it was immediately followed by a newline.

Example:

    https://example.com
	this is a test.

Would become:

	#^[url=https://example.com_this]https://example.com_this[/url]
	is a test


(cherry picked from commit 687cda3673)

Co-authored-by: Harald Eilertsen <haraldei@anduin.net>
2024-10-12 17:08:15 +00:00
Mario
c70bd08c10 Fix duplicate posts from forum clones
(cherry picked from commit e2cfe245b7)

Co-authored-by: Mario Vavti <mario@mariovavti.com>
2024-10-12 13:59:12 +00:00
Mario Vavti
69109a558b version 9.4.3 2024-10-10 12:16:33 +02:00
Mario
4aff6d19d6 changelog
(cherry picked from commit a5c1b669b4)

Co-authored-by: Mario Vavti <mario@mariovavti.com>
2024-10-10 10:11:31 +00:00
Mario
3cb5d14037 also discard Add/Remove at the AP side
(cherry picked from commit 2aee659cbd)

Co-authored-by: Mario Vavti <mario@mariovavti.com>
2024-10-10 10:04:17 +00:00
Mario
5f685bcf63 also dismiss add/remove collection activities in fetch_conversation()
(cherry picked from commit 44232677c8)

Co-authored-by: Mario <mario@mariovavti.com>
2024-10-10 10:03:51 +00:00
Mario
cb44f7e360 dismiss add/remove collection activities until we support themÃ
(cherry picked from commit 16603ca854)

Co-authored-by: Mario <mario@mariovavti.com>
2024-10-10 10:03:32 +00:00
Mario
8f74ee67e3 css fixes
(cherry picked from commit 2693e9e990)

Co-authored-by: Mario <mario@mariovavti.com>
2024-10-04 11:27:56 +00:00
Mario
b0a11537de remove bogus icon id
(cherry picked from commit 04741c761a)

Co-authored-by: Mario <mario@mariovavti.com>
2024-10-04 11:27:40 +00:00
Mario
4de9cb1142 more fa2bi fixes
(cherry picked from commit 8f890fb6fa)

Co-authored-by: Mario <mario@mariovavti.com>
2024-10-04 11:07:50 +00:00
Mario
158ddfb009 changelog
(cherry picked from commit 1afb2a4ce8)

Co-authored-by: Mario <mario@mariovavti.com>
2024-10-04 07:09:21 +00:00
Mario
2b4f344181 Version 9.4.2 2024-10-04 07:07:31 +00:00
Mario
2e5f922561 due to popular demand: indicate reacted state via icon color
(cherry picked from commit afbeb58c16)

Co-authored-by: Mario <mario@mariovavti.com>
2024-10-04 06:59:50 +00:00
Mario
a6498a0cfc Reflect change to btn-link in js template
(cherry picked from commit 8ffab25f5d)

Co-authored-by: Mario <mario@mariovavti.com>
2024-10-04 06:59:16 +00:00
Mario
1073392398 Fix modal backdrop not removed when like/unlike from the modal
(cherry picked from commit 7d41deebce)

Co-authored-by: Mario <mario@mariovavti.com>
2024-10-04 06:51:15 +00:00
Mario
76064dbb33 Fix missing handle icon in mod pdledit_gui
(cherry picked from commit 7e48caae6b)

Co-authored-by: Mario <mario@mariovavti.com>
2024-10-04 06:27:37 +00:00
Mario
1ab4f36a1b Merge branch 'master' of https://framagit.org/hubzilla/core 2024-10-03 04:52:40 +00:00
Mario
cea9c88b9e version 9.4.1 2024-10-03 04:52:18 +00:00
Mario
babe118383 Tests: Unbreak help test.
(cherry picked from commit 5b93aa1148)

Co-authored-by: Harald Eilertsen <haraldei@anduin.net>
2024-10-02 20:27:26 +00:00
Mario
2b140e53cc changelog
(cherry picked from commit 9008760aa6)

Co-authored-by: Mario <mario@mariovavti.com>
2024-10-02 20:21:51 +00:00
Mario
fb1514a782 Help: Drop language specifier in URL when redirecting
This finally fixes the help for languages other than english.


(cherry picked from commit 4daa03f025)

Co-authored-by: Harald Eilertsen <haraldei@anduin.net>
2024-10-02 20:09:36 +00:00
Mario
7679894684 Replace drop shadow in help toc with brighter color.
The drop shadow looked terrible in dark mode. While that could be fixed,
I think it looks better with just using the link hover color and making
the selected item bold.


(cherry picked from commit 8ae9df3bb7)

Co-authored-by: Harald Eilertsen <haraldei@anduin.net>
2024-10-02 20:08:55 +00:00
Mario
520cf1015c Move some help related styling from redbasic to module css.
(cherry picked from commit 25d9d3ba1b)

Co-authored-by: Harald Eilertsen <haraldei@anduin.net>
2024-10-02 20:08:33 +00:00
Mario
64bd8eef4b Use html details element for toc on top of help pages.
This allows us to get rid of some more unneccessary JavaScript that just
implements stuff that web browsers now do anyways.


(cherry picked from commit 26ce231951)

Co-authored-by: Harald Eilertsen <haraldei@anduin.net>
2024-10-02 20:08:04 +00:00
Mario
aa42f6a6b2 Remove link to "Contents" from the help table of contect sidebar.
Changes the element where the jQuery.toc plugin looks for headings to
only include the actual help contents, not the toc itself.


(cherry picked from commit fb9464437e)

Co-authored-by: Harald Eilertsen <haraldei@anduin.net>
2024-10-02 20:07:31 +00:00
Mario
13345d3cbe deps: Upgrade smarty/smarty to version 4.5.4
This eliminates a potential vulnerability where an template author could
inject arbitrary PHP files to be run via the 'extends' tag.

See:
  - https://github.com/smarty-php/smarty/security/advisories/GHSA-4rmg-292m-wg3w
  - 0be92bc8a6

Impact assessment:

In our case I would consider this a low severity issue as we don't
allow users to dynamically add or edit smarty templates. Templates has
to be updated via merge requests, or by installing a theme. In both
cases a malicious attacker already has easier ways to inject whatever
code they want.

Further, the extend tag is not in use in any of our core templates.


(cherry picked from commit 4dff1a1e5b)

Co-authored-by: Harald Eilertsen <haraldei@anduin.net>
2024-10-02 20:06:10 +00:00
Mario
888ee16d52 fix german about page title
(cherry picked from commit 1c45030c58)

Co-authored-by: Mario <mario@mariovavti.com>
2024-10-02 20:05:06 +00:00
Mario
0f3e01f343 markdown: Don't link URLs in code blocks.
When passing a content throught the `markdown_to_bb` function
to convert any markdown in the content, any recognized URLs in the
content would be converted to BBCode links as a post processing step
after the main conversion.

After commit a1ccacb825 this did no longer
consider content within code blocks, and would thus convert them as
to BBCode links.

Example: The following content

	[code]
	example url: https://example.com
	[/code]

Would be converted to

	[code]
	example url: [url=https://example.com]https://example.com[/url]
	[/code]

Prior to commit a1ccacb825, code blocks
would be protected, so this would not happen.

This patch removes the post processing step for converting plain URLs to
links completely from this routine. This functionality is in any case
covered in the actual BBCode parser where it belongs.

This will have some other side effects as well, such as images and links
created using Markdown, will not be converted to [zmg] or [zrl] tags
where that would be done automatically before. If you intend to use a
[zrl] or [zmg] tag, you now need to do so explicitly.


(cherry picked from commit 803cd74b48)

Co-authored-by: Harald Eilertsen <haraldei@anduin.net>
2024-10-02 20:04:30 +00:00
Mario
79220ede66 rewrite sql logic to rename photos - there are more DB queries involved now but the previous logic was throwing error in postgresql (while the result was correct anyway)
(cherry picked from commit a5f0253aef)

Co-authored-by: Mario Vavti <mario@mariovavti.com>
2024-10-02 20:03:54 +00:00
Mario
b498e50f74 adjust post app icon and remove obsolete arguments
(cherry picked from commit 5cbd42bbc4)

Co-authored-by: Mario <mario@mariovavti.com>
2024-09-27 16:51:57 +00:00
Mario
8c87b06caa Merge branch '9.4RC' 2024-09-25 19:39:51 +00:00
Mario
339e702beb version 9.4 2024-09-25 19:38:12 +00:00
Mario
8750c5f7bc update changelog
(cherry picked from commit 5281f4dd9b)

Co-authored-by: Mario <mario@mariovavti.com>
2024-09-25 19:36:46 +00:00
Mario
c3b4397ea3 update changelog
(cherry picked from commit 424b31b7f0)

Co-authored-by: Mario <mario@mariovavti.com>
2024-09-25 09:48:57 +00:00
Mario
a3f1189df7 Merge branch 'dev' into 9.4RC 2024-09-24 09:26:12 +00:00
Mario
67d8b977b6 version 9.4RC1 2024-09-24 08:59:26 +00:00
Mario
5b1e532791 Merge branch 'dev' into 9.4RC 2024-09-24 08:58:33 +00:00
Mario
4f129e05e5 notes: make sure we set App::$profile_uid in the module - issue #1865 and minor code cleanup
(cherry picked from commit 3733a80c1d)

Co-authored-by: Mario Vavti <mario@mariovavti.com>
2024-09-22 15:26:52 +00:00
Mario
99bff67168 version 9.2.1 2024-07-18 08:55:07 +00:00
Mario
bb3caeb629 Merge branch 'dev' 2024-07-18 08:54:21 +00:00
Mario
45275910e6 Merge branch '9.2RC' 2024-07-06 11:05:22 +00:00
Mario
c04e781926 version 9.2 2024-07-06 11:04:43 +00:00
Mario
09a609ef6b Merge branch 'dev' into 9.2RC 2024-07-06 11:02:56 +00:00
Mario
db5e524e3c Merge branch 'dev' into 9.2RC 2024-07-04 10:13:28 +00:00
Mario
8f9e9116df revert the strlen check 2024-07-04 09:43:20 +00:00
Mario
f944f46744 fix possible php error 2024-07-04 09:30:21 +00:00
Mario
3f5c45a567 Merge branch 'dev' into 9.2RC 2024-06-28 18:19:34 +00:00
Mario
b51ed67efb Merge branch 'dev' into 9.2RC 2024-06-27 20:02:54 +00:00
Mario
afdc3d6d18 Merge branch 'dev' into 9.2RC 2024-06-27 19:23:20 +00:00
Mario
6a710c3cc3 fix version 2024-06-27 08:29:01 +00:00
Mario
0c1d0f7498 version 9.0.2 2024-06-07 09:25:15 +00:00
Mario
81ba070e1a update changelog
(cherry picked from commit bd5f77dbeb)

Co-authored-by: Mario <mario@mariovavti.com>
2024-06-07 09:23:32 +00:00
Mario
a7812657f1 update changelog
(cherry picked from commit d862a6f075)

Co-authored-by: Mario <mario@mariovavti.com>
2024-06-07 09:23:02 +00:00
Mario
18725c47a0 move button class to the right dom
(cherry picked from commit 59b8c8ad48)

Co-authored-by: Mario <mario@mariovavti.com>
2024-06-07 09:12:32 +00:00
Mario
75e1b70584 php warnings/errors
(cherry picked from commit 10d1cbd3ce)

Co-authored-by: Mario <mario@mariovavti.com>
2024-06-04 09:09:41 +00:00
Mario
1dc73935d9 deal with inReplyTo array
(cherry picked from commit 2145207ad2)

Co-authored-by: Mario <mario@mariovavti.com>
2024-06-04 09:08:32 +00:00
Mario
7d7b43c5b9 hotfix to mitigate queueworker crash
(cherry picked from commit a4d63ab9a3)

Co-authored-by: Mario <mario@mariovavti.com>
2024-06-04 09:06:50 +00:00
Mario
b5223a4efb add pdl for mod home
(cherry picked from commit fe43e0994f)
2024-03-27 10:01:33 +00:00
Mario
d71c2c245f reduce default directory result set
(cherry picked from commit f85d2d3423)
2024-03-26 14:59:45 +00:00
Mario
526729c0f1 changelog
(cherry picked from commit 418b1eaf78)
2024-03-26 10:06:05 +00:00
Mario
1cd3369f6a version 9.0.1 2024-03-26 09:53:38 +00:00
Mario
c26ae553e6 if the updated item contains an open modal, the modal we be replaced with the new data but the backdrop will stay because it is attached to the end of the page -> remove it
(cherry picked from commit e0ac7b7f9f)
2024-03-26 09:28:25 +00:00
Mario
8d78698d00 deprecated bootstrap namespace in list mode
(cherry picked from commit 80d1e07908)
2024-03-25 21:53:25 +00:00
Mario
d5c189753a wrong dreport link in blog mode
(cherry picked from commit f72f5c7321)
2024-03-25 21:53:06 +00:00
Mario
9861e7a0c4 more bootstrap override
(cherry picked from commit a5d483fb5d)
2024-03-25 21:52:42 +00:00
Mario
6d5fa9205c more remove additional linebreaks after block element plus test
(cherry picked from commit 53354a1930)
2024-03-25 21:52:14 +00:00
Mario
0fee7804fb allow to run additional site specific commands at the end of util/udall
(cherry picked from commit c052b7fa99)
2024-03-25 21:51:49 +00:00
Mario
4002dbaa8b Merge branch 'master' of https://framagit.org/hubzilla/core 2024-03-25 17:32:11 +00:00
Mario
57e32a7912 add observer to the permissions query. this should not be necessary but it makes it clear why it should be included in the cache key 2024-03-25 17:31:53 +00:00
Mario
b6a72d6e4e tilt the piin
(cherry picked from commit 637f39f282)
2024-03-25 17:19:32 +00:00
Mario
6e592ed200 add the observer hash to the cache key in categories_widget() 2024-03-25 17:18:26 +00:00
Mario
6c033fc776 Merge branch 'fix-category-widget-template' into 'dev'
Fix smarty deprecation warning in category widget.

See merge request hubzilla/core!2116

(cherry picked from commit 2ff84ab25a)

b139e5bb Fix smarty deprecation warning in category widget.
2024-03-25 14:21:39 +00:00
Mario
7c4362db53 make sure we preserve linefeeds in the actual content of lists and tables also add tests
(cherry picked from commit 3c0d6339bb)
2024-03-24 17:00:56 +00:00
Mario
f7bf9ede72 revert default to activity type Article until we have a more stable solution to override it for platforms which do not support it
(cherry picked from commit ecdd9a4d6e)
2024-03-23 11:20:44 +00:00
Mario
1aeb05628b Merge branch '9.0RC' 2024-03-22 08:37:29 +00:00
Mario
b464fae3bf version 9.0 2024-03-22 08:36:38 +00:00
Mario
a34ce0732d Merge branch 'dev' into 9.0RC 2024-03-22 08:36:07 +00:00
Mario
01ebd51fb2 Merge branch 'dev' into 9.0RC 2024-03-22 08:27:18 +00:00
Mario
6c6fc82f43 Merge branch 'dev' into 9.0RC 2024-03-22 08:15:20 +00:00
Mario
192ab22cda Merge branch 'dev' into 9.0RC 2024-03-21 20:48:33 +00:00
Mario
2dc1adf091 Merge branch 'dev' into 9.0RC 2024-03-21 11:37:55 +00:00
Mario
6a3d372050 RC3 2024-03-20 19:41:28 +00:00
Mario
c95359024c Merge branch 'dev' into 9.0RC 2024-03-20 19:39:00 +00:00
Mario
1097bcdaf9 Merge branch 'dev' into 9.0RC 2024-03-19 09:53:33 +00:00
Mario
f910de849f Merge branch 'dev' into 9.0RC 2024-03-19 09:46:21 +00:00
Mario
5c2e10c01e changelog 2024-03-19 09:44:18 +00:00
Mario
569f243ebd Merge branch 'dev' into 9.0RC 2024-03-18 22:10:16 +00:00
Mario
d4b1bcd641 Merge branch 'dev' into 9.0RC 2024-03-18 09:12:58 +00:00
Mario
e237cf226a Merge branch 'dev' into 9.0RC 2024-03-18 09:10:34 +00:00
Mario
0745d0616a Merge branch 'dev' into 9.0RC 2024-03-17 21:34:06 +00:00
Mario
2114779037 Merge branch 'dev' into 9.0RC 2024-03-17 11:01:27 +00:00
Mario
9600789d6b slightly adjust text sizing 2024-03-16 19:39:48 +00:00
Mario
ea7559c158 RC2 2024-03-16 16:24:27 +00:00
Mario
baa12b7497 Merge branch 'dev' into 9.0RC 2024-03-16 16:23:32 +00:00
Mario
5860abf46f maybe git can be removed again now after the streams lib got removed? 2024-03-16 15:50:58 +00:00
Mario
2874d3e1e1 Merge branch 'dev' into 9.0RC 2024-03-16 15:47:55 +00:00
Mario
36778850ee Merge branch 'dev' into 9.0RC 2024-03-16 12:18:46 +00:00
Mario
24132e56d9 Merge branch 'dev' into 9.0RC 2024-03-16 11:56:44 +00:00
Mario
8873c10364 Merge branch '9.0RC' of https://framagit.org/hubzilla/core into 9.0RC 2024-03-16 11:50:42 +00:00
Mario
77e1220cf9 fix version 2024-03-16 11:50:16 +00:00
Mario
d846cefade adjust encrypted content representation and also add it to bb_to_markdown
(cherry picked from commit f3efdbf230)
2024-03-15 22:36:44 +00:00
System user; root
5b7387459c version 8.8.8 2024-02-29 11:05:01 +01:00
System user; root
5d64a9c90f changelog 2024-02-29 11:03:28 +01:00
System user; root
ffaa985339 streams compatibility 2024-02-29 11:01:30 +01:00
Mario Vavti
f57fbaa5dd version 8.8.7 2024-01-19 11:07:23 +01:00
Mario Vavti
aba8002170 Fix regression in Activity::actor_store()
(cherry picked from commit 9cc85adf47)
2024-01-19 10:03:58 +00:00
Mario Vavti
0bdffc4a2d make our jsonld parser happy 2024-01-13 08:49:14 +01:00
Mario Vavti
400dfb4e6b version 8.8.6 2024-01-11 17:57:51 +01:00
Mario
6b951734ce changelog
(cherry picked from commit 000fcfd1ac)
2024-01-11 16:56:13 +00:00
Mario
051e2ed6cd provide some more jsonld builtins
(cherry picked from commit aac406a245)
2024-01-11 16:40:16 +00:00
Mario Vavti
ef2952b5fd libsync: dev branch compatibility 2024-01-11 17:00:47 +01:00
Mario Vavti
d655e1d765 version 8.8.5 2024-01-01 21:30:45 +01:00
Mario Vavti
db70ed006d Merge branch 'dev' 2024-01-01 21:29:53 +01:00
Mario Vavti
9e2a253dda Merge branch 'dev' 2024-01-01 21:21:04 +01:00
Mario
b629eb5657 fix merge conflict 2023-12-20 12:32:15 +00:00
Mario
2e674cd0b3 version 8.8.4 2023-12-20 12:26:28 +00:00
Mario
3330e9a19a Merge branch 'dev' 2023-12-20 12:25:43 +00:00
Mario
c0d93bbcf4 Merge branch 'dev' 2023-12-20 12:20:04 +00:00
Mario
d372daff60 Revert "check return from Config::Load() and retry on failure plus cleanup"
This reverts commit 69266cd6c6
2023-12-17 11:16:58 +00:00
Mario
f742e6e394 Merge branch 'dev' 2023-12-17 08:53:32 +00:00
Mario
603c5692ae Merge branch 'dev' 2023-12-17 08:50:39 +00:00
Mario
abe2ab229a version 8.8.3 2023-12-17 08:43:08 +00:00
Mario
5ad9939bcf Merge branch 'dev' 2023-12-17 08:41:48 +00:00
Mario
70470016cc Merge branch 'dev' 2023-12-17 08:35:33 +00:00
Mario Vavti
c307a71f53 Merge branch 'dev' 2023-12-08 18:08:03 +01:00
Mario
f62d16d274 Merge branch 'dev' 2023-12-06 11:27:54 +00:00
Mario
5f942d78e6 Merge branch 'dev' 2023-12-06 11:23:21 +00:00
Mario
c8158c3d62 vesrion 8.8.2 2023-12-06 11:14:00 +00:00
Mario
1f4762060f Merge branch 'dev' 2023-12-06 11:12:25 +00:00
Mario
8c9d2bc6f6 Merge branch 'dev' 2023-11-27 08:48:47 +00:00
Mario
43c2e71b25 Merge branch 'dev' 2023-11-27 08:45:19 +00:00
Mario
76a1a6da34 version 8.8.1 2023-11-27 08:42:09 +00:00
Mario
0fd8e02a88 Merge branch '8.8RC' 2023-11-25 17:12:28 +01:00
Mario
55c4bfb670 version 8.8 2023-11-25 17:11:07 +01:00
Mario
ea1030f8bf Merge branch 'dev' into 8.8RC 2023-11-25 17:06:55 +01:00
Mario
ee149ed1eb Merge branch 'dev' into 8.8RC 2023-11-25 17:05:08 +01:00
Mario
6e59d95da5 Merge branch 'dev' into 8.8RC 2023-11-23 20:25:13 +00:00
Mario
5cb1a9dcc6 Merge branch 'dev' into 8.8RC 2023-11-22 16:21:58 +00:00
Mario
f8c631a3f0 Merge branch 'dev' into 8.8RC 2023-11-20 20:32:46 +00:00
Mario
0f453ae4cf version 8.8RC2 2023-11-20 20:27:35 +00:00
Mario Vavti
82e704ec5b remove the directory updates entry if the channel was removed
(cherry picked from commit 67aaa97904)
2023-10-26 14:08:33 +00:00
Mario
670228ff63 more changelog
(cherry picked from commit 4338e4ef86)
2023-09-16 20:16:59 +02:00
Mario
dbc712c53c version 8.6.3 2023-09-16 18:02:22 +00:00
Mario
bb6011ea73 Revert "version 8.6.3"
This reverts commit 1915f34de8
2023-09-16 18:00:42 +00:00
Mario
1915f34de8 version 8.6.3 2023-09-16 17:59:31 +00:00
Mario
675f26fe90 changelog
(cherry picked from commit 491e309911)
2023-09-16 19:58:52 +02:00
Mario
24d862c1bc fix regression in jsonld_document_loader()
(cherry picked from commit 3708c1ac8c)
2023-09-16 13:26:38 +02:00
Mario
a1e583129f more type checking
(cherry picked from commit a06b28b693)
2023-09-15 15:46:15 +02:00
System user; apache
3a01aa40d8 only select the required fields to prevent memory exhaustion on big result sets 2023-09-15 15:28:13 +02:00
Mario
ec66949b35 version 8.6.2 2023-08-27 19:06:14 +00:00
Mario
542a527d0d changelog 2023-08-27 19:02:55 +00:00
Mario Vavti
5e0c392287 test refactor the previous commit
(cherry picked from commit 0a3094fc9a)
2023-08-27 20:01:01 +02:00
Mario Vavti
05ff94941c allow public stream comments/reactions if item_fetched is set, when fetching parent of a reaction fetch the thr_parent
(cherry picked from commit 816bbad28a)
2023-08-27 20:00:30 +02:00
Mario
a27d75d610 fix notification text for likes in cases where obj.actor is not set
(cherry picked from commit ce4150cd06)
2023-08-27 19:59:24 +02:00
Mario
446e842904 make sure that we allow "0" as pathname
(cherry picked from commit be19b75031)
2023-08-27 19:58:55 +02:00
Mario
ba153e2c18 mising pdl file
(cherry picked from commit 2d5c99f459)
2023-08-27 19:58:07 +02:00
Mario
3342ea6891 fix unable to create folders with name 0
(cherry picked from commit c4af4e3297)
2023-08-27 19:57:01 +02:00
Mario
8c3d5fd295 fix index name mixup
(cherry picked from commit 1acfc05e2d)
2023-08-27 19:56:26 +02:00
Mario
5fc58fb10a missing semicolon
(cherry picked from commit b40c38f58c)
2023-08-27 19:56:10 +02:00
Mario
4e2ae9cd4e remove unused variable
(cherry picked from commit 0ec715d7c9)
2023-08-27 19:55:49 +02:00
DM42.Net Hubzilla Development
4167ca2e5c Fix Typo
(cherry picked from commit 3bd2a91992)
2023-08-27 19:55:23 +02:00
Mario
9b3e9dcf02 fix cached jsonld files fetched via network 2023-08-27 17:53:40 +00:00
Mario
f203fcc92e more performance improvements
(cherry picked from commit 4b1384be83)
2023-08-27 19:50:39 +02:00
Mario
0b20069c20 move trigger out of the loop for better performance and some var -> let 2023-08-27 17:49:52 +00:00
Mario
f89ce93940 ignore internal follow activity
(cherry picked from commit 7200c71673)
2023-08-27 19:45:51 +02:00
Mario
1ddbc8a26d fix admin table highlight by adding the correct classes
(cherry picked from commit c412c01a65)
2023-08-27 19:43:09 +02:00
Mario
7dad60bbd5 version 8.6.1 2023-07-18 13:09:56 +00:00
Mario
a66b4626fb changelog
(cherry picked from commit 330add963d)
2023-07-18 15:08:07 +02:00
Mario
74d7fa61d9 fix hcard addon regression
(cherry picked from commit ef6ea4484e)
2023-07-18 12:11:50 +02:00
Mario
63fb8d0392 Merge branch '8.6RC' 2023-07-11 18:57:18 +00:00
Mario
384de0925e version 8.6 2023-07-11 18:56:21 +00:00
Mario
5b5c569c82 RC2 2023-07-05 10:06:57 +00:00
Mario
57796a2f96 only remove the owner from delivery if its their post and minor cleanup
(cherry picked from commit 59b217f7ea)
2023-06-12 10:17:17 +02:00
Mario
ec6cec8854 even more changelog
(cherry picked from commit 3fb22d27ab)
2023-06-02 21:47:41 +02:00
Mario
1cbaab5889 more changelog
(cherry picked from commit fef19b47c2)
2023-06-02 21:39:27 +02:00
Mario
5126613825 update bootstrap to version 5.3 2023-06-02 19:35:23 +00:00
Mario
07097d2fa3 version 8.4.2 2023-06-02 19:27:36 +00:00
Mario
8c9fb32ca0 changelog
(cherry picked from commit 68d921e918)
2023-06-02 21:26:22 +02:00
Mario
330b36159d work around hubloc confusion in mod rmagic
(cherry picked from commit 02089f15c4)
2023-06-02 21:18:52 +02:00
Mario
046152e798 improved unseen forums notification
(cherry picked from commit 6a27afa6ac)
2023-06-02 21:18:30 +02:00
Mario
11ca5bb7d5 owa: workaround for friendica when using nginx until this will be fixed at their side and check prrofile uid for features instead of local channel
(cherry picked from commit 423fdc0a96)
2023-06-02 21:16:56 +02:00
Mario
e0838ff8ab fix wrong array key
(cherry picked from commit 11a2419c22)
2023-06-02 21:13:31 +02:00
Mario Vavti
1b0e268416 fix theme_attachments()
(cherry picked from commit 65a472c113)
2023-06-02 13:35:39 +02:00
Mario
1a27fad9b9 backgound url must be between quotation marks 2023-05-20 12:40:28 +00:00
Mario
c2cfe481df version 8.4.1 2023-05-20 09:28:32 +00:00
Mario
3a56fb6ec6 Merge branch 'dev' 2023-05-20 09:27:45 +00:00
Mario
1a614ea023 instead of setting the value to 0 just continue so that already existing values will not be overwritten 2023-05-19 13:17:45 +00:00
Mario
65d98af24c Merge branch '8.4RC' 2023-05-17 13:28:23 +00:00
Mario
62d35627f3 version 8.4 2023-05-17 13:28:05 +00:00
Mario
84f1ed1587 Merge branch 'dev' into 8.4RC 2023-05-17 13:26:01 +00:00
Mario
18914f2081 Merge branch 'dev' into 8.4RC 2023-05-15 08:04:02 +00:00
Mario
5bdd699c9d Merge branch 'dev' into 8.4RC 2023-05-13 19:52:52 +00:00
Mario Vavti
049fc91615 Merge branch 'dev' into 8.4RC 2023-05-13 21:38:08 +02:00
Mario
5d330e18ff save a query 2023-05-12 16:29:42 +00:00
Mario
07662ff076 Merge branch 'dev' into 8.4RC 2023-05-12 08:02:19 +00:00
Mario
247cf40ace bump version 2023-05-10 20:45:33 +00:00
Mario
4a6b450c55 Merge branch 'dev' into 8.4RC 2023-05-10 20:44:43 +00:00
Mario
efd2f78274 check if var is set and escape $ sign in ru translation 2023-05-10 16:42:12 +00:00
Mario
690ff955fa Merge branch 'dev' into 8.4RC 2023-05-10 16:09:36 +00:00
Mario
d8306fca6f Merge branch 'dev' into 8.4RC 2023-05-10 12:05:15 +00:00
Mario
4ae81d753c Merge branch 'dev' into 8.4RC 2023-05-10 08:16:09 +00:00
Mario
93dda6f1fd Merge branch 'dev' into 8.4RC 2023-05-09 09:20:48 +00:00
Mario
74436eb03e Merge branch 'dev' into 8.4RC 2023-05-09 09:08:06 +00:00
Mario
677e730235 Merge branch 'dev' into 8.4RC 2023-05-07 14:25:17 +00:00
Mario
06c0af50c3 Merge branch 'dev' into 8.4RC 2023-05-07 12:48:14 +00:00
Mario
f6b66f4cad Merge branch 'dev' into 8.4RC 2023-05-07 12:17:09 +00:00
Mario
ca913e5077 Merge branch 'dev' into 8.4RC 2023-05-07 12:04:54 +00:00
Mario
984980b170 version 2023-05-07 11:31:50 +00:00
Mario
a57739c462 Merge branch 'dev' 2023-04-09 20:01:22 +00:00
Mario
92246ce3a8 Merge branch 'dev' 2023-04-09 19:53:27 +00:00
Mario
4a72ea6666 Merge branch 'dev' 2023-04-09 19:45:04 +00:00
Mario
dcfdf3a5d7 Merge branch 'dev' 2023-04-09 19:02:36 +00:00
Mario
9083e99d2a in fact this can be removed
(cherry picked from commit 70ca247c88)
2023-03-24 10:29:09 +01:00
Mario
14fc0c735b encode object after we have dealt with deleted items and work around a php error
(cherry picked from commit 70b3c8080e)
2023-03-24 10:05:37 +01:00
Mario
79405cf1d3 Merge branch 'dev' 2023-03-21 09:20:57 +00:00
Mario
89285f1408 Merge branch '8.2RC' 2023-03-19 13:55:18 +00:00
Mario
17c0bb2069 version 8.2 2023-03-19 13:54:15 +00:00
Mario
192736f6d7 Merge branch 'dev' into 8.2RC 2023-03-19 13:53:12 +00:00
Mario
4eee8ce770 Merge branch 'dev' into 8.2RC 2023-03-19 13:50:39 +00:00
Mario
3dcfdba74b simplify 2023-03-19 13:28:43 +00:00
Mario
5428053663 Merge branch 'dev' into 8.2RC 2023-03-19 09:43:34 +00:00
Mario
1e184b781b Merge branch 'dev' into 8.2RC 2023-03-19 08:22:29 +00:00
Mario
c1ddb89990 Merge branch 'dev' into 8.2RC 2023-03-18 15:47:09 +00:00
Mario
7ac7cb129f Merge branch 'dev' into 8.2RC 2023-03-17 09:44:17 +00:00
Mario
11b9e546a9 Merge branch 'dev' into 8.2RC 2023-03-14 10:43:03 +00:00
Mario
3a3fd38e3a Merge branch 'dev' into 8.2RC 2023-03-12 22:04:37 +00:00
Mario
0a679e503e make sure to only transform channel wall posts for group actors
(cherry picked from commit 95cbcf30d7)
2023-03-12 19:56:34 +01:00
Mario
e7c529f2c3 Merge branch 'dev' into 8.2RC 2023-03-12 18:44:28 +00:00
Mario
dfc70021e3 fix version after merge 2023-03-12 09:38:31 +00:00
Harald Eilertsen
0140f9fe56 translations: Misc updates for nb_NO.
(cherry picked from commit ec6832687b)
2023-01-27 08:00:26 +01:00
Harald Eilertsen
03243df16a translations: Fix timeago for nb_NO.
(cherry picked from commit 9065a90ac0)
2023-01-27 08:00:07 +01:00
Mario
2805520d1b Merge remote-tracking branch 'origin/8.0RC' 2023-01-13 20:01:05 +00:00
Mario
fb7ca18820 version 8.0 2023-01-13 19:56:11 +00:00
Mario
1b00d5657f Merge branch 'dev' into 8.0RC 2023-01-13 19:55:44 +00:00
Mario
9ec516e5a4 Merge branch 'dev' into 8.0RC 2023-01-12 16:12:26 +00:00
Mario
5cb92b6e21 Merge branch 'dev' into 8.0RC 2023-01-12 11:55:13 +00:00
Mario
2ddb88a34d RC2 2023-01-11 19:57:35 +00:00
Mario
71761c9039 Merge branch 'dev' into 8.0RC 2023-01-11 16:35:21 +00:00
Mario
bd5e834b42 Merge branch 'dev' into 8.0RC 2023-01-08 08:20:05 +00:00
Mario
4c434129a6 Merge branch 'dev' into 8.0RC 2023-01-05 18:25:45 +01:00
Mario
66333aedb7 fix version 2023-01-05 18:23:00 +01:00
Mario
f6d9406063 streamline ap followers only privacy warning with current handling. also fixes possible php error.
(cherry picked from commit 34125177e8)
2022-12-19 10:05:02 +01:00
Mario
6e881bcef2 exclude not tagable xchan networks handle_tag()
(cherry picked from commit 4f9a933108)
2022-12-08 21:41:56 +01:00
Mario
12a963cc40 version 7.8.7 2022-12-03 09:01:18 +00:00
Mario
f89975fd0e changelog 7.8.7
(cherry picked from commit b4dffe5946)
2022-12-03 09:59:06 +01:00
Mario
00512579f3 fix regression when adding feed contacts
(cherry picked from commit 5216c5b232)
2022-12-02 18:47:40 +01:00
Mario
697a74f37e fix regression: new event not created
(cherry picked from commit d16b6c3838)
2022-12-02 18:06:20 +01:00
2879 changed files with 108834 additions and 322808 deletions

View File

@@ -1,5 +1,4 @@
stages:
- pretest
- test
- deploy
@@ -26,6 +25,23 @@ variables:
POSTGRES_USER: ci-user
POSTGRES_PASSWORD: ci-pass
before_script:
# Install & enable Xdebug for code coverage reports
- apt-get update
- apt-get install -yqq libicu-dev libjpeg-dev libpng-dev libpq-dev libyaml-dev libzip-dev mariadb-client postgresql-client unzip zip
- pecl install xdebug yaml
- docker-php-ext-enable xdebug yaml
- docker-php-ext-configure gd --with-jpeg=/usr/include/
- docker-php-ext-install gd bcmath intl pdo_mysql pdo_pgsql zip exif
# Install composer
- curl -sS https://getcomposer.org/installer | php
# Install dev libraries from composer
- php ./composer.phar install --no-progress
# php.ini settings
- echo 'xdebug.mode=coverage' >> /usr/local/etc/php/php.ini
# hidden job definition with template for MySQL/MariaDB
.job_template_mysql: &job_definition_mysql
stage: test
@@ -37,10 +53,10 @@ variables:
HZ_TEST_DB_DATABASE: $MYSQL_DATABASE
script:
# Import hubzilla's DB schema
- echo "USE $MYSQL_DATABASE; $(cat ./install/schema_mysql.sql)" | mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host="$DB_HOST" --skip-ssl "$MYSQL_DATABASE"
- echo "USE $MYSQL_DATABASE; $(cat ./install/schema_mysql.sql)" | mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host="$DB_HOST" "$MYSQL_DATABASE"
# Show databases and relations/tables of hubzilla's database
- echo "SHOW DATABASES;" | mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host="$DB_HOST" --skip-ssl "$MYSQL_DATABASE"
- echo "USE $MYSQL_DATABASE; SHOW TABLES;" | mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host="$DB_HOST" --skip-ssl "$MYSQL_DATABASE"
- echo "SHOW DATABASES;" | mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host="$DB_HOST" "$MYSQL_DATABASE"
- echo "USE $MYSQL_DATABASE; SHOW TABLES;" | mysql --user=root --password="$MYSQL_ROOT_PASSWORD" --host="$DB_HOST" "$MYSQL_DATABASE"
# Run the actual tests
- touch dbfail.out
- vendor/bin/phpunit -d memory_limit=256M --configuration tests/phpunit.xml --no-progress --stop-on-error --coverage-text --colors=never --log-junit tests/results/junit.xml || exit_code=$?
@@ -84,55 +100,29 @@ variables:
paths:
- tests/results/
default:
image: php:8.2
before_script:
# Install & enable Xdebug for code coverage reports
- apt-get update
- apt-get install -yqq libicu-dev libjpeg-dev libpng-dev libpq-dev libyaml-dev libgmp-dev libzip-dev mariadb-client postgresql-client libmagickcore-7.q16-dev libmagickwand-dev unzip zip
- pecl install imagick xdebug yaml
- docker-php-ext-enable imagick xdebug yaml
- docker-php-ext-configure gd --with-jpeg=/usr/include/
- docker-php-ext-install gd gmp intl pdo_mysql pdo_pgsql zip exif
# Install composer
- curl -sS https://getcomposer.org/installer | php
# Install dev libraries from composer
- php ./composer.phar install --no-progress
# php.ini settings
- echo 'xdebug.mode=coverage' >> /usr/local/etc/php/php.ini
check_templates:
stage: pretest
script:
- touch .htconfig.php
- php util/precompile_smarty3.php
phpstan:
stage: pretest
script:
- touch .htconfig.php
- vendor/bin/phpstan --memory-limit=512M
# PHP8.2 with MySQL 8.0
php8.2_mysql8.0.22:
# PHP8.1 with MySQL 8.0
php8.1_mysql8.0.22:
image: php:8.1
services:
- mysql:8.0
<<: *job_definition_mysql
<<: *artifacts_template
# PHP8.2 with MariaDB 10.6
php8.2_mariadb10.6:
# PHP8.1 with MariaDB 10.6
php8.1_mariadb10.6:
image: php:8.1
services:
- name: mariadb:10.6
alias: mysql
<<: *job_definition_mysql
<<: *artifacts_template
# PHP8.2 with PostgreSQL 14
php8.2_postgres14:
# PHP8.1 with PostgreSQL 12
php8.1_postgres12:
image: php:8.1
services:
- postgres:14-alpine
- postgres:12-alpine
<<: *job_definition_postgres
<<: *artifacts_template

232
CHANGELOG
View File

@@ -1,235 +1,3 @@
Hubzilla 11.2.1 (2026-05-20)
- Fix channel creation failing in some situations
- Drop payloads with unsafe json-ld keys in LDSignatures::verify() and add tests
- Fix App::$profile set for removed channels
- Fix MessageFilter breaking when expecting string but array is given
- Superblock: fix blocking failed in some setups (sponsored by NLnet NGI0 Commons Fund/Superblock)
- Superblock: fix missing import of attribute (sponsored by NLnet NGI0 Commons Fund/Superblock)
- Superblock: improved detection of reshares (sponsored by NLnet NGI0 Commons Fund/Superblock)
- Cards: fix PHP warning
- Gallery: fix PHP warnings and only implement observer related javascript if there actually is an observer
- Diaspora: fix missing local namespace for thr_parent
Hubzilla 11.2 (2026-03-26)
Features
- Introduce parse_webbie() for preparing webbies and URLs for webfinger usage
- Allow to override cUrl useragent
- New HQ system status widget for admins (sponsored by NLnet NGI0 Commons Fund/Performance Profiling)
- Refactor drop_query_params() to deal with array params and add test
Maintenance
- Remove private members from API docs
- Remove custom CA certs
- Add type annotations in Extend\Route
- Set method visibility in Extend\Route
- Add API docs and licence info to Extend\Route
- Add a short sleep interval to Activity::init_background_fetch() when adding new work items in a loop
- Add a short sleep interval to the convo daemon loop to spread the load for large collections
- Use PHP matching rules in util/run_xgettext
- Store translation templates as .pot instead of .po
- Deprecate NULL_DATE constant in favor of DBA::get_null_date()
- Composer require guzzlehttp/psr7
- Update composer libs
- Move HQ channel notifications widget HTML to template
- Deprecate tags and attachment in activities
- Update the nginx config example to meet the more modern approach
Bugfixes
- Fix route and widget register() not deduplicating entries
- Fix issue in route and widget unregister() where we unregistered even if only one of the two arguments did not match
- Fix issue in Storage/Directory where we returned a partial path instead of throwing exception if a directory of a path could not be found
- Fix possible endless loop in externals daemon
- Fix fatal error in italian translation file
- Fix mod network not displaying direct messages when filters active - issue #1973
- Fix ghost notifications with reshared items - issue #1970
- Fix issue with double typed objects in Lib/Activity
- Fix events displaying event timezone instead of adjusted to timezone
- Fix duplicated terms in activity object
- Fix last modified timestamp not updating in attach_store()
Addons
- Wopi: fix headers already set warning when serving the file to the client
- Superblock: complete rewrite with extended functionality and added tests for version 3.0 (sponsored by NLnet NGI0 Commons Fund/Superblock)
- Diaspora: use Diaspora/2 useragent when fetching hcards to prevent being redirected to some shady bot guard
- Add composer config and autoload files for addons
- Wopi: return early in construct_page hook and
- Wopi: fix wrong hook name in uninstall function
Hubzilla 11.0 (2026-01-30)
Features
- Rewrite Lib/MessageFilter (ported from forte) and add more tests
- Rewrite editor encryption feature to implement libsodium and PBDKF2 for password hashing
- Change default feed behaviour to return only toplevels - issue #1953
- Add active themes list to siteinfo
- Show viewsource link for pubstream items
- Implement singleton object cache
- Restructure cross protocol message payload
- View source will now display the raw object instead of just the object body
- Implement OWA2
Maintenance
- Remove capability to update xchan entries via api
- Move mod search HTML to template
- Move channel activities HTML to template
- Update util/run_xgettext.sh to ignore not relevant directories
- Update composer libs
- Move mod bookmarks HTML to template
- Update italian translation
- Use finfo class to determine mime type in attach_store()
- Bump attach.filetype field length to match photo.mimetype
- Move share container HTML to template
- Update cloud directory template to provid necessary data for the WOPI addon
- Remove appearances of curl_close() - deprected and noop since PHP version 8.0
- Remove appearances of GD imagedestroy() - deprected and noop since PHP version 8.0
- Improve replies id detection
- Remove deprecated ofeed and ochannel modules
- Remove support for deprecated AS1 verbs and objects in the network stream filters
- Improve detection of allday events
- Improve handling of events with no endTime
- Provide the event hash and timezone in the event object
- Improve test isolation
- Add quoteUri field to activities
- Update API docs for hooks
- Improve share to quote conversion
- Rename (un)serialise() -> json_(un)serialize()
- CI: Replace use of create_identity in MagicTest
- CI: Add test db fixtures for hubloc table
- CI: Stub crypto calls from CreateIdentityTest (performance)
- CI: Add tests for Profiles module
- CI: Add tests for Zotfinger module
- Use json serialisation for iconfig
- CI: Add test db fixtures for channel table
- CI: Reload test fixtures from db
- CI: Set logged in channel for delete account test
- CI: Use root passwd to set up MySQL test db
- Improve dba_pdo::insert function
- Update install document
- Remove unused sprintf.js
- Move twitteroauth lib to addon_common
- Move slinky lib to addon_common
- Move openid lib to openid addon
- Move XRI lib to wppost addon
- Move diff lib to wiki addon
- Remove unused bootbox lib
- Remove unused images
- Move language specific folders into its own subfolder in /view
- Remove deprecated sjcl lib and references
- Deprecate outbound JSalmon signatures
Bugfixes
- Fix reply-to button not diplayed for anonymous visitors allthough permission is granted
- Fix wrong icon class in mod cloud
- Fix bulk deleting files
- Fix cloud root folder shows unknown error
- Fix edited timestamp in attach_store() in case of update
- Fix grammar in Lib/Enotify::submit()
- Fix content-type and length header in Module\TestCase
- Fix args in xchan_fetch() not escaped
- Fix notification for events linking to wrong message id - issue #1954
- Fix post_mail permission not working independend from the send_stream/post_comment permissions - issue #1951
- Fix item relayed again in case it comes back from a channel that sources our channel
- Fix activity signer not set
- Fix category link not update at clone - issue #1932
- Fix tag delivery attempted item type is not post - fix issue #1941
- Fix block/unblock account - issue #1947
- Fix warnings in profile_edit template
- Fix warnings for optional args in field templates
- Fix undefined vars in mod profiles
- Fix fullscreen button class
- Fix delivery report when syncing cloned channels
Addon
- New addon implementing basic WOPI protocol - integrates collabora with the Files app
- Redphotos: addon removed - was used for migration from redmatrix to hubzilla
- Redfiles: addon removed - was used for migration from redmatrix to hubzilla
- Nsfw: rewrite to implement new MessageFilter
- Wiki: fix long loading time due to oembed attempts
- Wiki: improved SQL query
Hubzilla 10.6.1 (2025-11-21)
- Fix insufficient target attribution for forums
- Fix reshare regression in forum logic
Hubzilla 10.6 (2025-11-04)
Features
- Improved background fetching of replies collection
- Refactor ASCache and implement ASCache::isCacheable()
- Implement ASCache in ASCollection to improve performance
- Add json support for help index widget and add a new template
- Implement hidden pconfig system.notifications_count_limit
- Implement hidden pconfig system.invert_notifications_order
- Implement mark seen button for unseen pubstream notifications
- Iplemented per forum unseen items notifications
- Intoduce Activity::pasteQuote() to paste the quote into the body at the right place if applicable and add test
- Implement ASCache in Activity::get_quote()
- Display webfinger address instead of channel URL in HQ widget notifications tab for consistency with other tabs
- Implement FEP-e232 object links
Maintenance
- Use a better supported json canonicalization library
- CI: up PHP images to version 8.2
- Enable gmp PHP extension in gitlab-ci
- Update composer libs which now require gmp PHP extension
- HTTPSig: return early if we got no key info
- Removed deprecated conv_list template
- Refactor PhotoGd::imageString() to reduce complexity
- Add ImageQuality class to hold quality values
- CI: introduce a quick test to check that smarty templates compile in the pretest stage
- CI: upgrade to postgres 13
- CI: add pretest step and run PHPStan
- Updated Spanish strings
- Remove redundant f arg in tagcloud widget
- Remove dead code in handle_tag()
- Enable unit tests for extensions
- Remove obsolete phpunit configurations
- Code cleanup
Bugfixes
- Fix encoding for webpage, layout and block title and body when editing - issue #1946
- Fix issues which prevented files and photos to be updated correctly after rename via DAV
- Fix contact edit modal logic hijacking URL fragment on pages other than /connections
- Fix deprecation notice in MessagesWidgetTest
- Fix whole URL punified in mod follow
- Fix whole URL punified in mod search
- Fix setup check for zip PHP extension
- Fix issue where remote channels could post to wall if they had write_storage permission - issue #1940
- Fix not all notification icons updated
- Fix notification icon not updated on medium screen size
- Fix image/gif not handled in PhotoGd::imageString()
- Fix issue where not all seen items where removed from the unseen notifications
- Fix item_by_item_id() not returning Announce items - issue #1936
- Fix archive widget not returning results if in single item mode - issue #1911
- Fix db query in verify_email_address()
- Fix search in mod channel only returning single post items - issue #1929
- Fix commented out register_account hook breaking notify admin addon
Addon
- Superblock: add tests
- Wiki: fix photo embed buton for markdown
- Pubcrawl: return early if we could not find an AS actor id and add some logging
- Cart: adapt logic to fetch tpl from theme folder first
- Cavatar: remove redundant arg from cavatar_init()
Hubzilla 10.4.4 (2025-10-06)
- Fix issue when confirming pending registrations
- Fix TOS headings
- Fix TOS paths
- Add english TermsOfService.md
- CI: skip ssl check
Hubzilla 10.4.3 (2025-08-15)
- Refactor module vote to prohibit double votes at the sender side
- Fix vote answers counted as comments
- Start transition of deprecated AS1 item.verb vocabulary to AS2 on demand in mod channel, articles and cards
- Fix regression in retrieving channel address in wtagblock() and whitespace fixes
Hubzilla 10.4.2 (2025-08-08)
- Implement item_custom_display hook in mod HQ
- Refactor item fetching functions to reflect item_normal() changes

View File

@@ -38,6 +38,7 @@ class PermissionRoles {
];
$ret['limits'] = PermissionLimits::Std_Limits();
$ret['limits']['post_comments'] = PERMS_AUTHED;
$ret['limits']['post_mail'] = PERMS_AUTHED;
$ret['limits']['post_like'] = PERMS_AUTHED;
$ret['limits']['chat'] = PERMS_AUTHED;
break;

View File

@@ -212,7 +212,6 @@ class Permissions {
* @return array Associative array with
* * \e array \b perms Permission array
* * \e int \b automatic 0 or 1
* * \e srtring \b role
*/
static public function connect_perms($channel_id) {
@@ -231,6 +230,70 @@ class Permissions {
}
}
// look up the permission role to see if it specified auto-connect
// and if there was no permcat or a default permcat, set the perms
// from the role
/*
$role = get_pconfig($channel_id, 'system', 'permissions_role');
if ($role) {
$xx = PermissionRoles::role_perms($role);
if ($xx['perms_auto'])
$automatic = 1;
if ((!$my_perms) && ($xx['perms_connect'])) {
$default_perms = $xx['perms_connect'];
$my_perms = Permissions::FilledPerms($default_perms);
}
}
*/
// If we reached this point without having any permission information,
// it is likely a custom permissions role. First see if there are any
// automatic permissions.
/*
if (!$my_perms) {
$m = Permissions::FilledAutoperms($channel_id);
if ($m) {
$automatic = 1;
$my_perms = $m;
}
}
*/
// If we reached this point with no permissions, the channel is using
// custom perms but they are not automatic. They will be stored in abconfig with
// the channel's channel_hash (the 'self' connection).
/*
if (!$my_perms) {
$r = q("select channel_hash from channel where channel_id = %d",
intval($channel_id)
);
if ($r) {
$x = q("select * from abconfig where chan = %d and xchan = '%s' and cat = 'my_perms'",
intval($channel_id),
dbesc($r[0]['channel_hash'])
);
if ($x) {
foreach ($x as $xv) {
$my_perms[$xv['k']] = intval($xv['v']);
}
}
}
}
*/
return (['perms' => $my_perms, 'automatic' => $automatic, 'role' => $pc]);
}
/*
static public function serialise($p) {
$n = [];
if ($p) {
foreach ($p as $k => $v) {
if (intval($v)) {
$n[] = $k;
}
}
}
return implode(',', $n);
}
*/
}

View File

@@ -5,8 +5,6 @@ namespace Zotlabs\Daemon;
use Zotlabs\Lib\Activity;
use Zotlabs\Lib\ActivityStreams;
use Zotlabs\Lib\ASCollection;
use Zotlabs\Lib\ASCache;
use Zotlabs\Lib\Config;
class Convo {
@@ -14,77 +12,52 @@ class Convo {
logger('convo invoked: ' . print_r($argv, true));
if ($argc < 4) {
if ($argc != 4) {
return;
}
$channels = explode(',', $argv[1]);
if (!$channels) {
$id = $argv[1];
$channel_id = intval($argv[2]);
$contact_hash = $argv[3];
$channel = channelx_by_n($channel_id);
if (!$channel) {
return;
}
$observer_hash = $argv[2];
if (!$observer_hash) {
$r = q("SELECT abook.*, xchan.* FROM abook left join xchan on abook_xchan = xchan_hash
WHERE abook_channel = %d and abook_xchan = '%s' LIMIT 1",
intval($channel_id),
dbesc($contact_hash)
);
if (!$r) {
return;
}
$mid = $argv[3];
if (!$mid) {
$contact = array_shift($r);
$obj = new ASCollection($id, $channel);
$messages = $obj->get();
if (!$messages) {
return;
}
$force = $argv[4] ?? false;
$interval = Config::Get('queueworker', 'queue_interval', 500000);
foreach ($channels as $channel_id) {
$channel = channelx_by_n($channel_id);
$obj = new ASCollection($mid, $channel);
$messages = $obj->get();
if (!$messages) {
continue;
foreach ($messages as $message) {
if (is_string($message)) {
$message = Activity::fetch($message, $channel);
}
foreach ($messages as $message) {
$network_fetch = false;
if (is_string($message)) {
$cached = ASCache::Get($message);
if ($cached) {
// logger('convo_cached: ' . $message);
$data = $cached;
}
else {
// logger('convo_fetching: ' . $message);
$network_fetch = true;
$data = Activity::fetch($message, $channel);
if ($data) {
ASCache::Set($message, $data);
}
}
}
else {
$data = $message;
}
if (!$network_fetch) {
// Add some delay so that the DB will not be overwhelmed
// Fetched from network will already have a slight delay
usleep($interval);
}
$AS = new ActivityStreams($data);
if ($AS->is_valid() && is_array($AS->obj)) {
$item = Activity::decode_note($AS);
$item['item_fetched'] = true;
Activity::store($channel, $observer_hash, $AS, $item, false, $force);
}
// set client flag because comments will probably just be objects and not full blown activities
// and that lets us use implied_create
$AS = new ActivityStreams($message);
if ($AS->is_valid() && is_array($AS->obj)) {
$item = Activity::decode_note($AS);
$item['item_fetched'] = true;
Activity::store($channel, $contact['abook_xchan'], $AS, $item);
}
}
return;

View File

@@ -2,9 +2,7 @@
namespace Zotlabs\Daemon;
use DBA;
use Zotlabs\Lib\Config;
use Zotlabs\Lib\ObjCache;
use Zotlabs\Lib\Libsync;
use Zotlabs\Lib\Libzotdir;
@@ -94,7 +92,7 @@ class Cron {
// delete expired access tokens
$r = q("select atoken_id from atoken where atoken_expires > '%s' and atoken_expires < %s",
dbesc(DBA::$dba->get_null_date()),
dbesc(NULL_DATE),
db_utcnow()
);
if ($r) {
@@ -238,73 +236,6 @@ class Cron {
if (!$restart)
Master::Summon(array('Cronhooks'));
// move as obj cache to fs
if (!Config::Get('system', 'as_objects_moved')) {
$results = dbq("select iconfig.*, item.mid from iconfig left join item on iid = item.id where cat = 'activitypub' and k = 'rawmsg' limit 300");
if ($results) {
foreach ($results as $result) {
if (is_string($result['v'])) {
if (str_starts_with($result['v'], '{')) {
$result['v'] = json_decode($result['v'], true);
}
elseif (str_starts_with($result['v'], 'json:')) {
$result['v'] = json_unserialize($result['v']);
}
elseif (preg_match('|^a:[0-9]+:{.*}$|s', $result['v'])) {
$result['v'] = unserialize($result['v'], ['allowed_classes' => false]);
}
}
if (is_array($result['v'])) {
ObjCache::Set($result['mid'], $result['v']);
}
q("delete from iconfig where id = %d",
intval($result['id'])
);
}
}
else {
Config::Set('system', 'as_objects_moved', 1);
}
}
// move diaspora obj cache to fs
if (!Config::Get('system', 'diaspora_objects_moved')) {
$results = dbq("select iconfig.*, item.mid from iconfig left join item on iid = item.id where cat = 'diaspora' and k = 'fields' limit 300");
if ($results) {
foreach ($results as $result) {
if (is_string($result['v'])) {
if (str_starts_with($result['v'], '{')) {
$result['v'] = json_decode($result['v'], true);
}
elseif (str_starts_with($result['v'], 'json:')) {
$result['v'] = json_unserialize($result['v']);
}
elseif (preg_match('|^a:[0-9]+:{.*}$|s', $result['v'])) {
$result['v'] = unserialize($result['v'], ['allowed_classes' => false]);
}
}
if (is_array($result['v'])) {
ObjCache::Set($result['mid'], $result['v'], 'diaspora');
}
q("delete from iconfig where id = %d",
intval($result['id'])
);
}
}
else {
Config::Set('system', 'diaspora_objects_moved', 1);
}
}
Config::Set('system', 'lastcron', datetime_convert());
//All done - clear the lockfile

View File

@@ -74,8 +74,6 @@ class Externals {
}
}
$attempts++;
if (!$url) {
continue;
}
@@ -87,6 +85,7 @@ class Externals {
$blacklisted = true;
}
$attempts++;
// make sure we can eventually break out if somebody blacklists all known sites

View File

@@ -36,8 +36,6 @@ class Fetchparents {
Activity::fetch_and_store_parents($channel, $observer_hash, $mid, null, $force);
}
Activity::init_background_fetch($observer_hash);
return;
}

View File

@@ -4,8 +4,6 @@ namespace Zotlabs\Daemon;
use Zotlabs\Lib\Activity;
use Zotlabs\Lib\Config;
use Zotlabs\Lib\IConfig;
use Zotlabs\Lib\ObjCache;
use Zotlabs\Lib\Libzot;
use Zotlabs\Lib\Queue;
@@ -266,6 +264,7 @@ class Notifier {
}
if (!item_forwardable($target_item)) {
//hz_syslog(print_r($target_item,true));
logger('notifier: target item not forwardable', LOGGER_DEBUG);
return;
}
@@ -318,15 +317,10 @@ class Notifier {
return;
}
$m = ObjCache::Get($target_item['mid']);
if (!$m) {
$m = IConfig::Get($target_item, 'activitypub', 'rawmsg');
}
$m = get_iconfig($target_item, 'activitypub', 'signed_data');
// Re-use existing signature unless the activity type changed to a Tombstone, which won't verify.
if ($m && (!intval($target_item['item_deleted']))) {
self::$encoded_item = $m;
self::$encoded_item = json_decode($m, true);
}
else {
$activity = Activity::encode_activity($target_item);

View File

@@ -9,14 +9,10 @@ class Onedirsync {
static public function run($argc, $argv) {
if ($argc < 2 || is_int($argv[1]) === false) {
logger('onedirsync: no update id');
return;
}
logger('onedirsync: start ' . intval($argv[1]));
$update_id = intval($argv[1]);
if (($argc > 1) && (intval($argv[1])))
$update_id = intval($argv[1]);
if (!$update_id) {
logger('onedirsync: no update id');

View File

@@ -2,7 +2,6 @@
namespace Zotlabs\Daemon;
use DBA;
use Zotlabs\Lib\Activity;
use Zotlabs\Lib\ActivityStreams;
use Zotlabs\Lib\ASCollection;
@@ -16,14 +15,10 @@ class Onepoll {
static public function run($argc, $argv) {
if ($argc < 2 || is_int($argv[1]) === false) {
logger('onepoll: no contact');
return;
}
logger('onepoll: start');
$contact_id = intval($argv[1]);
if (($argc > 1) && (intval($argv[1])))
$contact_id = intval($argv[1]);
if (!$contact_id) {
logger('onepoll: no contact');
@@ -39,7 +34,7 @@ class Onepoll {
$contacts = q("SELECT abook.*, xchan.* FROM abook
LEFT JOIN xchan ON xchan_hash = abook_xchan
WHERE abook_id = %d",
$contact_id
intval($contact_id)
);
if (!$contacts) {
@@ -58,7 +53,7 @@ class Onepoll {
logger("onepoll: poll: ($contact_id) IMPORTER: {$importer['xchan_name']}, CONTACT: {$contact['xchan_name']}");
$last_update = ((($contact['abook_updated'] === $contact['abook_created']) || ($contact['abook_updated'] <= DBA::$dba->get_null_date()))
$last_update = ((($contact['abook_updated'] === $contact['abook_created']) || ($contact['abook_updated'] <= NULL_DATE))
? datetime_convert('UTC', 'UTC', 'now - 7 days')
: datetime_convert('UTC', 'UTC', $contact['abook_updated'] . ' - 2 days')
);

View File

@@ -2,7 +2,6 @@
namespace Zotlabs\Daemon;
use DBA;
use Zotlabs\Lib\Config;
class Poller {
@@ -118,7 +117,7 @@ class Poller {
// if we've never connected with them, start the mark for death countdown from now
if ($c <= DBA::$dba->get_null_date()) {
if ($c <= NULL_DATE) {
q("update abook set abook_connected = '%s' where abook_id = %d",
dbesc(datetime_convert()),
intval($contact['abook_id'])
@@ -174,7 +173,7 @@ class Poller {
if ($dirmode == DIRECTORY_MODE_SECONDARY || $dirmode == DIRECTORY_MODE_PRIMARY) {
$r = q("SELECT * FROM updates WHERE ud_update = 1 AND (ud_last = '%s' OR ud_last > %s - INTERVAL %s)",
dbesc(DBA::$dba->get_null_date()),
dbesc(NULL_DATE),
db_utcnow(),
db_quoteinterval('7 DAY')
);
@@ -185,7 +184,7 @@ class Poller {
// If they didn't respond when we attempted before, back off to once a day
// After 7 days we won't bother anymore
if ($rr['ud_last'] > DBA::$dba->get_null_date())
if ($rr['ud_last'] > NULL_DATE)
if ($rr['ud_last'] > datetime_convert('UTC', 'UTC', 'now - 1 day'))
continue;

View File

@@ -1,57 +1,15 @@
<?php
/*
* SPDX-FileCopyrightText: 2025 The Hubzilla Community
* SPDX-FileContributor: redmatrix
* SPDX-FileContributor: Klaus Weidenbach
* SPDX-FileContributor: zotlabs
* SPDX-FileContributor: Harald Eilertsen <haraldei@anduin.net>
*
* SPDX-License-Identifier: MIT
*/
namespace Zotlabs\Extend;
use App;
/**
* A class for hooking into Hubzilla.
* @brief Hook class.
*
* Hooks are functions that Hubzilla will invoke at certain points in the code
* during execution. An addon can register a callback handler that will be
* called whenever the specified hook is invoked. A callback handler is a
* function that takes a reference to an array containing the callback
* arguments as it's only argument.
*
* @see call_hooks
* @see load_hooks
*/
class Hook {
/**
* Register a callback handler for a hook.
*
* A callback handler is a function that takes a reference to an array
* containing the callback arguments as it's only argument.
*
* The contents and meaning of the array depends on the hook invoked. By
* modifying the contents of the array the hook can pass data back to the
* caller.
*
* Once the `Hook::register` function has been called, the callback may be
* invoked.
*
* @param string $hook The name of the hook to register a handler for.
* @param string $file The source file of the callback handler.
* @param string|array $function
* The function name of the callback handler, as a
* string or an array.
* @param int $version Hook interface version, allways 1.
* @param int $priority The priority of the callback handler, higher
* numbers takes precedence.
*
* @return true if the handler was already registered, otherwise the result
* from inserting the hook in the database.
*/
static public function register($hook,$file,$function,$version = 1,$priority = 0) {
if(is_array($function)) {
$function = serialize($function);
@@ -87,14 +45,6 @@ class Hook {
return $r;
}
/**
* Register an array of hook callback handlers.
*
* All of the handlers must be in the same source file.
*
* @param string $file The source file of the callback handlers.
* @param array $arr An array of `hookname => functionname` pairs.
*/
static public function register_array($file,$arr) {
if($arr) {
foreach($arr as $k => $v) {
@@ -104,20 +54,6 @@ class Hook {
}
/**
* Unregister a hook callback handler.
*
* @param string $hook The name of the hook to register a callback handler for.
* @param string $file The source file of the hook callback handler.
* @param string|array $function
* The function name of the callback handler, as a
* string or an array.
* @param int $version Hook interface version, allways 1.
* @param int $priority The priority of the callback handler, higher
* numbers takes precedence.
*
* @return The result of the database delete operation.
*/
static public function unregister($hook,$file,$function,$version = 1,$priority = 0) {
if(is_array($function)) {
$function = serialize($function);
@@ -134,13 +70,11 @@ class Hook {
}
/**
* Unregister all hooks handlers from a given source file.
* @brief Unregister all hooks with this file component.
*
* Useful for addon upgrades where you want to clean out old interfaces.
*
* @param string $file The source file where the hook handlers were defined.
*
* @return The result from the database delete operation.
* @param string $file
*/
static public function unregister_by_file($file) {
$r = q("DELETE FROM hook WHERE file = '%s' ",
@@ -151,22 +85,31 @@ class Hook {
}
/**
* Inserts a hook into a page request.
* @brief Inserts a hook into a page request.
*
* Insert a short-lived hook into the running page request. Hooks are
* normally persistent so that they can be called across asynchronous
* processes such as delivery and poll processes.
* Insert a short-lived hook into the running page request.
* Hooks are normally persistent so that they can be called
* across asynchronous processes such as delivery and poll
* processes.
*
* This function lets you attach a hook callback immediately which will not
* persist beyond the life of this page request or the current process.
* insert_hook lets you attach a hook callback immediately
* which will not persist beyond the life of this page request
* or the current process.
*
* @param string $hook Name of hook to attach callback.
* @param string|array $fn Name of callback handler as a string or array.
* @param int $version Hook interface version, allways 1.
* @param int $priority Currently not implemented in this function,
* would require the hook array to be resorted.
* @param string $hook
* name of hook to attach callback
* @param string $fn
* function name of callback handler
* @param int $version
* hook interface version, 0 uses two callback params, 1 uses one callback param
* @param int $priority
* currently not implemented in this function, would require the hook array to be resorted
*/
static public function insert($hook, $fn, $version = 0, $priority = 0) {
if(is_array($fn)) {
$fn = serialize($fn);
}
if(! is_array(App::$hooks))
App::$hooks = array();
@@ -176,4 +119,4 @@ class Hook {
App::$hooks[$hook][] = array('', $fn, $priority, $version);
}
}
}

View File

@@ -1,106 +1,23 @@
<?php
/*
* SPDX-FileCopyrightText: 2018 The Hubzilla Community
* SPDX-FileContributor: Zotlabs
* SPDX-FileContributor: Mario <mario@mariovavti.com>
* SPDX-FileContributor: Harald Eilertsen <haraldei@anduin.net>
*
* SPDX-License-Identifier: MIT
*/
namespace Zotlabs\Extend;
use Zotlabs\Lib\Config;
/**
* Class for managing routes.
*
* Routes connect a URL path to a module that will handle requests to that
* path.
*
* For example by registering a route like this:
*
* ```php
* Route::register(
* __DIR__ . '/Mod_Myroute.php',
* 'myroute'
* );
* ```
*
* Hubzilla will direct requests to the '/myroute' URL path to the 'Myroute'
* controller located in the '/Mod_Myroute.php' file in the same directory as
* the file this code was called from.
*
* Routes are stored persistently, so this function will typically be called from
* the `<addon>_load()` function if called from an addon. Accordingly, the route must
* be unregistered when no longer needed, like this:
*
* ```php
* Route::unregister(
* __DIR__ . '/Mod_Myroute.php',
* 'myroute'
* );
* ```
*
* This will typically be called from the `<addon>_unload()` function in an addon.
*/
class Route {
/**
* Register a new route.
*
* Example:
* ```php
* Route::register(
* __DIR__ . '/Mod_Myroute.php',
* 'myroute'
* );
* ```
*
* The route is stored persistently, and must be unregistered when no longer needed.
*
* @param string $file The file containing the controller for handling requests to this route.
* @param string $modname The name of the module (URL path).
*
* @see {@link Zotlabs::Extend::Route.unregister() unregister()}
* @see {@link Zotlabs::Extend::Route.unregister_by_file() unregister_by_file()}
*/
public static function register(string $file, string $modname): void {
static function register($file,$modname) {
$rt = self::get();
foreach ($rt as $r) {
if ($r[0] === $file && $r[1] === $modname) {
return;
}
}
$rt[] = [ $file, $modname ];
self::set($rt);
}
/**
* Unregister a route.
*
* Example:
* ```php
* Route::unregister(
* __DIR__ . '/Mod_Myroute.php',
* 'myroute'
* );
* ```
*
* @param string $file The file containing the controller for handling requests to this route.
* @param string $modname The name of the module (URL path).
*
* @see {@link Zotlabs::Extend::Route.register() register()}
* @see {@link Zotlabs::Extend::Route.unregister_by_file() unregister_by_file()}
*/
public static function unregister(string $file, string $modname): void {
static function unregister($file,$modname) {
$rt = self::get();
if($rt) {
$n = [];
foreach($rt as $r) {
if(!($r[0] === $file && $r[1] === $modname)) {
if($r[0] !== $file && $r[1] !== $modname) {
$n[] = $r;
}
}
@@ -108,23 +25,7 @@ class Route {
}
}
/**
* Unregister all routes by source file.
*
* Removes all persistently stored routes with hanclers in the
* given source file.
*
* Example:
* ```php
* Route::unregister_by_file(__DIR__ . '/Mod_Myroute.php');
* ```
*
* @param string $file The file containing the controllers to remove.
*
* @see {@link Zotlabs::Extend::Route.register() register()}
* @see {@link Zotlabs::Extend::Route.unregister() unregister()}
*/
public static function unregister_by_file(string $file): void {
static function unregister_by_file($file) {
$rt = self::get();
if($rt) {
$n = [];
@@ -137,18 +38,11 @@ class Route {
}
}
/**
* Get an array of all defined routes.
*
* @return An array of routes, where each entry is an array
* containing two elements, the file, and the module
* name.
*/
public static function get(): array {
static function get() {
return Config::Get('system','routes',[]);
}
private static function set(array $r): mixed {
static function set($r) {
return Config::Set('system','routes',$r);
}
}

View File

@@ -8,13 +8,6 @@ class Widget {
static function register($file,$widget) {
$rt = self::get();
foreach ($rt as $r) {
if ($r[0] === $file && $r[1] === $widget) {
return;
}
}
$rt[] = [ $file, $widget ];
self::set($rt);
}
@@ -24,7 +17,7 @@ class Widget {
if($rt) {
$n = [];
foreach($rt as $r) {
if(!($r[0] === $file && $r[1] === $widget)) {
if($r[0] !== $file && $r[1] !== $widget) {
$n[] = $r;
}
}

View File

@@ -7,63 +7,27 @@ namespace Zotlabs\Lib;
*/
class ASCache {
public static function isEnabled()
{
public static function isEnabled() {
return Config::Get('system', 'as_object_cache_enabled', true);
}
public static function getAge(): string
{
public static function getAge() {
return Config::Get('system', 'as_object_cache_time', '10 MINUTE');
}
public static function Get(string $key): array
{
if (!self::isEnabled()) {
return [];
}
$ret = Cache::get($key, self::getAge());
if ($ret) {
return json_unserialize($ret);
}
return [];
}
public static function Set(string $key, array $obj): void
{
public static function Get($key) {
if (!self::isEnabled()) {
return;
}
if (!self::isCacheable($obj)) {
return Cache::get($key, self::getAge());
}
public static function Set($key, $value) {
if (!self::isEnabled()) {
return;
}
Cache::set($key, json_serialize($obj));
}
public static function isCacheable(array $obj): bool
{
$to = [];
$cc = [];
if (isset($obj['to'])) {
$to = is_array($obj['to']) ? $obj['to'] : [$obj['to']];
}
if (isset($obj['cc'])) {
$cc = is_array($obj['cc']) ? $obj['cc'] : [$obj['cc']];
}
$receivers = array_merge($to, $cc);
if ($receivers && !in_array(ACTIVITY_PUBLIC_INBOX, $receivers)) {
return false;
}
return true;
Cache::set($key, $value);
}
}

View File

@@ -31,19 +31,7 @@ class ASCollection {
}
if (is_string($obj)) {
$cached = ASCache::Get($obj);
if ($cached) {
// logger('cached: ' . $obj);
$data = $cached;
}
else {
// logger('fetching: ' . $obj);
$data = Activity::fetch($obj, $channel);
if ($data) {
ASCache::Set($obj, $data);
}
}
$data = Activity::fetch($obj, $channel);
$this->history[] = $obj;
}
@@ -95,8 +83,6 @@ class ASCollection {
return false;
}
$data = null;
if (is_array($this->nextpage)) {
$data = $this->nextpage;
}
@@ -106,20 +92,7 @@ class ASCollection {
// recursion detected
return false;
}
$cached = ASCache::Get($this->nextpage);
if ($cached) {
// logger('cached: ' . $this->nextpage);
$data = $cached;
}
else {
$data = Activity::fetch($this->nextpage, $this->channel);
if ($data) {
// logger('fetching: ' . $this->nextpage);
ASCache::Set($this->nextpage, $data);
}
}
$data = Activity::fetch($this->nextpage, $this->channel);
$this->history[] = $this->nextpage;
}

View File

@@ -3,7 +3,6 @@
namespace Zotlabs\Lib;
use App;
use DBA;
use Zotlabs\Access\PermissionLimits;
use Zotlabs\Access\PermissionRoles;
use Zotlabs\Access\Permissions;
@@ -92,8 +91,6 @@ class Activity {
logger('fetch: ' . $url, LOGGER_DEBUG);
$start_timestamp = microtime(true);
if (strpos($url, 'x-zot:') === 0) {
$x = ZotURL::fetch($url, $channel);
}
@@ -132,6 +129,7 @@ class Activity {
}
$h = HTTPSig::create_sig($headers, $channel['channel_prvkey'], channel_url($channel), false);
$start_timestamp = microtime(true);
$x = z_fetch_url($url, true, $redirects, ['headers' => $h]);
}
@@ -384,12 +382,7 @@ class Activity {
if ($items) {
$x = [];
foreach ($items as $i) {
$m = ObjCache::Get($i['mid']);
if (!$m) {
$m = IConfig::Get($i['id'], 'activitypub', 'rawmsg');
}
$m = IConfig::Get($i['id'], 'activitypub', 'rawmsg');
if ($m) {
if (is_string($m))
$t = json_decode($m, true);
@@ -508,8 +501,8 @@ class Activity {
}
}
$ret['id'] = ((strpos($i['mid'], 'http') === 0) ? $i['mid'] : z_root() . '/item/' . urlencode($i['mid']));
$ret['uuid'] = $i['uuid'];
$ret['id'] = ((strpos($i['mid'], 'http') === 0) ? $i['mid'] : z_root() . '/item/' . urlencode($i['mid']));
$ret['diaspora:guid'] = $i['uuid'];
$images = [];
$audios = [];
@@ -569,7 +562,7 @@ class Activity {
$ret['published'] = datetime_convert('UTC', 'UTC', $i['created'], ATOM_TIME);
if ($i['created'] !== $i['edited'])
$ret['updated'] = datetime_convert('UTC', 'UTC', $i['edited'], ATOM_TIME);
if ($i['expires'] > DBA::$dba->get_null_date()) {
if ($i['expires'] > NULL_DATE) {
$ret['expires'] = datetime_convert('UTC', 'UTC', $i['expires'], ATOM_TIME);
}
@@ -594,7 +587,7 @@ class Activity {
$ret['commentPolicy'] = (($i['item_wall']) ? map_scope(PermissionLimits::Get($i['uid'], 'post_comments')) : '');
if (array_key_exists('comments_closed', $i) && $i['comments_closed'] !== EMPTY_STR && $i['comments_closed'] > DBA::$dba->get_null_date()) {
if (array_key_exists('comments_closed', $i) && $i['comments_closed'] !== EMPTY_STR && $i['comments_closed'] > NULL_DATE) {
if ($ret['commentPolicy']) {
$ret['commentPolicy'] .= ' ';
}
@@ -627,6 +620,15 @@ class Activity {
$ret['context'] = $cnv;
}
if ($i['mimetype'] === 'text/bbcode') {
if ($i['title'])
$ret['name'] = unescape_tags($i['title']);
if ($i['summary'])
$ret['summary'] = unescape_tags($i['summary']);
$ret['content'] = bbcode(unescape_tags($i['body']), ['cache' => true]);
$ret['source'] = ['content' => unescape_tags($i['body']), 'mediaType' => 'text/bbcode'];
}
$actor = self::encode_person($i['author'], false);
if ($actor)
$ret['actor'] = $actor;
@@ -644,57 +646,6 @@ class Activity {
$ret['tag'] = $t;
}
// TODO: Do not replace the if the owner is a forum.
// Receivers will not be able to fetch the original in that case.
if (str_contains($i['body'], '[/share]')) {
preg_match_all('/\[share(.*?)\](.*?)\[\/share\]/ism', $i['body'], $all_shares, PREG_SET_ORDER);
$quote_urls = [];
$obj_links = [];
foreach ($all_shares as $share) {
// Extract the link attribute from each [share] block if slated for quote
if (str_contains($share[1], "quote='true'") && preg_match("/link='(.*?)'/ism", $share[1], $match)) {
$url = $match[1];
$quote_urls[] = $url;
$quote_name = 'RE: ' . $url;
// Replace this share block with a formatted URL reference
$i['body'] = str_replace($share[0], $quote_name, $i['body']);
$obj_links[] = [
'type' => 'Link',
'mediaType' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
'href' => $url,
'name' => $quote_name
];
}
}
if ($quote_urls) {
$ret['quoteUrl'] = $quote_urls[0];
$ret['quoteUri'] = $quote_urls[0];
if (empty($ret['tag'])) {
$ret['tag'] = $obj_links;
}
else {
$ret['tag'] = array_merge($ret['tag'], $obj_links);
}
}
}
if ($i['mimetype'] === 'text/bbcode') {
if ($i['title'])
$ret['name'] = unescape_tags($i['title']);
if ($i['summary'])
$ret['summary'] = unescape_tags($i['summary']);
$ret['content'] = bbcode(unescape_tags($i['body']));
$ret['source'] = ['content' => unescape_tags($i['body']), 'mediaType' => 'text/bbcode'];
}
$a = self::encode_attachment($i);
if ($a) {
$ret['attachment'] = $a;
@@ -838,7 +789,8 @@ class Activity {
if ($iconfig && array_key_exists('iconfig', $item) && is_array($item['iconfig'])) {
foreach ($item['iconfig'] as $att) {
if ($att['sharing']) {
$ret[] = ['type' => 'PropertyValue', 'name' => 'zot.' . $att['cat'] . '.' . $att['k'], 'value' => $att['v']];
$value = ((is_string($att['v']) && preg_match('|^a:[0-9]+:{.*}$|s', $att['v'])) ? unserialize($att['v']) : $att['v']);
$ret[] = ['type' => 'PropertyValue', 'name' => 'zot.' . $att['cat'] . '.' . $att['k'], 'value' => $value];
}
}
}
@@ -859,10 +811,6 @@ class Activity {
$entry = [];
if (isset($att['type']) && $att['type'] === 'PropertyValue') {
if (isset($att['name'])) {
if (in_array($att['name'], ['zot.activitypub.rawmsg', 'zot.diaspora.fields'])) {
continue;
}
$key = explode('.', $att['name']);
if (count($key) === 3 && $key[0] === 'zot') {
$entry['cat'] = $key[1];
@@ -1009,7 +957,7 @@ class Activity {
$ret['id'] = ((strpos($i['mid'], 'http') === 0) ? $i['mid'] : z_root() . '/activity/' . urlencode($i['mid']));
}
$ret['uuid'] = $i['uuid'];
$ret['diaspora:guid'] = $i['uuid'];
if (!empty($i['title']))
$ret['name'] = html2plain(bbcode($i['title']));
@@ -1126,7 +1074,6 @@ class Activity {
return [];
}
/* Those should not be required in activities anymore after version 11
$t = self::encode_taxonomy($i);
if ($t) {
$ret['tag'] = $t;
@@ -1136,7 +1083,6 @@ class Activity {
if ($a) {
$ret['attachment'] = $a;
}
*/
if (intval($i['item_private']) === 0) {
$ret['to'] = [ACTIVITY_PUBLIC_INBOX];
@@ -1565,7 +1511,7 @@ class Activity {
'abook_created' => datetime_convert(),
'abook_updated' => datetime_convert(),
'abook_connected' => datetime_convert(),
'abook_dob' => DBA::$dba->get_null_date(),
'abook_dob' => NULL_DATE,
'abook_pending' => intval(($automatic) ? 0 : 1),
'abook_instance' => z_root()
]
@@ -1964,7 +1910,7 @@ class Activity {
);
if ($x) {
return sprintf('[zrl=%s]@%s[/zrl]', $x[0]['xchan_url'], $x[0]['xchan_name']);
return sprintf('@[zrl=%s]%s[/zrl]', $x[0]['xchan_url'], $x[0]['xchan_name']);
}
return '@{' . $id . '}';
@@ -2017,8 +1963,6 @@ class Activity {
$multi = true;
}
$answer_found = false;
if ($response) {
$mid = $response['mid'];
$content = trim($response['title']);
@@ -2048,6 +1992,7 @@ class Activity {
}
}
$answer_found = false;
$foundPrevious = false;
if ($multi) {
for ($c = 0; $c < count($o['anyOf']); $c++) {
@@ -2089,7 +2034,7 @@ class Activity {
}
}
}
if ($pollItem['comments_closed'] > DBA::$dba->get_null_date()) {
if ($pollItem['comments_closed'] > NULL_DATE) {
if ($pollItem['comments_closed'] > datetime_convert()) {
$o['closed'] = datetime_convert('UTC', 'UTC', $pollItem['comments_closed'], ATOM_TIME);
// set this to force an update
@@ -2141,16 +2086,15 @@ class Activity {
}
static function decode_note($act) {
$response_activity = false;
$s = [];
$obj_type = is_array($act->objprop('type')) ? $act->objprop('type')[0] : $act->objprop('type');
// These activities should have been handled separately in the Inbox module and should not be turned into posts
if (
in_array($act->type, ['Follow', 'Accept', 'Reject', 'Create', 'Update']) &&
($obj_type === 'Follow' || ActivityStreams::is_an_actor($obj_type))
($act->objprop('type') === 'Follow' || ActivityStreams::is_an_actor($act->objprop('type')))
) {
return false;
}
@@ -2223,7 +2167,7 @@ class Activity {
}
}
if (in_array($act->type, ['Invite', 'Create']) && $obj_type === 'Event') {
if (in_array($act->type, ['Invite', 'Create']) && $act->objprop('type') === 'Event') {
$s['mid'] = $s['parent_mid'] = $act->id;
}
@@ -2255,25 +2199,25 @@ class Activity {
$mention = self::get_actor_bbmention($obj_actor['id']);
if ($act->type === 'Like') {
$content['content'] = sprintf(t('Likes %1$s\'s %2$s'), $mention, $act->obj['type']);
$content['content'] = sprintf(t('Likes %1$s\'s %2$s'), $mention, $act->obj['type']) . EOL . EOL . ($content['content'] ?? '');
}
if ($act->type === 'Dislike') {
$content['content'] = sprintf(t('Doesn\'t like %1$s\'s %2$s'), $mention, $act->obj['type']);
$content['content'] = sprintf(t('Doesn\'t like %1$s\'s %2$s'), $mention, $act->obj['type']) . EOL . EOL . ($content['content'] ?? '');
}
// handle event RSVPs
if (in_array($obj_type, ['Event', 'Invite'])) {
if (($act->objprop('type') === 'Event') || ($act->objprop('type') === 'Invite' && array_path_exists('object/type', $act->obj) && $act->obj['object']['type'] === 'Event')) {
if ($act->type === 'Accept') {
$content['content'] = sprintf(t('Will attend %s\'s event'), $mention);
$content['content'] = sprintf(t('Will attend %s\'s event'), $mention) . EOL . EOL . ($content['content'] ?? '');
}
if ($act->type === 'Reject') {
$content['content'] = sprintf(t('Will not attend %s\'s event'), $mention);
$content['content'] = sprintf(t('Will not attend %s\'s event'), $mention) . EOL . EOL . ($content['content'] ?? '');
}
if ($act->type === 'TentativeAccept') {
$content['content'] = sprintf(t('May attend %s\'s event'), $mention);
$content['content'] = sprintf(t('May attend %s\'s event'), $mention) . EOL . EOL . ($content['content'] ?? '');
}
if ($act->type === 'TentativeReject') {
$content['content'] = sprintf(t('May not attend %s\'s event'), $mention);
$content['content'] = sprintf(t('May not attend %s\'s event'), $mention) . EOL . EOL . ($content['content'] ?? '');
}
}
@@ -2307,7 +2251,7 @@ class Activity {
if ($s['mid'] === $s['parent_mid']) {
$s['item_thread_top'] = 1;
$s['item_nocomment'] = 0;
$s['comments_closed'] = DBA::$dba->get_null_date();
$s['comments_closed'] = NULL_DATE;
// it is a parent node - decode the comment policy info if present
if ($act->objprop('commentPolicy')) {
@@ -2341,49 +2285,20 @@ class Activity {
$s['body'] = markdown_to_bb($act->objprop('content'));
}
$quote_urls = [];
if ($act->objprop('quoteUrl')) {
$quote_bbcode = self::get_quote_bbcode($act->obj['quoteUrl']);
if (isset($act->obj['tag'])) {
foreach($act->obj['tag'] as $t) {
if (is_array($t) && $t['type'] === 'Link' && $t['mediaType'] === 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"') {
$quote_urls[] = $t['href'];
}
if ($s['body']) {
$s['body'] .= "\r\n\r\n";
}
}
if (!$quote_urls) {
$quote_url = $act->obj['quoteUrl'] ?? $act->obj['quoteUri'] ?? $act->obj['_misskey_quote'] ?? $act->obj['quote'] ?? null;
if ($quote_url) {
$quote_urls = [$quote_url];
}
}
// Backwards compatibility: only process quote items if there is no share tag in them.
// Otherwise they will appear doubled.
if ($quote_urls && !str_contains($s['body'], '[/share]')) {
foreach($quote_urls as $quote_url) {
if (!is_string($quote_url)) {
// FIXME: requires investigation
logger('Not a string: ' . print_r($quote_url,true));
continue;
}
$quote = self::get_quote($quote_url);
if (!$quote) {
continue;
}
$s['body'] = self::pasteQuote($s['body'] ?? EMPTY_STR, $quote);
$s['term'] = $quote['term'];
}
$s['body'] .= $quote_bbcode;
}
$s['verb'] = self::activity_mapper($act->type);
// Mastodon does not provide update timestamps when updating poll tallies which means race conditions may occur here.
if ($act->type === 'Update' && $obj_type === 'Question' && $s['edited'] === $s['created']) {
if ($act->type === 'Update' && $act->objprop('type') === 'Question' && $s['edited'] === $s['created']) {
$s['edited'] = datetime_convert();
}
@@ -2391,8 +2306,8 @@ class Activity {
$s['item_deleted'] = 1;
}
if ($obj_type) {
$s['obj_type'] = self::activity_obj_mapper($obj_type);
if ($act->objprop('type')) {
$s['obj_type'] = self::activity_obj_mapper($act->obj['type']);
}
$s['obj'] = $act->obj;
@@ -2425,13 +2340,7 @@ class Activity {
$a = self::decode_taxonomy($act->obj);
if ($a) {
if (isset($s['term'])) {
// term might contain content from a quote post
$s['term'] = array_merge($s['term'], $a);
}
else {
$s['term'] = $a;
}
$s['term'] = $a;
}
$a = self::decode_attachment($act->obj);
@@ -2453,7 +2362,7 @@ class Activity {
$s = self::bb_attach($s);
}
if ($obj_type === 'Question' && in_array($act->type, ['Create', 'Update'])) {
if ($act->objprop('type') === 'Question' && in_array($act->type, ['Create', 'Update'])) {
if ($act->objprop('endTime')) {
$s['comments_closed'] = datetime_convert('UTC', 'UTC', $act->obj['endTime']);
}
@@ -2465,7 +2374,7 @@ class Activity {
if (!$response_activity) {
if ($obj_type === 'Profile') {
if ($act->objprop('type') === 'Profile') {
$s['parent_mid'] = $s['mid'];
$s['item_thread_top'] = 1;
}
@@ -2475,7 +2384,7 @@ class Activity {
// right now just link to the largest mp4 we find that will fit in our
// standard content region
if ($obj_type === 'Video') {
if ($act->objprop('type') === 'Video') {
$vtypes = [
'video/mp4',
@@ -2557,7 +2466,7 @@ class Activity {
}
}
if ($obj_type === 'Audio') {
if ($act->objprop('type') === 'Audio') {
$atypes = [
'audio/mpeg',
@@ -2589,7 +2498,7 @@ class Activity {
}
if ($obj_type === 'Image' && strpos($s['body'], 'zrl=') === false) {
if ($act->objprop('type') === 'Image' && strpos($s['body'], 'zrl=') === false) {
$ptr = null;
@@ -2604,10 +2513,10 @@ class Activity {
foreach ($ptr as $vurl) {
if (strpos($s['body'], $vurl['href']) === false) {
$bb_imgs = '[zmg]' . $vurl['href'] . '[/zmg]' . "\r\n";
$s['body'] = $bb_imgs . $s['body'];
break;
}
}
$s['body'] = $bb_imgs . $s['body'];
}
elseif (is_string($act->obj['url'])) {
if (strpos($s['body'], $act->obj['url']) === false) {
@@ -2617,7 +2526,7 @@ class Activity {
}
}
if ($obj_type === 'Page' && !$s['body']) {
if ($act->objprop('type') === 'Page' && !$s['body']) {
$ptr = null;
$purl = EMPTY_STR;
@@ -2657,7 +2566,7 @@ class Activity {
}
}
if (in_array($obj_type, ['Note', 'Article', 'Page', 'Question'])) {
if (in_array($act->objprop('type'), ['Note', 'Article', 'Page'])) {
$ptr = null;
if (array_key_exists('url', $act->obj)) {
@@ -2696,11 +2605,55 @@ class Activity {
$s['item_private'] = 2;
}
$ap_rawmsg = '';
$diaspora_rawmsg = '';
$raw_arr = [];
$raw_arr = json_decode($act->raw, true);
// This is a zot6 packet and the raw activitypub or diaspora message json
// is possibly available in the attachement.
if (array_key_exists('signed', $raw_arr) && isset($act->data['attachment']) && is_array($act->data['attachment'])) {
foreach($act->data['attachment'] as $a) {
if (
isset($a['type']) && $a['type'] === 'PropertyValue' &&
isset($a['name']) && $a['name'] === 'zot.activitypub.rawmsg' &&
isset($a['value'])
) {
$ap_rawmsg = $a['value'];
}
if (
isset($a['type']) && $a['type'] === 'PropertyValue' &&
isset($a['name']) && $a['name'] === 'zot.diaspora.fields' &&
isset($a['value'])
) {
$diaspora_rawmsg = $a['value'];
}
}
}
if (!$ap_rawmsg && array_key_exists('signed', $raw_arr)) {
// zap
$ap_rawmsg = json_encode($act->data, JSON_UNESCAPED_SLASHES);
}
if ($ap_rawmsg) {
IConfig::Set($s, 'activitypub', 'rawmsg', $ap_rawmsg, 1);
}
elseif (!array_key_exists('signed', $raw_arr)) {
IConfig::Set($s, 'activitypub', 'rawmsg', $act->raw, 1);
}
if ($diaspora_rawmsg) {
IConfig::Set($s, 'diaspora', 'fields', $diaspora_rawmsg, 1);
}
if ($act->raw_recips) {
IConfig::Set($s, 'activitypub', 'recips', $act->raw_recips);
}
if ($obj_type === 'Event' && $act->objprop('timezone')) {
if ($act->objprop('type') === 'Event' && $act->objprop('timezone')) {
IConfig::Set($s, 'event', 'timezone', $act->objprop('timezone'), true);
}
@@ -2713,9 +2666,8 @@ class Activity {
return $hookinfo['s'];
}
static function store($channel, $observer_hash, $act, $item, $fetch_parents = true, $force = false, $is_collection_operation = false) {
$is_sys_channel = $channel['channel_system'];
$is_sys_channel = is_sys_channel($channel['channel_id']);
$is_child_node = false;
$parent = null;
@@ -2762,9 +2714,7 @@ class Activity {
$force = true;
}
$attempt_parents_fetch = $fetch_parents && !in_array($channel['channel_id'], App::$cache['as_fetch_objects'][$item['mid']]['channels'] ?? []);
if ($attempt_parents_fetch) {
if ($fetch_parents) {
App::$cache['as_fetch_objects'][$item['mid']]['channels'][] = $channel['channel_id'];
App::$cache['as_fetch_objects'][$item['mid']]['force'] = intval($force);
return;
@@ -2866,11 +2816,15 @@ class Activity {
}
if (tgroup_check($channel['channel_id'], $item) && (!$is_child_node)) {
// for forum deliveries, make sure we keep a copy of the signed original
IConfig::Set($item, 'activitypub', 'rawmsg', $act->raw, 1);
$allowed = true;
}
if (intval($item['item_private']) === 2) {
$allowed = perm_is_allowed($channel['channel_id'], $observer_hash, 'post_mail');
if (perm_is_allowed($channel['channel_id'], $observer_hash, 'post_mail')) {
$allowed = true;
}
}
if ($is_sys_channel) {
@@ -2936,19 +2890,13 @@ class Activity {
if (!$item['author_xchan'] || !$item['owner_xchan'])
return;
if ($is_sys_channel) {
$incl = Config::Get('system', 'pubstream_incl', '');
$excl = Config::Get('system', 'pubstream_excl', '');
if ($channel['channel_system']) {
$incl = Config::Get('system','pubstream_incl');
$excl = Config::Get('system','pubstream_excl');
if ($incl || $excl) {
$plaintext = prepare_text($item['body'], ((isset($item['mimetype'])) ? $item['mimetype'] : 'text/bbcode'));
$plaintext = html2plain((isset($item['summary']) && $item['summary']) ? $item['summary'] . ' ' . $plaintext : $plaintext);
$plaintext = html2plain((isset($item['title']) && $item['title']) ? $item['title'] . ' ' . $plaintext : $plaintext);
if (!(new MessageFilter($item, html_entity_decode($incl), html_entity_decode($excl), ['plaintext' => $plaintext]))->evaluate()) {
logger('post is filtered');
return;
}
if(($incl || $excl) && !MessageFilter::evaluate($item, $incl, $excl)) {
logger('post is filtered');
return;
}
}
@@ -3047,22 +2995,13 @@ class Activity {
// TODO: not implemented
// self::rewrite_mentions($item);
if (!ObjCache::Get($item['mid'])) {
ObjCache::Set($item['mid'], $act->data);
}
$r = q("select id, created, edited, owner_xchan, author_xchan from item where mid = '%s' and uid = %d limit 1",
$r = q("select id, created, edited from item where mid = '%s' and uid = %d limit 1",
dbesc($item['mid']),
intval($item['uid'])
);
if ($r) {
if ($item['edited'] > $r[0]['edited']) {
// Only update the object cache if there is no owner/author mismatch.
if ($r[0]['owner_xchan'] === $item['owner_xchan'] && $r[0]['author_xchan'] === $item['author_xchan']) {
ObjCache::Set($item['mid'], $act->data);
}
$item['id'] = $r[0]['id'];
$x = item_store_update($item, deliver: false);
}
@@ -3096,21 +3035,30 @@ class Activity {
send_status_notifications($x['item_id'], $x['item']);
sync_an_item($channel['channel_id'], $x['item_id']);
}
$replies_id = null;
if (isset($act->obj['replies'])) {
$replies_id = is_array($act->obj['replies']) ? $act->obj['replies']['id'] : $act->obj['replies'];
}
// Only store replies collection for background fetching if the item has been fetched.
// A message that has just been posted usually will not have any replies yet.
// Also dismiss duplicates.
$attempt_replies_fetch = $replies_id && !empty($item['item_fetched']) && !in_array($channel['channel_id'], App::$cache['as_fetch_collection'][$replies_id]['channels'] ?? []);
if ($attempt_replies_fetch) {
App::$cache['as_fetch_collection'][$replies_id]['channels'][] = $channel['channel_id'];
App::$cache['as_fetch_collection'][$replies_id]['force'] = intval($force);
if ($fetch_parents && $parent && !intval($parent[0]['item_private'])) {
logger('topfetch', LOGGER_DEBUG);
// if the thread owner is a connnection, we will already receive any additional comments to their posts
// but if they are not we can try to fetch others in the background
$connected = q("SELECT abook.*, xchan.* FROM abook left join xchan on abook_xchan = xchan_hash
WHERE abook_channel = %d and abook_xchan = '%s' LIMIT 1",
intval($channel['channel_id']),
dbesc($parent[0]['owner_xchan'])
);
if (!$connected) {
// determine if the top-level post provides a replies collection
if ($parent[0]['obj']) {
$parent[0]['obj'] = json_decode($parent[0]['obj'], true);
}
logger('topfetch: ' . print_r($parent[0], true), LOGGER_ALL);
$id = ((array_path_exists('obj/replies/id', $parent[0])) ? $parent[0]['obj']['replies']['id'] : false);
if (!$id) {
$id = ((array_path_exists('obj/replies', $parent[0]) && is_string($parent[0]['obj']['replies'])) ? $parent[0]['obj']['replies'] : false);
}
if ($id) {
Master::Summon(['Convo', $id, $channel['channel_id'], $observer_hash]);
}
}
}
}
@@ -3159,7 +3107,7 @@ class Activity {
$cached = ASCache::Get($current_item['parent_mid']);
if ($cached) {
// logger('cached: ' . $current_item['parent_mid']);
$n = $cached;
$n = unserialise($cached);
}
else {
// logger('fetching: ' . $current_item['parent_mid']);
@@ -3167,7 +3115,7 @@ class Activity {
if (!$n) {
break;
}
ASCache::Set($current_item['parent_mid'], $n);
ASCache::Set($current_item['parent_mid'], serialise($n));
}
$a = new ActivityStreams($n);
@@ -3330,12 +3278,6 @@ class Activity {
return true;
}
// FIXME: it appears sometimes $s is an array (needs invetigation)
if (!is_string($s)) {
btlogger('Not a string: ' . print_r($s, true));
return true;
}
$s_alt = htmlspecialchars($s, ENT_QUOTES, 'UTF-8');
if (
@@ -3631,46 +3573,27 @@ class Activity {
return $ret;
}
static function get_quote($url) {
$ret = [];
$a = null;
static function get_quote_bbcode($url) {
$cached = ASCache::Get($url);
if ($cached) {
// logger('cached: ' . $url);
$a = $cached;
}
else {
// logger('fetching: ' . $url);
$a = self::fetch($url);
if ($a) {
ASCache::Set($url, $a);
}
}
$ret = '';
$a = self::fetch($url);
if ($a) {
$act = new ActivityStreams($a);
if ($act->is_valid()) {
$decoded = self::decode_note($act);
$content = self::get_content($act->obj);
$bbcode = "[share author='" . urlencode($act->actor['name'] ?? $act->actor['preferredUsername']) .
$ret .= "[share author='" . urlencode($act->actor['name'] ?? $act->actor['preferredUsername']) .
"' profile='" . $act->actor['id'] .
"' avatar='" . ($act->actor['icon']['url'] ?? z_root() . '/' . get_default_profile_photo(80)) .
"' link='" . $url .
"' link='" . $act->obj['id'] .
"' auth='" . ((is_matrix_url($act->actor['id'])) ? 'true' : 'false') .
"' posted='" . $act->obj['published'] .
"' message_id='" . $act->obj['id'] .
"']";
$bbcode .= $decoded['body'];
$bbcode .= '[/share]';
$ret['bbcode'] = $bbcode;
$ret['url'] = $decoded['plink'];
$ret['mid'] = $decoded['mid'];
$ret['term'] = $decoded['term'] ?? [];
$ret .= self::bb_content($content, 'content');
$ret .= '[/share]';
}
}
@@ -3747,11 +3670,10 @@ class Activity {
'conversation' => 'ostatus:conversation',
'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers',
'Hashtag' => 'as:Hashtag',
'guid' => 'diaspora:guid',
'quoteUrl' => 'as:quoteUrl',
'quoteUri' => 'http://fedibird.com/ns#quoteUri'
'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers',
'Hashtag' => 'as:Hashtag'
];
}
@@ -3790,13 +3712,11 @@ class Activity {
*/
public static function init_background_fetch(string $observer_hash = '') {
$interval = Config::Get('queueworker', 'queue_interval', 500000);
if (isset(App::$cache['zot_fetch_objects'])) {
$channels_str = '';
foreach (App::$cache['zot_fetch_objects'] as $mid => $info) {
$force = $info['force'];
$channels_str = '';
foreach ($info['channels'] as $c) {
if ($channels_str) {
@@ -3806,22 +3726,19 @@ class Activity {
}
Master::Summon(['Zotconvo', $channels_str, $mid, $force]);
if ($interval) {
usleep($interval);
}
}
}
if (!$observer_hash) {
logger('Attempt to initiate Fetchparents or Convo daemon without observer');
return;
}
if (isset(App::$cache['as_fetch_objects'])) {
if (!$observer_hash) {
logger('Attempt to initiate Fetchparents daemon without observer');
return;
}
$channels_str = '';
foreach (App::$cache['as_fetch_objects'] as $mid => $info) {
$force = $info['force'];
$channels_str = '';
foreach ($info['channels'] as $c) {
if ($channels_str) {
@@ -3831,33 +3748,8 @@ class Activity {
}
Master::Summon(['Fetchparents', $channels_str, $observer_hash, $mid, $force]);
if ($interval) {
usleep($interval);
}
}
}
if (isset(App::$cache['as_fetch_collection'])) {
foreach (App::$cache['as_fetch_collection'] as $mid => $info) {
$force = $info['force'];
$channels_str = '';
foreach ($info['channels'] as $c) {
if ($channels_str) {
$channels_str .= ',';
}
$channels_str .= $c;
}
Master::Summon(['Convo', $channels_str, $observer_hash, $mid, $force]);
if ($interval) {
usleep($interval);
}
}
}
}
public static function addToCollection($channel, $object, $target, $sourceItem = null, $deliver = true) {
@@ -3967,34 +3859,4 @@ class Activity {
?? '';
}
public static function pasteQuote(string $body, array $quote): string
{
// Escape URLs for regex safety
$urls = array_map('preg_quote', [$quote['url'], $quote['mid']], array_fill(0, 2, '/'));
$patterns = [];
foreach ($urls as $url) {
// Match both plain and BBCode-style references, with optional line breaks or spaces
$patterns[] = '/RE:\s*(?:\[url=' . $url . '\]' . $url . '\[\/url\]|' . $url . ')[\s\r\n]?/i';
}
$found = false;
foreach ($patterns as $pattern) {
if (preg_match($pattern, $body)) {
$found = true;
$body = preg_replace($pattern, $quote['bbcode'], $body);
break;
}
}
if (!$found) {
if (!empty($body)) {
$body .= "\r\n\r\n";
}
$body .= $quote['bbcode'];
}
return $body;
}
}

View File

@@ -97,7 +97,7 @@ class ActivityStreams {
}
// cache for future use
ASCache::Set($this->id, $this->data);
ASCache::Set($this->id, 'json:' . $this->raw);
$this->type = $this->get_primary_type();
$this->actor = $this->get_actor('actor', '', '');
@@ -413,13 +413,13 @@ class ActivityStreams {
$cached = ASCache::Get($x);
if ($cached) {
// logger('AS cached: ' . $x);
$y = $cached;
$y = unserialise($cached);
}
else {
// logger('AS fetching: ' . $x);
$y = $this->fetch_property($x);
if ($y) {
ASCache::Set($x, $y);
ASCache::Set($x, serialise($y));
}
}
if (is_array($y)) {
@@ -553,7 +553,6 @@ class ActivityStreams {
}
$url = unparse_url($parseUrl);
$this->signer = ['id' => $url];
$hublocs = Activity::get_actor_hublocs($url);

View File

@@ -1028,7 +1028,12 @@ class Apps {
if(! $syslist)
return;
$position = array_find_key($syslist, fn ($v) => $v['guid'] === $guid);
foreach($syslist as $k => $li) {
if($li['guid'] === $guid) {
$position = $k;
break;
}
}
if(! $position)
return;
@@ -1077,7 +1082,12 @@ class Apps {
if(! $syslist)
return;
$position = array_find_key($syslist, fn ($v) => $v['guid'] === $guid);
foreach($syslist as $k => $li) {
if($li['guid'] === $guid) {
$position = $k;
break;
}
}
if($position >= count($syslist) - 1)
return;

View File

@@ -132,8 +132,8 @@ class Config {
$value = App::$config[$family][$key];
if (! is_array($value)) {
if (str_starts_with($value, 'json:')) {
return json_unserialize($value);
if (substr($value, 0, 5) == 'json:') {
return json_decode(substr($value, 5), true);
} else if (preg_match('|^a:[0-9]+:{.*}$|s', $value)) {
// Unserialize in inherently unsafe. Try to mitigate by not
// allowing unserializing objects. Only kept for backwards

View File

@@ -1,43 +0,0 @@
<?php
/*
* SPDX-FileCopyrightText: 2026 The Hubzilla Community
* SPDX-FileContributor: Harald Eilertsen <haraldei@anduin.net>
*
* SPDX-License-Identifier: MIT
*/
namespace Zotlabs\Lib;
use DBA;
/**
* Abstract class to obtain statistics from the database.
*
* This class should not be instantiated on it's own, but you can get
* a concrete class for the configured database type of this site by
* calling the `DbStats::getStats()` function.
*/
abstract class DbStats {
/**
* Get an object for getting statistics from the database.
*
* @return DbStats The concrete class for obtaining the statistics from
* this instances database.
*/
public static function getStats(): DbStats {
return DBA::$dba->is_postgres()
? new PostgresDbStats()
: new MySQLDbStats();
}
/**
* Return the number of queries recorded by the database.
*
* @return int Number of queries.
*/
public abstract function getQueries(): int;
// Prevent instantiation of this class
private function __construct() {}
}

View File

@@ -431,7 +431,7 @@ class Enotify {
elseif (isset($params['type']) && $params['type'] === NOTIFY_INTRO) {
$subject = sprintf( t('[$Projectname:Notify] Introduction received'));
$preamble = sprintf( t('You\'ve received a new connection request from \'%1$s\' at %2$s'), $sender['xchan_name'], $sitename);
$preamble = sprintf( t('You\'ve received an new connection request from \'%1$s\' at %2$s'), $sender['xchan_name'], $sitename);
$epreamble = sprintf( t('You\'ve received [zrl=%1$s]a new connection request[/zrl] from %2$s.'),
$siteurl . '/connections/ifpending',
'[zrl=' . $sender['xchan_url'] . ']' . $sender['xchan_name'] . '[/zrl]');
@@ -511,7 +511,7 @@ class Enotify {
*/
$hash = ((in_array($params['verb'], ['Create', 'Update', 'Invite'])) ? $params['item']['uuid'] : $params['item']['thr_parent_uuid']);
$hash = ((in_array($params['verb'], ['Create', 'Update'])) ? $params['item']['uuid'] : $params['item']['thr_parent_uuid']);
if (!$hash) {
$hash = new_uuid();

View File

@@ -34,20 +34,8 @@ class IConfig {
if(is_array($item) && array_key_exists('iconfig',$item) && is_array($item['iconfig'])) {
foreach($item['iconfig'] as $c) {
if (isset($c['iid']) && $c['iid'] == $iid && isset($c['cat']) && $c['cat'] == $family && isset($c['k']) && $c['k'] == $key) {
if (is_string($c['v'])) {
if (str_starts_with($c['v'], 'json:')) {
$c['v'] = json_unserialize($c['v']);
} else if (preg_match('|^a:[0-9]+:{.*}$|s', $c['v'])) {
// Unserialize in inherently unsafe. Try to mitigate by not
// allowing unserializing objects. Only kept for backwards
// compatibility. JSON serialization should be prefered.
$c['v'] = unserialize($c['v'], ['allowed_classes' => false]);
}
}
if (isset($c['iid']) && $c['iid'] == $iid && isset($c['cat']) && $c['cat'] == $family && isset($c['k']) && $c['k'] == $key)
return $c['v'];
}
}
}
@@ -56,24 +44,12 @@ class IConfig {
dbesc($family),
dbesc($key)
);
if($r) {
if (str_starts_with($r[0]['v'], 'json:')) {
$r[0]['v'] = json_unserialize($r[0]['v']);
} else if (preg_match('|^a:[0-9]+:{.*}$|s', $r[0]['v'])) {
// Unserialize in inherently unsafe. Try to mitigate by not
// allowing unserializing objects. Only kept for backwards
// compatibility. JSON serialization should be prefered.
$r[0]['v'] = unserialize($r[0]['v'], ['allowed_classes' => false]);
}
if ($is_item) {
$r[0]['v'] = ((preg_match('|^a:[0-9]+:{.*}$|s',$r[0]['v'])) ? unserialize($r[0]['v']) : $r[0]['v']);
if($is_item)
$item['iconfig'][] = $r[0];
}
return $r[0]['v'];
}
return $default;
}
@@ -97,7 +73,7 @@ class IConfig {
static public function Set(&$item, $family, $key, $value, $sharing = false) {
$dbvalue = ((is_array($value)) ? json_serialize($value) : $value);
$dbvalue = ((is_array($value)) ? serialize($value) : $value);
$dbvalue = ((is_bool($dbvalue)) ? intval($dbvalue) : $dbvalue);
$is_item = false;
@@ -123,11 +99,11 @@ class IConfig {
return $value;
}
if(intval($item)) {
if(intval($item))
$iid = intval($item);
} else {
if(! $iid)
return false;
}
if(self::Get($item, $family, $key) === false) {
$r = q("insert into iconfig( iid, cat, k, v, sharing ) values ( %d, '%s', '%s', '%s', %d ) ",
@@ -174,11 +150,11 @@ class IConfig {
return true;
}
if(intval($item)) {
if(intval($item))
$iid = intval($item);
} else {
if(! $iid)
return false;
}
return q("delete from iconfig where iid = %d and cat = '%s' and k = '%s' ",
intval($iid),

View File

@@ -25,18 +25,18 @@ class Img_filesize {
static function getLocalFileSize($url) {
$fname = basename($url);
$resolution = 0;
if(strpos($fname,'.') !== false)
$fname = substr($fname,0,strpos($fname,'.'));
if(substr($fname,-2,1) == '-') {
$resolution = intval(substr($fname,-1,1));
$fname = substr($fname,0,-2);
}
$r = q("SELECT filesize FROM photo WHERE resource_id = '%s' AND imgscale = %d LIMIT 1",
dbesc($fname),
intval($resolution)
@@ -116,6 +116,7 @@ function getRemoteFileSize($url)
curl_exec($ch);
curl_getinfo($ch);
curl_close($ch);
return $size;
}
}

View File

@@ -2,7 +2,7 @@
namespace Zotlabs\Lib;
use Root23\JsonCanonicalizer\JsonCanonicalizer;
use Mmccook\JsonCanonicalizator\JsonCanonicalizatorFactory;
use StephenHill\Base58;
class JcsEddsa2022 {
@@ -62,16 +62,14 @@ class JcsEddsa2022 {
try {
$result = sodium_crypto_sign_verify_detached($base58->decode($encodedSignature), $optionsHash . $dataHash,
(new Multibase())->decode($publicKey, true));
logger('SignatureVerify (eddsa-jcs-2022) ' . (($result) ? 'true' : 'false'));
return $result;
}
catch (\Exception $e) {
logger('verify exception:' . $e->getMessage());
}
return false;
logger('SignatureVerify (eddsa-jcs-2022) ' . (($result) ? 'true' : 'false'));
return $result;
}
public function signableData($data) {
@@ -104,8 +102,8 @@ class JcsEddsa2022 {
}
public function canonicalize($data) {
$canonicalizer = new JsonCanonicalizer();
return $canonicalizer->canonicalize($data);
$canonicalization = JsonCanonicalizatorFactory::getInstance();
return $canonicalization->canonicalize($data);
}
}

View File

@@ -8,10 +8,9 @@ class LDSignatures {
static function verify($data,$pubkey) {
$expand_and_check_unsafe = true;
$ohash = self::hash(self::signable_options($data['signature']), $expand_and_check_unsafe);
$dhash = self::hash(self::signable_data($data), $expand_and_check_unsafe);
$ohash = self::hash(self::signable_options($data['signature']));
$dhash = self::hash(self::signable_data($data));
$x = Crypto::verify($ohash . $dhash,base64_decode($data['signature']['signatureValue']), $pubkey);
logger('LD-verify: ' . intval($x));
@@ -75,11 +74,11 @@ class LDSignatures {
return json_encode($newopts,JSON_UNESCAPED_SLASHES);
}
static function hash($obj, $expand_and_check_unsafe = false) {
return hash('sha256', self::normalise($obj, $expand_and_check_unsafe));
static function hash($obj) {
return hash('sha256', self::normalise($obj));
}
static function normalise($data, $expand_and_check_unsafe) {
static function normalise($data) {
$ret = '';
if(is_string($data)) {
@@ -91,15 +90,6 @@ class LDSignatures {
jsonld_set_document_loader('jsonld_document_loader');
if ($expand_and_check_unsafe) {
$expanded = jsonld_expand($data);
if (self::contains_unsafe_keys($expanded)) {
logger('contains_unsafe_keys: ' . print_r($data,true));
throw new \Exception('json-ld graph modification operation detected');
}
}
try {
$ret = jsonld_normalize($data,[ 'algorithm' => 'URDNA2015', 'format' => 'application/nquads' ]);
}
@@ -142,41 +132,6 @@ class LDSignatures {
}
static function contains_unsafe_keys(array|object $data, int $depth = 0): bool
{
if ($depth > 64) {
return true;
}
$unsafe_keys = ['@graph', '@included', '@reverse'];
if (is_object($data)) {
$data = (array) $data;
}
if (is_array($data)) {
foreach ($data as $key => $value) {
//
// We can't use `in_array` since the keys may contain more than
// just the keyword after expansion, typically "_:@included"
// for an unnamed node with the "@included" key.
//
// So we use `array_filter` with a callback instead:
$matches = array_filter($unsafe_keys, fn ($k) => strpos($key, $k) !== false);
if (!empty($matches)) {
return true;
}
if (is_array($value) || is_object($value)) {
if (self::contains_unsafe_keys($value, $depth + 1)) {
return true;
}
}
}
}
return false;
}
}

View File

@@ -199,7 +199,8 @@ class Libsync {
dbesc($sender)
);
$mid = $arr['item'][0]['message_id'] ?? 'sync';
$mid = 'sync';
$DR = new DReport(z_root(), $sender, $d, $mid);
@@ -305,8 +306,15 @@ class Libsync {
if (array_key_exists('item', $arr) && $arr['item']) {
sync_items($channel, $arr['item'], ((array_key_exists('relocate', $arr)) ? $arr['relocate'] : null));
$mid = $arr['item'][0]['message_id'] . '#sync';
}
// deprecated, maintaining for a few months for upward compatibility
// this should sync webpages, but the logic is a bit subtle
//if (array_key_exists('item_id', $arr) && $arr['item_id'])
// sync_items($channel, $arr['item_id']);
if (array_key_exists('menu', $arr) && $arr['menu'])
sync_menus($channel, $arr['menu']);
@@ -749,11 +757,12 @@ class Libsync {
*/
call_hooks('process_channel_sync_delivery', $addon);
$DR->update('channel sync processed');
$DR = new DReport(z_root(), $d, $d, $mid, 'channel sync processed');
$DR->set_name($channel['channel_name'] . ' <' . channel_reddress($channel) . '>');
$result[] = $DR->get();
}
return $result;
}

View File

@@ -3,10 +3,10 @@
namespace Zotlabs\Lib;
use App;
use DBA;
use Zotlabs\Access\PermissionLimits;
use Zotlabs\Access\Permissions;
use Zotlabs\Daemon\Master;
use Zotlabs\Lib\Config;
use Zotlabs\Web\HTTPSig;
require_once('include/crypto.php');
@@ -116,6 +116,10 @@ class Libzot {
}
if ($msg) {
$actors = get_hubloc_id_urls_by_x($channel['channel_hash']);
if ($encoding === 'activitystreams' && array_key_exists('actor', $msg) && is_string($msg['actor']) && in_array($msg['actor'], $actors)) {
$msg = JSalmon::sign($msg, $actors[0], $channel['channel_prvkey']);
}
$data['data'] = $msg;
}
else {
@@ -349,7 +353,7 @@ class Libzot {
$next_birthday = datetime_convert('UTC', 'UTC', $record['data']['profile']['next_birthday']);
}
else {
$next_birthday = DBA::$dba->get_null_date();
$next_birthday = NULL_DATE;
}
$profile_assign = get_pconfig($channel['channel_id'], 'system', 'profile_assign', '');
@@ -1298,17 +1302,8 @@ class Libzot {
$item['comment_policy'] = 'authenticated';
}
if (!ObjCache::Get($item['mid'])) {
ObjCache::Set($item['mid'], $AS->data);
}
else {
$existing = q("SELECT owner_xchan, author_xchan FROM item WHERE mid = '%s' LIMIT 1",
dbesc($item['mid'])
);
if ($existing && $existing[0]['owner_xchan'] === $item['owner_xchan'] && $existing[0]['author_xchan'] === $item['author_xchan']) {
ObjCache::Set($item['mid'], $AS->data);
}
if (isset($AS->meta['signed_data']) && $AS->meta['signed_data']) {
IConfig::Set($item, 'activitypub', 'signed_data', $AS->meta['signed_data'], false);
}
logger('Activity received: ' . print_r($item, true), LOGGER_DATA, LOG_DEBUG);
@@ -1650,19 +1645,12 @@ class Libzot {
if (intval($channel['channel_system']) && (!$arr['item_private']) && (!$relay)) {
$local_public = true;
$incl = Config::Get('system','pubstream_incl', '');
$excl = Config::Get('system','pubstream_excl', '');
$incl = Config::Get('system','pubstream_incl');
$excl = Config::Get('system','pubstream_excl');
if ($incl || $excl) {
$plaintext = prepare_text($arr['body'], ((isset($arr['mimetype'])) ? $arr['mimetype'] : 'text/bbcode'));
$plaintext = html2plain((isset($arr['summary']) && $arr['summary']) ? $arr['summary'] . ' ' . $plaintext : $plaintext);
$plaintext = html2plain((isset($arr['title']) && $arr['title']) ? $arr['title'] . ' ' . $plaintext : $plaintext);
if (!(new MessageFilter($arr, html_entity_decode($incl), html_entity_decode($excl), ['plaintext' => $plaintext]))->evaluate()) {
logger('post is filtered');
$local_public = false;
continue;
}
if(($incl || $excl) && !MessageFilter::evaluate($arr, $incl, $excl)) {
$local_public = false;
continue;
}
$r = q("select xchan_selfcensored, xchan_censored from xchan where xchan_hash = '%s'",
@@ -1671,7 +1659,6 @@ class Libzot {
// don't import sys channel posts from selfcensored or censored authors
if ($r && ($r[0]['xchan_selfcensored'] || $r[0]['xchan_censored'])) {
logger('author is censored');
$local_public = false;
continue;
}
@@ -1842,7 +1829,9 @@ class Libzot {
}
if (intval($arr['item_private']) === 2) {
$allowed = perm_is_allowed($channel['channel_id'], $sender, 'post_mail');
if (!perm_is_allowed($channel['channel_id'], $sender, 'post_mail')) {
$allowed = false;
}
}
if (!$allowed) {
@@ -1904,13 +1893,11 @@ class Libzot {
else {
$DR->update('update ignored');
$result[] = $DR->get();
// We need this line to ensure wall-to-wall comments and add/remove activities are relayed (by falling through to the relay bit),
// and at the same time not relay any other relayable posts more than once, because to do so is very wasteful.
// The second part should prevent possible items that come back to us from channels that source our channel from being relayed again (sender != owner or author).
if (!intval($r[0]['item_origin']) || (intval($r[0]['item_origin']) && !in_array($sender, [$r[0]['owner_xchan'], $r[0]['author_xchan']]))) {
if (!intval($r[0]['item_origin']))
continue;
}
}
@@ -2161,9 +2148,10 @@ class Libzot {
}
if (isset($AS->meta['signed_data'])) {
IConfig::Set($arr, 'activitypub', 'signed_data', $AS->meta['signed_data'], false);
$j = json_decode($AS->meta['signed_data'], true);
if ($j) {
ObjCache::Set($arr['mid'], json_encode(JSalmon::unpack($j['data'])));
IConfig::Set($arr, 'activitypub', 'rawmsg', json_encode(JSalmon::unpack($j['data'])), true);
}
}

View File

@@ -2,7 +2,6 @@
namespace Zotlabs\Lib;
use DBA;
use Zotlabs\Lib\Config;
use Zotlabs\Lib\Libzot;
use Zotlabs\Lib\Zotfinger;
@@ -217,7 +216,7 @@ class Libzotdir {
[
'site_url' => DIRECTORY_FALLBACK_MASTER,
'site_flags' => DIRECTORY_MODE_PRIMARY,
'site_update' => DBA::$dba->get_null_date(),
'site_update' => NULL_DATE,
'site_directory' => DIRECTORY_FALLBACK_MASTER . '/dirsearch',
'site_realm' => DIRECTORY_REALM,
'site_valid' => 1,
@@ -248,7 +247,7 @@ class Libzotdir {
$token = Config::Get('system','realm_token');
$syncdate = (($rr['site_sync'] <= DBA::$dba->get_null_date()) ? datetime_convert('UTC','UTC','now - 2 days') : $rr['site_sync']);
$syncdate = (($rr['site_sync'] <= NULL_DATE) ? datetime_convert('UTC','UTC','now - 2 days') : $rr['site_sync']);
$x = z_fetch_url($rr['site_directory'] . '?f=&sync=' . urlencode($syncdate) . (($token) ? '&t=' . $token : ''));
if (! $x['success'])
@@ -725,7 +724,7 @@ class Libzotdir {
if ($u) {
$x = q("UPDATE updates SET $date_sql $flag_sql ud_last = '%s', ud_host = '%s', ud_addr = '%s', ud_update = 0 WHERE ud_id = %d",
dbesc(DBA::$dba->get_null_date()),
dbesc(NULL_DATE),
dbesc(z_root()),
dbesc($addr),
intval($u[0]['ud_id'])

View File

@@ -4,417 +4,260 @@ namespace Zotlabs\Lib;
require_once('include/html2plain.php');
class MessageFilter
{
protected $lastMatch = '';
protected $item = null;
protected $include = '';
protected $exclude = '';
protected $options = [];
protected $tags = null;
protected $language = '';
protected $text = '';
protected $excludeRules = [];
protected $includeRules = [];
class MessageFilter {
public function __construct($item, $include = '', $exclude = '', $options = [])
{
$this->item = $item;
$this->include = $include;
$this->exclude = $exclude;
$this->options = $options;
$this->setup();
}
public static function evaluate($item, $incl, $excl) {
protected function setup()
{
// Option: plaintext
// Improve language detection by providing a plaintext version of $item['body'] which has no markup constructs/tags.
$text = prepare_text($item['body'], ((isset($item['mimetype'])) ? $item['mimetype'] : 'text/bbcode'));
$text = html2plain((!empty($item['title'])) ? $item['title'] . ' ' . $text : $text);
if (array_key_exists('plaintext', $this->options)) {
$this->text = $this->options['plaintext'];
} else {
$this->text = $this->item['body'];
}
$lang = null;
if ((strpos($incl, 'lang=') !== false) || (strpos($excl, 'lang=') !== false) || (strpos($incl, 'lang!=') !== false) || (strpos($excl, 'lang!=') !== false)) {
$lang = detect_language($text);
}
$this->language = '';
$tags = ((isset($item['term']) && is_array($item['term']) && count($item['term'])) ? $item['term'] : false);
// Language matching is a bit tricky, because the language can be ambiguous (detect_language() returns '').
// If the language is ambiguous, the message will pass (be accepted) regardless of language rules.
$until = null;
if (str_contains($this->include, 'lang=')
|| str_contains($this->exclude, 'lang=')
|| str_contains($this->include, 'lang!=')
|| str_contains($this->exclude, 'lang!=')) {
$this->language = detect_language($this->text);
}
// exclude always has priority
$this->tags = ((isset($this->item['term']) && is_array($this->item['term'])
&& count($this->item['term'])) ? $this->item['term'] : null);
$exclude = (($excl) ? explode("\n", $excl) : null);
$this->excludeRules = $this->parse($this->exclude);
$this->includeRules = $this->parse($this->include);
if ($exclude) {
foreach ($exclude as $word) {
$word = html_entity_decode(trim($word));
if (! $word) {
continue;
}
if (isset($lang) && ((strpos($word, 'lang=') === 0) || (strpos($word, 'lang!=') === 0))) {
if (!strlen($lang)) {
// Result is ambiguous. As we are matching deny rules only at this time, continue tests.
// Any matching deny rule concludes testing.
continue;
}
if (strpos($word, 'lang=') === 0 && strcasecmp($lang, trim(substr($word, 5))) == 0) {
return false;
} elseif (strpos($word, 'lang!=') === 0 && strcasecmp($lang, trim(substr($word, 6))) != 0) {
return false;
}
}
elseif (str_starts_with($word, 'until=')) {
$until = strtotime(trim(substr($word, 6)));
if ($until > strtotime($item['created'] . ' UTC')) {
return false;
}
}
elseif (substr($word, 0, 1) === '#' && $tags) {
foreach ($tags as $t) {
if ((($t['ttype'] == TERM_HASHTAG) || ($t['ttype'] == TERM_COMMUNITYTAG)) && (($t['term'] === substr($word, 1)) || (substr($word, 1) === '*'))) {
return false;
}
}
} elseif (substr($word, 0, 1) === '$' && $tags) {
foreach ($tags as $t) {
if (($t['ttype'] == TERM_CATEGORY) && (($t['term'] === substr($word, 1)) || (substr($word, 1) === '*'))) {
return false;
}
}
} elseif (substr($word, 0, 2) === '?+') {
if (self::test_condition(substr($word, 2), $item['obj'])) {
return false;
}
} elseif (substr($word, 0, 1) === '?') {
if (self::test_condition(substr($word, 1), $item)) {
return false;
}
} elseif ((strpos($word, '/') === 0) && preg_match($word, $text)) {
return false;
} elseif (stristr($text, $word) !== false) {
return false;
}
}
}
}
$include = (($incl) ? explode("\n", $incl) : null);
protected function parse($string): array
{
$rules = [];
if (! strlen($string)) {
return $rules;
}
if ($include) {
foreach ($include as $word) {
$word = html_entity_decode(trim($word));
if (! $word) {
continue;
}
if (isset($lang) && ((strpos($word, 'lang=') === 0) || (strpos($word, 'lang!=') === 0))) {
if (!strlen($lang)) {
// Result is ambiguous. However we are checking allow rules
// and an ambiguous language is always permitted.
return true;
}
if (strpos($word, 'lang=') === 0 && strcasecmp($lang, trim(substr($word, 5))) == 0) {
return true;
} elseif (strpos($word, 'lang!=') === 0 && strcasecmp($lang, trim(substr($word, 6))) != 0) {
return true;
}
}
elseif (str_starts_with($word, 'until=')) {
$until = strtotime(trim(substr($word, 6)));
if ($until > strtotime($item['created'] . ' UTC')) {
return true;
}
}
elseif (substr($word, 0, 1) === '#' && $tags) {
foreach ($tags as $t) {
if ((($t['ttype'] == TERM_HASHTAG) || ($t['ttype'] == TERM_COMMUNITYTAG)) && (($t['term'] === substr($word, 1)) || (substr($word, 1) === '*'))) {
return true;
}
}
} elseif (substr($word, 0, 1) === '$' && $tags) {
foreach ($tags as $t) {
if (($t['ttype'] == TERM_CATEGORY) && (($t['term'] === substr($word, 1)) || (substr($word, 1) === '*'))) {
return true;
}
}
} elseif (substr($word, 0, 2) === '?+') {
if (self::test_condition(substr($word, 2), $item['obj'])) {
return true;
}
} elseif (substr($word, 0, 1) === '?') {
if (self::test_condition(substr($word, 1), $item)) {
return true;
}
} elseif ((strpos($word, '/') === 0) && preg_match($word, $text)) {
return true;
} elseif (stristr($text, $word) !== false) {
return true;
}
}
} else {
return true;
}
$phrases = preg_split("/(\s\|\|\s|\s&&\s|\n)/", $string, flags: PREG_SPLIT_DELIM_CAPTURE);
if (!$phrases) {
return $rules;
}
for ($index = 0; $index < count($phrases); $index ++) {
// Even indices are rules and odd indices are operations, linefeed is an implict OR.
if (!($index & 1)) {
$currentRule = ['operation' => '', 'rule' => $phrases[$index]];
if ($index && isset($phrases[$index - 1])) {
$currentRule['operation'] = $phrases[$index - 1];
if ($currentRule['operation'] === "\n") {
$currentRule['operation'] = ' || ';
}
$index++;
}
$rules[] = $currentRule;
}
}
return $rules;
}
return false;
}
public function evaluate(): bool
{
$previousResult = $newResult = null;
// exclude always has priority
$exclude = $this->excludeRules;
$include = $this->includeRules;
if ($exclude) {
foreach ($exclude as $rule) {
if (!strlen(trim($rule['rule']))) {
continue;
}
if (!strlen($this->language) && ((str_starts_with($rule['rule'], 'lang=')) || (str_starts_with($rule['rule'], 'lang!=')))) {
continue;
}
$result = $this->evaluateRule($rule['rule']);
switch ($rule['operation']) {
case '':
$previousResult = $newResult = $result;
break;
case ' || ':
$newResult = $previousResult || $result;
break;
case ' && ':
$newResult = $previousResult && $result;
break;
}
}
if ($newResult) {
return false;
}
}
$previousResult = $newResult = null;
if ($include) {
foreach ($include as $rule) {
if (!strlen(trim($rule['rule']))) {
continue;
}
if (!strlen($this->language) && ((str_starts_with($rule['rule'], 'lang=')) || (str_starts_with($rule['rule'], 'lang!=')))) {
continue;
}
$result = $this->evaluateRule($rule['rule']);
switch ($rule['operation']) {
case '':
$previousResult = $newResult = $result;
break;
case ' || ':
$newResult = $previousResult || $result;
break;
case ' && ':
$newResult = $previousResult && $result;
break;
}
}
}
return $newResult ?? true;
}
protected function evaluateRule($ruleText): bool
{
$ruleText = trim($ruleText);
if (($this->language) && ((str_starts_with($ruleText, 'lang=')) || (str_starts_with($ruleText, 'lang!=')))) {
if (str_starts_with($ruleText, 'lang=') && strcasecmp($this->language, trim(substr($ruleText, 5))) == 0) {
$this->lastMatch = $ruleText;
return true;
} elseif (str_starts_with($ruleText, 'lang!=') && strcasecmp($this->language, trim(substr($ruleText, 6))) != 0) {
$this->lastMatch = $ruleText;
return true;
}
} elseif (str_starts_with($ruleText, 'until=')) {
$until = strtotime(trim(substr($ruleText, 6)));
if ($until > strtotime($this->item['created'] . ' UTC')) {
$this->lastMatch = $ruleText;
return true;
}
} elseif (str_starts_with($ruleText, '#') && $this->tags) {
// #hashtag match
foreach ($this->tags as $t) {
if ((($t['ttype'] == TERM_HASHTAG) || ($t['ttype'] == TERM_COMMUNITYTAG)) && (!strcasecmp($t['term'], substr($ruleText, 1)) || (substr($ruleText, 1) === '*'))) {
$this->lastMatch = $ruleText;
return true;
}
}
// hashtag count match
if (substr($ruleText, 1, 1) === '>') {
$hashtagLimit = (int)substr($ruleText, 2);
$hashtagCount = 0;
foreach ($this->tags as $t) {
if ($t['ttype'] == TERM_HASHTAG || $t['ttype'] == TERM_COMMUNITYTAG) {
$hashtagCount++;
}
}
if ($hashtagLimit && $hashtagCount > $hashtagLimit) {
$this->lastMatch = $ruleText;
return true;
}
}
} elseif (str_starts_with($ruleText, '@') && $this->tags) {
// @mention match
foreach ($this->tags as $t) {
if ((($t['ttype'] == TERM_MENTION && (!strcasecmp($t['term'], substr($ruleText, 1)))) || (substr($ruleText, 1) === '*'))) {
$this->lastMatch = $ruleText;
return true;
}
}
// mention count match
if (substr($ruleText, 1, 1) === '>') {
$mentionLimit = (int)substr($ruleText, 2);
$mentionCount = 0;
foreach ($this->tags as $t) {
if ($t['ttype'] == TERM_MENTION) {
$mentionCount++;
}
}
if ($mentionLimit && $mentionCount > $mentionLimit) {
$this->lastMatch = $ruleText;
return true;
}
}
} elseif (str_starts_with($ruleText, '$') && $this->tags) {
foreach ($this->tags as $t) {
if (($t['ttype'] == TERM_CATEGORY) && (($t['term'] === substr($ruleText, 1)) || (substr($ruleText, 1) === '*'))) {
$this->lastMatch = $ruleText;
return true;
}
}
} elseif (str_starts_with($ruleText, '?+') && is_array($this->item['obj'])) {
if ($this->test_condition(substr($ruleText, 2), $this->item['obj'])) {
$this->lastMatch = $ruleText;
return true;
}
} elseif (str_starts_with($ruleText, '?')) {
$this->item['ua'] = $_SERVER['HTTP_USER_AGENT'] ?? '';
if (empty($this->item['app'])) {
$author_app = $this->item['author']['site_project'] ?? '';
if (!$author_app && isset($this->item['author'])) {
if (str_contains($this->item['author']['xchan_hash'], 'threads.net') || str_contains($this->item['author']['xchan_hash'], 'threads.com')) {
$author_app = 'threads';
}
}
$this->item['app'] = $author_app;
}
if ($this->test_condition(substr($ruleText, 1), $this->item)) {
unset($this->item['ua']);
$this->lastMatch = $ruleText;
return true;
}
unset($this->item['ua']);
} elseif ((str_starts_with($ruleText, '/')) && preg_match($ruleText, $this->item['body'])) {
$this->lastMatch = $ruleText;
return true;
} elseif (stristr($this->item['body'], $ruleText) !== false) {
$this->lastMatch = $ruleText;
return true;
}
return false;
}
public function getLastMatch(): string
{
return $this->lastMatch;
}
public function setLastMatch($string): MessageFilter
{
$this->lastMatch = $string;
return $this;
}
/**
* @brief Test for Conditional Execution conditions. Shamelessly ripped off from src/Render/Comanche
*
* This is extensible. The first version of variable testing supports tests of the forms:
*
* - ?foo ~= baz will check if item.foo contains the string 'baz';
* - ?foo == baz will check if item.foo is the string 'baz';
* - ?foo != baz will check if item.foo is not the string 'baz';
* - ?foo // baz will check if item.foo matches the regular expression 'baz';
* - ?foo >= 3 will check if item.foo is greater than or equal to 3;
* - ?foo > 3 will check if item.foo is greater than 3;
* - ?foo <= 3 will check if item.foo is less than or equal to 3;
* - ?foo < 3 will check if item.foo is less than 3;
* - ?foo & 2 will check if item.foo has the second bit set.
* - ?foo !& 2 will check if item.foo does not have the second bit set.
*
* - ?foo {} baz which will check if 'baz' is an array element in item.foo
* - ?foo {*} baz which will check if 'baz' is an array key in item.foo
* - ?foo which will check for a return of a true condition for item.foo;
/**
* Evaluate a conditional expression with support for AND (&&) and OR (||) operators.
*
* - ?foo ~= baz which will check if item.foo contains the string 'baz';
* - ?foo == baz which will check if item.foo is the string 'baz';
* - ?foo != baz which will check if item.foo is not the string 'baz';
* - ?foo >= 3 which will check if item.foo is greater than or equal to 3;
* - ?foo > 3 which will check if item.foo is greater than 3;
* - ?foo <= 3 which will check if item.foo is less than or equal to 3;
* - ?foo < 3 which will check if item.foo is less than 3;
*
* - ?foo {} baz which will check if 'baz' is an array element in item.foo
* - ?foo {*} baz which will check if 'baz' is an array key in item.foo
* - ?foo which will check for a return of a true condition for item.foo;
* - ?!foo which will check for a return of a false condition for item.foo;
*
* The values 0, '', an empty array, and an unset value will all evaluate to false.
*
* @param string $s
* @param array $item
* @return bool
*/
protected function test_condition($s,$item)
{
$s = trim($s);
* The values 0, '', an empty array, and an unset value will all evaluate to false.
*
* @param string $s The condition string to evaluate.
* @param array $item The associative array providing variable values.
* @return bool True if the condition is met, false otherwise.
*/
if (preg_match('/(.*?)\s&\s(.*?)$/', $s, $matches)) {
$x = ((array_key_exists(trim($matches[1]),$item)) ? $item[trim($matches[1])] : EMPTY_STR);
if ($x & (int) trim($matches[2])) {
return true;
}
return false;
}
public static function test_condition($s, $item) {
$s = trim($s);
if (preg_match('/(.*?)\s!&\s(.*?)$/', $s, $matches)) {
$x = ((array_key_exists(trim($matches[1]),$item)) ? $item[trim($matches[1])] : EMPTY_STR);
if (!($x & (int) trim($matches[2]))) {
return true;
}
return false;
}
// Handle OR (||)
// Split on '||' not inside quotes
$or_parts = preg_split('/\s*\|\|\s*/', $s);
if (count($or_parts) > 1) {
foreach ($or_parts as $part) {
if (self::test_condition(ltrim($part, '?+'), $item)) {
return true;
}
}
return false;
}
if (preg_match('/(.*?)\s~=\s(.*?)$/', $s, $matches)) {
$x = ((array_key_exists(trim($matches[1]),$item)) ? $item[trim($matches[1])] : EMPTY_STR);
if (is_string($x) && stripos($x, trim($matches[2])) !== false) {
return true;
}
return false;
}
// Handle AND (&&)
// Split on '&&' not inside quotes
$and_parts = preg_split('/\s*\&\&\s*/', $s);
if (count($and_parts) > 1) {
foreach ($and_parts as $part) {
if (!self::test_condition(ltrim($part, '?+'), $item)) {
return false;
}
}
return true;
}
if (preg_match('/(.*?)\s==\s(.*?)$/', $s, $matches)) {
$x = ((array_key_exists(trim($matches[1]),$item)) ? $item[trim($matches[1])] : EMPTY_STR);
if ($x == trim($matches[2])) {
return true;
}
return false;
}
// Basic checks
if (preg_match('/(.*?)\s!=\s(.*?)$/', $s, $matches)) {
$x = ((array_key_exists(trim($matches[1]),$item)) ? $item[trim($matches[1])] : EMPTY_STR);
if ($x != trim($matches[2])) {
return true;
}
return false;
}
// Contains substring (case-insensitive)
if (preg_match('/(.*?)\s\~\=\s(.*?)$/', $s, $matches)) {
$x = ((array_key_exists(trim($matches[1]), $item)) ? $item[trim($matches[1])] : EMPTY_STR);
return (stripos($x, trim($matches[2])) !== false);
}
if (preg_match('/(.*?)\s\/\/\s(.*?)$/', $s, $matches)) {
$x = ((array_key_exists(trim($matches[1]),$item)) ? $item[trim($matches[1])] : EMPTY_STR);
if (substr(trim($matches[2]),0,1) !== substr(trim($matches[2]),-1)) {
$matches[2] = '/' . trim($matches[2]) . '/';
}
if (preg_match(trim($matches[2]), $x)) {
return true;
}
return false;
}
// Equality
if (preg_match('/(.*?)\s\=\=\s(.*?)$/', $s, $matches)) {
$x = ((array_key_exists(trim($matches[1]), $item)) ? $item[trim($matches[1])] : EMPTY_STR);
return ($x == trim($matches[2]));
}
if (preg_match('/(.*?)\s>=\s(.*?)$/', $s, $matches)) {
$x = ((array_key_exists(trim($matches[1]),$item)) ? $item[trim($matches[1])] : EMPTY_STR);
if ($x >= trim($matches[2])) {
return true;
}
return false;
}
// Inequality
if (preg_match('/(.*?)\s\!\=\s(.*?)$/', $s, $matches)) {
$x = ((array_key_exists(trim($matches[1]), $item)) ? $item[trim($matches[1])] : EMPTY_STR);
return ($x != trim($matches[2]));
}
if (preg_match('/(.*?)\s<=\s(.*?)$/', $s, $matches)) {
$x = ((array_key_exists(trim($matches[1]),$item)) ? $item[trim($matches[1])] : EMPTY_STR);
if ($x <= trim($matches[2])) {
return true;
}
return false;
}
// Greater than or equal
if (preg_match('/(.*?)\s\>\=\s(.*?)$/', $s, $matches)) {
$x = ((array_key_exists(trim($matches[1]), $item)) ? $item[trim($matches[1])] : EMPTY_STR);
return ($x >= trim($matches[2]));
}
if (preg_match('/(.*?)\s>\s(.*?)$/', $s, $matches)) {
$x = ((array_key_exists(trim($matches[1]),$item)) ? $item[trim($matches[1])] : EMPTY_STR);
if ($x > trim($matches[2])) {
return true;
}
return false;
}
// Less than or equal
if (preg_match('/(.*?)\s\<\=\s(.*?)$/', $s, $matches)) {
$x = ((array_key_exists(trim($matches[1]), $item)) ? $item[trim($matches[1])] : EMPTY_STR);
return ($x <= trim($matches[2]));
}
if (preg_match('/(.*?)\s<\s(.*?)$/', $s, $matches)) {
$x = ((array_key_exists(trim($matches[1]),$item)) ? $item[trim($matches[1])] : EMPTY_STR);
if ($x < trim($matches[2])) {
return true;
}
return false;
}
// Greater than
if (preg_match('/(.*?)\s\>\s(.*?)$/', $s, $matches)) {
$x = ((array_key_exists(trim($matches[1]), $item)) ? $item[trim($matches[1])] : EMPTY_STR);
return ($x > trim($matches[2]));
}
// Array contains value
if (preg_match('/(.*?)\s\{\}\s(.*?)$/', $s, $matches)) {
$x = ((array_key_exists(trim($matches[1]),$item)) ? $item[trim($matches[1])] : EMPTY_STR);
if (is_array($x) && in_array(trim($matches[2]), $x)) {
return true;
}
return false;
}
// Less than
if (preg_match('/(.*?)\s\<\s(.*?)$/', $s, $matches)) {
$x = ((array_key_exists(trim($matches[1]), $item)) ? $item[trim($matches[1])] : EMPTY_STR);
return ($x < trim($matches[2]));
}
// Array contains key
if (preg_match('/(.*?)\s\{\*}\s(.*?)$/', $s, $matches)) {
$x = ((array_key_exists(trim($matches[1]),$item)) ? $item[trim($matches[1])] : EMPTY_STR);
if (is_array($x) && array_key_exists(trim($matches[2]), $x)) {
return true;
}
return false;
}
// Array contains value
if (preg_match('/(.*?)\s\{\}\s(.*?)$/', $s, $matches)) {
$x = ((array_key_exists(trim($matches[1]), $item)) ? $item[trim($matches[1])] : EMPTY_STR);
return (is_array($x) && in_array(trim($matches[2]), $x));
}
// Array contains key
if (preg_match('/(.*?)\s\{\*\}\s(.*?)$/', $s, $matches)) {
$x = ((array_key_exists(trim($matches[1]), $item)) ? $item[trim($matches[1])] : EMPTY_STR);
return (is_array($x) && array_key_exists(trim($matches[2]), $x));
}
// Ordering of this check (for falsiness) with relation to the following one (check for truthiness) is important.
// Falsy check
if (preg_match('/\!(.*?)$/', $s, $matches)) {
$x = ((array_key_exists(trim($matches[1]), $item)) ? $item[trim($matches[1])] : EMPTY_STR);
return !$x;
}
// Truthy check (default)
if (preg_match('/(.*?)$/', $s, $matches)) {
$x = ((array_key_exists(trim($matches[1]), $item)) ? $item[trim($matches[1])] : EMPTY_STR);
return (bool)$x;
}
// If no conditions matched, return false
return false;
}
// Ordering of this check (for falseness) with relation to the following one (check for truthiness) is important.
if (preg_match('/!(.*?)$/', $s, $matches)) {
$x = ((array_key_exists(trim($matches[1]),$item)) ? $item[trim($matches[1])] : EMPTY_STR);
if (!$x) {
return true;
}
return false;
}
if (preg_match('/(.*?)$/', $s, $matches)) {
$x = ((array_key_exists(trim($matches[1]),$item)) ? $item[trim($matches[1])] : EMPTY_STR);
if ($x) {
return true;
}
return false;
}
return false;
}
}

View File

@@ -1,37 +0,0 @@
<?php
/*
* SPDX-FileCopyrightText: 2026 The Hubzilla Community
* SPDX-FileContributor: Harald Eilertsen <haraldei@anduin.net>
*
* SPDX-License-Identifier: MIT
*/
namespace Zotlabs\Lib;
use DBA;
use PDO;
/**
* Concrete implementation for getting stats from MySQL and MariaDB databases.
*/
class MySQLDbStats extends DbStats {
public function getQueries(): int {
//
// We can't use the regular Hubzilla db helper function here, as
// it will only return information from a `SELECT` statement.
//
// Use the basic PDO access instead.
//
$query = DBA::$dba->db->prepare('SHOW STATUS LIKE "Queries"');
$query->execute();
$result = $query->fetch(PDO::FETCH_ASSOC);
logger(print_r($result, true));
if (!empty($result)) {
return $result['Value'] ?? -1;
}
return 0;
}
}

View File

@@ -1,40 +0,0 @@
<?php
namespace Zotlabs\Lib;
class ObjCache
{
public static function Get($path, $type = 'as')
{
if (!$path) {
return [];
}
$localpath = Hashpath::path($path, 'store/[data]/[obj]/' . $type, 2, alg: 'sha256');
if (file_exists($localpath)) {
return json_unserialize(file_get_contents($localpath));
}
return [];
}
public static function Set($path, $content, $type = 'as') {
if (!$path) {
return;
}
$localpath = Hashpath::path($path, 'store/[data]/[obj]/' . $type, 2, alg: 'sha256');
file_put_contents($localpath, json_serialize($content));
}
public static function Delete($path, $type = 'as') {
if (!$path) {
return;
}
$localpath = Hashpath::path($path, 'store/[data]/[obj]/' . $type, 2, alg: 'sha256');
if (file_exists($localpath)) {
unlink($localpath);
}
}
}

View File

@@ -1,32 +0,0 @@
<?php
/*
* SPDX-FileCopyrightText: 2026 The Hubzilla Community
* SPDX-FileContributor: Harald Eilertsen <haraldei@anduin.net>
*
* SPDX-License-Identifier: MIT
*/
namespace Zotlabs\Lib;
use DBA;
/**
* Concrete implementation for getting stats from PostgreSQL databases.
*/
class PostgresDbStats extends DbStats {
public function getQueries(): int {
$sqlGetQps = <<<'SQL'
select (xact_commit + xact_rollback) as queries
from pg_stat_database
where datname='%s'
SQL;
$result = q($sqlGetQps, DBA::$dba->dbname);
if (!empty($result)) {
return $result[0]['queries'] ?? -1;
}
return 0;
}
}

View File

@@ -7,20 +7,6 @@ use Zotlabs\Zot6\Zot6Handler;
class Queue {
/**
* Get number of entries in the out queue.
*
* When delivery is successful, the item is removed from the out queue, so
* the number of items in the queue reflects the number of pending delivery
* attempts.
*
* @return int Number of items in the out queue.
*/
static function count(): int {
$r = dbq('select count(*) as total from outq');
return $r[0]['total'] ?? 0;
}
static function update($id, $add_priority = 0) {
logger('queue: requeue item ' . $id,LOGGER_DEBUG);

View File

@@ -1,28 +0,0 @@
<?php
/*
* SPDX-FileCopyrightText: 2026 The Hubzilla Community
* SPDX-FileContributor: Harald Eilertsen <haraldei@anduin.net>
*
* SPDX-License-Identifier: MIT
*/
namespace Zotlabs\Lib;
class QueueWorkerStats
{
public readonly int $size;
public readonly int $active;
public function __construct() {
$query = <<<'SQL'
select count(*) as total from workerq
union (select count(*) as qworkers from workerq where workerq_reservationid is not null)
SQL;
$result = dbq('select count(*) as total from workerq');
$this->size = !empty($result) ? $result[0]['total'] : -1;
$result = dbq('select count(*) as qworkers from workerq where workerq_reservationid is not null');
$this->active = !empty($result) ? $result[0]['qworkers'] : -1;
}
}

View File

@@ -118,28 +118,26 @@ class Share {
$photo_bb = $object['body'];
}
if (!str_contains($this->item['body'], '[/share]')) {
$quote = in_array($this->item['author']['xchan_network'], ['zot6', 'activitypub']) ? "quote='true'" : '';
$bb .= "[share author='" . urlencode($this->item['author']['xchan_name']) . "'
profile='" . $this->item['author']['xchan_url'] . "'
avatar='" . $this->item['author']['xchan_photo_s'] . "'
link='" . $this->item['plink'] . "'
auth='" . (($this->item['author']['xchan_network'] === 'zot6') ? 'true' : 'false') . "'
posted='" . $this->item['created'] . "'
message_id='" . $this->item['mid'] . "'
$quote
]";
if ($this->item['title']) {
if (strpos($this->item['body'], "[/share]") !== false) {
$pos = strpos($this->item['body'], "[share");
$bb = substr($this->item['body'], $pos);
} else {
$bb = "[share author='".urlencode($this->item['author']['xchan_name']).
"' profile='" . $this->item['author']['xchan_url'] .
"' avatar='" . $this->item['author']['xchan_photo_s'] .
"' link='" . $this->item['plink'] .
"' auth='" . (($this->item['author']['xchan_network'] === 'zot6') ? 'true' : 'false') .
"' posted='" . $this->item['created'] .
"' message_id='" . $this->item['mid'] .
"']";
if($this->item['title'])
$bb .= '[h3][b]'.$this->item['title'].'[/b][/h3]'."\r\n";
}
$bb .= (($is_photo) ? $photo_bb . "\r\n" . $this->item['body'] : $this->item['body']);
$bb .= "[/share]";
}
return $bb;
}
}

View File

@@ -3,7 +3,6 @@
namespace Zotlabs\Lib;
use App;
use DBA;
use Zotlabs\Access\AccessList;
require_once('include/text.php');
@@ -121,10 +120,10 @@ class ThreadItem {
$locktype = 0;
}
$shareable = ((local_channel() && $conv->get_profile_owner() == local_channel()) && (intval($item['item_private']) === 0) && !str_contains($item['body'], '[/share]'));
$shareable = ((local_channel() && $conv->get_profile_owner() == local_channel()) && (intval($item['item_private']) === 0));
// allow an exemption for sharing stuff from your private feeds
if ($item['author']['xchan_network'] === 'rss')
if($item['author']['xchan_network'] === 'rss')
$shareable = true;
$repeatable = ((local_channel() && $conv->get_profile_owner() == local_channel()) && intval($item['item_private']) === 0 && in_array($item['author']['xchan_network'], ['zot6', 'activitypub']));
@@ -284,12 +283,9 @@ class ThreadItem {
$reply_to = [];
$reactions_allowed = false;
if($this->is_commentable()) {
if($this->is_commentable() && $observer) {
$reply_to = array( t("Reply to this message"), t("reply"), t("Reply to"));
if ($observer) {
$reactions_allowed = true;
}
$reactions_allowed = true;
}
$share = [];
@@ -416,7 +412,7 @@ class ThreadItem {
'isotime' => datetime_convert('UTC', date_default_timezone_get(), $item['created'], 'c'),
'localtime' => datetime_convert('UTC', date_default_timezone_get(), $item['created']),
'editedtime' => (($item['edited'] != $item['created']) ? sprintf(t('Last edited %s'), relative_time($item['edited'])) : ''),
'expiretime' => (($item['expires'] > DBA::$dba->get_null_date()) ? sprintf(t('Expires %s'), relative_time($item['expires'])) : ''),
'expiretime' => (($item['expires'] > NULL_DATE) ? sprintf(t('Expires %s'), relative_time($item['expires'])) : ''),
'lock' => $lock,
'locktype' => $locktype,
'delayed' => (($item['item_delayed']) ? sprintf(t('Published %s'), relative_time($item['created'])) : ''),

View File

@@ -9,7 +9,7 @@ trait HelpHelperTrait {
// PHP versions before 8.2 does not support trait constants,
// Leave this commented out until we drop support for PHP 8.1.
//
// const VALID_FILE_EXT = ['md', 'bb', 'html', 'json'];
// const VALID_FILE_EXT = ['md', 'bb', 'html'];
private string $file_name = '';
private string $file_type = '';
@@ -58,7 +58,7 @@ trait HelpHelperTrait {
private function find_help_file(string $base_path, string $lang): void {
// Use local variable until we can use trait constants.
$valid_file_ext = ['md', 'bb', 'html', 'json'];
$valid_file_ext = ['md', 'bb', 'html'];
$base_path_with_lang = "doc/{$lang}/${base_path}";

View File

@@ -1,132 +0,0 @@
<?php
/*
* SPDX-FileCopyrightText: 2026 The Hubzilla Community
* SPDX-FileContributor: Mario Vavti <mario@mariovavti.com>
*
* SPDX-License-Identifier: MIT
*/
namespace Zotlabs\Lib;
class Url {
/**
* @brief Adds a zid parameter to a url.
*
* @param string $s
* The url to accept the zid
* @param string $address
* $address to use instead of session environment
* @return string
*/
public static function zid(string $url, string $address = ''): string
{
if (!$url || strpos($url, 'zid=') !== false) {
return $url;
}
$parts = parse_url($url);
if ($parts === false) {
return $url;
}
$mine = get_my_url();
$myaddr = $address ?: get_my_address();
if (!$mine || !$myaddr) {
return $url;
}
$mine_parts = parse_url($mine);
$same_host = isset($mine_parts['host'], $parts['host']) && strcasecmp($mine_parts['host'], $parts['host']) === 0;
if ($same_host) {
return $url;
}
$query = [];
if (!empty($parts['query'])) {
parse_str($parts['query'], $query);
}
$query['zid'] = $myaddr;
$parts['query'] = http_build_query($query);
$hookdata = [
'url' => $url,
'zid' => urlencode($myaddr),
'result' => self::unparse($parts)
];
/**
* @hooks zid
* Called when adding the observer's zid to a URL.
* * \e string \b url - url to accept zid
* * \e string \b zid - urlencoded zid
* * \e string \b result - the return string we calculated, change it if you want to return something else
*/
call_hooks('zid', $hookdata);
return $hookdata['result'];
}
/**
* Reconstructs a URL from its parsed components.
*
* This function takes a parsed URL as an associative array and reconstructs
* the URL based on the specified components (scheme, host, port, user, pass, path, query, fragment).
* You can specify which components should be included in the final URL by passing the optional
* `$parts` array. The function will return the complete URL string formed by combining
* only the parts that exist in both the parsed URL and the `$parts` array.
*
* @param array $parsed_url The parsed URL components as an associative array.
* The array can include keys like 'scheme', 'host', 'port', 'user', 'pass',
* 'path', 'query', 'fragment'.
*
* @param array $parts An optional array that specifies which components of the URL
* should be included in the final string. Defaults to:
* ['scheme', 'host', 'port', 'user', 'pass', 'path', 'query', 'fragment'].
* If any of the components are not required, they can be omitted from the array.
*
* @return string The reconstructed URL as a string.
*/
public static function unparse(array $parsed_url, array $parts = ['scheme', 'host', 'port', 'user', 'pass', 'path', 'query', 'fragment']): string {
$url_parts = [];
if (in_array('scheme', $parts) && array_key_exists('scheme', $parsed_url)) {
$url_parts[] = $parsed_url['scheme'] . '://';
}
if (in_array('user', $parts) && array_key_exists('user', $parsed_url)) {
$url_parts[] = $parsed_url['user'];
if (in_array('pass', $parts) && array_key_exists('pass', $parsed_url)) {
$url_parts[] = ':' . $parsed_url['pass'];
}
$url_parts[] = '@';
}
if (in_array('host', $parts) && array_key_exists('host', $parsed_url)) {
$url_parts[] = $parsed_url['host'];
}
if (in_array('port', $parts) && array_key_exists('port', $parsed_url)) {
$url_parts[] = ':' . $parsed_url['port'];
}
if (in_array('path', $parts) && array_key_exists('path', $parsed_url)) {
$url_parts[] = $parsed_url['path'];
}
if (in_array('query', $parts) && array_key_exists('query', $parsed_url)) {
$url_parts[] = '?' . $parsed_url['query'];
}
if (in_array('fragment', $parts) && array_key_exists('fragment', $parsed_url)) {
$url_parts[] = '#' . $parsed_url['fragment'];
}
return implode('', $url_parts);
}
}

View File

@@ -437,7 +437,9 @@ class Acl extends \Zotlabs\Web\Controller {
if(($dirmode == DIRECTORY_MODE_PRIMARY) || ($dirmode == DIRECTORY_MODE_STANDALONE)) {
$url = z_root() . '/dirsearch';
} else {
}
if(! $url) {
$directory = Libzotdir::find_upstream_directory($dirmode);
$url = $directory['url'] . '/dirsearch';
}

View File

@@ -210,7 +210,7 @@ class Activity extends Controller {
// Give ocap tokens priority
if ($ob_authorize) {
$sql_extra = " and item.uid = " . intval($item_uid) . " ";
$sql_extra = " and item.uid = " . intval($token['uid']) . " ";
}
else {
$sql_extra = item_permissions_sql(0);

View File

@@ -8,7 +8,6 @@
namespace Zotlabs\Module;
use DBA;
use Zotlabs\Lib\Config;
require_once('include/account.php');
@@ -91,7 +90,7 @@ class Admin extends \Zotlabs\Web\Controller {
$r = q("SELECT COUNT(CASE WHEN account_id > 0 THEN 1 ELSE NULL END) AS total, COUNT(CASE WHEN account_expires > %s THEN 1 ELSE NULL END) AS expiring, COUNT(CASE WHEN account_expires < %s AND account_expires > '%s' THEN 1 ELSE NULL END) AS expired, COUNT(CASE WHEN (account_flags & %d)>0 THEN 1 ELSE NULL END) AS blocked FROM account",
db_utcnow(),
db_utcnow(),
dbesc(DBA::$dba->get_null_date()),
dbesc(NULL_DATE),
intval(ACCOUNT_BLOCKED)
);
if ($r) {

View File

@@ -55,10 +55,11 @@ class Account_edit {
function get() {
$account_id = intval(argv(2));
if(argc() > 2)
$account_id = argv(2);
$x = q("select * from account where account_id = %d limit 1",
$account_id
intval($account_id)
);
if(! $x) {

View File

@@ -203,6 +203,7 @@ class Accounts {
$t = get_markup_template('admin_accounts.tpl');
$o = replace_macros($t, array(
// strings //
'$debug' => $debug,
'$title' => t('Administration'),
'$page' => t('Accounts'),
'$submit' => t('Submit'),
@@ -262,7 +263,7 @@ class Accounts {
if ($zarop && $zarat >= 0 && $zarse && $zarse == $_SESSION[self::MYP]['h'][$zarat]) {
$rc = 0;
//
if ($zarop == 'd') {
$rd = q("UPDATE register SET reg_vital = 0 WHERE reg_id = %d AND SUBSTR(reg_hash,1,4) = '%s' ",
intval($_SESSION[self::MYP]['i'][$zarat]),
@@ -278,6 +279,7 @@ class Accounts {
intval($_SESSION[self::MYP]['i'][$zarat]),
dbesc($_SESSION[self::MYP]['h'][$zarat])
);
$rc = 0;
$rs = q("SELECT * from register WHERE reg_id = %d ",
intval($_SESSION[self::MYP]['i'][$zarat])
);
@@ -339,27 +341,29 @@ class Accounts {
* @SuppressWarnings(PHPMD.ShortVariable)
*/
private function block_unblock_accounts(): void {
if (!isset($_POST['user'])) {
if (!isset($_POST['user']) || !isset($_POST['blocked'])) {
return;
}
$users = $_POST['user'];
$blocked = $_POST['blocked'];
if (!is_array($users)) {
if (!is_array($users) || !is_array($blocked)) {
return;
}
$xor = db_getfunc('^');
foreach($users as $i => $id) {
// if account is blocked remove blocked bit-flag, otherwise add blocked bit-flag
$op = $blocked[$i] ? '& ~' : '| ';
foreach($users as $id) {
q("UPDATE account SET account_flags = (account_flags $xor %d) WHERE account_id = %d",
q("UPDATE account SET account_flags = (account_flags $op%d) WHERE account_id = %d",
intval(ACCOUNT_BLOCKED),
intval($id)
);
}
$count = count($users);
$fmt = tt("%s account blocked/unblocked", "%s accounts blocked/unblocked", $count);
$fmt = tt("%s account blocked/unblocked", "%s account blocked/unblocked", $count);
notice(sprintf($fmt, $count));
}

View File

@@ -24,9 +24,6 @@ class Security {
$cloud_disksize = ((x($_POST,'cloud_disksize')) ? 1 : 0);
Config::Set('system','cloud_report_disksize',$cloud_disksize);
$propfind_depth_infinity = ((x($_POST, 'propfind_depth_infinity')) ? 1 : 0);
Config::Set('system','propfind_depth_infinity', $propfind_depth_infinity);
$ws = $this->trim_array_elems(explode("\n",$_POST['whitelisted_sites']));
Config::Set('system','whitelisted_sites',$ws);
@@ -112,7 +109,6 @@ class Security {
'$block_public' => array('block_public', t("Block public"), Config::Get('system','block_public'), t("Check to block public access to all otherwise public personal pages on this site unless you are currently authenticated.")),
'$cloud_noroot' => [ 'cloud_noroot', t('Provide a cloud root directory'), 1 - intval(Config::Get('system','cloud_disable_siteroot')), t('The cloud root directory lists all channel names which provide public files') ],
'$cloud_disksize' => [ 'cloud_disksize', t('Show total disk space available to cloud uploads'), intval(Config::Get('system','cloud_report_disksize')), '' ],
'$propfind_depth_infinity' => ['propfind_depth_infinity', t('Allow propfind requests with infinity depth'), intval(Config::Get('system', 'propfind_depth_infinity')), t('Only turn this on if you know what you are doing')],
'$transport_security' => array('transport_security', t('Set "Transport Security" HTTP header'),intval(Config::Get('system','transport_security_header')),''),
'$content_security' => array('content_security', t('Set "Content Security Policy" HTTP header'),intval(Config::Get('system','content_security_policy')),''),
'$allowed_email' => array('allowed_email', t("Allowed email domains"), Config::Get('system','allowed_email'), t("Comma separated list of domains which are allowed in email addresses for registrations to this site. Wildcards are accepted. Empty to allow any domains")),

View File

@@ -32,9 +32,6 @@ class Apporder extends \Zotlabs\Web\Controller {
$syslist = Zlib\Apps::app_order(local_channel(),$syslist, $l);
$navbar_apps = [];
$nav_apps = [];
foreach($syslist as $app) {
if($l === 'nav_pinned_app') {
$navbar_apps[] = Zlib\Apps::app_render($app,'nav-order-pinned');

View File

@@ -82,7 +82,7 @@ class Attach_edit extends Controller {
$admin_delete = false;
$is_creator = (($creator == $observer_hash) ? true : false);
$move = ((!$delete && !$copy && ($folder !== $newfolder || (($single) ? $filename !== $newfilename : false))) ? true : false);
$move = ((! $copy && ($folder !== $newfolder || (($single) ? $filename !== $newfilename : false))) ? true : false);
$perms = get_all_perms($channel_id, $observer_hash);

View File

@@ -12,31 +12,28 @@ class Authorize extends \Zotlabs\Web\Controller {
}
else {
$name = $_GET['client_name'];
$name = $_REQUEST['client_name'];
if(! $name) {
$name = $_GET['client_id'] ?: t('Unknown App');
$name = (($_REQUEST['client_id']) ?: t('Unknown App'));
}
$app = [
'name' => escape_tags($name),
'icon' => (x($_GET, 'logo_uri') ? $_GET['logo_uri'] : z_root() . '/images/icons/plugin.png'),
'url' => (x($_GET, 'client_uri') ? $_GET['client_uri'] : ''),
'name' => $name,
'icon' => (x($_REQUEST, 'logo_uri') ? $_REQUEST['logo_uri'] : z_root() . '/images/icons/plugin.png'),
'url' => (x($_REQUEST, 'client_uri') ? $_REQUEST['client_uri'] : ''),
];
$link = $app['url']
? '<a style="float: none;" href="' . escape_url($app['url']) . '">' . $app['name'] . '</a> '
: $app['name'];
$link = (($app['url']) ? '<a style="float: none;" href="' . $app['url'] . '">' . $app['name'] . '</a> ' : $app['name']);
return replace_macros(get_markup_template('oauth_authorize.tpl'), [
'$title' => t('Authorize'),
'$security' => get_form_security_token('oauth_authorize'),
'$authorize' => sprintf( t('Do you authorize the app %s to access your channel data?'), $link ),
'$app' => $app,
'$yes' => t('Allow'),
'$no' => t('Deny'),
'$client_id' => (x($_GET, 'client_id') ? $_GET['client_id'] : ''),
'$redirect_uri' => (x($_GET, 'redirect_uri') ? $_GET['redirect_uri'] : ''),
'$state' => (x($_GET, 'state') ? $_GET['state'] : ''),
'$client_id' => (x($_REQUEST, 'client_id') ? $_REQUEST['client_id'] : ''),
'$redirect_uri' => (x($_REQUEST, 'redirect_uri') ? $_REQUEST['redirect_uri'] : ''),
'$state' => (x($_REQUEST, 'state') ? $_REQUEST['state'] : ''),
]);
}
}
@@ -46,10 +43,6 @@ class Authorize extends \Zotlabs\Web\Controller {
return;
}
if (! check_form_security_token('oauth_authorize')) {
http_status_exit(401, t('You are not authorized to perform this action.'));
}
$storage = new OAuth2Storage(\DBA::$dba->db);
$s = new \Zotlabs\Identity\OAuth2Server($storage);

View File

@@ -7,63 +7,63 @@ require_once('include/conversation.php');
class Block extends \Zotlabs\Web\Controller {
function init() {
$which = argv(1);
$profile = 0;
profile_load($which,$profile);
if(\App::$profile['profile_uid'])
head_set_icon(\App::$profile['thumb']);
}
function get() {
if(! perm_is_allowed(\App::$profile['profile_uid'],get_observer_hash(),'view_pages')) {
notice( t('Permission denied.') . EOL);
return;
}
if(argc() < 3) {
notice( t('Invalid item.') . EOL);
return;
}
$channel_address = argv(1);
$page_id = argv(2);
$u = q("select channel_id from channel where channel_address = '%s' limit 1",
dbesc($channel_address)
);
if(! $u) {
notice( t('Channel not found.') . EOL);
return;
}
if($_REQUEST['rev'])
$revision = " and revision = " . intval($_REQUEST['rev']) . " ";
else
$revision = " order by revision desc ";
require_once('include/security.php');
$sql_options = item_permissions_sql($u[0]['channel_id']);
$r = q("select item.* from item left join iconfig on item.id = iconfig.iid
where item.uid = %d and iconfig.cat = 'system' and iconfig.v = '%s' and iconfig.k = 'BUILDBLOCK' and
where item.uid = %d and iconfig.cat = 'system' and iconfig.v = '%s' and iconfig.k = 'BUILDBLOCK' and
item_type = %d $sql_options $revision limit 1",
intval($u[0]['channel_id']),
dbesc($page_id),
intval(ITEM_TYPE_BLOCK)
);
if(! $r) {
// Check again with no permissions clause to see if it is a permissions issue
$x = q("select item.* from item left join iconfig on item.id = iconfig.iid
where item.uid = %d and iconfig.cat = 'system' and iconfig.v = '%s' and iconfig.k = 'BUILDBLOCK' and
where item.uid = %d and iconfig.cat = 'system' and iconfig.v = '%s' and iconfig.k = 'BUILDBLOCK' and
item_type = %d $revision limit 1",
intval($u[0]['channel_id']),
dbesc($page_id),
@@ -78,12 +78,13 @@ class Block extends \Zotlabs\Web\Controller {
}
return;
}
xchan_query($r);
$r = fetch_post_tags($r,true);
return prepare_page($r[0]);
$o .= prepare_page($r[0]);
return $o;
}
}

View File

@@ -83,31 +83,37 @@ class Bookmarks extends \Zotlabs\Web\Controller {
$channel = \App::get_channel();
$bookmarks = [];
$x = menu_list(local_channel(), '', MENU_BOOKMARK);
if ($x) {
foreach ($x as $xx) {
$y = menu_fetch($xx['menu_name'], local_channel(), get_observer_hash());
$bookmarks[] = menu_render($y, '', true);
$o = '';
$o .= '<div class="generic-content-wrapper-styled">';
$o .= '<h3>' . t('Bookmarks') . '</h3>';
$x = menu_list(local_channel(),'',MENU_BOOKMARK);
if($x) {
foreach($x as $xx) {
$y = menu_fetch($xx['menu_name'],local_channel(),get_observer_hash());
$o .= menu_render($y,'',true);
}
}
$conn_bookmarks = [];
$x = menu_list(local_channel(), '', MENU_SYSTEM | MENU_BOOKMARK);
if ($x) {
foreach ($x as $xx) {
$y = menu_fetch($xx['menu_name'], local_channel(), get_observer_hash());
$conn_bookmarks[] = menu_render($y, '', true);
$o .= '<h3>' . t('My Connections Bookmarks') . '</h3>';
$x = menu_list(local_channel(),'',MENU_SYSTEM|MENU_BOOKMARK);
if($x) {
foreach($x as $xx) {
$y = menu_fetch($xx['menu_name'],local_channel(),get_observer_hash());
$o .= menu_render($y,'',true);
}
}
return replace_macros(get_markup_template('bookmarks.tpl'), [
'$title1' => t('Bookmarks'),
'$title2' => t('My Connections Bookmarks'),
'$bookmarks' => $bookmarks,
'$conn_bookmarks' => $conn_bookmarks,
]);
$o .= '</div>';
return $o;
}

View File

@@ -136,13 +136,15 @@ class Cal extends Controller {
}
$html = '';
$tz = get_iconfig($rr, 'event', 'timezone', 'UTC');
if (x($_GET,'id')) {
$rr['timezone'] = $tz;
$html = format_event_html($rr);
}
$tz = get_iconfig($rr, 'event', 'timezone');
if(! $tz)
$tz = 'UTC';
$events[] = array(
'calendar_id' => 'channel_calendar',
'rw' => true,

View File

@@ -1,7 +1,6 @@
<?php
namespace Zotlabs\Module;
use DBA;
class Changeaddr extends \Zotlabs\Web\Controller {
@@ -30,7 +29,7 @@ class Changeaddr extends \Zotlabs\Web\Controller {
if(! ($x && $x['account']))
return;
if($account['account_password_changed'] > DBA::$dba->get_null_date()) {
if($account['account_password_changed'] > NULL_DATE) {
$d1 = datetime_convert('UTC','UTC','now - 48 hours');
if($account['account_password_changed'] > $d1) {
notice( t('Channel name changes are not allowed within 48 hours of changing the account password.') . EOL);

View File

@@ -148,14 +148,14 @@ class Channel extends Controller {
'rel' => 'alternate',
'type' => 'application/atom+xml',
'title' => t('Posts and comments'),
'href' => z_root() . '/feed/' . $which . '?top=0'
'href' => z_root() . '/feed/' . $which
]);
head_add_link([
'rel' => 'alternate',
'type' => 'application/atom+xml',
'title' => t('Only posts'),
'href' => z_root() . '/feed/' . $which . '?top=1'
'href' => z_root() . '/feed/' . $which . '?f=&top=1'
]);
@@ -243,7 +243,7 @@ class Channel extends Controller {
// search terms header
if ($search) {
$o .= replace_macros(get_markup_template("section_title.tpl"), [
'$title' => t('Searching for:') . ' ' . htmlspecialchars($search, ENT_COMPAT, 'UTF-8')
'$title' => t('Search Results For:') . ' ' . htmlspecialchars($search, ENT_COMPAT, 'UTF-8')
]);
}
@@ -386,8 +386,8 @@ class Channel extends Controller {
if ($noscript_content || $load) {
if ($mid) {
$r = q("SELECT item.parent AS item_id, item.verb from item where $identifier = '%s' and item.uid = %d $item_normal
AND item.item_wall = 1 $permission_sql $sql_extra limit 1",
$r = q("SELECT *, parent AS item_id from item where $identifier = '%s' and uid = %d $item_normal
AND item_wall = 1 $permission_sql $sql_extra limit 1",
dbesc($mid),
intval(App::$profile['profile_uid'])
);
@@ -396,7 +396,7 @@ class Channel extends Controller {
}
}
else {
$r = q("SELECT item.parent AS item_id, item.verb, $ordering FROM item
$r = q("SELECT parent AS item_id, $ordering FROM item
LEFT JOIN abook ON (item.author_xchan = abook.abook_xchan $abook_uids)
WHERE item.uid = %d
AND item.id = item.parent
@@ -417,11 +417,6 @@ class Channel extends Controller {
}
}
if ($r) {
// 11.08.2025 start transition deprecated AS1 item.verb vocabulary to AS2 on demand.
// Keep this until we officially deprecate AS1 data.
AS1_to_AS2_verbs($r);
$thr_parents = null;
if ($mid) {
$thr_parents = get_recursive_thr_parents($r[0]);
@@ -444,7 +439,7 @@ class Channel extends Controller {
$items = [];
}
$mode = 'channel';
$mode = (($search) ? 'search' : 'channel');
if ((!$update) && (!$load)) {

View File

@@ -3,7 +3,6 @@
namespace Zotlabs\Module;
use App;
use DBA;
use Zotlabs\Web\Controller;
use Zotlabs\Lib\Libsync;
use Zotlabs\Access\AccessList;
@@ -301,7 +300,7 @@ class Channel_calendar extends Controller {
from event left join item on item.resource_id = event.event_hash
where event.uid = %d and event.dtstart > '%s' and event.dtend > event.dtstart",
intval(local_channel()),
dbesc(DBA::$dba->get_null_date())
dbesc(NULL_DATE)
);
}
else {

View File

@@ -33,20 +33,26 @@ class Cloud extends Controller {
*/
function init() {
if (!is_dir('store')) {
os_mkdir('store', STORAGE_DEFAULT_PERMISSIONS, false);
// TODO: why is this required?
// if we arrived at this path with any query parameters in the url, build a clean url without
// them and redirect.
$parsed = parse_url(App::$query_string);
if (!empty($parsed['query'])) {
goaway(z_root() . '/' . $parsed['path']);
}
if (! is_dir('store'))
os_mkdir('store', STORAGE_DEFAULT_PERMISSIONS, false);
$which = null;
if (argc() > 1) {
if (argc() > 1)
$which = argv(1);
}
$profile = 0;
if ($which) {
if ($which)
profile_load( $which, $profile);
}
$auth = new BasicAuth();
@@ -65,7 +71,7 @@ class Cloud extends Controller {
$auth->observer = $ob_hash;
}
if (!array_key_exists('cloud_sort',$_SESSION)) {
if(! array_key_exists('cloud_sort',$_SESSION)) {
$_SESSION['cloud_sort'] = 'name';
}
@@ -93,6 +99,7 @@ class Cloud extends Controller {
// require_once('\Zotlabs\Storage/QuotaPlugin.php');
// $server->addPlugin(new \Zotlabs\Storage\\QuotaPlugin($auth));
// over-ride the default XML output on thrown exceptions
$server->on('exception', [ $this, 'DAVException' ]);
@@ -100,9 +107,8 @@ class Cloud extends Controller {
$server->start();
if ($browser->build_page) {
if($browser->build_page)
construct_page();
}
killme();
}

View File

@@ -10,7 +10,6 @@ namespace Zotlabs\Module;
use Sabre\DAV as SDAV;
use Zotlabs\Lib\Libzot;
use Zotlabs\Lib\Config;
use Zotlabs\Storage;
use Zotlabs\Web\HTTPSig;
@@ -107,16 +106,20 @@ class Dav extends \Zotlabs\Web\Controller {
// A SabreDAV server-object
$server = new SDAV\Server($rootDirectory);
$authPlugin = new \Sabre\DAV\Auth\Plugin($auth);
$server->addPlugin($authPlugin);
// prevent overwriting changes each other with a lock backend
$lockBackend = new SDAV\Locks\Backend\File('store/[data]/locks');
$lockPlugin = new SDAV\Locks\Plugin($lockBackend);
$server->addPlugin($lockPlugin);
$server->enablePropfindDepthInfinity = Config::Get('system', 'propfind_depth_infinity', false);
// provide a directory view for the cloud in Hubzilla
$browser = new \Zotlabs\Storage\Browser($auth);
$auth->setBrowserPlugin($browser);
// Experimental QuotaPlugin
// $server->addPlugin(new \Zotlabs\Storage\QuotaPlugin($auth));

View File

@@ -2,7 +2,6 @@
namespace Zotlabs\Module;
use App;
use DBA;
use Zotlabs\Lib\Config;
use Zotlabs\Web\Controller;
@@ -233,7 +232,7 @@ class Dirsearch extends Controller {
$spkt = array('transactions' => array());
$r = q("SELECT * FROM updates WHERE ud_update = 0 AND ud_last = '%s' AND ud_date >= '%s' ORDER BY ud_date DESC",
dbesc(DBA::$dba->get_null_date()),
dbesc(NULL_DATE),
dbesc($sync)
);

View File

@@ -61,10 +61,12 @@ class Dreport extends \Zotlabs\Web\Controller {
return;
}
$r = q("select * from dreport where dreport_xchan = '%s' and (dreport_mid = '%s' or dreport_mid = '%s')",
$r = q("select * from dreport where dreport_xchan = '%s' and (dreport_mid = '%s' or dreport_mid = '%s' or dreport_mid = '%s' or dreport_mid = '%s')",
dbesc($channel['channel_hash']),
dbesc($mid),
dbesc(str_replace('/item/', '/activity/', $mid))
dbesc($mid . '#sync'),
dbesc(str_replace('/item/', '/activity/', $mid)),
dbesc(str_replace('/item/', '/activity/', $mid) . '#sync')
);
if(! $r) {

View File

@@ -122,10 +122,10 @@ class Editblock extends \Zotlabs\Web\Controller {
'ptyp' => $itm[0]['type'],
'mimeselect' => true,
'mimetype' => $itm[0]['mimetype'],
'body' => htmlspecialchars_decode(undo_post_tagging($content), ENT_COMPAT),
'body' => undo_post_tagging($content),
'post_id' => $post_id,
'visitor' => true,
'title' => htmlspecialchars_decode($itm[0]['title'], ENT_COMPAT),
'title' => htmlspecialchars($itm[0]['title'],ENT_COMPAT,'UTF-8'),
'placeholdertitle' => t('Title (optional)'),
'pagetitle' => $block_title,
'profile_uid' => (intval($channel['channel_id'])),

View File

@@ -121,9 +121,9 @@ class Editlayout extends \Zotlabs\Web\Controller {
'hide_preview' => true,
'disable_comments' => true,
'ptyp' => $itm[0]['obj_type'],
'body' => htmlspecialchars_decode(undo_post_tagging($itm[0]['body']), ENT_COMPAT),
'body' => undo_post_tagging($itm[0]['body']),
'post_id' => $post_id,
'title' => htmlspecialchars_decode($itm[0]['title'], ENT_COMPAT),
'title' => htmlspecialchars($itm[0]['title'],ENT_COMPAT,'UTF-8'),
'pagetitle' => $layout_title,
'ptlabel' => t('Layout Name'),
'placeholdertitle' => t('Layout Description (Optional)'),

View File

@@ -144,7 +144,7 @@ class Editwebpage extends \Zotlabs\Web\Controller {
'hide_location' => true,
'hide_voting' => true,
'ptyp' => $itm[0]['type'],
'body' => htmlspecialchars_decode(undo_post_tagging($content), ENT_COMPAT),
'body' => undo_post_tagging($content),
'post_id' => $post_id,
'visitor' => ($is_owner) ? true : false,
'acl' => populate_acl($itm[0],false,\Zotlabs\Lib\PermissionDescription::fromGlobalPermission('view_pages')),
@@ -154,7 +154,7 @@ class Editwebpage extends \Zotlabs\Web\Controller {
'mimeselect' => true,
'layout' => $layout,
'layoutselect' => true,
'title' => htmlspecialchars_decode($itm[0]['title'], ENT_COMPAT),
'title' => htmlspecialchars($itm[0]['title'],ENT_COMPAT,'UTF-8'),
'lockstate' => (((strlen($itm[0]['allow_cid'])) || (strlen($itm[0]['allow_gid'])) || (strlen($itm[0]['deny_cid'])) || (strlen($itm[0]['deny_gid']))) ? 'lock' : 'unlock'),
'profile_uid' => (intval($owner)),
'bbcode' => (($mimetype == 'text/bbcode') ? true : false)

22
Zotlabs/Module/Embed.php Normal file
View File

@@ -0,0 +1,22 @@
<?php
namespace Zotlabs\Module;
require_once('include/security.php');
require_once('include/bbcode.php');
class Embed extends \Zotlabs\Web\Controller {
function init() {
$post_id = ((argc() > 1) ? intval(argv(1)) : 0);
if(! $post_id)
killme();
echo '[share=' . $post_id . '][/share]';
killme();
}
}

View File

@@ -2,47 +2,49 @@
namespace Zotlabs\Module;
use DBA;
use Zotlabs\Lib\PConfig;
use Zotlabs\Web\Controller;
class Feed extends Controller {
require_once('include/items.php');
class Feed extends \Zotlabs\Web\Controller {
function init() {
if (argc() < 2) {
killme();
}
$params = [];
$params['begin'] = ((x($_REQUEST,'date_begin')) ? $_REQUEST['date_begin'] : NULL_DATE);
$params['end'] = ((x($_REQUEST,'date_end')) ? $_REQUEST['date_end'] : '');
$params['type'] = ((stristr(argv(0),'json')) ? 'json' : 'xml');
$params['pages'] = ((x($_REQUEST,'pages')) ? intval($_REQUEST['pages']) : 0);
$params['top'] = ((x($_REQUEST,'top')) ? intval($_REQUEST['top']) : 0);
$params['start'] = ((x($_REQUEST,'start')) ? intval($_REQUEST['start']) : 0);
$params['records'] = ((x($_REQUEST,'records')) ? intval($_REQUEST['records']) : 10);
$params['direction'] = ((x($_REQUEST,'direction')) ? dbesc($_REQUEST['direction']) : 'desc');
$params['cat'] = ((x($_REQUEST,'cat')) ? escape_tags($_REQUEST['cat']) : '');
$params['compat'] = ((x($_REQUEST,'compat')) ? intval($_REQUEST['compat']) : 0);
if (observer_prohibited(true)) {
killme();
}
$channel = channelx_by_nick(argv(1));
if (!$channel) {
killme();
}
$params['begin'] = $_REQUEST['date_begin'] ?? DBA::$dba->get_null_date();
$params['end'] = $_REQUEST['date_end'] ?? '';
$params['type'] = 'xml';
$params['pages'] = ((!empty($_REQUEST['pages'])) ? intval($_REQUEST['pages']) : 0);
$params['top'] = ((array_key_exists('top', $_REQUEST)) ? intval($_REQUEST['top']) : PConfig::Get($channel['channel_id'], 'system', 'channel_simple_feed', 1));
$params['start'] = ((!empty($_REQUEST['start'])) ? intval($_REQUEST['start']) : 0);
$params['records'] = ((!empty($_REQUEST['records'])) ? intval($_REQUEST['records']) : 10);
$params['cat'] = ((!empty($_REQUEST['cat'])) ? escape_tags($_REQUEST['cat']) : '');
$params['compat'] = ((!empty($_REQUEST['compat'])) ? intval($_REQUEST['compat']) : 0);
$params['direction'] = ((!empty($_REQUEST['direction'])) ? dbesc($_REQUEST['direction']) : 'desc');
if (!in_array($params['direction'], ['asc', 'desc'])) {
if(! in_array($params['direction'],['asc','desc'])) {
$params['direction'] = 'desc';
}
logger('public feed request from ' . $_SERVER['REMOTE_ADDR'] . ' for ' . $channel['channel_address']);
if(argc() > 1) {
echo get_public_feed($channel, $params);
killme();
if(observer_prohibited(true)) {
killme();
}
$channel = channelx_by_nick(argv(1));
if(! $channel) {
killme();
}
logger('public feed request from ' . $_SERVER['REMOTE_ADDR'] . ' for ' . $channel['channel_address']);
echo get_public_feed($channel,$params);
killme();
}
}
}

View File

@@ -53,14 +53,7 @@ class Follow extends Controller {
}
$uid = local_channel();
$url = notags(trim($_REQUEST['url']));
$parsed = parse_url($url);
if (isset($parsed['host'])) {
$parsed['host'] = punify($parsed['host']);
$url = unparse_url($parsed);
}
$url = notags(punify(trim($_REQUEST['url'])));
$return_url = $_SESSION['return_url'];
$interactive = $_REQUEST['interactive'] ?? 1;
$channel = App::get_channel();

View File

@@ -104,15 +104,26 @@ class Home extends Controller {
goaway($frontpage);
}
$sitename = Config::Get('system', 'sitename', 'Hubzilla');
$welcome = sprintf(t('Welcome to %s'), $sitename);
$login_on_homepage = Config::Get('system', 'login_on_homepage');
$o .= '<div class="generic-content-wrapper">';
$tpl = get_markup_template('home.tpl');
return replace_macros($tpl, [
'welcome' => $welcome,
'loginbox' => $login_on_homepage ? login(true) : false,
]);
$sitename = Config::Get('system', 'sitename');
if ($sitename) {
$o .= '<div class="section-title-wrapper">';
$o .= '<h2 class="">' . sprintf(t('Welcome to %s'), $sitename) . '</h2>';
$o .= '</div>';
}
$o .= '<div class="section-content-wrapper">';
$loginbox = Config::Get('system', 'login_on_homepage');
if (intval($loginbox) || $loginbox === false)
$o .= login(true);
$o .= '</div>';
$o .= '</div>';
return $o;
}

View File

@@ -7,7 +7,6 @@ require_once('include/import.php');
require_once('include/perm_upgrade.php');
use App;
use DBA;
use URLify;
use Zotlabs\Daemon\Master;
use Zotlabs\Lib\Config;
@@ -332,7 +331,7 @@ class Import extends Controller {
else {
$photos = import_xchan_photo($xchan['xchan_photo_l'], $xchan['xchan_hash']);
if ($photos[4])
$photodate = DBA::$dba->get_null_date();
$photodate = NULL_DATE;
else
$photodate = $xchan['xchan_photo_date'];

View File

@@ -3,7 +3,6 @@
namespace Zotlabs\Module;
use App;
use DBA;
use URLify;
use Zotlabs\Lib\Config;
use Zotlabs\Lib\IConfig;
@@ -18,7 +17,6 @@ use Zotlabs\Lib\Libzot;
use Zotlabs\Lib\Libsync;
use Zotlabs\Lib\ThreadListener;
use Zotlabs\Access\PermissionRoles;
use Zotlabs\Lib\ObjCache;
require_once('include/crypto.php');
require_once('include/items.php');
@@ -187,8 +185,7 @@ class Item extends Controller {
$obj_type = ((!empty($_POST['obj_type'])) ? escape_tags($_POST['obj_type']) : 'Note');
// allow API to bulk load a bunch of imported items with sending out a bunch of posts.
$nopush = ((!empty($_POST['nopush'])) ? intval($_POST['nopush']) : $item_type !== ITEM_TYPE_POST);
$nopush = ((!empty($_POST['nopush'])) ? intval($_POST['nopush']) : 0);
/*
* Check service class limits
@@ -210,7 +207,7 @@ class Item extends Controller {
}
$expires = DBA::$dba->get_null_date();
$expires = NULL_DATE;
$route = '';
$parent_item = null;
@@ -560,7 +557,7 @@ class Item extends Controller {
if (!empty($_POST['expire'])) {
$expires = datetime_convert(date_default_timezone_get(), 'UTC', $_POST['expire']);
if ($expires <= datetime_convert())
$expires = DBA::$dba->get_null_date();
$expires = NULL_DATE;
}
}
@@ -758,7 +755,7 @@ class Item extends Controller {
$cats = explode(',', $categories);
foreach ($cats as $cat) {
$catlink = channel_url($channel) . '?cat=' . urlencode(trim($cat));
$catlink = $owner_xchan['xchan_url'] . '?f=&cat=' . urlencode(trim($cat));
$post_tags[] = [
'uid' => $profile_uid,
@@ -802,7 +799,7 @@ class Item extends Controller {
$item_origin = (($origin) ? 1 : 0);
$item_consensus = (($consensus) ? 1 : 0);
$item_nocomment = (($nocomment) ? 1 : 0);
$comments_closed = (($nocomment) ? $comments_closed : DBA::$dba->get_null_date());
$comments_closed = (($nocomment) ? $comments_closed : NULL_DATE);
// determine if this is a wall post
@@ -876,7 +873,7 @@ class Item extends Controller {
if ($obj['endTime']) {
$d = datetime_convert('UTC','UTC', $obj['endTime']);
if ($d > DBA::$dba->get_null_date()) {
if ($d > NULL_DATE) {
$comments_closed = $d;
}
}
@@ -1044,16 +1041,23 @@ class Item extends Controller {
$x = item_store_update($datarray, $execflag);
if ($x['success'] && intval($item_type) === ITEM_TYPE_POST) {
$item = [$x['item']];
xchan_query($item);
$item = fetch_post_tags($item);
$encoded_item = Activity::build_packet(Activity::encode_activity($item[0]), $channel, false);
ObjCache::Set($item[0]['mid'], $encoded_item);
if ($x['success']) {
$this->add_listeners($datarray);
}
/* sync this is done in item_store_update()
if (!$parent) {
$r = q("select * from item where id = %d",
intval($post_id)
);
if ($r) {
xchan_query($r);
$sync_item = fetch_post_tags($r);
Libsync::build_sync_packet($profile_uid, ['item' => [encode_item($sync_item[0], true)]]);
}
}
*/
if (!$nopush) {
Master::Summon(['Notifier', 'edit_post', $post_id]);
if (intval($x['approval_id'])) {
@@ -1076,17 +1080,9 @@ class Item extends Controller {
killme();
}
$post = item_store($datarray, $execflag);
if ($post['success'] && intval($item_type) === ITEM_TYPE_POST) {
$item = [$post['item']];
xchan_query($item);
// TODO: fetch_post_tags() will add term and iconfig twice if called twice and it looks like they are already added here
//$item = fetch_post_tags($item);
$encoded_item = Activity::build_packet(Activity::encode_activity($item[0]), $channel, false);
ObjCache::Set($item[0]['mid'], $encoded_item);
if ($post['success']) {
$this->add_listeners($datarray);
}
@@ -1166,6 +1162,19 @@ class Item extends Controller {
killme();
}
/* sync this is done in item_store_update()
if ($parent || $datarray['item_private'] == 1) {
$r = q("select * from item where id = %d",
intval($post_id)
);
if ($r) {
xchan_query($r);
$sync_item = fetch_post_tags($r);
Libsync::build_sync_packet($profile_uid, ['item' => [encode_item($sync_item[0], true)]]);
}
}
*/
$datarray['id'] = $post_id;
$datarray['llink'] = z_root() . '/display/' . $datarray['uuid'];
@@ -1204,6 +1213,11 @@ class Item extends Controller {
if ($mode === 'channel')
profile_load($channel['channel_address']);
$item[] = $datarray;
$item[0]['owner'] = $owner_xchan;
$item[0]['author'] = $observer;
$item[0]['attach'] = $datarray['attach'];
$json = [
'success' => 1,
'id' => $post_id,

View File

@@ -433,7 +433,7 @@ class Like extends Controller {
$arr['item_wall'] = 1;
}
else {
switch ($item['obj_type']) {
switch ($item['object_type']) {
case 'Image':
$post_type = t('image');
break;

View File

@@ -110,7 +110,7 @@ class Lockview extends Controller {
// as unknown specific recipients. The sender will have the visibility list and will fall through to the
// next section.
echo '<div class="dropdown-item-text">' . escape_tags(translate_scope((!$item['public_policy']) ? 'specific' : $item['public_policy'])) . '</div>';
echo '<div class="dropdown-item-text">' . translate_scope((!$item['public_policy']) ? 'specific' : $item['public_policy']) . '</div>';
killme();
}
@@ -232,17 +232,25 @@ class Lockview extends Controller {
}
}
$tpl = get_markup_template('access_dropdown.tpl');
$access_list_header = '<div class="dropdown-header text-uppercase h6">' . t('Access') . '</div>';
$guest_access_list_header = '<div class="dropdown-header text-uppercase h6">' . t('Guest access') . '</div>';
$ocap_access_list_header = '<div class="dropdown-header text-uppercase h6">' . t('OCAP access') . '</div>';
$divider = '<div class="dropdown-divider"></div>';
$str = '';
echo replace_macros($tpl, [
'$access_header' => t('Access'),
'$guest_access_header' => t('Guest access'),
'$ocap_access_header' => t('OCAP access'),
if ($access_list) {
$str .= $access_list_header . implode($access_list);
}
'$access_list' => $access_list ? implode($access_list) : '',
'$guest_access_list' => $guest_access_list ? implode($guest_access_list) : '',
'$ocap_access_list' => $ocap_access_list ? implode($ocap_access_list) : '',
]);
if ($guest_access_list) {
$str .= $divider . $guest_access_list_header . implode($guest_access_list);
}
if ($ocap_access_list) {
$str .= $divider . $ocap_access_list_header . implode($ocap_access_list);
}
echo $str;
killme();
}

View File

@@ -6,8 +6,6 @@ use Zotlabs\Web\Controller;
use Zotlabs\Web\HTTPSig;
use Zotlabs\Lib\Libzot;
use Zotlabs\Lib\SConfig;
use GuzzleHttp\Psr7\Request;
use HttpSignature\HttpMessageSigner;
class Magic extends Controller {
@@ -103,65 +101,26 @@ class Magic extends Controller {
$dest = strip_zids($dest);
$dest = strip_query_param($dest,'f');
// try RFC9421 first
// We now post to the OWA endpoint. This improves security by providing a signed digest
$request = new Request(
'GET',
$owapath,
[
'Host' => $parsed['host'],
'Date' => gmdate('D, d M Y H:i:s T'),
'Accept' => 'application/x-zot+json',
'X-Open-Web-Auth' => random_string(),
],
);
$data = json_encode([ 'OpenWebAuth' => random_string() ]);
$signer = new HttpMessageSigner();
$signer->setPrivateKey($channel['channel_prvkey']);
$signer->setAlgorithm('rsa-v1_5-sha256');
$signer->setKeyId(channel_url($channel));
$signer->setCreated(time());
$signer->setExpires(time() + 3600);
$coveredFields = '("@method" "@target-uri" "host" "date" "accept" "x-open-web-auth")';
$request = $signer->signRequest($coveredFields, $request);
$signedHeaders = $signer->getHeaders($request);
$curlHeaders = [];
foreach ($signedHeaders as $key => $value) {
$curlHeaders[] = $key . ': ' . $value;
}
$headers = [];
$headers['Accept'] = 'application/x-zot+json' ;
$headers['Content-Type'] = 'application/x-zot+json' ;
$headers['X-Open-Web-Auth'] = random_string();
$headers['Host'] = $parsed['host'];
$headers['(request-target)'] = 'get /owa';
$headers = HTTPSig::create_sig($headers,$channel['channel_prvkey'], channel_url($channel),true,'sha512');
$redirects = 0;
$x = z_fetch_url($owapath, false, $redirects, ['headers' => $curlHeaders]);
logger('owa RFC9421 fetch returned: ' . print_r($x,true),LOGGER_DATA);
$rfc9421 = false;
$x = z_fetch_url($owapath, false, $redirects, ['headers' => $headers]);
if ($x['success']) {
$rfc9421_result = json_decode($x['body'], true);
$rfc9421 = $rfc9421_result['success'];
}
if (!$rfc9421 || ($x['return_code'] >= 400 && $x['return_code'] != 404)) {
$headers = [];
$headers['Accept'] = 'application/x-zot+json' ;
$headers['Content-Type'] = 'application/x-zot+json' ;
$headers['X-Open-Web-Auth'] = random_string();
$headers['Host'] = $parsed['host'];
$headers['(request-target)'] = 'get /owa';
$headers = HTTPSig::create_sig($headers,$channel['channel_prvkey'], channel_url($channel),true,'sha512');
$redirects = 0;
$x = z_fetch_url($owapath, false, $redirects, ['headers' => $headers]);
logger('owa fetch returned: ' . print_r($x,true),LOGGER_DATA);
}
logger('owa fetch returned: ' . print_r($x,true),LOGGER_DATA);
if ($x['success']) {
$j = json_decode($x['body'],true);
if ($j['success'] && $j['encrypted_token']) {
// decrypt the token using our private key
$token = '';
@@ -180,6 +139,7 @@ class Magic extends Controller {
echo $o;
killme();
}
}
}

View File

@@ -135,6 +135,7 @@ class Network extends \Zotlabs\Web\Controller {
$status_editor = '';
if (Apps::system_app_installed(local_channel(), 'Affinity Tool')) {
$affinity_locked = intval(get_pconfig(local_channel(), 'affinity', 'lock', 1));
if ($affinity_locked) {
@@ -143,11 +144,8 @@ class Network extends \Zotlabs\Web\Controller {
}
}
if($search || $file || (!$pf && $cid) || $hashtags || $verb || $category || $conv || $unseen) {
if(x($_GET, 'search') || $file || (!$pf && $cid) || $hashtags || $verb || $category || $conv || $unseen)
$nouveau = true;
}
$dismiss_privacy_filter = array_intersect(['cid', 'star', 'conv', 'file', 'verb', 'cat', 'search'], array_keys($_GET));
$cid_r = [];
@@ -275,6 +273,18 @@ class Network extends \Zotlabs\Web\Controller {
// This is for nouveau view cid queries (not a public forum)
$sql_extra = " AND author_xchan = '" . dbesc($cid_r[0]['abook_xchan']) . "' ";
}
elseif($pf && $unseen && $nouveau) {
$vnotify = get_pconfig(local_channel(), 'system', 'vnotify');
$likes_sql = '';
if (!($vnotify & VNOTIFY_LIKE)) {
$likes_sql = " AND verb NOT IN ('Like', 'Dislike', '" . dbesc(ACTIVITY_LIKE) . "', '" . dbesc(ACTIVITY_DISLIKE) . "') ";
}
// This is for nouveau view public forum cid queries (if a forum notification is clicked)
$sql_extra = " AND item.parent IN (SELECT DISTINCT parent FROM item WHERE uid = " . intval(local_channel()) . " AND ( author_xchan = '" . dbesc($cid_r[0]['abook_xchan']) . "' OR owner_xchan = '" . dbesc($cid_r[0]['abook_xchan']) . "' ) $item_normal) AND item_unseen = 1 AND verb != 'Announce' $likes_sql ";
}
else {
// This is for threaded view cid queries (e.g. if a forum is selected from the forum filter)
$sql_extra = " AND item.parent IN (SELECT DISTINCT parent FROM item WHERE uid = " . intval(local_channel()) . " AND ( author_xchan = '" . dbesc($cid_r[0]['abook_xchan']) . "' OR owner_xchan = '" . dbesc($cid_r[0]['abook_xchan']) . "' ) $item_normal) ";
@@ -349,15 +359,15 @@ class Network extends \Zotlabs\Web\Controller {
// The name 'verb' is a holdover from the earlier XML
// ActivityStreams specification.
if (str_starts_with($verb, '.')) {
if (substr($verb, 0, 1) === '.') {
$sql_verb = substr($verb, 1);
$sql_extra .= sprintf(" AND item.obj_type = '%s' AND item.verb IN ('Create', 'Update', 'Invite') ",
dbesc(protect_sprintf($sql_verb))
$sql_extra .= sprintf(" AND item.obj_type like '%s' ",
dbesc(protect_sprintf('%' . $sql_verb . '%'))
);
}
else {
$sql_extra .= sprintf(" AND item.verb = '%s' ",
dbesc(protect_sprintf($verb))
$sql_extra .= sprintf(" AND item.verb like '%s' ",
dbesc(protect_sprintf('%' . $verb . '%'))
);
}
}
@@ -366,23 +376,30 @@ class Network extends \Zotlabs\Web\Controller {
$sql_extra .= term_query('item', $file, TERM_FILE);
}
if (!$dismiss_privacy_filter) {
if ($dm) {
$sql_extra .= ' AND item.item_private = 2 ';
}
else {
$sql_extra .= ' AND item.item_private IN (0, 1) ';
}
if ($dm) {
$sql_extra .= ' AND item.item_private = 2 ';
}
else {
$sql_extra .= ' AND item.item_private IN (0, 1) ';
}
if($conv) {
$item_thread_top = '';
$sql_extra .= " AND ( author_xchan = '" . dbesc($channel['channel_hash']) . "' OR item_mentionsme = 1 ) ";
}
$itemspage = get_pconfig(local_channel(), 'system', 'itemspage');
App::set_pager_itemspage(((intval($itemspage)) ? $itemspage : 10));
$pager_sql = sprintf(" LIMIT %d OFFSET %d ", intval(App::$pager['itemspage']), intval(App::$pager['start']));
if($update && ! $load) {
// only setup pagination on initial page view
$pager_sql = '';
}
else {
$itemspage = get_pconfig(local_channel(), 'system', 'itemspage');
App::set_pager_itemspage(((intval($itemspage)) ? $itemspage : 10));
$pager_sql = sprintf(" LIMIT %d OFFSET %d ", intval(App::$pager['itemspage']), intval(App::$pager['start']));
}
// cmin and cmax are both -1 when the affinity tool is disabled
@@ -419,6 +436,8 @@ class Network extends \Zotlabs\Web\Controller {
$page_mode = 'list';
}
$parents_str = '';
// This fixes a very subtle bug so I'd better explain it. You wake up in the morning or return after a day
// or three and look at your matrix page - after opening up your browser. The first page loads just as it
// should. All of a sudden a few seconds later, page 2 will get inserted at the beginning of the page
@@ -450,6 +469,10 @@ class Network extends \Zotlabs\Web\Controller {
ORDER BY item.created DESC $pager_sql "
);
$parents_str = ids_to_querystr($items, 'item_id');
require_once('include/items.php');
xchan_query($items);
$items = fetch_post_tags($items, true);

View File

@@ -33,7 +33,7 @@ class New_channel extends \Zotlabs\Web\Controller {
// first name
if(strpos($x,' '))
$test[] = legal_webbie(substr($x,0,strpos($x,' ')));
if (!empty($test[0])) {
if($test[0]) {
// first name plus first initial of last
$test[] = ((strpos($x,' ')) ? $test[0] . legal_webbie(trim(substr($x,strpos($x,' '),2))) : '');
// first name plus random number
@@ -69,7 +69,7 @@ class New_channel extends \Zotlabs\Web\Controller {
// first name
if(strpos($x,' '))
$test[] = legal_webbie(substr($x,0,strpos($x,' ')));
if (!empty($test[0])) {
if($test[0]) {
// first name plus first initial of last
$test[] = ((strpos($x,' ')) ? $test[0] . legal_webbie(trim(substr($x,strpos($x,' '),2))) : '');
// first name plus random number

View File

@@ -12,72 +12,51 @@ class Notifications extends \Zotlabs\Web\Controller {
}
// ajax mark all unseen items read
if(isset($_REQUEST['markRead'])) {
if (str_starts_with($_REQUEST['markRead'], 'forum_')) {
$forum_id = substr($_REQUEST['markRead'], 6);
$abook = q("SELECT abook_xchan FROM abook WHERE abook_channel = %d AND abook_id = %d",
intval(local_channel()),
intval($forum_id)
);
if ($abook) {
q("UPDATE item SET item_unseen = 0 WHERE uid = %d AND owner_xchan = '%s' AND item_unseen = 1 AND item_wall = 0 AND item_private IN (0, 1)",
intval(local_channel()),
dbesc($abook[0]['abook_xchan'])
);
}
killme();
}
if(x($_REQUEST, 'markRead')) {
switch($_REQUEST['markRead']) {
case 'dm':
q("UPDATE item SET item_unseen = 0 WHERE uid = %d AND item_unseen = 1 AND item_private = 2",
$r = q("UPDATE item SET item_unseen = 0 WHERE uid = %d AND item_unseen = 1 AND item_private = 2",
intval(local_channel())
);
break;
case 'network':
q("UPDATE item SET item_unseen = 0 WHERE uid = %d AND item_unseen = 1 AND item_wall = 0 AND item_private IN (0, 1)",
$r = q("UPDATE item SET item_unseen = 0 WHERE uid = %d AND item_unseen = 1 AND item_private IN (0, 1)",
intval(local_channel())
);
break;
case 'home':
q("UPDATE item SET item_unseen = 0 WHERE uid = %d AND item_unseen = 1 AND item_wall = 1 AND item_private IN (0, 1)",
$r = q("UPDATE item SET item_unseen = 0 WHERE uid = %d AND item_unseen = 1 AND item_wall = 1 AND item_private IN (0, 1)",
intval(local_channel())
);
break;
case 'all_events':
$evdays = intval(get_pconfig(local_channel(), 'system', 'evdays', 3));
q("UPDATE event SET dismissed = 1 WHERE uid = %d AND dismissed = 0 AND dtstart < '%s' AND dtstart > '%s' ",
$r = q("UPDATE event SET dismissed = 1 WHERE uid = %d AND dismissed = 0 AND dtstart < '%s' AND dtstart > '%s' ",
intval(local_channel()),
dbesc(datetime_convert('UTC', date_default_timezone_get(), 'now + ' . intval($evdays) . ' days')),
dbesc(datetime_convert('UTC', date_default_timezone_get(), 'now - 1 days'))
);
break;
case 'notify':
q("UPDATE notify SET seen = 1 WHERE seen = 0 AND uid = %d",
$r = q("UPDATE notify SET seen = 1 WHERE seen = 0 AND uid = %d",
intval(local_channel())
);
break;
case 'pubs':
$_SESSION['sse_loadtime'] = datetime_convert();
$_SESSION['static_loadtime'] = datetime_convert();
unset($_SESSION['static_loadtime']);
break;
default:
break;
}
killme();
}
// ajax mark all comments of a parent item read
if(isset($_REQUEST['markItemRead']) && local_channel()) {
if(x($_REQUEST, 'markItemRead') && local_channel()) {
$r = q("UPDATE item SET item_unseen = 0 WHERE uid = %d AND parent = %d",
intval(local_channel()),
intval($_REQUEST['markItemRead'])
);
killme();
}

View File

@@ -0,0 +1,69 @@
<?php
namespace Zotlabs\Module;
require_once('include/contact_widgets.php');
require_once('include/items.php');
require_once("include/bbcode.php");
require_once('include/security.php');
require_once('include/conversation.php');
require_once('include/acl_selectors.php');
require_once('include/permissions.php');
/**
* @brief Channel Controller for broken OStatus implementations
*
*/
class Ochannel extends \Zotlabs\Web\Controller {
function init() {
$which = null;
if(argc() > 1)
$which = argv(1);
if(! $which) {
if(local_channel()) {
$channel = \App::get_channel();
if($channel && $channel['channel_address'])
$which = $channel['channel_address'];
}
}
if(! $which) {
notice( t('You must be logged in to see this page.') . EOL );
return;
}
$profile = 0;
$channel = \App::get_channel();
if((local_channel()) && (argc() > 2) && (argv(2) === 'view')) {
$which = $channel['channel_address'];
$profile = argv(1);
}
head_add_link( [
'rel' => 'alternate',
'type' => 'application/atom+xml',
'href' => z_root() . '/ofeed/' . $which
]);
// Run profile_load() here to make sure the theme is set before
// we start loading content
profile_load($which,$profile);
}
function get($update = 0, $load = false) {
if(argc() < 2)
return;
if($load)
$_SESSION['loadtime'] = datetime_convert();
return '<script>window.location.href = "' . z_root() . '/' . str_replace('ochannel/','channel/',\App::$query_string) . '";</script>';
}
}

51
Zotlabs/Module/Ofeed.php Normal file
View File

@@ -0,0 +1,51 @@
<?php
namespace Zotlabs\Module;
/* Ofeed: Broken feed for software which requires broken feeds */
require_once('include/items.php');
class Ofeed extends \Zotlabs\Web\Controller {
function init() {
$params = [];
$params['begin'] = ((x($_REQUEST,'date_begin')) ? $_REQUEST['date_begin'] : NULL_DATE);
$params['end'] = ((x($_REQUEST,'date_end')) ? $_REQUEST['date_end'] : '');
$params['type'] = ((stristr(argv(0),'json')) ? 'json' : 'xml');
$params['pages'] = ((x($_REQUEST,'pages')) ? intval($_REQUEST['pages']) : 0);
$params['top'] = ((x($_REQUEST,'top')) ? intval($_REQUEST['top']) : 0);
$params['start'] = ((x($_REQUEST,'start')) ? intval($_REQUEST['start']) : 0);
$params['records'] = ((x($_REQUEST,'records')) ? intval($_REQUEST['records']) : 10);
$params['direction'] = ((x($_REQUEST,'direction')) ? dbesc($_REQUEST['direction']) : 'desc');
$params['cat'] = ((x($_REQUEST,'cat')) ? escape_tags($_REQUEST['cat']) : '');
$params['compat'] = ((x($_REQUEST,'compat')) ? intval($_REQUEST['compat']) : 1);
if(! in_array($params['direction'],['asc','desc'])) {
$params['direction'] = 'desc';
}
if(argc() > 1) {
if(observer_prohibited(true)) {
killme();
}
$channel = channelx_by_nick(argv(1));
if(! $channel) {
killme();
}
logger('public feed request from ' . $_SERVER['REMOTE_ADDR'] . ' for ' . $channel['channel_address']);
echo get_public_feed($channel,$params);
killme();
}
}
}

View File

@@ -3,7 +3,6 @@
namespace Zotlabs\Module;
use App;
use DBA;
use Zotlabs\Lib\Activity;
use Zotlabs\Lib\ActivityStreams;
use Zotlabs\Lib\Config;
@@ -48,7 +47,7 @@ class Outbox extends Controller {
$params = [];
$params['begin'] = ((x($_REQUEST, 'date_begin')) ? $_REQUEST['date_begin'] : DBA::$dba->get_null_date());
$params['begin'] = ((x($_REQUEST, 'date_begin')) ? $_REQUEST['date_begin'] : NULL_DATE);
$params['end'] = ((x($_REQUEST, 'date_end')) ? $_REQUEST['date_end'] : '');
$params['type'] = 'json';
$params['pages'] = ((x($_REQUEST, 'pages')) ? intval($_REQUEST['pages']) : 0);

View File

@@ -19,128 +19,96 @@ use Zotlabs\Web\Controller;
class Owa extends Controller {
public function init(): void
{
{
$ret = [ 'success' => false ];
// try OpenWebAuth over RFC9421
$sigdata = HTTPSig::verify(EMPTY_STR);
if ($sigdata && $sigdata['portable_id'] && $sigdata['header_valid']) {
$portable_id = $sigdata['portable_id'];
if (!check_channelallowed($portable_id)) {
json_return_and_die($ret, 'application/x-zot+json');
}
if (!check_siteallowed($sigdata['signer'])) {
json_return_and_die($ret, 'application/x-zot+json');
}
$hubs = q("SELECT * FROM hubloc LEFT JOIN xchan ON hubloc_hash = xchan_hash
WHERE hubloc_hash = '%s' ORDER BY hubloc_id DESC",
dbesc($portable_id)
);
if ($hubs) {
logger('OWA RFC9421 success: ' . $hubs[0]['hubloc_id_url'], LOGGER_DATA);
$ret['success'] = true;
$token = random_string(32);
Verify::create('owt', 0, $token, $hubs[0]['hubloc_id_url']);
$result = '';
openssl_public_encrypt($token, $result, $hubs[0]['xchan_pubkey']);
$ret['encrypted_token'] = base64url_encode($result);
}
if (!$this->validateAuthorizationHeader()) {
$this->error('Missing or invalid authorization header.');
}
else {
if (!$this->validateAuthorizationHeader()) {
$this->error('Missing or invalid authorization header.');
$_SERVER['HTTP_AUTHORIZATION'] = $_SERVER['HTTP_AUTHORIZATION'] ?? $_SERVER['REDIRECT_REMOTE_USER'];
$sigblock = HTTPSig::parse_sigheader($_SERVER['HTTP_AUTHORIZATION']);
if ($sigblock) {
$keyId = $sigblock['keyId'];
$parsed = parse_url($keyId);
if (str_starts_with($parsed['scheme'],'http')) {
unset($parsed['fragment']);
unset($parsed['query']);
$keyId = unparse_url($parsed);
}
$_SERVER['HTTP_AUTHORIZATION'] = $_SERVER['HTTP_AUTHORIZATION'] ?? $_SERVER['REDIRECT_REMOTE_USER'];
$sigblock = HTTPSig::parse_sigheader($_SERVER['HTTP_AUTHORIZATION']);
if ($sigblock) {
$keyId = $sigblock['keyId'];
$parsed = parse_url($keyId);
if (str_starts_with($parsed['scheme'],'http')) {
unset($parsed['fragment']);
unset($parsed['query']);
$keyId = unparse_url($parsed);
else {
$keyId = str_replace('acct:', '', $keyId);
}
if ($keyId) {
$r = q("SELECT * FROM hubloc LEFT JOIN xchan ON hubloc_hash = xchan_hash
WHERE (hubloc_addr = '%s' OR hubloc_id_url = '%s' OR xchan_hash = '%s')
AND hubloc_deleted = 0 AND xchan_pubkey != ''
ORDER BY hubloc_id DESC",
dbesc($keyId),
dbesc($keyId),
dbesc($keyId)
);
if (! $r) {
$found = discover_by_webbie($keyId);
logger('found = ' . print_r($found, true));
if ($found) {
$r = q("SELECT * FROM hubloc LEFT JOIN xchan ON hubloc_hash = xchan_hash
WHERE (hubloc_addr = '%s' OR hubloc_id_url = '%s' OR xchan_hash = '%s') AND hubloc_deleted = 0 AND xchan_pubkey != '' ORDER BY hubloc_id DESC ",
dbesc($keyId),
dbesc($keyId),
dbesc($keyId)
);
}
}
else {
$keyId = str_replace('acct:', '', $keyId);
}
if ($keyId) {
$r = q("SELECT * FROM hubloc LEFT JOIN xchan ON hubloc_hash = xchan_hash
WHERE (hubloc_addr = '%s' OR hubloc_id_url = '%s' OR xchan_hash = '%s')
AND hubloc_deleted = 0 AND xchan_pubkey != ''
ORDER BY hubloc_id DESC",
dbesc($keyId),
dbesc($keyId),
dbesc($keyId)
);
if (! $r) {
$found = discover_by_webbie($keyId);
logger('found = ' . print_r($found, true));
if ($found) {
$r = q("SELECT * FROM hubloc LEFT JOIN xchan ON hubloc_hash = xchan_hash
WHERE (hubloc_addr = '%s' OR hubloc_id_url = '%s' OR xchan_hash = '%s') AND hubloc_deleted = 0 AND xchan_pubkey != '' ORDER BY hubloc_id DESC ",
dbesc($keyId),
dbesc($keyId),
dbesc($keyId)
);
if ($r) {
foreach ($r as $hubloc) {
$verified = HTTPSig::verify(file_get_contents('php://input'), $hubloc['xchan_pubkey']);
if ($verified && $verified['header_signed'] && $verified['header_valid'] && ($verified['content_valid'] || (! $verified['content_signed']))) {
logger('OWA header: ' . print_r($verified,true),LOGGER_DATA);
logger('OWA success: ' . $hubloc['hubloc_id_url'],LOGGER_DATA);
$ret['success'] = true;
$token = random_string(32);
Verify::create('owt',0,$token,$hubloc['hubloc_id_url']);
$result = '';
openssl_public_encrypt($token,$result,$hubloc['xchan_pubkey']);
$ret['encrypted_token'] = base64url_encode($result);
break;
} else {
logger('OWA fail: ' . $hubloc['hubloc_id'] . ' ' . $hubloc['hubloc_id_url']);
}
}
if ($r) {
foreach ($r as $hubloc) {
$verified = HTTPSig::verify(file_get_contents('php://input'), $hubloc['xchan_pubkey']);
if ($verified && $verified['header_signed'] && $verified['header_valid'] && ($verified['content_valid'] || (! $verified['content_signed']))) {
logger('OWA header: ' . print_r($verified,true),LOGGER_DATA);
logger('OWA success: ' . $hubloc['hubloc_id_url'],LOGGER_DATA);
$ret['success'] = true;
$token = random_string(32);
Verify::create('owt',0,$token,$hubloc['hubloc_id_url']);
$result = '';
openssl_public_encrypt($token,$result,$hubloc['xchan_pubkey']);
$ret['encrypted_token'] = base64url_encode($result);
break;
} else {
logger('OWA fail: ' . $hubloc['hubloc_id'] . ' ' . $hubloc['hubloc_id_url']);
}
}
if (!$ret['success']) {
if (!$ret['success']) {
// Possible a reinstall?
// In this case we probably already have an old hubloc
// but not the new one yet.
// Possible a reinstall?
// In this case we probably already have an old hubloc
// but not the new one yet.
$found = discover_by_webbie($keyId);
$found = discover_by_webbie($keyId);
if ($found) {
$r = q("SELECT * FROM hubloc LEFT JOIN xchan ON hubloc_hash = xchan_hash
WHERE (hubloc_addr = '%s' OR hubloc_id_url = '%s') AND hubloc_deleted = 0 ORDER BY hubloc_id DESC LIMIT 1",
dbesc(str_replace('acct:', '', $keyId)),
dbesc($keyId)
);
if ($found) {
$r = q("SELECT * FROM hubloc LEFT JOIN xchan ON hubloc_hash = xchan_hash
WHERE (hubloc_addr = '%s' OR hubloc_id_url = '%s') AND hubloc_deleted = 0 ORDER BY hubloc_id DESC LIMIT 1",
dbesc(str_replace('acct:', '', $keyId)),
dbesc($keyId)
);
if ($r) {
$verified = HTTPSig::verify(file_get_contents('php://input'), $r[0]['xchan_pubkey']);
if ($verified && $verified['header_signed'] && $verified['header_valid'] && ($verified['content_valid'] || (! $verified['content_signed']))) {
logger('OWA header: ' . print_r($verified,true), LOGGER_DATA);
logger('OWA success: ' . $r[0]['hubloc_id_url'], LOGGER_DATA);
$ret['success'] = true;
$token = random_string(32);
Verify::create('owt', 0, $token, $r[0]['hubloc_id_url']);
$result = '';
openssl_public_encrypt($token, $result, $r[0]['xchan_pubkey']);
$ret['encrypted_token'] = base64url_encode($result);
} else {
logger('OWA fail: ' . $hubloc['hubloc_id'] . ' ' . $hubloc['hubloc_id_url']);
}
if ($r) {
$verified = HTTPSig::verify(file_get_contents('php://input'), $r[0]['xchan_pubkey']);
if ($verified && $verified['header_signed'] && $verified['header_valid'] && ($verified['content_valid'] || (! $verified['content_signed']))) {
logger('OWA header: ' . print_r($verified,true), LOGGER_DATA);
logger('OWA success: ' . $r[0]['hubloc_id_url'], LOGGER_DATA);
$ret['success'] = true;
$token = random_string(32);
Verify::create('owt', 0, $token, $r[0]['hubloc_id_url']);
$result = '';
openssl_public_encrypt($token, $result, $r[0]['xchan_pubkey']);
$ret['encrypted_token'] = base64url_encode($result);
} else {
logger('OWA fail: ' . $hubloc['hubloc_id'] . ' ' . $hubloc['hubloc_id_url']);
}
}
}

View File

@@ -1,79 +0,0 @@
<?php
/* Handler for perfstats requests.
*
* SPDX-FileCopyrightText: 2026 The Hubzilla Community
* SPDX-FileContributor: Harald Eilertsen <haraldei@anduin.net>
*
* SPDX-License-Identifier: MIT
*/
namespace Zotlabs\Module;
use DBA;
use Zotlabs\Lib\DbStats;
use Zotlabs\Lib\Queue;
use Zotlabs\Lib\QueueWorkerStats;
use Zotlabs\Web\Controller;
/**
* Controller for the `/perfstats` module.
*
* Collects various performance stats for the site, and reponds with the stats
* as a json array.
*/
class Perfstats extends Controller
{
public function init(): void {
//
// We only accept GET requests
//
if ($_SERVER['REQUEST_METHOD'] !== 'GET') {
http_status_exit(400, 'Unsupported method');
}
//
// We only accept json requests
//
if (getBestSupportedMimeType(['application/json']) === null) {
http_status_exit(400, 'No supported format');
}
//
// Only admins should be given access
//
if (!is_site_admin()) {
http_status(401, 'Access denied');
json_return_and_die(['error' => 'access denied']);
}
$data = $this->getStats();
json_return_and_die($data);
}
private function getStats(): array {
$stats = [];
if (function_exists('sys_getloadavg')) {
$stats['loadavg'] = sys_getloadavg();
}
$stats['dbqueries'] = $this->getNumQueries();
$stats['outqueue'] = Queue::count();
$qwstats = new QueueWorkerStats();
$stats['queueworkers'] = $qwstats->active;
$stats['workqsz'] = $qwstats->size;
// Return a timestamp, so that it is possible to infer
// changes of the stats over time. A resolution of
// seconds should be good enough for our purposes.
$stats['ts'] = time();
return $stats;
}
private function getNumQueries(): int {
$stats = DbStats::getStats();
return $stats->getQueries();
}
}

View File

@@ -60,16 +60,26 @@ class Profile extends Controller {
'rel' => 'alternate',
'type' => 'application/atom+xml',
'title' => t('Posts and comments'),
'href' => z_root() . '/feed/' . $which . '?top=0'
'href' => z_root() . '/feed/' . $which
]);
head_add_link([
'rel' => 'alternate',
'type' => 'application/atom+xml',
'title' => t('Only posts'),
'href' => z_root() . '/feed/' . $which . '?top=1'
'href' => z_root() . '/feed/' . $which . '?f=&top=1'
]);
if (!$profile) {
$x = q("select channel_id as profile_uid from channel where channel_address = '%s' limit 1",
dbesc(argv(1))
);
if ($x) {
App::$profile = $x[0];
}
}
profile_load($which, $profile);
}

View File

@@ -1,7 +1,6 @@
<?php
namespace Zotlabs\Module;
use DBA;
use Zotlabs\Lib\Config;
use Zotlabs\Lib\Libsync;
@@ -341,7 +340,7 @@ class Profiles extends \Zotlabs\Web\Controller {
$with = ((x($_POST,'with')) ? escape_tags(trim($_POST['with'])) : '');
if(! strlen($howlong))
$howlong = DBA::$dba->get_null_date();
$howlong = NULL_DATE;
else
$howlong = datetime_convert(date_default_timezone_get(),'UTC',$howlong);
@@ -697,9 +696,10 @@ class Profiles extends \Zotlabs\Web\Controller {
$show_presence = ['show_presence', t('Reveal my online status'), $show_presence_val, '', [t('No'), t('Yes')]];
}
$extra_fields = array();
$q = q("select * from profdef where true");
if($q) {
$extra_fields = array();
foreach($q as $qq) {
$mine = q("select v from profext where k = '%s' and hash = '%s' and channel_id = %d limit 1",
dbesc($qq['field_name']),
@@ -759,7 +759,7 @@ class Profiles extends \Zotlabs\Web\Controller {
'$profile_id' => $r[0]['id'],
'$profile_name' => array('profile_name', t('Profile name'), $r[0]['profile_name'], t('Required'), '*'),
'$is_default' => $is_default,
'$default' => t('This is your default profile.') . ' ' . translate_scope(map_scope(\Zotlabs\Access\PermissionLimits::Get($channel['channel_id'],'view_profile'))),
'$default' => t('This is your default profile.') . EOL . translate_scope(map_scope(\Zotlabs\Access\PermissionLimits::Get($channel['channel_id'],'view_profile'))),
'$advanced' => $advanced,
'$name' => array('name', t('Your full name'), $r[0]['fullname'], t('Required'), '*'),
'$pdesc' => array('pdesc', t('Short title/description'), $r[0]['pdesc'], t('Maximal 190 characters'), '', 'maxlength="190"'),
@@ -775,7 +775,7 @@ class Profiles extends \Zotlabs\Web\Controller {
'$marital' => marital_selector($r[0]['marital']),
'$marital_min' => marital_selector_min($r[0]['marital']),
'$with' => array('with', t("Who (if applicable)"), $r[0]['partner'], t('Examples: cathy123, Cathy Williams, cathy@example.com')),
'$howlong' => array('howlong', t('Since (date)'), ($r[0]['howlong'] <= DBA::$dba->get_null_date() ? '' : datetime_convert('UTC',date_default_timezone_get(),$r[0]['howlong']))),
'$howlong' => array('howlong', t('Since (date)'), ($r[0]['howlong'] <= NULL_DATE ? '' : datetime_convert('UTC',date_default_timezone_get(),$r[0]['howlong']))),
'$sexual' => sexpref_selector($r[0]['sexual']),
'$sexual_min' => sexpref_selector_min($r[0]['sexual']),
'$about' => array('about', t('Tell us about yourself'), $r[0]['about']),
@@ -833,8 +833,6 @@ class Profiles extends \Zotlabs\Web\Controller {
);
if($r) {
$profiles = '';
$tpl = get_markup_template('profile_entry.tpl');
foreach($r as $rr) {
$profiles .= replace_macros($tpl, array(
@@ -843,7 +841,7 @@ class Profiles extends \Zotlabs\Web\Controller {
'$alt' => t('Profile Image'),
'$profile_name' => $rr['profile_name'],
'$visible' => (($rr['is_default'])
? '<strong>' . escape_tags(translate_scope(map_scope(\Zotlabs\Access\PermissionLimits::Get($channel['channel_id'],'view_profile')))) . '</strong>'
? '<strong>' . translate_scope(map_scope(\Zotlabs\Access\PermissionLimits::Get($channel['channel_id'],'view_profile'))) . '</strong>'
: '<a href="' . z_root() . '/profperm/' . $rr['id'] . '" />' . t('Edit visibility') . '</a>')
));
}

View File

@@ -44,7 +44,6 @@ class Profperm extends \Zotlabs\Web\Controller {
if($switchtotext === false)
$switchtotext = 400;
$change = 0;
if((argc() > 2) && intval(argv(1)) && intval(argv(2))) {
$r = q("SELECT abook_id FROM abook WHERE abook_id = %d and abook_channel = %d limit 1",
@@ -75,11 +74,10 @@ class Profperm extends \Zotlabs\Web\Controller {
dbesc($profile['profile_guid'])
);
$ingroup = [];
if($r) {
$ingroup = array();
if($r)
foreach($r as $member)
$ingroup[] = $member['abook_id'];
}
$members = $r;
@@ -106,57 +104,68 @@ class Profperm extends \Zotlabs\Web\Controller {
);
$members = $r;
$ingroup = [];
if($r) {
$ingroup = array();
if(count($r))
foreach($r as $member)
$ingroup[] = $member['abook_id'];
}
}
$o .= '<h2>' . t('Profile Visibility Editor') . '</h2>';
$o .= '<h3>' . t('Profile') . ' \'' . $profile['profile_name'] . '\'</h3>';
$o .= '<div id="prof-edit-desc">' . t('Click on a contact to add or remove.') . '</div>';
}
// Build template data
$members_tpl = [];
$o .= '<div id="prof-update-wrapper">';
if($change)
$o = '';
$o .= '<div id="prof-members-title">';
$o .= '<h3>' . t('Visible To') . '</h3>';
$o .= '</div>';
$o .= '<div id="prof-members">';
$textmode = (($switchtotext && (count($members) > $switchtotext)) ? true : false);
if($members) {
foreach($members as $member) {
if($member['xchan_url']) {
$member['click'] = 'profChangeMember(' . $profile['id'] . ',' . $member['abook_id'] . '); return false;';
$members_tpl[] = [ 'micro' => micropro($member, true, 'mpprof', $textmode) ];
}
foreach($members as $member) {
if($member['xchan_url']) {
$member['click'] = 'profChangeMember(' . $profile['id'] . ',' . $member['abook_id'] . '); return false;';
$o .= micropro($member,true,'mpprof', $textmode);
}
}
$o .= '</div><div id="prof-members-end"></div>';
$o .= '<hr id="prof-separator" />';
$all_members_tpl = [];
$r = abook_connections(local_channel());
if($r) {
$textmode = (($switchtotext && (count($r) > $switchtotext)) ? true : false);
foreach($r as $member) {
if(! in_array($member['abook_id'], $ingroup)) {
$member['click'] = 'profChangeMember(' . $profile['id'] . ',' . $member['abook_id'] . '); return false;';
$all_members_tpl[] = [ 'micro' => micropro($member, true, 'mpprof', $textmode) ];
$o .= '<div id="prof-all-contcts-title">';
$o .= '<h3>' . t("All Connections") . '</h3>';
$o .= '</div>';
$o .= '<div id="prof-all-contacts">';
$r = abook_connections(local_channel());
if($r) {
$textmode = (($switchtotext && (count($r) > $switchtotext)) ? true : false);
foreach($r as $member) {
if(! in_array($member['abook_id'],$ingroup)) {
$member['click'] = 'profChangeMember(' . $profile['id'] . ',' . $member['abook_id'] . '); return false;';
$o .= micropro($member,true,'mpprof',$textmode);
}
}
}
}
// Use tpl for the inner part
$inner_html = replace_macros(get_markup_template('profile_members.tpl'), [
'$visible_to' => t('Visible To'),
'$all_connections' => t('All Connections'),
'$members' => $members_tpl,
'$all_members' => $all_members_tpl,
]);
$o .= '</div><div id="prof-all-contacts-end"></div>';
if($change) {
echo $inner_html;
echo $o;
killme();
}
$o .= $inner_html;
$o .= '</div>';
return $o;
}
}
}
}

View File

@@ -247,7 +247,11 @@ class Pubstream extends \Zotlabs\Web\Controller {
if($r) {
$items = items_by_parent_ids($r, blog_mode: $blog_mode);
xchan_query($items);
// use effective_uid param of xchan_query to help sort out comment permission
// for sys_channel owned items.
xchan_query($items, true, local_channel());
$items = fetch_post_tags($items,true);
$items = conv_sort($items, $ordering);
}

View File

@@ -142,15 +142,7 @@ class Regate extends \Zotlabs\Web\Controller {
if (($flags & ACCOUNT_PENDING ) == ACCOUNT_PENDING) {
$nextpage = 'regate/' . bin2hex($did2) . $didx;
$approve = send_reg_approval_email_from_register($r['reg_id']);
if ($approve['success']) {
q("COMMIT");
} else {
q("ROLLBACK");
$msg_code = 'ZAR1237E';
$msg = t('Account verification notify error');
zar_log($msg_code . ' ' . $msg . ': ' . print_r($approve, true));
}
q("COMMIT");
}
elseif (($flags ^ REGISTER_AGREED) == 0) {
@@ -394,7 +386,7 @@ class Regate extends \Zotlabs\Web\Controller {
}
else {
zar_log('ZAR1135E not awaited url parameter received');
goaway(z_root());
goaway(z_root);
}
}
else {

View File

@@ -323,6 +323,7 @@ class Register extends Controller {
$did2 = $email;
$didx = 'e';
push_lang(($reg['lang']) ? $reg['lang'] : App::$language);
$reonar['from'] = Config::Get('system', 'from_email');
$reonar['to'] = $email;
$reonar['subject'] = sprintf( t('Registration confirmation for %s'), Config::Get('system','sitename'));
@@ -337,6 +338,7 @@ class Register extends Controller {
'$hash' => $empin
]
);
pop_lang();
zar_reg_mail($reonar);
} else {
@@ -443,7 +445,7 @@ class Register extends Controller {
if(intval(Config::Get('system','register_policy')) == REGISTER_APPROVE) {
$register_msg = ['register_msg', t('Why do you want to join this hub?'), ((x($_REQUEST,'register_msg')) ? $_REQUEST['register_msg'] : ''), t('This will help to review your registration')];
$registration_is = t('Registration on this hub is by approval only.');
$other_sites = '<a href="pubsites">' . t('Register at another affiliated hub if preferred') . '</a>';
$other_sites = '<a href="pubsites">' . t('Register at another affiliated hub in case when prefered') . '</a>';
}
$duty = zar_register_dutystate();

View File

@@ -1,38 +1,37 @@
<?php
namespace Zotlabs\Module;
use DBA;
class Removeaccount extends \Zotlabs\Web\Controller {
function post() {
if(! local_channel())
return;
if($_SESSION['delegate'])
return;
if((! x($_POST,'qxz_password')) || (! strlen(trim($_POST['qxz_password']))))
return;
if((! x($_POST,'verify')) || (! strlen(trim($_POST['verify']))))
return;
if($_POST['verify'] !== $_SESSION['remove_account_verify'])
return;
$account = \App::get_account();
$account_id = get_account_id();
$x = account_verify_password($account['account_email'],$_POST['qxz_password']);
if(! ($x && $x['account']))
return;
if($account['account_password_changed'] > DBA::$dba->get_null_date()) {
if($account['account_password_changed'] > NULL_DATE) {
$d1 = datetime_convert('UTC','UTC','now - 48 hours');
if($account['account_password_changed'] > $d1) {
if($account['account_password_changed'] > d1) {
notice( t('Account removals are not allowed within 48 hours of changing the account password.') . EOL);
return;
}

View File

@@ -1,7 +1,6 @@
<?php
namespace Zotlabs\Module;
use DBA;
class Removeme extends \Zotlabs\Web\Controller {
@@ -30,7 +29,7 @@ class Removeme extends \Zotlabs\Web\Controller {
if(! ($x && $x['account']))
return;
if($account['account_password_changed'] > DBA::$dba->get_null_date()) {
if($account['account_password_changed'] > NULL_DATE) {
$d1 = datetime_convert('UTC','UTC','now - 48 hours');
if($account['account_password_changed'] > $d1) {
notice( t('Channel removals are not allowed within 48 hours of changing the account password.') . EOL);

View File

@@ -25,7 +25,7 @@ class Request extends Controller
}
private function processSubthreadRequest(): void
private function processSubthreadRequest() : string
{
$mid = $_GET['mid'];
$parent = intval($_GET['parent']);
@@ -52,11 +52,11 @@ class Request extends Controller
json_return_and_die($ret);
}
public function init() : void
public function get() : string
{
if (in_array($_GET['verb'], ['comment', 'load'])) {
self::processSubthreadRequest();
return self::processSubthreadRequest();
}
$verb = self::mapVerb($_GET['verb']);

View File

@@ -42,7 +42,9 @@ class Search extends Controller {
$observer = App::get_observer();
$observer_hash = (($observer) ? $observer['xchan_hash'] : '');
$title = t('Search');
$o = '<div class="generic-content-wrapper-styled">' . "\r\n";
$o .= '<h2>' . t('Search') . '</h2>';
if (x(App::$data, 'search'))
$search = trim(App::$data['search']);
@@ -55,7 +57,7 @@ class Search extends Controller {
$search = ((x($_GET, 'tag')) ? trim(escape_tags(rawurldecode($_GET['tag']))) : '');
}
$searchbox = search($search, 'search-box', '/search', ((local_channel()) ? true : false));
$o .= search($search, 'search-box', '/search', ((local_channel()) ? true : false));
if (local_channel() && str_starts_with($search, 'https://') && !$update && !$load) {
@@ -69,13 +71,7 @@ class Search extends Controller {
$url = unpack_link_id(basename($url));
}
$parsed = parse_url($url);
if (isset($parsed['host'])) {
$parsed['host'] = punify($parsed['host']);
$url = unparse_url($parsed);
}
$f = Libzot::fetch_conversation(App::get_channel(), $url, true);
$f = Libzot::fetch_conversation(App::get_channel(), punify($url), true);
if ($f) {
$uuid = $f[0]['message_uuid'];
@@ -91,7 +87,7 @@ class Search extends Controller {
else {
// try other fetch providers (e.g. diaspora, pubcrawl)
$hookdata = [
'url' => $url
'url' => punify($url)
];
call_hooks('fetch_provider', $hookdata);
}
@@ -119,17 +115,8 @@ class Search extends Controller {
goaway(z_root() . '/directory' . '?f=1&navsearch=1&search=' . $search);
}
if (!$search) {
$tpl = get_markup_template('search.tpl');
return replace_macros($tpl, [
'$title' => $title,
'$searchbox' => $searchbox,
'$livesearch' => '',
'$results_header' => '',
'$conversation' => '',
]);
}
if (!$search)
return $o;
if ($tag) {
$wildtag = str_replace('*', '%', $search);
@@ -150,15 +137,14 @@ class Search extends Controller {
// OR your own posts if you are a logged in member
// No items will be shown if the member has a blocked profile wall.
$livesearch = '';
if ((!$update) && (!$load)) {
// This is ugly, but we can't pass the profile_uid through the session to the ajax updater,
// because browser prefetching might change it on us. We have to deliver it with the page.
$livesearch .= '<div id="live-search"></div>' . "\r\n";
$livesearch .= "<script> var profile_uid = " . ((intval(local_channel())) ? local_channel() : (-1))
$o .= '<div id="live-search"></div>' . "\r\n";
$o .= "<script> var profile_uid = " . ((intval(local_channel())) ? local_channel() : (-1))
. "; var netargs = '?f='; var profile_page = " . App::$pager['page'] . "; </script>\r\n";
App::$page['htmlhead'] = replace_macros(get_markup_template("build_query.tpl"), [
@@ -262,21 +248,15 @@ class Search extends Controller {
}
if ($tag)
$results_header = sprintf(t('Items tagged with: %s'), $search);
$o .= '<h2>' . sprintf(t('Items tagged with: %s'), $search) . '</h2>';
else
$results_header = sprintf(t('Search results for: %s'), $search);
$o .= '<h2>' . sprintf(t('Search results for: %s'), $search) . '</h2>';
$conversation = conversation($items, 'search', $update, 'client');
$o .= conversation($items, 'search', $update, 'client');
$tpl = get_markup_template('search.tpl');
$o .= '</div>';
return replace_macros($tpl, [
'$title' => $title,
'$searchbox' => $searchbox,
'$livesearch' => $livesearch,
'$results_header' => $results_header ?? '',
'$conversation' => $conversation,
]);
return $o;
}

View File

@@ -153,6 +153,14 @@ class Channel {
Master::Summon(['Directory', local_channel()]);
Libsync::build_sync_packet();
$email_changed = false;
if ($email_changed && App::$config['system']['register_policy'] == REGISTER_VERIFY) {
// FIXME - set to un-verified, blocked and redirect to logout
// Q: Why? Are we verifying people or email addresses?
// A: the policy is to verify email addresses
}
goaway(z_root() . '/settings');
return; // NOTREACHED
}

View File

@@ -504,6 +504,12 @@ class Setup extends \Zotlabs\Web\Controller {
}
$this->check_add($checks, t('Generate ed25519 encryption keys'), $res, true, $help);
$res1 = extension_loaded('bcmath');
$res2 = extension_loaded('gmp');
if (! ($res1 || $res2)) {
$help = t('Error: one of "bcmath" or "gmp" (bigmath library) extensions are required.') . EOL;
}
$this->check_add($checks, t('Bigmath library (either bcmath or gmp)'), $res1||$res2, $help);
}
/**
@@ -528,7 +534,6 @@ class Setup extends \Zotlabs\Web\Controller {
$this->check_add($ck_funcs, t('xml PHP module'), true, true);
$this->check_add($ck_funcs, t('zip PHP module'), true, true);
$this->check_add($ck_funcs, t('intl PHP module'), true, true);
$this->check_add($ck_funcs, t('gmp PHP module'), true, true);
if(function_exists('apache_get_modules')){
if(! in_array('mod_rewrite', apache_get_modules())) {
@@ -585,15 +590,9 @@ class Setup extends \Zotlabs\Web\Controller {
$ck_funcs[6]['status'] = false;
$ck_funcs[6]['help'] = t('Error: zip PHP module required but not installed.');
}
if (!extension_loaded('intl')) {
$ck_funcs[7]['status'] = false;
$ck_funcs[7]['help'] = t('Error: intl PHP module required but not installed.');
}
if (!extension_loaded('gmp')) {
$ck_funcs[8]['status'] = false;
$ck_funcs[8]['help'] = t('Error: gmp PHP module required but not installed.');
if(! extension_loaded('intl')) {
$ck_funcs[6]['status'] = false;
$ck_funcs[6]['help'] = t('Error: intl PHP module required but not installed.');
}
$checks = array_merge($checks, $ck_funcs);

View File

@@ -120,7 +120,7 @@ class Share extends \Zotlabs\Web\Controller {
$arr['changed'] = $created;
$arr['item_type'] = ITEM_TYPE_POST;
$mention = '[zrl=' . $item['author']['xchan_url'] . ']@' . $item['author']['xchan_name'] . '[/zrl]';
$mention = '@[zrl=' . $item['author']['xchan_url'] . ']' . $item['author']['xchan_name'] . '[/zrl]';
$arr['body'] = sprintf( t('&#x1f501; Repeated %1$s\'s %2$s'), $mention, Activity::activity_obj_mapper($item['obj_type']));
$arr['author_xchan'] = $channel['channel_hash'];

View File

@@ -17,9 +17,6 @@ class Siteinfo extends \Zotlabs\Web\Controller {
$federated = [];
call_hooks('federated_transports',$federated);
$themes = Config::Get('system', 'allowed_themes');
$themes = array_map('trim', explode(',', $themes));
$siteinfo = replace_macros(get_markup_template('siteinfo.tpl'),
[
'$title' => t('About this site'),
@@ -43,7 +40,6 @@ class Siteinfo extends \Zotlabs\Web\Controller {
'$prj_link' => \Zotlabs\Lib\System::get_project_link(),
'$prj_src' => \Zotlabs\Lib\System::get_project_srclink(),
'$addons' => array( t('Active addons'), \App::$plugins ),
'$themes' => array( t('Active themes'), $themes ),
'$blocked_sites' => array( t('Blocked sites'), \Zotlabs\Lib\Config::Get('system', 'blacklisted_sites') )
]
);

View File

@@ -3,7 +3,6 @@
namespace Zotlabs\Module;
use App;
use DBA;
use Zotlabs\Lib\Apps;
use Zotlabs\Lib\Config;
use Zotlabs\Web\Controller;
@@ -137,7 +136,7 @@ class Sse extends Controller {
session_reset();
XConfig::Set(self::$ob_hash, 'sse', 'timestamp', DBA::$dba->get_null_date());
XConfig::Set(self::$ob_hash, 'sse', 'timestamp', NULL_DATE);
XConfig::Set(self::$ob_hash, 'sse', 'notifications', []);
if (ob_get_length() > 0) {

View File

@@ -20,8 +20,6 @@ class Sse_bs extends Controller {
public static $limit;
public static $offset;
public static $xchans;
public static $direction;
public static $count_limit;
function init() {
self::$uid = local_channel();
@@ -43,8 +41,6 @@ class Sse_bs extends Controller {
self::$limit = 30;
self::$offset = 0;
self::$xchans = '';
self::$direction = get_pconfig(self::$uid, 'system', 'invert_notifications_order', false) ? 'ASC' : 'DESC';
self::$count_limit = get_pconfig(self::$uid, 'system', 'notifications_count_limit', 100);
if (!empty($_REQUEST['sse_rmids'])) {
self::mark_read(explode(',', $_REQUEST['sse_rmids']));
@@ -68,6 +64,7 @@ class Sse_bs extends Controller {
$_SESSION['sse_loadtime'] = datetime_convert();
}
$network = false;
$dm = false;
$home = false;
@@ -94,14 +91,8 @@ class Sse_bs extends Controller {
default:
}
$selected_forum_id = null;
if (str_starts_with(argv(1), 'forum_')) {
$selected_forum_id = argv(1);
$f = 'bs_forums';
}
if(self::$offset && $f) {
$result = self::$f($selected_forum_id ?? true);
$result = self::$f(true);
json_return_and_die($result);
}
@@ -111,7 +102,7 @@ class Sse_bs extends Controller {
self::bs_home($home),
self::bs_notify(),
self::bs_intros(),
self::bs_forums($selected_forum_id),
self::bs_forums(),
self::bs_pubs($pubs),
self::bs_files(),
self::bs_all_events(),
@@ -131,7 +122,7 @@ class Sse_bs extends Controller {
$str = '';
$slice = 0;
$mids_all = isset($_SESSION['sse_mids_all']) ? json_unserialize($_SESSION['sse_mids_all']) : [];
$mids_all = isset($_SESSION['sse_mids_all']) ? unserialise($_SESSION['sse_mids_all']) : [];
if (count($mids_all) > 3000) {
$slice = count($mids_all) - 3000;
@@ -177,7 +168,7 @@ class Sse_bs extends Controller {
$mids_all = array_merge($mids_all, $activities_arr);
}
$_SESSION['sse_mids_all'] = json_serialize(array_unique($mids_all));
$_SESSION['sse_mids_all'] = serialise(array_unique($mids_all));
if(! self::$uid) {
return;
@@ -214,8 +205,6 @@ class Sse_bs extends Controller {
$limit = intval(self::$limit);
$offset = self::$offset;
$direction = self::$direction;
$count_limit = intval(self::$count_limit);
$sql_extra = '';
if (!(self::$vnotify & VNOTIFY_LIKE)) {
@@ -246,7 +235,7 @@ class Sse_bs extends Controller {
$item_normal
$sql_extra
$sql_extra2
ORDER BY item.created $direction LIMIT $limit OFFSET $offset",
ORDER BY item.created DESC LIMIT $limit OFFSET $offset",
intval(self::$uid),
dbescdate($_SESSION['sse_loadtime']),
dbesc(self::$ob_hash)
@@ -273,7 +262,7 @@ class Sse_bs extends Controller {
AND obj_type NOT IN ('Document', 'Video', 'Audio', 'Image')
AND author_xchan != '%s'
$item_normal
$sql_extra LIMIT $count_limit",
$sql_extra LIMIT 100",
intval(self::$uid),
dbesc(self::$ob_hash)
);
@@ -301,8 +290,6 @@ class Sse_bs extends Controller {
$limit = intval(self::$limit);
$offset = self::$offset;
$direction = self::$direction;
$count_limit = intval(self::$count_limit);
$sql_extra = '';
if (!(self::$vnotify & VNOTIFY_LIKE)) {
@@ -333,7 +320,7 @@ class Sse_bs extends Controller {
$item_normal
$sql_extra
$sql_extra2
ORDER BY created $direction LIMIT $limit OFFSET $offset",
ORDER BY created DESC LIMIT $limit OFFSET $offset",
intval(self::$uid),
dbescdate($_SESSION['sse_loadtime']),
dbesc(self::$ob_hash)
@@ -359,7 +346,7 @@ class Sse_bs extends Controller {
WHERE uid = %d and item_unseen = 1 AND item_private = 2
$item_normal
$sql_extra
AND author_xchan != '%s' LIMIT $count_limit",
AND author_xchan != '%s' LIMIT 100",
intval(self::$uid),
dbesc(self::$ob_hash)
);
@@ -387,8 +374,6 @@ class Sse_bs extends Controller {
$limit = intval(self::$limit);
$offset = self::$offset;
$direction = self::$direction;
$count_limit = intval(self::$count_limit);
$sql_extra = '';
if (!(self::$vnotify & VNOTIFY_LIKE)) {
@@ -420,7 +405,7 @@ class Sse_bs extends Controller {
$item_normal
$sql_extra
$sql_extra2
ORDER BY item.created $direction LIMIT $limit OFFSET $offset",
ORDER BY item.created DESC LIMIT $limit OFFSET $offset",
intval(self::$uid),
dbescdate($_SESSION['sse_loadtime']),
dbesc(self::$ob_hash)
@@ -446,7 +431,7 @@ class Sse_bs extends Controller {
WHERE uid = %d and item_unseen = 1 AND item_wall = 1 AND item_private IN (0, 1)
$item_normal
$sql_extra
AND author_xchan != '%s' LIMIT $count_limit",
AND author_xchan != '%s' LIMIT 100",
intval(self::$uid),
dbesc(self::$ob_hash)
);
@@ -485,8 +470,6 @@ class Sse_bs extends Controller {
$limit = intval(self::$limit);
$offset = self::$offset;
$direction = self::$direction;
$count_limit = intval(self::$count_limit);
$sys = get_sys_channel();
$sql_extra = '';
@@ -502,7 +485,7 @@ class Sse_bs extends Controller {
$sql_extra2 = " AND CASE WHEN item.verb = '" . ACTIVITY_SHARE . "' THEN item.owner_xchan ELSE item.author_xchan END IN (" . self::$xchans . ") ";
$sql_extra3 = '';
$sse_mids_all = isset($_SESSION['sse_mids_all']) ? json_unserialize($_SESSION['sse_mids_all']) : [];
$sse_mids_all = isset($_SESSION['sse_mids_all']) ? unserialise($_SESSION['sse_mids_all']) : [];
if ($sse_mids_all) {
$sql_extra3 = " AND item.uuid NOT IN (" . protect_sprintf(implode(',', $sse_mids_all)) . ") ";
}
@@ -532,9 +515,9 @@ class Sse_bs extends Controller {
$sql_extra
$sql_extra2
$sql_extra3
ORDER BY item.created $direction LIMIT $limit OFFSET $offset",
ORDER BY item.created DESC LIMIT $limit OFFSET $offset",
dbescdate($_SESSION['sse_loadtime']),
dbescdate($_SESSION['static_loadtime']),
dbescdate($_SESSION['last_login_date'] ?? $_SESSION['static_loadtime']),
dbesc(self::$ob_hash)
);
@@ -560,9 +543,9 @@ class Sse_bs extends Controller {
$item_normal
$sql_extra
$sql_extra3
AND author_xchan != '%s' LIMIT $count_limit",
AND author_xchan != '%s' LIMIT 100",
dbescdate($_SESSION['sse_loadtime']),
dbescdate($_SESSION['static_loadtime']),
dbescdate($_SESSION['last_login_date'] ?? $_SESSION['static_loadtime']),
dbesc(self::$ob_hash)
);
@@ -585,9 +568,7 @@ class Sse_bs extends Controller {
if(! (self::$vnotify & VNOTIFY_SYSTEM))
return $result;
$direction = self::$direction;
$r = q("SELECT * FROM notify WHERE uid = %d AND seen = 0 ORDER BY created $direction",
$r = q("SELECT * FROM notify WHERE uid = %d AND seen = 0 ORDER BY created DESC",
intval(self::$uid)
);
@@ -614,9 +595,7 @@ class Sse_bs extends Controller {
if(! (self::$vnotify & VNOTIFY_INTRO))
return $result;
$direction = self::$direction;
$r = q("SELECT * FROM abook left join xchan on abook.abook_xchan = xchan.xchan_hash where abook_channel = %d and abook_pending = 1 and abook_self = 0 and abook_ignored = 0 and xchan_deleted = 0 and xchan_orphan = 0 ORDER BY abook_created $direction LIMIT 50",
$r = q("SELECT * FROM abook left join xchan on abook.abook_xchan = xchan.xchan_hash where abook_channel = %d and abook_pending = 1 and abook_self = 0 and abook_ignored = 0 and xchan_deleted = 0 and xchan_orphan = 0 ORDER BY abook_created DESC LIMIT 50",
intval(self::$uid)
);
@@ -631,95 +610,69 @@ class Sse_bs extends Controller {
}
function bs_forums($selected_forum_id) {
function bs_forums() {
$result['forums']['notifications'] = [];
$result['forums']['count'] = 0;
$result['forums']['offset'] = -1;
if(! self::$uid)
return $result;
if(! (self::$vnotify & VNOTIFY_FORUMS))
return $result;
$forums = get_forum_channels(self::$uid);
if(!self::$uid || !(self::$vnotify & VNOTIFY_FORUMS) || !$forums) {
$result['forum']['notifications'] = [];
$result['forum']['count'] = 0;
$result['forum']['offset'] = -1;
return $result;
}
if($forums) {
$item_normal = item_normal();
$p_sql = '';
$sql_extra = '';
if(! (self::$vnotify & VNOTIFY_LIKE))
$sql_extra = " AND verb NOT IN ('Like', 'Dislike', '" . dbesc(ACTIVITY_LIKE) . "', '" . dbesc(ACTIVITY_DISLIKE) . "') ";
$fcount = count($forums);
$i = 0;
for($x = 0; $x < $fcount; $x ++) {
$forum_id = 'forum_' . $forums[$x]['abook_id'];
$result[$forum_id]['notifications'] = [];
$result[$forum_id]['count'] = 0;
$limit = intval(self::$limit);
$offset = self::$offset;
$direction = self::$direction;
$count_limit = intval(self::$count_limit);
$sql_extra = '';
if (!(self::$vnotify & VNOTIFY_LIKE)) {
$sql_extra = " AND item.verb NOT IN ('Like', 'Dislike', '" . dbesc(ACTIVITY_LIKE) . "', '" . dbesc(ACTIVITY_DISLIKE) . "') ";
}
elseif (!feature_enabled(self::$uid, 'dislike')) {
$sql_extra = " AND item.verb NOT IN ('Dislike', '" . dbesc(ACTIVITY_DISLIKE) . "') ";
}
$item_normal = item_normal();
// Filter internal follow activities and strerams add/remove activities
$item_normal .= " AND item.verb NOT IN ('Add', 'Remove', 'Follow', 'Ignore', '" . dbesc(ACTIVITY_FOLLOW) . "') ";
if ($forum_id === $selected_forum_id) {
$items = q("SELECT item.*, tp.uuid AS thr_parent_uuid FROM item
LEFT JOIN item tp ON item.thr_parent = tp.mid AND item.uid = tp.uid
WHERE item.uid = %d
AND item.created <= '%s'
AND item.owner_xchan = '%s'
AND item.item_unseen = 1 AND item.item_wall = 0 AND item.item_private IN (0, 1)
AND item.obj_type NOT IN ('Document', 'Video', 'Audio', 'Image')
AND NOT (item.verb = 'Announce' AND item.item_thread_top = 1) -- only show the announce activity and not the resulting item
AND NOT item.author_xchan = '%s'
$item_normal
$sql_extra
ORDER BY item.created $direction LIMIT $limit OFFSET $offset",
intval(self::$uid),
dbescdate($_SESSION['sse_loadtime']),
dbescdate($forums[$x]['xchan_hash']),
dbesc(self::$ob_hash)
);
if ($items) {
$result[$forum_id]['offset'] = ((count($items) == $limit) ? intval($offset + $limit) : -1);
xchan_query($items);
foreach($items as $item) {
$parsed = Enotify::format($item);
if($parsed) {
$result[$forum_id]['notifications'][] = $parsed;
}
}
}
else {
$result[$forum_id]['offset'] = -1;
}
}
$r = q("SELECT id FROM item
WHERE uid = %d and item_unseen = 1 AND item_wall = 0 AND item_private IN (0, 1)
AND obj_type NOT IN ('Document', 'Video', 'Audio', 'Image')
AND author_xchan != '%s'
AND item.owner_xchan = '%s'
$item_normal
$sql_extra LIMIT $count_limit",
$r = q("select count(*) as total from item
where uid = %d and (owner_xchan = '%s' or author_xchan = '%s') and author_xchan != '%s' and verb != 'Announce' and item_unseen = 1 $sql_extra $item_normal",
intval(self::$uid),
dbesc(self::$ob_hash),
dbesc($forums[$x]['xchan_hash'])
dbesc($forums[$x]['xchan_hash']),
dbesc($forums[$x]['xchan_hash']),
dbesc(self::$ob_hash)
);
if ($r) {
$result[$forum_id]['count'] = count($r);
if($r[0]['total']) {
$forums[$x]['notify_link'] = z_root() . '/network/?f=&pf=1&unseen=1&cid=' . $forums[$x]['abook_id'];
$forums[$x]['name'] = $forums[$x]['xchan_name'];
$forums[$x]['addr'] = $forums[$x]['xchan_addr'] ?? $forums[$x]['xchan_url'];
$forums[$x]['url'] = $forums[$x]['xchan_url'];
$forums[$x]['photo'] = $forums[$x]['xchan_photo_s'];
$forums[$x]['unseen'] = $r[0]['total'];
$forums[$x]['private_forum'] = ((isset($forums[$x]['private_forum']) && $forums[$x]['private_forum']) ? 'lock' : '');
$forums[$x]['message'] = ((isset($forums[$x]['private_forum']) && $forums[$x]['private_forum']) ? t('Private forum') : t('Public forum'));
unset($forums[$x]['abook_id']);
unset($forums[$x]['xchan_hash']);
unset($forums[$x]['xchan_name']);
unset($forums[$x]['xchan_url']);
unset($forums[$x]['xchan_photo_s']);
$i = $i + $r[0]['total'];
}
else {
unset($forums[$x]);
}
}
$result['forums']['count'] = $i;
$result['forums']['notifications'] = array_values($forums);
}
return $result;
@@ -738,7 +691,6 @@ class Sse_bs extends Controller {
if(! (self::$vnotify & VNOTIFY_FILES))
return $result;
$direction = self::$direction;
$item_normal = item_normal();
// Filter internal follow activities and strerams add/remove activities
@@ -751,7 +703,7 @@ class Sse_bs extends Controller {
AND author_xchan != '%s'
AND item_unseen = 1
$item_normal
ORDER BY created $direction",
ORDER BY created DESC",
dbesc(ACTIVITY_POST),
intval(self::$uid),
dbesc(self::$ob_hash)
@@ -783,12 +735,10 @@ class Sse_bs extends Controller {
if(! (self::$vnotify & VNOTIFY_EVENT))
return $result;
$direction = self::$direction;
$r = q("SELECT * FROM event left join xchan on event_xchan = xchan_hash
WHERE event.uid = %d AND dtstart < '%s' AND dtstart > '%s' and dismissed = 0
and etype in ( 'event', 'birthday' )
ORDER BY dtstart $direction",
ORDER BY dtstart DESC",
intval(self::$uid),
dbesc(datetime_convert('UTC', date_default_timezone_get(), 'now + ' . intval(self::$evdays) . ' days')),
dbesc(datetime_convert('UTC', date_default_timezone_get(), 'now - 1 days'))

View File

@@ -3,21 +3,21 @@ namespace Zotlabs\Module;
require_once('include/event.php');
use DBA;
class Tasks extends \Zotlabs\Web\Controller {
function init() {
// logger('request: ' . print_r($_REQUEST,true));
$arr = array();
if(argc() > 1 && argv(1) === 'fetch') {
if(argc() > 1 && argv(1) === 'fetch') {
if(argc() > 2 && argv(2) === 'all')
$arr['all'] = 1;
$x = tasks_fetch($arr);
$x['html'] = '';
if($x['tasks']) {
@@ -53,7 +53,7 @@ class Tasks extends \Zotlabs\Web\Controller {
$event = $r[0];
if($event['event_status'] === 'COMPLETED') {
$event['event_status'] = 'IN-PROCESS';
$event['event_status_date'] = DBA::$dba->get_null_date();
$event['event_status_date'] = NULL_DATE;
$event['event_percent'] = 0;
$event['event_sequence'] = $event['event_sequence'] + 1;
$event['edited'] = datetime_convert();

View File

@@ -3,7 +3,6 @@
namespace Zotlabs\Module;
use App;
use DBA;
use Zotlabs\Web\Controller;
use Zotlabs\Lib\Apps;
use Zotlabs\Lib\AccessList;
@@ -85,7 +84,7 @@ class Tokens extends Controller {
if(trim($_POST['expires']))
$expires = datetime_convert(date_default_timezone_get(),'UTC',$_POST['expires']);
else
$expires = DBA::$dba->get_null_date();
$expires = NULL_DATE;
$max_atokens = service_class_fetch($channel['channel_id'],'access_tokens');
if($max_atokens) {
$r = q("select count(atoken_id) as total where atoken_uid = %d",
@@ -291,7 +290,7 @@ class Tokens extends Controller {
'$atoken' => $atoken,
'$name' => array('name', t('Login Name') . ' <span class="required">*</span>', $atoken['atoken_name'] ?? '',''),
'$token'=> array('token', t('Login Password') . ' <span class="required">*</span>', $atoken['atoken_token'] ?? new_token(), ''),
'$expires'=> array('expires', t('Expires (yyyy-mm-dd)'), ((isset($atoken['atoken_expires']) && $atoken['atoken_expires'] > DBA::$dba->get_null_date()) ? datetime_convert('UTC',date_default_timezone_get(),$atoken['atoken_expires']) : ''), ''),
'$expires'=> array('expires', t('Expires (yyyy-mm-dd)'), ((isset($atoken['atoken_expires']) && $atoken['atoken_expires'] > NULL_DATE) ? datetime_convert('UTC',date_default_timezone_get(),$atoken['atoken_expires']) : ''), ''),
'$submit' => t('Submit'),
'$delete' => t('Delete')
));

View File

@@ -1,9 +1,7 @@
<?php
namespace Zotlabs\Module;
use Zotlabs\Lib\Activity;
use Zotlabs\Lib\IConfig;
use Zotlabs\Lib\ObjCache;
class Viewsrc extends \Zotlabs\Web\Controller {
@@ -30,16 +28,13 @@ class Viewsrc extends \Zotlabs\Web\Controller {
$item_normal = item_normal_search();
if(local_channel() && $item_id) {
$r = q("select * from item where uid in (%d , %d) and id = %d $item_normal limit 1",
$r = q("select id, mid, uuid, item_flags, mimetype, item_obscured, body, llink, plink from item where uid in (%d , %d) and id = %d $item_normal limit 1",
intval(local_channel()),
intval($sys['channel_id']),
intval($item_id)
);
if($r) {
xchan_query($r, true);
$r = fetch_post_tags($r);
if(intval($r[0]['item_obscured']))
$dload = true;
@@ -50,41 +45,15 @@ class Viewsrc extends \Zotlabs\Web\Controller {
killme();
}
$cached = true;
$obj = ObjCache::Get($r[0]['mid']);
if (!$obj) {
$obj = IConfig::Get($r[0], 'activitypub', 'rawmsg');
}
if (in_array($r[0]['owner']['xchan_network'], ['diaspora'])) {
$obj = ObjCache::Get($r[0]['mid'], 'diaspora');
if (!$obj) {
$obj = IConfig::Get($r[0], 'diaspora', 'fields');
}
}
if (!$obj) {
$cached = false;
$obj = Activity::encode_activity($r[0]);
}
if ($obj) {
$content = (($cached) ? 'Cached: ' : '') . '<pre>' . escape_tags(json_encode($obj, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)) . '</pre>';
}
else {
$content = escape_tags($r[0]['body']);
}
$o = (($json) ? json_encode($content) : str_replace("\n", '<br>', $content));
$content = escape_tags($r[0]['body']);
$o = (($json) ? json_encode($content) : $content);
}
}
if(is_ajax()) {
echo '<div class="p-1">';
echo '<div>id: ' . $r[0]['id'] . ' | <a href="' . $r[0]['plink'] . '" target="_blank">plink</a> | <a href="' . $r[0]['llink'] . '" target="_blank">llink</a><br>mid: ' . $r[0]['mid'] . '<br>hashpath: ' . hash('sha256', $r[0]['mid']) . '<br>uuid: ' . $r[0]['uuid'] . '</div>';
echo '<div>id: ' . $r[0]['id'] . ' | <a href="' . $r[0]['plink'] . '" target="_blank">plink</a> | <a href="' . $r[0]['llink'] . '" target="_blank">llink</a><br>mid: ' . $r[0]['mid'] . '<br>uuid: ' . $r[0]['uuid'] . '</div>';
echo '<hr>';
echo '<pre class="p-1">' . $o . '</pre>';
echo '</div>';

View File

@@ -20,35 +20,27 @@ class Vote extends Controller {
json_return_and_die($ret);
}
$fetch = null;
$id = argv(1);
$response = $_REQUEST['answer'];
if (!$id) {
$ret['message'] = t('Missing poll id.');
json_return_and_die($ret);
if ($id) {
$fetch = q("select * from item where id = %d limit 1",
intval($id)
);
}
$answer = q("select * from item where parent = %d and uid = %d and obj_type = 'Answer' limit 1",
intval($id),
intval($channel['channel_id'])
);
if ($answer) {
$ret['message'] = t('You have already submitted your vote for this poll.');
json_return_and_die($ret);
if ($fetch && $fetch[0]['obj_type'] === 'Question') {
$obj = json_decode($fetch[0]['obj'],true);
}
$poll = q("select * from item where id = %d limit 1",
intval($id)
);
if (!$poll && $poll[0]['obj_type'] !== 'Question') {
else {
$ret['message'] = t('Poll not found.');
json_return_and_die($ret);
}
$response = $_REQUEST['answer'];
$obj = json_decode($poll[0]['obj'],true);
$valid = false;
if ($obj['oneOf']) {
@@ -89,18 +81,17 @@ class Vote extends Controller {
$item['aid'] = $channel['channel_account_id'];
$item['uid'] = $channel['channel_id'];
$item['item_origin'] = 1;
$item['parent'] = $poll[0]['id'];
$item['parent_mid'] = $poll[0]['mid'];
$item['thr_parent'] = $poll[0]['mid'];
$item['parent'] = $fetch[0]['id'];
$item['parent_mid'] = $fetch[0]['mid'];
$item['thr_parent'] = $fetch[0]['mid'];
$item['uuid'] = new_uuid();
$item['mid'] = z_root() . '/item/' . $item['uuid'];
$item['verb'] = 'Create';
$item['title'] = $res;
$item['author_xchan'] = $channel['channel_hash'];
$item['owner_xchan'] = $poll[0]['author_xchan'];
$item['allow_cid'] = '<' . $poll[0]['author_xchan'] . '>';
$item['owner_xchan'] = $fetch[0]['author_xchan'];
$item['allow_cid'] = '<' . $fetch[0]['author_xchan'] . '>';
$item['item_private'] = 1;
$item['item_unseen'] = 0;
$item['obj_type'] = 'Note';
$item['author'] = channelx_by_n($channel['channel_id']);
$item['obj'] = Activity::encode_item($item);
@@ -113,7 +104,7 @@ class Vote extends Controller {
$x = item_store($item);
retain_item($poll[0]['id']);
retain_item($fetch[0]['id']);
if($x['success']) {
Master::Summon(['Notifier', 'like', $x['item_id']]);
@@ -134,7 +125,7 @@ class Vote extends Controller {
}
$ret['success'] = true;
$ret['message'] = t('Your vote has been submitted. Updates may not appear instantly.');
$ret['message'] = t('Response submitted. Updates may not appear instantly.');
json_return_and_die($ret);
}
}

View File

@@ -1,49 +0,0 @@
<?php
/*
* SPDX-FileCopyrightText: 2025 The Hubzilla Community
* SPDX-FileContributor: Harald Eilertsen <haraldei@anduin.net>
*
* SPDX-License-Identifier: MIT
*/
namespace Zotlabs\Photo;
/**
* A class to represent image quality values.
*/
class ImageQuality
{
readonly string $mimeType;
readonly int $value;
private const DEFAULT_VALUE = [
'image/jpeg' => JPEG_QUALITY,
'image/png' => PNG_QUALITY,
'image/avif' => AVIF_QUALITY,
'image/webp' => WEBP_QUALITY,
];
public function __construct(string $mimeType, int $value) {
[ $min, $max ] = $this->validRange($mimeType);
if ($value < $min || $value > $max) {
$value = self::DEFAULT_VALUE[$mimeType];
}
$this->value = $value;
$this->mimeType = $mimeType;
}
/**
* Return the valid quality setting range for the given mime type.
*
* @param string $mimeType The mime type whose range to return.
*
* @return The valid range as an array with two elements as
* [ $min, $max ].
*/
public static function validRange(string $mimeType): array {
// Max quality for PNG is 10, for all others it's 100.
return $mimeType === 'image/png' ? [ 0, 10 ] : [ 1, 100 ];
}
}

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