Add Install Mode

- merged `friendica/develop` to `nupplaPhil/install_mode`
- content merged `mod/install.php` to `src/Class/`
This commit is contained in:
Philipp Holzer 2018-04-28 15:11:01 +02:00
commit 1ab965c944
128 changed files with 34290 additions and 33303 deletions

View file

@ -49,7 +49,7 @@ class ForumManager
"SELECT `contact`.`id`, `contact`.`url`, `contact`.`name`, `contact`.`micro`, `contact`.`thumb`
FROM `contact`
WHERE `network`= 'dfrn' AND $select AND `uid` = ?
AND NOT `blocked` AND NOT `hidden` AND NOT `pending` AND NOT `archive`
AND NOT `blocked` AND NOT `pending` AND NOT `archive`
AND `success_update` > `failure_update`
$order ",
$uid

View file

@ -28,23 +28,23 @@ class Nav
if (!(x($a->page, 'nav'))) {
$a->page['nav'] = '';
}
$a->page['htmlhead'] .= replace_macros(get_markup_template('nav_head.tpl'), []);
/*
* Placeholder div for popup panel
*/
$a->page['nav'] .= '<div id="panel" style="display: none;"></div>' ;
$nav_info = self::getInfo($a);
/*
* Build the page
*/
$tpl = get_markup_template('nav.tpl');
$a->page['nav'] .= replace_macros($tpl, [
'$baseurl' => System::baseUrl(),
'$sitelocation' => $nav_info['sitelocation'],
@ -57,10 +57,10 @@ class Nav
'$clear_notifs' => L10n::t('Clear notifications'),
'$search_hint' => L10n::t('@name, !forum, #tags, content')
]);
Addon::callHooks('page_header', $a->page['nav']);
}
/**
* Prepares a list of navigation links
*
@ -75,27 +75,27 @@ class Nav
private static function getInfo(App $a)
{
$ssl_state = ((local_user()) ? true : false);
/*
* Our network is distributed, and as you visit friends some of the
* sites look exactly the same - it isn't always easy to know where you are.
* Display the current site location as a navigation aid.
*/
$myident = ((is_array($a->user) && isset($a->user['nickname'])) ? $a->user['nickname'] . '@' : '');
$sitelocation = $myident . substr(System::baseUrl($ssl_state), strpos(System::baseUrl($ssl_state), '//') + 2);
// nav links: array of array('href', 'text', 'extra css classes', 'title')
$nav = [];
// Display login or logout
$nav['usermenu'] = [];
$userinfo = null;
if (local_user()) {
$nav['logout'] = ['logout', L10n::t('Logout'), '', L10n::t('End this session')];
// user menu
$nav['usermenu'][] = ['profile/' . $a->user['nickname'], L10n::t('Status'), '', L10n::t('Your posts and conversations')];
$nav['usermenu'][] = ['profile/' . $a->user['nickname'] . '?tab=profile', L10n::t('Profile'), '', L10n::t('Your profile page')];
@ -103,7 +103,7 @@ class Nav
$nav['usermenu'][] = ['videos/' . $a->user['nickname'], L10n::t('Videos'), '', L10n::t('Your videos')];
$nav['usermenu'][] = ['events/', L10n::t('Events'), '', L10n::t('Your events')];
$nav['usermenu'][] = ['notes/', L10n::t('Personal notes'), '', L10n::t('Your personal notes')];
// user info
$contact = dba::selectFirst('contact', ['micro'], ['uid' => $a->user['uid'], 'self' => true]);
$userinfo = [
@ -113,120 +113,117 @@ class Nav
} else {
$nav['login'] = ['login', L10n::t('Login'), ($a->module == 'login' ? 'selected' : ''), L10n::t('Sign in')];
}
// "Home" should also take you home from an authenticated remote profile connection
$homelink = Profile::getMyURL();
if (! $homelink) {
$homelink = ((x($_SESSION, 'visitor_home')) ? $_SESSION['visitor_home'] : '');
}
if (($a->module != 'home') && (! (local_user()))) {
$nav['home'] = [$homelink, L10n::t('Home'), '', L10n::t('Home Page')];
}
if (($a->config['register_policy'] == REGISTER_OPEN) && (! local_user()) && (! remote_user())) {
$nav['register'] = ['register', L10n::t('Register'), '', L10n::t('Create an account')];
}
$help_url = 'help';
if (!Config::get('system', 'hide_help')) {
$nav['help'] = [$help_url, L10n::t('Help'), '', L10n::t('Help and documentation')];
}
if (count($a->apps) > 0) {
$nav['apps'] = ['apps', L10n::t('Apps'), '', L10n::t('Addon applications, utilities, games')];
}
if (local_user() || !Config::get('system', 'local_search')) {
$nav['search'] = ['search', L10n::t('Search'), '', L10n::t('Search site content')];
$nav['searchoption'] = [
L10n::t('Full Text'),
L10n::t('Tags'),
L10n::t('Contacts')
];
if (Config::get('system', 'poco_local_search')) {
$nav['searchoption'][] = L10n::t('Forums');
}
}
$gdirpath = 'directory';
if (strlen(Config::get('system', 'singleuser'))) {
$gdir = Config::get('system', 'directory');
if (strlen($gdir)) {
$gdirpath = Profile::zrl($gdir, true);
}
}
if (local_user() || Config::get('system', 'community_page_style') != CP_NO_COMMUNITY_PAGE) {
if ((local_user() || Config::get('system', 'community_page_style') != CP_NO_COMMUNITY_PAGE) &&
!(Config::get('system', 'community_page_style') == CP_NO_INTERNAL_COMMUNITY)) {
$nav['community'] = ['community', L10n::t('Community'), '', L10n::t('Conversations on this and other servers')];
}
if (local_user()) {
$nav['events'] = ['events', L10n::t('Events'), '', L10n::t('Events and Calendar')];
}
$nav['directory'] = [$gdirpath, L10n::t('Directory'), '', L10n::t('People directory')];
$nav['about'] = ['friendica', L10n::t('Information'), '', L10n::t('Information about this friendica instance')];
// The following nav links are only show to logged in users
if (local_user()) {
$nav['network'] = ['network', L10n::t('Network'), '', L10n::t('Conversations from your friends')];
$nav['net_reset'] = ['network/0?f=&order=comment&nets=all', L10n::t('Network Reset'), '', L10n::t('Load Network page with no filters')];
$nav['home'] = ['profile/' . $a->user['nickname'], L10n::t('Home'), '', L10n::t('Your posts and conversations')];
if (in_array($_SESSION['page_flags'], [PAGE_NORMAL, PAGE_SOAPBOX, PAGE_FREELOVE, PAGE_PRVGROUP])) {
// only show friend requests for normal pages. Other page types have automatic friendship.
if (in_array($_SESSION['page_flags'], [PAGE_NORMAL, PAGE_SOAPBOX, PAGE_PRVGROUP])) {
$nav['introductions'] = ['notifications/intros', L10n::t('Introductions'), '', L10n::t('Friend Requests')];
}
if (in_array($_SESSION['page_flags'], [PAGE_NORMAL, PAGE_SOAPBOX, PAGE_FREELOVE])) {
$nav['notifications'] = ['notifications', L10n::t('Notifications'), '', L10n::t('Notifications')];
$nav['notifications']['all'] = ['notifications/system', L10n::t('See all notifications'), '', ''];
$nav['notifications']['mark'] = ['', L10n::t('Mark as seen'), '', L10n::t('Mark all system notifications seen')];
}
// Don't show notifications for public communities
if ($_SESSION['page_flags'] != PAGE_COMMUNITY) {
$nav['introductions'] = ['notifications/intros', L10n::t('Introductions'), '', L10n::t('Friend Requests')];
$nav['notifications'] = ['notifications', L10n::t('Notifications'), '', L10n::t('Notifications')];
$nav['notifications']['all'] = ['notifications/system', L10n::t('See all notifications'), '', ''];
$nav['notifications']['mark'] = ['', L10n::t('Mark as seen'), '', L10n::t('Mark all system notifications seen')];
}
$nav['messages'] = ['message', L10n::t('Messages'), '', L10n::t('Private mail')];
$nav['messages']['inbox'] = ['message', L10n::t('Inbox'), '', L10n::t('Inbox')];
$nav['messages']['outbox'] = ['message/sent', L10n::t('Outbox'), '', L10n::t('Outbox')];
$nav['messages']['new'] = ['message/new', L10n::t('New Message'), '', L10n::t('New Message')];
if (is_array($a->identities) && count($a->identities) > 1) {
$nav['manage'] = ['manage', L10n::t('Manage'), '', L10n::t('Manage other pages')];
}
$nav['delegations'] = ['delegate', L10n::t('Delegations'), '', L10n::t('Delegate Page Management')];
$nav['settings'] = ['settings', L10n::t('Settings'), '', L10n::t('Account settings')];
if (Feature::isEnabled(local_user(), 'multi_profiles')) {
$nav['profiles'] = ['profiles', L10n::t('Profiles'), '', L10n::t('Manage/Edit Profiles')];
}
$nav['contacts'] = ['contacts', L10n::t('Contacts'), '', L10n::t('Manage/edit friends and contacts')];
}
// Show the link to the admin configuration page if user is admin
if (is_site_admin()) {
$nav['admin'] = ['admin/', L10n::t('Admin'), '', L10n::t('Site setup and configuration')];
}
$nav['navigation'] = ['navigation/', L10n::t('Navigation'), '', L10n::t('Site map')];
// Provide a banner/logo/whatever
$banner = Config::get('system', 'banner');
if (is_null($banner)) {
$banner = '<a href="https://friendi.ca"><img id="logo-img" src="images/friendica-32.png" alt="logo" /></a><span id="logo-text"><a href="https://friendi.ca">Friendica</a></span>';
}
Addon::callHooks('nav_info', $nav);
return [
'sitelocation' => $sitelocation,
'nav' => $nav,
@ -234,7 +231,7 @@ class Nav
'userinfo' => $userinfo,
];
}
/**
* Set a menu item in navbar as selected
*/

View file

@ -343,159 +343,20 @@ class BBCode extends BaseObject
}
/**
* @brief Convert a message into plaintext for connectors to other networks
* @brief Converts a BBCode text into plaintext
*
* @param array $b The message array that is about to be posted
* @param int $limit The maximum number of characters when posting to that network
* @param bool $includedlinks Has an attached link to be included into the message?
* @param int $htmlmode This triggers the behaviour of the bbcode conversion
* @param string $target_network Name of the network where the post should go to.
* @param bool $keep_urls Whether to keep URLs in the resulting plaintext
*
* @return string The converted message
* @return string
*/
public static function toPlaintext($b, $limit = 0, $includedlinks = false, $htmlmode = 2, $target_network = "")
public static function toPlaintext($text, $keep_urls = true)
{
// Remove the hash tags
$URLSearchString = "^\[\]";
$body = preg_replace("/([#@])\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", '$1$3', $b["body"]);
// Add an URL element if the text contains a raw link
$body = preg_replace("/([^\]\='".'"'."]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", '$1[url]$2[/url]', $body);
// Remove the abstract
$body = self::stripAbstract($body);
// At first look at data that is attached via "type-..." stuff
// This will hopefully replaced with a dedicated bbcode later
//$post = self::getAttachedData($b["body"]);
$post = self::getAttachedData($body, $b);
if (($b["title"] != "") && ($post["text"] != "")) {
$post["text"] = trim($b["title"]."\n\n".$post["text"]);
} elseif ($b["title"] != "") {
$post["text"] = trim($b["title"]);
$naked_text = preg_replace('/\[(.+?)\]/','', $text);
if (!$keep_urls) {
$naked_text = preg_replace('#https?\://[^\s<]+[^\s\.\)]#i', '', $naked_text);
}
$abstract = "";
// Fetch the abstract from the given target network
if ($target_network != "") {
$default_abstract = self::getAbstract($b["body"]);
$abstract = self::getAbstract($b["body"], $target_network);
// If we post to a network with no limit we only fetch
// an abstract exactly for this network
if (($limit == 0) && ($abstract == $default_abstract)) {
$abstract = "";
}
} else {// Try to guess the correct target network
switch ($htmlmode) {
case 8:
$abstract = self::getAbstract($b["body"], NETWORK_TWITTER);
break;
case 7:
$abstract = self::getAbstract($b["body"], NETWORK_STATUSNET);
break;
case 6:
$abstract = self::getAbstract($b["body"], NETWORK_APPNET);
break;
default: // We don't know the exact target.
// We fetch an abstract since there is a posting limit.
if ($limit > 0) {
$abstract = self::getAbstract($b["body"]);
}
}
}
if ($abstract != "") {
$post["text"] = $abstract;
if ($post["type"] == "text") {
$post["type"] = "link";
$post["url"] = $b["plink"];
}
}
$html = self::convert($post["text"].$post["after"], false, $htmlmode);
$msg = HTML::toPlaintext($html, 0, true);
$msg = trim(html_entity_decode($msg, ENT_QUOTES, 'UTF-8'));
$link = "";
if ($includedlinks) {
if ($post["type"] == "link") {
$link = $post["url"];
} elseif ($post["type"] == "text") {
$link = $post["url"];
} elseif ($post["type"] == "video") {
$link = $post["url"];
} elseif ($post["type"] == "photo") {
$link = $post["image"];
}
if (($msg == "") && isset($post["title"])) {
$msg = trim($post["title"]);
}
if (($msg == "") && isset($post["description"])) {
$msg = trim($post["description"]);
}
// If the link is already contained in the post, then it neeedn't to be added again
// But: if the link is beyond the limit, then it has to be added.
if (($link != "") && strstr($msg, $link)) {
$pos = strpos($msg, $link);
// Will the text be shortened in the link?
// Or is the link the last item in the post?
if (($limit > 0) && ($pos < $limit) && (($pos + 23 > $limit) || ($pos + strlen($link) == strlen($msg)))) {
$msg = trim(str_replace($link, "", $msg));
} elseif (($limit == 0) || ($pos < $limit)) {
// The limit has to be increased since it will be shortened - but not now
// Only do it with Twitter (htmlmode = 8)
if (($limit > 0) && (strlen($link) > 23) && ($htmlmode == 8)) {
$limit = $limit - 23 + strlen($link);
}
$link = "";
if ($post["type"] == "text") {
unset($post["url"]);
}
}
}
}
if ($limit > 0) {
// Reduce multiple spaces
// When posted to a network with limited space, we try to gain space where possible
while (strpos($msg, " ") !== false) {
$msg = str_replace(" ", " ", $msg);
}
// Twitter is using its own limiter, so we always assume that shortened links will have this length
if (iconv_strlen($link, "UTF-8") > 0) {
$limit = $limit - 23;
}
if (iconv_strlen($msg, "UTF-8") > $limit) {
if (($post["type"] == "text") && isset($post["url"])) {
$post["url"] = $b["plink"];
} elseif (!isset($post["url"])) {
$limit = $limit - 23;
$post["url"] = $b["plink"];
// Which purpose has this line? It is now uncommented, but left as a reminder
//} elseif (strpos($b["body"], "[share") !== false) {
// $post["url"] = $b["plink"];
} elseif (PConfig::get($b["uid"], "system", "no_intelligent_shortening")) {
$post["url"] = $b["plink"];
}
$msg = Plaintext::shorten($msg, $limit);
}
}
$post["text"] = trim($msg);
return($post);
return $naked_text;
}
public static function scaleExternalImages($srctext, $include_link = true, $scale_replace = false)
@ -1947,7 +1808,7 @@ class BBCode extends BaseObject
* @param string $addon The addon for which the abstract is meant for
* @return string The abstract
*/
private static function getAbstract($text, $addon = "")
public static function getAbstract($text, $addon = "")
{
$abstract = "";
$abstracts = [];

View file

@ -55,19 +55,24 @@ class Widget
}
}
return replace_macros(get_markup_template('peoplefind.tpl'), array(
'$findpeople' => L10n::t('Find People'),
'$desc' => L10n::t('Enter name or interest'),
'$label' => L10n::t('Connect/Follow'),
'$hint' => L10n::t('Examples: Robert Morgenstein, Fishing'),
'$findthem' => L10n::t('Find'),
'$suggest' => L10n::t('Friend Suggestions'),
'$similar' => L10n::t('Similar Interests'),
'$random' => L10n::t('Random Profile'),
'$inv' => L10n::t('Invite Friends'),
'$directory' => L10n::t('View Global Directory'),
'$global_dir' => $global_dir
));
$nv = [];
$nv['findpeople'] = L10n::t('Find People');
$nv['desc'] = L10n::t('Enter name or interest');
$nv['label'] = L10n::t('Connect/Follow');
$nv['hint'] = L10n::t('Examples: Robert Morgenstein, Fishing');
$nv['findthem'] = L10n::t('Find');
$nv['suggest'] = L10n::t('Friend Suggestions');
$nv['similar'] = L10n::t('Similar Interests');
$nv['random'] = L10n::t('Random Profile');
$nv['inv'] = L10n::t('Invite Friends');
$nv['directory'] = L10n::t('Global Directory');
$nv['global_dir'] = $global_dir;
$nv['local_directory'] = L10n::t('Local Directory');
$aside = [];
$aside['$nv'] = $nv;
return replace_macros(get_markup_template('peoplefind.tpl'), $aside);
}
/**

View file

@ -246,7 +246,7 @@ class Addon
} else {
// remove orphan hooks
$condition = ['hook' => $name, 'file' => $hook[0], 'function' => $hook[1]];
dba::delete('hook', $condition);
dba::delete('hook', $condition, ['cascade' => false]);
}
}

View file

@ -21,6 +21,7 @@ class Console extends \Asika\SimpleConsole\Console
'extract' => __NAMESPACE__ . '\Console\Extract',
'globalcommunityblock' => __NAMESPACE__ . '\Console\GlobalCommunityBlock',
'globalcommunitysilence' => __NAMESPACE__ . '\Console\GlobalCommunitySilence',
'archivecontact' => __NAMESPACE__ . '\Console\ArchiveContact',
'autoinstall' => __NAMESPACE__ . '\Console\AutomaticInstallation',
'maintenance' => __NAMESPACE__ . '\Console\Maintenance',
'newpassword' => __NAMESPACE__ . '\Console\NewPassword',
@ -42,6 +43,7 @@ Commands:
extract Generate translation string file for the Friendica project (deprecated)
globalcommunityblock Block remote profile from interacting with this node
globalcommunitysilence Silence remote profile from global community page
archivecontact Archive a contact when you know that it isn't existing anymore
help Show help about a command, e.g (bin/console help config)
autoinstall Starts automatic installation of friendica based on values from htconfig.php
maintenance Set maintenance mode for this node

View file

@ -0,0 +1,79 @@
<?php
namespace Friendica\Core\Console;
use Friendica\Core\L10n;
use dba;
/**
* @brief tool to archive a contact on the server
*
* With this tool you can archive a contact when you know that it isn't existing anymore.
* Normally this does happen automatically after a few days.
*
* License: AGPLv3 or later, same as Friendica
*
*/
class ArchiveContact extends \Asika\SimpleConsole\Console
{
protected $helpOptions = ['h', 'help', '?'];
protected function getHelp()
{
$help = <<<HELP
console archivecontact - archive a contact
Usage
bin/console archivecontact <profile_url> [-h|--help|-?] [-v]
Description
Archive a contact when you know that it isn't existing anymore. Normally this does happen automatically after a few days.
Options
-h|--help|-? Show help information
-v Show more debug information.
HELP;
return $help;
}
protected function doExecute()
{
$a = get_app();
if ($this->getOption('v')) {
$this->out('Class: ' . __CLASS__);
$this->out('Arguments: ' . var_export($this->args, true));
$this->out('Options: ' . var_export($this->options, true));
}
if (count($this->args) == 0) {
$this->out($this->getHelp());
return 0;
}
if (count($this->args) > 1) {
throw new \Asika\SimpleConsole\CommandArgsException('Too many arguments');
}
require_once '.htconfig.php';
$result = \dba::connect($db_host, $db_user, $db_pass, $db_data);
unset($db_host, $db_user, $db_pass, $db_data);
if (!$result) {
throw new \RuntimeException('Unable to connect to database');
}
$nurl = normalise_link($this->getArgument(0));
if (!dba::exists('contact', ['nurl' => $nurl, 'archive' => false])) {
throw new \RuntimeException(L10n::t('Could not find any unarchived contact entry for this URL (%s)', $nurl));
}
if (dba::update('contact', ['archive' => true], ['nurl' => $nurl])) {
$condition = ["`cid` IN (SELECT `id` FROM `contact` WHERE `archive`)"];
dba::delete('queue', $condition);
$this->out(L10n::t('The contact entries have been archived'));
} else {
throw new \RuntimeException('The contact archival failed.');
}
return 0;
}
}

View file

@ -423,7 +423,7 @@ class Contact extends BaseObject
// Fetch the data from the gcontact table
if (!DBM::is_result($r)) {
$s = dba::p("SELECT 0 AS `id`, 0 AS `cid`, `id` AS `gid`, 0 AS `zid`, 0 AS `uid`, `url`, `nurl`, `alias`, `network`, `name`, `nick`, `addr`, `location`, `about`, '' AS `xmpp`,
`keywords`, `gender`, `photo`, `photo` AS `thumb`, `photo` AS `micro`, `community` AS `forum`, 0 AS `prv`, `community`, `contact-type`, `birthday`, 0 AS `self`
`keywords`, `gender`, `photo`, `photo` AS `thumb`, `photo` AS `micro`, 0 AS `forum`, 0 AS `prv`, `community`, `contact-type`, `birthday`, 0 AS `self`
FROM `gcontact` WHERE `nurl` = ?", normalise_link($url));
$r = dba::inArray($s);
}

View file

@ -7,6 +7,7 @@
namespace Friendica\Model;
use Friendica\BaseObject;
use Friendica\Content\Text;
use Friendica\Core\Addon;
use Friendica\Core\Config;
use Friendica\Core\L10n;
@ -838,6 +839,101 @@ class Item extends BaseObject
return $current_post;
}
/**
* @brief Distributes public items to the receivers
*
* @param integer $itemid Item ID that should be added
*/
public static function distribute($itemid)
{
$condition = ["`id` IN (SELECT `parent` FROM `item` WHERE `id` = ?)", $itemid];
$parent = dba::selectFirst('item', ['owner-id'], $condition);
if (!DBM::is_result($parent)) {
return;
}
// Only distribute public items from native networks
$condition = ['id' => $itemid, 'uid' => 0,
'network' => [NETWORK_DFRN, NETWORK_DIASPORA, NETWORK_OSTATUS, ""],
'visible' => true, 'deleted' => false, 'moderated' => false, 'private' => false];
$item = dba::selectFirst('item', [], ['id' => $itemid]);
if (!DBM::is_result($item)) {
return;
}
unset($item['id']);
unset($item['parent']);
unset($item['mention']);
unset($item['wall']);
unset($item['origin']);
unset($item['starred']);
unset($item['rendered-hash']);
unset($item['rendered-html']);
$users = [];
$condition = ["`nurl` IN (SELECT `nurl` FROM `contact` WHERE `id` = ?) AND `uid` != 0 AND NOT `blocked` AND NOT `readonly` AND `rel` IN (?, ?)",
$parent['owner-id'], CONTACT_IS_SHARING, CONTACT_IS_FRIEND];
$contacts = dba::select('contact', ['uid'], $condition);
while ($contact = dba::fetch($contacts)) {
$users[$contact['uid']] = $contact['uid'];
}
if ($item['uri'] != $item['parent-uri']) {
$parents = dba::select('item', ['uid'], ["`uri` = ? AND `uid` != 0", $item['parent-uri']]);
while ($parent = dba::fetch($parents)) {
$users[$parent['uid']] = $parent['uid'];
}
}
foreach ($users as $uid) {
self::storeForUser($itemid, $item, $uid);
}
}
/**
* @brief Store public items for the receivers
*
* @param integer $itemid Item ID that should be added
* @param array $item The item entry that will be stored
* @param integer $uid The user that will receive the item entry
*/
private static function storeForUser($itemid, $item, $uid)
{
$item['uid'] = $uid;
$item['origin'] = 0;
$item['wall'] = 0;
if ($item['uri'] == $item['parent-uri']) {
$item['contact-id'] = Contact::getIdForURL($item['owner-link'], $uid);
} else {
$item['contact-id'] = Contact::getIdForURL($item['author-link'], $uid);
}
if (empty($item['contact-id'])) {
$self = dba::selectFirst('contact', ['id'], ['self' => true, 'uid' => $uid]);
if (!DBM::is_result($self)) {
return;
}
$item['contact-id'] = $self['id'];
}
if (in_array($item['type'], ["net-comment", "wall-comment"])) {
$item['type'] = 'remote-comment';
} elseif ($item['type'] == 'wall') {
$item['type'] = 'remote';
}
/// @todo Handling of "event-id"
$distributed = self::insert($item, false, false, true);
if (!$distributed) {
logger("Distributed public item " . $itemid . " for user " . $uid . " wasn't stored", LOGGER_DEBUG);
} else {
logger("Distributed public item " . $itemid . " for user " . $uid . " with id " . $distributed, LOGGER_DEBUG);
}
}
/**
* @brief Add a shadow entry for a given item id that is a thread starter
*
@ -849,8 +945,8 @@ class Item extends BaseObject
*/
public static function addShadow($itemid)
{
$fields = ['uid', 'wall', 'private', 'moderated', 'visible', 'contact-id', 'deleted', 'network', 'author-id', 'owner-id'];
$condition = ["`id` = ? AND (`parent` = ? OR `parent` = 0)", $itemid, $itemid];
$fields = ['uid', 'private', 'moderated', 'visible', 'deleted', 'network'];
$condition = ['id' => $itemid, 'parent' => [0, $itemid]];
$item = dba::selectFirst('item', $fields, $condition);
if (!DBM::is_result($item)) {
@ -872,35 +968,22 @@ class Item extends BaseObject
return;
}
// Only do these checks if the post isn't a wall post
if (!$item["wall"]) {
// Check, if hide-friends is activated - then don't do a shadow entry
if (dba::exists('profile', ['is-default' => true, 'uid' => $item['uid'], 'hide-friends' => true])) {
return;
}
// Check if the contact is hidden or blocked
if (!dba::exists('contact', ['hidden' => false, 'blocked' => false, 'id' => $item['contact-id']])) {
return;
}
}
// Only add a shadow, if the profile isn't hidden
if (dba::exists('user', ['uid' => $item['uid'], 'hidewall' => true])) {
return;
}
$item = dba::selectFirst('item', [], ['id' => $itemid]);
if (DBM::is_result($item) && ($item["allow_cid"] == '') && ($item["allow_gid"] == '') &&
if (DBM::is_result($item) && ($item["allow_cid"] == '') && ($item["allow_gid"] == '') &&
($item["deny_cid"] == '') && ($item["deny_gid"] == '')) {
if (!dba::exists('item', ['uri' => $item['uri'], 'uid' => 0])) {
// Preparing public shadow (removing user specific data)
unset($item['id']);
$item['uid'] = 0;
$item['origin'] = 0;
$item['wall'] = 0;
unset($item['id']);
unset($item['parent']);
unset($item['wall']);
unset($item['mention']);
unset($item['origin']);
unset($item['starred']);
unset($item['rendered-hash']);
unset($item['rendered-html']);
if ($item['uri'] == $item['parent-uri']) {
$item['contact-id'] = Contact::getIdForURL($item['owner-link']);
} else {
@ -954,11 +1037,20 @@ class Item extends BaseObject
return;
}
// Save "origin" and "parent" state
$origin = $item['origin'];
$parent = $item['parent'];
// Preparing public shadow (removing user specific data)
unset($item['id']);
$item['uid'] = 0;
$item['origin'] = 0;
$item['wall'] = 0;
unset($item['id']);
unset($item['parent']);
unset($item['wall']);
unset($item['mention']);
unset($item['origin']);
unset($item['starred']);
unset($item['rendered-hash']);
unset($item['rendered-html']);
$item['contact-id'] = Contact::getIdForURL($item['author-link']);
if (in_array($item['type'], ["net-comment", "wall-comment"])) {
@ -970,6 +1062,14 @@ class Item extends BaseObject
$public_shadow = self::insert($item, false, false, true);
logger("Stored public shadow for comment ".$item['uri']." under id ".$public_shadow, LOGGER_DEBUG);
// If this was a comment to a Diaspora post we don't get our comment back.
// This means that we have to distribute the comment by ourselves.
if ($origin) {
if (dba::exists('item', ['id' => $parent, 'network' => NETWORK_DIASPORA])) {
self::distribute($public_shadow);
}
}
}
/**
@ -977,35 +1077,35 @@ class Item extends BaseObject
* if possible and not already present.
* Expects "body" element to exist in $arr.
*/
private static function addLanguageInPostopts(&$arr)
private static function addLanguageInPostopts(&$item)
{
if (x($arr, 'postopts')) {
if (strstr($arr['postopts'], 'lang=')) {
if (!empty($item['postopts'])) {
if (strstr($item['postopts'], 'lang=')) {
// do not override
return;
}
$postopts = $arr['postopts'];
$postopts = $item['postopts'];
} else {
$postopts = "";
}
$naked_body = preg_replace('/\[(.+?)\]/','', $arr['body']);
$l = new Text_LanguageDetect();
$lng = $l->detect($naked_body, 3);
$naked_body = Text\BBCode::toPlaintext($item['body'], false);
if (sizeof($lng) > 0) {
if ($postopts != "") {
$languages = (new Text_LanguageDetect())->detect($naked_body, 3);
if (sizeof($languages) > 0) {
if ($postopts != '') {
$postopts .= '&'; // arbitrary separator, to be reviewed
}
$postopts .= 'lang=';
$sep = "";
foreach ($lng as $language => $score) {
foreach ($languages as $language => $score) {
$postopts .= $sep . $language . ";" . $score;
$sep = ':';
}
$arr['postopts'] = $postopts;
$item['postopts'] = $postopts;
}
}

176
src/Model/ItemContent.php Normal file
View file

@ -0,0 +1,176 @@
<?php
/**
* @file src/Model/ItemContent.php
*/
namespace Friendica\Model;
use Friendica\BaseObject;
use Friendica\Content\Text;
use Friendica\Core\PConfig;
require_once 'boot.php';
require_once 'include/items.php';
require_once 'include/text.php';
class ItemContent extends BaseObject
{
/**
* @brief Convert a message into plaintext for connectors to other networks
*
* @param array $item The message array that is about to be posted
* @param int $limit The maximum number of characters when posting to that network
* @param bool $includedlinks Has an attached link to be included into the message?
* @param int $htmlmode This controls the behavior of the BBCode conversion
* @param string $target_network Name of the network where the post should go to.
*
* @see \Friendica\Content\Text\BBCode::getAttachedData
*
* @return array Same array structure than \Friendica\Content\Text\BBCode::getAttachedData
*/
public static function getPlaintextPost($item, $limit = 0, $includedlinks = false, $htmlmode = 2, $target_network = '')
{
// Remove hashtags
$URLSearchString = '^\[\]';
$body = preg_replace("/([#@])\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", '$1$3', $item['body']);
// Add an URL element if the text contains a raw link
$body = preg_replace('/([^\]\=\'"]|^)(https?\:\/\/[a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism',
'$1[url]$2[/url]', $body);
// Remove the abstract
$body = Text\BBCode::stripAbstract($body);
// At first look at data that is attached via "type-..." stuff
// This will hopefully replaced with a dedicated bbcode later
//$post = self::getAttachedData($b['body']);
$post = Text\BBCode::getAttachedData($body, $item);
if (($item['title'] != '') && ($post['text'] != '')) {
$post['text'] = trim($item['title'] . "\n\n" . $post['text']);
} elseif ($item['title'] != '') {
$post['text'] = trim($item['title']);
}
$abstract = '';
// Fetch the abstract from the given target network
if ($target_network != '') {
$default_abstract = Text\BBCode::getAbstract($item['body']);
$abstract = Text\BBCode::getAbstract($item['body'], $target_network);
// If we post to a network with no limit we only fetch
// an abstract exactly for this network
if (($limit == 0) && ($abstract == $default_abstract)) {
$abstract = '';
}
} else {// Try to guess the correct target network
switch ($htmlmode) {
case 8:
$abstract = Text\BBCode::getAbstract($item['body'], NETWORK_TWITTER);
break;
case 7:
$abstract = Text\BBCode::getAbstract($item['body'], NETWORK_STATUSNET);
break;
case 6:
$abstract = Text\BBCode::getAbstract($item['body'], NETWORK_APPNET);
break;
default: // We don't know the exact target.
// We fetch an abstract since there is a posting limit.
if ($limit > 0) {
$abstract = Text\BBCode::getAbstract($item['body']);
}
}
}
if ($abstract != '') {
$post['text'] = $abstract;
if ($post['type'] == 'text') {
$post['type'] = 'link';
$post['url'] = $item['plink'];
}
}
$html = Text\BBCode::convert($post['text'] . $post['after'], false, $htmlmode);
$msg = Text\HTML::toPlaintext($html, 0, true);
$msg = trim(html_entity_decode($msg, ENT_QUOTES, 'UTF-8'));
$link = '';
if ($includedlinks) {
if ($post['type'] == 'link') {
$link = $post['url'];
} elseif ($post['type'] == 'text') {
$link = $post['url'];
} elseif ($post['type'] == 'video') {
$link = $post['url'];
} elseif ($post['type'] == 'photo') {
$link = $post['image'];
}
if (($msg == '') && isset($post['title'])) {
$msg = trim($post['title']);
}
if (($msg == '') && isset($post['description'])) {
$msg = trim($post['description']);
}
// If the link is already contained in the post, then it neeedn't to be added again
// But: if the link is beyond the limit, then it has to be added.
if (($link != '') && strstr($msg, $link)) {
$pos = strpos($msg, $link);
// Will the text be shortened in the link?
// Or is the link the last item in the post?
if (($limit > 0) && ($pos < $limit) && (($pos + 23 > $limit) || ($pos + strlen($link) == strlen($msg)))) {
$msg = trim(str_replace($link, '', $msg));
} elseif (($limit == 0) || ($pos < $limit)) {
// The limit has to be increased since it will be shortened - but not now
// Only do it with Twitter (htmlmode = 8)
if (($limit > 0) && (strlen($link) > 23) && ($htmlmode == 8)) {
$limit = $limit - 23 + strlen($link);
}
$link = '';
if ($post['type'] == 'text') {
unset($post['url']);
}
}
}
}
if ($limit > 0) {
// Reduce multiple spaces
// When posted to a network with limited space, we try to gain space where possible
while (strpos($msg, ' ') !== false) {
$msg = str_replace(' ', ' ', $msg);
}
// Twitter is using its own limiter, so we always assume that shortened links will have this length
if (iconv_strlen($link, 'UTF-8') > 0) {
$limit = $limit - 23;
}
if (iconv_strlen($msg, 'UTF-8') > $limit) {
if (($post['type'] == 'text') && isset($post['url'])) {
$post['url'] = $item['plink'];
} elseif (!isset($post['url'])) {
$limit = $limit - 23;
$post['url'] = $item['plink'];
} elseif (strpos($item['body'], '[share') !== false) {
$post['url'] = $item['plink'];
} elseif (PConfig::get($item['uid'], 'system', 'no_intelligent_shortening')) {
$post['url'] = $item['plink'];
}
$msg = Text\Plaintext::shorten($msg, $limit);
}
}
$post['text'] = trim($msg);
return $post;
}
}

View file

@ -92,14 +92,14 @@ class Profile
{
$user = dba::selectFirst('user', ['uid'], ['nickname' => $nickname]);
if (!$user && !count($user) && !count($profiledata)) {
if (!DBM::is_result($user) && empty($profiledata)) {
logger('profile error: ' . $a->query_string, LOGGER_DEBUG);
notice(L10n::t('Requested account is not available.') . EOL);
$a->error = 404;
return;
}
if (!x($a->page, 'aside')) {
if (empty($a->page['aside'])) {
$a->page['aside'] = '';
}
@ -157,10 +157,6 @@ class Profile
require_once $theme_info_file;
}
if (!x($a->page, 'aside')) {
$a->page['aside'] = '';
}
if (local_user() && local_user() == $a->profile['uid'] && $profiledata) {
$a->page['aside'] .= replace_macros(
get_markup_template('profile_edlink.tpl'),
@ -644,26 +640,26 @@ class Profile
$classtoday = '';
$s = dba::p(
"SELECT *
"SELECT `event`.*
FROM `event`
WHERE `event`.`uid` = ?
AND `event`.`type` != 'birthday'
AND `event`.`start` < ?
AND `event`.`start` >= ?
AND NOT EXISTS (
SELECT `id`
FROM `item`
WHERE `item`.`uid` = `event`.`uid`
INNER JOIN `item`
ON `item`.`uid` = `event`.`uid`
AND `item`.`parent-uri` = `event`.`uri`
AND `item`.`verb` = ?
AND `item`.`visible`
AND NOT `item`.`deleted`
)
WHERE `event`.`uid` = ?
AND `event`.`type` != 'birthday'
AND `event`.`start` < ?
AND `event`.`start` >= ?
AND `item`.`author-id` = ?
AND (`item`.`verb` = ? OR `item`.`verb` = ?)
AND `item`.`visible`
AND NOT `item`.`deleted`
ORDER BY `event`.`start` ASC",
local_user(),
DateTimeFormat::utc('now + 7 days'),
DateTimeFormat::utc('now - 1 days'),
ACTIVITY_ATTENDNO
public_contact(),
ACTIVITY_ATTEND,
ACTIVITY_ATTENDMAYBE
);
$r = [];
@ -954,7 +950,7 @@ class Profile
];
}
if ((!$is_owner) && ((count($a->profile)) || (!$a->profile['hide-friends']))) {
if (!$is_owner && empty($a->profile['hide-friends'])) {
$tabs[] = [
'label' => L10n::t('Contacts'),
'url' => System::baseUrl() . '/viewcontacts/' . $nickname,

View file

@ -78,7 +78,7 @@ class Post extends BaseObject
}
// Prepare the children
if (count($data['children'])) {
if (!empty($data['children'])) {
foreach ($data['children'] as $item) {
// Only add will be displayed
if ($item['network'] === NETWORK_MAIL && local_user() != $item['uid']) {

View file

@ -1197,6 +1197,7 @@ class DFRN
$ret = Network::curl($url);
if ($ret['errno'] == CURLE_OPERATION_TIMEDOUT) {
Contact::markForArchival($contact);
return -2; // timed out
}
@ -1204,24 +1205,28 @@ class DFRN
$curl_stat = $a->get_curl_code();
if (empty($curl_stat)) {
Contact::markForArchival($contact);
return -3; // timed out
}
logger('dfrn_deliver: ' . $xml, LOGGER_DATA);
if (empty($xml)) {
Contact::markForArchival($contact);
return 3;
}
if (strpos($xml, '<?xml') === false) {
logger('dfrn_deliver: no valid XML returned');
logger('dfrn_deliver: returned XML: ' . $xml, LOGGER_DATA);
Contact::markForArchival($contact);
return 3;
}
$res = XML::parseString($xml);
if ((intval($res->status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id))) {
if ((intval($res->status) != 0) || !strlen($res->challenge) || !strlen($res->dfrn_id)) {
Contact::markForArchival($contact);
return ($res->status ? $res->status : 3);
}
@ -1274,6 +1279,7 @@ class DFRN
if ($final_dfrn_id != $orig_id) {
logger('dfrn_deliver: wrong dfrn_id.');
// did not decode properly - cannot trust this site
Contact::markForArchival($contact);
return 3;
}
@ -1309,6 +1315,7 @@ class DFRN
break;
default:
logger("rino: invalid requested version '$rino_remote_version'");
Contact::markForArchival($contact);
return -8;
}
@ -1346,22 +1353,26 @@ class DFRN
$curl_stat = $a->get_curl_code();
if (empty($curl_stat) || empty($xml)) {
Contact::markForArchival($contact);
return -9; // timed out
}
if (($curl_stat == 503) && stristr($a->get_curl_headers(), 'retry-after')) {
Contact::markForArchival($contact);
return -10;
}
if (strpos($xml, '<?xml') === false) {
logger('dfrn_deliver: phase 2: no valid XML returned');
logger('dfrn_deliver: phase 2: returned XML: ' . $xml, LOGGER_DATA);
Contact::markForArchival($contact);
return 3;
}
$res = XML::parseString($xml);
if (!isset($res->status)) {
Contact::markForArchival($contact);
return -11;
}
@ -1374,7 +1385,7 @@ class DFRN
logger('Delivery returned status '.$res->status.' - '.$res->message, LOGGER_DEBUG);
}
if ($res->status == 200) {
if (($res->status >= 200) && ($res->status <= 299)) {
Contact::unmarkForArchival($contact);
}
@ -1394,28 +1405,41 @@ class DFRN
{
$a = get_app();
if (empty($contact['addr'])) {
logger('Empty contact handle for ' . $contact['id'] . ' - ' . $contact['url'] . ' - trying to update it.');
if (Contact::updateFromProbe($contact['id'])) {
$new_contact = dba::selectFirst('contact', ['addr'], ['id' => $contact['id']]);
$contact['addr'] = $new_contact['addr'];
}
if (!$public_batch) {
if (empty($contact['addr'])) {
logger('Unable to find contact handle for ' . $contact['id'] . ' - ' . $contact['url']);
return -21;
}
}
logger('Empty contact handle for ' . $contact['id'] . ' - ' . $contact['url'] . ' - trying to update it.');
if (Contact::updateFromProbe($contact['id'])) {
$new_contact = dba::selectFirst('contact', ['addr'], ['id' => $contact['id']]);
$contact['addr'] = $new_contact['addr'];
}
$fcontact = Diaspora::personByHandle($contact['addr']);
if (empty($fcontact)) {
logger('Unable to find contact details for ' . $contact['id'] . ' - ' . $contact['addr']);
return -22;
if (empty($contact['addr'])) {
logger('Unable to find contact handle for ' . $contact['id'] . ' - ' . $contact['url']);
Contact::markForArchival($contact);
return -21;
}
}
$fcontact = Diaspora::personByHandle($contact['addr']);
if (empty($fcontact)) {
logger('Unable to find contact details for ' . $contact['id'] . ' - ' . $contact['addr']);
Contact::markForArchival($contact);
return -22;
}
}
$envelope = Diaspora::buildMessage($atom, $owner, $contact, $owner['uprvkey'], $fcontact['pubkey'], $public_batch);
$dest_url = ($public_batch ? $fcontact["batch"] : $contact["notify"]);
// Create the endpoint for public posts. This is some WIP and should later be added to the probing
if ($public_batch && empty($contact["batch"])) {
$parts = parse_url($contact["notify"]);
$path_parts = explode('/', $parts['path']);
array_pop($path_parts);
$parts['path'] = implode('/', $path_parts);
$contact["batch"] = Network::unparseURL($parts);
}
$dest_url = ($public_batch ? $contact["batch"] : $contact["notify"]);
$content_type = ($public_batch ? "application/magic-envelope+xml" : "application/json");
@ -1424,22 +1448,26 @@ class DFRN
$curl_stat = $a->get_curl_code();
if (empty($curl_stat) || empty($xml)) {
logger('Empty answer from ' . $contact['id'] . ' - ' . $dest_url);
Contact::markForArchival($contact);
return -9; // timed out
}
if (($curl_stat == 503) && (stristr($a->get_curl_headers(), 'retry-after'))) {
Contact::markForArchival($contact);
return -10;
}
if (strpos($xml, '<?xml') === false) {
logger('No valid XML returned from ' . $contact['id'] . ' - ' . $dest_url);
logger('Returned XML: ' . $xml, LOGGER_DATA);
Contact::markForArchival($contact);
return 3;
}
$res = XML::parseString($xml);
if (empty($res->status)) {
Contact::markForArchival($contact);
return -23;
}
@ -1447,7 +1475,7 @@ class DFRN
logger('Transmit to ' . $dest_url . ' returned status '.$res->status.' - '.$res->message, LOGGER_DEBUG);
}
if ($res->status == 200) {
if (($res->status >= 200) && ($res->status <= 299)) {
Contact::unmarkForArchival($contact);
}
@ -1467,33 +1495,33 @@ class DFRN
// Check for duplicates
$r = q(
"SELECT `id` FROM `event` WHERE `uid` = %d AND `cid` = %d AND `start` = '%s' AND `type` = '%s' LIMIT 1",
intval($contact["uid"]),
intval($contact["id"]),
intval($contact['uid']),
intval($contact['id']),
dbesc(DateTimeFormat::utc($birthday)),
dbesc("birthday")
dbesc('birthday')
);
if (DBM::is_result($r)) {
return;
}
logger("updating birthday: ".$birthday." for contact ".$contact["id"]);
logger('updating birthday: ' . $birthday . ' for contact ' . $contact['id']);
$bdtext = L10n::t("%s\'s birthday", $contact["name"]);
$bdtext2 = L10n::t("Happy Birthday %s", " [url=".$contact["url"]."]".$contact["name"]."[/url]");
$bdtext = L10n::t('%s\'s birthday', $contact['name']);
$bdtext2 = L10n::t('Happy Birthday %s', ' [url=' . $contact['url'] . ']' . $contact['name'] . '[/url]');
$r = q(
"INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`)
VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s') ",
intval($contact["uid"]),
intval($contact["id"]),
intval($contact['uid']),
intval($contact['id']),
dbesc(DateTimeFormat::utcNow()),
dbesc(DateTimeFormat::utcNow()),
dbesc(DateTimeFormat::utc($birthday)),
dbesc(DateTimeFormat::utc($birthday . " + 1 day ")),
dbesc(DateTimeFormat::utc($birthday . ' + 1 day ')),
dbesc($bdtext),
dbesc($bdtext2),
dbesc("birthday")
dbesc('birthday')
);
}
@ -2747,6 +2775,10 @@ class DFRN
if ($posted_id) {
logger("Reply from contact ".$item["contact-id"]." was stored with id ".$posted_id, LOGGER_DEBUG);
if ($item['uid'] == 0) {
Item::distribute($posted_id);
}
$item["id"] = $posted_id;
$r = q(
@ -2771,7 +2803,7 @@ class DFRN
logger('ignoring read-only contact '.$importer["id"]);
return;
}
if ($importer["uid"] == 0) {
if (($importer["uid"] == 0) && ($importer["importer_uid"] != 0)) {
logger("Contact ".$importer["id"]." isn't known to user ".$importer["importer_uid"].". The post will be ignored.", LOGGER_DEBUG);
return;
}
@ -2801,6 +2833,10 @@ class DFRN
logger("Item was stored with id ".$posted_id, LOGGER_DEBUG);
if ($item['uid'] == 0) {
Item::distribute($posted_id);
}
if (stristr($item["verb"], ACTIVITY_POKE)) {
self::doPoke($item, $importer, $posted_id);
}
@ -2923,6 +2959,9 @@ class DFRN
logger("Import DFRN message for user " . $importer["importer_uid"] . " from contact " . $importer["id"], LOGGER_DEBUG);
// is it a public forum? Private forums aren't exposed with this method
$forum = intval($xpath->evaluate("/atom:feed/dfrn:community/text()")->item(0)->nodeValue);
// The account type is new since 3.5.1
if ($xpath->query("/atom:feed/dfrn:account_type")->length > 0) {
$accounttype = intval($xpath->evaluate("/atom:feed/dfrn:account_type/text()")->item(0)->nodeValue);
@ -2930,17 +2969,17 @@ class DFRN
if ($accounttype != $importer["contact-type"]) {
dba::update('contact', ['contact-type' => $accounttype], ['id' => $importer["id"]]);
}
}
// is it a public forum? Private forums aren't supported with this method
// This is deprecated since 3.5.1
$forum = intval($xpath->evaluate("/atom:feed/dfrn:community/text()")->item(0)->nodeValue);
if ($forum != $importer["forum"]) {
// A forum contact can either have set "forum" or "prv" - but not both
if (($accounttype == ACCOUNT_TYPE_COMMUNITY) && (($forum != $importer["forum"]) || ($forum == $importer["prv"]))) {
$condition = ['(`forum` != ? OR `prv` != ?) AND `id` = ?', $forum, !$forum, $importer["id"]];
dba::update('contact', ['forum' => $forum, 'prv' => !$forum], $condition);
}
} elseif ($forum != $importer["forum"]) { // Deprecated since 3.5.1
$condition = ['`forum` != ? AND `id` = ?', $forum, $importer["id"]];
dba::update('contact', ['forum' => $forum], $condition);
}
// We are processing relocations even if we are ignoring a contact
$relocations = $xpath->query("/atom:feed/dfrn:relocate");
foreach ($relocations as $relocation) {

View file

@ -590,59 +590,15 @@ class Diaspora
return false;
}
if (!($postdata = self::validPosting($msg))) {
if (!($fields = self::validPosting($msg))) {
logger("Invalid posting");
return false;
}
$fields = $postdata['fields'];
$importer = ["uid" => 0, "page-flags" => PAGE_FREELOVE];
$success = self::dispatch($importer, $msg, $fields);
// Is it a an action (comment, like, ...) for our own post?
if (isset($fields->parent_guid) && !$postdata["relayed"]) {
$guid = notags(unxmlify($fields->parent_guid));
$importer = self::importerForGuid($guid);
if (is_array($importer)) {
logger("delivering to origin: ".$importer["name"]);
$message_id = self::dispatch($importer, $msg, $fields);
return $message_id;
}
}
// Process item retractions. This has to be done separated from the other stuff,
// since retractions for comments could come even from non followers.
if (!empty($fields) && in_array($fields->getName(), ['retraction'])) {
$target = notags(unxmlify($fields->target_type));
if (in_array($target, ["Comment", "Like", "Post", "Reshare", "StatusMessage"])) {
logger('processing retraction for '.$target, LOGGER_DEBUG);
$importer = ["uid" => 0, "page-flags" => PAGE_FREELOVE];
$message_id = self::dispatch($importer, $msg, $fields);
return $message_id;
}
}
// Now distribute it to the followers
$r = q(
"SELECT `user`.* FROM `user` WHERE `user`.`uid` IN
(SELECT `contact`.`uid` FROM `contact` WHERE `contact`.`network` = '%s' AND `contact`.`addr` = '%s')
AND NOT `account_expired` AND NOT `account_removed`",
dbesc(NETWORK_DIASPORA),
dbesc($msg["author"])
);
if (DBM::is_result($r)) {
foreach ($r as $rr) {
logger("delivering to: ".$rr["username"]);
self::dispatch($rr, $msg, $fields);
}
} elseif (!Config::get('system', 'relay_subscribe', false)) {
logger("Unwanted message from ".$msg["author"]." send by ".$_SERVER["REMOTE_ADDR"]." with ".$_SERVER["HTTP_USER_AGENT"].": ".print_r($msg, true), LOGGER_DEBUG);
} else {
// Use a dummy importer to import the data for the public copy
$importer = ["uid" => 0, "page-flags" => PAGE_FREELOVE];
$message_id = self::dispatch($importer, $msg, $fields);
}
return $message_id;
return $success;
}
/**
@ -662,11 +618,13 @@ class Diaspora
// This is only needed for private postings since this is already done for public ones before
if (is_null($fields)) {
if (!($postdata = self::validPosting($msg))) {
$private = true;
if (!($fields = self::validPosting($msg))) {
logger("Invalid posting");
return false;
}
$fields = $postdata['fields'];
} else {
$private = false;
}
$type = $fields->getName();
@ -675,27 +633,47 @@ class Diaspora
switch ($type) {
case "account_migration":
if (!$private) {
logger('Message with type ' . $type . ' is not private, quitting.');
return false;
}
return self::receiveAccountMigration($importer, $fields);
case "account_deletion":
return self::receiveAccountDeletion($importer, $fields);
return self::receiveAccountDeletion($fields);
case "comment":
return self::receiveComment($importer, $sender, $fields, $msg["message"]);
case "contact":
if (!$private) {
logger('Message with type ' . $type . ' is not private, quitting.');
return false;
}
return self::receiveContactRequest($importer, $fields);
case "conversation":
if (!$private) {
logger('Message with type ' . $type . ' is not private, quitting.');
return false;
}
return self::receiveConversation($importer, $msg, $fields);
case "like":
return self::receiveLike($importer, $sender, $fields);
case "message":
if (!$private) {
logger('Message with type ' . $type . ' is not private, quitting.');
return false;
}
return self::receiveMessage($importer, $fields);
case "participation":
if (!$private) {
logger('Message with type ' . $type . ' is not private, quitting.');
return false;
}
return self::receiveParticipation($importer, $fields);
case "photo": // Not implemented
@ -705,6 +683,10 @@ class Diaspora
return self::receivePollParticipation($importer, $fields);
case "profile":
if (!$private) {
logger('Message with type ' . $type . ' is not private, quitting.');
return false;
}
return self::receiveProfile($importer, $fields);
case "reshare":
@ -840,7 +822,7 @@ class Diaspora
// Only some message types have signatures. So we quit here for the other types.
if (!in_array($type, ["comment", "like"])) {
return ["fields" => $fields, "relayed" => false];
return $fields;
}
// No author_signature? This is a must, so we quit.
if (!isset($author_signature)) {
@ -849,25 +831,29 @@ class Diaspora
}
if (isset($parent_author_signature)) {
$relayed = true;
$key = self::key($msg["author"]);
if (empty($key)) {
logger("No key found for parent author ".$msg["author"], LOGGER_DEBUG);
return false;
}
if (!Crypto::rsaVerify($signed_data, $parent_author_signature, $key, "sha256")) {
logger("No valid parent author signature for parent author ".$msg["author"]. " in type ".$type." - signed data: ".$signed_data." - Message: ".$msg["message"]." - Signature ".$parent_author_signature, LOGGER_DEBUG);
return false;
}
} else {
$relayed = false;
}
$key = self::key($fields->author);
if (empty($key)) {
logger("No key found for author ".$fields->author, LOGGER_DEBUG);
return false;
}
if (!Crypto::rsaVerify($signed_data, $author_signature, $key, "sha256")) {
logger("No valid author signature for author ".$fields->author. " in type ".$type." - signed data: ".$signed_data." - Message: ".$msg["message"]." - Signature ".$author_signature, LOGGER_DEBUG);
return false;
} else {
return ["fields" => $fields, "relayed" => $relayed];
return $fields;
}
}
@ -1650,25 +1636,23 @@ class Diaspora
/**
* @brief Processes an account deletion
*
* @param array $importer Array of the importer user
* @param object $data The message object
*
* @return bool Success
*/
private static function receiveAccountDeletion($importer, $data)
private static function receiveAccountDeletion($data)
{
/// @todo Account deletion should remove the contact from the global contacts as well
$author = notags(unxmlify($data->author));
$contact = self::contactByHandle($importer["uid"], $author);
if (!$contact) {
logger("cannot find contact for author: ".$author);
return false;
$contacts = dba::select('contact', ['id'], ['addr' => $author]);
while ($contact = dba::fetch($contacts)) {
Contact::remove($contact["id"]);
}
// We now remove the contact
Contact::remove($contact["id"]);
dba::delete('gcontact', ['addr' => $author]);
logger('Removed contacts for ' . $author);
return true;
}
@ -1836,6 +1820,9 @@ class Diaspora
if ($message_id) {
logger("Stored comment ".$datarray["guid"]." with message id ".$message_id, LOGGER_DEBUG);
if ($datarray['uid'] == 0) {
Item::distribute($message_id);
}
}
// If we are the origin of the parent we store the original data and notify our followers
@ -2157,6 +2144,9 @@ class Diaspora
if ($message_id) {
logger("Stored like ".$datarray["guid"]." with message id ".$message_id, LOGGER_DEBUG);
if ($datarray['uid'] == 0) {
Item::distribute($message_id);
}
}
// like on comments have the comment as parent. So we need to fetch the toplevel parent
@ -2739,10 +2729,15 @@ class Diaspora
*/
public static function originalItem($guid, $orig_author)
{
if (empty($guid)) {
logger('Empty guid. Quitting.');
return false;
}
// Do we already have this item?
$fields = ['body', 'tag', 'app', 'created', 'object-type', 'uri', 'guid',
'author-name', 'author-link', 'author-avatar'];
$condition = ['guid' => $guid, 'visible' => true, 'deleted' => false];
$condition = ['guid' => $guid, 'visible' => true, 'deleted' => false, 'private' => false];
$item = dba::selectfirst('item', $fields, $condition);
if (DBM::is_result($item)) {
@ -2752,7 +2747,7 @@ class Diaspora
// Then refetch the content, if it is a reshare from a reshare.
// If it is a reshared post from another network then reformat to avoid display problems with two share elements
if (self::isReshare($item["body"], true)) {
$r = [];
$item = [];
} elseif (self::isReshare($item["body"], false) || strstr($item["body"], "[share")) {
$item["body"] = Markdown::toBBCode(BBCode::toMarkdown($item["body"]));
@ -2767,21 +2762,26 @@ class Diaspora
}
}
if (!DBM::is_result($r)) {
$server = "https://".substr($orig_author, strpos($orig_author, "@") + 1);
logger("1st try: reshared message ".$guid." will be fetched via SSL from the server ".$server);
$item_id = self::storeByGuid($guid, $server);
if (!$item_id) {
$server = "http://".substr($orig_author, strpos($orig_author, "@") + 1);
logger("2nd try: reshared message ".$guid." will be fetched without SLL from the server ".$server);
$item_id = self::storeByGuid($guid, $server);
if (!DBM::is_result($item)) {
if (empty($orig_author)) {
logger('Empty author for guid ' . $guid . '. Quitting.');
return false;
}
if ($item_id) {
$server = "https://".substr($orig_author, strpos($orig_author, "@") + 1);
logger("1st try: reshared message ".$guid." will be fetched via SSL from the server ".$server);
$stored = self::storeByGuid($guid, $server);
if (!$stored) {
$server = "http://".substr($orig_author, strpos($orig_author, "@") + 1);
logger("2nd try: reshared message ".$guid." will be fetched without SSL from the server ".$server);
$stored = self::storeByGuid($guid, $server);
}
if ($stored) {
$fields = ['body', 'tag', 'app', 'created', 'object-type', 'uri', 'guid',
'author-name', 'author-link', 'author-avatar'];
$condition = ['id' => $item_id, 'visible' => true, 'deleted' => false];
$condition = ['guid' => $guid, 'visible' => true, 'deleted' => false, 'private' => false];
$item = dba::selectfirst('item', $fields, $condition);
if (DBM::is_result($item)) {
@ -2883,6 +2883,9 @@ class Diaspora
if ($message_id) {
logger("Stored reshare ".$datarray["guid"]." with message id ".$message_id, LOGGER_DEBUG);
if ($datarray['uid'] == 0) {
Item::distribute($message_id);
}
return true;
} else {
return false;
@ -3107,6 +3110,9 @@ class Diaspora
if ($message_id) {
logger("Stored item ".$datarray["guid"]." with message id ".$message_id, LOGGER_DEBUG);
if ($datarray['uid'] == 0) {
Item::distribute($message_id);
}
return true;
} else {
return false;

View file

@ -36,7 +36,30 @@ class Network
*/
public static function fetchUrl($url, $binary = false, &$redirects = 0, $timeout = 0, $accept_content = null, $cookiejar = 0)
{
$ret = self::curl(
$ret = self::fetchUrlFull($url, $binary, $redirects, $timeout, $accept_content, $cookiejar);
return $ret['body'];
}
/**
* @brief Curl wrapper with array of return values.
*
* Inner workings and parameters are the same as @ref fetchUrl but returns an array with
* all the information collected during the fetch.
*
* @param string $url URL to fetch
* @param boolean $binary default false
* TRUE if asked to return binary results (file download)
* @param integer $redirects The recursion counter for internal use - default 0
* @param integer $timeout Timeout in seconds, default system config value or 60 seconds
* @param string $accept_content supply Accept: header with 'accept_content' as the value
* @param string $cookiejar Path to cookie jar file
*
* @return array With all relevant information, 'body' contains the actual fetched content.
*/
public static function fetchUrlFull($url, $binary = false, &$redirects = 0, $timeout = 0, $accept_content = null, $cookiejar = 0)
{
return self::curl(
$url,
$binary,
$redirects,
@ -45,8 +68,6 @@ class Network
'cookiejar'=>$cookiejar
]
);
return($ret['body']);
}
/**

View file

@ -59,7 +59,7 @@ class Temporal
$o = '<select id="timezone_select" name="timezone">';
usort($timezone_identifiers, [self, 'timezoneCompareCallback']);
usort($timezone_identifiers, [__CLASS__, 'timezoneCompareCallback']);
$continent = '';
foreach ($timezone_identifiers as $value) {
$ex = explode("/", $value);

View file

@ -4,6 +4,7 @@
*/
namespace Friendica\Worker;
use Friendica\BaseObject;
use Friendica\Core\Config;
use Friendica\Core\L10n;
use Friendica\Core\System;
@ -19,98 +20,67 @@ use dba;
require_once 'include/items.php';
/// @todo This is some ugly code that needs to be split into several methods
class Delivery extends BaseObject
{
const MAIL = 'mail';
const SUGGESTION = 'suggest';
const RELOCATION = 'relocate';
const DELETION = 'drop';
const POST = 'wall-new';
const COMMENT = 'comment-new';
class Delivery {
public static function execute($cmd, $item_id, $contact_id) {
global $a;
public static function execute($cmd, $item_id, $contact_id)
{
logger('Invoked: ' . $cmd . ': ' . $item_id . ' to ' . $contact_id, LOGGER_DEBUG);
logger('delivery: invoked: '.$cmd.': '.$item_id.' to '.$contact_id, LOGGER_DEBUG);
$mail = false;
$fsuggest = false;
$relocate = false;
$top_level = false;
$recipients = [];
$followup = false;
$public_message = false;
$normal_mode = true;
$item = null;
$recipients[] = $contact_id;
if ($cmd === 'mail') {
$normal_mode = false;
$mail = true;
$message = q("SELECT * FROM `mail` WHERE `id` = %d LIMIT 1",
intval($item_id)
);
if (!count($message)) {
if ($cmd == self::MAIL) {
$target_item = dba::selectFirst('mail', [], ['id' => $item_id]);
if (!DBM::is_result($message)) {
return;
}
$uid = $message[0]['uid'];
$recipients[] = $message[0]['contact-id'];
$item = $message[0];
} elseif ($cmd === 'suggest') {
$normal_mode = false;
$fsuggest = true;
$suggest = q("SELECT * FROM `fsuggest` WHERE `id` = %d LIMIT 1",
intval($item_id)
);
if (!count($suggest)) {
$uid = $target_item['uid'];
} elseif ($cmd == self::SUGGESTION) {
$target_item = dba::selectFirst('fsuggest', [], ['id' => $item_id]);
if (!DBM::is_result($message)) {
return;
}
$uid = $suggest[0]['uid'];
$recipients[] = $suggest[0]['cid'];
$item = $suggest[0];
} elseif ($cmd === 'relocate') {
$normal_mode = false;
$relocate = true;
$uid = $target_item['uid'];
} elseif ($cmd == self::RELOCATION) {
$uid = $item_id;
} else {
// find ancestors
$target_item = dba::fetch_first("SELECT `item`.*, `contact`.`uid` AS `cuid` FROM `item`
INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
WHERE `item`.`id` = ? AND `visible` AND NOT `moderated`", $item_id);
if (!DBM::is_result($target_item) || !intval($target_item['parent'])) {
$item = dba::selectFirst('item', ['parent'], ['id' => $item_id]);
if (!DBM::is_result($item) || empty($item['parent'])) {
return;
}
$parent_id = intval($item['parent']);
$parent_id = intval($target_item['parent']);
$uid = $target_item['cuid'];
$updated = $target_item['edited'];
$items = q("SELECT `item`.*, `sign`.`signed_text`,`sign`.`signature`,`sign`.`signer`
FROM `item` LEFT JOIN `sign` ON `sign`.`iid` = `item`.`id` WHERE `parent` = %d AND visible = 1 AND moderated = 0 ORDER BY `id` ASC",
intval($parent_id)
);
if (!count($items)) {
return;
}
$icontacts = null;
$contacts_arr = [];
foreach ($items as $item) {
if (!in_array($item['contact-id'],$contacts_arr)) {
$contacts_arr[] = intval($item['contact-id']);
$itemdata = dba::p("SELECT `item`.*, `contact`.`uid` AS `cuid`,
`sign`.`signed_text`,`sign`.`signature`,`sign`.`signer`
FROM `item`
INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id`
LEFT JOIN `sign` ON `sign`.`iid` = `item`.`id`
WHERE `item`.`id` IN (?, ?) AND `visible` AND NOT `moderated`
ORDER BY `item`.`id`",
$item_id, $parent_id);
$items = [];
while ($item = dba::fetch($itemdata)) {
if ($item['id'] == $parent_id) {
$parent = $item;
}
if ($item['id'] == $item_id) {
$target_item = $item;
}
$items[] = $item;
}
if (count($contacts_arr)) {
$str_contacts = implode(',',$contacts_arr);
$icontacts = q("SELECT * FROM `contact`
WHERE `id` IN ( $str_contacts ) "
);
}
if ( !($icontacts && count($icontacts))) {
return;
}
dba::close($itemdata);
$uid = $target_item['cuid'];
// avoid race condition with deleting entries
if ($items[0]['deleted']) {
foreach ($items as $item) {
$item['deleted'] = 1;
@ -120,24 +90,10 @@ class Delivery {
// When commenting too fast after delivery, a post wasn't recognized as top level post.
// The count then showed more than one entry. The additional check should help.
// The check for the "count" should be superfluous, but I'm not totally sure by now, so we keep it.
if ((($items[0]['id'] == $item_id) || (count($items) == 1)) && ($items[0]['uri'] === $items[0]['parent-uri'])) {
logger('delivery: top level post');
if ((($parent['id'] == $item_id) || (count($items) == 1)) && ($parent['uri'] === $parent['parent-uri'])) {
logger('Top level post');
$top_level = true;
}
}
$owner = User::getOwnerDataById($uid);
if (!$owner) {
return;
}
// We don't treat Forum posts as "wall-to-wall" to be able to post them via Diaspora
$walltowall = $top_level && ($owner['id'] != $items[0]['contact-id']) & ($owner['account-type'] != ACCOUNT_TYPE_COMMUNITY);
$public_message = true;
if (!$mail && !$fsuggest && !$relocate) {
$parent = $items[0];
// This is IMPORTANT!!!!
@ -147,9 +103,9 @@ class Delivery {
// if $parent['wall'] == 1 we will already have the parent message in our array
// and we will relay the whole lot.
$localhost = $a->get_hostname();
if (strpos($localhost,':')) {
$localhost = substr($localhost,0,strpos($localhost,':'));
$localhost = self::getApp()->get_hostname();
if (strpos($localhost, ':')) {
$localhost = substr($localhost, 0, strpos($localhost, ':'));
}
/**
*
@ -159,26 +115,24 @@ class Delivery {
*
*/
$relay_to_owner = false;
if (!$top_level && ($parent['wall'] == 0) && stristr($target_item['uri'], $localhost)) {
$relay_to_owner = true;
}
if ($relay_to_owner) {
logger('followup '.$target_item["guid"], LOGGER_DEBUG);
logger('Followup ' . $target_item["guid"], LOGGER_DEBUG);
// local followup to remote post
$followup = true;
}
if (strlen($parent['allow_cid'])
|| strlen($parent['allow_gid'])
|| strlen($parent['deny_cid'])
|| strlen($parent['deny_gid'])
|| $parent["private"]) {
$public_message = false; // private recipients, not public
if (empty($parent['allow_cid'])
&& empty($parent['allow_gid'])
&& empty($parent['deny_cid'])
&& empty($parent['deny_gid'])
&& !$parent["private"]) {
$public_message = true;
}
}
$owner = User::getOwnerDataById($uid);
if (!DBM::is_result($owner)) {
return;
}
// We don't deliver our items to blocked or pending contacts, and not to ourselves either
@ -189,146 +143,22 @@ class Delivery {
return;
}
$deliver_status = 0;
// Transmit via Diaspora if not possible via Friendica
if (($item['uid'] == 0) && ($contact['network'] == NETWORK_DFRN)) {
// Transmit via Diaspora if the thread had started as Diaspora post
// This is done since the uri wouldn't match (Diaspora doesn't transmit it)
if (isset($parent) && ($parent['network'] == NETWORK_DIASPORA) && ($contact['network'] == NETWORK_DFRN)) {
$contact['network'] = NETWORK_DIASPORA;
}
logger("main delivery by delivery: followup=$followup mail=$mail fsuggest=$fsuggest relocate=$relocate - network ".$contact['network']);
logger("Delivering " . $cmd . " followup=$followup - via network " . $contact['network']);
switch ($contact['network']) {
case NETWORK_DFRN:
logger('notifier: '.$target_item["guid"].' dfrndelivery: '.$contact['name']);
if ($mail) {
$item['body'] = Item::fixPrivatePhotos($item['body'], $owner['uid'], null, $item['contact-id']);
$atom = DFRN::mail($item, $owner);
} elseif ($fsuggest) {
$atom = DFRN::fsuggest($item, $owner);
dba::delete('fsuggest', ['id' => $item['id']]);
} elseif ($relocate) {
$atom = DFRN::relocate($owner, $uid);
} elseif ($followup) {
$msgitems = [];
foreach ($items as $item) { // there is only one item
if (!$item['parent']) {
return;
}
if ($item['id'] == $item_id) {
logger('followup: item: '. print_r($item,true), LOGGER_DATA);
$msgitems[] = $item;
}
}
$atom = DFRN::entries($msgitems,$owner);
} else {
$msgitems = [];
foreach ($items as $item) {
if (!$item['parent']) {
return;
}
// private emails may be in included in public conversations. Filter them.
if ($public_message && $item['private']) {
return;
}
$item_contact = self::getItemContact($item,$icontacts);
if (!$item_contact) {
return;
}
if ($normal_mode) {
// Only add the parent when we don't delete other items.
if ($item_id == $item['id'] || (($item['id'] == $item['parent']) && ($cmd != 'drop'))) {
$item["entry:comment-allow"] = true;
$item["entry:cid"] = (($top_level) ? $contact['id'] : 0);
$msgitems[] = $item;
}
} else {
$item["entry:comment-allow"] = true;
$msgitems[] = $item;
}
}
$atom = DFRN::entries($msgitems,$owner);
}
logger('notifier entry: '.$contact["url"].' '.$target_item["guid"].' entry: '.$atom, LOGGER_DEBUG);
logger('notifier: '.$atom, LOGGER_DATA);
$basepath = implode('/', array_slice(explode('/',$contact['url']),0,3));
// perform local delivery if we are on the same site
if (link_compare($basepath,System::baseUrl())) {
$nickname = basename($contact['url']);
if ($contact['issued-id']) {
$sql_extra = sprintf(" AND `dfrn-id` = '%s' ", dbesc($contact['issued-id']));
} else {
$sql_extra = sprintf(" AND `issued-id` = '%s' ", dbesc($contact['dfrn-id']));
}
$x = q("SELECT `contact`.*, `contact`.`uid` AS `importer_uid`,
`contact`.`pubkey` AS `cpubkey`,
`contact`.`prvkey` AS `cprvkey`,
`contact`.`thumb` AS `thumb`,
`contact`.`url` as `url`,
`contact`.`name` as `senderName`,
`user`.*
FROM `contact`
INNER JOIN `user` ON `contact`.`uid` = `user`.`uid`
WHERE `contact`.`blocked` = 0 AND `contact`.`pending` = 0
AND `contact`.`network` = '%s' AND `user`.`nickname` = '%s'
$sql_extra
AND `user`.`account_expired` = 0 AND `user`.`account_removed` = 0 LIMIT 1",
dbesc(NETWORK_DFRN),
dbesc($nickname)
);
if ($x && count($x)) {
$write_flag = ((($x[0]['rel']) && ($x[0]['rel'] != CONTACT_IS_SHARING)) ? true : false);
if ((($owner['page-flags'] == PAGE_COMMUNITY) || $write_flag) && !$x[0]['writable']) {
dba::update('contact', ['writable' => true], ['id' => $x[0]['id']]);
$x[0]['writable'] = 1;
}
$ssl_policy = Config::get('system','ssl_policy');
$x[0] = Contact::updateSslPolicy($x[0], $ssl_policy);
// If we are setup as a soapbox we aren't accepting top level posts from this person
if (($x[0]['page-flags'] == PAGE_SOAPBOX) && $top_level) {
break;
}
logger('mod-delivery: local delivery');
DFRN::import($atom, $x[0]);
break;
}
}
if (!Queue::wasDelayed($contact['id'])) {
$deliver_status = DFRN::deliver($owner, $contact, $atom);
} else {
$deliver_status = -1;
}
logger('notifier: dfrn_delivery to '.$contact["url"].' with guid '.$target_item["guid"].' returns '.$deliver_status);
if ($deliver_status < 0) {
logger('notifier: delivery failed: queuing message');
Queue::add($contact['id'], NETWORK_DFRN, $atom, false, $target_item['guid']);
}
if (($deliver_status >= 200) && ($deliver_status <= 299)) {
// We successfully delivered a message, the contact is alive
Contact::unmarkForArchival($contact);
} else {
// The message could not be delivered. We mark the contact as "dead"
Contact::markForArchival($contact);
}
self::deliverDFRN($cmd, $contact, $owner, $items, $target_item, $public_message, $top_level, $followup);
break;
case NETWORK_DIASPORA:
self::deliverDiaspora($cmd, $contact, $owner, $items, $target_item, $public_message, $top_level, $followup);
break;
case NETWORK_OSTATUS:
@ -345,156 +175,7 @@ class Delivery {
break;
case NETWORK_MAIL:
if (Config::get('system','dfrn_only')) {
break;
}
// WARNING: does not currently convert to RFC2047 header encodings, etc.
$addr = $contact['addr'];
if (!strlen($addr)) {
break;
}
if ($cmd === 'wall-new' || $cmd === 'comment-new') {
$it = null;
if ($cmd === 'wall-new') {
$it = $items[0];
} else {
$r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1",
intval($item_id)
);
if (DBM::is_result($r)) {
$it = $r[0];
}
}
if (!$it) {
break;
}
$local_user = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
intval($uid)
);
if (!count($local_user)) {
break;
}
$reply_to = '';
$r1 = q("SELECT * FROM `mailacct` WHERE `uid` = %d LIMIT 1",
intval($uid)
);
if ($r1 && $r1[0]['reply_to']) {
$reply_to = $r1[0]['reply_to'];
}
$subject = (($it['title']) ? Email::encodeHeader($it['title'],'UTF-8') : L10n::t("\x28no subject\x29")) ;
// only expose our real email address to true friends
if (($contact['rel'] == CONTACT_IS_FRIEND) && !$contact['blocked']) {
if ($reply_to) {
$headers = 'From: '.Email::encodeHeader($local_user[0]['username'],'UTF-8').' <'.$reply_to.'>'."\n";
$headers .= 'Sender: '.$local_user[0]['email']."\n";
} else {
$headers = 'From: '.Email::encodeHeader($local_user[0]['username'],'UTF-8').' <'.$local_user[0]['email'].'>'."\n";
}
} else {
$headers = 'From: '. Email::encodeHeader($local_user[0]['username'], 'UTF-8') . ' <noreply@' . $a->get_hostname() . '>' . "\n";
}
//if ($reply_to)
// $headers .= 'Reply-to: '.$reply_to . "\n";
$headers .= 'Message-Id: <'. Email::iri2msgid($it['uri']).'>'. "\n";
//logger("Mail: uri: ".$it['uri']." parent-uri ".$it['parent-uri'], LOGGER_DEBUG);
//logger("Mail: Data: ".print_r($it, true), LOGGER_DEBUG);
//logger("Mail: Data: ".print_r($it, true), LOGGER_DATA);
if ($it['uri'] !== $it['parent-uri']) {
$headers .= "References: <".Email::iri2msgid($it["parent-uri"]).">";
// If Threading is enabled, write down the correct parent
if (($it["thr-parent"] != "") && ($it["thr-parent"] != $it["parent-uri"])) {
$headers .= " <".Email::iri2msgid($it["thr-parent"]).">";
}
$headers .= "\n";
if (!$it['title']) {
$r = q("SELECT `title` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
dbesc($it['parent-uri']),
intval($uid));
if (DBM::is_result($r) && ($r[0]['title'] != '')) {
$subject = $r[0]['title'];
} else {
$r = q("SELECT `title` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d LIMIT 1",
dbesc($it['parent-uri']),
intval($uid));
if (DBM::is_result($r) && ($r[0]['title'] != '')) {
$subject = $r[0]['title'];
}
}
}
if (strncasecmp($subject,'RE:',3)) {
$subject = 'Re: '.$subject;
}
}
Email::send($addr, $subject, $headers, $it);
}
break;
case NETWORK_DIASPORA:
if ($public_message) {
$loc = 'public batch '.$contact['batch'];
} else {
$loc = $contact['name'];
}
logger('delivery: diaspora batch deliver: '.$loc);
if (Config::get('system','dfrn_only') || !Config::get('system','diaspora_enabled')) {
break;
}
if ($mail) {
Diaspora::sendMail($item,$owner,$contact);
break;
}
if (!$normal_mode) {
break;
}
if (!$contact['pubkey'] && !$public_message) {
break;
}
if (($target_item['deleted']) && (($target_item['uri'] === $target_item['parent-uri']) || $followup)) {
// top-level retraction
logger('diaspora retract: '.$loc);
Diaspora::sendRetraction($target_item,$owner,$contact,$public_message);
break;
} elseif ($relocate) {
Diaspora::sendAccountMigration($owner, $contact, $uid);
break;
} elseif ($followup) {
// send comments and likes to owner to relay
logger('diaspora followup: '.$loc);
Diaspora::sendFollowup($target_item,$owner,$contact,$public_message);
break;
} elseif ($target_item['uri'] !== $target_item['parent-uri']) {
// we are the relay - send comments, likes and relayable_retractions to our conversants
logger('diaspora relay: '.$loc);
Diaspora::sendRelay($target_item,$owner,$contact,$public_message);
break;
} elseif ($top_level && !$walltowall) {
// currently no workable solution for sending walltowall
logger('diaspora status: '.$loc);
Diaspora::sendStatus($target_item,$owner,$contact,$public_message);
break;
}
logger('delivery: diaspora unknown mode: '.$contact['name']);
self::deliverMail($cmd, $contact, $owner, $target_item);
break;
default:
@ -504,16 +185,268 @@ class Delivery {
return;
}
private static function getItemContact($item, $contacts)
/**
* @brief Deliver content via DFRN
*
* @param string $cmd Command
* @param array $contact Contact record of the receiver
* @param array $owner Owner record of the sender
* @param array $items Item record of the content and the parent
* @param array $target_item Item record of the content
* @param boolean $public_message Is the content public?
* @param boolean $top_level Is it a thread starter?
* @param boolean $followup Is it an answer to a remote post?
*/
private static function deliverDFRN($cmd, $contact, $owner, $items, $target_item, $public_message, $top_level, $followup)
{
if (!count($contacts) || !is_array($item)) {
return false;
logger('Deliver ' . $target_item["guid"] . ' via DFRN to ' . $contact['addr']);
if ($cmd == self::MAIL) {
$item = $target_item;
$item['body'] = Item::fixPrivatePhotos($item['body'], $owner['uid'], null, $item['contact-id']);
$atom = DFRN::mail($item, $owner);
} elseif ($cmd == self::SUGGESTION) {
$item = $target_item;
$atom = DFRN::fsuggest($item, $owner);
dba::delete('fsuggest', ['id' => $item['id']]);
} elseif ($cmd == self::RELOCATION) {
$atom = DFRN::relocate($owner, $owner['uid']);
} elseif ($followup) {
$msgitems = [$target_item];
$atom = DFRN::entries($msgitems, $owner);
} else {
$msgitems = [];
foreach ($items as $item) {
// Only add the parent when we don't delete other items.
if (($target_item['id'] == $item['id']) || ($cmd != self::DELETION)) {
$item["entry:comment-allow"] = true;
$item["entry:cid"] = ($top_level ? $contact['id'] : 0);
$msgitems[] = $item;
}
}
$atom = DFRN::entries($msgitems, $owner);
}
foreach ($contacts as $contact) {
if ($contact['id'] == $item['contact-id']) {
return $contact;
logger('Notifier entry: ' . $contact["url"] . ' ' . $target_item["guid"] . ' entry: ' . $atom, LOGGER_DATA);
$basepath = implode('/', array_slice(explode('/', $contact['url']), 0, 3));
// perform local delivery if we are on the same site
if (link_compare($basepath, System::baseUrl())) {
$condition = ['nurl' => normalise_link($contact['url']), 'self' => true];
$target_self = dba::selectFirst('contact', ['uid'], $condition);
if (!DBM::is_result($target_self)) {
return;
}
$target_uid = $target_self['uid'];
// Check if the user has got this contact
$cid = Contact::getIdForURL($owner['url'], $target_uid);
if (!$cid) {
// Otherwise there should be a public contact
$cid = Contact::getIdForURL($owner['url']);
if (!$cid) {
return;
}
}
// We now have some contact, so we fetch it
$target_importer = dba::fetch_first("SELECT *, `name` as `senderName`
FROM `contact`
WHERE NOT `blocked` AND `id` = ? LIMIT 1",
$cid);
// This should never fail
if (!DBM::is_result($target_importer)) {
return;
}
// Set the user id. This is important if this is a public contact
$target_importer['importer_uid'] = $target_uid;
DFRN::import($atom, $target_importer);
return;
}
// We don't have a relationship with contacts on a public post.
// Se we transmit with the new method and via Diaspora as a fallback
if ($items[0]['uid'] == 0) {
// Transmit in public if it's a relay post
$public_dfrn = ($contact['contact-type'] == ACCOUNT_TYPE_RELAY);
$deliver_status = DFRN::transmit($owner, $contact, $atom, $public_dfrn);
if (($deliver_status < 200) || ($deliver_status > 299)) {
// Transmit via Diaspora if not possible via Friendica
self::deliverDiaspora($cmd, $contact, $owner, $items, $target_item, $public_message, $top_level, $followup);
return;
}
} else {
$deliver_status = DFRN::deliver($owner, $contact, $atom);
}
logger('Delivery to ' . $contact["url"] . ' with guid ' . $target_item["guid"] . ' returns ' . $deliver_status);
if ($deliver_status < 0) {
logger('Delivery failed: queuing message ' . $target_item["guid"] );
Queue::add($contact['id'], NETWORK_DFRN, $atom, false, $target_item['guid']);
}
if (($deliver_status >= 200) && ($deliver_status <= 299)) {
// We successfully delivered a message, the contact is alive
Contact::unmarkForArchival($contact);
} else {
// The message could not be delivered. We mark the contact as "dead"
Contact::markForArchival($contact);
}
}
/**
* @brief Deliver content via Diaspora
*
* @param string $cmd Command
* @param array $contact Contact record of the receiver
* @param array $owner Owner record of the sender
* @param array $items Item record of the content and the parent
* @param array $target_item Item record of the content
* @param boolean $public_message Is the content public?
* @param boolean $top_level Is it a thread starter?
* @param boolean $followup Is it an answer to a remote post?
*/
private static function deliverDiaspora($cmd, $contact, $owner, $items, $target_item, $public_message, $top_level, $followup)
{
// We don't treat Forum posts as "wall-to-wall" to be able to post them via Diaspora
$walltowall = $top_level && ($owner['id'] != $items[0]['contact-id']) & ($owner['account-type'] != ACCOUNT_TYPE_COMMUNITY);
if ($public_message) {
$loc = 'public batch ' . $contact['batch'];
} else {
$loc = $contact['addr'];
}
logger('Deliver ' . $target_item["guid"] . ' via Diaspora to ' . $loc);
if (Config::get('system', 'dfrn_only') || !Config::get('system', 'diaspora_enabled')) {
return;
}
if ($cmd == self::MAIL) {
Diaspora::sendMail($target_item, $owner, $contact);
return;
}
if ($cmd == self::SUGGESTION) {
return;
}
if (!$contact['pubkey'] && !$public_message) {
return;
}
if (($target_item['deleted']) && (($target_item['uri'] === $target_item['parent-uri']) || $followup)) {
// top-level retraction
logger('diaspora retract: ' . $loc);
Diaspora::sendRetraction($target_item, $owner, $contact, $public_message);
return;
} elseif ($cmd == self::RELOCATION) {
Diaspora::sendAccountMigration($owner, $contact, $owner['uid']);
return;
} elseif ($followup) {
// send comments and likes to owner to relay
logger('diaspora followup: ' . $loc);
Diaspora::sendFollowup($target_item, $owner, $contact, $public_message);
return;
} elseif ($target_item['uri'] !== $target_item['parent-uri']) {
// we are the relay - send comments, likes and relayable_retractions to our conversants
logger('diaspora relay: ' . $loc);
Diaspora::sendRelay($target_item, $owner, $contact, $public_message);
return;
} elseif ($top_level && !$walltowall) {
// currently no workable solution for sending walltowall
logger('diaspora status: ' . $loc);
Diaspora::sendStatus($target_item, $owner, $contact, $public_message);
return;
}
logger('Unknown mode ' . $cmd . ' for ' . $loc);
}
/**
* @brief Deliver content via mail
*
* @param string $cmd Command
* @param array $contact Contact record of the receiver
* @param array $owner Owner record of the sender
* @param array $target_item Item record of the content
*/
private static function deliverMail($cmd, $contact, $owner, $target_item)
{
if (Config::get('system','dfrn_only')) {
return;
}
// WARNING: does not currently convert to RFC2047 header encodings, etc.
$addr = $contact['addr'];
if (!strlen($addr)) {
return;
}
if (!in_array($cmd, [self::POST, self::COMMENT])) {
return;
}
$local_user = dba::selectFirst('user', [], ['uid' => $owner['uid']]);
if (!DBM::is_result($local_user)) {
return;
}
logger('Deliver ' . $target_item["guid"] . ' via mail to ' . $contact['addr']);
$reply_to = '';
$mailacct = dba::selectFirst('mailacct', ['reply_to'], ['uid' => $owner['uid']]);
if (DBM::is_result($mailacct) && !empty($mailacct['reply_to'])) {
$reply_to = $mailacct['reply_to'];
}
$subject = ($target_item['title'] ? Email::encodeHeader($target_item['title'], 'UTF-8') : L10n::t("\x28no subject\x29"));
// only expose our real email address to true friends
if (($contact['rel'] == CONTACT_IS_FRIEND) && !$contact['blocked']) {
if ($reply_to) {
$headers = 'From: ' . Email::encodeHeader($local_user['username'],'UTF-8') . ' <' . $reply_to.'>' . "\n";
$headers .= 'Sender: ' . $local_user['email'] . "\n";
} else {
$headers = 'From: ' . Email::encodeHeader($local_user['username'],'UTF-8').' <' . $local_user['email'] . '>' . "\n";
}
} else {
$headers = 'From: '. Email::encodeHeader($local_user['username'], 'UTF-8') . ' <noreply@' . self::getApp()->get_hostname() . '>' . "\n";
}
$headers .= 'Message-Id: <' . Email::iri2msgid($target_item['uri']) . '>' . "\n";
if ($target_item['uri'] !== $target_item['parent-uri']) {
$headers .= "References: <" . Email::iri2msgid($target_item["parent-uri"]) . ">";
// If Threading is enabled, write down the correct parent
if (($target_item["thr-parent"] != "") && ($target_item["thr-parent"] != $target_item["parent-uri"])) {
$headers .= " <".Email::iri2msgid($target_item["thr-parent"]).">";
}
$headers .= "\n";
if (empty($target_item['title'])) {
$condition = ['uri' => $target_item['parent-uri'], 'uid' => $owner['uid']];
$title = dba::selectFirst('item', ['title'], $condition);
if (DBM::is_result($title) && ($title['title'] != '')) {
$subject = $title['title'];
} else {
$condition = ['parent-uri' => $target_item['parent-uri'], 'uid' => $owner['uid']];
$title = dba::selectFirst('item', ['title'], $condition);
if (DBM::is_result($title) && ($title['title'] != '')) {
$subject = $title['title'];
}
}
}
if (strncasecmp($subject, 'RE:', 3)) {
$subject = 'Re: ' . $subject;
}
}
return false;
Email::send($addr, $subject, $headers, $target_item);
}
}

View file

@ -68,7 +68,7 @@ class PubSubPublish {
$rr['topic']),
"X-Hub-Signature: sha1=".$hmac_sig];
logger('POST '.print_r($headers, true)."\n".$params, LOGGER_DEBUG);
logger('POST '.print_r($headers, true)."\n".$params, LOGGER_DATA);
Network::post($rr['callback_url'], $params, $headers);
$ret = $a->get_curl_code();

View file

@ -63,7 +63,7 @@ class Queue
return;
}
if (empty($contact['notify'])) {
if (empty($contact['notify']) || $contact['archive']) {
QueueModel::removeItem($q_item['id']);
return;
}