diff --git a/README.translate.md b/README.translate.md index f43406361..728a56ab3 100644 --- a/README.translate.md +++ b/README.translate.md @@ -4,42 +4,32 @@ Friendica translations Translation Process ------------------- -The strings used in the UI of Friendica is translated at [Transifex] [1] and then -included in the git repository at github. If you want to help with translation -for any language, be it correcting terms or translating friendica to a -currently not supported language, please register an account at transifex.com -and contact the friendica translation team there. +The strings used in the UI of Friendica is translated at [Transifex] [1] and then included in the git repository at github. +If you want to help with translation for any language, be it correcting terms or translating friendica to a currently not supported language, please register an account at transifex.com and contact the friendica translation team there. -Translating friendica is simple. Just use the online tool at transifex. If you -don't want to deal with git & co. that is fine, we check the status of the -translations regularly and import them into the source tree at github so that -others can use them. +Translating friendica is simple. +Just use the online tool at transifex. +If you don't want to deal with git & co. that is fine, we check the status of the translations regularly and import them into the source tree at github so that others can use them. -We do not include every translation from transifex in the source tree to avoid -a scattered and disturbed overall experience. As an uneducated guess we have a -lower limit of 50% translated strings before we include the language (for the -core message.po file, addon translation will be included once all strings of -an addon are translated. This limit is judging only by the amount of translated -strings under the assumption that the most prominent strings for the UI will be -translated first by a translation team. If you feel your translation useable -before this limit, please contact us and we will probably include your teams -work in the source tree. +We do not include every translation from transifex in the source tree to avoid a scattered and disturbed overall experience. +As an uneducated guess we have a lower limit of 50% translated strings before we include the language (for the core messages.po file, addont translation will be included once all strings of an addon are translated. +This limit is judging only by the amount of translated strings under the assumption that the most prominent strings for the UI will be translated first by a translation team. +If you feel your translation useable before this limit, please contact us and we will probably include your teams work in the source tree. -If you want to get your work into the source tree yourself, feel free to do so -and contact us with and question that arises. The process is simple and -friendica ships with all the tools necessary. +If you want to help translating, please concentrate on the core messages.po file first. +We will only include translations with a sufficient translated messages.po file. +Translations of addons will only be included, when the core file is included as well. + +If you want to get your work into the source tree yourself, feel free to do so and contact us with and question that arises. +The process is simple and friendica ships with all the tools necessary. The location of the translated files in the source tree is /view/LNG-CODE/ where LNG-CODE is the language code used, e.g. de for German or fr for French. -For the email templates (the *.tpl files) just place them into the directory -and you are done. The translated strings come as a "message.po" file from -transifex which needs to be translated into the PHP file friendica uses. To do -so, place the file in the directory mentioned above and use the "po2php" -utility from the util directory of your friendica installation. +The translated strings come as a "message.po" file from transifex which needs to be translated into the PHP file friendica uses. +To do so, place the file in the directory mentioned above and use the "po2php" utility from the util directory of your friendica installation. -Assuming you want to convert the German localization which is placed in -view/de/message.po you would do the following. +Assuming you want to convert the German localization which is placed in view/de/message.po you would do the following. 1. Navigate at the command prompt to the base directory of your friendica installation @@ -51,11 +41,11 @@ view/de/message.po you would do the following. The output of the script will be placed at view/de/strings.php where friendica is expecting it, so you can test your translation immediately. - + 3. Visit your friendica page to check if it still works in the language you just translated. If not try to find the error, most likely PHP will give you a hint in the log/warnings.about the error. - + For debugging you can also try to "run" the file with PHP. This should not give any output if the file is ok but might give a hint for searching the bug in the file. @@ -69,30 +59,10 @@ view/de/message.po you would do the following. Utilities --------- -Additional to the po2php script there are some more utilities for translation -in the "util" directory of the friendica source tree. If you only want to -translate friendica into another language you won't need any of these tools most -likely but it gives you an idea how the translation process of friendica -works. +Additional to the po2php script there are some more utilities for translation in the "util" directory of the friendica source tree. +If you only want to translate friendica into another language you wont need any of these tools most likely but it gives you an idea how the translation process of friendica works. For further information see the utils/README file. -Known Problems --------------- - -Friendica uses the language setting of the visitors browser to determain the -language for the UI. Most of the time this works, but there are some known -quirks. - -One is that some browsers, like Safari, do the setting to "de-de" but friendica -only has a "de" localisation. A workaround would be to add a symbolic link -from - $friendica/view/de-de -pointing to - $friendica/view/de - -Links ------ - [1]: https://www.transifex.com/projects/p/friendica/ diff --git a/boot.php b/boot.php index 497a97b0e..dd70040c6 100644 --- a/boot.php +++ b/boot.php @@ -17,6 +17,8 @@ * easily as email does today. */ +require_once('include/autoloader.php'); + require_once('include/config.php'); require_once('include/network.php'); require_once('include/plugin.php'); @@ -36,7 +38,7 @@ define ( 'FRIENDICA_PLATFORM', 'Friendica'); define ( 'FRIENDICA_CODENAME', 'Asparagus'); define ( 'FRIENDICA_VERSION', '3.5-dev' ); define ( 'DFRN_PROTOCOL_VERSION', '2.23' ); -define ( 'DB_UPDATE_VERSION', 1191 ); +define ( 'DB_UPDATE_VERSION', 1194 ); /** * @brief Constant with a HTML line break. @@ -467,6 +469,7 @@ class App { public $is_tablet; public $is_friendica_app; public $performance = array(); + public $callstack = array(); public $nav_sel; @@ -529,6 +532,8 @@ class App { private $cached_profile_image; private $cached_profile_picdate; + private static $a; + /** * @brief App constructor. */ @@ -554,6 +559,12 @@ class App { $this->performance["marktime"] = 0; $this->performance["markstart"] = microtime(true); + $this->callstack["database"] = array(); + $this->callstack["network"] = array(); + $this->callstack["file"] = array(); + $this->callstack["rendering"] = array(); + $this->callstack["parser"] = array(); + $this->config = array(); $this->page = array(); $this->pager= array(); @@ -703,6 +714,8 @@ class App { } } + self::$a = $this; + } function get_basepath() { @@ -727,6 +740,10 @@ class App { function get_baseurl($ssl = false) { + // Is the function called statically? + if (!is_object($this)) + return(self::$a->get_baseurl($ssl)); + $scheme = $this->scheme; if((x($this->config,'system')) && (x($this->config['system'],'ssl_policy'))) { @@ -903,6 +920,10 @@ class App { } function get_cached_avatar_image($avatar_image){ + return $avatar_image; + + // The following code is deactivated. It doesn't seem to make any sense and it slows down the system. + /* if($this->cached_profile_image[$avatar_image]) return $this->cached_profile_image[$avatar_image]; @@ -922,6 +943,7 @@ class App { } } return $this->cached_profile_image[$avatar_image]; + */ } @@ -1016,6 +1038,20 @@ class App { $this->performance[$value] += (float)$duration; $this->performance["marktime"] += (float)$duration; + + // Trace the different functions with their timestamps + $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 5); + + array_shift($trace); + + $function = array(); + foreach ($trace AS $func) + $function[] = $func["function"]; + + $function = implode(", ", $function); + + $this->callstack[$value][$function] += (float)$duration; + } function mark_timestamp($mark) { diff --git a/database.sql b/database.sql index e3768c1ef..70b315ea2 100644 --- a/database.sql +++ b/database.sql @@ -1,6 +1,6 @@ -- ------------------------------------------ --- Friendica 3.4.2 (Lily of the valley) --- DB_UPDATE_VERSION 1190 +-- Friendica 3.5-dev (Asparagus) +-- DB_UPDATE_VERSION 1193 -- ------------------------------------------ @@ -8,20 +8,21 @@ -- TABLE addon -- CREATE TABLE IF NOT EXISTS `addon` ( - `id` int(11) NOT NULL auto_increment PRIMARY KEY, + `id` int(11) NOT NULL auto_increment, `name` varchar(255) NOT NULL DEFAULT '', `version` varchar(255) NOT NULL DEFAULT '', `installed` tinyint(1) NOT NULL DEFAULT 0, `hidden` tinyint(1) NOT NULL DEFAULT 0, `timestamp` bigint(20) NOT NULL DEFAULT 0, - `plugin_admin` tinyint(1) NOT NULL DEFAULT 0 + `plugin_admin` tinyint(1) NOT NULL DEFAULT 0, + PRIMARY KEY(`id`) ) DEFAULT CHARSET=utf8; -- -- TABLE attach -- CREATE TABLE IF NOT EXISTS `attach` ( - `id` int(11) NOT NULL auto_increment PRIMARY KEY, + `id` int(11) NOT NULL auto_increment, `uid` int(11) NOT NULL DEFAULT 0, `hash` varchar(64) NOT NULL DEFAULT '', `filename` varchar(255) NOT NULL DEFAULT '', @@ -33,28 +34,31 @@ CREATE TABLE IF NOT EXISTS `attach` ( `allow_cid` mediumtext NOT NULL, `allow_gid` mediumtext NOT NULL, `deny_cid` mediumtext NOT NULL, - `deny_gid` mediumtext NOT NULL + `deny_gid` mediumtext NOT NULL, + PRIMARY KEY(`id`) ) DEFAULT CHARSET=utf8; -- -- TABLE auth_codes -- CREATE TABLE IF NOT EXISTS `auth_codes` ( - `id` varchar(40) NOT NULL PRIMARY KEY, + `id` varchar(40) NOT NULL, `client_id` varchar(20) NOT NULL DEFAULT '', `redirect_uri` varchar(200) NOT NULL DEFAULT '', `expires` int(11) NOT NULL DEFAULT 0, - `scope` varchar(250) NOT NULL DEFAULT '' + `scope` varchar(250) NOT NULL DEFAULT '', + PRIMARY KEY(`id`) ) DEFAULT CHARSET=utf8; -- -- TABLE cache -- CREATE TABLE IF NOT EXISTS `cache` ( - `k` varchar(255) NOT NULL PRIMARY KEY, + `k` varchar(255) NOT NULL, `v` text NOT NULL, `expire_mode` int(11) NOT NULL DEFAULT 0, `updated` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', + PRIMARY KEY(`k`), INDEX `updated` (`updated`) ) DEFAULT CHARSET=utf8; @@ -62,34 +66,37 @@ CREATE TABLE IF NOT EXISTS `cache` ( -- TABLE challenge -- CREATE TABLE IF NOT EXISTS `challenge` ( - `id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY, + `id` int(10) unsigned NOT NULL auto_increment, `challenge` varchar(255) NOT NULL DEFAULT '', `dfrn-id` varchar(255) NOT NULL DEFAULT '', `expire` int(11) NOT NULL DEFAULT 0, `type` varchar(255) NOT NULL DEFAULT '', - `last_update` varchar(255) NOT NULL DEFAULT '' + `last_update` varchar(255) NOT NULL DEFAULT '', + PRIMARY KEY(`id`) ) DEFAULT CHARSET=utf8; -- -- TABLE clients -- CREATE TABLE IF NOT EXISTS `clients` ( - `client_id` varchar(20) NOT NULL PRIMARY KEY, + `client_id` varchar(20) NOT NULL, `pw` varchar(20) NOT NULL DEFAULT '', `redirect_uri` varchar(200) NOT NULL DEFAULT '', `name` text, `icon` text, - `uid` int(11) NOT NULL DEFAULT 0 + `uid` int(11) NOT NULL DEFAULT 0, + PRIMARY KEY(`client_id`) ) DEFAULT CHARSET=utf8; -- -- TABLE config -- CREATE TABLE IF NOT EXISTS `config` ( - `id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY, + `id` int(10) unsigned NOT NULL auto_increment, `cat` varchar(255) NOT NULL DEFAULT '', `k` varchar(255) NOT NULL DEFAULT '', `v` text NOT NULL, + PRIMARY KEY(`id`), INDEX `cat_k` (`cat`(30),`k`(30)) ) DEFAULT CHARSET=utf8; @@ -97,7 +104,7 @@ CREATE TABLE IF NOT EXISTS `config` ( -- TABLE contact -- CREATE TABLE IF NOT EXISTS `contact` ( - `id` int(11) NOT NULL auto_increment PRIMARY KEY, + `id` int(11) NOT NULL auto_increment, `uid` int(11) NOT NULL DEFAULT 0, `created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', `self` tinyint(1) NOT NULL DEFAULT 0, @@ -162,6 +169,7 @@ CREATE TABLE IF NOT EXISTS `contact` ( `notify_new_posts` tinyint(1) NOT NULL DEFAULT 0, `fetch_further_information` tinyint(1) NOT NULL DEFAULT 0, `ffi_keyword_blacklist` mediumtext NOT NULL, + PRIMARY KEY(`id`), INDEX `uid` (`uid`) ) DEFAULT CHARSET=utf8; @@ -169,7 +177,7 @@ CREATE TABLE IF NOT EXISTS `contact` ( -- TABLE conv -- CREATE TABLE IF NOT EXISTS `conv` ( - `id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY, + `id` int(10) unsigned NOT NULL auto_increment, `guid` varchar(64) NOT NULL DEFAULT '', `recips` mediumtext NOT NULL, `uid` int(11) NOT NULL DEFAULT 0, @@ -177,6 +185,7 @@ CREATE TABLE IF NOT EXISTS `conv` ( `created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', `updated` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', `subject` mediumtext NOT NULL, + PRIMARY KEY(`id`), INDEX `uid` (`uid`) ) DEFAULT CHARSET=utf8; @@ -184,27 +193,29 @@ CREATE TABLE IF NOT EXISTS `conv` ( -- TABLE deliverq -- CREATE TABLE IF NOT EXISTS `deliverq` ( - `id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY, + `id` int(10) unsigned NOT NULL auto_increment, `cmd` varchar(32) NOT NULL DEFAULT '', `item` int(11) NOT NULL DEFAULT 0, - `contact` int(11) NOT NULL DEFAULT 0 + `contact` int(11) NOT NULL DEFAULT 0, + PRIMARY KEY(`id`) ) DEFAULT CHARSET=utf8; -- -- TABLE dsprphotoq -- CREATE TABLE IF NOT EXISTS `dsprphotoq` ( - `id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY, + `id` int(10) unsigned NOT NULL auto_increment, `uid` int(11) NOT NULL DEFAULT 0, `msg` mediumtext NOT NULL, - `attempt` tinyint(4) NOT NULL DEFAULT 0 + `attempt` tinyint(4) NOT NULL DEFAULT 0, + PRIMARY KEY(`id`) ) DEFAULT CHARSET=utf8; -- -- TABLE event -- CREATE TABLE IF NOT EXISTS `event` ( - `id` int(11) NOT NULL auto_increment PRIMARY KEY, + `id` int(11) NOT NULL auto_increment, `uid` int(11) NOT NULL DEFAULT 0, `cid` int(11) NOT NULL DEFAULT 0, `uri` varchar(255) NOT NULL DEFAULT '', @@ -223,6 +234,7 @@ CREATE TABLE IF NOT EXISTS `event` ( `allow_gid` mediumtext NOT NULL, `deny_cid` mediumtext NOT NULL, `deny_gid` mediumtext NOT NULL, + PRIMARY KEY(`id`), INDEX `uid` (`uid`) ) DEFAULT CHARSET=utf8; @@ -230,7 +242,7 @@ CREATE TABLE IF NOT EXISTS `event` ( -- TABLE fcontact -- CREATE TABLE IF NOT EXISTS `fcontact` ( - `id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY, + `id` int(10) unsigned NOT NULL auto_increment, `url` varchar(255) NOT NULL DEFAULT '', `name` varchar(255) NOT NULL DEFAULT '', `photo` varchar(255) NOT NULL DEFAULT '', @@ -246,6 +258,7 @@ CREATE TABLE IF NOT EXISTS `fcontact` ( `alias` varchar(255) NOT NULL DEFAULT '', `pubkey` text NOT NULL, `updated` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', + PRIMARY KEY(`id`), INDEX `addr` (`addr`) ) DEFAULT CHARSET=utf8; @@ -253,20 +266,22 @@ CREATE TABLE IF NOT EXISTS `fcontact` ( -- TABLE ffinder -- CREATE TABLE IF NOT EXISTS `ffinder` ( - `id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY, + `id` int(10) unsigned NOT NULL auto_increment, `uid` int(10) unsigned NOT NULL DEFAULT 0, `cid` int(10) unsigned NOT NULL DEFAULT 0, - `fid` int(10) unsigned NOT NULL DEFAULT 0 + `fid` int(10) unsigned NOT NULL DEFAULT 0, + PRIMARY KEY(`id`) ) DEFAULT CHARSET=utf8; -- -- TABLE fserver -- CREATE TABLE IF NOT EXISTS `fserver` ( - `id` int(11) NOT NULL auto_increment PRIMARY KEY, + `id` int(11) NOT NULL auto_increment, `server` varchar(255) NOT NULL DEFAULT '', `posturl` varchar(255) NOT NULL DEFAULT '', `key` text NOT NULL, + PRIMARY KEY(`id`), INDEX `server` (`server`) ) DEFAULT CHARSET=utf8; @@ -274,7 +289,7 @@ CREATE TABLE IF NOT EXISTS `fserver` ( -- TABLE fsuggest -- CREATE TABLE IF NOT EXISTS `fsuggest` ( - `id` int(11) NOT NULL auto_increment PRIMARY KEY, + `id` int(11) NOT NULL auto_increment, `uid` int(11) NOT NULL DEFAULT 0, `cid` int(11) NOT NULL DEFAULT 0, `name` varchar(255) NOT NULL DEFAULT '', @@ -282,16 +297,18 @@ CREATE TABLE IF NOT EXISTS `fsuggest` ( `request` varchar(255) NOT NULL DEFAULT '', `photo` varchar(255) NOT NULL DEFAULT '', `note` text NOT NULL, - `created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00' + `created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', + PRIMARY KEY(`id`) ) DEFAULT CHARSET=utf8; -- -- TABLE gcign -- CREATE TABLE IF NOT EXISTS `gcign` ( - `id` int(11) NOT NULL auto_increment PRIMARY KEY, + `id` int(11) NOT NULL auto_increment, `uid` int(11) NOT NULL DEFAULT 0, `gcid` int(11) NOT NULL DEFAULT 0, + PRIMARY KEY(`id`), INDEX `uid` (`uid`), INDEX `gcid` (`gcid`) ) DEFAULT CHARSET=utf8; @@ -300,7 +317,7 @@ CREATE TABLE IF NOT EXISTS `gcign` ( -- TABLE gcontact -- CREATE TABLE IF NOT EXISTS `gcontact` ( - `id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY, + `id` int(10) unsigned NOT NULL auto_increment, `name` varchar(255) NOT NULL DEFAULT '', `nick` varchar(255) NOT NULL DEFAULT '', `url` varchar(255) NOT NULL DEFAULT '', @@ -315,11 +332,17 @@ CREATE TABLE IF NOT EXISTS `gcontact` ( `about` text NOT NULL, `keywords` text NOT NULL, `gender` varchar(32) NOT NULL DEFAULT '', + `birthday` varchar(32) NOT NULL DEFAULT '0000-00-00', `community` tinyint(1) NOT NULL DEFAULT 0, + `hide` tinyint(1) NOT NULL DEFAULT 0, + `nsfw` tinyint(1) NOT NULL DEFAULT 0, `network` varchar(255) NOT NULL DEFAULT '', `addr` varchar(255) NOT NULL DEFAULT '', + `notify` text NOT NULL, + `alias` varchar(255) NOT NULL DEFAULT '', `generation` tinyint(3) NOT NULL DEFAULT 0, `server_url` varchar(255) NOT NULL DEFAULT '', + PRIMARY KEY(`id`), INDEX `nurl` (`nurl`), INDEX `updated` (`updated`) ) DEFAULT CHARSET=utf8; @@ -328,12 +351,13 @@ CREATE TABLE IF NOT EXISTS `gcontact` ( -- TABLE glink -- CREATE TABLE IF NOT EXISTS `glink` ( - `id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY, + `id` int(10) unsigned NOT NULL auto_increment, `cid` int(11) NOT NULL DEFAULT 0, `uid` int(11) NOT NULL DEFAULT 0, `gcid` int(11) NOT NULL DEFAULT 0, `zcid` int(11) NOT NULL DEFAULT 0, `updated` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', + PRIMARY KEY(`id`), INDEX `cid_uid_gcid_zcid` (`cid`,`uid`,`gcid`,`zcid`), INDEX `gcid` (`gcid`), INDEX `zcid` (`zcid`) @@ -343,11 +367,12 @@ CREATE TABLE IF NOT EXISTS `glink` ( -- TABLE group -- CREATE TABLE IF NOT EXISTS `group` ( - `id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY, + `id` int(10) unsigned NOT NULL auto_increment, `uid` int(10) unsigned NOT NULL DEFAULT 0, `visible` tinyint(1) NOT NULL DEFAULT 0, `deleted` tinyint(1) NOT NULL DEFAULT 0, `name` varchar(255) NOT NULL DEFAULT '', + PRIMARY KEY(`id`), INDEX `uid` (`uid`) ) DEFAULT CHARSET=utf8; @@ -355,10 +380,11 @@ CREATE TABLE IF NOT EXISTS `group` ( -- TABLE group_member -- CREATE TABLE IF NOT EXISTS `group_member` ( - `id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY, + `id` int(10) unsigned NOT NULL auto_increment, `uid` int(10) unsigned NOT NULL DEFAULT 0, `gid` int(10) unsigned NOT NULL DEFAULT 0, `contact-id` int(10) unsigned NOT NULL DEFAULT 0, + PRIMARY KEY(`id`), INDEX `uid_gid_contactid` (`uid`,`gid`,`contact-id`) ) DEFAULT CHARSET=utf8; @@ -366,7 +392,7 @@ CREATE TABLE IF NOT EXISTS `group_member` ( -- TABLE gserver -- CREATE TABLE IF NOT EXISTS `gserver` ( - `id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY, + `id` int(10) unsigned NOT NULL auto_increment, `url` varchar(255) NOT NULL DEFAULT '', `nurl` varchar(255) NOT NULL DEFAULT '', `version` varchar(255) NOT NULL DEFAULT '', @@ -381,6 +407,7 @@ CREATE TABLE IF NOT EXISTS `gserver` ( `last_poco_query` datetime DEFAULT '0000-00-00 00:00:00', `last_contact` datetime DEFAULT '0000-00-00 00:00:00', `last_failure` datetime DEFAULT '0000-00-00 00:00:00', + PRIMARY KEY(`id`), INDEX `nurl` (`nurl`) ) DEFAULT CHARSET=utf8; @@ -388,11 +415,12 @@ CREATE TABLE IF NOT EXISTS `gserver` ( -- TABLE guid -- CREATE TABLE IF NOT EXISTS `guid` ( - `id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY, + `id` int(10) unsigned NOT NULL auto_increment, `guid` varchar(255) NOT NULL DEFAULT '', `plink` varchar(255) NOT NULL DEFAULT '', `uri` varchar(255) NOT NULL DEFAULT '', `network` varchar(32) NOT NULL DEFAULT '', + PRIMARY KEY(`id`), INDEX `guid` (`guid`), INDEX `plink` (`plink`), INDEX `uri` (`uri`) @@ -402,11 +430,12 @@ CREATE TABLE IF NOT EXISTS `guid` ( -- TABLE hook -- CREATE TABLE IF NOT EXISTS `hook` ( - `id` int(11) NOT NULL auto_increment PRIMARY KEY, + `id` int(11) NOT NULL auto_increment, `hook` varchar(255) NOT NULL DEFAULT '', `file` varchar(255) NOT NULL DEFAULT '', `function` varchar(255) NOT NULL DEFAULT '', `priority` int(11) unsigned NOT NULL DEFAULT 0, + PRIMARY KEY(`id`), INDEX `hook_file_function` (`hook`(30),`file`(60),`function`(30)) ) DEFAULT CHARSET=utf8; @@ -414,7 +443,7 @@ CREATE TABLE IF NOT EXISTS `hook` ( -- TABLE intro -- CREATE TABLE IF NOT EXISTS `intro` ( - `id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY, + `id` int(10) unsigned NOT NULL auto_increment, `uid` int(10) unsigned NOT NULL DEFAULT 0, `fid` int(11) NOT NULL DEFAULT 0, `contact-id` int(11) NOT NULL DEFAULT 0, @@ -424,18 +453,20 @@ CREATE TABLE IF NOT EXISTS `intro` ( `hash` varchar(255) NOT NULL DEFAULT '', `datetime` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', `blocked` tinyint(1) NOT NULL DEFAULT 1, - `ignore` tinyint(1) NOT NULL DEFAULT 0 + `ignore` tinyint(1) NOT NULL DEFAULT 0, + PRIMARY KEY(`id`) ) DEFAULT CHARSET=utf8; -- -- TABLE item -- CREATE TABLE IF NOT EXISTS `item` ( - `id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY, + `id` int(10) unsigned NOT NULL auto_increment, `guid` varchar(255) NOT NULL DEFAULT '', `uri` varchar(255) NOT NULL DEFAULT '', `uid` int(10) unsigned NOT NULL DEFAULT 0, `contact-id` int(11) NOT NULL DEFAULT 0, + `gcontact-id` int(11) unsigned NOT NULL DEFAULT 0, `type` varchar(255) NOT NULL DEFAULT '', `wall` tinyint(1) NOT NULL DEFAULT 0, `gravity` tinyint(1) NOT NULL DEFAULT 0, @@ -493,6 +524,7 @@ CREATE TABLE IF NOT EXISTS `item` ( `rendered-hash` varchar(32) NOT NULL DEFAULT '', `rendered-html` mediumtext NOT NULL, `global` tinyint(1) NOT NULL DEFAULT 0, + PRIMARY KEY(`id`), INDEX `guid` (`guid`), INDEX `uri` (`uri`), INDEX `parent` (`parent`), @@ -509,6 +541,7 @@ CREATE TABLE IF NOT EXISTS `item` ( INDEX `uid_thrparent` (`uid`,`thr-parent`), INDEX `uid_parenturi` (`uid`,`parent-uri`), INDEX `uid_contactid_created` (`uid`,`contact-id`,`created`), + INDEX `gcontactid_uid_created` (`gcontact-id`,`uid`,`created`), INDEX `wall_body` (`wall`,`body`(6)), INDEX `uid_visible_moderated_created` (`uid`,`visible`,`moderated`,`created`), INDEX `uid_uri` (`uid`,`uri`), @@ -531,11 +564,12 @@ CREATE TABLE IF NOT EXISTS `item` ( -- TABLE item_id -- CREATE TABLE IF NOT EXISTS `item_id` ( - `id` int(11) NOT NULL auto_increment PRIMARY KEY, + `id` int(11) NOT NULL auto_increment, `iid` int(11) NOT NULL DEFAULT 0, `uid` int(11) NOT NULL DEFAULT 0, `sid` varchar(255) NOT NULL DEFAULT '', `service` varchar(255) NOT NULL DEFAULT '', + PRIMARY KEY(`id`), INDEX `uid` (`uid`), INDEX `sid` (`sid`), INDEX `service` (`service`), @@ -546,17 +580,18 @@ CREATE TABLE IF NOT EXISTS `item_id` ( -- TABLE locks -- CREATE TABLE IF NOT EXISTS `locks` ( - `id` int(11) NOT NULL auto_increment PRIMARY KEY, + `id` int(11) NOT NULL auto_increment, `name` varchar(128) NOT NULL DEFAULT '', `locked` tinyint(1) NOT NULL DEFAULT 0, - `created` datetime DEFAULT '0000-00-00 00:00:00' + `created` datetime DEFAULT '0000-00-00 00:00:00', + PRIMARY KEY(`id`) ) DEFAULT CHARSET=utf8; -- -- TABLE mail -- CREATE TABLE IF NOT EXISTS `mail` ( - `id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY, + `id` int(10) unsigned NOT NULL auto_increment, `uid` int(10) unsigned NOT NULL DEFAULT 0, `guid` varchar(64) NOT NULL DEFAULT '', `from-name` varchar(255) NOT NULL DEFAULT '', @@ -573,6 +608,7 @@ CREATE TABLE IF NOT EXISTS `mail` ( `uri` varchar(255) NOT NULL DEFAULT '', `parent-uri` varchar(255) NOT NULL DEFAULT '', `created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', + PRIMARY KEY(`id`), INDEX `uid` (`uid`), INDEX `guid` (`guid`), INDEX `convid` (`convid`), @@ -585,7 +621,7 @@ CREATE TABLE IF NOT EXISTS `mail` ( -- TABLE mailacct -- CREATE TABLE IF NOT EXISTS `mailacct` ( - `id` int(11) NOT NULL auto_increment PRIMARY KEY, + `id` int(11) NOT NULL auto_increment, `uid` int(11) NOT NULL DEFAULT 0, `server` varchar(255) NOT NULL DEFAULT '', `port` int(11) NOT NULL DEFAULT 0, @@ -597,16 +633,18 @@ CREATE TABLE IF NOT EXISTS `mailacct` ( `action` int(11) NOT NULL DEFAULT 0, `movetofolder` varchar(255) NOT NULL DEFAULT '', `pubmail` tinyint(1) NOT NULL DEFAULT 0, - `last_check` datetime NOT NULL DEFAULT '0000-00-00 00:00:00' + `last_check` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', + PRIMARY KEY(`id`) ) DEFAULT CHARSET=utf8; -- -- TABLE manage -- CREATE TABLE IF NOT EXISTS `manage` ( - `id` int(11) NOT NULL auto_increment PRIMARY KEY, + `id` int(11) NOT NULL auto_increment, `uid` int(11) NOT NULL DEFAULT 0, `mid` int(11) NOT NULL DEFAULT 0, + PRIMARY KEY(`id`), INDEX `uid_mid` (`uid`,`mid`) ) DEFAULT CHARSET=utf8; @@ -614,7 +652,7 @@ CREATE TABLE IF NOT EXISTS `manage` ( -- TABLE notify -- CREATE TABLE IF NOT EXISTS `notify` ( - `id` int(11) NOT NULL auto_increment PRIMARY KEY, + `id` int(11) NOT NULL auto_increment, `hash` varchar(64) NOT NULL DEFAULT '', `type` int(11) NOT NULL DEFAULT 0, `name` varchar(255) NOT NULL DEFAULT '', @@ -629,6 +667,7 @@ CREATE TABLE IF NOT EXISTS `notify` ( `seen` tinyint(1) NOT NULL DEFAULT 0, `verb` varchar(255) NOT NULL DEFAULT '', `otype` varchar(16) NOT NULL DEFAULT '', + PRIMARY KEY(`id`), INDEX `uid` (`uid`) ) DEFAULT CHARSET=utf8; @@ -636,24 +675,50 @@ CREATE TABLE IF NOT EXISTS `notify` ( -- TABLE notify-threads -- CREATE TABLE IF NOT EXISTS `notify-threads` ( - `id` int(11) NOT NULL auto_increment PRIMARY KEY, + `id` int(11) NOT NULL auto_increment, `notify-id` int(11) NOT NULL DEFAULT 0, `master-parent-item` int(10) unsigned NOT NULL DEFAULT 0, `parent-item` int(10) unsigned NOT NULL DEFAULT 0, `receiver-uid` int(11) NOT NULL DEFAULT 0, + PRIMARY KEY(`id`), INDEX `master-parent-item` (`master-parent-item`), INDEX `receiver-uid` (`receiver-uid`) ) DEFAULT CHARSET=utf8; +-- +-- TABLE oembed +-- +CREATE TABLE IF NOT EXISTS `oembed` ( + `url` varchar(255) NOT NULL, + `content` text NOT NULL, + `created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', + PRIMARY KEY(`url`), + INDEX `created` (`created`) +) DEFAULT CHARSET=utf8; + +-- +-- TABLE parsed_url +-- +CREATE TABLE IF NOT EXISTS `parsed_url` ( + `url` varchar(255) NOT NULL, + `guessing` tinyint(1) NOT NULL DEFAULT 0, + `oembed` tinyint(1) NOT NULL DEFAULT 0, + `content` text NOT NULL, + `created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', + PRIMARY KEY(`url`,`guessing`,`oembed`), + INDEX `created` (`created`) +) DEFAULT CHARSET=utf8; + -- -- TABLE pconfig -- CREATE TABLE IF NOT EXISTS `pconfig` ( - `id` int(11) NOT NULL auto_increment PRIMARY KEY, + `id` int(11) NOT NULL auto_increment, `uid` int(11) NOT NULL DEFAULT 0, `cat` varchar(255) NOT NULL DEFAULT '', `k` varchar(255) NOT NULL DEFAULT '', `v` mediumtext NOT NULL, + PRIMARY KEY(`id`), INDEX `uid_cat_k` (`uid`,`cat`(30),`k`(30)) ) DEFAULT CHARSET=utf8; @@ -661,7 +726,7 @@ CREATE TABLE IF NOT EXISTS `pconfig` ( -- TABLE photo -- CREATE TABLE IF NOT EXISTS `photo` ( - `id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY, + `id` int(10) unsigned NOT NULL auto_increment, `uid` int(10) unsigned NOT NULL DEFAULT 0, `contact-id` int(10) unsigned NOT NULL DEFAULT 0, `guid` varchar(64) NOT NULL DEFAULT '', @@ -683,6 +748,7 @@ CREATE TABLE IF NOT EXISTS `photo` ( `allow_gid` mediumtext NOT NULL, `deny_cid` mediumtext NOT NULL, `deny_gid` mediumtext NOT NULL, + PRIMARY KEY(`id`), INDEX `uid` (`uid`), INDEX `resource-id` (`resource-id`), INDEX `guid` (`guid`) @@ -692,7 +758,7 @@ CREATE TABLE IF NOT EXISTS `photo` ( -- TABLE poll -- CREATE TABLE IF NOT EXISTS `poll` ( - `id` int(11) NOT NULL auto_increment PRIMARY KEY, + `id` int(11) NOT NULL auto_increment, `uid` int(11) NOT NULL DEFAULT 0, `q0` mediumtext NOT NULL, `q1` mediumtext NOT NULL, @@ -704,6 +770,7 @@ CREATE TABLE IF NOT EXISTS `poll` ( `q7` mediumtext NOT NULL, `q8` mediumtext NOT NULL, `q9` mediumtext NOT NULL, + PRIMARY KEY(`id`), INDEX `uid` (`uid`) ) DEFAULT CHARSET=utf8; @@ -711,9 +778,10 @@ CREATE TABLE IF NOT EXISTS `poll` ( -- TABLE poll_result -- CREATE TABLE IF NOT EXISTS `poll_result` ( - `id` int(11) NOT NULL auto_increment PRIMARY KEY, + `id` int(11) NOT NULL auto_increment, `poll_id` int(11) NOT NULL DEFAULT 0, `choice` int(11) NOT NULL DEFAULT 0, + PRIMARY KEY(`id`), INDEX `poll_id` (`poll_id`), INDEX `choice` (`choice`) ) DEFAULT CHARSET=utf8; @@ -722,7 +790,7 @@ CREATE TABLE IF NOT EXISTS `poll_result` ( -- TABLE profile -- CREATE TABLE IF NOT EXISTS `profile` ( - `id` int(11) NOT NULL auto_increment PRIMARY KEY, + `id` int(11) NOT NULL auto_increment, `uid` int(11) NOT NULL DEFAULT 0, `profile-name` varchar(255) NOT NULL DEFAULT '', `is-default` tinyint(1) NOT NULL DEFAULT 0, @@ -763,6 +831,7 @@ CREATE TABLE IF NOT EXISTS `profile` ( `thumb` varchar(255) NOT NULL DEFAULT '', `publish` tinyint(1) NOT NULL DEFAULT 0, `net-publish` tinyint(1) NOT NULL DEFAULT 0, + PRIMARY KEY(`id`), INDEX `hometown` (`hometown`) ) DEFAULT CHARSET=utf8; @@ -770,39 +839,42 @@ CREATE TABLE IF NOT EXISTS `profile` ( -- TABLE profile_check -- CREATE TABLE IF NOT EXISTS `profile_check` ( - `id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY, + `id` int(10) unsigned NOT NULL auto_increment, `uid` int(10) unsigned NOT NULL DEFAULT 0, `cid` int(10) unsigned NOT NULL DEFAULT 0, `dfrn_id` varchar(255) NOT NULL DEFAULT '', `sec` varchar(255) NOT NULL DEFAULT '', - `expire` int(11) NOT NULL DEFAULT 0 + `expire` int(11) NOT NULL DEFAULT 0, + PRIMARY KEY(`id`) ) DEFAULT CHARSET=utf8; -- -- TABLE push_subscriber -- CREATE TABLE IF NOT EXISTS `push_subscriber` ( - `id` int(11) NOT NULL auto_increment PRIMARY KEY, + `id` int(11) NOT NULL auto_increment, `uid` int(11) NOT NULL DEFAULT 0, `callback_url` varchar(255) NOT NULL DEFAULT '', `topic` varchar(255) NOT NULL DEFAULT '', `nickname` varchar(255) NOT NULL DEFAULT '', `push` int(11) NOT NULL DEFAULT 0, `last_update` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', - `secret` varchar(255) NOT NULL DEFAULT '' + `secret` varchar(255) NOT NULL DEFAULT '', + PRIMARY KEY(`id`) ) DEFAULT CHARSET=utf8; -- -- TABLE queue -- CREATE TABLE IF NOT EXISTS `queue` ( - `id` int(11) NOT NULL auto_increment PRIMARY KEY, + `id` int(11) NOT NULL auto_increment, `cid` int(11) NOT NULL DEFAULT 0, `network` varchar(32) NOT NULL DEFAULT '', `created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', `last` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', `content` mediumtext NOT NULL, `batch` tinyint(1) NOT NULL DEFAULT 0, + PRIMARY KEY(`id`), INDEX `cid` (`cid`), INDEX `created` (`created`), INDEX `last` (`last`), @@ -814,21 +886,23 @@ CREATE TABLE IF NOT EXISTS `queue` ( -- TABLE register -- CREATE TABLE IF NOT EXISTS `register` ( - `id` int(11) unsigned NOT NULL auto_increment PRIMARY KEY, + `id` int(11) unsigned NOT NULL auto_increment, `hash` varchar(255) NOT NULL DEFAULT '', `created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', `uid` int(11) unsigned NOT NULL DEFAULT 0, `password` varchar(255) NOT NULL DEFAULT '', - `language` varchar(16) NOT NULL DEFAULT '' + `language` varchar(16) NOT NULL DEFAULT '', + PRIMARY KEY(`id`) ) DEFAULT CHARSET=utf8; -- -- TABLE search -- CREATE TABLE IF NOT EXISTS `search` ( - `id` int(11) NOT NULL auto_increment PRIMARY KEY, + `id` int(11) NOT NULL auto_increment, `uid` int(11) NOT NULL DEFAULT 0, `term` varchar(255) NOT NULL DEFAULT '', + PRIMARY KEY(`id`), INDEX `uid` (`uid`), INDEX `term` (`term`) ) DEFAULT CHARSET=utf8; @@ -837,10 +911,11 @@ CREATE TABLE IF NOT EXISTS `search` ( -- TABLE session -- CREATE TABLE IF NOT EXISTS `session` ( - `id` bigint(20) unsigned NOT NULL auto_increment PRIMARY KEY, + `id` bigint(20) unsigned NOT NULL auto_increment, `sid` varchar(255) NOT NULL DEFAULT '', `data` text NOT NULL, `expire` int(10) unsigned NOT NULL DEFAULT 0, + PRIMARY KEY(`id`), INDEX `sid` (`sid`), INDEX `expire` (`expire`) ) DEFAULT CHARSET=utf8; @@ -849,12 +924,13 @@ CREATE TABLE IF NOT EXISTS `session` ( -- TABLE sign -- CREATE TABLE IF NOT EXISTS `sign` ( - `id` int(10) unsigned NOT NULL auto_increment PRIMARY KEY, + `id` int(10) unsigned NOT NULL auto_increment, `iid` int(10) unsigned NOT NULL DEFAULT 0, `retract_iid` int(10) unsigned NOT NULL DEFAULT 0, `signed_text` mediumtext NOT NULL, `signature` text NOT NULL, `signer` varchar(255) NOT NULL DEFAULT '', + PRIMARY KEY(`id`), INDEX `iid` (`iid`), INDEX `retract_iid` (`retract_iid`) ) DEFAULT CHARSET=utf8; @@ -863,12 +939,13 @@ CREATE TABLE IF NOT EXISTS `sign` ( -- TABLE spam -- CREATE TABLE IF NOT EXISTS `spam` ( - `id` int(11) NOT NULL auto_increment PRIMARY KEY, + `id` int(11) NOT NULL auto_increment, `uid` int(11) NOT NULL DEFAULT 0, `spam` int(11) NOT NULL DEFAULT 0, `ham` int(11) NOT NULL DEFAULT 0, `term` varchar(255) NOT NULL DEFAULT '', `date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', + PRIMARY KEY(`id`), INDEX `uid` (`uid`), INDEX `spam` (`spam`), INDEX `ham` (`ham`), @@ -879,7 +956,7 @@ CREATE TABLE IF NOT EXISTS `spam` ( -- TABLE term -- CREATE TABLE IF NOT EXISTS `term` ( - `tid` int(10) unsigned NOT NULL auto_increment PRIMARY KEY, + `tid` int(10) unsigned NOT NULL auto_increment, `oid` int(10) unsigned NOT NULL DEFAULT 0, `otype` tinyint(3) unsigned NOT NULL DEFAULT 0, `type` tinyint(3) unsigned NOT NULL DEFAULT 0, @@ -891,6 +968,7 @@ CREATE TABLE IF NOT EXISTS `term` ( `global` tinyint(1) NOT NULL DEFAULT 0, `aid` int(10) unsigned NOT NULL DEFAULT 0, `uid` int(10) unsigned NOT NULL DEFAULT 0, + PRIMARY KEY(`tid`), INDEX `oid_otype_type_term` (`oid`,`otype`,`type`,`term`), INDEX `uid_term_tid` (`uid`,`term`,`tid`), INDEX `type_term` (`type`,`term`), @@ -903,9 +981,10 @@ CREATE TABLE IF NOT EXISTS `term` ( -- TABLE thread -- CREATE TABLE IF NOT EXISTS `thread` ( - `iid` int(10) unsigned NOT NULL DEFAULT 0 PRIMARY KEY, + `iid` int(10) unsigned NOT NULL DEFAULT 0, `uid` int(10) unsigned NOT NULL DEFAULT 0, `contact-id` int(11) unsigned NOT NULL DEFAULT 0, + `gcontact-id` int(11) unsigned NOT NULL DEFAULT 0, `created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', `edited` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', `commented` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', @@ -926,12 +1005,15 @@ CREATE TABLE IF NOT EXISTS `thread` ( `forum_mode` tinyint(1) NOT NULL DEFAULT 0, `mention` tinyint(1) NOT NULL DEFAULT 0, `network` varchar(32) NOT NULL DEFAULT '', + PRIMARY KEY(`iid`), INDEX `created` (`created`), INDEX `commented` (`commented`), INDEX `uid_network_commented` (`uid`,`network`,`commented`), INDEX `uid_network_created` (`uid`,`network`,`created`), INDEX `uid_contactid_commented` (`uid`,`contact-id`,`commented`), INDEX `uid_contactid_created` (`uid`,`contact-id`,`created`), + INDEX `uid_gcontactid_commented` (`uid`,`gcontact-id`,`commented`), + INDEX `uid_gcontactid_created` (`uid`,`gcontact-id`,`created`), INDEX `wall_private_received` (`wall`,`private`,`received`), INDEX `uid_created` (`uid`,`created`), INDEX `uid_commented` (`uid`,`commented`) @@ -941,33 +1023,20 @@ CREATE TABLE IF NOT EXISTS `thread` ( -- TABLE tokens -- CREATE TABLE IF NOT EXISTS `tokens` ( - `id` varchar(40) NOT NULL PRIMARY KEY, + `id` varchar(40) NOT NULL, `secret` text NOT NULL, `client_id` varchar(20) NOT NULL DEFAULT '', `expires` int(11) NOT NULL DEFAULT 0, `scope` varchar(200) NOT NULL DEFAULT '', - `uid` int(11) NOT NULL DEFAULT 0 -) DEFAULT CHARSET=utf8; - --- --- TABLE unique_contacts --- -CREATE TABLE IF NOT EXISTS `unique_contacts` ( - `id` int(11) NOT NULL auto_increment PRIMARY KEY, - `url` varchar(255) NOT NULL DEFAULT '', - `nick` varchar(255) NOT NULL DEFAULT '', - `name` varchar(255) NOT NULL DEFAULT '', - `avatar` varchar(255) NOT NULL DEFAULT '', - `location` varchar(255) NOT NULL DEFAULT '', - `about` text NOT NULL, - INDEX `url` (`url`) + `uid` int(11) NOT NULL DEFAULT 0, + PRIMARY KEY(`id`) ) DEFAULT CHARSET=utf8; -- -- TABLE user -- CREATE TABLE IF NOT EXISTS `user` ( - `uid` int(11) NOT NULL auto_increment PRIMARY KEY, + `uid` int(11) NOT NULL auto_increment, `guid` varchar(64) NOT NULL DEFAULT '', `username` varchar(255) NOT NULL DEFAULT '', `password` varchar(255) NOT NULL DEFAULT '', @@ -1009,6 +1078,7 @@ CREATE TABLE IF NOT EXISTS `user` ( `deny_cid` mediumtext NOT NULL, `deny_gid` mediumtext NOT NULL, `openidserver` text NOT NULL, + PRIMARY KEY(`uid`), INDEX `nickname` (`nickname`) ) DEFAULT CHARSET=utf8; @@ -1016,8 +1086,9 @@ CREATE TABLE IF NOT EXISTS `user` ( -- TABLE userd -- CREATE TABLE IF NOT EXISTS `userd` ( - `id` int(11) NOT NULL auto_increment PRIMARY KEY, + `id` int(11) NOT NULL auto_increment, `username` varchar(255) NOT NULL, + PRIMARY KEY(`id`), INDEX `username` (`username`) ) DEFAULT CHARSET=utf8; @@ -1025,12 +1096,13 @@ CREATE TABLE IF NOT EXISTS `userd` ( -- TABLE workerqueue -- CREATE TABLE IF NOT EXISTS `workerqueue` ( - `id` int(11) NOT NULL auto_increment PRIMARY KEY, + `id` int(11) NOT NULL auto_increment, `parameter` text NOT NULL, `priority` tinyint(3) unsigned NOT NULL DEFAULT 0, `created` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', `pid` int(11) NOT NULL DEFAULT 0, `executed` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', + PRIMARY KEY(`id`), INDEX `created` (`created`) ) DEFAULT CHARSET=utf8; diff --git a/doc/Forums.md b/doc/Forums.md index 2eac81a72..54e8e3879 100644 --- a/doc/Forums.md +++ b/doc/Forums.md @@ -53,9 +53,12 @@ Posting to Community forums If you are a member of a community forum, you may post to the forum by including an @-tag in the post mentioning the forum. For example @bicycle would send my post to all members of the group "bicycle" in addition to the normal recipients. -If your post is private you must also explicitly include the group in the post permissions (to allow the forum "contact" to see the post) **and** mention it in a tag (which redistributes the post to the forum members). +If you mention a forum (you are a member of) in a new posting, the posting will be distributed to all members of the forum, regardless of your privacy settings for the posting. +Also, if the forum is a public forum, your posting will be public for the all internet users. +If your post is private you must also explicitly include the group in the post permissions (to allow the forum "contact" to see the post) **and** mention it in a tag (which redistributes the post to the forum members). +Posting privately to a public forum, will result in your posting being displayed on the forum wall, but not on yours. -You may also post to a community forum by posting a "wall-to-wall" post using secure cross-site authentication. +You may also post to a community forum by posting a "wall-to-wall" post using secure cross-site authentication. Comments which are relayed to community forums will be relayed back to the original post creator. Mentioning the forum with an @-tag in a comment does not relay the message, as distribution is controlled entirely by the original post creator. diff --git a/doc/Home.md b/doc/Home.md index 3b6442867..1f9b0cfab 100644 --- a/doc/Home.md +++ b/doc/Home.md @@ -47,8 +47,10 @@ Friendica Documentation and Resources * [Theme Development](help/themes) * [Smarty 3 Templates](help/smarty3-templates) * [Database schema documantation](help/database) +* [Class Autoloading](help/autoloader) * [Code - Reference(Doxygen generated - sets cookies)](doc/html/) + **External Resources** * [Main Website](http://friendica.com) diff --git a/doc/Plugins.md b/doc/Plugins.md index 24d403e1f..a30a3f4a7 100644 --- a/doc/Plugins.md +++ b/doc/Plugins.md @@ -1,5 +1,7 @@ Friendica Addon/Plugin development -========================== +============== + +* [Home](help) Please see the sample addon 'randplace' for a working example of using some of these features. Addons work by intercepting event hooks - which must be registered. @@ -16,12 +18,12 @@ Future extensions may provide for "setup" amd "remove". Plugins should contain a comment block with the four following parameters: - /* - * Name: My Great Plugin - * Description: This is what my plugin does. It's really cool - * Version: 1.0 - * Author: John Q. Public - */ + /* + * Name: My Great Plugin + * Description: This is what my plugin does. It's really cool. + * Version: 1.0 + * Author: John Q. Public + */ Register your plugin hooks during installation. @@ -45,7 +47,7 @@ Your hook callback functions will be called with at least one and possibly two a If you wish to make changes to the calling data, you must declare them as reference variables (with '&') during function declaration. -###$a +#### $a $a is the Friendica 'App' class. It contains a wealth of information about the current state of Friendica: @@ -56,13 +58,13 @@ It contains a wealth of information about the current state of Friendica: It is recommeded you call this '$a' to match its usage elsewhere. -###$b +#### $b $b can be called anything you like. This is information specific to the hook currently being processed, and generally contains information that is being immediately processed or acted on that you can use, display, or alter. Remember to declare it with '&' if you wish to alter it. Modules --------- +--- Plugins/addons may also act as "modules" and intercept all page requests for a given URL path. In order for a plugin to act as a module it needs to define a function "plugin_name_module()" which takes no arguments and needs not do anything. @@ -72,15 +74,15 @@ These are parsed into an array $a->argv, with a corresponding $a->argc indicatin So http://my.web.site/plugin/arg1/arg2 would look for a module named "plugin" and pass its module functions the $a App structure (which is available to many components). This will include: - $a->argc = 3 - $a->argv = array(0 => 'plugin', 1 => 'arg1', 2 => 'arg2'); + $a->argc = 3 + $a->argv = array(0 => 'plugin', 1 => 'arg1', 2 => 'arg2'); Your module functions will often contain the function plugin_name_content(&$a), which defines and returns the page body content. They may also contain plugin_name_post(&$a) which is called before the _content function and typically handles the results of POST forms. You may also have plugin_name_init(&$a) which is called very early on and often does module initialisation. Templates ----------- +--- If your plugin needs some template, you can use the Friendica template system. Friendica uses [smarty3](http://www.smarty.net/) as a template engine. @@ -104,140 +106,140 @@ See also the wiki page [Quick Template Guide](https://github.com/friendica/frien Current hooks ------------- -###'authenticate' +### 'authenticate' 'authenticate' is called when a user attempts to login. $b is an array containing: - 'username' => the supplied username - 'password' => the supplied password + 'username' => the supplied username + 'password' => the supplied password 'authenticated' => set this to non-zero to authenticate the user. 'user_record' => successful authentication must also return a valid user record from the database -###'logged_in' +### 'logged_in' 'logged_in' is called after a user has successfully logged in. $b contains the $a->user array. -###'display_item' +### 'display_item' 'display_item' is called when formatting a post for display. $b is an array: 'item' => The item (array) details pulled from the database 'output' => the (string) HTML representation of this item prior to adding it to the page -###'post_local' +### 'post_local' * called when a status post or comment is entered on the local system * $b is the item array of the information to be stored in the database * Please note: body contents are bbcode - not HTML -###'post_local_end' +### 'post_local_end' * called when a local status post or comment has been stored on the local system * $b is the item array of the information which has just been stored in the database * Please note: body contents are bbcode - not HTML -###'post_remote' +### 'post_remote' * called when receiving a post from another source. This may also be used to post local activity or system generated messages. * $b is the item array of information to be stored in the database and the item body is bbcode. -###'settings_form' +### 'settings_form' * called when generating the HTML for the user Settings page * $b is the (string) HTML of the settings page before the final '' tag. -###'settings_post' +### 'settings_post' * called when the Settings pages are submitted * $b is the $_POST array -###'plugin_settings' +### 'plugin_settings' * called when generating the HTML for the addon settings page * $b is the (string) HTML of the addon settings page before the final '' tag. -###'plugin_settings_post' +### 'plugin_settings_post' * called when the Addon Settings pages are submitted * $b is the $_POST array -###'profile_post' +### 'profile_post' * called when posting a profile page * $b is the $_POST array -###'profile_edit' +### 'profile_edit' 'profile_edit' is called prior to output of profile edit page. $b is an array containing: 'profile' => profile (array) record from the database 'entry' => the (string) HTML of the generated entry -###'profile_advanced' +### 'profile_advanced' * called when the HTML is generated for the 'Advanced profile', corresponding to the 'Profile' tab within a person's profile page * $b is the (string) HTML representation of the generated profile * The profile array details are in $a->profile. -###'directory_item' +### 'directory_item' 'directory_item' is called from the Directory page when formatting an item for display. $b is an array: 'contact' => contact (array) record for the person from the database 'entry' => the (string) HTML of the generated entry -###'profile_sidebar_enter' +### 'profile_sidebar_enter' * called prior to generating the sidebar "short" profile for a page * $b is the person's profile array -###'profile_sidebar' +### 'profile_sidebar' 'profile_sidebar is called when generating the sidebar "short" profile for a page. $b is an array: 'profile' => profile (array) record for the person from the database 'entry' => the (string) HTML of the generated entry -###'contact_block_end' +### 'contact_block_end' is called when formatting the block of contacts/friends on a profile sidebar has completed. $b is an array: 'contacts' => array of contacts 'output' => the (string) generated HTML of the contact block -###'bbcode' +### 'bbcode' * called during conversion of bbcode to html * $b is a string converted text -###'html2bbcode' +### 'html2bbcode' * called during conversion of html to bbcode (e.g. remote message posting) * $b is a string converted text -###'page_header' +### 'page_header' * called after building the page navigation section * $b is a string HTML of nav region -###'personal_xrd' +### 'personal_xrd' 'personal_xrd' is called prior to output of personal XRD file. $b is an array: 'user' => the user record for the person 'xml' => the complete XML to be output -###'home_content' +### 'home_content' * called prior to output home page content, shown to unlogged users * $b is (string) HTML of section region -###'contact_edit' +### 'contact_edit' is called when editing contact details on an individual from the Contacts page. $b is an array: 'contact' => contact record (array) of target contact 'output' => the (string) generated HTML of the contact edit page -###'contact_edit_post' +### 'contact_edit_post' * called when posting the contact edit page. * $b is the $_POST array -###'init_1' +### 'init_1' * called just after DB has been opened and before session start * $b is not used or passed -###'page_end' +### 'page_end' * called after HTML content functions have completed * $b is (string) HTML of content div -###'avatar_lookup' +### 'avatar_lookup' 'avatar_lookup' is called when looking up the avatar. $b is an array: @@ -245,11 +247,11 @@ $b is an array: 'email' => email to look up the avatar for 'url' => the (string) generated URL of the avatar -###'emailer_send_prepare' +### 'emailer_send_prepare' 'emailer_send_prepare' called from Emailer::send() before building the mime message. $b is an array, params to Emailer::send() - 'fromName' => name of the sender + 'fromName' => name of the sender 'fromEmail' => email fo the sender 'replyTo' => replyTo address to direct responses 'toEmail' => destination email address @@ -258,20 +260,20 @@ $b is an array, params to Emailer::send() 'textVersion' => text only version of the message 'additionalMailHeader' => additions to the smtp mail header -###'emailer_send' +### 'emailer_send' is called before calling PHP's mail(). $b is an array, params to mail() - 'to' - 'subject' + 'to' + 'subject' 'body' 'headers' -###'nav_info' +### 'nav_info' is called after the navigational menu is build in include/nav.php. $b is an array containing $nav from nav.php. -###'template_vars' +### 'template_vars' is called before vars are passed to the template engine to render the page. The registered function can add,change or remove variables passed to template. $b is an array with: @@ -463,4 +465,3 @@ mod/cb.php: call_hooks('cb_afterpost'); mod/cb.php: call_hooks('cb_content', $o); mod/directory.php: call_hooks('directory_item', $arr); - diff --git a/doc/SSL.md b/doc/SSL.md index a72eec2a1..bcff929fe 100644 --- a/doc/SSL.md +++ b/doc/SSL.md @@ -90,8 +90,8 @@ If you run your own server, upload the files and check out the Mozilla wiki link Let's encrypt --- -If you run your own server and you control your name server, the "Let's encrypt" initiative might become an interesting alternative. -Their offer is not ready, yet. +If you run your own server, the "Let's encrypt" initiative might become an interesting alternative. +Their offer is in public beta right now. Check out [their website](https://letsencrypt.org/) for status updates. Web server settings diff --git a/doc/Settings.md b/doc/Settings.md index ae7d91607..86254cb29 100644 --- a/doc/Settings.md +++ b/doc/Settings.md @@ -24,6 +24,9 @@ You are not required to provide the year. System settings --- +**Settings should be done in the admin panel** (/admin). +Those settings found in the database, will always override the settings added to the ``.htconfig.php`` file. + ###Language Please see util/README for information on creating language translations. @@ -219,6 +222,8 @@ LOGGER_DEBUG will show a good deal of information about system activity but will You may also select LOGGER_ALL but due to the volume of information we recommend only enabling this when you are tracking down a specific problem. Other log levels are possible but are not being used at the present time. +Please be aware that turning on the logging can fill up the disk space on your server relatively quick. +You should take preventions with e.g. [logrotate](https://en.wikipedia.org/wiki/Log_rotation) or similar tools. ###PHP error logging @@ -237,4 +242,6 @@ The vast majority of issues reported at these levels are completely harmless. Please report to the developers any errors you encounter in the logs using the recommended settings above. They generally indicate issues which need to be resolved. -If you encounter a blank (white) page when using the application, view the PHP logs - as this almost always indicates an error has occurred. +If you encounter a blank (white) page when using the application, view the PHP logs - as this almost always indicates an error has occurred. + +*Note*: PHP logging cannot be activated from the admin panel but has to be configured from the ``.htconfig.php`` file. diff --git a/doc/Tags-and-Mentions.md b/doc/Tags-and-Mentions.md index 5501d0d58..eac2c97f8 100644 --- a/doc/Tags-and-Mentions.md +++ b/doc/Tags-and-Mentions.md @@ -21,11 +21,22 @@ You can tag a person on a different network or one that is **not in your social * @mike@macgirvin.com - This is called a "remote mention" and can only be an email-style locator, not a web URL. -Unless their system blocks unsolicited "mentions", the person tagged will likely receive a "Mention" post/activity or become a direct participant in the conversation in the case of public posts. Please note that Friendica blocks incoming "mentions" from people with no relationship to you. This is a spam prevention measure. +Unless their system blocks unsolicited "mentions", the person tagged will likely receive a "Mention" post/activity or become a direct participant in the conversation in the case of public posts. +Friendica blocks incoming “mentions” from people with no relationship to you. +The exception is an ongiong conversation started from a contact of both you and the 3rd person or a conversation in a forum where you are a member of. +This is a spam prevention measure. -Remote mentions are delivered using the OStatus protocol. This protocol is used by Friendica and GNU Social and several other systems, but is not currently implemented in Diaspora. +Remote mentions are delivered using the OStatus protocol. +This protocol is used by Friendica and GNU Social and several other systems, but is not currently implemented in Diaspora. +As the OStatus protocol allows this Friendica user can be @-mentioned by users from platforms using this protocol in conversations if the "Enable OStatus support" is activated on the Friendica node. +These @-mentions wont be blocked, even if there is no relationship between the sender and the receiver of the message. -Friendica makes no distinction between people and groups for the purpose of tagging. (Some other networks use !group to indicate a group.) +Friendica makes no distinction between people and forums for the purpose of tagging. +(Some other networks use !forum to indicate a forum.) + +If you sort your contacts into groups, you cannot @-mention these groups. +But you can select the group in the access control when creating a new posting, to allow (or disallow) a certain group of people to see the posting. +See [Groups and Privacy](help/Groups-and-Privacy) for more details about grouping your contacts. **Topical Tags** diff --git a/doc/api.md b/doc/api.md index ced078f55..c020f403f 100644 --- a/doc/api.md +++ b/doc/api.md @@ -7,6 +7,21 @@ Please refer to the linked documentation for further information. ## Implemented API calls ### General +#### HTTP Method + +API endpoints can restrict the method used to request them. +Using an invalid method results in HTTP error 405 "Method Not Allowed". + +In this document, the required method is listed after the endpoint name. "*" means every method can be used. + +#### Auth + +Friendica supports basic http auth and OAuth 1 to authenticate the user to the api. + +OAuth settings can be added by the user in web UI under /settings/oauth/ + +In this document, endpoints which requires auth are marked with "AUTH" after endpoint name + #### Unsupported parameters * cursor: Not implemented in GNU Social * trim_user: Not implemented in GNU Social @@ -38,9 +53,9 @@ Error body is json: ``` { - "error": "Specific error message", - "request": "API path requested", - "code": "HTTP error code" + "error": "Specific error message", + "request": "API path requested", + "code": "HTTP error code" } ``` @@ -54,19 +69,20 @@ xml: ``` --- -### account/rate_limit_status +### account/rate_limit_status (*; AUTH) --- -### account/verify_credentials +### account/verify_credentials (*; AUTH) #### Parameters + * skip_status: Don't show the "status" field. (Default: false) * include_entities: "true" shows entities for pictures and links (Default: false) --- -### conversation/show +### conversation/show (*; AUTH) Unofficial Twitter command. It shows all direct answers (excluding the original post) to a given id. -#### Parameters +#### Parameter * id: id of the post * count: Items per page (default: 20) * page: page number @@ -80,7 +96,7 @@ Unofficial Twitter command. It shows all direct answers (excluding the original * contributor_details --- -### direct_messages +### direct_messages (*; AUTH) #### Parameters * count: Items per page (default: 20) * page: page number @@ -93,7 +109,7 @@ Unofficial Twitter command. It shows all direct answers (excluding the original * skip_status --- -### direct_messages/all +### direct_messages/all (*; AUTH) #### Parameters * count: Items per page (default: 20) * page: page number @@ -102,7 +118,7 @@ Unofficial Twitter command. It shows all direct answers (excluding the original * getText: Defines the format of the status field. Can be "html" or "plain" --- -### direct_messages/conversation +### direct_messages/conversation (*; AUTH) Shows all direct messages of a conversation #### Parameters * count: Items per page (default: 20) @@ -113,7 +129,7 @@ Shows all direct messages of a conversation * uri: URI of the conversation --- -### direct_messages/new +### direct_messages/new (POST,PUT; AUTH) #### Parameters * user_id: id of the user * screen_name: screen name (for technical reasons, this value is not unique!) @@ -122,7 +138,7 @@ Shows all direct messages of a conversation * title: Title of the direct message --- -### direct_messages/sent +### direct_messages/sent (*; AUTH) #### Parameters * count: Items per page (default: 20) * page: page number @@ -132,7 +148,7 @@ Shows all direct messages of a conversation * include_entities: "true" shows entities for pictures and links (Default: false) --- -### favorites +### favorites (*; AUTH) #### Parameters * count: Items per page (default: 20) * page: page number @@ -144,22 +160,23 @@ Shows all direct messages of a conversation * user_id * screen_name -Favorites aren't displayed to other users, so "user_id" and "screen_name". So setting this value will result in an empty array. +Favorites aren't displayed to other users, so "user_id" and "screen_name" are unsupported. +Set this values will result in an empty array. --- -### favorites/create +### favorites/create (POST,PUT; AUTH) #### Parameters * id * include_entities: "true" shows entities for pictures and links (Default: false) --- -### favorites/destroy +### favorites/destroy (POST,DELETE; AUTH) #### Parameters * id * include_entities: "true" shows entities for pictures and links (Default: false) --- -### followers/ids +### followers/ids (*; AUTH) #### Parameters * stringify_ids: Should the id numbers be sent as text (true) or number (false)? (default: false) @@ -171,139 +188,7 @@ Favorites aren't displayed to other users, so "user_id" and "screen_name". So se Friendica doesn't allow showing followers of other users. --- -### friendica/activity/ -#### parameters -* id: item id - -Add or remove an activity from an item. -'verb' can be one of: -- like -- dislike -- attendyes -- attendno -- attendmaybe - -To remove an activity, prepend the verb with "un", eg. "unlike" or "undislike" -Attend verbs disable eachother: that means that if "attendyes" was added to an item, adding "attendno" remove previous "attendyes". -Attend verbs should be used only with event-related items (there is no check at the moment) - -#### Return values - -On success: -json -```"ok"``` - -xml -```true``` - -On error: -HTTP 400 BadRequest - ---- -### friendica/photo -#### Parameters -* photo_id: Resource id of a photo. -* scale: (optional) scale value of the photo - -Returns data of a picture with the given resource. -If 'scale' isn't provided, returned data include full url to each scale of the photo. -If 'scale' is set, returned data include image data base64 encoded. - -possibile scale value are: -0: original or max size by server settings -1: image with or height at <= 640 -2: image with or height at <= 320 -3: thumbnail 160x160 - -4: Profile image at 175x175 -5: Profile image at 80x80 -6: Profile image at 48x48 - -An image used as profile image has only scale 4-6, other images only 0-3 - -#### Return values - -json -``` - { - "id": "photo id" - "created": "date(YYYY-MM-GG HH:MM:SS)", - "edited": "date(YYYY-MM-GG HH:MM:SS)", - "title": "photo title", - "desc": "photo description", - "album": "album name", - "filename": "original file name", - "type": "mime type", - "height": "number", - "width": "number", - "profile": "1 if is profile photo", - "link": { - "": "url to image" - ... - }, - // if 'scale' is set - "datasize": "size in byte", - "data": "base64 encoded image data" - } -``` - -xml -``` - - photo id - date(YYYY-MM-GG HH:MM:SS) - date(YYYY-MM-GG HH:MM:SS) - photo title - photo description - album name - original file name - mime type - number - number - 1 if is profile photo - - - ... - - -``` - ---- -### friendica/photos/list - -Returns a list of all photo resources of the logged in user. - -#### Return values - -json -``` - [ - { - id: "resource_id", - album: "album name", - filename: "original file name", - type: "image mime type", - thumb: "url to thumb sized image" - }, - ... - ] -``` - -xml -``` - - - "url to thumb sized image" - - ... - -``` - ---- -### friends/ids +### friends/ids (*; AUTH) #### Parameters * stringify_ids: Should the id numbers be sent as text (true) or number (false)? (default: false) @@ -315,15 +200,15 @@ xml Friendica doesn't allow showing friends of other users. --- -### help/test +### help/test (*) --- -### media/upload +### media/upload (POST,PUT; AUTH) #### Parameters * media: image data --- -### oauth/request_token +### oauth/request_token (*) #### Parameters * oauth_callback @@ -331,7 +216,7 @@ Friendica doesn't allow showing friends of other users. * x_auth_access_type --- -### oauth/access_token +### oauth/access_token (*) #### Parameters * oauth_verifier @@ -341,7 +226,7 @@ Friendica doesn't allow showing friends of other users. * x_auth_mode --- -### statuses/destroy +### statuses/destroy (POST,DELETE; AUTH) #### Parameters * id: message number * include_entities: "true" shows entities for pictures and links (Default: false) @@ -350,15 +235,21 @@ Friendica doesn't allow showing friends of other users. * trim_user --- -### statuses/followers +### statuses/followers (*; AUTH) + +#### Parameters + * include_entities: "true" shows entities for pictures and links (Default: false) --- -### statuses/friends +### statuses/friends (*; AUTH) + +#### Parameters + * include_entities: "true" shows entities for pictures and links (Default: false) --- -### statuses/friends_timeline +### statuses/friends_timeline (*; AUTH) #### Parameters * count: Items per page (default: 20) * page: page number @@ -374,7 +265,7 @@ Friendica doesn't allow showing friends of other users. * contributor_details --- -### statuses/home_timeline +### statuses/home_timeline (*; AUTH) #### Parameters * count: Items per page (default: 20) * page: page number @@ -390,7 +281,7 @@ Friendica doesn't allow showing friends of other users. * contributor_details --- -### statuses/mentions +### statuses/mentions (*; AUTH) #### Parameters * count: Items per page (default: 20) * page: page number @@ -404,7 +295,7 @@ Friendica doesn't allow showing friends of other users. * contributor_details --- -### statuses/public_timeline +### statuses/public_timeline (*; AUTH) #### Parameters * count: Items per page (default: 20) * page: page number @@ -418,7 +309,7 @@ Friendica doesn't allow showing friends of other users. * trim_user --- -### statuses/replies +### statuses/replies (*; AUTH) #### Parameters * count: Items per page (default: 20) * page: page number @@ -432,7 +323,7 @@ Friendica doesn't allow showing friends of other users. * contributor_details --- -### statuses/retweet +### statuses/retweet (POST,PUT; AUTH) #### Parameters * id: message number * include_entities: "true" shows entities for pictures and links (Default: false) @@ -441,7 +332,7 @@ Friendica doesn't allow showing friends of other users. * trim_user --- -### statuses/show +### statuses/show (*; AUTH) #### Parameters * id: message number * conversation: if set to "1" show all messages of the conversation with the given id @@ -476,7 +367,7 @@ Friendica doesn't allow showing friends of other users. * display_coordinates --- -### statuses/user_timeline +### statuses/user_timeline (*; AUTH) #### Parameters * user_id: id of the user * screen_name: screen name (for technical reasons, this value is not unique!) @@ -489,15 +380,16 @@ Friendica doesn't allow showing friends of other users. * include_entities: "true" shows entities for pictures and links (Default: false) #### Unsupported parameters + * include_rts * trim_user * contributor_details --- -### statusnet/config +### statusnet/config (*) --- -### statusnet/version +### statusnet/version (*) #### Unsupported parameters * user_id @@ -507,7 +399,7 @@ Friendica doesn't allow showing friends of other users. Friendica doesn't allow showing followers of other users. --- -### users/search +### users/search (*) #### Parameters * q: name of the user @@ -517,7 +409,7 @@ Friendica doesn't allow showing followers of other users. * include_entities --- -### users/show +### users/show (*) #### Parameters * user_id: id of the user * screen_name: screen name (for technical reasons, this value is not unique!) @@ -533,8 +425,39 @@ Friendica doesn't allow showing friends of other users. ## Implemented API calls (not compatible with other APIs) + --- -### friendica/group_show +### friendica/activity/ +#### parameters +* id: item id + +Add or remove an activity from an item. +'verb' can be one of: + +- like +- dislike +- attendyes +- attendno +- attendmaybe + +To remove an activity, prepend the verb with "un", eg. "unlike" or "undislike" +Attend verbs disable eachother: that means that if "attendyes" was added to an item, adding "attendno" remove previous "attendyes". +Attend verbs should be used only with event-related items (there is no check at the moment) + +#### Return values + +On success: +json +```"ok"``` + +xml +```true``` + +On error: +HTTP 400 BadRequest + +--- +### friendica/group_show (*; AUTH) Return all or a specified group of the user with the containing contacts as array. #### Parameters @@ -542,22 +465,23 @@ Return all or a specified group of the user with the containing contacts as arra #### Return values Array of: + * name: name of the group * gid: id of the group * user: array of group members (return from api_get_user() function for each member) --- -### friendica/group_delete +### friendica/group_delete (POST,DELETE; AUTH) delete the specified group of contacts; API call need to include the correct gid AND name of the group to be deleted. ---- -### Parameters +#### Parameters * gid: id of the group to be deleted * name: name of the group to be deleted #### Return values Array of: + * success: true if successfully deleted * gid: gid of the deleted group * name: name of the deleted group @@ -566,19 +490,22 @@ Array of: --- -### friendica/group_create +### friendica/group_create (POST,PUT; AUTH) Create the group with the posted array of contacts as members. + #### Parameters * name: name of the group to be created #### POST data -JSON data as Array like the result of „users/group_show“: +JSON data as Array like the result of "users/group_show": + * gid * name * array of users #### Return values Array of: + * success: true if successfully created or reactivated * gid: gid of the created group * name: name of the created group @@ -587,26 +514,175 @@ Array of: --- -### friendica/group_update +### friendica/group_update (POST) Update the group with the posted array of contacts as members (post all members of the group to the call; function will remove members not posted). + #### Parameters * gid: id of the group to be changed * name: name of the group to be changed #### POST data JSON data as array like the result of „users/group_show“: + * gid * name * array of users #### Return values Array of: + * success: true if successfully updated * gid: gid of the changed group * name: name of the changed group * status: „missing user“ | „ok“ * wrong users: array of users, which were not available in the contact table + + +--- +### friendica/notifications (GET) +Return last 50 notification for current user, ordered by date with unseen item on top + +#### Parameters +none + +#### Return values +Array of: + +* id: id of the note +* type: type of notification as int (see NOTIFY_* constants in boot.php) +* name: full name of the contact subject of the note +* url: contact's profile url +* photo: contact's profile photo +* date: datetime string of the note +* timestamp: timestamp of the node +* date_rel: relative date of the note (eg. "1 hour ago") +* msg: note message in bbcode +* msg_html: note message in html +* msg_plain: note message in plain text +* link: link to note +* seen: seen state: 0 or 1 + + +--- +### friendica/notifications/seen (POST) +Set note as seen, returns item object if possible + +#### Parameters +id: id of the note to set seen + +#### Return values +If the note is linked to an item, the item is returned, just like one of the "statuses/*_timeline" api. + +If the note is not linked to an item, a success status is returned: + +* "success" (json) | "<status>success</status>" (xml) + + +--- +### friendica/photo (*; AUTH) +#### Parameters +* photo_id: Resource id of a photo. +* scale: (optional) scale value of the photo + +Returns data of a picture with the given resource. +If 'scale' isn't provided, returned data include full url to each scale of the photo. +If 'scale' is set, returned data include image data base64 encoded. + +possibile scale value are: + +* 0: original or max size by server settings +* 1: image with or height at <= 640 +* 2: image with or height at <= 320 +* 3: thumbnail 160x160 +* 4: Profile image at 175x175 +* 5: Profile image at 80x80 +* 6: Profile image at 48x48 + +An image used as profile image has only scale 4-6, other images only 0-3 + +#### Return values + +json +``` + { + "id": "photo id" + "created": "date(YYYY-MM-GG HH:MM:SS)", + "edited": "date(YYYY-MM-GG HH:MM:SS)", + "title": "photo title", + "desc": "photo description", + "album": "album name", + "filename": "original file name", + "type": "mime type", + "height": "number", + "width": "number", + "profile": "1 if is profile photo", + "link": { + "": "url to image" + ... + }, + // if 'scale' is set + "datasize": "size in byte", + "data": "base64 encoded image data" + } +``` + +xml +``` + + photo id + date(YYYY-MM-GG HH:MM:SS) + date(YYYY-MM-GG HH:MM:SS) + photo title + photo description + album name + original file name + mime type + number + number + 1 if is profile photo + + + ... + + +``` + +--- +### friendica/photos/list (*; AUTH) + +Returns a list of all photo resources of the logged in user. + +#### Return values + +json +``` + [ + { + id: "resource_id", + album: "album name", + filename: "original file name", + type: "image mime type", + thumb: "url to thumb sized image" + }, + ... + ] +``` + +xml +``` + + + "url to thumb sized image" + + ... + +``` + + --- ## Not Implemented API calls The following API calls are implemented in GNU Social but not in Friendica: (incomplete) @@ -702,13 +778,13 @@ The following API calls from the Twitter API aren't implemented neither in Frien ### BASH / cURL Betamax has documentated some example API usage from a [bash script](https://en.wikipedia.org/wiki/Bash_(Unix_shell) employing [curl](https://en.wikipedia.org/wiki/CURL) (see [his posting](https://betamax65.de/display/betamax65/43539)). - /usr/bin/curl -u USER:PASS https://YOUR.FRIENDICA.TLD/api/statuses/update.xml -d source="some source id" -d status="the status you want to post" +/usr/bin/curl -u USER:PASS https://YOUR.FRIENDICA.TLD/api/statuses/update.xml -d source="some source id" -d status="the status you want to post" ### Python The [RSStoFriedika](https://github.com/pafcu/RSStoFriendika) code can be used as an example of how to use the API with python. The lines for posting are located at [line 21](https://github.com/pafcu/RSStoFriendika/blob/master/RSStoFriendika.py#L21) and following. - def tweet(server, message, group_allow=None): - url = server + '/api/statuses/update' - urllib2.urlopen(url, urllib.urlencode({'status': message,'group_allow[]':group_allow}, doseq=True)) +def tweet(server, message, group_allow=None): +url = server + '/api/statuses/update' +urllib2.urlopen(url, urllib.urlencode({'status': message,'group_allow[]':group_allow}, doseq=True)) There is also a [module for python 3](https://bitbucket.org/tobiasd/python-friendica) for using the API. diff --git a/doc/autoloader.md b/doc/autoloader.md new file mode 100644 index 000000000..947eade23 --- /dev/null +++ b/doc/autoloader.md @@ -0,0 +1,209 @@ +Autoloader +========== + +* [Home](help) + +There is some initial support to class autoloading in Friendica core. + +The autoloader code is in `include/autoloader.php`. +It's derived from composer autoloader code. + +Namespaces and Classes are mapped to folders and files in `library/`, +and the map must be updated by hand, because we don't use composer yet. +The mapping is defined by files in `include/autoloader/` folder. + +Currently, only HTMLPurifier library is loaded using autoloader. + + +## A quick introdution to class autoloading + +The autoloader it's a way for php to automagically include the file that define a class when the class is first used, without the need to use "require_once" every time. + +Once is setup you don't have to use it in any way. You need a class? you use the class. + +At his basic is a function passed to the "spl_autoload_register()" function, which receive as argument the class name the script want and is it job to include the correct php file where that class is defined. +The best source for documentation is [php site](http://php.net/manual/en/language.oop5.autoload.php). + +One example, based on fictional friendica code. + +Let's say you have a php file in "include/" that define a very useful class: + +``` + file: include/ItemsManager.php + array($baseDir."/include"); + ); +``` + + +That tells the autoloader code to look for files that defines classes in "Friendica" namespace under "include/" folder. (And btw, that's why the file has the same name as the class it defines.) + +*note*: The structure of files in "include/autoloader/" has been copied from the code generated by composer, to ease the work of enable autoloader for external libraries under "library/" + +Let's say now that you need to load some items in a view, maybe in a fictional "mod/network.php". +Somewere at the start of the scripts, the autoloader was initialized. In Friendica is done at the top of "boot.php", with "require_once('include/autoloader.php');". + +The code will be something like: + +``` + file: mod/network.php + getAll(); + + // pass $items to template + // return result + } +``` + +That's a quite simple example, but look: no "require()"! +You need to use a class, you use the class and you don't need to do anything more. + +Going further: now we have a bunch of "*Manager" classes that cause some code duplication, let's define a BaseManager class, where to move all code in common between all managers: + +``` + file: include/BaseManager.php + - */ + /* + * Name: My Great Plugin + * Description: This is what my plugin does. It's really cool. + * Version: 1.0 + * Author: John Q. Public + */ Registriere deine Plugin-Hooks während der Installation. @@ -29,45 +30,50 @@ Registriere deine Plugin-Hooks während der Installation. $hookname ist ein String und entspricht einem bekannten Friendica-Hook. -$file steht für den Pfadnamen, der relativ zum Top-Level-Friendicaverzeichnis liegt. +$file steht für den Pfadnamen, der relativ zum Top-Level-Friendicaverzeichnis liegt. Das *sollte* "addon/plugin_name/plugin_name.php' sein. $function ist ein String und der Name der Funktion, die ausgeführt wird, wenn der Hook aufgerufen wird. +Argumente +--- + Deine Hook-Callback-Funktion wird mit mindestens einem und bis zu zwei Argumenten aufgerufen function myhook_function(&$a, &$b) { } -Wenn du Änderungen an den aufgerufenen Daten vornehmen willst, musst du diese als Referenzvariable (mit "&") während der Funktionsdeklaration deklarieren. +Wenn du Änderungen an den aufgerufenen Daten vornehmen willst, musst du diese als Referenzvariable (mit "&") während der Funktionsdeklaration deklarieren. -$a ist die Friendica "App"-Klasse, die eine Menge an Informationen über den aktuellen Friendica-Status beinhaltet, u.a. welche Module genutzt werden, Konfigurationsinformationen, Inhalte der Seite zum Zeitpunkt des Hook-Aufrufs. -Es ist empfohlen, diese Funktion "$a" zu nennen, um seine Nutzung an den Gebrauch an anderer Stelle anzugleichen. +$a ist die Friendica "App"-Klasse, die eine Menge an Informationen über den aktuellen Friendica-Status beinhaltet, u.a. welche Module genutzt werden, Konfigurationsinformationen, Inhalte der Seite zum Zeitpunkt des Hook-Aufrufs. +Es ist empfohlen, diese Funktion "$a" zu nennen, um seine Nutzung an den Gebrauch an anderer Stelle anzugleichen. -$b kann frei benannt werden. -Diese Information ist speziell auf den Hook bezogen, der aktuell bearbeitet wird, und beinhaltet normalerweise Daten, die du sofort nutzen, anzeigen oder bearbeiten kannst. -Achte darauf, diese mit "&" zu deklarieren, wenn du sie bearbeiten willst. +$b kann frei benannt werden. +Diese Information ist speziell auf den Hook bezogen, der aktuell bearbeitet wird, und beinhaltet normalerweise Daten, die du sofort nutzen, anzeigen oder bearbeiten kannst. +Achte darauf, diese mit "&" zu deklarieren, wenn du sie bearbeiten willst. -**Module** +Module +--- -Plugins/Addons können auch als "Module" agieren und alle Seitenanfragen für eine bestimte URL abfangen. -Um ein Plugin als Modul zu nutzen, ist es nötig, die Funktion "plugin_name_module()" zu definieren, die keine Argumente benötigt und nichts weiter machen muss. +Plugins/Addons können auch als "Module" agieren und alle Seitenanfragen für eine bestimte URL abfangen. +Um ein Plugin als Modul zu nutzen, ist es nötig, die Funktion "plugin_name_module()" zu definieren, die keine Argumente benötigt und nichts weiter machen muss. -Wenn diese Funktion existiert, wirst du nun alle Seitenanfragen für "http://my.web.site/plugin_name" erhalten - mit allen URL-Komponenten als zusätzliche Argumente. -Diese werden in ein Array $a->argv geparst und stimmen mit $a->argc überein, wobei sie die Anzahl der URL-Komponenten abbilden. -So würde http://my.web.site/plugin/arg1/arg2 nach einem Modul "plugin" suchen und seiner Modulfunktion die $a-App-Strukur übergeben (dies ist für viele Komponenten verfügbar). Das umfasst: +Wenn diese Funktion existiert, wirst du nun alle Seitenanfragen für "http://example.com/plugin_name" erhalten - mit allen URL-Komponenten als zusätzliche Argumente. +Diese werden in ein Array $a->argv geparst und stimmen mit $a->argc überein, wobei sie die Anzahl der URL-Komponenten abbilden. +So würde http://example.com/plugin/arg1/arg2 nach einem Modul "plugin" suchen und seiner Modulfunktion die $a-App-Strukur übergeben (dies ist für viele Komponenten verfügbar). Das umfasst: - $a->argc = 3 - $a->argv = array(0 => 'plugin', 1 => 'arg1', 2 => 'arg2'); + $a->argc = 3 + $a->argv = array(0 => 'plugin', 1 => 'arg1', 2 => 'arg2'); -Deine Modulfunktionen umfassen oft die Funktion plugin_name_content(&$a), welche den Seiteninhalt definiert und zurückgibt. -Sie können auch plugin_name_post(&$a) umfassen, welches vor der content-Funktion aufgerufen wird und normalerweise die Resultate der POST-Formulare handhabt. +Deine Modulfunktionen umfassen oft die Funktion plugin_name_content(&$a), welche den Seiteninhalt definiert und zurückgibt. +Sie können auch plugin_name_post(&$a) umfassen, welches vor der content-Funktion aufgerufen wird und normalerweise die Resultate der POST-Formulare handhabt. Du kannst ebenso plugin_name_init(&$a) nutzen, was oft frühzeitig aufgerufen wird und das Modul initialisert. -**Derzeitige Hooks:** +Derzeitige Hooks +--- **'authenticate'** - wird aufgerufen, wenn sich der User einloggt. $b ist ein Array @@ -180,6 +186,9 @@ Du kannst ebenso plugin_name_init(&$a) nutzen, was oft frühzeitig aufgerufen wi - wird aufgerufen nachdem in include/nav,php der Inhalt des Navigations Menüs erzeugt wurde. - $b ist ein Array, das $nav wiederspiegelt. +Komplette Liste der Hook-Callbacks +--- + Eine komplette Liste aller Hook-Callbacks mit den zugehörigen Dateien (am 14-Feb-2012 generiert): Bitte schau in die Quellcodes für Details zu Hooks, die oben nicht dokumentiert sind. boot.php: call_hooks('login_hook',$o); @@ -204,7 +213,7 @@ include/text.php: call_hooks('contact_block_end', $arr); include/text.php: call_hooks('smilie', $s); -include/text.php: call_hooks('prepare_body_init', $item); +include/text.php: call_hooks('prepare_body_init', $item); include/text.php: call_hooks('prepare_body', $prep_arr); @@ -359,4 +368,3 @@ mod/cb.php: call_hooks('cb_afterpost'); mod/cb.php: call_hooks('cb_content', $o); mod/directory.php: call_hooks('directory_item', $arr); - diff --git a/doc/htconfig.md b/doc/htconfig.md index 4764c287c..f9c92bfa0 100644 --- a/doc/htconfig.md +++ b/doc/htconfig.md @@ -34,6 +34,7 @@ line to your .htconfig.php: * like_no_comment (Boolean) - Don't update the "commented" value of an item when it is liked. * local_block (Boolean) - Used in conjunction with "block_public". * local_search (Boolean) - Blocks the search for not logged in users to prevent crawlers from blocking your system. +* max_connections - The poller process isn't started when 3/4 of the possible database connections are used. When the system can't detect the maximum numbers of connection then this value can be used. * max_contact_queue - Default value is 500. * max_batch_queue - Default value is 1000. * no_oembed (Boolean) - Don't use OEmbed to fetch more information about a link. diff --git a/doc/snarty3-templates.md b/doc/smarty3-templates.md similarity index 100% rename from doc/snarty3-templates.md rename to doc/smarty3-templates.md diff --git a/doc/translations.md b/doc/translations.md index b874e9ea2..728a56ab3 100644 --- a/doc/translations.md +++ b/doc/translations.md @@ -4,42 +4,32 @@ Friendica translations Translation Process ------------------- -The strings used in the UI of Friendica is translated at [Transifex] [1] and then -included in the git repository at github. If you want to help with translation -for any language, be it correcting terms or translating friendica to a -currently not supported language, please register an account at transifex.com -and contact the friendica translation team there. +The strings used in the UI of Friendica is translated at [Transifex] [1] and then included in the git repository at github. +If you want to help with translation for any language, be it correcting terms or translating friendica to a currently not supported language, please register an account at transifex.com and contact the friendica translation team there. -Translating friendica is simple. Just use the online tool at transifex. If you -don't want to deal with git & co. that is fine, we check the status of the -translations regularly and import them into the source tree at github so that -others can use them. +Translating friendica is simple. +Just use the online tool at transifex. +If you don't want to deal with git & co. that is fine, we check the status of the translations regularly and import them into the source tree at github so that others can use them. -We do not include every translation from transifex in the source tree to avoid -a scattered and disturbed overall experience. As an uneducated guess we have a -lower limit of 50% translated strings before we include the language (for the -core message.po file, addont translation will be included once all strings of -an addon are translated. This limit is judging only by the amount of translated -strings under the assumption that the most prominent strings for the UI will be -translated first by a translation team. If you feel your translation useable -before this limit, please contact us and we will probably include your teams -work in the source tree. +We do not include every translation from transifex in the source tree to avoid a scattered and disturbed overall experience. +As an uneducated guess we have a lower limit of 50% translated strings before we include the language (for the core messages.po file, addont translation will be included once all strings of an addon are translated. +This limit is judging only by the amount of translated strings under the assumption that the most prominent strings for the UI will be translated first by a translation team. +If you feel your translation useable before this limit, please contact us and we will probably include your teams work in the source tree. -If you want to get your work into the source tree yourself, feel free to do so -and contact us with and question that arises. The process is simple and -friendica ships with all the tools necessary. +If you want to help translating, please concentrate on the core messages.po file first. +We will only include translations with a sufficient translated messages.po file. +Translations of addons will only be included, when the core file is included as well. + +If you want to get your work into the source tree yourself, feel free to do so and contact us with and question that arises. +The process is simple and friendica ships with all the tools necessary. The location of the translated files in the source tree is /view/LNG-CODE/ where LNG-CODE is the language code used, e.g. de for German or fr for French. -For the email templates (the *.tpl files) just place them into the directory -and you are done. The translated strings come as a "message.po" file from -transifex which needs to be translated into the PHP file friendica uses. To do -so, place the file in the directory mentioned above and use the "po2php" -utility from the util directory of your friendica installation. +The translated strings come as a "message.po" file from transifex which needs to be translated into the PHP file friendica uses. +To do so, place the file in the directory mentioned above and use the "po2php" utility from the util directory of your friendica installation. -Assuming you want to convert the German localization which is placed in -view/de/message.po you would do the following. +Assuming you want to convert the German localization which is placed in view/de/message.po you would do the following. 1. Navigate at the command prompt to the base directory of your friendica installation @@ -47,15 +37,15 @@ view/de/message.po you would do the following. 2. Execute the po2php script, which will place the translation in the strings.php file that is used by friendica. - $> php util/po2php.php view/de/message.po + $> php util/po2php.php view/de/messages.po The output of the script will be placed at view/de/strings.php where - froemdoca os expecting it, so you can test your translation mmediately. - + friendica is expecting it, so you can test your translation immediately. + 3. Visit your friendica page to check if it still works in the language you just translated. If not try to find the error, most likely PHP will give you a hint in the log/warnings.about the error. - + For debugging you can also try to "run" the file with PHP. This should not give any output if the file is ok but might give a hint for searching the bug in the file. @@ -69,27 +59,10 @@ view/de/message.po you would do the following. Utilities --------- -Additional to the po2php script there are some more utilities for translation -in the "util" directory of the friendica source tree. If you only want to -translate friendica into another language you wont need any of these tools most -likely but it gives you an idea how the translation process of friendica -works. +Additional to the po2php script there are some more utilities for translation in the "util" directory of the friendica source tree. +If you only want to translate friendica into another language you wont need any of these tools most likely but it gives you an idea how the translation process of friendica works. For further information see the utils/README file. -Known Problems --------------- - -Friendica uses the language setting of the visitors browser to determain the -language for the UI. Most of the time this works, but there are some known -quirks. - -One is that some browsers, like Safari, do the setting to "de-de" but friendica -only has a "de" localisation. A workaround would be to add a symbolic link -from - $friendica/view/de-de -pointing to - $friendica/view/de - [1]: https://www.transifex.com/projects/p/friendica/ diff --git a/friendica_test_data.sql b/friendica_test_data.sql index c39a05765..45080e44d 100644 --- a/friendica_test_data.sql +++ b/friendica_test_data.sql @@ -1657,35 +1657,6 @@ LOCK TABLES `tokens` WRITE; /*!40000 ALTER TABLE `tokens` ENABLE KEYS */; UNLOCK TABLES; --- --- Table structure for table `unique_contacts` --- - -DROP TABLE IF EXISTS `unique_contacts`; -/*!40101 SET @saved_cs_client = @@character_set_client */; -/*!40101 SET character_set_client = utf8 */; -CREATE TABLE `unique_contacts` ( - `id` int(11) NOT NULL AUTO_INCREMENT, - `url` varchar(255) NOT NULL DEFAULT '', - `nick` varchar(255) NOT NULL DEFAULT '', - `name` varchar(255) NOT NULL DEFAULT '', - `avatar` varchar(255) NOT NULL DEFAULT '', - `location` varchar(255) NOT NULL DEFAULT '', - `about` text NOT NULL, - PRIMARY KEY (`id`), - KEY `url` (`url`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8; -/*!40101 SET character_set_client = @saved_cs_client */; - --- --- Dumping data for table `unique_contacts` --- - -LOCK TABLES `unique_contacts` WRITE; -/*!40000 ALTER TABLE `unique_contacts` DISABLE KEYS */; -/*!40000 ALTER TABLE `unique_contacts` ENABLE KEYS */; -UNLOCK TABLES; - -- -- Table structure for table `user` -- diff --git a/htconfig.php b/htconfig.php index 508de9a32..fe6c0d82e 100644 --- a/htconfig.php +++ b/htconfig.php @@ -57,7 +57,7 @@ $a->config['system']['huburl'] = '[internal]'; // allowed themes (change this from admin panel after installation) -$a->config['system']['allowed_themes'] = 'dispy,quattro,vier,darkzero,duepuntozero,greenzero,purplezero,slackr,diabook'; +$a->config['system']['allowed_themes'] = 'quattro,vier,duepuntozero'; // default system theme diff --git a/include/Contact.php b/include/Contact.php index a3cbbfed1..3799e0b18 100644 --- a/include/Contact.php +++ b/include/Contact.php @@ -132,8 +132,8 @@ function terminate_friendship($user,$self,$contact) { diaspora_unshare($user,$contact); } elseif($contact['network'] === NETWORK_DFRN) { - require_once('include/items.php'); - dfrn_deliver($user,$contact,'placeholder', 1); + require_once('include/dfrn.php'); + dfrn::deliver($user,$contact,'placeholder', 1); } } @@ -205,60 +205,49 @@ function get_contact_details_by_url($url, $uid = -1) { if ((($profile["addr"] == "") OR ($profile["name"] == "")) AND in_array($profile["network"], array(NETWORK_DFRN, NETWORK_DIASPORA, NETWORK_OSTATUS))) proc_run('php',"include/update_gcontact.php", $profile["gid"]); - - } else { - $r = q("SELECT `url`, `name`, `nick`, `avatar` AS `photo`, `location`, `about` FROM `unique_contacts` WHERE `url` = '%s'", - dbesc(normalise_link($url))); - - if (count($r)) { - $profile = $r[0]; - $profile["keywords"] = ""; - $profile["gender"] = ""; - $profile["community"] = false; - $profile["network"] = ""; - $profile["addr"] = ""; - } } // Fetching further contact data from the contact table - $r = q("SELECT `id`, `uid`, `url`, `network`, `name`, `nick`, `addr`, `location`, `about`, `keywords`, `gender`, `photo`, `addr`, `forum`, `prv`, `bd` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d AND `network` = '%s'", + $r = q("SELECT `id`, `uid`, `url`, `network`, `name`, `nick`, `addr`, `location`, `about`, `keywords`, `gender`, `photo`, `thumb`, `addr`, `forum`, `prv`, `bd`, `self` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d AND `network` IN ('%s', '')", dbesc(normalise_link($url)), intval($uid), dbesc($profile["network"])); - if (!count($r)) - $r = q("SELECT `id`, `uid`, `url`, `network`, `name`, `nick`, `addr`, `location`, `about`, `keywords`, `gender`, `photo`, `addr`, `forum`, `prv`, `bd` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d", + if (!count($r) AND !isset($profile)) + $r = q("SELECT `id`, `uid`, `url`, `network`, `name`, `nick`, `addr`, `location`, `about`, `keywords`, `gender`, `photo`, `thumb`, `addr`, `forum`, `prv`, `bd`, `self` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d", dbesc(normalise_link($url)), intval($uid)); - if (!count($r)) - $r = q("SELECT `id`, `uid`, `url`, `network`, `name`, `nick`, `addr`, `location`, `about`, `keywords`, `gender`, `photo`, `addr`, `forum`, `prv`, `bd` FROM `contact` WHERE `nurl` = '%s' AND `uid` = 0", + if (!count($r) AND !isset($profile)) + $r = q("SELECT `id`, `uid`, `url`, `network`, `name`, `nick`, `addr`, `location`, `about`, `keywords`, `gender`, `photo`, `thumb`, `addr`, `forum`, `prv`, `bd` FROM `contact` WHERE `nurl` = '%s' AND `uid` = 0", dbesc(normalise_link($url))); if ($r) { - if (isset($r[0]["url"]) AND $r[0]["url"]) + if (!isset($profile["url"]) AND $r[0]["url"]) $profile["url"] = $r[0]["url"]; - if (isset($r[0]["name"]) AND $r[0]["name"]) + if (!isset($profile["name"]) AND $r[0]["name"]) $profile["name"] = $r[0]["name"]; - if (isset($r[0]["nick"]) AND $r[0]["nick"] AND ($profile["nick"] == "")) + if (!isset($profile["nick"]) AND $r[0]["nick"]) $profile["nick"] = $r[0]["nick"]; - if (isset($r[0]["addr"]) AND $r[0]["addr"] AND ($profile["addr"] == "")) + if (!isset($profile["addr"]) AND $r[0]["addr"]) $profile["addr"] = $r[0]["addr"]; - if (isset($r[0]["photo"]) AND $r[0]["photo"]) + if ((!isset($profile["photo"]) OR $r[0]["self"]) AND $r[0]["photo"]) $profile["photo"] = $r[0]["photo"]; - if (isset($r[0]["location"]) AND $r[0]["location"]) + if (!isset($profile["location"]) AND $r[0]["location"]) $profile["location"] = $r[0]["location"]; - if (isset($r[0]["about"]) AND $r[0]["about"]) + if (!isset($profile["about"]) AND $r[0]["about"]) $profile["about"] = $r[0]["about"]; - if (isset($r[0]["keywords"]) AND $r[0]["keywords"]) + if (!isset($profile["keywords"]) AND $r[0]["keywords"]) $profile["keywords"] = $r[0]["keywords"]; - if (isset($r[0]["gender"]) AND $r[0]["gender"]) + if (!isset($profile["gender"]) AND $r[0]["gender"]) $profile["gender"] = $r[0]["gender"]; if (isset($r[0]["forum"]) OR isset($r[0]["prv"])) $profile["community"] = ($r[0]["forum"] OR $r[0]["prv"]); - if (isset($r[0]["network"]) AND $r[0]["network"]) + if (!isset($profile["network"]) AND $r[0]["network"]) $profile["network"] = $r[0]["network"]; - if (isset($r[0]["addr"]) AND $r[0]["addr"]) + if (!isset($profile["addr"]) AND $r[0]["addr"]) $profile["addr"] = $r[0]["addr"]; - if (isset($r[0]["bd"]) AND $r[0]["bd"]) + if (!isset($profile["bd"]) AND $r[0]["bd"]) $profile["bd"] = $r[0]["bd"]; + if (isset($r[0]["thumb"])) + $profile["thumb"] = $r[0]["thumb"]; if ($r[0]["uid"] == 0) $profile["cid"] = 0; else @@ -415,6 +404,7 @@ function get_contact($url, $uid = 0) { $contactid = 0; // is it an address in the format user@server.tld? + /// @todo use gcontact and/or the addr field for a lookup if (!strstr($url, "http") OR strstr($url, "@")) { $data = probe_url($url); $url = $data["url"]; @@ -422,12 +412,12 @@ function get_contact($url, $uid = 0) { return 0; } - $contact = q("SELECT `id`, `avatar-date` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d", + $contact = q("SELECT `id`, `avatar-date` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d ORDER BY `id` LIMIT 2", dbesc(normalise_link($url)), intval($uid)); if (!$contact) - $contact = q("SELECT `id`, `avatar-date` FROM `contact` WHERE `alias` IN ('%s', '%s') AND `uid` = %d", + $contact = q("SELECT `id`, `avatar-date` FROM `contact` WHERE `alias` IN ('%s', '%s') AND `uid` = %d ORDER BY `id` LIMIT 1", dbesc($url), dbesc(normalise_link($url)), intval($uid)); @@ -451,9 +441,7 @@ function get_contact($url, $uid = 0) { if (!in_array($data["network"], array(NETWORK_DFRN, NETWORK_OSTATUS, NETWORK_DIASPORA))) return 0; - // tempory programming. Can be deleted after 2015-02-07 - if (($data["alias"] == "") AND (normalise_link($data["url"]) != normalise_link($url))) - $data["alias"] = normalise_link($url); + $url = $data["url"]; if ($contactid == 0) { q("INSERT INTO `contact` (`uid`, `created`, `url`, `nurl`, `addr`, `alias`, `notify`, `poll`, @@ -482,7 +470,7 @@ function get_contact($url, $uid = 0) { dbesc($data["poco"]) ); - $contact = q("SELECT `id` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d", + $contact = q("SELECT `id` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d ORDER BY `id` LIMIT 2", dbesc(normalise_link($data["url"])), intval($uid)); if (!$contact) @@ -491,25 +479,217 @@ function get_contact($url, $uid = 0) { $contactid = $contact[0]["id"]; } + if ((count($contact) > 1) AND ($uid == 0) AND ($contactid != 0) AND ($url != "")) + q("DELETE FROM `contact` WHERE `nurl` = '%s' AND `id` != %d", + dbesc(normalise_link($url)), + intval($contactid)); + require_once("Photo.php"); - $photos = import_profile_photo($data["photo"],$uid,$contactid); + update_contact_avatar($data["photo"],$uid,$contactid); - q("UPDATE `contact` SET `photo` = '%s', `thumb` = '%s', `micro` = '%s', - `addr` = '%s', `alias` = '%s', `name` = '%s', `nick` = '%s', - `name-date` = '%s', `uri-date` = '%s', `avatar-date` = '%s' WHERE `id` = %d", - dbesc($photos[0]), - dbesc($photos[1]), - dbesc($photos[2]), + q("UPDATE `contact` SET `addr` = '%s', `alias` = '%s', `name` = '%s', `nick` = '%s', + `name-date` = '%s', `uri-date` = '%s' WHERE `id` = %d", dbesc($data["addr"]), dbesc($data["alias"]), dbesc($data["name"]), dbesc($data["nick"]), dbesc(datetime_convert()), dbesc(datetime_convert()), - dbesc(datetime_convert()), intval($contactid) ); return $contactid; } + +/** + * @brief Returns posts from a given gcontact + * + * @param App $a argv application class + * @param int $gcontact_id Global contact + * + * @return string posts in HTML + */ +function posts_from_gcontact($a, $gcontact_id) { + + require_once('include/conversation.php'); + + // There are no posts with "uid = 0" with connector networks + // This speeds up the query a lot + $r = q("SELECT `network` FROM `gcontact` WHERE `id` = %d", dbesc($gcontact_id)); + if (in_array($r[0]["network"], array(NETWORK_DFRN, NETWORK_DIASPORA, NETWORK_OSTATUS, ""))) + $sql = "(`item`.`uid` = 0 OR (`item`.`uid` = %d AND `item`.`private`))"; + else + $sql = "`item`.`uid` = %d"; + + if(get_config('system', 'old_pager')) { + $r = q("SELECT COUNT(*) AS `total` FROM `item` + WHERE `gcontact-id` = %d and $sql", + intval($gcontact_id), + intval(local_user())); + + $a->set_pager_total($r[0]['total']); + } + + $r = q("SELECT `item`.`uri`, `item`.*, `item`.`id` AS `item_id`, + `author-name` AS `name`, `owner-avatar` AS `photo`, + `owner-link` AS `url`, `owner-avatar` AS `thumb` + FROM `item` FORCE INDEX (`gcontactid_uid_created`) + WHERE `gcontact-id` = %d AND $sql AND + NOT `deleted` AND NOT `moderated` AND `visible` + ORDER BY `item`.`created` DESC LIMIT %d, %d", + intval($gcontact_id), + intval(local_user()), + intval($a->pager['start']), + intval($a->pager['itemspage']) + ); + + $o = conversation($a,$r,'community',false); + + if(!get_config('system', 'old_pager')) { + $o .= alt_pager($a,count($r)); + } else { + $o .= paginate($a); + } + + return $o; +} + +/** + * @brief set the gcontact-id in all item entries + * + * This job has to be started multiple times until all entries are set. + * It isn't started in the update function since it would consume too much time and can be done in the background. + */ +function item_set_gcontact() { + define ('POST_UPDATE_VERSION', 1192); + + // Was the script completed? + if (get_config("system", "post_update_version") >= POST_UPDATE_VERSION) + return; + + // Check if the first step is done (Setting "gcontact-id" in the item table) + $r = q("SELECT `author-link`, `author-name`, `author-avatar`, `uid`, `network` FROM `item` WHERE `gcontact-id` = 0 LIMIT 1000"); + if (!$r) { + // Are there unfinished entries in the thread table? + $r = q("SELECT COUNT(*) AS `total` FROM `thread` + INNER JOIN `item` ON `item`.`id` =`thread`.`iid` + WHERE `thread`.`gcontact-id` = 0 AND + (`thread`.`uid` IN (SELECT `uid` from `user`) OR `thread`.`uid` = 0)"); + + if ($r AND ($r[0]["total"] == 0)) { + set_config("system", "post_update_version", POST_UPDATE_VERSION); + return false; + } + + // Update the thread table from the item table + q("UPDATE `thread` INNER JOIN `item` ON `item`.`id`=`thread`.`iid` + SET `thread`.`gcontact-id` = `item`.`gcontact-id` + WHERE `thread`.`gcontact-id` = 0 AND + (`thread`.`uid` IN (SELECT `uid` from `user`) OR `thread`.`uid` = 0)"); + + return false; + } + + $item_arr = array(); + foreach ($r AS $item) { + $index = $item["author-link"]."-".$item["uid"]; + $item_arr[$index] = array("author-link" => $item["author-link"], + "uid" => $item["uid"], + "network" => $item["network"]); + } + + // Set the "gcontact-id" in the item table and add a new gcontact entry if needed + foreach($item_arr AS $item) { + $gcontact_id = get_gcontact_id(array("url" => $item['author-link'], "network" => $item['network'], + "photo" => $item['author-avatar'], "name" => $item['author-name'])); + q("UPDATE `item` SET `gcontact-id` = %d WHERE `uid` = %d AND `author-link` = '%s' AND `gcontact-id` = 0", + intval($gcontact_id), intval($item["uid"]), dbesc($item["author-link"])); + } + return true; +} + +/** + * @brief Returns posts from a given contact + * + * @param App $a argv application class + * @param int $contact_id contact + * + * @return string posts in HTML + */ +function posts_from_contact($a, $contact_id) { + + require_once('include/conversation.php'); + + $r = q("SELECT `url` FROM `contact` WHERE `id` = %d", intval($contact_id)); + if (!$r) + return false; + + $contact = $r[0]; + + if(get_config('system', 'old_pager')) { + $r = q("SELECT COUNT(*) AS `total` FROM `item` + WHERE `item`.`uid` = %d AND `author-link` IN ('%s', '%s')", + intval(local_user()), + dbesc(str_replace("https://", "http://", $contact["url"])), + dbesc(str_replace("http://", "https://", $contact["url"]))); + + $a->set_pager_total($r[0]['total']); + } + + $r = q("SELECT `item`.`uri`, `item`.*, `item`.`id` AS `item_id`, + `author-name` AS `name`, `owner-avatar` AS `photo`, + `owner-link` AS `url`, `owner-avatar` AS `thumb` + FROM `item` FORCE INDEX (`uid_contactid_created`) + WHERE `item`.`uid` = %d AND `contact-id` = %d + AND `author-link` IN ('%s', '%s') + AND NOT `deleted` AND NOT `moderated` AND `visible` + ORDER BY `item`.`created` DESC LIMIT %d, %d", + intval(local_user()), + intval($contact_id), + dbesc(str_replace("https://", "http://", $contact["url"])), + dbesc(str_replace("http://", "https://", $contact["url"])), + intval($a->pager['start']), + intval($a->pager['itemspage']) + ); + + $o .= conversation($a,$r,'community',false); + + if(!get_config('system', 'old_pager')) + $o .= alt_pager($a,count($r)); + else + $o .= paginate($a); + + return $o; +} + +/** + * @brief Returns a formatted location string from the given profile array + * + * @param array $profile Profile array (Generated from the "profile" table) + * + * @return string Location string + */ +function formatted_location($profile) { + $location = ''; + + if($profile['locality']) + $location .= $profile['locality']; + + if($profile['region'] AND ($profile['locality'] != $profile['region'])) { + if($location) + $location .= ', '; + + $location .= $profile['region']; + } + + if($profile['country-name']) { + if($location) + $location .= ', '; + + $location .= $profile['country-name']; + } + + return $location; +} +?> diff --git a/include/ForumManager.php b/include/ForumManager.php new file mode 100644 index 000000000..49417d183 --- /dev/null +++ b/include/ForumManager.php @@ -0,0 +1,190 @@ + forum url + * 'name' => forum name + * 'id' => number of the key from the array + * 'micro' => contact photo in format micro + */ + public static function get_list($uid, $showhidden = true, $lastitem, $showprivate = false) { + + $forumlist = array(); + + $order = (($showhidden) ? '' : ' AND NOT `hidden` '); + $order .= (($lastitem) ? ' ORDER BY `last-item` DESC ' : ' ORDER BY `name` ASC '); + $select = '`forum` '; + if ($showprivate) { + $select = '(`forum` OR `prv`)'; + } + + $contacts = q("SELECT `contact`.`id`, `contact`.`url`, `contact`.`name`, `contact`.`micro` FROM `contact` + WHERE `network`= 'dfrn' AND $select AND `uid` = %d + AND NOT `blocked` AND NOT `hidden` AND NOT `pending` AND NOT `archive` + AND `success_update` > `failure_update` + $order ", + intval($uid) + ); + + if (!$contacts) + return($forumlist); + + foreach($contacts as $contact) { + $forumlist[] = array( + 'url' => $contact['url'], + 'name' => $contact['name'], + 'id' => $contact['id'], + 'micro' => $contact['micro'], + ); + } + return($forumlist); + } + + + /** + * @brief Forumlist widget + * + * Sidebar widget to show subcribed friendica forums. If activated + * in the settings, it appears at the notwork page sidebar + * + * @param int $uid The ID of the User + * @param int $cid + * The contact id which is used to mark a forum as "selected" + * @return string + */ + public static function widget($uid,$cid = 0) { + + if(! intval(feature_enabled(local_user(),'forumlist_widget'))) + return; + + $o = ''; + + //sort by last updated item + $lastitem = true; + + $contacts = self::get_list($uid,true,$lastitem, true); + $total = count($contacts); + $visible_forums = 10; + + if(count($contacts)) { + + $id = 0; + + foreach($contacts as $contact) { + + $selected = (($cid == $contact['id']) ? ' forum-selected' : ''); + + $entry = array( + 'url' => z_root() . '/network?f=&cid=' . $contact['id'], + 'external_url' => z_root() . '/redir/' . $contact['id'], + 'name' => $contact['name'], + 'cid' => $contact['id'], + 'selected' => $selected, + 'micro' => proxy_url($contact['micro'], false, PROXY_SIZE_MICRO), + 'id' => ++$id, + ); + $entries[] = $entry; + } + + $tpl = get_markup_template('widget_forumlist.tpl'); + + $o .= replace_macros($tpl,array( + '$title' => t('Forums'), + '$forums' => $entries, + '$link_desc' => t('External link to forum'), + '$total' => $total, + '$visible_forums' => $visible_forums, + '$showmore' => t('show more'), + )); + } + + return $o; + } + + /** + * @brief Format forumlist as contact block + * + * This function is used to show the forumlist in + * the advanced profile. + * + * @param int $uid The ID of the User + * @return string + * + */ + public static function profile_advanced($uid) { + + $profile = intval(feature_enabled($uid,'forumlist_profile')); + if(! $profile) + return; + + $o = ''; + + // place holder in case somebody wants configurability + $show_total = 9999; + + //don't sort by last updated item + $lastitem = false; + + $contacts = self::get_list($uid,false,$lastitem,false); + + $total_shown = 0; + + foreach($contacts as $contact) { + $forumlist .= micropro($contact,false,'forumlist-profile-advanced'); + $total_shown ++; + if($total_shown == $show_total) + break; + } + + if(count($contacts) > 0) + $o .= $forumlist; + return $o; + } + + /** + * @brief count unread forum items + * + * Count unread items of connected forums and private groups + * + * @return array + * 'id' => contact id + * 'name' => contact/forum name + * 'count' => counted unseen forum items + * + */ + public static function count_unseen_items() { + $r = q("SELECT `contact`.`id`, `contact`.`name`, COUNT(*) AS `count` FROM `item` + INNER JOIN `contact` ON `item`.`contact-id` = `contact`.`id` + WHERE `item`.`uid` = %d AND `item`.`visible` AND NOT `item`.`deleted` AND `item`.`unseen` + AND `contact`.`network`= 'dfrn' AND (`contact`.`forum` OR `contact`.`prv`) + AND NOT `contact`.`blocked` AND NOT `contact`.`hidden` + AND NOT `contact`.`pending` AND NOT `contact`.`archive` + AND `contact`.`success_update` > `failure_update` + GROUP BY `contact`.`id` ", + intval(local_user()) + ); + + return $r; + } + +} \ No newline at end of file diff --git a/include/NotificationsManager.php b/include/NotificationsManager.php new file mode 100644 index 000000000..5f8211eb8 --- /dev/null +++ b/include/NotificationsManager.php @@ -0,0 +1,136 @@ +a = get_app(); + } + + /** + * @brief set some extra note properties + * + * @param array $notes array of note arrays from db + * @return array Copy of input array with added properties + * + * Set some extra properties to note array from db: + * - timestamp as int in default TZ + * - date_rel : relative date string + * - msg_html: message as html string + * - msg_plain: message as plain text string + */ + private function _set_extra($notes) { + $rets = array(); + foreach($notes as $n) { + $local_time = datetime_convert('UTC',date_default_timezone_get(),$n['date']); + $n['timestamp'] = strtotime($local_time); + $n['date_rel'] = relative_date($n['date']); + $n['msg_html'] = bbcode($n['msg'], false, false, false, false); + $n['msg_plain'] = explode("\n",trim(html2plain($n['msg_html'], 0)))[0]; + + $rets[] = $n; + } + return $rets; + } + + + /** + * @brief get all notifications for local_user() + * + * @param array $filter optional Array "column name"=>value: filter query by columns values + * @param string $order optional Space separated list of column to sort by. prepend name with "+" to sort ASC, "-" to sort DESC. Default to "-date" + * @param string $limit optional Query limits + * + * @return array of results or false on errors + */ + public function getAll($filter = array(), $order="-date", $limit="") { + $filter_str = array(); + $filter_sql = ""; + foreach($filter as $column => $value) { + $filter_str[] = sprintf("`%s` = '%s'", $column, dbesc($value)); + } + if (count($filter_str)>0) { + $filter_sql = "AND ".implode(" AND ", $filter_str); + } + + $aOrder = explode(" ", $order); + $asOrder = array(); + foreach($aOrder as $o) { + $dir = "asc"; + if ($o[0]==="-") { + $dir = "desc"; + $o = substr($o,1); + } + if ($o[0]==="+") { + $dir = "asc"; + $o = substr($o,1); + } + $asOrder[] = "$o $dir"; + } + $order_sql = implode(", ", $asOrder); + + if ($limit!="") $limit = " LIMIT ".$limit; + + $r = q("SELECT * FROM `notify` WHERE `uid` = %d $filter_sql ORDER BY $order_sql $limit", + intval(local_user()) + ); + if ($r!==false && count($r)>0) return $this->_set_extra($r); + return false; + } + + /** + * @brief get one note for local_user() by $id value + * + * @param int $id + * @return array note values or null if not found + */ + public function getByID($id) { + $r = q("SELECT * FROM `notify` WHERE `id` = %d AND `uid` = %d LIMIT 1", + intval($id), + intval(local_user()) + ); + if($r!==false && count($r)>0) { + return $this->_set_extra($r)[0]; + } + return null; + } + + /** + * @brief set seen state of $note of local_user() + * + * @param array $note + * @param bool $seen optional true or false, default true + * @return bool true on success, false on errors + */ + public function setSeen($note, $seen = true) { + return q("UPDATE `notify` SET `seen` = %d WHERE ( `link` = '%s' OR ( `parent` != 0 AND `parent` = %d AND `otype` = '%s' )) AND `uid` = %d", + intval($seen), + dbesc($note['link']), + intval($note['parent']), + dbesc($note['otype']), + intval(local_user()) + ); + } + + /** + * @brief set seen state of all notifications of local_user() + * + * @param bool $seen optional true or false. default true + * @return bool true on success, false on error + */ + public function setAllSeen($seen = true) { + return q("UPDATE `notify` SET `seen` = %d WHERE `uid` = %d", + intval($seen), + intval(local_user()) + ); + } +} diff --git a/include/Photo.php b/include/Photo.php index 30424747d..91fce55a8 100644 --- a/include/Photo.php +++ b/include/Photo.php @@ -720,65 +720,101 @@ function guess_image_type($filename, $fromcurl=false) { } -function import_profile_photo($photo,$uid,$cid) { +/** + * @brief Updates the avatar links in a contact only if needed + * + * @param string $avatar Link to avatar picture + * @param int $uid User id of contact owner + * @param int $cid Contact id + * @param bool $force force picture update + * + * @return array Returns array of the different avatar sizes + */ +function update_contact_avatar($avatar,$uid,$cid, $force = false) { - $a = get_app(); + $r = q("SELECT `avatar`, `photo`, `thumb`, `micro` FROM `contact` WHERE `id` = %d LIMIT 1", intval($cid)); + if (!$r) + return false; + else + $data = array($r[0]["photo"], $r[0]["thumb"], $r[0]["micro"]); - $r = q("select `resource-id` from photo where `uid` = %d and `contact-id` = %d and `scale` = 4 and `album` = 'Contact Photos' limit 1", - intval($uid), - intval($cid) - ); - if(count($r) && strlen($r[0]['resource-id'])) { - $hash = $r[0]['resource-id']; - } - else { - $hash = photo_new_resource(); - } + if (($r[0]["avatar"] != $avatar) OR $force) { + $photos = import_profile_photo($avatar,$uid,$cid, true); - $photo_failure = false; + if ($photos) { + q("UPDATE `contact` SET `avatar` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s', `avatar-date` = '%s' WHERE `id` = %d", + dbesc($avatar), dbesc($photos[0]), dbesc($photos[1]), dbesc($photos[2]), + dbesc(datetime_convert()), intval($cid)); + return $photos; + } + } - $filename = basename($photo); - $img_str = fetch_url($photo,true); + return $data; +} - $type = guess_image_type($photo,true); - $img = new Photo($img_str, $type); - if($img->is_valid()) { +function import_profile_photo($photo,$uid,$cid, $quit_on_error = false) { - $img->scaleImageSquare(175); + $a = get_app(); - $r = $img->store($uid, $cid, $hash, $filename, 'Contact Photos', 4 ); + $r = q("select `resource-id` from photo where `uid` = %d and `contact-id` = %d and `scale` = 4 and `album` = 'Contact Photos' limit 1", + intval($uid), + intval($cid) + ); + if(count($r) && strlen($r[0]['resource-id'])) { + $hash = $r[0]['resource-id']; + } else { + $hash = photo_new_resource(); + } - if($r === false) - $photo_failure = true; + $photo_failure = false; - $img->scaleImage(80); + $filename = basename($photo); + $img_str = fetch_url($photo,true); - $r = $img->store($uid, $cid, $hash, $filename, 'Contact Photos', 5 ); + if ($quit_on_error AND ($img_str == "")) + return false; - if($r === false) - $photo_failure = true; + $type = guess_image_type($photo,true); + $img = new Photo($img_str, $type); + if($img->is_valid()) { - $img->scaleImage(48); + $img->scaleImageSquare(175); - $r = $img->store($uid, $cid, $hash, $filename, 'Contact Photos', 6 ); + $r = $img->store($uid, $cid, $hash, $filename, 'Contact Photos', 4 ); - if($r === false) - $photo_failure = true; + if($r === false) + $photo_failure = true; - $photo = $a->get_baseurl() . '/photo/' . $hash . '-4.' . $img->getExt(); - $thumb = $a->get_baseurl() . '/photo/' . $hash . '-5.' . $img->getExt(); - $micro = $a->get_baseurl() . '/photo/' . $hash . '-6.' . $img->getExt(); - } - else - $photo_failure = true; + $img->scaleImage(80); - if($photo_failure) { - $photo = $a->get_baseurl() . '/images/person-175.jpg'; - $thumb = $a->get_baseurl() . '/images/person-80.jpg'; - $micro = $a->get_baseurl() . '/images/person-48.jpg'; - } + $r = $img->store($uid, $cid, $hash, $filename, 'Contact Photos', 5 ); - return(array($photo,$thumb,$micro)); + if($r === false) + $photo_failure = true; + + $img->scaleImage(48); + + $r = $img->store($uid, $cid, $hash, $filename, 'Contact Photos', 6 ); + + if($r === false) + $photo_failure = true; + + $photo = $a->get_baseurl() . '/photo/' . $hash . '-4.' . $img->getExt(); + $thumb = $a->get_baseurl() . '/photo/' . $hash . '-5.' . $img->getExt(); + $micro = $a->get_baseurl() . '/photo/' . $hash . '-6.' . $img->getExt(); + } else + $photo_failure = true; + + if($photo_failure AND $quit_on_error) + return false; + + if($photo_failure) { + $photo = $a->get_baseurl() . '/images/person-175.jpg'; + $thumb = $a->get_baseurl() . '/images/person-80.jpg'; + $micro = $a->get_baseurl() . '/images/person-48.jpg'; + } + + return(array($photo,$thumb,$micro)); } @@ -792,15 +828,19 @@ function get_photo_info($url) { $filesize = strlen($img_str); - $tempfile = tempnam(get_temppath(), "cache"); + if (function_exists("getimagesizefromstring")) + $data = getimagesizefromstring($img_str); + else { + $tempfile = tempnam(get_temppath(), "cache"); - $a = get_app(); - $stamp1 = microtime(true); - file_put_contents($tempfile, $img_str); - $a->save_timestamp($stamp1, "file"); + $a = get_app(); + $stamp1 = microtime(true); + file_put_contents($tempfile, $img_str); + $a->save_timestamp($stamp1, "file"); - $data = getimagesize($tempfile); - unlink($tempfile); + $data = getimagesize($tempfile); + unlink($tempfile); + } if ($data) $data["size"] = $filesize; diff --git a/include/Scrape.php b/include/Scrape.php index cb192c77c..ca6489b16 100644 --- a/include/Scrape.php +++ b/include/Scrape.php @@ -235,7 +235,9 @@ function scrape_feed($url) { $a = get_app(); $ret = array(); - $s = fetch_url($url); + $cookiejar = tempnam(get_temppath(), 'cookiejar-scrape-feed-'); + $s = fetch_url($url, false, $redirects, 0, Null, $cookiejar); + unlink($cookiejar); $headers = $a->get_curl_headers(); $code = $a->get_curl_code(); @@ -559,10 +561,11 @@ function probe_url($url, $mode = PROBE_NORMAL, $level = 1) { $pubkey = $hcard_key; } } - if($diaspora && $diaspora_base && $diaspora_guid) { - if($mode == PROBE_DIASPORA || ! $notify) { - $notify = $diaspora_base . 'receive/users/' . $diaspora_guid; + $diaspora_notify = $diaspora_base.'receive/users/'.$diaspora_guid; + + if($mode == PROBE_DIASPORA || ! $notify || ($notify == $diaspora_notify)) { + $notify = $diaspora_notify; $batch = $diaspora_base . 'receive/public' ; } if(strpos($url,'@')) @@ -661,7 +664,9 @@ function probe_url($url, $mode = PROBE_NORMAL, $level = 1) { $vcard['photo'] = $feedret['photo']; require_once('library/simplepie/simplepie.inc'); $feed = new SimplePie(); - $xml = fetch_url($poll); + $cookiejar = tempnam(get_temppath(), 'cookiejar-scrape-feed-'); + $xml = fetch_url($poll, false, $redirects, 0, Null, $cookiejar); + unlink($cookiejar); logger('probe_url: fetch feed: ' . $poll . ' returns: ' . $xml, LOGGER_DATA); $a = get_app(); diff --git a/include/acl_selectors.php b/include/acl_selectors.php index 4ef3d05ea..69181b735 100644 --- a/include/acl_selectors.php +++ b/include/acl_selectors.php @@ -20,7 +20,7 @@ function group_select($selname,$selclass,$preselected = false,$size = 4) { $o .= "'; return $o; -}} +} -// return a select using 'field_select_raw' template, with timezones -// groupped (primarily) by continent -// arguments follow convetion as other field_* template array: -// 'name', 'label', $value, 'help' -if (!function_exists('field_timezone')){ + + +/** + * @brief Generating a Timezone selector + * + * Return a select using 'field_select_raw' template, with timezones + * groupped (primarily) by continent + * arguments follow convetion as other field_* template array: + * 'name', 'label', $value, 'help' + * + * @param string $name Name of the selector + * @param string $label Label for the selector + * @param string $current Timezone + * @param string $help Help text + * + * @return string Parsed HTML + */ function field_timezone($name='timezone', $label='', $current = 'America/Los_Angeles', $help){ $options = select_timezone($current); $options = str_replace(''; + // if ($dob && $dob != '0000-00-00') // $o = datesel($f,mktime(0,0,0,0,0,1900),mktime(),mktime(0,0,0,$month,$day,$year),'dob'); // else // $o = datesel($f,mktime(0,0,0,0,0,1900),mktime(),false,'dob'); + return $o; } /** - * returns a date selector - * @param $format - * format string, e.g. 'ymd' or 'mdy'. Not currently supported - * @param $min - * unix timestamp of minimum date - * @param $max - * unix timestap of maximum date - * @param $default - * unix timestamp of default date - * @param $id - * id and name of datetimepicker (defaults to "datetimepicker") + * @brief Returns a date selector + * + * @param string $format + * Format string, e.g. 'ymd' or 'mdy'. Not currently supported + * @param string $min + * Unix timestamp of minimum date + * @param string $max + * Unix timestap of maximum date + * @param string $default + * Unix timestamp of default date + * @param string $id + * ID and name of datetimepicker (defaults to "datetimepicker") + * + * @return string Parsed HTML output. */ -if(! function_exists('datesel')) { function datesel($format, $min, $max, $default, $id = 'datepicker') { return datetimesel($format,$min,$max,$default,$id,true,false, '',''); -}} +} /** - * returns a time selector - * @param $format - * format string, e.g. 'ymd' or 'mdy'. Not currently supported + * @brief Returns a time selector + * + * @param string $format + * Format string, e.g. 'ymd' or 'mdy'. Not currently supported * @param $h - * already selected hour + * Already selected hour * @param $m - * already selected minute - * @param $id - * id and name of datetimepicker (defaults to "timepicker") + * Already selected minute + * @param string $id + * ID and name of datetimepicker (defaults to "timepicker") + * + * @return string Parsed HTML output. */ -if(! function_exists('timesel')) { function timesel($format, $h, $m, $id='timepicker') { return datetimesel($format,new DateTime(),new DateTime(),new DateTime("$h:$m"),$id,false,true); -}} +} /** * @brief Returns a datetime selector. * - * @param $format + * @param string $format * format string, e.g. 'ymd' or 'mdy'. Not currently supported - * @param $min + * @param string $min * unix timestamp of minimum date - * @param $max + * @param string $max * unix timestap of maximum date - * @param $default + * @param string $default * unix timestamp of default date * @param string $id * id and name of datetimepicker (defaults to "datetimepicker") - * @param boolean $pickdate + * @param bool $pickdate * true to show date picker (default) * @param boolean $picktime * true to show time picker (default) @@ -201,17 +244,15 @@ function timesel($format, $h, $m, $id='timepicker') { * set minimum date from picker with id $minfrom (none by default) * @param $maxfrom * set maximum date from picker with id $maxfrom (none by default) - * @param boolean $required default false + * @param bool $required default false + * * @return string Parsed HTML output. * * @todo Once browser support is better this could probably be replaced with * native HTML5 date picker. */ -if(! function_exists('datetimesel')) { function datetimesel($format, $min, $max, $default, $id = 'datetimepicker', $pickdate = true, $picktime = true, $minfrom = '', $maxfrom = '', $required = false) { - $a = get_app(); - // First day of the week (0 = Sunday) $firstDay = get_pconfig(local_user(),'system','first_day_of_week'); if ($firstDay === false) $firstDay=0; @@ -224,43 +265,58 @@ function datetimesel($format, $min, $max, $default, $id = 'datetimepicker', $pic $o = ''; $dateformat = ''; + if($pickdate) $dateformat .= 'Y-m-d'; if($pickdate && $picktime) $dateformat .= ' '; if($picktime) $dateformat .= 'H:i'; + $minjs = $min ? ",minDate: new Date({$min->getTimestamp()}*1000), yearStart: " . $min->format('Y') : ''; $maxjs = $max ? ",maxDate: new Date({$max->getTimestamp()}*1000), yearEnd: " . $max->format('Y') : ''; $input_text = $default ? 'value="' . date($dateformat, $default->getTimestamp()) . '"' : ''; $defaultdatejs = $default ? ",defaultDate: new Date({$default->getTimestamp()}*1000)" : ''; + $pickers = ''; if(!$pickdate) $pickers .= ',datepicker: false'; if(!$picktime) $pickers .= ',timepicker: false'; + $extra_js = ''; $pickers .= ",dayOfWeekStart: ".$firstDay.",lang:'".$lang."'"; if($minfrom != '') $extra_js .= "\$('#$minfrom').data('xdsoft_datetimepicker').setOptions({onChangeDateTime: function (currentDateTime) { \$('#$id').data('xdsoft_datetimepicker').setOptions({minDate: currentDateTime})}})"; if($maxfrom != '') $extra_js .= "\$('#$maxfrom').data('xdsoft_datetimepicker').setOptions({onChangeDateTime: function (currentDateTime) { \$('#$id').data('xdsoft_datetimepicker').setOptions({maxDate: currentDateTime})}})"; + $readable_format = $dateformat; $readable_format = str_replace('Y','yyyy',$readable_format); $readable_format = str_replace('m','mm',$readable_format); $readable_format = str_replace('d','dd',$readable_format); $readable_format = str_replace('H','HH',$readable_format); $readable_format = str_replace('i','MM',$readable_format); + $o .= "
"; $o .= '
'; $o .= ""; + return $o; -}} +} -// implements "3 seconds ago" etc. -// based on $posted_date, (UTC). -// Results relative to current timezone -// Limited to range of timestamps - -if(! function_exists('relative_date')) { +/** + * @brief Returns a relative date string. + * + * Implements "3 seconds ago" etc. + * Based on $posted_date, (UTC). + * Results relative to current timezone. + * Limited to range of timestamps. + * + * @param string $posted_date + * @param string $format (optional) Parsed with sprintf() + * %1$d %2$s ago, e.g. 22 hours ago, 1 minute ago + * + * @return string with relative date + */ function relative_date($posted_date,$format = null) { $localtime = datetime_convert('UTC',date_default_timezone_get(),$posted_date); @@ -300,23 +356,33 @@ function relative_date($posted_date,$format = null) { // translators - e.g. 22 hours ago, 1 minute ago if(! $format) $format = t('%1$d %2$s ago'); + return sprintf( $format,$r, (($r == 1) ? $str[0] : $str[1])); - } - } -}} - - - -// Returns age in years, given a date of birth, -// the timezone of the person whose date of birth is provided, -// and the timezone of the person viewing the result. -// Why? Bear with me. Let's say I live in Mittagong, Australia, and my -// birthday is on New Year's. You live in San Bruno, California. -// When exactly are you going to see my age increase? -// A: 5:00 AM Dec 31 San Bruno time. That's precisely when I start -// celebrating and become a year older. If you wish me happy birthday -// on January 1 (San Bruno time), you'll be a day late. + } + } +} +/** + * @brief Returns timezone correct age in years. + * + * Returns the age in years, given a date of birth, the timezone of the person + * whose date of birth is provided, and the timezone of the person viewing the + * result. + * + * Why? Bear with me. Let's say I live in Mittagong, Australia, and my birthday + * is on New Year's. You live in San Bruno, California. + * When exactly are you going to see my age increase? + * + * A: 5:00 AM Dec 31 San Bruno time. That's precisely when I start celebrating + * and become a year older. If you wish me happy birthday on January 1 + * (San Bruno time), you'll be a day late. + * + * @param string $dob Date of Birth + * @param string $owner_tz (optional) Timezone of the person of interest + * @param string $viewer_tz (optional) Timezone of the person viewing + * + * @return int Age in years + */ function age($dob,$owner_tz = '',$viewer_tz = '') { if(! intval($dob)) return 0; @@ -333,64 +399,79 @@ function age($dob,$owner_tz = '',$viewer_tz = '') { if(($curr_month < $month) || (($curr_month == $month) && ($curr_day < $day))) $year_diff--; + return $year_diff; } - - -// Get days in month -// get_dim($year, $month); -// returns number of days. -// $month[1] = 'January'; -// to match human usage. - -if(! function_exists('get_dim')) { +/** + * @brief Get days of a month in a given year. + * + * Returns number of days in the month of the given year. + * $m = 1 is 'January' to match human usage. + * + * @param int $y Year + * @param int $m Month (1=January, 12=December) + * + * @return int Number of days in the given month + */ function get_dim($y,$m) { - $dim = array( 0, - 31, 28, 31, 30, 31, 30, - 31, 31, 30, 31, 30, 31); - - if($m != 2) - return $dim[$m]; - if(((($y % 4) == 0) && (($y % 100) != 0)) || (($y % 400) == 0)) - return 29; - return $dim[2]; -}} + $dim = array( 0, + 31, 28, 31, 30, 31, 30, + 31, 31, 30, 31, 30, 31); + if($m != 2) + return $dim[$m]; -// Returns the first day in month for a given month, year -// get_first_dim($year,$month) -// returns 0 = Sunday through 6 = Saturday -// Months start at 1. + if(((($y % 4) == 0) && (($y % 100) != 0)) || (($y % 400) == 0)) + return 29; -if(! function_exists('get_first_dim')) { + return $dim[2]; +} + +/** + * @brief Returns the first day in month for a given month, year. + * + * Months start at 1. + * + * @param int $y Year + * @param int $m Month (1=January, 12=December) + * + * @return string day 0 = Sunday through 6 = Saturday + */ function get_first_dim($y,$m) { - $d = sprintf('%04d-%02d-01 00:00', intval($y), intval($m)); - return datetime_convert('UTC','UTC',$d,'w'); -}} + $d = sprintf('%04d-%02d-01 00:00', intval($y), intval($m)); -// output a calendar for the given month, year. -// if $links are provided (array), e.g. $links[12] => 'http://mylink' , -// date 12 will be linked appropriately. Today's date is also noted by -// altering td class. -// Months count from 1. + return datetime_convert('UTC','UTC',$d,'w'); +} - -/// @TODO Provide (prev,next) links, define class variations for different size calendars - - -if(! function_exists('cal')) { +/** + * @brief Output a calendar for the given month, year. + * + * If $links are provided (array), e.g. $links[12] => 'http://mylink' , + * date 12 will be linked appropriately. Today's date is also noted by + * altering td class. + * Months count from 1. + * + * @param int $y Year + * @param int $m Month + * @param bool $links (default false) + * @param string $class + * + * @return string + * + * @todo Provide (prev,next) links, define class variations for different size calendars + */ function cal($y = 0,$m = 0, $links = false, $class='') { // month table - start at 1 to match human usage. $mtab = array(' ', - 'January','February','March', - 'April','May','June', - 'July','August','September', - 'October','November','December' + 'January','February','March', + 'April','May','June', + 'July','August','September', + 'October','November','December' ); $thisyear = datetime_convert('UTC',date_default_timezone_get(),'now','Y'); @@ -400,54 +481,63 @@ function cal($y = 0,$m = 0, $links = false, $class='') { if(! $m) $m = intval($thismonth); - $dn = array('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'); - $f = get_first_dim($y,$m); - $l = get_dim($y,$m); - $d = 1; - $dow = 0; - $started = false; + $dn = array('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'); + $f = get_first_dim($y,$m); + $l = get_dim($y,$m); + $d = 1; + $dow = 0; + $started = false; - if(($y == $thisyear) && ($m == $thismonth)) - $tddate = intval(datetime_convert('UTC',date_default_timezone_get(),'now','j')); + if(($y == $thisyear) && ($m == $thismonth)) + $tddate = intval(datetime_convert('UTC',date_default_timezone_get(),'now','j')); $str_month = day_translate($mtab[$m]); - $o = ''; - $o .= ""; - for($a = 0; $a < 7; $a ++) - $o .= ''; - $o .= ''; + $o = '
$str_month $y
' . mb_substr(day_translate($dn[$a]),0,3,'UTF-8') . '
'; + $o .= ""; + for($a = 0; $a < 7; $a ++) + $o .= ''; - while($d <= $l) { - if(($dow == $f) && (! $started)) - $started = true; - $today = (((isset($tddate)) && ($tddate == $d)) ? "class=\"today\" " : ''); - $o .= "'; - $dow ++; - if(($dow == 7) && ($d <= $l)) { - $dow = 0; - $o .= ''; - } - } - if($dow) - for($a = $dow; $a < 7; $a ++) - $o .= ''; - $o .= '
$str_month $y
' . mb_substr(day_translate($dn[$a]),0,3,'UTF-8') . '"; - $day = str_replace(' ',' ',sprintf('%2.2d', $d)); - if($started) { - if(is_array($links) && isset($links[$d])) - $o .= "$day"; - else - $o .= $day; - $d ++; - } - else - $o .= ' '; - $o .= '
 
'."\r\n"; + $o .= ''; - return $o; -}} + while($d <= $l) { + if(($dow == $f) && (! $started)) + $started = true; + $today = (((isset($tddate)) && ($tddate == $d)) ? "class=\"today\" " : ''); + $o .= ""; + $day = str_replace(' ',' ',sprintf('%2.2d', $d)); + if($started) { + if(is_array($links) && isset($links[$d])) + $o .= "$day"; + else + $o .= $day; + $d ++; + } else { + $o .= ' '; + } + + $o .= ''; + $dow ++; + if(($dow == 7) && ($d <= $l)) { + $dow = 0; + $o .= ''; + } + } + if($dow) + for($a = $dow; $a < 7; $a ++) + $o .= ' '; + + $o .= ''."\r\n"; + + return $o; +} + +/** + * @brief Create a birthday event. + * + * Update the year and the birthday. + */ function update_contact_birthdays() { // This only handles foreign or alien networks where a birthday has been provided. @@ -474,8 +564,6 @@ function update_contact_birthdays() { $bdtext = sprintf( t('%s\'s birthday'), $rr['name']); $bdtext2 = sprintf( t('Happy Birthday %s'), ' [url=' . $rr['url'] . ']' . $rr['name'] . '[/url]') ; - - $r = q("INSERT INTO `event` (`uid`,`cid`,`created`,`edited`,`start`,`finish`,`summary`,`desc`,`type`,`adjust`) VALUES ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%d' ) ", intval($rr['uid']), diff --git a/include/dbstructure.php b/include/dbstructure.php index 851eb2328..ddf036f2c 100644 --- a/include/dbstructure.php +++ b/include/dbstructure.php @@ -453,6 +453,7 @@ function db_definition() { "keywords" => array("type" => "text", "not null" => "1"), "gender" => array("type" => "varchar(32)", "not null" => "1", "default" => ""), "attag" => array("type" => "varchar(255)", "not null" => "1", "default" => ""), + "avatar" => array("type" => "varchar(255)", "not null" => "1", "default" => ""), "photo" => array("type" => "text", "not null" => "1"), "thumb" => array("type" => "text", "not null" => "1"), "micro" => array("type" => "text", "not null" => "1"), @@ -666,9 +667,14 @@ function db_definition() { "about" => array("type" => "text", "not null" => "1"), "keywords" => array("type" => "text", "not null" => "1"), "gender" => array("type" => "varchar(32)", "not null" => "1", "default" => ""), + "birthday" => array("type" => "varchar(32)", "not null" => "1", "default" => "0000-00-00"), "community" => array("type" => "tinyint(1)", "not null" => "1", "default" => "0"), + "hide" => array("type" => "tinyint(1)", "not null" => "1", "default" => "0"), + "nsfw" => array("type" => "tinyint(1)", "not null" => "1", "default" => "0"), "network" => array("type" => "varchar(255)", "not null" => "1", "default" => ""), "addr" => array("type" => "varchar(255)", "not null" => "1", "default" => ""), + "notify" => array("type" => "text", "not null" => "1"), + "alias" => array("type" => "varchar(255)", "not null" => "1", "default" => ""), "generation" => array("type" => "tinyint(3)", "not null" => "1", "default" => "0"), "server_url" => array("type" => "varchar(255)", "not null" => "1", "default" => ""), ), @@ -742,21 +748,6 @@ function db_definition() { "nurl" => array("nurl"), ) ); - $database["guid"] = array( - "fields" => array( - "id" => array("type" => "int(10) unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1"), - "guid" => array("type" => "varchar(255)", "not null" => "1", "default" => ""), - "plink" => array("type" => "varchar(255)", "not null" => "1", "default" => ""), - "uri" => array("type" => "varchar(255)", "not null" => "1", "default" => ""), - "network" => array("type" => "varchar(32)", "not null" => "1", "default" => ""), - ), - "indexes" => array( - "PRIMARY" => array("id"), - "guid" => array("guid"), - "plink" => array("plink"), - "uri" => array("uri"), - ) - ); $database["hook"] = array( "fields" => array( "id" => array("type" => "int(11)", "not null" => "1", "extra" => "auto_increment", "primary" => "1"), @@ -795,6 +786,7 @@ function db_definition() { "uri" => array("type" => "varchar(255)", "not null" => "1", "default" => ""), "uid" => array("type" => "int(10) unsigned", "not null" => "1", "default" => "0"), "contact-id" => array("type" => "int(11)", "not null" => "1", "default" => "0"), + "gcontact-id" => array("type" => "int(11) unsigned", "not null" => "1", "default" => "0"), "type" => array("type" => "varchar(255)", "not null" => "1", "default" => ""), "wall" => array("type" => "tinyint(1)", "not null" => "1", "default" => "0"), "gravity" => array("type" => "tinyint(1)", "not null" => "1", "default" => "0"), @@ -871,6 +863,7 @@ function db_definition() { "uid_thrparent" => array("uid","thr-parent"), "uid_parenturi" => array("uid","parent-uri"), "uid_contactid_created" => array("uid","contact-id","created"), + "gcontactid_uid_created" => array("gcontact-id","uid","created"), "wall_body" => array("wall","body(6)"), "uid_visible_moderated_created" => array("uid","visible","moderated","created"), "uid_uri" => array("uid","uri"), @@ -1014,6 +1007,30 @@ function db_definition() { "receiver-uid" => array("receiver-uid"), ) ); + $database["oembed"] = array( + "fields" => array( + "url" => array("type" => "varchar(255)", "not null" => "1", "primary" => "1"), + "content" => array("type" => "text", "not null" => "1"), + "created" => array("type" => "datetime", "not null" => "1", "default" => "0000-00-00 00:00:00"), + ), + "indexes" => array( + "PRIMARY" => array("url"), + "created" => array("created"), + ) + ); + $database["parsed_url"] = array( + "fields" => array( + "url" => array("type" => "varchar(255)", "not null" => "1", "primary" => "1"), + "guessing" => array("type" => "tinyint(1)", "not null" => "1", "default" => "0", "primary" => "1"), + "oembed" => array("type" => "tinyint(1)", "not null" => "1", "default" => "0", "primary" => "1"), + "content" => array("type" => "text", "not null" => "1"), + "created" => array("type" => "datetime", "not null" => "1", "default" => "0000-00-00 00:00:00"), + ), + "indexes" => array( + "PRIMARY" => array("url", "guessing", "oembed"), + "created" => array("created"), + ) + ); $database["pconfig"] = array( "fields" => array( "id" => array("type" => "int(11)", "not null" => "1", "extra" => "auto_increment", "primary" => "1"), @@ -1287,6 +1304,7 @@ function db_definition() { "iid" => array("type" => "int(10) unsigned", "not null" => "1", "default" => "0", "primary" => "1"), "uid" => array("type" => "int(10) unsigned", "not null" => "1", "default" => "0"), "contact-id" => array("type" => "int(11) unsigned", "not null" => "1", "default" => "0"), + "gcontact-id" => array("type" => "int(11) unsigned", "not null" => "1", "default" => "0"), "created" => array("type" => "datetime", "not null" => "1", "default" => "0000-00-00 00:00:00"), "edited" => array("type" => "datetime", "not null" => "1", "default" => "0000-00-00 00:00:00"), "commented" => array("type" => "datetime", "not null" => "1", "default" => "0000-00-00 00:00:00"), @@ -1316,6 +1334,8 @@ function db_definition() { "uid_network_created" => array("uid","network","created"), "uid_contactid_commented" => array("uid","contact-id","commented"), "uid_contactid_created" => array("uid","contact-id","created"), + "uid_gcontactid_commented" => array("uid","gcontact-id","commented"), + "uid_gcontactid_created" => array("uid","gcontact-id","created"), "wall_private_received" => array("wall","private","received"), "uid_created" => array("uid","created"), "uid_commented" => array("uid","commented"), @@ -1334,21 +1354,6 @@ function db_definition() { "PRIMARY" => array("id"), ) ); - $database["unique_contacts"] = array( - "fields" => array( - "id" => array("type" => "int(11)", "not null" => "1", "extra" => "auto_increment", "primary" => "1"), - "url" => array("type" => "varchar(255)", "not null" => "1", "default" => ""), - "nick" => array("type" => "varchar(255)", "not null" => "1", "default" => ""), - "name" => array("type" => "varchar(255)", "not null" => "1", "default" => ""), - "avatar" => array("type" => "varchar(255)", "not null" => "1", "default" => ""), - "location" => array("type" => "varchar(255)", "not null" => "1", "default" => ""), - "about" => array("type" => "text", "not null" => "1"), - ), - "indexes" => array( - "PRIMARY" => array("id"), - "url" => array("url"), - ) - ); $database["user"] = array( "fields" => array( "uid" => array("type" => "int(11)", "not null" => "1", "extra" => "auto_increment", "primary" => "1"), diff --git a/include/delivery.php b/include/delivery.php index a88873361..021ceb996 100644 --- a/include/delivery.php +++ b/include/delivery.php @@ -5,6 +5,7 @@ require_once('include/html2plain.php'); require_once("include/Scrape.php"); require_once('include/diaspora.php'); require_once("include/ostatus.php"); +require_once("include/dfrn.php"); function delivery_run(&$argv, &$argc){ global $a, $db; @@ -264,8 +265,6 @@ function delivery_run(&$argv, &$argc){ if(count($r)) $contact = $r[0]; - $hubxml = feed_hublinks(); - if($contact['self']) continue; @@ -278,138 +277,54 @@ function delivery_run(&$argv, &$argc){ case NETWORK_DFRN: logger('notifier: '.$target_item["guid"].' dfrndelivery: ' . $contact['name']); - $feed_template = get_markup_template('atom_feed.tpl'); - $mail_template = get_markup_template('atom_mail.tpl'); - - $atom = ''; - - - $birthday = feed_birthday($owner['uid'],$owner['timezone']); - - if(strlen($birthday)) - $birthday = '' . xmlify($birthday) . ''; - - $atom .= replace_macros($feed_template, array( - '$version' => xmlify(FRIENDICA_VERSION), - '$feed_id' => xmlify($a->get_baseurl() . '/profile/' . $owner['nickname'] ), - '$feed_title' => xmlify($owner['name']), - '$feed_updated' => xmlify(datetime_convert('UTC', 'UTC', $updated . '+00:00' , ATOM_TIME)) , - '$hub' => $hubxml, - '$salmon' => '', // private feed, we don't use salmon here - '$name' => xmlify($owner['name']), - '$profile_page' => xmlify($owner['url']), - '$photo' => xmlify($owner['photo']), - '$thumb' => xmlify($owner['thumb']), - '$picdate' => xmlify(datetime_convert('UTC','UTC',$owner['avatar-date'] . '+00:00' , ATOM_TIME)) , - '$uridate' => xmlify(datetime_convert('UTC','UTC',$owner['uri-date'] . '+00:00' , ATOM_TIME)) , - '$namdate' => xmlify(datetime_convert('UTC','UTC',$owner['name-date'] . '+00:00' , ATOM_TIME)) , - '$birthday' => $birthday, - '$community' => (($owner['page-flags'] == PAGE_COMMUNITY) ? '1' : '') - )); - - if($mail) { - $public_message = false; // mail is not public - - $body = fix_private_photos($item['body'],$owner['uid'],null,$message[0]['contact-id']); - - $atom .= replace_macros($mail_template, array( - '$name' => xmlify($owner['name']), - '$profile_page' => xmlify($owner['url']), - '$thumb' => xmlify($owner['thumb']), - '$item_id' => xmlify($item['uri']), - '$subject' => xmlify($item['title']), - '$created' => xmlify(datetime_convert('UTC', 'UTC', $item['created'] . '+00:00' , ATOM_TIME)), - '$content' => xmlify($body), - '$parent_id' => xmlify($item['parent-uri']) - )); - } elseif($fsuggest) { - $public_message = false; // suggestions are not public - - $sugg_template = get_markup_template('atom_suggest.tpl'); - - $atom .= replace_macros($sugg_template, array( - '$name' => xmlify($item['name']), - '$url' => xmlify($item['url']), - '$photo' => xmlify($item['photo']), - '$request' => xmlify($item['request']), - '$note' => xmlify($item['note']) - )); - - // We don't need this any more - - q("DELETE FROM `fsuggest` WHERE `id` = %d LIMIT 1", - intval($item['id']) - ); - } elseif($relocate) { - $public_message = false; // suggestions are not public - - $sugg_template = get_markup_template('atom_relocate.tpl'); - - /* get site pubkey. this could be a new installation with no site keys*/ - $pubkey = get_config('system','site_pubkey'); - if(! $pubkey) { - $res = new_keypair(1024); - set_config('system','site_prvkey', $res['prvkey']); - set_config('system','site_pubkey', $res['pubkey']); - } - - $rp = q("SELECT `resource-id` , `scale`, type FROM `photo` - WHERE `profile` = 1 AND `uid` = %d ORDER BY scale;", $uid); - $photos = array(); - $ext = Photo::supportedTypes(); - foreach($rp as $p){ - $photos[$p['scale']] = $a->get_baseurl().'/photo/'.$p['resource-id'].'-'.$p['scale'].'.'.$ext[$p['type']]; - } - unset($rp, $ext); - - $atom .= replace_macros($sugg_template, array( - '$name' => xmlify($owner['name']), - '$photo' => xmlify($photos[4]), - '$thumb' => xmlify($photos[5]), - '$micro' => xmlify($photos[6]), - '$url' => xmlify($owner['url']), - '$request' => xmlify($owner['request']), - '$confirm' => xmlify($owner['confirm']), - '$notify' => xmlify($owner['notify']), - '$poll' => xmlify($owner['poll']), - '$sitepubkey' => xmlify(get_config('system','site_pubkey')), - //'$pubkey' => xmlify($owner['pubkey']), - //'$prvkey' => xmlify($owner['prvkey']), - )); - unset($photos); - } elseif($followup) { + if ($mail) { + $item['body'] = fix_private_photos($item['body'],$owner['uid'],null,$message[0]['contact-id']); + $atom = dfrn::mail($item, $owner); + } elseif ($fsuggest) { + $atom = dfrn::fsuggest($item, $owner); + q("DELETE FROM `fsuggest` WHERE `id` = %d LIMIT 1", intval($item['id'])); + } elseif ($relocate) + $atom = dfrn::relocate($owner, $uid); + elseif($followup) { + $msgitems = array(); foreach($items as $item) { // there is only one item - if(! $item['parent']) + if(!$item['parent']) continue; if($item['id'] == $item_id) { logger('followup: item: ' . print_r($item,true), LOGGER_DATA); - $atom .= atom_entry($item,'text',null,$owner,false); + $msgitems[] = $item; } } + $atom = dfrn::entries($msgitems,$owner); } else { + $msgitems = array(); foreach($items as $item) { - if(! $item['parent']) + if(!$item['parent']) continue; // private emails may be in included in public conversations. Filter them. - if(($public_message) && $item['private'] == 1) + if(($public_message) && $item['private']) continue; $item_contact = get_item_contact($item,$icontacts); - if(! $item_contact) + if(!$item_contact) continue; if($normal_mode) { - if($item_id == $item['id'] || $item['id'] == $item['parent']) - $atom .= atom_entry($item,'text',null,$owner,true,(($top_level) ? $contact['id'] : 0)); - } else - $atom .= atom_entry($item,'text',null,$owner,true); + if($item_id == $item['id'] || $item['id'] == $item['parent']) { + $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); } - $atom .= '' . "\r\n"; - - logger('notifier: '.$contact["url"].' '.$target_item["guid"].' entry: '.$atom, LOGGER_DEBUG); + 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)); @@ -458,15 +373,14 @@ function delivery_run(&$argv, &$argc){ if (($x[0]['page-flags'] == PAGE_SOAPBOX) AND $top_level) break; - require_once('library/simplepie/simplepie.inc'); logger('mod-delivery: local delivery'); - local_delivery($x[0],$atom); + dfrn::import($atom, $x[0]); break; } } if(! was_recently_delayed($contact['id'])) - $deliver_status = dfrn_deliver($owner,$contact,$atom); + $deliver_status = dfrn::deliver($owner,$contact,$atom); else $deliver_status = (-1); diff --git a/include/dfrn.php b/include/dfrn.php new file mode 100644 index 000000000..a96d2830e --- /dev/null +++ b/include/dfrn.php @@ -0,0 +1,2422 @@ +formatOutput = true; + + $root = self::add_header($doc, $owner, "dfrn:owner", "", false); + + if(! count($items)) + return trim($doc->saveXML()); + + foreach($items as $item) { + $entry = self::entry($doc, "text", $item, $owner, $item["entry:comment-allow"], $item["entry:cid"]); + $root->appendChild($entry); + } + + return(trim($doc->saveXML())); + } + + /** + * @brief Generate an atom feed for the given user + * + * This function is called when another server is pulling data from the user feed. + * + * @param string $dfrn_id DFRN ID from the requesting party + * @param string $owner_nick Owner nick name + * @param string $last_update Date of the last update + * @param int $direction Can be -1, 0 or 1. + * + * @return string DFRN feed entries + */ + public static function feed($dfrn_id, $owner_nick, $last_update, $direction = 0) { + + $a = get_app(); + + $sitefeed = ((strlen($owner_nick)) ? false : true); // not yet implemented, need to rewrite huge chunks of following logic + $public_feed = (($dfrn_id) ? false : true); + $starred = false; // not yet implemented, possible security issues + $converse = false; + + if($public_feed && $a->argc > 2) { + for($x = 2; $x < $a->argc; $x++) { + if($a->argv[$x] == 'converse') + $converse = true; + if($a->argv[$x] == 'starred') + $starred = true; + if($a->argv[$x] === 'category' && $a->argc > ($x + 1) && strlen($a->argv[$x+1])) + $category = $a->argv[$x+1]; + } + } + + + + // default permissions - anonymous user + + $sql_extra = " AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = '' AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = '' "; + + $r = q("SELECT `contact`.*, `user`.`uid` AS `user_uid`, `user`.`nickname`, `user`.`timezone`, `user`.`page-flags` + FROM `contact` INNER JOIN `user` ON `user`.`uid` = `contact`.`uid` + WHERE `contact`.`self` = 1 AND `user`.`nickname` = '%s' LIMIT 1", + dbesc($owner_nick) + ); + + if(! count($r)) + killme(); + + $owner = $r[0]; + $owner_id = $owner['user_uid']; + $owner_nick = $owner['nickname']; + + $sql_post_table = ""; + $visibility = ""; + + if(! $public_feed) { + + $sql_extra = ''; + switch($direction) { + case (-1): + $sql_extra = sprintf(" AND `issued-id` = '%s' ", dbesc($dfrn_id)); + $my_id = $dfrn_id; + break; + case 0: + $sql_extra = sprintf(" AND `issued-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id)); + $my_id = '1:' . $dfrn_id; + break; + case 1: + $sql_extra = sprintf(" AND `dfrn-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id)); + $my_id = '0:' . $dfrn_id; + break; + default: + return false; + break; // NOTREACHED + } + + $r = q("SELECT * FROM `contact` WHERE `blocked` = 0 AND `pending` = 0 AND `contact`.`uid` = %d $sql_extra LIMIT 1", + intval($owner_id) + ); + + if(! count($r)) + killme(); + + $contact = $r[0]; + require_once('include/security.php'); + $groups = init_groups_visitor($contact['id']); + + if(count($groups)) { + for($x = 0; $x < count($groups); $x ++) + $groups[$x] = '<' . intval($groups[$x]) . '>' ; + $gs = implode('|', $groups); + } else + $gs = '<<>>' ; // Impossible to match + + $sql_extra = sprintf(" + AND ( `allow_cid` = '' OR `allow_cid` REGEXP '<%d>' ) + AND ( `deny_cid` = '' OR NOT `deny_cid` REGEXP '<%d>' ) + AND ( `allow_gid` = '' OR `allow_gid` REGEXP '%s' ) + AND ( `deny_gid` = '' OR NOT `deny_gid` REGEXP '%s') + ", + intval($contact['id']), + intval($contact['id']), + dbesc($gs), + dbesc($gs) + ); + } + + if($public_feed) + $sort = 'DESC'; + else + $sort = 'ASC'; + + $date_field = "`changed`"; + $sql_order = "`item`.`parent` ".$sort.", `item`.`created` ASC"; + + if(! strlen($last_update)) + $last_update = 'now -30 days'; + + if(isset($category)) { + $sql_post_table = sprintf("INNER JOIN (SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d ORDER BY `tid` DESC) AS `term` ON `item`.`id` = `term`.`oid` ", + dbesc(protect_sprintf($category)), intval(TERM_OBJ_POST), intval(TERM_CATEGORY), intval($owner_id)); + //$sql_extra .= file_tag_file_query('item',$category,'category'); + } + + if($public_feed) { + if(! $converse) + $sql_extra .= " AND `contact`.`self` = 1 "; + } + + $check_date = datetime_convert('UTC','UTC',$last_update,'Y-m-d H:i:s'); + + // AND ( `item`.`edited` > '%s' OR `item`.`changed` > '%s' ) + // dbesc($check_date), + + $r = q("SELECT STRAIGHT_JOIN `item`.*, `item`.`id` AS `item_id`, + `contact`.`name`, `contact`.`network`, `contact`.`photo`, `contact`.`url`, + `contact`.`name-date`, `contact`.`uri-date`, `contact`.`avatar-date`, + `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`, + `contact`.`id` AS `contact-id`, `contact`.`uid` AS `contact-uid`, + `sign`.`signed_text`, `sign`.`signature`, `sign`.`signer` + FROM `item` $sql_post_table + INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id` + AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0 + LEFT JOIN `sign` ON `sign`.`iid` = `item`.`id` + WHERE `item`.`uid` = %d AND `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`parent` != 0 + AND ((`item`.`wall` = 1) $visibility) AND `item`.$date_field > '%s' + $sql_extra + ORDER BY $sql_order LIMIT 0, 300", + intval($owner_id), + dbesc($check_date), + dbesc($sort) + ); + + // Will check further below if this actually returned results. + // We will provide an empty feed if that is the case. + + $items = $r; + + $doc = new DOMDocument('1.0', 'utf-8'); + $doc->formatOutput = true; + + $alternatelink = $owner['url']; + + if(isset($category)) + $alternatelink .= "/category/".$category; + + if ($public_feed) + $author = "dfrn:owner"; + else + $author = "author"; + + $root = self::add_header($doc, $owner, $author, $alternatelink, true); + + // This hook can't work anymore + // call_hooks('atom_feed', $atom); + + if(! count($items)) { + $atom = trim($doc->saveXML()); + + call_hooks('atom_feed_end', $atom); + + return $atom; + } + + foreach($items as $item) { + + // prevent private email from leaking. + if($item['network'] === NETWORK_MAIL) + continue; + + // public feeds get html, our own nodes use bbcode + + if($public_feed) { + $type = 'html'; + // catch any email that's in a public conversation and make sure it doesn't leak + if($item['private']) + continue; + } else + $type = 'text'; + + $entry = self::entry($doc, $type, $item, $owner, true); + $root->appendChild($entry); + + } + + $atom = trim($doc->saveXML()); + + call_hooks('atom_feed_end', $atom); + + return $atom; + } + + /** + * @brief Create XML text for DFRN mails + * + * @param array $item message elements + * @param array $owner Owner record + * + * @return string DFRN mail + */ + public static function mail($item, $owner) { + $doc = new DOMDocument('1.0', 'utf-8'); + $doc->formatOutput = true; + + $root = self::add_header($doc, $owner, "dfrn:owner", "", false); + + $mail = $doc->createElement("dfrn:mail"); + $sender = $doc->createElement("dfrn:sender"); + + xml_add_element($doc, $sender, "dfrn:name", $owner['name']); + xml_add_element($doc, $sender, "dfrn:uri", $owner['url']); + xml_add_element($doc, $sender, "dfrn:avatar", $owner['thumb']); + + $mail->appendChild($sender); + + xml_add_element($doc, $mail, "dfrn:id", $item['uri']); + xml_add_element($doc, $mail, "dfrn:in-reply-to", $item['parent-uri']); + xml_add_element($doc, $mail, "dfrn:sentdate", datetime_convert('UTC', 'UTC', $item['created'] . '+00:00' , ATOM_TIME)); + xml_add_element($doc, $mail, "dfrn:subject", $item['title']); + xml_add_element($doc, $mail, "dfrn:content", $item['body']); + + $root->appendChild($mail); + + return(trim($doc->saveXML())); + } + + /** + * @brief Create XML text for DFRN friend suggestions + * + * @param array $item suggestion elements + * @param array $owner Owner record + * + * @return string DFRN suggestions + */ + public static function fsuggest($item, $owner) { + $doc = new DOMDocument('1.0', 'utf-8'); + $doc->formatOutput = true; + + $root = self::add_header($doc, $owner, "dfrn:owner", "", false); + + $suggest = $doc->createElement("dfrn:suggest"); + + xml_add_element($doc, $suggest, "dfrn:url", $item['url']); + xml_add_element($doc, $suggest, "dfrn:name", $item['name']); + xml_add_element($doc, $suggest, "dfrn:photo", $item['photo']); + xml_add_element($doc, $suggest, "dfrn:request", $item['request']); + xml_add_element($doc, $suggest, "dfrn:note", $item['note']); + + $root->appendChild($suggest); + + return(trim($doc->saveXML())); + } + + /** + * @brief Create XML text for DFRN relocations + * + * @param array $owner Owner record + * @param int $uid User ID + * + * @return string DFRN relocations + */ + public static function relocate($owner, $uid) { + + /* get site pubkey. this could be a new installation with no site keys*/ + $pubkey = get_config('system','site_pubkey'); + if(! $pubkey) { + $res = new_keypair(1024); + set_config('system','site_prvkey', $res['prvkey']); + set_config('system','site_pubkey', $res['pubkey']); + } + + $rp = q("SELECT `resource-id` , `scale`, type FROM `photo` + WHERE `profile` = 1 AND `uid` = %d ORDER BY scale;", $uid); + $photos = array(); + $ext = Photo::supportedTypes(); + + foreach($rp as $p) + $photos[$p['scale']] = app::get_baseurl().'/photo/'.$p['resource-id'].'-'.$p['scale'].'.'.$ext[$p['type']]; + + unset($rp, $ext); + + $doc = new DOMDocument('1.0', 'utf-8'); + $doc->formatOutput = true; + + $root = self::add_header($doc, $owner, "dfrn:owner", "", false); + + $relocate = $doc->createElement("dfrn:relocate"); + + xml_add_element($doc, $relocate, "dfrn:url", $owner['url']); + xml_add_element($doc, $relocate, "dfrn:name", $owner['name']); + xml_add_element($doc, $relocate, "dfrn:photo", $photos[4]); + xml_add_element($doc, $relocate, "dfrn:thumb", $photos[5]); + xml_add_element($doc, $relocate, "dfrn:micro", $photos[6]); + xml_add_element($doc, $relocate, "dfrn:request", $owner['request']); + xml_add_element($doc, $relocate, "dfrn:confirm", $owner['confirm']); + xml_add_element($doc, $relocate, "dfrn:notify", $owner['notify']); + xml_add_element($doc, $relocate, "dfrn:poll", $owner['poll']); + xml_add_element($doc, $relocate, "dfrn:sitepubkey", get_config('system','site_pubkey')); + + $root->appendChild($relocate); + + return(trim($doc->saveXML())); + } + + /** + * @brief Adds the header elements for the DFRN protocol + * + * @param object $doc XML document + * @param array $owner Owner record + * @param string $authorelement Element name for the author + * @param string $alternatelink link to profile or category + * @param bool $public Is it a header for public posts? + * + * @return object XML root object + */ + private function add_header($doc, $owner, $authorelement, $alternatelink = "", $public = false) { + + if ($alternatelink == "") + $alternatelink = $owner['url']; + + $root = $doc->createElementNS(NAMESPACE_ATOM1, 'feed'); + $doc->appendChild($root); + + $root->setAttribute("xmlns:thr", NAMESPACE_THREAD); + $root->setAttribute("xmlns:at", NAMESPACE_TOMB); + $root->setAttribute("xmlns:media", NAMESPACE_MEDIA); + $root->setAttribute("xmlns:dfrn", NAMESPACE_DFRN); + $root->setAttribute("xmlns:activity", NAMESPACE_ACTIVITY); + $root->setAttribute("xmlns:georss", NAMESPACE_GEORSS); + $root->setAttribute("xmlns:poco", NAMESPACE_POCO); + $root->setAttribute("xmlns:ostatus", NAMESPACE_OSTATUS); + $root->setAttribute("xmlns:statusnet", NAMESPACE_STATUSNET); + + xml_add_element($doc, $root, "id", app::get_baseurl()."/profile/".$owner["nick"]); + xml_add_element($doc, $root, "title", $owner["name"]); + + $attributes = array("uri" => "https://friendi.ca", "version" => FRIENDICA_VERSION."-".DB_UPDATE_VERSION); + xml_add_element($doc, $root, "generator", FRIENDICA_PLATFORM, $attributes); + + $attributes = array("rel" => "license", "href" => "http://creativecommons.org/licenses/by/3.0/"); + xml_add_element($doc, $root, "link", "", $attributes); + + $attributes = array("rel" => "alternate", "type" => "text/html", "href" => $alternatelink); + xml_add_element($doc, $root, "link", "", $attributes); + + + if ($public) { + // DFRN itself doesn't uses this. But maybe someone else wants to subscribe to the public feed. + ostatus_hublinks($doc, $root); + + $attributes = array("rel" => "salmon", "href" => app::get_baseurl()."/salmon/".$owner["nick"]); + xml_add_element($doc, $root, "link", "", $attributes); + + $attributes = array("rel" => "http://salmon-protocol.org/ns/salmon-replies", "href" => app::get_baseurl()."/salmon/".$owner["nick"]); + xml_add_element($doc, $root, "link", "", $attributes); + + $attributes = array("rel" => "http://salmon-protocol.org/ns/salmon-mention", "href" => app::get_baseurl()."/salmon/".$owner["nick"]); + xml_add_element($doc, $root, "link", "", $attributes); + } + + if ($owner['page-flags'] == PAGE_COMMUNITY) + xml_add_element($doc, $root, "dfrn:community", 1); + + /// @todo We need a way to transmit the different page flags like "PAGE_PRVGROUP" + + xml_add_element($doc, $root, "updated", datetime_convert("UTC", "UTC", "now", ATOM_TIME)); + + $author = self::add_author($doc, $owner, $authorelement, $public); + $root->appendChild($author); + + return $root; + } + + /** + * @brief Adds the author element in the header for the DFRN protocol + * + * @param object $doc XML document + * @param array $owner Owner record + * @param string $authorelement Element name for the author + * + * @return object XML author object + */ + private function add_author($doc, $owner, $authorelement, $public) { + + $author = $doc->createElement($authorelement); + + $namdate = datetime_convert('UTC', 'UTC', $owner['name-date'].'+00:00' , ATOM_TIME); + $uridate = datetime_convert('UTC', 'UTC', $owner['uri-date'].'+00:00', ATOM_TIME); + $picdate = datetime_convert('UTC', 'UTC', $owner['avatar-date'].'+00:00', ATOM_TIME); + + $attributes = array("dfrn:updated" => $namdate); + xml_add_element($doc, $author, "name", $owner["name"], $attributes); + + $attributes = array("dfrn:updated" => $namdate); + xml_add_element($doc, $author, "uri", app::get_baseurl().'/profile/'.$owner["nickname"], $attributes); + + $attributes = array("dfrn:updated" => $namdate); + xml_add_element($doc, $author, "dfrn:handle", $owner["addr"], $attributes); + + $attributes = array("rel" => "photo", "type" => "image/jpeg", "dfrn:updated" => $picdate, + "media:width" => 175, "media:height" => 175, "href" => $owner['photo']); + xml_add_element($doc, $author, "link", "", $attributes); + + $attributes = array("rel" => "avatar", "type" => "image/jpeg", "dfrn:updated" => $picdate, + "media:width" => 175, "media:height" => 175, "href" => $owner['photo']); + xml_add_element($doc, $author, "link", "", $attributes); + + $birthday = feed_birthday($owner['user_uid'], $owner['timezone']); + + if ($birthday) + xml_add_element($doc, $author, "dfrn:birthday", $birthday); + + // The following fields will only be generated if this isn't for a public feed + if ($public) + return $author; + + // Only show contact details when we are allowed to + $r = q("SELECT `profile`.`about`, `profile`.`name`, `profile`.`homepage`, `user`.`nickname`, `user`.`timezone`, + `profile`.`locality`, `profile`.`region`, `profile`.`country-name`, `profile`.`pub_keywords`, `profile`.`dob` + FROM `profile` + INNER JOIN `user` ON `user`.`uid` = `profile`.`uid` + WHERE `profile`.`is-default` AND NOT `user`.`hidewall` AND `user`.`uid` = %d", + intval($owner['user_uid'])); + if ($r) { + $profile = $r[0]; + xml_add_element($doc, $author, "poco:displayName", $profile["name"]); + xml_add_element($doc, $author, "poco:updated", $namdate); + + if (trim($profile["dob"]) != "0000-00-00") + xml_add_element($doc, $author, "poco:birthday", "0000-".date("m-d", strtotime($profile["dob"]))); + + xml_add_element($doc, $author, "poco:note", $profile["about"]); + xml_add_element($doc, $author, "poco:preferredUsername", $profile["nickname"]); + + $savetz = date_default_timezone_get(); + date_default_timezone_set($profile["timezone"]); + xml_add_element($doc, $author, "poco:utcOffset", date("P")); + date_default_timezone_set($savetz); + + if (trim($profile["homepage"]) != "") { + $urls = $doc->createElement("poco:urls"); + xml_add_element($doc, $urls, "poco:type", "homepage"); + xml_add_element($doc, $urls, "poco:value", $profile["homepage"]); + xml_add_element($doc, $urls, "poco:primary", "true"); + $author->appendChild($urls); + } + + if (trim($profile["pub_keywords"]) != "") { + $keywords = explode(",", $profile["pub_keywords"]); + + foreach ($keywords AS $keyword) + xml_add_element($doc, $author, "poco:tags", trim($keyword)); + + } + + /// @todo When we are having the XMPP address in the profile we should propagate it here + $xmpp = ""; + if (trim($xmpp) != "") { + $ims = $doc->createElement("poco:ims"); + xml_add_element($doc, $ims, "poco:type", "xmpp"); + xml_add_element($doc, $ims, "poco:value", $xmpp); + xml_add_element($doc, $ims, "poco:primary", "true"); + $author->appendChild($ims); + } + + if (trim($profile["locality"].$profile["region"].$profile["country-name"]) != "") { + $element = $doc->createElement("poco:address"); + + xml_add_element($doc, $element, "poco:formatted", formatted_location($profile)); + + if (trim($profile["locality"]) != "") + xml_add_element($doc, $element, "poco:locality", $profile["locality"]); + + if (trim($profile["region"]) != "") + xml_add_element($doc, $element, "poco:region", $profile["region"]); + + if (trim($profile["country-name"]) != "") + xml_add_element($doc, $element, "poco:country", $profile["country-name"]); + + $author->appendChild($element); + } + } + + return $author; + } + + /** + * @brief Adds the author elements in the "entry" elements of the DFRN protocol + * + * @param object $doc XML document + * @param string $element Element name for the author + * @param string $contact_url Link of the contact + * @param array $items Item elements + * + * @return object XML author object + */ + private function add_entry_author($doc, $element, $contact_url, $item) { + + $contact = get_contact_details_by_url($contact_url, $item["uid"]); + + $author = $doc->createElement($element); + xml_add_element($doc, $author, "name", $contact["name"]); + xml_add_element($doc, $author, "uri", $contact["url"]); + xml_add_element($doc, $author, "dfrn:handle", $contact["addr"]); + + /// @Todo + /// - Check real image type and image size + /// - Check which of these boths elements we should use + $attributes = array( + "rel" => "photo", + "type" => "image/jpeg", + "media:width" => 80, + "media:height" => 80, + "href" => $contact["photo"]); + xml_add_element($doc, $author, "link", "", $attributes); + + $attributes = array( + "rel" => "avatar", + "type" => "image/jpeg", + "media:width" => 80, + "media:height" => 80, + "href" => $contact["photo"]); + xml_add_element($doc, $author, "link", "", $attributes); + + return $author; + } + + /** + * @brief Adds the activity elements + * + * @param object $doc XML document + * @param string $element Element name for the activity + * @param string $activity activity value + * + * @return object XML activity object + */ + private function create_activity($doc, $element, $activity) { + + if($activity) { + $entry = $doc->createElement($element); + + $r = parse_xml_string($activity, false); + if(!$r) + return false; + if($r->type) + xml_add_element($doc, $entry, "activity:object-type", $r->type); + if($r->id) + xml_add_element($doc, $entry, "id", $r->id); + if($r->title) + xml_add_element($doc, $entry, "title", $r->title); + if($r->link) { + if(substr($r->link,0,1) === '<') { + if(strstr($r->link,'&') && (! strstr($r->link,'&'))) + $r->link = str_replace('&','&', $r->link); + + $r->link = preg_replace('/\/','',$r->link); + + // XML does need a single element as root element so we add a dummy element here + $data = parse_xml_string("".$r->link."", false); + if (is_object($data)) { + foreach ($data->link AS $link) { + $attributes = array(); + foreach ($link->attributes() AS $parameter => $value) + $attributes[$parameter] = $value; + xml_add_element($doc, $entry, "link", "", $attributes); + } + } + } else { + $attributes = array("rel" => "alternate", "type" => "text/html", "href" => $r->link); + xml_add_element($doc, $entry, "link", "", $attributes); + } + } + if($r->content) + xml_add_element($doc, $entry, "content", bbcode($r->content), array("type" => "html")); + + return $entry; + } + + return false; + } + + /** + * @brief Adds the elements for attachments + * + * @param object $doc XML document + * @param object $root XML root + * @param array $item Item element + * + * @return object XML attachment object + */ + private function get_attachment($doc, $root, $item) { + $arr = explode('[/attach],',$item['attach']); + if(count($arr)) { + foreach($arr as $r) { + $matches = false; + $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches); + if($cnt) { + $attributes = array("rel" => "enclosure", + "href" => $matches[1], + "type" => $matches[3]); + + if(intval($matches[2])) + $attributes["length"] = intval($matches[2]); + + if(trim($matches[4]) != "") + $attributes["title"] = trim($matches[4]); + + xml_add_element($doc, $root, "link", "", $attributes); + } + } + } + } + + /** + * @brief Adds the "entry" elements for the DFRN protocol + * + * @param object $doc XML document + * @param string $type "text" or "html" + * @param array $item Item element + * @param array $owner Owner record + * @param bool $comment Trigger the sending of the "comment" element + * @param int $cid Contact ID of the recipient + * + * @return object XML entry object + */ + private function entry($doc, $type, $item, $owner, $comment = false, $cid = 0) { + + $mentioned = array(); + + if(!$item['parent']) + return; + + if($item['deleted']) { + $attributes = array("ref" => $item['uri'], "when" => datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)); + return xml_create_element($doc, "at:deleted-entry", "", $attributes); + } + + $entry = $doc->createElement("entry"); + + if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid']) + $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid); + else + $body = $item['body']; + + if ($type == 'html') { + $htmlbody = $body; + + if ($item['title'] != "") + $htmlbody = "[b]".$item['title']."[/b]\n\n".$htmlbody; + + $htmlbody = bbcode($htmlbody, false, false, 7); + } + + $author = self::add_entry_author($doc, "author", $item["author-link"], $item); + $entry->appendChild($author); + + $dfrnowner = self::add_entry_author($doc, "dfrn:owner", $item["owner-link"], $item); + $entry->appendChild($dfrnowner); + + if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) { + $parent = q("SELECT `guid` FROM `item` WHERE `id` = %d", intval($item["parent"])); + $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']); + $attributes = array("ref" => $parent_item, "type" => "text/html", + "href" => app::get_baseurl().'/display/'.$parent[0]['guid'], + "dfrn:diaspora_guid" => $parent[0]['guid']); + xml_add_element($doc, $entry, "thr:in-reply-to", "", $attributes); + } + + xml_add_element($doc, $entry, "id", $item["uri"]); + xml_add_element($doc, $entry, "title", $item["title"]); + + xml_add_element($doc, $entry, "published", datetime_convert("UTC","UTC",$item["created"]."+00:00",ATOM_TIME)); + xml_add_element($doc, $entry, "updated", datetime_convert("UTC","UTC",$item["edited"]."+00:00",ATOM_TIME)); + + // "dfrn:env" is used to read the content + xml_add_element($doc, $entry, "dfrn:env", base64url_encode($body, true)); + + // The "content" field is not read by the receiver. We could remove it when the type is "text" + // We keep it at the moment, maybe there is some old version that doesn't read "dfrn:env" + xml_add_element($doc, $entry, "content", (($type === 'html') ? $htmlbody : $body), array("type" => $type)); + + // We save this value in "plink". Maybe we should read it from there as well? + xml_add_element($doc, $entry, "link", "", array("rel" => "alternate", "type" => "text/html", + "href" => app::get_baseurl()."/display/".$item["guid"])); + + // "comment-allow" is some old fashioned stuff for old Friendica versions. + // It is included in the rewritten code for completeness + if ($comment) + xml_add_element($doc, $entry, "dfrn:comment-allow", intval($item['last-child'])); + + if($item['location']) + xml_add_element($doc, $entry, "dfrn:location", $item['location']); + + if($item['coord']) + xml_add_element($doc, $entry, "georss:point", $item['coord']); + + if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid'])) + xml_add_element($doc, $entry, "dfrn:private", (($item['private']) ? $item['private'] : 1)); + + if($item['extid']) + xml_add_element($doc, $entry, "dfrn:extid", $item['extid']); + + if($item['bookmark']) + xml_add_element($doc, $entry, "dfrn:bookmark", "true"); + + if($item['app']) + xml_add_element($doc, $entry, "statusnet:notice_info", "", array("local_id" => $item['id'], "source" => $item['app'])); + + xml_add_element($doc, $entry, "dfrn:diaspora_guid", $item["guid"]); + + // The signed text contains the content in Markdown, the sender handle and the signatur for the content + // It is needed for relayed comments to Diaspora. + if($item['signed_text']) { + $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer']))); + xml_add_element($doc, $entry, "dfrn:diaspora_signature", $sign); + } + + xml_add_element($doc, $entry, "activity:verb", construct_verb($item)); + + if ($item['object-type'] != "") + xml_add_element($doc, $entry, "activity:object-type", $item['object-type']); + elseif ($item['id'] == $item['parent']) + xml_add_element($doc, $entry, "activity:object-type", ACTIVITY_OBJ_NOTE); + else + xml_add_element($doc, $entry, "activity:object-type", ACTIVITY_OBJ_COMMENT); + + $actobj = self::create_activity($doc, "activity:object", $item['object']); + if ($actobj) + $entry->appendChild($actobj); + + $actarg = self::create_activity($doc, "activity:target", $item['target']); + if ($actarg) + $entry->appendChild($actarg); + + $tags = item_getfeedtags($item); + + if(count($tags)) { + foreach($tags as $t) + if (($type != 'html') OR ($t[0] != "@")) + xml_add_element($doc, $entry, "category", "", array("scheme" => "X-DFRN:".$t[0].":".$t[1], "term" => $t[2])); + } + + if(count($tags)) + foreach($tags as $t) + if ($t[0] == "@") + $mentioned[$t[1]] = $t[1]; + + foreach ($mentioned AS $mention) { + $r = q("SELECT `forum`, `prv` FROM `contact` WHERE `uid` = %d AND `nurl` = '%s'", + intval($owner["uid"]), + dbesc(normalise_link($mention))); + if ($r[0]["forum"] OR $r[0]["prv"]) + xml_add_element($doc, $entry, "link", "", array("rel" => "mentioned", + "ostatus:object-type" => ACTIVITY_OBJ_GROUP, + "href" => $mention)); + else + xml_add_element($doc, $entry, "link", "", array("rel" => "mentioned", + "ostatus:object-type" => ACTIVITY_OBJ_PERSON, + "href" => $mention)); + } + + self::get_attachment($doc, $entry, $item); + + return $entry; + } + + /** + * @brief Delivers the atom content to the contacts + * + * @param array $owner Owner record + * @param array $contactr Contact record of the receiver + * @param string $atom Content that will be transmitted + * @param bool $dissolve (to be documented) + * + * @return int Deliver status. -1 means an error. + */ + public static function deliver($owner,$contact,$atom, $dissolve = false) { + + $a = get_app(); + + $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']); + + if($contact['duplex'] && $contact['dfrn-id']) + $idtosend = '0:' . $orig_id; + if($contact['duplex'] && $contact['issued-id']) + $idtosend = '1:' . $orig_id; + + + $rino = get_config('system','rino_encrypt'); + $rino = intval($rino); + // use RINO1 if mcrypt isn't installed and RINO2 was selected + if ($rino==2 and !function_exists('mcrypt_create_iv')) $rino=1; + + logger("Local rino version: ". $rino, LOGGER_DEBUG); + + $ssl_val = intval(get_config('system','ssl_policy')); + $ssl_policy = ''; + + switch($ssl_val){ + case SSL_POLICY_FULL: + $ssl_policy = 'full'; + break; + case SSL_POLICY_SELFSIGN: + $ssl_policy = 'self'; + break; + case SSL_POLICY_NONE: + default: + $ssl_policy = 'none'; + break; + } + + $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino='.$rino : ''); + + logger('dfrn_deliver: ' . $url); + + $xml = fetch_url($url); + + $curl_stat = $a->get_curl_code(); + if(! $curl_stat) + return(-1); // timed out + + logger('dfrn_deliver: ' . $xml, LOGGER_DATA); + + if(! $xml) + return 3; + + if(strpos($xml,'status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id))) + return (($res->status) ? $res->status : 3); + + $postvars = array(); + $sent_dfrn_id = hex2bin((string) $res->dfrn_id); + $challenge = hex2bin((string) $res->challenge); + $perm = (($res->perm) ? $res->perm : null); + $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0); + $rino_remote_version = intval($res->rino); + $page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0); + + logger("Remote rino version: ".$rino_remote_version." for ".$contact["url"], LOGGER_DEBUG); + + if($owner['page-flags'] == PAGE_PRVGROUP) + $page = 2; + + $final_dfrn_id = ''; + + if($perm) { + if((($perm == 'rw') && (! intval($contact['writable']))) + || (($perm == 'r') && (intval($contact['writable'])))) { + q("update contact set writable = %d where id = %d", + intval(($perm == 'rw') ? 1 : 0), + intval($contact['id']) + ); + $contact['writable'] = (string) 1 - intval($contact['writable']); + } + } + + if(($contact['duplex'] && strlen($contact['pubkey'])) + || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey'])) + || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) { + openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']); + openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']); + } else { + openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']); + openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']); + } + + $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.')); + + if(strpos($final_dfrn_id,':') == 1) + $final_dfrn_id = substr($final_dfrn_id,2); + + if($final_dfrn_id != $orig_id) { + logger('dfrn_deliver: wrong dfrn_id.'); + // did not decode properly - cannot trust this site + return 3; + } + + $postvars['dfrn_id'] = $idtosend; + $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION; + if($dissolve) + $postvars['dissolve'] = '1'; + + + if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) { + $postvars['data'] = $atom; + $postvars['perm'] = 'rw'; + } else { + $postvars['data'] = str_replace('1','0',$atom); + $postvars['perm'] = 'r'; + } + + $postvars['ssl_policy'] = $ssl_policy; + + if($page) + $postvars['page'] = $page; + + + if($rino>0 && $rino_remote_version>0 && (! $dissolve)) { + logger('rino version: '. $rino_remote_version); + + switch($rino_remote_version) { + case 1: + // Deprecated rino version! + $key = substr(random_string(),0,16); + $data = aes_encrypt($postvars['data'],$key); + break; + case 2: + // RINO 2 based on php-encryption + try { + $key = Crypto::createNewRandomKey(); + } catch (CryptoTestFailed $ex) { + logger('Cannot safely create a key'); + return -1; + } catch (CannotPerformOperation $ex) { + logger('Cannot safely create a key'); + return -1; + } + try { + $data = Crypto::encrypt($postvars['data'], $key); + } catch (CryptoTestFailed $ex) { + logger('Cannot safely perform encryption'); + return -1; + } catch (CannotPerformOperation $ex) { + logger('Cannot safely perform encryption'); + return -1; + } + break; + default: + logger("rino: invalid requested verision '$rino_remote_version'"); + return -1; + } + + $postvars['rino'] = $rino_remote_version; + $postvars['data'] = bin2hex($data); + + #logger('rino: sent key = ' . $key, LOGGER_DEBUG); + + + if($dfrn_version >= 2.1) { + if(($contact['duplex'] && strlen($contact['pubkey'])) + || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey'])) + || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) + + openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']); + else + openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']); + + } else { + if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) + openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']); + else + openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']); + + } + + logger('md5 rawkey ' . md5($postvars['key'])); + + $postvars['key'] = bin2hex($postvars['key']); + } + + + logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA); + + $xml = post_url($contact['notify'],$postvars); + + logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA); + + $curl_stat = $a->get_curl_code(); + if((! $curl_stat) || (! strlen($xml))) + return(-1); // timed out + + if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after'))) + return(-1); + + if(strpos($xml,'status; + } + + /** + * @brief Add new birthday event for this person + * + * @param array $contact Contact record + * @param string $birthday Birthday of the contact + * + */ + private function birthday_event($contact, $birthday) { + + logger("updating birthday: ".$birthday." for contact ".$contact["id"]); + + $bdtext = sprintf(t("%s\'s birthday"), $contact["name"]); + $bdtext2 = sprintf(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"]), + dbesc(datetime_convert()), + dbesc(datetime_convert()), + dbesc(datetime_convert("UTC","UTC", $birthday)), + dbesc(datetime_convert("UTC","UTC", $birthday." + 1 day ")), + dbesc($bdtext), + dbesc($bdtext2), + dbesc("birthday") + ); + } + + /** + * @brief Fetch the author data from head or entry items + * + * @param object $xpath XPath object + * @param object $context In which context should the data be searched + * @param array $importer Record of the importer user mixed with contact of the content + * @param string $element Element name from which the data is fetched + * @param bool $onlyfetch Should the data only be fetched or should it update the contact record as well + * + * @return Returns an array with relevant data of the author + */ + private function fetchauthor($xpath, $context, $importer, $element, $onlyfetch) { + + $author = array(); + $author["name"] = $xpath->evaluate($element."/atom:name/text()", $context)->item(0)->nodeValue; + $author["link"] = $xpath->evaluate($element."/atom:uri/text()", $context)->item(0)->nodeValue; + + $r = q("SELECT `id`, `uid`, `network`, `avatar-date`, `name-date`, `uri-date`, `addr`, + `name`, `nick`, `about`, `location`, `keywords`, `bdyear`, `bd` + FROM `contact` WHERE `uid` = %d AND `nurl` = '%s' AND `network` != '%s'", + intval($importer["uid"]), dbesc(normalise_link($author["link"])), dbesc(NETWORK_STATUSNET)); + if ($r) { + $contact = $r[0]; + $author["contact-id"] = $r[0]["id"]; + $author["network"] = $r[0]["network"]; + } else { + $author["contact-id"] = $importer["id"]; + $author["network"] = $importer["network"]; + $onlyfetch = true; + } + + // Until now we aren't serving different sizes - but maybe later + $avatarlist = array(); + // @todo check if "avatar" or "photo" would be the best field in the specification + $avatars = $xpath->query($element."/atom:link[@rel='avatar']", $context); + foreach($avatars AS $avatar) { + $href = ""; + $width = 0; + foreach($avatar->attributes AS $attributes) { + if ($attributes->name == "href") + $href = $attributes->textContent; + if ($attributes->name == "width") + $width = $attributes->textContent; + if ($attributes->name == "updated") + $contact["avatar-date"] = $attributes->textContent; + } + if (($width > 0) AND ($href != "")) + $avatarlist[$width] = $href; + } + if (count($avatarlist) > 0) { + krsort($avatarlist); + $author["avatar"] = current($avatarlist); + } + + if ($r AND !$onlyfetch) { + + // When was the last change to name or uri? + $name_element = $xpath->query($element."/atom:name", $context)->item(0); + foreach($name_element->attributes AS $attributes) + if ($attributes->name == "updated") + $contact["name-date"] = $attributes->textContent; + + $link_element = $xpath->query($element."/atom:link", $context)->item(0); + foreach($link_element->attributes AS $attributes) + if ($attributes->name == "updated") + $contact["uri-date"] = $attributes->textContent; + + // Update contact data + $value = $xpath->evaluate($element."/dfrn:handle/text()", $context)->item(0)->nodeValue; + if ($value != "") + $contact["addr"] = $value; + + $value = $xpath->evaluate($element."/poco:displayName/text()", $context)->item(0)->nodeValue; + if ($value != "") + $contact["name"] = $value; + + $value = $xpath->evaluate($element."/poco:preferredUsername/text()", $context)->item(0)->nodeValue; + if ($value != "") + $contact["nick"] = $value; + + $value = $xpath->evaluate($element."/poco:note/text()", $context)->item(0)->nodeValue; + if ($value != "") + $contact["about"] = $value; + + $value = $xpath->evaluate($element."/poco:address/poco:formatted/text()", $context)->item(0)->nodeValue; + if ($value != "") + $contact["location"] = $value; + + /// @todo Add support for the following fields that we don't support by now in the contact table: + /// - poco:utcOffset + /// - poco:ims + /// - poco:urls + /// - poco:locality + /// - poco:region + /// - poco:country + + // Save the keywords into the contact table + $tags = array(); + $tagelements = $xpath->evaluate($element."/poco:tags/text()", $context); + foreach($tagelements AS $tag) + $tags[$tag->nodeValue] = $tag->nodeValue; + + if (count($tags)) + $contact["keywords"] = implode(", ", $tags); + + // "dfrn:birthday" contains the birthday converted to UTC + $old_bdyear = $contact["bdyear"]; + + $birthday = $xpath->evaluate($element."/dfrn:birthday/text()", $context)->item(0)->nodeValue; + + if (strtotime($birthday) > time()) { + $bd_timestamp = strtotime($birthday); + + $contact["bdyear"] = date("Y", $bd_timestamp); + } + + // "poco:birthday" is the birthday in the format "yyyy-mm-dd" + $value = $xpath->evaluate($element."/poco:birthday/text()", $context)->item(0)->nodeValue; + + if (!in_array($value, array("", "0000-00-00"))) { + $bdyear = date("Y"); + $value = str_replace("0000", $bdyear, $value); + + if (strtotime($value) < time()) { + $value = str_replace($bdyear, $bdyear + 1, $value); + $bdyear = $bdyear + 1; + } + + $contact["bd"] = $value; + } + + if ($old_bdyear != $contact["bdyear"]) + self::birthday_event($contact, $birthday); + + // Get all field names + $fields = array(); + foreach ($r[0] AS $field => $data) + $fields[$field] = $data; + + unset($fields["id"]); + unset($fields["uid"]); + unset($fields["avatar-date"]); + unset($fields["name-date"]); + unset($fields["uri-date"]); + + // Update check for this field has to be done differently + $datefields = array("name-date", "uri-date"); + foreach ($datefields AS $field) + if (strtotime($contact[$field]) > strtotime($r[0][$field])) { + logger("Difference for contact ".$contact["id"]." in field '".$field."'. Old value: '".$contact[$field]."', new value '".$r[0][$field]."'", LOGGER_DEBUG); + $update = true; + } + + foreach ($fields AS $field => $data) + if ($contact[$field] != $r[0][$field]) { + logger("Difference for contact ".$contact["id"]." in field '".$field."'. Old value: '".$contact[$field]."', new value '".$r[0][$field]."'", LOGGER_DEBUG); + $update = true; + } + + if ($update) { + logger("Update contact data for contact ".$contact["id"], LOGGER_DEBUG); + + q("UPDATE `contact` SET `name` = '%s', `nick` = '%s', `about` = '%s', `location` = '%s', + `addr` = '%s', `keywords` = '%s', `bdyear` = '%s', `bd` = '%s', + `name-date` = '%s', `uri-date` = '%s' + WHERE `id` = %d AND `network` = '%s'", + dbesc($contact["name"]), dbesc($contact["nick"]), dbesc($contact["about"]), dbesc($contact["location"]), + dbesc($contact["addr"]), dbesc($contact["keywords"]), dbesc($contact["bdyear"]), + dbesc($contact["bd"]), dbesc($contact["name-date"]), dbesc($contact["uri-date"]), + intval($contact["id"]), dbesc($contact["network"])); + } + + update_contact_avatar($author["avatar"], $importer["uid"], $contact["id"], + (strtotime($contact["avatar-date"]) > strtotime($r[0]["avatar-date"]))); + + // The generation is a sign for the reliability of the provided data. + // It is used in the socgraph.php to prevent that old contact data + // that was relayed over several servers can overwrite contact + // data that we received directly. + $contact["generation"] = 2; + $contact["photo"] = $author["avatar"]; + update_gcontact($contact); + } + + return($author); + } + + /** + * @brief Transforms activity objects into an XML string + * + * @param object $xpath XPath object + * @param object $activity Activity object + * @param text $element element name + * + * @return string XML string + */ + private function transform_activity($xpath, $activity, $element) { + if (!is_object($activity)) + return ""; + + $obj_doc = new DOMDocument("1.0", "utf-8"); + $obj_doc->formatOutput = true; + + $obj_element = $obj_doc->createElementNS(NAMESPACE_ATOM1, $element); + + $activity_type = $xpath->query("activity:object-type/text()", $activity)->item(0)->nodeValue; + xml_add_element($obj_doc, $obj_element, "type", $activity_type); + + $id = $xpath->query("atom:id", $activity)->item(0); + if (is_object($id)) + $obj_element->appendChild($obj_doc->importNode($id, true)); + + $title = $xpath->query("atom:title", $activity)->item(0); + if (is_object($title)) + $obj_element->appendChild($obj_doc->importNode($title, true)); + + $links = $xpath->query("atom:link", $activity); + if (is_object($links)) + foreach ($links AS $link) + $obj_element->appendChild($obj_doc->importNode($link, true)); + + $content = $xpath->query("atom:content", $activity)->item(0); + if (is_object($content)) + $obj_element->appendChild($obj_doc->importNode($content, true)); + + $obj_doc->appendChild($obj_element); + + $objxml = $obj_doc->saveXML($obj_element); + + // @todo This isn't totally clean. We should find a way to transform the namespaces + $objxml = str_replace("<".$element.' xmlns="http://www.w3.org/2005/Atom">', "<".$element.">", $objxml); + return($objxml); + } + + /** + * @brief Processes the mail elements + * + * @param object $xpath XPath object + * @param object $mail mail elements + * @param array $importer Record of the importer user mixed with contact of the content + */ + private function process_mail($xpath, $mail, $importer) { + + logger("Processing mails"); + + $msg = array(); + $msg["uid"] = $importer["importer_uid"]; + $msg["from-name"] = $xpath->query("dfrn:sender/dfrn:name/text()", $mail)->item(0)->nodeValue; + $msg["from-url"] = $xpath->query("dfrn:sender/dfrn:uri/text()", $mail)->item(0)->nodeValue; + $msg["from-photo"] = $xpath->query("dfrn:sender/dfrn:avatar/text()", $mail)->item(0)->nodeValue; + $msg["contact-id"] = $importer["id"]; + $msg["uri"] = $xpath->query("dfrn:id/text()", $mail)->item(0)->nodeValue; + $msg["parent-uri"] = $xpath->query("dfrn:in-reply-to/text()", $mail)->item(0)->nodeValue; + $msg["created"] = $xpath->query("dfrn:sentdate/text()", $mail)->item(0)->nodeValue; + $msg["title"] = $xpath->query("dfrn:subject/text()", $mail)->item(0)->nodeValue; + $msg["body"] = $xpath->query("dfrn:content/text()", $mail)->item(0)->nodeValue; + $msg["seen"] = 0; + $msg["replied"] = 0; + + dbesc_array($msg); + + $r = dbq("INSERT INTO `mail` (`".implode("`, `", array_keys($msg))."`) VALUES ('".implode("', '", array_values($msg))."')"); + + // send notifications. + + $notif_params = 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" => $msg, + "source_name" => $msg["from-name"], + "source_link" => $importer["url"], + "source_photo" => $importer["thumb"], + "verb" => ACTIVITY_POST, + "otype" => "mail" + ); + + notification($notif_params); + + logger("Mail is processed, notification was sent."); + } + + /** + * @brief Processes the suggestion elements + * + * @param object $xpath XPath object + * @param object $suggestion suggestion elements + * @param array $importer Record of the importer user mixed with contact of the content + */ + private function process_suggestion($xpath, $suggestion, $importer) { + + logger("Processing suggestions"); + + $suggest = array(); + $suggest["uid"] = $importer["importer_uid"]; + $suggest["cid"] = $importer["id"]; + $suggest["url"] = $xpath->query("dfrn:url/text()", $suggestion)->item(0)->nodeValue; + $suggest["name"] = $xpath->query("dfrn:name/text()", $suggestion)->item(0)->nodeValue; + $suggest["photo"] = $xpath->query("dfrn:photo/text()", $suggestion)->item(0)->nodeValue; + $suggest["request"] = $xpath->query("dfrn:request/text()", $suggestion)->item(0)->nodeValue; + $suggest["body"] = $xpath->query("dfrn:note/text()", $suggestion)->item(0)->nodeValue; + + // Does our member already have a friend matching this description? + + $r = q("SELECT `id` FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1", + dbesc($suggest["name"]), + dbesc(normalise_link($suggest["url"])), + intval($suggest["uid"]) + ); + if(count($r)) + return false; + + // Do we already have an fcontact record for this person? + + $fid = 0; + $r = q("SELECT `id` FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1", + dbesc($suggest["url"]), + dbesc($suggest["name"]), + dbesc($suggest["request"]) + ); + if(count($r)) { + $fid = $r[0]["id"]; + + // OK, we do. Do we already have an introduction for this person ? + $r = q("SELECT `id` FROM `intro` WHERE `uid` = %d AND `fid` = %d LIMIT 1", + intval($suggest["uid"]), + intval($fid) + ); + if(count($r)) + return false; + } + if(!$fid) + $r = q("INSERT INTO `fcontact` (`name`,`url`,`photo`,`request`) VALUES ('%s', '%s', '%s', '%s')", + dbesc($suggest["name"]), + dbesc($suggest["url"]), + dbesc($suggest["photo"]), + dbesc($suggest["request"]) + ); + $r = q("SELECT `id` FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1", + dbesc($suggest["url"]), + dbesc($suggest["name"]), + dbesc($suggest["request"]) + ); + if(count($r)) + $fid = $r[0]["id"]; + else + // database record did not get created. Quietly give up. + return false; + + + $hash = random_string(); + + $r = q("INSERT INTO `intro` (`uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked`) + VALUES(%d, %d, %d, '%s', '%s', '%s', %d)", + intval($suggest["uid"]), + intval($fid), + intval($suggest["cid"]), + dbesc($suggest["body"]), + dbesc($hash), + dbesc(datetime_convert()), + intval(0) + ); + + notification(array( + "type" => NOTIFY_SUGGEST, + "notify_flags" => $importer["notify-flags"], + "language" => $importer["language"], + "to_name" => $importer["username"], + "to_email" => $importer["email"], + "uid" => $importer["importer_uid"], + "item" => $suggest, + "link" => App::get_baseurl()."/notifications/intros", + "source_name" => $importer["name"], + "source_link" => $importer["url"], + "source_photo" => $importer["photo"], + "verb" => ACTIVITY_REQ_FRIEND, + "otype" => "intro" + )); + + return true; + + } + + /** + * @brief Processes the relocation elements + * + * @param object $xpath XPath object + * @param object $relocation relocation elements + * @param array $importer Record of the importer user mixed with contact of the content + */ + private function process_relocation($xpath, $relocation, $importer) { + + logger("Processing relocations"); + + $relocate = array(); + $relocate["uid"] = $importer["importer_uid"]; + $relocate["cid"] = $importer["id"]; + $relocate["url"] = $xpath->query("dfrn:url/text()", $relocation)->item(0)->nodeValue; + $relocate["name"] = $xpath->query("dfrn:name/text()", $relocation)->item(0)->nodeValue; + $relocate["photo"] = $xpath->query("dfrn:photo/text()", $relocation)->item(0)->nodeValue; + $relocate["thumb"] = $xpath->query("dfrn:thumb/text()", $relocation)->item(0)->nodeValue; + $relocate["micro"] = $xpath->query("dfrn:micro/text()", $relocation)->item(0)->nodeValue; + $relocate["request"] = $xpath->query("dfrn:request/text()", $relocation)->item(0)->nodeValue; + $relocate["confirm"] = $xpath->query("dfrn:confirm/text()", $relocation)->item(0)->nodeValue; + $relocate["notify"] = $xpath->query("dfrn:notify/text()", $relocation)->item(0)->nodeValue; + $relocate["poll"] = $xpath->query("dfrn:poll/text()", $relocation)->item(0)->nodeValue; + $relocate["sitepubkey"] = $xpath->query("dfrn:sitepubkey/text()", $relocation)->item(0)->nodeValue; + + // update contact + $r = q("SELECT `photo`, `url` FROM `contact` WHERE `id` = %d AND `uid` = %d;", + intval($importer["id"]), + intval($importer["importer_uid"])); + if (!$r) + return false; + + $old = $r[0]; + + $x = q("UPDATE `contact` SET + `name` = '%s', + `photo` = '%s', + `thumb` = '%s', + `micro` = '%s', + `url` = '%s', + `nurl` = '%s', + `request` = '%s', + `confirm` = '%s', + `notify` = '%s', + `poll` = '%s', + `site-pubkey` = '%s' + WHERE `id` = %d AND `uid` = %d;", + dbesc($relocate["name"]), + dbesc($relocate["photo"]), + dbesc($relocate["thumb"]), + dbesc($relocate["micro"]), + dbesc($relocate["url"]), + dbesc(normalise_link($relocate["url"])), + dbesc($relocate["request"]), + dbesc($relocate["confirm"]), + dbesc($relocate["notify"]), + dbesc($relocate["poll"]), + dbesc($relocate["sitepubkey"]), + intval($importer["id"]), + intval($importer["importer_uid"])); + + if ($x === false) + return false; + + // update items + $fields = array( + 'owner-link' => array($old["url"], $relocate["url"]), + 'author-link' => array($old["url"], $relocate["url"]), + 'owner-avatar' => array($old["photo"], $relocate["photo"]), + 'author-avatar' => array($old["photo"], $relocate["photo"]), + ); + foreach ($fields as $n=>$f){ + $x = q("UPDATE `item` SET `%s` = '%s' WHERE `%s` = '%s' AND `uid` = %d", + $n, dbesc($f[1]), + $n, dbesc($f[0]), + intval($importer["importer_uid"])); + if ($x === false) + return false; + } + + /// @TODO + /// merge with current record, current contents have priority + /// update record, set url-updated + /// update profile photos + /// schedule a scan? + return true; + } + + /** + * @brief Updates an item + * + * @param array $current the current item record + * @param array $item the new item record + * @param array $importer Record of the importer user mixed with contact of the content + * @param int $entrytype Is it a toplevel entry, a comment or a relayed comment? + */ + private function update_content($current, $item, $importer, $entrytype) { + $changed = false; + + if (edited_timestamp_is_newer($current, $item)) { + + // do not accept (ignore) an earlier edit than one we currently have. + if(datetime_convert("UTC","UTC",$item["edited"]) < $current["edited"]) + return(false); + + $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d", + dbesc($item["title"]), + dbesc($item["body"]), + dbesc($item["tag"]), + dbesc(datetime_convert("UTC","UTC",$item["edited"])), + dbesc(datetime_convert()), + dbesc($item["uri"]), + intval($importer["importer_uid"]) + ); + create_tags_from_itemuri($item["uri"], $importer["importer_uid"]); + update_thread_uri($item["uri"], $importer["importer_uid"]); + + $changed = true; + + if ($entrytype == DFRN_REPLY_RC) + proc_run("php", "include/notifier.php","comment-import", $current["id"]); + } + + // update last-child if it changes + if($item["last-child"] AND ($item["last-child"] != $current["last-child"])) { + $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d", + dbesc(datetime_convert()), + dbesc($item["parent-uri"]), + intval($importer["importer_uid"]) + ); + $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d", + intval($item["last-child"]), + dbesc(datetime_convert()), + dbesc($item["uri"]), + intval($importer["importer_uid"]) + ); + } + return $changed; + } + + /** + * @brief Detects the entry type of the item + * + * @param array $importer Record of the importer user mixed with contact of the content + * @param array $item the new item record + * + * @return int Is it a toplevel entry, a comment or a relayed comment? + */ + private function get_entry_type($importer, $item) { + if ($item["parent-uri"] != $item["uri"]) { + $community = false; + + if($importer["page-flags"] == PAGE_COMMUNITY || $importer["page-flags"] == PAGE_PRVGROUP) { + $sql_extra = ""; + $community = true; + logger("possible community action"); + } else + $sql_extra = " AND `contact`.`self` AND `item`.`wall` "; + + // was the top-level post for this action written by somebody on this site? + // Specifically, the recipient? + + $is_a_remote_action = false; + + $r = q("SELECT `item`.`parent-uri` FROM `item` + WHERE `item`.`uri` = '%s' + LIMIT 1", + dbesc($item["parent-uri"]) + ); + if($r && count($r)) { + $r = q("SELECT `item`.`forum_mode`, `item`.`wall` FROM `item` + INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id` + WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' OR `item`.`thr-parent` = '%s') + AND `item`.`uid` = %d + $sql_extra + LIMIT 1", + dbesc($r[0]["parent-uri"]), + dbesc($r[0]["parent-uri"]), + dbesc($r[0]["parent-uri"]), + intval($importer["importer_uid"]) + ); + if($r && count($r)) + $is_a_remote_action = true; + } + + // Does this have the characteristics of a community or private group action? + // If it's an action to a wall post on a community/prvgroup page it's a + // valid community action. Also forum_mode makes it valid for sure. + // If neither, it's not. + + if($is_a_remote_action && $community) { + if((!$r[0]["forum_mode"]) && (!$r[0]["wall"])) { + $is_a_remote_action = false; + logger("not a community action"); + } + } + + if ($is_a_remote_action) + return DFRN_REPLY_RC; + else + return DFRN_REPLY; + + } else + return DFRN_TOP_LEVEL; + + } + + /** + * @brief Send a "poke" + * + * @param array $item the new item record + * @param array $importer Record of the importer user mixed with contact of the content + * @param int $posted_id The record number of item record that was just posted + */ + private function do_poke($item, $importer, $posted_id) { + $verb = urldecode(substr($item["verb"],strpos($item["verb"], "#")+1)); + if(!$verb) + return; + $xo = parse_xml_string($item["object"],false); + + if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) { + + // somebody was poked/prodded. Was it me? + foreach($xo->link as $l) { + $atts = $l->attributes(); + switch($atts["rel"]) { + case "alternate": + $Blink = $atts["href"]; + break; + default: + break; + } + } + + if($Blink && link_compare($Blink,App::get_baseurl()."/profile/".$importer["nickname"])) { + + // send a notification + notification(array( + "type" => NOTIFY_POKE, + "notify_flags" => $importer["notify-flags"], + "language" => $importer["language"], + "to_name" => $importer["username"], + "to_email" => $importer["email"], + "uid" => $importer["importer_uid"], + "item" => $item, + "link" => App::get_baseurl()."/display/".urlencode(get_item_guid($posted_id)), + "source_name" => stripslashes($item["author-name"]), + "source_link" => $item["author-link"], + "source_photo" => ((link_compare($item["author-link"],$importer["url"])) + ? $importer["thumb"] : $item["author-avatar"]), + "verb" => $item["verb"], + "otype" => "person", + "activity" => $verb, + "parent" => $item["parent"] + )); + } + } + } + + /** + * @brief Processes several actions, depending on the verb + * + * @param int $entrytype Is it a toplevel entry, a comment or a relayed comment? + * @param array $importer Record of the importer user mixed with contact of the content + * @param array $item the new item record + * @param bool $is_like Is the verb a "like"? + * + * @return bool Should the processing of the entries be continued? + */ + private function process_verbs($entrytype, $importer, &$item, &$is_like) { + if (($entrytype == DFRN_TOP_LEVEL)) { + // The filling of the the "contact" variable is done for legcy reasons + // The functions below are partly used by ostatus.php as well - where we have this variable + $r = q("SELECT * FROM `contact` WHERE `id` = %d", intval($importer["id"])); + $contact = $r[0]; + $nickname = $contact["nick"]; + + // Big question: Do we need these functions? They were part of the "consume_feed" function. + // This function once was responsible for DFRN and OStatus. + if(activity_match($item["verb"],ACTIVITY_FOLLOW)) { + logger("New follower"); + new_follower($importer, $contact, $item, $nickname); + return false; + } + if(activity_match($item["verb"],ACTIVITY_UNFOLLOW)) { + logger("Lost follower"); + lose_follower($importer, $contact, $item); + return false; + } + if(activity_match($item["verb"],ACTIVITY_REQ_FRIEND)) { + logger("New friend request"); + new_follower($importer, $contact, $item, $nickname, true); + return false; + } + if(activity_match($item["verb"],ACTIVITY_UNFRIEND)) { + logger("Lost sharer"); + lose_sharer($importer, $contact, $item); + return false; + } + } else { + if(($item["verb"] === ACTIVITY_LIKE) + || ($item["verb"] === ACTIVITY_DISLIKE) + || ($item["verb"] === ACTIVITY_ATTEND) + || ($item["verb"] === ACTIVITY_ATTENDNO) + || ($item["verb"] === ACTIVITY_ATTENDMAYBE)) { + $is_like = true; + $item["type"] = "activity"; + $item["gravity"] = GRAVITY_LIKE; + // only one like or dislike per person + // splitted into two queries for performance issues + $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `parent-uri` = '%s' AND NOT `deleted` LIMIT 1", + intval($item["uid"]), + dbesc($item["author-link"]), + dbesc($item["verb"]), + dbesc($item["parent-uri"]) + ); + if($r && count($r)) + return false; + + $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `thr-parent` = '%s' AND NOT `deleted` LIMIT 1", + intval($item["uid"]), + dbesc($item["author-link"]), + dbesc($item["verb"]), + dbesc($item["parent-uri"]) + ); + if($r && count($r)) + return false; + } else + $is_like = false; + + if(($item["verb"] === ACTIVITY_TAG) && ($item["object-type"] === ACTIVITY_OBJ_TAGTERM)) { + + $xo = parse_xml_string($item["object"],false); + $xt = parse_xml_string($item["target"],false); + + if($xt->type == ACTIVITY_OBJ_NOTE) { + $r = q("SELECT `id`, `tag` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", + dbesc($xt->id), + intval($importer["importer_uid"]) + ); + + if(!count($r)) + return false; + + // extract tag, if not duplicate, add to parent item + if($xo->content) { + if(!(stristr($r[0]["tag"],trim($xo->content)))) { + q("UPDATE `item` SET `tag` = '%s' WHERE `id` = %d", + dbesc($r[0]["tag"] . (strlen($r[0]["tag"]) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'), + intval($r[0]["id"]) + ); + create_tags_from_item($r[0]["id"]); + } + } + } + } + } + return true; + } + + /** + * @brief Processes the link elements + * + * @param object $links link elements + * @param array $item the item record + */ + private function parse_links($links, &$item) { + $rel = ""; + $href = ""; + $type = ""; + $length = "0"; + $title = ""; + foreach ($links AS $link) { + foreach($link->attributes AS $attributes) { + if ($attributes->name == "href") + $href = $attributes->textContent; + if ($attributes->name == "rel") + $rel = $attributes->textContent; + if ($attributes->name == "type") + $type = $attributes->textContent; + if ($attributes->name == "length") + $length = $attributes->textContent; + if ($attributes->name == "title") + $title = $attributes->textContent; + } + if (($rel != "") AND ($href != "")) + switch($rel) { + case "alternate": + $item["plink"] = $href; + break; + case "enclosure": + $enclosure = $href; + if(strlen($item["attach"])) + $item["attach"] .= ","; + + $item["attach"] .= '[attach]href="'.$href.'" length="'.$length.'" type="'.$type.'" title="'.$title.'"[/attach]'; + break; + } + } + } + + /** + * @brief Processes the entry elements which contain the items and comments + * + * @param array $header Array of the header elements that always stay the same + * @param object $xpath XPath object + * @param object $entry entry elements + * @param array $importer Record of the importer user mixed with contact of the content + */ + private function process_entry($header, $xpath, $entry, $importer) { + + logger("Processing entries"); + + $item = $header; + + // Get the uri + $item["uri"] = $xpath->query("atom:id/text()", $entry)->item(0)->nodeValue; + + // Fetch the owner + $owner = self::fetchauthor($xpath, $entry, $importer, "dfrn:owner", true); + + $item["owner-name"] = $owner["name"]; + $item["owner-link"] = $owner["link"]; + $item["owner-avatar"] = $owner["avatar"]; + + // fetch the author + $author = self::fetchauthor($xpath, $entry, $importer, "atom:author", true); + + $item["author-name"] = $author["name"]; + $item["author-link"] = $author["link"]; + $item["author-avatar"] = $author["avatar"]; + + $item["title"] = $xpath->query("atom:title/text()", $entry)->item(0)->nodeValue; + + $item["created"] = $xpath->query("atom:published/text()", $entry)->item(0)->nodeValue; + $item["edited"] = $xpath->query("atom:updated/text()", $entry)->item(0)->nodeValue; + + $item["body"] = $xpath->query("dfrn:env/text()", $entry)->item(0)->nodeValue; + $item["body"] = str_replace(array(' ',"\t","\r","\n"), array('','','',''),$item["body"]); + // make sure nobody is trying to sneak some html tags by us + $item["body"] = notags(base64url_decode($item["body"])); + + $item["body"] = limit_body_size($item["body"]); + + /// @todo Do we really need this check for HTML elements? (It was copied from the old function) + if((strpos($item['body'],'<') !== false) && (strpos($item['body'],'>') !== false)) { + + $item['body'] = reltoabs($item['body'],$base_url); + + $item['body'] = html2bb_video($item['body']); + + $item['body'] = oembed_html2bbcode($item['body']); + + $config = HTMLPurifier_Config::createDefault(); + $config->set('Cache.DefinitionImpl', null); + + // we shouldn't need a whitelist, because the bbcode converter + // will strip out any unsupported tags. + + $purifier = new HTMLPurifier($config); + $item['body'] = $purifier->purify($item['body']); + + $item['body'] = @html2bbcode($item['body']); + } + + // We don't need the content element since "dfrn:env" is always present + //$item["body"] = $xpath->query("atom:content/text()", $entry)->item(0)->nodeValue; + + $item["last-child"] = $xpath->query("dfrn:comment-allow/text()", $entry)->item(0)->nodeValue; + $item["location"] = $xpath->query("dfrn:location/text()", $entry)->item(0)->nodeValue; + + $georsspoint = $xpath->query("georss:point", $entry); + if ($georsspoint) + $item["coord"] = $georsspoint->item(0)->nodeValue; + + $item["private"] = $xpath->query("dfrn:private/text()", $entry)->item(0)->nodeValue; + + $item["extid"] = $xpath->query("dfrn:extid/text()", $entry)->item(0)->nodeValue; + + if ($xpath->query("dfrn:extid/text()", $entry)->item(0)->nodeValue == "true") + $item["bookmark"] = true; + + $notice_info = $xpath->query("statusnet:notice_info", $entry); + if ($notice_info AND ($notice_info->length > 0)) { + foreach($notice_info->item(0)->attributes AS $attributes) { + if ($attributes->name == "source") + $item["app"] = strip_tags($attributes->textContent); + } + } + + $item["guid"] = $xpath->query("dfrn:diaspora_guid/text()", $entry)->item(0)->nodeValue; + + // We store the data from "dfrn:diaspora_signature" in a different table, this is done in "item_store" + $dsprsig = unxmlify($xpath->query("dfrn:diaspora_signature/text()", $entry)->item(0)->nodeValue); + if ($dsprsig != "") + $item["dsprsig"] = $dsprsig; + + $item["verb"] = $xpath->query("activity:verb/text()", $entry)->item(0)->nodeValue; + + if ($xpath->query("activity:object-type/text()", $entry)->item(0)->nodeValue != "") + $item["object-type"] = $xpath->query("activity:object-type/text()", $entry)->item(0)->nodeValue; + + $object = $xpath->query("activity:object", $entry)->item(0); + $item["object"] = self::transform_activity($xpath, $object, "object"); + + if (trim($item["object"]) != "") { + $r = parse_xml_string($item["object"], false); + if (isset($r->type)) + $item["object-type"] = $r->type; + } + + $target = $xpath->query("activity:target", $entry)->item(0); + $item["target"] = self::transform_activity($xpath, $target, "target"); + + $categories = $xpath->query("atom:category", $entry); + if ($categories) { + foreach ($categories AS $category) { + foreach($category->attributes AS $attributes) + if ($attributes->name == "term") { + $term = $attributes->textContent; + if(strlen($item["tag"])) + $item["tag"] .= ","; + + $item["tag"] .= "#[url=".App::get_baseurl()."/search?tag=".$term."]".$term."[/url]"; + } + } + } + + $enclosure = ""; + + $links = $xpath->query("atom:link", $entry); + if ($links) + self::parse_links($links, $item); + + // Is it a reply or a top level posting? + $item["parent-uri"] = $item["uri"]; + + $inreplyto = $xpath->query("thr:in-reply-to", $entry); + if (is_object($inreplyto->item(0))) + foreach($inreplyto->item(0)->attributes AS $attributes) + if ($attributes->name == "ref") + $item["parent-uri"] = $attributes->textContent; + + // Get the type of the item (Top level post, reply or remote reply) + $entrytype = self::get_entry_type($importer, $item); + + // Now assign the rest of the values that depend on the type of the message + if (in_array($entrytype, array(DFRN_REPLY, DFRN_REPLY_RC))) { + if (!isset($item["object-type"])) + $item["object-type"] = ACTIVITY_OBJ_COMMENT; + + if ($item["contact-id"] != $owner["contact-id"]) + $item["contact-id"] = $owner["contact-id"]; + + if (($item["network"] != $owner["network"]) AND ($owner["network"] != "")) + $item["network"] = $owner["network"]; + + if ($item["contact-id"] != $author["contact-id"]) + $item["contact-id"] = $author["contact-id"]; + + if (($item["network"] != $author["network"]) AND ($author["network"] != "")) + $item["network"] = $author["network"]; + + // This code was taken from the old DFRN code + // When activated, forums don't work. + // And: Why should we disallow commenting by followers? + // the behaviour is now similar to the Diaspora part. + //if($importer["rel"] == CONTACT_IS_FOLLOWER) { + // logger("Contact ".$importer["id"]." is only follower. Quitting", LOGGER_DEBUG); + // return; + //} + } + + if ($entrytype == DFRN_REPLY_RC) { + $item["type"] = "remote-comment"; + $item["wall"] = 1; + } elseif ($entrytype == DFRN_TOP_LEVEL) { + if (!isset($item["object-type"])) + $item["object-type"] = ACTIVITY_OBJ_NOTE; + + // Is it an event? + if ($item["object-type"] == ACTIVITY_OBJ_EVENT) { + logger("Item ".$item["uri"]." seems to contain an event.", LOGGER_DEBUG); + $ev = bbtoevent($item["body"]); + if((x($ev, "desc") || x($ev, "summary")) && x($ev, "start")) { + logger("Event in item ".$item["uri"]." was found.", LOGGER_DEBUG); + $ev["cid"] = $importer["id"]; + $ev["uid"] = $importer["uid"]; + $ev["uri"] = $item["uri"]; + $ev["edited"] = $item["edited"]; + $ev['private'] = $item['private']; + $ev["guid"] = $item["guid"]; + + $r = q("SELECT `id` FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", + dbesc($item["uri"]), + intval($importer["uid"]) + ); + if(count($r)) + $ev["id"] = $r[0]["id"]; + + $event_id = event_store($ev); + logger("Event ".$event_id." was stored", LOGGER_DEBUG); + return; + } + } + } + + $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", + dbesc($item["uri"]), + intval($importer["importer_uid"]) + ); + + if (!self::process_verbs($entrytype, $importer, $item, $is_like)) { + logger("Exiting because 'process_verbs' told us so", LOGGER_DEBUG); + return; + } + + // Update content if 'updated' changes + if(count($r)) { + if (self::update_content($r[0], $item, $importer, $entrytype)) + logger("Item ".$item["uri"]." was updated.", LOGGER_DEBUG); + else + logger("Item ".$item["uri"]." already existed.", LOGGER_DEBUG); + return; + } + + if (in_array($entrytype, array(DFRN_REPLY, DFRN_REPLY_RC))) { + $posted_id = item_store($item); + $parent = 0; + + if($posted_id) { + + logger("Reply from contact ".$item["contact-id"]." was stored with id ".$posted_id, LOGGER_DEBUG); + + $item["id"] = $posted_id; + + $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1", + intval($posted_id), + intval($importer["importer_uid"]) + ); + if(count($r)) { + $parent = $r[0]["parent"]; + $parent_uri = $r[0]["parent-uri"]; + } + + if(!$is_like) { + $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d", + dbesc(datetime_convert()), + intval($importer["importer_uid"]), + intval($r[0]["parent"]) + ); + + $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d", + dbesc(datetime_convert()), + intval($importer["importer_uid"]), + intval($posted_id) + ); + } + + if($posted_id AND $parent AND ($entrytype == DFRN_REPLY_RC)) { + logger("Notifying followers about comment ".$posted_id, LOGGER_DEBUG); + proc_run("php", "include/notifier.php", "comment-import", $posted_id); + } + + return true; + } + } else { // $entrytype == DFRN_TOP_LEVEL + if(!link_compare($item["owner-link"],$importer["url"])) { + // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery, + // but otherwise there's a possible data mixup on the sender's system. + // the tgroup delivery code called from item_store will correct it if it's a forum, + // but we're going to unconditionally correct it here so that the post will always be owned by our contact. + logger('Correcting item owner.', LOGGER_DEBUG); + $item["owner-name"] = $importer["senderName"]; + $item["owner-link"] = $importer["url"]; + $item["owner-avatar"] = $importer["thumb"]; + } + + if(($importer["rel"] == CONTACT_IS_FOLLOWER) && (!tgroup_check($importer["importer_uid"], $item))) { + logger("Contact ".$importer["id"]." is only follower and tgroup check was negative.", LOGGER_DEBUG); + return; + } + + // This is my contact on another system, but it's really me. + // Turn this into a wall post. + $notify = item_is_remote_self($importer, $item); + + $posted_id = item_store($item, false, $notify); + + logger("Item was stored with id ".$posted_id, LOGGER_DEBUG); + + if(stristr($item["verb"],ACTIVITY_POKE)) + self::do_poke($item, $importer, $posted_id); + } + } + + /** + * @brief Deletes items + * + * @param object $xpath XPath object + * @param object $deletion deletion elements + * @param array $importer Record of the importer user mixed with contact of the content + */ + private function process_deletion($xpath, $deletion, $importer) { + + logger("Processing deletions"); + + foreach($deletion->attributes AS $attributes) { + if ($attributes->name == "ref") + $uri = $attributes->textContent; + if ($attributes->name == "when") + $when = $attributes->textContent; + } + if ($when) + $when = datetime_convert("UTC", "UTC", $when, "Y-m-d H:i:s"); + else + $when = datetime_convert("UTC", "UTC", "now", "Y-m-d H:i:s"); + + if (!$uri OR !$importer["id"]) + return false; + + /// @todo Only select the used fields + $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id` + WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1", + dbesc($uri), + intval($importer["uid"]), + intval($importer["id"]) + ); + if(!count($r)) { + logger("Item with uri ".$uri." from contact ".$importer["id"]." for user ".$importer["uid"]." wasn't found.", LOGGER_DEBUG); + return; + } else { + + $item = $r[0]; + + $entrytype = self::get_entry_type($importer, $item); + + if(!$item["deleted"]) + logger('deleting item '.$item["id"].' uri='.$uri, LOGGER_DEBUG); + else + return; + + if($item["object-type"] === ACTIVITY_OBJ_EVENT) { + logger("Deleting event ".$item["event-id"], LOGGER_DEBUG); + event_delete($item["event-id"]); + } + + if(($item["verb"] === ACTIVITY_TAG) && ($item["object-type"] === ACTIVITY_OBJ_TAGTERM)) { + $xo = parse_xml_string($item["object"],false); + $xt = parse_xml_string($item["target"],false); + if($xt->type === ACTIVITY_OBJ_NOTE) { + $i = q("SELECT `id`, `contact-id`, `tag` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", + dbesc($xt->id), + intval($importer["importer_uid"]) + ); + if(count($i)) { + + // For tags, the owner cannot remove the tag on the author's copy of the post. + + $owner_remove = (($item["contact-id"] == $i[0]["contact-id"]) ? true: false); + $author_remove = (($item["origin"] && $item["self"]) ? true : false); + $author_copy = (($item["origin"]) ? true : false); + + if($owner_remove && $author_copy) + return; + if($author_remove || $owner_remove) { + $tags = explode(',',$i[0]["tag"]); + $newtags = array(); + if(count($tags)) { + foreach($tags as $tag) + if(trim($tag) !== trim($xo->body)) + $newtags[] = trim($tag); + } + q("UPDATE `item` SET `tag` = '%s' WHERE `id` = %d", + dbesc(implode(',',$newtags)), + intval($i[0]["id"]) + ); + create_tags_from_item($i[0]["id"]); + } + } + } + } + + if($entrytype == DFRN_TOP_LEVEL) { + $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', + `body` = '', `title` = '' + WHERE `parent-uri` = '%s' AND `uid` = %d", + dbesc($when), + dbesc(datetime_convert()), + dbesc($uri), + intval($importer["uid"]) + ); + create_tags_from_itemuri($uri, $importer["uid"]); + create_files_from_itemuri($uri, $importer["uid"]); + update_thread_uri($uri, $importer["uid"]); + } else { + $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', + `body` = '', `title` = '' + WHERE `uri` = '%s' AND `uid` = %d", + dbesc($when), + dbesc(datetime_convert()), + dbesc($uri), + intval($importer["uid"]) + ); + create_tags_from_itemuri($uri, $importer["uid"]); + create_files_from_itemuri($uri, $importer["uid"]); + update_thread_uri($uri, $importer["importer_uid"]); + if($item["last-child"]) { + // ensure that last-child is set in case the comment that had it just got wiped. + q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ", + dbesc(datetime_convert()), + dbesc($item["parent-uri"]), + intval($item["uid"]) + ); + // who is the last child now? + $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d + ORDER BY `created` DESC LIMIT 1", + dbesc($item["parent-uri"]), + intval($importer["uid"]) + ); + if(count($r)) { + q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d", + intval($r[0]["id"]) + ); + } + } + // if this is a relayed delete, propagate it to other recipients + + if($entrytype == DFRN_REPLY_RC) { + logger("Notifying followers about deletion of post ".$item["id"], LOGGER_DEBUG); + proc_run("php", "include/notifier.php","drop", $item["id"]); + } + } + } + } + + /** + * @brief Imports a DFRN message + * + * @param text $xml The DFRN message + * @param array $importer Record of the importer user mixed with contact of the content + * @param bool $sort_by_date Is used when feeds are polled + */ + public static function import($xml,$importer, $sort_by_date = false) { + + if ($xml == "") + return; + + if($importer["readonly"]) { + // We aren't receiving stuff from this person. But we will quietly ignore them + // rather than a blatant "go away" message. + logger('ignoring contact '.$importer["id"]); + return; + } + + $doc = new DOMDocument(); + @$doc->loadXML($xml); + + $xpath = new DomXPath($doc); + $xpath->registerNamespace("atom", NAMESPACE_ATOM1); + $xpath->registerNamespace("thr", NAMESPACE_THREAD); + $xpath->registerNamespace("at", NAMESPACE_TOMB); + $xpath->registerNamespace("media", NAMESPACE_MEDIA); + $xpath->registerNamespace("dfrn", NAMESPACE_DFRN); + $xpath->registerNamespace("activity", NAMESPACE_ACTIVITY); + $xpath->registerNamespace("georss", NAMESPACE_GEORSS); + $xpath->registerNamespace("poco", NAMESPACE_POCO); + $xpath->registerNamespace("ostatus", NAMESPACE_OSTATUS); + $xpath->registerNamespace("statusnet", NAMESPACE_STATUSNET); + + $header = array(); + $header["uid"] = $importer["uid"]; + $header["network"] = NETWORK_DFRN; + $header["type"] = "remote"; + $header["wall"] = 0; + $header["origin"] = 0; + $header["contact-id"] = $importer["id"]; + + // Update the contact table if the data has changed + // Only the "dfrn:owner" in the head section contains all data + self::fetchauthor($xpath, $doc->firstChild, $importer, "dfrn:owner", false); + + logger("Import DFRN message for user ".$importer["uid"]." from contact ".$importer["id"], LOGGER_DEBUG); + + // is it a public forum? Private forums aren't supported by now with this method + $forum = intval($xpath->evaluate("/atom:feed/dfrn:community/text()", $context)->item(0)->nodeValue); + + if ($forum != $importer["forum"]) + q("UPDATE `contact` SET `forum` = %d WHERE `forum` != %d AND `id` = %d", + intval($forum), intval($forum), + intval($importer["id"]) + ); + + $mails = $xpath->query("/atom:feed/dfrn:mail"); + foreach ($mails AS $mail) + self::process_mail($xpath, $mail, $importer); + + $suggestions = $xpath->query("/atom:feed/dfrn:suggest"); + foreach ($suggestions AS $suggestion) + self::process_suggestion($xpath, $suggestion, $importer); + + $relocations = $xpath->query("/atom:feed/dfrn:relocate"); + foreach ($relocations AS $relocation) + self::process_relocation($xpath, $relocation, $importer); + + $deletions = $xpath->query("/atom:feed/at:deleted-entry"); + foreach ($deletions AS $deletion) + self::process_deletion($xpath, $deletion, $importer); + + if (!$sort_by_date) { + $entries = $xpath->query("/atom:feed/atom:entry"); + foreach ($entries AS $entry) + self::process_entry($header, $xpath, $entry, $importer); + } else { + $newentries = array(); + $entries = $xpath->query("/atom:feed/atom:entry"); + foreach ($entries AS $entry) { + $created = $xpath->query("atom:published/text()", $entry)->item(0)->nodeValue; + $newentries[strtotime($created)] = $entry; + } + + // Now sort after the publishing date + ksort($newentries); + + foreach ($newentries AS $entry) + self::process_entry($header, $xpath, $entry, $importer); + } + logger("Import done for user ".$importer["uid"]." from contact ".$importer["id"], LOGGER_DEBUG); + } +} +?> diff --git a/include/diaspora.php b/include/diaspora.php index 75e4a7106..93fe2a472 100644 --- a/include/diaspora.php +++ b/include/diaspora.php @@ -14,6 +14,7 @@ require_once('include/queue_fn.php'); require_once('include/lock.php'); require_once('include/threads.php'); require_once('mod/share.php'); +require_once('include/enotify.php'); function diaspora_dispatch_public($msg) { @@ -728,7 +729,7 @@ function diaspora_request($importer,$xml) { require_once('include/Photo.php'); - $photos = import_profile_photo($contact_record['photo'],$importer['uid'],$contact_record['id']); + update_contact_avatar($contact_record['photo'],$importer['uid'],$contact_record['id']); // technically they are sharing with us (CONTACT_IS_SHARING), // but if our page-type is PAGE_COMMUNITY or PAGE_SOAPBOX @@ -739,26 +740,17 @@ function diaspora_request($importer,$xml) { else $new_relation = CONTACT_IS_FOLLOWER; - $r = q("UPDATE `contact` SET - `photo` = '%s', - `thumb` = '%s', - `micro` = '%s', - `rel` = %d, + $r = q("UPDATE `contact` SET `rel` = %d, `name-date` = '%s', `uri-date` = '%s', - `avatar-date` = '%s', `blocked` = 0, `pending` = 0, `writable` = 1 WHERE `id` = %d ", - dbesc($photos[0]), - dbesc($photos[1]), - dbesc($photos[2]), intval($new_relation), dbesc(datetime_convert()), dbesc(datetime_convert()), - dbesc(datetime_convert()), intval($contact_record['id']) ); @@ -826,6 +818,23 @@ function diaspora_plink($addr, $guid) { return 'https://'.substr($addr,strpos($addr,'@')+1).'/posts/'.$guid; } +function diaspora_repair_signature($signature, $handle = "", $level = 1) { + + if ($signature == "") + return($signature); + + if (base64_encode(base64_decode(base64_decode($signature))) == base64_decode($signature)) { + $signature = base64_decode($signature); + logger("Repaired double encoded signature from Diaspora/Hubzilla handle ".$handle." - level ".$level, LOGGER_DEBUG); + + // Do a recursive call to be able to fix even multiple levels + if ($level < 10) + $signature = diaspora_repair_signature($signature, $handle, ++$level); + } + + return($signature); +} + function diaspora_post($importer,$xml,$msg) { $a = get_app(); @@ -1565,62 +1574,22 @@ function diaspora_comment($importer,$xml,$msg) { //); //} - if(($parent_item['origin']) && (! $parent_author_signature)) { + // If we are the origin of the parent we store the original signature and notify our followers + if($parent_item['origin']) { + $author_signature_base64 = base64_encode($author_signature); + $author_signature_base64 = diaspora_repair_signature($author_signature_base64, $diaspora_handle); + 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($author_signature_base64), 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. - + // notify others 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['uid']) - ); - - 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['uid'], - 'item' => $datarray, - 'link' => $a->get_baseurl().'/display/'.urlencode($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; } @@ -1775,7 +1744,6 @@ function diaspora_conversation($importer,$xml,$msg) { intval($conversation['id']) ); - require_once('include/enotify.php'); notification(array( 'type' => NOTIFY_MAIL, 'notify_flags' => $importer['notify-flags'], @@ -1973,11 +1941,15 @@ function diaspora_photo($importer,$xml,$msg,$attempt=1) { array($remote_photo_name, 'scaled_full_' . $remote_photo_name)); if(strpos($parent_item['body'],$link_text) === false) { + + $parent_item['body'] = $link_text . $parent_item['body']; + $r = q("UPDATE `item` SET `body` = '%s', `visible` = 1 WHERE `id` = %d AND `uid` = %d", - dbesc($link_text . $parent_item['body']), + dbesc($parent_item['body']), intval($parent_item['id']), intval($parent_item['uid']) ); + put_item_in_cache($parent_item, true); update_thread($parent_item['id']); } @@ -2222,21 +2194,21 @@ EOT; // ); //} - if(! $parent_author_signature) { + // If we are the origin of the parent we store the original signature and notify our followers + if($parent_item['origin']) { + $author_signature_base64 = base64_encode($author_signature); + $author_signature_base64 = diaspora_repair_signature($author_signature_base64, $diaspora_handle); + 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($author_signature_base64), 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)) + // notify others proc_run('php','include/notifier.php','comment-import',$message_id); + } return; } @@ -2332,8 +2304,7 @@ function diaspora_signed_retraction($importer,$xml,$msg) { return; } - } - else { + } else { $sig_decode = base64_decode($sig); @@ -2367,7 +2338,7 @@ function diaspora_signed_retraction($importer,$xml,$msg) { intval($r[0]['parent']) ); if(count($p)) { - if(($p[0]['origin']) && (! $parent_author_signature)) { + if($p[0]['origin']) { q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ", $r[0]['id'], dbesc($signed_data), @@ -2407,10 +2378,10 @@ function diaspora_profile($importer,$xml,$msg) { if(! $contact) return; - if($contact['blocked']) { - logger('diaspora_post: Ignoring this author.'); - return 202; - } + //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); @@ -2418,6 +2389,8 @@ function diaspora_profile($importer,$xml,$msg) { $location = diaspora2bb(unxmlify($xml->location)); $about = diaspora2bb(unxmlify($xml->bio)); $gender = unxmlify($xml->gender); + $searchable = (unxmlify($xml->searchable) == "true"); + $nsfw = (unxmlify($xml->nsfw) == "true"); $tags = unxmlify($xml->tag_string); $tags = explode("#", $tags); @@ -2432,6 +2405,8 @@ function diaspora_profile($importer,$xml,$msg) { $keywords = implode(", ", $keywords); $handle_parts = explode("@", $diaspora_handle); + $nick = $handle_parts[0]; + if($name === '') { $name = $handle_parts[0]; } @@ -2448,7 +2423,7 @@ function diaspora_profile($importer,$xml,$msg) { require_once('include/Photo.php'); - $images = import_profile_photo($image_url,$importer['uid'],$contact['id']); + update_contact_avatar($image_url,$importer['uid'],$contact['id']); // Generic birthday. We don't know the timezone. The year is irrelevant. @@ -2466,12 +2441,11 @@ function diaspora_profile($importer,$xml,$msg) { /// @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', `location` = '%s', `about` = '%s', `keywords` = '%s', `gender` = '%s' WHERE `id` = %d AND `uid` = %d", + $r = q("UPDATE `contact` SET `name` = '%s', `nick` = '%s', `addr` = '%s', `name-date` = '%s', `bd` = '%s', + `location` = '%s', `about` = '%s', `keywords` = '%s', `gender` = '%s' WHERE `id` = %d AND `uid` = %d", dbesc($name), - dbesc(datetime_convert()), - dbesc($images[0]), - dbesc($images[1]), - dbesc($images[2]), + dbesc($nick), + dbesc($diaspora_handle), dbesc(datetime_convert()), dbesc($birthday), dbesc($location), @@ -2482,26 +2456,17 @@ function diaspora_profile($importer,$xml,$msg) { intval($importer['uid']) ); - if (unxmlify($xml->searchable) == "true") { + if ($searchable) { require_once('include/socgraph.php'); - poco_check($contact['url'], $name, NETWORK_DIASPORA, $images[0], $about, $location, $gender, $keywords, "", + poco_check($contact['url'], $name, NETWORK_DIASPORA, $image_url, $about, $location, $gender, $keywords, "", datetime_convert(), 2, $contact['id'], $importer['uid']); } - $profileurl = ""; - $author = q("SELECT * FROM `unique_contacts` WHERE `url`='%s' LIMIT 1", - dbesc(normalise_link($contact['url']))); - - if (count($author) == 0) { - q("INSERT INTO `unique_contacts` (`url`, `name`, `avatar`, `location`, `about`) VALUES ('%s', '%s', '%s', '%s', '%s')", - dbesc(normalise_link($contact['url'])), dbesc($name), dbesc($location), dbesc($about), dbesc($images[0])); - - $author = q("SELECT id FROM unique_contacts WHERE url='%s' LIMIT 1", - dbesc(normalise_link($contact['url']))); - } else if (normalise_link($contact['url']).$name.$location.$about != normalise_link($author[0]["url"]).$author[0]["name"].$author[0]["location"].$author[0]["about"]) { - q("UPDATE unique_contacts SET name = '%s', avatar = '%s', `location` = '%s', `about` = '%s' WHERE url = '%s'", - dbesc($name), dbesc($images[0]), dbesc($location), dbesc($about), dbesc(normalise_link($contact['url']))); - } + update_gcontact(array("url" => $contact['url'], "network" => NETWORK_DIASPORA, "generation" => 2, + "photo" => $image_url, "name" => $name, "location" => $location, + "about" => $about, "birthday" => $birthday, "gender" => $gender, + "addr" => $diaspora_handle, "nick" => $nick, "keywords" => $keywords, + "hide" => !$searchable, "nsfw" => $nsfw)); /* if($r) { if($oldphotos) { @@ -2643,11 +2608,12 @@ function diaspora_send_status($item,$owner,$contact,$public_batch = false) { } logger('diaspora_send_status: '.$owner['username'].' -> '.$contact['name'].' base message: '.$msg, LOGGER_DATA); + logger('send guid '.$item['guid'], LOGGER_DEBUG); $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); + $return_code = diaspora_transmit($owner,$contact,$slap,$public_batch,false,$item['guid']); logger('diaspora_send_status: guid: '.$item['guid'].' result '.$return_code, LOGGER_DEBUG); @@ -2758,10 +2724,12 @@ function diaspora_send_images($item,$owner,$contact,$images,$public_batch = fals logger('diaspora_send_photo: base message: ' . $msg, LOGGER_DATA); + logger('send guid '.$r[0]['guid'], LOGGER_DEBUG); + $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); + diaspora_transmit($owner,$contact,$slap,$public_batch,false,$r[0]['guid']); } } @@ -2815,7 +2783,7 @@ function diaspora_send_followup($item,$owner,$contact,$public_batch = false) { // sign it if($like) - $signed_text = $item['guid'] . ';' . $target_type . ';' . $parent['guid'] . ';' . $positive . ';' . $myaddr; + $signed_text = $positive . ';' . $item['guid'] . ';' . $target_type . ';' . $parent['guid'] . ';' . $myaddr; else $signed_text = $item['guid'] . ';' . $parent['guid'] . ';' . $text . ';' . $myaddr; @@ -2832,11 +2800,12 @@ function diaspora_send_followup($item,$owner,$contact,$public_batch = false) { )); logger('diaspora_followup: base message: ' . $msg, LOGGER_DATA); + logger('send guid '.$item['guid'], LOGGER_DEBUG); $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)); + return(diaspora_transmit($owner,$contact,$slap,$public_batch,false,$item['guid'])); } @@ -2847,9 +2816,6 @@ function diaspora_send_relay($item,$owner,$contact,$public_batch = false) { $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 @@ -2900,61 +2866,53 @@ function diaspora_send_relay($item,$owner,$contact,$public_batch = false) { // 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", + $r = q("SELECT `signed_text`, `signature`, `signer` FROM `sign` WHERE " . $sql_sign_id . " = %d LIMIT 1", intval($item['id']) ); - if(count($r)) { + if(count($r)) { $orig_sign = $r[0]; $signed_text = $orig_sign['signed_text']; $authorsig = $orig_sign['signature']; $handle = $orig_sign['signer']; + + // Split the signed text + $signed_parts = explode(";", $signed_text); + + // Remove the parent guid + array_shift($signed_parts); + + // Remove the comment guid + array_shift($signed_parts); + + // Remove the handle + array_pop($signed_parts); + + // Glue the parts together + $text = implode(";", $signed_parts); } else { + // This part is meant for cases where we don't have the signatur. (Which shouldn't happen with posts from Diaspora and Friendica) + // This means that the comment won't be accepted by newer Diaspora servers - // 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; - }*/ + $body = $item['body']; + $text = html_entity_decode(bb2diaspora($body)); - /* 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; - $handle = diaspora_handle_from_contact($item['contact-id']); - if(! $handle) - return; + if($relay_retract) + $signed_text = $item['guid'] . ';' . $target_type; + elseif($like) + $signed_text = $item['guid'] . ';' . $target_type . ';' . $parent['guid'] . ';' . $positive . ';' . $handle; + else + $signed_text = $item['guid'] . ';' . $parent['guid'] . ';' . $text . ';' . $handle; - - 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; + $authorsig = base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256')); + } // 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')); + $parentauthorsig = base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256')); $msg = replace_macros($tpl,array( '$guid' => xmlify($item['guid']), @@ -2968,12 +2926,12 @@ function diaspora_send_relay($item,$owner,$contact,$public_batch = false) { )); logger('diaspora_send_relay: base message: ' . $msg, LOGGER_DATA); - + logger('send guid '.$item['guid'], LOGGER_DEBUG); $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)); + return(diaspora_transmit($owner,$contact,$slap,$public_batch,false,$item['guid'])); } @@ -3005,10 +2963,12 @@ function diaspora_send_retraction($item,$owner,$contact,$public_batch = false) { '$signature' => xmlify(base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256'))) )); + logger('send guid '.$item['guid'], LOGGER_DEBUG); + $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)); + return(diaspora_transmit($owner,$contact,$slap,$public_batch,false,$item['guid'])); } function diaspora_send_mail($item,$owner,$contact) { @@ -3065,16 +3025,17 @@ function diaspora_send_mail($item,$owner,$contact) { } logger('diaspora_conversation: ' . print_r($xmsg,true), LOGGER_DATA); + logger('send guid '.$item['guid'], LOGGER_DEBUG); $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)); + return(diaspora_transmit($owner,$contact,$slap,false,false,$item['guid'])); } -function diaspora_transmit($owner,$contact,$slap,$public_batch,$queue_run=false) { +function diaspora_transmit($owner,$contact,$slap,$public_batch,$queue_run=false,$guid = "") { $enabled = intval(get_config('system','diaspora_enabled')); if(! $enabled) { @@ -3087,9 +3048,9 @@ function diaspora_transmit($owner,$contact,$slap,$public_batch,$queue_run=false) if(! $dest_url) { logger('diaspora_transmit: no url for contact: ' . $contact['id'] . ' batch mode =' . $public_batch); return 0; - } + } - logger('diaspora_transmit: ' . $logid . ' ' . $dest_url); + logger('diaspora_transmit: '.$logid.'-'.$guid.' '.$dest_url); if( (! $queue_run) && (was_recently_delayed($contact['id'])) ) { $return_code = 0; @@ -3104,7 +3065,7 @@ function diaspora_transmit($owner,$contact,$slap,$public_batch,$queue_run=false) } } - logger('diaspora_transmit: ' . $logid . ' returns: ' . $return_code); + logger('diaspora_transmit: '.$logid.'-'.$guid.' returns: '.$return_code); if((! $return_code) || (($return_code == 503) && (stristr($a->get_curl_headers(),'retry-after')))) { logger('diaspora_transmit: queue message'); diff --git a/include/discover_poco.php b/include/discover_poco.php index 6293317d3..a8f670334 100644 --- a/include/discover_poco.php +++ b/include/discover_poco.php @@ -76,11 +76,18 @@ function discover_poco_run(&$argv, &$argc){ update_suggestions(); elseif (($mode == 2) AND get_config('system','poco_completion')) discover_users(); - elseif (($mode == 1) AND ($search != "") and get_config('system','poco_local_search')) + elseif (($mode == 1) AND ($search != "") and get_config('system','poco_local_search')) { discover_directory($search); - elseif (($mode == 0) AND ($search == "") and (get_config('system','poco_discovery') > 0)) + gs_search_user($search); + } elseif (($mode == 0) AND ($search == "") and (get_config('system','poco_discovery') > 0)) { + // Query Friendica and Hubzilla servers for their users poco_discover(); + // Query GNU Social servers for their users ("statistics" addon has to be enabled on the GS server) + if (!get_config('system','ostatus_disabled')) + gs_discover(); + } + logger('end '.$search); return; @@ -128,7 +135,7 @@ function discover_users() { else $server_url = poco_detect_server($user["url"]); - if (poco_check_server($server_url, $gcontacts[0]["network"])) { + if (($server_url == "") OR poco_check_server($server_url, $gcontacts[0]["network"])) { logger('Check user '.$user["url"]); poco_last_updated($user["url"], true); @@ -191,6 +198,36 @@ function discover_directory($search) { Cache::set("dirsearch:".$search, time(), CACHE_DAY); } +/** + * @brief Search for GNU Social user with gstools.org + * + * @param str $search User name + */ +function gs_search_user($search) { + + $a = get_app(); + + $url = "http://gstools.org/api/users_search/".urlencode($search); + + $result = z_fetch_url($url); + if (!$result["success"]) + return false; + + $contacts = json_decode($result["body"]); + + if ($contacts->status == 'ERROR') + return false; + + foreach($contacts->data AS $user) { + $contact = probe_url($user->site_address."/".$user->name); + if ($contact["network"] != NETWORK_PHANTOM) { + $contact["about"] = $user->description; + update_gcontact($contact); + } + } +} + + if (array_search(__file__,get_included_files())===0){ discover_poco_run($_SERVER["argv"],$_SERVER["argc"]); killme(); diff --git a/include/enotify.php b/include/enotify.php index e02c61383..54b59857c 100644 --- a/include/enotify.php +++ b/include/enotify.php @@ -633,4 +633,129 @@ function notification($params) { } +/** + * @brief Checks for item related notifications and sends them + * + * @param int $itemid ID of the item for which the check should be done + * @param int $uid User ID + * @param str $defaulttype (Optional) Forces a notification with this type. + */ +function check_item_notification($itemid, $uid, $defaulttype = "") { + + $notification_data = array("uid" => $uid, "profiles" => array()); + call_hooks('check_item_notification', $notification_data); + + $profiles = $notification_data["profiles"]; + + $user = q("SELECT `notify-flags`, `language`, `username`, `email` FROM `user` WHERE `uid` = %d", intval($uid)); + if (!$user) + return false; + + $owner = q("SELECT `id`, `url` FROM `contact` WHERE `self` AND `uid` = %d LIMIT 1", intval($uid)); + if (!$owner) + return false; + + $profiles[] = $owner[0]["url"]; + + $profiles2 = array(); + + foreach ($profiles AS $profile) { + $profiles2[] = normalise_link($profile); + $profiles2[] = str_replace("http://", "https://", normalise_link($profile)); + } + + $profiles = $profiles2; + + $profile_list = ""; + + foreach ($profiles AS $profile) { + if ($profile_list != "") + $profile_list .= "', '"; + + $profile_list .= dbesc($profile); + } + + $profile_list = "'".$profile_list."'"; + + // Only act if it is a "real" post + // We need the additional check for the "local_profile" because of mixed situations on connector networks + $item = q("SELECT `id`, `mention`, `tag`,`parent`, `title`, `body`, `author-name`, `author-link`, `author-avatar`, `guid`, + `parent-uri`, `uri`, `contact-id` + FROM `item` WHERE `id` = %d AND `verb` IN ('%s', '') AND `type` != 'activity' AND + NOT (`author-link` IN ($profile_list)) LIMIT 1", + intval($itemid), dbesc(ACTIVITY_POST)); + if (!$item) + return false; + + // Generate the notification array + $params = array(); + $params["uid"] = $uid; + $params["notify_flags"] = $user[0]["notify-flags"]; + $params["language"] = $user[0]["language"]; + $params["to_name"] = $user[0]["username"]; + $params["to_email"] = $user[0]["email"]; + $params["item"] = $item[0]; + $params["parent"] = $item[0]["parent"]; + $params["link"] = App::get_baseurl().'/display/'.urlencode($item[0]["guid"]); + $params["otype"] = 'item'; + $params["source_name"] = $item[0]["author-name"]; + $params["source_link"] = $item[0]["author-link"]; + $params["source_photo"] = $item[0]["author-avatar"]; + + if ($item[0]["parent-uri"] === $item[0]["uri"]) { + // Send a notification for every new post? + $r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1", + intval($item[0]['contact-id']), + intval($uid) + ); + $send_notification = count($r); + + if (!$send_notification) { + $tags = q("SELECT `url` FROM `term` WHERE `otype` = %d AND `oid` = %d AND `type` = %d AND `uid` = %d", + intval(TERM_OBJ_POST), intval($itemid), intval(TERM_MENTION), intval($uid)); + + if (count($tags)) { + foreach ($tags AS $tag) { + $r = q("SELECT `id` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d AND `notify_new_posts`", + normalise_link($tag["url"]), intval($uid)); + if (count($r)) + $send_notification = true; + } + } + } + + if ($send_notification) { + $params["type"] = NOTIFY_SHARE; + $params["verb"] = ACTIVITY_TAG; + } + } + + // Is the user mentioned in this post? + $tagged = false; + + foreach ($profiles AS $profile) { + if (strpos($item[0]["tag"], "=".$profile."]") OR strpos($item[0]["body"], "=".$profile."]")) + $tagged = true; + } + + if ($item[0]["mention"] OR $tagged OR ($defaulttype == NOTIFY_TAGSELF)) { + $params["type"] = NOTIFY_TAGSELF; + $params["verb"] = ACTIVITY_TAG; + } + + // Is it a post that the user had started or where he interacted? + $parent = q("SELECT `thread`.`iid` FROM `thread` INNER JOIN `item` ON `item`.`parent` = `thread`.`iid` + WHERE `thread`.`iid` = %d AND `thread`.`uid` = %d AND NOT `thread`.`ignored` AND + (`thread`.`mention` OR `item`.`author-link` IN ($profile_list)) + LIMIT 1", + intval($item[0]["parent"]), intval($uid)); + + if ($parent AND !isset($params["type"])) { + $params["type"] = NOTIFY_COMMENT; + $params["verb"] = ACTIVITY_POST; + } + + if (isset($params["type"])) + notification($params); +} ?> diff --git a/include/event.php b/include/event.php index c4111dc0b..a9f054fc2 100644 --- a/include/event.php +++ b/include/event.php @@ -61,7 +61,7 @@ function format_event_html($ev, $simple = false) { . bbcode($ev['location']) . '

' . "\r\n"; - if (strpos($ev['location'], "[map")===False) { + if (strpos($ev['location'], "[map") !== False) { $map = generate_named_map($ev['location']); if ($map!==$ev['location']) $o.=$map; } @@ -76,7 +76,6 @@ function format_event_html($ev, $simple = false) { function parse_event($h) { require_once('include/Scrape.php'); - require_once('library/HTMLPurifier.auto.php'); require_once('include/html2bbcode'); $h = '' . $h . ''; diff --git a/include/expire.php b/include/expire.php index 308680421..873c594e8 100644 --- a/include/expire.php +++ b/include/expire.php @@ -18,7 +18,6 @@ function expire_run(&$argv, &$argc){ require_once('include/session.php'); require_once('include/datetime.php'); - require_once('library/simplepie/simplepie.inc'); require_once('include/items.php'); require_once('include/Contact.php'); diff --git a/include/features.php b/include/features.php index 263960457..87a9b46d5 100644 --- a/include/features.php +++ b/include/features.php @@ -1,23 +1,25 @@ $uid, 'feature' => $feature, 'enabled' => $x); call_hooks('feature_enabled',$arr); @@ -42,14 +44,17 @@ function get_feature_default($feature) { } /** - * @ brief get a list of all available features + * @brief Get a list of all available features + * * The array includes the setting group, the setting name, * explainations for the setting and if it's enabled or disabled * by default * + * @param bool $filtered True removes any locked features + * * @return array */ -function get_features() { +function get_features($filtered = true) { $arr = array( @@ -57,56 +62,78 @@ function get_features() { 'general' => array( t('General Features'), //array('expire', t('Content Expiration'), t('Remove old posts/comments after a period of time')), - array('multi_profiles', t('Multiple Profiles'), t('Ability to create multiple profiles'),false), - array('photo_location', t('Photo Location'), t('Photo metadata is normally stripped. This extracts the location (if present) prior to stripping metadata and links it to a map.'),false), + array('multi_profiles', t('Multiple Profiles'), t('Ability to create multiple profiles'), false, get_config('feature_lock','multi_profiles')), + array('photo_location', t('Photo Location'), t('Photo metadata is normally stripped. This extracts the location (if present) prior to stripping metadata and links it to a map.'), false, get_config('feature_lock','photo_location')), ), // Post composition 'composition' => array( t('Post Composition Features'), - array('richtext', t('Richtext Editor'), t('Enable richtext editor'),false), - array('preview', t('Post Preview'), t('Allow previewing posts and comments before publishing them'),false), - array('aclautomention', t('Auto-mention Forums'), t('Add/remove mention when a fourm page is selected/deselected in ACL window.'),false), + array('richtext', t('Richtext Editor'), t('Enable richtext editor'), false, get_config('feature_lock','richtext')), + array('preview', t('Post Preview'), t('Allow previewing posts and comments before publishing them'), false, get_config('feature_lock','preview')), + array('aclautomention', t('Auto-mention Forums'), t('Add/remove mention when a fourm page is selected/deselected in ACL window.'), false, get_config('feature_lock','aclautomention')), ), // Network sidebar widgets 'widgets' => array( t('Network Sidebar Widgets'), - array('archives', t('Search by Date'), t('Ability to select posts by date ranges'),false), - array('forumlist_widget', t('List Forums'), t('Enable widget to display the forums your are connected with'),true), - array('groups', t('Group Filter'), t('Enable widget to display Network posts only from selected group'),false), - array('networks', t('Network Filter'), t('Enable widget to display Network posts only from selected network'),false), - array('savedsearch', t('Saved Searches'), t('Save search terms for re-use'),false), + array('archives', t('Search by Date'), t('Ability to select posts by date ranges'), false, get_config('feature_lock','archives')), + array('forumlist_widget', t('List Forums'), t('Enable widget to display the forums your are connected with'), true, get_config('feature_lock','forumlist_widget')), + array('groups', t('Group Filter'), t('Enable widget to display Network posts only from selected group'), false, get_config('feature_lock','groups')), + array('networks', t('Network Filter'), t('Enable widget to display Network posts only from selected network'), false, get_config('feature_lock','networks')), + array('savedsearch', t('Saved Searches'), t('Save search terms for re-use'), false, get_config('feature_lock','savedsearch')), ), // Network tabs 'net_tabs' => array( t('Network Tabs'), - array('personal_tab', t('Network Personal Tab'), t('Enable tab to display only Network posts that you\'ve interacted on'),false), - array('new_tab', t('Network New Tab'), t('Enable tab to display only new Network posts (from the last 12 hours)'),false), - array('link_tab', t('Network Shared Links Tab'), t('Enable tab to display only Network posts with links in them'),false), + array('personal_tab', t('Network Personal Tab'), t('Enable tab to display only Network posts that you\'ve interacted on'), false, get_config('feature_lock','personal_tab')), + array('new_tab', t('Network New Tab'), t('Enable tab to display only new Network posts (from the last 12 hours)'), false, get_config('feature_lock','new_tab')), + array('link_tab', t('Network Shared Links Tab'), t('Enable tab to display only Network posts with links in them'), false, get_config('feature_lock','link_tab')), ), // Item tools 'tools' => array( t('Post/Comment Tools'), - array('multi_delete', t('Multiple Deletion'), t('Select and delete multiple posts/comments at once'),false), - array('edit_posts', t('Edit Sent Posts'), t('Edit and correct posts and comments after sending'),false), - array('commtag', t('Tagging'), t('Ability to tag existing posts'),false), - array('categories', t('Post Categories'), t('Add categories to your posts'),false), - array('filing', t('Saved Folders'), t('Ability to file posts under folders'),false), - array('dislike', t('Dislike Posts'), t('Ability to dislike posts/comments')), - array('star_posts', t('Star Posts'), t('Ability to mark special posts with a star indicator'),false), - array('ignore_posts', t('Mute Post Notifications'), t('Ability to mute notifications for a thread'),false), + array('multi_delete', t('Multiple Deletion'), t('Select and delete multiple posts/comments at once'), false, get_config('feature_lock','multi_delete')), + array('edit_posts', t('Edit Sent Posts'), t('Edit and correct posts and comments after sending'), false, get_config('feature_lock','edit_posts')), + array('commtag', t('Tagging'), t('Ability to tag existing posts'), false, get_config('feature_lock','commtag')), + array('categories', t('Post Categories'), t('Add categories to your posts'), false, get_config('feature_lock','categories')), + array('filing', t('Saved Folders'), t('Ability to file posts under folders'), false, get_config('feature_lock','filing')), + array('dislike', t('Dislike Posts'), t('Ability to dislike posts/comments'), false, get_config('feature_lock','dislike')), + array('star_posts', t('Star Posts'), t('Ability to mark special posts with a star indicator'), false, get_config('feature_lock','star_posts')), + array('ignore_posts', t('Mute Post Notifications'), t('Ability to mute notifications for a thread'), false, get_config('feature_lock','ignore_posts')), ), // Advanced Profile Settings 'advanced_profile' => array( t('Advanced Profile Settings'), - array('forumlist_profile', t('List Forums'), t('Show visitors public community forums at the Advanced Profile Page'),false), + array('forumlist_profile', t('List Forums'), t('Show visitors public community forums at the Advanced Profile Page'), false, get_config('feature_lock','forumlist_profile')), ), ); + // removed any locked features and remove the entire category if this makes it empty + + if($filtered) { + foreach($arr as $k => $x) { + $has_items = false; + $kquantity = count($arr[$k]); + for($y = 0; $y < $kquantity; $y ++) { + if(is_array($arr[$k][$y])) { + if($arr[$k][$y][4] === false) { + $has_items = true; + } + else { + unset($arr[$k][$y]); + } + } + } + if(! $has_items) { + unset($arr[$k]); + } + } + } + call_hooks('get_features',$arr); return $arr; } diff --git a/include/follow.php b/include/follow.php index 21c05c8f3..22ff079b6 100644 --- a/include/follow.php +++ b/include/follow.php @@ -264,24 +264,8 @@ function new_contact($uid,$url,$interactive = false) { require_once("include/Photo.php"); - $photos = import_profile_photo($ret['photo'],$uid,$contact_id); - - $r = q("UPDATE `contact` SET `photo` = '%s', - `thumb` = '%s', - `micro` = '%s', - `name-date` = '%s', - `uri-date` = '%s', - `avatar-date` = '%s' - WHERE `id` = %d", - dbesc($photos[0]), - dbesc($photos[1]), - dbesc($photos[2]), - dbesc(datetime_convert()), - dbesc(datetime_convert()), - dbesc(datetime_convert()), - intval($contact_id) - ); - + // Update the avatar + update_contact_avatar($ret['photo'],$uid,$contact_id); // pull feed and consume it, which should subscribe to the hub. diff --git a/include/forums.php b/include/forums.php deleted file mode 100644 index 995a29cad..000000000 --- a/include/forums.php +++ /dev/null @@ -1,182 +0,0 @@ - forum url - * 'name' => forum name - * 'id' => number of the key from the array - * 'micro' => contact photo in format micro - */ -function get_forumlist($uid, $showhidden = true, $lastitem, $showprivate = false) { - - $forumlist = array(); - - $order = (($showhidden) ? '' : ' AND NOT `hidden` '); - $order .= (($lastitem) ? ' ORDER BY `last-item` DESC ' : ' ORDER BY `name` ASC '); - $select = '`forum` '; - if ($showprivate) { - $select = '(`forum` OR `prv`)'; - } - - $contacts = q("SELECT `contact`.`id`, `contact`.`url`, `contact`.`name`, `contact`.`micro` FROM `contact` - WHERE `network`= 'dfrn' AND $select AND `uid` = %d - AND NOT `blocked` AND NOT `hidden` AND NOT `pending` AND NOT `archive` - AND `success_update` > `failure_update` - $order ", - intval($uid) - ); - - foreach($contacts as $contact) { - $forumlist[] = array( - 'url' => $contact['url'], - 'name' => $contact['name'], - 'id' => $contact['id'], - 'micro' => $contact['micro'], - ); - } - return($forumlist); -} - - -/** - * @brief Forumlist widget - * - * Sidebar widget to show subcribed friendica forums. If activated - * in the settings, it appears at the notwork page sidebar - * - * @param int $uid - * @param int $cid - * The contact id which is used to mark a forum as "selected" - * @return string - */ -function widget_forumlist($uid,$cid = 0) { - - if(! intval(feature_enabled(local_user(),'forumlist_widget'))) - return; - - $o = ''; - - //sort by last updated item - $lastitem = true; - - $contacts = get_forumlist($uid,true,$lastitem, true); - $total = count($contacts); - $visible_forums = 10; - - if(count($contacts)) { - - $id = 0; - - foreach($contacts as $contact) { - - $selected = (($cid == $contact['id']) ? ' forum-selected' : ''); - - $entry = array( - 'url' => z_root() . '/network?f=&cid=' . $contact['id'], - 'external_url' => z_root() . '/redir/' . $contact['id'], - 'name' => $contact['name'], - 'cid' => $contact['id'], - 'selected' => $selected, - 'micro' => proxy_url($contact['micro'], false, PROXY_SIZE_MICRO), - 'id' => ++$id, - ); - $entries[] = $entry; - } - - $tpl = get_markup_template('widget_forumlist.tpl'); - - $o .= replace_macros($tpl,array( - '$title' => t('Forums'), - '$forums' => $entries, - '$link_desc' => t('External link to forum'), - '$total' => $total, - '$visible_forums' => $visible_forums, - '$showmore' => t('show more'), - )); - } - - return $o; -} - -/** - * @brief Format forumlist as contact block - * - * This function is used to show the forumlist in - * the advanced profile. - * - * @param int $uid - * @return string - * - */ -function forumlist_profile_advanced($uid) { - - $profile = intval(feature_enabled($uid,'forumlist_profile')); - if(! $profile) - return; - - $o = ''; - - // place holder in case somebody wants configurability - $show_total = 9999; - - //don't sort by last updated item - $lastitem = false; - - $contacts = get_forumlist($uid,false,$lastitem,false); - - $total_shown = 0; - - foreach($contacts as $contact) { - $forumlist .= micropro($contact,false,'forumlist-profile-advanced'); - $total_shown ++; - if($total_shown == $show_total) - break; - } - - if(count($contacts) > 0) - $o .= $forumlist; - return $o; -} - -/** - * @brief count unread forum items - * - * Count unread items of connected forums and private groups - * - * @return array - * 'id' => contact id - * 'name' => contact/forum name - * 'count' => counted unseen forum items - * - */ - -function forums_count_unseen() { - $r = q("SELECT `contact`.`id`, `contact`.`name`, COUNT(*) AS `count` FROM `item` - INNER JOIN `contact` ON `item`.`contact-id` = `contact`.`id` - WHERE `item`.`uid` = %d AND `item`.`visible` AND NOT `item`.`deleted` AND `item`.`unseen` - AND `contact`.`network`= 'dfrn' AND (`contact`.`forum` OR `contact`.`prv`) - AND NOT `contact`.`blocked` AND NOT `contact`.`hidden` - AND NOT `contact`.`pending` AND NOT `contact`.`archive` - AND `contact`.`success_update` > `failure_update` - GROUP BY `contact`.`id` ", - intval(local_user()) - ); - - return $r; -} diff --git a/include/friendica_smarty.php b/include/friendica_smarty.php index 99dc12bf8..9ba2d2a74 100644 --- a/include/friendica_smarty.php +++ b/include/friendica_smarty.php @@ -60,6 +60,8 @@ class FriendicaSmartyEngine implements ITemplateEngine { $template = $s; $s = new FriendicaSmarty(); } + + $r['$APP'] = get_app(); // "middleware": inject variables into templates $arr = array( diff --git a/include/gprobe.php b/include/gprobe.php index 84292f263..dfa9137d7 100644 --- a/include/gprobe.php +++ b/include/gprobe.php @@ -33,7 +33,7 @@ function gprobe_run(&$argv, &$argc){ $url = hex2bin($argv[1]); - $r = q("select * from gcontact where nurl = '%s' limit 1", + $r = q("SELECT `id`, `url`, `network` FROM `gcontact` WHERE `nurl` = '%s' ORDER BY `id` LIMIT 1", dbesc(normalise_link($url)) ); @@ -58,21 +58,16 @@ function gprobe_run(&$argv, &$argc){ if (is_null($result)) Cache::set("gprobe:".$urlparts["host"],serialize($arr)); - if(count($arr) && x($arr,'network') && $arr['network'] === NETWORK_DFRN) { - q("insert into `gcontact` (`name`,`url`,`nurl`,`photo`) - values ( '%s', '%s', '%s', '%s') ", - dbesc($arr['name']), - dbesc($arr['url']), - dbesc(normalise_link($arr['url'])), - dbesc($arr['photo']) - ); - } - $r = q("select * from gcontact where nurl = '%s' limit 1", + if (!in_array($result["network"], array(NETWORK_FEED, NETWORK_PHANTOM))) + update_gcontact($arr); + + $r = q("SELECT `id`, `url`, `network` FROM `gcontact` WHERE `nurl` = '%s' ORDER BY `id` LIMIT 1", dbesc(normalise_link($url)) ); } if(count($r)) - poco_load(0,0,$r[0]['id'], str_replace('/profile/','/poco/',$r[0]['url'])); + if ($r[0]["network"] == NETWORK_DFRN) + poco_load(0,0,$r[0]['id'], str_replace('/profile/','/poco/',$r[0]['url'])); logger("gprobe end for ".normalise_link($url), LOGGER_DEBUG); return; diff --git a/include/group.php b/include/group.php index fd1c97dfd..2b872f16a 100644 --- a/include/group.php +++ b/include/group.php @@ -215,7 +215,7 @@ function mini_group_select($uid,$gid = 0) { /** * @brief Create group sidebar widget - * + * * @param string $every * @param string $each * @param string $editmode @@ -234,7 +234,7 @@ function group_side($every="contacts",$each="group",$editmode = "standard", $gro return ''; $groups = array(); - + $groups[] = array( 'text' => t('Everybody'), 'id' => 0, @@ -255,7 +255,7 @@ function group_side($every="contacts",$each="group",$editmode = "standard", $gro if(count($r)) { foreach($r as $rr) { $selected = (($group_id == $rr['id']) ? ' group-selected' : ''); - + if ($editmode == "full") { $groupedit = array( 'href' => "group/".$rr['id'], @@ -264,7 +264,7 @@ function group_side($every="contacts",$each="group",$editmode = "standard", $gro } else { $groupedit = null; } - + $groups[] = array( 'id' => $rr['id'], 'cid' => $cid, @@ -297,17 +297,26 @@ function group_side($every="contacts",$each="group",$editmode = "standard", $gro return $o; } -function expand_groups($a,$check_dead = false) { +function expand_groups($a,$check_dead = false, $use_gcontact = false) { if(! (is_array($a) && count($a))) return array(); $groups = implode(',', $a); $groups = dbesc($groups); - $r = q("SELECT `contact-id` FROM `group_member` WHERE `gid` IN ( $groups )"); + + if ($use_gcontact) + $r = q("SELECT `gcontact`.`id` AS `contact-id` FROM `group_member` + INNER JOIN `contact` ON `contact`.`id` = `group_member`.`contact-id` + INNER JOIN `gcontact` ON `gcontact`.`nurl` = `contact`.`nurl` + WHERE `gid` IN ($groups)"); + else + $r = q("SELECT `contact-id` FROM `group_member` WHERE `gid` IN ( $groups )"); + + $ret = array(); if(count($r)) foreach($r as $rr) $ret[] = $rr['contact-id']; - if($check_dead) { + if($check_dead AND !$use_gcontact) { require_once('include/acl_selectors.php'); $ret = prune_deadguys($ret); } @@ -353,17 +362,16 @@ function groups_containing($uid,$c) { */ function groups_count_unseen() { - $r = q("SELECT `group`.`id`, `group`.`name`, COUNT(`item`.`id`) AS `count` FROM `group`, `group_member`, `item` - WHERE `group`.`uid` = %d - AND `item`.`uid` = %d - AND `item`.`unseen` AND `item`.`visible` - AND NOT `item`.`deleted` - AND `item`.`contact-id` = `group_member`.`contact-id` - AND `group_member`.`gid` = `group`.`id` - GROUP BY `group`.`id` ", + $r = q("SELECT `group`.`id`, `group`.`name`, + (SELECT COUNT(*) FROM `item` + WHERE `uid` = %d AND `unseen` AND + `contact-id` IN (SELECT `contact-id` FROM `group_member` + WHERE `group_member`.`gid` = `group`.`id` AND `group_member`.`uid` = %d)) AS `count` + FROM `group` WHERE `group`.`uid` = %d;", + intval(local_user()), intval(local_user()), intval(local_user()) ); return $r; -} \ No newline at end of file +} diff --git a/include/identity.php b/include/identity.php index fb405b90f..ec66225d0 100644 --- a/include/identity.php +++ b/include/identity.php @@ -3,7 +3,7 @@ * @file include/identity.php */ -require_once('include/forums.php'); +require_once('include/ForumManager.php'); require_once('include/bbcode.php'); require_once("mod/proxy.php"); @@ -300,6 +300,7 @@ function profile_sidebar($profile, $block = 0) { $account_type = ""; if((x($profile,'address') == 1) + || (x($profile,'location') == 1) || (x($profile,'locality') == 1) || (x($profile,'region') == 1) || (x($profile,'postal-code') == 1) @@ -368,6 +369,8 @@ function profile_sidebar($profile, $block = 0) { if (isset($p["address"])) $p["address"] = bbcode($p["address"]); + else + $p["address"] = bbcode($p["location"]); if (isset($p["photo"])) $p["photo"] = proxy_url($p["photo"], false, PROXY_SIZE_SMALL); @@ -652,7 +655,7 @@ function advanced_profile(&$a) { //show subcribed forum if it is enabled in the usersettings if (feature_enabled($uid,'forumlist_profile')) { - $profile['forumlist'] = array( t('Forums:'), forumlist_profile_advanced($uid)); + $profile['forumlist'] = array( t('Forums:'), ForumManager::profile_advanced($uid)); } if ($a->profile['uid'] == local_user()) diff --git a/include/items.php b/include/items.php index c9e13b1a1..1af6fe1b5 100644 --- a/include/items.php +++ b/include/items.php @@ -14,314 +14,19 @@ require_once('include/socgraph.php'); require_once('include/plaintext.php'); require_once('include/ostatus.php'); require_once('include/feed.php'); +require_once('include/Contact.php'); require_once('mod/share.php'); +require_once('include/enotify.php'); +require_once('include/dfrn.php'); require_once('library/defuse/php-encryption-1.2.1/Crypto.php'); - -function get_feed_for(&$a, $dfrn_id, $owner_nick, $last_update, $direction = 0, $forpubsub = false) { - - - $sitefeed = ((strlen($owner_nick)) ? false : true); // not yet implemented, need to rewrite huge chunks of following logic - $public_feed = (($dfrn_id) ? false : true); - $starred = false; // not yet implemented, possible security issues - $converse = false; - - if($public_feed && $a->argc > 2) { - for($x = 2; $x < $a->argc; $x++) { - if($a->argv[$x] == 'converse') - $converse = true; - if($a->argv[$x] == 'starred') - $starred = true; - if($a->argv[$x] === 'category' && $a->argc > ($x + 1) && strlen($a->argv[$x+1])) - $category = $a->argv[$x+1]; - } - } - - - - // default permissions - anonymous user - - $sql_extra = " AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = '' AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = '' "; - - $r = q("SELECT `contact`.*, `user`.`uid` AS `user_uid`, `user`.`nickname`, `user`.`timezone`, `user`.`page-flags` - FROM `contact` INNER JOIN `user` ON `user`.`uid` = `contact`.`uid` - WHERE `contact`.`self` = 1 AND `user`.`nickname` = '%s' LIMIT 1", - dbesc($owner_nick) - ); - - if(! count($r)) - killme(); - - $owner = $r[0]; - $owner_id = $owner['user_uid']; - $owner_nick = $owner['nickname']; - - $birthday = feed_birthday($owner_id,$owner['timezone']); - - $sql_post_table = ""; - $visibility = ""; - - if(! $public_feed) { - - $sql_extra = ''; - switch($direction) { - case (-1): - $sql_extra = sprintf(" AND `issued-id` = '%s' ", dbesc($dfrn_id)); - $my_id = $dfrn_id; - break; - case 0: - $sql_extra = sprintf(" AND `issued-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id)); - $my_id = '1:' . $dfrn_id; - break; - case 1: - $sql_extra = sprintf(" AND `dfrn-id` = '%s' AND `duplex` = 1 ", dbesc($dfrn_id)); - $my_id = '0:' . $dfrn_id; - break; - default: - return false; - break; // NOTREACHED - } - - $r = q("SELECT * FROM `contact` WHERE `blocked` = 0 AND `pending` = 0 AND `contact`.`uid` = %d $sql_extra LIMIT 1", - intval($owner_id) - ); - - if(! count($r)) - killme(); - - $contact = $r[0]; - require_once('include/security.php'); - $groups = init_groups_visitor($contact['id']); - - if(count($groups)) { - for($x = 0; $x < count($groups); $x ++) - $groups[$x] = '<' . intval($groups[$x]) . '>' ; - $gs = implode('|', $groups); - } - else - $gs = '<<>>' ; // Impossible to match - - $sql_extra = sprintf(" - AND ( `allow_cid` = '' OR `allow_cid` REGEXP '<%d>' ) - AND ( `deny_cid` = '' OR NOT `deny_cid` REGEXP '<%d>' ) - AND ( `allow_gid` = '' OR `allow_gid` REGEXP '%s' ) - AND ( `deny_gid` = '' OR NOT `deny_gid` REGEXP '%s') - ", - intval($contact['id']), - intval($contact['id']), - dbesc($gs), - dbesc($gs) - ); - } - - if($public_feed) - $sort = 'DESC'; - else - $sort = 'ASC'; - - // Include answers to status.net posts in pubsub feeds - if($forpubsub) { - $sql_post_table = "INNER JOIN `thread` ON `thread`.`iid` = `item`.`parent` - LEFT JOIN `item` AS `thritem` ON `thritem`.`uri`=`item`.`thr-parent` AND `thritem`.`uid`=`item`.`uid`"; - $visibility = sprintf("AND (`item`.`parent` = `item`.`id`) OR (`item`.`network` = '%s' AND ((`thread`.`network`='%s') OR (`thritem`.`network` = '%s')))", - dbesc(NETWORK_DFRN), dbesc(NETWORK_OSTATUS), dbesc(NETWORK_OSTATUS)); - $date_field = "`received`"; - $sql_order = "`item`.`received` DESC"; - } else { - $date_field = "`changed`"; - $sql_order = "`item`.`parent` ".$sort.", `item`.`created` ASC"; - } - - if(! strlen($last_update)) - $last_update = 'now -30 days'; - - if(isset($category)) { - $sql_post_table = sprintf("INNER JOIN (SELECT `oid` FROM `term` WHERE `term` = '%s' AND `otype` = %d AND `type` = %d AND `uid` = %d ORDER BY `tid` DESC) AS `term` ON `item`.`id` = `term`.`oid` ", - dbesc(protect_sprintf($category)), intval(TERM_OBJ_POST), intval(TERM_CATEGORY), intval($owner_id)); - //$sql_extra .= file_tag_file_query('item',$category,'category'); - } - - if($public_feed) { - if(! $converse) - $sql_extra .= " AND `contact`.`self` = 1 "; - } - - $check_date = datetime_convert('UTC','UTC',$last_update,'Y-m-d H:i:s'); - - // AND ( `item`.`edited` > '%s' OR `item`.`changed` > '%s' ) - // dbesc($check_date), - - $r = q("SELECT STRAIGHT_JOIN `item`.*, `item`.`id` AS `item_id`, - `contact`.`name`, `contact`.`network`, `contact`.`photo`, `contact`.`url`, - `contact`.`name-date`, `contact`.`uri-date`, `contact`.`avatar-date`, - `contact`.`thumb`, `contact`.`dfrn-id`, `contact`.`self`, - `contact`.`id` AS `contact-id`, `contact`.`uid` AS `contact-uid`, - `sign`.`signed_text`, `sign`.`signature`, `sign`.`signer` - FROM `item` $sql_post_table - INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id` - AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0 - LEFT JOIN `sign` ON `sign`.`iid` = `item`.`id` - WHERE `item`.`uid` = %d AND `item`.`visible` = 1 and `item`.`moderated` = 0 AND `item`.`parent` != 0 - AND ((`item`.`wall` = 1) $visibility) AND `item`.$date_field > '%s' - $sql_extra - ORDER BY $sql_order LIMIT 0, 300", - intval($owner_id), - dbesc($check_date), - dbesc($sort) - ); - - // Will check further below if this actually returned results. - // We will provide an empty feed if that is the case. - - $items = $r; - - $feed_template = get_markup_template(($dfrn_id) ? 'atom_feed_dfrn.tpl' : 'atom_feed.tpl'); - - $atom = ''; - - $hubxml = feed_hublinks(); - - $salmon = feed_salmonlinks($owner_nick); - - $alternatelink = $owner['url']; - - if(isset($category)) - $alternatelink .= "/category/".$category; - - $atom .= replace_macros($feed_template, array( - '$version' => xmlify(FRIENDICA_VERSION), - '$feed_id' => xmlify($a->get_baseurl() . '/profile/' . $owner_nick), - '$feed_title' => xmlify($owner['name']), - '$feed_updated' => xmlify(datetime_convert('UTC', 'UTC', 'now' , ATOM_TIME)) , - '$hub' => $hubxml, - '$salmon' => $salmon, - '$alternatelink' => xmlify($alternatelink), - '$name' => xmlify($owner['name']), - '$profile_page' => xmlify($owner['url']), - '$photo' => xmlify($owner['photo']), - '$thumb' => xmlify($owner['thumb']), - '$picdate' => xmlify(datetime_convert('UTC','UTC',$owner['avatar-date'] . '+00:00' , ATOM_TIME)) , - '$uridate' => xmlify(datetime_convert('UTC','UTC',$owner['uri-date'] . '+00:00' , ATOM_TIME)) , - '$namdate' => xmlify(datetime_convert('UTC','UTC',$owner['name-date'] . '+00:00' , ATOM_TIME)) , - '$birthday' => ((strlen($birthday)) ? '' . xmlify($birthday) . '' : ''), - '$community' => (($owner['page-flags'] == PAGE_COMMUNITY) ? '1' : '') - )); - - call_hooks('atom_feed', $atom); - - if(! count($items)) { - - call_hooks('atom_feed_end', $atom); - - $atom .= '' . "\r\n"; - return $atom; - } - - foreach($items as $item) { - - // prevent private email from leaking. - if($item['network'] === NETWORK_MAIL) - continue; - - // public feeds get html, our own nodes use bbcode - - if($public_feed) { - $type = 'html'; - // catch any email that's in a public conversation and make sure it doesn't leak - if($item['private']) - continue; - } - else { - $type = 'text'; - } - - $atom .= atom_entry($item,$type,null,$owner,true); - } - - call_hooks('atom_feed_end', $atom); - - $atom .= '' . "\r\n"; - - return $atom; -} - - function construct_verb($item) { if($item['verb']) return $item['verb']; return ACTIVITY_POST; } -function construct_activity_object($item) { - - if($item['object']) { - $o = '' . "\r\n"; - $r = parse_xml_string($item['object'],false); - - - if(! $r) - return ''; - if($r->type) - $o .= '' . xmlify($r->type) . '' . "\r\n"; - if($r->id) - $o .= '' . xmlify($r->id) . '' . "\r\n"; - if($r->title) - $o .= '' . xmlify($r->title) . '' . "\r\n"; - if($r->link) { - if(substr($r->link,0,1) === '<') { - // patch up some facebook "like" activity objects that got stored incorrectly - // for a couple of months prior to 9-Jun-2011 and generated bad XML. - // we can probably remove this hack here and in the following function in a few months time. - if(strstr($r->link,'&') && (! strstr($r->link,'&'))) - $r->link = str_replace('&','&', $r->link); - $r->link = preg_replace('/\/','',$r->link); - $o .= $r->link; - } - else - $o .= '' . "\r\n"; - } - if($r->content) - $o .= '' . xmlify(bbcode($r->content)) . '' . "\r\n"; - $o .= '' . "\r\n"; - return $o; - } - - return ''; -} - -function construct_activity_target($item) { - - if($item['target']) { - $o = '' . "\r\n"; - $r = parse_xml_string($item['target'],false); - if(! $r) - return ''; - if($r->type) - $o .= '' . xmlify($r->type) . '' . "\r\n"; - if($r->id) - $o .= '' . xmlify($r->id) . '' . "\r\n"; - if($r->title) - $o .= '' . xmlify($r->title) . '' . "\r\n"; - if($r->link) { - if(substr($r->link,0,1) === '<') { - if(strstr($r->link,'&') && (! strstr($r->link,'&'))) - $r->link = str_replace('&','&', $r->link); - $r->link = preg_replace('/\/','',$r->link); - $o .= $r->link; - } - else - $o .= '' . "\r\n"; - } - if($r->content) - $o .= '' . xmlify(bbcode($r->content)) . '' . "\r\n"; - $o .= '' . "\r\n"; - return $o; - } - - return ''; -} - /* limit_body_size() * * The purpose of this function is to apply system message length limits to @@ -440,482 +145,6 @@ function title_is_body($title, $body) { return($title == $body); } - - -function get_atom_elements($feed, $item, $contact = array()) { - - require_once('library/HTMLPurifier.auto.php'); - require_once('include/html2bbcode.php'); - - $best_photo = array(); - - $res = array(); - - $author = $item->get_author(); - if($author) { - $res['author-name'] = unxmlify($author->get_name()); - $res['author-link'] = unxmlify($author->get_link()); - } - else { - $res['author-name'] = unxmlify($feed->get_title()); - $res['author-link'] = unxmlify($feed->get_permalink()); - } - $res['uri'] = unxmlify($item->get_id()); - $res['title'] = unxmlify($item->get_title()); - $res['body'] = unxmlify($item->get_content()); - $res['plink'] = unxmlify($item->get_link(0)); - - if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) { - logger("get_atom_elements: detected app.net posting: ".print_r($res, true), LOGGER_DEBUG); - $res['title'] = ""; - $res['body'] = nl2br($res['body']); - } - - // removing the content of the title if its identically to the body - // This helps with auto generated titles e.g. from tumblr - if (title_is_body($res["title"], $res["body"])) - $res['title'] = ""; - - if($res['plink']) - $base_url = implode('/', array_slice(explode('/',$res['plink']),0,3)); - else - $base_url = ''; - - // look for a photo. We should check media size and find the best one, - // but for now let's just find any author photo - // Additionally we look for an alternate author link. On OStatus this one is the one we want. - - $authorlinks = $item->feed->data["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["feed"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["author"][0]["child"]["http://www.w3.org/2005/Atom"]["link"]; - if (is_array($authorlinks)) { - foreach ($authorlinks as $link) { - $linkdata = array_shift($link["attribs"]); - - if ($linkdata["rel"] == "alternate") - $res["author-link"] = $linkdata["href"]; - }; - } - - $rawauthor = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author'); - - if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) { - $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']; - foreach($base as $link) { - if($link['attribs']['']['rel'] === 'alternate') - $res['author-link'] = unxmlify($link['attribs']['']['href']); - - if(!x($res, 'author-avatar') || !$res['author-avatar']) { - if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar') - $res['author-avatar'] = unxmlify($link['attribs']['']['href']); - } - } - } - - $rawactor = $item->get_item_tags(NAMESPACE_ACTIVITY, 'actor'); - - if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) { - $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']; - if($base && count($base)) { - foreach($base as $link) { - if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link'])) - $res['author-link'] = unxmlify($link['attribs']['']['href']); - if(!x($res, 'author-avatar') || !$res['author-avatar']) { - if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo') - $res['author-avatar'] = unxmlify($link['attribs']['']['href']); - } - } - } - } - - // No photo/profile-link on the item - look at the feed level - - if((! (x($res,'author-link'))) || (! (x($res,'author-avatar')))) { - $rawauthor = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'author'); - if($rawauthor && $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) { - $base = $rawauthor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']; - foreach($base as $link) { - if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link'])) - $res['author-link'] = unxmlify($link['attribs']['']['href']); - if(! $res['author-avatar']) { - if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar') - $res['author-avatar'] = unxmlify($link['attribs']['']['href']); - } - } - } - - $rawactor = $feed->get_feed_tags(NAMESPACE_ACTIVITY, 'subject'); - - if($rawactor && activity_match($rawactor[0]['child'][NAMESPACE_ACTIVITY]['object-type'][0]['data'],ACTIVITY_OBJ_PERSON)) { - $base = $rawactor[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']; - - if($base && count($base)) { - foreach($base as $link) { - if($link['attribs']['']['rel'] === 'alternate' && (! $res['author-link'])) - $res['author-link'] = unxmlify($link['attribs']['']['href']); - if(! (x($res,'author-avatar'))) { - if($link['attribs']['']['rel'] === 'avatar' || $link['attribs']['']['rel'] === 'photo') - $res['author-avatar'] = unxmlify($link['attribs']['']['href']); - } - } - } - } - } - - $apps = $item->get_item_tags(NAMESPACE_STATUSNET,'notice_info'); - if($apps && $apps[0]['attribs']['']['source']) { - $res['app'] = strip_tags(unxmlify($apps[0]['attribs']['']['source'])); - if($res['app'] === 'web') - $res['app'] = 'OStatus'; - } - - // base64 encoded json structure representing Diaspora signature - - $dsig = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_signature'); - if($dsig) { - $res['dsprsig'] = unxmlify($dsig[0]['data']); - } - - $dguid = $item->get_item_tags(NAMESPACE_DFRN,'diaspora_guid'); - if($dguid) - $res['guid'] = unxmlify($dguid[0]['data']); - - $bm = $item->get_item_tags(NAMESPACE_DFRN,'bookmark'); - if($bm) - $res['bookmark'] = ((unxmlify($bm[0]['data']) === 'true') ? 1 : 0); - - - /** - * If there's a copy of the body content which is guaranteed to have survived mangling in transit, use it. - */ - - $have_real_body = false; - - $rawenv = $item->get_item_tags(NAMESPACE_DFRN, 'env'); - if($rawenv) { - $have_real_body = true; - $res['body'] = $rawenv[0]['data']; - $res['body'] = str_replace(array(' ',"\t","\r","\n"), array('','','',''),$res['body']); - // make sure nobody is trying to sneak some html tags by us - $res['body'] = notags(base64url_decode($res['body'])); - } - - - $res['body'] = limit_body_size($res['body']); - - // It isn't certain at this point whether our content is plaintext or html and we'd be foolish to trust - // the content type. Our own network only emits text normally, though it might have been converted to - // html if we used a pubsubhubbub transport. But if we see even one html tag in our text, we will - // have to assume it is all html and needs to be purified. - - // It doesn't matter all that much security wise - because before this content is used anywhere, we are - // going to escape any tags we find regardless, but this lets us import a limited subset of html from - // the wild, by sanitising it and converting supported tags to bbcode before we rip out any remaining - // html. - - if((strpos($res['body'],'<') !== false) && (strpos($res['body'],'>') !== false)) { - - $res['body'] = reltoabs($res['body'],$base_url); - - $res['body'] = html2bb_video($res['body']); - - $res['body'] = oembed_html2bbcode($res['body']); - - $config = HTMLPurifier_Config::createDefault(); - $config->set('Cache.DefinitionImpl', null); - - // we shouldn't need a whitelist, because the bbcode converter - // will strip out any unsupported tags. - - $purifier = new HTMLPurifier($config); - $res['body'] = $purifier->purify($res['body']); - - $res['body'] = @html2bbcode($res['body']); - - - } - elseif(! $have_real_body) { - - // it's not one of our messages and it has no tags - // so it's probably just text. We'll escape it just to be safe. - - $res['body'] = escape_tags($res['body']); - } - - - // this tag is obsolete but we keep it for really old sites - - $allow = $item->get_item_tags(NAMESPACE_DFRN,'comment-allow'); - if($allow && $allow[0]['data'] == 1) - $res['last-child'] = 1; - else - $res['last-child'] = 0; - - $private = $item->get_item_tags(NAMESPACE_DFRN,'private'); - if($private && intval($private[0]['data']) > 0) - $res['private'] = intval($private[0]['data']); - else - $res['private'] = 0; - - $extid = $item->get_item_tags(NAMESPACE_DFRN,'extid'); - if($extid && $extid[0]['data']) - $res['extid'] = $extid[0]['data']; - - $rawlocation = $item->get_item_tags(NAMESPACE_DFRN, 'location'); - if($rawlocation) - $res['location'] = unxmlify($rawlocation[0]['data']); - - - $rawcreated = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'published'); - if($rawcreated) - $res['created'] = unxmlify($rawcreated[0]['data']); - - - $rawedited = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10,'updated'); - if($rawedited) - $res['edited'] = unxmlify($rawedited[0]['data']); - - if((x($res,'edited')) && (! (x($res,'created')))) - $res['created'] = $res['edited']; - - if(! $res['created']) - $res['created'] = $item->get_date('c'); - - if(! $res['edited']) - $res['edited'] = $item->get_date('c'); - - - // Disallow time travelling posts - - $d1 = strtotime($res['created']); - $d2 = strtotime($res['edited']); - $d3 = strtotime('now'); - - if($d1 > $d3) - $res['created'] = datetime_convert(); - if($d2 > $d3) - $res['edited'] = datetime_convert(); - - $rawowner = $item->get_item_tags(NAMESPACE_DFRN, 'owner'); - if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']) - $res['owner-name'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']); - elseif($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data']) - $res['owner-name'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data']); - if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']) - $res['owner-link'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']); - elseif($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data']) - $res['owner-link'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data']); - - if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) { - $base = $rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']; - - foreach($base as $link) { - if(!x($res, 'owner-avatar') || !$res['owner-avatar']) { - if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar') - $res['owner-avatar'] = unxmlify($link['attribs']['']['href']); - } - } - } - - $rawgeo = $item->get_item_tags(NAMESPACE_GEORSS,'point'); - if($rawgeo) - $res['coord'] = unxmlify($rawgeo[0]['data']); - - if ($contact["network"] == NETWORK_FEED) { - $res['verb'] = ACTIVITY_POST; - $res['object-type'] = ACTIVITY_OBJ_NOTE; - } - - $rawverb = $item->get_item_tags(NAMESPACE_ACTIVITY, 'verb'); - - // select between supported verbs - - if($rawverb) { - $res['verb'] = unxmlify($rawverb[0]['data']); - } - - // translate OStatus unfollow to activity streams if it happened to get selected - - if((x($res,'verb')) && ($res['verb'] === 'http://ostatus.org/schema/1.0/unfollow')) - $res['verb'] = ACTIVITY_UNFOLLOW; - - $cats = $item->get_categories(); - if($cats) { - $tag_arr = array(); - foreach($cats as $cat) { - $term = $cat->get_term(); - if(! $term) - $term = $cat->get_label(); - $scheme = $cat->get_scheme(); - if($scheme && $term && stristr($scheme,'X-DFRN:')) - $tag_arr[] = substr($scheme,7,1) . '[url=' . unxmlify(substr($scheme,9)) . ']' . unxmlify($term) . '[/url]'; - elseif($term) - $tag_arr[] = notags(trim($term)); - } - $res['tag'] = implode(',', $tag_arr); - } - - $attach = $item->get_enclosures(); - if($attach) { - $att_arr = array(); - foreach($attach as $att) { - $len = intval($att->get_length()); - $link = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_link())))); - $title = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_title())))); - $type = str_replace(array(',','"'),array('%2D','%22'),notags(trim(unxmlify($att->get_type())))); - if(strpos($type,';')) - $type = substr($type,0,strpos($type,';')); - if((! $link) || (strpos($link,'http') !== 0)) - continue; - - if(! $title) - $title = ' '; - if(! $type) - $type = 'application/octet-stream'; - - $att_arr[] = '[attach]href="' . $link . '" length="' . $len . '" type="' . $type . '" title="' . $title . '"[/attach]'; - } - $res['attach'] = implode(',', $att_arr); - } - - $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'object'); - - if($rawobj) { - $res['object'] = '' . "\n"; - $child = $rawobj[0]['child']; - if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) { - $res['object-type'] = $child[NAMESPACE_ACTIVITY]['object-type'][0]['data']; - $res['object'] .= '' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '' . "\n"; - } - if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data']) - $res['object'] .= '' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '' . "\n"; - if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) - $res['object'] .= '' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '' . "\n"; - if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'title') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data']) - $res['object'] .= '' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '' . "\n"; - if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'content') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) { - $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']; - if(! $body) - $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data']; - // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events - $res['object'] .= '' . xmlify($body) . '' . "\n"; - if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) { - - $body = html2bb_video($body); - - $config = HTMLPurifier_Config::createDefault(); - $config->set('Cache.DefinitionImpl', null); - - $purifier = new HTMLPurifier($config); - $body = $purifier->purify($body); - $body = html2bbcode($body); - } - - $res['object'] .= '' . $body . '' . "\n"; - } - - $res['object'] .= '' . "\n"; - } - - $rawobj = $item->get_item_tags(NAMESPACE_ACTIVITY, 'target'); - - if($rawobj) { - $res['target'] = '' . "\n"; - $child = $rawobj[0]['child']; - if($child[NAMESPACE_ACTIVITY]['object-type'][0]['data']) { - $res['target'] .= '' . $child[NAMESPACE_ACTIVITY]['object-type'][0]['data'] . '' . "\n"; - } - if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'id') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data']) - $res['target'] .= '' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['id'][0]['data'] . '' . "\n"; - if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'link') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) - $res['target'] .= '' . encode_rel_links($child[SIMPLEPIE_NAMESPACE_ATOM_10]['link']) . '' . "\n"; - if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data']) - $res['target'] .= '' . $child[SIMPLEPIE_NAMESPACE_ATOM_10]['title'][0]['data'] . '' . "\n"; - if(x($child[SIMPLEPIE_NAMESPACE_ATOM_10], 'data') && $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']) { - $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['content'][0]['data']; - if(! $body) - $body = $child[SIMPLEPIE_NAMESPACE_ATOM_10]['summary'][0]['data']; - // preserve a copy of the original body content in case we later need to parse out any microformat information, e.g. events - $res['target'] .= '' . xmlify($body) . '' . "\n"; - if((strpos($body,'<') !== false) || (strpos($body,'>') !== false)) { - - $body = html2bb_video($body); - - $config = HTMLPurifier_Config::createDefault(); - $config->set('Cache.DefinitionImpl', null); - - $purifier = new HTMLPurifier($config); - $body = $purifier->purify($body); - $body = html2bbcode($body); - } - - $res['target'] .= '' . $body . '' . "\n"; - } - - $res['target'] .= '' . "\n"; - } - - // This is some experimental stuff. By now retweets are shown with "RT:" - // But: There is data so that the message could be shown similar to native retweets - // There is some better way to parse this array - but it didn't worked for me. - $child = $item->feed->data["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["feed"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["entry"][0]["child"]["http://activitystrea.ms/spec/1.0/"][object][0]["child"]; - if (is_array($child)) { - logger('get_atom_elements: Looking for status.net repeated message'); - - $message = $child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["content"][0]["data"]; - $orig_id = ostatus_convert_href($child["http://activitystrea.ms/spec/1.0/"]["object"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]["id"][0]["data"]); - $author = $child[SIMPLEPIE_NAMESPACE_ATOM_10]["author"][0]["child"][SIMPLEPIE_NAMESPACE_ATOM_10]; - $uri = $author["uri"][0]["data"]; - $name = $author["name"][0]["data"]; - $avatar = @array_shift($author["link"][2]["attribs"]); - $avatar = $avatar["href"]; - - if (($name != "") and ($uri != "") and ($avatar != "") and ($message != "")) { - logger('get_atom_elements: fixing sender of repeated message. '.$orig_id, LOGGER_DEBUG); - - if (!intval(get_config('system','wall-to-wall_share'))) { - $prefix = share_header($name, $uri, $avatar, "", "", $orig_link); - - $res["body"] = $prefix.html2bbcode($message)."[/share]"; - } else { - $res["owner-name"] = $res["author-name"]; - $res["owner-link"] = $res["author-link"]; - $res["owner-avatar"] = $res["author-avatar"]; - - $res["author-name"] = $name; - $res["author-link"] = $uri; - $res["author-avatar"] = $avatar; - - $res["body"] = html2bbcode($message); - } - } - } - - if (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND $contact['fetch_further_information']) { - $preview = ""; - - // Handle enclosures and treat them as preview picture - if (isset($attach)) - foreach ($attach AS $attachment) - if ($attachment->type == "image/jpeg") - $preview = $attachment->link; - - $res["body"] = $res["title"].add_page_info($res['plink'], false, $preview, ($contact['fetch_further_information'] == 2), $contact['ffi_keyword_blacklist']); - $res["tag"] = add_page_keywords($res['plink'], false, $preview, ($contact['fetch_further_information'] == 2), $contact['ffi_keyword_blacklist']); - $res["title"] = ""; - $res["object-type"] = ACTIVITY_OBJ_BOOKMARK; - unset($res["attach"]); - } elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_OSTATUS)) - $res["body"] = add_page_info_to_body($res["body"]); - elseif (isset($contact["network"]) AND ($contact["network"] == NETWORK_FEED) AND strstr($res['plink'], ".app.net/")) { - $res["body"] = add_page_info_to_body($res["body"]); - } - - $arr = array('feed' => $feed, 'item' => $item, 'result' => $res); - - call_hooks('parse_atom', $arr); - - return $res; -} - function add_page_info_data($data) { call_hooks('page_info_data', $data); @@ -955,8 +184,9 @@ function add_page_info_data($data) { $a = get_app(); $hashtags = "\n"; foreach ($data["keywords"] AS $keyword) { - $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'"), - array("","", "", "", "", ""), $keyword); + /// @todo make a positive list of allowed characters + $hashtag = str_replace(array(" ", "+", "/", ".", "#", "'", "’", "`", "(", ")", "„", "“"), + array("","", "", "", "", "", "", "", "", "", "", ""), $keyword); $hashtags .= "#[url=".$a->get_baseurl()."/search?tag=".rawurlencode($hashtag)."]".$hashtag."[/url] "; } } @@ -967,12 +197,7 @@ function add_page_info_data($data) { function query_page_info($url, $no_photos = false, $photo = "", $keywords = false, $keyword_blacklist = "") { require_once("mod/parse_url.php"); - $data = Cache::get("parse_url:".$url); - if (is_null($data)){ - $data = parseurl_getsiteinfo($url, true); - Cache::set("parse_url:".$url,serialize($data), CACHE_DAY); - } else - $data = unserialize($data); + $data = parseurl_getsiteinfo_cached($url, true); if ($photo != "") $data["images"][0]["src"] = $photo; @@ -1066,37 +291,6 @@ function add_page_info_to_body($body, $texturl = false, $no_photos = false) { return $body; } -function encode_rel_links($links) { - $o = ''; - if(! ((is_array($links)) && (count($links)))) - return $o; - foreach($links as $link) { - $o .= '') !== false)) @@ -1253,6 +444,10 @@ function item_store($arr,$force_parent = false, $notify = false, $dontcache = fa if ($notify) $guid_prefix = ""; + elseif ((trim($arr['guid']) == "") AND (trim($arr['plink']) != "")) + $arr['guid'] = uri_to_guid($arr['plink']); + elseif ((trim($arr['guid']) == "") AND (trim($arr['uri']) != "")) + $arr['guid'] = uri_to_guid($arr['uri']); else { $parsed = parse_url($arr["author-link"]); $guid_prefix = hash("crc32", $parsed["host"]); @@ -1304,6 +499,16 @@ function item_store($arr,$force_parent = false, $notify = false, $dontcache = fa $arr['inform'] = ((x($arr,'inform')) ? trim($arr['inform']) : ''); $arr['file'] = ((x($arr,'file')) ? trim($arr['file']) : ''); + + if (($arr['author-link'] == "") AND ($arr['owner-link'] == "")) { + $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 5); + foreach ($trace AS $func) + $function[] = $func["function"]; + + $function = implode(", ", $function); + logger("Both author-link and owner-link are empty. Called by: ".$function, LOGGER_DEBUG); + } + if ($arr['plink'] == "") { $a = get_app(); $arr['plink'] = $a->get_baseurl().'/display/'.urlencode($arr['guid']); @@ -1338,6 +543,38 @@ function item_store($arr,$force_parent = false, $notify = false, $dontcache = fa logger("item_store: Set network to ".$arr["network"]." for ".$arr["uri"], LOGGER_DEBUG); } + // The contact-id should be set before "item_store" was called - but there seems to be some issues + if ($arr["contact-id"] == 0) { + // First we are looking for a suitable contact that matches with the author of the post + // This is done only for comments (See below explanation at "gcontact-id") + if($arr['parent-uri'] != $arr['uri']) + $arr["contact-id"] = get_contact($arr['author-link'], $uid); + + // If not present then maybe the owner was found + if ($arr["contact-id"] == 0) + $arr["contact-id"] = get_contact($arr['owner-link'], $uid); + + // Still missing? Then use the "self" contact of the current user + if ($arr["contact-id"] == 0) { + $r = q("SELECT `id` FROM `contact` WHERE `self` AND `uid` = %d", intval($uid)); + if ($r) + $arr["contact-id"] = $r[0]["id"]; + } + logger("Contact-id was missing for post ".$arr["guid"]." from user id ".$uid." - now set to ".$arr["contact-id"], LOGGER_DEBUG); + } + + if ($arr["gcontact-id"] == 0) { + // The gcontact should mostly behave like the contact. But is is supposed to be global for the system. + // This means that wall posts, repeated posts, etc. should have the gcontact id of the owner. + // On comments the author is the better choice. + if($arr['parent-uri'] === $arr['uri']) + $arr["gcontact-id"] = get_gcontact_id(array("url" => $arr['owner-link'], "network" => $arr['network'], + "photo" => $arr['owner-avatar'], "name" => $arr['owner-name'])); + else + $arr["gcontact-id"] = get_gcontact_id(array("url" => $arr['author-link'], "network" => $arr['network'], + "photo" => $arr['author-avatar'], "name" => $arr['author-name'])); + } + if ($arr['guid'] != "") { // Checking if there is already an item with the same guid logger('checking for an item for user '.$arr['uid'].' on network '.$arr['network'].' with the guid '.$arr['guid'], LOGGER_DEBUG); @@ -1531,9 +768,6 @@ function item_store($arr,$force_parent = false, $notify = false, $dontcache = fa return 0; } elseif(count($r)) { - // Store the guid and other relevant data - add_guid($arr); - $current_post = $r[0]['id']; logger('item_store: created item ' . $current_post); @@ -1609,6 +843,14 @@ function item_store($arr,$force_parent = false, $notify = false, $dontcache = fa ); if($dsprsig) { + + // Friendica servers lower than 3.4.3-2 had double encoded the signature ... + // We can check for this condition when we decode and encode the stuff again. + if (base64_encode(base64_decode(base64_decode($dsprsig->signature))) == base64_decode($dsprsig->signature)) { + $dsprsig->signature = base64_decode($dsprsig->signature); + logger("Repaired double encoded signature from handle ".$dsprsig->signer, LOGGER_DEBUG); + } + q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ", intval($current_post), dbesc($dsprsig->signed_text), @@ -1653,67 +895,15 @@ function item_store($arr,$force_parent = false, $notify = false, $dontcache = fa create_files_from_item($current_post); // Only check for notifications on start posts - if ($arr['parent-uri'] === $arr['uri']) { + if ($arr['parent-uri'] === $arr['uri']) add_thread($current_post); - logger('item_store: Check notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG); - - // Send a notification for every new post? - $r = q("SELECT `notify_new_posts` FROM `contact` WHERE `id` = %d AND `uid` = %d AND `notify_new_posts` LIMIT 1", - intval($arr['contact-id']), - intval($arr['uid']) - ); - $send_notification = count($r); - - if (!$send_notification) { - $tags = q("SELECT `url` FROM `term` WHERE `otype` = %d AND `oid` = %d AND `type` = %d AND `uid` = %d", - intval(TERM_OBJ_POST), intval($current_post), intval(TERM_MENTION), intval($arr['uid'])); - - if (count($tags)) { - foreach ($tags AS $tag) { - $r = q("SELECT `id` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d AND `notify_new_posts`", - normalise_link($tag["url"]), intval($arr['uid'])); - if (count($r)) - $send_notification = true; - } - } - } - - if ($send_notification) { - logger('item_store: Send notification for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG); - $u = q("SELECT * FROM user WHERE uid = %d LIMIT 1", - intval($arr['uid'])); - - $item = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d", - intval($current_post), - intval($arr['uid']) - ); - - $a = get_app(); - - require_once('include/enotify.php'); - notification(array( - 'type' => NOTIFY_SHARE, - 'notify_flags' => $u[0]['notify-flags'], - 'language' => $u[0]['language'], - 'to_name' => $u[0]['username'], - 'to_email' => $u[0]['email'], - 'uid' => $u[0]['uid'], - 'item' => $item[0], - 'link' => $a->get_baseurl().'/display/'.urlencode($arr['guid']), - 'source_name' => $item[0]['author-name'], - 'source_link' => $item[0]['author-link'], - 'source_photo' => $item[0]['author-avatar'], - 'verb' => ACTIVITY_TAG, - 'otype' => 'item', - 'parent' => $arr['parent'] - )); - logger('item_store: Notification sent for contact '.$arr['contact-id'].' and post '.$current_post, LOGGER_DEBUG); - } - } else { + else { update_thread($parent_id); add_shadow_entry($arr); } + check_item_notification($current_post, $uid); + if ($notify) proc_run('php', "include/notifier.php", $notify_type, $current_post); @@ -1909,37 +1099,6 @@ function tag_deliver($uid,$item_id) { return; } - - // send a notification - - // use a local photo if we have one - - $r = q("select * from contact where uid = %d and nurl = '%s' limit 1", - intval($u[0]['uid']), - dbesc(normalise_link($item['author-link'])) - ); - $photo = (($r && count($r)) ? $r[0]['thumb'] : $item['author-avatar']); - - - require_once('include/enotify.php'); - notification(array( - 'type' => NOTIFY_TAGSELF, - 'notify_flags' => $u[0]['notify-flags'], - 'language' => $u[0]['language'], - 'to_name' => $u[0]['username'], - 'to_email' => $u[0]['email'], - 'uid' => $u[0]['uid'], - 'item' => $item, - 'link' => $a->get_baseurl() . '/display/'.urlencode(get_item_guid($item['id'])), - 'source_name' => $item['author-name'], - 'source_link' => $item['author-link'], - 'source_photo' => $photo, - 'verb' => ACTIVITY_TAG, - 'otype' => 'item', - 'parent' => $item['parent'] - )); - - $arr = array('item' => $item, 'user' => $u[0], 'contact' => $r[0]); call_hooks('tagged', $arr); @@ -2036,245 +1195,9 @@ function tgroup_check($uid,$item) { if((! $community_page) && (! $prvgroup)) return false; - - return true; - } - - - - - -function dfrn_deliver($owner,$contact,$atom, $dissolve = false) { - - $a = get_app(); - - $idtosend = $orig_id = (($contact['dfrn-id']) ? $contact['dfrn-id'] : $contact['issued-id']); - - if($contact['duplex'] && $contact['dfrn-id']) - $idtosend = '0:' . $orig_id; - if($contact['duplex'] && $contact['issued-id']) - $idtosend = '1:' . $orig_id; - - - $rino = get_config('system','rino_encrypt'); - $rino = intval($rino); - // use RINO1 if mcrypt isn't installed and RINO2 was selected - if ($rino==2 and !function_exists('mcrypt_create_iv')) $rino=1; - - logger("Local rino version: ". $rino, LOGGER_DEBUG); - - $ssl_val = intval(get_config('system','ssl_policy')); - $ssl_policy = ''; - - switch($ssl_val){ - case SSL_POLICY_FULL: - $ssl_policy = 'full'; - break; - case SSL_POLICY_SELFSIGN: - $ssl_policy = 'self'; - break; - case SSL_POLICY_NONE: - default: - $ssl_policy = 'none'; - break; - } - - $url = $contact['notify'] . '&dfrn_id=' . $idtosend . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . (($rino) ? '&rino='.$rino : ''); - - logger('dfrn_deliver: ' . $url); - - $xml = fetch_url($url); - - $curl_stat = $a->get_curl_code(); - if(! $curl_stat) - return(-1); // timed out - - logger('dfrn_deliver: ' . $xml, LOGGER_DATA); - - if(! $xml) - return 3; - - if(strpos($xml,'status) != 0) || (! strlen($res->challenge)) || (! strlen($res->dfrn_id))) - return (($res->status) ? $res->status : 3); - - $postvars = array(); - $sent_dfrn_id = hex2bin((string) $res->dfrn_id); - $challenge = hex2bin((string) $res->challenge); - $perm = (($res->perm) ? $res->perm : null); - $dfrn_version = (float) (($res->dfrn_version) ? $res->dfrn_version : 2.0); - $rino_remote_version = intval($res->rino); - $page = (($owner['page-flags'] == PAGE_COMMUNITY) ? 1 : 0); - - logger("Remote rino version: ".$rino_remote_version." for ".$contact["url"], LOGGER_DEBUG); - - if($owner['page-flags'] == PAGE_PRVGROUP) - $page = 2; - - $final_dfrn_id = ''; - - if($perm) { - if((($perm == 'rw') && (! intval($contact['writable']))) - || (($perm == 'r') && (intval($contact['writable'])))) { - q("update contact set writable = %d where id = %d", - intval(($perm == 'rw') ? 1 : 0), - intval($contact['id']) - ); - $contact['writable'] = (string) 1 - intval($contact['writable']); - } - } - - if(($contact['duplex'] && strlen($contact['pubkey'])) - || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey'])) - || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) { - openssl_public_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['pubkey']); - openssl_public_decrypt($challenge,$postvars['challenge'],$contact['pubkey']); - } - else { - openssl_private_decrypt($sent_dfrn_id,$final_dfrn_id,$contact['prvkey']); - openssl_private_decrypt($challenge,$postvars['challenge'],$contact['prvkey']); - } - - $final_dfrn_id = substr($final_dfrn_id, 0, strpos($final_dfrn_id, '.')); - - if(strpos($final_dfrn_id,':') == 1) - $final_dfrn_id = substr($final_dfrn_id,2); - - if($final_dfrn_id != $orig_id) { - logger('dfrn_deliver: wrong dfrn_id.'); - // did not decode properly - cannot trust this site - return 3; - } - - $postvars['dfrn_id'] = $idtosend; - $postvars['dfrn_version'] = DFRN_PROTOCOL_VERSION; - if($dissolve) - $postvars['dissolve'] = '1'; - - - if((($contact['rel']) && ($contact['rel'] != CONTACT_IS_SHARING) && (! $contact['blocked'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) { - $postvars['data'] = $atom; - $postvars['perm'] = 'rw'; - } - else { - $postvars['data'] = str_replace('1','0',$atom); - $postvars['perm'] = 'r'; - } - - $postvars['ssl_policy'] = $ssl_policy; - - if($page) - $postvars['page'] = $page; - - - if($rino>0 && $rino_remote_version>0 && (! $dissolve)) { - logger('rino version: '. $rino_remote_version); - - switch($rino_remote_version) { - case 1: - // Deprecated rino version! - $key = substr(random_string(),0,16); - $data = aes_encrypt($postvars['data'],$key); - break; - case 2: - // RINO 2 based on php-encryption - try { - $key = Crypto::createNewRandomKey(); - } catch (CryptoTestFailed $ex) { - logger('Cannot safely create a key'); - return -1; - } catch (CannotPerformOperation $ex) { - logger('Cannot safely create a key'); - return -1; - } - try { - $data = Crypto::encrypt($postvars['data'], $key); - } catch (CryptoTestFailed $ex) { - logger('Cannot safely perform encryption'); - return -1; - } catch (CannotPerformOperation $ex) { - logger('Cannot safely perform encryption'); - return -1; - } - break; - default: - logger("rino: invalid requested verision '$rino_remote_version'"); - return -1; - } - - $postvars['rino'] = $rino_remote_version; - $postvars['data'] = bin2hex($data); - - #logger('rino: sent key = ' . $key, LOGGER_DEBUG); - - - if($dfrn_version >= 2.1) { - if(($contact['duplex'] && strlen($contact['pubkey'])) - || ($owner['page-flags'] == PAGE_COMMUNITY && strlen($contact['pubkey'])) - || ($contact['rel'] == CONTACT_IS_SHARING && strlen($contact['pubkey']))) { - - openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']); - } - else { - openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']); - } - } - else { - if(($contact['duplex'] && strlen($contact['prvkey'])) || ($owner['page-flags'] == PAGE_COMMUNITY)) { - openssl_private_encrypt($key,$postvars['key'],$contact['prvkey']); - } - else { - openssl_public_encrypt($key,$postvars['key'],$contact['pubkey']); - } - } - - logger('md5 rawkey ' . md5($postvars['key'])); - - $postvars['key'] = bin2hex($postvars['key']); - } - - - logger('dfrn_deliver: ' . "SENDING: " . print_r($postvars,true), LOGGER_DATA); - - $xml = post_url($contact['notify'],$postvars); - - logger('dfrn_deliver: ' . "RECEIVED: " . $xml, LOGGER_DATA); - - $curl_stat = $a->get_curl_code(); - if((! $curl_stat) || (! strlen($xml))) - return(-1); // timed out - - if(($curl_stat == 503) && (stristr($a->get_curl_headers(),'retry-after'))) - return(-1); - - if(strpos($xml,'status; -} - - /* This function returns true if $update has an edited timestamp newer than $existing, i.e. $update contains new data which should override @@ -2342,639 +1265,25 @@ function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0) return; } - require_once('library/simplepie/simplepie.inc'); - require_once('include/contact_selectors.php'); - - if(! strlen($xml)) { - logger('consume_feed: empty input'); - return; - } - - $feed = new SimplePie(); - $feed->set_raw_data($xml); - if($datedir) - $feed->enable_order_by_date(true); - else - $feed->enable_order_by_date(false); - $feed->init(); - - if($feed->error()) - logger('consume_feed: Error parsing XML: ' . $feed->error()); - - $permalink = $feed->get_permalink(); - - // Check at the feed level for updated contact name and/or photo - - $name_updated = ''; - $new_name = ''; - $photo_timestamp = ''; - $photo_url = ''; - $birthday = ''; - $contact_updated = ''; - - $hubs = $feed->get_links('hub'); - logger('consume_feed: hubs: ' . print_r($hubs,true), LOGGER_DATA); - - if(count($hubs)) - $hub = implode(',', $hubs); - - $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner'); - if(! $rawtags) - $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author'); - if($rawtags) { - $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]; - if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) { - $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']; - $new_name = $elems['name'][0]['data']; - - // Manually checking for changed contact names - if (($new_name != $contact['name']) AND ($new_name != "") AND ($name_updated <= $contact['name-date'])) { - $name_updated = date("c"); - $photo_timestamp = date("c"); - } - } - if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) { - if ($photo_timestamp == "") - $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']); - $photo_url = $elems['link'][0]['attribs']['']['href']; - } - - if((x($rawtags[0]['child'], NAMESPACE_DFRN)) && (x($rawtags[0]['child'][NAMESPACE_DFRN],'birthday'))) { - $birthday = datetime_convert('UTC','UTC', $rawtags[0]['child'][NAMESPACE_DFRN]['birthday'][0]['data']); - } - } - - if((is_array($contact)) && ($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $contact['avatar-date'])) { - logger('consume_feed: Updating photo for '.$contact['name'].' from '.$photo_url.' uid: '.$contact['uid']); - - $contact_updated = $photo_timestamp; - - require_once("include/Photo.php"); - $photos = import_profile_photo($photo_url,$contact['uid'],$contact['id']); - - q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s' - WHERE `uid` = %d AND `id` = %d AND NOT `self`", - dbesc(datetime_convert()), - dbesc($photos[0]), - dbesc($photos[1]), - dbesc($photos[2]), - intval($contact['uid']), - intval($contact['id']) - ); - } - - if((is_array($contact)) && ($name_updated) && (strlen($new_name)) && ($name_updated > $contact['name-date'])) { - if ($name_updated > $contact_updated) - $contact_updated = $name_updated; - - $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `id` = %d LIMIT 1", - intval($contact['uid']), - intval($contact['id']) - ); - - $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d AND `name` != '%s' AND NOT `self`", - dbesc(notags(trim($new_name))), - dbesc(datetime_convert()), - intval($contact['uid']), - intval($contact['id']), - dbesc(notags(trim($new_name))) - ); - - // do our best to update the name on content items - - if(count($r) AND (notags(trim($new_name)) != $r[0]['name'])) { - q("UPDATE `item` SET `author-name` = '%s' WHERE `author-name` = '%s' AND `author-link` = '%s' AND `uid` = %d AND `author-name` != '%s'", - dbesc(notags(trim($new_name))), - dbesc($r[0]['name']), - dbesc($r[0]['url']), - intval($contact['uid']), - dbesc(notags(trim($new_name))) - ); - } - } - - if ($contact_updated AND $new_name AND $photo_url) - poco_check($contact['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $contact['id'], $contact['uid']); - - if(strlen($birthday)) { - if(substr($birthday,0,4) != $contact['bdyear']) { - logger('consume_feed: updating birthday: ' . $birthday); - - /** - * - * Add new birthday event for this person - * - * $bdtext is just a readable placeholder in case the event is shared - * with others. We will replace it during presentation to our $importer - * to contain a sparkle link and perhaps a photo. - * - */ - - $bdtext = sprintf( t('%s\'s birthday'), $contact['name']); - $bdtext2 = sprintf( 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']), - dbesc(datetime_convert()), - dbesc(datetime_convert()), - dbesc(datetime_convert('UTC','UTC', $birthday)), - dbesc(datetime_convert('UTC','UTC', $birthday . ' + 1 day ')), - dbesc($bdtext), - dbesc($bdtext2), - dbesc('birthday') - ); - - - // update bdyear - - q("UPDATE `contact` SET `bdyear` = '%s' WHERE `uid` = %d AND `id` = %d", - dbesc(substr($birthday,0,4)), - intval($contact['uid']), - intval($contact['id']) - ); - - // This function is called twice without reloading the contact - // Make sure we only create one event. This is why &$contact - // is a reference var in this function - - $contact['bdyear'] = substr($birthday,0,4); - } - } - - $community_page = 0; - $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community'); - if($rawtags) { - $community_page = intval($rawtags[0]['data']); - } - if(is_array($contact) && intval($contact['forum']) != $community_page) { - q("update contact set forum = %d where id = %d", - intval($community_page), - intval($contact['id']) - ); - $contact['forum'] = (string) $community_page; - } - - - // process any deleted entries - - $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry'); - if(is_array($del_entries) && count($del_entries) && $pass != 2) { - foreach($del_entries as $dentry) { - $deleted = false; - if(isset($dentry['attribs']['']['ref'])) { - $uri = $dentry['attribs']['']['ref']; - $deleted = true; - if(isset($dentry['attribs']['']['when'])) { - $when = $dentry['attribs']['']['when']; - $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s'); - } - else - $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s'); - } - if($deleted && is_array($contact)) { - $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN `contact` on `item`.`contact-id` = `contact`.`id` - WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1", - dbesc($uri), - intval($importer['uid']), - intval($contact['id']) - ); - if(count($r)) { - $item = $r[0]; - - if(! $item['deleted']) - logger('consume_feed: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG); - - if($item['object-type'] === ACTIVITY_OBJ_EVENT) { - logger("Deleting event ".$item['event-id'], LOGGER_DEBUG); - event_delete($item['event-id']); - } - - if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) { - $xo = parse_xml_string($item['object'],false); - $xt = parse_xml_string($item['target'],false); - if($xt->type === ACTIVITY_OBJ_NOTE) { - $i = q("select * from `item` where uri = '%s' and uid = %d limit 1", - dbesc($xt->id), - intval($importer['importer_uid']) - ); - if(count($i)) { - - // For tags, the owner cannot remove the tag on the author's copy of the post. - - $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false); - $author_remove = (($item['origin'] && $item['self']) ? true : false); - $author_copy = (($item['origin']) ? true : false); - - if($owner_remove && $author_copy) - continue; - if($author_remove || $owner_remove) { - $tags = explode(',',$i[0]['tag']); - $newtags = array(); - if(count($tags)) { - foreach($tags as $tag) - if(trim($tag) !== trim($xo->body)) - $newtags[] = trim($tag); - } - q("update item set tag = '%s' where id = %d", - dbesc(implode(',',$newtags)), - intval($i[0]['id']) - ); - create_tags_from_item($i[0]['id']); - } - } - } - } - - if($item['uri'] == $item['parent-uri']) { - $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', - `body` = '', `title` = '' - WHERE `parent-uri` = '%s' AND `uid` = %d", - dbesc($when), - dbesc(datetime_convert()), - dbesc($item['uri']), - intval($importer['uid']) - ); - create_tags_from_itemuri($item['uri'], $importer['uid']); - create_files_from_itemuri($item['uri'], $importer['uid']); - update_thread_uri($item['uri'], $importer['uid']); - } - else { - $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', - `body` = '', `title` = '' - WHERE `uri` = '%s' AND `uid` = %d", - dbesc($when), - dbesc(datetime_convert()), - dbesc($uri), - intval($importer['uid']) - ); - create_tags_from_itemuri($uri, $importer['uid']); - create_files_from_itemuri($uri, $importer['uid']); - if($item['last-child']) { - // ensure that last-child is set in case the comment that had it just got wiped. - q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ", - dbesc(datetime_convert()), - dbesc($item['parent-uri']), - intval($item['uid']) - ); - // who is the last child now? - $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `moderated` = 0 AND `uid` = %d - ORDER BY `created` DESC LIMIT 1", - dbesc($item['parent-uri']), - intval($importer['uid']) - ); - if(count($r)) { - q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d", - intval($r[0]['id']) - ); - } - } - } - } - } - } - } - - // Now process the feed - - if($feed->get_item_quantity()) { - - logger('consume_feed: feed item count = ' . $feed->get_item_quantity()); - - // in inverse date order - if ($datedir) - $items = array_reverse($feed->get_items()); - else - $items = $feed->get_items(); - - - foreach($items as $item) { - - $is_reply = false; - $item_id = $item->get_id(); - $rawthread = $item->get_item_tags( NAMESPACE_THREAD,'in-reply-to'); - if(isset($rawthread[0]['attribs']['']['ref'])) { - $is_reply = true; - $parent_uri = $rawthread[0]['attribs']['']['ref']; - } - - if(($is_reply) && is_array($contact)) { - - if($pass == 1) - continue; - - // not allowed to post - - if($contact['rel'] == CONTACT_IS_FOLLOWER) - continue; - - - // Have we seen it? If not, import it. - - $item_id = $item->get_id(); - $datarray = get_atom_elements($feed, $item, $contact); - - if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN)) - $datarray['author-name'] = $contact['name']; - if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN)) - $datarray['author-link'] = $contact['url']; - if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN)) - $datarray['author-avatar'] = $contact['thumb']; - - if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) { - logger('consume_feed: no author information! ' . print_r($datarray,true)); - continue; - } - - $force_parent = false; - if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) { - if($contact['network'] === NETWORK_OSTATUS) - $force_parent = true; - if(strlen($datarray['title'])) - unset($datarray['title']); - $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d", - dbesc(datetime_convert()), - dbesc($parent_uri), - intval($importer['uid']) - ); - $datarray['last-child'] = 1; - update_thread_uri($parent_uri, $importer['uid']); - } - - - $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", - dbesc($item_id), - intval($importer['uid']) - ); - - // Update content if 'updated' changes - - if(count($r)) { - if (edited_timestamp_is_newer($r[0], $datarray)) { - - // do not accept (ignore) an earlier edit than one we currently have. - if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited']) - continue; - - $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d", - dbesc($datarray['title']), - dbesc($datarray['body']), - dbesc($datarray['tag']), - dbesc(datetime_convert('UTC','UTC',$datarray['edited'])), - dbesc(datetime_convert()), - dbesc($item_id), - intval($importer['uid']) - ); - create_tags_from_itemuri($item_id, $importer['uid']); - update_thread_uri($item_id, $importer['uid']); - } - - // update last-child if it changes - - $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow'); - if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) { - $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d", - dbesc(datetime_convert()), - dbesc($parent_uri), - intval($importer['uid']) - ); - $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d", - intval($allow[0]['data']), - dbesc(datetime_convert()), - dbesc($item_id), - intval($importer['uid']) - ); - update_thread_uri($item_id, $importer['uid']); - } - continue; - } - - - if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) { - // one way feed - no remote comment ability - $datarray['last-child'] = 0; - } - $datarray['parent-uri'] = $parent_uri; - $datarray['uid'] = $importer['uid']; - $datarray['contact-id'] = $contact['id']; - if(($datarray['verb'] === ACTIVITY_LIKE) - || ($datarray['verb'] === ACTIVITY_DISLIKE) - || ($datarray['verb'] === ACTIVITY_ATTEND) - || ($datarray['verb'] === ACTIVITY_ATTENDNO) - || ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) { - $datarray['type'] = 'activity'; - $datarray['gravity'] = GRAVITY_LIKE; - // only one like or dislike per person - // splitted into two queries for performance issues - $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `parent-uri` = '%s' AND NOT `deleted` LIMIT 1", - intval($datarray['uid']), - dbesc($datarray['author-link']), - dbesc($datarray['verb']), - dbesc($datarray['parent-uri']) - ); - if($r && count($r)) - continue; - - $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `thr-parent` = '%s' AND NOT `deleted` LIMIT 1", - intval($datarray['uid']), - dbesc($datarray['author-link']), - dbesc($datarray['verb']), - dbesc($datarray['parent-uri']) - ); - if($r && count($r)) - continue; - } - - if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) { - $xo = parse_xml_string($datarray['object'],false); - $xt = parse_xml_string($datarray['target'],false); - - if($xt->type == ACTIVITY_OBJ_NOTE) { - $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1", - dbesc($xt->id), - intval($importer['importer_uid']) - ); - if(! count($r)) - continue; - - // extract tag, if not duplicate, add to parent item - if($xo->id && $xo->content) { - $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]'; - if(! (stristr($r[0]['tag'],$newtag))) { - q("UPDATE item SET tag = '%s' WHERE id = %d", - dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . $newtag), - intval($r[0]['id']) - ); - create_tags_from_item($r[0]['id']); - } - } - } - } - - $r = item_store($datarray,$force_parent); - continue; - } - - else { - - // Head post of a conversation. Have we seen it? If not, import it. - - $item_id = $item->get_id(); - - $datarray = get_atom_elements($feed, $item, $contact); - - if(is_array($contact)) { - if((! x($datarray,'author-name')) && ($contact['network'] != NETWORK_DFRN)) - $datarray['author-name'] = $contact['name']; - if((! x($datarray,'author-link')) && ($contact['network'] != NETWORK_DFRN)) - $datarray['author-link'] = $contact['url']; - if((! x($datarray,'author-avatar')) && ($contact['network'] != NETWORK_DFRN)) - $datarray['author-avatar'] = $contact['thumb']; - } - - if((! x($datarray,'author-name')) || (! x($datarray,'author-link'))) { - logger('consume_feed: no author information! ' . print_r($datarray,true)); - continue; - } - - // special handling for events - - if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) { - $ev = bbtoevent($datarray['body']); - if((x($ev,'desc') || x($ev,'summary')) && x($ev,'start')) { - $ev['uid'] = $importer['uid']; - $ev['uri'] = $item_id; - $ev['edited'] = $datarray['edited']; - $ev['private'] = $datarray['private']; - $ev['guid'] = $datarray['guid']; - - if(is_array($contact)) - $ev['cid'] = $contact['id']; - $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", - dbesc($item_id), - intval($importer['uid']) - ); - if(count($r)) - $ev['id'] = $r[0]['id']; - $xyz = event_store($ev); - continue; - } - } - - if($contact['network'] === NETWORK_OSTATUS || stristr($contact['url'],'twitter.com')) { - if(strlen($datarray['title'])) - unset($datarray['title']); - $datarray['last-child'] = 1; - } - - - $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", - dbesc($item_id), - intval($importer['uid']) - ); - - // Update content if 'updated' changes - - if(count($r)) { - if (edited_timestamp_is_newer($r[0], $datarray)) { - - // do not accept (ignore) an earlier edit than one we currently have. - if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited']) - continue; - - $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d", - dbesc($datarray['title']), - dbesc($datarray['body']), - dbesc($datarray['tag']), - dbesc(datetime_convert('UTC','UTC',$datarray['edited'])), - dbesc(datetime_convert()), - dbesc($item_id), - intval($importer['uid']) - ); - create_tags_from_itemuri($item_id, $importer['uid']); - update_thread_uri($item_id, $importer['uid']); - } - - // update last-child if it changes - - $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow'); - if($allow && $allow[0]['data'] != $r[0]['last-child']) { - $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d", - intval($allow[0]['data']), - dbesc(datetime_convert()), - dbesc($item_id), - intval($importer['uid']) - ); - update_thread_uri($item_id, $importer['uid']); - } - continue; - } - - if(activity_match($datarray['verb'],ACTIVITY_FOLLOW)) { - logger('consume-feed: New follower'); - new_follower($importer,$contact,$datarray,$item); - return; - } - if(activity_match($datarray['verb'],ACTIVITY_UNFOLLOW)) { - lose_follower($importer,$contact,$datarray,$item); - return; - } - - if(activity_match($datarray['verb'],ACTIVITY_REQ_FRIEND)) { - logger('consume-feed: New friend request'); - new_follower($importer,$contact,$datarray,$item,true); - return; - } - if(activity_match($datarray['verb'],ACTIVITY_UNFRIEND)) { - lose_sharer($importer,$contact,$datarray,$item); - return; - } - - - if(! is_array($contact)) - return; - - - if(($contact['network'] === NETWORK_FEED) || (! strlen($contact['notify']))) { - // one way feed - no remote comment ability - $datarray['last-child'] = 0; - } - if($contact['network'] === NETWORK_FEED) - $datarray['private'] = 2; - - $datarray['parent-uri'] = $item_id; - $datarray['uid'] = $importer['uid']; - $datarray['contact-id'] = $contact['id']; - - if(! link_compare($datarray['owner-link'],$contact['url'])) { - // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery, - // but otherwise there's a possible data mixup on the sender's system. - // the tgroup delivery code called from item_store will correct it if it's a forum, - // but we're going to unconditionally correct it here so that the post will always be owned by our contact. - logger('consume_feed: Correcting item owner.', LOGGER_DEBUG); - $datarray['owner-name'] = $contact['name']; - $datarray['owner-link'] = $contact['url']; - $datarray['owner-avatar'] = $contact['thumb']; - } - - // We've allowed "followers" to reach this point so we can decide if they are - // posting an @-tag delivery, which followers are allowed to do for certain - // page types. Now that we've parsed the post, let's check if it is legit. Otherwise ignore it. - - if(($contact['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['uid'],$datarray))) - continue; - - // This is my contact on another system, but it's really me. - // Turn this into a wall post. - $notify = item_is_remote_self($contact, $datarray); - - $r = item_store($datarray, false, $notify); - logger('Stored - Contact '.$contact['url'].' Notify '.$notify.' return '.$r.' Item '.print_r($datarray, true), LOGGER_DEBUG); - continue; - - } + if ($contact['network'] === NETWORK_DFRN) { + logger("Consume DFRN messages", LOGGER_DEBUG); + + $r = 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` + LEFT JOIN `user` ON `contact`.`uid` = `user`.`uid` + WHERE `contact`.`id` = %d AND `user`.`uid` = %d", + dbesc($contact["id"]), dbesc($importer["uid"]) + ); + if ($r) { + logger("Now import the DFRN feed"); + dfrn::import($xml,$r[0], true); + return; } } } @@ -3039,1144 +1348,6 @@ function item_is_remote_self($contact, &$datarray) { return true; } -function local_delivery($importer,$data) { - $a = get_app(); - - logger(__function__, LOGGER_TRACE); - - if($importer['readonly']) { - // We aren't receiving stuff from this person. But we will quietly ignore them - // rather than a blatant "go away" message. - logger('local_delivery: ignoring'); - return 0; - //NOTREACHED - } - - // Consume notification feed. This may differ from consuming a public feed in several ways - // - might contain email or friend suggestions - // - might contain remote followup to our message - // - in which case we need to accept it and then notify other conversants - // - we may need to send various email notifications - - $feed = new SimplePie(); - $feed->set_raw_data($data); - $feed->enable_order_by_date(false); - $feed->init(); - - - if($feed->error()) - logger('local_delivery: Error parsing XML: ' . $feed->error()); - - - // Check at the feed level for updated contact name and/or photo - - $name_updated = ''; - $new_name = ''; - $photo_timestamp = ''; - $photo_url = ''; - $contact_updated = ''; - - - $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'owner'); - -// Fallback should not be needed here. If it isn't DFRN it won't have DFRN updated tags -// if(! $rawtags) -// $rawtags = $feed->get_feed_tags( SIMPLEPIE_NAMESPACE_ATOM_10, 'author'); - - if($rawtags) { - $elems = $rawtags[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]; - if($elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']) { - $name_updated = $elems['name'][0]['attribs'][NAMESPACE_DFRN]['updated']; - $new_name = $elems['name'][0]['data']; - - // Manually checking for changed contact names - if (($new_name != $importer['name']) AND ($new_name != "") AND ($name_updated <= $importer['name-date'])) { - $name_updated = date("c"); - $photo_timestamp = date("c"); - } - } - if((x($elems,'link')) && ($elems['link'][0]['attribs']['']['rel'] === 'photo') && ($elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated'])) { - if ($photo_timestamp == "") - $photo_timestamp = datetime_convert('UTC','UTC',$elems['link'][0]['attribs'][NAMESPACE_DFRN]['updated']); - $photo_url = $elems['link'][0]['attribs']['']['href']; - } - } - - if(($photo_timestamp) && (strlen($photo_url)) && ($photo_timestamp > $importer['avatar-date'])) { - - $contact_updated = $photo_timestamp; - - logger('local_delivery: Updating photo for ' . $importer['name']); - require_once("include/Photo.php"); - - $photos = import_profile_photo($photo_url,$importer['importer_uid'],$importer['id']); - - q("UPDATE `contact` SET `avatar-date` = '%s', `photo` = '%s', `thumb` = '%s', `micro` = '%s' - WHERE `uid` = %d AND `id` = %d AND NOT `self`", - dbesc(datetime_convert()), - dbesc($photos[0]), - dbesc($photos[1]), - dbesc($photos[2]), - intval($importer['importer_uid']), - intval($importer['id']) - ); - } - - if(($name_updated) && (strlen($new_name)) && ($name_updated > $importer['name-date'])) { - if ($name_updated > $contact_updated) - $contact_updated = $name_updated; - - $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `id` = %d LIMIT 1", - intval($importer['importer_uid']), - intval($importer['id']) - ); - - $x = q("UPDATE `contact` SET `name` = '%s', `name-date` = '%s' WHERE `uid` = %d AND `id` = %d AND `name` != '%s' AND NOT `self`", - dbesc(notags(trim($new_name))), - dbesc(datetime_convert()), - intval($importer['importer_uid']), - intval($importer['id']), - dbesc(notags(trim($new_name))) - ); - - // do our best to update the name on content items - - if(count($r) AND (notags(trim($new_name)) != $r[0]['name'])) { - q("UPDATE `item` SET `author-name` = '%s' WHERE `author-name` = '%s' AND `author-link` = '%s' AND `uid` = %d AND `author-name` != '%s'", - dbesc(notags(trim($new_name))), - dbesc($r[0]['name']), - dbesc($r[0]['url']), - intval($importer['importer_uid']), - dbesc(notags(trim($new_name))) - ); - } - } - - if ($contact_updated AND $new_name AND $photo_url) - poco_check($importer['url'], $new_name, NETWORK_DFRN, $photo_url, "", "", "", "", "", $contact_updated, 2, $importer['id'], $importer['importer_uid']); - - // Currently unsupported - needs a lot of work - $reloc = $feed->get_feed_tags( NAMESPACE_DFRN, 'relocate' ); - if(isset($reloc[0]['child'][NAMESPACE_DFRN])) { - $base = $reloc[0]['child'][NAMESPACE_DFRN]; - $newloc = array(); - $newloc['uid'] = $importer['importer_uid']; - $newloc['cid'] = $importer['id']; - $newloc['name'] = notags(unxmlify($base['name'][0]['data'])); - $newloc['photo'] = notags(unxmlify($base['photo'][0]['data'])); - $newloc['thumb'] = notags(unxmlify($base['thumb'][0]['data'])); - $newloc['micro'] = notags(unxmlify($base['micro'][0]['data'])); - $newloc['url'] = notags(unxmlify($base['url'][0]['data'])); - $newloc['request'] = notags(unxmlify($base['request'][0]['data'])); - $newloc['confirm'] = notags(unxmlify($base['confirm'][0]['data'])); - $newloc['notify'] = notags(unxmlify($base['notify'][0]['data'])); - $newloc['poll'] = notags(unxmlify($base['poll'][0]['data'])); - $newloc['sitepubkey'] = notags(unxmlify($base['sitepubkey'][0]['data'])); - /** relocated user must have original key pair */ - /*$newloc['pubkey'] = notags(unxmlify($base['pubkey'][0]['data'])); - $newloc['prvkey'] = notags(unxmlify($base['prvkey'][0]['data']));*/ - - logger("items:relocate contact ".print_r($newloc, true).print_r($importer, true), LOGGER_DEBUG); - - // update contact - $r = q("SELECT photo, url FROM contact WHERE id=%d AND uid=%d;", - intval($importer['id']), - intval($importer['importer_uid'])); - if ($r === false) - return 1; - $old = $r[0]; - - $x = q("UPDATE contact SET - name = '%s', - photo = '%s', - thumb = '%s', - micro = '%s', - url = '%s', - nurl = '%s', - request = '%s', - confirm = '%s', - notify = '%s', - poll = '%s', - `site-pubkey` = '%s' - WHERE id=%d AND uid=%d;", - dbesc($newloc['name']), - dbesc($newloc['photo']), - dbesc($newloc['thumb']), - dbesc($newloc['micro']), - dbesc($newloc['url']), - dbesc(normalise_link($newloc['url'])), - dbesc($newloc['request']), - dbesc($newloc['confirm']), - dbesc($newloc['notify']), - dbesc($newloc['poll']), - dbesc($newloc['sitepubkey']), - intval($importer['id']), - intval($importer['importer_uid'])); - - if ($x === false) - return 1; - // update items - $fields = array( - 'owner-link' => array($old['url'], $newloc['url']), - 'author-link' => array($old['url'], $newloc['url']), - 'owner-avatar' => array($old['photo'], $newloc['photo']), - 'author-avatar' => array($old['photo'], $newloc['photo']), - ); - foreach ($fields as $n=>$f){ - $x = q("UPDATE `item` SET `%s`='%s' WHERE `%s`='%s' AND uid=%d", - $n, dbesc($f[1]), - $n, dbesc($f[0]), - intval($importer['importer_uid'])); - if ($x === false) - return 1; - } - - /// @TODO - /// merge with current record, current contents have priority - /// update record, set url-updated - /// update profile photos - /// schedule a scan? - return 0; - } - - - // handle friend suggestion notification - - $sugg = $feed->get_feed_tags( NAMESPACE_DFRN, 'suggest' ); - if(isset($sugg[0]['child'][NAMESPACE_DFRN])) { - $base = $sugg[0]['child'][NAMESPACE_DFRN]; - $fsugg = array(); - $fsugg['uid'] = $importer['importer_uid']; - $fsugg['cid'] = $importer['id']; - $fsugg['name'] = notags(unxmlify($base['name'][0]['data'])); - $fsugg['photo'] = notags(unxmlify($base['photo'][0]['data'])); - $fsugg['url'] = notags(unxmlify($base['url'][0]['data'])); - $fsugg['request'] = notags(unxmlify($base['request'][0]['data'])); - $fsugg['body'] = escape_tags(unxmlify($base['note'][0]['data'])); - - // Does our member already have a friend matching this description? - - $r = q("SELECT * FROM `contact` WHERE `name` = '%s' AND `nurl` = '%s' AND `uid` = %d LIMIT 1", - dbesc($fsugg['name']), - dbesc(normalise_link($fsugg['url'])), - intval($fsugg['uid']) - ); - if(count($r)) - return 0; - - // Do we already have an fcontact record for this person? - - $fid = 0; - $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1", - dbesc($fsugg['url']), - dbesc($fsugg['name']), - dbesc($fsugg['request']) - ); - if(count($r)) { - $fid = $r[0]['id']; - - // OK, we do. Do we already have an introduction for this person ? - $r = q("select id from intro where uid = %d and fid = %d limit 1", - intval($fsugg['uid']), - intval($fid) - ); - if(count($r)) - return 0; - } - if(! $fid) - $r = q("INSERT INTO `fcontact` ( `name`,`url`,`photo`,`request` ) VALUES ( '%s', '%s', '%s', '%s' ) ", - dbesc($fsugg['name']), - dbesc($fsugg['url']), - dbesc($fsugg['photo']), - dbesc($fsugg['request']) - ); - $r = q("SELECT * FROM `fcontact` WHERE `url` = '%s' AND `name` = '%s' AND `request` = '%s' LIMIT 1", - dbesc($fsugg['url']), - dbesc($fsugg['name']), - dbesc($fsugg['request']) - ); - if(count($r)) { - $fid = $r[0]['id']; - } - // database record did not get created. Quietly give up. - else - return 0; - - - $hash = random_string(); - - $r = q("INSERT INTO `intro` ( `uid`, `fid`, `contact-id`, `note`, `hash`, `datetime`, `blocked` ) - VALUES( %d, %d, %d, '%s', '%s', '%s', %d )", - intval($fsugg['uid']), - intval($fid), - intval($fsugg['cid']), - dbesc($fsugg['body']), - dbesc($hash), - dbesc(datetime_convert()), - intval(0) - ); - - notification(array( - 'type' => NOTIFY_SUGGEST, - 'notify_flags' => $importer['notify-flags'], - 'language' => $importer['language'], - 'to_name' => $importer['username'], - 'to_email' => $importer['email'], - 'uid' => $importer['importer_uid'], - 'item' => $fsugg, - 'link' => $a->get_baseurl() . '/notifications/intros', - 'source_name' => $importer['name'], - 'source_link' => $importer['url'], - 'source_photo' => $importer['photo'], - 'verb' => ACTIVITY_REQ_FRIEND, - 'otype' => 'intro' - )); - - return 0; - } - - $ismail = false; - - $rawmail = $feed->get_feed_tags( NAMESPACE_DFRN, 'mail' ); - if(isset($rawmail[0]['child'][NAMESPACE_DFRN])) { - - logger('local_delivery: private message received'); - - $ismail = true; - $base = $rawmail[0]['child'][NAMESPACE_DFRN]; - - $msg = array(); - $msg['uid'] = $importer['importer_uid']; - $msg['from-name'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['name'][0]['data'])); - $msg['from-photo'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['avatar'][0]['data'])); - $msg['from-url'] = notags(unxmlify($base['sender'][0]['child'][NAMESPACE_DFRN]['uri'][0]['data'])); - $msg['contact-id'] = $importer['id']; - $msg['title'] = notags(unxmlify($base['subject'][0]['data'])); - $msg['body'] = escape_tags(unxmlify($base['content'][0]['data'])); - $msg['seen'] = 0; - $msg['replied'] = 0; - $msg['uri'] = notags(unxmlify($base['id'][0]['data'])); - $msg['parent-uri'] = notags(unxmlify($base['in-reply-to'][0]['data'])); - $msg['created'] = datetime_convert(notags(unxmlify('UTC','UTC',$base['sentdate'][0]['data']))); - - dbesc_array($msg); - - $r = dbq("INSERT INTO `mail` (`" . implode("`, `", array_keys($msg)) - . "`) VALUES ('" . implode("', '", array_values($msg)) . "')" ); - - // send notifications. - - require_once('include/enotify.php'); - - $notif_params = 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' => $msg, - 'source_name' => $msg['from-name'], - 'source_link' => $importer['url'], - 'source_photo' => $importer['thumb'], - 'verb' => ACTIVITY_POST, - 'otype' => 'mail' - ); - - notification($notif_params); - return 0; - - // NOTREACHED - } - - $community_page = 0; - $rawtags = $feed->get_feed_tags( NAMESPACE_DFRN, 'community'); - if($rawtags) { - $community_page = intval($rawtags[0]['data']); - } - if(intval($importer['forum']) != $community_page) { - q("update contact set forum = %d where id = %d", - intval($community_page), - intval($importer['id']) - ); - $importer['forum'] = (string) $community_page; - } - - logger('local_delivery: feed item count = ' . $feed->get_item_quantity()); - - // process any deleted entries - - $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry'); - if(is_array($del_entries) && count($del_entries)) { - foreach($del_entries as $dentry) { - $deleted = false; - if(isset($dentry['attribs']['']['ref'])) { - $uri = $dentry['attribs']['']['ref']; - $deleted = true; - if(isset($dentry['attribs']['']['when'])) { - $when = $dentry['attribs']['']['when']; - $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s'); - } - else - $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s'); - } - if($deleted) { - - // check for relayed deletes to our conversation - - $is_reply = false; - $r = q("select * from item where uri = '%s' and uid = %d limit 1", - dbesc($uri), - intval($importer['importer_uid']) - ); - if(count($r)) { - $parent_uri = $r[0]['parent-uri']; - if($r[0]['id'] != $r[0]['parent']) - $is_reply = true; - } - - if($is_reply) { - $community = false; - - if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) { - $sql_extra = ''; - $community = true; - logger('local_delivery: possible community delete'); - } - else - $sql_extra = " and contact.self = 1 and item.wall = 1 "; - - // was the top-level post for this reply written by somebody on this site? - // Specifically, the recipient? - - $is_a_remote_delete = false; - - // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used? - $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`, - `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item` - INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id` - WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s') - AND `item`.`uid` = %d - $sql_extra - LIMIT 1", - dbesc($parent_uri), - dbesc($parent_uri), - dbesc($parent_uri), - intval($importer['importer_uid']) - ); - if($r && count($r)) - $is_a_remote_delete = true; - - // Does this have the characteristics of a community or private group comment? - // If it's a reply to a wall post on a community/prvgroup page it's a - // valid community comment. Also forum_mode makes it valid for sure. - // If neither, it's not. - - if($is_a_remote_delete && $community) { - if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) { - $is_a_remote_delete = false; - logger('local_delivery: not a community delete'); - } - } - - if($is_a_remote_delete) { - logger('local_delivery: received remote delete'); - } - } - - $r = q("SELECT `item`.*, `contact`.`self` FROM `item` INNER JOIN contact on `item`.`contact-id` = `contact`.`id` - WHERE `uri` = '%s' AND `item`.`uid` = %d AND `contact-id` = %d AND NOT `item`.`file` LIKE '%%[%%' LIMIT 1", - dbesc($uri), - intval($importer['importer_uid']), - intval($importer['id']) - ); - - if(count($r)) { - $item = $r[0]; - - if($item['deleted']) - continue; - - logger('local_delivery: deleting item ' . $item['id'] . ' uri=' . $item['uri'], LOGGER_DEBUG); - - if($item['object-type'] === ACTIVITY_OBJ_EVENT) { - logger("Deleting event ".$item['event-id'], LOGGER_DEBUG); - event_delete($item['event-id']); - } - - if(($item['verb'] === ACTIVITY_TAG) && ($item['object-type'] === ACTIVITY_OBJ_TAGTERM)) { - $xo = parse_xml_string($item['object'],false); - $xt = parse_xml_string($item['target'],false); - - if($xt->type === ACTIVITY_OBJ_NOTE) { - $i = q("select * from `item` where uri = '%s' and uid = %d limit 1", - dbesc($xt->id), - intval($importer['importer_uid']) - ); - if(count($i)) { - - // For tags, the owner cannot remove the tag on the author's copy of the post. - - $owner_remove = (($item['contact-id'] == $i[0]['contact-id']) ? true: false); - $author_remove = (($item['origin'] && $item['self']) ? true : false); - $author_copy = (($item['origin']) ? true : false); - - if($owner_remove && $author_copy) - continue; - if($author_remove || $owner_remove) { - $tags = explode(',',$i[0]['tag']); - $newtags = array(); - if(count($tags)) { - foreach($tags as $tag) - if(trim($tag) !== trim($xo->body)) - $newtags[] = trim($tag); - } - q("update item set tag = '%s' where id = %d", - dbesc(implode(',',$newtags)), - intval($i[0]['id']) - ); - create_tags_from_item($i[0]['id']); - } - } - } - } - - if($item['uri'] == $item['parent-uri']) { - $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', - `body` = '', `title` = '' - WHERE `parent-uri` = '%s' AND `uid` = %d", - dbesc($when), - dbesc(datetime_convert()), - dbesc($item['uri']), - intval($importer['importer_uid']) - ); - create_tags_from_itemuri($item['uri'], $importer['importer_uid']); - create_files_from_itemuri($item['uri'], $importer['importer_uid']); - update_thread_uri($item['uri'], $importer['importer_uid']); - } - else { - $r = q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', - `body` = '', `title` = '' - WHERE `uri` = '%s' AND `uid` = %d", - dbesc($when), - dbesc(datetime_convert()), - dbesc($uri), - intval($importer['importer_uid']) - ); - create_tags_from_itemuri($uri, $importer['importer_uid']); - create_files_from_itemuri($uri, $importer['importer_uid']); - update_thread_uri($uri, $importer['importer_uid']); - if($item['last-child']) { - // ensure that last-child is set in case the comment that had it just got wiped. - q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d ", - dbesc(datetime_convert()), - dbesc($item['parent-uri']), - intval($item['uid']) - ); - // who is the last child now? - $r = q("SELECT `id` FROM `item` WHERE `parent-uri` = '%s' AND `type` != 'activity' AND `deleted` = 0 AND `uid` = %d - ORDER BY `created` DESC LIMIT 1", - dbesc($item['parent-uri']), - intval($importer['importer_uid']) - ); - if(count($r)) { - q("UPDATE `item` SET `last-child` = 1 WHERE `id` = %d", - intval($r[0]['id']) - ); - } - } - // if this is a relayed delete, propagate it to other recipients - - if($is_a_remote_delete) - proc_run('php',"include/notifier.php","drop",$item['id']); - } - } - } - } - } - - - foreach($feed->get_items() as $item) { - - $is_reply = false; - $item_id = $item->get_id(); - $rawthread = $item->get_item_tags( NAMESPACE_THREAD, 'in-reply-to'); - if(isset($rawthread[0]['attribs']['']['ref'])) { - $is_reply = true; - $parent_uri = $rawthread[0]['attribs']['']['ref']; - } - - if($is_reply) { - $community = false; - - if($importer['page-flags'] == PAGE_COMMUNITY || $importer['page-flags'] == PAGE_PRVGROUP ) { - $sql_extra = ''; - $community = true; - logger('local_delivery: possible community reply'); - } - else - $sql_extra = " and contact.self = 1 and item.wall = 1 "; - - // was the top-level post for this reply written by somebody on this site? - // Specifically, the recipient? - - $is_a_remote_comment = false; - $top_uri = $parent_uri; - - $r = q("select `item`.`parent-uri` from `item` - WHERE `item`.`uri` = '%s' - LIMIT 1", - dbesc($parent_uri) - ); - if($r && count($r)) { - $top_uri = $r[0]['parent-uri']; - - // POSSIBLE CLEANUP --> Why select so many fields when only forum_mode and wall are used? - $r = q("select `item`.`id`, `item`.`uri`, `item`.`tag`, `item`.`forum_mode`,`item`.`origin`,`item`.`wall`, - `contact`.`name`, `contact`.`url`, `contact`.`thumb` from `item` - INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id` - WHERE `item`.`uri` = '%s' AND (`item`.`parent-uri` = '%s' or `item`.`thr-parent` = '%s') - AND `item`.`uid` = %d - $sql_extra - LIMIT 1", - dbesc($top_uri), - dbesc($top_uri), - dbesc($top_uri), - intval($importer['importer_uid']) - ); - if($r && count($r)) - $is_a_remote_comment = true; - } - - // Does this have the characteristics of a community or private group comment? - // If it's a reply to a wall post on a community/prvgroup page it's a - // valid community comment. Also forum_mode makes it valid for sure. - // If neither, it's not. - - if($is_a_remote_comment && $community) { - if((! $r[0]['forum_mode']) && (! $r[0]['wall'])) { - $is_a_remote_comment = false; - logger('local_delivery: not a community reply'); - } - } - - if($is_a_remote_comment) { - logger('local_delivery: received remote comment'); - $is_like = false; - // remote reply to our post. Import and then notify everybody else. - - $datarray = get_atom_elements($feed, $item); - - $r = q("SELECT `id`, `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", - dbesc($item_id), - intval($importer['importer_uid']) - ); - - // Update content if 'updated' changes - - if(count($r)) { - $iid = $r[0]['id']; - if (edited_timestamp_is_newer($r[0], $datarray)) { - - // do not accept (ignore) an earlier edit than one we currently have. - if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited']) - continue; - - logger('received updated comment' , LOGGER_DEBUG); - $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d", - dbesc($datarray['title']), - dbesc($datarray['body']), - dbesc($datarray['tag']), - dbesc(datetime_convert('UTC','UTC',$datarray['edited'])), - dbesc(datetime_convert()), - dbesc($item_id), - intval($importer['importer_uid']) - ); - create_tags_from_itemuri($item_id, $importer['importer_uid']); - - proc_run('php',"include/notifier.php","comment-import",$iid); - - } - - continue; - } - - - - $own = q("select name,url,thumb from contact where uid = %d and self = 1 limit 1", - intval($importer['importer_uid']) - ); - - - $datarray['type'] = 'remote-comment'; - $datarray['wall'] = 1; - $datarray['parent-uri'] = $parent_uri; - $datarray['uid'] = $importer['importer_uid']; - $datarray['owner-name'] = $own[0]['name']; - $datarray['owner-link'] = $own[0]['url']; - $datarray['owner-avatar'] = $own[0]['thumb']; - $datarray['contact-id'] = $importer['id']; - - if(($datarray['verb'] === ACTIVITY_LIKE) - || ($datarray['verb'] === ACTIVITY_DISLIKE) - || ($datarray['verb'] === ACTIVITY_ATTEND) - || ($datarray['verb'] === ACTIVITY_ATTENDNO) - || ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) { - $is_like = true; - $datarray['type'] = 'activity'; - $datarray['gravity'] = GRAVITY_LIKE; - $datarray['last-child'] = 0; - // only one like or dislike per person - // splitted into two queries for performance issues - $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `parent-uri` = '%s' AND NOT `deleted` LIMIT 1", - intval($datarray['uid']), - dbesc($datarray['author-link']), - dbesc($datarray['verb']), - dbesc($datarray['parent-uri']) - ); - if($r && count($r)) - continue; - - $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `thr-parent` = '%s' AND NOT `deleted` LIMIT 1", - intval($datarray['uid']), - dbesc($datarray['author-link']), - dbesc($datarray['verb']), - dbesc($datarray['parent-uri']) - - ); - if($r && count($r)) - continue; - } - - if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) { - - $xo = parse_xml_string($datarray['object'],false); - $xt = parse_xml_string($datarray['target'],false); - - if(($xt->type == ACTIVITY_OBJ_NOTE) && ($xt->id)) { - - // fetch the parent item - - $tagp = q("select * from item where uri = '%s' and uid = %d limit 1", - dbesc($xt->id), - intval($importer['importer_uid']) - ); - if(! count($tagp)) - continue; - - // extract tag, if not duplicate, and this user allows tags, add to parent item - - if($xo->id && $xo->content) { - $newtag = '#[url=' . $xo->id . ']'. $xo->content . '[/url]'; - if(! (stristr($tagp[0]['tag'],$newtag))) { - $i = q("SELECT `blocktags` FROM `user` where `uid` = %d LIMIT 1", - intval($importer['importer_uid']) - ); - if(count($i) && ! intval($i[0]['blocktags'])) { - q("UPDATE item SET tag = '%s', `edited` = '%s', `changed` = '%s' WHERE id = %d", - dbesc($tagp[0]['tag'] . (strlen($tagp[0]['tag']) ? ',' : '') . $newtag), - intval($tagp[0]['id']), - dbesc(datetime_convert()), - dbesc(datetime_convert()) - ); - create_tags_from_item($tagp[0]['id']); - } - } - } - } - } - - - $posted_id = item_store($datarray); - $parent = 0; - - if($posted_id) { - - $datarray["id"] = $posted_id; - - $r = q("SELECT `parent`, `parent-uri` FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1", - intval($posted_id), - intval($importer['importer_uid']) - ); - if(count($r)) { - $parent = $r[0]['parent']; - $parent_uri = $r[0]['parent-uri']; - } - - if(! $is_like) { - $r1 = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `uid` = %d AND `parent` = %d", - dbesc(datetime_convert()), - intval($importer['importer_uid']), - intval($r[0]['parent']) - ); - - $r2 = q("UPDATE `item` SET `last-child` = 1, `changed` = '%s' WHERE `uid` = %d AND `id` = %d", - dbesc(datetime_convert()), - intval($importer['importer_uid']), - intval($posted_id) - ); - } - - if($posted_id && $parent) { - - proc_run('php',"include/notifier.php","comment-import","$posted_id"); - - if((! $is_like) && (! $importer['self'])) { - - require_once('include/enotify.php'); - - notification(array( - 'type' => NOTIFY_COMMENT, - 'notify_flags' => $importer['notify-flags'], - 'language' => $importer['language'], - 'to_name' => $importer['username'], - 'to_email' => $importer['email'], - 'uid' => $importer['importer_uid'], - 'item' => $datarray, - 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)), - 'source_name' => stripslashes($datarray['author-name']), - 'source_link' => $datarray['author-link'], - 'source_photo' => ((link_compare($datarray['author-link'],$importer['url'])) - ? $importer['thumb'] : $datarray['author-avatar']), - 'verb' => ACTIVITY_POST, - 'otype' => 'item', - 'parent' => $parent, - 'parent_uri' => $parent_uri, - )); - - } - } - - return 0; - // NOTREACHED - } - } - else { - - // regular comment that is part of this total conversation. Have we seen it? If not, import it. - - $item_id = $item->get_id(); - $datarray = get_atom_elements($feed,$item); - - if($importer['rel'] == CONTACT_IS_FOLLOWER) - continue; - - $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", - dbesc($item_id), - intval($importer['importer_uid']) - ); - - // Update content if 'updated' changes - - if(count($r)) { - if (edited_timestamp_is_newer($r[0], $datarray)) { - - // do not accept (ignore) an earlier edit than one we currently have. - if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited']) - continue; - - $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d", - dbesc($datarray['title']), - dbesc($datarray['body']), - dbesc($datarray['tag']), - dbesc(datetime_convert('UTC','UTC',$datarray['edited'])), - dbesc(datetime_convert()), - dbesc($item_id), - intval($importer['importer_uid']) - ); - create_tags_from_itemuri($item_id, $importer['importer_uid']); - } - - // update last-child if it changes - - $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow'); - if(($allow) && ($allow[0]['data'] != $r[0]['last-child'])) { - $r = q("UPDATE `item` SET `last-child` = 0, `changed` = '%s' WHERE `parent-uri` = '%s' AND `uid` = %d", - dbesc(datetime_convert()), - dbesc($parent_uri), - intval($importer['importer_uid']) - ); - $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d", - intval($allow[0]['data']), - dbesc(datetime_convert()), - dbesc($item_id), - intval($importer['importer_uid']) - ); - } - continue; - } - - $datarray['parent-uri'] = $parent_uri; - $datarray['uid'] = $importer['importer_uid']; - $datarray['contact-id'] = $importer['id']; - if(($datarray['verb'] === ACTIVITY_LIKE) - || ($datarray['verb'] === ACTIVITY_DISLIKE) - || ($datarray['verb'] === ACTIVITY_ATTEND) - || ($datarray['verb'] === ACTIVITY_ATTENDNO) - || ($datarray['verb'] === ACTIVITY_ATTENDMAYBE)) { - $datarray['type'] = 'activity'; - $datarray['gravity'] = GRAVITY_LIKE; - // only one like or dislike per person - // splitted into two queries for performance issues - $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `parent-uri` = '%s' AND NOT `deleted` LIMIT 1", - intval($datarray['uid']), - dbesc($datarray['author-link']), - dbesc($datarray['verb']), - dbesc($datarray['parent-uri']) - ); - if($r && count($r)) - continue; - - $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `author-link` = '%s' AND `verb` = '%s' AND `thr-parent` = '%s' AND NOT `deleted` LIMIT 1", - intval($datarray['uid']), - dbesc($datarray['author-link']), - dbesc($datarray['verb']), - dbesc($datarray['parent-uri']) - ); - if($r && count($r)) - continue; - - } - - if(($datarray['verb'] === ACTIVITY_TAG) && ($datarray['object-type'] === ACTIVITY_OBJ_TAGTERM)) { - - $xo = parse_xml_string($datarray['object'],false); - $xt = parse_xml_string($datarray['target'],false); - - if($xt->type == ACTIVITY_OBJ_NOTE) { - $r = q("select * from item where `uri` = '%s' AND `uid` = %d limit 1", - dbesc($xt->id), - intval($importer['importer_uid']) - ); - if(! count($r)) - continue; - - // extract tag, if not duplicate, add to parent item - if($xo->content) { - if(! (stristr($r[0]['tag'],trim($xo->content)))) { - q("UPDATE item SET tag = '%s' WHERE id = %d", - dbesc($r[0]['tag'] . (strlen($r[0]['tag']) ? ',' : '') . '#[url=' . $xo->id . ']'. $xo->content . '[/url]'), - intval($r[0]['id']) - ); - create_tags_from_item($r[0]['id']); - } - } - } - } - - $posted_id = item_store($datarray); - - // find out if our user is involved in this conversation and wants to be notified. - - if(!x($datarray['type']) || $datarray['type'] != 'activity') { - - $myconv = q("SELECT `author-link`, `author-avatar`, `parent` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d AND `parent` != 0 AND `deleted` = 0", - dbesc($top_uri), - intval($importer['importer_uid']) - ); - - if(count($myconv)) { - $importer_url = $a->get_baseurl() . '/profile/' . $importer['nickname']; - - // first make sure this isn't our own post coming back to us from a wall-to-wall event - if(! link_compare($datarray['author-link'],$importer_url)) { - - - 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['importer_uid'], - 'item' => $datarray, - 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)), - 'source_name' => stripslashes($datarray['author-name']), - 'source_link' => $datarray['author-link'], - 'source_photo' => ((link_compare($datarray['author-link'],$importer['url'])) - ? $importer['thumb'] : $datarray['author-avatar']), - 'verb' => ACTIVITY_POST, - 'otype' => 'item', - 'parent' => $conv_parent, - 'parent_uri' => $parent_uri - - )); - - // only send one notification - break; - } - } - } - } - continue; - } - } - - else { - - // Head post of a conversation. Have we seen it? If not, import it. - - - $item_id = $item->get_id(); - $datarray = get_atom_elements($feed,$item); - - if((x($datarray,'object-type')) && ($datarray['object-type'] === ACTIVITY_OBJ_EVENT)) { - $ev = bbtoevent($datarray['body']); - if((x($ev,'desc') || x($ev,'summary')) && x($ev,'start')) { - $ev['cid'] = $importer['id']; - $ev['uid'] = $importer['uid']; - $ev['uri'] = $item_id; - $ev['edited'] = $datarray['edited']; - $ev['private'] = $datarray['private']; - $ev['guid'] = $datarray['guid']; - - $r = q("SELECT * FROM `event` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", - dbesc($item_id), - intval($importer['uid']) - ); - if(count($r)) - $ev['id'] = $r[0]['id']; - $xyz = event_store($ev); - continue; - } - } - - $r = q("SELECT `uid`, `last-child`, `edited`, `body` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", - dbesc($item_id), - intval($importer['importer_uid']) - ); - - // Update content if 'updated' changes - - if(count($r)) { - if (edited_timestamp_is_newer($r[0], $datarray)) { - - // do not accept (ignore) an earlier edit than one we currently have. - if(datetime_convert('UTC','UTC',$datarray['edited']) < $r[0]['edited']) - continue; - - $r = q("UPDATE `item` SET `title` = '%s', `body` = '%s', `tag` = '%s', `edited` = '%s', `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d", - dbesc($datarray['title']), - dbesc($datarray['body']), - dbesc($datarray['tag']), - dbesc(datetime_convert('UTC','UTC',$datarray['edited'])), - dbesc(datetime_convert()), - dbesc($item_id), - intval($importer['importer_uid']) - ); - create_tags_from_itemuri($item_id, $importer['importer_uid']); - update_thread_uri($item_id, $importer['importer_uid']); - } - - // update last-child if it changes - - $allow = $item->get_item_tags( NAMESPACE_DFRN, 'comment-allow'); - if($allow && $allow[0]['data'] != $r[0]['last-child']) { - $r = q("UPDATE `item` SET `last-child` = %d , `changed` = '%s' WHERE `uri` = '%s' AND `uid` = %d", - intval($allow[0]['data']), - dbesc(datetime_convert()), - dbesc($item_id), - intval($importer['importer_uid']) - ); - } - continue; - } - - $datarray['parent-uri'] = $item_id; - $datarray['uid'] = $importer['importer_uid']; - $datarray['contact-id'] = $importer['id']; - - - if(! link_compare($datarray['owner-link'],$importer['url'])) { - // The item owner info is not our contact. It's OK and is to be expected if this is a tgroup delivery, - // but otherwise there's a possible data mixup on the sender's system. - // the tgroup delivery code called from item_store will correct it if it's a forum, - // but we're going to unconditionally correct it here so that the post will always be owned by our contact. - logger('local_delivery: Correcting item owner.', LOGGER_DEBUG); - $datarray['owner-name'] = $importer['senderName']; - $datarray['owner-link'] = $importer['url']; - $datarray['owner-avatar'] = $importer['thumb']; - } - - if(($importer['rel'] == CONTACT_IS_FOLLOWER) && (! tgroup_check($importer['importer_uid'],$datarray))) - continue; - - // This is my contact on another system, but it's really me. - // Turn this into a wall post. - $notify = item_is_remote_self($importer, $datarray); - - $posted_id = item_store($datarray, false, $notify); - - if(stristr($datarray['verb'],ACTIVITY_POKE)) { - $verb = urldecode(substr($datarray['verb'],strpos($datarray['verb'],'#')+1)); - if(! $verb) - continue; - $xo = parse_xml_string($datarray['object'],false); - - if(($xo->type == ACTIVITY_OBJ_PERSON) && ($xo->id)) { - - // somebody was poked/prodded. Was it me? - - $links = parse_xml_string("".unxmlify($xo->link)."",false); - - foreach($links->link as $l) { - $atts = $l->attributes(); - switch($atts['rel']) { - case "alternate": - $Blink = $atts['href']; - break; - default: - break; - } - } - if($Blink && link_compare($Blink,$a->get_baseurl() . '/profile/' . $importer['nickname'])) { - - // send a notification - require_once('include/enotify.php'); - - notification(array( - 'type' => NOTIFY_POKE, - 'notify_flags' => $importer['notify-flags'], - 'language' => $importer['language'], - 'to_name' => $importer['username'], - 'to_email' => $importer['email'], - 'uid' => $importer['importer_uid'], - 'item' => $datarray, - 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($posted_id)), - 'source_name' => stripslashes($datarray['author-name']), - 'source_link' => $datarray['author-link'], - 'source_photo' => ((link_compare($datarray['author-link'],$importer['url'])) - ? $importer['thumb'] : $datarray['author-avatar']), - 'verb' => $datarray['verb'], - 'otype' => 'person', - 'activity' => $verb, - 'parent' => $datarray['parent'] - )); - } - } - } - - continue; - } - } - - return 0; - // NOTREACHED - -} - - function new_follower($importer,$contact,$datarray,$item,$sharing = false) { $url = notags(trim($datarray['author-link'])); $name = notags(trim($datarray['author-name'])); @@ -4287,7 +1458,7 @@ function new_follower($importer,$contact,$datarray,$item,$sharing = false) { } } -function lose_follower($importer,$contact,$datarray,$item) { +function lose_follower($importer,$contact,$datarray = array(),$item = "") { if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_SHARING)) { q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d", @@ -4300,7 +1471,7 @@ function lose_follower($importer,$contact,$datarray,$item) { } } -function lose_sharer($importer,$contact,$datarray,$item) { +function lose_sharer($importer,$contact,$datarray = array(),$item = "") { if(($contact['rel'] == CONTACT_IS_FRIEND) || ($contact['rel'] == CONTACT_IS_FOLLOWER)) { q("UPDATE `contact` SET `rel` = %d WHERE `id` = %d", @@ -4313,7 +1484,6 @@ function lose_sharer($importer,$contact,$datarray,$item) { } } - function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') { $a = get_app(); @@ -4356,189 +1526,6 @@ function subscribe_to_hub($url,$importer,$contact,$hubmode = 'subscribe') { } - -function atom_author($tag,$name,$uri,$h,$w,$photo,$geo) { - $o = ''; - if(! $tag) - return $o; - $name = xmlify($name); - $uri = xmlify($uri); - $h = intval($h); - $w = intval($w); - $photo = xmlify($photo); - - - $o .= "<$tag>\r\n"; - $o .= "\t$name\r\n"; - $o .= "\t$uri\r\n"; - $o .= "\t".'' . "\r\n"; - $o .= "\t".'' . "\r\n"; - - if ($tag == "author") { - - if($geo) - $o .= ''.xmlify($geo).''."\r\n"; - - $r = q("SELECT `profile`.`locality`, `profile`.`region`, `profile`.`country-name`, - `profile`.`name`, `profile`.`pub_keywords`, `profile`.`about`, - `profile`.`homepage`,`contact`.`nick` FROM `profile` - INNER JOIN `contact` ON `contact`.`uid` = `profile`.`uid` - INNER JOIN `user` ON `user`.`uid` = `profile`.`uid` - WHERE `profile`.`is-default` AND `contact`.`self` AND - NOT `user`.`hidewall` AND `contact`.`nurl`='%s'", - dbesc(normalise_link($uri))); - if ($r) { - $location = ''; - if($r[0]['locality']) - $location .= $r[0]['locality']; - if($r[0]['region']) { - if($location) - $location .= ', '; - $location .= $r[0]['region']; - } - if($r[0]['country-name']) { - if($location) - $location .= ', '; - $location .= $r[0]['country-name']; - } - - $o .= "\t".xmlify($r[0]["nick"])."\r\n"; - $o .= "\t".xmlify($r[0]["name"])."\r\n"; - $o .= "\t".xmlify(bbcode($r[0]["about"]))."\r\n"; - $o .= "\t\r\n"; - $o .= "\t\t".xmlify($location)."\r\n"; - $o .= "\t\r\n"; - $o .= "\t\r\n"; - $o .= "\thomepage\r\n"; - $o .= "\t\t".xmlify($r[0]["homepage"])."\r\n"; - $o .= "\t\ttrue\r\n"; - $o .= "\t\r\n"; - } - } - - call_hooks('atom_author', $o); - - $o .= "\r\n"; - return $o; -} - -function atom_entry($item,$type,$author,$owner,$comment = false,$cid = 0) { - - $a = get_app(); - - if(! $item['parent']) - return; - - if($item['deleted']) - return '' . "\r\n"; - - - if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid']) - $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid); - else - $body = $item['body']; - - - $o = "\r\n\r\n\r\n"; - - if(is_array($author)) - $o .= atom_author('author',$author['name'],$author['url'],80,80,$author['thumb'], $item['coord']); - else - $o .= atom_author('author',(($item['author-name']) ? $item['author-name'] : $item['name']),(($item['author-link']) ? $item['author-link'] : $item['url']),80,80,(($item['author-avatar']) ? $item['author-avatar'] : $item['thumb']), $item['coord']); - if(strlen($item['owner-name'])) - $o .= atom_author('dfrn:owner',$item['owner-name'],$item['owner-link'],80,80,$item['owner-avatar'], $item['coord']); - - if(($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) { - $parent = q("SELECT `guid` FROM `item` WHERE `id` = %d", intval($item["parent"])); - $parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']); - $o .= ''."\r\n"; - } - - $htmlbody = $body; - - if ($item['title'] != "") - $htmlbody = "[b]".$item['title']."[/b]\n\n".$htmlbody; - - $htmlbody = bbcode($htmlbody, false, false, 7); - - $o .= '' . xmlify($item['uri']) . '' . "\r\n"; - $o .= '' . xmlify($item['title']) . '' . "\r\n"; - $o .= '' . xmlify(datetime_convert('UTC','UTC',$item['created'] . '+00:00',ATOM_TIME)) . '' . "\r\n"; - $o .= '' . xmlify(datetime_convert('UTC','UTC',$item['edited'] . '+00:00',ATOM_TIME)) . '' . "\r\n"; - $o .= '' . base64url_encode($body, true) . '' . "\r\n"; - $o .= '' . xmlify((($type === 'html') ? $htmlbody : $body)) . '' . "\r\n"; - $o .= ''."\r\n"; - - $o .= ''."\r\n"; - - if($comment) - $o .= '' . intval($item['last-child']) . '' . "\r\n"; - - if($item['location']) { - $o .= '' . xmlify($item['location']) . '' . "\r\n"; - $o .= '' . xmlify($item['location']) . '' . "\r\n"; - } - - if($item['coord']) - $o .= '' . xmlify($item['coord']) . '' . "\r\n"; - - if(($item['private']) || strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid'])) - $o .= '' . (($item['private']) ? $item['private'] : 1) . '' . "\r\n"; - - if($item['extid']) - $o .= '' . xmlify($item['extid']) . '' . "\r\n"; - if($item['bookmark']) - $o .= 'true' . "\r\n"; - - if($item['app']) - $o .= '' . "\r\n"; - - if($item['guid']) - $o .= '' . $item['guid'] . '' . "\r\n"; - - if($item['signed_text']) { - $sign = base64_encode(json_encode(array('signed_text' => $item['signed_text'],'signature' => $item['signature'],'signer' => $item['signer']))); - $o .= '' . xmlify($sign) . '' . "\r\n"; - } - - $verb = construct_verb($item); - $o .= '' . xmlify($verb) . '' . "\r\n"; - $actobj = construct_activity_object($item); - if(strlen($actobj)) - $o .= $actobj; - $actarg = construct_activity_target($item); - if(strlen($actarg)) - $o .= $actarg; - - $tags = item_getfeedtags($item); - if(count($tags)) { - foreach($tags as $t) - if (($type != 'html') OR ($t[0] != "@")) - $o .= '' . "\r\n"; - } - - /// @TODO - /// To support these elements, the API needs to be enhanced - /// $o .= ''."\r\n"; - /// $o .= "\t".''."\r\n"; - /// $o .= "\t".''."\r\n"; - - // Deactivated since it was meant only for OStatus - //$o .= item_get_attachment($item); - - $o .= item_getfeedattach($item); - - $mentioned = get_mentions($item); - if($mentioned) - $o .= $mentioned; - - call_hooks('atom_entry', $o); - - $o .= '' . "\r\n"; - - return $o; -} - function fix_private_photos($s, $uid, $item = null, $cid = 0) { if(get_config('system','disable_embedded')) @@ -4642,7 +1629,6 @@ function fix_private_photos($s, $uid, $item = null, $cid = 0) { return($new_body); } - function has_permissions($obj) { if(($obj['allow_cid'] != '') || ($obj['allow_gid'] != '') || ($obj['deny_cid'] != '') || ($obj['deny_gid'] != '')) return true; @@ -4703,50 +1689,6 @@ function item_getfeedtags($item) { return $ret; } -function item_get_attachment($item) { - $o = ""; - $siteinfo = get_attached_data($item["body"]); - - switch($siteinfo["type"]) { - case 'link': - $o = ''."\r\n"; - break; - case 'photo': - $imgdata = get_photo_info($siteinfo["image"]); - $o = ''."\r\n"; - break; - case 'video': - $o = ''."\r\n"; - break; - default: - break; - } - - return $o; -} - -function item_getfeedattach($item) { - $ret = ''; - $arr = explode('[/attach],',$item['attach']); - if(count($arr)) { - foreach($arr as $r) { - $matches = false; - $cnt = preg_match('|\[attach\]href=\"(.*?)\" length=\"(.*?)\" type=\"(.*?)\" title=\"(.*?)\"|',$r,$matches); - if($cnt) { - $ret .= ' 0)) { + $r = q("SELECT prvkey FROM user WHERE uid = %d LIMIT 1", + intval($contact['uid']) + ); - if( $r) - $authorsig = base64_encode(rsa_sign($signed_text,$r['prvkey'],'sha256')); + if($r) + $authorsig = base64_encode(rsa_sign($signed_text,$r[0]['prvkey'],'sha256')); } if(! isset($authorsig)) @@ -329,6 +338,10 @@ function store_diaspora_like_sig($activity, $post_type, $contact, $post_id) { $contact_baseurl = substr($contact['url'], $contact_baseurl_start, $contact_baseurl_length); $diaspora_handle = $contact['nick'] . '@' . $contact_baseurl; + + // This code could never had worked (the return values form the queries were used in a wrong way. + // Additionally it is needlessly complicated. Either the contact is owner or not. And we have this data already. +/* // Get contact's private key if he's a user of the local Friendica server $r = q("SELECT `contact`.`uid` FROM `contact` WHERE `url` = '%s' AND `self` = 1 LIMIT 1", dbesc($contact['url']) @@ -343,6 +356,17 @@ function store_diaspora_like_sig($activity, $post_type, $contact, $post_id) { if( $r) $contact_uprvkey = $r['prvkey']; } +*/ + + // Is the contact the owner? Then fetch the private key + if ($contact['self'] AND ($contact['uid'] > 0)) { + $r = q("SELECT prvkey FROM user WHERE uid = %d LIMIT 1", + intval($contact['uid']) + ); + + if($r) + $contact_uprvkey = $r[0]['prvkey']; + } $r = q("SELECT guid, parent FROM `item` WHERE id = %d LIMIT 1", intval($post_id) @@ -353,7 +377,7 @@ function store_diaspora_like_sig($activity, $post_type, $contact, $post_id) { intval($r[0]['parent']) ); if( $p) { - $signed_text = $r[0]['guid'] . ';Post;' . $p[0]['guid'] . ';true;' . $diaspora_handle; + $signed_text = 'true;'.$r[0]['guid'].';Post;'.$p[0]['guid'].';'.$diaspora_handle; if(isset($contact_uprvkey)) $authorsig = base64_encode(rsa_sign($signed_text,$contact_uprvkey,'sha256')); diff --git a/include/network.php b/include/network.php index ac5191b25..c6379e407 100644 --- a/include/network.php +++ b/include/network.php @@ -42,6 +42,7 @@ if(!function_exists('z_fetch_url')){ * @return array an assoziative array with: * * \e int \b return_code => HTTP return code or 0 if timeout or failure * * \e boolean \b success => boolean true (if HTTP 2xx result) or false + * * \e string \b redirect_url => in case of redirect, content was finally retrieved from this URL * * \e string \b header => HTTP headers * * \e string \b body => fetched content */ @@ -116,6 +117,9 @@ function z_fetch_url($url,$binary = false, &$redirects = 0, $opts=array()) { // if it throws any errors. $s = @curl_exec($ch); + if (curl_errno($ch) !== CURLE_OK) { + logger('fetch_url error fetching '.$url.': '.curl_error($ch), LOGGER_NORMAL); + } $base = $s; $curl_info = @curl_getinfo($ch); @@ -133,6 +137,10 @@ function z_fetch_url($url,$binary = false, &$redirects = 0, $opts=array()) { $base = substr($base,strlen($chunk)); } + $a->set_curl_code($http_code); + $a->set_curl_content_type($curl_info['content_type']); + $a->set_curl_headers($header); + if($http_code == 301 || $http_code == 302 || $http_code == 303 || $http_code == 307) { $new_location_info = @parse_url($curl_info["redirect_url"]); $old_location_info = @parse_url($curl_info["url"]); @@ -160,13 +168,13 @@ function z_fetch_url($url,$binary = false, &$redirects = 0, $opts=array()) { $a->set_curl_content_type($curl_info['content_type']); $body = substr($s,strlen($header)); - $a->set_curl_headers($header); $rc = intval($http_code); $ret['return_code'] = $rc; $ret['success'] = (($rc >= 200 && $rc <= 299) ? true : false); + $ret['redirect_url'] = $url; if(! $ret['success']) { $ret['error'] = curl_error($ch); $ret['debug'] = $curl_info; @@ -1246,6 +1254,9 @@ function original_url($url, $depth=1, $fetchbody = false) { $a->save_timestamp($stamp1, "network"); + if ($http_code == 0) + return($url); + if ((($curl_info['http_code'] == "301") OR ($curl_info['http_code'] == "302")) AND (($curl_info['redirect_url'] != "") OR ($curl_info['location'] != ""))) { if ($curl_info['redirect_url'] != "") diff --git a/include/notifier.php b/include/notifier.php index 92e48c355..6c42f19c6 100644 --- a/include/notifier.php +++ b/include/notifier.php @@ -535,7 +535,7 @@ function notifier_run(&$argv, &$argc){ if($public_message) { - if (!$followup) + if (!$followup AND $top_level) $r0 = diaspora_fetch_relay(); else $r0 = array(); diff --git a/include/oembed.php b/include/oembed.php index c848a4580..b1770f689 100755 --- a/include/oembed.php +++ b/include/oembed.php @@ -13,7 +13,13 @@ function oembed_fetch_url($embedurl, $no_rich_type = false){ $a = get_app(); - $txt = Cache::get($a->videowidth . $embedurl); + $r = q("SELECT * FROM `oembed` WHERE `url` = '%s'", + dbesc(normalise_link($embedurl))); + + if ($r) + $txt = $r[0]["content"]; + else + $txt = Cache::get($a->videowidth . $embedurl); // These media files should now be caught in bbcode.php // left here as a fallback in case this is called from another source @@ -66,8 +72,14 @@ function oembed_fetch_url($embedurl, $no_rich_type = false){ if ($txt[0]!="{") $txt='{"type":"error"}'; - else //save in cache + else { //save in cache + $j = json_decode($txt); + if ($j->type != "error") + q("INSERT INTO `oembed` (`url`, `content`, `created`) VALUES ('%s', '%s', '%s')", + dbesc(normalise_link($embedurl)), dbesc($txt), dbesc(datetime_convert())); + Cache::set($a->videowidth . $embedurl,$txt, CACHE_DAY); + } } $j = json_decode($txt); @@ -85,7 +97,7 @@ function oembed_fetch_url($embedurl, $no_rich_type = false){ // If fetching information doesn't work, then improve via internal functions if (($j->type == "error") OR ($no_rich_type AND ($j->type == "rich"))) { require_once("mod/parse_url.php"); - $data = parseurl_getsiteinfo($embedurl, true, false); + $data = parseurl_getsiteinfo_cached($embedurl, true, false); $j->type = $data["type"]; if ($j->type == "photo") { diff --git a/include/onepoll.php b/include/onepoll.php index 516f1dfd4..6fb191f73 100644 --- a/include/onepoll.php +++ b/include/onepoll.php @@ -27,7 +27,6 @@ function onepoll_run(&$argv, &$argc){ require_once('include/session.php'); require_once('include/datetime.php'); - require_once('library/simplepie/simplepie.inc'); require_once('include/items.php'); require_once('include/Contact.php'); require_once('include/email.php'); @@ -335,7 +334,9 @@ function onepoll_run(&$argv, &$argc){ if($contact['rel'] == CONTACT_IS_FOLLOWER || $contact['blocked'] || $contact['readonly']) return; - $xml = fetch_url($contact['poll']); + $cookiejar = tempnam(get_temppath(), 'cookiejar-onepoll-'); + $xml = fetch_url($contact['poll'], false, $redirects, 0, Null, $cookiejar); + unlink($cookiejar); } elseif($contact['network'] === NETWORK_MAIL || $contact['network'] === NETWORK_MAIL2) { diff --git a/include/ostatus.php b/include/ostatus.php index 1087596e2..00022f8c6 100644 --- a/include/ostatus.php +++ b/include/ostatus.php @@ -17,15 +17,6 @@ define('OSTATUS_DEFAULT_POLL_INTERVAL', 30); // given in minutes define('OSTATUS_DEFAULT_POLL_TIMEFRAME', 1440); // given in minutes define('OSTATUS_DEFAULT_POLL_TIMEFRAME_MENTIONS', 14400); // given in minutes -define("NS_ATOM", "http://www.w3.org/2005/Atom"); -define("NS_THR", "http://purl.org/syndication/thread/1.0"); -define("NS_GEORSS", "http://www.georss.org/georss"); -define("NS_ACTIVITY", "http://activitystrea.ms/spec/1.0/"); -define("NS_MEDIA", "http://purl.org/syndication/atommedia"); -define("NS_POCO", "http://portablecontacts.net/spec/1.0"); -define("NS_OSTATUS", "http://ostatus.org/schema/1.0"); -define("NS_STATUSNET", "http://status.net/schema/api/1/"); - function ostatus_check_follow_friends() { $r = q("SELECT `uid`,`v` FROM `pconfig` WHERE `cat`='system' AND `k`='ostatus_legacy_contact' AND `v` != ''"); @@ -127,47 +118,57 @@ function ostatus_fetchauthor($xpath, $context, $importer, &$contact, $onlyfetch) $author["owner-link"] = $author["author-link"]; $author["owner-avatar"] = $author["author-avatar"]; - if ($r AND !$onlyfetch) { + // Only update the contacts if it is an OStatus contact + if ($r AND !$onlyfetch AND ($contact["network"] == NETWORK_OSTATUS)) { // Update contact data - $update_contact = ($r[0]['name-date'] < datetime_convert('','','now -12 hours')); - if ($update_contact) { + + $value = $xpath->query("atom:link[@rel='salmon']", $context)->item(0)->nodeValue; + if ($value != "") + $contact["notify"] = $value; + + $value = $xpath->evaluate('atom:author/uri/text()', $context)->item(0)->nodeValue; + if ($value != "") + $contact["alias"] = $value; + + $value = $xpath->evaluate('atom:author/poco:displayName/text()', $context)->item(0)->nodeValue; + if ($value != "") + $contact["name"] = $value; + + $value = $xpath->evaluate('atom:author/poco:preferredUsername/text()', $context)->item(0)->nodeValue; + if ($value != "") + $contact["nick"] = $value; + + $value = $xpath->evaluate('atom:author/poco:note/text()', $context)->item(0)->nodeValue; + if ($value != "") + $contact["about"] = html2bbcode($value); + + $value = $xpath->evaluate('atom:author/poco:address/poco:formatted/text()', $context)->item(0)->nodeValue; + if ($value != "") + $contact["location"] = $value; + + if (($contact["name"] != $r[0]["name"]) OR ($contact["nick"] != $r[0]["nick"]) OR ($contact["about"] != $r[0]["about"]) OR ($contact["location"] != $r[0]["location"])) { + logger("Update contact data for contact ".$contact["id"], LOGGER_DEBUG); - $value = $xpath->evaluate('atom:author/poco:displayName/text()', $context)->item(0)->nodeValue; - if ($value != "") - $contact["name"] = $value; - - $value = $xpath->evaluate('atom:author/poco:preferredUsername/text()', $context)->item(0)->nodeValue; - if ($value != "") - $contact["nick"] = $value; - - $value = $xpath->evaluate('atom:author/poco:note/text()', $context)->item(0)->nodeValue; - if ($value != "") - $contact["about"] = html2bbcode($value); - - $value = $xpath->evaluate('atom:author/poco:address/poco:formatted/text()', $context)->item(0)->nodeValue; - if ($value != "") - $contact["location"] = $value; - - q("UPDATE `contact` SET `name` = '%s', `nick` = '%s', `about` = '%s', `location` = '%s', `name-date` = '%s' WHERE `id` = %d AND `network` = '%s'", + q("UPDATE `contact` SET `name` = '%s', `nick` = '%s', `about` = '%s', `location` = '%s', `name-date` = '%s' WHERE `id` = %d", dbesc($contact["name"]), dbesc($contact["nick"]), dbesc($contact["about"]), dbesc($contact["location"]), - dbesc(datetime_convert()), intval($contact["id"]), dbesc(NETWORK_OSTATUS)); + dbesc(datetime_convert()), intval($contact["id"])); poco_check($contact["url"], $contact["name"], $contact["network"], $author["author-avatar"], $contact["about"], $contact["location"], - "", "", "", datetime_convert(), 2, $contact["id"], $contact["uid"]); + "", "", "", datetime_convert(), 2, $contact["id"], $contact["uid"]); } - $update_photo = ($r[0]['avatar-date'] < datetime_convert('','','now -12 hours')); - - if ($update_photo AND isset($author["author-avatar"])) { + if (isset($author["author-avatar"]) AND ($author["author-avatar"] != $r[0]['avatar'])) { logger("Update profile picture for contact ".$contact["id"], LOGGER_DEBUG); - $photos = import_profile_photo($author["author-avatar"], $importer["uid"], $contact["id"]); - - q("UPDATE `contact` SET `photo` = '%s', `thumb` = '%s', `micro` = '%s', `avatar-date` = '%s' WHERE `id` = %d AND `network` = '%s'", - dbesc($photos[0]), dbesc($photos[1]), dbesc($photos[2]), - dbesc(datetime_convert()), intval($contact["id"]), dbesc(NETWORK_OSTATUS)); + update_contact_avatar($author["author-avatar"], $importer["uid"], $contact["id"]); } + + + /// @todo Add the "addr" field + $contact["generation"] = 2; + $contact["photo"] = $author["author-avatar"]; + update_gcontact($contact); } return($author); @@ -183,14 +184,14 @@ function ostatus_salmon_author($xml, $importer) { @$doc->loadXML($xml); $xpath = new DomXPath($doc); - $xpath->registerNamespace('atom', "http://www.w3.org/2005/Atom"); - $xpath->registerNamespace('thr', "http://purl.org/syndication/thread/1.0"); - $xpath->registerNamespace('georss', "http://www.georss.org/georss"); - $xpath->registerNamespace('activity', "http://activitystrea.ms/spec/1.0/"); - $xpath->registerNamespace('media', "http://purl.org/syndication/atommedia"); - $xpath->registerNamespace('poco', "http://portablecontacts.net/spec/1.0"); - $xpath->registerNamespace('ostatus', "http://ostatus.org/schema/1.0"); - $xpath->registerNamespace('statusnet', "http://status.net/schema/api/1/"); + $xpath->registerNamespace('atom', NAMESPACE_ATOM1); + $xpath->registerNamespace('thr', NAMESPACE_THREAD); + $xpath->registerNamespace('georss', NAMESPACE_GEORSS); + $xpath->registerNamespace('activity', NAMESPACE_ACTIVITY); + $xpath->registerNamespace('media', NAMESPACE_MEDIA); + $xpath->registerNamespace('poco', NAMESPACE_POCO); + $xpath->registerNamespace('ostatus', NAMESPACE_OSTATUS); + $xpath->registerNamespace('statusnet', NAMESPACE_STATUSNET); $entries = $xpath->query('/atom:entry'); @@ -214,14 +215,14 @@ function ostatus_import($xml,$importer,&$contact, &$hub) { @$doc->loadXML($xml); $xpath = new DomXPath($doc); - $xpath->registerNamespace('atom', "http://www.w3.org/2005/Atom"); - $xpath->registerNamespace('thr', "http://purl.org/syndication/thread/1.0"); - $xpath->registerNamespace('georss', "http://www.georss.org/georss"); - $xpath->registerNamespace('activity', "http://activitystrea.ms/spec/1.0/"); - $xpath->registerNamespace('media', "http://purl.org/syndication/atommedia"); - $xpath->registerNamespace('poco', "http://portablecontacts.net/spec/1.0"); - $xpath->registerNamespace('ostatus', "http://ostatus.org/schema/1.0"); - $xpath->registerNamespace('statusnet', "http://status.net/schema/api/1/"); + $xpath->registerNamespace('atom', NAMESPACE_ATOM1); + $xpath->registerNamespace('thr', NAMESPACE_THREAD); + $xpath->registerNamespace('georss', NAMESPACE_GEORSS); + $xpath->registerNamespace('activity', NAMESPACE_ACTIVITY); + $xpath->registerNamespace('media', NAMESPACE_MEDIA); + $xpath->registerNamespace('poco', NAMESPACE_POCO); + $xpath->registerNamespace('ostatus', NAMESPACE_OSTATUS); + $xpath->registerNamespace('statusnet', NAMESPACE_STATUSNET); $gub = ""; $hub_attributes = $xpath->query("/atom:feed/atom:link[@rel='hub']")->item(0)->attributes; @@ -546,29 +547,6 @@ function ostatus_import($xml,$importer,&$contact, &$hub) { } logger("Item was stored with id ".$item_id, LOGGER_DEBUG); - $item["id"] = $item_id; - - if ($mention) { - $u = q("SELECT `notify-flags`, `language`, `username`, `email` FROM user WHERE uid = %d LIMIT 1", intval($item['uid'])); - $r = q("SELECT `parent` FROM `item` WHERE `id` = %d", intval($item_id)); - - notification(array( - 'type' => NOTIFY_TAGSELF, - 'notify_flags' => $u[0]["notify-flags"], - 'language' => $u[0]["language"], - 'to_name' => $u[0]["username"], - 'to_email' => $u[0]["email"], - 'uid' => $item["uid"], - 'item' => $item, - 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($item_id)), - 'source_name' => $item["author-name"], - 'source_link' => $item["author-link"], - 'source_photo' => $item["author-avatar"], - 'verb' => ACTIVITY_TAG, - 'otype' => 'item', - 'parent' => $r[0]["parent"] - )); - } } } @@ -1013,28 +991,6 @@ function ostatus_completion($conversation_url, $uid, $item = array()) { // Add the conversation entry (but don't fetch the whole conversation) ostatus_store_conversation($newitem, $conversation_url); - if ($mention) { - $u = q("SELECT `notify-flags`, `language`, `username`, `email` FROM user WHERE uid = %d LIMIT 1", intval($uid)); - $r = q("SELECT `parent` FROM `item` WHERE `id` = %d", intval($newitem)); - - notification(array( - 'type' => NOTIFY_TAGSELF, - 'notify_flags' => $u[0]["notify-flags"], - 'language' => $u[0]["language"], - 'to_name' => $u[0]["username"], - 'to_email' => $u[0]["email"], - 'uid' => $uid, - 'item' => $arr, - 'link' => $a->get_baseurl().'/display/'.urlencode(get_item_guid($newitem)), - 'source_name' => $arr["author-name"], - 'source_link' => $arr["author-link"], - 'source_photo' => $arr["author-avatar"], - 'verb' => ACTIVITY_TAG, - 'otype' => 'item', - 'parent' => $r[0]["parent"] - )); - } - // If the newly created item is the top item then change the parent settings of the thread // This shouldn't happen anymore. This is supposed to be absolote. if ($arr["uri"] == $first_id) { @@ -1109,7 +1065,7 @@ function get_reshared_guid($item) { return $guid; } -function xml_add_element($doc, $parent, $element, $value = "", $attributes = array()) { +function xml_create_element($doc, $element, $value = "", $attributes = array()) { $element = $doc->createElement($element, xmlify($value)); foreach ($attributes AS $key => $value) { @@ -1117,7 +1073,11 @@ function xml_add_element($doc, $parent, $element, $value = "", $attributes = arr $attribute->value = xmlify($value); $element->appendChild($attribute); } + return $element; +} +function xml_add_element($doc, $parent, $element, $value = "", $attributes = array()) { + $element = xml_create_element($doc, $element, $value, $attributes); $parent->appendChild($element); } @@ -1151,16 +1111,16 @@ function ostatus_format_picture_post($body) { function ostatus_add_header($doc, $owner) { $a = get_app(); - $root = $doc->createElementNS(NS_ATOM, 'feed'); + $root = $doc->createElementNS(NAMESPACE_ATOM1, 'feed'); $doc->appendChild($root); - $root->setAttribute("xmlns:thr", NS_THR); - $root->setAttribute("xmlns:georss", NS_GEORSS); - $root->setAttribute("xmlns:activity", NS_ACTIVITY); - $root->setAttribute("xmlns:media", NS_MEDIA); - $root->setAttribute("xmlns:poco", NS_POCO); - $root->setAttribute("xmlns:ostatus", NS_OSTATUS); - $root->setAttribute("xmlns:statusnet", NS_STATUSNET); + $root->setAttribute("xmlns:thr", NAMESPACE_THREAD); + $root->setAttribute("xmlns:georss", NAMESPACE_GEORSS); + $root->setAttribute("xmlns:activity", NAMESPACE_ACTIVITY); + $root->setAttribute("xmlns:media", NAMESPACE_MEDIA); + $root->setAttribute("xmlns:poco", NAMESPACE_POCO); + $root->setAttribute("xmlns:ostatus", NAMESPACE_OSTATUS); + $root->setAttribute("xmlns:statusnet", NAMESPACE_STATUSNET); $attributes = array("uri" => "https://friendi.ca", "version" => FRIENDICA_VERSION."-".DB_UPDATE_VERSION); xml_add_element($doc, $root, "generator", FRIENDICA_PLATFORM, $attributes); @@ -1352,6 +1312,10 @@ function ostatus_add_author($doc, $owner) { function ostatus_entry($doc, $item, $owner, $toplevel = false, $repeat = false) { $a = get_app(); + if (($item["id"] != $item["parent"]) AND (normalise_link($item["author-link"]) != normalise_link($owner["url"]))) { + logger("OStatus entry is from author ".$owner["url"]." - not from ".$item["author-link"].". Quitting.", LOGGER_DEBUG); + } + $is_repeat = false; /* if (!$repeat) { @@ -1374,15 +1338,15 @@ function ostatus_entry($doc, $item, $owner, $toplevel = false, $repeat = false) $entry = $doc->createElement("activity:object"); $title = sprintf("New note by %s", $owner["nick"]); } else { - $entry = $doc->createElementNS(NS_ATOM, "entry"); + $entry = $doc->createElementNS(NAMESPACE_ATOM1, "entry"); - $entry->setAttribute("xmlns:thr", NS_THR); - $entry->setAttribute("xmlns:georss", NS_GEORSS); - $entry->setAttribute("xmlns:activity", NS_ACTIVITY); - $entry->setAttribute("xmlns:media", NS_MEDIA); - $entry->setAttribute("xmlns:poco", NS_POCO); - $entry->setAttribute("xmlns:ostatus", NS_OSTATUS); - $entry->setAttribute("xmlns:statusnet", NS_STATUSNET); + $entry->setAttribute("xmlns:thr", NAMESPACE_THREAD); + $entry->setAttribute("xmlns:georss", NAMESPACE_GEORSS); + $entry->setAttribute("xmlns:activity", NAMESPACE_ACTIVITY); + $entry->setAttribute("xmlns:media", NAMESPACE_MEDIA); + $entry->setAttribute("xmlns:poco", NAMESPACE_POCO); + $entry->setAttribute("xmlns:ostatus", NAMESPACE_OSTATUS); + $entry->setAttribute("xmlns:statusnet", NAMESPACE_STATUSNET); $author = ostatus_add_author($doc, $owner); $entry->appendChild($author); @@ -1447,9 +1411,12 @@ function ostatus_entry($doc, $item, $owner, $toplevel = false, $repeat = false) $repeated_owner["about"] = ""; $repeated_owner["uid"] = 0; - $r =q("SELECT * FROM `unique_contacts` WHERE `url` = '%s'", normalise_link($repeated_item["author-link"])); + // Fetch the missing data from the global contacts + $r =q("SELECT * FROM `gcontact` WHERE `nurl` = '%s'", normalise_link($repeated_item["author-link"])); if ($r) { - $repeated_owner["nick"] = $r[0]["nick"]; + if ($r[0]["nick"] != "") + $repeated_owner["nick"] = $r[0]["nick"]; + $repeated_owner["location"] = $r[0]["location"]; $repeated_owner["about"] = $r[0]["about"]; } diff --git a/include/plaintext.php b/include/plaintext.php index cb56850d4..05431bee2 100644 --- a/include/plaintext.php +++ b/include/plaintext.php @@ -90,6 +90,14 @@ function get_attached_data($body) { $post["text"] = $body; } } + + if (preg_match_all("(\[url\]([$URLSearchString]*)\[\/url\])ism", $body, $links, PREG_SET_ORDER)) { + if (count($links) == 1) { + $post["type"] = "text"; + $post["url"] = $links[0][1]; + $post["text"] = $body; + } + } if (!isset($post["type"])) { $post["type"] = "text"; $post["text"] = trim($body); @@ -129,9 +137,17 @@ function plaintext($a, $b, $limit = 0, $includedlinks = false, $htmlmode = 2) { require_once("include/html2plain.php"); require_once("include/network.php"); + // 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); + // At first look at data that is attached via "type-..." stuff // This will hopefully replaced with a dedicated bbcode later - $post = get_attached_data($b["body"]); + //$post = get_attached_data($b["body"]); + $post = get_attached_data($body); if (($b["title"] != "") AND ($post["text"] != "")) $post["text"] = trim($b["title"]."\n\n".$post["text"]); @@ -146,6 +162,8 @@ function plaintext($a, $b, $limit = 0, $includedlinks = false, $htmlmode = 2) { 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") @@ -161,8 +179,22 @@ function plaintext($a, $b, $limit = 0, $includedlinks = false, $htmlmode = 2) { // But: if the link is beyond the limit, then it has to be added. if (($link != "") AND strstr($msg, $link)) { $pos = strpos($msg, $link); - if (($limit == 0) OR ($pos < $limit)) + + // Will the text be shortened in the link? + // Or is the link the last item in the post? + if (($limit > 0) AND ($pos < $limit) AND (($pos + 23 > $limit) OR ($pos + strlen($link) == strlen($msg)))) + $msg = trim(str_replace($link, "", $msg)); + elseif (($limit == 0) OR ($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) AND (strlen($link) > 23) AND ($htmlmode == 8)) + $limit = $limit - 23 + strlen($link); + $link = ""; + + if ($post["type"] == "text") + unset($post["url"]); + } } } @@ -178,7 +210,9 @@ function plaintext($a, $b, $limit = 0, $includedlinks = false, $htmlmode = 2) { if (iconv_strlen($msg, "UTF-8") > $limit) { - if (!isset($post["url"])) { + if (($post["type"] == "text") AND isset($post["url"])) + $post["url"] = $b["plink"]; + elseif (!isset($post["url"])) { $limit = $limit - 23; $post["url"] = $b["plink"]; } elseif (strpos($b["body"], "[share") !== false) diff --git a/include/poller.php b/include/poller.php index c681bfb38..90e94ede9 100644 --- a/include/poller.php +++ b/include/poller.php @@ -26,6 +26,9 @@ function poller_run(&$argv, &$argc){ unset($db_host, $db_user, $db_pass, $db_data); }; + if (poller_max_connections_reached()) + return; + $load = current_load(); if($load) { $maxsysload = intval(get_config('system','maxloadavg')); @@ -39,8 +42,10 @@ function poller_run(&$argv, &$argc){ } // Checking the number of workers - if (poller_too_much_workers(1)) + if (poller_too_much_workers(1)) { + poller_kill_stale_workers(); return; + } if(($argc <= 1) OR ($argv[1] != "no_cron")) { // Run the cron job that calls all other jobs @@ -50,16 +55,7 @@ function poller_run(&$argv, &$argc){ proc_run("php","include/cronhooks.php"); // Cleaning dead processes - $r = q("SELECT DISTINCT(`pid`) FROM `workerqueue` WHERE `executed` != '0000-00-00 00:00:00'"); - foreach($r AS $pid) - if (!posix_kill($pid["pid"], 0)) - q("UPDATE `workerqueue` SET `executed` = '0000-00-00 00:00:00', `pid` = 0 WHERE `pid` = %d", - intval($pid["pid"])); - else { - /// @TODO Kill long running processes - /// But: Update processes (like the database update) mustn't be killed - } - + poller_kill_stale_workers(); } else // Sleep four seconds before checking for running processes again to avoid having too many workers sleep(4); @@ -72,6 +68,10 @@ function poller_run(&$argv, &$argc){ while ($r = q("SELECT * FROM `workerqueue` WHERE `executed` = '0000-00-00 00:00:00' ORDER BY `created` LIMIT 1")) { + // Constantly check the number of available database connections to let the frontend be accessible at any time + if (poller_max_connections_reached()) + return; + // Count active workers and compare them with a maximum value that depends on the load if (poller_too_much_workers(3)) return; @@ -124,6 +124,107 @@ function poller_run(&$argv, &$argc){ } +/** + * @brief Checks if the number of database connections has reached a critical limit. + * + * @return bool Are more than 3/4 of the maximum connections used? + */ +function poller_max_connections_reached() { + + // Fetch the max value from the config. This is needed when the system cannot detect the correct value by itself. + $max = get_config("system", "max_connections"); + + if ($max == 0) { + // the maximum number of possible user connections can be a system variable + $r = q("SHOW VARIABLES WHERE `variable_name` = 'max_user_connections'"); + if ($r) + $max = $r[0]["Value"]; + + // Or it can be granted. This overrides the system variable + $r = q("SHOW GRANTS"); + if ($r) + foreach ($r AS $grants) { + $grant = array_pop($grants); + if (stristr($grant, "GRANT USAGE ON")) + if (preg_match("/WITH MAX_USER_CONNECTIONS (\d*)/", $grant, $match)) + $max = $match[1]; + } + } + + // If $max is set we will use the processlist to determine the current number of connections + // The processlist only shows entries of the current user + if ($max != 0) { + $r = q("SHOW PROCESSLIST"); + if (!$r) + return false; + + $used = count($r); + + logger("Connection usage (user values): ".$used."/".$max, LOGGER_DEBUG); + + $level = $used / $max; + + if ($level >= (3/4)) { + logger("Maximum level (3/4) of user connections reached: ".$used."/".$max); + return true; + } + } + + // We will now check for the system values. + // This limit could be reached although the user limits are fine. + $r = q("SHOW VARIABLES WHERE `variable_name` = 'max_connections'"); + if (!$r) + return false; + + $max = intval($r[0]["Value"]); + if ($max == 0) + return false; + + $r = q("SHOW STATUS WHERE `variable_name` = 'Threads_connected'"); + if (!$r) + return false; + + $used = intval($r[0]["Value"]); + if ($used == 0) + return false; + + logger("Connection usage (system values): ".$used."/".$max, LOGGER_DEBUG); + + $level = $used / $max; + + if ($level < (3/4)) + return false; + + logger("Maximum level (3/4) of system connections reached: ".$used."/".$max); + return true; +} + +/** + * @brief fix the queue entry if the worker process died + * + */ +function poller_kill_stale_workers() { + $r = q("SELECT `pid`, `executed` FROM `workerqueue` WHERE `executed` != '0000-00-00 00:00:00'"); + foreach($r AS $pid) + if (!posix_kill($pid["pid"], 0)) + q("UPDATE `workerqueue` SET `executed` = '0000-00-00 00:00:00', `pid` = 0 WHERE `pid` = %d", + intval($pid["pid"])); + else { + // Kill long running processes + $duration = (time() - strtotime($pid["executed"])) / 60; + if ($duration > 180) { + logger("Worker process ".$pid["pid"]." took more than 3 hours. It will be killed now."); + posix_kill($pid["pid"], SIGTERM); + + // Question: If a process is stale: Should we remove it or should we reschedule it? + // By now we rescheduling it. It's maybe not the wisest decision? + q("UPDATE `workerqueue` SET `executed` = '0000-00-00 00:00:00', `pid` = 0 WHERE `pid` = %d", + intval($pid["pid"])); + } else + logger("Worker process ".$pid["pid"]." now runs for ".round($duration)." minutes. That's okay.", LOGGER_DEBUG); + } +} + function poller_too_much_workers($stage) { $queues = get_config("system", "worker_queues"); diff --git a/include/profile_update.php b/include/profile_update.php index 0fcf3617f..7cc72cc86 100644 --- a/include/profile_update.php +++ b/include/profile_update.php @@ -3,6 +3,7 @@ require_once('include/datetime.php'); require_once('include/diaspora.php'); require_once('include/queue_fn.php'); +require_once('include/Contact.php'); function profile_change() { @@ -53,19 +54,7 @@ function profile_change() { $about = xmlify($profile['about']); require_once('include/bbcode.php'); $about = xmlify(strip_tags(bbcode($about))); - $location = ''; - if($profile['locality']) - $location .= $profile['locality']; - if($profile['region']) { - if($location) - $location .= ', '; - $location .= $profile['region']; - } - if($profile['country-name']) { - if($location) - $location .= ', '; - $location .= $profile['country-name']; - } + $location = formatted_location($profile); $location = xmlify($location); $tags = ''; if($profile['pub_keywords']) { diff --git a/include/queue.php b/include/queue.php index cb5fe28ad..1525ca3ab 100644 --- a/include/queue.php +++ b/include/queue.php @@ -1,6 +1,7 @@ UTC_TIMESTAMP() - INTERVAL 12 HOUR && `last` < UTC_TIMESTAMP() - INTERVAL 15 MINUTE ) OR ( `last` < UTC_TIMESTAMP() - INTERVAL 1 HOUR ))"); + $r = q("SELECT `id` FROM `queue` WHERE ((`created` > UTC_TIMESTAMP() - INTERVAL 12 HOUR && `last` < UTC_TIMESTAMP() - INTERVAL 15 MINUTE) OR (`last` < UTC_TIMESTAMP() - INTERVAL 1 HOUR)) ORDER BY `cid`, `created`"); } if(! $r){ return; @@ -116,7 +119,7 @@ function queue_run(&$argv, &$argc){ // so check again if this entry still needs processing if($queue_id) { - $qi = q("select * from queue where `id` = %d limit 1", + $qi = q("SELECT * FROM `queue` WHERE `id` = %d LIMIT 1", intval($queue_id) ); } @@ -142,8 +145,18 @@ function queue_run(&$argv, &$argc){ continue; } - if (!poco_reachable($c[0]['url'])) { - logger('queue: skipping probably dead url: ' . $c[0]['url']); + $server = poco_detect_server($c[0]['url']); + + if (($server != "") AND !in_array($server, $serverlist)) { + logger("Check server ".$server." (".$c[0]["network"].")"); + if (!poco_check_server($server, $c[0]["network"], true)) + $deadservers[] = $server; + + $serverlist[] = $server; + } + + if (($server != "") AND in_array($server, $deadservers)) { + logger('queue: skipping known dead server: '.$server); update_queue_time($q_item['id']); continue; } @@ -166,37 +179,39 @@ function queue_run(&$argv, &$argc){ switch($contact['network']) { case NETWORK_DFRN: - logger('queue: dfrndelivery: item ' . $q_item['id'] . ' for ' . $contact['name']); - $deliver_status = dfrn_deliver($owner,$contact,$data); + logger('queue: dfrndelivery: item '.$q_item['id'].' for '.$contact['name'].' <'.$contact['url'].'>'); + $deliver_status = dfrn::deliver($owner,$contact,$data); if($deliver_status == (-1)) { update_queue_time($q_item['id']); $deadguys[] = $contact['notify']; - } - else { + } else remove_queue_item($q_item['id']); - } + break; case NETWORK_OSTATUS: if($contact['notify']) { - logger('queue: slapdelivery: item ' . $q_item['id'] . ' for ' . $contact['name']); + logger('queue: slapdelivery: item '.$q_item['id'].' for '.$contact['name'].' <'.$contact['url'].'>'); $deliver_status = slapper($owner,$contact['notify'],$data); - if($deliver_status == (-1)) + if($deliver_status == (-1)) { update_queue_time($q_item['id']); - else + $deadguys[] = $contact['notify']; + } else remove_queue_item($q_item['id']); } break; case NETWORK_DIASPORA: if($contact['notify']) { - logger('queue: diaspora_delivery: item ' . $q_item['id'] . ' for ' . $contact['name']); + logger('queue: diaspora_delivery: item '.$q_item['id'].' for '.$contact['name'].' <'.$contact['url'].'>'); $deliver_status = diaspora_transmit($owner,$contact,$data,$public,true); - if($deliver_status == (-1)) + if($deliver_status == (-1)) { update_queue_time($q_item['id']); - else + $deadguys[] = $contact['notify']; + } else remove_queue_item($q_item['id']); + } break; @@ -212,6 +227,7 @@ function queue_run(&$argv, &$argc){ break; } + logger('Deliver status '.$deliver_status.' for item '.$q_item['id'].' to '.$contact['name'].' <'.$contact['url'].'>'); } return; diff --git a/include/remoteupdate.php b/include/remoteupdate.php deleted file mode 100644 index 9effc9b6e..000000000 --- a/include/remoteupdate.php +++ /dev/null @@ -1,261 +0,0 @@ -tags as $i=>$v){ - $i = (float)$i; - if ($i>$tag) $tag=$i; - } - - if ($tag==0.0) return false; - $f = fetch_url("https://raw.github.com/".F9KREPO."/".$tag."/boot.php","r"); - preg_match("|'FRIENDICA_VERSION', *'([^']*)'|", $f, $m); - $version = $m[1]; - - $lv = explode(".", FRIENDICA_VERSION); - $rv = explode(".",$version); - foreach($lv as $i=>$v){ - if ((int)$lv[$i] < (int)$rv[$i]) { - return array($tag, $version, "https://github.com/friendica/friendica/zipball/".$tag); - break; - } - } - return false; -} -function canWeWrite(){ - $bd = dirname(dirname(__file__)); - return is_writable( $bd."/boot.php" ); -} - -function out($txt){ echo "§".$txt."§"; ob_end_flush(); flush();} - -function up_count($path){ - - $file_count = 0; - - $dir_handle = opendir($path); - - if (!$dir_handle) return -1; - - while ($file = readdir($dir_handle)) { - - if ($file == '.' || $file == '..') continue; - $file_count++; - - if (is_dir($path . $file)){ - $file_count += up_count($path . $file . DIRECTORY_SEPARATOR); - } - - } - - closedir($dir_handle); - - return $file_count; -} - - - -function up_unzip($file, $folder="/tmp"){ - $folder.="/"; - $zip = zip_open($file); - if ($zip) { - while ($zip_entry = zip_read($zip)) { - $zip_entry_name = zip_entry_name($zip_entry); - if (substr($zip_entry_name,strlen($zip_entry_name)-1,1)=="/"){ - mkdir($folder.$zip_entry_name,0777, true); - } else { - $fp = fopen($folder.$zip_entry_name, "w"); - if (zip_entry_open($zip, $zip_entry, "r")) { - $buf = zip_entry_read($zip_entry, zip_entry_filesize($zip_entry)); - fwrite($fp,"$buf"); - zip_entry_close($zip_entry); - fclose($fp); - } - } - } - zip_close($zip); - } -} - -/** - * Walk recoursively in a folder and call a callback function on every - * dir entry. - * args: - * $dir string base dir to walk - * $callback function callback function - * $sort int 0: ascending, 1: descending - * $cb_argv any extra value passed to callback - * - * callback signature: - * function name($fn, $dir [, $argv]) - * $fn string full dir entry name - * $dir string start dir path - * $argv any user value to callback - * - */ -function up_walktree($dir, $callback=Null, $sort=0, $cb_argv=Null , $startdir=Null){ - if (is_null($callback)) return; - if (is_null($startdir)) $startdir = $dir; - $res = scandir($dir, $sort); - foreach($res as $i=>$v){ - if ($v!="." && $v!=".."){ - $fn = $dir."/".$v; - if ($sort==0) $callback($fn, $startdir, $cb_argv); - if (is_dir($fn)) up_walktree($fn, $callback, $sort, $cb_argv, $startdir); - if ($sort==1) $callback($fn, $startdir, $cb_argv); - } - } - -} - -function up_copy($fn, $dir){ - global $up_countfiles, $up_totalfiles, $up_lastp; - $up_countfiles++; $prc=(int)(((float)$up_countfiles/(float)$up_totalfiles)*100); - - if (strpos($fn, ".gitignore")>-1 || strpos($fn, ".htaccess")>-1) return; - $ddest = dirname(dirname(__file__)); - $fd = str_replace($dir, $ddest, $fn); - - if (is_dir($fn) && !is_dir($fd)) { - $re=mkdir($fd,0777,true); - } - if (!is_dir($fn)){ - $re=copy($fn, $fd); - } - - if ($re===false) { - out("ERROR. Abort."); - killme(); - } - out("copy@Copy@$prc%"); -} - -function up_ftp($fn, $dir, $argv){ - global $up_countfiles, $up_totalfiles, $up_lastp; - $up_countfiles++; $prc=(int)(((float)$up_countfiles/(float)$up_totalfiles)*100); - - if (strpos($fn, ".gitignore")>-1 || strpos($fn, ".htaccess")>-1) return; - - list($ddest, $conn_id) = $argv; - $l = strlen($ddest)-1; - if (substr($ddest,$l,1)=="/") $ddest = substr($ddest,0,$l); - $fd = str_replace($dir, $ddest, $fn); - - if (is_dir($fn)){ - if (ftp_nlist($conn_id, $fd)===false) { - $ret = ftp_mkdir($conn_id, $fd); - } else { - $ret=true; - } - } else { - $ret = ftp_put($conn_id, $fd, $fn, FTP_BINARY); - } - if (!$ret) { - out("ERROR. Abort."); - killme(); - } - out("copy@Copy@$prc%"); -} - -function up_rm($fn, $dir){ - if (is_dir($fn)){ - rmdir($fn); - } else { - unlink($fn); - } -} - -function up_dlfile($url, $file) { - $in = fopen ($url, "r"); - $out = fopen ($file, "w"); - - $fs = filesize($url); - - - if (!$in || !$out) return false; - - $s=0; $count=0; - while (!feof ($in)) { - $line = fgets ($in, 1024); - fwrite( $out, $line); - - $count++; $s += strlen($line); - if ($count==50){ - $count=0; - $sp=$s/1024.0; $ex="Kb"; - if ($sp>1024) { $sp=$sp/1024; $ex="Mb"; } - if ($sp>1024) { $sp=$sp/1024; $ex="Gb"; } - $sp = ((int)($sp*100))/100; - out("dwl@Download@".$sp.$ex); - } - } - fclose($in); - return true; -} - -function doUpdate($remotefile, $ftpdata=false){ - global $up_totalfiles; - - - $localtmpfile = tempnam("/tmp", "fk"); - out("dwl@Download@starting..."); - $rt= up_dlfile($remotefile, $localtmpfile); - if ($rt==false || filesize($localtmpfile)==0){ - out("dwl@Download@ERROR."); - unlink($localtmpfile); - return; - } - out("dwl@Download@Ok."); - - out("unzip@Unzip@"); - $tmpdirname = $localfile."ex"; - mkdir($tmpdirname); - up_unzip($localtmpfile, $tmpdirname); - $basedir = glob($tmpdirname."/*"); $basedir=$basedir[0]; - out ("unzip@Unzip@Ok."); - - $up_totalfiles = up_count($basedir."/"); - - if (canWeWrite()){ - out("copy@Copy@"); - up_walktree($basedir, 'up_copy'); - } - if ($ftpdata!==false && is_array($ftpdata) && $ftpdata['ftphost']!="" ){ - out("ftpcon@Connect to FTP@"); - $conn_id = ftp_connect($ftpdata['ftphost']); - $login_result = ftp_login($conn_id, $ftpdata['ftpuser'], $ftpdata['ftppwd']); - - if ((!$conn_id) || (!$login_result)) { - out("ftpcon@Connect to FTP@FAILED"); - up_clean($tmpdirname, $localtmpfile); - return; - } else { - out("ftpcon@Connect to FTP@Ok."); - } - out("copy@Copy@"); - up_walktree($basedir, 'up_ftp', 0, array( $ftpdata['ftppath'], $conn_id)); - - ftp_close($conn_id); - } - - up_clean($tmpdirname, $localtmpfile); - -} - -function up_clean($tmpdirname, $localtmpfile){ - out("clean@Clean up@"); - unlink($localtmpfile); - up_walktree($tmpdirname, 'up_rm', 1); - rmdir($tmpdirname); - out("clean@Clean up@Ok."); -} diff --git a/include/socgraph.php b/include/socgraph.php index 89b09515e..3b8e9140f 100644 --- a/include/socgraph.php +++ b/include/socgraph.php @@ -139,15 +139,16 @@ function poco_load($cid,$uid = 0,$zcid = 0,$url = null) { poco_check($profile_url, $name, $network, $profile_photo, $about, $location, $gender, $keywords, $connect_url, $updated, $generation, $cid, $uid, $zcid); // Update the Friendica contacts. Diaspora is doing it via a message. (See include/diaspora.php) - if (($location != "") OR ($about != "") OR ($keywords != "") OR ($gender != "")) - q("UPDATE `contact` SET `location` = '%s', `about` = '%s', `keywords` = '%s', `gender` = '%s' - WHERE `nurl` = '%s' AND NOT `self` AND `network` = '%s'", - dbesc($location), - dbesc($about), - dbesc($keywords), - dbesc($gender), - dbesc(normalise_link($profile_url)), - dbesc(NETWORK_DFRN)); + // Deactivated because we now update Friendica contacts in dfrn.php + //if (($location != "") OR ($about != "") OR ($keywords != "") OR ($gender != "")) + // q("UPDATE `contact` SET `location` = '%s', `about` = '%s', `keywords` = '%s', `gender` = '%s' + // WHERE `nurl` = '%s' AND NOT `self` AND `network` = '%s'", + // dbesc($location), + // dbesc($about), + // dbesc($keywords), + // dbesc($gender), + // dbesc(normalise_link($profile_url)), + // dbesc(NETWORK_DFRN)); } logger("poco_load: loaded $total entries",LOGGER_DEBUG); @@ -227,6 +228,8 @@ function poco_check($profile_url, $name, $network, $profile_photo, $about, $loca $server_url = $x[0]["server_url"]; $nick = $x[0]["nick"]; $addr = $x[0]["addr"]; + $alias = $x[0]["alias"]; + $notify = $x[0]["notify"]; } else { $created = "0000-00-00 00:00:00"; $server_url = ""; @@ -234,6 +237,8 @@ function poco_check($profile_url, $name, $network, $profile_photo, $about, $loca $urlparts = parse_url($profile_url); $nick = end(explode("/", $urlparts["path"])); $addr = ""; + $alias = ""; + $notify = ""; } if ((($network == "") OR ($name == "") OR ($addr == "") OR ($profile_photo == "") OR ($server_url == "") OR $alternate) @@ -246,6 +251,8 @@ function poco_check($profile_url, $name, $network, $profile_photo, $about, $loca $name = $data["name"]; $nick = $data["nick"]; $addr = $data["addr"]; + $alias = $data["alias"]; + $notify = $data["notify"]; $profile_url = $data["url"]; $profile_photo = $data["photo"]; $server_url = $data["baseurl"]; @@ -283,76 +290,25 @@ function poco_check($profile_url, $name, $network, $profile_photo, $about, $loca poco_check_server($server_url, $network); - if(count($x)) { - $gcid = $x[0]['id']; + $gcontact = array("url" => $profile_url, + "addr" => $addr, + "alias" => $alias, + "name" => $name, + "network" => $network, + "photo" => $profile_photo, + "about" => $about, + "location" => $location, + "gender" => $gender, + "keywords" => $keywords, + "server_url" => $server_url, + "connect" => $connect_url, + "notify" => $notify, + "updated" => $updated, + "generation" => $generation); - if (($location == "") AND ($x[0]['location'] != "")) - $location = $x[0]['location']; + $gcid = update_gcontact($gcontact); - if (($about == "") AND ($x[0]['about'] != "")) - $about = $x[0]['about']; - - if (($gender == "") AND ($x[0]['gender'] != "")) - $gender = $x[0]['gender']; - - if (($keywords == "") AND ($x[0]['keywords'] != "")) - $keywords = $x[0]['keywords']; - - if (($addr == "") AND ($x[0]['addr'] != "")) - $addr = $x[0]['addr']; - - if (($generation == 0) AND ($x[0]['generation'] > 0)) - $generation = $x[0]['generation']; - - if($x[0]['name'] != $name || $x[0]['photo'] != $profile_photo || $x[0]['updated'] < $updated) { - q("UPDATE `gcontact` SET `name` = '%s', `addr` = '%s', `network` = '%s', `photo` = '%s', `connect` = '%s', `url` = '%s', `server_url` = '%s', - `updated` = '%s', `location` = '%s', `about` = '%s', `keywords` = '%s', `gender` = '%s', `generation` = %d - WHERE (`generation` >= %d OR `generation` = 0) AND `nurl` = '%s'", - dbesc($name), - dbesc($addr), - dbesc($network), - dbesc($profile_photo), - dbesc($connect_url), - dbesc($profile_url), - dbesc($server_url), - dbesc($updated), - dbesc($location), - dbesc($about), - dbesc($keywords), - dbesc($gender), - intval($generation), - intval($generation), - dbesc(normalise_link($profile_url)) - ); - } - } else { - q("INSERT INTO `gcontact` (`name`, `nick`, `addr`, `network`, `url`, `nurl`, `photo`, `connect`, `server_url`, `created`, `updated`, `location`, `about`, `keywords`, `gender`, `generation`) - VALUES ('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d)", - dbesc($name), - dbesc($nick), - dbesc($addr), - dbesc($network), - dbesc($profile_url), - dbesc(normalise_link($profile_url)), - dbesc($profile_photo), - dbesc($connect_url), - dbesc($server_url), - dbesc(datetime_convert()), - dbesc($updated), - dbesc($location), - dbesc($about), - dbesc($keywords), - dbesc($gender), - intval($generation) - ); - $x = q("SELECT * FROM `gcontact` WHERE `nurl` = '%s' LIMIT 1", - dbesc(normalise_link($profile_url)) - ); - if(count($x)) - $gcid = $x[0]['id']; - } - - if(! $gcid) + if(!$gcid) return $gcid; $r = q("SELECT * FROM `glink` WHERE `cid` = %d AND `uid` = %d AND `gcid` = %d AND `zcid` = %d LIMIT 1", @@ -379,13 +335,6 @@ function poco_check($profile_url, $name, $network, $profile_photo, $about, $loca ); } - // For unknown reasons there are sometimes duplicates - q("DELETE FROM `gcontact` WHERE `nurl` = '%s' AND `id` != %d AND - NOT EXISTS (SELECT `gcid` FROM `glink` WHERE `gcid` = `gcontact`.`id`)", - dbesc(normalise_link($profile_url)), - intval($gcid) - ); - return $gcid; } @@ -698,6 +647,10 @@ function poco_to_boolean($val) { function poco_check_server($server_url, $network = "", $force = false) { + // Unify the server address + $server_url = trim($server_url, "/"); + $server_url = str_replace("/index.php", "", $server_url); + if ($server_url == "") return false; @@ -769,17 +722,23 @@ function poco_check_server($server_url, $network = "", $force = false) { // Test for Diaspora $serverret = z_fetch_url($server_url); - $lines = explode("\n",$serverret["header"]); - if(count($lines)) - foreach($lines as $line) { - $line = trim($line); - if(stristr($line,'X-Diaspora-Version:')) { - $platform = "Diaspora"; - $version = trim(str_replace("X-Diaspora-Version:", "", $line)); - $version = trim(str_replace("x-diaspora-version:", "", $version)); - $network = NETWORK_DIASPORA; + if (!$serverret["success"] OR ($serverret["body"] == "")) + $failure = true; + else { + $lines = explode("\n",$serverret["header"]); + if(count($lines)) + foreach($lines as $line) { + $line = trim($line); + if(stristr($line,'X-Diaspora-Version:')) { + $platform = "Diaspora"; + $version = trim(str_replace("X-Diaspora-Version:", "", $line)); + $version = trim(str_replace("x-diaspora-version:", "", $version)); + $network = NETWORK_DIASPORA; + $versionparts = explode("-", $version); + $version = $versionparts[0]; + } } - } + } } if (!$failure) { @@ -1290,18 +1249,30 @@ function poco_discover_federation() { return; } + // Discover Friendica, Hubzilla and Diaspora servers $serverdata = fetch_url("http://the-federation.info/pods.json"); - if (!$serverdata) - return; + if ($serverdata) { + $servers = json_decode($serverdata); - $servers = json_decode($serverdata); + foreach($servers->pods AS $server) + poco_check_server("https://".$server->host); + } - foreach($servers->pods AS $server) - poco_check_server("https://".$server->host); + // Discover GNU Social Servers + if (!get_config('system','ostatus_disabled')) { + $serverdata = "http://gstools.org/api/get_open_instances/"; + + $result = z_fetch_url($serverdata); + if ($result["success"]) { + $servers = json_decode($result["body"]); + + foreach($servers->data AS $server) + poco_check_server($server->instance_address); + } + } set_config('poco','last_federation_discovery', time()); - } function poco_discover($complete = false) { @@ -1481,4 +1452,214 @@ function poco_discover_server($data, $default_generation = 0) { } return $success; } + +/** + * @brief Fetch the gcontact id, add an entry if not existed + * + * @param arr $contact contact array + * @return bool|int Returns false if not found, integer if contact was found + */ +function get_gcontact_id($contact) { + + $gcontact_id = 0; + + if ($contact["network"] == NETWORK_STATUSNET) + $contact["network"] = NETWORK_OSTATUS; + + $r = q("SELECT `id` FROM `gcontact` WHERE `nurl` = '%s' ORDER BY `id` LIMIT 2", + dbesc(normalise_link($contact["url"]))); + + if ($r) + $gcontact_id = $r[0]["id"]; + else { + q("INSERT INTO `gcontact` (`name`, `nick`, `addr` , `network`, `url`, `nurl`, `photo`, `created`, `updated`, `location`, `about`, `generation`) + VALUES ('%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d)", + dbesc($contact["name"]), + dbesc($contact["nick"]), + dbesc($contact["addr"]), + dbesc($contact["network"]), + dbesc($contact["url"]), + dbesc(normalise_link($contact["url"])), + dbesc($contact["photo"]), + dbesc(datetime_convert()), + dbesc(datetime_convert()), + dbesc($contact["location"]), + dbesc($contact["about"]), + intval($contact["generation"]) + ); + + $r = q("SELECT `id` FROM `gcontact` WHERE `nurl` = '%s' ORDER BY `id` LIMIT 2", + dbesc(normalise_link($contact["url"]))); + + if ($r) + $gcontact_id = $r[0]["id"]; + } + + if ((count($r) > 1) AND ($gcontact_id > 0) AND ($contact["url"] != "")) + q("DELETE FROM `gcontact` WHERE `nurl` = '%s' AND `id` != %d", + dbesc(normalise_link($contact["url"])), + intval($gcontact_id)); + + return $gcontact_id; +} + +/** + * @brief Updates the gcontact table from a given array + * + * @param arr $contact contact array + * @return bool|int Returns false if not found, integer if contact was found + */ +function update_gcontact($contact) { + + /// @todo update contact table as well + + $gcontact_id = get_gcontact_id($contact); + + if (!$gcontact_id) + return false; + + $r = q("SELECT `name`, `nick`, `photo`, `location`, `about`, `addr`, `generation`, `birthday`, `gender`, `keywords`, + `hide`, `nsfw`, `network`, `alias`, `notify`, `server_url`, `connect`, `updated`, `url` + FROM `gcontact` WHERE `id` = %d LIMIT 1", + intval($gcontact_id)); + + // Get all field names + $fields = array(); + foreach ($r[0] AS $field => $data) + $fields[$field] = $data; + + unset($fields["url"]); + unset($fields["updated"]); + + // assign all unassigned fields from the database entry + foreach ($fields AS $field => $data) + if (!isset($contact[$field])) + $contact[$field] = $r[0][$field]; + + if ($contact["network"] == NETWORK_STATUSNET) + $contact["network"] = NETWORK_OSTATUS; + + if (!isset($contact["updated"])) + $contact["updated"] = datetime_convert(); + + // Check if any field changed + $update = false; + unset($fields["generation"]); + + foreach ($fields AS $field => $data) + if ($contact[$field] != $r[0][$field]) + $update = true; + + if ($contact["generation"] < $r[0]["generation"]) + $update = true; + + if ($update) { + q("UPDATE `gcontact` SET `photo` = '%s', `name` = '%s', `nick` = '%s', `addr` = '%s', `network` = '%s', + `birthday` = '%s', `gender` = '%s', `keywords` = '%s', `hide` = %d, `nsfw` = %d, + `alias` = '%s', `notify` = '%s', `url` = '%s', + `location` = '%s', `about` = '%s', `generation` = %d, `updated` = '%s', + `server_url` = '%s', `connect` = '%s' + WHERE `nurl` = '%s' AND (`generation` = 0 OR `generation` >= %d)", + dbesc($contact["photo"]), dbesc($contact["name"]), dbesc($contact["nick"]), + dbesc($contact["addr"]), dbesc($contact["network"]), dbesc($contact["birthday"]), + dbesc($contact["gender"]), dbesc($contact["keywords"]), intval($contact["hide"]), + intval($contact["nsfw"]), dbesc($contact["alias"]), dbesc($contact["notify"]), + dbesc($contact["url"]), dbesc($contact["location"]), dbesc($contact["about"]), + intval($contact["generation"]), dbesc($contact["updated"]), + dbesc($contact["server_url"]), dbesc($contact["connect"]), + dbesc(normalise_link($contact["url"])), intval($contact["generation"])); + } + + return $gcontact_id; +} + +/** + * @brief Updates the gcontact entry from probe + * + * @param str $url profile link + */ +function update_gcontact_from_probe($url) { + $data = probe_url($url); + + if ($data["network"] != NETWORK_PHANTOM) + update_gcontact($data); +} + +/** + * @brief Fetches users of given GNU Social server + * + * If the "Statistics" plugin is enabled (See http://gstools.org/ for details) we query user data with this. + * + * @param str $server Server address + */ +function gs_fetch_users($server) { + + logger("Fetching users from GNU Social server ".$server, LOGGER_DEBUG); + + $a = get_app(); + + $url = $server."/main/statistics"; + + $result = z_fetch_url($url); + if (!$result["success"]) + return false; + + $statistics = json_decode($result["body"]); + + if (is_object($statistics->config)) { + if ($statistics->config->instance_with_ssl) + $server = "https://"; + else + $server = "http://"; + + $server .= $statistics->config->instance_address; + + $hostname = $statistics->config->instance_address; + } else { + if ($statistics->instance_with_ssl) + $server = "https://"; + else + $server = "http://"; + + $server .= $statistics->instance_address; + + $hostname = $statistics->instance_address; + } + + if (is_object($statistics->users)) + foreach ($statistics->users AS $nick => $user) { + $profile_url = $server."/".$user->nickname; + + $contact = array("url" => $profile_url, + "name" => $user->fullname, + "addr" => $user->nickname."@".$hostname, + "nick" => $user->nickname, + "about" => $user->bio, + "network" => NETWORK_OSTATUS, + "photo" => $a->get_baseurl()."/images/person-175.jpg"); + get_gcontact_id($contact); + } +} + +/** + * @brief Asking GNU Social server on a regular base for their user data + * + */ +function gs_discover() { + + $requery_days = intval(get_config("system", "poco_requery_days")); + + $last_update = date("c", time() - (60 * 60 * 24 * $requery_days)); + + $r = q("SELECT `nurl`, `url` FROM `gserver` WHERE `last_contact` >= `last_failure` AND `network` = '%s' AND `last_poco_query` < '%s' ORDER BY RAND() LIMIT 5", + dbesc(NETWORK_OSTATUS), dbesc($last_update)); + + if (!$r) + return; + + foreach ($r AS $server) { + gs_fetch_users($server["url"]); + q("UPDATE `gserver` SET `last_poco_query` = '%s' WHERE `nurl` = '%s'", dbesc(datetime_convert()), dbesc($server["nurl"])); + } +} ?> diff --git a/include/text.php b/include/text.php index 1b03c39d2..c7681a4d5 100644 --- a/include/text.php +++ b/include/text.php @@ -20,10 +20,10 @@ function replace_macros($s,$r) { $stamp1 = microtime(true); $a = get_app(); - + // pass $baseurl to all templates - $r['$baseurl'] = z_root(); - + $r['$baseurl'] = $a->get_baseurl(); + $t = $a->template_engine(); try { @@ -829,35 +829,6 @@ function qp($s) { return str_replace ("%","=",rawurlencode($s)); }} - - -if(! function_exists('get_mentions')) { -/** - * @param array $item - * @return string html for mentions #FIXME: remove html - */ -function get_mentions($item) { - $o = ''; - if(! strlen($item['tag'])) - return $o; - - $arr = explode(',',$item['tag']); - foreach($arr as $x) { - $matches = null; - if(preg_match('/@\[url=([^\]]*)\]/',$x,$matches)) { - $o .= "\t\t" . '' . "\r\n"; - $o .= "\t\t" . '' . "\r\n"; - } - } - - if (!$item['private']) { - $o .= "\t\t".''."\r\n"; - $o .= "\t\t".''."\r\n"; - } - - return $o; -}} - if(! function_exists('contact_block')) { /** * Get html for contact block. @@ -895,9 +866,9 @@ function contact_block() { $micropro = Null; } else { - $r = q("SELECT * FROM `contact` - WHERE `uid` = %d AND `self` = 0 AND `blocked` = 0 and `pending` = 0 - AND `hidden` = 0 AND `archive` = 0 + $r = q("SELECT `id`, `uid`, `addr`, `url`, `name`, `micro`, `network` FROM `contact` + WHERE `uid` = %d AND NOT `self` AND NOT `blocked` AND NOT `pending` + AND NOT `hidden` AND NOT `archive` AND `network` IN ('%s', '%s', '%s') ORDER BY RAND() LIMIT %d", intval($a->profile['uid']), dbesc(NETWORK_DFRN), @@ -1415,7 +1386,14 @@ function prepare_body(&$item,$attach = false, $preview = false) { $item['hashtags'] = $hashtags; $item['mentions'] = $mentions; - put_item_in_cache($item, true); + // Update the cached values if there is no "zrl=..." on the links + $update = (!local_user() and !remote_user() and ($item["uid"] == 0)); + + // Or update it if the current viewer is the intented viewer + if (($item["uid"] == local_user()) AND ($item["uid"] != 0)) + $update = true; + + put_item_in_cache($item, $update); $s = $item["rendered-html"]; $prep_arr = array('item' => $item, 'html' => $s, 'preview' => $preview); @@ -1526,7 +1504,7 @@ function prepare_body(&$item,$attach = false, $preview = false) { $pos = strpos($s, $spoilersearch); $rnd = random_string(8); - $spoilerreplace = '
'.sprintf(t('Click to open/close')).''. + $spoilerreplace = '
'.sprintf(t('Click to open/close')).''. '