aboutsummaryrefslogtreecommitdiffstats
path: root/include
diff options
context:
space:
mode:
Diffstat (limited to 'include')
-rw-r--r--include/Contact.php82
-rw-r--r--include/acl_selectors.php2
-rw-r--r--include/api.php53
-rw-r--r--include/apps.php8
-rw-r--r--include/auth.php37
-rw-r--r--include/bbcode.php19
-rw-r--r--include/chat.php6
-rw-r--r--include/contact_selectors.php4
-rw-r--r--include/contact_widgets.php1
-rw-r--r--include/conversation.php9
-rw-r--r--include/crypto.php192
-rwxr-xr-xinclude/diaspora.php2691
-rw-r--r--include/dir_fns.php31
-rw-r--r--include/enotify.php16
-rw-r--r--include/externals.php24
-rw-r--r--include/features.php2
-rw-r--r--include/identity.php78
-rwxr-xr-xinclude/items.php99
-rw-r--r--include/nav.php7
-rw-r--r--include/permissions.php80
-rw-r--r--include/photo/photo_driver.php22
-rw-r--r--include/photos.php88
-rw-r--r--include/profile_selectors.php55
-rw-r--r--include/widgets.php7
-rw-r--r--include/zot.php85
25 files changed, 3495 insertions, 203 deletions
diff --git a/include/Contact.php b/include/Contact.php
index 787612c83..140f449af 100644
--- a/include/Contact.php
+++ b/include/Contact.php
@@ -162,7 +162,7 @@ function user_remove($uid) {
}
-function account_remove($account_id,$local = true) {
+function account_remove($account_id,$local = true,$unset_session=true) {
logger('account_remove: ' . $account_id);
@@ -185,6 +185,7 @@ function account_remove($account_id,$local = true) {
$r = q("select * from account where account_id = %d limit 1",
intval($account_id)
);
+ $account_email=$r[0]['account_email'];
if(! $r) {
logger('account_remove: No account with id: ' . $account_id);
@@ -196,7 +197,7 @@ function account_remove($account_id,$local = true) {
);
if($x) {
foreach($x as $xx) {
- channel_remove($xx['channel_id'],$local);
+ channel_remove($xx['channel_id'],$local,false);
}
}
@@ -204,11 +205,17 @@ function account_remove($account_id,$local = true) {
intval($account_id)
);
+ if ($unset_session) {
+ unset($_SESSION['authenticated']);
+ unset($_SESSION['uid']);
+ notice( sprintf(t("User '%s' deleted"),$account_email) . EOL);
+ goaway(get_app()->get_baseurl());
+ }
return $r;
}
-function channel_remove($channel_id, $local = true) {
+function channel_remove($channel_id, $local = true, $unset_session=true) {
if(! $channel_id)
return;
@@ -292,7 +299,7 @@ function channel_remove($channel_id, $local = true) {
proc_run('php','include/directory.php',$channel_id);
- if($channel_id == local_user()) {
+ if($channel_id == local_user() && $unset_session) {
unset($_SESSION['authenticated']);
unset($_SESSION['uid']);
goaway($a->get_baseurl());
@@ -506,73 +513,6 @@ function contact_remove($channel_id, $abook_id) {
}
-// sends an unfriend message. Does not remove the contact
-
-function terminate_friendship($user,$self,$contact) {
-
-
- $a = get_app();
-
- require_once('include/datetime.php');
-
- if($contact['network'] === NETWORK_DFRN) {
- require_once('include/items.php');
- dfrn_deliver($user,$contact,'placeholder', 1);
- }
-
-}
-
-
-// Contact has refused to recognise us as a friend. We will start a countdown.
-// If they still don't recognise us in 32 days, the relationship is over,
-// and we won't waste any more time trying to communicate with them.
-// This provides for the possibility that their database is temporarily messed
-// up or some other transient event and that there's a possibility we could recover from it.
-
-if(! function_exists('mark_for_death')) {
-function mark_for_death($contact) {
-
- if($contact['archive'])
- return;
-
- if($contact['term_date'] == '0000-00-00 00:00:00') {
- q("UPDATE `contact` SET `term_date` = '%s' WHERE `id` = %d LIMIT 1",
- dbesc(datetime_convert()),
- intval($contact['id'])
- );
- }
- else {
-
- // TODO: We really should send a notification to the owner after 2-3 weeks
- // so they won't be surprised when the contact vanishes and can take
- // remedial action if this was a serious mistake or glitch
-
- $expiry = $contact['term_date'] . ' + 32 days ';
- if(datetime_convert() > datetime_convert('UTC','UTC',$expiry)) {
-
- // relationship is really truly dead.
- // archive them rather than delete
- // though if the owner tries to unarchive them we'll start the whole process over again
-
- q("update contact set `archive` = 1 where id = %d limit 1",
- intval($contact['id'])
- );
-
- //contact_remove($contact['id']);
-
- }
- }
-
-}}
-
-if(! function_exists('unmark_for_death')) {
-function unmark_for_death($contact) {
- // It's a miracle. Our dead contact has inexplicably come back to life.
- q("UPDATE `contact` SET `term_date` = '%s' WHERE `id` = %d LIMIT 1",
- dbesc('0000-00-00 00:00:00'),
- intval($contact['id'])
- );
-}}
function random_profile() {
$r = q("select xchan_url from xchan left join hubloc on hubloc_hash = xchan_hash where hubloc_connected > UTC_TIMESTAMP() - interval 30 day order by rand() limit 1");
diff --git a/include/acl_selectors.php b/include/acl_selectors.php
index 8d94264e4..0b68ba227 100644
--- a/include/acl_selectors.php
+++ b/include/acl_selectors.php
@@ -237,7 +237,7 @@ function populate_acl($defaults = null,$show_jotnets = true) {
$tpl = get_markup_template("acl_selector.tpl");
$o = replace_macros($tpl, array(
- '$showall'=> t("Visible to everybody"),
+ '$showall'=> t("Visible to your default audience"),
'$show' => t("Show"),
'$hide' => t("Don't show"),
'$allowcid' => json_encode($allow_cid),
diff --git a/include/api.php b/include/api.php
index 57551a3b0..c0f54af19 100644
--- a/include/api.php
+++ b/include/api.php
@@ -119,16 +119,36 @@ require_once('include/items.php');
// process normal login request
require_once('include/auth.php');
+ $channel_login = 0;
$record = account_verify_password($_SERVER['PHP_AUTH_USER'],$_SERVER['PHP_AUTH_PW']);
if(! $record) {
- logger('API_login failure: ' . print_r($_SERVER,true), LOGGER_DEBUG);
- header('WWW-Authenticate: Basic realm="Red"');
- header('HTTP/1.0 401 Unauthorized');
- die('This api requires login');
+ $r = q("select * from channel where channel_address = '%s' limit 1",
+ dbesc($_SERVER['PHP_AUTH_USER'])
+ );
+ if ($r) {
+ $x = q("select * from account where account_id = %d limit 1",
+ intval($r[0]['channel_account_id'])
+ );
+ if ($x) {
+ $record = account_verify_password($x[0]['account_email'],$_SERVER['PHP_AUTH_PW']);
+ if($record)
+ $channel_login = $r[0]['channel_id'];
+ }
+ }
+ if(! $record) {
+ logger('API_login failure: ' . print_r($_SERVER,true), LOGGER_DEBUG);
+ header('WWW-Authenticate: Basic realm="Red"');
+ header('HTTP/1.0 401 Unauthorized');
+ die('This api requires login');
+ }
}
require_once('include/security.php');
authenticate_success($record);
+
+ if($channel_login)
+ change_channel($channel_login);
+
$_SESSION['allow_api'] = true;
}
@@ -1498,6 +1518,9 @@ require_once('include/items.php');
$a = get_app();
$ret = array();
+ if(! $r)
+ return $ret;
+
foreach($r as $item) {
localize_item($item);
@@ -1875,17 +1898,19 @@ require_once('include/items.php');
);
$ret = Array();
- foreach($r as $item) {
- if ($box == "inbox" || $item['from-url'] != $profile_url){
- $recipient = $user_info;
- $sender = api_get_user($a,$item['contact-id']);
- }
- elseif ($box == "sentbox" || $item['from-url'] != $profile_url){
- $recipient = api_get_user($a,$item['contact-id']);
- $sender = $user_info;
+ if($r) {
+ foreach($r as $item) {
+ if ($box == "inbox" || $item['from-url'] != $profile_url){
+ $recipient = $user_info;
+ $sender = api_get_user($a,$item['contact-id']);
+ }
+ elseif ($box == "sentbox" || $item['from-url'] != $profile_url){
+ $recipient = api_get_user($a,$item['contact-id']);
+ $sender = $user_info;
+ }
+
+ $ret[]=api_format_messages($item, $recipient, $sender);
}
-
- $ret[]=api_format_messages($item, $recipient, $sender);
}
diff --git a/include/apps.php b/include/apps.php
index 135eaa99a..bd50b953a 100644
--- a/include/apps.php
+++ b/include/apps.php
@@ -141,7 +141,13 @@ function translate_system_apps(&$arr) {
'Chat' => t('Chat'),
'Search' => t('Search'),
'Probe' => t('Probe'),
- 'Suggest' => t('Suggest')
+ 'Suggest' => t('Suggest'),
+ 'Random Channel' => t('Random Channel'),
+ 'Invite' => t('Invite'),
+ 'Features' => t('Features'),
+ 'Language' => t('Language'),
+ 'Post' => t('Post'),
+ 'Profile Photo' => t('Profile Photo')
);
if(array_key_exists($arr['name'],$apps))
diff --git a/include/auth.php b/include/auth.php
index 8e02b7b4f..cc07917b7 100644
--- a/include/auth.php
+++ b/include/auth.php
@@ -128,13 +128,40 @@ if((isset($_SESSION)) && (x($_SESSION,'authenticated')) && ((! (x($_POST,'auth-p
// first check if we're enforcing that sessions can't change IP address
- if($_SESSION['addr'] != $_SERVER['REMOTE_ADDR']) {
+ if($_SESSION['addr'] && $_SESSION['addr'] != $_SERVER['REMOTE_ADDR']) {
logger('SECURITY: Session IP address changed: ' . $_SESSION['addr'] . ' != ' . $_SERVER['REMOTE_ADDR']);
- if(get_config('system','paranoia')) {
- logger('Session address changed. Paranoid setting in effect, blocking session. '
+
+ $partial1 = substr($_SESSION['addr'],0,strrpos($_SESSION['addr'],'.'));
+ $partial2 = substr($_SERVER['REMOTE_ADDR'],0,strrpos($_SERVER['REMOTE_ADDR'],'.'));
+
+
+ $paranoia = intval(get_pconfig($_SESSION['uid'],'system','paranoia'));
+ if(! $paranoia)
+ $paranoia = intval(get_config('system','paranoia'));
+
+ switch($paranoia) {
+ case 0:
+ // no IP checking
+ break;
+ case 2:
+ // check 2 octets
+ $partial1 = substr($partial1,0,strrpos($partial1,'.'));
+ $partial2 = substr($partial2,0,strrpos($partial2,'.'));
+ if($partial1 == $partial2)
+ break;
+ case 1:
+ // check 3 octets
+ if($partial1 == $partial2)
+ break;
+ case 3:
+ default:
+ // check any difference at all
+ logger('Session address changed. Paranoid setting in effect, blocking session. '
. $_SESSION['addr'] . ' != ' . $_SERVER['REMOTE_ADDR']);
- nuke_session();
- goaway(z_root());
+ nuke_session();
+ goaway(z_root());
+ break;
+
}
}
diff --git a/include/bbcode.php b/include/bbcode.php
index 45126c0eb..a7055fc45 100644
--- a/include/bbcode.php
+++ b/include/bbcode.php
@@ -502,6 +502,13 @@ function bbcode($Text,$preserve_nl = false, $tryoembed = true) {
if (strpos($Text,'[o]') !== false) {
$Text = preg_replace("(\[o\](.*?)\[\/o\])ism",'<span class="overline">$1</span>',$Text);
}
+ if (strpos($Text,'[sup]') !== false) {
+ $Text = preg_replace("(\[sup\](.*?)\[\/sup\])ism",'<sup>$1</sup>',$Text);
+ }
+ if (strpos($Text,'[sub]') !== false) {
+ $Text = preg_replace("(\[sub\](.*?)\[\/sub\])ism",'<sub>$1</sub>',$Text);
+ }
+
// Check for colored text
if (strpos($Text,'[/color]') !== false) {
$Text = preg_replace("(\[color=(.*?)\](.*?)\[\/color\])ism","<span style=\"color: $1;\">$2</span>",$Text);
@@ -657,24 +664,24 @@ function bbcode($Text,$preserve_nl = false, $tryoembed = true) {
// [img=widthxheight]pathtoimage[/img]
if (strpos($Text,'[/img]') !== false) {
- $Text = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", '<img src="$3" style="width: 100%; max-width: $1px;" alt="' . t('Image/photo') . '" >', $Text);
+ $Text = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", '<img src="$3" style="width: 100%; max-width: $1px;" alt="' . t('Image/photo') . '" />', $Text);
}
if (strpos($Text,'[/zmg]') !== false) {
- $Text = preg_replace("/\[zmg\=([0-9]*)x([0-9]*)\](.*?)\[\/zmg\]/ism", '<img class="zrl" src="$3" style="width: 100%; max-width: $1px;" alt="' . t('Image/photo') . '" >', $Text);
+ $Text = preg_replace("/\[zmg\=([0-9]*)x([0-9]*)\](.*?)\[\/zmg\]/ism", '<img class="zrl" src="$3" style="width: 100%; max-width: $1px;" alt="' . t('Image/photo') . '" />', $Text);
}
// [img=widthxheight float={left, right}]pathtoimage[/img]
if (strpos($Text,'[/img]') !== false) {
- $Text = preg_replace("/\[img\=([0-9]*)x([0-9]*) float=left\](.*?)\[\/img\]/ism", '<img src="$3" style="width: 100%; max-width: $1px; float: left;" alt="' . t('Image/photo') . '" >', $Text);
+ $Text = preg_replace("/\[img\=([0-9]*)x([0-9]*) float=left\](.*?)\[\/img\]/ism", '<img src="$3" style="width: 100%; max-width: $1px; float: left;" alt="' . t('Image/photo') . '" />', $Text);
}
if (strpos($Text,'[/img]') !== false) {
- $Text = preg_replace("/\[img\=([0-9]*)x([0-9]*) float=right\](.*?)\[\/img\]/ism", '<img src="$3" style="width: 100%; max-width: $1px; float: right;" alt="' . t('Image/photo') . '" >', $Text);
+ $Text = preg_replace("/\[img\=([0-9]*)x([0-9]*) float=right\](.*?)\[\/img\]/ism", '<img src="$3" style="width: 100%; max-width: $1px; float: right;" alt="' . t('Image/photo') . '" />', $Text);
}
if (strpos($Text,'[/zmg]') !== false) {
- $Text = preg_replace("/\[zmg\=([0-9]*)x([0-9]*) float=left\](.*?)\[\/zmg\]/ism", '<img class="zrl" src="$3" style="width: 100%; max-width: $1px; float: left;" alt="' . t('Image/photo') . '" >', $Text);
+ $Text = preg_replace("/\[zmg\=([0-9]*)x([0-9]*) float=left\](.*?)\[\/zmg\]/ism", '<img class="zrl" src="$3" style="width: 100%; max-width: $1px; float: left;" alt="' . t('Image/photo') . '" />', $Text);
}
if (strpos($Text,'[/zmg]') !== false) {
- $Text = preg_replace("/\[zmg\=([0-9]*)x([0-9]*) float=right\](.*?)\[\/zmg\]/ism", '<img class="zrl" src="$3" style="width: 100%; max-width: $1px; float: right;" alt="' . t('Image/photo') . '" >', $Text);
+ $Text = preg_replace("/\[zmg\=([0-9]*)x([0-9]*) float=right\](.*?)\[\/zmg\]/ism", '<img class="zrl" src="$3" style="width: 100%; max-width: $1px; float: right;" alt="' . t('Image/photo') . '" />', $Text);
}
// style (sanitized)
diff --git a/include/chat.php b/include/chat.php
index 9d2341dfa..5f69853e7 100644
--- a/include/chat.php
+++ b/include/chat.php
@@ -119,10 +119,10 @@ function chatroom_enter($observer_xchan,$room_id,$status,$client) {
$limit = service_class_fetch($r[0]['cr_uid'],'chatters_inroom');
if($limit !== false) {
- $x = q("select count(*) as total from chatpresence where cp_room = %d",
+ $y = q("select count(*) as total from chatpresence where cp_room = %d",
intval($room_id)
);
- if($x && $x[0]['total'] > $limit) {
+ if($y && $y[0]['total'] > $limit) {
notice( t('Room is full') . EOL);
return false;
}
@@ -235,6 +235,8 @@ function chat_message($uid,$room_id,$xchan,$text) {
*/
function chatroom_flush($room_id,$xchan) {
+
+
$date_limit = date('Y-m-d H:i:s', time() - 3600 * MAX_CHATROOM_HOURS);
$d = q("delete from chat where chat_room = %d and chat_xchan = '%s' and created < '%s'",
intval($room_id),
diff --git a/include/contact_selectors.php b/include/contact_selectors.php
index a3cfd2489..726efce9d 100644
--- a/include/contact_selectors.php
+++ b/include/contact_selectors.php
@@ -20,6 +20,7 @@ function contact_profile_assign($current) {
return $o;
}
+/* unused currently
function contact_reputation($current) {
@@ -27,7 +28,7 @@ function contact_reputation($current) {
$o .= "<select id=\"contact-reputation-selector\" name=\"reputation\" />\r\n";
$rep = array(
- 0 => t('Unknown | Not categorised'),
+ 0 => t('Unknown | Not categorized'),
1 => t('Block immediately'),
2 => t('Shady, spammer, self-marketer'),
3 => t('Known to me, but no opinion'),
@@ -43,6 +44,7 @@ function contact_reputation($current) {
return $o;
}
+*/
function contact_poll_interval($current, $disabled = false) {
diff --git a/include/contact_widgets.php b/include/contact_widgets.php
index 758b7291b..28a9fcfd3 100644
--- a/include/contact_widgets.php
+++ b/include/contact_widgets.php
@@ -79,6 +79,7 @@ function categories_widget($baseurl,$selected = '') {
and term.uid = item.uid
and term.type = %d
and item.author_xchan = '%s'
+ and item.item_restrict = 0
order by term.term asc",
intval($a->profile['profile_uid']),
intval(TERM_CATEGORY),
diff --git a/include/conversation.php b/include/conversation.php
index 5481037e7..836bd1b24 100644
--- a/include/conversation.php
+++ b/include/conversation.php
@@ -179,8 +179,8 @@ function localize_item(&$item){
if (activity_match($item['verb'],ACTIVITY_FRIEND)) {
-
-// if ($item['obj_type']=="" || $item['obj_type']!== ACTIVITY_OBJ_PERSON) return;
+ if ($item['obj_type'] == "" || $item['obj_type'] !== ACTIVITY_OBJ_PERSON)
+ return;
$Aname = $item['author']['xchan_name'];
$Alink = $item['author']['xchan_url'];
@@ -902,6 +902,7 @@ function item_photo_menu($item){
$contact_url="";
$pm_url="";
$vsrc_link = "";
+ $follow_url = "";
if(local_user()) {
$ssl_state = true;
@@ -923,6 +924,9 @@ function item_photo_menu($item){
if($a->contacts && array_key_exists($item['author_xchan'],$a->contacts))
$contact = $a->contacts[$item['author_xchan']];
+ else
+ if(local_user() && $item['author']['xchan_addr'])
+ $follow_url = z_root() . '/follow/?f=&url=' . $item['author']['xchan_addr'];
if($contact) {
$poke_link = $a->get_baseurl($ssl_state) . '/poke/?f=&c=' . $contact['abook_id'];
@@ -940,6 +944,7 @@ function item_photo_menu($item){
t("View Profile") => $profile_link,
t("View Photos") => $photos_link,
t("Matrix Activity") => $posts_link,
+ t("Follow") => $follow_url,
t("Edit Contact") => $contact_url,
t("Send PM") => $pm_url,
t("Poke") => $poke_link
diff --git a/include/crypto.php b/include/crypto.php
index 33cdc10c0..c053dfae2 100644
--- a/include/crypto.php
+++ b/include/crypto.php
@@ -127,3 +127,195 @@ function new_keypair($bits) {
}
+function pkcs1to8($oldkey,$len) {
+
+ if($len == 4096)
+ $c = 'g';
+ if($len == 2048)
+ $c = 'Q';
+
+ if(strstr($oldkey,'BEGIN PUBLIC'))
+ return $oldkey;
+
+ $oldkey = str_replace('-----BEGIN RSA PUBLIC KEY-----', '', $oldkey);
+ $oldkey = trim(str_replace('-----END RSA PUBLIC KEY-----', '', $oldkey));
+ $key = 'MIICIjANBgkqhkiG9w0BAQEFAAOCA' . $c . '8A' . str_replace("\n", '', $oldkey);
+ $key = "-----BEGIN PUBLIC KEY-----\n" . wordwrap($key, 64, "\n", true) . "\n-----END PUBLIC KEY-----";
+ return $key;
+}
+
+function pkcs8to1($oldkey,$len) {
+
+ if(strstr($oldkey,'BEGIN RSA'))
+ return $oldkey;
+
+ $oldkey = str_replace('-----BEGIN PUBLIC KEY-----', '', $oldkey);
+ $oldkey = trim(str_replace('-----END PUBLIC KEY-----', '', $oldkey));
+ $key = str_replace("\n",'',$oldkey);
+ $key = substr($key,32);
+ $key = "-----BEGIN RSA PUBLIC KEY-----\n" . wordwrap($key, 64, "\n", true) . "\n-----END RSA PUBLIC KEY-----";
+ return $key;
+}
+
+
+function DerToPem($Der, $Private=false)
+{
+ //Encode:
+ $Der = base64_encode($Der);
+ //Split lines:
+ $lines = str_split($Der, 65);
+ $body = implode("\n", $lines);
+ //Get title:
+ $title = $Private? 'RSA PRIVATE KEY' : 'PUBLIC KEY';
+ //Add wrapping:
+ $result = "-----BEGIN {$title}-----\n";
+ $result .= $body . "\n";
+ $result .= "-----END {$title}-----\n";
+
+ return $result;
+}
+
+function DerToRsa($Der)
+{
+ //Encode:
+ $Der = base64_encode($Der);
+ //Split lines:
+ $lines = str_split($Der, 64);
+ $body = implode("\n", $lines);
+ //Get title:
+ $title = 'RSA PUBLIC KEY';
+ //Add wrapping:
+ $result = "-----BEGIN {$title}-----\n";
+ $result .= $body . "\n";
+ $result .= "-----END {$title}-----\n";
+
+ return $result;
+}
+
+
+function pkcs8_encode($Modulus,$PublicExponent) {
+ //Encode key sequence
+ $modulus = new ASNValue(ASNValue::TAG_INTEGER);
+ $modulus->SetIntBuffer($Modulus);
+ $publicExponent = new ASNValue(ASNValue::TAG_INTEGER);
+ $publicExponent->SetIntBuffer($PublicExponent);
+ $keySequenceItems = array($modulus, $publicExponent);
+ $keySequence = new ASNValue(ASNValue::TAG_SEQUENCE);
+ $keySequence->SetSequence($keySequenceItems);
+ //Encode bit string
+ $bitStringValue = $keySequence->Encode();
+ $bitStringValue = chr(0x00) . $bitStringValue; //Add unused bits byte
+ $bitString = new ASNValue(ASNValue::TAG_BITSTRING);
+ $bitString->Value = $bitStringValue;
+ //Encode body
+ $bodyValue = "\x30\x0d\x06\x09\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01\x05\x00" . $bitString->Encode();
+ $body = new ASNValue(ASNValue::TAG_SEQUENCE);
+ $body->Value = $bodyValue;
+ //Get DER encoded public key:
+ $PublicDER = $body->Encode();
+ return $PublicDER;
+}
+
+
+function pkcs1_encode($Modulus,$PublicExponent) {
+ //Encode key sequence
+ $modulus = new ASNValue(ASNValue::TAG_INTEGER);
+ $modulus->SetIntBuffer($Modulus);
+ $publicExponent = new ASNValue(ASNValue::TAG_INTEGER);
+ $publicExponent->SetIntBuffer($PublicExponent);
+ $keySequenceItems = array($modulus, $publicExponent);
+ $keySequence = new ASNValue(ASNValue::TAG_SEQUENCE);
+ $keySequence->SetSequence($keySequenceItems);
+ //Encode bit string
+ $bitStringValue = $keySequence->Encode();
+ return $bitStringValue;
+}
+
+
+function metopem($m,$e) {
+ $der = pkcs8_encode($m,$e);
+ $key = DerToPem($der,false);
+ return $key;
+}
+
+
+function pubrsatome($key,&$m,&$e) {
+ require_once('library/asn1.php');
+ require_once('include/salmon.php');
+
+ $lines = explode("\n",$key);
+ unset($lines[0]);
+ unset($lines[count($lines)]);
+ $x = base64_decode(implode('',$lines));
+
+ $r = ASN_BASE::parseASNString($x);
+
+ $m = base64url_decode($r[0]->asnData[0]->asnData);
+ $e = base64url_decode($r[0]->asnData[1]->asnData);
+}
+
+
+function rsatopem($key) {
+ pubrsatome($key,$m,$e);
+ return(metopem($m,$e));
+}
+
+function pemtorsa($key) {
+ pemtome($key,$m,$e);
+ return(metorsa($m,$e));
+}
+
+function pemtome($key,&$m,&$e) {
+ require_once('include/salmon.php');
+ $lines = explode("\n",$key);
+ unset($lines[0]);
+ unset($lines[count($lines)]);
+ $x = base64_decode(implode('',$lines));
+
+ $r = ASN_BASE::parseASNString($x);
+
+ $m = base64url_decode($r[0]->asnData[1]->asnData[0]->asnData[0]->asnData);
+ $e = base64url_decode($r[0]->asnData[1]->asnData[0]->asnData[1]->asnData);
+}
+
+function metorsa($m,$e) {
+ $der = pkcs1_encode($m,$e);
+ $key = DerToRsa($der);
+ return $key;
+}
+
+function salmon_key($pubkey) {
+ pemtome($pubkey,$m,$e);
+ return 'RSA' . '.' . base64url_encode($m,true) . '.' . base64url_encode($e,true) ;
+}
+
+// old function for providing mysql compatible encryption and is also
+// used in Friendica 'RINO'. This function is messy and should be retired.
+
+
+if(! function_exists('aes_decrypt')) {
+function aes_decrypt($val,$ky)
+{
+ $key="\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
+ for($a=0;$a<strlen($ky);$a++)
+ $key[$a%16]=chr(ord($key[$a%16]) ^ ord($ky[$a]));
+ $mode = MCRYPT_MODE_ECB;
+ $enc = MCRYPT_RIJNDAEL_128;
+ $dec = @mcrypt_decrypt($enc, $key, $val, $mode, @mcrypt_create_iv( @mcrypt_get_iv_size($enc, $mode), MCRYPT_DEV_URANDOM ) );
+ return rtrim($dec,(( ord(substr($dec,strlen($dec)-1,1))>=0 and ord(substr($dec, strlen($dec)-1,1))<=16)? chr(ord( substr($dec,strlen($dec)-1,1))):null));
+}}
+
+
+if(! function_exists('aes_encrypt')) {
+function aes_encrypt($val,$ky)
+{
+ $key="\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
+ for($a=0;$a<strlen($ky);$a++)
+ $key[$a%16]=chr(ord($key[$a%16]) ^ ord($ky[$a]));
+ $mode=MCRYPT_MODE_ECB;
+ $enc=MCRYPT_RIJNDAEL_128;
+ $val=str_pad($val, (16*(floor(strlen($val) / 16)+(strlen($val) % 16==0?2:1))), chr(16-(strlen($val) % 16)));
+ return mcrypt_encrypt($enc, $key, $val, $mode, mcrypt_create_iv( mcrypt_get_iv_size($enc, $mode), MCRYPT_DEV_URANDOM));
+}}
+
+
diff --git a/include/diaspora.php b/include/diaspora.php
new file mode 100755
index 000000000..6121466f2
--- /dev/null
+++ b/include/diaspora.php
@@ -0,0 +1,2691 @@
+<?php
+
+require_once('include/crypto.php');
+require_once('include/items.php');
+require_once('include/bb2diaspora.php');
+require_once('include/contact_selectors.php');
+//require_once('include/queue_fn.php');
+//require_once('include/lock.php');
+
+function diaspora_dispatch_public($msg) {
+
+ $enabled = intval(get_config('system','diaspora_enabled'));
+ if(! $enabled) {
+ logger('mod-diaspora: disabled');
+ return;
+ }
+
+ // find everybody following or allowing this author
+
+
+ $r = q("SELECT * from channel where channel_id in ( SELECT abook_channel from abook WHERE abook_network = 'diaspora' and abook_xchan = '%s' )",
+ dbesc($msg['author'])
+ );
+
+ // also need to look for those following public streams
+
+ if($r) {
+ foreach($r as $rr) {
+ logger('diaspora_public: delivering to: ' . $rr['channel_name'] . ' (' . $rr['channel_address'] . ') ');
+ diaspora_dispatch($rr,$msg);
+ }
+ }
+ else
+ logger('diaspora_public: no subscribers');
+}
+
+
+
+function diaspora_dispatch($importer,$msg,$attempt=1) {
+
+ $ret = 0;
+
+ $enabled = intval(get_config('system','diaspora_enabled'));
+ if(! $enabled) {
+ logger('mod-diaspora: disabled');
+ return;
+ }
+
+ // php doesn't like dashes in variable names
+
+ $msg['message'] = str_replace(
+ array('<activity_streams-photo>','</activity_streams-photo>'),
+ array('<asphoto>','</asphoto>'),
+ $msg['message']);
+
+
+ $parsed_xml = parse_xml_string($msg['message'],false);
+
+ $xmlbase = $parsed_xml->post;
+
+ logger('diaspora_dispatch: ' . print_r($xmlbase,true), LOGGER_DEBUG);
+
+
+ if($xmlbase->request) {
+ $ret = diaspora_request($importer,$xmlbase->request);
+ }
+ elseif($xmlbase->status_message) {
+ $ret = diaspora_post($importer,$xmlbase->status_message,$msg);
+ }
+ elseif($xmlbase->profile) {
+ $ret = diaspora_profile($importer,$xmlbase->profile,$msg);
+ }
+ elseif($xmlbase->comment) {
+ $ret = diaspora_comment($importer,$xmlbase->comment,$msg);
+ }
+ elseif($xmlbase->like) {
+ $ret = diaspora_like($importer,$xmlbase->like,$msg);
+ }
+ elseif($xmlbase->asphoto) {
+ $ret = diaspora_asphoto($importer,$xmlbase->asphoto,$msg);
+ }
+ elseif($xmlbase->reshare) {
+ $ret = diaspora_reshare($importer,$xmlbase->reshare,$msg);
+ }
+ elseif($xmlbase->retraction) {
+ $ret = diaspora_retraction($importer,$xmlbase->retraction,$msg);
+ }
+ elseif($xmlbase->signed_retraction) {
+ $ret = diaspora_signed_retraction($importer,$xmlbase->signed_retraction,$msg);
+ }
+ elseif($xmlbase->relayable_retraction) {
+ $ret = diaspora_signed_retraction($importer,$xmlbase->relayable_retraction,$msg);
+ }
+ elseif($xmlbase->photo) {
+ $ret = diaspora_photo($importer,$xmlbase->photo,$msg,$attempt);
+ }
+ elseif($xmlbase->conversation) {
+ $ret = diaspora_conversation($importer,$xmlbase->conversation,$msg);
+ }
+ elseif($xmlbase->message) {
+ $ret = diaspora_message($importer,$xmlbase->message,$msg);
+ }
+ else {
+ logger('diaspora_dispatch: unknown message type: ' . print_r($xmlbase,true));
+ }
+ return $ret;
+}
+
+function diaspora_handle_from_contact($contact_id) {
+ $handle = false;
+
+ logger("diaspora_handle_from_contact: contact id is " . $contact_id, LOGGER_DEBUG);
+
+ $r = q("SELECT * from abook left join xchan on abook_xchan = xchan_hash where abook_id = %d",
+ intval($contact_id)
+ );
+ if($r) {
+ $contact = $r[0];
+ }
+ $handle = $contact['xchan_addr'];
+ return $handle;
+}
+
+function diaspora_get_contact_by_handle($uid,$handle) {
+ $r = q("SELECT * FROM abook left join xchan on xchan_hash = abook_xchan where xchan_addr = '%s' and abook_channel = %d limit 1",
+ dbesc($handle),
+ intval($uid)
+ );
+ if($r)
+ return $r[0];
+ return false;
+}
+
+function find_diaspora_person_by_handle($handle) {
+
+ $person = false;
+ $update = false;
+ $got_lock = false;
+
+ $endlessloop = 0;
+ $maxloops = 10;
+
+ do {
+ $r = q("select * from xchan where xchan_addr = '%s' limit 1",
+ dbesc($handle)
+ );
+ if($r) {
+ $person = $r[0];
+ logger('find_diaspora_person_by handle: in cache ' . print_r($r,true), LOGGER_DEBUG);
+
+ // update record occasionally so it doesn't get stale
+ $d = strtotime($person['updated'] . ' +00:00');
+ if($d < strtotime('now - 14 days'))
+ $update = true;
+ }
+
+
+ // FETCHING PERSON INFORMATION FROM REMOTE SERVER
+ //
+ // If the person isn't in our 'fcontact' table, or if he/she is but
+ // his/her information hasn't been updated for more than 14 days, then
+ // we want to fetch the person's information from the remote server.
+ //
+ // Note that $person isn't changed by this block of code unless the
+ // person's information has been successfully fetched from the remote
+ // server. So if $person was 'false' to begin with (because he/she wasn't
+ // in the local cache), it'll stay false, and if $person held the local
+ // cache information to begin with, it'll keep that information. That way
+ // if there's a problem with the remote fetch, we can at least use our
+ // cached information--it's better than nothing.
+
+//fixme!!!
+
+ if((! $person) || ($update)) {
+ // Lock the function to prevent race conditions if multiple items
+ // come in at the same time from a person who doesn't exist in
+ // fcontact
+ //
+ // Don't loop forever. On the last loop, try to create the contact
+ // whether the function is locked or not. Maybe the locking thread
+ // has died or something. At any rate, a duplicate in 'fcontact'
+ // is a much smaller problem than a deadlocked thread
+// $got_lock = lock_function('find_diaspora_person_by_handle', false);
+ if(($endlessloop + 1) >= $maxloops)
+ $got_lock = true;
+
+ if($got_lock) {
+ logger('find_diaspora_person_by_handle: create or refresh', LOGGER_DEBUG);
+ require_once('include/Scrape.php');
+ $r = probe_url($handle, PROBE_DIASPORA);
+
+ // Note that Friendica contacts can return a "Diaspora person"
+ // if Diaspora connectivity is enabled on their server
+ if((count($r)) && ($r['network'] === NETWORK_DIASPORA)) {
+ add_fcontact($r,$update);
+ $person = ($r);
+ }
+
+// unlock_function('find_diaspora_person_by_handle');
+ }
+ else {
+ logger('find_diaspora_person_by_handle: couldn\'t lock function', LOGGER_DEBUG);
+// if(! $person)
+// block_on_function_lock('find_diaspora_person_by_handle');
+ }
+ }
+ } while((! $person) && (! $got_lock) && (++$endlessloop < $maxloops));
+
+ // We need to try again if the person wasn't in 'fcontact' but the function was locked.
+ // The fact that the function was locked may mean that another process was creating the
+ // person's record. It could also mean another process was creating or updating an unrelated
+ // person.
+ //
+ // At any rate, we need to keep trying until we've either got the person or had a chance to
+ // try to fetch his/her remote information. But we don't want to block on locking the
+ // function, because if the other process is creating the record, then when we acquire the lock
+ // we'll dive right into creating another, duplicate record. We DO want to at least wait
+ // until the lock is released, so we don't flood the database with requests.
+ //
+ // If the person was in the 'fcontact' table, don't try again. It's not worth the time, since
+ // we do have some information for the person
+
+ return $person;
+}
+
+
+function get_diaspora_key($uri) {
+ logger('Fetching diaspora key for: ' . $uri);
+
+ $r = find_diaspora_person_by_handle($uri);
+ if($r)
+ return $r['pubkey'];
+ return '';
+}
+
+
+function diaspora_pubmsg_build($msg,$user,$contact,$prvkey,$pubkey) {
+
+ $a = get_app();
+
+ logger('diaspora_pubmsg_build: ' . $msg, LOGGER_DATA);
+
+
+ $handle = $user['xchan_addr'];
+
+ $b64url_data = base64url_encode($msg);
+
+ $data = str_replace(array("\n","\r"," ","\t"),array('','','',''),$b64url_data);
+
+ $type = 'application/xml';
+ $encoding = 'base64url';
+ $alg = 'RSA-SHA256';
+
+ $signable_data = $data . '.' . base64url_encode($type) . '.'
+ . base64url_encode($encoding) . '.' . base64url_encode($alg) ;
+
+ $signature = rsa_sign($signable_data,$prvkey);
+ $sig = base64url_encode($signature);
+
+$magic_env = <<< EOT
+<?xml version='1.0' encoding='UTF-8'?>
+<diaspora xmlns="https://joindiaspora.com/protocol" xmlns:me="http://salmon-protocol.org/ns/magic-env" >
+ <header>
+ <author_id>$handle</author_id>
+ </header>
+ <me:env>
+ <me:encoding>base64url</me:encoding>
+ <me:alg>RSA-SHA256</me:alg>
+ <me:data type="application/xml">$data</me:data>
+ <me:sig>$sig</me:sig>
+ </me:env>
+</diaspora>
+EOT;
+
+ logger('diaspora_pubmsg_build: magic_env: ' . $magic_env, LOGGER_DATA);
+ return $magic_env;
+
+}
+
+
+
+
+function diaspora_msg_build($msg,$user,$contact,$prvkey,$pubkey,$public = false) {
+ $a = get_app();
+
+ if($public)
+ return diaspora_pubmsg_build($msg,$user,$contact,$prvkey,$pubkey);
+
+ logger('diaspora_msg_build: ' . $msg, LOGGER_DATA);
+
+ // without a public key nothing will work
+
+ if(! $pubkey) {
+ logger('diaspora_msg_build: pubkey missing: contact id: ' . $contact['abook_id']);
+ return '';
+ }
+
+ $inner_aes_key = random_string(32);
+ $b_inner_aes_key = base64_encode($inner_aes_key);
+ $inner_iv = random_string(16);
+ $b_inner_iv = base64_encode($inner_iv);
+
+ $outer_aes_key = random_string(32);
+ $b_outer_aes_key = base64_encode($outer_aes_key);
+ $outer_iv = random_string(16);
+ $b_outer_iv = base64_encode($outer_iv);
+
+ $handle = $user['xchan_addr'];
+
+ $padded_data = pkcs5_pad($msg,16);
+ $inner_encrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $inner_aes_key, $padded_data, MCRYPT_MODE_CBC, $inner_iv);
+
+ $b64_data = base64_encode($inner_encrypted);
+
+
+ $b64url_data = base64url_encode($b64_data);
+ $data = str_replace(array("\n","\r"," ","\t"),array('','','',''),$b64url_data);
+
+ $type = 'application/xml';
+ $encoding = 'base64url';
+ $alg = 'RSA-SHA256';
+
+ $signable_data = $data . '.' . base64url_encode($type) . '.'
+ . base64url_encode($encoding) . '.' . base64url_encode($alg) ;
+
+ $signature = rsa_sign($signable_data,$prvkey);
+ $sig = base64url_encode($signature);
+
+$decrypted_header = <<< EOT
+<decrypted_header>
+ <iv>$b_inner_iv</iv>
+ <aes_key>$b_inner_aes_key</aes_key>
+ <author_id>$handle</author_id>
+</decrypted_header>
+EOT;
+
+ $decrypted_header = pkcs5_pad($decrypted_header,16);
+
+ $ciphertext = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $outer_aes_key, $decrypted_header, MCRYPT_MODE_CBC, $outer_iv);
+
+ $outer_json = json_encode(array('iv' => $b_outer_iv,'key' => $b_outer_aes_key));
+
+ $encrypted_outer_key_bundle = '';
+ openssl_public_encrypt($outer_json,$encrypted_outer_key_bundle,$pubkey);
+
+ $b64_encrypted_outer_key_bundle = base64_encode($encrypted_outer_key_bundle);
+
+ logger('outer_bundle: ' . $b64_encrypted_outer_key_bundle . ' key: ' . $pubkey, LOGGER_DATA);
+
+ $encrypted_header_json_object = json_encode(array('aes_key' => base64_encode($encrypted_outer_key_bundle),
+ 'ciphertext' => base64_encode($ciphertext)));
+ $cipher_json = base64_encode($encrypted_header_json_object);
+
+ $encrypted_header = '<encrypted_header>' . $cipher_json . '</encrypted_header>';
+
+$magic_env = <<< EOT
+<?xml version='1.0' encoding='UTF-8'?>
+<diaspora xmlns="https://joindiaspora.com/protocol" xmlns:me="http://salmon-protocol.org/ns/magic-env" >
+ $encrypted_header
+ <me:env>
+ <me:encoding>base64url</me:encoding>
+ <me:alg>RSA-SHA256</me:alg>
+ <me:data type="application/xml">$data</me:data>
+ <me:sig>$sig</me:sig>
+ </me:env>
+</diaspora>
+EOT;
+
+ logger('diaspora_msg_build: magic_env: ' . $magic_env, LOGGER_DATA);
+ return $magic_env;
+
+}
+
+/**
+ *
+ * diaspora_decode($importer,$xml)
+ * array $importer -> from user table
+ * string $xml -> urldecoded Diaspora salmon
+ *
+ * Returns array
+ * 'message' -> decoded Diaspora XML message
+ * 'author' -> author diaspora handle
+ * 'key' -> author public key (converted to pkcs#8)
+ *
+ * Author and key are used elsewhere to save a lookup for verifying replies and likes
+ */
+
+
+function diaspora_decode($importer,$xml) {
+
+ $public = false;
+ $basedom = parse_xml_string($xml);
+
+ $children = $basedom->children('https://joindiaspora.com/protocol');
+
+ if($children->header) {
+ $public = true;
+ $author_link = str_replace('acct:','',$children->header->author_id);
+ }
+ else {
+
+ $encrypted_header = json_decode(base64_decode($children->encrypted_header));
+
+ $encrypted_aes_key_bundle = base64_decode($encrypted_header->aes_key);
+ $ciphertext = base64_decode($encrypted_header->ciphertext);
+
+ $outer_key_bundle = '';
+ openssl_private_decrypt($encrypted_aes_key_bundle,$outer_key_bundle,$importer['prvkey']);
+
+ $j_outer_key_bundle = json_decode($outer_key_bundle);
+
+ $outer_iv = base64_decode($j_outer_key_bundle->iv);
+ $outer_key = base64_decode($j_outer_key_bundle->key);
+
+ $decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $outer_key, $ciphertext, MCRYPT_MODE_CBC, $outer_iv);
+
+
+ $decrypted = pkcs5_unpad($decrypted);
+
+ /**
+ * $decrypted now contains something like
+ *
+ * <decrypted_header>
+ * <iv>8e+G2+ET8l5BPuW0sVTnQw==</iv>
+ * <aes_key>UvSMb4puPeB14STkcDWq+4QE302Edu15oaprAQSkLKU=</aes_key>
+
+***** OBSOLETE
+
+ * <author>
+ * <name>Ryan Hughes</name>
+ * <uri>acct:galaxor@diaspora.pirateship.org</uri>
+ * </author>
+
+***** CURRENT
+
+ * <author_id>galaxor@diaspora.priateship.org</author_id>
+
+***** END DIFFS
+
+ * </decrypted_header>
+ */
+
+ logger('decrypted: ' . $decrypted, LOGGER_DEBUG);
+ $idom = parse_xml_string($decrypted,false);
+
+ $inner_iv = base64_decode($idom->iv);
+ $inner_aes_key = base64_decode($idom->aes_key);
+
+ $author_link = str_replace('acct:','',$idom->author_id);
+
+ }
+
+ $dom = $basedom->children(NAMESPACE_SALMON_ME);
+
+ // figure out where in the DOM tree our data is hiding
+
+ if($dom->provenance->data)
+ $base = $dom->provenance;
+ elseif($dom->env->data)
+ $base = $dom->env;
+ elseif($dom->data)
+ $base = $dom;
+
+ if(! $base) {
+ logger('mod-diaspora: unable to locate salmon data in xml ');
+ http_status_exit(400);
+ }
+
+
+ // Stash the signature away for now. We have to find their key or it won't be good for anything.
+ $signature = base64url_decode($base->sig);
+
+ // unpack the data
+
+ // strip whitespace so our data element will return to one big base64 blob
+ $data = str_replace(array(" ","\t","\r","\n"),array("","","",""),$base->data);
+
+
+ // stash away some other stuff for later
+
+ $type = $base->data[0]->attributes()->type[0];
+ $keyhash = $base->sig[0]->attributes()->keyhash[0];
+ $encoding = $base->encoding;
+ $alg = $base->alg;
+
+
+ $signed_data = $data . '.' . base64url_encode($type) . '.' . base64url_encode($encoding) . '.' . base64url_encode($alg);
+
+
+ // decode the data
+ $data = base64url_decode($data);
+
+
+ if($public) {
+ $inner_decrypted = $data;
+ }
+ else {
+
+ // Decode the encrypted blob
+
+ $inner_encrypted = base64_decode($data);
+ $inner_decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $inner_aes_key, $inner_encrypted, MCRYPT_MODE_CBC, $inner_iv);
+ $inner_decrypted = pkcs5_unpad($inner_decrypted);
+ }
+
+ if(! $author_link) {
+ logger('mod-diaspora: Could not retrieve author URI.');
+ http_status_exit(400);
+ }
+
+ // Once we have the author URI, go to the web and try to find their public key
+ // (first this will look it up locally if it is in the fcontact cache)
+ // This will also convert diaspora public key from pkcs#1 to pkcs#8
+
+ logger('mod-diaspora: Fetching key for ' . $author_link );
+ $key = get_diaspora_key($author_link);
+
+ if(! $key) {
+ logger('mod-diaspora: Could not retrieve author key.');
+ http_status_exit(400);
+ }
+
+ $verify = rsa_verify($signed_data,$signature,$key);
+
+ if(! $verify) {
+ logger('mod-diaspora: Message did not verify. Discarding.');
+ http_status_exit(400);
+ }
+
+ logger('mod-diaspora: Message verified.');
+
+ return array('message' => $inner_decrypted, 'author' => $author_link, 'key' => $key);
+
+}
+
+
+/* sender is now sharing with recipient */
+
+function diaspora_request($importer,$xml) {
+
+ $a = get_app();
+
+ $sender_handle = unxmlify($xml->sender_handle);
+ $recipient_handle = unxmlify($xml->recipient_handle);
+
+ if(! $sender_handle || ! $recipient_handle)
+ return;
+
+
+ // Do we already have an abook record?
+
+ $contact = diaspora_get_contact_by_handle($importer['channel_id'],$sender_handle);
+
+ if($contact && $contact['abook_id']) {
+
+ // perhaps we were already sharing with this person. Now they're sharing with us.
+ // That makes us friends. Maybe.
+
+ $newperms = PERMS_R_STREAM|PERMS_R_PROFILE|PERMS_R_PHOTO|PERMS_R_ABOOK|PERMS_W_STREAM|PERMS_W_COMMENT|PERMS_W_MAIL|PERMS_W_CHAT;
+
+ $r = q("update abook set abook_their_perms = %d where abook_id = %d and abook_channel = %d limit 1",
+ intval($newperms),
+ intval($contact['abook_id']),
+ intval($importer['channel_id'])
+ );
+
+ return;
+ }
+
+ $ret = find_diaspora_person_by_handle($sender_handle);
+
+ if((! $ret) || ($ret['xchan_network'] != 'diaspora')) {
+ logger('diaspora_request: Cannot resolve diaspora handle ' . $sender_handle . ' for ' . $recipient_handle);
+ return;
+ }
+
+ $default_perms = 0;
+ // look for default permissions to apply in return - e.g. auto-friend
+ $z = q("select * from abook where abook_channel = %d and (abook_flags & %d) limit 1",
+ intval($importer['channel_id']),
+ intval(ABOOK_FLAG_SELF)
+ );
+
+ if($z)
+ $default_perms = intval($z[0]['abook_my_perms']);
+
+ $their_perms = PERMS_R_STREAM|PERMS_R_PROFILE|PERMS_R_PHOTO|PERMS_R_ABOOK|PERMS_W_STREAM|PERMS_W_COMMENT|PERMS_W_MAIL|PERMS_CHAT;
+
+ $r = q("insert into abook ( abook_account, abook_channel, abook_xchan, abook_my_perms, abook_their_perms, abook_closeness, abook_rating, abook_created, abook_updated, abook_connected, abook_dob, abook_flags, abook_profile) values ( %d, %d, '%s' %d %d, %d, %d, '%s', '%s', '%s', '%s', %d, '%s')",
+ intval($importer['channel_account_id']),
+ intval($importer['channel_id']),
+ dbesc($ret['xchan_hash']),
+ intval($default_perms),
+ intval($their_perms),
+ intval(99),
+ intval(0),
+ dbesc(datetime_convert()),
+ dbesc(datetime_convert()),
+ dbesc(datetime_convert()),
+ dbesc('0000-00-00 00:00:00'),
+ intval(($default_perms) ? 0 : ABOOK_FLAG_PENDING)
+ );
+
+
+ if($r) {
+ logger("New Diaspora introduction received for {$importer['channel_name']}");
+
+ $new_connection = q("select * from abook left join xchan on abook_xchan = xchan_hash where abook_channel = %d and abook_xchan = '%s' order by abook_created desc limit 1",
+ intval($importer['channel_id']),
+ dbesc($ret['xchan_hash'])
+ );
+ if($new_connection) {
+ require_once('include/enotify.php');
+ notification(array(
+ 'type' => NOTIFY_INTRO,
+ 'from_xchan' => $ret['xchan_hash'],
+ 'to_xchan' => $importer['channel_hash'],
+ 'link' => z_root() . '/connedit/' . $new_connection[0]['abook_id'],
+ ));
+
+ if($default_perms) {
+ // Send back a sharing notification to them
+ diaspora_share($importer['channel_id'],$new_connection[0]);
+
+ }
+ }
+ }
+
+ // find the abook record we just created
+
+ $contact_record = diaspora_get_contact_by_handle($importer['channel_id'],$sender_handle);
+
+ if(! $contact_record) {
+ logger('diaspora_request: unable to locate newly created contact record.');
+ return;
+ }
+
+//FIXME
+// $g = q("select def_gid from user where uid = %d limit 1",
+// intval($importer['channel_id'])
+// );
+// if($g && intval($g[0]['def_gid'])) {
+// require_once('include/group.php');
+// group_add_member($importer['channel_id'],'',$contact_record['id'],$g[0]['def_gid']);
+// }
+
+
+ return;
+}
+
+
+
+
+function diaspora_post($importer,$xml,$msg) {
+
+ $a = get_app();
+ $guid = notags(unxmlify($xml->guid));
+ $diaspora_handle = notags(unxmlify($xml->diaspora_handle));
+
+ if($diaspora_handle != $msg['author']) {
+ logger('diaspora_post: Potential forgery. Message handle is not the same as envelope sender.');
+ return 202;
+ }
+
+ $contact = diaspora_get_contact_by_handle($importer['channel_id'],$diaspora_handle);
+ if(! $contact)
+ return;
+
+ if(! perm_is_allowed($importer['channel_id'],$contact['xchan_hash'],'send_stream')) {
+ logger('diaspora_post: Ignoring this author.');
+ return 202;
+ }
+
+ $message_id = $diaspora_handle . ':' . $guid;
+ $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s' AND `guid` = '%s' LIMIT 1",
+ intval($importer['channel_id']),
+ dbesc($message_id),
+ dbesc($guid)
+ );
+ if(count($r)) {
+ logger('diaspora_post: message exists: ' . $guid);
+ return;
+ }
+
+ // allocate a guid on our system - we aren't fixing any collisions.
+ // we're ignoring them
+
+ $g = q("select * from guid where guid = '%s' limit 1",
+ dbesc($guid)
+ );
+ if(! count($g)) {
+ q("insert into guid ( guid ) values ( '%s' )",
+ dbesc($guid)
+ );
+ }
+
+ $created = unxmlify($xml->created_at);
+ $private = ((unxmlify($xml->public) == 'false') ? 1 : 0);
+
+ $body = diaspora2bb($xml->raw_message);
+
+ // Add OEmbed and other information to the body
+ $body = add_page_info_to_body($body, false, true);
+
+ $datarray = array();
+
+ $str_tags = '';
+
+ $tags = get_tags($body);
+
+ if(count($tags)) {
+ foreach($tags as $tag) {
+ if(strpos($tag,'#') === 0) {
+ if(strpos($tag,'[url='))
+ continue;
+
+ // don't link tags that are already embedded in links
+
+ if(preg_match('/\[(.*?)' . preg_quote($tag,'/') . '(.*?)\]/',$body))
+ continue;
+ if(preg_match('/\[(.*?)\]\((.*?)' . preg_quote($tag,'/') . '(.*?)\)/',$body))
+ continue;
+
+ $basetag = str_replace('_',' ',substr($tag,1));
+ $body = str_replace($tag,'#[url=' . $a->get_baseurl() . '/search?tag=' . rawurlencode($basetag) . ']' . $basetag . '[/url]',$body);
+ if(strlen($str_tags))
+ $str_tags .= ',';
+ $str_tags .= '#[url=' . $a->get_baseurl() . '/search?tag=' . rawurlencode($basetag) . ']' . $basetag . '[/url]';
+ continue;
+ }
+ }
+ }
+
+ $cnt = preg_match_all('/@\[url=(.*?)\[\/url\]/ism',$body,$matches,PREG_SET_ORDER);
+ if($cnt) {
+ foreach($matches as $mtch) {
+ if(strlen($str_tags))
+ $str_tags .= ',';
+ $str_tags .= '@[url=' . $mtch[1] . '[/url]';
+ }
+ }
+
+ $plink = 'https://'.substr($diaspora_handle,strpos($diaspora_handle,'@')+1).'/posts/'.$guid;
+
+ $datarray['uid'] = $importer['channel_id'];
+ $datarray['contact-id'] = $contact['id'];
+ $datarray['wall'] = 0;
+ $datarray['network'] = NETWORK_DIASPORA;
+ $datarray['verb'] = ACTIVITY_POST;
+ $datarray['guid'] = $guid;
+ $datarray['uri'] = $datarray['parent-uri'] = $message_id;
+ $datarray['changed'] = $datarray['created'] = $datarray['edited'] = datetime_convert('UTC','UTC',$created);
+ $datarray['private'] = $private;
+ $datarray['parent'] = 0;
+ $datarray['plink'] = $plink;
+ $datarray['owner-name'] = $contact['name'];
+ $datarray['owner-link'] = $contact['url'];
+ //$datarray['owner-avatar'] = $contact['thumb'];
+ $datarray['owner-avatar'] = ((x($contact,'thumb')) ? $contact['thumb'] : $contact['photo']);
+ $datarray['author-name'] = $contact['name'];
+ $datarray['author-link'] = $contact['url'];
+ $datarray['author-avatar'] = $contact['thumb'];
+ $datarray['body'] = $body;
+ $datarray['tag'] = $str_tags;
+ $datarray['app'] = 'Diaspora';
+
+ // if empty content it might be a photo that hasn't arrived yet. If a photo arrives, we'll make it visible.
+
+ $datarray['visible'] = ((strlen($body)) ? 1 : 0);
+
+ $message_id = item_store($datarray);
+
+ //if($message_id) {
+ // q("update item set plink = '%s' where id = %d",
+ // dbesc($a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $message_id),
+ // intval($message_id)
+ // );
+ //}
+
+ return;
+
+}
+
+function diaspora_reshare($importer,$xml,$msg) {
+
+ logger('diaspora_reshare: init: ' . print_r($xml,true));
+
+ $a = get_app();
+ $guid = notags(unxmlify($xml->guid));
+ $diaspora_handle = notags(unxmlify($xml->diaspora_handle));
+
+
+ if($diaspora_handle != $msg['author']) {
+ logger('diaspora_post: Potential forgery. Message handle is not the same as envelope sender.');
+ return 202;
+ }
+
+ $contact = diaspora_get_contact_by_handle($importer['channel_id'],$diaspora_handle);
+ if(! $contact)
+ return;
+
+ if(! perm_is_allowed($importer['channel_id'],$contact['xchan_hash'],'send_stream')) {
+ logger('diaspora_reshare: Ignoring this author: ' . $diaspora_handle . ' ' . print_r($xml,true));
+ return 202;
+ }
+
+ $message_id = $diaspora_handle . ':' . $guid;
+ $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s' AND `guid` = '%s' LIMIT 1",
+ intval($importer['channel_id']),
+ dbesc($message_id),
+ dbesc($guid)
+ );
+ if(count($r)) {
+ logger('diaspora_reshare: message exists: ' . $guid);
+ return;
+ }
+
+ $orig_author = notags(unxmlify($xml->root_diaspora_id));
+ $orig_guid = notags(unxmlify($xml->root_guid));
+
+ $source_url = 'https://' . substr($orig_author,strpos($orig_author,'@')+1) . '/p/' . $orig_guid . '.xml';
+ $orig_url = 'https://'.substr($orig_author,strpos($orig_author,'@')+1).'/posts/'.$orig_guid;
+ $x = fetch_url($source_url);
+ if(! $x)
+ $x = fetch_url(str_replace('https://','http://',$source_url));
+ if(! $x) {
+ logger('diaspora_reshare: unable to fetch source url ' . $source_url);
+ return;
+ }
+ logger('diaspora_reshare: source: ' . $x);
+
+ $x = str_replace(array('<activity_streams-photo>','</activity_streams-photo>'),array('<asphoto>','</asphoto>'),$x);
+ $source_xml = parse_xml_string($x,false);
+
+ if(strlen($source_xml->post->asphoto->objectId) && ($source_xml->post->asphoto->objectId != 0) && ($source_xml->post->asphoto->image_url)) {
+ $body = '[url=' . notags(unxmlify($source_xml->post->asphoto->image_url)) . '][img]' . notags(unxmlify($source_xml->post->asphoto->objectId)) . '[/img][/url]' . "\n";
+ $body = scale_external_images($body,false);
+ }
+ elseif($source_xml->post->asphoto->image_url) {
+ $body = '[img]' . notags(unxmlify($source_xml->post->asphoto->image_url)) . '[/img]' . "\n";
+ $body = scale_external_images($body);
+ }
+ elseif($source_xml->post->status_message) {
+ $body = diaspora2bb($source_xml->post->status_message->raw_message);
+
+ // Checking for embedded pictures
+ if($source_xml->post->status_message->photo->remote_photo_path AND
+ $source_xml->post->status_message->photo->remote_photo_name) {
+
+ $remote_photo_path = notags(unxmlify($source_xml->post->status_message->photo->remote_photo_path));
+ $remote_photo_name = notags(unxmlify($source_xml->post->status_message->photo->remote_photo_name));
+
+ $body = '[img]'.$remote_photo_path.$remote_photo_name.'[/img]'."\n".$body;
+
+ logger('diaspora_reshare: embedded picture link found: '.$body, LOGGER_DEBUG);
+ }
+
+ $body = scale_external_images($body);
+
+ // Add OEmbed and other information to the body
+ $body = add_page_info_to_body($body, false, true);
+ }
+ else {
+ // Maybe it is a reshare of a photo that will be delivered at a later time (testing)
+ logger('diaspora_reshare: no reshare content found: ' . print_r($source_xml,true));
+ $body = "";
+ //return;
+ }
+
+ //if(! $body) {
+ // logger('diaspora_reshare: empty body: source= ' . $x);
+ // return;
+ //}
+
+ $person = find_diaspora_person_by_handle($orig_author);
+
+ /*if(is_array($person) && x($person,'name') && x($person,'url'))
+ $details = '[url=' . $person['url'] . ']' . $person['name'] . '[/url]';
+ else
+ $details = $orig_author;
+
+ $prefix = html_entity_decode("&#x2672; ", ENT_QUOTES, 'UTF-8') . $details . "\n";*/
+
+
+ // allocate a guid on our system - we aren't fixing any collisions.
+ // we're ignoring them
+
+ $g = q("select * from guid where guid = '%s' limit 1",
+ dbesc($guid)
+ );
+ if(! count($g)) {
+ q("insert into guid ( guid ) values ( '%s' )",
+ dbesc($guid)
+ );
+ }
+
+ $created = unxmlify($xml->created_at);
+ $private = ((unxmlify($xml->public) == 'false') ? 1 : 0);
+
+ $datarray = array();
+
+ $str_tags = '';
+
+ $tags = get_tags($body);
+
+ if(count($tags)) {
+ foreach($tags as $tag) {
+ if(strpos($tag,'#') === 0) {
+ if(strpos($tag,'[url='))
+ continue;
+
+ // don't link tags that are already embedded in links
+
+ if(preg_match('/\[(.*?)' . preg_quote($tag,'/') . '(.*?)\]/',$body))
+ continue;
+ if(preg_match('/\[(.*?)\]\((.*?)' . preg_quote($tag,'/') . '(.*?)\)/',$body))
+ continue;
+
+
+ $basetag = str_replace('_',' ',substr($tag,1));
+ $body = str_replace($tag,'#[url=' . $a->get_baseurl() . '/search?tag=' . rawurlencode($basetag) . ']' . $basetag . '[/url]',$body);
+ if(strlen($str_tags))
+ $str_tags .= ',';
+ $str_tags .= '#[url=' . $a->get_baseurl() . '/search?tag=' . rawurlencode($basetag) . ']' . $basetag . '[/url]';
+ continue;
+ }
+ }
+ }
+
+ $plink = 'https://'.substr($diaspora_handle,strpos($diaspora_handle,'@')+1).'/posts/'.$guid;
+
+ $datarray['uid'] = $importer['channel_id'];
+ $datarray['contact-id'] = $contact['id'];
+ $datarray['wall'] = 0;
+ $datarray['network'] = NETWORK_DIASPORA;
+ $datarray['guid'] = $guid;
+ $datarray['uri'] = $datarray['parent-uri'] = $message_id;
+ $datarray['changed'] = $datarray['created'] = $datarray['edited'] = datetime_convert('UTC','UTC',$created);
+ $datarray['private'] = $private;
+ $datarray['parent'] = 0;
+ $datarray['plink'] = $plink;
+ $datarray['owner-name'] = $contact['name'];
+ $datarray['owner-link'] = $contact['url'];
+ $datarray['owner-avatar'] = ((x($contact,'thumb')) ? $contact['thumb'] : $contact['photo']);
+ if (!intval(get_config('system','wall-to-wall_share'))) {
+ $prefix = "[share author='".str_replace(array("'", "[", "]"), array("&#x27;", "&#x5B;", "&#x5D;"),$person['name']).
+ "' profile='".$person['url'].
+ "' avatar='".((x($person,'thumb')) ? $person['thumb'] : $person['photo']).
+ "' link='".str_replace(array("'", "[", "]"), array("&#x27;", "&#x5B;", "&#x5D;"),$orig_url)."']";
+ $datarray['author-name'] = $contact['name'];
+ $datarray['author-link'] = $contact['url'];
+ $datarray['author-avatar'] = $contact['thumb'];
+ $datarray['body'] = $prefix.$body."[/share]";
+ } else {
+ // Let reshared messages look like wall-to-wall posts
+ $datarray['author-name'] = $person['name'];
+ $datarray['author-link'] = $person['url'];
+ $datarray['author-avatar'] = ((x($person,'thumb')) ? $person['thumb'] : $person['photo']);
+ $datarray['body'] = $body;
+ }
+
+ $datarray['tag'] = $str_tags;
+ $datarray['app'] = 'Diaspora';
+
+ // if empty content it might be a photo that hasn't arrived yet. If a photo arrives, we'll make it visible. (testing)
+ $datarray['visible'] = ((strlen($body)) ? 1 : 0);
+
+ $message_id = item_store($datarray);
+
+ //if($message_id) {
+ // q("update item set plink = '%s' where id = %d",
+ // dbesc($a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $message_id),
+ // intval($message_id)
+ // );
+ //}
+
+ return;
+
+}
+
+
+function diaspora_asphoto($importer,$xml,$msg) {
+ logger('diaspora_asphoto called');
+
+ $a = get_app();
+ $guid = notags(unxmlify($xml->guid));
+ $diaspora_handle = notags(unxmlify($xml->diaspora_handle));
+
+ if($diaspora_handle != $msg['author']) {
+ logger('diaspora_post: Potential forgery. Message handle is not the same as envelope sender.');
+ return 202;
+ }
+
+ $contact = diaspora_get_contact_by_handle($importer['channel_id'],$diaspora_handle);
+ if(! $contact)
+ return;
+
+ if(! perm_is_allowed($importer['channel_id'],$contact['xchan_hash'],'send_stream')) {
+ logger('diaspora_asphoto: Ignoring this author.');
+ return 202;
+ }
+
+ $message_id = $diaspora_handle . ':' . $guid;
+ $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s' AND `guid` = '%s' LIMIT 1",
+ intval($importer['channel_id']),
+ dbesc($message_id),
+ dbesc($guid)
+ );
+ if(count($r)) {
+ logger('diaspora_asphoto: message exists: ' . $guid);
+ return;
+ }
+
+ // allocate a guid on our system - we aren't fixing any collisions.
+ // we're ignoring them
+
+ $g = q("select * from guid where guid = '%s' limit 1",
+ dbesc($guid)
+ );
+ if(! count($g)) {
+ q("insert into guid ( guid ) values ( '%s' )",
+ dbesc($guid)
+ );
+ }
+
+ $created = unxmlify($xml->created_at);
+ $private = ((unxmlify($xml->public) == 'false') ? 1 : 0);
+
+ if(strlen($xml->objectId) && ($xml->objectId != 0) && ($xml->image_url)) {
+ $body = '[url=' . notags(unxmlify($xml->image_url)) . '][img]' . notags(unxmlify($xml->objectId)) . '[/img][/url]' . "\n";
+ $body = scale_external_images($body,false);
+ }
+ elseif($xml->image_url) {
+ $body = '[img]' . notags(unxmlify($xml->image_url)) . '[/img]' . "\n";
+ $body = scale_external_images($body);
+ }
+ else {
+ logger('diaspora_asphoto: no photo url found.');
+ return;
+ }
+
+ $plink = 'https://'.substr($diaspora_handle,strpos($diaspora_handle,'@')+1).'/posts/'.$guid;
+
+ $datarray = array();
+
+ $datarray['uid'] = $importer['channel_id'];
+ $datarray['contact-id'] = $contact['id'];
+ $datarray['wall'] = 0;
+ $datarray['network'] = NETWORK_DIASPORA;
+ $datarray['guid'] = $guid;
+ $datarray['uri'] = $datarray['parent-uri'] = $message_id;
+ $datarray['changed'] = $datarray['created'] = $datarray['edited'] = datetime_convert('UTC','UTC',$created);
+ $datarray['private'] = $private;
+ $datarray['parent'] = 0;
+ $datarray['plink'] = $plink;
+ $datarray['owner-name'] = $contact['name'];
+ $datarray['owner-link'] = $contact['url'];
+ //$datarray['owner-avatar'] = $contact['thumb'];
+ $datarray['owner-avatar'] = ((x($contact,'thumb')) ? $contact['thumb'] : $contact['photo']);
+ $datarray['author-name'] = $contact['name'];
+ $datarray['author-link'] = $contact['url'];
+ $datarray['author-avatar'] = $contact['thumb'];
+ $datarray['body'] = $body;
+
+ $datarray['app'] = 'Diaspora/Cubbi.es';
+
+ $message_id = item_store($datarray);
+
+ //if($message_id) {
+ // q("update item set plink = '%s' where id = %d",
+ // dbesc($a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $message_id),
+ // intval($message_id)
+ // );
+ //}
+
+ return;
+
+}
+
+
+
+
+
+
+function diaspora_comment($importer,$xml,$msg) {
+
+ $a = get_app();
+ $guid = notags(unxmlify($xml->guid));
+ $parent_guid = notags(unxmlify($xml->parent_guid));
+ $diaspora_handle = notags(unxmlify($xml->diaspora_handle));
+ $target_type = notags(unxmlify($xml->target_type));
+ $text = unxmlify($xml->text);
+ $author_signature = notags(unxmlify($xml->author_signature));
+
+ $parent_author_signature = (($xml->parent_author_signature) ? notags(unxmlify($xml->parent_author_signature)) : '');
+
+ $contact = diaspora_get_contact_by_handle($importer['channel_id'],$msg['author']);
+ if(! $contact) {
+ logger('diaspora_comment: cannot find contact: ' . $msg['author']);
+ return;
+ }
+
+ if(! perm_is_allowed($importer['channel_id'],$contact['xchan_hash'],'post_comments')) {
+ logger('diaspora_comment: Ignoring this author.');
+ return 202;
+ }
+
+ $r = q("SELECT * FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1",
+ intval($importer['channel_id']),
+ dbesc($guid)
+ );
+ if(count($r)) {
+ logger('diaspora_comment: our comment just got relayed back to us (or there was a guid collision) : ' . $guid);
+ return;
+ }
+
+ $r = q("SELECT * FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1",
+ intval($importer['channel_id']),
+ dbesc($parent_guid)
+ );
+ if(! count($r)) {
+ logger('diaspora_comment: parent item not found: parent: ' . $parent_guid . ' item: ' . $guid);
+ return;
+ }
+ $parent_item = $r[0];
+
+
+ /* How Diaspora performs comment signature checking:
+
+ - If an item has been sent by the comment author to the top-level post owner to relay on
+ to the rest of the contacts on the top-level post, the top-level post owner should check
+ the author_signature, then create a parent_author_signature before relaying the comment on
+ - If an item has been relayed on by the top-level post owner, the contacts who receive it
+ check only the parent_author_signature. Basically, they trust that the top-level post
+ owner has already verified the authenticity of anything he/she sends out
+ - In either case, the signature that get checked is the signature created by the person
+ who sent the salmon
+ */
+
+ $signed_data = $guid . ';' . $parent_guid . ';' . $text . ';' . $diaspora_handle;
+ $key = $msg['key'];
+
+ if($parent_author_signature) {
+ // If a parent_author_signature exists, then we've received the comment
+ // relayed from the top-level post owner. There's no need to check the
+ // author_signature if the parent_author_signature is valid
+
+ $parent_author_signature = base64_decode($parent_author_signature);
+
+ if(! rsa_verify($signed_data,$parent_author_signature,$key,'sha256')) {
+ logger('diaspora_comment: top-level owner verification failed.');
+ return;
+ }
+ }
+ else {
+ // If there's no parent_author_signature, then we've received the comment
+ // from the comment creator. In that case, the person is commenting on
+ // our post, so he/she must be a contact of ours and his/her public key
+ // should be in $msg['key']
+
+ $author_signature = base64_decode($author_signature);
+
+ if(! rsa_verify($signed_data,$author_signature,$key,'sha256')) {
+ logger('diaspora_comment: comment author verification failed.');
+ return;
+ }
+ }
+
+ // Phew! Everything checks out. Now create an item.
+
+ // Find the original comment author information.
+ // We need this to make sure we display the comment author
+ // information (name and avatar) correctly.
+ if(strcasecmp($diaspora_handle,$msg['author']) == 0)
+ $person = $contact;
+ else {
+ $person = find_diaspora_person_by_handle($diaspora_handle);
+
+ if(! is_array($person)) {
+ logger('diaspora_comment: unable to find author details');
+ return;
+ }
+ }
+
+ $body = diaspora2bb($text);
+ $message_id = $diaspora_handle . ':' . $guid;
+
+ $datarray = array();
+
+ $str_tags = '';
+
+ $tags = get_tags($body);
+
+ if(count($tags)) {
+ foreach($tags as $tag) {
+ if(strpos($tag,'#') === 0) {
+ if(strpos($tag,'[url='))
+ continue;
+
+ // don't link tags that are already embedded in links
+
+ if(preg_match('/\[(.*?)' . preg_quote($tag,'/') . '(.*?)\]/',$body))
+ continue;
+ if(preg_match('/\[(.*?)\]\((.*?)' . preg_quote($tag,'/') . '(.*?)\)/',$body))
+ continue;
+
+
+ $basetag = str_replace('_',' ',substr($tag,1));
+ $body = str_replace($tag,'#[url=' . $a->get_baseurl() . '/search?tag=' . rawurlencode($basetag) . ']' . $basetag . '[/url]',$body);
+ if(strlen($str_tags))
+ $str_tags .= ',';
+ $str_tags .= '#[url=' . $a->get_baseurl() . '/search?tag=' . rawurlencode($basetag) . ']' . $basetag . '[/url]';
+ continue;
+ }
+ }
+ }
+
+ $datarray['uid'] = $importer['channel_id'];
+ $datarray['contact-id'] = $contact['id'];
+ $datarray['type'] = 'remote-comment';
+ $datarray['wall'] = $parent_item['wall'];
+ $datarray['network'] = NETWORK_DIASPORA;
+ $datarray['verb'] = ACTIVITY_POST;
+ $datarray['gravity'] = GRAVITY_COMMENT;
+ $datarray['guid'] = $guid;
+ $datarray['uri'] = $message_id;
+ $datarray['parent-uri'] = $parent_item['uri'];
+
+ // No timestamps for comments? OK, we'll the use current time.
+ $datarray['changed'] = $datarray['created'] = $datarray['edited'] = datetime_convert();
+ $datarray['private'] = $parent_item['private'];
+
+ $datarray['owner-name'] = $parent_item['owner-name'];
+ $datarray['owner-link'] = $parent_item['owner-link'];
+ $datarray['owner-avatar'] = $parent_item['owner-avatar'];
+
+ $datarray['author-name'] = $person['name'];
+ $datarray['author-link'] = $person['url'];
+ $datarray['author-avatar'] = ((x($person,'thumb')) ? $person['thumb'] : $person['photo']);
+ $datarray['body'] = $body;
+ $datarray['tag'] = $str_tags;
+
+ // We can't be certain what the original app is if the message is relayed.
+ if(($parent_item['origin']) && (! $parent_author_signature))
+ $datarray['app'] = 'Diaspora';
+
+ $message_id = item_store($datarray);
+
+ //if($message_id) {
+ //q("update item set plink = '%s' where id = %d",
+ // //dbesc($a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $message_id),
+ // dbesc($a->get_baseurl().'/display/'.$datarray['guid']),
+ // intval($message_id)
+ //);
+ //}
+
+ if(($parent_item['origin']) && (! $parent_author_signature)) {
+ q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
+ intval($message_id),
+ dbesc($signed_data),
+ dbesc(base64_encode($author_signature)),
+ dbesc($diaspora_handle)
+ );
+
+ // if the message isn't already being relayed, notify others
+ // the existence of parent_author_signature means the parent_author or owner
+ // is already relaying.
+
+ proc_run('php','include/notifier.php','comment-import',$message_id);
+ }
+
+ $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0 ",
+ dbesc($parent_item['uri']),
+ intval($importer['channel_id'])
+ );
+
+ if(count($myconv)) {
+ $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname'];
+
+ foreach($myconv as $conv) {
+
+ // now if we find a match, it means we're in this conversation
+
+ if(! link_compare($conv['author-link'],$importer_url))
+ continue;
+
+ require_once('include/enotify.php');
+
+ $conv_parent = $conv['parent'];
+
+ notification(array(
+ 'type' => NOTIFY_COMMENT,
+ 'notify_flags' => $importer['notify-flags'],
+ 'language' => $importer['language'],
+ 'to_name' => $importer['username'],
+ 'to_email' => $importer['email'],
+ 'uid' => $importer['channel_id'],
+ 'item' => $datarray,
+ //'link' => $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $message_id,
+ 'link' => $a->get_baseurl().'/display/'.$datarray['guid'],
+ 'source_name' => $datarray['author-name'],
+ 'source_link' => $datarray['author-link'],
+ 'source_photo' => $datarray['author-avatar'],
+ 'verb' => ACTIVITY_POST,
+ 'otype' => 'item',
+ 'parent' => $conv_parent,
+ 'parent_uri' => $parent_uri
+ ));
+
+ // only send one notification
+ break;
+ }
+ }
+ return;
+}
+
+
+
+
+function diaspora_conversation($importer,$xml,$msg) {
+
+ $a = get_app();
+
+ $guid = notags(unxmlify($xml->guid));
+ $subject = notags(unxmlify($xml->subject));
+ $diaspora_handle = notags(unxmlify($xml->diaspora_handle));
+ $participant_handles = notags(unxmlify($xml->participant_handles));
+ $created_at = datetime_convert('UTC','UTC',notags(unxmlify($xml->created_at)));
+
+ $parent_uri = $diaspora_handle . ':' . $guid;
+
+ $messages = $xml->message;
+
+ if(! count($messages)) {
+ logger('diaspora_conversation: empty conversation');
+ return;
+ }
+
+ $contact = diaspora_get_contact_by_handle($importer['channel_id'],$msg['author']);
+ if(! $contact) {
+ logger('diaspora_conversation: cannot find contact: ' . $msg['author']);
+ return;
+ }
+
+ if(($contact['rel'] == CONTACT_IS_FOLLOWER) || ($contact['blocked']) || ($contact['readonly'])) {
+ logger('diaspora_conversation: Ignoring this author.');
+ return 202;
+ }
+
+ $conversation = null;
+
+ $c = q("select * from conv where uid = %d and guid = '%s' limit 1",
+ intval($importer['channel_id']),
+ dbesc($guid)
+ );
+ if(count($c))
+ $conversation = $c[0];
+ else {
+ $r = q("insert into conv (uid,guid,creator,created,updated,subject,recips) values(%d, '%s', '%s', '%s', '%s', '%s', '%s') ",
+ intval($importer['channel_id']),
+ dbesc($guid),
+ dbesc($diaspora_handle),
+ dbesc(datetime_convert('UTC','UTC',$created_at)),
+ dbesc(datetime_convert()),
+ dbesc($subject),
+ dbesc($participant_handles)
+ );
+ if($r)
+ $c = q("select * from conv where uid = %d and guid = '%s' limit 1",
+ intval($importer['channel_id']),
+ dbesc($guid)
+ );
+ if(count($c))
+ $conversation = $c[0];
+ }
+ if(! $conversation) {
+ logger('diaspora_conversation: unable to create conversation.');
+ return;
+ }
+
+ foreach($messages as $mesg) {
+
+ $reply = 0;
+
+ $msg_guid = notags(unxmlify($mesg->guid));
+ $msg_parent_guid = notags(unxmlify($mesg->parent_guid));
+ $msg_parent_author_signature = notags(unxmlify($mesg->parent_author_signature));
+ $msg_author_signature = notags(unxmlify($mesg->author_signature));
+ $msg_text = unxmlify($mesg->text);
+ $msg_created_at = datetime_convert('UTC','UTC',notags(unxmlify($mesg->created_at)));
+ $msg_diaspora_handle = notags(unxmlify($mesg->diaspora_handle));
+ $msg_conversation_guid = notags(unxmlify($mesg->conversation_guid));
+ if($msg_conversation_guid != $guid) {
+ logger('diaspora_conversation: message conversation guid does not belong to the current conversation. ' . $xml);
+ continue;
+ }
+
+ $body = diaspora2bb($msg_text);
+ $message_id = $msg_diaspora_handle . ':' . $msg_guid;
+
+ $author_signed_data = $msg_guid . ';' . $msg_parent_guid . ';' . $msg_text . ';' . unxmlify($mesg->created_at) . ';' . $msg_diaspora_handle . ';' . $msg_conversation_guid;
+
+ $author_signature = base64_decode($msg_author_signature);
+
+ if(strcasecmp($msg_diaspora_handle,$msg['author']) == 0) {
+ $person = $contact;
+ $key = $msg['key'];
+ }
+ else {
+ $person = find_diaspora_person_by_handle($msg_diaspora_handle);
+
+ if(is_array($person) && x($person,'pubkey'))
+ $key = $person['pubkey'];
+ else {
+ logger('diaspora_conversation: unable to find author details');
+ continue;
+ }
+ }
+
+ if(! rsa_verify($author_signed_data,$author_signature,$key,'sha256')) {
+ logger('diaspora_conversation: verification failed.');
+ continue;
+ }
+
+ if($msg_parent_author_signature) {
+ $owner_signed_data = $msg_guid . ';' . $msg_parent_guid . ';' . $msg_text . ';' . unxmlify($mesg->created_at) . ';' . $msg_diaspora_handle . ';' . $msg_conversation_guid;
+
+ $parent_author_signature = base64_decode($msg_parent_author_signature);
+
+ $key = $msg['key'];
+
+ if(! rsa_verify($owner_signed_data,$parent_author_signature,$key,'sha256')) {
+ logger('diaspora_conversation: owner verification failed.');
+ continue;
+ }
+ }
+
+ $r = q("select id from mail where `uri` = '%s' limit 1",
+ dbesc($message_id)
+ );
+ if(count($r)) {
+ logger('diaspora_conversation: duplicate message already delivered.', LOGGER_DEBUG);
+ continue;
+ }
+
+ q("insert into mail ( `uid`, `guid`, `convid`, `from-name`,`from-photo`,`from-url`,`contact-id`,`title`,`body`,`seen`,`reply`,`uri`,`parent-uri`,`created`) values ( %d, '%s', %d, '%s', '%s', '%s', %d, '%s', '%s', %d, %d, '%s','%s','%s')",
+ intval($importer['channel_id']),
+ dbesc($msg_guid),
+ intval($conversation['id']),
+ dbesc($person['name']),
+ dbesc($person['photo']),
+ dbesc($person['url']),
+ intval($contact['id']),
+ dbesc($subject),
+ dbesc($body),
+ 0,
+ 0,
+ dbesc($message_id),
+ dbesc($parent_uri),
+ dbesc($msg_created_at)
+ );
+
+ q("update conv set updated = '%s' where id = %d",
+ dbesc(datetime_convert()),
+ intval($conversation['id'])
+ );
+
+ require_once('include/enotify.php');
+ notification(array(
+ 'type' => NOTIFY_MAIL,
+ 'notify_flags' => $importer['notify-flags'],
+ 'language' => $importer['language'],
+ 'to_name' => $importer['username'],
+ 'to_email' => $importer['email'],
+ 'uid' =>$importer['importer_uid'],
+ 'item' => array('subject' => $subject, 'body' => $body),
+ 'source_name' => $person['name'],
+ 'source_link' => $person['url'],
+ 'source_photo' => $person['thumb'],
+ 'verb' => ACTIVITY_POST,
+ 'otype' => 'mail'
+ ));
+ }
+
+ return;
+}
+
+function diaspora_message($importer,$xml,$msg) {
+
+ $a = get_app();
+
+ $msg_guid = notags(unxmlify($xml->guid));
+ $msg_parent_guid = notags(unxmlify($xml->parent_guid));
+ $msg_parent_author_signature = notags(unxmlify($xml->parent_author_signature));
+ $msg_author_signature = notags(unxmlify($xml->author_signature));
+ $msg_text = unxmlify($xml->text);
+ $msg_created_at = datetime_convert('UTC','UTC',notags(unxmlify($xml->created_at)));
+ $msg_diaspora_handle = notags(unxmlify($xml->diaspora_handle));
+ $msg_conversation_guid = notags(unxmlify($xml->conversation_guid));
+
+ $parent_uri = $diaspora_handle . ':' . $msg_parent_guid;
+
+ $contact = diaspora_get_contact_by_handle($importer['channel_id'],$msg_diaspora_handle);
+ if(! $contact) {
+ logger('diaspora_message: cannot find contact: ' . $msg_diaspora_handle);
+ return;
+ }
+
+ if(($contact['rel'] == CONTACT_IS_FOLLOWER) || ($contact['blocked']) || ($contact['readonly'])) {
+ logger('diaspora_message: Ignoring this author.');
+ return 202;
+ }
+
+ $conversation = null;
+
+ $c = q("select * from conv where uid = %d and guid = '%s' limit 1",
+ intval($importer['channel_id']),
+ dbesc($msg_conversation_guid)
+ );
+ if(count($c))
+ $conversation = $c[0];
+ else {
+ logger('diaspora_message: conversation not available.');
+ return;
+ }
+
+ $reply = 0;
+
+ $body = diaspora2bb($msg_text);
+ $message_id = $msg_diaspora_handle . ':' . $msg_guid;
+
+ $author_signed_data = $msg_guid . ';' . $msg_parent_guid . ';' . $msg_text . ';' . unxmlify($xml->created_at) . ';' . $msg_diaspora_handle . ';' . $msg_conversation_guid;
+
+
+ $author_signature = base64_decode($msg_author_signature);
+
+ $person = find_diaspora_person_by_handle($msg_diaspora_handle);
+ if(is_array($person) && x($person,'pubkey'))
+ $key = $person['pubkey'];
+ else {
+ logger('diaspora_message: unable to find author details');
+ return;
+ }
+
+ if(! rsa_verify($author_signed_data,$author_signature,$key,'sha256')) {
+ logger('diaspora_message: verification failed.');
+ return;
+ }
+
+ $r = q("select id from mail where `uri` = '%s' and uid = %d limit 1",
+ dbesc($message_id),
+ intval($importer['channel_id'])
+ );
+ if(count($r)) {
+ logger('diaspora_message: duplicate message already delivered.', LOGGER_DEBUG);
+ return;
+ }
+
+ q("insert into mail ( `uid`, `guid`, `convid`, `from-name`,`from-photo`,`from-url`,`contact-id`,`title`,`body`,`seen`,`reply`,`uri`,`parent-uri`,`created`) values ( %d, '%s', %d, '%s', '%s', '%s', %d, '%s', '%s', %d, %d, '%s','%s','%s')",
+ intval($importer['channel_id']),
+ dbesc($msg_guid),
+ intval($conversation['id']),
+ dbesc($person['name']),
+ dbesc($person['photo']),
+ dbesc($person['url']),
+ intval($contact['id']),
+ dbesc($conversation['subject']),
+ dbesc($body),
+ 0,
+ 1,
+ dbesc($message_id),
+ dbesc($parent_uri),
+ dbesc($msg_created_at)
+ );
+
+ q("update conv set updated = '%s' where id = %d",
+ dbesc(datetime_convert()),
+ intval($conversation['id'])
+ );
+
+ return;
+}
+
+
+function diaspora_photo($importer,$xml,$msg,$attempt=1) {
+
+ $a = get_app();
+
+ logger('diaspora_photo: init',LOGGER_DEBUG);
+
+ $remote_photo_path = notags(unxmlify($xml->remote_photo_path));
+
+ $remote_photo_name = notags(unxmlify($xml->remote_photo_name));
+
+ $status_message_guid = notags(unxmlify($xml->status_message_guid));
+
+ $guid = notags(unxmlify($xml->guid));
+
+ $diaspora_handle = notags(unxmlify($xml->diaspora_handle));
+
+ $public = notags(unxmlify($xml->public));
+
+ $created_at = notags(unxmlify($xml_created_at));
+
+ logger('diaspora_photo: status_message_guid: ' . $status_message_guid, LOGGER_DEBUG);
+
+ $contact = diaspora_get_contact_by_handle($importer['channel_id'],$msg['author']);
+ if(! $contact) {
+ logger('diaspora_photo: contact record not found: ' . $msg['author'] . ' handle: ' . $diaspora_handle);
+ return;
+ }
+
+ if(! perm_is_allowed($importer['channel_id'],$contact['xchan_hash'],'send_stream')) {
+ logger('diaspora_photo: Ignoring this author.');
+ return 202;
+ }
+
+ $r = q("SELECT * FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1",
+ intval($importer['channel_id']),
+ dbesc($status_message_guid)
+ );
+ if(! count($r)) {
+ if($attempt <= 3) {
+ q("INSERT INTO dsprphotoq (uid, msg, attempt) VALUES (%d, '%s', %d)",
+ intval($importer['channel_id']),
+ dbesc(serialize($msg)),
+ intval($attempt + 1)
+ );
+ }
+ logger('diaspora_photo: attempt = ' . $attempt . '; status message not found: ' . $status_message_guid . ' for photo: ' . $guid);
+ return;
+ }
+
+ $parent_item = $r[0];
+
+ $link_text = '[img]' . $remote_photo_path . $remote_photo_name . '[/img]' . "\n";
+
+ $link_text = scale_external_images($link_text, true,
+ array($remote_photo_name, 'scaled_full_' . $remote_photo_name));
+
+ if(strpos($parent_item['body'],$link_text) === false) {
+ $r = q("update item set `body` = '%s', `visible` = 1 where `id` = %d and `uid` = %d",
+ dbesc($link_text . $parent_item['body']),
+ intval($parent_item['id']),
+ intval($parent_item['uid'])
+ );
+ }
+
+ return;
+}
+
+
+
+
+function diaspora_like($importer,$xml,$msg) {
+
+ $a = get_app();
+ $guid = notags(unxmlify($xml->guid));
+ $parent_guid = notags(unxmlify($xml->parent_guid));
+ $diaspora_handle = notags(unxmlify($xml->diaspora_handle));
+ $target_type = notags(unxmlify($xml->target_type));
+ $positive = notags(unxmlify($xml->positive));
+ $author_signature = notags(unxmlify($xml->author_signature));
+
+ $parent_author_signature = (($xml->parent_author_signature) ? notags(unxmlify($xml->parent_author_signature)) : '');
+
+ // likes on comments not supported here and likes on photos not supported by Diaspora
+
+// if($target_type !== 'Post')
+// return;
+
+ $contact = diaspora_get_contact_by_handle($importer['channel_id'],$msg['author']);
+ if(! $contact) {
+ logger('diaspora_like: cannot find contact: ' . $msg['author']);
+ return;
+ }
+
+
+ if(! perm_is_allowed($importer['channel_id'],$contact['xchan_hash'],'post_comments')) {
+ logger('diaspora_like: Ignoring this author.');
+ return 202;
+ }
+
+ $r = q("SELECT * FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1",
+ intval($importer['channel_id']),
+ dbesc($parent_guid)
+ );
+ if(! count($r)) {
+ logger('diaspora_like: parent item not found: ' . $guid);
+ return;
+ }
+
+ $parent_item = $r[0];
+
+ $r = q("SELECT * FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1",
+ intval($importer['channel_id']),
+ dbesc($guid)
+ );
+ if(count($r)) {
+ if($positive === 'true') {
+ logger('diaspora_like: duplicate like: ' . $guid);
+ return;
+ }
+ // Note: I don't think "Like" objects with positive = "false" are ever actually used
+ // It looks like "RelayableRetractions" are used for "unlike" instead
+ if($positive === 'false') {
+ logger('diaspora_like: received a like with positive set to "false"...ignoring');
+/* q("UPDATE `item` SET `deleted` = 1 WHERE `id` = %d AND `uid` = %d",
+ intval($r[0]['id']),
+ intval($importer['channel_id'])
+ );*/
+ // FIXME--actually don't unless it turns out that Diaspora does indeed send out "false" likes
+ // send notification via proc_run()
+ return;
+ }
+ }
+ // Note: I don't think "Like" objects with positive = "false" are ever actually used
+ // It looks like "RelayableRetractions" are used for "unlike" instead
+ if($positive === 'false') {
+ logger('diaspora_like: received a like with positive set to "false"');
+ logger('diaspora_like: unlike received with no corresponding like...ignoring');
+ return;
+ }
+
+
+ /* How Diaspora performs "like" signature checking:
+
+ - If an item has been sent by the like author to the top-level post owner to relay on
+ to the rest of the contacts on the top-level post, the top-level post owner should check
+ the author_signature, then create a parent_author_signature before relaying the like on
+ - If an item has been relayed on by the top-level post owner, the contacts who receive it
+ check only the parent_author_signature. Basically, they trust that the top-level post
+ owner has already verified the authenticity of anything he/she sends out
+ - In either case, the signature that get checked is the signature created by the person
+ who sent the salmon
+ */
+
+ $signed_data = $guid . ';' . $target_type . ';' . $parent_guid . ';' . $positive . ';' . $diaspora_handle;
+ $key = $msg['key'];
+
+ if($parent_author_signature) {
+ // If a parent_author_signature exists, then we've received the like
+ // relayed from the top-level post owner. There's no need to check the
+ // author_signature if the parent_author_signature is valid
+
+ $parent_author_signature = base64_decode($parent_author_signature);
+
+ if(! rsa_verify($signed_data,$parent_author_signature,$key,'sha256')) {
+ if (intval(get_config('system','ignore_diaspora_like_signature')))
+ logger('diaspora_like: top-level owner verification failed. Proceeding anyway.');
+ else {
+ logger('diaspora_like: top-level owner verification failed.');
+ return;
+ }
+ }
+ }
+ else {
+ // If there's no parent_author_signature, then we've received the like
+ // from the like creator. In that case, the person is "like"ing
+ // our post, so he/she must be a contact of ours and his/her public key
+ // should be in $msg['key']
+
+ $author_signature = base64_decode($author_signature);
+
+ if(! rsa_verify($signed_data,$author_signature,$key,'sha256')) {
+ if (intval(get_config('system','ignore_diaspora_like_signature')))
+ logger('diaspora_like: like creator verification failed. Proceeding anyway');
+ else {
+ logger('diaspora_like: like creator verification failed.');
+ return;
+ }
+ }
+ }
+
+ // Phew! Everything checks out. Now create an item.
+
+ // Find the original comment author information.
+ // We need this to make sure we display the comment author
+ // information (name and avatar) correctly.
+ if(strcasecmp($diaspora_handle,$msg['author']) == 0)
+ $person = $contact;
+ else {
+ $person = find_diaspora_person_by_handle($diaspora_handle);
+
+ if(! is_array($person)) {
+ logger('diaspora_like: unable to find author details');
+ return;
+ }
+ }
+
+ $uri = $diaspora_handle . ':' . $guid;
+
+ $activity = ACTIVITY_LIKE;
+ $post_type = (($parent_item['resource-id']) ? t('photo') : t('status'));
+ $objtype = (($parent_item['resource-id']) ? ACTIVITY_OBJ_PHOTO : ACTIVITY_OBJ_NOTE );
+ $link = xmlify('<link rel="alternate" type="text/html" href="' . $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $parent_item['id'] . '" />' . "\n") ;
+ $body = $parent_item['body'];
+
+ $obj = <<< EOT
+
+ <object>
+ <type>$objtype</type>
+ <local>1</local>
+ <id>{$parent_item['uri']}</id>
+ <link>$link</link>
+ <title></title>
+ <content>$body</content>
+ </object>
+EOT;
+ $bodyverb = t('%1$s likes %2$s\'s %3$s');
+
+ $arr = array();
+
+ $arr['uri'] = $uri;
+ $arr['uid'] = $importer['channel_id'];
+ $arr['guid'] = $guid;
+ $arr['network'] = NETWORK_DIASPORA;
+ $arr['contact-id'] = $contact['id'];
+ $arr['type'] = 'activity';
+ $arr['wall'] = $parent_item['wall'];
+ $arr['gravity'] = GRAVITY_LIKE;
+ $arr['parent'] = $parent_item['id'];
+ $arr['parent-uri'] = $parent_item['uri'];
+
+ $arr['owner-name'] = $parent_item['name'];
+ $arr['owner-link'] = $parent_item['url'];
+ //$arr['owner-avatar'] = $parent_item['thumb'];
+ $arr['owner-avatar'] = ((x($parent_item,'thumb')) ? $parent_item['thumb'] : $parent_item['photo']);
+
+ $arr['author-name'] = $person['name'];
+ $arr['author-link'] = $person['url'];
+ $arr['author-avatar'] = ((x($person,'thumb')) ? $person['thumb'] : $person['photo']);
+
+ $ulink = '[url=' . $contact['url'] . ']' . $contact['name'] . '[/url]';
+ $alink = '[url=' . $parent_item['author-link'] . ']' . $parent_item['author-name'] . '[/url]';
+ //$plink = '[url=' . $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $parent_item['id'] . ']' . $post_type . '[/url]';
+ $plink = '[url='.$a->get_baseurl().'/display/'.$guid.']'.$post_type.'[/url]';
+ $arr['body'] = sprintf( $bodyverb, $ulink, $alink, $plink );
+
+ $arr['app'] = 'Diaspora';
+
+ $arr['private'] = $parent_item['private'];
+ $arr['verb'] = $activity;
+ $arr['object-type'] = $objtype;
+ $arr['object'] = $obj;
+ $arr['visible'] = 1;
+ $arr['unseen'] = 1;
+ $arr['last-child'] = 0;
+
+ $message_id = item_store($arr);
+
+
+ //if($message_id) {
+ // q("update item set plink = '%s' where id = %d",
+ // //dbesc($a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $message_id),
+ // dbesc($a->get_baseurl().'/display/'.$guid),
+ // intval($message_id)
+ // );
+ //}
+
+ if(! $parent_author_signature) {
+ q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
+ intval($message_id),
+ dbesc($signed_data),
+ dbesc(base64_encode($author_signature)),
+ dbesc($diaspora_handle)
+ );
+ }
+
+ // if the message isn't already being relayed, notify others
+ // the existence of parent_author_signature means the parent_author or owner
+ // is already relaying. The parent_item['origin'] indicates the message was created on our system
+
+ if(($parent_item['origin']) && (! $parent_author_signature))
+ proc_run('php','include/notifier.php','comment-import',$message_id);
+
+ return;
+}
+
+function diaspora_retraction($importer,$xml) {
+
+
+ $guid = notags(unxmlify($xml->guid));
+ $diaspora_handle = notags(unxmlify($xml->diaspora_handle));
+ $type = notags(unxmlify($xml->type));
+
+ $contact = diaspora_get_contact_by_handle($importer['channel_id'],$diaspora_handle);
+ if(! $contact)
+ return;
+
+ if($type === 'Person') {
+ require_once('include/Contact.php');
+ contact_remove($contact['id']);
+ }
+ elseif($type === 'Post') {
+ $r = q("select * from item where guid = '%s' and uid = %d and not file like '%%[%%' limit 1",
+ dbesc('guid'),
+ intval($importer['channel_id'])
+ );
+ if(count($r)) {
+ if(link_compare($r[0]['author-link'],$contact['url'])) {
+ q("update item set `deleted` = 1, `changed` = '%s' where `id` = %d",
+ dbesc(datetime_convert()),
+ intval($r[0]['id'])
+ );
+ }
+ }
+ }
+
+ return 202;
+ // NOTREACHED
+}
+
+function diaspora_signed_retraction($importer,$xml,$msg) {
+
+
+ $guid = notags(unxmlify($xml->target_guid));
+ $diaspora_handle = notags(unxmlify($xml->sender_handle));
+ $type = notags(unxmlify($xml->target_type));
+ $sig = notags(unxmlify($xml->target_author_signature));
+
+ $parent_author_signature = (($xml->parent_author_signature) ? notags(unxmlify($xml->parent_author_signature)) : '');
+
+ $contact = diaspora_get_contact_by_handle($importer['channel_id'],$diaspora_handle);
+ if(! $contact) {
+ logger('diaspora_signed_retraction: no contact ' . $diaspora_handle . ' for ' . $importer['channel_id']);
+ return;
+ }
+
+
+ $signed_data = $guid . ';' . $type ;
+ $key = $msg['key'];
+
+ /* How Diaspora performs relayable_retraction signature checking:
+
+ - If an item has been sent by the item author to the top-level post owner to relay on
+ to the rest of the contacts on the top-level post, the top-level post owner checks
+ the author_signature, then creates a parent_author_signature before relaying the item on
+ - If an item has been relayed on by the top-level post owner, the contacts who receive it
+ check only the parent_author_signature. Basically, they trust that the top-level post
+ owner has already verified the authenticity of anything he/she sends out
+ - In either case, the signature that get checked is the signature created by the person
+ who sent the salmon
+ */
+
+ if($parent_author_signature) {
+
+ $parent_author_signature = base64_decode($parent_author_signature);
+
+ if(! rsa_verify($signed_data,$parent_author_signature,$key,'sha256')) {
+ logger('diaspora_signed_retraction: top-level post owner verification failed');
+ return;
+ }
+
+ }
+ else {
+
+ $sig_decode = base64_decode($sig);
+
+ if(! rsa_verify($signed_data,$sig_decode,$key,'sha256')) {
+ logger('diaspora_signed_retraction: retraction owner verification failed.' . print_r($msg,true));
+ return;
+ }
+ }
+
+ if($type === 'StatusMessage' || $type === 'Comment' || $type === 'Like') {
+ $r = q("select * from item where guid = '%s' and uid = %d and not file like '%%[%%' limit 1",
+ dbesc($guid),
+ intval($importer['channel_id'])
+ );
+ if(count($r)) {
+ if(link_compare($r[0]['author-link'],$contact['url'])) {
+ q("update item set `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = '' where `id` = %d",
+ dbesc(datetime_convert()),
+ dbesc(datetime_convert()),
+ intval($r[0]['id'])
+ );
+
+ // Now check if the retraction needs to be relayed by us
+ //
+ // The first item in the `item` table with the parent id is the parent. However, MySQL doesn't always
+ // return the items ordered by `item`.`id`, in which case the wrong item is chosen as the parent.
+ // The only item with `parent` and `id` as the parent id is the parent item.
+ $p = q("select origin from item where parent = %d and id = %d limit 1",
+ $r[0]['parent'],
+ $r[0]['parent']
+ );
+ if(count($p)) {
+ if(($p[0]['origin']) && (! $parent_author_signature)) {
+ q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
+ $r[0]['id'],
+ dbesc($signed_data),
+ dbesc($sig),
+ dbesc($diaspora_handle)
+ );
+
+ // the existence of parent_author_signature would have meant the parent_author or owner
+ // is already relaying.
+ logger('diaspora_signed_retraction: relaying relayable_retraction');
+
+ proc_run('php','include/notifier.php','drop',$r[0]['id']);
+ }
+ }
+ }
+ }
+ }
+ else
+ logger('diaspora_signed_retraction: unknown type: ' . $type);
+
+ return 202;
+ // NOTREACHED
+}
+
+function diaspora_profile($importer,$xml,$msg) {
+
+ $a = get_app();
+ $diaspora_handle = notags(unxmlify($xml->diaspora_handle));
+
+
+ if($diaspora_handle != $msg['author']) {
+ logger('diaspora_post: Potential forgery. Message handle is not the same as envelope sender.');
+ return 202;
+ }
+
+ $contact = diaspora_get_contact_by_handle($importer['channel_id'],$diaspora_handle);
+ if(! $contact)
+ return;
+
+ if($contact['blocked']) {
+ logger('diaspora_post: Ignoring this author.');
+ return 202;
+ }
+
+ $name = unxmlify($xml->first_name) . ((strlen($xml->last_name)) ? ' ' . unxmlify($xml->last_name) : '');
+ $image_url = unxmlify($xml->image_url);
+ $birthday = unxmlify($xml->birthday);
+
+
+ $handle_parts = explode("@", $diaspora_handle);
+ if($name === '') {
+ $name = $handle_parts[0];
+ }
+
+
+ if( preg_match("|^https?://|", $image_url) === 0) {
+ $image_url = "http://" . $handle_parts[1] . $image_url;
+ }
+
+/* $r = q("SELECT DISTINCT ( `resource-id` ) FROM `photo` WHERE `uid` = %d AND `contact-id` = %d AND `album` = 'Contact Photos' ",
+ intval($importer['channel_id']),
+ intval($contact['id'])
+ );
+ $oldphotos = ((count($r)) ? $r : null);*/
+
+ require_once('include/Photo.php');
+
+ $images = import_profile_photo($image_url,$importer['channel_id'],$contact['id']);
+
+ // Generic birthday. We don't know the timezone. The year is irrelevant.
+
+ $birthday = str_replace('1000','1901',$birthday);
+
+ $birthday = datetime_convert('UTC','UTC',$birthday,'Y-m-d');
+
+ // this is to prevent multiple birthday notifications in a single year
+ // if we already have a stored birthday and the 'm-d' part hasn't changed, preserve the entry, which will preserve the notify year
+
+ if(substr($birthday,5) === substr($contact['bd'],5))
+ $birthday = $contact['bd'];
+
+ // TODO: update name on item['author-name'] if the name changed. See consume_feed()
+ // Not doing this currently because D* protocol is scheduled for revision soon.
+
+ $r = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s', `avatar-date` = '%s' , `bd` = '%s' WHERE `id` = %d AND `uid` = %d",
+ dbesc($name),
+ dbesc(datetime_convert()),
+ dbesc($images[0]),
+ dbesc($images[1]),
+ dbesc($images[2]),
+ dbesc(datetime_convert()),
+ dbesc($birthday),
+ intval($contact['id']),
+ intval($importer['channel_id'])
+ );
+
+/* if($r) {
+ if($oldphotos) {
+ foreach($oldphotos as $ph) {
+ q("DELETE FROM `photo` WHERE `uid` = %d AND `contact-id` = %d AND `album` = 'Contact Photos' AND `resource-id` = '%s' ",
+ intval($importer['channel_id']),
+ intval($contact['id']),
+ dbesc($ph['resource-id'])
+ );
+ }
+ }
+ } */
+
+ return;
+
+}
+
+function diaspora_share($me,$contact) {
+ $a = get_app();
+ $myaddr = $me['nickname'] . '@' . substr($a->get_baseurl(), strpos($a->get_baseurl(),'://') + 3);
+ $theiraddr = $contact['addr'];
+
+ $tpl = get_markup_template('diaspora_share.tpl');
+ $msg = replace_macros($tpl, array(
+ '$sender' => $myaddr,
+ '$recipient' => $theiraddr
+ ));
+
+ $slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($msg,$me,$contact,$me['prvkey'],$contact['pubkey'])));
+ //$slap = 'xml=' . urlencode(diaspora_msg_build($msg,$me,$contact,$me['prvkey'],$contact['pubkey']));
+
+ return(diaspora_transmit($owner,$contact,$slap, false));
+}
+
+function diaspora_unshare($me,$contact) {
+
+ $a = get_app();
+ $myaddr = $me['nickname'] . '@' . substr($a->get_baseurl(), strpos($a->get_baseurl(),'://') + 3);
+
+ $tpl = get_markup_template('diaspora_retract.tpl');
+ $msg = replace_macros($tpl, array(
+ '$guid' => $me['guid'],
+ '$type' => 'Person',
+ '$handle' => $myaddr
+ ));
+
+ $slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($msg,$me,$contact,$me['prvkey'],$contact['pubkey'])));
+ //$slap = 'xml=' . urlencode(diaspora_msg_build($msg,$me,$contact,$me['prvkey'],$contact['pubkey']));
+
+ return(diaspora_transmit($owner,$contact,$slap, false));
+
+}
+
+
+function diaspora_send_status($item,$owner,$contact,$public_batch = false) {
+
+ $a = get_app();
+ $myaddr = $owner['nickname'] . '@' . substr($a->get_baseurl(), strpos($a->get_baseurl(),'://') + 3);
+ $theiraddr = $contact['addr'];
+
+ $images = array();
+
+ $title = $item['title'];
+ $body = $item['body'];
+
+/*
+ // We're trying to match Diaspora's split message/photo protocol but
+ // all the photos are displayed on D* as links and not img's - even
+ // though we're sending pretty much precisely what they send us when
+ // doing the same operation.
+ // Commented out for now, we'll use bb2diaspora to convert photos to markdown
+ // which seems to get through intact.
+
+ $cnt = preg_match_all('|\[img\](.*?)\[\/img\]|',$body,$matches,PREG_SET_ORDER);
+ if($cnt) {
+ foreach($matches as $mtch) {
+ $detail = array();
+ $detail['str'] = $mtch[0];
+ $detail['path'] = dirname($mtch[1]) . '/';
+ $detail['file'] = basename($mtch[1]);
+ $detail['guid'] = $item['guid'];
+ $detail['handle'] = $myaddr;
+ $images[] = $detail;
+ $body = str_replace($detail['str'],$mtch[1],$body);
+ }
+ }
+*/
+
+ //if(strlen($title))
+ // $body = "[b]".html_entity_decode($title)."[/b]\n\n".$body;
+
+ // convert to markdown
+ $body = xmlify(html_entity_decode(bb2diaspora($body)));
+ //$body = bb2diaspora($body);
+
+ // Adding the title
+ if(strlen($title))
+ $body = "## ".html_entity_decode($title)."\n\n".$body;
+
+ if($item['attach']) {
+ $cnt = preg_match_all('/href=\"(.*?)\"(.*?)title=\"(.*?)\"/ism',$item['attach'],$matches,PREG_SET_ORDER);
+ if(cnt) {
+ $body .= "\n" . t('Attachments:') . "\n";
+ foreach($matches as $mtch) {
+ $body .= '[' . $mtch[3] . '](' . $mtch[1] . ')' . "\n";
+ }
+ }
+ }
+
+
+ $public = (($item['private']) ? 'false' : 'true');
+
+ require_once('include/datetime.php');
+ $created = datetime_convert('UTC','UTC',$item['created'],'Y-m-d H:i:s \U\T\C');
+
+ // Detect a share element and do a reshare
+ // see: https://github.com/Raven24/diaspora-federation/blob/master/lib/diaspora-federation/entities/reshare.rb
+ if (!$item['private'] AND ($ret = diaspora_is_reshare($item["body"]))) {
+ $tpl = get_markup_template('diaspora_reshare.tpl');
+ $msg = replace_macros($tpl, array(
+ '$root_handle' => xmlify($ret['root_handle']),
+ '$root_guid' => $ret['root_guid'],
+ '$guid' => $item['guid'],
+ '$handle' => xmlify($myaddr),
+ '$public' => $public,
+ '$created' => $created,
+ '$provider' => $item["app"]
+ ));
+ } else {
+ $tpl = get_markup_template('diaspora_post.tpl');
+ $msg = replace_macros($tpl, array(
+ '$body' => $body,
+ '$guid' => $item['guid'],
+ '$handle' => xmlify($myaddr),
+ '$public' => $public,
+ '$created' => $created,
+ '$provider' => $item["app"]
+ ));
+ }
+
+ logger('diaspora_send_status: '.$owner['username'].' -> '.$contact['name'].' base message: '.$msg, LOGGER_DATA);
+
+ $slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch)));
+ //$slap = 'xml=' . urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch));
+
+ $return_code = diaspora_transmit($owner,$contact,$slap,$public_batch);
+
+ logger('diaspora_send_status: guid: '.$item['guid'].' result '.$return_code, LOGGER_DEBUG);
+
+ if(count($images)) {
+ diaspora_send_images($item,$owner,$contact,$images,$public_batch);
+ }
+
+ return $return_code;
+}
+
+function diaspora_is_reshare($body) {
+ $body = trim($body);
+
+ // Skip if it isn't a pure repeated messages
+ // Does it start with a share?
+ if (strpos($body, "[share") > 0)
+ return(false);
+
+ // Does it end with a share?
+ if (strlen($body) > (strrpos($body, "[/share]") + 8))
+ return(false);
+
+ $attributes = preg_replace("/\[share(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism","$1",$body);
+ // Skip if there is no shared message in there
+ if ($body == $attributes)
+ return(false);
+
+ $profile = "";
+ preg_match("/profile='(.*?)'/ism", $attributes, $matches);
+ if ($matches[1] != "")
+ $profile = $matches[1];
+
+ preg_match('/profile="(.*?)"/ism', $attributes, $matches);
+ if ($matches[1] != "")
+ $profile = $matches[1];
+
+ $ret= array();
+
+ $ret["root_handle"] = preg_replace("=https?://(.*)/u/(.*)=ism", "$2@$1", $profile);
+ if (($ret["root_handle"] == $profile) OR ($ret["root_handle"] == ""))
+ return(false);
+
+ $link = "";
+ preg_match("/link='(.*?)'/ism", $attributes, $matches);
+ if ($matches[1] != "")
+ $link = $matches[1];
+
+ preg_match('/link="(.*?)"/ism', $attributes, $matches);
+ if ($matches[1] != "")
+ $link = $matches[1];
+
+ $ret["root_guid"] = preg_replace("=https?://(.*)/posts/(.*)=ism", "$2", $link);
+ if (($ret["root_guid"] == $link) OR ($ret["root_guid"] == ""))
+ return(false);
+
+ return($ret);
+}
+
+function diaspora_send_images($item,$owner,$contact,$images,$public_batch = false) {
+ $a = get_app();
+ if(! count($images))
+ return;
+ $mysite = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://') + 3) . '/photo';
+
+ $tpl = get_markup_template('diaspora_photo.tpl');
+ foreach($images as $image) {
+ if(! stristr($image['path'],$mysite))
+ continue;
+ $resource = str_replace('.jpg','',$image['file']);
+ $resource = substr($resource,0,strpos($resource,'-'));
+
+ $r = q("select * from photo where `resource-id` = '%s' and `uid` = %d limit 1",
+ dbesc($resource),
+ intval($owner['uid'])
+ );
+ if(! count($r))
+ continue;
+ $public = (($r[0]['allow_cid'] || $r[0]['allow_gid'] || $r[0]['deny_cid'] || $r[0]['deny_gid']) ? 'false' : 'true' );
+ $msg = replace_macros($tpl,array(
+ '$path' => xmlify($image['path']),
+ '$filename' => xmlify($image['file']),
+ '$msg_guid' => xmlify($image['guid']),
+ '$guid' => xmlify($r[0]['guid']),
+ '$handle' => xmlify($image['handle']),
+ '$public' => xmlify($public),
+ '$created_at' => xmlify(datetime_convert('UTC','UTC',$r[0]['created'],'Y-m-d H:i:s \U\T\C'))
+ ));
+
+
+ logger('diaspora_send_photo: base message: ' . $msg, LOGGER_DATA);
+ $slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch)));
+ //$slap = 'xml=' . urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch));
+
+ diaspora_transmit($owner,$contact,$slap,$public_batch);
+ }
+
+}
+
+function diaspora_send_followup($item,$owner,$contact,$public_batch = false) {
+
+ $a = get_app();
+ $myaddr = $owner['nickname'] . '@' . substr($a->get_baseurl(), strpos($a->get_baseurl(),'://') + 3);
+// $theiraddr = $contact['addr'];
+
+ // Diaspora doesn't support threaded comments, but some
+ // versions of Diaspora (i.e. Diaspora-pistos) support
+ // likes on comments
+ if($item['verb'] === ACTIVITY_LIKE && $item['thr-parent']) {
+ $p = q("select guid, type, uri, `parent-uri` from item where uri = '%s' limit 1",
+ dbesc($item['thr-parent'])
+ );
+ }
+ else {
+ // The first item in the `item` table with the parent id is the parent. However, MySQL doesn't always
+ // return the items ordered by `item`.`id`, in which case the wrong item is chosen as the parent.
+ // The only item with `parent` and `id` as the parent id is the parent item.
+ $p = q("select guid, type, uri, `parent-uri` from item where parent = %d and id = %d limit 1",
+ intval($item['parent']),
+ intval($item['parent'])
+ );
+ }
+ if(count($p))
+ $parent = $p[0];
+ else
+ return;
+
+ if($item['verb'] === ACTIVITY_LIKE) {
+ $tpl = get_markup_template('diaspora_like.tpl');
+ $like = true;
+ $target_type = ( $parent['uri'] === $parent['parent-uri'] ? 'Post' : 'Comment');
+// $target_type = (strpos($parent['type'], 'comment') ? 'Comment' : 'Post');
+// $positive = (($item['deleted']) ? 'false' : 'true');
+ $positive = 'true';
+
+ if(($item['deleted']))
+ logger('diaspora_send_followup: received deleted "like". Those should go to diaspora_send_retraction');
+ }
+ else {
+ $tpl = get_markup_template('diaspora_comment.tpl');
+ $like = false;
+ }
+
+ $text = html_entity_decode(bb2diaspora($item['body']));
+
+ // sign it
+
+ if($like)
+ $signed_text = $item['guid'] . ';' . $target_type . ';' . $parent['guid'] . ';' . $positive . ';' . $myaddr;
+ else
+ $signed_text = $item['guid'] . ';' . $parent['guid'] . ';' . $text . ';' . $myaddr;
+
+ $authorsig = base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256'));
+
+ $msg = replace_macros($tpl,array(
+ '$guid' => xmlify($item['guid']),
+ '$parent_guid' => xmlify($parent['guid']),
+ '$target_type' =>xmlify($target_type),
+ '$authorsig' => xmlify($authorsig),
+ '$body' => xmlify($text),
+ '$positive' => xmlify($positive),
+ '$handle' => xmlify($myaddr)
+ ));
+
+ logger('diaspora_followup: base message: ' . $msg, LOGGER_DATA);
+
+ $slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch)));
+ //$slap = 'xml=' . urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch));
+
+ return(diaspora_transmit($owner,$contact,$slap,$public_batch));
+}
+
+
+function diaspora_send_relay($item,$owner,$contact,$public_batch = false) {
+
+
+ $a = get_app();
+ $myaddr = $owner['nickname'] . '@' . substr($a->get_baseurl(), strpos($a->get_baseurl(),'://') + 3);
+// $theiraddr = $contact['addr'];
+
+ $body = $item['body'];
+ $text = html_entity_decode(bb2diaspora($body));
+
+ // Diaspora doesn't support threaded comments, but some
+ // versions of Diaspora (i.e. Diaspora-pistos) support
+ // likes on comments
+ if($item['verb'] === ACTIVITY_LIKE && $item['thr-parent']) {
+ $p = q("select guid, type, uri, `parent-uri` from item where uri = '%s' limit 1",
+ dbesc($item['thr-parent'])
+ );
+ }
+ else {
+ // The first item in the `item` table with the parent id is the parent. However, MySQL doesn't always
+ // return the items ordered by `item`.`id`, in which case the wrong item is chosen as the parent.
+ // The only item with `parent` and `id` as the parent id is the parent item.
+ $p = q("select guid, type, uri, `parent-uri` from item where parent = %d and id = %d limit 1",
+ intval($item['parent']),
+ intval($item['parent'])
+ );
+ }
+ if(count($p))
+ $parent = $p[0];
+ else
+ return;
+
+ $like = false;
+ $relay_retract = false;
+ $sql_sign_id = 'iid';
+ if( $item['deleted']) {
+ $relay_retract = true;
+
+ $target_type = ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
+
+ $sql_sign_id = 'retract_iid';
+ $tpl = get_markup_template('diaspora_relayable_retraction.tpl');
+ }
+ elseif($item['verb'] === ACTIVITY_LIKE) {
+ $like = true;
+
+ $target_type = ( $parent['uri'] === $parent['parent-uri'] ? 'Post' : 'Comment');
+// $positive = (($item['deleted']) ? 'false' : 'true');
+ $positive = 'true';
+
+ $tpl = get_markup_template('diaspora_like_relay.tpl');
+ }
+ else { // item is a comment
+ $tpl = get_markup_template('diaspora_comment_relay.tpl');
+ }
+
+
+ // fetch the original signature if the relayable was created by a Diaspora
+ // or DFRN user. Relayables for other networks are not supported.
+
+/* $r = q("select * from sign where " . $sql_sign_id . " = %d limit 1",
+ intval($item['id'])
+ );
+ if(count($r)) {
+ $orig_sign = $r[0];
+ $signed_text = $orig_sign['signed_text'];
+ $authorsig = $orig_sign['signature'];
+ $handle = $orig_sign['signer'];
+ }
+ else {
+
+ // Author signature information (for likes, comments, and retractions of likes or comments,
+ // whether from Diaspora or Friendica) must be placed in the `sign` table before this
+ // function is called
+ logger('diaspora_send_relay: original author signature not found, cannot send relayable');
+ return;
+ }*/
+
+ /* Since the author signature is only checked by the parent, not by the relay recipients,
+ * I think it may not be necessary for us to do so much work to preserve all the original
+ * signatures. The important thing that Diaspora DOES need is the original creator's handle.
+ * Let's just generate that and forget about all the original author signature stuff.
+ *
+ * Note: this might be more of an problem if we want to support likes on comments for older
+ * versions of Diaspora (diaspora-pistos), but since there are a number of problems with
+ * doing that, let's ignore it for now.
+ *
+ * Currently, only DFRN contacts are supported. StatusNet shouldn't be hard, but it hasn't
+ * been done yet
+ */
+
+ $handle = diaspora_handle_from_contact($item['contact-id']);
+ if(! $handle)
+ return;
+
+
+ if($relay_retract)
+ $sender_signed_text = $item['guid'] . ';' . $target_type;
+ elseif($like)
+ $sender_signed_text = $item['guid'] . ';' . $target_type . ';' . $parent['guid'] . ';' . $positive . ';' . $handle;
+ else
+ $sender_signed_text = $item['guid'] . ';' . $parent['guid'] . ';' . $text . ';' . $handle;
+
+ // Sign the relayable with the top-level owner's signature
+ //
+ // We'll use the $sender_signed_text that we just created, instead of the $signed_text
+ // stored in the database, because that provides the best chance that Diaspora will
+ // be able to reconstruct the signed text the same way we did. This is particularly a
+ // concern for the comment, whose signed text includes the text of the comment. The
+ // smallest change in the text of the comment, including removing whitespace, will
+ // make the signature verification fail. Since we translate from BB code to Diaspora's
+ // markup at the top of this function, which is AFTER we placed the original $signed_text
+ // in the database, it's hazardous to trust the original $signed_text.
+
+ $parentauthorsig = base64_encode(rsa_sign($sender_signed_text,$owner['uprvkey'],'sha256'));
+
+ $msg = replace_macros($tpl,array(
+ '$guid' => xmlify($item['guid']),
+ '$parent_guid' => xmlify($parent['guid']),
+ '$target_type' =>xmlify($target_type),
+ '$authorsig' => xmlify($authorsig),
+ '$parentsig' => xmlify($parentauthorsig),
+ '$body' => xmlify($text),
+ '$positive' => xmlify($positive),
+ '$handle' => xmlify($handle)
+ ));
+
+ logger('diaspora_send_relay: base message: ' . $msg, LOGGER_DATA);
+
+
+ $slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch)));
+ //$slap = 'xml=' . urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch));
+
+ return(diaspora_transmit($owner,$contact,$slap,$public_batch));
+
+}
+
+
+
+function diaspora_send_retraction($item,$owner,$contact,$public_batch = false) {
+
+ $a = get_app();
+ $myaddr = $owner['nickname'] . '@' . substr($a->get_baseurl(), strpos($a->get_baseurl(),'://') + 3);
+
+ // Check whether the retraction is for a top-level post or whether it's a relayable
+ if( $item['uri'] !== $item['parent-uri'] ) {
+
+ $tpl = get_markup_template('diaspora_relay_retraction.tpl');
+ $target_type = (($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment');
+ }
+ else {
+
+ $tpl = get_markup_template('diaspora_signed_retract.tpl');
+ $target_type = 'StatusMessage';
+ }
+
+ $signed_text = $item['guid'] . ';' . $target_type;
+
+ $msg = replace_macros($tpl, array(
+ '$guid' => xmlify($item['guid']),
+ '$type' => xmlify($target_type),
+ '$handle' => xmlify($myaddr),
+ '$signature' => xmlify(base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256')))
+ ));
+
+ $slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch)));
+ //$slap = 'xml=' . urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch));
+
+ return(diaspora_transmit($owner,$contact,$slap,$public_batch));
+}
+
+function diaspora_send_mail($item,$owner,$contact) {
+
+ $a = get_app();
+ $myaddr = $owner['nickname'] . '@' . substr($a->get_baseurl(), strpos($a->get_baseurl(),'://') + 3);
+
+ $r = q("select * from conv where id = %d and uid = %d limit 1",
+ intval($item['convid']),
+ intval($item['uid'])
+ );
+
+ if(! count($r)) {
+ logger('diaspora_send_mail: conversation not found.');
+ return;
+ }
+ $cnv = $r[0];
+
+ $conv = array(
+ 'guid' => xmlify($cnv['guid']),
+ 'subject' => xmlify($cnv['subject']),
+ 'created_at' => xmlify(datetime_convert('UTC','UTC',$cnv['created'],'Y-m-d H:i:s \U\T\C')),
+ 'diaspora_handle' => xmlify($cnv['creator']),
+ 'participant_handles' => xmlify($cnv['recips'])
+ );
+
+ $body = bb2diaspora($item['body']);
+ $created = datetime_convert('UTC','UTC',$item['created'],'Y-m-d H:i:s \U\T\C');
+
+ $signed_text = $item['guid'] . ';' . $cnv['guid'] . ';' . $body . ';'
+ . $created . ';' . $myaddr . ';' . $cnv['guid'];
+
+ $sig = base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256'));
+
+ $msg = array(
+ 'guid' => xmlify($item['guid']),
+ 'parent_guid' => xmlify($cnv['guid']),
+ 'parent_author_signature' => (($item['reply']) ? null : xmlify($sig)),
+ 'author_signature' => xmlify($sig),
+ 'text' => xmlify($body),
+ 'created_at' => xmlify($created),
+ 'diaspora_handle' => xmlify($myaddr),
+ 'conversation_guid' => xmlify($cnv['guid'])
+ );
+
+ if($item['reply']) {
+ $tpl = get_markup_template('diaspora_message.tpl');
+ $xmsg = replace_macros($tpl, array('$msg' => $msg));
+ }
+ else {
+ $conv['messages'] = array($msg);
+ $tpl = get_markup_template('diaspora_conversation.tpl');
+ $xmsg = replace_macros($tpl, array('$conv' => $conv));
+ }
+
+ logger('diaspora_conversation: ' . print_r($xmsg,true), LOGGER_DATA);
+
+ $slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($xmsg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],false)));
+ //$slap = 'xml=' . urlencode(diaspora_msg_build($xmsg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],false));
+
+ return(diaspora_transmit($owner,$contact,$slap,false));
+
+
+}
+
+function diaspora_transmit($owner,$contact,$slap,$public_batch,$queue_run=false) {
+
+ $enabled = intval(get_config('system','diaspora_enabled'));
+ if(! $enabled) {
+ return 200;
+ }
+
+ $a = get_app();
+ $logid = random_string(4);
+ $dest_url = (($public_batch) ? $contact['batch'] : $contact['notify']);
+ if(! $dest_url) {
+ logger('diaspora_transmit: no url for contact: ' . $contact['id'] . ' batch mode =' . $public_batch);
+ return 0;
+ }
+
+ logger('diaspora_transmit: ' . $logid . ' ' . $dest_url);
+
+ if( (! $queue_run) && (was_recently_delayed($contact['id'])) ) {
+ $return_code = 0;
+ }
+ else {
+ if (!intval(get_config('system','diaspora_test'))) {
+ post_url($dest_url . '/', $slap);
+ $return_code = $a->get_curl_code();
+ } else {
+ logger('diaspora_transmit: test_mode');
+ return 200;
+ }
+ }
+
+ logger('diaspora_transmit: ' . $logid . ' returns: ' . $return_code);
+
+ if((! $return_code) || (($return_code == 503) && (stristr($a->get_curl_headers(),'retry-after')))) {
+ logger('diaspora_transmit: queue message');
+
+ $r = q("SELECT id from queue where cid = %d and network = '%s' and content = '%s' and batch = %d limit 1",
+ intval($contact['id']),
+ dbesc(NETWORK_DIASPORA),
+ dbesc($slap),
+ intval($public_batch)
+ );
+ if(count($r)) {
+ logger('diaspora_transmit: add_to_queue ignored - identical item already in queue');
+ }
+ else {
+ // queue message for redelivery
+ add_to_queue($contact['id'],NETWORK_DIASPORA,$slap,$public_batch);
+ }
+ }
+
+
+ return(($return_code) ? $return_code : (-1));
+}
+
+
diff --git a/include/dir_fns.php b/include/dir_fns.php
index 1bc97cfec..815b6a197 100644
--- a/include/dir_fns.php
+++ b/include/dir_fns.php
@@ -64,26 +64,40 @@ function sync_directories($dirmode) {
if($dirmode == DIRECTORY_MODE_STANDALONE || $dirmode == DIRECTORY_MODE_NORMAL)
return;
- $r = q("select * from site where (site_flags & %d) and site_url != '%s'",
- intval(DIRECTORY_MODE_PRIMARY|DIRECTORY_MODE_SECONDARY),
- dbesc(z_root())
- );
+ $realm = get_directory_realm();
+ if($realm == DIRECTORY_REALM) {
+ $r = q("select * from site where (site_flags & %d) and site_url != '%s' and ( site_realm = '%s' or site_realm = '') ",
+ intval(DIRECTORY_MODE_PRIMARY|DIRECTORY_MODE_SECONDARY),
+ dbesc(z_root()),
+ dbesc($realm)
+ );
+ }
+ else {
+ $r = q("select * from site where (site_flags & %d) and site_url != '%s' and site_realm like '%s' ",
+ intval(DIRECTORY_MODE_PRIMARY|DIRECTORY_MODE_SECONDARY),
+ dbesc(z_root()),
+ dbesc(protect_sprintf('%' . $realm . '%'))
+ );
+ }
// 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)) {
$r = array(
'site_url' => DIRECTORY_FALLBACK_MASTER,
'site_flags' => DIRECTORY_MODE_PRIMARY,
'site_update' => '0000-00-00 00:00:00',
- 'site_directory' => DIRECTORY_FALLBACK_MASTER . '/dirsearch'
+ 'site_directory' => DIRECTORY_FALLBACK_MASTER . '/dirsearch',
+ 'site_realm' => DIRECTORY_REALM
);
- $x = q("insert into site ( site_url, site_flags, site_update, site_directory )
- values ( '%s', %d', '%s', '%s' ) ",
+ $x = q("insert into site ( site_url, site_flags, site_update, site_directory, site_realm )
+ values ( '%s', %d', '%s', '%s', '%s' ) ",
dbesc($r[0]['site_url']),
intval($r[0]['site_flags']),
dbesc($r[0]['site_update']),
- dbesc($r[0]['site_directory'])
+ dbesc($r[0]['site_directory']),
+ dbesc($r[0]['site_realm'])
);
$r = q("select * from site where (site_flags & %d) and site_url != '%s'",
@@ -185,6 +199,7 @@ function local_dir_update($uid,$force) {
);
$profile = array();
+ $profile['encoding'] = 'zot';
if($p) {
$hash = $p[0]['channel_hash'];
diff --git a/include/enotify.php b/include/enotify.php
index 8baf5c09f..2503f9ab0 100644
--- a/include/enotify.php
+++ b/include/enotify.php
@@ -65,7 +65,7 @@ function notification($params) {
localize_item($i);
$title = $i['title'];
$body = $i['body'];
- $private = $i['item_private'];
+ $private = (($i['item_private']) || ($i['item_flags'] & ITEM_OBSCURED));
}
else {
$title = $params['item']['title'];
@@ -135,7 +135,7 @@ function notification($params) {
$item_post_type = item_post_type($p[0]);
- $private = $p[0]['item_private'];
+// $private = $p[0]['item_private'];
$parent_id = $p[0]['id'];
//$possess_desc = str_replace('<!item_type!>',$possess_desc);
@@ -411,7 +411,7 @@ function notification($params) {
$textversion = strip_tags(html_entity_decode(bbcode(stripslashes(str_replace(array("\\r", "\\n"), array( "", "\n"), $body))),ENT_QUOTES,'UTF-8'));
- $htmlversion = html_entity_decode(bbcode(stripslashes(str_replace(array("\\r","\\n"), array("","<br />\n"),$body))), ENT_QUOTES,'UTF-8');
+ $htmlversion = bbcode(stripslashes(str_replace(array("\\r","\\n"), array("","<br />\n"),$body)));
// use $_SESSION['zid_override'] to force zid() to use
@@ -461,6 +461,8 @@ function notification($params) {
// Might be interesting to use GPG,PGP,S/MIME encryption instead
// but we'll save that for a clever plugin developer to implement
+ $private_activity = false;
+
if(! $datarray['email_secure']) {
switch($params['type']) {
case NOTIFY_WALL:
@@ -469,14 +471,22 @@ function notification($params) {
case NOTIFY_COMMENT:
if(! $private)
break;
+ $private_activity = true;
case NOTIFY_MAIL:
$datarray['textversion'] = $datarray['htmlversion'] = $datarray['title'] = '';
+ $datarray['subject'] = preg_replace('/' . preg_quote(t('[Red:Notify]')) . '/','$0*',$datarray['subject']);
break;
default:
break;
}
}
+ if($private_activity
+ && intval(get_pconfig($datarray['uid'],'system','ignore_private_notifications'))) {
+ pop_lang();
+ return;
+ }
+
// load the template for private message notifications
$tpl = get_markup_template('email_notify_html.tpl');
$email_html_body = replace_macros($tpl,array(
diff --git a/include/externals.php b/include/externals.php
index 1d9fd2902..a96bf7c97 100644
--- a/include/externals.php
+++ b/include/externals.php
@@ -33,8 +33,32 @@ function externals_run($argv, $argc){
$url = $r[0]['site_url'];
}
+ // Note: blacklisted sites must be stored in the config as an array.
+ // No simple way to turn this into a personal config because we have no identity here.
+ // For that we probably need a variant of superblock.
+
+ $blacklisted = false;
+ $bl1 = get_config('system','blacklisted_sites');
+ if(is_array($bl1) && $bl1) {
+ foreach($bl1 as $bl) {
+ if(strpos($url,$bl) !== false) {
+ $blacklisted = true;
+ break;
+ }
+ }
+ }
+
$attempts ++;
+ // make sure we can eventually break out if somebody blacklists all known sites
+
+ if($blacklisted) {
+ if($attempts > 20)
+ break;
+ $attempts --;
+ continue;
+ }
+
if($url) {
if($r[0]['site_pull'] !== '0000-00-00 00:00:00')
$mindate = urlencode($r[0]['site_pull']);
diff --git a/include/features.php b/include/features.php
index a2698cc72..6bb444cb6 100644
--- a/include/features.php
+++ b/include/features.php
@@ -24,6 +24,8 @@ function get_features() {
// This is per post, and different from fixed expiration 'expire' which isn't working yet
array('content_expire', t('Content Expiration'), t('Remove posts/comments and/or private messages at a future time')),
array('multi_profiles', t('Multiple Profiles'), t('Ability to create multiple profiles')),
+ array('advanced_profiles', t('Advanced Profiles'), t('Additional profile sections and selections')),
+ array('profile_export', t('Profile Import/Export'), t('Save and load profile details across sites/channels')),
array('webpages', t('Web Pages'), t('Provide managed web pages on your channel')),
array('private_notes', t('Private Notes'), t('Enables a tool to store notes and reminders')),
// prettyphoto has licensing issues and will no longer be provided in core -
diff --git a/include/identity.php b/include/identity.php
index e210b37ab..9335673a0 100644
--- a/include/identity.php
+++ b/include/identity.php
@@ -559,6 +559,37 @@ function profile_load(&$a, $nickname, $profile = '') {
return;
}
+ $q = q("select * from profext where hash = '%s' and channel_id = %d",
+ dbesc($p[0]['profile_guid']),
+ intval($p[0]['profile_uid'])
+ );
+ if($q) {
+
+ $extra_fields = array();
+
+ require_once('include/identity.php');
+ $profile_fields_basic = get_profile_fields_basic();
+ $profile_fields_advanced = get_profile_fields_advanced();
+
+ $advanced = ((feature_enabled(local_user(),'advanced_profiles')) ? true : false);
+ if($advanced)
+ $fields = $profile_fields_advanced;
+ else
+ $fields = $profile_fields_basic;
+
+ foreach($q as $qq) {
+ foreach($fields as $k => $f) {
+ if($k == $qq['k']) {
+ $p[0][$k] = $qq['v'];
+ $extra_fields[] = $k;
+ break;
+ }
+ }
+ }
+ }
+
+ $p[0]['extra_fields'] = $extra_fields;
+
$z = q("select xchan_photo_date from xchan where xchan_hash = '%s' limit 1",
dbesc($p[0]['channel_hash'])
);
@@ -952,7 +983,7 @@ function advanced_profile(&$a) {
if($a->profile['gender']) $profile['gender'] = array( t('Gender:'), $a->profile['gender'] );
$ob_hash = get_observer_hash();
- if($ob_hash && perm_is_allowed($a->profile['profile_uid'],$ob_hash,'post_wall')) {
+ if($ob_hash && perm_is_allowed($a->profile['profile_uid'],$ob_hash,'post_like')) {
$profile['canlike'] = true;
$profile['likethis'] = t('Like this channel');
$profile['profile_guid'] = $a->profile['profile_guid'];
@@ -1041,6 +1072,16 @@ function advanced_profile(&$a) {
if($txt = prepare_text($a->profile['education'])) $profile['education'] = array( t('School/education:'), $txt );
+ if($a->profile['extra_fields']) {
+ foreach($a->profile['extra_fields'] as $f) {
+ $x = q("select * from profdef where field_name = '%s' limit 1",
+ dbesc($f)
+ );
+ if($x && $txt = prepare_text($a->profile[$f]))
+ $profile[$f] = array( $x[0]['field_desc'] . ':',$txt);
+ }
+ $profile['extra_fields'] = $a->profile['extra_fields'];
+ }
$things = get_things($a->profile['profile_guid'],$a->profile['profile_uid']);
@@ -1312,3 +1353,38 @@ function is_public_profile() {
return true;
return false;
}
+
+function get_profile_fields_basic($filter = 0) {
+
+ $profile_fields_basic = (($filter == 0) ? get_config('system','profile_fields_basic') : null);
+ if(! $profile_fields_basic)
+ $profile_fields_basic = array('name','pdesc','chandesc','gender','dob','dob_tz','address','locality','region','postal_code','country_name','marital','sexual','homepage','hometown','keywords','about','contact');
+
+ $x = array();
+ if($profile_fields_basic)
+ foreach($profile_fields_basic as $f)
+ $x[$f] = 1;
+
+ return $x;
+
+}
+
+
+function get_profile_fields_advanced($filter = 0) {
+ $basic = get_profile_fields_basic($filter);
+ $profile_fields_advanced = (($filter == 0) ? get_config('system','profile_fields_advanced') : null);
+ if(! $profile_fields_advanced)
+ $profile_fields_advanced = array('with','howlong','politic','religion','likes','dislikes','interest','channels','music','book','film','tv','romance','work','education');
+
+ $x = array();
+ if($basic)
+ foreach($basic as $f => $v)
+ $x[$f] = $v;
+ if($profile_fields_advanced)
+ foreach($profile_fields_advanced as $f)
+ $x[$f] = 1;
+
+ return $x;
+}
+
+
diff --git a/include/items.php b/include/items.php
index 10daa85f2..05ff1b078 100755
--- a/include/items.php
+++ b/include/items.php
@@ -7,17 +7,13 @@ require_once('include/photo/photo_driver.php');
require_once('include/permissions.php');
-function collect_recipients($item,&$private) {
+function collect_recipients($item,&$private_envelope) {
require_once('include/group.php');
- $private = ((intval($item['item_private'])) ? true : false);
+ $private_envelope = ((intval($item['item_private'])) ? true : false);
$recipients = array();
- // if the post is marked private but there are no recipients, only add the author and owner
- // as recipients. The ACL for the post may live on the hub of a different clone. We need to
- // get the post to that hub.
-
if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid']) {
// it is private
@@ -49,10 +45,21 @@ function collect_recipients($item,&$private) {
$deny = array_unique(array_merge($deny_people,$deny_groups));
$recipients = array_diff($recipients,$deny);
- $private = true;
+ $private_envelope = true;
}
else {
- if(! $private) {
+
+ // if the post is marked private but there are no recipients and public_policy/scope = self,
+ // only add the author and owner as recipients. The ACL for the post may live on the hub of
+ // a different clone. We need to get the post to that hub.
+
+ // The post may be private by virtue of not being visible to anybody on the internet,
+ // but there are no envelope recipients, so set this to false. Delivery is controlled
+ // by the directives in $item['public_policy'].
+
+ $private_envelope = false;
+
+ if(array_key_exists('public_policy',$item) && $item['public_policy'] !== 'self') {
$r = q("select abook_xchan from abook where abook_channel = %d and not (abook_flags & %d) ",
intval($item['uid']),
intval(ABOOK_FLAG_SELF|ABOOK_FLAG_PENDING|ABOOK_FLAG_ARCHIVED)
@@ -664,10 +671,16 @@ function title_is_body($title, $body) {
function get_item_elements($x) {
-
$arr = array();
$arr['body'] = (($x['body']) ? htmlspecialchars($x['body'],ENT_COMPAT,'UTF-8',false) : '');
+ $maxlen = get_max_import_size();
+
+ if($maxlen && mb_strlen($arr['body']) > $maxlen) {
+ $arr['body'] = mb_substr($arr['body'],0,$maxlen,'UTF-8');
+ logger('get_item_elements: message length exceeds max_import_size: truncated');
+ }
+
$arr['created'] = datetime_convert('UTC','UTC',$x['created']);
$arr['edited'] = datetime_convert('UTC','UTC',$x['edited']);
@@ -703,6 +716,11 @@ function get_item_elements($x) {
$arr['mimetype'] = (($x['mimetype']) ? htmlspecialchars($x['mimetype'], ENT_COMPAT,'UTF-8',false) : '');
$arr['obj_type'] = (($x['object_type']) ? htmlspecialchars($x['object_type'], ENT_COMPAT,'UTF-8',false) : '');
$arr['tgt_type'] = (($x['target_type']) ? htmlspecialchars($x['target_type'], ENT_COMPAT,'UTF-8',false) : '');
+
+ $arr['public_policy'] = (($x['public_scope']) ? htmlspecialchars($x['public_scope'], ENT_COMPAT,'UTF-8',false) : '');
+ if($arr['public_policy'] === 'public')
+ $arr['public_policy'] = '';
+
$arr['comment_policy'] = (($x['comment_scope']) ? htmlspecialchars($x['comment_scope'], ENT_COMPAT,'UTF-8',false) : 'contacts');
$arr['sig'] = (($x['signature']) ? htmlspecialchars($x['signature'], ENT_COMPAT,'UTF-8',false) : '');
@@ -838,6 +856,7 @@ function import_author_rss($x) {
function encode_item($item) {
$x = array();
$x['type'] = 'activity';
+ $x['encoding'] = 'zot';
// logger('encode_item: ' . print_r($item,true));
@@ -845,16 +864,15 @@ function encode_item($item) {
intval($item['uid'])
);
- if($r) {
- $public_scope = $r[0]['channel_r_stream'];
+ if($r)
$comment_scope = $r[0]['channel_w_comment'];
- }
- else {
- $public_scope = 0;
+ else
$comment_scope = 0;
- }
- $scope = map_scope($public_scope);
+ $scope = $item['public_policy'];
+ if(! $scope)
+ $scope = 'public';
+
$c_scope = map_scope($comment_scope);
if(array_key_exists('item_flags',$item) && ($item['item_flags'] & ITEM_OBSCURED)) {
@@ -897,8 +915,7 @@ function encode_item($item) {
if($y = encode_item_flags($item))
$x['flags'] = $y;
- if(! in_array('private',$y))
- $x['public_scope'] = $scope;
+ $x['public_scope'] = $scope;
if($item['item_flags'] & ITEM_NOCOMMENT)
$x['comment_scope'] = 'none';
@@ -915,14 +932,18 @@ function encode_item($item) {
}
-function map_scope($scope) {
+function map_scope($scope,$strip = false) {
switch($scope) {
case 0:
return 'self';
case PERMS_PUBLIC:
+ if($strip)
+ return '';
return 'public';
case PERMS_NETWORK:
return 'network: red';
+ case PERMS_AUTHED:
+ return 'authenticated';
case PERMS_SITE:
return 'site: ' . get_app()->get_hostname();
case PERMS_PENDING:
@@ -933,7 +954,22 @@ function map_scope($scope) {
}
}
-
+function translate_scope($scope) {
+ if(! $scope || $scope === 'public')
+ return t('Visible to anybody on the internet.');
+ if(strpos($scope,'self') === 0)
+ return t('Visible to you only.');
+ if(strpos($scope,'network:') === 0)
+ return t('Visible to anybody in this network.');
+ if(strpos($scope,'authenticated') === 0)
+ return t('Visible to anybody authenticated.');
+ if(strpos($scope,'site:') === 0)
+ return sprintf( t('Visible to anybody on %s.'), strip_tags(substr($scope,6)));
+ if(strpos($scope,'any connections') === 0)
+ return t('Visible to all connections.');
+ if(strpos($scope,'contacts') === 0)
+ return t('Visible to approved connections.');
+}
function encode_item_xchan($xchan) {
@@ -1069,6 +1105,7 @@ function encode_item_flags($item) {
function encode_mail($item) {
$x = array();
$x['type'] = 'mail';
+ $x['encoding'] = 'zot';
if(array_key_exists('mail_flags',$item) && ($item['mail_flags'] & MAIL_OBSCURED)) {
$key = get_config('system','prvkey');
@@ -1730,6 +1767,8 @@ function item_store($arr,$allow_exec = false) {
$arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
$arr['item_restrict'] = ((x($arr,'item_restrict')) ? intval($arr['item_restrict']) : 0 );
+ $arr['public_policy'] = ((x($arr,'public_policy')) ? notags(trim($arr['public_policy'])) : '' );
+
$arr['comment_policy'] = ((x($arr,'comment_policy')) ? notags(trim($arr['comment_policy'])) : 'contacts' );
@@ -1762,6 +1801,7 @@ function item_store($arr,$allow_exec = false) {
$allow_gid = $arr['allow_gid'];
$deny_cid = $arr['deny_cid'];
$deny_gid = $arr['deny_gid'];
+ $public_policy = $arr['public_policy'];
$arr['item_flags'] = $arr['item_flags'] | ITEM_THREAD_TOP;
}
else {
@@ -1798,6 +1838,7 @@ function item_store($arr,$allow_exec = false) {
$allow_gid = $r[0]['allow_gid'];
$deny_cid = $r[0]['deny_cid'];
$deny_gid = $r[0]['deny_gid'];
+ $public_policy = $r[0]['public_policy'];
if($r[0]['item_flags'] & ITEM_WALL)
$arr['item_flags'] = $arr['item_flags'] | ITEM_WALL;
@@ -1811,10 +1852,7 @@ function item_store($arr,$allow_exec = false) {
$uplinked_comment = true;
}
-
// if the parent is private, force privacy for the entire conversation
- // This differs from the above settings as it subtly allows comments from
- // email correspondents to be private even if the overall thread is not.
if($r[0]['item_private'])
$arr['item_private'] = $r[0]['item_private'];
@@ -1906,7 +1944,7 @@ function item_store($arr,$allow_exec = false) {
if((! $parent_id) || ($arr['parent_mid'] === $arr['mid']))
$parent_id = $current_post;
- if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid))
+ if(strlen($allow_cid) || strlen($allow_gid) || strlen($deny_cid) || strlen($deny_gid) || strlen($public_policy))
$private = 1;
else
$private = $arr['item_private'];
@@ -1914,12 +1952,13 @@ function item_store($arr,$allow_exec = false) {
// Set parent id - and also make sure to inherit the parent's ACL's.
$r = q("UPDATE item SET parent = %d, allow_cid = '%s', allow_gid = '%s',
- deny_cid = '%s', deny_gid = '%s', item_private = %d WHERE id = %d LIMIT 1",
+ deny_cid = '%s', deny_gid = '%s', public_policy = '%s', item_private = %d WHERE id = %d LIMIT 1",
intval($parent_id),
dbesc($allow_cid),
dbesc($allow_gid),
dbesc($deny_cid),
dbesc($deny_gid),
+ dbesc($public_policy),
intval($private),
intval($current_post)
);
@@ -1931,6 +1970,7 @@ function item_store($arr,$allow_exec = false) {
$arr['allow_gid'] = $allow_gid;
$arr['deny_cid'] = $deny_cid;
$arr['deny_gid'] = $deny_gid;
+ $arr['public_policy'] = $public_policy;
$arr['item_private'] = $private;
// Store taxonomy
@@ -2132,6 +2172,11 @@ function item_store_update($arr,$allow_exec = false) {
$arr['sig'] = ((x($arr,'sig')) ? $arr['sig'] : '');
$arr['layout_mid'] = ((array_key_exists('layout_mid',$arr)) ? dbesc($arr['layout_mid']) : $orig[0]['layout_mid'] );
+ $arr['public_policy'] = ((x($arr,'public_policy')) ? notags(trim($arr['public_policy'])) : $orig[0]['public_policy'] );
+ $arr['comment_policy'] = ((x($arr,'comment_policy')) ? notags(trim($arr['comment_policy'])) : $orig[0]['comment_policy'] );
+
+
+
call_hooks('post_remote_update',$arr);
if(x($arr,'cancel')) {
@@ -2417,6 +2462,8 @@ function tag_deliver($uid,$item_id) {
$private = (($u[0]['channel_allow_cid'] || $u[0]['channel_allow_gid'] || $u[0]['channel_deny_cid'] || $u[0]['channel_deny_gid']) ? 1 : 0);
+//FIXME - add check for public_policy
+
$flag_bits = ITEM_WALL|ITEM_ORIGIN;
// maintain the original source, which will be the original item owner and was stored in source_xchan
@@ -2557,6 +2604,8 @@ function tag_deliver($uid,$item_id) {
$private = (($u[0]['channel_allow_cid'] || $u[0]['channel_allow_gid'] || $u[0]['channel_deny_cid'] || $u[0]['channel_deny_gid']) ? 1 : 0);
+// FIXME set public_policy and recheck private
+
$flag_bits = ITEM_WALL|ITEM_ORIGIN|ITEM_UPLINK;
// preserve the source
diff --git a/include/nav.php b/include/nav.php
index 80e4955e5..8133ecf67 100644
--- a/include/nav.php
+++ b/include/nav.php
@@ -34,6 +34,10 @@ EOT;
if(local_user()) {
$channel = $a->get_channel();
$observer = $a->get_observer();
+ $prof = q("select id from profile where uid = %d and is_default = 1",
+ intval($channel['channel_id'])
+ );
+
}
elseif(remote_user())
$observer = $a->get_observer();
@@ -81,6 +85,9 @@ EOT;
$nav['usermenu'][] = Array('profile/' . $channel['channel_address'], t('View Profile'), "", t('Your profile page'));
if(feature_enabled(local_user(),'multi_profiles'))
$nav['usermenu'][] = Array('profiles', t('Edit Profiles'),"", t('Manage/Edit profiles'));
+ else
+ $nav['usermenu'][] = Array('profiles/' . $prof[0]['id'], t('Edit Profile'),"", t('Edit your profile'));
+
$nav['usermenu'][] = Array('photos/' . $channel['channel_address'], t('Photos'), "", t('Your photos'));
$nav['usermenu'][] = Array('cloud/' . $channel['channel_address'],t('Files'),"",t('Your files'));
diff --git a/include/permissions.php b/include/permissions.php
index facba037f..8e4676f51 100644
--- a/include/permissions.php
+++ b/include/permissions.php
@@ -10,12 +10,12 @@ function get_perms() {
$global_perms = array(
// Read only permissions
- 'view_stream' => array('channel_r_stream', intval(PERMS_R_STREAM), true, t('Can view my "public" stream and posts'), ''),
- 'view_profile' => array('channel_r_profile', intval(PERMS_R_PROFILE), true, t('Can view my "public" channel profile'), ''),
- 'view_photos' => array('channel_r_photos', intval(PERMS_R_PHOTOS), true, t('Can view my "public" photo albums'), ''),
- 'view_contacts' => array('channel_r_abook', intval(PERMS_R_ABOOK), true, t('Can view my "public" address book'), ''),
- 'view_storage' => array('channel_r_storage', intval(PERMS_R_STORAGE), true, t('Can view my "public" file storage'), ''),
- 'view_pages' => array('channel_r_pages', intval(PERMS_R_PAGES), true, t('Can view my "public" pages'), ''),
+ 'view_stream' => array('channel_r_stream', intval(PERMS_R_STREAM), true, t('Can view my normal stream and posts'), ''),
+ 'view_profile' => array('channel_r_profile', intval(PERMS_R_PROFILE), true, t('Can view my default channel profile'), ''),
+ 'view_photos' => array('channel_r_photos', intval(PERMS_R_PHOTOS), true, t('Can view my photo albums'), ''),
+ 'view_contacts' => array('channel_r_abook', intval(PERMS_R_ABOOK), true, t('Can view my connections'), ''),
+ 'view_storage' => array('channel_r_storage', intval(PERMS_R_STORAGE), true, t('Can view my file storage'), ''),
+ 'view_pages' => array('channel_r_pages', intval(PERMS_R_PAGES), true, t('Can view my webpages'), ''),
// Write permissions
'send_stream' => array('channel_w_stream', intval(PERMS_W_STREAM), false, t('Can send me their channel stream and posts'), ''),
@@ -23,14 +23,14 @@ function get_perms() {
'post_comments' => array('channel_w_comment', intval(PERMS_W_COMMENT), false, t('Can comment on or like my posts'), ''),
'post_mail' => array('channel_w_mail', intval(PERMS_W_MAIL), false, t('Can send me private mail messages'), ''),
'post_photos' => array('channel_w_photos', intval(PERMS_W_PHOTOS), false, t('Can post photos to my photo albums'), ''),
- 'post_like' => array('channel_w_like', intval(PERMS_W_LIKE), false, t('Can like/dislike stuff'), 'Profiles and things other than posts/comments'),
+ 'post_like' => array('channel_w_like', intval(PERMS_W_LIKE), false, t('Can like/dislike stuff'), t('Profiles and things other than posts/comments')),
'tag_deliver' => array('channel_w_tagwall', intval(PERMS_W_TAGWALL), false, t('Can forward to all my channel contacts via post @mentions'), t('Advanced - useful for creating group forum channels')),
'chat' => array('channel_w_chat', intval(PERMS_W_CHAT), false, t('Can chat with me (when available)'), t('')),
- 'write_storage' => array('channel_w_storage', intval(PERMS_W_STORAGE), false, t('Can write to my "public" file storage'), ''),
- 'write_pages' => array('channel_w_pages', intval(PERMS_W_PAGES), false, t('Can edit my "public" pages'), ''),
+ 'write_storage' => array('channel_w_storage', intval(PERMS_W_STORAGE), false, t('Can write to my file storage'), ''),
+ 'write_pages' => array('channel_w_pages', intval(PERMS_W_PAGES), false, t('Can edit my webpages'), ''),
- 'republish' => array('channel_a_republish', intval(PERMS_A_REPUBLISH), false, t('Can source my "public" posts in derived channels'), t('Somewhat advanced - very useful in open communities')),
+ 'republish' => array('channel_a_republish', intval(PERMS_A_REPUBLISH), false, t('Can source my public posts in derived channels'), t('Somewhat advanced - very useful in open communities')),
'delegate' => array('channel_a_delegate', intval(PERMS_A_DELEGATE), false, t('Can administer my channel resources'), t('Extremely advanced. Leave this alone unless you know what you are doing')),
);
@@ -411,3 +411,63 @@ function site_default_perms() {
}
return $ret;
}
+
+
+/**
+ * @function get_role_perms($role)
+ * @param string $role
+ *
+ * Given a string for the channel role ('social','forum', etc)
+ * return an array of all permission fields pre-filled for this role.
+ * This includes the channel permission scope indicators as well as
+ * perms_auto: The permissions to apply automatically on receipt of a connection request
+ * perms_follow: The permissions to apply when initiating a connection request to another channel
+ * perms_accept: The permissions to apply when accepting a connection request from another channel (not automatic)
+ *
+ * Any attributes may be extended (new roles defined) and modified (specific permissions altered) by plugins
+ *
+ */
+
+function get_role_perms($role) {
+
+ $ret = array();
+
+ $ret['role'] = $role;
+
+ switch($role) {
+ case 'social':
+ $ret['perms_auto'] = 0;
+ $ret['perms_follow'] = PERMS_R_STREAM|PERMS_R_PROFILE|PERMS_R_PHOTOS|PERMS_R_ABOOK
+ |PERMS_W_STREAM|PERMS_W_WALL|PERMS_W_COMMENT|PERMS_W_MAIL|PERMS_W_CHAT
+ |PERMS_R_STORAGE|PERMS_R_PAGES|PERMS_A_REPUBLISH|PERMS_W_LIKE;
+ $ret['perms_accept'] = PERMS_R_STREAM|PERMS_R_PROFILE|PERMS_R_PHOTOS|PERMS_R_ABOOK
+ |PERMS_W_STREAM|PERMS_W_WALL|PERMS_W_COMMENT|PERMS_W_MAIL|PERMS_W_CHAT
+ |PERMS_R_STORAGE|PERMS_R_PAGES|PERMS_A_REPUBLISH|PERMS_W_LIKE;
+ $ret['channel_r_stream'] = PERMS_PUBLIC;
+ $ret['channel_r_profile'] = PERMS_PUBLIC;
+ $ret['channel_r_photos'] = PERMS_PUBLIC;
+ $ret['channel_r_abook'] = PERMS_PUBLIC;
+ $ret['channel_w_stream'] = PERMS_CONTACTS;
+ $ret['channel_w_wall'] = PERMS_CONTACTS;
+ $ret['channel_w_tagwall'] = PERMS_SPECIFIC;
+ $ret['channel_w_comment'] = PERMS_CONTACTS;
+ $ret['channel_w_mail'] = PERMS_CONTACTS;
+ $ret['channel_w_photos'] = 0;
+ $ret['channel_w_chat'] = PERMS_CONTACTS;
+ $ret['channel_a_delegate'] = 0;
+ $ret['channel_r_storage'] = PERMS_PUBLIC;
+ $ret['channel_r_pages'] = PERMS_PUBLIC;
+ $ret['channel_w_pages'] = 0;
+ $ret['channel_a_republish'] = PERMS_SPECIFIC;
+ $ret['channel_w_like'] = PERMS_NETWORK;
+
+ break;
+
+ }
+
+ call_hooks('get_role_perms',$ret);
+
+ return $ret;
+}
+
+
diff --git a/include/photo/photo_driver.php b/include/photo/photo_driver.php
index f5e915402..daf1bfc25 100644
--- a/include/photo/photo_driver.php
+++ b/include/photo/photo_driver.php
@@ -287,6 +287,10 @@ 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['title'] = (($arr['title']) ? $arr['title'] : '');
+ $p['description'] = (($arr['description']) ? $arr['description'] : '');
// temporary until we get rid of photo['profile'] and just use photo['photo_flags']
// but this will require updating all existing photos in the DB.
@@ -318,6 +322,8 @@ abstract class photo_driver {
`scale` = %d,
`profile` = %d,
`photo_flags` = %d,
+ `title` = '%s',
+ `description` = '%s',
`allow_cid` = '%s',
`allow_gid` = '%s',
`deny_cid` = '%s',
@@ -328,8 +334,8 @@ abstract class photo_driver {
intval($p['uid']),
dbesc($p['xchan']),
dbesc($p['resource_id']),
- dbesc(datetime_convert()),
- dbesc(datetime_convert()),
+ dbesc($p['created']),
+ dbesc($p['edited']),
dbesc(basename($p['filename'])),
dbesc($this->getType()),
dbesc($p['album']),
@@ -340,6 +346,8 @@ abstract class photo_driver {
intval($p['scale']),
intval($p['profile']),
intval($p['photo_flags']),
+ dbesc($p['title']),
+ dbesc($p['description']),
dbesc($p['allow_cid']),
dbesc($p['allow_gid']),
dbesc($p['deny_cid']),
@@ -349,14 +357,14 @@ abstract class photo_driver {
}
else {
$r = q("INSERT INTO `photo`
- ( `aid`, `uid`, `xchan`, `resource_id`, `created`, `edited`, `filename`, type, `album`, `height`, `width`, `data`, `size`, `scale`, `profile`, `photo_flags`, `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' )",
+ ( `aid`, `uid`, `xchan`, `resource_id`, `created`, `edited`, `filename`, type, `album`, `height`, `width`, `data`, `size`, `scale`, `profile`, `photo_flags`, `title`, `description`, `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' )",
intval($p['aid']),
intval($p['uid']),
dbesc($p['xchan']),
dbesc($p['resource_id']),
- dbesc(datetime_convert()),
- dbesc(datetime_convert()),
+ dbesc($p['created']),
+ dbesc($p['edited']),
dbesc(basename($p['filename'])),
dbesc($this->getType()),
dbesc($p['album']),
@@ -367,6 +375,8 @@ abstract class photo_driver {
intval($p['scale']),
intval($p['profile']),
intval($p['photo_flags']),
+ dbesc($p['title']),
+ dbesc($p['description']),
dbesc($p['allow_cid']),
dbesc($p['allow_gid']),
dbesc($p['deny_cid']),
diff --git a/include/photos.php b/include/photos.php
index 9819c7ef2..06a99457a 100644
--- a/include/photos.php
+++ b/include/photos.php
@@ -61,47 +61,64 @@ function photo_upload($channel, $observer, $args) {
$str_group_deny = perms2str(((is_array($args['group_deny'])) ? $args['group_deny'] : explode(',',$args['group_deny'])));
$str_contact_deny = perms2str(((is_array($args['contact_deny'])) ? $args['contact_deny'] : explode(',',$args['contact_deny'])));
- $f = array('src' => '', 'filename' => '', 'filesize' => 0, 'type' => '');
- call_hooks('photo_upload_file',$f);
+ if($args['data']) {
- if(x($f,'src') && x($f,'filesize')) {
- $src = $f['src'];
- $filename = $f['filename'];
- $filesize = $f['filesize'];
- $type = $f['type'];
+ // allow an import from a binary string representing the image.
+ // This bypasses the upload step and max size limit checking
+
+ $imagedata = $args['data'];
+ $filename = $args['filename'];
+ $filesize = strlen($imagedata);
+ // this is going to be deleted if it exists
+ $src = '/tmp/deletemenow';
+ $type = $args['type'];
}
else {
- $src = $_FILES['userfile']['tmp_name'];
- $filename = basename($_FILES['userfile']['name']);
- $filesize = intval($_FILES['userfile']['size']);
- $type = $_FILES['userfile']['type'];
- }
+ $f = array('src' => '', 'filename' => '', 'filesize' => 0, 'type' => '');
- if (! $type)
- $type=guess_image_type($filename);
+ call_hooks('photo_upload_file',$f);
- logger('photo_upload: received file: ' . $filename . ' as ' . $src . ' ('. $type . ') ' . $filesize . ' bytes', LOGGER_DEBUG);
+ if(x($f,'src') && x($f,'filesize')) {
+ $src = $f['src'];
+ $filename = $f['filename'];
+ $filesize = $f['filesize'];
+ $type = $f['type'];
+ }
+ else {
+ $src = $_FILES['userfile']['tmp_name'];
+ $filename = basename($_FILES['userfile']['name']);
+ $filesize = intval($_FILES['userfile']['size']);
+ $type = $_FILES['userfile']['type'];
+ }
- $maximagesize = get_config('system','maximagesize');
+ if (! $type)
+ $type=guess_image_type($filename);
- if(($maximagesize) && ($filesize > $maximagesize)) {
- $ret['message'] = sprintf ( t('Image exceeds website size limit of %lu bytes'), $maximagesize);
- @unlink($src);
- call_hooks('photo_upload_end',$ret);
- return $ret;
- }
+ logger('photo_upload: received file: ' . $filename . ' as ' . $src . ' ('. $type . ') ' . $filesize . ' bytes', LOGGER_DEBUG);
- if(! $filesize) {
- $ret['message'] = t('Image file is empty.');
- @unlink($src);
- call_hooks('photo_post_end',$ret);
- return $ret;
- }
+
+ $maximagesize = get_config('system','maximagesize');
+
+ if(($maximagesize) && ($filesize > $maximagesize)) {
+ $ret['message'] = sprintf ( t('Image exceeds website size limit of %lu bytes'), $maximagesize);
+ @unlink($src);
+ call_hooks('photo_upload_end',$ret);
+ return $ret;
+ }
- logger('photo_upload: loading the contents of ' . $src , LOGGER_DEBUG);
+ if(! $filesize) {
+ $ret['message'] = t('Image file is empty.');
+ @unlink($src);
+ call_hooks('photo_post_end',$ret);
+ return $ret;
+ }
+
+ logger('photo_upload: loading the contents of ' . $src , LOGGER_DEBUG);
+
+ $imagedata = @file_get_contents($src);
+ }
- $imagedata = @file_get_contents($src);
$r = q("select sum(size) as total from photo where aid = %d and scale = 0 ",
intval($account_id)
@@ -141,7 +158,7 @@ function photo_upload($channel, $observer, $args) {
$smallest = 0;
- $photo_hash = photo_new_resource();
+ $photo_hash = (($args['resource_id']) ? $args['resource_id'] : photo_new_resource());
$visitor = '';
if($channel['channel_hash'] !== $observer['xchan_hash'])
@@ -154,6 +171,15 @@ function photo_upload($channel, $observer, $args) {
'allow_cid' => $str_contact_allow, 'allow_gid' => $str_group_allow,
'deny_cid' => $str_contact_deny, 'deny_gid' => $str_group_deny
);
+ if($args['created'])
+ $p['created'] = $args['created'];
+ if($args['edited'])
+ $p['edited'] = $args['edited'];
+ if($args['title'])
+ $p['title'] = $args['title'];
+ if($args['description'])
+ $p['desciprion'] = $args['description'];
+
$r1 = $ph->save($p);
if(! $r1)
diff --git a/include/profile_selectors.php b/include/profile_selectors.php
index 1ffcd49be..a80677cb3 100644
--- a/include/profile_selectors.php
+++ b/include/profile_selectors.php
@@ -18,6 +18,25 @@ function gender_selector($current="",$suffix="") {
return $o;
}
+function gender_selector_min($current="",$suffix="") {
+ $o = '';
+ $select = array('', t('Male'), t('Female'), t('Other'));
+
+ call_hooks('gender_selector_min', $select);
+
+ $o .= "<select name=\"gender$suffix\" id=\"gender-select$suffix\" size=\"1\" >";
+ foreach($select as $selection) {
+ if($selection !== 'NOTRANSLATION') {
+ $selected = (($selection == $current) ? ' selected="selected" ' : '');
+ $o .= "<option value=\"$selection\" $selected >$selection</option>";
+ }
+ }
+ $o .= '</select>';
+ return $o;
+}
+
+
+
function sexpref_selector($current="",$suffix="") {
$o = '';
$select = array('', t('Males'), t('Females'), t('Gay'), t('Lesbian'), t('No Preference'), t('Bisexual'), t('Autosexual'), t('Abstinent'), t('Virgin'), t('Deviant'), t('Fetish'), t('Oodles'), t('Nonsexual'));
@@ -37,6 +56,25 @@ function sexpref_selector($current="",$suffix="") {
}
+function sexpref_selector_min($current="",$suffix="") {
+ $o = '';
+ $select = array('', t('Males'), t('Females'), t('Other'));
+
+ call_hooks('sexpref_selector_min', $select);
+
+ $o .= "<select name=\"sexual$suffix\" id=\"sexual-select$suffix\" size=\"1\" >";
+ foreach($select as $selection) {
+ if($selection !== 'NOTRANSLATION') {
+ $selected = (($selection == $current) ? ' selected="selected" ' : '');
+ $o .= "<option value=\"$selection\" $selected >$selection</option>";
+ }
+ }
+ $o .= '</select>';
+ return $o;
+}
+
+
+
function marital_selector($current="",$suffix="") {
$o = '';
$select = array('', t('Single'), t('Lonely'), t('Available'), t('Unavailable'), t('Has crush'), t('Infatuated'), t('Dating'), t('Unfaithful'), t('Sex Addict'), t('Friends'), t('Friends/Benefits'), t('Casual'), t('Engaged'), t('Married'), t('Imaginarily married'), t('Partners'), t('Cohabiting'), t('Common law'), t('Happy'), t('Not looking'), t('Swinger'), t('Betrayed'), t('Separated'), t('Unstable'), t('Divorced'), t('Imaginarily divorced'), t('Widowed'), t('Uncertain'), t('It\'s complicated'), t('Don\'t care'), t('Ask me') );
@@ -53,3 +91,20 @@ function marital_selector($current="",$suffix="") {
$o .= '</select>';
return $o;
}
+
+function marital_selector_min($current="",$suffix="") {
+ $o = '';
+ $select = array('', t('Single'), t('Dating'), t('Cohabiting'), t('Married'), t('Separated'), t('Divorced'), t('Widowed'), t('It\'s complicated'), t('Other'));
+
+ call_hooks('marital_selector_min', $select);
+
+ $o .= "<select name=\"marital\" id=\"marital-select\" size=\"1\" >";
+ foreach($select as $selection) {
+ if($selection !== 'NOTRANSLATION') {
+ $selected = (($selection == $current) ? ' selected="selected" ' : '');
+ $o .= "<option value=\"$selection\" $selected >$selection</option>";
+ }
+ }
+ $o .= '</select>';
+ return $o;
+}
diff --git a/include/widgets.php b/include/widgets.php
index 96bced87f..1aa018fb6 100644
--- a/include/widgets.php
+++ b/include/widgets.php
@@ -48,6 +48,13 @@ function widget_collections($args) {
$abook_id = 0;
$wmode = 0;
break;
+ case 'connections':
+ $every = 'connections';
+ $each = 'group';
+ $edit = true;
+ $current = $_REQUEST['gid'];
+ $abook_id = 0;
+ $wmode = 0;
case 'groups':
$every = 'connections';
$each = argv(0);
diff --git a/include/zot.php b/include/zot.php
index 0fbeed519..8b0efe09d 100644
--- a/include/zot.php
+++ b/include/zot.php
@@ -625,6 +625,10 @@ function import_xchan($arr,$ud_flags = UPDATE_FLAGS_UPDATED, $ud_arr = null) {
return $ret;
}
+ if(! ($arr['guid'] && $arr['guid_sig'])) {
+ logger('import_xchan: no identity information provided. ' . print_r($arr,true));
+ return $ret;
+ }
$xchan_hash = make_xchan_hash($arr['guid'],$arr['guid_sig']);
$import_photos = false;
@@ -985,8 +989,22 @@ function import_xchan($arr,$ud_flags = UPDATE_FLAGS_UPDATED, $ud_arr = null) {
// Are we a directory server of some kind?
+ $other_realm = false;
+ $realm = get_directory_realm();
+ if(array_key_exists('site',$arr)
+ && array_key_exists('realm',$arr['site'])
+ && (strpos($arr['site']['realm'],$realm) !== false))
+ $other_realm = true;
+
if($dirmode != DIRECTORY_MODE_NORMAL) {
- if(array_key_exists('profile',$arr) && is_array($arr['profile'])) {
+
+ // We're some kind of directory server. However we can only add directory information
+ // if the entry is in the same realm (or is a sub-realm). Sub-realms are denoted by
+ // including the parent realm in the name. e.g. 'RED_GLOBAL:foo' would allow an entry to
+ // be in directories for the local realm (foo) and also the RED_GLOBAL realm.
+
+
+ if(array_key_exists('profile',$arr) && is_array($arr['profile']) && (! $other_realm)) {
$profile_changed = import_directory_profile($xchan_hash,$arr['profile'],$address,$ud_flags, 1);
if($profile_changed) {
$what .= 'profile ';
@@ -1200,6 +1218,12 @@ function zot_import($arr, $sender_url) {
continue;
}
+ // It's a specifically targetted post. If we were sent a public_scope hint (likely),
+ // get rid of it so that it doesn't get stored and cause trouble.
+
+ if(array_key_exists('message',$i) && array_key_exists('public_scope',$i['message']))
+ unset($i['message']['public_scope']);
+
$deliveries = $r;
// We found somebody on this site that's in the recipient list.
@@ -1207,18 +1231,32 @@ function zot_import($arr, $sender_url) {
}
else {
if(($i['message']) && (array_key_exists('flags',$i['message'])) && (in_array('private',$i['message']['flags']))) {
- // This should not happen but until we can stop it...
- logger('private message was delivered with no recipients.');
- continue;
+ if(array_key_exists('public_scope',$i['message']) && $i['message']['public_scope'] === 'public') {
+ // This should not happen but until we can stop it...
+ logger('private message was delivered with no recipients.');
+ continue;
+ }
}
- logger('public post');
+ logger('public post');
// Public post. look for any site members who are or may be accepting posts from this sender
// and who are allowed to see them based on the sender's permissions
$deliveries = allowed_public_recips($i);
+ // if the scope is anything but 'public' we're going to store it as private regardless
+ // of the private flag on the post.
+
+ if($i['message'] && array_key_exists('public_scope',$i['message'])
+ && $i['message']['public_scope'] !== 'public') {
+
+ if(! array_key_exists('flags',$i['message']))
+ $i['message']['flags'] = array();
+ if(! in_array('private',$i['message']['flags']))
+ $i['message']['flags'][] = 'private';
+
+ }
}
// Go through the hash array and remove duplicates. array_unique() won't do this because the array is more than one level.
@@ -1406,7 +1444,7 @@ function allowed_public_recips($msg) {
$hash = make_xchan_hash($msg['notify']['sender']['guid'],$msg['notify']['sender']['guid_sig']);
- if($scope === 'public' || $scope === 'network: red')
+ if($scope === 'public' || $scope === 'network: red' || $scope === 'authenticated')
return $recips;
if(strpos($scope,'site:') === 0) {
@@ -1648,11 +1686,6 @@ function delete_imported_item($sender,$item,$uid) {
require_once('include/items.php');
- // FIXME issue #230 is related
- // Chicken/egg problem because we have to drop_item, but this removes information that tag_deliver may need to do its stuff.
- // We can't reverse the order because drop_item refuses to run if the item already has the deleted flag set and we need to
- // set that flag prior to calling tag_deliver.
-
// Use phased deletion to set the deleted flag, call both tag_deliver and the notifier to notify downstream channels
// and then clean up after ourselves with a cron job after several days to do the delete_item_lowlevel() (DROPITEM_PHASE2).
@@ -1997,6 +2030,7 @@ function import_site($arr,$pubkey) {
$url = htmlspecialchars($arr['url'],ENT_COMPAT,'UTF-8',false);
$sellpage = htmlspecialchars($arr['sellpage'],ENT_COMPAT,'UTF-8',false);
$site_location = htmlspecialchars($arr['location'],ENT_COMPAT,'UTF-8',false);
+ $site_realm = htmlspecialchars($arr['realm'],ENT_COMPAT,'UTF-8',false);
if($exists) {
if(($siterecord['site_flags'] != $site_directory)
@@ -2004,13 +2038,14 @@ function import_site($arr,$pubkey) {
|| ($siterecord['site_directory'] != $directory_url)
|| ($siterecord['site_sellpage'] != $sellpage)
|| ($siterecord['site_location'] != $site_location)
- || ($siterecord['site_register'] != $register_policy)) {
+ || ($siterecord['site_register'] != $register_policy)
+ || ($siterecord['site_realm'] != $site_realm)) {
$update = true;
// logger('import_site: input: ' . print_r($arr,true));
// logger('import_site: stored: ' . print_r($siterecord,true));
- $r = q("update site set site_location = '%s', site_flags = %d, site_access = %d, site_directory = '%s', site_register = %d, site_update = '%s', site_sellpage = '%s'
+ $r = q("update site set site_location = '%s', site_flags = %d, site_access = %d, site_directory = '%s', site_register = %d, site_update = '%s', site_sellpage = '%s', site_realm = '%s'
where site_url = '%s' limit 1",
dbesc($site_location),
intval($site_directory),
@@ -2019,6 +2054,7 @@ function import_site($arr,$pubkey) {
intval($register_policy),
dbesc(datetime_convert()),
dbesc($sellpage),
+ dbesc($site_realm),
dbesc($url)
);
if(! $r) {
@@ -2028,8 +2064,8 @@ function import_site($arr,$pubkey) {
}
else {
$update = true;
- $r = q("insert into site ( site_location, site_url, site_access, site_flags, site_update, site_directory, site_register, site_sellpage )
- values ( '%s', '%s', %d, %d, '%s', '%s', %d, '%s' )",
+ $r = q("insert into site ( site_location, site_url, site_access, site_flags, site_update, site_directory, site_register, site_sellpage, site_realm )
+ values ( '%s', '%s', %d, %d, '%s', '%s', %d, '%s', '%s' )",
dbesc($site_location),
dbesc($url),
intval($access_policy),
@@ -2037,7 +2073,8 @@ function import_site($arr,$pubkey) {
dbesc(datetime_convert()),
dbesc($directory_url),
intval($register_policy),
- dbesc($sellpage)
+ dbesc($sellpage),
+ dbesc($site_realm)
);
if(! $r) {
logger('import_site: record create failed. ' . print_r($arr,true));
@@ -2104,6 +2141,7 @@ function build_sync_packet($uid = 0, $packet = null, $groups_changed = false) {
$info = (($packet) ? $packet : array());
$info['type'] = 'channel_sync';
+ $info['encoding'] = 'red'; // note: not zot, this packet is very red specific
if(array_key_exists($uid,$a->config) && array_key_exists('transient',$a->config[$uid])) {
$settings = $a->config[$uid]['transient'];
@@ -2234,6 +2272,21 @@ function process_channel_sync_delivery($sender,$arr,$deliveries) {
$clean = array();
foreach($arr['abook'] as $abook) {
+ if($abook['abook_xchan'] && $abook['entry_deleted']) {
+ logger('process_channel_sync_delivery: removing abook entry for ' . $abook['abook_xchan']);
+ require_once('include/Contact.php');
+
+ $r = q("select abook_id from abook where abook_xchan = '%s' and abook_channel = %d and not ( abook_flags & %d ) limit 1",
+ dbesc($abook['abook_xchan']),
+ intval($channel['channel_id']),
+ intval(ABOOK_FLAG_SELF)
+ );
+ if($r)
+ contact_remove($channel['channel_id'],$r[0]['abook_id']);
+
+ continue;
+ }
+
// Perform discovery if the referenced xchan hasn't ever been seen on this hub.
// This relies on the undocumented behaviour that red sites send xchan info with the abook