Compare commits

..

118 Commits
11.0 ... 11.2

Author SHA1 Message Date
Mario
89a1af1794 Merge branch '11.2RC' 2026-03-26 08:08:37 +00:00
Mario
d5e0c24e13 version 11.2 2026-03-26 08:06:51 +00:00
Mario
8fe73d73ce Merge branch 'dev' into 11.2RC 2026-03-26 08:05:53 +00:00
Mario
7559d6bb5c update changelog 2026-03-26 08:05:06 +00:00
Mario
6c40576f94 Merge branch 'dev' into 11.2RC 2026-03-26 07:50:56 +00:00
Mario
1c9c0dc70e update changelog 2026-03-26 07:50:08 +00:00
Mario
bf4227bfef Merge branch 'drop-private-members-from-apidocs' into 'dev'
Remove private members from API docs

See merge request hubzilla/core!2275
2026-03-25 18:12:54 +00:00
Mario
c296d4bbed Merge branch 'drop-custom-ca-certs' into 'dev'
Remove custom CA certs

See merge request hubzilla/core!2274
2026-03-25 18:12:21 +00:00
Harald Eilertsen
68452fb07b Remove private members from API docs
Private class members are internal implementation details, and should
not be in public API documentation.
2026-03-25 18:29:38 +01:00
Harald Eilertsen
7f98cd606b Remove custom CA certs
The included collection of CA certificates has not been updated since
2021, which means it's outdated and possibly a security risk. Also it
should not be necessary, as curl is able to find the system installed CA
certs which is more likely to be up to date and safer.
2026-03-25 18:06:39 +01:00
Mario
d0ae7a0493 Merge branch 'route-apidoc-fixes' into 'dev'
Zotlabs\Extend\Route: API docs and visibility/type fixes

See merge request hubzilla/core!2273
2026-03-25 15:18:27 +00:00
Harald Eilertsen
81e79eec04 Zotlabs\Extend\Route: Add type annotations
Make the code safer by adding type annotations for parameters and return
values.
2026-03-25 15:34:24 +01:00
Harald Eilertsen
7d70f2f0f2 Zotlabs\Extend\Route: Set method visibility
Make `Route::set()` private. It is not referenced outside of the class
itself, and is also unsafe as it uncritically replaces the system routes
with whatever value it was passed.

The remaining functions are set to public visibility.
2026-03-25 12:45:44 +01:00
Harald Eilertsen
016a11ad27 Zotlabs\Extend\Route: Add API docs + licence info 2026-03-25 12:33:11 +01:00
Mario
1637e61681 update changelog 2026-03-25 10:30:27 +00:00
Mario
1ec0e91405 fix issue in route and widget unregister: we should remove only if both arguments are different. Also only register routes and widgets if they are not yet registered 2026-03-25 10:16:49 +00:00
Mario
3ab52a060b add sleep intervals when adding worker tasks in a loop in Activity::init_background_fetch()
(cherry picked from commit 955ee217e3)

Co-authored-by: Mario <mario@mariovavti.com>
2026-03-25 07:54:51 +00:00
Mario
866cd52073 Convo: fetched collections can be huge - add a slight delay between storing items if we did not fetch them from the network to allow the DB to do its thing
(cherry picked from commit 6d8bfe58ef)

Co-authored-by: Mario <mario@mariovavti.com>
2026-03-25 07:52:33 +00:00
Mario
1774140307 Storage/Directory: fix issue where we returned a partial path instead of throwing exception if a directory of a path could not be found 2026-03-23 20:57:46 +00:00
Mario
698dce044d rc2 2026-03-18 09:13:58 +00:00
Mario
bf0d73515e Merge branch 'dev' into 11.2RC 2026-03-18 09:13:23 +00:00
Mario
8152da1275 update changelog 2026-03-18 09:13:00 +00:00
Mario
01de48b994 Merge branch 'dev' into 11.2RC 2026-03-18 09:02:20 +00:00
Mario
eb10820195 Merge branch 'daemon-externals-endless-loop' into 'dev'
Daemon/Externals: Shorten endless loop

See merge request hubzilla/core!2272
2026-03-18 09:01:32 +00:00
Harald Eilertsen
9d33456c89 Daemon/Externals: Shorten endless loop
If the query towards the hubloc table don't return any entries, the loop
would continue without modifying any of the variables that could
possibly make the loop end.
2026-03-17 21:42:28 +01:00
Mario
56c7f76230 Merge branch 'dev' into 11.2RC 2026-03-17 11:47:27 +00:00
Mario
3dd9559d9f fix spdx info and remove superfluous use statements 2026-03-17 06:24:03 +00:00
Mario Vavti
51ac502d97 refactor drop_query_params() to deal with array params and add test 2026-03-16 10:32:59 +01:00
Mario
955ee217e3 add sleep intervals when adding worker tasks in a loop in Activity::init_background_fetch() 2026-03-14 19:58:09 +00:00
Mario
6d8bfe58ef Convo: fetched collections can be huge - add a slight delay between storing items if we did not fetch them from the network to allow the DB to do its thing 2026-03-14 19:41:26 +00:00
Mario
f98f540256 update changelog 2026-03-14 19:37:37 +00:00
Mario
54c7319075 fix version 2026-03-14 10:33:06 +00:00
Mario
72c930f964 remove accent color test 2026-03-14 10:31:32 +00:00
Mario
ce24b86841 fix fatal error in italian hstrings.php 2026-03-14 10:29:48 +00:00
Mario
f63ba541d7 bump dev version 2026-03-13 16:14:46 +00:00
Mario
a9f54473db version and strings and move hmessages.po to hmessages.pot 2026-03-13 16:11:13 +00:00
Mario
eb5ea6536b fix php warning 2026-03-11 19:05:39 +00:00
Mario
2e9b64347d Merge branch 'gettext-updates' into 'dev'
util/run_xgettext: Rename template to .pot and force PHP matching

See merge request hubzilla/core!2271
2026-03-11 18:48:47 +00:00
Mario
4fcff43fb5 Merge branch 'tests-make-loadFixtures-public' into 'dev'
tests: Make loadFixtures public

See merge request hubzilla/core!2270
2026-03-11 18:41:24 +00:00
Harald Eilertsen
260b61ba3b util/run_xgettext: Use PHP matching rules
Explicitly tell xgettext to use PHP matching rules to find translatable
strings.

Project......: Improve Superblock Addon
Sponsored-by.: NLnet NGI0 Commons Fund
2026-03-11 11:51:35 +01:00
Harald Eilertsen
d0b7e4ea79 util/run_xgettext: Rename template to .pot
Renaming the translation template to *.pot, works better with some
tooling like poedit and lokalize.

Project......: Improve Superblock Addon
Sponsored-by.: NLnet NGI0 Commons Fund
2026-03-11 11:48:09 +01:00
Harald Eilertsen
33c95c810a tests: Make loadFixtures public
Some tests will need to load their own database fixtures. By making this
API public we make it easy for them to do so.

This change is needed by the tests for the Superblock addon.

Project......: Improve Superblock Addon
Sponsored-by.: NLnet NGI0 Commons Fund
2026-03-11 11:40:31 +01:00
Mario
81105ff9de Merge branch 'null-date-fix' into 'dev'
Remove use of NULL_DATE constant in core

See merge request hubzilla/core!2269
2026-03-08 17:07:41 +00:00
Harald Eilertsen
ced3113516 Remove unneccessary use statements 2026-03-07 12:00:56 +01:00
Harald Eilertsen
cbd208eea3 Remove use of NULL_DATE constant in core
The NULL_DATE constant is defined conditionally in the DBA static class.
This causes issues with static analyzing tools like PHPStan, because
they can not really know if the constant is defined or not.

We could make PHPStan ignore this, but since there already is a
`get_null_date()` method on the `dba_driver` class, this patch
changes the code to use this method instead.

We could also use the public static attribute `$null_date` on the DBA
class directly, but using a method feels cleaner, and allows for making
the attribute private, or even removing it completely at some later
time.

I'm not removing the NULL_DATE constant for now, in case it is in use by
any extensions.
2026-03-07 11:15:46 +01:00
Mario Vavti
ad85825cab remove composer dev stuff 2026-03-04 18:34:45 +01:00
Mario Vavti
2fb816139a guzzlehttp/psr7 has been removed from the HttpSigner library because it is not required there, hubzilla still needs it 2026-03-04 18:29:33 +01:00
DDEV User
c9166b26c5 update composer libs 2026-03-04 17:51:14 +01:00
Mario Vavti
86d58065b3 deal with leading or missing @ and add more tests 2026-03-04 10:25:32 +01:00
Mario
1f265cc6d5 fix tests (hopefully) 2026-03-04 08:44:09 +00:00
Mario
4474fdd4f9 introduce parse_webbie() with basic tests 2026-03-04 08:34:45 +00:00
Mario
f71eeab5be fix php warning 2026-03-03 11:27:01 +00:00
Mario
b3526415f9 allow to override curl useragent 2026-03-03 10:59:11 +00:00
Mario
471ded3efa Merge branch 'sys_stat_fixes' into dev 2026-03-03 09:19:09 +00:00
Mario
e954d8c55e mod network: dismiss privacy filter for various filter options - fix issue #1973 2026-03-03 09:17:56 +00:00
Mario
70f82c3967 stop duplicating content in reactions - fix issue #1970 2026-03-03 07:57:39 +00:00
Mario
707e07bbbc Merge branch 'sys_stat_fixes' into 'dev'
Only poll perfstats if the widget is actually visible

See merge request hubzilla/core!2267
2026-03-02 13:47:51 +00:00
Mario
e89eb04427 Merge branch 'dev' into sys_stat_fixes 2026-03-02 08:59:45 +00:00
Mario
ae0e82ee3a wait for domn content loaded 2026-03-02 08:48:14 +00:00
Mario
9ea5d20a3d use JS listener instead of jquery 2026-03-02 08:45:42 +00:00
Mario
fd69008484 this probably should not be const but keep it non global anyway 2026-03-02 08:05:31 +00:00
Mario
3256aa8be9 only poll perfstats if the widget id actually visible (the widget is mostly hidden when viewing content in HQ) 2026-03-02 07:34:43 +00:00
Mario
add26a5b5f fix Queue::count() return value 2026-03-01 19:12:01 +00:00
Mario
dc03263bef Merge branch 'system-status-activity-widget' into 'dev'
Add system status activity widget to HQ for admins

See merge request hubzilla/core!2266
2026-03-01 18:42:54 +00:00
Harald Eilertsen
1897cd0b1b Make system status widget a bit more snappy
Fetch the initial data when the widget loads, and then update every five
sec after that. Also better indication of uninitialized values.

Project......: Performance Profiling
Sponsored-by.: NLnet NGI0 Commons Fund
2026-03-01 11:47:39 +01:00
Harald Eilertsen
ba24958b37 Remove some debug logging in system status widget
Project......: Performance Profiling
Sponsored-by.: NLnet NGI0 Commons Fund
2026-03-01 11:38:07 +01:00
Harald Eilertsen
52a2a0d89a Pass loadavg as array from Perfstats
There's no reason we should format the data into a string in the
Perfstats module. Let the recipient do what they want with it instead.

As an example, we reduce the precision of the loadavg stats in the
system status widget. 3 digits precision should be more than enough for
this type of status display.

Project......: Performance Profiling
Sponsored-by.: NLnet NGI0 Commons Fund
2026-03-01 11:34:28 +01:00
Harald Eilertsen
91944da69e Report queries/sec in system status widget
The total number of queries is not that interesting for performance
measurements, so let's do queries pr/second instead. Adds a timestamp
column to the dataset, which could be useful for other purposes as well
(like making a graph over time etc.)

Project......: Performance Profiling
Sponsored-by.: NLnet NGI0 Commons Fund
2026-03-01 11:16:05 +01:00
Harald Eilertsen
78e30a4d32 Simplify stats for getting size of output queue
Project......: Performance Profiling
Sponsored-by.: NLnet NGI0 Commons Fund
2026-02-26 16:28:59 +01:00
Harald Eilertsen
ccd6d1a38c Move db stats to separate classes for each db type
Project......: Performance Profiling
Sponsored-by.: NLnet NGI0 Commons Fund
2026-02-26 16:19:17 +01:00
Mario
f14c1be963 Some projects use double typing for its objects (bandwagon in this case) - we do not support this, hence go with the first entry and hope for the best. 2026-02-26 11:31:56 +00:00
Harald Eilertsen
db5e92b72d Remove unused/incomplete function
Project......: Performance Profiling
Sponsored-by.: NLnet NGI0 Commons Fund
2026-02-23 21:34:04 +01:00
Harald Eilertsen
3d3580b23f Validate requests to perfstats
Make some stricter rules for accessing the perfstats module. We only
want to respond to GET request for json data made by a site admin.

This means we also need to pass along the credentials in the request.
Didn't immediately figure out how to do that using `z_fetch_url`, so we
just fall back to using the JavaScript to populate the widget. This
makes it idle for about 5 sec before it gets the first data sample.

I think that's probably OK.

Project......: Performance Profiling
Sponsored-by.: NLnet NGI0 Commons Fund
2026-02-23 21:21:09 +01:00
Harald Eilertsen
04d44c9965 Remove link from system status widget title
At least for now, there's no sensible link target, so it's better to not
link anywhere.

Project......: Performance Profiling
Sponsored-by.: NLnet NGI0 Commons Fund
2026-02-23 21:21:09 +01:00
Harald Eilertsen
82bd91d9d7 Log error if system status can't load
Project......: Performance Profiling
Sponsored-by.: NLnet NGI0 Commons Fund
2026-02-23 21:21:09 +01:00
Harald Eilertsen
492533729d Human labels for system status widget
Project......: Performance Profiling
Sponsored-by.: NLnet NGI0 Commons Fund
2026-02-23 21:21:09 +01:00
Harald Eilertsen
283b606c09 Update system status activity widget periodically
Adds a bit of javascript that requests the performance stats every 5
seconds.

Project......: Performance Profiling
Sponsored-by.: NLnet NGI0 Commons Fund
2026-02-23 21:21:09 +01:00
Harald Eilertsen
b0b5523f2b Add perfstat module
Moves collecting performance statistics for the system status activity
widget to a separate module. This will make it easier to get the data
from JavaScript or external monitoring tools.

Project......: Performance Profiling
Sponsored-by.: NLnet NGI0 Commons Fund
2026-02-23 21:21:09 +01:00
Harald Eilertsen
738797467d Turn dba_driver params into attributes
To allow for inspecting the params used to connect to the database, we
now save the params in the dba_driver object instance as readonly
attributes. An exception is made to the $pass parameter which is set to
protected access, to make it slightly harder to accidentally leak it.

Project......: Performance Profiling
Sponsored-by.: NLnet NGI0 Commons Fund
2026-02-23 21:20:52 +01:00
Harald Eilertsen
a2ee5705f4 Add System Status panel to HQ for admins
Project......: Performance Profiling
Sponsored-by.: NLnet NGI0 Commons Fund
2026-02-23 20:15:58 +01:00
Harald Eilertsen
cc1713b69a Add Queue::get_undelivered() helper function
Returns the number of undelivered messages in the outq.

Instead of having direct database queries at various places in the code,
I thought it would be better to keep them in a more logical place.

Project......: Performance Profiling
Sponsored-by.: NLnet NGI0 Commons Fund
2026-02-23 20:15:06 +01:00
Harald Eilertsen
6f5e4c5c2e Add QueueWorkerStats class
A simple class to fetch and hold queueworker stats.

Project......: Performance Profiling
Sponsored-by.: NLnet NGI0 Commons Fund
2026-02-23 20:13:33 +01:00
Mario
3e0b9c01b6 Merge branch 'channel-manage-tpl' into 'dev'
move manage channel alert to tpl

See merge request hubzilla/core!2265
2026-02-17 09:22:07 +00:00
SK
25218fea43 move manage channel alert to tpl
allows a way to customize appearance of html for channel manager alerts
2026-02-15 06:42:35 +05:30
Mario
c907e569f1 fix wrong var after refactor 2026-02-14 10:41:52 +00:00
Mario
b6bec6f7b7 reverse logic 2026-02-14 10:29:14 +00:00
Mario
31b5cfe7ef if the event timezone is different from the channel timezone, display the timezone we have adjusted to instead of the actual event timezone. Otherwise we might create UX confusion about if the displayed time is already timezone adjusted or not. 2026-02-13 08:50:25 +00:00
Mario
0efb3c5c95 comment out deprecated use of tags and attachments in activities 2026-02-13 08:25:05 +00:00
Mario
d88f3169c8 fix possibly duplicated terms in activity object 2026-02-13 08:22:19 +00:00
Mario
3772e910df fixup after testing to get a minimalistic working config 2026-02-12 20:12:18 +00:00
Mario
e995d45b53 Merge branch 'patch-1' into 'dev'
Updated the nginx config example to meet the more modern approach.

See merge request hubzilla/core!2249
2026-02-12 19:45:21 +00:00
spirillen
669136bce7 Updated the nginx config example to meet the more modern approach. 2026-02-12 19:45:20 +00:00
Mario
2448e6df27 Merge branch 'fix-undefined-var-issues' into 'dev'
Fix various possibly undefined var issues

See merge request hubzilla/core!2264
2026-02-12 18:30:59 +00:00
Mario
4c0b37db66 fix required $x variable overwritten with attach_mkdirp() data on update 2026-02-12 18:18:00 +00:00
Mario Vavti
0ea3f3d36d update changelog 2026-01-30 14:14:11 +01:00
Mario Vavti
cb7dc2059a Remove capability to update xchan entries via api. If this functionality is required it should be implemented over a channel endpoint where xchan and channel info would be updated after verification. 2026-01-30 13:34:54 +01:00
Harald Eilertsen
e988bc9fae Fix various possibly undefined var issues
These are all cases where a variable is used where it may possibly be
undefined at the time of use. Typically the var is defined within a
conditional block, but is used outside of that block, or even within
other conditional blocks.

In the common case the variables will most likely be defined, but it is
possible to reach the code where they are used without the variable
being defined.

All issues discovered with PHPStan at level 1.
2026-01-29 22:31:16 +01:00
Mario Vavti
cb23a9e235 update changelog 2026-01-29 17:27:29 +01:00
Mario Vavti
8d9623674d fix issue where reply-to button is not diplayed for anonymous visitors allthough permission is granted 2026-01-29 17:25:45 +01:00
Mario Vavti
6d181ee69e update changelog 2026-01-29 16:56:58 +01:00
Mario Vavti
2d97f8fa25 fix wrong icon class 2026-01-29 16:45:27 +01:00
Mario Vavti
98a3c97820 fix issue deleting multiple files 2026-01-29 16:36:07 +01:00
Mario Vavti
89e1328ed0 update changelog 2026-01-29 12:38:59 +01:00
Mario
5cb4db0353 Merge branch 'dev-send_reg_approval' into 'dev'
(Re?)enable email to user after account approval

See merge request hubzilla/core!2263
2026-01-29 10:45:25 +00:00
Mario
dc43cd9a85 Merge branch 'dev-fix-approve-email' into 'dev'
Add new send_reg_approval_email_from_register function

See merge request hubzilla/core!2262
2026-01-29 10:42:21 +00:00
Mario
5a6a7386a8 Merge branch 'more-xchan-tests' into 'dev'
tests: A few more xchan tests

See merge request hubzilla/core!2261
2026-01-29 10:38:35 +00:00
Mario
4f545e31dd Merge branch 'tests-add-ajax-request-helper' into 'dev'
tests: Add ajax request helper to module test case

See merge request hubzilla/core!2260
2026-01-29 10:35:43 +00:00
Mario
437c0a8913 Merge branch 'search-module-tpl' into 'dev'
move html from search module to tpl

See merge request hubzilla/core!2258
2026-01-29 10:33:05 +00:00
ltning
268fccdb30 Fix SQL in send_reg_confirmation_email_from_register 2026-01-28 20:06:59 +00:00
ltning
bb7689be93 Return boolean instead of nonexistent array 2026-01-28 02:51:58 +00:00
ltning
bd63af69b2 (Re?)enable email to user after account approval
Existing function account_allow() does not seem to be used a whole
lot, and certainly not when approving a registration request.

Created new function send_reg_confirmation_email_from_register
(going for longest-function-name-in-Hubzilla) which attempts to
send such a confirmation email, and also a new email template.

Plugged this into the create_account_from_register function.

Open question: Should the sending of this email be among the success
criteria for the create_account_from_register function?
2026-01-28 02:51:46 +00:00
ltning
e5d4358d61 Clean up send_reg_approval_email_from_register, new email template
Since there is no easy way to produce direct links to approve/reject
registrations, create new email template which simply links to the
Accounts admin page.

In response to comments:
 - Add type annotations
 - Simplify some 'if' statements
 - add and fix some whitespace
 - simplify input arguments to the function
2026-01-27 16:32:16 +00:00
ltning
0fa4962620 Clean up some whitespace confusion in the previous commit 2026-01-27 16:32:16 +00:00
ltning
fe4d6229a4 Add new send_reg_approval_email_from_register function
Current send_reg_approval_email function expects account to already
exist, and does not seem to be called anywhere. New function takes
necessary information from the register table.

Added call to new function in Regate.php after verification is
done. Will fail and roll back if the function call fails or if no
admin accounts were found to notify (delivered<1).
2026-01-27 16:32:16 +00:00
Mario Vavti
99a1569d07 another hotfix for an issue which requires investigation 2026-01-27 12:26:06 +01:00
Harald Eilertsen
9fb5cd12be tests: A few more xchan tests 2026-01-27 09:43:44 +01:00
Harald Eilertsen
d85c737db7 tests: Add ajax request helper to module test case
This is a bit crude, but serves the purpose for now. To emulate an AJAX
request we set the `Content-Type` header to `application/json`, and json
encode the params in the body of the request.
2026-01-26 23:03:07 +01:00
SK
a0cb5fcb3f move html from search module to tpl 2026-01-22 15:26:20 +05:30
151 changed files with 17260 additions and 17410 deletions

View File

@@ -1,3 +1,49 @@
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

View File

@@ -6,6 +6,7 @@ use Zotlabs\Lib\Activity;
use Zotlabs\Lib\ActivityStreams;
use Zotlabs\Lib\ASCollection;
use Zotlabs\Lib\ASCache;
use Zotlabs\Lib\Config;
class Convo {
@@ -33,6 +34,7 @@ class Convo {
}
$force = $argv[4] ?? false;
$interval = Config::Get('queueworker', 'queue_interval', 500000);
foreach ($channels as $channel_id) {
$channel = channelx_by_n($channel_id);
@@ -46,6 +48,8 @@ class Convo {
}
foreach ($messages as $message) {
$network_fetch = false;
if (is_string($message)) {
$cached = ASCache::Get($message);
if ($cached) {
@@ -54,6 +58,8 @@ class Convo {
}
else {
// logger('convo_fetching: ' . $message);
$network_fetch = true;
$data = Activity::fetch($message, $channel);
if ($data) {
ASCache::Set($message, $data);
@@ -65,6 +71,12 @@ class Convo {
$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);

View File

@@ -2,6 +2,7 @@
namespace Zotlabs\Daemon;
use DBA;
use Zotlabs\Lib\Config;
use Zotlabs\Lib\ObjCache;
use Zotlabs\Lib\Libsync;
@@ -93,7 +94,7 @@ class Cron {
// delete expired access tokens
$r = q("select atoken_id from atoken where atoken_expires > '%s' and atoken_expires < %s",
dbesc(NULL_DATE),
dbesc(DBA::$dba->get_null_date()),
db_utcnow()
);
if ($r) {

View File

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

View File

@@ -9,10 +9,14 @@ 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]));
if (($argc > 1) && (intval($argv[1])))
$update_id = intval($argv[1]);
$update_id = intval($argv[1]);
if (!$update_id) {
logger('onedirsync: no update id');

View File

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

View File

@@ -2,6 +2,7 @@
namespace Zotlabs\Daemon;
use DBA;
use Zotlabs\Lib\Config;
class Poller {
@@ -117,7 +118,7 @@ class Poller {
// if we've never connected with them, start the mark for death countdown from now
if ($c <= NULL_DATE) {
if ($c <= DBA::$dba->get_null_date()) {
q("update abook set abook_connected = '%s' where abook_id = %d",
dbesc(datetime_convert()),
intval($contact['abook_id'])
@@ -173,7 +174,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(NULL_DATE),
dbesc(DBA::$dba->get_null_date()),
db_utcnow(),
db_quoteinterval('7 DAY')
);
@@ -184,7 +185,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'] > NULL_DATE)
if ($rr['ud_last'] > DBA::$dba->get_null_date())
if ($rr['ud_last'] > datetime_convert('UTC', 'UTC', 'now - 1 day'))
continue;

View File

@@ -1,23 +1,106 @@
<?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 {
static function register($file,$modname) {
/**
* 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 {
$rt = self::get();
foreach ($rt as $r) {
if ($r[0] === $file && $r[1] === $modname) {
return;
}
}
$rt[] = [ $file, $modname ];
self::set($rt);
}
static function unregister($file,$modname) {
/**
* 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 {
$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;
}
}
@@ -25,7 +108,23 @@ class Route {
}
}
static function unregister_by_file($file) {
/**
* 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 {
$rt = self::get();
if($rt) {
$n = [];
@@ -38,11 +137,18 @@ class Route {
}
}
static function get() {
/**
* 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 {
return Config::Get('system','routes',[]);
}
static function set($r) {
private static function set(array $r): mixed {
return Config::Set('system','routes',$r);
}
}

View File

@@ -8,6 +8,13 @@ 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);
}
@@ -17,7 +24,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

@@ -95,6 +95,8 @@ class ASCollection {
return false;
}
$data = null;
if (is_array($this->nextpage)) {
$data = $this->nextpage;
}

View File

@@ -3,6 +3,7 @@
namespace Zotlabs\Lib;
use App;
use DBA;
use Zotlabs\Access\PermissionLimits;
use Zotlabs\Access\PermissionRoles;
use Zotlabs\Access\Permissions;
@@ -91,6 +92,8 @@ class Activity {
logger('fetch: ' . $url, LOGGER_DEBUG);
$start_timestamp = microtime(true);
if (strpos($url, 'x-zot:') === 0) {
$x = ZotURL::fetch($url, $channel);
}
@@ -129,7 +132,6 @@ 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]);
}
@@ -567,7 +569,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'] > NULL_DATE) {
if ($i['expires'] > DBA::$dba->get_null_date()) {
$ret['expires'] = datetime_convert('UTC', 'UTC', $i['expires'], ATOM_TIME);
}
@@ -592,7 +594,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'] > NULL_DATE) {
if (array_key_exists('comments_closed', $i) && $i['comments_closed'] !== EMPTY_STR && $i['comments_closed'] > DBA::$dba->get_null_date()) {
if ($ret['commentPolicy']) {
$ret['commentPolicy'] .= ' ';
}
@@ -648,6 +650,7 @@ class Activity {
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
@@ -1123,6 +1126,7 @@ class Activity {
return [];
}
/* Those should not be required in activities anymore after version 11
$t = self::encode_taxonomy($i);
if ($t) {
$ret['tag'] = $t;
@@ -1132,6 +1136,7 @@ class Activity {
if ($a) {
$ret['attachment'] = $a;
}
*/
if (intval($i['item_private']) === 0) {
$ret['to'] = [ACTIVITY_PUBLIC_INBOX];
@@ -1560,7 +1565,7 @@ class Activity {
'abook_created' => datetime_convert(),
'abook_updated' => datetime_convert(),
'abook_connected' => datetime_convert(),
'abook_dob' => NULL_DATE,
'abook_dob' => DBA::$dba->get_null_date(),
'abook_pending' => intval(($automatic) ? 0 : 1),
'abook_instance' => z_root()
]
@@ -2012,6 +2017,8 @@ class Activity {
$multi = true;
}
$answer_found = false;
if ($response) {
$mid = $response['mid'];
$content = trim($response['title']);
@@ -2041,7 +2048,6 @@ class Activity {
}
}
$answer_found = false;
$foundPrevious = false;
if ($multi) {
for ($c = 0; $c < count($o['anyOf']); $c++) {
@@ -2083,7 +2089,7 @@ class Activity {
}
}
}
if ($pollItem['comments_closed'] > NULL_DATE) {
if ($pollItem['comments_closed'] > DBA::$dba->get_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
@@ -2138,11 +2144,13 @@ class Activity {
$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']) &&
($act->objprop('type') === 'Follow' || ActivityStreams::is_an_actor($act->objprop('type')))
($obj_type === 'Follow' || ActivityStreams::is_an_actor($obj_type))
) {
return false;
}
@@ -2215,7 +2223,7 @@ class Activity {
}
}
if (in_array($act->type, ['Invite', 'Create']) && $act->objprop('type') === 'Event') {
if (in_array($act->type, ['Invite', 'Create']) && $obj_type === 'Event') {
$s['mid'] = $s['parent_mid'] = $act->id;
}
@@ -2247,25 +2255,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']) . EOL . EOL . ($content['content'] ?? '');
$content['content'] = sprintf(t('Likes %1$s\'s %2$s'), $mention, $act->obj['type']);
}
if ($act->type === 'Dislike') {
$content['content'] = sprintf(t('Doesn\'t like %1$s\'s %2$s'), $mention, $act->obj['type']) . EOL . EOL . ($content['content'] ?? '');
$content['content'] = sprintf(t('Doesn\'t like %1$s\'s %2$s'), $mention, $act->obj['type']);
}
// handle event RSVPs
if (($act->objprop('type') === 'Event') || ($act->objprop('type') === 'Invite' && array_path_exists('object/type', $act->obj) && $act->obj['object']['type'] === 'Event')) {
if (in_array($obj_type, ['Event', 'Invite'])) {
if ($act->type === 'Accept') {
$content['content'] = sprintf(t('Will attend %s\'s event'), $mention) . EOL . EOL . ($content['content'] ?? '');
$content['content'] = sprintf(t('Will attend %s\'s event'), $mention);
}
if ($act->type === 'Reject') {
$content['content'] = sprintf(t('Will not attend %s\'s event'), $mention) . EOL . EOL . ($content['content'] ?? '');
$content['content'] = sprintf(t('Will not attend %s\'s event'), $mention);
}
if ($act->type === 'TentativeAccept') {
$content['content'] = sprintf(t('May attend %s\'s event'), $mention) . EOL . EOL . ($content['content'] ?? '');
$content['content'] = sprintf(t('May attend %s\'s event'), $mention);
}
if ($act->type === 'TentativeReject') {
$content['content'] = sprintf(t('May not attend %s\'s event'), $mention) . EOL . EOL . ($content['content'] ?? '');
$content['content'] = sprintf(t('May not attend %s\'s event'), $mention);
}
}
@@ -2299,7 +2307,7 @@ class Activity {
if ($s['mid'] === $s['parent_mid']) {
$s['item_thread_top'] = 1;
$s['item_nocomment'] = 0;
$s['comments_closed'] = NULL_DATE;
$s['comments_closed'] = DBA::$dba->get_null_date();
// it is a parent node - decode the comment policy info if present
if ($act->objprop('commentPolicy')) {
@@ -2375,7 +2383,7 @@ class Activity {
$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' && $act->objprop('type') === 'Question' && $s['edited'] === $s['created']) {
if ($act->type === 'Update' && $obj_type === 'Question' && $s['edited'] === $s['created']) {
$s['edited'] = datetime_convert();
}
@@ -2383,8 +2391,8 @@ class Activity {
$s['item_deleted'] = 1;
}
if ($act->objprop('type')) {
$s['obj_type'] = self::activity_obj_mapper($act->obj['type']);
if ($obj_type) {
$s['obj_type'] = self::activity_obj_mapper($obj_type);
}
$s['obj'] = $act->obj;
@@ -2445,7 +2453,7 @@ class Activity {
$s = self::bb_attach($s);
}
if ($act->objprop('type') === 'Question' && in_array($act->type, ['Create', 'Update'])) {
if ($obj_type === 'Question' && in_array($act->type, ['Create', 'Update'])) {
if ($act->objprop('endTime')) {
$s['comments_closed'] = datetime_convert('UTC', 'UTC', $act->obj['endTime']);
}
@@ -2457,7 +2465,7 @@ class Activity {
if (!$response_activity) {
if ($act->objprop('type') === 'Profile') {
if ($obj_type === 'Profile') {
$s['parent_mid'] = $s['mid'];
$s['item_thread_top'] = 1;
}
@@ -2467,7 +2475,7 @@ class Activity {
// right now just link to the largest mp4 we find that will fit in our
// standard content region
if ($act->objprop('type') === 'Video') {
if ($obj_type === 'Video') {
$vtypes = [
'video/mp4',
@@ -2549,7 +2557,7 @@ class Activity {
}
}
if ($act->objprop('type') === 'Audio') {
if ($obj_type === 'Audio') {
$atypes = [
'audio/mpeg',
@@ -2581,7 +2589,7 @@ class Activity {
}
if ($act->objprop('type') === 'Image' && strpos($s['body'], 'zrl=') === false) {
if ($obj_type === 'Image' && strpos($s['body'], 'zrl=') === false) {
$ptr = null;
@@ -2596,10 +2604,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) {
@@ -2609,7 +2617,7 @@ class Activity {
}
}
if ($act->objprop('type') === 'Page' && !$s['body']) {
if ($obj_type === 'Page' && !$s['body']) {
$ptr = null;
$purl = EMPTY_STR;
@@ -2649,7 +2657,7 @@ class Activity {
}
}
if (in_array($act->objprop('type'), ['Note', 'Article', 'Page', 'Question'])) {
if (in_array($obj_type, ['Note', 'Article', 'Page', 'Question'])) {
$ptr = null;
if (array_key_exists('url', $act->obj)) {
@@ -2692,7 +2700,7 @@ class Activity {
IConfig::Set($s, 'activitypub', 'recips', $act->raw_recips);
}
if ($act->objprop('type') === 'Event' && $act->objprop('timezone')) {
if ($obj_type === 'Event' && $act->objprop('timezone')) {
IConfig::Set($s, 'event', 'timezone', $act->objprop('timezone'), true);
}
@@ -3782,6 +3790,9 @@ 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'])) {
foreach (App::$cache['zot_fetch_objects'] as $mid => $info) {
$force = $info['force'];
@@ -3795,6 +3806,10 @@ class Activity {
}
Master::Summon(['Zotconvo', $channels_str, $mid, $force]);
if ($interval) {
usleep($interval);
}
}
}
@@ -3816,6 +3831,10 @@ class Activity {
}
Master::Summon(['Fetchparents', $channels_str, $observer_hash, $mid, $force]);
if ($interval) {
usleep($interval);
}
}
}
@@ -3832,6 +3851,10 @@ class Activity {
}
Master::Summon(['Convo', $channels_str, $observer_hash, $mid, $force]);
if ($interval) {
usleep($interval);
}
}
}

View File

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

43
Zotlabs/Lib/DbStats.php Normal file
View File

@@ -0,0 +1,43 @@
<?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

@@ -3,6 +3,7 @@
namespace Zotlabs\Lib;
use App;
use DBA;
use Zotlabs\Access\PermissionLimits;
use Zotlabs\Access\Permissions;
use Zotlabs\Daemon\Master;
@@ -348,7 +349,7 @@ class Libzot {
$next_birthday = datetime_convert('UTC', 'UTC', $record['data']['profile']['next_birthday']);
}
else {
$next_birthday = NULL_DATE;
$next_birthday = DBA::$dba->get_null_date();
}
$profile_assign = get_pconfig($channel['channel_id'], 'system', 'profile_assign', '');

View File

@@ -2,6 +2,7 @@
namespace Zotlabs\Lib;
use DBA;
use Zotlabs\Lib\Config;
use Zotlabs\Lib\Libzot;
use Zotlabs\Lib\Zotfinger;
@@ -216,7 +217,7 @@ class Libzotdir {
[
'site_url' => DIRECTORY_FALLBACK_MASTER,
'site_flags' => DIRECTORY_MODE_PRIMARY,
'site_update' => NULL_DATE,
'site_update' => DBA::$dba->get_null_date(),
'site_directory' => DIRECTORY_FALLBACK_MASTER . '/dirsearch',
'site_realm' => DIRECTORY_REALM,
'site_valid' => 1,
@@ -247,7 +248,7 @@ class Libzotdir {
$token = Config::Get('system','realm_token');
$syncdate = (($rr['site_sync'] <= NULL_DATE) ? datetime_convert('UTC','UTC','now - 2 days') : $rr['site_sync']);
$syncdate = (($rr['site_sync'] <= DBA::$dba->get_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'])
@@ -724,7 +725,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(NULL_DATE),
dbesc(DBA::$dba->get_null_date()),
dbesc(z_root()),
dbesc($addr),
intval($u[0]['ud_id'])

View File

@@ -0,0 +1,37 @@
<?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

@@ -0,0 +1,32 @@
<?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,6 +7,20 @@ 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

@@ -0,0 +1,28 @@
<?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

@@ -3,6 +3,7 @@
namespace Zotlabs\Lib;
use App;
use DBA;
use Zotlabs\Access\AccessList;
require_once('include/text.php');
@@ -415,7 +416,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'] > NULL_DATE) ? sprintf(t('Expires %s'), relative_time($item['expires'])) : ''),
'expiretime' => (($item['expires'] > DBA::$dba->get_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

@@ -8,6 +8,7 @@
namespace Zotlabs\Module;
use DBA;
use Zotlabs\Lib\Config;
require_once('include/account.php');
@@ -90,7 +91,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(NULL_DATE),
dbesc(DBA::$dba->get_null_date()),
intval(ACCOUNT_BLOCKED)
);
if ($r) {

View File

@@ -1,6 +1,7 @@
<?php
namespace Zotlabs\Module;
use DBA;
class Changeaddr extends \Zotlabs\Web\Controller {
@@ -29,7 +30,7 @@ class Changeaddr extends \Zotlabs\Web\Controller {
if(! ($x && $x['account']))
return;
if($account['account_password_changed'] > NULL_DATE) {
if($account['account_password_changed'] > DBA::$dba->get_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

@@ -3,6 +3,7 @@
namespace Zotlabs\Module;
use App;
use DBA;
use Zotlabs\Web\Controller;
use Zotlabs\Lib\Libsync;
use Zotlabs\Access\AccessList;
@@ -300,7 +301,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(NULL_DATE)
dbesc(DBA::$dba->get_null_date())
);
}
else {

View File

@@ -2,6 +2,7 @@
namespace Zotlabs\Module;
use App;
use DBA;
use Zotlabs\Lib\Config;
use Zotlabs\Web\Controller;
@@ -232,7 +233,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(NULL_DATE),
dbesc(DBA::$dba->get_null_date()),
dbesc($sync)
);

View File

@@ -2,6 +2,7 @@
namespace Zotlabs\Module;
use DBA;
use Zotlabs\Lib\PConfig;
use Zotlabs\Web\Controller;
@@ -21,7 +22,7 @@ class Feed extends Controller {
killme();
}
$params['begin'] = $_REQUEST['date_begin'] ?? NULL_DATE;
$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);

View File

@@ -7,6 +7,7 @@ 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;
@@ -331,7 +332,7 @@ class Import extends Controller {
else {
$photos = import_xchan_photo($xchan['xchan_photo_l'], $xchan['xchan_hash']);
if ($photos[4])
$photodate = NULL_DATE;
$photodate = DBA::$dba->get_null_date();
else
$photodate = $xchan['xchan_photo_date'];

View File

@@ -3,6 +3,7 @@
namespace Zotlabs\Module;
use App;
use DBA;
use URLify;
use Zotlabs\Lib\Config;
use Zotlabs\Lib\IConfig;
@@ -209,7 +210,7 @@ class Item extends Controller {
}
$expires = NULL_DATE;
$expires = DBA::$dba->get_null_date();
$route = '';
$parent_item = null;
@@ -559,7 +560,7 @@ class Item extends Controller {
if (!empty($_POST['expire'])) {
$expires = datetime_convert(date_default_timezone_get(), 'UTC', $_POST['expire']);
if ($expires <= datetime_convert())
$expires = NULL_DATE;
$expires = DBA::$dba->get_null_date();
}
}
@@ -801,7 +802,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 : NULL_DATE);
$comments_closed = (($nocomment) ? $comments_closed : DBA::$dba->get_null_date());
// determine if this is a wall post
@@ -875,7 +876,7 @@ class Item extends Controller {
if ($obj['endTime']) {
$d = datetime_convert('UTC','UTC', $obj['endTime']);
if ($d > NULL_DATE) {
if ($d > DBA::$dba->get_null_date()) {
$comments_closed = $d;
}
}
@@ -1075,12 +1076,14 @@ 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);
$item = fetch_post_tags($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);

View File

@@ -143,8 +143,11 @@ class Network extends \Zotlabs\Web\Controller {
}
}
if(x($_GET, 'search') || $file || (!$pf && $cid) || $hashtags || $verb || $category || $conv || $unseen)
if($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 = [];
@@ -363,13 +366,14 @@ class Network extends \Zotlabs\Web\Controller {
$sql_extra .= term_query('item', $file, TERM_FILE);
}
if ($dm) {
$sql_extra .= ' AND item.item_private = 2 ';
if (!$dismiss_privacy_filter) {
if ($dm) {
$sql_extra .= ' AND item.item_private = 2 ';
}
else {
$sql_extra .= ' AND item.item_private IN (0, 1) ';
}
}
else {
$sql_extra .= ' AND item.item_private IN (0, 1) ';
}
if($conv) {
$item_thread_top = '';

View File

@@ -3,6 +3,7 @@
namespace Zotlabs\Module;
use App;
use DBA;
use Zotlabs\Lib\Activity;
use Zotlabs\Lib\ActivityStreams;
use Zotlabs\Lib\Config;
@@ -47,7 +48,7 @@ class Outbox extends Controller {
$params = [];
$params['begin'] = ((x($_REQUEST, 'date_begin')) ? $_REQUEST['date_begin'] : NULL_DATE);
$params['begin'] = ((x($_REQUEST, 'date_begin')) ? $_REQUEST['date_begin'] : DBA::$dba->get_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

@@ -0,0 +1,79 @@
<?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

@@ -1,6 +1,7 @@
<?php
namespace Zotlabs\Module;
use DBA;
use Zotlabs\Lib\Config;
use Zotlabs\Lib\Libsync;
@@ -340,7 +341,7 @@ class Profiles extends \Zotlabs\Web\Controller {
$with = ((x($_POST,'with')) ? escape_tags(trim($_POST['with'])) : '');
if(! strlen($howlong))
$howlong = NULL_DATE;
$howlong = DBA::$dba->get_null_date();
else
$howlong = datetime_convert(date_default_timezone_get(),'UTC',$howlong);
@@ -774,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'] <= NULL_DATE ? '' : datetime_convert('UTC',date_default_timezone_get(),$r[0]['howlong']))),
'$howlong' => array('howlong', t('Since (date)'), ($r[0]['howlong'] <= DBA::$dba->get_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']),

View File

@@ -142,7 +142,15 @@ class Regate extends \Zotlabs\Web\Controller {
if (($flags & ACCOUNT_PENDING ) == ACCOUNT_PENDING) {
$nextpage = 'regate/' . bin2hex($did2) . $didx;
q("COMMIT");
$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));
}
}
elseif (($flags ^ REGISTER_AGREED) == 0) {

View File

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

View File

@@ -1,6 +1,7 @@
<?php
namespace Zotlabs\Module;
use DBA;
class Removeme extends \Zotlabs\Web\Controller {
@@ -29,7 +30,7 @@ class Removeme extends \Zotlabs\Web\Controller {
if(! ($x && $x['account']))
return;
if($account['account_password_changed'] > NULL_DATE) {
if($account['account_password_changed'] > DBA::$dba->get_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

@@ -150,13 +150,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 .= '<div id="live-search"></div>' . "\r\n";
$livesearch .= "<script> var profile_uid = " . ((intval(local_channel())) ? local_channel() : (-1))
. "; var netargs = '?f='; var profile_page = " . App::$pager['page'] . "; </script>\r\n";

View File

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

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'] = NULL_DATE;
$event['event_status_date'] = DBA::$dba->get_null_date();
$event['event_percent'] = 0;
$event['event_sequence'] = $event['event_sequence'] + 1;
$event['edited'] = datetime_convert();

View File

@@ -3,6 +3,7 @@
namespace Zotlabs\Module;
use App;
use DBA;
use Zotlabs\Web\Controller;
use Zotlabs\Lib\Apps;
use Zotlabs\Lib\AccessList;
@@ -84,7 +85,7 @@ class Tokens extends Controller {
if(trim($_POST['expires']))
$expires = datetime_convert(date_default_timezone_get(),'UTC',$_POST['expires']);
else
$expires = NULL_DATE;
$expires = DBA::$dba->get_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",
@@ -290,7 +291,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'] > 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'] > DBA::$dba->get_null_date()) ? datetime_convert('UTC',date_default_timezone_get(),$atoken['atoken_expires']) : ''), ''),
'$submit' => t('Submit'),
'$delete' => t('Delete')
));

View File

@@ -519,8 +519,8 @@ class Directory extends DAV\Node implements DAV\ICollection, DAV\IQuota, DAV\IMo
* @return void
*/
function getDir() {
logger('GetDir: ' . $this->ext_path, LOGGER_DEBUG);
$this->auth->log();
$modulename = \App::$module;
@@ -538,7 +538,6 @@ class Directory extends DAV\Node implements DAV\ICollection, DAV\IQuota, DAV\IMo
$file = trim($file, '/');
$path_arr = explode('/', $file);
if (! $path_arr)
return;
@@ -558,9 +557,9 @@ class Directory extends DAV\Node implements DAV\ICollection, DAV\IQuota, DAV\IMo
$this->auth->owner_id = $channel_id;
$this->auth->owner_nick = $channel_name;
$path = '/' . $channel_name;
$folder = '';
$os_path = '';
$not_found = '';
for ($x = 1; $x < count($path_arr); $x++) {
$r = q("select id, hash, filename, flags, is_dir from attach where folder = '%s' and filename = '%s' and uid = %d and is_dir != 0",
@@ -568,15 +567,31 @@ class Directory extends DAV\Node implements DAV\ICollection, DAV\IQuota, DAV\IMo
dbesc($path_arr[$x]),
intval($channel_id)
);
if ($r && intval($r[0]['is_dir'])) {
$folder = $r[0]['hash'];
if (strlen($os_path))
$os_path .= '/';
$os_path .= $folder;
$path = $path . '/' . $r[0]['filename'];
if (strlen($os_path)) {
$os_path .= '/';
}
$os_path .= $folder;
}
else {
// if we got a bogus path collect the
if (strlen($not_found)) {
$not_found .= ',';
}
$not_found .= $path_arr[$x];
}
}
if ($not_found) {
throw new DAV\Exception\NotFound("Path $file does not exist. One or more directories could not be found: $not_found");
}
$this->folder_hash = $folder;
$this->os_path = $os_path;
}

View File

@@ -9,6 +9,8 @@ namespace Zotlabs\Widget;
use App;
use Zotlabs\Lib\Apps;
use Zotlabs\Lib\Queue;
use Zotlabs\Lib\QueueWorkerStats;
class Channel_activities {
@@ -25,6 +27,9 @@ class Channel_activities {
self::$uid = local_channel();
self::$channel = App::get_channel();
if (is_site_admin()) {
self::get_system_status();
}
self::get_photos_activity();
self::get_files_activity();
self::get_webpages_activity();
@@ -38,6 +43,8 @@ class Channel_activities {
call_hooks('channel_activities_widget', $hookdata);
$activity_html = '';
if ($hookdata['activities']) {
$keys = array_column($hookdata['activities'], 'date');
array_multisort($keys, SORT_DESC, $hookdata['activities']);
@@ -46,10 +53,11 @@ class Channel_activities {
$activity_html .= replace_macros(
get_markup_template($a['tpl']),
[
'$url' => $a['url'],
'$url' => $a['url'] ?? null,
'$icon' => $a['icon'],
'$label' => $a['label'],
'$items' => $a['items']
'$items' => $a['items'],
'$labels' => $a['labels'] ?? [],
]
);
}
@@ -221,11 +229,17 @@ class Channel_activities {
$footer .= intval($notices[0]['total']) . ' ' . tt('notice', 'notices', intval($notices[0]['total']), 'noun');
}
$tpl = get_markup_template('manage_channel_item.tpl');
$i[] = [
'url' => z_root() . '/manage/' . $rr['channel_id'],
'title' => '',
'summary' => '<div class="text-truncate lh-sm"><img src="' . $rr['xchan_photo_s'] . '" class="menu-img-2">' . '<strong>' . $rr['channel_name'] . '</strong><br><small class="text-body-secondary">' . $rr['xchan_addr'] . '</small></div>',
'footer' => $footer
'url' => z_root() . '/manage/' . $rr['channel_id'],
'title' => '',
'summary' => replace_macros($tpl, [
'$photo' => $rr['xchan_photo_s'],
'$name' => $rr['channel_name'],
'$addr' => $rr['xchan_addr'],
]),
'footer' => $footer
];
$channels_activity++;
@@ -247,5 +261,29 @@ class Channel_activities {
}
private static function get_system_status(): void {
self::$activities['status'] = [
'label' => t('System status'),
'icon' => 'gpu-card',
'date' => datetime_convert(),
'items' => [
'loadavg' => '0 / 0 / 0',
'dbqueries' => 0,
'outqueue' => 0,
'queueworkers' => 0,
'workqsz' => 0,
'ts' => time(),
],
'tpl' => 'system_status_widget.tpl',
'labels' => [
'loadavg' => t('Load average'),
'dbqueries' => t('DB queries/sec'),
'outqueue' => t('Output queue'),
'queueworkers' => t('Queue workers'),
'workqsz' => t('Work queue size'),
],
];
}
}

View File

@@ -1,6 +1,7 @@
<?php
namespace Zotlabs\Widget;
use DBA;
use Zotlabs\Lib\Config;
/**
@@ -60,7 +61,7 @@ class Pinned {
$profile_link = chanlink_hash($item['author_xchan']);
$profile_name = $author['xchan_name'];
$commentable = ($item['item_nocomment'] == 0 && $item['comments_closed'] == NULL_DATE ? true : false);
$commentable = ($item['item_nocomment'] == 0 && $item['comments_closed'] == DBA::$dba->get_null_date() ? true : false);
$location = format_location($item);
$isevent = false;
@@ -131,7 +132,7 @@ class Pinned {
'isotime' => datetime_convert('UTC', date_default_timezone_get(), $item['created'], 'c'),
'localtime' => datetime_convert('UTC', date_default_timezone_get(), $item['created'], 'r'),
'editedtime' => (($item['edited'] != $item['created']) ? sprintf( t('last edited: %s'), datetime_convert('UTC', date_default_timezone_get(), $item['edited'], 'r') ) : ''),
'expiretime' => ($item['expires'] > NULL_DATE ? sprintf( t('Expires: %s'), datetime_convert('UTC', date_default_timezone_get(), $item['expires'], 'r') ) : ''),
'expiretime' => ($item['expires'] > DBA::$dba->get_null_date() ? sprintf( t('Expires: %s'), datetime_convert('UTC', date_default_timezone_get(), $item['expires'], 'r') ) : ''),
'verified' => $verified,
'forged' => $forged,
'location' => $location,

View File

@@ -70,7 +70,7 @@ require_once('include/security.php');
define('PLATFORM_NAME', 'hubzilla');
define('STD_VERSION', '11.0');
define('STD_VERSION', '11.2');
define('ZOT_REVISION', '6.0');
define('DB_UPDATE_VERSION', 1265);
@@ -1896,7 +1896,7 @@ function notice($s) {
$x = null;
$t = get_xconfig($hash, 'sse', 'timestamp', NULL_DATE);
$t = get_xconfig($hash, 'sse', 'timestamp', DBA::$dba->get_null_date());
if (datetime_convert('UTC', 'UTC', $t) < datetime_convert('UTC', 'UTC', '- 30 seconds')) {
set_xconfig($hash, 'sse', 'notifications', []);
@@ -1945,7 +1945,7 @@ function info($s) {
$x = null;
$t = get_xconfig($hash, 'sse', 'timestamp', NULL_DATE);
$t = get_xconfig($hash, 'sse', 'timestamp', DBA::$dba->get_null_date());
if (datetime_convert('UTC', 'UTC', $t) < datetime_convert('UTC', 'UTC', '- 30 seconds')) {
set_xconfig($hash, 'sse', 'notifications', []);

View File

@@ -57,7 +57,8 @@
"scssphp/scssphp": "^2.0.1",
"twbs/bootstrap-icons": "^1.11",
"macgirvin/http-message-signer": "^0.2.6",
"root23/php-json-canonicalization": "^1.0"
"root23/php-json-canonicalization": "^1.0",
"guzzlehttp/psr7": "^2.8"
},
"require-dev": {
"ext-yaml": "*",

219
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "08bc7d463277e84c8db1ce8cf1a90b7b",
"content-hash": "5593f8a57f112fef0e3e5bc50a0e9c26",
"packages": [
{
"name": "bakame/http-structured-fields",
@@ -155,16 +155,16 @@
},
{
"name": "brick/math",
"version": "0.14.1",
"version": "0.14.8",
"source": {
"type": "git",
"url": "https://github.com/brick/math.git",
"reference": "f05858549e5f9d7bb45875a75583240a38a281d0"
"reference": "63422359a44b7f06cae63c3b429b59e8efcc0629"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/brick/math/zipball/f05858549e5f9d7bb45875a75583240a38a281d0",
"reference": "f05858549e5f9d7bb45875a75583240a38a281d0",
"url": "https://api.github.com/repos/brick/math/zipball/63422359a44b7f06cae63c3b429b59e8efcc0629",
"reference": "63422359a44b7f06cae63c3b429b59e8efcc0629",
"shasum": ""
},
"require": {
@@ -203,7 +203,7 @@
],
"support": {
"issues": "https://github.com/brick/math/issues",
"source": "https://github.com/brick/math/tree/0.14.1"
"source": "https://github.com/brick/math/tree/0.14.8"
},
"funding": [
{
@@ -211,7 +211,7 @@
"type": "github"
}
],
"time": "2025-11-24T14:40:29+00:00"
"time": "2026-02-10T14:33:43+00:00"
},
{
"name": "bshaffer/oauth2-server-php",
@@ -1014,20 +1014,20 @@
},
{
"name": "league/uri",
"version": "7.7.0",
"version": "7.8.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/uri.git",
"reference": "8d587cddee53490f9b82bf203d3a9aa7ea4f9807"
"reference": "4436c6ec8d458e4244448b069cc572d088230b76"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/uri/zipball/8d587cddee53490f9b82bf203d3a9aa7ea4f9807",
"reference": "8d587cddee53490f9b82bf203d3a9aa7ea4f9807",
"url": "https://api.github.com/repos/thephpleague/uri/zipball/4436c6ec8d458e4244448b069cc572d088230b76",
"reference": "4436c6ec8d458e4244448b069cc572d088230b76",
"shasum": ""
},
"require": {
"league/uri-interfaces": "^7.7",
"league/uri-interfaces": "^7.8",
"php": "^8.1",
"psr/http-factory": "^1"
},
@@ -1041,11 +1041,11 @@
"ext-gmp": "to improve IPV4 host parsing",
"ext-intl": "to handle IDN host with the best performance",
"ext-uri": "to use the PHP native URI class",
"jeremykendall/php-domain-parser": "to resolve Public Suffix and Top Level Domain",
"league/uri-components": "Needed to easily manipulate URI objects components",
"league/uri-polyfill": "Needed to backport the PHP URI extension for older versions of PHP",
"jeremykendall/php-domain-parser": "to further parse the URI host and resolve its Public Suffix and Top Level Domain",
"league/uri-components": "to provide additional tools to manipulate URI objects components",
"league/uri-polyfill": "to backport the PHP URI extension for older versions of PHP",
"php-64bit": "to improve IPV4 host parsing",
"rowbot/url": "to handle WHATWG URL",
"rowbot/url": "to handle URLs using the WHATWG URL Living Standard specification",
"symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present"
},
"type": "library",
@@ -1100,7 +1100,7 @@
"docs": "https://uri.thephpleague.com",
"forum": "https://thephpleague.slack.com",
"issues": "https://github.com/thephpleague/uri-src/issues",
"source": "https://github.com/thephpleague/uri/tree/7.7.0"
"source": "https://github.com/thephpleague/uri/tree/7.8.0"
},
"funding": [
{
@@ -1108,20 +1108,20 @@
"type": "github"
}
],
"time": "2025-12-07T16:02:06+00:00"
"time": "2026-01-14T17:24:56+00:00"
},
{
"name": "league/uri-interfaces",
"version": "7.7.0",
"version": "7.8.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/uri-interfaces.git",
"reference": "62ccc1a0435e1c54e10ee6022df28d6c04c2946c"
"reference": "c5c5cd056110fc8afaba29fa6b72a43ced42acd4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/62ccc1a0435e1c54e10ee6022df28d6c04c2946c",
"reference": "62ccc1a0435e1c54e10ee6022df28d6c04c2946c",
"url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/c5c5cd056110fc8afaba29fa6b72a43ced42acd4",
"reference": "c5c5cd056110fc8afaba29fa6b72a43ced42acd4",
"shasum": ""
},
"require": {
@@ -1134,7 +1134,7 @@
"ext-gmp": "to improve IPV4 host parsing",
"ext-intl": "to handle IDN host with the best performance",
"php-64bit": "to improve IPV4 host parsing",
"rowbot/url": "to handle WHATWG URL",
"rowbot/url": "to handle URLs using the WHATWG URL Living Standard specification",
"symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present"
},
"type": "library",
@@ -1184,7 +1184,7 @@
"docs": "https://uri.thephpleague.com",
"forum": "https://thephpleague.slack.com",
"issues": "https://github.com/thephpleague/uri-src/issues",
"source": "https://github.com/thephpleague/uri-interfaces/tree/7.7.0"
"source": "https://github.com/thephpleague/uri-interfaces/tree/7.8.0"
},
"funding": [
{
@@ -1192,7 +1192,7 @@
"type": "github"
}
],
"time": "2025-12-07T16:03:21+00:00"
"time": "2026-01-15T06:54:53+00:00"
},
{
"name": "lukasreschke/id3parser",
@@ -1236,22 +1236,21 @@
},
{
"name": "macgirvin/http-message-signer",
"version": "v0.2.12",
"version": "v0.2.13",
"source": {
"type": "git",
"url": "https://github.com/macgirvin/HTTP-Message-Signer.git",
"reference": "6e5b25a5536576e5046f5b0a7db620b5eb451453"
"reference": "b231228c630bf88ac3415b2ba28c057654c9fbea"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/macgirvin/HTTP-Message-Signer/zipball/6e5b25a5536576e5046f5b0a7db620b5eb451453",
"reference": "6e5b25a5536576e5046f5b0a7db620b5eb451453",
"url": "https://api.github.com/repos/macgirvin/HTTP-Message-Signer/zipball/b231228c630bf88ac3415b2ba28c057654c9fbea",
"reference": "b231228c630bf88ac3415b2ba28c057654c9fbea",
"shasum": ""
},
"require": {
"bakame/http-structured-fields": "^2.0",
"ext-openssl": "*",
"guzzlehttp/psr7": "^2.0",
"paragonie/easy-ecc": "^1.3",
"php": "^8.1",
"phpseclib/phpseclib": "~3.0",
@@ -1259,6 +1258,7 @@
"psr/http-message": "^2.0"
},
"require-dev": {
"guzzlehttp/psr7": "^2.8",
"phpunit/phpunit": "^10.0"
},
"type": "library",
@@ -1274,9 +1274,9 @@
"description": "RFC 9421 HTTP Message Signer and Verifier for PSR-7 requests",
"support": {
"issues": "https://github.com/macgirvin/HTTP-Message-Signer/issues",
"source": "https://github.com/macgirvin/HTTP-Message-Signer/tree/v0.2.12"
"source": "https://github.com/macgirvin/HTTP-Message-Signer/tree/v0.2.13"
},
"time": "2025-12-02T18:48:05+00:00"
"time": "2026-02-01T19:54:05+00:00"
},
{
"name": "michelf/php-markdown",
@@ -1791,20 +1791,21 @@
"issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=Text_LanguageDetect",
"source": "https://github.com/pear/Text_LanguageDetect"
},
"abandoned": true,
"time": "2023-02-27T20:54:21+00:00"
},
{
"name": "phpseclib/phpseclib",
"version": "3.0.48",
"version": "3.0.49",
"source": {
"type": "git",
"url": "https://github.com/phpseclib/phpseclib.git",
"reference": "64065a5679c50acb886e82c07aa139b0f757bb89"
"reference": "6233a1e12584754e6b5daa69fe1289b47775c1b9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/64065a5679c50acb886e82c07aa139b0f757bb89",
"reference": "64065a5679c50acb886e82c07aa139b0f757bb89",
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/6233a1e12584754e6b5daa69fe1289b47775c1b9",
"reference": "6233a1e12584754e6b5daa69fe1289b47775c1b9",
"shasum": ""
},
"require": {
@@ -1885,7 +1886,7 @@
],
"support": {
"issues": "https://github.com/phpseclib/phpseclib/issues",
"source": "https://github.com/phpseclib/phpseclib/tree/3.0.48"
"source": "https://github.com/phpseclib/phpseclib/tree/3.0.49"
},
"funding": [
{
@@ -1901,7 +1902,7 @@
"type": "tidelift"
}
],
"time": "2025-12-15T11:51:42+00:00"
"time": "2026-01-27T09:17:28+00:00"
},
{
"name": "phpseclib/phpseclib2_compat",
@@ -3055,16 +3056,16 @@
},
{
"name": "smarty/smarty",
"version": "v5.7.0",
"version": "v5.8.0",
"source": {
"type": "git",
"url": "https://github.com/smarty-php/smarty.git",
"reference": "73da7e90f302175a570662fcb0ba41f57b7a92ab"
"reference": "78d259d3b971c59a0cd719c270cc5cbb740c36a7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/smarty-php/smarty/zipball/73da7e90f302175a570662fcb0ba41f57b7a92ab",
"reference": "73da7e90f302175a570662fcb0ba41f57b7a92ab",
"url": "https://api.github.com/repos/smarty-php/smarty/zipball/78d259d3b971c59a0cd719c270cc5cbb740c36a7",
"reference": "78d259d3b971c59a0cd719c270cc5cbb740c36a7",
"shasum": ""
},
"require": {
@@ -3119,7 +3120,7 @@
"support": {
"forum": "https://github.com/smarty-php/smarty/discussions",
"issues": "https://github.com/smarty-php/smarty/issues",
"source": "https://github.com/smarty-php/smarty/tree/v5.7.0"
"source": "https://github.com/smarty-php/smarty/tree/v5.8.0"
},
"funding": [
{
@@ -3127,20 +3128,20 @@
"type": "github"
}
],
"time": "2025-11-19T21:36:38+00:00"
"time": "2026-02-15T14:27:15+00:00"
},
{
"name": "spomky-labs/otphp",
"version": "11.4.1",
"version": "11.4.2",
"source": {
"type": "git",
"url": "https://github.com/Spomky-Labs/otphp.git",
"reference": "126c99b6cbbc18992cf3fba3b87931ba4e312482"
"reference": "2a1b503fd1c1a5c751ab3c5cd37f2d2d26ab74ad"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Spomky-Labs/otphp/zipball/126c99b6cbbc18992cf3fba3b87931ba4e312482",
"reference": "126c99b6cbbc18992cf3fba3b87931ba4e312482",
"url": "https://api.github.com/repos/Spomky-Labs/otphp/zipball/2a1b503fd1c1a5c751ab3c5cd37f2d2d26ab74ad",
"reference": "2a1b503fd1c1a5c751ab3c5cd37f2d2d26ab74ad",
"shasum": ""
},
"require": {
@@ -3185,7 +3186,7 @@
],
"support": {
"issues": "https://github.com/Spomky-Labs/otphp/issues",
"source": "https://github.com/Spomky-Labs/otphp/tree/11.4.1"
"source": "https://github.com/Spomky-Labs/otphp/tree/11.4.2"
},
"funding": [
{
@@ -3197,7 +3198,7 @@
"type": "patreon"
}
],
"time": "2026-01-05T13:20:36+00:00"
"time": "2026-01-23T10:53:01+00:00"
},
{
"name": "stephenhill/base58",
@@ -3321,16 +3322,16 @@
},
{
"name": "symfony/filesystem",
"version": "v7.4.0",
"version": "v7.4.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
"reference": "d551b38811096d0be9c4691d406991b47c0c630a"
"reference": "3ebc794fa5315e59fd122561623c2e2e4280538e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/d551b38811096d0be9c4691d406991b47c0c630a",
"reference": "d551b38811096d0be9c4691d406991b47c0c630a",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/3ebc794fa5315e59fd122561623c2e2e4280538e",
"reference": "3ebc794fa5315e59fd122561623c2e2e4280538e",
"shasum": ""
},
"require": {
@@ -3367,7 +3368,7 @@
"description": "Provides basic utilities for the filesystem",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/filesystem/tree/v7.4.0"
"source": "https://github.com/symfony/filesystem/tree/v7.4.6"
},
"funding": [
{
@@ -3387,7 +3388,7 @@
"type": "tidelift"
}
],
"time": "2025-11-27T13:27:24+00:00"
"time": "2026-02-25T16:50:00+00:00"
},
{
"name": "symfony/polyfill-ctype",
@@ -4259,27 +4260,27 @@
},
{
"name": "php-mock/php-mock",
"version": "2.6.2",
"version": "2.7.0",
"source": {
"type": "git",
"url": "https://github.com/php-mock/php-mock.git",
"reference": "e134d210e4707c29724ebc7fe50d220123f0fdd9"
"reference": "b59734f19765296bb0311942850d02288a224890"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-mock/php-mock/zipball/e134d210e4707c29724ebc7fe50d220123f0fdd9",
"reference": "e134d210e4707c29724ebc7fe50d220123f0fdd9",
"url": "https://api.github.com/repos/php-mock/php-mock/zipball/b59734f19765296bb0311942850d02288a224890",
"reference": "b59734f19765296bb0311942850d02288a224890",
"shasum": ""
},
"require": {
"php": "^5.6 || ^7.0 || ^8.0",
"phpunit/php-text-template": "^1 || ^2 || ^3 || ^4 || ^5"
"phpunit/php-text-template": "^1 || ^2 || ^3 || ^4 || ^5 || ^6"
},
"replace": {
"malkusch/php-mock": "*"
},
"require-dev": {
"phpunit/phpunit": "^5.7 || ^6.5 || ^7.5 || ^8.0 || ^9.0 || ^10.0 || ^11.0 || ^12.0",
"phpunit/phpunit": "^5.7 || ^6.5 || ^7.5 || ^8.0 || ^9.0 || ^10.0 || ^11.0 || ^12.0 || ^13.0",
"squizlabs/php_codesniffer": "^3.8"
},
"suggest": {
@@ -4323,7 +4324,7 @@
],
"support": {
"issues": "https://github.com/php-mock/php-mock/issues",
"source": "https://github.com/php-mock/php-mock/tree/2.6.2"
"source": "https://github.com/php-mock/php-mock/tree/2.7.0"
},
"funding": [
{
@@ -4331,29 +4332,29 @@
"type": "github"
}
],
"time": "2025-08-18T19:59:14+00:00"
"time": "2026-02-06T07:39:37+00:00"
},
{
"name": "php-mock/php-mock-integration",
"version": "3.0.0",
"version": "3.1.0",
"source": {
"type": "git",
"url": "https://github.com/php-mock/php-mock-integration.git",
"reference": "8ceb860f343a143af604efeb66a7a124381cc52e"
"reference": "cbbf39705ec13dece5b04133cef4e2fd3137a345"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-mock/php-mock-integration/zipball/8ceb860f343a143af604efeb66a7a124381cc52e",
"reference": "8ceb860f343a143af604efeb66a7a124381cc52e",
"url": "https://api.github.com/repos/php-mock/php-mock-integration/zipball/cbbf39705ec13dece5b04133cef4e2fd3137a345",
"reference": "cbbf39705ec13dece5b04133cef4e2fd3137a345",
"shasum": ""
},
"require": {
"php": ">=5.6",
"php-mock/php-mock": "^2.5",
"phpunit/php-text-template": "^1 || ^2 || ^3 || ^4 || ^5"
"phpunit/php-text-template": "^1 || ^2 || ^3 || ^4 || ^5 || ^6"
},
"require-dev": {
"phpunit/phpunit": "^5.7.27 || ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12"
"phpunit/phpunit": "^5.7.27 || ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || ^13"
},
"type": "library",
"autoload": {
@@ -4386,7 +4387,7 @@
],
"support": {
"issues": "https://github.com/php-mock/php-mock-integration/issues",
"source": "https://github.com/php-mock/php-mock-integration/tree/3.0.0"
"source": "https://github.com/php-mock/php-mock-integration/tree/3.1.0"
},
"funding": [
{
@@ -4394,26 +4395,26 @@
"type": "github"
}
],
"time": "2025-03-08T19:22:38+00:00"
"time": "2026-02-06T07:44:43+00:00"
},
{
"name": "php-mock/php-mock-phpunit",
"version": "2.14.0",
"version": "2.15.0",
"source": {
"type": "git",
"url": "https://github.com/php-mock/php-mock-phpunit.git",
"reference": "c074f7a260cb80bdc7cf0823dc23174bc49064e1"
"reference": "701df15b183f25af663af134eb71353cd838b955"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-mock/php-mock-phpunit/zipball/c074f7a260cb80bdc7cf0823dc23174bc49064e1",
"reference": "c074f7a260cb80bdc7cf0823dc23174bc49064e1",
"url": "https://api.github.com/repos/php-mock/php-mock-phpunit/zipball/701df15b183f25af663af134eb71353cd838b955",
"reference": "701df15b183f25af663af134eb71353cd838b955",
"shasum": ""
},
"require": {
"php": ">=7",
"php-mock/php-mock-integration": "^3.0",
"phpunit/phpunit": "^6 || ^7 || ^8 || ^9 || ^10.0.17 || ^11 || ^12.0.9"
"phpunit/phpunit": "^6 || ^7 || ^8 || ^9 || ^10.0.17 || ^11 || ^12.0.9 || ^13"
},
"require-dev": {
"mockery/mockery": "^1.3.6"
@@ -4454,7 +4455,7 @@
],
"support": {
"issues": "https://github.com/php-mock/php-mock-phpunit/issues",
"source": "https://github.com/php-mock/php-mock-phpunit/tree/2.14.0"
"source": "https://github.com/php-mock/php-mock-phpunit/tree/2.15.0"
},
"funding": [
{
@@ -4462,7 +4463,7 @@
"type": "github"
}
],
"time": "2025-11-19T21:07:31+00:00"
"time": "2026-02-06T09:12:10+00:00"
},
{
"name": "phpmd/phpmd",
@@ -4549,11 +4550,11 @@
},
{
"name": "phpstan/phpstan",
"version": "2.1.33",
"version": "2.1.40",
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/9e800e6bee7d5bd02784d4c6069b48032d16224f",
"reference": "9e800e6bee7d5bd02784d4c6069b48032d16224f",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/9b2c7aeb83a75d8680ea5e7c9b7fca88052b766b",
"reference": "9b2c7aeb83a75d8680ea5e7c9b7fca88052b766b",
"shasum": ""
},
"require": {
@@ -4598,7 +4599,7 @@
"type": "github"
}
],
"time": "2025-12-05T10:24:31+00:00"
"time": "2026-02-23T15:04:35+00:00"
},
{
"name": "phpunit/php-code-coverage",
@@ -4923,16 +4924,16 @@
},
{
"name": "phpunit/phpunit",
"version": "10.5.60",
"version": "10.5.63",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "f2e26f52f80ef77832e359205f216eeac00e320c"
"reference": "33198268dad71e926626b618f3ec3966661e4d90"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f2e26f52f80ef77832e359205f216eeac00e320c",
"reference": "f2e26f52f80ef77832e359205f216eeac00e320c",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/33198268dad71e926626b618f3ec3966661e4d90",
"reference": "33198268dad71e926626b618f3ec3966661e4d90",
"shasum": ""
},
"require": {
@@ -4953,7 +4954,7 @@
"phpunit/php-timer": "^6.0.0",
"sebastian/cli-parser": "^2.0.1",
"sebastian/code-unit": "^2.0.0",
"sebastian/comparator": "^5.0.4",
"sebastian/comparator": "^5.0.5",
"sebastian/diff": "^5.1.1",
"sebastian/environment": "^6.1.0",
"sebastian/exporter": "^5.1.4",
@@ -5004,7 +5005,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
"source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.60"
"source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.63"
},
"funding": [
{
@@ -5028,7 +5029,7 @@
"type": "tidelift"
}
],
"time": "2025-12-06T07:50:42+00:00"
"time": "2026-01-27T05:48:37+00:00"
},
{
"name": "psr/container",
@@ -5253,16 +5254,16 @@
},
{
"name": "sebastian/comparator",
"version": "5.0.4",
"version": "5.0.5",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/comparator.git",
"reference": "e8e53097718d2b53cfb2aa859b06a41abf58c62e"
"reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/e8e53097718d2b53cfb2aa859b06a41abf58c62e",
"reference": "e8e53097718d2b53cfb2aa859b06a41abf58c62e",
"url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/55dfef806eb7dfeb6e7a6935601fef866f8ca48d",
"reference": "55dfef806eb7dfeb6e7a6935601fef866f8ca48d",
"shasum": ""
},
"require": {
@@ -5318,7 +5319,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/comparator/issues",
"security": "https://github.com/sebastianbergmann/comparator/security/policy",
"source": "https://github.com/sebastianbergmann/comparator/tree/5.0.4"
"source": "https://github.com/sebastianbergmann/comparator/tree/5.0.5"
},
"funding": [
{
@@ -5338,7 +5339,7 @@
"type": "tidelift"
}
],
"time": "2025-09-07T05:25:07+00:00"
"time": "2026-01-24T09:25:16+00:00"
},
{
"name": "sebastian/complexity",
@@ -6117,16 +6118,16 @@
},
{
"name": "symfony/config",
"version": "v7.4.3",
"version": "v7.4.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/config.git",
"reference": "800ce889e358a53a9678b3212b0c8cecd8c6aace"
"reference": "9400e2f9226b3b64ebb0a8ae967ae84e54e39640"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/config/zipball/800ce889e358a53a9678b3212b0c8cecd8c6aace",
"reference": "800ce889e358a53a9678b3212b0c8cecd8c6aace",
"url": "https://api.github.com/repos/symfony/config/zipball/9400e2f9226b3b64ebb0a8ae967ae84e54e39640",
"reference": "9400e2f9226b3b64ebb0a8ae967ae84e54e39640",
"shasum": ""
},
"require": {
@@ -6172,7 +6173,7 @@
"description": "Helps you find, load, combine, autofill and validate configuration values of any kind",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/config/tree/v7.4.3"
"source": "https://github.com/symfony/config/tree/v7.4.6"
},
"funding": [
{
@@ -6192,20 +6193,20 @@
"type": "tidelift"
}
],
"time": "2025-12-23T14:24:27+00:00"
"time": "2026-02-25T16:50:00+00:00"
},
{
"name": "symfony/dependency-injection",
"version": "v7.4.3",
"version": "v7.4.6",
"source": {
"type": "git",
"url": "https://github.com/symfony/dependency-injection.git",
"reference": "54122901b6d772e94f1e71a75e0533bc16563499"
"reference": "a3f7d594ca53a34a7d39ae683fbca09408b0c598"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/dependency-injection/zipball/54122901b6d772e94f1e71a75e0533bc16563499",
"reference": "54122901b6d772e94f1e71a75e0533bc16563499",
"url": "https://api.github.com/repos/symfony/dependency-injection/zipball/a3f7d594ca53a34a7d39ae683fbca09408b0c598",
"reference": "a3f7d594ca53a34a7d39ae683fbca09408b0c598",
"shasum": ""
},
"require": {
@@ -6256,7 +6257,7 @@
"description": "Allows you to standardize and centralize the way objects are constructed in your application",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/dependency-injection/tree/v7.4.3"
"source": "https://github.com/symfony/dependency-injection/tree/v7.4.6"
},
"funding": [
{
@@ -6276,7 +6277,7 @@
"type": "tidelift"
}
],
"time": "2025-12-28T10:55:46+00:00"
"time": "2026-02-25T16:50:00+00:00"
},
{
"name": "symfony/service-contracts",
@@ -6518,5 +6519,5 @@
"platform-dev": {
"ext-yaml": "*"
},
"plugin-api-version": "2.6.0"
"plugin-api-version": "2.9.0"
}

View File

@@ -186,7 +186,7 @@ function create_account_from_register($arr) {
if ( ! $register ) return $result;
// account
$expires = NULL_DATE;
$expires = DBA::$dba->get_null_date();
$default_service_class = Config::Get('system','default_service_class');
if($default_service_class === false)
@@ -264,9 +264,10 @@ function create_account_from_register($arr) {
$result['account']['parent'] = $result['account']['account_id'];
}
$result['success'] = true;
call_hooks('register_account',$result);
if ( send_reg_confirmation_email_from_register($arr['reg_id']) ) {
$result['success'] = true;
call_hooks('register_account',$result);
}
return $result;
}
@@ -311,11 +312,59 @@ function verify_email_address(string $email): bool {
pop_lang();
if(! $res)
logger("send_reg_approval_email: failed sending email to: {$email}");
logger("send_reg_verification_email: failed sending email to: {$email}");
return $res;
}
/**
* Send email to user confirming approved registration.
*
* @param int $reg_id The reg_id of the user (from register table)
*
* @return bool `true` if the email was sucessfully sent, otherwise `false`.
*/
function send_reg_confirmation_email_from_register(int $reg_id): bool {
$register = q("SELECT reg_email, reg_lang FROM register WHERE reg_id = %d", $reg_id);
if (empty($register)) {
logger('send_reg_confirmation_email_from_register: could not find email address for for reg_id ' . $reg_id);
return false;
} else {
logger('send_reg_confirmation_email_from_register: sending confirmation email to ' . $register[0]['reg_email']);
}
if(strlen($register['reg_lang'])) {
push_lang($register['reg_lang']);
} else {
push_lang('en');
}
$email_msg = replace_macros(get_intltext_template('register_approved_eml.tpl'), array(
'$sitename' => Config::Get('system','sitename'),
'$siteurl' => z_root(),
'$email' => $register[0]['reg_email'],
));
$res = z_mail(
[
'toEmail' => $register[0]['reg_email'],
'messageSubject' => sprintf( t('Registration approved at %s'), Config::Get('system','sitename')),
'textVersion' => $email_msg,
]
);
pop_lang();
if ($res) {
return true;
} else {
logger('send_reg_confirmation_email_from_register: failed to send confirmation email to ' . $register[0]['reg_email']);
return false;
}
}
function send_reg_approval_email($arr) {
@@ -392,6 +441,88 @@ function send_reg_approval_email($arr) {
return($delivered ? true : false);
}
/**
* send_reg_approval_email_from_register
* @author ltning
* @since 2026-01-25
*
* Account approval after verification based on table register.
* This function sends email to admin(s).
*
*/
function send_reg_approval_email_from_register(int $reg_id): array {
$result = array('success' => false, 'message' => 'rid:' . $reg_id);
$now = datetime_convert();
$register = q("SELECT * FROM register WHERE reg_id = %d",
intval($reg_id)
);
if (empty($register)) {
logger('send_reg_approval_email: could not find data for reg_id ' . $reg_id);
return $result;
}
$r = q("select * from account where (account_roles & %d) >= 4096",
intval(ACCOUNT_ROLE_ADMIN)
);
$admins = array();
foreach($r as $rr) {
if (strlen($rr['account_email'])) {
$admins[] = array('email' => $rr['account_email'], 'lang' => $rr['account_lang']);
}
}
if (empty($admins)) {
logger('send_reg_approval_email: could not find any admins to notify for reg_id ' . $reg_id);
return $result;
}
$delivered = 0;
foreach($admins as $admin) {
if (strlen($admin['lang'])) {
push_lang($admin['lang']);
} else {
push_lang('en');
}
$email_msg = replace_macros(get_intltext_template('register_verify_eml_no_links.tpl'), array(
'$sitename' => Config::Get('system','sitename'),
'$siteurl' => z_root(),
'$email' => $register[0]['reg_email'],
'$details' => $register[0]['reg_atip']
));
$res = z_mail(
[
'toEmail' => $admin['email'],
'messageSubject' => sprintf( t('Registration request at %s'), Config::Get('system','sitename')),
'textVersion' => $email_msg,
]
);
if ($res) {
$delivered ++;
} else {
logger('send_reg_approval_email: failed to ' . $admin['email'] . 'reg_email: ' . $register[0]['reg_email']);
}
pop_lang();
}
$result['delivered'] = $delivered;
if($delivered > 0) {
$result['success'] = true;
}
return $result;
}
function send_register_success_email($email,$password) {
$email_msg = replace_macros(get_intltext_template('register_open_eml.tpl'), array(
@@ -770,7 +901,7 @@ function downgrade_accounts() {
and account_expires > '%s'
and account_expires < %s ",
intval(ACCOUNT_EXPIRED),
dbesc(NULL_DATE),
dbesc(DBA::$dba->get_null_date()),
db_getfunc('UTC_TIMESTAMP')
);
@@ -784,7 +915,7 @@ function downgrade_accounts() {
q("UPDATE account set account_service_class = '%s', account_expires = '%s'
where account_id = %d",
dbesc($basic),
dbesc(NULL_DATE),
dbesc(DBA::$dba->get_null_date()),
intval($rr['account_id'])
);
$ret = array('account' => $rr);
@@ -1083,7 +1214,7 @@ function get_pending_accounts($get_all = false) {
function remove_expired_registrations() {
q("DELETE FROM register WHERE (reg_expires < '%s' OR reg_expires = '%s') AND (reg_flags & %d) > 0",
dbesc(datetime_convert()),
dbesc(NULL_DATE),
dbesc(DBA::$dba->get_null_date()),
dbesc(ACCOUNT_UNVERIFIED)
);
}

View File

@@ -105,7 +105,7 @@
$records = 10;
}
if(! $_REQUEST['since'])
$start = NULL_DATE;
$start = DBA::$dba->get_null_date();
else {
$start = datetime_convert(date_default_timezone_get(),'UTC', $_REQUEST['since']);
}
@@ -210,7 +210,7 @@
$start = ((array_key_exists('start',$_REQUEST)) ? intval($_REQUEST['start']) : 0);
$records = ((array_key_exists('records',$_REQUEST)) ? intval($_REQUEST['records']) : 0);
$since = ((array_key_exists('since',$_REQUEST)) ? datetime_convert(date_default_timezone_get(),'UTC',$_REQUEST['since']) : NULL_DATE);
$since = ((array_key_exists('since',$_REQUEST)) ? datetime_convert(date_default_timezone_get(),'UTC',$_REQUEST['since']) : DBA::$dba->get_null_date());
$until = ((array_key_exists('until',$_REQUEST)) ? datetime_convert(date_default_timezone_get(),'UTC',$_REQUEST['until']) : datetime_convert());
$x = attach_list_files(api_user(),get_observer_hash(),$hash,$filename,$filetype,'created asc',$start,$records, $since, $until);

View File

@@ -226,7 +226,7 @@ function attach_list_files($channel_id, $observer, $hash = '', $filename = '', $
$limit = " LIMIT " . intval($entries) . " OFFSET " . intval($start) . " ";
if(! $since)
$since = NULL_DATE;
$since = DBA::$dba->get_null_date();
if(! $until)
$until = datetime_convert();
@@ -660,6 +660,7 @@ function attach_store($channel, $observer_hash, $options = '', $arr = null) {
dbesc($arr['hash']),
intval($channel_id)
);
if(! $x) {
logger('update file source not found');
$ret['message'] = t('Cannot locate file to revise/update');
@@ -729,14 +730,14 @@ function attach_store($channel, $observer_hash, $options = '', $arr = null) {
$direct = null;
if($pathname) {
$x = attach_mkdirp($channel, $observer_hash, $darr);
$folder_hash = (($x['success']) ? $x['data']['hash'] : '');
$direct = (($x['success']) ? $x['data'] : null);
$new_dir = attach_mkdirp($channel, $observer_hash, $darr);
$folder_hash = (($new_dir['success']) ? $new_dir['data']['hash'] : '');
$direct = (($new_dir['success']) ? $new_dir['data'] : null);
if((! $str_contact_allow) && (! $str_group_allow) && (! $str_contact_deny) && (! $str_group_deny)) {
$str_contact_allow = $x['data']['allow_cid'];
$str_group_allow = $x['data']['allow_gid'];
$str_contact_deny = $x['data']['deny_cid'];
$str_group_deny = $x['data']['deny_gid'];
$str_contact_allow = $new_dir['data']['allow_cid'];
$str_group_allow = $new_dir['data']['allow_gid'];
$str_contact_deny = $new_dir['data']['deny_cid'];
$str_group_deny = $new_dir['data']['deny_gid'];
}
}
else {

View File

@@ -1302,7 +1302,7 @@ function channel_export_items_page($channel_id, $start, $finish, $page = 0, $lim
}
if(! $start)
$start = NULL_DATE;
$start = DBA::$dba->get_null_date();
else
$start = datetime_convert('UTC', 'UTC', $start);
@@ -1823,7 +1823,7 @@ function advanced_profile() {
if(App::$profile['partner'])
$profile['marital']['partner'] = zidify_links(bbcode(App::$profile['partner']));
if(strlen(App::$profile['howlong']) && App::$profile['howlong'] > NULL_DATE) {
if(strlen(App::$profile['howlong']) && App::$profile['howlong'] > DBA::$dba->get_null_date()) {
$profile['howlong'] = relative_date(App::$profile['howlong'], t('for %1$d %2$s'));
}
@@ -2649,10 +2649,10 @@ function channel_store_lowlevel($arr) {
'channel_eprvkey' => ((array_key_exists('channel_eprvkey',$arr)) ? $arr['channel_eprvkey'] : ''),
'channel_notifyflags' => ((array_key_exists('channel_notifyflags',$arr)) ? $arr['channel_notifyflags'] : '65535'),
'channel_pageflags' => ((array_key_exists('channel_pageflags',$arr)) ? $arr['channel_pageflags'] : '0'),
'channel_dirdate' => ((array_key_exists('channel_dirdate',$arr)) ? $arr['channel_dirdate'] : NULL_DATE),
'channel_lastpost' => ((array_key_exists('channel_lastpost',$arr)) ? $arr['channel_lastpost'] : NULL_DATE),
'channel_deleted' => ((array_key_exists('channel_deleted',$arr)) ? $arr['channel_deleted'] : NULL_DATE),
'channel_active' => ((array_key_exists('channel_active',$arr)) ? $arr['channel_active'] : NULL_DATE),
'channel_dirdate' => ((array_key_exists('channel_dirdate',$arr)) ? $arr['channel_dirdate'] : DBA::$dba->get_null_date()),
'channel_lastpost' => ((array_key_exists('channel_lastpost',$arr)) ? $arr['channel_lastpost'] : DBA::$dba->get_null_date()),
'channel_deleted' => ((array_key_exists('channel_deleted',$arr)) ? $arr['channel_deleted'] : DBA::$dba->get_null_date()),
'channel_active' => ((array_key_exists('channel_active',$arr)) ? $arr['channel_active'] : DBA::$dba->get_null_date()),
'channel_max_anon_mail' => ((array_key_exists('channel_max_anon_mail',$arr)) ? $arr['channel_max_anon_mail'] : '10'),
'channel_max_friend_req' => ((array_key_exists('channel_max_friend_req',$arr)) ? $arr['channel_max_friend_req'] : '10'),
'channel_expire_days' => ((array_key_exists('channel_expire_days',$arr)) ? $arr['channel_expire_days'] : '0'),
@@ -2695,7 +2695,7 @@ function profile_store_lowlevel($arr) {
'gender' => ((array_key_exists('gender',$arr)) ? $arr['gender'] : ''),
'marital' => ((array_key_exists('marital',$arr)) ? $arr['marital'] : ''),
'partner' => ((array_key_exists('partner',$arr)) ? $arr['partner'] : ''),
'howlong' => ((array_key_exists('howlong',$arr)) ? $arr['howlong'] : NULL_DATE),
'howlong' => ((array_key_exists('howlong',$arr)) ? $arr['howlong'] : DBA::$dba->get_null_date()),
'sexual' => ((array_key_exists('sexual',$arr)) ? $arr['sexual'] : ''),
'politic' => ((array_key_exists('politic',$arr)) ? $arr['politic'] : ''),
'religion' => ((array_key_exists('religion',$arr)) ? $arr['religion'] : ''),

View File

@@ -12,10 +12,10 @@ function abook_store_lowlevel($arr) {
'abook_my_perms' => ((array_key_exists('abook_my_perms',$arr)) ? $arr['abook_my_perms'] : 0),
'abook_their_perms' => ((array_key_exists('abook_their_perms',$arr)) ? $arr['abook_their_perms'] : 0),
'abook_closeness' => ((array_key_exists('abook_closeness',$arr)) ? $arr['abook_closeness'] : 99),
'abook_created' => ((array_key_exists('abook_created',$arr)) ? $arr['abook_created'] : NULL_DATE),
'abook_updated' => ((array_key_exists('abook_updated',$arr)) ? $arr['abook_updated'] : NULL_DATE),
'abook_connected' => ((array_key_exists('abook_connected',$arr)) ? $arr['abook_connected'] : NULL_DATE),
'abook_dob' => ((array_key_exists('abook_dob',$arr)) ? $arr['abook_dob'] : NULL_DATE),
'abook_created' => ((array_key_exists('abook_created',$arr)) ? $arr['abook_created'] : DBA::$dba->get_null_date()),
'abook_updated' => ((array_key_exists('abook_updated',$arr)) ? $arr['abook_updated'] : DBA::$dba->get_null_date()),
'abook_connected' => ((array_key_exists('abook_connected',$arr)) ? $arr['abook_connected'] : DBA::$dba->get_null_date()),
'abook_dob' => ((array_key_exists('abook_dob',$arr)) ? $arr['abook_dob'] : DBA::$dba->get_null_date()),
'abook_flags' => ((array_key_exists('abook_flags',$arr)) ? $arr['abook_flags'] : 0),
'abook_blocked' => ((array_key_exists('abook_blocked',$arr)) ? $arr['abook_blocked'] : 0),
'abook_ignored' => ((array_key_exists('abook_ignored',$arr)) ? $arr['abook_ignored'] : 0),

View File

@@ -630,7 +630,7 @@ function conversation($items, $mode, $update, $page_mode = 'traditional', $prepa
'isotime' => datetime_convert('UTC', date_default_timezone_get(), $item['created'], 'c'),
'localtime' => datetime_convert('UTC', date_default_timezone_get(), $item['created'], 'r'),
'editedtime' => (($item['edited'] != $item['created']) ? sprintf( t('last edited: %s'), datetime_convert('UTC', date_default_timezone_get(), $item['edited'], 'r')) : ''),
'expiretime' => (($item['expires'] > NULL_DATE) ? sprintf( t('Expires: %s'), datetime_convert('UTC', date_default_timezone_get(), $item['expires'], 'r')):''),
'expiretime' => (($item['expires'] > DBA::$dba->get_null_date()) ? sprintf( t('Expires: %s'), datetime_convert('UTC', date_default_timezone_get(), $item['expires'], 'r')):''),
'location' => $location,
'divider' => false,
'indent' => '',

View File

@@ -101,19 +101,13 @@ abstract class dba_driver {
public $error = false;
/**
* @brief Connect to the database.
* Connects to the database.
*
* This abstract function needs to be implemented in the real driver.
*
* @param string $server DB server name
* @param string $scheme DB scheme
* @param string $port DB port
* @param string $user DB username
* @param string $pass DB password
* @param string $db database name
* @return bool
*/
abstract function connect($server, $scheme, $port, $user, $pass, $db, $db_charset);
abstract function connect(): bool;
/**
* @brief Perform a DB query with the SQL statement $sql.
@@ -147,18 +141,27 @@ abstract class dba_driver {
*/
abstract function getdriver();
function __construct($server, $scheme, $port, $user,$pass,$db,$db_charset,$install = false) {
if(($install) && (! $this->install($server, $scheme, $port, $user, $pass, $db, $db_charset))) {
function __construct(
readonly string $server,
readonly string $scheme,
readonly string $port,
readonly string $user,
protected string $pass,
readonly string $dbname,
readonly string $db_charset,
$install = false)
{
if ($install && ! $this->install()) {
return;
}
$this->connect($server, $scheme, $port, $user, $pass, $db, $db_charset);
$this->connect();
}
function get_null_date() {
return \DBA::$null_date;
}
function get_install_script() {
public static function get_install_script() {
$platform_name = \Zotlabs\Lib\System::get_platform_name();
if(file_exists('install/' . $platform_name . '/' . \DBA::$install_script))
return 'install/' . $platform_name . '/' . \DBA::$install_script;
@@ -174,8 +177,8 @@ abstract class dba_driver {
return \DBA::$utc_now;
}
function install($server,$scheme,$port,$user,$pass,$db) {
if (!(strlen($server) && strlen($user))){
function install() {
if (!strlen($this->server) && strlen($this->user)) {
$this->connected = false;
$this->db = null;
return false;
@@ -269,7 +272,7 @@ function dbg($state) {
function dbesc($str) {
if(is_null_date($str))
$str = NULL_DATE;
$str = DBA::$dba->get_null_date();
if(\DBA::$dba && \DBA::$dba->connected)
return(\DBA::$dba->escape($str));
@@ -286,7 +289,7 @@ function dbunescbin($str) {
function dbescdate($date) {
if(is_null_date($date))
return \DBA::$dba->escape(NULL_DATE);
return \DBA::$dba->escape(DBA::$dba->get_null_date());
return \DBA::$dba->escape($date);
}
@@ -389,7 +392,7 @@ function dbq($sql) {
function dbesc_array_cb(&$item, $key) {
if(is_string($item)) {
if(is_null_date($item))
$item = NULL_DATE;
$item = DBA::$dba->get_null_date();
$item = dbesc($item);
}
}

View File

@@ -14,31 +14,35 @@ class dba_pdo extends dba_driver {
/**
* {@inheritDoc}
*
* @see dba_driver::connect()
*/
function connect($server, $scheme, $port, $user, $pass, $db, $db_charset) {
function connect(): bool {
$this->driver_dbtype = $scheme;
$this->driver_dbtype = $this->scheme;
if(strpbrk($server,':;')) {
$dsn = $this->driver_dbtype . ':unix_socket=' . trim($server, ':;');
if(strpbrk($this->server,':;')) {
$dsn = $this->driver_dbtype . ':unix_socket=' . trim($this->server, ':;');
}
else {
$dsn = $this->driver_dbtype . ':host=' . $server . (intval($port) ? ';port=' . $port : '');
$dsn = $this->driver_dbtype
. ':host='
. $this->server
. (intval($this->port) ? ';port=' . $this->port : '');
}
$dsn .= ';dbname=' . $db;
$dsn .= ';dbname=' . $this->dbname;
if ($this->driver_dbtype === 'mysql') {
$dsn .= ';charset=' . $db_charset;
$dsn .= ';charset=' . $this->db_charset;
}
else {
$dsn .= ";options='--client_encoding=" . $db_charset . "'";
$dsn .= ";options='--client_encoding=" . $this->db_charset . "'";
}
try {
$this->db = new PDO($dsn,$user,$pass);
$this->db->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);
$this->db = new PDO($dsn, $this->user, $this->pass);
$this->db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$this->server_version = $this->db->getAttribute(PDO::ATTR_SERVER_VERSION);
}
catch(PDOException $e) {

View File

@@ -162,7 +162,7 @@ function format_event_obj($jobject) {
'$dtend_dt' => $dtend_dt,
'$allday' => $allday,
'$oneday' => $oneday,
'$event_tz' => ['label' => t('Timezone'), 'value' => (($tz === date_default_timezone_get()) ? '' : $tz)]
'$tz' => ['label' => t('Timezone'), 'value' => (($tz && $tz !== date_default_timezone_get()) ? date_default_timezone_get() : '')]
));
$event['content'] = replace_macros(get_markup_template('event_item_content.tpl'), array(
@@ -537,14 +537,14 @@ function event_store_event($arr) {
$arr['deny_gid'] = $arr['deny_gid'] ?? '';
if (! $arr['dtend']) {
$arr['dtend'] = NULL_DATE;
$arr['dtend'] = DBA::$dba->get_null_date();
$arr['nofinish'] = 1;
}
if(array_key_exists('event_status_date',$arr))
$arr['event_status_date'] = datetime_convert('UTC','UTC', $arr['event_status_date']);
else
$arr['event_status_date'] = NULL_DATE;
$arr['event_status_date'] = DBA::$dba->get_null_date();
$existing_event = null;

View File

@@ -25,7 +25,7 @@ function get_public_feed($channel, $params) {
$params = [];
$params['type'] = ((x($params,'type')) ? $params['type'] : 'xml');
$params['begin'] = ((x($params,'begin')) ? $params['begin'] : NULL_DATE);
$params['begin'] = ((x($params,'begin')) ? $params['begin'] : DBA::$dba->get_null_date());
$params['end'] = ((x($params,'end')) ? $params['end'] : datetime_convert('UTC','UTC','now'));
$params['start'] = ((x($params,'start')) ? $params['start'] : 0);
$params['records'] = ((x($params,'records')) ? $params['records'] : 40);
@@ -1302,7 +1302,7 @@ function consume_feed($xml, $importer, &$contact, $pass = 0) {
if($r) {
$parent_item = $r[0];
if(intval($parent_item['item_nocomment']) || $parent_item['comment_policy'] === 'none'
|| ($parent_item['comments_closed'] > NULL_DATE && $parent_item['comments_closed'] < datetime_convert())) {
|| ($parent_item['comments_closed'] > DBA::$dba->get_null_date() && $parent_item['comments_closed'] < datetime_convert())) {
logger('comments disabled for post ' . $parent_item['mid']);
continue;
}
@@ -1459,7 +1459,7 @@ function consume_feed($xml, $importer, &$contact, $pass = 0) {
$datarray['owner_xchan'] = $contact['xchan_hash'];
if(array_key_exists('created',$datarray) && $datarray['created'] > NULL_DATE && $expire_days) {
if(array_key_exists('created',$datarray) && $datarray['created'] > DBA::$dba->get_null_date() && $expire_days) {
$t1 = $datarray['created'];
$t2 = datetime_convert('UTC','UTC','now - ' . $expire_days . 'days');
if($t1 < $t2) {

View File

@@ -35,8 +35,8 @@ function hubloc_store_lowlevel($arr) {
'hubloc_callback' => ((array_key_exists('hubloc_callback',$arr)) ? $arr['hubloc_callback'] : ''),
'hubloc_connect' => ((array_key_exists('hubloc_connect',$arr)) ? $arr['hubloc_connect'] : ''),
'hubloc_sitekey' => ((array_key_exists('hubloc_sitekey',$arr)) ? $arr['hubloc_sitekey'] : ''),
'hubloc_updated' => ((array_key_exists('hubloc_updated',$arr)) ? $arr['hubloc_updated'] : NULL_DATE),
'hubloc_connected' => ((array_key_exists('hubloc_connected',$arr)) ? $arr['hubloc_connected'] : NULL_DATE),
'hubloc_updated' => ((array_key_exists('hubloc_updated',$arr)) ? $arr['hubloc_updated'] : DBA::$dba->get_null_date()),
'hubloc_connected' => ((array_key_exists('hubloc_connected',$arr)) ? $arr['hubloc_connected'] : DBA::$dba->get_null_date()),
'hubloc_primary' => ((array_key_exists('hubloc_primary',$arr)) ? $arr['hubloc_primary'] : 0),
'hubloc_orphancheck' => ((array_key_exists('hubloc_orphancheck',$arr)) ? $arr['hubloc_orphancheck'] : 0),
'hubloc_error' => ((array_key_exists('hubloc_error',$arr)) ? $arr['hubloc_error'] : 0),
@@ -52,9 +52,9 @@ function site_store_lowlevel($arr) {
'site_url' => ((array_key_exists('site_url',$arr)) ? $arr['site_url'] : ''),
'site_access' => ((array_key_exists('site_access',$arr)) ? $arr['site_access'] : 0),
'site_flags' => ((array_key_exists('site_flags',$arr)) ? $arr['site_flags'] : 0),
'site_update' => ((array_key_exists('site_update',$arr)) ? $arr['site_update'] : NULL_DATE),
'site_pull' => ((array_key_exists('site_pull',$arr)) ? $arr['site_pull'] : NULL_DATE),
'site_sync' => ((array_key_exists('site_sync',$arr)) ? $arr['site_sync'] : NULL_DATE),
'site_update' => ((array_key_exists('site_update',$arr)) ? $arr['site_update'] : DBA::$dba->get_null_date()),
'site_pull' => ((array_key_exists('site_pull',$arr)) ? $arr['site_pull'] : DBA::$dba->get_null_date()),
'site_sync' => ((array_key_exists('site_sync',$arr)) ? $arr['site_sync'] : DBA::$dba->get_null_date()),
'site_directory' => ((array_key_exists('site_directory',$arr)) ? $arr['site_directory'] : ''),
'site_register' => ((array_key_exists('site_register',$arr)) ? $arr['site_register'] : 0),
'site_sellpage' => ((array_key_exists('site_sellpage',$arr)) ? $arr['site_sellpage'] : ''),
@@ -362,7 +362,7 @@ function z6_discover() {
// find unregistered zot6 clone hublocs
$c = q("select channel_hash, channel_portable_id from channel where channel_deleted = '%s'",
dbesc(NULL_DATE)
dbesc(DBA::$dba->get_null_date())
);
if ($c) {
foreach ($c as $entry) {

View File

@@ -1,5 +1,6 @@
<?php
use DBA;
use Zotlabs\Lib\Apps;
use Zotlabs\Lib\Config;
use Zotlabs\Lib\IConfig;
@@ -532,9 +533,9 @@ function sync_apps($channel, $apps) {
);
}
if((! $app['app_created']) || ($app['app_created'] <= NULL_DATE))
if((! $app['app_created']) || ($app['app_created'] <= DBA::$dba->get_null_date()))
$app['app_created'] = datetime_convert();
if((! $app['app_edited']) || ($app['app_edited'] <= NULL_DATE))
if((! $app['app_edited']) || ($app['app_edited'] <= DBA::$dba->get_null_date()))
$app['app_edited'] = datetime_convert();
$app['app_channel'] = $channel['channel_id'];
@@ -746,9 +747,9 @@ function sync_chatrooms($channel, $chatrooms) {
unset($chatroom['cr_aid']);
unset($chatroom['cr_uid']);
if((! $chatroom['cr_created']) || ($chatroom['cr_created'] <= NULL_DATE))
if((! $chatroom['cr_created']) || ($chatroom['cr_created'] <= DBA::$dba->get_null_date()))
$chatroom['cr_created'] = datetime_convert();
if((! $chatroom['cr_edited']) || ($chatroom['cr_edited'] <= NULL_DATE))
if((! $chatroom['cr_edited']) || ($chatroom['cr_edited'] <= DBA::$dba->get_null_date()))
$chatroom['cr_edited'] = datetime_convert();
$chatroom['cr_aid'] = $channel['channel_account_id'];

View File

@@ -232,7 +232,7 @@ function comments_are_now_closed($item) {
return $x['closed'];
}
if($item['comments_closed'] > NULL_DATE) {
if($item['comments_closed'] > DBA::$dba->get_null_date()) {
$d = datetime_convert();
if($d > $item['comments_closed'])
return true;
@@ -732,14 +732,14 @@ function get_item_elements($x,$allow_code = false) {
$arr['expires'] = ((!empty($x['expires']) && $x['expires'])
? datetime_convert('UTC','UTC',$x['expires'])
: NULL_DATE);
: DBA::$dba->get_null_date());
$arr['commented'] = ((!empty($x['commented']) && $x['commented'])
? datetime_convert('UTC','UTC',$x['commented'])
: $arr['created']);
$arr['comments_closed'] = ((!empty($x['comments_closed']) && $x['comments_closed'])
? datetime_convert('UTC','UTC',$x['comments_closed'])
: NULL_DATE);
: DBA::$dba->get_null_date());
$arr['title'] = (($x['title']) ? htmlspecialchars($x['title'], ENT_COMPAT,'UTF-8',false) : '');
@@ -1210,7 +1210,7 @@ function encode_item($item,$mirror = false,$zap_compat = false) {
if($y = encode_item_flags($item))
$x['flags'] = $y;
if($item['comments_closed'] > NULL_DATE)
if($item['comments_closed'] > DBA::$dba->get_null_date())
$x['comments_closed'] = $item['comments_closed'];
$x['public_scope'] = $item['public_policy'];
@@ -1773,9 +1773,9 @@ function item_store($arr, $allow_exec = false, $deliver = true, $addAndSync = tr
$arr['owner_xchan'] = ((!empty($arr['owner_xchan'])) ? notags(trim($arr['owner_xchan'])) : '');
$arr['created'] = ((!empty($arr['created']) !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
$arr['edited'] = ((!empty($arr['edited']) !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
$arr['expires'] = ((!empty($arr['expires']) !== false) ? datetime_convert('UTC','UTC',$arr['expires']) : NULL_DATE);
$arr['expires'] = ((!empty($arr['expires']) !== false) ? datetime_convert('UTC','UTC',$arr['expires']) : DBA::$dba->get_null_date());
$arr['commented'] = ((!empty($arr['commented']) !== false) ? datetime_convert('UTC','UTC',$arr['commented']) : datetime_convert());
$arr['comments_closed'] = ((!empty($arr['comments_closed']) !== false) ? datetime_convert('UTC','UTC',$arr['comments_closed']) : NULL_DATE);
$arr['comments_closed'] = ((!empty($arr['comments_closed']) !== false) ? datetime_convert('UTC','UTC',$arr['comments_closed']) : DBA::$dba->get_null_date());
$arr['html'] = ((array_key_exists('html',$arr)) ? $arr['html'] : '');
if($deliver) {
@@ -4440,7 +4440,7 @@ function zot_feed($uid, $observer_hash, $arr) {
}
if (!$mindate)
$mindate = NULL_DATE;
$mindate = DBA::$dba->get_null_date();
$mindate = dbesc($mindate);
@@ -4458,7 +4458,7 @@ function zot_feed($uid, $observer_hash, $arr) {
$limit = " LIMIT 5000 ";
if ($mindate > NULL_DATE) {
if ($mindate > DBA::$dba->get_null_date()) {
$sql_extra .= " and ( created > '$mindate' or changed > '$mindate' ) ";
}

View File

@@ -13,15 +13,6 @@ use Zotlabs\Web\HTTPSig;
* @brief Network related functions.
*/
/**
* @brief Returns path to CA file.
*
* @return string
*/
function get_capath() {
return appdirpath() . '/library/cacert.pem';
}
/**
* @brief fetches an URL.
*
@@ -63,12 +54,17 @@ function z_fetch_url($url, $binary = false, $redirects = 0, $opts = array()) {
@curl_setopt($ch, CURLOPT_HEADER, true);
@curl_setopt($ch, CURLINFO_HEADER_OUT, true);
@curl_setopt($ch, CURLOPT_CAINFO, get_capath());
@curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
@curl_setopt($ch, CURLOPT_RETURNTRANSFER,true);
@curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (compatible; zot)');
@curl_setopt($ch, CURLOPT_ENCODING, '');
if (!empty($opts['useragent'])) {
@curl_setopt($ch, CURLOPT_USERAGENT, $opts['useragent']);
}
else {
@curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (compatible; zot)');
}
$ciphers = @Config::Get('system','curl_ssl_ciphers');
if($ciphers)
@curl_setopt($ch, CURLOPT_SSL_CIPHER_LIST, $ciphers);
@@ -256,13 +252,18 @@ function z_post_url($url, $params, $redirects = 0, $opts = array()) {
@curl_setopt($ch, CURLOPT_HEADER, true);
@curl_setopt($ch, CURLINFO_HEADER_OUT, true);
@curl_setopt($ch, CURLOPT_CAINFO, get_capath());
@curl_setopt($ch, CURLOPT_RETURNTRANSFER,true);
@curl_setopt($ch, CURLOPT_POST,1);
@curl_setopt($ch, CURLOPT_POSTFIELDS,$params);
@curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (compatible; zot)");
@curl_setopt($ch, CURLOPT_ENCODING, '');
if (!empty($opts['useragent'])) {
@curl_setopt($ch, CURLOPT_USERAGENT, $opts['useragent']);
}
else {
@curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (compatible; zot)');
}
$ciphers = @Config::Get('system','curl_ssl_ciphers');
if($ciphers)
@curl_setopt($ch, CURLOPT_SSL_CIPHER_LIST, $ciphers);
@@ -1220,39 +1221,62 @@ function discover_by_webbie($webbie, $protocol = '') {
* @return boolean|string false or associative array from result JSON
*/
function webfinger_rfc7033($webbie, $zot = false) {
$parsed = parse_webbie($webbie);
if(filter_var($webbie, FILTER_VALIDATE_EMAIL)) {
$lhs = substr($webbie,0,strpos($webbie,'@'));
$rhs = substr($webbie,strpos($webbie,'@')+1);
$resource = urlencode('acct:' . $webbie);
}
elseif(filter_var($webbie, FILTER_VALIDATE_URL)) {
$m = parse_url($webbie);
if($m) {
if($m['scheme'] !== 'https')
return false;
$rhs = $m['host'] . (array_key_exists('port', $m) ? ':' . $m['port'] : '');
$resource = urlencode($webbie);
}
}
else
if (!$parsed) {
return false;
}
logger('fetching url from resource: ' . $rhs . ':' . $webbie);
logger('fetching url from resource: ' . $parsed['host'] . ':' . $parsed['resource']);
$counter = 0;
$s = z_fetch_url('https://' . $rhs . '/.well-known/webfinger?f=&resource=' . $resource . (($zot) ? '&zot=1' : ''),
false, $counter, [ 'headers' => [ 'Accept: application/jrd+json, application/json, */*' ] ]);
$s = z_fetch_url('https://' . $parsed['host'] . '/.well-known/webfinger?f=&resource=' . $parsed['resource'] . (($zot) ? '&zot=1' : ''),
false, 0, [ 'headers' => [ 'Accept: application/jrd+json, application/json, */*' ] ]);
if($s['success']) {
$j = json_decode($s['body'], true);
return($j);
return $j;
}
return false;
}
function parse_webbie($webbie) {
$parsed = parse_url($webbie);
if (!$parsed) {
return false;
}
if (!isset($parsed['scheme'])) {
$parsed['scheme'] = 'acct';
}
if (in_array($parsed['scheme'], ['http', 'https'])) {
$result['host'] = $parsed['host'] . ((isset($parsed['port'])) ? ':' . $parsed['port'] : '');
$result['resource'] = urlencode($webbie);
}
elseif ($parsed['scheme'] === 'acct') {
$parts = explode('@', ltrim($parsed['path'], '@'));
if (count($parts) !== 2) {
return false;
}
$result['host'] = $parts[1];
$result['resource'] = urlencode('acct:' . $parts[0] . '@' . $parts[1]);
}
else {
return false;
}
if (isset($result['host'], $result['resource'])) {
return $result;
}
return false;
}
function old_webfinger($webbie) {
$host = '';
@@ -1838,13 +1862,20 @@ function probe_api_path($host) {
}
function scrape_vcard($url) {
function scrape_vcard($url, $useragent = '') {
$ret = array();
logger('url=' . $url);
$x = z_fetch_url($url);
$opts = [];
if ($useragent) {
$opts['useragent'] = $useragent;
}
$x = z_fetch_url($url, opts: $opts);
if(! $x['success']) {
logger('ERROR fetching URL');
return $ret;

View File

@@ -2017,7 +2017,7 @@ function format_poll($item,$s,$opts) {
$message = (($totalResponses) ? sprintf(tt('%d Vote in total', '%d Votes in total', $totalResponses, 'noun'), $totalResponses) . EOL : '');
if ($item['comments_closed'] > NULL_DATE) {
if ($item['comments_closed'] > DBA::$dba->get_null_date()) {
$t = datetime_convert('UTC',date_default_timezone_get(), $item['comments_closed'], 'Y-m-d H:i');
$closed = ((datetime_convert() > $item['comments_closed']) ? true : false);
if ($closed) {

View File

@@ -31,9 +31,9 @@ function xchan_store_lowlevel($arr) {
'xchan_network' => ((array_key_exists('xchan_network',$arr)) ? $arr['xchan_network'] : ''),
'xchan_instance_url' => ((array_key_exists('xchan_instance_url',$arr)) ? $arr['xchan_instance_url'] : ''),
'xchan_flags' => ((array_key_exists('xchan_flags',$arr)) ? intval($arr['xchan_flags']) : 0),
'xchan_photo_date' => ((array_key_exists('xchan_photo_date',$arr)) ? datetime_convert('UTC','UTC',$arr['xchan_photo_date']) : NULL_DATE),
'xchan_name_date' => ((array_key_exists('xchan_name_date',$arr)) ? datetime_convert('UTC','UTC',$arr['xchan_name_date']) : NULL_DATE),
'xchan_updated' => ((array_key_exists('xchan_updated',$arr)) ? datetime_convert('UTC','UTC',$arr['xchan_updated']) : NULL_DATE),
'xchan_photo_date' => ((array_key_exists('xchan_photo_date',$arr)) ? datetime_convert('UTC','UTC',$arr['xchan_photo_date']) : DBA::$dba->get_null_date()),
'xchan_name_date' => ((array_key_exists('xchan_name_date',$arr)) ? datetime_convert('UTC','UTC',$arr['xchan_name_date']) : DBA::$dba->get_null_date()),
'xchan_updated' => ((array_key_exists('xchan_updated',$arr)) ? datetime_convert('UTC','UTC',$arr['xchan_updated']) : DBA::$dba->get_null_date()),
'xchan_hidden' => ((array_key_exists('xchan_hidden',$arr)) ? intval($arr['xchan_hidden']) : 0),
'xchan_orphan' => ((array_key_exists('xchan_orphan',$arr)) ? intval($arr['xchan_orphan']) : 0),
'xchan_censored' => ((array_key_exists('xchan_censored',$arr)) ? intval($arr['xchan_censored']) : 0),

View File

@@ -150,28 +150,29 @@ function clean_query_string($s = '') {
*/
function drop_query_params($s, $p) {
$unescaped = unescape_tags($s);
$parsed = parse_url($unescaped);
$s = unescape_tags($s);
$parsed = parse_url($s);
$query = '';
$query_args = null;
if(isset($parsed['query'])) {
parse_str($parsed['query'], $query_args);
if (empty($parsed['query'])) {
// No query parameters were found, return the original string
return $s;
}
if(is_array($query_args)) {
foreach($query_args as $k => $v) {
if(in_array($k, $p))
continue;
$query .= (($query) ? '&' : '') . urlencode($k) . '=' . urlencode($v);
$query_args = [];
parse_str($parsed['query'], $query_args);
foreach($query_args as $k => $v) {
if (in_array($k, $p)) {
unset($query_args[$k]);
}
}
unset($parsed['query']);
if($query) {
$query = http_build_query($query_args, '', '&');
if ($query) {
$parsed['query'] = $query;
}

View File

@@ -1,150 +1,73 @@
##
# Red Nginx configuration
# by Olaf Conradi
#
# On Debian based distributions you can add this file to
# /etc/nginx/sites-available
#
# Then customize to your needs. To enable the configuration
# symlink it to /etc/nginx/sites-enabled and reload Nginx using
#
# service nginx reload
##
##
# You should look at the following URL's in order to grasp a solid understanding
# of Nginx configuration files in order to fully unleash the power of Nginx.
#
# http://wiki.nginx.org/Pitfalls
# http://wiki.nginx.org/QuickStart
# http://wiki.nginx.org/Configuration
##
##
# This configuration assumes your domain is example.net
# You have a separate subdomain red.example.net
# You want all red traffic to be https
# You have an SSL certificate and key for your subdomain
# You have PHP FastCGI Process Manager (php5-fpm) running on localhost
# You have Red installed in /var/www/red
##
server {
listen 80;
server_name red.example.net;
server_name hub.example.org;
index index.php;
root /var/www/red;
rewrite ^ https://red.example.net$request_uri? permanent;
listen 80;
listen [::]:80;
return 301 https://$host$request_uri;
}
##
# Configure Red with SSL
#
# All requests are routed to the front controller
# except for certain known file types like images, css, etc.
# Those are served statically whenever possible with a
# fall back to the front controller (needed for avatars, for example)
##
server {
listen 443 ssl;
server_name red.example.net;
server_name hub.example.org;
ssl on;
ssl_certificate /etc/nginx/ssl/red.example.net.chain.pem;
ssl_certificate_key /etc/nginx/ssl/example.net.key;
ssl_session_timeout 5m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS;
ssl_prefer_server_ciphers on;
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
fastcgi_param HTTPS on;
## Quic and HTTP3 Requires Nginx >= 1.25.0
## https://nginx.org/en/docs/quic.html
# listen [::]:443 quic reuseport;
# listen 443 quic reuseport;
# http3 on;
index index.php;
charset utf-8;
root /var/www/red;
access_log /var/log/nginx/red.log;
#Uncomment the following line to include a standard configuration file
#Note that the most specific rule wins and your standard configuration
#will therefore *add* to this file, but not override it.
#include standard.conf
# allow uploads up to 20MB in size
client_max_body_size 20m;
client_body_buffer_size 128k;
## Headers for Quic and HTTP3
# add_header alt-svc 'h3=":443"; ma=86400' always;
include mime.types;
## SSL Cerver certificat settings
ssl_certificate /etc/letsencrypt/live/hub.example.org/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/hub.example.org/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/hub.example.org/chain.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
# rewrite to front controller as default rule
location / {
if (!-e $request_filename) {
rewrite ^(.*)$ /index.php?q=$1;
## Basic folder and files settings
root /var/www/hub.example.org;
index index.php;
## How big files are users allowed to upload
client_max_body_size 20m;
client_body_buffer_size 128k;
include mime.types;
location / {
try_files $uri /index.php?q=$uri&$args;
}
}
# make sure webfinger and other well known services aren't blocked
# by denying dot files and rewrite request to the front controller
location ^~ /.well-known/ {
allow all;
if (!-e $request_filename) {
rewrite ^(.*)$ /index.php?q=$1;
location ^~ /.well-known/ {
allow all;
try_files $uri /index.php?q=$uri&$args;
}
}
# statically serve these file types when possible
# otherwise fall back to front controller
# allow browser to cache them
# added .htm for advanced source code editor library
# location ~* \.(jpg|jpeg|gif|png|ico|css|js|htm|html|map|ttf|woff|woff2|svg)$ {
# expires 30d;
# try_files $uri /index.php?q=$uri&$args;
# }
location ~* \.(jpg|jpeg|gif|png|ico|css|js|htm|html|map|ttf|woff|woff2|svg)$ {
expires max;
try_files $uri /index.php?q=$uri&$args;
}
# block these file types
location ~* \.(tpl|md|tgz|log|out)$ {
deny all;
}
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
# or a unix socket
location ~* \.php$ {
# Zero-day exploit defense.
# http://forum.nginx.org/read.php?2,88845,page=3
# Won't work properly (404 error) if the file is not stored on this
# server, which is entirely possible with php-fpm/php-fcgi.
# Comment the 'try_files' line out if you set up php-fpm/php-fcgi on
# another machine. And then cross your fingers that you won't get hacked.
try_files $uri =404;
# NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini
fastcgi_split_path_info ^(.+\.php)(/.+)$;
# With php5-cgi alone:
# fastcgi_pass 127.0.0.1:9000;
# With php5-fpm:
fastcgi_pass unix:/var/run/php5-fpm.sock;
include fastcgi_params;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
# deny access to all dot files
location ~ /\. {
deny all;
}
#deny access to store
location ~ /store {
deny all;
}
#deny access to util
location ~ /util {
deny all;
}
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.*)$;
fastcgi_pass unix:/var/run/php/php-fpm.sock;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $request_filename;
fastcgi_param HTTPS $https;
}
## block hidden files, extensions, and directories
location ~* \.(bak|tpl|md|tgz|log|out)$ {
deny all;
}
location ~* /(?:\.git|store|util)(?:/|$) {
deny all;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,41 @@
<?php
/* Test cases for DbStats
*
* SPDX-FileCopyrightText: 2026 The Hubzilla Community
* SPDX-FileContributor: Harald Eilertsen <haraldei@anduin.net>
*
* SPDX-License-Identifier: MIT
*/
namespace Zotlabs\Tests\Unit\Lib;
use DBA;
use Zotlabs\Lib\DbStats;
use Zotlabs\Tests\Unit\UnitTestCase;
class DbStatsTest extends UnitTestCase
{
public function testGetQueries(): void {
$stats = DbStats::getStats();
$this->assertNotNull($stats);
$this->assertInstanceOf(DbStats::class, $stats);
$numQueries = $stats->getQueries();
$this->assertNotEquals(0, $numQueries);
if (!DBA::$dba->is_postgres()) {
//
// Postgres will only update the stats once the transaction
// is committed or rolled back. As we wrap the tests in a
// transaction to begin with, the stats won't change here,
// so we skip this test on Postgres.
//
dbq("select * from account");
dbq("select * from channel");
dbq("select * from xchan");
$numMoreQueries = $stats->getQueries();
$this->assertGreaterThan($numQueries, $numMoreQueries);
}
}
}

View File

@@ -35,17 +35,21 @@ class TestCase extends UnitTestCase {
$this->goaway_stub = null;
}
protected function do_request(string $method, string $uri, array $query = [], array $params = []): void {
$_GET['q'] = $uri;
$_GET = array_merge($_GET, $query);
$_POST = $params;
protected function do_request(array $args): void {
// string $uri, array $query = [], array $params = []): void {
$_GET['q'] = $args['uri'];
$_GET = array_merge($_GET, $args['query'] ?? []);
$_POST = $args['post_params'] ?? [];
$_SERVER['REQUEST_METHOD'] = $method;
$_SERVER['REQUEST_METHOD'] = $args['method'];
$_SERVER['SERVER_PROTOCOL'] = 'HTTP/1.1';
$_SERVER['QUERY_STRING'] = "q={$uri}";
$_SERVER['REQUEST_URI'] = $uri;
$_SERVER['HTTP_CONTENT_TYPE'] = 'text/html';
$_SERVER['HTTP_CONTENT_LENGTH'] = 0;
$_SERVER['QUERY_STRING'] = "q={$args['uri']}";
$_SERVER['REQUEST_URI'] = $args['uri'];
$_SERVER['HTTP_CONTENT_TYPE'] = $args['content_type'] ?? 'text/html';
$_SERVER['HTTP_CONTENT_LENGTH'] = strlen($args['body'] ?? 0);
if (!empty($args['body'])) {
$_SERVER['HTTP_POST_BODY'] = $args['body'];
}
// phpcs:disable Generic.PHP.DisallowRequestSuperglobal.Found
$_REQUEST = array_merge($_GET, $_POST);
@@ -58,6 +62,15 @@ class TestCase extends UnitTestCase {
$router->Dispatch();
}
protected function ajax_request(string $method, string $uri, array $params): void {
$this->do_request([
'method' => $method,
'uri' => $uri,
'content_type' => 'application/json',
'body' => json_encode($params),
]);
}
/**
* Emulate a GET request.
*
@@ -67,7 +80,11 @@ class TestCase extends UnitTestCase {
* as keys.
*/
protected function get(string $uri, array $query = []): void {
$this->do_request('GET', $uri, $query);
$this->do_request([
'method' => 'GET',
'uri' => $uri,
'query' => $query
]);
}
/**
@@ -81,7 +98,12 @@ class TestCase extends UnitTestCase {
* as keys.
*/
protected function post(string $uri, array $query = [], array $params = []): void {
$this->do_request('POST', $uri, $query, $params);
$this->do_request([
'method' => 'POST',
'uri' => $uri,
'query' => $query,
'post_params' => $params
]);
}
/**

View File

@@ -181,7 +181,7 @@ class UnitTestCase extends TestCase {
* directory of the process, which should normally
* be the Hubzilla root directory.
*/
private function loadFixture($file) : void {
public function loadFixture($file) : void {
$table_name = basename($file, '.yml');
$data = yaml_parse_file($file)[$table_name];

View File

@@ -119,4 +119,37 @@ class NetworkTest extends Zotlabs\Tests\Unit\UnitTestCase {
{
$this->assertEquals('', unparse_url([]));
}
/**
* Test that the parse_webbie function.
*
* @dataProvider parse_webbie_provider
*/
public function test_parse_webbie(string $webbie, array|false $expected) : void {
$this->assertEquals($expected, parse_webbie($webbie));
}
public static function parse_webbie_provider() : array {
return [
// test valid webfinger address
['test@example.net', ['host' => 'example.net', 'resource' => urlencode('acct:test@example.net')]],
// test valid webfinger address with scheme
['acct:test@example.net', ['host' => 'example.net', 'resource' => urlencode('acct:test@example.net')]],
// test address with leading @
['@test@example.net', ['host' => 'example.net', 'resource' => urlencode('acct:test@example.net')]],
// test address with missing user
['@example.net', false],
// test URL
['https://example.net/channel/test', ['host' => 'example.net', 'resource' => urlencode('https://example.net/channel/test')]],
// test unsupported URL
['ftp://example.net/channel/test', false],
];
}
}

View File

@@ -8,27 +8,67 @@
namespace Zotlabs\Tests\Unit;
use PHPUnit\Framework\Attributes\Before;
use Zotlabs\Lib\Libzot;
class XChanTest extends UnitTestCase {
public function testXChanFetchShouldRejectInvalidArgs(): void {
$addr = 'example';
$guid = Libzot::new_uid($addr);
$hash = Libzot::make_xchan_hash($guid, 'dummy-public-key');
private string $addr;
private string $guid;
private string $hash;
#[Before]
public function populateXChans(): void {
$this->addr = 'example';
$this->guid = Libzot::new_uid($this->addr);
$this->hash = Libzot::make_xchan_hash($this->guid, 'dummy-public-key');
xchan_store_lowlevel([
'xchan_hash' => $hash,
'xchan_guid' => $guid,
'xchan_addr' => $addr,
'xchan_hash' => $this->hash,
'xchan_guid' => $this->guid,
'xchan_addr' => $this->addr,
]);
}
public function testXChanFetchReturnsStoredXchan(): void {
$byHash = xchan_fetch(['hash' => $this->hash]);
$this->assertIsArray($byHash);
$this->assertEquals($this->addr, $byHash['address']);
$this->assertEquals($this->guid, $byHash['guid']);
$this->assertEquals($this->hash, $byHash['hash']);
$byGuid = xchan_fetch(['guid' => $this->guid]);
$this->assertIsArray($byGuid);
$this->assertEquals($this->addr, $byGuid['address']);
$this->assertEquals($this->guid, $byGuid['guid']);
$this->assertEquals($this->hash, $byGuid['hash']);
$byAddr = xchan_fetch(['address' => $this->addr]);
$this->assertIsArray($byAddr);
$this->assertEquals($this->addr, $byAddr['address']);
$this->assertEquals($this->guid, $byAddr['guid']);
$this->assertEquals($this->hash, $byAddr['hash']);
}
public function testXChanFetchShouldRejectInvalidArgs(): void {
// Trivial SQL Injection
$this->assertFalse(xchan_fetch(['hash' => "{$hash}' or 1=1; -- "]));
$this->assertFalse(xchan_fetch(['guid' => "{$guid}' or 1=1; -- "]));
$this->assertFalse(xchan_fetch(['address' => "{$addr}' or 1=1; -- "]));
$this->assertFalse(xchan_fetch(['hash' => "{$this->hash}' or 1=1; -- "]));
$this->assertFalse(xchan_fetch(['guid' => "{$this->guid}' or 1=1; -- "]));
$this->assertFalse(xchan_fetch(['address' => "{$this->addr}' or 1=1; -- "]));
// Not a valid key
$this->assertFalse(xchan_fetch(['wrongkey' => $hash]));
$this->assertFalse(xchan_fetch(['wrongkey' => $this->hash]));
// Empty values
$this->assertFalse(xchan_fetch(['hash' => '']));
$this->assertFalse(xchan_fetch(['guid' => '']));
$this->assertFalse(xchan_fetch(['address' => '']));
// Non existing xchan hash/guid/address
$this->assertFalse(xchan_fetch([
'hash' => Libzot::make_xchan_hash(Libzot::new_uid('nobody'), 'another-dummy-key')
]));
$this->assertFalse(xchan_fetch(['guid' => Libzot::new_uid('nobody')]));
$this->assertFalse(xchan_fetch(['address' => 'nobody']));
}
}

View File

@@ -0,0 +1,35 @@
<?php
/*
* SPDX-FileCopyrightText: 2026 The Hubzilla Community
* SPDX-FileContributor: Mario Vavti <mario@mariovavti.com>
*
* SPDX-License-Identifier: MIT
*/
namespace Zotlabs\Tests\Unit;
class ZidTest extends UnitTestCase {
/**
* Test the drop_query_params function.
*
* @dataProvider drop_query_params_provider
*/
public function test_drop_query_params(string $url, array $drop_params, string $expected) : void {
$this->assertEquals(escape_tags($expected), drop_query_params($url, $drop_params));
}
public static function drop_query_params_provider() : array {
return [
// Query params with array
['https://www.example.net/en/pro/detail/some-detail?tx_news_pi1%5Bday%5D=15&tx_news_pi1%5Bmonth%5D=3&tx_news_pi1%5Byear%5D=2026&cHash=85200a0007de8fecd4cd55199146e19c', ['zid', 'f'], 'https://www.example.net/en/pro/detail/some-detail?tx_news_pi1%5Bday%5D=15&tx_news_pi1%5Bmonth%5D=3&tx_news_pi1%5Byear%5D=2026&cHash=85200a0007de8fecd4cd55199146e19c'],
// Query params with zid
['https://www.example.net/channel/test?zid=test@example.net', ['zid', 'f'], 'https://www.example.net/channel/test'],
// Query params with zid and empty f
['https://www.example.net/channel/test?f=&zid=test@example.net', ['zid', 'f'], 'https://www.example.net/channel/test'],
// Query params with zid and empty f encoded
['https://www.example.net/channel/test?f=&amp;zid=test@example.net', ['zid', 'f'], 'https://www.example.net/channel/test'],
];
}
}

View File

@@ -13,7 +13,7 @@ HTML_FILE_EXTENSION = .html
HTML_COLORSTYLE = TOGGLE
GENERATE_LATEX = NO
EXTRACT_ALL = YES
EXTRACT_PRIVATE = YES
EXTRACT_PRIVATE = NO
GENERATE_TODOLIST = YES
USE_MDFILE_AS_MAINPAGE = README.md
REFERENCED_BY_RELATION = YES

File diff suppressed because it is too large Load Diff

View File

@@ -22,12 +22,12 @@ if [ $ADDONMODE ]
then
cd "$FULLPATH/../addon/$ADDONNAME"
mkdir -p "$FULLPATH/../addon/$ADDONNAME/lang/C"
OUTFILE="$FULLPATH/../addon/$ADDONNAME/lang/C/hmessages.po"
OUTFILE="$FULLPATH/../addon/$ADDONNAME/lang/C/hmessages.pot"
FINDSTARTDIR="."
FINDOPTS=
else
cd "$FULLPATH/../view/lang/en/"
OUTFILE="$FULLPATH/hmessages.po"
OUTFILE="$FULLPATH/hmessages.pot"
FINDSTARTDIR="../../../"
# skip addon folder
FINDOPTS="( -wholename */store -o -wholename */vendor -o -wholename */extend ) -prune -o"
@@ -59,7 +59,7 @@ for f in $(find -L "$FINDSTARTDIR" $FINDOPTS -name "*.php" -type f)
do
if [ ! -d "$f" ]
then
xgettext $KEYWORDS $OPTS -j -o "$OUTFILE" --from-code=UTF-8 "$f" > /dev/null 2>&1
xgettext $KEYWORDS $OPTS -L PHP -j -o "$OUTFILE" --from-code=UTF-8 "$f" > /dev/null 2>&1
fi
done

View File

@@ -2,6 +2,118 @@
All notable changes to this project will be documented in this file.
## [0.14.8](https://github.com/brick/math/releases/tag/0.14.8) - 2026-02-10
🗑️ **Deprecations**
- Method `BigInteger::testBit()` is deprecated, use `isBitSet()` instead
**New features**
- New method: `BigInteger::isBitSet()` (replaces `testBit()`)
- New method: `BigNumber::toString()` (alias of magic method `__toString()`)
👌 **Improvements**
- Performance optimization of `BigRational` comparison methods
- More exceptions have been documented with `@throws` annotations
## [0.14.7](https://github.com/brick/math/releases/tag/0.14.7) - 2026-02-07
**New features**
- `clamp()` is now available on the base `BigNumber` class
👌 **Improvements**
- Improved `@throws` exception documentation
## [0.14.6](https://github.com/brick/math/releases/tag/0.14.6) - 2026-02-05
🗑️ **Deprecations**
- Not passing a `$scale` to `BigDecimal::dividedBy()` is deprecated; **`$scale` will be required in 0.15**
👌 **Improvements**
- `BigRational::toFloat()` never returns `NAN` anymore
## [0.14.5](https://github.com/brick/math/releases/tag/0.14.5) - 2026-02-03
🗑️ **Deprecations**
- Not passing a rounding mode to `BigInteger::sqrt()` and `BigDecimal::sqrt()` triggers a deprecation notice: **the default rounding mode will change from `Down` to `Unnecessary` in 0.15**
**New features**
- `BigInteger::sqrt()` and `BigDecimal::sqrt()` now support rounding
- `abs()` and `negated()` methods are now available on the base `BigNumber` class
👌 **Improvements**
- Alphabet is now checked for duplicate characters in `BigInteger::(from|to)ArbitraryBase()`
- `BigNumber::ofNullable()` is now marked as `@pure`
## [0.14.4](https://github.com/brick/math/releases/tag/0.14.4) - 2026-02-02
🗑️ **Deprecations**
- Passing a negative modulus to `BigInteger::mod()` is deprecated to align with Euclidean modulo semantics; it will throw `NegativeNumberException` in 0.15
- Method `BigDecimal::stripTrailingZeros()` is deprecated, use `strippedOfTrailingZeros()` instead
**New features**
- `BigInteger::modPow()` now accepts negative bases
- New method: `BigDecimal::strippedOfTrailingZeros()` (replaces `stripTrailingZeros()`)
👌 **Improvements**
- `clamp()` methods are now marked as `@pure`
## [0.14.3](https://github.com/brick/math/releases/tag/0.14.3) - 2026-02-01
**New features**
- New method: `BigInteger::lcm()`
- New method: `BigInteger::lcmAll()`
- New method: `BigRational::toRepeatingDecimalString()`
🐛 **Bug fixes**
- `BigInteger::gcdAll()` / `gcdMultiple()` could return a negative result when used with a single negative number
## [0.14.2](https://github.com/brick/math/releases/tag/0.14.2) - 2026-01-30
🗑️ **Deprecations**
- **Passing `float` values to `of()` or arithmetic methods is deprecated** and will be removed in 0.15; cast to string explicitly to preserve the previous behaviour (#105)
- **Accessing `RoundingMode` enum cases through upper snake case (e.g. `HALF_UP`) is deprecated**, use the pascal case version (e.g. `HalfUp`) instead
- Method `BigInteger::gcdMultiple()` is deprecated, use `gcdAll()` instead
- Method `BigDecimal::exactlyDividedBy()` is deprecated, use `dividedByExact()` instead
- Method `BigDecimal::getIntegralPart()` is deprecated (will be removed in 0.15, and re-introduced as returning `BigInteger` in 0.16)
- Method `BigDecimal::getFractionalPart()` is deprecated (will be removed in 0.15, and re-introduced as returning `BigDecimal` with a different meaning in 0.16)
- Method `BigRational::nd()` is deprecated, use `ofFraction()` instead
- Method `BigRational::quotient()` is deprecated, use `getIntegralPart()` instead
- Method `BigRational::remainder()` is deprecated, use `$number->getNumerator()->remainder($number->getDenominator())` instead
- Method `BigRational::quotientAndRemainder()` is deprecated, use `$number->getNumerator()->quotientAndRemainder($number->getDenominator())` instead
**New features**
- New method: `BigInteger::gcdAll()` (replaces `gcdMultiple()`)
- New method: `BigRational::clamp()`
- New method: `BigRational::ofFraction()` (replaces `nd()`)
- New method: `BigRational::getIntegralPart()` (replaces `quotient()`)
- New method: `BigRational::getFractionalPart()`
👌 **Improvements**
- `BigInteger::modInverse()` now accepts `BigNumber|int|float|string` instead of just `BigInteger`
- `BigInteger::gcdMultiple()` now accepts `BigNumber|int|float|string` instead of just `BigInteger`
🐛 **Bug fixes**
- `BigInteger::clamp()` and `BigDecimal::clamp()` now throw an exception on inverted bounds, instead of returning an incorrect result
## [0.14.1](https://github.com/brick/math/releases/tag/0.14.1) - 2025-11-24
**New features**
@@ -520,4 +632,3 @@ Added `BigDecimal::divideAndRemainder()`
## [0.1.0](https://github.com/brick/math/releases/tag/0.1.0) - 2014-08-31
First beta release.

View File

@@ -1,14 +0,0 @@
parameters:
level: 10
checkUninitializedProperties: true
paths:
- src
ignoreErrors:
- '~Impure call to function array_shift\(\) in pure method~'
- '~Possibly impure call to function assert\(\) in pure method~'
- '~Possibly impure call to function hex2bin\(\) in pure method~'
- '~Possibly impure call to function preg_match\(\) in pure method~'
- '~Impure static variable in pure method Brick\\Math\\Big(Integer|Decimal|Rational)::(zero|one|ten)\(\)~'
-
message: '~Parameter #\d \$\S+ of function bc\S+ expects numeric-string, string given~'
path: src/Internal/Calculator/BcMathCalculator.php

View File

@@ -7,23 +7,34 @@ namespace Brick\Math;
use Brick\Math\Exception\DivisionByZeroException;
use Brick\Math\Exception\MathException;
use Brick\Math\Exception\NegativeNumberException;
use Brick\Math\Exception\RoundingNecessaryException;
use Brick\Math\Internal\Calculator;
use Brick\Math\Internal\CalculatorRegistry;
use InvalidArgumentException;
use LogicException;
use Override;
use function func_num_args;
use function in_array;
use function intdiv;
use function max;
use function rtrim;
use function sprintf;
use function str_pad;
use function str_repeat;
use function strlen;
use function substr;
use function trigger_error;
use const E_USER_DEPRECATED;
use const STR_PAD_LEFT;
/**
* Immutable, arbitrary-precision signed decimal numbers.
* An arbitrarily large decimal number.
*
* This class is immutable.
*
* The scale of the number is the number of digits after the decimal point. It is always positive or zero.
*/
final readonly class BigDecimal extends BigNumber
{
@@ -62,15 +73,21 @@ final readonly class BigDecimal extends BigNumber
*
* Example: `(12345, 3)` will result in the BigDecimal `12.345`.
*
* A negative scale is normalized to zero by appending zeros to the unscaled value.
*
* Example: `(12345, -3)` will result in the BigDecimal `12345000`.
*
* @param BigNumber|int|float|string $value The unscaled value. Must be convertible to a BigInteger.
* @param int $scale The scale of the number. If negative, the scale will be set to zero
* and the unscaled value will be adjusted accordingly.
*
* @throws MathException If the value is not valid, or is not convertible to a BigInteger.
*
* @pure
*/
public static function ofUnscaledValue(BigNumber|int|float|string $value, int $scale = 0): BigDecimal
{
$value = (string) BigInteger::of($value);
$value = BigInteger::of($value)->toString();
if ($scale < 0) {
if ($value !== '0') {
@@ -159,7 +176,7 @@ final readonly class BigDecimal extends BigNumber
[$a, $b] = $this->scaleValues($this, $that);
$value = CalculatorRegistry::get()->add($a, $b);
$scale = $this->scale > $that->scale ? $this->scale : $that->scale;
$scale = max($this->scale, $that->scale);
return new BigDecimal($value, $scale);
}
@@ -186,7 +203,7 @@ final readonly class BigDecimal extends BigNumber
[$a, $b] = $this->scaleValues($this, $that);
$value = CalculatorRegistry::get()->sub($a, $b);
$scale = $this->scale > $that->scale ? $this->scale : $that->scale;
$scale = max($this->scale, $that->scale);
return new BigDecimal($value, $scale);
}
@@ -198,7 +215,7 @@ final readonly class BigDecimal extends BigNumber
*
* @param BigNumber|int|float|string $that The multiplier. Must be convertible to a BigDecimal.
*
* @throws MathException If the multiplier is not a valid number, or is not convertible to a BigDecimal.
* @throws MathException If the multiplier is not valid, or is not convertible to a BigDecimal.
*
* @pure
*/
@@ -223,16 +240,19 @@ final readonly class BigDecimal extends BigNumber
/**
* Returns the result of the division of this number by the given one, at the given scale.
*
* @param BigNumber|int|float|string $that The divisor.
* @param int|null $scale The desired scale, or null to use the scale of this number.
* @param RoundingMode $roundingMode An optional rounding mode, defaults to UNNECESSARY.
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal.
* @param int|null $scale The desired scale. Omitting this parameter is deprecated; it will be required in 0.15.
* @param RoundingMode $roundingMode An optional rounding mode, defaults to Unnecessary.
*
* @throws InvalidArgumentException If the scale or rounding mode is invalid.
* @throws MathException If the number is invalid, is zero, or rounding was necessary.
* @throws InvalidArgumentException If the scale is negative.
* @throws MathException If the divisor is not valid, or is not convertible to a BigDecimal.
* @throws DivisionByZeroException If the divisor is zero.
* @throws RoundingNecessaryException If RoundingMode::Unnecessary is used and the result cannot be represented
* exactly at the given scale.
*
* @pure
*/
public function dividedBy(BigNumber|int|float|string $that, ?int $scale = null, RoundingMode $roundingMode = RoundingMode::UNNECESSARY): BigDecimal
public function dividedBy(BigNumber|int|float|string $that, ?int $scale = null, RoundingMode $roundingMode = RoundingMode::Unnecessary): BigDecimal
{
$that = BigDecimal::of($that);
@@ -241,9 +261,15 @@ final readonly class BigDecimal extends BigNumber
}
if ($scale === null) {
// @phpstan-ignore-next-line
trigger_error(
'Not passing a $scale to BigDecimal::dividedBy() is deprecated. ' .
'Use $a->dividedBy($b, $a->getScale(), $roundingMode) to retain current behavior.',
E_USER_DEPRECATED,
);
$scale = $this->scale;
} elseif ($scale < 0) {
throw new InvalidArgumentException('Scale cannot be negative.');
throw new InvalidArgumentException('Scale must not be negative.');
}
if ($that->value === '1' && $that->scale === 0 && $scale === $this->scale) {
@@ -263,14 +289,37 @@ final readonly class BigDecimal extends BigNumber
*
* The scale of the result is automatically calculated to fit all the fraction digits.
*
* @deprecated Will be removed in 0.15. Use dividedByExact() instead.
*
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal.
*
* @throws MathException If the divisor is not a valid number, is not convertible to a BigDecimal, is zero,
* or the result yields an infinite number of digits.
*/
public function exactlyDividedBy(BigNumber|int|float|string $that): BigDecimal
{
trigger_error(
'BigDecimal::exactlyDividedBy() is deprecated and will be removed in 0.15. Use dividedByExact() instead.',
E_USER_DEPRECATED,
);
return $this->dividedByExact($that);
}
/**
* Returns the exact result of the division of this number by the given one.
*
* The scale of the result is automatically calculated to fit all the fraction digits.
*
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal.
*
* @throws MathException If the divisor is not valid, or is not convertible to a BigDecimal.
* @throws DivisionByZeroException If the divisor is zero.
* @throws RoundingNecessaryException If the result yields an infinite number of digits.
*
* @pure
*/
public function exactlyDividedBy(BigNumber|int|float|string $that): BigDecimal
public function dividedByExact(BigNumber|int|float|string $that): BigDecimal
{
$that = BigDecimal::of($that);
@@ -298,30 +347,7 @@ final readonly class BigDecimal extends BigNumber
}
}
return $this->dividedBy($that, $scale)->stripTrailingZeros();
}
/**
* Limits (clamps) this number between the given minimum and maximum values.
*
* If the number is lower than $min, returns a copy of $min.
* If the number is greater than $max, returns a copy of $max.
* Otherwise, returns this number unchanged.
*
* @param BigNumber|int|float|string $min The minimum. Must be convertible to a BigDecimal.
* @param BigNumber|int|float|string $max The maximum. Must be convertible to a BigDecimal.
*
* @throws MathException If min/max are not convertible to a BigDecimal.
*/
public function clamp(BigNumber|int|float|string $min, BigNumber|int|float|string $max): BigDecimal
{
if ($this->isLessThan($min)) {
return BigDecimal::of($min);
} elseif ($this->isGreaterThan($max)) {
return BigDecimal::of($max);
}
return $this;
return $this->dividedBy($that, $scale)->strippedOfTrailingZeros();
}
/**
@@ -359,9 +385,17 @@ final readonly class BigDecimal extends BigNumber
*
* The quotient has a scale of `0`.
*
* Examples:
*
* - `7.5` quotient `3` returns `2`
* - `7.5` quotient `-3` returns `-2`
* - `-7.5` quotient `3` returns `-2`
* - `-7.5` quotient `-3` returns `2`
*
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal.
*
* @throws MathException If the divisor is not a valid decimal number, or is zero.
* @throws MathException If the divisor is not valid, or is not convertible to a BigDecimal.
* @throws DivisionByZeroException If the divisor is zero.
*
* @pure
*/
@@ -385,10 +419,19 @@ final readonly class BigDecimal extends BigNumber
* Returns the remainder of the division of this number by the given one.
*
* The remainder has a scale of `max($this->scale, $that->scale)`.
* The remainder, when non-zero, has the same sign as the dividend.
*
* Examples:
*
* - `7.5` remainder `3` returns `1.5`
* - `7.5` remainder `-3` returns `1.5`
* - `-7.5` remainder `3` returns `-1.5`
* - `-7.5` remainder `-3` returns `-1.5`
*
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal.
*
* @throws MathException If the divisor is not a valid decimal number, or is zero.
* @throws MathException If the divisor is not valid, or is not convertible to a BigDecimal.
* @throws DivisionByZeroException If the divisor is zero.
*
* @pure
*/
@@ -405,7 +448,7 @@ final readonly class BigDecimal extends BigNumber
$remainder = CalculatorRegistry::get()->divR($p, $q);
$scale = $this->scale > $that->scale ? $this->scale : $that->scale;
$scale = max($this->scale, $that->scale);
return new BigDecimal($remainder, $scale);
}
@@ -415,11 +458,19 @@ final readonly class BigDecimal extends BigNumber
*
* The quotient has a scale of `0`, and the remainder has a scale of `max($this->scale, $that->scale)`.
*
* Examples:
*
* - `7.5` quotientAndRemainder `3` returns [`2`, `1.5`]
* - `7.5` quotientAndRemainder `-3` returns [`-2`, `1.5`]
* - `-7.5` quotientAndRemainder `3` returns [`-2`, `-1.5`]
* - `-7.5` quotientAndRemainder `-3` returns [`2`, `-1.5`]
*
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigDecimal.
*
* @return array{BigDecimal, BigDecimal} An array containing the quotient and the remainder.
*
* @throws MathException If the divisor is not a valid decimal number, or is zero.
* @throws MathException If the divisor is not valid, or is not convertible to a BigDecimal.
* @throws DivisionByZeroException If the divisor is zero.
*
* @pure
*/
@@ -436,7 +487,7 @@ final readonly class BigDecimal extends BigNumber
[$quotient, $remainder] = CalculatorRegistry::get()->divQR($p, $q);
$scale = $this->scale > $that->scale ? $this->scale : $that->scale;
$scale = max($this->scale, $that->scale);
$quotient = new BigDecimal($quotient, 0);
$remainder = new BigDecimal($remainder, $scale);
@@ -445,17 +496,34 @@ final readonly class BigDecimal extends BigNumber
}
/**
* Returns the square root of this number, rounded down to the given number of decimals.
* Returns the square root of this number, rounded to the given scale according to the given rounding mode.
*
* @throws InvalidArgumentException If the scale is negative.
* @throws NegativeNumberException If this number is negative.
* @param int $scale The target scale. Must be non-negative.
* @param RoundingMode $roundingMode The rounding mode to use, defaults to Down.
* ⚠️ WARNING: the default rounding mode was kept as Down for backward
* compatibility, but will change to Unnecessary in version 0.15. Pass a rounding
* mode explicitly to avoid this upcoming breaking change.
*
* @throws InvalidArgumentException If the scale is negative.
* @throws NegativeNumberException If this number is negative.
* @throws RoundingNecessaryException If RoundingMode::Unnecessary is used and the result cannot be represented
* exactly at the given scale.
*
* @pure
*/
public function sqrt(int $scale): BigDecimal
public function sqrt(int $scale, RoundingMode $roundingMode = RoundingMode::Down): BigDecimal
{
if (func_num_args() === 1) {
// @phpstan-ignore-next-line
trigger_error(
'The default rounding mode of BigDecimal::sqrt() will change from Down to Unnecessary in version 0.15. ' .
'Pass a rounding mode explicitly to avoid this breaking change.',
E_USER_DEPRECATED,
);
}
if ($scale < 0) {
throw new InvalidArgumentException('Scale cannot be negative.');
throw new InvalidArgumentException('Scale must not be negative.');
}
if ($this->value === '0') {
@@ -467,28 +535,44 @@ final readonly class BigDecimal extends BigNumber
}
$value = $this->value;
$addDigits = 2 * $scale - $this->scale;
$inputScale = $this->scale;
if ($addDigits > 0) {
// add zeros
$value .= str_repeat('0', $addDigits);
} elseif ($addDigits < 0) {
// trim digits
if (-$addDigits >= strlen($this->value)) {
// requesting a scale too low, will always yield a zero result
return new BigDecimal('0', $scale);
}
$value = substr($value, 0, $addDigits);
if ($inputScale % 2 !== 0) {
$value .= '0';
$inputScale++;
}
$value = CalculatorRegistry::get()->sqrt($value);
$calculator = CalculatorRegistry::get();
return new BigDecimal($value, $scale);
// Keep one extra digit for rounding.
$intermediateScale = max($scale, intdiv($inputScale, 2)) + 1;
$value .= str_repeat('0', 2 * $intermediateScale - $inputScale);
$sqrt = $calculator->sqrt($value);
$isExact = $calculator->mul($sqrt, $sqrt) === $value;
if (! $isExact) {
if ($roundingMode === RoundingMode::Unnecessary) {
throw RoundingNecessaryException::roundingNecessary();
}
// Non-perfect-square sqrt is irrational, so the true value is strictly above this sqrt floor.
// Add one at the intermediate scale to guarantee Up/Ceiling round up at the target scale.
if (in_array($roundingMode, [RoundingMode::Up, RoundingMode::Ceiling], true)) {
$sqrt = $calculator->add($sqrt, '1');
}
// Irrational sqrt cannot land exactly on a midpoint; treat tie-to-down modes as HalfUp.
elseif (in_array($roundingMode, [RoundingMode::HalfDown, RoundingMode::HalfEven, RoundingMode::HalfFloor], true)) {
$roundingMode = RoundingMode::HalfUp;
}
}
return (new BigDecimal($sqrt, $intermediateScale))->toScale($scale, $roundingMode);
}
/**
* Returns a copy of this BigDecimal with the decimal point moved $n places to the left.
* Returns a copy of this BigDecimal with the decimal point moved to the left by the given number of places.
*
* @pure
*/
@@ -506,7 +590,7 @@ final readonly class BigDecimal extends BigNumber
}
/**
* Returns a copy of this BigDecimal with the decimal point moved $n places to the right.
* Returns a copy of this BigDecimal with the decimal point moved to the right by the given number of places.
*
* @pure
*/
@@ -536,9 +620,24 @@ final readonly class BigDecimal extends BigNumber
/**
* Returns a copy of this BigDecimal with any trailing zeros removed from the fractional part.
*
* @pure
* @deprecated Use strippedOfTrailingZeros() instead.
*/
public function stripTrailingZeros(): BigDecimal
{
trigger_error(
'BigDecimal::stripTrailingZeros() is deprecated, use strippedOfTrailingZeros() instead.',
E_USER_DEPRECATED,
);
return $this->strippedOfTrailingZeros();
}
/**
* Returns a copy of this BigDecimal with any trailing zeros removed from the fractional part.
*
* @pure
*/
public function strippedOfTrailingZeros(): BigDecimal
{
if ($this->scale === 0) {
return $this;
@@ -566,22 +665,8 @@ final readonly class BigDecimal extends BigNumber
return new BigDecimal($value, $scale);
}
/**
* Returns the absolute value of this number.
*
* @pure
*/
public function abs(): BigDecimal
{
return $this->isNegative() ? $this->negated() : $this;
}
/**
* Returns the negated value of this number.
*
* @pure
*/
public function negated(): BigDecimal
#[Override]
public function negated(): static
{
return new BigDecimal(CalculatorRegistry::get()->neg($this->value), $this->scale);
}
@@ -660,10 +745,15 @@ final readonly class BigDecimal extends BigNumber
*
* Example: `-123.456` => `-123`.
*
* @pure
* @deprecated Will be removed in 0.15 and re-introduced as returning BigInteger in 0.16.
*/
public function getIntegralPart(): string
{
trigger_error(
'BigDecimal::getIntegralPart() is deprecated and will be removed in 0.15. It will be re-introduced as returning BigInteger in 0.16.',
E_USER_DEPRECATED,
);
if ($this->scale === 0) {
return $this->value;
}
@@ -680,10 +770,15 @@ final readonly class BigDecimal extends BigNumber
*
* Examples: `-123.456` => '456', `123` => ''.
*
* @pure
* @deprecated Will be removed in 0.15 and re-introduced as returning BigDecimal with a different meaning in 0.16.
*/
public function getFractionalPart(): string
{
trigger_error(
'BigDecimal::getFractionalPart() is deprecated and will be removed in 0.15. It will be re-introduced as returning BigDecimal with a different meaning in 0.16.',
E_USER_DEPRECATED,
);
if ($this->scale === 0) {
return '';
}
@@ -700,7 +795,13 @@ final readonly class BigDecimal extends BigNumber
*/
public function hasNonZeroFractionalPart(): bool
{
return $this->getFractionalPart() !== str_repeat('0', $this->scale);
if ($this->scale === 0) {
return false;
}
$value = $this->getUnscaledValueWithLeadingZeros();
return substr($value, -$this->scale) !== str_repeat('0', $this->scale);
}
#[Override]
@@ -727,7 +828,7 @@ final readonly class BigDecimal extends BigNumber
}
#[Override]
public function toScale(int $scale, RoundingMode $roundingMode = RoundingMode::UNNECESSARY): BigDecimal
public function toScale(int $scale, RoundingMode $roundingMode = RoundingMode::Unnecessary): BigDecimal
{
if ($scale === $this->scale) {
return $this;
@@ -745,14 +846,14 @@ final readonly class BigDecimal extends BigNumber
#[Override]
public function toFloat(): float
{
return (float) (string) $this;
return (float) $this->toString();
}
/**
* @return numeric-string
*/
#[Override]
public function __toString(): string
public function toString(): string
{
if ($this->scale === 0) {
/** @var numeric-string */

View File

@@ -9,6 +9,7 @@ use Brick\Math\Exception\IntegerOverflowException;
use Brick\Math\Exception\MathException;
use Brick\Math\Exception\NegativeNumberException;
use Brick\Math\Exception\NumberFormatException;
use Brick\Math\Exception\RoundingNecessaryException;
use Brick\Math\Internal\Calculator;
use Brick\Math\Internal\CalculatorRegistry;
use InvalidArgumentException;
@@ -18,7 +19,9 @@ use Override;
use function assert;
use function bin2hex;
use function chr;
use function count_chars;
use function filter_var;
use function func_num_args;
use function hex2bin;
use function in_array;
use function intdiv;
@@ -32,14 +35,15 @@ use function str_repeat;
use function strlen;
use function strtolower;
use function substr;
use function trigger_error;
use const E_USER_DEPRECATED;
use const FILTER_VALIDATE_INT;
/**
* An arbitrary-size integer.
* An arbitrarily large integer number.
*
* All methods accepting a number as a parameter accept either a BigInteger instance,
* an integer, or a string representing an arbitrary size integer.
* This class is immutable.
*/
final readonly class BigInteger extends BigNumber
{
@@ -85,7 +89,7 @@ final readonly class BigInteger extends BigNumber
public static function fromBase(string $number, int $base): BigInteger
{
if ($number === '') {
throw new NumberFormatException('The number cannot be empty.');
throw new NumberFormatException('The number must not be empty.');
}
if ($base < 2 || $base > 36) {
@@ -103,7 +107,7 @@ final readonly class BigInteger extends BigNumber
}
if ($number === '') {
throw new NumberFormatException('The number cannot be empty.');
throw new NumberFormatException('The number must not be empty.');
}
$number = ltrim($number, '0');
@@ -137,20 +141,23 @@ final readonly class BigInteger extends BigNumber
/**
* Parses a string containing an integer in an arbitrary base, using a custom alphabet.
*
* Because this method accepts an alphabet with any character, including dash, it does not handle negative numbers.
* This method is byte-oriented: the alphabet is interpreted as a sequence of single-byte characters.
* Multibyte UTF-8 characters are not supported.
*
* Because this method accepts any single-byte character, including dash, it does not handle negative numbers.
*
* @param string $number The number to parse.
* @param string $alphabet The alphabet, for example '01' for base 2, or '01234567' for base 8.
*
* @throws NumberFormatException If the given number is empty or contains invalid chars for the given alphabet.
* @throws InvalidArgumentException If the alphabet does not contain at least 2 chars.
* @throws InvalidArgumentException If the alphabet does not contain at least 2 chars, or contains duplicates.
*
* @pure
*/
public static function fromArbitraryBase(string $number, string $alphabet): BigInteger
{
if ($number === '') {
throw new NumberFormatException('The number cannot be empty.');
throw new NumberFormatException('The number must not be empty.');
}
$base = strlen($alphabet);
@@ -159,6 +166,10 @@ final readonly class BigInteger extends BigNumber
throw new InvalidArgumentException('The alphabet must contain at least 2 chars.');
}
if (strlen(count_chars($alphabet, 3)) !== $base) {
throw new InvalidArgumentException('The alphabet must not contain duplicate chars.');
}
$pattern = '/[^' . preg_quote($alphabet, '/') . ']/';
if (preg_match($pattern, $number, $matches) === 1) {
@@ -229,7 +240,7 @@ final readonly class BigInteger extends BigNumber
public static function randomBits(int $numBits, ?callable $randomBytesGenerator = null): BigInteger
{
if ($numBits < 0) {
throw new InvalidArgumentException('The number of bits cannot be negative.');
throw new InvalidArgumentException('The number of bits must not be negative.');
}
if ($numBits === 0) {
@@ -253,7 +264,7 @@ final readonly class BigInteger extends BigNumber
}
/**
* Generates a pseudo-random number between `$min` and `$max`.
* Generates a pseudo-random number between `$min` and `$max`, inclusive.
*
* Using the default random bytes generator, this method is suitable for cryptographic use.
*
@@ -275,7 +286,7 @@ final readonly class BigInteger extends BigNumber
$max = BigInteger::of($max);
if ($min->isGreaterThan($max)) {
throw new MathException('$min cannot be greater than $max.');
throw new MathException('$min must be less than or equal to $max.');
}
if ($min->isEqualTo($max)) {
@@ -345,14 +356,23 @@ final readonly class BigInteger extends BigNumber
}
/**
* Returns the greatest common divisor of the given numbers.
*
* The GCD is always positive, unless all numbers are zero, in which case it is zero.
*
* @param BigNumber|int|float|string $a The first number. Must be convertible to a BigInteger.
* @param BigNumber|int|float|string ...$n The additional numbers. Each number must be convertible to a BigInteger.
*
* @throws MathException If one of the parameters cannot be converted to a BigInteger.
*
* @pure
*/
public static function gcdMultiple(BigInteger $a, BigInteger ...$n): BigInteger
public static function gcdAll(BigNumber|int|float|string $a, BigNumber|int|float|string ...$n): BigInteger
{
$result = $a;
$result = BigInteger::of($a)->abs();
foreach ($n as $next) {
$result = $result->gcd($next);
$result = $result->gcd(BigInteger::of($next));
if ($result->isEqualTo(1)) {
return $result;
@@ -362,6 +382,49 @@ final readonly class BigInteger extends BigNumber
return $result;
}
/**
* Returns the least common multiple of the given numbers.
*
* The LCM is always positive, unless one of the numbers is zero, in which case it is zero.
*
* @param BigNumber|int|float|string $a The first number. Must be convertible to a BigInteger.
* @param BigNumber|int|float|string ...$n The additional numbers. Each number must be convertible to a BigInteger.
*
* @throws MathException If one of the parameters cannot be converted to a BigInteger.
*
* @pure
*/
public static function lcmAll(BigNumber|int|float|string $a, BigNumber|int|float|string ...$n): BigInteger
{
$result = BigInteger::of($a)->abs();
foreach ($n as $next) {
$result = $result->lcm(BigInteger::of($next));
if ($result->isZero()) {
return $result;
}
}
return $result;
}
/**
* @deprecated Use gcdAll() instead.
*
* @param BigNumber|int|float|string $a The first number. Must be convertible to a BigInteger.
* @param BigNumber|int|float|string ...$n The subsequent numbers. Must be convertible to BigInteger.
*/
public static function gcdMultiple(BigNumber|int|float|string $a, BigNumber|int|float|string ...$n): BigInteger
{
trigger_error(
'BigInteger::gcdMultiple() is deprecated and will be removed in version 0.15. Use gcdAll() instead.',
E_USER_DEPRECATED,
);
return self::gcdAll($a, ...$n);
}
/**
* Returns the sum of this number and the given one.
*
@@ -415,7 +478,7 @@ final readonly class BigInteger extends BigNumber
*
* @param BigNumber|int|float|string $that The multiplier. Must be convertible to a BigInteger.
*
* @throws MathException If the multiplier is not a valid number, or is not convertible to a BigInteger.
* @throws MathException If the multiplier is not valid, or is not convertible to a BigInteger.
*
* @pure
*/
@@ -440,14 +503,15 @@ final readonly class BigInteger extends BigNumber
* Returns the result of the division of this number by the given one.
*
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigInteger.
* @param RoundingMode $roundingMode An optional rounding mode, defaults to UNNECESSARY.
* @param RoundingMode $roundingMode An optional rounding mode, defaults to Unnecessary.
*
* @throws MathException If the divisor is not a valid number, is not convertible to a BigInteger, is zero,
* or RoundingMode::UNNECESSARY is used and the remainder is not zero.
* @throws MathException If the divisor is not valid, or is not convertible to a BigInteger.
* @throws DivisionByZeroException If the divisor is zero.
* @throws RoundingNecessaryException If RoundingMode::Unnecessary is used and the remainder is not zero.
*
* @pure
*/
public function dividedBy(BigNumber|int|float|string $that, RoundingMode $roundingMode = RoundingMode::UNNECESSARY): BigInteger
public function dividedBy(BigNumber|int|float|string $that, RoundingMode $roundingMode = RoundingMode::Unnecessary): BigInteger
{
$that = BigInteger::of($that);
@@ -464,29 +528,6 @@ final readonly class BigInteger extends BigNumber
return new BigInteger($result);
}
/**
* Limits (clamps) this number between the given minimum and maximum values.
*
* If the number is lower than $min, returns a copy of $min.
* If the number is greater than $max, returns a copy of $max.
* Otherwise, returns this number unchanged.
*
* @param BigNumber|int|float|string $min The minimum. Must be convertible to a BigInteger.
* @param BigNumber|int|float|string $max The maximum. Must be convertible to a BigInteger.
*
* @throws MathException If min/max are not convertible to a BigInteger.
*/
public function clamp(BigNumber|int|float|string $min, BigNumber|int|float|string $max): BigInteger
{
if ($this->isLessThan($min)) {
return BigInteger::of($min);
} elseif ($this->isGreaterThan($max)) {
return BigInteger::of($max);
}
return $this;
}
/**
* Returns this number exponentiated to the given value.
*
@@ -518,8 +559,16 @@ final readonly class BigInteger extends BigNumber
/**
* Returns the quotient of the division of this number by the given one.
*
* Examples:
*
* - `7` quotient `3` returns `2`
* - `7` quotient `-3` returns `-2`
* - `-7` quotient `3` returns `-2`
* - `-7` quotient `-3` returns `2`
*
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigInteger.
*
* @throws MathException If the divisor is not valid, or is not convertible to a BigInteger.
* @throws DivisionByZeroException If the divisor is zero.
*
* @pure
@@ -546,8 +595,16 @@ final readonly class BigInteger extends BigNumber
*
* The remainder, when non-zero, has the same sign as the dividend.
*
* Examples:
*
* - `7` remainder `3` returns `1`
* - `7` remainder `-3` returns `1`
* - `-7` remainder `3` returns `-1`
* - `-7` remainder `-3` returns `-1`
*
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigInteger.
*
* @throws MathException If the divisor is not valid, or is not convertible to a BigInteger.
* @throws DivisionByZeroException If the divisor is zero.
*
* @pure
@@ -572,10 +629,18 @@ final readonly class BigInteger extends BigNumber
/**
* Returns the quotient and remainder of the division of this number by the given one.
*
* Examples:
*
* - `7` quotientAndRemainder `3` returns [`2`, `1`]
* - `7` quotientAndRemainder `-3` returns [`-2`, `1`]
* - `-7` quotientAndRemainder `3` returns [`-2`, `-1`]
* - `-7` quotientAndRemainder `-3` returns [`2`, `-1`]
*
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigInteger.
*
* @return array{BigInteger, BigInteger} An array containing the quotient and the remainder.
*
* @throws MathException If the divisor is not valid, or is not convertible to a BigInteger.
* @throws DivisionByZeroException If the divisor is zero.
*
* @pure
@@ -606,6 +671,7 @@ final readonly class BigInteger extends BigNumber
*
* @param BigNumber|int|float|string $that The divisor. Must be convertible to a BigInteger.
*
* @throws MathException If the divisor is not valid, or is not convertible to a BigInteger.
* @throws DivisionByZeroException If the divisor is zero.
*
* @pure
@@ -614,10 +680,18 @@ final readonly class BigInteger extends BigNumber
{
$that = BigInteger::of($that);
if ($that->value === '0') {
if ($that->isZero()) {
throw DivisionByZeroException::modulusMustNotBeZero();
}
if ($that->isNegative()) {
// @phpstan-ignore-next-line
trigger_error(
'Passing a negative modulus to BigInteger::mod() is deprecated and will throw a NegativeNumberException in 0.15.',
E_USER_DEPRECATED,
);
}
$value = CalculatorRegistry::get()->mod($this->value, $that->value);
return new BigInteger($value);
@@ -626,6 +700,9 @@ final readonly class BigInteger extends BigNumber
/**
* Returns the modular multiplicative inverse of this BigInteger modulo $m.
*
* @param BigNumber|int|float|string $m The modulus. Must be convertible to a BigInteger.
*
* @throws MathException If the modulus is not valid, or is not convertible to a BigInteger.
* @throws DivisionByZeroException If $m is zero.
* @throws NegativeNumberException If $m is negative.
* @throws MathException If this BigInteger has no multiplicative inverse mod m (that is, this BigInteger
@@ -633,8 +710,10 @@ final readonly class BigInteger extends BigNumber
*
* @pure
*/
public function modInverse(BigInteger $m): BigInteger
public function modInverse(BigNumber|int|float|string $m): BigInteger
{
$m = BigInteger::of($m);
if ($m->value === '0') {
throw DivisionByZeroException::modulusMustNotBeZero();
}
@@ -659,12 +738,13 @@ final readonly class BigInteger extends BigNumber
/**
* Returns this number raised into power with modulo.
*
* This operation only works on positive numbers.
* This operation requires a non-negative exponent and a strictly positive modulus.
*
* @param BigNumber|int|float|string $exp The exponent. Must be positive or zero.
* @param BigNumber|int|float|string $mod The modulus. Must be strictly positive.
* @param BigNumber|int|float|string $exp The exponent. Must be convertible to a BigInteger.
* @param BigNumber|int|float|string $mod The modulus. Must be convertible to a BigInteger.
*
* @throws NegativeNumberException If any of the operands is negative.
* @throws MathException If the exponent or modulus is not valid, or is not convertible to a BigInteger.
* @throws NegativeNumberException If the exponent or modulus is negative.
* @throws DivisionByZeroException If the modulus is zero.
*
* @pure
@@ -674,8 +754,12 @@ final readonly class BigInteger extends BigNumber
$exp = BigInteger::of($exp);
$mod = BigInteger::of($mod);
if ($this->isNegative() || $exp->isNegative() || $mod->isNegative()) {
throw new NegativeNumberException('The operands cannot be negative.');
if ($exp->isNegative()) {
throw new NegativeNumberException('The exponent cannot be negative.');
}
if ($mod->isNegative()) {
throw new NegativeNumberException('The modulus cannot be negative.');
}
if ($mod->isZero()) {
@@ -692,7 +776,9 @@ final readonly class BigInteger extends BigNumber
*
* The GCD is always positive, unless both operands are zero, in which case it is zero.
*
* @param BigNumber|int|float|string $that The operand. Must be convertible to an integer number.
* @param BigNumber|int|float|string $that The operand. Must be convertible to a BigInteger.
*
* @throws MathException If the operand is not valid, or is not convertible to a BigInteger.
*
* @pure
*/
@@ -714,41 +800,107 @@ final readonly class BigInteger extends BigNumber
}
/**
* Returns the integer square root number of this number, rounded down.
* Returns the least common multiple of this number and the given one.
*
* The result is the largest x such that x² ≤ n.
* The LCM is always positive, unless at least one operand is zero, in which case it is zero.
*
* @throws NegativeNumberException If this number is negative.
* @param BigNumber|int|float|string $that The operand. Must be convertible to a BigInteger.
*
* @throws MathException If the operand is not valid, or is not convertible to a BigInteger.
*
* @pure
*/
public function sqrt(): BigInteger
public function lcm(BigNumber|int|float|string $that): BigInteger
{
if ($this->value[0] === '-') {
throw new NegativeNumberException('Cannot calculate the square root of a negative number.');
$that = BigInteger::of($that);
if ($this->isZero() || $that->isZero()) {
return BigInteger::zero();
}
$value = CalculatorRegistry::get()->sqrt($this->value);
$value = CalculatorRegistry::get()->lcm($this->value, $that->value);
return new BigInteger($value);
}
/**
* Returns the absolute value of this number.
* Returns the integer square root of this number, rounded according to the given rounding mode.
*
* @param RoundingMode $roundingMode The rounding mode to use, defaults to Down.
* ⚠️ WARNING: the default rounding mode was kept as Down for backward
* compatibility, but will change to Unnecessary in version 0.15. Pass a rounding
* mode explicitly to avoid this upcoming breaking change.
*
* @throws NegativeNumberException If this number is negative.
* @throws RoundingNecessaryException If RoundingMode::Unnecessary is used, and the number is not a perfect square.
*
* @pure
*/
public function abs(): BigInteger
public function sqrt(RoundingMode $roundingMode = RoundingMode::Down): BigInteger
{
return $this->isNegative() ? $this->negated() : $this;
if (func_num_args() === 0) {
// @phpstan-ignore-next-line
trigger_error(
'The default rounding mode of BigInteger::sqrt() will change from Down to Unnecessary in version 0.15. ' .
'Pass a rounding mode explicitly to avoid this breaking change.',
E_USER_DEPRECATED,
);
}
if ($this->value[0] === '-') {
throw new NegativeNumberException('Cannot calculate the square root of a negative number.');
}
$calculator = CalculatorRegistry::get();
$sqrt = $calculator->sqrt($this->value);
// For Down and Floor (equivalent for non-negative numbers), return floor sqrt
if ($roundingMode === RoundingMode::Down || $roundingMode === RoundingMode::Floor) {
return new BigInteger($sqrt);
}
// Check if the sqrt is exact
$s2 = $calculator->mul($sqrt, $sqrt);
$remainder = $calculator->sub($this->value, $s2);
if ($remainder === '0') {
// sqrt is exact
return new BigInteger($sqrt);
}
// sqrt is not exact
if ($roundingMode === RoundingMode::Unnecessary) {
throw RoundingNecessaryException::roundingNecessary();
}
// For Up and Ceiling (equivalent for non-negative numbers), round up
if ($roundingMode === RoundingMode::Up || $roundingMode === RoundingMode::Ceiling) {
return new BigInteger($calculator->add($sqrt, '1'));
}
// For Half* modes, compare our number to the midpoint of the interval [s², (s+1)²[.
// The midpoint is s² + s + 0.5. Comparing n >= s² + s + 0.5 with remainder = n
// is equivalent to comparing 2*remainder >= 2*s + 1.
$twoRemainder = $calculator->mul($remainder, '2');
$threshold = $calculator->add($calculator->mul($sqrt, '2'), '1');
$cmp = $calculator->cmp($twoRemainder, $threshold);
// We're supposed to increment (round up) when:
// - HalfUp, HalfCeiling => $cmp >= 0
// - HalfDown, HalfFloor => $cmp > 0
// - HalfEven => $cmp > 0 || ($cmp === 0 && $sqrt % 2 === 1)
// But 2*remainder is always even and 2*s + 1 is always odd, so $cmp is never zero.
// Therefore, all Half* modes simplify to:
if ($cmp > 0) {
$sqrt = $calculator->add($sqrt, '1');
}
return new BigInteger($sqrt);
}
/**
* Returns the inverse of this number.
*
* @pure
*/
public function negated(): BigInteger
#[Override]
public function negated(): static
{
return new BigInteger(CalculatorRegistry::get()->neg($this->value));
}
@@ -758,7 +910,9 @@ final readonly class BigInteger extends BigNumber
*
* This method returns a negative BigInteger if and only if both operands are negative.
*
* @param BigNumber|int|float|string $that The operand. Must be convertible to an integer number.
* @param BigNumber|int|float|string $that The operand. Must be convertible to a BigInteger.
*
* @throws MathException If the operand is not valid, or is not convertible to a BigInteger.
*
* @pure
*/
@@ -774,7 +928,9 @@ final readonly class BigInteger extends BigNumber
*
* This method returns a negative BigInteger if and only if either of the operands is negative.
*
* @param BigNumber|int|float|string $that The operand. Must be convertible to an integer number.
* @param BigNumber|int|float|string $that The operand. Must be convertible to a BigInteger.
*
* @throws MathException If the operand is not valid, or is not convertible to a BigInteger.
*
* @pure
*/
@@ -790,7 +946,9 @@ final readonly class BigInteger extends BigNumber
*
* This method returns a negative BigInteger if and only if exactly one of the operands is negative.
*
* @param BigNumber|int|float|string $that The operand. Must be convertible to an integer number.
* @param BigNumber|int|float|string $that The operand. Must be convertible to a BigInteger.
*
* @throws MathException If the operand is not valid, or is not convertible to a BigInteger.
*
* @pure
*/
@@ -814,6 +972,8 @@ final readonly class BigInteger extends BigNumber
/**
* Returns the integer left shifted by a given number of bits.
*
* @throws InvalidArgumentException If the number of bits is out of range.
*
* @pure
*/
public function shiftedLeft(int $distance): BigInteger
@@ -832,6 +992,8 @@ final readonly class BigInteger extends BigNumber
/**
* Returns the integer right shifted by a given number of bits.
*
* @throws InvalidArgumentException If the number of bits is out of range.
*
* @pure
*/
public function shiftedRight(int $distance): BigInteger
@@ -850,7 +1012,7 @@ final readonly class BigInteger extends BigNumber
return $this->quotient($operand);
}
return $this->dividedBy($operand, RoundingMode::UP);
return $this->dividedBy($operand, RoundingMode::Up);
}
/**
@@ -897,6 +1059,26 @@ final readonly class BigInteger extends BigNumber
return -1;
}
/**
* Returns true if and only if the designated bit is set.
*
* Computes ((this & (1<<n)) != 0).
*
* @param int $n The bit to test, 0-based.
*
* @throws InvalidArgumentException If the bit to test is negative.
*
* @pure
*/
public function isBitSet(int $n): bool
{
if ($n < 0) {
throw new InvalidArgumentException('The bit to test cannot be negative.');
}
return $this->shiftedRight($n)->isOdd();
}
/**
* Returns whether this number is even.
*
@@ -922,19 +1104,20 @@ final readonly class BigInteger extends BigNumber
*
* Computes ((this & (1<<n)) != 0).
*
* @deprecated Use isBitSet().
*
* @param int $n The bit to test, 0-based.
*
* @throws InvalidArgumentException If the bit to test is negative.
*
* @pure
*/
public function testBit(int $n): bool
{
if ($n < 0) {
throw new InvalidArgumentException('The bit to test cannot be negative.');
}
trigger_error(
'The BigInteger::testBit() method is deprecated, use isBitSet() instead.',
E_USER_DEPRECATED,
);
return $this->shiftedRight($n)->isOdd();
return $this->isBitSet($n);
}
#[Override]
@@ -974,7 +1157,7 @@ final readonly class BigInteger extends BigNumber
}
#[Override]
public function toScale(int $scale, RoundingMode $roundingMode = RoundingMode::UNNECESSARY): BigDecimal
public function toScale(int $scale, RoundingMode $roundingMode = RoundingMode::Unnecessary): BigDecimal
{
return $this->toBigDecimal()->toScale($scale, $roundingMode);
}
@@ -1022,13 +1205,16 @@ final readonly class BigInteger extends BigNumber
/**
* Returns a string representation of this number in an arbitrary base with a custom alphabet.
*
* Because this method accepts an alphabet with any character, including dash, it does not handle negative numbers;
* This method is byte-oriented: the alphabet is interpreted as a sequence of single-byte characters.
* Multibyte UTF-8 characters are not supported.
*
* Because this method accepts any single-byte character, including dash, it does not handle negative numbers;
* a NegativeNumberException will be thrown when attempting to call this method on a negative number.
*
* @param string $alphabet The alphabet, for example '01' for base 2, or '01234567' for base 8.
*
* @throws NegativeNumberException If this number is negative.
* @throws InvalidArgumentException If the given alphabet does not contain at least 2 chars.
* @throws InvalidArgumentException If the alphabet does not contain at least 2 chars, or contains duplicates.
*
* @pure
*/
@@ -1040,6 +1226,10 @@ final readonly class BigInteger extends BigNumber
throw new InvalidArgumentException('The alphabet must contain at least 2 chars.');
}
if (strlen(count_chars($alphabet, 3)) !== $base) {
throw new InvalidArgumentException('The alphabet must not contain duplicate chars.');
}
if ($this->value[0] === '-') {
throw new NegativeNumberException(__FUNCTION__ . '() does not support negative numbers.');
}
@@ -1115,7 +1305,7 @@ final readonly class BigInteger extends BigNumber
* @return numeric-string
*/
#[Override]
public function __toString(): string
public function toString(): string
{
/** @var numeric-string */
return $this->value;

View File

@@ -26,9 +26,10 @@ use function str_contains;
use function str_repeat;
use function strlen;
use function substr;
use function trigger_error;
use const E_USER_DEPRECATED;
use const FILTER_VALIDATE_INT;
use const PREG_UNMATCHED_AS_NULL;
/**
@@ -60,7 +61,7 @@ abstract readonly class BigNumber implements JsonSerializable, Stringable
'/^' .
'(?<sign>[\-\+])?' .
'(?<numerator>[0-9]+)' .
'\/?' .
'\/' .
'(?<denominator>[0-9]+)' .
'$/';
@@ -72,7 +73,7 @@ abstract readonly class BigNumber implements JsonSerializable, Stringable
*
* - BigNumber instances are returned as is
* - integer numbers are returned as BigInteger
* - floating point numbers are converted to a string then parsed as such
* - floating point numbers are converted to a string then parsed as such (deprecated, will be removed in 0.15)
* - strings containing a `/` character are returned as BigRational
* - strings containing a `.` character or using an exponential notation are returned as BigDecimal
* - strings containing only digits with an optional leading `+` or `-` sign are returned as BigInteger
@@ -109,8 +110,10 @@ abstract readonly class BigNumber implements JsonSerializable, Stringable
* @throws NumberFormatException If the format of the number is not valid.
* @throws DivisionByZeroException If the value represents a rational number with a denominator of zero.
* @throws RoundingNecessaryException If the value cannot be converted to an instance of the subclass without rounding.
*
* @pure
*/
public static function ofNullable(BigNumber|int|float|string|null $value): ?static
final public static function ofNullable(BigNumber|int|float|string|null $value): ?static
{
if (is_null($value)) {
return null;
@@ -122,11 +125,15 @@ abstract readonly class BigNumber implements JsonSerializable, Stringable
/**
* Returns the minimum of the given values.
*
* @param BigNumber|int|float|string ...$values The numbers to compare. All the numbers need to be convertible
* to an instance of the class this method is called on.
* If several values are equal and minimal, the first one is returned.
* This can affect the concrete return type when calling this method on BigNumber.
*
* @param BigNumber|int|float|string ...$values The numbers to compare. All the numbers must be convertible to an
* instance of the class this method is called on.
*
* @throws InvalidArgumentException If no values are given.
* @throws MathException If an argument is not valid.
* @throws MathException If a number is not valid, or is not convertible to an instance of the class
* this method is called on.
*
* @pure
*/
@@ -152,11 +159,15 @@ abstract readonly class BigNumber implements JsonSerializable, Stringable
/**
* Returns the maximum of the given values.
*
* @param BigNumber|int|float|string ...$values The numbers to compare. All the numbers need to be convertible
* to an instance of the class this method is called on.
* If several values are equal and maximal, the first one is returned.
* This can affect the concrete return type when calling this method on BigNumber.
*
* @param BigNumber|int|float|string ...$values The numbers to compare. All the numbers must be convertible to an
* instance of the class this method is called on.
*
* @throws InvalidArgumentException If no values are given.
* @throws MathException If an argument is not valid.
* @throws MathException If a number is not valid, or is not convertible to an instance of the class
* this method is called on.
*
* @pure
*/
@@ -188,11 +199,12 @@ abstract readonly class BigNumber implements JsonSerializable, Stringable
* When called on BigInteger, BigDecimal, or BigRational, sum() requires that all values can be converted to that
* specific subclass, and returns a result of the same type.
*
* @param BigNumber|int|float|string ...$values The values to add. All values must be convertible to the class on
* which this method is called.
* @param BigNumber|int|float|string ...$values The numbers to add. All the numbers must be convertible to an
* instance of the class this method is called on.
*
* @throws InvalidArgumentException If no values are given.
* @throws MathException If an argument is not valid.
* @throws MathException If a number is not valid, or is not convertible to an instance of the class
* this method is called on.
*
* @pure
*/
@@ -218,6 +230,8 @@ abstract readonly class BigNumber implements JsonSerializable, Stringable
/**
* Checks if this number is equal to the given one.
*
* @throws MathException If the given number is not valid.
*
* @pure
*/
final public function isEqualTo(BigNumber|int|float|string $that): bool
@@ -226,7 +240,9 @@ abstract readonly class BigNumber implements JsonSerializable, Stringable
}
/**
* Checks if this number is strictly lower than the given one.
* Checks if this number is strictly less than the given one.
*
* @throws MathException If the given number is not valid.
*
* @pure
*/
@@ -236,7 +252,9 @@ abstract readonly class BigNumber implements JsonSerializable, Stringable
}
/**
* Checks if this number is lower than or equal to the given one.
* Checks if this number is less than or equal to the given one.
*
* @throws MathException If the given number is not valid.
*
* @pure
*/
@@ -248,6 +266,8 @@ abstract readonly class BigNumber implements JsonSerializable, Stringable
/**
* Checks if this number is strictly greater than the given one.
*
* @throws MathException If the given number is not valid.
*
* @pure
*/
final public function isGreaterThan(BigNumber|int|float|string $that): bool
@@ -258,6 +278,8 @@ abstract readonly class BigNumber implements JsonSerializable, Stringable
/**
* Checks if this number is greater than or equal to the given one.
*
* @throws MathException If the given number is not valid.
*
* @pure
*/
final public function isGreaterThanOrEqualTo(BigNumber|int|float|string $that): bool
@@ -315,6 +337,23 @@ abstract readonly class BigNumber implements JsonSerializable, Stringable
return $this->getSign() >= 0;
}
/**
* Returns the absolute value of this number.
*
* @pure
*/
final public function abs(): static
{
return $this->isNegative() ? $this->negated() : $this;
}
/**
* Returns the negated value of this number.
*
* @pure
*/
abstract public function negated(): static;
/**
* Returns the sign of this number.
*
@@ -339,6 +378,41 @@ abstract readonly class BigNumber implements JsonSerializable, Stringable
*/
abstract public function compareTo(BigNumber|int|float|string $that): int;
/**
* Limits (clamps) this number between the given minimum and maximum values.
*
* If the number is lower than $min, returns $min.
* If the number is greater than $max, returns $max.
* Otherwise, returns this number unchanged.
*
* @param BigNumber|int|float|string $min The minimum. Must be convertible to an instance of the class this method is called on.
* @param BigNumber|int|float|string $max The maximum. Must be convertible to an instance of the class this method is called on.
*
* @throws MathException If min/max are not convertible to an instance of the class this method is called on.
* @throws InvalidArgumentException If min is greater than max.
*
* @pure
*/
final public function clamp(BigNumber|int|float|string $min, BigNumber|int|float|string $max): static
{
$min = static::of($min);
$max = static::of($max);
if ($min->isGreaterThan($max)) {
throw new InvalidArgumentException('Minimum value must be less than or equal to maximum value.');
}
if ($this->isLessThan($min)) {
return $min;
}
if ($this->isGreaterThan($max)) {
return $max;
}
return $this;
}
/**
* Converts this number to a BigInteger.
*
@@ -367,15 +441,16 @@ abstract readonly class BigNumber implements JsonSerializable, Stringable
/**
* Converts this number to a BigDecimal with the given scale, using rounding if necessary.
*
* @param int $scale The scale of the resulting `BigDecimal`.
* @param RoundingMode $roundingMode An optional rounding mode, defaults to UNNECESSARY.
* @param int $scale The scale of the resulting `BigDecimal`. Must be non-negative.
* @param RoundingMode $roundingMode An optional rounding mode, defaults to Unnecessary.
*
* @throws RoundingNecessaryException If this number cannot be converted to the given scale without rounding.
* This only applies when RoundingMode::UNNECESSARY is used.
* @throws InvalidArgumentException If the scale is negative.
* @throws RoundingNecessaryException If RoundingMode::Unnecessary is used, and this number cannot be converted to
* the given scale without rounding.
*
* @pure
*/
abstract public function toScale(int $scale, RoundingMode $roundingMode = RoundingMode::UNNECESSARY): BigDecimal;
abstract public function toScale(int $scale, RoundingMode $roundingMode = RoundingMode::Unnecessary): BigDecimal;
/**
* Returns the exact value of this number as a native integer.
@@ -397,26 +472,35 @@ abstract readonly class BigNumber implements JsonSerializable, Stringable
*
* If the number is greater than the largest representable floating point number, positive infinity is returned.
* If the number is less than the smallest representable floating point number, negative infinity is returned.
* This method never returns NaN.
*
* @pure
*/
abstract public function toFloat(): float;
#[Override]
final public function jsonSerialize(): string
{
return $this->__toString();
}
/**
* Returns a string representation of this number.
*
* The output of this method can be parsed by the `of()` factory method;
* this will yield an object equal to this one, without any information loss.
* The output of this method can be parsed by the `of()` factory method; this will yield an object equal to this
* one, but possibly of a different type if instantiated through `BigNumber::of()`.
*
* @pure
*/
abstract public function __toString(): string;
abstract public function toString(): string;
#[Override]
final public function jsonSerialize(): string
{
return $this->toString();
}
/**
* @pure
*/
final public function __toString(): string
{
return $this->toString();
}
/**
* Overridden by subclasses to convert a BigNumber to an instance of the subclass.
@@ -480,6 +564,13 @@ abstract readonly class BigNumber implements JsonSerializable, Stringable
}
if (is_float($value)) {
// @phpstan-ignore-next-line
trigger_error(
'Passing floats to BigNumber::of() and arithmetic methods is deprecated and will be removed in 0.15. ' .
'Cast the float to string explicitly to preserve the previous behaviour.',
E_USER_DEPRECATED,
);
if (is_nan($value)) {
$value = 'NAN';
} else {

View File

@@ -12,10 +12,22 @@ use InvalidArgumentException;
use LogicException;
use Override;
use function is_finite;
use function max;
use function min;
use function strlen;
use function substr;
use function trigger_error;
use const E_USER_DEPRECATED;
/**
* An arbitrarily large rational number.
*
* This class is immutable.
*
* Fractions are automatically simplified to lowest terms. For example, `2/4` becomes `1/2`.
* The denominator is always strictly positive; the sign is carried by the numerator.
*/
final readonly class BigRational extends BigNumber
{
@@ -63,16 +75,42 @@ final readonly class BigRational extends BigNumber
* If the denominator is negative, the signs of both the numerator and the denominator
* will be inverted to ensure that the denominator is always positive.
*
* @deprecated Use ofFraction() instead.
*
* @param BigNumber|int|float|string $numerator The numerator. Must be convertible to a BigInteger.
* @param BigNumber|int|float|string $denominator The denominator. Must be convertible to a BigInteger.
*
* @throws NumberFormatException If an argument does not represent a valid number.
* @throws RoundingNecessaryException If an argument represents a non-integer number.
* @throws DivisionByZeroException If the denominator is zero.
*/
public static function nd(
BigNumber|int|float|string $numerator,
BigNumber|int|float|string $denominator,
): BigRational {
trigger_error(
'The BigRational::nd() method is deprecated, use BigRational::ofFraction() instead.',
E_USER_DEPRECATED,
);
return self::ofFraction($numerator, $denominator);
}
/**
* Creates a BigRational out of a numerator and a denominator.
*
* If the denominator is negative, the signs of both the numerator and the denominator
* will be inverted to ensure that the denominator is always positive.
*
* @param BigNumber|int|float|string $numerator The numerator. Must be convertible to a BigInteger.
* @param BigNumber|int|float|string $denominator The denominator. Must be convertible to a BigInteger.
*
* @throws MathException If an argument is not valid, or is not convertible to a BigInteger.
* @throws DivisionByZeroException If the denominator is zero.
*
* @pure
*/
public static function nd(
public static function ofFraction(
BigNumber|int|float|string $numerator,
BigNumber|int|float|string $denominator,
): BigRational {
@@ -152,35 +190,84 @@ final readonly class BigRational extends BigNumber
/**
* Returns the quotient of the division of the numerator by the denominator.
*
* @pure
* @deprecated Will be removed in 0.15. Use getIntegralPart() instead.
*/
public function quotient(): BigInteger
{
trigger_error(
'BigRational::quotient() is deprecated and will be removed in 0.15. Use getIntegralPart() instead.',
E_USER_DEPRECATED,
);
return $this->numerator->quotient($this->denominator);
}
/**
* Returns the remainder of the division of the numerator by the denominator.
*
* @pure
* @deprecated Will be removed in 0.15. Use `$number->getNumerator()->remainder($number->getDenominator())` instead.
*/
public function remainder(): BigInteger
{
trigger_error(
'BigRational::remainder() is deprecated and will be removed in 0.15. Use `$number->getNumerator()->remainder($number->getDenominator())` instead.',
E_USER_DEPRECATED,
);
return $this->numerator->remainder($this->denominator);
}
/**
* Returns the quotient and remainder of the division of the numerator by the denominator.
*
* @return array{BigInteger, BigInteger}
* @deprecated Will be removed in 0.15. Use `$number->getNumerator()->quotientAndRemainder($number->getDenominator())` instead.
*
* @pure
* @return array{BigInteger, BigInteger}
*/
public function quotientAndRemainder(): array
{
trigger_error(
'BigRational::quotientAndRemainder() is deprecated and will be removed in 0.15. Use `$number->getNumerator()->quotientAndRemainder($number->getDenominator())` instead.',
E_USER_DEPRECATED,
);
return $this->numerator->quotientAndRemainder($this->denominator);
}
/**
* Returns the integral part of this rational number.
*
* Examples:
*
* - `7/3` returns `2` (since 7/3 = 2 + 1/3)
* - `-7/3` returns `-2` (since -7/3 = -2 + (-1/3))
*
* The following identity holds: `$r->isEqualTo($r->getFractionalPart()->plus($r->getIntegralPart()))`.
*
* @pure
*/
public function getIntegralPart(): BigInteger
{
return $this->numerator->quotient($this->denominator);
}
/**
* Returns the fractional part of this rational number.
*
* Examples:
*
* - `7/3` returns `1/3` (since 7/3 = 2 + 1/3)
* - `-7/3` returns `-1/3` (since -7/3 = -2 + (-1/3))
*
* The following identity holds: `$r->isEqualTo($r->getFractionalPart()->plus($r->getIntegralPart()))`.
*
* @pure
*/
public function getFractionalPart(): BigRational
{
return new BigRational($this->numerator->remainder($this->denominator), $this->denominator, false);
}
/**
* Returns the sum of this number and the given one.
*
@@ -226,7 +313,7 @@ final readonly class BigRational extends BigNumber
*
* @param BigNumber|int|float|string $that The multiplier.
*
* @throws MathException If the multiplier is not a valid number.
* @throws MathException If the multiplier is not valid.
*
* @pure
*/
@@ -245,7 +332,8 @@ final readonly class BigRational extends BigNumber
*
* @param BigNumber|int|float|string $that The divisor.
*
* @throws MathException If the divisor is not a valid number, or is zero.
* @throws MathException If the divisor is not valid.
* @throws DivisionByZeroException If the divisor is zero.
*
* @pure
*/
@@ -253,6 +341,10 @@ final readonly class BigRational extends BigNumber
{
$that = BigRational::of($that);
if ($that->isZero()) {
throw DivisionByZeroException::divisionByZero();
}
$numerator = $this->numerator->multipliedBy($that->denominator);
$denominator = $this->denominator->multipliedBy($that->numerator);
@@ -269,9 +361,7 @@ final readonly class BigRational extends BigNumber
public function power(int $exponent): BigRational
{
if ($exponent === 0) {
$one = BigInteger::one();
return new BigRational($one, $one, false);
return BigRational::one();
}
if ($exponent === 1) {
@@ -299,22 +389,8 @@ final readonly class BigRational extends BigNumber
return new BigRational($this->denominator, $this->numerator, true);
}
/**
* Returns the absolute value of this BigRational.
*
* @pure
*/
public function abs(): BigRational
{
return new BigRational($this->numerator->abs(), $this->denominator, false);
}
/**
* Returns the negated value of this BigRational.
*
* @pure
*/
public function negated(): BigRational
#[Override]
public function negated(): static
{
return new BigRational($this->numerator->negated(), $this->denominator, false);
}
@@ -337,7 +413,15 @@ final readonly class BigRational extends BigNumber
#[Override]
public function compareTo(BigNumber|int|float|string $that): int
{
return $this->minus($that)->getSign();
$that = BigRational::of($that);
if ($this->denominator->isEqualTo($that->denominator)) {
return $this->numerator->compareTo($that->numerator);
}
return $this->numerator
->multipliedBy($that->denominator)
->compareTo($that->numerator->multipliedBy($this->denominator));
}
#[Override]
@@ -361,7 +445,7 @@ final readonly class BigRational extends BigNumber
#[Override]
public function toBigDecimal(): BigDecimal
{
return $this->numerator->toBigDecimal()->exactlyDividedBy($this->denominator);
return $this->numerator->toBigDecimal()->dividedByExact($this->denominator);
}
#[Override]
@@ -371,7 +455,7 @@ final readonly class BigRational extends BigNumber
}
#[Override]
public function toScale(int $scale, RoundingMode $roundingMode = RoundingMode::UNNECESSARY): BigDecimal
public function toScale(int $scale, RoundingMode $roundingMode = RoundingMode::Unnecessary): BigDecimal
{
return $this->numerator->toBigDecimal()->dividedBy($this->denominator, $scale, $roundingMode);
}
@@ -386,15 +470,32 @@ final readonly class BigRational extends BigNumber
public function toFloat(): float
{
$simplified = $this->simplified();
$numeratorFloat = $simplified->numerator->toFloat();
$denominatorFloat = $simplified->denominator->toFloat();
return $simplified->numerator->toFloat() / $simplified->denominator->toFloat();
if (is_finite($numeratorFloat) && is_finite($denominatorFloat)) {
return $numeratorFloat / $denominatorFloat;
}
// At least one side overflows to INF; use a decimal approximation instead.
// We need ~17 significant digits for double precision (we use 20 for some margin). Since $scale controls
// decimal places (not significant digits), we subtract the estimated order of magnitude so that large results
// use fewer decimal places and small results use more (to look past leading zeros). Clamped to [0, 350] as
// doubles range from e-324 to e308 (350 ≈ 324 + 20 significant digits + margin).
$magnitude = strlen($simplified->numerator->abs()->toString()) - strlen($simplified->denominator->toString());
$scale = min(350, max(0, 20 - $magnitude));
return $simplified->numerator
->toBigDecimal()
->dividedBy($simplified->denominator, $scale, RoundingMode::HalfEven)
->toFloat();
}
#[Override]
public function __toString(): string
public function toString(): string
{
$numerator = (string) $this->numerator;
$denominator = (string) $this->denominator;
$numerator = $this->numerator->toString();
$denominator = $this->denominator->toString();
if ($denominator === '1') {
return $numerator;
@@ -403,6 +504,67 @@ final readonly class BigRational extends BigNumber
return $numerator . '/' . $denominator;
}
/**
* Returns the decimal representation of this rational number, with repeating decimals in parentheses.
*
* WARNING: This method is unbounded.
* The length of the repeating decimal period can be as large as `denominator - 1`.
* For fractions with large denominators, this method can use excessive memory and CPU time.
* For example, `1/100019` has a repeating period of 100,018 digits.
*
* Examples:
*
* - `10/3` returns `3.(3)`
* - `171/70` returns `2.4(428571)`
* - `1/2` returns `0.5`
*
* @pure
*/
public function toRepeatingDecimalString(): string
{
if ($this->numerator->isZero()) {
return '0';
}
$sign = $this->numerator->isNegative() ? '-' : '';
$numerator = $this->numerator->abs();
$denominator = $this->denominator;
$integral = $numerator->quotient($denominator);
$remainder = $numerator->remainder($denominator);
$integralString = $integral->toString();
if ($remainder->isZero()) {
return $sign . $integralString;
}
$digits = '';
$remainderPositions = [];
$index = 0;
while (! $remainder->isZero()) {
$remainderString = $remainder->toString();
if (isset($remainderPositions[$remainderString])) {
$repeatIndex = $remainderPositions[$remainderString];
$nonRepeating = substr($digits, 0, $repeatIndex);
$repeating = substr($digits, $repeatIndex);
return $sign . $integralString . '.' . $nonRepeating . '(' . $repeating . ')';
}
$remainderPositions[$remainderString] = $index;
$remainder = $remainder->multipliedBy(10);
$digits .= $remainder->quotient($denominator)->toString();
$remainder = $remainder->remainder($denominator);
$index++;
}
return $sign . $integralString . '.' . $digits;
}
/**
* This method is required for serializing the object and SHOULD NOT be accessed directly.
*

View File

@@ -23,6 +23,6 @@ final class IntegerOverflowException extends MathException
{
$message = '%s is out of range %d to %d and cannot be represented as an integer.';
return new self(sprintf($message, (string) $value, PHP_INT_MIN, PHP_INT_MAX));
return new self(sprintf($message, $value->toString(), PHP_INT_MIN, PHP_INT_MAX));
}
}

View File

@@ -31,19 +31,30 @@ final class NumberFormatException extends MathException
* @pure
*/
public static function charNotInAlphabet(string $char): self
{
return new self(sprintf(
'Character %s is not valid in the given alphabet.',
self::charToString($char),
));
}
/**
* @pure
*/
private static function charToString(string $char): string
{
$ord = ord($char);
if ($ord < 32 || $ord > 126) {
$char = strtoupper(dechex($ord));
if ($ord < 10) {
if ($ord < 16) {
$char = '0' . $char;
}
} else {
$char = '"' . $char . '"';
return '0x' . $char;
}
return new self(sprintf('Char %s is not a valid character in the given alphabet.', $char));
return '"' . $char . '"';
}
}

View File

@@ -217,7 +217,7 @@ abstract readonly class Calculator
/**
* Raises a number into power with modulo.
*
* @param string $base The base number; must be positive or zero.
* @param string $base The base number.
* @param string $exp The exponent; must be positive or zero.
* @param string $mod The modulus; must be strictly positive.
*
@@ -248,6 +248,25 @@ abstract readonly class Calculator
return $this->gcd($b, $this->divR($a, $b));
}
/**
* Returns the least common multiple of the two numbers.
*
* This method can be overridden by the concrete implementation if the underlying library
* has built-in support for LCM calculations.
*
* @return string The LCM, always positive, or zero if at least one argument is zero.
*
* @pure
*/
public function lcm(string $a, string $b): string
{
if ($a === '0' || $b === '0') {
return '0';
}
return $this->divQ($this->abs($this->mul($a, $b)), $this->gcd($a, $b));
}
/**
* Returns the square root of the given number, rounded down.
*
@@ -394,7 +413,7 @@ abstract readonly class Calculator
* @param string $b The divisor, must not be zero.
* @param RoundingMode $roundingMode The rounding mode.
*
* @throws RoundingNecessaryException If RoundingMode::UNNECESSARY is provided but rounding is necessary.
* @throws RoundingNecessaryException If RoundingMode::Unnecessary is provided but rounding is necessary.
*
* @pure
*/
@@ -415,52 +434,52 @@ abstract readonly class Calculator
$increment = false;
switch ($roundingMode) {
case RoundingMode::UNNECESSARY:
case RoundingMode::Unnecessary:
if ($hasDiscardedFraction) {
throw RoundingNecessaryException::roundingNecessary();
}
break;
case RoundingMode::UP:
case RoundingMode::Up:
$increment = $hasDiscardedFraction;
break;
case RoundingMode::DOWN:
case RoundingMode::Down:
break;
case RoundingMode::CEILING:
case RoundingMode::Ceiling:
$increment = $hasDiscardedFraction && $isPositiveOrZero;
break;
case RoundingMode::FLOOR:
case RoundingMode::Floor:
$increment = $hasDiscardedFraction && ! $isPositiveOrZero;
break;
case RoundingMode::HALF_UP:
case RoundingMode::HalfUp:
$increment = $discardedFractionSign() >= 0;
break;
case RoundingMode::HALF_DOWN:
case RoundingMode::HalfDown:
$increment = $discardedFractionSign() > 0;
break;
case RoundingMode::HALF_CEILING:
case RoundingMode::HalfCeiling:
$increment = $isPositiveOrZero ? $discardedFractionSign() >= 0 : $discardedFractionSign() > 0;
break;
case RoundingMode::HALF_FLOOR:
case RoundingMode::HalfFloor:
$increment = $isPositiveOrZero ? $discardedFractionSign() > 0 : $discardedFractionSign() >= 0;
break;
case RoundingMode::HALF_EVEN:
case RoundingMode::HalfEven:
$lastDigit = (int) $quotient[-1];
$lastDigitIsEven = ($lastDigit % 2 === 0);
$increment = $lastDigitIsEven ? $discardedFractionSign() > 0 : $discardedFractionSign() >= 0;

View File

@@ -71,6 +71,9 @@ final readonly class BcMathCalculator extends Calculator
#[Override]
public function modPow(string $base, string $exp, string $mod): string
{
// normalize to Euclidean representative so modPow() stays consistent with mod()
$base = $this->mod($base, $mod);
return bcpowmod($base, $exp, $mod, 0);
}

View File

@@ -16,6 +16,7 @@ use function gmp_div_r;
use function gmp_gcd;
use function gmp_init;
use function gmp_invert;
use function gmp_lcm;
use function gmp_mul;
use function gmp_or;
use function gmp_pow;
@@ -107,6 +108,12 @@ final readonly class GmpCalculator extends Calculator
return gmp_strval(gmp_gcd($a, $b));
}
#[Override]
public function lcm(string $a, string $b): string
{
return gmp_strval(gmp_lcm($a, $b));
}
#[Override]
public function fromBase(string $number, int $base): string
{

View File

@@ -230,10 +230,8 @@ final readonly class NativeCalculator extends Calculator
#[Override]
public function modPow(string $base, string $exp, string $mod): string
{
// special case: the algorithm below fails with 0 power 0 mod 1 (returns 1 instead of 0)
if ($base === '0' && $exp === '0' && $mod === '1') {
return '0';
}
// normalize to Euclidean representative so modPow() stays consistent with mod()
$base = $this->mod($base, $mod);
// special case: the algorithm below fails with power 0 mod 1 (returns 1 instead of 0)
if ($exp === '0' && $mod === '1') {

View File

@@ -5,13 +5,8 @@ declare(strict_types=1);
namespace Brick\Math;
/**
* Specifies a rounding behavior for numerical operations capable of discarding precision.
*
* Each rounding mode indicates how the least significant returned digit of a rounded result
* is to be calculated. If fewer digits are returned than the digits needed to represent the
* exact numerical result, the discarded digits will be referred to as the discarded fraction
* regardless the digits' contribution to the value of the number. In other words, considered
* as a numerical value, the discarded fraction could have an absolute value greater than one.
* Specifies rounding behavior by defining how discarded digits affect the returned result when an exact value cannot
* be represented at the requested scale.
*/
enum RoundingMode
{
@@ -21,7 +16,7 @@ enum RoundingMode
* If this rounding mode is specified on an operation that yields a result that
* cannot be represented at the requested scale, a RoundingNecessaryException is thrown.
*/
case UNNECESSARY;
case Unnecessary;
/**
* Rounds away from zero.
@@ -29,7 +24,7 @@ enum RoundingMode
* Always increments the digit prior to a nonzero discarded fraction.
* Note that this rounding mode never decreases the magnitude of the calculated value.
*/
case UP;
case Up;
/**
* Rounds towards zero.
@@ -37,62 +32,112 @@ enum RoundingMode
* Never increments the digit prior to a discarded fraction (i.e., truncates).
* Note that this rounding mode never increases the magnitude of the calculated value.
*/
case DOWN;
case Down;
/**
* Rounds towards positive infinity.
*
* If the result is positive, behaves as for UP; if negative, behaves as for DOWN.
* If the result is positive, behaves as for Up; if negative, behaves as for Down.
* Note that this rounding mode never decreases the calculated value.
*/
case CEILING;
case Ceiling;
/**
* Rounds towards negative infinity.
*
* If the result is positive, behave as for DOWN; if negative, behave as for UP.
* If the result is positive, behaves as for Down; if negative, behaves as for Up.
* Note that this rounding mode never increases the calculated value.
*/
case FLOOR;
case Floor;
/**
* Rounds towards "nearest neighbor" unless both neighbors are equidistant, in which case round up.
*
* Behaves as for UP if the discarded fraction is >= 0.5; otherwise, behaves as for DOWN.
* Behaves as for Up if the discarded fraction is >= 0.5; otherwise, behaves as for Down.
* Note that this is the rounding mode commonly taught at school.
*/
case HALF_UP;
case HalfUp;
/**
* Rounds towards "nearest neighbor" unless both neighbors are equidistant, in which case round down.
*
* Behaves as for UP if the discarded fraction is > 0.5; otherwise, behaves as for DOWN.
* Behaves as for Up if the discarded fraction is > 0.5; otherwise, behaves as for Down.
*/
case HALF_DOWN;
case HalfDown;
/**
* Rounds towards "nearest neighbor" unless both neighbors are equidistant, in which case round towards positive infinity.
*
* If the result is positive, behaves as for HALF_UP; if negative, behaves as for HALF_DOWN.
* If the result is positive, behaves as for HalfUp; if negative, behaves as for HalfDown.
*/
case HALF_CEILING;
case HalfCeiling;
/**
* Rounds towards "nearest neighbor" unless both neighbors are equidistant, in which case round towards negative infinity.
*
* If the result is positive, behaves as for HALF_DOWN; if negative, behaves as for HALF_UP.
* If the result is positive, behaves as for HalfDown; if negative, behaves as for HalfUp.
*/
case HALF_FLOOR;
case HalfFloor;
/**
* Rounds towards the "nearest neighbor" unless both neighbors are equidistant, in which case rounds towards the even neighbor.
*
* Behaves as for HALF_UP if the digit to the left of the discarded fraction is odd;
* behaves as for HALF_DOWN if it's even.
* Behaves as for HalfUp if the digit to the left of the discarded fraction is odd;
* behaves as for HalfDown if it's even.
*
* Note that this is the rounding mode that statistically minimizes
* cumulative error when applied repeatedly over a sequence of calculations.
* It is sometimes known as "Banker's rounding", and is chiefly used in the USA.
*/
case HALF_EVEN;
case HalfEven;
/**
* @deprecated Use RoundingMode::Unnecessary instead.
*/
public const UNNECESSARY = self::Unnecessary;
/**
* @deprecated Use RoundingMode::Up instead.
*/
public const UP = self::Up;
/**
* @deprecated Use RoundingMode::Down instead.
*/
public const DOWN = self::Down;
/**
* @deprecated Use RoundingMode::Ceiling instead.
*/
public const CEILING = self::Ceiling;
/**
* @deprecated Use RoundingMode::Floor instead.
*/
public const FLOOR = self::Floor;
/**
* @deprecated Use RoundingMode::HalfUp instead.
*/
public const HALF_UP = self::HalfUp;
/**
* @deprecated Use RoundingMode::HalfDown instead.
*/
public const HALF_DOWN = self::HalfDown;
/**
* @deprecated Use RoundingMode::HalfCeiling instead.
*/
public const HALF_CEILING = self::HalfCeiling;
/**
* @deprecated Use RoundingMode::HalfFloor instead.
*/
public const HALF_FLOOR = self::HalfFloor;
/**
* @deprecated Use RoundingMode::HalfEven instead.
*/
public const HALF_EVEN = self::HalfEven;
}

View File

@@ -1,10 +0,0 @@
{
"require": {
"brick/coding-standard": "v2"
},
"config": {
"allow-plugins": {
"dealerdirect/phpcodesniffer-composer-installer": false
}
}
}

View File

@@ -1,33 +0,0 @@
<?php
declare(strict_types=1);
use PhpCsFixer\Fixer\ClassNotation\OrderedTypesFixer;
use PhpCsFixer\Fixer\Phpdoc\PhpdocTypesOrderFixer;
use SlevomatCodingStandard\Sniffs\Whitespaces\DuplicateSpacesSniff;
use Symplify\EasyCodingStandard\Config\ECSConfig;
return static function (ECSConfig $ecsConfig): void {
$ecsConfig->import(__DIR__ . '/vendor/brick/coding-standard/ecs.php');
$libRootPath = realpath(__DIR__ . '/../..');
$ecsConfig->paths(
[
$libRootPath . '/src',
$libRootPath . '/tests',
$libRootPath . '/phpunit.php',
$libRootPath . '/random-tests.php',
__FILE__,
],
);
$ecsConfig->skip([
// Allows alignment in test providers
DuplicateSpacesSniff::class => [$libRootPath . '/tests'],
// We want to keep BigNumber|int|float|string order
OrderedTypesFixer::class => null,
PhpdocTypesOrderFixer::class => null,
]);
};

View File

@@ -450,6 +450,7 @@ return array(
'League\\HTMLToMarkdown\\HtmlConverterInterface' => $vendorDir . '/league/html-to-markdown/src/HtmlConverterInterface.php',
'League\\HTMLToMarkdown\\PreConverterInterface' => $vendorDir . '/league/html-to-markdown/src/PreConverterInterface.php',
'League\\Uri\\BaseUri' => $vendorDir . '/league/uri/BaseUri.php',
'League\\Uri\\Builder' => $vendorDir . '/league/uri/Builder.php',
'League\\Uri\\Contracts\\AuthorityInterface' => $vendorDir . '/league/uri-interfaces/Contracts/AuthorityInterface.php',
'League\\Uri\\Contracts\\Conditionable' => $vendorDir . '/league/uri-interfaces/Contracts/Conditionable.php',
'League\\Uri\\Contracts\\DataPathInterface' => $vendorDir . '/league/uri-interfaces/Contracts/DataPathInterface.php',
@@ -462,6 +463,7 @@ return array(
'League\\Uri\\Contracts\\PortInterface' => $vendorDir . '/league/uri-interfaces/Contracts/PortInterface.php',
'League\\Uri\\Contracts\\QueryInterface' => $vendorDir . '/league/uri-interfaces/Contracts/QueryInterface.php',
'League\\Uri\\Contracts\\SegmentedPathInterface' => $vendorDir . '/league/uri-interfaces/Contracts/SegmentedPathInterface.php',
'League\\Uri\\Contracts\\Transformable' => $vendorDir . '/league/uri-interfaces/Contracts/Transformable.php',
'League\\Uri\\Contracts\\UriAccess' => $vendorDir . '/league/uri-interfaces/Contracts/UriAccess.php',
'League\\Uri\\Contracts\\UriComponentInterface' => $vendorDir . '/league/uri-interfaces/Contracts/UriComponentInterface.php',
'League\\Uri\\Contracts\\UriException' => $vendorDir . '/league/uri-interfaces/Contracts/UriException.php',
@@ -489,8 +491,11 @@ return array(
'League\\Uri\\Idna\\Option' => $vendorDir . '/league/uri-interfaces/Idna/Option.php',
'League\\Uri\\Idna\\Result' => $vendorDir . '/league/uri-interfaces/Idna/Result.php',
'League\\Uri\\KeyValuePair\\Converter' => $vendorDir . '/league/uri-interfaces/KeyValuePair/Converter.php',
'League\\Uri\\QueryComposeMode' => $vendorDir . '/league/uri-interfaces/QueryComposeMode.php',
'League\\Uri\\QueryExtractMode' => $vendorDir . '/league/uri-interfaces/QueryExtractMode.php',
'League\\Uri\\QueryString' => $vendorDir . '/league/uri-interfaces/QueryString.php',
'League\\Uri\\SchemeType' => $vendorDir . '/league/uri/SchemeType.php',
'League\\Uri\\StringCoercionMode' => $vendorDir . '/league/uri-interfaces/StringCoercionMode.php',
'League\\Uri\\Uri' => $vendorDir . '/league/uri/Uri.php',
'League\\Uri\\UriComparisonMode' => $vendorDir . '/league/uri-interfaces/UriComparisonMode.php',
'League\\Uri\\UriInfo' => $vendorDir . '/league/uri/UriInfo.php',
@@ -1913,6 +1918,7 @@ return array(
'Zotlabs\\Lib\\Crypto' => $baseDir . '/Zotlabs/Lib/Crypto.php',
'Zotlabs\\Lib\\DB_Upgrade' => $baseDir . '/Zotlabs/Lib/DB_Upgrade.php',
'Zotlabs\\Lib\\DReport' => $baseDir . '/Zotlabs/Lib/DReport.php',
'Zotlabs\\Lib\\DbStats' => $baseDir . '/Zotlabs/Lib/DbStats.php',
'Zotlabs\\Lib\\Enotify' => $baseDir . '/Zotlabs/Lib/Enotify.php',
'Zotlabs\\Lib\\ExtendedZip' => $baseDir . '/Zotlabs/Lib/ExtendedZip.php',
'Zotlabs\\Lib\\Hashpath' => $baseDir . '/Zotlabs/Lib/Hashpath.php',
@@ -1930,12 +1936,15 @@ return array(
'Zotlabs\\Lib\\MarkdownSoap' => $baseDir . '/Zotlabs/Lib/MarkdownSoap.php',
'Zotlabs\\Lib\\MessageFilter' => $baseDir . '/Zotlabs/Lib/MessageFilter.php',
'Zotlabs\\Lib\\Multibase' => $baseDir . '/Zotlabs/Lib/Multibase.php',
'Zotlabs\\Lib\\MySQLDbStats' => $baseDir . '/Zotlabs/Lib/MySQLDbStats.php',
'Zotlabs\\Lib\\ObjCache' => $baseDir . '/Zotlabs/Lib/ObjCache.php',
'Zotlabs\\Lib\\PConfig' => $baseDir . '/Zotlabs/Lib/PConfig.php',
'Zotlabs\\Lib\\Permcat' => $baseDir . '/Zotlabs/Lib/Permcat.php',
'Zotlabs\\Lib\\PermissionDescription' => $baseDir . '/Zotlabs/Lib/PermissionDescription.php',
'Zotlabs\\Lib\\PostgresDbStats' => $baseDir . '/Zotlabs/Lib/PostgresDbStats.php',
'Zotlabs\\Lib\\Queue' => $baseDir . '/Zotlabs/Lib/Queue.php',
'Zotlabs\\Lib\\QueueWorker' => $baseDir . '/Zotlabs/Lib/QueueWorker.php',
'Zotlabs\\Lib\\QueueWorkerStats' => $baseDir . '/Zotlabs/Lib/QueueWorkerStats.php',
'Zotlabs\\Lib\\SConfig' => $baseDir . '/Zotlabs/Lib/SConfig.php',
'Zotlabs\\Lib\\Share' => $baseDir . '/Zotlabs/Lib/Share.php',
'Zotlabs\\Lib\\SvgSanitizer' => $baseDir . '/Zotlabs/Lib/SvgSanitizer.php',
@@ -2076,6 +2085,7 @@ return array(
'Zotlabs\\Module\\Pconfig' => $baseDir . '/Zotlabs/Module/Pconfig.php',
'Zotlabs\\Module\\Pdledit' => $baseDir . '/Zotlabs/Module/Pdledit.php',
'Zotlabs\\Module\\Pdledit_gui' => $baseDir . '/Zotlabs/Module/Pdledit_gui.php',
'Zotlabs\\Module\\Perfstats' => $baseDir . '/Zotlabs/Module/Perfstats.php',
'Zotlabs\\Module\\Permcat' => $baseDir . '/Zotlabs/Module/Permcat.php',
'Zotlabs\\Module\\Permcats' => $baseDir . '/Zotlabs/Module/Permcats.php',
'Zotlabs\\Module\\Photo' => $baseDir . '/Zotlabs/Module/Photo.php',

View File

@@ -15,9 +15,9 @@ return array(
'3569eecfeed3bcf0bad3c998a494ecb8' => $vendorDir . '/sabre/xml/lib/Deserializer/functions.php',
'93aa591bc4ca510c520999e34229ee79' => $vendorDir . '/sabre/xml/lib/Serializer/functions.php',
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
'7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php',
'320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php',
'3253b43614197c132b67e5f343def5b7' => $vendorDir . '/paragonie/easy-ecc/autoload-shim.php',
'7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php',
'ebdb698ed4152ae445614b69b5e4bb6a' => $vendorDir . '/sabre/http/lib/functions.php',
'6e3fae29631ef280660b3cdad06f25a8' => $vendorDir . '/symfony/deprecation-contracts/function.php',
'2cffec82183ee1cea088009cef9a6fc3' => $vendorDir . '/ezyang/htmlpurifier/library/HTMLPurifier.composer.php',

View File

@@ -16,9 +16,9 @@ class ComposerStaticInit7b34d7e50a62201ec5d5e526a5b8b35d
'3569eecfeed3bcf0bad3c998a494ecb8' => __DIR__ . '/..' . '/sabre/xml/lib/Deserializer/functions.php',
'93aa591bc4ca510c520999e34229ee79' => __DIR__ . '/..' . '/sabre/xml/lib/Serializer/functions.php',
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
'7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php',
'320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php',
'3253b43614197c132b67e5f343def5b7' => __DIR__ . '/..' . '/paragonie/easy-ecc/autoload-shim.php',
'7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php',
'ebdb698ed4152ae445614b69b5e4bb6a' => __DIR__ . '/..' . '/sabre/http/lib/functions.php',
'6e3fae29631ef280660b3cdad06f25a8' => __DIR__ . '/..' . '/symfony/deprecation-contracts/function.php',
'2cffec82183ee1cea088009cef9a6fc3' => __DIR__ . '/..' . '/ezyang/htmlpurifier/library/HTMLPurifier.composer.php',
@@ -27,25 +27,25 @@ class ComposerStaticInit7b34d7e50a62201ec5d5e526a5b8b35d
);
public static $prefixLengthsPsr4 = array (
'v' =>
'v' =>
array (
'voku\\' => 5,
),
'p' =>
'p' =>
array (
'phpseclib\\' => 10,
'phpseclib3\\' => 11,
),
'c' =>
'c' =>
array (
'chillerlan\\Settings\\' => 20,
'chillerlan\\QRCode\\' => 18,
),
'Z' =>
'Z' =>
array (
'Zotlabs\\' => 8,
),
'S' =>
'S' =>
array (
'Symfony\\Polyfill\\Mbstring\\' => 26,
'Symfony\\Polyfill\\Ctype\\' => 23,
@@ -62,13 +62,13 @@ class ComposerStaticInit7b34d7e50a62201ec5d5e526a5b8b35d
'Sabre\\Event\\' => 12,
'Sabre\\' => 6,
),
'R' =>
'R' =>
array (
'Root23\\JsonCanonicalizer\\' => 25,
'Ramsey\\Uuid\\' => 12,
'Ramsey\\Collection\\' => 18,
),
'P' =>
'P' =>
array (
'Psr\\Log\\' => 8,
'Psr\\Http\\Message\\' => 17,
@@ -77,47 +77,47 @@ class ComposerStaticInit7b34d7e50a62201ec5d5e526a5b8b35d
'ParagonIE\\EasyECC\\' => 18,
'ParagonIE\\ConstantTime\\' => 23,
),
'O' =>
'O' =>
array (
'OTPHP\\' => 6,
),
'M' =>
'M' =>
array (
'Michelf\\' => 8,
'Mdanter\\Ecc\\' => 12,
),
'L' =>
'L' =>
array (
'League\\Uri\\' => 11,
'League\\HTMLToMarkdown\\' => 22,
'LanguageDetection\\' => 18,
),
'I' =>
'I' =>
array (
'ID3Parser\\' => 10,
),
'H' =>
'H' =>
array (
'Hubzilla\\' => 9,
'HttpSignature\\' => 14,
),
'G' =>
'G' =>
array (
'GuzzleHttp\\Psr7\\' => 16,
),
'F' =>
'F' =>
array (
'FG\\' => 3,
),
'D' =>
'D' =>
array (
'Defuse\\Crypto\\' => 14,
),
'C' =>
'C' =>
array (
'CommerceGuys\\Intl\\' => 18,
),
'B' =>
'B' =>
array (
'Brick\\Math\\' => 11,
'Bakame\\Http\\StructuredFields\\' => 29,
@@ -125,219 +125,219 @@ class ComposerStaticInit7b34d7e50a62201ec5d5e526a5b8b35d
);
public static $prefixDirsPsr4 = array (
'voku\\' =>
'voku\\' =>
array (
0 => __DIR__ . '/..' . '/voku/stop-words/src/voku',
1 => __DIR__ . '/..' . '/voku/portable-ascii/src/voku',
),
'phpseclib\\' =>
'phpseclib\\' =>
array (
0 => __DIR__ . '/..' . '/phpseclib/phpseclib2_compat/src',
),
'phpseclib3\\' =>
'phpseclib3\\' =>
array (
0 => __DIR__ . '/..' . '/phpseclib/phpseclib/phpseclib',
),
'chillerlan\\Settings\\' =>
'chillerlan\\Settings\\' =>
array (
0 => __DIR__ . '/..' . '/chillerlan/php-settings-container/src',
),
'chillerlan\\QRCode\\' =>
'chillerlan\\QRCode\\' =>
array (
0 => __DIR__ . '/..' . '/chillerlan/php-qrcode/src',
),
'Zotlabs\\' =>
'Zotlabs\\' =>
array (
0 => __DIR__ . '/../..' . '/Zotlabs',
),
'Symfony\\Polyfill\\Mbstring\\' =>
'Symfony\\Polyfill\\Mbstring\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring',
),
'Symfony\\Polyfill\\Ctype\\' =>
'Symfony\\Polyfill\\Ctype\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/polyfill-ctype',
),
'Symfony\\Component\\Filesystem\\' =>
'Symfony\\Component\\Filesystem\\' =>
array (
0 => __DIR__ . '/..' . '/symfony/filesystem',
),
'StephenHill\\' =>
'StephenHill\\' =>
array (
0 => __DIR__ . '/..' . '/stephenhill/base58/src',
),
'SourceSpan\\' =>
'SourceSpan\\' =>
array (
0 => __DIR__ . '/..' . '/scssphp/source-span/src',
),
'Smarty\\' =>
'Smarty\\' =>
array (
0 => __DIR__ . '/..' . '/smarty/smarty/src',
),
'SimplePie\\' =>
'SimplePie\\' =>
array (
0 => __DIR__ . '/..' . '/simplepie/simplepie/src',
),
'ScssPhp\\ScssPhp\\' =>
'ScssPhp\\ScssPhp\\' =>
array (
0 => __DIR__ . '/..' . '/scssphp/scssphp/src',
),
'Sabre\\Xml\\' =>
'Sabre\\Xml\\' =>
array (
0 => __DIR__ . '/..' . '/sabre/xml/lib',
),
'Sabre\\VObject\\' =>
'Sabre\\VObject\\' =>
array (
0 => __DIR__ . '/..' . '/sabre/vobject/lib',
),
'Sabre\\Uri\\' =>
'Sabre\\Uri\\' =>
array (
0 => __DIR__ . '/..' . '/sabre/uri/lib',
),
'Sabre\\HTTP\\' =>
'Sabre\\HTTP\\' =>
array (
0 => __DIR__ . '/..' . '/sabre/http/lib',
),
'Sabre\\Event\\' =>
'Sabre\\Event\\' =>
array (
0 => __DIR__ . '/..' . '/sabre/event/lib',
),
'Sabre\\' =>
'Sabre\\' =>
array (
0 => __DIR__ . '/..' . '/sabre/dav/lib',
),
'Root23\\JsonCanonicalizer\\' =>
'Root23\\JsonCanonicalizer\\' =>
array (
0 => __DIR__ . '/..' . '/root23/php-json-canonicalization/src',
),
'Ramsey\\Uuid\\' =>
'Ramsey\\Uuid\\' =>
array (
0 => __DIR__ . '/..' . '/ramsey/uuid/src',
),
'Ramsey\\Collection\\' =>
'Ramsey\\Collection\\' =>
array (
0 => __DIR__ . '/..' . '/ramsey/collection/src',
),
'Psr\\Log\\' =>
'Psr\\Log\\' =>
array (
0 => __DIR__ . '/..' . '/psr/log/src',
),
'Psr\\Http\\Message\\' =>
'Psr\\Http\\Message\\' =>
array (
0 => __DIR__ . '/..' . '/psr/http-factory/src',
1 => __DIR__ . '/..' . '/psr/http-message/src',
),
'Psr\\Clock\\' =>
'Psr\\Clock\\' =>
array (
0 => __DIR__ . '/..' . '/psr/clock/src',
),
'ParagonIE\\Sodium\\' =>
'ParagonIE\\Sodium\\' =>
array (
0 => __DIR__ . '/..' . '/paragonie/sodium_compat/namespaced',
),
'ParagonIE\\EasyECC\\' =>
'ParagonIE\\EasyECC\\' =>
array (
0 => __DIR__ . '/..' . '/paragonie/easy-ecc/src',
),
'ParagonIE\\ConstantTime\\' =>
'ParagonIE\\ConstantTime\\' =>
array (
0 => __DIR__ . '/..' . '/paragonie/constant_time_encoding/src',
),
'OTPHP\\' =>
'OTPHP\\' =>
array (
0 => __DIR__ . '/..' . '/spomky-labs/otphp/src',
),
'Michelf\\' =>
'Michelf\\' =>
array (
0 => __DIR__ . '/..' . '/michelf/php-markdown/Michelf',
),
'Mdanter\\Ecc\\' =>
'Mdanter\\Ecc\\' =>
array (
0 => __DIR__ . '/..' . '/paragonie/ecc/src',
),
'League\\Uri\\' =>
'League\\Uri\\' =>
array (
0 => __DIR__ . '/..' . '/league/uri',
1 => __DIR__ . '/..' . '/league/uri-interfaces',
),
'League\\HTMLToMarkdown\\' =>
'League\\HTMLToMarkdown\\' =>
array (
0 => __DIR__ . '/..' . '/league/html-to-markdown/src',
),
'LanguageDetection\\' =>
'LanguageDetection\\' =>
array (
0 => __DIR__ . '/..' . '/patrickschur/language-detection/src/LanguageDetection',
),
'ID3Parser\\' =>
'ID3Parser\\' =>
array (
0 => __DIR__ . '/..' . '/lukasreschke/id3parser/src',
),
'Hubzilla\\' =>
'Hubzilla\\' =>
array (
0 => __DIR__ . '/../..' . '/include',
),
'HttpSignature\\' =>
'HttpSignature\\' =>
array (
0 => __DIR__ . '/..' . '/macgirvin/http-message-signer/src',
),
'GuzzleHttp\\Psr7\\' =>
'GuzzleHttp\\Psr7\\' =>
array (
0 => __DIR__ . '/..' . '/guzzlehttp/psr7/src',
),
'FG\\' =>
'FG\\' =>
array (
0 => __DIR__ . '/..' . '/genkgo/php-asn1/lib',
),
'Defuse\\Crypto\\' =>
'Defuse\\Crypto\\' =>
array (
0 => __DIR__ . '/..' . '/defuse/php-encryption/src',
),
'CommerceGuys\\Intl\\' =>
'CommerceGuys\\Intl\\' =>
array (
0 => __DIR__ . '/..' . '/commerceguys/intl/src',
),
'Brick\\Math\\' =>
'Brick\\Math\\' =>
array (
0 => __DIR__ . '/..' . '/brick/math/src',
),
'Bakame\\Http\\StructuredFields\\' =>
'Bakame\\Http\\StructuredFields\\' =>
array (
0 => __DIR__ . '/..' . '/bakame/http-structured-fields/src',
),
);
public static $prefixesPsr0 = array (
'U' =>
'U' =>
array (
'URLify' =>
'URLify' =>
array (
0 => __DIR__ . '/..' . '/jbroadway/urlify',
),
),
'T' =>
'T' =>
array (
'Text' =>
'Text' =>
array (
0 => __DIR__ . '/..' . '/pear/text_languagedetect',
),
),
'S' =>
'S' =>
array (
'SimplePie' =>
'SimplePie' =>
array (
0 => __DIR__ . '/..' . '/simplepie/simplepie/library',
),
),
'O' =>
'O' =>
array (
'OAuth2' =>
'OAuth2' =>
array (
0 => __DIR__ . '/..' . '/bshaffer/oauth2-server-php/src',
),
),
'H' =>
'H' =>
array (
'HTMLPurifier' =>
'HTMLPurifier' =>
array (
0 => __DIR__ . '/..' . '/ezyang/htmlpurifier/library',
),
@@ -789,6 +789,7 @@ class ComposerStaticInit7b34d7e50a62201ec5d5e526a5b8b35d
'League\\HTMLToMarkdown\\HtmlConverterInterface' => __DIR__ . '/..' . '/league/html-to-markdown/src/HtmlConverterInterface.php',
'League\\HTMLToMarkdown\\PreConverterInterface' => __DIR__ . '/..' . '/league/html-to-markdown/src/PreConverterInterface.php',
'League\\Uri\\BaseUri' => __DIR__ . '/..' . '/league/uri/BaseUri.php',
'League\\Uri\\Builder' => __DIR__ . '/..' . '/league/uri/Builder.php',
'League\\Uri\\Contracts\\AuthorityInterface' => __DIR__ . '/..' . '/league/uri-interfaces/Contracts/AuthorityInterface.php',
'League\\Uri\\Contracts\\Conditionable' => __DIR__ . '/..' . '/league/uri-interfaces/Contracts/Conditionable.php',
'League\\Uri\\Contracts\\DataPathInterface' => __DIR__ . '/..' . '/league/uri-interfaces/Contracts/DataPathInterface.php',
@@ -801,6 +802,7 @@ class ComposerStaticInit7b34d7e50a62201ec5d5e526a5b8b35d
'League\\Uri\\Contracts\\PortInterface' => __DIR__ . '/..' . '/league/uri-interfaces/Contracts/PortInterface.php',
'League\\Uri\\Contracts\\QueryInterface' => __DIR__ . '/..' . '/league/uri-interfaces/Contracts/QueryInterface.php',
'League\\Uri\\Contracts\\SegmentedPathInterface' => __DIR__ . '/..' . '/league/uri-interfaces/Contracts/SegmentedPathInterface.php',
'League\\Uri\\Contracts\\Transformable' => __DIR__ . '/..' . '/league/uri-interfaces/Contracts/Transformable.php',
'League\\Uri\\Contracts\\UriAccess' => __DIR__ . '/..' . '/league/uri-interfaces/Contracts/UriAccess.php',
'League\\Uri\\Contracts\\UriComponentInterface' => __DIR__ . '/..' . '/league/uri-interfaces/Contracts/UriComponentInterface.php',
'League\\Uri\\Contracts\\UriException' => __DIR__ . '/..' . '/league/uri-interfaces/Contracts/UriException.php',
@@ -828,8 +830,11 @@ class ComposerStaticInit7b34d7e50a62201ec5d5e526a5b8b35d
'League\\Uri\\Idna\\Option' => __DIR__ . '/..' . '/league/uri-interfaces/Idna/Option.php',
'League\\Uri\\Idna\\Result' => __DIR__ . '/..' . '/league/uri-interfaces/Idna/Result.php',
'League\\Uri\\KeyValuePair\\Converter' => __DIR__ . '/..' . '/league/uri-interfaces/KeyValuePair/Converter.php',
'League\\Uri\\QueryComposeMode' => __DIR__ . '/..' . '/league/uri-interfaces/QueryComposeMode.php',
'League\\Uri\\QueryExtractMode' => __DIR__ . '/..' . '/league/uri-interfaces/QueryExtractMode.php',
'League\\Uri\\QueryString' => __DIR__ . '/..' . '/league/uri-interfaces/QueryString.php',
'League\\Uri\\SchemeType' => __DIR__ . '/..' . '/league/uri/SchemeType.php',
'League\\Uri\\StringCoercionMode' => __DIR__ . '/..' . '/league/uri-interfaces/StringCoercionMode.php',
'League\\Uri\\Uri' => __DIR__ . '/..' . '/league/uri/Uri.php',
'League\\Uri\\UriComparisonMode' => __DIR__ . '/..' . '/league/uri-interfaces/UriComparisonMode.php',
'League\\Uri\\UriInfo' => __DIR__ . '/..' . '/league/uri/UriInfo.php',
@@ -2252,6 +2257,7 @@ class ComposerStaticInit7b34d7e50a62201ec5d5e526a5b8b35d
'Zotlabs\\Lib\\Crypto' => __DIR__ . '/../..' . '/Zotlabs/Lib/Crypto.php',
'Zotlabs\\Lib\\DB_Upgrade' => __DIR__ . '/../..' . '/Zotlabs/Lib/DB_Upgrade.php',
'Zotlabs\\Lib\\DReport' => __DIR__ . '/../..' . '/Zotlabs/Lib/DReport.php',
'Zotlabs\\Lib\\DbStats' => __DIR__ . '/../..' . '/Zotlabs/Lib/DbStats.php',
'Zotlabs\\Lib\\Enotify' => __DIR__ . '/../..' . '/Zotlabs/Lib/Enotify.php',
'Zotlabs\\Lib\\ExtendedZip' => __DIR__ . '/../..' . '/Zotlabs/Lib/ExtendedZip.php',
'Zotlabs\\Lib\\Hashpath' => __DIR__ . '/../..' . '/Zotlabs/Lib/Hashpath.php',
@@ -2269,12 +2275,15 @@ class ComposerStaticInit7b34d7e50a62201ec5d5e526a5b8b35d
'Zotlabs\\Lib\\MarkdownSoap' => __DIR__ . '/../..' . '/Zotlabs/Lib/MarkdownSoap.php',
'Zotlabs\\Lib\\MessageFilter' => __DIR__ . '/../..' . '/Zotlabs/Lib/MessageFilter.php',
'Zotlabs\\Lib\\Multibase' => __DIR__ . '/../..' . '/Zotlabs/Lib/Multibase.php',
'Zotlabs\\Lib\\MySQLDbStats' => __DIR__ . '/../..' . '/Zotlabs/Lib/MySQLDbStats.php',
'Zotlabs\\Lib\\ObjCache' => __DIR__ . '/../..' . '/Zotlabs/Lib/ObjCache.php',
'Zotlabs\\Lib\\PConfig' => __DIR__ . '/../..' . '/Zotlabs/Lib/PConfig.php',
'Zotlabs\\Lib\\Permcat' => __DIR__ . '/../..' . '/Zotlabs/Lib/Permcat.php',
'Zotlabs\\Lib\\PermissionDescription' => __DIR__ . '/../..' . '/Zotlabs/Lib/PermissionDescription.php',
'Zotlabs\\Lib\\PostgresDbStats' => __DIR__ . '/../..' . '/Zotlabs/Lib/PostgresDbStats.php',
'Zotlabs\\Lib\\Queue' => __DIR__ . '/../..' . '/Zotlabs/Lib/Queue.php',
'Zotlabs\\Lib\\QueueWorker' => __DIR__ . '/../..' . '/Zotlabs/Lib/QueueWorker.php',
'Zotlabs\\Lib\\QueueWorkerStats' => __DIR__ . '/../..' . '/Zotlabs/Lib/QueueWorkerStats.php',
'Zotlabs\\Lib\\SConfig' => __DIR__ . '/../..' . '/Zotlabs/Lib/SConfig.php',
'Zotlabs\\Lib\\Share' => __DIR__ . '/../..' . '/Zotlabs/Lib/Share.php',
'Zotlabs\\Lib\\SvgSanitizer' => __DIR__ . '/../..' . '/Zotlabs/Lib/SvgSanitizer.php',
@@ -2415,6 +2424,7 @@ class ComposerStaticInit7b34d7e50a62201ec5d5e526a5b8b35d
'Zotlabs\\Module\\Pconfig' => __DIR__ . '/../..' . '/Zotlabs/Module/Pconfig.php',
'Zotlabs\\Module\\Pdledit' => __DIR__ . '/../..' . '/Zotlabs/Module/Pdledit.php',
'Zotlabs\\Module\\Pdledit_gui' => __DIR__ . '/../..' . '/Zotlabs/Module/Pdledit_gui.php',
'Zotlabs\\Module\\Perfstats' => __DIR__ . '/../..' . '/Zotlabs/Module/Perfstats.php',
'Zotlabs\\Module\\Permcat' => __DIR__ . '/../..' . '/Zotlabs/Module/Permcat.php',
'Zotlabs\\Module\\Permcats' => __DIR__ . '/../..' . '/Zotlabs/Module/Permcats.php',
'Zotlabs\\Module\\Photo' => __DIR__ . '/../..' . '/Zotlabs/Module/Photo.php',

View File

@@ -155,17 +155,17 @@
},
{
"name": "brick/math",
"version": "0.14.1",
"version_normalized": "0.14.1.0",
"version": "0.14.8",
"version_normalized": "0.14.8.0",
"source": {
"type": "git",
"url": "https://github.com/brick/math.git",
"reference": "f05858549e5f9d7bb45875a75583240a38a281d0"
"reference": "63422359a44b7f06cae63c3b429b59e8efcc0629"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/brick/math/zipball/f05858549e5f9d7bb45875a75583240a38a281d0",
"reference": "f05858549e5f9d7bb45875a75583240a38a281d0",
"url": "https://api.github.com/repos/brick/math/zipball/63422359a44b7f06cae63c3b429b59e8efcc0629",
"reference": "63422359a44b7f06cae63c3b429b59e8efcc0629",
"shasum": ""
},
"require": {
@@ -176,7 +176,7 @@
"phpstan/phpstan": "2.1.22",
"phpunit/phpunit": "^11.5"
},
"time": "2025-11-24T14:40:29+00:00",
"time": "2026-02-10T14:33:43+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
@@ -206,7 +206,7 @@
],
"support": {
"issues": "https://github.com/brick/math/issues",
"source": "https://github.com/brick/math/tree/0.14.1"
"source": "https://github.com/brick/math/tree/0.14.8"
},
"funding": [
{
@@ -1046,21 +1046,21 @@
},
{
"name": "league/uri",
"version": "7.7.0",
"version_normalized": "7.7.0.0",
"version": "7.8.0",
"version_normalized": "7.8.0.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/uri.git",
"reference": "8d587cddee53490f9b82bf203d3a9aa7ea4f9807"
"reference": "4436c6ec8d458e4244448b069cc572d088230b76"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/uri/zipball/8d587cddee53490f9b82bf203d3a9aa7ea4f9807",
"reference": "8d587cddee53490f9b82bf203d3a9aa7ea4f9807",
"url": "https://api.github.com/repos/thephpleague/uri/zipball/4436c6ec8d458e4244448b069cc572d088230b76",
"reference": "4436c6ec8d458e4244448b069cc572d088230b76",
"shasum": ""
},
"require": {
"league/uri-interfaces": "^7.7",
"league/uri-interfaces": "^7.8",
"php": "^8.1",
"psr/http-factory": "^1"
},
@@ -1074,14 +1074,14 @@
"ext-gmp": "to improve IPV4 host parsing",
"ext-intl": "to handle IDN host with the best performance",
"ext-uri": "to use the PHP native URI class",
"jeremykendall/php-domain-parser": "to resolve Public Suffix and Top Level Domain",
"league/uri-components": "Needed to easily manipulate URI objects components",
"league/uri-polyfill": "Needed to backport the PHP URI extension for older versions of PHP",
"jeremykendall/php-domain-parser": "to further parse the URI host and resolve its Public Suffix and Top Level Domain",
"league/uri-components": "to provide additional tools to manipulate URI objects components",
"league/uri-polyfill": "to backport the PHP URI extension for older versions of PHP",
"php-64bit": "to improve IPV4 host parsing",
"rowbot/url": "to handle WHATWG URL",
"rowbot/url": "to handle URLs using the WHATWG URL Living Standard specification",
"symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present"
},
"time": "2025-12-07T16:02:06+00:00",
"time": "2026-01-14T17:24:56+00:00",
"type": "library",
"extra": {
"branch-alias": {
@@ -1135,7 +1135,7 @@
"docs": "https://uri.thephpleague.com",
"forum": "https://thephpleague.slack.com",
"issues": "https://github.com/thephpleague/uri-src/issues",
"source": "https://github.com/thephpleague/uri/tree/7.7.0"
"source": "https://github.com/thephpleague/uri/tree/7.8.0"
},
"funding": [
{
@@ -1147,17 +1147,17 @@
},
{
"name": "league/uri-interfaces",
"version": "7.7.0",
"version_normalized": "7.7.0.0",
"version": "7.8.0",
"version_normalized": "7.8.0.0",
"source": {
"type": "git",
"url": "https://github.com/thephpleague/uri-interfaces.git",
"reference": "62ccc1a0435e1c54e10ee6022df28d6c04c2946c"
"reference": "c5c5cd056110fc8afaba29fa6b72a43ced42acd4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/62ccc1a0435e1c54e10ee6022df28d6c04c2946c",
"reference": "62ccc1a0435e1c54e10ee6022df28d6c04c2946c",
"url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/c5c5cd056110fc8afaba29fa6b72a43ced42acd4",
"reference": "c5c5cd056110fc8afaba29fa6b72a43ced42acd4",
"shasum": ""
},
"require": {
@@ -1170,10 +1170,10 @@
"ext-gmp": "to improve IPV4 host parsing",
"ext-intl": "to handle IDN host with the best performance",
"php-64bit": "to improve IPV4 host parsing",
"rowbot/url": "to handle WHATWG URL",
"rowbot/url": "to handle URLs using the WHATWG URL Living Standard specification",
"symfony/polyfill-intl-idn": "to handle IDN host via the Symfony polyfill if ext-intl is not present"
},
"time": "2025-12-07T16:03:21+00:00",
"time": "2026-01-15T06:54:53+00:00",
"type": "library",
"extra": {
"branch-alias": {
@@ -1222,7 +1222,7 @@
"docs": "https://uri.thephpleague.com",
"forum": "https://thephpleague.slack.com",
"issues": "https://github.com/thephpleague/uri-src/issues",
"source": "https://github.com/thephpleague/uri-interfaces/tree/7.7.0"
"source": "https://github.com/thephpleague/uri-interfaces/tree/7.8.0"
},
"funding": [
{
@@ -1273,23 +1273,22 @@
},
{
"name": "macgirvin/http-message-signer",
"version": "v0.2.12",
"version_normalized": "0.2.12.0",
"version": "v0.2.13",
"version_normalized": "0.2.13.0",
"source": {
"type": "git",
"url": "https://github.com/macgirvin/HTTP-Message-Signer.git",
"reference": "6e5b25a5536576e5046f5b0a7db620b5eb451453"
"reference": "b231228c630bf88ac3415b2ba28c057654c9fbea"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/macgirvin/HTTP-Message-Signer/zipball/6e5b25a5536576e5046f5b0a7db620b5eb451453",
"reference": "6e5b25a5536576e5046f5b0a7db620b5eb451453",
"url": "https://api.github.com/repos/macgirvin/HTTP-Message-Signer/zipball/b231228c630bf88ac3415b2ba28c057654c9fbea",
"reference": "b231228c630bf88ac3415b2ba28c057654c9fbea",
"shasum": ""
},
"require": {
"bakame/http-structured-fields": "^2.0",
"ext-openssl": "*",
"guzzlehttp/psr7": "^2.0",
"paragonie/easy-ecc": "^1.3",
"php": "^8.1",
"phpseclib/phpseclib": "~3.0",
@@ -1297,9 +1296,10 @@
"psr/http-message": "^2.0"
},
"require-dev": {
"guzzlehttp/psr7": "^2.8",
"phpunit/phpunit": "^10.0"
},
"time": "2025-12-02T18:48:05+00:00",
"time": "2026-02-01T19:54:05+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
@@ -1314,7 +1314,7 @@
"description": "RFC 9421 HTTP Message Signer and Verifier for PSR-7 requests",
"support": {
"issues": "https://github.com/macgirvin/HTTP-Message-Signer/issues",
"source": "https://github.com/macgirvin/HTTP-Message-Signer/tree/v0.2.12"
"source": "https://github.com/macgirvin/HTTP-Message-Signer/tree/v0.2.13"
},
"install-path": "../macgirvin/http-message-signer"
},
@@ -1855,21 +1855,22 @@
"issues": "http://pear.php.net/bugs/search.php?cmd=display&package_name[]=Text_LanguageDetect",
"source": "https://github.com/pear/Text_LanguageDetect"
},
"abandoned": true,
"install-path": "../pear/text_languagedetect"
},
{
"name": "phpseclib/phpseclib",
"version": "3.0.48",
"version_normalized": "3.0.48.0",
"version": "3.0.49",
"version_normalized": "3.0.49.0",
"source": {
"type": "git",
"url": "https://github.com/phpseclib/phpseclib.git",
"reference": "64065a5679c50acb886e82c07aa139b0f757bb89"
"reference": "6233a1e12584754e6b5daa69fe1289b47775c1b9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/64065a5679c50acb886e82c07aa139b0f757bb89",
"reference": "64065a5679c50acb886e82c07aa139b0f757bb89",
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/6233a1e12584754e6b5daa69fe1289b47775c1b9",
"reference": "6233a1e12584754e6b5daa69fe1289b47775c1b9",
"shasum": ""
},
"require": {
@@ -1887,7 +1888,7 @@
"ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.",
"ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations."
},
"time": "2025-12-15T11:51:42+00:00",
"time": "2026-01-27T09:17:28+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
@@ -1952,7 +1953,7 @@
],
"support": {
"issues": "https://github.com/phpseclib/phpseclib/issues",
"source": "https://github.com/phpseclib/phpseclib/tree/3.0.48"
"source": "https://github.com/phpseclib/phpseclib/tree/3.0.49"
},
"funding": [
{
@@ -3176,17 +3177,17 @@
},
{
"name": "smarty/smarty",
"version": "v5.7.0",
"version_normalized": "5.7.0.0",
"version": "v5.8.0",
"version_normalized": "5.8.0.0",
"source": {
"type": "git",
"url": "https://github.com/smarty-php/smarty.git",
"reference": "73da7e90f302175a570662fcb0ba41f57b7a92ab"
"reference": "78d259d3b971c59a0cd719c270cc5cbb740c36a7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/smarty-php/smarty/zipball/73da7e90f302175a570662fcb0ba41f57b7a92ab",
"reference": "73da7e90f302175a570662fcb0ba41f57b7a92ab",
"url": "https://api.github.com/repos/smarty-php/smarty/zipball/78d259d3b971c59a0cd719c270cc5cbb740c36a7",
"reference": "78d259d3b971c59a0cd719c270cc5cbb740c36a7",
"shasum": ""
},
"require": {
@@ -3197,7 +3198,7 @@
"phpunit/phpunit": "^8.5 || ^7.5",
"smarty/smarty-lexer": "^4.0.2"
},
"time": "2025-11-19T21:36:38+00:00",
"time": "2026-02-15T14:27:15+00:00",
"type": "library",
"extra": {
"branch-alias": {
@@ -3243,7 +3244,7 @@
"support": {
"forum": "https://github.com/smarty-php/smarty/discussions",
"issues": "https://github.com/smarty-php/smarty/issues",
"source": "https://github.com/smarty-php/smarty/tree/v5.7.0"
"source": "https://github.com/smarty-php/smarty/tree/v5.8.0"
},
"funding": [
{
@@ -3255,17 +3256,17 @@
},
{
"name": "spomky-labs/otphp",
"version": "11.4.1",
"version_normalized": "11.4.1.0",
"version": "11.4.2",
"version_normalized": "11.4.2.0",
"source": {
"type": "git",
"url": "https://github.com/Spomky-Labs/otphp.git",
"reference": "126c99b6cbbc18992cf3fba3b87931ba4e312482"
"reference": "2a1b503fd1c1a5c751ab3c5cd37f2d2d26ab74ad"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Spomky-Labs/otphp/zipball/126c99b6cbbc18992cf3fba3b87931ba4e312482",
"reference": "126c99b6cbbc18992cf3fba3b87931ba4e312482",
"url": "https://api.github.com/repos/Spomky-Labs/otphp/zipball/2a1b503fd1c1a5c751ab3c5cd37f2d2d26ab74ad",
"reference": "2a1b503fd1c1a5c751ab3c5cd37f2d2d26ab74ad",
"shasum": ""
},
"require": {
@@ -3277,7 +3278,7 @@
"require-dev": {
"symfony/error-handler": "^6.4|^7.0|^8.0"
},
"time": "2026-01-05T13:20:36+00:00",
"time": "2026-01-23T10:53:01+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
@@ -3312,7 +3313,7 @@
],
"support": {
"issues": "https://github.com/Spomky-Labs/otphp/issues",
"source": "https://github.com/Spomky-Labs/otphp/tree/11.4.1"
"source": "https://github.com/Spomky-Labs/otphp/tree/11.4.2"
},
"funding": [
{
@@ -3454,17 +3455,17 @@
},
{
"name": "symfony/filesystem",
"version": "v7.4.0",
"version_normalized": "7.4.0.0",
"version": "v7.4.6",
"version_normalized": "7.4.6.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/filesystem.git",
"reference": "d551b38811096d0be9c4691d406991b47c0c630a"
"reference": "3ebc794fa5315e59fd122561623c2e2e4280538e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/d551b38811096d0be9c4691d406991b47c0c630a",
"reference": "d551b38811096d0be9c4691d406991b47c0c630a",
"url": "https://api.github.com/repos/symfony/filesystem/zipball/3ebc794fa5315e59fd122561623c2e2e4280538e",
"reference": "3ebc794fa5315e59fd122561623c2e2e4280538e",
"shasum": ""
},
"require": {
@@ -3475,7 +3476,7 @@
"require-dev": {
"symfony/process": "^6.4|^7.0|^8.0"
},
"time": "2025-11-27T13:27:24+00:00",
"time": "2026-02-25T16:50:00+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
@@ -3503,7 +3504,7 @@
"description": "Provides basic utilities for the filesystem",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/filesystem/tree/v7.4.0"
"source": "https://github.com/symfony/filesystem/tree/v7.4.6"
},
"funding": [
{

View File

@@ -1,9 +1,9 @@
<?php return array(
'root' => array(
'name' => 'zotlabs/hubzilla',
'pretty_version' => 'dev-10.6RC',
'version' => 'dev-10.6RC',
'reference' => '38f040f9b528378aa796fc0d72e971cb30bc9bd4',
'pretty_version' => 'dev-11.2RC',
'version' => 'dev-11.2RC',
'reference' => '955ee217e30e12dec86bdcd936ce10abc3ed366a',
'type' => 'application',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@@ -29,9 +29,9 @@
'dev_requirement' => false,
),
'brick/math' => array(
'pretty_version' => '0.14.1',
'version' => '0.14.1.0',
'reference' => 'f05858549e5f9d7bb45875a75583240a38a281d0',
'pretty_version' => '0.14.8',
'version' => '0.14.8.0',
'reference' => '63422359a44b7f06cae63c3b429b59e8efcc0629',
'type' => 'library',
'install_path' => __DIR__ . '/../brick/math',
'aliases' => array(),
@@ -137,18 +137,18 @@
'dev_requirement' => false,
),
'league/uri' => array(
'pretty_version' => '7.7.0',
'version' => '7.7.0.0',
'reference' => '8d587cddee53490f9b82bf203d3a9aa7ea4f9807',
'pretty_version' => '7.8.0',
'version' => '7.8.0.0',
'reference' => '4436c6ec8d458e4244448b069cc572d088230b76',
'type' => 'library',
'install_path' => __DIR__ . '/../league/uri',
'aliases' => array(),
'dev_requirement' => false,
),
'league/uri-interfaces' => array(
'pretty_version' => '7.7.0',
'version' => '7.7.0.0',
'reference' => '62ccc1a0435e1c54e10ee6022df28d6c04c2946c',
'pretty_version' => '7.8.0',
'version' => '7.8.0.0',
'reference' => 'c5c5cd056110fc8afaba29fa6b72a43ced42acd4',
'type' => 'library',
'install_path' => __DIR__ . '/../league/uri-interfaces',
'aliases' => array(),
@@ -164,9 +164,9 @@
'dev_requirement' => false,
),
'macgirvin/http-message-signer' => array(
'pretty_version' => 'v0.2.12',
'version' => '0.2.12.0',
'reference' => '6e5b25a5536576e5046f5b0a7db620b5eb451453',
'pretty_version' => 'v0.2.13',
'version' => '0.2.13.0',
'reference' => 'b231228c630bf88ac3415b2ba28c057654c9fbea',
'type' => 'library',
'install_path' => __DIR__ . '/../macgirvin/http-message-signer',
'aliases' => array(),
@@ -245,9 +245,9 @@
'dev_requirement' => false,
),
'phpseclib/phpseclib' => array(
'pretty_version' => '3.0.48',
'version' => '3.0.48.0',
'reference' => '64065a5679c50acb886e82c07aa139b0f757bb89',
'pretty_version' => '3.0.49',
'version' => '3.0.49.0',
'reference' => '6233a1e12584754e6b5daa69fe1289b47775c1b9',
'type' => 'library',
'install_path' => __DIR__ . '/../phpseclib/phpseclib',
'aliases' => array(),
@@ -437,18 +437,18 @@
'dev_requirement' => false,
),
'smarty/smarty' => array(
'pretty_version' => 'v5.7.0',
'version' => '5.7.0.0',
'reference' => '73da7e90f302175a570662fcb0ba41f57b7a92ab',
'pretty_version' => 'v5.8.0',
'version' => '5.8.0.0',
'reference' => '78d259d3b971c59a0cd719c270cc5cbb740c36a7',
'type' => 'library',
'install_path' => __DIR__ . '/../smarty/smarty',
'aliases' => array(),
'dev_requirement' => false,
),
'spomky-labs/otphp' => array(
'pretty_version' => '11.4.1',
'version' => '11.4.1.0',
'reference' => '126c99b6cbbc18992cf3fba3b87931ba4e312482',
'pretty_version' => '11.4.2',
'version' => '11.4.2.0',
'reference' => '2a1b503fd1c1a5c751ab3c5cd37f2d2d26ab74ad',
'type' => 'library',
'install_path' => __DIR__ . '/../spomky-labs/otphp',
'aliases' => array(),
@@ -473,9 +473,9 @@
'dev_requirement' => false,
),
'symfony/filesystem' => array(
'pretty_version' => 'v7.4.0',
'version' => '7.4.0.0',
'reference' => 'd551b38811096d0be9c4691d406991b47c0c630a',
'pretty_version' => 'v7.4.6',
'version' => '7.4.6.0',
'reference' => '3ebc794fa5315e59fd122561623c2e2e4280538e',
'type' => 'library',
'install_path' => __DIR__ . '/../symfony/filesystem',
'aliases' => array(),
@@ -542,9 +542,9 @@
'dev_requirement' => false,
),
'zotlabs/hubzilla' => array(
'pretty_version' => 'dev-10.6RC',
'version' => 'dev-10.6RC',
'reference' => '38f040f9b528378aa796fc0d72e971cb30bc9bd4',
'pretty_version' => 'dev-11.2RC',
'version' => 'dev-11.2RC',
'reference' => '955ee217e30e12dec86bdcd936ce10abc3ed366a',
'type' => 'application',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),

View File

@@ -13,6 +13,7 @@ declare(strict_types=1);
namespace League\Uri\Contracts;
use BackedEnum;
use Countable;
use Iterator;
use IteratorAggregate;
@@ -20,7 +21,14 @@ use League\Uri\Exceptions\SyntaxError;
use Stringable;
/**
* @extends IteratorAggregate<string>
* @extends IteratorAggregate<int, string>
*
* @method bool isSubdomainOf(BackedEnum|Stringable|string|null $parentHost) Tells whether the current domain instance is a subdomain of the parent host.
* @method bool hasSubdomain(BackedEnum|Stringable|string|null $childHost) Tells whether the submitted host is a subdomain of the current instance.
* @method bool isSiblingOf(BackedEnum|Stringable|string|null $siblingHost) Tells whether the submitted host share the same parent domain as the current instance.
* @method static commonAncestorWith(BackedEnum|Stringable|string|null $other) Returns the common longest ancestor between 2 domain. The returned domain is empty if no ancestor is found
* @method static parentHost() Returns the current parent domain for the current instance. The returned domain is empty if no ancestor is found
* @method bool isEmpty() Tells whether the domain contains any label.
*/
interface DomainHostInterface extends Countable, HostInterface, IteratorAggregate
{

View File

@@ -17,6 +17,8 @@ use Stringable;
/**
* @see https://wicg.github.io/scroll-to-text-fragment/#the-fragment-directive
*
* @method string toFragmentValue() returns the encoded string representation of the directive as a fragment string
*/
interface FragmentDirective extends Stringable
{
@@ -33,7 +35,7 @@ interface FragmentDirective extends Stringable
public function value(): ?string;
/**
* The encoded string representation of the fragment.
* The encoded string representation of the directive.
*/
public function toString(): string;

View File

@@ -13,22 +13,40 @@ declare(strict_types=1);
namespace League\Uri\Contracts;
use BackedEnum;
use Countable;
use Deprecated;
use Iterator;
use IteratorAggregate;
use League\Uri\QueryComposeMode;
use League\Uri\StringCoercionMode;
use Stringable;
/**
* @extends IteratorAggregate<array{0:string, 1:string|null}>
*
* @method self withoutPairByKey(string ...$keys) Returns an instance without pairs with the specified keys.
* @method self withoutPairByValue(Stringable|string|int|bool|null ...$values) Returns an instance without pairs with the specified values.
* @method self withoutPairByKeyValue(string $key, Stringable|string|int|bool|null $value) Returns an instance without pairs with the specified key/value pair
* @method bool hasPair(string $key, ?string $value) Tells whether the pair exists in the query.
* @method ?string toFormData() Returns the string representation using the applicat/www-form-urlencoded rules
* @method ?string toRFC3986() Returns the string representation using RFC3986 rules
* @method string|null toFormData() Returns the string representation using the application/www-form-urlencoded rules
* @method string|null toRFC3986() Returns the string representation using RFC3986 rules
* @method string|null first(string $key) Returns the first value associated with the given name
* @method string|null last(string $key) Returns the first value associated with the given name
* @method int|null indexOf(string $key, int $nth = 0) Returns the offset of the pair based on its key and its nth occurrence; negative occurrences are supported
* @method int|null indexOfValue(?string $value, int $nth = 0) Returns the offset of the pair based on its value and its nth occurrence; negative occurrences are supported
* @method array pair(int $offset) Returns the key/value pair at the given numeric offset; negative occurrences are supported
* @method int countDistinctKeys() Returns the total number of distinct keys
* @method string|null valueAt(int $offset): Returns the value at the given numeric offset; negative occurrences are supported
* @method string keyAt(int $offset): Returns the key at the given numeric offset; negative occurrences are supported
* @method self normalize() returns the normalized string representation of the component
* @method self withoutPairByKey(string ...$keys) Returns an instance without pairs with the specified keys.
* @method self withoutPairByValue(array|BackedEnum|Stringable|string|int|bool|null $values, StringCoercionMode $coercionMode = StringCoercionMode::Native) Returns an instance without pairs with the specified values.
* @method self withoutPairByKeyValue(string $key, BackedEnum|Stringable|string|int|bool|null $value, StringCoercionMode $coercionMode = StringCoercionMode::Native) Returns an instance without pairs with the specified key/value pair
* @method bool hasPair(string $key, ?string $value) Tells whether the pair exists in the query.
* @method array getList(string $name) Returns the list associated with the given name or an empty array if it does not exist.
* @method bool hasList(string ...$names) Tells whether the parameter list exists in the query.
* @method self appendList(string $name, array $values, QueryComposeMode $composeMode = QueryComposeMode::Native) Appends a parameter to the query string
* @method self withList(string $name, array $values, QueryComposeMode $composeMode = QueryComposeMode::Native) Adds a new parameter to the query string and remove any previously set values
* @method self withoutList(string ...$names) Removes any given list associated with the given names
* @method self withoutLists() Removes all lists from the query string
* @method self onlyLists() Removes all pairs without a valid PHP's bracket notation
*/
interface QueryInterface extends Countable, IteratorAggregate, UriComponentInterface
{

View File

@@ -0,0 +1,27 @@
<?php
/**
* League.Uri (https://uri.thephpleague.com)
*
* (c) Ignace Nyamagana Butera <nyamsprod@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace League\Uri\Contracts;
interface Transformable
{
/**
* Apply a transformation to this instance and return a new instance.
*
* This method MUST retain the state of the current instance, and return
* a new instance of the same type.
*
* @param callable(static): static $callback
*/
public function transform(callable $callback): static;
}

View File

@@ -13,19 +13,19 @@ declare(strict_types=1);
namespace League\Uri;
use BackedEnum;
use Closure;
use Deprecated;
use League\Uri\Contracts\UriComponentInterface;
use League\Uri\Exceptions\SyntaxError;
use League\Uri\IPv6\Converter as IPv6Converter;
use SensitiveParameter;
use Stringable;
use Throwable;
use function explode;
use function filter_var;
use function gettype;
use function in_array;
use function is_scalar;
use function preg_match;
use function preg_replace_callback;
use function rawurldecode;
@@ -59,15 +59,19 @@ final class Encoder
*
* @see https://www.rfc-editor.org/rfc/rfc3986.html#section-2.3
*/
private const REGEXP_UNRESERVED_CHARACTERS = ',%(2[1-9A-Fa-f]|[3-7][0-9A-Fa-f]|61|62|64|65|66|7[AB]|5F),';
private const REGEXP_UNRESERVED_CHARACTERS = ',%(2[DdEe]|3[0-9]|4[1-9A-Fa-f]|5[AaFf]|6[1-9A-Fa-f]|7[0-9A-Ea-e]),';
/**
* Tell whether the user component is correctly encoded.
*/
public static function isUserEncoded(Stringable|string|null $encoded): bool
public static function isUserEncoded(BackedEnum|Stringable|string|null $encoded): bool
{
static $pattern = '/[^'.self::REGEXP_PART_UNRESERVED.self::REGEXP_PART_SUBDELIM.']+|'.self::REGEXP_PART_ENCODED.'/';
if ($encoded instanceof BackedEnum) {
$encoded = $encoded->value;
}
return null === $encoded || 1 !== preg_match($pattern, (string) $encoded);
}
@@ -76,11 +80,11 @@ final class Encoder
*
* All generic delimiters MUST be encoded
*/
public static function encodeUser(Stringable|string|null $component): ?string
public static function encodeUser(BackedEnum|Stringable|string|null $user): ?string
{
static $pattern = '/[^'.self::REGEXP_PART_UNRESERVED.self::REGEXP_PART_SUBDELIM.']+|'.self::REGEXP_PART_ENCODED.'/';
return self::encode($component, $pattern);
return self::encode($user, $pattern);
}
/**
@@ -90,7 +94,7 @@ final class Encoder
* any characters. To determine what characters to encode, please refer to
* RFC 3986.
*/
public static function normalizeUser(Stringable|string|null $user): ?string
public static function normalizeUser(BackedEnum|Stringable|string|null $user): ?string
{
return self::normalize(self::encodeUser(self::decodeUnreservedCharacters($user)));
}
@@ -111,10 +115,14 @@ final class Encoder
/**
* Tell whether the password component is correctly encoded.
*/
public static function isPasswordEncoded(#[SensitiveParameter] Stringable|string|null $encoded): bool
public static function isPasswordEncoded(#[SensitiveParameter] BackedEnum|Stringable|string|null $encoded): bool
{
static $pattern = '/[^'.self::REGEXP_PART_UNRESERVED.self::REGEXP_PART_SUBDELIM.':]+|'.self::REGEXP_PART_ENCODED.'/';
if ($encoded instanceof BackedEnum) {
$encoded = $encoded->value;
}
return null === $encoded || 1 !== preg_match($pattern, (string) $encoded);
}
@@ -123,7 +131,7 @@ final class Encoder
*
* Generic delimiters ":" MUST NOT be encoded
*/
public static function encodePassword(#[SensitiveParameter] Stringable|string|null $component): ?string
public static function encodePassword(#[SensitiveParameter] BackedEnum|Stringable|string|null $component): ?string
{
static $pattern = '/[^'.self::REGEXP_PART_UNRESERVED.self::REGEXP_PART_SUBDELIM.':]+|'.self::REGEXP_PART_ENCODED.'/';
@@ -137,7 +145,7 @@ final class Encoder
* any characters. To determine what characters to encode, please refer to
* RFC 3986.
*/
public static function normalizePassword(#[SensitiveParameter] Stringable|string|null $password): ?string
public static function normalizePassword(#[SensitiveParameter] BackedEnum|Stringable|string|null $password): ?string
{
return self::normalize(self::encodePassword(self::decodeUnreservedCharacters($password)));
}
@@ -145,24 +153,32 @@ final class Encoder
/**
* Tell whether the userInfo component is correctly encoded.
*/
public static function isUserInfoEncoded(#[SensitiveParameter] Stringable|string|null $userInfo): bool
public static function isUserInfoEncoded(#[SensitiveParameter] BackedEnum|Stringable|string|null $userInfo): bool
{
if (null === $userInfo) {
return true;
}
if ($userInfo instanceof BackedEnum) {
$userInfo = $userInfo->value;
}
[$user, $password] = explode(':', (string) $userInfo, 2) + [1 => null];
return self::isUserEncoded($user)
&& self::isPasswordEncoded($password);
}
public static function encodeUserInfo(#[SensitiveParameter] Stringable|string|null $userInfo): ?string
public static function encodeUserInfo(#[SensitiveParameter] BackedEnum|Stringable|string|null $userInfo): ?string
{
if (null === $userInfo) {
return null;
}
if ($userInfo instanceof BackedEnum) {
$userInfo = $userInfo->value;
}
[$user, $password] = explode(':', (string) $userInfo, 2) + [1 => null];
$userInfo = self::encodeUser($user);
if (null === $password) {
@@ -172,12 +188,16 @@ final class Encoder
return $userInfo.':'.self::encodePassword($password);
}
public static function normalizeUserInfo(#[SensitiveParameter] Stringable|string|null $userInfo): ?string
public static function normalizeUserInfo(#[SensitiveParameter] BackedEnum|Stringable|string|null $userInfo): ?string
{
if (null === $userInfo) {
return null;
}
if ($userInfo instanceof BackedEnum) {
$userInfo = $userInfo->value;
}
[$user, $password] = explode(':', (string) $userInfo, 2) + [1 => null];
$userInfo = self::normalizeUser($user);
if (null === $password) {
@@ -190,7 +210,7 @@ final class Encoder
/**
* Decodes all the URI component characters.
*/
public static function decodeAll(Stringable|string|null $component): ?string
public static function decodeAll(BackedEnum|Stringable|string|null $component): ?string
{
return self::decode($component, static fn (array $matches): string => rawurldecode($matches[0]));
}
@@ -198,7 +218,7 @@ final class Encoder
/**
* Decodes the URI component without decoding the unreserved characters which are already encoded.
*/
public static function decodeNecessary(Stringable|string|int|null $component): ?string
public static function decodeNecessary(BackedEnum|Stringable|string|int|null $component): ?string
{
$decoder = static function (array $matches): string {
if (1 === preg_match(self::REGEXP_CHARS_PREVENTS_DECODING, $matches[0])) {
@@ -214,8 +234,12 @@ final class Encoder
/**
* Decodes the component unreserved characters.
*/
public static function decodeUnreservedCharacters(Stringable|string|null $str): ?string
public static function decodeUnreservedCharacters(BackedEnum|Stringable|string|null $str): ?string
{
if ($str instanceof BackedEnum) {
$str = $str->value;
}
if (null === $str) {
return null;
}
@@ -230,10 +254,14 @@ final class Encoder
/**
* Tell whether the path component is correctly encoded.
*/
public static function isPathEncoded(Stringable|string|null $encoded): bool
public static function isPathEncoded(BackedEnum|Stringable|string|null $encoded): bool
{
static $pattern = '/[^'.self::REGEXP_PART_UNRESERVED.self::REGEXP_PART_SUBDELIM.':@\/]+|'.self::REGEXP_PART_ENCODED.'/';
if ($encoded instanceof BackedEnum) {
$encoded = $encoded->value;
}
return null === $encoded || 1 !== preg_match($pattern, (string) $encoded);
}
@@ -242,7 +270,7 @@ final class Encoder
*
* Generic delimiters ":", "@", and "/" MUST NOT be encoded
*/
public static function encodePath(Stringable|string|null $component): string
public static function encodePath(BackedEnum|Stringable|string|null $component): string
{
static $pattern = '/[^'.self::REGEXP_PART_UNRESERVED.self::REGEXP_PART_SUBDELIM.':@\/]+|'.self::REGEXP_PART_ENCODED.'/';
@@ -252,7 +280,7 @@ final class Encoder
/**
* Decodes the path component while preserving characters that should not be decoded in the context of a full valid URI.
*/
public static function decodePath(Stringable|string|null $path): ?string
public static function decodePath(BackedEnum|Stringable|string|null $path): ?string
{
$decoder = static function (array $matches): string {
$encodedChar = strtoupper($matches[0]);
@@ -270,7 +298,7 @@ final class Encoder
* any characters. To determine what characters to encode, please refer to
* RFC 3986.
*/
public static function normalizePath(Stringable|string|null $component): ?string
public static function normalizePath(BackedEnum|Stringable|string|null $component): ?string
{
return self::normalize(self::encodePath(self::decodePath($component)));
}
@@ -278,9 +306,12 @@ final class Encoder
/**
* Tell whether the query component is correctly encoded.
*/
public static function isQueryEncoded(Stringable|string|null $encoded): bool
public static function isQueryEncoded(BackedEnum|Stringable|string|null $encoded): bool
{
static $pattern = '/[^'.self::REGEXP_PART_UNRESERVED.self::REGEXP_PART_SUBDELIM.'\/?%]+|'.self::REGEXP_PART_ENCODED.'/';
if ($encoded instanceof BackedEnum) {
$encoded = $encoded->value;
}
return null === $encoded || 1 !== preg_match($pattern, (string) $encoded);
}
@@ -288,7 +319,7 @@ final class Encoder
/**
* Decodes the query component while preserving characters that should not be decoded in the context of a full valid URI.
*/
public static function decodeQuery(Stringable|string|null $path): ?string
public static function decodeQuery(BackedEnum|Stringable|string|null $path): ?string
{
$decoder = static function (array $matches): string {
$encodedChar = strtoupper($matches[0]);
@@ -306,7 +337,7 @@ final class Encoder
* any characters. To determine what characters to encode, please refer to
* RFC 3986.
*/
public static function normalizeQuery(Stringable|string|null $query): ?string
public static function normalizeQuery(BackedEnum|Stringable|string|null $query): ?string
{
return self::normalize(self::encodeQueryOrFragment(self::decodeQuery($query)));
}
@@ -314,17 +345,21 @@ final class Encoder
/**
* Tell whether the query component is correctly encoded.
*/
public static function isFragmentEncoded(Stringable|string|null $encoded): bool
public static function isFragmentEncoded(BackedEnum|Stringable|string|null $encoded): bool
{
static $pattern = '/[^'.self::REGEXP_PART_UNRESERVED.self::REGEXP_PART_SUBDELIM.':@\/?%]|'.self::REGEXP_PART_ENCODED.'/';
if ($encoded instanceof BackedEnum) {
$encoded = $encoded->value;
}
return null === $encoded || 1 !== preg_match($pattern, (string) $encoded);
}
/**
* Decodes the fragment component while preserving characters that should not be decoded in the context of a full valid URI.
*/
public static function decodeFragment(Stringable|string|null $path): ?string
public static function decodeFragment(BackedEnum|Stringable|string|null $path): ?string
{
return self::decode($path, static fn (array $matches): string => '%20' === $matches[0] ? $matches[0] : rawurldecode($matches[0]));
}
@@ -336,7 +371,7 @@ final class Encoder
* any characters. To determine what characters to encode, please refer to
* RFC 3986.
*/
public static function normalizeFragment(Stringable|string|null $fragment): ?string
public static function normalizeFragment(BackedEnum|Stringable|string|null $fragment): ?string
{
return self::normalize(self::encodeQueryOrFragment(self::decodeFragment($fragment)));
}
@@ -350,8 +385,12 @@ final class Encoder
* any characters. To determine what characters to encode, please refer to
* RFC 3986.
*/
public static function normalizeHost(Stringable|string|null $host): ?string
public static function normalizeHost(BackedEnum|Stringable|string|null $host): ?string
{
if ($host instanceof BackedEnum) {
$host = (string) $host->value;
}
if ($host instanceof Stringable) {
$host = (string) $host;
}
@@ -378,7 +417,7 @@ final class Encoder
*
* Generic delimiters ":", "@", "?", and "/" MUST NOT be encoded
*/
public static function encodeQueryOrFragment(Stringable|string|null $component): ?string
public static function encodeQueryOrFragment(BackedEnum|Stringable|string|null $component): ?string
{
static $pattern = '/[^'.self::REGEXP_PART_UNRESERVED.self::REGEXP_PART_SUBDELIM.':@\/?]+|'.self::REGEXP_PART_ENCODED.'/';
@@ -400,21 +439,20 @@ final class Encoder
private static function filterComponent(mixed $component): ?string
{
return match (true) {
true === $component => '1',
false === $component => '0',
$component instanceof UriComponentInterface => $component->value(),
$component instanceof Stringable,
is_scalar($component) => (string) $component,
null === $component => null,
default => throw new SyntaxError(sprintf('The component must be a scalar value `%s` given.', gettype($component))),
};
try {
return StringCoercionMode::Native->coerce($component);
} catch (Throwable $exception) {
throw new SyntaxError(
sprintf('The component must be a scalar value `%s` given.', gettype($component)),
previous: $exception
);
}
}
/**
* Encodes the URI component characters using a regular expression to find which characters need encoding.
*/
private static function encode(Stringable|string|int|bool|null $component, string $pattern): ?string
private static function encode(BackedEnum|Stringable|string|int|bool|null $component, string $pattern): ?string
{
$component = self::filterComponent($component);
if (null === $component || '' === $component) {
@@ -431,7 +469,7 @@ final class Encoder
/**
* Decodes the URI component characters using a closure.
*/
private static function decode(Stringable|string|int|null $component, Closure $decoder): ?string
private static function decode(BackedEnum|Stringable|string|int|null $component, Closure $decoder): ?string
{
$component = self::filterComponent($component);
if (null === $component || '' === $component) {
@@ -461,7 +499,7 @@ final class Encoder
* Create a new instance from the environment.
*/
#[Deprecated(message:'use League\Uri\Encoder::decodeNecessary() instead', since:'league/uri:7.6.0')]
public static function decodePartial(Stringable|string|int|null $component): ?string
public static function decodePartial(BackedEnum|Stringable|string|int|null $component): ?string
{
return self::decodeNecessary($component);
}

View File

@@ -13,6 +13,7 @@ declare(strict_types=1);
namespace League\Uri\Exceptions;
use BackedEnum;
use League\Uri\Idna\Error;
use League\Uri\Idna\Result;
use Stringable;
@@ -27,10 +28,14 @@ final class ConversionFailed extends SyntaxError
parent::__construct($message);
}
public static function dueToIdnError(Stringable|string $host, Result $result): self
public static function dueToIdnError(BackedEnum|Stringable|string $host, Result $result): self
{
$reasons = array_map(fn (Error $error): string => $error->description(), $result->errors());
if ($host instanceof BackedEnum) {
$host = (string) $host->value;
}
return new self('Host `'.$host.'` is invalid: '.implode('; ', $reasons).'.', (string) $host, $result);
}

View File

@@ -44,7 +44,6 @@ final class FeatureDetection
$isSupported = $isSupported ?? (function_exists('\idn_to_ascii') && defined('\INTL_IDNA_VARIANT_UTS46'));
$isSupported || throw new MissingFeature('Support for IDN host requires the `intl` extension for best performance or run "composer require symfony/polyfill-intl-idn" to install a polyfill.');
}
public static function supportsIPv4Conversion(): void

View File

@@ -13,6 +13,7 @@ declare(strict_types=1);
namespace League\Uri;
use BackedEnum;
use Exception;
use JsonSerializable;
use League\Uri\Contracts\UriComponentInterface;
@@ -223,7 +224,7 @@ final class HostRecord implements JsonSerializable
return $this->ipValue;
}
public static function isValid(Stringable|string|null $host): bool
public static function isValid(BackedEnum|Stringable|string|null $host): bool
{
try {
HostRecord::from($host);
@@ -287,8 +288,12 @@ final class HostRecord implements JsonSerializable
/**
* @throws SyntaxError
*/
public static function from(Stringable|string|null $host): self
public static function from(BackedEnum|Stringable|string|null $host): self
{
if ($host instanceof BackedEnum) {
$host = $host->value;
}
if ($host instanceof UriComponentInterface) {
$host = $host->value();
}
@@ -434,7 +439,7 @@ final class HostRecord implements JsonSerializable
$record = self::from($properties['host'] ?? throw new Exception('The `host` property is missing from the serialized object.'));
//if the Host computed value are already cache this avoid recomputing them
foreach (get_object_vars($record) as $prop => $value) {
/** @phpstan-ignore-next-line */
/* @phpstan-ignore-next-line */
$this->{$prop} = $value;
}
}

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