diff options
Diffstat (limited to 'include')
-rw-r--r-- | include/account.php | 296 | ||||
-rw-r--r-- | include/auth.php | 7 | ||||
-rw-r--r-- | include/bbcode.php | 58 | ||||
-rw-r--r-- | include/conversation.php | 18 | ||||
-rw-r--r-- | include/event.php | 2 | ||||
-rw-r--r-- | include/items.php | 457 | ||||
-rw-r--r-- | include/js_strings.php | 1 | ||||
-rw-r--r-- | include/network.php | 1 | ||||
-rw-r--r-- | include/photo/photo_driver.php | 2 |
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)=(.*?)\&\;(.*?)\>/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)) { |