diff options
Diffstat (limited to 'include')
39 files changed, 2193 insertions, 580 deletions
diff --git a/include/acl_selectors.php b/include/acl_selectors.php index 35e385058..a3476439a 100644 --- a/include/acl_selectors.php +++ b/include/acl_selectors.php @@ -79,24 +79,44 @@ function populate_acl($defaults = null,$show_jotnets = true, $emptyACL_descripti $custom = false; } - $r = q("SELECT id, profile_guid, profile_name from profile where is_default = 0 and uid = %d order by profile_name", + $r = q("SELECT id, hash, gname FROM pgrp WHERE deleted = 0 AND uid = %d ORDER BY gname ASC", intval(local_channel()) ); + if($r) { - foreach($r as $rv) { - $selected = (($single_group && 'vp.' . $rv['profile_guid'] === $allow_gid[0]) ? ' selected = "selected" ' : ''); - $groups .= '<option id="' . 'vp' . $rv['id'] . '" value="' . 'vp.' . $rv['profile_guid'] . '"' . $selected . '>' . t('Profile','acl') . ' ' . $rv['profile_name'] . '</option>' . "\r\n"; + $groups .= '<optgroup label = "' . t('Privacy Groups').'">'; + foreach($r as $rr) { + $selected = (($single_group && $rr['hash'] === $allow_gid[0]) ? ' selected = "selected" ' : ''); + $groups .= '<option id="' . $rr['id'] . '" value="' . $rr['hash'] . '"' . $selected . '>' . $rr['gname'] . '</option>' . "\r\n"; } + $groups .= '</optgroup>'; } - $r = q("SELECT id, hash, gname FROM pgrp WHERE deleted = 0 AND uid = %d ORDER BY gname ASC", + $r = q("SELECT id, profile_guid, profile_name from profile where is_default = 0 and uid = %d order by profile_name", intval(local_channel()) ); if($r) { - foreach($r as $rr) { - $selected = (($single_group && $rr['hash'] === $allow_gid[0]) ? ' selected = "selected" ' : ''); - $groups .= '<option id="' . $rr['id'] . '" value="' . $rr['hash'] . '"' . $selected . '>' . $rr['gname'] . '</option>' . "\r\n"; + $groups .= '<optgroup label = "' . t('Profile-Based Privacy Groups').'">'; + foreach($r as $rv) { + $selected = (($single_group && 'vp.' . $rv['profile_guid'] === $allow_gid[0]) ? ' selected = "selected" ' : ''); + $groups .= '<option id="' . 'vp' . $rv['id'] . '" value="' . 'vp.' . $rv['profile_guid'] . '"' . $selected . '>' . $rv['profile_name'] . '</option>' . "\r\n"; + } + $groups .= '</optgroup>'; + } + + // $dialog_description is only set in places where we set permissions for a post. + // Abuse this fact to decide if forums should be displayed or not. + if($dialog_description) { + $forums = get_forum_channels(local_channel(),1); + if($forums) { + $groups .= '<optgroup label = "' . t('Forums').'">'; + foreach($forums as $f) { + $private = (($f['private_forum']) ? ' (' . t('Private Forum') . ')' : ''); + $selected = (($single_group && $f['hash'] === $allow_cid[0]) ? ' selected = "selected" ' : ''); + $groups .= '<option id="^' . $f['abook_id'] . '" value="^' . $f['xchan_hash'] . '"' . $selected . '>' . $f['xchan_name'] . $private . '</option>' . "\r\n"; + } + $groups .= '</optgroup>'; } } @@ -104,17 +124,18 @@ function populate_acl($defaults = null,$show_jotnets = true, $emptyACL_descripti $o = replace_macros($tpl, array( '$showall' => $showall_caption, '$onlyme' => t('Only me'), - '$groups' => $groups, + '$groups' => $groups, '$public_selected' => (($has_acl) ? false : true), '$justme_selected' => $just_me, '$custom_selected' => $custom, '$showallOrigin' => $showall_origin, '$showallIcon' => $showall_icon, - '$select_label' => t('Who can see this?'), + '$select_label' => t('Share with'), '$custom' => t('Custom selection'), - '$showlimitedDesc' => t('Select "Show" to allow viewing. "Don\'t show" lets you override and limit the scope of "Show".'), - '$show' => t('Show'), - '$hide' => t("Don't show"), + '$custom_label' => t('Advanced'), + '$showlimitedDesc' => t('Select "Allow" to allow viewing. "Don\'t allow" lets you override and limit the scope of "Allow".'), + '$show' => t('Allow'), + '$hide' => t("Don't allow"), '$search' => t('Search'), '$allowcid' => json_encode($allow_cid), '$allowgid' => json_encode($allow_gid), diff --git a/include/api_zot.php b/include/api_zot.php index 287720484..8f621d998 100644 --- a/include/api_zot.php +++ b/include/api_zot.php @@ -1,7 +1,9 @@ <?php function zot_api_init() { - api_register_func('api/red/version','api_zot_version',false); + api_register_func('api/z/1.0/verify','api_verify', true); + + api_register_func('api/red/version','api_zot_version',false); api_register_func('api/z/1.0/version','api_zot_version',false); api_register_func('api/export/basic','api_export_basic', true); api_register_func('api/red/channel/export/basic','api_export_basic', true); @@ -47,6 +49,17 @@ return; } + function api_verify($type) { + if (api_user() === false) { + logger('no channel'); + return false; + } + $channel = channelx_by_n(api_user()); + // logger('channel: ' . print_r($channel,true)); + + json_return_and_die($channel); + } + function api_zot_version($type) { diff --git a/include/attach.php b/include/attach.php index 80efe0838..c9649a4ce 100644 --- a/include/attach.php +++ b/include/attach.php @@ -11,6 +11,11 @@ * @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; + require_once('include/permissions.php'); require_once('include/security.php'); require_once('include/group.php'); @@ -56,6 +61,7 @@ function z_mime_content_type($filename) { 'jpeg' => 'image/jpeg', 'jpg' => 'image/jpeg', 'gif' => 'image/gif', + 'webp' => 'image/webp', 'bmp' => 'image/bmp', 'ico' => 'image/vnd.microsoft.icon', 'tiff' => 'image/tiff', @@ -616,7 +622,7 @@ function attach_store($channel, $observer_hash, $options = '', $arr = null) { $is_photo = 0; $gis = @getimagesize($src); logger('getimagesize: ' . print_r($gis,true), LOGGER_DATA); - if(($gis) && ($gis[2] === IMAGETYPE_GIF || $gis[2] === IMAGETYPE_JPEG || $gis[2] === IMAGETYPE_PNG)) { + if(($gis) && ($gis[2] === IMAGETYPE_GIF || $gis[2] === IMAGETYPE_JPEG || $gis[2] === IMAGETYPE_PNG || $gis[2] === IMAGETYPE_WEBP)) { $is_photo = 1; if($gis[2] === IMAGETYPE_GIF) $def_extension = '.gif'; @@ -624,6 +630,8 @@ function attach_store($channel, $observer_hash, $options = '', $arr = null) { $def_extension = '.jpg'; if($gis[2] === IMAGETYPE_PNG) $def_extension = '.png'; + if($gis[2] === IMAGETYPE_WEBP) + $def_extension = '.webp'; } // If we know it's a photo, over-ride the type in case the source system could not determine what it was @@ -908,7 +916,7 @@ function attach_store($channel, $observer_hash, $options = '', $arr = null) { ); } - if($is_photo) { + if($is_photo && $r) { $args = array( 'source' => $source, 'visible' => $visible, 'resource_id' => $hash, 'album' => $pathname, 'os_syspath' => $os_basepath . $os_relpath, 'os_path' => $os_path, 'display_path' => $display_path, 'filename' => $filename, 'getimagesize' => $gis, 'directory' => $direct, 'options' => $options ); if($arr['contact_allow']) @@ -942,9 +950,15 @@ function attach_store($channel, $observer_hash, $options = '', $arr = null) { $args['deliver'] = $dosync; $p = photo_upload($channel,$observer,$args); - if($p['success']) { - $ret['body'] = $p['body']; + if($p['success']) + $ret['body'] = $p['body']; + else { + // Attach as ordinary file if image processing is failed + $x = q("UPDATE attach SET is_photo = 0 WHERE hash = '%s'", + dbesc($hash) + ); } + } if(($options !== 'update') && ($remove_when_processed)) @@ -1009,13 +1023,11 @@ function attach_store($channel, $observer_hash, $options = '', $arr = null) { $sync = attach_export_data($channel,$hash); if($sync) - build_sync_packet($channel['channel_id'],array('file' => array($sync))); + Libsync::build_sync_packet($channel['channel_id'],array('file' => array($sync))); } if($notify) { - $cloudPath = z_root() . '/cloud/' . $channel['channel_address'] . '/' . $r['0']['display_path']; - $object = get_file_activity_object($channel['channel_id'], $r['0']['hash'], $cloudPath); - file_activity($channel['channel_id'], $object, $r['0']['allow_cid'], $r['0']['allow_gid'], $r['0']['deny_cid'], $r['0']['deny_gid'], 'post', $notify); + attach_store_item($channel, $observer, $r[0]); } return $ret; @@ -1394,7 +1406,7 @@ function attach_change_permissions($channel_id, $resource, $allow_cid, $allow_gi $data = attach_export_data($channel,$resource); if($data) - build_sync_packet($channel['channel_id'],array('file' => array($data))); + Libsync::build_sync_packet($channel['channel_id'],array('file' => array($data))); } } @@ -1441,9 +1453,6 @@ function attach_delete($channel_id, $resource, $is_photo = 0) { return; } - $url = get_cloud_url($channel_id, $channel_address, $resource); - $object = get_file_activity_object($channel_id, $resource, $url); - // If resource is a directory delete everything in the directory recursive if(intval($r[0]['is_dir'])) { $x = q("SELECT hash, os_storage, is_dir, flags FROM attach WHERE folder = '%s' AND uid = %d", @@ -1487,6 +1496,9 @@ function attach_delete($channel_id, $resource, $is_photo = 0) { if($r[0]['is_photo']) { attach_drop_photo($channel_id,$resource); } + else { + attach_drop_item($channel_id,$resource); + } // update the parent folder's lastmodified timestamp @@ -1506,8 +1518,6 @@ function attach_delete($channel_id, $resource, $is_photo = 0) { */ call_hooks('attach_delete', $arr); - file_activity($channel_id, $object, $object['allow_cid'], $object['allow_gid'], $object['deny_cid'], $object['deny_gid'], 'update', true); - return; } @@ -1542,6 +1552,21 @@ function attach_drop_photo($channel_id,$resource) { } +function attach_drop_item($channel_id,$resource) { + + $x = q("select id, item_hidden from item where resource_id = '%s' and resource_type = 'attach' and uid = %d and item_deleted = 0", + dbesc($resource), + intval($channel_id) + ); + + 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); + } + +} + /** * @brief Returns path to file in cloud/. @@ -1743,6 +1768,7 @@ function pipe_streams($in, $out, $bufsize = 16384) { * @param string $verb * @param boolean $notify */ +/* function file_activity($channel_id, $object, $allow_cid, $allow_gid, $deny_cid, $deny_gid, $verb, $notify) { require_once('include/items.php'); @@ -1791,7 +1817,7 @@ function file_activity($channel_id, $object, $allow_cid, $allow_gid, $deny_cid, $uuid = item_message_id(); $mid = z_root() . '/item/' . $uuid; - $objtype = ACTIVITY_OBJ_FILE; + $objtype = 'ACTIVITY_OBJ_FILE'; $arr = array(); $arr['aid'] = get_account_id(); @@ -1890,6 +1916,148 @@ function file_activity($channel_id, $object, $allow_cid, $allow_gid, $deny_cid, return; } +*/ + + +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']), + dbesc($file) + ); + + if(! $r) + return; + + $file = $r[0]; + + } + + $path = z_root() . '/cloud/' . $channel['channel_address'] . '/' . $file['display_path']; + + $r = q("SELECT * FROM item WHERE resource_id = '%s' AND resource_type = 'attach' and uid = %d LIMIT 1", + dbesc($file['hash']), + intval($channel['channel_id']) + ); + + if($r) { + + // At the moment only file permission edits are possible. + // Since we do not support permission editing on posts yet, + // we will delete the item and create a new one with the new permissions for now. + + if($r[0]['allow_cid'] === $file['allow_cid'] && $r[0]['allow_gid'] === $file['allow_gid'] && $r[0]['deny_cid'] === $file['deny_cid'] && $r[0]['deny_gid'] === $file['deny_gid']) { + + /* once possible, other edits (eg rename) can be done here. + + q("UPDATE item SET title = '%s' WHERE id = %d AND uid = %d", + dbesc($file['filename']) + ); + + $meta = [ + 'name' => $file['filename'], + 'type' => $file['filetype'], + 'size' => $file['filesize'], + 'revision' => $file['revision'], + 'size' => $file['filesize'], + 'created' => $file['created'], + 'edited' => $file['edited'], + 'path' => $path + ]; + + set_iconfig($r[0], 'attach', 'meta' , $meta, true); + + $post = item_store($arr); + + $item_id = $post['item_id']; + + if($item_id) { + Master::Summon(['Notifier', 'activity', $item_id]); + } + + */ + + return; + + } + + $stage = (($r[0]['item_hidden']) ? DROPITEM_NORMAL : DROPITEM_PHASE1); + $interactive = (($r[0]['item_hidden']) ? false : true); + drop_item($r[0]['id'], $interactive, $stage); + + } + + $filetype_parts = explode('/', $file['filetype']); + + switch($filetype_parts[0]) { + case 'image': + $type = 'Image'; + break; + case 'audio': + $type = 'Audio'; + break; + case 'video': + $type = 'Video'; + break; + default: + $type = 'Document'; + } + + $resource_id = $file['hash']; + $uuid = new_uuid(); + + $mid = z_root() . '/item/' . $uuid; + + $arr = []; // Initialize the array of parameters for the post + $arr['aid'] = $channel['channel_account_id']; + $arr['uuid'] = $uuid; + $arr['uid'] = $channel['channel_id']; + $arr['mid'] = $mid; + $arr['parent_mid'] = $mid; + $arr['resource_type'] = 'attach'; + $arr['resource_id'] = $resource_id; + $arr['owner_xchan'] = $channel['channel_hash']; + $arr['author_xchan'] = $observer['xchan_hash']; + $arr['title'] = $file['filename']; + $arr['allow_cid'] = $file['allow_cid']; + $arr['allow_gid'] = $file['allow_gid']; + $arr['deny_cid'] = $file['deny_cid']; + $arr['deny_gid'] = $file['deny_gid']; + $arr['item_wall'] = 1; + $arr['item_origin'] = 1; + $arr['item_thread_top'] = 1; + $arr['item_private'] = (($file['allow_cid'] || $file['allow_gid'] || $file['deny_cid'] || $file['deny_gid']) ? 1 : 0); + $arr['verb'] = ACTIVITY_CREATE; + $arr['obj_type'] = $type; + $arr['title'] = $file['filename']; + $body_str = sprintf(t('%s shared a %s with you'), '[zrl=' . $observer['xchan_url'] . ']' . $observer['xchan_name'] . '[/zrl]', '[zrl=' . $path . ']' . t('file') . '[/zrl]'); + $arr['body'] = $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 + ]; + + set_iconfig($arr, 'attach', 'meta' , $meta, true); + + $post = item_store($arr); + + $item_id = $post['item_id']; + + if($item_id) { + Master::Summon(['Notifier', 'activity', $item_id]); + } + +} + /** * @brief Create file activity object. @@ -1906,17 +2074,28 @@ function get_file_activity_object($channel_id, $hash, $url) { dbesc($hash) ); - $url = rawurlencode($url); - - $links = array(); - $links[] = array( + $links = []; + $links[] = [ 'rel' => 'alternate', - 'type' => 'text/html', + 'type' => $x[0]['filetype'], 'href' => $url - ); + ]; + + $filetype_parts = explode('/', $x[0]['filetype']); + + switch($filetype_parts[0]) { + case 'audio': + $type = 'Audio'; + break; + case 'video': + $type = 'Video'; + break; + default: + $type = 'Document'; + } $object = array( - 'type' => ACTIVITY_OBJ_FILE, + 'type' => $type, 'title' => $x[0]['filename'], 'id' => $url, 'link' => $links, @@ -2628,6 +2807,12 @@ function save_chunk($channel,$start,$end,$len) { $new_path = $new_base . '/' . $_FILES['files']['name']; + if(file_exists($new_path) && intval($start) === 0) { + $result['partial'] = true; + $result['length'] = intval(filesize($new_path)); + return $result; + } + if(! file_exists($new_path)) { rename($tmp_path,$new_path); } @@ -2654,5 +2839,3 @@ function save_chunk($channel,$start,$end,$len) { $result['length'] = intval(filesize($new_path)); return $result; } - - diff --git a/include/auth.php b/include/auth.php index b952754fd..8eeb077b5 100644 --- a/include/auth.php +++ b/include/auth.php @@ -9,6 +9,8 @@ * Also provides a function for OpenID identiy matching. */ +use Zotlabs\Lib\Libzot; + require_once('include/api_auth.php'); require_once('include/security.php'); @@ -224,12 +226,13 @@ if((isset($_SESSION)) && (x($_SESSION, 'authenticated')) && $r = array(atoken_xchan($y[0])); } else { - $r = q("select * from xchan left join hubloc on xchan_hash = hubloc_hash where xchan_hash = '%s' limit 1", + $r = q("select * from xchan left join hubloc on xchan_hash = hubloc_hash where xchan_hash = '%s' and hubloc_deleted = 0", dbesc($_SESSION['visitor_id']) ); } if($r) { - App::set_observer($r[0]); + $r = Libzot::zot_record_preferred($r); + App::set_observer($r); } else { unset($_SESSION['visitor_id']); diff --git a/include/bbcode.php b/include/bbcode.php index c7dea53c5..d79429719 100644 --- a/include/bbcode.php +++ b/include/bbcode.php @@ -218,7 +218,7 @@ function bb_replace_images($body, $images) { // We're depending on the property of 'foreach' (specified on the PHP website) that // it loops over the array starting from the first element and going sequentially // to the last element - $newbody = str_replace('[$#saved_image' . $cnt . '#$]', '<img src="' . $image .'" alt="' . t('Image/photo') . '" />', $newbody); + $newbody = str_replace('[$#saved_image' . $cnt . '#$]', '<img src="' . $image .'" alt="' . t('Image/photo') . '" loading="eager" />', $newbody); $cnt++; } // logger('replace_images: ' . $newbody); @@ -233,7 +233,7 @@ function bb_replace_images($body, $images) { */ function bb_parse_crypt($match) { - $matches = array(); + $matches = []; $attributes = $match[1]; $algorithm = ""; @@ -257,7 +257,17 @@ function bb_parse_crypt($match) { $x = random_string(); - $Text = '<br /><div id="' . $x . '"><img src="' . z_root() . '/images/lock_icon.gif" onclick="red_decrypt(\'' . $algorithm . '\',\'' . $hint . '\',\'' . $match[2] . '\',\'#' . $x . '\');" alt="' . t('Encrypted content') . '" title="' . t('Encrypted content') . '" /></div><br />'; + $f = 'hz_decrypt'; + + //legacy cryptojs support + if(plugin_is_installed('cryptojs')) { + $f = ((in_array($algorithm, ['AES-128-CCM', 'rot13', 'triple-rot13'])) ? 'hz_decrypt' : 'red_decrypt'); + } + + $onclick = 'onclick="' . $f . '(\'' . $algorithm . '\',\'' . $hint . '\',\'' . $match[2] . '\',\'#' . $x . '\');"'; + $label = t('Encrypted content'); + + $Text = '<br /><div id="' . $x . '"><img class="cursor-pointer" src="' . z_root() . '/images/lock_icon.svg" ' . $onclick . ' alt="' . $label . '" title="' . $label . '" /></div><br />'; return $Text; } @@ -319,6 +329,139 @@ function translate_design_element($type) { return $ret; } +function bb_format_attachdata($body) { + + $data = getAttachmentData($body); + + if($data) { + $txt = ''; + if($data['url'] && $data['title']) { + $txt .= "\n\n" . '[url=' . $data['url'] . ']' . $data['title'] . '[/url]'; + } + else { + if($data['url']) { + $txt .= "\n\n" . $data['url']; + } + if($data['title']) { + $txt .= "\n\n" . $data['title']; + } + } + if($data['preview']) { + $txt .= "\n\n" . '[img]' . $data['preview'] . '[/img]'; + } + if($data['image']) { + $txt .= "\n\n" . '[img]' . $data['image'] . '[/img]'; + } + + + $txt .= "\n\n" . $data['text']; + return preg_replace('/\[attachment(.*?)\](.*?)\[\/attachment\]/ism',$txt,$body); + } + + return $body; +} + +function getAttachmentData($body) { + + $data = []; + + if (! preg_match("/\[attachment(.*?)\](.*?)\[\/attachment\]/ism", $body, $match)) { + return null; + } + + $attributes = $match[1]; + + $data["text"] = trim($match[2]); + + $type = ""; + preg_match("/type='(.*?)'/ism", $attributes, $matches); + + if (x($matches, 1)) { + $type = strtolower($matches[1]); + } + + preg_match('/type=\"\;(.*?)\"\;/ism', $attributes, $matches); + if (x($matches, 1)) { + $type = strtolower($matches[1]); + } + + if ($type == "") { + return []; + } + + if (!in_array($type, ["link", "audio", "photo", "video"])) { + return []; + } + + if ($type != "") { + $data["type"] = $type; + } + $url = ""; + preg_match("/url='(.*?)'/ism", $attributes, $matches); + if (x($matches, 1)) { + $url = $matches[1]; + } + + preg_match('/url=\"\;(.*?)\"\;/ism', $attributes, $matches); + if (x($matches, 1)) { + $url = $matches[1]; + } + + if ($url != "") { + $data["url"] = html_entity_decode($url, ENT_QUOTES, 'UTF-8'); + } + + $title = ""; + preg_match("/title='(.*?)'/ism", $attributes, $matches); + if (x($matches, 1)) { + $title = $matches[1]; + } + + preg_match('/title=\"\;(.*?)\"\;/ism', $attributes, $matches); + if (x($matches, 1)) { + $title = $matches[1]; + } + if ($title != "") { + $title = html_entity_decode($title, ENT_QUOTES, 'UTF-8'); + $title = str_replace(["[", "]"], ["[", "]"], $title); + $data["title"] = $title; + } + + $image = ""; + preg_match("/image='(.*?)'/ism", $attributes, $matches); + if (x($matches, 1)) { + $image = $matches[1]; + } + + preg_match('/image=\"\;(.*?)\"\;/ism', $attributes, $matches); + if (x($matches, 1)) { + $image = $matches[1]; + } + + if ($image != "") { + $data["image"] = html_entity_decode($image, ENT_QUOTES, 'UTF-8'); + } + + $preview = ""; + preg_match("/preview='(.*?)'/ism", $attributes, $matches); + if (x($matches, 1)) { + $preview = $matches[1]; + } + + preg_match('/preview=\"\;(.*?)\"\;/ism', $attributes, $matches); + if (x($matches, 1)) { + $preview = $matches[1]; + } + if ($preview != "") { + $data["preview"] = html_entity_decode($preview, ENT_QUOTES, 'UTF-8'); + } + + $data["description"] = trim($match[3]); + + $data["after"] = trim($match[4]); + + return $data; +} function bb_ShareAttributes($match) { @@ -370,7 +513,7 @@ function bb_ShareAttributes($match) { $headline = '<div id="shared_container_' . $rnd . '" class="shared_container"> <div id="shared_header_' . $rnd . '" class="shared_header">'; if ($avatar != "") - $headline .= '<a href="' . (($auth) ? zid($profile) : $profile) . '" ><img src="' . $avatar . '" alt="' . $author . '" height="32" width="32" /></a>'; + $headline .= '<a href="' . (($auth) ? zid($profile) : $profile) . '" ><img src="' . $avatar . '" alt="' . $author . '" height="32" width="32" loading="lazy" /></a>'; if(strpos($link,'/cards/')) $type = t('card'); @@ -382,7 +525,7 @@ function bb_ShareAttributes($match) { // Bob Smith wrote the following post 2 hours ago $fmt = sprintf( t('%1$s wrote the following %2$s %3$s'), - '<a href="' . (($auth) ? zid($profile) : $profile) . '" >' . $author . '</a>', + '<a href="' . (($auth) ? zid($profile) : $profile) . '" ><bdi>' . $author . '</bdi></a>', '<a href="' . (($auth) ? zid($link) : $link) . '" >' . $type . '</a>', $reldate ); @@ -655,6 +798,109 @@ function bb_observer($Text) { return $Text; } +function bb_imgoptions($match) { + + // $Text = preg_replace_callback("/\[([zi])mg([ \=])(.*?)\](.*?)\[\/[zi]mg\]/ism",'bb_imgoptions',$Text); + // alt text cannot contain ']' + + // [img|zmg=wwwxhhh float=left|right alt=alt text]url[/img|zmg] + + $local_match = null; + $width = 0; + $float = false; + $alt = false; + + $style = EMPTY_STR; + + $attributes = $match[3]; + + $x = preg_match("/alt='(.*?)'/ism", $attributes, $matches); + if ($x) { + $alt = $matches[1]; + } + + $x = preg_match("/alt=\"\;(.*?)\"\;/ism", $attributes, $matches); + if ($x) { + $alt = $matches[1]; + } + + $x = preg_match("/width='(.*?)'/ism", $attributes, $matches); + if ($x) { + $width = $matches[1]; + } + + $x = preg_match("/width=\"\;(.*?)\"\;/ism", $attributes, $matches); + if ($x) { + $width = $matches[1]; + } + + $x = preg_match("/height='(.*?)'/ism", $attributes, $matches); + if ($x) { + $height = $matches[1]; + } + + $x = preg_match("/height=\"\;(.*?)\"\;/ism", $attributes, $matches); + if ($x) { + $height = $matches[1]; + } + + $x = preg_match("/style='(.*?)'/ism", $attributes, $matches); + if ($x) { + $style = $matches[1]; + } + + $x = preg_match("/style=\"\;(.*?)\"\;/ism", $attributes, $matches); + if ($x) { + $style = $matches[1]; + } + + // legacy img options + + if ($match[2] === '=') { + // pull out (optional) legacy size declarations first + if (preg_match("/([0-9]*)x([0-9]*)/ism",$match[3],$local_match)) { + $width = intval($local_match[1]); + } + $match[3] = substr($match[3],strpos($match[3],' ')); + } + + // then (optional) legacy float specifiers + if ($n = strpos($match[3],'float=left') !== false) { + $float = 'left'; + $match[3] = substr($match[3],$n + 10); + } + if ($n = strpos($match[3],'float=right') !== false) { + $float = 'right'; + $match[3] = substr($match[3],$n + 11); + } + + // finally alt text which extends to the close of the tag + if ((! $alt) && ($n = strpos($match[3],'alt=') !== false)) { + $alt = substr($match[3],$n + 4); + } + + // now assemble the resulting img tag from these components + + $output = '<img ' . (($match[1] === 'z') ? 'class="zrl" loading="eager"' : '') . ' '; + + if ($width) { + $style .= 'width: 100%; max-width: ' . $width . 'px; '; + } + else { + $style .= 'max-width: 100%; '; + } + if ($float) { + $style .= 'float: ' . $float . '; '; + } + + $output .= (($style) ? 'style="' . $style . '" ' : '') . 'alt="' . htmlentities(($alt) ? $alt : t('Image/photo'),ENT_COMPAT,'UTF-8') . '" '; + + $output .= 'src="' . $match[4] . '" >'; + + return $output; + +} + function bb_code_protect($s) { return 'b64.^9e%.' . base64_encode($s) . '.b64.$9e%'; } @@ -832,6 +1078,8 @@ function bbcode($Text, $options = []) { $Text = preg_replace_callback("/\[pre\](.*?)\[\/pre\]/ism", 'bb_spacefy',$Text); } + $Text = bb_format_attachdata($Text); + // If we find any event code, turn it into an event. // After we're finished processing the bbcode we'll // replace all of the event code with a reformatted version. @@ -1111,6 +1359,12 @@ function bbcode($Text, $options = []) { if (strpos($Text,'[/footer]') !== false) { $Text = preg_replace("(\[footer\](.*?)\[\/footer\])ism", "<div class=\"wall-item-footer\">$1</div>", $Text); } + + // Check for bdi + if (strpos($Text,'[/bdi]') !== false) { + $Text = preg_replace("(\[bdi\](.*?)\[\/bdi\])ism", "<bdi>$1</bdi>", $Text); + } + // Check for list text $Text = preg_replace("/<br \/>\[\*\]/ism",'[*]',$Text); @@ -1229,47 +1483,22 @@ function bbcode($Text, $options = []) { // Images // [img]pathtoimage[/img] if (strpos($Text,'[/img]') !== false) { - $Text = preg_replace("/\[img\](.*?)\[\/img\]/ism", '<img style="max-width: 100%;" src="$1" alt="' . t('Image/photo') . '" />', $Text); - } - if (strpos($Text,'[/zmg]') !== false) { - $Text = preg_replace("/\[zmg\](.*?)\[\/zmg\]/ism", '<img class="zrl" style="max-width: 100%;" src="$1" alt="' . t('Image/photo') . '" />', $Text); - } - - // [img float={left, right}]pathtoimage[/img] - if (strpos($Text,'[/img]') !== false) { - $Text = preg_replace("/\[img float=left\](.*?)\[\/img\]/ism", '<img src="$1" style="max-width: 100%; float: left;" alt="' . t('Image/photo') . '" />', $Text); + $Text = preg_replace("/\[img\](.*?)\[\/img\]/ism", '<img style="max-width: 100%;" src="$1" alt="' . t('Image/photo') . '" loading="eager" />', $Text); } + // [img=pathtoimage]image description[/img] if (strpos($Text,'[/img]') !== false) { - $Text = preg_replace("/\[img float=right\](.*?)\[\/img\]/ism", '<img src="$1" style="max-width: 100%; float: right;" alt="' . t('Image/photo') . '" />', $Text); - } - if (strpos($Text,'[/zmg]') !== false) { - $Text = preg_replace("/\[zmg float=left\](.*?)\[\/zmg\]/ism", '<img class="zrl" src="$1" style="max-width: 100%; float: left;" alt="' . t('Image/photo') . '" />', $Text); + $Text = preg_replace("/\[img=http(.*?)\](.*?)\[\/img\]/ism", '<img style="max-width: 100%;" src="http$1" alt="$2" title="$2" loading="eager" />', $Text); } + // [zmg]pathtoimage[/zmg] if (strpos($Text,'[/zmg]') !== false) { - $Text = preg_replace("/\[zmg float=right\](.*?)\[\/zmg\]/ism", '<img class="zrl" src="$1" style="max-width: 100%; float: right;" alt="' . t('Image/photo') . '" />', $Text); - } - - // [img=widthxheight]pathtoimage[/img] - if (strpos($Text,'[/img]') !== false) { - $Text = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", '<img src="$3" style="width: 100%; max-width: $1px;" alt="' . t('Image/photo') . '" />', $Text); + $Text = preg_replace("/\[zmg\](.*?)\[\/zmg\]/ism", '<img class="zrl" style="max-width: 100%;" src="$1" alt="' . t('Image/photo') . '" loading="eager" />', $Text); } + // [zmg=pathtoimage]image description[/zmg] if (strpos($Text,'[/zmg]') !== false) { - $Text = preg_replace("/\[zmg\=([0-9]*)x([0-9]*)\](.*?)\[\/zmg\]/ism", '<img class="zrl" src="$3" style="width: 100%; max-width: $1px;" alt="' . t('Image/photo') . '" />', $Text); + $Text = preg_replace("/\[zmg=http(.*?)\](.*?)\[\/zmg\]/ism", '<img class="zrl" style="max-width: 100%;" src="http$1" alt="$2" title="$2" loading="eager" />', $Text); } - // [img=widthxheight float={left, right}]pathtoimage[/img] - if (strpos($Text,'[/img]') !== false) { - $Text = preg_replace("/\[img\=([0-9]*)x([0-9]*) float=left\](.*?)\[\/img\]/ism", '<img src="$3" style="width: 100%; max-width: $1px; float: left;" alt="' . t('Image/photo') . '" />', $Text); - } - if (strpos($Text,'[/img]') !== false) { - $Text = preg_replace("/\[img\=([0-9]*)x([0-9]*) float=right\](.*?)\[\/img\]/ism", '<img src="$3" style="width: 100%; max-width: $1px; float: right;" alt="' . t('Image/photo') . '" />', $Text); - } - if (strpos($Text,'[/zmg]') !== false) { - $Text = preg_replace("/\[zmg\=([0-9]*)x([0-9]*) float=left\](.*?)\[\/zmg\]/ism", '<img class="zrl" src="$3" style="width: 100%; max-width: $1px; float: left;" alt="' . t('Image/photo') . '" />', $Text); - } - if (strpos($Text,'[/zmg]') !== false) { - $Text = preg_replace("/\[zmg\=([0-9]*)x([0-9]*) float=right\](.*?)\[\/zmg\]/ism", '<img class="zrl" src="$3" style="width: 100%; max-width: $1px; float: right;" alt="' . t('Image/photo') . '" />', $Text); - } + $Text = preg_replace_callback("/\[([zi])mg([ \=])(.*?)\](.*?)\[\/[zi]mg\]/ism",'bb_imgoptions',$Text); // style (sanitized) if (strpos($Text,'[/style]') !== false) { @@ -1279,7 +1508,7 @@ function bbcode($Text, $options = []) { // crypt if (strpos($Text,'[/crypt]') !== false) { $x = random_string(); - $Text = preg_replace("/\[crypt\](.*?)\[\/crypt\]/ism",'<br /><div id="' . $x . '"><img src="' .z_root() . '/images/lock_icon.gif" onclick="red_decrypt(\'rot13\',\'\',\'$1\',\'#' . $x . '\');" alt="' . t('Encrypted content') . '" title="' . t('Encrypted content') . '" /><br /></div>', $Text); + $Text = preg_replace("/\[crypt\](.*?)\[\/crypt\]/ism",'<br /><div id="' . $x . '"><img class="cursor-pointer" src="' .z_root() . '/images/lock_icon.svg" onclick="red_decrypt(\'rot13\',\'\',\'$1\',\'#' . $x . '\');" alt="' . t('Encrypted content') . '" title="' . t('Encrypted content') . '" /><br /></div>', $Text); $Text = preg_replace_callback("/\[crypt (.*?)\](.*?)\[\/crypt\]/ism", 'bb_parse_crypt', $Text); } diff --git a/include/cdav.php b/include/cdav.php new file mode 100644 index 000000000..ef248a9fe --- /dev/null +++ b/include/cdav.php @@ -0,0 +1,186 @@ +<?php + +/** + * @brief Process CardDAV card + * + * @param array $f fields + * @param obj $vcard SabreDAV object + * @param bool $edit update card + * + */ + +function process_cdav_card($f, &$vcard, $edit = false) { + + if($f['org']) + $vcard->ORG = $f['org']; + else + if($edit) + unset($vcard->ORG); + + + if($f['title']) + $vcard->TITLE = $f['title']; + else + if($edit) + unset($vcard->TITLE); + + if($edit) + unset($vcard->TEL); + if($f['tel']) { + $i = 0; + foreach($f['tel'] as $item) { + if($item) { + $vcard->add('TEL', $item, ['type' => $f['tel_type'][$i]]); + } + $i++; + } + } + + if($edit) + unset($vcard->EMAIL); + if($f['email']) { + $i = 0; + foreach($f['email'] as $item) { + if($item) { + $vcard->add('EMAIL', $item, ['type' => $f['email_type'][$i]]); + } + $i++; + } + } + + if($edit) + unset($vcard->IMPP); + if($f['impp']) { + $i = 0; + foreach($f['impp'] as $item) { + if($item) { + $vcard->add('IMPP', $item, ['type' => $f['impp_type'][$i]]); + } + $i++; + } + } + + if($edit) + unset($vcard->URL); + if($f['url']) { + $i = 0; + foreach($f['url'] as $item) { + if($item) { + $vcard->add('URL', $item, ['type' => $f['url_type'][$i]]); + } + $i++; + } + } + + if($edit) + unset($vcard->ADR); + if($f['adr']) { + $i = 0; + foreach($f['adr'] as $item) { + if($item) { + $vcard->add('ADR', $item, ['type' => $f['adr_type'][$i]]); + } + $i++; + } + } + + if($f['note']) { + $vcard->NOTE = $f['note']; + } + else + if($edit) + unset($vcard->NOTE); +} + + +/** + * @brief Import CardDAV or CalDAV card + * + * @param mixed $id card id + * @param str $ext card extension + * @param str $table name + * @param str $column name + * @param obj $objects + * @param str $profile + * @param obj $backend + * @param array $ids + * @param bool $notice + * + */ + +function import_cdav_card($id, $ext, $table, $column, $objects, $profile, $backend, &$ids, $notice = false) { + + $i = 0; + $newid = (count($ids) ? false : true); + + while ($object = $objects->getNext()) { + + if($_REQUEST['a_upload']) + $object = $object->convert(\Sabre\VObject\Document::VCARD40); + + $ret = $object->validate($profile & \Sabre\VObject\Node::REPAIR); + + //level 3 Means that the document is invalid, + //level 2 means a warning. A warning means it's valid but it could cause interopability issues, + //level 1 means that there was a problem earlier, but the problem was automatically repaired. + + if($ret[0]['level'] < 3) { + + if($newid) { + do { + $duplicate = false; + $objectUri = random_string(40) . '.' . $ext; + + $r = q("SELECT uri FROM $table WHERE $column = %d AND uri = '%s' LIMIT 1", + dbesc($id), + dbesc($objectUri) + ); + if (count($r)) + $duplicate = true; + } while ($duplicate == true); + $ids[$i] = $objectUri; + } + else + $objectUri = $ids[$i]; + + $i++; + + if($ext == 'ics') + $backend->createCalendarObject($id, $objectUri, $object->serialize()); + + if($ext == 'vcf') + $backend->createCard($id, $objectUri, $object->serialize()); + } + else { + if($notice && $ext == 'ics') { + notice( + '<strong>' . t('INVALID EVENT DISMISSED!') . '</strong>' . EOL . + '<strong>' . t('Summary: ') . '</strong>' . (($object->VEVENT->SUMMARY) ? $object->VEVENT->SUMMARY : t('Unknown')) . EOL . + '<strong>' . t('Date: ') . '</strong>' . (($object->VEVENT->DTSTART) ? $object->VEVENT->DTSTART : t('Unknown')) . EOL . + '<strong>' . t('Reason: ') . '</strong>' . $ret[0]['message'] . EOL + ); + } + + if($notice && $exp == 'vcf') { + notice( + '<strong>' . t('INVALID CARD DISMISSED!') . '</strong>' . EOL . + '<strong>' . t('Name: ') . '</strong>' . (($object->FN) ? $object->FN : t('Unknown')) . EOL . + '<strong>' . t('Reason: ') . '</strong>' . $ret[0]['message'] . EOL + ); + } + } + } +} + + +function get_cdav_id($principaluri, $uri, $table) { + + $r = q("SELECT * FROM $table WHERE principaluri = '%s' AND uri = '%s' LIMIT 1", + dbesc($principaluri), + dbesc($uri) + ); + if(! $r) + return false; + + return $r[0]; +} diff --git a/include/channel.php b/include/channel.php index 29835eac6..e2be4d8a8 100644 --- a/include/channel.php +++ b/include/channel.php @@ -4,6 +4,7 @@ * @brief Channel related functions. */ + use Zotlabs\Access\PermissionRoles; use Zotlabs\Access\PermissionLimits; use Zotlabs\Access\Permissions; @@ -11,6 +12,8 @@ use Zotlabs\Daemon\Master; use Zotlabs\Lib\System; use Zotlabs\Render\Comanche; use Zotlabs\Lib\Libzot; +use Zotlabs\Lib\Connect; +use Zotlabs\Lib\Libsync; require_once('include/zot.php'); require_once('include/crypto.php'); @@ -126,10 +129,10 @@ function create_sys_channel() { * @return array|boolean */ function get_sys_channel() { - $r = q("select * from channel left join xchan on channel_hash = xchan_hash where channel_system = 1 limit 1"); + $r = q("select * from channel left join xchan on channel_hash = xchan_hash where channel_system = 1"); if ($r) - return $r[0]; + return Libzot::zot_record_preferred($r, 'xchan_network'); return false; } @@ -228,12 +231,16 @@ function create_identity($arr) { return $ret; } - $guid = zot_new_uid($nick); + $guid = Libzot::new_uid($nick); $key = new_keypair(4096); - $sig = base64url_encode(rsa_sign($guid,$key['prvkey'])); - $hash = make_xchan_hash($guid,$sig); - $zhash = Libzot::make_xchan_hash($guid,$key['pubkey']); + // legacy zot + $zsig = base64url_encode(rsa_sign($guid,$key['prvkey'])); + $zhash = make_xchan_hash($guid,$zsig); + + // zot6 + $sig = Libzot::sign($guid,$key['prvkey']); + $hash = Libzot::make_xchan_hash($guid,$key['pubkey']); // Force a few things on the short term until we can provide a theme or app with choice @@ -332,8 +339,9 @@ function create_identity($arr) { $r = hubloc_store_lowlevel( [ 'hubloc_guid' => $guid, - 'hubloc_guid_sig' => $sig, - 'hubloc_hash' => $hash, + 'hubloc_guid_sig' => $zsig, + 'hubloc_hash' => $zhash, + 'hubloc_id_url' => channel_url($ret['channel']), 'hubloc_addr' => channel_reddress($ret['channel']), 'hubloc_primary' => intval($primary), 'hubloc_url' => z_root(), @@ -346,18 +354,18 @@ function create_identity($arr) { ] ); if(! $r) - logger('Unable to store hub location'); + logger('Unable to store hub location (zot)'); $r = hubloc_store_lowlevel( [ 'hubloc_guid' => $guid, - 'hubloc_guid_sig' => 'sha256.' . $sig, - 'hubloc_hash' => $zhash, - 'hubloc_id_url' => channel_url($ret['channel']), + 'hubloc_guid_sig' => $sig, + 'hubloc_hash' => $hash, + 'hubloc_id_url' => channel_url($ret['channel']), 'hubloc_addr' => channel_reddress($ret['channel']), 'hubloc_primary' => intval($primary), 'hubloc_url' => z_root(), - 'hubloc_url_sig' => 'sha256.' . base64url_encode(rsa_sign(z_root(),$ret['channel']['channel_prvkey'])), + '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_host' => App::get_hostname(), 'hubloc_callback' => z_root() . '/zot', @@ -367,16 +375,16 @@ function create_identity($arr) { ] ); if(! $r) - logger('Unable to store hub location'); + logger('Unable to store hub location (zot6)'); $newuid = $ret['channel']['channel_id']; $r = xchan_store_lowlevel( [ - 'xchan_hash' => $hash, + 'xchan_hash' => $zhash, 'xchan_guid' => $guid, - 'xchan_guid_sig' => $sig, + 'xchan_guid_sig' => $zsig, 'xchan_pubkey' => $key['pubkey'], 'xchan_photo_mimetype' => (($photo_type) ? $photo_type : 'image/png'), 'xchan_photo_l' => z_root() . "/photo/profile/l/{$newuid}", @@ -393,12 +401,14 @@ function create_identity($arr) { 'xchan_system' => $system ] ); + if(! $r) + logger('Unable to store xchan (zot)'); $r = xchan_store_lowlevel( [ - 'xchan_hash' => $zhash, + 'xchan_hash' => $hash, 'xchan_guid' => $guid, - 'xchan_guid_sig' => 'sha256.' . $sig, + 'xchan_guid_sig' => $sig, 'xchan_pubkey' => $key['pubkey'], 'xchan_photo_mimetype' => (($photo_type) ? $photo_type : 'image/png'), 'xchan_photo_l' => z_root() . "/photo/profile/l/{$newuid}", @@ -415,6 +425,8 @@ function create_identity($arr) { 'xchan_system' => $system ] ); + if(! $r) + logger('Unable to store xchan (zot6)'); @@ -521,13 +533,22 @@ function create_identity($arr) { $accts = get_config('system','auto_follow'); if(($accts) && (! $total_identities)) { - require_once('include/follow.php'); if(! is_array($accts)) $accts = array($accts); foreach($accts as $acct) { - if(trim($acct)) - new_contact($newuid,trim($acct),$ret['channel'],false); + $acct = trim($acct); + if($acct) { + $f = connect_and_sync($ret['channel'], $acct); + if($f['success']) { + $can_view_stream = their_perms_contains($ret['channel']['channel_id'],$f['abook']['abook_xchan'],'view_stream'); + + // If we can view their stream, pull in some posts + if(($can_view_stream) || ($f['abook']['xchan_network'] === 'rss')) { + Master::Summon([ 'Onepoll',$f['abook']['abook_id'] ]); + } + } + } } } @@ -539,6 +560,7 @@ function create_identity($arr) { call_hooks('create_identity', $newuid); Master::Summon(array('Directory', $ret['channel']['channel_id'])); + } $ret['success'] = true; @@ -546,6 +568,35 @@ function create_identity($arr) { } +function connect_and_sync($channel,$address, $sub_channel = false) { + + if((! $channel) || (! $address)) { + return false; + } + + $f = Connect::connect($channel,$address, $sub_channel); + if($f['success']) { + $clone = []; + foreach($f['abook'] as $k => $v) { + if(strpos($k,'abook_') === 0) { + $clone[$k] = $v; + } + } + unset($clone['abook_id']); + unset($clone['abook_account']); + unset($clone['abook_channel']); + + $abconfig = load_abconfig($channel['channel_id'],$clone['abook_xchan']); + if($abconfig) { + $clone['abconfig'] = $abconfig; + } + + Libsync::build_sync_packet($channel['channel_id'], [ 'abook' => [ $clone ] ], true); + return $f; + } + return false; +} + function change_channel_keys($channel) { $ret = array('success' => false); @@ -624,7 +675,7 @@ function change_channel_keys($channel) { } } - build_sync_packet($channel['channel_id'], [ 'keychange' => $stored ]); + Libsync::build_sync_packet($channel['channel_id'], [ 'keychange' => $stored ]); $a = q("select * from abook where abook_xchan = '%s' and abook_self = 1", dbesc($stored['old_hash']) @@ -796,7 +847,7 @@ function get_default_export_sections() { * @return array * See function for details */ -function identity_basic_export($channel_id, $sections = null) { +function identity_basic_export($channel_id, $sections = null, $zap_compat = false) { /* * basic channel export @@ -812,12 +863,16 @@ function identity_basic_export($channel_id, $sections = null) { // with a non-standard platform and version. $ret['compatibility'] = [ - 'project' => PLATFORM_NAME, + 'project' => (($zap_compat) ? 'zap' : PLATFORM_NAME), 'version' => STD_VERSION, 'database' => DB_UPDATE_VERSION, 'server_role' => System::get_server_role() ]; + if ($zap_compat) { + $ret['compatibility']['codebase'] = 'zap'; + } + /* * Process channel information regardless of it is one of the sections desired * because we need the channel relocation information in all export files/streams. @@ -834,6 +889,13 @@ function identity_basic_export($channel_id, $sections = null) { unset($ret['channel']['channel_password']); unset($ret['channel']['channel_salt']); } + if ($zap_compat) { + $channel['channel_guid_sig'] = 'sha256.' . $channel['channel_guid_sig']; + $channel['channel_hash'] = $channel['channel_portable_id']; + unset($channel['channel_portable_id']); + } + + } if(in_array('channel',$sections) || in_array('profile',$sections)) { @@ -853,7 +915,7 @@ function identity_basic_export($channel_id, $sections = null) { $ret['photo'] = [ 'type' => $r[0]['mimetype'], 'data' => (($r[0]['os_storage']) - ? base64url_encode(file_get_contents($r[0]['content'])) : base64url_encode($r[0]['content'])) + ? base64url_encode(file_get_contents($r[0]['content'])) : base64url_encode(dbunescbin($r[0]['content']))) ]; } } @@ -867,11 +929,38 @@ function identity_basic_export($channel_id, $sections = null) { $ret['abook'] = $r; for($x = 0; $x < count($ret['abook']); $x ++) { + $xchans[] = $ret['abook'][$x]['abook_xchan']; + $my_perms = []; + $their_perms = []; + $newconfig = []; $abconfig = load_abconfig($channel_id,$ret['abook'][$x]['abook_xchan']); - if($abconfig) - $ret['abook'][$x]['abconfig'] = $abconfig; + if($abconfig) { + foreach ($abconfig as $abc) { + + if ($abc['cat'] === 'my_perms' && intval($abc['v'])) { + $my_perms[] = $abc['k']; + continue; + } + if ($abc['cat'] === 'their_perms' && intval($abc['v'])) { + $their_perms[] = $abc['k']; + continue; + } + if ($zap_compat && preg_match('|^a:[0-9]+:{.*}$|s', $abc['v'])) { + $abc['v'] = serialise(unserialize($abc['v'])); + } + $newconfig[] = $abc; + } + + $ret['abook'][$x]['abconfig'] = $newconfig; + if ($zap_compat) { + $ret['abook'][$x]['abconfig'][] = [ 'chan' => $channel_id, 'xchan' => $ret['abook'][$x]['abook_chan'], 'cat' => 'system', 'k' => 'my_perms', 'v' => implode(',',$my_perms) ]; + $ret['abook'][$x]['abconfig'][] = [ 'chan' => $channel_id, 'xchan' => $ret['abook'][$x]['abook_chan'], 'cat' => 'system', 'k' => 'their_perms', 'v' => implode(',',$their_perms) ]; + } + } + translate_abook_perms_outbound($ret['abook'][$x]); + } // pick up the zot6 xchan and hublocs also @@ -911,8 +1000,17 @@ function identity_basic_export($channel_id, $sections = null) { $r = q("select * from pconfig where uid = %d", intval($channel_id) ); - if($r) + + if($r) { + if ($zap_compat) { + for($x = 0; $x < count($r); $x ++) { + if (preg_match('|^a:[0-9]+:{.*}$|s', $r[$x]['v'])) { + $r[$x]['v'] = serialise(unserialize($r[$x]['v'])); + } + } + } $ret['config'] = $r; + } // All other term types will be included in items, if requested. @@ -1827,15 +1925,16 @@ function zid_init() { call_hooks('zid_init', $arr); if(! local_channel()) { - $r = q("select * from hubloc where hubloc_addr = '%s' order by hubloc_connected desc", + $r = q("select hubloc_url, hubloc_hash, hubloc_network from hubloc where hubloc_addr = '%s' order by hubloc_connected desc", dbesc($tmp_str) ); if(! $r) { Master::Summon(array('Gprobe',bin2hex($tmp_str))); } if($r) { - $r = zot_record_preferred($r); + $r = Libzot::zot_record_preferred($r); } + if($r && remote_channel() && remote_channel() === $r['hubloc_hash']) return; @@ -2145,7 +2244,7 @@ function profiles_build_sync($channel_id,$send = true) { ); if($r) { if($send) { - build_sync_packet($channel_id,array('profile' => $r)); + Libsync::build_sync_packet($channel_id,array('profile' => $r)); } else { return $r; @@ -2285,8 +2384,8 @@ 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','pexels-94622'); - $cover = [ 'href' => z_root() . '/images/default_cover_photos/' . $default_cover . '/' . $cover_width . '.jpg' ]; + $default_cover = get_config('system','default_cover_photo','bggenerator'); + $cover = [ 'href' => z_root() . '/images/default_cover_photos/' . $default_cover . '/' . $cover_width . '.png' ]; } $o .= replace_macros(get_markup_template('zcard.tpl'), array( @@ -2359,8 +2458,8 @@ 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','pexels-94622'); - $cover = [ 'href' => z_root() . '/images/default_cover_photos/' . $default_cover . '/' . $cover_width . '.jpg' ]; + $default_cover = get_config('system','default_cover_photo','bggenerator'); + $cover = [ 'href' => z_root() . '/images/default_cover_photos/' . $default_cover . '/' . $cover_width . '.png' ]; } $o .= replace_macros(get_markup_template('zcard_embed.tpl'),array( @@ -2390,6 +2489,12 @@ function channelx_by_nick($nick) { $nick = punify($nick); + // return a cached copy if there is a cached copy and it's a match + + if (App::$channel && is_array(App::$channel) && array_key_exists('channel_address',App::$channel) && App::$channel['channel_address'] === $nick) { + return App::$channel; + } + $r = q("SELECT * FROM channel left join xchan on channel_hash = xchan_hash WHERE channel_address = '%s' and channel_removed = 0 LIMIT 1", dbesc($nick) ); @@ -2404,6 +2509,11 @@ function channelx_by_nick($nick) { * @return array|boolean false if channel ID not found, otherwise the channel array */ function channelx_by_hash($hash) { + + if (App::$channel && is_array(App::$channel) && array_key_exists('channel_hash',App::$channel) && App::$channel['channel_hash'] === $hash) { + return App::$channel; + } + $r = q("SELECT * FROM channel left join xchan on channel_hash = xchan_hash WHERE channel_hash = '%s' and channel_removed = 0 LIMIT 1", dbesc($hash) ); @@ -2419,6 +2529,11 @@ function channelx_by_hash($hash) { * @return array|boolean false if channel ID not found, otherwise the channel array */ function channelx_by_portid($hash) { + + if (App::$channel && is_array(App::$channel) && array_key_exists('channel_portable_id',App::$channel) && intval(App::$channel['channel_portable_id']) === intval($hash)) { + return App::$channel; + } + $r = q("SELECT * FROM channel left join xchan on channel_portable_id = xchan_hash WHERE channel_portable_id = '%s' and channel_removed = 0 LIMIT 1", dbesc($hash) ); @@ -2433,6 +2548,11 @@ function channelx_by_portid($hash) { * @return array|boolean false if channel ID not found, otherwise the channel array */ function channelx_by_n($id) { + + if (App::$channel && is_array(App::$channel) && array_key_exists('channel_id',App::$channel) && intval(App::$channel['channel_id']) === intval($id)) { + return App::$channel; + } + $r = q("SELECT * FROM channel LEFT JOIN xchan ON channel_hash = xchan_hash WHERE channel_id = %d AND channel_removed = 0 LIMIT 1", dbesc($id) ); @@ -2596,6 +2716,9 @@ function account_remove($account_id, $local = true, $unset_session = true) { logger('account_remove: ' . $account_id); + // Global removal (all clones) not currently supported + $local = true; + if(! intval($account_id)) { logger('No account.'); return false; @@ -2656,6 +2779,11 @@ function channel_remove($channel_id, $local = true, $unset_session = false) { if(! $channel_id) return; + // global removal (all clones) not currently supported + // hence this operation _may_ leave orphan data on remote servers + + $local = true; + logger('Removing channel: ' . $channel_id); logger('local only: ' . intval($local)); @@ -2674,6 +2802,7 @@ function channel_remove($channel_id, $local = true, $unset_session = false) { */ call_hooks('channel_remove', $r[0]); +/* if(! $local) { if(intval($r[0]['channel_removed'])) { @@ -2693,17 +2822,20 @@ function channel_remove($channel_id, $local = true, $unset_session = false) { logger('deleting hublocs',LOGGER_DEBUG); - $r = q("update hubloc set hubloc_deleted = 1 where hubloc_hash = '%s'", - dbesc($channel['channel_hash']) + $r = q("UPDATE hubloc SET hubloc_deleted = 1 WHERE hubloc_hash = '%s' OR hubloc_hash = '%s'", + dbesc($channel['channel_hash']), + dbesc($channel['channel_portable_id']) + ); - $r = q("update xchan set xchan_deleted = 1 where xchan_hash = '%s'", - dbesc($channel['channel_hash']) + $r = q("UPDATE xchan SET xchan_deleted = 1 WHERE xchan_hash = '%s' OR xchan_hash = '%s'", + dbesc($channel['channel_hash']), + dbesc($channel['channel_portable_id']) ); Master::Summon(array('Notifier','purge_all',$channel_id)); } - +*/ $r = q("select iid from iconfig left join item on item.id = iconfig.iid where item.uid = %d", @@ -2782,8 +2914,9 @@ function channel_remove($channel_id, $local = true, $unset_session = false) { logger('deleting hublocs',LOGGER_DEBUG); - $r = q("update hubloc set hubloc_deleted = 1 where hubloc_hash = '%s' and hubloc_url = '%s' ", + $r = q("UPDATE hubloc SET hubloc_deleted = 1 WHERE (hubloc_hash = '%s' OR hubloc_hash = '%s') AND hubloc_url = '%s' ", dbesc($channel['channel_hash']), + dbesc($channel['channel_portable_id']), dbesc(z_root()) ); @@ -2798,14 +2931,14 @@ function channel_remove($channel_id, $local = true, $unset_session = false) { $hublocs = count($r); if(! $hublocs) { - $r = q("update xchan set xchan_deleted = 1 where xchan_hash = '%s' ", - dbesc($channel['channel_hash']) + $r = q("UPDATE xchan SET xchan_deleted = 1 WHERE xchan_hash = '%s' OR xchan_hash = '%s'", + dbesc($channel['channel_hash']), + dbesc($channel['channel_portable_id']) ); } //remove from file system - $f = 'store/' . $channel['channel_address']; if(is_dir($f)) { @rrmdir($f); @@ -2953,3 +3086,8 @@ function pchan_to_chan($pchan) { function channel_url($channel) { return (($channel) ? z_root() . '/channel/' . $channel['channel_address'] : z_root()); } + +function get_channel_hashes() { + $r = q("SELECT channel_hash FROM channel WHERE channel_removed = 0"); + return flatten_array_recursive($r); +} diff --git a/include/connections.php b/include/connections.php index 51df18b70..658fb6ee6 100644 --- a/include/connections.php +++ b/include/connections.php @@ -446,8 +446,13 @@ function contact_remove($channel_id, $abook_id) { ); $r = q("delete from abconfig where chan = %d and xchan = '%s'", - intval($channel_id), - dbesc($abook['abook_xchan']) + intval($channel_id), + dbesc($abook['abook_xchan']) + ); + + $r = q("delete from source where src_channel_id = %d and src_xchan = '%s'", + intval($channel_id), + dbesc($abook['abook_xchan']) ); return true; @@ -772,3 +777,67 @@ function vcard_query(&$r) { } } } + +function z6trans_connections() { + + $r = dbq("SELECT DISTINCT abook.abook_xchan, hubloc.hubloc_addr, hubloc.hubloc_url, hubloc.hubloc_guid, site.site_project, site.site_version FROM abook + LEFT JOIN hubloc ON abook_xchan = hubloc_hash + LEFT JOIN site ON hubloc_url = site_url + WHERE abook.abook_self = 0 AND hubloc.hubloc_network = 'zot' + AND hubloc.hubloc_deleted = 0 AND site.site_dead = 0" + ); + + foreach($r as $rr) { + if(stripos($rr['site_project'], 'hubzilla') !== false && version_compare($rr['site_version'], '4.7.4', '>=')) { + + $zot_xchan = $rr['abook_xchan']; + $guid = $rr['hubloc_guid']; + $hub_url = $rr['hubloc_url']; + $addr = $rr['hubloc_addr']; + + $x = q("SELECT hubloc_hash FROM hubloc + WHERE hubloc_guid = '%s' AND hubloc_url = '%s' AND hubloc_network = 'zot6' AND hubloc_deleted = 0", + dbesc($guid), + dbesc($hub_url) + ); + + if(!$x) { + logger("z6trans_connections: zot6 hubloc for $addr not found"); + discover_by_webbie($addr,'zot6'); + continue; + } + + $zot6_xchan = $x[0]['hubloc_hash']; + + logger("z6trans_connections: transition $zot_xchan to $zot6_xchan"); + + dbq("START TRANSACTION"); + + $q1 = q("UPDATE abook set abook_xchan = '%s' WHERE abook_xchan = '%s'", + dbesc($zot6_xchan), + dbesc($zot_xchan) + ); + + $q2 = q("UPDATE abconfig set xchan = '%s' WHERE xchan = '%s'", + dbesc($zot6_xchan), + dbesc($zot_xchan) + ); + + $q3 = q("UPDATE pgrp_member set xchan = '%s' WHERE xchan = '%s'", + dbesc($zot6_xchan), + dbesc($zot_xchan) + ); + + if($q1 && $q2 && $q3) { + dbq("COMMIT"); + logger("z6trans_connections: completed"); + continue; + } + + logger("z6trans_connections: failed - performing rollback"); + dbq("ROLLBACK"); + + } + } + +} diff --git a/include/contact_widgets.php b/include/contact_widgets.php index 6ad276b00..626a825b2 100644 --- a/include/contact_widgets.php +++ b/include/contact_widgets.php @@ -1,6 +1,6 @@ <?php /** @file */ - +use Zotlabs\Lib\Cache; function findpeople_widget() { @@ -59,6 +59,7 @@ function fileas_widget($baseurl,$selected = '') { )); } + function categories_widget($baseurl,$selected = '') { if(! feature_enabled(App::$profile['profile_uid'],'categories')) @@ -70,25 +71,36 @@ function categories_widget($baseurl,$selected = '') { $item_normal = item_normal(); + $key = __FUNCTION__ . "-" . App::$profile['profile_uid']; + $content = Cache::get($key, '5 MINUTE'); + + if (! $content) { + $r = q("select distinct(term.term) from term join item on term.oid = item.id + where item.uid = %d + and term.uid = item.uid + and term.ttype = %d + and term.otype = %d + and item.owner_xchan = '%s' + and item.item_wall = 1 + and item.verb != '%s' + $item_normal + $sql_extra + order by term.term asc", + intval(App::$profile['profile_uid']), + intval(TERM_CATEGORY), + intval(TERM_OBJ_POST), + dbesc(App::$profile['channel_hash']), + dbesc(ACTIVITY_UPDATE) + ); + } + else + $r = unserialize($content); + $terms = array(); - $r = q("select distinct(term.term) from term join item on term.oid = item.id - where item.uid = %d - and term.uid = item.uid - and term.ttype = %d - and term.otype = %d - and item.owner_xchan = '%s' - and item.item_wall = 1 - and item.verb != '%s' - $item_normal - $sql_extra - order by term.term asc", - intval(App::$profile['profile_uid']), - intval(TERM_CATEGORY), - intval(TERM_OBJ_POST), - dbesc(App::$profile['channel_hash']), - dbesc(ACTIVITY_UPDATE) - ); if($r && count($r)) { + + Cache::set($key, serialize($r)); + foreach($r as $rr) $terms[] = array('name' => $rr['term'], 'selected' => (($selected == $rr['term']) ? 'selected' : '')); @@ -104,6 +116,7 @@ function categories_widget($baseurl,$selected = '') { return ''; } + function cardcategories_widget($baseurl,$selected = '') { if(! feature_enabled(App::$profile['profile_uid'],'categories')) diff --git a/include/conversation.php b/include/conversation.php index e2dd02ffc..e77404cff 100644 --- a/include/conversation.php +++ b/include/conversation.php @@ -1,5 +1,7 @@ <?php /** @file */ +use Zotlabs\Lib\Apps; + require_once('include/items.php'); @@ -100,22 +102,33 @@ function localize_item(&$item){ logger('localize_item: failed to decode object: ' . print_r($item['obj'],true)); } - if($obj['author'] && $obj['author']['link']) + if(is_array($obj['author']) && $obj['author']['link']) $author_link = get_rel_link($obj['author']['link'],'alternate'); + elseif(is_array($obj['actor']) && $obj['actor']['url']) + $author_link = ((is_array($obj['actor']['url'])) ? $obj['actor']['url'][0]['href'] : $obj['actor']['url']); else $author_link = ''; $author_name = (($obj['author'] && $obj['author']['name']) ? $obj['author']['name'] : ''); - $item_url = get_rel_link($obj['link'],'alternate'); + if(!$author_name) + $author_name = ((is_array($obj['actor']) && $obj['actor']['name']) ? $obj['actor']['name'] : ''); + + if(is_array($obj['link'])) + $item_url = get_rel_link($obj['link'],'alternate'); + + if(!$item_url) + $item_url = $obj['id']; $Bphoto = ''; switch($obj['type']) { case ACTIVITY_OBJ_PHOTO: + case 'Photo': $post_type = t('photo'); break; case ACTIVITY_OBJ_EVENT: + case 'Event': $post_type = t('event'); break; case ACTIVITY_OBJ_PERSON: @@ -140,9 +153,10 @@ function localize_item(&$item){ break; case ACTIVITY_OBJ_NOTE: + case 'Note': default: - $post_type = t('status'); - if($obj['id'] != $obj['parent']) + $post_type = t('post'); + if(($obj['parent'] && $obj['id'] != $obj['parent']) || $obj['inReplyTo']) $post_type = t('comment'); break; } @@ -172,9 +186,9 @@ function localize_item(&$item){ $shortbodyverb = t('doesn\'t like %1$s\'s %2$s'); } - $item['shortlocalize'] = sprintf($shortbodyverb, $objauthor, $plink); + $item['shortlocalize'] = sprintf($shortbodyverb, '[bdi]' . $author_name . '[/bdi]', $post_type); - $item['body'] = $item['localize'] = sprintf($bodyverb, $author, $objauthor, $plink); + $item['body'] = $item['localize'] = sprintf($bodyverb, '[bdi]' . $author . '[/bdi]', '[bdi]' . $objauthor . '[/bdi]', $plink); if($Bphoto != "") $item['body'] .= "\n\n\n" . '[zrl=' . chanlink_url($author_link) . '][zmg=80x80]' . $Bphoto . '[/zmg][/zrl]'; @@ -205,10 +219,12 @@ function localize_item(&$item){ $Bname = $obj['title']; - $A = '[zrl=' . chanlink_url($Alink) . ']' . $Aname . '[/zrl]'; - $B = '[zrl=' . chanlink_url($Blink) . ']' . $Bname . '[/zrl]'; + $A = '[zrl=' . chanlink_url($Alink) . '][bdi]' . $Aname . '[/bdi][/zrl]'; + $B = '[zrl=' . chanlink_url($Blink) . '][bdi]' . $Bname . '[/bdi][/zrl]'; if ($Bphoto!="") $Bphoto = '[zrl=' . chanlink_url($Blink) . '][zmg=80x80]' . $Bphoto . '[/zmg][/zrl]'; + $item['shortlocalize'] = sprintf( t('%1$s is now connected with %2$s'), '[bdi]' . $Aname . '[/bdi]', '[bdi]' . $Bname . '[/bdi]'); + $item['body'] = $item['localize'] = sprintf( t('%1$s is now connected with %2$s'), $A, $B); $item['body'] .= "\n\n\n" . $Bphoto; } @@ -237,8 +253,8 @@ function localize_item(&$item){ } $Bname = $obj['title']; - $A = '[zrl=' . chanlink_url($Alink) . ']' . $Aname . '[/zrl]'; - $B = '[zrl=' . chanlink_url($Blink) . ']' . $Bname . '[/zrl]'; + $A = '[zrl=' . chanlink_url($Alink) . '][bdi]' . $Aname . '[/bdi][/zrl]'; + $B = '[zrl=' . chanlink_url($Blink) . '][bdi]' . $Bname . '[/bdi][/zrl]'; if ($Bphoto!="") $Bphoto = '[zrl=' . chanlink_url($Blink) . '][zmg=80x80]' . $Bphoto . '[/zmg][/zrl]'; // we can't have a translation string with three positions but no distinguishable text @@ -252,6 +268,8 @@ function localize_item(&$item){ // then do the sprintf on the translation string + $item['shortlocalize'] = sprintf($txt, '[bdi]' . $Aname . '[/bdi]', '[bdi]' . $Bname . '[/bdi]'); + $item['body'] = $item['localize'] = sprintf($txt, $A, $B); $item['body'] .= "\n\n\n" . $Bphoto; } @@ -263,7 +281,7 @@ function localize_item(&$item){ $Aname = $item['author']['xchan_name']; $Alink = $item['author']['xchan_url']; - $A = '[zrl=' . chanlink_url($Alink) . ']' . $Aname . '[/zrl]'; + $A = '[zrl=' . chanlink_url($Alink) . '][bdi]' . $Aname . '[/bdi][/zrl]'; $txt = t('%1$s is %2$s','mood'); @@ -409,14 +427,28 @@ function visible_activity($item) { if(intval($item['item_notshown'])) return false; + if ($item['obj_type'] === 'Answer') { + return false; + } + foreach($hidden_activities as $act) { if((activity_match($item['verb'], $act)) && ($item['mid'] != $item['parent_mid'])) { return false; } } - if(is_edit_activity($item)) - return false; + // We only need edit activities for other federated protocols + // which do not support edits natively. While this does federate + // edits, it presents a number of issues locally - such as #757 and #758. + // The SQL check for an edit activity would not perform that well so to fix these issues + // requires an additional item flag (perhaps 'item_edit_activity') that we can add to the + // query for searches and notifications. + + // For now we'll just forget about trying to make edits work on network protocols that + // don't support them. + + // if(is_edit_activity($item)) + // return false; return true; } @@ -615,11 +647,17 @@ function conversation($items, $mode, $update, $page_mode = 'traditional', $prepa $items = $cb['items']; - $conv_responses = array( - 'like' => array('title' => t('Likes','title')),'dislike' => array('title' => t('Dislikes','title')), - 'agree' => array('title' => t('Agree','title')),'disagree' => array('title' => t('Disagree','title')), 'abstain' => array('title' => t('Abstain','title')), - 'attendyes' => array('title' => t('Attending','title')), 'attendno' => array('title' => t('Not attending','title')), 'attendmaybe' => array('title' => t('Might attend','title')) - ); + $conv_responses = [ + 'like' => ['title' => t('Likes','title')], + 'dislike' => ['title' => t('Dislikes','title')], + 'agree' => ['title' => t('Agree','title')], + 'disagree' => ['title' => t('Disagree','title')], + 'abstain' => ['title' => t('Abstain','title')], + 'attendyes' => ['title' => t('Attending','title')], + 'attendno' => ['title' => t('Not attending','title')], + 'attendmaybe' => ['title' => t('Might attend','title')], + 'answer' => [] + ]; // array with html for each thread (parent+comments) @@ -736,6 +774,7 @@ function conversation($items, $mode, $update, $page_mode = 'traditional', $prepa 'delete' => t('Delete'), 'preview_lbl' => $preview_lbl, 'id' => (($preview) ? 'P0' : $item['item_id']), + 'mids' => json_encode(['b64.' . base64url_encode($item['mid'])]), 'linktitle' => sprintf( t('View %s\'s profile @ %s'), $profile_name, $profile_url), 'profile_url' => $profile_link, 'thread_action_menu' => thread_action_menu($item,$mode), @@ -1009,18 +1048,18 @@ function thread_author_menu($item, $mode = '') { $contact = App::$contacts[$item['author_xchan']]; } else { - if($local_channel && $item['author']['xchan_addr'] && (! in_array($item['author']['xchan_network'],[ 'rss', 'anon','unknown' ]))) { - $follow_url = z_root() . '/follow/?f=&url=' . urlencode($item['author']['xchan_addr']) . '&interactive=0'; + $url = (($item['author']['xchan_addr']) ? $item['author']['xchan_addr'] : $item['author']['xchan_url']); + if($local_channel && $url && (! in_array($item['author']['xchan_network'],[ 'rss', 'anon','unknown' ]))) { + $follow_url = z_root() . '/follow/?f=&url=' . urlencode($url) . '&interactive=0'; } } - if($item['uid'] > 0 && author_is_pmable($item['author'],$contact)) { $pm_url = z_root() . '/mail/new/?f=&hash=' . urlencode($item['author_xchan']); } } if($contact) { - $poke_link = z_root() . '/poke/?f=&c=' . $contact['abook_id']; + $poke_link = ((Apps::system_app_installed($local_channel, 'Poke')) ? z_root() . '/poke/?f=&c=' . $contact['abook_id'] : ''); if (! intval($contact['abook_self'])) $contact_url = z_root() . '/connedit/' . $contact['abook_id']; $posts_link = z_root() . '/network/?cid=' . $contact['abook_id']; @@ -1125,7 +1164,7 @@ function builtin_activity_puller($item, &$conv_responses) { // if this item is a post or comment there's nothing for us to do here, just return. - if(activity_match($item['verb'],ACTIVITY_POST)) + if(activity_match($item['verb'],ACTIVITY_POST) && $item['obj_type'] !== 'Answer') return; foreach($conv_responses as $mode => $v) { @@ -1157,6 +1196,9 @@ function builtin_activity_puller($item, &$conv_responses) { case 'attendmaybe': $verb = ACTIVITY_ATTENDMAYBE; break; + case 'answer': + $verb = ACTIVITY_POST; + break; default: return; break; @@ -1172,6 +1214,11 @@ function builtin_activity_puller($item, &$conv_responses) { if(! $item['thr_parent']) $item['thr_parent'] = $item['parent_mid']; + $conv_responses[$mode]['mids'][$item['thr_parent']][] = 'b64.' . base64url_encode($item['mid']); + + if($item['obj_type'] === 'Answer') + continue; + if(! ((isset($conv_responses[$mode][$item['thr_parent'] . '-l'])) && (is_array($conv_responses[$mode][$item['thr_parent'] . '-l'])))) $conv_responses[$mode][$item['thr_parent'] . '-l'] = array(); @@ -1262,13 +1309,6 @@ function hz_status_editor($a, $x, $popup = false) { $plaintext = true; -// if(feature_enabled(local_channel(),'richtext')) -// $plaintext = false; - - $feature_voting = feature_enabled($x['profile_uid'], 'consensus_tools'); - if(x($x, 'hide_voting')) - $feature_voting = false; - $feature_nocomment = feature_enabled($x['profile_uid'], 'disable_comments'); if(x($x, 'disable_comments')) $feature_nocomment = false; @@ -1370,7 +1410,7 @@ function hz_status_editor($a, $x, $popup = false) { $cipher = get_pconfig($x['profile_uid'], 'system', 'default_cipher'); if(! $cipher) - $cipher = 'aes256'; + $cipher = 'AES-128-CCM'; if(array_key_exists('catsenabled',$x)) $catsenabled = $x['catsenabled']; @@ -1416,7 +1456,11 @@ function hz_status_editor($a, $x, $popup = false) { '$embedPhotosModalOK' => t('OK'), '$setloc' => $setloc, '$voting' => t('Toggle voting'), - '$feature_voting' => $feature_voting, + '$poll' => t('Toggle poll'), + '$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')]], '$consensus' => ((array_key_exists('item',$x)) ? $x['item']['item_consensus'] : 0), '$nocommenttitle' => t('Disable comments'), '$nocommenttitlesub' => t('Toggle comments'), diff --git a/include/dba/dba_driver.php b/include/dba/dba_driver.php index cfb208e2d..b96601fec 100755 --- a/include/dba/dba_driver.php +++ b/include/dba/dba_driver.php @@ -307,6 +307,10 @@ function db_use_index($str) { return \DBA::$dba->use_index($str); } +function db_str_to_date($str) { + return \DBA::$dba->str_to_date($str); +} + /** * @brief Execute a SQL query with printf style args. * diff --git a/include/dba/dba_pdo.php b/include/dba/dba_pdo.php index 0279342ec..49f741601 100755 --- a/include/dba/dba_pdo.php +++ b/include/dba/dba_pdo.php @@ -139,6 +139,15 @@ class dba_pdo extends dba_driver { } } + function str_to_date($str) { + if($this->driver_dbtype === 'pgsql') { + return "TO_TIMESTAMP($str, 'YYYY-MM-DD HH24:MI:SS')"; + } + else { + return "STR_TO_DATE($str, '%Y-%m-%d %H:%i:%s')"; + } + } + function quote_interval($txt) { if($this->driver_dbtype === 'pgsql') { return "'$txt'"; diff --git a/include/dir_fns.php b/include/dir_fns.php index 08a9fb653..f477b35dd 100644 --- a/include/dir_fns.php +++ b/include/dir_fns.php @@ -3,6 +3,10 @@ * @file include/dir_fns.php */ +use Zotlabs\Lib\Libzot; +use Zotlabs\Lib\Webfinger; +use Zotlabs\Lib\Zotfinger; + require_once('include/permissions.php'); /** @@ -67,15 +71,10 @@ function check_upstream_directory() { $isadir = true; if ($directory) { - $h = parse_url($directory); - if ($h) { - $j = Zotlabs\Zot\Finger::run('[system]@' . $h['host']); - if ($j['success']) { - if (array_key_exists('site', $j) && array_key_exists('directory_mode', $j['site'])) { - if ($j['site']['directory_mode'] === 'normal') { - $isadir = false; - } - } + $j = Zotfinger::exec($directory); + if (array_path_exists('data/directory_mode',$j)) { + if ($j['data']['directory_mode'] === 'normal') { + $isadir = false; } } } @@ -341,15 +340,15 @@ function update_directory_entry($ud) { // Hubzilla channels running traditional zot which have not upgraded can or will be dropped from the directory or // "not found" at the end of the transition period as the directory will only serve zot6 entries at that time. - $uri = \Zotlabs\Lib\Webfinger::zot_url($ud['ud_addr']); + $uri = Webfinger::zot_url($ud['ud_addr']); if($uri) { - $record = \Zotlabs\Lib\Zotfinger::exec($uri); + $record = Zotfinger::exec($uri); // Check the HTTP signature $hsig = $record['signature']; if($hsig && $hsig['signer'] === $url && $hsig['header_valid'] === true && $hsig['content_valid'] === true) { - $x = \Zotlabs\Zot\Libzot::import_xchan($record['data'], 0, $ud); + $x = \Zotlabs\Lib\Libzot::import_xchan($record['data'], 0, $ud); if($x['success']) { $success = true; } diff --git a/include/event.php b/include/event.php index 6be1b6705..765086167 100644 --- a/include/event.php +++ b/include/event.php @@ -8,6 +8,7 @@ use Sabre\VObject; use Zotlabs\Lib\Activity; +use Zotlabs\Lib\Libsync; use Ramsey\Uuid\Uuid; use Ramsey\Uuid\Exception\UnsatisfiedDependencyException; @@ -40,7 +41,7 @@ function format_event_html($ev) { $o .= '<div class="event-start"><span class="event-label">' . t('Starts:') . '</span> <span class="dtstart" title="' . datetime_convert('UTC', 'UTC', $ev['dtstart'], (($ev['adjust']) ? ATOM_TIME : 'Y-m-d\TH:i:s' )) . '" >' - . (($ev['adjust']) ? day_translate(datetime_convert($tz, date_default_timezone_get(), + . (($ev['adjust']) ? day_translate(datetime_convert('UTC', date_default_timezone_get(), $ev['dtstart'] , $bd_format )) : day_translate(datetime_convert('UTC', 'UTC', $ev['dtstart'] , $bd_format))) @@ -50,7 +51,7 @@ function format_event_html($ev) { $o .= '<div class="event-end" ><span class="event-label">' . t('Finishes:') . '</span> <span class="dtend" title="' . datetime_convert('UTC','UTC',$ev['dtend'], (($ev['adjust']) ? ATOM_TIME : 'Y-m-d\TH:i:s' )) . '" >' - . (($ev['adjust']) ? day_translate(datetime_convert($tz, date_default_timezone_get(), + . (($ev['adjust']) ? day_translate(datetime_convert('UTC', date_default_timezone_get(), $ev['dtend'] , $bd_format )) : day_translate(datetime_convert('UTC', 'UTC', $ev['dtend'] , $bd_format ))) @@ -73,48 +74,67 @@ function format_event_obj($jobject) { $object = json_decode($jobject,true); - //ensure compatibility with older items - this check can be removed at a later point - if(array_key_exists('description', $object)) { + $event_tz = ''; + if($object['adjust'] && is_array($object['asld']) && is_array($object['asld']['attachment'])) { + foreach($object['asld']['attachment'] as $attachment) { + if($attachment['type'] === 'PropertyValue' && $attachment['name'] == 'zot.event.timezone' ) { + // check if the offset of the timezones is different and only set event_tz if offset is not the same + $local_tz = new DateTimeZone(date_default_timezone_get()); + $local_dt = new DateTime('now', $local_tz); + + $ev_tz = new DateTimeZone($attachment['value']); + $ev_dt = new DateTime('now', $ev_tz); + if($local_dt->getOffset() !== $ev_dt->getOffset()) + $event_tz = $attachment['value']; + break; + } + } - $tz = (($object['timezone']) ? $object['timezone'] : 'UTC'); - $allday = (($object['adjust']) ? false : true); + } - $dtstart = new DateTime($object['dtstart']); - $dtend = new DateTime($object['dtend']); - $dtdiff = $dtstart->diff($dtend); + $allday = (($object['adjust']) ? false : true); - if($allday && ($dtdiff->days < 2)) - $oneday = true; + $dtstart = new DateTime($object['dtstart']); + $dtend = new DateTime($object['dtend']); + $dtdiff = $dtstart->diff($dtend); - if($allday && !$oneday) { - // Subtract one day from the end date so we can use the "first day - last day" format for display. - $dtend->modify('-1 day'); - $object['dtend'] = datetime_convert('UTC', 'UTC', $dtend->format('Y-m-d H:i:s')); - } + if($allday && ($dtdiff->days < 2)) + $oneday = true; - $bd_format = (($allday) ? t('l F d, Y') : t('l F d, Y \@ g:i A')); // Friday January 18, 2011 @ 8:01 AM or Friday January 18, 2011 for allday events - - $event['header'] = replace_macros(get_markup_template('event_item_header.tpl'),array( - '$title' => zidify_links(smilies(bbcode($object['title']))), - '$dtstart_label' => t('Start:'), - '$dtstart_title' => datetime_convert($tz, date_default_timezone_get(), $object['dtstart'], (($object['adjust']) ? ATOM_TIME : 'Y-m-d\TH:i:s' )), - '$dtstart_dt' => (($object['adjust']) ? day_translate(datetime_convert($tz, date_default_timezone_get(), $object['dtstart'] , $bd_format )) : day_translate(datetime_convert('UTC', 'UTC', $object['dtstart'] , $bd_format))), - '$finish' => (($object['nofinish']) ? false : true), - '$dtend_label' => t('End:'), - '$dtend_title' => datetime_convert($tz, date_default_timezone_get(), $object['dtend'], (($object['adjust']) ? ATOM_TIME : 'Y-m-d\TH:i:s' )), - '$dtend_dt' => (($object['adjust']) ? day_translate(datetime_convert($tz, date_default_timezone_get(), $object['dtend'] , $bd_format )) : day_translate(datetime_convert('UTC', 'UTC', $object['dtend'] , $bd_format ))), - '$allday' => $allday, - '$oneday' => $oneday - )); - - $event['content'] = replace_macros(get_markup_template('event_item_content.tpl'),array( - '$description' => zidify_links(smilies(bbcode($object['description']))), - '$location_label' => t('Location:'), - '$location' => zidify_links(smilies(bbcode($object['location']))) - )); + if($allday && !$oneday) { + // Subtract one day from the end date so we can use the "first day - last day" format for display. + $dtend->modify('-1 day'); + $object['dtend'] = datetime_convert('UTC', 'UTC', $dtend->format('Y-m-d H:i:s')); + } + $bd_format = (($allday) ? t('l F d, Y') : t('l F d, Y \@ g:i A')); // Friday January 18, 2011 @ 8:01 AM or Friday January 18, 2011 for allday events + + $event['header'] = replace_macros(get_markup_template('event_item_header.tpl'),array( + '$title' => zidify_links(smilies(bbcode($object['title']))), + '$dtstart_label' => t('Start:'), + '$dtstart_title' => datetime_convert('UTC', date_default_timezone_get(), $object['dtstart'], (($object['adjust']) ? ATOM_TIME : 'Y-m-d\TH:i:s' )), + '$dtstart_dt' => (($object['adjust']) ? day_translate(datetime_convert('UTC', date_default_timezone_get(), $object['dtstart'] , $bd_format )) : day_translate(datetime_convert('UTC', 'UTC', $object['dtstart'] , $bd_format))), + '$finish' => (($object['nofinish']) ? false : true), + '$dtend_label' => t('End:'), + '$dtend_title' => datetime_convert('UTC', date_default_timezone_get(), $object['dtend'], (($object['adjust']) ? ATOM_TIME : 'Y-m-d\TH:i:s' )), + '$dtend_dt' => (($object['adjust']) ? day_translate(datetime_convert('UTC', date_default_timezone_get(), $object['dtend'] , $bd_format )) : day_translate(datetime_convert('UTC', 'UTC', $object['dtend'] , $bd_format ))), + '$allday' => $allday, + '$oneday' => $oneday, + '$event_tz' => ['label' => t('Timezone'), 'value' => (($event_tz === date_default_timezone_get()) ? '' : $event_tz)] + )); + + $event_src = []; + + if(array_path_exists('asld/source', $object) && $object['asld']['source']['mediaType'] === 'text/bbcode') { + $event_src = bbtoevent($object['asld']['source']['content']); } + $event['content'] = replace_macros(get_markup_template('event_item_content.tpl'),array( + '$description' => zidify_links(smilies(bbcode($event_src ? $event_src['description'] : $object['description']))), + '$location_label' => t('Location:'), + '$location' => zidify_links(smilies(bbcode($event_src ? $event_src['location'] : $object['location']))), + )); + return $event; } @@ -250,14 +270,22 @@ function format_ical_sourcetext($s) { } -function format_event_bbcode($ev) { +function format_event_bbcode($ev, $utc = false) { $o = ''; if($ev['event_vdata']) { $o .= '[event]' . $ev['event_vdata'] . '[/event]'; } - +/* + if ($utc && $ev['event-timezone'] !== 'UTC') { + $ev['dtstart'] = datetime_convert($ev['timezone'],'UTC',$ev['dtstart']); + if ($ev['dtend'] && ! $ev['nofinish']) { + $ev['dtend'] = datetime_convert($ev['timezone'],'UTC',$ev['dtend']); + } + $ev['timezone'] = 'UTC'; + } +*/ if($ev['summary']) $o .= '[event-summary]' . $ev['summary'] . '[/event-summary]'; @@ -276,8 +304,8 @@ function format_event_bbcode($ev) { if($ev['event_hash']) $o .= '[event-id]' . $ev['event_hash'] . '[/event-id]'; - if($ev['timezone']) - $o .= '[event-timezone]' . $ev['timezone'] . '[/event-timezone]'; +// if($ev['timezone']) +// $o .= '[event-timezone]' . $ev['timezone'] . '[/event-timezone]'; if($ev['adjust']) $o .= '[event-adjust]' . $ev['adjust'] . '[/event-adjust]'; @@ -394,6 +422,10 @@ function event_store_event($arr) { $arr['event_xchan'] = (($arr['event_xchan']) ? $arr['event_xchan'] : ''); $arr['event_priority'] = (($arr['event_priority']) ? $arr['event_priority'] : 0); + if (! $arr['dtend']) { + $arr['dtend'] = NULL_DATE; + $arr['nofinish'] = 1; + } if(array_key_exists('event_status_date',$arr)) $arr['event_status_date'] = datetime_convert('UTC','UTC', $arr['event_status_date']); @@ -479,9 +511,9 @@ function event_store_event($arr) { deny_gid = '%s' WHERE id = %d AND uid = %d", - dbesc($arr['edited']), - dbesc($arr['dtstart']), - dbesc($arr['dtend']), + dbesc(datetime_convert('UTC','UTC',$arr['edited'])), + dbesc(datetime_convert('UTC','UTC',$arr['dtstart'])), + dbesc(datetime_convert('UTC','UTC',$arr['dtend'])), dbesc($arr['summary']), dbesc($arr['description']), dbesc($arr['location']), @@ -489,7 +521,7 @@ function event_store_event($arr) { intval($arr['adjust']), intval($arr['nofinish']), dbesc($arr['event_status']), - dbesc($arr['event_status_date']), + dbesc(datetime_convert('UTC','UTC',$arr['event_status_date'])), intval($arr['event_percent']), dbesc($arr['event_repeat']), intval($arr['event_sequence']), @@ -525,10 +557,10 @@ function event_store_event($arr) { intval($arr['account']), dbesc($arr['event_xchan']), dbesc($hash), - dbesc($arr['created']), - dbesc($arr['edited']), - dbesc($arr['dtstart']), - dbesc($arr['dtend']), + dbesc(datetime_convert('UTC','UTC',$arr['created'])), + dbesc(datetime_convert('UTC','UTC',$arr['edited'])), + dbesc(datetime_convert('UTC','UTC',$arr['dtstart'])), + dbesc(datetime_convert('UTC','UTC',$arr['dtend'])), dbesc($arr['summary']), dbesc($arr['description']), dbesc($arr['location']), @@ -536,7 +568,7 @@ function event_store_event($arr) { intval($arr['adjust']), intval($arr['nofinish']), dbesc($arr['event_status']), - dbesc($arr['event_status_date']), + dbesc(datetime_convert('UTC','UTC',$arr['event_status_date'])), intval($arr['event_percent']), dbesc($arr['event_repeat']), intval($arr['event_sequence']), @@ -553,9 +585,19 @@ function event_store_event($arr) { dbesc($hash), intval($arr['uid']) ); - if($r) + if($r) { + + /** + * @hooks event_store_event_end + * Called after an event record was stored. + * * \e array \b event + */ + call_hooks('event_store_event_end', $r[0]); + return $r[0]; + } + return false; } @@ -596,7 +638,7 @@ function event_addtocal($item_id, $uid) { $ev['event_hash'] = $item['resource_id']; } - if($ev->private) + if($ev['private']) $ev['allow_cid'] = '<' . $channel['channel_hash'] . '>'; else { $acl = new Zotlabs\Access\AccessList($channel); @@ -626,7 +668,7 @@ function event_addtocal($item_id, $uid) { intval($channel['channel_id']) ); if($z) { - build_sync_packet($channel['channel_id'],array('event_item' => array(encode_item($sync_item[0],true)),'event' => $z)); + Libsync::build_sync_packet($channel['channel_id'],array('event_item' => array(encode_item($sync_item[0],true)),'event' => $z)); } return true; } @@ -1073,12 +1115,17 @@ function event_store_item($arr, $event) { $item_arr['comment_policy'] = 'none'; } - $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", + $r = q("SELECT * FROM item WHERE resource_id = '%s' AND resource_type = 'event' and uid = %d LIMIT 1", dbesc($event['event_hash']), intval($arr['uid']) ); if($r) { + + set_iconfig($r[0]['id'], 'event', 'timezone', $arr['timezone'], true); + xchan_query($r); + $r = fetch_post_tags($r,true); + $object = json_encode(array( 'type' => ACTIVITY_OBJ_EVENT, 'id' => z_root() . '/event/' . $r[0]['resource_id'], @@ -1093,13 +1140,13 @@ function event_store_item($arr, $event) { 'content' => format_event_bbcode($arr), 'attachment' => Activity::encode_attachment($r[0]), 'author' => array( - 'name' => $r[0]['xchan_name'], - 'address' => $r[0]['xchan_addr'], - 'guid' => $r[0]['xchan_guid'], - 'guid_sig' => $r[0]['xchan_guid_sig'], + 'name' => $r[0]['author']['xchan_name'], + 'address' => $r[0]['author']['xchan_addr'], + 'guid' => $r[0]['author']['xchan_guid'], + 'guid_sig' => $r[0]['author']['xchan_guid_sig'], 'link' => array( - array('rel' => 'alternate', 'type' => 'text/html', 'href' => $r[0]['xchan_url']), - array('rel' => 'photo', 'type' => $r[0]['xchan_photo_mimetype'], 'href' => $r[0]['xchan_photo_m']) + array('rel' => 'alternate', 'type' => 'text/html', 'href' => $r[0]['author']['xchan_url']), + array('rel' => 'photo', 'type' => $r[0]['author']['xchan_photo_mimetype'], 'href' => $r[0]['author']['xchan_photo_m']) ), ), )); @@ -1149,7 +1196,6 @@ function event_store_item($arr, $event) { } $item_id = $r[0]['id']; - set_iconfig($item_id, 'event', 'timezone', $arr['timezone'], true); /** * @hooks event_updated @@ -1184,7 +1230,7 @@ function event_store_item($arr, $event) { if(! $arr['mid']) { $arr['uuid'] = $event['event_hash']; - $arr['mid'] = z_root() . '/event/' . $event['event_hash']; + $arr['mid'] = z_root() . '/activity/' . $event['event_hash']; } $item_arr['aid'] = $z[0]['channel_account_id']; @@ -1201,7 +1247,7 @@ function event_store_item($arr, $event) { $item_arr['deny_cid'] = $arr['deny_cid']; $item_arr['deny_gid'] = $arr['deny_gid']; $item_arr['item_private'] = $private; - $item_arr['verb'] = ACTIVITY_POST; + $item_arr['verb'] = 'Invite'; $item_arr['item_wall'] = $item_wall; $item_arr['item_origin'] = $item_origin; $item_arr['item_thread_top'] = $item_thread_top; @@ -1229,10 +1275,12 @@ function event_store_item($arr, $event) { // otherwise we'll fallback to /display/$message_id if($wall) - $item_arr['plink'] = z_root() . '/channel/' . $z[0]['channel_address'] . '/?f=&mid=' . gen_link_id($item_arr['mid']); + $item_arr['plink'] = $item_arr['mid']; else $item_arr['plink'] = z_root() . '/display/' . gen_link_id($item_arr['mid']); + set_iconfig($item_arr, 'event','timezone',$arr['timezone'],true); + $x = q("select * from xchan where xchan_hash = '%s' limit 1", dbesc($arr['event_xchan']) ); @@ -1268,7 +1316,6 @@ function event_store_item($arr, $event) { // activities refer to the item message_id as the parent. set_iconfig($item_arr, 'system','event_id',$event['event_hash'],true); - set_iconfig($item_arr, 'event','timezone',$arr['timezone'],true); $res = item_store($item_arr); diff --git a/include/features.php b/include/features.php index 87df0c50d..e1457604b 100644 --- a/include/features.php +++ b/include/features.php @@ -144,7 +144,7 @@ function get_features($filtered = true, $level = (-1)) { 'conversation' => [ t('Conversation'), - + /* disable until we agree on how to implemnt this in zot6/activitypub [ 'commtag', t('Community Tagging'), @@ -152,7 +152,7 @@ function get_features($filtered = true, $level = (-1)) { false, get_config('feature_lock','commtag'), ], - + */ [ 'emojis', t('Emoji Reactions'), @@ -229,14 +229,6 @@ function get_features($filtered = true, $level = (-1)) { false, get_config('feature_lock','content_encrypt'), ], - - [ - 'consensus_tools', - t('Enable Voting Tools'), - t('Provide a class of post which others can vote on'), - false, - get_config('feature_lock','consensus_tools'), - ], [ 'disable_comments', @@ -299,6 +291,22 @@ function get_features($filtered = true, $level = (-1)) { t('Network'), [ + 'events_tab', + t('Events Filter'), + t('Ability to display only events'), + false, + get_config('feature_lock','events_tab') + ], + + [ + 'polls_tab', + t('Polls Filter'), + t('Ability to display only polls'), + false, + get_config('feature_lock','polls_tab') + ], + + [ 'savedsearch', t('Saved Searches'), t('Save search terms for re-use'), diff --git a/include/feedutils.php b/include/feedutils.php index 6d14eb5c4..0a9af7ee1 100644 --- a/include/feedutils.php +++ b/include/feedutils.php @@ -155,7 +155,7 @@ function get_feed_for($channel, $observer_hash, $params) { if($item['item_private']) continue; - $atom .= atom_entry($item, $type, null, $owner, true, '', $params['compat']); + $atom .= atom_entry($item, $type, null, $channel, true, '', $params['compat']); } } @@ -452,6 +452,7 @@ function get_atom_elements($feed, $item) { else { $res['title'] = bbcode($res['title'], [ 'tryoembed' => false ]); $res['title'] = html2plain($res['title'], 0, true); + $res['title'] = strip_tags($res['title']); $res['title'] = html_entity_decode($res['title'], ENT_QUOTES, 'UTF-8'); $res['title'] = preg_replace("/https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,\@]+/", "", $res['title']); while (strpos($res['title'], "\n") !== false) @@ -1921,7 +1922,7 @@ function atom_entry($item, $type, $author, $owner, $comment = false, $cid = 0, $ $summary = ''; if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid']) - $body = fix_private_photos($body,$owner['uid'],$item,$cid); + $body = fix_private_photos($body,$owner['channel_id'],$item,$cid); if($compat) { $compat_photos = compat_photos_list($body); diff --git a/include/group.php b/include/group.php index 6011af08f..efda389d6 100644 --- a/include/group.php +++ b/include/group.php @@ -1,5 +1,6 @@ <?php /** @file */ +use Zotlabs\Lib\Libsync; function group_add($uid,$name,$public = 0) { @@ -44,7 +45,7 @@ function group_add($uid,$name,$public = 0) { $ret = $r; } - build_sync_packet($uid,null,true); + Libsync::build_sync_packet($uid,null,true); return $ret; } @@ -113,7 +114,7 @@ function group_rmv($uid,$name) { } - build_sync_packet($uid,null,true); + Libsync::build_sync_packet($uid,null,true); return $ret; } @@ -155,7 +156,7 @@ function group_rmv_member($uid,$name,$member) { dbesc($member) ); - build_sync_packet($uid,null,true); + Libsync::build_sync_packet($uid,null,true); return $r; @@ -186,7 +187,7 @@ function group_add_member($uid,$name,$member,$gid = 0) { dbesc($member) ); - build_sync_packet($uid,null,true); + Libsync::build_sync_packet($uid,null,true); return $r; } diff --git a/include/help.php b/include/help.php index e82fa96da..affe64495 100644 --- a/include/help.php +++ b/include/help.php @@ -223,7 +223,7 @@ function find_doc_file($s) { */ function search_doc_files($s) { - \App::set_pager_itemspage(60); + \App::set_pager_itemspage(30); $pager_sql = sprintf(" LIMIT %d OFFSET %d ", intval(\App::$pager['itemspage']), intval(\App::$pager['start'])); $regexop = db_getfunc('REGEXP'); diff --git a/include/hubloc.php b/include/hubloc.php index 4a1f77733..6b896c627 100644 --- a/include/hubloc.php +++ b/include/hubloc.php @@ -247,6 +247,7 @@ function hubloc_change_primary($hubloc) { * * We use the post url to distinguish between http and https hublocs. * The https might be alive, and the http dead. + * Also set site_dead for the corresponding entry in the site table. * * @param string $posturl Hubloc callback url which to disable */ @@ -255,6 +256,13 @@ function hubloc_mark_as_down($posturl) { intval(HUBLOC_OFFLINE), dbesc($posturl) ); + + // extract the baseurl and set site.site_dead to match + $m = parse_url($posturl); + $h = $m['scheme'] . '://' . $m['host']; + $r = q("update site set site_dead = 1 where site_url = '%s'", + dbesc($h) + ); } @@ -317,7 +325,7 @@ function z6_discover() { if ($c) { foreach ($c as $entry) { $q1 = q("select * from hubloc left join site on hubloc_url = site_url where hubloc_deleted = 0 and site_dead = 0 and hubloc_hash = '%s' and hubloc_url != '%s'", - dbesc($entry['channel_hash']), + dbesc($entry['channel_portable_id']), dbesc(z_root()) ); if (! $q1) { @@ -327,7 +335,7 @@ function z6_discover() { // does this particular server have a zot6 clone registered on our site for this channel? foreach ($q1 as $q) { $q2 = q("select * from hubloc left join site on hubloc_url = site_url where hubloc_deleted = 0 and site_dead = 0 and hubloc_hash = '%s' and hubloc_url = '%s'", - dbesc($entry['channel_portable_id']), + dbesc($entry['channel_hash']), dbesc($q['hubloc_url']) ); if ($q2) { diff --git a/include/import.php b/include/import.php index 6a3895b9f..910cb8be7 100644 --- a/include/import.php +++ b/include/import.php @@ -1,6 +1,7 @@ <?php use Zotlabs\Lib\IConfig; +use Zotlabs\Lib\Libzot; use Zotlabs\Web\HTTPSig; @@ -31,7 +32,7 @@ function import_channel($channel, $account_id, $seize, $newname = '') { // Ignore the hash provided and re-calculate - $channel['channel_hash'] = make_xchan_hash($channel['channel_guid'],$channel['channel_guid_sig']); + $channel['channel_hash'] = Libzot::make_xchan_hash($channel['channel_guid'],$channel['channel_pubkey']); if($newname) { $channel['channel_address'] = $newname; @@ -1503,6 +1504,144 @@ function sync_files($channel, $files) { } } + +/** + * @brief Synchronize addressbooks. + * + * @param array $channel + * @param array $data + */ +function sync_addressbook($channel, $data) { + + if(! \Zotlabs\Lib\Apps::system_app_installed($channel['channel_id'], 'CardDAV')) + return; + + logger("debug: " . print_r($data,true), LOGGER_DEBUG); + + require_once('include/cdav.php'); + + $principalUri = 'principals/' . $channel['channel_address']; + + if($data['action'] !== 'create') { + $id = get_cdav_id($principalUri, $data['uri'], 'addressbooks'); + if(! $id) + return; + } + + $pdo = \DBA::$dba->db; + + $carddavBackend = new \Sabre\CardDAV\Backend\PDO($pdo); + $addressbooks = $carddavBackend->getAddressBooksForUser($principalUri); + + switch($data['action']) { + + case 'create': + $carddavBackend->createAddressBook($principalUri, $data['uri'], $data['properties']); + break; + + case 'drop': + $carddavBackend->deleteAddressBook($id); + break; + + case 'edit': + $patch = new \Sabre\DAV\PropPatch($data['mutations']); + $carddavBackend->updateAddressBook($id, $patch); + $patch->commit(); + break; + + case 'delete_card': + $carddavBackend->deleteCard($id, $data['carduri']); + break; + + case 'update_card': + $vcard = \Sabre\VObject\Reader::read($data['card']); + $object = $vcard->convert(\Sabre\VObject\Document::VCARD40); + $cardData = $vcard->serialize(); + $carddavBackend->updateCard($id, $data['carduri'], $cardData); + break; + + case 'import': + $objects = new \Sabre\VObject\Splitter\VCard($data['card']); + $profile = \Sabre\VObject\Node::PROFILE_CARDDAV; + import_cdav_card($id, 'vcf', 'cards', 'addressbookid', $objects, $profile, $carddavBackend, $data['ids']); + break; + + default: + break; + } +} + + +/** + * @brief Synchronize calendars. + * + * @param array $channel + * @param array $data + */ +function sync_calendar($channel, $data) { + + if(! \Zotlabs\Lib\Apps::system_app_installed($channel['channel_id'], 'Calendar')) + return; + + logger("debug: " . print_r($data,true), LOGGER_DEBUG); + + require_once('include/cdav.php'); + + $principalUri = 'principals/' . $channel['channel_address']; + + if($data['action'] !== 'create') { + $x = get_cdav_id($principalUri, $data['uri'], 'calendarinstances'); + if(! $x) + return; + $id = [ $x['id'], $x['calendarid'] ]; + } + + $pdo = \DBA::$dba->db; + + $caldavBackend = new \Sabre\CalDAV\Backend\PDO($pdo); + $calendars = $caldavBackend->getCalendarsForUser($principalUri); + + switch($data['action']) { + + case 'create': + $id = $caldavBackend->createCalendar($principalUri, $data['uri'], $data['properties']); + set_pconfig($channel['channel_id'], 'cdav_calendar', $id[0], 1); + break; + + case 'drop': + $caldavBackend->deleteCalendar($id); + break; + + case 'edit': + $patch = new \Sabre\DAV\PropPatch($data['mutations']); + $caldavBackend->updateCalendar($id, $patch); + $patch->commit(); + break; + + case 'delete_card': + $caldavBackend->deleteCalendarObject($id, $data['carduri']); + break; + + case 'update_card': + $caldavBackend->updateCalendarObject($id, $data['carduri'], $data['card']); + break; + + case 'switch': + set_pconfig($channel['channel_id'], 'cdav_calendar', $id[0], $data['switch']); + break; + + case 'import': + $objects = new \Sabre\VObject\Splitter\ICalendar($data['card']); + $profile = \Sabre\VObject\Node::PROFILE_CALDAV; + import_cdav_card($id, 'ics', 'calendarobjects', 'calendarid', $objects, $profile, $caldavBackend, $data['ids']); + break; + + default: + break; + } +} + + /** * @brief Rename a key in an array. * diff --git a/include/items.php b/include/items.php index 917808ad5..960aa3580 100755 --- a/include/items.php +++ b/include/items.php @@ -9,7 +9,10 @@ use Zotlabs\Lib\MarkdownSoap; use Zotlabs\Lib\MessageFilter; use Zotlabs\Lib\ThreadListener; use Zotlabs\Lib\IConfig; +use Zotlabs\Lib\Activity; +use Zotlabs\Lib\Libsync; use Zotlabs\Access\PermissionLimits; +use Zotlabs\Access\PermissionRoles; use Zotlabs\Access\AccessList; use Zotlabs\Daemon\Master; @@ -237,19 +240,19 @@ function comments_are_now_closed($item) { function item_normal() { return " and item.item_hidden = 0 and item.item_type = 0 and item.item_deleted = 0 and item.item_unpublished = 0 and item.item_delayed = 0 and item.item_pending_remove = 0 - and item.item_blocked = 0 and item.obj_type != '" . ACTIVITY_OBJ_FILE . "' "; + and item.item_blocked = 0 "; } 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 - and item.item_blocked = 0 and item.obj_type != '" . ACTIVITY_OBJ_FILE . "' "; + and item.item_blocked = 0 "; } function item_normal_update() { return " and item.item_hidden = 0 and item.item_type = 0 and item.item_unpublished = 0 and item.item_delayed = 0 and item.item_pending_remove = 0 - and item.item_blocked = 0 and item.obj_type != '" . ACTIVITY_OBJ_FILE . "' "; + and item.item_blocked = 0 "; } @@ -265,7 +268,7 @@ function is_item_normal($item) { if(intval($item['item_hidden']) || intval($item['item_type']) || intval($item['item_deleted']) || intval($item['item_unpublished']) || intval($item['item_delayed']) || intval($item['item_pending_remove']) - || intval($item['item_blocked']) || ($item['obj_type'] == ACTIVITY_OBJ_FILE)) + || intval($item['item_blocked'])) return false; return true; @@ -288,7 +291,7 @@ function is_item_normal($item) { */ function can_comment_on_post($observer_xchan, $item) { -// logger('Comment_policy: ' . $item['comment_policy'], LOGGER_DEBUG); + // logger('Comment_policy: ' . $item['comment_policy'], LOGGER_DEBUG); $x = [ 'observer_hash' => $observer_xchan, @@ -349,7 +352,8 @@ function can_comment_on_post($observer_xchan, $item) { return true; } break; - default: + + default: break; } if(strstr($item['comment_policy'],'network:') && strstr($item['comment_policy'],'red')) { @@ -421,7 +425,7 @@ function post_activity_item($arr, $allow_code = false, $deliver = true) { if(! array_key_exists('item_origin',$arr)) $arr['item_origin'] = 1; if(! array_key_exists('item_wall',$arr) && (! $is_comment)) - $arr['item_wall'] = 1; + $arr['item_wall'] = 0; if(! array_key_exists('item_thread_top',$arr) && (! $is_comment)) $arr['item_thread_top'] = 1; @@ -930,8 +934,19 @@ function import_author_xchan($x) { } // if we were told that it's a zot connection, don't probe/import anything else - if(array_key_exists('network',$x) && $x['network'] === 'zot') + if(array_key_exists('network',$x) && $x['network'] === 'zot') { + if($x['url']) { + // check if we already have the zot6 xchan of this xchan_url. if not import it. + $r = q("SELECT xchan_hash FROM xchan WHERE xchan_url = '%s' AND xchan_network = 'zot6'", + dbesc($x['url']) + ); + + if(! $r) + discover_by_webbie($x['url'], 'zot6'); + } + return $y; + } // perform zot6 discovery @@ -1936,6 +1951,11 @@ function item_store($arr, $allow_exec = false, $deliver = true) { if(intval($r[0]['item_uplink']) && (! $r[0]['item_private'])) $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],$arr); + } + } else { logger('item_store: item parent was not found - ignoring item'); @@ -1955,8 +1975,9 @@ function item_store($arr, $allow_exec = false, $deliver = true) { ); } else { - $r = q("SELECT id FROM item WHERE mid = '%s' AND uid = %d and revision = %d LIMIT 1", + $r = q("SELECT id FROM item WHERE (mid = '%s' OR mid = '%s') AND uid = %d and revision = %d LIMIT 1", dbesc($arr['mid']), + dbesc(basename(rawurldecode($arr['mid']))), // de-duplicate relayed comments from hubzilla < 4.0 intval($arr['uid']), intval($arr['revision']) ); @@ -2609,6 +2630,12 @@ function get_item_contact($item,$contacts) { */ function tag_deliver($uid, $item_id) { + $role = get_pconfig($uid,'system','permissions_role'); + $rolesettings = PermissionRoles::role_perms($role); + $channel_type = isset($rolesettings['channel_type']) ? $rolesettings['channel_type'] : 'normal'; + + $is_group = (($channel_type === 'group') ? true : false); + $mention = false; /* @@ -2628,18 +2655,43 @@ function tag_deliver($uid, $item_id) { if(! $i) return; + xchan_query($i,true); $i = fetch_post_tags($i); $item = $i[0]; - if(($item['source_xchan']) && intval($item['item_uplink']) + if(intval($item['item_uplink']) && $item['source_xchan'] && intval($item['item_thread_top']) && ($item['edited'] != $item['created'])) { // this is an update (edit) to a post which was already processed by us and has a second delivery chain // Just start the second delivery chain to deliver the updated post // after resetting ownership and permission bits logger('updating edited tag_deliver post for ' . $u[0]['channel_address']); - start_delivery_chain($u[0], $item, $item_id, 0, true); + start_delivery_chain($u[0], $item, $item_id, 0, false, true); + return; + } + + if ($is_group && intval($item['item_private']) === 2 && intval($item['item_thread_top'])) { + // group delivery via DM + if(perm_is_allowed($uid,$item['owner_xchan'],'post_wall') || perm_is_allowed($uid,$item['owner_xchan'],'tag_deliver')) { + logger('group DM delivery for ' . $u[0]['channel_address']); + start_delivery_chain($u[0], $item, $item_id, 0, true, (($item['edited'] != $item['created']) || $item['item_deleted'])); + } + return; + } + + + if ($is_group && intval($item['item_thread_top']) && intval($item['item_wall']) && $item['author_xchan'] !== $item['owner_xchan']) { + if (strpos($item['body'],'[/share]')) { + logger('W2W post already shared'); + return; + } + // group delivery via W2W + logger('rewriting W2W post for ' . $u[0]['channel_address']); + start_delivery_chain($u[0], $item, $item_id, 0, true, (($item['edited'] != $item['created']) || $item['item_deleted'])); + q("update item set item_wall = 0 where id = %d", + intval($item_id) + ); return; } @@ -2946,6 +2998,13 @@ function item_community_tag($channel,$item) { */ function tgroup_check($uid, $item) { + + $role = get_pconfig($uid,'system','permissions_role'); + $rolesettings = PermissionRoles::role_perms($role); + $channel_type = isset($rolesettings['channel_type']) ? $rolesettings['channel_type'] : 'normal'; + + $is_group = (($channel_type === 'group') ? true : false); + $mention = false; // check that the message originated elsewhere and is a top-level post @@ -2962,6 +3021,15 @@ function tgroup_check($uid, $item) { return false; } + // post to group via DM + + if ($is_group) { + if (intval($item['item_private']) === 2 && $item['mid'] === $item['parent_mid']) { + 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", @@ -3070,7 +3138,7 @@ function tgroup_check($uid, $item) { * @param boolean $edit (optional) default false * @return void */ -function start_delivery_chain($channel, $item, $item_id, $parent, $edit = false) { +function start_delivery_chain($channel, $item, $item_id, $parent, $group = false, $edit = false) { $sourced = check_item_source($channel['channel_id'],$item); @@ -3107,6 +3175,7 @@ function start_delivery_chain($channel, $item, $item_id, $parent, $edit = false) // This MAY cause you to run afoul of copyright law. $rewrite_author = intval(get_abconfig($channel['channel_id'],$item['owner_xchan'],'system','rself')); + if($rewrite_author) { $item['author_xchan'] = $channel['channel_hash']; @@ -3117,6 +3186,107 @@ function start_delivery_chain($channel, $item, $item_id, $parent, $edit = false) } } + if ($group && (! $parent)) { + + $arr = []; + + if ($edit) { + // process edit or delete action + $r = q("select * from item where source_xchan = '%s' and body like '%s' and uid = %d limit 1", + dbesc($item['owner_xchan']), + dbesc("%message_id='" . $item['mid'] . "'%"), + intval($channel['channel_id']) + ); + if ($r) { + if (intval($item['item_deleted'])) { + drop_item($r[0]['id'],false,DROPITEM_PHASE1); + Master::Summon([ 'Notifier','drop',$r[0]['id'] ]); + return; + } + $arr['id'] = intval($r[0]['id']); + $arr['parent'] = intval($r[0]['parent']); + $arr['uuid'] = $r[0]['uuid']; + $arr['mid'] = $r[0]['mid']; + $arr['parent_mid'] = $r[0]['parent_mid']; + $arr['edited'] = datetime_convert(); + } + else { + logger('unable to locate original group post ' . $item['mid']); + return; + } + + } + else { + $arr['uuid'] = item_message_id(); + $arr['mid'] = z_root() . '/activity/' . $arr['uuid']; + $arr['parent_mid'] = $arr['mid']; + } + + $arr['aid'] = $channel['channel_account_id']; + $arr['uid'] = $channel['channel_id']; + + // WARNING: the presence of both source_xchan and non-zero item_uplink here will cause a delivery loop + + $arr['item_uplink'] = 0; + $arr['source_xchan'] = $item['owner_xchan']; + + $arr['item_private'] = (($channel['channel_allow_cid'] || $channel['channel_allow_gid'] + || $channel['channel_deny_cid'] || $channel['channel_deny_gid']) ? 1 : 0); + + $arr['item_origin'] = 1; + $arr['item_wall'] = 1; + + $arr['item_thread_top'] = 1; + + if (strpos($item['body'], "[/share]") !== false) { + $pos = strpos($item['body'], "[share"); + $bb = substr($item['body'], $pos); + } else { + $bb = "[share author='" . urlencode($item['author']['xchan_name']). + "' profile='" . $item['author']['xchan_url'] . + "' portable_id='" . $item['author']['xchan_hash'] . + "' avatar='" . $item['author']['xchan_photo_s'] . + "' link='" . $item['plink'] . + "' auth='" . ((in_array($item['author']['xchan_network'], ['zot6','zot'])) ? 'true' : 'false') . + "' posted='" . $item['created'] . + "' message_id='" . $item['mid'] . + "']"; + if($item['title']) + $bb .= '[b]'.$item['title'].'[/b]'."\r\n"; + $bb .= $item['body']; + $bb .= "[/share]"; + } + + $arr['body'] = $bb; + + $arr['author_xchan'] = $channel['channel_hash']; + $arr['owner_xchan'] = $channel['channel_hash']; + + $arr['obj_type'] = $item['obj_type']; + $arr['verb'] = ACTIVITY_POST; + + $arr['allow_cid'] = $channel['channel_allow_cid']; + $arr['allow_gid'] = $channel['channel_allow_gid']; + $arr['deny_cid'] = $channel['channel_deny_cid']; + $arr['deny_gid'] = $channel['channel_deny_gid']; + $arr['comment_policy'] = map_scope(PermissionLimits::Get($channel['channel_id'],'post_comments')); + + if ($arr['id']) { + $post = item_store_update($arr); + } + else { + $post = item_store($arr); + } + + $post_id = $post['item_id']; + + if($post_id) { + Master::Summon([ 'Notifier','tgroup',$post_id ]); + } + 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 @@ -3206,7 +3376,7 @@ function start_delivery_chain($channel, $item, $item_id, $parent, $edit = false) function check_item_source($uid, $item) { logger('source: uid: ' . $uid, LOGGER_DEBUG); - $xchan = (($item['source_xchan']) ? $item['source_xchan'] : $item['owner_xchan']); + $xchan = (($item['source_xchan'] && intval($item['item_uplink'])) ? $item['source_xchan'] : $item['owner_xchan']); $r = q("select * from source where src_channel_id = %d and ( src_xchan = '%s' or src_xchan = '*' ) limit 1", intval($uid), @@ -3856,7 +4026,7 @@ function delete_item_lowlevel($item, $stage = DROPITEM_NORMAL) { if($x) { $sync_data['event_deleted'] = 1; - build_sync_packet($item['uid'], ['event' => [$sync_data]]); + Libsync::build_sync_packet($item['uid'], ['event' => [$sync_data]]); } } @@ -3865,7 +4035,7 @@ function delete_item_lowlevel($item, $stage = DROPITEM_NORMAL) { $channel = channelx_by_n($item['uid']); $sync_data = attach_export_data($channel, $item['resource_id'], true); if($sync_data) - build_sync_packet($item['uid'], ['file' => [$sync_data]]); + Libsync::build_sync_packet($item['uid'], ['file' => [$sync_data]]); } // immediately remove any undesired profile likes. @@ -4367,8 +4537,8 @@ function items_fetch($arr,$channel = null,$observer_hash = null,$client_mode = C // only setup pagination on initial page view $pager_sql = ''; } else { - $itemspage = (($channel) ? get_pconfig($uid,'system','itemspage') : 20); - App::set_pager_itemspage(((intval($itemspage)) ? $itemspage : 20)); + $itemspage = (($channel) ? get_pconfig($uid,'system','itemspage') : 10); + App::set_pager_itemspage(((intval($itemspage)) ? $itemspage : 10)); $pager_sql = sprintf(" LIMIT %d OFFSET %d ", intval(App::$pager['itemspage']), intval(App::$pager['start'])); } @@ -4661,14 +4831,6 @@ function send_profile_photo_activity($channel,$photo,$profile) { $arr['item_thread_top'] = 1; $arr['item_origin'] = 1; $arr['item_wall'] = 1; - $arr['obj_type'] = ACTIVITY_OBJ_PHOTO; - $arr['verb'] = ACTIVITY_UPDATE; - - $arr['obj'] = json_encode(array( - 'type' => $arr['obj_type'], - 'id' => z_root() . '/photo/profile/l/' . $channel['channel_id'], - 'link' => array('rel' => 'photo', 'type' => $photo['type'], 'href' => z_root() . '/photo/profile/l/' . $channel['channel_id']) - )); if(stripos($profile['gender'],t('female')) !== false) $t = t('%1$s updated her %2$s'); @@ -4709,7 +4871,7 @@ function sync_an_item($channel_id,$item_id) { if($r) { xchan_query($r); $sync_item = fetch_post_tags($r); - build_sync_packet($channel_id, array('item' => array(encode_item($sync_item[0],true)))); + Libsync::build_sync_packet($channel_id, array('item' => array(encode_item($sync_item[0],true)))); } } @@ -4911,7 +5073,7 @@ function item_create_edit_activity($post) { if($r) { xchan_query($r); $sync_item = fetch_post_tags($r); - build_sync_packet($new_item['uid'],array('item' => array(encode_item($sync_item[0],true)))); + Libsync::build_sync_packet($new_item['uid'],array('item' => array(encode_item($sync_item[0],true)))); } } diff --git a/include/js_strings.php b/include/js_strings.php index f01fa87ae..6f559f4c4 100644 --- a/include/js_strings.php +++ b/include/js_strings.php @@ -33,7 +33,10 @@ function js_strings() { '$name_empty' => t('A channel name is required.'), '$name_ok1' => t('This is a '), '$name_ok2' => t(' channel name'), - '$to_reply' => t('Back to reply'), + '$to_reply' => t('Back to reply'), + '$pinned' => t('Pinned'), + '$pin_item' => t('Pin to the top'), + '$unpin_item' => t('Unpin from the top'), // translatable prefix and suffix strings for jquery.timeago - // using the defaults set below if left untranslated, empty strings if diff --git a/include/language.php b/include/language.php index e9d62e434..622b9614d 100644 --- a/include/language.php +++ b/include/language.php @@ -217,9 +217,10 @@ function t($s, $ctx = '') { */ function translate_projectname($s) { - - return str_replace(array('$projectname','$Projectname'),array(Zotlabs\Lib\System::get_platform_name(),ucfirst(Zotlabs\Lib\System::get_platform_name())),$s); - + if(strpos($s,'rojectname') !== false) { + return str_replace(array('$projectname','$Projectname'),array(Zotlabs\Lib\System::get_platform_name(),ucfirst(Zotlabs\Lib\System::get_platform_name())),$s); + } + return $s; } diff --git a/include/markdown.php b/include/markdown.php index 69cc264df..0bfe595b8 100644 --- a/include/markdown.php +++ b/include/markdown.php @@ -76,7 +76,7 @@ function markdown_to_bb($s, $use_zrl = false, $options = []) { $s = html2bbcode($s); - $s = bb_code_protect($s); + // $s = bb_code_protect($s); // Convert everything that looks like a link to a link if($use_zrl) { @@ -84,13 +84,13 @@ function markdown_to_bb($s, $use_zrl = false, $options = []) { $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\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,\@\(\)]+)/ismu", 'use_zrl_cb_link',$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\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,\@\(\)]+)/ismu", '$1[url=$2$3]$2$3[/url]',$s); + $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); + // $s = bb_code_unprotect($s); // remove duplicate adjacent code tags $s = preg_replace("/(\[code\])+(.*?)(\[\/code\])+/ism","[code]$2[/code]", $s); @@ -110,9 +110,9 @@ function use_zrl_cb_link($match) { $is_zid = is_matrix_url(trim($match[0])); if($is_zid) - $res = $match[1] . '[zrl=' . $match[2] . $match[3] . ']' . $match[2] . $match[3] . '[/zrl]'; + $res = $match[1] . '[zrl=' . $match[2] . $match[3] . ']' . $match[2] . $match[3] . '[/zrl]' . $match[4]; else - $res = $match[1] . '[url=' . $match[2] . $match[3] . ']' . $match[2] . $match[3] . '[/url]'; + $res = $match[1] . '[url=' . $match[2] . $match[3] . ']' . $match[2] . $match[3] . '[/url]' . $match[4]; return $res; } diff --git a/include/menu.php b/include/menu.php index 1a2059451..88863f57b 100644 --- a/include/menu.php +++ b/include/menu.php @@ -1,5 +1,7 @@ <?php /** @file */ +use Zotlabs\Lib\Libsync; + require_once('include/security.php'); require_once('include/bbcode.php'); @@ -405,7 +407,7 @@ function menu_sync_packet($uid,$observer_hash,$menu_id,$delete = false) { if($m) { if($delete) $m['menu_delete'] = 1; - build_sync_packet($uid,array('menu' => array(menu_element($c,$m)))); + Libsync::build_sync_packet($uid,array('menu' => array(menu_element($c,$m)))); } } } diff --git a/include/message.php b/include/message.php index 7d05b9ab7..37fe6749d 100644 --- a/include/message.php +++ b/include/message.php @@ -299,10 +299,6 @@ function create_conversation($channel,$recipient,$subject) { } - - - - function private_messages_list($uid, $mailbox = '', $start = 0, $numitems = 0) { $where = ''; diff --git a/include/nav.php b/include/nav.php index 672cc2689..b2a061661 100644 --- a/include/nav.php +++ b/include/nav.php @@ -59,6 +59,8 @@ function nav($template = 'default') { if($banner === false) $banner = get_config('system','sitename'); + + call_hooks('get_banner',$banner); App::$page['header'] .= replace_macros(get_markup_template('hdr.tpl'), array( //we could additionally use this to display important system notifications e.g. for updates @@ -294,14 +296,10 @@ function nav($template = 'default') { $app['active'] = true; if($is_owner) { - if(strpos($app['categories'],'nav_pinned_app') === false) { - $nav_apps[] = Apps::app_render($app,'nav'); - } + $nav_apps[] = Apps::app_render($app,'nav'); } elseif(! $is_owner && strpos($app['requires'], 'local_channel') === false) { - if(strpos($app['categories'],'nav_pinned_app') === false) { - $nav_apps[] = Apps::app_render($app,'nav'); - } + $nav_apps[] = Apps::app_render($app,'nav'); } } } diff --git a/include/network.php b/include/network.php index f6992291d..9d4c00ee8 100644 --- a/include/network.php +++ b/include/network.php @@ -61,7 +61,8 @@ function z_fetch_url($url, $binary = false, $redirects = 0, $opts = array()) { @curl_setopt($ch, CURLOPT_CAINFO, get_capath()); @curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); @curl_setopt($ch, CURLOPT_RETURNTRANSFER,true); - @curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (compatible; zot)"); + @curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (compatible; zot)'); + @curl_setopt($ch, CURLOPT_ENCODING, ''); $ciphers = @get_config('system','curl_ssl_ciphers'); if($ciphers) @@ -257,6 +258,7 @@ function z_post_url($url, $params, $redirects = 0, $opts = array()) { @curl_setopt($ch, CURLOPT_POST,1); @curl_setopt($ch, CURLOPT_POSTFIELDS,$params); @curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/5.0 (compatible; zot)"); + @curl_setopt($ch, CURLOPT_ENCODING, ''); $ciphers = @get_config('system','curl_ssl_ciphers'); if($ciphers) @@ -505,7 +507,7 @@ function z_dns_check($h,$check_mx = 0) { // Otherwise we will assume dns_get_record() works as documented - $opts = DNS_A + DNS_CNAME + DNS_PTR; + $opts = DNS_A + DNS_AAAA; if($check_mx) $opts += DNS_MX; @@ -1099,7 +1101,33 @@ function discover_by_webbie($webbie, $protocol = '') { $network = null; $x = webfinger_rfc7033($webbie, true); - if($x && array_key_exists('links',$x) && $x['links']) { + if($x && array_key_exists('links',$x) && is_array($x['links'])) { + + foreach($x['links'] as $link) { + if(array_key_exists('rel',$link)) { + if($link['rel'] === PROTOCOL_ZOT6 && ((! $protocol) || (strtolower($protocol) === 'zot6'))) { + logger('zot6 found for ' . $webbie, LOGGER_DEBUG); + $record = Zotfinger::exec($link['href']); + + // Check the HTTP signature + + $hsig = $record['signature']; + if($hsig && ($hsig['signer'] === $url || $hsig['signer'] === $link['href']) && $hsig['header_valid'] === true && $hsig['content_valid'] === true) + $hsig_valid = true; + + if(! $hsig_valid) { + logger('http signature not valid: ' . print_r($hsig,true)); + continue; + } + + $y = Libzot::import_xchan($record['data']); + if($y['success']) { + return $y['hash']; + } + } + } + } + foreach($x['links'] as $link) { if(array_key_exists('rel',$link)) { @@ -1124,30 +1152,6 @@ function discover_by_webbie($webbie, $protocol = '') { } } - foreach($x['links'] as $link) { - if(array_key_exists('rel',$link)) { - if($link['rel'] === PROTOCOL_ZOT6 && ((! $protocol) || (strtolower($protocol) === 'zot6'))) { - logger('zot6 found for ' . $webbie, LOGGER_DEBUG); - $record = Zotfinger::exec($link['href']); - - // Check the HTTP signature - - $hsig = $record['signature']; - if($hsig && ($hsig['signer'] === $url || $hsig['signer'] === $link['href']) && $hsig['header_valid'] === true && $hsig['content_valid'] === true) - $hsig_valid = true; - - if(! $hsig_valid) { - logger('http signature not valid: ' . print_r($hsig,true)); - continue; - } - - $x = Libzot::import_xchan($record['data']); - if($x['success']) { - return $x['hash']; - } - } - } - } } logger('webfinger: ' . print_r($x,true), LOGGER_DATA, LOG_INFO); @@ -1329,14 +1333,15 @@ function fetch_xrd_links($url) { */ function scrape_feed($url) { - require_once('library/HTML5/Parser.php'); $ret = array(); $level = 0; $x = z_fetch_url($url,false,$level,array('novalidate' => true)); - if(! $x['success']) + if(! $x['success']) { + logger('ERROR fetching URL'); return $ret; + } $headers = $x['header']; $code = $x['return_code']; @@ -1370,17 +1375,16 @@ function scrape_feed($url) { } } + $dom = new DOMDocument(); try { - $dom = HTML5_Parser::parse($s); + $dom->loadHTML( $s); } catch (DOMException $e) { - logger('Parse error: ' . $e); - } - - if(! $dom) { - logger('Failed to parse.'); + logger('Feed parse error: ' . $e); + // logger('Feed parse ERROR: ' . libxml_get_last_error()->message); return $ret; } + $head = $dom->getElementsByTagName('base'); if($head) { foreach($head as $head0) { @@ -1842,15 +1846,15 @@ function probe_api_path($host) { function scrape_vcard($url) { - require_once('library/HTML5/Parser.php'); - $ret = array(); logger('url=' . $url); $x = z_fetch_url($url); - if(! $x['success']) + if(! $x['success']) { + logger('ERROR fetching URL'); return $ret; + } $s = $x['body']; @@ -1867,14 +1871,14 @@ function scrape_vcard($url) { } } + $dom = new DOMDocument(); try { - $dom = HTML5_Parser::parse($s); + $dom->loadHTML( $s); } catch (DOMException $e) { - logger('Parse error: ' . $e); - } - - if(! $dom) + logger('hCard parse error: ' . $e); + // logger('hCard fetch ERROR: ' . libxml_get_last_error()->message); return $ret; + } // Pull out hCard profile elements @@ -2057,3 +2061,23 @@ function get_request_string($url) { return '/' . ((count($a) > 3) ? $a[3] : EMPTY_STR); } + + +/* + * + * Takes the output of parse_url and builds a URL from it + * + */ + +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"; +} diff --git a/include/permissions.php b/include/permissions.php index 501b2cc77..ca8ff6e93 100644 --- a/include/permissions.php +++ b/include/permissions.php @@ -554,4 +554,24 @@ function site_default_perms() { return $ret; } +function their_perms_contains($channel_id,$xchan_hash,$perm) { + $x = get_abconfig($channel_id,$xchan_hash,'system','their_perms'); + if($x) { + $y = explode(',',$x); + if(in_array($perm,$y)) { + return true; + } + } + return false; +} +function my_perms_contains($channel_id,$xchan_hash,$perm) { + $x = get_abconfig($channel_id,$xchan_hash,'system','my_perms'); + if($x) { + $y = explode(',',$x); + if(in_array($perm,$y)) { + return true; + } + } + return false; +} diff --git a/include/photo/photo_driver.php b/include/photo/photo_driver.php index c11580bdc..52f761b65 100644 --- a/include/photo/photo_driver.php +++ b/include/photo/photo_driver.php @@ -135,6 +135,8 @@ function guess_image_type($filename, $headers = '') { $type = 'image/gif'; elseif(strpos(strtolower($filename),'png') !== false) $type = 'image/png'; + elseif(strpos(strtolower($filename),'webp') !== false) + $type = 'image/webp'; } } @@ -188,25 +190,27 @@ function delete_thing_photo($url, $ob_hash) { * * \e string \b 5 => modification date */ function import_xchan_photo($photo, $xchan, $thing = false, $force = false) { + $modified = ''; $o = null; - $flags = (($thing) ? PHOTO_THING : PHOTO_XCHAN); $album = (($thing) ? 'Things' : 'Contact Photos'); logger('Updating channel photo from ' . $photo . ' for ' . $xchan, LOGGER_DEBUG); - if($thing) { + if($thing) $hash = photo_new_resource(); - } else { - $r = q("select resource_id, edited, mimetype from photo where xchan = '%s' and photo_usage = %d and imgscale = 4 limit 1", dbesc($xchan), intval(PHOTO_XCHAN)); + else { + $r = q("SELECT resource_id, edited, mimetype, expires, description FROM photo WHERE xchan = '%s' AND photo_usage = %d AND imgscale = 4 LIMIT 1", dbesc($xchan), intval(PHOTO_XCHAN)); if($r) { $hash = $r[0]['resource_id']; $modified = $r[0]['edited']; $type = $r[0]['mimetype']; - } else { - $hash = photo_new_resource(); + $exp = strtotime($r[0]['expires']); + $etag = $r[0]['description']; } + else + $hash = photo_new_resource(); } $photo_failure = false; @@ -214,37 +218,74 @@ function import_xchan_photo($photo, $xchan, $thing = false, $force = false) { if($photo) { - if($force || $modified == '') { + if($force || empty($modified)) $result = z_fetch_url($photo, true); - } else { - $h = [ - 'headers' => [ - 'If-Modified-Since: ' . gmdate('D, d M Y H:i:s', strtotime($modified . 'Z')) . ' GMT' - ] - ]; - $result = z_fetch_url($photo, true, 0, $h); + elseif($exp - 60 < time()) { + $h = []; + $h[] = "If-Modified-Since: " . gmdate("D, d M Y H:i:s", $exp) . " GMT"; + if(! empty($etag)) + $h[] = "If-None-Match: " . $etag; + $result = z_fetch_url($photo, true, 0, [ 'headers' => $h ]); } - if($result['success']) { - $img_str = $result['body']; - $type = guess_image_type($photo, $result['header']); - $modified = gmdate('Y-m-d H:i:s', (preg_match('/last-modified: (.+) \S+/i', $result['header'], $o) ? strtotime($o[1] . 'Z') : time())); - if(is_null($type)) + if(isset($result)) { + $hdrs = []; + $h = explode("\n", $result['header']); + foreach ($h as $l) { + list($t,$v) = array_map("trim", explode(":", trim($l), 2)); + $hdrs[strtolower($t)] = $v; + } + + if(array_key_exists('expires', $hdrs)) + $expires = strtotime($hdrs['expires']); + if($expires - 60 < time()) + $expires = time() + 60; + else { + $cc = ''; + if(array_key_exists('cache-control', $hdrs)) + $cc = $hdrs['cache-control']; + if(strpos($cc, 'no-cache')) + $expires = time() + 60; + else { + $ttl = (preg_match('/max-age=(\d+)/i', $cc, $o) ? intval($o[1]) : 86400); + $expires = time() + $ttl; + } + } + + $modified = gmdate('Y-m-d H:i:s', (array_key_exists('last-modified', $hdrs) ? strtotime($hdrs['last-modified']) : time())); + + if($result['success']) { + $img_str = $result['body']; + $type = guess_image_type($photo, $result['header']); + if(is_null($type)) + $photo_failure = true; + } + else $photo_failure = true; - } elseif($result['return_code'] == 304) { + } + + if(! isset($result) || $result['return_code'] == 304) { $photo = z_root() . '/photo/' . $hash . '-4'; $thumb = z_root() . '/photo/' . $hash . '-5'; $micro = z_root() . '/photo/' . $hash . '-6'; - } else { + if(isset($result)) + q("UPDATE photo SET expires = '%s' WHERE xchan = '%s' and photo_usage = %d and imgscale IN (4, 5, 6)", + dbescdate(gmdate('Y-m-d H:i:s', (isset($expires) ? $expires : time() + 86400))), + dbesc($xchan), + intval(PHOTO_XCHAN) + ); + $photo_notmodified = true; $photo_failure = true; } - } else { - $photo_failure = true; } + else + $photo_failure = true; + + if(! $photo_failure) { - if(!$photo_failure && $result['return_code'] != 304) { $img = photo_factory($img_str, $type); if($img->is_valid()) { + $width = $img->getWidth(); $height = $img->getHeight(); @@ -253,25 +294,28 @@ function import_xchan_photo($photo, $xchan, $thing = false, $force = false) { // crop out the sides $margin = $width - $height; $img->cropImage(300, ($margin / 2), 0, $height, $height); - } elseif(($height / $width) > 1.2) { + } + elseif(($height / $width) > 1.2) { // crop out the bottom $margin = $height - $width; $img->cropImage(300, 0, 0, $width, $width); - } else { - $img->scaleImageSquare(300); } - } else { - $photo_failure = true; + else + $img->scaleImageSquare(300); } + else + $photo_failure = true; $p = [ - 'xchan' => $xchan, - 'resource_id' => $hash, - 'filename' => basename($photo), - 'album' => $album, - 'photo_usage' => $flags, - 'imgscale' => 4, - 'edited' => $modified, + 'xchan' => $xchan, + 'resource_id' => $hash, + 'filename' => basename($photo), + 'album' => $album, + 'photo_usage' => $flags, + 'imgscale' => 4, + 'edited' => $modified, + 'description' => (array_key_exists('etag', $hdrs) ? $hdrs['etag'] : ''), + 'expires' => gmdate('Y-m-d H:i:s', (isset($expires) ? $expires : time() + 86400)) ]; $r = $img->save($p); @@ -293,12 +337,14 @@ function import_xchan_photo($photo, $xchan, $thing = false, $force = false) { $photo = z_root() . '/photo/' . $hash . '-4'; $thumb = z_root() . '/photo/' . $hash . '-5'; $micro = z_root() . '/photo/' . $hash . '-6'; - } else { + } + else { logger('Invalid image from ' . $photo); $photo_failure = true; } } - if($photo_failure) { + + if($photo_failure && ! isset($photo_notmodified)) { $default = get_default_profile_photo(); $photo = z_root() . '/' . $default; $thumb = z_root() . '/' . get_default_profile_photo(80); @@ -307,12 +353,12 @@ function import_xchan_photo($photo, $xchan, $thing = false, $force = false) { $modified = gmdate('Y-m-d H:i:s', filemtime($default)); } - logger('HTTP code: ' . $result['return_code'] . '; modified: ' . $modified - . '; failure: ' . ($photo_failure ? 'yes' : 'no') . '; URL: ' . $photo, LOGGER_DEBUG); + logger('xchan: ' . $xchan . '; HTTP code: ' . (isset($result) && array_key_exists('return_code', $result) ? $result['return_code'] : 'none') . '; modified: ' . $modified . '; URL: ' . $photo, LOGGER_DEBUG); - return([$photo, $thumb, $micro, $type, $photo_failure, $modified]); + return([ $photo, $thumb, $micro, $type, $photo_failure, $modified ]); } + /** * @brief Import channel photo from a URL. * diff --git a/include/photos.php b/include/photos.php index ee662f707..11dd07586 100644 --- a/include/photos.php +++ b/include/photos.php @@ -388,7 +388,7 @@ function photo_upload($channel, $observer, $args) { 'title' => $title, 'created' => $p['created'], 'edited' => $p['edited'], - 'id' => z_root() . '/photos/' . $channel['channel_address'] . '/image/' . $photo_hash, + 'id' => z_root() . '/item/' . $photo_hash, 'link' => $link, 'body' => $summary ); @@ -438,13 +438,13 @@ function photo_upload($channel, $observer, $args) { } } else { - $uuid = item_message_id(); - $mid = z_root() . '/item/' . $uuid; + // $uuid = item_message_id(); + $mid = z_root() . '/item/' . $photo_hash; $arr = [ 'aid' => $account_id, 'uid' => $channel_id, - 'uuid' => $uuid, + 'uuid' => $photo_hash, 'mid' => $mid, 'parent_mid' => $mid, 'item_hidden' => $item_hidden, @@ -469,7 +469,7 @@ function photo_upload($channel, $observer, $args) { 'body' => $summary ]; - $arr['plink'] = z_root() . '/channel/' . $channel['channel_address'] . '/?f=&mid=' . urlencode($arr['mid']); + $arr['plink'] = $mid; if($lat && $lon) $arr['coord'] = $lat . ' ' . $lon; @@ -850,7 +850,7 @@ function photos_create_item($channel, $creator_hash, $photo, $visible = false) { $arr['deny_cid'] = $photo['deny_cid']; $arr['deny_gid'] = $photo['deny_gid']; - $arr['plink'] = z_root() . '/channel/' . $channel['channel_address'] . '/?f=&mid=' . urlencode($arr['mid']); + $arr['plink'] = $mid; $arr['body'] = '[zrl=' . z_root() . '/photos/' . $channel['channel_address'] . '/image/' . $photo['resource_id'] . ']' . '[zmg]' . z_root() . '/photo/' . $photo['resource_id'] . '-' . $photo['imgscale'] . '[/zmg]' diff --git a/include/queue_fn.php b/include/queue_fn.php index 865228041..b72730d2f 100644 --- a/include/queue_fn.php +++ b/include/queue_fn.php @@ -228,49 +228,49 @@ function queue_deliver($outq, $immediate = false) { // normal zot delivery - logger('deliver: dest: ' . $outq['outq_posturl'], LOGGER_DEBUG); + logger('deliver: dest: ' . $outq['outq_posturl'] . ' driver: ' . $outq['outq_driver'], LOGGER_DEBUG); if($outq['outq_driver'] === 'zot6') { - if($outq['outq_posturl'] === z_root() . '/zot') { - // local delivery - $zot = new Receiver(new Zot6Handler(),$outq['outq_notify']); - $result = $zot->run(true); - logger('returned_json: ' . json_encode($result,JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES), LOGGER_DATA); - logger('deliver: local zot6 delivery succeeded to ' . $outq['outq_posturl']); - Libzot::process_response($outq['outq_posturl'],[ 'success' => true, 'body' => json_encode($result) ], $outq); - } - else { - logger('remote'); - $channel = null; - - if($outq['outq_channel']) { - $channel = channelx_by_n($outq['outq_channel']); - } - - $host_crypto = null; - if($channel && $base) { - $h = q("select hubloc_sitekey, site_crypto from hubloc left join site on hubloc_url = site_url where site_url = '%s' and hubloc_network = 'zot6' order by hubloc_id desc limit 1", - dbesc($base) - ); - if($h) { - $host_crypto = $h[0]; - } - } - - $msg = $outq['outq_notify']; - - $result = Libzot::zot($outq['outq_posturl'],$msg,$channel,$host_crypto); - - if($result['success']) { - logger('deliver: remote zot6 delivery succeeded to ' . $outq['outq_posturl']); - Libzot::process_response($outq['outq_posturl'],$result, $outq); - } - else { - logger('deliver: remote zot6 delivery failed to ' . $outq['outq_posturl']); - logger('deliver: remote zot6 delivery fail data: ' . print_r($result,true), LOGGER_DATA); - update_queue_item($outq['outq_hash'],10); - } + if($outq['outq_posturl'] === z_root() . '/zot') { + // local delivery + $zot = new Receiver(new Zot6Handler(),$outq['outq_notify']); + $result = $zot->run(true); + logger('returned_json: ' . json_encode($result,JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES), LOGGER_DATA); + logger('deliver: local zot6 delivery succeeded to ' . $outq['outq_posturl']); + Libzot::process_response($outq['outq_posturl'],[ 'success' => true, 'body' => json_encode($result) ], $outq); + } + else { + logger('remote'); + $channel = null; + + if($outq['outq_channel']) { + $channel = channelx_by_n($outq['outq_channel']); + } + + $host_crypto = null; + if($channel && $base) { + $h = q("select hubloc_sitekey, site_crypto from hubloc left join site on hubloc_url = site_url where site_url = '%s' and hubloc_network = 'zot6' order by hubloc_id desc limit 1", + dbesc($base) + ); + if($h) { + $host_crypto = $h[0]; + } + } + + $msg = $outq['outq_notify']; + + $result = Libzot::zot($outq['outq_posturl'],$msg,$channel,$host_crypto); + + if($result['success']) { + logger('deliver: remote zot6 delivery succeeded to ' . $outq['outq_posturl']); + Libzot::process_response($outq['outq_posturl'],$result, $outq); + } + else { + logger('deliver: remote zot6 delivery failed to ' . $outq['outq_posturl']); + logger('deliver: remote zot6 delivery fail data: ' . print_r($result,true), LOGGER_DATA); + update_queue_item($outq['outq_hash'],10); + } } return; diff --git a/include/security.php b/include/security.php index 38cb72263..c9df00f1e 100644 --- a/include/security.php +++ b/include/security.php @@ -594,9 +594,11 @@ function check_form_security_token($typename = '', $formname = 'form_security_to $hash = $_REQUEST[$formname]; $max_livetime = 10800; // 3 hours + $min_livetime = 3; // 3 sec $x = explode('.', $hash); - if (time() > (IntVal($x[0]) + $max_livetime)) return false; + if (time() > (IntVal($x[0]) + $max_livetime) || time() < (IntVal($x[0]) + $min_livetime)) + return false; $sec_hash = hash('whirlpool', App::$observer['xchan_guid'] . ((local_channel()) ? App::$channel['channel_prvkey'] : '') . session_id() . $x[0] . $typename); diff --git a/include/socgraph.php b/include/socgraph.php index 3d26f5cfd..3da4dce63 100644 --- a/include/socgraph.php +++ b/include/socgraph.php @@ -158,7 +158,7 @@ function poco_load($xchan = '', $url = null) { if($address) { if($network === 'zot6') { $j = Zotfinger::exec($profile_url); - if(is_array($j) && array_path_exists('signature/signer',$j) && $j['signature']['signer'] === $profile_url && intval($j['signature']['header_valid'])) { + if(array_path_exists('signature/signer',$j) && $j['signature']['signer'] === $profile_url && intval($j['signature']['header_valid'])) { Libzot::import_xchan($j['data']); } $x = q("select xchan_hash from xchan where xchan_hash = '%s' limit 1", diff --git a/include/taxonomy.php b/include/taxonomy.php index b0304de5b..e06568d19 100644 --- a/include/taxonomy.php +++ b/include/taxonomy.php @@ -5,6 +5,9 @@ // and save to file categories in square brackets. // To do this we need to escape these characters if they appear in our tag. +use Zotlabs\Lib\Cache; + + function file_tag_encode($s) { return str_replace(array('<','>','[',']'),array('%3c','%3e','%5b','%5d'),$s); } @@ -327,50 +330,56 @@ function pubtagblock($net,$site,$limit,$recent = 0,$safemode = 1, $type = TERM_H return $o; } -function pub_tagadelic($net,$site,$limit,$recent,$safemode,$type) { - - - $item_normal = item_normal(); - $count = intval($limit); - - if($site) { - $uids = " and item.uid in ( " . stream_perms_api_uids(PERMS_PUBLIC) . " ) and item_private = 0 and item_wall = 1 "; - } - else { - $sys = get_sys_channel(); - $uids = " and item.uid = " . intval($sys['channel_id']) . " "; - $sql_extra = " and item_private = 0 "; - } - - if($recent) - $sql_extra .= " and item.created > '" . datetime_convert('UTC','UTC', 'now - ' . intval($recent) . ' days ') . "' "; +function pub_tagadelic($net,$site,$limit,$recent,$safemode,$type) { - if($safemode) { - $unsafetags = get_config('system','unsafepubtags', [ 'boobs', 'bot', 'rss', 'girl','girls', 'nsfw', 'sexy', 'nude' ]); - if($unsafetags) { - $sql_extra .= " and not term.term in ( " . stringify_array($unsafetags,true) . ") "; - } - } - - - // Fetch tags - $r = q("select term, count(term) as total from term left join item on term.oid = item.id - where term.ttype = %d - and otype = %d and item_type = %d - $sql_extra $uids $item_normal - group by term order by total desc %s", - intval($type), - intval(TERM_OBJ_POST), - intval(ITEM_TYPE_POST), - ((intval($count)) ? "limit $count" : '') - ); - - if(! $r) - return array(); - - return Zotlabs\Text\Tagadelic::calc($r); - + $item_normal = item_normal(); + $count = intval($limit); + + if($site) + $uids = " and item.uid in ( " . stream_perms_api_uids(PERMS_PUBLIC) . " ) and item_private = 0 and item_wall = 1 "; + else { + $sys = get_sys_channel(); + $uids = " and item.uid = " . intval($sys['channel_id']) . " "; + $sql_extra = " and item_private = 0 "; + } + + if($recent) + $sql_extra .= " and item.created > '" . datetime_convert('UTC','UTC', 'now - ' . intval($recent) . ' days ') . "' "; + + + if($safemode) { + $unsafetags = get_config('system','unsafepubtags', [ 'boobs', 'bot', 'rss', 'girl','girls', 'nsfw', 'sexy', 'nude' ]); + if($unsafetags) { + $sql_extra .= " and not term.term in ( " . stringify_array($unsafetags,true) . ") "; + } + } + + + $key = __FUNCTION__ . "-" . md5($site . $recent . $safemode . $limit . $type); + $content = Cache::get($key, '1 MINUTE'); + + if(! $content) { + // Fetch tags + $r = q("SELECT term, count(term) AS total FROM term LEFT JOIN item ON term.oid = item.id + where term.ttype = %d + and otype = %d and item_type = %d + $sql_extra $uids $item_normal + group by term order by total desc %s", + intval($type), + intval(TERM_OBJ_POST), + intval(ITEM_TYPE_POST), + ((intval($count)) ? "limit $count" : '') + ); + } else + $r = unserialize($content); + + if(! $r) + return array(); + else + Cache::set($key, serialize($r)); + + return Zotlabs\Text\Tagadelic::calc($r); } diff --git a/include/text.php b/include/text.php index 44af40810..58cde7838 100644 --- a/include/text.php +++ b/include/text.php @@ -659,6 +659,26 @@ function hz_syslog($msg, $priority = LOG_INFO) { closelog(); } +/** + * @brief like hz_syslog() but with a function backtrace to pinpoint certain classes + * of problems which show up deep in the calling stack. + * + * @param string $msg Message to log + * @param int $priority - compatible with syslog + */ +function bt_syslog($msg, $priority = LOG_INFO) { + $stack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); + if($stack) { + for($x = 1; $x < count($stack); $x ++) { + $s = 'stack: ' . basename($stack[$x]['file']) . ':' . $stack[$x]['line'] . ':' . $stack[$x]['function'] . '()'; + openlog("bt-log", LOG_PID | LOG_PERROR, LOG_LOCAL0); + syslog($priority, $s); + closelog(); + } + } +} + + /** * @brief Logging function for Hubzilla. @@ -742,7 +762,6 @@ function btlogger($msg, $level = LOGGER_NORMAL, $priority = LOG_INFO) { for($x = 1; $x < count($stack); $x ++) { $s = 'stack: ' . basename($stack[$x]['file']) . ':' . $stack[$x]['line'] . ':' . $stack[$x]['function'] . '()'; logger($s,$level, $priority); - if(file_exists(BTLOGGER_DEBUG_FILE) && is_writable(BTLOGGER_DEBUG_FILE)) { @file_put_contents(BTLOGGER_DEBUG_FILE, $s . PHP_EOL, FILE_APPEND); } @@ -864,6 +883,11 @@ function get_tags($s) { $ret[] = $mtch; } } + if(preg_match_all('/([@#\!]\".*?\")/',$s,$match)) { + foreach($match[1] as $mtch) { + $ret[] = $mtch; + } + } // match bracket mentions @@ -1010,16 +1034,36 @@ function contact_block() { if(count($r)) { $contacts = t('Connections'); - $micropro = Array(); + $micropro = []; foreach($r as $rr) { // There is no setting to discover if you are bi-directionally connected // Use the ability to post comments as an indication that this relationship is more // than wishful thinking; even though soapbox channels and feeds will disable it. + $rr['perminfo']['connpermcount']=0; + $rr['perminfo']['connperms']=t('Accepts').': '; + if(intval(get_abconfig(App::$profile['uid'],$rr['xchan_hash'],'their_perms','post_comments'))) { + $rr['perminfo']['connpermcount']++; + $rr['perminfo']['connperms'] .= t('Comments'); + } + if(intval(get_abconfig(App::$profile['uid'],$rr['xchan_hash'],'their_perms','send_stream'))) { + $rr['perminfo']['connpermcount']++; + $rr['perminfo']['connperms'] = ($rr['perminfo']['connperms']) ? $rr['perminfo']['connperms'] . ', ' : $rr['perminfo']['connperms'] ; + $rr['perminfo']['connperms'] .= t('Stream items'); + } + if(intval(get_abconfig(App::$profile['uid'],$rr['xchan_hash'],'their_perms','post_wall'))) { + $rr['perminfo']['connpermcount']++; + $rr['perminfo']['connperms'] = ($rr['perminfo']['connperms']) ? $rr['perminfo']['connperms'] . ', ' : $rr['perminfo']['connperms'] ; + $rr['perminfo']['connperms'] .= t('Wall posts'); + } - if(! intval(get_abconfig(App::$profile['uid'],$rr['xchan_hash'],'their_perms','post_comments'))) { - $rr['oneway'] = true; + if ($rr['perminfo']['connpermcount'] == 0) { + $rr['perminfo']['connperms'] .= t('Nothing'); } + + if(!$is_owner && $rr['perminfo']['connpermcount'] !== 0) + unset($rr['perminfo']); + $micropro[] = micropro($rr,true,'mpfriend'); } } @@ -1030,7 +1074,7 @@ function contact_block() { '$contacts' => $contacts, '$nickname' => App::$profile['channel_address'], '$viewconnections' => (($total > $shown) ? sprintf(t('View all %s connections'),$total) : ''), - '$micropro' => $micropro, + '$micropro' => $micropro )); $arr = ['contacts' => $r, 'output' => $o]; @@ -1086,6 +1130,7 @@ function micropro($contact, $redirect = false, $class = '', $mode = false) { '$click' => (($contact['click']) ? $contact['click'] : ''), '$class' => $class . (($contact['archived']) ? ' archived' : ''), '$oneway' => (($contact['oneway']) ? true : false), + '$perminfo' => $contact['perminfo'], '$url' => $url, '$photo' => $contact['xchan_photo_s'], '$name' => $contact['xchan_name'], @@ -1713,6 +1758,11 @@ function prepare_body(&$item,$attach = false,$opts = false) { } } + $poll = (($item['obj_type'] === 'Question' && in_array($item['verb'],[ ACTIVITY_POST, ACTIVITY_UPDATE, ACTIVITY_SHARE ])) ? format_poll($item, $s, $opts) : false); + if ($poll) { + $s = $poll; + } + $event = (($item['obj_type'] === ACTIVITY_OBJ_EVENT) ? format_event_obj($item['obj']) : false); $prep_arr = [ @@ -1796,6 +1846,89 @@ function prepare_binary($item) { } +function format_poll($item,$s,$opts) { + + if (! is_array($item['obj'])) { + $act = json_decode($item['obj'],true); + } + else { + $act = $item['obj']; + } + + if (! is_array($act)) { + return EMPTY_STR; + } + + $commentable = can_comment_on_post(((local_channel()) ? get_observer_hash() : EMPTY_STR),$item); + + //logger('format_poll: ' . print_r($item,true)); + $activated = ((local_channel() && local_channel() == $item['uid']) ? true : false); + $output = $s . EOL. EOL; + + if ($act['type'] === 'Question') { + if ($activated and $commentable) { + $output .= '<form id="question-form-' . $item['id'] . '" >'; + } + if (array_key_exists('anyOf',$act) && is_array($act['anyOf'])) { + foreach ($act['anyOf'] as $poll) { + if (array_key_exists('name',$poll) && $poll['name']) { + $text = html2plain(purify_html($poll['name']),256); + if (array_path_exists('replies/totalItems',$poll)) { + $total = $poll['replies']['totalItems']; + } + else { + $total = 0; + } + if ($activated && $commentable) { + $output .= '<input type="checkbox" name="answer[]" value="' . htmlspecialchars($text) . '"> ' . $text . '</input>' . ' (' . $total . ')' . EOL; + } + else { + $output .= '[ ] ' . $text . ' (' . $total . ')' . EOL; + } + } + } + } + if (array_key_exists('oneOf',$act) && is_array($act['oneOf'])) { + foreach ($act['oneOf'] as $poll) { + if (array_key_exists('name',$poll) && $poll['name']) { + $text = html2plain(purify_html($poll['name']),256); + if (array_path_exists('replies/totalItems',$poll)) { + $total = $poll['replies']['totalItems']; + } + else { + $total = 0; + } + if ($activated && $commentable) { + $output .= '<input type="radio" name="answer" value="' . htmlspecialchars($text) . '"> ' . $text . '</input>' . ' (' . $total . ')' . EOL; + } + else { + $output .= '( ) ' . $text . ' (' . $total . ')' . EOL; + } + } + } + } + if ($item['comments_closed'] > NULL_DATE) { + $t = datetime_convert('UTC',date_default_timezone_get(), $item['comments_closed'], 'Y-m-d H:i'); + $closed = ((datetime_convert() > $item['comments_closed']) ? true : false); + if ($closed) { + $message = t('Poll has ended.'); + } + else { + $message = sprintf(t('Poll ends: %s'),$t); + } + $output .= EOL . '<div>' . $message . '</div>'; + } + if ($activated and $commentable) { + $output .= EOL . '<input type="button" class="btn btn-std btn-success" name="vote" value="' . t("Vote") . '" onclick="submitPoll(' . $item['id'] . '); return false;">'. '</form>'; + } + + } + return $output; +} + + + + /** * @brief Given a text string, convert from content_type to HTML. * @@ -1932,7 +2065,7 @@ function get_plink($item,$conversation_mode = true) { $zidify = true; - if(array_key_exists('author',$item) && $item['author']['xchan_network'] !== 'zot') + if(array_key_exists('author',$item) && in_array($item['author']['xchan_network'], ['zot6', 'zot']) === false) $zidify = false; if(x($item,$key)) { @@ -2122,7 +2255,7 @@ function item_post_type($item) { $post_type = t('event'); break; default: - $post_type = t('status'); + $post_type = t('post'); if($item['mid'] != $item['parent_mid']) $post_type = t('comment'); break; @@ -2131,6 +2264,9 @@ function item_post_type($item) { if(strlen($item['verb']) && (! activity_match($item['verb'],ACTIVITY_POST))) $post_type = t('activity'); + if($item['obj_type'] === 'Question') + $post_type = t('poll'); + return $post_type; } @@ -2686,6 +2822,10 @@ function handle_tag(&$body, &$str_tags, $profile_uid, $tag, $in_network = true) $basetag = substr($tag,7); $basetag = substr($basetag,0,-6); } + elseif((substr($tag,0,2) === '#"') && (substr($tag,-1,1) === '"')) { + $basetag = substr($tag,2); + $basetag = substr($basetag,0,-1); + } else $basetag = substr($tag,1); @@ -2768,6 +2908,10 @@ function handle_tag(&$body, &$str_tags, $profile_uid, $tag, $in_network = true) $newname = substr($name,6); $newname = substr($newname,0,-6); } + elseif((substr($name,0,1) === '"') && (substr($name,-1,1) === '"')) { + $newname = substr($name,1); + $newname = substr($newname,0,-1); + } // select someone from this user's contacts by name @@ -2974,6 +3118,7 @@ function getIconFromType($type) { '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', //Archive 'application/zip' => 'fa-file-archive-o', @@ -3535,11 +3680,15 @@ function get_forum_channels($uid) { if(! $uid) return; - $xf = false; + if(App::$data['forum_channels']) + return App::$data['forum_channels']; + + $xf = ''; $x1 = q("select xchan from abconfig where chan = %d and cat = 'their_perms' and k = 'send_stream' and v = '0'", intval($uid) ); + if($x1) { $xc = ids_to_querystr($x1,'xchan',true); @@ -3547,22 +3696,21 @@ function get_forum_channels($uid) { intval($uid) ); - if($x2) { - $xf = ids_to_querystr($x2,'xchan',true); + $xf = ids_to_querystr($x2,'xchan',true); + $sql_extra = (($xf) ? ' and not xchan in (' . $xf . ')' : ''); - // private forums - $x3 = q("select xchan from abconfig where chan = %d and cat = 'their_perms' and k = 'post_wall' and v = '1' and xchan in (" . $xc . ") and not xchan in (" . $xf . ") ", - intval(local_channel()) - ); - if($x3) { - $xf = ids_to_querystr(array_merge($x2,$x3),'xchan',true); - } + // private forums + $x3 = q("select xchan from abconfig where chan = %d and cat = 'their_perms' and k = 'post_wall' and v = '1' and xchan in (" . $xc . ") $sql_extra ", + intval(local_channel()) + ); + if($x3) { + $xf = ids_to_querystr(array_merge($x2,$x3),'xchan',true); } } - $sql_extra = (($xf) ? " and ( xchan_hash in (" . $xf . ") or xchan_pubforum = 1 ) " : " and xchan_pubforum = 1 "); + $sql_extra_1 = (($xf) ? " and ( xchan_hash in (" . $xf . ") or xchan_pubforum = 1 ) " : " and xchan_pubforum = 1 "); - $r = q("select abook_id, xchan_hash, xchan_name, xchan_url, xchan_addr, xchan_photo_s from abook left join xchan on abook_xchan = xchan_hash where xchan_deleted = 0 and abook_channel = %d and abook_pending = 0 and abook_ignored = 0 and abook_blocked = 0 and abook_archived = 0 $sql_extra order by xchan_name", + $r = q("select abook_id, xchan_hash, xchan_name, xchan_url, xchan_addr, xchan_photo_s from abook left join xchan on abook_xchan = xchan_hash where xchan_deleted = 0 and abook_channel = %d and abook_pending = 0 and abook_ignored = 0 and abook_blocked = 0 and abook_archived = 0 $sql_extra_1 order by xchan_name", intval($uid) ); @@ -3576,6 +3724,8 @@ function get_forum_channels($uid) { } } + App::$data['forum_channels'] = $r; + return $r; } @@ -3625,7 +3775,7 @@ function array_path_exists($str,$arr) { if($search) { foreach($search as $s) { - if(array_key_exists($s,$ptr)) { + if ($ptr && is_array($ptr) && array_key_exists($s,$ptr)) { $ptr = $ptr[$s]; } else { @@ -3675,3 +3825,27 @@ function svg2bb($s) { } return EMPTY_STR; } + + + +function serialise($x) { + return ((is_array($x)) ? 'json:' . json_encode($x) : $x); +} + +function unserialise($x) { + if (is_array($x)) { + return $x; + } + $y = ((substr($x,0,5) === 'json:') ? json_decode(substr($x,5),true) : ''); + return ((is_array($y)) ? $y : $x); +} + +/** + * @brief Remove new lines and tabs from strings. + * + * @return string + */ +function sanitize_text_field($str) { + return preg_replace('/\s+/S', ' ', $str); +} + diff --git a/include/xchan.php b/include/xchan.php index d69d707aa..5de828e7f 100644 --- a/include/xchan.php +++ b/include/xchan.php @@ -25,7 +25,7 @@ function xchan_store_lowlevel($arr) { 'xchan_connurl' => ((array_key_exists('xchan_connurl',$arr)) ? $arr['xchan_connurl'] : ''), 'xchan_follow' => ((array_key_exists('xchan_follow',$arr)) ? $arr['xchan_follow'] : ''), 'xchan_connpage' => ((array_key_exists('xchan_connpage',$arr)) ? $arr['xchan_connpage'] : ''), - 'xchan_name' => ((array_key_exists('xchan_name',$arr)) ? $arr['xchan_name'] : ''), + 'xchan_name' => ((array_key_exists('xchan_name',$arr)) ? sanitize_text_field($arr['xchan_name']) : ''), 'xchan_network' => ((array_key_exists('xchan_network',$arr)) ? $arr['xchan_network'] : ''), 'xchan_instance_url' => ((array_key_exists('xchan_instance_url',$arr)) ? $arr['xchan_instance_url'] : ''), 'xchan_flags' => ((array_key_exists('xchan_flags',$arr)) ? intval($arr['xchan_flags']) : 0), @@ -264,4 +264,4 @@ function xchan_change_key($oldx,$newx,$data) { foreach($acls as $k => $v) { xchan_keychange_acl($k,$v,$oldx,$newx); } -}
\ No newline at end of file +} diff --git a/include/zid.php b/include/zid.php index 3b3dd8554..538adcc41 100644 --- a/include/zid.php +++ b/include/zid.php @@ -1,5 +1,6 @@ <?php +use Zotlabs\Lib\Libzot; use Zotlabs\Lib\Verify; function is_matrix_url($url) { @@ -278,44 +279,39 @@ function owt_init($token) { Verify::purge('owt', '3 MINUTE'); - $key = Verify::get_meta('owt', 0, $token); + $ob_hash = Verify::get_meta('owt', 0, $token); - if($key === false) { - return; - } - - $parts = explode(',',$key,2); - if(count($parts) < 2) { + if($ob_hash === false) { return; } $r = q("select * from hubloc left join xchan on xchan_hash = hubloc_hash - where hubloc_network = '%s' and hubloc_addr = '%s' order by hubloc_id desc", - dbesc($parts[0]), - dbesc($parts[1]) + where hubloc_addr = '%s' or hubloc_id_url = '%s' or hubloc_hash = '%s' order by hubloc_id desc", + dbesc($ob_hash), + dbesc($ob_hash), + dbesc($ob_hash) ); if(! $r) { - // finger them if they can't be found. - // @todo check that this is still needed. Discovery should have been performed in the Owa module. - - $j = \Zotlabs\Zot\Finger::run($parts[1], null); - if ($j['success']) { - import_xchan($j); + $wf = discover_by_webbie($ob_hash); + if($wf) { $r = q("select * from hubloc left join xchan on xchan_hash = hubloc_hash - where hubloc_network = '%s' and hubloc_addr = '%s' order by hubloc_id desc", - dbesc($parts[0]), - dbesc($parts[1]) + where hubloc_addr = '%s' or hubloc_id_url = '%s' or hubloc_hash = '%s' order by hubloc_id desc", + dbesc($ob_hash), + dbesc($ob_hash), + dbesc($ob_hash) ); } } if(! $r) { - logger('owt: unable to finger ' . $key); + logger('owt: unable to finger ' . $ob_hash); return; } - - $hubloc = $r[0]; + + $r = Libzot::zot_record_preferred($r); + + $hubloc = $r; $_SESSION['authenticated'] = 1; @@ -341,7 +337,7 @@ function owt_init($token) { if (! $delegate_success) { // normal visitor (remote_channel) login session credentials $_SESSION['visitor_id'] = $hubloc['xchan_hash']; - $_SESSION['my_url'] = $hubloc['xchan_url']; + $_SESSION['my_url'] = $hubloc['xchan_url']; $_SESSION['my_address'] = $hubloc['hubloc_addr']; $_SESSION['remote_hub'] = $hubloc['hubloc_url']; $_SESSION['DNT'] = 1; @@ -401,9 +397,7 @@ function observer_auth($ob_hash) { return; } - // Note: this has no Libzot namespace so prefers zot over zot6 - - $hubloc = zot_record_preferred($r); + $hubloc = Libzot::zot_record_preferred($r); $_SESSION['authenticated'] = 1; diff --git a/include/zot.php b/include/zot.php index d08146287..8a2177de7 100644 --- a/include/zot.php +++ b/include/zot.php @@ -9,6 +9,8 @@ */ use Zotlabs\Lib\DReport; +use Zotlabs\Lib\Libzot; +use Zotlabs\Lib\Activity; require_once('include/crypto.php'); require_once('include/items.php'); @@ -407,8 +409,8 @@ function zot_refresh($them, $channel = null, $force = false) { $postvars['token'] = $token; if($channel) { - $postvars['target'] = $channel['channel_guid']; - $postvars['target_sig'] = $channel['channel_guid_sig']; + $postvars['target'] = $channel['xchan_guid']; + $postvars['target_sig'] = str_replace('sha256.', '', $channel['xchan_guid_sig']); $postvars['key'] = $channel['channel_pubkey']; } @@ -426,7 +428,6 @@ function zot_refresh($them, $channel = null, $force = false) { logger('zot_refresh: ' . $url, LOGGER_DATA, LOG_INFO); - $result = z_post_url($url . $rhs,$postvars); if ($result['success']) { @@ -578,7 +579,7 @@ function zot_refresh($them, $channel = null, $force = false) { [ 'type' => NOTIFY_INTRO, 'from_xchan' => $x['hash'], - 'to_xchan' => $channel['channel_hash'], + 'to_xchan' => $channel['channel_portable_id'], 'link' => z_root() . '/connedit/' . $new_connection[0]['abook_id'] ] ); @@ -921,7 +922,7 @@ function import_xchan($arr, $ud_flags = UPDATE_FLAGS_UPDATED, $ud_arr = null) { // see if this is a channel clone that's hosted locally - which we treat different from other xchans/connections - $local = q("select channel_account_id, channel_id from channel where channel_hash = '%s' limit 1", + $local = q("select channel_account_id, channel_id from channel where channel_portable_id = '%s' limit 1", dbesc($xchan_hash) ); @@ -1299,7 +1300,7 @@ function zot_fetch($arr) { * * @returns array * Suitable for logging remotely, enumerating the processing results of each message/recipient combination - * * [0] => \e string $channel_hash + * * [0] => \e string $channel_portable_id * * [1] => \e string $delivery_status * * [2] => \e string $address */ @@ -1385,7 +1386,7 @@ function zot_import($arr, $sender_url) { if($recip_arr) { stringify_array_elms($recip_arr); $recips = implode(',',$recip_arr); - $r = q("select channel_hash as hash from channel where channel_hash in ( " . $recips . " ) + $r = q("select channel_portable_id as hash from channel where channel_portable_id in ( " . $recips . " ) and channel_removed = 0 "); } @@ -1596,11 +1597,11 @@ function public_recips($msg) { $r = array(); - $c = q("select channel_id, channel_hash from channel where channel_removed = 0"); + $c = q("select channel_id, channel_portable_id from channel where channel_removed = 0"); if($c) { foreach($c as $cc) { if(perm_is_allowed($cc['channel_id'],$msg['notify']['sender']['hash'],$perm)) { - $r[] = [ 'hash' => $cc['channel_hash'] ]; + $r[] = [ 'hash' => $cc['channel_portable_id'] ]; } } } @@ -1610,7 +1611,7 @@ function public_recips($msg) { if($include_sys && array_key_exists('public_scope',$msg['message']) && $msg['message']['public_scope'] === 'public') { $sys = get_sys_channel(); if($sys) - $r[] = [ 'hash' => $sys['channel_hash'] ]; + $r[] = [ 'hash' => $sys['channel_portable_id'] ]; } // look for any public mentions on this site @@ -1624,7 +1625,7 @@ function public_recips($msg) { if(($tag['type'] === 'mention' || $tag['type'] === 'forum') && (strpos($tag['url'],z_root()) !== false)) { $address = basename($tag['url']); if($address) { - $z = q("select channel_hash as hash from channel where channel_address = '%s' + $z = q("select channel_portable_id as hash from channel where channel_address = '%s' and channel_removed = 0 limit 1", dbesc($address) ); @@ -1727,7 +1728,7 @@ function allowed_public_recips($msg) { $condensed_recips[] = $rr['hash']; $results = array(); - $r = q("select channel_hash as hash, channel_id from channel left join abook on abook_channel = channel_id where abook_xchan = '%s' and channel_removed = 0 ", + $r = q("select channel_portable_id as hash, channel_id from channel left join abook on abook_channel = channel_id where abook_xchan = '%s' and channel_removed = 0 ", dbesc($hash) ); if($r) { @@ -1776,7 +1777,7 @@ function process_delivery($sender, $arr, $deliveries, $relay, $public = false, $ $DR = new Zotlabs\Lib\DReport(z_root(),$sender['hash'],$d['hash'],$arr['mid']); - $channel = channelx_by_hash($d['hash']); + $channel = channelx_by_portid($d['hash']); if(! $channel) { $DR->update('recipient not found'); @@ -1953,6 +1954,34 @@ function process_delivery($sender, $arr, $deliveries, $relay, $public = false, $ intval($channel['channel_id']), dbesc($arr['owner_xchan']) ); + + if(! $ab) { + + $best_owner_xchan = find_best_zot_identity($arr['owner_xchan']); + + $ab = q("select * from abook where abook_channel = %d and abook_xchan = '%s'", + intval($channel['channel_id']), + dbesc($best_owner_xchan) + ); + + if($ab) { + logger('rewrite owner: ' . $arr['owner_xchan'] . ' > ' . $best_owner_xchan); + $arr['owner_xchan'] = $best_owner_xchan; + } + } + + $best_author_xchan = find_best_zot_identity($arr['author_xchan']); + + $ab_author = q("select * from abook where abook_channel = %d and abook_xchan = '%s'", + intval($channel['channel_id']), + dbesc($best_author_xchan) + ); + + if($ab_author) { + logger('rewrite author: ' . $arr['author_xchan'] . ' > ' . $best_author_xchan); + $arr['author_xchan'] = $best_author_xchan; + } + $abook = (($ab) ? $ab[0] : null); if(intval($arr['item_deleted'])) { @@ -2076,7 +2105,7 @@ function process_delivery($sender, $arr, $deliveries, $relay, $public = false, $ $stored = (($item_result && $item_result['item']) ? $item_result['item'] : false); if((is_array($stored)) && ($stored['id'] != $stored['parent']) - && ($stored['author_xchan'] === $channel['channel_hash'])) { + && ($stored['author_xchan'] === $channel['channel_portable_id'])) { retain_item($stored['item']['parent']); } @@ -2344,7 +2373,7 @@ function process_mail_delivery($sender, $arr, $deliveries) { $DR = new Zotlabs\Lib\DReport(z_root(),$sender['hash'],$d['hash'],$arr['mid']); - $r = q("select * from channel where channel_hash = '%s' limit 1", + $r = q("select * from channel where channel_portable_id = '%s' limit 1", dbesc($d['hash']) ); @@ -2555,7 +2584,7 @@ function check_location_move($sender_hash, $locations) { $loc = $locations[0]; - $r = q("select * from channel where channel_hash = '%s' limit 1", + $r = q("select * from channel where channel_portable_id = '%s' limit 1", dbesc($sender_hash) ); @@ -2563,7 +2592,7 @@ function check_location_move($sender_hash, $locations) { return; if($loc['url'] !== z_root()) { - $x = q("update channel set channel_moved = '%s' where channel_hash = '%s' limit 1", + $x = q("update channel set channel_moved = '%s' where channel_portable_id = '%s' limit 1", dbesc($loc['url']), dbesc($sender_hash) ); @@ -2764,6 +2793,8 @@ function sync_locations($sender, $arr, $absolute = false) { } logger('New hub: ' . $location['url']); + $addr_arr = explode('@', $location['address']); + $r = hubloc_store_lowlevel( [ 'hubloc_guid' => $sender['guid'], @@ -2778,7 +2809,8 @@ function sync_locations($sender, $arr, $absolute = false) { 'hubloc_callback' => $location['callback'], 'hubloc_sitekey' => $location['sitekey'], 'hubloc_updated' => datetime_convert(), - 'hubloc_connected' => datetime_convert() + 'hubloc_connected' => datetime_convert(), + 'hubloc_id_url' => $location['url'] . '/channel/' . $addr_arr[0] ] ); @@ -2826,13 +2858,13 @@ function sync_locations($sender, $arr, $absolute = false) { * * @see zot_get_hublocs() * @param array $channel an associative array which must contain - * * \e string \b channel_hash the hash of the channel + * * \e string \b channel_portable_id the hash of the channel * @return array an array with associative arrays */ function zot_encode_locations($channel) { $ret = array(); - $x = zot_get_hublocs($channel['channel_hash']); + $x = zot_get_hublocs($channel['channel_portable_id']); if($x && count($x)) { foreach($x as $hub) { @@ -3300,8 +3332,8 @@ function build_sync_packet($uid = 0, $packet = null, $groups_changed = false) { if(intval($channel['channel_removed'])) return; - $h = q("select hubloc.*, site.site_crypto from hubloc left join site on site_url = hubloc_url where hubloc_hash = '%s' and hubloc_deleted = 0", - dbesc(($keychange) ? $packet['keychange']['old_hash'] : $channel['channel_hash']) + $h = q("select hubloc.*, site.site_crypto, site.site_version, site.site_project from hubloc left join site on site_url = hubloc_url where hubloc_hash = '%s' and hubloc_deleted = 0", + dbesc(($keychange) ? $packet['keychange']['old_hash'] : $channel['channel_portable_id']) ); if(! $h) @@ -3313,6 +3345,14 @@ function build_sync_packet($uid = 0, $packet = null, $groups_changed = false) { if($x['hubloc_host'] == App::get_hostname()) continue; + if(stripos($x['site_project'], 'hubzilla') !== false && version_compare($x['site_version'], '4.7.3', '<=')) { + + logger('Dismiss sync due to incompatible version.'); + // logger(print_r($x,true)); + continue; + + } + $y = q("select site_dead from site where site_url = '%s' limit 1", dbesc($x['hubloc_url']) ); @@ -3325,8 +3365,9 @@ function build_sync_packet($uid = 0, $packet = null, $groups_changed = false) { return; $r = q("select xchan_guid, xchan_guid_sig from xchan where xchan_hash = '%s' limit 1", - dbesc($channel['channel_hash']) + dbesc($channel['channel_portable_id']) ); + if(! $r) return; @@ -3611,6 +3652,12 @@ function process_channel_sync_delivery($sender, $arr, $deliveries) { if(array_key_exists('app',$arr) && $arr['app']) sync_apps($channel,$arr['app']); + if(array_key_exists('addressbook',$arr) && $arr['addressbook']) + sync_addressbook($channel,$arr['addressbook']); + + if(array_key_exists('calendar',$arr) && $arr['calendar']) + sync_calendar($channel,$arr['calendar']); + if(array_key_exists('chatroom',$arr) && $arr['chatroom']) sync_chatrooms($channel,$arr['chatroom']); @@ -4186,7 +4233,7 @@ function zot_reply_message_request($data) { $arr = $data['recipients'][0]; $recip_hash = make_xchan_hash($arr['guid'],$arr['guid_sig']); - $c = q("select * from channel left join xchan on channel_hash = xchan_hash where channel_hash = '%s' limit 1", + $c = q("select * from channel left join xchan on channel_portable_id = xchan_hash where channel_portable_id = '%s' limit 1", dbesc($recip_hash) ); if (! $c) { @@ -4332,21 +4379,21 @@ function zotinfo($arr) { $r = null; if(strlen($zhash)) { - $r = q("select channel.*, xchan.* from channel left join xchan on channel_hash = xchan_hash - where channel_hash = '%s' limit 1", + $r = q("select channel.*, xchan.* from channel left join xchan on channel_portable_id = xchan_hash + where channel_portable_id = '%s' limit 1", dbesc($zhash) ); } elseif(strlen($zguid) && strlen($zguid_sig)) { - $r = q("select channel.*, xchan.* from channel left join xchan on channel_hash = xchan_hash + $r = q("select channel.*, xchan.* from channel left join xchan on channel_portable_id = xchan_hash where channel_guid = '%s' and channel_guid_sig = '%s' limit 1", dbesc($zguid), - dbesc($zguid_sig) + dbesc('sha256.' . $zguid_sig) ); } elseif(strlen($zaddr)) { if(strpos($zaddr,'[system]') === false) { /* normal address lookup */ - $r = q("select channel.*, xchan.* from channel left join xchan on channel_hash = xchan_hash + $r = q("select channel.*, xchan.* from channel left join xchan on channel_portable_id = xchan_hash where ( channel_address = '%s' or xchan_addr = '%s' ) limit 1", dbesc($zaddr), dbesc($zaddr) @@ -4366,10 +4413,10 @@ function zotinfo($arr) { * */ - $r = q("select channel.*, xchan.* from channel left join xchan on channel_hash = xchan_hash + $r = q("select channel.*, xchan.* from channel left join xchan on channel_portable_id = xchan_hash where channel_system = 1 order by channel_id limit 1"); if(! $r) { - $r = q("select channel.*, xchan.* from channel left join xchan on channel_hash = xchan_hash + $r = q("select channel.*, xchan.* from channel left join xchan on channel_portable_id = xchan_hash where channel_removed = 0 order by channel_id limit 1"); } } @@ -4693,14 +4740,14 @@ function check_zotinfo($channel, $locations, &$ret) { // for the sys channel as normal channels will be trickier. q("delete from hubloc where hubloc_hash = '%s'", - dbesc($channel['channel_hash']) + dbesc($channel['channel_portable_id']) ); $r = hubloc_store_lowlevel( [ 'hubloc_guid' => $channel['channel_guid'], 'hubloc_guid_sig' => $channel['channel_guid_sig'], - 'hubloc_hash' => $channel['channel_hash'], + 'hubloc_hash' => $channel['channel_portable_id'], 'hubloc_addr' => channel_reddress($channel), 'hubloc_network' => 'zot', 'hubloc_primary' => 1, @@ -4755,7 +4802,7 @@ function delivery_report_is_storable($dr) { // Is the sender one of our channels? - $c = q("select channel_id from channel where channel_hash = '%s' limit 1", + $c = q("select channel_id from channel where channel_portable_id = '%s' limit 1", dbesc($dr['sender']) ); if(! $c) @@ -5101,7 +5148,7 @@ function zot_reply_auth_check($data,$encrypted_packet) { $arr = $data['recipients'][0]; $recip_hash = make_xchan_hash($arr['guid'], $arr['guid_sig']); - $c = q("select channel_id, channel_account_id, channel_prvkey from channel where channel_hash = '%s' limit 1", + $c = q("select channel_id, channel_account_id, channel_prvkey from channel where channel_portable_id = '%s' limit 1", dbesc($recip_hash) ); if (! $c) { @@ -5168,7 +5215,7 @@ function zot_reply_purge($sender, $recipients) { // basically this means "unfriend" foreach ($recipients as $recip) { $r = q("select channel.*,xchan.* from channel - left join xchan on channel_hash = xchan_hash + left join xchan on channel_portable_id = xchan_hash where channel_guid = '%s' and channel_guid_sig = '%s' limit 1", dbesc($recip['guid']), dbesc($recip['guid_sig']) @@ -5221,12 +5268,11 @@ function zot_reply_refresh($sender, $recipients) { foreach ($recipients as $recip) { $r = q("select channel.*,xchan.* from channel - left join xchan on channel_hash = xchan_hash - where channel_guid = '%s' and channel_guid_sig = '%s' limit 1", + left join xchan on channel_portable_id = xchan_hash + where xchan_guid = '%s' and xchan_guid_sig = '%s' limit 1", dbesc($recip['guid']), dbesc($recip['guid_sig']) ); - $x = zot_refresh(array( 'xchan_guid' => $sender['guid'], 'xchan_guid_sig' => $sender['guid_sig'], @@ -5325,3 +5371,24 @@ function zot_record_preferred($arr, $check = 'hubloc_network') { return $arr[0]; } + +function find_best_zot_identity($xchan) { + + $r = q("select hubloc_addr from hubloc where hubloc_hash = '%s' and hubloc_network in ('zot6', 'zot') and hubloc_deleted = 0", + dbesc($xchan) + ); + + if ($r) { + + $r = q("select hubloc_hash, hubloc_network from hubloc where hubloc_addr = '%s' and hubloc_network in ('zot6', 'zot') and hubloc_deleted = 0", + dbesc($r[0]['hubloc_addr']) + ); + if ($r) { + $r = Libzot::zot_record_preferred($r); + logger('find_best_zot_identity: ' . $xchan . ' > ' . $r['hubloc_hash']); + return $r['hubloc_hash']; + } + } + + return $xchan; +} |