diff options
Diffstat (limited to 'include')
34 files changed, 2655 insertions, 1401 deletions
diff --git a/include/acl_selectors.php b/include/acl_selectors.php index 09c24f82c..4e203074b 100644 --- a/include/acl_selectors.php +++ b/include/acl_selectors.php @@ -110,7 +110,7 @@ function fixacl(&$item) { * * @param array $defaults Optional access control list for the initial state of the dialog. * @param boolean $show_jotnets Whether plugins for federated networks should be included in the permissions dialog -* @param PermissionDescription $emptyACL_description - An optional description for the permission implied by selecting an empty ACL. Preferably an instance of PermissionDescription. +* @param \Zotlabs\Lib\PermissionDescription $emptyACL_description - An optional description for the permission implied by selecting an empty ACL. Preferably an instance of PermissionDescription. * @param string $dialog_description Optional message to include at the top of the dialog. E.g. "Warning: Post permissions cannot be changed once sent". * @param string $context_help Allows the dialog to present a help icon. E.g. "acl_dialog_post" * @param boolean $readonly Not implemented yet. When implemented, the dialog will use acl_readonly.tpl instead, so that permissions may be viewed for posts that can no longer have their permissions changed. @@ -172,7 +172,7 @@ function populate_acl($defaults = null,$show_jotnets = true, $emptyACL_descripti if(count($allow_cid) === 1 && $channel && $allow_cid[0] = $channel['channel_hash'] && (! $allow_gid) && (! $deny_gid) && (! $deny_cid)) { $just_me = true; $custom = false; - } + } $r = q("SELECT id, profile_guid, profile_name from profile where is_default = 0 and uid = %d order by profile_name", intval(local_channel()) diff --git a/include/api_auth.php b/include/api_auth.php index 0818fa54b..5c0bcb317 100644 --- a/include/api_auth.php +++ b/include/api_auth.php @@ -52,6 +52,7 @@ function api_login(&$a){ /* Signature authentication */ if(array_key_exists($head,$_SERVER) && substr(trim($_SERVER[$head]),0,9) === 'Signature') { + if($head !== 'HTTP_AUTHORIZATION') { $_SERVER['HTTP_AUTHORIZATION'] = $_SERVER[$head]; continue; @@ -59,7 +60,7 @@ function api_login(&$a){ $sigblock = \Zotlabs\Web\HTTPSig::parse_sigheader($_SERVER[$head]); if($sigblock) { - $keyId = $sigblock['keyId']; + $keyId = str_replace('acct:','',$sigblock['keyId']); if($keyId) { $r = q("select * from hubloc where hubloc_addr = '%s' limit 1", dbesc($keyId) diff --git a/include/api_zot.php b/include/api_zot.php index aaa9ee497..54f905b4c 100644 --- a/include/api_zot.php +++ b/include/api_zot.php @@ -150,7 +150,11 @@ $start = ((array_key_exists('start',$_REQUEST)) ? intval($_REQUEST['start']) : 0); $records = ((array_key_exists('records',$_REQUEST)) ? intval($_REQUEST['records']) : 0); - $x = attach_list_files(api_user(),get_observer_hash(),$hash,$filename,$filetype,'created asc',$start,$records); + $since = ((array_key_exists('since',$_REQUEST)) ? datetime_convert(date_default_timezone_get(),'UTC',$_REQUEST['since']) : NULL_DATE); + $until = ((array_key_exists('until',$_REQUEST)) ? datetime_convert(date_default_timezone_get(),'UTC',$_REQUEST['until']) : datetime_convert()); + + $x = attach_list_files(api_user(),get_observer_hash(),$hash,$filename,$filetype,'created asc',$start,$records, $since, $until); + if($start || $records) { $x['start'] = $start; $x['records'] = count($x['results']); @@ -226,7 +230,10 @@ if(! $_REQUEST['file_id']) return false; - $ret = attach_export_data(api_user(),$_REQUEST['file_id']); + $channel = channelx_by_n(api_user()); + + $ret = attach_export_data($channel,$_REQUEST['file_id']); + if($ret) { json_return_and_die($ret); } diff --git a/include/attach.php b/include/attach.php index e1db67af4..39269eb03 100644 --- a/include/attach.php +++ b/include/attach.php @@ -21,7 +21,7 @@ require_once('include/group.php'); * This function takes a file name and guess the mimetype from the * filename extension. * - * @param $filename a string filename + * @param string $filename a string filename * @return string The mimetype according to a file ending. */ function z_mime_content_type($filename) { @@ -31,7 +31,6 @@ function z_mime_content_type($filename) { 'txt' => 'text/plain', 'htm' => 'text/html', 'html' => 'text/html', - 'php' => 'text/html', 'css' => 'text/css', 'md' => 'text/markdown', 'bb' => 'text/bbcode', @@ -41,8 +40,17 @@ function z_mime_content_type($filename) { 'swf' => 'application/x-shockwave-flash', 'flv' => 'video/x-flv', 'epub' => 'application/epub+zip', + 'c' => 'text/plain', + 'h' => 'text/plain', + 'sh' => 'text/plain', + 'py' => 'text/plain', + 'php' => 'text/plain', + 'rb' => 'text/plain', + 'pdl' => 'text/plain', + // images + 'png' => 'image/png', 'jpe' => 'image/jpeg', 'jpeg' => 'image/jpeg', @@ -73,9 +81,7 @@ function z_mime_content_type($filename) { 'flac' => 'audio/flac', 'opus' => 'audio/ogg', 'webm' => 'video/webm', -// 'webm' => 'audio/webm', 'mp4' => 'video/mp4', -// 'mp4' => 'audio/mp4', 'mkv' => 'video/x-matroska', // adobe @@ -183,7 +189,7 @@ function attach_count_files($channel_id, $observer, $hash = '', $filename = '', * * \e array|boolean \b results array with results, or false * * \e string \b message with error messages if any */ -function attach_list_files($channel_id, $observer, $hash = '', $filename = '', $filetype = '', $orderby = 'created desc', $start = 0, $entries = 0) { +function attach_list_files($channel_id, $observer, $hash = '', $filename = '', $filetype = '', $orderby = 'created desc', $start = 0, $entries = 0, $since = '', $until = '') { $ret = array('success' => false); @@ -192,6 +198,7 @@ function attach_list_files($channel_id, $observer, $hash = '', $filename = '', $ return $ret; } + require_once('include/security.php'); $sql_extra = permissions_sql($channel_id); @@ -207,14 +214,22 @@ function attach_list_files($channel_id, $observer, $hash = '', $filename = '', $ if($entries) $limit = " limit " . intval($start) . ", " . intval($entries) . " "; - // Retrieve all columns except 'data' + if(! $since) + $since = NULL_DATE; + + if(! $until) + $until = datetime_convert(); + + $sql_extra .= " and created >= '" . dbesc($since) . "' and created <= '" . dbesc($until) . "' "; + + // Retrieve all columns except 'content' $r = q("select id, aid, uid, hash, filename, filetype, filesize, revision, folder, os_path, display_path, os_storage, is_dir, is_photo, flags, created, edited, allow_cid, allow_gid, deny_cid, deny_gid from attach where uid = %d $sql_extra ORDER BY $orderby $limit", intval($channel_id) ); $ret['success'] = ((is_array($r)) ? true : false); - $ret['results'] = ((is_array($r)) ? $r : false); + $ret['results'] = ((is_array($r)) ? $r : []); return $ret; } @@ -270,6 +285,8 @@ function attach_by_hash($hash, $observer_hash, $rev = 0) { return $ret; } + $r[0]['content'] = dbunescbin($r[0]['content']); + if($r[0]['folder']) { $x = attach_can_view_folder($r[0]['uid'],$observer_hash,$r[0]['folder']); if(! $x) { @@ -291,6 +308,11 @@ function attach_can_view_folder($uid,$ob_hash,$folder_hash) { $hash = $folder_hash; $result = false; + if(! $folder_hash) { + return perm_is_allowed($uid,$ob_hash,'view_storage'); + } + + do { $r = q("select folder from attach where hash = '%s' and uid = %d $sql_extra", dbesc($hash), @@ -412,6 +434,10 @@ function attach_store($channel, $observer_hash, $options = '', $arr = null) { require_once('include/photos.php'); + /** + * @hooks photo_upload_begin + * Called when attempting to upload a photo. + */ call_hooks('photo_upload_begin', $arr); $ret = array('success' => false); @@ -486,14 +512,36 @@ function attach_store($channel, $observer_hash, $options = '', $arr = null) { $x = attach_mkdir($channel,$observer_hash,$arr); if($x['message']) logger('import_directory: ' . $x['message']); + return; } } elseif($options !== 'update') { - $f = array('src' => '', 'filename' => '', 'filesize' => 0, 'type' => ''); - - call_hooks('photo_upload_file',$f); - call_hooks('attach_upload_file',$f); + $f = [ + 'src' => '', + 'filename' => '', + 'filesize' => 0, + 'type' => '' + ]; + + /** + * @hooks photo_upload_file + * Called to generate alternate filenames for an upload. + * * \e string \b src - return value, default empty + * * \e string \b filename - return value, default empty + * * \e number \b filesize - return value, default 0 + * * \e string \b type - return value, default empty + */ + call_hooks('photo_upload_file', $f); + /** + * @hooks attach_upload_file + * Called when uploading a file. + * * \e string \b src - return value, default empty + * * \e string \b filename - return value, default empty + * * \e number \b filesize - return value, default 0 + * * \e string \b type - return value, default empty + */ + call_hooks('attach_upload_file', $f); if (x($f,'src') && x($f,'filesize')) { $src = $f['src']; @@ -502,6 +550,7 @@ function attach_store($channel, $observer_hash, $options = '', $arr = null) { $type = $f['type']; } else { if(! x($_FILES,'userfile')) { + logger('No source file'); $ret['message'] = t('No source file.'); return $ret; } @@ -542,6 +591,7 @@ function attach_store($channel, $observer_hash, $options = '', $arr = null) { intval($channel_id) ); if(! $x) { + logger('update file source not found'); $ret['message'] = t('Cannot locate file to revise/update'); return $ret; } @@ -562,6 +612,12 @@ function attach_store($channel, $observer_hash, $options = '', $arr = null) { $def_extension = '.png'; } + // If we know it's a photo, over-ride the type in case the source system could not determine what it was + + if($is_photo) { + $type = $gis['mime']; + } + $pathname = ''; if($is_photo) { @@ -677,10 +733,17 @@ function attach_store($channel, $observer_hash, $options = '', $arr = null) { $maxfilesize = get_config('system','maxfilesize'); if(($maxfilesize) && ($filesize > $maxfilesize)) { + logger('quota_exceeded'); $ret['message'] = sprintf( t('File exceeds size limit of %d'), $maxfilesize); if($remove_when_processed) @unlink($src); - call_hooks('photo_upload_end',$ret); + + /** + * @hooks photo_upload_end + * Called when a photo upload has been processed. + */ + call_hooks('photo_upload_end', $ret); + return $ret; } @@ -691,11 +754,17 @@ function attach_store($channel, $observer_hash, $options = '', $arr = null) { intval($channel['channel_account_id']) ); if(($r) && (($r[0]['total'] + $filesize) > ($limit - $existing_size))) { + logger('service_class limit exceeded'); $ret['message'] = upgrade_message(true) . sprintf(t("You have reached your limit of %1$.0f Mbytes attachment storage."), $limit / 1024000); if($remove_when_processed) @unlink($src); - call_hooks('photo_upload_end',$ret); + /** + * @hooks photo_upload_end + * Called when a photo upload has been processed. + */ + call_hooks('photo_upload_end', $ret); + return $ret; } } @@ -718,8 +787,15 @@ function attach_store($channel, $observer_hash, $options = '', $arr = null) { $os_path = $os_relpath; $display_path = ltrim($pathname . '/' . $filename,'/'); - if($src) - @file_put_contents($os_basepath . $os_relpath,@file_get_contents($src)); + if($src) { + $istream = @fopen($src,'rb'); + $ostream = @fopen($os_basepath . $os_relpath,'wb'); + if($istream && $ostream) { + pipe_streams($istream,$ostream,65535); + fclose($istream); + fclose($ostream); + } + } if(array_key_exists('created', $arr)) $created = $arr['created']; @@ -773,7 +849,7 @@ function attach_store($channel, $observer_hash, $options = '', $arr = null) { ); } elseif($options === 'update') { - $r = q("update attach set filename = '%s', filetype = '%s', folder = '%s', edited = '%s', os_storage = %d, is_photo = %d, os_path = '%s', + $r = q("update attach set filename = '%s', filetype = '%s', folder = '%s', edited = '%s', os_storage = %d, is_photo = %d, os_path = '%s', display_path = '%s', allow_cid = '%s', allow_gid = '%s', deny_cid = '%s', deny_gid = '%s' where id = %d and uid = %d", dbesc((array_key_exists('filename',$arr)) ? $arr['filename'] : $x[0]['filename']), dbesc((array_key_exists('filetype',$arr)) ? $arr['filetype'] : $x[0]['filetype']), @@ -862,7 +938,13 @@ function attach_store($channel, $observer_hash, $options = '', $arr = null) { if(! $r) { $ret['message'] = t('File upload failed. Possible system limit or action terminated.'); - call_hooks('photo_upload_end',$ret); + + /** + * @hooks photo_upload_end + * Called when a photo upload has been processed. + */ + call_hooks('photo_upload_end', $ret); + return $ret; } @@ -875,17 +957,30 @@ function attach_store($channel, $observer_hash, $options = '', $arr = null) { if(! $r) { $ret['message'] = t('Stored file could not be verified. Upload failed.'); - call_hooks('photo_upload_end',$ret); + + /** + * @hooks photo_upload_end + * Called when a photo upload has been processed. + */ + call_hooks('photo_upload_end', $ret); + return $ret; } $ret['success'] = true; $ret['data'] = $r[0]; if(! $is_photo) { - // This would've been called already with a success result in photos_upload() if it was a photo. - call_hooks('photo_upload_end',$ret); + /** + * @hooks photo_upload_end + * Called when a photo upload has been processed. + * This would've been called already with a success result in photos_upload() if it was a photo. + */ + call_hooks('photo_upload_end', $ret); } + \Zotlabs\Daemon\Master::Summon([ 'Thumbnail' , $hash ]); + + if($dosync) { $sync = attach_export_data($channel,$hash); @@ -894,9 +989,9 @@ function attach_store($channel, $observer_hash, $options = '', $arr = null) { } 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); + $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); } return $ret; @@ -986,7 +1081,7 @@ function attach_mkdir($channel, $observer_hash, $arr = null) { $os_basepath = 'store/' . $channel['channel_address']; - logger('attach_mkdir: basepath: ' . $os_basepath); + logger('basepath: ' . $os_basepath); if(! is_dir($os_basepath)) os_mkdir($os_basepath,STORAGE_DEFAULT_PERMISSIONS, true); @@ -1055,7 +1150,7 @@ function attach_mkdir($channel, $observer_hash, $arr = null) { return $ret; } - $dpath = $r[0]['filename'] . (($dpath) ? '/' . $dpath : ''); + $dpath = $r[0]['filename'] . (($dpath) ? '/' . $dpath : ''); if($lfile) $lpath = $r[0]['hash'] . (($lpath) ? '/' . $lpath : ''); @@ -1154,7 +1249,7 @@ function attach_mkdirp($channel, $observer_hash, $arr = null) { $basepath = 'store/' . $channel['channel_address']; - logger('attach_mkdirp: basepath: ' . $basepath); + logger('basepath: ' . $basepath); if(! is_dir($basepath)) os_mkdir($basepath,STORAGE_DEFAULT_PERMISSIONS, true); @@ -1180,6 +1275,7 @@ function attach_mkdirp($channel, $observer_hash, $arr = null) { foreach($paths as $p) { if(! $p) continue; + $arx = array( 'filename' => $p, 'folder' => $current_parent, @@ -1308,7 +1404,7 @@ function attach_delete($channel_id, $resource, $is_photo = 0) { ); if(! $r) { - attach_drop_photo($channel_id,$resource); + attach_drop_photo($channel_id,$resource); return; } @@ -1367,7 +1463,7 @@ function attach_delete($channel_id, $resource, $is_photo = 0) { intval($channel_id) ); - file_activity($channel_id, $object, $object['allow_cid'], $object['allow_gid'], $object['deny_cid'], $object['deny_gid'], 'update', $notify=1); + file_activity($channel_id, $object, $object['allow_cid'], $object['allow_gid'], $object['deny_cid'], $object['deny_gid'], 'update', true); return; } @@ -1501,6 +1597,13 @@ function find_folder_hash_by_attach_hash($channel_id, $attachHash, $recurse = fa return $hash; } +/** + * @brief Return the hash of an attachment's folder. + * + * @param int $channel_id + * @param string $path + * @return string + */ function find_folder_hash_by_path($channel_id, $path) { if(! $path) @@ -1530,7 +1633,6 @@ function find_folder_hash_by_path($channel_id, $path) { return ''; return $parent_hash; - } /** @@ -1557,14 +1659,17 @@ function find_filename_by_hash($channel_id, $attachHash) { } /** + * @brief Pipes $in to $out in 16KB chunks. * - * @param $in - * @param $out + * @param resource $in File pointer of input + * @param resource $out File pointer of output + * @param int $bufsize size of chunk, default 16384 + * @return number with the size */ -function pipe_streams($in, $out) { +function pipe_streams($in, $out, $bufsize = 16384) { $size = 0; while (!feof($in)) - $size += fwrite($out, fread($in, 16384)); + $size += fwrite($out, fread($in, $bufsize)); return $size; } @@ -1726,11 +1831,12 @@ function file_activity($channel_id, $object, $allow_cid, $allow_gid, $deny_cid, } /** - * @brief Create file activity object + * @brief Create file activity object. * * @param int $channel_id * @param string $hash - * @param string $cloudpath + * @param string $url + * @return array An associative array for the specified file. */ function get_file_activity_object($channel_id, $hash, $url) { @@ -1958,6 +2064,7 @@ function attach_export_data($channel, $resource_id, $deleted = false) { if($hash_ptr === $resource_id) { $attach_ptr = $r[0]; } + $r[0]['content'] = dbunescbin($r[0]['content']); $hash_ptr = $r[0]['folder']; $paths[] = $r[0]; @@ -1970,6 +2077,9 @@ function attach_export_data($channel, $resource_id, $deleted = false) { if($attach_ptr['is_photo']) { + + // This query could potentially result in a few megabytes of data use. + $r = q("select * from photo where resource_id = '%s' and uid = %d order by imgscale asc", dbesc($resource_id), intval($channel['channel_id']) @@ -1981,6 +2091,17 @@ function attach_export_data($channel, $resource_id, $deleted = false) { $ret['photo'] = $r; } +// This query can be used instead in memory starved environments. There will be a corresponding +// performance hit during sync because the data will need to be fetched over the network. +// $r = q("select aid,uid,xchan,resource_id,created,edited,title,description,album,filename,mimetype,height,width,filesize,imgscale,photo_usage,profile,is_nsfw,os_storage,display_path,photo_flags,allow_cid,allow_gid,deny_cid,deny_gid from photo where resource_id = '%s' and uid = %d order by imgscale asc", +// dbesc($resource_id), +// intval($channel['channel_id']) +// ); + +// if($r) { +// $ret['photo'] = $r; +// } + $r = q("select * from item where resource_id = '%s' and resource_type = 'photo' and uid = %d ", dbesc($resource_id), intval($channel['channel_id']) @@ -2143,7 +2264,6 @@ function copy_folder_to_cloudfiles($channel, $observer_hash, $srcpath, $cloudpat * the attach.hash of the new parent folder, which must already exist. If $new_folder_hash is blank or empty, * the file is relocated to the root of the channel's storage area. * - * @fixme: this operation is currently not synced to clones !! * * @param int $channel_id * @param int $resource_id @@ -2153,7 +2273,7 @@ function copy_folder_to_cloudfiles($channel, $observer_hash, $srcpath, $cloudpat function attach_move($channel_id, $resource_id, $new_folder_hash) { $c = channelx_by_n($channel_id); - if(! $c) + if(! ($c && $resource_id)) return false; $r = q("select * from attach where hash = '%s' and uid = %d limit 1", @@ -2165,13 +2285,32 @@ function attach_move($channel_id, $resource_id, $new_folder_hash) { $oldstorepath = dbunescbin($r[0]['content']); + if($r[0]['is_dir']) { + $move_success = true; + $x = q("select hash from attach where folder = '%s' and uid = %d", + dbesc($r[0]['hash']), + intval($channel_id) + ); + if($x) { + foreach($x as $xv) { + $rs = attach_move($channel_id,$xv['hash'],$r[0]['hash']); + if(! $rs) { + $move_success = false; + break; + } + } + } + return $move_success; + } + + if($new_folder_hash) { - $n = q("select * from attach where hash = '%s' and uid = %d limit 1", + $n = q("select * from attach where hash = '%s' and uid = %d and is_dir = 1 limit 1", dbesc($new_folder_hash), intval($channel_id) ); if(! $n) - return; + return false; $newdirname = $n[0]['filename']; $newstorepath = dbunescbin($n[0]['content']) . '/' . $resource_id; @@ -2257,7 +2396,7 @@ function attach_move($channel_id, $resource_id, $new_folder_hash) { if($r[0]['is_photo']) { - $t = q("update photo set album = '%s', filename = '%s', os_path = '%s', display_path = '%s' + $t = q("update photo set album = '%s', filename = '%s', os_path = '%s', display_path = '%s' where resource_id = '%s' and uid = %d", dbesc($newdirname), dbesc($filename), @@ -2278,6 +2417,11 @@ function attach_move($channel_id, $resource_id, $new_folder_hash) { } +/** + * Used to generate a select input box of all your folders + */ + + function attach_folder_select_list($channel_id) { $r = q("select * from attach where is_dir = 1 and uid = %d", @@ -2328,6 +2472,10 @@ function attach_folder_rpaths($all_folders,$that_folder) { return (($error) ? false : [ $current_hash , $path ]); } +/** + * @brief Given a channel_id and attach_hash, return an array with the full relative path and os_path + */ + function attach_syspaths($channel_id,$attach_hash) { @@ -2349,10 +2497,19 @@ function attach_syspaths($channel_id,$attach_hash) { while($attach_hash); return [ 'os_path' => $os_path, 'path' => $path ]; - - } +/** + * in earlier releases we did not fill in os_path and display_path in the attach DB structure. + * (It was not needed or used). Going forward we intend to make use of these fields. + * A cron task checks for empty values (as older attachments may have arrived at our site + * in a clone operation) and executes attach_syspaths() to generate these field values and correct + * the attach table entry. The operation is limited to 100 DB entries at a time so as not to + * overload the system in any cron run. Eventually it will catch up with old attach structures + * and switch into maintenance mode to correct any that might arrive in clone packets from older + * sites. + */ + function attach_upgrade() { @@ -2379,6 +2536,12 @@ function attach_upgrade() { } +/** + * Chunked uploader for integration with the blueimp jquery-uploader + * This is currently used. + */ + + function save_chunk($channel,$start,$end,$len) { $result = []; @@ -2386,7 +2549,7 @@ function save_chunk($channel,$start,$end,$len) { $tmp_path = $_FILES['files']['tmp_name']; $new_base = 'store/[data]/' . $channel['channel_address'] . '/tmp'; os_mkdir($new_base,STORAGE_DEFAULT_PERMISSIONS,true); - + $new_path = $new_base . '/' . $_FILES['files']['name']; if(! file_exists($new_path)) { @@ -2403,7 +2566,7 @@ function save_chunk($channel,$start,$end,$len) { } if(($len - 1) == $end) { unlink($tmp_path); - $result['name'] = $_FILES['files']['tmp_name']; + $result['name'] = $_FILES['files']['name']; $result['type'] = $_FILES['files']['type']; $result['tmp_name'] = $new_path; $result['error'] = 0; @@ -2417,65 +2580,3 @@ function save_chunk($channel,$start,$end,$len) { } -/* - * chunkloader - * Submit handler for chunked uploads - * - */ - -function chunkloader($channel,$arr) { - - logger('request: ' . print_r($arr,true), LOGGER_DEBUG); - logger('files: ' . print_r($_FILES,true), LOGGER_DEBUG); - - - $result = []; - - - $tmp_path = $_FILES['file']['tmp_name']; - $new_base = 'store/[data]/' . $channel['channel_address'] . '/tmp'; - os_mkdir($new_base,STORAGE_DEFAULT_PERMISSIONS,true); - - $new_path = $new_base . '/' . $arr['resumableFilename']; - - rename($tmp_path,$new_path . '.' . intval($arr['resumableChunkNumber'])); - - $missing_parts = false; - for($x = 1; $x <= intval($arr['resumableTotalChunks']); $x ++) { - if(! file_exists($new_path . '.' . $x)) { - $missing_parts = true; - break; - } - } - - if($missing_parts) { - $result['partial'] = true; - return $result; - } - - if(intval($arr['resumableTotalChunks']) === 1) { - rename($new_path . '.' . '1', $new_path); - } - else { - for($x = 1; $x <= intval($arr['resumableTotalChunks']); $x ++) { - $istream = fopen($new_path . '.' . $x,'rb'); - $ostream = fopen($new_path,'ab'); - if($istream && $ostream) { - pipe_streams($istream,$ostream); - fclose($istream); - fclose($ostream); - } - unlink($new_path . '.' . $x); - } - } - - $result['name'] = $arr['resumableFilename']; - $result['type'] = $arr['resumableType']; - $result['tmp_name'] = $new_path; - $result['error'] = 0; - $result['size'] = $arr['resumableTotalSize']; - $result['complete'] = true; - return $result; - -} - diff --git a/include/bbcode.php b/include/bbcode.php index 050ab2d29..775a91f9a 100644 --- a/include/bbcode.php +++ b/include/bbcode.php @@ -246,8 +246,8 @@ function bb_parse_element($match) { $j = json_decode(base64url_decode($match[1]),true); if ($j && local_channel()) { - $text = sprintf( t('Install %s element: '), translate_design_element($j['type'])) . $j['pagetitle']; - $o = EOL . '<a href="#" onclick="importElement(\'' . $match[1] . '\'); return false;" >' . $text . '</a>' . EOL; + $text = sprintf( t('Install %1$s element %2$s'), translate_design_element($j['type']), $j['pagetitle']); + $o = EOL . '<button class="btn btn-primary" onclick="importElement(\'' . $match[1] . '\'); return false;" >' . $text . '</button>' . EOL; } else { $text = sprintf( t('This post contains an installable %s element, however you lack permissions to install it on this site.' ), translate_design_element($j['type'])) . $j['pagetitle']; @@ -329,6 +329,8 @@ function bb_ShareAttributes($match) { if(strpos($link,'/cards/')) $type = t('card'); + elseif(strpos($link,'/articles/')) + $type = t('article'); else $type = t('post'); @@ -705,10 +707,12 @@ function parseIdentityAwareHTML($Text) { return $Text; } - // BBcode 2 HTML was written by WAY2WEB.net - // extended to work with Mistpark/Friendica/Redmatrix/Hubzilla - Mike Macgirvin -function bbcode($Text, $preserve_nl = false, $tryoembed = true, $cache = false) { +function bbcode($Text, $options = []) { + + $preserve_nl = ((array_key_exists('preserve_nl',$options)) ? $options['preserve_nl'] : false); + $tryoembed = ((array_key_exists('tryoembed',$options)) ? $options['tryoembed'] : true); + $cache = ((array_key_exists('cache',$options)) ? $options['cache'] : false); call_hooks('bbcode_filter', $Text); @@ -937,27 +941,34 @@ function bbcode($Text, $preserve_nl = false, $tryoembed = true, $cache = false) // Check for h1 if (strpos($Text,'[h1]') !== false) { $Text = preg_replace("(\[h1\](.*?)\[\/h1\])ism",'<h1>$1</h1>',$Text); + $Text = str_replace('</h1><br />', '</h1>', $Text); } // Check for h2 if (strpos($Text,'[h2]') !== false) { $Text = preg_replace("(\[h2\](.*?)\[\/h2\])ism",'<h2>$1</h2>',$Text); + $Text = str_replace('</h2><br />', '</h2>', $Text); } // Check for h3 if (strpos($Text,'[h3]') !== false) { $Text = preg_replace("(\[h3\](.*?)\[\/h3\])ism",'<h3>$1</h3>',$Text); + $Text = str_replace('</h3><br />', '</h3>', $Text); } // Check for h4 if (strpos($Text,'[h4]') !== false) { $Text = preg_replace("(\[h4\](.*?)\[\/h4\])ism",'<h4>$1</h4>',$Text); + $Text = str_replace('</h4><br />', '</h4>', $Text); } // Check for h5 if (strpos($Text,'[h5]') !== false) { $Text = preg_replace("(\[h5\](.*?)\[\/h5\])ism",'<h5>$1</h5>',$Text); + $Text = str_replace('</h5><br />', '</h5>', $Text); } // Check for h6 if (strpos($Text,'[h6]') !== false) { $Text = preg_replace("(\[h6\](.*?)\[\/h6\])ism",'<h6>$1</h6>',$Text); + $Text = str_replace('</h6><br />', '</h6>', $Text); } + // Check for table of content without params while(strpos($Text,'[toc]') !== false) { $toc_id = 'toc-' . random_string(10); diff --git a/include/channel.php b/include/channel.php index d7116ce28..b9adc588b 100644 --- a/include/channel.php +++ b/include/channel.php @@ -1,6 +1,7 @@ <?php /** * @file include/channel.php + * @brief Channel related functions. */ require_once('include/zot.php'); @@ -58,10 +59,8 @@ function identity_check_service_class($account_id) { * Plugins can set additional policies such as full name requirements, character * sets, multi-byte length, etc. * - * @hooks validate_channelname - * * \e array \b name * @param string $name - * @returns nil return if name is valid, or string describing the error state. + * @return string describing the error state, or nil return if name is valid */ function validate_channelname($name) { @@ -72,6 +71,12 @@ function validate_channelname($name) { return t('Name too long'); $arr = ['name' => $name]; + /** + * @hooks validate_channelname + * Used to validate the names used by a channel. + * * \e string \b name + * * \e string \b message - return error message + */ call_hooks('validate_channelname', $arr); if (x($arr, 'message')) @@ -96,14 +101,14 @@ function create_sys_channel() { set_config('system', 'prvkey', $hostkey['prvkey']); } - create_identity(array( - 'account_id' => 'xxx', // This will create an identity with an (integer) account_id of 0, but account_id is required - 'nickname' => 'sys', - 'name' => 'System', - 'pageflags' => 0, - 'publish' => 0, - 'system' => 1 - )); + create_identity([ + 'account_id' => 'xxx', // Typecast trickery: account_id is required. This will create an identity with an (integer) account_id of 0 + 'nickname' => 'sys', + 'name' => 'System', + 'pageflags' => 0, + 'publish' => 0, + 'system' => 1 + ]); } @@ -259,7 +264,6 @@ function create_identity($arr) { 'channel_system' => intval($system), 'channel_expire_days' => intval($expire), 'channel_timezone' => App::$timezone - ] ); @@ -280,9 +284,19 @@ function create_identity($arr) { $photo_type = null; - $z = [ 'account' => $a[0], 'channel' => $r[0], 'photo_url' => '' ]; - call_hooks('create_channel_photo',$z); - + $z = [ + 'account' => $a[0], + 'channel' => $r[0], + 'photo_url' => '' + ]; + /** + * @hooks create_channel_photo + * * \e array \b account + * * \e array \b channel + * * \e string \b photo_url - Return value + */ + call_hooks('create_channel_photo', $z); + if($z['photo_url']) { $photo_type = import_channel_photo_from_url($z['photo_url'],$arr['account_id'],$r[0]['channel_id']); } @@ -311,7 +325,7 @@ function create_identity($arr) { 'hubloc_guid_sig' => $sig, 'hubloc_hash' => $hash, 'hubloc_addr' => channel_reddress($ret['channel']), - 'hubloc_primary' => $primary, + 'hubloc_primary' => intval($primary), 'hubloc_url' => z_root(), 'hubloc_url_sig' => base64url_encode(rsa_sign(z_root(),$ret['channel']['channel_prvkey'])), 'hubloc_host' => App::get_hostname(), @@ -322,7 +336,7 @@ function create_identity($arr) { ] ); if(! $r) - logger('create_identity: Unable to store hub location'); + logger('Unable to store hub location'); $newuid = $ret['channel']['channel_id']; @@ -454,12 +468,18 @@ function create_identity($arr) { 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); } } + /** + * @hooks create_identity + * Called when creating a channel. + * * \e int - The UID of the created identity + */ call_hooks('create_identity', $newuid); Zotlabs\Daemon\Master::Summon(array('Directory', $ret['channel']['channel_id'])); @@ -576,14 +596,14 @@ function channel_change_address($channel,$new_address) { $old_address = $channel['channel_address']; if($new_address === 'sys') { - $ret['message'] = t('Reserved nickname. Please choose another.'); - return $ret; - } + $ret['message'] = t('Reserved nickname. Please choose another.'); + return $ret; + } - if(check_webbie(array($new_address)) !== $new_address) { - $ret['message'] = t('Nickname has unsupported characters or is already being used on this site.'); - return $ret; - } + if(check_webbie(array($new_address)) !== $new_address) { + $ret['message'] = t('Nickname has unsupported characters or is already being used on this site.'); + return $ret; + } $r = q("update channel set channel_address = '%s' where channel_id = %d", dbesc($new_address), @@ -644,7 +664,7 @@ function channel_change_address($channel,$new_address) { ); } } - } + } Zotlabs\Daemon\Master::Summon(array('Notifier', 'refresh_all', $channel['channel_id'])); @@ -653,10 +673,6 @@ function channel_change_address($channel,$new_address) { } - - - - /** * @brief Set default channel to be used on login. * @@ -664,9 +680,9 @@ function channel_change_address($channel,$new_address) { * login account * @param int $channel_id * channel id to set as default for this account - * @param boolean $force - * if true, set this default unconditionally - * if $force is false only do this if there is no existing default + * @param boolean $force (optional) default true + * - if true, set this default unconditionally + * - if $force is false only do this if there is no existing default */ function set_default_login_identity($account_id, $channel_id, $force = true) { $r = q("select account_default_channel from account where account_id = %d limit 1", @@ -685,8 +701,6 @@ function set_default_login_identity($account_id, $channel_id, $force = true) { /** * @brief Return an array with default list of sections to export. * - * @hooks get_default_export_sections - * * \e array \b sections * @return array with default section names to export */ function get_default_export_sections() { @@ -703,6 +717,11 @@ function get_default_export_sections() { ]; $cb = [ 'sections' => $sections ]; + /** + * @hooks get_default_export_sections + * Called to get the default list of functional data groups to export in identity_basic_export(). + * * \e array \b sections - return value + */ call_hooks('get_default_export_sections', $cb); return $cb['sections']; @@ -714,15 +733,11 @@ function get_default_export_sections() { * which would be necessary to create a nomadic identity clone. This includes * most channel resources and connection information with the exception of content. * - * @hooks identity_basic_export - * * \e int \b channel_id - * * \e array \b sections - * * \e array \b data * @param int $channel_id * Channel_id to export * @param array $sections (optional) * Which sections to include in the export, default see get_default_export_sections() - * @returns array + * @return array * See function for details */ function identity_basic_export($channel_id, $sections = null) { @@ -967,7 +982,7 @@ function identity_basic_export($channel_id, $sections = null) { } } - if(in_array('items',$sections)) { + if(in_array('items', $sections)) { /** @warning this may run into memory limits on smaller systems */ /** export three months of posts. If you want to export and import all posts you have to start with @@ -991,22 +1006,42 @@ function identity_basic_export($channel_id, $sections = null) { } } - $addon = [ 'channel_id' => $channel_id, 'sections' => $sections, 'data' => $ret]; - call_hooks('identity_basic_export',$addon); + $addon = [ + 'channel_id' => $channel_id, + 'sections' => $sections, + 'data' => $ret + ]; + /** + * @hooks identity_basic_export + * Called when exporting a channel's basic information for backup or transfer. + * * \e number \b channel_id - The channel ID + * * \e array \b sections + * * \e array \b data - The data will get returned + */ + call_hooks('identity_basic_export', $addon); $ret = $addon['data']; return $ret; } - -function identity_export_year($channel_id,$year,$month = 0) { +/** + * @brief Export items for a year, or a month of a year. + * + * @param int $channel_id The channel ID + * @param number $year YYYY + * @param number $month (optional) 0-12, default 0 complete year + * @return array An associative array + * * \e array \b relocate - (optional) + * * \e array \b item - array with items encoded_item() + */ +function identity_export_year($channel_id, $year, $month = 0) { if(! $year) return array(); if($month && $month <= 12) { - $target_month = sprintf('%02d',$month); - $target_month_plus = sprintf('%02d',$month+1); + $target_month = sprintf('%02d', $month); + $target_month_plus = sprintf('%02d', $month+1); } else $target_month = '01'; @@ -1017,13 +1052,13 @@ function identity_export_year($channel_id,$year,$month = 0) { if($ch) { $ret['relocate'] = [ 'channel_address' => $ch['channel_address'], 'url' => z_root()]; } - $mindate = datetime_convert('UTC','UTC',$year . '-' . $target_month . '-01 00:00:00'); + $mindate = datetime_convert('UTC', 'UTC', $year . '-' . $target_month . '-01 00:00:00'); if($month && $month < 12) - $maxdate = datetime_convert('UTC','UTC',$year . '-' . $target_month_plus . '-01 00:00:00'); + $maxdate = datetime_convert('UTC', 'UTC', $year . '-' . $target_month_plus . '-01 00:00:00'); else - $maxdate = datetime_convert('UTC','UTC',$year+1 . '-01-01 00:00:00'); + $maxdate = datetime_convert('UTC', 'UTC', $year+1 . '-01-01 00:00:00'); - $r = q("select * from item where ( item_wall = 1 or item_type != %d ) and item_deleted = 0 and uid = %d and created >= '%s' and created < '%s' and resource_type = '' order by created", + $r = q("select * from item where ( item_wall = 1 or item_type != %d ) and item_deleted = 0 and uid = %d and created >= '%s' and created < '%s' and resource_type = '' order by created", intval(ITEM_TYPE_POST), intval($channel_id), dbesc($mindate), @@ -1033,9 +1068,9 @@ function identity_export_year($channel_id,$year,$month = 0) { if($r) { $ret['item'] = array(); xchan_query($r); - $r = fetch_post_tags($r,true); + $r = fetch_post_tags($r, true); foreach($r as $rr) - $ret['item'][] = encode_item($rr,true); + $ret['item'][] = encode_item($rr, true); } return $ret; @@ -1274,6 +1309,7 @@ function profile_edit_menu($uid) { foreach($r as $rr) { if(!($multi_profiles || $rr['is_default'])) continue; + $ret['menu']['entries'][] = array( 'photo' => $rr['thumb'], 'id' => $rr['id'], @@ -1298,8 +1334,8 @@ function profile_edit_menu($uid) { * * @param array $profile * @param int $block - * @param boolean $show_connect - * @param mixed $zcard + * @param boolean $show_connect (optional) default true + * @param mixed $zcard (optional) default false * * @return HTML string suitable for sidebar inclusion * Exceptions: Returns empty string if passed $profile is wrong type or not populated @@ -1327,6 +1363,10 @@ function profile_sidebar($profile, $block = 0, $show_connect = true, $zcard = fa $profile['picdate'] = urlencode($profile['picdate']); + /** + * @hooks profile_sidebar_enter + * Called before generating the 'channel sidebar' or mini-profile. + */ call_hooks('profile_sidebar_enter', $profile); if($show_connect) { @@ -1409,22 +1449,30 @@ function profile_sidebar($profile, $block = 0, $show_connect = true, $zcard = fa '$reddress' => $reddress, '$rating' => '', '$contact_block' => $contact_block, - '$editmenu' => profile_edit_menu($profile['uid']) + '$editmenu' => profile_edit_menu($profile['uid']) )); - $arr = array('profile' => $profile, 'entry' => $o); + $arr = [ + 'profile' => $profile, + 'entry' => $o + ]; + /** + * @hooks profile_sidebar + * Called when generating the 'channel sidebar' or mini-profile. + * * \e array \b profile + * * \e string \b entry - The parsed HTML template + */ call_hooks('profile_sidebar', $arr); return $arr['entry']; - } function gender_icon($gender) { // logger('gender: ' . $gender); - // This can easily get throw off if the observer language is different + // This can easily get throw off if the observer language is different // than the channel owner language. if(strpos(strtolower($gender),strtolower(t('Female'))) !== false) @@ -1442,7 +1490,7 @@ function gender_icon($gender) { } -function advanced_profile(&$a) { +function advanced_profile() { require_once('include/text.php'); if(! perm_is_allowed(App::$profile['profile_uid'],get_observer_hash(),'view_profile')) return ''; @@ -1622,16 +1670,21 @@ function get_my_address() { * If somebody arrives at our site using a zid, add their xchan to our DB if we * don't have it already. * And if they aren't already authenticated here, attempt reverse magic auth. - * - * @hooks zid_init - * * \e string \b zid - their zid - * * \e string \b url - the destination url */ function zid_init() { $tmp_str = get_my_address(); if(validate_email($tmp_str)) { - $arr = array('zid' => $tmp_str, 'url' => App::$cmd); - call_hooks('zid_init',$arr); + $arr = [ + 'zid' => $tmp_str, + 'url' => App::$cmd + ]; + /** + * @hooks zid_init + * * \e string \b zid - their zid + * * \e string \b url - the destination url + */ + call_hooks('zid_init', $arr); + if(! local_channel()) { $r = q("select * from hubloc where hubloc_addr = '%s' order by hubloc_connected desc limit 1", dbesc($tmp_str) @@ -1641,7 +1694,8 @@ function zid_init() { } if($r && remote_channel() && remote_channel() === $r[0]['hubloc_hash']) return; - logger('zid_init: not authenticated. Invoking reverse magic-auth for ' . $tmp_str); + + logger('Not authenticated. Invoking reverse magic-auth for ' . $tmp_str); // try to avoid recursion - but send them home to do a proper magic auth $query = App::$query_string; $query = str_replace(array('?zid=','&zid='),array('?rzid=','&rzid='),$query); @@ -1650,7 +1704,7 @@ function zid_init() { goaway($r[0]['hubloc_url'] . '/magic' . '?f=&rev=1&owa=1&dest=' . z_root() . $dest); } else - logger('zid_init: no hubloc found.'); + logger('No hubloc found.'); } } } @@ -1673,14 +1727,16 @@ function zat_init() { } - - -// Used from within PCSS themes to set theme parameters. If there's a -// puid request variable, that is the "page owner" and normally their theme -// settings take precedence; unless a local user sets the "always_my_theme" -// system pconfig, which means they don't want to see anybody else's theme -// settings except their own while on this site. - +/** + * @brief Used from within PCSS themes to set theme parameters. + * + * If there's a puid request variable, that is the "page owner" and normally + * their theme settings take precedence; unless a local user sets the "always_my_theme" + * system pconfig, which means they don't want to see anybody else's theme + * settings except their own while on this site. + * + * @return int + */ function get_theme_uid() { $uid = (($_REQUEST['puid']) ? intval($_REQUEST['puid']) : 0); if(local_channel()) { @@ -1700,9 +1756,9 @@ function get_theme_uid() { * @brief Retrieves the path of the default_profile_photo for this system * with the specified size. * -* @param int $size -* one of (300, 80, 48) -* @returns string with path to profile photo +* @param int $size (optional) default 300 +* one of (300, 80, 48) +* @return string with path to profile photo */ function get_default_profile_photo($size = 300) { $scheme = get_config('system','default_profile_photo'); @@ -1715,9 +1771,9 @@ function get_default_profile_photo($size = 300) { /** * @brief Test whether a given identity is NOT a member of the Hubzilla. * - * @param string $s; + * @param string $s * xchan_hash of the identity in question - * @returns boolean true or false + * @return boolean true or false */ function is_foreigner($s) { return((strpbrk($s, '.:@')) ? true : false); @@ -1726,14 +1782,21 @@ function is_foreigner($s) { /** * @brief Test whether a given identity is a member of the Hubzilla. * - * @param string $s; + * @param string $s * xchan_hash of the identity in question - * @returns boolean true or false + * @return boolean true or false */ function is_member($s) { return((is_foreigner($s)) ? false : true); } +/** + * @brief Get chatpresence status for nick. + * + * @param string $nick + * @return array An associative array with + * * \e bool \b result + */ function get_online_status($nick) { $ret = array('result' => false); @@ -1748,6 +1811,7 @@ function get_online_status($nick) { $hide = get_pconfig($r[0]['channel_id'],'system','hide_online_status'); if($hide) return $ret; + $x = q("select cp_status from chatpresence where cp_xchan = '%s' and cp_room = 0 limit 1", dbesc($r[0]['channel_hash']) ); @@ -1759,6 +1823,12 @@ function get_online_status($nick) { } +/** + * @brief + * + * @param string $webbie + * @return array|boolean|string + */ function remote_online_status($webbie) { $result = false; @@ -1782,11 +1852,10 @@ function remote_online_status($webbie) { /** - * @brief + * @brief Return the parsed identity selector HTML template. * - * @return string + * @return string with parsed HTML channel_id_selet template */ - function identity_selector() { if(local_channel()) { $r = q("select channel.*, xchan.* from channel left join xchan on channel.channel_hash = xchan.xchan_hash where channel.channel_account_id = %d and channel_removed = 0 order by channel_name ", @@ -1798,6 +1867,7 @@ function identity_selector() { '$channels' => $r, '$selected' => local_channel() )); + return $o; } } @@ -1809,14 +1879,17 @@ function identity_selector() { function is_public_profile() { if(! local_channel()) return false; + if(intval(get_config('system','block_public'))) return false; + $channel = App::get_channel(); if($channel) { $perm = \Zotlabs\Access\PermissionLimits::Get($channel['channel_id'],'view_profile'); if($perm == PERMS_PUBLIC) return true; } + return false; } @@ -1861,7 +1934,7 @@ function get_profile_fields_advanced($filter = 0) { * * @param int $channel_id * The channel to disable notifications for - * @returns int + * @return int * Current notification flag value. Send this to notifications_on() to restore the channel settings when finished * with the activity requiring notifications_off(); */ @@ -1909,12 +1982,17 @@ function get_channel_default_perms($uid) { } -function profiles_build_sync($channel_id) { +function profiles_build_sync($channel_id,$send = true) { $r = q("select * from profile where uid = %d", intval($channel_id) ); if($r) { - build_sync_packet($channel_id,array('profile' => $r)); + if($send) { + build_sync_packet($channel_id,array('profile' => $r)); + } + else { + return $r; + } } } @@ -1993,13 +2071,14 @@ function get_cover_photo($channel_id,$format = 'bbcode', $res = PHOTO_RES_COVER_ return $output; } + /** - * @brief + * @brief Return parsed HTML zcard template. * * @param array $channel - * @param string $observer_hash - * @param array $args - * @return string + * @param string $observer_hash (optional) + * @param array $args (optional) + * @return string parsed HTML from \e zcard template */ function get_zcard($channel, $observer_hash = '', $args = array()) { @@ -2067,6 +2146,14 @@ function get_zcard($channel, $observer_hash = '', $args = array()) { } +/** + * @brief Return parsed HTML embed zcard template. + * + * @param array $channel + * @param string $observer_hash (optional) + * @param array $args (optional) + * @return string parsed HTML from \e zcard_embed template + */ function get_zcard_embed($channel, $observer_hash = '', $args = array()) { logger('get_zcard_embed'); @@ -2133,10 +2220,12 @@ function get_zcard_embed($channel, $observer_hash = '', $args = array()) { } /** - * @brief + * @brief Get a channel array from a channel nickname. * - * @param string $nick - * @return mixed + * @param string $nick - A channel_address nickname + * @return array|boolean + * - array with channel entry + * - false if no channel with $nick was found */ function channelx_by_nick($nick) { $r = q("SELECT * FROM channel left join xchan on channel_hash = xchan_hash WHERE channel_address = '%s' and channel_removed = 0 LIMIT 1", @@ -2147,10 +2236,10 @@ function channelx_by_nick($nick) { } /** - * @brief + * @brief Get a channel array by a channel_hash. * * @param string $hash - * @return mixed + * @return array|boolean false if channel ID not found, otherwise the channel array */ function channelx_by_hash($hash) { $r = q("SELECT * FROM channel left join xchan on channel_hash = xchan_hash WHERE channel_hash = '%s' and channel_removed = 0 LIMIT 1", @@ -2161,13 +2250,13 @@ function channelx_by_hash($hash) { } /** - * @brief + * @brief Get a channel array by a channel ID. * - * @param int $id - * @return mixed + * @param int $id A channel ID + * @return array|boolean false if channel ID not found, otherwise the channel array */ function channelx_by_n($id) { - $r = q("SELECT * FROM channel left join xchan on channel_hash = xchan_hash WHERE channel_id = %d and channel_removed = 0 LIMIT 1", + $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) ); @@ -2177,7 +2266,7 @@ function channelx_by_n($id) { /** * @brief * - * @param string $channel + * @param array $channel * @return string */ function channel_reddress($channel) { @@ -2188,67 +2277,77 @@ function channel_reddress($channel) { } +/** + * @brief Get manual channel conversation update config. + * + * Check the channel config \e manual_conversation_update, if not set fall back + * to global system config, defaults to 1 if nothing set. + * + * @param int $channel_id + * @return int + */ function channel_manual_conv_update($channel_id) { - $x = get_pconfig($channel_id, 'system','manual_conversation_update'); + $x = get_pconfig($channel_id, 'system', 'manual_conversation_update'); if($x === false) - $x = get_config('system','manual_conversation_update', 1); + $x = get_config('system', 'manual_conversation_update', 1); return intval($x); } +/** + * @brief Return parsed HTML remote_login template. + * + * @return string with parsed HTML from \e remote_login template + */ function remote_login() { + $o = replace_macros(get_markup_template('remote_login.tpl'),array( + '$title' => t('Remote Authentication'), + '$desc' => t('Enter your channel address (e.g. channel@example.com)'), + '$submit' => t('Authenticate') + )); - $o = replace_macros(get_markup_template('remote_login.tpl'),array( - '$title' => t('Remote Authentication'), - '$desc' => t('Enter your channel address (e.g. channel@example.com)'), - '$submit' => t('Authenticate') - )); - return $o; - + return $o; } function channel_store_lowlevel($arr) { - $store = [ - 'channel_account_id' => ((array_key_exists('channel_account_id',$arr)) ? $arr['channel_account_id'] : '0'), - 'channel_primary' => ((array_key_exists('channel_primary',$arr)) ? $arr['channel_primary'] : '0'), - 'channel_name' => ((array_key_exists('channel_name',$arr)) ? $arr['channel_name'] : ''), - 'channel_address' => ((array_key_exists('channel_address',$arr)) ? $arr['channel_address'] : ''), - 'channel_guid' => ((array_key_exists('channel_guid',$arr)) ? $arr['channel_guid'] : ''), - 'channel_guid_sig' => ((array_key_exists('channel_guid_sig',$arr)) ? $arr['channel_guid_sig'] : ''), - 'channel_hash' => ((array_key_exists('channel_hash',$arr)) ? $arr['channel_hash'] : ''), - 'channel_timezone' => ((array_key_exists('channel_timezone',$arr)) ? $arr['channel_timezone'] : 'UTC'), - 'channel_location' => ((array_key_exists('channel_location',$arr)) ? $arr['channel_location'] : ''), - 'channel_theme' => ((array_key_exists('channel_theme',$arr)) ? $arr['channel_theme'] : ''), - 'channel_startpage' => ((array_key_exists('channel_startpage',$arr)) ? $arr['channel_startpage'] : ''), - 'channel_pubkey' => ((array_key_exists('channel_pubkey',$arr)) ? $arr['channel_pubkey'] : ''), - 'channel_prvkey' => ((array_key_exists('channel_prvkey',$arr)) ? $arr['channel_prvkey'] : ''), - 'channel_notifyflags' => ((array_key_exists('channel_notifyflags',$arr)) ? $arr['channel_notifyflags'] : '65535'), - 'channel_pageflags' => ((array_key_exists('channel_pageflags',$arr)) ? $arr['channel_pageflags'] : '0'), - 'channel_dirdate' => ((array_key_exists('channel_dirdate',$arr)) ? $arr['channel_dirdate'] : NULL_DATE), - 'channel_lastpost' => ((array_key_exists('channel_lastpost',$arr)) ? $arr['channel_lastpost'] : NULL_DATE), - 'channel_deleted' => ((array_key_exists('channel_deleted',$arr)) ? $arr['channel_deleted'] : NULL_DATE), - 'channel_max_anon_mail' => ((array_key_exists('channel_max_anon_mail',$arr)) ? $arr['channel_max_anon_mail'] : '10'), - 'channel_max_friend_req' => ((array_key_exists('channel_max_friend_req',$arr)) ? $arr['channel_max_friend_req'] : '10'), - 'channel_expire_days' => ((array_key_exists('channel_expire_days',$arr)) ? $arr['channel_expire_days'] : '0'), - 'channel_passwd_reset' => ((array_key_exists('channel_passwd_reset',$arr)) ? $arr['channel_passwd_reset'] : ''), - 'channel_default_group' => ((array_key_exists('channel_default_group',$arr)) ? $arr['channel_default_group'] : ''), - 'channel_allow_cid' => ((array_key_exists('channel_allow_cid',$arr)) ? $arr['channel_allow_cid'] : ''), - 'channel_allow_gid' => ((array_key_exists('channel_allow_gid',$arr)) ? $arr['channel_allow_gid'] : ''), - 'channel_deny_cid' => ((array_key_exists('channel_deny_cid',$arr)) ? $arr['channel_deny_cid'] : ''), - 'channel_deny_gid' => ((array_key_exists('channel_deny_gid',$arr)) ? $arr['channel_deny_gid'] : ''), - 'channel_removed' => ((array_key_exists('channel_removed',$arr)) ? $arr['channel_removed'] : '0'), - 'channel_system' => ((array_key_exists('channel_system',$arr)) ? $arr['channel_system'] : '0'), - + $store = [ + 'channel_account_id' => ((array_key_exists('channel_account_id',$arr)) ? $arr['channel_account_id'] : '0'), + 'channel_primary' => ((array_key_exists('channel_primary',$arr)) ? $arr['channel_primary'] : '0'), + 'channel_name' => ((array_key_exists('channel_name',$arr)) ? $arr['channel_name'] : ''), + 'channel_address' => ((array_key_exists('channel_address',$arr)) ? $arr['channel_address'] : ''), + 'channel_guid' => ((array_key_exists('channel_guid',$arr)) ? $arr['channel_guid'] : ''), + 'channel_guid_sig' => ((array_key_exists('channel_guid_sig',$arr)) ? $arr['channel_guid_sig'] : ''), + 'channel_hash' => ((array_key_exists('channel_hash',$arr)) ? $arr['channel_hash'] : ''), + 'channel_timezone' => ((array_key_exists('channel_timezone',$arr)) ? $arr['channel_timezone'] : 'UTC'), + 'channel_location' => ((array_key_exists('channel_location',$arr)) ? $arr['channel_location'] : ''), + 'channel_theme' => ((array_key_exists('channel_theme',$arr)) ? $arr['channel_theme'] : ''), + 'channel_startpage' => ((array_key_exists('channel_startpage',$arr)) ? $arr['channel_startpage'] : ''), + 'channel_pubkey' => ((array_key_exists('channel_pubkey',$arr)) ? $arr['channel_pubkey'] : ''), + 'channel_prvkey' => ((array_key_exists('channel_prvkey',$arr)) ? $arr['channel_prvkey'] : ''), + 'channel_notifyflags' => ((array_key_exists('channel_notifyflags',$arr)) ? $arr['channel_notifyflags'] : '65535'), + 'channel_pageflags' => ((array_key_exists('channel_pageflags',$arr)) ? $arr['channel_pageflags'] : '0'), + 'channel_dirdate' => ((array_key_exists('channel_dirdate',$arr)) ? $arr['channel_dirdate'] : NULL_DATE), + 'channel_lastpost' => ((array_key_exists('channel_lastpost',$arr)) ? $arr['channel_lastpost'] : NULL_DATE), + 'channel_deleted' => ((array_key_exists('channel_deleted',$arr)) ? $arr['channel_deleted'] : NULL_DATE), + 'channel_max_anon_mail' => ((array_key_exists('channel_max_anon_mail',$arr)) ? $arr['channel_max_anon_mail'] : '10'), + 'channel_max_friend_req' => ((array_key_exists('channel_max_friend_req',$arr)) ? $arr['channel_max_friend_req'] : '10'), + 'channel_expire_days' => ((array_key_exists('channel_expire_days',$arr)) ? $arr['channel_expire_days'] : '0'), + 'channel_passwd_reset' => ((array_key_exists('channel_passwd_reset',$arr)) ? $arr['channel_passwd_reset'] : ''), + 'channel_default_group' => ((array_key_exists('channel_default_group',$arr)) ? $arr['channel_default_group'] : ''), + 'channel_allow_cid' => ((array_key_exists('channel_allow_cid',$arr)) ? $arr['channel_allow_cid'] : ''), + 'channel_allow_gid' => ((array_key_exists('channel_allow_gid',$arr)) ? $arr['channel_allow_gid'] : ''), + 'channel_deny_cid' => ((array_key_exists('channel_deny_cid',$arr)) ? $arr['channel_deny_cid'] : ''), + 'channel_deny_gid' => ((array_key_exists('channel_deny_gid',$arr)) ? $arr['channel_deny_gid'] : ''), + 'channel_removed' => ((array_key_exists('channel_removed',$arr)) ? $arr['channel_removed'] : '0'), + 'channel_system' => ((array_key_exists('channel_system',$arr)) ? $arr['channel_system'] : '0'), 'channel_moved' => ((array_key_exists('channel_moved',$arr)) ? $arr['channel_moved'] : ''), 'channel_password' => ((array_key_exists('channel_password',$arr)) ? $arr['channel_password'] : ''), 'channel_salt' => ((array_key_exists('channel_salt',$arr)) ? $arr['channel_salt'] : '') - ]; - return create_table_from_array('channel',$store); - + return create_table_from_array('channel', $store); } function profile_store_lowlevel($arr) { @@ -2304,16 +2403,22 @@ function profile_store_lowlevel($arr) { } -// Included here for completeness, but this is a very dangerous operation. -// It is the caller's responsibility to confirm the requestor's intent and -// authorisation to do this. - -function account_remove($account_id,$local = true,$unset_session = true) { +/** + * Included here for completeness, but this is a very dangerous operation. + * It is the caller's responsibility to confirm the requestor's intent and + * authorisation to do this. + * + * @param int $account_id + * @param boolean $local (optional) default true + * @param boolean $unset_session (optional) default true + * @return boolean|array + */ +function account_remove($account_id, $local = true, $unset_session = true) { logger('account_remove: ' . $account_id); if(! intval($account_id)) { - logger('account_remove: no account.'); + logger('No account.'); return false; } @@ -2334,7 +2439,7 @@ function account_remove($account_id,$local = true,$unset_session = true) { $account_email=$r[0]['account_email']; if(! $r) { - logger('account_remove: No account with id: ' . $account_id); + logger('No account with id: ' . $account_id); return false; } @@ -2351,7 +2456,6 @@ function account_remove($account_id,$local = true,$unset_session = true) { intval($account_id) ); - if ($unset_session) { App::$session->nuke(); notice( sprintf(t('Account \'%s\' deleted'),$account_email) . EOL); @@ -2364,8 +2468,6 @@ function account_remove($account_id,$local = true,$unset_session = true) { /** * @brief Removes a channel. * - * @hooks channel_remove - * * \e array \b entry from channel tabel for $channel_id * @param int $channel_id * @param boolean $local default true * @param boolean $unset_session default false @@ -2386,6 +2488,11 @@ function channel_remove($channel_id, $local = true, $unset_session = false) { $channel = $r[0]; + /** + * @hooks channel_remove + * Called when removing a channel. + * * \e array with entry from channel tabel for $channel_id + */ call_hooks('channel_remove', $r[0]); if(! $local) { @@ -2425,19 +2532,43 @@ function channel_remove($channel_id, $local = true, $unset_session = false) { } } + q("DELETE FROM app WHERE app_channel = %d", intval($channel_id)); + q("DELETE FROM atoken WHERE atoken_uid = %d", intval($channel_id)); + q("DELETE FROM chatroom WHERE cr_uid = %d", intval($channel_id)); + q("DELETE FROM conv WHERE uid = %d", intval($channel_id)); q("DELETE FROM groups WHERE uid = %d", intval($channel_id)); q("DELETE FROM group_member WHERE uid = %d", intval($channel_id)); q("DELETE FROM event WHERE uid = %d", intval($channel_id)); - q("DELETE FROM item WHERE uid = %d", intval($channel_id)); q("DELETE FROM mail WHERE channel_id = %d", intval($channel_id)); + q("DELETE FROM menu WHERE menu_channel_id = %d", intval($channel_id)); + q("DELETE FROM menu_item WHERE mitem_channel_id = %d", intval($channel_id)); + q("DELETE FROM notify WHERE uid = %d", intval($channel_id)); + q("DELETE FROM obj WHERE obj_channel = %d", intval($channel_id)); + + q("DELETE FROM photo WHERE uid = %d", intval($channel_id)); q("DELETE FROM attach WHERE uid = %d", intval($channel_id)); q("DELETE FROM profile WHERE uid = %d", intval($channel_id)); - q("DELETE FROM pconfig WHERE uid = %d", intval($channel_id)); + q("DELETE FROM src WHERE src_channel_id = %d", intval($channel_id)); + + $r = q("select resource_id FROM attach WHERE uid = %d", intval($channel_id)); + if($r) { + foreach($r as $rv) { + attach_delete($channel_id,$rv['resource_id']); + } + } + + + + $r = q("select id from item where uid = %d", intval($channel_id)); + if($r) { + foreach($r as $rv) { + drop_item($rv['id'],false); + } + } - /// @FIXME At this stage we need to remove the file resources located under /store/$nickname q("delete from abook where abook_xchan = '%s' and abook_self = 1 ", dbesc($channel['channel_hash']) @@ -2457,6 +2588,7 @@ function channel_remove($channel_id, $local = true, $unset_session = false) { $rr = q("update account set account_default_channel = %d where account_id = %d", intval($r[0]['channel_id']), intval(App::$account['account_id'])); + logger("Default channel deleted, changing default to channel_id " . $r[0]['channel_id']); } else { @@ -2490,19 +2622,11 @@ function channel_remove($channel_id, $local = true, $unset_session = false) { } //remove from file system - $r = q("select channel_address from channel where channel_id = %d limit 1", - intval($channel_id) - ); - if($r) { - $channel_address = $r[0]['channel_address'] ; - } - if($channel_address) { - $f = 'store/' . $channel_address.'/'; - logger('delete '. $f); - if(is_dir($f)) { - @rrmdir($f); - } + + $f = 'store/' . $channel['channel_address']; + if(is_dir($f)) { + @rrmdir($f); } Zotlabs\Daemon\Master::Summon(array('Directory',$channel_id)); @@ -2513,6 +2637,20 @@ function channel_remove($channel_id, $local = true, $unset_session = false) { } } +// execute this at least a week after removing a channel + +function channel_remove_final($channel_id) { + + q("delete from abook where abook_channel = %d", intval($channel_id)); + q("delete from abconfig where chan = %d", intval($channel_id)); + q("delete from pconfig where uid = %d", intval($channel_id)); + + +} + + + + /** * @brief This checks if a channel is allowed to publish executable code. * @@ -2531,20 +2669,30 @@ function channel_codeallowed($channel_id) { return true; return false; - } function anon_identity_init($reqvars) { - $x = [ 'request_vars' => $reqvars, 'xchan' => null, 'success' => 'unset' ]; - call_hooks('anon_identity_init',$x); + $x = [ + 'request_vars' => $reqvars, + 'xchan' => null, + 'success' => 'unset' + ]; + /** + * @hooks anon_identity_init + * * \e array \b request_vars + * * \e string \b xchan - return value + * * \e string|int \b success - Must be a number, so xchan return value gets used + */ + call_hooks('anon_identity_init', $x); + if($x['success'] !== 'unset' && intval($x['success']) && $x['xchan']) return $x['xchan']; - // allow a captcha handler to over-ride + // allow a captcha handler to over-ride if($x['success'] !== 'unset' && (intval($x['success']) === 0)) - return false; - + return false; + $anon_name = strip_tags(trim($reqvars['anonname'])); $anon_email = strip_tags(trim($reqvars['anonmail'])); @@ -2571,7 +2719,7 @@ function anon_identity_init($reqvars) { ); if(! $x) { - xchan_store_lowlevel([ + xchan_store_lowlevel([ 'xchan_guid' => $anon_email, 'xchan_hash' => $hash, 'xchan_name' => $anon_name, @@ -2579,7 +2727,7 @@ function anon_identity_init($reqvars) { 'xchan_network' => 'unknown', 'xchan_name_date' => datetime_convert() ]); - + $x = q("select * from xchan where xchan_guid = '%s' and xchan_hash = '%s' and xchan_network = 'unknown' limit 1", dbesc($anon_email), @@ -2597,10 +2745,26 @@ function anon_identity_init($reqvars) { dbesc($anon_email), dbesc($hash) ); - } return $x[0]; } +/** + * @brief Create a channel array from proxy channel (pchan). + * + * @param array $pchan The proxy channel + * @return array channel array + */ +function pchan_to_chan($pchan) { + $chan = $pchan; + $chan['channel_address'] = $pchan['pchan_guid']; + $chan['channel_hash'] = $pchan['pchan_hash']; + $chan['channel_pubkey'] = $pchan['pchan_pubkey']; + $chan['channel_prvkey'] = $pchan['pchan_prvkey']; + $chan['channel_name'] = $pchan['xchan_name']; + + return $chan; +} + diff --git a/include/connections.php b/include/connections.php index 60bce018e..e9d7daa2d 100644 --- a/include/connections.php +++ b/include/connections.php @@ -369,7 +369,7 @@ function contact_remove($channel_id, $abook_id) { return false; - $r = q("select * from item where (owner_xchan = '%s' or author_xchan = '%s') and uid = %d", + $r = q("select id from item where (owner_xchan = '%s' or author_xchan = '%s') and uid = %d", dbesc($abook['abook_xchan']), dbesc($abook['abook_xchan']), intval($channel_id) diff --git a/include/contact_widgets.php b/include/contact_widgets.php index 9cc9f0baf..a105bca19 100644 --- a/include/contact_widgets.php +++ b/include/contact_widgets.php @@ -72,21 +72,66 @@ function categories_widget($baseurl,$selected = '') { $item_normal = item_normal(); $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)) { + foreach($r as $rr) + $terms[] = array('name' => $rr['term'], 'selected' => (($selected == $rr['term']) ? 'selected' : '')); + + return replace_macros(get_markup_template('categories_widget.tpl'),array( + '$title' => t('Categories'), + '$desc' => '', + '$sel_all' => (($selected == '') ? 'selected' : ''), + '$all' => t('Everything'), + '$terms' => $terms, + '$base' => $baseurl, + + )); + } + return ''; +} + +function cardcategories_widget($baseurl,$selected = '') { + + if(! feature_enabled(App::$profile['profile_uid'],'categories')) + return ''; + + $sql_extra = item_permissions_sql(App::$profile['profile_uid']); + + $item_normal = "and item.item_hidden = 0 and item.item_type = 6 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 "; + + $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 + 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.owner_xchan = '%s' $item_normal $sql_extra - order by term.term asc", + order by term.term asc", intval(App::$profile['profile_uid']), - intval(TERM_CATEGORY), + intval(TERM_CATEGORY), intval(TERM_OBJ_POST), - dbesc(App::$profile['channel_hash']) + dbesc(App::$profile['channel_hash']) ); if($r && count($r)) { foreach($r as $rr) @@ -105,32 +150,33 @@ function categories_widget($baseurl,$selected = '') { return ''; } -function cardcategories_widget($baseurl,$selected = '') { + +function articlecategories_widget($baseurl,$selected = '') { if(! feature_enabled(App::$profile['profile_uid'],'categories')) return ''; $sql_extra = item_permissions_sql(App::$profile['profile_uid']); - $item_normal = "and item.item_hidden = 0 and item.item_type = 6 and item.item_deleted = 0 + $item_normal = "and item.item_hidden = 0 and item.item_type = 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 "; $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 + 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.owner_xchan = '%s' $item_normal $sql_extra - order by term.term asc", + order by term.term asc", intval(App::$profile['profile_uid']), - intval(TERM_CATEGORY), + intval(TERM_CATEGORY), intval(TERM_OBJ_POST), - dbesc(App::$profile['channel_hash']) + dbesc(App::$profile['channel_hash']) ); if($r && count($r)) { foreach($r as $rr) @@ -151,6 +197,9 @@ function cardcategories_widget($baseurl,$selected = '') { + + + function common_friends_visitor_widget($profile_uid,$cnt = 25) { if(local_channel() == $profile_uid) diff --git a/include/conversation.php b/include/conversation.php index f395b2cbe..77694deb3 100644 --- a/include/conversation.php +++ b/include/conversation.php @@ -162,6 +162,18 @@ function localize_item(&$item){ elseif(activity_match($item['verb'],ACTIVITY_DISLIKE)) { $bodyverb = t('%1$s doesn\'t like %2$s\'s %3$s'); } + + // short version, in notification strings the author will be displayed separately + + if(activity_match($item['verb'],ACTIVITY_LIKE)) { + $shortbodyverb = t('likes %1$s\'s %2$s'); + } + elseif(activity_match($item['verb'],ACTIVITY_DISLIKE)) { + $shortbodyverb = t('doesn\'t like %1$s\'s %2$s'); + } + + $item['shortlocalize'] = sprintf($shortbodyverb, $objauthor, $plink); + $item['body'] = $item['localize'] = sprintf($bodyverb, $author, $objauthor, $plink); if($Bphoto != "") $item['body'] .= "\n\n\n" . '[zrl=' . chanlink_url($author_link) . '][zmg=80x80]' . $Bphoto . '[/zmg][/zrl]'; @@ -470,10 +482,10 @@ function conversation($items, $mode, $update, $page_mode = 'traditional', $prepa $previewing = (($preview) ? ' preview ' : ''); $preview_lbl = t('This is an unsaved preview'); - if ($mode === 'network') { + if (in_array($mode, [ 'network', 'pubstream'])) { $profile_owner = local_channel(); - $page_writeable = true; + $page_writeable = ((local_channel()) ? true : false); if (!$update) { // The special div is needed for liveUpdate to kick in for this page. @@ -501,6 +513,12 @@ function conversation($items, $mode, $update, $page_mode = 'traditional', $prepa } } + elseif ($mode === 'hq') { + $profile_owner = local_channel(); + $page_writeable = true; + $live_update_div = '<div id="live-hq"></div>' . "\r\n"; + } + elseif ($mode === 'channel') { $profile_owner = App::$profile['profile_uid']; $page_writeable = ($profile_owner == local_channel()); @@ -527,6 +545,15 @@ function conversation($items, $mode, $update, $page_mode = 'traditional', $prepa $jsreload = $_SESSION['return_url']; } + elseif ($mode === 'articles') { + $profile_owner = App::$profile['profile_uid']; + $page_writeable = ($profile_owner == local_channel()); + $live_update_div = '<div id="live-articles"></div>' . "\r\n" + . "<script> var profile_uid = " . App::$profile['profile_uid'] + . "; var netargs = '?f='; var profile_page = " . App::$pager['page'] . "; </script>\r\n"; + $jsreload = $_SESSION['return_url']; + } + elseif ($mode === 'display') { $profile_owner = local_channel(); @@ -561,19 +588,16 @@ function conversation($items, $mode, $update, $page_mode = 'traditional', $prepa if (! feature_enabled($profile_owner,'multi_delete')) $page_dropping = false; - $uploading = true; + $uploading = false; - if($profile_owner > 0) { - $owner_channel = channelx_by_n($profile_owner); - if($owner_channel['channel_allow_cid'] || $owner_channel['channel_allow_gid'] - || $owner_channel['channel_deny_cid'] || $owner_channel['channel_deny_gid']) { - $uploading = false; + if(local_channel()) { + $cur_channel = App::get_channel(); + if($cur_channel['channel_allow_cid'] === '' && $cur_channel['channel_allow_gid'] === '' + && $cur_channel['channel_deny_cid'] === '' && $cur_channel['channel_deny_gid'] === '' + && intval(\Zotlabs\Access\PermissionLimits::Get(local_channel(),'view_storage')) === PERMS_PUBLIC) { + $uploading = true; } } - else { - $uploading = false; - } - $channel = App::get_channel(); $observer = App::get_observer(); @@ -610,13 +634,14 @@ function conversation($items, $mode, $update, $page_mode = 'traditional', $prepa // "New Item View" on network page or search page results // - just loop through the items and format them minimally for display - - //$tpl = get_markup_template('search_item.tpl'); $tpl = 'search_item.tpl'; foreach($items as $item) { - $x = [ 'mode' => $mode, 'item' => $item ]; + $x = [ + 'mode' => $mode, + 'item' => $item + ]; call_hooks('stream_item',$x); if($x['item']['blocked']) @@ -637,14 +662,7 @@ function conversation($items, $mode, $update, $page_mode = 'traditional', $prepa if(((activity_match($item['verb'],ACTIVITY_LIKE)) || (activity_match($item['verb'],ACTIVITY_DISLIKE))) && ($item['id'] != $item['parent'])) continue; -// $nickname = $item['nickname']; } -// else -// $nickname = App::$user['nickname']; - -// $profile_name = ((strlen($item['author-name'])) ? $item['author-name'] : $item['name']); -// if($item['author-link'] && (! $item['author-name'])) -// $profile_name = $item['author-link']; $sp = false; $profile_link = best_link_url($item,$sp); @@ -653,8 +671,6 @@ function conversation($items, $mode, $update, $page_mode = 'traditional', $prepa else $profile_link = zid($profile_link); -// $normalised = normalise_link((strlen($item['author-link'])) ? $item['author-link'] : $item['url']); - $profile_name = $item['author']['xchan_name']; $profile_link = $item['author']['xchan_url']; $profile_avatar = $item['author']['xchan_photo_m']; @@ -705,7 +721,7 @@ function conversation($items, $mode, $update, $page_mode = 'traditional', $prepa $conv_link_mid = (($mode == 'moderate') ? $item['parent_mid'] : $item['mid']); - $conv_link = (($item['item_type'] == ITEM_TYPE_CARD) ? $item['plink'] : z_root() . '/display/' . gen_link_id($conv_link_mid)); + $conv_link = ((in_array($item['item_type'],[ ITEM_TYPE_CARD, ITEM_TYPE_ARTICLE] )) ? $item['plink'] : z_root() . '/display/' . gen_link_id($conv_link_mid)); $tmp_item = array( @@ -739,8 +755,8 @@ function conversation($items, $mode, $update, $page_mode = 'traditional', $prepa 'forged' => $forged, 'txt_cats' => t('Categories:'), 'txt_folders' => t('Filed under:'), - 'has_cats' => ((count($body['categories'])) ? 'true' : ''), - 'has_folders' => ((count($body['folders'])) ? 'true' : ''), + 'has_cats' => (($body['categories']) ? 'true' : ''), + 'has_folders' => (($body['folders']) ? 'true' : ''), 'text' => strip_tags($body['html']), 'ago' => relative_date($item['created']), 'app' => $item['app'], @@ -750,6 +766,7 @@ function conversation($items, $mode, $update, $page_mode = 'traditional', $prepa 'editedtime' => (($item['edited'] != $item['created']) ? sprintf( t('last edited: %s'), datetime_convert('UTC', date_default_timezone_get(), $item['edited'], 'r')) : ''), 'expiretime' => (($item['expires'] > NULL_DATE) ? sprintf( t('Expires: %s'), datetime_convert('UTC', date_default_timezone_get(), $item['expires'], 'r')):''), 'location' => $location, + 'divider' => false, 'indent' => '', 'owner_name' => $owner_name, 'owner_url' => $owner_url, @@ -1456,7 +1473,7 @@ function sort_item_children($items) { $result = $items; usort($result,'sort_thr_created_rev'); foreach($result as $k => $i) { - if(count($result[$k]['children'])) { + if($result[$k]['children']) { $result[$k]['children'] = sort_item_children($result[$k]['children']); } } @@ -1466,7 +1483,7 @@ function sort_item_children($items) { function add_children_to_list($children, &$arr) { foreach($children as $y) { $arr[] = $y; - if(count($y['children'])) + if($y['children']) add_children_to_list($y['children'], $arr); } } @@ -1878,6 +1895,17 @@ function profile_tabs($a, $is_owner = false, $nickname = null){ 'icon' => 'list' ); } + + if(feature_enabled($uid,'articles')) { + $tabs[] = array( + 'label' => t('articles'), + 'url' => z_root() . '/articles/' . $nickname, + 'sel' => ((argv(0) == 'articles') ? 'active' : ''), + 'title' => t('View Articles'), + 'id' => 'articles-tab', + 'icon' => 'file-text-o' + ); + } if($has_webpages && feature_enabled($uid,'webpages')) { $tabs[] = array( diff --git a/include/datetime.php b/include/datetime.php index 85e87848b..0fcd957be 100644 --- a/include/datetime.php +++ b/include/datetime.php @@ -8,6 +8,8 @@ /** * @brief Two-level sort for timezones. * + * Can be used in usort() to sort timezones. + * * @param string $a * @param string $b * @return number @@ -27,13 +29,14 @@ function timezone_cmp($a, $b) { function is_null_date($s) { if($s === '0000-00-00 00:00:00' || $s === '0001-01-01 00:00:00') return true; + return false; } - /** * @brief Return timezones grouped (primarily) by continent. * + * @see timezone_cmp() * @return array */ function get_timezones( ){ @@ -54,7 +57,7 @@ function get_timezones( ){ $city = $ex[0]; $continent = t('Miscellaneous'); } - $city = str_replace('_', ' ', t($city)); + $city = str_replace('_', ' ', t($city)); if (!x($continents, $ex[0])) $continents[$ex[0]] = array(); $continents[$continent][$value] = $city; @@ -109,7 +112,7 @@ function datetime_convert($from = 'UTC', $to = 'UTC', $s = 'now', $fmt = "Y-m-d try { $d = new DateTime($s, $from_obj); } catch(Exception $e) { - logger('datetime_convert: exception: ' . $e->getMessage()); + logger('exception: ' . $e->getMessage()); $d = new DateTime('now', $from_obj); } @@ -128,7 +131,7 @@ function datetime_convert($from = 'UTC', $to = 'UTC', $s = 'now', $fmt = "Y-m-d * @brief Wrapper for date selector, tailored for use in birthday fields. * * @param string $dob Date of Birth - * @return string + * @return string Parsed HTML with selector */ function dob($dob) { @@ -161,59 +164,62 @@ function dob($dob) { } /** - * returns a date selector - * @param $format - * format string, e.g. 'ymd' or 'mdy'. Not currently supported - * @param $min - * unix timestamp of minimum date - * @param $max - * unix timestap of maximum date - * @param $default - * unix timestamp of default date - * @param $id - * id and name of datetimepicker (defaults to "datetimepicker") + * @brief Returns a date selector. + * + * @see datetimesel() + * @param string $format + * format string, e.g. 'ymd' or 'mdy'. Not currently supported + * @param DateTime $min + * unix timestamp of minimum date + * @param DateTime $max + * unix timestap of maximum date + * @param DateTime $default + * unix timestamp of default date + * @param string $id + * id and name of datetimepicker (defaults to "datetimepicker") */ function datesel($format, $min, $max, $default, $id = 'datepicker') { - return datetimesel($format, $min, $max, $default, '', $id,true, false, '', ''); + return datetimesel($format, $min, $max, $default, '', $id, true, false, '', ''); } /** - * returns a time selector - * @param $format - * format string, e.g. 'ymd' or 'mdy'. Not currently supported - * @param $h - * already selected hour - * @param $m + * @brief Returns a time selector. + * + * @param string $format + * format string, e.g. 'ymd' or 'mdy'. Not currently supported + * @param string $h + * already selected hour + * @param string $m * already selected minute - * @param $id + * @param string $id * id and name of datetimepicker (defaults to "timepicker") */ function timesel($format, $h, $m, $id='timepicker') { - return datetimesel($format,new DateTime(),new DateTime(),new DateTime("$h:$m"),'', $id,false,true); + return datetimesel($format, new DateTime(), new DateTime(), new DateTime("$h:$m"), '', $id, false, true); } /** * @brief Returns a datetime selector. * * @param string $format - * format string, e.g. 'ymd' or 'mdy'. Not currently supported - * @param $min - * unix timestamp of minimum date - * @param $max - * unix timestap of maximum date - * @param $default - * unix timestamp of default date + * format string, e.g. 'ymd' or 'mdy'. Not currently supported + * @param DateTime $min + * unix timestamp of minimum date + * @param DateTime $max + * unix timestap of maximum date + * @param DateTime $default + * unix timestamp of default date * @param string $label * @param string $id - * id and name of datetimepicker (defaults to "datetimepicker") + * id and name of datetimepicker (defaults to "datetimepicker") * @param boolean $pickdate - * true to show date picker (default) + * true to show date picker (default) * @param boolean $picktime - * true to show time picker (default) - * @param $minfrom - * set minimum date from picker with id $minfrom (none by default) - * @param $maxfrom - * set maximum date from picker with id $maxfrom (none by default) + * true to show time picker (default) + * @param DateTime $minfrom + * set minimum date from picker with id $minfrom (none by default) + * @param DateTime $maxfrom + * set maximum date from picker with id $maxfrom (none by default) * @param boolean $required default false * @param int $first_day (optional) default 0 * @return string Parsed HTML output. @@ -343,9 +349,6 @@ function plural_dates($k,$n) { } } - - - /** * @brief Returns timezone correct age in years. * @@ -418,7 +421,7 @@ function get_dim($y, $m) { * * @param int $y Year * @param int $m Month (1=January, 12=December) - * @return day 0 = Sunday through 6 = Saturday + * @return string day 0 = Sunday through 6 = Saturday */ function get_first_dim($y, $m) { $d = sprintf('%04d-%02d-01 00:00', intval($y), intval($m)); diff --git a/include/event.php b/include/event.php index b56a5fb3e..282c1a9d7 100644 --- a/include/event.php +++ b/include/event.php @@ -1,15 +1,16 @@ <?php -use Sabre\VObject; - /** * @file include/event.php + * @brief Event related functions. */ +use Sabre\VObject; + /** - * @brief Returns an event as HTML + * @brief Returns an event as HTML. * * @param array $ev - * @return string + * @return string HTML formatted event */ function format_event_html($ev) { @@ -21,7 +22,7 @@ function format_event_html($ev) { $bd_format = t('l F d, Y \@ g:i A') ; // Friday January 18, 2011 @ 8:01 AM - //todo: move this to template + /// @TODO move this to template $o = '<div class="vevent">' . "\r\n"; @@ -29,27 +30,27 @@ 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('UTC', 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', + : day_translate(datetime_convert('UTC', 'UTC', $ev['dtstart'] , $bd_format))) . '</span></div>' . "\r\n"; if(! $ev['nofinish']) $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('UTC', 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', + : day_translate(datetime_convert('UTC', 'UTC', $ev['dtend'] , $bd_format ))) . '</span></div>' . "\r\n"; $o .= '<div class="event-description">' . zidify_links(smilies(bbcode($ev['description']))) . '</div>' . "\r\n"; if(strlen($ev['location'])) - $o .= '<div class="event-location"><span class="event-label"> ' . t('Location:') . '</span> <span class="location">' + $o .= '<div class="event-location"><span class="event-label"> ' . t('Location:') . '</span> <span class="location">' . zidify_links(smilies(bbcode($ev['location']))) . '</span></div>' . "\r\n"; @@ -123,9 +124,9 @@ function format_event_ical($ev) { $o .= "\r\nCREATED:" . datetime_convert('UTC','UTC', $ev['created'],'Ymd\\THis\\Z'); $o .= "\r\nLAST-MODIFIED:" . datetime_convert('UTC','UTC', $ev['edited'],'Ymd\\THis\\Z'); $o .= "\r\nDTSTAMP:" . datetime_convert('UTC','UTC', $ev['edited'],'Ymd\\THis\\Z'); - if($ev['dtstart']) + if($ev['dtstart']) $o .= "\r\nDTSTART:" . datetime_convert('UTC','UTC', $ev['dtstart'],'Ymd\\THis' . (($ev['adjust']) ? '\\Z' : '')); - if($ev['dtend'] && ! $ev['nofinish']) + if($ev['dtend'] && ! $ev['nofinish']) $o .= "\r\nDTEND:" . datetime_convert('UTC','UTC', $ev['dtend'],'Ymd\\THis' . (($ev['adjust']) ? '\\Z' : '')); if($ev['summary']) { $o .= "\r\nSUMMARY:" . format_ical_text($ev['summary']); @@ -143,7 +144,7 @@ function format_event_ical($ev) { $o .= "\r\nPRIORITY:" . intval($ev['event_priority']); $o .= "\r\nUID:" . $ev['event_hash'] ; $o .= "\r\nEND:VEVENT\r\n"; - + return $o; } @@ -156,9 +157,9 @@ function format_todo_ical($ev) { $o .= "\r\nCREATED:" . datetime_convert('UTC','UTC', $ev['created'],'Ymd\\THis\\Z'); $o .= "\r\nLAST-MODIFIED:" . datetime_convert('UTC','UTC', $ev['edited'],'Ymd\\THis\\Z'); $o .= "\r\nDTSTAMP:" . datetime_convert('UTC','UTC', $ev['edited'],'Ymd\\THis\\Z'); - if($ev['dtstart']) + if($ev['dtstart']) $o .= "\r\nDTSTART:" . datetime_convert('UTC','UTC', $ev['dtstart'],'Ymd\\THis' . (($ev['adjust']) ? '\\Z' : '')); - if($ev['dtend'] && ! $ev['nofinish']) + if($ev['dtend'] && ! $ev['nofinish']) $o .= "\r\nDUE:" . datetime_convert('UTC','UTC', $ev['dtend'],'Ymd\\THis' . (($ev['adjust']) ? '\\Z' : '')); if($ev['summary']) { $o .= "\r\nSUMMARY:" . format_ical_text($ev['summary']); @@ -170,8 +171,8 @@ function format_todo_ical($ev) { $o .= "\r\nCOMPLETED:" . datetime_convert('UTC','UTC', $ev['event_status_date'],'Ymd\\THis\\Z'); } if(intval($ev['event_percent'])) - $o .= "\r\nPERCENT-COMPLETE:" . $ev['event_percent']; - if(intval($ev['event_sequence'])) + $o .= "\r\nPERCENT-COMPLETE:" . $ev['event_percent']; + if(intval($ev['event_sequence'])) $o .= "\r\nSEQUENCE:" . $ev['event_sequence']; if($ev['location']) { $o .= "\r\nLOCATION:" . format_ical_text($ev['location']); @@ -196,12 +197,13 @@ function format_ical_text($s) { $s = html2plain(bbcode($s)); $s = str_replace(["\r\n","\n"],["",""],$s); - return(wordwrap(str_replace(['\\',',',';'],['\\\\','\\,','\\;'],$s),72,"\r\n ",true)); + return(wordwrap(str_replace(['\\',',',';'],['\\\\','\\,','\\;'],$s),72,"\r\n ",true)); } function format_ical_sourcetext($s) { $s = base64_encode($s); + return(wordwrap(str_replace(['\\',',',';'],['\\\\','\\,','\\;'],$s),72,"\r\n ",true)); } @@ -225,7 +227,7 @@ function format_event_bbcode($ev) { if(($ev['dtend']) && (! $ev['nofinish'])) $o .= '[event-finish]' . $ev['dtend'] . '[/event-finish]'; - + if($ev['location']) $o .= '[event-location]' . $ev['location'] . '[/event-location]'; @@ -253,7 +255,6 @@ function bbtoevent($s) { $ev = array(); - $match = ''; if(preg_match("/\[event\](.*?)\[\/event\]/is",$s,$match)) { // only parse one object per event tag @@ -306,7 +307,7 @@ function bbtoevent($s) { * * @see ev_compare() * @param array $arr - * @return sorted array + * @return array Date sorted array of events */ function sort_by_date($arr) { if (is_array($arr)) @@ -318,6 +319,8 @@ function sort_by_date($arr) { /** * @brief Compare function for events. * + * This function can be used in usort() to sort events by date. + * * @see sort_by_date() * @param array $a * @param array $b @@ -375,8 +378,19 @@ function event_store_event($arr) { } } - $hook_info = [ 'event' => $arr, 'existing_event' => $existing_event, 'cancel' => false ]; - call_hooks('event_store_event',$hook_info); + $hook_info = [ + 'event' => $arr, + 'existing_event' => $existing_event, + 'cancel' => false + ]; + /** + * @hooks event_store_event + * Called when an event record is created or updated. + * * \e array \b event + * * \e array \b existing_event + * * \e boolean \b cancel - default false + */ + call_hooks('event_store_event', $hook_info); if($hook_info['cancel']) return false; @@ -386,7 +400,7 @@ function event_store_event($arr) { if($existing_event) { if($existing_event['edited'] >= $arr['edited']) { - // Nothing has changed. + // Nothing has changed. return $existing_event; } @@ -444,7 +458,6 @@ function event_store_event($arr) { // New event. Store it. - if(array_key_exists('external_id',$arr)) $hash = $arr['external_id']; elseif(array_key_exists('event_hash',$arr)) @@ -531,7 +544,7 @@ function event_addtocal($item_id, $uid) { } if($ev->private) - $ev['allow_cid'] = '<' . $channel['channel_hash'] . '>'; + $ev['allow_cid'] = '<' . $channel['channel_hash'] . '>'; else { $acl = new Zotlabs\Access\AccessList($channel); $x = $acl->get(); @@ -596,14 +609,12 @@ function ical_to_ev($s) { date_default_timezone_set($saved_timezone); return $ev; - } function parse_vobject($ical, $type) { - $ev = []; if(! isset($ical->DTSTART)) { @@ -698,10 +709,8 @@ function parse_vobject($ical, $type) { - - function parse_ical_file($f,$uid) { -require_once('vendor/autoload.php'); + require_once('vendor/autoload.php'); $s = @file_get_contents($f); @@ -731,6 +740,7 @@ require_once('vendor/autoload.php'); if($ical) return true; + return false; } @@ -779,7 +789,6 @@ function event_import_ical($ical, $uid) { $ev['created'] = datetime_convert('UTC','UTC',$created->format(\DateTime::W3C)); } - if(isset($ical->{'LAST-MODIFIED'})) { $edited = $ical->{'LAST-MODIFIED'}->getDateTime(); $ev['edited'] = datetime_convert('UTC','UTC',$edited->format(\DateTime::W3C)); @@ -814,7 +823,7 @@ function event_import_ical($ical, $uid) { else $ev['external_id'] = $evuid; } - + if($ev['summary'] && $ev['dtstart']) { $ev['event_xchan'] = $channel['channel_hash']; $ev['uid'] = $channel['channel_id']; @@ -822,7 +831,7 @@ function event_import_ical($ical, $uid) { $ev['private'] = 1; $ev['allow_cid'] = '<' . $channel['channel_hash'] . '>'; - logger('storing event: ' . print_r($ev,true), LOGGER_ALL); + logger('storing event: ' . print_r($ev,true), LOGGER_ALL); $event = event_store_event($ev); if($event) { $item_id = event_store_item($ev,$event); @@ -831,7 +840,6 @@ function event_import_ical($ical, $uid) { } return false; - } function event_ical_get_sourcetext($s) { @@ -931,7 +939,7 @@ function event_import_ical_task($ical, $uid) { if(isset($ical->SEQUENCE)) { $ev['event_sequence'] = (string) $ical->SEQUENCE; // see if our stored event is more current than the one we're importing - if((intval($ev['event_sequence']) <= intval($stored_event['event_sequence'])) + if((intval($ev['event_sequence']) <= intval($stored_event['event_sequence'])) && ($ev['edited'] <= $stored_event['edited'])) return false; } @@ -958,7 +966,7 @@ function event_import_ical_task($ical, $uid) { $ev['private'] = 1; $ev['allow_cid'] = '<' . $channel['channel_hash'] . '>'; - logger('storing event: ' . print_r($ev,true), LOGGER_ALL); + logger('storing event: ' . print_r($ev,true), LOGGER_ALL); $event = event_store_event($ev); if($event) { $item_id = event_store_item($ev,$event); @@ -967,14 +975,10 @@ function event_import_ical_task($ical, $uid) { } return false; - } - - - function event_store_item($arr, $event) { require_once('include/datetime.php'); @@ -995,7 +999,6 @@ function event_store_item($arr, $event) { } - $item_arr = array(); $prefix = ''; // $birthday = false; @@ -1008,9 +1011,9 @@ function event_store_item($arr, $event) { $prefix = t('This event has been added to your calendar.'); // $birthday = true; - // The event is created on your own site by the system, but appears to belong + // The event is created on your own site by the system, but appears to belong // to the birthday person. It also isn't propagated - so we need to prevent - // folks from trying to comment on it. If you're looking at this and trying to + // folks from trying to comment on it. If you're looking at this and trying to // fix it, you'll need to completely change the way birthday events are created // and send them out from the source. This has its own issues. @@ -1048,9 +1051,10 @@ function event_store_item($arr, $event) { $private = (($arr['allow_cid'] || $arr['allow_gid'] || $arr['deny_cid'] || $arr['deny_gid']) ? 1 : 0); - // @FIXME can only update sig if we have the author's channel on this site - // Until fixed, set it to nothing so it won't give us signature errors - + /** + * @FIXME can only update sig if we have the author's channel on this site + * Until fixed, set it to nothing so it won't give us signature errors. + */ $sig = ''; q("UPDATE item SET title = '%s', body = '%s', obj = '%s', allow_cid = '%s', allow_gid = '%s', deny_cid = '%s', deny_gid = '%s', edited = '%s', sig = '%s', item_flags = %d, item_private = %d, obj_type = '%s' WHERE id = %d AND uid = %d", @@ -1090,6 +1094,10 @@ function event_store_item($arr, $event) { } $item_id = $r[0]['id']; + /** + * @hooks event_updated + * Called when an event record is modified. + */ call_hooks('event_updated', $event['id']); return $item_id; @@ -1103,7 +1111,7 @@ function event_store_item($arr, $event) { $item_wall = 0; $item_origin = 0; - $item_thread_top = 0; + $item_thread_top = 0; if($item) { $item_arr['id'] = $item['id']; @@ -1196,6 +1204,10 @@ function event_store_item($arr, $event) { $item_id = $res['item_id']; + /** + * @hooks event_created + * Called when an event record is created. + */ call_hooks('event_created', $event['id']); return $item_id; @@ -1216,25 +1228,24 @@ function todo_stat() { function tasks_fetch($arr) { - if(! local_channel()) - return; + if(! local_channel()) + return; - $ret = array(); - $sql_extra = " and event_status != 'COMPLETED' "; - if($arr && $arr['all'] == 1) - $sql_extra = ''; + $ret = array(); + $sql_extra = " and event_status != 'COMPLETED' "; + if($arr && $arr['all'] == 1) + $sql_extra = ''; - $r = q("select * from event where etype = 'task' and uid = %d $sql_extra order by created desc", - intval(local_channel()) - ); + $r = q("select * from event where etype = 'task' and uid = %d $sql_extra order by created desc", + intval(local_channel()) + ); - $ret['success'] = (($r) ? true : false); - if($r) { - $ret['tasks'] = $r; - } + $ret['success'] = (($r) ? true : false); + if($r) { + $ret['tasks'] = $r; + } return $ret; - } function cdav_principal($uri) { diff --git a/include/features.php b/include/features.php index f84c9cb05..8601ff79e 100644 --- a/include/features.php +++ b/include/features.php @@ -126,6 +126,16 @@ function get_features($filtered = true) { feature_level('cards',1), ], +/* reserved, work in progress + [ + 'articles', + t('Articles'), + t('Create interactive articles'), + false, + get_config('feature_lock','articles'), + feature_level('articles',1), + ], +*/ [ 'nav_channel_select', t('Navigation Channel Select'), @@ -364,6 +374,15 @@ function get_features($filtered = true) { t('Post/Comment Tools'), [ + 'markdown', + t('Markdown'), + t('Use markdown for editing posts'), + false, + get_config('feature_lock','markdown'), + feature_level('markdown',2), + ], + + [ 'commtag', t('Community Tagging'), t('Ability to tag existing posts'), diff --git a/include/feedutils.php b/include/feedutils.php index 217da8188..4638ef66a 100644 --- a/include/feedutils.php +++ b/include/feedutils.php @@ -1,5 +1,8 @@ <?php - +/** + * @file include/feedutils.php + * @brief Some functions to work with XML feeds. + */ /** * @brief Return an Atom feed for channel. @@ -57,21 +60,39 @@ function get_public_feed($channel, $params) { * @param array $params * @return string with an atom feed */ - function get_feed_for($channel, $observer_hash, $params) { if(! $channel) http_status_exit(401); + + // logger('params: ' . print_r($params,true)); + + + $interactive = ((is_array($params) && array_key_exists('interactive',$params)) ? intval($params['interactive']) : 0); + + if($params['pages']) { - if(! perm_is_allowed($channel['channel_id'],$observer_hash,'view_pages')) - http_status_exit(403); - } else { - if(! perm_is_allowed($channel['channel_id'],$observer_hash,'view_stream')) - http_status_exit(403); + if(! perm_is_allowed($channel['channel_id'],$observer_hash,'view_pages')) { + if($interactive) { + return ''; + } + else { + http_status_exit(403); + } + } + } + else { + if(! perm_is_allowed($channel['channel_id'],$observer_hash,'view_stream')) { + if($interactive) { + return ''; + } + else { + http_status_exit(403); + } + } } - // logger('params: ' . print_r($params,true)); $feed_template = get_markup_template('atom_feed.tpl'); @@ -105,12 +126,28 @@ function get_feed_for($channel, $observer_hash, $params) { )); - $x = [ 'xml' => $atom, 'channel' => $channel, 'observer_hash' => $observer_hash, 'params' => $params ]; - call_hooks('atom_feed_top',$x); + $x = [ + 'xml' => $atom, + 'channel' => $channel, + 'observer_hash' => $observer_hash, + 'params' => $params + ]; + /** + * @hooks atom_feed_top + * * \e string \b xml - the generated feed and what will get returned from the hook + * * \e array \b channel + * * \e string \b observer_hash + * * \e array \b params + */ + call_hooks('atom_feed_top', $x); $atom = $x['xml']; - // a much simpler interface + /** + * @hooks atom_feed + * A much simpler interface than atom_feed_top. + * * \e string - the feed after atom_feed_top hook + */ call_hooks('atom_feed', $atom); $items = items_fetch( @@ -135,11 +172,14 @@ function get_feed_for($channel, $observer_hash, $params) { if($item['item_private']) continue; - /** @BUG $owner is undefined in this call */ $atom .= atom_entry($item, $type, null, $owner, true, '', $params['compat']); } } + /** + * @hooks atom_feed_end + * \e string - The created XML feed as a string without closing tag + */ call_hooks('atom_feed_end', $atom); $atom .= '</feed>' . "\r\n"; @@ -344,6 +384,7 @@ function get_atom_elements($feed, $item, &$author) { } } + // check for a yahoo media element (github etc.) if(! $author['author_photo']) { @@ -356,7 +397,6 @@ function get_atom_elements($feed, $item, &$author) { // No photo/profile-link on the item - look at the feed level - if((! (x($author,'author_link'))) || (! (x($author,'author_photo')))) { $rawauthor = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author'); if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) { @@ -396,7 +436,7 @@ function get_atom_elements($feed, $item, &$author) { // new style $ostatus_conversation = normalise_id(unxmlify($rawcnv[0]['attribs']['']['ref'])); if(! $ostatus_conversation) { - // old style + // old style $ostatus_conversation = normalise_id(unxmlify($rawcnv[0]['data'])); } if($ostatus_conversation) { @@ -406,7 +446,7 @@ function get_atom_elements($feed, $item, &$author) { } $ostatus_protocol = (($ostatus_conversation) ? true : false); - + $mastodon = (($item->get_item_tags('http://mastodon.social/schema/1.0','scope')) ? true : false); if($mastodon) { $ostatus_protocol = true; @@ -645,7 +685,7 @@ function get_atom_elements($feed, $item, &$author) { $res['attach'][] = array('href' => $link, 'length' => $len, 'type' => $type, 'title' => $title ); } } - + $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'object'); @@ -723,7 +763,6 @@ function get_atom_elements($feed, $item, &$author) { $res['target'] = $obj; } - if(array_key_exists('verb',$res) && $res['verb'] === ACTIVITY_SHARE && array_key_exists('obj_type',$res) && in_array($res['obj_type'], [ ACTIVITY_OBJ_NOTE, ACTIVITY_OBJ_COMMENT, ACTIVITY_OBJ_ACTIVITY ] )) { @@ -737,7 +776,13 @@ function get_atom_elements($feed, $item, &$author) { 'author' => $author, 'result' => $res ]; - + /** + * @hooks parse_atom + * * \e SimplePie \b feed - The original SimplePie feed + * * \e array \b item + * * \e array \b author + * * \e array \b result - the result array that will also get returned + */ call_hooks('parse_atom', $arr); logger('author: ' .print_r($arr['author'], true), LOGGER_DATA); @@ -782,7 +827,7 @@ function feed_get_reshare(&$res,$item) { $share['avatar'] = z_root() . '/' . get_default_profile_photo(80); if(! $share['profile']) $share['profile'] = z_root(); - + $child = $rawobj[0]['child']; @@ -809,7 +854,7 @@ function feed_get_reshare(&$res,$item) { $body = html2bbcode($body); } } - + $attach = $share['links']; if($attach) { @@ -845,16 +890,16 @@ function feed_get_reshare(&$res,$item) { } } } - + if((! $body) && ($share['alternate'])) { $body = $share['alternate']; - } + } - $res['body'] = "[share author='" . urlencode($share['author']) . + $res['body'] = "[share author='" . urlencode($share['author']) . "' profile='" . $share['profile'] . "' avatar='" . $share['avatar'] . "' link='" . $share['alternate'] . - "' posted='" . $share['created'] . + "' posted='" . $share['created'] . "' message_id='" . $share['message_id'] . "']"; $res['body'] .= $body; @@ -864,7 +909,6 @@ function feed_get_reshare(&$res,$item) { } - /** * @brief Encodes SimplePie_Item link arrays. * @@ -940,7 +984,7 @@ function process_feed_tombstones($feed,$importer,$contact,$pass) { * * @param string $xml * The (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds. - * @param $importer + * @param array $importer * The contact_record (joined to user_record) of the local user who owns this * relationship. It is this person's stuff that is going to be updated. * @param[in,out] array $contact @@ -1103,7 +1147,7 @@ function consume_feed($xml, $importer, &$contact, $pass = 0) { // Update content if 'updated' changes if($r) { - if(activity_match($datarray['verb'],ACTIVITY_DELETE) + if(activity_match($datarray['verb'],ACTIVITY_DELETE) && $datarray['author_xchan'] === $r[0]['author_xchan']) { if(! intval($r[0]['item_deleted'])) { logger('deleting item ' . $r[0]['id'] . ' mid=' . $datarray['mid'], LOGGER_DEBUG); @@ -1147,6 +1191,7 @@ function consume_feed($xml, $importer, &$contact, $pass = 0) { $datarray['parent_mid'] = $pmid; } } + if(($item_parent_mid) && (! $pmid)) { logger('find_parent: matched in-reply-to: ' . $parent_mid, LOGGER_DEBUG); $pmid = $item_parent_mid[0]['parent_mid']; @@ -1172,7 +1217,7 @@ function consume_feed($xml, $importer, &$contact, $pass = 0) { dbesc($parent_mid), intval($importer['channel_id']) ); - + if($x) { $item_parent_mid = $x; $pmid = $x[0]['parent_mid']; @@ -1205,7 +1250,7 @@ function consume_feed($xml, $importer, &$contact, $pass = 0) { ); if($r) { $parent_item = $r[0]; - if(intval($parent_item['item_nocomment']) || $parent_item['comment_policy'] === 'none' + if(intval($parent_item['item_nocomment']) || $parent_item['comment_policy'] === 'none' || ($parent_item['comments_closed'] > NULL_DATE && $parent_item['comments_closed'] < datetime_convert())) { logger('comments disabled for post ' . $parent_item['mid']); continue; @@ -1215,7 +1260,7 @@ function consume_feed($xml, $importer, &$contact, $pass = 0) { $allowed = false; if($parent_item) { - if($parent_item['owner_xchan'] == $importer['channel_hash']) + if($parent_item['owner_xchan'] == $importer['channel_hash']) $allowed = perm_is_allowed($importer['channel_id'],$contact['xchan_hash'],'post_comments'); else $allowed = true; @@ -1230,16 +1275,17 @@ function consume_feed($xml, $importer, &$contact, $pass = 0) { // interactive feeds (such as OStatus) downstream to our followers // We do not want to set it for non-interactive feeds or conversations we do not own - if(array_key_exists('send_downstream',$importer) && intval($importer['send_downstream']) + if(array_key_exists('send_downstream',$importer) && intval($importer['send_downstream']) && ($parent_item['owner_xchan'] == $importer['channel_hash'])) { $send_downstream = true; } } else { if((! perm_is_allowed($importer['channel_id'],$contact['xchan_hash'],'send_stream')) && (! $importer['system'])) { - // @fixme check for and process ostatus autofriend - // otherwise - + /** + * @fixme check for and process ostatus autofriend + * otherwise ignore this author. + */ logger('Ignoring this author.'); continue; } @@ -1249,17 +1295,17 @@ function consume_feed($xml, $importer, &$contact, $pass = 0) { // immediate parent wasn't found. Turn into a top-level post if permissions allow // but save the thread_parent in case we need to refer to it later. - + if(! post_is_importable($datarray, $contact)) continue; + $datarray['parent_mid'] = $datarray['mid']; set_iconfig($datarray,'system','parent_mid',$parent_mid,true); } - // allow likes of comments - if($item_parent_mid && activity_match($datarray['verb'],ACTVITY_LIKE)) { + if($item_parent_mid && activity_match($datarray['verb'],ACTIVITY_LIKE)) { $datarray['thr_parent'] = $item_parent_mid[0]['parent_mid']; } @@ -1306,7 +1352,6 @@ function consume_feed($xml, $importer, &$contact, $pass = 0) { } - // if we have everything but a photo, provide the default profile photo if($author['author_name'] && $author['author_link'] && (! $author['author_photo'])) @@ -1364,7 +1409,7 @@ function consume_feed($xml, $importer, &$contact, $pass = 0) { // Update content if 'updated' changes if($r) { - if(activity_match($datarray['verb'],ACTIVITY_DELETE) + if(activity_match($datarray['verb'],ACTIVITY_DELETE) && $datarray['author_xchan'] === $r[0]['author_xchan']) { if(! intval($r[0]['item_deleted'])) { logger('deleting item ' . $r[0]['id'] . ' mid=' . $datarray['mid'], LOGGER_DEBUG); @@ -1426,12 +1471,12 @@ function feed_conversation_fetch($importer,$contact,$parent_link) { // GNU-Social flavoured feeds if(strpos($parent_link,'/notice/')) { $link = str_replace('/notice/','/api/statuses/show/',$parent_link) . '.atom'; - } + } // Mastodon flavoured feeds if(strpos($parent_link,'/users/') && strpos($parent_link,'/updates/')) { $link = $parent_link . '.atom'; - } + } if(! $link) return false; @@ -1446,21 +1491,21 @@ function feed_conversation_fetch($importer,$contact,$parent_link) { $data = $fetch['body']; // We will probably receive an atom 'entry' and not an atom 'feed'. Unfortunately - // our parser is a bit strict about compliance so we'll insert just enough of a feed - // tag to trick it into believing it's a compliant feed. + // our parser is a bit strict about compliance so we'll insert just enough of a feed + // tag to trick it into believing it's a compliant feed. if(! strstr($data,'<feed')) { - $data = str_replace('<entry ','<feed xmlns="http://www.w3.org/2005/Atom"><entry ',$data); + $data = str_replace('<entry ','<feed xmlns="http://www.w3.org/2005/Atom"><entry ',$data); $data .= '</feed>'; - } - + } + consume_feed($data,$importer,$contact,1); consume_feed($data,$importer,$contact,2); return true; - } + /** * @brief Normalise an id. * @@ -1479,7 +1524,7 @@ function normalise_id($id) { * * @param string $xml * The (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds. - * @param $importer + * @param array $importer (unused) * The contact_record (joined to user_record) of the local user who owns this * relationship. It is this person's stuff that is going to be updated. */ @@ -1617,6 +1662,7 @@ function feed_meta($xml) { return $ret; } + /** * @brief Not yet implemented function to update feed item. * @@ -1627,6 +1673,7 @@ function update_feed_item($uid, $datarray) { item_store_update($datarray); } + /** * @brief Fetch the content of a feed and further consume it. * @@ -1659,11 +1706,10 @@ function handle_feed($uid, $abook_id, $url) { } } + /** * @brief Return a XML tag with author information. * - * @hooks \b atom_author Possibility to add further tags to returned XML string - * * \e string The created XML tag as a string without closing tag * @param string $tag The XML tag to create * @param string $nick preferred username * @param string $name displayed name of the author @@ -1672,7 +1718,7 @@ function handle_feed($uid, $abook_id, $url) { * @param int $w image width * @param string $type profile photo mime type * @param string $photo Fully qualified URL to a profile/avator photo - * @return string + * @return string XML tag */ function atom_author($tag, $nick, $name, $uri, $h, $w, $type, $photo) { $o = ''; @@ -1695,6 +1741,11 @@ function atom_author($tag, $nick, $name, $uri, $h, $w, $type, $photo) { $o .= ' <poco:preferredUsername>' . $nick . '</poco:preferredUsername>' . "\r\n"; $o .= ' <poco:displayName>' . $name . '</poco:displayName>' . "\r\n"; + /** + * @hooks atom_author + * Possibility to add further tags to returned XML string + * * \e string - The created XML tag as a string without closing tag + */ call_hooks('atom_author', $o); $o .= "</$tag>\r\n"; @@ -1703,17 +1754,23 @@ function atom_author($tag, $nick, $name, $uri, $h, $w, $type, $photo) { } -function atom_render_author($tag,$xchan) { +/** + * @brief Return an atom tag with author information from an xchan. + * + * @param string $tag + * @param array $xchan + * @return string + */ +function atom_render_author($tag, $xchan) { - - $nick = xmlify(substr($xchan['xchan_addr'],0,strpos($xchan['xchan_addr'],'@'))); + $nick = xmlify(substr($xchan['xchan_addr'], 0, strpos($xchan['xchan_addr'], '@'))); $id = xmlify($xchan['xchan_url']); $name = xmlify($xchan['xchan_name']); $photo = xmlify($xchan['xchan_photo_l']); $type = xmlify($xchan['xchan_photo_mimetype']); $w = $h = 300; - $o .= "<$tag>\r\n"; + $o = "<$tag>\r\n"; $o .= " <as:object-type>http://activitystrea.ms/schema/1.0/person</as:object-type>\r\n"; $o .= " <id>$id</id>\r\n"; $o .= " <name>$nick</name>\r\n"; @@ -1724,13 +1781,16 @@ function atom_render_author($tag,$xchan) { $o .= ' <poco:preferredUsername>' . $nick . '</poco:preferredUsername>' . "\r\n"; $o .= ' <poco:displayName>' . $name . '</poco:displayName>' . "\r\n"; + /** + * @hooks atom_render_author + * Possibility to add further tags to returned XML string. + * * \e string The created XML tag as a string without closing tag + */ call_hooks('atom_render_author', $o); $o .= "</$tag>\r\n"; return $o; - - } function compat_photos_list($s) { @@ -1740,7 +1800,7 @@ function compat_photos_list($s) { $found = preg_match_all('/\[[zi]mg(.*?)\](.*?)\[/ism',$s,$matches,PREG_SET_ORDER); if($found) { - foreach($matches as $match) { + foreach($matches as $match) { $ret[] = [ 'href' => $match[2], 'length' => 0, @@ -1754,7 +1814,6 @@ function compat_photos_list($s) { } - /** * @brief Create an item for the Atom feed. * @@ -1766,11 +1825,11 @@ function compat_photos_list($s) { * @param array $owner * @param string $comment default false * @param number $cid default 0 + * @param boolean $compat default false * @return void|string */ function atom_entry($item, $type, $author, $owner, $comment = false, $cid = 0, $compat = false) { - if(! $item['parent']) return; @@ -1922,9 +1981,17 @@ function atom_entry($item, $type, $author, $owner, $comment = false, $cid = 0, $ 'abook_id' => $cid, 'entry' => $o ]; - + /** + * @hooks atom_entry + * * \e array \b item + * * \e string \b type + * * \e array \b author + * * \e array \b owner + * * \e string \b comment + * * \e number \b abook_id + * * \e string \b entry - The generated entry and what will get returned + */ call_hooks('atom_entry', $x); return $x['entry']; } - diff --git a/include/follow.php b/include/follow.php index 56d8294c5..0843802c5 100644 --- a/include/follow.php +++ b/include/follow.php @@ -226,6 +226,8 @@ function new_contact($uid,$url,$channel,$interactive = false, $confirm = false) } + $profile_assign = get_pconfig($uid,'system','profile_assign',''); + $r = q("select abook_id, abook_xchan, abook_pending, abook_instance from abook where abook_xchan = '%s' and abook_channel = %d limit 1", @@ -265,6 +267,7 @@ function new_contact($uid,$url,$channel,$interactive = false, $confirm = false) 'abook_channel' => intval($uid), 'abook_closeness' => intval($closeness), 'abook_xchan' => $xchan_hash, + 'abook_profile' => $profile_assign, 'abook_feed' => intval(($xchan['xchan_network'] === 'rss') ? 1 : 0), 'abook_created' => datetime_convert(), 'abook_updated' => datetime_convert(), diff --git a/include/group.php b/include/group.php index e0c20b536..03ebf7ee5 100644 --- a/include/group.php +++ b/include/group.php @@ -18,10 +18,6 @@ function group_add($uid,$name,$public = 0) { intval($r) ); if(($z) && $z[0]['deleted']) { - /*$r = q("UPDATE groups SET deleted = 0 WHERE uid = %d AND gname = '%s'", - intval($uid), - dbesc($name) - );*/ q('UPDATE groups SET deleted = 0 WHERE id = %d', intval($z[0]['id'])); notice( t('A deleted group with this name was revived. Existing item permissions <strong>may</strong> apply to this group and any future members. If this is not what you intended, please create another group with a different name.') . EOL); } @@ -81,11 +77,11 @@ function group_rmv($uid,$name) { $user_info['channel_default_group'] = ''; $change = true; } - if(strpos($user_info['channel_allow_gid'], '<' . $group_id . '>') !== false) { + if(strpos($user_info['channel_allow_gid'], '<' . $group_hash . '>') !== false) { $user_info['channel_allow_gid'] = str_replace('<' . $group_hash . '>', '', $user_info['channel_allow_gid']); $change = true; } - if(strpos($user_info['channel_deny_gid'], '<' . $group_id . '>') !== false) { + if(strpos($user_info['channel_deny_gid'], '<' . $group_hash . '>') !== false) { $user_info['channel_deny_gid'] = str_replace('<' . $group_hash . '>', '', $user_info['channel_deny_gid']); $change = true; } diff --git a/include/help.php b/include/help.php index 02c3cb8e4..0dc37e517 100644 --- a/include/help.php +++ b/include/help.php @@ -28,7 +28,7 @@ function get_help_content($tocpath = false) { } if($path) { - + $title = basename($path); if(! $tocpath) \App::$page['title'] = t('Help:') . ' ' . ucwords(str_replace('-',' ',notags($title))); @@ -38,10 +38,10 @@ function get_help_content($tocpath = false) { // available and so default back to the English TOC at /doc/toc.{html,bb,md} // TODO: This is incompatible with the hierarchical TOC construction // defined in /Zotlabs/Widget/Helpindex.php. - if($tocpath !== false && - load_doc_file('doc/' . $path . '.md') === '' && - load_doc_file('doc/' . $path . '.bb') === '' && - load_doc_file('doc/' . $path . '.html') === '' + if($tocpath !== false && + load_doc_file('doc/' . $path . '.md') === '' && + load_doc_file('doc/' . $path . '.bb') === '' && + load_doc_file('doc/' . $path . '.html') === '' ) { $path = $title; } @@ -120,22 +120,28 @@ function preg_callback_help_include($matches) { } +/** + * @brief + * + * @return boolean|array + */ function determine_help_language() { - require_once('Text/LanguageDetect.php'); $lang_detect = new Text_LanguageDetect(); // Set this mode to recognize language by the short code like "en", "ru", etc. $lang_detect->setNameMode(2); - // If the language was specified in the URL, override the language preference + // If the language was specified in the URL, override the language preference // of the browser. Default to English if both of these are absent. if($lang_detect->languageExists(argv(1))) { $lang = argv(1); $from_url = true; } else { $lang = \App::$language; - if(! isset($lang)) + if(! isset($lang)) $lang = 'en'; + $from_url = false; } + return array('language' => $lang, 'from_url' => $from_url); } @@ -145,14 +151,14 @@ function load_doc_file($s) { $x = determine_help_language(); $lang = $x['language']; $url_idx = ($x['from_url'] ? 1 : 0); - // The English translation is at the root of /doc/. Other languages are in + // The English translation is at the root of /doc/. Other languages are in // subfolders named by the language code such as "de", "es", etc. if($lang !== 'en') { - $path .= '/' . $lang; + $path .= '/' . $lang; } $b = basename($s); - + for($i=1+$url_idx; $i<argc()-1; $i++) { $path .= '/' . argv($i); } diff --git a/include/html2bbcode.php b/include/html2bbcode.php index d7fbd8660..f67231847 100644 --- a/include/html2bbcode.php +++ b/include/html2bbcode.php @@ -196,12 +196,12 @@ function html2bbcode($message) //node2bbcode($doc, 'tr', array(), "[tr]", "[/tr]"); //node2bbcode($doc, 'td', array(), "[td]", "[/td]"); - node2bbcode($doc, 'h1', array(), "\n\n[size=xx-large][b]", "[/b][/size]\n"); - node2bbcode($doc, 'h2', array(), "\n\n[size=x-large][b]", "[/b][/size]\n"); - node2bbcode($doc, 'h3', array(), "\n\n[size=large][b]", "[/b][/size]\n"); - node2bbcode($doc, 'h4', array(), "\n\n[size=medium][b]", "[/b][/size]\n"); - node2bbcode($doc, 'h5', array(), "\n\n[size=small][b]", "[/b][/size]\n"); - node2bbcode($doc, 'h6', array(), "\n\n[size=x-small][b]", "[/b][/size]\n"); + node2bbcode($doc, 'h1', array(), "\n\n[h1]", "[/h1]\n"); + node2bbcode($doc, 'h2', array(), "\n\n[h2]", "[/h2]\n"); + node2bbcode($doc, 'h3', array(), "\n\n[h3]", "[/h3]\n"); + node2bbcode($doc, 'h4', array(), "\n\n[h4]", "[/h4]\n"); + node2bbcode($doc, 'h5', array(), "\n\n[h5]", "[/h5]\n"); + node2bbcode($doc, 'h6', array(), "\n\n[h6]", "[/h6]\n"); node2bbcode($doc, 'a', array('href'=>'/(.+)/'), '[url=$1]', '[/url]'); diff --git a/include/import.php b/include/import.php index f32f655da..a6d738e52 100644 --- a/include/import.php +++ b/include/import.php @@ -163,13 +163,17 @@ function import_profiles($channel, $profiles) { convert_oldfields($profile,'work','employment'); /** - * @TODO we are going to reset all profile photos to the original - * somebody will have to fix this later and put all the applicable - * photos into the export. + * @TODO put all the applicable photos into the export. */ - $profile['photo'] = z_root() . '/photo/profile/l/' . $channel['channel_id']; - $profile['thumb'] = z_root() . '/photo/profile/m/' . $channel['channel_id']; + if((strpos($profile['thumb'],'/photo/profile/l/') !== false) || intval($profile['is_default'])) { + $profile['photo'] = z_root() . '/photo/profile/l/' . $channel['channel_id']; + $profile['thumb'] = z_root() . '/photo/profile/m/' . $channel['channel_id']; + } + else { + $profile['photo'] = z_root() . '/photo/' . basename($profile['photo']); + $profile['thumb'] = z_root() . '/photo/' . basename($profile['thumb']); + } create_table_from_array('profile', $profile); } @@ -1082,6 +1086,7 @@ function sync_files($channel, $files) { logger('sync_files duplicate check: attach_by_hash() returned ' . print_r($x,true), LOGGER_DEBUG); if($x['success']) { + $orig_attach = $x[0]; $attach_exists = true; $attach_id = $x[0]['id']; } @@ -1146,12 +1151,18 @@ function sync_files($channel, $files) { // If the hash ever contains any escapable chars this could cause // problems. Currently it does not. - /// @TODO implement os_path if(!isset($att['os_path'])) $att['os_path'] = ''; if($attach_exists) { logger('sync_files attach exists: ' . print_r($att,true), LOGGER_DEBUG); + + // process/sync a remote rename/move operation + + if($orig_attach['content'] !== $newfname) { + rename($orig_attach['content'],$newfname); + } + if(! dbesc_array($att)) continue; @@ -1200,7 +1211,14 @@ function sync_files($channel, $files) { continue; } $redirects = 0; - $x = z_post_url($fetch_url,$parr,$redirects,array('filep' => $fp)); + + + $headers = []; + $headers['Accept'] = 'application/x-zot+json' ; + $headers['Sigtoken'] = random_string(); + $headers = \Zotlabs\Web\HTTPSig::create_sig('',$headers,$channel['channel_prvkey'], 'acct:' . $channel['channel_address'] . '@' . \App::get_hostname(),false,true,'sha512'); + + $x = z_post_url($fetch_url,$parr,$redirects,[ 'filep' => $fp, 'headers' => $headers]); fclose($fp); if($x['success']) { @@ -1250,10 +1268,43 @@ function sync_files($channel, $files) { ); } - if($p['imgscale'] === 0 && $p['os_storage']) + if(intval($p['imgscale']) === 0 && $p['os_storage']) $p['content'] = $store_path; else - $p['content'] = base64_decode($p['content']); + $p['content'] = (($p['content'])? base64_decode($p['content']) : ''); + + if(intval($p['imgscale']) && (! $p['content'])) { + + $time = datetime_convert(); + + $parr = array('hash' => $channel['channel_hash'], + 'time' => $time, + 'resource' => $att['hash'], + 'revision' => 0, + 'signature' => base64url_encode(rsa_sign($channel['channel_hash'] . '.' . $time, $channel['channel_prvkey'])), + 'resolution' => $p['imgscale'] + ); + + $stored_image = $newfname . '-' . intval($p['imgscale']); + + $fp = fopen($stored_image,'w'); + if(! $fp) { + logger('failed to open storage file.',LOGGER_NORMAL,LOG_ERR); + continue; + } + $redirects = 0; + + + $headers = []; + $headers['Accept'] = 'application/x-zot+json' ; + $headers['Sigtoken'] = random_string(); + $headers = \Zotlabs\Web\HTTPSig::create_sig('',$headers,$channel['channel_prvkey'], 'acct:' . $channel['channel_address'] . '@' . \App::get_hostname(),false,true,'sha512'); + + $x = z_post_url($fetch_url,$parr,$redirects,[ 'filep' => $fp, 'headers' => $headers]); + fclose($fp); + $p['content'] = file_get_contents($stored_image); + unlink($stored_image); + } if(!isset($p['display_path'])) $p['display_path'] = ''; @@ -1282,6 +1333,9 @@ function sync_files($channel, $files) { } } } + + \Zotlabs\Daemon\Master::Summon([ 'Thumbnail' , $att['hash'] ]); + if($f['item']) { sync_items($channel,$f['item'], ['channel_address' => $original_channel,'url' => $oldbase] diff --git a/include/items.php b/include/items.php index dd8b394d3..d0b9cffc9 100755 --- a/include/items.php +++ b/include/items.php @@ -1,6 +1,7 @@ <?php /** * @file include/items.php + * @brief Items related functions. */ use Zotlabs\Lib as Zlib; @@ -173,19 +174,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.item_blocked = 0 and item.obj_type != '" . ACTIVITY_OBJ_FILE . "' "; } function item_normal_search() { return " and item.item_hidden = 0 and item.item_type in (0,3,6) 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.item_blocked = 0 and item.obj_type != '" . ACTIVITY_OBJ_FILE . "' "; } 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.item_blocked = 0 and item.obj_type != '" . ACTIVITY_OBJ_FILE . "' "; } @@ -201,14 +202,14 @@ 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'])) + || intval($item['item_blocked']) || ($item['obj_type'] == ACTIVITY_OBJ_FILE)) return false; return true; } /** - * @brief + * @brief Decide if current observer has sufficient privileges to comment on item. * * This function examines the comment_policy attached to an item and decides if the current observer has * sufficient privileges to comment. This will normally be called on a remote site where perm_is_allowed() @@ -224,10 +225,21 @@ function is_item_normal($item) { */ function can_comment_on_post($observer_xchan, $item) { -// logger('can_comment_on_post: comment_policy: ' . $item['comment_policy'], LOGGER_DEBUG); - - $x = [ 'observer_hash' => $observer_xchan, 'item' => $item, 'allowed' => 'unset' ]; - call_hooks('can_comment_on_post',$x); +// logger('Comment_policy: ' . $item['comment_policy'], LOGGER_DEBUG); + + $x = [ + 'observer_hash' => $observer_xchan, + 'item' => $item, + 'allowed' => 'unset' + ]; + /** + * @hooks can_comment_on_post + * Called when deciding whether or not to present a comment box for a post. + * * \e string \b observer_hash + * * \e array \b item + * * \e boolean \b allowed - return value + */ + call_hooks('can_comment_on_post', $x); if($x['allowed'] !== 'unset') return $x['allowed']; @@ -386,10 +398,14 @@ function post_activity_item($arr, $allow_code = false, $deliver = true) { $_REQUEST['api_source'] = 1; - call_hooks('post_local',$arr); + /** + * @hooks post_local + * Called when an item has been posted on this machine via mod/item.php (also via API). + */ + call_hooks('post_local', $arr); - if(x($arr,'cancel')) { - logger('post_activity_item: post cancelled by plugin.'); + if(x($arr, 'cancel')) { + logger('Post cancelled by plugin.'); return $ret; } @@ -400,6 +416,12 @@ function post_activity_item($arr, $allow_code = false, $deliver = true) { $ret['success'] = true; $ret['item_id'] = $post_id; $ret['activity'] = $post['item']; + + /** + * @hooks post_local_end + * Called after a local post operation has completed. + * * \e array - the item returned from item_store() + */ call_hooks('post_local_end', $ret['activity']); } @@ -407,8 +429,8 @@ function post_activity_item($arr, $allow_code = false, $deliver = true) { Zotlabs\Daemon\Master::Summon(array('Notifier','activity',$post_id)); } - $ret['success'] = true; + return $ret; } @@ -430,7 +452,6 @@ function validate_item_elements($message,$arr) { $result['success'] = true; return $result; - } @@ -469,7 +490,7 @@ function limit_body_size($body) { if( ($textlen + $img_start) > $maxlen ) { if($textlen < $maxlen) { - logger('limit_body_size: the limit happens before an embedded image', LOGGER_DEBUG); + logger('The limit happens before an embedded image', LOGGER_DEBUG); $new_body = $new_body . substr($orig_body, 0, $maxlen - $textlen); $textlen = $maxlen; } @@ -799,8 +820,17 @@ function get_item_elements($x,$allow_code = false) { function import_author_xchan($x) { - $arr = array('xchan' => $x, 'xchan_hash' => ''); - call_hooks('import_author_xchan',$arr); + $arr = [ + 'xchan' => $x, + 'xchan_hash' => '' + ]; + /** + * @hooks import_author_xchan + * Called when looking up an author of a post by xchan_hash to ensure they have an xchan record on our site. + * * \e array \b xchan + * * \e string \b xchan_hash - Thre returned value + */ + call_hooks('import_author_xchan', $arr); if($arr['xchan_hash']) return $arr['xchan_hash']; @@ -823,7 +853,6 @@ function import_author_xchan($x) { } return($y); - } /** @@ -835,7 +864,6 @@ function import_author_xchan($x) { * * \e string \b guid * @return boolean|string */ - function import_author_rss($x) { if(! $x['url']) return false; @@ -844,7 +872,7 @@ function import_author_rss($x) { dbesc($x['url']) ); if($r) { - logger('import_author_rss: in cache' , LOGGER_DEBUG); + logger('In cache' , LOGGER_DEBUG); return $r[0]['xchan_hash']; } $name = trim($x['name']); @@ -883,7 +911,15 @@ function import_author_rss($x) { function import_author_unknown($x) { - $arr = [ 'author' => $x, 'result' => false ]; + $arr = [ + 'author' => $x, + 'result' => false + ]; + /** + * @hooks import_author + * * \e array \b author + * * \e boolean|string \b result - Return value, default false + */ call_hooks('import_author', $arr); if($arr['result']) return $arr['result']; @@ -895,7 +931,7 @@ function import_author_unknown($x) { dbesc($x['url']) ); if($r) { - logger('import_author_unknown: in cache' , LOGGER_DEBUG); + logger('In cache' , LOGGER_DEBUG); return $r[0]['xchan_hash']; } @@ -1469,8 +1505,11 @@ function get_profile_elements($x) { } - - +/** + * @brief Signs an item body. + * + * @param[in,out] array $item + */ function item_sign(&$item) { if(array_key_exists('sig',$item) && $item['sig']) @@ -1483,14 +1522,13 @@ function item_sign(&$item) { if(! $r) return; - $item['sig'] = base64url_encode(rsa_sign($item['body'],$r[0]['channel_prvkey'])); + $item['sig'] = base64url_encode(rsa_sign($item['body'], $r[0]['channel_prvkey'])); $item['item_verified'] = 1; - } /** - * @brief + * @brief Stores an item type record. * * @param array $arr * @param boolean $allow_exec (optional) default false @@ -1502,8 +1540,17 @@ function item_sign(&$item) { */ function item_store($arr, $allow_exec = false, $deliver = true) { - $d = array('item' => $arr, 'allow_exec' => $allow_exec); - call_hooks('item_store', $d ); + $d = [ + 'item' => $arr, + 'allow_exec' => $allow_exec + ]; + /** + * @hooks item_store + * Called when item_store() stores a record of type item. + * * \e array \b item + * * \e boolean \b allow_exec + */ + call_hooks('item_store', $d); $arr = $d['item']; $allow_exec = $d['allow_exec']; @@ -1548,7 +1595,6 @@ function item_store($arr, $allow_exec = false, $deliver = true) { return $ret; } - $arr['title'] = ((array_key_exists('title',$arr) && strlen($arr['title'])) ? trim($arr['title']) : ''); $arr['body'] = ((array_key_exists('body',$arr) && strlen($arr['body'])) ? trim($arr['body']) : ''); @@ -1566,7 +1612,6 @@ function item_store($arr, $allow_exec = false, $deliver = true) { $arr['item_flags'] = ((x($arr,'item_flags')) ? intval($arr['item_flags']) : 0 ); - $arr['lang'] = detect_language($arr['body']); // apply the input filter here @@ -1581,10 +1626,23 @@ function item_store($arr, $allow_exec = false, $deliver = true) { $allowed_languages = get_pconfig($arr['uid'],'system','allowed_languages'); if((is_array($allowed_languages)) && ($arr['lang']) && (! array_key_exists($arr['lang'],$allowed_languages))) { - $translate = array('item' => $arr, 'from' => $arr['lang'], 'to' => $allowed_languages, 'translated' => false); + $translate = [ + 'item' => $arr, + 'from' => $arr['lang'], + 'to' => $allowed_languages, + 'translated' => false + ]; + /** + * @hooks item_translate + * Called from item_store and item_store_update after the post language has been autodetected. + * * \e array \b item + * * \e string \b from + * * \e string \b to + * * \e boolean \b translated + */ call_hooks('item_translate', $translate); if((! $translate['translated']) && (intval(get_pconfig($arr['uid'],'system','reject_disallowed_languages')))) { - logger('item_store: language ' . $arr['lang'] . ' not accepted for uid ' . $arr['uid']); + logger('language ' . $arr['lang'] . ' not accepted for uid ' . $arr['uid']); $ret['message'] = 'language not accepted'; return $ret; } @@ -1776,18 +1834,26 @@ function item_store($arr, $allow_exec = false, $deliver = true) { intval($arr['revision']) ); if($r) { - logger('item_store: duplicate item ignored. ' . print_r($arr,true)); + logger('duplicate item ignored. ' . print_r($arr,true)); $ret['message'] = 'duplicate post.'; return $ret; } - call_hooks('item_store',$arr); + /** + * @hooks item_store + * Called when item_store() stores a record of type item. + */ + call_hooks('item_store', $arr); - // This hook remains for backward compatibility. - call_hooks('post_remote',$arr); + /** + * @hooks post_remote + * Called when an activity arrives from another site. + * This hook remains for backward compatibility. + */ + call_hooks('post_remote', $arr); - if(x($arr,'cancel')) { - logger('item_store: post cancelled by plugin.'); + if(x($arr, 'cancel')) { + logger('Post cancelled by plugin.'); $ret['message'] = 'cancelled.'; return $ret; } @@ -1894,7 +1960,11 @@ function item_store($arr, $allow_exec = false, $deliver = true) { $ret['item'] = $arr; - call_hooks('post_remote_end',$arr); + /** + * @hooks post_remote_end + * Called after processing a remote post. + */ + call_hooks('post_remote_end', $arr); // update the commented timestamp on the parent - unless this is potentially a clone of an older item // which we don't wish to bring to the surface. As the queue only holds deliveries for 3 days, it's @@ -1931,11 +2001,28 @@ function item_store($arr, $allow_exec = false, $deliver = true) { } - -function item_store_update($arr,$allow_exec = false, $deliver = true) { - - $d = array('item' => $arr, 'allow_exec' => $allow_exec); - call_hooks('item_store_update', $d ); +/** + * @brief Update a stored item. + * + * @param array $arr an item + * @param boolean $allow_exec (optional) default false + * @param boolean $deliver (optional) default true + * @return array + */ +function item_store_update($arr, $allow_exec = false, $deliver = true) { + + $d = [ + 'item' => $arr, + 'allow_exec' => $allow_exec + ]; + /** + * @hooks item_store_update + * Called when item_store_update() is called to update a stored item. It + * overwrites the function's parameters $arr and $allow_exec. + * * \e array \b item + * * \e boolean \b allow_exec + */ + call_hooks('item_store_update', $d); $arr = $d['item']; $allow_exec = $d['allow_exec']; @@ -1947,12 +2034,12 @@ function item_store_update($arr,$allow_exec = false, $deliver = true) { } if(! intval($arr['uid'])) { - logger('item_store_update: no uid'); + logger('no uid'); $ret['message'] = 'no uid.'; return $ret; } if(! intval($arr['id'])) { - logger('item_store_update: no id'); + logger('no id'); $ret['message'] = 'no id.'; return $ret; } @@ -1965,7 +2052,7 @@ function item_store_update($arr,$allow_exec = false, $deliver = true) { intval($uid) ); if(! $orig) { - logger('item_store_update: original post not found: ' . $orig_post_id); + logger('Original post not found: ' . $orig_post_id); $ret['message'] = 'no original'; return $ret; } @@ -1997,7 +2084,20 @@ function item_store_update($arr,$allow_exec = false, $deliver = true) { $allowed_languages = get_pconfig($arr['uid'],'system','allowed_languages'); if((is_array($allowed_languages)) && ($arr['lang']) && (! array_key_exists($arr['lang'],$allowed_languages))) { - $translate = array('item' => $arr, 'from' => $arr['lang'], 'to' => $allowed_languages, 'translated' => false); + $translate = [ + 'item' => $arr, + 'from' => $arr['lang'], + 'to' => $allowed_languages, + 'translated' => false + ]; + /** + * @hooks item_translate + * Called from item_store() and item_store_update() after the post language has been autodetected. + * * \e array \b item - returned value + * * \e string \b from + * * \e string \b to + * * \e boolean \b translated - default false, set true if hook translated it and provide it in item + */ call_hooks('item_translate', $translate); if((! $translate['translated']) && (intval(get_pconfig($arr['uid'],'system','reject_disallowed_languages')))) { logger('item_store: language ' . $arr['lang'] . ' not accepted for uid ' . $arr['uid']); @@ -2097,18 +2197,20 @@ function item_store_update($arr,$allow_exec = false, $deliver = true) { $arr['item_pending_remove'] = ((array_key_exists('item_pending_remove',$arr)) ? intval($arr['item_pending_remove']) : $orig[0]['item_pending_remove'] ); $arr['item_blocked'] = ((array_key_exists('item_blocked',$arr)) ? intval($arr['item_blocked']) : $orig[0]['item_blocked'] ); - - $arr['sig'] = ((x($arr,'sig')) ? $arr['sig'] : ''); $arr['layout_mid'] = ((array_key_exists('layout_mid',$arr)) ? dbesc($arr['layout_mid']) : $orig[0]['layout_mid'] ); $arr['public_policy'] = ((x($arr,'public_policy')) ? notags(trim($arr['public_policy'])) : $orig[0]['public_policy'] ); $arr['comment_policy'] = ((x($arr,'comment_policy')) ? notags(trim($arr['comment_policy'])) : $orig[0]['comment_policy'] ); - call_hooks('post_remote_update',$arr); + /** + * @hooks post_remote_update + * Called when processing a remote post that involved an edit or update. + */ + call_hooks('post_remote_update', $arr); - if(x($arr,'cancel')) { - logger('item_store_update: post cancelled by plugin.'); + if(x($arr, 'cancel')) { + logger('Post cancelled by plugin.'); $ret['message'] = 'cancelled.'; return $ret; } @@ -2145,9 +2247,9 @@ function item_store_update($arr,$allow_exec = false, $deliver = true) { $r = dbq("update item set " . $str . " where id = " . $orig_post_id ); if($r) - logger('item_store_update: updated item ' . $orig_post_id, LOGGER_DEBUG); + logger('Updated item ' . $orig_post_id, LOGGER_DEBUG); else { - logger('item_store_update: could not update item'); + logger('Could not update item'); $ret['message'] = 'DB update failed.'; return $ret; } @@ -2194,7 +2296,11 @@ function item_store_update($arr,$allow_exec = false, $deliver = true) { $ret['item'] = $arr; - call_hooks('post_remote_update_end',$arr); + /** + * @hooks post_remote_update_end + * Called after processing a remote post that involved an edit or update. + */ + call_hooks('post_remote_update_end', $arr); if($deliver) { send_status_notifications($orig_post_id,$arr); @@ -2427,7 +2533,7 @@ function tag_deliver($uid, $item_id) { } } else - logger('tag_deliver: tag permission denied for ' . $u[0]['channel_address']); + logger('Tag permission denied for ' . $u[0]['channel_address']); } /* @@ -2463,139 +2569,163 @@ function tag_deliver($uid, $item_id) { $terms = array_merge(get_terms_oftype($item['term'],TERM_MENTION),get_terms_oftype($item['term'],TERM_FORUM)); if($terms) - logger('tag_deliver: post mentions: ' . print_r($terms,true), LOGGER_DATA); + logger('Post mentions: ' . print_r($terms,true), LOGGER_DATA); + + + $max_forums = get_config('system','max_tagged_forums',2); + $matched_forums = 0; + $link = normalise_link($u[0]['xchan_url']); + if($terms) { foreach($terms as $term) { - if(link_compare($term['url'],$link)) { - $mention = true; - break; + if(! link_compare($term['url'],$link)) { + continue; } - } - } - if($mention) { - logger('tag_deliver: mention found for ' . $u[0]['channel_name']); + $mention = true; - $r = q("update item set item_mentionsme = 1 where id = %d", - intval($item_id) - ); + logger('Mention found for ' . $u[0]['channel_name']); - // At this point we've determined that the person receiving this post was mentioned in it or it is a union. - // Now let's check if this mention was inside a reshare so we don't spam a forum - // If it's private we may have to unobscure it momentarily so that we can parse it. + $r = q("update item set item_mentionsme = 1 where id = %d", + intval($item_id) + ); - $body = $item['body']; + // At this point we've determined that the person receiving this post was mentioned in it or it is a union. + // Now let's check if this mention was inside a reshare so we don't spam a forum + // If it's private we may have to unobscure it momentarily so that we can parse it. - $body = preg_replace('/\[share(.*?)\[\/share\]/','',$body); + $body = preg_replace('/\[share(.*?)\[\/share\]/','',$item['body']); - $tagged = false; - $plustagged = false; - $matches = array(); + $tagged = false; + $plustagged = false; + $matches = array(); - $pattern = '/[\!@]\!?\[zrl\=' . preg_quote($term['url'],'/') . '\]' . preg_quote($term['term'],'/') . '\[\/zrl\]/'; - if(preg_match($pattern,$body,$matches)) - $tagged = true; + $pattern = '/[\!@]\!?\[zrl\=' . preg_quote($term['url'],'/') . '\]' . preg_quote($term['term'],'/') . '\[\/zrl\]/'; + if(preg_match($pattern,$body,$matches)) + $tagged = true; - // original red forum tagging sequence @forumname+ - // standard forum tagging sequence !forumname + // original red forum tagging sequence @forumname+ + // standard forum tagging sequence !forumname - $pluspattern = '/@\!?\[zrl\=([^\]]*?)\]((?:.(?!\[zrl\=))*?)\+\[\/zrl\]/'; + $pluspattern = '/@\!?\[zrl\=([^\]]*?)\]((?:.(?!\[zrl\=))*?)\+\[\/zrl\]/'; - $forumpattern = '/\!\!?\[zrl\=([^\]]*?)\]((?:.(?!\[zrl\=))*?)\[\/zrl\]/'; + $forumpattern = '/\!\!?\[zrl\=([^\]]*?)\]((?:.(?!\[zrl\=))*?)\[\/zrl\]/'; - $found = false; + $found = false; - $max_forums = get_config('system','max_tagged_forums'); - if(! $max_forums) - $max_forums = 2; - $matched_forums = 0; - $matches = array(); + $matches = array(); - if(preg_match_all($pluspattern,$body,$matches,PREG_SET_ORDER)) { - foreach($matches as $match) { - $matched_forums ++; - if($term['url'] === $match[1] && $term['term'] === $match[2]) { - if($matched_forums <= $max_forums) { - $plustagged = true; - $found = true; - break; + if(preg_match_all($pluspattern,$body,$matches,PREG_SET_ORDER)) { + foreach($matches as $match) { + $matched_forums ++; + if($term['url'] === $match[1] && $term['term'] === $match[2] && intval($term['ttype']) === TERM_MENTION) { + if($matched_forums <= $max_forums) { + $plustagged = true; + $found = true; + break; + } + logger('forum ' . $term['term'] . ' exceeded max_tagged_forums - ignoring'); } - logger('forum ' . $term['term'] . ' exceeded max_tagged_forums - ignoring'); } } - } - if(preg_match_all($forumpattern,$body,$matches,PREG_SET_ORDER)) { - foreach($matches as $match) { - $matched_forums ++; - if($term['url'] === $match[1] && $term['term'] === $match[2]) { - if($matched_forums <= $max_forums) { - $plustagged = true; - $found = true; - break; + if(preg_match_all($forumpattern,$body,$matches,PREG_SET_ORDER)) { + foreach($matches as $match) { + $matched_forums ++; + if($term['url'] === $match[1] && $term['term'] === $match[2] && intval($term['ttype']) === TERM_FORUM) { + if($matched_forums <= $max_forums) { + $plustagged = true; + $found = true; + break; + } + logger('forum ' . $term['term'] . ' exceeded max_tagged_forums - ignoring'); } - logger('forum ' . $term['term'] . ' exceeded max_tagged_forums - ignoring'); } } - } - if(! ($tagged || $plustagged)) { - logger('tag_deliver: mention was in a reshare or exceeded max_tagged_forums - ignoring'); - return; - } + if(! ($tagged || $plustagged)) { + logger('Mention was in a reshare or exceeded max_tagged_forums - ignoring'); + continue; + } - $arr = array('channel_id' => $uid, 'item' => $item, 'body' => $body); - call_hooks('tagged',$arr); + $arr = [ + 'channel_id' => $uid, + 'item' => $item, + 'body' => $body + ]; + /** + * @hooks tagged + * Called when a delivery is processed which results in you being tagged. + * * \e number \b channel_id + * * \e array \b item + * * \e string \b body + */ + call_hooks('tagged', $arr); + + /* + * Kill two birds with one stone. As long as we're here, send a mention notification. + */ - /* - * Kill two birds with one stone. As long as we're here, send a mention notification. - */ + Zlib\Enotify::submit(array( + 'to_xchan' => $u[0]['channel_hash'], + 'from_xchan' => $item['author_xchan'], + 'type' => NOTIFY_TAGSELF, + 'item' => $item, + 'link' => $i[0]['llink'], + 'verb' => ACTIVITY_TAG, + 'otype' => 'item' + )); - Zlib\Enotify::submit(array( - 'to_xchan' => $u[0]['channel_hash'], - 'from_xchan' => $item['author_xchan'], - 'type' => NOTIFY_TAGSELF, - 'item' => $item, - 'link' => $i[0]['llink'], - 'verb' => ACTIVITY_TAG, - 'otype' => 'item' - )); + // Just a normal tag? - // Just a normal tag? + if(! $plustagged) { + logger('Not a plus tag', LOGGER_DEBUG); + continue; + } - if(! $plustagged) { - logger('tag_deliver: not a plus tag', LOGGER_DEBUG); - return; - } + // plustagged - keep going, next check permissions - // plustagged - keep going, next check permissions + if(! perm_is_allowed($uid,$item['author_xchan'],'tag_deliver')) { + logger('tag_delivery denied for uid ' . $uid . ' and xchan ' . $item['author_xchan']); + continue; + } - if(! perm_is_allowed($uid,$item['author_xchan'],'tag_deliver')) { - logger('tag_delivery denied for uid ' . $uid . ' and xchan ' . $item['author_xchan']); - return; - } - } - if((! $mention) && (! $union)) { - logger('tag_deliver: no mention for ' . $u[0]['channel_name'] . ' and no union.'); - return; + if(! $mention) { + logger('No mention for ' . $u[0]['channel_name']); + continue; + } + + // tgroup delivery - setup a second delivery chain + // prevent delivery looping - only proceed + // if the message originated elsewhere and is a top-level post + + + if(intval($item['item_wall']) || intval($item['item_origin']) || (! intval($item['item_thread_top'])) || ($item['id'] != $item['parent'])) { + logger('Item was local or a comment. rejected.'); + continue; + } + + logger('Creating second delivery chain.'); + start_delivery_chain($u[0],$item,$item_id,null); + + } } - // tgroup delivery - setup a second delivery chain - // prevent delivery looping - only proceed - // if the message originated elsewhere and is a top-level post + if($union) { + if(intval($item['item_wall']) || intval($item['item_origin']) || (! intval($item['item_thread_top'])) || ($item['id'] != $item['parent'])) { + logger('Item was local or a comment. rejected.'); + return; + } + logger('Creating second delivery chain.'); + start_delivery_chain($u[0],$item,$item_id,null); - if(intval($item['item_wall']) || intval($item['item_origin']) || (! intval($item['item_thread_top'])) || ($item['id'] != $item['parent'])) { - logger('tag_deliver: item was local or a comment. rejected.'); - return; } - logger('tag_deliver: creating second delivery chain.'); - start_delivery_chain($u[0],$item,$item_id,null); } /** @@ -2605,8 +2735,12 @@ function tag_deliver($uid, $item_id) { * This is so that the channel with tag_delivery enabled can receive the post even if they turn off * permissions for the sender to send their stream. tag_deliver() can't be called until the post is actually stored. * By then it would be too late to reject it. + * + * @param number $uid A chnnel_id + * @param array $item + * @return boolean */ -function tgroup_check($uid,$item) { +function tgroup_check($uid, $item) { $mention = false; @@ -2639,78 +2773,73 @@ function tgroup_check($uid,$item) { if($terms) logger('tgroup_check: post mentions: ' . print_r($terms,true), LOGGER_DATA); + $max_forums = get_config('system','max_tagged_forums',2); + $matched_forums = 0; + $link = normalise_link($u[0]['xchan_url']); if($terms) { foreach($terms as $term) { - if(link_compare($term['url'],$link)) { - $mention = true; - break; + if(! link_compare($term['url'],$link)) { + continue; } - } - } - if($mention) { - logger('tgroup_check: mention found for ' . $u[0]['channel_name']); - } - else - return false; + $mention = true; + logger('tgroup_check: mention found for ' . $u[0]['channel_name']); - // At this point we've determined that the person receiving this post was mentioned in it. - // Now let's check if this mention was inside a reshare so we don't spam a forum - // note: $term has been set to the matching term + // At this point we've determined that the person receiving this post was mentioned in it. + // Now let's check if this mention was inside a reshare so we don't spam a forum + // note: $term has been set to the matching term - $body = $item['body']; + $body = preg_replace('/\[share(.*?)\[\/share\]/','',$item['body']); - $body = preg_replace('/\[share(.*?)\[\/share\]/','',$body); + $pluspattern = '/@\!?\[zrl\=([^\]]*?)\]((?:.(?!\[zrl\=))*?)\+\[\/zrl\]/'; - $pluspattern = '/@\!?\[zrl\=([^\]]*?)\]((?:.(?!\[zrl\=))*?)\+\[\/zrl\]/'; + $forumpattern = '/\!\!?\[zrl\=([^\]]*?)\]((?:.(?!\[zrl\=))*?)\[\/zrl\]/'; - $forumpattern = '/\!\!?\[zrl\=([^\]]*?)\]((?:.(?!\[zrl\=))*?)\[\/zrl\]/'; + $found = false; + $matches = array(); - $found = false; - - $max_forums = get_config('system','max_tagged_forums'); - if(! $max_forums) - $max_forums = 2; - $matched_forums = 0; - $matches = array(); - - if(preg_match_all($pluspattern,$body,$matches,PREG_SET_ORDER)) { - foreach($matches as $match) { - $matched_forums ++; - if($term['url'] === $match[1] && $term['term'] === $match[2]) { - if($matched_forums <= $max_forums) { - $found = true; - break; + if(preg_match_all($pluspattern,$body,$matches,PREG_SET_ORDER)) { + foreach($matches as $match) { + $matched_forums ++; + if($term['url'] === $match[1] && $term['term'] === $match[2] && intval($term['ttype']) === TERM_MENTION) { + if($matched_forums <= $max_forums) { + $found = true; + break; + } + logger('forum ' . $term['term'] . ' exceeded max_tagged_forums - ignoring'); + } } - logger('forum ' . $term['term'] . ' exceeded max_tagged_forums - ignoring'); } - } - } - if(preg_match_all($forumpattern,$body,$matches,PREG_SET_ORDER)) { - foreach($matches as $match) { - $matched_forums ++; - if($term['url'] === $match[1] && $term['term'] === $match[2]) { - if($matched_forums <= $max_forums) { - $found = true; - break; + if(preg_match_all($forumpattern,$body,$matches,PREG_SET_ORDER)) { + foreach($matches as $match) { + $matched_forums ++; + if($term['url'] === $match[1] && $term['term'] === $match[2] && intval($term['ttype']) === TERM_FORUM) { + if($matched_forums <= $max_forums) { + $found = true; + break; + } + logger('forum ' . $term['term'] . ' exceeded max_tagged_forums - ignoring'); + } } - logger('forum ' . $term['term'] . ' exceeded max_tagged_forums - ignoring'); } + + if(! $found) { + logger('tgroup_check: mention was in a reshare or exceeded max_tagged_forums - ignoring'); + continue; + } + + return true; } } - if(! $found) { - logger('tgroup_check: mention was in a reshare or exceeded max_tagged_forums - ignoring'); - return false; - } + return false; - return true; } /** @@ -2731,8 +2860,8 @@ function start_delivery_chain($channel, $item, $item_id, $parent) { if($sourced) { $r = q("select * from source where src_channel_id = %d and ( src_xchan = '%s' or src_xchan = '*' ) limit 1", intval($channel['channel_id']), - dbesc(($item['source_xchan']) ? $item['source_xchan'] : $item['owner_xchan']) - ); + dbesc(($item['source_xchan']) ? $item['source_xchan'] : $item['owner_xchan']) + ); if($r) { $t = trim($r[0]['src_tag']); if($t) { @@ -2741,15 +2870,15 @@ function start_delivery_chain($channel, $item, $item_id, $parent) { foreach($tags as $tt) { $tt = trim($tt); if($tt) { - q("insert into term (uid,oid,otype,ttype,term,url) - values(%d,%d,%d,%d,'%s','%s') ", - intval($channel['channel_id']), - intval($item_id), - intval(TERM_OBJ_POST), - intval(TERM_CATEGORY), - dbesc($tt), + q("insert into term (uid,oid,otype,ttype,term,url) + values(%d,%d,%d,%d,'%s','%s') ", + intval($channel['channel_id']), + intval($item_id), + intval(TERM_OBJ_POST), + intval(TERM_CATEGORY), + dbesc($tt), dbesc(z_root() . '/channel/' . $channel['channel_address'] . '?f=&cat=' . urlencode($tt)) - ); + ); } } } @@ -3014,7 +3143,6 @@ function mail_store($arr) { $arr['mail_flags'] = ((x($arr,'mail_flags')) ? intval($arr['mail_flags']) : 0 ); $arr['mail_raw'] = ((x($arr,'mail_raw')) ? intval($arr['mail_raw']) : 0 ); - if($arr['parent_mid']) { $parent_item = q("select * from mail where mid = '%s' and channel_id = %d limit 1", @@ -3022,7 +3150,7 @@ function mail_store($arr) { intval($arr['channel_id']) ); if(($parent_item) && (! $arr['conv_guid'])) { - $arr['conv_guid'] = $parent_item[0]['conv_guid']; + $arr['conv_guid'] = $parent_item[0]['conv_guid']; } } else { @@ -3048,19 +3176,23 @@ function mail_store($arr) { ); if($r) { - logger('mail_store: duplicate item ignored. ' . print_r($arr,true)); + logger('Duplicate item ignored. ' . print_r($arr,true)); return 0; } if(! $r && $arr['mail_recalled'] == 1) { - logger('mail_store: recalled item not found. ' . print_r($arr,true)); + logger('Recalled item not found. ' . print_r($arr,true)); return 0; } - call_hooks('post_mail',$arr); + /** + * @hooks post_mail + * Called when a mail message has been composed. + */ + call_hooks('post_mail', $arr); if(x($arr,'cancel')) { - logger('mail_store: post cancelled by plugin.'); + logger('Post cancelled by plugin.'); return 0; } @@ -3077,15 +3209,15 @@ function mail_store($arr) { if($r) { $current_post = $r[0]['id']; - logger('mail_store: created item ' . $current_post, LOGGER_DEBUG); + logger('Created item ' . $current_post, LOGGER_DEBUG); $arr['id'] = $current_post; // for notification } else { - logger('mail_store: could not locate created item'); + logger('Could not locate created item'); return 0; } if(count($r) > 1) { - logger('mail_store: duplicated post occurred. Removing duplicates.'); + logger('Duplicated post occurred. Removing duplicates.'); q("DELETE FROM mail WHERE mid = '%s' AND channel_id = %d AND id != %d ", $arr['mid'], intval($arr['channel_id']), @@ -3114,7 +3246,11 @@ function mail_store($arr) { ); } - call_hooks('post_mail_end',$arr); + /** + * @hooks post_mail_end + * Called when a mail message has been delivered. + */ + call_hooks('post_mail_end', $arr); return $current_post; } @@ -3135,7 +3271,7 @@ function fix_private_photos($s, $uid, $item = null, $cid = 0) { $img_st_close++; // make it point to AFTER the closing bracket $image = substr($orig_body, $img_start + $img_st_close, $img_len); - logger('fix_private_photos: found photo ' . $image, LOGGER_DEBUG); + logger('Found photo ' . $image, LOGGER_DEBUG); if(stristr($image , $site . '/photo/')) { // Only embed locally hosted photos @@ -3179,7 +3315,7 @@ function fix_private_photos($s, $uid, $item = null, $cid = 0) { // If a custom width and height were specified, apply before embedding if(preg_match("/\[zmg\=([0-9]*)x([0-9]*)\]/is", substr($orig_body, $img_start, $img_st_close), $match)) { - logger('fix_private_photos: scaling photo', LOGGER_DEBUG); + logger('Scaling photo', LOGGER_DEBUG); $width = intval($match[1]); $height = intval($match[2]); @@ -3192,9 +3328,9 @@ function fix_private_photos($s, $uid, $item = null, $cid = 0) { } } - logger('fix_private_photos: replacing photo', LOGGER_DEBUG); + logger('Replacing photo', LOGGER_DEBUG); $image = 'data:' . $type . ';base64,' . base64_encode($data); - logger('fix_private_photos: replaced: ' . $image, LOGGER_DATA); + logger('Replaced: ' . $image, LOGGER_DATA); } } } @@ -3451,8 +3587,16 @@ function drop_item($id,$interactive = true,$stage = DROPITEM_NORMAL,$force = fal ); } - $arr = array('item' => $item, 'interactive' => $interactive, 'stage' => $stage); - call_hooks('drop_item', $arr ); + $arr = [ + 'item' => $item, + 'interactive' => $interactive, + 'stage' => $stage + ]; + /** + * @hooks drop_item + * Called when an 'item' is removed. + */ + call_hooks('drop_item', $arr); $notify_id = intval($item['id']); @@ -3602,8 +3746,14 @@ function delete_item_lowlevel($item, $stage = DROPITEM_NORMAL, $force = false) { return true; } - -function first_post_date($uid,$wall = false) { +/** + * @brief Return the first post date. + * + * @param int $uid + * @param boolean $wall (optional) default false + * @return string|boolean date string, otherwise false + */ +function first_post_date($uid, $wall = false) { $wall_sql = (($wall) ? " and item_wall = 1 " : "" ); $item_normal = item_normal(); @@ -3612,7 +3762,6 @@ function first_post_date($uid,$wall = false) { where uid = %d and id = parent $item_normal $wall_sql order by created asc limit 1", intval($uid) - ); if($r) { // logger('first_post_date: ' . $r[0]['id'] . ' ' . $r[0]['created'], LOGGER_DATA); @@ -3628,8 +3777,8 @@ function first_post_date($uid,$wall = false) { * current flat list of all representative dates. * * @param int $uid - * @param unknown $wall - * @param unknown $mindate + * @param boolean $wall + * @param string $mindate * @return array */ function list_post_dates($uid, $wall, $mindate) { @@ -3700,8 +3849,14 @@ function posted_dates($uid,$wall) { return $ret; } - -function fetch_post_tags($items,$link = false) { +/** + * @brief Extend an item array with the associated tags of the posts. + * + * @param array $items + * @param boolean $link (optional) default false + * @return array Return the provided $items array after extended the posts with tags + */ +function fetch_post_tags($items, $link = false) { $tag_finder = array(); if($items) { @@ -3720,7 +3875,6 @@ function fetch_post_tags($items,$link = false) { } $tag_finder_str = implode(', ', $tag_finder); - if(strlen($tag_finder_str)) { $tags = q("select * from term where oid in ( %s ) and otype = %d", dbesc($tag_finder_str), @@ -3729,7 +3883,6 @@ function fetch_post_tags($items,$link = false) { $imeta = q("select * from iconfig where iid in ( %s )", dbesc($tag_finder_str) ); - } for($x = 0; $x < count($items); $x ++) { @@ -3779,8 +3932,15 @@ function fetch_post_tags($items,$link = false) { } - -function zot_feed($uid,$observer_hash,$arr) { +/** + * @brief + * + * @param int $uid + * @param string $observer_hash + * @param array $arr + * @return array + */ +function zot_feed($uid, $observer_hash, $arr) { $result = array(); $mindate = null; @@ -3903,8 +4063,9 @@ function items_fetch($arr,$channel = null,$observer_hash = null,$client_mode = C $item_uids = ' true '; $item_normal = item_normal(); - - if ($arr['uid']) $uid= $arr['uid']; + if($arr['uid']) { + $uid = $arr['uid']; + } if($channel) { $uid = $channel['channel_id']; @@ -3979,7 +4140,7 @@ function items_fetch($arr,$channel = null,$observer_hash = null,$client_mode = C } if($channel && intval($arr['compat']) === 1) { - $sql_extra = " AND author_xchan = '" . $channel['channel_hash'] . "' and item_private = 0 "; + $sql_extra = " AND author_xchan = '" . $channel['channel_hash'] . "' and item_private = 0 $item_normal "; } if ($arr['datequery']) { @@ -3990,24 +4151,23 @@ function items_fetch($arr,$channel = null,$observer_hash = null,$client_mode = C } if($arr['search']) { + if(strpos($arr['search'],'#') === 0) + $sql_extra .= term_query('item',substr($arr['search'],1),TERM_HASHTAG,TERM_COMMUNITYTAG); + else + $sql_extra .= sprintf(" AND item.body like '%s' ", + dbesc(protect_sprintf('%' . $arr['search'] . '%')) + ); + } - if(strpos($arr['search'],'#') === 0) - $sql_extra .= term_query('item',substr($arr['search'],1),TERM_HASHTAG,TERM_COMMUNITYTAG); - else - $sql_extra .= sprintf(" AND item.body like '%s' ", - dbesc(protect_sprintf('%' . $arr['search'] . '%')) - ); - } - - if(strlen($arr['file'])) { - $sql_extra .= term_query('item',$arr['files'],TERM_FILE); - } + if(strlen($arr['file'])) { + $sql_extra .= term_query('item',$arr['files'],TERM_FILE); + } - if($arr['conv'] && $channel) { - $sql_extra .= sprintf(" AND parent IN (SELECT distinct parent from item where ( author_xchan like '%s' or item_mentionsme = 1 )) ", - dbesc(protect_sprintf($uidhash)) - ); - } + if($arr['conv'] && $channel) { + $sql_extra .= sprintf(" AND parent IN (SELECT distinct parent from item where ( author_xchan like '%s' or item_mentionsme = 1 )) ", + dbesc(protect_sprintf($uidhash)) + ); + } if (($client_mode & CLIENT_MODE_UPDATE) && (! ($client_mode & CLIENT_MODE_LOAD))) { // only setup pagination on initial page view @@ -4042,9 +4202,9 @@ function items_fetch($arr,$channel = null,$observer_hash = null,$client_mode = C } } - $simple_update = (($client_mode & CLIENT_MODE_UPDATE) ? " and item.item_unseen = 1 " : ''); - if($client_mode & CLIENT_MODE_LOAD) - $simple_update = ''; + $simple_update = (($client_mode & CLIENT_MODE_UPDATE) ? " and item.item_unseen = 1 " : ''); + if($client_mode & CLIENT_MODE_LOAD) + $simple_update = ''; //$start = dba_timer(); @@ -4067,7 +4227,7 @@ function items_fetch($arr,$channel = null,$observer_hash = null,$client_mode = C $items = q("SELECT item.*, item.id AS item_id FROM item WHERE $item_uids $item_restrict $simple_update - $sql_extra $sql_nets + $sql_extra $sql_nets $sql_extra3 ORDER BY item.received DESC $pager_sql" ); @@ -4087,27 +4247,26 @@ function items_fetch($arr,$channel = null,$observer_hash = null,$client_mode = C if(($client_mode & CLIENT_MODE_LOAD) || ($client_mode == CLIENT_MODE_NORMAL)) { - // Fetch a page full of parent items for this page - - $r = q("SELECT distinct item.id AS item_id, item.$ordering FROM item - left join abook on item.author_xchan = abook.abook_xchan - WHERE $item_uids $item_restrict - AND item.parent = item.id - and (abook.abook_blocked = 0 or abook.abook_flags is null) - $sql_extra3 $sql_extra $sql_nets - ORDER BY item.$ordering DESC $pager_sql " - ); - - } - else { - // update - $r = q("SELECT item.parent AS item_id FROM item - left join abook on item.author_xchan = abook.abook_xchan - WHERE $item_uids $item_restrict $simple_update - and (abook.abook_blocked = 0 or abook.abook_flags is null) - $sql_extra3 $sql_extra $sql_nets " - ); - } + // Fetch a page full of parent items for this page + + $r = q("SELECT distinct item.id AS item_id, item.$ordering FROM item + left join abook on item.author_xchan = abook.abook_xchan + WHERE $item_uids $item_restrict + AND item.parent = item.id + and (abook.abook_blocked = 0 or abook.abook_flags is null) + $sql_extra3 $sql_extra $sql_nets + ORDER BY item.$ordering DESC $pager_sql " + ); + } + else { + // update + $r = q("SELECT item.parent AS item_id FROM item + left join abook on item.author_xchan = abook.abook_xchan + WHERE $item_uids $item_restrict $simple_update + and (abook.abook_blocked = 0 or abook.abook_flags is null) + $sql_extra3 $sql_extra $sql_nets " + ); + } //$first = dba_timer(); @@ -4163,16 +4322,17 @@ function webpage_to_namespace($webpage) { $page_type = 'PDL'; elseif($webpage == ITEM_TYPE_CARD) $page_type = 'CARD'; + elseif($webpage == ITEM_TYPE_ARTICLE) + $page_type = 'ARTICLE'; elseif($webpage == ITEM_TYPE_DOC) $page_type = 'docfile'; else $page_type = 'unknown'; - return $page_type; + return $page_type; } - function update_remote_id($channel,$post_id,$webpage,$pagetitle,$namespace,$remote_id,$mid) { if(! $post_id) @@ -4296,7 +4456,6 @@ function comment_local_origin($item) { - function send_profile_photo_activity($channel,$photo,$profile) { // for now only create activities for the default profile @@ -4345,8 +4504,6 @@ function send_profile_photo_activity($channel,$photo,$profile) { $arr['author_xchan'] = $channel['channel_hash']; post_activity_item($arr); - - } @@ -4549,7 +4706,6 @@ function item_create_edit_activity($post) { )); - $x = post_activity_item($new_item); $post_id = $x['id']; @@ -4565,5 +4721,63 @@ function item_create_edit_activity($post) { } \Zotlabs\Daemon\Master::Summon(array('Notifier', 'edit_activity', $post_id)); +} +/** + * @brief copies an entire conversation from the pubstream to this channel's stream + * which will allow you to interact with it. + */ + + + +function copy_of_pubitem($channel,$mid) { + + $result = null; + $syschan = get_sys_channel(); + + // logger('copy_of_pubitem: ' . $channel['channel_id'] . ' mid: ' . $mid); + + $r = q("select * from item where mid = '%s' and uid = %d limit 1", + dbesc($mid), + intval($channel['channel_id']) + ); + + if($r) { + logger('exists'); + $item = fetch_post_tags($r,true); + return $item[0]; + } + + + $r = q("select * from item where parent_mid = (select parent_mid from item where mid = '%s' and uid = %d ) order by id ", + dbesc($mid), + intval($syschan['channel_id']) + ); + + if($r) { + $items = fetch_post_tags($r,true); + foreach($items as $rv) { + $d = q("select id from item where mid = '%s' and uid = %d limit 1", + dbesc($rv['mid']), + intval($channel['channel_id']) + ); + if($d) { + continue; + } + + unset($rv['id']); + unset($rv['parent']); + $rv['aid'] = $channel['channel_account_id']; + $rv['uid'] = $channel['channel_id']; + $rv['item_wall'] = 0; + $rv['item_origin'] = 0; + + $x = item_store($rv); + if($x['item_id'] && $x['item']['mid'] === $mid) { + $result = $x['item']; + } + + } + } + return $result; } diff --git a/include/language.php b/include/language.php index efe9397fb..f6f266685 100644 --- a/include/language.php +++ b/include/language.php @@ -8,6 +8,7 @@ * language related tasks. */ +use CommerceGuys\Intl\Language\LanguageRepository; /** * @brief Get the browser's submitted preferred languages. @@ -17,7 +18,7 @@ * * Get the language setting directly from system variables, bypassing get_config() * as database may not yet be configured. - * + * * If possible, we use the value from the browser. * * @return array with ordered list of preferred languages from browser @@ -28,7 +29,7 @@ function get_browser_language() { if (x($_SERVER, 'HTTP_ACCEPT_LANGUAGE')) { // break up string into pieces (languages and q factors) - preg_match_all('/([a-z]{1,8}(-[a-z]{1,8})?)\s*(;\s*q\s*=\s*(1|0\.[0-9]+))?/i', + preg_match_all('/([a-z]{1,8}(-[a-z]{1,8})?)\s*(;\s*q\s*=\s*(1|0\.[0-9]+))?/i', $_SERVER['HTTP_ACCEPT_LANGUAGE'], $lang_parse); if (count($lang_parse[1])) { @@ -40,7 +41,7 @@ function get_browser_language() { if ($val === '') $langs[$lang] = 1; } - // sort list based on value + // sort list based on value arsort($langs, SORT_NUMERIC); } } @@ -52,7 +53,7 @@ function get_browser_language() { * @brief Returns the best language for which also a translation exists. * * This function takes the results from get_browser_language() and compares it - * with the available translations and returns the best fitting language for + * with the available translations and returns the best fitting language for * which there exists a translation. * * If there is no match fall back to config['system']['language'] @@ -243,11 +244,9 @@ function string_plural_select_default($n) { * * @see http://pear.php.net/package/Text_LanguageDetect * @param string $s A string to examine - * @return Language code in 2-letter ISO 639-1 (en, de, fr) format + * @return string Language code in 2-letter ISO 639-1 (en, de, fr) format */ function detect_language($s) { - require_once('Text/LanguageDetect.php'); - $min_length = get_config('system', 'language_detect_min_length'); if ($min_length === false) $min_length = LANGUAGE_DETECT_MIN_LENGTH; @@ -257,7 +256,7 @@ function detect_language($s) { $min_confidence = LANGUAGE_DETECT_MIN_CONFIDENCE; // embedded apps have long base64 strings which will trip up the detector. - $naked_body = preg_replace('/\[app\](.*?)\[\/app\]/','',$s); + $naked_body = preg_replace('/\[app\](.*?)\[\/app\]/', '', $s); // strip off bbcode $naked_body = preg_replace('/\[(.+?)\]/', '', $naked_body); if (mb_strlen($naked_body) < intval($min_length)) { @@ -300,11 +299,7 @@ function detect_language($s) { * @param string $s Language code to look up * @param string $l (optional) In which language to return the name * @return string with the language name, or $s if unrecognized - * - * @todo include CommerceGuys\Intl through composer like SabreDAV. */ -require_once(__DIR__ . '/../library/intl/vendor/autoload.php'); -use CommerceGuys\Intl\Language\LanguageRepository; function get_language_name($s, $l = null) { // get() expects the second part to be in upper case if (strpos($s, '-') !== false) $s = substr($s, 0, 2) . strtoupper(substr($s, 2)); @@ -322,6 +317,8 @@ function get_language_name($s, $l = null) { $language = $languageRepository->get($s, $l); } catch (CommerceGuys\Intl\Exception\UnknownLanguageException $e) { return $s; // Give up + } catch (CommerceGuys\Intl\Exception\UnknownLocaleException $e) { + return $s; // Give up } } @@ -379,7 +376,7 @@ function lang_selector() { $o = replace_macros($tpl, array( '$title' => t('Select an alternate language'), '$langs' => array($lang_options, $selected), - + )); return $o; diff --git a/include/markdown.php b/include/markdown.php index 15e19bde1..e4a35e3c3 100644 --- a/include/markdown.php +++ b/include/markdown.php @@ -14,27 +14,25 @@ require_once("include/bbcode.php"); /** - * @brief + * @brief Convert Markdown to bbcode. * * We don't want to support a bbcode specific markdown interpreter * and the markdown library we have is pretty good, but provides HTML output. * So we'll use that to convert to HTML, then convert the HTML back to bbcode, * and then clean up a few Diaspora specific constructs. * - * @param string $s + * @param string $s The message as Markdown * @param boolean $use_zrl default false - * @return string + * @param array $options default empty + * @return string The message converted to bbcode */ - function markdown_to_bb($s, $use_zrl = false, $options = []) { - if(is_array($s)) { - btlogger('markdown_to_bb called with array. ' . print_r($s,true), LOGGER_NORMAL, LOG_WARNING); + btlogger('markdown_to_bb called with array. ' . print_r($s, true), LOGGER_NORMAL, LOG_WARNING); return ''; } - $s = str_replace("
","\r",$s); $s = str_replace("
\n>","",$s); @@ -43,20 +41,32 @@ function markdown_to_bb($s, $use_zrl = false, $options = []) { // if empty link text replace with the url $s = preg_replace("/\[\]\((.*?)\)/ism",'[$1]($1)',$s); - $x = [ 'text' => $s , 'zrl' => $use_zrl, 'options' => $options ]; - - call_hooks('markdown_to_bb_init',$x); + $x = [ + 'text' => $s, + 'zrl' => $use_zrl, + 'options' => $options + ]; + /** + * @hooks markdown_to_bb_init + * * \e string \b text - The message as Markdown and what will get returned + * * \e boolean \b zrl + * * \e array \b options + */ + call_hooks('markdown_to_bb_init', $x); $s = $x['text']; - // Escaping the hash tags - doesn't always seem to work - // $s = preg_replace('/\#([^\s\#])/','\\#$1',$s); - // This seems to work + // Escaping the hash tags $s = preg_replace('/\#([^\s\#])/','#$1',$s); $s = MarkdownExtra::defaultTransform($s); - $s = str_replace("\r","",$s); + if($options && $options['preserve_lf']) { + $s = str_replace(["\r","\n"],["",'<br>'],$s); + } + else { + $s = str_replace("\r","",$s); + } $s = str_replace('#','#',$s); @@ -77,13 +87,22 @@ function markdown_to_bb($s, $use_zrl = false, $options = []) { // Don't show link to full picture (until it is fixed) $s = scale_external_images($s, false); - call_hooks('markdown_to_bb',$s); + /** + * @hooks markdown_to_bb + * * \e string - The already converted message as bbcode + */ + call_hooks('markdown_to_bb', $s); return $s; } - +/** + * @brief + * + * @param array $match + * @return string + */ function bb_to_markdown_share($match) { $matches = array(); @@ -150,17 +169,22 @@ function bb_to_markdown_share($match) { } - +/** + * @brief Convert bbcode to Markdown. + * + * @param string $Text The message as bbcode + * @param array $options default empty + * @return string The message converted to Markdown + */ function bb_to_markdown($Text, $options = []) { /* * Transform #tags, strip off the [url] and replace spaces with underscore */ - $Text = preg_replace_callback('/#\[([zu])rl\=(.*?)\](.*?)\[\/[(zu)]rl\]/i', + $Text = preg_replace_callback('/#\[([zu])rl\=(.*?)\](.*?)\[\/[(zu)]rl\]/i', create_function('$match', 'return \'#\'. str_replace(\' \', \'_\', $match[3]);'), $Text); - $Text = preg_replace('/#\^\[([zu])rl\=(.*?)\](.*?)\[\/([zu])rl\]/i', '[$1rl=$2]$3[/$4rl]', $Text); // Converting images with size parameters to simple images. Markdown doesn't know it. @@ -170,12 +194,17 @@ function bb_to_markdown($Text, $options = []) { $x = [ 'bbcode' => $Text, 'options' => $options ]; - call_hooks('bb_to_markdown_bb',$x); + /** + * @hooks bb_to_markdown_bb + * * \e string \b bbcode - The message as bbcode and what will get returned + * * \e array \b options + */ + call_hooks('bb_to_markdown_bb', $x); $Text = $x['bbcode']; // Convert it to HTML - don't try oembed - $Text = bbcode($Text, $preserve_nl, false); + $Text = bbcode($Text, [ 'tryoembed' => false ]); // Markdownify does not preserve previously escaped html entities such as <> and &. $Text = str_replace(array('<','>','&'),array('&_lt_;','&_gt_;','&_amp_;'),$Text); @@ -199,14 +228,16 @@ function bb_to_markdown($Text, $options = []) { $Text = trim($Text); + /** + * @hooks bb_to_markdown + * * \e string - The already converted message as bbcode and what will get returned + */ call_hooks('bb_to_markdown', $Text); return $Text; - } - /** * @brief Convert a HTML text into Markdown. * @@ -214,8 +245,6 @@ function bb_to_markdown($Text, $options = []) { * * If the HTML text can not get parsed it will return an empty string. * - * @see HTMLToMarkdown - * * @param string $html The HTML code to convert * @return string Markdown representation of the given HTML text, empty on error */ @@ -229,8 +258,5 @@ function html2markdown($html) { logger("Invalid HTML. HTMLToMarkdown library threw an exception."); } - // The old html 2 markdown library "pixel418/markdownify": "^2.2", - //$md = new HtmlConverter(); - //$markdown = $md->convert($Text); return $markdown; } diff --git a/include/message.php b/include/message.php index 477c7172c..4a673b961 100644 --- a/include/message.php +++ b/include/message.php @@ -215,7 +215,7 @@ function send_message($uid = 0, $recipient = '', $body = '', $subject = '', $rep return $ret; } - if(count($images)) { + if($images) { foreach($images as $image) { if(! stristr($image,z_root() . '/photo/')) continue; @@ -335,12 +335,9 @@ function private_messages_list($uid, $mailbox = '', $start = 0, $numitems = 0) { case 'combined': default: - - $parents = q("SELECT parent_mid FROM mail WHERE mid = parent_mid AND channel_id = %d ORDER BY created DESC", + $parents = q("SELECT mail.parent_mid FROM mail LEFT JOIN conv ON mail.conv_guid = conv.guid WHERE mail.mid = mail.parent_mid AND mail.channel_id = %d ORDER BY conv.updated DESC $limit", dbesc($local_channel) ); - //FIXME: We need the latest mail of a thread here. This query throws errors in postgres. We now look for the latest in php until somebody can fix this... - //$sql = "SELECT * FROM ( SELECT * FROM mail WHERE channel_id = $local_channel ORDER BY created DESC $limit ) AS temp_table GROUP BY parent_mid ORDER BY created DESC"; break; } diff --git a/include/nav.php b/include/nav.php index 6b98a807d..8566cc58c 100644 --- a/include/nav.php +++ b/include/nav.php @@ -73,6 +73,11 @@ EOT; // nav links: array of array('href', 'text', 'extra css classes', 'title') $nav = []; + $disable_discover_tab = get_config('system','disable_discover_tab') || get_config('system','disable_discover_tab') === false; + + if(! $disable_discover_tab) + $nav['pubs'] = true; + /** * Display login or logout */ @@ -171,13 +176,15 @@ EOT; } - - $homelink = get_my_url(); - if(! $homelink) { + $my_url = get_my_url(); + if(! $my_url) { $observer = App::get_observer(); - $homelink = (($observer) ? $observer['xchan_url'] : ''); + $my_url = (($observer) ? $observer['xchan_url'] : ''); } + $homelink_arr = parse_url($my_url); + $homelink = $homelink_arr['scheme'] . '://' . $homelink_arr['host']; + if(! $is_owner) { $nav['rusermenu'] = array( $homelink, @@ -253,7 +260,7 @@ EOT; } $syslist = array(); - $list = Zlib\Apps::app_list(local_channel(), false, 'nav_featured_app'); + $list = Zlib\Apps::app_list(local_channel(), false, ['nav_featured_app', 'nav_pinned_app']); if($list) { foreach($list as $li) { $syslist[] = Zlib\Apps::app_encode($li); @@ -274,16 +281,20 @@ EOT; $app['active'] = true; if($is_owner) { - $nav_apps[] = Zlib\Apps::app_render($app,'nav'); - if(strpos($app['categories'],'navbar_' . $template)) { + if(strpos($app['categories'],'nav_pinned_app') !== false) { $navbar_apps[] = Zlib\Apps::app_render($app,'navbar'); } + else { + $nav_apps[] = Zlib\Apps::app_render($app,'nav'); + } } elseif(! $is_owner && strpos($app['requires'], 'local_channel') === false) { - $nav_apps[] = Zlib\Apps::app_render($app,'nav'); - if(strpos($app['categories'],'navbar_' . $template)) { + if(strpos($app['categories'],'nav_pinned_app') !== false) { $navbar_apps[] = Zlib\Apps::app_render($app,'navbar'); } + else { + $nav_apps[] = Zlib\Apps::app_render($app,'nav'); + } } } @@ -304,7 +315,7 @@ EOT; '$sitelocation' => $sitelocation, '$nav' => $x['nav'], '$banner' => $banner, - '$emptynotifications' => t('Loading...'), + '$emptynotifications' => t('Loading'), '$userinfo' => $x['usermenu'], '$localuser' => local_channel(), '$is_owner' => $is_owner, @@ -314,7 +325,7 @@ EOT; '$pleasewait' => t('Please wait...'), '$nav_apps' => $nav_apps, '$navbar_apps' => $navbar_apps, - '$channel_menu' => get_config('system','channel_menu'), + '$channel_menu' => get_pconfig(App::$profile_uid,'system','channel_menu',get_config('system','channel_menu')), '$channel_thumb' => ((App::$profile) ? App::$profile['thumb'] : ''), '$channel_apps' => $channel_apps, '$addapps' => t('Add Apps'), @@ -487,6 +498,17 @@ function channel_apps($is_owner = false, $nickname = null) { ]; } + if($p['view_pages'] && feature_enabled($uid,'articles')) { + $tabs[] = [ + 'label' => t('Articles'), + 'url' => z_root() . '/articles/' . $nickname , + 'sel' => ((argv(0) == 'articles') ? 'active' : ''), + 'title' => t('View Articles'), + 'id' => 'articles-tab', + 'icon' => 'file-text-o' + ]; + } + if($has_webpages && feature_enabled($uid,'webpages')) { $tabs[] = [ diff --git a/include/network.php b/include/network.php index 7e2dbf4cf..79a8c6578 100644 --- a/include/network.php +++ b/include/network.php @@ -1,6 +1,7 @@ <?php /** * @file include/network.php + * @brief Network related functions. */ /** @@ -189,7 +190,7 @@ function z_fetch_url($url, $binary = false, $redirects = 0, $opts = array()) { /** - * @brief + * @brief Does a curl post request. * * @param string $url * URL to post @@ -214,7 +215,7 @@ function z_fetch_url($url, $binary = false, $redirects = 0, $opts = array()) { * * \e string \b body => content * * \e string \b debug => from curl_info() */ -function z_post_url($url,$params, $redirects = 0, $opts = array()) { +function z_post_url($url, $params, $redirects = 0, $opts = array()) { // logger('url: ' . $url); // logger('params: ' . print_r($params,true)); @@ -276,13 +277,10 @@ function z_post_url($url,$params, $redirects = 0, $opts = array()) { @curl_setopt($ch, CURLOPT_USERPWD, $opts['http_auth']); } - if(x($opts,'cookiejar')) @curl_setopt($ch, CURLOPT_COOKIEJAR, $opts['cookiejar']); if(x($opts,'cookiefile')) @curl_setopt($ch, CURLOPT_COOKIEFILE, $opts['cookiefile']); - - if(x($opts,'cookie')) @curl_setopt($ch, CURLOPT_COOKIE, $opts['cookie']); @@ -423,7 +421,7 @@ function http_status($val, $msg = '') { * integer HTTP status result value * @param string $msg * optional message - * @return does not return, process is terminated + * @return void does not return, process is terminated */ function http_status_exit($val, $msg = '') { http_status($val, $msg); @@ -431,10 +429,10 @@ function http_status_exit($val, $msg = '') { } /** - * @brief convert an XML document to a normalised, case-corrected array used by webfinger. + * @brief Convert an XML document to a normalised, case-corrected array used by webfinger. * * @param string|array|SimpleXMLElement $xml_element - * @param int $recursion_depth[in,out] + * @param[in,out] int $recursion_depth * @return NULL|string|array */ function convert_xml_element_to_array($xml_element, &$recursion_depth=0) { @@ -501,14 +499,14 @@ function z_dns_check($h,$check_mx = 0) { } /** - * @brief Validates a given URL + * @brief Validates a given URL. * * Take a URL from the wild, prepend http:// if necessary and check DNS to see * if it's real (or check if is a valid IP address). * * @see z_dns_check() * - * @param string $url[in,out] URL to check + * @param[in,out] string $url URL to check * @return boolean Return true if it's OK, false if something is wrong with it */ function validate_url(&$url) { @@ -593,6 +591,7 @@ function allowed_url($url) { } } } + return $found; } @@ -658,7 +657,7 @@ function allowed_email($email) { -function parse_xml_string($s,$strict = true) { +function parse_xml_string($s, $strict = true) { if($strict) { if(! strstr($s,'<?xml')) return false; @@ -683,14 +682,21 @@ function parse_xml_string($s,$strict = true) { return $x; } - +/** + * @brief Scales an external image. + * + * @param string $s + * @param string $include_link default true + * @param string $scale_replace default false + * @return string + */ function scale_external_images($s, $include_link = true, $scale_replace = false) { // Picture addresses can contain special characters $s = htmlspecialchars_decode($s, ENT_COMPAT); $matches = null; - $c = preg_match_all('/\[([zi])mg(.*?)\](.*?)\[\/[zi]mg\]/ism',$s,$matches,PREG_SET_ORDER); + $c = preg_match_all('/\[([zi])mg(.*?)\](.*?)\[\/[zi]mg\]/ism', $s, $matches, PREG_SET_ORDER); if($c) { require_once('include/photo/photo_driver.php'); @@ -716,19 +722,22 @@ function scale_external_images($s, $include_link = true, $scale_replace = false) $scaled = str_replace($scale_replace[0], $scale_replace[1], $mtch[3]); else $scaled = $mtch[3]; - $i = z_fetch_url($scaled,true); + if(! strpbrk(substr($scaled, 0, 1), 'zhfmt')) + continue; + + $i = z_fetch_url($scaled, true); - $cache = get_config('system','itemcache'); + $cache = get_config('system', 'itemcache'); if (($cache != '') and is_dir($cache)) { - $cachefile = $cache."/".hash("md5", $scaled); + $cachefile = $cache . '/' . hash('md5', $scaled); file_put_contents($cachefile, $i['body']); } // guess mimetype from headers or filename - $type = guess_image_type($mtch[3],$i['header']); - if(strpos($type,'image') === false) + $type = guess_image_type($mtch[3], $i['header']); + if(strpos($type, 'image') === false) continue; if($i['success']) { @@ -743,13 +752,14 @@ function scale_external_images($s, $include_link = true, $scale_replace = false) if($orig_width > 1024 || $orig_height > 1024) { $tag = (($match[1] == 'z') ? 'zmg' : 'img'); + $linktag = (($match[1] == 'z') ? 'zrl' : 'url'); $ph->scaleImage(1024); $new_width = $ph->getWidth(); $new_height = $ph->getHeight(); logger('data: ' . $orig_width . '->' . $new_width . 'w ' . $orig_height . '->' . $new_height . 'h' . ' match: ' . $mtch[0], LOGGER_DEBUG); $s = str_replace($mtch[0],'[' . $tag . '=' . $new_width . 'x' . $new_height. ']' . $scaled . '[/' . $tag . ']' . "\n" . (($include_link) - ? '[zrl=' . $mtch[2] . ']' . t('view full size') . '[/zrl]' . "\n" + ? '[' . $linktag . '=' . $mtch[3] . ']' . t('view full size') . '[/' . $linktag . ']' . "\n" : ''),$s); logger('new string: ' . $s, LOGGER_DEBUG); } @@ -760,7 +770,7 @@ function scale_external_images($s, $include_link = true, $scale_replace = false) // replace the special char encoding - $s = htmlspecialchars($s,ENT_COMPAT,'UTF-8'); + $s = htmlspecialchars($s, ENT_COMPAT, 'UTF-8'); return $s; } @@ -1137,7 +1147,14 @@ function discover_by_url($url, $arr = null) { return true; } -function discover_by_webbie($webbie,$protocol = '') { +/** + * @brief + * + * @param string $webbie + * @param string $protocol (optional) default empty + * @return boolean + */ +function discover_by_webbie($webbie, $protocol = '') { $result = []; @@ -1145,7 +1162,7 @@ function discover_by_webbie($webbie,$protocol = '') { // $webbie = strtolower($webbie); - $x = webfinger_rfc7033($webbie,true); + $x = webfinger_rfc7033($webbie, true); if($x && array_key_exists('links',$x) && $x['links']) { foreach($x['links'] as $link) { if(array_key_exists('rel',$link)) { @@ -1154,7 +1171,7 @@ function discover_by_webbie($webbie,$protocol = '') { // here. if($link['rel'] === PROTOCOL_ZOT && ((! $protocol) || (strtolower($protocol) === 'zot'))) { - logger('discover_by_webbie: zot found for ' . $webbie, LOGGER_DEBUG); + logger('zot found for ' . $webbie, LOGGER_DEBUG); if(array_key_exists('zot',$x) && $x['zot']['success']) { $i = import_xchan($x['zot']); return true; @@ -1174,16 +1191,35 @@ function discover_by_webbie($webbie,$protocol = '') { logger('webfinger: ' . print_r($x,true), LOGGER_DATA, LOG_INFO); - $arr = array('address' => $webbie, 'protocol' => $protocol, 'success' => false, 'webfinger' => $x); + $arr = [ + 'address' => $webbie, + 'protocol' => $protocol, + 'success' => false, + 'webfinger' => $x + ]; + /** + * @hooks discover_channel_webfinger + * Called when performing a webfinger lookup. + * * \e string \b address - The webbie + * * \e string \b protocol + * * \e array \b webfinger - The result from webfinger_rfc7033() + * * \e boolean \b success - The return value, default false + */ call_hooks('discover_channel_webfinger', $arr); if($arr['success']) return true; return false; - } -function webfinger_rfc7033($webbie,$zot = false) { +/** + * @brief Fetch and return a webfinger for a webbie. + * + * @param string $webbie - The webbie + * @param boolean $zot (optional) default false + * @return boolean|string false or associative array from result JSON + */ +function webfinger_rfc7033($webbie, $zot = false) { if(strpos($webbie,'@')) { $lhs = substr($webbie,0,strpos($webbie,'@')); @@ -1195,6 +1231,7 @@ function webfinger_rfc7033($webbie,$zot = false) { if($m) { if($m['scheme'] !== 'https') return false; + $rhs = $m['host'] . (($m['port']) ? ':' . $m['port'] : ''); $resource = urlencode($webbie); } @@ -1207,20 +1244,19 @@ function webfinger_rfc7033($webbie,$zot = false) { // and results in a 406 (Not Acceptable) response, and will also incorrectly produce an XML // document if you use 'application/jrd+json, */*'. We could set this to application/jrd+json, // but some test webfinger servers may not explicitly set the content type and they would be - // blocked. The best compromise until Mastodon is fixed is to remove the Accept header which is - // accomplished by setting it to nothing. + // blocked. The best compromise until Mastodon is fixed is to remove the Accept header which is + // accomplished by setting it to nothing. $counter = 0; - $s = z_fetch_url('https://' . $rhs . '/.well-known/webfinger?f=&resource=' . $resource . (($zot) ? '&zot=1' : ''), + $s = z_fetch_url('https://' . $rhs . '/.well-known/webfinger?f=&resource=' . $resource . (($zot) ? '&zot=1' : ''), false, $counter, [ 'headers' => [ 'Accept:' ] ]); if($s['success']) { - $j = json_decode($s['body'],true); + $j = json_decode($s['body'], true); return($j); } return false; - } function old_webfinger($webbie) { @@ -1429,11 +1465,27 @@ function scrape_feed($url) { -function do_delivery($deliveries) { + + +function do_delivery($deliveries, $force = false) { + + // $force is set if a site that wasn't responding suddenly returns to life. + // Try and shove through everything going to that site while it's responding. if(! (is_array($deliveries) && count($deliveries))) return; + + $x = q("select count(outq_hash) as total from outq where outq_delivered = 0"); + if(intval($x[0]['total']) > intval(get_config('system','force_queue_threshold',300)) && (! $force)) { + logger('immediate delivery deferred.', LOGGER_DEBUG, LOG_INFO); + foreach($deliveries as $d) { + update_queue_item($d); + } + return; + } + + $interval = ((get_config('system','delivery_interval') !== false) ? intval(get_config('system','delivery_interval')) : 2 ); @@ -1584,7 +1636,13 @@ function check_siteallowed($url) { $retvalue = true; $arr = array('url' => $url); - call_hooks('check_siteallowed',$arr); + /** + * @hooks check_siteallowed + * Used to over-ride or bypass the site black/white block lists. + * * \e string \b url + * * \e boolean \b allowed - optional return value set in hook + */ + call_hooks('check_siteallowed', $arr); if(array_key_exists('allowed',$arr)) return $arr['allowed']; @@ -1623,7 +1681,13 @@ function check_channelallowed($hash) { $retvalue = true; $arr = array('hash' => $hash); - call_hooks('check_channelallowed',$arr); + /** + * @hooks check_channelallowed + * Used to over-ride or bypass the channel black/white block lists. + * * \e string \b hash + * * \e boolean \b allowed - optional return value set in hook + */ + call_hooks('check_channelallowed', $arr); if(array_key_exists('allowed',$arr)) return $arr['allowed']; @@ -1712,6 +1776,10 @@ function network_to_name($s) { NETWORK_MYSPACE => t('MySpace'), ); + /** + * @hooks network_to_name + * @deprecated + */ call_hooks('network_to_name', $nets); $search = array_keys($nets); @@ -1723,7 +1791,7 @@ function network_to_name($s) { /** * @brief Send a text email message. * - * @param array $params an assoziative array with: + * @param array $params an associative array with: * * \e string \b fromName name of the sender * * \e string \b fromEmail email of the sender * * \e string \b replyTo replyTo address to direct responses @@ -1754,6 +1822,10 @@ function z_mail($params) { $params['sent'] = false; $params['result'] = false; + /** + * @hooks email_send + * * \e params @see z_mail() + */ call_hooks('email_send', $params); if($params['sent']) { @@ -1901,60 +1973,78 @@ function service_plink($contact, $guid) { $plink = $url . '/channel/' . $handle . '?f=&mid=' . $guid; $x = [ 'xchan' => $contact, 'guid' => $guid, 'url' => $url, 'plink' => $plink ]; + /** + * @hooks service_plink + * * \e array \b xchan + * * \e string \b guid + * * \e string \b url + * * \e string \b plink will get returned + */ call_hooks('service_plink', $x); return $x['plink']; } + +/** + * @brief + * + * @param array $mimeTypes + * @param string $acceptedTypes by default false will use $_SERVER['HTTP_ACCEPT'] + * @return array|NULL + */ function getBestSupportedMimeType($mimeTypes = null, $acceptedTypes = false) { - // Values will be stored in this array + // Values will be stored in this array + $AcceptTypes = []; if($acceptedTypes === false) $acceptedTypes = $_SERVER['HTTP_ACCEPT']; - $AcceptTypes = Array (); - - // Accept header is case insensitive, and whitespace isn’t important - $accept = strtolower(str_replace(' ', '', $acceptedTypes)); - // divide it into parts in the place of a "," - $accept = explode(',', $accept); - foreach ($accept as $a) { - // the default quality is 1. - $q = 1; - // check if there is a different quality - if (strpos($a, ';q=')) { - // divide "mime/type;q=X" into two parts: "mime/type" i "X" - list($a, $q) = explode(';q=', $a); - } - // mime-type $a is accepted with the quality $q - // WARNING: $q == 0 means, that mime-type isn’t supported! - $AcceptTypes[$a] = $q; - } - arsort($AcceptTypes); - - // if no parameter was passed, just return parsed data - if (!$mimeTypes) return $AcceptTypes; - - $mimeTypes = array_map('strtolower', (array)$mimeTypes); - - // let’s check our supported types: - foreach ($AcceptTypes as $mime => $q) { - if ($q && in_array($mime, $mimeTypes)) return $mime; - } - // no mime-type found - return null; -} + // Accept header is case insensitive, and whitespace isn’t important + $accept = strtolower(str_replace(' ', '', $acceptedTypes)); + // divide it into parts in the place of a "," + $accept = explode(',', $accept); + foreach ($accept as $a) { + // the default quality is 1. + $q = 1; + // check if there is a different quality + if (strpos($a, ';q=')) { + // divide "mime/type;q=X" into two parts: "mime/type" i "X" + list($a, $q) = explode(';q=', $a); + } + // mime-type $a is accepted with the quality $q + // WARNING: $q == 0 means, that mime-type isn’t supported! + $AcceptTypes[$a] = $q; + } + arsort($AcceptTypes); + // if no parameter was passed, just return parsed data + if (!$mimeTypes) return $AcceptTypes; -function jsonld_document_loader($url) { + $mimeTypes = array_map('strtolower', (array)$mimeTypes); - // perform caching for jsonld normaliser + // let’s check our supported types: + foreach ($AcceptTypes as $mime => $q) { + if ($q && in_array($mime, $mimeTypes)) return $mime; + } + + // no mime-type found + return null; +} + +/** + * @brief Perform caching for jsonld normaliser. + * + * @param string $url + * @return mixed|boolean|array + */ +function jsonld_document_loader($url) { require_once('library/jsonld/jsonld.php'); $cachepath = 'store/[data]/ldcache'; if(! is_dir($cachepath)) - os_mkdir($cachepath,STORAGE_DEFAULT_PERMISSIONS,true); + os_mkdir($cachepath, STORAGE_DEFAULT_PERMISSIONS, true); $filename = $cachepath . '/' . urlencode($url); if(file_exists($filename) && filemtime($filename) > time() - (12 * 60 * 60)) { @@ -1963,7 +2053,7 @@ function jsonld_document_loader($url) { $r = jsonld_default_document_loader($url); if($r) { - file_put_contents($filename,json_encode($r)); + file_put_contents($filename, json_encode($r)); return $r; } @@ -1973,5 +2063,4 @@ function jsonld_document_loader($url) { } return []; - }
\ No newline at end of file diff --git a/include/photo/photo_driver.php b/include/photo/photo_driver.php index 5eb1f9113..6af753195 100644 --- a/include/photo/photo_driver.php +++ b/include/photo/photo_driver.php @@ -129,7 +129,14 @@ abstract class photo_driver { return $this->types[$this->getType()]; } - public function scaleImage($max) { + /** + * @brief scale image + * int $max maximum pixel size in either dimension + * boolean $float_height - if true allow height to float to any length on tall images, + * constraining only the width + */ + + public function scaleImage($max, $float_height = true) { if(!$this->is_valid()) return FALSE; @@ -146,7 +153,7 @@ abstract class photo_driver { // very tall image (greater than 16:9) // constrain the width - let the height float. - if((($height * 9) / 16) > $width) { + if(((($height * 9) / 16) > $width) && ($float_height)) { $dest_width = $max; $dest_height = intval(( $height * $max ) / $width); } @@ -173,7 +180,7 @@ abstract class photo_driver { // very tall image (greater than 16:9) // but width is OK - don't do anything - if((($height * 9) / 16) > $width) { + if(((($height * 9) / 16) > $width) && ($float_height)) { $dest_width = $width; $dest_height = $height; } @@ -241,74 +248,94 @@ abstract class photo_driver { } + /** + * @brief reads exif data from filename + */ + public function exif($filename) { - public function orient($filename) { - /** - * This function is a bit unusual, because it is operating on a file, but you must - * first create an image from that file to initialise the type and check validity - * of the image. - */ - - if(! $this->is_valid()) - return false; - - if((! function_exists('exif_read_data')) || ($this->getType() !== 'image/jpeg')) + if((! function_exists('exif_read_data')) + || (! in_array($this->getType(), [ 'image/jpeg' , 'image/tiff'] ))) { return false; + } - $exif = @exif_read_data($filename,null,true); - - if($exif) { - $ort = $exif['IFD0']['Orientation']; - - switch($ort) - { - case 1: // nothing - break; + /* + * PHP 7.2 allows you to use a stream resource, which should reduce/avoid + * memory exhaustion on large images. + */ - case 2: // horizontal flip - $this->flip(); - break; + if(version_compare(PHP_VERSION,'7.2.0') >= 0) { + $f = @fopen($filename,'rb'); + } + else { + $f = $filename; + } - case 3: // 180 rotate left - $this->rotate(180); - break; + if($f) { + return @exif_read_data($f); + } - case 4: // vertical flip - $this->flip(false, true); - break; + return false; + } - case 5: // vertical flip + 90 rotate right - $this->flip(false, true); - $this->rotate(-90); - break; + /** + * @brief orients current image based on exif orientation information + */ - case 6: // 90 rotate right - $this->rotate(-90); - break; + public function orient($exif) { - case 7: // horizontal flip + 90 rotate right - $this->flip(); - $this->rotate(-90); - break; + if(! ($this->is_valid() && $exif)) { + return false; + } - case 8: // 90 rotate left - $this->rotate(90); - break; - } + $ort = $exif['IFD0']['Orientation']; - return $exif; + if(! $ort) { + return false; + } + switch($ort) { + case 1: // nothing + break; + case 2: // horizontal flip + $this->flip(); + break; + case 3: // 180 rotate left + $this->rotate(180); + break; + case 4: // vertical flip + $this->flip(false, true); + break; + case 5: // vertical flip + 90 rotate right + $this->flip(false, true); + $this->rotate(-90); + break; + case 6: // 90 rotate right + $this->rotate(-90); + break; + case 7: // horizontal flip + 90 rotate right + $this->flip(); + $this->rotate(-90); + break; + case 8: // 90 rotate left + $this->rotate(90); + break; + default: + break; } - - return false; + return true; } public function save($arr) { + if(! $this->is_valid()) { + logger('attempt to store invalid photo.'); + return false; + } + $p = array(); $p['aid'] = ((intval($arr['aid'])) ? intval($arr['aid']) : 0); @@ -331,6 +358,8 @@ abstract class photo_driver { $p['os_path'] = $arr['os_path']; $p['os_syspath'] = ((array_key_exists('os_syspath',$arr)) ? $arr['os_syspath'] : ''); $p['display_path'] = (($arr['display_path']) ? $arr['display_path'] : ''); + $p['width'] = (($arr['width']) ? $arr['width'] : $this->getWidth()); + $p['height'] = (($arr['height']) ? $arr['height'] : $this->getHeight()); if(! intval($p['imgscale'])) logger('save: ' . print_r($arr,true), LOGGER_DATA); @@ -378,8 +407,8 @@ abstract class photo_driver { dbesc(basename($p['filename'])), dbesc($this->getType()), dbesc($p['album']), - intval($this->getHeight()), - intval($this->getWidth()), + intval($p['height']), + intval($p['width']), (intval($p['os_storage']) ? dbescbin($p['os_syspath']) : dbescbin($this->imageString())), intval($p['os_storage']), intval(strlen($this->imageString())), @@ -409,8 +438,8 @@ abstract class photo_driver { dbesc(basename($p['filename'])), dbesc($this->getType()), dbesc($p['album']), - intval($this->getHeight()), - intval($this->getWidth()), + intval($p['height']), + intval($p['width']), (intval($p['os_storage']) ? dbescbin($p['os_syspath']) : dbescbin($this->imageString())), intval($p['os_storage']), intval(strlen($this->imageString())), @@ -426,6 +455,7 @@ abstract class photo_driver { dbesc($p['deny_gid']) ); } + logger('photo save ' . $p['imgscale'] . ' returned ' . intval($r)); return $r; } diff --git a/include/photos.php b/include/photos.php index 503a725cd..b1391f284 100644 --- a/include/photos.php +++ b/include/photos.php @@ -10,14 +10,13 @@ require_once('include/photo/photo_driver.php'); require_once('include/text.php'); /** - * @brief + * @brief Upload a photo. * * @param array $channel * @param array $observer * @param array $args * @return array */ - function photo_upload($channel, $observer, $args) { $ret = array('success' => false); @@ -63,27 +62,52 @@ function photo_upload($channel, $observer, $args) { $ac = $acl->get(); + $width = $height = 0; + + if($args['getimagesize']) { + $width = $args['getimagesize'][0]; + $height = $args['getimagesize'][1]; + } + + $os_storage = 0; + $max_thumb = get_config('system','max_thumbnail',1600); + if($args['os_syspath'] && $args['getimagesize']) { - if($args['getimagesize'][0] > 1600 || $args['getimagesize'][1] > 1600) { + if($args['getimagesize'][0] > $max_thumb || $args['getimagesize'][1] > $max_thumb) { $imagick_path = get_config('system','imagick_convert_path'); if($imagick_path && @file_exists($imagick_path)) { $tmp_name = $args['os_syspath'] . '-001'; - $newsize = photo_calculate_1600_scale($args['getimagesize']); - exec($imagick_path . ' ' . $args['os_syspath'] . ' -resize ' . $newsize . '^ ' . $tmp_name); + $newsize = photo_calculate_scale(array_merge($args['getimagesize'],['max' => $max_thumb])); + $cmd = $imagick_path . ' ' . escapeshellarg(PROJECT_BASE . '/' . $args['os_syspath']) . ' -thumbnail ' . $newsize . ' ' . escapeshellarg(PROJECT_BASE . '/' . $tmp_name); + // logger('imagick thumbnail command: ' . $cmd); + for($x = 0; $x < 4; $x ++) { + exec($cmd); + if(! file_exists($tmp_name)) { + logger('imagick scale failed. Retrying.'); + continue; + } + } + if(! file_exists($tmp_name)) { + logger('imagick scale failed. Abort.'); + return $ret; + } + $imagedata = @file_get_contents($tmp_name); + $filesize = @filesize($args['os_syspath']); @unlink($tmp_name); } else { $imagedata = @file_get_contents($args['os_syspath']); + $filesize = strlen($imagedata); } } else { $imagedata = @file_get_contents($args['os_syspath']); + $filesize = strlen($imagedata); } $filename = $args['filename']; - $filesize = strlen($imagedata); // this is going to be deleted if it exists $src = '/tmp/deletemenow'; $type = $args['getimagesize']['mime']; @@ -118,25 +142,33 @@ function photo_upload($channel, $observer, $args) { if (! $type) $type=guess_image_type($filename); - logger('photo_upload: received file: ' . $filename . ' as ' . $src . ' ('. $type . ') ' . $filesize . ' bytes', LOGGER_DEBUG); + logger('Received file: ' . $filename . ' as ' . $src . ' ('. $type . ') ' . $filesize . ' bytes', LOGGER_DEBUG); $maximagesize = get_config('system','maximagesize'); if (($maximagesize) && ($filesize > $maximagesize)) { $ret['message'] = sprintf ( t('Image exceeds website size limit of %lu bytes'), $maximagesize); @unlink($src); - call_hooks('photo_upload_end',$ret); + /** + * @hooks photo_upload_end + * Called when a photo upload has been processed. + */ + call_hooks('photo_upload_end', $ret); return $ret; } if (! $filesize) { $ret['message'] = t('Image file is empty.'); @unlink($src); - call_hooks('photo_post_end',$ret); + /** + * @hooks photo_post_end + * Called after uploading a photo. + */ + call_hooks('photo_post_end', $ret); return $ret; } - logger('photo_upload: loading the contents of ' . $src , LOGGER_DEBUG); + logger('Loading the contents of ' . $src , LOGGER_DEBUG); $imagedata = @file_get_contents($src); } @@ -149,7 +181,11 @@ function photo_upload($channel, $observer, $args) { if (($r) && ($limit !== false) && (($r[0]['total'] + strlen($imagedata)) > $limit)) { $ret['message'] = upgrade_message(); @unlink($src); - call_hooks('photo_post_end',$ret); + /** + * @hooks photo_post_end + * Called after uploading a photo. + */ + call_hooks('photo_post_end', $ret); return $ret; } @@ -157,13 +193,23 @@ function photo_upload($channel, $observer, $args) { if (! $ph->is_valid()) { $ret['message'] = t('Unable to process image'); - logger('photo_upload: unable to process image'); + logger('unable to process image'); @unlink($src); - call_hooks('photo_upload_end',$ret); + /** + * @hooks photo_upload_end + * Called when a photo upload has been processed. + */ + call_hooks('photo_upload_end', $ret); return $ret; } - $exif = $ph->orient(($args['os_syspath']) ? $args['os_syspath'] : $src); + // obtain exif data from the source file if present + + $exif = $ph->exif(($args['os_syspath']) ? $args['os_syspath'] : $src); + + if($exif) { + $ph->orient($exif); + } @unlink($src); @@ -173,8 +219,10 @@ function photo_upload($channel, $observer, $args) { if ($max_length > 0) $ph->scaleImage($max_length); - $width = $ph->getWidth(); - $height = $ph->getHeight(); + if(! $width) + $width = $ph->getWidth(); + if(! $height) + $height = $ph->getHeight(); $smallest = 0; @@ -188,6 +236,7 @@ function photo_upload($channel, $observer, $args) { $p = array('aid' => $account_id, 'uid' => $channel_id, 'xchan' => $visitor, 'resource_id' => $photo_hash, 'filename' => $filename, 'album' => $album, 'imgscale' => 0, 'photo_usage' => PHOTO_NORMAL, + 'width' => $width, 'height' => $height, 'allow_cid' => $ac['allow_cid'], 'allow_gid' => $ac['allow_gid'], 'deny_cid' => $ac['deny_cid'], 'deny_gid' => $ac['deny_gid'], 'os_storage' => $os_storage, 'os_syspath' => $args['os_syspath'], @@ -209,14 +258,16 @@ function photo_upload($channel, $observer, $args) { 'rel' => 'alternate', 'type' => 'text/html', 'href' => z_root() . '/photo/' . $photo_hash . '-0.' . $ph->getExt(), - 'width' => $ph->getWidth(), - 'height' => $ph->getHeight() + 'width' => $width, + 'height' => $height ); if(! $r0) $errors = true; unset($p['os_storage']); unset($p['os_syspath']); + unset($p['width']); + unset($p['height']); if(($width > 1024 || $height > 1024) && (! $errors)) $ph->scaleImage(1024); @@ -269,8 +320,12 @@ function photo_upload($channel, $observer, $args) { intval($channel_id) ); $ret['message'] = t('Photo storage failed.'); - logger('photo_upload: photo store failed.'); - call_hooks('photo_upload_end',$ret); + logger('Photo store failed.'); + /** + * @hooks photo_upload_end + * Called when a photo upload has been processed. + */ + call_hooks('photo_upload_end', $ret); return $ret; } @@ -296,8 +351,6 @@ function photo_upload($channel, $observer, $args) { $width = $link[1]['width']; $height = $link[1]['height']; $tag = (($r1) ? '[zmg=' . $width . 'x' . $height . ']' : '[zmg]'); - - } else { $scale = 2; @@ -355,7 +408,6 @@ function photo_upload($channel, $observer, $args) { $item['target'] = json_encode($target); $force = true; - } $r = q("select id, edited from item where mid = '%s' and uid = %d limit 1", dbesc($item['mid']), @@ -423,7 +475,6 @@ function photo_upload($channel, $observer, $args) { $arr['item_private'] = 1; - $result = item_store($arr,false,$deliver); $item_id = $result['item_id']; @@ -437,15 +488,19 @@ function photo_upload($channel, $observer, $args) { $ret['resource_id'] = $photo_hash; $ret['photoitem_id'] = $item_id; - call_hooks('photo_upload_end',$ret); + /** + * @hooks photo_upload_end + * Called when a photo upload has been processed. + */ + call_hooks('photo_upload_end', $ret); return $ret; } -function photo_calculate_1600_scale($arr) { +function photo_calculate_scale($arr) { - $max = 1600; + $max = $arr['max']; $width = $arr[0]; $height = $arr[1]; @@ -502,10 +557,8 @@ function photo_calculate_1600_scale($arr) { } return $dest_width . 'x' . $dest_height; - } - /** * @brief Returns a list with all photo albums observer is allowed to see. * @@ -520,7 +573,6 @@ function photo_calculate_1600_scale($arr) { * * \e boolean \b success * * \e array \b albums */ - function photos_albums_list($channel, $observer, $sort_key = 'display_path', $direction = 'asc') { $channel_id = $channel['channel_id']; @@ -613,14 +665,13 @@ function photos_album_widget($channelx,$observer,$sortkey = 'display_path',$dire } /** - * @brief + * @brief Return an array of photos. * * @param array $channel * @param array $observer - * @param string $album default empty + * @param string $album (optional) default empty * @return boolean|array */ - function photos_list_photos($channel, $observer, $album = '') { $channel_id = $channel['channel_id']; @@ -657,21 +708,20 @@ function photos_list_photos($channel, $observer, $album = '') { * @brief Check if given photo album exists in channel. * * @param int $channel_id id of the channel + * @param string $observer_hash * @param string $album name of the album * @return boolean */ - - function photos_album_exists($channel_id, $observer_hash, $album) { - $sql_extra = permissions_sql($channel_id,$observer_hash); + $sql_extra = permissions_sql($channel_id, $observer_hash); $r = q("SELECT folder, hash, is_dir, filename, os_path, display_path FROM attach WHERE hash = '%s' AND is_dir = 1 AND uid = %d $sql_extra limit 1", dbesc($album), intval($channel_id) ); - // partial backward compatibility with Hubzilla < 2.4 when we used the filename only + // partial backward compatibility with Hubzilla < 2.4 when we used the filename only // (ambiguous which would get chosen if you had two albums of the same name in different directories) if(!$r && ctype_xdigit($album)) { $r = q("SELECT folder, hash, is_dir, filename, os_path, display_path FROM attach WHERE filename = '%s' AND is_dir = 1 AND uid = %d $sql_extra limit 1", @@ -693,7 +743,6 @@ function photos_album_exists($channel_id, $observer_hash, $album) { * @param string $newname The new name of the album * @return bool|array */ - function photos_album_rename($channel_id, $oldname, $newname) { return q("UPDATE photo SET album = '%s' WHERE album = '%s' AND uid = %d", dbesc($newname), @@ -702,17 +751,14 @@ function photos_album_rename($channel_id, $oldname, $newname) { ); } - - /** * @brief * * @param int $channel_id * @param string $album - * @param string $remote_xchan + * @param string $remote_xchan (optional) default empty * @return string|boolean */ - function photos_album_get_db_idstr($channel_id, $album, $remote_xchan = '') { if($remote_xchan) { @@ -746,15 +792,13 @@ function photos_album_get_db_idstr($channel_id, $album, $remote_xchan = '') { * @param array $channel * @param string $creator_hash * @param array $photo - * @param boolean $visible default false + * @param boolean $visible (optional) default false * @return int item_id */ - function photos_create_item($channel, $creator_hash, $photo, $visible = false) { // Create item container - $item_hidden = (($visible) ? 0 : 1 ); $mid = item_message_id(); @@ -794,36 +838,36 @@ function photos_create_item($channel, $creator_hash, $photo, $visible = false) { function getGps($exifCoord, $hemi) { - $degrees = count($exifCoord) > 0 ? gps2Num($exifCoord[0]) : 0; - $minutes = count($exifCoord) > 1 ? gps2Num($exifCoord[1]) : 0; - $seconds = count($exifCoord) > 2 ? gps2Num($exifCoord[2]) : 0; + $degrees = count($exifCoord) > 0 ? gps2Num($exifCoord[0]) : 0; + $minutes = count($exifCoord) > 1 ? gps2Num($exifCoord[1]) : 0; + $seconds = count($exifCoord) > 2 ? gps2Num($exifCoord[2]) : 0; - $flip = ($hemi == 'W' or $hemi == 'S') ? -1 : 1; + $flip = ($hemi == 'W' or $hemi == 'S') ? -1 : 1; - return floatval($flip * ($degrees + ($minutes / 60) + ($seconds / 3600))); + return floatval($flip * ($degrees + ($minutes / 60) + ($seconds / 3600))); } function getGpstimestamp($exifCoord) { - $hours = count($exifCoord) > 0 ? gps2Num($exifCoord[0]) : 0; - $minutes = count($exifCoord) > 1 ? gps2Num($exifCoord[1]) : 0; - $seconds = count($exifCoord) > 2 ? gps2Num($exifCoord[2]) : 0; + $hours = count($exifCoord) > 0 ? gps2Num($exifCoord[0]) : 0; + $minutes = count($exifCoord) > 1 ? gps2Num($exifCoord[1]) : 0; + $seconds = count($exifCoord) > 2 ? gps2Num($exifCoord[2]) : 0; - return sprintf('%02d:%02d:%02d',$hours,$minutes,$seconds); + return sprintf('%02d:%02d:%02d', $hours, $minutes, $seconds); } function gps2Num($coordPart) { - $parts = explode('/', $coordPart); + $parts = explode('/', $coordPart); - if (count($parts) <= 0) - return 0; + if (count($parts) <= 0) + return 0; - if (count($parts) == 1) - return $parts[0]; + if (count($parts) == 1) + return $parts[0]; - return floatval($parts[0]) / floatval($parts[1]); + return floatval($parts[0]) / floatval($parts[1]); } @@ -835,7 +879,7 @@ function photo_profile_setperms($channel_id,$resource_id,$profile_id) { $r = q("select profile_guid, is_default from profile where id = %d and uid = %d limit 1", dbesc($profile_id), intval($channel_id) - ); + ); if(! $r) return; @@ -844,26 +888,26 @@ function photo_profile_setperms($channel_id,$resource_id,$profile_id) { $profile_guid = $r[0]['profile_guid']; if($is_default) { - $r = q("update photo set allow_cid = '', allow_gid = '', deny_cid = '', deny_gid = '' + $r = q("update photo set allow_cid = '', allow_gid = '', deny_cid = '', deny_gid = '' where resource_id = '%s' and uid = %d", dbesc($resource_id), intval($channel_id) ); - $r = q("update attach set allow_cid = '', allow_gid = '', deny_cid = '', deny_gid = '' + $r = q("update attach set allow_cid = '', allow_gid = '', deny_cid = '', deny_gid = '' where hash = '%s' and uid = %d", dbesc($resource_id), intval($channel_id) ); } else { - $r = q("update photo set allow_cid = '', allow_gid = '%s', deny_cid = '', deny_gid = '' + $r = q("update photo set allow_cid = '', allow_gid = '%s', deny_cid = '', deny_gid = '' where resource_id = '%s' and uid = %d", dbesc('<vp.' . $profile_guid . '>'), dbesc($resource_id), intval($channel_id) ); - $r = q("update attach set allow_cid = '', allow_gid = '%s', deny_cid = '', deny_gid = '' + $r = q("update attach set allow_cid = '', allow_gid = '%s', deny_cid = '', deny_gid = '' where hash = '%s' and uid = %d", dbesc('<vp.' . $profile_guid . '>'), dbesc($resource_id), @@ -872,71 +916,72 @@ function photo_profile_setperms($channel_id,$resource_id,$profile_id) { } } +/** + * @brief + * + * @param int $uid + * @param int|string $profileid + */ function profile_photo_set_profile_perms($uid, $profileid = 0) { $allowcid = ''; - if($profileid) { + $r = q("SELECT photo, profile_guid, id, is_default, uid + FROM profile WHERE uid = %d and ( profile.id = %d OR profile.profile_guid = '%s') LIMIT 1", + intval($uid), + intval($profileid), + dbesc($profileid) + ); + } + else { + logger('Resetting permissions on default-profile-photo for user'.local_channel()); - $r = q("SELECT photo, profile_guid, id, is_default, uid - FROM profile WHERE uid = %d and ( profile.id = %d OR profile.profile_guid = '%s') LIMIT 1", - intval($uid), - intval($profileid), - dbesc($profileid) - ); - } - else { - logger('Resetting permissions on default-profile-photo for user'.local_channel()); + $r = q("SELECT photo, profile_guid, id, is_default, uid FROM profile + WHERE profile.uid = %d AND is_default = 1 LIMIT 1", + intval($uid) + ); //If no profile is given, we update the default profile + } + if(! $r) + return; - $r = q("SELECT photo, profile_guid, id, is_default, uid FROM profile - WHERE profile.uid = %d AND is_default = 1 LIMIT 1", - intval($uid) - ); //If no profile is given, we update the default profile - } - if(! $r) - return; - - $profile = $r[0]; - - if($profile['id'] && $profile['photo']) { - preg_match("@\w*(?=-\d*$)@i", $profile['photo'], $resource_id); - $resource_id = $resource_id[0]; - - if (! intval($profile['is_default'])) { - $r0 = q("SELECT channel_hash FROM channel WHERE channel_id = %d LIMIT 1", - intval($uid) - ); - //Should not be needed in future. Catches old int-profile-ids. - $r1 = q("SELECT abook.abook_xchan FROM abook WHERE abook_profile = '%d' ", - intval($profile['id']) - ); - $r2 = q("SELECT abook.abook_xchan FROM abook WHERE abook_profile = '%s'", - dbesc($profile['profile_guid']) - ); - $allowcid = "<" . $r0[0]['channel_hash'] . ">"; - foreach ($r1 as $entry) { - $allowcid .= "<" . $entry['abook_xchan'] . ">"; - } - foreach ($r2 as $entry) { - $allowcid .= "<" . $entry['abook_xchan'] . ">"; - } + $profile = $r[0]; - q("UPDATE photo SET allow_cid = '%s' WHERE resource_id = '%s' AND uid = %d", - dbesc($allowcid), - dbesc($resource_id), - intval($uid) - ); + if($profile['id'] && $profile['photo']) { + preg_match("@\w*(?=-\d*$)@i", $profile['photo'], $resource_id); + $resource_id = $resource_id[0]; + if (! intval($profile['is_default'])) { + $r0 = q("SELECT channel_hash FROM channel WHERE channel_id = %d LIMIT 1", + intval($uid) + ); + //Should not be needed in future. Catches old int-profile-ids. + $r1 = q("SELECT abook.abook_xchan FROM abook WHERE abook_profile = '%d' ", + intval($profile['id']) + ); + $r2 = q("SELECT abook.abook_xchan FROM abook WHERE abook_profile = '%s'", + dbesc($profile['profile_guid']) + ); + $allowcid = "<" . $r0[0]['channel_hash'] . ">"; + foreach ($r1 as $entry) { + $allowcid .= "<" . $entry['abook_xchan'] . ">"; } - else { - //Reset permissions on default profile picture to public - q("UPDATE photo SET allow_cid = '' WHERE photo_usage = %d AND uid = %d", - intval(PHOTO_PROFILE), - intval($uid) - ); + foreach ($r2 as $entry) { + $allowcid .= "<" . $entry['abook_xchan'] . ">"; } - } - return; + q("UPDATE photo SET allow_cid = '%s' WHERE resource_id = '%s' AND uid = %d", + dbesc($allowcid), + dbesc($resource_id), + intval($uid) + ); + } + else { + //Reset permissions on default profile picture to public + q("UPDATE photo SET allow_cid = '' WHERE photo_usage = %d AND uid = %d", + intval(PHOTO_PROFILE), + intval($uid) + ); + } } +} diff --git a/include/plugin.php b/include/plugin.php index db20152ea..379d8e799 100755 --- a/include/plugin.php +++ b/include/plugin.php @@ -7,7 +7,7 @@ /** - * @brief unloads an addon. + * @brief Unloads an addon. * * @param string $plugin name of the addon */ @@ -22,7 +22,7 @@ function unload_plugin($plugin){ } /** - * @brief uninstalls an addon. + * @brief Uninstalls an addon. * * @param string $plugin name of the addon * @return boolean @@ -110,6 +110,13 @@ function load_plugin($plugin) { } } + +/** + * @brief Check if addon is installed. + * + * @param string $name + * @return boolean + */ function plugin_is_installed($name) { $r = q("select aname from addon where aname = '%s' and installed = 1 limit 1", dbesc($name) @@ -121,8 +128,9 @@ function plugin_is_installed($name) { } -// reload all updated plugins - +/** + * @brief Reload all updated plugins. + */ function reload_plugins() { $plugins = get_config('system', 'addon'); if(strlen($plugins)) { @@ -167,13 +175,18 @@ function reload_plugins() { } } + +/** + * @brief Get a list of non hidden addons. + * + * @return array + */ function visible_plugin_list() { $r = q("select * from addon where hidden = 0 order by aname asc"); return(($r) ? ids_to_array($r,'aname') : array()); } - /** * @brief registers a hook. * @@ -282,7 +295,7 @@ function insert_hook($hook, $fn, $version = 0, $priority = 0) { * the provided data. * * @param string $name of the hook to call - * @param string|array &$data to transmit to the callback handler + * @param[in,out] string|array &$data to transmit to the callback handler */ function call_hooks($name, &$data = null) { $a = 0; @@ -414,8 +427,8 @@ function check_plugin_versions($info) { || stristr($info['serverroles'],'any') || stristr($info['serverroles'],$role))) { logger('serverrole limit: ' . $info['name'],LOGGER_NORMAL,LOG_WARNING); - return false; + return false; } } @@ -447,8 +460,6 @@ function check_plugin_versions($info) { } - - /** * @brief Parse theme comment in search of theme infos. * @@ -532,7 +543,7 @@ function get_theme_info($theme){ * * The screenshot is expected as view/theme/$theme/img/screenshot.[png|jpg]. * - * @param sring $theme The name of the theme + * @param string $theme The name of the theme * @return string */ function get_theme_screenshot($theme) { @@ -626,15 +637,16 @@ function format_css_if_exists($source) { } } -/* +/** * This basically calculates the baseurl. We have other functions to do that, but * there was an issue with script paths and mixed-content whose details are arcane * and perhaps lost in the message archives. The short answer is that we're ignoring * the URL which we are "supposed" to use, and generating script paths relative to * the URL which we are currently using; in order to ensure they are found and aren't * blocked due to mixed content issues. + * + * @return string */ - function script_path() { if(x($_SERVER,'HTTPS') && $_SERVER['HTTPS']) $scheme = 'https'; @@ -659,6 +671,7 @@ function script_path() { else { return z_root(); } + return $scheme . '://' . $hostname; } @@ -675,10 +688,13 @@ function head_remove_js($src, $priority = 0) { unset(App::$js_sources[$priority][$index]); } -// We should probably try to register main.js with a high priority, but currently we handle it -// separately and put it at the end of the html head block in case any other javascript is -// added outside the head_add_js construct. - +/** + * We should probably try to register main.js with a high priority, but currently + * we handle it separately and put it at the end of the html head block in case + * any other javascript is added outside the head_add_js construct. + * + * @return string + */ function head_get_js() { $str = ''; @@ -694,6 +710,7 @@ function head_get_js() { } } } + return $str; } @@ -703,6 +720,7 @@ function head_get_main_js() { if(count($sources)) foreach($sources as $source) $str .= format_js_if_exists($source,true); + return $str; } @@ -716,7 +734,7 @@ function format_js_if_exists($source) { if(substr($source,0,2) === '//') { $path_prefix = ''; } - } + } else { // It's a file from the theme $path = '/' . theme_include($source); @@ -781,12 +799,16 @@ function get_markup_template($s, $root = '') { return $template; } +/** + * @brief + * + * @param string $folder + * @return boolean|string + */ +function folder_exists($folder) { + // Get canonicalized absolute pathname + $path = realpath($folder); -function folder_exists($folder) -{ - // Get canonicalized absolute pathname - $path = realpath($folder); - - // If it exist, check if it's a directory - return (($path !== false) && is_dir($path)) ? $path : false; + // If it exist, check if it's a directory + return (($path !== false) && is_dir($path)) ? $path : false; } diff --git a/include/queue_fn.php b/include/queue_fn.php index c9179b953..5fb0d5f1e 100644 --- a/include/queue_fn.php +++ b/include/queue_fn.php @@ -42,7 +42,7 @@ function update_queue_item($id, $add_priority = 0) { $next = datetime_convert('UTC','UTC','now + 1 hour'); } else { - $next = datetime_convert('UTC','UTC','now + 15 minutes'); + $next = datetime_convert('UTC','UTC','now + ' . intval($add_priority) . ' minutes'); } q("UPDATE outq SET outq_updated = '%s', @@ -158,6 +158,8 @@ function queue_deliver($outq, $immediate = false) { } } + + $arr = array('outq' => $outq, 'base' => $base, 'handled' => false, 'immediate' => $immediate); call_hooks('queue_deliver',$arr); if($arr['handled']) @@ -198,14 +200,15 @@ function queue_deliver($outq, $immediate = false) { } } if($piled_up) { - do_delivery($piled_up); + // call do_delivery() with the force flag + do_delivery($piled_up, true); } } } else { logger('deliver: queue post returned ' . $result['return_code'] . ' from ' . $outq['outq_posturl'],LOGGER_DEBUG); - update_queue_item($outq['outq_posturl']); + update_queue_item($outq['outq_hash'],10); } return; } diff --git a/include/security.php b/include/security.php index 450cc4f69..8b7e7d076 100644 --- a/include/security.php +++ b/include/security.php @@ -264,6 +264,7 @@ function change_channel($change_channel) { App::set_channel($r[0]); $_SESSION['theme'] = $r[0]['channel_theme']; $_SESSION['mobile_theme'] = get_pconfig(local_channel(),'system', 'mobile_theme'); + $_SESSION['cloud_tiles'] = get_pconfig(local_channel(),'system', 'cloud_tiles'); date_default_timezone_set($r[0]['channel_timezone']); $ret = $r[0]; } diff --git a/include/socgraph.php b/include/socgraph.php index a5b5d1378..26446d9c7 100644 --- a/include/socgraph.php +++ b/include/socgraph.php @@ -178,9 +178,11 @@ function poco_load($xchan = '', $url = null) { ); if(! $r) { - q("insert into xlink ( xlink_xchan, xlink_link, xlink_updated, xlink_static ) values ( '%s', '%s', '%s', 0 ) ", + q("insert into xlink ( xlink_xchan, xlink_link, xlink_rating, xlink_rating_text, xlink_updated, xlink_static ) values ( '%s', '%s', %d, '%s', '%s', 0 ) ", dbesc($xchan), dbesc($hash), + intval(0), + dbesc(''), dbesc(datetime_convert()) ); } @@ -385,7 +387,7 @@ function poco($a,$extended = false) { $sql_extra = " and abook_self = 0 "; if($cid) - $sql_extra = sprintf(" and abook_id = %d and abook_hidden = 0 ",intval($cid)); + $sql_extra = sprintf(" and abook_id = %d and abook_hidden = 0 and abook_pending = 0 ",intval($cid)); if($system_mode) { $r = q("SELECT count(*) as total from abook where abook_self = 1 diff --git a/include/taxonomy.php b/include/taxonomy.php index 23acaa24d..a646df28c 100644 --- a/include/taxonomy.php +++ b/include/taxonomy.php @@ -55,18 +55,20 @@ function term_item_parent_query($uid,$table,$s,$type = TERM_UNKNOWN, $type2 = '' $s = str_replace('*','%',$s); if($type2) { - $r = q("select parent from item left join term on term.oid = item.id where term.ttype in (%d, %d) and term.term like '%s' and term.uid = %d and term.otype = 1", + $r = q("select parent from item left join term on term.oid = item.id where term.ttype in (%d, %d) and term.term like '%s' and term.uid = %d and term.otype = 1 and item.verb != '%s'", intval($type), intval($type2), dbesc($s), - intval($uid) + intval($uid), + dbesc(ACTIVITY_UPDATE) ); } else { - $r = q("select parent from item left join term on term.oid = item.id where term.ttype = %d and term.term like '%s' and term.uid = %d and term.otype = 1", + $r = q("select parent from item left join term on term.oid = item.id where term.ttype = %d and term.term like '%s' and term.uid = %d and term.otype = 1 and item.verb != '%s'", intval($type), dbesc($s), - intval($uid) + intval($uid), + dbesc(ACTIVITY_UPDATE) ); } @@ -253,23 +255,86 @@ function card_tagadelic($uid, $count = 0, $authors = '', $owner = '', $flags = 0 } +function article_tagadelic($uid, $count = 0, $authors = '', $owner = '', $flags = 0, $restrict = 0, $type = TERM_CATEGORY) { + require_once('include/security.php'); + if(! perm_is_allowed($uid,get_observer_hash(),'view_pages')) + return array(); -function dir_tagadelic($count = 0) { - + $item_normal = item_normal(); + $sql_options = item_permissions_sql($uid); $count = intval($count); + if($flags) { + if($flags === 'wall') + $sql_options .= " and item_wall = 1 "; + } + + if($authors) { + if(! is_array($authors)) + $authors = array($authors); + + stringify_array_elms($authors,true); + $sql_options .= " and author_xchan in (" . implode(',',$authors) . ") "; + } + + if($owner) { + $sql_options .= " and owner_xchan = '" . dbesc($owner) . "' "; + } + + // Fetch tags - $r = q("select xtag_term as term, count(xtag_term) as total from xtag where xtag_flags = 0 - group by xtag_term order by total desc %s", + $r = q("select term, count(term) as total from term left join item on term.oid = item.id + where term.uid = %d and term.ttype = %d + and otype = %d and item_type = %d and item_private = 0 + $sql_options $item_normal + group by term order by total desc %s", + intval($uid), + intval($type), + intval(TERM_OBJ_POST), + intval($restrict), ((intval($count)) ? "limit $count" : '') ); if(! $r) return array(); + return Zotlabs\Text\Tagadelic::calc($r); + +} + + + + + +function dir_tagadelic($count = 0) { + + $count = intval($count); + + $dirmode = get_config('system','directory_mode'); + + if($dirmode == DIRECTORY_MODE_STANDALONE) { + // Fetch tags + $r = q("select xtag_term as term, count(xtag_term) as total from xtag + left join hubloc on xtag_hash = hubloc_hash + where xtag_flags = 0 and hubloc_url = '%s' + group by xtag_term order by total desc %s", + dbesc(z_root()), + ((intval($count)) ? "limit $count" : '') + ); + } + else { + // Fetch tags + $r = q("select xtag_term as term, count(xtag_term) as total from xtag where xtag_flags = 0 + group by xtag_term order by total desc %s", + ((intval($count)) ? "limit $count" : '') + ); + } + if(! $r) + return array(); + return Zotlabs\Text\Tagadelic::calc($r); @@ -395,6 +460,27 @@ function card_catblock($uid,$count = 0,$authors = '',$owner = '', $flags = 0,$re } +function article_catblock($uid,$count = 0,$authors = '',$owner = '', $flags = 0,$restrict = 0,$type = TERM_CATEGORY) { + $o = ''; + + $r = article_tagadelic($uid,$count,$authors,$owner,$flags,$restrict,$type); + + if($r) { + $c = q("select channel_address from channel where channel_id = %d limit 1", + intval($uid) + ); + + $o = '<div class="tagblock widget"><h3>' . t('Categories') . '</h3><div class="tags" align="center">'; + foreach($r as $rr) { + $o .= '<a href="articles/' . $c[0]['channel_address']. '?f=&cat=' . urlencode($rr[0]).'" class="tag'.$rr[2].'">'.$rr[0].'</a> ' . "\r\n"; + } + $o .= '</div></div>'; + } + + return $o; +} + + function dir_tagblock($link,$r) { $o = ''; diff --git a/include/text.php b/include/text.php index c74e515d2..107efe0cb 100644 --- a/include/text.php +++ b/include/text.php @@ -1679,9 +1679,9 @@ function prepare_text($text, $content_type = 'text/bbcode', $cache = false) { require_once('include/bbcode.php'); if(stristr($text,'[nosmile]')) - $s = bbcode($text,false,true,$cache); + $s = bbcode($text, [ 'cache' => $cache ]); else - $s = smilies(bbcode($text,false,true,$cache)); + $s = smilies(bbcode($text, [ 'cache' => $cache ])); $s = zidify_links($s); @@ -1754,9 +1754,14 @@ function get_plink($item,$conversation_mode = true) { else $key = 'llink'; + $zidify = true; + + if(array_key_exists('author',$item) && $item['author']['xchan_network'] !== 'zot') + $zidify = false; + if(x($item,$key)) { return array( - 'href' => zid($item[$key]), + 'href' => (($zidify) ? zid($item[$key]) : $item[$key]), 'title' => t('Link to Source'), ); } @@ -2704,12 +2709,15 @@ function linkify_tags($a, &$body, $uid, $diaspora = false) { function getIconFromType($type) { $iconMap = array( //Folder - t('Collection') => 'fa-folder', - 'multipart/mixed' => 'fa-folder', //dirs in attach use this mime type + t('Collection') => 'fa-folder-o', + 'multipart/mixed' => 'fa-folder-o', //dirs in attach use this mime type //Common file 'application/octet-stream' => 'fa-file-o', //Text 'text/plain' => 'fa-file-text-o', + 'text/markdown' => 'fa-file-text-o', + 'text/bbcode' => 'fa-file-text-o', + 'text/html' => 'fa-file-text-o', 'application/msword' => 'fa-file-word-o', 'application/pdf' => 'fa-file-pdf-o', 'application/vnd.oasis.opendocument.text' => 'fa-file-word-o', @@ -2739,11 +2747,33 @@ function getIconFromType($type) { 'video/x-matroska' => 'fa-file-video-o' ); - $iconFromType = 'fa-file-o'; + $catMap = [ + 'application' => 'fa-file-code-o', + 'multipart' => 'fa-folder', + 'audio' => 'fa-file-audio-o', + 'video' => 'fa-file-video-o', + 'text' => 'fa-file-text-o', + 'image' => 'fa=file-picture-o', + 'message' => 'fa-file-text-o' + ]; + + + $iconFromType = ''; if (array_key_exists($type, $iconMap)) { $iconFromType = $iconMap[$type]; } + else { + $parts = explode('/',$type); + if($parts[0] && $catMap[$parts[0]]) { + $iconFromType = $catMap[$parts[0]]; + } + } + + if(! $iconFromType) { + $iconFromType = 'fa-file-o'; + } + return $iconFromType; } diff --git a/include/zid.php b/include/zid.php index ce9f70385..6ebc9a6ab 100644 --- a/include/zid.php +++ b/include/zid.php @@ -21,6 +21,7 @@ function is_matrix_url($url) { } $remembered[$m['host']] = false; } + return false; } @@ -32,14 +33,8 @@ function is_matrix_url($url) { * @param boolean $address * $address to use instead of session environment * @return string - * - * @hooks 'zid' - * string url - url to accept zid - * string zid - urlencoded zid - * string result - the return string we calculated, change it if you want to return something else */ - -function zid($s,$address = '') { +function zid($s, $address = '') { if (! strlen($s) || strpos($s,'zid=')) return $s; @@ -74,7 +69,18 @@ function zid($s,$address = '') { if($fragment) $zurl .= '#' . $fragment; - $arr = array('url' => $s, 'zid' => urlencode($myaddr), 'result' => $zurl); + $arr = [ + 'url' => $s, + 'zid' => urlencode($myaddr), + 'result' => $zurl + ]; + /** + * @hooks zid + * Called when adding the observer's zid to a URL. + * * \e string \b url - url to accept zid + * * \e string \b zid - urlencoded zid + * * \e string \b result - the return string we calculated, change it if you want to return something else + */ call_hooks('zid', $arr); return $arr['result']; @@ -89,36 +95,51 @@ function strip_zids($s) { return preg_replace('/[\?&]zid=(.*?)(&|$)/ism','$2',$s); } +function strip_owt($s) { + return preg_replace('/[\?&]owt=(.*?)(&|$)/ism','$2',$s); +} + function strip_zats($s) { return preg_replace('/[\?&]zat=(.*?)(&|$)/ism','$2',$s); } + +function clean_query_string($s = '') { + $x = strip_zids(($s) ? $s : \App::$query_string); + $x = strip_owt($x); + $x = strip_zats($x); + + return strip_query_param($x,'f'); +} + + /** * zidify_callback() and zidify_links() work together to turn any HTML a tags with class="zrl" into zid links * These will typically be generated by a bbcode '[zrl]' tag. This is done inside prepare_text() rather than bbcode() * because the latter is used for general purpose conversions and the former is used only when preparing text for * immediate display. * - * Issues: Currently the order of HTML parameters in the text is somewhat rigid and inflexible. + * @TODO Issues: Currently the order of HTML parameters in the text is somewhat rigid and inflexible. * We assume it looks like \<a class="zrl" href="xxxxxxxxxx"\> and will not work if zrl and href appear in a different order. * * @param array $match * @return string */ function zidify_callback($match) { - $is_zid = ((feature_enabled(local_channel(),'sendzid')) || (strpos($match[1],'zrl')) ? true : false); + $is_zid = ((feature_enabled(local_channel(), 'sendzid')) || (strpos($match[1], 'zrl')) ? true : false); $replace = '<a' . $match[1] . ' href="' . (($is_zid) ? zid($match[2]) : $match[2]) . '"'; - $x = str_replace($match[0],$replace,$match[0]); + + $x = str_replace($match[0], $replace, $match[0]); return $x; } function zidify_img_callback($match) { - $is_zid = ((feature_enabled(local_channel(),'sendzid')) || (strpos($match[1],'zrl')) ? true : false); + $is_zid = ((feature_enabled(local_channel(), 'sendzid')) || (strpos($match[1], 'zrl')) ? true : false); $replace = '<img' . $match[1] . ' src="' . (($is_zid) ? zid($match[2]) : $match[2]) . '"'; - $x = str_replace($match[0],$replace,$match[0]); + $x = str_replace($match[0], $replace, $match[0]); return $x; } @@ -132,12 +153,11 @@ function zidify_links($s) { } - - function zidify_text_callback($match) { $is_zid = is_matrix_url($match[2]); $replace = '<a' . $match[1] . ' href="' . (($is_zid) ? zid($match[2]) : $match[2]) . '"'; - $x = str_replace($match[0],$replace,$match[0]); + + $x = str_replace($match[0], $replace, $match[0]); return $x; } @@ -146,7 +166,7 @@ function zidify_text_img_callback($match) { $is_zid = is_matrix_url($match[2]); $replace = '<img' . $match[1] . ' src="' . (($is_zid) ? zid($match[2]) : $match[2]) . '"'; - $x = str_replace($match[0],$replace,$match[0]); + $x = str_replace($match[0], $replace, $match[0]); return $x; } @@ -157,8 +177,6 @@ function zidify_text($s) { $s = preg_replace_callback('/\<img(.*?)src\=\"(.*?)\"/ism','zidify_text_img_callback',$s); return $s; - - } @@ -198,7 +216,6 @@ function red_zrl_callback($matches) { * @param array $matches * @return string */ - function red_escape_zrl_callback($matches) { // Uncertain why the url/zrl forms weren't picked up by the non-greedy regex. @@ -234,11 +251,17 @@ function red_zrlify_img_callback($matches) { return $matches[0]; } + +/** + * @brief OpenWebAuth authentication. + * + * @param string $token + */ function owt_init($token) { - \Zotlabs\Zot\Verify::purge('owt','3 MINUTE'); + \Zotlabs\Zot\Verify::purge('owt', '3 MINUTE'); - $ob_hash = \Zotlabs\Zot\Verify::get_meta('owt',0,$token); + $ob_hash = \Zotlabs\Zot\Verify::get_meta('owt', 0, $token); if($ob_hash === false) { return; @@ -262,7 +285,7 @@ function owt_init($token) { } if(! $r) { logger('owt: unable to finger ' . $ob_hash); - return; + return; } $hubloc = $r[0]; @@ -296,14 +319,25 @@ function owt_init($token) { $_SESSION['DNT'] = 1; } - $arr = array('xchan' => $hubloc, 'url' => \App::$query_string, 'session' => $_SESSION); - call_hooks('magic_auth_success',$arr); + $arr = [ + 'xchan' => $hubloc, + 'url' => \App::$query_string, + 'session' => $_SESSION + ]; + /** + * @hooks magic_auth_success + * Called when a magic-auth was successful. + * * \e array \b xchan + * * \e string \b url + * * \e array \b session + */ + call_hooks('magic_auth_success', $arr); + \App::set_observer($hubloc); require_once('include/security.php'); \App::set_groups(init_groups_visitor($_SESSION['visitor_id'])); - if(! get_config('system','hide_owa_greeting')) + if(! get_config('system', 'hide_owa_greeting')) info(sprintf( t('OpenWebAuth: %1$s welcomes %2$s'),\App::get_hostname(), $hubloc['xchan_name'])); - logger('OpenWebAuth: auth success from ' . $hubloc['xchan_addr']); - + logger('OpenWebAuth: auth success from ' . $hubloc['xchan_addr']); }
\ No newline at end of file diff --git a/include/zot.php b/include/zot.php index 9bb25dd6f..18960db46 100644 --- a/include/zot.php +++ b/include/zot.php @@ -31,7 +31,6 @@ require_once('include/perm_upgrade.php'); * @param string $channel_nick a unique nickname of controlling entity * @returns string */ - function zot_new_uid($channel_nick) { $rawstr = z_root() . '/' . $channel_nick . '.' . mt_rand(); return(base64url_encode(hash('whirlpool', $rawstr, true), true)); @@ -49,7 +48,6 @@ function zot_new_uid($channel_nick) { * @param string $guid * @param string $guid_sig */ - function make_xchan_hash($guid, $guid_sig) { return base64url_encode(hash('whirlpool', $guid . $guid_sig, true)); } @@ -104,6 +102,8 @@ function zot_get_hublocs($hash) { * @param string $remote_key * optional public site key of target hub used to encrypt entire packet * NOTE: remote_key and encrypted packets are required for 'auth_check' packets, optional for all others + * @param string $methods + * optional comma separated list of encryption methods @ref zot_best_algorithm() * @param string $secret * random string, required for packets which require verification/callback * e.g. 'pickup', 'purge', 'notify', 'auth_check'. Packet types 'ping', 'force_refresh', and 'refresh' do not require verification @@ -159,29 +159,38 @@ function zot_build_packet($channel, $type = 'notify', $recipients = null, $remot } /** - * @brief choose best encryption function from those available on both sites - * + * @brief Choose best encryption function from those available on both sites. + * * @param string $methods * comma separated list of encryption methods * @return string first match from our site method preferences crypto_methods() array * of a method which is common to both sites; or 'aes256cbc' if no matches are found. */ - function zot_best_algorithm($methods) { - $x = [ 'methods' => $methods, 'result' => '' ]; - call_hooks('zot_best_algorithm',$x); + $x = [ + 'methods' => $methods, + 'result' => '' + ]; + /** + * @hooks zot_best_algorithm + * Called when negotiating crypto algorithms with remote sites. + * * \e string \b methods - comma separated list of encryption methods + * * \e string \b result - the algorithm to return + */ + call_hooks('zot_best_algorithm', $x); + if($x['result']) return $x['result']; if($methods) { - $x = explode(',',$methods); + $x = explode(',', $methods); if($x) { $y = crypto_methods(); if($y) { foreach($y as $yv) { $yv = trim($yv); - if(in_array($yv,$x)) { + if(in_array($yv, $x)) { return($yv); } } @@ -193,7 +202,6 @@ function zot_best_algorithm($methods) { } - /** * @brief * @@ -210,7 +218,7 @@ function zot_zot($url, $data) { /** * @brief Refreshes after permission changed or friending, etc. * - * The top half of this function is similar to \Zotlabs\Zot\Finger::run() and could potentially be + * The top half of this function is similar to \\Zotlabs\\Zot\\Finger::run() and could potentially be * consolidated. * * zot_refresh is typically invoked when somebody has changed permissions of a channel and they are notified @@ -230,21 +238,22 @@ function zot_zot($url, $data) { * * @param array $them => xchan structure of sender * @param array $channel => local channel structure of target recipient, required for "friending" operations - * @param array $force default false + * @param array $force (optional) default false * - * @returns boolean true if successful, else false + * @return boolean + * * \b true if successful + * * otherwise \b false */ - function zot_refresh($them, $channel = null, $force = false) { if (array_key_exists('xchan_network', $them) && ($them['xchan_network'] !== 'zot')) { - logger('zot_refresh: not got zot. ' . $them['xchan_name']); + logger('not got zot. ' . $them['xchan_name']); return true; } - logger('zot_refresh: them: ' . print_r($them,true), LOGGER_DATA, LOG_DEBUG); + logger('them: ' . print_r($them,true), LOGGER_DATA, LOG_DEBUG); if ($channel) - logger('zot_refresh: channel: ' . print_r($channel,true), LOGGER_DATA, LOG_DEBUG); + logger('channel: ' . print_r($channel,true), LOGGER_DATA, LOG_DEBUG); $url = null; @@ -285,6 +294,16 @@ function zot_refresh($them, $channel = null, $force = false) { return false; } + $s = q("select site_dead from site where site_url = '%s' limit 1", + dbesc($url) + ); + + if($s && intval($s[0]['site_dead']) && (! $force)) { + logger('zot_refresh: site ' . $url . ' is marked dead and force flag is not set. Cancelling operation.'); + return false; + } + + $token = random_string(); $postvars = []; @@ -305,7 +324,6 @@ function zot_refresh($them, $channel = null, $force = false) { && array_key_exists('xchan_guid_sig',$them) && $them['xchan_guid_sig']) { $postvars['guid'] = $them['xchan_guid']; $postvars['guid_sig'] = $them['xchan_guid_sig']; - } $rhs = '/.well-known/zot-info'; @@ -320,7 +338,7 @@ function zot_refresh($them, $channel = null, $force = false) { $j = json_decode($result['body'],true); if (! (($j) && ($j['success']))) { - logger('zot_refresh: result not decodable'); + logger('Result not decodable'); return false; } @@ -376,6 +394,7 @@ function zot_refresh($them, $channel = null, $force = false) { $next_birthday = NULL_DATE; } + $profile_assign = get_pconfig($channel['channel_id'],'system','profile_assign',''); // Keep original perms to check if we need to notify them $previous_perms = get_all_perms($channel['channel_id'],$x['hash']); @@ -437,6 +456,7 @@ function zot_refresh($them, $channel = null, $force = false) { 'abook_channel' => intval($channel['channel_id']), 'abook_closeness' => intval($closeness), 'abook_xchan' => $x['hash'], + 'abook_profile' => $profile_assign, 'abook_created' => datetime_convert(), 'abook_updated' => datetime_convert(), 'abook_dob' => $next_birthday, @@ -474,7 +494,7 @@ function zot_refresh($them, $channel = null, $force = false) { } - /** If there is a default group for this channel, add this connection to it */ + // If there is a default group for this channel, add this connection to it $default_group = $channel['channel_default_group']; if($default_group) { @@ -518,10 +538,10 @@ function zot_refresh($them, $channel = null, $force = false) { * * \e string \b url_sig => URL signed with conversant's private key * @param boolean $multiple (optional) default false * - * @returns array|null null if site is blacklisted or not found, otherwise an - * array with an hubloc record + * @return array|null + * * null if site is blacklisted or not found + * * otherwise an array with an hubloc record */ - function zot_gethub($arr, $multiple = false) { if($arr['guid'] && $arr['guid_sig'] && $arr['url'] && $arr['url_sig']) { @@ -544,11 +564,11 @@ function zot_gethub($arr, $multiple = false) { dbesc($arr['url_sig']) ); if($r) { - logger('zot_gethub: found', LOGGER_DEBUG); + logger('Found', LOGGER_DEBUG); return (($multiple) ? $r : $r[0]); } } - logger('zot_gethub: not found: ' . print_r($arr,true), LOGGER_DEBUG); + logger('Not found: ' . print_r($arr,true), LOGGER_DEBUG); return false; } @@ -567,11 +587,10 @@ function zot_gethub($arr, $multiple = false) { * * \e string \b url => URL of the origination hub of this communication * * \e string \b url_sig => URL signed with conversant's private key * - * @returns array an associative array with + * @return array An associative array with * * \b success boolean true or false * * \b message (optional) error string only if success is false */ - function zot_register_hub($arr) { $result = [ 'success' => false ]; @@ -609,7 +628,7 @@ function zot_register_hub($arr) { $result['success'] = true; } else { - logger('zot_register_hub: failure to verify returned packet using ' . $method); + logger('Failure to verify returned packet using ' . $method); } } } @@ -632,13 +651,17 @@ function zot_register_hub($arr) { * If set [typically by update_directory_entry()] indicates a specific update table row and more particularly * contains a particular address (ud_addr) which needs to be updated in that table. * - * @return associative array + * @return array An associative array with: * * \e boolean \b success boolean true or false * * \e string \b message (optional) error string only if success is false */ +function import_xchan($arr, $ud_flags = UPDATE_FLAGS_UPDATED, $ud_arr = null) { -function import_xchan($arr,$ud_flags = UPDATE_FLAGS_UPDATED, $ud_arr = null) { - + /** + * @hooks import_xchan + * Called when processing the result of zot_finger() to store the result + * * \e array + */ call_hooks('import_xchan', $arr); $ret = array('success' => false); @@ -648,13 +671,13 @@ function import_xchan($arr,$ud_flags = UPDATE_FLAGS_UPDATED, $ud_arr = null) { $what = ''; if(! (is_array($arr) && array_key_exists('success',$arr) && $arr['success'])) { - logger('import_xchan: invalid data packet: ' . print_r($arr,true)); + logger('Invalid data packet: ' . print_r($arr,true)); $ret['message'] = t('Invalid data packet'); return $ret; } if(! ($arr['guid'] && $arr['guid_sig'])) { - logger('import_xchan: no identity information provided. ' . print_r($arr,true)); + logger('No identity information provided. ' . print_r($arr,true)); return $ret; } @@ -668,7 +691,7 @@ function import_xchan($arr,$ud_flags = UPDATE_FLAGS_UPDATED, $ud_arr = null) { foreach($sig_methods as $method) { if(! rsa_verify($arr['guid'],base64url_decode($arr['guid_sig']),$arr['key'],$method)) { - logger('import_xchan: Unable to verify channel signature for ' . $arr['address'] . ' using ' . $method); + logger('Unable to verify channel signature for ' . $arr['address'] . ' using ' . $method); continue; } else { @@ -751,8 +774,8 @@ function import_xchan($arr,$ud_flags = UPDATE_FLAGS_UPDATED, $ud_arr = null) { dbesc($xchan_hash) ); - logger('import_xchan: update: existing: ' . print_r($r[0],true), LOGGER_DATA, LOG_DEBUG); - logger('import_xchan: update: new: ' . print_r($arr,true), LOGGER_DATA, LOG_DEBUG); + logger('Update: existing: ' . print_r($r[0],true), LOGGER_DATA, LOG_DEBUG); + logger('Update: new: ' . print_r($arr,true), LOGGER_DATA, LOG_DEBUG); $what .= 'xchan '; $changed = true; } @@ -767,7 +790,7 @@ function import_xchan($arr,$ud_flags = UPDATE_FLAGS_UPDATED, $ud_arr = null) { $x = xchan_store_lowlevel( [ - 'xchan_hash' => $xchan_hash, + 'xchan_hash' => $xchan_hash, 'xchan_guid' => $arr['guid'], 'xchan_guid_sig' => $arr['guid_sig'], 'xchan_pubkey' => $arr['key'], @@ -918,7 +941,7 @@ function import_xchan($arr,$ud_flags = UPDATE_FLAGS_UPDATED, $ud_arr = null) { } } else { - logger('import_xchan: profile not available - hiding'); + logger('Profile not available - hiding'); // they may have made it private $r = q("delete from xprof where xprof_hash = '%s'", dbesc($xchan_hash) @@ -940,7 +963,7 @@ function import_xchan($arr,$ud_flags = UPDATE_FLAGS_UPDATED, $ud_arr = null) { if(($changed) || ($ud_flags == UPDATE_FLAGS_FORCED)) { $guid = random_string() . '@' . App::get_hostname(); update_modtime($xchan_hash,$guid,$address,$ud_flags); - logger('import_xchan: changed: ' . $what,LOGGER_DEBUG); + logger('Changed: ' . $what,LOGGER_DEBUG); } elseif(! $ud_flags) { // nothing changed but we still need to update the updates record @@ -956,7 +979,7 @@ function import_xchan($arr,$ud_flags = UPDATE_FLAGS_UPDATED, $ud_arr = null) { $ret['hash'] = $xchan_hash; } - logger('import_xchan: result: ' . print_r($ret,true), LOGGER_DATA, LOG_DEBUG); + logger('Result: ' . print_r($ret,true), LOGGER_DATA, LOG_DEBUG); return $ret; } @@ -970,19 +993,18 @@ function import_xchan($arr,$ud_flags = UPDATE_FLAGS_UPDATED, $ud_arr = null) { * @param array $arr - output of z_post_url() * @param array $outq - The queue structure attached to this request */ - function zot_process_response($hub, $arr, $outq) { if(! $arr['success']) { - logger('zot_process_response: failed: ' . $hub); + logger('Failed: ' . $hub); return; } $x = json_decode($arr['body'], true); if(! $x) { - logger('zot_process_response: No json from ' . $hub); - logger('zot_process_response: headers: ' . print_r($arr['header'],true), LOGGER_DATA, LOG_DEBUG); + logger('No json from ' . $hub); + logger('Headers: ' . print_r($arr['header'], true), LOGGER_DATA, LOG_DEBUG); } if(is_array($x) && array_key_exists('delivery_report',$x) && is_array($x['delivery_report'])) { @@ -990,7 +1012,7 @@ function zot_process_response($hub, $arr, $outq) { if(array_key_exists('iv',$x['delivery_report'])) { $j = crypto_unencapsulate($x['delivery_report'],get_config('system','prvkey')); if($j) { - $x['delivery_report'] = json_decode($j,true); + $x['delivery_report'] = json_decode($j,true); } if(! (is_array($x['delivery_report']) && count($x['delivery_report']))) { logger('encrypted delivery report could not be decrypted'); @@ -1050,7 +1072,6 @@ function zot_process_response($hub, $arr, $outq) { * decrypted and json decoded notify packet from remote site * @return array from zot_import() */ - function zot_fetch($arr) { logger('zot_fetch: ' . print_r($arr,true), LOGGER_DATA, LOG_DEBUG); @@ -1063,7 +1084,7 @@ function zot_fetch($arr) { $ret_hubs = zot_gethub($arr['sender'],true); if(! $ret_hubs) { - logger('zot_fetch: no hub: ' . print_r($arr['sender'],true)); + logger('No hub: ' . print_r($arr['sender'],true)); return; } @@ -1095,7 +1116,6 @@ function zot_fetch($arr) { } return; - } /** @@ -1104,7 +1124,7 @@ function zot_fetch($arr) { * Process an incoming array of messages which were obtained via pickup, and * import, update, delete as directed. * - * The message types handled here are 'activity' (e.g. posts), 'mail' , + * The message types handled here are 'activity' (e.g. posts), 'mail', * 'profile', 'location' and 'channel_sync'. * * @param array $arr @@ -1116,18 +1136,17 @@ function zot_fetch($arr) { * currently processing. * * @returns array - * suitable for logging remotely, enumerating the processing results of each message/recipient combination - * * [0] => \e string $channel_hash - * * [1] => \e string $delivery_status - * * [2] => \e string $address + * Suitable for logging remotely, enumerating the processing results of each message/recipient combination + * * [0] => \e string $channel_hash + * * [1] => \e string $delivery_status + * * [2] => \e string $address */ - function zot_import($arr, $sender_url) { $data = json_decode($arr['body'], true); if(! $data) { - logger('zot_import: empty body'); + logger('Empty body'); return array(); } @@ -1163,7 +1182,7 @@ function zot_import($arr, $sender_url) { $i['notify'] = json_decode(crypto_unencapsulate($i['notify'],get_config('system','prvkey')),true); } - logger('zot_import: notify: ' . print_r($i['notify'],true), LOGGER_DATA, LOG_DEBUG); + logger('Notify: ' . print_r($i['notify'],true), LOGGER_DATA, LOG_DEBUG); if(! is_array($i['notify'])) { logger('decode error'); @@ -1173,7 +1192,7 @@ function zot_import($arr, $sender_url) { $hub = zot_gethub($i['notify']['sender']); if((! $hub) || ($hub['hubloc_url'] != $sender_url)) { - logger('zot_import: potential forgery: wrong site for sender: ' . $sender_url . ' != ' . print_r($i['notify'],true)); + logger('Potential forgery: wrong site for sender: ' . $sender_url . ' != ' . print_r($i['notify'],true)); continue; } @@ -1281,7 +1300,7 @@ function zot_import($arr, $sender_url) { } if(! $deliveries) { - logger('zot_import: no deliveries on this site'); + logger('No deliveries on this site'); continue; } @@ -1361,7 +1380,6 @@ function zot_import($arr, $sender_url) { * @param array $msg * @return NULL|array */ - function public_recips($msg) { require_once('include/channel.php'); @@ -1491,9 +1509,8 @@ function public_recips($msg) { } /** - * @brief + * @brief This is the second part of public_recips(). * - * This is the second part of public_recips(). * We'll find all the channels willing to accept public posts from us, then * match them against the sender privacy scope and see who in that list that * the sender is allowing. @@ -1573,7 +1590,6 @@ function allowed_public_recips($msg) { * @param boolean $request (optional) default false * @return array */ - function process_delivery($sender, $arr, $deliveries, $relay, $public = false, $request = false) { $result = array(); @@ -1584,12 +1600,11 @@ function process_delivery($sender, $arr, $deliveries, $relay, $public = false, $ if(! $public) { if($sender['hash'] != $arr['owner_xchan'] && $sender['hash'] != $arr['author_xchan']) { - logger("process_delivery: sender {$sender['hash']} is not owner {$arr['owner_xchan']} or author {$arr['author_xchan']} - mid {$arr['mid']}"); + logger("Sender {$sender['hash']} is not owner {$arr['owner_xchan']} or author {$arr['author_xchan']} - mid {$arr['mid']}"); return; } } - foreach($deliveries as $d) { $local_public = $public; @@ -1818,7 +1833,6 @@ function process_delivery($sender, $arr, $deliveries, $relay, $public = false, $ $DR->update('update ignored'); $result[] = $DR->get(); - // We need this line to ensure wall-to-wall comments are relayed (by falling through to the relay bit), // and at the same time not relay any other relayable posts more than once, because to do so is very wasteful. if(! intval($r[0]['item_origin'])) @@ -1832,8 +1846,14 @@ function process_delivery($sender, $arr, $deliveries, $relay, $public = false, $ // if it's a sourced post, call the post_local hooks as if it were // posted locally so that crosspost connectors will be triggered. - if(check_item_source($arr['uid'], $arr)) + if(check_item_source($arr['uid'], $arr)) { + /** + * @hooks post_local + * Called when an item has been posted on this machine via mod/item.php (also via API). + * * \e array with an item + */ call_hooks('post_local', $arr); + } $item_id = 0; @@ -1845,8 +1865,21 @@ function process_delivery($sender, $arr, $deliveries, $relay, $public = false, $ $item_result = item_store($arr); if($item_result['success']) { $item_id = $item_result['item_id']; - $parr = array('item_id' => $item_id,'item' => $arr,'sender' => $sender,'channel' => $channel); - call_hooks('activity_received',$parr); + $parr = [ + 'item_id' => $item_id, + 'item' => $arr, + 'sender' => $sender, + 'channel' => $channel + ]; + /** + * @hooks activity_received + * Called when an activity (post, comment, like, etc.) has been received from a zot source. + * * \e int \b item_id + * * \e array \b item + * * \e array \b sender + * * \e array \b channel + */ + call_hooks('activity_received', $parr); // don't add a source route if it's a relay or later recipients will get a route mismatch if(! $relay) add_source_route($item_id,$sender['hash']); @@ -1865,7 +1898,7 @@ function process_delivery($sender, $arr, $deliveries, $relay, $public = false, $ } if($relay && $item_id) { - logger('process_delivery: invoking relay'); + logger('Invoking relay'); Zotlabs\Daemon\Master::Summon(array('Notifier','relay',intval($item_id))); $DR->addto_update('relayed'); $result[] = $DR->get(); @@ -1875,13 +1908,13 @@ function process_delivery($sender, $arr, $deliveries, $relay, $public = false, $ if(! $deliveries) $result[] = array('', 'no recipients', '', $arr['mid']); - logger('process_delivery: local results: ' . print_r($result, true), LOGGER_DEBUG); + logger('Local results: ' . print_r($result, true), LOGGER_DEBUG); return $result; } /** - * @brief + * @brief Remove community tag. * * @param array $sender an associative array with * * \e string \b hash a xchan_hash @@ -1891,7 +1924,6 @@ function process_delivery($sender, $arr, $deliveries, $relay, $public = false, $ * * \e int \b mid * @param int $uid */ - function remove_community_tag($sender, $arr, $uid) { if(! (activity_match($arr['verb'], ACTIVITY_TAG) && ($arr['obj_type'] == ACTIVITY_OBJ_TAGTERM))) @@ -1900,7 +1932,7 @@ function remove_community_tag($sender, $arr, $uid) { logger('remove_community_tag: invoked'); if(! get_pconfig($uid,'system','blocktags')) { - logger('remove_community tag: permission denied.'); + logger('Permission denied.'); return; } @@ -1909,12 +1941,12 @@ function remove_community_tag($sender, $arr, $uid) { intval($uid) ); if(! $r) { - logger('remove_community_tag: no item'); + logger('No item'); return; } if(($sender['hash'] != $r[0]['owner_xchan']) && ($sender['hash'] != $r[0]['author_xchan'])) { - logger('remove_community_tag: sender not authorised.'); + logger('Sender not authorised.'); return; } @@ -1926,7 +1958,7 @@ function remove_community_tag($sender, $arr, $uid) { $i['object'] = json_decode($i['object'],true); if(! ($i['target'] && $i['object'])) { - logger('remove_community_tag: no target/object'); + logger('No target/object'); return; } @@ -1937,7 +1969,7 @@ function remove_community_tag($sender, $arr, $uid) { intval($uid) ); if(! $r) { - logger('remove_community_tag: no parent message'); + logger('No parent message'); return; } @@ -1953,16 +1985,16 @@ function remove_community_tag($sender, $arr, $uid) { } /** - * @brief Just calls item_store_update() and logs result. + * @brief Updates an imported item. * * @see item_store_update() * - * @param array $sender (unused) + * @param array $sender * @param array $item * @param array $orig * @param int $uid + * @param boolean $tag_delivery */ - function update_imported_item($sender, $item, $orig, $uid, $tag_delivery) { // If this is a comment being updated, remove any privacy information @@ -1976,11 +2008,11 @@ function update_imported_item($sender, $item, $orig, $uid, $tag_delivery) { unset($item['item_private']); } - // we need the tag_delivery check for downstream flowing posts as the stored post - // may have a different owner than the one being transmitted. + // we need the tag_delivery check for downstream flowing posts as the stored post + // may have a different owner than the one being transmitted. if(($sender['hash'] != $orig['owner_xchan'] && $sender['hash'] != $orig['author_xchan']) && (! $tag_delivery)) { - notice('sender is not owner or author'); + logger('sender is not owner or author'); return; } @@ -2014,10 +2046,9 @@ function update_imported_item($sender, $item, $orig, $uid, $tag_delivery) { * @param boolean $relay * @return boolean|int post_id */ - function delete_imported_item($sender, $item, $uid, $relay) { - logger('delete_imported_item invoked', LOGGER_DEBUG); + logger('invoked', LOGGER_DEBUG); $ownership_valid = false; $item_found = false; @@ -2176,7 +2207,6 @@ function process_mail_delivery($sender, $arr, $deliveries) { * * \e string \b hash a xchan_hash * @param array $arr */ - function process_rating_delivery($sender, $arr) { logger('process_rating_delivery: ' . print_r($arr,true)); @@ -2236,7 +2266,6 @@ function process_rating_delivery($sender, $arr) { * @param array $arr * @param array $deliveries (unused) */ - function process_profile_delivery($sender, $arr, $deliveries) { logger('process_profile_delivery', LOGGER_DEBUG); @@ -2248,7 +2277,16 @@ function process_profile_delivery($sender, $arr, $deliveries) { import_directory_profile($sender['hash'], $arr, $r[0]['xchan_addr'], UPDATE_FLAGS_UPDATED, 0); } -function process_location_delivery($sender,$arr,$deliveries) { + +/** + * @brief + * + * @param array $sender an associative array + * * \e string \b hash a xchan_hash + * @param array $arr + * @param array $deliveries (unused) deliveries is irrelevant + */ +function process_location_delivery($sender, $arr, $deliveries) { // deliveries is irrelevant logger('process_location_delivery', LOGGER_DEBUG); @@ -2261,7 +2299,7 @@ function process_location_delivery($sender,$arr,$deliveries) { if(array_key_exists('locations',$arr) && $arr['locations']) { $x = sync_locations($sender,$arr,true); - logger('process_location_delivery: results: ' . print_r($x,true), LOGGER_DEBUG); + logger('results: ' . print_r($x,true), LOGGER_DEBUG); if($x['changed']) { $guid = random_string() . '@' . App::get_hostname(); update_modtime($sender['hash'],$sender['guid'],$arr['locations'][0]['address'],UPDATE_FLAGS_UPDATED); @@ -2270,7 +2308,7 @@ function process_location_delivery($sender,$arr,$deliveries) { } /** - * @brief checks for a moved UNO channel and sets the channel_moved flag + * @brief Checks for a moved UNO channel and sets the channel_moved flag. * * Currently the effect of this flag is to turn the channel into 'read-only' mode. * New content will not be processed (there was still an issue with blocking the @@ -2284,11 +2322,11 @@ function process_location_delivery($sender,$arr,$deliveries) { * if a new location is reported and there is only one location record. * The rest of the hubloc syncronisation will be handled within * sync_locations + * + * @param string $sender_hash A channel hash + * @param array $locations */ - - - -function check_location_move($sender_hash,$locations) { +function check_location_move($sender_hash, $locations) { if(! $locations) return; @@ -2314,11 +2352,18 @@ function check_location_move($sender_hash,$locations) { // federation plugins may wish to notify connections // of the move on singleton networks - $arr = array('channel' => $r[0],'locations' => $locations); - call_hooks('location_move',$arr); - + $arr = [ + 'channel' => $r[0], + 'locations' => $locations + ]; + /** + * @hooks location_move + * Called when a new location has been provided to a UNO channel (indicating a move rather than a clone). + * * \e array \b channel + * * \e array \b locations + */ + call_hooks('location_move', $arr); } - } @@ -2330,7 +2375,6 @@ function check_location_move($sender_hash,$locations) { * @param boolean $absolute (optional) default false * @return array */ - function sync_locations($sender, $arr, $absolute = false) { $ret = array(); @@ -2340,7 +2384,6 @@ function sync_locations($sender, $arr, $absolute = false) { if($absolute) check_location_move($sender['hash'],$arr['locations']); - $xisting = q("select hubloc_id, hubloc_url, hubloc_sitekey from hubloc where hubloc_hash = '%s'", dbesc($sender['hash']) ); @@ -2362,7 +2405,7 @@ function sync_locations($sender, $arr, $absolute = false) { foreach($arr['locations'] as $location) { if(! rsa_verify($location['url'],base64url_decode($location['url_sig']),$sender['key'])) { - logger('sync_locations: Unable to verify site signature for ' . $location['url']); + logger('Unable to verify site signature for ' . $location['url']); $ret['message'] .= sprintf( t('Unable to verify site signature for %s'), $location['url']) . EOL; continue; } @@ -2375,7 +2418,7 @@ function sync_locations($sender, $arr, $absolute = false) { } if(! $location['sitekey']) { - logger('sync_locations: empty hubloc sitekey. ' . print_r($location,true)); + logger('Empty hubloc sitekey. ' . print_r($location,true)); continue; } @@ -2398,7 +2441,7 @@ function sync_locations($sender, $arr, $absolute = false) { dbesc($location['sitekey']) ); if($r) { - logger('sync_locations: hub exists: ' . $location['url'], LOGGER_DEBUG); + logger('Hub exists: ' . $location['url'], LOGGER_DEBUG); // update connection timestamp if this is the site we're talking to // This only happens when called from import_xchan @@ -2500,7 +2543,7 @@ function sync_locations($sender, $arr, $absolute = false) { dbesc($sender['hash']) ); } - logger('sync_locations: new hub: ' . $location['url']); + logger('New hub: ' . $location['url']); $r = hubloc_store_lowlevel( [ @@ -2509,7 +2552,7 @@ function sync_locations($sender, $arr, $absolute = false) { 'hubloc_hash' => $sender['hash'], 'hubloc_addr' => $location['address'], 'hubloc_network' => 'zot', - 'hubloc_primary' => $location['primary'], + 'hubloc_primary' => intval($location['primary']), 'hubloc_url' => $location['url'], 'hubloc_url_sig' => $location['url_sig'], 'hubloc_host' => $location['host'], @@ -2538,7 +2581,7 @@ function sync_locations($sender, $arr, $absolute = false) { if($absolute && $xisting) { foreach($xisting as $x) { if(! array_key_exists('updated',$x)) { - logger('sync_locations: deleting unreferenced hub location ' . $x['hubloc_addr']); + logger('Deleting unreferenced hub location ' . $x['hubloc_addr']); $r = q("update hubloc set hubloc_deleted = 1, hubloc_updated = '%s' where hubloc_id = %d", dbesc(datetime_convert()), intval($x['hubloc_id']) @@ -2603,11 +2646,10 @@ function zot_encode_locations($channel) { * @param string $hash * @param array $profile * @param string $addr - * @param number $ud_flags - * @param number $suppress_update default 0 + * @param number $ud_flags (optional) UPDATE_FLAGS_UPDATED + * @param number $suppress_update (optional) default 0 * @return boolean $updated if something changed */ - function import_directory_profile($hash, $profile, $addr, $ud_flags = UPDATE_FLAGS_UPDATED, $suppress_update = 0) { logger('import_directory_profile', LOGGER_DEBUG); @@ -2707,7 +2749,7 @@ function import_directory_profile($hash, $profile, $addr, $ud_flags = UPDATE_FLA } } else { $update = true; - logger('import_directory_profile: new profile '); + logger('New profile'); q("insert into xprof (xprof_hash, xprof_desc, xprof_dob, xprof_age, xprof_gender, xprof_marital, xprof_sexual, xprof_locale, xprof_region, xprof_postcode, xprof_country, xprof_about, xprof_homepage, xprof_hometown, xprof_keywords) values ('%s', '%s', '%s', %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s') ", dbesc($arr['xprof_hash']), dbesc($arr['xprof_desc']), @@ -2727,7 +2769,18 @@ function import_directory_profile($hash, $profile, $addr, $ud_flags = UPDATE_FLA ); } - $d = array('xprof' => $arr, 'profile' => $profile, 'update' => $update); + $d = [ + 'xprof' => $arr, + 'profile' => $profile, + 'update' => $update + ]; + /** + * @hooks import_directory_profile + * Called when processing delivery of a profile structure from an external source (usually for directory storage). + * * \e array \b xprof + * * \e array \b profile + * * \e boolean \b update + */ call_hooks('import_directory_profile', $d); if (($d['update']) && (! $suppress_update)) @@ -2739,10 +2792,9 @@ function import_directory_profile($hash, $profile, $addr, $ud_flags = UPDATE_FLA /** * @brief * - * @param string $hash + * @param string $hash An xtag_hash * @param array $keywords */ - function import_directory_keywords($hash, $keywords) { $existing = array(); @@ -2787,7 +2839,6 @@ function import_directory_keywords($hash, $keywords) { * @param string $addr * @param int $flags (optional) default 0 */ - function update_modtime($hash, $guid, $addr, $flags = 0) { $dirmode = intval(get_config('system', 'directory_mode')); @@ -2820,13 +2871,12 @@ function update_modtime($hash, $guid, $addr, $flags = 0) { * @param string $pubkey * @return boolean true if updated or inserted */ - function import_site($arr, $pubkey) { if( (! is_array($arr)) || (! $arr['url']) || (! $arr['url_sig'])) return false; if(! rsa_verify($arr['url'], base64url_decode($arr['url_sig']), $pubkey)) { - logger('import_site: bad url_sig'); + logger('Bad url_sig'); return false; } @@ -2905,7 +2955,7 @@ function import_site($arr, $pubkey) { if(array_key_exists('zot',$arr)) { set_sconfig($arr['url'],'system','zot_version',$arr['zot']); - } + } if($exists) { if(($siterecord['site_flags'] != $site_flags) @@ -2924,7 +2974,6 @@ function import_site($arr, $pubkey) { // logger('import_site: input: ' . print_r($arr,true)); // logger('import_site: stored: ' . print_r($siterecord,true)); - $r = q("update site set site_dead = 0, site_location = '%s', site_flags = %d, site_access = %d, site_directory = '%s', site_register = %d, site_update = '%s', site_sellpage = '%s', site_realm = '%s', site_type = %d, site_project = '%s', site_version = '%s', site_crypto = '%s' where site_url = '%s'", dbesc($site_location), @@ -2942,7 +2991,7 @@ function import_site($arr, $pubkey) { dbesc($url) ); if(! $r) { - logger('import_site: update failed. ' . print_r($arr,true)); + logger('Update failed. ' . print_r($arr,true)); } } else { @@ -2956,7 +3005,7 @@ function import_site($arr, $pubkey) { else { $update = true; - $r = site_store_lowlevel( + $r = site_store_lowlevel( [ 'site_location' => $site_location, 'site_url' => $url, @@ -2975,7 +3024,7 @@ function import_site($arr, $pubkey) { ); if(! $r) { - logger('import_site: record create failed. ' . print_r($arr,true)); + logger('Record create failed. ' . print_r($arr,true)); } } @@ -2984,20 +3033,19 @@ function import_site($arr, $pubkey) { /** + * @brief Builds and sends a sync packet. + * * Send a zot packet to all hubs where this channel is duplicated, refreshing * such things as personal settings, channel permissions, address book updates, etc. * - * @param int $uid + * @param int $uid (optional) default 0 * @param array $packet (optional) default null * @param boolean $groups_changed (optional) default false */ - function build_sync_packet($uid = 0, $packet = null, $groups_changed = false) { - logger('build_sync_packet'); - $keychange = (($packet && array_key_exists('keychange',$packet)) ? true : false); if($keychange) { logger('keychange sync'); @@ -3112,6 +3160,7 @@ function build_sync_packet($uid = 0, $packet = null, $groups_changed = false) { ); if($r) $info['collections'] = $r; + $r = q("select groups.hash as collection, group_member.xchan as member from groups left join group_member on groups.id = group_member.gid where group_member.uid = %d", intval($uid) ); @@ -3122,7 +3171,7 @@ function build_sync_packet($uid = 0, $packet = null, $groups_changed = false) { $interval = ((get_config('system','delivery_interval') !== false) ? intval(get_config('system','delivery_interval')) : 2 ); - logger('build_sync_packet: packet: ' . print_r($info,true), LOGGER_DATA, LOG_DEBUG); + logger('Packet: ' . print_r($info,true), LOGGER_DATA, LOG_DEBUG); $total = count($synchubs); @@ -3138,6 +3187,15 @@ function build_sync_packet($uid = 0, $packet = null, $groups_changed = false) { 'msg' => json_encode($info) )); + + $x = q("select count(outq_hash) as total from outq where outq_delivered = 0"); + if(intval($x[0]['total']) > intval(get_config('system','force_queue_threshold',300))) { + logger('immediate delivery deferred.', LOGGER_DEBUG, LOG_INFO); + update_queue_item($hash); + continue; + } + + Zotlabs\Daemon\Master::Summon(array('Deliver', $hash)); $total = $total - 1; @@ -3154,7 +3212,6 @@ function build_sync_packet($uid = 0, $packet = null, $groups_changed = false) { * @param array $deliveries * @return array */ - function process_channel_sync_delivery($sender, $arr, $deliveries) { require_once('include/import.php'); @@ -3182,7 +3239,7 @@ function process_channel_sync_delivery($sender, $arr, $deliveries) { $max_feeds = account_service_class_fetch($channel['channel_account_id'],'total_feeds'); if($channel['channel_hash'] != $sender['hash']) { - logger('process_channel_sync_delivery: possible forgery. Sender ' . $sender['hash'] . ' is not ' . $channel['channel_hash']); + logger('Possible forgery. Sender ' . $sender['hash'] . ' is not ' . $channel['channel_hash']); $result[] = array($d['hash'],'channel mismatch',$channel['channel_name'],''); continue; } @@ -3198,7 +3255,7 @@ function process_channel_sync_delivery($sender, $arr, $deliveries) { $hash = make_xchan_hash($channel['channel_guid'],$sig); - $r = q("update channel set channel_prvkey = '%s', channel_pubkey = '%s', channel_guid_sig = '%s', + $r = q("update channel set channel_prvkey = '%s', channel_pubkey = '%s', channel_guid_sig = '%s', channel_hash = '%s' where channel_id = %d", dbesc($arr['channel']['channel_prvkey']), dbesc($arr['channel']['channel_pubkey']), @@ -3341,7 +3398,6 @@ function process_channel_sync_delivery($sender, $arr, $deliveries) { $arr['channel']['channel_pageflags'] = $arr['channel']['channel_pageflags'] - 0x8000; if($arr['channel']['channel_pageflags'] & 0x1000) $arr['channel']['channel_pageflags'] = $arr['channel']['channel_pageflags'] - 0x1000; - } $disallowed = [ @@ -3407,7 +3463,7 @@ function process_channel_sync_delivery($sender, $arr, $deliveries) { $clean = array(); if($abook['abook_xchan'] && $abook['entry_deleted']) { - logger('process_channel_sync_delivery: removing abook entry for ' . $abook['abook_xchan']); + logger('Removing abook entry for ' . $abook['abook_xchan']); $r = q("select abook_id, abook_feed from abook where abook_xchan = '%s' and abook_channel = %d and abook_self = 0 limit 1", dbesc($abook['abook_xchan']), @@ -3432,7 +3488,7 @@ function process_channel_sync_delivery($sender, $arr, $deliveries) { if(! $h) { $xhash = import_author_xchan(encode_item_xchan($abook)); if(! $xhash) { - logger('process_channel_sync_delivery: import of ' . $abook['xchan_addr'] . ' failed.'); + logger('Import of ' . $abook['xchan_addr'] . ' failed.'); continue; } } @@ -3449,7 +3505,7 @@ function process_channel_sync_delivery($sender, $arr, $deliveries) { if(array_key_exists('abook_instance',$clean) && $clean['abook_instance'] && strpos($clean['abook_instance'],z_root()) === false) { $clean['abook_not_here'] = 1; - } + } $r = q("select * from abook where abook_xchan = '%s' and abook_channel = %d limit 1", @@ -3461,11 +3517,11 @@ function process_channel_sync_delivery($sender, $arr, $deliveries) { if(! $r) { if($max_friends !== false && $total_friends > $max_friends) { - logger('process_channel_sync_delivery: total_channels service class limit exceeded'); + logger('total_channels service class limit exceeded'); continue; } if($max_feeds !== false && intval($clean['abook_feed']) && $total_feeds > $max_feeds) { - logger('process_channel_sync_delivery: total_feeds service class limit exceeded'); + logger('total_feeds service class limit exceeded'); continue; } abook_store_lowlevel( @@ -3484,6 +3540,7 @@ function process_channel_sync_delivery($sender, $arr, $deliveries) { foreach($clean as $k => $v) { if($k == 'abook_dob') $v = dbescdate($v); + $r = dbq("UPDATE abook set " . dbesc($k) . " = '" . dbesc($v) . "' where abook_xchan = '" . dbesc($clean['abook_xchan']) . "' and abook_channel = " . intval($channel['channel_id'])); } @@ -3496,7 +3553,7 @@ function process_channel_sync_delivery($sender, $arr, $deliveries) { translate_abook_perms_inbound($channel,$abook); if($abconfig) { - // @fixme does not handle sync of del_abconfig + /// @fixme does not handle sync of del_abconfig foreach($abconfig as $abc) { set_abconfig($channel['channel_id'],$abc['xchan'],$abc['cat'],$abc['k'],$abc['v']); } @@ -3688,20 +3745,35 @@ function process_channel_sync_delivery($sender, $arr, $deliveries) { * @TODO * We also need to import local photos if a custom photo is selected */ + + if((strpos($profile['thumb'],'/photo/profile/l/') !== false) || intval($profile['is_default'])) { + $profile['photo'] = z_root() . '/photo/profile/l/' . $channel['channel_id']; + $profile['thumb'] = z_root() . '/photo/profile/m/' . $channel['channel_id']; + } + else { + $profile['photo'] = z_root() . '/photo/' . basename($profile['photo']); + $profile['thumb'] = z_root() . '/photo/' . basename($profile['thumb']); + } } if(count($clean)) { foreach($clean as $k => $v) { $r = dbq("UPDATE profile set " . TQUOT . dbesc($k) . TQUOT . " = '" . dbesc($v) - . "' where profile_guid = '" . dbesc($profile['profile_guid']) + . "' where profile_guid = '" . dbesc($profile['profile_guid']) . "' and uid = " . intval($channel['channel_id'])); } } } } - $addon = array('channel' => $channel,'data' => $arr); - call_hooks('process_channel_sync_delivery',$addon); + $addon = ['channel' => $channel, 'data' => $arr]; + /** + * @hooks process_channel_sync_delivery + * Called when accepting delivery of a 'sync packet' containing structure and table updates from a channel clone. + * * \e array \b channel + * * \e array \b data + */ + call_hooks('process_channel_sync_delivery', $addon); // we should probably do this for all items, but usually we only send one. @@ -3713,7 +3785,6 @@ function process_channel_sync_delivery($sender, $arr, $deliveries) { $DR = new Zotlabs\Zot\DReport(z_root(),$d['hash'],$d['hash'],'sync packet','channel sync delivered'); $result[] = $DR->get(); - } return $result; @@ -3728,7 +3799,6 @@ function process_channel_sync_delivery($sender, $arr, $deliveries) { * * \e string \b xchan_url * @return string */ - function get_rpost_path($observer) { if(! $observer) return ''; @@ -3744,7 +3814,6 @@ function get_rpost_path($observer) { * @param array $x * @return boolean|string return false or a hash */ - function import_author_zot($x) { // Check that we have both a hubloc and xchan record - as occasionally storage calls will fail and @@ -3778,7 +3847,7 @@ function import_author_zot($x) { $site_dead = true; } - // We have valid and somewhat fresh information. + // We have valid and somewhat fresh information. if($r1 && $r2 && $r1[0]['hubloc_updated'] > datetime_convert('UTC','UTC','now - 1 week')) { logger('in cache', LOGGER_DEBUG); @@ -3787,16 +3856,16 @@ function import_author_zot($x) { logger('not in cache or cache stale - probing: ' . print_r($x,true), LOGGER_DEBUG,LOG_INFO); - // The primary hub may be dead. Try to find another one associated with this identity that is - // still alive. If we find one, use that url for the discovery/refresh probe. Otherwise, the dead site - // is all we have and there is no point probing it. Just return the hash indicating we have a - // cached entry and the identity is valid. It's just unreachable until they bring back their - // server from the grave or create another clone elsewhere. + // The primary hub may be dead. Try to find another one associated with this identity that is + // still alive. If we find one, use that url for the discovery/refresh probe. Otherwise, the dead site + // is all we have and there is no point probing it. Just return the hash indicating we have a + // cached entry and the identity is valid. It's just unreachable until they bring back their + // server from the grave or create another clone elsewhere. if($site_dead) { logger('dead site - ignoring', LOGGER_DEBUG,LOG_INFO); - $r = q("select hubloc_url from hubloc left join site on hubloc_url = site_url + $r = q("select hubloc_url from hubloc left join site on hubloc_url = site_url where hubloc_hash = '%s' and site_dead = 0", dbesc($hash) ); @@ -3807,8 +3876,7 @@ function import_author_zot($x) { else { return $hash; } - } - + } $them = array('hubloc_url' => $desturl, 'xchan_guid' => $x['guid'], 'xchan_guid_sig' => $x['guid_sig']); @@ -3835,7 +3903,6 @@ function import_author_zot($x) { * @param array $data * @return array */ - function zot_reply_message_request($data) { $ret = array('success' => false); @@ -3872,7 +3939,7 @@ function zot_reply_message_request($data) { if ($messages) { $env_recips = null; - $r = q("select hubloc.*, site.site_crypto from hubloc left join site on hubloc_url = site_url where hubloc_hash = '%s' and hubloc_error = 0 and hubloc_deleted = 0", + $r = q("select hubloc.*, site.site_crypto from hubloc left join site on hubloc_url = site_url where hubloc_hash = '%s' and hubloc_error = 0 and hubloc_deleted = 0 and site.site_dead = 0 ", dbesc($sender_hash) ); if (! $r) { @@ -3905,6 +3972,14 @@ function zot_reply_message_request($data) { 'msg' => $data_packet )); + + $x = q("select count(outq_hash) as total from outq where outq_delivered = 0"); + if(intval($x[0]['total']) > intval(get_config('system','force_queue_threshold',300))) { + logger('immediate delivery deferred.', LOGGER_DEBUG, LOG_INFO); + update_queue_item($hash); + continue; + } + /* * invoke delivery to send out the notify packet */ @@ -3923,9 +3998,9 @@ function zot_rekey_request($sender,$data) { // newsig is newkey signed with oldkey // The original xchan will remain. In Zot/Receiver we will have imported the new xchan and hubloc to verify - // the packet authenticity. What we will do now is verify that the keychange operation was signed by the - // oldkey, and if so change all the abook, abconfig, group, and permission elements which reference the - // old xchan_hash. + // the packet authenticity. What we will do now is verify that the keychange operation was signed by the + // oldkey, and if so change all the abook, abconfig, group, and permission elements which reference the + // old xchan_hash. if((! $data['old_key']) && (! $data['new_key']) && (! $data['new_sig'])) json_return_and_die($ret); @@ -4048,10 +4123,18 @@ function zotinfo($arr) { $id = $e['channel_id']; - $x = [ 'channel_id' => $id, 'protocols' => ['zot'] ]; - call_hooks('channel_protocols',$x); - $protocols = $x['protocols']; + $x = [ + 'channel_id' => $id, + 'protocols' => ['zot'] + ]; + /** + * @hooks channel_protocols + * * \e int \b channel_id + * * \e array \b protocols + */ + call_hooks('channel_protocols', $x); + $protocols = $x['protocols']; $sys_channel = (intval($e['channel_system']) ? true : false); $special_channel = (($e['channel_pageflags'] & PAGE_PREMIUM) ? true : false); @@ -4104,6 +4187,7 @@ function zotinfo($arr) { if($age = age($p[0]['dob'],$e['channel_timezone'],'')) $profile['age'] = $age; + $profile['gender'] = $p[0]['gender']; $profile['marital'] = $p[0]['marital']; $profile['sexual'] = $p[0]['sexual']; @@ -4163,7 +4247,6 @@ function zotinfo($arr) { $ret['deleted_locally'] = true; - // premium or other channel desiring some contact with potential followers before connecting. // This is a template - %s will be replaced with the follow_url we discover for the return channel. @@ -4207,10 +4290,14 @@ function zotinfo($arr) { check_zotinfo($e,$x,$ret); + /** + * @hooks zot_finger + * Called when a zot-info packet has been requested (this is our webfinger discovery mechanism). + * * \e array The final return array + */ + call_hooks('zot_finger', $ret); - call_hooks('zot_finger',$ret); return($ret); - } @@ -4301,11 +4388,17 @@ function zot_site_info($channel_key = '') { } return $ret['site']; - } -function check_zotinfo($channel,$locations,&$ret) { - +/** + * @brief + * + * @param array $channel + * @param array $locations + * @param[out] array $ret + * \e array \b locations result of zot_encode_locations() + */ +function check_zotinfo($channel, $locations, &$ret) { // logger('locations: ' . print_r($locations,true),LOGGER_DATA, LOG_DEBUG); @@ -4313,7 +4406,7 @@ function check_zotinfo($channel,$locations,&$ret) { // 1. Because magic-auth is reliant on it, ensure that the system channel has a valid hubloc // Force this to be the case if anything is found to be wrong with it. - // @FIXME ensure that the system channel exists in the first place and has an xchan + /// @FIXME ensure that the system channel exists in the first place and has an xchan if($channel['channel_system']) { // the sys channel must have a location (hubloc) @@ -4368,12 +4461,23 @@ function check_zotinfo($channel,$locations,&$ret) { } } +/** + * @brief + * + * @param array $dr + * @return boolean + */ function delivery_report_is_storable($dr) { - if(get_config('system','disable_dreport')) + if(get_config('system', 'disable_dreport')) return false; - call_hooks('dreport_is_storable',$dr); + /** + * @hooks dreport_is_storable + * Called before storing a dreport record to determine whether to store it. + * * \e array + */ + call_hooks('dreport_is_storable', $dr); // let plugins accept or reject - if neither, continue on if(array_key_exists('accept',$dr) && intval($dr['accept'])) @@ -4428,12 +4532,18 @@ function delivery_report_is_storable($dr) { return true; return false; - } - -function update_hub_connected($hub,$sitekey = '') { +/** + * @brief + * + * @param array $hub + * @param string $sitekey (optional, default empty) + * + * @return string hubloc_url + */ +function update_hub_connected($hub, $sitekey = '') { if($sitekey) { @@ -4462,7 +4572,7 @@ function update_hub_connected($hub,$sitekey = '') { // Update our DB to show when we last communicated successfully with this hub // This will allow us to prune dead hubs from using up resources - $t = datetime_convert('UTC','UTC','now - 15 minutes'); + $t = datetime_convert('UTC', 'UTC', 'now - 15 minutes'); $r = q("update hubloc set hubloc_connected = '%s' where hubloc_id = %d and hubloc_sitekey = '%s' and hubloc_connected < '%s' ", dbesc(datetime_convert()), @@ -4492,14 +4602,18 @@ function update_hub_connected($hub,$sitekey = '') { return $hub['hubloc_url']; } - +/** + * @brief Useful to get a health check on a remote site. + * + * This will let us know if any important communication details + * that we may have stored are no longer valid, regardless of xchan details. + * + * @return json_return_and_die() + */ function zot_reply_ping() { $ret = array('success'=> false); - // Useful to get a health check on a remote site. - // This will let us know if any important communication details - // that we may have stored are no longer valid, regardless of xchan details. logger('POST: got ping send pong now back: ' . z_root() , LOGGER_DEBUG ); $ret['success'] = true; @@ -4507,6 +4621,7 @@ function zot_reply_ping() { $ret['site']['url'] = z_root(); $ret['site']['url_sig'] = base64url_encode(rsa_sign(z_root(),get_config('system','prvkey'))); $ret['site']['sitekey'] = get_config('system','pubkey'); + json_return_and_die($ret); } @@ -4524,6 +4639,7 @@ function zot_reply_pickup($data) { if((! $data['secret']) || (! $data['secret_sig'])) { $ret['message'] = 'no verification signature'; logger('mod_zot: pickup: ' . $ret['message'], LOGGER_DEBUG); + json_return_and_die($ret); } @@ -4534,6 +4650,7 @@ function zot_reply_pickup($data) { if(! $r) { $ret['message'] = 'site not found'; logger('mod_zot: pickup: ' . $ret['message']); + json_return_and_die($ret); } @@ -4563,12 +4680,14 @@ function zot_reply_pickup($data) { if($forgery) { $ret['message'] = 'possible site forgery'; logger('mod_zot: pickup: ' . $ret['message']); + json_return_and_die($ret); } if($secret_fail) { $ret['message'] = 'secret validation failed'; logger('mod_zot: pickup: ' . $ret['message']); + json_return_and_die($ret); } @@ -4585,6 +4704,7 @@ function zot_reply_pickup($data) { if(! $r) { $ret['message'] = 'nothing to pick up'; logger('mod_zot: pickup: ' . $ret['message']); + json_return_and_die($ret); } @@ -4630,8 +4750,8 @@ function zot_reply_pickup($data) { $algorithm = zot_best_algorithm(($x) ? $x[0]['site_crypto'] : ''); $encrypted = crypto_encapsulate(json_encode($ret),$sitekey,$algorithm); - json_return_and_die($encrypted); + json_return_and_die($encrypted); /* pickup: end */ } @@ -4659,6 +4779,7 @@ function zot_reply_auth_check($data,$encrypted_packet) { if (! $encrypted_packet) { logger('mod_zot: auth_check packet was not encrypted.'); $ret['message'] .= 'no packet encryption' . EOL; + json_return_and_die($ret); } @@ -4667,11 +4788,12 @@ function zot_reply_auth_check($data,$encrypted_packet) { // garbage collect any old unused notifications - // This was and should be 10 minutes but my hosting provider has time lag between the DB and - // the web server. We should probably convert this to webserver time rather than DB time so - // that the different clocks won't affect it and allow us to keep the time short. - - Zotlabs\Zot\Verify::purge('auth','30 MINUTE'); + /** + * @TODO This was and should be 10 minutes but my hosting provider has time lag between the DB and + * the web server. We should probably convert this to webserver time rather than DB time so + * that the different clocks won't affect it and allow us to keep the time short. + */ + Zotlabs\Zot\Verify::purge('auth', '30 MINUTE'); $y = q("select xchan_pubkey from xchan where xchan_hash = '%s' limit 1", dbesc($sender_hash) @@ -4685,6 +4807,7 @@ function zot_reply_auth_check($data,$encrypted_packet) { if ((! $y) || (! rsa_verify($data['secret'], base64url_decode($data['secret_sig']),$y[0]['xchan_pubkey']))) { logger('mod_zot: auth_check: sender not found or secret_sig invalid.'); $ret['message'] .= 'sender not found or sig invalid ' . print_r($y,true) . EOL; + json_return_and_die($ret); } @@ -4702,6 +4825,7 @@ function zot_reply_auth_check($data,$encrypted_packet) { if (! $c) { logger('mod_zot: auth_check: recipient channel not found.'); $ret['message'] .= 'recipient not found.' . EOL; + json_return_and_die($ret); } @@ -4710,11 +4834,11 @@ function zot_reply_auth_check($data,$encrypted_packet) { // This additionally checks for forged sites since we already stored the expected result in meta // and we've already verified that this is them via zot_gethub() and that their key signed our token - $z = Zotlabs\Zot\Verify::match('auth',$c[0]['channel_id'],$data['secret'],$data['sender']['url']); if (! $z) { logger('mod_zot: auth_check: verification key not found.'); $ret['message'] .= 'verification key not found' . EOL; + json_return_and_die($ret); } @@ -4742,11 +4866,19 @@ function zot_reply_auth_check($data,$encrypted_packet) { json_return_and_die($ret); } + json_return_and_die($ret); } - -function zot_reply_purge($sender,$recipients) { +/** + * @brief + * + * @param array $sender + * @param array $recipients + * + * return json_return_and_die() + */ +function zot_reply_purge($sender, $recipients) { $ret = array('success' => false); @@ -4784,17 +4916,23 @@ function zot_reply_purge($sender,$recipients) { json_return_and_die($ret); } - -function zot_reply_refresh($sender,$recipients) { +/** + * @brief Remote channel info (such as permissions or photo or something) + * has been updated. Grab a fresh copy and sync it. + * + * The difference between refresh and force_refresh is that force_refresh + * unconditionally creates a directory update record, even if no changes were + * detected upon processing. + * + * @param array $sender + * @param array $recipients + * + * @return json_return_and_die() + */ +function zot_reply_refresh($sender, $recipients) { $ret = array('success' => false); - // remote channel info (such as permissions or photo or something) - // has been updated. Grab a fresh copy and sync it. - // The difference between refresh and force_refresh is that - // force_refresh unconditionally creates a directory update record, - // even if no changes were detected upon processing. - if($recipients) { // This would be a permissions update, typically for one connection @@ -4815,7 +4953,7 @@ function zot_reply_refresh($sender,$recipients) { } } else { - // system wide refresh + // system wide refresh $x = zot_refresh(array( 'xchan_guid' => $sender['guid'], @@ -4826,7 +4964,6 @@ function zot_reply_refresh($sender,$recipients) { $ret['success'] = true; json_return_and_die($ret); - } @@ -4849,5 +4986,4 @@ function zot_reply_notify($data) { $ret['success'] = true; json_return_and_die($ret); - } |