diff options
Diffstat (limited to 'include')
45 files changed, 3362 insertions, 949 deletions
diff --git a/include/Contact.php b/include/Contact.php index 5725e06f0..2dab62fd8 100644 --- a/include/Contact.php +++ b/include/Contact.php @@ -77,6 +77,23 @@ function vcard_from_xchan($xchan, $observer = null, $mode = '') { $a = get_app(); + if(! $xchan) { + if($a->poi) { + $xchan = $a->poi; + } + elseif($a->profile['channel_hash']) { + $r = q("select * from xchan where xchan_hash = '%s' limit 1", + dbesc($a->profile['channel_hash']) + ); + if($r) + $xchan = $r[0]; + } + } + + if(! $xchan) + return; + +// FIXME - show connect button to observer if appropriate $connect = false; if(local_user()) { $r = q("select * from abook where abook_xchan = '%s' and abook_channel = %d limit 1", @@ -97,7 +114,7 @@ function vcard_from_xchan($xchan, $observer = null, $mode = '') { return replace_macros(get_markup_template('xchan_vcard.tpl'),array( '$name' => $xchan['xchan_name'], - '$photo' => $xchan['xchan_photo_l'], + '$photo' => ((array_key_exists('photo',$a->profile)) ? $a->profile['photo'] : $xchan['xchan_photo_l']), '$follow' => $xchan['xchan_addr'], '$connect' => $connect, '$newwin' => (($mode === 'chanview') ? t('New window') : ''), @@ -238,7 +255,7 @@ function channel_remove($channel_id, $local = true) { } - q("DELETE FROM `group` 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)); diff --git a/include/ItemObject.php b/include/ItemObject.php index 6088a2c1c..2922ee473 100644 --- a/include/ItemObject.php +++ b/include/ItemObject.php @@ -10,6 +10,7 @@ require_once('boot.php'); /** * An item */ + class Item extends BaseObject { public $data = array(); private $template = 'conv_item.tpl'; @@ -170,6 +171,15 @@ class Item extends BaseObject { ); } + $has_bookmarks = false; + if(is_array($item['term'])) { + foreach($item['term'] as $t) { + if($t['type'] == TERM_BOOKMARK) + $has_bookmarks = true; + } + } + + if($this->is_commentable()) { $like = array( t("I like this \x28toggle\x29"), t("like")); $dislike = array( t("I don't like this \x28toggle\x29"), t("dislike")); @@ -217,6 +227,7 @@ class Item extends BaseObject { 'isotime' => datetime_convert('UTC', date_default_timezone_get(), $item['created'], 'c'), 'localtime' => datetime_convert('UTC', date_default_timezone_get(), $item['created'], 'r'), 'editedtime' => (($item['edited'] != $item['created']) ? sprintf( t('last edited: %s'), datetime_convert('UTC', date_default_timezone_get(), $item['edited'], 'r')) : ''), + 'expiretime' => (($item['expires'] !== '0000-00-00 00:00:00') ? sprintf( t('Expires: %s'), datetime_convert('UTC', date_default_timezone_get(), $item['expires'], 'r')):''), 'lock' => $lock, 'verified' => $verified, 'unverified' => $unverified, @@ -230,11 +241,13 @@ class Item extends BaseObject { 'like' => $like, 'dislike' => ((feature_enabled($conv->get_profile_owner(),'dislike')) ? $dislike : ''), 'share' => $share, - 'plink' => get_plink($item,$mode), + 'rawmid' => $item['mid'], + 'plink' => get_plink($item), 'edpost' => ((feature_enabled($conv->get_profile_owner(),'edit_posts')) ? $edpost : ''), 'star' => ((feature_enabled($conv->get_profile_owner(),'star_posts')) ? $star : ''), 'tagger' => ((feature_enabled($conv->get_profile_owner(),'commtag')) ? $tagger : ''), 'filer' => ((feature_enabled($conv->get_profile_owner(),'filing')) ? $filer : ''), + 'bookmark' => (($conv->get_profile_owner() == local_user() && $has_bookmarks) ? t('Bookmark Links') : ''), 'drop' => $drop, 'multidrop' => ((feature_enabled($conv->get_profile_owner(),'multi_delete')) ? $multidrop : ''), // end toolbar buttons @@ -457,6 +470,11 @@ class Item extends BaseObject { return $this->template; } + + private function set_template($t) { + $this->template = $t; + } + /** * Check if this is a toplevel post */ diff --git a/include/account.php b/include/account.php index ab442ab39..1206223d9 100644 --- a/include/account.php +++ b/include/account.php @@ -111,6 +111,7 @@ function create_account($arr) { $expires = ((x($arr,'expires')) ? intval($arr['expires']) : '0000-00-00 00:00:00'); $default_service_class = get_config('system','default_service_class'); + if($default_service_class === false) $default_service_class = ''; @@ -382,11 +383,11 @@ function user_deny($hash) { if(! count($register)) return false; - $user = q("SELECT account_id FROM account WHERE account_id = %d LIMIT 1", + $account = q("SELECT account_id FROM account WHERE account_id = %d LIMIT 1", intval($register[0]['uid']) ); - if(! $user) + if(! $account) return false; $r = q("DELETE FROM account WHERE account_id = %d LIMIT 1", @@ -400,3 +401,58 @@ function user_deny($hash) { return true; } + + +/** + * @function downgrade_accounts() + * Checks for accounts that have past their expiration date. + * If the account has a service class which is not the site default, + * the service class is reset to the site default and expiration reset to never. + * If the account has no service class it is expired and subsequently disabled. + * called from include/poller.php as a scheduled task. + * + * Reclaiming resources which are no longer within the service class limits is + * not the job of this function, but this can be implemented by plugin if desired. + * Default behaviour is to stop allowing additional resources to be consumed. + */ + + +function downgrade_accounts() { + + $r = q("select * from account where not ( account_flags & %d ) + and account_expires != '0000-00-00 00:00:00' + and account_expires < UTC_TIMESTAMP() ", + intval(ACCOUNT_EXPIRED) + ); + + if(! $r) + return; + + $basic = get_config('system','default_service_class'); + + + foreach($r as $rr) { + + if(($basic) && ($rr['account_service_class']) && ($rr['account_service_class'] != $basic)) { + $x = q("UPDATE account set account_service_class = '%s', account_expires = '%s' + where account_id = %d limit 1", + dbesc($basic), + dbesc('0000-00-00 00:00:00'), + intval($rr['account_id']) + ); + $ret = array('account' => $rr); + call_hooks('account_downgrade', $ret ); + logger('downgrade_accounts: Account id ' . $rr['account_id'] . ' downgraded.'); + } + else { + $x = q("UPDATE account SET account_flags = (account_flags | %d) where account_id = %d limit 1", + intval(ACCOUNT_EXPIRED), + intval($rr['account_id']) + ); + $ret = array('account' => $rr); + call_hooks('account_downgrade', $ret); + logger('downgrade_accounts: Account id ' . $rr['account_id'] . ' expired.'); + } + } +} + diff --git a/include/acl_selectors.php b/include/acl_selectors.php index 930f9967a..749ca75eb 100644 --- a/include/acl_selectors.php +++ b/include/acl_selectors.php @@ -14,7 +14,7 @@ function group_select($selname,$selclass,$preselected = false,$size = 4) { $o .= "<select name=\"{$selname}[]\" id=\"$selclass\" class=\"$selclass\" multiple=\"multiple\" size=\"$size\" >\r\n"; - $r = q("SELECT * FROM `group` WHERE `deleted` = 0 AND `uid` = %d ORDER BY `name` ASC", + $r = q("SELECT * FROM `groups` WHERE `deleted` = 0 AND `uid` = %d ORDER BY `name` ASC", intval(local_user()) ); diff --git a/include/activities.php b/include/activities.php index 73180eae0..4502b758e 100644 --- a/include/activities.php +++ b/include/activities.php @@ -24,6 +24,8 @@ function profile_activity($changed, $value) { $arr['item_flags'] = ITEM_WALL|ITEM_ORIGIN|ITEM_THREAD_TOP; $arr['verb'] = ACTIVITY_UPDATE; $arr['obj_type'] = ACTIVITY_OBJ_PROFILE; + + $arr['$plink'] = z_root() . '/channel/' . $self['channel_address'] . '/?f=&mid=' . $arr['mid']; $A = '[url=' . z_root() . '/channel/' . $self['channel_address'] . ']' . $self['channel_name'] . '[/url]'; diff --git a/include/api.php b/include/api.php index 093839875..dc270167b 100644 --- a/include/api.php +++ b/include/api.php @@ -7,6 +7,7 @@ require_once("oauth.php"); require_once("html2plain.php"); require_once('include/security.php'); require_once('include/photos.php'); +require_once('include/items.php'); /* * @@ -362,7 +363,8 @@ require_once('include/photos.php'); 'location' => ($usr) ? $usr[0]['channel_location'] : '', 'profile_image_url' => $uinfo[0]['xchan_photo_l'], 'url' => $uinfo[0]['xchan_url'], - 'contact_url' => $a->get_baseurl()."/connections/".$uinfo[0]['abook_id'], +//FIXME + 'contact_url' => $a->get_baseurl() . "/connections/".$uinfo[0]['abook_id'], 'protected' => false, 'friends_count' => intval($countfriends), 'created_at' => api_date($uinfo[0]['abook_created']), @@ -555,7 +557,7 @@ require_once('include/photos.php'); function api_photos(&$a,$type) { $album = $_REQUEST['album']; - json_return_and_die(photos_list_photos($a->get_channel(),$a->get_observer()),$album); + json_return_and_die(photos_list_photos($a->get_channel(),$a->get_observer(),$album)); } api_register_func('api/red/photos','api_photos', true); @@ -1241,27 +1243,43 @@ require_once('include/photos.php'); $sql_extra = ''; if ($user_info['self']==1) $sql_extra .= " AND `item`.`wall` = 1 "; + +//FIXME - this isn't yet implemented if ($exclude_replies > 0) $sql_extra .= ' AND `item`.`parent` = `item`.`id`'; - $r = q("SELECT `item`.*, `item`.`id` AS `item_id`, - `contact`.`name`, `contact`.`photo`, `contact`.`url`, `contact`.`rel`, - `contact`.`network`, `contact`.`thumb`, `contact`.`dfrn_id`, `contact`.`self`, - `contact`.`id` AS `cid`, `contact`.`uid` AS `contact-uid` - FROM `item`, `contact` - WHERE `item`.`uid` = %d - AND `item`.`contact-id` = %d - AND `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`deleted` = 0 - AND `contact`.`id` = `item`.`contact-id` - AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0 - $sql_extra - AND `item`.`id`>%d - ORDER BY `item`.`received` DESC LIMIT %d ,%d ", - intval(api_user()), - intval($user_info['id']), - intval($since_id), - intval($start), intval($count) - ); +// $r = q("SELECT `item`.*, `item`.`id` AS `item_id`, +// `contact`.`name`, `contact`.`photo`, `contact`.`url`, `contact`.`rel`, +// `contact`.`network`, `contact`.`thumb`, `contact`.`dfrn_id`, `contact`.`self`, +// `contact`.`id` AS `cid`, `contact`.`uid` AS `contact-uid` +// FROM `item`, `contact` +// WHERE `item`.`uid` = %d +// AND `item`.`contact-id` = %d +// AND `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`deleted` = 0 +// AND `contact`.`id` = `item`.`contact-id` +// AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0 +// $sql_extra +// AND `item`.`id`>%d +// ORDER BY `item`.`received` DESC LIMIT %d ,%d ", +// intval(api_user()), +// intval($user_info['id']), +// intval($since_id), +// intval($start), intval($count) +// ); + + $arr = array( + 'uid' => api_user(), + 'since_id' => $since_id, + 'start' => $start, + 'records' => $count); + + if ($user_info['self']==1) + $arr['wall'] = 1; + else + $arr['cid'] = $user_info['id']; + + $r = items_fetch($arr,get_app()->get_channel(),get_observer_hash()); + $ret = api_format_items($r,$user_info); diff --git a/include/attach.php b/include/attach.php index 0c748cba6..af1159957 100644 --- a/include/attach.php +++ b/include/attach.php @@ -26,6 +26,7 @@ function z_mime_content_type($filename) { 'xml' => 'application/xml', 'swf' => 'application/x-shockwave-flash', 'flv' => 'video/x-flv', + 'epub' => 'application/epub+zip', // images 'png' => 'image/png', @@ -79,17 +80,12 @@ function z_mime_content_type($filename) { if (array_key_exists($ext, $mime_types)) { return $mime_types[$ext]; } + + } -// can't use this because we're just passing a name, e.g. not a file that can be opened -// elseif (function_exists('finfo_open')) { -// $finfo = @finfo_open(FILEINFO_MIME); -// $mimetype = @finfo_file($finfo, $filename); -// @finfo_close($finfo); -// return $mimetype; -// } - else { - return 'application/octet-stream'; - } + + return 'application/octet-stream'; + } @@ -193,13 +189,13 @@ function attach_by_hash($hash,$rev = 0) { $sql_extra = permissions_sql($r[0]['uid']); // Now we'll see if we can access the attachment -dbg(1); + $r = q("SELECT * FROM attach WHERE hash = '%s' and uid = %d $sql_extra LIMIT 1", dbesc($hash), intval($r[0]['uid']) ); -dbg(0); + if(! $r) { $ret['message'] = t('Permission denied.'); return $ret; @@ -242,7 +238,7 @@ function attach_by_hash_nodata($hash,$rev = 0) { // Now we'll see if we can access the attachment - $r = q("select id, aid, uid, hash, filename, filetype, filesize, revision, folder, flags, created, edited, allow_cid, allow_gid, deny_cid, deny_gid from attach where uid = %d and hash = '%s' $sql_extra limit 1", + $r = q("select id, aid, uid, hash, creator, filename, filetype, filesize, revision, folder, flags, created, edited, allow_cid, allow_gid, deny_cid, deny_gid from attach where uid = %d and hash = '%s' $sql_extra limit 1", intval($r[0]['uid']), dbesc($hash) ); @@ -334,9 +330,10 @@ function attach_store($channel,$observer_hash,$options = '',$arr = null) { } $limit = service_class_fetch($channel_id,'attach_upload_limit'); + if($limit !== false) { - $r = q("select sum(filesize) as total from attach where uid = %d ", - intval($channel_id) + $r = q("select sum(filesize) as total from attach where aid = %d ", + intval($channel['channel_account_id']) ); if(($r) && (($r[0]['total'] + $filesize) > ($limit - $existing_size))) { $ret['message'] = upgrade_message(true).sprintf(t("You have reached your limit of %1$.0f Mbytes attachment storage."),$limit / 1024000); @@ -363,11 +360,12 @@ function attach_store($channel,$observer_hash,$options = '',$arr = null) { ); } elseif($options === 'revise') { - $r = q("insert into attach ( aid, uid, hash, filename, filetype, filesize, revision, data, created, edited, allow_cid, allow_gid, deny_cid, deny_gid ) - VALUES ( %d, %d, '%s', '%s', '%s', %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ", + $r = q("insert into attach ( aid, uid, hash, creator, filename, filetype, filesize, revision, data, created, edited, allow_cid, allow_gid, deny_cid, deny_gid ) + VALUES ( %d, %d, '%s', '%s', '%s', '%s', %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ", intval($x[0]['aid']), intval($channel_id), dbesc($x[0]['hash']), + dbesc(get_observer_hash()), dbesc($filename), dbesc($mimetype), intval($filesize), @@ -398,11 +396,12 @@ function attach_store($channel,$observer_hash,$options = '',$arr = null) { } else { - $r = q("INSERT INTO attach ( aid, uid, hash, filename, filetype, filesize, revision, data, created, edited, allow_cid, allow_gid,deny_cid, deny_gid ) - VALUES ( %d, %d, '%s', '%s', '%s', %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ", + $r = q("INSERT INTO attach ( aid, uid, hash, creator, filename, filetype, filesize, revision, data, created, edited, allow_cid, allow_gid,deny_cid, deny_gid ) + VALUES ( %d, %d, '%s', '%s', '%s', '%s', %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ", intval($channel['channel_account_id']), intval($channel_id), dbesc($hash), + dbesc(get_observer_hash()), dbesc($filename), dbesc($mimetype), intval($filesize), @@ -427,7 +426,7 @@ function attach_store($channel,$observer_hash,$options = '',$arr = null) { // Caution: This re-uses $sql_options set further above - $r = q("select id, aid, uid, hash, filename, filetype, filesize, revision, folder, flags, created, edited, allow_cid, allow_gid, deny_cid, deny_gid from attach where uid = %d and hash = '%s' $sql_options limit 1", + $r = q("select id, aid, uid, hash, creator, filename, filetype, filesize, revision, folder, flags, created, edited, allow_cid, allow_gid, deny_cid, deny_gid from attach where uid = %d and hash = '%s' $sql_options limit 1", intval($channel_id), dbesc($hash) ); @@ -487,7 +486,7 @@ function z_readdir($channel_id,$observer_hash,$pathname, $parent_hash = '') { else $paths = array($pathname); - $r = q("select id, aid, uid, hash, filename, filetype, filesize, revision, folder, flags, created, edited, allow_cid, allow_gid, deny_cid, deny_gid from attach where id = %d and folder = '%s' and filename = '%s' and (flags & %d ) " . permissions_sql($channel_id), + $r = q("select id, aid, uid, hash, creator, filename, filetype, filesize, revision, folder, flags, created, edited, allow_cid, allow_gid, deny_cid, deny_gid from attach where id = %d and folder = '%s' and filename = '%s' and (flags & %d ) " . permissions_sql($channel_id), intval($channel_id), dbesc($parent_hash), dbesc($paths[0]), @@ -531,11 +530,14 @@ function attach_mkdir($channel,$observer_hash,$arr = null) { $sql_options = ''; $basepath = 'store/' . $channel['channel_address']; + + logger('attach_mkdir: basepath: ' . $basepath); + if(! is_dir($basepath)) - @mkdir($basepath,STORAGE_DEFAULT_PERMISSIONS,true); + mkdir($basepath,STORAGE_DEFAULT_PERMISSIONS,true); - if(! perm_is_allowed($channel_id, get_observer_hash(),'write_storage')) { + if(! perm_is_allowed($channel_id, $observer_hash,'write_storage')) { $ret['message'] = t('Permission denied.'); return $ret; } @@ -571,16 +573,19 @@ function attach_mkdir($channel,$observer_hash,$arr = null) { $lpath = ''; $lfile = $arr['folder']; - $sql_options = permissions_sql($channel); + $sql_options = permissions_sql($channel['channel_id']); do { + $r = q("select filename, hash, flags, folder from attach where uid = %d and hash = '%s' and ( flags & %d ) $sql_options limit 1", intval($channel['channel_id']), dbesc($lfile), intval(ATTACH_FLAG_DIR) ); + if(! $r) { + logger('attach_mkdir: hash ' . $lfile . ' not found in ' . $lpath); $ret['message'] = t('Path not found.'); return $ret; } @@ -598,28 +603,29 @@ function attach_mkdir($channel,$observer_hash,$arr = null) { $created = datetime_convert(); - $r = q("INSERT INTO attach ( aid, uid, hash, filename, filetype, filesize, revision, folder, flags, data, created, edited, allow_cid, allow_gid, deny_cid, deny_gid ) - VALUES ( %d, %d, '%s', '%s', '%s', %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ", + $r = q("INSERT INTO attach ( aid, uid, hash, creator, filename, filetype, filesize, revision, folder, flags, data, created, edited, allow_cid, allow_gid, deny_cid, deny_gid ) + VALUES ( %d, %d, '%s', '%s', '%s', '%s', %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ", intval($channel['channel_account_id']), intval($channel_id), dbesc($arr['hash']), + dbesc(get_observer_hash()), dbesc($arr['filename']), dbesc('multipart/mixed'), intval(0), intval(0), dbesc($arr['folder']), - intval(ATTACH_FLAG_DIR), - dbesc(''), + intval(ATTACH_FLAG_DIR|ATTACH_FLAG_OS), + dbesc($path), dbesc($created), dbesc($created), - dbesc(($arr && array_key_exists('allow_cid',$arr)) ? $arr['allow_cid'] : ''), - dbesc(($arr && array_key_exists('allow_gid',$arr)) ? $arr['allow_gid'] : ''), - dbesc(($arr && array_key_exists('deny_cid',$arr)) ? $arr['deny_cid'] : ''), - dbesc(($arr && array_key_exists('deny_gid',$arr)) ? $arr['deny_gid'] : '') + dbesc(($arr && array_key_exists('allow_cid',$arr)) ? $arr['allow_cid'] : $channel['channel_allow_cid']), + dbesc(($arr && array_key_exists('allow_gid',$arr)) ? $arr['allow_gid'] : $channel['channel_allow_gid']), + dbesc(($arr && array_key_exists('deny_cid',$arr)) ? $arr['deny_cid'] : $channel['channel_deny_cid']), + dbesc(($arr && array_key_exists('deny_gid',$arr)) ? $arr['deny_gid'] : $channel['channel_deny_gid']) ); if($r) { - if(mkdir($path,STORAGE_DEFAULT_PERMISSIONS)) { + if(mkdir($path,STORAGE_DEFAULT_PERMISSIONS,true)) { $ret['success'] = true; $ret['data'] = $arr; } @@ -633,4 +639,153 @@ function attach_mkdir($channel,$observer_hash,$arr = null) { return $ret; +} + + + +function attach_change_permissions($channel_id,$resource,$allow_cid,$allow_gid,$deny_cid,$deny_gid,$recurse = false) { + + $r = q("select hash, flags from attach where hash = '%s' and uid = %d limit 1", + dbesc($resource), + intval($channel_id) + ); + + if(! $r) + return; + + if($r[0]['flags'] & ATTACH_FLAG_DIR) { + if($recurse) { + $r = q("select hash, flags from attach where folder = '%s' and uid = %d", + dbesc($resource), + intval($channel_id) + ); + if($r) { + foreach($r as $rr) { + attach_change_permissions($channel_id,$resource,$allow_cid,$allow_gid,$deny_cid,$deny_gid,$recurse); + } + } + } + } + + $x = q("update attach set allow_cid = '%s', allow_gid = '%s', deny_cid = '%s', deny_gid = '%s' where hash = '%s' and uid = %d limit 1", + dbesc($allow_cid), + dbesc($allow_gid), + dbesc($deny_cid), + dbesc($deny_gid), + dbesc($resource), + intval($channel_id) + ); + + return; +} + + + +function attach_delete($channel_id,$resource) { + + + $c = q("select channel_address from channel where channel_id = %d limit 1", + intval($channel_id) + ); + + $channel_address = (($c) ? $c[0]['channel_address'] : 'notfound'); + + $r = q("select hash, flags from attach where hash = '%s' and uid = %d limit 1", + dbesc($resource), + intval($channel_id) + ); + + + if(! $r) + return; + + if($r[0]['flags'] & ATTACH_FLAG_DIR) { + $x = q("select hash, flags from attach where folder = '%s' and uid = %d", + dbesc($resource), + intval($channel_id) + ); + if($x) { + foreach($x as $xx) { + attach_delete($channel_id,$xx['hash']); + } + } + } + if($r[0]['flags'] & ATTACH_FLAG_OS) { + $y = q("select data from attach where hash = '%s' and uid = %d limit 1", + dbesc($resource), + intval($channel_id) + ); + + if($y) { + $f = 'store/' . $channel_address . '/' . $y[0]['data']; + if(is_dir($f)) + @rmdir($f); + elseif(file_exists($f)) + unlink($f); + } + } + + $z = q("delete from attach where hash = '%s' and uid = %d limit 1", + dbesc($resource), + intval($channel_id) + ); + + return; +} + + + +function get_cloudpath($arr) { + + $basepath = 'cloud/'; + if($arr['uid']) { + $r = q("select channel_address from channel where channel_id = %d limit 1", + intval($arr['uid']) + ); + if($r) + $basepath .= $r[0]['channel_address'] . '/'; + } + + + $path = $basepath; + + if($arr['folder']) { + + $lpath = ''; + $lfile = $arr['folder']; + + do { + $r = q("select filename, hash, flags, folder from attach where uid = %d and hash = '%s' and ( flags & %d ) + limit 1", + intval($arr['uid']), + dbesc($lfile), + intval(ATTACH_FLAG_DIR) + ); + + if(! $r) + break; + + if($lfile) + $lpath = $r[0]['filename'] . '/' . $lpath; + $lfile = $r[0]['folder']; + + } while ( ($r[0]['folder']) && ($r[0]['flags'] & ATTACH_FLAG_DIR)) ; + + $path .= $lpath; + + } + + $path .= $arr['filename']; + return $path; + +} + + + + +function pipe_streams($in, $out) { + $size = 0; + while (!feof($in)) + $size += fwrite($out,fread($in,8192)); + return $size; }
\ No newline at end of file diff --git a/include/auth.php b/include/auth.php index c0002e6c1..a3b028c73 100644 --- a/include/auth.php +++ b/include/auth.php @@ -34,6 +34,7 @@ function nuke_session() { */ function account_verify_password($email,$pass) { + $r = q("select * from account where account_email = '%s'", dbesc($email) ); @@ -46,7 +47,13 @@ function account_verify_password($email,$pass) { return $record; } } - logger('password failed for ' . $email); + $error = 'password failed for ' . $email; + logger($error); + // Also log failed logins to a separate auth log to reduce overhead for server side intrusion prevention + $authlog = get_config('system', 'authlog'); + if ($authlog) + @file_put_contents($authlog, datetime_convert() . ':' . session_id() . ' ' . $error . "\n", FILE_APPEND); + return null; } @@ -86,7 +93,7 @@ if((isset($_SESSION)) && (x($_SESSION,'authenticated')) && ((! (x($_POST,'auth-p } } - $r = q("select * from hubloc left join xchan on xchan_hash = hubloc_hash where hubloc_hash = '%s' limit 1", + $r = q("select * from xchan left join hubloc on xchan_hash = hubloc_hash where xchan_hash = '%s' limit 1", dbesc($_SESSION['visitor_id']) ); if($r) { @@ -186,7 +193,13 @@ else { } if((! $record) || (! count($record))) { - logger('authenticate: failed login attempt: ' . notags(trim($_POST['username'])) . ' from IP ' . $_SERVER['REMOTE_ADDR']); + $error = 'authenticate: failed login attempt: ' . notags(trim($_POST['username'])) . ' from IP ' . $_SERVER['REMOTE_ADDR']; + logger($error); + // Also log failed logins to a separate auth log to reduce overhead for server side intrusion prevention + $authlog = get_config('system', 'authlog'); + if ($authlog) + @file_put_contents($authlog, datetime_convert() . ':' . session_id() . ' ' . $error . "\n", FILE_APPEND); + notice( t('Login failed.') . EOL ); goaway(z_root()); } @@ -217,3 +230,13 @@ else { authenticate_success($record, true, true); } } + + +function match_openid($authid) { + $r = q("select * from pconfig where cat = 'system' and k = 'openid' and v = '%s' limit 1", + dbesc($authid) + ); + if($r) + return $r[0]['uid']; + return false; +} diff --git a/include/bbcode.php b/include/bbcode.php index 271cace73..1969f8444 100644 --- a/include/bbcode.php +++ b/include/bbcode.php @@ -16,6 +16,40 @@ function tryoembed($match) { return $html; } +function tryzrlaudio($match) { + + $link = $match[1]; + $m = @parse_url($link); + $zrl = false; + if($m['host']) { + $r = q("select hubloc_url from hubloc where hubloc_host = '%s' limit 1", + dbesc($m['host']) + ); + if($r) + $zrl = true; + } + if($zrl) + $link = zid($link); + return '<audio src="' . $link . '" controls="controls" ><a href="' . $link . '">' . $link . '</a></audio>'; +} + +function tryzrlvideo($match) { + $link = $match[1]; + $m = @parse_url($link); + $zrl = false; + if($m['host']) { + $r = q("select hubloc_url from hubloc where hubloc_host = '%s' limit 1", + dbesc($m['host']) + ); + if($r) + $zrl = true; + } + if($zrl) + $link = zid($link); + return '<video src="' . $link . '" controls="controls" width="' . get_app()->videowidth . '" height="' . $a->videoheight . '"><a href="' . $link . '">' . $link . '</a></video>'; + +} + // [noparse][i]italic[/i][/noparse] turns into // [noparse][ i ]italic[ /i ][/noparse], // to hide them from parser. @@ -105,21 +139,24 @@ function bb_parse_crypt($match) { $attributes = $match[1]; $algorithm = ""; + preg_match("/alg='(.*?)'/ism", $attributes, $matches); if ($matches[1] != "") - $algorithm = html_entity_decode($matches[1],ENT_QUOTES,'UTF-8'); + $algorithm = $matches[1]; preg_match("/alg=\"\;(.*?)\"\;/ism", $attributes, $matches); if ($matches[1] != "") - $algorithm = html_entity_decode($matches[1],ENT_QUOTES,'UTF-8'); + $algorithm = $matches[1]; $hint = ""; + + preg_match("/hint='(.*?)'/ism", $attributes, $matches); if ($matches[1] != "") - $hint = html_entity_decode($matches[1],ENT_QUOTES,'UTF-8'); + $hint = $matches[1]; preg_match("/hint=\"\;(.*?)\"\;/ism", $attributes, $matches); if ($matches[1] != "") - $hint = html_entity_decode($matches[1],ENT_QUOTES,'UTF-8'); + $hint = $matches[1]; $x = random_string(); @@ -129,6 +166,9 @@ function bb_parse_crypt($match) { } +function bb_qr($match) { + return '<img class="zrl" src="' . z_root() . '/photo/qr?f=&qr=' . urlencode($match[1]) . '" alt="' . t('QR code') . '" title="' . htmlspecialchars($match[1],ENT_QUOTES,'UTF-8') . '" />'; +} function bb_ShareAttributes($match) { @@ -183,6 +223,10 @@ function bb_ShareAttributes($match) { return($text); } +function bb_location($match) { + // not yet implemented +} + function bb_ShareAttributesSimple($match) { $attributes = $match[1]; @@ -218,6 +262,56 @@ function rpost_callback($match) { } } +function bb_sanitize_style($input) { + //whitelist property limits (0 = no limitation) + $w = array( // color properties + "color" => 0, + "background-color" => 0, + // box properties + "padding" => array("px"=>100, "%"=>0, "em"=>2, "ex"=>2, "mm"=>0, "cm"=>0, "in"=>0, "pt"=>0, "pc"=>0), + "margin" => array("px"=>100, "%"=>0, "em"=>2, "ex"=>2, "mm"=>0, "cm"=>0, "in"=>0, "pt"=>0, "pc"=>0), + "border" => array("px"=>100, "%"=>0, "em"=>2, "ex"=>2, "mm"=>0, "cm"=>0, "in"=>0, "pt"=>0, "pc"=>0), + "float" => 0, + "clear" => 0, + // text properties + "text-decoration" => 0, + + ); + + $css_string = $input[1]; + $a = explode(';',$css_string); + foreach($a as $parts){ + list($k, $v) = explode(':', $parts); + $css[ trim($k) ] = trim($v); + } + + // sanitize properties + $b = array_merge(array_diff_key($css, $w), array_diff_key($w, $css)); + $css = array_diff_key($css, $b); + + foreach($css as $key => $value) { + if($w[$key] != null) { + foreach($w[$key] as $limit_key => $limit_value) { + //sanitize values + if(strpos($value, $limit_key)) { + $value = preg_replace_callback( + "/(\S.*?)$limit_key/ism", + function($match) use($limit_value, $limit_key) { + if($match[1] > $limit_value) { + return $limit_value . $limit_key; + } else { + return $match[1] . $limit_key; + } + }, + $value + ); + } + } + } + $css_string_san .= $key . ":" . $value ."; "; + } + return "<span style=\"" . $css_string_san . "\">" . $input[2] . "</span>"; +} // BBcode 2 HTML was written by WAY2WEB.net // extended to work with Mistpark/Friendica/Red - Mike Macgirvin @@ -226,15 +320,6 @@ function bbcode($Text,$preserve_nl = false, $tryoembed = true) { $a = get_app(); - // Extract the private images which use data url's since preg has issues with - // large data sizes. Stash them away while we do bbcode conversion, and then put them back - // in after we've done all the regex matching. We cannot use any preg functions to do this. - - $extracted = bb_extract_images($Text); - $Text = $extracted['body']; - $saved_image = $extracted['images']; - - // Move all spaces out of the tags $Text = preg_replace("/\[(\w*)\](\s*)/ism", '$2[$1]', $Text); $Text = preg_replace("/(\s*)\[\/(\w*)\]/ism", '[/$2]$1', $Text); @@ -250,6 +335,11 @@ function bbcode($Text,$preserve_nl = false, $tryoembed = true) { $Text = preg_replace_callback("/\[pre\](.*?)\[\/pre\]/ism", 'bb_spacefy',$Text); } +// Not yet implemented - thinking this should display a map or perhaps be a map directive +// if (strpos($Text,'[location]') !== false) { +// $Text = preg_replace_callback("/\[location\](.*?)\[\/location\]/ism", 'bb_location',$Text); +// } + // If we find any event code, turn it into an event. @@ -260,6 +350,7 @@ function bbcode($Text,$preserve_nl = false, $tryoembed = true) { // process [observer] tags before we do anything else because we might // be stripping away stuff that then doesn't need to be worked on anymore + $observer = $a->get_observer(); if ((strpos($Text,'[/observer]') !== false) || (strpos($Text,'[/rpost]') !== false)) { if ($observer) { @@ -273,6 +364,21 @@ function bbcode($Text,$preserve_nl = false, $tryoembed = true) { } } + $channel = $a->get_channel(); + if (strpos($Text,'[/channel]') !== false) { + if ($channel) { + $Text = preg_replace("/\[channel\=1\](.*?)\[\/channel\]/ism", '$1', $Text); + $Text = preg_replace("/\[channel\=0\].*?\[\/channel\]/ism", '', $Text); + } else { + $Text = preg_replace("/\[channel\=1\].*?\[\/channel\]/ism", '', $Text); + $Text = preg_replace("/\[channel\=0\](.*?)\[\/channel\]/ism", '$1', $Text); + } + } + + + $Text = str_replace(array('[baseurl]','[sitename]'),array(z_root(),get_config('system','sitename')),$Text); + + // Replace any html brackets with HTML Entities to prevent executing HTML or script // Don't use strip_tags here because it breaks [url] search by replacing & with amp @@ -325,6 +431,12 @@ function bbcode($Text,$preserve_nl = false, $tryoembed = true) { if (strpos($Text,'http') !== false) { $Text = preg_replace("/([^\]\='".'"'."]|^)(https?\:\/\/$urlchars+)/ism", '$1<a href="$2" >$2</a>', $Text); } + + if (strpos($Text,'[/qr]') !== false) { + $Text = preg_replace_callback("/\[qr\](.*?)\[\/qr\]/ism","bb_qr",$Text); + } + + if (strpos($Text,'[/share]') !== false) { $Text = preg_replace_callback("/\[share(.*?)\](.*?)\[\/share\]/ism","bb_ShareAttributes",$Text); } @@ -334,10 +446,14 @@ function bbcode($Text,$preserve_nl = false, $tryoembed = true) { } } if (strpos($Text,'[/url]') !== false) { + $Text = preg_replace("/\#\^\[url\]([$URLSearchString]*)\[\/url\]/ism", '<span class="bookmark-identifier">#^</span><a class="bookmark" href="$1" >$1</a>', $Text); + $Text = preg_replace("/\#\^\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", '<span class="bookmark-identifier">#^</span><a class="bookmark" href="$1" >$2</a>', $Text); $Text = preg_replace("/\[url\]([$URLSearchString]*)\[\/url\]/ism", '<a href="$1" >$1</a>', $Text); $Text = preg_replace("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", '<a href="$1" >$2</a>', $Text); } if (strpos($Text,'[/zrl]') !== false) { + $Text = preg_replace("/\#\^\[zrl\]([$URLSearchString]*)\[\/zrl\]/ism", '<span class="bookmark-identifier">#^</span><a class="zrl bookmark" href="$1" >$1</a>', $Text); + $Text = preg_replace("/\#\^\[zrl\=([$URLSearchString]*)\](.*?)\[\/zrl\]/ism", '<span class="bookmark-identifier">#^</span><a class="zrl bookmark" href="$1" >$2</a>', $Text); $Text = preg_replace("/\[zrl\]([$URLSearchString]*)\[\/zrl\]/ism", '<a class="zrl" href="$1" >$1</a>', $Text); $Text = preg_replace("/\[zrl\=([$URLSearchString]*)\](.*?)\[\/zrl\]/ism", '<a class="zrl" href="$1" >$2</a>', $Text); } @@ -383,14 +499,6 @@ function bbcode($Text,$preserve_nl = false, $tryoembed = true) { // Check for list text $Text = str_replace("[*]", "<li>", $Text); - // Check for style sheet commands - if (strpos($Text,'[/style]') !== false) { - $Text = preg_replace("(\[style=(.*?)\](.*?)\[\/style\])ism","<span style=\"$1;\">$2</span>",$Text); - } - // Check for CSS classes - if (strpos($Text,'[/class]') !== false) { - $Text = preg_replace("(\[class=(.*?)\](.*?)\[\/class\])ism","<span class=\"$1\">$2</span>",$Text); - } // handle nested lists $endlessloop = 0; @@ -418,7 +526,7 @@ function bbcode($Text,$preserve_nl = false, $tryoembed = true) { if (strpos($Text,'[tr]') !== false) { $Text = preg_replace("/\[tr\](.*?)\[\/tr\]/sm", '<tr>$1</tr>' ,$Text); } - if (strpos($Text,'[table]') !== false) { + if (strpos($Text,'[/table]') !== false) { $Text = preg_replace("/\[table\](.*?)\[\/table\]/sm", '<table>$1</table>' ,$Text); $Text = preg_replace("/\[table border=1\](.*?)\[\/table\]/sm", '<table border="1" >$1</table>' ,$Text); $Text = preg_replace("/\[table border=0\](.*?)\[\/table\]/sm", '<table border="0" >$1</table>' ,$Text); @@ -480,36 +588,74 @@ function bbcode($Text,$preserve_nl = false, $tryoembed = true) { "<br /><strong class=".'"author"'.">" . $t_wrote . "</strong><blockquote>$2</blockquote>", $Text); - // [img=widthxheight]image source[/img] - //$Text = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", '<img src="$3" style="height: $2px; width: $1px;" >', $Text); - if (strpos($Text,'[/img]') !== false) { - $Text = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", '<img src="$3" style="width: $1px;" >', $Text); - } - if (strpos($Text,'[/zmg]') !== false) { - $Text = preg_replace("/\[zmg\=([0-9]*)x([0-9]*)\](.*?)\[\/zmg\]/ism", '<img class="zrl" src="$3" style="width: $1px;" >', $Text); - } // Images // [img]pathtoimage[/img] - if (strpos($Text,'[/img]') !== false) { + if (strpos($Text,'[/img]') !== false) { $Text = preg_replace("/\[img\](.*?)\[\/img\]/ism", '<img src="$1" alt="' . t('Image/photo') . '" />', $Text); } - if (strpos($Text,'[/zmg]') !== false) { + if (strpos($Text,'[/zmg]') !== false) { $Text = preg_replace("/\[zmg\](.*?)\[\/zmg\]/ism", '<img class="zrl" src="$1" alt="' . t('Image/photo') . '" />', $Text); } + // [img float={left, right}]pathtoimage[/img] + if (strpos($Text,'[/img]') !== false) { + $Text = preg_replace("/\[img float=left\](.*?)\[\/img\]/ism", '<img src="$1" style="float: left;" alt="' . t('Image/photo') . '" />', $Text); + } + if (strpos($Text,'[/img]') !== false) { + $Text = preg_replace("/\[img float=right\](.*?)\[\/img\]/ism", '<img src="$1" style="float: right;" alt="' . t('Image/photo') . '" />', $Text); + } + if (strpos($Text,'[/zmg]') !== false) { + $Text = preg_replace("/\[zmg float=left\](.*?)\[\/zmg\]/ism", '<img class="zrl" src="$1" style="float: left;" alt="' . t('Image/photo') . '" />', $Text); + } + if (strpos($Text,'[/zmg]') !== false) { + $Text = preg_replace("/\[zmg float=right\](.*?)\[\/zmg\]/ism", '<img class="zrl" src="$1" style="float: right;" alt="' . t('Image/photo') . '" />', $Text); + } + + // [img=widthxheight]pathtoimage[/img] + if (strpos($Text,'[/img]') !== false) { + $Text = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", '<img src="$3" style="width: $1px;" alt="' . t('Image/photo') . '" >', $Text); + } + if (strpos($Text,'[/zmg]') !== false) { + $Text = preg_replace("/\[zmg\=([0-9]*)x([0-9]*)\](.*?)\[\/zmg\]/ism", '<img class="zrl" src="$3" style="width: $1px;" alt="' . t('Image/photo') . '" >', $Text); + } + + // [img=widthxheight float={left, right}]pathtoimage[/img] + if (strpos($Text,'[/img]') !== false) { + $Text = preg_replace("/\[img\=([0-9]*)x([0-9]*) float=left\](.*?)\[\/img\]/ism", '<img src="$3" style="width: $1px; float: left;" alt="' . t('Image/photo') . '" >', $Text); + } + if (strpos($Text,'[/img]') !== false) { + $Text = preg_replace("/\[img\=([0-9]*)x([0-9]*) float=right\](.*?)\[\/img\]/ism", '<img src="$3" style="width: $1px; float: right;" alt="' . t('Image/photo') . '" >', $Text); + } + if (strpos($Text,'[/zmg]') !== false) { + $Text = preg_replace("/\[zmg\=([0-9]*)x([0-9]*) float=left\](.*?)\[\/zmg\]/ism", '<img class="zrl" src="$3" style="width: $1px; float: left;" alt="' . t('Image/photo') . '" >', $Text); + } + if (strpos($Text,'[/zmg]') !== false) { + $Text = preg_replace("/\[zmg\=([0-9]*)x([0-9]*) float=right\](.*?)\[\/zmg\]/ism", '<img class="zrl" src="$3" style="width: $1px; float: right;" alt="' . t('Image/photo') . '" >', $Text); + } + + // style (sanitized) + if (strpos($Text,'[/style]') !== false) { + $Text = preg_replace_callback("(\[style=(.*?)\](.*?)\[\/style\])ism", "bb_sanitize_style", $Text); + } + + // crypt if (strpos($Text,'[/crypt]') !== false) { $x = random_string(); $Text = preg_replace("/\[crypt\](.*?)\[\/crypt\]/ism",'<br/><div id="' . $x . '"><img src="' .$a->get_baseurl() . '/images/lock_icon.gif" onclick="red_decrypt(\'rot13\',\'\',\'$1\',\'#' . $x . '\');" alt="' . t('Encrypted content') . '" title="' . t('Encrypted content') . '" /><br /></div>', $Text); $Text = preg_replace_callback("/\[crypt (.*?)\](.*?)\[\/crypt\]/ism", 'bb_parse_crypt', $Text); } + + // html5 video and audio + if (strpos($Text,'[/video]') !== false) { + $Text = preg_replace_callback("/\[video\](.*?\.(ogg|ogv|oga|ogm|webm|mp4))\[\/video\]/ism", 'tryzrlvideo', $Text); + } + if (strpos($Text,'[/audio]') !== false) { + $Text = preg_replace_callback("/\[audio\](.*?\.(ogg|ogv|oga|ogm|webm|mp4|mp3))\[\/audio\]/ism", 'tryzrlaudio', $Text); + } + // Try to Oembed if ($tryoembed) { - if (strpos($Text,'[/video]') !== false) { - $Text = preg_replace("/\[video\](.*?\.(ogg|ogv|oga|ogm|webm|mp4))\[\/video\]/ism", '<video src="$1" controls="controls" width="' . $a->videowidth . '" height="' . $a->videoheight . '"><a href="$1">$1</a></video>', $Text); - } - if (strpos($Text,'[/audio]') !== false) { - $Text = preg_replace("/\[audio\](.*?\.(ogg|ogv|oga|ogm|webm|mp4|mp3))\[\/audio\]/ism", '<audio src="$1" controls="controls"><a href="$1">$1</a></audio>', $Text); - } + if (strpos($Text,'[/video]') !== false) { $Text = preg_replace_callback("/\[video\](.*?)\[\/video\]/ism", 'tryoembed', $Text); } @@ -527,7 +673,6 @@ function bbcode($Text,$preserve_nl = false, $tryoembed = true) { } - // html5 video and audio if ($tryoembed){ @@ -570,8 +715,6 @@ function bbcode($Text,$preserve_nl = false, $tryoembed = true) { else $Text = preg_replace("/\[vimeo\]([0-9]+)(.*?)\[\/vimeo\]/ism", "http://vimeo.com/$1", $Text); } -// $Text = preg_replace("/\[youtube\](.*?)\[\/youtube\]/", '<object width="425" height="350" type="application/x-shockwave-flash" data="http://www.youtube.com/v/$1" ><param name="movie" value="http://www.youtube.com/v/$1"></param><!--[if IE]><embed src="http://www.youtube.com/v/$1" type="application/x-shockwave-flash" width="425" height="350" /><![endif]--></object>', $Text); - // oembed tag $Text = oembed_bbcode2html($Text); @@ -614,28 +757,6 @@ function bbcode($Text,$preserve_nl = false, $tryoembed = true) { $Text = preg_replace("/\<(.*?)(src|href)=\"[^hfm](.*?)\>/ism",'<$1$2="">',$Text); - if($saved_image) - $Text = bb_replace_images($Text, $saved_image); - - // Clean up the HTML by loading and saving the HTML with the DOM - // Only do it when it has to be done - for performance reasons -// if (!$tryoembed) {// -// $doc = new DOMDocument(); -// $doc->preserveWhiteSpace = false; - -// $Text = mb_convert_encoding($Text, 'HTML-ENTITIES', "UTF-8"); - -// $doctype = '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">'; -// @$doc->loadHTML($doctype."<html><body>".$Text."</body></html>"); - -// $Text = $doc->saveHTML(); -// $Text = str_replace(array("<html><body>", "</body></html>", $doctype), array("", "", ""), $Text); - -// $Text = str_replace('<br></li>','</li>', $Text); - -// $Text = mb_convert_encoding($Text, "UTF-8", 'HTML-ENTITIES'); -// } - call_hooks('bbcode',$Text); return $Text; diff --git a/include/bookmarks.php b/include/bookmarks.php new file mode 100644 index 000000000..99cb60e64 --- /dev/null +++ b/include/bookmarks.php @@ -0,0 +1,55 @@ +<?php /** @file */ + +require_once('include/menu.php'); + +function bookmark_add($channel,$sender,$taxonomy,$private) { + + $iarr = array(); + $channel_id = $channel['channel_id']; + + if($private) + $iarr['contact_allow'] = array($channel['channel_hash']); + $iarr['mitem_link'] = $taxonomy['url']; + $iarr['mitem_desc'] = $taxonomy['term']; + $iarr['mitem_flags'] = 0; + + $m = @parse_url($taxonomy['url']); + $zrl = false; + if($m['host']) { + $r = q("select hubloc_url from hubloc where hubloc_host = '%s' limit 1", + dbesc($m['host']) + ); + if($r) + $zrl = true; + } + + if($zrl) + $iarr['mitem_flags'] |= MENU_ITEM_ZID; + + $arr = array(); + $arr['menu_name'] = substr($sender['xchan_hash'],0,16) . ' ' . $sender['xchan_name']; + $arr['menu_desc'] = sprintf( t('%1$s\'s bookmarks'), $sender['xchan_name']); + $arr['menu_flags'] = (($sender['xchan_hash'] === $channel['channel_hash']) ? MENU_BOOKMARK : MENU_SYSTEM|MENU_BOOKMARK); + $arr['menu_channel_id'] = $channel_id; + + $x = menu_list($arr['menu_channel_id'],$arr['menu_name'],$arr['menu_flags']); + if($x) + $menu_id = $x[0]['menu_id']; + else + $menu_id = menu_create($arr); + if(! $menu_id) { + logger('bookmark_add: unable to create menu ' . $arr['menu_name']); + return; + } + logger('add_bookmark: menu_id ' . $menu_id); + $r = q("select * from menu_item where mitem_link = '%s' and mitem_menu_id = %d and mitem_channel_id = %d limit 1", + dbesc($iarr['mitem_link']), + intval($menu_id), + intval($channel_id) + ); + if($r) + logger('add_bookmark: duplicate menu entry', LOGGER_DEBUG); + if(! $r) + $r = menu_add_item($menu_id,$channel_id,$iarr); + return $r; +}
\ No newline at end of file diff --git a/include/chat.php b/include/chat.php new file mode 100644 index 000000000..5a17230e0 --- /dev/null +++ b/include/chat.php @@ -0,0 +1,184 @@ +<?php /** @file */ + + +function chatroom_create($channel,$arr) { + + $ret = array('success' => false); + + $name = trim($arr['name']); + if(! $name) { + $ret['message'] = t('Missing room name'); + return $ret; + } + + $r = q("select cr_id from chatroom where cr_uid = %d and cr_name = '%s' limit 1", + intval($channel['channel_id']), + dbesc($name) + ); + if($r) { + $ret['message'] = t('Duplicate room name'); + return $ret; + } + + $r = q("select count(cr_id) as total from chatroom where cr_aid = %d", + intval($channel['channel_account_id']) + ); + if($r) + $limit = service_class_fetch($channel_id,'chatrooms'); + + if(($r) && ($limit !== false) && ($r[0]['total'] >= $limit)) { + $ret['message'] = upgrade_message(); + return $ret; + } + + if(! array_key_exists('expire',$arr)) + $arr['expire'] = 120; // minutes, e.g. 2 hours + + $created = datetime_convert(); + + $x = q("insert into chatroom ( cr_aid, cr_uid, cr_name, cr_created, cr_edited, cr_expire, allow_cid, allow_gid, deny_cid, deny_gid ) + values ( %d, %d , '%s', '%s', '%s', %d, '%s', '%s', '%s', '%s' ) ", + intval($channel['channel_account_id']), + intval($channel['channel_id']), + dbesc($name), + dbesc($created), + dbesc($created), + intval($arr['expire']), + dbesc($arr['allow_cid']), + dbesc($arr['allow_gid']), + dbesc($arr['deny_cid']), + dbesc($arr['deny_gid']) + ); + + if($x) + $ret['success'] = true; + + return $ret; +} + + +function chatroom_destroy($channel,$arr) { + + $ret = array('success' => false); + if(intval($arr['cr_id'])) + $sql_extra = " and cr_id = " . intval($arr['cr_id']) . " "; + elseif(trim($arr['cr_name'])) + $sql_extra = " and cr_name = '" . protect_sprintf(dbesc(trim($arr['cr_name']))) . "' "; + else { + $ret['message'] = t('Invalid room specifier.'); + return $ret; + } + + $r = q("select * from chatroom where cr_uid = %d $sql_extra limit 1", + intval($channel['channel_id']) + ); + if(! $r) { + $ret['message'] = t('Invalid room specifier.'); + return $ret; + } + + q("delete from chatroom where cr_id = %d limit 1", + intval($r[0]['cr_id']) + ); + if($r[0]['cr_id']) { + q("delete from chatpresence where cp_room = %d", + intval($r[0]['cr_id']) + ); + } + $ret['success'] = true; + return $ret; +} + + +function chatroom_enter($observer_xchan,$room_id,$status,$client) { + + if(! $room_id || ! $observer_xchan) + return; + + $r = q("select * from chatroom where cr_id = %d limit 1", + intval($room_id) + ); + if(! $r) { + notice( t('Room not found.') . EOL); + return false; + } + require_once('include/security.php'); + $sql_extra = permissions_sql($r[0]['cr_uid']); + + $x = q("select * from chatroom where cr_id = %d and cr_uid = %d $sql_extra limit 1", + intval($room_id), + intval($r[0]['cr_uid']) + ); + if(! $x) { + notice( t('Permission denied.') . EOL); + return false; + } + + $limit = service_class_fetch($r[0]['cr_uid'],'chatters_inroom'); + if($limit !== false) { + $x = q("select count(*) as total from chatpresence where cp_room = %d", + intval($room_id) + ); + if($x && $x[0]['total'] > $limit) { + notice( t('Room is full') . EOL); + return false; + } + } + + if(intval($x[0]['cr_expire'])) + $r = q("delete from chat where created < UTC_TIMESTAMP() - INTERVAL " . intval($x[0]['cr_expire']) . " MINUTE and chat_room = " . intval($x[0]['cr_id'])); + + $r = q("select * from chatpresence where cp_xchan = '%s' and cp_room = %d limit 1", + dbesc($observer_xchan), + intval($room_id) + ); + if($r) { + q("update chatpresence set cp_last = '%s' where cp_id = %d and cp_client = '%s' limit 1", + dbesc(datetime_convert()), + intval($r[0]['cp_id']), + dbesc($client) + ); + return true; + } + + $r = q("insert into chatpresence ( cp_room, cp_xchan, cp_last, cp_status, cp_client ) + values ( %d, '%s', '%s', '%s', '%s' )", + intval($room_id), + dbesc($observer_xchan), + dbesc(datetime_convert()), + dbesc($status), + dbesc($client) + ); + return $r; +} + + +function chatroom_leave($observer_xchan,$room_id,$client) { + if(! $room_id || ! $observer_xchan) + return; + + $r = q("select * from chatpresence where cp_xchan = '%s' and cp_room = %d and cp_client = '%s' limit 1", + dbesc($observer_xchan), + intval($room_id), + dbesc($client) + ); + if($r) { + q("delete from chatpresence where cp_id = %d limit 1", + intval($r[0]['cp_id']) + ); + } + + return true; +} + + +function chatroom_list($uid) { + require_once('include/security.php'); + $sql_extra = permissions_sql($uid); + + $r = q("select cr_name, cr_id, count(cp_id) as cr_inroom from chatroom left join chatpresence on cr_id = cp_room where cr_uid = %d $sql_extra group by cr_name order by cr_name", + intval($uid) + ); + + return $r; +} diff --git a/include/config.php b/include/config.php index bccf0737f..8d98d56fa 100644 --- a/include/config.php +++ b/include/config.php @@ -65,7 +65,7 @@ function get_config($family, $key) { if(! array_key_exists($key,$a->config[$family])) { return false; } - return ((preg_match('|^a:[0-9]+:{.*}$|s', $a->config[$family][$key])) + return ((! is_array($a->config[$family][$key])) && (preg_match('|^a:[0-9]+:{.*}$|s', $a->config[$family][$key])) ? unserialize($a->config[$family][$key]) : $a->config[$family][$key] ); @@ -174,8 +174,8 @@ function get_pconfig($uid,$family, $key, $instore = false) { if((! array_key_exists($family,$a->config[$uid])) || (! array_key_exists($key,$a->config[$uid][$family]))) return false; - - return ((preg_match('|^a:[0-9]+:{.*}$|s', $a->config[$uid][$family][$key])) + + return ((! is_array($a->config[$uid][$family][$key])) && (preg_match('|^a:[0-9]+:{.*}$|s', $a->config[$uid][$family][$key])) ? unserialize($a->config[$uid][$family][$key]) : $a->config[$uid][$family][$key] ); @@ -304,7 +304,7 @@ function get_xconfig($xchan,$family, $key) { if((! array_key_exists($family,$a->config[$xchan])) || (! array_key_exists($key,$a->config[$xchan][$family]))) return false; - return ((preg_match('|^a:[0-9]+:{.*}$|s', $a->config[$xchan][$family][$key])) + return ((! is_array($a->config[$xchan][$family][$key])) && (preg_match('|^a:[0-9]+:{.*}$|s', $a->config[$xchan][$family][$key])) ? unserialize($a->config[$xchan][$family][$key]) : $a->config[$xchan][$family][$key] ); diff --git a/include/contact_selectors.php b/include/contact_selectors.php index b56a77937..a3cfd2489 100644 --- a/include/contact_selectors.php +++ b/include/contact_selectors.php @@ -5,7 +5,7 @@ function contact_profile_assign($current) { $o = ''; - $o .= "<select id=\"contact-profile-selector\" name=\"profile-assign\" />\r\n"; + $o .= "<select id=\"contact-profile-selector\" name=\"profile_assign\" />\r\n"; $r = q("SELECT profile_guid, profile_name FROM `profile` WHERE `uid` = %d", intval($_SESSION['uid'])); diff --git a/include/contact_widgets.php b/include/contact_widgets.php index cc0a3d617..482bbed78 100644 --- a/include/contact_widgets.php +++ b/include/contact_widgets.php @@ -25,7 +25,8 @@ function findpeople_widget() { '$suggest' => t('Channel Suggestions'), '$similar' => '', // FIXME and uncomment when mod/match working // t('Similar Interests'), '$random' => t('Random Profile'), - '$inv' => t('Invite Friends') + '$inv' => t('Invite Friends'), + '$loggedin' => local_user() )); } diff --git a/include/conversation.php b/include/conversation.php index f5fc9da93..16ac4e909 100644 --- a/include/conversation.php +++ b/include/conversation.php @@ -687,12 +687,13 @@ function conversation(&$a, $items, $mode, $update, $page_mode = 'traditional', $ 'isotime' => datetime_convert('UTC', date_default_timezone_get(), $item['created'], 'c'), 'localtime' => datetime_convert('UTC', date_default_timezone_get(), $item['created'], 'r'), 'editedtime' => (($item['edited'] != $item['created']) ? sprintf( t('last edited: %s'), datetime_convert('UTC', date_default_timezone_get(), $item['edited'], 'r')) : ''), + 'expiretime' => (($item['expires'] !== '0000-00-00 00:00:00') ? sprintf( t('Expires: %s'), datetime_convert('UTC', date_default_timezone_get(), $item['expires'], 'r')):''), 'location' => $location, 'indent' => '', 'owner_name' => $owner_name, 'owner_url' => $owner_url, 'owner_photo' => $owner_photo, - 'plink' => get_plink($item,$mode), + 'plink' => get_plink($item,false), 'edpost' => false, 'isstarred' => $isstarred, 'star' => $star, @@ -782,6 +783,9 @@ function conversation(&$a, $items, $mode, $update, $page_mode = 'traditional', $ // $tx1 = dba_timer(); $item_object = new Item($item); $conv->add_thread($item_object); + if($page_mode === 'list') + $item_object->set_template('conv_list.tpl'); + // $tx2 = dba_timer(); // if($mode === 'network') // profiler($tx1,$tx2,'add thread ' . $item['id']); @@ -908,14 +912,14 @@ function item_photo_menu($item){ } $profile_link = z_root() . "/chanview/?f=&hash=" . $item['author_xchan']; - $pm_url = $a->get_baseurl($ssl_state) . '/message/new/?f=&hash=' . $item['author_xchan']; + $pm_url = $a->get_baseurl($ssl_state) . '/mail/new/?f=&hash=' . $item['author_xchan']; if($a->contacts && array_key_exists($item['author_xchan'],$a->contacts)) $contact = $a->contacts[$item['author_xchan']]; if($contact) { $poke_link = $a->get_baseurl($ssl_state) . '/poke/?f=&c=' . $contact['abook_id']; - $contact_url = $a->get_baseurl($ssl_state) . '/connections/' . $contact['abook_id']; + $contact_url = $a->get_baseurl($ssl_state) . '/connedit/' . $contact['abook_id']; $posts_link = $a->get_baseurl($ssl_state) . '/network/?cid=' . $contact['abook_id']; $clean_url = normalise_link($item['author-link']); @@ -1108,7 +1112,7 @@ function status_editor($a,$x,$popup=false) { '$shortsetloc' => t('set location'), '$noloc' => t('Clear browser location'), '$shortnoloc' => t('clear location'), - '$title' => ((x($x,'title')) ? htmlspecialchars($x['title']) : ''), + '$title' => ((x($x,'title')) ? htmlspecialchars($x['title'], ENT_COMPAT,'UTF-8') : ''), '$placeholdertitle' => t('Set title'), '$catsenabled' => ((feature_enabled($x['profile_uid'],'categories') && (! $webpage)) ? 'categories' : ''), '$category' => "", @@ -1117,7 +1121,7 @@ function status_editor($a,$x,$popup=false) { '$permset' => t('Permission settings'), '$shortpermset' => t('permissions'), '$ptyp' => (($notes_cid) ? 'note' : 'wall'), - '$content' => ((x($x,'body')) ? htmlspecialchars($x['body']) : ''), + '$content' => ((x($x,'body')) ? htmlspecialchars($x['body'], ENT_COMPAT,'UTF-8') : ''), '$post_id' => '', '$baseurl' => $a->get_baseurl(true), '$defloc' => $x['default_location'], @@ -1142,6 +1146,8 @@ function status_editor($a,$x,$popup=false) { '$feature_encrypt' => ((feature_enabled($x['profile_uid'],'content_encrypt') && (! $webpage)) ? 'block' : 'none'), '$encrypt' => t('Encrypt text'), '$cipher' => $cipher, + '$expiryModalOK' => t('OK'), + '$expiryModalCANCEL' => t('Cancel'), )); @@ -1294,18 +1300,25 @@ function prepare_page($item) { $a = get_app(); $naked = ((get_pconfig($item['uid'],'system','nakedpage')) ? 1 : 0); + $observer = $a->get_observer(); + $zid = ($observer['xchan_addr']); + //240 chars is the longest we can have before we start hitting problems with suhosin sites + $preview = substr(urlencode($item['body']), 0, 240); + $link = z_root() . '/' . $a->cmd; if(array_key_exists('webpage',$a->layout) && array_key_exists('authored',$a->layout['webpage'])) { if($a->layout['webpage']['authored'] === 'none') $naked = 1; // ... other possible options } - return replace_macros(get_markup_template('page_display.tpl'),array( '$author' => (($naked) ? '' : $item['author']['xchan_name']), '$auth_url' => (($naked) ? '' : $item['author']['xchan_url']), + '$zid' => $zid, '$date' => (($naked) ? '' : datetime_convert('UTC',date_default_timezone_get(),$item['created'],'Y-m-d H:i')), '$title' => smilies(bbcode($item['title'])), - '$body' => prepare_body($item,true) + '$body' => prepare_body($item,true), + '$preview' => $preview, + '$link' => $link, )); } @@ -1366,26 +1379,26 @@ function network_tabs() { $tabs = array( array( 'label' => t('Commented Order'), - 'url'=>$a->get_baseurl(true) . '/' . $cmd . '?f=&order=comment' . ((x($_GET,'cid')) ? '&cid=' . $_GET['cid'] : ''), + 'url'=>$a->get_baseurl(true) . '/' . $cmd . '?f=&order=comment' . ((x($_GET,'cid')) ? '&cid=' . $_GET['cid'] : '') . ((x($_GET,'gid')) ? '&gid=' . $_GET['gid'] : ''), 'sel'=>$all_active, 'title'=> t('Sort by Comment Date'), ), array( 'label' => t('Posted Order'), - 'url'=>$a->get_baseurl(true) . '/' . $cmd . '?f=&order=post' . ((x($_GET,'cid')) ? '&cid=' . $_GET['cid'] : ''), + 'url'=>$a->get_baseurl(true) . '/' . $cmd . '?f=&order=post' . ((x($_GET,'cid')) ? '&cid=' . $_GET['cid'] : '') . ((x($_GET,'gid')) ? '&gid=' . $_GET['gid'] : ''), 'sel'=>$postord_active, 'title' => t('Sort by Post Date'), ), array( 'label' => t('Personal'), - 'url' => $a->get_baseurl(true) . '/' . $cmd . ((x($_GET,'cid')) ? '/?f=&cid=' . $_GET['cid'] : '') . '&conv=1', + 'url' => $a->get_baseurl(true) . '/' . $cmd . '?f=' . ((x($_GET,'cid')) ? '&cid=' . $_GET['cid'] : '') . '&conv=1', 'sel' => $conv_active, 'title' => t('Posts that mention or involve you'), ), array( 'label' => t('New'), - 'url' => $a->get_baseurl(true) . '/' . $cmd . ((x($_GET,'cid')) ? '/?f=&cid=' . $_GET['cid'] : '') . '&new=1', + 'url' => $a->get_baseurl(true) . '/' . $cmd . '?f=' . ((x($_GET,'cid')) ? '&cid=' . $_GET['cid'] : '') . '&new=1' . ((x($_GET,'gid')) ? '&gid=' . $_GET['gid'] : ''), 'sel' => $new_active, 'title' => t('Activity Stream - by date'), ), @@ -1423,7 +1436,8 @@ function network_tabs() { function profile_tabs($a, $is_owner=False, $nickname=Null){ //echo "<pre>"; var_dump($a->user); killme(); - + + $channel = $a->get_channel(); if (is_null($nickname)) @@ -1443,24 +1457,51 @@ function profile_tabs($a, $is_owner=False, $nickname=Null){ 'title' => t('Status Messages and Posts'), 'id' => 'status-tab', ), - array( + ); + + $p = get_all_perms($a->profile['profile_uid'],get_observer_hash()); + + if($p['view_profile']) { + $tabs[] = array( 'label' => t('About'), 'url' => $pr, 'sel' => ((argv(0) == 'profile') ? 'active' : ''), 'title' => t('Profile Details'), 'id' => 'profile-tab', - ), - array( + ); + } + if($p['view_photos']) { + $tabs[] = array( 'label' => t('Photos'), 'url' => $a->get_baseurl() . '/photos/' . $nickname, 'sel' => ((argv(0) == 'photos') ? 'active' : ''), 'title' => t('Photo Albums'), 'id' => 'photo-tab', - ), + ); + } + if($p['view_storage']) { + $tabs[] = array( + 'label' => t('Files'), + 'url' => $a->get_baseurl() . '/cloud/' . $nickname . ((get_observer_hash()) ? '' : '?f=&davguest=1'), + 'sel' => ((argv(0) == 'cloud') ? 'active' : ''), + 'title' => t('Files and Storage'), + 'id' => 'files-tab', + ); + } + + require_once('include/chat.php'); + $chats = chatroom_list($a->profile['profile_uid']); + $subdued = ((count($chats)) ? '' : ' subdued'); + $tabs[] = array( + 'label' => t('Chatrooms'), + 'url' => $a->get_baseurl() . '/chat/' . $nickname, + 'sel' => ((argv(0) == 'chat') ? 'active' . $subdued : '' . $subdued), + 'title' => t('Chatrooms'), + 'id' => 'chat-tab', ); - if ($is_owner){ + if($is_owner) { $tabs[] = array( 'label' => t('Events'), 'url' => $a->get_baseurl() . '/events', @@ -1468,15 +1509,27 @@ function profile_tabs($a, $is_owner=False, $nickname=Null){ 'title' => t('Events and Calendar'), 'id' => 'events-tab', ); - if(feature_enabled(local_user(),'webpages')){ + + $tabs[] = array( + 'label' => t('Bookmarks'), + 'url' => $a->get_baseurl() . '/bookmarks', + 'sel' => ((argv(0) == 'bookmarks') ? 'active' : ''), + 'title' => t('Saved Bookmarks'), + 'id' => 'bookmarks-tab', + ); + } + + + if($is_owner && feature_enabled($a->profile['profile_uid'],'webpages')) { $tabs[] = array( 'label' => t('Webpages'), 'url' => $a->get_baseurl() . '/webpages/' . $nickname, 'sel' => ((argv(0) == 'webpages') ? 'active' : ''), 'title' => t('Manage Webpages'), 'id' => 'webpages-tab', - );} + ); } + else { // FIXME // we probably need a listing of events that were created by diff --git a/include/crypto.php b/include/crypto.php index e9372fbb4..33cdc10c0 100644 --- a/include/crypto.php +++ b/include/crypto.php @@ -4,6 +4,8 @@ function rsa_sign($data,$key,$alg = 'sha256') { if(! $key) return 'no key'; $sig = ''; + if(intval(OPENSSL_ALGO_SHA256) && $alg === 'sha256') + $alg = OPENSSL_ALGO_SHA256; openssl_sign($data,$sig,$key,$alg); return $sig; } @@ -13,6 +15,8 @@ function rsa_verify($data,$sig,$key,$alg = 'sha256') { if(! $key) return false; + if(intval(OPENSSL_ALGO_SHA256) && $alg === 'sha256') + $alg = OPENSSL_ALGO_SHA256; $verify = openssl_verify($data,$sig,$key,$alg); return $verify; } diff --git a/include/deliver.php b/include/deliver.php index b1314ce39..b0d15e1ef 100644 --- a/include/deliver.php +++ b/include/deliver.php @@ -26,6 +26,7 @@ function deliver_run($argv, $argc) { // If there is no outq_msg, this is a refresh_all message which does not require local handling if($r[0]['outq_msg']) { $msg = array('body' => json_encode(array('pickup' => array(array('notify' => json_decode($r[0]['outq_notify'],true),'message' => json_decode($r[0]['outq_msg'],true)))))); + zot_import($msg,z_root()); $r = q("delete from outq where outq_hash = '%s' limit 1", dbesc($argv[$x]) diff --git a/include/dir_fns.php b/include/dir_fns.php index 0c9a6bd9f..c2e614831 100644 --- a/include/dir_fns.php +++ b/include/dir_fns.php @@ -22,7 +22,8 @@ function dir_sort_links() { function dir_safe_mode() { $observer = get_observer_hash(); - + if (! $observer) + return; if ($observer) $safe_mode = get_xconfig($observer,'directory','safe_mode'); if($safe_mode === '0') @@ -138,12 +139,15 @@ function update_directory_entry($ud) { } +/** + * @function local_dir_update($uid,$force) + * push local channel updates to a local directory server + * + */ +function local_dir_update($uid,$force) { - -function syncdirs($uid) { - - logger('syncdirs', LOGGER_DEBUG); + logger('local_dir_update', LOGGER_DEBUG); $p = q("select channel.channel_hash, channel_address, channel_timezone, profile.* from profile left join channel on channel_id = uid where uid = %d and is_default = 1", intval($uid) @@ -166,6 +170,10 @@ function syncdirs($uid) { $profile['region'] = $p[0]['region']; $profile['postcode'] = $p[0]['postal_code']; $profile['country'] = $p[0]['country_name']; + $profile['about'] = $p[0]['about']; + $profile['homepage'] = $p[0]['homepage']; + $profile['hometown'] = $p[0]['hometown']; + if($p[0]['keywords']) { $tags = array(); $k = explode(' ',$p[0]['keywords']); @@ -200,10 +208,10 @@ function syncdirs($uid) { } + $address = $p[0]['channel_address'] . '@' . get_app()->get_hostname(); if(perm_is_allowed($uid,'','view_profile')) { - import_directory_profile($hash,$profile); - + import_directory_profile($hash,$profile,$address,0); } else { // they may have made it private @@ -216,8 +224,8 @@ function syncdirs($uid) { } } - $ud_hash = random_string(); - update_modtime($ud_hash,$hash,$p[0]['channel_address'] . '@' . get_app()->get_hostname(),1); + $ud_hash = random_string() . '@' . get_app()->get_hostname(); + update_modtime($hash,$ud_hash,$p[0]['channel_address'] . '@' . get_app()->get_hostname(),(($force) ? (-1) : 1)); } diff --git a/include/directory.php b/include/directory.php index 491240a9d..794420b6f 100644 --- a/include/directory.php +++ b/include/directory.php @@ -10,9 +10,13 @@ function directory_run($argv, $argc){ cli_startup(); - if($argc != 2) + if($argc < 2) return; + $force = false; + if(($argc > 2) && ($argv[2] === 'force')) + $force = true; + logger('directory update', LOGGER_DEBUG); $dirmode = get_config('system','directory_mode'); @@ -29,7 +33,8 @@ function directory_run($argv, $argc){ if(($dirmode == DIRECTORY_MODE_PRIMARY) || ($dirmode == DIRECTORY_MODE_STANDALONE)) { - syncdirs($argv[1]); + + local_dir_update($argv[1],$force); q("update channel set channel_dirdate = '%s' where channel_id = %d limit 1", dbesc(datetime_convert()), @@ -53,7 +58,7 @@ function directory_run($argv, $argc){ // ensure the upstream directory is updated - $packet = zot_build_packet($channel,'refresh'); + $packet = zot_build_packet($channel,(($force) ? 'force_refresh' : 'refresh')); $z = zot_zot($url,$packet); // re-queue if unsuccessful diff --git a/include/enotify.php b/include/enotify.php index 011a1cde2..e0991257f 100644 --- a/include/enotify.php +++ b/include/enotify.php @@ -86,14 +86,17 @@ function notification($params) { $preamble = sprintf( t('%1$s, %2$s sent you a new private message at %3$s.'),$recip['channel_name'], $sender['xchan_name'],$sitename); $epreamble = sprintf( t('%1$s sent you %2$s.'),'[zrl=' . $sender['xchan_url'] . ']' . $sender['xchan_name'] . '[/zrl]', '[zrl=$itemlink]' . t('a private message') . '[/zrl]'); $sitelink = t('Please visit %s to view and/or reply to your private messages.'); - $tsitelink = sprintf( $sitelink, $siteurl . '/message/' . $params['item']['id'] ); - $hsitelink = sprintf( $sitelink, '<a href="' . $siteurl . '/message/' . $params['item']['id'] . '">' . $sitename . '</a>'); + $tsitelink = sprintf( $sitelink, $siteurl . '/mail/' . $params['item']['id'] ); + $hsitelink = sprintf( $sitelink, '<a href="' . $siteurl . '/mail/' . $params['item']['id'] . '">' . $sitename . '</a>'); $itemlink = $siteurl . '/message/' . $params['item']['id']; } if($params['type'] == NOTIFY_COMMENT) { // logger("notification: params = " . print_r($params, true), LOGGER_DEBUG); + $itemlink = $params['link']; + + // ignore like/unlike activity on posts - they probably require a sepearate notification preference if(array_key_exists('item',$params) && (! visible_activity($params['item']))) @@ -171,7 +174,6 @@ function notification($params) { $sitelink = t('Please visit %s to view and/or reply to the conversation.'); $tsitelink = sprintf( $sitelink, $siteurl ); $hsitelink = sprintf( $sitelink, '<a href="' . $siteurl . '">' . $sitename . '</a>'); - $itemlink = $params['link']; } if($params['type'] == NOTIFY_WALL) { @@ -183,9 +185,6 @@ function notification($params) { $recip['channel_name'], '[zrl=' . $sender['xchan_url'] . ']' . $sender['xchan_name'] . '[/zrl]', $params['link']); - - // FIXME - check the item privacy - $private = false; $sitelink = t('Please visit %s to view and/or reply to the conversation.'); $tsitelink = sprintf( $sitelink, $siteurl ); @@ -455,6 +454,8 @@ function notification($params) { if(! $datarray['email_secure']) { switch($params['type']) { case NOTIFY_WALL: + case NOTIFY_TAGSELF: + case NOTIFY_POKE: case NOTIFY_COMMENT: if(! $private) break; diff --git a/include/event.php b/include/event.php index 7873de1ef..08b94dafa 100644 --- a/include/event.php +++ b/include/event.php @@ -15,7 +15,7 @@ function format_event_html($ev) { $o .= '<p class="summary event-summary">' . bbcode($ev['summary']) . '</p>' . "\r\n"; - $o .= '<p class="description event-description">' . bbcode($ev['desc']) . '</p>' . "\r\n"; + $o .= '<p class="description event-description">' . bbcode($ev['description']) . '</p>' . "\r\n"; $o .= '<p class="event-start">' . t('Starts:') . ' <abbr class="dtstart" title="' . datetime_convert('UTC','UTC',$ev['start'], (($ev['adjust']) ? ATOM_TIME : 'Y-m-d\TH:i:s' )) @@ -52,8 +52,8 @@ function format_event_bbcode($ev) { if($ev['summary']) $o .= '[event-summary]' . $ev['summary'] . '[/event-summary]'; - if($ev['desc']) - $o .= '[event-description]' . $ev['desc'] . '[/event-description]'; + if($ev['description']) + $o .= '[event-description]' . $ev['description'] . '[/event-description]'; if($ev['start']) $o .= '[event-start]' . $ev['start'] . '[/event-start]'; @@ -75,7 +75,7 @@ function format_event_bbcode($ev) { function bbtovcal($s) { $o = ''; $ev = bbtoevent($s); - if($ev['desc']) + if($ev['description']) $o = format_event_html($ev); return $o; } @@ -90,7 +90,7 @@ function bbtoevent($s) { $ev['summary'] = $match[1]; $match = ''; if(preg_match("/\[event\-description\](.*?)\[\/event\-description\]/is",$s,$match)) - $ev['desc'] = $match[1]; + $ev['description'] = $match[1]; $match = ''; if(preg_match("/\[event\-start\](.*?)\[\/event\-start\]/is",$s,$match)) $ev['start'] = $match[1]; @@ -122,7 +122,7 @@ function ev_compare($a,$b) { $date_b = (($b['adjust']) ? datetime_convert('UTC',date_default_timezone_get(),$b['start']) : $b['start']); if($date_a === $date_b) - return strcasecmp($a['desc'],$b['desc']); + return strcasecmp($a['description'],$b['description']); return strcmp($date_a,$date_b); } @@ -178,7 +178,7 @@ function event_store($arr) { `start` = '%s', `finish` = '%s', `summary` = '%s', - `desc` = '%s', + `description` = '%s', `location` = '%s', `type` = '%s', `adjust` = %d, @@ -193,7 +193,7 @@ function event_store($arr) { dbesc($arr['start']), dbesc($arr['finish']), dbesc($arr['summary']), - dbesc($arr['desc']), + dbesc($arr['description']), dbesc($arr['location']), dbesc($arr['type']), intval($arr['adjust']), @@ -266,7 +266,7 @@ function event_store($arr) { $arr['mid'] = item_message_id(); - $r = q("INSERT INTO event ( uid,aid,event_xchan,event_hash,created,edited,start,finish,summary, `desc`,location,type, + $r = q("INSERT INTO event ( uid,aid,event_xchan,event_hash,created,edited,start,finish,summary,description,location,type, adjust,nofinish,allow_cid,allow_gid,deny_cid,deny_gid) VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, '%s', '%s', '%s', '%s' ) ", intval($arr['uid']), @@ -278,7 +278,7 @@ function event_store($arr) { dbesc($arr['start']), dbesc($arr['finish']), dbesc($arr['summary']), - dbesc($arr['desc']), + dbesc($arr['description']), dbesc($arr['location']), dbesc($arr['type']), intval($arr['adjust']), @@ -337,6 +337,8 @@ function event_store($arr) { $item_arr['obj_type'] = ACTIVITY_OBJ_EVENT; $item_arr['body'] = format_event_bbcode($arr); + $item_arr['plink'] = z_root() . '/channel/' . $z[0]['channel_address'] . '/?f=&mid=' . $item_arr['mid']; + $x = q("select * from xchan where xchan_hash = '%s' limit 1", dbesc($arr['event_xchan']) ); diff --git a/include/features.php b/include/features.php index 05206106a..cc8d457bc 100644 --- a/include/features.php +++ b/include/features.php @@ -7,6 +7,8 @@ function feature_enabled($uid,$feature) { $x = get_pconfig($uid,'feature',$feature); + if($x === false) + $x = get_config('feature',$feature); $arr = array('uid' => $uid, 'feature' => $feature, 'enabled' => $x); call_hooks('feature_enabled',$arr); return($arr['enabled']); @@ -24,9 +26,11 @@ function get_features() { array('multi_profiles', t('Multiple Profiles'), t('Ability to create multiple profiles')), array('webpages', t('Web Pages'), t('Provide managed web pages on your channel')), array('private_notes', t('Private Notes'), t('Enables a tool to store notes and reminders')), - array('prettyphoto', t('Enhanced Photo Albums'), t('Enable photo album with enhanced features')), +// prettyphoto has licensing issues and will no longer be provided in core - +// in any event this setting should probably be a theme option or plugin +// array('prettyphoto', t('Enhanced Photo Albums'), t('Enable photo album with enhanced features')), //FIXME - needs a description, but how the hell do we explain this to normals? - array('sendzid', t('Extended Identity Sharing'), t(' ')), + array('sendzid', t('Extended Identity Sharing'), t('Share your identity with all websites on the internet. When disabled, identity is only shared with sites in the matrix.')), array('expert', t('Expert Mode'), t('Enable Expert Mode to provide advanced configuration options')), array('premium_channel', t('Premium Channel'), t('Allows you to set restrictions and terms on those that connect with your channel')), ), @@ -37,7 +41,7 @@ function get_features() { array('richtext', t('Richtext Editor'), t('Enable richtext editor')), array('preview', t('Post Preview'), t('Allow previewing posts and comments before publishing them')), array('channel_sources', t('Channel Sources'), t('Automatically import channel content from other channels or feeds')), - array('content_encrypt', t('Even More Encryption'), t('Allow encryption of content end-to-end with a shared secret key')), + array('content_encrypt', t('Even More Encryption'), t('Allow optional encryption of content end-to-end with a shared secret key')), ), // Network Tools @@ -49,6 +53,7 @@ function get_features() { array('personal_tab', t('Network Personal Tab'), t('Enable tab to display only Network posts that you\'ve interacted on')), array('new_tab', t('Network New Tab'), t('Enable tab to display all new Network activity')), array('affinity', t('Affinity Tool'), t('Filter stream activity by depth of relationships')), + array('suggest', t('Suggest Channels'), t('Show channel suggestions')), ), // Item tools diff --git a/include/follow.php b/include/follow.php index 845ce11da..0508a8b37 100644 --- a/include/follow.php +++ b/include/follow.php @@ -16,6 +16,8 @@ function new_contact($uid,$url,$channel,$interactive = false, $confirm = false) $result = array('success' => false,'message' => ''); $a = get_app(); + $is_red = false; + if(! allowed_url($url)) { $result['message'] = t('Channel is blocked on this site.'); @@ -37,82 +39,94 @@ function new_contact($uid,$url,$channel,$interactive = false, $confirm = false) $ret = zot_finger($url,$channel); if($ret['success']) { + $is_red = true; $j = json_decode($ret['body'],true); } - else { - $result['message'] = t('Channel discovery failed. Website may be down or misconfigured.'); - logger('mod_follow: ' . $result['message']); - return $result; - } - logger('follow: ' . $url . ' ' . print_r($j,true)); + if($is_red && $j) { - if(! $j) { - $result['message'] = t('Response from remote channel was not understood.'); - logger('mod_follow: ' . $result['message']); - return $result; - } + $my_perms = PERMS_W_STREAM|PERMS_W_MAIL; + logger('follow: ' . $url . ' ' . print_r($j,true), LOGGER_DEBUG); - if(! ($j['success'] && $j['guid'])) { - $result['message'] = t('Response from remote channel was incomplete.'); - logger('mod_follow: ' . $result['message']); - return $result; - } - // Premium channel, set confirm before callback to avoid recursion + if(! ($j['success'] && $j['guid'])) { + $result['message'] = t('Response from remote channel was incomplete.'); + logger('mod_follow: ' . $result['message']); + return $result; + } - if(array_key_exists('connect_url',$j) && (! $confirm)) - goaway(zid($j['connect_url'])); + // Premium channel, set confirm before callback to avoid recursion + if(array_key_exists('connect_url',$j) && (! $confirm)) + goaway(zid($j['connect_url'])); - // check service class limits + // check service class limits - $r = q("select count(*) as total from abook where abook_channel = %d and not (abook_flags & %d) ", - intval($uid), - intval(ABOOK_FLAG_SELF) - ); - if($r) - $total_channels = $r[0]['total']; + $r = q("select count(*) as total from abook where abook_channel = %d and not (abook_flags & %d) ", + intval($uid), + intval(ABOOK_FLAG_SELF) + ); + if($r) + $total_channels = $r[0]['total']; - if(! service_class_allows($uid,'total_channels',$total_channels)) { - $result['message'] = upgrade_message(); - return $result; - } + if(! service_class_allows($uid,'total_channels',$total_channels)) { + $result['message'] = upgrade_message(); + return $result; + } - // do we have an xchan and hubloc? - // If not, create them. + // do we have an xchan and hubloc? + // If not, create them. - $x = import_xchan($j); + $x = import_xchan($j); - if(! $x['success']) - return $x; + if(! $x['success']) + return $x; - $xchan_hash = $x['hash']; + $xchan_hash = $x['hash']; - $their_perms = 0; + $their_perms = 0; - $global_perms = get_perms(); + $global_perms = get_perms(); - if( array_key_exists('permissions',$j) && array_key_exists('data',$j['permissions'])) { - $permissions = crypto_unencapsulate(array( - 'data' => $j['permissions']['data'], - 'key' => $j['permissions']['key'], - 'iv' => $j['permissions']['iv']), - $channel['channel_prvkey']); - if($permissions) - $permissions = json_decode($permissions,true); - logger('decrypted permissions: ' . print_r($permissions,true), LOGGER_DATA); - } - else - $permissions = $j['permissions']; + if( array_key_exists('permissions',$j) && array_key_exists('data',$j['permissions'])) { + $permissions = crypto_unencapsulate(array( + 'data' => $j['permissions']['data'], + 'key' => $j['permissions']['key'], + 'iv' => $j['permissions']['iv']), + $channel['channel_prvkey']); + if($permissions) + $permissions = json_decode($permissions,true); + logger('decrypted permissions: ' . print_r($permissions,true), LOGGER_DATA); + } + else + $permissions = $j['permissions']; - foreach($permissions as $k => $v) { - if($v) { - $their_perms = $their_perms | intval($global_perms[$k][1]); + foreach($permissions as $k => $v) { + if($v) { + $their_perms = $their_perms | intval($global_perms[$k][1]); + } } } + else { + + // attempt network auto-discovery + + $my_perms = 0; + $their_perms = 0; + $xchan_hash = ''; + + + + + } + + if(! $xchan_hash) { + $result['message'] = t('Channel discovery failed.'); + logger('follow: ' . $result['message']); + return $result; + } if((local_user()) && $uid == local_user()) { $aid = get_account_id(); @@ -156,7 +170,7 @@ function new_contact($uid,$url,$channel,$interactive = false, $confirm = false) intval($uid), dbesc($xchan_hash), intval($their_perms), - intval(PERMS_W_STREAM|PERMS_W_MAIL), + intval($my_perms), dbesc(datetime_convert()), dbesc(datetime_convert()) ); @@ -172,7 +186,8 @@ function new_contact($uid,$url,$channel,$interactive = false, $confirm = false) ); if($r) { $result['abook'] = $r[0]; - proc_run('php', 'include/notifier.php', 'permission_update', $result['abook']['abook_id']); + if($is_red) + proc_run('php', 'include/notifier.php', 'permission_update', $result['abook']['abook_id']); } $arr = array('channel_id' => $uid, 'abook' => $result['abook']); @@ -188,12 +203,6 @@ function new_contact($uid,$url,$channel,$interactive = false, $confirm = false) group_add_member($uid,'',$xchan_hash,$g['id']); } - // Then send a ping/message to the other side - - $result['success'] = true; return $result; - - - } diff --git a/include/group.php b/include/group.php index 8f690785d..56f177ab0 100644 --- a/include/group.php +++ b/include/group.php @@ -14,11 +14,11 @@ function group_add($uid,$name,$public = 0) { // access lists. What we're doing here is reviving the dead group, but old content which // was restricted to this group may now be seen by the new group members. - $z = q("SELECT * FROM `group` WHERE `id` = %d LIMIT 1", + $z = q("SELECT * FROM `groups` WHERE `id` = %d LIMIT 1", intval($r) ); if(count($z) && $z[0]['deleted']) { - $r = q("UPDATE `group` SET `deleted` = 0 WHERE `uid` = %d AND `name` = '%s' LIMIT 1", + $r = q("UPDATE `groups` SET `deleted` = 0 WHERE `uid` = %d AND `name` = '%s' LIMIT 1", intval($uid), dbesc($name) ); @@ -31,13 +31,13 @@ function group_add($uid,$name,$public = 0) { $dups = false; $hash = random_string() . $name; - $r = q("SELECT id FROM `group` WHERE hash = '%s' LIMIT 1", dbesc($hash)); + $r = q("SELECT id FROM `groups` WHERE hash = '%s' LIMIT 1", dbesc($hash)); if($r) $dups = true; } while($dups == true); - $r = q("INSERT INTO `group` ( hash, uid, visible, name ) + $r = q("INSERT INTO `groups` ( hash, uid, visible, name ) VALUES( '%s', %d, %d, '%s' ) ", dbesc($hash), intval($uid), @@ -53,7 +53,7 @@ function group_add($uid,$name,$public = 0) { function group_rmv($uid,$name) { $ret = false; if(x($uid) && x($name)) { - $r = q("SELECT id, hash FROM `group` WHERE `uid` = %d AND `name` = '%s' LIMIT 1", + $r = q("SELECT id, hash FROM `groups` WHERE `uid` = %d AND `name` = '%s' LIMIT 1", intval($uid), dbesc($name) ); @@ -104,7 +104,7 @@ function group_rmv($uid,$name) { ); // remove group - $r = q("UPDATE `group` SET `deleted` = 1 WHERE `uid` = %d AND `name` = '%s' LIMIT 1", + $r = q("UPDATE `groups` SET `deleted` = 1 WHERE `uid` = %d AND `name` = '%s' LIMIT 1", intval($uid), dbesc($name) ); @@ -119,7 +119,7 @@ function group_rmv($uid,$name) { function group_byname($uid,$name) { if((! $uid) || (! strlen($name))) return false; - $r = q("SELECT * FROM `group` WHERE `uid` = %d AND `name` = '%s' LIMIT 1", + $r = q("SELECT * FROM `groups` WHERE `uid` = %d AND `name` = '%s' LIMIT 1", intval($uid), dbesc($name) ); @@ -132,7 +132,7 @@ function group_byname($uid,$name) { function group_rec_byhash($uid,$hash) { if((! $uid) || (! strlen($hash))) return false; - $r = q("SELECT * FROM `group` WHERE `uid` = %d AND `hash` = '%s' LIMIT 1", + $r = q("SELECT * FROM `groups` WHERE `uid` = %d AND `hash` = '%s' LIMIT 1", intval($uid), dbesc($hash) ); @@ -188,11 +188,11 @@ function group_get_members($gid) { if(intval($gid)) { $r = q("SELECT * FROM `group_member` LEFT JOIN abook ON abook_xchan = `group_member`.`xchan` left join xchan on xchan_hash = abook_xchan - WHERE `gid` = %d AND abook_channel = %d and `group_member`.`uid` = %d and not ( abook_flags & %d ) and not ( abook_flags & %d ) and not ( abook_flags & %d ) ORDER BY xchan_name ASC ", + WHERE `gid` = %d AND abook_channel = %d and `group_member`.`uid` = %d and not ( xchan_flags & %d ) and not ( abook_flags & %d ) and not ( abook_flags & %d ) ORDER BY xchan_name ASC ", intval($gid), intval(local_user()), intval(local_user()), - intval(ABOOK_FLAG_SELF), + intval(XCHAN_FLAGS_DELETED), intval(ABOOK_FLAG_BLOCKED), intval(ABOOK_FLAG_PENDING) ); @@ -207,7 +207,7 @@ function mini_group_select($uid,$group = '') { $grps = array(); $o = ''; - $r = q("SELECT * FROM `group` WHERE `deleted` = 0 AND `uid` = %d ORDER BY `name` ASC", + $r = q("SELECT * FROM `groups` WHERE `deleted` = 0 AND `uid` = %d ORDER BY `name` ASC", intval($uid) ); $grps[] = array('name' => '', 'hash' => '0', 'selected' => ''); @@ -229,7 +229,7 @@ function mini_group_select($uid,$group = '') { -function group_side($every="contacts",$each="group",$edit = false, $group_id = 0, $cid = '',$mode = 1) { +function group_side($every="connections",$each="group",$edit = false, $group_id = 0, $cid = '',$mode = 1) { $o = ''; @@ -246,7 +246,7 @@ function group_side($every="contacts",$each="group",$edit = false, $group_id = 0 ); - $r = q("SELECT * FROM `group` WHERE `deleted` = 0 AND `uid` = %d ORDER BY `name` ASC", + $r = q("SELECT * FROM `groups` WHERE `deleted` = 0 AND `uid` = %d ORDER BY `name` ASC", intval($_SESSION['uid']) ); $member_of = array(); @@ -272,7 +272,7 @@ function group_side($every="contacts",$each="group",$edit = false, $group_id = 0 'cid' => $cid, 'text' => $rr['name'], 'selected' => $selected, - 'href' => (($mode == 0) ? $each.'?f=&gid='.$rr['id'] : $each."/".$rr['id']), + 'href' => (($mode == 0) ? $each.'?f=&gid='.$rr['id'] : $each."/".$rr['id']) . ((x($_GET,'new')) ? '&new=' . $_GET['new'] : '') . ((x($_GET,'order')) ? '&order=' . $_GET['order'] : ''), 'edit' => $groupedit, 'ismember' => in_array($rr['id'],$member_of), ); @@ -302,7 +302,7 @@ function expand_groups($a) { $groups = implode(',', $x); if($groups) - $r = q("SELECT xchan FROM group_member WHERE gid IN ( select id from `group` where hash in ( $groups ))"); + $r = q("SELECT xchan FROM group_member WHERE gid IN ( select id from `groups` where hash in ( $groups ))"); $ret = array(); if($r) @@ -314,7 +314,7 @@ function expand_groups($a) { function member_of($c) { - $r = q("SELECT `group`.`name`, `group`.`id` FROM `group` LEFT JOIN `group_member` ON `group_member`.`gid` = `group`.`id` WHERE `group_member`.`xchan` = '%s' AND `group`.`deleted` = 0 ORDER BY `group`.`name` ASC ", + $r = q("SELECT `groups`.`name`, `groups`.`id` FROM `groups` LEFT JOIN `group_member` ON `group_member`.`gid` = `groups`.`id` WHERE `group_member`.`xchan` = '%s' AND `groups`.`deleted` = 0 ORDER BY `groups`.`name` ASC ", dbesc($c) ); diff --git a/include/identity.php b/include/identity.php index b25594c87..d83498a69 100644 --- a/include/identity.php +++ b/include/identity.php @@ -22,8 +22,9 @@ require_once('include/crypto.php'); function identity_check_service_class($account_id) { $ret = array('success' => false, $message => ''); - $r = q("select count(channel_id) as total from channel where channel_account_id = %d ", - intval($account_id) + $r = q("select count(channel_id) as total from channel where channel_account_id = %d and not ( channel_pageflags & %d ) ", + intval($account_id), + intval(PAGE_REMOVED) ); if(! ($r && count($r))) { $ret['message'] = t('Unable to obtain identity information from database'); @@ -71,26 +72,37 @@ function validate_channelname($name) { /** - * @function create_dir_account() + * @function create_sys_channel() * Create a system channel - which has no account attached * - * Currently unused. - * */ -function create_dir_account() { +function create_sys_channel() { + if(get_sys_channel()) + return; create_identity(array( 'account_id' => 'xxx', // This will create an identity with an (integer) account_id of 0, but account_id is required - 'nickname' => 'dir', - 'name' => 'Directory', - 'pageflags' => PAGE_DIRECTORY_CHANNEL|PAGE_HIDDEN, - 'publish' => 0 + 'nickname' => 'sys', + 'name' => 'System', + 'pageflags' => PAGE_SYSTEM, + 'publish' => 0, + 'xchanflags' => XCHAN_FLAGS_SYSTEM )); } +function get_sys_channel() { + $r = q("select * from channel left join xchan on channel_hash = xchan_hash where (channel_pageflags & %d) limit 1", + intval(PAGE_SYSTEM) + ); + if($r) + return $r[0]; + return false; +} + + /** * @channel_total() - * Return the total number of channels on this site. No filtering is performed. + * Return the total number of channels on this site. No filtering is performed except to check PAGE_REMOVED * * @returns int * on error returns boolean false @@ -98,7 +110,10 @@ function create_dir_account() { */ function channel_total() { - $r = q("select channel_id from channel where true"); + $r = q("select channel_id from channel where not ( channel_pageflags & %d )", + intval(PAGE_REMOVED) + ); + if(is_array($r)) return count($r); return false; @@ -145,7 +160,7 @@ function create_identity($arr) { $name = escape_tags($arr['name']); $pageflags = ((x($arr,'pageflags')) ? intval($arr['pageflags']) : PAGE_NORMAL); - + $xchanflags = ((x($arr,'xchanflags')) ? intval($arr['xchanflags']) : XCHAN_FLAGS_NORMAL); $name_error = validate_channelname($arr['name']); if($name_error) { $ret['message'] = $name_error; @@ -243,7 +258,7 @@ function create_identity($arr) { $newuid = $ret['channel']['channel_id']; - $r = q("insert into xchan ( xchan_hash, xchan_guid, xchan_guid_sig, xchan_pubkey, xchan_photo_l, xchan_photo_m, xchan_photo_s, xchan_addr, xchan_url, xchan_follow, xchan_connurl, xchan_name, xchan_network, xchan_photo_date, xchan_name_date ) values ('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s')", + $r = q("insert into xchan ( xchan_hash, xchan_guid, xchan_guid_sig, xchan_pubkey, xchan_photo_l, xchan_photo_m, xchan_photo_s, xchan_addr, xchan_url, xchan_follow, xchan_connurl, xchan_name, xchan_network, xchan_photo_date, xchan_name_date, xchan_flags ) values ('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d)", dbesc($hash), dbesc($guid), dbesc($sig), @@ -258,7 +273,8 @@ function create_identity($arr) { dbesc($ret['channel']['channel_name']), dbesc('zot'), dbesc(datetime_convert()), - dbesc(datetime_convert()) + dbesc(datetime_convert()), + intval($xchanflags) ); // Not checking return value. @@ -396,7 +412,7 @@ function identity_basic_export($channel_id) { $ret['hubloc'] = $r; } - $r = q("select * from `group` where uid = %d ", + $r = q("select * from `groups` where uid = %d ", intval($channel_id) ); @@ -532,6 +548,9 @@ function profile_load(&$a, $nickname, $profile = '') { } $a->profile = $r[0]; + $online = get_online_status($nickname); + $a->profile['online_status'] = $online['result']; + $a->profile_uid = $r[0]['profile_uid']; $a->page['title'] = $a->profile['channel_name'] . " - " . $a->profile['channel_address'] . "@" . $a->get_hostname(); @@ -655,6 +674,7 @@ function profile_sidebar($profile, $block = 0, $show_connect = true) { } } + if((x($profile,'address') == 1) || (x($profile,'locality') == 1) || (x($profile,'region') == 1) @@ -665,9 +685,15 @@ function profile_sidebar($profile, $block = 0, $show_connect = true) { $gender = ((x($profile,'gender') == 1) ? t('Gender:') : False); $marital = ((x($profile,'marital') == 1) ? t('Status:') : False); $homepage = ((x($profile,'homepage') == 1) ? t('Homepage:') : False); + $profile['online'] = (($profile['online_status'] === 'online') ? t('Online Now') : False); +logger('online: ' . $profile['online']); + + if(! perm_is_allowed($profile['uid'],((is_array($observer)) ? $observer['xchan_hash'] : ''),'view_profile')) { + $block = true; + } if(($profile['hidewall'] || $block) && (! local_user()) && (! remote_user())) { - $location = $pdesc = $gender = $marital = $homepage = False; + $location = $pdesc = $gender = $marital = $homepage = $online = False; } $firstname = ((strpos($profile['name'],' ')) @@ -688,7 +714,7 @@ function profile_sidebar($profile, $block = 0, $show_connect = true) { $channel_menu = menu_render($m); } $menublock = get_pconfig($profile['uid'],'system','channel_menublock'); - if ($menublock) { + if ($menublock && (! $block)) { require_once('include/comanche.php'); $channel_menu .= comanche_block($menublock); } @@ -935,6 +961,8 @@ function advanced_profile(&$a) { if($txt = prepare_text($a->profile['contact'])) $profile['contact'] = array( t('Contact information and Social Networks:'), $txt); + if($txt = prepare_text($a->profile['channels'])) $profile['channels'] = array( t('My other channels:'), $txt); + if($txt = prepare_text($a->profile['music'])) $profile['music'] = array( t('Musical interests:'), $txt); if($txt = prepare_text($a->profile['book'])) $profile['book'] = array( t('Books, literature:'), $txt); @@ -949,44 +977,15 @@ function advanced_profile(&$a) { if($txt = prepare_text($a->profile['education'])) $profile['education'] = array( t('School/education:'), $txt ); - $r = q("select * from obj left join term on obj_obj = term_hash where term_hash != '' and obj_page = '%s' and uid = %d and obj_type = %d - order by obj_verb, term", - dbesc($a->profile['profile_guid']), - intval($a->profile['profile_uid']), - intval(TERM_OBJ_THING) - ); - $things = null; + $things = get_things($a->profile['profile_guid'],$a->profile['profile_uid']); - if($r) { - $things = array(); - - // Use the system obj_verbs array as a sort key, since we don't really - // want an alphabetic sort. To change the order, use a plugin to - // alter the obj_verbs() array or alter it in code. Unknown verbs come - // after the known ones - in no particular order. - - $v = obj_verbs(); - foreach($v as $k => $foo) - $things[$k] = null; - foreach($r as $rr) { - if(! $things[$rr['obj_verb']]) - $things[$rr['obj_verb']] = array(); - $things[$rr['obj_verb']][] = array('term' => $rr['term'],'url' => $rr['url'],'img' => $rr['imgurl']); - } - $sorted_things = array(); - if($things) - foreach($things as $k => $v) - if(is_array($things[$k])) - $sorted_things[$k] = $v; - } - - logger('mod_profile: things: ' . print_r($sorted_things,true), LOGGER_DATA); + logger('mod_profile: things: ' . print_r($things,true), LOGGER_DATA); return replace_macros($tpl, array( '$title' => t('Profile'), '$profile' => $profile, - '$things' => $sorted_things + '$things' => $things )); } @@ -1028,15 +1027,18 @@ function zid_init(&$a) { if(validate_email($tmp_str)) { proc_run('php','include/gprobe.php',bin2hex($tmp_str)); $arr = array('zid' => $tmp_str, 'url' => $a->cmd); - call_hooks('zid_init',$arr); - if((! local_user()) && (! remote_user())) { - logger('zid_init: not authenticated. Invoking reverse magic-auth for ' . $tmp_str); - $r = q("select * from hubloc where hubloc_addr = '%s' order by hubloc_id desc limit 1", + call_hooks('zid_init',$arr); + if(! local_user()) { + $r = q("select * from hubloc where hubloc_addr = '%s' order by hubloc_connected desc limit 1", dbesc($tmp_str) ); + if($r && remote_user() && remote_user() === $r[0]['hubloc_hash']) + return; + logger('zid_init: not authenticated. Invoking reverse magic-auth for ' . $tmp_str); // try to avoid recursion - but send them home to do a proper magic auth - $dest = '/' . $a->query_string; - $dest = str_replace(array('?zid=','&zid='),array('?rzid=','&rzid='),$dest); + $query = $a->query_string; + $query = str_replace(array('?zid=','&zid='),array('?rzid=','&rzid='),$query); + $dest = '/' . urlencode($query); if($r && ($r[0]['hubloc_url'] != z_root()) && (! strstr($dest,'/magic')) && (! strstr($dest,'/rmagic'))) { goaway($r[0]['hubloc_url'] . '/magic' . '?f=&rev=1&dest=' . z_root() . $dest); } @@ -1102,5 +1104,105 @@ function get_theme_uid() { if(! $uid) return local_user(); } + if(! $uid) { + $x = get_sys_channel(); + if($x) + return $x['channel_id']; + } return $uid; } + +/** +* @function get_default_profile_photo($size = 175) +* Retrieves the path of the default_profile_photo for this system +* with the specified size. +* @param int $size +* one of (175, 80, 48) +* @returns string +* +*/ + +function get_default_profile_photo($size = 175) { + $scheme = get_config('system','default_profile_photo'); + if(! $scheme) + $scheme = 'rainbow_man'; + return 'images/default_profile_photos/' . $scheme . '/' . $size . '.jpg'; +} + + +/** + * + * @function is_foreigner($s) + * Test whether a given identity is NOT a member of the Red Matrix + * @param string $s; + * xchan_hash of the identity in question + * + * @returns boolean true or false + * + */ + +function is_foreigner($s) { + return((strpbrk($s,'.:@')) ? true : false); +} + + +/** + * + * @function is_member($s) + * Test whether a given identity is a member of the Red Matrix + * @param string $s; + * xchan_hash of the identity in question + * + * @returns boolean true or false + * + */ + +function is_member($s) { + return((is_foreigner($s)) ? false : true); +} + +function get_online_status($nick) { + + $ret = array('result' => false); + + if(get_config('system','block_public') && ! local_user() && ! remote_user()) + return $ret; + + $r = q("select channel_id, channel_hash from channel where channel_address = '%s' limit 1", + dbesc(argv(1)) + ); + if($r) { + $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']) + ); + if($x) + $ret['result'] = $x[0]['cp_status']; + } + + return $ret; +} + + +function remote_online_status($webbie) { + + $result = false; + $r = q("select * from hubloc where hubloc_addr = '%s' limit 1", + dbesc($webbie) + ); + if(! $r) + return $result; + + $url = $r[0]['hubloc_url'] . '/online/' . substr($webbie,0,strpos($webbie,'@')); + + $x = z_fetch_url($url); + if($x['success']) { + $j = json_decode($x['body'],true); + if($j) + $result = (($j['result']) ? $j['result'] : false); + } + return $result; + +} diff --git a/include/items.php b/include/items.php index dd3cf7644..7e15e9411 100755 --- a/include/items.php +++ b/include/items.php @@ -18,10 +18,17 @@ function collect_recipients($item,&$private) { require_once('include/group.php'); - if($item['item_private']) - $private = true; + $private = ((intval($item['item_private'])) ? true : false); + $recipients = array(); + + // if the post is marked private but there are no recipients, only add the author and owner + // as recipients. The ACL for the post may live on the hub of a different clone. We need to + // get the post to that hub. if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid']) { + + // it is private + $allow_people = expand_acl($item['allow_cid']); $allow_groups = expand_groups(expand_acl($item['allow_gid'])); @@ -54,19 +61,19 @@ function collect_recipients($item,&$private) { $private = true; } else { - $recipients = array(); - $r = q("select * from abook where abook_channel = %d and not (abook_flags & %d) and not (abook_flags & %d) and not (abook_flags & %d)", - intval($item['uid']), - intval(ABOOK_FLAG_SELF), - intval(ABOOK_FLAG_PENDING), - intval(ABOOK_FLAG_ARCHIVED) - ); - if($r) { - foreach($r as $rr) { - $recipients[] = $rr['abook_xchan']; + if(! $private) { + $r = q("select abook_xchan from abook where abook_channel = %d and not (abook_flags & %d) and not (abook_flags & %d) and not (abook_flags & %d)", + intval($item['uid']), + intval(ABOOK_FLAG_SELF), + intval(ABOOK_FLAG_PENDING), + intval(ABOOK_FLAG_ARCHIVED) + ); + if($r) { + foreach($r as $rr) { + $recipients[] = $rr['abook_xchan']; + } } } - $private = false; } // This is a somewhat expensive operation but important. @@ -138,7 +145,9 @@ function can_comment_on_post($observer_xchan,$item) { * @function red_zrl_callback * preg_match function when fixing 'naked' links in mod item.php * Check if we've got a hubloc for the site and use a zrl if we do, a url if we don't. - * + * Remove any existing zid= param which may have been pasted by mistake - and will have + * the author's credentials. zid's are dynamic and can't really be passed around like + * that. */ @@ -152,11 +161,41 @@ function red_zrl_callback($matches) { if($r) $zrl = true; } + + $t = strip_zids($matches[2]); + if($t !== $matches[2]) { + $zrl = true; + $matches[2] = $t; + } + + if($matches[1] === '#^') + $matches[1] = ''; if($zrl) - return $matches[1] . '[zrl=' . $matches[2] . ']' . $matches[2] . '[/zrl]'; - return $matches[0]; + return $matches[1] . '#^[zrl=' . $matches[2] . ']' . $matches[2] . '[/zrl]'; + return $matches[1] . '#^[url=' . $matches[2] . ']' . $matches[2] . '[/url]'; +} + + +// If we've got a url or zrl tag with a naked url somewhere in the link text, +// escape it with quotes unless the naked url is a linked photo. + +function red_escape_zrl_callback($matches) { + + // Uncertain why the url/zrl forms weren't picked up by the non-greedy regex. + + if((strpos($matches[3],'zmg') !== false) || (strpos($matches[3],'img') !== false) || (strpos($matches[3],'zrl') !== false) || (strpos($matches[3],'url') !== false)) + return $matches[0]; + return '[' . $matches[1] . 'rl' . $matches[2] . ']' . $matches[3] . '"' . $matches[4] . '"' . $matches[5] . '[/' . $matches[6] . 'rl]'; +} + +function red_escape_codeblock($m) { + return '[$b64' . $m[2] . base64_encode($m[1]) . '[/' . $m[2] . ']'; } +function red_unescape_codeblock($m) { + return '[' . $m[2] . base64_decode($m[1]) . '[/' . $m[2] . ']'; + +} /** @@ -243,6 +282,12 @@ function post_activity_item($arr) { $arr['comment_policy'] = map_scope($channel['channel_w_comment']); + + if ((! $arr['plink']) && ($arr['item_flags'] & ITEM_THREAD_TOP)) { + $arr['plink'] = z_root() . '/channel/' . $channel['channel_address'] . '/?f=&mid=' . $arr['mid']; + } + + // for the benefit of plugins, we will behave as if this is an API call rather than a normal online post $_REQUEST['api_source'] = 1; @@ -564,9 +609,9 @@ function title_is_body($title, $body) { function get_item_elements($x) { -// logger('get_item_elements'); + $arr = array(); - $arr['body'] = (($x['body']) ? htmlentities($x['body'],ENT_COMPAT,'UTF-8',false) : ''); + $arr['body'] = (($x['body']) ? htmlspecialchars($x['body'],ENT_COMPAT,'UTF-8',false) : ''); $arr['created'] = datetime_convert('UTC','UTC',$x['created']); $arr['edited'] = datetime_convert('UTC','UTC',$x['edited']); @@ -584,27 +629,27 @@ function get_item_elements($x) { ? datetime_convert('UTC','UTC',$x['commented']) : $arr['created']); - $arr['title'] = (($x['title']) ? htmlentities($x['title'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['title'] = (($x['title']) ? htmlspecialchars($x['title'], ENT_COMPAT,'UTF-8',false) : ''); if(mb_strlen($arr['title']) > 255) $arr['title'] = mb_substr($arr['title'],0,255); - $arr['app'] = (($x['app']) ? htmlentities($x['app'], ENT_COMPAT,'UTF-8',false) : ''); - $arr['mid'] = (($x['message_id']) ? htmlentities($x['message_id'], ENT_COMPAT,'UTF-8',false) : ''); - $arr['parent_mid'] = (($x['message_top']) ? htmlentities($x['message_top'], ENT_COMPAT,'UTF-8',false) : ''); - $arr['thr_parent'] = (($x['message_parent']) ? htmlentities($x['message_parent'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['app'] = (($x['app']) ? htmlspecialchars($x['app'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['mid'] = (($x['message_id']) ? htmlspecialchars($x['message_id'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['parent_mid'] = (($x['message_top']) ? htmlspecialchars($x['message_top'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['thr_parent'] = (($x['message_parent']) ? htmlspecialchars($x['message_parent'], ENT_COMPAT,'UTF-8',false) : ''); - $arr['plink'] = (($x['permalink']) ? htmlentities($x['permalink'], ENT_COMPAT,'UTF-8',false) : ''); - $arr['location'] = (($x['location']) ? htmlentities($x['location'], ENT_COMPAT,'UTF-8',false) : ''); - $arr['coord'] = (($x['longlat']) ? htmlentities($x['longlat'], ENT_COMPAT,'UTF-8',false) : ''); - $arr['verb'] = (($x['verb']) ? htmlentities($x['verb'], ENT_COMPAT,'UTF-8',false) : ''); - $arr['mimetype'] = (($x['mimetype']) ? htmlentities($x['mimetype'], ENT_COMPAT,'UTF-8',false) : ''); - $arr['obj_type'] = (($x['object_type']) ? htmlentities($x['object_type'], ENT_COMPAT,'UTF-8',false) : ''); - $arr['tgt_type'] = (($x['target_type']) ? htmlentities($x['target_type'], ENT_COMPAT,'UTF-8',false) : ''); - $arr['comment_policy'] = (($x['comment_scope']) ? htmlentities($x['comment_scope'], ENT_COMPAT,'UTF-8',false) : 'contacts'); + $arr['plink'] = (($x['permalink']) ? htmlspecialchars($x['permalink'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['location'] = (($x['location']) ? htmlspecialchars($x['location'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['coord'] = (($x['longlat']) ? htmlspecialchars($x['longlat'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['verb'] = (($x['verb']) ? htmlspecialchars($x['verb'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['mimetype'] = (($x['mimetype']) ? htmlspecialchars($x['mimetype'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['obj_type'] = (($x['object_type']) ? htmlspecialchars($x['object_type'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['tgt_type'] = (($x['target_type']) ? htmlspecialchars($x['target_type'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['comment_policy'] = (($x['comment_scope']) ? htmlspecialchars($x['comment_scope'], ENT_COMPAT,'UTF-8',false) : 'contacts'); - $arr['sig'] = (($x['signature']) ? htmlentities($x['signature'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['sig'] = (($x['signature']) ? htmlspecialchars($x['signature'], ENT_COMPAT,'UTF-8',false) : ''); $arr['object'] = activity_sanitise($x['object']); @@ -667,7 +712,6 @@ function get_item_elements($x) { $arr['body'] = json_encode(crypto_encapsulate($arr['body'],$key)); } - return $arr; } @@ -681,14 +725,60 @@ function import_author_xchan($x) { return $arr['xchan_hash']; if((! array_key_exists('network', $x)) || ($x['network'] === 'zot')) { - return import_author_zot($x); + $y = import_author_zot($x); + } + + if($x['network'] === 'rss') { + $y = import_author_rss($x); + } + + return(($y) ? $y : false); +} + +function import_author_rss($x) { + + if(! $x['url']) + return false; + + $r = q("select xchan_hash from xchan where xchan_network = 'rss' and xchan_url = '%s' limit 1", + dbesc($x['url']) + ); + if($r) { + logger('import_author_rss: in cache' , LOGGER_DEBUG); + return $r[0]['xchan_hash']; } + $name = trim($x['name']); + + $r = q("insert into xchan ( xchan_hash, xchan_url, xchan_name, xchan_network ) + values ( '%s', '%s', '%s', '%s' )", + dbesc($x['url']), + dbesc($x['url']), + dbesc(($name) ? $name : t('Unknown')), + dbesc('rss') + ); + if($r) { + + $photos = import_profile_photo($x['photo'],$x['url']); - // TODO: create xchans for other common and/or aligned networks + if($photos) { + $r = q("update xchan set xchan_photo_date = '%s', xchan_photo_l = '%s', xchan_photo_m = '%s', xchan_photo_s = '%s', xchan_photo_mimetype = '%s' where xchan_url = '%s' and xchan_network = 'rss' limit 1", + dbesc(datetime_convert('UTC','UTC',$arr['photo_updated'])), + dbesc($photos[0]), + dbesc($photos[1]), + dbesc($photos[2]), + dbesc($photos[3]), + dbesc($x['url']) + ); + if($r) + return $x['url']; + } + } return false; + } + function encode_item($item) { $x = array(); $x['type'] = 'activity'; @@ -810,7 +900,7 @@ function encode_item_xchan($xchan) { function encode_item_terms($terms) { $ret = array(); - $allowed_export_terms = array( TERM_UNKNOWN, TERM_HASHTAG, TERM_MENTION, TERM_CATEGORY ); + $allowed_export_terms = array( TERM_UNKNOWN, TERM_HASHTAG, TERM_MENTION, TERM_CATEGORY, TERM_BOOKMARK ); if($terms) { foreach($terms as $term) { @@ -822,7 +912,7 @@ function encode_item_terms($terms) { } function termtype($t) { - $types = array('unknown','hashtag','mention','category','private_category','file','search'); + $types = array('unknown','hashtag','mention','category','private_category','file','search','thing','bookmark'); return(($types[$t]) ? $types[$t] : 'unknown'); } @@ -832,8 +922,8 @@ function decode_tags($t) { $ret = array(); foreach($t as $x) { $tag = array(); - $tag['term'] = htmlentities($x['tag'], ENT_COMPAT,'UTF-8',false); - $tag['url'] = htmlentities($x['url'], ENT_COMPAT,'UTF-8',false); + $tag['term'] = htmlspecialchars($x['tag'], ENT_COMPAT,'UTF-8',false); + $tag['url'] = htmlspecialchars($x['url'], ENT_COMPAT,'UTF-8',false); switch($x['type']) { case 'hashtag': $tag['type'] = TERM_HASHTAG; @@ -853,6 +943,12 @@ function decode_tags($t) { case 'search': $tag['type'] = TERM_SEARCH; break; + case 'thing': + $tag['type'] = TERM_THING; + break; + case 'bookmark': + $tag['type'] = TERM_BOOKMARK; + break; default: case 'unknown': $tag['type'] = TERM_UNKNOWN; @@ -876,12 +972,12 @@ function activity_sanitise($arr) { if(is_array($x)) $ret[$k] = activity_sanitise($x); else - $ret[$k] = htmlentities($x, ENT_COMPAT,'UTF-8',false); + $ret[$k] = htmlspecialchars($x, ENT_COMPAT,'UTF-8',false); } return $ret; } else { - return htmlentities($arr, ENT_COMPAT,'UTF-8', false); + return htmlspecialchars($arr, ENT_COMPAT,'UTF-8', false); } } return ''; @@ -893,7 +989,7 @@ function array_sanitise($arr) { if($arr) { $ret = array(); foreach($arr as $x) { - $ret[] = htmlentities($x, ENT_COMPAT,'UTF-8',false); + $ret[] = htmlspecialchars($x, ENT_COMPAT,'UTF-8',false); } return $ret; } @@ -958,8 +1054,8 @@ function get_mail_elements($x) { $arr = array(); - $arr['body'] = (($x['body']) ? htmlentities($x['body'], ENT_COMPAT,'UTF-8',false) : ''); - $arr['title'] = (($x['title'])? htmlentities($x['title'],ENT_COMPAT,'UTF-8',false) : ''); + $arr['body'] = (($x['body']) ? htmlspecialchars($x['body'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['title'] = (($x['title'])? htmlspecialchars($x['title'],ENT_COMPAT,'UTF-8',false) : ''); $arr['created'] = datetime_convert('UTC','UTC',$x['created']); if((! array_key_exists('expires',$x)) || ($x['expires'] === '0000-00-00 00:00:00')) @@ -977,18 +1073,18 @@ function get_mail_elements($x) { $key = get_config('system','pubkey'); $arr['mail_flags'] |= MAIL_OBSCURED; - $arr['body'] = htmlentities($arr['body'],ENT_COMPAT,'UTF-8',false); + $arr['body'] = htmlspecialchars($arr['body'],ENT_COMPAT,'UTF-8',false); if($arr['body']) $arr['body'] = json_encode(crypto_encapsulate($arr['body'],$key)); - $arr['title'] = htmlentities($arr['title'],ENT_COMPAT,'UTF-8',false); + $arr['title'] = htmlspecialchars($arr['title'],ENT_COMPAT,'UTF-8',false); if($arr['title']) $arr['title'] = json_encode(crypto_encapsulate($arr['title'],$key)); if($arr['created'] > datetime_convert()) $arr['created'] = datetime_convert(); - $arr['mid'] = (($x['message_id']) ? htmlentities($x['message_id'], ENT_COMPAT,'UTF-8',false) : ''); - $arr['parent_mid'] = (($x['message_parent']) ? htmlentities($x['message_parent'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['mid'] = (($x['message_id']) ? htmlspecialchars($x['message_id'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['parent_mid'] = (($x['message_parent']) ? htmlspecialchars($x['message_parent'], ENT_COMPAT,'UTF-8',false) : ''); if($x['attach']) $arr['attach'] = activity_sanitise($x['attach']); @@ -1017,18 +1113,18 @@ function get_profile_elements($x) { else return array(); - $arr['desc'] = (($x['title']) ? htmlentities($x['title'],ENT_COMPAT,'UTF-8',false) : ''); + $arr['desc'] = (($x['title']) ? htmlspecialchars($x['title'],ENT_COMPAT,'UTF-8',false) : ''); $arr['dob'] = datetime_convert('UTC','UTC',$x['birthday'],'Y-m-d'); $arr['age'] = (($x['age']) ? intval($x['age']) : 0); - $arr['gender'] = (($x['gender']) ? htmlentities($x['gender'], ENT_COMPAT,'UTF-8',false) : ''); - $arr['marital'] = (($x['marital']) ? htmlentities($x['marital'], ENT_COMPAT,'UTF-8',false) : ''); - $arr['sexual'] = (($x['sexual']) ? htmlentities($x['sexual'], ENT_COMPAT,'UTF-8',false) : ''); - $arr['locale'] = (($x['locale']) ? htmlentities($x['locale'], ENT_COMPAT,'UTF-8',false) : ''); - $arr['region'] = (($x['region']) ? htmlentities($x['region'], ENT_COMPAT,'UTF-8',false) : ''); - $arr['postcode'] = (($x['postcode']) ? htmlentities($x['postcode'], ENT_COMPAT,'UTF-8',false) : ''); - $arr['country'] = (($x['country']) ? htmlentities($x['country'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['gender'] = (($x['gender']) ? htmlspecialchars($x['gender'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['marital'] = (($x['marital']) ? htmlspecialchars($x['marital'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['sexual'] = (($x['sexual']) ? htmlspecialchars($x['sexual'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['locale'] = (($x['locale']) ? htmlspecialchars($x['locale'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['region'] = (($x['region']) ? htmlspecialchars($x['region'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['postcode'] = (($x['postcode']) ? htmlspecialchars($x['postcode'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['country'] = (($x['country']) ? htmlspecialchars($x['country'], ENT_COMPAT,'UTF-8',false) : ''); $arr['keywords'] = (($x['keywords'] && is_array($x['keywords'])) ? array_sanitise($x['keywords']) : array()); @@ -1606,6 +1702,8 @@ function item_store($arr,$allow_exec = false) { if(! $arr['plink']) $arr['plink'] = $arr['llink']; + + if($arr['parent_mid'] === $arr['mid']) { $parent_id = 0; $parent_deleted = 0; @@ -2087,6 +2185,17 @@ function send_status_notifications($post_id,$item) { } } + $link = get_app()->get_baseurl() . '/display/' . $item['mid']; + + + $y = q("select id from notify where link = '%s' and uid = %d limit 1", + dbesc($link), + intval($item['uid']) + ); + + if($y) + $notify = false; + if(! $notify) return; require_once('include/enotify.php'); @@ -2095,7 +2204,7 @@ function send_status_notifications($post_id,$item) { 'from_xchan' => $item['author_xchan'], 'to_xchan' => $r[0]['channel_hash'], 'item' => $item, - 'link' => get_app()->get_baseurl() . '/display/' . $item['mid'], + 'link' => $link, 'verb' => ACTIVITY_POST, 'otype' => 'item', 'parent' => $parent, @@ -2150,6 +2259,34 @@ function tag_deliver($uid,$item_id) { $item = $i[0]; + $terms = get_terms_oftype($item['term'],TERM_BOOKMARK); + + if($terms && (! $item['item_restrict'])) { + logger('tag_deliver: found bookmark'); + $bookmark_self = intval(get_pconfig($uid,'system','bookmark_self')); + if(perm_is_allowed($u[0]['channel_id'],$item['author_xchan'],'bookmark') && (($item['author_xchan'] != $u[0]['channel_hash']) || ($bookmark_self))) { + require_once('include/bookmarks.php'); + require_once('include/Contact.php'); + + $s = q("select * from xchan where xchan_hash = '%s' limit 1", + dbesc($item['author_xchan']) + ); + if($s) { + foreach($terms as $t) { + bookmark_add($u[0],$s[0],$t,$item['item_private']); + } + } + } + } + + if(($item['source_xchan']) && ($item['item_flags'] & ITEM_UPLINK) && ($item['item_flags'] & ITEM_THREAD_TOP) && ($item['edited'] != $item['created'])) { + // this is an update to a post which was already processed by us and has a second delivery chain + // Just start the second delivery chain to deliver the updated post + proc_run('php','include/notifier.php','tgroup',$item['id']); + return; + } + + if($item['obj_type'] === ACTIVITY_OBJ_TAGTERM) { // We received a community tag activity for a post. @@ -2172,6 +2309,13 @@ function tag_deliver($uid,$item_id) { if(is_array($j_obj['link'])) $taglink = get_rel_link($j_obj['link'],'alternate'); store_item_tag($u[0]['channel_id'],$p[0]['id'],TERM_OBJ_POST,TERM_HASHTAG,$j_obj['title'],$j_obj['id']); + $x = q("update item set edited = '%s', received = '%s', changed = '%s' where mid = '%s' and uid = %d limit 1", + dbesc(datetime_convert()), + dbesc(datetime_convert()), + dbesc(datetime_convert()), + dbesc($j_tgt['id']), + intval($u[0]['channel_id']) + ); proc_run('php','include/notifier.php','edit_post',$p[0]['id']); } } @@ -2187,7 +2331,7 @@ function tag_deliver($uid,$item_id) { logger('check_item_source returns true'); - // This might be a followup by the original post author to a tagged forum + // This might be a followup (e.g. comment) by the original post author to a tagged forum // If so setup a second delivery chain $r = null; @@ -2206,7 +2350,7 @@ function tag_deliver($uid,$item_id) { // now change this copy of the post to a forum head message and deliver to all the tgroup members // also reset all the privacy bits to the forum default permissions - $private = (($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0); + $private = (($u[0]['channel_allow_cid'] || $u[0]['channel_allow_gid'] || $u[0]['channel_deny_cid'] || $u[0]['channel_deny_gid']) ? 1 : 0); $flag_bits = ITEM_WALL|ITEM_ORIGIN; @@ -2222,10 +2366,10 @@ function tag_deliver($uid,$item_id) { deny_cid = '%s', deny_gid = '%s', item_private = %d where id = %d limit 1", intval($flag_bits), dbesc($u[0]['channel_hash']), - dbesc($u[0]['allow_cid']), - dbesc($u[0]['allow_gid']), - dbesc($u[0]['deny_cid']), - dbesc($u[0]['deny_gid']), + dbesc($u[0]['channel_allow_cid']), + dbesc($u[0]['channel_allow_gid']), + dbesc($u[0]['channel_deny_cid']), + dbesc($u[0]['channel_deny_gid']), intval($private), intval($item_id) ); @@ -2245,7 +2389,7 @@ function tag_deliver($uid,$item_id) { if($terms) { foreach($terms as $term) { - if(($term['term'] == $u[0]['channel_name']) && link_compare($term['url'],$link)) { + if((strcasecmp($term['term'],$u[0]['channel_name']) == 0) && link_compare($term['url'],$link)) { $mention = true; break; } @@ -2278,7 +2422,7 @@ function tag_deliver($uid,$item_id) { $body = preg_replace('/\[share(.*?)\[\/share\]/','',$body); - $pattern = '/@\[zrl\=' . preg_quote($term['url'],'/') . '\]' . preg_quote($u[0]['channel_name'],'/') . '\[\/zrl\]/'; + $pattern = '/@\!?\[zrl\=' . preg_quote($term['url'],'/') . '\]' . preg_quote($u[0]['channel_name'],'/') . '\[\/zrl\]/'; if(! preg_match($pattern,$body,$matches)) { logger('tag_deliver: mention was in a reshare - ignoring'); @@ -2308,8 +2452,10 @@ function tag_deliver($uid,$item_id) { } - if((! $mention) && (! $union)) + if((! $mention) && (! $union)) { + logger('tag_deliver: no mention and no union.'); return; + } // tgroup delivery - setup a second delivery chain @@ -2326,7 +2472,7 @@ function tag_deliver($uid,$item_id) { // now change this copy of the post to a forum head message and deliver to all the tgroup members // also reset all the privacy bits to the forum default permissions - $private = (($u[0]['allow_cid'] || $u[0]['allow_gid'] || $u[0]['deny_cid'] || $u[0]['deny_gid']) ? 1 : 0); + $private = (($u[0]['channel_allow_cid'] || $u[0]['channel_allow_gid'] || $u[0]['channel_deny_cid'] || $u[0]['channel_deny_gid']) ? 1 : 0); $flag_bits = ITEM_WALL|ITEM_ORIGIN|ITEM_UPLINK; @@ -2340,10 +2486,10 @@ function tag_deliver($uid,$item_id) { deny_cid = '%s', deny_gid = '%s', item_private = %d where id = %d limit 1", intval($flag_bits), dbesc($u[0]['channel_hash']), - dbesc($u[0]['allow_cid']), - dbesc($u[0]['allow_gid']), - dbesc($u[0]['deny_cid']), - dbesc($u[0]['deny_gid']), + dbesc($u[0]['channel_allow_cid']), + dbesc($u[0]['channel_allow_gid']), + dbesc($u[0]['channel_deny_cid']), + dbesc($u[0]['channel_deny_gid']), intval($private), intval($item_id) ); @@ -2363,12 +2509,13 @@ function tgroup_check($uid,$item) { $mention = false; // check that the message originated elsewhere and is a top-level post - // or is a followup and we have already accepted the top level post + // or is a followup and we have already accepted the top level post as an uplink if($item['mid'] != $item['parent_mid']) { - $r = q("select id from item where mid = '%s' and uid = %d limit 1", + $r = q("select id from item where mid = '%s' and uid = %d and ( item_flags & %d ) limit 1", dbesc($item['parent_mid']), - intval($uid) + intval($uid), + intval(ITEM_UPLINK) ); if($r) return true; @@ -2411,7 +2558,7 @@ function tgroup_check($uid,$item) { $body = preg_replace('/\[share(.*?)\[\/share\]/','',$item['body']); - $pattern = '/@\[zrl\=' . preg_quote($term['url'],'/') . '\]' . preg_quote($u[0]['channel_name'],'/') . '\[\/zrl\]/'; + $pattern = '/@\!?\[zrl\=' . preg_quote($term['url'],'/') . '\]' . preg_quote($u[0]['channel_name'],'/') . '\[\/zrl\]/'; if(! preg_match($pattern,$body,$matches)) { logger('tgroup_check: mention was in a reshare - ignoring'); @@ -2442,9 +2589,9 @@ function check_item_source($uid,$item) { return false; - $r = q("select * from source where src_channel_id = %d and src_xchan = '%s' limit 1", + $r = q("select * from source where src_channel_id = %d and ( src_xchan = '%s' || src_xchan = '*' ) limit 1", intval($uid), - dbesc($item['owner_xchan']) + dbesc(($item['source_xchan']) ? $item['source_xchan'] : $item['owner_xchan']) ); if(! $r) @@ -2478,7 +2625,7 @@ function check_item_source($uid,$item) { foreach($words as $word) { if(substr($word,0,1) === '#' && $tags) { foreach($tags as $t) - if($t['type'] == TERM_HASHTAG && substr($t,1) === $word) + if(($t['type'] == TERM_HASHTAG) && ((substr($t,1) === substr($word,1)) || (substr($word,1) === '*'))) return true; } if(stristr($text,$word) !== false) @@ -3537,26 +3684,6 @@ function posted_dates($uid,$wall) { } -function posted_date_widget($url,$uid,$wall) { - $o = ''; - - if(! feature_enabled($uid,'archives')) - return $o; - - $ret = posted_dates($uid,$wall); - if(! count($ret)) - return $o; - - $o = replace_macros(get_markup_template('posted_date_widget.tpl'),array( - '$title' => t('Archives'), - '$size' => ((count($ret) > 6) ? 6 : count($ret)), - '$url' => $url, - '$dates' => $ret - )); - return $o; -} - - function fetch_post_tags($items,$link = false) { $tag_finder = array(); @@ -3695,23 +3822,28 @@ function items_fetch($arr,$channel = null,$observer_hash = null,$client_mode = C $def_acl = ''; $item_uids = ' true '; - + + if ($arr['uid']) $uid= $arr['uid']; + if($channel) { $uid = $channel['channel_id']; $uidhash = $channel['channel_hash']; $item_uids = " item.uid = " . intval($uid) . " "; } - + if($arr['star']) $sql_options .= " and (item_flags & " . intval(ITEM_STARRED) . ") "; if($arr['wall']) $sql_options .= " and (item_flags & " . intval(ITEM_WALL) . ") "; - + $sql_extra = " AND item.parent IN ( SELECT parent FROM item WHERE (item_flags & " . intval(ITEM_THREAD_TOP) . ") $sql_options ) "; - + + if($arr['since_id']) + $sql_extra .= " and item.id > " . $since_id . " "; + if($arr['gid'] && $uid) { - $r = q("SELECT * FROM `group` WHERE id = %d AND uid = %d LIMIT 1", + $r = q("SELECT * FROM `groups` WHERE id = %d AND uid = %d LIMIT 1", intval($arr['group']), intval($uid) ); @@ -3789,6 +3921,7 @@ function items_fetch($arr,$channel = null,$observer_hash = null,$client_mode = C ); } + if(($client_mode & CLIENT_MODE_UPDATE) && (! ($client_mode & CLIENT_MODE_LOAD))) { // only setup pagination on initial page view @@ -3801,6 +3934,8 @@ function items_fetch($arr,$channel = null,$observer_hash = null,$client_mode = C $pager_sql = sprintf(" LIMIT %d, %d ",intval(get_app()->pager['start']), intval(get_app()->pager['itemspage'])); } + if(isset($arr['start']) && isset($arr['records'])) + $pager_sql = sprintf(" LIMIT %d, %d ",intval($arr['start']), intval($arr['records'])); if(($arr['cmin'] != 0) || ($arr['cmax'] != 99)) { @@ -3835,7 +3970,7 @@ function items_fetch($arr,$channel = null,$observer_hash = null,$client_mode = C $item_restrict = " AND item_restrict = 0 "; - if($arr['nouveau'] && ($client_mode & CLIENT_MODELOAD) && $channel) { + if($arr['nouveau'] && ($client_mode & CLIENT_MODE_LOAD) && $channel) { // "New Item View" - show all items unthreaded in reverse created date order $items = q("SELECT item.*, item.id AS item_id FROM item @@ -3860,7 +3995,7 @@ function items_fetch($arr,$channel = null,$observer_hash = null,$client_mode = C else $ordering = "commented"; - if(($client_mode & CLIENT_MODE_LOAD) || ($client_mode & CLIENT_MODE_NORMAL)) { + if(($client_mode & CLIENT_MODE_LOAD) || ($client_mode == CLIENT_MODE_NORMAL)) { // Fetch a page full of parent items for this page @@ -3873,7 +4008,7 @@ function items_fetch($arr,$channel = null,$observer_hash = null,$client_mode = C ORDER BY item.$ordering DESC $pager_sql ", intval(ABOOK_FLAG_BLOCKED) ); - + } else { // update @@ -3928,3 +4063,52 @@ function items_fetch($arr,$channel = null,$observer_hash = null,$client_mode = C return $items; } + + +function update_remote_id($channel,$post_id,$webpage,$pagetitle,$namespace,$remote_id,$mid) { + + $page_type = ''; + + if($webpage & ITEM_WEBPAGE) + $page_type = 'WEBPAGE'; + elseif($webpage & ITEM_BUILDBLOCK) + $page_type = 'BUILDBLOCK'; + elseif($webpage & ITEM_PDL) + $page_type = 'PDL'; + elseif($namespace && $remote_id) { + $page_type = $namespace; + $pagetitle = $remote_id; + } + + if($page_type) { + + // store page info as an alternate message_id so we can access it via + // https://sitename/page/$channelname/$pagetitle + // if no pagetitle was given or it couldn't be transliterated into a url, use the first + // sixteen bytes of the mid - which makes the link portable and not quite as daunting + // as the entire mid. If it were the post_id the link would be less portable. + + $r = q("select * from item_id where iid = %d and uid = %d and service = '%s' limit 1", + intval($post_id), + intval($channel['channel_id']), + dbesc($page_type) + ); + if($r) { + q("update item_id set sid = '%s' where id = %d limit 1", + dbesc(($pagetitle) ? $pagetitle : substr($mid,0,16)), + intval($r[0]['id']) + ); + } + else { + q("insert into item_id ( iid, uid, sid, service ) values ( %d, %d, '%s','%s' )", + intval($post_id), + intval($channel['channel_id']), + dbesc(($pagetitle) ? $pagetitle : substr($mid,0,16)), + dbesc($page_type) + ); + } + } + +} + + diff --git a/include/language.php b/include/language.php index 2e7ad5ff1..b43f5aacc 100644 --- a/include/language.php +++ b/include/language.php @@ -1,22 +1,28 @@ -<?php /** @file */ - - +<?php /** - * translation support + * @file + * + * @brief translation support + * + * This file contains functions to work with translations and other + * language related tasks. */ /** + * @brief Get the browser's submitted preferred languages. + * + * This functions parses the HTTP_ACCEPT_LANGUAGE header sent by the browser and + * extracts the preferred languages and their priority. * * Get the language setting directly from system variables, bypassing get_config() * 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 */ - function get_browser_language() { - $langs = array(); if (x($_SERVER,'HTTP_ACCEPT_LANGUAGE')) { @@ -43,9 +49,18 @@ function get_browser_language() { return $langs; } - +/** + * @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 + * which there exists a translation. + * + * If there is no match fall back to config['system']['language'] + * + * @return Language code in 2-letter ISO 639-1 (en). + */ function get_best_language() { - $langs = get_browser_language(); if(isset($langs) && count($langs)) { @@ -79,7 +94,6 @@ function push_lang($language) { $a->strings = array(); load_translation_table($language); $a->language = $language; - } function pop_lang() { @@ -109,7 +123,7 @@ function load_translation_table($lang, $install = false) { if(! $install) { $plugins = q("SELECT name FROM addon WHERE installed=1;"); - if ($plugins!==false) { + if ($plugins !== false) { foreach($plugins as $p) { $name = $p['name']; if(file_exists("addon/$name/lang/$lang/strings.php")) { @@ -128,15 +142,18 @@ function load_translation_table($lang, $install = false) { } -// translate string if translation exists - +/** + * @brief translate string if translation exists. + * + * @param s string that should get translated + * @return translated string if exsists, otherwise s + */ function t($s) { - global $a; if(x($a->strings,$s)) { $t = $a->strings[$s]; - return is_array($t)?$t[0]:$t; + return is_array($t) ? $t[0] : $t; } return $s; } @@ -147,14 +164,14 @@ function tt($singular, $plural, $count){ if(x($a->strings,$singular)) { $t = $a->strings[$singular]; - $f = 'string_plural_select_' . str_replace('-','_',$a->language); + $f = 'string_plural_select_' . str_replace('-', '_', $a->language); if(! function_exists($f)) $f = 'string_plural_select_default'; $k = $f($count); - return is_array($t)?$t[$k]:$t; + return is_array($t) ? $t[$k] : $t; } - if ($count!=1){ + if ($count != 1){ return $plural; } else { return $singular; @@ -168,84 +185,47 @@ function string_plural_select_default($n) { return ($n != 1); } - - +/** + * @brief Takes a string and tries to identify the language. + * + * It uses the pear library Text_LanguageDetect and it can identify 52 human languages. + * It returns the identified languges and a confidence score for each. + * + * Strings need to have a min length config['system']['language_detect_min_length'] + * and you can influence the confidence that must be met before a result will get + * returned through config['system']['language_detect_min_confidence']. + * + * @see http://pear.php.net/package/Text_LanguageDetect + * @param s A string to examine + * @return Language code in 2-letter ISO 639-1 (en, de, fr) format + */ function detect_language($s) { - - $detected_languages = array( - 'Albanian' => 'sq', - 'Arabic' => 'ar', - 'Azeri' => 'az', - 'Bengali' => 'bn', - 'Bulgarian' => 'bg', - 'Cebuano' => '', - 'Croatian' => 'hr', - 'Czech' => 'cz', - 'Danish' => 'da', - 'Dutch' => 'nl', - 'English' => 'en', - 'Estonian' => 'et', - 'Farsi' => 'fa', - 'Finnish' => 'fi', - 'French' => 'fr', - 'German' => 'de', - 'Hausa' => 'ha', - 'Hawaiian' => '', - 'Hindi' => 'hi', - 'Hungarian' => 'hu', - 'Icelandic' => 'is', - 'Indonesian' => 'id', - 'Italian' => 'it', - 'Kazakh' => 'kk', - 'Kyrgyz' => 'ky', - 'Latin' => 'la', - 'Latvian' => 'lv', - 'Lithuanian' => 'lt', - 'Macedonian' => 'mk', - 'Mongolian' => 'mn', - 'Nepali' => 'ne', - 'Norwegian' => 'no', - 'Pashto' => 'ps', - 'Pidgin' => '', - 'Polish' => 'pl', - 'Portuguese' => 'pt', - 'Romanian' => 'ro', - 'Russian' => 'ru', - 'Serbian' => 'sr', - 'Slovak' => 'sk', - 'Slovene' => 'sl', - 'Somali' => 'so', - 'Spanish' => 'es', - 'Swahili' => 'sw', - 'Swedish' => 'sv', - 'Tagalog' => 'tl', - 'Turkish' => 'tr', - 'Ukrainian' => 'uk', - 'Urdu' => 'ur', - 'Uzbek' => 'uz', - 'Vietnamese' => 'vi', - 'Welsh' => 'cy' - ); - require_once('Text/LanguageDetect.php'); - $min_length = get_config('system','language_detect_min_length'); + $min_length = get_config('system', 'language_detect_min_length'); if($min_length === false) $min_length = LANGUAGE_DETECT_MIN_LENGTH; - $min_confidence = get_config('system','language_detect_min_confidence'); + $min_confidence = get_config('system', 'language_detect_min_confidence'); if($min_confidence === false) $min_confidence = LANGUAGE_DETECT_MIN_CONFIDENCE; - - $naked_body = preg_replace('/\[(.+?)\]/','',$s); - if(mb_strlen($naked_body) < intval($min_length)) + // strip off bbcode + $naked_body = preg_replace('/\[(.+?)\]/', '', $s); + if(mb_strlen($naked_body) < intval($min_length)) { + logger('detect language: string length less than ' . intval($min_length), LOGGER_DATA); return ''; + } $l = new Text_LanguageDetect; - $lng = $l->detectConfidence($naked_body); - - logger('detect language: ' . print_r($lng,true) . $naked_body, LOGGER_DATA); + try { + // return 2-letter ISO 639-1 (en) language code + $l->setNameMode(2); + $lng = $l->detectConfidence($naked_body); + logger('detect language: ' . print_r($lng, true) . $naked_body, LOGGER_DATA); + } catch (Text_LanguageDetect_Exception $e) { + logger('detect language exception: ' . $e->getMessage(), LOGGER_DATA); + } if((! $lng) || (! (x($lng,'language')))) { return ''; @@ -256,6 +236,29 @@ function detect_language($s) { return ''; } - return(($lng && (x($lng,'language'))) ? $detected_languages[ucfirst($lng['language'])] : ''); + return($lng['language']); +} + +/** + * @brief Returns the display name of a given language code. + * + * By default we use the localized language name. You can switch the result + * to any language with the optional 2nd parameter $l. + * + * $s and $l can be in any format that PHP's Locale understands. We will mostly + * use the 2-letter ISO 639-1 (en, de, fr) format. + * + * If nothing could be looked up it returns $s. + * + * @param $s Language code to look up + * @param $l (optional) In which language to return the name + * @return string with the language name, or $s if unrecognized + */ +function get_language_name($s, $l = null) { + if($l === null) + $l = $s; + logger('get_language_name: for ' . $s . ' in ' . $l . ' returns: ' . Locale::getDisplayLanguage($s, $l), LOGGER_DEBUG); + return Locale::getDisplayLanguage($s, $l); } + diff --git a/include/menu.php b/include/menu.php index c10a669b3..2f1719d0b 100644 --- a/include/menu.php +++ b/include/menu.php @@ -1,6 +1,7 @@ <?php /** @file */ require_once('include/security.php'); +require_once('include/bbcode.php'); function menu_fetch($name,$uid,$observer_xchan) { @@ -23,18 +24,21 @@ function menu_fetch($name,$uid,$observer_xchan) { return null; } - -function menu_render($menu) { +function menu_render($menu, $edit = false) { if(! $menu) return ''; - for($x = 0; $x < count($menu['items']); $x ++) - if($menu['items']['mitem_flags'] & MENU_ITEM_ZID) - $menu['items']['mitem_link'] = zid($menu['items']['mitem_link']); - if($menu['items']['mitem_flags'] & MENU_ITEM_NEWWIN) - $menu['items']['newwin'] = '1'; + + for($x = 0; $x < count($menu['items']); $x ++) { + if($menu['items'][$x]['mitem_flags'] & MENU_ITEM_ZID) + $menu['items'][$x]['mitem_link'] = zid($menu['items'][$x]['mitem_link']); + if($menu['items'][$x]['mitem_flags'] & MENU_ITEM_NEWWIN) + $menu['items'][$x]['newwin'] = '1'; + $menu['items'][$x]['mitem_desc'] = bbcode($menu['items'][$x]['mitem_desc']); + } return replace_macros(get_markup_template('usermenu.tpl'),array( '$menu' => $menu['menu'], + '$edit' => (($edit) ? t("Edit") : ''), '$items' => $menu['items'] )); } @@ -58,6 +62,8 @@ function menu_create($arr) { $menu_name = trim(escape_tags($arr['menu_name'])); $menu_desc = trim(escape_tags($arr['menu_desc'])); + $menu_flags = intval($arr['menu_flags']); + if(! $menu_desc) $menu_desc = $menu_name; @@ -65,6 +71,9 @@ function menu_create($arr) { if(! $menu_name) return false; + if(! $menu_flags) + $menu_flags = 0; + $menu_channel_id = intval($arr['menu_channel_id']); @@ -76,10 +85,11 @@ function menu_create($arr) { if($r) return false; - $r = q("insert into menu ( menu_name, menu_desc, menu_channel_id ) - values( '%s', '%s', %d )", + $r = q("insert into menu ( menu_name, menu_desc, menu_flags, menu_channel_id ) + values( '%s', '%s', %d, %d )", dbesc($menu_name), dbesc($menu_desc), + intval($menu_flags), intval($menu_channel_id) ); if(! $r) @@ -95,8 +105,19 @@ function menu_create($arr) { } -function menu_list($channel_id) { - $r = q("select * from menu where menu_channel_id = %d order by menu_name", +/** + * If $flags is present, check that all the bits in $flags are set + * so that MENU_SYSTEM|MENU_BOOKMARK will return entries with both + * bits set. We will use this to find system generated bookmarks. + */ + +function menu_list($channel_id, $name = '', $flags = 0) { + + $sel_options = ''; + $sel_options .= (($name) ? " and menu_name = '" . protect_sprintf(dbesc($name)) . "' " : ''); + $sel_options .= (($flags) ? " and menu_flags = " . intval($flags) . " " : ''); + + $r = q("select * from menu where menu_channel_id = %d $sel_options order by menu_desc", intval($channel_id) ); return $r; @@ -110,6 +131,7 @@ function menu_edit($arr) { $menu_name = trim(escape_tags($arr['menu_name'])); $menu_desc = trim(escape_tags($arr['menu_desc'])); + $menu_flags = intval($arr['menu_flags']); if(! $menu_desc) $menu_desc = $menu_name; @@ -117,6 +139,9 @@ function menu_edit($arr) { if(! $menu_name) return false; + if(! $menu_flags) + $menu_flags = 0; + $menu_channel_id = intval($arr['menu_channel_id']); @@ -139,21 +164,11 @@ function menu_edit($arr) { return false; } - - $r = q("select * from menu where menu_name = '%s' and menu_channel_id = %d and menu_desc = '%s' limit 1", - dbesc($menu_name), - intval($menu_channel_id), - dbesc($menu_desc) - ); - - if($r) - return false; - - - return q("update menu set menu_name = '%s', menu_desc = '%s' + return q("update menu set menu_name = '%s', menu_desc = '%s', menu_flags = %d where menu_id = %d and menu_channel_id = %d limit 1", dbesc($menu_name), dbesc($menu_desc), + intval($menu_flags), intval($menu_id), intval($menu_channel_id) ); @@ -201,7 +216,8 @@ function menu_add_item($menu_id, $uid, $arr) { $channel = get_app()->get_channel(); } - if ((! $arr['contact_allow']) + if (($channel) + && (! $arr['contact_allow']) && (! $arr['group_allow']) && (! $arr['contact_deny']) && (! $arr['group_deny'])) { @@ -220,11 +236,11 @@ function menu_add_item($menu_id, $uid, $arr) { $str_contact_deny = perms2str($arr['contact_deny']); } - - $allow_cid = perms2str($arr['allow_cid']); - $allow_gid = perms2str($arr['allow_gid']); - $deny_cid = perms2str($arr['deny_cid']); - $deny_gid = perms2str($arr['deny_gid']); +// unused +// $allow_cid = perms2str($arr['allow_cid']); +// $allow_gid = perms2str($arr['allow_gid']); +// $deny_cid = perms2str($arr['deny_cid']); +// $deny_gid = perms2str($arr['deny_gid']); $r = q("insert into menu_item ( mitem_link, mitem_desc, mitem_flags, allow_cid, allow_gid, deny_cid, deny_gid, mitem_channel_id, mitem_menu_id, mitem_order ) values ( '%s', '%s', %d, '%s', '%s', '%s', '%s', %d, %d, %d ) ", dbesc($mitem_link), diff --git a/include/nav.php b/include/nav.php index f89de2de0..8fef4a1f9 100644 --- a/include/nav.php +++ b/include/nav.php @@ -75,10 +75,14 @@ EOT; $nav['usermenu'][] = Array('channel/' . $channel['channel_address'], t('Home'), "", t('Your posts and conversations')); $nav['usermenu'][] = Array('profile/' . $channel['channel_address'], t('View Profile'), "", t('Your profile page')); if(feature_enabled(local_user(),'multi_profiles')) - $nav['usermenu'][] = Array('profiles', t('Edit Profiles'),"", t('Manage/Edit Profiles')); + $nav['usermenu'][] = Array('profiles', t('Edit Profiles'),"", t('Manage/Edit profiles')); $nav['usermenu'][] = Array('photos/' . $channel['channel_address'], t('Photos'), "", t('Your photos')); -// $nav['usermenu'][] = Array('events/', t('Events'), "", t('Your events')); - + $nav['usermenu'][] = Array('cloud/' . $channel['channel_address'],t('Files'),"",t('Your files')); + $nav['usermenu'][] = Array('chat/' . $channel['channel_address'],t('Chat'),"",t('Your chatrooms')); + $nav['usermenu'][] = Array('events', t('Events'), "", t('Your events')); + $nav['usermenu'][] = Array('bookmarks', t('Bookmarks'), "", t('Your bookmarks')); + if(feature_enabled($channel['channel_id'],'webpages')) + $nav['usermenu'][] = Array('webpages/' . $channel['channel_address'],t('Webpages'),"",t('Your webpages')); } else { if(! get_account_id()) @@ -165,7 +169,7 @@ EOT; $nav['messages']['mark'] = array('', t('Mark all private messages seen'), '',''); $nav['messages']['inbox'] = array('message', t('Inbox'), "", t('Inbox')); $nav['messages']['outbox']= array('message/sent', t('Outbox'), "", t('Outbox')); - $nav['messages']['new'] = array('message/new', t('New Message'), "", t('New Message')); + $nav['messages']['new'] = array('mail/new', t('New Message'), "", t('New Message')); $nav['all_events'] = array('events', t('Events'), "", t('Event Calendar')); @@ -196,7 +200,7 @@ EOT; $banner = get_config('system','banner'); if($banner === false) - $banner = 'red'; + $banner = get_config('system','sitename'); $x = array('nav' => $nav, 'usermenu' => $userinfo ); call_hooks('nav', $x); diff --git a/include/network.php b/include/network.php index 50f853ca0..1fb4beaa7 100644 --- a/include/network.php +++ b/include/network.php @@ -78,7 +78,7 @@ function z_fetch_url($url, $binary = false, $redirects = 0, $opts = array()) { @curl_setopt($ch, CURLOPT_BINARYTRANSFER,1); - // don't let curl abort the entire application + // don't let curl abort the entire application' // if it throws any errors. $s = @curl_exec($ch); @@ -86,7 +86,7 @@ function z_fetch_url($url, $binary = false, $redirects = 0, $opts = array()) { $base = $s; $curl_info = @curl_getinfo($ch); $http_code = $curl_info['http_code']; -// logger('fetch_url:' . $http_code . ' data: ' . $s); + //logger('fetch_url:' . $http_code . ' data: ' . $s); $header = ''; // Pull out multiple headers, e.g. proxy and continuation headers @@ -129,7 +129,7 @@ function z_fetch_url($url, $binary = false, $redirects = 0, $opts = array()) { function z_post_url($url,$params, $redirects = 0, $opts = array()) { - + $ret = array('return_code' => 0, 'success' => false, 'header' => "", 'body' => ""); $ch = curl_init($url); @@ -548,7 +548,7 @@ function avatar_img($email) { call_hooks('avatar_lookup', $avatar); if(! $avatar['success']) - $avatar['url'] = $a->get_baseurl() . '/images/default_profile_photos/rainbow_man/175.jpg'; + $avatar['url'] = $a->get_baseurl() . '/' . get_default_profile_photo(); logger('Avatar: ' . $avatar['email'] . ' ' . $avatar['url'], LOGGER_DEBUG); return $avatar['url']; @@ -582,7 +582,7 @@ function scale_external_images($s, $include_link = true, $scale_replace = false) $a = get_app(); // Picture addresses can contain special characters - $s = htmlspecialchars_decode($s); + $s = htmlspecialchars_decode($s, ENT_COMPAT); $matches = null; $c = preg_match_all('/\[img(.*?)\](.*?)\[\/img\]/ism',$s,$matches,PREG_SET_ORDER); diff --git a/include/notifier.php b/include/notifier.php index 0868ac77e..81f971107 100644 --- a/include/notifier.php +++ b/include/notifier.php @@ -425,8 +425,28 @@ function notifier_run($argv, $argc){ $sql_extra = (($private) ? "" : " or hubloc_url = '" . dbesc(z_root()) . "' "); - $r = q("select hubloc_sitekey, hubloc_flags, hubloc_callback, hubloc_host from hubloc - where hubloc_hash in (" . implode(',',$recipients) . ") $sql_extra group by hubloc_sitekey"); + + if($relay_to_owner && (! $private) && ($cmd !== 'relay')) { + + // If sending a followup to the post owner, only send it to one channel clone - to avoid race conditions. + // In this case we'll pick the most recently contacted hub, as their primary might be down and the most + // recently contacted has the best chance of being alive. + + // For private posts or uplinks we have to do things differently as only the sending clone will have the recipient list. + // We have to send to all clone channels of the owner to find out who has the definitive list. Posts with + // item_private set (but no ACL list) will return empty recipients (except for the sender and owner) in + // collect_recipients() above. The end result is we should get only one delivery per delivery chain if we + // aren't the owner or author. + + + $r = q("select hubloc_sitekey, hubloc_flags, hubloc_callback, hubloc_host from hubloc + where hubloc_hash in (" . implode(',',$recipients) . ") group by hubloc_sitekey order by hubloc_connected desc limit 1"); + } + else { + $r = q("select hubloc_sitekey, hubloc_flags, hubloc_callback, hubloc_host from hubloc + where hubloc_hash in (" . implode(',',$recipients) . ") $sql_extra group by hubloc_sitekey"); + } + if(! $r) { logger('notifier: no hubs'); return; diff --git a/include/oembed.php b/include/oembed.php index 520b69892..d8671a752 100755 --- a/include/oembed.php +++ b/include/oembed.php @@ -1,12 +1,10 @@ <?php /** @file */ function oembed_replacecb($matches){ -// logger('oembedcb'); + $embedurl=$matches[1]; $j = oembed_fetch_url($embedurl); - $s = oembed_format_object($j); - return $s;//oembed_iframe($s,$j->width,$j->height); - - + $s = oembed_format_object($j); + return $s; } @@ -26,7 +24,21 @@ function oembed_fetch_url($embedurl){ if(is_null($txt)){ $txt = ""; - if (!in_array($ext, $noexts)){ + if (in_array($ext, $noexts)) { + $m = @parse_url($embedurl); + $zrl = false; + if($m['host']) { + $r = q("select hubloc_url from hubloc where hubloc_host = '%s' limit 1", + dbesc($m['host']) + ); + if($r) + $zrl = true; + } + if($zrl) { + $embedurl = zid($embedurl); + } + } + else { // try oembed autodiscovery $redirects = 0; @@ -57,12 +69,6 @@ function oembed_fetch_url($embedurl){ call_hooks('oembed_probe',$x); if(array_key_exists('embed',$x)) $txt = $x['embed']; - - // try oohembed service -// $ourl = "http://oohembed.com/oohembed/?url=".urlencode($embedurl).'&maxwidth=' . $a->videowidth; -// $result = z_fetch_url($ourl); -// if($result['success']) -// $txt = $result['body']; } $txt=trim($txt); @@ -82,6 +88,7 @@ function oembed_format_object($j){ $a = get_app(); $embedurl = $j->embedurl; $jhtml = oembed_iframe($j->embedurl,(isset($j->width) ? $j->width : null), (isset($j->height) ? $j->height : null) ); + $ret="<span class='oembed ".$j->type."'>"; switch ($j->type) { case "video": { diff --git a/include/permissions.php b/include/permissions.php index 45ea7c3eb..eb1a7966f 100644 --- a/include/permissions.php +++ b/include/permissions.php @@ -24,11 +24,12 @@ function get_perms() { 'post_mail' => array('channel_w_mail', intval(PERMS_W_MAIL), false, t('Can send me private mail messages'), ''), 'post_photos' => array('channel_w_photos', intval(PERMS_W_PHOTOS), false, t('Can post photos to my photo albums'), ''), 'tag_deliver' => array('channel_w_tagwall', intval(PERMS_W_TAGWALL), false, t('Can forward to all my channel contacts via post @mentions'), t('Advanced - useful for creating group forum channels')), - 'chat' => array('channel_w_chat', intval(PERMS_W_CHAT), false, t('Can chat with me (when available)'), t('Requires compatible chat plugin')), + 'chat' => array('channel_w_chat', intval(PERMS_W_CHAT), false, t('Can chat with me (when available)'), t('')), 'write_storage' => array('channel_w_storage', intval(PERMS_W_STORAGE), false, t('Can write to my "public" file storage'), ''), 'write_pages' => array('channel_w_pages', intval(PERMS_W_PAGES), false, t('Can edit my "public" pages'), ''), 'republish' => array('channel_a_republish', intval(PERMS_A_REPUBLISH), false, t('Can source my "public" posts in derived channels'), t('Somewhat advanced - very useful in open communities')), + 'bookmark' => array('channel_a_bookmark', intval(PERMS_A_BOOKMARK), false, t('Can send me bookmarks'), 'Bookmarks from this person will automatically be saved'), 'delegate' => array('channel_a_delegate', intval(PERMS_A_DELEGATE), false, t('Can administer my channel resources'), t('Extremely advanced. Leave this alone unless you know what you are doing')), ); $ret = array('global_permissions' => $global_perms); @@ -87,8 +88,13 @@ function get_all_perms($uid,$observer_xchan,$internal_use = true) { // These take priority over all other settings. if($observer_xchan) { + if($r[0][$channel_perm] & PERMS_AUTHED) { + $ret[$perm_name] = true; + continue; + } + if(! $abook_checked) { - $x = q("select abook_my_perms, abook_flags from abook + $x = q("select abook_my_perms, abook_flags, xchan_network from abook left join xchan on abook_xchan = xchan_hash where abook_channel = %d and abook_xchan = '%s' and not ( abook_flags & %d ) limit 1", intval($uid), dbesc($observer_xchan), @@ -136,9 +142,9 @@ function get_all_perms($uid,$observer_xchan,$internal_use = true) { continue; } - // If we're still here, we have an observer, which means they're in the network. + // If we're still here, we have an observer, check the network. - if($r[0][$channel_perm] & PERMS_NETWORK) { + if(($r[0][$channel_perm] & PERMS_NETWORK) && ($x[0]['xchan_network'] === 'zot')) { $ret[$perm_name] = true; continue; } @@ -239,7 +245,11 @@ function perm_is_allowed($uid,$observer_xchan,$permission) { return false; if($observer_xchan) { - $x = q("select abook_my_perms, abook_flags from abook where abook_channel = %d and abook_xchan = '%s' and not ( abook_flags & %d ) limit 1", + if($r[0][$channel_perm] & PERMS_AUTHED) + return true; + + $x = q("select abook_my_perms, abook_flags, xchan_network from abook left join xchan on abook_xchan = xchan_hash + where abook_channel = %d and abook_xchan = '%s' and not ( abook_flags & %d ) limit 1", intval($uid), dbesc($observer_xchan), intval(ABOOK_FLAG_SELF) @@ -271,9 +281,9 @@ function perm_is_allowed($uid,$observer_xchan,$permission) { return false; } - // If we're still here, we have an observer, which means they're in the network. + // If we're still here, we have an observer, check the network. - if($r[0][$channel_perm] & PERMS_NETWORK) + if(($r[0][$channel_perm] & PERMS_NETWORK) && ($x[0]['xchan_network'] === 'zot')) return true; diff --git a/include/photo/photo_driver.php b/include/photo/photo_driver.php index 8730b4298..484550cb7 100644 --- a/include/photo/photo_driver.php +++ b/include/photo/photo_driver.php @@ -357,7 +357,7 @@ abstract class photo_driver { dbesc($p['resource_id']), dbesc(datetime_convert()), dbesc(datetime_convert()), - dbesc(basename($filename)), + dbesc(basename($p['filename'])), dbesc($this->getType()), dbesc($p['album']), intval($this->getHeight()), @@ -513,38 +513,72 @@ function guess_image_type($filename, $headers = '') { } -function import_profile_photo($photo,$xchan) { +function import_profile_photo($photo,$xchan,$thing = false) { $a = get_app(); + $flags = (($thing) ? PHOTO_THING : PHOTO_XCHAN); + $album = (($thing) ? 'Things' : 'Contact Photos'); + logger('import_profile_photo: updating channel photo from ' . $photo . ' for ' . $xchan, LOGGER_DEBUG); - $r = q("select resource_id from photo where xchan = '%s' and scale = 4 limit 1", - dbesc($xchan) - ); - if($r) { - $hash = $r[0]['resource_id']; - } - else { + if($thing) $hash = photo_new_resource(); + else { + $r = q("select resource_id from photo where xchan = '%s' and (photo_flags & %d ) and scale = 4 limit 1", + dbesc($xchan), + intval(PHOTO_XCHAN) + ); + if($r) { + $hash = $r[0]['resource_id']; + } + else { + $hash = photo_new_resource(); + } } $photo_failure = false; + $img_str = ''; + if($photo) { + $filename = basename($photo); + $type = guess_image_type($photo,true); - $filename = basename($photo); - $type = guess_image_type($photo,true); - $result = z_fetch_url($photo,true); + if(! $type) + $type = 'image/jpeg'; - if($result['success']) - $img_str = $result['body']; + $result = z_fetch_url($photo,true); + + if($result['success']) + $img_str = $result['body']; + } $img = photo_factory($img_str, $type); if($img->is_valid()) { + $width = $img->getWidth(); + $height = $img->getHeight(); + + if($width && $height) { + if(($width / $height) > 1.2) { + // crop out the sides + $margin = $width - $height; + $img->cropImage(175,($margin / 2),0,$height,$height); + } + elseif(($height / $width) > 1.2) { + // crop out the bottom + $margin = $height - $width; + $img->cropImage(175,0,0,$width,$width); - $img->scaleImageSquare(175); + } + else { + $img->scaleImageSquare(175); + } + + } + else + $photo_failure = true; - $p = array('xchan' => $xchan,'resource_id' => $hash, 'filename' => 'Contact Photos', 'photo_flags' => PHOTO_XCHAN, 'scale' => 4); + $p = array('xchan' => $xchan,'resource_id' => $hash, 'filename' => basename($photo), 'album' => $album, 'photo_flags' => $flags, 'scale' => 4); $r = $img->save($p); @@ -576,9 +610,9 @@ function import_profile_photo($photo,$xchan) { $photo_failure = true; } if($photo_failure) { - $photo = $a->get_baseurl() . '/images/default_profile_photos/rainbow_man/175.jpg'; - $thumb = $a->get_baseurl() . '/images/default_profile_photos/rainbow_man/80.jpg'; - $micro = $a->get_baseurl() . '/images/default_profile_photos/rainbow_man/48.jpg'; + $photo = $a->get_baseurl() . '/' . get_default_profile_photo(); + $thumb = $a->get_baseurl() . '/' . get_default_profile_photo(80); + $micro = $a->get_baseurl() . '/' . get_default_profile_photo(48); $type = 'image/jpeg'; } diff --git a/include/photos.php b/include/photos.php index ea4b494e0..82af4aaeb 100644 --- a/include/photos.php +++ b/include/photos.php @@ -77,6 +77,7 @@ function photo_upload($channel, $observer, $args) { $filesize = intval($_FILES['userfile']['size']); $type = $_FILES['userfile']['type']; } + if (! $type) $type=guess_image_type($filename); @@ -102,12 +103,10 @@ function photo_upload($channel, $observer, $args) { $imagedata = @file_get_contents($src); - $r = q("select sum(size) as total from photo where uid = %d and scale = 0 ", - intval($channel_id) + $r = q("select sum(size) as total from photo where aid = %d and scale = 0 ", + intval($account_id) ); -// FIXME service class limits should probably apply to accounts and not channels - $limit = service_class_fetch($channel_id,'photo_upload_limit'); if(($r) && ($limit !== false) && (($r[0]['total'] + strlen($imagedata)) > $limit)) { @@ -218,6 +217,9 @@ function photo_upload($channel, $observer, $args) { $arr['deny_gid'] = $str_group_deny; $arr['verb'] = ACTIVITY_POST; + $arr['plink'] = z_root() . '/channel/' . $channel['channel_address'] . '/?f=&mid=' . $arr['mid']; + + $arr['body'] = '[zrl=' . z_root() . '/photos/' . $channel['channel_address'] . '/image/' . $photo_hash . ']' . '[zmg]' . z_root() . "/photo/{$photo_hash}-{$smallest}.".$ph->getExt() . '[/zmg]' . '[/zrl]'; @@ -267,7 +269,11 @@ function photos_albums_list($channel,$observer) { if($albums) { $ret['success'] = true; foreach($albums as $k => $album) { - $entry = array('text' => $album['album'], 'urlencode' => urlencode($album['album']),'bin2hex' => bin2hex($album['album'])); + $entry = array( + 'text' => $album['album'], + 'url' => z_root() . '/photos/' . $channel['channel_address'] . '/album/' . bin2hex($album['album']), + 'urlencode' => urlencode($album['album']), + 'bin2hex' => bin2hex($album['album'])); $ret[] = $entry; } } @@ -279,8 +285,16 @@ function photos_album_widget($channelx,$observer,$albums = null) { $o = ''; - if(! $albums) - $albums = photos_albums_list($channelx,$observer); + // If we weren't passed an album list, see if the photos module + // dropped one for us to find in $a->data['albums']. + // If all else fails, load it. + + if(! $albums) { + if(array_key_exists('albums', get_app()->data)) + $albums = get_app()->data['albums']; + else + $albums = photos_albums_list($channelx,$observer); + } if($albums) { $o = replace_macros(get_markup_template('photo_albums.tpl'),array( @@ -311,13 +325,16 @@ function photos_list_photos($channel,$observer,$album = '') { $ret = array('success' => false); - $r = q("select resource_id, created, edited, title, `desc`, album, filename, `type`, height, width, `size`, `scale`, profile, photo_flags, allow_cid, allow_gid, deny_cid, deny_gid from photo where uid = %d and ( photo_flags = %d or photo_flags = %d ) $sql_extra ", + $r = q("select resource_id, created, edited, title, description, album, filename, type, height, width, size, scale, profile, photo_flags, allow_cid, allow_gid, deny_cid, deny_gid from photo where uid = %d and ( photo_flags = %d or photo_flags = %d ) $sql_extra ", intval($channel_id), intval(PHOTO_NORMAL), intval(PHOTO_PROFILE) ); - + if($r) { + for($x = 0; $x < count($r); $x ++) { + $r[$x]['src'] = z_root() . '/photo/' . $r[$x]['resource_id'] . '-' . $r[$x]['scale']; + } $ret['success'] = true; $ret['photos'] = $r; } @@ -398,6 +415,8 @@ function photos_create_item($channel, $creator_hash, $photo, $visible = false) { $arr['allow_gid'] = $photo['allow_gid']; $arr['deny_cid'] = $photo['deny_cid']; $arr['deny_gid'] = $photo['deny_gid']; + + $arr['plink'] = z_root() . '/channel/' . $channel['channel_address'] . '/?f=&mid=' . $arr['mid']; $arr['body'] = '[zrl=' . z_root() . '/photos/' . $channel['channel_address'] . '/image/' . $photo['resource_id'] . ']' . '[zmg]' . z_root() . '/photo/' . $photo['resource_id'] . '-' . $photo['scale'] . '[/zmg]' diff --git a/include/plugin.php b/include/plugin.php index 5ed2a1736..9982a48a2 100755 --- a/include/plugin.php +++ b/include/plugin.php @@ -494,6 +494,15 @@ function head_add_css($src,$media = 'screen') { get_app()->css_sources[] = array($src,$media); } + +function head_remove_css($src,$media = 'screen') { + $a = get_app(); + $index = array_search(array($src,$media),$a->css_sources); + if($index !== false) + unset($a->css_sources[$index]); + +} + function head_get_css() { $str = ''; $sources = get_app()->css_sources; @@ -511,15 +520,44 @@ function format_css_if_exists($source) { $path = theme_include($source[0]); if($path) - return '<link rel="stylesheet" href="' . z_root() . '/' . $path . '" type="text/css" media="' . $source[1] . '" />' . "\r\n"; + return '<link rel="stylesheet" href="' . script_path() . '/' . $path . '" type="text/css" media="' . $source[1] . '" />' . "\r\n"; } +function script_path() { + if(x($_SERVER,'HTTPS') && $_SERVER['HTTPS']) + $scheme = 'https'; + elseif(x($_SERVER,'SERVER_PORT') && (intval($_SERVER['SERVER_PORT']) == 443)) + $scheme = 'https'; + else + $scheme = 'http'; + + if(x($_SERVER,'SERVER_NAME')) { + $hostname = $_SERVER['SERVER_NAME']; + } + else { + return z_root(); + } + + if(x($_SERVER,'SERVER_PORT') && $_SERVER['SERVER_PORT'] != 80 && $_SERVER['SERVER_PORT'] != 443) { + $hostname .= ':' . $_SERVER['SERVER_PORT']; + } + + return $scheme . '://' . $hostname; +} function head_add_js($src) { get_app()->js_sources[] = $src; } +function head_remove_js($src) { + $a = get_app(); + $index = array_search($src,$a->js_sources); + if($index !== false) + unset($a->js_sources[$index]); + +} + function head_get_js() { $str = ''; $sources = get_app()->js_sources; @@ -536,7 +574,7 @@ function format_js_if_exists($source) { else $path = theme_include($source); if($path) - return '<script src="' . z_root() . '/' . $path . '" ></script>' . "\r\n" ; + return '<script src="' . script_path() . '/' . $path . '" ></script>' . "\r\n" ; } diff --git a/include/poller.php b/include/poller.php index 0dcec4c0f..1c6f68eab 100644 --- a/include/poller.php +++ b/include/poller.php @@ -32,21 +32,13 @@ function poller_run($argv, $argc){ proc_run('php',"include/queue.php"); - // expire any expired accounts - - q("UPDATE account - SET account_flags = (account_flags | %d) - where not (account_flags & %d) - and account_expires != '0000-00-00 00:00:00' - and account_expires < UTC_TIMESTAMP() ", - intval(ACCOUNT_EXPIRED), - intval(ACCOUNT_EXPIRED) - ); // expire any expired mail q("delete from mail where expires != '0000-00-00 00:00:00' and expires < UTC_TIMESTAMP() "); + // expire any expired items + $r = q("select id from item where expires != '0000-00-00 00:00:00' and expires < UTC_TIMESTAMP() and not ( item_restrict & %d ) ", intval(ITEM_DELETED) @@ -56,7 +48,8 @@ function poller_run($argv, $argc){ foreach($r as $rr) drop_item($rr['id'],false); } - + + // Ensure that every channel pings a directory server once a month. This way we can discover // channels and sites that quietly vanished and prevent the directory from accumulating stale // or dead entries. @@ -64,7 +57,7 @@ function poller_run($argv, $argc){ $r = q("select channel_id from channel where channel_dirdate < UTC_TIMESTAMP() - INTERVAL 30 DAY"); if($r) { foreach($r as $rr) { - proc_run('php','include/directory.php',$rr['channel_id']); + proc_run('php','include/directory.php',$rr['channel_id'],'force'); if($interval) @time_sleep_until(microtime(true) + (float) $interval); } @@ -103,8 +96,19 @@ function poller_run($argv, $argc){ $dirmode = get_config('system','directory_mode'); + + // Actions in the following block are executed once per day, not on every poller run + if($d2 != intval($d1)) { + // expire any read notifications over a month old + + q("delete from notify where seen = 1 and date < UTC_TIMESTAMP() - INTERVAL 30 DAY"); + + // expire any expired accounts + require_once('include/account.php'); + downgrade_accounts(); + // If this is a directory server, request a sync with an upstream // directory at least once a day, up to once every poll interval. // Pull remote changes and push local changes. @@ -115,14 +119,8 @@ function poller_run($argv, $argc){ sync_directories($dirmode); } - set_config('system','last_expire_day',$d2); -// Uncomment when expire protocol component is working -// Update - this is not going to happen. We are only going to -// implement per-item expire, not blanket expiration -// proc_run('php','include/expire.php'); - proc_run('php','include/cli_suggest.php'); } diff --git a/include/reddav.php b/include/reddav.php index c24414610..6182aeacd 100644 --- a/include/reddav.php +++ b/include/reddav.php @@ -1,196 +1,1078 @@ <?php /** @file */ use Sabre\DAV; - require_once('vendor/autoload.php'); +require_once('vendor/autoload.php'); -class RedInode implements DAV\INode { +require_once('include/attach.php'); - private $attach; +class RedDirectory extends DAV\Node implements DAV\ICollection, DAV\IQuota { + + private $red_path; + private $folder_hash; + private $ext_path; + private $root_dir = ''; + private $auth; + private $os_path = ''; + + function __construct($ext_path,&$auth_plugin) { + logger('RedDirectory::__construct() ' . $ext_path, LOGGER_DEBUG); + $this->ext_path = $ext_path; + $this->red_path = ((strpos($ext_path,'/cloud') === 0) ? substr($ext_path,6) : $ext_path); + if(! $this->red_path) + $this->red_path = '/'; + $this->auth = $auth_plugin; + $this->folder_hash = ''; + + $this->getDir(); + + if($this->auth->browser) + $this->auth->browser->set_writeable(); - function __construct($attach) { - $this->attach = $attach; } - function delete() { - if(! perm_is_allowed($this->channel_id,'','view_storage')) - return; - - /** - * Since I don't believe this is documented elsewhere - - * ATTACH_FLAG_OS means that the file contents are stored in the OS - * rather than in the DB - as is the case for attachments. - * Exactly how they are stored (what path and filename) are still - * TBD. We will probably not be using the original filename but - * instead the attachment 'hash' as this will prevent folks from - * uploading PHP code onto misconfigured servers and executing it. - * It's easy to misconfigure servers because we can provide a - * rule for Apache, but folks using nginx will then be susceptible. - * Then there are those who don't understand these kinds of exploits - * and don't have any idea allowing uploaded PHP files to be executed - * by the server could be a problem. We also don't have any idea what - * executable types are served on their system - like .py, .pyc, .pl, .sh - * .cgi, .exe, .bat, .net, whatever. - */ - - if($this->attach['flags'] & ATTACH_FLAG_OS) { - // FIXME delete physical file - } - if($this->attach['flags'] & ATTACH_FLAG_DIR) { - // FIXME delete contents (recursive?) + function log() { + logger('RedDirectory::log() ext_path ' . $this->ext_path, LOGGER_DATA); + logger('RedDirectory::log() os_path ' . $this->os_path, LOGGER_DATA); + logger('RedDirectory::log() red_path ' . $this->red_path, LOGGER_DATA); + } + + function getChildren() { + + logger('RedDirectory::getChildren() called for ' . $this->ext_path, LOGGER_DATA); + + $this->log(); + + if(get_config('system','block_public') && (! $this->auth->channel_id) && (! $this->auth->observer)) { + throw new DAV\Exception\Forbidden('Permission denied.'); + return; + } + + if(($this->auth->owner_id) && (! perm_is_allowed($this->auth->owner_id,$this->auth->observer,'view_storage'))) { + throw new DAV\Exception\Forbidden('Permission denied.'); + return; } + + $contents = RedCollectionData($this->red_path,$this->auth); + return $contents; + } + + + function getChild($name) { + + logger('RedDirectory::getChild : ' . $name, LOGGER_DATA); + + if(get_config('system','block_public') && (! $this->auth->channel_id) && (! $this->auth->observer)) { + throw new DAV\Exception\Forbidden('Permission denied.'); + return; + } + + if(($this->auth->owner_id) && (! perm_is_allowed($this->auth->owner_id,$this->auth->observer,'view_storage'))) { + throw new DAV\Exception\Forbidden('Permission denied.'); + return; + } + + if($this->red_path === '/' && $name === 'cloud') { + return new RedDirectory('/cloud', $this->auth); + } + + $x = RedFileData($this->ext_path . '/' . $name, $this->auth); + if($x) + return $x; + + throw new DAV\Exception\NotFound('The file with name: ' . $name . ' could not be found'); - q("delete from attach where id = %d limit 1", - intval($this->attach['id']) + } + + function getName() { + logger('RedDirectory::getName returns: ' . basename($this->red_path), LOGGER_DATA); + return (basename($this->red_path)); + } + + + + + function createFile($name,$data = null) { + logger('RedDirectory::createFile : ' . $name, LOGGER_DEBUG); + + if(! $this->auth->owner_id) { + logger('createFile: permission denied'); + throw new DAV\Exception\Forbidden('Permission denied.'); + return; + } + + if(! perm_is_allowed($this->auth->owner_id,$this->auth->observer,'write_storage')) { + logger('createFile: permission denied'); + throw new DAV\Exception\Forbidden('Permission denied.'); + return; + } + + $mimetype = z_mime_content_type($name); + + + $c = q("select * from channel where channel_id = %d and not (channel_pageflags & %d) limit 1", + intval($this->auth->owner_id), + intval(PAGE_REMOVED) + + ); + + if(! $c) { + logger('createFile: no channel'); + throw new DAV\Exception\Forbidden('Permission denied.'); + return; + } + + + $filesize = 0; + $hash = random_string(); + + $r = q("INSERT INTO attach ( aid, uid, hash, creator, filename, folder, flags, filetype, filesize, revision, data, created, edited, allow_cid, allow_gid, deny_cid, deny_gid ) + VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s' ) ", + intval($c[0]['channel_account_id']), + intval($c[0]['channel_id']), + dbesc($hash), + dbesc($this->auth->observer), + dbesc($name), + dbesc($this->folder_hash), + dbesc(ATTACH_FLAG_OS), + dbesc($mimetype), + intval($filesize), + intval(0), + dbesc($this->os_path . '/' . $hash), + dbesc(datetime_convert()), + dbesc(datetime_convert()), + dbesc($c[0]['channel_allow_cid']), + dbesc($c[0]['channel_allow_gid']), + dbesc($c[0]['channel_deny_cid']), + dbesc($c[0]['channel_deny_gid']) + + + ); + + $f = 'store/' . $this->auth->owner_nick . '/' . (($this->os_path) ? $this->os_path . '/' : '') . $hash; + + file_put_contents($f, $data); + $size = filesize($f); + + $edited = datetime_convert(); + + $d = q("update attach set filesize = '%s', edited = '%s' where hash = '%s' and uid = %d limit 1", + dbesc($size), + dbesc($edited), + dbesc($hash), + intval($c[0]['channel_id']) ); + $e = q("update attach set edited = '%s' where folder = '%s' and uid = %d limit 1", + dbesc($edited), + dbesc($this->folder_hash), + intval($c[0]['channel_id']) + ); + + $maxfilesize = get_config('system','maxfilesize'); + + if(($maxfilesize) && ($size > $maxfilesize)) { + attach_delete($c[0]['channel_id'],$hash); + return; + } + + $limit = service_class_fetch($c[0]['channel_id'],'attach_upload_limit'); + if($limit !== false) { + $x = q("select sum(filesize) as total from attach where aid = %d ", + intval($c[0]['channel_account_id']) + ); + if(($x) && ($x[0]['total'] + $size > $limit)) { + logger('reddav: service class limit exceeded for ' . $c[0]['channel_name'] . ' total usage is ' . $x[0]['total'] . ' limit is ' . $limit); + attach_delete($c[0]['channel_id'],$hash); + return; + } + } } - function getName() { - return $this->attach['filename']; + + function createDirectory($name) { + + logger('RedDirectory::createDirectory: ' . $name, LOGGER_DEBUG); + + if((! $this->auth->owner_id) || (! perm_is_allowed($this->auth->owner_id,$this->auth->observer,'write_storage'))) { + throw new DAV\Exception\Forbidden('Permission denied.'); + return; + } + + $r = q("select * from channel where channel_id = %d and not (channel_pageflags & %d) limit 1", + intval($this->auth->owner_id), + intval(PAGE_REMOVED) + ); + + if($r) { + $result = attach_mkdir($r[0],$this->auth->observer,array('filename' => $name,'folder' => $this->folder_hash)); + if(! $result['success']) + logger('RedDirectory::createDirectory: ' . print_r($result,true), LOGGER_DEBUG); + } } - function setName($newName) { - if((! $newName) || (! perm_is_allowed($this->channel_id,'','view_storage'))) + function childExists($name) { + + if($this->red_path === '/' && $name === 'cloud') { + logger('RedDirectory::childExists /cloud: true', LOGGER_DATA); + return true; + } + + $x = RedFileData($this->ext_path . '/' . $name, $this->auth,true); + logger('RedFileData returns: ' . print_r($x,true), LOGGER_DATA); + if($x) + return true; + return false; + } + + function getDir() { + logger('getDir: ' . $this->ext_path, LOGGER_DEBUG); + $this->auth->log(); + + $file = $this->ext_path; + + $x = strpos($file,'/cloud'); + if($x === false) + return; + if($x === 0) { + $file = substr($file,6); + } + + if((! $file) || ($file === '/')) { + return; + } + + $file = trim($file,'/'); + $path_arr = explode('/', $file); + + if(! $path_arr) return; - $this->attach['filename'] = $newName; - $r = q("update attach set filename = '%s' where id = %d limit 1", - dbesc($this->attach['filename']), - intval($this->attach['id']) + + logger('getDir(): path: ' . print_r($path_arr,true), LOGGER_DEBUG); + + $channel_name = $path_arr[0]; + + + $r = q("select channel_id from channel where channel_address = '%s' and not ( channel_pageflags & %d ) limit 1", + dbesc($channel_name), + intval(PAGE_REMOVED) ); + if(! $r) { + throw new DAV\Exception\NotFound('The file with name: ' . $channel_name . ' could not be found'); + + return; + } + $channel_id = $r[0]['channel_id']; + $this->auth->owner_id = $channel_id; + $this->auth->owner_nick = $channel_name; + + $path = '/' . $channel_name; + + $folder = ''; + $os_path = ''; + + for($x = 1; $x < count($path_arr); $x ++) { + + $r = q("select id, hash, filename, flags from attach where folder = '%s' and filename = '%s' and (flags & %d)", + dbesc($folder), + dbesc($path_arr[$x]), + intval($channel_id), + intval(ATTACH_FLAG_DIR) + ); + + if($r && ( $r[0]['flags'] & ATTACH_FLAG_DIR)) { + $folder = $r[0]['hash']; + if(strlen($os_path)) + $os_path .= '/'; + $os_path .= $folder; + + $path = $path . '/' . $r[0]['filename']; + } + } + $this->folder_hash = $folder; + $this->os_path = $os_path; + return; } + function getLastModified() { - return $this->attach['edited']; + $r = q("select edited from attach where folder = '%s' and uid = %d order by edited desc limit 1", + dbesc($this->folder_hash), + intval($this->auth->owner_id) + ); + if($r) + return datetime_convert('UTC','UTC', $r[0]['edited'],'U'); + return ''; + } + + + public function getQuotaInfo() { + + $limit = disk_total_space('store'); + $free = disk_free_space('store'); + + if($this->auth->owner_id) { + + $c = q("select * from channel where channel_id = %d and not (channel_pageflags & %d) limit 1", + intval($this->auth->owner_id), + intval(PAGE_REMOVED) + + ); + + $ulimit = service_class_fetch($c[0]['channel_id'],'attach_upload_limit'); + $limit = (($ulimit) ? $ulimit : $limit); + + $x = q("select sum(filesize) as total from attach where aid = %d ", + intval($c[0]['channel_account_id']) + ); + $free = (($x) ? $limit - $x[0]['total'] : 0); + } + + return array( + $limit - $free, + $free + ); + } } -abstract class RedDirectory extends DAV\Node implements DAV\ICollection { +class RedFile extends DAV\Node implements DAV\IFile { - private $red_path; - private $dir_key; + private $data; private $auth; - private $channel_id; + private $name; + + function __construct($name, $data, &$auth) { + $this->name = $name; + $this->data = $data; + $this->auth = $auth; + + logger('RedFile::_construct: ' . print_r($this->data,true), LOGGER_DATA); + } + + + function getName() { + logger('RedFile::getName: ' . basename($this->name), LOGGER_DEBUG); + return basename($this->name); + + } + + + function setName($newName) { + logger('RedFile::setName: ' . basename($this->name) . ' -> ' . $newName, LOGGER_DEBUG); + + if((! $newName) || (! $this->auth->owner_id) || (! perm_is_allowed($this->auth->owner_id,$this->auth->observer,'write_storage'))) { + throw new DAV\Exception\Forbidden('Permission denied.'); + return; + } + + $newName = str_replace('/','%2F',$newName); + + $r = q("update attach set filename = '%s' where hash = '%s' and id = %d limit 1", + dbesc($this->data['filename']), + intval($this->data['id']) + ); - function __construct($red_path,$auth_plugin) { - $this->red_path = $red_path; - $this->auth = $auth_plugin; } - function getChildren() { - if(! perm_is_allowed($this->channel_id,'','view_storage')) - return array(); + function put($data) { + logger('RedFile::put: ' . basename($this->name), LOGGER_DEBUG); + + $c = q("select * from channel where channel_id = %d and not (channel_pageflags & %d) limit 1", + intval(PAGE_REMOVED), + intval($this->auth->owner_id) + ); - $ret = array(); - $r = q("select distinct filename from attach where folder = '%s' and uid = %d group by filename", - dbesc($this->dir_key), - intval($this->channel_id) + $r = q("select flags, folder, data from attach where hash = '%s' and uid = %d limit 1", + dbesc($this->data['hash']), + intval($c[0]['channel_id']) ); if($r) { - foreach($r as $rr) { - $ret[] = $rr['filename']; + if($r[0]['flags'] & ATTACH_FLAG_OS) { + $f = 'store/' . $this->auth->owner_nick . '/' . (($r[0]['data']) ? $r[0]['data'] : ''); + @file_put_contents($f, $data); + $size = @filesize($f); + logger('reddav: put() filename: ' . $f . ' size: ' . $size, LOGGER_DEBUG); } + else { + $r = q("update attach set data = '%s' where hash = '%s' and uid = %d limit 1", + dbesc(stream_get_contents($data)), + dbesc($this->data['hash']), + intval($this->data['uid']) + ); + $r = q("select length(data) as fsize from attach where hash = '%s' and uid = %d limit 1", + dbesc($this->data['hash']), + intval($this->data['uid']) + ); + if($r) + $size = $r[0]['fsize']; + } + + } + + $edited = datetime_convert(); + + $d = q("update attach set filesize = '%s', edited = '%s' where hash = '%s' and uid = %d limit 1", + dbesc($size), + dbesc($edited), + dbesc($this->data['hash']), + intval($c[0]['channel_id']) + ); + + $e = q("update attach set edited = '%s' where folder = '%s' and uid = %d limit 1", + dbesc($edited), + dbesc($r[0]['folder']), + intval($c[0]['channel_id']) + ); + + $maxfilesize = get_config('system','maxfilesize'); + + if(($maxfilesize) && ($size > $maxfilesize)) { + attach_delete($c[0]['channel_id'],$this->data['hash']); + return; } - return $ret; + $limit = service_class_fetch($c[0]['channel_id'],'attach_upload_limit'); + if($limit !== false) { + $x = q("select sum(filesize) as total from attach where aid = %d ", + intval($c[0]['channel_account_id']) + ); + if(($x) && ($x[0]['total'] + $size > $limit)) { + logger('reddav: service class limit exceeded for ' . $c[0]['channel_name'] . ' total usage is ' . $x[0]['total'] . ' limit is ' . $limit); + attach_delete($c[0]['channel_id'],$this->data['hash']); + return; + } + } } - function getChild($name) { - if(! perm_is_allowed($this->channel_id,'','view_storage')) { + function get() { + logger('RedFile::get: ' . basename($this->name), LOGGER_DEBUG); + + $r = q("select data, flags, filename, filetype from attach where hash = '%s' and uid = %d limit 1", + dbesc($this->data['hash']), + intval($this->data['uid']) + ); + if($r) { + $unsafe_types = array('text/html','text/css','application/javascript'); + + if(in_array($r[0]['filetype'],$unsafe_types)) { + header('Content-disposition: attachment; filename="' . $r[0]['filename'] . '"'); + header('Content-type: text/plain'); + } + + if($r[0]['flags'] & ATTACH_FLAG_OS ) { + $f = 'store/' . $this->auth->owner_nick . '/' . (($this->os_path) ? $this->os_path . '/' : '') . $r[0]['data']; + return fopen($f,'rb'); + } + return $r[0]['data']; + } + + } + + function getETag() { + return $this->data['hash']; + } + + + function getContentType() { + $unsafe_types = array('text/html','text/css','application/javascript'); + if(in_array($this->data['filetype'],$unsafe_types)) { + return 'text/plain'; + } + return $this->data['filetype']; + } + + + function getSize() { + return $this->data['filesize']; + } + + + function getLastModified() { + return datetime_convert('UTC','UTC',$this->data['edited'],'U'); + } + + + function delete() { + if((! $this->auth->owner_id) || (! perm_is_allowed($this->auth->owner_id,$this->auth->observer,'write_storage'))) { throw new DAV\Exception\Forbidden('Permission denied.'); return; } -// FIXME check revisions + if($this->auth->owner_id !== $this->auth->channel_id) { + if(($this->auth->observer !== $this->data['creator']) || ($this->data['flags'] & ATTACH_FLAG_DIR)) { + throw new DAV\Exception\Forbidden('Permission denied.'); + return; + } + } + + attach_delete($this->auth->owner_id,$this->data['hash']); + } + +} + +function RedChannelList(&$auth) { + + $ret = array(); + + $r = q("select channel_id, channel_address from channel where not (channel_pageflags & %d) and not (channel_pageflags & %d) ", + intval(PAGE_REMOVED), + intval(PAGE_HIDDEN) + ); + + if($r) { + foreach($r as $rr) { + if(perm_is_allowed($rr['channel_id'],$auth->observer,'view_storage')) { + logger('RedChannelList: ' . '/cloud/' . $rr['channel_address'], LOGGER_DATA); + $ret[] = new RedDirectory('/cloud/' . $rr['channel_address'],$auth); + } + } + } + return $ret; + +} + + +function RedCollectionData($file,&$auth) { + + $ret = array(); + + $x = strpos($file,'/cloud'); + if($x === 0) { + $file = substr($file,6); + } + + if((! $file) || ($file === '/')) { + return RedChannelList($auth); + } + + $file = trim($file,'/'); + $path_arr = explode('/', $file); + + if(! $path_arr) + return null; - $r = q("select * from attach where folder = '%s' and filename = '%s' and uid = %d limit 1", - dbesc($this->dir_key), - dbesc($name), - dbesc($this->channel_id) + $channel_name = $path_arr[0]; + + $r = q("select channel_id from channel where channel_address = '%s' limit 1", + dbesc($channel_name) + ); + + if(! $r) + return null; + + $channel_id = $r[0]['channel_id']; + $perms = permissions_sql($channel_id); + + $auth->owner_id = $channel_id; + + $path = '/' . $channel_name; + + $folder = ''; + $errors = false; + $permission_error = false; + + for($x = 1; $x < count($path_arr); $x ++) { + + $r = q("select id, hash, filename, flags from attach where folder = '%s' and filename = '%s' and (flags & %d) $perms limit 1", + dbesc($folder), + dbesc($path_arr[$x]), + intval(ATTACH_FLAG_DIR) ); if(! $r) { - throw new DAV\Exception\NotFound('The file with name: ' . $name . ' could not be found'); - } + // path wasn't found. Try without permissions to see if it was the result of permissions. + $errors = true; + $r = q("select id, hash, filename, flags from attach where folder = '%s' and filename = '%s' and (flags & %d) limit 1", + dbesc($folder), + basename($path_arr[$x]), + intval(ATTACH_FLAG_DIR) + ); + if($r) { + $permission_error = true; + } + break; + } - + if($r && ( $r[0]['flags'] & ATTACH_FLAG_DIR)) { + $folder = $r[0]['hash']; + $path = $path . '/' . $r[0]['filename']; + } } + if($errors) { + if($permission_error) { + throw new DAV\Exception\Forbidden('Permission denied.'); + return; + } + else { + throw new DAV\Exception\NotFound('A component of the request file path could not be found'); + return; + } + } - function createFile($name,$data = null) { + // This should no longer be needed since we just returned errors for paths not found + if($path !== '/' . $file) { + logger("RedCollectionData: Path mismatch: $path !== /$file"); + return NULL; + } + + $ret = array(); + + $r = q("select id, uid, hash, filename, filetype, filesize, revision, folder, flags, created, edited from attach where folder = '%s' and uid = %d $perms group by filename", + dbesc($folder), + intval($channel_id) + ); + foreach($r as $rr) { + logger('RedCollectionData: filename: ' . $rr['filename'], LOGGER_DATA); + + if($rr['flags'] & ATTACH_FLAG_DIR) + $ret[] = new RedDirectory('/cloud' . $path . '/' . $rr['filename'],$auth); + else + $ret[] = new RedFile('/cloud' . $path . '/' . $rr['filename'],$rr,$auth); } - function createDirectory($name) { + return $ret; +} + +function RedFileData($file, &$auth,$test = false) { + + logger('RedFileData:' . $file . (($test) ? ' (test mode) ' : ''), LOGGER_DEBUG); + $x = strpos($file,'/cloud'); + if($x === 0) { + $file = substr($file,6); } + if((! $file) || ($file === '/')) { + return new RedDirectory('/',$auth); - function childExists($name) { - $r = q("select distinct filename from attach where folder = '%s' and filename = '%s' and uid = %d group by filename", - dbesc($this->dir_key), - dbesc($name), - intval($this->channel_id) + } + + $file = trim($file,'/'); + + $path_arr = explode('/', $file); + + if(! $path_arr) + return null; + + + $channel_name = $path_arr[0]; + + + $r = q("select channel_id from channel where channel_address = '%s' limit 1", + dbesc($channel_name) + ); + + if(! $r) + return null; + + $channel_id = $r[0]['channel_id']; + + $path = '/' . $channel_name; + + $auth->owner_id = $channel_id; + + $permission_error = false; + + $folder = ''; + + require_once('include/security.php'); + $perms = permissions_sql($channel_id); + + $errors = false; + + for($x = 1; $x < count($path_arr); $x ++) { + $r = q("select id, hash, filename, flags from attach where folder = '%s' and filename = '%s' and uid = %d and (flags & %d) $perms", + dbesc($folder), + dbesc($path_arr[$x]), + intval($channel_id), + intval(ATTACH_FLAG_DIR) ); - if($r) + + if($r && ( $r[0]['flags'] & ATTACH_FLAG_DIR)) { + $folder = $r[0]['hash']; + $path = $path . '/' . $r[0]['filename']; + } + if(! $r) { + $r = q("select id, uid, hash, filename, filetype, filesize, revision, folder, flags, created, edited from attach + where folder = '%s' and filename = '%s' and uid = %d $perms group by filename limit 1", + dbesc($folder), + basename($file), + intval($channel_id) + + ); + } + if(! $r) { + + $errors = true; + $r = q("select id, uid, hash, filename, filetype, filesize, revision, folder, flags, created, edited from attach + where folder = '%s' and filename = '%s' and uid = %d group by filename limit 1", + dbesc($folder), + basename($file), + intval($channel_id) + ); + if($r) + $permission_error = true; + + } + + } + + if($path === '/' . $file) { + if($test) return true; - return false; + // final component was a directory. + return new RedDirectory('/cloud/' . $file,$auth); + } + if($errors) { + logger('RedFileData: not found'); + if($test) + return false; + if($permission_error) { + logger('RedFileData: permission error'); + throw new DAV\Exception\Forbidden('Permission denied.'); + } + return; } + if($r) { + if($test) + return true; + + if($r[0]['flags'] & ATTACH_FLAG_DIR) + return new RedDirectory('/cloud' . $path . '/' . $r[0]['filename'],$auth); + else + return new RedFile('/cloud' . $path . '/' . $r[0]['filename'],$r[0],$auth); + } + return false; } -abstract class RedFile extends DAV\Node implements DAV\IFile { +class RedBasicAuth extends Sabre\DAV\Auth\Backend\AbstractBasic { - private $data; + public $channel_name = ''; + public $channel_id = 0; + public $channel_hash = ''; + public $observer = ''; + public $browser; + public $owner_id; + public $owner_nick = ''; + public $timezone; + protected function validateUserPass($username, $password) { - function __construct($data) { - $this->data = $data; - } + if(trim($password) === '+++') { + logger('reddav: validateUserPass: guest ' . $username); + return true; + } + require_once('include/auth.php'); + $record = account_verify_password($username,$password); + if($record && $record['account_default_channel']) { + $r = q("select * from channel where channel_account_id = %d and channel_id = %d limit 1", + intval($record['account_id']), + intval($record['account_default_channel']) + ); + if($r) { + $this->currentUser = $r[0]['channel_address']; + $this->channel_name = $r[0]['channel_address']; + $this->channel_id = $r[0]['channel_id']; + $this->channel_hash = $this->observer = $r[0]['channel_hash']; + $_SESSION['uid'] = $r[0]['channel_id']; + $_SESSION['authenticated'] = true; + return true; + } + } + $r = q("select * from channel where channel_address = '%s' limit 1", + dbesc($username) + ); + if($r) { + $x = q("select * from account where account_id = %d limit 1", + intval($r[0]['channel_account_id']) + ); + if($x) { + foreach($x as $record) { + if(($record['account_flags'] == ACCOUNT_OK) || ($record['account_flags'] == ACCOUNT_UNVERIFIED) + && (hash('whirlpool',$record['account_salt'] . $password) === $record['account_password'])) { + logger('(DAV) RedBasicAuth: password verified for ' . $username); + $this->currentUser = $r[0]['channel_address']; + $this->channel_name = $r[0]['channel_address']; + $this->channel_id = $r[0]['channel_id']; + $this->channel_hash = $this->observer = $r[0]['channel_hash']; + $_SESSION['uid'] = $r[0]['channel_id']; + $_SESSION['authenticated'] = true; + return true; + } + } + } + } + logger('(DAV) RedBasicAuth: password failed for ' . $username); + return false; + } + function setCurrentUser($name) { + $this->currentUser = $name; + } - function put($data) { + function setBrowserPlugin($browser) { + $this->browser = $browser; + } + + function log() { + logger('dav: auth: channel_name ' . $this->channel_name, LOGGER_DATA); + logger('dav: auth: channel_id ' . $this->channel_id, LOGGER_DATA); + logger('dav: auth: channel_hash ' . $this->channel_hash, LOGGER_DATA); + logger('dav: auth: observer ' . $this->observer, LOGGER_DATA); + logger('dav: auth: owner_id ' . $this->owner_id, LOGGER_DATA); + logger('dav: auth: owner_nick ' . $this->owner_nick, LOGGER_DATA); } - function get() { +} - } +class RedBrowser extends DAV\Browser\Plugin { - function getETag() { + private $auth; + function __construct(&$auth) { + $this->auth = $auth; + $this->enableAssets = false; } + // The DAV browser is instantiated after the auth module and directory classes but before we know the current + // directory and who the owner and observer are. So we add a pointer to the browser into the auth module and vice + // versa. Then when we've figured out what directory is actually being accessed, we call the following function + // to decide whether or not to show web elements which include writeable objects. - function getContentType() { - return $this->data['filetype']; - } + function set_writeable() { + + if(! $this->auth->owner_id) + $this->enablePost = false; + + if(! perm_is_allowed($this->auth->owner_id, get_observer_hash(), 'write_storage')) + $this->enablePost = false; + else + $this->enablePost = true; - function getSize() { - return $this->data['filesize']; } -} + public function generateDirectoryIndex($path) { + + if($this->auth->timezone) + date_default_timezone_set($this->auth->timezone); + + $version = ''; + + $html = " +<body> + <h1>Index for " . $this->escapeHTML($path) . "/</h1> + <table> + <tr><th width=\"24\"></th><th>Name</th><th>Type</th><th>Size</th><th>Last modified</th></tr> + <tr><td colspan=\"5\"><hr /></td></tr>"; + $files = $this->server->getPropertiesForPath($path,array( + '{DAV:}displayname', + '{DAV:}resourcetype', + '{DAV:}getcontenttype', + '{DAV:}getcontentlength', + '{DAV:}getlastmodified', + ),1); + $parent = $this->server->tree->getNodeForPath($path); + if ($path) { + list($parentUri) = DAV\URLUtil::splitPath($path); + $fullPath = DAV\URLUtil::encodePath($this->server->getBaseUri() . $parentUri); + + $icon = $this->enableAssets?'<a href="' . $fullPath . '"><img src="' . $this->getAssetUrl('icons/parent' . $this->iconExtension) . '" width="24" alt="Parent" /></a>':''; + $html.= "<tr> + <td>$icon</td> + <td><a href=\"{$fullPath}\">..</a></td> + <td>[parent]</td> + <td></td> + <td></td> + </tr>"; + + } + + foreach($files as $file) { + + // This is the current directory, we can skip it + if (rtrim($file['href'],'/')==$path) continue; + + list(, $name) = DAV\URLUtil::splitPath($file['href']); + + $type = null; + + + if (isset($file[200]['{DAV:}resourcetype'])) { + $type = $file[200]['{DAV:}resourcetype']->getValue(); + + // resourcetype can have multiple values + if (!is_array($type)) $type = array($type); + + foreach($type as $k=>$v) { + + // Some name mapping is preferred + switch($v) { + case '{DAV:}collection' : + $type[$k] = 'Collection'; + break; + case '{DAV:}principal' : + $type[$k] = 'Principal'; + break; + case '{urn:ietf:params:xml:ns:carddav}addressbook' : + $type[$k] = 'Addressbook'; + break; + case '{urn:ietf:params:xml:ns:caldav}calendar' : + $type[$k] = 'Calendar'; + break; + case '{urn:ietf:params:xml:ns:caldav}schedule-inbox' : + $type[$k] = 'Schedule Inbox'; + break; + case '{urn:ietf:params:xml:ns:caldav}schedule-outbox' : + $type[$k] = 'Schedule Outbox'; + break; + case '{http://calendarserver.org/ns/}calendar-proxy-read' : + $type[$k] = 'Proxy-Read'; + break; + case '{http://calendarserver.org/ns/}calendar-proxy-write' : + $type[$k] = 'Proxy-Write'; + break; + } + + } + $type = implode(', ', $type); + } + + // If no resourcetype was found, we attempt to use + // the contenttype property + if (!$type && isset($file[200]['{DAV:}getcontenttype'])) { + $type = $file[200]['{DAV:}getcontenttype']; + } + if (!$type) $type = 'Unknown'; + + $size = isset($file[200]['{DAV:}getcontentlength'])?(int)$file[200]['{DAV:}getcontentlength']:''; + $lastmodified = ((isset($file[200]['{DAV:}getlastmodified']))? $file[200]['{DAV:}getlastmodified']->getTime()->format('Y-m-d H:i:s') :''); + + $fullPath = DAV\URLUtil::encodePath('/' . trim($this->server->getBaseUri() . ($path?$path . '/':'') . $name,'/')); + + $displayName = isset($file[200]['{DAV:}displayname'])?$file[200]['{DAV:}displayname']:$name; + + $displayName = $this->escapeHTML($displayName); + $type = $this->escapeHTML($type); + + $icon = ''; + + if ($this->enableAssets) { + $node = $this->server->tree->getNodeForPath(($path?$path.'/':'') . $name); + foreach(array_reverse($this->iconMap) as $class=>$iconName) { + + if ($node instanceof $class) { + $icon = '<a href="' . $fullPath . '"><img src="' . $this->getAssetUrl($iconName . $this->iconExtension) . '" alt="" width="24" /></a>'; + break; + } + + + } + + } + + $html.= "<tr> + <td>$icon</td> + <td><a href=\"{$fullPath}\">{$displayName}</a></td> + <td>{$type}</td> + <td>{$size}</td> + <td>" . (($lastmodified) ? datetime_convert('UTC', date_default_timezone_get(),$lastmodified) : '') . "</td> + </tr>"; + + } + + $html.= "<tr><td colspan=\"5\"><hr /></td></tr>"; + + $output = ''; + + if ($this->enablePost) { + $this->server->broadcastEvent('onHTMLActionsPanel',array($parent, &$output)); + } + + $html.=$output; + + $html.= "</table>"; + + get_app()->page['content'] = $html; + construct_page(get_app()); + +// return $html; + + } + + + public function htmlActionsPanel(DAV\INode $node, &$output) { + + if (!$node instanceof DAV\ICollection) + return; + + // We also know fairly certain that if an object is a non-extended + // SimpleCollection, we won't need to show the panel either. + + if (get_class($node)==='Sabre\\DAV\\SimpleCollection') + return; + + $output.= '<tr><td colspan="2"><form method="post" action=""> + <h3>Create new folder</h3> + <input type="hidden" name="sabreAction" value="mkcol" /> + Name: <input type="text" name="name" /> + <input type="submit" value="create" /> + </form> + <form method="post" action="" enctype="multipart/form-data"> + <h3>Upload file</h3> + <input type="hidden" name="sabreAction" value="put" /> + Name (optional): <input type="text" name="name" /><br /> + File: <input type="file" name="file" /><br /> + <input type="submit" value="upload" /> + </form> + </td></tr>'; + + + if($this->auth->owner_id && $this->auth->owner_id == $this->auth->channel_id) { + $channel = get_app()->get_channel(); + if($channel) { + $output .= '<tr><td> </td></tr><tr><td colspan="2"><a href="filestorage/' . $channel['channel_address'] . '" >' . t('Edit File properties') . '</a></td></tr>'; + } + } + + } + + /** + * This method takes a path/name of an asset and turns it into url + * suiteable for http access. + * + * @param string $assetName + * @return string + */ + protected function getAssetUrl($assetName) { + return z_root() .'/cloud/?sabreAction=asset&assetName=' . urlencode($assetName); + } + +} diff --git a/include/security.php b/include/security.php index 296fa450f..5e86cf790 100644 --- a/include/security.php +++ b/include/security.php @@ -31,90 +31,6 @@ function authenticate_success($user_record, $login_initial = false, $interactive } } - else { - $_SESSION['uid'] = $user_record['uid']; - $_SESSION['theme'] = $user_record['theme']; - $_SESSION['mobile_theme'] = get_pconfig($user_record['uid'], 'system', 'mobile_theme'); - $_SESSION['authenticated'] = 1; - $_SESSION['page_flags'] = $user_record['page-flags']; - $_SESSION['my_url'] = $a->get_baseurl() . '/channel/' . $user_record['nickname']; - $_SESSION['my_address'] = $user_record['nickname'] . '@' . substr($a->get_baseurl(),strpos($a->get_baseurl(),'://')+3); - - $a->user = $user_record; - - if($interactive) { - if($a->user['login_date'] === '0000-00-00 00:00:00') { - $_SESSION['return_url'] = 'profile_photo/new'; - $a->module = 'profile_photo'; - info( t("Welcome ") . $a->user['username'] . EOL); - info( t('Please upload a profile photo.') . EOL); - } - else - info( t("Welcome back ") . $a->user['username'] . EOL); - } - - $member_since = strtotime($a->user['register_date']); - if(time() < ($member_since + ( 60 * 60 * 24 * 14))) - $_SESSION['new_member'] = true; - else - $_SESSION['new_member'] = false; - if(strlen($a->user['timezone'])) { - date_default_timezone_set($a->user['timezone']); - $a->timezone = $a->user['timezone']; - } - - $master_record = $a->user; - - if((x($_SESSION,'submanage')) && intval($_SESSION['submanage'])) { - $r = q("select * from user where uid = %d limit 1", - intval($_SESSION['submanage']) - ); - if(count($r)) - $master_record = $r[0]; - } - - $r = q("SELECT `uid`,`username`,`nickname` FROM `user` WHERE `password` = '%s' AND `email` = '%s'", - dbesc($master_record['password']), - dbesc($master_record['email']) - ); - if($r && count($r)) - $a->identities = $r; - else - $a->identities = array(); - - $r = q("select `user`.`uid`, `user`.`username`, `user`.`nickname` - from manage left join user on manage.mid = user.uid - where `manage`.`uid` = %d", - intval($master_record['uid']) - ); - if($r && count($r)) - $a->identities = array_merge($a->identities,$r); - - if($login_initial) - logger('auth_identities: ' . print_r($a->identities,true), LOGGER_DEBUG); - - $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `self` = 1 LIMIT 1", - intval($_SESSION['uid'])); - if(count($r)) { - $a->contact = $r[0]; - $a->cid = $r[0]['id']; - $_SESSION['cid'] = $a->cid; - } - - header('X-Account-Management-Status: active; name="' . $a->user['username'] . '"; id="' . $a->user['nickname'] .'"'); - - if($login_initial) { - $l = get_browser_language(); - - q("UPDATE `user` SET `login_date` = '%s', `language` = '%s' WHERE `uid` = %d LIMIT 1", - dbesc(datetime_convert()), - dbesc($l), - intval($_SESSION['uid']) - ); - - - } - } if($login_initial) call_hooks('logged_in', $user_record); @@ -144,6 +60,7 @@ function change_channel($change_channel) { intval(get_account_id()), intval(PAGE_REMOVED) ); + if($r) { $hash = $r[0]['channel_hash']; $_SESSION['uid'] = intval($r[0]['channel_id']); @@ -158,11 +75,14 @@ function change_channel($change_channel) { ); if($x) { $_SESSION['my_url'] = $x[0]['xchan_url']; - $_SESSION['my_address'] = $x[0]['xchan_addr']; + $_SESSION['my_address'] = $r[0]['channel_address'] . '@' . substr(get_app()->get_baseurl(),strpos(get_app()->get_baseurl(),'://')+3); get_app()->set_observer($x[0]); get_app()->set_perms(get_all_perms(local_user(),$hash)); } + if(! is_dir('store/' . $r[0]['channel_address'])) + @mkdir('store/' . $r[0]['channel_address'], STORAGE_DEFAULT_PERMISSIONS,true); + } return $ret; @@ -336,7 +256,7 @@ function get_form_security_token($typename = '') { $timestamp = time(); $sec_hash = hash('whirlpool', $a->user['guid'] . $a->user['prvkey'] . session_id() . $timestamp . $typename); - + return $timestamp . '.' . $sec_hash; } @@ -386,7 +306,7 @@ function check_form_security_token_ForbiddenOnErr($typename = '', $formname = 'f if(! function_exists('init_groups_visitor')) { function init_groups_visitor($contact_id) { $groups = array(); - $r = q("SELECT hash FROM `group` left join group_member on group.id = group_member.gid WHERE xchan = '%s' ", + $r = q("SELECT hash FROM `groups` left join group_member on groups.id = group_member.gid WHERE xchan = '%s' ", dbesc($contact_id) ); if(count($r)) { diff --git a/include/session.php b/include/session.php index 6072bdb33..be1ec5ee7 100644 --- a/include/session.php +++ b/include/session.php @@ -15,6 +15,15 @@ function new_cookie($time) { session_regenerate_id(false); q("UPDATE session SET sid = '%s' WHERE sid = '%s'", dbesc(session_id()), dbesc($old_sid)); + + if (x($_COOKIE, 'jsAvailable')) { + if ($time) { + $expires = time() + $time; + } else { + $expires = 0; + } + setcookie('jsAvailable', $_COOKIE['jsAvailable'], $expires); + } } diff --git a/include/spam.php b/include/spam.php new file mode 100644 index 000000000..8b158b7ae --- /dev/null +++ b/include/spam.php @@ -0,0 +1,35 @@ +<?php /** @file */ + + +function string_splitter($s) { + + if(! $s) + return array(); + + $s = preg_replace('/\pP+/','',$s); + + $x = mb_split("\[|\]|\s",$s); + + $ret = array(); + if($x) { + foreach($x as $y) { + if(mb_strlen($y) > 2) + $ret[] = substr($y,0,64); + } + } + return $ret; +} + + + +function get_words($uid,$list) { + + stringify($list,true); + + $r = q("select * from spam where term in ( " . $list . ") and uid = %d", + intval($uid) + ); + + return $r; +} + diff --git a/include/taxonomy.php b/include/taxonomy.php index 5159dad02..4f2b5e8fd 100644 --- a/include/taxonomy.php +++ b/include/taxonomy.php @@ -87,9 +87,9 @@ function format_term_for_display($term) { return $s; if($term['url']) - $s .= '<a href="' . $term['url'] . '">' . htmlspecialchars($term['term']) . '</a>'; + $s .= '<a href="' . $term['url'] . '">' . htmlspecialchars($term['term'], ENT_COMPAT,'UTF-8') . '</a>'; else - $s .= htmlspecialchars($term['term']); + $s .= htmlspecialchars($term['term'], ENT_COMPAT,'UTF-8'); return $s; } @@ -217,16 +217,19 @@ function tagblock($link,$uid,$count = 0,$authors = '',$flags = 0,$restrict = 0,$ } function dir_tagblock($link,$r) { - $o = ''; - $tab = 0; + $o = ''; + $tab = 0; - if($r) { - $o = '<div class="dirtagblock widget"><h3>' . t('Keywords') . '</h3><div class="tags" align="center">'; - foreach($r as $rr) { - $o .= '<a href="'.$link .'/' . '?f=&keywords=' . urlencode($rr['term']).'" class="tag'.$rr['normalise'].'" rel="nofollow" >'.$rr['term'].'</a> ' . "\r\n"; + if(! $r) + $r = get_app()->data['directory_keywords']; + + if($r) { + $o = '<div class="dirtagblock widget"><h3>' . t('Keywords') . '</h3><div class="tags" align="center">'; + foreach($r as $rr) { + $o .= '<a href="'.$link .'/' . '?f=&keywords=' . urlencode($rr['term']).'" class="tag'.$rr['normalise'].'" rel="nofollow" >'.$rr['term'].'</a> ' . "\r\n"; + } + $o .= '</div></div>'; } - $o .= '</div></div>'; - } return $o; } @@ -258,13 +261,81 @@ function obj_verbs() { } -function obj_verb_selector() { +function obj_verb_selector($current = '') { $verbs = obj_verbs(); $o .= '<select class="obj-verb-selector" name="verb" >'; foreach($verbs as $k => $v) { - $o .= '<option value="' . urlencode($k) . '">' . $v[0] . '</option>'; + $selected = (($k == $current) ? ' selected="selected" ' : ''); + $o .= '<option value="' . urlencode($k) . '"' . $selected . '>' . $v[0] . '</option>'; } $o .= '</select>'; return $o; +} + +function get_things($profile_hash,$uid) { + + $sql_extra = (($profile_hash) ? " and obj_page = '" . $profile_hash . "' " : ''); + + $r = q("select * from obj left join term on obj_obj = term_hash where term_hash != '' and uid = %d and obj_type = %d $sql_extra order by obj_verb, term", + intval($uid), + intval(TERM_OBJ_THING) + ); + + $things = $sorted_things = null; + + $profile_hashes = array(); + + if($r) { + + // if no profile_hash was specified (display on profile page mode), match each of the things to a profile name + // (list all my things mode). This is harder than it sounds. + + foreach($r as $rr) { + $rr['profile_name'] = ''; + if(! in_array($rr['term_hash'],$profile_hashes)) + $profile_hashes[] = $rr['term_hash']; + } + stringify_array_elms($profile_hashes); + if(! $profile_hash) { + $exp = explode(',',$profile_hashes); + $p = q("select profile_guid as hash, profile_name as name from profile where profile_guid in ( $exp ) "); + if($p) { + foreach($r as $rr) { + foreach($p as $pp) { + if($rr['obj_page'] == $pp['hash']) { + $rr['profile_name'] == $pp['name']; + } + } + } + } + } + + $things = array(); + + // Use the system obj_verbs array as a sort key, since we don't really + // want an alphabetic sort. To change the order, use a plugin to + // alter the obj_verbs() array or alter it in code. Unknown verbs come + // after the known ones - in no particular order. + + $v = obj_verbs(); + foreach($v as $k => $foo) + $things[$k] = null; + foreach($r as $rr) { + if(! $things[$rr['obj_verb']]) + $things[$rr['obj_verb']] = array(); + $things[$rr['obj_verb']][] = array('term' => $rr['term'],'url' => $rr['url'],'img' => $rr['imgurl'], 'profile' => $rr['profile_name']); + } + $sorted_things = array(); + if($things) { + foreach($things as $k => $v) { + if(is_array($things[$k])) { + $sorted_things[$k] = $v; + } + } + } + } + + return $sorted_things; + }
\ No newline at end of file diff --git a/include/text.php b/include/text.php index aa23f96b0..dfd35c769 100755 --- a/include/text.php +++ b/include/text.php @@ -442,7 +442,7 @@ function item_message_id() { $mid = $hash . '@' . get_app()->get_hostname(); - $r = q("SELECT `id` FROM `item` WHERE `mid` = '%s' LIMIT 1", + $r = q("SELECT id FROM item WHERE mid = '%s' LIMIT 1", dbesc($mid)); if(count($r)) $dups = true; @@ -459,7 +459,7 @@ function photo_new_resource() { do { $found = false; $resource = hash('md5',uniqid(mt_rand(),true)); - $r = q("SELECT `id` FROM `photo` WHERE `resource_id` = '%s' LIMIT 1", + $r = q("SELECT id FROM photo WHERE resource_id = '%s' LIMIT 1", dbesc($resource) ); if(count($r)) @@ -565,6 +565,10 @@ function get_tags($s) { $s = preg_replace('/\[code\](.*?)\[\/code\]/sm','',$s); + // ignore anything in [style= ] + + $s = preg_replace('/\[style=(.*?)\]/sm','',$s); + // Match full names against @tags including the space between first and last // We will look these up afterward to see if they are full names or not recognisable. @@ -593,7 +597,7 @@ function get_tags($s) { if(substr($mtch,-1,1) === '.') $mtch = substr($mtch,0,-1); // ignore strictly numeric tags like #1 - if((strpos($mtch,'#') === 0) && ctype_digit(substr($mtch,1))) + if((strpos($mtch,'#') === 0) && ( ctype_digit(substr($mtch,1)) || substr($mtch,1,1) === '^')) continue; // try not to catch url fragments if(strpos($s,$mtch) && preg_match('/[a-zA-z0-9\/]/',substr($s,strpos($s,$mtch)-1,1))) @@ -601,10 +605,27 @@ function get_tags($s) { $ret[] = $mtch; } } + + // bookmarks + + if(preg_match_all('/#\^\[(url|zrl)(.*?)\](.*?)\[\/(url|zrl)\]/',$s,$match,PREG_SET_ORDER)) { + foreach($match as $mtch) { + $ret[] = $mtch[0]; + } + } + + + // logger('get_tags: ' . print_r($ret,true)); + return $ret; } +function strip_zids($s) { + return preg_replace('/[\?&]zid=(.*?)(&|$)/ism','$2',$s); +} + + // quick and dirty quoted_printable encoding @@ -781,6 +802,34 @@ function linkify($s) { return($s); } +/** + * @function sslify($s) + * Replace media element using http url with https to a local redirector if using https locally + * @param string $s + * + * Looks for HTML tags containing src elements that are http when we're viewing an https page + * Typically this throws an insecure content violation in the browser. So we redirect them + * to a local redirector which uses https and which redirects to the selected content + * + * @returns string + */ + + +function sslify($s) { + if(strpos(z_root(),'https:') === false) + return $s; + $matches = null; + $cnt = preg_match_all("/\<(.*?)src=\"(http\:.*?)\"(.*?)\>/",$s,$matches,PREG_SET_ORDER); + if($cnt) { + foreach($matches as $match) { + $s = str_replace($match[2],z_root() . '/sslify?f=&url=' . urlencode($match[2]),$s); + } + } + return $s; +} + + + function get_poke_verbs() { // index is present tense verb @@ -843,8 +892,8 @@ function get_mood_verbs() { * Returns string * * It is expected that this function will be called using HTML text. - * We will escape text between HTML pre and code blocks from being - * processed. + * We will escape text between HTML pre and code blocks, and HTML attributes + * (such as urls) from being processed. * * At a higher level, the bbcode [nosmile] tag can be used to prevent this * function from being executed by the prepare_text() routine when preparing @@ -861,8 +910,8 @@ function smilies($s, $sample = false) { || (local_user() && intval(get_pconfig(local_user(),'system','no_smilies')))) return $s; - $s = preg_replace_callback('/<pre>(.*?)<\/pre>/ism','smile_encode',$s); - $s = preg_replace_callback('/<code>(.*?)<\/code>/ism','smile_encode',$s); + $s = preg_replace_callback('{<(pre|code)>.*?</\1>}ism','smile_shield',$s); + $s = preg_replace_callback('/<[a-z]+ .*?>/ism','smile_shield',$s); $texts = array( '<3', @@ -953,19 +1002,18 @@ function smilies($s, $sample = false) { $s = str_replace($params['texts'],$params['icons'],$params['string']); } - $s = preg_replace_callback('/<pre>(.*?)<\/pre>/ism','smile_decode',$s); - $s = preg_replace_callback('/<code>(.*?)<\/code>/ism','smile_decode',$s); + $s = preg_replace_callback('/<!--base64:(.*?)-->/ism', 'smile_unshield', $s); return $s; } -function smile_encode($m) { - return(str_replace($m[1],base64url_encode($m[1]),$m[0])); +function smile_shield($m) { + return '<!--base64:' . base64url_encode($m[0]) . '-->'; } -function smile_decode($m) { - return(str_replace($m[1],base64url_decode($m[1]),$m[0])); +function smile_unshield($m) { + return base64url_decode($m[1]); } // expand <3333 to the correct number of hearts @@ -1065,7 +1113,7 @@ function theme_attachments(&$item) { break; } - $title = htmlentities($r['title'], ENT_COMPAT,'UTF-8'); + $title = htmlspecialchars($r['title'], ENT_COMPAT,'UTF-8'); if(! $title) $title = t('unknown.???'); $title .= ' ' . $r['length'] . ' ' . t('bytes'); @@ -1095,7 +1143,7 @@ function format_categories(&$item,$writeable) { if($terms) { $categories = array(); foreach($terms as $t) { - $term = htmlspecialchars($t['term'],ENT_COMPAT,'UTF-8') ; + $term = htmlspecialchars($t['term'],ENT_COMPAT,'UTF-8',false) ; if(! trim($term)) continue; $removelink = (($writeable) ? z_root() . '/filerm/' . $item['id'] . '?f=&cat=' . urlencode($t['term']) : ''); @@ -1117,7 +1165,7 @@ function format_filer(&$item) { if($terms) { $categories = array(); foreach($terms as $t) { - $term = htmlspecialchars($t['term'],ENT_COMPAT,'UTF-8') ; + $term = htmlspecialchars($t['term'],ENT_COMPAT,'UTF-8',false) ; if(! trim($term)) continue; $removelink = z_root() . '/filerm/' . $item['id'] . '?f=&term=' . urlencode($t['term']); @@ -1166,6 +1214,10 @@ function prepare_body(&$item,$attach = false) { if(local_user() == $item['uid']) $s .= format_filer($item); + + $s = sslify($s); + + // Look for spoiler $spoilersearch = '<blockquote class="spoiler">'; @@ -1272,24 +1324,15 @@ function prepare_text($text,$content_type = 'text/bbcode') { function zidify_callback($match) { - if (feature_enabled(local_user(),'sendzid')) { - $replace = '<a' . $match[1] . ' href="' . zid($match[2]) . '"'; - } - else { - $replace = '<a' . $match[1] . 'class="zrl"' . $match[2] . ' href="' . zid($match[3]) . '"'; - } - + $is_zid = ((feature_enabled(local_user(),'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]); return $x; } function zidify_img_callback($match) { - if (feature_enabled(local_user(),'sendzid')) { - $replace = '<img' . $match[1] . ' src="' . zid($match[2]) . '"'; - } - else { - $replace = '<img' . $match[1] . ' src="' . zid($match[2]) . '"'; - } + $is_zid = ((feature_enabled(local_user(),'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]); return $x; @@ -1297,25 +1340,13 @@ function zidify_img_callback($match) { function zidify_links($s) { - if(feature_enabled(local_user(),'sendzid')) { - $s = preg_replace_callback('/\<a(.*?)href\=\"(.*?)\"/ism','zidify_callback',$s); - $s = preg_replace_callback('/\<img(.*?)src\=\"(.*?)\"/ism','zidify_img_callback',$s); - } - else { - $s = preg_replace_callback('/\<a(.*?)class\=\"zrl\"(.*?)href\=\"(.*?)\"/ism','zidify_callback',$s); - $s = preg_replace_callback('/\<img class\=\"zrl\"(.*?)src\=\"(.*?)\"/ism','zidify_img_callback',$s); -// FIXME - remove the following line and redo the regex for the prev line once all Red images are converted to zmg - $s = preg_replace_callback('/\<img(.*?)src\=\"(.*?)\"/ism','zidify_img_callback',$s); - } - + $s = preg_replace_callback('/\<a(.*?)href\=\"(.*?)\"/ism','zidify_callback',$s); + $s = preg_replace_callback('/\<img(.*?)src\=\"(.*?)\"/ism','zidify_img_callback',$s); return $s; } - - - /** * return atom link elements for all of our hubs */ @@ -1357,9 +1388,9 @@ function feed_salmonlinks($nick) { } -function get_plink($item,$mode) { +function get_plink($item,$conversation_mode = true) { $a = get_app(); - if($mode == 'display') + if($conversation_mode) $key = 'plink'; else $key = 'llink'; @@ -1485,20 +1516,6 @@ function return_bytes ($size_str) { } } -function generate_user_guid() { - $found = true; - do { - $guid = random_string(16); - $x = q("SELECT `uid` FROM `user` WHERE `guid` = '%s' LIMIT 1", - dbesc($guid) - ); - if(! count($x)) - $found = false; - } while ($found == true ); - return $guid; -} - - function base64url_encode($s, $strip_padding = true) { @@ -1516,23 +1533,6 @@ function base64url_decode($s) { logger('base64url_decode: illegal input: ' . print_r(debug_backtrace(), true)); return $s; } - -/* - * // Placeholder for new rev of salmon which strips base64 padding. - * // PHP base64_decode handles the un-padded input without requiring this step - * // Uncomment if you find you need it. - * - * $l = strlen($s); - * if(! strpos($s,'=')) { - * $m = $l % 4; - * if($m == 2) - * $s .= '=='; - * if($m == 3) - * $s .= '='; - * } - * - */ - return base64_decode(strtr($s,'-_','+/')); } @@ -1637,17 +1637,12 @@ function item_post_type($item) { } -function normalise_openid($s) { - return trim(str_replace(array('http://','https://'),array('',''),$s),'/'); -} - - function undo_post_tagging($s) { $matches = null; - $cnt = preg_match_all('/([@#])\[zrl=(.*?)\](.*?)\[\/zrl\]/ism',$s,$matches,PREG_SET_ORDER); + $cnt = preg_match_all('/([@#])(\!*)\[zrl=(.*?)\](.*?)\[\/zrl\]/ism',$s,$matches,PREG_SET_ORDER); if($cnt) { foreach($matches as $mtch) { - $s = str_replace($mtch[0], $mtch[1] . $mtch[3],$s); + $s = str_replace($mtch[0], $mtch[1] . $mtch[2] . str_replace(' ','_',$mtch[4]),$s); } } return $s; @@ -1889,18 +1884,17 @@ function json_decode_plus($s) { function design_tools() { -$channel = get_app()->get_channel(); -$who = $channel['channel_address']; - -return replace_macros(get_markup_template('design_tools.tpl'), array( - '$title' => t('Design'), - '$who' => $who, - '$blocks' => t('Blocks'), - '$menus' => t('Menus'), - '$layout' => t('Layouts'), - '$pages' => t('Pages') - )); - + $channel = get_app()->get_channel(); + $who = $channel['channel_address']; + + return replace_macros(get_markup_template('design_tools.tpl'), array( + '$title' => t('Design'), + '$who' => $who, + '$blocks' => t('Blocks'), + '$menus' => t('Menus'), + '$layout' => t('Layouts'), + '$pages' => t('Pages') + )); } /* case insensitive in_array() */ @@ -1909,3 +1903,7 @@ function in_arrayi($needle, $haystack) { return in_array(strtolower($needle), array_map('strtolower', $haystack)); } +function normalise_openid($s) { + return trim(str_replace(array('http://','https://'),array('',''),$s),'/'); +} + diff --git a/include/widgets.php b/include/widgets.php index cea5a6ce2..3c2333323 100644 --- a/include/widgets.php +++ b/include/widgets.php @@ -1,17 +1,7 @@ <?php /** @file */ -function list_widgets() { - $widgets = array( - 'profile' => t('Displays a full channel profile'), - 'tagcloud' => t('Tag cloud of webpage categories'), - 'collections' => t('List and filter by collection'), - 'suggestions' => t('Show a couple of channel suggestion'), - 'follow' => t('Provide a channel follow form') - ); - $arr = array('widgets' => $widgets); - call_hooks('list_widgets',$arr); - return $arr['widgets']; -} +require_once('include/dir_fns.php'); +require_once('include/contact_widgets.php'); function widget_profile($args) { @@ -47,16 +37,48 @@ function widget_tagcloud($args) { function widget_collections($args) { require_once('include/group.php'); - $page = argv(0); - $gid = $_REQUEST['gid']; - return group_side($page,$page,true,$_REQUEST['gid'],'',0); + $mode = ((array_key_exists('mode',$args)) ? $args['mode'] : 'conversation'); + switch($mode) { + case 'conversation': + $every = argv(0); + $each = argv(0); + $edit = true; + $current = $_REQUEST['gid']; + $abook_id = 0; + $wmode = 0; + break; + case 'groups': + $every = 'connections'; + $each = argv(0); + $edit = false; + $current = intval(argv(1)); + $abook_id = 0; + $wmode = 1; + break; + case 'abook': + $every = 'connections'; + $each = 'group'; + $edit = false; + $current = 0; + $abook_id = get_app()->poi['abook_xchan']; + $wmode = 1; + break; + default: + return ''; + break; + } + + return group_side($every, $each, $edit, $current, $abook_id, $wmode); } function widget_suggestions($arr) { + if((! local_user()) || (! feature_enabled(local_user(),'suggest'))) + return ''; + require_once('include/socgraph.php'); $r = suggestion_query(local_user(),get_observer_hash(),0,20); @@ -143,7 +165,7 @@ function widget_notes($arr) { if(! feature_enabled(local_user(),'private_notes')) return ''; - $text = htmlspecialchars(get_pconfig(local_user(),'notes','text')); + $text = get_pconfig(local_user(),'notes','text'); $o = replace_macros(get_markup_template('notes.tpl'), array( '$banner' => t('Notes'), @@ -216,7 +238,7 @@ function widget_savedsearch($arr) { 'term' => $rr['term'], 'dellink' => z_root() . '/' . $srchurl . (($hasq) ? '' : '?f=') . '&searchremove=1&search=' . urlencode($rr['term']), 'srchlink' => z_root() . '/' . $srchurl . (($hasq) ? '' : '?f=') . '&search=' . urlencode($rr['term']), - 'displayterm' => htmlspecialchars($rr['term']), + 'displayterm' => htmlspecialchars($rr['term'], ENT_COMPAT,'UTF-8'), 'encodedterm' => urlencode($rr['term']), 'delete' => t('Remove term'), 'selected' => ($search==$rr['term']), @@ -314,7 +336,7 @@ function widget_fullprofile($arr) { function widget_categories($arr) { $a = get_app(); - $cat = ((x($_REQUEST,'cat')) ? htmlspecialchars($_REQUEST['cat']) : ''); + $cat = ((x($_REQUEST,'cat')) ? htmlspecialchars($_REQUEST['cat'],ENT_COMPAT,'UTF-8') : ''); $srchurl = $a->query_string; $srchurl = rtrim(preg_replace('/cat\=[^\&].*?(\&|$)/is','',$srchurl),'&'); $srchurl = str_replace(array('?f=','&f='),array('',''),$srchurl); @@ -330,4 +352,240 @@ function widget_tagcloud_wall($arr) { if(feature_enabled($a->profile['profile_uid'],'tagadelic')) return tagblock('search',$a->profile['profile_uid'],$limit,$a->profile['channel_hash'],ITEM_WALL); return ''; -}
\ No newline at end of file +} + + +function widget_affinity($arr) { + + if(! local_user()) + return ''; + + $cmin = ((x($_REQUEST,'cmin')) ? intval($_REQUEST['cmin']) : 0); + $cmax = ((x($_REQUEST,'cmax')) ? intval($_REQUEST['cmax']) : 99); + + if(feature_enabled(local_user(),'affinity')) { + $tpl = get_markup_template('main_slider.tpl'); + $x = replace_macros($tpl,array( + '$val' => $cmin . ';' . $cmax, + '$refresh' => t('Refresh'), + '$me' => t('Me'), + '$intimate' => t('Best Friends'), + '$friends' => t('Friends'), + '$coworkers' => t('Co-workers'), + '$oldfriends' => t('Former Friends'), + '$acquaintances' => t('Acquaintances'), + '$world' => t('Everybody') + )); + $arr = array('html' => $x); + call_hooks('main_slider',$arr); + return $arr['html']; + } + return ''; +} + + +function widget_settings_menu($arr) { + + if(! local_user()) + return; + + $a = get_app(); + $channel = $a->get_channel(); + + $abook_self_id = 0; + + // Retrieve the 'self' address book entry for use in the auto-permissions link + + $abk = q("select abook_id from abook where abook_channel = %d and ( abook_flags & %d ) limit 1", + intval(local_user()), + intval(ABOOK_FLAG_SELF) + ); + if($abk) + $abook_self_id = $abk[0]['abook_id']; + + + $tabs = array( + array( + 'label' => t('Account settings'), + 'url' => $a->get_baseurl(true).'/settings/account', + 'selected' => ((argv(1) === 'account') ? 'active' : ''), + ), + + array( + 'label' => t('Channel settings'), + 'url' => $a->get_baseurl(true).'/settings/channel', + 'selected' => ((argv(1) === 'channel') ? 'active' : ''), + ), + + array( + 'label' => t('Additional features'), + 'url' => $a->get_baseurl(true).'/settings/features', + 'selected' => ((argv(1) === 'features') ? 'active' : ''), + ), + + array( + 'label' => t('Feature settings'), + 'url' => $a->get_baseurl(true).'/settings/featured', + 'selected' => ((argv(1) === 'featured') ? 'active' : ''), + ), + + array( + 'label' => t('Display settings'), + 'url' => $a->get_baseurl(true).'/settings/display', + 'selected' => ((argv(1) === 'display') ? 'active' : ''), + ), + + array( + 'label' => t('Connected apps'), + 'url' => $a->get_baseurl(true) . '/settings/oauth', + 'selected' => ((argv(1) === 'oauth') ? 'active' : ''), + ), + + array( + 'label' => t('Export channel'), + 'url' => $a->get_baseurl(true) . '/uexport/basic', + 'selected' => '' + ), + +// array( +// 'label' => t('Export account'), +// 'url' => $a->get_baseurl(true) . '/uexport/complete', +// 'selected' => '' +// ), + + array( + 'label' => t('Automatic Permissions (Advanced)'), + 'url' => $a->get_baseurl(true) . '/connedit/' . $abook_self_id, + 'selected' => '' + ), + + + ); + + if(feature_enabled(local_user(),'premium_channel')) { + $tabs[] = array( + 'label' => t('Premium Channel Settings'), + 'url' => $a->get_baseurl(true) . '/connect/' . $channel['channel_address'], + 'selected' => '' + ); + + } + + if(feature_enabled(local_user(),'channel_sources')) { + $tabs[] = array( + 'label' => t('Channel Sources'), + 'url' => $a->get_baseurl(true) . '/sources', + 'selected' => '' + ); + + } + + + + $tabtpl = get_markup_template("generic_links_widget.tpl"); + return replace_macros($tabtpl, array( + '$title' => t('Settings'), + '$class' => 'settings-widget', + '$items' => $tabs, + )); + +} + + +function widget_mailmenu($arr) { + if (! local_user()) + return; + + $a = get_app(); + return replace_macros(get_markup_template('message_side.tpl'), array( + '$tabs'=> array(), + + '$check'=>array( + 'label' => t('Check Mail'), + 'url' => $a->get_baseurl(true) . '/message', + 'sel' => (argv(1) == ''), + ), + '$new'=>array( + 'label' => t('New Message'), + 'url' => $a->get_baseurl(true) . '/mail/new', + 'sel'=> (argv(1) == 'new'), + ) + + )); + +} + +function widget_design_tools($arr) { + $a = get_app(); + + // mod menu doesn't load a profile. For any modules which load a profile, check it. + // otherwise local_user() is sufficient for permissions. + + if($a->profile['profile_uid']) + if($a->profile['profile_uid'] != local_user()) + return ''; + + if(! local_user()) + return ''; + + return design_tools(); +} + +function widget_findpeople($arr) { + return findpeople_widget(); +} + + +function widget_photo_albums($arr) { + $a = get_app(); + if(! $a->profile['profile_uid']) + return ''; + $channelx = channelx_by_n($a->profile['profile_uid']); + if((! $channelx) || (! perm_is_allowed($a->profile['profile_uid'],get_observer_hash(),'view_photos'))) + return ''; + return photos_album_widget($channelx[0],$a->get_observer()); + +} + + +function widget_vcard($arr) { + require_once ('include/Contact.php'); + return vcard_from_xchan('',get_app()->get_observer()); +} + + +/** + * The following directory widgets are only useful on the directory page + */ + +function widget_dirsafemode($arr) { + return dir_safe_mode(); +} + +function widget_dirsort($arr) { + return dir_sort_links(); +} + +function widget_dirtags($arr) { + return dir_tagblock(z_root() . '/directory',null); +} + +function widget_menu_preview($arr) { + if(! get_app()->data['menu_item']) + return; + require_once('include/menu.php'); + return menu_render(get_app()->data['menu_item']); +} + +function widget_chatroom_list($arr) { + $a = get_app(); + require_once("include/chat.php"); + $r = chatroom_list($a->profile['profile_uid']); + return replace_macros(get_markup_template('chatroomlist.tpl'),array( + '$header' => t('Chat Rooms'), + '$baseurl' => z_root(), + '$nickname' => $a->profile['channel_address'], + '$items' => $r, + )); +} + diff --git a/include/zot.php b/include/zot.php index 77d82f110..c9d426cc2 100644 --- a/include/zot.php +++ b/include/zot.php @@ -79,12 +79,12 @@ function zot_get_hublocs($hash) { * zot it to the other side * * @param array $channel => sender channel structure - * @param string $type => packet type: one of 'ping', 'pickup', 'purge', 'refresh', 'notify', 'auth_check' + * @param string $type => packet type: one of 'ping', 'pickup', 'purge', 'refresh', 'force_refresh', 'notify', 'auth_check' * @param array $recipients => envelope information, array ( 'guid' => string, 'guid_sig' => string ); empty for public posts * @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 $secret => random string, required for packets which require verification/callback - * e.g. 'pickup', 'purge', 'notify', 'auth_check' --- 'ping' and 'refresh' do not require verification + * e.g. 'pickup', 'purge', 'notify', 'auth_check'. Packet types 'ping', 'force_refresh', and 'refresh' do not require verification * * @returns string json encoded zot packet */ @@ -228,7 +228,7 @@ function zot_finger($webbie,$channel,$autofallback = true) { } /** - * @function: zot_refresh($them, $channel = null) + * @function: zot_refresh($them, $channel = null, $force = false) * * zot_refresh is typically invoked when somebody has changed permissions of a channel and they are notified * to fetch new permissions via a finger/discovery operation. This may result in a new connection @@ -251,7 +251,7 @@ function zot_finger($webbie,$channel,$autofallback = true) { * @returns boolean true if successful, else false */ -function zot_refresh($them,$channel = null) { +function zot_refresh($them,$channel = null, $force = false) { logger('zot_refresh: them: ' . print_r($them,true), LOGGER_DATA); if($channel) @@ -305,7 +305,7 @@ function zot_refresh($them,$channel = null) { return false; } - $x = import_xchan($j); + $x = import_xchan($j,(($force) ? (-1) : 1)); if(! $x['success']) return false; @@ -518,12 +518,15 @@ function zot_register_hub($arr) { /** * @function import_xchan($arr,$ud_flags = 1) - * Takes an associative array of a fecthed discovery packet and updates + * Takes an associative array of a fetched discovery packet and updates * all internal data structures which need to be updated as a result. * * @param array $arr => json_decoded discovery packet * @param int $ud_flags * Determines whether to create a directory update record if any changes occur, default 1 or true + * $ud_flags = (-1) indicates a forced refresh where we unconditionally create a directory update record + * this typically occurs once a month for each channel as part of a scheduled ping to notify the directory + * that the channel still exists * * @returns array => 'success' (boolean true or false) * 'message' (optional error string only if success is false) @@ -885,7 +888,7 @@ function import_xchan($arr,$ud_flags = 1) { } } - if($changed) { + if(($changed) || ($ud_flags == (-1))) { $guid = random_string() . '@' . get_app()->get_hostname(); update_modtime($xchan_hash,$guid,$arr['address'],$ud_flags); logger('import_xchan: changed: ' . $what,LOGGER_DEBUG); @@ -1061,7 +1064,9 @@ function zot_import($arr, $sender_url) { } stringify_array_elms($recip_arr); $recips = implode(',',$recip_arr); - $r = q("select channel_hash as hash from channel where channel_hash in ( " . $recips . " ) "); + $r = q("select channel_hash as hash from channel where channel_hash in ( " . $recips . " ) and not ( channel_pageflags & %d ) ", + intval(PAGE_REMOVED) + ); if(! $r) { logger('recips: no recipients on this site'); continue; @@ -1219,8 +1224,7 @@ function public_recips($msg) { if(! $r) $r = array(); - $x = q("select channel_hash as hash from channel left join abook on abook_channel = channel_id where abook_xchan = '%s' - and (( " . $col . " & " . PERMS_SPECIFIC . " ) and ( abook_my_perms & " . $field . " )) OR ( " . $col . " & " . PERMS_CONTACTS . " ) ", + $x = q("select channel_hash as hash from channel left join abook on abook_channel = channel_id where abook_xchan = '%s' and not ( channel_pageflags & " . PAGE_REMOVED . " ) and (( " . $col . " & " . PERMS_SPECIFIC . " ) and ( abook_my_perms & " . $field . " )) OR ( " . $col . " & " . PERMS_CONTACTS . " ) ", dbesc($msg['notify']['sender']['hash']) ); @@ -1301,8 +1305,9 @@ function allowed_public_recips($msg) { $condensed_recips[] = $rr['hash']; $results = array(); - $r = q("select channel_hash as hash from channel left join abook on abook_channel = channel_id where abook_xchan = '%s' ", - dbesc($hash) + $r = q("select channel_hash as hash from channel left join abook on abook_channel = channel_id where abook_xchan = '%s' and not ( channel_pageflags & %d ) ", + dbesc($hash), + intval(PAGE_REMOVED) ); if($r) { foreach($r as $rr) @@ -1367,8 +1372,10 @@ function process_delivery($sender,$arr,$deliveries,$relay) { // remove_community_tag is a no-op if this isn't a community tag activity remove_community_tag($sender,$arr,$channel['channel_id']); + + $item_id = delete_imported_item($sender,$arr,$channel['channel_id']); - $result[] = array($d['hash'],'deleted',$channel['channel_name'] . ' <' . $channel['channel_address'] . '@' . get_app()->get_hostname() . '>'); + $result[] = array($d['hash'],(($item_id) ? 'deleted' : 'delete_failed'),$channel['channel_name'] . ' <' . $channel['channel_address'] . '@' . get_app()->get_hostname() . '>'); if($relay && $item_id) { logger('process_delivery: invoking relay'); @@ -1384,7 +1391,7 @@ function process_delivery($sender,$arr,$deliveries,$relay) { if((x($arr,'obj_type')) && (activity_match($arr['obj_type'],ACTIVITY_OBJ_EVENT))) { require_once('include/event.php'); $ev = bbtoevent($arr['body']); - if(x($ev,'desc') && x($ev,'start')) { + if(x($ev,'desc') && x($ev,'start')) { $ev['event_xchan'] = $arr['author_xchan']; $ev['uid'] = $channel['channel_id']; $ev['account'] = $channel['channel_account_id']; @@ -1524,10 +1531,11 @@ function delete_imported_item($sender,$item,$uid) { logger('delete_imported_item invoked',LOGGER_DEBUG); - $r = q("select id from item where ( author_xchan = '%s' or owner_xchan = '%s' ) + $r = q("select id, item_restrict from item where ( author_xchan = '%s' or owner_xchan = '%s' or source_xchan = '%s' ) and mid = '%s' and uid = %d limit 1", dbesc($sender['hash']), dbesc($sender['hash']), + dbesc($sender['hash']), dbesc($item['mid']), intval($uid) ); @@ -1536,9 +1544,17 @@ function delete_imported_item($sender,$item,$uid) { logger('delete_imported_item: failed: ownership issue'); return false; } + + if($r[0]['item_restrict'] & ITEM_DELETED) { + logger('delete_imported_item: item was already deleted'); + return false; + } require_once('include/items.php'); drop_item($r[0]['id'],false); + + tag_deliver($uid,$r[0]['id']); + return $r[0]['id']; } @@ -1633,22 +1649,26 @@ function import_directory_profile($hash,$profile,$addr,$ud_flags = 1, $suppress_ $arr = array(); $arr['xprof_hash'] = $hash; - $arr['xprof_desc'] = (($profile['description']) ? htmlentities($profile['description'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['xprof_desc'] = (($profile['description']) ? htmlspecialchars($profile['description'], ENT_COMPAT,'UTF-8',false) : ''); $arr['xprof_dob'] = datetime_convert('','',$profile['birthday'],'Y-m-d'); // !!!! check this for 0000 year $arr['xprof_age'] = (($profile['age']) ? intval($profile['age']) : 0); - $arr['xprof_gender'] = (($profile['gender']) ? htmlentities($profile['gender'], ENT_COMPAT,'UTF-8',false) : ''); - $arr['xprof_marital'] = (($profile['marital']) ? htmlentities($profile['marital'], ENT_COMPAT,'UTF-8',false) : ''); - $arr['xprof_sexual'] = (($profile['sexual']) ? htmlentities($profile['sexual'], ENT_COMPAT,'UTF-8',false) : ''); - $arr['xprof_locale'] = (($profile['locale']) ? htmlentities($profile['locale'], ENT_COMPAT,'UTF-8',false) : ''); - $arr['xprof_region'] = (($profile['region']) ? htmlentities($profile['region'], ENT_COMPAT,'UTF-8',false) : ''); - $arr['xprof_postcode'] = (($profile['postcode']) ? htmlentities($profile['postcode'], ENT_COMPAT,'UTF-8',false) : ''); - $arr['xprof_country'] = (($profile['country']) ? htmlentities($profile['country'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['xprof_gender'] = (($profile['gender']) ? htmlspecialchars($profile['gender'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['xprof_marital'] = (($profile['marital']) ? htmlspecialchars($profile['marital'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['xprof_sexual'] = (($profile['sexual']) ? htmlspecialchars($profile['sexual'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['xprof_locale'] = (($profile['locale']) ? htmlspecialchars($profile['locale'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['xprof_region'] = (($profile['region']) ? htmlspecialchars($profile['region'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['xprof_postcode'] = (($profile['postcode']) ? htmlspecialchars($profile['postcode'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['xprof_country'] = (($profile['country']) ? htmlspecialchars($profile['country'], ENT_COMPAT,'UTF-8',false) : ''); + + $arr['xprof_about'] = (($profile['about']) ? htmlspecialchars($profile['about'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['xprof_homepage'] = (($profile['homepage']) ? htmlspecialchars($profile['homepage'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['xprof_hometown'] = (($profile['hometown']) ? htmlspecialchars($profile['hometown'], ENT_COMPAT,'UTF-8',false) : ''); $clean = array(); if(array_key_exists('keywords',$profile) and is_array($profile['keywords'])) { import_directory_keywords($hash,$profile['keywords']); foreach($profile['keywords'] as $kw) { - $kw = trim(htmlentities($kw,ENT_COMPAT,'UTF-8',false)); + $kw = trim(htmlspecialchars($kw,ENT_COMPAT,'UTF-8',false)); $kw = trim($kw,','); $clean[] = $kw; } @@ -1692,6 +1712,9 @@ function import_directory_profile($hash,$profile,$addr,$ud_flags = 1, $suppress_ xprof_region = '%s', xprof_postcode = '%s', xprof_country = '%s', + xprof_about = '%s', + xprof_homepage = '%s', + xprof_hometown = '%s', xprof_keywords = '%s' where xprof_hash = '%s' limit 1", dbesc($arr['xprof_desc']), @@ -1704,6 +1727,9 @@ function import_directory_profile($hash,$profile,$addr,$ud_flags = 1, $suppress_ dbesc($arr['xprof_region']), dbesc($arr['xprof_postcode']), dbesc($arr['xprof_country']), + dbesc($arr['xprof_about']), + dbesc($arr['xprof_homepage']), + dbesc($arr['xprof_hometown']), dbesc($arr['xprof_keywords']), dbesc($arr['xprof_hash']) ); @@ -1712,7 +1738,7 @@ function import_directory_profile($hash,$profile,$addr,$ud_flags = 1, $suppress_ else { $update = true; logger('import_directory_profile: new profile'); - $x = 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_keywords) values ('%s', '%s', '%s', %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s') ", + $x = 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']), dbesc($arr['xprof_dob']), @@ -1724,6 +1750,9 @@ function import_directory_profile($hash,$profile,$addr,$ud_flags = 1, $suppress_ dbesc($arr['xprof_region']), dbesc($arr['xprof_postcode']), dbesc($arr['xprof_country']), + dbesc($arr['xprof_about']), + dbesc($arr['xprof_homepage']), + dbesc($arr['xprof_hometown']), dbesc($arr['xprof_keywords']) ); } @@ -1750,7 +1779,7 @@ function import_directory_keywords($hash,$keywords) { $clean = array(); foreach($keywords as $kw) { - $kw = trim(htmlentities($kw,ENT_COMPAT,'UTF-8',false)); + $kw = trim(htmlspecialchars($kw,ENT_COMPAT,'UTF-8',false)); $kw = trim($kw,','); $clean[] = $kw; } @@ -1849,10 +1878,21 @@ function import_site($arr,$pubkey) { $access_policy = ACCESS_TIERED; } - $directory_url = htmlentities($arr['directory_url'],ENT_COMPAT,'UTF-8',false); - $url = htmlentities($arr['url'],ENT_COMPAT,'UTF-8',false); - $sellpage = htmlentities($arr['sellpage'],ENT_COMPAT,'UTF-8',false); - $site_location = htmlentities($arr['location'],ENT_COMPAT,'UTF-8',false); + // don't let insecure sites register as public hubs + + if(strpos($arr['url'],'https://') === false) + $access_policy = ACCESS_PRIVATE; + + if($access_policy != ACCESS_PRIVATE) { + $x = z_fetch_url($arr['url'] . '/siteinfo/json'); + if(! $x['success']) + $access_policy = ACCESS_PRIVATE; + } + + $directory_url = htmlspecialchars($arr['directory_url'],ENT_COMPAT,'UTF-8',false); + $url = htmlspecialchars($arr['url'],ENT_COMPAT,'UTF-8',false); + $sellpage = htmlspecialchars($arr['sellpage'],ENT_COMPAT,'UTF-8',false); + $site_location = htmlspecialchars($arr['location'],ENT_COMPAT,'UTF-8',false); if($exists) { if(($siterecord['site_flags'] != $site_directory) @@ -2050,7 +2090,7 @@ function process_channel_sync_delivery($sender,$arr,$deliveries) { } if(array_key_exists('channel',$arr) && is_array($arr['channel']) && count($arr['channel'])) { - $disallowed = array('channel_id','channel_account_id','channel_primary','channel_prvkey', 'channel_address'); + $disallowed = array('channel_id','channel_account_id','channel_primary','channel_prvkey', 'channel_address', 'channel_notifyflags'); $clean = array(); foreach($arr['channel'] as $k => $v) { @@ -2073,6 +2113,33 @@ function process_channel_sync_delivery($sender,$arr,$deliveries) { $clean = array(); foreach($arr['abook'] as $abook) { + + // Perform discovery if the referenced xchan hasn't ever been seen on this hub. + // This relies on the undocumented behaviour that red sites send xchan info with the abook + + if($abook['abook_xchan'] && $abook['xchan_address']) { + $h = zot_get_hublocs($abook['abook_xchan']); + if(! $h) { + $f = zot_finger($abook['xchan_address'],$channel); + if(! $f['success']) { + logger('process_channel_sync_delivery: abook not probe-able' . $abook['xchan_address']); + continue; + } + $j = json_decode($f['body'],true); + if(! ($j['success'] && $j['guid'])) { + logger('process_channel_sync_delivery: probe failed.'); + continue; + } + + $x = import_xchan($j); + + if(! $x['success']) { + logger('process_channel_sync_delivery: import failed.'); + continue; + } + } + } + foreach($abook as $k => $v) { if(in_array($k,$disallowed) || (strpos($k,'abook') !== 0)) continue; |