aboutsummaryrefslogtreecommitdiffstats
path: root/include
diff options
context:
space:
mode:
Diffstat (limited to 'include')
-rw-r--r--include/account.php390
-rw-r--r--include/acl_selectors.php2
-rw-r--r--include/api_zot.php11
-rw-r--r--include/attach.php143
-rw-r--r--include/auth.php20
-rw-r--r--include/bbcode.php99
-rw-r--r--include/channel.php38
-rw-r--r--include/config.php2
-rw-r--r--include/connections.php6
-rw-r--r--include/conversation.php321
-rw-r--r--include/datetime.php41
-rw-r--r--include/dba/dba_pdo.php112
-rw-r--r--include/event.php129
-rw-r--r--include/features.php10
-rw-r--r--include/feedutils.php59
-rw-r--r--include/help.php6
-rw-r--r--include/html2bbcode.php12
-rw-r--r--include/html2plain.php4
-rw-r--r--include/import.php8
-rw-r--r--include/items.php1283
-rw-r--r--include/js_strings.php30
-rw-r--r--include/language.php49
-rw-r--r--include/markdown.php16
-rw-r--r--include/nav.php33
-rw-r--r--include/network.php159
-rw-r--r--include/observer.php68
-rw-r--r--include/oembed.php4
-rw-r--r--include/permissions.php4
-rw-r--r--include/photo/photo_driver.php151
-rw-r--r--include/photos.php61
-rw-r--r--include/plugin.php10
-rw-r--r--include/security.php143
-rw-r--r--include/socgraph.php11
-rw-r--r--include/text.php195
-rw-r--r--include/xchan.php6
-rw-r--r--include/zid.php33
36 files changed, 2070 insertions, 1599 deletions
diff --git a/include/account.php b/include/account.php
index 884c07389..0c07bd85f 100644
--- a/include/account.php
+++ b/include/account.php
@@ -17,10 +17,38 @@ require_once('include/crypto.php');
require_once('include/channel.php');
-function get_account_by_id($account_id) {
- $r = q("select * from account where account_id = %d",
- intval($account_id)
- );
+/**
+ * Returns the id of a locally logged in account or false.
+ *
+ * Returns the numeric account id of the current session if authenticated, or
+ * false otherwise.
+ *
+ * @note It is possible to be authenticated, and not connected to a channel.
+ *
+ * @return int|false Numeric account id or false.
+ */
+function get_account_id(): int|false {
+ if (isset($_SESSION['account_id'])) {
+ return intval($_SESSION['account_id']);
+ }
+
+ if (App::$account) {
+ return intval(App::$account['account_id']);
+ }
+
+ return false;
+}
+
+/**
+ * Get the account with the given id from the database.
+ *
+ * @param int $account_id The numeric id of the account to fetch.
+ *
+ * @return array|false An array containing the attributes of the requested
+ * account, or false if it could not be retreived.
+ */
+function get_account_by_id(int $account_id): array|false {
+ $r = q("select * from account where account_id = %d", $account_id);
return (($r) ? $r[0] : false);
}
@@ -117,11 +145,16 @@ function check_account_invite($invite_code) {
}
function check_account_admin($arr) {
- if(is_site_admin())
+ if (is_site_admin()) {
return true;
+ }
+
$admin_email = trim(Config::Get('system','admin_email'));
- if(strlen($admin_email) && $admin_email === trim($arr['email']))
+
+ if (strlen($admin_email) && $admin_email === trim($arr['reg_email'])) {
return true;
+ }
+
return false;
}
@@ -132,167 +165,6 @@ function account_total() {
return false;
}
-// legacy
-function account_store_lowlevel_IS_OBSOLETE($arr) {
-
- $store = [
- 'account_parent' => ((array_key_exists('account_parent',$arr)) ? $arr['account_parent'] : '0'),
- 'account_default_channel' => ((array_key_exists('account_default_channel',$arr)) ? $arr['account_default_channel'] : '0'),
- 'account_salt' => ((array_key_exists('account_salt',$arr)) ? $arr['account_salt'] : ''),
- 'account_password' => ((array_key_exists('account_password',$arr)) ? $arr['account_password'] : ''),
- 'account_email' => ((array_key_exists('account_email',$arr)) ? $arr['account_email'] : ''),
- 'account_external' => ((array_key_exists('account_external',$arr)) ? $arr['account_external'] : ''),
- 'account_language' => ((array_key_exists('account_language',$arr)) ? $arr['account_language'] : 'en'),
- 'account_created' => ((array_key_exists('account_created',$arr)) ? $arr['account_created'] : '0001-01-01 00:00:00'),
- 'account_lastlog' => ((array_key_exists('account_lastlog',$arr)) ? $arr['account_lastlog'] : '0001-01-01 00:00:00'),
- 'account_flags' => ((array_key_exists('account_flags',$arr)) ? $arr['account_flags'] : '0'),
- 'account_roles' => ((array_key_exists('account_roles',$arr)) ? $arr['account_roles'] : '0'),
- 'account_reset' => ((array_key_exists('account_reset',$arr)) ? $arr['account_reset'] : ''),
- 'account_expires' => ((array_key_exists('account_expires',$arr)) ? $arr['account_expires'] : '0001-01-01 00:00:00'),
- 'account_expire_notified' => ((array_key_exists('account_expire_notified',$arr)) ? $arr['account_expire_notified'] : '0001-01-01 00:00:00'),
- 'account_service_class' => ((array_key_exists('account_service_class',$arr)) ? $arr['account_service_class'] : ''),
- 'account_level' => '5',
- 'account_password_changed' => ((array_key_exists('account_password_changed',$arr)) ? $arr['account_password_changed'] : '0001-01-01 00:00:00')
- ];
-
- // never ever is this a create table but a pdo insert into account
- // strange function placement in text.php (obscure by design :-)
- return create_table_from_array('account',$store);
- // the TODO may be to adjust others using create_table_from_array():
- // channel.php
- // connections.php
- // event.php
- // hubloc.php
- // import.php
-}
-
-
-
-// legacy
-function create_account_IS_OBSOLETE($arr) {
-
- // Required: { email, password }
-
- $result = array('success' => false, 'email' => '', 'password' => '', 'message' => '');
-
- $invite_code = ((x($arr,'invite_code')) ? notags(trim($arr['invite_code'])) : '');
- $email = ((x($arr,'email')) ? notags(punify(trim($arr['email']))) : '');
- $password = ((x($arr,'password')) ? trim($arr['password']) : '');
- $parent = ((x($arr,'parent')) ? intval($arr['parent']) : 0 );
- $flags = ((x($arr,'account_flags')) ? intval($arr['account_flags']) : ACCOUNT_OK);
- $roles = ((x($arr,'account_roles')) ? intval($arr['account_roles']) : 0 );
- $expires = ((x($arr,'expires')) ? intval($arr['expires']) : NULL_DATE);
-
- $default_service_class = Config::Get('system','default_service_class');
-
- if($default_service_class === false)
- $default_service_class = '';
-
- if((! x($email)) || (! x($password))) {
- $result['message'] = t('Please enter the required information.');
- return $result;
- }
-
- // prevent form hackery
-
- if($roles & ACCOUNT_ROLE_ADMIN) {
- $admin_result = check_account_admin($arr);
- if(! $admin_result) {
- $roles = 0;
- }
- }
-
- // allow the admin_email account to be admin, but only if it's the first account.
-
- $c = account_total();
- if (($c === 0) && (check_account_admin($arr)))
- $roles |= ACCOUNT_ROLE_ADMIN;
-
- // Ensure that there is a host keypair.
-
- if ((! Config::Get('system', 'pubkey')) && (! Config::Get('system', 'prvkey'))) {
- $hostkey = Crypto::new_keypair(4096);
- Config::Set('system', 'pubkey', $hostkey['pubkey']);
- Config::Set('system', 'prvkey', $hostkey['prvkey']);
- }
-
- $invite_result = check_account_invite($invite_code);
- if($invite_result['error']) {
- $result['message'] = $invite_result['message'];
- return $result;
- }
-
- $email_result = check_account_email($email);
-
- if($email_result['error']) {
- $result['message'] = $email_result['message'];
- return $result;
- }
-
- $password_result = check_account_password($password);
-
- if($password_result['error']) {
- $result['message'] = $password_result['message'];
- return $result;
- }
-
- $salt = random_string(32);
- $password_encoded = hash('whirlpool', $salt . $password);
-
- $r = account_store_lowlevel(
- [
- 'account_parent' => intval($parent),
- 'account_salt' => $salt,
- 'account_password' => $password_encoded,
- 'account_email' => $email,
- 'account_language' => get_best_language(),
- 'account_created' => datetime_convert(),
- 'account_flags' => intval($flags),
- 'account_roles' => intval($roles),
- 'account_level' => 5,
- 'account_expires' => $expires,
- 'account_service_class' => $default_service_class
- ]
- );
- if(! $r) {
- logger('create_account: DB INSERT failed.');
- $result['message'] = t('Failed to store account information.');
- return($result);
- }
-
- $r = q("select * from account where account_email = '%s' and account_password = '%s' limit 1",
- dbesc($email),
- dbesc($password_encoded)
- );
- if($r && count($r)) {
- $result['account'] = $r[0];
- }
- else {
- logger('create_account: could not retrieve newly created account');
- }
-
- // Set the parent record to the current record_id if no parent was provided
-
- if(! $parent) {
- $r = q("update account set account_parent = %d where account_id = %d",
- intval($result['account']['account_id']),
- intval($result['account']['account_id'])
- );
- if(! $r) {
- logger('create_account: failed to set parent');
- }
- $result['account']['parent'] = $result['account']['account_id'];
- }
-
- $result['success'] = true;
- $result['email'] = $email;
- $result['password'] = $password;
-
- call_hooks('register_account',$result);
-
- return $result;
-}
-
/**
* create_account_from_register
* @author hilmar runge
@@ -324,18 +196,18 @@ function create_account_from_register($arr) {
if($default_service_class === false)
$default_service_class = '';
- $roles = 0;
- // prevent form hackery
- if($roles & ACCOUNT_ROLE_ADMIN) {
- $admin_result = check_account_admin($arr);
- if(! $admin_result) {
- $roles = 0;
- }
+ // any accounts available ?
+ $total = q("SELECT COUNT(*) AS total FROM account");
+
+ if ($total && intval($total[0]['total']) === 0 && !check_account_admin($register[0])) {
+ logger('create_account: first account is not admin');
+ $result['message'] = t('First account is not admin.');
+ return $result;
}
- // any accounts available ?
- $isa = q("SELECT COUNT(*) AS isa FROM account");
- if ($isa && $isa[0]['isa'] == 0) {
+ $roles = 0;
+
+ if (check_account_admin($register[0])) {
$roles = ACCOUNT_ROLE_ADMIN;
}
@@ -446,76 +318,6 @@ function verify_email_address($arr) {
return $res;
}
-function verify_email_addressNOP($arr) {
-
- if(array_key_exists('resend',$arr)) {
- $a = q("select * from account where account_email = '%s' limit 1",
- dbesc($arr['email'])
- );
- if(! ($a && ($a[0]['account_flags'] & ACCOUNT_UNVERIFIED))) {
- return false;
- }
- $account = $a[0];
- // [hilmar ->
- $v = q("SELECT * FROM register WHERE reg_uid = %d AND reg_vital = 1 "
- . " AND reg_pass = 'verify' LIMIT 1",
- intval($account['account_id'])
- );
- // <- hilmar]
- if($v) {
- $hash = $v[0]['reg_hash'];
- }
- else {
- return false;
- }
- }
- else {
- $hash = random_string(24);
-
- // [hilmar ->
- q("INSERT INTO register ( reg_hash, reg_created, reg_uid, reg_pass, reg_lang, reg_stuff ) "
- ." VALUES ( '%s', '%s', %d, '%s', '%s', '' ) ",
- dbesc($hash),
- dbesc(datetime_convert()),
- intval($arr['account']['account_id']),
- dbesc('verify'),
- dbesc($arr['account']['account_language'])
- );
- // <- hilmar]
- $account = $arr['account'];
- }
-
- push_lang(($account['account_language']) ? $account['account_language'] : 'en');
-
- $email_msg = replace_macros(get_intltext_template('register_verify_member.tpl'),
- [
- '$sitename' => Config::Get('system','sitename'),
- '$siteurl' => z_root(),
- '$email' => $arr['email'],
- '$uid' => $account['account_id'],
- '$hash' => $hash,
- '$details' => ''
- ]
- );
-
- $res = z_mail(
- [
- 'toEmail' => $arr['email'],
- 'messageSubject' => sprintf( t('Registration confirmation for %s'), Config::Get('system','sitename')),
- 'textVersion' => $email_msg,
- ]
- );
-
- pop_lang();
-
- if(! $res)
- logger('send_reg_approval_email: failed to account_id: ' . $arr['account']['account_id']);
-
- return $res;
-}
-
-
-
function send_reg_approval_email($arr) {
@@ -613,59 +415,45 @@ function send_register_success_email($email,$password) {
}
/**
- * @brief Allows a user registration.
+ * Mark a pending registration as approved, and notify the account
+ * holder by email.
*
- * @param string $hash
- * @return array|boolean
+ * @param string $hash The registration hash of the entry to approve
+ *
+ * @return bool
*/
-function account_allow($hash) {
-
- $ret = array('success' => false);
+function account_allow(string $hash): bool {
$register = q("SELECT * FROM register WHERE reg_hash = '%s' LIMIT 1",
dbesc($hash)
);
- if(! $register)
- return $ret;
+ if (! $register) {
+ logger(
+ "Entry with hash '{$hash}' was not found in the register table.",
+ LOGGER_NORMAL,
+ LOG_ERR
+ );
+ return false;
+ }
- $account = q("SELECT * FROM account WHERE account_id = %d LIMIT 1",
- intval($register[0]['reg_uid'])
- );
+ $account = get_account_by_id($register[0]['reg_uid']);
- // a register entry without account assigned to
- if(! $account)
- return $ret;
+ if (! $account) {
+ logger(
+ "Account '{$register[0]['reg_uid']}' mentioned by registration hash '{$hash}' was not found.",
+ LOGGER_NORMAL,
+ LOG_ERR
+ );
+ return false;
+ }
- // [hilmar ->
+ $transaction = new DbaTransaction(DBA::$dba);
- q("START TRANSACTION");
- //q("DELETE FROM register WHERE reg_hash = '%s'",
- // dbesc($register[0]['reg_hash'])
- //);
$r1 = q("UPDATE register SET reg_vital = 0 WHERE reg_hash = '%s'",
dbesc($register[0]['reg_hash'])
);
- /* instead of ...
-
- // unblock
- q("UPDATE account SET account_flags = (account_flags & ~%d) "
- . " WHERE (account_flags & %d)>0 AND account_id = %d",
- intval(ACCOUNT_BLOCKED),
- intval(ACCOUNT_BLOCKED),
- intval($register[0]['reg_uid'])
- );
-
- // unpend
- q("UPDATE account SET account_flags = (account_flags & ~%d) "
- . " WHERE (account_flags & %d)>0 AND account_id = %d",
- intval(ACCOUNT_PENDING),
- intval(ACCOUNT_PENDING),
- intval($register[0]['reg_uid'])
- );
-
- */
// together unblock and unpend
$r2 = q("UPDATE account SET account_flags = %d WHERE account_id = %d",
intval($account['account_flags']
@@ -674,9 +462,7 @@ function account_allow($hash) {
);
if($r1 && $r2) {
- q("COMMIT");
-
- // <- hilmar]
+ $transaction->commit();
push_lang($register[0]['reg_lang']);
@@ -684,35 +470,35 @@ function account_allow($hash) {
$email_msg = replace_macros($email_tpl, array(
'$sitename' => Config::Get('system','sitename'),
'$siteurl' => z_root(),
- '$username' => $account[0]['account_email'],
- '$email' => $account[0]['account_email'],
+ '$username' => $account['account_email'],
+ '$email' => $account['account_email'],
'$password' => '',
- '$uid' => $account[0]['account_id']
+ '$uid' => $account['account_id']
));
$res = z_mail(
[
- 'toEmail' => $account[0]['account_email'],
+ 'toEmail' => $account['account_email'],
'messageSubject' => sprintf( t('Registration details for %s'), Config::Get('system','sitename')),
'textVersion' => $email_msg,
]
);
- pop_lang();
+ if (! $res) {
+ info(t("Sending account approval email to {$account['email']} failed..."));
+ }
- if(Config::Get('system', 'auto_channel_create', 1))
- auto_channel_create($register[0]['uid']);
+ pop_lang();
- if ($res) {
- info( t('Account approved.') . EOL );
- return true;
+ if(Config::Get('system', 'auto_channel_create', 1)) {
+ auto_channel_create($register[0]['reg_uid']);
}
- // [hilmar ->
- } else {
- q("ROLLBACK");
+ info( t('Account approved.') . EOL );
+ return true;
}
- // <- hilmar]
+
+ return false;
}
diff --git a/include/acl_selectors.php b/include/acl_selectors.php
index f158a439b..f0e0140dc 100644
--- a/include/acl_selectors.php
+++ b/include/acl_selectors.php
@@ -26,7 +26,7 @@ function populate_acl($defaults = null,$show_jotnets = true, $emptyACL_descripti
$allow_cid = $allow_gid = $deny_cid = $deny_gid = false;
$showall_origin = '';
- $showall_icon = 'fa-globe';
+ $showall_icon = 'bi-globe';
$role = get_pconfig(local_channel(), 'system', 'permissions_role');
if(! $emptyACL_description) {
diff --git a/include/api_zot.php b/include/api_zot.php
index 56cec005d..a2753269d 100644
--- a/include/api_zot.php
+++ b/include/api_zot.php
@@ -546,15 +546,13 @@
return false;
}
-
- logger('api_red_item_store: REQUEST ' . print_r($_REQUEST,true));
+ logger('api_red_item_store: REQUEST ' . print_r($_POST,true));
logger('api_red_item_store: FILES ' . print_r($_FILES,true));
-
// set this so that the item_post() function is quiet and doesn't redirect or emit json
- $_REQUEST['api_source'] = true;
- $_REQUEST['profile_uid'] = api_user();
+ $_POST['api_source'] = true;
+ $_POST['profile_uid'] = api_user();
if(x($_FILES,'media')) {
$_FILES['userfile'] = $_FILES['media'];
@@ -562,11 +560,12 @@
$mod = new Zotlabs\Module\Wall_attach();
$media = $mod->post();
if($media)
- $_REQUEST['body'] = $media . "\n" . $_REQUEST['body'];
+ $_POST['body'] = $media . "\n" . $_POST['body'];
}
$mod = new Zotlabs\Module\Item();
$x = $mod->post();
+
json_return_and_die($x);
}
diff --git a/include/attach.php b/include/attach.php
index 449721793..0569b97fb 100644
--- a/include/attach.php
+++ b/include/attach.php
@@ -63,6 +63,7 @@ function z_mime_content_type($filename) {
'jpg' => 'image/jpeg',
'gif' => 'image/gif',
'webp' => 'image/webp',
+ 'avif' => 'image/avif',
'bmp' => 'image/bmp',
'ico' => 'image/vnd.microsoft.icon',
'tiff' => 'image/tiff',
@@ -673,7 +674,7 @@ function attach_store($channel, $observer_hash, $options = '', $arr = null) {
logger('getimagesize: ' . print_r($gis,true), LOGGER_DATA);
}
- if(($gis) && ($gis[2] === IMAGETYPE_GIF || $gis[2] === IMAGETYPE_JPEG || $gis[2] === IMAGETYPE_PNG || $gis[2] === IMAGETYPE_WEBP)) {
+ if(($gis) && ($gis[2] === IMAGETYPE_GIF || $gis[2] === IMAGETYPE_JPEG || $gis[2] === IMAGETYPE_PNG || $gis[2] === IMAGETYPE_WEBP || $gis[2] === IMAGETYPE_AVIF)) {
$is_photo = 1;
if($gis[2] === IMAGETYPE_GIF)
$def_extension = '.gif';
@@ -683,6 +684,8 @@ function attach_store($channel, $observer_hash, $options = '', $arr = null) {
$def_extension = '.png';
if($gis[2] === IMAGETYPE_WEBP)
$def_extension = '.webp';
+ if($gis[2] === IMAGETYPE_AVIF)
+ $def_extension = '.avif';
}
// If we know it's a photo, over-ride the type in case the source system could not determine what it was
@@ -796,6 +799,12 @@ function attach_store($channel, $observer_hash, $options = '', $arr = null) {
}
}
+ if (mb_strlen($filename, 'UTF-8') > MAX_FILENAME_LENGTH) {
+ logger('filename too long');
+ $ret['message'] = t('Filename too long');
+ return $ret;
+ }
+
if(! $hash)
$hash = new_uuid();
@@ -1180,11 +1189,17 @@ function attach_mkdir($channel, $observer_hash, $arr = null) {
return $ret;
}
- if(isset($arr['filename']) && !strlen($arr['filename'])) {
+ if(empty($arr['filename'])) {
$ret['message'] = t('Empty pathname');
return $ret;
}
+ if(mb_strlen($arr['filename'], 'UTF-8') > MAX_FOLDER_LENGTH) {
+ logger('pathname too long');
+ $ret['message'] = t('Pathname too long');
+ return $ret;
+ }
+
$arr['hash'] = $arr['hash'] ?? new_uuid();
// Check for duplicate name.
@@ -1606,8 +1621,7 @@ function attach_drop_photo($channel_id,$resource) {
if($x) {
$stage = (($x[0]['item_hidden']) ? DROPITEM_NORMAL : DROPITEM_PHASE1);
- $interactive = (($x[0]['item_hidden']) ? false : true);
- drop_item($x[0]['id'], $interactive, $stage);
+ drop_item($x[0]['id'], $stage);
}
$r = q("SELECT content FROM photo WHERE resource_id = '%s' AND uid = %d AND os_storage = 1",
@@ -1636,8 +1650,7 @@ function attach_drop_item($channel_id,$resource) {
if($x) {
$stage = (($x[0]['item_hidden']) ? DROPITEM_NORMAL : DROPITEM_PHASE1);
- $interactive = (($x[0]['item_hidden']) ? false : true);
- drop_item($x[0]['id'], $interactive, $stage);
+ drop_item($x[0]['id'], $stage);
}
}
@@ -1855,8 +1868,6 @@ function pipe_streams($in, $out, $bufsize = 16384) {
}
function attach_store_item($channel, $observer, $file) {
-
-
if(is_string($file)) {
$r = q("SELECT * FROM attach WHERE uid = %d AND hash = '%s' LIMIT 1",
intval($channel['channel_id']),
@@ -1906,10 +1917,11 @@ function attach_store_item($channel, $observer, $file) {
$post = item_store($arr);
- $item_id = $post['item_id'];
-
- if($item_id) {
- Master::Summon(['Notifier', 'activity', $item_id]);
+ if ($post['success']) {
+ Master::Summon(['Notifier', 'activity', $post['item_id']]);
+ if (!empty($post['approval_id'])) {
+ Master::Summon(['Notifier', 'activity', $post['approval_id']]);
+ }
}
*/
@@ -1919,8 +1931,11 @@ function attach_store_item($channel, $observer, $file) {
}
$stage = (($r[0]['item_hidden']) ? DROPITEM_NORMAL : DROPITEM_PHASE1);
- $interactive = (($r[0]['item_hidden']) ? false : true);
- drop_item($r[0]['id'], $interactive, $stage);
+ drop_item($r[0]['id'], $stage);
+
+ if (empty($r[0]['item_hidden'])) {
+ Master::Summon(['Notifier', 'drop', $r[0]['id']]);
+ }
}
@@ -1945,6 +1960,12 @@ function attach_store_item($channel, $observer, $file) {
$mid = z_root() . '/item/' . $uuid;
+ $target = [
+ 'id' => z_root() . '/conversation/' . $uuid,
+ 'type' => 'Collection',
+ 'attributedTo' => channel_url($channel),
+ ];
+
$arr = []; // Initialize the array of parameters for the post
$arr['aid'] = $channel['channel_account_id'];
$arr['uuid'] = $uuid;
@@ -1965,6 +1986,8 @@ function attach_store_item($channel, $observer, $file) {
$arr['item_thread_top'] = 1;
$arr['item_private'] = (($file['allow_cid'] || $file['allow_gid'] || $file['deny_cid'] || $file['deny_gid']) ? 1 : 0);
$arr['verb'] = 'Create';
+ $arr['target'] = $target;
+ $arr['target_type'] = 'Collection';
$arr['obj_type'] = $type;
$arr['title'] = $file['filename'];
@@ -1982,14 +2005,13 @@ function attach_store_item($channel, $observer, $file) {
}
$body_str = sprintf((($type === 'Image') ? t('%s shared an %s with you') : t('%s shared a %s with you')), '[zrl=' . $observer['xchan_url'] . ']' . $observer['xchan_name'] . '[/zrl]', '[zrl=' . $path . ']' . (($type === 'Image') ? t('image') : t('file')) . '[/zrl]');
- $arr['body'] .= $body_str;
+ $arr['body'] .= "\r\n" . $body_str;
$meta = [
'name' => $file['filename'],
'type' => $file['filetype'],
'size' => $file['filesize'],
'revision' => $file['revision'],
- 'size' => $file['filesize'],
'created' => $file['created'],
'edited' => $file['edited'],
'path' => $path
@@ -1999,10 +2021,11 @@ function attach_store_item($channel, $observer, $file) {
$post = item_store($arr);
- $item_id = $post['item_id'];
-
- if($item_id) {
- Master::Summon(['Notifier', 'activity', $item_id]);
+ if ($post['success']) {
+ Master::Summon(['Notifier', 'activity', $post['item_id']]);
+ if (!empty($post['approval_id'])) {
+ Master::Summon(['Notifier', 'activity', $post['approval_id']]);
+ }
}
}
@@ -2581,6 +2604,11 @@ function attach_move($channel_id, $resource_id, $new_folder_hash, $newname = '',
}
}
+ if (mb_strlen($filename, 'UTF-8') > MAX_FILENAME_LENGTH) {
+ logger('filename too long');
+ $ret['message'] = t('Filename too long');
+ return $ret;
+ }
q("update attach set content = '%s', folder = '%s', filename = '%s', edited = '%s' where id = %d",
dbescbin($newstorepath),
@@ -2599,33 +2627,31 @@ function attach_move($channel_id, $resource_id, $new_folder_hash, $newname = '',
intval($r[0]['id'])
);
- if($r[0]['is_photo']) {
- q("update photo set album = '%s', filename = '%s', os_path = '%s', display_path = '%s'
- where resource_id = '%s' and uid = %d",
- dbesc($newalbumname),
- dbesc($filename),
- dbesc($x['os_path']),
- dbesc($x['path']),
- dbesc($resource_id),
- intval($channel_id)
- );
-
- q("update photo set content = CASE imgscale WHEN 0 THEN %s ELSE CONCAT(%s, '-', imgscale) END where resource_id = '%s' and uid = %d and os_storage = 1",
- dbescbin($newstorepath),
- dbescbin($newstorepath),
- dbesc($resource_id),
- intval($channel_id)
- );
-
- // now rename the thumbnails in os_storage - the original should have been copied before already
- $ps = q("SELECT content, imgscale FROM photo WHERE uid = %d AND resource_id = '%s' and imgscale > 0 and os_storage = 1",
+ if ($r[0]['is_photo']) {
+ // update the photo DB entries and copy the thumbnails
+ $ps = q("SELECT imgscale FROM photo WHERE uid = %d AND resource_id = '%s' and os_storage = 1",
intval($channel_id),
dbesc($resource_id)
);
if ($recurse) {
foreach($ps as $p) {
- rename($oldstorepath . '-' . $p['imgscale'], $p['content']);
+ q("update photo set album = '%s', filename = '%s', os_path = '%s', display_path = '%s', content = '%s'
+ where resource_id = '%s' and imgscale = %d and uid = %d",
+ dbesc($newalbumname),
+ dbesc($filename),
+ dbesc($x['os_path']),
+ dbesc($x['path']),
+ dbescbin($newstorepath . ((intval($p['imgscale']) > 0) ? '-' . $p['imgscale'] : '')),
+ dbesc($resource_id),
+ intval($p['imgscale']),
+ intval($channel_id)
+ );
+
+ // the original should have been copied already
+ if (intval($p['imgscale']) > 0) {
+ rename($oldstorepath . '-' . $p['imgscale'], $newstorepath . '-' . $p['imgscale']);
+ }
}
}
}
@@ -2935,41 +2961,6 @@ function attach_syspaths($channel_id,$attach_hash) {
return [ 'os_path' => $os_path, 'path' => $path ];
}
-/**
- * in earlier releases we did not fill in os_path and display_path in the attach DB structure.
- * (It was not needed or used). Going forward we intend to make use of these fields.
- * A cron task checks for empty values (as older attachments may have arrived at our site
- * in a clone operation) and executes attach_syspaths() to generate these field values and correct
- * the attach table entry. The operation is limited to 100 DB entries at a time so as not to
- * overload the system in any cron run. Eventually it will catch up with old attach structures
- * and switch into maintenance mode to correct any that might arrive in clone packets from older
- * sites.
- */
-
-
-
-function attach_upgrade() {
- $r = q("SELECT id, uid, hash FROM attach WHERE os_path = '' OR display_path = '' LIMIT 100");
- if($r) {
- foreach($r as $rv) {
- $x = attach_syspaths($rv['uid'],$rv['hash']);
- if($x) {
- q("update attach set os_path = '%s', display_path = '%s' where id = %d",
- dbesc($x['os_path']),
- dbesc($x['path']),
- intval($rv['id'])
- );
- q("update photo set os_path = '%s', display_path = '%s' where uid = %d and resource_id = '%s'",
- dbesc($x['os_path']),
- dbesc($x['path']),
- intval($rv['uid']),
- dbesc($rv['hash'])
- );
- }
- }
- }
-}
-
/**
* Chunked uploader for integration with the blueimp jquery-uploader
diff --git a/include/auth.php b/include/auth.php
index 1fc2cc556..36a9043ce 100644
--- a/include/auth.php
+++ b/include/auth.php
@@ -216,12 +216,11 @@ function requires_mfa_check(int $account_id, string $module, string $arg): bool
* also handles logout
*/
-if((isset($_SESSION)) && (x($_SESSION, 'authenticated')) &&
- ((! (x($_POST, 'auth-params'))) || ($_POST['auth-params'] !== 'login'))) {
+if(!empty($_SESSION['authenticated']) && (empty($_POST['auth-params']) || $_POST['auth-params'] !== 'login')) {
// process a logout request
- if(((x($_POST, 'auth-params')) && ($_POST['auth-params'] === 'logout')) || (App::$module === 'logout')) {
+ if((!empty($_POST['auth-params']) && $_POST['auth-params'] === 'logout') || App::$module === 'logout') {
// process logout request
$args = array('channel_id' => local_channel());
call_hooks('logging_out', $args);
@@ -241,7 +240,7 @@ if((isset($_SESSION)) && (x($_SESSION, 'authenticated')) &&
// re-validate a visitor, optionally invoke "su" if permitted to do so
- if(x($_SESSION, 'visitor_id') && (! x($_SESSION, 'uid'))) {
+ if(!empty($_SESSION['visitor_id']) && empty($_SESSION['uid'])) {
// if our authenticated guest is allowed to take control of the admin channel, make it so.
$admins = Config::Get('system', 'remote_admin');
if($admins && is_array($admins) && in_array($_SESSION['visitor_id'], $admins)) {
@@ -281,7 +280,7 @@ if((isset($_SESSION)) && (x($_SESSION, 'authenticated')) &&
// already logged in user returning
- if(x($_SESSION, 'uid') || x($_SESSION, 'account_id')) {
+ if(!empty($_SESSION['uid']) || !empty($_SESSION['account_id'])) {
App::$session->return_check();
@@ -292,7 +291,7 @@ if((isset($_SESSION)) && (x($_SESSION, 'authenticated')) &&
if(($r) && (($r[0]['account_flags'] == ACCOUNT_OK) || ($r[0]['account_flags'] == ACCOUNT_UNVERIFIED))) {
App::$account = $r[0];
$login_refresh = false;
- if(! x($_SESSION,'last_login_date')) {
+ if(empty($_SESSION['last_login_date'])) {
$_SESSION['last_login_date'] = datetime_convert('UTC','UTC');
}
if(strcmp(datetime_convert('UTC','UTC','now - 12 hours'), $_SESSION['last_login_date']) > 0 ) {
@@ -331,7 +330,7 @@ else {
if($password)
$encrypted = hash('whirlpool', trim($password));
- if((x($_POST, 'auth-params')) && $_POST['auth-params'] === 'login') {
+ if(!empty($_POST['auth-params']) && $_POST['auth-params'] === 'login') {
$atoken = null;
$account = null;
@@ -354,9 +353,6 @@ else {
elseif($atoken) {
atoken_login($atoken);
}
- else {
- notice( t('Failed authentication') . EOL);
- }
if(! ($account || $atoken)) {
$error = 'authenticate: failed login attempt: ' . notags(trim($username)) . ' from IP ' . $_SERVER['REMOTE_ADDR'];
@@ -365,8 +361,8 @@ else {
$authlog = Config::Get('system', 'authlog');
if ($authlog)
@file_put_contents($authlog, datetime_convert() . ':' . session_id() . ' ' . $error . "\n", FILE_APPEND);
- notice( t('Login failed.') . EOL );
- goaway(z_root() . '/login');
+
+ goaway(z_root() . '/login?retry=1');
}
// If the user specified to remember the authentication, then change the cookie
diff --git a/include/bbcode.php b/include/bbcode.php
index 15a75ce3f..d5e1bafd9 100644
--- a/include/bbcode.php
+++ b/include/bbcode.php
@@ -420,12 +420,12 @@ function getAttachmentData($body) {
$type = "";
preg_match("/type='(.*?)'/ism", $attributes, $matches);
- if (x($matches, 1)) {
+ if (!empty($matches[1])) {
$type = strtolower($matches[1]);
}
preg_match('/type=\&quot\;(.*?)\&quot\;/ism', $attributes, $matches);
- if (x($matches, 1)) {
+ if (!empty($matches[1])) {
$type = strtolower($matches[1]);
}
@@ -442,12 +442,12 @@ function getAttachmentData($body) {
}
$url = "";
preg_match("/url='(.*?)'/ism", $attributes, $matches);
- if (x($matches, 1)) {
+ if (!empty($matches[1])) {
$url = $matches[1];
}
preg_match('/url=\&quot\;(.*?)\&quot\;/ism', $attributes, $matches);
- if (x($matches, 1)) {
+ if (!empty($matches[1])) {
$url = $matches[1];
}
@@ -457,12 +457,12 @@ function getAttachmentData($body) {
$title = "";
preg_match("/title='(.*?)'/ism", $attributes, $matches);
- if (x($matches, 1)) {
+ if (!empty($matches[1])) {
$title = $matches[1];
}
preg_match('/title=\&quot\;(.*?)\&quot\;/ism', $attributes, $matches);
- if (x($matches, 1)) {
+ if (!empty($matches[1])) {
$title = $matches[1];
}
if ($title != "") {
@@ -473,12 +473,12 @@ function getAttachmentData($body) {
$image = "";
preg_match("/image='(.*?)'/ism", $attributes, $matches);
- if (x($matches, 1)) {
+ if (!empty($matches[1])) {
$image = $matches[1];
}
preg_match('/image=\&quot\;(.*?)\&quot\;/ism', $attributes, $matches);
- if (x($matches, 1)) {
+ if (!empty($matches[1])) {
$image = $matches[1];
}
@@ -488,12 +488,12 @@ function getAttachmentData($body) {
$preview = "";
preg_match("/preview='(.*?)'/ism", $attributes, $matches);
- if (x($matches, 1)) {
+ if (!empty($matches[1])) {
$preview = $matches[1];
}
preg_match('/preview=\&quot\;(.*?)\&quot\;/ism', $attributes, $matches);
- if (x($matches, 1)) {
+ if (!empty($matches[1])) {
$preview = $matches[1];
}
if ($preview != "") {
@@ -1126,9 +1126,11 @@ function parseIdentityAwareHTML($Text) {
if ($observer) {
$s1 = '<span class="bb_observer" title="' . t('Different viewers will see this text differently') . '">';
$s2 = '</span>';
- $obsBaseURL = $observer['xchan_connurl'];
- $obsBaseURL = preg_replace("/\/poco\/.*$/", '', $obsBaseURL);
- $Text = str_replace('[observer.baseurl]', $obsBaseURL, $Text);
+
+ $parsed = parse_url($observer['xchan_url']);
+ $observer_base_url = unparse_url($parsed, ['scheme', 'host', 'port']);
+
+ $Text = str_replace('[observer.baseurl]', $observer_base_url, $Text);
$Text = str_replace('[observer.url]',$observer['xchan_url'], $Text);
$Text = str_replace('[observer.name]',$s1 . $observer['xchan_name'] . $s2, $Text);
$Text = str_replace('[observer.address]',$s1 . $observer['xchan_addr'] . $s2, $Text);
@@ -1188,7 +1190,7 @@ function bbcode($text, $options = []) {
$cache = ((array_key_exists('cache',$options)) ? $options['cache'] : false);
$newwin = ((array_key_exists('newwin',$options)) ? $options['newwin'] : true);
- $target = (($newwin) ? ' target="_blank" ' : '');
+ $target = (($newwin) ? 'target="_blank"' : '');
/**
* @hooks bbcode_filter
@@ -1311,9 +1313,11 @@ function bbcode($text, $options = []) {
if ($observer) {
$s1 = '<span class="bb_observer" title="' . t('Different viewers will see this text differently') . '">';
$s2 = '</span>';
- $obsBaseURL = $observer['xchan_connurl'];
- $obsBaseURL = preg_replace("/\/poco\/.*$/", '', $obsBaseURL);
- $text = str_replace('[observer.baseurl]', $obsBaseURL, $text);
+
+ $parsed = parse_url($observer['xchan_url']);
+ $observer_base_url = unparse_url($parsed, ['scheme', 'host', 'port']);
+
+ $text = str_replace('[observer.baseurl]', $observer_base_url, $text);
$text = str_replace('[observer.url]',$observer['xchan_url'], $text);
$text = str_replace('[observer.name]',$s1 . $observer['xchan_name'] . $s2, $text);
$text = str_replace('[observer.address]',$s1 . $observer['xchan_addr'] . $s2, $text);
@@ -1328,21 +1332,31 @@ function bbcode($text, $options = []) {
$text = str_replace('[observer.photo]','', $text);
}
-
-
// Perform URL Search
-
$urlchars = '[a-zA-Z0-9\pL\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,\@\(\)]';
if (strpos($text,'http') !== false) {
if($tryoembed) {
$text = preg_replace_callback("/([^\]\='".'"'."\;\/]|^|\#\^)(https?\:\/\/$urlchars+)/ismu", 'tryoembed', $text);
}
- // Is this still desired?
- // We already turn naked URLs into links during creation time cleanup_bbcode()
+
$text = preg_replace("/([^\]\='".'"'."\;\/]|^|\#\^)(https?\:\/\/$urlchars+)/ismu", '$1<a href="$2" ' . $target . ' rel="nofollow noopener">$2</a>', $text);
}
+ // Turn naked geo URIs into clickable links
+ if (str_contains($text, 'geo:')) {
+ $text = preg_replace_callback(
+ '/([^\]\=\'";\/]|^|\#\^)(geo:([A-Za-z0-9:.,;_+\-?&=%]+)(?:\(([^)]+)\))?)/ismu',
+ function ($matches) {
+ $before = $matches[1];
+ $geo_uri = $matches[2];
+ $label = ((!empty($matches[4])) ? '📍' . urldecode($matches[4]) : $geo_uri);
+ return $before . '<a href="' . htmlspecialchars($geo_uri) . '" target="_blank" rel="nofollow noopener">' . htmlspecialchars($label) . '</a>';
+ },
+ $text
+ );
+ }
+
$count = 0;
while (strpos($text,'[/share]') !== false && $count < 10) {
$text = preg_replace_callback("/\[share(.*?)\](.*?)\[\/share\]/ism", 'bb_ShareAttributes', $text);
@@ -1356,23 +1370,23 @@ function bbcode($text, $options = []) {
}
if (strpos($text,'[/url]') !== false) {
- $text = preg_replace("/\#\^\[url\]([$URLSearchString]*)\[\/url\]/ism", '<span class="bookmark-identifier">#^</span><a class="bookmark" href="$1" ' . $target . ' rel="nofollow noopener" >$1</a>', $text);
- $text = preg_replace("/\#\^\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", '<span class="bookmark-identifier">#^</span><a class="bookmark" href="$1" ' . $target . ' rel="nofollow noopener" >$2</a>', $text);
- $text = preg_replace("/\[url\]([$URLSearchString]*)\[\/url\]/ism", '<a href="$1" ' . $target . ' rel="nofollow noopener" >$1</a>', $text);
- $text = preg_replace("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", '<a href="$1" ' . $target . ' rel="nofollow noopener" >$2</a>', $text);
+ $text = preg_replace("/\#\^\[url\]([$URLSearchString]*)\[\/url\]/ism", '<span class="bookmark-identifier">#^</span><a class="bookmark" href="$1" ' . $target . ' rel="nofollow noopener">$1</a>', $text);
+ $text = preg_replace("/\#\^\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", '<span class="bookmark-identifier">#^</span><a class="bookmark" href="$1" ' . $target . ' rel="nofollow noopener">$2</a>', $text);
+ $text = preg_replace("/\[url\]([$URLSearchString]*)\[\/url\]/ism", '<a href="$1" ' . $target . ' rel="nofollow noopener">$1</a>', $text);
+ $text = preg_replace("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", '<a href="$1" ' . $target . ' rel="nofollow noopener">$2</a>', $text);
}
if (strpos($text,'[/zrl]') !== false) {
- $text = preg_replace("/\#\^\[zrl\]([$URLSearchString]*)\[\/zrl\]/ism", '<span class="bookmark-identifier">#^</span><a class="zrl bookmark" href="$1" ' . $target . ' rel="nofollow noopener" >$1</a>', $text);
- $text = preg_replace("/\#\^\[zrl\=([$URLSearchString]*)\](.*?)\[\/zrl\]/ism", '<span class="bookmark-identifier">#^</span><a class="zrl bookmark" href="$1" ' . $target . ' rel="nofollow noopener" >$2</a>', $text);
- $text = preg_replace("/\[zrl\]([$URLSearchString]*)\[\/zrl\]/ism", '<a class="zrl" href="$1" ' . $target . ' rel="nofollow noopener" >$1</a>', $text);
- $text = preg_replace("/\[zrl\=([$URLSearchString]*)\](.*?)\[\/zrl\]/ism", '<a class="zrl" href="$1" ' . $target . ' rel="nofollow noopener" >$2</a>', $text);
+ $text = preg_replace("/\#\^\[zrl\]([$URLSearchString]*)\[\/zrl\]/ism", '<span class="bookmark-identifier">#^</span><a class="zrl bookmark" href="$1" ' . $target . ' rel="nofollow noopener">$1</a>', $text);
+ $text = preg_replace("/\#\^\[zrl\=([$URLSearchString]*)\](.*?)\[\/zrl\]/ism", '<span class="bookmark-identifier">#^</span><a class="zrl bookmark" href="$1" ' . $target . ' rel="nofollow noopener">$2</a>', $text);
+ $text = preg_replace("/\[zrl\]([$URLSearchString]*)\[\/zrl\]/ism", '<a class="zrl" href="$1" ' . $target . ' rel="nofollow noopener">$1</a>', $text);
+ $text = preg_replace("/\[zrl\=([$URLSearchString]*)\](.*?)\[\/zrl\]/ism", '<a class="zrl" href="$1" ' . $target . ' rel="nofollow noopener">$2</a>', $text);
}
// Perform MAIL Search
if (strpos($text,'[/mail]') !== false) {
- $text = preg_replace("/\[mail\]([$MAILSearchString]*)\[\/mail\]/", '<a href="mailto:$1" ' . $target . ' rel="nofollow noopener" >$1</a>', $text);
- $text = preg_replace("/\[mail\=([$MAILSearchString]*)\](.*?)\[\/mail\]/", '<a href="mailto:$1" ' . $target . ' rel="nofollow noopener" >$2</a>', $text);
+ $text = preg_replace("/\[mail\]([$MAILSearchString]*)\[\/mail\]/", '<a href="mailto:$1" ' . $target . ' rel="nofollow noopener">$1</a>', $text);
+ $text = preg_replace("/\[mail\=([$MAILSearchString]*)\](.*?)\[\/mail\]/", '<a href="mailto:$1" ' . $target . ' rel="nofollow noopener">$2</a>', $text);
}
@@ -1398,6 +1412,9 @@ function bbcode($text, $options = []) {
if (strpos($text,'[b]') !== false) {
$text = preg_replace("(\[b\](.*?)\[\/b\])ism", '<strong>$1</strong>', $text);
}
+ if (strpos($text,'[strong]') !== false) {
+ $text = preg_replace("(\[strong\](.*?)\[\/strong\])ism", '<strong>$1</strong>', $text);
+ }
// Check for Italics text
if (strpos($text,'[i]') !== false) {
$text = preg_replace("(\[i\](.*?)\[\/i\])ism", '<em>$1</em>', $text);
@@ -1730,17 +1747,17 @@ function bbcode($text, $options = []) {
// if video couldn't be embedded, link to it instead.
if (strpos($text,'[/video]') !== false) {
- $text = preg_replace("/\[video\](.*?)\[\/video\]/", '<a href="$1" ' . $target . ' rel="nofollow noopener" >$1</a>', $text);
+ $text = preg_replace("/\[video\](.*?)\[\/video\]/", '<a href="$1" ' . $target . ' rel="nofollow noopener">$1</a>', $text);
}
if (strpos($text,'[/audio]') !== false) {
- $text = preg_replace("/\[audio\](.*?)\[\/audio\]/", '<a href="$1" ' . $target . ' rel="nofollow noopener" >$1</a>', $text);
+ $text = preg_replace("/\[audio\](.*?)\[\/audio\]/", '<a href="$1" ' . $target . ' rel="nofollow noopener">$1</a>', $text);
}
if (strpos($text,'[/zvideo]') !== false) {
- $text = preg_replace("/\[zvideo\](.*?)\[\/zvideo\]/", '<a class="zid" href="$1" ' . $target . ' rel="nofollow noopener" >$1</a>', $text);
+ $text = preg_replace("/\[zvideo\](.*?)\[\/zvideo\]/", '<a class="zid" href="$1" ' . $target . ' rel="nofollow noopener">$1</a>', $text);
}
if (strpos($text,'[/zaudio]') !== false) {
- $text = preg_replace("/\[zaudio\](.*?)\[\/zaudio\]/", '<a class="zid" href="$1" ' . $target . ' rel="nofollow noopener" >$1</a>', $text);
+ $text = preg_replace("/\[zaudio\](.*?)\[\/zaudio\]/", '<a class="zid" href="$1" ' . $target . ' rel="nofollow noopener">$1</a>', $text);
}
// oembed tag
@@ -1754,7 +1771,7 @@ function bbcode($text, $options = []) {
// Summary (e.g. title) is required, earlier revisions only required description (in addition to
// start which is always required). Allow desc with a missing summary for compatibility.
- if ((x($ev,'desc') || x($ev,'summary')) && x($ev,'dtstart')) {
+ if ((!empty($ev['desc']) || !empty($ev['summary'])) && !empty($ev['dtstart'])) {
$sub = format_event_html($ev);
@@ -1806,9 +1823,13 @@ function bbcode($text, $options = []) {
$text = preg_replace("/\<(.*?)(src|href)=(.*?)\&amp\;(.*?)\>/ism", '<$1$2=$3&$4>', $text);
// This is subtle - it's an XSS filter. It only accepts links with a protocol scheme and where
- // the scheme begins with z (zhttp), h (http(s)), f (ftp(s)), m (mailto), t (tel) and named anchors.
+ // the scheme begins with http:, https:, mailto:, tel:, geo: and named anchors.
- $text = preg_replace("/\<(.*?)(src|href)=\"[^zhfmt#](.*?)\>/ism", '<$1$2="">', $text);
+ $text = preg_replace(
+ '/(<[^>]*?\b(?:src|href)\s*=\s*([\'"])\s*)(?!https?:|geo:|mailto:|tel:|#)[^\'"]*?\2/iu',
+ '$1$2$2',
+ $text
+ );
$text = bb_replace_images($text, $saved_images);
diff --git a/include/channel.php b/include/channel.php
index 22b5bcde1..fe67f5304 100644
--- a/include/channel.php
+++ b/include/channel.php
@@ -94,8 +94,10 @@ function validate_channelname($name) {
*/
call_hooks('validate_channelname', $arr);
- if (x($arr, 'message'))
+ if (!empty($arr['message']))
return $arr['message'];
+
+ return null;
}
@@ -217,8 +219,8 @@ function create_identity($arr) {
}
$name = escape_tags($arr['name']);
- $pageflags = ((x($arr,'pageflags')) ? intval($arr['pageflags']) : PAGE_NORMAL);
- $system = ((x($arr,'system')) ? intval($arr['system']) : 0);
+ $pageflags = ((!empty($arr['pageflags'])) ? intval($arr['pageflags']) : PAGE_NORMAL);
+ $system = ((!empty($arr['system'])) ? intval($arr['system']) : 0);
$name_error = validate_channelname($arr['name']);
if($name_error) {
$ret['message'] = $name_error;
@@ -1632,19 +1634,19 @@ function profile_sidebar($profile, $block = 0, $show_connect = true, $details =
$connect_url = z_root() . '/connect/' . $profile['channel_address'];
}
- if((x($profile,'address') == 1)
- || (x($profile,'locality') == 1)
- || (x($profile,'region') == 1)
- || (x($profile,'postal_code') == 1)
- || (x($profile,'country_name') == 1))
+ if(!empty($profile['address'])
+ || !empty($profile['locality'])
+ || !empty($profile['region'])
+ || !empty($profile['postal_code'])
+ || !empty($profile['country_name']))
$location = t('Location:');
$profile['homepage'] = linkify($profile['homepage'],true);
- $gender = ((x($profile,'gender') == 1) ? t('Gender:') : False);
- $marital = ((x($profile,'marital') == 1) ? t('Status:') : False);
- $homepage = ((x($profile,'homepage') == 1) ? t('Homepage:') : False);
- $hometown = ((x($profile,'hometown') == 1) ? t('Hometown:') : False);
+ $gender = ((!empty($profile['gender'])) ? t('Gender:') : False);
+ $marital = ((!empty($profile['marital'])) ? t('Status:') : False);
+ $homepage = ((!empty($profile['homepage'])) ? t('Homepage:') : False);
+ $hometown = ((!empty($profile['hometown'])) ? t('Hometown:') : False);
$profile['online'] = (($profile['online_status'] === 'online') ? t('Online Now') : False);
// logger('online: ' . $profile['online']);
@@ -2428,7 +2430,7 @@ function get_zcard($channel, $observer_hash = '', $args = array()) {
$cover = [ 'href' => z_root() . '/images/default_cover_photos/' . $default_cover . '/' . $cover_width . '.png' ];
}
- $o .= replace_macros(get_markup_template('zcard.tpl'), array(
+ return replace_macros(get_markup_template('zcard.tpl'), array(
'$maxwidth' => $maxwidth,
'$scale' => $scale,
'$translate' => $translate,
@@ -2437,8 +2439,6 @@ function get_zcard($channel, $observer_hash = '', $args = array()) {
'$pphoto' => $pphoto,
'$zcard' => $zcard
));
-
- return $o;
}
@@ -2505,17 +2505,13 @@ function get_zcard_embed($channel, $observer_hash = '', $args = array()) {
$cover = [ 'href' => z_root() . '/images/default_cover_photos/' . $default_cover . '/' . $cover_width . '.png' ];
}
- $o .= replace_macros(get_markup_template('zcard_embed.tpl'),array(
+ return replace_macros(get_markup_template('zcard_embed.tpl'),array(
'$maxwidth' => $maxwidth,
- '$scale' => $scale,
- '$translate' => $translate,
'$size' => $size,
'$cover' => $cover,
'$pphoto' => $pphoto,
'$zcard' => $zcard
));
-
- return $o;
}
/**
@@ -3106,7 +3102,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() {
diff --git a/include/config.php b/include/config.php
index 4dd40eccf..50fe60eb0 100644
--- a/include/config.php
+++ b/include/config.php
@@ -39,8 +39,6 @@ use Zotlabs\Lib as Zlib;
*
* @param string $family The category of the configuration value
*
- * @return Nothing
- *
* @deprecated
* This function is deprecated, use Zotlabs\Lib\Config::Load
* instead.
diff --git a/include/connections.php b/include/connections.php
index efc531171..f0ea8583b 100644
--- a/include/connections.php
+++ b/include/connections.php
@@ -86,7 +86,7 @@ function deliverable_abook_xchans($channel_id, $filter = [], $flatten = true) {
$r = q("SELECT abook_xchan, xchan_network FROM abook LEFT JOIN xchan ON abook_xchan = xchan_hash WHERE
abook_channel = %d $filter_sql
AND abook_self = 0
- AND abook_pending = 0
+ AND abook_blocked = 0
AND abook_archived = 0
AND abook_not_here = 0
AND xchan_network NOT IN ('anon', 'token', 'rss')",
@@ -342,7 +342,7 @@ function remove_all_xchan_resources($xchan, $channel_id = 0) {
if($r) {
foreach($r as $rr) {
- drop_item($rr['id'],false);
+ drop_item($rr['id']);
}
}
@@ -513,7 +513,7 @@ function remove_abook_items($channel_id, $xchan_hash) {
continue;
}
- drop_item($rr['id'],false);
+ drop_item($rr['id'], uid: $channel_id);
}
}
diff --git a/include/conversation.php b/include/conversation.php
index c631d53a2..07e4df088 100644
--- a/include/conversation.php
+++ b/include/conversation.php
@@ -3,6 +3,7 @@
use Zotlabs\Lib\Activity;
use Zotlabs\Lib\Apps;
use Zotlabs\Lib\Config;
+use Zotlabs\Lib\PConfig;
require_once('include/items.php');
@@ -176,9 +177,7 @@ function localize_item(&$item){
case ACTIVITY_OBJ_NOTE:
case 'Note':
default:
- $post_type = t('post');
- if(((isset($obj['parent']) && isset($obj['id']) && $obj['id'] != $obj['parent'])) || isset($obj['inReplyTo']))
- $post_type = t('comment');
+ $post_type = t('message');
break;
}
@@ -346,20 +345,20 @@ function conversation($items, $mode, $update, $page_mode = 'traditional', $prepa
. "<script> var profile_uid = " . $_SESSION['uid']
. "; var netargs = '" . substr(App::$cmd,8)
. '?f='
- . ((x($_GET,'cid')) ? '&cid=' . $_GET['cid'] : '')
- . ((x($_GET,'search')) ? '&search=' . $_GET['search'] : '')
- . ((x($_GET,'star')) ? '&star=' . $_GET['star'] : '')
- . ((x($_GET,'order')) ? '&order=' . $_GET['order'] : '')
- . ((x($_GET,'bmark')) ? '&bmark=' . $_GET['bmark'] : '')
- . ((x($_GET,'liked')) ? '&liked=' . $_GET['liked'] : '')
- . ((x($_GET,'conv')) ? '&conv=' . $_GET['conv'] : '')
- . ((x($_GET,'spam')) ? '&spam=' . $_GET['spam'] : '')
- . ((x($_GET,'nets')) ? '&nets=' . $_GET['nets'] : '')
- . ((x($_GET,'cmin')) ? '&cmin=' . $_GET['cmin'] : '')
- . ((x($_GET,'cmax')) ? '&cmax=' . $_GET['cmax'] : '')
- . ((x($_GET,'file')) ? '&file=' . $_GET['file'] : '')
- . ((x($_GET,'uri')) ? '&uri=' . $_GET['uri'] : '')
- . ((x($_GET,'pf')) ? '&pf=' . $_GET['pf'] : '')
+ . (!empty($_GET['cid']) ? '&cid=' . $_GET['cid'] : '')
+ . (!empty($_GET['search']) ? '&search=' . $_GET['search'] : '')
+ . (!empty($_GET['star']) ? '&star=' . $_GET['star'] : '')
+ . (!empty($_GET['order']) ? '&order=' . $_GET['order'] : '')
+ . (!empty($_GET['bmark']) ? '&bmark=' . $_GET['bmark'] : '')
+ . (!empty($_GET['liked']) ? '&liked=' . $_GET['liked'] : '')
+ . (!empty($_GET['conv']) ? '&conv=' . $_GET['conv'] : '')
+ . (!empty($_GET['spam']) ? '&spam=' . $_GET['spam'] : '')
+ . (!empty($_GET['nets']) ? '&nets=' . $_GET['nets'] : '')
+ . (!empty($_GET['cmin']) ? '&cmin=' . $_GET['cmin'] : '')
+ . (!empty($_GET['cmax']) ? '&cmax=' . $_GET['cmax'] : '')
+ . (!empty($_GET['file']) ? '&file=' . $_GET['file'] : '')
+ . (!empty($_GET['uri']) ? '&uri=' . $_GET['uri'] : '')
+ . (!empty($_GET['pf']) ? '&pf=' . $_GET['pf'] : '')
. "'; var profile_page = " . App::$pager['page'] . "; </script>\r\n";
}
}
@@ -438,17 +437,22 @@ function conversation($items, $mode, $update, $page_mode = 'traditional', $prepa
$uploading = false;
- if(local_channel()) {
- $cur_channel = App::get_channel();
- if($cur_channel['channel_allow_cid'] === '' && $cur_channel['channel_allow_gid'] === ''
- && $cur_channel['channel_deny_cid'] === '' && $cur_channel['channel_deny_gid'] === ''
+ $channel = App::get_channel();
+ $observer = App::get_observer();
+
+ if (local_channel()) {
+ // Allow uploading if there is no default privacy and the view_storage permission is set to PERMS_PUBLIC
+ if ($channel['channel_allow_cid'] === '' && $channel['channel_allow_gid'] === ''
+ && $channel['channel_deny_cid'] === '' && $channel['channel_deny_gid'] === ''
&& intval(\Zotlabs\Access\PermissionLimits::Get(local_channel(),'view_storage')) === PERMS_PUBLIC) {
$uploading = true;
}
- }
- $channel = App::get_channel();
- $observer = App::get_observer();
+ // Allow uploading if OCAP tokens are enabled
+ if (PConfig::Get(local_channel(), 'system', 'ocap_enabled')) {
+ $uploading = true;
+ }
+ }
if (!$update) {
$_SESSION['return_url'] = App::$query_string;
@@ -461,17 +465,6 @@ function conversation($items, $mode, $update, $page_mode = 'traditional', $prepa
$items = $cb['items'];
- $conv_responses = [
- 'like' => ['title' => t('Likes','title')],
- 'dislike' => ['title' => t('Dislikes','title')],
- 'attendyes' => ['title' => t('Attending','title')],
- 'attendno' => ['title' => t('Not attending','title')],
- 'attendmaybe' => ['title' => t('Might attend','title')],
- 'answer' => [],
- 'announce' => ['title' => t('Repeats','title')],
- ];
-
-
// array with html for each thread (parent+comments)
$threads = array();
$threadsid = -1;
@@ -560,7 +553,7 @@ function conversation($items, $mode, $update, $page_mode = 'traditional', $prepa
$shareable = false;
$verified = (intval($item['item_verified']) ? t('Message signature validated') : '');
- $forged = ((($item['sig']) && (! intval($item['item_verified']))) ? t('Message signature incorrect') : '');
+ $forged = ((!empty($item['sig']) && !intval($item['item_verified'])) ? t('Message signature incorrect') : '');
$unverified = '';
@@ -698,14 +691,10 @@ function conversation($items, $mode, $update, $page_mode = 'traditional', $prepa
$item = $x['item'];
- builtin_activity_puller($item, $conv_responses);
-
- if(! visible_activity($item)) {
+ if (!visible_activity($item)) {
continue;
}
- $mid_uuid_map[$item['mid']] = $item['uuid'];
-
$item['pagedrop'] = $page_dropping;
if($item['id'] == $item['parent'] || $r_preview) {
@@ -714,7 +703,6 @@ function conversation($items, $mode, $update, $page_mode = 'traditional', $prepa
$conv->add_thread($item_object);
if(($page_mode === 'list') || ($page_mode === 'pager_list')) {
- $item_object->set_template('conv_list.tpl');
$item_object->set_display_mode('list');
}
if($mode === 'cards' || $mode === 'articles') {
@@ -724,7 +712,7 @@ function conversation($items, $mode, $update, $page_mode = 'traditional', $prepa
}
}
- $threads = $conv->get_template_data($conv_responses, $mid_uuid_map);
+ $threads = $conv->get_template_data();
if(!$threads) {
logger('[ERROR] conversation : Failed to get template data.', LOGGER_DEBUG);
$threads = array();
@@ -775,7 +763,7 @@ function best_link_url($item) {
$clean_url = isset($item['author-link']) ? normalise_link($item['author-link']) : '';
if($clean_url && local_channel() && (local_channel() == $item['uid'])) {
- if(isset(App::$contacts) && x(App::$contacts, $clean_url)) {
+ if(isset(App::$contacts) && !empty(App::$contacts[$clean_url])) {
if(App::$contacts[$clean_url]['network'] === NETWORK_DFRN) {
$best_url = z_root() . '/redir/' . App::$contacts[$clean_url]['id'];
$sparkle = true;
@@ -817,7 +805,7 @@ function thread_action_menu($item,$mode = '') {
$menu[] = [
'menu' => 'unfollow_thread',
'title' => t('Unfollow Thread'),
- 'icon' => 'minus',
+ 'icon' => 'dash',
'action' => 'dounsubthread(' . $item['id'] . '); return false;',
'href' => '#'
];
@@ -946,101 +934,6 @@ function thread_author_menu($item, $mode = '') {
}
-
-
-
-/**
- * @brief Checks item to see if it is one of the builtin activities (like/dislike, event attendance, consensus items, etc.)
- *
- * Increments the count of each matching activity and adds a link to the author as needed.
- *
- * @param array $item
- * @param array &$conv_responses (already created with builtin activity structure)
- */
-function builtin_activity_puller($item, &$conv_responses) {
-
- // if this item is a post or comment there's nothing for us to do here, just return.
-
- if(activity_match($item['verb'], ['Create', ACTIVITY_POST]) && $item['obj_type'] !== 'Answer')
- return;
-
- foreach($conv_responses as $mode => $v) {
-
- $url = '';
-
- switch($mode) {
- case 'like':
- $verb = ['Like', ACTIVITY_LIKE];
- break;
- case 'dislike':
- $verb = ['Dislike', ACTIVITY_DISLIKE];
- break;
- case 'attendyes':
- $verb = ['Accept', ACTIVITY_ATTEND];
- break;
- case 'attendno':
- $verb = ['Reject', ACTIVITY_ATTENDNO];
- break;
- case 'attendmaybe':
- $verb = ['TentativeAccept', ACTIVITY_ATTENDMAYBE];
- break;
- case 'answer':
- $verb = ['Create', ACTIVITY_POST];
- break;
- case 'announce':
- $verb = 'Announce';
- break;
- default:
- return;
- break;
- }
-
- if((activity_match($item['verb'], $verb)) && ($item['id'] != $item['parent'])) {
-
- $name = (($item['author']['xchan_name']) ? $item['author']['xchan_name'] : t('Unknown'));
-
- $moderate = ((intval($item['item_blocked']) === ITEM_MODERATED) ? '<a href="moderate/' . $item['id'] . '/approve" onclick="moderate_approve(' . $item['id'] . '); return false;" class="text-success pe-2" title="' . t('Approve this item') . '"><i class="fa fa-check" ></i></a><a href="moderate/' . $item['id'] . '/drop" onclick="moderate_drop(' . $item['id'] . '); return false;" class="text-danger pe-2" title="' . t('Delete this item') . '"><i class="fa fa-trash-o" ></i></a>' : '');
-
- $url = (($item['author_xchan'] && $item['author']['xchan_photo_s'])
- ? '<div class="dropdown-item">' . $moderate . '<a href="' . chanlink_hash($item['author_xchan']) . '" class="text-reset">' . '<img class="menu-img-1" src="' . zid($item['author']['xchan_photo_s']) . '" alt="' . urlencode($name) . '" /> ' . $name . '</a></div>'
- : '<a class="dropdown-item" href="#" class="disabled">' . $name . '</a>'
- );
-
-
-
- if(! $item['thr_parent'])
- $item['thr_parent'] = $item['parent_mid'];
-
- $conv_responses[$mode]['mids'][$item['thr_parent']][] = $item['uuid'];
-
- if($item['obj_type'] === 'Answer')
- continue;
-
- if(! ((isset($conv_responses[$mode][$item['thr_parent'] . '-l']))
- && (is_array($conv_responses[$mode][$item['thr_parent'] . '-l']))))
- $conv_responses[$mode][$item['thr_parent'] . '-l'] = array();
-
- // only list each unique author once
- if(in_array($url,$conv_responses[$mode][$item['thr_parent'] . '-l']))
- continue;
-
- if(! isset($conv_responses[$mode][$item['thr_parent']]))
- $conv_responses[$mode][$item['thr_parent']] = 1;
- else
- $conv_responses[$mode][$item['thr_parent']] ++;
-
- $conv_responses[$mode][$item['thr_parent'] . '-l'][] = $url;
- if(get_observer_hash() && get_observer_hash() === $item['author_xchan']) {
- $conv_responses[$mode][$item['thr_parent'] . '-m'] = true;
- }
-
- // there can only be one activity verb per item so if we found anything, we can stop looking
- return;
- }
- }
-}
-
-
/**
* @brief Format the like/dislike text for a profile item.
*
@@ -1098,54 +991,52 @@ function status_editor($x, $popup = false, $module='') {
function hz_status_editor($x, $popup = false) {
- $o = '';
-
$c = channelx_by_n($x['profile_uid']);
if($c && $c['channel_moved'])
- return $o;
+ return;
- $webpage = ((x($x,'webpage')) ? $x['webpage'] : '');
+ $webpage = ((!empty($x['webpage'])) ? $x['webpage'] : '');
$plaintext = true;
$feature_nocomment = feature_enabled($x['profile_uid'], 'disable_comments');
- if(x($x, 'disable_comments'))
+ if(!empty($x['disable_comments']))
$feature_nocomment = false;
$feature_expire = ((feature_enabled($x['profile_uid'], 'content_expire') && (! $webpage)) ? true : false);
- if(x($x, 'hide_expire'))
+ if(!empty($x['hide_expire']))
$feature_expire = false;
$feature_future = ((feature_enabled($x['profile_uid'], 'delayed_posting') && (! $webpage)) ? true : false);
- if(x($x, 'hide_future'))
+ if(!empty($x['hide_future']))
$feature_future = false;
$geotag = ((isset($x['allow_location']) && $x['allow_location']) ? replace_macros(get_markup_template('jot_geotag.tpl'), array()) : '');
$setloc = t('Set your location');
$clearloc = ((get_pconfig($x['profile_uid'], 'system', 'use_browser_location')) ? t('Clear browser location') : '');
- if(x($x, 'hide_location'))
+ if(!empty($x['hide_location']))
$geotag = $setloc = $clearloc = '';
- $mimetype = ((x($x,'mimetype')) ? $x['mimetype'] : 'text/bbcode');
+ $mimetype = ((!empty($x['mimetype'])) ? $x['mimetype'] : 'text/bbcode');
- $mimeselect = ((x($x,'mimeselect')) ? $x['mimeselect'] : false);
+ $mimeselect = ((!empty($x['mimeselect'])) ? $x['mimeselect'] : false);
if($mimeselect)
$mimeselect = mimetype_select($x['profile_uid'], $mimetype);
else
$mimeselect = '<input type="hidden" name="mimetype" value="' . $mimetype . '" />';
$weblink = (($mimetype === 'text/bbcode') ? t('Insert web link') : false);
- if(x($x, 'hide_weblink'))
+ if(!empty($x['hide_weblink']))
$weblink = false;
$embedPhotos = t('Embed (existing) photo from your photo albums');
$writefiles = (($mimetype === 'text/bbcode') ? perm_is_allowed($x['profile_uid'], get_observer_hash(), 'write_storage') : false);
- if(x($x, 'hide_attach'))
+ if(!empty($x['hide_attach']))
$writefiles = false;
- $layout = ((x($x,'layout')) ? $x['layout'] : '');
+ $layout = ((!empty($x['layout'])) ? $x['layout'] : '');
- $layoutselect = ((x($x,'layoutselect')) ? $x['layoutselect'] : false);
+ $layoutselect = ((!empty($x['layoutselect'])) ? $x['layoutselect'] : false);
if($layoutselect)
$layoutselect = layout_select($x['profile_uid'], $layout);
else
@@ -1158,7 +1049,7 @@ function hz_status_editor($x, $popup = false) {
else
$id_select = '';
- $reset = ((x($x,'reset')) ? $x['reset'] : '');
+ $reset = ((!empty($x['reset'])) ? $x['reset'] : '');
$feature_auto_save_draft = ((feature_enabled($x['profile_uid'], 'auto_save_draft')) ? "true" : "false");
@@ -1167,14 +1058,14 @@ function hz_status_editor($x, $popup = false) {
$tplmacros = [
'$baseurl' => z_root(),
'$editselect' => (($plaintext) ? 'none' : '/(profile-jot-text|prvmail-text)/'),
- '$pretext' => ((x($x,'pretext')) ? $x['pretext'] : ''),
+ '$pretext' => ((!empty($x['pretext'])) ? $x['pretext'] : ''),
'$geotag' => $geotag,
'$nickname' => $x['nickname'],
'$linkurl' => t('Please enter a link URL:'),
'$term' => t('Tag term:'),
'$whereareu' => t('Where are you right now?'),
- '$editor_autocomplete'=> ((x($x,'editor_autocomplete')) ? $x['editor_autocomplete'] : ''),
- '$bbco_autocomplete'=> ((x($x,'bbco_autocomplete')) ? $x['bbco_autocomplete'] : ''),
+ '$editor_autocomplete'=> ((!empty($x['editor_autocomplete'])) ? $x['editor_autocomplete'] : ''),
+ '$bbco_autocomplete'=> ((!empty($x['bbco_autocomplete'])) ? $x['bbco_autocomplete'] : ''),
'$modalchooseimages' => t('Choose images to embed'),
'$modalchoosealbum' => t('Choose an album'),
'$modaldiffalbum' => t('Choose a different album...'),
@@ -1201,7 +1092,7 @@ function hz_status_editor($x, $popup = false) {
$tpl = get_markup_template('jot.tpl');
$preview = t('Preview');
- if(x($x, 'hide_preview'))
+ if(!empty($x['hide_preview']))
$preview = '';
$defexpire = ((($z = get_pconfig($x['profile_uid'], 'system', 'default_post_expire')) && (! $webpage)) ? $z : '');
@@ -1229,26 +1120,27 @@ function hz_status_editor($x, $popup = false) {
call_hooks('jot_tool', $jotplugins);
$jotnets = '';
- if(x($x,'jotnets')) {
+ if(!empty($x['jotnets'])) {
call_hooks('jot_networks', $jotnets);
}
- $sharebutton = (x($x,'button') ? $x['button'] : t('Share'));
- $placeholdtext = (x($x,'content_label') ? $x['content_label'] : $sharebutton);
+ $sharebutton = (!empty($x['button']) ? $x['button'] : t('Submit'));
+ $placeholdtext = (!empty($x['content_label']) ? $x['content_label'] : t('Start a conversation'));
$tplmacros = [
- '$return_path' => ((x($x, 'return_path')) ? $x['return_path'] : App::$query_string),
+ '$return_path' => ((!empty($x['return_path'])) ? $x['return_path'] : App::$query_string),
'$action' => z_root() . '/item',
'$share' => $sharebutton,
'$placeholdtext' => $placeholdtext,
'$webpage' => $webpage,
- '$placeholdpagetitle' => ((x($x,'ptlabel')) ? $x['ptlabel'] : t('Page link name')),
- '$pagetitle' => (x($x,'pagetitle') ? $x['pagetitle'] : ''),
+ '$placeholdpagetitle' => ((!empty($x['ptlabel'])) ? $x['ptlabel'] : t('Page link name')),
+ '$pagetitle' => (!empty($x['pagetitle']) ? $x['pagetitle'] : ''),
'$id_select' => $id_select,
'$id_seltext' => t('Post as'),
'$writefiles' => $writefiles,
'$bold' => t('Bold'),
'$italic' => t('Italic'),
+ '$highlighter' => t('Highlight selected text'),
'$underline' => t('Underline'),
'$quote' => t('Quote'),
'$code' => t('Code'),
@@ -1271,18 +1163,18 @@ function hz_status_editor($x, $popup = false) {
'$feature_nocomment' => $feature_nocomment,
'$nocomment' => ((array_key_exists('item',$x)) ? $x['item']['item_nocomment'] : 0),
'$clearloc' => $clearloc,
- '$title' => ((x($x, 'title')) ? htmlspecialchars($x['title'], ENT_COMPAT,'UTF-8') : ''),
- '$summary' => ((x($x, 'summary')) ? htmlspecialchars($x['summary'], ENT_COMPAT,'UTF-8') : ''),
- '$placeholdertitle' => ((x($x, 'placeholdertitle')) ? $x['placeholdertitle'] : t('Title (optional)')),
- '$placeholdersummary' => ((x($x, 'placeholdersummary')) ? $x['placeholdersummary'] : t('Summary (optional)')),
+ '$title' => ((!empty($x['title'])) ? htmlspecialchars($x['title'], ENT_COMPAT,'UTF-8') : ''),
+ '$summary' => ((!empty($x['summary'])) ? htmlspecialchars($x['summary'], ENT_COMPAT,'UTF-8') : ''),
+ '$placeholdertitle' => ((!empty($x['placeholdertitle'])) ? $x['placeholdertitle'] : t('Title (optional)')),
+ '$placeholdersummary' => ((!empty($x['placeholdersummary'])) ? $x['placeholdersummary'] : t('Summary (optional)')),
'$catsenabled' => $catsenabled,
- '$category' => ((x($x, 'category')) ? $x['category'] : ''),
+ '$category' => ((!empty($x['category'])) ? $x['category'] : ''),
'$placeholdercategory' => t('Categories (optional, comma-separated list)'),
'$permset' => t('Permission settings'),
- '$ptyp' => ((x($x, 'ptyp')) ? $x['ptyp'] : ''),
- '$content' => ((x($x,'body')) ? htmlspecialchars($x['body'], ENT_COMPAT,'UTF-8') : ''),
- '$attachment' => ((x($x, 'attachment')) ? $x['attachment'] : ''),
- '$post_id' => ((x($x, 'post_id')) ? $x['post_id'] : ''),
+ '$ptyp' => ((!empty($x['ptyp'])) ? $x['ptyp'] : ''),
+ '$content' => ((!empty($x['body'])) ? htmlspecialchars($x['body'], ENT_COMPAT,'UTF-8') : ''),
+ '$attachment' => ((!empty($x['attachment'])) ? $x['attachment'] : ''),
+ '$post_id' => ((!empty($x['post_id'])) ? $x['post_id'] : ''),
'$defloc' => $x['default_location'] ?? '',
'$visitor' => $x['visitor'] ?? '',
'$lockstate' => $x['lockstate'] ?? '',
@@ -1297,7 +1189,7 @@ function hz_status_editor($x, $popup = false) {
'$bang' => $x['bang'] ?? '',
'$profile_uid' => $x['profile_uid'],
'$preview' => $preview,
- '$source' => ((x($x, 'source')) ? $x['source'] : ''),
+ '$source' => ((!empty($x['source'])) ? $x['source'] : ''),
'$jotplugins' => $jotplugins,
'$jotnets' => $jotnets,
'$jotnets_label' => t('Other networks and post services'),
@@ -1312,33 +1204,31 @@ function hz_status_editor($x, $popup = false) {
'$cipher' => $cipher,
'$expiryModalOK' => t('OK'),
'$expiryModalCANCEL' => t('Cancel'),
- '$expanded' => ((x($x, 'expanded')) ? $x['expanded'] : false),
- '$bbcode' => ((x($x, 'bbcode')) ? $x['bbcode'] : false),
+ '$expanded' => ((!empty($x['expanded'])) ? $x['expanded'] : false),
+ '$bbcode' => ((!empty($x['bbcode'])) ? $x['bbcode'] : false),
'$parent' => ((array_key_exists('parent',$x) && $x['parent']) ? $x['parent'] : 0),
'$reset' => $reset,
'$is_owner' => ((local_channel() && (local_channel() == $x['profile_uid'])) ? true : false),
'$customjotheaders' => '',
'$custommoretoolsdropdown' => '',
'$custommoretoolsbuttons' => '',
- '$customsubmitright' => []
+ '$customsubmitright' => [],
+ '$popup' => $popup
];
call_hooks('jot_tpl_filter',$tplmacros);
- $o .= replace_macros($tpl, $tplmacros);
- if ($popup === true) {
- $o = '<div id="jot-popup" style="display:none">' . $o . '</div>';
- }
-
- return $o;
+ return replace_macros($tpl, $tplmacros);
}
function get_item_children($arr, $parent) {
- $children = array();
+ $children = [];
+ $thread_allow = ((local_channel()) ? PConfig::Get(local_channel(), 'system', 'thread_allow', true) : Config::Get('system', 'thread_allow', true));
+
foreach($arr as $item) {
if($item['id'] != $item['parent']) {
- if(Config::Get('system','thread_allow')) {
+ if ($thread_allow) {
// Fallback to parent_mid if thr_parent is not set
$thr_parent = $item['thr_parent'];
if($thr_parent == '')
@@ -1467,14 +1357,18 @@ function render_location_default($item) {
$location = $item['location'];
$coord = $item['coord'];
- if($coord) {
+ if ($coord) {
if($location)
- $location .= '&nbsp;<span class="smalltext">(' . $coord . ')</span>';
+ $location .= '&nbsp;(' . $coord . ')';
else
- $location = '<span class="smalltext">' . $coord . '</span>';
+ $location = $coord;
}
- return $location;
+ if (!$location) {
+ return '';
+ }
+
+ return '<i class="bi bi-geo-alt" title="' . $location . '"></i>';
}
@@ -1521,53 +1415,50 @@ function prepare_page($item) {
));
}
-function get_responses($conv_responses,$response_verbs,$ob,$item) {
+function get_responses($response_verbs, $item) {
$ret = array();
foreach($response_verbs as $v) {
- $ret[$v] = [];
- $ret[$v]['count'] = $conv_responses[$v][$item['mid']] ?? 0;
- $ret[$v]['list'] = ((isset($conv_responses[$v][$item['mid']])) ? $conv_responses[$v][$item['mid'] . '-l'] : '');
- $ret[$v]['button'] = get_response_button_text($v, $ret[$v]['count']);
- $ret[$v]['title'] = $conv_responses[$v]['title'] ?? '';
- $ret[$v]['modal'] = (($ret[$v]['count'] > MAX_LIKERS) ? true : false);
- }
+ if ($v === 'answer') {
+ // we require the structure to collect the response hashes
+ // but we do not use them for display - do not collect them.
+ continue;
+ }
- $count = 0;
- foreach ($ret as $key) {
- if ($key['count'] == true)
- $count++;
+ $ret[$v]['count'] = $item[$v . '_count'] ?? 0;
+ $ret[$v]['button'] = get_response_button_text($v, $ret[$v]['count'], $item['item_thread_top']);
}
- $ret['count'] = $count;
-
//logger('ret: ' . print_r($ret,true));
return $ret;
}
-function get_response_button_text($v,$count) {
+function get_response_button_text($v, $count = 0, $top_level = 0) {
switch($v) {
case 'like':
- return ['label' => tt('Like','Likes',$count,'noun'), 'icon' => 'thumbs-o-up', 'class' => 'like'];
+ return ['label' => tt('Like','Likes',$count,'noun'), 'icon' => 'hand-thumbs-up', 'class' => 'like', 'action' => 'dolike'];
break;
case 'announce':
- return ['label' => tt('Repeat','Repeats',$count,'noun'), 'icon' => 'retweet', 'class' => 'announce'];
+ return ['label' => tt('Repeat','Repeats',$count,'noun'), 'icon' => 'repeat', 'class' => 'announce', 'action' => 'jotShare'];
break;
case 'dislike':
- return ['label' => tt('Dislike','Dislikes',$count,'noun'), 'icon' => 'thumbs-o-down', 'class' => 'dislike'];
+ return ['label' => tt('Dislike','Dislikes',$count,'noun'), 'icon' => 'hand-thumbs-down', 'class' => 'dislike', 'action' => 'dolike'];
+ break;
+ case 'comment':
+ return ['label' => (($top_level) ? tt('Comment', 'Comments' ,$count, 'noun') : tt('Reply', 'Replies', $count, 'noun')), 'icon' => 'chat', 'class' => 'comment', 'action' => ''];
break;
- case 'attendyes':
- return ['label' => tt('Attending','Attending',$count,'noun'), 'icon' => 'calendar-check-o', 'class' => 'attendyes'];
+ case 'accept':
+ return ['label' => tt('Attending','Attending',$count,'noun'), 'icon' => 'calendar-check', 'class' => 'accept', 'action' => 'dolike'];
break;
- case 'attendno':
- return ['label' => tt('Not Attending','Not Attending',$count,'noun'), 'icon' => 'calendar-times-o', 'class' => 'attendno'];
+ case 'reject':
+ return ['label' => tt('Not attending','Not attending',$count,'noun'), 'icon' => 'calendar-x', 'class' => 'reject', 'action' => 'dolike'];
break;
- case 'attendmaybe':
- return ['label' => tt('Undecided','Undecided',$count,'noun'), 'icon' => 'calendar-o', 'class' => 'attendmaybe'];
+ case 'tentativeaccept':
+ return ['label' => tt('Undecided','Undecided',$count,'noun'), 'icon' => 'calendar', 'class' => 'tentativeaccept', 'action' => 'dolike'];
break;
default:
- return '';
+ return [];
break;
}
}
diff --git a/include/datetime.php b/include/datetime.php
index 4c7105138..89e2876d0 100644
--- a/include/datetime.php
+++ b/include/datetime.php
@@ -264,6 +264,45 @@ function relative_date($posted_date, $format = null) {
return sprintf($format, $r, plural_dates($str,$r));
}
}
+
+ return $abs;
+}
+
+/**
+ * @brief Returns a relative time string like 3 seconds ago.
+ * @param string $posted_date (UTC)
+ * @param DateTime $now (optional)
+ * @return string with relative time
+ */
+function relative_time($timestamp, $now = new DateTime()) {
+ $localtime = datetime_convert('UTC', date_default_timezone_get(), $timestamp);
+ $time = new DateTime($localtime);
+
+ $interval = $now->diff($time);
+
+ $prefix = '';
+ $appendix = ' ' . t('ago');
+
+ if ($time > $now) {
+ $prefix = t('in') . ' ';
+ $appendix = '';
+ }
+
+ if ($interval->y > 0) {
+ return $prefix . $interval->y . ' ' . plural_dates('y', $interval->y) . $appendix;
+ } elseif ($interval->m > 0) {
+ return $prefix . $interval->m . ' ' . plural_dates('m', $interval->m) . $appendix;
+ } elseif ($interval->d > 0) {
+ return $prefix . $interval->d . ' ' . plural_dates('d', $interval->d) . $appendix;
+ } elseif ($interval->h > 0) {
+ return $prefix . $interval->h . ' ' . plural_dates('h', $interval->h) . $appendix;
+ } elseif ($interval->i > 0) {
+ return $prefix . $interval->i . ' ' . plural_dates('i', $interval->i) . $appendix;
+ } elseif ($interval->s > 0) {
+ return $prefix . $interval->s . ' ' . plural_dates('s', $interval->s) . $appendix;
+ } else {
+ return t('now');
+ }
}
function plural_dates($k,$n) {
@@ -525,7 +564,7 @@ function update_birthdays() {
$z = event_store_event($ev);
if ($z) {
- $item_id = event_store_item($ev, $z);
+ event_store_item($ev, $z, false);
q("update abook set abook_dob = '%s' where abook_id = %d",
dbesc(intval($rr['abook_dob']) + 1 . substr($rr['abook_dob'], 4)),
intval($rr['abook_id'])
diff --git a/include/dba/dba_pdo.php b/include/dba/dba_pdo.php
index c8a1b6c85..3b0edefcd 100644
--- a/include/dba/dba_pdo.php
+++ b/include/dba/dba_pdo.php
@@ -10,6 +10,8 @@ class dba_pdo extends dba_driver {
public $driver_dbtype = null;
+ private string $server_version = '';
+
/**
* {@inheritDoc}
* @see dba_driver::connect()
@@ -37,6 +39,7 @@ class dba_pdo extends dba_driver {
try {
$this->db = new PDO($dsn,$user,$pass);
$this->db->setAttribute(PDO::ATTR_ERRMODE,PDO::ERRMODE_EXCEPTION);
+ $this->server_version = $this->db->getAttribute(PDO::ATTR_SERVER_VERSION);
}
catch(PDOException $e) {
if(file_exists('dbfail.out')) {
@@ -73,9 +76,9 @@ class dba_pdo extends dba_driver {
}
}
- $result = null;
+ $result = false;
$this->error = '';
- $select = ((stripos($sql, 'select') === 0) ? true : false);
+ $select = stripos($sql, 'select') === 0 || stripos($sql, 'with') === 0 || stripos($sql, 'returning ') > 0;
try {
$result = $this->db->query($sql, PDO::FETCH_ASSOC);
@@ -115,6 +118,111 @@ class dba_pdo extends dba_driver {
return (($this->error) ? false : $r);
}
+ /**
+ * Insert a row into a table.
+ *
+ * The `$data` argument is an array of key/value pairs of the columns to
+ * insert, where the key is the column name. Values are automatically
+ * escaped if needed, and should be provided unescaped to this function.
+ *
+ * @note it is the callers responsibility to ensure that only valid
+ * column names are passed as keys in the array.
+ *
+ * The inserted row will be returned.
+ *
+ * @param string $table The table to insert the row into.
+ * @param array $data The data to insert as an array of column name => value pairs.
+ * @param string $idcol The column name for the primary key of the table. We need to
+ * specify this since we don't have a consistent naming of primary
+ * id for tables.
+ *
+ * @return array|bool The complete record as read back from the database, or false if we
+ * could not fetch it.
+ */
+ public function insert(string $table, array $data, string $idcol): array|bool {
+ $keys = array_keys($data);
+ $values = array_map(
+ fn ($v) => is_numeric($v) ? $v : "'" . dbesc($v) . "'",
+ array_values($data)
+ );
+
+ $query = "INSERT INTO {$table} ("
+ . implode(', ', $keys) . ') VALUES ('
+ . implode(', ', $values) . ')';
+
+ // MySQL is the only supported DB that don't support the returning
+ // clause. Since the driver type is 'mysql' also for MariaDB, we need
+ // to check the actual server version to be sure we only exclude actual
+ // MySQL systems.
+ if ($this->driver_dbtype !== 'mysql' || stripos($this->server_version, 'mariadb') !== false) {
+ $query .= ' RETURNING *';
+ }
+
+ $res = $this->q($query);
+
+ if (is_a($res, PDOStatement::class)) {
+ //
+ // Calling PDO::lastInsertId should be safe here.
+ // The last inserted id is kept for each connection, so we're not risking
+ // a race condition wrt inserts by other requests that happen simultaneously.
+ //
+ $id = $this->db->lastInsertId($table);
+
+ $res = $this->q("SELECT * FROM {$table} WHERE {$idcol} = {$id}");
+
+ if (is_a($res, PDOStatement::class)) {
+ db_logger('dba_pdo: PDOStatement returned, did not expect that.');
+ return false;
+ }
+ }
+
+ if (is_array($res)) {
+ // Since we should never have more than one result, unwrap the array
+ // so we only have the resulting row.
+ $res = $res[0];
+ }
+
+ return $res;
+ }
+
+ /**
+ * Update an existing row in a table.
+ *
+ * The `$data` argument is an array of key/value pairs of the columns to
+ * update, where the key is the column name. Values are automatically
+ * escaped if needed, and should be provided unescaped to this function.
+ *
+ * @note it is the callers responsibility to ensure that only valid
+ * column names are passed as keys in the array.
+ *
+ * The row to be updated is identified by `$idcol` and `$idval` as the
+ * column name and value respectively. This should normally be the unique
+ * id column of the table, but can in theory be any column with a unique
+ * value that identifies a specific row.
+ *
+ * @param string $table The table to update.
+ * @param array $data The columns to update as key => value pairs.
+ * @param string $idcol The name of the id column to check $idval against.
+ * @param mixed $idval The id of the row to update.
+ *
+ * @return bool True if the update succeeded, false otherwise.
+ */
+ public function update(string $table, array $data, string $idcol, mixed $idval): bool {
+ $set_statements = [];
+
+ foreach ($data as $k => $v) {
+ $set_statements[] = "set {$k}=" . (is_numeric($v) ? $v : "'" . dbesc($v) . "'");
+ }
+
+ $query = "UPDATE {$table} "
+ . implode(', ', $set_statements)
+ . " WHERE {$idcol} = {$idval}";
+
+ $res = $this->q($query);
+
+ return is_a($res, PDOStatement::class);
+ }
+
function escape($str) {
if($this->db && $this->connected) {
return substr(substr(@$this->db->quote($str),1),0,-1);
diff --git a/include/event.php b/include/event.php
index f8511cbe2..39d0c49c2 100644
--- a/include/event.php
+++ b/include/event.php
@@ -13,7 +13,7 @@ use Zotlabs\Lib\Libsync;
use Zotlabs\Access\AccessList;
use Ramsey\Uuid\Uuid;
-use Ramsey\Uuid\Exception\UnsatisfiedDependencyException;
+use Ramsey\Uuid\Exception\UnableToBuildUuidException;
require_once('include/bbcode.php');
@@ -38,7 +38,7 @@ function format_event_html($ev) {
$o = '<div class="vevent">' . "\r\n";
- $o .= '<div class="event-title"><h3><i class="fa fa-calendar"></i>&nbsp;' . zidify_links(smilies(bbcode($ev['summary']))) . '</h3></div>' . "\r\n";
+ $o .= '<div class="event-title"><h3><i class="bi bi-calendar-date"></i>&nbsp;' . zidify_links(smilies(bbcode($ev['summary']))) . '</h3></div>' . "\r\n";
$o .= '<div class="event-start"><span class="event-label">' . t('Starts:') . '</span>&nbsp;<span class="dtstart" title="'
. datetime_convert('UTC', 'UTC', $ev['dtstart'], ((isset($ev['adjust']) && $ev['adjust']) ? ATOM_TIME : 'Y-m-d\TH:i:s' ))
@@ -102,7 +102,15 @@ function format_event_obj($jobject) {
if (is_array($object) && (array_key_exists('summary', $object) || array_key_exists('name', $object))) {
$dtend = ((array_key_exists('endTime', $object)) ? $object['endTime'] : NULL_DATE);
- $title = ((isset($object['summary']) && $object['summary']) ? zidify_links(smilies(bbcode($object['summary']))) : $object['name']);
+
+ $title = $object['name'] ?? '';
+ $content = html2bbcode($object['content']);
+
+ if (isset($object['source']['content']) && strpos($object['source']['content'], '[/event-description]') !== false) {
+ $bbdescription = [];
+ preg_match("/\[event\-description\](.*?)\[\/event\-description\]/ism", $object['source']['content'], $bbdescription);
+ $content = $bbdescription[1];
+ }
// mobilizon sets a timezone in the object
// we will assume that events with an timezone should be adjusted
@@ -146,15 +154,8 @@ function format_event_obj($jobject) {
'$event_tz' => ['label' => t('Timezone'), 'value' => (($tz === date_default_timezone_get()) ? '' : $tz)]
));
-
- $description = [];
-
- if (strpos($object['source']['content'], '[/event-description]') !== false) {
- preg_match("/\[event\-description\](.*?)\[\/event\-description\]/ism", $object['source']['content'], $description);
- }
-
$event['content'] = replace_macros(get_markup_template('event_item_content.tpl'), array(
- '$description' => ((isset($description[1]))? zidify_links(smilies(bbcode($description[1]))) : EMPTY_STR),
+ '$description' => zidify_links(smilies(bbcode($content, ['tryoembed' => false]))),
'$location_label' => t('Location:'),
'$location' => ((array_path_exists('location/name', $object)) ? zidify_links(smilies(bbcode($object['location']['name']))) : EMPTY_STR)
));
@@ -235,7 +236,7 @@ function ical_wrapper($ev) {
if(! ((is_array($ev)) && count($ev)))
return '';
- $o .= "BEGIN:VCALENDAR";
+ $o = "BEGIN:VCALENDAR";
$o .= "\r\nVERSION:2.0";
$o .= "\r\nMETHOD:PUBLISH";
$o .= "\r\nPRODID:-//" . Config::Get('system','sitename') . "//" . Zotlabs\Lib\System::get_platform_name() . "//" . strtoupper(App::$language). "\r\n";
@@ -647,7 +648,7 @@ function event_store_event($arr) {
else {
try {
$hash = Uuid::uuid4()->toString();
- } catch (UnsatisfiedDependencyException $e) {
+ } catch (UnableToBuildUuidException $e) {
$hash = random_string(48);
}
}
@@ -1070,7 +1071,7 @@ function event_import_ical($ical, $uid) {
logger('storing event: ' . print_r($ev,true), LOGGER_ALL);
$event = event_store_event($ev);
if($event) {
- $item_id = event_store_item($ev,$event);
+ event_store_item($ev, $event, false);
return true;
}
}
@@ -1205,7 +1206,7 @@ function event_import_ical_task($ical, $uid) {
logger('storing event: ' . print_r($ev,true), LOGGER_ALL);
$event = event_store_event($ev);
if($event) {
- $item_id = event_store_item($ev,$event);
+ event_store_item($ev, $event, false);
return true;
}
}
@@ -1214,8 +1215,7 @@ function event_import_ical_task($ical, $uid) {
}
-
-function event_store_item($arr, $event) {
+function event_store_item($arr, $event, $deliver = true) {
require_once('include/datetime.php');
require_once('include/items.php');
@@ -1234,7 +1234,7 @@ function event_store_item($arr, $event) {
}
- $item_arr = array();
+ $item_arr = [];
$prefix = '';
// $birthday = false;
@@ -1255,7 +1255,7 @@ function event_store_item($arr, $event) {
$item_arr['comment_policy'] = 'none';
}
- $r = q("SELECT * FROM item WHERE resource_id = '%s' AND resource_type = 'event' and uid = %d LIMIT 1",
+ $r = q("SELECT * FROM item left join xchan on author_xchan = xchan_hash WHERE resource_id = '%s' AND resource_type = 'event' and uid = %d LIMIT 1",
dbesc($event['event_hash']),
intval($arr['uid'])
);
@@ -1292,51 +1292,21 @@ function event_store_item($arr, $event) {
$object = json_encode($x);
- $private = (($arr['allow_cid'] || $arr['allow_gid'] || $arr['deny_cid'] || $arr['deny_gid']) ? 1 : 0);
-
- /**
- * @FIXME can only update sig if we have the author's channel on this site
- * Until fixed, set it to nothing so it won't give us signature errors.
- */
- $sig = '';
-
- q("UPDATE item SET title = '%s', body = '%s', obj = '%s', allow_cid = '%s', allow_gid = '%s', deny_cid = '%s', deny_gid = '%s', edited = '%s', sig = '%s', item_flags = %d, item_private = %d, obj_type = '%s' WHERE id = %d AND uid = %d",
- dbesc($arr['summary']),
- dbesc($prefix . format_event_bbcode($arr)),
- dbesc($object),
- dbesc($arr['allow_cid']),
- dbesc($arr['allow_gid']),
- dbesc($arr['deny_cid']),
- dbesc($arr['deny_gid']),
- dbesc($arr['edited']),
- dbesc($sig),
- intval($r[0]['item_flags']),
- intval($private),
- dbesc('Event'),
- intval($r[0]['id']),
- intval($arr['uid'])
- );
-
- q("delete from term where oid = %d and otype = %d",
- intval($r[0]['id']),
- intval(TERM_OBJ_POST)
- );
-
- if(($arr['term']) && (is_array($arr['term']))) {
- foreach($arr['term'] as $t) {
- q("insert into term (uid,oid,otype,ttype,term,url)
- values(%d,%d,%d,%d,'%s','%s') ",
- intval($arr['uid']),
- intval($r[0]['id']),
- intval(TERM_OBJ_POST),
- intval($t['ttype']),
- dbesc($t['term']),
- dbesc($t['url'])
- );
- }
- }
-
- $item_id = $r[0]['id'];
+ $item_arr['id'] = $r[0]['id'];
+ $item_arr['uid'] = $arr['uid'];
+ $item_arr['obj'] = $object;
+ $item_arr['edited'] = $arr['edited'];
+ $item_arr['allow_cid'] = $arr['allow_cid'];
+ $item_arr['allow_gid'] = $arr['allow_gid'];
+ $item_arr['deny_cid'] = $arr['deny_cid'];
+ $item_arr['deny_gid'] = $arr['deny_gid'];
+ $item_arr['item_private'] = (($arr['allow_cid'] || $arr['allow_gid'] || $arr['deny_cid'] || $arr['deny_gid']) ? 1 : 0);
+ $item_arr['title'] = $arr['summary'];
+ $item_arr['sig'] = '';
+ $item_arr['body'] = $prefix . format_event_bbcode($arr);
+ $item_arr['term'] = $arr['term'];
+
+ $post = item_store_update($item_arr, deliver: $deliver);
/**
* @hooks event_updated
@@ -1344,14 +1314,10 @@ function event_store_item($arr, $event) {
*/
call_hooks('event_updated', $event['id']);
- return $item_id;
+ return $post;
} else {
- $z = q("select * from channel where channel_id = %d limit 1",
- intval($arr['uid'])
- );
-
- $private = (($arr['allow_cid'] || $arr['allow_gid'] || $arr['deny_cid'] || $arr['deny_gid']) ? 1 : 0);
+ $z = channelx_by_n($arr['uid']);
$item_wall = 0;
$item_origin = 0;
@@ -1361,7 +1327,7 @@ function event_store_item($arr, $event) {
$item_arr['id'] = $item['id'];
}
else {
- $wall = (($z[0]['channel_hash'] == $event['event_xchan']) ? true : false);
+ $wall = (($z['channel_hash'] == $event['event_xchan']) ? true : false);
$item_thread_top = 1;
if($wall) {
$item_wall = 1;
@@ -1374,20 +1340,20 @@ function event_store_item($arr, $event) {
$arr['mid'] = z_root() . '/activity/' . $event['event_hash'];
}
- $item_arr['aid'] = $z[0]['channel_account_id'];
+ $item_arr['aid'] = $z['channel_account_id'];
$item_arr['uid'] = $arr['uid'];
$item_arr['uuid'] = $arr['uuid'];
$item_arr['author_xchan'] = $arr['event_xchan'];
$item_arr['mid'] = $arr['mid'];
$item_arr['parent_mid'] = $arr['mid'];
- $item_arr['owner_xchan'] = (($wall) ? $z[0]['channel_hash'] : $arr['event_xchan']);
+ $item_arr['owner_xchan'] = (($wall) ? $z['channel_hash'] : $arr['event_xchan']);
$item_arr['author_xchan'] = $arr['event_xchan'];
$item_arr['title'] = $arr['summary'];
$item_arr['allow_cid'] = $arr['allow_cid'];
$item_arr['allow_gid'] = $arr['allow_gid'];
$item_arr['deny_cid'] = $arr['deny_cid'];
$item_arr['deny_gid'] = $arr['deny_gid'];
- $item_arr['item_private'] = $private;
+ $item_arr['item_private'] = (($arr['allow_cid'] || $arr['allow_gid'] || $arr['deny_cid'] || $arr['deny_gid']) ? 1 : 0);
$item_arr['verb'] = 'Invite';
$item_arr['item_wall'] = $item_wall;
$item_arr['item_origin'] = $item_origin;
@@ -1452,16 +1418,21 @@ function event_store_item($arr, $event) {
$item_arr['obj'] = json_encode($y);
}
+ $item_arr['target'] = [
+ 'id' => str_replace('/item/', '/conversation/', $item_arr['parent_mid']),
+ 'type' => 'Collection',
+ 'attributedTo' => z_root() . '/channel/' . $z['channel_address'],
+ ];
+ $item_arr['tgt_type'] = 'Collection';
+
// propagate the event resource_id so that posts containing it are easily searchable in downstream copies
// of the item which have not stored the actual event. Required for Diaspora event federation as Diaspora
// event_participation messages refer to the event resource_id as a parent, while out own event attendance
// activities refer to the item message_id as the parent.
- set_iconfig($item_arr, 'system','event_id',$event['event_hash'],true);
-
- $res = item_store($item_arr);
+ set_iconfig($item_arr, 'system', 'event_id', $event['event_hash'], true);
- $item_id = $res['item_id'];
+ $post = item_store($item_arr, deliver: $deliver);
/**
* @hooks event_created
@@ -1469,7 +1440,7 @@ function event_store_item($arr, $event) {
*/
call_hooks('event_created', $event['id']);
- return $item_id;
+ return $post;
}
}
diff --git a/include/features.php b/include/features.php
index d527f60e6..65ead6604 100644
--- a/include/features.php
+++ b/include/features.php
@@ -170,12 +170,20 @@ function get_features($filtered = true, $level = (-1)) {
[
'star_posts',
t('Star Posts'),
- t('Ability to mark special posts with a star indicator'),
+ t('Ability to mark conversations with a star'),
false,
Config::Get('feature_lock','star_posts'),
],
[
+ 'filing',
+ t('File Posts'),
+ t('Ability to file posts'),
+ false,
+ Config::Get('feature_lock','filing'),
+ ],
+
+ [
'reply_to',
t('Reply on comment'),
t('Ability to reply on selected comment'),
diff --git a/include/feedutils.php b/include/feedutils.php
index a50214746..1487ea2d6 100644
--- a/include/feedutils.php
+++ b/include/feedutils.php
@@ -228,15 +228,24 @@ function construct_activity_target($item) {
if($item['target']) {
$o = '<as:target>' . "\r\n";
$r = json_decode($item['target'],false);
- if(! $r)
+
+ if (!$r) {
return '';
- if($r->type)
+ }
+
+ if (isset($r->type)) {
$o .= '<as:obj_type>' . xmlify($r->type) . '</as:obj_type>' . "\r\n";
- if($r->id)
+ }
+
+ if (isset($r->id)) {
$o .= '<id>' . xmlify($r->id) . '</id>' . "\r\n";
- if($r->title)
+ }
+
+ if (isset($r->title)) {
$o .= '<title>' . xmlify($r->title) . '</title>' . "\r\n";
- if($r->links) {
+ }
+
+ if (isset($r->link)) {
/** @FIXME !!! */
if(substr($r->link,0,1) === '<') {
if(strstr($r->link,'&') && (! strstr($r->link,'&amp;')))
@@ -244,11 +253,14 @@ function construct_activity_target($item) {
$r->link = preg_replace('/\<link(.*?)\"\>/','<link$1"/>',$r->link);
$o .= $r->link;
}
- else
+ else {
$o .= '<link rel="alternate" type="text/html" href="' . xmlify($r->link) . '" />' . "\r\n";
+ }
}
- if($r->content)
+
+ if(isset($r->content)) {
$o .= '<content type="html" >' . xmlify(bbcode($r->content)) . '</content>' . "\r\n";
+ }
$o .= '</as:target>' . "\r\n";
@@ -311,7 +323,7 @@ function get_atom_author($feed, $item) {
$base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link'];
if($base && count($base)) {
foreach($base as $link) {
- if($link['attribs']['']['rel'] === 'alternate' && (! $res['author_link']))
+ if($link['attribs']['']['rel'] === 'alternate' && (!$author['author_link']))
$author['author_link'] = unxmlify($link['attribs']['']['href']);
if(!x($author, 'author_photo') || ! $author['author_photo']) {
if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
@@ -357,7 +369,7 @@ function get_atom_author($feed, $item) {
if($base && count($base)) {
foreach($base as $link) {
- if($link['attribs']['']['rel'] === 'alternate' && (! $res['author_link']))
+ if($link['attribs']['']['rel'] === 'alternate' && (!$author['author_link']))
$author['author_link'] = unxmlify($link['attribs']['']['href']);
if(! (x($author,'author_photo'))) {
if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo')
@@ -998,7 +1010,7 @@ function process_feed_tombstones($feed,$importer,$contact,$pass) {
if(! intval($item['item_deleted'])) {
logger('deleting item ' . $item['id'] . ' mid=' . $item['mid'], LOGGER_DEBUG);
- drop_item($item['id'],false);
+ drop_item($item['id']);
}
}
}
@@ -1173,15 +1185,19 @@ function consume_feed($xml, $importer, &$contact, $pass = 0) {
intval($importer['channel_id'])
);
-
// Update content if 'updated' changes
- if($r) {
- if(activity_match($datarray['verb'], ['Delete', ACTIVITY_DELETE])
- && $datarray['author_xchan'] === $r[0]['author_xchan']) {
+ if ($r) {
+ // Check ownership
+ if ($datarray['author_xchan'] !== $r[0]['author_xchan']) {
+ logger('stored item author is not imported item author', LOGGER_DEBUG);
+ continue;
+ }
+
+ if (activity_match($datarray['verb'], ['Delete', ACTIVITY_DELETE])) {
if(! intval($r[0]['item_deleted'])) {
logger('deleting item ' . $r[0]['id'] . ' mid=' . $datarray['mid'], LOGGER_DEBUG);
- drop_item($r[0]['id'],false);
+ drop_item($r[0]['id']);
}
continue;
}
@@ -1444,12 +1460,17 @@ function consume_feed($xml, $importer, &$contact, $pass = 0) {
// Update content if 'updated' changes
- if($r) {
- if(isset($datarray['verb']) && activity_match($datarray['verb'], ['Delete', ACTIVITY_DELETE])
- && isset($datarray['author_xchan']) && $datarray['author_xchan'] === $r[0]['author_xchan']) {
+ if ($r) {
+ // Check ownership
+ if ($datarray['author_xchan'] !== $r[0]['author_xchan']) {
+ logger('stored item author is not imported item author', LOGGER_DEBUG);
+ continue;
+ }
+
+ if (isset($datarray['verb']) && activity_match($datarray['verb'], ['Delete', ACTIVITY_DELETE])) {
if(! intval($r[0]['item_deleted'])) {
logger('deleting item ' . $r[0]['id'] . ' mid=' . $datarray['mid'], LOGGER_DEBUG);
- drop_item($r[0]['id'],false);
+ drop_item($r[0]['id'], uid: $importer['channel_id']);
}
continue;
}
diff --git a/include/help.php b/include/help.php
index 12721a30b..2358f1289 100644
--- a/include/help.php
+++ b/include/help.php
@@ -1,6 +1,6 @@
<?php
-use \Michelf\MarkdownExtra;
+use Michelf\MarkdownExtra;
use CommerceGuys\Intl\Language\LanguageRepository;
require_once('include/items.php');
@@ -156,12 +156,12 @@ function store_doc_file($s) {
if($r) {
$item['id'] = $r[0]['id'];
$item['mid'] = $item['parent_mid'] = $r[0]['mid'];
- $x = item_store_update($item);
+ $x = item_store_update($item, deliver: false, addAndSync: false);
}
else {
$item['uuid'] = item_message_id();
$item['mid'] = $item['parent_mid'] = z_root() . '/item/' . $item['uuid'];
- $x = item_store($item);
+ $x = item_store($item, deliver: false, addAndSync: false);
}
return $x;
diff --git a/include/html2bbcode.php b/include/html2bbcode.php
index 8c35cdf03..ba298aa58 100644
--- a/include/html2bbcode.php
+++ b/include/html2bbcode.php
@@ -29,9 +29,6 @@ function node2bbcodesub(&$doc, $oldnode, $attributes, $startbb, $endbb)
foreach ($list as $oldNode) {
- if ($oldnode == 'li')
- hz_syslog(print_r($oldNode,true));
-
$attr = array();
if ($oldNode->attributes->length)
foreach ($oldNode->attributes as $attribute)
@@ -179,7 +176,7 @@ function html2bbcode($message)
//node2bbcode($doc, 'span', array('style'=>'/.*font-size:\s*(\d+)[,;].*/'), '[size=$1]', '[/size]');
//node2bbcode($doc, 'span', array('style'=>'/.*font-size:\s*(.+?)[,;].*/'), '[size=$1]', '[/size]');
- node2bbcode($doc, 'span', array('style'=>'/.*color:\s*(.+?)[,;].*/'), '[color="$1"]', '[/color]');
+ node2bbcode($doc, 'span', array('style'=>'/.*color:\s*(.+?)[,;].*/'), '[color=$1]', '[/color]');
//node2bbcode($doc, 'span', array('style'=>'/.*font-family:\s*(.+?)[,;].*/'), '[font=$1]', '[/font]');
//node2bbcode($doc, 'div', array('style'=>'/.*font-family:\s*(.+?)[,;].*font-size:\s*(\d+?)pt.*/'), '[font=$1][size=$2]', '[/size][/font]');
@@ -217,14 +214,11 @@ function html2bbcode($message)
node2bbcode($doc, 'hr', array(), "[hr]", "");
-// node2bbcode($doc, 'table', array(), "", "");
-// node2bbcode($doc, 'tr', array(), "\n", "");
-// node2bbcode($doc, 'td', array(), "\t", "");
-
- node2bbcode($doc, 'table', array(), "[table]", "[/table]");
node2bbcode($doc, 'th', array(), "[th]", "[/th]");
node2bbcode($doc, 'tr', array(), "[tr]", "[/tr]");
node2bbcode($doc, 'td', array(), "[td]", "[/td]");
+ node2bbcode($doc, 'table', array(), "[table]", "[/table]");
+
node2bbcode($doc, 'h1', array(), "[h1]", "[/h1]");
node2bbcode($doc, 'h2', array(), "[h2]", "[/h2]");
diff --git a/include/html2plain.php b/include/html2plain.php
index 69fb5193a..6d580becf 100644
--- a/include/html2plain.php
+++ b/include/html2plain.php
@@ -120,6 +120,10 @@ function collecturls($message) {
function html2plain($html, $wraplength = 75, $compact = false)
{
+ if (!$html) {
+ return '';
+ }
+
$message = str_replace("\r", "", $html);
// mb_convert_encoding() is deprecated
diff --git a/include/import.php b/include/import.php
index 479c2c255..77d35a3e7 100644
--- a/include/import.php
+++ b/include/import.php
@@ -825,13 +825,13 @@ function import_items($channel, $items, $sync = false, $relocate = null) {
if($item['edited'] >= $r[0]['edited']) {
$item['id'] = $r[0]['id'];
$item['uid'] = $channel['channel_id'];
- $item_result = item_store_update($item,$allow_code,$deliver);
+ $item_result = item_store_update($item, $allow_code, $deliver, addAndSync: false);
}
}
else {
$item['aid'] = $channel['channel_account_id'];
$item['uid'] = $channel['channel_id'];
- $item_result = item_store($item,$allow_code,$deliver);
+ $item_result = item_store($item, $allow_code, $deliver, addAndSync: false);
}
// preserve conversations you've been involved in from being expired
@@ -1886,7 +1886,7 @@ function import_webpage_element($element, $channel, $type) {
$arr['id'] = $i[0]['id'];
// don't update if it has the same timestamp as the original
if($arr['edited'] > $i[0]['edited'])
- $x = item_store_update($arr,$execflag);
+ $x = item_store_update($arr, $execflag, deliver: false, addAndSync: false);
}
else {
if(($i) && (intval($i[0]['item_deleted']))) {
@@ -1897,7 +1897,7 @@ function import_webpage_element($element, $channel, $type) {
);
}
else
- $x = item_store($arr,$execflag);
+ $x = item_store($arr, $execflag, deliver: false, addAndSync: false);
}
if($x && $x['success']) {
diff --git a/include/items.php b/include/items.php
index dc9e9a7d9..b80c5672b 100644
--- a/include/items.php
+++ b/include/items.php
@@ -240,24 +240,46 @@ function comments_are_now_closed($item) {
return false;
}
-function item_normal() {
- $profile_uid = App::$profile['profile_uid'] ?? App::$profile_uid ?? null;
+function item_normal($profile_uid = null, $prefix = 'item') {
+ if ($profile_uid === null) {
+ $profile_uid = App::$profile['profile_uid'] ?? App::$profile_uid ?? null;
+ }
+
$uid = local_channel();
$is_owner = ($uid && intval($profile_uid) === $uid);
- $sql = " and item.item_hidden = 0 and item.item_type = 0 and item.item_deleted = 0
- and item.item_unpublished = 0 and item.item_pending_remove = 0";
+ $sql = " and $prefix.item_hidden = 0 and $prefix.item_type = 0 and $prefix.item_deleted = 0
+ and $prefix.item_unpublished = 0 and $prefix.item_pending_remove = 0";
if ($is_owner) {
- $sql .= " and item.item_blocked IN (0, " . intval(ITEM_MODERATED) . ") and item.item_delayed IN (0, 1) ";
+ $sql .= " and $prefix.item_blocked IN (0, " . intval(ITEM_MODERATED) . ") and $prefix.item_delayed IN (0, 1) ";
}
else {
- $sql .= " and item.item_blocked = 0 and item.item_delayed = 0 ";
+ $sql .= " and $prefix.item_blocked = 0 and $prefix.item_delayed = 0 ";
}
return $sql;
}
+function item_forwardable($item) {
+ if (intval($item['item_unpublished']) ||
+ intval($item['item_delayed']) ||
+ intval($item['item_blocked']) ||
+ intval($item['item_hidden']) ||
+ intval($item['item_restrict']) || // this might change in the future
+ // internal follow/unfollow thread
+ in_array($item['verb'], ['Follow', 'Ignore', ACTIVITY_FOLLOW, ACTIVITY_UNFOLLOW]) ||
+ str_contains($item['postopts'], 'nodeliver') ||
+ // actor not fetchable
+ (isset($item['author']['xchan_network']) && in_array($item['author']['xchan_network'], ['rss', 'anon', 'token']))
+
+ ) {
+ return false;
+ }
+
+ return true;
+}
+
function item_normal_search() {
return " and item.item_hidden = 0 and item.item_type in (0,3,6,7) and item.item_deleted = 0
and item.item_unpublished = 0 and item.item_delayed = 0 and item.item_pending_remove = 0
@@ -361,7 +383,7 @@ function can_comment_on_post($observer_xchan, $item) {
case 'specific':
case 'contacts':
case '':
- if(local_channel() && get_abconfig(local_channel(), (($item['verb'] === ACTIVITY_SHARE) ? $item['source_xchan'] : $item['owner_xchan']), 'their_perms', 'post_comments')) {
+ if(local_channel() && get_abconfig(local_channel(), $item['owner_xchan'], 'their_perms', 'post_comments')) {
return true;
}
if(intval($item['item_wall']) && perm_is_allowed($item['uid'],$observer_xchan,'post_comments')) {
@@ -431,12 +453,13 @@ function add_source_route($iid, $hash) {
* * \e boolean \b success true or false
* * \e array \b activity the resulting activity if successful
*/
-function post_activity_item($arr, $allow_code = false, $deliver = true) {
+
+function post_activity_item($arr, $allow_code = false, $deliver = true, $channel = null, $observer = null, $addAndSync = true) {
$ret = array('success' => false);
$is_comment = false;
- if((($arr['parent']) && $arr['parent'] != $arr['id']) || (($arr['parent_mid']) && $arr['parent_mid'] != $arr['mid']))
+ if((isset($arr['parent'], $arr['id']) && intval($arr['parent']) !== intval($arr['id'])) || (isset($arr['parent_mid'], $arr['mid']) && $arr['parent_mid'] !== $arr['mid']))
$is_comment = true;
if(! array_key_exists('item_origin',$arr))
@@ -446,11 +469,16 @@ function post_activity_item($arr, $allow_code = false, $deliver = true) {
if(! array_key_exists('item_thread_top',$arr) && (! $is_comment))
$arr['item_thread_top'] = 1;
- $channel = App::get_channel();
- $observer = App::get_observer();
+ if (!$channel) {
+ $channel = App::get_channel();
+ }
+
+ if (!$observer) {
+ $observer = App::get_observer();
+ }
- $arr['aid'] = ((x($arr,'aid')) ? $arr['aid'] : $channel['channel_account_id']);
- $arr['uid'] = ((x($arr,'uid')) ? $arr['uid'] : $channel['channel_id']);
+ $arr['aid'] = ((!empty($arr['aid'])) ? $arr['aid'] : $channel['channel_account_id']);
+ $arr['uid'] = ((!empty($arr['uid'])) ? $arr['uid'] : $channel['channel_id']);
if(! perm_is_allowed($arr['uid'],$observer['xchan_hash'],(($is_comment) ? 'post_comments' : 'post_wall'))) {
$ret['message'] = t('Permission denied');
@@ -466,18 +494,18 @@ function post_activity_item($arr, $allow_code = false, $deliver = true) {
$arr['mimetype'] = 'text/bbcode';
- if(! $arr['mid']) {
- $arr['uuid'] = ((x($arr,'uuid')) ? $arr['uuid'] : new_uuid());
+ if(empty($arr['mid'])) {
+ $arr['uuid'] = ((!empty($arr['uuid'])) ? $arr['uuid'] : new_uuid());
}
- $arr['mid'] = ((x($arr,'mid')) ? $arr['mid'] : z_root() . '/item/' . $arr['uuid']);
- $arr['parent_mid'] = ((x($arr,'parent_mid')) ? $arr['parent_mid'] : $arr['mid']);
- $arr['thr_parent'] = ((x($arr,'thr_parent')) ? $arr['thr_parent'] : $arr['mid']);
+ $arr['mid'] = ((!empty($arr['mid'])) ? $arr['mid'] : z_root() . '/item/' . $arr['uuid']);
+ $arr['parent_mid'] = ((!empty($arr['parent_mid'])) ? $arr['parent_mid'] : $arr['mid']);
+ $arr['thr_parent'] = ((!empty($arr['thr_parent'])) ? $arr['thr_parent'] : $arr['mid']);
- $arr['owner_xchan'] = ((x($arr,'owner_xchan')) ? $arr['owner_xchan'] : $channel['channel_hash']);
- $arr['author_xchan'] = ((x($arr,'author_xchan')) ? $arr['author_xchan'] : $observer['xchan_hash']);
+ $arr['owner_xchan'] = ((!empty($arr['owner_xchan'])) ? $arr['owner_xchan'] : $channel['channel_hash']);
+ $arr['author_xchan'] = ((!empty($arr['author_xchan'])) ? $arr['author_xchan'] : $observer['xchan_hash']);
- $arr['verb'] = ((x($arr,'verb')) ? $arr['verb'] : 'Create');
- $arr['obj_type'] = ((x($arr,'obj_type')) ? $arr['obj_type'] : 'Note');
+ $arr['verb'] = ((!empty($arr['verb'])) ? $arr['verb'] : 'Create');
+ $arr['obj_type'] = ((!empty($arr['obj_type'])) ? $arr['obj_type'] : 'Note');
if(! ( array_key_exists('allow_cid',$arr) || array_key_exists('allow_gid',$arr)
|| array_key_exists('deny_cid',$arr) || array_key_exists('deny_gid',$arr))) {
@@ -489,10 +517,18 @@ function post_activity_item($arr, $allow_code = false, $deliver = true) {
$arr['comment_policy'] = map_scope(PermissionLimits::Get($channel['channel_id'],'post_comments'));
- if ((! $arr['plink']) && (intval($arr['item_thread_top']))) {
+ if (empty($arr['plink']) && (intval($arr['item_thread_top']))) {
$arr['plink'] = $arr['mid'];
}
+ if (!$arr['target']) {
+ $arr['target'] = [
+ 'id' => str_replace('/item/', '/conversation/', $arr['parent_mid']),
+ 'type' => 'Collection',
+ 'attributedTo' => z_root() . '/channel/' . $channel['channel_address'],
+ ];
+ $arr['tgt_type'] = 'Collection';
+ }
// for the benefit of plugins, we will behave as if this is an API call rather than a normal online post
@@ -504,34 +540,35 @@ function post_activity_item($arr, $allow_code = false, $deliver = true) {
*/
call_hooks('post_local', $arr);
- if(x($arr, 'cancel')) {
+ if (!empty($arr['cancel'])) {
logger('Post cancelled by plugin.');
return $ret;
}
- $post = item_store($arr,$allow_code,$deliver);
-
- if($post['success']) {
- $post_id = $post['item_id'];
- $ret['success'] = true;
- $ret['item_id'] = $post_id;
- $ret['activity'] = $post['item'];
+ $post = item_store($arr, $allow_code, $deliver, $addAndSync);
- /**
- * @hooks post_local_end
- * Called after a local post operation has completed.
- * * \e array - the item returned from item_store()
- */
- call_hooks('post_local_end', $ret['activity']);
- }
- else
+ if (!$post['success']) {
return $ret;
-
- if($post_id && $deliver) {
- Master::Summon(['Notifier','activity', $post_id]);
}
+ $post_id = $post['item_id'];
$ret['success'] = true;
+ $ret['item_id'] = $post_id;
+ $ret['activity'] = $post['item'];
+
+ /**
+ * @hooks post_local_end
+ * Called after a local post operation has completed.
+ * * \e array - the item returned from item_store()
+ */
+ call_hooks('post_local_end', $ret['activity']);
+
+ if($post_id && $deliver) {
+ Master::Summon(['Notifier', 'activity', $post_id]);
+ if (!empty($post['approval_id'])) {
+ Master::Summon(['Notifier', 'activity', $post['approval_id']]);
+ }
+ }
return $ret;
}
@@ -693,14 +730,14 @@ function get_item_elements($x,$allow_code = false) {
if($arr['edited'] > datetime_convert())
$arr['edited'] = datetime_convert();
- $arr['expires'] = ((x($x,'expires') && $x['expires'])
+ $arr['expires'] = ((!empty($x['expires']) && $x['expires'])
? datetime_convert('UTC','UTC',$x['expires'])
: NULL_DATE);
- $arr['commented'] = ((x($x,'commented') && $x['commented'])
+ $arr['commented'] = ((!empty($x['commented']) && $x['commented'])
? datetime_convert('UTC','UTC',$x['commented'])
: $arr['created']);
- $arr['comments_closed'] = ((x($x,'comments_closed') && $x['comments_closed'])
+ $arr['comments_closed'] = ((!empty($x['comments_closed']) && $x['comments_closed'])
? datetime_convert('UTC','UTC',$x['comments_closed'])
: NULL_DATE);
@@ -1594,7 +1631,7 @@ function item_json_encapsulate($arr, $k) {
* * \e boolean \b success
* * \e int \b item_id
*/
-function item_store($arr, $allow_exec = false, $deliver = true) {
+function item_store($arr, $allow_exec = false, $deliver = true, $addAndSync = true) {
$d = [
'item' => $arr,
@@ -1645,7 +1682,7 @@ function item_store($arr, $allow_exec = false, $deliver = true) {
if(array_key_exists('parent',$arr))
unset($arr['parent']);
- $arr['mimetype'] = ((x($arr,'mimetype')) ? notags(trim($arr['mimetype'])) : 'text/bbcode');
+ $arr['mimetype'] = ((!empty($arr['mimetype'])) ? notags(trim($arr['mimetype'])) : 'text/bbcode');
if(($arr['mimetype'] == 'application/x-php') && (! $allow_exec)) {
logger('item_store: php mimetype but allow_exec is denied.');
@@ -1657,19 +1694,19 @@ function item_store($arr, $allow_exec = false, $deliver = true) {
$arr['summary'] = ((array_key_exists('summary',$arr) && $arr['summary']) ? trim($arr['summary']) : '');
$arr['body'] = ((array_key_exists('body',$arr) && $arr['body']) ? trim($arr['body']) : '');
- $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
- $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
- $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
- $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
- $arr['postopts'] = ((x($arr,'postopts')) ? trim($arr['postopts']) : '');
- $arr['route'] = ((x($arr,'route')) ? trim($arr['route']) : '');
- $arr['uuid'] = ((x($arr,'uuid')) ? trim($arr['uuid']) : '');
- $arr['item_private'] = ((x($arr,'item_private')) ? intval($arr['item_private']) : 0 );
- $arr['item_wall'] = ((x($arr,'item_wall')) ? intval($arr['item_wall']) : 0 );
- $arr['item_type'] = ((x($arr,'item_type')) ? intval($arr['item_type']) : 0 );
+ $arr['allow_cid'] = ((!empty($arr['allow_cid'])) ? trim($arr['allow_cid']) : '');
+ $arr['allow_gid'] = ((!empty($arr['allow_gid'])) ? trim($arr['allow_gid']) : '');
+ $arr['deny_cid'] = ((!empty($arr['deny_cid'])) ? trim($arr['deny_cid']) : '');
+ $arr['deny_gid'] = ((!empty($arr['deny_gid'])) ? trim($arr['deny_gid']) : '');
+ $arr['postopts'] = ((!empty($arr['postopts'])) ? trim($arr['postopts']) : '');
+ $arr['route'] = ((!empty($arr['route'])) ? trim($arr['route']) : '');
+ $arr['uuid'] = ((!empty($arr['uuid'])) ? trim($arr['uuid']) : '');
+ $arr['item_private'] = ((!empty($arr['item_private'])) ? intval($arr['item_private']) : 0 );
+ $arr['item_wall'] = ((!empty($arr['item_wall'])) ? intval($arr['item_wall']) : 0 );
+ $arr['item_type'] = ((!empty($arr['item_type'])) ? intval($arr['item_type']) : 0 );
// obsolete, but needed so as not to throw not-null constraints on some database driveres
- $arr['item_flags'] = ((x($arr,'item_flags')) ? intval($arr['item_flags']) : 0 );
+ $arr['item_flags'] = ((!empty($arr['item_flags'])) ? intval($arr['item_flags']) : 0 );
$arr['lang'] = detect_language($arr['body']);
@@ -1710,32 +1747,32 @@ function item_store($arr, $allow_exec = false, $deliver = true) {
$arr = $translate['item'];
}
- if((x($arr,'obj')) && is_array($arr['obj'])) {
+ if((!empty($arr['obj'])) && is_array($arr['obj'])) {
activity_sanitise($arr['obj']);
$arr['obj'] = json_encode($arr['obj']);
}
- if((x($arr,'target')) && is_array($arr['target'])) {
+ if((!empty($arr['target'])) && is_array($arr['target'])) {
activity_sanitise($arr['target']);
$arr['target'] = json_encode($arr['target']);
}
- if((x($arr,'attach')) && is_array($arr['attach'])) {
+ if((!empty($arr['attach'])) && is_array($arr['attach'])) {
activity_sanitise($arr['attach']);
$arr['attach'] = json_encode($arr['attach']);
}
- $arr['aid'] = ((x($arr,'aid')) ? intval($arr['aid']) : 0);
- $arr['mid'] = ((x($arr,'mid')) ? notags(trim($arr['mid'])) : random_string());
- $arr['revision'] = ((x($arr,'revision') && intval($arr['revision']) > 0) ? intval($arr['revision']) : 0);
+ $arr['aid'] = ((!empty($arr['aid'])) ? intval($arr['aid']) : 0);
+ $arr['mid'] = ((!empty($arr['mid'])) ? notags(trim($arr['mid'])) : random_string());
+ $arr['revision'] = ((!empty($arr['revision']) && intval($arr['revision']) > 0) ? intval($arr['revision']) : 0);
- $arr['author_xchan'] = ((x($arr,'author_xchan')) ? notags(trim($arr['author_xchan'])) : '');
- $arr['owner_xchan'] = ((x($arr,'owner_xchan')) ? notags(trim($arr['owner_xchan'])) : '');
- $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
- $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
- $arr['expires'] = ((x($arr,'expires') !== false) ? datetime_convert('UTC','UTC',$arr['expires']) : NULL_DATE);
- $arr['commented'] = ((x($arr,'commented') !== false) ? datetime_convert('UTC','UTC',$arr['commented']) : datetime_convert());
- $arr['comments_closed'] = ((x($arr,'comments_closed') !== false) ? datetime_convert('UTC','UTC',$arr['comments_closed']) : NULL_DATE);
+ $arr['author_xchan'] = ((!empty($arr['author_xchan'])) ? notags(trim($arr['author_xchan'])) : '');
+ $arr['owner_xchan'] = ((!empty($arr['owner_xchan'])) ? notags(trim($arr['owner_xchan'])) : '');
+ $arr['created'] = ((!empty($arr['created']) !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
+ $arr['edited'] = ((!empty($arr['edited']) !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
+ $arr['expires'] = ((!empty($arr['expires']) !== false) ? datetime_convert('UTC','UTC',$arr['expires']) : NULL_DATE);
+ $arr['commented'] = ((!empty($arr['commented']) !== false) ? datetime_convert('UTC','UTC',$arr['commented']) : datetime_convert());
+ $arr['comments_closed'] = ((!empty($arr['comments_closed']) !== false) ? datetime_convert('UTC','UTC',$arr['comments_closed']) : NULL_DATE);
$arr['html'] = ((array_key_exists('html',$arr)) ? $arr['html'] : '');
if($deliver) {
@@ -1749,26 +1786,26 @@ function item_store($arr, $allow_exec = false, $deliver = true) {
// will still take place through backdoor methods. Since these fields are rarely used
// otherwise, just preserve the original timestamp.
- $arr['received'] = ((x($arr,'received') !== false) ? datetime_convert('UTC','UTC',$arr['received']) : datetime_convert());
- $arr['changed'] = ((x($arr,'changed') !== false) ? datetime_convert('UTC','UTC',$arr['changed']) : datetime_convert());
+ $arr['received'] = ((!empty($arr['received']) !== false) ? datetime_convert('UTC','UTC',$arr['received']) : datetime_convert());
+ $arr['changed'] = ((!empty($arr['changed']) !== false) ? datetime_convert('UTC','UTC',$arr['changed']) : datetime_convert());
}
- $arr['location'] = ((x($arr,'location')) ? notags(trim($arr['location'])) : '');
- $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
- $arr['parent_mid'] = ((x($arr,'parent_mid')) ? notags(trim($arr['parent_mid'])) : '');
- $arr['thr_parent'] = ((x($arr,'thr_parent')) ? notags(trim($arr['thr_parent'])) : $arr['parent_mid']);
- $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : 'Create');
- $arr['obj_type'] = ((x($arr,'obj_type')) ? notags(trim($arr['obj_type'])) : 'Note');
- $arr['obj'] = ((x($arr,'obj')) ? trim($arr['obj']) : '');
- $arr['tgt_type'] = ((x($arr,'tgt_type')) ? notags(trim($arr['tgt_type'])) : '');
- $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
- $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
- $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
- $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
+ $arr['location'] = ((!empty($arr['location'])) ? notags(trim($arr['location'])) : '');
+ $arr['coord'] = ((!empty($arr['coord'])) ? notags(trim($arr['coord'])) : '');
+ $arr['parent_mid'] = ((!empty($arr['parent_mid'])) ? notags(trim($arr['parent_mid'])) : '');
+ $arr['thr_parent'] = ((!empty($arr['thr_parent'])) ? notags(trim($arr['thr_parent'])) : $arr['parent_mid']);
+ $arr['verb'] = ((!empty($arr['verb'])) ? notags(trim($arr['verb'])) : 'Create');
+ $arr['obj_type'] = ((!empty($arr['obj_type'])) ? notags(trim($arr['obj_type'])) : 'Note');
+ $arr['obj'] = ((!empty($arr['obj'])) ? trim($arr['obj']) : '');
+ $arr['tgt_type'] = ((!empty($arr['tgt_type'])) ? notags(trim($arr['tgt_type'])) : '');
+ $arr['target'] = ((!empty($arr['target'])) ? trim($arr['target']) : '');
+ $arr['plink'] = ((!empty($arr['plink'])) ? notags(trim($arr['plink'])) : '');
+ $arr['attach'] = ((!empty($arr['attach'])) ? notags(trim($arr['attach'])) : '');
+ $arr['app'] = ((!empty($arr['app'])) ? notags(trim($arr['app'])) : '');
- $arr['public_policy'] = ((x($arr,'public_policy')) ? notags(trim($arr['public_policy'])) : '' );
+ $arr['public_policy'] = ((!empty($arr['public_policy'])) ? notags(trim($arr['public_policy'])) : '' );
- $arr['comment_policy'] = ((x($arr,'comment_policy')) ? notags(trim($arr['comment_policy'])) : 'contacts' );
+ $arr['comment_policy'] = ((!empty($arr['comment_policy'])) ? notags(trim($arr['comment_policy'])) : 'contacts' );
if(! array_key_exists('item_unseen',$arr))
$arr['item_unseen'] = 1;
@@ -1776,16 +1813,6 @@ function item_store($arr, $allow_exec = false, $deliver = true) {
if((! array_key_exists('item_nocomment',$arr)) && ($arr['comment_policy'] == 'none'))
$arr['item_nocomment'] = 1;
- // handle time travelers
- // Allow a bit of fudge in case somebody just has a slightly slow/fast clock
-
- $d1 = new DateTime('now +10 minutes', new DateTimeZone('UTC'));
- $d2 = new DateTime($arr['created'] . '+00:00');
-
- if($d2 > $d1) {
- $arr['item_delayed'] = 1;
- }
-
if(empty($arr['llink'])) {
$arr['llink'] = z_root() . '/display/' . $arr['uuid'];
}
@@ -1827,7 +1854,7 @@ function item_store($arr, $allow_exec = false, $deliver = true) {
);
}
- if(comments_are_now_closed($r[0])) {
+ if(comments_are_now_closed($r[0]) && !in_array($arr['verb'], ['Add', 'Remove'])) {
logger('item_store: comments closed');
$ret['message'] = 'Comments closed.';
return $ret;
@@ -1882,7 +1909,7 @@ function item_store($arr, $allow_exec = false, $deliver = true) {
$arr['item_private'] = 0;
if(in_array($arr['obj_type'], ['Note','Answer']) && $r[0]['obj_type'] === 'Question' && intval($r[0]['item_wall'])) {
- Activity::update_poll($r[0]['id'], $arr);
+ Activity::update_poll($r[0], $arr);
}
}
@@ -1922,7 +1949,7 @@ function item_store($arr, $allow_exec = false, $deliver = true) {
*/
call_hooks('post_remote', $arr);
- if(x($arr, 'cancel')) {
+ if(!empty($arr['cancel'])) {
logger('Post cancelled by plugin.');
$ret['message'] = 'cancelled.';
return $ret;
@@ -1965,7 +1992,9 @@ function item_store($arr, $allow_exec = false, $deliver = true) {
// find the item we just created
- $r = q("SELECT * FROM item WHERE mid = '%s' AND uid = %d and revision = %d ORDER BY id ASC ",
+ $r = q("SELECT item.*, tp.uuid AS thr_parent_uuid FROM item
+ LEFT JOIN item tp ON item.thr_parent = tp.mid AND item.uid = tp.uid
+ WHERE item.mid = '%s' AND item.uid = %d and item.revision = %d ORDER BY item.id ASC ",
dbesc($arr['mid']),
intval($arr['uid']),
intval($arr['revision'])
@@ -2047,9 +2076,15 @@ function item_store($arr, $allow_exec = false, $deliver = true) {
item_update_parent_commented($arr);
+ if (str_contains($arr['body'], '[/embed]') || str_contains($arr['body'], '[/img]') || str_contains($arr['body'], '[/zmg]')) {
+ Master::Summon(['Cache_embeds', $arr['uuid']]);
+ }
- if(strpos($arr['body'],'[embed]') !== false) {
- Master::Summon([ 'Cache_embeds', $current_post ]);
+ $ret['success'] = true;
+ $ret['item_id'] = $current_post;
+
+ if ($addAndSync) {
+ $ret = addToCollectionAndSync($ret);
}
// If _creating_ a deleted item, don't propagate it further or send out notifications.
@@ -2062,9 +2097,6 @@ function item_store($arr, $allow_exec = false, $deliver = true) {
tag_deliver($arr['uid'],$current_post);
}
- $ret['success'] = true;
- $ret['item_id'] = $current_post;
-
return $ret;
}
@@ -2077,7 +2109,7 @@ function item_store($arr, $allow_exec = false, $deliver = true) {
* @param boolean $deliver (optional) default true
* @return array
*/
-function item_store_update($arr, $allow_exec = false, $deliver = true) {
+function item_store_update($arr, $allow_exec = false, $deliver = true, $addAndSync = true) {
$d = [
'item' => $arr,
@@ -2135,7 +2167,7 @@ function item_store_update($arr, $allow_exec = false, $deliver = true) {
if(array_key_exists('edit',$arr))
unset($arr['edit']);
- $arr['mimetype'] = ((x($arr,'mimetype')) ? notags(trim($arr['mimetype'])) : 'text/bbcode');
+ $arr['mimetype'] = ((!empty($arr['mimetype'])) ? notags(trim($arr['mimetype'])) : 'text/bbcode');
if(($arr['mimetype'] == 'application/x-php') && (! $allow_exec)) {
logger('item_store: php mimetype but allow_exec is denied.');
@@ -2206,12 +2238,12 @@ function item_store_update($arr, $allow_exec = false, $deliver = true) {
unset($arr['thr_parent']);
unset($arr['llink']);
- $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
- $arr['expires'] = ((x($arr,'expires') !== false) ? datetime_convert('UTC','UTC',$arr['expires']) : $orig[0]['expires']);
+ $arr['edited'] = ((!empty($arr['edited'])) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
+ $arr['expires'] = ((!empty($arr['expires'])) ? datetime_convert('UTC','UTC',$arr['expires']) : $orig[0]['expires']);
- $arr['revision'] = ((x($arr,'revision') && $arr['revision'] > 0) ? intval($arr['revision']) : 0);
+ $arr['revision'] = ((!empty($arr['revision'])) ? intval($arr['revision']) : 0);
- if(array_key_exists('comments_closed',$arr) && $arr['comments_closed'] > NULL_DATE)
+ if(array_key_exists('comments_closed',$arr))
$arr['comments_closed'] = datetime_convert('UTC','UTC',$arr['comments_closed']);
else
$arr['comments_closed'] = $orig[0]['comments_closed'];
@@ -2223,15 +2255,15 @@ function item_store_update($arr, $allow_exec = false, $deliver = true) {
$arr['route'] = ((array_key_exists('route',$arr)) ? trim($arr['route']) : $orig[0]['route']);
- $arr['location'] = ((x($arr,'location')) ? notags(trim($arr['location'])) : $orig[0]['location']);
- $arr['uuid'] = ((x($arr,'uuid')) ? notags(trim($arr['uuid'])) : $orig[0]['uuid']);
- $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : $orig[0]['coord']);
- $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : $orig[0]['verb']);
- $arr['obj_type'] = ((x($arr,'obj_type')) ? notags(trim($arr['obj_type'])) : $orig[0]['obj_type']);
- $arr['obj'] = ((x($arr,'obj')) ? trim($arr['obj']) : $orig[0]['obj']);
- $arr['tgt_type'] = ((x($arr,'tgt_type')) ? notags(trim($arr['tgt_type'])) : $orig[0]['tgt_type']);
- $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : $orig[0]['target']);
- $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : $orig[0]['plink']);
+ $arr['location'] = ((!empty($arr['location'])) ? notags(trim($arr['location'])) : $orig[0]['location']);
+ $arr['uuid'] = ((!empty($arr['uuid'])) ? notags(trim($arr['uuid'])) : $orig[0]['uuid']);
+ $arr['coord'] = ((!empty($arr['coord'])) ? notags(trim($arr['coord'])) : $orig[0]['coord']);
+ $arr['verb'] = ((!empty($arr['verb'])) ? notags(trim($arr['verb'])) : $orig[0]['verb']);
+ $arr['obj_type'] = ((!empty($arr['obj_type'])) ? notags(trim($arr['obj_type'])) : $orig[0]['obj_type']);
+ $arr['obj'] = ((!empty($arr['obj'])) ? trim($arr['obj']) : $orig[0]['obj']);
+ $arr['tgt_type'] = ((!empty($arr['tgt_type'])) ? notags(trim($arr['tgt_type'])) : $orig[0]['tgt_type']);
+ $arr['target'] = ((!empty($arr['target'])) ? trim($arr['target']) : $orig[0]['target']);
+ $arr['plink'] = ((!empty($arr['plink'])) ? notags(trim($arr['plink'])) : $orig[0]['plink']);
$arr['allow_cid'] = ((array_key_exists('allow_cid',$arr)) ? trim($arr['allow_cid']) : $orig[0]['allow_cid']);
$arr['allow_gid'] = ((array_key_exists('allow_gid',$arr)) ? trim($arr['allow_gid']) : $orig[0]['allow_gid']);
@@ -2270,11 +2302,11 @@ function item_store_update($arr, $allow_exec = false, $deliver = true) {
$arr['item_pending_remove'] = ((array_key_exists('item_pending_remove',$arr)) ? intval($arr['item_pending_remove']) : $orig[0]['item_pending_remove'] );
$arr['item_blocked'] = ((array_key_exists('item_blocked',$arr)) ? intval($arr['item_blocked']) : $orig[0]['item_blocked'] );
- $arr['sig'] = ((x($arr,'sig')) ? $arr['sig'] : '');
+ $arr['sig'] = ((!empty($arr['sig'])) ? $arr['sig'] : '');
$arr['layout_mid'] = ((array_key_exists('layout_mid',$arr)) ? dbesc($arr['layout_mid']) : $orig[0]['layout_mid'] );
- $arr['public_policy'] = ((x($arr,'public_policy')) ? notags(trim($arr['public_policy'])) : $orig[0]['public_policy'] );
- $arr['comment_policy'] = ((x($arr,'comment_policy')) ? notags(trim($arr['comment_policy'])) : $orig[0]['comment_policy'] );
+ $arr['public_policy'] = ((!empty($arr['public_policy'])) ? notags(trim($arr['public_policy'])) : $orig[0]['public_policy'] );
+ $arr['comment_policy'] = ((!empty($arr['comment_policy'])) ? notags(trim($arr['comment_policy'])) : $orig[0]['comment_policy'] );
/**
* @hooks post_remote_update
@@ -2282,7 +2314,7 @@ function item_store_update($arr, $allow_exec = false, $deliver = true) {
*/
call_hooks('post_remote_update', $arr);
- if(x($arr, 'cancel')) {
+ if(!empty($arr['cancel'])) {
logger('Post cancelled by plugin.');
$ret['message'] = 'cancelled.';
return $ret;
@@ -2334,7 +2366,9 @@ function item_store_update($arr, $allow_exec = false, $deliver = true) {
// fetch an unescaped complete copy of the stored item
- $r = q("select * from item where id = %d",
+ $r = q("SELECT item.*, tp.uuid AS thr_parent_uuid FROM item
+ LEFT JOIN item tp ON item.thr_parent = tp.mid AND item.uid = tp.uid
+ WHERE item.id = %d",
intval($orig_post_id)
);
if($r)
@@ -2348,14 +2382,15 @@ function item_store_update($arr, $allow_exec = false, $deliver = true) {
if(is_array($terms)) {
foreach($terms as $t) {
- q("insert into term (uid,oid,otype,ttype,term,url)
- values(%d,%d,%d,%d,'%s','%s') ",
+ q("insert into term (uid, oid, otype, ttype, term, url, imgurl)
+ values (%d, %d, %d, %d, '%s', '%s', '%s')",
intval($uid),
intval($orig_post_id),
intval(TERM_OBJ_POST),
intval($t['ttype']),
dbesc($t['term']),
- dbesc($t['url'])
+ dbesc($t['url']),
+ dbesc($t['imgurl'] ?? ''),
);
}
$arr['term'] = $terms;
@@ -2386,21 +2421,23 @@ function item_store_update($arr, $allow_exec = false, $deliver = true) {
*/
call_hooks('item_stored_update',$arr);
- if(strpos($arr['body'],'[embed]') !== false) {
- Master::Summon([ 'Cache_embeds', $orig_post_id ]);
+ if (str_contains($arr['body'], '[/embed]') || str_contains($arr['body'], '[/img]') || str_contains($arr['body'], '[/zmg]')) {
+ Master::Summon(['Cache_embeds', $arr['uuid']]);
}
+ $ret['success'] = true;
+ $ret['item_id'] = $orig_post_id;
-
+ if ($addAndSync) {
+ $ret = addToCollectionAndSync($ret);
+ }
if($deliver) {
- send_status_notifications($orig_post_id,$arr);
+ // don't send notify_comment for edits
+ // send_status_notifications($orig_post_id,$arr);
tag_deliver($uid,$orig_post_id);
}
- $ret['success'] = true;
- $ret['item_id'] = $orig_post_id;
-
return $ret;
}
@@ -2448,21 +2485,23 @@ function send_status_notifications($post_id,$item) {
$parent = 0;
$is_reaction = false;
- $thr_parent_id = 0;
+ $thr_parent_id = null;
+ $thr_parent_uuid = null;
$type = ((intval($item['item_private']) === 2) ? NOTIFY_MAIL : NOTIFY_COMMENT);
- if(array_key_exists('verb',$item) && activity_match($item['verb'], ['Like', 'Dislike', ACTIVITY_LIKE, ACTIVITY_DISLIKE])) {
+ if(array_key_exists('verb',$item) && activity_match($item['verb'], ['Like', 'Dislike', ACTIVITY_LIKE, ACTIVITY_DISLIKE, 'Announce'])) {
$type = NOTIFY_LIKE;
- $r = q("select id from item where mid = '%s' and uid = %d limit 1",
+ $r = q("select id, uuid from item where mid = '%s' and uid = %d limit 1",
dbesc($item['thr_parent']),
intval($item['uid'])
);
if ($r) {
$thr_parent_id = $r[0]['id'];
+ $thr_parent_uuid = $r[0]['uuid'];
}
}
@@ -2487,6 +2526,7 @@ function send_status_notifications($post_id,$item) {
dbesc($item['parent_mid']),
intval($item['uid'])
);
+
if($x) {
foreach($x as $xx) {
if($xx['author_xchan'] === $r[0]['channel_hash']) {
@@ -2531,7 +2571,7 @@ function send_status_notifications($post_id,$item) {
'link' => $link,
'verb' => $item['verb'],
'otype' => 'item',
- 'parent' => $thr_parent_id ? $thr_parent_id : $parent,
+ 'parent' => $thr_parent_id ?? $parent,
'parent_mid' => $thr_parent_id ? $item['thr_parent'] : $item['parent_mid']
));
}
@@ -2808,8 +2848,8 @@ function tag_deliver($uid, $item_id) {
'from_xchan' => $item['author_xchan'],
'type' => NOTIFY_TAGSELF,
'item' => $item,
- 'link' => $i[0]['llink'],
- 'verb' => ACTIVITY_TAG,
+ 'link' => $item['llink'],
+ 'verb' => $item['verb'],
'otype' => 'item'
));
@@ -2951,35 +2991,32 @@ function tgroup_check($uid, $item) {
// post to group via DM
if ($is_group) {
- if (intval($item['item_private']) === 2 && $item['mid'] === $item['parent_mid']) {
+ if (intval($item['item_private']) === 2 && $item['mid'] === $item['parent_mid'] && perm_is_allowed($uid, $item['owner_xchan'], 'post_wall')) {
return true;
}
}
-
// see if we already have this item. Maybe it is being updated.
$r = q("select id from item where mid = '%s' and uid = %d limit 1",
dbesc($item['mid']),
intval($uid)
);
- if($r)
+
+ if ($r) {
return true;
+ }
- if(! perm_is_allowed($uid,$item['author_xchan'],'tag_deliver'))
- return false;
+ $u = channelx_by_n($uid);
- $u = q("select * from channel left join xchan on channel_hash = xchan_hash where channel_id = %d limit 1",
- intval($uid)
- );
-
- if(! $u)
+ if (!$u) {
return false;
+ }
$max_forums = Config::Get('system','max_tagged_forums',2);
$matched_forums = 0;
- $link = normalise_link($u[0]['xchan_url']);
+ $link = normalise_link($u['xchan_url']);
$terms = [];
@@ -2996,7 +3033,7 @@ function tgroup_check($uid, $item) {
}
$mention = true;
- logger('tgroup_check: mention found for ' . $u[0]['channel_name']);
+ logger('tgroup_check: mention found for ' . $u['channel_name']);
// At this point we've determined that the person receiving this post was mentioned in it.
// Now let's check if this mention was inside a reshare so we don't spam a forum
@@ -3104,6 +3141,11 @@ function i_am_mentioned($channel, $item, $check_groups = false) {
*/
function start_delivery_chain($channel, $item, $item_id, $parent, $group = false, $edit = false) {
+ if ($item['author_xchan'] === $channel['channel_hash'] && in_array($item['verb'], ['Add', 'Remove'])) {
+ logger('delivery chain already started');
+ return;
+ }
+
$sourced = check_item_source($channel['channel_id'],$item);
if($sourced) {
@@ -3149,17 +3191,113 @@ function start_delivery_chain($channel, $item, $item_id, $parent, $group = false
$item['parent_mid'] = $item['mid'];
$item['thr_parent'] = $item['mid'];
$item['llink'] = z_root() . '/display/' . $item['uuid'];
+ $item['target'] = json_encode([
+ 'id' => str_replace('/item/', '/conversation/', $item['mid']),
+ 'type' => 'Collection',
+ 'attributedTo' => z_root() . '/channel/' . $channel['channel_address']
+ ]);
+ $item['tgt_type'] = 'Collection';
}
+ }
- $r = q("UPDATE item SET author_xchan = '%s', mid = '%s', parent_mid = '%s', thr_parent = '%s', llink = '%s' WHERE id = %d",
- dbesc($item['author_xchan']),
- dbesc($item['mid']),
- dbesc($item['parent_mid']),
- dbesc($item['thr_parent']),
- dbesc($item['llink']),
+ $private = (($channel['channel_allow_cid'] || $channel['channel_allow_gid']
+ || $channel['channel_deny_cid'] || $channel['channel_deny_gid']) ? 1 : 0);
+
+ $new_public_policy = map_scope(PermissionLimits::Get($channel['channel_id'],'view_stream'),true);
+
+ if((! $private) && $new_public_policy)
+ $private = 1;
+
+ $item_wall = 1;
+ $item_origin = (($item['item_deleted']) ? 0 : 1); // item_origin for deleted items is set to 0 in delete_imported_item() to prevent looping. In this case we probably should not set it back to 1 here.
+ $item_uplink = 0;
+ $item_nocomment = 0;
+
+ $flag_bits = $item['item_flags'];
+
+ // maintain the original source, which will be the original item owner and was stored in source_xchan
+ // when we created the delivery fork
+
+ if($parent) {
+ $r = q("update item set source_xchan = '%s' where id = %d",
+ dbesc($parent['source_xchan']),
intval($item_id)
);
}
+ else {
+ $item_uplink = (($item['item_rss']) ? 0 : 1); // Do not set item_uplink for rss items - we can not send anything to them.
+
+ // if this is an edit, item_store_update() will have already updated the item
+ // with the correct value for source_xchan (by ignoring it). We cannot set to owner_xchan
+ // in this case because owner_xchan will point to the parent of this chain
+ // and not the original sender.
+
+ if(!$edit) {
+ $r = q("update item set source_xchan = owner_xchan where id = %d",
+ intval($item_id)
+ );
+ }
+ }
+
+ // this will not work with item_store_update()
+
+ $r = q("update item set item_uplink = %d, item_nocomment = %d, item_flags = %d, owner_xchan = '%s', allow_cid = '%s', allow_gid = '%s',
+ deny_cid = '%s', deny_gid = '%s', item_private = %d, public_policy = '%s', comment_policy = '%s', title = '%s', body = '%s', item_wall = %d, item_origin = %d,
+ author_xchan = '%s', mid = '%s', parent_mid = '%s', thr_parent = '%s', llink = '%s', target = '%s', tgt_type = '%s' where id = %d",
+ intval($item_uplink),
+ intval($item_nocomment),
+ intval($flag_bits),
+ dbesc($channel['channel_hash']),
+ dbesc($channel['channel_allow_cid']),
+ dbesc($channel['channel_allow_gid']),
+ dbesc($channel['channel_deny_cid']),
+ dbesc($channel['channel_deny_gid']),
+ intval($private),
+ dbesc($new_public_policy),
+ dbesc(map_scope(PermissionLimits::Get($channel['channel_id'],'post_comments'))),
+ dbesc($item['title']),
+ dbesc($item['body']),
+ intval($item_wall),
+ intval($item_origin),
+ dbesc($item['author_xchan']),
+ dbesc($item['mid']),
+ dbesc($item['parent_mid']),
+ dbesc($item['thr_parent']),
+ dbesc($item['llink']),
+ dbesc($item['target']),
+ dbesc($item['tgt_type']),
+ intval($item_id)
+ );
+
+ if($r) {
+ $rr = q("select * from item where id = %d",
+ intval($item_id)
+ );
+
+ if ($rr) {
+
+ // this is hackish but since we can not use item_store_update() here,
+ // we will prepare a similar output to feed to addToCollectionAndSync()
+ $ret['success'] = 1;
+ $ret['item_id'] = $rr[0]['id'];
+ $ret['item'] = $rr[0];
+
+ $result = addToCollectionAndSync($ret);
+
+ Master::Summon(['Notifier', 'tgroup', $result['item_id']]);
+ if ($result['approval_id']) {
+ Master::Summon(['Notifier', 'tgroup', $result['approval_id']]);
+ }
+ }
+ }
+ else {
+ logger('start_delivery_chain: failed to update item');
+ // reset the source xchan to prevent loops
+ $r = q("update item set source_xchan = '' where id = %d",
+ intval($item_id)
+ );
+ }
+ return;
}
if ($group && (! $parent)) {
@@ -3181,8 +3319,8 @@ function start_delivery_chain($channel, $item, $item_id, $parent, $group = false
if ($r) {
if (intval($item['item_deleted'])) {
- drop_item($r[0]['id'], false, DROPITEM_PHASE1);
- Master::Summon([ 'Notifier', 'drop', $r[0]['id'] ]);
+ drop_item($r[0]['id'], DROPITEM_PHASE1, uid: $r[0]['uid']);
+ Master::Summon(['Notifier', 'drop' ,$r[0]['id']]);
return;
}
$arr['id'] = intval($r[0]['id']);
@@ -3199,9 +3337,14 @@ 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,
+ // we will create a v5 UUID of the source item mid.
+ // Add some extra entropy to prevent duplicate UUIDs with items where we already
+ // created an UUID from the mid (activities which do not provide an UUID field).
+ $arr['uuid'] = uuid_from_url($item['mid'] . '#group_item');
$arr['mid'] = z_root() . '/item/' . $arr['uuid'];
$arr['parent_mid'] = $arr['mid'];
+ $arr['plink'] = $arr['mid'];
}
$arr['aid'] = $channel['channel_account_id'];
@@ -3262,8 +3405,15 @@ function start_delivery_chain($channel, $item, $item_id, $parent, $group = false
}
$arr['title'] = $item['title'];
- $arr['tgt_type'] = $item['tgt_type'];
- $arr['target'] = $item['target'];
+// $arr['tgt_type'] = $item['tgt_type'];
+// $arr['target'] = $item['target'];
+
+ $arr['tgt_type'] = 'Collection';
+ $arr['target'] = [
+ 'id' => str_replace('/item/', '/conversation/', $arr['parent_mid']),
+ 'type' => 'Collection',
+ 'attributedTo' => channel_url($channel['channel_address'])
+ ];
$arr['term'] = $item['term'];
@@ -3286,10 +3436,14 @@ function start_delivery_chain($channel, $item, $item_id, $parent, $group = false
$post = item_store($arr);
}
- $post_id = $post['item_id'];
+ $post_id = $post['item_id'] ?? 0;
+ $approval_id = $post['approval_id'] ?? 0;
if($post_id) {
Master::Summon([ 'Notifier','tgroup',$post_id ]);
+ if ($approval_id) {
+ Master::Summon(['Notifier', 'tgroup', $approval_id]);
+ }
}
return;
}
@@ -3314,14 +3468,14 @@ function start_delivery_chain($channel, $item, $item_id, $parent, $group = false
if ($edit) {
if (intval($item['item_deleted'])) {
- drop_item($item['id'],false,DROPITEM_PHASE1);
- Master::Summon([ 'Notifier','drop',$item['id'] ]);
+ drop_item($item['id'], DROPITEM_PHASE1, uid: $item['uid']);
+ Master::Summon(['Notifier', 'drop', $item['id']]);
return;
}
return;
}
else {
- $arr['uuid'] = item_message_id();
+ $arr['uuid'] = uuid_from_url($item['mid']);
$arr['mid'] = z_root() . '/activity/' . $arr['uuid'];
$arr['parent_mid'] = $item['parent_mid'];
//IConfig::Set($arr,'activitypub','context', str_replace('/item/','/conversation/',$item['parent_mid']));
@@ -3365,12 +3519,12 @@ function start_delivery_chain($channel, $item, $item_id, $parent, $group = false
$arr['deny_gid'] = $channel['channel_deny_gid'];
$arr['comment_policy'] = map_scope(PermissionLimits::Get($channel['channel_id'],'post_comments'));
- $post = item_store($arr);
- $post_id = $post['item_id'];
+ $post = item_store($arr, deliver: false, addAndSync: false);
+ $post_id = $post['item_id'] ?? 0;
- if ($post_id) {
- Master::Summon([ 'Notifier','tgroup',$post_id ]);
- }
+ if ($post_id) {
+ Master::Summon(['Notifier', 'tgroup', $post_id]);
+ }
q("update channel set channel_lastpost = '%s' where channel_id = %d",
dbesc(datetime_convert()),
@@ -3380,81 +3534,6 @@ function start_delivery_chain($channel, $item, $item_id, $parent, $group = false
return;
}
-
- // Change this copy of the post to a forum head message and deliver to all the tgroup members
- // also reset all the privacy bits to the forum default permissions
-
- $private = (($channel['channel_allow_cid'] || $channel['channel_allow_gid']
- || $channel['channel_deny_cid'] || $channel['channel_deny_gid']) ? 1 : 0);
-
- $new_public_policy = map_scope(PermissionLimits::Get($channel['channel_id'],'view_stream'),true);
-
- if((! $private) && $new_public_policy)
- $private = 1;
-
- $item_wall = 1;
- $item_origin = (($item['item_deleted']) ? 0 : 1); // item_origin for deleted items is set to 0 in delete_imported_item() to prevent looping. In this case we probably should not set it back to 1 here.
- $item_uplink = 0;
- $item_nocomment = 0;
-
- $flag_bits = $item['item_flags'];
-
- // maintain the original source, which will be the original item owner and was stored in source_xchan
- // when we created the delivery fork
-
- if($parent) {
- $r = q("update item set source_xchan = '%s' where id = %d",
- dbesc($parent['source_xchan']),
- intval($item_id)
- );
- }
- else {
- $item_uplink = (($item['item_rss']) ? 0 : 1); // Do not set item_uplink for rss items - we can not send anything to them.
-
- // if this is an edit, item_store_update() will have already updated the item
- // with the correct value for source_xchan (by ignoring it). We cannot set to owner_xchan
- // in this case because owner_xchan will point to the parent of this chain
- // and not the original sender.
-
- if(! $edit) {
- $r = q("update item set source_xchan = owner_xchan where id = %d",
- intval($item_id)
- );
- }
- }
-
- $title = $item['title'];
- $body = $item['body'];
-
- $r = q("update item set item_uplink = %d, item_nocomment = %d, item_flags = %d, owner_xchan = '%s', allow_cid = '%s', allow_gid = '%s',
- deny_cid = '%s', deny_gid = '%s', item_private = %d, public_policy = '%s', comment_policy = '%s', title = '%s', body = '%s', item_wall = %d, item_origin = %d where id = %d",
- intval($item_uplink),
- intval($item_nocomment),
- intval($flag_bits),
- dbesc($channel['channel_hash']),
- dbesc($channel['channel_allow_cid']),
- dbesc($channel['channel_allow_gid']),
- dbesc($channel['channel_deny_cid']),
- dbesc($channel['channel_deny_gid']),
- intval($private),
- dbesc($new_public_policy),
- dbesc(map_scope(PermissionLimits::Get($channel['channel_id'],'post_comments'))),
- dbesc($title),
- dbesc($body),
- intval($item_wall),
- intval($item_origin),
- intval($item_id)
- );
-
- if($r)
- Master::Summon([ 'Notifier','tgroup',$item_id ]);
- else {
- logger('start_delivery_chain: failed to update item');
- // reset the source xchan to prevent loops
- $r = q("update item set source_xchan = '' where id = %d",
- intval($item_id)
- );
- }
}
/**
@@ -3793,7 +3872,7 @@ function item_expire($uid,$days,$comment_days = 7) {
if ($r) {
foreach ($r as $item) {
- drop_item($item['id'], false);
+ drop_item($item['id'], uid: $uid);
}
}
@@ -3806,25 +3885,12 @@ function retain_item($id) {
);
}
-function drop_items($items,$interactive = false,$stage = DROPITEM_NORMAL) {
- $uid = 0;
-
- if(! local_channel() && ! remote_channel())
- return;
-
- if(count($items)) {
+function drop_items($items, $stage = DROPITEM_NORMAL, $force = false, $expire = false) {
+ if ($items) {
foreach($items as $item) {
- $owner = drop_item($item,$interactive,$stage);
- if($owner && ! $uid)
- $uid = $owner;
+ drop_item($item, $stage, $force, expire: $expire);
}
}
-
- // multiple threads may have been deleted, send an expire notification
-
- if($uid) {
- Master::Summon([ 'Notifier','expire',$uid ]);
- }
}
@@ -3837,7 +3903,7 @@ function drop_items($items,$interactive = false,$stage = DROPITEM_NORMAL) {
// $stage = 1 => set deleted flag on the item and perform intial notifications
// $stage = 2 => perform low level delete at a later stage
-function drop_item($id,$interactive = true,$stage = DROPITEM_NORMAL) {
+function drop_item($id, $stage = DROPITEM_NORMAL, $force = false, $uid = 0, $observer_hash = '', $expire = false, $recurse = false) {
// locate item to be deleted
@@ -3845,33 +3911,48 @@ function drop_item($id,$interactive = true,$stage = DROPITEM_NORMAL) {
intval($id)
);
- if((! $r) || (intval($r[0]['item_deleted']) && ($stage === DROPITEM_NORMAL))) {
- if(! $interactive)
- return 0;
- notice( t('Item not found.') . EOL);
- //goaway(z_root() . '/' . $_SESSION['return_url']);
+ if(!$r || (intval($r[0]['item_deleted']) && $stage === DROPITEM_NORMAL)) {
+ return false;
}
$item = $r[0];
- $ok_to_delete = false;
+ if(!$recurse) {
+ drop_related($item, $stage, $force, $uid, $observer_hash, $expire);
+ }
- // system deletion
- if(! $interactive)
- $ok_to_delete = true;
+ $ok_to_delete = false;
// admin deletion
- if(is_site_admin())
+ if(is_site_admin()) {
$ok_to_delete = true;
+ }
// owner deletion
- if(local_channel() && local_channel() == $item['uid'])
+ if(local_channel() && local_channel() == $item['uid']) {
$ok_to_delete = true;
+ }
+
+ // remote delete when nobody is authenticated (called from Libzot and Daemons)
+ if ($uid && intval($uid) === intval($item['uid'])) {
+ $ok_to_delete = true;
+ }
// author deletion
- $observer = App::get_observer();
- if($observer && $observer['xchan_hash'] && ($observer['xchan_hash'] === $item['author_xchan']))
+ if ($observer_hash) {
+ $observer = ['xchan_hash' => $observer_hash];
+ }
+ else {
+ $observer = App::get_observer();
+ }
+
+ if (isset($observer['xchan_hash']) && $observer['xchan_hash'] === $item['author_xchan']) {
$ok_to_delete = true;
+ }
+
+ if (isset($observer['xchan_hash']) && $observer['xchan_hash'] === $item['owner_xchan']) {
+ $ok_to_delete = true;
+ }
if($ok_to_delete) {
@@ -3884,9 +3965,9 @@ function drop_item($id,$interactive = true,$stage = DROPITEM_NORMAL) {
$arr = [
'item' => $item,
- 'interactive' => $interactive,
'stage' => $stage
];
+
/**
* @hooks drop_item
* Called when an 'item' is removed.
@@ -3909,30 +3990,95 @@ function drop_item($id,$interactive = true,$stage = DROPITEM_NORMAL) {
delete_item_lowlevel($item, $stage);
}
- if(! $interactive)
- return 1;
+ return true;
+ }
+ else {
+ return false;
+ }
+}
- // send the notification upstream/downstream as the case may be
- // only send notifications to others if this is the owner's wall item.
- // This isn't optimal. We somehow need to pass to this function whether or not
- // to call the notifier, or we need to call the notifier from the calling function.
- // We'll rely on the undocumented behaviour that DROPITEM_PHASE1 is (hopefully) only
- // set if we know we're going to send delete notifications out to others.
+// If somebody deletes a 'Create' activity, find any associated 'Add/Collection'
+// activity and delete it. And vice versa.
- if((intval($item['item_wall']) && ($stage != DROPITEM_PHASE2)) || ($stage == DROPITEM_PHASE1)) {
- Master::Summon([ 'Notifier','drop',$notify_id ]);
+function drop_related($item, $stage = DROPITEM_NORMAL, $force = false, $uid = 0, $observer_hash = '', $expire = false, $recurse = false) {
+ $allRelated = q("select * from item where parent_mid = '%s' and uid = %d",
+ dbesc($item['parent_mid']),
+ intval($item['uid'])
+ );
+ if (! $allRelated) {
+ return;
+ }
+ if ($item['verb'] === 'Add' && $item['tgt_type'] === 'Collection') {
+ if (is_array($item['obj'])) {
+ $thisItem = $item['obj'];
+ }
+ else {
+ $thisItem = json_decode($item['obj'], true);
+ }
+ if (isset($thisItem['object']['id'])) {
+ $targetMid = $thisItem['object']['id'];
+ }
+ if (!$targetMid) {
+ return;
+ }
+ foreach ($allRelated as $related) {
+ if ($related['mid'] === $targetMid) {
+ drop_item($related['id'], $stage, $force, $uid, $observer_hash, $expire, recurse: true);
+ break;
+ }
}
- //goaway(z_root() . '/' . $_SESSION['return_url']);
}
else {
- if(! $interactive)
- return 0;
- notice( t('Permission denied.') . EOL);
- //goaway(z_root() . '/' . $_SESSION['return_url']);
+ foreach ($allRelated as $related) {
+ if ($related['verb'] === 'Add' && str_contains($related['tgt_type'], 'Collection')) {
+ $thisItem = json_decode($related['obj'], true);
+ if (isset($thisItem['id']) && $thisItem['id'] === str_replace('/item/', '/activity/', $item['mid'])) {
+ drop_item($related['id'], $stage, $force, $uid, $observer_hash, $expire, recurse: true);
+ break;
+ }
+ }
+ }
+ }
+}
+
+
+function find_related($item) {
+ $allRelated = q("select * from item where parent_mid = '%s' and uid = %d",
+ dbesc($item['parent_mid']),
+ intval($item['uid'])
+ );
+ if (! $allRelated) {
+ return false;
}
+ if ($item['verb'] === 'Add' && $item['tgt_type'] === 'Collection') {
+ $thisItem = json_decode($item['obj'],true);
+ if (is_array($thisItem)) {
+ $targetMid = $thisItem['object']['id'];
+ }
+ if (!$targetMid) {
+ return false;
+ }
+ foreach ($allRelated as $related) {
+ if ($related['mid'] === $targetMid) {
+ return $related;
+ }
+ }
+ }
+ else {
+ foreach ($allRelated as $related) {
+ if ($related['verb'] === 'Add' && str_contains($related['tgt_type'], 'Collection')) {
+ $thisItem = json_decode($related['obj'], true);
+ if (isset($thisItem['object']['id']) && $thisItem['object']['id'] === $item['mid']) {
+ return $related;
+ }
+ }
+ }
+ }
+ return false;
}
+
/**
* @warning This function does not check for permission and does not send
* notifications and does not check recursion.
@@ -4656,19 +4802,19 @@ function items_fetch($arr,$channel = null,$observer_hash = null,$client_mode = C
return $items;
}
-function webpage_to_namespace($webpage) {
+function item_type_to_namespace($item_type) {
- if($webpage == ITEM_TYPE_WEBPAGE)
+ if($item_type == ITEM_TYPE_WEBPAGE)
$page_type = 'WEBPAGE';
- elseif($webpage == ITEM_TYPE_BLOCK)
+ elseif($item_type == ITEM_TYPE_BLOCK)
$page_type = 'BUILDBLOCK';
- elseif($webpage == ITEM_TYPE_PDL)
+ elseif($item_type == ITEM_TYPE_PDL)
$page_type = 'PDL';
- elseif($webpage == ITEM_TYPE_CARD)
+ elseif($item_type == ITEM_TYPE_CARD)
$page_type = 'CARD';
- elseif($webpage == ITEM_TYPE_ARTICLE)
+ elseif($item_type == ITEM_TYPE_ARTICLE)
$page_type = 'ARTICLE';
- elseif($webpage == ITEM_TYPE_DOC)
+ elseif($item_type == ITEM_TYPE_DOC)
$page_type = 'docfile';
else
$page_type = 'unknown';
@@ -4677,12 +4823,12 @@ function webpage_to_namespace($webpage) {
}
-function update_remote_id($channel,$post_id,$webpage,$pagetitle,$namespace,$remote_id,$mid) {
+function update_remote_id($channel,$post_id,$item_type,$pagetitle,$namespace,$remote_id,$mid) {
if(! intval($post_id))
return;
- $page_type = webpage_to_namespace($webpage);
+ $page_type = item_type_to_namespace($item_type);
if($page_type == 'unknown' && $namespace && $remote_id) {
$page_type = $namespace;
@@ -5008,7 +5154,7 @@ function fix_attached_permissions($uid, $body, $str_contact_allow, $str_group_al
$attach = array_shift($attach_q);
- $new_public = !(($str_contact_allow || $str_group_allow || $str_contact_deny || $str_group_deny));
+ //$new_public = !(($str_contact_allow || $str_group_allow || $str_contact_deny || $str_group_deny));
$existing_public = !(($attach['allow_cid'] || $attach['allow_gid'] || $attach['deny_cid'] || $attach['deny_gid']));
if ($existing_public) {
@@ -5023,10 +5169,11 @@ function fix_attached_permissions($uid, $body, $str_contact_allow, $str_group_al
continue;
}
- $item_private = 0;
-
- if ($new_public === false) {
- $item_private = (($str_group_allow || ($str_contact_allow && substr_count($str_contact_allow,'<') > 2)) ? 1 : 2);
+ if ($token) {
+ $str_contact_allow = $attach['allow_cid'];
+ $str_group_allow = $attach['allow_gid'];
+ $str_contact_deny = $attach['deny_cid'];
+ $str_group_deny = $attach['deny_gid'];
// preserve any existing tokens that may have been set for this file
$token_matches = null;
@@ -5054,7 +5201,7 @@ function fix_attached_permissions($uid, $body, $str_contact_allow, $str_group_al
);
if ($attach['is_photo']) {
- $r = q("UPDATE photo SET allow_cid = '%s', allow_gid = '%s', deny_cid = '%s', deny_gid = '%s'
+ q("UPDATE photo SET allow_cid = '%s', allow_gid = '%s', deny_cid = '%s', deny_gid = '%s'
WHERE resource_id = '%s' AND uid = %d ",
dbesc($str_contact_allow),
dbesc($str_group_allow),
@@ -5064,7 +5211,16 @@ function fix_attached_permissions($uid, $body, $str_contact_allow, $str_group_al
intval($uid)
);
- $r = q("UPDATE item SET allow_cid = '%s', allow_gid = '%s', deny_cid = '%s', deny_gid = '%s', item_private = %d
+ $item_private = 0;
+
+ if ($str_group_allow || $str_contact_deny || $str_group_deny) {
+ $item_private = 1;
+ }
+ elseif ($str_contact_allow) {
+ $item_private = 2;
+ }
+
+ q("UPDATE item SET allow_cid = '%s', allow_gid = '%s', deny_cid = '%s', deny_gid = '%s', item_private = %d
WHERE resource_id = '%s' AND 'resource_type' = 'photo' AND uid = %d",
dbesc($str_contact_allow),
dbesc($str_group_allow),
@@ -5085,7 +5241,7 @@ function fix_attached_permissions($uid, $body, $str_contact_allow, $str_group_al
* which will allow you to interact with it.
*/
-function copy_of_pubitem($channel,$mid) {
+function copy_of_pubitem($channel, $mid) {
$result = null;
$syschan = get_sys_channel();
@@ -5103,36 +5259,517 @@ 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;
$rv['item_origin'] = 0;
- $x = item_store($rv);
+ $x = item_store($rv, deliver: false, addAndSync: false);
if($x['item_id'] && $x['item']['mid'] === $mid) {
$result = $x['item'];
+ sync_an_item($channel['channel_id'], $x['item_id']);
}
}
}
+
return $result;
}
+
+function addToCollectionAndSync($ret) {
+ if (!$ret['success']) {
+ return $ret;
+ }
+
+ $channel = channelx_by_n($ret['item']['uid']);
+ if ($channel && $channel['channel_hash'] === $ret['item']['owner_xchan']) {
+ $items = [$ret['item']];
+
+ if ((int)$items[0]['item_blocked'] === ITEM_MODERATED
+ || (int)$items[0]['item_unpublished'] || (int)$items[0]['item_delayed']) {
+ return $ret;
+ }
+
+ xchan_query($items);
+ // TODO: fetch_post_tags() will add term and iconfig twice if called twice and it looks like they are already added here
+ // $items = fetch_post_tags($items);
+ $sync_items = [];
+ $sync_items[] = encode_item($items[0], true);
+
+ if (!in_array($ret['item']['verb'], ['Add', 'Remove'])) {
+ $activity = Activity::encode_activity($items[0]);
+
+ if (!$activity) {
+ return $ret;
+ }
+
+ $new_obj = Activity::build_packet($activity, $channel, false);
+ $approval = Activity::addToCollection($channel, $new_obj, $ret['item']['parent_mid'], $ret['item'], deliver: false);
+
+ if ($approval['success']) {
+ $ret['approval_id'] = $approval['item_id'];
+ $ret['approval'] = $approval['activity'];
+ $add_items = [$approval['activity']];
+ xchan_query($add_items);
+ $add_items = fetch_post_tags($add_items);
+ $sync_items[] = encode_item($add_items[0], true);
+ }
+ }
+
+ $resource_type = $ret['item']['resource_type'];
+
+ if ($resource_type === 'event') {
+ $z = q("select * from event where event_hash = '%s' and uid = %d limit 1",
+ dbesc($ret['item']['resource_id']),
+ intval($channel['channel_id'])
+ );
+
+ if ($z) {
+ Libsync::build_sync_packet($channel['channel_id'], ['event_item' => $sync_items, 'event' => $z]);
+ }
+ }
+ elseif ($resource_type === 'photo') {
+ // reserved for future use, currently handled in the photo upload workflow
+ }
+ else {
+ Libsync::build_sync_packet($ret['item']['uid'], ['item' => $sync_items]);
+ }
+ }
+
+ return $ret;
+}
+
+function reverse_activity_mid($string) {
+ return str_replace(z_root() . '/activity/', z_root() . '/item/', $string);
+}
+
+function set_activity_mid($string) {
+ return str_replace(z_root() . '/item/', z_root() . '/activity/', $string);
+}
+
+/**
+ * @brief returns an item by id and parent belonging to local_channel()
+ * including activity counts.
+ * @param int $id
+ * @param int $parent
+ */
+
+function item_by_item_id(int $id, int $parent): array
+{
+ if (!$id && !$parent && !local_channel()) {
+ return [];
+ }
+
+ $item_normal_sql = item_normal();
+
+ $reaction = item_reaction_sql($parent);
+ $reaction_cte_sql = $reaction['cte'];
+ $reaction_select_sql = $reaction['select'];
+ $reaction_join_sql = $reaction['join'];
+
+ return q("WITH
+ $reaction_cte_sql
+ SELECT
+ *,
+ $reaction_select_sql
+ FROM item
+ $reaction_join_sql
+ WHERE
+ item.id = %d
+ AND item.uid = %d
+ AND item.verb IN ('Create', 'Update', 'EmojiReact')
+ $item_normal_sql",
+ intval($id),
+ intval(local_channel())
+ );
+}
+
+
+/**
+ * @brief returns an array of items by ids
+ * ATTENTION: no permissions for the parents are checked here!!!
+ * Permissions MUST be checked by the module which calls this function.
+ * @param array $parents
+ * @param null|array $thr_parents (optional) - thr_parent mids which will be included
+ * @param string $permission_sql (optional) - SQL as provided by item_permission_sql() from the calling module
+ * @param bool $blog_mode (optional) - if set to yes only the parent items will be returned
+ */
+
+function items_by_parent_ids(array $parents, null|array $thr_parents = null, string $permission_sql = '', bool $blog_mode = false): array
+{
+ if (!$parents) {
+ return [];
+ }
+
+ $ids = ids_to_querystr($parents, 'item_id');
+ $thread_allow = ((local_channel()) ? PConfig::Get(local_channel(), 'system', 'thread_allow', true) : Config::Get('system', 'thread_allow', true));
+ $item_normal_sql = item_normal();
+ $limit = $thread_allow ? 3 : 1000;
+
+ $thr_parent_sql = (($thread_allow) ? " AND item.thr_parent = item.parent_mid " : '');
+ if ($thr_parents && $thread_allow) {
+ $limit = 300;
+ $thr_parent_str = stringify_array($thr_parents, true);
+ $thr_parent_sql = " AND item.thr_parent IN (" . protect_sprintf($thr_parent_str) . ") ";
+ }
+
+ $reaction = item_reaction_sql($ids, $permission_sql, 'final_selection');
+ $reaction_cte_sql = $reaction['cte'];
+ $reaction_select_sql = $reaction['select'];
+ $reaction_join_sql = $reaction['join'];
+
+ if ($blog_mode) {
+ $q = <<<SQL
+ WITH
+ final_selection AS (
+ SELECT
+ item.*
+ FROM
+ item
+ WHERE
+ item.id IN ($ids)
+ ),
+
+ $reaction_cte_sql
+
+ SELECT
+ final_selection.*,
+ $reaction_select_sql
+ FROM final_selection
+ $reaction_join_sql
+ SQL;
+
+ return dbq(trim($q));
+ }
+
+ $q = <<<SQL
+ WITH
+ parent_items AS (
+ SELECT
+ item.*,
+ 0 AS rn
+ FROM item
+ WHERE
+ item.id IN ($ids)
+ ),
+
+ $reaction_cte_sql,
+
+ all_comments AS (
+ SELECT
+ item.*,
+ ROW_NUMBER() OVER (PARTITION BY item.parent ORDER BY item.created DESC) AS rn
+ FROM item
+ WHERE item.parent IN ($ids)
+ AND item.verb IN ('Create', 'Update', 'EmojiReact')
+ AND item.item_thread_top = 0
+ $thr_parent_sql
+ $permission_sql
+ $item_normal_sql
+ ),
+
+ final_selection AS (
+ SELECT * FROM parent_items
+ UNION ALL
+ SELECT * FROM all_comments WHERE all_comments.rn <= $limit
+ )
+
+ SELECT
+ final_selection.*,
+ $reaction_select_sql
+ FROM final_selection
+ $reaction_join_sql
+ SQL;
+
+ return dbq(trim($q));
+}
+
+/**
+ * @brief prepare reaction sql for items_by_parent_ids()
+ * ATTENTION: no permissions for the pa are checked here!!!
+ * Permissions MUST be checked by the function which returns the ids.
+ * @param string $ids
+ * @param string $permission_sql (optional) - SQL provided by item_permission_sql()
+ * @param string $join_prefix (optional) - prefix for the join part defaults to 'item'
+ */
+
+function item_reaction_sql(string $ids, string $permission_sql = '', string $join_prefix = 'item'): array
+{
+ $item_normal_sql = item_normal();
+ $observer = get_observer_hash();
+
+ $verbs = [
+ 'like' => ['Like'],
+ 'dislike' => ['Dislike'],
+ 'announce' => ['Announce'],
+ 'accept' => ['Accept'],
+ 'reject' => ['Reject'],
+ 'tentativeaccept' => ['TentativeAccept']
+ ];
+
+ $thread_allow = ((local_channel()) ? PConfig::Get(local_channel(), 'system', 'thread_allow', true) : Config::Get('system', 'thread_allow', true));
+
+ if ($thread_allow) {
+ $verbs['comment'] = ['Create', 'Update', 'EmojiReact'];
+ }
+
+ $cte = '';
+ $select = '';
+ $join = '';
+
+ foreach($verbs as $k => $v) {
+
+ $observer_sql = "0 AS observer_{$k}_count";
+ if ($observer) {
+ $observer_sql = "COUNT(CASE WHEN item.author_xchan = '$observer' THEN 1 END) AS observer_{$k}_count";
+ }
+
+ $verbs_str = stringify_array($v);
+
+ if ($cte) {
+ $cte .= ",\n";
+ }
+
+ $cte .= <<<SQL
+ reaction_{$k} AS (
+ SELECT
+ item.thr_parent,
+ -- COUNT(DISTINCT item.author_xchan) AS {$k}_count, (should we prevent multiple reactions by the same author?)
+ COUNT(*) AS {$k}_count,
+ $observer_sql
+ FROM item
+ WHERE item.verb IN ($verbs_str)
+ AND item.item_thread_top = 0
+ AND item.parent IN ($ids)
+ $item_normal_sql
+ $permission_sql
+ GROUP BY item.thr_parent
+ )
+ SQL;
+
+ if ($select) {
+ $select .= ",\n";
+ }
+
+ $select .= <<<SQL
+ COALESCE(reaction_{$k}.{$k}_count, 0) AS {$k}_count,
+ COALESCE(reaction_{$k}.observer_{$k}_count, 0) AS observer_{$k}_count
+ SQL;
+
+ $join .= <<<SQL
+ LEFT JOIN reaction_{$k} ON reaction_{$k}.thr_parent = $join_prefix.mid
+ SQL;
+
+ }
+
+ $ret['cte'] = $cte;
+ $ret['select'] = $select;
+ $ret['join'] = $join;
+
+ return $ret;
+}
+
+
+
+/**
+ * @brief returns an array of items by thr_parent mid of a parent
+
+ * @param string $mid
+ * @param int $parent
+ * @param int|null $offset
+ */
+
+function items_by_thr_parent(string $mid, int $parent, int|null $offset = null): array
+{
+ if (!$mid && !$parent) {
+ return [];
+ }
+
+ $parent_item = q("SELECT uid FROM item WHERE id = %d",
+ intval($parent)
+ );
+
+ $order_sql = "ORDER BY item.created";
+ if (isset($offset)) {
+ $order_sql = "ORDER BY item.created DESC, item.received DESC LIMIT 3 OFFSET $offset";
+ }
+
+ $owner_uid = intval($parent_item[0]['uid']);
+ $item_normal_sql = item_normal($owner_uid);
+
+ if (local_channel() === $owner_uid) {
+ $reaction = item_reaction_sql($parent);
+ $reaction_cte_sql = $reaction['cte'];
+ $reaction_select_sql = $reaction['select'];
+ $reaction_join_sql = $reaction['join'];
+
+ $ret = q("WITH
+ $reaction_cte_sql
+ SELECT
+ item.*,
+ $reaction_select_sql
+ FROM item
+ $reaction_join_sql
+ WHERE
+ item.thr_parent = '%s'
+ AND item.uid = %d
+ AND item.verb IN ('Create', 'Update', 'EmojiReact')
+ AND item.item_thread_top = 0
+ $item_normal_sql
+ $order_sql",
+ dbesc($mid),
+ intval($owner_uid)
+ );
+ }
+ else {
+ $observer_hash = get_observer_hash();
+ $permission_sql = item_permissions_sql($owner_uid, $observer_hash);
+
+ $reaction = item_reaction_sql($parent, $permission_sql);
+ $reaction_cte_sql = $reaction['cte'];
+ $reaction_select_sql = $reaction['select'];
+ $reaction_join_sql = $reaction['join'];
+
+ $ret = q("WITH
+ $reaction_cte_sql
+ SELECT
+ item.*,
+ $reaction_select_sql
+ FROM item
+ $reaction_join_sql
+ WHERE
+ item.thr_parent = '%s'
+ AND item.uid = %d
+ AND item.verb IN ('Create', 'Update', 'EmojiReact')
+ AND item.item_thread_top = 0
+ $permission_sql
+ $item_normal_sql
+ $order_sql",
+ dbesc($mid),
+ intval($owner_uid)
+ );
+ }
+
+ if (isset($offset)) {
+ $ret = array_reverse($ret);
+ }
+
+ return $ret;
+}
+
+
+/**
+ * @brief returns an array of xchan entries (partly) for activities of an item by mid of a parent.
+ * Also checks if observer is allowed to add activities to the item.
+ * @param string $mid
+ * @param int $parent
+ * @param string $verb
+ */
+
+function item_activity_xchans(string $mid, int $parent, string $verb): array
+{
+ if (!$mid && !$parent && !$verb) {
+ return [];
+ }
+
+ $observer_hash = get_observer_hash();
+ $parent_item = q("SELECT * FROM item WHERE id = %d",
+ intval($parent)
+ );
+
+ $owner_uid = intval($parent_item[0]['uid']);
+ $item_normal = item_normal($owner_uid);
+
+ if (local_channel() === $owner_uid) {
+ $ret = q("SELECT item.id, item.item_blocked, xchan.xchan_hash, xchan.xchan_name as name, xchan.xchan_url as url, xchan.xchan_photo_s as photo FROM item
+ LEFT JOIN xchan ON item.author_xchan = xchan.xchan_hash
+ WHERE item.uid = %d
+ AND item.parent = %d
+ AND item.thr_parent = '%s'
+ AND item.verb = '%s'
+ AND item.item_thread_top = 0
+ $item_normal
+ -- GROUP BY item.author_xchan (should we prevent multiple reactions by the same author?)
+ ORDER BY item.created",
+ intval(local_channel()),
+ intval($parent),
+ dbesc($mid),
+ dbesc($verb)
+ );
+ }
+ else {
+ $sql_extra = item_permissions_sql($owner_uid, $observer_hash);
+
+ $ret = q("SELECT item.id, item.item_blocked, xchan.xchan_hash, xchan.xchan_name as name, xchan.xchan_url as url, xchan.xchan_photo_s as photo FROM item
+ LEFT JOIN xchan ON item.author_xchan = xchan.xchan_hash
+ WHERE item.uid = %d
+ AND item.thr_parent = '%s'
+ AND item.verb = '%s'
+ AND item.item_thread_top = 0
+ $sql_extra
+ $item_normal
+ -- GROUP BY item.author_xchan (should we prevent multiple reactions by the same author?)
+ ORDER BY item.created",
+ intval($owner_uid),
+ dbesc($mid),
+ dbesc($verb)
+ );
+ }
+
+ $ret['is_commentable'] = can_comment_on_post($observer_hash, $parent_item[0]);
+
+ return $ret;
+}
+
+
+/**
+ * @brief find and return thr_parents we need to show when displaying a nested comment.
+ * TODO: can this be improved or maybe implemented differently in the UI?
+ * @param array $item
+ */
+
+function get_recursive_thr_parents(array $item): array|null
+{
+ if ($item['id'] === $item['parent']) {
+ // This is a toplevel post, return null.
+ return null;
+ }
+
+ $thr_parents[] = $item['thr_parent'];
+
+ $mid = $item['thr_parent'];
+ $parent_mid = $item['parent_mid'];
+ $uid = $item['uid'];
+ $i = 0;
+
+ while ($mid !== $item['parent_mid'] && $i < 100) {
+ $x = q("SELECT thr_parent, mid FROM item WHERE uid = %d AND mid = '%s'",
+ intval($uid),
+ dbesc($mid)
+ );
+
+ if (!$x) {
+ break;
+ }
+
+ $mid = $x[0]['thr_parent'];
+ $thr_parents[] = $x[0]['thr_parent'];
+
+ $i++;
+ }
+
+ return $thr_parents;
+}
diff --git a/include/js_strings.php b/include/js_strings.php
index 090d28ce3..6f2ffd351 100644
--- a/include/js_strings.php
+++ b/include/js_strings.php
@@ -5,10 +5,8 @@ function js_strings() {
'$delitem' => t('Delete this item?'),
'$itemdel' => t('Item deleted'),
'$comment' => t('Comment'),
- '$showmore' => sprintf( t('%s show all'), '<i class=\'fa fa-chevron-down\'></i>'),
- '$showfewer' => sprintf( t('%s show less'), '<i class=\'fa fa-chevron-up\'></i>'),
- '$divgrowmore' => sprintf( t('%s expand'), '<i class=\'fa fa-chevron-down\'></i>'),
- '$divgrowless' => sprintf( t('%s collapse'),'<i class=\'fa fa-chevron-up\'></i>'),
+ '$divgrowmore' => t('expand'),
+ '$divgrowless' => t('collapse'),
'$pwshort' => t("Password too short"),
'$pwnomatch' => t("Passwords do not match"),
'$everybody' => t('everybody'),
@@ -38,6 +36,7 @@ function js_strings() {
'$pinned' => t('Pinned'),
'$pin_item' => t('Pin to the top'),
'$unpin_item' => t('Unpin from the top'),
+ '$dblclick_to_exit_zoom' => t('Double click to exit zoom'),
// translatable prefix and suffix strings for jquery.timeago -
// using the defaults set below if left untranslated, empty strings if
@@ -49,29 +48,6 @@ function js_strings() {
'months' => tt('%d months', '%d months', '%d'),
'years' => tt('%d years', '%d years', '%d'),
- // get plural function code
- 'plural_func' => tf(),
-
- '$t01' => ((t('timeago.prefixAgo') == 'timeago.prefixAgo') ? '' : ((t('timeago.prefixAgo') == 'NONE') ? '' : t('timeago.prefixAgo'))),
- '$t02' => ((t('timeago.prefixFromNow') == 'timeago.prefixFromNow') ? '' : ((t('timeago.prefixFromNow') == 'NONE') ? '' : t('timeago.prefixFromNow'))),
- '$t03' => ((t('timeago.suffixAgo') == 'timeago.suffixAgo') ? 'ago' : ((t('timeago.suffixAgo') == 'NONE') ? '' : t('timeago.suffixAgo'))),
- '$t04' => ((t('timeago.suffixFromNow') == 'timeago.suffixFromNow') ? 'from now' : ((t('timeago.suffixFromNow') == 'NONE') ? '' : t('timeago.suffixFromNow'))),
-
- // translatable main strings for jquery.timeago
- '$t05' => t('less than a minute'),
- '$t06' => t('about a minute'),
- '$t07' => ta('%d minutes'),
- '$t08' => t('about an hour'),
- '$t09' => ta('about %d hours'),
- '$t10' => t('a day'),
- '$t11' => ta('%d days'),
- '$t12' => t('about a month'),
- '$t13' => ta('%d months'),
- '$t14' => t('about a year'),
- '$t15' => ta('%d years'),
- '$t16' => t(' '), // wordSeparator
- '$t17' => ((t('timeago.numbers') != 'timeago.numbers') ? t('timeago.numbers') : '[]'),
-
'$January' => t('January'),
'$February' => t('February'),
'$March' => t('March'),
diff --git a/include/language.php b/include/language.php
index 538f67d90..9b68717f8 100644
--- a/include/language.php
+++ b/include/language.php
@@ -29,7 +29,7 @@ function get_browser_language() {
$langs = [];
$lang_parse = [];
- if (x($_SERVER, 'HTTP_ACCEPT_LANGUAGE')) {
+ if (!empty($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
// break up string into pieces (languages and q factors)
preg_match_all('/([a-z]{1,8}(-[a-z]{1,8})?)\s*(;\s*q\s*=\s*(1|0\.[0-9]+))?/i',
$_SERVER['HTTP_ACCEPT_LANGUAGE'], $lang_parse);
@@ -204,7 +204,7 @@ function load_translation_table($lang, $install = false) {
function t($s, $ctx = ''): string {
$cs = $ctx ? '__ctx:' . $ctx . '__ ' . $s : $s;
- if (x(App::$strings, $cs)) {
+ if (!empty(App::$strings[$cs])) {
$t = App::$strings[$cs];
return ((is_array($t)) ? translate_projectname($t[0]) : translate_projectname($t));
@@ -219,7 +219,7 @@ function t($s, $ctx = ''): string {
*/
function translate_projectname($s) {
- if(strpos($s,'rojectname') !== false) {
+ if(str_contains($s,'rojectname')) {
return str_replace(array('$projectname','$Projectname'),array(Zotlabs\Lib\System::get_platform_name(),ucfirst(Zotlabs\Lib\System::get_platform_name())),$s);
}
return $s;
@@ -239,7 +239,7 @@ function translate_projectname($s) {
function tt($singular, $plural, $count, $ctx = ''){
$cs = $ctx ? "__ctx:" . $ctx . "__ " . $singular : $singular;
- if (x(App::$strings,$cs)) {
+ if (!empty(App::$strings[$cs])) {
$t = App::$strings[$cs];
$f = 'string_plural_select_' . str_replace('-', '_', App::$language);
if (! function_exists($f))
@@ -282,9 +282,8 @@ function ta($k){
*/
function tf() {
-
- $s = "plural_function_code";
- return (x(App::$strings, $s) ? App::$strings[$s] : "0");
+ $s = "plural_function_code";
+ return (!empty(App::$strings[$s]) ? App::$strings[$s] : "0");
}
/**
@@ -391,7 +390,6 @@ function language_list() {
$langs = glob('view/*/hstrings.php');
$lang_options = array();
- $selected = "";
if(is_array($langs) && count($langs)) {
if(! in_array('view/en/hstrings.php',$langs))
@@ -406,41 +404,6 @@ function language_list() {
return $lang_options;
}
-function lang_selector() {
-
- $langs = glob('view/*/hstrings.php');
-
- $lang_options = array();
- $selected = "";
-
- if(is_array($langs) && count($langs)) {
- $langs[] = '';
- if(! in_array('view/en/hstrings.php',$langs))
- $langs[] = 'view/en/';
- asort($langs);
- foreach($langs as $l) {
- if($l == '') {
- $lang_options[""] = t('default');
- continue;
- }
- $ll = substr($l,5);
- $ll = substr($ll,0,strrpos($ll,'/'));
- $selected = (($ll === App::$language && (x($_SESSION, 'language'))) ? $ll : $selected);
- $lang_options[$ll] = get_language_name($ll, $ll) . " ($ll)";
- }
- }
-
- $tpl = get_markup_template('lang_selector.tpl');
-
- $o = replace_macros($tpl, array(
- '$title' => t('Select an alternate language'),
- '$langs' => array($lang_options, $selected),
-
- ));
-
- return $o;
-}
-
function rtl_languages() {
return [
'ar',
diff --git a/include/markdown.php b/include/markdown.php
index b2adcd0d5..90d671fe4 100644
--- a/include/markdown.php
+++ b/include/markdown.php
@@ -80,22 +80,6 @@ function markdown_to_bb($s, $use_zrl = false, $options = []) {
$s = html2bbcode($s);
- // $s = bb_code_protect($s);
-
- // Convert everything that looks like a link to a link
- if($use_zrl) {
- if (strpos($s,'[/img]') !== false) {
- $s = preg_replace_callback("/\[img\](.*?)\[\/img\]/ism", 'use_zrl_cb_img', $s);
- $s = preg_replace_callback("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", 'use_zrl_cb_img_x', $s);
- }
- $s = preg_replace_callback("/([^\]\=\{\/]|^)(https?\:\/\/)([a-zA-Z0-9\pL\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,\@\(\)]+)([\,\.\:\;]\s|$)/ismu", 'use_zrl_cb_link',$s);
- }
- else {
- $s = preg_replace("/([^\]\=\{\/]|^)(https?\:\/\/)([a-zA-Z0-9\pL\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,\@\(\)]+)([\,\.\:\;]\s|$)/ismu", '$1[url=$2$3]$2$3[/url]$4',$s);
- }
-
- // $s = bb_code_unprotect($s);
-
// remove duplicate adjacent code tags
$s = preg_replace("/(\[code\])+(.*?)(\[\/code\])+/ism","[code]$2[/code]", $s);
diff --git a/include/nav.php b/include/nav.php
index a68d564a0..3426db5f2 100644
--- a/include/nav.php
+++ b/include/nav.php
@@ -1,8 +1,8 @@
<?php /** @file */
-use \Zotlabs\Lib\Apps;
-use \Zotlabs\Lib\Chatroom;
-use \Zotlabs\Lib\Config;
+use Zotlabs\Lib\Apps;
+use Zotlabs\Lib\Chatroom;
+use Zotlabs\Lib\Config;
require_once('include/security.php');
require_once('include/menu.php');
@@ -366,7 +366,7 @@ function nav($template = 'default') {
'$form_security_token' => get_form_security_token('pconfig')
]);
- if (x($_SESSION, 'reload_avatar') && $observer) {
+ if (!empty($_SESSION['reload_avatar']) && $observer) {
// The avatar has been changed on the server but the browser doesn't know that,
// force the browser to reload the image from the server instead of its cache.
$tpl = get_markup_template('force_image_reload.tpl');
@@ -449,7 +449,7 @@ function channel_apps($is_owner = false, $nickname = null) {
'sel' => ((argv(0) == 'channel') ? 'active' : ''),
'title' => t('Status Messages and Posts'),
'id' => 'status-tab',
- 'icon' => 'home'
+ 'icon' => 'house'
],
];
@@ -462,7 +462,7 @@ function channel_apps($is_owner = false, $nickname = null) {
'sel' => ((argv(0) == 'profile') ? 'active' : ''),
'title' => t('Profile Details'),
'id' => 'profile-tab',
- 'icon' => 'user'
+ 'icon' => 'person'
];
}
if ($p['view_storage']) {
@@ -472,7 +472,7 @@ function channel_apps($is_owner = false, $nickname = null) {
'sel' => ((argv(0) == 'photos') ? 'active' : ''),
'title' => t('Photo Albums'),
'id' => 'photo-tab',
- 'icon' => 'photo'
+ 'icon' => 'image'
];
$tabs[] = [
'label' => t('Files'),
@@ -480,7 +480,7 @@ function channel_apps($is_owner = false, $nickname = null) {
'sel' => ((argv(0) == 'cloud' || argv(0) == 'sharedwithme') ? 'active' : ''),
'title' => t('Files and Storage'),
'id' => 'files-tab',
- 'icon' => 'folder-open'
+ 'icon' => 'folder'
];
}
@@ -491,7 +491,7 @@ function channel_apps($is_owner = false, $nickname = null) {
'sel' => ((argv(0) == 'cal') ? 'active' : ''),
'title' => t('Calendar'),
'id' => 'event-tab',
- 'icon' => 'calendar'
+ 'icon' => 'calendar-date'
];
}
@@ -505,7 +505,7 @@ function channel_apps($is_owner = false, $nickname = null) {
'sel' => ((argv(0) == 'chat') ? 'active' : ''),
'title' => t('Chatrooms'),
'id' => 'chat-tab',
- 'icon' => 'comments-o'
+ 'icon' => 'chat'
];
}
}
@@ -529,18 +529,7 @@ function channel_apps($is_owner = false, $nickname = null) {
'sel' => ((argv(0) == 'webpages') ? 'active' : ''),
'title' => t('View Webpages'),
'id' => 'webpages-tab',
- 'icon' => 'newspaper-o'
- ];
- }
-
- if ($p['view_wiki'] && Apps::system_app_installed($uid, 'Wiki')) {
- $tabs[] = [
- 'label' => t('Wikis'),
- 'url' => z_root() . '/wiki/' . $nickname,
- 'sel' => ((argv(0) == 'wiki') ? 'active' : ''),
- 'title' => t('Wiki'),
- 'id' => 'wiki-tab',
- 'icon' => 'pencil-square-o'
+ 'icon' => 'layout-text-sidebar'
];
}
diff --git a/include/network.php b/include/network.php
index b3a3d715c..83bb281a4 100644
--- a/include/network.php
+++ b/include/network.php
@@ -2,6 +2,7 @@
use Zotlabs\Lib\Activity;
use Zotlabs\Lib\Config;
+use Zotlabs\Lib\Mailer;
use Zotlabs\Lib\Zotfinger;
use Zotlabs\Lib\Libzot;
use Zotlabs\Lib\Queue;
@@ -72,21 +73,21 @@ function z_fetch_url($url, $binary = false, $redirects = 0, $opts = array()) {
if($ciphers)
@curl_setopt($ch, CURLOPT_SSL_CIPHER_LIST, $ciphers);
- if(x($opts,'filep')) {
+ if(!empty($opts['filep'])) {
@curl_setopt($ch, CURLOPT_FILE, $opts['filep']);
@curl_setopt($ch, CURLOPT_HEADER, false);
}
- if(x($opts,'upload'))
+ if(!empty($opts['upload']))
@curl_setopt($ch, CURLOPT_UPLOAD, $opts['upload']);
- if(x($opts,'infile'))
+ if(!empty($opts['infile']))
@curl_setopt($ch, CURLOPT_INFILE, $opts['infile']);
- if(x($opts,'infilesize'))
+ if(!empty($opts['infilesize']))
@curl_setopt($ch, CURLOPT_INFILESIZE, $opts['infilesize']);
- if(x($opts,'readfunc'))
+ if(!empty($opts['readfunc']))
@curl_setopt($ch, CURLOPT_READFUNCTION, $opts['readfunc']);
// When using the session option and fetching from our own site,
@@ -96,7 +97,7 @@ function z_fetch_url($url, $binary = false, $redirects = 0, $opts = array()) {
$instance_headers = ((array_key_exists('headers',$opts) && is_array($opts['headers'])) ? $opts['headers'] : []);
- if(x($opts,'session')) {
+ if(!empty($opts['session'])) {
if(strpos($url,z_root()) === 0) {
$instance_headers[] = 'Cookie: PHPSESSID=' . session_id();
}
@@ -105,13 +106,13 @@ function z_fetch_url($url, $binary = false, $redirects = 0, $opts = array()) {
@curl_setopt($ch, CURLOPT_HTTPHEADER, $instance_headers);
- if(x($opts,'nobody'))
+ if(!empty($opts['nobody']))
@curl_setopt($ch, CURLOPT_NOBODY, $opts['nobody']);
- if(x($opts,'custom'))
+ if(!empty($opts['custom']))
@curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $opts['custom']);
- if(x($opts,'timeout') && intval($opts['timeout'])) {
+ if(!empty($opts['timeout'])) {
@curl_setopt($ch, CURLOPT_TIMEOUT, intval($opts['timeout']));
}
else {
@@ -119,7 +120,7 @@ function z_fetch_url($url, $binary = false, $redirects = 0, $opts = array()) {
@curl_setopt($ch, CURLOPT_TIMEOUT, (($curl_time !== 0) ? $curl_time : 60));
}
- if(x($opts,'connecttimeout') && intval($opts['connecttimeout'])) {
+ if(!empty($opts['connecttimeout'])) {
@curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, intval($opts['connecttimeout']));
}
else {
@@ -127,7 +128,7 @@ function z_fetch_url($url, $binary = false, $redirects = 0, $opts = array()) {
@curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, (($curl_contime !== 0) ? $curl_contime : 30));
}
- if(x($opts,'http_auth')) {
+ if(!empty($opts['http_auth'])) {
// "username" . ':' . "password"
@curl_setopt($ch, CURLOPT_USERPWD, $opts['http_auth']);
}
@@ -135,16 +136,16 @@ function z_fetch_url($url, $binary = false, $redirects = 0, $opts = array()) {
if(array_key_exists('http_version',$opts))
@curl_setopt($ch,CURLOPT_HTTP_VERSION,$opts['http_version']);
- if(x($opts,'cookiejar'))
+ if(!empty($opts['cookiejar']))
@curl_setopt($ch, CURLOPT_COOKIEJAR, $opts['cookiejar']);
- if(x($opts,'cookiefile'))
+ if(!empty($opts['cookiefile']))
@curl_setopt($ch, CURLOPT_COOKIEFILE, $opts['cookiefile']);
- if(x($opts,'cookie'))
+ if(!empty($opts['cookie']))
@curl_setopt($ch, CURLOPT_COOKIE, $opts['cookie']);
@curl_setopt($ch, CURLOPT_SSL_VERIFYPEER,
- ((x($opts,'novalidate') && intval($opts['novalidate'])) ? false : true));
+ ((!empty($opts['novalidate'])) ? false : true));
$prx = @Config::Get('system','proxy');
if(strlen($prx)) {
@@ -204,7 +205,7 @@ function z_fetch_url($url, $binary = false, $redirects = 0, $opts = array()) {
$ret['header'] = $header;
$ret['request_target'] = $opts['request_target'];
- if(x($opts,'debug')) {
+ if(!empty($opts['debug'])) {
$ret['debug'] = $curl_info;
}
@@ -432,7 +433,6 @@ function as_return_and_die($obj, $channel = []) {
$headers['Content-Type'] = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"' ;
$headers['Date'] = datetime_convert('UTC','UTC', 'now', 'D, d M Y H:i:s \\G\\M\\T');
$headers['Digest'] = HTTPSig::generate_digest_header($ret);
- $headers['(request-target)'] = strtolower($_SERVER['REQUEST_METHOD']) . ' ' . $_SERVER['REQUEST_URI'];
if ($channel) {
$h = HTTPSig::create_sig($headers, $channel['channel_prvkey'], channel_url($channel));
@@ -612,7 +612,7 @@ function validate_email(string $addr): bool {
$matches = array();
$result = preg_match(
- '/^[A-Z0-9._%-]+@([A-Z0-9.-]+\.[A-Z0-9-]{2,})$/i',
+ '/^[A-Z0-9._%+-]+@([A-Z0-9.-]+\.[A-Z0-9-]{2,})$/i',
punify($addr),
$matches);
@@ -1488,11 +1488,11 @@ function do_delivery($deliveries, $force = false) {
$interval = Config::Get('queueworker', 'queue_interval', 500000);
- $deliveries_per_process = intval(Config::Get('system','delivery_batch_count'));
+ $deliveries_per_process = intval(Config::Get('system', 'delivery_batch_count'));
- if($deliveries_per_process <= 0)
+ if($deliveries_per_process <= 0) {
$deliveries_per_process = 1;
-
+ }
$deliver = [];
foreach($deliveries as $d) {
@@ -1813,54 +1813,9 @@ function network_to_name($s) {
*/
function z_mail($params) {
- if(! $params['fromEmail']) {
- $params['fromEmail'] = Config::Get('system','from_email');
- if(! $params['fromEmail'])
- $params['fromEmail'] = 'Administrator' . '@' . App::get_hostname();
- }
- if(! $params['fromName']) {
- $params['fromName'] = Config::Get('system','from_email_name');
- if(! $params['fromName'])
- $params['fromName'] = Zotlabs\Lib\System::get_site_name();
- }
- if(! $params['replyTo']) {
- $params['replyTo'] = Config::Get('system','reply_address');
- if(! $params['replyTo'])
- $params['replyTo'] = 'noreply' . '@' . App::get_hostname();
- }
-
- $params['sent'] = false;
- $params['result'] = false;
-
- /**
- * @hooks email_send
- * * \e params @see z_mail()
- */
- call_hooks('email_send', $params);
-
- if($params['sent']) {
- logger('notification: z_mail returns ' . (($params['result']) ? 'success' : 'failure'), LOGGER_DEBUG);
- return $params['result'];
- }
-
- $fromName = email_header_encode(html_entity_decode($params['fromName'],ENT_QUOTES,'UTF-8'),'UTF-8');
- $messageSubject = email_header_encode(html_entity_decode($params['messageSubject'],ENT_QUOTES,'UTF-8'),'UTF-8');
-
- $messageHeader =
- $params['additionalMailHeader'] .
- "From: $fromName <{$params['fromEmail']}>" . PHP_EOL .
- "Reply-To: $fromName <{$params['replyTo']}>" . PHP_EOL .
- "Content-Type: text/plain; charset=UTF-8";
-
- // send the message
- $res = mail(
- $params['toEmail'], // send to address
- $messageSubject, // subject
- $params['textVersion'],
- $messageHeader // message headers
- );
- logger('notification: z_mail returns ' . (($res) ? 'success' : 'failure'), LOGGER_DEBUG);
- return $res;
+ // Delegate the call to the Mailer class.
+ $mailer = new Mailer($params);
+ return $mailer->deliver();
}
@@ -2145,21 +2100,59 @@ function get_request_string($url) {
/**
- * Builds a url from the result of `parse_url`.
+ * Reconstructs a URL from its parsed components.
*
- * @param array $parsed_url An associative array as produced by `parse_url`.
+ * 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.
*
- * @return string The reassembled URL as a string.
+ * @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.
*/
-function unparse_url(array $parsed_url): string {
- $scheme = isset($parsed_url['scheme']) ? $parsed_url['scheme'] . '://' : '';
- $host = isset($parsed_url['host']) ? $parsed_url['host'] : '';
- $port = isset($parsed_url['port']) ? ':' . $parsed_url['port'] : '';
- $user = isset($parsed_url['user']) ? $parsed_url['user'] : '';
- $pass = isset($parsed_url['pass']) ? ':' . $parsed_url['pass'] : '';
- $pass = ($user || $pass) ? "$pass@" : '';
- $path = isset($parsed_url['path']) ? $parsed_url['path'] : '';
- $query = isset($parsed_url['query']) ? '?' . $parsed_url['query'] : '';
- $fragment = isset($parsed_url['fragment']) ? '#' . $parsed_url['fragment'] : '';
- return $scheme . $user . $pass . $host . $port . $path . $query . $fragment;
+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);
}
diff --git a/include/observer.php b/include/observer.php
new file mode 100644
index 000000000..4483e1d8b
--- /dev/null
+++ b/include/observer.php
@@ -0,0 +1,68 @@
+<?php
+/**
+ * Helper functions for getting info about the observer.
+ *
+ * SPDX-FileCopyrightText: 2025 The Hubzilla Community
+ * SPDX-FileContributor: Harald Eilertsen <haraldei@anduin.net>
+ *
+ * SPDX-License-Identifier: MIT
+ *
+ * The _observer_ in Hubzilla is the channel visiting the site in the current
+ * session. This could be a local channel, or a remote channel logged in via
+ * OpenWebAuth.
+ *
+ * If the observer is not set, or empty, this indicates an unauthenticated
+ * visitor, which may mean a visitor from another site that don't support, or
+ * has not enabled OpenWebAuth.
+ */
+
+/**
+ * Get the unique hash identifying the current observer.
+ *
+ * Observer can be a local or remote channel.
+ *
+ * @return string Unique hash of observer, otherwise empty string if no
+ * observer
+ */
+function get_observer_hash() {
+ $observer = App::get_observer();
+ if (is_array($observer)) {
+ return $observer['xchan_hash'];
+ }
+
+ return '';
+}
+
+/**
+ * Get the guid of the current observer.
+ *
+ * Observer can be a local or remote channel.
+ *
+ * @return string The GUID of the observer, otherwise empty string if no
+ * observer
+ */
+function get_observer_guid() {
+ $observer = App::get_observer();
+ if (is_array($observer)) {
+ return $observer['xchan_guid'];
+ }
+
+ return '';
+}
+
+/**
+ * Get the name of the current observer.
+ *
+ * Observer can be a local or remote channel.
+ *
+ * @return string The name of the observer, otherwise empty string if no
+ * observer
+ */
+function get_observer_name() {
+ $observer = App::get_observer();
+ if (is_array($observer)) {
+ return $observer['xchan_name'];
+ }
+
+ return '';
+}
diff --git a/include/oembed.php b/include/oembed.php
index f52f73225..840164663 100644
--- a/include/oembed.php
+++ b/include/oembed.php
@@ -143,6 +143,10 @@ function oembed_fetch_url($embedurl){
$furl = ((local_channel() && $zrl) ? zid($embedurl) : $embedurl);
+ if (empty($furl)) {
+ return;
+ }
+
if($action !== 'block' && (! Config::Get('system','oembed_cache_disable'))) {
$txt = Cache::get('[' . App::$videowidth . '] ' . $furl);
}
diff --git a/include/permissions.php b/include/permissions.php
index be6fc8594..29d242537 100644
--- a/include/permissions.php
+++ b/include/permissions.php
@@ -408,7 +408,7 @@ function get_all_api_perms($uid,$api) {
$arr = array(
'channel_id' => $uid,
- 'observer_hash' => $observer_xchan,
+ 'observer_hash' => null, //$observer_xchan,
'permissions' => $ret);
call_hooks('get_all_api_perms',$arr);
@@ -422,7 +422,7 @@ function api_perm_is_allowed($uid,$api,$permission) {
$arr = array(
'channel_id' => $uid,
- 'observer_hash' => $observer_xchan,
+ 'observer_hash' => null, //$observer_xchan,
'permission' => $permission,
'result' => false
);
diff --git a/include/photo/photo_driver.php b/include/photo/photo_driver.php
index 66a5d19f9..88b9d1d62 100644
--- a/include/photo/photo_driver.php
+++ b/include/photo/photo_driver.php
@@ -65,114 +65,95 @@ function photo_factory($data, $type = null) {
*
* @param string $filename
* Image filename
- * @param string $data (optional)
+ * @param array $data (optional)
* Data array fetched from cURL with z_fetch_url
* @return null|string Guessed mimetype
*/
-function guess_image_type($filename, $data = '') {
-
- if($data)
- $headers = (is_array($data) ? $data['header'] : $data);
-
- // logger('Photo: guess_image_type: '.$filename . ($headers?' from curl headers':''), LOGGER_DEBUG);
-
- $type = null;
- $m = null;
- $headers = '';
-
+function guess_image_type($filename, $data = []) {
$ph = photo_factory('');
$types = $ph->supportedTypes();
- if($headers) {
- $hdrs = [];
- $h = explode("\n", $headers);
- foreach ($h as $l) {
- if (strpos($l, ':') === false) {
- continue;
- }
+ logger('filename: ' . print_r($filename, true), LOGGER_DEBUG);
- list($k, $v) = array_map('trim', explode(':', trim($l), 2));
- $hdrs[strtolower($k)] = $v;
+ // Try Fileinfo from raw data
+ if (class_exists('finfo') && !empty($data['body'])) {
+ $finfo = new finfo(FILEINFO_MIME_TYPE);
+ $mime = $finfo->buffer($data['body']);
+ if ($mime && array_key_exists($mime, $types)) {
+ logger('finfo mime type: ' . print_r($mime, true), LOGGER_DEBUG);
+ return $mime;
}
- logger('Curl headers: ' .var_export($hdrs, true), LOGGER_DEBUG);
- if(array_key_exists('content-type', $hdrs) && array_key_exists($hdrs['content-type'], $types))
- $type = $hdrs['content-type'];
}
- if(is_null($type)){
- $ignore_imagick = Config::Get('system', 'ignore_imagick');
- // Guessing from extension? Isn't that... dangerous?
- if(class_exists('Imagick') && ! $ignore_imagick) {
- $v = Imagick::getVersion();
- preg_match('/ImageMagick ([0-9]+\.[0-9]+\.[0-9]+)/', $v['versionString'], $m);
- if(version_compare($m[1], '6.6.7') >= 0) {
- /**
- * Well, this not much better,
- * but at least it comes from the data inside the image,
- * we won't be tricked by a manipulated extension
- */
- $body = false;
- if (strpos($filename, 'http') === false && file_exists($filename) && is_readable($filename))
- $body == file_get_contents($filename);
- elseif (is_array($data) && array_key_exists('body', $data))
- $body = $data['body'];
- if ($body) {
- $image = new Imagick();
-
- try{
- $image->readImageBlob($body);
- } catch (\Exception $e) {
- logger('Imagick readImageBlob() exception:' . print_r($e, true));
- return $type;
- }
-
- $r = $image->identifyImage();
- if ($r && is_array($r) && array_key_exists($r['mimetype'], $types))
- $type = $r['mimetype'];
- }
- }
- else {
- // earlier imagick versions have issues with scaling png's
- // don't log this because it will just fill the logfile.
- // leave this note here so those who are looking for why
- // we aren't using imagick can find it
+ // Try exif_imagetype + image_type_to_mime_type if file exists locally
+ if (function_exists('exif_imagetype') && is_file($filename) && is_readable($filename)) {
+ $image_type = @exif_imagetype($filename);
+ if ($image_type !== false) {
+ $mime = image_type_to_mime_type($image_type);
+ if ($mime && array_key_exists($mime, $types)) {
+ logger('exif_imagetype mime type: ' . print_r($mime, true), LOGGER_DEBUG);
+ return $mime;
}
}
+ }
- if(is_null($type)) {
- $ext = pathinfo($filename, PATHINFO_EXTENSION);
- foreach($types as $m => $e) {
- if($ext === $e) {
- $type = $m;
+ // Try getimagesize for URLs
+ if (filter_var($filename, FILTER_VALIDATE_URL)) {
+ $size = @getimagesize($filename);
+ if (isset($size['mime']) && array_key_exists($size['mime'], $types)) {
+ logger('getimagesize mime type: ' . print_r($size['mime'], true), LOGGER_DEBUG);
+ return $size['mime'];
+ }
+ }
+
+ // Try Imagick if available and not disabled
+ $ignore_imagick = Config::Get('system', 'ignore_imagick');
+ if (class_exists('Imagick') && !$ignore_imagick) {
+ $v = Imagick::getVersion();
+ if (preg_match('/ImageMagick ([0-9]+\.[0-9]+\.[0-9]+)/', $v['versionString'], $m) && version_compare($m[1], '6.6.7') >= 0) {
+ $body = false;
+ if (is_file($filename) && is_readable($filename)) {
+ $body = file_get_contents($filename);
+ } elseif (!empty($data['body'])) {
+ $body = $data['body'];
+ }
+ if ($body) {
+ $image = new Imagick();
+ try {
+ $image->readImageBlob($body);
+ $r = $image->identifyImage();
+ if (isset($r['mimetype']) && array_key_exists($r['mimetype'], $types)) {
+ logger('imagick mime type: ' . print_r($r['mimetype'], true), LOGGER_DEBUG);
+ return $r['mimetype'];
+ }
+ } catch (\Exception $e) {
+ logger('Imagick readImageBlob() exception:' . print_r($e, true));
}
}
}
+ }
- if(is_null($type) && strpos($filename, 'http') === 0) {
- $size = getimagesize($filename);
- if ($size && array_key_exists($size['mime'], $types))
- $type = $size['mime'];
+ // Try Content-Type header
+ if (!empty($data['header'])) {
+ $hdrs = [];
+ foreach (explode("\n", $data['header']) as $l) {
+ if (strpos($l, ':') !== false) {
+ list($k, $v) = array_map('trim', explode(':', trim($l), 2));
+ $hdrs[strtolower($k)] = $v;
+ }
}
-
- if(is_null($type)) {
- if(strpos(strtolower($filename),'jpg') !== false)
- $type = 'image/jpeg';
- elseif(strpos(strtolower($filename),'jpeg') !== false)
- $type = 'image/jpeg';
- elseif(strpos(strtolower($filename),'gif') !== false)
- $type = 'image/gif';
- elseif(strpos(strtolower($filename),'png') !== false)
- $type = 'image/png';
- elseif(strpos(strtolower($filename),'webp') !== false)
- $type = 'image/webp';
+ if (isset($hdrs['content-type']) && array_key_exists($hdrs['content-type'], $types)) {
+ logger('headers mime type: ' . print_r($hdrs['content-type'], true), LOGGER_DEBUG);
+ return $hdrs['content-type'];
}
-
}
- logger('Photo: guess_image_type: filename = ' . $filename . ' type = ' . $type, LOGGER_DEBUG);
- return $type;
+ logger('failed to guess image type', LOGGER_DEBUG);
+
+ return null;
}
+
/**
* @brief Delete thing photo from database.
*
diff --git a/include/photos.php b/include/photos.php
index 85c97d1fd..a9f92e103 100644
--- a/include/photos.php
+++ b/include/photos.php
@@ -7,6 +7,7 @@
use Zotlabs\Access\PermissionLimits;
use Zotlabs\Lib\Activity;
use Zotlabs\Lib\Config;
+use Zotlabs\Daemon\Master;
require_once('include/permissions.php');
require_once('include/items.php');
@@ -405,7 +406,7 @@ function photo_upload($channel, $observer, $args) {
}
}
- $attribution = (($visitor) ? $visitor : $channel['xchan_url']);
+ $attribution = (($visitor) ? $visitor : channel_url($channel));
//// Create item object
$object = [
@@ -438,13 +439,13 @@ function photo_upload($channel, $observer, $args) {
else {
$object['to'] = Activity::map_acl(array_merge($ac, ['item_private' => 1 - intval($public)]));
}
-
+/*
$target = [
'type' => 'orderedCollection',
'name' => ((strlen($album)) ? $album : '/'),
'id' => z_root() . '/album/' . $channel['channel_address'] . ((isset($args['directory']['hash'])) ? '/' . $args['directory']['hash'] : EMPTY_STR)
];
-
+*/
// Create item container
if (isset($args['item'])) {
foreach ($args['item'] as $i) {
@@ -453,6 +454,11 @@ function photo_upload($channel, $observer, $args) {
$force = false;
if ($item['mid'] === $item['parent_mid']) {
+ $target = [
+ 'id' => str_replace('/item/', '/conversation/', $item['mid']),
+ 'type' => 'Collection',
+ 'attributedTo' => $attribution,
+ ];
$item['body'] = $summary;
$item['mimetype'] = 'text/bbcode';
@@ -460,10 +466,10 @@ function photo_upload($channel, $observer, $args) {
$object['id'] = $item['mid'];
$object['diaspora:guid'] = $item['uuid'];
- $item['obj'] = json_encode($object);
+ $item['obj'] = $object;
- $item['tgt_type'] = 'orderedCollection';
- $item['target'] = json_encode($target);
+ $item['tgt_type'] = 'Collection';
+ $item['target'] = $target;
if ($post_tags) {
$arr['term'] = $post_tags;
}
@@ -477,15 +483,23 @@ function photo_upload($channel, $observer, $args) {
if (($item['edited'] > $r[0]['edited']) || $force) {
$item['id'] = $r[0]['id'];
$item['uid'] = $channel['channel_id'];
- item_store_update($item, false, $deliver);
+ $result = item_store_update($item, deliver: $deliver);
continue;
}
}
else {
$item['aid'] = $channel['channel_account_id'];
$item['uid'] = $channel['channel_id'];
- item_store($item, false, $deliver);
+ $result = item_store($item, deliver: $deliver);
}
+
+ if ($result['success'] && $visible && $deliver) {
+ Master::Summon(['Notifier', 'wall-new', $result['item_id']]);
+ if (!empty($result['approval_id'])) {
+ Master::Summon(['Notifier', 'wall-new', $result['approval_id']]);
+ }
+ }
+
}
}
else {
@@ -496,6 +510,12 @@ function photo_upload($channel, $observer, $args) {
$object['id'] = $mid;
$object['diaspora:guid'] = $uuid;
+ $target = [
+ 'id' => z_root() . '/conversation/' . $uuid,
+ 'type' => 'Collection',
+ 'attributedTo' => $attribution,
+ ];
+
$arr = [
'aid' => $account_id,
'uid' => $channel_id,
@@ -514,9 +534,9 @@ function photo_upload($channel, $observer, $args) {
'deny_gid' => $ac['deny_gid'],
'verb' => 'Create',
'obj_type' => 'Image',
- 'obj' => json_encode($object),
- 'tgt_type' => 'orderedCollection',
- 'target' => json_encode($target),
+ 'obj' => $object,
+ 'tgt_type' => 'Collection',
+ 'target' => $target,
'item_wall' => $visible,
'item_origin' => 1,
'item_thread_top' => 1,
@@ -541,21 +561,26 @@ function photo_upload($channel, $observer, $args) {
// linked item from leaking into the feed when somebody has a channel with read_stream restrictions.
$arr['public_policy'] = map_scope(PermissionLimits::Get($channel['channel_id'], 'view_stream'), true);
- if ($arr['public_policy'])
+
+ if ($arr['public_policy']) {
$arr['item_private'] = 1;
+ }
- $result = item_store($arr, false, $deliver);
- $item_id = $result['item_id'];
+ $result = item_store($arr, deliver: $deliver);
- if ($visible && $deliver)
- Zotlabs\Daemon\Master::Summon(['Notifier', 'wall-new', $item_id]);
+ if ($result['success'] && $visible && $deliver) {
+ Master::Summon(['Notifier', 'wall-new', $result['item_id']]);
+ if (!empty($result['approval_id'])) {
+ Master::Summon(['Notifier', 'wall-new', $result['approval_id']]);
+ }
+ }
}
$ret['success'] = true;
$ret['item'] = $arr;
$ret['body'] = $obj_body;
$ret['resource_id'] = $photo_hash;
- $ret['photoitem_id'] = $item_id;
+ $ret['photoitem_id'] = $result['item_id'];
/**
* @hooks photo_upload_end
@@ -911,7 +936,7 @@ function photos_create_item($channel, $creator_hash, $photo, $visible = false) {
. '[zmg]' . z_root() . '/photo/' . $photo['resource_id'] . '-' . $photo['imgscale'] . '[/zmg]'
. '[/zrl]';
- $result = item_store($arr);
+ $result = item_store($arr, deliver: false, addAndSync: true);
$item_id = $result['item_id'];
return $item_id;
diff --git a/include/plugin.php b/include/plugin.php
index 62b643c3e..b5f9959b9 100644
--- a/include/plugin.php
+++ b/include/plugin.php
@@ -987,12 +987,13 @@ function format_css_if_exists($source) {
}
} else {
// It's a file from the theme
- $path = theme_include($script);
+ $theme_include = theme_include($script);
+ $path = (($theme_include) ? '/' . $theme_include : '');
}
if($path) {
$qstring = ((parse_url($path, PHP_URL_QUERY)) ? '&' : '?') . 'v=' . STD_VERSION;
- return '<link rel="stylesheet" href="' . $path_prefix . '/' . $path . $qstring . '" type="text/css" media="' . $source[1] . '">' . "\r\n";
+ return '<link rel="stylesheet" href="' . $path_prefix . $path . $qstring . '" type="text/css" media="' . $source[1] . '">' . "\r\n";
}
}
@@ -1059,11 +1060,12 @@ function format_js_if_exists($source) {
}
else {
// It's a file from the theme
- $path = theme_include($source);
+ $theme_include = theme_include($source);
+ $path = (($theme_include) ? '/' . $theme_include : '');
}
if($path) {
$qstring = ((parse_url($path, PHP_URL_QUERY)) ? '&' : '?') . 'v=' . STD_VERSION;
- return '<script src="' . $path_prefix . '/' . $path . $qstring . '" ></script>' . "\r\n" ;
+ return '<script src="' . $path_prefix . $path . $qstring . '"></script>' . "\r\n" ;
}
}
diff --git a/include/security.php b/include/security.php
index 545788bcd..32ca4f268 100644
--- a/include/security.php
+++ b/include/security.php
@@ -22,7 +22,7 @@ function authenticate_success($user_record, $channel = null, $login_initial = fa
$lastlog_updated = false;
$uid_to_load = null;
- if (x($user_record, 'account_id')) {
+ if (!empty($user_record['account_id'])) {
App::$account = $user_record;
$_SESSION['account_id'] = $user_record['account_id'];
$_SESSION['authenticated'] = 1;
@@ -31,7 +31,7 @@ function authenticate_success($user_record, $channel = null, $login_initial = fa
$uid_to_load = $channel['channel_id'];
if (!$uid_to_load) {
- $uid_to_load = (((x($_SESSION, 'uid')) && (intval($_SESSION['uid'])))
+ $uid_to_load = ((!empty($_SESSION['uid']))
? intval($_SESSION['uid'])
: intval(App::$account['account_default_channel'])
);
@@ -60,12 +60,12 @@ function authenticate_success($user_record, $channel = null, $login_initial = fa
// might want to log success here
}
- if ($return || x($_SESSION, 'workflow')) {
+ if ($return || isset($_SESSION['workflow'])) {
unset($_SESSION['workflow']);
return;
}
- if ((App::$module !== 'home') && x($_SESSION, 'login_return_url') && strlen($_SESSION['login_return_url'])) {
+ if (App::$module !== 'home' && !empty($_SESSION['login_return_url'])) {
$return_url = $_SESSION['login_return_url'];
// don't let members get redirected to a raw ajax page update - this can happen
@@ -321,6 +321,7 @@ function change_channel($change_channel) {
function permissions_sql($owner_id, $remote_observer = null, $table = '', $token = EMPTY_STR) {
$local_channel = local_channel();
+ $observer = $remote_observer ?? get_observer_hash();
/**
* Construct permissions
@@ -344,15 +345,22 @@ function permissions_sql($owner_id, $remote_observer = null, $table = '', $token
if (($local_channel) && ($local_channel == $owner_id)) {
return EMPTY_STR;
}
- /**
- * Authenticated visitor.
- */
-
else {
- $observer = ((!is_null($remote_observer)) ? $remote_observer : get_observer_hash());
+ /*
+ * OCAP token access
+ */
+
+ if ($token) {
+ $sql = " AND ( {$table}allow_cid like '" . protect_sprintf('%<token:' . $token . '>%') .
+ "' OR ( {$table}allow_cid = '' AND {$table}allow_gid = '' AND {$table}deny_cid = '' AND {$table}deny_gid = '' ) )";
+ }
- if ($observer) {
+ /**
+ * Authenticated visitor.
+ */
+
+ elseif ($observer) {
$sec = get_security_ids($owner_id, $observer);
@@ -400,16 +408,6 @@ function permissions_sql($owner_id, $remote_observer = null, $table = '', $token
dbesc($gs)
);
}
-
- /*
- * OCAP token access
- */
-
- elseif ($token) {
- $sql = " AND ( {$table}allow_cid like '" . protect_sprintf('%<token:' . $token . '>%') .
- "' OR ( {$table}allow_cid = '' AND {$table}allow_gid = '' AND {$table}deny_cid = '' AND {$table}deny_gid = '' ) )";
- }
-
}
return $sql;
@@ -434,7 +432,7 @@ function item_permissions_sql($owner_id, $remote_observer = null) {
* default permissions - anonymous user
*/
- $sql = " AND item_private = 0 ";
+ $sql = " AND item.item_private = 0 ";
/**
* Profile owner - everything is visible
@@ -494,10 +492,10 @@ function item_permissions_sql($owner_id, $remote_observer = null) {
$regexop = db_getfunc('REGEXP');
$sql = sprintf(
- " AND ( author_xchan = '%s' OR owner_xchan = '%s' OR
- (( NOT (deny_cid $regexop '%s' OR deny_gid $regexop '%s')
- AND ( allow_cid $regexop '%s' OR allow_gid $regexop '%s' OR ( allow_cid = '' AND allow_gid = '' AND item_private = 0 ))
- )))
+ " AND ( item.author_xchan = '%s' OR item.owner_xchan = '%s' OR
+ (( NOT (item.deny_cid $regexop '%s' OR item.deny_gid $regexop '%s')
+ AND ( item.allow_cid $regexop '%s' OR item.allow_gid $regexop '%s' OR ( item.allow_cid = '' AND item.allow_gid = '' AND item.item_private = 0 ))
+ )) OR ( item.item_private = 1 $scope ))
",
dbesc($observer),
dbesc($observer),
@@ -520,11 +518,11 @@ function item_permissions_sql($owner_id, $remote_observer = null) {
function scopes_sql($uid, $observer) {
- $str = " and ( public_policy = 'authenticated' ";
+ $str = " and ( item.public_policy = 'authenticated' ";
if (!is_foreigner($observer))
- $str .= " or public_policy = 'network: red' ";
+ $str .= " or item.public_policy = 'network: red' ";
if (local_channel())
- $str .= " or public_policy = 'site: " . App::get_hostname() . "' ";
+ $str .= " or item.public_policy = 'site: " . App::get_hostname() . "' ";
$ab = q("select * from abook where abook_xchan = '%s' and abook_channel = %d limit 1",
dbesc($observer),
@@ -533,8 +531,8 @@ function scopes_sql($uid, $observer) {
if (!$ab)
return $str . " ) ";
if ($ab[0]['abook_pending'])
- $str .= " or public_policy = 'any connections' ";
- $str .= " or public_policy = 'contacts' ) ";
+ $str .= " or item.public_policy = 'any connections' ";
+ $str .= " or item.public_policy = 'contacts' ) ";
return $str;
}
@@ -609,14 +607,14 @@ function public_permissions_sql($observer_hash) {
function get_form_security_token($typename = '') {
$timestamp = time();
- $guid = App::$observer['xchan_guid'] ?? '';
+ $guid = get_observer_guid();
$sec_hash = hash('whirlpool', $guid . ((local_channel()) ? App::$channel['channel_prvkey'] : '') . session_id() . $timestamp . $typename);
return $timestamp . '.' . $sec_hash;
}
function check_form_security_token($typename = '', $formname = 'form_security_token') {
- if (!x($_REQUEST, $formname)) return false;
+ if (empty($_REQUEST[$formname])) return false;
$hash = $_REQUEST[$formname];
$max_livetime = 10800; // 3 hours
@@ -625,7 +623,7 @@ function check_form_security_token($typename = '', $formname = 'form_security_to
if (time() > (IntVal($x[0]) + $max_livetime))
return false;
- $sec_hash = hash('whirlpool', App::$observer['xchan_guid'] . ((local_channel()) ? App::$channel['channel_prvkey'] : '') . session_id() . $x[0] . $typename);
+ $sec_hash = hash('whirlpool', get_observer_guid() . ((local_channel()) ? App::$channel['channel_prvkey'] : '') . session_id() . $x[0] . $typename);
return ($sec_hash == $x[1]);
}
@@ -637,7 +635,7 @@ function check_form_security_std_err_msg() {
function check_form_security_token_redirectOnErr($err_redirect, $typename = '', $formname = 'form_security_token') {
if (!check_form_security_token($typename, $formname)) {
- logger('check_form_security_token failed: user ' . App::$observer['xchan_name'] . ' - form element ' . $typename);
+ logger('check_form_security_token failed: user ' . get_observer_name() . ' - form element ' . $typename);
logger('check_form_security_token failed: _REQUEST data: ' . print_r($_REQUEST, true), LOGGER_DATA);
notice(check_form_security_std_err_msg());
goaway(z_root() . $err_redirect);
@@ -646,7 +644,7 @@ function check_form_security_token_redirectOnErr($err_redirect, $typename = '',
function check_form_security_token_ForbiddenOnErr($typename = '', $formname = 'form_security_token') {
if (!check_form_security_token($typename, $formname)) {
- logger('check_form_security_token failed: user ' . App::$observer['xchan_name'] . ' - form element ' . $typename);
+ logger('check_form_security_token failed: user ' . get_observer_name() . ' - form element ' . $typename);
logger('check_form_security_token failed: _REQUEST data: ' . print_r($_REQUEST, true), LOGGER_DATA);
header('HTTP/1.1 403 Forbidden');
killme();
@@ -708,56 +706,61 @@ function get_security_ids($channel_id, $ob_hash) {
'allow_gid' => []
];
- if ($channel_id) {
- $ch = q("select channel_hash from channel where channel_id = %d",
- intval($channel_id)
- );
- if ($ch) {
- $ret['channel_id'][] = $ch[0]['channel_hash'];
- }
- }
-
- $groups = [];
-
- $x = q("select * from xchan where xchan_hash = '%s'",
+ $x = q("select xchan_hash from xchan where xchan_hash = '%s'",
dbesc($ob_hash)
);
- if ($x) {
+ if (!$x) {
+ return $ret;
+ }
- // include xchans for all zot-like networks
+ $ret['allow_cid'][] = $x[0]['xchan_hash'];
- $xchans = q("select xchan_hash from xchan where xchan_hash = '%s' OR ( xchan_guid = '%s' AND xchan_pubkey = '%s' ) ",
- dbesc($ob_hash),
- dbesc($x[0]['xchan_guid']),
- dbesc($x[0]['xchan_pubkey'])
- );
+ if (!$channel_id) {
+ return $ret;
+ }
+
+ $ch = q("select channel_hash from channel where channel_id = %d",
+ intval($channel_id)
+ );
+ if ($ch) {
+ $ret['channel_id'][] = $ch[0]['channel_hash'];
+ }
- if ($xchans) {
- $ret['allow_cid'] = ids_to_array($xchans, 'xchan_hash');
- $hashes = ids_to_querystr($xchans, 'xchan_hash', true);
+ $groups = [];
- // private profiles are treated as a virtual group
+ // private profiles are treated as a virtual group
- $r = q("SELECT abook_profile from abook where abook_xchan in ( " . protect_sprintf($hashes) . " ) and abook_profile != '' ");
- if ($r) {
- foreach ($r as $rv) {
- $groups[] = 'vp.' . $rv['abook_profile'];
- }
+ $r = q("SELECT abook_profile from abook where abook_channel = %d and abook_xchan = '%s' and abook_profile != ''",
+ intval($channel_id),
+ dbesc(protect_sprintf($x[0]['xchan_hash']))
+ );
+
+ if ($r) {
+ foreach ($r as $rv) {
+ if (!in_array('vp.' . $rv['abook_profile'], $groups)) {
+ $groups[] = 'vp.' . $rv['abook_profile'];
}
+ }
+ }
- // physical groups this identity is a member of
+ // physical groups this identity is a member of
- $r = q("SELECT hash FROM pgrp left join pgrp_member on pgrp.id = pgrp_member.gid WHERE xchan in ( " . protect_sprintf($hashes) . " ) ");
- if ($r) {
- foreach ($r as $rv) {
- $groups[] = $rv['hash'];
- }
+ $r = q("SELECT hash FROM pgrp left join pgrp_member on pgrp.id = pgrp_member.gid WHERE pgrp.uid = %d and pgrp_member.xchan = '%s'",
+ intval($channel_id),
+ dbesc(protect_sprintf($x[0]['xchan_hash']))
+ );
+
+ if ($r) {
+ foreach ($r as $rv) {
+ if (!in_array($rv['hash'], $groups)) {
+ $groups[] = $rv['hash'];
}
- $ret['allow_gid'] = $groups;
}
}
+ $ret['allow_gid'] = $groups;
+
return $ret;
}
diff --git a/include/socgraph.php b/include/socgraph.php
index 336c1c0c3..2da0040b0 100644
--- a/include/socgraph.php
+++ b/include/socgraph.php
@@ -364,8 +364,8 @@ function poco() {
elseif(argv(3) === '@self')
$justme = true;
}
- if(argc() > 4 && intval(argv(4)) && $justme == false)
- $cid = intval(argv(4));
+
+ $cid = ((argc() > 4 && intval(argv(4)) && $justme == false) ? intval(argv(4)) : null);
if(! $system_mode) {
@@ -413,11 +413,8 @@ function poco() {
else
$totalResults = 0;
- $startIndex = intval($_GET['startIndex']);
- if(! $startIndex)
- $startIndex = 0;
-
- $itemsPerPage = ((x($_GET,'count') && intval($_GET['count'])) ? intval($_GET['count']) : $totalResults);
+ $startIndex = $_GET['startIndex'] ?? 0;
+ $itemsPerPage = $_GET['count'] ?? $totalResults;
if($system_mode) {
$r = q("SELECT abook.*, xchan.* from abook left join xchan on abook_xchan = xchan_hash where abook_self = 1
diff --git a/include/text.php b/include/text.php
index b03e2d1a9..9ac6efdc2 100644
--- a/include/text.php
+++ b/include/text.php
@@ -1145,7 +1145,7 @@ function chanlink_cid($d) {
function magiclink_url($observer,$myaddr,$url) {
return (($observer)
- ? z_root() . '/magic?f=&owa=1&bdest=' . bin2hex($url) . '&addr=' . $myaddr
+ ? z_root() . '/magic?owa=1&bdest=' . bin2hex($url) . '&addr=' . $myaddr
: $url
);
}
@@ -1272,24 +1272,38 @@ function sslify($s) {
function get_emojis() {
$emojis = [
// Reactions (emojitwo emojis)
- 'astonished_face' => ['shortname' => ':astonished_face:', 'filepath' => 'images/emoji/astonished_face.png'],
- 'bottle_with_popping_cork' => ['shortname' => ':bottle_with_popping_cork:', 'filepath' => 'images/emoji/bottle_with_popping_cork.png'],
- 'clapping_hands' => ['shortname' => ':clapping_hands:', 'filepath' => 'images/emoji/clapping_hands.png'],
- 'disappointed_face' => ['shortname' => ':disappointed_face:', 'filepath' => 'images/emoji/disappointed_face.png'],
- 'grinning_face' => ['shortname' => ':grinning_face:', 'filepath' => 'images/emoji/grinning_face.png'],
- 'kiss_mark' => ['shortname' => ':kiss_mark:', 'filepath' => 'images/emoji/kiss_mark.png'],
- 'red_heart' => ['shortname' => ':red_heart:', 'filepath' => 'images/emoji/red_heart.png'],
- 'sleeping_face' => ['shortname' => ':sleeping_face:', 'filepath' => 'images/emoji/sleeping_face.png'],
- 'slightly_smiling_face' => ['shortname' => ':slightly_smiling_face:', 'filepath' => 'images/emoji/slightly_smiling_face.png'],
- 'smiling_face_with_halo' => ['shortname' => ':smiling_face_with_halo:', 'filepath' => 'images/emoji/smiling_face_with_halo.png'],
- 'smiling_face_with_horns' => ['shortname' => ':smiling_face_with_horns:', 'filepath' => 'images/emoji/smiling_face_with_horns.png'],
- 'winking_face_with_tongue' => ['shortname' => ':winking_face_with_tongue:', 'filepath' => 'images/emoji/winking_face_with_tongue.png'],
-
- 'facepalm' => ['shortname' => ':facepalm:', 'filepath' => 'images/emoticons/smiley-facepalm.gif']
+ 'astonished_face' => ['shortname' => ':astonished_face:', 'filepath' => 'images/emoji/emojitwo/astonished_face.png'],
+ 'bottle_with_popping_cork' => ['shortname' => ':bottle_with_popping_cork:', 'filepath' => 'images/emoji/emojitwo/bottle_with_popping_cork.png'],
+ 'clapping_hands' => ['shortname' => ':clapping_hands:', 'filepath' => 'images/emoji/emojitwo/clapping_hands.png'],
+ 'disappointed_face' => ['shortname' => ':disappointed_face:', 'filepath' => 'images/emoji/emojitwo/disappointed_face.png'],
+ 'grinning_face' => ['shortname' => ':grinning_face:', 'filepath' => 'images/emoji/emojitwo/grinning_face.png'],
+ 'kiss_mark' => ['shortname' => ':kiss_mark:', 'filepath' => 'images/emoji/emojitwo/kiss_mark.png'],
+ 'red_heart' => ['shortname' => ':red_heart:', 'filepath' => 'images/emoji/emojitwo/red_heart.png'],
+ 'sleeping_face' => ['shortname' => ':sleeping_face:', 'filepath' => 'images/emoji/emojitwo/sleeping_face.png'],
+ 'slightly_smiling_face' => ['shortname' => ':slightly_smiling_face:', 'filepath' => 'images/emoji/emojitwo/slightly_smiling_face.png'],
+ 'smiling_face_with_halo' => ['shortname' => ':smiling_face_with_halo:', 'filepath' => 'images/emoji/emojitwo/smiling_face_with_halo.png'],
+ 'smiling_face_with_horns' => ['shortname' => ':smiling_face_with_horns:', 'filepath' => 'images/emoji/emojitwo/smiling_face_with_horns.png'],
+ 'winking_face_with_tongue' => ['shortname' => ':winking_face_with_tongue:', 'filepath' => 'images/emoji/emojitwo/winking_face_with_tongue.png'],
+
+ // Hubzilla custom
+ 'facepalm' => ['shortname' => ':facepalm:', 'filepath' => 'images/emoji/hubzilla/smiley-facepalm.gif'],
+ 'hubzilla' => ['shortname' => ':hubzilla:', 'filepath' => 'images/emoji/hubzilla/hubzilla.png']
];
+ // Provided by addon
call_hooks('get_emojis', $emojis);
+ // Custom site emojis
+ $custom_json_path = 'images/emoji/custom/custom_emojis.json';
+ if (file_exists($custom_json_path)) {
+ $custom_json = file_get_contents($custom_json_path);
+ $custom_arr = json_decode($custom_json, true);
+
+ if ($custom_arr) {
+ $emojis = array_merge($emojis, $custom_arr);
+ }
+ }
+
return $emojis;
}
@@ -1406,14 +1420,12 @@ function list_smilies($default_only = false) {
* @param boolean $sample (optional) default false
* @return string
*/
-function smilies($s, $sample = false) {
-
+function smilies($s, $sample = false, $terms = []) {
if(intval(Config::Get('system', 'no_smilies'))
|| (local_channel() && intval(get_pconfig(local_channel(), 'system', 'no_smilies'))))
return $s;
-
- $s = preg_replace_callback('{<(pre|code)>.*?</\1>}ism', 'smile_shield', $s);
+ $s = preg_replace_callback('/<(pre|code)\b[^>]*>.*?<\/(pre|code)>/ism', 'smile_shield', $s);
$s = preg_replace_callback('/<[a-z]+ .*?>/ism', 'smile_shield', $s);
if (preg_match_all('/(\:(\w|\+|\-)+\:)(?=|[\!\.\?]|$)/', $s, $match)) {
@@ -1421,13 +1433,23 @@ function smilies($s, $sample = false) {
$emojis = get_emojis();
foreach ($match[0] as $mtch) {
$name = trim($mtch, ':');
+ $emoji = $emojis[$name] ?? [];
+
+ if (!$emoji && !empty($terms)) {
+ foreach($terms as $term) {
+ // some platforms provide the term without :
+ $term['term'] = ':' . trim($term['term'], ':') . ':';
+ if (intval($term['ttype']) === TERM_EMOJI && $term['term'] === $mtch) {
+ $emoji['filepath'] = $term['imgurl'];
+ $emoji['shortname'] = $term['term'];
+ }
+ }
+ }
- if (!isset($emojis[$name])) {
+ if (!$emoji || empty($emoji['filepath'])) {
continue;
}
- $emoji = $emojis[$name];
-
$class = 'emoji';
if (is_solo_string($mtch, $s)) {
$class .= ' single-emoji';
@@ -1579,18 +1601,16 @@ function theme_attachments(&$item) {
$title = t('Size') . ' ' . (isset($r['length']) ? userReadableSize($r['length']) : t('unknown'));
- $revision = $r['revision'] ?? '';
-
require_once('include/channel.php');
if (isset($r['href'])) {
if(is_foreigner($item['author_xchan']))
$url = $r['href'];
else
- $url = z_root() . '/magic?f=&owa=1&hash=' . $item['author_xchan'] . '&bdest=' . bin2hex($r['href'] . '/' . $revision);
+ $url = z_root() . '/magic?owa=1&bdest=' . bin2hex($r['href']);
}
- if (isset($label) && isset($url) && isset($icon) && isset($title)) {
+ if (isset($label, $url, $icon, $title)) {
array_unshift($attaches, ['label' => $label, 'url' => $url, 'icon' => $icon, 'title' => $title]);
}
}
@@ -1652,7 +1672,7 @@ function format_hashtags(&$item) {
if($s)
$s .= ' ';
- $s .= '<span class="badge rounded-pill bg-info"><i class="fa fa-hashtag"></i>&nbsp;<a class="text-white" href="' . zid($t['url']) . '" >' . $term . '</a></span>';
+ $s .= '<span class="badge rounded-pill bg-info"><i class="bi bi-hash"></i>&nbsp;<a class="text-white" href="' . zid($t['url']) . '" >' . $term . '</a></span>';
}
}
@@ -1675,7 +1695,7 @@ function format_mentions(&$item) {
continue;
if($s)
$s .= ' ';
- $s .= '<span class="badge rounded-pill bg-success"><i class="fa fa-at"></i>&nbsp;<a class="text-white" href="' . zid($t['url']) . '" >' . $term . '</a></span>';
+ $s .= '<span class="badge rounded-pill bg-success"><i class="bi bi-at"></i>&nbsp;<a class="text-white" href="' . zid($t['url']) . '" >' . $term . '</a></span>';
}
}
@@ -1795,7 +1815,7 @@ function prepare_body(&$item,$attach = false,$opts = false) {
$s .= prepare_text('[summary]' . $item['summary'] . '[/summary]' . $item['body'],$item['mimetype'],$opts);
}
else {
- $s .= prepare_text($item['body'],$item['mimetype'], $opts);
+ $s .= prepare_text($item['body'],$item['mimetype'], $opts, $item['term'] ?? []);
}
}
@@ -2004,7 +2024,7 @@ function format_poll($item,$s,$opts) {
$message .= t('Poll has ended');
}
else {
- $message .= sprintf(t('Poll ends in %s'), '<span class="autotime" title="' . $t . '"></span>');
+ $message .= sprintf(t('Poll ends %s'), '<span class="autotime" title="' . $t . '"></span>');
}
}
@@ -2041,8 +2061,7 @@ function format_poll($item,$s,$opts) {
* @return string
* The parsed $text as prepared HTML.
*/
-function prepare_text($text, $content_type = 'text/bbcode', $opts = false) {
-
+function prepare_text($text, $content_type = 'text/bbcode', $opts = false, $terms = []) {
switch($content_type) {
case 'text/plain':
$s = escape_tags($text);
@@ -2088,7 +2107,7 @@ function prepare_text($text, $content_type = 'text/bbcode', $opts = false) {
$s = bbcode($text, ((is_array($opts)) ? $opts : [] ));
}
else {
- $s = smilies(bbcode($text, ((is_array($opts)) ? $opts : [] )));
+ $s = smilies(bbcode($text, ((is_array($opts)) ? $opts : [] )), terms: $terms);
}
}
@@ -2168,7 +2187,7 @@ function get_plink($item,$conversation_mode = true) {
if(array_key_exists('author',$item) && $item['author']['xchan_network'] !== 'zot6')
$zidify = false;
- if(x($item,$key)) {
+ if(!empty($item[$key])) {
return array(
'href' => (($zidify) ? zid($item[$key]) : $item[$key]),
'title' => t('Link to Source'),
@@ -2361,17 +2380,19 @@ function item_post_type($item) {
$post_type = t('event');
break;
default:
- $post_type = t('post');
+ $post_type = t('conversation');
if($item['mid'] != $item['parent_mid'])
- $post_type = t('comment');
+ $post_type = t('message');
break;
}
- if(strlen($item['verb']) && (! activity_match($item['verb'],ACTIVITY_POST)))
+ if(strlen($item['verb']) && (!activity_match($item['verb'], ['Create', ACTIVITY_POST]))) {
$post_type = t('activity');
+ }
- if($item['obj_type'] === 'Question')
+ if($item['obj_type'] === 'Question') {
$post_type = t('poll');
+ }
return $post_type;
}
@@ -2632,20 +2653,20 @@ function xchan_query(&$items, $abook = true, $effective_uid = 0) {
$arr[] = "'" . dbesc($item['owner_xchan']) . "'";
if($item['author_xchan'] && (! in_array("'" . dbesc($item['author_xchan']) . "'",$arr)))
$arr[] = "'" . dbesc($item['author_xchan']) . "'";
- if($item['source_xchan'] && (! in_array("'" . dbesc($item['source_xchan']) . "'",$arr)))
+ if(!empty($item['source_xchan']) && (! in_array("'" . dbesc($item['source_xchan']) . "'",$arr)))
$arr[] = "'" . dbesc($item['source_xchan']) . "'";
}
}
if(count($arr)) {
if($abook) {
$chans = q("select * from xchan left join hubloc on hubloc_hash = xchan_hash left join abook on abook_xchan = xchan_hash and abook_channel = %d
- where xchan_hash in (" . protect_sprintf(implode(',', $arr)) . ") and hubloc_deleted = 0 order by hubloc_primary desc",
+ where xchan_hash in (" . protect_sprintf(implode(',', $arr)) . ") order by hubloc_primary desc, hubloc_deleted ASC",
intval($item['uid'])
);
}
else {
$chans = q("select xchan.*,hubloc.* from xchan left join hubloc on hubloc_hash = xchan_hash
- where xchan_hash in (" . protect_sprintf(implode(',', $arr)) . ") and hubloc_deleted = 0 order by hubloc_primary desc");
+ where xchan_hash in (" . protect_sprintf(implode(',', $arr)) . ") order by hubloc_primary desc, hubloc_deleted ASC");
}
$xchans = q("select * from xchan where xchan_hash in (" . protect_sprintf(implode(',',$arr)) . ") and xchan_network in ('rss','unknown', 'anon', 'token')");
if(! $chans)
@@ -2657,7 +2678,9 @@ function xchan_query(&$items, $abook = true, $effective_uid = 0) {
for($x = 0; $x < count($items); $x ++) {
$items[$x]['owner'] = find_xchan_in_array($items[$x]['owner_xchan'],$chans);
$items[$x]['author'] = find_xchan_in_array($items[$x]['author_xchan'],$chans);
- $items[$x]['source'] = find_xchan_in_array($items[$x]['source_xchan'],$chans);
+ if (!empty($items[$x]['source_xchan'])) {
+ $items[$x]['source'] = find_xchan_in_array($items[$x]['source_xchan'],$chans);
+ }
}
}
}
@@ -3203,53 +3226,54 @@ function linkify_tags(&$body, $uid, $in_network = true) {
function getIconFromType($type) {
$iconMap = array(
//Folder
- 'Collection' => 'fa-folder-o',
- 'multipart/mixed' => 'fa-folder-o', //dirs in attach use this mime type
+ 'Collection' => 'bi-folder',
+ 'multipart/mixed' => 'bi-folder', //dirs in attach use this mime type
//Common file
- 'application/octet-stream' => 'fa-file-o',
+ 'application/octet-stream' => 'bi-file-earmark',
//Text
- 'text/plain' => 'fa-file-text-o',
- 'text/markdown' => 'fa-file-text-o',
- 'text/bbcode' => 'fa-file-text-o',
- 'text/html' => 'fa-file-text-o',
- 'application/msword' => 'fa-file-word-o',
- 'application/pdf' => 'fa-file-pdf-o',
- 'application/vnd.oasis.opendocument.text' => 'fa-file-word-o',
- 'application/epub+zip' => 'fa-book',
+ 'text/plain' => 'bi-file-earmark-text',
+ 'text/markdown' => 'bi-filetype-md',
+ 'text/bbcode' => 'bi-file-earmark-text',
+ 'text/html' => 'bi-filetype-html',
+ 'text/uri-list' => 'bi-box-arrow-up-right',
+ 'application/msword' => 'bi-file-earmark-word',
+ 'application/pdf' => 'bi-file-earmark-pdf',
+ 'application/vnd.oasis.opendocument.text' => 'bifile--earmark-text',
+ 'application/epub+zip' => 'bi-file-earmark-text',
//Spreadsheet
- 'application/vnd.oasis.opendocument.spreadsheet' => 'fa-file-excel-o',
- 'application/vnd.ms-excel' => 'fa-file-excel-o',
+ 'application/vnd.oasis.opendocument.spreadsheet' => 'bi-file-earmark-spreadsheet',
+ 'application/vnd.ms-excel' => 'bi-file-earmark-spreadsheet',
//Image
- 'image/jpeg' => 'fa-picture-o',
- 'image/png' => 'fa-picture-o',
- 'image/gif' => 'fa-picture-o',
- 'image/webp' => 'fa-picture-o',
- 'image/svg+xml' => 'fa-picture-o',
+ 'image/jpeg' => 'bi-file-earmark-image',
+ 'image/png' => 'bi-file-earmark-image',
+ 'image/gif' => 'bi-file-earmark-image',
+ 'image/webp' => 'bi-file-earmark-image',
+ 'image/svg+xml' => 'bi-filetype-svg',
//Archive
- 'application/zip' => 'fa-file-archive-o',
- 'application/x-rar-compressed' => 'fa-file-archive-o',
+ 'application/zip' => 'bi-file-earmark-zip',
+ 'application/x-rar-compressed' => 'bi-file-earmark-zip',
//Audio
- 'audio/mpeg' => 'fa-file-audio-o',
- 'audio/wav' => 'fa-file-audio-o',
- 'application/ogg' => 'fa-file-audio-o',
- 'audio/ogg' => 'fa-file-audio-o',
- 'audio/webm' => 'fa-file-audio-o',
- 'audio/mp4' => 'fa-file-audio-o',
+ 'audio/mpeg' => 'bi-file-earmark-music',
+ 'audio/wav' => 'bi-file-earmark-music',
+ 'application/ogg' => 'bi-file-earmark-music',
+ 'audio/ogg' => 'bi-file-earmark-music',
+ 'audio/webm' => 'bi-file-earmark-music',
+ 'audio/mp4' => 'bi-file-earmark-music',
//Video
- 'video/quicktime' => 'fa-file-video-o',
- 'video/webm' => 'fa-file-video-o',
- 'video/mp4' => 'fa-file-video-o',
- 'video/x-matroska' => 'fa-file-video-o'
+ 'video/quicktime' => 'bi-file-earmark-play',
+ 'video/webm' => 'bi-file-earmark-play',
+ 'video/mp4' => 'bi-file-earmark-play',
+ 'video/x-matroska' => 'bi-file-earmark-play'
);
$catMap = [
- 'application' => 'fa-file-code-o',
- 'multipart' => 'fa-folder',
- 'audio' => 'fa-file-audio-o',
- 'video' => 'fa-file-video-o',
- 'text' => 'fa-file-text-o',
- 'image' => 'fa=file-picture-o',
- 'message' => 'fa-file-text-o'
+ 'application' => 'bi-file-earmark',
+ 'multipart' => 'bi-folder',
+ 'audio' => 'bi-file-earmark-music',
+ 'video' => 'bi-file-earmark-play',
+ 'text' => 'bi-file-earmark-text',
+ 'image' => 'bi-file-earmark-image',
+ 'message' => 'bi-file-earmark-text'
];
@@ -3266,7 +3290,7 @@ function getIconFromType($type) {
}
if(! $iconFromType) {
- $iconFromType = 'fa-file-o';
+ $iconFromType = 'bi-file-earmark';
}
@@ -3755,12 +3779,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);
@@ -3846,7 +3867,7 @@ function featured_sort($a,$b) {
function unpunify($s) {
- if (function_exists('idn_to_utf8') && isset($s)) {
+ if (function_exists('idn_to_utf8') && !empty($s)) {
return idn_to_utf8($s);
}
return $s;
@@ -3854,7 +3875,7 @@ function unpunify($s) {
function punify($s) {
- if (function_exists('idn_to_ascii') && isset($s)) {
+ if (function_exists('idn_to_ascii') && !empty($s)) {
return idn_to_ascii($s);
}
return $s;
diff --git a/include/xchan.php b/include/xchan.php
index b8677c8c4..c492a77dc 100644
--- a/include/xchan.php
+++ b/include/xchan.php
@@ -217,6 +217,10 @@ function xchan_keychange_acl($table,$column,$oldxchan,$newxchan) {
function xchan_change_key($oldx,$newx,$data) {
+ // TODO: this will need a refactor to eliminate duplicate keys
+ // E.G. item => [author_xchan, owner_xchan, source_kchan]
+ // Also: add a test!
+
$tables = [
'abook' => 'abook_xchan',
'abconfig' => 'xchan',
@@ -227,8 +231,6 @@ function xchan_change_key($oldx,$newx,$data) {
'item' => 'owner_xchan',
'item' => 'author_xchan',
'item' => 'source_xchan',
- 'mail' => 'from_xchan',
- 'mail' => 'to_xchan',
'shares' => 'share_xchan',
'source' => 'src_channel_xchan',
'source' => 'src_xchan',
diff --git a/include/zid.php b/include/zid.php
index 159a3b834..b74e82930 100644
--- a/include/zid.php
+++ b/include/zid.php
@@ -150,6 +150,9 @@ function clean_query_string($s = '') {
*/
function drop_query_params($s, $p) {
+
+ $s = unescape_tags($s);
+
$parsed = parse_url($s);
$query = '';
$query_args = null;
@@ -172,7 +175,7 @@ function drop_query_params($s, $p) {
$parsed['query'] = $query;
}
- return unparse_url($parsed);
+ return escape_tags(unparse_url($parsed));
}
@@ -261,25 +264,25 @@ function zidify_text($s) {
*/
function red_zrl_callback($matches) {
- // Catch and exclude trailing punctuation
- preg_match("/[.,;:!?)]*$/i", $matches[2], $pts);
- $matches[2] = substr($matches[2], 0, strlen($matches[2])-strlen($pts[0]));
+ // Catch and exclude trailing punctuation
+ preg_match("/[.,;:!?)]*$/i", $matches[2], $pts);
+ $matches[2] = substr($matches[2], 0, strlen($matches[2])-strlen($pts[0]));
- $zrl = is_matrix_url($matches[2]);
+ $zrl = is_matrix_url($matches[2]);
- $t = strip_zids($matches[2]);
- if($t !== $matches[2]) {
- $zrl = true;
- $matches[2] = $t;
- }
+ $t = strip_zids($matches[2]);
+ if($t !== $matches[2]) {
+ $zrl = true;
+ $matches[2] = $t;
+ }
- if($matches[1] === '#^')
- $matches[1] = '';
+ if($matches[1] === '#^')
+ $matches[1] = '';
- if($zrl)
- return $matches[1] . '#^[zrl=' . $matches[2] . ']' . $matches[2] . '[/zrl]' . $pts[0];
+ if($zrl)
+ 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];
}
/**