Compare commits

...

32 Commits
9.4.2 ... 9.4.4

Author SHA1 Message Date
Mario
173c4d242d version 9.4.4 2024-11-06 13:06:05 +00:00
Mario
76b8c36f7c changelog
(cherry picked from commit 12c88c06d8)

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

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


(cherry picked from commit 472484dde0)

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


(cherry picked from commit 5c3bdbd1e0)

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

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

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

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

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


(cherry picked from commit 8ab3ad6531)

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

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


(cherry picked from commit bf008465ad)

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


(cherry picked from commit a29a1c768d)

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


(cherry picked from commit 90bc987ea7)

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

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

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

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


(cherry picked from commit 065f85bab1)

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

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

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

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

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

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

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

Example:

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

Would become:

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


(cherry picked from commit 687cda3673)

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

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

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

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

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

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

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

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

Co-authored-by: Mario <mario@mariovavti.com>
2024-10-04 11:07:50 +00:00
41 changed files with 17038 additions and 16887 deletions

View File

@@ -1,3 +1,35 @@
Hubzilla 9.4.4 (2024-11-06)
- Update Norwegian translations
- Fix error adding things when multiple profiles not enabled
- Port mod thing to use $_GET/$_POST instead of $_REQUEST
- Move Norwegian translations from nb-no to nb
- Fix intact alernative network hublocs being marked deleted in Libsync::sync_locations()
- Fix modals only partly removed when DOM emlement updated via ajax
- Add explicit check for channel_address in channel_url()
- Fix PHP deprecation warnings in mod setup
- Fix PHP warning in Web/HttpMeta
- Fix typo in UnitTestCase
- Fix missing CSRF token checks in mod account_edit
- Fix OCAP tokens only added to images
- Fix unescaped zid parameter
- Fix wrong date format in published date in update question activities
- Fix include for en TOS page
- Fix query in copy_of_pubitem() returning duplicate items
- Fix edit button not clickable if below right aside when viewing webpages
- Fix rendering of category tags icons in the editor
- Fix regex to detect URLs in cleanup_bbcode
- Fix duplicate posts from forum clones
- Fix follow to non primary hub location in pubcrawl addon
- Fix id host not rewritten to local host in pubcrawl addon
- Fix regression when manually fetching items in pubcrawl addon
Hubzilla 9.4.3 (2024-10-10)
- Discard Add/Remove activities (Hubzilla 10 and (streams) compatibility)
- Fix HQ channel activities icons
- Fix saved search icons
Hubzilla 9.4.2 (2024-10-04)
- Indicate reacted state via icon color (community wish)
- Fix modal backdrop not removed when reacting from the modal

View File

@@ -503,15 +503,21 @@ class Activity {
$ret['diaspora:guid'] = $i['uuid'];
$images = [];
$audios = [];
$videos = [];
$has_images = preg_match_all('/\[[zi]mg(.*?)](.*?)\[/ism', $i['body'], $images, PREG_SET_ORDER);
$has_audios = preg_match_all('/\[zaudio](.*?)\[/ism', $i['body'], $audios, PREG_SET_ORDER);
$has_videos = preg_match_all('/\[zvideo](.*?)\[/ism', $i['body'], $videos, PREG_SET_ORDER);
// provide ocap access token for private media.
// set this for descendants even if the current item is not private
// because it may have been relayed from a private item.
$token = IConfig::Get($i, 'ocap', 'relay');
$matches_processed = [];
if ($token && $has_images) {
$matches_processed = [];
for ($n = 0; $n < count($images); $n++) {
$match = $images[$n];
if (str_starts_with($match[1], '=http') && str_contains($match[1], z_root() . '/photo/') && !in_array($match[1], $matches_processed)) {
@@ -526,6 +532,28 @@ class Activity {
}
}
if ($token && $has_audios) {
for ($n = 0; $n < count($audios); $n++) {
$match = $audios[$n];
if (str_contains($match[1], z_root() . '/attach/') && !in_array($match[1], $matches_processed)) {
$i['body'] = str_replace($match[1], $match[1] . '?token=' . $token, $i['body']);
$audios[$n][1] = $match[1] . '?token=' . $token;
$matches_processed[] = $match[1];
}
}
}
if ($token && $has_videos) {
for ($n = 0; $n < count($videos); $n++) {
$match = $videos[$n];
if (str_contains($match[1], z_root() . '/attach/') && !in_array($match[1], $matches_processed)) {
$i['body'] = str_replace($match[1], $match[1] . '?token=' . $token, $i['body']);
$videos[$n][1] = $match[1] . '?token=' . $token;
$matches_processed[] = $match[1];
}
}
}
if ($i['title'])
$ret['name'] = unescape_tags($i['title']);
@@ -694,6 +722,8 @@ class Activity {
$ret = [];
$token = IConfig::Get($item, 'ocap', 'relay');
if (!$iconfig && array_key_exists('attach', $item)) {
$atts = ((is_array($item['attach'])) ? $item['attach'] : json_decode($item['attach'], true));
if ($atts) {
@@ -702,11 +732,17 @@ class Activity {
continue;
}
if (isset($att['type']) && strpos($att['type'], 'image')) {
$ret[] = ['type' => 'Image', 'mediaType' => $att['type'], 'name' => $att['title'], 'url' => $att['href']];
if (str_starts_with($att['type'], 'image')) {
$ret[] = ['type' => 'Image', 'mediaType' => $att['type'], 'name' => $att['title'], 'url' => $att['href'] . (($token) ? '?token=' . $token : '')];
}
elseif (str_starts_with($att['type'], 'audio')) {
$ret[] = ['type' => 'Audio', 'mediaType' => $att['type'], 'name' => $att['title'], 'url' => $att['href'] . (($token) ? '?token=' . $token : '')];
}
elseif (str_starts_with($att['type'], 'video')) {
$ret[] = ['type' => 'Video', 'mediaType' => $att['type'], 'name' => $att['title'], 'url' => $att['href'] . (($token) ? '?token=' . $token : '')];
}
else {
$ret[] = ['type' => 'Link', 'mediaType' => $att['type'], 'name' => $att['title'], 'href' => $att['href']];
$ret[] = ['type' => 'Link', 'mediaType' => $att['type'], 'name' => $att['title'], 'href' => $att['href'] . (($token) ? '?token=' . $token : '')];
}
}
}
@@ -3054,13 +3090,6 @@ class Activity {
}
$a = new ActivityStreams($n);
if ($a->type === 'Announce' && is_array($a->obj)
&& array_key_exists('object', $a->obj) && array_key_exists('actor', $a->obj)) {
// This is a relayed/forwarded Activity (as opposed to a shared/boosted object)
// Reparse the encapsulated Activity and use that instead
logger('relayed activity', LOGGER_DEBUG);
$a = new ActivityStreams($a->obj);
}
logger($a->debug(), LOGGER_DATA);
@@ -3069,6 +3098,24 @@ class Activity {
break;
}
if (in_array($a->type, ['Add', 'Remove'])
&& is_array($a->obj)
&& array_key_exists('object', $a->obj)
&& array_key_exists('actor', $a->obj)
&& !empty($a->tgt)) {
logger('unsupported collection operation', LOGGER_DEBUG);
return;
}
if ($a->type === 'Announce' && is_array($a->obj)
&& array_key_exists('object', $a->obj) && array_key_exists('actor', $a->obj)) {
// This is a relayed/forwarded Activity (as opposed to a shared/boosted object)
// Reparse the encapsulated Activity and use that instead
logger('relayed activity', LOGGER_DEBUG);
$a = new ActivityStreams($a->obj);
}
$item = Activity::decode_note($a);
if (!$item) {

View File

@@ -885,7 +885,7 @@ class Libsync {
dbesc($t)
);
q("update hubloc set hubloc_error = 1, hubloc_deleted = 1 where hubloc_url = '%s' and hubloc_sitekey != '%s'",
q("update hubloc set hubloc_error = 1, hubloc_deleted = 1 where hubloc_url = '%s' and hubloc_sitekey != '%s' and hubloc_network = 'zot6'",
dbesc($r[0]['hubloc_url']),
dbesc($r[0]['hubloc_sitekey'])
);

View File

@@ -1148,6 +1148,17 @@ class Libzot {
logger('Activity rejected: ' . print_r($data, true));
return;
}
if (in_array($AS->type, ['Add', 'Remove'])
&& is_array($AS->obj)
&& array_key_exists('object', $AS->obj)
&& array_key_exists('actor', $AS->obj)
&& !empty($AS->tgt)) {
logger('unsupported collection operation', LOGGER_DEBUG);
return;
}
if (is_array($AS->obj)) {
$item = Activity::decode_note($AS);
if (!$item) {
@@ -1158,6 +1169,7 @@ class Libzot {
else {
$item = [];
}
logger($AS->debug(), LOGGER_DATA);
}
@@ -2006,7 +2018,13 @@ class Libzot {
foreach ($items as $activity) {
$AS = new ActivityStreams($activity);
if ($AS->is_valid() && $AS->type === 'Announce' && is_array($AS->obj)
if (!$AS->is_valid()) {
logger('Fetched activity rejected: ' . print_r($activity, true));
continue;
}
if ($AS->type === 'Announce' && is_array($AS->obj)
&& array_key_exists('object', $AS->obj) && array_key_exists('actor', $AS->obj)) {
// This is a relayed/forwarded Activity (as opposed to a shared/boosted object)
// Reparse the encapsulated Activity and use that instead
@@ -2014,9 +2032,14 @@ class Libzot {
$AS = new ActivityStreams($AS->obj);
}
if (!$AS->is_valid()) {
logger('Fetched activity rejected: ' . print_r($activity, true));
continue;
if (in_array($AS->type, ['Add', 'Remove'])
&& is_array($AS->obj)
&& array_key_exists('object', $AS->obj)
&& array_key_exists('actor', $AS->obj)
&& !empty($AS->tgt)) {
logger('unsupported collection operation', LOGGER_DEBUG);
return;
}
// logger($AS->debug());

View File

@@ -8,6 +8,11 @@ class Account_edit {
function post() {
// Validate CSRF token
//
// We terminate with a 403 Forbidden status if the check fails.
check_form_security_token_ForbiddenOnErr('admin_account_edit', 'security');
$account_id = $_REQUEST['aid'];
if(! $account_id)
@@ -18,7 +23,7 @@ class Account_edit {
if($pass1 && $pass2 && ($pass1 === $pass2)) {
$salt = random_string(32);
$password_encoded = hash('whirlpool', $salt . $pass1);
$r = q("update account set account_salt = '%s', account_password = '%s',
$r = q("update account set account_salt = '%s', account_password = '%s',
account_password_changed = '%s' where account_id = %d",
dbesc($salt),
dbesc($password_encoded),
@@ -34,7 +39,7 @@ class Account_edit {
$account_level = 5;
$account_language = trim($_REQUEST['account_language']);
$r = q("update account set account_service_class = '%s', account_level = %d, account_language = '%s'
$r = q("update account set account_service_class = '%s', account_level = %d, account_language = '%s'
where account_id = %d",
dbesc($service_class),
intval($account_level),
@@ -62,8 +67,8 @@ class Account_edit {
return '';
}
$a = replace_macros(get_markup_template('admin_account_edit.tpl'), [
'$security' => get_form_security_token('admin_account_edit'),
'$account' => $x[0],
'$title' => t('Account Edit'),
'$pass1' => [ 'pass1', t('New Password'), ' ','' ],

View File

@@ -1060,7 +1060,7 @@ class Item extends Controller {
$obj['id'] = $mid;
$obj['diaspora:guid'] = $uuid;
$obj['attributedTo'] = channel_url($channel);
$obj['published'] = $created;
$obj['published'] = datetime_convert('UTC', 'UTC', $created, ATOM_TIME);
$obj['name'] = $title;
$datarray['obj'] = $obj;

View File

@@ -263,7 +263,10 @@ class Setup extends \Zotlabs\Web\Controller {
$this->check_htaccess($checks);
$checkspassed = array_reduce($checks, "self::check_passed", true);
$checkspassed = array_reduce(
$checks,
"Zotlabs\Module\Setup::check_passed",
true);
$tpl = get_markup_template('install_checks.tpl');
$o .= replace_macros($tpl, array(

View File

@@ -50,24 +50,31 @@ class Thing extends \Zotlabs\Web\Controller {
$channel = \App::get_channel();
$term_hash = (($_REQUEST['term_hash']) ? $_REQUEST['term_hash'] : '');
$term_hash = (($_POST['term_hash']) ? $_POST['term_hash'] : '');
$name = escape_tags($_REQUEST['term']);
$verb = escape_tags($_REQUEST['verb']);
$activity = intval($_REQUEST['activity']);
$profile_guid = escape_tags($_REQUEST['profile_assign']);
$url = $_REQUEST['url'];
$photo = $_REQUEST['img'];
$name = escape_tags($_POST['term']);
$verb = escape_tags($_POST['verb']);
$activity = intval($_POST['activity']);
$url = $_POST['url'];
$photo = $_POST['img'];
$profile_guid = isset($_POST['profile_assign'])
? escape_tags($_POST['profile_assign'])
: null;
$hash = new_uuid();
$verbs = obj_verbs();
/**
* verbs: [0] = first person singular, e.g. "I want", [1] = 3rd person singular, e.g. "Bill wants"
* We use the first person form when creating an activity, but the third person for use in activities
* @FIXME There is no accounting for verb gender for languages where this is significant. We may eventually
* require obj_verbs() to provide full conjugations and specify which form to use in the $_REQUEST params to this module.
/*
* verbs: [0] = first person singular, e.g. "I want", [1] = 3rd person
* singular, e.g. "Bill wants" We use the first person form when
* creating an activity, but the third person for use in activities
*
* @FIXME There is no accounting for verb gender for languages where
* this is significant. We may eventually require obj_verbs() to
* provide full conjugations and specify which form to use in the
* $_POST params to this module.
*/
$translated_verb = $verbs[$verb][1];
@@ -100,7 +107,7 @@ class Thing extends \Zotlabs\Web\Controller {
return;
$acl = new \Zotlabs\Access\AccessList($channel);
$acl->set_from_array($_REQUEST);
$acl->set_from_array($_POST);
$x = $acl->get();
@@ -394,7 +401,7 @@ class Thing extends \Zotlabs\Web\Controller {
'$profile_lbl' => t('Select a profile'),
'$profile_select' => contact_profile_assign(''),
'$verb_lbl' => $channel['channel_name'],
'$activity' => array('activity',t('Post an activity'),((array_key_exists('activity',$_REQUEST)) ? $_REQUEST['activity'] : true),t('Only sends to viewers of the applicable profile')),
'$activity' => array('activity',t('Post an activity'),((array_key_exists('activity',$_GET)) ? $_GET['activity'] : true),t('Only sends to viewers of the applicable profile')),
'$verb_select' => obj_verb_selector(),
'$thing_lbl' => t('Name of thing e.g. something'),
'$url_lbl' => t('URL of thing (optional)'),

View File

@@ -5,16 +5,9 @@ namespace Zotlabs\Web;
class HttpMeta {
private $vars = null;
private $og = null;
function __construct() {
$this->vars = [];
$this->og = [];
$this->ogproperties = [];
}
private $vars = [];
private $og = [];
private $ogproperties = [];
//Set Meta Value
// Mode:

View File

@@ -2,6 +2,8 @@
namespace Zotlabs\Web;
use Zotlabs\Lib\Text;
class WebServer {
public function run() {
@@ -60,7 +62,7 @@ class WebServer {
\App::$query_string = strip_zids(\App::$query_string);
if(! local_channel()) {
if (!isset($_SESSION['my_address']) || $_SESSION['my_address'] != $_GET['zid']) {
$_SESSION['my_address'] = $_GET['zid'];
$_SESSION['my_address'] = Text::escape_tags($_GET['zid']);
$_SESSION['authenticated'] = 0;
}
if(!$_SESSION['authenticated']) {

View File

@@ -91,7 +91,7 @@ class Channel_activities {
self::$activities['photos'] = [
'label' => t('Photos'),
'icon' => 'photo',
'icon' => 'image',
'url' => z_root() . '/photos/' . self::$channel['channel_address'],
'date' => $r[0]['edited'],
'items' => $i,
@@ -123,7 +123,7 @@ class Channel_activities {
self::$activities['files'] = [
'label' => t('Files'),
'icon' => 'folder-open',
'icon' => 'folder',
'url' => z_root() . '/cloud/' . self::$channel['channel_address'],
'date' => $r[0]['edited'],
'items' => $i,
@@ -166,7 +166,7 @@ class Channel_activities {
self::$activities['webpages'] = [
'label' => t('Webpages'),
'icon' => 'newspaper-o',
'icon' => 'layout-text-sidebar',
'url' => z_root() . '/webpages/' . self::$channel['channel_address'],
'date' => $r[0]['edited'],
'items' => $i,
@@ -237,7 +237,7 @@ class Channel_activities {
self::$activities['channels'] = [
'label' => t('Channels'),
'icon' => 'home',
'icon' => 'house',
'url' => z_root() . '/manage',
'date' => datetime_convert(),
'items' => $i,

View File

@@ -66,7 +66,7 @@ require_once('include/security.php');
define('PLATFORM_NAME', 'hubzilla');
define('STD_VERSION', '9.4.2');
define('STD_VERSION', '9.4.4');
define('ZOT_REVISION', '6.0');
define('DB_UPDATE_VERSION', 1263);

View File

@@ -1,11 +1,11 @@
Privacy Policy
==============
#include doc/gdpr1.md;
#include doc/en/gdpr1.md;
Terms of Service
================
#include doc/SiteTOS.md;
#include doc/en/SiteTOS.md;

View File

@@ -3106,7 +3106,7 @@ function pchan_to_chan($pchan) {
}
function channel_url($channel) {
return (($channel) ? z_root() . '/channel/' . $channel['channel_address'] : z_root());
return ((isset($channel['channel_address'])) ? z_root() . '/channel/' . $channel['channel_address'] : z_root());
}
function get_channel_hashes() {

View File

@@ -3196,7 +3196,9 @@ function start_delivery_chain($channel, $item, $item_id, $parent, $group = false
}
else {
$arr['uuid'] = item_message_id();
// To prevent duplicates from possible clones of the forum/group,
// will create a v5 UUID of the source item mid.
$arr['uuid'] = uuid_from_url($item['mid']);
$arr['mid'] = z_root() . '/item/' . $arr['uuid'];
$arr['parent_mid'] = $arr['mid'];
}
@@ -5110,25 +5112,19 @@ function copy_of_pubitem($channel,$mid) {
return $item[0];
}
$r = q("select * from item where parent_mid = (select parent_mid from item where mid = '%s' and uid = %d ) order by id ",
$r = q("select * from item where parent_mid = (select parent_mid from item where mid = '%s' and uid = %d) and uid = %d order by id ",
dbesc($mid),
intval($syschan['channel_id']),
intval($syschan['channel_id'])
);
if($r) {
$items = fetch_post_tags($r,true);
foreach($items as $rv) {
$d = q("select id from item where mid = '%s' and uid = %d limit 1",
dbesc($rv['mid']),
intval($channel['channel_id'])
);
if($d) {
continue;
}
unset($rv['id']);
unset($rv['parent']);
$rv['aid'] = $channel['channel_account_id'];
$rv['uid'] = $channel['channel_id'];
$rv['item_wall'] = 0;
@@ -5141,5 +5137,6 @@ function copy_of_pubitem($channel,$mid) {
}
}
return $result;
}

View File

@@ -3755,12 +3755,9 @@ 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", '\nakedoembed', $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

@@ -0,0 +1,214 @@
<?php
/* Tests for Account_edit module
*
* SPDX-FileCopyrightText: 2024 Hubzilla Community
* SPDX-FileContributor: Harald Eilertsen
*
* SPDX-License-Identifier: MIT
*/
namespace Zotlabs\Tests\Unit\Module;
use DateTimeImmutable;
use PHPUnit\Framework\Attributes\{Before, After};
use Zotlabs\Model\Account;
class AdminAccountEditTest extends TestCase {
#[Before]
public function setup_mocks(): void {
/*
* As we're testing pages that should only be reachable by the
* site admin, it makes no sense to have it return anything else
* than true.
*/
$this->stub_is_site_admin =
$this->getFunctionMock('Zotlabs\Module', 'is_site_admin')
->expects($this->once())
->willReturn(true);
$this->info = [];
$this->stub_info =
$this->getFunctionMock('Zotlabs\Module\Admin', 'info')
->expects($this->any())
->willReturnCallback(function (string $arg) {
$this->info[] = $arg;
});
$this->notice = [];
$this->stub_notice =
$this->getFunctionMock('Zotlabs\Module\Admin', 'notice')
->expects($this->any())
->willReturnCallback(function (string $arg) {
$this->notice[] = $arg;
});
}
#[After]
public function tear_down_mocks(): void {
$this->stub_is_site_admin = null;
$this->stub_info = null;
$this->stub_notice = null;
$this->stub_check_security = null;
$this->stub_get_form_security_token = null;
}
public function test_rendering_admin_account_edit_page(): void {
$this->stub_get_form_security_token =
$this->getFunctionMock('Zotlabs\Module\Admin', 'get_form_security_token')
->expects($this->once())
->willReturn('the-csrf-token');
$account = $this->fixtures['account'][0];
$this->get("admin/account_edit/{$account['account_id']}");
$this->assertPageContains("<form action=\"admin/account_edit/{$account['account_id']}\" method=\"post\"");
$this->assertPageContains($account['account_email']);
// Check that we generate a CSRF token for the form
$this->assertPageContains("<input type=\"hidden\" name=\"security\" value=\"the-csrf-token\"");
}
public function test_rendering_admin_account_edit_page_fails_if_id_is_not_found(): void {
$this->get("admin/account_edit/666");
$this->assertEquals('', \App::$page['content']);
}
public function test_rendering_admin_account_edit_page_fails_if_id_is_not_numeric(): void {
$this->get("admin/account_edit/66invalid");
$this->assertEquals('', \App::$page['content']);
}
public function test_post_empty_form_does_not_modify_account(): void {
$this->stub_goaway();
$this->stub_check_form_security(true);
$account = get_account_by_id($this->fixtures['account'][0]['account_id']);
try {
$this->post(
"admin/account_edit/{$account['account_id']}",
[],
[
'aid' => $account['account_id'],
'pass1' => '',
'pass2' => '',
'service_class' => $account['account_service_class'],
'account_language' => $account['account_language'],
'security' => 'The security token',
]
);
} catch (RedirectException $ex) {
$this->assertEquals(z_root() . '/admin/accounts', $ex->getMessage());
}
$reloaded = get_account_by_id($account['account_id']);
$this->assertEquals($account, $reloaded);
// Not sure if this is expected behaviour, but this is how it is today.
$this->assertContains('Account settings updated.' . EOL, $this->info);
}
public function test_post_form_changes_account(): void {
$this->stub_goaway();
$this->stub_check_form_security(true);
// clone account from fixture, to ensure it's not replaced with
// the reloaded one below.
$account = get_account_by_id($this->fixtures['account'][0]['account_id']);
try {
$this->post(
"admin/account_edit/{$account['account_id']}",
[],
[
'aid' => $account['account_id'],
'pass1' => 'hunter2',
'pass2' => 'hunter2',
'service_class' => 'Some other class',
'account_language' => 'nn',
'security' => 'The security token',
]
);
} catch (RedirectException $ex) {
$this->assertEquals(z_root() . '/admin/accounts', $ex->getMessage());
}
$reloaded = get_account_by_id($account['account_id']);
$this->assertNotEquals($account, $reloaded);
$this->assertEquals('Some other class', $reloaded['account_service_class']);
$this->assertEquals('nn', $reloaded['account_language']);
$now = new DateTimeImmutable('now');
$this->assertEquals($now->format('Y-m-d H:i:s'), $reloaded['account_password_changed']);
$this->assertContains('Account settings updated.' . EOL, $this->info);
$this->assertContains("Password changed for account {$account['account_id']}." . EOL, $this->info);
}
public function test_form_with_missing_or_incalid_csrf_token_is_rejected(): void {
$this->expectException(KillmeException::class);
// Emulate a failed CSRF check
$this->stub_check_form_security(false);
$account_id = $this->fixtures['account'][0]['account_id'];
$this->post(
"admin/account_edit/{$account_id}",
[],
[
'aid' => $account_id,
'pass1' => 'hunter2',
'pass2' => 'hunter2',
'service_class' => 'Some other class',
'account_language' => 'nn',
'security' => 'Invalid security token',
]
);
}
/*
* Override the stub_goaway method because we need the stub to live in the
* Admin namespace.
*/
protected function stub_goaway(): void {
$this->goaway_stub = $this->getFunctionMock('Zotlabs\Module\Admin', 'goaway')
->expects($this->once())
->willReturnCallback(
function (string $uri) {
throw new RedirectException($uri);
}
);
}
/**
* Stub the check_form_security_token_ForbiddenOnErr.
*
* In these tests we're not really interested in _how_ the form security
* tokens work, but that the code under test perform the checks. This stub
* allows us to do that without having to worry if everything is set up so
* that the real function would work or not.
*
* @param bool $valid true if emulating a valid token, false otherwise.
*/
protected function stub_check_form_security(bool $valid): void {
$this->stub_check_security =
$this->getFunctionMock('Zotlabs\Module\Admin', 'check_form_security_token_ForbiddenOnErr')
->expects($this->once())
->with(
$this->identicalTo('admin_account_edit'),
$this->identicalTo('security'))
->willReturnCallback(function () use ($valid) {
if (! $valid) {
throw new KillmeException();
}
});
}
}

View File

@@ -10,6 +10,7 @@
namespace Zotlabs\Tests\Unit\Module;
use PHPUnit\Framework\Attributes\After;
use Zotlabs\Tests\Unit\UnitTestCase;
use App;
@@ -25,6 +26,31 @@ class TestCase extends UnitTestCase {
// Import PHPMock methods into this class
use \phpmock\phpunit\PHPMock;
#[After]
public function cleanup_stubs(): void {
$this->killme_stub = null;
$this->goaway_stub = null;
}
protected function do_request(string $method, string $uri, array $query = [], array $params = []): void {
$_GET['q'] = $uri;
$_GET = array_merge($_GET, $query);
$_POST = $params;
$_SERVER['REQUEST_METHOD'] = $method;
$_SERVER['SERVER_PROTOCOL'] = 'HTTP/1.1';
$_SERVER['QUERY_STRING'] = "q={$uri}";
// phpcs:disable Generic.PHP.DisallowRequestSuperglobal.Found
$_REQUEST = array_merge($_GET, $_POST);
// phpcs::enable
\App::init();
\App::$page['content'] = '';
$router = new \Zotlabs\Web\Router();
$router->Dispatch();
}
/**
* Emulate a GET request.
*
@@ -34,24 +60,21 @@ class TestCase extends UnitTestCase {
* as keys.
*/
protected function get(string $uri, array $query = []): void {
$_GET['q'] = $uri;
$this->do_request('GET', $uri, $query);
}
if (!empty($query)) {
$_GET = array_merge($_GET, $query);
}
$_SERVER['REQUEST_METHOD'] = 'GET';
$_SERVER['SERVER_PROTOCOL'] = 'HTTP/1.1';
$_SERVER['QUERY_STRING'] = "q={$uri}";
// phpcs:disable Generic.PHP.DisallowRequestSuperglobal.Found
$_REQUEST = $_GET;
// phpcs::enable
\App::init();
\App::$page['content'] = '';
$router = new \Zotlabs\Web\Router();
$router->Dispatch();
/**
* Emulate a POST request.
*
* @param string $uri The URI to request. Typically this will be the module
* name, followed by any req args separated by slashes.
* @param array $query Associative array of query args, with the parameters
* as keys.
* @param array $params Associative array of POST params, with the param names
* as keys.
*/
protected function post(string $uri, array $query = [], array $params = []): void {
$this->do_request('POST', $uri, $query, $params);
}
/**
@@ -100,8 +123,7 @@ class TestCase extends UnitTestCase {
* @throws KillmeException
*/
protected function stub_killme(): void {
$killme_stub = $this->getFunctionMock('Zotlabs\Module', 'killme');
$killme_stub
$this->killme_stub = $this->getFunctionMock('Zotlabs\Module', 'killme')
->expects($this->once())
->willReturnCallback(
function () {
@@ -147,8 +169,7 @@ class TestCase extends UnitTestCase {
* @throws RedirectException
*/
protected function stub_goaway(): void {
$goaway_stub = $this->getFunctionMock('Zotlabs\Module', 'goaway');
$goaway_stub
$this->goaway_stub = $this->getFunctionMock('Zotlabs\Module', 'goaway')
->expects($this->once())
->willReturnCallback(
function (string $uri) {

View File

@@ -47,7 +47,7 @@ require_once 'include/dba/dba_transaction.php';
*/
class UnitTestCase extends TestCase {
protected array $fixtures = array();
protected ?\DbaTransaction $db_transacton = null;
protected ?\DbaTransaction $db_transaction = null;
/**
* Connect to the test db, load fixtures and global config.

View File

@@ -8,6 +8,8 @@
* SPDX-License-Identifier: MIT
*/
use PHPUnit\Framework\Attributes\Before;
/**
* Test class for testing the Helpindex widget.
*/
@@ -15,6 +17,8 @@ class HelpindexTest extends \Zotlabs\Tests\Unit\Module\TestCase {
use \phpmock\phpunit\PHPMock;
private string $output;
/**
* Define the stubs to make sure they work later in the test.
*
@@ -27,6 +31,12 @@ class HelpindexTest extends \Zotlabs\Tests\Unit\Module\TestCase {
self::defineFunctionMock('Zotlabs\Widget', 'file_get_contents');
}
#[Before]
public function setup_state(): void {
// Make sure the output is cleared before running the test
$this->output = '';
}
public function test_loading_toc(): void {
// Stub `file_get_contents` to plant our own content.
$fgc_stub = $this->getFunctionMock('Zotlabs\Widget', 'file_get_contents');

View File

@@ -3,9 +3,11 @@ account:
account_id: 42
account_email: "hubzilla@example.com"
account_language: "no"
account_level: 5
account_flags: 0
-
account_id: 43
account_email: "hubzilla@example.org"
account_language: "de"
account_level: 5
account_flags: 1

View File

@@ -1178,20 +1178,27 @@ function justifyPhotosAjax(id) {
}
function dolike(ident, verb) {
$('#like-rotator-' + ident.toString()).show();
$('#like-rotator-' + ident).show();
if(typeof conv_mode == typeof undefined)
if (typeof conv_mode == typeof undefined) {
conv_mode = '';
}
if(typeof page_mode == typeof undefined)
if (typeof page_mode == typeof undefined) {
page_mode = '';
}
var reload = '';
if(module == 'photos')
let reload = 0;
if (module == 'photos') {
reload = 1;
}
$.get('like/' + ident.toString() + '?verb=' + verb + '&conv_mode=' + conv_mode + '&page_mode=' + page_mode + '&reload=' + reload, function (data) {
if(data.success) {
$.get('like/' + ident + '?verb=' + verb + '&conv_mode=' + conv_mode + '&page_mode=' + page_mode + '&reload=' + reload, function (data) {
if (data.success) {
close_modal();
// mod photos
if (data.reload) {
@@ -1213,10 +1220,9 @@ function dolike(ident, verb) {
$('#wall-item-ago-' + data.id + ' .autotime').timeago();
collapseHeight();
liking = 0;
// remove modal backdrop in case the update was triggered from a modal
$('.modal-backdrop').remove();
}
});
liking = 1;
}
@@ -1332,26 +1338,28 @@ function dropItem(url, object, b64mid) {
}
function dosubthread(ident) {
$('#like-rotator-' + ident.toString()).show();
$.get('subthread/sub/' + ident.toString(), updateInit );
$('#like-rotator-' + ident).show();
$.get('subthread/sub/' + ident, updateInit );
liking = 1;
}
function dounsubthread(ident) {
$('#like-rotator-' + ident.toString()).show();
$.get('subthread/unsub/' + ident.toString(), updateInit );
$('#like-rotator-' + ident).show();
$.get('subthread/unsub/' + ident, updateInit );
liking = 1;
}
function moderate_approve(ident) {
$('#like-rotator-' + ident.toString()).show();
$.get('moderate/' + ident.toString() + '/approve', updateInit );
function moderate_approve(ident, verb) {
$('#like-rotator-' + ident).show();
close_modal();
$.get('moderate/' + ident + '/approve', updateInit );
liking = 1;
}
function moderate_drop(ident) {
$('#like-rotator-' + ident.toString()).show();
$.get('moderate/' + ident.toString() + '/drop', $('#thread-wrapper-' + ident.toString()).fadeOut(function() { this.remove(); }));
$('#like-rotator-' + ident).show();
close_modal();
$.get('moderate/' + ident + '/drop', $('#thread-wrapper-' + ident).fadeOut(function() { this.remove(); }));
liking = 1;
}
@@ -1723,3 +1731,11 @@ function toast(string, severity) {
toastInstance.show();
}
function close_modal() {
let modal = bootstrap.Modal.getInstance(document.querySelector('.modal.show'));
if (modal) {
modal.hide();
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1363,12 +1363,16 @@ dl.bb-dl > dd > li {
}
.bootstrap-tagsinput .tag:before {
/* Copied from fa-asterisk, is there a better way to do it? */
font-family: ForkAwesome;
font-weight: normal;
font-style: normal;
text-decoration: inherit;
content:"\f069 ";
font-family: bootstrap-icons;
font-size: 0.5rem;
content: "\F151";
margin-right: .25rem;
}
.bootstrap-tagsinput .tag [data-role="remove"]:after {
font-family: bootstrap-icons;
font-size: 0.5rem;
content: "\F659";
}
/* Modified original CSS to match input in Redbasic */

View File

@@ -5,6 +5,7 @@
<form action="admin/account_edit/{{$account.account_id}}" method="post" >
<input type="hidden" name="aid" value="{{$account.account_id}}" />
<input type="hidden" name="security" value="{{$security}}">
{{include file="field_password.tpl" field=$pass1}}
{{include file="field_password.tpl" field=$pass2}}

View File

@@ -1,6 +1,6 @@
<div id="id_{{$field.0}}_wrapper" class="mb-3">
<label for="id_{{$field.0}}" id="label_{{$field.0}}">
{{$field.1}}{{if $field.4}}<sup class="required zuiqmid"> {{$field.4}}</sup>{{/if}}
{{$field.1}}{{if isset($field.4)}}<sup class="required zuiqmid"> {{$field.4}}</sup>{{/if}}
</label>
<input
class="form-control"
@@ -8,7 +8,7 @@
id="id_{{$field.0}}"
type="text"
value="{{$field.2|escape:'html':'UTF-8':FALSE}}"
{{if $field.5}}{{$field.5}}{{/if}}
{{if isset($field.5)}}{{$field.5}}{{/if}}
>
<small id="help_{{$field.0}}" class="form-text text-muted">
{{$field.3}}

View File

@@ -1,5 +1,5 @@
<div class="mb-3">
<label for="id_{{$field.0}}">{{$field.1}}</label>
<input class="form-control" type="password" name="{{$field.0}}" id="id_{{$field.0}}" value="{{$field.2}}"{{if $field.5}} {{$field.5}}{{/if}}>{{if $field.4}} <span class="required">{{$field.4}}</span> {{/if}}
<input class="form-control" type="password" name="{{$field.0}}" id="id_{{$field.0}}" value="{{$field.2}}"{{if isset($field.5)}} {{$field.5}}{{/if}}>{{if isset($field.4)}} <span class="required">{{$field.4}}</span> {{/if}}
<small id="help_{{$field.0}}" class="form-text text-muted">{{$field.3}}</small>
</div>

View File

@@ -1,11 +1,11 @@
<div id="id_{{$field.0}}_wrapper" class="mb-3">
<label for="id_{{$field.0}}">{{$field.1}}{{if $field.5}}<sup class="required zuiqmid"> {{$field.5}}</sup>{{/if}}</label>
<label for="id_{{$field.0}}">{{$field.1}}{{if isset($field.5)}}<sup class="required zuiqmid"> {{$field.5}}</sup>{{/if}}</label>
<select class="form-control" name="{{$field.0}}" id="id_{{$field.0}}">
{{foreach $field.4 as $opt=>$val}}<option value="{{$opt}}" {{if $opt==$field.2}}selected="selected"{{/if}}>{{$val}}</option>{{/foreach}}
</select>
<small class="form-text text-muted">{{$field.3}}</small >
</div>
{{*
{{*
COMMENTS for this template:
@author hilmar runge, 2020.01
$field array index:

View File

@@ -11,7 +11,7 @@
var updateInterval = {{$update_interval}};
var sse_enabled = {{$sse_enabled}};
var localUser = {{if $local_channel}}{{$local_channel}}{{else}}false{{/if}};
var zid = {{if $zid}}'{{$zid}}'{{else}}null{{/if}};
var zid = {{if $zid}}'{{$zid|escape:url}}'{{else}}null{{/if}};
var justifiedGalleryActive = false;
{{if $channel_hash}}var channelHash = '{{$channel_hash}}';{{/if}}
var channelId = {{if $channel_id}}{{$channel_id}}{{else}}false{{/if}};{{* Used in e.g. autocomplete *}}

View File

@@ -10,7 +10,7 @@
<div class="page-date">{{$date}}</div>
<div class="page-body">{{$body}}</div>
{{if $edit_link}}
<div class="position-fixed bottom-0 end-0 m-3">
<div class="position-fixed bottom-0 end-0 m-3 z-1">
<a href="{{$edit_link}}" class="btn btn-lg btn-primary rounded-circle"><i class="bi bi-pencil"></i></a>
</div>
{{/if}}

View File

@@ -1,6 +1,6 @@
{{$body}}
{{if $edit_link}}
<div class="position-fixed bottom-0 end-0 m-3">
<a href="{{$edit_link}}" class="btn btn-lg btn-primary rounded-circle"><i class="bi bi-pencil"></i></a>
<a href="{{$edit_link}}" class="btn btn-lg btn-primary rounded-circle z-1"><i class="bi bi-pencil"></i></a>
</div>
{{/if}}

View File

@@ -4,7 +4,7 @@
<ul id="saved-search-list" class="nav nav-pills flex-column">
{{foreach $saved as $search}}
<li class="nav-item nav-item-hack" id="search-term-{{$search.id}}">
<a class="nav-link widget-nav-pills-icons" title="{{$search.delete}}" onclick="return confirmDelete();" id="drop-saved-search-term-{{$search.id}}" href="{{$search.dellink}}"><i id="dropfa-floppy-od-search-term-{{$search.id}}" class="bi bi-trash drop-icons" ></i></a>
<a class="nav-link widget-nav-pills-icons{{if $search.selected}} active{{/if}}" title="{{$search.delete}}" onclick="return confirmDelete();" id="drop-saved-search-term-{{$search.id}}" href="{{$search.dellink}}"><i class="bi bi-trash"></i></a>
<a id="saved-search-term-{{$search.id}}" class="nav-link{{if $search.selected}} active{{/if}}" href="{{$search.srchlink}}">{{$search.displayterm}}</a>
</li>
{{/foreach}}

View File

@@ -4,7 +4,7 @@
<input class="form-control" type="text" name="search" id="search-text" value="{{$s}}" onclick="this.submit();" />
<button type="submit" name="submit" class="btn btn-outline-secondary" id="search-submit" value="{{$search_label}}"><i class="bi bi-search"></i></button>
{{if $savedsearch}}
<button type="submit" name="searchsave" class="btn btn-outline-secondary" id="search-save" value="{{$save_label}}"><i class="bi fa-floppy-o"></i></button>
<button type="submit" name="searchsave" class="btn btn-outline-secondary" id="search-save" value="{{$save_label}}"><i class="bi bi-save"></i></button>
{{/if}}
</div>
</form>