Compare commits

...

59 Commits
11.2 ... dev

Author SHA1 Message Date
Mario
3ee82b5ccc make sure we have all the necessary parts to calculate lat/lon before attempting (there might be issues in getGps() otherwise) - fix issue #1986 2026-06-18 08:47:10 +00:00
Mario
d4401faed5 remove redundant redirect and minor cleanup - fixes issue #1988 2026-06-17 07:48:42 +00:00
Mario
9daecfbb46 Merge branch 'default-homepage-template' into 'dev'
Move default home page to template

See merge request hubzilla/core!2294
2026-06-17 06:09:38 +00:00
Mario
2adb8ffbb5 Merge branch 'cleanup-hooks' into 'dev'
Some cleanup in Hook API's and tests

See merge request hubzilla/core!2293
2026-06-17 06:09:13 +00:00
Mario
2e1a04f6e3 remove unneeded browser plugin and add propfind_depth_infinity option 2026-06-16 11:45:29 +00:00
Harald Eilertsen
9f0fc77ac6 Move default home page to template 2026-06-15 18:59:24 +02:00
Harald Eilertsen
bb303ebc4c Some cleanup in Hook API's and tests
Clean up duplicated code in include/plugin.php and Zotlabs\Extend\Hook,
making the former just wrappers calling the latter. Also removed the
unnecessary serialization of arrays in Hook::insert, and cleaned up and
expaned the tests a bit.
2026-06-12 21:19:59 +02:00
Mario
b8683f73ce Merge branch 'sec-hardening-authorize' into 'dev'
Hardening of Authorize module

See merge request hubzilla/core!2292
2026-06-11 08:10:56 +00:00
Mario
2eae33736b Merge branch 'fix-register-verify-member-email' into 'dev'
Fix registration email verification email template

See merge request hubzilla/core!2291
2026-06-11 07:52:20 +00:00
Harald Eilertsen
4dbcbbb1af Add CSRF token to Authorize module
Issue........: https://framagit.org/hubzilla/core/-/work_items/1987
2026-06-10 20:57:56 +02:00
PepeCyB
8af931a4b9 Update German register email template 2026-06-10 14:59:10 +02:00
Harald Eilertsen
b80cb0adad Fix registration email verification email template
The email template used for verifying the email on new account
registration is wrong in several translations, including British
English.

Clicking any of the links in the emails produced results in a blank page
with no information or helpful error to the poor person trying to
register with a hub.

This patch updates the translated templates to correspond to the (US)
English main template. Translators should revisit the templates to
translate the english texts to the corresponding templates language
where necessary.

Issue........: https://framagit.org/hubzilla/core/-/work_items/1989
2026-06-10 12:47:31 +02:00
Harald Eilertsen
b5f7e55c2b Escape input params in Authorize module
Issue........: https://framagit.org/hubzilla/core/-/work_items/1987
2026-06-08 21:40:19 +02:00
Mario
3952aba824 Merge branch 'apidocs-head_add_css' into 'dev'
Improve API docs and naming in head_add_css()

See merge request hubzilla/core!2290
2026-06-05 19:24:59 +00:00
Mario
15db8dd945 Merge branch 'photo-imagick-avif' into 'dev'
Add AVIF support for PhotoImagick

See merge request hubzilla/core!2289
2026-06-05 19:24:15 +00:00
Mario
1876762216 Merge branch 'fix-duration-template' into 'dev'
Fix min/max fields in duration template

See merge request hubzilla/core!2288
2026-06-05 19:23:27 +00:00
Mario
492e8df555 Merge branch 'pt-translate-fix' into 'dev'
Fix errors in po files

See merge request hubzilla/core!2287
2026-06-05 19:22:37 +00:00
Mario
7667af5659 Merge branch 'phpcompat' into 'dev'
Make array_find available for PHP < 8.4

See merge request hubzilla/core!2286
2026-06-05 19:22:10 +00:00
Mario
92ca97ea6b Merge branch 'misc-phpstan-fixes' into 'dev'
Misc PHPStan fixes

See merge request hubzilla/core!2284
2026-06-05 19:20:36 +00:00
Harald Eilertsen
bf57bd34f9 Add AVIF support for PhotoImagick
It seems AVIF support was added to the GD photo driver, but not
ImageMagick. This patch adds it to ImageMagick as well, if supported by
the underlying library.

I also added pecl imagick extension to CI so that we can test it.
2026-05-28 19:42:01 +02:00
Harald Eilertsen
f1bd35b555 Improve API docs and naming in head_add_css() 2026-05-26 21:15:32 +02:00
Harald Eilertsen
5a77f23186 Fix min/max fields in duration template
If the min or max value for the number input was 0, it would be
interpreted as not set, and thus no limit applied.

Project......: Improve Superblock Addon
Sponsored-by.: NLnet NGI0 Commons Fund
2026-05-22 17:06:52 +02:00
Harald Eilertsen
6b605fecb0 Drop EOL whitespace fron field_duraction template
Project......: Improve Superblock Addon
Sponsored-by.: NLnet NGI0 Commons Fund
2026-05-22 17:06:52 +02:00
Mario
496dade675 update changelog 2026-05-20 05:51:03 +00:00
Mario
82b8ed2652 test variable before using it 2026-05-20 07:48:59 +02:00
Mario
2625fe9527 update changelog 2026-05-20 05:42:08 +00:00
Harald Eilertsen
320019fca3 Fix invalid plural in ru translation 2026-05-19 15:43:17 +02:00
Harald Eilertsen
78f2bb46f9 Fix extra plurals in spanish translation 2026-05-19 11:32:14 +02:00
Harald Eilertsen
88c1cda002 Fix po file for pt-br translation 2026-05-18 22:38:44 +02:00
Harald Eilertsen
6f65d2d6ef Make array_find available for PHP < 8.4
array_find is a useful function, but only available in PHP 8.4 or later.
Here we implement it ourselves if it's not already defined, to make it
available for all supported PHP versions.
2026-05-16 09:32:26 +02:00
Mario
674522c8da Merge branch 'improve-jsonld-fix' into 'dev'
Improve detecting suspicious ActivityStreams keys

See merge request hubzilla/core!2283
2026-05-15 21:02:05 +00:00
Harald Eilertsen
47c902a5ea Misc PHPStan fixes
Mainly variables that could possibly be referenced without being
defined.
2026-05-09 23:25:02 +02:00
Harald Eilertsen
0c7731bb76 Improve detecting suspicious ActivityStreams keys
Using string comparison on the whole key does not work, as some keys
will be given prefixes during expansion. We need to check if the payload
has keys that _contain_ the suspicious keywords we're looking for.
2026-05-09 18:40:55 +02:00
Mario
62582509cd Merge branch 'json-ld' into 'dev'
check for currently unsafe json-ld constructs

See merge request hubzilla/core!2280
2026-05-05 10:44:18 +00:00
Mario
67d73f74ac check for currently unsafe json-ld constructs 2026-05-05 10:44:17 +00:00
Mario
1553bc708f Merge branch 'tests-for-LDSignatures' into 'dev'
Tests for LDSignatures::verify

See merge request hubzilla/core!2281
2026-05-05 07:15:05 +00:00
Mario
99728bf038 Merge branch 'opengraph-tests' into 'dev'
tests: Tests for opengraph meta tags

See merge request hubzilla/core!2282
2026-05-05 07:14:33 +00:00
Harald Eilertsen
965e643c71 tests: Tests for opengraph meta tags
Also fixed a minor whitespace issue in include/opengraph.php, and added
braces so the logic is clearer.
2026-05-02 23:19:18 +02:00
Harald Eilertsen
8d283e0be5 tests: Use minimal channel for LDSignature tests
Move the channel keypair and other fields needed by the
LDSignature::verify function to the test case itself. This makes the
test case independent on any potential future changes to the fixtures.
2026-05-02 11:54:42 +02:00
Harald Eilertsen
0cd682d85e tests: Add basic test for LDSignature::verify 2026-05-02 11:41:54 +02:00
Harald Eilertsen
9aff1d4024 tests: Fix invalid keypair for channel fixture 2026-05-02 11:38:37 +02:00
Mario
b0a2bf47c8 fix wording 2026-04-29 14:27:58 +00:00
Mario
3d29c498e9 move unparse_url() to Url class, also move tests 2026-04-29 14:16:15 +00:00
Mario
acf683a7d3 no need to rebuild the initial url. also add spdx info 2026-04-29 14:59:21 +02:00
Mario
fdeab5b2d1 add same host test and get rid of the www subdomain 2026-04-28 13:10:25 +02:00
Mario
fa7c8e93f1 merge dev 2026-04-28 12:54:40 +02:00
Mario
fd6b252b36 refactor zid() and add tests 2026-04-28 12:50:38 +02:00
Mario
f7a29f7539 Merge branch 'refactor-cleanup-bbcode' into 'dev'
Clean up detection of naked URL's in posts

See merge request hubzilla/core!2278
2026-04-28 08:21:34 +00:00
Harald Eilertsen
de9e2c27ea Drop cleaning empty f query param from links 2026-04-27 12:40:45 +02:00
Mario
0376b5d442 actually there is no need to set App::$profile here because it will be set in profile_load() if applicable 2026-04-27 08:18:50 +00:00
Mario
b20ed4f455 do not set App::$profile for removed channels and minor cleanup 2026-04-27 07:55:17 +00:00
Harald Eilertsen
a574c79f9e Clean up detection of naked URL's in posts
Fix detecting naked IPv6 URL's in posts, and clean up a bit how we deal
with zid parameters. Also drops the `nakedoembed` function which no
longer had anything to do with oembed handling. This also means we no
longer need to scan the body twice for the same regex.
2026-04-26 16:22:18 +02:00
Mario
9c9ca7728c Merge branch 'remove-bookmark-tag-from-naked-urls' into 'dev'
Remove bookmark prefix from naked URLs

See merge request hubzilla/core!2277
2026-04-22 16:26:35 +00:00
Harald Eilertsen
7fc1d58b10 Remove bookmark prefix from naked URLs
See discussion here:
https://hubzilla.org/channel/adminsforum?mid=00a360a1-6281-52aa-b4de-65493b8899f5
2026-04-22 13:22:32 +02:00
Mario
782765e377 move mention- and hashtag symbols inside the anchor tag 2026-04-15 07:37:15 +00:00
Mario
e2dbf0a785 it probably makes sense to link the system status label to /admin 2026-03-30 19:24:06 +00:00
Mario
1680bac8b5 refactor get_files_activity() so that it can display latest touched files by filetype category (document, audio, video, uncategorized) 2026-03-30 19:06:10 +00:00
Mario Vavti
231384a58f messageFilter: make sure we do not choke if we expect a string but an array is given 2026-03-27 22:42:59 +01:00
Mario Vavti
6edeea4b42 composer stuff 2026-03-26 22:00:54 +01:00
70 changed files with 1133 additions and 556 deletions

View File

@@ -89,9 +89,9 @@ default:
before_script:
# Install & enable Xdebug for code coverage reports
- apt-get update
- apt-get install -yqq libicu-dev libjpeg-dev libpng-dev libpq-dev libyaml-dev libgmp-dev libzip-dev mariadb-client postgresql-client unzip zip
- pecl install xdebug yaml
- docker-php-ext-enable xdebug yaml
- apt-get install -yqq libicu-dev libjpeg-dev libpng-dev libpq-dev libyaml-dev libgmp-dev libzip-dev mariadb-client postgresql-client libmagickcore-7.q16-dev libmagickwand-dev unzip zip
- pecl install imagick xdebug yaml
- docker-php-ext-enable imagick xdebug yaml
- docker-php-ext-configure gd --with-jpeg=/usr/include/
- docker-php-ext-install gd gmp intl pdo_mysql pdo_pgsql zip exif

View File

@@ -1,3 +1,16 @@
Hubzilla 11.2.1 (2026-05-20)
- Fix channel creation failing in some situations
- Drop payloads with unsafe json-ld keys in LDSignatures::verify() and add tests
- Fix App::$profile set for removed channels
- Fix MessageFilter breaking when expecting string but array is given
- Superblock: fix blocking failed in some setups (sponsored by NLnet NGI0 Commons Fund/Superblock)
- Superblock: fix missing import of attribute (sponsored by NLnet NGI0 Commons Fund/Superblock)
- Superblock: improved detection of reshares (sponsored by NLnet NGI0 Commons Fund/Superblock)
- Cards: fix PHP warning
- Gallery: fix PHP warnings and only implement observer related javascript if there actually is an observer
- Diaspora: fix missing local namespace for thr_parent
Hubzilla 11.2 (2026-03-26)
Features
- Introduce parse_webbie() for preparing webbies and URLs for webfinger usage

View File

@@ -167,10 +167,6 @@ class Hook {
* would require the hook array to be resorted.
*/
static public function insert($hook, $fn, $version = 0, $priority = 0) {
if(is_array($fn)) {
$fn = serialize($fn);
}
if(! is_array(App::$hooks))
App::$hooks = array();

View File

@@ -1964,7 +1964,7 @@ class Activity {
);
if ($x) {
return sprintf('@[zrl=%s]%s[/zrl]', $x[0]['xchan_url'], $x[0]['xchan_name']);
return sprintf('[zrl=%s]@%s[/zrl]', $x[0]['xchan_url'], $x[0]['xchan_name']);
}
return '@{' . $id . '}';

View File

@@ -123,11 +123,11 @@ class IConfig {
return $value;
}
if(intval($item))
if(intval($item)) {
$iid = intval($item);
if(! $iid)
} else {
return false;
}
if(self::Get($item, $family, $key) === false) {
$r = q("insert into iconfig( iid, cat, k, v, sharing ) values ( %d, '%s', '%s', '%s', %d ) ",
@@ -174,11 +174,11 @@ class IConfig {
return true;
}
if(intval($item))
if(intval($item)) {
$iid = intval($item);
if(! $iid)
} else {
return false;
}
return q("delete from iconfig where iid = %d and cat = '%s' and k = '%s' ",
intval($iid),

View File

@@ -62,14 +62,16 @@ class JcsEddsa2022 {
try {
$result = sodium_crypto_sign_verify_detached($base58->decode($encodedSignature), $optionsHash . $dataHash,
(new Multibase())->decode($publicKey, true));
logger('SignatureVerify (eddsa-jcs-2022) ' . (($result) ? 'true' : 'false'));
return $result;
}
catch (\Exception $e) {
logger('verify exception:' . $e->getMessage());
}
logger('SignatureVerify (eddsa-jcs-2022) ' . (($result) ? 'true' : 'false'));
return $result;
return false;
}
public function signableData($data) {

View File

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

View File

@@ -316,7 +316,7 @@ class MessageFilter
if (preg_match('/(.*?)\s~=\s(.*?)$/', $s, $matches)) {
$x = ((array_key_exists(trim($matches[1]),$item)) ? $item[trim($matches[1])] : EMPTY_STR);
if (stripos($x, trim($matches[2])) !== false) {
if (is_string($x) && stripos($x, trim($matches[2])) !== false) {
return true;
}
return false;

132
Zotlabs/Lib/Url.php Normal file
View File

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

View File

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

View File

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

View File

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

View File

@@ -203,7 +203,6 @@ class Accounts {
$t = get_markup_template('admin_accounts.tpl');
$o = replace_macros($t, array(
// strings //
'$debug' => $debug,
'$title' => t('Administration'),
'$page' => t('Accounts'),
'$submit' => t('Submit'),
@@ -263,7 +262,7 @@ class Accounts {
if ($zarop && $zarat >= 0 && $zarse && $zarse == $_SESSION[self::MYP]['h'][$zarat]) {
//
$rc = 0;
if ($zarop == 'd') {
$rd = q("UPDATE register SET reg_vital = 0 WHERE reg_id = %d AND SUBSTR(reg_hash,1,4) = '%s' ",
intval($_SESSION[self::MYP]['i'][$zarat]),
@@ -279,7 +278,6 @@ class Accounts {
intval($_SESSION[self::MYP]['i'][$zarat]),
dbesc($_SESSION[self::MYP]['h'][$zarat])
);
$rc = 0;
$rs = q("SELECT * from register WHERE reg_id = %d ",
intval($_SESSION[self::MYP]['i'][$zarat])
);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,6 +2,7 @@
namespace Zotlabs\Photo;
use Imagick;
use Zotlabs\Lib\Config;
/**
@@ -16,8 +17,14 @@ class PhotoImagick extends PhotoDriver {
'image/png' => 'png',
'image/gif' => 'gif'
];
if(\Imagick::queryFormats("WEBP"))
if (Imagick::queryFormats('WEBP')) {
$ret['image/webp'] = 'webp';
}
if (Imagick::queryFormats('AVIF')) {
$ret['image/avif'] = 'avif';
}
return $ret;
}

View File

@@ -31,7 +31,10 @@ class Channel_activities {
self::get_system_status();
}
self::get_photos_activity();
self::get_files_activity();
self::get_files_activity('uncategorized');
self::get_files_activity('document');
self::get_files_activity('audio');
self::get_files_activity('video');
self::get_webpages_activity();
self::get_channels_activity();
@@ -109,10 +112,28 @@ class Channel_activities {
}
private static function get_files_activity() {
private static function get_files_activity($category) {
$not = '';
$mime_types = stringify_array(self::get_mime_types_by_category($category));
switch($category) {
case 'audio':
$label = t('Audios');
break;
case 'video':
$label = t('Videos');
break;
case 'document':
$label = t('Documents');
break;
default:
$label = t('Uploads');
$not = 'NOT';
}
$r = q("SELECT * FROM attach WHERE uid = %d
AND is_dir = 0 AND is_photo = 0
AND is_dir = 0 AND is_photo = 0 AND filetype $not IN ($mime_types)
ORDER BY edited DESC LIMIT %d",
intval(self::$uid),
intval(self::$limit)
@@ -130,8 +151,8 @@ class Channel_activities {
];
}
self::$activities['files'] = [
'label' => t('Files'),
self::$activities[$category] = [
'label' => $label,
'icon' => 'folder',
'url' => z_root() . '/cloud/' . self::$channel['channel_address'],
'date' => $r[0]['edited'],
@@ -141,6 +162,129 @@ class Channel_activities {
}
private static function get_mime_types_by_category($category): array
{
$mime_types = [
'document' => [
'application/vnd.ms-powerpoint',
'application/vnd.ms-excel',
'application/vnd.sun.xml.writer',
'application/vnd.oasis.opendocument.text',
'application/vnd.oasis.opendocument.text-flat-xml',
'application/vnd.sun.xml.calc',
'application/vnd.oasis.opendocument.spreadsheet',
'application/vnd.oasis.opendocument.spreadsheet-flat-xml',
'application/vnd.sun.xml.impress',
'application/vnd.oasis.opendocument.presentation',
'application/vnd.oasis.opendocument.presentation-flat-xml',
'application/vnd.sun.xml.draw',
'application/vnd.oasis.opendocument.graphics',
'application/vnd.oasis.opendocument.graphics-flat-xml',
'application/vnd.oasis.opendocument.chart',
'application/vnd.sun.xml.writer.global',
'application/vnd.oasis.opendocument.text-master',
'application/vnd.sun.xml.writer.template',
'application/vnd.oasis.opendocument.text-template',
'application/vnd.oasis.opendocument.text-master-template',
'application/vnd.sun.xml.calc.template',
'application/vnd.oasis.opendocument.spreadsheet-template',
'application/vnd.sun.xml.impress.template',
'application/vnd.oasis.opendocument.presentation-template',
'application/vnd.sun.xml.draw.template',
'application/vnd.oasis.opendocument.graphics-template',
'application/msword',
'application/msword',
'application/vnd.ms-excel',
'application/vnd.ms-powerpoint',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/vnd.ms-word.document.macroEnabled.12',
'application/vnd.openxmlformats-officedocument.wordprocessingml.template',
'application/vnd.ms-word.template.macroEnabled.12',
'application/vnd.openxmlformats-officedocument.spreadsheetml.template',
'application/vnd.ms-excel.template.macroEnabled.12',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'application/vnd.ms-excel.sheet.binary.macroEnabled.12',
'application/vnd.ms-excel.sheet.macroEnabled.12',
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'application/vnd.ms-powerpoint.presentation.macroEnabled.12',
'application/vnd.openxmlformats-officedocument.presentationml.template',
'application/vnd.ms-powerpoint.template.macroEnabled.12',
'application/vnd.wordperfect',
'application/x-aportisdoc',
'application/x-hwp',
'application/vnd.ms-works',
'application/vnd.ms-office',
'application/x-mswrite',
'application/x-dif-document',
'text/spreadsheet',
'application/x-dbase',
'application/vnd.lotus-1-2-3',
'application/coreldraw',
'application/vnd.visio2013',
'application/vnd.visio',
'application/vnd.ms-visio.drawing',
'application/x-mspublisher',
'application/x-sony-bbeb',
'application/x-gnumeric',
'application/macwriteii',
'application/x-iwork-numbers-sffnumbers',
'application/vnd.oasis.opendocument.text-web',
'application/x-pagemaker',
'text/rtf',
'text/plain',
'application/x-fictionbook+xml',
'application/clarisworks',
'application/x-iwork-pages-sffpages',
'application/vnd.openxmlformats-officedocument.presentationml.slideshow',
'application/x-iwork-keynote-sffkey',
'application/x-abiword',
'application/vnd.sun.xml.chart',
'application/x-t602',
'application/pdf',
],
'audio' => [
'audio/mpeg', // MP3
'audio/mp3',
'audio/wav', // WAV
'audio/x-wav',
'audio/webm', // WebM audio
'audio/ogg', // OGG
'audio/aac', // AAC
'audio/flac', // FLAC
'audio/x-flac',
'audio/mp4', // M4A / MP4 audio
'audio/x-m4a',
'audio/3gpp', // 3GP audio
'audio/3gpp2',
'audio/amr', // AMR
'audio/x-ms-wma', // Windows Media Audio
'audio/basic', // µ-law / basic audio
],
'video' => [
'video/mp4', // MP4
'video/x-msvideo', // AVI
'video/x-ms-wmv', // WMV
'video/mpeg', // MPEG
'video/ogg', // OGG/Theora
'video/webm', // WebM
'video/3gpp', // 3GP
'video/3gpp2',
'video/quicktime', // MOV
'video/x-flv', // Flash Video
'video/x-matroska', // MKV
'video/mp2t', // MPEG-TS (.ts)
]
];
if ($category === 'uncategorized') {
return array_merge(...array_values($mime_types));
}
return $mime_types[$category];
}
private static function get_webpages_activity() {
if(!Apps::system_app_installed(self::$uid, 'Webpages')) {

View File

@@ -8,15 +8,18 @@
namespace Zotlabs\Widget;
use App;
class Fullprofile {
function widget($arr) {
if(!(isset(\App::$profile['profile_uid']) && \App::$profile['profile_uid']))
if (empty(App::$profile['profile_uid'])) {
return;
}
$block = observer_prohibited();
return profile_sidebar(\App::$profile, $block, true, true);
return profile_sidebar(App::$profile, $block, true, true);
}
}

View File

@@ -13,7 +13,7 @@ use App;
class Profile {
function widget($args) {
if(!isset(App::$profile['profile_uid'])) {
if (empty(App::$profile['profile_uid'])) {
return;
}

View File

@@ -42,6 +42,7 @@ use Zotlabs\Web\HttpMeta;
require_once('vendor/autoload.php');
require_once('include/php_compat.php');
require_once('include/config.php');
require_once('include/network.php');
require_once('include/plugin.php');

View File

@@ -84,19 +84,6 @@ function tryoembed($match) {
return $html;
}
function nakedoembed($match) {
$url = ((count($match) == 2) ? $match[1] : $match[2]);
$strip_url = strip_escaped_zids($url);
// this function no longer performs oembed on naked links
// because they author may have created naked links intentionally.
// Now it just strips zids on naked links.
return str_replace($url,$strip_url,$match[0]);
}
function tryzrlaudio($match) {
$link = $match[1];
$zrl = is_matrix_url($link);

View File

@@ -6,6 +6,7 @@ use Zotlabs\Lib\Mailer;
use Zotlabs\Lib\Zotfinger;
use Zotlabs\Lib\Libzot;
use Zotlabs\Lib\Queue;
use Zotlabs\Lib\Url;
use Zotlabs\Web\HTTPSig;
/**
@@ -2126,6 +2127,7 @@ function get_request_string($url) {
/**
* @deprecated use Url::unparse() instead
* Reconstructs a URL from its parsed components.
*
* This function takes a parsed URL as an associative array and reconstructs
@@ -2146,39 +2148,5 @@ function get_request_string($url) {
* @return string The reconstructed URL as a string.
*/
function unparse_url(array $parsed_url, array $parts = ['scheme', 'host', 'port', 'user', 'pass', 'path', 'query', 'fragment']): string {
$url_parts = [];
if (in_array('scheme', $parts) && array_key_exists('scheme', $parsed_url)) {
$url_parts[] = $parsed_url['scheme'] . '://';
}
if (in_array('user', $parts) && array_key_exists('user', $parsed_url)) {
$url_parts[] = $parsed_url['user'];
if (in_array('pass', $parts) && array_key_exists('pass', $parsed_url)) {
$url_parts[] = ':' . $parsed_url['pass'];
}
$url_parts[] = '@';
}
if (in_array('host', $parts) && array_key_exists('host', $parsed_url)) {
$url_parts[] = $parsed_url['host'];
}
if (in_array('port', $parts) && array_key_exists('port', $parsed_url)) {
$url_parts[] = ':' . $parsed_url['port'];
}
if (in_array('path', $parts) && array_key_exists('path', $parsed_url)) {
$url_parts[] = $parsed_url['path'];
}
if (in_array('query', $parts) && array_key_exists('query', $parsed_url)) {
$url_parts[] = '?' . $parsed_url['query'];
}
if (in_array('fragment', $parts) && array_key_exists('fragment', $parsed_url)) {
$url_parts[] = '#' . $parsed_url['fragment'];
}
return implode('', $url_parts);
return Url::unparse($parsed_url, $parts);
}

View File

@@ -62,8 +62,10 @@
$ogimagetype = $channel['xchan_photo_mimetype'];
}
if (! isset(App::$page['htmlhead']))
App::$page['htmlhead'] = '';
if (! isset(App::$page['htmlhead'])) {
App::$page['htmlhead'] = '';
}
App::$page['htmlhead'] .= '<meta property="og:title" content="' . htmlspecialchars((isset($ogtitle) ? $ogtitle : $channel['channel_name'])) . '">' . "\r\n";
App::$page['htmlhead'] .= '<meta property="og:image" content="' . $ogimage . '">' . "\r\n";
App::$page['htmlhead'] .= '<meta property="og:image:type" content="' . $ogimagetype . '">' . "\r\n";

View File

@@ -344,7 +344,7 @@ function photo_upload($channel, $observer, $args) {
elseif (array_key_exists('GPSLatitude', $exif)) {
$gps = $exif;
}
if ($gps) {
if (isset($gps['GPSLatitude'], $gps['GPSLatitudeRef'], $gps['GPSLongitude'], $gps['GPSLongitudeRef'])) {
$lat = getGps($gps['GPSLatitude'], $gps['GPSLatitudeRef']);
$lon = getGps($gps['GPSLongitude'], $gps['GPSLongitudeRef']);
}

28
include/php_compat.php Normal file
View File

@@ -0,0 +1,28 @@
<?php
/*
* Forward compatibility with more recent PHP versions.
*
* SPDX-FileCopyrightText: 2026 The Hubzilla Community
* SPDX-FileContributor: Harald Eilertsen <haraldei@anduin.net>
*
* SPDX-License-Identifier: MIT
*
* This file contains functions that allow us to use convenient functions from
* later PHP versions in earlier versions where these functions may not be
* supported directly.
*/
if (!function_exists('array_find')) {
// array_find is defined in PHP 8.4 or higher, so for earlier PHP versions we
// define it here.
function array_find(array $array, callable $callback): mixed {
foreach ($array as $key => $entry) {
if ($callback($entry, $key) === true) {
return $entry;
}
}
return null;
}
}

View File

@@ -5,6 +5,7 @@
* @brief Some functions to handle addons and themes.
*/
use Zotlabs\Extend\Hook;
use Zotlabs\Lib\Config;
/**
@@ -342,29 +343,14 @@ function visible_plugin_list() {
* @return mixed|bool
*/
function register_hook($hook, $file, $function, $priority = 0) {
$r = q("SELECT * FROM hook WHERE hook = '%s' AND file = '%s' AND fn = '%s' LIMIT 1",
dbesc($hook),
dbesc($file),
dbesc($function)
);
if($r)
return true;
$r = q("INSERT INTO hook (hook, file, fn, priority) VALUES ( '%s', '%s', '%s', '%s' )",
dbesc($hook),
dbesc($file),
dbesc($function),
dbesc($priority)
);
return $r;
return Hook::Register($hook, $file, $function, 1, $priority);
}
/**
* @brief unregisters a hook.
*
* @see ::Zotlabs::Extend::Hook::unregister
* @see ::Zotlabs::Extend::Hook::unregister()
*
* @param string $hook the name of the hook
* @param string $file the name of the file that hooks into
@@ -372,13 +358,7 @@ function register_hook($hook, $file, $function, $priority = 0) {
* @return array
*/
function unregister_hook($hook, $file, $function) {
$r = q("DELETE FROM hook WHERE hook = '%s' AND file = '%s' AND fn = '%s'",
dbesc($hook),
dbesc($file),
dbesc($function)
);
return $r;
return Hook::unregister($hook, $file, $function, 1, 0);
}
/**
@@ -428,7 +408,7 @@ function load_hooks() {
}
/**
* @brief Inserts a hook into a page request.
* Inserts a hook into a page request.
*
* Insert a short-lived hook into the running page request.
* Hooks are normally persistent so that they can be called
@@ -439,6 +419,8 @@ function load_hooks() {
* which will not persist beyond the life of this page request
* or the current process.
*
* @see ::Zotlabs::Extend::Hook::insert()
*
* @param string $hook
* name of hook to attach callback
* @param string $fn
@@ -447,14 +429,7 @@ function load_hooks() {
* @param int $priority (optional) default 0
*/
function insert_hook($hook, $fn, $version = 0, $priority = 0) {
if(! is_array(App::$hooks))
App::$hooks = array();
if(! array_key_exists($hook, App::$hooks))
App::$hooks[$hook] = array();
App::$hooks[$hook][] = array('', $fn, $priority, $version);
Hook::insert($hook, $fn, $version, $priority);
}
/**
@@ -930,13 +905,15 @@ function get_theme_screenshot($theme) {
}
/**
* @brief add CSS to \<head\>
* Add stylesheet to \<head\> section.
*
* @param string $src
* @param string $media change media attribute (default to 'screen')
* @param string $urlpath
* The URL path relative to the domain to the stylesheet to add.
* @param string $media
* Media attribute for the stylesheet (default to 'screen')
*/
function head_add_css($src, $media = 'screen') {
App::$css_sources[] = array($src, $media);
function head_add_css($urlpath, $media = 'screen'): void {
App::$css_sources[] = array($urlpath, $media);
}
function head_remove_css($src, $media = 'screen') {

View File

@@ -468,7 +468,7 @@ function tagblock($link,$uid,$count = 0,$authors = '',$owner = '', $flags = 0,$r
if($r) {
$o = '<div class="tagblock widget"><h3>' . t('Tags') . '</h3><div class="tags" align="center">';
foreach($r as $rr) {
$o .= '<span class="tag'.$rr[2].'">#</span><a href="'.$link .'/' . '?f=&tag=' . urlencode($rr[0]).'" class="tag'.$rr[2].'">'.$rr[0].'</a> ' . "\r\n";
$o .= '<a href="'.$link .'/' . '?f=&tag=' . urlencode($rr[0]).'" class="tag'.$rr[2].'">#'.$rr[0].'</a> ' . "\r\n";
}
$o .= '</div></div>';
}

View File

@@ -2956,11 +2956,11 @@ function handle_tag(&$body, &$str_tags, $profile_uid, $tag, $in_network = true)
// create text for link
$url = z_root() . '/search?tag=' . rawurlencode($basetag);
$newtag = '#[zrl=' . z_root() . '/search?tag=' . rawurlencode($basetag) . ']' . $basetag . '[/zrl]';
$newtag = '[zrl=' . z_root() . '/search?tag=' . rawurlencode($basetag) . ']#' . $basetag . '[/zrl]';
// replace tag by the link. Make sure to not replace something in the middle of a word
$body = preg_replace('/(?<![a-zA-Z0-9=\/])'.preg_quote($tag,'/').'/', $newtag, $body);
$body = preg_replace('/(?<![\]a-zA-Z0-9=\/])'.preg_quote($tag,'/').'/', $newtag, $body);
$replaced = true;
}
@@ -3095,10 +3095,10 @@ function handle_tag(&$body, &$str_tags, $profile_uid, $tag, $in_network = true)
$url = $profile;
$bb_tag = (($xc['xchan_network'] === 'zot6') ? 'zrl' : 'url');
$newtag = '@' . (($exclusive) ? '!' : '') . '[' . $bb_tag . '=' . $profile . ']' . $newname . '[/' . $bb_tag . ']';
$newtag = '[' . $bb_tag . '=' . $profile . ']@' . (($exclusive) ? '!' : '') . $newname . '[/' . $bb_tag . ']';
// Replace tag but make sure to not replace something in the middle of a word
$body = preg_replace('/(?<![a-zA-Z0-9=\/])' . preg_quote($tag, '/') . '/', $newtag, $body);
$body = preg_replace('/(?<![\]a-zA-Z0-9=\/])' . preg_quote($tag, '/') . '/', $newtag, $body);
// $body = str_replace('@' . (($exclusive) ? '!' : '') . $name, $newtag, $body);
// append tag to str_tags
@@ -3141,9 +3141,9 @@ function handle_tag(&$body, &$str_tags, $profile_uid, $tag, $in_network = true)
}
$channel = App::get_channel();
if($channel) {
$newtag = '@' . (($exclusive) ? '!' : '') . '[zrl=' . z_root() . '/channel/' . $channel['channel_address'] . ']' . $newname . '[/zrl]';
$newtag = '[zrl=' . z_root() . '/channel/' . $channel['channel_address'] . ']@' . (($exclusive) ? '!' : '') . $newname . '[/zrl]';
// Replace tag but make sure to not replace something in the middle of a word
$body = preg_replace('/(?<![a-zA-Z0-9=\/])' . preg_quote($tag, '/') . '/', $newtag, $body);
$body = preg_replace('/(?<![\]a-zA-Z0-9=\/])' . preg_quote($tag, '/') . '/', $newtag, $body);
// $body = str_replace('@' . (($exclusive) ? '!' : '') . $name, $newtag, $body);
}
}
@@ -3752,9 +3752,10 @@ function cleanup_bbcode($body) {
$body = preg_replace_callback('/\[img(.*?)\[\/(img)\]/ism','\red_escape_codeblock',$body);
$body = preg_replace_callback('/\[zmg(.*?)\[\/(zmg)\]/ism','\red_escape_codeblock',$body);
$body = preg_replace_callback("/([^\]\='".'"'."\;\/\{]|^|\#\^)(https?\:\/\/[a-zA-Z0-9\pL\:\/\-\?\&\;\.\=\@\_\~\#\%\$\!\\+\,\(\)]+)/ismu", '\nakedoembed', $body);
$body = preg_replace_callback("/([^\]\='".'"'."\;\/\{]|^|\#\^)(https?\:\/\/[a-zA-Z0-9\pL\:\/\-\?\&\;\.\=\@\_\~\#\%\$\!\\+\,\(\)]+)/ismu", '\red_zrl_callback', $body);
$body = preg_replace_callback(
"/([^\]\='".'"'."\;\/\{]|^|\#\^)(https?\:\/\/[a-zA-Z0-9\pL\:\/\-\?\&\;\.\=\@\_\~\#\%\$\!\\+\,\(\)\[\]]+)/ismu",
'\red_zrl_callback',
$body);
$body = preg_replace_callback('/\[\$b64code(.*?)\[\/(code)\]/ism','\red_unescape_codeblock',$body);
$body = preg_replace_callback('/\[\$b64summary(.*?)\[\/(summary)\]/ism','\red_unescape_codeblock',$body);

View File

@@ -3,6 +3,7 @@
use Zotlabs\Lib\Config;
use Zotlabs\Lib\Libzot;
use Zotlabs\Lib\Verify;
use Zotlabs\Lib\Url;
function is_matrix_url($url) {
@@ -29,75 +30,20 @@ function is_matrix_url($url) {
}
/**
* @brief Adds a zid parameter to a url.
* @brief Adds a zid parameter to a url
* @deprecated use Url::zid() instead
*
* @param string $s
* @param string $url
* The url to accept the zid
* @param boolean $address
* @param string $address (optional)
* $address to use instead of session environment
* @return string
*/
function zid($s, $address = '') {
if (!$s || strpos($s,'zid=')) {
return $s;
}
$m = parse_url($s);
if (!is_array($m)) {
return $s;
}
$fragment = ((array_key_exists('fragment',$m) && $m['fragment']) ? $m['fragment'] : false);
if($fragment !== false)
$s = str_replace('#' . $fragment,'',$s);
$has_params = ((strpos($s,'?')) ? true : false);
$num_slashes = substr_count($s, '/');
if (! $has_params)
$has_params = ((strpos($s, '&')) ? true : false);
$achar = strpos($s,'?') ? '&' : '?';
$mine = get_my_url();
$myaddr = (($address) ? $address : get_my_address());
$mine_parsed = parse_url($mine);
$s_parsed = parse_url($s);
$url_match = false;
if(isset($mine_parsed['host']) && isset($s_parsed['host']) && $mine_parsed['host'] === $s_parsed['host'])
$url_match = true;
if ($mine && $myaddr && (! $url_match))
$zurl = $s . (($num_slashes >= 3) ? '' : '/') . (($achar === '?') ? '?f=&' : '&') . 'zid=' . urlencode($myaddr);
else
$zurl = $s;
// put fragment at the end
if($fragment)
$zurl .= '#' . $fragment;
$arr = [
'url' => $s,
'zid' => urlencode($myaddr),
'result' => $zurl
];
/**
* @hooks zid
* Called when adding the observer's zid to a URL.
* * \e string \b url - url to accept zid
* * \e string \b zid - urlencoded zid
* * \e string \b result - the return string we calculated, change it if you want to return something else
*/
call_hooks('zid', $arr);
return $arr['result'];
function zid(string $url, string $address = ''): string
{
return Url::zid($url, $address);
}
function strip_query_param($s, $param) {
return drop_query_params($s, [$param]);
//return preg_replace('/[\?&]' . $param . '=(.*?)(&|$)/ism','$2',$s);
@@ -277,13 +223,10 @@ function red_zrl_callback($matches) {
$matches[2] = $t;
}
if($matches[1] === '#^')
$matches[1] = '';
if($zrl)
return $matches[1] . '#^[zrl=' . $matches[2] . ']' . $matches[2] . '[/zrl]' . $pts[0];
return $matches[1] . '[zrl=' . $matches[2] . ']' . $matches[2] . '[/zrl]' . $pts[0];
return $matches[1] . '#^[url=' . $matches[2] . ']' . $matches[2] . '[/url]' . $pts[0];
return $matches[1] . '[url=' . $matches[2] . ']' . $matches[2] . '[/url]' . $pts[0];
}
/**

View File

@@ -4,7 +4,7 @@
"type": "@type",
"proof": {
"@id": "https://w3id.org/security#proof",
"@type": "@id",
"@type": "@id"
},
"DataIntegrityProof": {
"@id": "https://w3id.org/security#DataIntegrityProof"
@@ -35,19 +35,19 @@
},
"assertionMethod": {
"@id": "https://w3id.org/security#assertionMethod",
"@type": "@id",
"@type": "@id"
},
"authentication": {
"@id": "https://w3id.org/security#authenticationMethod",
"@type": "@id",
"@type": "@id"
},
"capabilityInvocation": {
"@id": "https://w3id.org/security#capabilityInvocationMethod",
"@type": "@id",
"@type": "@id"
},
"capabilityDelegation": {
"@id": "https://w3id.org/security#capabilityDelegationMethod",
"@type": "@id",
"@type": "@id"
},
"keyAgreement": {
"@id": "https://w3id.org/security#keyAgreementMethod",

View File

@@ -10,8 +10,10 @@
namespace Zotlabs\Tests\Unit;
use PHPUnit\Framework\Attributes\BackupStaticProperties;
use App;
use PHPUnit\Framework\Attributes\BackupStaticProperties;
use PHPUnit\Framework\Attributes\DataProvider;
use Zotlabs\Extend\Hook;
#[BackupStaticProperties(App::class)]
class CallHooksTest extends UnitTestCase {
@@ -27,39 +29,71 @@ class CallHooksTest extends UnitTestCase {
$this->assertHookInvoked();
}
public function test_static_class_function_as_string(): void {
insert_hook('test_hook', 'Zotlabs\Tests\Unit\CallHooksTest::static_test_hook');
#[DataProvider('hookProvider')]
public function testOldInsertHookApi(mixed $hook): void {
insert_hook('test_hook', $hook);
$this->assertHookInvoked();
}
public function test_static_class_function_as_array(): void {
insert_hook('test_hook', ['Zotlabs\Tests\Unit\CallHooksTest', 'static_test_hook']);
#[DataProvider('hookProvider')]
public function testNewHookInsertApi(mixed $hook): void {
Hook::insert('test_hook', $hook);
$this->assertHookInvoked();
}
public function test_static_class_function_as_serialized_array(): void {
insert_hook('test_hook', serialize(['Zotlabs\Tests\Unit\CallHooksTest', 'static_test_hook']));
#[DataProvider('hookProvider')]
public function testNewHookRegisterApi(mixed $hook): void {
Hook::register('test_hook', __FILE__, $hook);
load_hooks();
$this->assertHookInvoked();
Hook::unregister('test_hook', __FILE__, $hook);
load_hooks();
$this->assertNotHookInvoked();
}
public function test_instance_function_as_array(): void {
insert_hook('test_hook', [$this, 'instance_test_hook']);
$this->assertHookInvoked();
}
//
// Helper functions
//
public function assertHookInvoked(): void {
private function invokeHook(): bool {
$test_hook_args = ['called' => false];
call_hooks('test_hook', $test_hook_args);
$this->assertTrue($test_hook_args['called']);
return $test_hook_args['called'];
}
public function instance_test_hook(array &$args): void {
$args['called'] = true;
private function assertHookInvoked(): void {
$this->assertTrue($this->invokeHook());
}
private function assertNotHookInvoked(): void {
$this->assertFalse($this->invokeHook());
}
//
// A static function to invoke via the hook
//
public static function static_test_hook(array &$args): void {
$args['called'] = true;
}
//
// Data provider for the hook tests
//
public static function hookProvider(): array {
return [
'hook is static class function as string' => [
'Zotlabs\Tests\Unit\CallHooksTest::static_test_hook'
],
'hook is static class function as array' => [
['Zotlabs\Tests\Unit\CallHooksTest', 'static_test_hook']
],
];
}
}

View File

@@ -19,9 +19,41 @@ class CleanupBBCodeTest extends UnitTestCase {
public static function cleanup_bbcode_provider(): array {
return [
'url followed by newline' => [
"#^[url=https://example.com]https://example.com[/url]\na test link",
"[url=https://example.com]https://example.com[/url]\na test link",
"https://example.com\na test link",
]
],
'bookmarked url' => [
"#^[url=https://example.com]https://example.com[/url] a test link",
"#^https://example.com a test link",
],
'naked url followed by question mark' => [
"Is this the link [url=https://example.com]https://example.com[/url]?",
"Is this the link https://example.com?",
],
'naked url with query params' => [
"Is this the link [url=https://example.com?arg1=42]https://example.com?arg1=42[/url]",
"Is this the link https://example.com?arg1=42",
],
//
// This scenario does not work. Not sure if it's worth the bother
//
// 'naked url with query params followed by question mark' => [
// "Is this the link [url=https://example.com?arg1=42]https://example.com?arg1=42[/url]?",
// "Is this the link https://example.com?arg1=42?",
// ],
//
'naked IPv6 url with port number' => [
"[url=https://[2001:db8:85a3:8d3:1319:8a2e:370:7348]:443/]https://[2001:db8:85a3:8d3:1319:8a2e:370:7348]:443/[/url]",
"https://[2001:db8:85a3:8d3:1319:8a2e:370:7348]:443/",
],
'naked url with zid turn to zrl' => [
"[zrl=https://example.com/?f=]https://example.com/?f=[/zrl]",
"https://example.com/?f=&zid=kjhkjhkjh",
],
'naked IPv6 url with zid' => [
"[zrl=https://[2001:db8:85a3:8d3:1319:8a2e:370:7348]:443/]https://[2001:db8:85a3:8d3:1319:8a2e:370:7348]:443/[/zrl]",
"https://[2001:db8:85a3:8d3:1319:8a2e:370:7348]:443/?zid=kjbkjbkjb",
],
];
}
}

View File

@@ -0,0 +1,106 @@
<?php
/*
* SPDX-FileCopyrightText: 2026 The Hubzilla Community
* SPDX-FileContributor: Harald Eilertsen <haraldei@anduin.net>
*
* SPDX-License-Identifier: MIT
*/
namespace Zotlabs\Tests\Unit\Lib;
use Zotlabs\Lib\Crypto;
use Zotlabs\Lib\LDSignatures;
use Zotlabs\Tests\Unit\UnitTestCase;
class LDSignaturesTest extends UnitTestCase {
public function testVerifyLDSignature(): void {
$channel = [
'channel_address' => 'test',
'channel_prvkey' => <<<'EOD'
-----BEGIN PRIVATE KEY-----
MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEAvUNHsxNNL8egKxBl
rQdhdKz7N1DfJ8yMAYGPizta9uZ9uoe2qbgYpFPP41gbWvCJqDptmRCWXVZnmH6E
Pe6pLwIDAQABAkBtvgJoKsv55YXREqvyPbJbxiXQuFr9J9US1n5WXG9tc8+S1SB3
Azh7GtORAVnFkba5Ruj/Qij+CLe1ggCkwu0pAiEA41HXbZzQbb4hOxB9mkVvlMYj
r8UqOEtbvKEpnCUAeLUCIQDVJD8BnuCLKRxdtJRbWjwSe++/czwCTVTFv+XIyXX8
0wIgMx6qhZnoPWWub2vr8w9+YkSUreh28CXyQV80zkp76qkCIQCA1X34Ps6/j0QE
KCkc5vg0vBF5CfCV+6RoO8xrh8r33QIhANFLEX+THSmi6s+/d3rUHRqj7cTzJdHh
31v2ixfbFhB6
-----END PRIVATE KEY-----
EOD,
'channel_pubkey' => <<<'EOD'
-----BEGIN PUBLIC KEY-----
MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAL1DR7MTTS/HoCsQZa0HYXSs+zdQ3yfM
jAGBj4s7WvbmfbqHtqm4GKRTz+NYG1rwiag6bZkQll1WZ5h+hD3uqS8CAwEAAQ==
-----END PUBLIC KEY-----
EOD,
];
$activity = [
'@context' => 'https://www.w3.org/ns/activitystreams',
'type' => 'Like',
'actor' => 'https://hubzilla.test/channel/test',
'to' => [
'https://hatchat.example/sarah/',
'https://example.com/peeps/john/',
],
'object' => [
'@context' => ['@language' => 'en'],
'id' => 'https://example.org/~alice/note/23',
'type' => 'Note',
'attributedTo' => 'https://example.org/~alice',
'content' => "I'm a goat",
],
'signature' => [
'@context' => [
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1'
],
'type' => 'RsaSignature2017',
'nonce' => 'e3158dce627da99138d238805d2f129db05bd65efce0f712de494565c852f7fc',
'created' => '2026-05-01T09:22:05Z',
'creator' => 'https://hubzilla.test/channel/test',
'signatureValue' =>
'iFo/j+sG4h7zDiVis8FMOaJvw7ZanbsPdUrS5WTUkcMzL/dg3xPPpt9pKREhVx73ne6y6tIjzq6TWzWEpYvUFg=='
],
];
$this->assertTrue(LDSignatures::verify($activity, $channel['channel_pubkey']));
// Verify that the signature is independent of the order
// of the fields
$activity_2 = [
'type' => 'Like',
'@context' => 'https://www.w3.org/ns/activitystreams',
'actor' => 'https://hubzilla.test/channel/test',
'to' => [
'https://example.com/peeps/john/',
'https://hatchat.example/sarah/',
],
'signature' => [
'nonce' => 'e3158dce627da99138d238805d2f129db05bd65efce0f712de494565c852f7fc',
'@context' => [
'https://www.w3.org/ns/activitystreams',
'https://w3id.org/security/v1'
],
'created' => '2026-05-01T09:22:05Z',
'type' => 'RsaSignature2017',
'creator' => 'https://hubzilla.test/channel/test',
'signatureValue' =>
'iFo/j+sG4h7zDiVis8FMOaJvw7ZanbsPdUrS5WTUkcMzL/dg3xPPpt9pKREhVx73ne6y6tIjzq6TWzWEpYvUFg=='
],
'object' => [
'@context' => ['@language' => 'en'],
'type' => 'Note',
'attributedTo' => 'https://example.org/~alice',
'id' => 'https://example.org/~alice/note/23',
'content' => "I'm a goat",
],
];
$this->assertFalse($activity == $activity_2);
$this->assertTrue(LDSignatures::verify($activity_2, $channel['channel_pubkey']));
}
}

View File

@@ -28,6 +28,11 @@ class MessageFilterTest extends UnitTestCase {
'obj' => [
'type' => 'Note',
'attributedTo' => 'https://example.com/users/test',
// this field does not realy exist but above attributedTo can also be an array
'attributedToArray' => [
['type' => 'Group', 'id' =>'https://example.com/group/somegroup'],
['type' => 'Person', 'id' =>'https://example.com/users/test']
],
'summary' => null,
'content' => "A grasshopper spent the summer hopping about in the sun and singing to his heart's content. One day, an ant went hurrying by, looking very hot and weary.\r\n#story #grasshopper #ant",
'sensitive' => false
@@ -212,6 +217,11 @@ class MessageFilterTest extends UnitTestCase {
'',
true
],
'obj.attributedToArray contains test in incl' => [
'?+attributedToArray ~= test',
'',
false // we can not compare arrays with strings - hence false
],
'obj.sensitive = true in incl' => [
'?+sensitive',
'',

109
tests/unit/Lib/UrlTest.php Normal file
View File

@@ -0,0 +1,109 @@
<?php
/*
* SPDX-FileCopyrightText: 2026 The Hubzilla Community
* SPDX-FileContributor: Mario Vavti <mario@mariovavti.com>
*
* SPDX-License-Identifier: MIT
*/
namespace Zotlabs\Tests\Unit;
use Zotlabs\Lib\Url;
use phpmock\phpunit\PHPMock;
class UrlTest extends UnitTestCase {
use PHPMock;
/**
* Test Url::zid()
*
* @dataProvider zid_test_provider
*/
public function test_zid(string $url, string $addr, string $expected) : void {
// Mock get_my_url()
$getMyUrl = $this->getFunctionMock('Zotlabs\Lib', 'get_my_url');
$getMyUrl->expects($this->atMost(1))
->willReturn('https://example.org/channel/visitor');
$getMyAdr = $this->getFunctionMock('Zotlabs\Lib', 'get_my_address');
$getMyAdr->expects($this->atMost(1))
->willReturn('visitor@example.org');
$this->assertEquals(urldecode(Url::zid($url, $addr)), $expected);
}
public static function zid_test_provider() : array {
return [
// URL without params
['https://example.net/channel/test', 'visitor@example.org', 'https://example.net/channel/test?zid=visitor@example.org'],
// URL with args
['https://example.net/channel/test?t=test', 'visitor@example.org', 'https://example.net/channel/test?t=test&zid=visitor@example.org'],
// URL with fragment
['https://example.net/channel/test#fragment', 'visitor@example.org', 'https://example.net/channel/test?zid=visitor@example.org#fragment'],
// URL with args and fragment
['https://example.net/channel/test?t=test#fragment', 'visitor@example.org', 'https://example.net/channel/test?t=test&zid=visitor@example.org#fragment'],
// URL with zid
['https://example.net/channel/test?zid=visitor@example.org', 'visitor@example.org', 'https://example.net/channel/test?zid=visitor@example.org'],
// No addr provided
['https://example.net/channel/test', '', 'https://example.net/channel/test?zid=visitor@example.org'],
// Same host, no addr
['https://example.org/channel/test', '', 'https://example.org/channel/test'],
];
}
/**
* Test Url::unparse()
*
*/
public function test_unparse_full()
{
$parsed_url = [
'scheme' => 'https',
'host' => 'www.example.com',
'port' => '8080',
'user' => 'username',
'pass' => 'password',
'path' => '/path',
'query' => 'param=value',
'fragment' => 'section'
];
$expected = 'https://username:password@www.example.com:8080/path?param=value#section';
$this->assertEquals($expected, Url::unparse($parsed_url));
}
public function test_unparse_partial()
{
$parsed_url = [
'scheme' => 'http',
'host' => 'example.com',
'path' => '/index.php'
];
$expected = 'http://example.com/index.php';
$this->assertEquals($expected, Url::unparse($parsed_url));
}
public function test_unparse_custom()
{
$parsed_url = [
'scheme' => 'https',
'host' => 'www.example.com',
'port' => '443',
'path' => '/api'
];
$parts = ['scheme', 'host'];
$expected = 'https://www.example.com';
$this->assertEquals($expected, Url::unparse($parsed_url, $parts));
}
public function test_unparse_empty()
{
$this->assertEquals('', Url::unparse([]));
}
}

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\Tests\Unit\Photo;
use PHPUnit\Framework\TestCase;
use Zotlabs\Photo\PhotoImagick;
class PhotoMagickTest extends TestCase
{
public function testSupportedFormats(): void {
$ph = new PhotoImagick(null, null);
$types = $ph->supportedTypes();
$this->assertEquals('webp', $types['image/webp']);
$this->assertEquals('avif', $types['image/avif']);
$this->assertEquals('gif', $types['image/gif']);
$this->assertEquals('jpg', $types['image/jpeg']);
$this->assertEquals('png', $types['image/png']);
}
public function testInitializingWithImage(): void {
$data = file_get_contents('images/hz-16.png');
// If no type is given, make it a jpeg, regardless of input format.
$ph = new PhotoImagick($data);
$this->assertEquals('image/jpeg', $ph->getType());
$formats = $ph->supportedTypes();
// When a type is givem the image should be given that type.
foreach($formats as $format => $ext) {
$ph = new PhotoImagick($data, $format);
$this->assertEquals($format, $ph->getType());
}
}
}

View File

@@ -68,59 +68,6 @@ class NetworkTest extends Zotlabs\Tests\Unit\UnitTestCase {
];
}
/**
* Test the unparse_url function.
*
*/
public function test_unparse_url_full()
{
$parsed_url = [
'scheme' => 'https',
'host' => 'www.example.com',
'port' => '8080',
'user' => 'username',
'pass' => 'password',
'path' => '/path',
'query' => 'param=value',
'fragment' => 'section'
];
$expected = 'https://username:password@www.example.com:8080/path?param=value#section';
$this->assertEquals($expected, unparse_url($parsed_url));
}
public function test_unparse_url_partial()
{
$parsed_url = [
'scheme' => 'http',
'host' => 'example.com',
'path' => '/index.php'
];
$expected = 'http://example.com/index.php';
$this->assertEquals($expected, unparse_url($parsed_url));
}
public function test_unparse_url_custom()
{
$parsed_url = [
'scheme' => 'https',
'host' => 'www.example.com',
'port' => '443',
'path' => '/api'
];
$parts = ['scheme', 'host'];
$expected = 'https://www.example.com';
$this->assertEquals($expected, unparse_url($parsed_url, $parts));
}
public function test_unparse_url_empty()
{
$this->assertEquals('', unparse_url([]));
}
/**
* Test that the parse_webbie function.
*

View File

@@ -0,0 +1,72 @@
<?php
/*
* SPDX-FileCopyrightText: 2026 The Hubzilla Community
* SPDX-FileContributor: Harald Eilertsen <haraldei@anduin.net>
*
* SPDX-License-Identifier: MIT
*/
namespace Zotlabs\Tests\Unit\includes;
use App;
use PHPUnit\Framework\Attributes\BackupStaticProperties;
use Zotlabs\Tests\Unit\UnitTestCase;
require_once 'include/opengraph.php';
#[BackupStaticProperties('App')]
class OpenGraphTest extends UnitTestCase
{
public function testWithEmptyItem(): void {
App::$profile['about'] = false;
$channel = [
'channel_name' => 'test',
'xchan_photo_l' => z_root() . '/photos/1',
'xchan_photo_mimetype' => 'image/jpeg',
];
opengraph_add_meta(null, $channel);
$this->assertMetaOGTag('og:title', 'test');
$this->assertMetaOGTag('og:image', z_root() . '/photos/1');
$this->assertMetaOGTag('og:image:type', 'image/jpeg');
$this->assertMetaOGTag('og:description', 'This is the home page of test.');
$this->assertMetaOGTag('og:type', 'profile');
}
public function testWithPostItem(): void {
App::$profile['about'] = false;
$channel = [
'channel_name' => 'test',
'xchan_photo_l' => z_root() . '/photos/1',
'xchan_photo_mimetype' => 'image/jpeg',
];
$item = [
'title' => 'The post title',
'body' => '[zmg=' . z_root() . '/some_image.jpg]An image with alt text[/zmg]',
'summary' => 'A summary of the post',
];
opengraph_add_meta($item, $channel);
$this->assertMetaOGTag('og:title', 'The post title');
$this->assertMetaOGTag('og:image', z_root() . '/some_image.jpg');
//
// Image type is empty because `guess_image_type()` won't be able
// to locate the image, and we can't mock the function as it's not
// called from a namespace.
//
$this->assertMetaOGTag('og:image:type', '');
$this->assertMetaOGTag('og:description', 'A summary of the post');
$this->assertMetaOGTag('og:type', 'article');
}
private function assertMetaOGTag(string $tag, string $content): void {
$this->assertStringContainsString(
"<meta property=\"{$tag}\" content=\"{$content}\">",
App::$page['htmlhead']);
}
}

View File

@@ -118,37 +118,20 @@ channel:
channel_startpage: ""
channel_pubkey: |
-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAn2ads2RKygqTbN0kxuGI
3Vuh/1SSvHls2cYMXZJpwqix5+y2/zqqzAVUayMCPt1ZHLfc+uG0Z97+XmWNuuIo
RCBB5Os29NKo4+K66prYlfBFeuvji9tV1zGwBMYMC2RabITIjH9yazE+Tox54iV+
kw/IYPsnv4vPIcuC8dnvZ0v7/Kkhm6Hp0ZrGRNKccHuinJeiuhvfXXG3xrrBrMY7
iw7I3i2hvw/BSFdJ1RnuMGlr2W3qcZQvlqCWIGAzEpmXom9bwBKo/ZZ2MSJCgEVY
KKPqjLs/7IKRzlzUFIuk8Uibrhoxmc0NaqIeQOUtyelFdc/P3p2fEjwJd7sr8dzo
NFVgUMHGiqu0C5fZ90KnfLNrXJcMb4VjsvGFyIDt0AZY5w4rjoKmjlB/p4/CRfyz
qFnp2ZK4eRPqurmGwnSArsjPLMhWjlc7NRXtQ1nyMNAEi4RRhlJyjmvJIRrdeVu2
Nq6ZknBq0SeDlH/3uRfNPwHxqgDFNTCgsg4/Lgj2au1XbaEBlgRq3PDAh4a3H+fO
iGKinJy2/p9TGvrKTcO3mremHbuVg41G6FjxvzuZKx3P7ssN4KBlCJ9L/tJX5RDn
4m5u4ZdaPZBXxjgXj24SDLETaz+K0DxwtjGQ7kTpC0zv6hLcEGxS2220cX7Msgih
pWX+ItWfet/uLkmfyCpaxt0CAwEAAQ==
MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAL1DR7MTTS/HoCsQZa0HYXSs+zdQ3yfM
jAGBj4s7WvbmfbqHtqm4GKRTz+NYG1rwiag6bZkQll1WZ5h+hD3uqS8CAwEAAQ==
-----END PUBLIC KEY-----
channel_prvkey: |
-----BEGIN PRIVATE KEY-----
MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQCfZp2zZErKCpNs
3STG4YjdW6H/VJK8eWzZxgxdkmnCqLHn7Lb/OqrMBVRrIwI+3Vkct9z64bRn3v5e
ZY264ihEIEHk6zb00qjj4rrqmtiV8EV66+OL21XXMbAExgwLZFpshMiMf3JrMT5O
jHniJX6TD8hg+ye/i88hy4Lx2e9nS/v8qSGboenRmsZE0pxwe6Kcl6K6G99dcbfG
usGsxjuLDsjeLaG/D8FIV0nVGe4waWvZbepxlC+WoJYgYDMSmZeib1vAEqj9lnYx
IkKARVgoo+qMuz/sgpHOXNQUi6TxSJuuGjGZzQ1qoh5A5S3J6UV1z8/enZ8SPAl3
uyvx3Og0VWBQwcaKq7QLl9n3Qqd8s2tclwxvhWOy8YXIgO3QBljnDiuOgqaOUH+n
j8JF/LOoWenZkrh5E+q6uYbCdICuyM8syFaOVzs1Fe1DWfIw0ASLhFGGUnKOa8kh
Gt15W7Y2rpmScGrRJ4OUf/e5F80/AfGqAMU1MKCyDj8uCPZq7VdtoQGWBGrc8MCH
hrcf586IYqKcnLb+n1Ma+spNw7eat6Ydu5WDjUboWPG/O5krHc/uyw3goGUIn0v+
0lflEOfibm7hl1o9kFfGOBePbhIMsRNrP4rQPHC2MZDuROkLTO/qEtwQbFLbbbRx
fsyyCKGlZf4i1Z963+4uSZ/IKlrG3QIDAQABAoICAAGXQ9gpllXVYgV1CzwBPhKI
KqynyAN3CnHS+EZ1IraVZJnTYuaBtqOCIxv4Sw+xua+1GxDARhgU9utzboUbYuT+
gGyGjMjH6HUbyxtHz9pmkz7ykesQVZc9UPZ/O9lfl0fG0dHrJ2BwAEvxkCQvFR1m
doIXR50xtJpvqPR9JRaeFhtTsc5B667n8Ut4YaTSwGZOh3UoODllxucwxSQ9DJTq
9nDB5P/JP7fXMoQHYKPy4
MIIBVQIBADANBgkqhkiG9w0BAQEFAASCAT8wggE7AgEAAkEAvUNHsxNNL8egKxBl
rQdhdKz7N1DfJ8yMAYGPizta9uZ9uoe2qbgYpFPP41gbWvCJqDptmRCWXVZnmH6E
Pe6pLwIDAQABAkBtvgJoKsv55YXREqvyPbJbxiXQuFr9J9US1n5WXG9tc8+S1SB3
Azh7GtORAVnFkba5Ruj/Qij+CLe1ggCkwu0pAiEA41HXbZzQbb4hOxB9mkVvlMYj
r8UqOEtbvKEpnCUAeLUCIQDVJD8BnuCLKRxdtJRbWjwSe++/czwCTVTFv+XIyXX8
0wIgMx6qhZnoPWWub2vr8w9+YkSUreh28CXyQV80zkp76qkCIQCA1X34Ps6/j0QE
KCkc5vg0vBF5CfCV+6RoO8xrh8r33QIhANFLEX+THSmi6s+/d3rUHRqj7cTzJdHh
31v2ixfbFhB6
-----END PRIVATE KEY-----
channel_epubkey: "cBYhT+C+ZzzXDLlZh1hEsWzz5HI06TwW6o8HgfyLlFo"
channel_eprvkey: "/ClTxXKn7Lts+O6CgqJ2cCe0FlKpNSZfrBLPaB1Bq2twFiFP4L5nPNcMuVmHWESxbPPkcjTpPBbqjweB/IuUWg"
channel_notifyflags: 65535

View File

@@ -1955,6 +1955,7 @@ return array(
'Zotlabs\\Lib\\ThreadListener' => $baseDir . '/Zotlabs/Lib/ThreadListener.php',
'Zotlabs\\Lib\\ThreadStream' => $baseDir . '/Zotlabs/Lib/ThreadStream.php',
'Zotlabs\\Lib\\Traits\\HelpHelperTrait' => $baseDir . '/Zotlabs/Lib/Traits/HelpHelperTrait.php',
'Zotlabs\\Lib\\Url' => $baseDir . '/Zotlabs/Lib/Url.php',
'Zotlabs\\Lib\\Verify' => $baseDir . '/Zotlabs/Lib/Verify.php',
'Zotlabs\\Lib\\Webfinger' => $baseDir . '/Zotlabs/Lib/Webfinger.php',
'Zotlabs\\Lib\\XConfig' => $baseDir . '/Zotlabs/Lib/XConfig.php',

View File

@@ -2294,6 +2294,7 @@ class ComposerStaticInit7b34d7e50a62201ec5d5e526a5b8b35d
'Zotlabs\\Lib\\ThreadListener' => __DIR__ . '/../..' . '/Zotlabs/Lib/ThreadListener.php',
'Zotlabs\\Lib\\ThreadStream' => __DIR__ . '/../..' . '/Zotlabs/Lib/ThreadStream.php',
'Zotlabs\\Lib\\Traits\\HelpHelperTrait' => __DIR__ . '/../..' . '/Zotlabs/Lib/Traits/HelpHelperTrait.php',
'Zotlabs\\Lib\\Url' => __DIR__ . '/../..' . '/Zotlabs/Lib/Url.php',
'Zotlabs\\Lib\\Verify' => __DIR__ . '/../..' . '/Zotlabs/Lib/Verify.php',
'Zotlabs\\Lib\\Webfinger' => __DIR__ . '/../..' . '/Zotlabs/Lib/Webfinger.php',
'Zotlabs\\Lib\\XConfig' => __DIR__ . '/../..' . '/Zotlabs/Lib/XConfig.php',

View File

@@ -3,7 +3,7 @@
'name' => 'zotlabs/hubzilla',
'pretty_version' => 'dev-11.2RC',
'version' => 'dev-11.2RC',
'reference' => '955ee217e30e12dec86bdcd936ce10abc3ed366a',
'reference' => 'fa7c8e93f1466624ec49170a553d23eee857c70a',
'type' => 'application',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@@ -544,7 +544,7 @@
'zotlabs/hubzilla' => array(
'pretty_version' => 'dev-11.2RC',
'version' => 'dev-11.2RC',
'reference' => '955ee217e30e12dec86bdcd936ce10abc3ed366a',
'reference' => 'fa7c8e93f1466624ec49170a553d23eee857c70a',
'type' => 'application',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),

View File

@@ -1,57 +1,41 @@
Vielen Dank für Ihre Registrierung auf {{$sitename}}.
Vielen Dank für Ihre Registrierung bei {{$sitename}}.
Bitte lesen Sie diese Nachricht aufmerksam durch, bevor Sie eine der beschriebenen Optionen ausführen.
Ihre Anmeldedaten lauten wie folgt:
Die Details für die Anmeldung sind folgende:
Adresse der Seite: {{$siteurl}}
Login-Name: {{$email}}
Portal Adresse: {{$siteurl}}
Login Name: {{$email}}
Melden Sie sich mit dem Passwort an, das Sie bei der Registrierung gewählt
haben.
Die Anmeldung erfolgt mit dem von Ihnen bei der Registrierung festgelegten Kennwort. Falls Sie bereits bei der
Registrierung einen Nicknamen für Ihren Kanal festgelegt hatten, können Sie den alternativ zur Email Adresse als
Login Name benutzen. Falls noch kein Kanal besteht, werden Sie den unmittelbar nach der nächsten Anmeldung
festlegen.
Wir müssen Ihre E-Mail-Adresse verifizieren, um Ihnen vollen Zugriff zu
gewähren.
Um Ihnen vollständigen Zugriff geben zu können, benötigen wir zur Überprüfung die Bestätigung Ihrer Email Adresse.
Der Prüfungscode ist:
Ihr Bestätigungscode lautet
{{$hash}}
{{if $due}}{{$due}}{{/if}}
{{if $timeframe}}
Dieser Code ist von {{$timeframe.0}} UTC bis {{$timeframe.1}} UTC gültig.
Wir gehen davon aus, dass Sie dieses Anmeldkonto registriert haben und bitten Sie, den Prüungscode auf der folgenden Adresse
zu bestätigen:
{{/if}}
Wenn Sie dieses Konto registriert haben, geben Sie bitte den Bestätigungscode
ein, wenn Sie dazu aufgefordert werden, oder klicken Sie auf den folgenden
Link:
{{$siteurl}}/regate/{{$mail}}
Leider kommt es auch vor, dass Email Adressen missbräuchlich verwendet werden. Auf vielen Portalen im Internet können Böswillige versuchen, durch Eingabe einer beliebigen bekannten Email Adresse eine Benutzerkonto-Registrierung einzuleiten. Sollten Sie die
Registrierung selber nicht beabsichtigt haben, raten wir aus Sicherheitsgründen dringend davon ab, den Vorgang zu bestätigen. Das
gilt auch dann, wenn Sie nach Besuch des Portals nicht abgeneigt sein sollten, dort Zugang zu erhalten. Ein Böswiller hat die
Registrierung vielleicht schon soweit vorbereitet, dass eine Bestätigung nicht Ihnen, sondern letztlich Unberechtigten Zugang
zum Portal bewährt.
Im Fall einer mißbräuchlich versuchten Registrierung können Sie uns helfen, indem Sie den Registrierungsvorgang stornieren.
Zur Stornierung und Zurückweisung der Registrierung steht folgende Portal Adresse zur Verfügung:
Um den Antrag abzulehnen und das Konto zu löschen, besuchen Sie bitte:
{{$siteurl}}/regate/{{$mail}}{{if $ko}}/{{$ko}}{{/if}}
{{$siteurl}}/regate/{{$mail}}{{if $ko}}/{{$ko}}{{/if}}
Vielen Dank!
Falls Sie über die mißbräuchliche Nutzung Ihrer Email Adresse verunsichert sein sollten und Bedenken gegen die Durchführung der
Stornierung haben, möchten wir Sie beruhigen und vergewissern, das der Registrierungsvorgang nach einer gewissen Zeit
automatisch storniert wird. Jedenfalls bedauern wir die Unannehmlichkeiten.
Vielen Dank für Ihre Aufwerksamkeit und Kooperation.
Die Betreiber der Platform
--
Datenschutzerklärung,Terms Of Service:
Nutzungsbedingungen:
{{$siteurl}}/help/TermsOfService

View File

@@ -10,23 +10,26 @@ Login with the password you chose at registration.
We need to verify your email address in order to give you full access.
Your validation code is
Your verification token is
{{$hash}}
{{if $timeframe}}
This token is valid from {{$timeframe.0}} UTC until {{$timeframe.1}} UTC
{{/if}}
If you registered this account, please enter the validation code when requested or visit the following link:
{{$siteurl}}/regver/allow/{{$hash}}
{{$siteurl}}/regate/{{$mail}}
To deny the request and remove the account, please visit:
{{$siteurl}}/regver/deny/{{$hash}}
{{$siteurl}}/regate/{{$mail}}{{if $ko}}/{{$ko}}{{/if}}
Thank you.
Thank you!
--

View File

@@ -10,23 +10,26 @@ Login with the password you chose at registration.
We need to verify your email address in order to give you full access.
Your validation code is
Your verification token is
{{$hash}}
{{if $timeframe}}
This token is valid from {{$timeframe.0}} UTC until {{$timeframe.1}} UTC
{{/if}}
If you registered this account, please enter the validation code when requested or visit the following link:
{{$siteurl}}/regver/allow/{{$hash}}
{{$siteurl}}/regate/{{$mail}}
To deny the request and remove the account, please visit:
{{$siteurl}}/regver/deny/{{$hash}}
{{$siteurl}}/regate/{{$mail}}{{if $ko}}/{{$ko}}{{/if}}
Thank you.
Thank you!
--

View File

@@ -1,7 +1,7 @@
# hubzilla
# Copyright (C) 2012-2016 hubzilla
# This file is distributed under the same license as the hubzilla package.
#
#
# Translators:
# Alfonso Martínez, 2015
# inboxwall <axetransit@gmail.com>, 2015
@@ -1457,9 +1457,8 @@ msgstr "%1$sactualizó el %2$s. Ha cambiado %3$s."
#, php-format
msgid "%d invitation available"
msgid_plural "%d invitations available"
msgstr[0] "%d invitación pendiente"
msgstr[0] "%d invitación disponible"
msgstr[1] "%d invitaciones disponibles"
msgstr[2] "%d invitaciones disponibles"
#: ../../include/contact_widgets.php:18 ../../include/acl_selectors.php:145
#: ../../Zotlabs/Module/Admin/Site.php:407
@@ -1654,7 +1653,6 @@ msgid "Like"
msgid_plural "Likes"
msgstr[0] "Me gusta"
msgstr[1] "Me gusta"
msgstr[2] "Me gusta"
#: ../../include/photo/photo_driver.php:439
#: ../../Zotlabs/Module/Profile_photo.php:168
@@ -1901,7 +1899,6 @@ msgid "%d minutes"
msgid_plural "%d minutes"
msgstr[0] "%d minutos"
msgstr[1] "%d minutos"
msgstr[2] "%d minutos"
#: ../../include/js_strings.php:46
#, php-format
@@ -1909,7 +1906,6 @@ msgid "about %d hours"
msgid_plural "about %d hours"
msgstr[0] "alrededor de %d horas"
msgstr[1] "alrededor de %d horas"
msgstr[2] "alrededor de %d horas"
#: ../../include/js_strings.php:47
#, php-format
@@ -1917,7 +1913,6 @@ msgid "%d days"
msgid_plural "%d days"
msgstr[0] "%d días"
msgstr[1] "%d días"
msgstr[2] "%d días"
#: ../../include/js_strings.php:48
#, php-format
@@ -1925,7 +1920,6 @@ msgid "%d months"
msgid_plural "%d months"
msgstr[0] "%d meses"
msgstr[1] "%d meses"
msgstr[2] "%d meses"
#: ../../include/js_strings.php:49
#, php-format
@@ -1933,7 +1927,6 @@ msgid "%d years"
msgid_plural "%d years"
msgstr[0] "%d años"
msgstr[1] "%d años"
msgstr[2] "%d años"
#: ../../include/js_strings.php:51 ../../include/text.php:1525
msgid "January"
@@ -2344,7 +2337,6 @@ msgid "<span %1$s>%2$d people</span> like this."
msgid_plural "<span %1$s>%2$d people</span> like this."
msgstr[0] "a <span %1$s>%2$d personas</span> le gusta esto."
msgstr[1] "A <span %1$s>%2$d personas</span> les gusta esto."
msgstr[2] "A <span %1$s>%2$d personas</span> les gusta esto."
#: ../../include/conversation.php:955
#, php-format
@@ -2352,7 +2344,6 @@ msgid "<span %1$s>%2$d people</span> don't like this."
msgid_plural "<span %1$s>%2$d people</span> don't like this."
msgstr[0] "a <span %1$s>%2$d personas</span> no les gusta esto."
msgstr[1] "A <span %1$s>%2$d personas</span> no les gusta esto."
msgstr[2] "A <span %1$s>%2$d personas</span> no les gusta esto."
#: ../../include/conversation.php:961
msgid "and"
@@ -2364,7 +2355,6 @@ msgid ", and %d other people"
msgid_plural ", and %d other people"
msgstr[0] ", y %d persona más"
msgstr[1] ", y %d personas más"
msgstr[2] ", y %d personas más"
#: ../../include/conversation.php:965
#, php-format
@@ -2621,8 +2611,7 @@ msgctxt "noun"
msgid "Repeat"
msgid_plural "Repeats"
msgstr[0] "Se repite"
msgstr[1] "Se repite"
msgstr[2] "Repite"
msgstr[1] "Repite"
#: ../../include/conversation.php:1446 ../../Zotlabs/Module/Photos.php:1123
msgctxt "noun"
@@ -2630,7 +2619,6 @@ msgid "Dislike"
msgid_plural "Dislikes"
msgstr[0] "No me gusta"
msgstr[1] "No me gusta"
msgstr[2] "No me gusta"
#: ../../include/conversation.php:1449
msgctxt "noun"
@@ -2638,7 +2626,6 @@ msgid "Comment"
msgid_plural "Comments"
msgstr[0] "Comentar"
msgstr[1] "Comentarios"
msgstr[2] "Comentarios"
#: ../../include/conversation.php:1449
msgctxt "noun"
@@ -2646,7 +2633,6 @@ msgid "Reply"
msgid_plural "Replies"
msgstr[0] "Responder"
msgstr[1] "Respuestas"
msgstr[2] "Respuestas"
#: ../../include/conversation.php:1452
msgctxt "noun"
@@ -2654,7 +2640,6 @@ msgid "Attending"
msgid_plural "Attending"
msgstr[0] "Participaré"
msgstr[1] "Participaré"
msgstr[2] "Participaré"
#: ../../include/conversation.php:1455
msgctxt "noun"
@@ -2662,7 +2647,6 @@ msgid "Not attending"
msgid_plural "Not attending"
msgstr[0] "No participaré"
msgstr[1] "No participaré"
msgstr[2] "No participaré"
#: ../../include/conversation.php:1458
msgctxt "noun"
@@ -2670,7 +2654,6 @@ msgid "Undecided"
msgid_plural "Undecided"
msgstr[0] "Indeciso/a"
msgstr[1] "Indecisos/as"
msgstr[2] "Indecisos/as"
#: ../../include/nav.php:109
msgid "Remote authentication"
@@ -2936,7 +2919,6 @@ msgid "year"
msgid_plural "years"
msgstr[0] "año"
msgstr[1] "años"
msgstr[2] "años"
#: ../../include/datetime.php:315
msgctxt "relative_date"
@@ -2944,7 +2926,6 @@ msgid "month"
msgid_plural "months"
msgstr[0] "mes"
msgstr[1] "meses"
msgstr[2] "meses"
#: ../../include/datetime.php:318
msgctxt "relative_date"
@@ -2952,7 +2933,6 @@ msgid "week"
msgid_plural "weeks"
msgstr[0] "semana"
msgstr[1] "semanas"
msgstr[2] "semanas"
#: ../../include/datetime.php:321
msgctxt "relative_date"
@@ -2960,7 +2940,6 @@ msgid "day"
msgid_plural "days"
msgstr[0] "día"
msgstr[1] "días"
msgstr[2] "días"
#: ../../include/datetime.php:324
msgctxt "relative_date"
@@ -2968,7 +2947,6 @@ msgid "hour"
msgid_plural "hours"
msgstr[0] "hora"
msgstr[1] "horas"
msgstr[2] "horas"
#: ../../include/datetime.php:327
msgctxt "relative_date"
@@ -2976,7 +2954,6 @@ msgid "minute"
msgid_plural "minutes"
msgstr[0] "minuto"
msgstr[1] "minutos"
msgstr[2] "minutos"
#: ../../include/datetime.php:330
msgctxt "relative_date"
@@ -2984,7 +2961,6 @@ msgid "second"
msgid_plural "seconds"
msgstr[0] "segundo"
msgstr[1] "segundos"
msgstr[2] "segundos"
#: ../../include/datetime.php:559
#, php-format
@@ -3312,18 +3288,16 @@ msgstr "Descargar contenido binario o cifrado"
msgctxt "noun"
msgid "%d Vote"
msgid_plural "%d Votes"
msgstr[0] "%dVoto"
msgstr[1] "%dVotos"
msgstr[2] "%dVotos"
msgstr[0] "%d Voto"
msgstr[1] "%d Votos"
#: ../../include/text.php:2018
#, php-format
msgctxt "noun"
msgid "%d Vote in total"
msgid_plural "%d Votes in total"
msgstr[0] "%dVoto en total"
msgstr[1] "%dVotos en total"
msgstr[2] "%dVotos en total"
msgstr[0] "%d Voto en total"
msgstr[1] "%d Votos en total"
#: ../../include/text.php:2024
msgid "Poll has ended"
@@ -7297,7 +7271,6 @@ msgid "%d comment"
msgid_plural "%d comments"
msgstr[0] "%d comentario"
msgstr[1] "%d comentarios"
msgstr[2] "%d comentarios"
#: ../../Zotlabs/Lib/ThreadItem.php:332
#, php-format
@@ -8154,7 +8127,6 @@ msgid "new connection"
msgid_plural "new connections"
msgstr[0] "Nueva conexión"
msgstr[1] "Nuevas conexiones"
msgstr[2] "nuevas conexiones"
#: ../../Zotlabs/Widget/Channel_activities.php:220
msgctxt "noun"
@@ -8162,7 +8134,6 @@ msgid "notice"
msgid_plural "notices"
msgstr[0] "aviso"
msgstr[1] "avisos"
msgstr[2] "avisos"
#: ../../Zotlabs/Widget/Affinity.php:36
#: ../../Zotlabs/Module/Contactedit.php:281
@@ -10727,7 +10698,6 @@ msgid "%s account blocked/unblocked"
msgid_plural "%s account blocked/unblocked"
msgstr[0] "%s cuenta bloqueada/desbloqueada"
msgstr[1] "%s cuenta bloqueada/desbloqueada"
msgstr[2] "%s cuenta bloqueada/desbloqueada"
#: ../../Zotlabs/Module/Admin/Accounts.php:389
#, php-format
@@ -10735,7 +10705,6 @@ msgid "%s account deleted"
msgid_plural "%s accounts deleted"
msgstr[0] "%s cuentas eliminadas"
msgstr[1] "%s cuentas eliminadas"
msgstr[2] "%s cuentas eliminadas"
#: ../../Zotlabs/Module/Admin/Queueworker.php:66
msgid "Max queueworker threads"
@@ -10940,7 +10909,6 @@ msgid "%s channel censored/uncensored"
msgid_plural "%s channels censored/uncensored"
msgstr[0] "%s canales censurados/no censurados"
msgstr[1] "%s canales censurados/no censurados"
msgstr[2] "%s canales censurados/no censurados"
#: ../../Zotlabs/Module/Admin/Channels.php:57
#, php-format
@@ -10948,7 +10916,6 @@ msgid "%s channel code allowed/disallowed"
msgid_plural "%s channels code allowed/disallowed"
msgstr[0] "%s código permitido/no permitido al canal"
msgstr[1] "%s código permitido/no permitido al canal"
msgstr[2] "%s código permitido/no permitido al canal"
#: ../../Zotlabs/Module/Admin/Channels.php:63
#, php-format
@@ -10956,7 +10923,6 @@ msgid "%s channel deleted"
msgid_plural "%s channels deleted"
msgstr[0] "%s canales eliminados"
msgstr[1] "%s canales eliminados"
msgstr[2] "%s canales eliminados"
#: ../../Zotlabs/Module/Admin/Channels.php:90
#, php-format

View File

@@ -10,20 +10,24 @@ Inicie la sesión con la contraseña que eligió durante el registro.
Necesitamos verificar su correo electrónico para poder darle pleno acceso.
Su código de validación es
Su código de validación es
{{$hash}}
{{if $timeframe}}
This token is valid from {{$timeframe.0}} UTC until {{$timeframe.1}} UTC
{{/if}}
Si ha registrado esta cuenta, introduzca el código de validación cuando se le solicite o visite el siguiente enlace:
{{$siteurl}}/regver/allow/{{$hash}}
{{$siteurl}}/regate/{{$mail}}
Para rechazar la petición y eliminar la cuenta , siga:
{{$siteurl}}/regver/deny/{{$hash}}
{{$siteurl}}/regate/{{$mail}}{{if $ko}}/{{$ko}}{{/if}}
Gracias.

View File

@@ -1,29 +1,35 @@
Merci de vous être enregistré sur {{$sitename}}.
Voici les détails de connexion :
Adresse du site: {{$siteurl}}
Utilisateur: {{$email}}
Connectez-vous avec le mot de passe que vous avez choisi au moment de l'enregistrement.
Nous devons vérifier votre adresse électronique afin de vous donner un accès complet au réseau.
Votre code de vérification est :
{{$hash}}
{{if $timeframe}}
Ce code est valable de {{$timeframe.0}} UTC jusqu'à {{$timeframe.1}}
{{/if}}
Si vous avez enregistré ce compte, veuillez entrer le code de vérification lorsque cela vous est demandé ou cliquez sur le lien suivant :
{{$siteurl}}/regate/{{$mail}}
Pour refuser la demande et supprimer le compte, merci de vous rendre à cette adresse :
{{$siteurl}}/regate/{{$mail}}{{if $ko}}/{{$ko}}{{/if}}
Merci.
Merci de vous être enregistré sur {{$sitename}}.
Voici les détails de connexion :
Adresse du site: {{$siteurl}}
Utilisateur: {{$email}}
Connectez-vous avec le mot de passe que vous avez choisi au moment de l'enregistrement.
Nous devons vérifier votre adresse électronique afin de vous donner un accès complet au réseau.
Votre code de vérification est :
{{$hash}}
{{if $timeframe}}
Ce code est valable de {{$timeframe.0}} UTC jusqu'à {{$timeframe.1}}
{{/if}}
Si vous avez enregistré ce compte, veuillez entrer le code de vérification lorsque cela vous est demandé ou cliquez sur le lien suivant :
{{$siteurl}}/regate/{{$mail}}
Pour refuser la demande et supprimer le compte, merci de vous rendre à cette adresse :
{{$siteurl}}/regate/{{$mail}}{{if $ko}}/{{$ko}}{{/if}}
Merci.
--
Terms Of Service:
{{$siteurl}}/help/TermsOfService

View File

@@ -10,15 +10,29 @@
עלינו לאמת את כתובת הדוא״ל שלך על מנת להעניק לך גישה מלאה.
Your verification token is
{{$hash}}
{{if $timeframe}}
This token is valid from {{$timeframe.0}} UTC until {{$timeframe.1}} UTC
{{/if}}
אם רשמת את חשבון זה, אנא פנה לקישור הבא:
{{$siteurl}}/regver/allow/{{$hash}}
{{$siteurl}}/regate/{{$mail}}
כדי לדחות את הבקשה ולהסיר את החשבון, פנה:
{{$siteurl}}/regver/deny/{{$hash}}
{{$siteurl}}/regate/{{$mail}}{{if $ko}}/{{$ko}}{{/if}}
תודה.
--
Terms Of Service:
{{$siteurl}}/help/TermsOfService

View File

@@ -30,3 +30,8 @@ Om de registratie van dit account te annuleren en deze te verwijderen bezoek je:
Bedankt
--
Terms Of Service:
{{$siteurl}}/help/TermsOfService

View File

@@ -14,14 +14,19 @@ Twój kod weryfikacyjny, to:
{{$hash}}
{{if $timeframe}}
This token is valid from {{$timeframe.0}} UTC until {{$timeframe.1}} UTC
{{/if}}
Jeśli zarejestrowałeś to konto, wprowadź kod weryfikacyjny do żądania lub odwiedź
poniższy link:
{{$siteurl}}/regver/allow/{{$hash}}
{{$siteurl}}/regate/{{$mail}}
Aby odrzucić rejestrację i usunąć konto, odwiedź:
{{$siteurl}}/regver/deny/{{$hash}}
{{$siteurl}}/regate/{{$mail}}{{if $ko}}/{{$ko}}{{/if}}
Dziękjemy.

View File

@@ -2198,7 +2198,7 @@ msgstr "Permissão negada."
#: include/photos.php:152
#, php-format
msgid "Image exceeds website size limit of %lu bytes"
msgstr "A imagem excede o limite de tamanho do site, que é de %"
msgstr "A imagem excede o limite de tamanho do site, que é de %lu"
#: include/photos.php:163
#, fuzzy

View File

@@ -6123,7 +6123,6 @@ msgstr[2] "Не посетят"
#: ../../include/conversation.php:1775
msgctxt "noun"
msgid "Undecided"
msgid_plural "Undecided"
msgstr "Не решил"
#: ../../include/conversation.php:1778

View File

@@ -8,17 +8,31 @@ Användarnamn: {{$email}}
Logga in med lösenordet som du valde vid registreringen.
Your verification token is
{{$hash}}
{{if $timeframe}}
This token is valid from {{$timeframe.0}} UTC until {{$timeframe.1}} UTC
{{/if}}
Vi behöver bekräfta din e-postadress för att ge dig full åtkomst.
Om du registrerade det här kontot, följ den här länken:
{{$siteurl}}/regver/allow/{{$hash}}
{{$siteurl}}/regate/{{$mail}}
För att avbryta registreringen och ta bort kontot, gå till:
{{$siteurl}}/regver/deny/{{$hash}}
{{$siteurl}}/regate/{{$mail}}{{if $ko}}/{{$ko}}{{/if}}
Tack.
--
Terms Of Service:
{{$siteurl}}/help/TermsOfService

View File

@@ -5,7 +5,6 @@
<input type="hidden" name="form_security_token" value="{{$form_security_token}}">
<h3>{{$h_pending}}</h3>
{{if $debug}}<div>{{$debug}}</div>{{/if}}
{{if $pending}}
<table id="pending" class="table table-hover">
<thead>

View File

@@ -9,6 +9,7 @@
{{include file="field_checkbox.tpl" field=$block_public}}
{{include file="field_checkbox.tpl" field=$cloud_noroot}}
{{include file="field_checkbox.tpl" field=$cloud_disksize}}
{{include file="field_checkbox.tpl" field=$propfind_depth_infinity}}
{{include file="field_checkbox.tpl" field=$transport_security}}
{{include file="field_checkbox.tpl" field=$content_security}}
{{include file="field_checkbox.tpl" field=$embed_sslonly}}

View File

@@ -1,18 +1,18 @@
{{if $wrapper!="no"}}<div id="{{$qmc}}{{$field.name}}_wrapper" class="mb-3">{{/if}}
<label for="{{$qmc}}{{$field.name}}fs">{{$label}}
{{if $qmcid}}<sup class="zuiqmid required">{{$qmcid}}</sup>{{/if}}
{{if $qmcid}}<sup class="zuiqmid required">{{$qmcid}}</sup>{{/if}}
</label>
<fieldset name="{{$qmc}}{{$field.name}}fs" id="id_{{$qmc}}{{$field.name}}_fs" title="{{$field.title}}">
<input id="{{$qmc}}{{$field.name}}n"
name="{{$qmc}}{{$field.name}}n"
class="inline-block mr-1 text-center" style="width: 5rem;"
type="number"
{{if $field.min}} min="{{$field.min}}"{{/if}}
{{if $field.max}} max="{{$field.max}}"{{/if}}
size="{{$field.size}}"
value="{{$field.value}}"
<input id="{{$qmc}}{{$field.name}}n"
name="{{$qmc}}{{$field.name}}n"
class="inline-block mr-1 text-center" style="width: 5rem;"
type="number"
{{if isset($field.min)}} min="{{$field.min}}"{{/if}}
{{if isset($field.max)}} max="{{$field.max}}"{{/if}}
size="{{$field.size}}"
value="{{$field.value}}"
title="{{$field.title}}">
{{foreach $rabot as $k=>$v}}
@@ -31,7 +31,7 @@
* Template field_duration.qmc.tpl
* **********************************
* Hilmar Runge, 2020.02
* The template generates one input field for numeric values and a radio button group, where one
* The template generates one input field for numeric values and a radio button group, where one
* (and only one or no) selection can be active. The primary intented use is for entering time/date
* data in the form of amount (numeric) and the units (ie hours, days etc).
* Instead of using positional array parameters, keyed (named) parameters are treated. Imo, named parameters
@@ -56,14 +56,14 @@
* Example to apply in php like:
* *****************************
$testcase = replace_macros(get_markup_template('field_radio_group.qmc.tpl'),
array(
'label' => t('Exiration duration',
array(
'label' => t('Exiration duration',
'qmc' => 'zai', // not required
'qmcid' => 'ZAI0000I', // not required
'wrapper' => 'no', // when no wrapper around is desired
'field' => // fieldset properties
array(
'name' => 'due',
'name' => 'due',
'min' => "1", // the minimum value for the numeric input
'max' => "99", // the maximum value for the numeric input
'size' => "2", // the max digits for the numeric input
@@ -77,7 +77,7 @@
'd' => 'Day(s)' ,
'w' => 'Week(s)' ,
'm' => 'Month(s)' ,
'y' => 'Year(s)'
'y' => 'Year(s)'
)
)
);

10
view/tpl/home.tpl Normal file
View File

@@ -0,0 +1,10 @@
<div class="generic-content-wrapper">
{{if $welcome}}
<div class="section-title-wrapper">
<h2 class="">{{$welcome}}</h2>
</div>
{{/if}}
<div class="section-content-wrapper">
{{if $loginbox}}{{$loginbox}}{{/if}}
</div>
</div>

View File

@@ -1,16 +1,17 @@
<h1>{{$title}}</h1>
<div class='oauthapp'>
<img src='{{$app.icon}}'>
<img src='{{$app.icon|escape}}'>
<h3>{{$app.name}}</h3>
<p class="descriptive-paragraph">{{$authorize}}</p>
<form method="POST">
<div class="settings-submit-wrapper">
<input type="hidden" name="client_id" value="{{$client_id}}" />
<input type="hidden" name="redirect_uri" value="{{$redirect_uri}}" />
<input type="hidden" name="state" value="{{$state}}" />
<input type="hidden" name="form_security_token" value="{{$security}}" />
<input type="hidden" name="client_id" value="{{$client_id|escape}}" />
<input type="hidden" name="redirect_uri" value="{{$redirect_uri|escape}}" />
<input type="hidden" name="state" value="{{$state|escape}}" />
<button class="btn btn-lg btn-danger" name="authorize" value="deny" type="submit">{{$no}}</button>
<button class="btn btn-lg btn-success" name="authorize" value="allow" type="submit">{{$yes}}</button>
</div>
</form>
</div>
</div>

View File

@@ -1,5 +1,5 @@
<div class="mb-1 text-uppercase">
<i class="bi bi-{{$icon|escape}} generic-icons-nav"></i>{{$label|escape}}
<a href="/admin"><i class="bi bi-{{$icon|escape}} generic-icons-nav"></i>{{$label|escape}}</a>
</div>
<div class="card mb-4">
<div class="card-body clearfix">

View File

@@ -2,7 +2,7 @@
<h3>{{$title}}</h3>
<div class="tags text-center">
{{foreach $tags as $tag}}
<span class="tag{{$tag.2}}">#</span><a href="{{$baseurl}}{{$tag.0}}" class="tag{{$tag.2}}">{{$tag.0}}</a>
<a href="{{$baseurl}}{{$tag.0}}" class="tag{{$tag.2}}">#{{$tag.0}}</a>
{{/foreach}}
</div>
</div>