diff options
33 files changed, 844 insertions, 424 deletions
@@ -1,3 +1,21 @@ +Hubzilla 3.8.4 (2018-11-14) + - Fix xss issue (thanks to Eduardo) + - Implement hook in enotify to be used by superblock + - Various css fixes + - Improve photo cache handling + - Provide a function hz_syslog() to log to syslog + - Fix request_target in z_post_url() + - Fix plural handling for various languages + - Some preparatory work for zot6 + - Fix warning in gallery addon + - Fix date issue on xchan photo update in diaspora and pubcrawl addons + - Fix typos in startpage addon + - Improve activitypub addressing + - Fix taxonomy in activitypub direct messages + - Fix syntax error in diaspora addon + - New e-learning addon flashcards + + Hubzilla 3.8.3 (2018-11-05) - Do not count likes in forum notifications if likes notifications are disabled - Fix typo in spanish translation which broke javascript diff --git a/Zotlabs/Lib/Enotify.php b/Zotlabs/Lib/Enotify.php index cfb0bd344..25c96d9cc 100644 --- a/Zotlabs/Lib/Enotify.php +++ b/Zotlabs/Lib/Enotify.php @@ -825,7 +825,7 @@ class Enotify { // convert this logic into a json array just like the system notifications - return array( + $x = array( 'notify_link' => $item['llink'], 'name' => $item['author']['xchan_name'], 'url' => $item['author']['xchan_url'], @@ -835,9 +835,19 @@ class Enotify { 'b64mid' => ((in_array($item['verb'], [ACTIVITY_LIKE, ACTIVITY_DISLIKE])) ? 'b64.' . base64url_encode($item['thr_parent']) : 'b64.' . base64url_encode($item['mid'])), 'notify_id' => 'undefined', 'thread_top' => (($item['item_thread_top']) ? true : false), - 'message' => strip_tags(bbcode($itemem_text)) + 'message' => strip_tags(bbcode($itemem_text)), + // these are for the superblock addon + 'hash' => $item['author']['xchan_hash'], + 'uid' => local_channel(), + 'display' => true ); + call_hooks('enotify_format',$x); + if(! $x['display']) { + return []; + } + + return $x; } } diff --git a/Zotlabs/Lib/Libzot.php b/Zotlabs/Lib/Libzot.php index ec9db4ce1..2c726aff4 100644 --- a/Zotlabs/Lib/Libzot.php +++ b/Zotlabs/Lib/Libzot.php @@ -7,16 +7,10 @@ namespace Zotlabs\Lib; * */ -use Zotlabs\Lib\DReport; -use Zotlabs\Lib\Enotify; -use Zotlabs\Lib\Group; -use Zotlabs\Lib\Libsync; -use Zotlabs\Lib\Libzotdir; -use Zotlabs\Lib\System; -use Zotlabs\Lib\MessageFilter; -use Zotlabs\Lib\Queue; -use Zotlabs\Lib\Zotfinger; -use Zotlabs\Web\HTTPSig; +use Zotlabs\Zot6\HTTPSig; +use Zotlabs\Access\Permissions; +use Zotlabs\Access\PermissionLimits; +use Zotlabs\Daemon\Master; require_once('include/crypto.php'); @@ -211,9 +205,10 @@ class Libzot { if($channel) { $headers = [ - 'X-Zot-Token' => random_string(), - 'Digest' => HTTPSig::generate_digest_header($data), - 'Content-type' => 'application/x-zot+json' + 'X-Zot-Token' => random_string(), + 'Digest' => HTTPSig::generate_digest_header($data), + 'Content-type' => 'application/x-zot+json', + '(request-target)' => 'post ' . get_request_string($url) ]; $h = HTTPSig::create_sig($headers,$channel['channel_prvkey'],channel_url($channel),false,'sha512', @@ -378,13 +373,13 @@ class Libzot { else { // if we were just granted read stream permission and didn't have it before, try to pull in some posts if((! $old_read_stream_perm) && (intval($permissions['view_stream']))) - \Zotlabs\Daemon\Master::Summon(array('Onepoll',$r[0]['abook_id'])); + Master::Summon([ 'Onepoll', $r[0]['abook_id'] ]); } } else { - $p = \Zotlabs\Access\Permissions::connect_perms($channel['channel_id']); - $my_perms = \Zotlabs\Access\Permissions::serialise($p['perms']); + $p = Permissions::connect_perms($channel['channel_id']); + $my_perms = Permissions::serialise($p['perms']); $automatic = $p['automatic']; @@ -424,8 +419,8 @@ class Libzot { ); if($new_connection) { - if(! \Zotlabs\Access\Permissions::PermsCompare($new_perms,$previous_perms)) - \Zotlabs\Daemon\Master::Summon(array('Notifier','permissions_create',$new_connection[0]['abook_id'])); + if(! Permissions::PermsCompare($new_perms,$previous_perms)) + Master::Summon([ 'Notifier', 'permissions_create', $new_connection[0]['abook_id'] ]); Enotify::submit( [ 'type' => NOTIFY_INTRO, @@ -438,7 +433,7 @@ class Libzot { if(intval($permissions['view_stream'])) { if(intval(get_pconfig($channel['channel_id'],'perm_limits','send_stream') & PERMS_PENDING) || (! intval($new_connection[0]['abook_pending']))) - \Zotlabs\Daemon\Master::Summon(array('Onepoll',$new_connection[0]['abook_id'])); + Master::Summon([ 'Onepoll', $new_connection[0]['abook_id'] ]); } @@ -975,39 +970,45 @@ class Libzot { $x = json_decode($x,true); } - if(! $x['success']) { + if(! is_array($x)) { + btlogger('failed communication - no response'); + } - // handle remote validation issues + if($x) { + if(! $x['success']) { - $b = q("update dreport set dreport_result = '%s', dreport_time = '%s' where dreport_queue = '%s'", - dbesc(($x['message']) ? $x['message'] : 'unknown delivery error'), - dbesc(datetime_convert()), - dbesc($outq['outq_hash']) - ); - } + // handle remote validation issues + + $b = q("update dreport set dreport_result = '%s', dreport_time = '%s' where dreport_queue = '%s'", + dbesc(($x['message']) ? $x['message'] : 'unknown delivery error'), + dbesc(datetime_convert()), + dbesc($outq['outq_hash']) + ); + } - if(array_key_exists('delivery_report',$x) && is_array($x['delivery_report'])) { - foreach($x['delivery_report'] as $xx) { - if(is_array($xx) && array_key_exists('message_id',$xx) && DReport::is_storable($xx)) { - q("insert into dreport ( dreport_mid, dreport_site, dreport_recip, dreport_name, dreport_result, dreport_time, dreport_xchan ) values ( '%s', '%s', '%s','%s','%s','%s','%s' ) ", - dbesc($xx['message_id']), - dbesc($xx['location']), - dbesc($xx['recipient']), - dbesc($xx['name']), - dbesc($xx['status']), - dbesc(datetime_convert($xx['date'])), - dbesc($xx['sender']) - ); + if(is_array($x) && array_key_exists('delivery_report',$x) && is_array($x['delivery_report'])) { + foreach($x['delivery_report'] as $xx) { + call_hooks('dreport_process',$xx); + if(is_array($xx) && array_key_exists('message_id',$xx) && DReport::is_storable($xx)) { + q("insert into dreport ( dreport_mid, dreport_site, dreport_recip, dreport_name, dreport_result, dreport_time, dreport_xchan ) values ( '%s', '%s', '%s','%s','%s','%s','%s' ) ", + dbesc($xx['message_id']), + dbesc($xx['location']), + dbesc($xx['recipient']), + dbesc($xx['name']), + dbesc($xx['status']), + dbesc(datetime_convert($xx['date'])), + dbesc($xx['sender']) + ); + } } - } - // we have a more descriptive delivery report, so discard the per hub 'queue' report. + // we have a more descriptive delivery report, so discard the per hub 'queue' report. - q("delete from dreport where dreport_queue = '%s' ", - dbesc($outq['outq_hash']) - ); + q("delete from dreport where dreport_queue = '%s' ", + dbesc($outq['outq_hash']) + ); + } } - // update the timestamp for this site q("update site set site_dead = 0, site_update = '%s' where site_url = '%s'", @@ -1092,13 +1093,27 @@ class Libzot { return; } - $message_request = ((array_key_exists('message_id',$env)) ? true : false); - if($message_request) - logger('processing message request'); + $message_request = false; + $has_data = array_key_exists('data',$env) && $env['data']; $data = (($has_data) ? $env['data'] : false); - + + $AS = null; + + if($env['encoding'] === 'activitystreams') { + + $AS = new ActivityStreams($data); + if(! $AS->is_valid()) { + logger('Activity rejected: ' . print_r($data,true)); + return; + } + $arr = Activity::decode_note($AS); + + logger($AS->debug()); + } + + $deliveries = null; if(array_key_exists('recipients',$env) && count($env['recipients'])) { @@ -1140,7 +1155,7 @@ class Libzot { // and who are allowed to see them based on the sender's permissions // @fixme; - $deliveries = self::public_recips($env); + $deliveries = self::public_recips($env,$AS); } @@ -1157,48 +1172,42 @@ class Libzot { if(in_array($env['type'],['activity','response'])) { - if($env['encoding'] === 'zot') { - $arr = get_item_elements($data); - - $v = validate_item_elements($data,$arr); - - if(! $v['success']) { - logger('Activity rejected: ' . $v['message'] . ' ' . print_r($data,true)); - return; - } - } - elseif($env['encoding'] === 'activitystreams') { + $arr = Activity::decode_note($AS); - $AS = new \Zotlabs\Lib\ActivityStreams($data); - if(! $AS->is_valid()) { - logger('Activity rejected: ' . print_r($data,true)); - return; - } - $arr = \Zotlabs\Lib\Activity::decode_note($AS); + //logger($AS->debug()); - logger($AS->debug()); + $r = q("select hubloc_hash from hubloc where hubloc_id_url = '%s' limit 1", + dbesc($AS->actor['id']) + ); - $r = q("select hubloc_hash from hubloc where hubloc_id_url = '%s' limit 1", - dbesc($AS->actor['id']) - ); + if($r) { + $arr['author_xchan'] = $r[0]['hubloc_hash']; + } - if($r) { - $arr['author_xchan'] = $r[0]['hubloc_hash']; - } - // @fixme (in individual delivery, change owner if needed) + + $s = q("select hubloc_hash from hubloc where hubloc_id_url = '%s' limit 1", + dbesc($env['sender']) + ); + + // in individual delivery, change owner if needed + if($s) { + $arr['owner_xchan'] = $s[0]['hubloc_hash']; + } + else { $arr['owner_xchan'] = $env['sender']; - if($private) { - $arr['item_private'] = true; - } - // @fixme - spoofable - if($AS->data['hubloc']) { - $arr['item_verified'] = true; - } - if($AS->data['signed_data']) { - IConfig::Set($arr,'activitystreams','signed_data',$AS->data['signed_data'],false); - } + } + if($private) { + $arr['item_private'] = true; + } + // @fixme - spoofable + if($AS->data['hubloc']) { + $arr['item_verified'] = true; } + if($AS->data['signed_data']) { + IConfig::Set($arr,'activitystreams','signed_data',$AS->data['signed_data'],false); + } + logger('Activity received: ' . print_r($arr,true), LOGGER_DATA, LOG_DEBUG); logger('Activity recipients: ' . print_r($deliveries,true), LOGGER_DATA, LOG_DEBUG); @@ -1225,15 +1234,31 @@ class Libzot { } - static function is_top_level($env) { + static function is_top_level($env,$act) { if($env['encoding'] === 'zot' && array_key_exists('flags',$env) && in_array('thread_parent', $env['flags'])) { return true; } - if($env['encoding'] === 'activitystreams') { - if(array_key_exists('inReplyTo',$env['data']) && $env['data']['inReplyTo']) { + if($act) { + if(in_array($act->type, ['Like','Dislike'])) { return false; } - return true; + $x = self::find_parent($env,$act); + if($x === $act->id || $x === $act->obj['id']) { + return true; + } + } + return false; + } + + + static function find_parent($env,$act) { + if($act) { + if(in_array($act->type, ['Like','Dislike'])) { + return $act->obj['id']; + } + if($act->parent_id) { + return $act->parent_id; + } } return false; } @@ -1255,7 +1280,7 @@ class Libzot { * @return NULL|array */ - static function public_recips($msg) { + static function public_recips($msg, $act) { require_once('include/channel.php'); @@ -1269,7 +1294,7 @@ class Libzot { $perm = 'send_stream'; - if(self::is_top_level($msg)) { + if(self::is_top_level($msg,$act)) { $check_mentions = true; } } @@ -1301,9 +1326,9 @@ class Libzot { if($check_mentions) { // It's a top level post. Look at the tags. See if any of them are mentions and are on this hub. - if(array_path_exists('data/object/tag',$msg)) { - if(is_array($msg['data']['object']['tag']) && $msg['data']['object']['tag']) { - foreach($msg['data']['object']['tag'] as $tag) { + if($act && $act->obj) { + if(is_array($act->obj['tag']) && $act->obj['tag']) { + foreach($act->obj['tag'] as $tag) { if($tag['type'] === 'Mention' && (strpos($tag['href'],z_root()) !== false)) { $address = basename($tag['href']); if($address) { @@ -1325,9 +1350,12 @@ class Libzot { // everybody that stored a copy of the parent. This way we know we're covered. We'll check the // comment permissions when we deliver them. - if(array_path_exists('data/inReplyTo',$msg)) { - $z = q("select owner_xchan as hash from item where parent_mid = '%s' ", - dbesc($msg['data']['inReplyTo']) + $thread_parent = self::find_parent($msg,$act); + + if($thread_parent) { + $z = q("select channel_hash as hash from channel left join item on channel.channel_id = item.uid where ( item.thr_parent = '%s' OR item.parent_mid = '%s' ) ", + dbesc($thread_parent), + dbesc($thread_parent) ); if($z) { foreach($z as $zv) { @@ -1341,7 +1369,7 @@ class Libzot { // It's a bit of work since it's a multi-dimensional array if($r) { - $r = array_unique($r); + $r = array_values(array_unique($r)); } logger('public_recips: ' . print_r($r,true), LOGGER_DATA, LOG_DEBUG); @@ -1378,7 +1406,7 @@ class Libzot { $local_public = $public; - $DR = new \Zotlabs\Lib\DReport(z_root(),$sender,$d,$arr['mid']); + $DR = new DReport(z_root(),$sender,$d,$arr['mid']); $channel = channelx_by_hash($d); @@ -1413,7 +1441,7 @@ class Libzot { $local_public = true; $r = q("select xchan_selfcensored from xchan where xchan_hash = '%s' limit 1", - dbesc($sender['hash']) + dbesc($sender) ); // don't import sys channel posts from selfcensored authors if($r && (intval($r[0]['xchan_selfcensored']))) { @@ -1441,11 +1469,30 @@ class Libzot { $arr['item_wall'] = 0; } - if((! perm_is_allowed($channel['channel_id'],$sender,$perm)) && (! $tag_delivery) && (! $local_public)) { - logger("permission denied for delivery to channel {$channel['channel_id']} {$channel['channel_address']}"); - $DR->update('permission denied'); - $result[] = $DR->get(); - continue; + $friendofriend = false; + + if ((! $tag_delivery) && (! $local_public)) { + $allowed = (perm_is_allowed($channel['channel_id'],$sender,$perm)); + if((! $allowed) && $perm === 'post_comments') { + $parent = q("select * from item where mid = '%s' and uid = %d limit 1", + dbesc($arr['parent_mid']), + intval($channel['channel_id']) + ); + if ($parent) { + $allowed = can_comment_on_post($d,$parent[0]); + } + } + if($request) { + $allowed = true; + $friendofriend = true; + } + + if (! $allowed) { + logger("permission denied for delivery to channel {$channel['channel_id']} {$channel['channel_address']}"); + $DR->update('permission denied'); + $result[] = $DR->get(); + continue; + } } if($arr['mid'] != $arr['parent_mid']) { @@ -1456,7 +1503,7 @@ class Libzot { // As a side effect we will also do a preliminary check that we have the top-level-post, otherwise // processing it is pointless. - $r = q("select route, id from item where mid = '%s' and uid = %d limit 1", + $r = q("select route, id, owner_xchan, item_private from item where mid = '%s' and uid = %d limit 1", dbesc($arr['parent_mid']), intval($channel['channel_id']) ); @@ -1480,15 +1527,21 @@ class Libzot { if((! $relay) && (! $request) && (! $local_public) && perm_is_allowed($channel['channel_id'],$sender,'send_stream')) { - \Zotlabs\Daemon\Master::Summon(array('Notifier', 'request', $channel['channel_id'], $sender, $arr['parent_mid'])); + self::fetch_conversation($channel,$arr['parent_mid']); } continue; } - if($relay) { + + if($relay || $friendofriend || (intval($r[0]['item_private']) === 0 && intval($arr['item_private']) === 0)) { // reset the route in case it travelled a great distance upstream // use our parent's route so when we go back downstream we'll match // with whatever route our parent has. + // Also friend-of-friend conversations may have been imported without a route, + // but we are now getting comments via listener delivery + // and if there is no privacy on this or the parent, we don't care about the route, + // so just set the owner and route accordingly. $arr['route'] = $r[0]['route']; + $arr['owner_xchan'] = $r[0]['owner_xchan']; } else { @@ -1546,13 +1599,13 @@ class Libzot { $arr['aid'] = $channel['channel_account_id']; $arr['uid'] = $channel['channel_id']; - $item_id = delete_imported_item($sender,$arr,$channel['channel_id'],$relay); + $item_id = self::delete_imported_item($sender,$arr,$channel['channel_id'],$relay); $DR->update(($item_id) ? 'deleted' : 'delete_failed'); $result[] = $DR->get(); if($relay && $item_id) { logger('process_delivery: invoking relay'); - \Zotlabs\Daemon\Master::Summon(array('Notifier','relay',intval($item_id))); + Master::Summon([ 'Notifier', 'relay', intval($item_id) ]); $DR->update('relayed'); $result[] = $DR->get(); } @@ -1662,7 +1715,7 @@ class Libzot { if($relay && $item_id) { logger('Invoking relay'); - \Zotlabs\Daemon\Master::Summon(array('Notifier','relay',intval($item_id))); + Master::Summon([ 'Notifier', 'relay', intval($item_id) ]); $DR->addto_update('relayed'); $result[] = $DR->get(); } @@ -1676,6 +1729,98 @@ class Libzot { return $result; } + static public function fetch_conversation($channel,$mid) { + + // Use Zotfinger to create a signed request + + $a = Zotfinger::exec($mid,$channel); + + logger('received conversation: ' . print_r($a,true), LOGGER_DATA); + + if($a['data']['type'] !== 'OrderedCollection') { + return; + } + + if(! intval($a['data']['totalItems'])) { + return; + } + + $ret = []; + + foreach($a['data']['orderedItems'] as $activity) { + + $AS = new ActivityStreams($activity); + if(! $AS->is_valid()) { + logger('FOF Activity rejected: ' . print_r($activity,true)); + continue; + } + $arr = Activity::decode_note($AS); + + logger($AS->debug()); + + + $r = q("select hubloc_hash from hubloc where hubloc_id_url = '%s' limit 1", + dbesc($AS->actor['id']) + ); + + if(! $r) { + $y = import_author_xchan([ 'url' => $AS->actor['id'] ]); + if($y) { + $r = q("select hubloc_hash from hubloc where hubloc_id_url = '%s' limit 1", + dbesc($AS->actor['id']) + ); + } + if(! $r) { + logger('FOF Activity: no actor'); + continue; + } + } + + if($AS->obj['actor'] && $AS->obj['actor']['id'] && $AS->obj['actor']['id'] !== $AS->actor['id']) { + $y = import_author_xchan([ 'url' => $AS->obj['actor']['id'] ]); + if(! $y) { + logger('FOF Activity: no object actor'); + continue; + } + } + + + if($r) { + $arr['author_xchan'] = $r[0]['hubloc_hash']; + } + + $s = q("select hubloc_hash from hubloc where hubloc_id_url = '%s' limit 1", + dbesc($a['signature']['signer']) + ); + + if($s) { + $arr['owner_xchan'] = $s[0]['hubloc_hash']; + } + else { + $arr['owner_xchan'] = $a['signature']['signer']; + } + + // @fixme - spoofable + if($AS->data['hubloc']) { + $arr['item_verified'] = true; + } + if($AS->data['signed_data']) { + IConfig::Set($arr,'activitystreams','signed_data',$AS->data['signed_data'],false); + } + + logger('FOF Activity received: ' . print_r($arr,true), LOGGER_DATA, LOG_DEBUG); + logger('FOF Activity recipient: ' . $channel['channel_hash'], LOGGER_DATA, LOG_DEBUG); + + $result = self::process_delivery($arr['owner_xchan'],$arr, [ $channel['channel_hash'] ],false,false,true); + if ($result) { + $ret = array_merge($ret, $result); + } + } + + return $ret; + } + + /** * @brief Remove community tag. * @@ -1900,7 +2045,7 @@ class Libzot { foreach($deliveries as $d) { - $DR = new \Zotlabs\Lib\DReport(z_root(),$sender,$d,$arr['mid']); + $DR = new DReport(z_root(),$sender,$d,$arr['mid']); $r = q("select * from channel where channel_hash = '%s' limit 1", dbesc($d['hash']) @@ -2112,7 +2257,8 @@ class Libzot { if(intval($channel['channel_removed']) && $hub['hubloc_url'] === z_root()) $hub['hubloc_deleted'] = 1; - $ret[] = [ + + $z = [ 'host' => $hub['hubloc_host'], 'address' => $hub['hubloc_addr'], 'id_url' => $hub['hubloc_id_url'], @@ -2120,10 +2266,25 @@ class Libzot { 'url' => $hub['hubloc_url'], 'url_sig' => $hub['hubloc_url_sig'], 'site_id' => $hub['hubloc_site_id'], - 'callback' => $hub['hubloc_callback'], + 'callback' => $hub['hubloc_url'] . '/zot', 'sitekey' => $hub['hubloc_sitekey'], 'deleted' => (intval($hub['hubloc_deleted']) ? true : false) ]; + + // version compatibility tweaks + + if(! strpos($z['url_sig'],'.')) { + $z['url_sig'] = 'sha256.' . $z['url_sig']; + } + + if(! $z['id_url']) { + $z['id_url'] = $z['url'] . '/channel/' . substr($z['address'],0,strpos($z['address'],'@')); + } + if(! $z['site_id']) { + $z['site_id'] = Libzot::make_xchan_hash($z['url'],$z['sitekey']); + } + + $ret[] = $z; } } @@ -2331,6 +2492,10 @@ class Libzot { // we may only end up with one; which results in posts with no author name or photo and are a bit // of a hassle to repair. If either or both are missing, do a full discovery probe. + if(! array_key_exists('id',$x)) { + return import_author_activitypub($x); + } + $hash = self::make_xchan_hash($x['id'],$x['key']); $desturl = $x['url']; @@ -2502,7 +2667,7 @@ class Libzot { } else { // check if it has characteristics of a public forum based on custom permissions. - $m = \Zotlabs\Access\Permissions::FilledAutoperms($e['channel_id']); + $m = Permissions::FilledAutoperms($e['channel_id']); if($m) { foreach($m as $k => $v) { if($k == 'tag_deliver' && intval($v) == 1) @@ -2584,13 +2749,13 @@ class Libzot { ]; $ret['channel_role'] = get_pconfig($e['channel_id'],'system','permissions_role','custom'); - + $ret['protocols'] = [ 'zot6' ]; $ret['searchable'] = $searchable; $ret['adult_content'] = $adult_channel; $ret['public_forum'] = $public_forum; - $ret['comments'] = map_scope(\Zotlabs\Access\PermissionLimits::Get($e['channel_id'],'post_comments')); - $ret['mail'] = map_scope(\Zotlabs\Access\PermissionLimits::Get($e['channel_id'],'post_mail')); + $ret['comments'] = map_scope(PermissionLimits::Get($e['channel_id'],'post_comments')); + $ret['mail'] = map_scope(PermissionLimits::Get($e['channel_id'],'post_mail')); if($deleted) $ret['deleted'] = $deleted; diff --git a/Zotlabs/Lib/PConfig.php b/Zotlabs/Lib/PConfig.php index ec0792ce1..b9384cf6b 100644 --- a/Zotlabs/Lib/PConfig.php +++ b/Zotlabs/Lib/PConfig.php @@ -57,6 +57,7 @@ class PConfig { \App::$config[$uid][$c]['config_loaded'] = true; } \App::$config[$uid][$c][$k] = $rr['v']; + \App::$config[$uid][$c]['pcfgud:'.$k] = $rr['updated']; } } } @@ -113,7 +114,7 @@ class PConfig { * The value to store * @return mixed Stored $value or false */ - static public function Set($uid, $family, $key, $value) { + static public function Set($uid, $family, $key, $value, $updated=NULL) { // this catches subtle errors where this function has been called // with local_channel() when not logged in (which returns false) @@ -130,29 +131,74 @@ class PConfig { $dbvalue = ((is_array($value)) ? serialize($value) : $value); $dbvalue = ((is_bool($dbvalue)) ? intval($dbvalue) : $dbvalue); + if (! $updated) { + $updated = datetime_convert(); + } + + $hash = hash('sha256',$family.':'.$key); + + if (self::Get($uid, 'hz_delpconfig', $hash) !== false) { + if (Get($uid, 'hz_delpconfig', $hash) > $updated) { + logger('Refusing to update pconfig with outdated info (Item deleted more recently).', LOGGER_NORMAL, LOG_ERR); + return self::Get($uid,$family,$key); + } else { + self::Delete($uid,'hz_delpconfig',$hash); + } + } + if(self::Get($uid, $family, $key) === false) { if(! array_key_exists($uid, \App::$config)) \App::$config[$uid] = array(); if(! array_key_exists($family, \App::$config[$uid])) \App::$config[$uid][$family] = array(); - $ret = q("INSERT INTO pconfig ( uid, cat, k, v ) VALUES ( %d, '%s', '%s', '%s' ) ", + + $ret = q("INSERT INTO pconfig ( uid, cat, k, v, updated ) VALUES ( %d, '%s', '%s', '%s', '%s' ) ", intval($uid), dbesc($family), dbesc($key), - dbesc($dbvalue) + dbesc($dbvalue), + dbesc($updated) ); + + // There is a possible race condition if another process happens + // to insert something after this thread has Loaded and now. We should + // at least make a note of it if it happens. + + if (!$ret) { + logger("Error: Insert to pconfig failed.",LOGGER_NORMAL, LOG_ERR); + } + + \App::$config[$uid][$family]['pcfgud:'.$key] = $updated; + } else { + $new = (\App::$config[$uid][$family]['pcfgud:'.$key] < $updated); - $ret = q("UPDATE pconfig SET v = '%s' WHERE uid = %d and cat = '%s' AND k = '%s'", - dbesc($dbvalue), - intval($uid), - dbesc($family), - dbesc($key) - ); + if ($new) { + + // @NOTE There is still a possible race condition under limited circumstances + // where a value will be updated by another thread with more current data than + // we have. At this point there is no easy way to test for it, so we update + // and hope for the best. + + $ret = q("UPDATE pconfig SET v = '%s', updated = '%s' WHERE uid = %d and cat = '%s' AND k = '%s' ", + dbesc($dbvalue), + dbesc($updated), + intval($uid), + dbesc($family), + dbesc($key) + ); + + \App::$config[$uid][$family]['pcfgud:'.$key] = $updated; + + } else { + logger('Refusing to update pconfig with outdated info.', LOGGER_NORMAL, LOG_ERR); + return self::Get($uid, $family, $key); + } } + // keep a separate copy for all variables which were // set in the life of this page. We need this to // synchronise channel clones. @@ -163,7 +209,11 @@ class PConfig { \App::$config[$uid]['transient'][$family] = array(); \App::$config[$uid][$family][$key] = $value; - \App::$config[$uid]['transient'][$family][$key] = $value; + + if ($new) { + \App::$config[$uid]['transient'][$family][$key] = $value; + \App::$config[$uid]['transient'][$family]['pcfgud:'.$key] = $updated; + } if($ret) return $value; @@ -186,18 +236,29 @@ class PConfig { * The configuration key to delete * @return mixed */ - static public function Delete($uid, $family, $key) { + static public function Delete($uid, $family, $key, $updated = NULL) { if(is_null($uid) || $uid === false) return false; + $updated = ($updated) ? $updated : datetime_convert(); + + $newer = (\App::$config[$uid][$family]['pcfgud:'.$key] < $updated); + + if (! $newer) { + logger('Refusing to delete pconfig with outdated delete request.', LOGGER_NORMAL, LOG_ERR); + return false; + } + $ret = false; - if(array_key_exists($uid,\App::$config) - && is_array(\App::$config['uid']) - && array_key_exists($family,\App::$config['uid']) - && array_key_exists($key, \App::$config[$uid][$family])) + if (isset(\App::$config[$uid][$family][$key])) { unset(\App::$config[$uid][$family][$key]); + } + + if (isset(\App::$config[$uid][$family]['pcfgud:'.$key])) { + unset(\App::$config[$uid][$family]['pcfgud:'.$key]); + } $ret = q("DELETE FROM pconfig WHERE uid = %d AND cat = '%s' AND k = '%s'", intval($uid), @@ -205,6 +266,22 @@ class PConfig { dbesc($key) ); + if ($family != 'hz_delpconfig') { + $hash = hash('sha256',$family.':'.$key); + set_pconfig($uid,'hz_delpconfig',$hash,$updated); + } + + // Synchronize delete with clones. + + if(! array_key_exists('transient', \App::$config[$uid])) + \App::$config[$uid]['transient'] = array(); + if(! array_key_exists($family, \App::$config[$uid]['transient'])) + \App::$config[$uid]['transient'][$family] = array(); + + if ($new) { + \App::$config[$uid]['transient'][$family]['pcfgdel:'.$key] = $updated; + } + return $ret; } diff --git a/Zotlabs/Module/Owa.php b/Zotlabs/Module/Owa.php index 4a488086f..ad57f883c 100644 --- a/Zotlabs/Module/Owa.php +++ b/Zotlabs/Module/Owa.php @@ -31,15 +31,17 @@ class Owa extends \Zotlabs\Web\Controller { if($keyId) { $r = q("select * from hubloc left join xchan on hubloc_hash = xchan_hash - where hubloc_addr = '%s' ", - dbesc(str_replace('acct:','',$keyId)) + where ( hubloc_addr = '%s' or hubloc_id_url = '%s' ) ", + dbesc(str_replace('acct:','',$keyId)), + dbesc($keyId) ); if(! $r) { $found = discover_by_webbie(str_replace('acct:','',$keyId)); if($found) { $r = q("select * from hubloc left join xchan on hubloc_hash = xchan_hash - where hubloc_addr = '%s' ", - dbesc(str_replace('acct:','',$keyId)) + where ( hubloc_addr = '%s' or hubloc_id_url = '%s' ) ", + dbesc(str_replace('acct:','',$keyId)), + dbesc($keyId) ); } } diff --git a/Zotlabs/Module/Photo.php b/Zotlabs/Module/Photo.php index 8efc00707..30e8340e2 100644 --- a/Zotlabs/Module/Photo.php +++ b/Zotlabs/Module/Photo.php @@ -4,13 +4,12 @@ namespace Zotlabs\Module; require_once('include/security.php'); require_once('include/attach.php'); require_once('include/photo/photo_driver.php'); -require_once('include/photos.php'); class Photo extends \Zotlabs\Web\Controller { function init() { - + $prvcachecontrol = false; $streaming = null; $channel = null; @@ -32,26 +31,26 @@ class Photo extends \Zotlabs\Web\Controller { } $observer_xchan = get_observer_hash(); + $ismodified = $_SERVER['HTTP_IF_MODIFIED_SINCE']; - $default = z_root() . '/' . get_default_profile_photo(); - if(isset($type)) { /** * Profile photos - Access controls on default profile photos are not honoured since they need to be exchanged with remote sites. * */ - + + $default = get_default_profile_photo(); + if($type === 'profile') { switch($res) { - case 'm': $resolution = 5; - $default = z_root() . '/' . get_default_profile_photo(80); + $default = get_default_profile_photo(80); break; case 's': $resolution = 6; - $default = z_root() . '/' . get_default_profile_photo(48); + $default = get_default_profile_photo(48); break; case 'l': default: @@ -60,6 +59,8 @@ class Photo extends \Zotlabs\Web\Controller { } } + $modified = filemtime($default); + $default = z_root() . '/' . $default; $uid = $person; $d = [ 'imgscale' => $resolution, 'channel_id' => $uid, 'default' => $default, 'data' => '', 'mimetype' => '' ]; @@ -78,17 +79,18 @@ class Photo extends \Zotlabs\Web\Controller { intval(PHOTO_PROFILE) ); if($r) { + $modified = strtotime($r[0]['edited'] . "Z"); $data = dbunescbin($r[0]['content']); $mimetype = $r[0]['mimetype']; } if(intval($r[0]['os_storage'])) $data = file_get_contents($data); } + if(! $data) { - $data = fetch_image_from_url($default,$mimetype); - } - if(! $mimetype) { - $mimetype = 'image/png'; + $x = z_fetch_url($default,true,0,[ 'novalidate' => true ]); + $data = ($x['success'] ? $x['body'] : EMPTY_STR); + $mimetype = 'image/png'; } } else { @@ -124,9 +126,7 @@ class Photo extends \Zotlabs\Web\Controller { $photo = substr($photo,0,-2); // If viewing on a high-res screen, attempt to serve a higher resolution image: if ($resolution == 2 && ($cookie_value > 1)) - { $resolution = 1; - } } $r = q("SELECT uid, photo_usage FROM photo WHERE resource_id = '%s' AND imgscale = %d LIMIT 1", @@ -163,10 +163,13 @@ class Photo extends \Zotlabs\Web\Controller { if($exists && $allowed) { $data = dbunescbin($e[0]['content']); + $filesize = $e[0]['filesize']; $mimetype = $e[0]['mimetype']; - if(intval($e[0]['os_storage'])) { + $modified = strtotime($e[0]['edited'] . 'Z'); + if(intval($e[0]['os_storage'])) $streaming = $data; - } + if($e[0]['allow_cid'] != '' || $e[0]['allow_gid'] != '' || $e[0]['deny_gid'] != '' || $e[0]['deny_gid'] != '') + $prvcachecontrol = true; } else { if(! $allowed) { @@ -177,27 +180,40 @@ class Photo extends \Zotlabs\Web\Controller { } } + } else { + http_status_exit(404,'not found'); } } - + + header_remove('Pragma'); + + if($ismodified === gmdate("D, d M Y H:i:s", $modified) . " GMT") { + header_remove('Expires'); + header_remove('Cache-Control'); + header_remove('Set-Cookie'); + http_status_exit(304,'not modified'); + } + if(! isset($data)) { if(isset($resolution)) { switch($resolution) { - case 4: - $data = fetch_image_from_url(z_root() . '/' . get_default_profile_photo(),$mimetype); + $default = get_default_profile_photo(); break; case 5: - $data = fetch_image_from_url(z_root() . '/' . get_default_profile_photo(80),$mimetype); + $default = get_default_profile_photo(80); break; case 6: - $data = fetch_image_from_url(z_root() . '/' . get_default_profile_photo(48),$mimetype); + $default = get_default_profile_photo(48); break; default: killme(); // NOTREACHED break; } + $x = z_fetch_url(z_root() . '/' . $default,true,0,[ 'novalidate' => true ]); + $data = ($x['success'] ? $x['body'] : EMPTY_STR); + $mimetype = 'image/png'; } } @@ -210,15 +226,14 @@ class Photo extends \Zotlabs\Web\Controller { } } + // @FIXME Seems never invoked // Writing in cachefile - if (isset($cachefile) && $cachefile != '') + if (isset($cachefile) && $cachefile != '') { file_put_contents($cachefile, $data); - - if(function_exists('header_remove')) { - header_remove('Pragma'); - header_remove('pragma'); + $modified = filemtime($cachefile); } - + + header("Content-type: " . $mimetype); if($prvcachecontrol) { @@ -240,15 +255,16 @@ class Photo extends \Zotlabs\Web\Controller { // This has performance considerations but we highly recommend you // leave it alone. - $cache = get_config('system','photo_cache_time'); - if(! $cache) - $cache = (3600 * 24); // 1 day - + $cache = get_config('system','photo_cache_time', 86400); // 1 day by default + header("Expires: " . gmdate("D, d M Y H:i:s", time() + $cache) . " GMT"); header("Cache-Control: max-age=" . $cache); } + header("Last-Modified: " . gmdate("D, d M Y H:i:s", $modified) . " GMT"); + header("Content-Length: " . (isset($filesize) ? $filesize : strlen($data))); + // If it's a file resource, stream it. if($streaming && $channel) { diff --git a/Zotlabs/Module/Photos.php b/Zotlabs/Module/Photos.php index 78bfb1f09..03fd8a53d 100644 --- a/Zotlabs/Module/Photos.php +++ b/Zotlabs/Module/Photos.php @@ -263,7 +263,8 @@ class Photos extends \Zotlabs\Web\Controller { $fsize = strlen($data); } - $x = q("update photo set content = '%s', filesize = %d, height = %d, width = %d where resource_id = '%s' and uid = %d and imgscale = 0", + $x = q("update photo set edited = '%s', content = '%s', filesize = %d, height = %d, width = %d where resource_id = '%s' and uid = %d and imgscale = 0", + dbesc(datetime_convert()), dbescbin($data), intval($fsize), intval($height), @@ -278,7 +279,8 @@ class Photos extends \Zotlabs\Web\Controller { $width = $ph->getWidth(); $height = $ph->getHeight(); - $x = q("update photo set content = '%s', height = %d, width = %d where resource_id = '%s' and uid = %d and imgscale = 1", + $x = q("update photo set edited = '%s', content = '%s', height = %d, width = %d where resource_id = '%s' and uid = %d and imgscale = 1", + dbesc(datetime_convert()), dbescbin($ph->imageString()), intval($height), intval($width), @@ -293,7 +295,8 @@ class Photos extends \Zotlabs\Web\Controller { $width = $ph->getWidth(); $height = $ph->getHeight(); - $x = q("update photo set content = '%s', height = %d, width = %d where resource_id = '%s' and uid = %d and imgscale = 2", + $x = q("update photo set edited = '%s', content = '%s', height = %d, width = %d where resource_id = '%s' and uid = %d and imgscale = 2", + dbesc(datetime_convert()), dbescbin($ph->imageString()), intval($height), intval($width), @@ -308,7 +311,8 @@ class Photos extends \Zotlabs\Web\Controller { $width = $ph->getWidth(); $height = $ph->getHeight(); - $x = q("update photo set content = '%s', height = %d, width = %d where resource_id = '%s' and uid = %d and imgscale = 3", + $x = q("update photo set edited = '%s', content = '%s', height = %d, width = %d where resource_id = '%s' and uid = %d and imgscale = 3", + dbesc(datetime_convert()), dbescbin($ph->imageString()), intval($height), intval($width), diff --git a/Zotlabs/Module/Search.php b/Zotlabs/Module/Search.php index e520c671d..838f9d6b9 100644 --- a/Zotlabs/Module/Search.php +++ b/Zotlabs/Module/Search.php @@ -6,7 +6,7 @@ class Search extends \Zotlabs\Web\Controller { function init() { if(x($_REQUEST,'search')) - \App::$data['search'] = $_REQUEST['search']; + \App::$data['search'] = escape_tags($_REQUEST['search']); } @@ -46,12 +46,12 @@ class Search extends \Zotlabs\Web\Controller { if(x(\App::$data,'search')) $search = trim(\App::$data['search']); else - $search = ((x($_GET,'search')) ? trim(rawurldecode($_GET['search'])) : ''); + $search = ((x($_GET,'search')) ? trim(escape_tags(rawurldecode($_GET['search']))) : ''); $tag = false; if(x($_GET,'tag')) { $tag = true; - $search = ((x($_GET,'tag')) ? trim(rawurldecode($_GET['tag'])) : ''); + $search = ((x($_GET,'tag')) ? trim(escape_tags(rawurldecode($_GET['tag']))) : ''); } $static = ((array_key_exists('static',$_REQUEST)) ? intval($_REQUEST['static']) : 0); @@ -227,9 +227,9 @@ class Search extends \Zotlabs\Web\Controller { } if($tag) - $o .= '<h2>' . sprintf( t('Items tagged with: %s'),htmlspecialchars($search, ENT_COMPAT,'UTF-8')) . '</h2>'; + $o .= '<h2>' . sprintf( t('Items tagged with: %s'),$search) . '</h2>'; else - $o .= '<h2>' . sprintf( t('Search results for: %s'),htmlspecialchars($search, ENT_COMPAT,'UTF-8')) . '</h2>'; + $o .= '<h2>' . sprintf( t('Search results for: %s'),$search) . '</h2>'; $o .= conversation($items,'search',$update,'client'); diff --git a/Zotlabs/Update/_1225.php b/Zotlabs/Update/_1225.php new file mode 100644 index 000000000..a7d866154 --- /dev/null +++ b/Zotlabs/Update/_1225.php @@ -0,0 +1,26 @@ +<?php + +namespace Zotlabs\Update; + +class _1225 { + + function run() { + + if(ACTIVE_DBTYPE == DBTYPE_POSTGRES) { + $r1 = q("ALTER TABLE pconfig ADD updated timestamp NOT NULL DEFAULT '0001-01-01 00:00:00' "); + $r2 = q("create index \"pconfig_updated_idx\" on pconfig (\"updated\")"); + + $r = ($r1 && $r2); + } + else { + $r = q("ALTER TABLE `pconfig` ADD `updated` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' , + ADD INDEX `pconfig_updated` (`updated`)"); + } + + if($r) + return UPDATE_SUCCESS; + return UPDATE_FAILED; + + } + +} @@ -50,11 +50,10 @@ require_once('include/attach.php'); require_once('include/bbcode.php'); define ( 'PLATFORM_NAME', 'hubzilla' ); -define ( 'STD_VERSION', '3.8.3' ); +define ( 'STD_VERSION', '3.8.4' ); define ( 'ZOT_REVISION', '6.0a' ); - -define ( 'DB_UPDATE_VERSION', 1224 ); +define ( 'DB_UPDATE_VERSION', 1225 ); define ( 'PROJECT_BASE', __DIR__ ); diff --git a/include/bbcode.php b/include/bbcode.php index 137e25a9c..c5d6ef998 100644 --- a/include/bbcode.php +++ b/include/bbcode.php @@ -1198,24 +1198,24 @@ function bbcode($Text, $options = []) { // Images // [img]pathtoimage[/img] if (strpos($Text,'[/img]') !== false) { - $Text = preg_replace("/\[img\](.*?)\[\/img\]/ism", '<img style="max-width=100%;" src="$1" alt="' . t('Image/photo') . '" />', $Text); + $Text = preg_replace("/\[img\](.*?)\[\/img\]/ism", '<img style="max-width: 100%;" src="$1" alt="' . t('Image/photo') . '" />', $Text); } if (strpos($Text,'[/zmg]') !== false) { - $Text = preg_replace("/\[zmg\](.*?)\[\/zmg\]/ism", '<img class="zrl" style="max-width=100%;" src="$1" alt="' . t('Image/photo') . '" />', $Text); + $Text = preg_replace("/\[zmg\](.*?)\[\/zmg\]/ism", '<img class="zrl" style="max-width: 100%;" src="$1" alt="' . t('Image/photo') . '" />', $Text); } // [img float={left, right}]pathtoimage[/img] if (strpos($Text,'[/img]') !== false) { - $Text = preg_replace("/\[img float=left\](.*?)\[\/img\]/ism", '<img style="max-width=100%;" src="$1" style="float: left;" alt="' . t('Image/photo') . '" />', $Text); + $Text = preg_replace("/\[img float=left\](.*?)\[\/img\]/ism", '<img src="$1" style="max-width: 100%; float: left;" alt="' . t('Image/photo') . '" />', $Text); } if (strpos($Text,'[/img]') !== false) { - $Text = preg_replace("/\[img float=right\](.*?)\[\/img\]/ism", '<img style="max-width=100%;" src="$1" style="float: right;" alt="' . t('Image/photo') . '" />', $Text); + $Text = preg_replace("/\[img float=right\](.*?)\[\/img\]/ism", '<img src="$1" style="max-width: 100%; float: right;" alt="' . t('Image/photo') . '" />', $Text); } if (strpos($Text,'[/zmg]') !== false) { - $Text = preg_replace("/\[zmg float=left\](.*?)\[\/zmg\]/ism", '<img style="max-width=100%;" class="zrl" src="$1" style="float: left;" alt="' . t('Image/photo') . '" />', $Text); + $Text = preg_replace("/\[zmg float=left\](.*?)\[\/zmg\]/ism", '<img class="zrl" src="$1" style="max-width: 100%; float: left;" alt="' . t('Image/photo') . '" />', $Text); } if (strpos($Text,'[/zmg]') !== false) { - $Text = preg_replace("/\[zmg float=right\](.*?)\[\/zmg\]/ism", '<img style="max-width=100%;" class="zrl" src="$1" style="float: right;" alt="' . t('Image/photo') . '" />', $Text); + $Text = preg_replace("/\[zmg float=right\](.*?)\[\/zmg\]/ism", '<img class="zrl" src="$1" style="max-width: 100%; float: right;" alt="' . t('Image/photo') . '" />', $Text); } // [img=widthxheight]pathtoimage[/img] diff --git a/include/config.php b/include/config.php index 0be791715..ec3547a82 100644 --- a/include/config.php +++ b/include/config.php @@ -59,8 +59,8 @@ function set_pconfig($uid, $family, $key, $value) { return Zlib\PConfig::Set($uid,$family,$key,$value); } -function del_pconfig($uid, $family, $key) { - return Zlib\PConfig::Delete($uid,$family,$key); +function del_pconfig($uid, $family, $key, $updated = NULL) { + return Zlib\PConfig::Delete($uid,$family,$key,$updated); } function load_xconfig($xchan) { diff --git a/include/dba/dba_driver.php b/include/dba/dba_driver.php index 9533acc7f..ee0e06a91 100755 --- a/include/dba/dba_driver.php +++ b/include/dba/dba_driver.php @@ -173,14 +173,14 @@ abstract class dba_driver { return false; } - if(strlen($server) && ($server !== 'localhost') && ($server !== '127.0.0.1') && (! strpbrk($server,':;'))) { - if(! z_dns_check($server)) { - $this->error = sprintf( t('Cannot locate DNS info for database server \'%s\''), $server); - $this->connected = false; - $this->db = null; - return false; - } - } + // if(strlen($server) && ($server !== 'localhost') && ($server !== '127.0.0.1') && (! strpbrk($server,':;'))) { + // if(! z_dns_check($server)) { + // $this->error = sprintf( t('Cannot locate DNS info for database server \'%s\''), $server); + // $this->connected = false; + // $this->db = null; + // return false; + // } + // } return true; } @@ -468,7 +468,7 @@ function db_columns($table) { if(ACTIVE_DBTYPE === DBTYPE_POSTGRES) { $r = q("SELECT column_name as field FROM information_schema.columns WHERE table_schema = 'public' AND table_name = '%s'", dbesc($table) - ); + ); if($r) { return ids_to_array($r,'field'); } diff --git a/include/network.php b/include/network.php index f976dcc35..d37da05f7 100644 --- a/include/network.php +++ b/include/network.php @@ -233,7 +233,7 @@ function z_post_url($url, $params, $redirects = 0, $opts = array()) { return $ret; if(! array_key_exists('request_target',$opts)) { - $opts['request_target'] = 'get ' . get_request_string($url); + $opts['request_target'] = 'post ' . get_request_string($url); } @curl_setopt($ch, CURLOPT_HEADER, true); diff --git a/include/photo/photo_driver.php b/include/photo/photo_driver.php index 2e2f5a758..4173d727e 100644 --- a/include/photo/photo_driver.php +++ b/include/photo/photo_driver.php @@ -350,8 +350,7 @@ abstract class photo_driver { $p['allow_gid'] = (($arr['allow_gid']) ? $arr['allow_gid'] : ''); $p['deny_cid'] = (($arr['deny_cid']) ? $arr['deny_cid'] : ''); $p['deny_gid'] = (($arr['deny_gid']) ? $arr['deny_gid'] : ''); - $p['created'] = (($arr['created']) ? $arr['created'] : datetime_convert()); - $p['edited'] = (($arr['edited']) ? $arr['edited'] : $p['created']); + $p['edited'] = (($arr['edited']) ? $arr['edited'] : datetime_convert()); $p['title'] = (($arr['title']) ? $arr['title'] : ''); $p['description'] = (($arr['description']) ? $arr['description'] : ''); $p['photo_usage'] = intval($arr['photo_usage']); @@ -365,13 +364,15 @@ abstract class photo_driver { if(! intval($p['imgscale'])) logger('save: ' . print_r($arr,true), LOGGER_DATA); - $x = q("select id from photo where resource_id = '%s' and uid = %d and xchan = '%s' and imgscale = %d limit 1", + $x = q("select id, created from photo where resource_id = '%s' and uid = %d and xchan = '%s' and imgscale = %d limit 1", dbesc($p['resource_id']), intval($p['uid']), dbesc($p['xchan']), intval($p['imgscale']) ); + if($x) { + $p['created'] = (($x['created']) ? $x['created'] : $p['edited']); $r = q("UPDATE photo set aid = %d, uid = %d, @@ -427,6 +428,7 @@ abstract class photo_driver { ); } else { + $p['created'] = (($arr['created']) ? $arr['created'] : $p['edited']); $r = q("INSERT INTO photo ( aid, uid, xchan, resource_id, created, edited, filename, mimetype, album, height, width, content, os_storage, filesize, imgscale, photo_usage, title, description, os_path, display_path, allow_cid, allow_gid, deny_cid, deny_gid ) VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, '%s', %d, %d, %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s' )", @@ -464,11 +466,6 @@ abstract class photo_driver { - - - - - /** * Guess image mimetype from filename or from Content-Type header * @@ -485,11 +482,11 @@ function guess_image_type($filename, $headers = '') { $h = explode("\n",$headers); foreach ($h as $l) { list($k,$v) = array_map("trim", explode(":", trim($l), 2)); - $hdrs[$k] = $v; + $hdrs[strtolower($k)] = $v; } logger('Curl headers: '.var_export($hdrs, true), LOGGER_DEBUG); - if (array_key_exists('Content-Type', $hdrs)) - $type = $hdrs['Content-Type']; + if (array_key_exists('content-type', $hdrs)) + $type = $hdrs['content-type']; } if (is_null($type)){ @@ -570,122 +567,166 @@ function delete_thing_photo($url,$ob_hash) { -function import_xchan_photo($photo,$xchan,$thing = false) { - - $flags = (($thing) ? PHOTO_THING : PHOTO_XCHAN); - $album = (($thing) ? 'Things' : 'Contact Photos'); - - logger('import_xchan_photo: updating channel photo from ' . $photo . ' for ' . $xchan, LOGGER_DEBUG); - - if($thing) - $hash = photo_new_resource(); - else { - $r = q("select resource_id from photo where xchan = '%s' and photo_usage = %d and imgscale = 4 limit 1", - dbesc($xchan), - intval(PHOTO_XCHAN) - ); - if($r) { - $hash = $r[0]['resource_id']; - } - else { - $hash = photo_new_resource(); - } - } - - $photo_failure = false; - $img_str = ''; - - if($photo) { - $filename = basename($photo); - - $result = z_fetch_url($photo,true); - - if($result['success']) { - $img_str = $result['body']; - $type = guess_image_type($photo, $result['header']); - - $h = explode("\n",$result['header']); - if($h) { - foreach($h as $hl) { - if(stristr($hl,'content-type:')) { - if(! stristr($hl,'image/')) { - $photo_failure = true; - } - } - } - } - } - } - else { - $photo_failure = true; - } - - if(! $photo_failure) { - $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(300,($margin / 2),0,$height,$height); - } - elseif(($height / $width) > 1.2) { - // crop out the bottom - $margin = $height - $width; - $img->cropImage(300,0,0,$width,$width); - - } - else { - $img->scaleImageSquare(300); - } - - } - else - $photo_failure = true; - - $p = array('xchan' => $xchan,'resource_id' => $hash, 'filename' => basename($photo), 'album' => $album, 'photo_usage' => $flags, 'imgscale' => 4); - - $r = $img->save($p); - - if($r === false) - $photo_failure = true; - - $img->scaleImage(80); - $p['imgscale'] = 5; - - $r = $img->save($p); - - if($r === false) - $photo_failure = true; - - $img->scaleImage(48); - $p['imgscale'] = 6; - - $r = $img->save($p); - - if($r === false) - $photo_failure = true; - - $photo = z_root() . '/photo/' . $hash . '-4'; - $thumb = z_root() . '/photo/' . $hash . '-5'; - $micro = z_root() . '/photo/' . $hash . '-6'; - } - else { - logger('import_xchan_photo: invalid image from ' . $photo); - $photo_failure = true; - } - } - if($photo_failure) { - $photo = z_root() . '/' . get_default_profile_photo(); - $thumb = z_root() . '/' . get_default_profile_photo(80); - $micro = z_root() . '/' . get_default_profile_photo(48); - $type = 'image/png'; - } +/** + * @brief fetches an photo from external site and prepares its miniatures. + * + * @param string $photo + * external URL to fetch base image + * @param string $xchan + * channel unique hash + * @param boolean $thing + * TRUE if this is a thing URL + * @param boolean $force + * TRUE if ignore image modification date check (force fetch) + * + * @return array of results + * * \e string \b 0 => local URL to full image + * * \e string \b 1 => local URL to standard thumbnail + * * \e string \b 2 => local URL to micro thumbnail + * * \e string \b 3 => image type + * * \e boolean \b 4 => TRUE if fetch failure + * * \e string \b 5 => modification date + */ - return(array($photo,$thumb,$micro,$type,$photo_failure)); +function import_xchan_photo($photo,$xchan,$thing = false,$force = false) { + + $modified = ''; + + $flags = (($thing) ? PHOTO_THING : PHOTO_XCHAN); + $album = (($thing) ? 'Things' : 'Contact Photos'); + + logger('import_xchan_photo: updating channel photo from ' . $photo . ' for ' . $xchan, LOGGER_DEBUG); + + if($thing) { + $hash = photo_new_resource(); + } + else { + $r = q("select resource_id, edited, mimetype from photo where xchan = '%s' and photo_usage = %d and imgscale = 4 limit 1", + dbesc($xchan), + intval(PHOTO_XCHAN) + ); + if($r) { + $hash = $r[0]['resource_id']; + $modified = $r[0]['edited']; + $type = $r[0]['mimetype']; + } + else { + $hash = photo_new_resource(); + } + } + + $photo_failure = false; + $img_str = ''; + + if($photo) { + $filename = basename($photo); + + if($force || $modified == '') { + $result = z_fetch_url($photo,true); + } + else { + $h = array('headers' => array("If-Modified-Since: " . gmdate("D, d M Y H:i:s", strtotime($modified . "Z")) . " GMT")); + $result = z_fetch_url($photo,true,0,$h); + } + + if($result['success']) { + $img_str = $result['body']; + $type = guess_image_type($photo, $result['header']); + $modified = gmdate('Y-m-d H:i:s', (preg_match('/last-modified: (.+) \S+/i', $result['header'], $o) ? strtotime($o[1] . 'Z') : time())); + + if(is_null($type)) + $photo_failure = true; + } + elseif($result['return_code'] == 304) { + $photo = z_root() . '/photo/' . $hash . '-4'; + $thumb = z_root() . '/photo/' . $hash . '-5'; + $micro = z_root() . '/photo/' . $hash . '-6'; + } + else { + $photo_failure = true; + } + + } + else + $photo_failure = true; + + if(! $photo_failure && $result['return_code'] != 304) { + $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(300,($margin / 2),0,$height,$height); + } + elseif(($height / $width) > 1.2) { + // crop out the bottom + $margin = $height - $width; + $img->cropImage(300,0,0,$width,$width); + + } + else { + $img->scaleImageSquare(300); + } + + } + else + $photo_failure = true; + + $p = array( + 'xchan' => $xchan, + 'resource_id' => $hash, + 'filename' => basename($photo), + 'album' => $album, + 'photo_usage' => $flags, + 'imgscale' => 4, + 'edited' => $modified + ); + + $r = $img->save($p); + + if($r === false) + $photo_failure = true; + + $img->scaleImage(80); + $p['imgscale'] = 5; + + $r = $img->save($p); + + if($r === false) + $photo_failure = true; + + $img->scaleImage(48); + $p['imgscale'] = 6; + + $r = $img->save($p); + + if($r === false) + $photo_failure = true; + + $photo = z_root() . '/photo/' . $hash . '-4'; + $thumb = z_root() . '/photo/' . $hash . '-5'; + $micro = z_root() . '/photo/' . $hash . '-6'; + } + else { + logger('import_xchan_photo: invalid image from ' . $photo); + $photo_failure = true; + } + } + if($photo_failure) { + $default = get_default_profile_photo(); + $photo = z_root() . '/' . $default; + $thumb = z_root() . '/' . get_default_profile_photo(80); + $micro = z_root() . '/' . get_default_profile_photo(48); + $type = 'image/png'; + $modified = gmdate('Y-m-d H:i:s', filemtime($default)); + } + + logger('HTTP code: ' . $result['return_code'] . '; modified: ' . $modified . '; failure: ' . ($photo_failure ? 'yes' : 'no') . '; URL: ' . $photo, LOGGER_DEBUG); + return(array($photo,$thumb,$micro,$type,$photo_failure,$modified)); } @@ -700,16 +741,8 @@ function import_channel_photo_from_url($photo,$aid,$uid) { $img_str = $result['body']; $type = guess_image_type($photo, $result['header']); - $h = explode("\n",$result['header']); - if($h) { - foreach($h as $hl) { - if(stristr($hl,'content-type:')) { - if(! stristr($hl,'image/')) { - $photo_failure = true; - } - } - } - } + if(is_null($type)) + $photo_failure = true; } } else { diff --git a/include/photos.php b/include/photos.php index d0c5f77fc..d5553b495 100644 --- a/include/photos.php +++ b/include/photos.php @@ -1011,23 +1011,3 @@ function profile_photo_set_profile_perms($uid, $profileid = 0) { } } } - -function fetch_image_from_url($url,&$mimetype) { - - $redirects = 0; - $x = z_fetch_url($url,true,$redirects,[ 'novalidate' => true ]); - if($x['success']) { - $hdrs = []; - $h = explode("\n",$x['header']); - foreach ($h as $l) { - list($k,$v) = array_map("trim", explode(":", trim($l), 2)); - $hdrs[strtolower($k)] = $v; - } - if (array_key_exists('content-type', $hdrs)) - $mimetype = $hdrs['content-type']; - - return $x['body']; - } - - return EMPTY_STR; -}
\ No newline at end of file diff --git a/include/text.php b/include/text.php index 1d884593f..076c98407 100644 --- a/include/text.php +++ b/include/text.php @@ -634,6 +634,19 @@ function attribute_contains($attr, $s) { } /** + * @brief Log to syslog + * + * @param string $msg Message to log + * @param int $priority - compatible with syslog + */ +function hz_syslog($msg, $priority = LOG_INFO) { + openlog("hz-log", LOG_PID | LOG_PERROR, LOG_LOCAL0); + syslog($priority, $msg); + closelog(); +} + + +/** * @brief Logging function for Hubzilla. * * Logging output is configured through Hubzilla's system config. The log file @@ -3219,8 +3232,16 @@ function create_table_from_array($table, $arr, $binary_fields = []) { if(! ($arr && $table)) return false; + $columns = db_columns($table); + $clean = []; foreach($arr as $k => $v) { + + if(! in_array($k,$columns)) { + continue; + } + + $matches = false; if(preg_match('/([^a-zA-Z0-9\-\_\.])/',$k,$matches)) { return false; diff --git a/include/xchan.php b/include/xchan.php index aad56063f..eb5f1b4a3 100644 --- a/include/xchan.php +++ b/include/xchan.php @@ -1,5 +1,7 @@ <?php +use Zotlabs\Zot6\HTTPSig; + function xchan_store_lowlevel($arr) { @@ -39,6 +41,13 @@ function xchan_store_lowlevel($arr) { function xchan_store($arr) { + $update_photo = false; + $update_name = false; + + if(! ($arr['guid'] || $arr['hash'])) { + $arr = json_decode(file_get_contents('php://input'),true); + } + logger('xchan_store: ' . print_r($arr,true)); if(! $arr['hash']) @@ -49,57 +58,90 @@ function xchan_store($arr) { $r = q("select * from xchan where xchan_hash = '%s' limit 1", dbesc($arr['hash']) ); - if($r) - return true; - - if(! $arr['network']) - $arr['network'] = 'unknown'; - if(! $arr['name']) - $arr['name'] = 'unknown'; - if(! $arr['url']) - $arr['url'] = z_root(); - if(! $arr['photo']) - $arr['photo'] = z_root() . '/' . get_default_profile_photo(); - - - if($arr['network'] === 'zot') { - if((! $arr['key']) || (! rsa_verify($arr['guid'],base64url_decode($arr['guid_sig']),$arr['key']))) { - logger('Unable to verify signature for ' . $arr['hash']); - return false; + if(! $r) { + + $update_photo = true; + + if(! $arr['network']) + $arr['network'] = 'unknown'; + if(! $arr['name']) + $arr['name'] = 'unknown'; + if(! $arr['url']) + $arr['url'] = z_root(); + if(! $arr['photo']) + $arr['photo'] = z_root() . '/' . get_default_profile_photo(); + + if($arr['network'] === 'zot6') { + if((! $arr['key']) || (! Libzot::verify($arr['id'],$arr['id_sig'],$arr['key']))) { + logger('Unable to verify signature for ' . $arr['hash']); + return false; + } } - } - $x = []; - foreach($arr as $k => $v) { - if($k === 'key') { - $x['xchan_pubkey'] = $v; - continue; - } - if($k === 'photo') { - continue; + if($arr['network'] === 'zot') { + if((! $arr['key']) || (! rsa_verify($arr['guid'],base64url_decode($arr['guid_sig']),$arr['key']))) { + logger('Unable to verify signature for ' . $arr['hash']); + return false; + } } - - $x['xchan_' . $k] = $v; - } - $x['xchan_name_date'] = datetime_convert(); + $columns = db_columns('xchan'); + + $x = []; + foreach($arr as $k => $v) { + if($k === 'key') { + $x['xchan_pubkey'] = HTTPSig::convertKey(escape_tags($v));; + continue; + } + if($k === 'photo') { + continue; + } + + if(in_array($columns,'xchan_' . $k)) + $x['xchan_' . $k] = escape_tags($v); + } - $r = xchan_store_lowlevel($x); + $x['xchan_name_date'] = datetime_convert(); + $x['xchan_photo_date'] = datetime_convert(); + $x['xchan_system'] = false; - if(! $r) - return $r; - - $photos = import_xchan_photo($arr['photo'],$arr['hash']); - $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_hash = '%s'", - dbesc(datetime_convert()), - dbesc($photos[0]), - dbesc($photos[1]), - dbesc($photos[2]), - dbesc($photos[3]), - dbesc($arr['hash']) - ); - return $r; + $result = xchan_store_lowlevel($x); + + if(! $result) + return $result; + } + else { + if($r[0]['network'] === 'zot6') { + return true; + } + if($r[0]['xchan_photo_date'] < datetime_convert('UTC','UTC',$arr['photo_date'])) { + $update_photo = true; + } + if($r[0]['xchan_name_date'] < datetime_convert('UTC','UTC',$arr['name_date'])) { + $update_name = true; + } + } + + if($update_photo && $arr['photo']) { + $photos = import_xchan_photo($arr['photo'],$arr['hash']); + $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_hash = '%s'", + dbesc(datetime_convert()), + dbesc($photos[0]), + dbesc($photos[1]), + dbesc($photos[2]), + dbesc($photos[3]), + dbesc($arr['hash']) + ); + } + if($update_name && $arr['name']) { + $x = q("update xchan set xchan_name = '%s', xchan_name_date = '%s' where xchan_hash = '%s'", + dbesc(escape_tags($arr['name'])), + dbesc(datetime_convert()), + dbesc($arr['hash']) + ); + } + return true; } diff --git a/include/zot.php b/include/zot.php index 1a632cf87..49fc89e33 100644 --- a/include/zot.php +++ b/include/zot.php @@ -3507,8 +3507,41 @@ function process_channel_sync_delivery($sender, $arr, $deliveries) { if(array_key_exists('config',$arr) && is_array($arr['config']) && count($arr['config'])) { foreach($arr['config'] as $cat => $k) { - foreach($arr['config'][$cat] as $k => $v) - set_pconfig($channel['channel_id'],$cat,$k,$v); + + $pconfig_updated = []; + $pconfig_del = []; + + foreach($arr['config'][$cat] as $k => $v) { + + if (strpos($k,'pcfgud:')===0) { + + $realk = substr($k,7); + $pconfig_updated[$realk] = $v; + unset($arr['config'][$cat][$k]); + + } + + if (strpos($k,'pcfgdel:')===0) { + $realk = substr($k,8); + $pconfig_del[$realk] = datetime_convert(); + unset($arr['config'][$cat][$k]); + } + } + + foreach($arr['config'][$cat] as $k => $v) { + + if (!isset($pconfig_updated[$k])) { + $pconfig_updated[$k] = NULL; + } + + set_pconfig($channel['channel_id'],$cat,$k,$v,$pconfig_updated[$k]); + + } + + foreach($pconfig_del as $k => $updated) { + del_pconfig($channel['channel_id'],$cat,$k,$updated); + } + } } diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php index d55227eb1..ce0232306 100644 --- a/vendor/composer/autoload_static.php +++ b/vendor/composer/autoload_static.php @@ -1505,6 +1505,7 @@ class ComposerStaticInit7b34d7e50a62201ec5d5e526a5b8b35d 'Zotlabs\\Update\\_1222' => __DIR__ . '/../..' . '/Zotlabs/Update/_1222.php', 'Zotlabs\\Update\\_1223' => __DIR__ . '/../..' . '/Zotlabs/Update/_1223.php', 'Zotlabs\\Update\\_1224' => __DIR__ . '/../..' . '/Zotlabs/Update/_1224.php', + 'Zotlabs\\Update\\_1225' => __DIR__ . '/../..' . '/Zotlabs/Update/_1225.php', 'Zotlabs\\Web\\CheckJS' => __DIR__ . '/../..' . '/Zotlabs/Web/CheckJS.php', 'Zotlabs\\Web\\Controller' => __DIR__ . '/../..' . '/Zotlabs/Web/Controller.php', 'Zotlabs\\Web\\HTTPHeaders' => __DIR__ . '/../..' . '/Zotlabs/Web/HTTPHeaders.php', diff --git a/view/de-de/hmessages.po b/view/de-de/hmessages.po index 8af9dce6b..4eaec3629 100644 --- a/view/de-de/hmessages.po +++ b/view/de-de/hmessages.po @@ -35,7 +35,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: de\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Plural-Forms: nplurals=2; plural=(n != 1 ? 1 : 0);\n" #: ../../Zotlabs/Access/Permissions.php:56 msgid "Can view my channel stream and posts" diff --git a/view/es-es/hmessages.po b/view/es-es/hmessages.po index f2c1ccf79..3b42394b1 100644 --- a/view/es-es/hmessages.po +++ b/view/es-es/hmessages.po @@ -22,7 +22,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: es_ES\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Plural-Forms: nplurals=2; plural=(n != 1 ? 1 : 0);\n" #: ../../Zotlabs/Access/Permissions.php:56 msgid "Can view my channel stream and posts" diff --git a/view/es-es/hstrings.php b/view/es-es/hstrings.php index 2024504a1..a459e10dd 100644 --- a/view/es-es/hstrings.php +++ b/view/es-es/hstrings.php @@ -2,10 +2,10 @@ if(! function_exists("string_plural_select_es_es")) { function string_plural_select_es_es($n){ - return ($n != 1);; + return ($n != 1 ? 1 : 0); }} App::$rtl = 0; -App::$strings["plural_function_code"] = "(n != 1)"; +App::$strings["plural_function_code"] = "(n != 1 ? 1 : 0)"; App::$strings["Can view my channel stream and posts"] = "Pueden verse la actividad y publicaciones de mi canal"; App::$strings["Can send me their channel stream and posts"] = "Se me pueden enviar entradas y contenido de un canal"; App::$strings["Can view my default channel profile"] = "Puede verse mi perfil de canal predeterminado."; diff --git a/view/fr/hmessages.po b/view/fr/hmessages.po index c5263c2df..0c89da139 100644 --- a/view/fr/hmessages.po +++ b/view/fr/hmessages.po @@ -22,7 +22,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: fr\n" -"Plural-Forms: nplurals=2; plural=(n > 1);\n" +"Plural-Forms: nplurals=2; plural=(n > 1 ? 1 : 0);\n" #: ../../Zotlabs/Access/Permissions.php:56 msgid "Can view my channel stream and posts" diff --git a/view/he/hmessages.po b/view/he/hmessages.po index 991b7f64f..837dfabd0 100644 --- a/view/he/hmessages.po +++ b/view/he/hmessages.po @@ -17,7 +17,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: he\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Plural-Forms: nplurals=2; plural=(n != 1 ? 1 : 0);\n" #: ../../Zotlabs/Storage/Browser.php:107 ../../Zotlabs/Storage/Browser.php:239 msgid "parent" diff --git a/view/it/hmessages.po b/view/it/hmessages.po index 94911bc20..8c1dc08d7 100644 --- a/view/it/hmessages.po +++ b/view/it/hmessages.po @@ -18,7 +18,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: it\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Plural-Forms: nplurals=2; plural=(n != 1 ? 1 : 0);\n" #: ../../Zotlabs/Access/Permissions.php:56 msgid "Can view my channel stream and posts" diff --git a/view/nb-no/hmessages.po b/view/nb-no/hmessages.po index bcb926d90..0f7f2e8c5 100644 --- a/view/nb-no/hmessages.po +++ b/view/nb-no/hmessages.po @@ -16,7 +16,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: nb_NO\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Plural-Forms: nplurals=2; plural=(n != 1 ? 1 : 0);\n" #: ../../Zotlabs/Storage/Browser.php:107 ../../Zotlabs/Storage/Browser.php:239 msgid "parent" diff --git a/view/nl/hmessages.po b/view/nl/hmessages.po index c98121122..c672e534e 100644 --- a/view/nl/hmessages.po +++ b/view/nl/hmessages.po @@ -18,7 +18,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: nl\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Plural-Forms: nplurals=2; plural=(n != 1 ? 1 : 0);\n" #: ../../Zotlabs/Access/Permissions.php:56 msgid "Can view my channel stream and posts" diff --git a/view/pt-br/hmessages.po b/view/pt-br/hmessages.po index 029642f9b..2645cc9bd 100644 --- a/view/pt-br/hmessages.po +++ b/view/pt-br/hmessages.po @@ -25,7 +25,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: pt_BR\n" -"Plural-Forms: nplurals=2; plural=(n > 1);\n" +"Plural-Forms: nplurals=2; plural=(n > 1 ? 1 : 0);\n" #: ../../include/dba/dba_driver.php:50 #, php-format diff --git a/view/sv/hmessages.po b/view/sv/hmessages.po index 4486a8630..249dbd8be 100644 --- a/view/sv/hmessages.po +++ b/view/sv/hmessages.po @@ -17,7 +17,7 @@ msgstr "" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "Language: sv\n" -"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"Plural-Forms: nplurals=2; plural=(n != 1 ? 1 : 0);\n" #: ../../include/dba/dba_driver.php:142 #, php-format diff --git a/view/theme/redbasic/css/style.css b/view/theme/redbasic/css/style.css index ddf3d1295..f2c1b7a48 100644 --- a/view/theme/redbasic/css/style.css +++ b/view/theme/redbasic/css/style.css @@ -872,10 +872,6 @@ div.jGrowl div.jGrowl-notification { margin-left: 20px; } -.reshared-content img { - width: 100%; -} - .shared_header img { border-radius: $radius; margin-right: 10px; @@ -884,21 +880,19 @@ div.jGrowl div.jGrowl-notification { .tag1 { font-size : 0.9em !important; } + .tag2 { font-size : 1.0em !important; } - .tag3 { font-size : 1.1em !important; } - .tag4 { font-size : 1.2em !important; } - .tag5 { font-size : 1.3em !important; } @@ -918,12 +912,10 @@ div.jGrowl div.jGrowl-notification { font-size : 1.6em !important; } - .tag9 { font-size : 1.7em !important; } - .tag10 { font-size : 1.8em !important; } diff --git a/view/theme/redbasic/schema/Focus-Light.php b/view/theme/redbasic/schema/Focus-Light.php index 14ee130d9..8a542d1b8 100644 --- a/view/theme/redbasic/schema/Focus-Light.php +++ b/view/theme/redbasic/schema/Focus-Light.php @@ -3,10 +3,11 @@ if (! $nav_bg) $nav_bg = "#f8f9fa"; if (! $nav_icon_colour) - $nav_icon_colour = "rgba(0, 0, 0, 0.5);"; + $nav_icon_colour = "rgba(0, 0, 0, 0.5)"; if (! $nav_active_icon_colour) $nav_active_icon_colour = "rgba(0, 0, 0, 0.7)"; if (! $radius) $radius = "4px"; if (! $banner_colour) $banner_colour = "rgba(0, 0, 0, 0.7)"; + diff --git a/view/tpl/activity_filter_widget.tpl b/view/tpl/activity_filter_widget.tpl index 1eb11c10f..7d10100ba 100644 --- a/view/tpl/activity_filter_widget.tpl +++ b/view/tpl/activity_filter_widget.tpl @@ -17,7 +17,7 @@ </form> </div> <script> - $("#cid-filter").name_autocomplete(baseurl + '/acl', 'a', true, function(data) { + $("#cid-filter").contact_autocomplete(baseurl + '/acl', 'a', true, function(data) { $("#cid").val(data.id); }); </script> |