diff options
Diffstat (limited to 'include')
44 files changed, 1580 insertions, 1309 deletions
diff --git a/include/account.php b/include/account.php index a1fb0f159..615c802f4 100644 --- a/include/account.php +++ b/include/account.php @@ -4,6 +4,7 @@ * @brief Somme account related functions. */ +use Zotlabs\Lib\Config; use Zotlabs\Lib\Crypto; require_once('include/config.php'); @@ -83,8 +84,8 @@ function check_account_invite($invite_code) { $result = array('error' => false, 'message' => ''); // [hilmar -> - $using_invites = (get_config('system','invitation_only') - || get_config('system','invitation_also')); + $using_invites = (Config::Get('system','invitation_only') + || Config::Get('system','invitation_also')); if($using_invites) { @@ -118,7 +119,7 @@ function check_account_invite($invite_code) { function check_account_admin($arr) { if(is_site_admin()) return true; - $admin_email = trim(get_config('system','admin_email')); + $admin_email = trim(Config::Get('system','admin_email')); if(strlen($admin_email) && $admin_email === trim($arr['email'])) return true; return false; @@ -182,7 +183,7 @@ function create_account_IS_OBSOLETE($arr) { $roles = ((x($arr,'account_roles')) ? intval($arr['account_roles']) : 0 ); $expires = ((x($arr,'expires')) ? intval($arr['expires']) : NULL_DATE); - $default_service_class = get_config('system','default_service_class'); + $default_service_class = Config::Get('system','default_service_class'); if($default_service_class === false) $default_service_class = ''; @@ -209,10 +210,10 @@ function create_account_IS_OBSOLETE($arr) { // Ensure that there is a host keypair. - if ((! get_config('system', 'pubkey')) && (! get_config('system', 'prvkey'))) { + if ((! Config::Get('system', 'pubkey')) && (! Config::Get('system', 'prvkey'))) { $hostkey = Crypto::new_keypair(4096); - set_config('system', 'pubkey', $hostkey['pubkey']); - set_config('system', 'prvkey', $hostkey['prvkey']); + Config::Set('system', 'pubkey', $hostkey['pubkey']); + Config::Set('system', 'prvkey', $hostkey['prvkey']); } $invite_result = check_account_invite($invite_code); @@ -319,7 +320,7 @@ function create_account_from_register($arr) { // account $expires = NULL_DATE; - $default_service_class = get_config('system','default_service_class'); + $default_service_class = Config::Get('system','default_service_class'); if($default_service_class === false) $default_service_class = ''; @@ -420,7 +421,7 @@ function verify_email_address($arr) { $email_msg = replace_macros(get_intltext_template('register_verify_member.tpl'), [ - '$sitename' => get_config('system','sitename'), + '$sitename' => Config::Get('system','sitename'), '$siteurl' => z_root(), '$email' => $arr['email'], '$uid' => 1, @@ -432,7 +433,7 @@ function verify_email_address($arr) { $res = z_mail( [ 'toEmail' => $arr['email'], - 'messageSubject' => sprintf( t('Registration confirmation for %s'), get_config('system','sitename')), + 'messageSubject' => sprintf( t('Registration confirmation for %s'), Config::Get('system','sitename')), 'textVersion' => $email_msg, ] ); @@ -488,7 +489,7 @@ function verify_email_addressNOP($arr) { $email_msg = replace_macros(get_intltext_template('register_verify_member.tpl'), [ - '$sitename' => get_config('system','sitename'), + '$sitename' => Config::Get('system','sitename'), '$siteurl' => z_root(), '$email' => $arr['email'], '$uid' => $account['account_id'], @@ -500,7 +501,7 @@ function verify_email_addressNOP($arr) { $res = z_mail( [ 'toEmail' => $arr['email'], - 'messageSubject' => sprintf( t('Registration confirmation for %s'), get_config('system','sitename')), + 'messageSubject' => sprintf( t('Registration confirmation for %s'), Config::Get('system','sitename')), 'textVersion' => $email_msg, ] ); @@ -564,7 +565,7 @@ function send_reg_approval_email($arr) { push_lang('en'); $email_msg = replace_macros(get_intltext_template('register_verify_eml.tpl'), array( - '$sitename' => get_config('system','sitename'), + '$sitename' => Config::Get('system','sitename'), '$siteurl' => z_root(), '$email' => $arr['email'], '$uid' => $arr['account']['account_id'], @@ -575,7 +576,7 @@ function send_reg_approval_email($arr) { $res = z_mail( [ 'toEmail' => $admin['email'], - 'messageSubject' => sprintf( t('Registration request at %s'), get_config('system','sitename')), + 'messageSubject' => sprintf( t('Registration request at %s'), Config::Get('system','sitename')), 'textVersion' => $email_msg, ] ); @@ -594,7 +595,7 @@ function send_reg_approval_email($arr) { function send_register_success_email($email,$password) { $email_msg = replace_macros(get_intltext_template('register_open_eml.tpl'), array( - '$sitename' => get_config('system','sitename'), + '$sitename' => Config::Get('system','sitename'), '$siteurl' => z_root(), '$email' => $email, '$password' => t('your registration password'), @@ -603,7 +604,7 @@ function send_register_success_email($email,$password) { $res = z_mail( [ 'toEmail' => $email, - 'messageSubject' => sprintf( t('Registration details for %s'), get_config('system','sitename')), + 'messageSubject' => sprintf( t('Registration details for %s'), Config::Get('system','sitename')), 'textVersion' => $email_msg, ] ); @@ -612,59 +613,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'] @@ -673,45 +660,43 @@ function account_allow($hash) { ); if($r1 && $r2) { - q("COMMIT"); - - // <- hilmar] + $transaction->commit(); push_lang($register[0]['reg_lang']); $email_tpl = get_intltext_template("register_open_eml.tpl"); $email_msg = replace_macros($email_tpl, array( - '$sitename' => get_config('system','sitename'), + '$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'], - 'messageSubject' => sprintf( t('Registration details for %s'), get_config('system','sitename')), + '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(get_config('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; } @@ -844,7 +829,7 @@ function account_approve($hash) { if(! $account) return $ret; - if(get_config('system','auto_channel_create')) + if(Config::Get('system','auto_channel_create')) auto_channel_create($register[0]['reg_uid']); else { $_SESSION['login_return_url'] = 'new_channel'; @@ -992,7 +977,7 @@ function downgrade_accounts() { if(! $r) return; - $basic = get_config('system','default_service_class'); + $basic = Config::Get('system','default_service_class'); foreach($r as $rr) { if(($basic) && ($rr['account_service_class']) && ($rr['account_service_class'] != $basic)) { @@ -1135,7 +1120,7 @@ function service_class_fetch($uid, $property) { if(! x($service_class)) return false; // everything is allowed - $arr = get_config('service_class', $service_class); + $arr = Config::Get('service_class', $service_class); if(! is_array($arr) || (! count($arr))) return false; @@ -1169,7 +1154,7 @@ function account_service_class_fetch($aid, $property) { if(! isset($service_class)) return false; // everything is allowed - $arr = get_config('service_class', $service_class); + $arr = Config::Get('service_class', $service_class); if(! is_array($arr) || (! count($arr))) return false; @@ -1179,7 +1164,7 @@ function account_service_class_fetch($aid, $property) { function upgrade_link($bbcode = false) { - $l = get_config('service_class', 'upgrade_link'); + $l = Config::Get('service_class', 'upgrade_link'); if(! $l) return ''; if($bbcode) @@ -1208,7 +1193,7 @@ function get_account_techlevel($account_id = 0) { function zar_log($msg='') { - if(get_config('system', 'register_logfile', 0)) { + if(Config::Get('system', 'register_logfile', 0)) { file_put_contents('./zar.log', date('Y-m-d_H:i:s') . ' ' . $msg . ', ip: § ' . $_SERVER['REMOTE_ADDR'] . ' §' . "\n", FILE_APPEND); } 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 22692b962..56cec005d 100644 --- a/include/api_zot.php +++ b/include/api_zot.php @@ -546,6 +546,7 @@ return false; } + logger('api_red_item_store: REQUEST ' . print_r($_REQUEST,true)); logger('api_red_item_store: FILES ' . print_r($_FILES,true)); @@ -561,7 +562,7 @@ $mod = new Zotlabs\Module\Wall_attach(); $media = $mod->post(); if($media) - $_REQUEST['body'] = $media . "\n\n" . $_REQUEST['body']; + $_REQUEST['body'] = $media . "\n" . $_REQUEST['body']; } $mod = new Zotlabs\Module\Item(); diff --git a/include/attach.php b/include/attach.php index e5a2900b3..bda4905f1 100644 --- a/include/attach.php +++ b/include/attach.php @@ -11,11 +11,12 @@ * @todo Also an 'append' option to the storage function might be a useful addition. */ -use Zotlabs\Lib\Libsync; -use Zotlabs\Lib\Activity; use Zotlabs\Access\PermissionLimits; use Zotlabs\Daemon\Master; use Zotlabs\Lib\AccessList; +use Zotlabs\Lib\Activity; +use Zotlabs\Lib\Config; +use Zotlabs\Lib\Libsync; require_once('include/permissions.php'); require_once('include/security.php'); @@ -112,7 +113,6 @@ function z_mime_content_type($filename) { 'odf' => 'application/vnd.oasis.opendocument.formula', 'odi' => 'application/vnd.oasis.opendocument.image', 'odm' => 'application/vnd.oasis.opendocument.text-master', - 'odb' => 'application/vnd.oasis.opendocument.base', 'odb' => 'application/vnd.oasis.opendocument.database', 'ott' => 'application/vnd.oasis.opendocument.text-template', 'ots' => 'application/vnd.oasis.opendocument.spreadsheet-template', @@ -801,7 +801,7 @@ function attach_store($channel, $observer_hash, $options = '', $arr = null) { // Check storage limits if($options !== 'update') { - $maxfilesize = get_config('system','maxfilesize'); + $maxfilesize = Config::Get('system','maxfilesize'); if(($maxfilesize) && ($filesize > $maxfilesize)) { logger('quota_exceeded'); @@ -1606,8 +1606,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 +1635,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 +1853,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 +1902,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 +1916,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 +1945,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 +1971,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 +1990,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 +2006,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']]); + } } } @@ -2466,8 +2474,8 @@ function copy_folder_to_cloudfiles($channel, $observer_hash, $srcpath, $cloudpat * @param int $channel_id * @param int $resource_id * @param string $new_folder_hash - * @param (optional) string $newname - * @param (optional) boolean $recurse + * @param string (optional) $newname + * @param boolean (optional) $recurse * @return array Associative array with: * * \e boolean \b success * * \e string \b resource_id @@ -2599,33 +2607,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']); + } } } } @@ -2667,8 +2673,8 @@ function attach_move($channel_id, $resource_id, $new_folder_hash, $newname = '', * @param int $channel_id * @param int $resource_id * @param string $new_folder_hash - * @param (optional) string $newname - * @param (optional) boolean $recurse + * @param string (optional) $newname + * @param boolean (optional) $recurse * @return array Associative array with: * * \e boolean \b success * * \e string \b resource_id of the new resource @@ -2935,41 +2941,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 @@ -2981,11 +2952,17 @@ function save_chunk($channel,$start,$end,$len) { $result = []; - $tmp_path = $_FILES['files']['tmp_name']; + $file = $_FILES['files'] ?? $_FILES['userfile'] ?? []; + + if (!$file) { + return $result; + } + + $tmp_path = $file['tmp_name']; $new_base = 'store/[data]/' . $channel['channel_address'] . '/tmp'; os_mkdir($new_base,STORAGE_DEFAULT_PERMISSIONS,true); - $new_path = $new_base . '/' . $_FILES['files']['name']; + $new_path = $new_base . '/' . $file['name']; if(file_exists($new_path) && intval($start) === 0) { $result['partial'] = true; @@ -3007,8 +2984,8 @@ function save_chunk($channel,$start,$end,$len) { } if(($len - 1) == $end) { unlink($tmp_path); - $result['name'] = $_FILES['files']['name']; - $result['type'] = $_FILES['files']['type']; + $result['name'] = $file['name']; + $result['type'] = $file['type']; $result['tmp_name'] = $new_path; $result['error'] = 0; $result['size'] = $len; diff --git a/include/auth.php b/include/auth.php index 5956b89e2..1fc2cc556 100644 --- a/include/auth.php +++ b/include/auth.php @@ -9,8 +9,9 @@ * Also provides a function for OpenID identiy matching. */ -use Zotlabs\Lib\Libzot; use Zotlabs\Lib\AConfig; +use Zotlabs\Lib\Config; +use Zotlabs\Lib\Libzot; use Zotlabs\Module\Totp_check; require_once('include/api_auth.php'); @@ -43,8 +44,8 @@ function account_verify_password($login, $pass) { $ret = [ 'account' => null, 'channel' => null, 'xchan' => null ]; $login = punify($login); - $email_verify = get_config('system', 'verify_email'); - $register_policy = get_config('system', 'register_policy'); + $email_verify = Config::Get('system', 'verify_email'); + $register_policy = Config::Get('system', 'register_policy'); if(!$login || !$pass) return null; @@ -170,11 +171,45 @@ function account_verify_password($login, $pass) { * Error message to display for failed login. */ function log_failed_login($errormsg) { - $authlog = get_config('system', 'authlog'); + $authlog = Config::Get('system', 'authlog'); if ($authlog) @file_put_contents($authlog, datetime_convert() . ':' . session_id() . ' ' . $errormsg . PHP_EOL, FILE_APPEND); } + +/** + * Determines if checking for multifactor authentication needs to be checked. + * + * Checks that multi factor authentication is enabled for the given account_id, + * and whether it's already authenticated or not. + * + * Some modules needs to be excluded from the mfa checks for various reasons: + * + * - `totp_check` is used by the mfa module itself. + * - `dav` provides WebDAV access, and has no way of providing a mfa code. + * - `cdav` is accessed both via CardDAV which has the same limitations as + * the `dav` module, but may also be accessed via a web browser over http. + * We only exclude it if it's not being accessed via a web browser. + * + * @param int $account_id The id of the account we're verifying. + * @param string $module The requested module. + * @param string $arg The first arg passed to the module (or empty if none.) + * + * @return bool `true` if mfa status needs to be checked, `false` otherwise. + */ +function requires_mfa_check(int $account_id, string $module, string $arg): bool { + if (in_array($module, ['totp_check', 'dav'], true)) { + return false; + } + + if ($module === 'cdav' && !in_array($arg, ['addressbook', 'calendar'], true)) { + return false; + } + + $multiFactor = AConfig::Get($account_id, 'system', 'mfa_enabled'); + return $multiFactor && empty($_SESSION['2FA_VERIFIED']); +} + /** * Inline - not a function * look for auth parameters or re-validate an existing session @@ -208,10 +243,10 @@ if((isset($_SESSION)) && (x($_SESSION, 'authenticated')) && if(x($_SESSION, 'visitor_id') && (! x($_SESSION, 'uid'))) { // if our authenticated guest is allowed to take control of the admin channel, make it so. - $admins = get_config('system', 'remote_admin'); + $admins = Config::Get('system', 'remote_admin'); if($admins && is_array($admins) && in_array($_SESSION['visitor_id'], $admins)) { $x = q("select * from account where account_email = '%s' and account_email != '' and ( account_flags & %d )>0 limit 1", - dbesc(get_config('system', 'admin_email')), + dbesc(Config::Get('system', 'admin_email')), intval(ACCOUNT_ROLE_ADMIN) ); if($x) { @@ -266,8 +301,7 @@ if((isset($_SESSION)) && (x($_SESSION, 'authenticated')) && $login_refresh = true; } - $multiFactor = AConfig::Get(App::$account['account_id'], 'system', 'mfa_enabled'); - if ($multiFactor && empty($_SESSION['2FA_VERIFIED']) && App::$module !== 'totp_check') { + if (requires_mfa_check(App::$account['account_id'], App::$module, argv(1))) { $o = new Totp_check; echo $o->get(); killme(); @@ -328,7 +362,7 @@ else { $error = 'authenticate: failed login attempt: ' . notags(trim($username)) . ' from IP ' . $_SERVER['REMOTE_ADDR']; logger($error); // Also log failed logins to a separate auth log to reduce overhead for server side intrusion prevention - $authlog = get_config('system', 'authlog'); + $authlog = Config::Get('system', 'authlog'); if ($authlog) @file_put_contents($authlog, datetime_convert() . ':' . session_id() . ' ' . $error . "\n", FILE_APPEND); notice( t('Login failed.') . EOL ); diff --git a/include/bbcode.php b/include/bbcode.php index d7ed3a243..65bda1b7b 100644 --- a/include/bbcode.php +++ b/include/bbcode.php @@ -4,8 +4,9 @@ * @brief BBCode related functions for parsing, etc. */ -use Zotlabs\Lib\SvgSanitizer; +use Zotlabs\Lib\Config; use Zotlabs\Lib\Libzot; +use Zotlabs\Lib\SvgSanitizer; require_once('include/oembed.php'); require_once('include/event.php'); @@ -111,7 +112,7 @@ function tryzrlvideo($match) { if($zrl) $link = zid($link); - $static_link = get_config('system','video_default_poster','images/video_poster.jpg'); + $static_link = Config::Get('system','video_default_poster','images/video_poster.jpg'); if($static_link) $poster = 'poster="' . escape_tags($static_link) . '" ' ; @@ -1125,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); @@ -1142,7 +1145,7 @@ function parseIdentityAwareHTML($Text) { $Text = str_replace('[observer.photo]','', $Text); } - $Text = str_replace(array('[baseurl]','[sitename]'),array(z_root(),get_config('system','sitename')),$Text); + $Text = str_replace(array('[baseurl]','[sitename]'),array(z_root(),Config::Get('system','sitename')),$Text); // Unhide all [noparse] contained bbtags unspacefying them @@ -1249,7 +1252,7 @@ function bbcode($text, $options = []) { $text = $x['body']; $saved_images = $x['images']; - $text = str_replace(array('[baseurl]','[sitename]'),array(z_root(),get_config('system','sitename')),$text); + $text = str_replace(array('[baseurl]','[sitename]'),array(z_root(),Config::Get('system','sitename')),$text); // Replace any html brackets with HTML Entities to prevent executing HTML or script // Don't use strip_tags here because it breaks [url] search by replacing & with amp @@ -1310,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); @@ -1407,7 +1412,7 @@ function bbcode($text, $options = []) { } // Check for strike-through text if (strpos($text,'[s]') !== false) { - $text = preg_replace("(\[s\](.*?)\[\/s\])ism", '<span style="text-decoration: line-through;">$1</span>', $text); + $text = preg_replace("(\[s\](.*?)\[\/s\])ism", '<del>$1</del>', $text); } // Check for over-line text if (strpos($text,'[o]') !== false) { @@ -1744,10 +1749,8 @@ function bbcode($text, $options = []) { // oembed tag if (strpos($text,'[/embed]') !== false) { + $text = str_replace(["[/embed]\r", "[/embed]\n"], '[/embed]', $text); $text = oembed_bbcode2html($text); - - // Avoid triple linefeeds through oembed - $text = str_replace("<br style='clear:left'></span><br /><br />", "<br style='clear:left'></span>", $text); } // If we found an event earlier, strip out all the event code and replace with a reformatted version. diff --git a/include/channel.php b/include/channel.php index a82794bfd..a3ba1a765 100644 --- a/include/channel.php +++ b/include/channel.php @@ -9,6 +9,7 @@ use Zotlabs\Access\PermissionRoles; use Zotlabs\Access\PermissionLimits; use Zotlabs\Access\Permissions; use Zotlabs\Daemon\Master; +use Zotlabs\Lib\Config; use Zotlabs\Lib\Crypto; use Zotlabs\Lib\System; use Zotlabs\Render\Comanche; @@ -95,6 +96,8 @@ function validate_channelname($name) { if (x($arr, 'message')) return $arr['message']; + + return null; } @@ -108,11 +111,11 @@ function create_sys_channel() { // Ensure that there is a host keypair. - if ((! get_config('system', 'pubkey')) && (! get_config('system', 'prvkey'))) { + if ((! Config::Get('system', 'pubkey')) && (! Config::Get('system', 'prvkey'))) { require_once('include/crypto.php'); $hostkey = Crypto::new_keypair(4096); - set_config('system', 'pubkey', $hostkey['pubkey']); - set_config('system', 'prvkey', $hostkey['prvkey']); + Config::Set('system', 'pubkey', $hostkey['pubkey']); + Config::Set('system', 'prvkey', $hostkey['prvkey']); } create_identity([ @@ -198,8 +201,8 @@ function create_identity($arr) { $ret = array('success' => false); - if(! $arr['account_id']) { - $ret['message'] = t('No account identifier'); + if(empty($arr['account_id'])) { + $ret['message'] = t('No account identifier'); return $ret; } $ret = identity_check_service_class($arr['account_id']); @@ -357,10 +360,10 @@ function create_identity($arr) { 'hubloc_primary' => intval($primary), 'hubloc_url' => z_root(), 'hubloc_url_sig' => Libzot::sign(z_root(),$ret['channel']['channel_prvkey']), - 'hubloc_site_id' => Libzot::make_xchan_hash(z_root(),get_config('system','pubkey')), + 'hubloc_site_id' => Libzot::make_xchan_hash(z_root(),Config::Get('system','pubkey')), 'hubloc_host' => App::get_hostname(), 'hubloc_callback' => z_root() . '/zot', - 'hubloc_sitekey' => get_config('system','pubkey'), + 'hubloc_sitekey' => Config::Get('system','pubkey'), 'hubloc_network' => 'zot6', 'hubloc_updated' => datetime_convert() ] @@ -493,7 +496,7 @@ function create_identity($arr) { // otherwise it could get annoying. Don't make this list too big // or it will impact registration time. - $accts = get_config('system','auto_follow'); + $accts = Config::Get('system','auto_follow'); if(($accts) && (! $total_identities)) { if(! is_array($accts)) $accts = array($accts); @@ -1591,7 +1594,7 @@ function profile_sidebar($profile, $block = 0, $show_connect = true, $details = $reddress = true; $connect_url = ''; $connect = ''; - $default_cover = get_config('system', 'default_cover_photo', 'hubzilla'); + $default_cover = Config::Get('system', 'default_cover_photo', 'hubzilla'); $default_cover_url = z_root() . '/images/default_cover_photos/' . $default_cover . '/425.png'; @@ -2040,7 +2043,7 @@ function get_theme_uid() { * @return string with path to profile photo */ function get_default_profile_photo($size = 300) { - $scheme = get_config('system','default_profile_photo'); + $scheme = Config::Get('system','default_profile_photo'); if(! $scheme) $scheme = 'rainbow_man'; @@ -2171,7 +2174,7 @@ function is_public_profile() { if(! local_channel()) return false; - if(intval(get_config('system','block_public'))) + if(intval(Config::Get('system','block_public'))) return false; $channel = App::get_channel(); @@ -2186,7 +2189,7 @@ function is_public_profile() { function get_profile_fields_basic($filter = 0) { - $profile_fields_basic = (($filter == 0) ? get_config('system','profile_fields_basic') : null); + $profile_fields_basic = (($filter == 0) ? Config::Get('system','profile_fields_basic') : null); if(! $profile_fields_basic) $profile_fields_basic = array('fullname','pdesc','chandesc','comms','gender','dob','dob_tz','region','country_name','marital','sexual','homepage','hometown','keywords','about','contact'); @@ -2202,7 +2205,7 @@ function get_profile_fields_basic($filter = 0) { function get_profile_fields_advanced($filter = 0) { $basic = get_profile_fields_basic($filter); - $profile_fields_advanced = (($filter == 0) ? get_config('system','profile_fields_advanced') : null); + $profile_fields_advanced = (($filter == 0) ? Config::Get('system','profile_fields_advanced') : null); if(! $profile_fields_advanced) $profile_fields_advanced = array('address','locality','postal_code','partner','howlong','politic','religion','likes','dislikes','interest','channels','music','book','film','tv','romance','employment','education'); @@ -2423,11 +2426,11 @@ function get_zcard($channel, $observer_hash = '', $args = array()) { $cover = $r[0]; $cover['href'] = z_root() . '/photo/' . $r[0]['resource_id'] . '-' . $r[0]['imgscale']; } else { - $default_cover = get_config('system', 'default_cover_photo', 'hubzilla'); + $default_cover = Config::Get('system', 'default_cover_photo', 'hubzilla'); $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, @@ -2436,8 +2439,6 @@ function get_zcard($channel, $observer_hash = '', $args = array()) { '$pphoto' => $pphoto, '$zcard' => $zcard )); - - return $o; } @@ -2500,21 +2501,17 @@ function get_zcard_embed($channel, $observer_hash = '', $args = array()) { $cover['href'] = z_root() . '/photo/' . $r[0]['resource_id'] . '-' . $r[0]['imgscale']; } else { - $default_cover = get_config('system', 'default_cover_photo', 'hubzilla'); + $default_cover = Config::Get('system', 'default_cover_photo', 'hubzilla'); $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; } /** @@ -3105,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/cli_startup.php b/include/cli_startup.php index b9e7d124d..012b29fbf 100644 --- a/include/cli_startup.php +++ b/include/cli_startup.php @@ -2,11 +2,13 @@ require_once('boot.php'); +use Zotlabs\Lib\Config; + // Everything we need to boot standalone 'background' processes function cli_startup() { sys_boot(); - App::set_baseurl(get_config('system','baseurl')); + App::set_baseurl(Config::Get('system','baseurl')); } diff --git a/include/config.php b/include/config.php index ec3547a82..50fe60eb0 100644 --- a/include/config.php +++ b/include/config.php @@ -31,18 +31,81 @@ use Zotlabs\Lib as Zlib; +/** + * Loads the hub's configuration from database to a cached storage. + * + * Retrieve a category ($family) of config variables from database to a cached + * storage in the global App::$config[$family]. + * + * @param string $family The category of the configuration value + * + * @deprecated + * This function is deprecated, use Zotlabs\Lib\Config::Load + * instead. + */ function load_config($family) { Zlib\Config::Load($family); } +/** + * Get a particular config variable given the category name ($family) + * and a key. + * + * Get a particular config variable from the given category ($family) and the + * $key from a cached storage in App::$config[$family]. If a key is found in the + * DB but does not exist in local config cache, pull it into the cache so we + * do not have to hit the DB again for this item. + * + * Returns false if not set. + * + * @param string $family The category of the configuration value + * @param string $key The configuration key to query + * @param string $default (optional) default false + * + * @return mixed|false Return value or false on error or if not set + * + * @deprecated + * This function is deprecated, use Zotlabs\Lib\Config::Get + * instead. + */ function get_config($family, $key, $default = false) { return Zlib\Config::Get($family,$key,$default); } +/** + * Sets a configuration value for the hub. + * + * Stores a config value ($value) in the category ($family) under the key ($key). + * + * @param string $family The category of the configuration value + * @param string $key The configuration key to set + * @param mixed $value The value to store in the configuration + * + * @return mixed|false Return the set value, or false if the database update failed + * + * @deprecated + * This function is deprecated, use Zotlabs\Lib\Config::Set + * instead. + */ function set_config($family, $key, $value) { return Zlib\Config::Set($family,$key,$value); } +/** + * Deletes the given key from the hub's configuration database. + * + * Removes the configured value from the stored cache in App::$config[$family] + * and removes it from the database. + * + * @param string $family The category of the configuration value + * @param string $key The configuration key to delete + * + * @return mixed + * + * @deprecated + * This function is deprecated, use Zotlabs\Lib\Config::Delete + * instead. + */ function del_config($family, $key) { return Zlib\Config::Delete($family,$key); } @@ -55,8 +118,8 @@ function get_pconfig($uid, $family, $key, $default = false) { return Zlib\PConfig::Get($uid,$family,$key,$default); } -function set_pconfig($uid, $family, $key, $value) { - return Zlib\PConfig::Set($uid,$family,$key,$value); +function set_pconfig($uid, $family, $key, $value, $updated = NULL) { + return Zlib\PConfig::Set($uid, $family, $key, $value, $updated); } function del_pconfig($uid, $family, $key, $updated = NULL) { diff --git a/include/connections.php b/include/connections.php index 9a6ee7d8d..b3e9ba89d 100644 --- a/include/connections.php +++ b/include/connections.php @@ -1,6 +1,7 @@ <?php /** @file */ use Zotlabs\Daemon\Master; +use Zotlabs\Lib\Config; function abook_store_lowlevel($arr) { @@ -67,6 +68,14 @@ function rconnect_url($channel_id,$xchan) { } +/** + * @brief returns deliverable xchans for a channel. + * + * @param intval $channel_id + * @param array $filter (optional) + * @param boolean $flatten (optional) + */ + function deliverable_abook_xchans($channel_id, $filter = [], $flatten = true) { $filter_sql = ''; @@ -238,7 +247,7 @@ function abook_toggle_flag($abook,$flag) { function mark_orphan_hubsxchans() { - $dirmode = intval(get_config('system','directory_mode')); + $dirmode = intval(Config::Get('system','directory_mode')); if($dirmode == DIRECTORY_MODE_NORMAL) return; @@ -333,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']); } } @@ -504,15 +513,15 @@ function remove_abook_items($channel_id, $xchan_hash) { continue; } - drop_item($rr['id'],false); + drop_item($rr['id'], uid: $channel_id); } } function random_profile() { $randfunc = db_getfunc('rand'); - $checkrandom = get_config('randprofile','check'); // False by default - $retryrandom = intval(get_config('randprofile','retry')); + $checkrandom = Config::Get('randprofile','check'); // False by default + $retryrandom = intval(Config::Get('randprofile','retry')); if($retryrandom == 0) $retryrandom = 5; for($i = 0; $i < $retryrandom; $i++) { diff --git a/include/contact_widgets.php b/include/contact_widgets.php index e3fbc1057..84f688d36 100644 --- a/include/contact_widgets.php +++ b/include/contact_widgets.php @@ -1,11 +1,12 @@ <?php /** @file */ use Zotlabs\Lib\Cache; +use Zotlabs\Lib\Config; use Zotlabs\Daemon\Master; function findpeople_widget() { - if(get_config('system','invitation_only')) { + if(Config::Get('system','invitation_only')) { $x = get_pconfig(local_channel(),'system','invites_remaining'); if($x || is_site_admin()) { App::$page['aside'] .= '<div class="side-link" id="side-invite-remain">' diff --git a/include/conversation.php b/include/conversation.php index 79fe12d54..6dfefa707 100644 --- a/include/conversation.php +++ b/include/conversation.php @@ -1,7 +1,9 @@ <?php /** @file */ -use Zotlabs\Lib\Apps; use Zotlabs\Lib\Activity; +use Zotlabs\Lib\Apps; +use Zotlabs\Lib\Config; +use Zotlabs\Lib\PConfig; require_once('include/items.php'); @@ -437,17 +439,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; @@ -816,7 +823,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' => '#' ]; @@ -998,10 +1005,10 @@ function builtin_activity_puller($item, &$conv_responses) { $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>' : ''); + $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="bi bi-check-lg" ></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="bi bi-trash" ></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>' + ? '<div class="dropdown-item">' . $moderate . '<a href="' . chanlink_hash($item['author_xchan']) . '" class="text-reset">' . '<img class="menu-img-1" src="' . $item['author']['xchan_photo_s'] . '" alt="' . urlencode($name) . '" loading="lazy" /> ' . $name . '</a></div>' : '<a class="dropdown-item" href="#" class="disabled">' . $name . '</a>' ); @@ -1182,6 +1189,7 @@ function hz_status_editor($x, $popup = false) { '$modalerroralbum' => t('Error getting album'), '$nocomment_enabled' => t('Comments enabled'), '$nocomment_disabled' => t('Comments disabled'), + '$confirmdelete' => t('Confirm delete'), '$auto_save_draft' => $feature_auto_save_draft, '$reset' => $reset, '$popup' => $popup @@ -1247,6 +1255,7 @@ function hz_status_editor($x, $popup = false) { '$writefiles' => $writefiles, '$bold' => t('Bold'), '$italic' => t('Italic'), + '$highlighter' => t('Highlight selected text'), '$underline' => t('Underline'), '$quote' => t('Quote'), '$code' => t('Code'), @@ -1262,7 +1271,7 @@ function hz_status_editor($x, $popup = false) { '$poll_option_label' => t('Option'), '$poll_add_option_label' => t('Add option'), '$poll_expire_unit_label' => [t('Minutes'), t('Hours'), t('Days')], - '$multiple_answers' => ['poll_multiple_answers', t("Allow multiple answers"), '', '', [t('No'), t('Yes')]], + '$multiple_answers' => ['poll_multiple_answers', t("Allow multiple answers"), '', '', [t('No'), t('Yes')],null,null], '$consensus' => ((array_key_exists('item',$x)) ? $x['item']['item_consensus'] : 0), '$nocommenttitle' => t('Disable comments'), '$nocommenttitlesub' => t('Toggle comments'), @@ -1336,7 +1345,7 @@ function get_item_children($arr, $parent) { $children = array(); foreach($arr as $item) { if($item['id'] != $item['parent']) { - if(get_config('system','thread_allow')) { + if(Config::Get('system','thread_allow')) { // Fallback to parent_mid if thr_parent is not set $thr_parent = $item['thr_parent']; if($thr_parent == '') @@ -1465,14 +1474,18 @@ function render_location_default($item) { $location = $item['location']; $coord = $item['coord']; - if($coord) { + if ($coord) { if($location) - $location .= ' <span class="smalltext">(' . $coord . ')</span>'; + $location .= ' (' . $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>'; } @@ -1523,6 +1536,12 @@ function get_responses($conv_responses,$response_verbs,$ob,$item) { $ret = array(); foreach($response_verbs as $v) { + 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; + } + $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'] : ''); @@ -1531,14 +1550,6 @@ function get_responses($conv_responses,$response_verbs,$ob,$item) { $ret[$v]['modal'] = (($ret[$v]['count'] > MAX_LIKERS) ? true : false); } - $count = 0; - foreach ($ret as $key) { - if ($key['count'] == true) - $count++; - } - - $ret['count'] = $count; - //logger('ret: ' . print_r($ret,true)); return $ret; @@ -1547,25 +1558,25 @@ function get_responses($conv_responses,$response_verbs,$ob,$item) { function get_response_button_text($v,$count) { 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', 'onclick' => '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', 'onclick' => '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', 'onclick' => 'dolike']; break; case 'attendyes': - return ['label' => tt('Attending','Attending',$count,'noun'), 'icon' => 'calendar-check-o', 'class' => 'attendyes']; + return ['label' => tt('Attending','Attending',$count,'noun'), 'icon' => 'calendar-check', 'class' => 'attendyes', 'onclick' => 'dolike']; break; case 'attendno': - return ['label' => tt('Not Attending','Not Attending',$count,'noun'), 'icon' => 'calendar-times-o', 'class' => 'attendno']; + return ['label' => tt('Not Attending','Not Attending',$count,'noun'), 'icon' => 'calendar-x', 'class' => 'attendno', 'onclick' => 'dolike']; break; case 'attendmaybe': - return ['label' => tt('Undecided','Undecided',$count,'noun'), 'icon' => 'calendar-o', 'class' => 'attendmaybe']; + return ['label' => tt('Undecided','Undecided',$count,'noun'), 'icon' => 'calendar', 'class' => 'attendmaybe', 'onclick' => 'dolike']; break; default: - return ''; + return []; break; } } diff --git a/include/crypto.php b/include/crypto.php index 40e68a4e7..4d50310fb 100644 --- a/include/crypto.php +++ b/include/crypto.php @@ -1,5 +1,7 @@ <?php /** @file */ +use Zotlabs\Lib\Config; + require_once('library/ASNValue.class.php'); require_once('library/asn1.php'); @@ -282,7 +284,7 @@ function new_keypair($bits) { 'encrypt_key' => false ); - $conf = get_config('system','openssl_conf_file'); + $conf = Config::Get('system','openssl_conf_file'); if($conf) $openssl_options['config'] = $conf; @@ -460,12 +462,12 @@ function convert_salmon_key($key) { function z_obscure($s) { - return json_encode(crypto_encapsulate($s,get_config('system','pubkey'))); + return json_encode(crypto_encapsulate($s,Config::Get('system','pubkey'))); } function z_unobscure($s) { if(strpos($s,"{\"") !== 0) return $s; - return crypto_unencapsulate(json_decode($s,true),get_config('system','prvkey')); + return crypto_unencapsulate(json_decode($s,true),Config::Get('system','prvkey')); } 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..a12629e19 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, '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 701f3c330..b83a733b8 100644 --- a/include/event.php +++ b/include/event.php @@ -8,11 +8,12 @@ use Sabre\VObject; use Zotlabs\Lib\Activity; +use Zotlabs\Lib\Config; 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'); @@ -37,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> ' . zidify_links(smilies(bbcode($ev['summary']))) . '</h3></div>' . "\r\n"; + $o .= '<div class="event-title"><h3><i class="bi bi-calendar-date"></i> ' . zidify_links(smilies(bbcode($ev['summary']))) . '</h3></div>' . "\r\n"; $o .= '<div class="event-start"><span class="event-label">' . t('Starts:') . '</span> <span class="dtstart" title="' . datetime_convert('UTC', 'UTC', $ev['dtstart'], ((isset($ev['adjust']) && $ev['adjust']) ? ATOM_TIME : 'Y-m-d\TH:i:s' )) @@ -101,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 (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 @@ -145,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) )); @@ -234,10 +236,10 @@ 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:-//" . get_config('system','sitename') . "//" . Zotlabs\Lib\System::get_platform_name() . "//" . strtoupper(App::$language). "\r\n"; + $o .= "\r\nPRODID:-//" . Config::Get('system','sitename') . "//" . Zotlabs\Lib\System::get_platform_name() . "//" . strtoupper(App::$language). "\r\n"; if(array_key_exists('dtstart', $ev)) $o .= format_event_ical($ev); else { @@ -271,9 +273,9 @@ function format_event_ical($ev) { if($ev['adjust']) { if($ev['dtstart']) - $o .= "\r\nDTSTART$tzid:" . datetime_convert($tz,'UTC', $ev['dtstart'],'Ymd\\THis\\Z'); + $o .= "\r\nDTSTART$tzid:" . datetime_convert('UTC', $tz, $ev['dtstart'],'Ymd\\THis'); if($ev['dtend'] && ! $ev['nofinish']) - $o .= "\r\nDTEND$tzid:" . datetime_convert($tz,'UTC', $ev['dtend'],'Ymd\\THis\\Z'); + $o .= "\r\nDTEND$tzid:" . datetime_convert('UTC', $tz, $ev['dtend'],'Ymd\\THis'); } else { if($ev['dtstart']) @@ -646,7 +648,7 @@ function event_store_event($arr) { else { try { $hash = Uuid::uuid4()->toString(); - } catch (UnsatisfiedDependencyException $e) { + } catch (UnableToBuildUuidException $e) { $hash = random_string(48); } } @@ -796,7 +798,7 @@ function parse_event_object($event_object_json) { $tz = $object['timezone'] ?? 'UTC'; $ev['summary'] = $object['summary'] ?? $object['name'] ?? ''; - $ev['description'] = html2bbcode($content['content']) ?? ''; + $ev['description'] = html2bbcode($object['content']) ?? ''; $ev['dtstart'] = $object['startTime'] ? datetime_convert('UTC', 'UTC', $object['startTime']) : ''; $ev['dtend'] = $object['endTime'] ? datetime_convert('UTC', 'UTC', $object['endTime']) : $ev['dtstart']; $ev['location'] = $object['location']['name'] ?? ''; @@ -1069,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; } } @@ -1204,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; } } @@ -1213,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'); @@ -1233,7 +1234,7 @@ function event_store_item($arr, $event) { } - $item_arr = array(); + $item_arr = []; $prefix = ''; // $birthday = false; @@ -1254,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']) ); @@ -1291,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 @@ -1343,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; @@ -1360,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; @@ -1373,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; @@ -1451,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 @@ -1468,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 e57859aa8..65ead6604 100644 --- a/include/features.php +++ b/include/features.php @@ -4,17 +4,15 @@ * Features management */ - - - +use Zotlabs\Lib\Config; function feature_enabled($uid,$feature) { - $x = get_config('feature_lock',$feature); + $x = Config::Get('feature_lock',$feature); if($x === false) { $x = get_pconfig($uid,'feature',$feature); if($x === false) { - $x = get_config('feature',$feature); + $x = Config::Get('feature',$feature); if($x === false) $x = get_feature_default($feature); } @@ -38,7 +36,7 @@ function get_feature_default($feature) { function feature_level($feature,$def) { - $x = get_config('feature_level',$feature); + $x = Config::Get('feature_level',$feature); if($x !== false) return intval($x); return $def; @@ -86,7 +84,7 @@ function get_features($filtered = true, $level = (-1)) { t('Start calendar week on Monday'), t('Default is Sunday'), false, - get_config('feature_lock','cal_first_day') + Config::Get('feature_lock','cal_first_day') ], [ @@ -94,7 +92,7 @@ function get_features($filtered = true, $level = (-1)) { t('Event Timezone Selection'), t('Allow event creation in timezones other than your own.'), false, - get_config('feature_lock','event_tz_select'), + Config::Get('feature_lock','event_tz_select'), ] ], @@ -108,7 +106,7 @@ function get_features($filtered = true, $level = (-1)) { t('Search by Date'), t('Ability to select posts by date ranges'), false, - get_config('feature_lock','archives') + Config::Get('feature_lock','archives') ], [ @@ -116,7 +114,7 @@ function get_features($filtered = true, $level = (-1)) { t('Tag Cloud'), t('Provide a personal tag cloud on your channel page'), false, - get_config('feature_lock','tagadelic'), + Config::Get('feature_lock','tagadelic'), ], [ @@ -124,7 +122,7 @@ function get_features($filtered = true, $level = (-1)) { t('Use blog/list mode'), t('Comments will be displayed separately'), false, - get_config('feature_lock','channel_list_mode'), + Config::Get('feature_lock','channel_list_mode'), ] ], @@ -137,7 +135,7 @@ function get_features($filtered = true, $level = (-1)) { t('Connection Filtering'), t('Filter incoming posts from connections based on keywords/content'), false, - get_config('feature_lock','connfilter') + Config::Get('feature_lock','connfilter') ] ], @@ -150,7 +148,7 @@ function get_features($filtered = true, $level = (-1)) { t('Community Tagging'), t('Ability to tag existing posts'), false, - get_config('feature_lock','commtag'), + Config::Get('feature_lock','commtag'), ], */ [ @@ -158,7 +156,7 @@ function get_features($filtered = true, $level = (-1)) { t('Emoji Reactions'), t('Add emoji reaction ability to posts'), true, - get_config('feature_lock','emojis'), + Config::Get('feature_lock','emojis'), ], [ @@ -166,15 +164,23 @@ function get_features($filtered = true, $level = (-1)) { t('Dislike Posts'), t('Ability to dislike posts/comments'), false, - get_config('feature_lock','dislike'), + Config::Get('feature_lock','dislike'), ], [ '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, - get_config('feature_lock','star_posts'), + Config::Get('feature_lock','filing'), ], [ @@ -182,7 +188,7 @@ function get_features($filtered = true, $level = (-1)) { t('Reply on comment'), t('Ability to reply on selected comment'), false, - get_config('feature_lock','reply_to'), + Config::Get('feature_lock','reply_to'), ] ], @@ -196,7 +202,7 @@ function get_features($filtered = true, $level = (-1)) { t('Advanced Directory Search'), t('Allows creation of complex directory search queries'), false, - get_config('feature_lock','advanced_dirsearch'), + Config::Get('feature_lock','advanced_dirsearch'), ] ], @@ -210,7 +216,7 @@ function get_features($filtered = true, $level = (-1)) { t('Post Categories'), t('Add categories to your posts'), false, - get_config('feature_lock','categories'), + Config::Get('feature_lock','categories'), ], [ @@ -218,7 +224,7 @@ function get_features($filtered = true, $level = (-1)) { t('Large Photos'), t('Include large (1024px) photo thumbnails in posts. If not enabled, use small (640px) photo thumbnails'), false, - get_config('feature_lock','large_photos'), + Config::Get('feature_lock','large_photos'), ], [ @@ -226,7 +232,7 @@ function get_features($filtered = true, $level = (-1)) { t('Even More Encryption'), t('Allow optional encryption of content end-to-end with a shared secret key'), false, - get_config('feature_lock','content_encrypt'), + Config::Get('feature_lock','content_encrypt'), ], [ @@ -234,7 +240,7 @@ function get_features($filtered = true, $level = (-1)) { t('Disable Comments'), t('Provide the option to disable comments for a post'), false, - get_config('feature_lock','disable_comments'), + Config::Get('feature_lock','disable_comments'), ], [ @@ -242,7 +248,7 @@ function get_features($filtered = true, $level = (-1)) { t('Delayed Posting'), t('Allow posts to be published at a later date'), false, - get_config('feature_lock','delayed_posting'), + Config::Get('feature_lock','delayed_posting'), ], [ @@ -250,7 +256,7 @@ function get_features($filtered = true, $level = (-1)) { t('Content Expiration'), t('Remove posts/comments and/or private messages at a future time'), false, - get_config('feature_lock','content_expire'), + Config::Get('feature_lock','content_expire'), ], [ @@ -258,7 +264,7 @@ function get_features($filtered = true, $level = (-1)) { t('Suppress Duplicate Posts/Comments'), t('Prevent posts with identical content to be published with less than two minutes in between submissions.'), true, - get_config('feature_lock','suppress_duplicates'), + Config::Get('feature_lock','suppress_duplicates'), ], [ @@ -266,7 +272,7 @@ function get_features($filtered = true, $level = (-1)) { t('Auto-save drafts of posts and comments'), t('Automatically saves post and comment drafts in local browser storage to help prevent accidental loss of compositions'), true, - get_config('feature_lock','auto_save_draft'), + Config::Get('feature_lock','auto_save_draft'), ] ], @@ -280,7 +286,7 @@ function get_features($filtered = true, $level = (-1)) { t('Navigation Channel Select'), t('Change channels directly from within the navigation dropdown menu'), false, - get_config('feature_lock','nav_channel_select'), + Config::Get('feature_lock','nav_channel_select'), ] ], @@ -294,7 +300,7 @@ function get_features($filtered = true, $level = (-1)) { t('Events Filter'), t('Ability to display only events'), false, - get_config('feature_lock','events_tab') + Config::Get('feature_lock','events_tab') ], [ @@ -302,7 +308,7 @@ function get_features($filtered = true, $level = (-1)) { t('Polls Filter'), t('Ability to display only polls'), false, - get_config('feature_lock','polls_tab') + Config::Get('feature_lock','polls_tab') ], [ @@ -310,7 +316,7 @@ function get_features($filtered = true, $level = (-1)) { t('Saved Searches'), t('Save search terms for re-use'), false, - get_config('feature_lock','savedsearch') + Config::Get('feature_lock','savedsearch') ], [ @@ -318,7 +324,7 @@ function get_features($filtered = true, $level = (-1)) { t('Saved Folders'), t('Ability to file posts under folders'), false, - get_config('feature_lock','filing'), + Config::Get('feature_lock','filing'), ], [ @@ -326,7 +332,7 @@ function get_features($filtered = true, $level = (-1)) { t('Alternate Stream Order'), t('Ability to order the stream by last post date, last comment date or unthreaded activities'), false, - get_config('feature_lock','order_tab') + Config::Get('feature_lock','order_tab') ], [ @@ -334,7 +340,7 @@ function get_features($filtered = true, $level = (-1)) { t('Contact Filter'), t('Ability to display only posts of a selected contact'), false, - get_config('feature_lock','name_tab') + Config::Get('feature_lock','name_tab') ], [ @@ -342,7 +348,7 @@ function get_features($filtered = true, $level = (-1)) { t('Forum Filter'), t('Ability to display only posts of a specific forum'), false, - get_config('feature_lock','forums_tab') + Config::Get('feature_lock','forums_tab') ], [ @@ -350,7 +356,7 @@ function get_features($filtered = true, $level = (-1)) { t('Personal Posts Filter'), t('Ability to display only posts that you\'ve interacted on'), false, - get_config('feature_lock','personal_tab') + Config::Get('feature_lock','personal_tab') ], [ @@ -358,7 +364,7 @@ function get_features($filtered = true, $level = (-1)) { t('Use blog/list mode'), t('Comments will be displayed separately'), false, - get_config('feature_lock','network_list_mode'), + Config::Get('feature_lock','network_list_mode'), ] ], @@ -372,7 +378,7 @@ function get_features($filtered = true, $level = (-1)) { t('Photo Location'), t('If location data is available on uploaded photos, link this to a map.'), false, - get_config('feature_lock','photo_location'), + Config::Get('feature_lock','photo_location'), ], [ @@ -380,7 +386,7 @@ function get_features($filtered = true, $level = (-1)) { t('Flag Adult Photos'), t('Provide photo edit option to hide inappropriate photos from default album view'), false, - get_config('feature_lock','adult_photo_flagging'), + Config::Get('feature_lock','adult_photo_flagging'), ] ], @@ -394,7 +400,7 @@ function get_features($filtered = true, $level = (-1)) { t('Advanced Profiles'), t('Additional profile sections and selections'), false, - get_config('feature_lock','advanced_profiles') + Config::Get('feature_lock','advanced_profiles') ], [ @@ -402,7 +408,7 @@ function get_features($filtered = true, $level = (-1)) { t('Profile Import/Export'), t('Save and load profile details across sites/channels'), false, - get_config('feature_lock','profile_export') + Config::Get('feature_lock','profile_export') ], [ @@ -410,7 +416,7 @@ function get_features($filtered = true, $level = (-1)) { t('Multiple Profiles'), t('Ability to create multiple profiles'), false, - get_config('feature_lock','multi_profiles') + Config::Get('feature_lock','multi_profiles') ] ] diff --git a/include/feedutils.php b/include/feedutils.php index f05c15414..1487ea2d6 100644 --- a/include/feedutils.php +++ b/include/feedutils.php @@ -4,6 +4,8 @@ * @brief Some functions to work with XML feeds. */ +use Zotlabs\Lib\Config; + /** * @brief Return an Atom feed for channel. * @@ -226,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,'&'))) @@ -242,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"; @@ -309,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') @@ -355,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') @@ -996,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']); } } } @@ -1037,7 +1051,7 @@ function consume_feed($xml, $importer, &$contact, $pass = 0) { - $sys_expire = intval(get_config('system', 'default_expire_days')); + $sys_expire = intval(Config::Get('system', 'default_expire_days')); $chn_expire = intval($importer['channel_expire_days']); $expire_days = $sys_expire; @@ -1171,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; } @@ -1325,7 +1343,7 @@ function consume_feed($xml, $importer, &$contact, $pass = 0) { // but save the thread_parent in case we need to refer to it later. if($importer['channel_system']) { - if( ! \Zotlabs\Lib\MessageFilter::evaluate($datarray,get_config('system','pubstream_incl'),get_config('system','pubstream_excl'))) { + if( ! \Zotlabs\Lib\MessageFilter::evaluate($datarray,Config::Get('system','pubstream_incl'),Config::Get('system','pubstream_excl'))) { continue; } } @@ -1442,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; } @@ -1481,7 +1504,7 @@ function consume_feed($xml, $importer, &$contact, $pass = 0) { } if($importer['channel_system']) { - if( ! \Zotlabs\Lib\MessageFilter::evaluate($datarray,get_config('system','pubstream_incl'),get_config('system','pubstream_excl'))) { + if( ! \Zotlabs\Lib\MessageFilter::evaluate($datarray,Config::Get('system','pubstream_incl'),Config::Get('system','pubstream_excl'))) { continue; } } diff --git a/include/help.php b/include/help.php index 9e4be57f9..2358f1289 100644 --- a/include/help.php +++ b/include/help.php @@ -1,207 +1,10 @@ <?php -use \Michelf\MarkdownExtra; +use Michelf\MarkdownExtra; use CommerceGuys\Intl\Language\LanguageRepository; require_once('include/items.php'); -/** - * @brief - * - * @param string $path - * @param string $suffix (optional) default null - * @return string - */ -function get_help_fullpath($path, $suffix = null) { - - $docroot = (\App::$override_helproot) ? \App::$override_helproot : 'doc/'; - $docroot = (substr($docroot,-1)!='/') ? $docroot .= '/' : $docroot; - - // Determine the language and modify the path accordingly - $x = determine_help_language(); - $lang = $x['language']; - - // The English translation is at the root of /doc/. Other languages are in - // subfolders named by the language code such as "de", "es", etc. - if($lang !== 'en') { - $langpath = $lang . '/' . $path; - } else { - $langpath = $path; - } - - $newpath = (isset(\App::$override_helpfiles[$langpath])) ? \App::$override_helpfiles[$langpath] : $langpath; - $newpath = ($newpath == $langpath) ? $docroot . $newpath : $newpath; - - if ($suffix) { - if (file_exists($newpath . $suffix)) { - return $newpath; - } - } elseif (file_exists($newpath . '.md') || - file_exists($newpath . '.bb') || - file_exists($newpath . '.html')) { - return $newpath; - } - - $newpath = (isset(\App::$override_helpfiles[$path])) ? \App::$override_helpfiles[$path] : null; - - $newpath = (!$newpath) ? $docroot.$path : $newpath; - return $newpath; -} - - -/** - * @brief - * - * @param string $tocpath (optional) default false - * @return string - */ -function get_help_content($tocpath = false) { - - $doctype = 'markdown'; - - $text = ''; - - $path = (($tocpath !== false) ? $tocpath : ''); - $docroot = (\App::$override_helproot) ? \App::$override_helproot : 'doc/'; - $docroot = (substr($docroot,-1)!='/') ? $docroot .= '/' : $docroot; - - if($tocpath === false && argc() > 1) { - $path = ''; - for($x = 1; $x < argc(); $x ++) { - if(strlen($path)) - $path .= '/'; - $path .= argv($x); - } - } - - - if($path) { - $fullpath = get_help_fullpath($path); - $title = basename($path); - if(! $tocpath) - \App::$page['title'] = t('Help:') . ' ' . ucwords(str_replace('-',' ',notags($title))); - - // Check that there is a "toc" or "sitetoc" located at the specified path. - // If there is not, then there was not a translation of the table of contents - // available and so default back to the English TOC at /doc/toc.{html,bb,md} - // TODO: This is incompatible with the hierarchical TOC construction - // defined in /Zotlabs/Widget/Helpindex.php. - if($tocpath !== false && - load_doc_file($fullpath . '.md') === '' && - load_doc_file($fullpath . '.bb') === '' && - load_doc_file($fullpath . '.html') === '' - ) { - $path = $title; - } - $fullpath = get_help_fullpath($path); - $text = load_doc_file($fullpath . '.md'); - - if(! $text) { - $text = load_doc_file($fullpath . '.bb'); - if($text) - $doctype = 'bbcode'; - } - if(! $text) { - $text = load_doc_file($fullpath . '.html'); - if($text) - $doctype = 'html'; - } - } - - if(($tocpath) && (! $text)) - return ''; - - if($tocpath === false) { - if(! $text) { - $path = 'Site'; - $fullpath = get_help_fullpath($path,'.md'); - $text = load_doc_file($fullpath . '.md'); - \App::$page['title'] = t('Help'); - } - if(! $text) { - $doctype = 'bbcode'; - $path = 'main'; - $fullpath = get_help_fullpath($path,'.md'); - $text = load_doc_file($fullpath . '.bb'); - goaway('/help/about/about'); - \App::$page['title'] = t('Help'); - } - - if(! $text) { - header($_SERVER["SERVER_PROTOCOL"] . ' 404 ' . t('Not Found')); - $tpl = get_markup_template("404.tpl"); - return replace_macros($tpl, array( - '$message' => t('Page not found.') - )); - } - } - - if($doctype === 'html') - $content = parseIdentityAwareHTML($text); - if($doctype === 'markdown') { - # escape #include tags - $text = preg_replace('/#include/ism', '%%include', $text); - $content = MarkdownExtra::defaultTransform($text); - $content = preg_replace('/%%include/ism', '#include', $content); - } - if($doctype === 'bbcode') { - require_once('include/bbcode.php'); - $content = zidify_links(bbcode($text)); - // bbcode retargets external content to new windows. This content is internal. - $content = str_replace(' target="_blank"', '', $content); - } - - $content = preg_replace_callback("/#include (.*?)\;/ism", 'preg_callback_help_include', $content); - - return translate_projectname($content); -} - -function preg_callback_help_include($matches) { - - if($matches[1]) { - $include = str_replace($matches[0],load_doc_file($matches[1]),$matches[0]); - if(preg_match('/\.bb$/', $matches[1]) || preg_match('/\.txt$/', $matches[1])) { - require_once('include/bbcode.php'); - $include = zidify_links(bbcode($include)); - $include = str_replace(' target="_blank"','',$include); - } - elseif(preg_match('/\.md$/', $matches[1])) { - $include = MarkdownExtra::defaultTransform($include); - } - return $include; - } - -} - -/** - * @brief Determines help language. - * - * If the language was specified in the URL, override the language preference - * of the browser. Default to English if both of these are absent. - * - * @return array Associative array with: - * * \e string \b language - 2-letter ISO 639-1 code ("en") - * * \e boolean \b from_url - true if language from URL overrides browser default - */ -function determine_help_language() { - - $language_repository = new LanguageRepository; - $languages = $language_repository->getList(); - - if(array_key_exists(argv(1), $languages)) { - $lang = argv(1); - $from_url = true; - } else { - $lang = \App::$language; - if(! isset($lang)) - $lang = 'en'; - - $from_url = false; - } - - return array('language' => $lang, 'from_url' => $from_url); -} - function load_doc_file($s) { $c = find_doc_file($s); @@ -353,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 03e09cd62..b799a0c28 100644 --- a/include/html2bbcode.php +++ b/include/html2bbcode.php @@ -26,6 +26,7 @@ function node2bbcodesub(&$doc, $oldnode, $attributes, $startbb, $endbb) $xpath = new DomXPath($doc); $list = $xpath->query("//".$oldnode); + foreach ($list as $oldNode) { $attr = array(); @@ -113,13 +114,6 @@ function html2bbcode($message) $message = str_replace("\r", "", $message); - $message = str_replace(array( - "<li><p>", - "</p></li>"), - array( - "<li>", - "</li>"), - $message); // remove namespaces $message = preg_replace('=<(\w+):(.+?)>=', '<removeme>', $message); @@ -129,7 +123,16 @@ function html2bbcode($message) //$message = mb_convert_encoding($message, 'HTML-ENTITIES', "UTF-8"); $message = mb_encode_numericentity($message, [0x80, 0x10FFFF, 0, ~0], 'UTF-8'); + // TODO: It would be better to do the list parsing with node2bbcode() but it has serious issues when + // parsing nested lists. Especially if the li tag has no closing tag (which is valid). + $message = preg_replace('/\<ul(.*?)\>/', '[list]', $message); + $message = preg_replace('/\<ol(.*?)\>/', '[list=1]', $message); + + $message = str_replace(['<li><p>', '</p></li>'], ['<li>', '</li>'], $message); + $message = preg_replace('/\<li(.*?)\>/', '[*]', $message); + $message = str_replace(['</ul>', '</ol>'], '[/list]', $message); + $message = str_replace('</li>', '', $message); if(!$message) return; @@ -185,7 +188,11 @@ function html2bbcode($message) node2bbcode($doc, 'b', array(), '[b]', '[/b]'); node2bbcode($doc, 'i', array(), '[i]', '[/i]'); node2bbcode($doc, 'u', array(), '[u]', '[/u]'); + // The s tag is deprecated in HTML5 node2bbcode($doc, 's', array(), '[s]', '[/s]'); + node2bbcode($doc, 'del', [], '[s]', '[/s]'); + + node2bbcode($doc, 'mark', array(), '[mark]', '[/mark]'); node2bbcode($doc, 'span', array(), "", ""); @@ -205,21 +212,13 @@ function html2bbcode($message) node2bbcode($doc, 'audio', array('src'=>'/(.+)/'), '[audio]$1', '[/audio]'); // node2bbcode($doc, 'iframe', array('src'=>'/(.+)/'), '[iframe]$1', '[/iframe]'); - - node2bbcode($doc, 'ul', array(), "[list]", "[/list]"); - node2bbcode($doc, 'ol', array(), "[list=1]", "[/list]"); - node2bbcode($doc, 'li', array(), "[*]", ""); - 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 5cb7ee35d..69fb5193a 100644 --- a/include/html2plain.php +++ b/include/html2plain.php @@ -129,6 +129,8 @@ function html2plain($html, $wraplength = 75, $compact = false) if(!$message) return; + $message = preg_replace('/\<li(.*?)\>/', "\n*", $message); + $message = str_replace('</li>', '', $message); $doc = new DOMDocument(); $doc->preserveWhiteSpace = false; @@ -178,7 +180,7 @@ function html2plain($html, $wraplength = 75, $compact = false) //node2bbcode($doc, 'ul', array(), "\n[list]", "[/list]\n"); //node2bbcode($doc, 'ol', array(), "\n[list=1]", "[/list]\n"); - node2bbcode($doc, 'li', array(), "\n* ", "\n"); + //node2bbcode($doc, 'li', array(), "\n* ", "\n"); node2bbcode($doc, 'hr', array(), "\n".str_repeat("-", 70)."\n", ""); @@ -194,7 +196,7 @@ function html2plain($html, $wraplength = 75, $compact = false) // Problem: there is no reliable way to detect if it is a link to a tag or profile //node2bbcode($doc, 'a', array('href'=>'/(.+)/'), ' $1 ', '', true); - node2bbcode($doc, 'a', array('href'=>'/(.+)/', 'rel'=>'oembed'), ' $1 ', '', true); + node2bbcode($doc, 'a', array('href'=>'/(.+)/', 'rel'=>'oembed'), ' $1 ', ''); //node2bbcode($doc, 'img', array('alt'=>'/(.+)/'), '$1', ''); //node2bbcode($doc, 'img', array('title'=>'/(.+)/'), '$1', ''); //node2bbcode($doc, 'img', array(), '', ''); @@ -203,7 +205,7 @@ function html2plain($html, $wraplength = 75, $compact = false) else node2bbcode($doc, 'img', array('src'=>'/(.+)/'), '', ''); - node2bbcode($doc, 'iframe', array('src'=>'/(.+)/'), ' $1 ', '', true); + node2bbcode($doc, 'iframe', array('src'=>'/(.+)/'), ' $1 ', ''); $message = $doc->saveHTML(); diff --git a/include/hubloc.php b/include/hubloc.php index 4d2980a6b..982455f9c 100644 --- a/include/hubloc.php +++ b/include/hubloc.php @@ -5,6 +5,7 @@ */ use Zotlabs\Daemon\Master; +use Zotlabs\Lib\Config; /** * @brief Create an array for hubloc table and insert record. @@ -129,7 +130,7 @@ function remove_obsolete_hublocs() { $r = q("select hubloc_id from hubloc where hubloc_url = '%s' and hubloc_sitekey = '%s'", dbesc(z_root()), - dbesc(get_config('system', 'pubkey')) + dbesc(Config::Get('system', 'pubkey')) ); if((! $r) || (! count($r))) return; @@ -139,11 +140,11 @@ function remove_obsolete_hublocs() { // Do we have any invalid ones? $r = q("select hubloc_id, hubloc_hash from hubloc where hubloc_sitekey = '%s' and hubloc_url != '%s'", - dbesc(get_config('system', 'pubkey')), + dbesc(Config::Get('system', 'pubkey')), dbesc(z_root()) ); $p = q("select hubloc_id, hubloc_hash from hubloc where hubloc_sitekey != '%s' and hubloc_url = '%s'", - dbesc(get_config('system', 'pubkey')), + dbesc(Config::Get('system', 'pubkey')), dbesc(z_root()) ); if(is_array($r) && is_array($p)) @@ -156,7 +157,7 @@ function remove_obsolete_hublocs() { logger('remove_obsolete_hublocs: removing ' . count($r) . ' hublocs.'); - $interval = get_config('queueworker', 'queue_interval', 500000); + $interval = Config::Get('queueworker', 'queue_interval', 500000); foreach($r as $rr) { q("update hubloc set hubloc_deleted = 1 where hubloc_id = %d", diff --git a/include/import.php b/include/import.php index 7dac518f5..77d35a3e7 100644 --- a/include/import.php +++ b/include/import.php @@ -1,6 +1,7 @@ <?php use Zotlabs\Lib\Apps; +use Zotlabs\Lib\Config; use Zotlabs\Lib\IConfig; use Zotlabs\Lib\Libzot; @@ -824,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 @@ -1479,7 +1480,7 @@ function sync_files($channel, $files) { fclose($fp); // Override remote hub thumbnails storage settings - if(! boolval(get_config('system','photo_storage_type', 1))) { + if(! boolval(Config::Get('system','photo_storage_type', 1))) { $p['os_storage'] = 0; $p['content'] = file_get_contents($stored_image); @unlink($stored_image); @@ -1885,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']))) { @@ -1896,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 a5a23650e..1f3671c83 100644 --- a/include/items.php +++ b/include/items.php @@ -4,6 +4,7 @@ * @brief Items related functions. */ +use Zotlabs\Lib\Config; use Zotlabs\Lib\Crypto; use Zotlabs\Lib\Enotify; use Zotlabs\Lib\MarkdownSoap; @@ -35,10 +36,10 @@ require_once('include/permissions.php'); * @param boolean $include_groups * @return array containing the recipients */ -function collect_recipients($item, &$private_envelope,$include_groups = true) { +function collect_recipients($item, &$private_envelope, $include_groups = true) { $private_envelope = ((intval($item['item_private'])) ? true : false); - $recipients = array(); + $recipients = []; if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid']) { @@ -53,8 +54,15 @@ function collect_recipients($item, &$private_envelope,$include_groups = true) { $allow_groups = []; } - $raw_recipients = array_unique(array_merge($allow_people, $allow_groups)); - $recipients = deliverable_abook_xchans($item['uid'], $raw_recipients); + $recipients = array_unique(array_merge($allow_people, $allow_groups)); + + if ($recipients) { + // deliverable_abook_xchans() will return all deliverable xchans + // if passed an empty array as 2nd argument (no filtering). + // Hence only call it if we do actually have any recipients. + $recipients = deliverable_abook_xchans($item['uid'], $recipients); + } + // if you specifically deny somebody but haven't allowed anybody, we'll allow everybody in your // address book minus the denied connections. The post is still private and can't be seen publicly @@ -250,6 +258,25 @@ function item_normal() { 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 @@ -353,7 +380,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')) { @@ -423,8 +450,9 @@ 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; @@ -438,8 +466,13 @@ 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']); @@ -485,6 +518,14 @@ function post_activity_item($arr, $allow_code = false, $deliver = true) { $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 @@ -501,29 +542,30 @@ function post_activity_item($arr, $allow_code = false, $deliver = true) { 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; } @@ -1078,7 +1120,7 @@ function encode_item($item,$mirror = false,$zap_compat = false) { $x['type'] = 'activity'; $x['encoding'] = 'zot'; - $key = get_config('system','prvkey'); + $key = Config::Get('system','prvkey'); // If we're trying to backup an item so that it's recoverable or for export/imprt, // add all the attributes we need to recover it @@ -1586,7 +1628,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, @@ -1768,16 +1810,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']; } @@ -1819,7 +1851,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; @@ -1874,7 +1906,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); } } @@ -2044,6 +2076,13 @@ function item_store($arr, $allow_exec = false, $deliver = true) { 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. // We need to store the item details just in case the delete came in before the original post, // so that we have an item in the DB that's marked deleted and won't store a fresh post @@ -2054,9 +2093,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; } @@ -2069,7 +2105,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, @@ -2203,7 +2239,7 @@ function item_store_update($arr, $allow_exec = false, $deliver = true) { $arr['revision'] = ((x($arr,'revision') && $arr['revision'] > 0) ? 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']; @@ -2382,17 +2418,19 @@ function item_store_update($arr, $allow_exec = false, $deliver = true) { Master::Summon([ 'Cache_embeds', $orig_post_id ]); } + $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; } @@ -2696,7 +2734,7 @@ function tag_deliver($uid, $item_id) { logger('Post mentions: ' . print_r($terms,true), LOGGER_DATA); } - $max_forums = get_config('system','max_tagged_forums',2); + $max_forums = Config::Get('system','max_tagged_forums',2); $matched_forums = 0; @@ -2943,35 +2981,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) - return true; - if(! perm_is_allowed($uid,$item['author_xchan'],'tag_deliver')) - return false; + if ($r) { + return true; + } - $u = q("select * from channel left join xchan on channel_hash = xchan_hash where channel_id = %d limit 1", - intval($uid) - ); + $u = channelx_by_n($uid); - if(! $u) + if (!$u) { return false; + } - $max_forums = get_config('system','max_tagged_forums',2); + $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 = []; @@ -2988,7 +3023,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 @@ -3073,7 +3108,7 @@ function i_am_mentioned($channel, $item, $check_groups = false) { } } } - $unless = intval(get_pconfig($channel['channel_id'], 'system', 'unless_mention_count', get_config('system', 'unless_mention_count', 20))); + $unless = intval(get_pconfig($channel['channel_id'], 'system', 'unless_mention_count', Config::Get('system', 'unless_mention_count', 20))); if ($unless && $terms && count($terms) > $unless) { $tagged = false; } @@ -3096,6 +3131,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) { @@ -3141,17 +3181,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)) { @@ -3173,8 +3309,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']); @@ -3191,9 +3327,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']; @@ -3254,8 +3395,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']; @@ -3278,10 +3426,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; } @@ -3306,14 +3458,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'])); @@ -3357,12 +3509,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()), @@ -3372,81 +3524,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) - ); - } } /** @@ -3762,7 +3839,7 @@ function item_expire($uid,$days,$comment_days = 7) { $sql_extra = ((intval($expire_network_only)) ? " AND item_wall = 0 " : ""); - $expire_limit = get_config('system','expire_limit', 1000); + $expire_limit = Config::Get('system','expire_limit', 1000); $item_normal = item_normal(); @@ -3785,7 +3862,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); } } @@ -3798,25 +3875,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 ]); - } } @@ -3829,7 +3893,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 @@ -3837,33 +3901,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) { @@ -3876,9 +3955,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. @@ -3901,30 +3980,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. @@ -5000,7 +5144,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) { @@ -5015,10 +5159,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; @@ -5046,7 +5191,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), @@ -5056,7 +5201,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), @@ -5077,7 +5231,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(); @@ -5095,36 +5249,104 @@ 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); +} + diff --git a/include/js_strings.php b/include/js_strings.php index 090d28ce3..b41c34508 100644 --- a/include/js_strings.php +++ b/include/js_strings.php @@ -5,10 +5,10 @@ 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>'), + '$showmore' => t('show all'), + '$showfewer' => t('show less'), + '$divgrowmore' => t('expand'), + '$divgrowless' => t('collapse'), '$pwshort' => t("Password too short"), '$pwnomatch' => t("Passwords do not match"), '$everybody' => t('everybody'), @@ -49,29 +49,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 1b2e7332e..538f67d90 100644 --- a/include/language.php +++ b/include/language.php @@ -10,6 +10,7 @@ use CommerceGuys\Intl\Language\LanguageRepository; use LanguageDetection\Language; +use Zotlabs\Lib\Config; /** * @brief Get the browser's submitted preferred languages. @@ -17,7 +18,7 @@ use LanguageDetection\Language; * This functions parses the HTTP_ACCEPT_LANGUAGE header sent by the browser and * extracts the preferred languages and their priority. * - * Get the language setting directly from system variables, bypassing get_config() + * Get the language setting directly from system variables, bypassing Config::Get() * as database may not yet be configured. * * If possible, we use the value from the browser. @@ -197,10 +198,10 @@ function load_translation_table($lang, $install = false) { * * @param string $s string that should get translated * @param string $ctx (optional) context to appear in po file - * @return translated string if exists, otherwise return $s + * @return string translated string if exists, otherwise return $s * */ -function t($s, $ctx = '') { +function t($s, $ctx = ''): string { $cs = $ctx ? '__ctx:' . $ctx . '__ ' . $s : $s; if (x(App::$strings, $cs)) { @@ -313,8 +314,8 @@ function detect_language($s) { return EMPTY_STR; } - $min_length = get_config('system', 'language_detect_min_length', LANGUAGE_DETECT_MIN_LENGTH); - $min_confidence = get_config('system', 'language_detect_min_confidence', LANGUAGE_DETECT_MIN_CONFIDENCE); + $min_length = Config::Get('system', 'language_detect_min_length', LANGUAGE_DETECT_MIN_LENGTH); + $min_confidence = Config::Get('system', 'language_detect_min_confidence', LANGUAGE_DETECT_MIN_CONFIDENCE); // embedded apps have long base64 strings which will trip up the detector. $naked_body = preg_replace('/\[app\](.*?)\[\/app\]/', '', $s); 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/menu.php b/include/menu.php index 1f65f987d..2fcf88bca 100644 --- a/include/menu.php +++ b/include/menu.php @@ -166,7 +166,8 @@ function menu_create($arr) { if($r) return false; - $t = datetime_convert(); + $time_created = datetime_convert('UTC', 'UTC', empty($arr['menu_created']) ? 'now' : $arr['menu_created']); + $time_edited = empty($arr['menu_edited']) ? $time_created : datetime_convert('UTC', 'UTC', $arr['menu_edited']); $r = q("insert into menu ( menu_name, menu_desc, menu_flags, menu_channel_id, menu_created, menu_edited ) values( '%s', '%s', %d, %d, '%s', '%s' )", @@ -174,8 +175,8 @@ function menu_create($arr) { dbesc($menu_desc), intval($menu_flags), intval($menu_channel_id), - dbesc(datetime_convert('UTC','UTC',(($arr['menu_created']) ? $arr['menu_created'] : $t))), - dbesc(datetime_convert('UTC','UTC',(($arr['menu_edited']) ? $arr['menu_edited'] : $t))) + dbesc($time_created), + dbesc($time_edited) ); if(! $r) return false; diff --git a/include/nav.php b/include/nav.php index e4df7e4e5..1bee5a2db 100644 --- a/include/nav.php +++ b/include/nav.php @@ -1,7 +1,8 @@ <?php /** @file */ -use \Zotlabs\Lib\Apps; -use \Zotlabs\Lib\Chatroom; +use Zotlabs\Lib\Apps; +use Zotlabs\Lib\Chatroom; +use Zotlabs\Lib\Config; require_once('include/security.php'); require_once('include/menu.php'); @@ -57,10 +58,10 @@ function nav($template = 'default') { * */ - $banner = get_config('system', 'banner'); + $banner = Config::Get('system', 'banner'); if ($banner === false) - $banner = get_config('system', 'sitename'); + $banner = Config::Get('system', 'sitename'); call_hooks('get_banner', $banner); @@ -175,15 +176,15 @@ function nav($template = 'default') { ]; } - if ((get_config('system', 'register_policy') == REGISTER_OPEN || get_config('system', 'register_policy') == REGISTER_APPROVE) && empty($_SESSION['authenticated'])) { + if ((Config::Get('system', 'register_policy') == REGISTER_OPEN || Config::Get('system', 'register_policy') == REGISTER_APPROVE) && empty($_SESSION['authenticated'])) { $nav['register'] = ['register', t('Register'), "", t('Create an account'), 'register_nav_btn']; } // TODO: update help content for various modules - if (false /* !get_config('system', 'hide_help') */) { + if (false /* !Config::Get('system', 'hide_help') */) { $help_url = z_root() . '/help?f=&cmd=' . App::$cmd; $context_help = ''; - $enable_context_help = ((intval(get_config('system', 'enable_context_help')) === 1 || get_config('system', 'enable_context_help') === false) ? true : false); + $enable_context_help = ((intval(Config::Get('system', 'enable_context_help')) === 1 || Config::Get('system', 'enable_context_help') === false) ? true : false); if ($enable_context_help === true) { require_once('include/help.php'); $context_help = load_context_help(); @@ -350,7 +351,7 @@ function nav($template = 'default') { '$pleasewait' => t('Please wait...'), '$nav_apps' => $nav_apps, '$navbar_apps' => $navbar_apps, - '$channel_menu' => get_pconfig(App::$profile_uid, 'system', 'channel_menu', get_config('system', 'channel_menu')), + '$channel_menu' => get_pconfig(App::$profile_uid, 'system', 'channel_menu', Config::Get('system', 'channel_menu')), '$channel_thumb' => ((App::$profile) ? App::$profile['thumb'] : ''), '$channel_apps' => $channel_apps, '$addapps' => t('Apps'), @@ -448,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' ], ]; @@ -461,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']) { @@ -471,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'), @@ -479,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' ]; } @@ -490,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' ]; } @@ -504,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' ]; } } @@ -528,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 a5c14f9d1..55eecac84 100644 --- a/include/network.php +++ b/include/network.php @@ -1,6 +1,8 @@ <?php use Zotlabs\Lib\Activity; +use Zotlabs\Lib\Config; +use Zotlabs\Lib\Mailer; use Zotlabs\Lib\Zotfinger; use Zotlabs\Lib\Libzot; use Zotlabs\Lib\Queue; @@ -67,7 +69,7 @@ function z_fetch_url($url, $binary = false, $redirects = 0, $opts = array()) { @curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (compatible; zot)'); @curl_setopt($ch, CURLOPT_ENCODING, ''); - $ciphers = @get_config('system','curl_ssl_ciphers'); + $ciphers = @Config::Get('system','curl_ssl_ciphers'); if($ciphers) @curl_setopt($ch, CURLOPT_SSL_CIPHER_LIST, $ciphers); @@ -114,16 +116,16 @@ function z_fetch_url($url, $binary = false, $redirects = 0, $opts = array()) { @curl_setopt($ch, CURLOPT_TIMEOUT, intval($opts['timeout'])); } else { - $curl_time = intval(@get_config('system','curl_timeout')); - @curl_setopt($ch, CURLOPT_TIMEOUT, (($curl_time !== false) ? $curl_time : 60)); + $curl_time = intval(@Config::Get('system','curl_timeout')); + @curl_setopt($ch, CURLOPT_TIMEOUT, (($curl_time !== 0) ? $curl_time : 60)); } if(x($opts,'connecttimeout') && intval($opts['connecttimeout'])) { @curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, intval($opts['connecttimeout'])); } else { - $curl_contime = intval(@get_config('system','curl_connecttimeout')); - @curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, (($curl_contime !== false) ? $curl_contime : 30)); + $curl_contime = intval(@Config::Get('system','curl_connecttimeout')); + @curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, (($curl_contime !== 0) ? $curl_contime : 30)); } if(x($opts,'http_auth')) { @@ -145,11 +147,11 @@ function z_fetch_url($url, $binary = false, $redirects = 0, $opts = array()) { @curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, ((x($opts,'novalidate') && intval($opts['novalidate'])) ? false : true)); - $prx = @get_config('system','proxy'); + $prx = @Config::Get('system','proxy'); if(strlen($prx)) { @curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1); @curl_setopt($ch, CURLOPT_PROXY, $prx); - $prxusr = @get_config('system','proxyuser'); + $prxusr = @Config::Get('system','proxyuser'); if(strlen($prxusr)) @curl_setopt($ch, CURLOPT_PROXYUSERPWD, $prxusr); } @@ -263,7 +265,7 @@ function z_post_url($url, $params, $redirects = 0, $opts = array()) { @curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (compatible; zot)"); @curl_setopt($ch, CURLOPT_ENCODING, ''); - $ciphers = @get_config('system','curl_ssl_ciphers'); + $ciphers = @Config::Get('system','curl_ssl_ciphers'); if($ciphers) @curl_setopt($ch, CURLOPT_SSL_CIPHER_LIST, $ciphers); @@ -296,8 +298,8 @@ function z_post_url($url, $params, $redirects = 0, $opts = array()) { @curl_setopt($ch, CURLOPT_TIMEOUT, $opts['timeout']); } else { - $curl_time = intval(@get_config('system','curl_timeout')); - @curl_setopt($ch, CURLOPT_TIMEOUT, (($curl_time !== false) ? $curl_time : 60)); + $curl_time = intval(@Config::Get('system','curl_timeout')); + @curl_setopt($ch, CURLOPT_TIMEOUT, (($curl_time !== 0) ? $curl_time : 60)); } if(x($opts,'http_auth')) { @@ -318,11 +320,11 @@ function z_post_url($url, $params, $redirects = 0, $opts = array()) { @curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, ((x($opts,'novalidate') && intval($opts['novalidate'])) ? false : true)); - $prx = get_config('system','proxy'); + $prx = Config::Get('system','proxy'); if(strlen($prx)) { @curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1); @curl_setopt($ch, CURLOPT_PROXY, $prx); - $prxusr = get_config('system','proxyuser'); + $prxusr = Config::Get('system','proxyuser'); if(strlen($prxusr)) @curl_setopt($ch, CURLOPT_PROXYUSERPWD, $prxusr); } @@ -555,12 +557,15 @@ function z_dns_check($h,$check_mx = 0) { return((@dns_get_record($h,$opts) || filter_var($h, FILTER_VALIDATE_IP)) ? true : false); } -function is_local_url($url) { - if (str_starts_with($url, z_root()) || str_starts_with($url, '/')) { - return true; - } - - return false; +/** + * Returns whether an URL is local to the site, or not. + * + * @param string $url The URL to check + * + * @return bool True if the URL is local, false otherwise. + */ +function is_local_url(string $url): bool { + return str_starts_with($url, z_root()) || str_starts_with($url, '/'); } /** @@ -603,12 +608,12 @@ function validate_url(&$url) { */ function validate_email(string $addr): bool { - if(get_config('system', 'disable_email_validation')) + if(Config::Get('system', 'disable_email_validation')) return true; $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); @@ -638,7 +643,7 @@ function allowed_url($url) { return false; } - $str_allowed = get_config('system', 'allowed_sites'); + $str_allowed = Config::Get('system', 'allowed_sites'); if(! $str_allowed) return true; @@ -682,8 +687,8 @@ function allowed_email($email) { if(! $domain) return false; - $str_allowed = get_config('system', 'allowed_email'); - $str_not_allowed = get_config('system', 'not_allowed_email'); + $str_allowed = Config::Get('system', 'allowed_email'); + $str_not_allowed = Config::Get('system', 'not_allowed_email'); if(! $str_allowed && ! $str_not_allowed) return true; @@ -1472,7 +1477,7 @@ function do_delivery($deliveries, $force = false) { /* $x = q("select count(outq_hash) as total from outq where outq_delivered = 0"); - if(intval($x[0]['total']) > intval(get_config('system','force_queue_threshold',3000)) && (! $force)) { + if(intval($x[0]['total']) > intval(Config::Get('system','force_queue_threshold',3000)) && (! $force)) { logger('immediate delivery deferred.', LOGGER_DEBUG, LOG_INFO); foreach($deliveries as $d) { Queue::update($d); @@ -1482,13 +1487,13 @@ function do_delivery($deliveries, $force = false) { */ - $interval = get_config('queueworker', 'queue_interval', 500000); + $interval = Config::Get('queueworker', 'queue_interval', 500000); - $deliveries_per_process = intval(get_config('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) { @@ -1542,9 +1547,9 @@ function get_site_info() { $admin = false; } - $def_service_class = get_config('system','default_service_class'); + $def_service_class = Config::Get('system','default_service_class'); if($def_service_class) - $service_class = get_config('service_class',$def_service_class); + $service_class = Config::Get('service_class',$def_service_class); else $service_class = false; @@ -1555,9 +1560,9 @@ function get_site_info() { if(! isset($commit) || strlen($commit) > 16) $commit = ''; - $site_info = get_config('system','info'); - $site_name = get_config('system','sitename'); - if(! get_config('system','hidden_version_siteinfo')) { + $site_info = Config::Get('system','info'); + $site_name = Config::Get('system','sitename'); + if(! Config::Get('system','hidden_version_siteinfo')) { $version = Zotlabs\Lib\System::get_project_version(); $tag = Zotlabs\Lib\System::get_std_version(); @@ -1573,15 +1578,15 @@ function get_site_info() { } //Statistics - $channels_total_stat = intval(get_config('system','channels_total_stat')); - $channels_active_halfyear_stat = intval(get_config('system','channels_active_halfyear_stat')); - $channels_active_monthly_stat = intval(get_config('system','channels_active_monthly_stat')); - $local_posts_stat = intval(get_config('system','local_posts_stat')); - $local_comments_stat = intval(get_config('system','local_comments_stat')); - $hide_in_statistics = intval(get_config('system','hide_in_statistics')); - $site_expire = intval(get_config('system', 'default_expire_days')); - - load_config('feature_lock'); + $channels_total_stat = intval(Config::Get('system','channels_total_stat')); + $channels_active_halfyear_stat = intval(Config::Get('system','channels_active_halfyear_stat')); + $channels_active_monthly_stat = intval(Config::Get('system','channels_active_monthly_stat')); + $local_posts_stat = intval(Config::Get('system','local_posts_stat')); + $local_comments_stat = intval(Config::Get('system','local_comments_stat')); + $hide_in_statistics = intval(Config::Get('system','hide_in_statistics')); + $site_expire = intval(Config::Get('system', 'default_expire_days')); + + Config::Load('feature_lock'); $locked_features = array(); if(is_array(App::$config['feature_lock']) && count(App::$config['feature_lock'])) { foreach(App::$config['feature_lock'] as $k => $v) { @@ -1602,18 +1607,18 @@ function get_site_info() { 'server_role' => Zotlabs\Lib\System::get_server_role(), 'commit' => $commit, 'plugins' => $visible_plugins, - 'register_policy' => $register_policy[get_config('system','register_policy')], - 'invitation_only' => (bool) intval(get_config('system','invitation_only')), - 'directory_mode' => $directory_mode[get_config('system','directory_mode')], - 'directory_server' => get_config('system','directory_server'), - 'language' => get_config('system','language'), - 'rss_connections' => (bool) intval(get_config('system','feed_contacts')), + 'register_policy' => $register_policy[Config::Get('system','register_policy')], + 'invitation_only' => (bool) intval(Config::Get('system','invitation_only')), + 'directory_mode' => $directory_mode[Config::Get('system','directory_mode')], + 'directory_server' => Config::Get('system','directory_server'), + 'language' => Config::Get('system','language'), + 'rss_connections' => (bool) intval(Config::Get('system','feed_contacts')), 'expiration' => $site_expire, 'default_service_restrictions' => $service_class, 'locked_features' => $locked_features, 'admin' => $admin, 'dbdriver' => DBA::$dba->getdriver() . ' ' . ((ACTIVE_DBTYPE == DBTYPE_POSTGRES) ? 'postgres' : 'mysql'), - 'lastpoll' => get_config('system','lastpoll'), + 'lastpoll' => Config::Get('system','lastpoll'), 'info' => (($site_info) ? $site_info : ''), 'channels_total' => $channels_total_stat, 'hide_in_statistics' => $hide_in_statistics @@ -1651,7 +1656,7 @@ function check_siteallowed($url) { if(array_key_exists('allowed',$arr)) return $arr['allowed']; - $bl1 = get_config('system','whitelisted_sites'); + $bl1 = Config::Get('system','whitelisted_sites'); if(is_array($bl1) && $bl1) { foreach($bl1 as $bl) { if($bl1 === '*') @@ -1660,7 +1665,7 @@ function check_siteallowed($url) { return true; } } - $bl1 = get_config('system','blacklisted_sites'); + $bl1 = Config::Get('system','blacklisted_sites'); if(is_array($bl1) && $bl1) { foreach($bl1 as $bl) { if($bl1 === '*') @@ -1696,7 +1701,7 @@ function check_channelallowed($hash) { if(array_key_exists('allowed',$arr)) return $arr['allowed']; - $bl1 = get_config('system','whitelisted_channels'); + $bl1 = Config::Get('system','whitelisted_channels'); if(is_array($bl1) && $bl1) { foreach($bl1 as $bl) { if($bl1 === '*') @@ -1705,7 +1710,7 @@ function check_channelallowed($hash) { return true; } } - $bl1 = get_config('system','blacklisted_channels'); + $bl1 = Config::Get('system','blacklisted_channels'); if(is_array($bl1) && $bl1) { foreach($bl1 as $bl) { if($bl1 === '*') @@ -1809,54 +1814,9 @@ function network_to_name($s) { */ function z_mail($params) { - if(! $params['fromEmail']) { - $params['fromEmail'] = get_config('system','from_email'); - if(! $params['fromEmail']) - $params['fromEmail'] = 'Administrator' . '@' . App::get_hostname(); - } - if(! $params['fromName']) { - $params['fromName'] = get_config('system','from_email_name'); - if(! $params['fromName']) - $params['fromName'] = Zotlabs\Lib\System::get_site_name(); - } - if(! $params['replyTo']) { - $params['replyTo'] = get_config('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(); } @@ -2141,21 +2101,59 @@ function get_request_string($url) { /** - * Builds a url from the result of `parse_url`. + * Reconstructs a URL from its parsed components. + * + * This function takes a parsed URL as an associative array and reconstructs + * the URL based on the specified components (scheme, host, port, user, pass, path, query, fragment). + * You can specify which components should be included in the final URL by passing the optional + * `$parts` array. The function will return the complete URL string formed by combining + * only the parts that exist in both the parsed URL and the `$parts` array. + * + * @param array $parsed_url The parsed URL components as an associative array. + * The array can include keys like 'scheme', 'host', 'port', 'user', 'pass', + * 'path', 'query', 'fragment'. * - * @param array $parsed_url An associative array as produced by `parse_url`. + * @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 The reassembled URL as a string. + * @return string The reconstructed URL as a string. */ -function unparse_url($parsed_url) { - $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/oauth.php b/include/oauth.php index 845ec4558..3426f9ca5 100644 --- a/include/oauth.php +++ b/include/oauth.php @@ -1,11 +1,13 @@ <?php /** @file */ -/** +/** * OAuth server * Based on oauth2-php <http://code.google.com/p/oauth2-php/> - * + * */ +use Zotlabs\Lib\Config; + define('REQUEST_TOKEN_DURATION', 300); define('ACCESS_TOKEN_DURATION', 31536000); @@ -18,7 +20,7 @@ class ZotOAuth1DataStore extends OAuth1DataStore { function gen_token(){ return md5(base64_encode(pack('N6', mt_rand(), mt_rand(), mt_rand(), mt_rand(), mt_rand(), uniqid()))); } - + function lookup_consumer($consumer_key) { logger('consumer_key: ' . $consumer_key, LOGGER_DEBUG); @@ -72,7 +74,7 @@ class ZotOAuth1DataStore extends OAuth1DataStore { $key = $this->gen_token(); $sec = $this->gen_token(); - + if ($consumer->key){ $k = $consumer->key; } else { @@ -94,19 +96,19 @@ class ZotOAuth1DataStore extends OAuth1DataStore { function new_access_token($token, $consumer, $verifier = null) { logger(__function__.":".$token.", ". $consumer.", ". $verifier, LOGGER_DEBUG); - + // return a new access token attached to this consumer // for the user associated with this token if the request token // is authorized // should also invalidate the request token - + $ret=Null; - + // get user for this verifier - $uverifier = get_config("oauth", $verifier); + $uverifier = Config::Get("oauth", $verifier); logger(__function__.":".$verifier.",".$uverifier, LOGGER_DEBUG); if (is_null($verifier) || ($uverifier!==false)) { - + $key = $this->gen_token(); $sec = $this->gen_token(); @@ -119,16 +121,16 @@ class ZotOAuth1DataStore extends OAuth1DataStore { intval($uverifier)); if ($r) - $ret = new OAuth1Token($key,$sec); + $ret = new OAuth1Token($key,$sec); } - - + + q("DELETE FROM tokens WHERE id='%s'", $token->key); - - + + if (!is_null($ret) && $uverifier!==false) { - del_config("oauth", $verifier); - + Config::Delete("oauth", $verifier); + // $apps = get_pconfig($uverifier, "oauth", "apps"); // if ($apps===false) $apps=array(); // $apps[] = $consumer->key; @@ -145,7 +147,7 @@ class ZotOAuth1 extends OAuth1Server { $this->add_signature_method(new OAuth1SignatureMethod_PLAINTEXT()); $this->add_signature_method(new OAuth1SignatureMethod_HMAC_SHA1()); } - + function loginUser($uid){ logger("ZotOAuth1::loginUser $uid"); @@ -174,7 +176,7 @@ class ZotOAuth1 extends OAuth1Server { $_SESSION['allow_api'] = true; } } - + } /* @@ -195,13 +197,13 @@ class FKOAuth2 extends OAuth2 { dbesc($client_secret), dbesc($redirect_uri) ); - + return $r; } protected function checkClientCredentials($client_id, $client_secret = NULL) { $client_secret = $this->db_secret($client_secret); - + $r = q("SELECT pw FROM clients WHERE client_id = '%s'", dbesc($client_id)); @@ -223,21 +225,21 @@ class FKOAuth2 extends OAuth2 { protected function getAccessToken($oauth_token) { $r = q("SELECT client_id, expires, scope FROM tokens WHERE id = '%s'", dbesc($oauth_token)); - + if (count($r)) return $r[0]; return null; } - + protected function setAccessToken($oauth_token, $client_id, $expires, $scope = NULL) { $r = q("INSERT INTO tokens (id, client_id, expires, scope) VALUES ('%s', '%s', %d, '%s')", dbesc($oauth_token), dbesc($client_id), intval($expires), dbesc($scope)); - + return $r; } @@ -251,23 +253,23 @@ class FKOAuth2 extends OAuth2 { protected function getAuthCode($code) { $r = q("SELECT id, client_id, redirect_uri, expires, auth_scope FROM auth_codes WHERE id = '%s'", dbesc($code)); - + if (count($r)) return $r[0]; return null; } protected function setAuthCode($code, $client_id, $redirect_uri, $expires, $scope = NULL) { - $r = q("INSERT INTO auth_codes - (id, client_id, redirect_uri, expires, auth_scope) VALUES + $r = q("INSERT INTO auth_codes + (id, client_id, redirect_uri, expires, auth_scope) VALUES ('%s', '%s', '%s', %d, '%s')", dbesc($code), dbesc($client_id), dbesc($redirect_uri), intval($expires), dbesc($scope)); - return $r; - } - + return $r; + } + } */ diff --git a/include/oauth2.php b/include/oauth2.php index 3a71a651d..f7a279892 100644 --- a/include/oauth2.php +++ b/include/oauth2.php @@ -12,12 +12,12 @@ $oauth2_server->addGrantType(new OAuth2\GrantType\ClientCredentials($storage)); $oauth2_server->addGrantType(new OAuth2\GrantType\AuthorizationCode($storage)); - $keyStorage = new OAuth2\Storage\Memory( [ - 'keys' => [ - 'public_key' => get_config('system','pubkey'), - 'private_key' => get_config('system','prvkey') + $keyStorage = new OAuth2\Storage\Memory( [ + 'keys' => [ + 'public_key' => Config::Get('system','pubkey'), + 'private_key' => Config::Get('system','prvkey') ] ]); $oauth2_server->addStorage($keyStorage,'public_key'); -*/
\ No newline at end of file +*/ diff --git a/include/oembed.php b/include/oembed.php index a90c91641..840164663 100644 --- a/include/oembed.php +++ b/include/oembed.php @@ -1,7 +1,7 @@ <?php /** @file */ use Zotlabs\Lib\Cache; - +use Zotlabs\Lib\Config; function oembed_replacecb($matches){ @@ -28,7 +28,7 @@ function oembed_action($embedurl) { logger('oembed_action: ' . $embedurl, LOGGER_DEBUG, LOG_INFO); if(strpos($embedurl,'http://') === 0) { - if(intval(get_config('system','embed_sslonly'))) { + if(intval(Config::Get('system','embed_sslonly'))) { $action = 'block'; } } @@ -39,7 +39,7 @@ function oembed_action($embedurl) { // site white/black list - if(($x = get_config('system','embed_deny'))) { + if(($x = Config::Get('system','embed_deny'))) { if(($x) && (! is_array($x))) $x = explode("\n",$x); if($x) { @@ -55,7 +55,7 @@ function oembed_action($embedurl) { $found = false; - if(($x = get_config('system','embed_allow'))) { + if(($x = Config::Get('system','embed_allow'))) { if(($x) && (! is_array($x))) $x = explode("\n",$x); if($x) { @@ -139,16 +139,20 @@ function oembed_fetch_url($embedurl){ // we should try to cache this and avoid a lookup on each render $is_matrix = is_matrix_url($embedurl); - $zrl = ((get_config('system','oembed_zrl')) ? $is_matrix : false); + $zrl = ((Config::Get('system','oembed_zrl')) ? $is_matrix : false); $furl = ((local_channel() && $zrl) ? zid($embedurl) : $embedurl); - if($action !== 'block' && (! get_config('system','oembed_cache_disable'))) { + if (empty($furl)) { + return; + } + + if($action !== 'block' && (! Config::Get('system','oembed_cache_disable'))) { $txt = Cache::get('[' . App::$videowidth . '] ' . $furl); } - if(strpos(strtolower($embedurl),'.pdf') !== false && get_config('system','inline_pdf')) { + if(strpos(strtolower($embedurl),'.pdf') !== false && Config::Get('system','inline_pdf')) { $action = 'allow'; $j = [ 'html' => '<object data="' . $embedurl . '" type="application/pdf" style="width: 100%; height: 300px;"></object>', @@ -164,7 +168,7 @@ function oembed_fetch_url($embedurl){ $txt = EMPTY_STR; if ($action !== 'block') { - $max_oembed_size = get_config('system', 'oembed_max_size', 1 * 1024 * 1024 /* 1MB */); + $max_oembed_size = Config::Get('system', 'oembed_max_size', 1 * 1024 * 1024 /* 1MB */); stream_context_set_default( [ @@ -259,7 +263,7 @@ function oembed_fetch_url($embedurl){ // save in cache - if(! get_config('system','oembed_cache_disable')) + if(! Config::Get('system','oembed_cache_disable')) Cache::set('[' . App::$videowidth . '] ' . $furl, $txt); } @@ -328,7 +332,7 @@ function oembed_format_object($j){ $jhtml = oembed_iframe($j['embedurl'],(isset($j['width']) ? $j['width'] : null), (isset($j['height']) ? $j['height'] : null)); - $ret="<span class='oembed " . $j['type'] . "'>"; + $ret="<span class='clearfix d-block oembed " . $j['type'] . "'>"; switch ($j['type']) { case "video": { if (isset($j['thumbnail_url'])) { @@ -351,7 +355,6 @@ function oembed_format_object($j){ } else { $ret=$jhtml; } - $ret.="<br>"; }; break; case "photo": { $ret.= "<img width='".$j['width']."' src='".$j['url']."'>"; @@ -386,15 +389,15 @@ function oembed_format_object($j){ // add link to source if not present in "rich" type if ( $j['type'] != 'rich' || !strpos($j['html'],$embedurl) ){ $embedlink = (isset($j['title']))?$j['title'] : $embedurl; - $ret .= '<br />' . "<a href='$embedurl' rel='oembed'>$embedlink</a>"; + $ret .= "<a href='$embedurl' rel='oembed'>$embedlink</a>"; $ret .= "<br />"; if (isset($j['author_name'])) $ret .= t(' by ') . $j['author_name']; if (isset($j['provider_name'])) $ret .= t(' on ') . $j['provider_name']; } else { // add <a> for html2bbcode conversion - $ret .= "<br /><a href='$embedurl' rel='oembed'>$embedurl</a>"; + $ret .= "<a href='$embedurl' rel='oembed'>$embedurl</a>"; } - $ret.="<br style='clear:left'></span>"; + $ret.="</span>"; // mb_convert_encoding() is deprecated // return mb_convert_encoding($ret, 'HTML-ENTITIES', mb_detect_encoding($ret)); @@ -414,8 +417,8 @@ function oembed_iframe($src,$width,$height) { } // try and leave some room for the description line. - $height = intval($height) + 80; - $width = intval($width) + 40; + $height = intval($height); + $width = intval($width); $s = z_root() . '/oembed/' . base64url_encode($src); @@ -429,7 +432,7 @@ function oembed_iframe($src,$width,$height) { function oembed_bbcode2html($text){ - $stopoembed = get_config("system","no_oembed"); + $stopoembed = Config::Get("system","no_oembed"); if ($stopoembed == true){ return preg_replace("/\[embed\](.+?)\[\/embed\]/is", "<!-- oembed $1 --><i>". t('Embedding disabled') ." : $1</i><!-- /oembed $1 -->" ,$text); } diff --git a/include/permissions.php b/include/permissions.php index 28f242712..29d242537 100644 --- a/include/permissions.php +++ b/include/permissions.php @@ -1,5 +1,7 @@ <?php +use Zotlabs\Lib\Config; + require_once('include/security.php'); /** @@ -105,7 +107,7 @@ function get_all_perms($uid, $observer_xchan, $check_siteblock = true, $default_ // system is blocked to anybody who is not authenticated - if(($check_siteblock) && (! $observer_xchan) && intval(get_config('system', 'block_public'))) { + if(($check_siteblock) && (! $observer_xchan) && intval(Config::Get('system', 'block_public'))) { $ret[$perm_name] = 0; continue; } @@ -291,7 +293,7 @@ function perm_is_allowed($uid, $observer_xchan, $permission, $check_siteblock = // system is blocked to anybody who is not authenticated - if(($check_siteblock) && (! $observer_xchan) && intval(get_config('system', 'block_public'))) + if(($check_siteblock) && (! $observer_xchan) && intval(Config::Get('system', 'block_public'))) return false; // Check if this $uid is actually the $observer_xchan @@ -406,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); @@ -420,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 ); @@ -496,7 +498,7 @@ function site_default_perms() { $global_perms = \Zotlabs\Access\Permissions::Perms(); foreach($global_perms as $perm => $v) { - $x = get_config('default_perms', $perm, $typical[$perm]); + $x = Config::Get('default_perms', $perm, $typical[$perm]); $ret[$perm] = $x; } diff --git a/include/photo/photo_driver.php b/include/photo/photo_driver.php index 4394d3238..66a5d19f9 100644 --- a/include/photo/photo_driver.php +++ b/include/photo/photo_driver.php @@ -1,5 +1,6 @@ <?php +use Zotlabs\Lib\Config; use Zotlabs\Photo\PhotoDriver; use Zotlabs\Photo\PhotoGd; use Zotlabs\Photo\PhotoImagick; @@ -32,13 +33,13 @@ function photo_factory($data, $type = null) { return null; } - $ignore_imagick = get_config('system', 'ignore_imagick'); + $ignore_imagick = Config::Get('system', 'ignore_imagick'); 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) { - $limits = get_config('system', 'imagick_limits', false); + $limits = Config::Get('system', 'imagick_limits', false); if ($limits) foreach ($limits as $k => $v) IMagick::setResourceLimit($k, $v); @@ -99,7 +100,7 @@ function guess_image_type($filename, $data = '') { } if(is_null($type)){ - $ignore_imagick = get_config('system', 'ignore_imagick'); + $ignore_imagick = Config::Get('system', 'ignore_imagick'); // Guessing from extension? Isn't that... dangerous? if(class_exists('Imagick') && ! $ignore_imagick) { $v = Imagick::getVersion(); diff --git a/include/photos.php b/include/photos.php index 5e993e15f..a9f92e103 100644 --- a/include/photos.php +++ b/include/photos.php @@ -6,6 +6,8 @@ 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'); @@ -74,11 +76,11 @@ function photo_upload($channel, $observer, $args) { $os_storage = 0; - $max_thumb = get_config('system', 'max_thumbnail', 1600); + $max_thumb = Config::Get('system', 'max_thumbnail', 1600); if ($args['os_syspath'] && $args['getimagesize']) { if ($args['getimagesize'][0] > $max_thumb || $args['getimagesize'][1] > $max_thumb) { - $imagick_path = get_config('system', 'imagick_convert_path'); + $imagick_path = Config::Get('system', 'imagick_convert_path'); if ($imagick_path && @file_exists($imagick_path)) { $tmp_name = $args['os_syspath'] . '-001'; $newsize = photo_calculate_scale(array_merge($args['getimagesize'], ['max' => $max_thumb])); @@ -146,7 +148,7 @@ function photo_upload($channel, $observer, $args) { logger('Received file: ' . $filename . ' as ' . $src . ' (' . $type . ') ' . $filesize . ' bytes', LOGGER_DEBUG); - $maximagesize = get_config('system', 'maximagesize'); + $maximagesize = Config::Get('system', 'maximagesize'); if (($maximagesize) && ($filesize > $maximagesize)) { $ret['message'] = sprintf(t('Image exceeds website size limit of %lu bytes'), $maximagesize); @@ -219,7 +221,7 @@ function photo_upload($channel, $observer, $args) { @unlink($src); - $max_length = get_config('system', 'max_image_length'); + $max_length = Config::Get('system', 'max_image_length'); if (!$max_length) $max_length = MAX_IMAGE_LENGTH; if ($max_length > 0) @@ -404,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 = [ @@ -437,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) { @@ -452,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'; @@ -459,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; } @@ -476,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 { @@ -495,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, @@ -513,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, @@ -540,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 @@ -910,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 bbfeab988..b5f9959b9 100644 --- a/include/plugin.php +++ b/include/plugin.php @@ -5,6 +5,7 @@ * @brief Some functions to handle addons and themes. */ +use Zotlabs\Lib\Config; /** * @brief Handle errors in plugin calls. @@ -25,7 +26,7 @@ function handleerrors_plugin($plugin, $notice, $log, $uninstall = false){ $idx = array_search($plugin, \App::$plugins); unset(\App::$plugins[$idx]); uninstall_plugin($plugin); - set_config("system", "addon", implode(", ", \App::$plugins)); + Config::Set("system", "addon", implode(", ", \App::$plugins)); } } @@ -187,7 +188,7 @@ function plugin_is_installed($name) { * @brief Reload all updated plugins. */ function reload_plugins() { - $plugins = get_config('system', 'addon'); + $plugins = Config::Get('system', 'addon'); if(strlen($plugins)) { $r = dbq("SELECT * FROM addon WHERE installed = 1"); if($r) @@ -266,7 +267,7 @@ function plugins_sync() { $installed = plugins_installed_list(); - $plugins = get_config('system', 'addon', ''); + $plugins = Config::Get('system', 'addon', ''); $plugins_arr = explode(',', $plugins); @@ -487,18 +488,19 @@ function call_hooks($name, &$data = null) { @include_once($hook[0]); } - if(preg_match('|^a:[0-9]+:{.*}$|s', $hook[1])) { - $hook[1] = unserialize($hook[1]); - } - elseif(strpos($hook[1],'::')) { - // We shouldn't need to do this, but it appears that PHP - // isn't able to directly execute a string variable with a class - // method in the manner we are attempting it, so we'll - // turn it into an array. - $hook[1] = explode('::',$hook[1]); + if(is_string($hook[1])) { + if (preg_match('|^a:[0-9]+:{.*}$|s', $hook[1])) { + $hook[1] = unserialize($hook[1]); + } + elseif(strpos($hook[1],'::')) { + // We shouldn't need to do this, but it appears that PHP + // isn't able to directly execute a string variable with a class + // method in the manner we are attempting it, so we'll + // turn it into an array. + $hook[1] = explode('::',$hook[1]); + } } - if(is_callable($hook[1])) { $func = $hook[1]; $func($data); @@ -719,7 +721,7 @@ function check_plugin_versions($info) { continue; if(strpos($test,'.')) { $conf = explode('.',$test); - if(get_config(trim($conf[0]),trim($conf[1]))) + if(Config::Get(trim($conf[0]),trim($conf[1]))) return true; else return false; @@ -985,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"; } } @@ -1057,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 b3e45742e..4b072cf92 100644 --- a/include/security.php +++ b/include/security.php @@ -5,8 +5,10 @@ * @brief Some security related functions. */ +use Zotlabs\Lib\Config; + /** - * @param int $user_record The account_id + * @param array $user_record The account record * @param array $channel * @param bool $login_initial default false * @param bool $interactive default false @@ -319,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 @@ -342,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 ($observer) { + 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 = '' ) )"; + } + + /** + * Authenticated visitor. + */ + + elseif ($observer) { $sec = get_security_ids($owner_id, $observer); @@ -398,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; @@ -495,7 +495,7 @@ function item_permissions_sql($owner_id, $remote_observer = null) { " 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 )) - ))) + )) OR ( item_private = 1 $scope )) ", dbesc($observer), dbesc($observer), @@ -706,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; + } - if ($xchans) { - $ret['allow_cid'] = ids_to_array($xchans, 'xchan_hash'); - $hashes = ids_to_querystr($xchans, 'xchan_hash', true); + $ch = q("select channel_hash from channel where channel_id = %d", + intval($channel_id) + ); + if ($ch) { + $ret['channel_id'][] = $ch[0]['channel_hash']; + } - // private profiles are treated as a virtual group + $groups = []; - $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']; - } + // private profiles are treated as a virtual group + + $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; } @@ -872,7 +877,7 @@ function stream_perms_xchans($perms = NULL) { */ function zarIsDuty($wd=NULL, $hhmm=NULL, $op='isOpen') { - $isduty = get_config('system', 'register_duty_jso'); + $isduty = Config::Get('system', 'register_duty_jso'); if (!$isduty) return (bool)false; @@ -906,7 +911,6 @@ function zarIsDuty($wd=NULL, $hhmm=NULL, $op='isOpen') { } } return $dutyis; - break; case 'nextOpen': /** @@ -951,7 +955,6 @@ function zarIsDuty($wd=NULL, $hhmm=NULL, $op='isOpen') { } } return $is1; // false or array - break; default: // diff --git a/include/socgraph.php b/include/socgraph.php index e5e8ddf74..336c1c0c3 100644 --- a/include/socgraph.php +++ b/include/socgraph.php @@ -1,6 +1,7 @@ <?php /** @file */ +use Zotlabs\Lib\Config; use Zotlabs\Lib\Libzot; use Zotlabs\Lib\Libzotdir; use Zotlabs\Lib\Zotfinger; @@ -288,7 +289,7 @@ function suggestion_query($uid, $myxchan, $start = 0, $limit = 80) { function update_suggestions() { - $dirmode = get_config('system', 'directory_mode', DIRECTORY_MODE_NORMAL); + $dirmode = Config::Get('system', 'directory_mode', DIRECTORY_MODE_NORMAL); if($dirmode == DIRECTORY_MODE_STANDALONE) { poco_load('', z_root() . '/poco'); diff --git a/include/statistics_fns.php b/include/statistics_fns.php index 98b0efd41..5915c82a2 100644 --- a/include/statistics_fns.php +++ b/include/statistics_fns.php @@ -1,13 +1,15 @@ <?php /** @file */ +use Zotlabs\Lib\Config; + function update_channels_total_stat() { $r = q("select count(channel_id) as channels_total from channel left join account on account_id = channel_account_id where account_flags = 0 "); if($r) { $channels_total_stat = intval($r[0]['channels_total']); - set_config('system','channels_total_stat',$channels_total_stat); + Config::Set('system','channels_total_stat',$channels_total_stat); } else { - set_config('system','channels_total_stat',0); + Config::Set('system','channels_total_stat',0); } } @@ -17,10 +19,10 @@ function update_channels_active_halfyear_stat() { db_utcnow(), db_quoteinterval('6 MONTH') ); if($r) { - set_config('system','channels_active_halfyear_stat',count($r)); + Config::Set('system','channels_active_halfyear_stat',count($r)); } else { - set_config('system','channels_active_halfyear_stat','0'); + Config::Set('system','channels_active_halfyear_stat','0'); } } @@ -30,10 +32,10 @@ function update_channels_active_monthly_stat() { db_utcnow(), db_quoteinterval('1 MONTH') ); if($r) { - set_config('system','channels_active_monthly_stat',count($r)); + Config::Set('system','channels_active_monthly_stat',count($r)); } else { - set_config('system','channels_active_monthly_stat','0'); + Config::Set('system','channels_active_monthly_stat','0'); } } @@ -41,9 +43,9 @@ function update_local_posts_stat() { $posts = q("SELECT COUNT(*) AS local_posts FROM item WHERE item_wall = 1 and id = parent"); if (is_array($posts)) { $local_posts_stat = intval($posts[0]["local_posts"]); - set_config('system','local_posts_stat',$local_posts_stat); + Config::Set('system','local_posts_stat',$local_posts_stat); } else { - set_config('system','local_posts_stat',0); + Config::Set('system','local_posts_stat',0); } } @@ -54,5 +56,5 @@ function update_local_comments_stat() { else $local_posts = $posts[0]["local_posts"]; - set_config('system','local_comments_stat', $local_posts); -}
\ No newline at end of file + Config::Set('system','local_comments_stat', $local_posts); +} diff --git a/include/taxonomy.php b/include/taxonomy.php index dd56ab956..45287fa63 100644 --- a/include/taxonomy.php +++ b/include/taxonomy.php @@ -6,8 +6,10 @@ // To do this we need to escape these characters if they appear in our tag. use Zotlabs\Lib\Cache; +use Zotlabs\Lib\Config; use Zotlabs\Daemon\Master; + function file_tag_encode($s) { return str_replace(array('<','>','[',']'),array('%3c','%3e','%5b','%5d'),$s); } @@ -351,7 +353,7 @@ function pub_tagadelic($net, $site, $limit, $recent, $safemode, $type) { } if($safemode) { - $unsafetags = get_config('system','unsafepubtags', [ 'boobs', 'bot', 'rss', 'girl','girls', 'nsfw', 'sexy', 'nude' ]); + $unsafetags = Config::Get('system','unsafepubtags', [ 'boobs', 'bot', 'rss', 'girl','girls', 'nsfw', 'sexy', 'nude' ]); if($unsafetags) { $sql_extra .= " and not term.term in ( " . stringify_array($unsafetags,true) . ") "; } diff --git a/include/text.php b/include/text.php index 713911af2..7692a6f3e 100644 --- a/include/text.php +++ b/include/text.php @@ -9,6 +9,7 @@ use Michelf\MarkdownExtra; use Ramsey\Uuid\Uuid; use Ramsey\Uuid\Exception\UnableToBuildUuidException; +use Zotlabs\Lib\Config; use Zotlabs\Lib\Crypto; use Zotlabs\Lib\SvgSanitizer; use Zotlabs\Lib\Libzot; @@ -741,9 +742,9 @@ function logger($msg, $level = LOGGER_NORMAL, $priority = LOG_INFO) { $loglevel = LOGGER_ALL; } else { - $debugging = get_config('system', 'debugging'); - $loglevel = intval(get_config('system', 'loglevel')); - $logfile = get_config('system', 'logfile'); + $debugging = Config::Get('system', 'debugging'); + $loglevel = intval(Config::Get('system', 'loglevel')); + $logfile = Config::Get('system', 'logfile'); } if((! $debugging) || (! $logfile) || ($level > $loglevel)) @@ -846,9 +847,9 @@ function dlogger($msg, $level = 0) { if(App::$module == 'setup') return; - $debugging = get_config('system','debugging'); - $loglevel = intval(get_config('system','loglevel')); - $logfile = get_config('system','dlogfile'); + $debugging = Config::Get('system','debugging'); + $loglevel = intval(Config::Get('system','loglevel')); + $logfile = Config::Get('system','dlogfile'); if((! $debugging) || (! $logfile) || ($level > $loglevel)) return; @@ -1144,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 ); } @@ -1253,7 +1254,7 @@ function sslify($s) { // The downside is that http: media files will likely be blocked by your browser // Complain to your browser maker - $allow = get_config('system','sslify_everything'); + $allow = Config::Get('system','sslify_everything'); $pattern = (($allow) ? "/\<(.*?)src=[\"|'](http\:.*?)[\"|'](.*?)\>/" : "/\<img(.*?)src=[\"|'](http\:.*?)[\"|'](.*?)\>/" ); $matches = null; @@ -1407,7 +1408,7 @@ function list_smilies($default_only = false) { */ function smilies($s, $sample = false) { - if(intval(get_config('system', 'no_smilies')) + if(intval(Config::Get('system', 'no_smilies')) || (local_channel() && intval(get_pconfig(local_channel(), 'system', 'no_smilies')))) return $s; @@ -1578,15 +1579,13 @@ 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)) { @@ -1651,7 +1650,7 @@ function format_hashtags(&$item) { if($s) $s .= ' '; - $s .= '<span class="badge rounded-pill bg-info"><i class="fa fa-hashtag"></i> <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> <a class="text-white" href="' . zid($t['url']) . '" >' . $term . '</a></span>'; } } @@ -1674,7 +1673,7 @@ function format_mentions(&$item) { continue; if($s) $s .= ' '; - $s .= '<span class="badge rounded-pill bg-success"><i class="fa fa-at"></i> <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> <a class="text-white" href="' . zid($t['url']) . '" >' . $term . '</a></span>'; } } @@ -2003,7 +2002,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>'); } } @@ -2081,10 +2080,15 @@ function prepare_text($text, $content_type = 'text/bbcode', $opts = false) { default: require_once('include/bbcode.php'); - if(stristr($text, '[nosmile]')) - $s = bbcode($text, ((is_array($opts)) ? $opts : [] )); - else - $s = smilies(bbcode($text, ((is_array($opts)) ? $opts : [] ))); + // events are handled in format_event_obj() + if (empty($opts['is_event_item'])) { + if(stristr($text, '[nosmile]')) { + $s = bbcode($text, ((is_array($opts)) ? $opts : [] )); + } + else { + $s = smilies(bbcode($text, ((is_array($opts)) ? $opts : [] ))); + } + } $s = zidify_links($s); @@ -2113,7 +2117,7 @@ function create_export_photo_body(&$item) { * @return string */ function feed_hublinks() { - $hub = get_config('system', 'huburl'); + $hub = Config::Get('system', 'huburl'); $hubxml = ''; if(strlen($hub)) { @@ -2496,7 +2500,7 @@ function check_webbie($arr) { // These names conflict with the CalDAV server $taken = [ 'principals', 'addressbooks', 'calendars' ]; - $reservechan = get_config('system','reserved_channels'); + $reservechan = Config::Get('system','reserved_channels'); if(strlen($reservechan)) { $taken = array_merge($taken,explode(',', $reservechan)); } @@ -2633,13 +2637,13 @@ function xchan_query(&$items, $abook = true, $effective_uid = 0) { 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) @@ -3197,53 +3201,53 @@ 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', + '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' ]; @@ -3260,7 +3264,7 @@ function getIconFromType($type) { } if(! $iconFromType) { - $iconFromType = 'fa-file-o'; + $iconFromType = 'bi-file-earmark'; } @@ -3749,12 +3753,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); @@ -3840,7 +3841,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; @@ -3848,7 +3849,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 b38457d99..b74e82930 100644 --- a/include/zid.php +++ b/include/zid.php @@ -1,5 +1,6 @@ <?php +use Zotlabs\Lib\Config; use Zotlabs\Lib\Libzot; use Zotlabs\Lib\Verify; @@ -37,10 +38,16 @@ function is_matrix_url($url) { * @return string */ function zid($s, $address = '') { - if (!$s || strpos($s,'zid=')) + if (!$s || strpos($s,'zid=')) { return $s; + } $m = parse_url($s); + + if (!is_array($m)) { + return $s; + } + $fragment = ((array_key_exists('fragment',$m) && $m['fragment']) ? $m['fragment'] : false); if($fragment !== false) $s = str_replace('#' . $fragment,'',$s); @@ -143,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; @@ -165,7 +175,7 @@ function drop_query_params($s, $p) { $parsed['query'] = $query; } - return unparse_url($parsed); + return escape_tags(unparse_url($parsed)); } @@ -254,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]; } /** @@ -406,7 +416,7 @@ function owt_init($token) { App::set_observer($hubloc); require_once('include/security.php'); App::set_groups(init_groups_visitor($_SESSION['visitor_id'])); - if(! get_config('system', 'hide_owa_greeting')) + if(! Config::Get('system', 'hide_owa_greeting')) info(sprintf( t('OpenWebAuth: %1$s welcomes %2$s'),App::get_hostname(), $hubloc['xchan_name'])); logger('OpenWebAuth: auth success from ' . $hubloc['xchan_addr']); |