Merge remote-tracking branch 'mike/master' into dev

This commit is contained in:
Mario Vavti 2018-08-09 11:07:38 +02:00
commit 5c5a808290
23 changed files with 8237 additions and 1718 deletions

1725
Zotlabs/Lib/Activity.php Normal file

File diff suppressed because it is too large Load diff

View file

@ -927,10 +927,11 @@ class Apps {
$darray['app_requires'] = ((x($arr,'requires')) ? escape_tags($arr['requires']) : '');
$darray['app_system'] = ((x($arr,'system')) ? intval($arr['system']) : 0);
$darray['app_deleted'] = ((x($arr,'deleted')) ? intval($arr['deleted']) : 0);
$darray['app_options'] = ((x($arr,'options')) ? intval($arr['options']) : 0);
$created = datetime_convert();
$r = q("insert into app ( app_id, app_sig, app_author, app_name, app_desc, app_url, app_photo, app_version, app_channel, app_addr, app_price, app_page, app_requires, app_created, app_edited, app_system, app_plugin, app_deleted ) values ( '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, '%s', '%s', '%s', '%s', '%s', '%s', %d, '%s', %d )",
$r = q("insert into app ( app_id, app_sig, app_author, app_name, app_desc, app_url, app_photo, app_version, app_channel, app_addr, app_price, app_page, app_requires, app_created, app_edited, app_system, app_plugin, app_deleted, app_options ) values ( '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, '%s', '%s', '%s', '%s', '%s', '%s', %d, '%s', %d, %d )",
dbesc($darray['app_id']),
dbesc($darray['app_sig']),
dbesc($darray['app_author']),
@ -948,7 +949,8 @@ class Apps {
dbesc($created),
intval($darray['app_system']),
dbesc($darray['app_plugin']),
intval($darray['app_deleted'])
intval($darray['app_deleted']),
intval($darray['app_options'])
);
if($r) {
@ -1009,10 +1011,11 @@ class Apps {
$darray['app_requires'] = ((x($arr,'requires')) ? escape_tags($arr['requires']) : '');
$darray['app_system'] = ((x($arr,'system')) ? intval($arr['system']) : 0);
$darray['app_deleted'] = ((x($arr,'deleted')) ? intval($arr['deleted']) : 0);
$darray['app_options'] = ((x($arr,'options')) ? intval($arr['options']) : 0);
$edited = datetime_convert();
$r = q("update app set app_sig = '%s', app_author = '%s', app_name = '%s', app_desc = '%s', app_url = '%s', app_photo = '%s', app_version = '%s', app_addr = '%s', app_price = '%s', app_page = '%s', app_requires = '%s', app_edited = '%s', app_system = %d, app_plugin = '%s', app_deleted = %d where app_id = '%s' and app_channel = %d",
$r = q("update app set app_sig = '%s', app_author = '%s', app_name = '%s', app_desc = '%s', app_url = '%s', app_photo = '%s', app_version = '%s', app_addr = '%s', app_price = '%s', app_page = '%s', app_requires = '%s', app_edited = '%s', app_system = %d, app_plugin = '%s', app_deleted = %d, app_options = %d where app_id = '%s' and app_channel = %d",
dbesc($darray['app_sig']),
dbesc($darray['app_author']),
dbesc($darray['app_name']),
@ -1028,6 +1031,7 @@ class Apps {
intval($darray['app_system']),
dbesc($darray['app_plugin']),
intval($darray['app_deleted']),
intval($darray['app_options']),
dbesc($darray['app_id']),
intval($darray['app_channel'])
);
@ -1117,6 +1121,9 @@ class Apps {
if($app['app_system'])
$ret['system'] = $app['app_system'];
if($app['app_options'])
$ret['options'] = $app['app_options'];
if($app['app_plugin'])
$ret['plugin'] = trim($app['app_plugin']);

405
Zotlabs/Lib/Group.php Normal file
View file

@ -0,0 +1,405 @@
<?php
namespace Zotlabs\Lib;
use Zotlabs\Lib\Libsync;
class Group {
static function add($uid,$name,$public = 0) {
$ret = false;
if(x($uid) && x($name)) {
$r = self::byname($uid,$name); // check for dups
if($r !== false) {
// This could be a problem.
// Let's assume we've just created a group which we once deleted
// all the old members are gone, but the group remains so we don't break any security
// 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 groups WHERE id = %d LIMIT 1",
intval($r)
);
if(($z) && $z[0]['deleted']) {
q('UPDATE groups SET deleted = 0 WHERE id = %d', intval($z[0]['id']));
notice( t('A deleted group with this name was revived. Existing item permissions <strong>may</strong> apply to this group and any future members. If this is not what you intended, please create another group with a different name.') . EOL);
}
return true;
}
do {
$dups = false;
$hash = random_string(32) . str_replace(['<','>'],['.','.'], $name);
$r = q("SELECT id FROM groups WHERE hash = '%s' LIMIT 1", dbesc($hash));
if($r)
$dups = true;
} while($dups == true);
$r = q("INSERT INTO groups ( hash, uid, visible, gname )
VALUES( '%s', %d, %d, '%s' ) ",
dbesc($hash),
intval($uid),
intval($public),
dbesc($name)
);
$ret = $r;
}
Libsync::build_sync_packet($uid,null,true);
return $ret;
}
static function remove($uid,$name) {
$ret = false;
if(x($uid) && x($name)) {
$r = q("SELECT id, hash FROM groups WHERE uid = %d AND gname = '%s' LIMIT 1",
intval($uid),
dbesc($name)
);
if($r) {
$group_id = $r[0]['id'];
$group_hash = $r[0]['hash'];
}
if(! $group_id)
return false;
// remove group from default posting lists
$r = q("SELECT channel_default_group, channel_allow_gid, channel_deny_gid FROM channel WHERE channel_id = %d LIMIT 1",
intval($uid)
);
if($r) {
$user_info = $r[0];
$change = false;
if($user_info['channel_default_group'] == $group_hash) {
$user_info['channel_default_group'] = '';
$change = true;
}
if(strpos($user_info['channel_allow_gid'], '<' . $group_hash . '>') !== false) {
$user_info['channel_allow_gid'] = str_replace('<' . $group_hash . '>', '', $user_info['channel_allow_gid']);
$change = true;
}
if(strpos($user_info['channel_deny_gid'], '<' . $group_hash . '>') !== false) {
$user_info['channel_deny_gid'] = str_replace('<' . $group_hash . '>', '', $user_info['channel_deny_gid']);
$change = true;
}
if($change) {
q("UPDATE channel SET channel_default_group = '%s', channel_allow_gid = '%s', channel_deny_gid = '%s'
WHERE channel_id = %d",
intval($user_info['channel_default_group']),
dbesc($user_info['channel_allow_gid']),
dbesc($user_info['channel_deny_gid']),
intval($uid)
);
}
}
// remove all members
$r = q("DELETE FROM group_member WHERE uid = %d AND gid = %d ",
intval($uid),
intval($group_id)
);
// remove group
$r = q("UPDATE groups SET deleted = 1 WHERE uid = %d AND gname = '%s'",
intval($uid),
dbesc($name)
);
$ret = $r;
}
Libsync::build_sync_packet($uid,null,true);
return $ret;
}
static function byname($uid,$name) {
if((! $uid) || (! strlen($name)))
return false;
$r = q("SELECT * FROM groups WHERE uid = %d AND gname = '%s' LIMIT 1",
intval($uid),
dbesc($name)
);
if($r)
return $r[0]['id'];
return false;
}
static function rec_byhash($uid,$hash) {
if((! $uid) || (! strlen($hash)))
return false;
$r = q("SELECT * FROM groups WHERE uid = %d AND hash = '%s' LIMIT 1",
intval($uid),
dbesc($hash)
);
if($r)
return $r[0];
return false;
}
static function member_remove($uid,$name,$member) {
$gid = self::byname($uid,$name);
if(! $gid)
return false;
if(! ( $uid && $gid && $member))
return false;
$r = q("DELETE FROM group_member WHERE uid = %d AND gid = %d AND xchan = '%s' ",
intval($uid),
intval($gid),
dbesc($member)
);
Libsync::build_sync_packet($uid,null,true);
return $r;
}
static function member_add($uid,$name,$member,$gid = 0) {
if(! $gid)
$gid = self::byname($uid,$name);
if((! $gid) || (! $uid) || (! $member))
return false;
$r = q("SELECT * FROM group_member WHERE uid = %d AND gid = %d AND xchan = '%s' LIMIT 1",
intval($uid),
intval($gid),
dbesc($member)
);
if($r)
return true; // You might question this, but
// we indicate success because the group member was in fact created
// -- It was just created at another time
if(! $r)
$r = q("INSERT INTO group_member (uid, gid, xchan)
VALUES( %d, %d, '%s' ) ",
intval($uid),
intval($gid),
dbesc($member)
);
Libsync::build_sync_packet($uid,null,true);
return $r;
}
static function members($gid) {
$ret = array();
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 xchan_deleted = 0 and abook_self = 0 and abook_blocked = 0 and abook_pending = 0 ORDER BY xchan_name ASC ",
intval($gid),
intval(local_channel()),
intval(local_channel())
);
if($r)
$ret = $r;
}
return $ret;
}
static function members_xchan($gid) {
$ret = [];
if(intval($gid)) {
$r = q("SELECT xchan FROM group_member WHERE gid = %d AND uid = %d",
intval($gid),
intval(local_channel())
);
if($r) {
foreach($r as $rr) {
$ret[] = $rr['xchan'];
}
}
}
return $ret;
}
static function members_profile_xchan($uid,$gid) {
$ret = [];
if(intval($gid)) {
$r = q("SELECT abook_xchan as xchan from abook left join profile on abook_profile = profile_guid where profile.id = %d and profile.uid = %d",
intval($gid),
intval($uid)
);
if($r) {
foreach($r as $rr) {
$ret[] = $rr['xchan'];
}
}
}
return $ret;
}
static function select($uid,$group = '') {
$grps = [];
$o = '';
$r = q("SELECT * FROM groups WHERE deleted = 0 AND uid = %d ORDER BY gname ASC",
intval($uid)
);
$grps[] = array('name' => '', 'hash' => '0', 'selected' => '');
if($r) {
foreach($r as $rr) {
$grps[] = array('name' => $rr['gname'], 'id' => $rr['hash'], 'selected' => (($group == $rr['hash']) ? 'true' : ''));
}
}
logger('select: ' . print_r($grps,true), LOGGER_DATA);
$o = replace_macros(get_markup_template('group_selection.tpl'), array(
'$label' => t('Add new connections to this privacy group'),
'$groups' => $grps
));
return $o;
}
static function widget($every="connections",$each="group",$edit = false, $group_id = 0, $cid = '',$mode = 1) {
$o = '';
if(! (local_channel() && feature_enabled(local_channel(),'groups'))) {
return '';
}
$groups = array();
$r = q("SELECT * FROM groups WHERE deleted = 0 AND uid = %d ORDER BY gname ASC",
intval($_SESSION['uid'])
);
$member_of = array();
if($cid) {
$member_of = self::containing(local_channel(),$cid);
}
if($r) {
foreach($r as $rr) {
$selected = (($group_id == $rr['id']) ? ' group-selected' : '');
if ($edit) {
$groupedit = [ 'href' => "group/".$rr['id'], 'title' => t('edit') ];
}
else {
$groupedit = null;
}
$groups[] = [
'id' => $rr['id'],
'enc_cid' => base64url_encode($cid),
'cid' => $cid,
'text' => $rr['gname'],
'selected' => $selected,
'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),
];
}
}
$tpl = get_markup_template("group_side.tpl");
$o = replace_macros($tpl, array(
'$title' => t('Privacy Groups'),
'$edittext' => t('Edit group'),
'$createtext' => t('Add privacy group'),
'$ungrouped' => (($every === 'contacts') ? t('Channels not in any privacy group') : ''),
'$groups' => $groups,
'$add' => t('add'),
));
return $o;
}
static function expand($g) {
if(! (is_array($g) && count($g)))
return array();
$ret = [];
$x = [];
// private profile linked virtual groups
foreach($g as $gv) {
if(substr($gv,0,3) === 'vp.') {
$profile_hash = substr($gv,3);
if($profile_hash) {
$r = q("select abook_xchan from abook where abook_profile = '%s'",
dbesc($profile_hash)
);
if($r) {
foreach($r as $rv) {
$ret[] = $rv['abook_xchan'];
}
}
}
}
else {
$x[] = $gv;
}
}
if($x) {
stringify_array_elms($x,true);
$groups = implode(',', $x);
if($groups) {
$r = q("SELECT xchan FROM group_member WHERE gid IN ( select id from groups where hash in ( $groups ))");
if($r) {
foreach($r as $rr) {
$ret[] = $rr['xchan'];
}
}
}
}
return $ret;
}
static function member_of($c) {
$r = q("SELECT groups.gname, 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.gname ASC ",
dbesc($c)
);
return $r;
}
static function containing($uid,$c) {
$r = q("SELECT gid FROM group_member WHERE uid = %d AND group_member.xchan = '%s' ",
intval($uid),
dbesc($c)
);
$ret = array();
if($r) {
foreach($r as $rr)
$ret[] = $rr['gid'];
}
return $ret;
}
}

1019
Zotlabs/Lib/Libsync.php Normal file

File diff suppressed because it is too large Load diff

2849
Zotlabs/Lib/Libzot.php Normal file

File diff suppressed because it is too large Load diff

654
Zotlabs/Lib/Libzotdir.php Normal file
View file

@ -0,0 +1,654 @@
<?php
namespace Zotlabs\Lib;
use Zotlabs\Lib\Libzot;
require_once('include/permissions.php');
class Libzotdir {
/**
* @brief
*
* @param int $dirmode
* @return array
*/
static function find_upstream_directory($dirmode) {
global $DIRECTORY_FALLBACK_SERVERS;
$preferred = get_config('system','directory_server');
// Thwart attempts to use a private directory
if(($preferred) && ($preferred != z_root())) {
$r = q("select * from site where site_url = '%s' limit 1",
dbesc($preferred)
);
if(($r) && ($r[0]['site_flags'] & DIRECTORY_MODE_STANDALONE)) {
$preferred = '';
}
}
if (! $preferred) {
/*
* No directory has yet been set. For most sites, pick one at random
* from our list of directory servers. However, if we're a directory
* server ourself, point at the local instance
* We will then set this value so this should only ever happen once.
* Ideally there will be an admin setting to change to a different
* directory server if you don't like our choice or if circumstances change.
*/
$dirmode = intval(get_config('system','directory_mode'));
if ($dirmode == DIRECTORY_MODE_NORMAL) {
$toss = mt_rand(0,count($DIRECTORY_FALLBACK_SERVERS));
$preferred = $DIRECTORY_FALLBACK_SERVERS[$toss];
if(! $preferred) {
$preferred = DIRECTORY_FALLBACK_MASTER;
}
set_config('system','directory_server',$preferred);
}
else {
set_config('system','directory_server',z_root());
}
}
if($preferred) {
return [ 'url' => $preferred ];
}
else {
return [];
}
}
/**
* Directories may come and go over time. We will need to check that our
* directory server is still valid occasionally, and reset to something that
* is if our directory has gone offline for any reason
*/
static function check_upstream_directory() {
$directory = get_config('system', 'directory_server');
// it's possible there is no directory server configured and the local hub is being used.
// If so, default to preserving the absence of a specific server setting.
$isadir = true;
if ($directory) {
$j = Zotfinger::exec($directory);
if(array_path_exists('data/directory_mode',$j)) {
if ($j['data']['directory_mode'] === 'normal') {
$isadir = false;
}
}
}
if (! $isadir)
set_config('system', 'directory_server', '');
}
static function get_directory_setting($observer, $setting) {
if ($observer)
$ret = get_xconfig($observer, 'directory', $setting);
else
$ret = ((array_key_exists($setting,$_SESSION)) ? intval($_SESSION[$setting]) : false);
if($ret === false)
$ret = get_config('directory', $setting);
// 'safemode' is the default if there is no observer or no established preference.
if($setting === 'safemode' && $ret === false)
$ret = 1;
if($setting === 'globaldir' && intval(get_config('system','localdir_hide')))
$ret = 1;
return $ret;
}
/**
* @brief Called by the directory_sort widget.
*/
static function dir_sort_links() {
$safe_mode = 1;
$observer = get_observer_hash();
$safe_mode = self::get_directory_setting($observer, 'safemode');
$globaldir = self::get_directory_setting($observer, 'globaldir');
$pubforums = self::get_directory_setting($observer, 'pubforums');
$hide_local = intval(get_config('system','localdir_hide'));
if($hide_local)
$globaldir = 1;
// Build urls without order and pubforums so it's easy to tack on the changed value
// Probably there's an easier way to do this
$directory_sort_order = get_config('system','directory_sort_order');
if(! $directory_sort_order)
$directory_sort_order = 'date';
$current_order = (($_REQUEST['order']) ? $_REQUEST['order'] : $directory_sort_order);
$suggest = (($_REQUEST['suggest']) ? '&suggest=' . $_REQUEST['suggest'] : '');
$url = 'directory?f=';
$tmp = array_merge($_GET,$_POST);
unset($tmp['suggest']);
unset($tmp['pubforums']);
unset($tmp['global']);
unset($tmp['safe']);
unset($tmp['q']);
unset($tmp['f']);
$forumsurl = $url . http_build_query($tmp) . $suggest;
$o = replace_macros(get_markup_template('dir_sort_links.tpl'), [
'$header' => t('Directory Options'),
'$forumsurl' => $forumsurl,
'$safemode' => array('safemode', t('Safe Mode'),$safe_mode,'',array(t('No'), t('Yes')),' onchange=\'window.location.href="' . $forumsurl . '&safe="+(this.checked ? 1 : 0)\''),
'$pubforums' => array('pubforums', t('Public Forums Only'),$pubforums,'',array(t('No'), t('Yes')),' onchange=\'window.location.href="' . $forumsurl . '&pubforums="+(this.checked ? 1 : 0)\''),
'$hide_local' => $hide_local,
'$globaldir' => array('globaldir', t('This Website Only'), 1-intval($globaldir),'',array(t('No'), t('Yes')),' onchange=\'window.location.href="' . $forumsurl . '&global="+(this.checked ? 0 : 1)\''),
]);
return $o;
}
/**
* @brief Checks the directory mode of this hub.
*
* Checks the directory mode of this hub to see if it is some form of directory server. If it is,
* get the directory realm of this hub. Fetch a list of all other directory servers in this realm and request
* a directory sync packet. This will contain both directory updates and new ratings. Store these all in the DB.
* In the case of updates, we will query each of them asynchronously from a poller task. Ratings are stored
* directly if the rater's signature matches.
*
* @param int $dirmode;
*/
static function sync_directories($dirmode) {
if ($dirmode == DIRECTORY_MODE_STANDALONE || $dirmode == DIRECTORY_MODE_NORMAL)
return;
$realm = get_directory_realm();
if ($realm == DIRECTORY_REALM) {
$r = q("select * from site where (site_flags & %d) > 0 and site_url != '%s' and site_type = %d and ( site_realm = '%s' or site_realm = '') ",
intval(DIRECTORY_MODE_PRIMARY|DIRECTORY_MODE_SECONDARY),
dbesc(z_root()),
intval(SITE_TYPE_ZOT),
dbesc($realm)
);
}
else {
$r = q("select * from site where (site_flags & %d) > 0 and site_url != '%s' and site_realm like '%s' and site_type = %d ",
intval(DIRECTORY_MODE_PRIMARY|DIRECTORY_MODE_SECONDARY),
dbesc(z_root()),
dbesc(protect_sprintf('%' . $realm . '%')),
intval(SITE_TYPE_ZOT)
);
}
// If there are no directory servers, setup the fallback master
/** @FIXME What to do if we're in a different realm? */
if ((! $r) && (z_root() != DIRECTORY_FALLBACK_MASTER)) {
$x = site_store_lowlevel(
[
'site_url' => DIRECTORY_FALLBACK_MASTER,
'site_flags' => DIRECTORY_MODE_PRIMARY,
'site_update' => NULL_DATE,
'site_directory' => DIRECTORY_FALLBACK_MASTER . '/dirsearch',
'site_realm' => DIRECTORY_REALM,
'site_valid' => 1,
]
);
$r = q("select * from site where site_flags in (%d, %d) and site_url != '%s' and site_type = %d ",
intval(DIRECTORY_MODE_PRIMARY),
intval(DIRECTORY_MODE_SECONDARY),
dbesc(z_root()),
intval(SITE_TYPE_ZOT)
);
}
if (! $r)
return;
foreach ($r as $rr) {
if (! $rr['site_directory'])
continue;
logger('sync directories: ' . $rr['site_directory']);
// for brand new directory servers, only load the last couple of days.
// It will take about a month for a new directory to obtain the full current repertoire of channels.
/** @FIXME Go back and pick up earlier ratings if this is a new directory server. These do not get refreshed. */
$token = get_config('system','realm_token');
$syncdate = (($rr['site_sync'] <= NULL_DATE) ? datetime_convert('UTC','UTC','now - 2 days') : $rr['site_sync']);
$x = z_fetch_url($rr['site_directory'] . '?f=&sync=' . urlencode($syncdate) . (($token) ? '&t=' . $token : ''));
if (! $x['success'])
continue;
$j = json_decode($x['body'],true);
if (!($j['transactions']) || ($j['ratings']))
continue;
q("update site set site_sync = '%s' where site_url = '%s'",
dbesc(datetime_convert()),
dbesc($rr['site_url'])
);
logger('sync_directories: ' . $rr['site_url'] . ': ' . print_r($j,true), LOGGER_DATA);
if (is_array($j['transactions']) && count($j['transactions'])) {
foreach ($j['transactions'] as $t) {
$r = q("select * from updates where ud_guid = '%s' limit 1",
dbesc($t['transaction_id'])
);
if($r)
continue;
$ud_flags = 0;
if (is_array($t['flags']) && in_array('deleted',$t['flags']))
$ud_flags |= UPDATE_FLAGS_DELETED;
if (is_array($t['flags']) && in_array('forced',$t['flags']))
$ud_flags |= UPDATE_FLAGS_FORCED;
$z = q("insert into updates ( ud_hash, ud_guid, ud_date, ud_flags, ud_addr )
values ( '%s', '%s', '%s', %d, '%s' ) ",
dbesc($t['hash']),
dbesc($t['transaction_id']),
dbesc($t['timestamp']),
intval($ud_flags),
dbesc($t['address'])
);
}
}
}
}
/**
* @brief
*
* Given an update record, probe the channel, grab a zot-info packet and refresh/sync the data.
*
* Ignore updating records marked as deleted.
*
* If successful, sets ud_last in the DB to the current datetime for this
* reddress/webbie.
*
* @param array $ud Entry from update table
*/
static function update_directory_entry($ud) {
logger('update_directory_entry: ' . print_r($ud,true), LOGGER_DATA);
if ($ud['ud_addr'] && (! ($ud['ud_flags'] & UPDATE_FLAGS_DELETED))) {
$success = false;
$href = \Zotlabs\Lib\Webfinger::zot_url(punify($url));
if($href) {
$zf = \Zotlabs\Lib\Zotfinger::exec($href);
}
if(is_array($zf) && array_path_exists('signature/signer',$zf) && $zf['signature']['signer'] === $href && intval($zf['signature']['header_valid'])) {
$xc = Libzot::import_xchan($zf['data'], 0, $ud);
}
else {
q("update updates set ud_last = '%s' where ud_addr = '%s'",
dbesc(datetime_convert()),
dbesc($ud['ud_addr'])
);
}
}
}
/**
* @brief Push local channel updates to a local directory server.
*
* This is called from include/directory.php if a profile is to be pushed to the
* directory and the local hub in this case is any kind of directory server.
*
* @param int $uid
* @param boolean $force
*/
static function local_dir_update($uid, $force) {
logger('local_dir_update: uid: ' . $uid, 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)
);
$profile = array();
$profile['encoding'] = 'zot';
if ($p) {
$hash = $p[0]['channel_hash'];
$profile['description'] = $p[0]['pdesc'];
$profile['birthday'] = $p[0]['dob'];
if ($age = age($p[0]['dob'],$p[0]['channel_timezone'],''))
$profile['age'] = $age;
$profile['gender'] = $p[0]['gender'];
$profile['marital'] = $p[0]['marital'];
$profile['sexual'] = $p[0]['sexual'];
$profile['locale'] = $p[0]['locality'];
$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']);
if ($k)
foreach ($k as $kk)
if (trim($kk))
$tags[] = trim($kk);
if ($tags)
$profile['keywords'] = $tags;
}
$hidden = (1 - intval($p[0]['publish']));
logger('hidden: ' . $hidden);
$r = q("select xchan_hidden from xchan where xchan_hash = '%s' limit 1",
dbesc($p[0]['channel_hash'])
);
if(intval($r[0]['xchan_hidden']) != $hidden) {
$r = q("update xchan set xchan_hidden = %d where xchan_hash = '%s'",
intval($hidden),
dbesc($p[0]['channel_hash'])
);
}
$arr = [ 'channel_id' => $uid, 'hash' => $hash, 'profile' => $profile ];
call_hooks('local_dir_update', $arr);
$address = channel_reddress($p[0]);
if (perm_is_allowed($uid, '', 'view_profile')) {
self::import_directory_profile($hash, $arr['profile'], $address, 0);
}
else {
// they may have made it private
$r = q("delete from xprof where xprof_hash = '%s'",
dbesc($hash)
);
$r = q("delete from xtag where xtag_hash = '%s'",
dbesc($hash)
);
}
}
$ud_hash = random_string() . '@' . \App::get_hostname();
self::update_modtime($hash, $ud_hash, channel_reddress($p[0]),(($force) ? UPDATE_FLAGS_FORCED : UPDATE_FLAGS_UPDATED));
}
/**
* @brief Imports a directory profile.
*
* @param string $hash
* @param array $profile
* @param string $addr
* @param number $ud_flags (optional) UPDATE_FLAGS_UPDATED
* @param number $suppress_update (optional) default 0
* @return boolean $updated if something changed
*/
static function import_directory_profile($hash, $profile, $addr, $ud_flags = UPDATE_FLAGS_UPDATED, $suppress_update = 0) {
logger('import_directory_profile', LOGGER_DEBUG);
if (! $hash)
return false;
$arr = array();
$arr['xprof_hash'] = $hash;
$arr['xprof_dob'] = (($profile['birthday'] === '0000-00-00') ? $profile['birthday'] : datetime_convert('','',$profile['birthday'],'Y-m-d')); // !!!! check this for 0000 year
$arr['xprof_age'] = (($profile['age']) ? intval($profile['age']) : 0);
$arr['xprof_desc'] = (($profile['description']) ? htmlspecialchars($profile['description'], 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'])) {
self::import_directory_keywords($hash,$profile['keywords']);
foreach ($profile['keywords'] as $kw) {
$kw = trim(htmlspecialchars($kw,ENT_COMPAT, 'UTF-8', false));
$kw = trim($kw, ',');
$clean[] = $kw;
}
}
$arr['xprof_keywords'] = implode(' ',$clean);
// Self censored, make it so
// These are not translated, so the German "erwachsenen" keyword will not censor the directory profile. Only the English form - "adult".
if(in_arrayi('nsfw',$clean) || in_arrayi('adult',$clean)) {
q("update xchan set xchan_selfcensored = 1 where xchan_hash = '%s'",
dbesc($hash)
);
}
$r = q("select * from xprof where xprof_hash = '%s' limit 1",
dbesc($hash)
);
if ($arr['xprof_age'] > 150)
$arr['xprof_age'] = 150;
if ($arr['xprof_age'] < 0)
$arr['xprof_age'] = 0;
if ($r) {
$update = false;
foreach ($r[0] as $k => $v) {
if ((array_key_exists($k,$arr)) && ($arr[$k] != $v)) {
logger('import_directory_profile: update ' . $k . ' => ' . $arr[$k]);
$update = true;
break;
}
}
if ($update) {
q("update xprof set
xprof_desc = '%s',
xprof_dob = '%s',
xprof_age = %d,
xprof_gender = '%s',
xprof_marital = '%s',
xprof_sexual = '%s',
xprof_locale = '%s',
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'",
dbesc($arr['xprof_desc']),
dbesc($arr['xprof_dob']),
intval($arr['xprof_age']),
dbesc($arr['xprof_gender']),
dbesc($arr['xprof_marital']),
dbesc($arr['xprof_sexual']),
dbesc($arr['xprof_locale']),
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'])
);
}
} else {
$update = true;
logger('New profile');
q("insert into xprof (xprof_hash, xprof_desc, xprof_dob, xprof_age, xprof_gender, xprof_marital, xprof_sexual, xprof_locale, xprof_region, xprof_postcode, xprof_country, xprof_about, xprof_homepage, xprof_hometown, xprof_keywords) values ('%s', '%s', '%s', %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s') ",
dbesc($arr['xprof_hash']),
dbesc($arr['xprof_desc']),
dbesc($arr['xprof_dob']),
intval($arr['xprof_age']),
dbesc($arr['xprof_gender']),
dbesc($arr['xprof_marital']),
dbesc($arr['xprof_sexual']),
dbesc($arr['xprof_locale']),
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'])
);
}
$d = [
'xprof' => $arr,
'profile' => $profile,
'update' => $update
];
/**
* @hooks import_directory_profile
* Called when processing delivery of a profile structure from an external source (usually for directory storage).
* * \e array \b xprof
* * \e array \b profile
* * \e boolean \b update
*/
call_hooks('import_directory_profile', $d);
if (($d['update']) && (! $suppress_update))
self::update_modtime($arr['xprof_hash'],random_string() . '@' . \App::get_hostname(), $addr, $ud_flags);
return $d['update'];
}
/**
* @brief
*
* @param string $hash An xtag_hash
* @param array $keywords
*/
static function import_directory_keywords($hash, $keywords) {
$existing = array();
$r = q("select * from xtag where xtag_hash = '%s' and xtag_flags = 0",
dbesc($hash)
);
if($r) {
foreach($r as $rr)
$existing[] = $rr['xtag_term'];
}
$clean = array();
foreach($keywords as $kw) {
$kw = trim(htmlspecialchars($kw,ENT_COMPAT, 'UTF-8', false));
$kw = trim($kw, ',');
$clean[] = $kw;
}
foreach($existing as $x) {
if(! in_array($x, $clean))
$r = q("delete from xtag where xtag_hash = '%s' and xtag_term = '%s' and xtag_flags = 0",
dbesc($hash),
dbesc($x)
);
}
foreach($clean as $x) {
if(! in_array($x, $existing)) {
$r = q("insert into xtag ( xtag_hash, xtag_term, xtag_flags) values ( '%s' ,'%s', 0 )",
dbesc($hash),
dbesc($x)
);
}
}
}
/**
* @brief
*
* @param string $hash
* @param string $guid
* @param string $addr
* @param int $flags (optional) default 0
*/
static function update_modtime($hash, $guid, $addr, $flags = 0) {
$dirmode = intval(get_config('system', 'directory_mode'));
if($dirmode == DIRECTORY_MODE_NORMAL)
return;
if($flags) {
q("insert into updates (ud_hash, ud_guid, ud_date, ud_flags, ud_addr ) values ( '%s', '%s', '%s', %d, '%s' )",
dbesc($hash),
dbesc($guid),
dbesc(datetime_convert()),
intval($flags),
dbesc($addr)
);
}
else {
q("update updates set ud_flags = ( ud_flags | %d ) where ud_addr = '%s' and not (ud_flags & %d)>0 ",
intval(UPDATE_FLAGS_UPDATED),
dbesc($addr),
intval(UPDATE_FLAGS_UPDATED)
);
}
}
}

278
Zotlabs/Lib/Queue.php Normal file
View file

@ -0,0 +1,278 @@
<?php /** @file */
namespace Zotlabs\Lib;
use Zotlabs\Lib\Libzot;
class Queue {
static function update($id, $add_priority = 0) {
logger('queue: requeue item ' . $id,LOGGER_DEBUG);
$x = q("select outq_created, outq_posturl from outq where outq_hash = '%s' limit 1",
dbesc($id)
);
if(! $x)
return;
$y = q("select min(outq_created) as earliest from outq where outq_posturl = '%s'",
dbesc($x[0]['outq_posturl'])
);
// look for the oldest queue entry with this destination URL. If it's older than a couple of days,
// the destination is considered to be down and only scheduled once an hour, regardless of the
// age of the current queue item.
$might_be_down = false;
if($y)
$might_be_down = ((datetime_convert('UTC','UTC',$y[0]['earliest']) < datetime_convert('UTC','UTC','now - 2 days')) ? true : false);
// Set all other records for this destination way into the future.
// The queue delivers by destination. We'll keep one queue item for
// this destination (this one) with a shorter delivery. If we succeed
// once, we'll try to deliver everything for that destination.
// The delivery will be set to at most once per hour, and if the
// queue item is less than 12 hours old, we'll schedule for fifteen
// minutes.
$r = q("UPDATE outq SET outq_scheduled = '%s' WHERE outq_posturl = '%s'",
dbesc(datetime_convert('UTC','UTC','now + 5 days')),
dbesc($x[0]['outq_posturl'])
);
$since = datetime_convert('UTC','UTC',$x[0]['outq_created']);
if(($might_be_down) || ($since < datetime_convert('UTC','UTC','now - 12 hour'))) {
$next = datetime_convert('UTC','UTC','now + 1 hour');
}
else {
$next = datetime_convert('UTC','UTC','now + ' . intval($add_priority) . ' minutes');
}
q("UPDATE outq SET outq_updated = '%s',
outq_priority = outq_priority + %d,
outq_scheduled = '%s'
WHERE outq_hash = '%s'",
dbesc(datetime_convert()),
intval($add_priority),
dbesc($next),
dbesc($id)
);
}
static function remove($id,$channel_id = 0) {
logger('queue: remove queue item ' . $id,LOGGER_DEBUG);
$sql_extra = (($channel_id) ? " and outq_channel = " . intval($channel_id) . " " : '');
q("DELETE FROM outq WHERE outq_hash = '%s' $sql_extra",
dbesc($id)
);
}
static function remove_by_posturl($posturl) {
logger('queue: remove queue posturl ' . $posturl,LOGGER_DEBUG);
q("DELETE FROM outq WHERE outq_posturl = '%s' ",
dbesc($posturl)
);
}
static function set_delivered($id,$channel = 0) {
logger('queue: set delivered ' . $id,LOGGER_DEBUG);
$sql_extra = (($channel_id) ? " and outq_channel = " . intval($channel_id) . " " : '');
// Set the next scheduled run date so far in the future that it will be expired
// long before it ever makes it back into the delivery chain.
q("update outq set outq_delivered = 1, outq_updated = '%s', outq_scheduled = '%s' where outq_hash = '%s' $sql_extra ",
dbesc(datetime_convert()),
dbesc(datetime_convert('UTC','UTC','now + 5 days')),
dbesc($id)
);
}
static function insert($arr) {
// do not queue anything with no destination
if(! (array_key_exists('posturl',$arr) && trim($arr['posturl']))) {
return false;
}
$x = q("insert into outq ( outq_hash, outq_account, outq_channel, outq_driver, outq_posturl, outq_async, outq_priority,
outq_created, outq_updated, outq_scheduled, outq_notify, outq_msg )
values ( '%s', %d, %d, '%s', '%s', %d, %d, '%s', '%s', '%s', '%s', '%s' )",
dbesc($arr['hash']),
intval($arr['account_id']),
intval($arr['channel_id']),
dbesc(($arr['driver']) ? $arr['driver'] : 'zot'),
dbesc($arr['posturl']),
intval(1),
intval(($arr['priority']) ? $arr['priority'] : 0),
dbesc(datetime_convert()),
dbesc(datetime_convert()),
dbesc(datetime_convert()),
dbesc($arr['notify']),
dbesc(($arr['msg']) ? $arr['msg'] : '')
);
return $x;
}
static function deliver($outq, $immediate = false) {
$base = null;
$h = parse_url($outq['outq_posturl']);
if($h !== false)
$base = $h['scheme'] . '://' . $h['host'] . (($h['port']) ? ':' . $h['port'] : '');
if(($base) && ($base !== z_root()) && ($immediate)) {
$y = q("select site_update, site_dead from site where site_url = '%s' ",
dbesc($base)
);
if($y) {
if(intval($y[0]['site_dead'])) {
self::remove_by_posturl($outq['outq_posturl']);
logger('dead site ignored ' . $base);
return;
}
if($y[0]['site_update'] < datetime_convert('UTC','UTC','now - 1 month')) {
self::update($outq['outq_hash'],10);
logger('immediate delivery deferred for site ' . $base);
return;
}
}
else {
// zot sites should all have a site record, unless they've been dead for as long as
// your site has existed. Since we don't know for sure what these sites are,
// call them unknown
site_store_lowlevel(
[
'site_url' => $base,
'site_update' => datetime_convert(),
'site_dead' => 0,
'site_type' => intval(($outq['outq_driver'] === 'post') ? SITE_TYPE_NOTZOT : SITE_TYPE_UNKNOWN),
'site_crypto' => ''
]
);
}
}
$arr = array('outq' => $outq, 'base' => $base, 'handled' => false, 'immediate' => $immediate);
call_hooks('queue_deliver',$arr);
if($arr['handled'])
return;
// "post" queue driver - used for diaspora and friendica-over-diaspora communications.
if($outq['outq_driver'] === 'post') {
$result = z_post_url($outq['outq_posturl'],$outq['outq_msg']);
if($result['success'] && $result['return_code'] < 300) {
logger('deliver: queue post success to ' . $outq['outq_posturl'], LOGGER_DEBUG);
if($base) {
q("update site set site_update = '%s', site_dead = 0 where site_url = '%s' ",
dbesc(datetime_convert()),
dbesc($base)
);
}
q("update dreport set dreport_result = '%s', dreport_time = '%s' where dreport_queue = '%s'",
dbesc('accepted for delivery'),
dbesc(datetime_convert()),
dbesc($outq['outq_hash'])
);
self::remove($outq['outq_hash']);
// server is responding - see if anything else is going to this destination and is piled up
// and try to send some more. We're relying on the fact that do_delivery() results in an
// immediate delivery otherwise we could get into a queue loop.
if(! $immediate) {
$x = q("select outq_hash from outq where outq_posturl = '%s' and outq_delivered = 0",
dbesc($outq['outq_posturl'])
);
$piled_up = array();
if($x) {
foreach($x as $xx) {
$piled_up[] = $xx['outq_hash'];
}
}
if($piled_up) {
// call do_delivery() with the force flag
do_delivery($piled_up, true);
}
}
}
else {
logger('deliver: queue post returned ' . $result['return_code']
. ' from ' . $outq['outq_posturl'],LOGGER_DEBUG);
self::update($outq['outq_hash'],10);
}
return;
}
// normal zot delivery
logger('deliver: dest: ' . $outq['outq_posturl'], LOGGER_DEBUG);
if($outq['outq_posturl'] === z_root() . '/zot') {
// local delivery
$zot = new \Zotlabs\Zot6\Receiver(new \Zotlabs\Zot6\Zot6Handler(),$outq['outq_notify']);
$result = $zot->run(true);
logger('returned_json: ' . json_encode($result,JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES), LOGGER_DATA);
logger('deliver: local zot delivery succeeded to ' . $outq['outq_posturl']);
Libzot::process_response($outq['outq_posturl'],[ 'success' => true, 'body' => json_encode($result) ], $outq);
}
else {
logger('remote');
$channel = null;
if($outq['outq_channel']) {
$channel = channelx_by_n($outq['outq_channel']);
}
$host_crypto = null;
if($channel && $base) {
$h = q("select hubloc_sitekey, site_crypto from hubloc left join site on hubloc_url = site_url where site_url = '%s' order by hubloc_id desc limit 1",
dbesc($base)
);
if($h) {
$host_crypto = $h[0];
}
}
$msg = $outq['outq_notify'];
$result = Libzot::zot($outq['outq_posturl'],$msg,$channel,$host_crypto);
if($result['success']) {
logger('deliver: remote zot delivery succeeded to ' . $outq['outq_posturl']);
Libzot::process_response($outq['outq_posturl'],$result, $outq);
}
else {
logger('deliver: remote zot delivery failed to ' . $outq['outq_posturl']);
logger('deliver: remote zot delivery fail data: ' . print_r($result,true), LOGGER_DATA);
self::update($outq['outq_hash'],10);
}
}
return;
}
}

109
Zotlabs/Lib/Webfinger.php Normal file
View file

@ -0,0 +1,109 @@
<?php
namespace Zotlabs\Lib;
/**
* @brief Fetch and return a webfinger for a resource
*
* @param string $resource - The resource
* @return boolean|string false or associative array from result JSON
*/
class Webfinger {
static private $server = EMPTY_STR;
static private $resource = EMPTY_STR;
static function exec($resource) {
if(! $resource) {
return false;
}
self::parse_resource($resource);
if(! ( self::$server && self::$resource)) {
return false;
}
if(! check_siteallowed(self::$server)) {
logger('blacklisted: ' . self::$server);
return false;
}
btlogger('fetching resource: ' . self::$resource . ' from ' . self::$server, LOGGER_DEBUG, LOG_INFO);
$url = 'https://' . self::$server . '/.well-known/webfinger?f=&resource=' . self::$resource ;
$counter = 0;
$s = z_fetch_url($url, false, $counter, [ 'headers' => [ 'Accept: application/jrd+json, */*' ] ]);
if($s['success']) {
$j = json_decode($s['body'], true);
return($j);
}
return false;
}
static function parse_resource($resource) {
self::$resource = urlencode($resource);
if(strpos($resource,'http') === 0) {
$m = parse_url($resource);
if($m) {
if($m['scheme'] !== 'https') {
return false;
}
self::$server = $m['host'] . (($m['port']) ? ':' . $m['port'] : '');
}
else {
return false;
}
}
elseif(strpos($resource,'tag:') === 0) {
$arr = explode(':',$resource); // split the tag
$h = explode(',',$arr[1]); // split the host,date
self::$server = $h[0];
}
else {
$x = explode('@',$resource);
$username = $x[0];
if(count($x) > 1) {
self::$server = $x[1];
}
else {
return false;
}
if(strpos($resource,'acct:') !== 0) {
self::$resource = urlencode('acct:' . $resource);
}
}
}
/**
* @brief fetch a webfinger resource and return a zot6 discovery url if present
*
*/
static function zot_url($resource) {
$arr = self::exec($resource);
if(is_array($arr) && array_key_exists('links',$arr)) {
foreach($arr['links'] as $link) {
if(array_key_exists('rel',$link) && $link['rel'] === PROTOCOL_ZOT6) {
if(array_key_exists('href',$link) && $link['href'] !== EMPTY_STR) {
return $link['href'];
}
}
}
}
return false;
}
}

50
Zotlabs/Lib/Zotfinger.php Normal file
View file

@ -0,0 +1,50 @@
<?php
namespace Zotlabs\Lib;
use Zotlabs\Web\HTTPSig;
class Zotfinger {
static function exec($resource,$channel = null) {
if(! $resource) {
return false;
}
if($channel) {
$headers = [
'Accept' => 'application/x-zot+json',
'X-Zot-Token' => random_string(),
];
$h = HTTPSig::create_sig($headers,$channel['channel_prvkey'],channel_url($channel),false);
}
else {
$h = [ 'Accept: application/x-zot+json' ];
}
$result = [];
$redirects = 0;
$x = z_fetch_url($resource,false,$redirects, [ 'headers' => $h ] );
if($x['success']) {
$result['signature'] = HTTPSig::verify($x);
$result['data'] = json_decode($x['body'],true);
if($result['data'] && is_array($result['data']) && array_key_exists('encrypted',$result['data']) && $result['data']['encrypted']) {
$result['data'] = json_decode(crypto_unencapsulate($result['data'],get_config('system','prvkey')),true);
}
return $result;
}
return false;
}
}

View file

@ -27,12 +27,11 @@ class Probe extends \Zotlabs\Web\Controller {
$o .= '<pre>';
if(! $j['success']) {
$o .= sprintf( t('Fetching URL returns error: %1$s'),$res['error'] . "\r\n\r\n");
$o .= "<strong>https connection failed. Trying again with auto failover to http.</strong>\r\n\r\n";
$j = \Zotlabs\Zot\Finger::run($addr,$channel,true);
if(! $j['success'])
$o .= sprintf( t('Fetching URL returns error: %1$s'),$res['error'] . "\r\n\r\n");
if(! $j['success']) {
return $o;
}
}
if($do_import && $j)
$x = import_xchan($j);

View file

@ -36,10 +36,6 @@ class Zfinger extends \Zotlabs\Web\Controller {
echo $ret;
killme();
json_return_and_die($x);
}

25
Zotlabs/Module/Zot.php Normal file
View file

@ -0,0 +1,25 @@
<?php
/**
* @file Zotlabs/Module/Zot.php
*
* @brief Zot endpoint.
*
*/
namespace Zotlabs\Module;
use Zotlabs\Zot6 as ZotProtocol;
/**
* @brief Zot module.
*
*/
class Zot extends \Zotlabs\Web\Controller {
function init() {
$zot = new ZotProtocol\Receiver(new ZotProtocol\Zot6Handler());
json_return_and_die($zot->run(),'application/x-zot+jzon');
}
}

22
Zotlabs/Update/_1217.php Normal file
View file

@ -0,0 +1,22 @@
<?php
namespace Zotlabs\Update;
class _1217 {
function run() {
if(ACTIVE_DBTYPE == DBTYPE_POSTGRES) {
$r = q("ALTER TABLE app ADD app_options smallint NOT NULL DEFAULT '0' ");
}
else {
$r = q("ALTER TABLE app ADD app_options int(11) NOT NULL DEFAULT 0 ");
}
if($r) {
return UPDATE_SUCCESS;
}
return UPDATE_FAILED;
}
}

View file

@ -34,6 +34,7 @@ class Activity_order {
break;
default:
$commentord_active = 'active';
break;
}
}
else {

146
Zotlabs/Zot6/Finger.php Normal file
View file

@ -0,0 +1,146 @@
<?php
namespace Zotlabs\Zot6;
/**
* @brief Finger
*
*/
class Finger {
static private $token;
/**
* @brief Look up information about channel.
*
* @param string $webbie
* does not have to be host qualified e.g. 'foo' is treated as 'foo\@thishub'
* @param array $channel
* (optional), if supplied permissions will be enumerated specifically for $channel
* @param boolean $autofallback
* fallback/failover to http if https connection cannot be established. Default is true.
*
* @return zotinfo array (with 'success' => true) or array('success' => false);
*/
static public function run($webbie, $channel = null, $autofallback = true) {
$ret = array('success' => false);
self::$token = random_string();
if (strpos($webbie, '@') === false) {
$address = $webbie;
$host = \App::get_hostname();
} else {
$address = substr($webbie,0,strpos($webbie,'@'));
$host = substr($webbie,strpos($webbie,'@')+1);
if(strpos($host,'/'))
$host = substr($host,0,strpos($host,'/'));
}
$xchan_addr = $address . '@' . $host;
if ((! $address) || (! $xchan_addr)) {
logger('zot_finger: no address :' . $webbie);
return $ret;
}
logger('using xchan_addr: ' . $xchan_addr, LOGGER_DATA, LOG_DEBUG);
// potential issue here; the xchan_addr points to the primary hub.
// The webbie we were called with may not, so it might not be found
// unless we query for hubloc_addr instead of xchan_addr
$r = q("select xchan.*, hubloc.* from xchan
left join hubloc on xchan_hash = hubloc_hash
where xchan_addr = '%s' and hubloc_primary = 1 limit 1",
dbesc($xchan_addr)
);
if($r) {
$url = $r[0]['hubloc_url'];
if($r[0]['hubloc_network'] && $r[0]['hubloc_network'] !== 'zot') {
logger('zot_finger: alternate network: ' . $webbie);
logger('url: ' . $url . ', net: ' . var_export($r[0]['hubloc_network'],true), LOGGER_DATA, LOG_DEBUG);
return $ret;
}
} else {
$url = 'https://' . $host;
}
$rhs = '/.well-known/zot-info';
$https = ((strpos($url,'https://') === 0) ? true : false);
logger('zot_finger: ' . $address . ' at ' . $url, LOGGER_DEBUG);
if ($channel) {
$postvars = array(
'address' => $address,
'target' => $channel['channel_guid'],
'target_sig' => $channel['channel_guid_sig'],
'key' => $channel['channel_pubkey'],
'token' => self::$token
);
$headers = [];
$headers['X-Zot-Channel'] = $channel['channel_address'] . '@' . \App::get_hostname();
$headers['X-Zot-Nonce'] = random_string();
$xhead = \Zotlabs\Web\HTTPSig::create_sig('',$headers,$channel['channel_prvkey'],
'acct:' . $channel['channel_address'] . '@' . \App::get_hostname(),false);
$retries = 0;
$result = z_post_url($url . $rhs,$postvars,$retries, [ 'headers' => $xhead ]);
if ((! $result['success']) && ($autofallback)) {
if ($https) {
logger('zot_finger: https failed. falling back to http');
$result = z_post_url('http://' . $host . $rhs,$postvars, $retries, [ 'headers' => $xhead ]);
}
}
}
else {
$rhs .= '?f=&address=' . urlencode($address) . '&token=' . self::$token;
$result = z_fetch_url($url . $rhs);
if((! $result['success']) && ($autofallback)) {
if($https) {
logger('zot_finger: https failed. falling back to http');
$result = z_fetch_url('http://' . $host . $rhs);
}
}
}
if(! $result['success']) {
logger('zot_finger: no results');
return $ret;
}
$x = json_decode($result['body'], true);
$verify = \Zotlabs\Web\HTTPSig::verify($result,(($x) ? $x['key'] : ''));
if($x && (! $verify['header_valid'])) {
$signed_token = ((is_array($x) && array_key_exists('signed_token', $x)) ? $x['signed_token'] : null);
if($signed_token) {
$valid = zot_verify('token.' . self::$token, base64url_decode($signed_token), $x['key']);
if(! $valid) {
logger('invalid signed token: ' . $url . $rhs, LOGGER_NORMAL, LOG_ERR);
return $ret;
}
}
else {
logger('No signed token from ' . $url . $rhs, LOGGER_NORMAL, LOG_WARNING);
return $ret;
}
}
return $x;
}
}

18
Zotlabs/Zot6/IHandler.php Normal file
View file

@ -0,0 +1,18 @@
<?php
namespace Zotlabs\Zot6;
interface IHandler {
function Notify($data,$hub);
function Request($data,$hub);
function Rekey($sender,$data,$hub);
function Refresh($sender,$recipients,$hub);
function Purge($sender,$recipients,$hub);
}

220
Zotlabs/Zot6/Receiver.php Normal file
View file

@ -0,0 +1,220 @@
<?php
namespace Zotlabs\Zot6;
use Zotlabs\Lib\Config;
use Zotlabs\Lib\Libzot;
use Zotlabs\Web\HTTPSig;
class Receiver {
protected $data;
protected $encrypted;
protected $error;
protected $messagetype;
protected $sender;
protected $site_id;
protected $validated;
protected $recipients;
protected $response;
protected $handler;
protected $prvkey;
protected $rawdata;
protected $sigdata;
function __construct($handler, $localdata = null) {
$this->error = false;
$this->validated = false;
$this->messagetype = '';
$this->response = [ 'success' => false ];
$this->handler = $handler;
$this->data = null;
$this->rawdata = null;
$this->site_id = null;
$this->prvkey = Config::get('system','prvkey');
if($localdata) {
$this->rawdata = $localdata;
}
else {
$this->rawdata = file_get_contents('php://input');
// All access to the zot endpoint must use http signatures
if (! $this->Valid_Httpsig()) {
logger('signature failed');
$this->error = true;
$this->response['message'] = 'signature invalid';
return;
}
}
logger('received raw: ' . print_r($this->rawdata,true), LOGGER_DATA);
if ($this->rawdata) {
$this->data = json_decode($this->rawdata,true);
}
else {
$this->error = true;
$this->response['message'] = 'no data';
}
logger('received_json: ' . json_encode($this->data,JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES), LOGGER_DATA);
logger('received: ' . print_r($this->data,true), LOGGER_DATA);
if ($this->data && is_array($this->data)) {
$this->encrypted = ((array_key_exists('encrypted',$this->data) && intval($this->data['encrypted'])) ? true : false);
if ($this->encrypted && $this->prvkey) {
$uncrypted = crypto_unencapsulate($this->data,$this->prvkey);
if ($uncrypted) {
$this->data = json_decode($uncrypted,true);
}
else {
$this->error = true;
$this->response['message'] = 'no data';
}
}
}
}
function run() {
if ($this->error) {
// make timing attacks on the decryption engine a bit more difficult
usleep(mt_rand(10000,100000));
return($this->response);
}
if ($this->data) {
if (array_key_exists('type',$this->data)) {
$this->messagetype = $this->data['type'];
}
if (! $this->messagetype) {
$this->error = true;
$this->response['message'] = 'no datatype';
return $this->response;
}
$this->sender = ((array_key_exists('sender',$this->data)) ? $this->data['sender'] : null);
$this->recipients = ((array_key_exists('recipients',$this->data)) ? $this->data['recipients'] : null);
$this->site_id = ((array_key_exists('site_id',$this->data)) ? $this->data['site_id'] : null);
}
if ($this->sender) {
$result = $this->ValidateSender();
if (! $result) {
$this->error = true;
return $this->response;
}
}
return $this->Dispatch();
}
function ValidateSender() {
$hub = Libzot::valid_hub($this->sender,$this->site_id);
if (! $hub) {
$x = Libzot::register_hub($this->sigdata['signer']);
if($x['success']) {
$hub = Libzot::valid_hub($this->sender,$this->site_id);
}
if(! $hub) {
$this->response['message'] = 'sender unknown';
return false;
}
}
if (! check_siteallowed($hub['hubloc_url'])) {
$this->response['message'] = 'forbidden';
return false;
}
if (! check_channelallowed($this->sender)) {
$this->response['message'] = 'forbidden';
return false;
}
Libzot::update_hub_connected($hub,$this->site_id);
$this->validated = true;
$this->hub = $hub;
return true;
}
function Valid_Httpsig() {
$result = false;
$this->sigdata = HTTPSig::verify($this->rawdata);
if ($this->sigdata && $this->sigdata['header_signed'] && $this->sigdata['header_valid']) {
$result = true;
// It is OK to not have signed content - not all messages provide content.
// But if it is signed, it has to be valid
if (($this->sigdata['content_signed']) && (! $this->sigdata['content_valid'])) {
$result = false;
}
}
return $result;
}
function Dispatch() {
switch ($this->messagetype) {
case 'request':
$this->response = $this->handler->Request($this->data,$this->hub);
break;
case 'purge':
$this->response = $this->handler->Purge($this->sender,$this->recipients,$this->hub);
break;
case 'refresh':
$this->response = $this->handler->Refresh($this->sender,$this->recipients,$this->hub);
break;
case 'rekey':
$this->response = $this->handler->Rekey($this->sender, $this->data,$this->hub);
break;
case 'activity':
case 'response': // upstream message
case 'sync':
default:
$this->response = $this->handler->Notify($this->data,$this->hub);
break;
}
logger('response_to_return: ' . print_r($this->response,true),LOGGER_DATA);
if ($this->encrypted) {
$this->EncryptResponse();
}
return($this->response);
}
function EncryptResponse() {
$algorithm = Libzot::best_algorithm($this->hub['site_crypto']);
if ($algorithm) {
$this->response = crypto_encapsulate(json_encode($this->response),$this->hub['hubloc_sitekey'], $algorithm);
}
}
}

View file

@ -0,0 +1,266 @@
<?php
namespace Zotlabs\Zot6;
use Zotlabs\Lib\Libzot;
use Zotlabs\Lib\Queue;
class Zot6Handler implements IHandler {
function Notify($data,$hub) {
return self::reply_notify($data,$hub);
}
function Request($data,$hub) {
return self::reply_message_request($data,$hub);
}
function Rekey($sender,$data,$hub) {
return self::reply_rekey_request($sender,$data,$hub);
}
function Refresh($sender,$recipients,$hub) {
return self::reply_refresh($sender,$recipients,$hub);
}
function Purge($sender,$recipients,$hub) {
return self::reply_purge($sender,$recipients,$hub);
}
// Implementation of specific methods follows;
// These generally do a small amout of validation and call Libzot
// to do any heavy lifting
static function reply_notify($data,$hub) {
$ret = [ 'success' => false ];
logger('notify received from ' . $hub['hubloc_url']);
$x = Libzot::fetch($data);
$ret['delivery_report'] = $x;
$ret['success'] = true;
return $ret;
}
/**
* @brief Remote channel info (such as permissions or photo or something)
* has been updated. Grab a fresh copy and sync it.
*
* The difference between refresh and force_refresh is that force_refresh
* unconditionally creates a directory update record, even if no changes were
* detected upon processing.
*
* @param array $sender
* @param array $recipients
*
* @return json_return_and_die()
*/
static function reply_refresh($sender, $recipients,$hub) {
$ret = array('success' => false);
if($recipients) {
// This would be a permissions update, typically for one connection
foreach ($recipients as $recip) {
$r = q("select channel.*,xchan.* from channel
left join xchan on channel_hash = xchan_hash
where channel_hash ='%s' limit 1",
dbesc($recip)
);
$x = Libzot::refresh( [ 'hubloc_id_url' => $hub['hubloc_id_url'] ], $r[0], (($msgtype === 'force_refresh') ? true : false));
}
}
else {
// system wide refresh
$x = Libzot::refresh( [ 'hubloc_id_url' => $hub['hubloc_id_url'] ], null, (($msgtype === 'force_refresh') ? true : false));
}
$ret['success'] = true;
return $ret;
}
/**
* @brief Process a message request.
*
* If a site receives a comment to a post but finds they have no parent to attach it with, they
* may send a 'request' packet containing the message_id of the missing parent. This is the handler
* for that packet. We will create a message_list array of the entire conversation starting with
* the missing parent and invoke delivery to the sender of the packet.
*
* Zotlabs/Daemon/Deliver.php (for local delivery) and
* mod/post.php???? @fixme (for web delivery) detect the existence of
* this 'message_list' at the destination and split it into individual messages which are
* processed/delivered in order.
*
*
* @param array $data
* @return array
*/
static function reply_message_request($data,$hub) {
$ret = [ 'success' => false ];
$message_id = EMPTY_STR;
if(array_key_exists('data',$data))
$ptr = $data['data'];
if(is_array($ptr) && array_key_exists(0,$ptr)) {
$ptr = $ptr[0];
}
if(is_string($ptr)) {
$message_id = $ptr;
}
if(is_array($ptr) && array_key_exists('id',$ptr)) {
$message_id = $ptr['id'];
}
if (! $message_id) {
$ret['message'] = 'no message_id';
logger('no message_id');
return $ret;
}
$sender = $hub['hubloc_hash'];
/*
* Find the local channel in charge of this post (the first and only recipient of the request packet)
*/
$arr = $data['recipients'][0];
$c = q("select * from channel left join xchan on channel_hash = xchan_hash where channel_hash = '%s' limit 1",
dbesc($arr['portable_id'])
);
if (! $c) {
logger('recipient channel not found.');
$ret['message'] .= 'recipient not found.' . EOL;
return $ret;
}
/*
* fetch the requested conversation
*/
$messages = zot_feed($c[0]['channel_id'],$sender_hash, [ 'message_id' => $data['message_id'], 'encoding' => 'activitystreams' ]);
return (($messages) ? : [] );
}
static function rekey_request($sender,$data,$hub) {
$ret = array('success' => false);
// newsig is newkey signed with oldkey
// The original xchan will remain. In Zot/Receiver we will have imported the new xchan and hubloc to verify
// the packet authenticity. What we will do now is verify that the keychange operation was signed by the
// oldkey, and if so change all the abook, abconfig, group, and permission elements which reference the
// old xchan_hash.
if((! $data['old_key']) && (! $data['new_key']) && (! $data['new_sig']))
return $ret;
$old = null;
if(Libzot::verify($data['old_guid'],$data['old_guid_sig'],$data['old_key'])) {
$oldhash = make_xchan_hash($data['old_guid'],$data['old_key']);
$old = q("select * from xchan where xchan_hash = '%s' limit 1",
dbesc($oldhash)
);
}
else
return $ret;
if(! $old) {
return $ret;
}
$xchan = $old[0];
if(! Libzot::verify($data['new_key'],$data['new_sig'],$xchan['xchan_pubkey'])) {
return $ret;
}
$r = q("select * from xchan where xchan_hash = '%s' limit 1",
dbesc($sender)
);
$newxchan = $r[0];
// @todo
// if ! $update create a linked identity
xchan_change_key($xchan,$newxchan,$data);
$ret['success'] = true;
return $ret;
}
/**
* @brief
*
* @param array $sender
* @param array $recipients
*
* return json_return_and_die()
*/
static function reply_purge($sender, $recipients, $hub) {
$ret = array('success' => false);
if ($recipients) {
// basically this means "unfriend"
foreach ($recipients as $recip) {
$r = q("select channel.*,xchan.* from channel
left join xchan on channel_hash = xchan_hash
where channel_hash = '%s' and channel_guid_sig = '%s' limit 1",
dbesc($recip)
);
if ($r) {
$r = q("select abook_id from abook where uid = %d and abook_xchan = '%s' limit 1",
intval($r[0]['channel_id']),
dbesc($sender)
);
if ($r) {
contact_remove($r[0]['channel_id'],$r[0]['abook_id']);
}
}
}
$ret['success'] = true;
}
else {
// Unfriend everybody - basically this means the channel has committed suicide
remove_all_xchan_resources($sender);
$ret['success'] = true;
}
return $ret;
}
}

View file

@ -54,7 +54,7 @@ define ( 'STD_VERSION', '3.7.1' );
define ( 'ZOT_REVISION', '6.0a' );
define ( 'DB_UPDATE_VERSION', 1216 );
define ( 'DB_UPDATE_VERSION', 1217 );
define ( 'PROJECT_BASE', __DIR__ );
@ -2072,8 +2072,8 @@ function load_pdl() {
if (! count(App::$layout)) {
$arr = [
'module' => App::$module,
'layout' => ''
'module' => App::$module,
'layout' => ''
];
/**
* @hooks load_pdl
@ -2093,6 +2093,16 @@ function load_pdl() {
if((! $s) && (($p = theme_include($n)) != ''))
$s = @file_get_contents($p);
elseif(file_exists('addon/'. App::$module . '/' . $n))
$s = @file_get_contents('addon/'. App::$module . '/' . $n);
$arr = [
'module' => App::$module,
'layout' => $s
];
call_hooks('alter_pdl',$arr);
$s = $arr['layout'];
if($s) {
App::$comanche->parse($s);
App::$pdl = $s;

View file

@ -127,6 +127,7 @@ CREATE TABLE IF NOT EXISTS `app` (
`app_deleted` int(11) NOT NULL DEFAULT 0 ,
`app_system` int(11) NOT NULL DEFAULT 0 ,
`app_plugin` char(191) NOT NULL DEFAULT '',
`app_options` int(11) NOT NULL DEFAULT 0 ,
`app_created` datetime NOT NULL DEFAULT '0001-01-01 00:00:00',
`app_edited` datetime NOT NULL DEFAULT '0001-01-01 00:00:00',
PRIMARY KEY (`id`),

View file

@ -125,6 +125,7 @@ CREATE TABLE "app" (
"app_deleted" smallint NOT NULL DEFAULT '0',
"app_system" smallint NOT NULL DEFAULT '0',
"app_plugin" text NOT NULL DEFAULT '',
"app_options" smallint NOT NULL DEFAULT '0',
"app_created" timestamp NOT NULL DEFAULT '0001-01-01 00:00:00',
"app_edited" timestamp NOT NULL DEFAULT '0001-01-01 00:00:00',
PRIMARY KEY ("id")

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff