aboutsummaryrefslogtreecommitdiffstats
path: root/include
diff options
context:
space:
mode:
Diffstat (limited to 'include')
-rw-r--r--include/account.php296
-rw-r--r--include/auth.php7
-rw-r--r--include/bbcode.php58
-rw-r--r--include/conversation.php18
-rw-r--r--include/event.php2
-rw-r--r--include/items.php457
-rw-r--r--include/js_strings.php1
-rw-r--r--include/network.php1
-rw-r--r--include/photo/photo_driver.php2
9 files changed, 333 insertions, 509 deletions
diff --git a/include/account.php b/include/account.php
index 615c802f4..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) {
diff --git a/include/auth.php b/include/auth.php
index 439f064e4..36a9043ce 100644
--- a/include/auth.php
+++ b/include/auth.php
@@ -353,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'];
@@ -364,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 2c8ef3f20..d5e1bafd9 100644
--- a/include/bbcode.php
+++ b/include/bbcode.php
@@ -1190,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
@@ -1332,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);
@@ -1360,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);
}
@@ -1737,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
@@ -1813,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/conversation.php b/include/conversation.php
index 97a65c27d..07e4df088 100644
--- a/include/conversation.php
+++ b/include/conversation.php
@@ -1426,7 +1426,7 @@ function get_responses($response_verbs, $item) {
}
$ret[$v]['count'] = $item[$v . '_count'] ?? 0;
- $ret[$v]['button'] = get_response_button_text($v, $ret[$v]['count']);
+ $ret[$v]['button'] = get_response_button_text($v, $ret[$v]['count'], $item['item_thread_top']);
}
//logger('ret: ' . print_r($ret,true));
@@ -1434,7 +1434,7 @@ function get_responses($response_verbs, $item) {
return $ret;
}
-function get_response_button_text($v, $count = 0) {
+function get_response_button_text($v, $count = 0, $top_level = 0) {
switch($v) {
case 'like':
return ['label' => tt('Like','Likes',$count,'noun'), 'icon' => 'hand-thumbs-up', 'class' => 'like', 'action' => 'dolike'];
@@ -1446,16 +1446,16 @@ function get_response_button_text($v, $count = 0) {
return ['label' => tt('Dislike','Dislikes',$count,'noun'), 'icon' => 'hand-thumbs-down', 'class' => 'dislike', 'action' => 'dolike'];
break;
case 'comment':
- return ['label' => tt('Reply','Replies',$count,'noun'), 'icon' => 'chat', 'class' => 'comment', 'action' => ''];
+ 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', 'class' => 'attendyes', 'action' => 'dolike'];
+ 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-x', 'class' => 'attendno', 'action' => 'dolike'];
+ 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', 'class' => 'attendmaybe', 'action' => 'dolike'];
+ case 'tentativeaccept':
+ return ['label' => tt('Undecided','Undecided',$count,'noun'), 'icon' => 'calendar', 'class' => 'tentativeaccept', 'action' => 'dolike'];
break;
default:
return [];
diff --git a/include/event.php b/include/event.php
index b83a733b8..39d0c49c2 100644
--- a/include/event.php
+++ b/include/event.php
@@ -106,7 +106,7 @@ function format_event_obj($jobject) {
$title = $object['name'] ?? '';
$content = html2bbcode($object['content']);
- if (strpos($object['source']['content'], '[/event-description]') !== false) {
+ 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];
diff --git a/include/items.php b/include/items.php
index 55d768e28..b80c5672b 100644
--- a/include/items.php
+++ b/include/items.php
@@ -4802,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';
@@ -4823,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;
@@ -5361,250 +5361,238 @@ function set_activity_mid($string) {
}
/**
- * @brief returns SQL which counts activities for an item and
- * if there is an observer also count activities authored by observer.
- * @param string $prefix (optional)
- */
-
-function item_activity_sql($prefix = 'c') {
- $sql = '';
- $observer = get_observer_hash();
-
- $thread_allow = ((local_channel()) ? PConfig::Get(local_channel(), 'system', 'thread_allow', true) : Config::Get('system', 'thread_allow', true));
-
- if ($observer) {
- $sql = <<<SQL
- COUNT(CASE WHEN $prefix.verb = 'Like' AND $prefix.author_xchan = '$observer' THEN 1 END) AS observer_liked,
- COUNT(CASE WHEN $prefix.verb = 'Dislike' AND $prefix.author_xchan = '$observer' THEN 1 END) AS observer_disliked,
- COUNT(CASE WHEN $prefix.verb = 'Announce' AND $prefix.author_xchan = '$observer' THEN 1 END) AS observer_announced,
- COUNT(CASE WHEN $prefix.verb = 'Accept' AND $prefix.author_xchan = '$observer' THEN 1 END) AS observer_accepted,
- COUNT(CASE WHEN $prefix.verb = 'Reject' AND $prefix.author_xchan = '$observer' THEN 1 END) AS observer_rejected,
- COUNT(CASE WHEN $prefix.verb = 'TentativeAccept' AND $prefix.author_xchan = '$observer' THEN 1 END) AS observer_tentativelyaccepted,
- SQL;
-
- if ($thread_allow) {
- $sql .= " COUNT(CASE WHEN $prefix.verb IN ('Create','Update') AND $prefix.author_xchan = '$observer' THEN 1 END) AS observer_commented, ";
- }
- }
-
-
- if ($thread_allow) {
- $sql .= "COUNT(CASE WHEN $prefix.verb IN ('Create','Update') THEN 1 END) AS comment_count,";
- }
-
- $sql .= <<<SQL
- COUNT(CASE WHEN $prefix.verb = 'Like' THEN 1 END) AS like_count,
- COUNT(CASE WHEN $prefix.verb = 'Dislike' THEN 1 END) AS dislike_count,
- COUNT(CASE WHEN $prefix.verb = 'Announce' THEN 1 END) AS announce_count,
- COUNT(CASE WHEN $prefix.verb = 'Accept' THEN 1 END) AS attendyes_count,
- COUNT(CASE WHEN $prefix.verb = 'Reject' THEN 1 END) AS attendno_count,
- COUNT(CASE WHEN $prefix.verb = 'TentativeAccept' THEN 1 END) AS attendmaybe_count
- SQL;
-
- return $sql;
-
-}
-
-/**
- * @brief returns an item by id belonging to local_channel()
+ * @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): array
+function item_by_item_id(int $id, int $parent): array
{
- if (!$id) {
+ if (!$id && !$parent && !local_channel()) {
return [];
}
- $item_normal = item_normal();
- $item_normal_c = item_normal(prefix: 'c');
- $activity_sql = item_activity_sql('c');
+ $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'];
- $ret = q("SELECT item.*,
- $activity_sql
+ return q("WITH
+ $reaction_cte_sql
+ SELECT
+ *,
+ $reaction_select_sql
FROM item
- LEFT JOIN item c
- ON c.parent = item.parent
- AND c.item_thread_top = 0
- AND c.thr_parent = item.mid
- $item_normal_c
- WHERE item.id = $id
+ $reaction_join_sql
+ WHERE
+ item.id = %d
AND item.uid = %d
- $item_normal
- GROUP BY item.id",
+ AND item.verb IN ('Create', 'Update', 'EmojiReact')
+ $item_normal_sql",
+ intval($id),
intval(local_channel())
);
-
- return $ret;
}
+
/**
* @brief returns an array of items by ids
- * ATTENTION: no permissions for the pa are checked here!!!
- * Permissions MUST be checked by the function which returns the ids.
- * @param string $ids - a string with ids separated by comma
- * @param array $thr_parents (optional) - a string with thr_parent mids separated by comma
- * which will be included
- * @param string $permission_sql (optional) - SQL provided by item_permission_sql() from the calling module
+ * 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(string $ids, array $thr_parents = [], string $permission_sql = '', bool $blog_mode = false): array
+function items_by_parent_ids(array $parents, null|array $thr_parents = null, string $permission_sql = '', bool $blog_mode = false): array
{
- if (!$ids) {
+ 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();
- $activity_sql_cte = item_activity_sql_cte();
- $activity_sql_cte_sub = item_activity_sql_cte('sub');
+ $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) {
- $ret = q("SELECT item.*,
- $activity_sql_cte
- FROM item
- WHERE item.id IN (%s)
- $item_normal_sql
- $permission_sql",
- dbesc($ids)
- );
+ $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));
}
- else {
- $ret = q("WITH parents AS (
- SELECT item.*,
- 0 AS rn, -- this is required for union (equal amount of coulumns)
- $activity_sql_cte
+
+ $q = <<<SQL
+ WITH
+ parent_items AS (
+ SELECT
+ item.*,
+ 0 AS rn
FROM item
- WHERE item.id IN (%s)
- $item_normal_sql
+ 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
- ),
- comments AS (
- SELECT sub.*,
- $activity_sql_cte_sub
- FROM (
- SELECT item.*,
- ROW_NUMBER() OVER (PARTITION BY item.parent ORDER BY item.created DESC) AS rn
- FROM item
- WHERE item.parent IN (%s)
- AND item.id != item.parent
- AND (
- item.verb NOT IN ('Like', 'Dislike', 'Announce', 'Accept', 'Reject', 'TentativeAccept')
- OR (item.verb = 'Announce' AND item.item_thread_top = 1)
- )
- $thr_parent_sql
- $item_normal_sql
- $permission_sql
- ) sub
- WHERE rn <= 100 -- number of comments we want to load
- )
- SELECT * FROM parents
+ $item_normal_sql
+ ),
+
+ final_selection AS (
+ SELECT * FROM parent_items
UNION ALL
- SELECT * FROM comments",
- dbesc($ids),
- dbesc($ids)
- );
- }
+ SELECT * FROM all_comments WHERE all_comments.rn <= $limit
+ )
+
+ SELECT
+ final_selection.*,
+ $reaction_select_sql
+ FROM final_selection
+ $reaction_join_sql
+ SQL;
- return $ret;
+ return dbq(trim($q));
}
/**
- * @brief returns SQL which counts activities for an item and
- * if there is an observer also count activities authored by observer.
- * @param string $prefix (optional)
+ * @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_activity_sql_cte($prefix = 'item'): string
+function item_reaction_sql(string $ids, string $permission_sql = '', string $join_prefix = 'item'): array
{
- $thread_allow = ((local_channel()) ? PConfig::Get(local_channel(), 'system', 'thread_allow', true) : Config::Get('system', 'thread_allow', true));
+ $item_normal_sql = item_normal();
$observer = get_observer_hash();
- $sql = '';
-
- if ($observer) {
- $observer_verbs = [
- 'Like' => 'observer_liked',
- 'Dislike' => 'observer_disliked',
- 'Announce' => 'observer_announced',
- 'Accept' => 'observer_accepted',
- 'Reject' => 'observer_rejected',
- 'TentativeAccept' => 'observer_tentativelyaccepted'
- ];
- foreach($observer_verbs as $k => $v) {
- if ($sql) {
- $sql .= ",\n";
- }
+ $verbs = [
+ 'like' => ['Like'],
+ 'dislike' => ['Dislike'],
+ 'announce' => ['Announce'],
+ 'accept' => ['Accept'],
+ 'reject' => ['Reject'],
+ 'tentativeaccept' => ['TentativeAccept']
+ ];
- $sql .= <<<SQL
- (SELECT COUNT(*) FROM item AS reaction
- WHERE reaction.parent = $prefix.parent AND reaction.verb = '$k' AND reaction.author_xchan = '$observer' AND reaction.item_thread_top = 0 AND reaction.thr_parent = $prefix.mid
- ) AS $v
- SQL;
- }
+ $thread_allow = ((local_channel()) ? PConfig::Get(local_channel(), 'system', 'thread_allow', true) : Config::Get('system', 'thread_allow', true));
- if ($thread_allow) {
- $sql .= ",\n";
- $sql .= <<<SQL
- (SELECT COUNT(*) FROM item AS reaction
- WHERE reaction.parent = $prefix.parent AND reaction.verb IN ('Create', 'Update') AND reaction.author_xchan = '$observer' AND reaction.item_thread_top = 0 AND reaction.thr_parent = $prefix.mid
- ) AS observer_commented
- SQL;
- }
+ if ($thread_allow) {
+ $verbs['comment'] = ['Create', 'Update', 'EmojiReact'];
}
- $verbs = [
- 'Like' => 'like_count',
- 'Dislike' => 'dislike_count',
- 'Announce' => 'announce_count',
- 'Accept' => 'attendyes_count',
- 'Reject' => 'attendno_count',
- 'TentativeAccept' => 'attendmaybe_count'
- ];
+ $cte = '';
+ $select = '';
+ $join = '';
foreach($verbs as $k => $v) {
- if ($sql) {
- $sql .= ",\n";
+
+ $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";
}
- $sql .= <<<SQL
- (SELECT COUNT(*) FROM item AS reaction
- WHERE reaction.parent = $prefix.parent AND reaction.verb = '$k' AND reaction.item_thread_top = 0 AND reaction.thr_parent = $prefix.mid
- ) AS $v
+ $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 ($thread_allow) {
- $sql .= ",\n";
- $sql .= <<<SQL
- (SELECT COUNT(*) FROM item AS reaction
- WHERE reaction.parent = $prefix.parent AND reaction.verb IN ('Create', 'Update') AND reaction.item_thread_top = 0 AND reaction.thr_parent = $prefix.mid
- ) AS comment_count
+ 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;
+
}
- return $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): array
+function items_by_thr_parent(string $mid, int $parent, int|null $offset = null): array
{
if (!$mid && !$parent) {
return [];
@@ -5614,63 +5602,75 @@ function items_by_thr_parent(string $mid, int $parent): array
intval($parent)
);
- $owner_uid = intval($parent_item[0]['uid']);
+ $order_sql = "ORDER BY item.created";
+ if (isset($offset)) {
+ $order_sql = "ORDER BY item.created DESC, item.received DESC LIMIT 3 OFFSET $offset";
+ }
- $item_normal = item_normal($owner_uid);
- $item_normal_c = item_normal($owner_uid, 'c');
- $activity_sql = item_activity_sql('c');
+ $owner_uid = intval($parent_item[0]['uid']);
+ $item_normal_sql = item_normal($owner_uid);
if (local_channel() === $owner_uid) {
- $ret = q(
- "SELECT item.*,
- $activity_sql
+ $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
- LEFT JOIN item c ON c.parent = item.parent
- AND c.item_thread_top = 0
- AND c.thr_parent = item.mid
- $item_normal_c
- WHERE item.thr_parent = '%s'
+ $reaction_join_sql
+ WHERE
+ item.thr_parent = '%s'
AND item.uid = %d
- AND item.parent = %d
- AND item.verb NOT IN ('Like', 'Dislike', 'Announce', 'Accept', 'Reject', 'TentativeAccept')
+ AND item.verb IN ('Create', 'Update', 'EmojiReact')
AND item.item_thread_top = 0
- $item_normal
- GROUP BY item.id
- ORDER BY item.created",
+ $item_normal_sql
+ $order_sql",
dbesc($mid),
- intval(local_channel()),
- intval($parent)
+ intval($owner_uid)
);
}
-
- if (!$ret) {
+ else {
$observer_hash = get_observer_hash();
- $sql_extra = item_permissions_sql($owner_uid, $observer_hash);
-
- $ret = q(
- "SELECT item.*,
- $activity_sql
+ $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
- LEFT JOIN item c ON c.parent = item.parent
- AND c.item_thread_top = 0
- AND c.thr_parent = item.mid
- $item_normal_c
- WHERE item.thr_parent = '%s'
+ $reaction_join_sql
+ WHERE
+ item.thr_parent = '%s'
AND item.uid = %d
- AND item.verb NOT IN ('Like', 'Dislike', 'Announce', 'Accept', 'Reject', 'TentativeAccept')
+ AND item.verb IN ('Create', 'Update', 'EmojiReact')
AND item.item_thread_top = 0
- $sql_extra
- $item_normal
- GROUP BY item.id
- ORDER BY item.created",
+ $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.
@@ -5702,6 +5702,7 @@ function item_activity_xchans(string $mid, int $parent, string $verb): array
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),
@@ -5709,8 +5710,7 @@ function item_activity_xchans(string $mid, int $parent, string $verb): array
dbesc($verb)
);
}
-
- if (!$ret) {
+ 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
@@ -5721,6 +5721,7 @@ function item_activity_xchans(string $mid, int $parent, string $verb): array
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),
@@ -5740,8 +5741,13 @@ function item_activity_xchans(string $mid, int $parent, string $verb): array
* @param array $item
*/
-function get_recursive_thr_parents(array $item): array
+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'];
@@ -5755,8 +5761,13 @@ function get_recursive_thr_parents(array $item): array
dbesc($mid)
);
+ if (!$x) {
+ break;
+ }
+
$mid = $x[0]['thr_parent'];
$thr_parents[] = $x[0]['thr_parent'];
+
$i++;
}
diff --git a/include/js_strings.php b/include/js_strings.php
index 1772cb66b..6f2ffd351 100644
--- a/include/js_strings.php
+++ b/include/js_strings.php
@@ -36,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
diff --git a/include/network.php b/include/network.php
index cb5027922..83bb281a4 100644
--- a/include/network.php
+++ b/include/network.php
@@ -433,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));
diff --git a/include/photo/photo_driver.php b/include/photo/photo_driver.php
index 3de873638..88b9d1d62 100644
--- a/include/photo/photo_driver.php
+++ b/include/photo/photo_driver.php
@@ -76,7 +76,7 @@ function guess_image_type($filename, $data = []) {
logger('filename: ' . print_r($filename, true), LOGGER_DEBUG);
// Try Fileinfo from raw data
- if (!empty($data['body'])) {
+ 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)) {