From 74d4f7fcd6332f2440339515e16d34cd745bd035 Mon Sep 17 00:00:00 2001 From: Roland Haeder Date: Sat, 12 Mar 2016 22:54:06 +0100 Subject: [PATCH 001/211] count() didn't notice about FALSE ... #2390 Signed-off-by: Roland Haeder --- mod/profile.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mod/profile.php b/mod/profile.php index 26bd395230..c3f58ab528 100644 --- a/mod/profile.php +++ b/mod/profile.php @@ -300,7 +300,7 @@ function profile_content(&$a, $update = 0) { $parents_arr = array(); $parents_str = ''; - if(count($r)) { + if (dba::is_result($r)) { foreach($r as $rr) $parents_arr[] = $rr['item_id']; $parents_str = implode(', ', $parents_arr); From 35bd5792bc0362d169fabeca3eb41aea1fc8bb2f Mon Sep 17 00:00:00 2001 From: rabuzarus <> Date: Tue, 19 Jan 2016 18:11:40 +0100 Subject: [PATCH 002/211] rework autocomplete: initial commit --- js/autocomplete.js | 192 +++ js/fk.autocomplete.js | 2 +- js/main.js | 2 +- library/jquery-textcomplete/LICENSE | 21 + .../jquery.textcomplete.js | 1104 +++++++++++++++++ view/templates/display-head.tpl | 4 +- view/templates/head.tpl | 30 +- view/templates/jot-header.tpl | 2 +- 8 files changed, 1338 insertions(+), 19 deletions(-) create mode 100644 js/autocomplete.js create mode 100644 library/jquery-textcomplete/LICENSE create mode 100644 library/jquery-textcomplete/jquery.textcomplete.js diff --git a/js/autocomplete.js b/js/autocomplete.js new file mode 100644 index 0000000000..aa4494b714 --- /dev/null +++ b/js/autocomplete.js @@ -0,0 +1,192 @@ +/** + * Red people autocomplete + * + * require jQuery, jquery.textcomplete + */ +function contact_search(term, callback, backend_url, type) { + + // Check if there is a cached result that contains the same information we would get with a full server-side search + var bt = backend_url+type; + if(!(bt in contact_search.cache)) contact_search.cache[bt] = {}; + + var lterm = term.toLowerCase(); // Ignore case + for(var t in contact_search.cache[bt]) { + if(lterm.indexOf(t) >= 0) { // A more broad search has been performed already, so use those results + // Filter old results locally + var matching = contact_search.cache[bt][t].filter(function (x) { return (x.name.toLowerCase().indexOf(lterm) >= 0 || (typeof x.nick !== 'undefined' && x.nick.toLowerCase().indexOf(lterm) >= 0)); }); // Need to check that nick exists because groups don't have one + matching.unshift({taggable:false, text: term, replace: term}); + setTimeout(function() { callback(matching); } , 1); // Use "pseudo-thread" to avoid some problems + return; + } + } + + var postdata = { + start:0, + count:100, + search:term, + type:type, + }; + + + $.ajax({ + type:'POST', + url: backend_url, + data: postdata, + dataType: 'json', + success: function(data){ + // Cache results if we got them all (more information would not improve results) + // data.count represents the maximum number of items + if(data.items.length -1 < data.count) { + contact_search.cache[bt][lterm] = data.items; + } + var items = data.items.slice(0); + items.unshift({taggable:false, text: term, replace: term}); + callback(items); + }, + }).fail(function () {callback([]); }); // Callback must be invoked even if something went wrong. +} +contact_search.cache = {}; + + +function contact_format(item) { + // Show contact information if not explicitly told to show something else + if(typeof item.text === 'undefined') { + var desc = ((item.label) ? item.nick + ' ' + item.label : item.nick); + if(typeof desc === 'undefined') desc = ''; + if(desc) desc = ' ('+desc+')'; + return "
{2}{3}
".format(item.taggable, item.photo, item.name, desc, item.link); + } + else + return "
" + item.text + "
"; +} + +function editor_replace(item) { + if(typeof item.replace !== 'undefined') { + return '$1$2' + item.replace; + } + + // $2 ensures that prefix (@,@!) is preserved + var id = item.id; + // 16 chars of hash should be enough. Full hash could be used if it can be done in a visually appealing way. + // 16 chars is also the minimum length in the backend (otherwise it's interpreted as a local id). + if(id.length > 16) + id = item.id.substring(0,16); + + return '$1$2' + item.nick.replace(' ', '') + '+' + id + ' '; +} + +function basic_replace(item) { + if(typeof item.replace !== 'undefined') + return '$1'+item.replace; + + return '$1'+item.name+' '; +} + +function trim_replace(item) { + if(typeof item.replace !== 'undefined') + return '$1'+item.replace; + + return '$1'+item.name; +} + + +function submit_form(e) { + $(e).parents('form').submit(); +} + +/** + * jQuery plugin 'editor_autocomplete' + */ +(function( $ ) { + $.fn.editor_autocomplete = function(backend_url) { + + // Autocomplete contacts + contacts = { + match: /(^|\s)(@\!*)([^ \n]+)$/, + index: 3, + search: function(term, callback) { contact_search(term, callback, backend_url, 'c'); }, + replace: editor_replace, + template: contact_format, + }; + + smilies = { + match: /(^|\s)(:[a-z]{2,})$/, + index: 2, + search: function(term, callback) { $.getJSON('/smilies/json').done(function(data) { callback($.map(data, function(entry) { return entry.text.indexOf(term) === 0 ? entry : null; })); }); }, + template: function(item) { return item.icon + item.text; }, + replace: function(item) { return "$1" + item.text + ' '; }, + }; + this.attr('autocomplete','off'); + this.textcomplete([contacts,smilies], {className:'acpopup', zIndex:1020}); + }; +})( jQuery ); + +/** + * jQuery plugin 'search_autocomplete' + */ +(function( $ ) { + $.fn.search_autocomplete = function(backend_url) { + // Autocomplete contacts + contacts = { + match: /(^@)([^\n]{2,})$/, + index: 2, + search: function(term, callback) { contact_search(term, callback, backend_url, 'x'); }, + replace: basic_replace, + template: contact_format, + }; + this.attr('autocomplete', 'off'); + var a = this.textcomplete([contacts], {className:'acpopup', maxCount:100, zIndex: 1020, appendTo:'nav'}); + a.on('textComplete:select', function(e, value, strategy) { submit_form(this); }); + }; +})( jQuery ); + +(function( $ ) { + $.fn.contact_autocomplete = function(backend_url, typ, autosubmit, onselect) { + if(typeof typ === 'undefined') typ = ''; + if(typeof autosubmit === 'undefined') autosubmit = false; + + // Autocomplete contacts + contacts = { + match: /(^)([^\n]+)$/, + index: 2, + search: function(term, callback) { contact_search(term, callback, backend_url, typ); }, + replace: basic_replace, + template: contact_format, + }; + + this.attr('autocomplete','off'); + var a = this.textcomplete([contacts], {className:'acpopup', zIndex:1020}); + + if(autosubmit) + a.on('textComplete:select', function(e,value,strategy) { submit_form(this); }); + + if(typeof onselect !== 'undefined') + a.on('textComplete:select', function(e, value, strategy) { onselect(value); }); + }; +})( jQuery ); + + +(function( $ ) { + $.fn.name_autocomplete = function(backend_url, typ, autosubmit, onselect) { + if(typeof typ === 'undefined') typ = ''; + if(typeof autosubmit === 'undefined') autosubmit = false; + + // Autocomplete contacts + names = { + match: /(^)([^\n]+)$/, + index: 2, + search: function(term, callback) { contact_search(term, callback, backend_url, typ); }, + replace: trim_replace, + template: contact_format, + }; + + this.attr('autocomplete','off'); + var a = this.textcomplete([names], {className:'acpopup', zIndex:1020}); + + if(autosubmit) + a.on('textComplete:select', function(e,value,strategy) { submit_form(this); }); + + if(typeof onselect !== 'undefined') + a.on('textComplete:select', function(e, value, strategy) { onselect(value); }); + }; +})( jQuery ); diff --git a/js/fk.autocomplete.js b/js/fk.autocomplete.js index 6010578ab7..d7c81276bb 100644 --- a/js/fk.autocomplete.js +++ b/js/fk.autocomplete.js @@ -86,7 +86,7 @@ ACPopup.prototype._search = function(){ if (data.tot>0){ that.cont.show(); $(data.items).each(function(){ - var html = "{1} ({2})".format(this.photo, this.name, this.nick); + var html = " {1} ({2})".format(this.photo, this.name, this.addr); var nick = this.nick.replace(' ',''); if (this.id!=='') nick += '+' + this.id; that.add(html, nick + ' - ' + this.link); diff --git a/js/main.js b/js/main.js index 5a1affe3cb..1ed0c4b6af 100644 --- a/js/main.js +++ b/js/main.js @@ -494,7 +494,7 @@ $('body').css('cursor', 'auto'); } /* autocomplete @nicknames */ - $(".comment-edit-form textarea").contact_autocomplete(baseurl+"/acl"); + $(".comment-edit-form textarea").editor_autocomplete(baseurl+"/acl"); // setup videos, since VideoJS won't take care of any loaded via AJAX if(typeof videojs != 'undefined') videojs.autoSetup(); diff --git a/library/jquery-textcomplete/LICENSE b/library/jquery-textcomplete/LICENSE new file mode 100644 index 0000000000..4848bd6377 --- /dev/null +++ b/library/jquery-textcomplete/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2013-2014 Yuku Takahashi + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/library/jquery-textcomplete/jquery.textcomplete.js b/library/jquery-textcomplete/jquery.textcomplete.js new file mode 100644 index 0000000000..3df84f3b43 --- /dev/null +++ b/library/jquery-textcomplete/jquery.textcomplete.js @@ -0,0 +1,1104 @@ +/*! + * jQuery.textcomplete + * + * Repository: https://github.com/yuku-t/jquery-textcomplete + * License: MIT (https://github.com/yuku-t/jquery-textcomplete/blob/master/LICENSE) + * Author: Yuku Takahashi + */ + +if (typeof jQuery === 'undefined') { + throw new Error('jQuery.textcomplete requires jQuery'); +} + ++function ($) { + 'use strict'; + + var warn = function (message) { + if (console.warn) { console.warn(message); } + }; + + $.fn.textcomplete = function (strategies, option) { + var args = Array.prototype.slice.call(arguments); + return this.each(function () { + var $this = $(this); + var completer = $this.data('textComplete'); + if (!completer) { + completer = new $.fn.textcomplete.Completer(this, option || {}); + $this.data('textComplete', completer); + } + if (typeof strategies === 'string') { + if (!completer) return; + args.shift() + completer[strategies].apply(completer, args); + if (strategies === 'destroy') { + $this.removeData('textComplete'); + } + } else { + // For backward compatibility. + // TODO: Remove at v0.4 + $.each(strategies, function (obj) { + $.each(['header', 'footer', 'placement', 'maxCount'], function (name) { + if (obj[name]) { + completer.option[name] = obj[name]; + warn(name + 'as a strategy param is deprecated. Use option.'); + delete obj[name]; + } + }); + }); + completer.register($.fn.textcomplete.Strategy.parse(strategies)); + } + }); + }; + +}(jQuery); + ++function ($) { + 'use strict'; + + // Exclusive execution control utility. + // + // func - The function to be locked. It is executed with a function named + // `free` as the first argument. Once it is called, additional + // execution are ignored until the free is invoked. Then the last + // ignored execution will be replayed immediately. + // + // Examples + // + // var lockedFunc = lock(function (free) { + // setTimeout(function { free(); }, 1000); // It will be free in 1 sec. + // console.log('Hello, world'); + // }); + // lockedFunc(); // => 'Hello, world' + // lockedFunc(); // none + // lockedFunc(); // none + // // 1 sec past then + // // => 'Hello, world' + // lockedFunc(); // => 'Hello, world' + // lockedFunc(); // none + // + // Returns a wrapped function. + var lock = function (func) { + var locked, queuedArgsToReplay; + + return function () { + // Convert arguments into a real array. + var args = Array.prototype.slice.call(arguments); + if (locked) { + // Keep a copy of this argument list to replay later. + // OK to overwrite a previous value because we only replay + // the last one. + queuedArgsToReplay = args; + return; + } + locked = true; + var self = this; + args.unshift(function replayOrFree() { + if (queuedArgsToReplay) { + // Other request(s) arrived while we were locked. + // Now that the lock is becoming available, replay + // the latest such request, then call back here to + // unlock (or replay another request that arrived + // while this one was in flight). + var replayArgs = queuedArgsToReplay; + queuedArgsToReplay = undefined; + replayArgs.unshift(replayOrFree); + func.apply(self, replayArgs); + } else { + locked = false; + } + }); + func.apply(this, args); + }; + }; + + var isString = function (obj) { + return Object.prototype.toString.call(obj) === '[object String]'; + }; + + var uniqueId = 0; + + function Completer(element, option) { + this.$el = $(element); + this.id = 'textcomplete' + uniqueId++; + this.strategies = []; + this.views = []; + this.option = $.extend({}, Completer._getDefaults(), option); + + if (!this.$el.is('input[type=text]') && !this.$el.is('textarea') && !element.isContentEditable && element.contentEditable != 'true') { + throw new Error('textcomplete must be called on a Textarea or a ContentEditable.'); + } + + if (element === document.activeElement) { + // element has already been focused. Initialize view objects immediately. + this.initialize() + } else { + // Initialize view objects lazily. + var self = this; + this.$el.one('focus.' + this.id, function () { self.initialize(); }); + } + } + + Completer._getDefaults = function () { + if (!Completer.DEFAULTS) { + Completer.DEFAULTS = { + appendTo: $('body'), + zIndex: '100' + }; + } + + return Completer.DEFAULTS; + } + + $.extend(Completer.prototype, { + // Public properties + // ----------------- + + id: null, + option: null, + strategies: null, + adapter: null, + dropdown: null, + $el: null, + + // Public methods + // -------------- + + initialize: function () { + var element = this.$el.get(0); + // Initialize view objects. + this.dropdown = new $.fn.textcomplete.Dropdown(element, this, this.option); + var Adapter, viewName; + if (this.option.adapter) { + Adapter = this.option.adapter; + } else { + if (this.$el.is('textarea') || this.$el.is('input[type=text]')) { + viewName = typeof element.selectionEnd === 'number' ? 'Textarea' : 'IETextarea'; + } else { + viewName = 'ContentEditable'; + } + Adapter = $.fn.textcomplete[viewName]; + } + this.adapter = new Adapter(element, this, this.option); + }, + + destroy: function () { + this.$el.off('.' + this.id); + if (this.adapter) { + this.adapter.destroy(); + } + if (this.dropdown) { + this.dropdown.destroy(); + } + this.$el = this.adapter = this.dropdown = null; + }, + + // Invoke textcomplete. + trigger: function (text, skipUnchangedTerm) { + if (!this.dropdown) { this.initialize(); } + text != null || (text = this.adapter.getTextFromHeadToCaret()); + var searchQuery = this._extractSearchQuery(text); + if (searchQuery.length) { + var term = searchQuery[1]; + // Ignore shift-key, ctrl-key and so on. + if (skipUnchangedTerm && this._term === term) { return; } + this._term = term; + this._search.apply(this, searchQuery); + } else { + this._term = null; + this.dropdown.deactivate(); + } + }, + + fire: function (eventName) { + var args = Array.prototype.slice.call(arguments, 1); + this.$el.trigger(eventName, args); + return this; + }, + + register: function (strategies) { + Array.prototype.push.apply(this.strategies, strategies); + }, + + // Insert the value into adapter view. It is called when the dropdown is clicked + // or selected. + // + // value - The selected element of the array callbacked from search func. + // strategy - The Strategy object. + select: function (value, strategy) { + this.adapter.select(value, strategy); + this.fire('change').fire('textComplete:select', value, strategy); + this.adapter.focus(); + }, + + // Private properties + // ------------------ + + _clearAtNext: true, + _term: null, + + // Private methods + // --------------- + + // Parse the given text and extract the first matching strategy. + // + // Returns an array including the strategy, the query term and the match + // object if the text matches an strategy; otherwise returns an empty array. + _extractSearchQuery: function (text) { + for (var i = 0; i < this.strategies.length; i++) { + var strategy = this.strategies[i]; + var context = strategy.context(text); + if (context || context === '') { + if (isString(context)) { text = context; } + var match = text.match(strategy.match); + if (match) { return [strategy, match[strategy.index], match]; } + } + } + return [] + }, + + // Call the search method of selected strategy.. + _search: lock(function (free, strategy, term, match) { + var self = this; + strategy.search(term, function (data, stillSearching) { + if (!self.dropdown.shown) { + self.dropdown.activate(); + self.dropdown.setPosition(self.adapter.getCaretPosition()); + } + if (self._clearAtNext) { + // The first callback in the current lock. + self.dropdown.clear(); + self._clearAtNext = false; + } + self.dropdown.render(self._zip(data, strategy)); + if (!stillSearching) { + // The last callback in the current lock. + free(); + self._clearAtNext = true; // Call dropdown.clear at the next time. + } + }, match); + }), + + // Build a parameter for Dropdown#render. + // + // Examples + // + // this._zip(['a', 'b'], 's'); + // //=> [{ value: 'a', strategy: 's' }, { value: 'b', strategy: 's' }] + _zip: function (data, strategy) { + return $.map(data, function (value) { + return { value: value, strategy: strategy }; + }); + } + }); + + $.fn.textcomplete.Completer = Completer; +}(jQuery); + ++function ($) { + 'use strict'; + + var include = function (zippedData, datum) { + var i, elem; + var idProperty = datum.strategy.idProperty + for (i = 0; i < zippedData.length; i++) { + elem = zippedData[i]; + if (elem.strategy !== datum.strategy) continue; + if (idProperty) { + if (elem.value[idProperty] === datum.value[idProperty]) return true; + } else { + if (elem.value === datum.value) return true; + } + } + return false; + }; + + var dropdownViews = {}; + $(document).on('click', function (e) { + var id = e.originalEvent && e.originalEvent.keepTextCompleteDropdown; + $.each(dropdownViews, function (key, view) { + if (key !== id) { view.deactivate(); } + }); + }); + + // Dropdown view + // ============= + + // Construct Dropdown object. + // + // element - Textarea or contenteditable element. + function Dropdown(element, completer, option) { + this.$el = Dropdown.findOrCreateElement(option); + this.completer = completer; + this.id = completer.id + 'dropdown'; + this._data = []; // zipped data. + this.$inputEl = $(element); + this.option = option; + + // Override setPosition method. + if (option.listPosition) { this.setPosition = option.listPosition; } + if (option.height) { this.$el.height(option.height); } + var self = this; + $.each(['maxCount', 'placement', 'footer', 'header', 'className'], function (_i, name) { + if (option[name] != null) { self[name] = option[name]; } + }); + this._bindEvents(element); + dropdownViews[this.id] = this; + } + + $.extend(Dropdown, { + // Class methods + // ------------- + + findOrCreateElement: function (option) { + var $parent = option.appendTo; + if (!($parent instanceof $)) { $parent = $($parent); } + var $el = $parent.children('.dropdown-menu') + if (!$el.length) { + $el = $('').css({ + display: 'none', + left: 0, + position: 'absolute', + zIndex: option.zIndex + }).appendTo($parent); + } + return $el; + } + }); + + $.extend(Dropdown.prototype, { + // Public properties + // ----------------- + + $el: null, // jQuery object of ul.dropdown-menu element. + $inputEl: null, // jQuery object of target textarea. + completer: null, + footer: null, + header: null, + id: null, + maxCount: 10, + placement: '', + shown: false, + data: [], // Shown zipped data. + className: '', + + // Public methods + // -------------- + + destroy: function () { + // Don't remove $el because it may be shared by several textcompletes. + this.deactivate(); + + this.$el.off('.' + this.id); + this.$inputEl.off('.' + this.id); + this.clear(); + this.$el = this.$inputEl = this.completer = null; + delete dropdownViews[this.id] + }, + + render: function (zippedData) { + var contentsHtml = this._buildContents(zippedData); + var unzippedData = $.map(this.data, function (d) { return d.value; }); + if (this.data.length) { + this._renderHeader(unzippedData); + this._renderFooter(unzippedData); + if (contentsHtml) { + this._renderContents(contentsHtml); + this._activateIndexedItem(); + } + this._setScroll(); + } else if (this.shown) { + this.deactivate(); + } + }, + + setPosition: function (position) { + this.$el.css(this._applyPlacement(position)); + + // Make the dropdown fixed if the input is also fixed + // This can't be done during init, as textcomplete may be used on multiple elements on the same page + // Because the same dropdown is reused behind the scenes, we need to recheck every time the dropdown is showed + var position = 'absolute'; + // Check if input or one of its parents has positioning we need to care about + this.$inputEl.add(this.$inputEl.parents()).each(function() { + if($(this).css('position') === 'absolute') // The element has absolute positioning, so it's all OK + return false; + if($(this).css('position') === 'fixed') { + position = 'fixed'; + return false; + } + }); + this.$el.css({ position: position }); // Update positioning + + return this; + }, + + clear: function () { + this.$el.html(''); + this.data = []; + this._index = 0; + this._$header = this._$footer = null; + }, + + activate: function () { + if (!this.shown) { + this.clear(); + this.$el.show(); + if (this.className) { this.$el.addClass(this.className); } + this.completer.fire('textComplete:show'); + this.shown = true; + } + return this; + }, + + deactivate: function () { + if (this.shown) { + this.$el.hide(); + if (this.className) { this.$el.removeClass(this.className); } + this.completer.fire('textComplete:hide'); + this.shown = false; + } + return this; + }, + + isUp: function (e) { + return e.keyCode === 38 || (e.ctrlKey && e.keyCode === 80); // UP, Ctrl-P + }, + + isDown: function (e) { + return e.keyCode === 40 || (e.ctrlKey && e.keyCode === 78); // DOWN, Ctrl-N + }, + + isEnter: function (e) { + var modifiers = e.ctrlKey || e.altKey || e.metaKey || e.shiftKey; + return !modifiers && (e.keyCode === 13 || e.keyCode === 9 || (this.option.completeOnSpace === true && e.keyCode === 32)) // ENTER, TAB + }, + + isPageup: function (e) { + return e.keyCode === 33; // PAGEUP + }, + + isPagedown: function (e) { + return e.keyCode === 34; // PAGEDOWN + }, + + // Private properties + // ------------------ + + _data: null, // Currently shown zipped data. + _index: null, + _$header: null, + _$footer: null, + + // Private methods + // --------------- + + _bindEvents: function () { + this.$el.on('mousedown.' + this.id, '.textcomplete-item', $.proxy(this._onClick, this)) + this.$el.on('mouseover.' + this.id, '.textcomplete-item', $.proxy(this._onMouseover, this)); + this.$inputEl.on('keydown.' + this.id, $.proxy(this._onKeydown, this)); + }, + + _onClick: function (e) { + var $el = $(e.target); + e.preventDefault(); + e.originalEvent.keepTextCompleteDropdown = this.id; + if (!$el.hasClass('textcomplete-item')) { + $el = $el.closest('.textcomplete-item'); + } + var datum = this.data[parseInt($el.data('index'), 10)]; + this.completer.select(datum.value, datum.strategy); + var self = this; + // Deactive at next tick to allow other event handlers to know whether + // the dropdown has been shown or not. + setTimeout(function () { self.deactivate(); }, 0); + }, + + // Activate hovered item. + _onMouseover: function (e) { + var $el = $(e.target); + e.preventDefault(); + if (!$el.hasClass('textcomplete-item')) { + $el = $el.closest('.textcomplete-item'); + } + this._index = parseInt($el.data('index'), 10); + this._activateIndexedItem(); + }, + + _onKeydown: function (e) { + if (!this.shown) { return; } + if (this.isUp(e)) { + e.preventDefault(); + this._up(); + } else if (this.isDown(e)) { + e.preventDefault(); + this._down(); + } else if (this.isEnter(e)) { + e.preventDefault(); + this._enter(); + } else if (this.isPageup(e)) { + e.preventDefault(); + this._pageup(); + } else if (this.isPagedown(e)) { + e.preventDefault(); + this._pagedown(); + } + }, + + _up: function () { + if (this._index === 0) { + this._index = this.data.length - 1; + } else { + this._index -= 1; + } + this._activateIndexedItem(); + this._setScroll(); + }, + + _down: function () { + if (this._index === this.data.length - 1) { + this._index = 0; + } else { + this._index += 1; + } + this._activateIndexedItem(); + this._setScroll(); + }, + + _enter: function () { + var datum = this.data[parseInt(this._getActiveElement().data('index'), 10)]; + this.completer.select(datum.value, datum.strategy); + this._setScroll(); + }, + + _pageup: function () { + var target = 0; + var threshold = this._getActiveElement().position().top - this.$el.innerHeight(); + this.$el.children().each(function (i) { + if ($(this).position().top + $(this).outerHeight() > threshold) { + target = i; + return false; + } + }); + this._index = target; + this._activateIndexedItem(); + this._setScroll(); + }, + + _pagedown: function () { + var target = this.data.length - 1; + var threshold = this._getActiveElement().position().top + this.$el.innerHeight(); + this.$el.children().each(function (i) { + if ($(this).position().top > threshold) { + target = i; + return false + } + }); + this._index = target; + this._activateIndexedItem(); + this._setScroll(); + }, + + _activateIndexedItem: function () { + this.$el.find('.textcomplete-item.active').removeClass('active'); + this._getActiveElement().addClass('active'); + }, + + _getActiveElement: function () { + return this.$el.children('.textcomplete-item:nth(' + this._index + ')'); + }, + + _setScroll: function () { + var $activeEl = this._getActiveElement(); + var itemTop = $activeEl.position().top; + var itemHeight = $activeEl.outerHeight(); + var visibleHeight = this.$el.innerHeight(); + var visibleTop = this.$el.scrollTop(); + if (this._index === 0 || this._index == this.data.length - 1 || itemTop < 0) { + this.$el.scrollTop(itemTop + visibleTop); + } else if (itemTop + itemHeight > visibleHeight) { + this.$el.scrollTop(itemTop + itemHeight + visibleTop - visibleHeight); + } + }, + + _buildContents: function (zippedData) { + var datum, i, index; + var html = ''; + for (i = 0; i < zippedData.length; i++) { + if (this.data.length === this.maxCount) break; + datum = zippedData[i]; + if (include(this.data, datum)) { continue; } + index = this.data.length; + this.data.push(datum); + html += '
  • '; + html += datum.strategy.template(datum.value); + html += '
  • '; + } + return html; + }, + + _renderHeader: function (unzippedData) { + if (this.header) { + if (!this._$header) { + this._$header = $('
  • ').prependTo(this.$el); + } + var html = $.isFunction(this.header) ? this.header(unzippedData) : this.header; + this._$header.html(html); + } + }, + + _renderFooter: function (unzippedData) { + if (this.footer) { + if (!this._$footer) { + this._$footer = $('').appendTo(this.$el); + } + var html = $.isFunction(this.footer) ? this.footer(unzippedData) : this.footer; + this._$footer.html(html); + } + }, + + _renderContents: function (html) { + if (this._$footer) { + this._$footer.before(html); + } else { + this.$el.append(html); + } + }, + + _applyPlacement: function (position) { + // If the 'placement' option set to 'top', move the position above the element. + if (this.placement.indexOf('top') !== -1) { + // Overwrite the position object to set the 'bottom' property instead of the top. + position = { + top: 'auto', + bottom: this.$el.parent().height() - position.top + position.lineHeight, + left: position.left + }; + } else { + position.bottom = 'auto'; + delete position.lineHeight; + } + if (this.placement.indexOf('absleft') !== -1) { + position.left = 0; + } else if (this.placement.indexOf('absright') !== -1) { + position.right = 0; + position.left = 'auto'; + } + return position; + } + }); + + $.fn.textcomplete.Dropdown = Dropdown; +}(jQuery); + ++function ($) { + 'use strict'; + + // Memoize a search function. + var memoize = function (func) { + var memo = {}; + return function (term, callback) { + if (memo[term]) { + callback(memo[term]); + } else { + func.call(this, term, function (data) { + memo[term] = (memo[term] || []).concat(data); + callback.apply(null, arguments); + }); + } + }; + }; + + function Strategy(options) { + $.extend(this, options); + if (this.cache) { this.search = memoize(this.search); } + } + + Strategy.parse = function (optionsArray) { + return $.map(optionsArray, function (options) { + return new Strategy(options); + }); + }; + + $.extend(Strategy.prototype, { + // Public properties + // ----------------- + + // Required + match: null, + replace: null, + search: null, + + // Optional + cache: false, + context: function () { return true; }, + index: 2, + template: function (obj) { return obj; }, + idProperty: null + }); + + $.fn.textcomplete.Strategy = Strategy; + +}(jQuery); + ++function ($) { + 'use strict'; + + var now = Date.now || function () { return new Date().getTime(); }; + + // Returns a function, that, as long as it continues to be invoked, will not + // be triggered. The function will be called after it stops being called for + // `wait` msec. + // + // This utility function was originally implemented at Underscore.js. + var debounce = function (func, wait) { + var timeout, args, context, timestamp, result; + var later = function () { + var last = now() - timestamp; + if (last < wait) { + timeout = setTimeout(later, wait - last); + } else { + timeout = null; + result = func.apply(context, args); + context = args = null; + } + }; + + return function () { + context = this; + args = arguments; + timestamp = now(); + if (!timeout) { + timeout = setTimeout(later, wait); + } + return result; + }; + }; + + function Adapter () {} + + $.extend(Adapter.prototype, { + // Public properties + // ----------------- + + id: null, // Identity. + completer: null, // Completer object which creates it. + el: null, // Textarea element. + $el: null, // jQuery object of the textarea. + option: null, + + // Public methods + // -------------- + + initialize: function (element, completer, option) { + this.el = element; + this.$el = $(element); + this.id = completer.id + this.constructor.name; + this.completer = completer; + this.option = option; + + if (this.option.debounce) { + this._onKeyup = debounce(this._onKeyup, this.option.debounce); + } + + this._bindEvents(); + }, + + destroy: function () { + this.$el.off('.' + this.id); // Remove all event handlers. + this.$el = this.el = this.completer = null; + }, + + // Update the element with the given value and strategy. + // + // value - The selected object. It is one of the item of the array + // which was callbacked from the search function. + // strategy - The Strategy associated with the selected value. + select: function (/* value, strategy */) { + throw new Error('Not implemented'); + }, + + // Returns the caret's relative coordinates from body's left top corner. + // + // FIXME: Calculate the left top corner of `this.option.appendTo` element. + getCaretPosition: function () { + var position = this._getCaretRelativePosition(); + var offset = this.$el.offset(); + position.top += offset.top; + position.left += offset.left; + return position; + }, + + // Focus on the element. + focus: function () { + this.$el.focus(); + }, + + // Private methods + // --------------- + + _bindEvents: function () { + this.$el.on('keyup.' + this.id, $.proxy(this._onKeyup, this)); + }, + + _onKeyup: function (e) { + if (this._skipSearch(e)) { return; } + this.completer.trigger(this.getTextFromHeadToCaret(), true); + }, + + // Suppress searching if it returns true. + _skipSearch: function (clickEvent) { + switch (clickEvent.keyCode) { + case 40: // DOWN + case 38: // UP + return true; + } + if (clickEvent.ctrlKey) switch (clickEvent.keyCode) { + case 78: // Ctrl-N + case 80: // Ctrl-P + return true; + } + } + }); + + $.fn.textcomplete.Adapter = Adapter; +}(jQuery); + ++function ($) { + 'use strict'; + + // Textarea adapter + // ================ + // + // Managing a textarea. It doesn't know a Dropdown. + function Textarea(element, completer, option) { + this.initialize(element, completer, option); + } + + Textarea.DIV_PROPERTIES = { + left: -9999, + position: 'absolute', + top: 0, + whiteSpace: 'pre-wrap' + } + + Textarea.COPY_PROPERTIES = [ + 'border-width', 'font-family', 'font-size', 'font-style', 'font-variant', + 'font-weight', 'height', 'letter-spacing', 'word-spacing', 'line-height', + 'text-decoration', 'text-align', 'width', 'padding-top', 'padding-right', + 'padding-bottom', 'padding-left', 'margin-top', 'margin-right', + 'margin-bottom', 'margin-left', 'border-style', 'box-sizing', 'tab-size' + ]; + + $.extend(Textarea.prototype, $.fn.textcomplete.Adapter.prototype, { + // Public methods + // -------------- + + // Update the textarea with the given value and strategy. + select: function (value, strategy) { + var pre = this.getTextFromHeadToCaret(); + var post = this.el.value.substring(this.el.selectionEnd); + var newSubstr = strategy.replace(value); + if ($.isArray(newSubstr)) { + post = newSubstr[1] + post; + newSubstr = newSubstr[0]; + } + pre = pre.replace(strategy.match, newSubstr); + this.$el.val(pre + post); + this.el.selectionStart = this.el.selectionEnd = pre.length; + }, + + // Private methods + // --------------- + + // Returns the caret's relative coordinates from textarea's left top corner. + // + // Browser native API does not provide the way to know the position of + // caret in pixels, so that here we use a kind of hack to accomplish + // the aim. First of all it puts a dummy div element and completely copies + // the textarea's style to the element, then it inserts the text and a + // span element into the textarea. + // Consequently, the span element's position is the thing what we want. + _getCaretRelativePosition: function () { + var dummyDiv = $('
    ').css(this._copyCss()) + .text(this.getTextFromHeadToCaret()); + var span = $('').text('.').appendTo(dummyDiv); + this.$el.before(dummyDiv); + var position = span.position(); + position.top += span.height() - this.$el.scrollTop(); + position.lineHeight = span.height(); + dummyDiv.remove(); + return position; + }, + + _copyCss: function () { + return $.extend({ + // Set 'scroll' if a scrollbar is being shown; otherwise 'auto'. + overflow: this.el.scrollHeight > this.el.offsetHeight ? 'scroll' : 'auto' + }, Textarea.DIV_PROPERTIES, this._getStyles()); + }, + + _getStyles: (function ($) { + var color = $('
    ').css(['color']).color; + if (typeof color !== 'undefined') { + return function () { + return this.$el.css(Textarea.COPY_PROPERTIES); + }; + } else { // jQuery < 1.8 + return function () { + var $el = this.$el; + var styles = {}; + $.each(Textarea.COPY_PROPERTIES, function (i, property) { + styles[property] = $el.css(property); + }); + return styles; + }; + } + })($), + + getTextFromHeadToCaret: function () { + return this.el.value.substring(0, this.el.selectionEnd); + } + }); + + $.fn.textcomplete.Textarea = Textarea; +}(jQuery); + ++function ($) { + 'use strict'; + + var sentinelChar = '吶'; + + function IETextarea(element, completer, option) { + this.initialize(element, completer, option); + $('' + sentinelChar + '').css({ + position: 'absolute', + top: -9999, + left: -9999 + }).insertBefore(element); + } + + $.extend(IETextarea.prototype, $.fn.textcomplete.Textarea.prototype, { + // Public methods + // -------------- + + select: function (value, strategy) { + var pre = this.getTextFromHeadToCaret(); + var post = this.el.value.substring(pre.length); + var newSubstr = strategy.replace(value); + if ($.isArray(newSubstr)) { + post = newSubstr[1] + post; + newSubstr = newSubstr[0]; + } + pre = pre.replace(strategy.match, newSubstr); + this.$el.val(pre + post); + this.el.focus(); + var range = this.el.createTextRange(); + range.collapse(true); + range.moveEnd('character', pre.length); + range.moveStart('character', pre.length); + range.select(); + }, + + getTextFromHeadToCaret: function () { + this.el.focus(); + var range = document.selection.createRange(); + range.moveStart('character', -this.el.value.length); + var arr = range.text.split(sentinelChar) + return arr.length === 1 ? arr[0] : arr[1]; + } + }); + + $.fn.textcomplete.IETextarea = IETextarea; +}(jQuery); + +// NOTE: TextComplete plugin has contenteditable support but it does not work +// fine especially on old IEs. +// Any pull requests are REALLY welcome. + ++function ($) { + 'use strict'; + + // ContentEditable adapter + // ======================= + // + // Adapter for contenteditable elements. + function ContentEditable (element, completer, option) { + this.initialize(element, completer, option); + } + + $.extend(ContentEditable.prototype, $.fn.textcomplete.Adapter.prototype, { + // Public methods + // -------------- + + // Update the content with the given value and strategy. + // When an dropdown item is selected, it is executed. + select: function (value, strategy) { + var pre = this.getTextFromHeadToCaret(); + var sel = window.getSelection() + var range = sel.getRangeAt(0); + var selection = range.cloneRange(); + selection.selectNodeContents(range.startContainer); + var content = selection.toString(); + var post = content.substring(range.startOffset); + var newSubstr = strategy.replace(value); + if ($.isArray(newSubstr)) { + post = newSubstr[1] + post; + newSubstr = newSubstr[0]; + } + pre = pre.replace(strategy.match, newSubstr); + range.selectNodeContents(range.startContainer); + range.deleteContents(); + var node = document.createTextNode(pre + post); + range.insertNode(node); + range.setStart(node, pre.length); + range.collapse(true); + sel.removeAllRanges(); + sel.addRange(range); + }, + + // Private methods + // --------------- + + // Returns the caret's relative position from the contenteditable's + // left top corner. + // + // Examples + // + // this._getCaretRelativePosition() + // //=> { top: 18, left: 200, lineHeight: 16 } + // + // Dropdown's position will be decided using the result. + _getCaretRelativePosition: function () { + var range = window.getSelection().getRangeAt(0).cloneRange(); + var node = document.createElement('span'); + range.insertNode(node); + range.selectNodeContents(node); + range.deleteContents(); + var $node = $(node); + var position = $node.offset(); + position.left -= this.$el.offset().left; + position.top += $node.height() - this.$el.offset().top; + position.lineHeight = $node.height(); + var dir = this.$el.attr('dir') || this.$el.css('direction'); + if (dir === 'rtl') { position.left -= this.listView.$el.width(); } + return position; + }, + + // Returns the string between the first character and the caret. + // Completer will be triggered with the result for start autocompleting. + // + // Example + // + // // Suppose the html is 'hello wor|ld' and | is the caret. + // this.getTextFromHeadToCaret() + // // => ' wor' // not 'hello wor' + getTextFromHeadToCaret: function () { + var range = window.getSelection().getRangeAt(0); + var selection = range.cloneRange(); + selection.selectNodeContents(range.startContainer); + return selection.toString().substring(0, range.startOffset); + } + }); + + $.fn.textcomplete.ContentEditable = ContentEditable; +}(jQuery); diff --git a/view/templates/display-head.tpl b/view/templates/display-head.tpl index 2050cdec0f..9a96a23988 100644 --- a/view/templates/display-head.tpl +++ b/view/templates/display-head.tpl @@ -1,9 +1,9 @@ diff --git a/view/templates/head.tpl b/view/templates/head.tpl index fdf9a7716e..deac208d18 100644 --- a/view/templates/head.tpl +++ b/view/templates/head.tpl @@ -28,20 +28,22 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + +EOT; $a->page['nav'] .= '' ; $nav_info = nav_info($a); From 52896d64c217356b8437d6f6777c7c2a2a64fdc3 Mon Sep 17 00:00:00 2001 From: rabuzarus <> Date: Thu, 21 Jan 2016 13:28:29 +0100 Subject: [PATCH 004/211] rework autocomplete: css work --- js/autocomplete.js | 171 ++++++++++++++++++- view/global.css | 57 ++++++- view/templates/msg-header.tpl | 2 +- view/templates/wallmsg-header.tpl | 2 +- view/theme/duepuntozero/style.css | 12 ++ view/theme/frost-mobile/js/main.js | 2 +- view/theme/frost-mobile/js/theme.js | 4 +- view/theme/frost-mobile/style.css | 58 +++++++ view/theme/frost-mobile/templates/end.tpl | 3 +- view/theme/frost/js/main.js | 2 +- view/theme/frost/js/theme.js | 6 +- view/theme/frost/style.css | 65 ++++++- view/theme/frost/templates/end.tpl | 3 +- view/theme/quattro/dark/style.css | 15 +- view/theme/quattro/green/style.css | 15 +- view/theme/quattro/lilac/style.css | 15 +- view/theme/quattro/quattro.less | 12 +- view/theme/smoothly/style.css | 13 +- view/theme/smoothly/templates/jot-header.tpl | 2 +- view/theme/vier/style.css | 21 ++- 20 files changed, 452 insertions(+), 28 deletions(-) diff --git a/js/autocomplete.js b/js/autocomplete.js index aa4494b714..1f7df011d3 100644 --- a/js/autocomplete.js +++ b/js/autocomplete.js @@ -1,10 +1,14 @@ /** - * Red people autocomplete + * Friendica people autocomplete * * require jQuery, jquery.textcomplete */ function contact_search(term, callback, backend_url, type) { + // Check if there is a conversation id to include the unkonwn contacts of the conversation + var conv_id = document.activeElement.id.match(/\d+$/); + + // Check if there is a cached result that contains the same information we would get with a full server-side search var bt = backend_url+type; if(!(bt in contact_search.cache)) contact_search.cache[bt] = {}; @@ -27,6 +31,9 @@ function contact_search(term, callback, backend_url, type) { type:type, }; + if(conv_id !== null) + postdata['conversation'] = conv_id[0]; + $.ajax({ type:'POST', @@ -54,7 +61,7 @@ function contact_format(item) { var desc = ((item.label) ? item.nick + ' ' + item.label : item.nick); if(typeof desc === 'undefined') desc = ''; if(desc) desc = ' ('+desc+')'; - return "
    {2}{3}
    ".format(item.taggable, item.photo, item.name, desc, item.link); + return "
    {2}{3}
    ".format(item.taggable, item.photo, item.name, desc, item.link); } else return "
    " + item.text + "
    "; @@ -190,3 +197,163 @@ function submit_form(e) { a.on('textComplete:select', function(e, value, strategy) { onselect(value); }); }; })( jQuery ); + + +/** + * Friendica people autocomplete legacy + * code which is needed for tinymce + * + * require jQuery, jquery.textareas + */ + +function ACPopup(elm,backend_url){ + this.idsel=-1; + this.element = elm; + this.searchText=""; + this.ready=true; + this.kp_timer = false; + this.url = backend_url; + + this.conversation_id = null; + var conv_id = this.element.id.match(/\d+$/); + if (conv_id) this.conversation_id = conv_id[0]; + console.log("ACPopup elm id",this.element.id,"conversation",this.conversation_id); + + var w = 530; + var h = 130; + + + if(tinyMCE.activeEditor == null) { + style = $(elm).offset(); + w = $(elm).width(); + h = $(elm).height(); + } + else { + // I can't find an "official" way to get the element who get all + // this fraking thing that is tinyMCE. + // This code will broke again at some point... + var container = $(tinyMCE.activeEditor.getContainer()).find("table"); + style = $(container).offset(); + w = $(container).width(); + h = $(container).height(); + } + + style.top=style.top+h; + style.width = w; + style.position = 'absolute'; + /* style['max-height'] = '150px'; + style.border = '1px solid red'; + style.background = '#cccccc'; + + style.overflow = 'auto'; + style['z-index'] = '100000'; + */ + style.display = 'none'; + + this.cont = $("
    "); + this.cont.css(style); + + $("body").append(this.cont); + } + +ACPopup.prototype.close = function(){ + $(this.cont).remove(); + this.ready=false; +} +ACPopup.prototype.search = function(text){ + var that = this; + this.searchText=text; + if (this.kp_timer) clearTimeout(this.kp_timer); + this.kp_timer = setTimeout( function(){that._search();}, 500); +} + +ACPopup.prototype._search = function(){ + console.log("_search"); + var that = this; + var postdata = { + start:0, + count:100, + search:this.searchText, + type:'c', + conversation: this.conversation_id, + } + + $.ajax({ + type:'POST', + url: this.url, + data: postdata, + dataType: 'json', + success:function(data){ + that.cont.html(""); + if (data.tot>0){ + that.cont.show(); + $(data.items).each(function(){ + var html = "{1} ({2})".format(this.photo, this.name, this.nick); + var nick = this.nick.replace(' ',''); + if (this.id!=='') nick += '+' + this.id; + that.add(html, nick + ' - ' + this.link); + }); + } else { + that.cont.hide(); + } + } + }); + +} + +ACPopup.prototype.add = function(label, value){ + var that=this; + var elm = $("
    "+label+"
    "); + elm.click(function(e){ + t = $(this).attr('title').replace(new RegExp(' \- .*'),''); + if(typeof(that.element.container) === "undefined") { + el=$(that.element); + sel = el.getSelection(); + sel.start = sel.start- that.searchText.length; + el.setSelection(sel.start,sel.end).replaceSelectedText(t+' ').collapseSelection(false); + that.close(); + } + else { + txt = tinyMCE.activeEditor.getContent(); + // alert(that.searchText + ':' + t); + newtxt = txt.replace('@' + that.searchText,'@' + t +' '); + tinyMCE.activeEditor.setContent(newtxt); + tinyMCE.activeEditor.focus(); + that.close(); + } + }); + $(this.cont).append(elm); +} + +ACPopup.prototype.onkey = function(event){ + if (event.keyCode == '13') { + if(this.idsel>-1) { + this.cont.children()[this.idsel].click(); + event.preventDefault(); + } + else + this.close(); + } + if (event.keyCode == '38') { //cursor up + cmax = this.cont.children().size()-1; + this.idsel--; + if (this.idsel<0) this.idsel=cmax; + event.preventDefault(); + } + if (event.keyCode == '40' || event.keyCode == '9') { //cursor down + cmax = this.cont.children().size()-1; + this.idsel++; + if (this.idsel>cmax) this.idsel=0; + event.preventDefault(); + } + + if (event.keyCode == '38' || event.keyCode == '40' || event.keyCode == '9') { + this.cont.children().removeClass('selected'); + $(this.cont.children()[this.idsel]).addClass('selected'); + } + + if (event.keyCode == '27') { //ESC + this.close(); + } +} + diff --git a/view/global.css b/view/global.css index 41af643ecc..0857cba27b 100644 --- a/view/global.css +++ b/view/global.css @@ -203,12 +203,67 @@ key { display: inline; background-color: #eee; color: #666; padding:0.2em; font- /* fields help text */ .field .field_help { - clear: left; + clear: left; } /* notifications unseen */ .notify-unseen { background-color: #cceeFF; } +/* autocomplete popup */ + +ul.acpopup { + list-style: none; + float: left; + min-width: 160px; + padding: 5px 0; + margin: 2px 0 0; +} +nav .acpopup { + width: 290px; + margin-left: -35px; + max-height: 450px; + max-width: 300px; + overflow-y: auto; + overflow-x: hidden; + margin-top: 0px; +} +img.acpopup-img { + float: left; + width: 36px; + height: 36px; + margin-right: 5px; + vertical-align: middle; +} +.acpopup-contactname { + padding-top: 2px; + font-weight: bold; + line-height: 1em; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + display: block; +} +.acpopup-sub-text { + color: #777; + font-size: 0.833em; + line-height: 1em; + overflow: hidden; + text-overflow: ellipsis; + display: block; +} +.textcomplete-item a { + color: inherit; + overflow: hidden; + text-overflow: ellipsis; + clear: both; + white-space: nowrap; + padding: 3px 20px; + display: block; +} +.textcomplete-item a:hover { + text-decoration: none; +} + /* plain text editor upload/select popup */ .fbrowser .path a { padding: 5px; } diff --git a/view/templates/msg-header.tpl b/view/templates/msg-header.tpl index 86598bbf6c..9b1a92ef5f 100644 --- a/view/templates/msg-header.tpl +++ b/view/templates/msg-header.tpl @@ -44,7 +44,7 @@ if(plaintext != 'none') { }); } else - $("#prvmail-text").contact_autocomplete(baseurl+"/acl"); + $("#prvmail-text").editor_autocomplete(baseurl+"/acl"); diff --git a/view/templates/wallmsg-header.tpl b/view/templates/wallmsg-header.tpl index f03f496fe2..2d4cd23797 100644 --- a/view/templates/wallmsg-header.tpl +++ b/view/templates/wallmsg-header.tpl @@ -44,7 +44,7 @@ if(plaintext != 'none') { }); } else - $("#prvmail-text").contact_autocomplete(baseurl+"/acl"); + $("#prvmail-text").editor_autocomplete(baseurl+"/acl"); diff --git a/view/theme/duepuntozero/style.css b/view/theme/duepuntozero/style.css index 787e52600c..53d034d865 100644 --- a/view/theme/duepuntozero/style.css +++ b/view/theme/duepuntozero/style.css @@ -3308,6 +3308,12 @@ aside input[type='text'] { /* autocomplete popup */ .acpopup { + background-color:#ffffff; + overflow:auto; + z-index:100000; + border:1px solid #cccccc; +} +.acpopup-mce { max-height:150px; background-color:#ffffff; overflow:auto; @@ -3326,6 +3332,12 @@ aside input[type='text'] { .acpopupitem.selected { color: #FFFFFF; background: #3465A4; } +.textcomplete-item.active { + color: #FFFFFF; background: #3465A4; +} +.active a .acpopup-sub-text { + color: #fff; +} /* popup notifications */ div.jGrowl div.notice { diff --git a/view/theme/frost-mobile/js/main.js b/view/theme/frost-mobile/js/main.js index 7e2880594d..3ec2421df2 100644 --- a/view/theme/frost-mobile/js/main.js +++ b/view/theme/frost-mobile/js/main.js @@ -388,7 +388,7 @@ $('body').css('cursor', 'auto'); } /* autocomplete @nicknames */ - $(".comment-edit-form textarea").contact_autocomplete(baseurl+"/acl"); + $(".comment-edit-form textarea").editor_autocomplete(baseurl+"/acl"); // setup videos, since VideoJS won't take care of any loaded via AJAX if(typeof videojs != 'undefined') videojs.autoSetup(); diff --git a/view/theme/frost-mobile/js/theme.js b/view/theme/frost-mobile/js/theme.js index 8133c602c8..c021370874 100644 --- a/view/theme/frost-mobile/js/theme.js +++ b/view/theme/frost-mobile/js/theme.js @@ -121,7 +121,7 @@ $(document).ready(function() { a.setOptions({ params: { type: 'a' }}); break; case 'display-head': - $(".comment-wwedit-wrapper textarea").contact_autocomplete(baseurl+"/acl"); + $(".comment-wwedit-wrapper textarea").editor_autocomplete(baseurl+"/acl"); break; default: break; @@ -286,7 +286,7 @@ function initEditor(cb){ if(plaintext == 'none') { // $("#profile-jot-text-loading").hide(); $("#profile-jot-text").css({ 'height': 200, 'color': '#000' }); - $("#profile-jot-text").contact_autocomplete(baseurl+"/acl"); + $("#profile-jot-text").editor_autocomplete(baseurl+"/acl"); editor = true; /* $("a#jot-perms-icon").colorbox({ 'inline' : true, diff --git a/view/theme/frost-mobile/style.css b/view/theme/frost-mobile/style.css index a99cc17a91..36b621b766 100644 --- a/view/theme/frost-mobile/style.css +++ b/view/theme/frost-mobile/style.css @@ -4210,6 +4210,64 @@ aside input[type='text'] { .acpopupitem.selected { color: #FFFFFF; background: #3465A4; } +ul.acpopup { + list-style: none; + float: left; + min-width: 160px; + padding: 5px 0; + margin: 2px 0 0; +} +nav .acpopup { + width: 290px; + margin-left: -35px; + max-height: 450px; + max-width: 300px; + overflow-y: auto; + overflow-x: hidden; + margin-top: 0px; +} +img.acpopup-img { + float: left; + width: 36px; + height: 36px; + margin-right: 5px; + vertical-align: middle; +} +.acpopup-contactname { + padding-top: 2px; + font-weight: bold; + line-height: 1em; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + display: block; +} +.acpopup-sub-text { + color: #777; + font-size: 0.833em; + line-height: 1em; + overflow: hidden; + text-overflow: ellipsis; + display: block; +} +.textcomplete-item a { + color: inherit; + overflow: hidden; + text-overflow: ellipsis; + clear: both; + white-space: nowrap; + padding: 3px 20px; + display: block; +} +.textcomplete-item a:hover { + text-decoration: none; +} +.textcomplete-item.active { + color: #FFFFFF; background: #3465A4; +} +.active a .acpopup-sub-text { + color: #fff; +} /* popup notifications */ div.jGrowl div.notice { diff --git a/view/theme/frost-mobile/templates/end.tpl b/view/theme/frost-mobile/templates/end.tpl index 8d4b26bdcb..c1acbfb2a6 100644 --- a/view/theme/frost-mobile/templates/end.tpl +++ b/view/theme/frost-mobile/templates/end.tpl @@ -7,7 +7,8 @@ - + + diff --git a/view/theme/frost/js/main.js b/view/theme/frost/js/main.js index 5483ad6bc3..733064b30d 100644 --- a/view/theme/frost/js/main.js +++ b/view/theme/frost/js/main.js @@ -402,7 +402,7 @@ $('body').css('cursor', 'auto'); } /* autocomplete @nicknames */ - $(".comment-edit-form textarea").contact_autocomplete(baseurl+"/acl"); + $(".comment-edit-form textarea").editor_autocomplete(baseurl+"/acl"); collapseHeight(); diff --git a/view/theme/frost/js/theme.js b/view/theme/frost/js/theme.js index a14a034bc1..4418c1f3c4 100644 --- a/view/theme/frost/js/theme.js +++ b/view/theme/frost/js/theme.js @@ -232,7 +232,7 @@ $(document).ready(function() { a.setOptions({ params: { type: 'a' }}); break; case 'display-head': - $(".comment-wwedit-wrapper textarea").contact_autocomplete(baseurl+"/acl"); + $(".comment-wwedit-wrapper textarea").editor_autocomplete(baseurl+"/acl"); break; default: break; @@ -587,7 +587,7 @@ function initEditor(cb){ plaintextFn : function() { $("#profile-jot-text-loading").hide(); $("#profile-jot-text").css({ 'height': 200, 'color': '#000' }); - $("#profile-jot-text").contact_autocomplete(baseurl+"/acl"); + $("#profile-jot-text").editor_autocomplete(baseurl+"/acl"); $(".jothidden").show(); if (typeof cb!="undefined") cb(); } @@ -660,7 +660,7 @@ function msgInitEditor() { }); }, plaintextFn : function() { - $("#prvmail-text").contact_autocomplete(baseurl+"/acl"); + $("#prvmail-text").editor_autocomplete(baseurl+"/acl"); } } InitMCEEditor(editorData); diff --git a/view/theme/frost/style.css b/view/theme/frost/style.css index 1054b55c11..82a89f93d2 100644 --- a/view/theme/frost/style.css +++ b/view/theme/frost/style.css @@ -4064,13 +4064,15 @@ aside input[type='text'] { /* autocomplete popup */ -.acpopup { - max-height:150px; +.acpopup, .acpopup-mce { background-color:#ffffff; overflow:auto; z-index:100000; border:1px solid #cccccc; } +.acpopup-mce { + max-height:150px; +} .acpopupitem { background-color:#ffffff; padding: 4px; clear:left; @@ -4084,6 +4086,65 @@ aside input[type='text'] { color: #FFFFFF; background: #3465A4; } +ul.acpopup { + list-style: none; + float: left; + min-width: 160px; + padding: 5px 0; + margin: 2px 0 0; +} +nav .acpopup { + width: 290px; + margin-left: -35px; + max-height: 450px; + max-width: 300px; + overflow-y: auto; + overflow-x: hidden; + margin-top: 0px; +} +img.acpopup-img { + float: left; + width: 36px; + height: 36px; + margin-right: 5px; + vertical-align: middle; +} +.acpopup-contactname { + padding-top: 2px; + font-weight: bold; + line-height: 1em; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + display: block; +} +.acpopup-sub-text { + color: #777; + font-size: 0.833em; + line-height: 1em; + overflow: hidden; + text-overflow: ellipsis; + display: block; +} +.textcomplete-item a { + color: inherit; + overflow: hidden; + text-overflow: ellipsis; + clear: both; + white-space: nowrap; + padding: 3px 20px; + display: block; +} +.textcomplete-item a:hover { + text-decoration: none; +} +.textcomplete-item.active { + color: #FFFFFF; background: #3465A4; +} +.active a .acpopup-sub-text { + color: #fff; +} + /* popup notifications */ div.jGrowl div.notice { background: #511919 url("../../../images/icons/48/notice.png") no-repeat 5px center; diff --git a/view/theme/frost/templates/end.tpl b/view/theme/frost/templates/end.tpl index 4242f80c81..e864a9498d 100644 --- a/view/theme/frost/templates/end.tpl +++ b/view/theme/frost/templates/end.tpl @@ -19,7 +19,8 @@ - + + diff --git a/view/theme/quattro/dark/style.css b/view/theme/quattro/dark/style.css index aed53fdac6..53864c12f5 100644 --- a/view/theme/quattro/dark/style.css +++ b/view/theme/quattro/dark/style.css @@ -744,8 +744,12 @@ ul.menu-popup .toolbar a:hover { } /* autocomplete popup */ .autocomplete, -.acpopup { +.acpopup-mce { max-height: 150px; +} +.autocomplete, +.acpopup-mce, +.acpopup { background-color: #ffffff; color: #2d2d2d; border: 1px solid #364e59; @@ -774,6 +778,15 @@ ul.menu-popup .toolbar a:hover { .acpopupitem.selected { background-color: #bdcdd4; } +.textcomplete-item { + color: #2d2d2d; +} +.textcomplete-item a:hover { + color: #2d2d2d; +} +.textcomplete-item.active { + background-color: #bdcdd4; +} #nav-notifications-menu { width: 400px; max-height: 550px; diff --git a/view/theme/quattro/green/style.css b/view/theme/quattro/green/style.css index 74ab5b9cd0..e099a31d6e 100644 --- a/view/theme/quattro/green/style.css +++ b/view/theme/quattro/green/style.css @@ -744,8 +744,12 @@ ul.menu-popup .toolbar a:hover { } /* autocomplete popup */ .autocomplete, -.acpopup { +.acpopup-mce { max-height: 150px; +} +.autocomplete, +.acpopup-mce, +.acpopup { background-color: #ffffff; color: #2d2d2d; border: 1px solid #364e59; @@ -774,6 +778,15 @@ ul.menu-popup .toolbar a:hover { .acpopupitem.selected { background-color: #ccff42; } +.textcomplete-item { + color: #2d2d2d; +} +.textcomplete-item a:hover { + color: #2d2d2d; +} +.textcomplete-item.active { + background-color: #ccff42; +} #nav-notifications-menu { width: 400px; max-height: 550px; diff --git a/view/theme/quattro/lilac/style.css b/view/theme/quattro/lilac/style.css index 327309fa5e..631b0233d6 100644 --- a/view/theme/quattro/lilac/style.css +++ b/view/theme/quattro/lilac/style.css @@ -744,8 +744,12 @@ ul.menu-popup .toolbar a:hover { } /* autocomplete popup */ .autocomplete, -.acpopup { +.acpopup-mce { max-height: 150px; +} +.autocomplete, +.acpopup-mce, +.acpopup { background-color: #ffffff; color: #2d2d2d; border: 1px solid #364e59; @@ -774,6 +778,15 @@ ul.menu-popup .toolbar a:hover { .acpopupitem.selected { background-color: #c0a3c7; } +.textcomplete-item { + color: #2d2d2d; +} +.textcomplete-item a:hover { + color: #2d2d2d; +} +.textcomplete-item.active { + background-color: #c0a3c7; +} #nav-notifications-menu { width: 400px; max-height: 550px; diff --git a/view/theme/quattro/quattro.less b/view/theme/quattro/quattro.less index d81aedf41a..5d25b0fb40 100644 --- a/view/theme/quattro/quattro.less +++ b/view/theme/quattro/quattro.less @@ -265,9 +265,10 @@ ul.menu-popup { } /* autocomplete popup */ +.autocomplete, .acpopup-mce { max-height:150px; } .autocomplete, +.acpopup-mce, .acpopup { - max-height:150px; background-color:@MenuBg; color: @Menu; border:1px solid @MenuBorder; @@ -291,6 +292,15 @@ ul.menu-popup { background-color: @MenuItemHoverBg; } } +.textcomplete-item { + color: @MenuItem; + a:hover{ + color: @MenuItem; + } + &.active{ + background-color: @MenuItemHoverBg; + } +} #nav-notifications-menu { diff --git a/view/theme/smoothly/style.css b/view/theme/smoothly/style.css index 87c7342c9d..e38a7ef6dc 100644 --- a/view/theme/smoothly/style.css +++ b/view/theme/smoothly/style.css @@ -4377,8 +4377,7 @@ a.active { } /* autocomplete popup */ -.acpopup { - max-height: 150px; +.acpopup, acpopup-mce { overflow: auto; z-index: 100000; color: #2e3436; @@ -4395,6 +4394,10 @@ a.active { -webkit-box-shadow: 0 0 8px #BDBDBD; } +.acpopup-mce { + max-height: 150px; +} + .acpopupitem { color: #2e3436; padding: 4px; @@ -4405,7 +4408,7 @@ a.active { margin-right: 4px; } -.acpopupitem.selected { +.acpopupitem.selected, .textcomplete-item.active { color: #efefef; background: -webkit-gradient( linear, left top, left bottom, color-stop(0.05, #1873a2), color-stop(1, #6da6c4) ); background: -moz-linear-gradient( center top, #1873a2 5%, #6da6c4 100% ); @@ -4414,6 +4417,10 @@ a.active { order-bottom: none; } +.textcomplete-item a:hover, .textcomplete-item a:hover .acpopup-sub-text, .textcomplete-item.active a .acpopup-sub-text { + color: #efefef; +} + .qcomment { opacity: 0.8; filter: alpha(opacity=0); diff --git a/view/theme/smoothly/templates/jot-header.tpl b/view/theme/smoothly/templates/jot-header.tpl index f096d25823..8b2666f0f3 100644 --- a/view/theme/smoothly/templates/jot-header.tpl +++ b/view/theme/smoothly/templates/jot-header.tpl @@ -12,7 +12,7 @@ function initEditor(cb){ if(plaintext == 'none') { $("#profile-jot-text-loading").hide(); $("#profile-jot-text").css({ 'height': 200, 'color': '#000' }); - $("#profile-jot-text").contact_autocomplete(baseurl+"/acl"); + $("#profile-jot-text").editor_autocomplete(baseurl+"/acl"); $(".jothidden").show(); editor = true; $("a#jot-perms-icon").colorbox({ diff --git a/view/theme/vier/style.css b/view/theme/vier/style.css index e08e103b8a..8ade8e2aa5 100644 --- a/view/theme/vier/style.css +++ b/view/theme/vier/style.css @@ -429,7 +429,7 @@ code { .sidebar-group-li:hover, #sidebar-new-group:hover, #sidebar-edit-groups:hover,#forum-widget-collapse:hover, #sidebar-ungrouped:hover, .side-link:hover, .nets-ul li:hover, #forumlist-sidebar li:hover, #forumlist-sidebar-right li:hover, .nets-all:hover, .saved-search-li:hover, li.tool:hover, .admin.link:hover, aside h4 a:hover, right_aside h4 a:hover, #message-new:hover, -#sidebar-photos-albums li:hover, .photos-upload-link:hover { +#sidebar-photos-albums li:hover, .photos-upload-link:hover, .textcomplete-item.active { /* background-color: #ddd; */ /* background-color: #e5e5e5; */ background-color: #F5F5F5; @@ -900,15 +900,18 @@ ul.menu-popup .empty { color: #9eabb0; } /* autocomplete popup */ -.acpopup { - max-height: 150px; + +.acpopup, .acpopup-mce { background-color: #ffffff; - color: #2d2d2d; border: 1px solid #MenuBorder; overflow: auto; z-index: 100000; box-shadow: 0px 5px 10px rgba(0, 0, 0, 0.7); } +acpopup-mce { + color: #2d2d2d; + max-height: 150px; +} .acpopupitem { color: #2d2d2d; padding: 4px; @@ -921,6 +924,16 @@ ul.menu-popup .empty { .acpopupitem.selected { background-color: #bdcdd4; } +.textcomplete-item { + float: none; +} +.textcomplete-item a { + color: #737373; +} +.textcomplete-item a:hover { + padding: 3px 20px; +} + #nav-notifications-menu { width: 400px; max-height: 550px; From 7ee0bca2bb7e592f05d0ae155a86f320fd9cc032 Mon Sep 17 00:00:00 2001 From: rabuzarus <> Date: Fri, 22 Jan 2016 17:47:48 +0100 Subject: [PATCH 005/211] rework autocomplete: new ac for poke, contacts, prv_messages --- include/acl_selectors.php | 43 +- library/jquery_ac/README | 4 - library/jquery_ac/friendica.complete.js | 395 ------------------ library/jquery_ac/friendica.complete.min.js | 10 - library/jquery_ac/jquery-1.3.2.min.js | 19 - library/jquery_ac/jquery.autocomplete-min.js | 11 - library/jquery_ac/jquery.autocomplete.js | 390 ----------------- library/jquery_ac/shadow.png | Bin 3403 -> 0 bytes library/jquery_ac/styles.css | 6 - view/templates/contacts-head.tpl | 27 +- view/templates/head.tpl | 1 - view/templates/message-head.tpl | 20 +- view/templates/poke_head.tpl | 20 +- view/theme/frost-mobile/js/theme.js | 26 +- .../frost-mobile/templates/contacts-end.tpl | 3 - .../frost-mobile/templates/message-end.tpl | 3 - view/theme/frost/js/theme.js | 26 +- view/theme/frost/templates/contacts-end.tpl | 3 - view/theme/frost/templates/message-end.tpl | 3 - 19 files changed, 63 insertions(+), 947 deletions(-) delete mode 100644 library/jquery_ac/README delete mode 100644 library/jquery_ac/friendica.complete.js delete mode 100644 library/jquery_ac/friendica.complete.min.js delete mode 100644 library/jquery_ac/jquery-1.3.2.min.js delete mode 100644 library/jquery_ac/jquery.autocomplete-min.js delete mode 100644 library/jquery_ac/jquery.autocomplete.js delete mode 100644 library/jquery_ac/shadow.png delete mode 100644 library/jquery_ac/styles.css diff --git a/include/acl_selectors.php b/include/acl_selectors.php index 19197981e0..d78393aeee 100644 --- a/include/acl_selectors.php +++ b/include/acl_selectors.php @@ -257,7 +257,7 @@ function prune_deadguys($arr) { if(! $arr) return $arr; $str = dbesc(implode(',',$arr)); - $r = q("select id from contact where id in ( " . $str . ") and blocked = 0 and pending = 0 and archive = 0 "); + $r = q("SELECT `id` FROM `contact` WHERE `id` IN ( " . $str . ") AND `blocked` = 0 AND `pending` = 0 AND `archive` = 0 "); if($r) { $ret = array(); foreach($r as $rr) @@ -399,7 +399,7 @@ function acl_lookup(&$a, $out_type = 'json') { $type = (x($_REQUEST,'type')?$_REQUEST['type']:""); $conv_id = (x($_REQUEST,'conversation')?$_REQUEST['conversation']:null); - // For use with jquery.autocomplete for private mail completion + // For use with jquery.textcomplete for private mail completion if(x($_REQUEST,'query') && strlen($_REQUEST['query'])) { if(! $type) @@ -428,6 +428,7 @@ function acl_lookup(&$a, $out_type = 'json') { $sql_extra2 .= " ".unavailable_networks(); + // autocomplete for editor mentions if ($type=='' || $type=='c'){ $r = q("SELECT COUNT(*) AS c FROM `contact` WHERE `uid` = %d AND `self` = 0 @@ -476,7 +477,7 @@ function acl_lookup(&$a, $out_type = 'json') { if ($type=='' || $type=='g'){ - $r = q("SELECT `group`.`id`, `group`.`name`, GROUP_CONCAT(DISTINCT `group_member`.`contact-id` SEPARATOR ',') as uids + $r = q("SELECT `group`.`id`, `group`.`name`, GROUP_CONCAT(DISTINCT `group_member`.`contact-id` SEPARATOR ',') AS uids FROM `group`,`group_member` WHERE `group`.`deleted` = 0 AND `group`.`uid` = %d AND `group_member`.`gid`=`group`.`id` @@ -505,7 +506,7 @@ function acl_lookup(&$a, $out_type = 'json') { if ($type==''){ - $r = q("SELECT `id`, `name`, `nick`, `micro`, `network`, `url`, `attag`, forum FROM `contact` + $r = q("SELECT `id`, `name`, `nick`, `micro`, `network`, `url`, `attag`, `forum` FROM `contact` WHERE `uid` = %d AND `self` = 0 AND `blocked` = 0 AND `pending` = 0 AND `archive` = 0 AND `notify` != '' AND NOT (`network` IN ('%s', '%s')) $sql_extra2 @@ -516,7 +517,7 @@ function acl_lookup(&$a, $out_type = 'json') { } elseif ($type=='c'){ - $r = q("SELECT `id`, `name`, `nick`, `micro`, `network`, `url`, `attag`, forum FROM `contact` + $r = q("SELECT `id`, `name`, `nick`, `micro`, `network`, `url`, `attag`, `forum` FROM `contact` WHERE `uid` = %d AND `self` = 0 AND `blocked` = 0 AND `pending` = 0 AND `archive` = 0 AND `notify` != '' AND NOT (`network` IN ('%s')) $sql_extra2 @@ -546,6 +547,7 @@ function acl_lookup(&$a, $out_type = 'json') { ); } elseif($type == 'x') { + // autocomplete for global contact search (e.g. navbar search) $r = navbar_complete($a); $contacts = array(); if($r) { @@ -569,25 +571,6 @@ function acl_lookup(&$a, $out_type = 'json') { $r = array(); - if($type == 'm' || $type == 'a') { - $x = array(); - $x['query'] = $search; - $x['photos'] = array(); - $x['links'] = array(); - $x['suggestions'] = array(); - $x['data'] = array(); - if(count($r)) { - foreach($r as $g) { - $x['photos'][] = proxy_url($g['micro'], false, PROXY_SIZE_MICRO); - $x['links'][] = $g['url']; - $x['suggestions'][] = htmlentities($g['name']); - $x['data'][] = intval($g['id']); - } - } - echo json_encode($x); - killme(); - } - if(count($r)) { foreach($r as $g){ $contacts[] = array( @@ -611,14 +594,10 @@ function acl_lookup(&$a, $out_type = 'json') { function _contact_link($i){ return dbesc($i['link']); } $known_contacts = array_map(_contact_link, $contacts); $unknow_contacts=array(); - $r = q("select - `author-avatar`,`author-name`,`author-link` - from item where parent=%d - and ( - `author-name` LIKE '%%%s%%' OR - `author-link` LIKE '%%%s%%' - ) and - `author-link` NOT IN ('%s') + $r = q("SELECT `author-avatar`,`author-name`,`author-link` + FROM `item` WHERE `parent` = %d + AND (`author-name` LIKE '%%%s%%' OR `author-link` LIKE '%%%s%%') + AND `author-link` NOT IN ('%s') GROUP BY `author-link` ORDER BY `author-name` ASC ", diff --git a/library/jquery_ac/README b/library/jquery_ac/README deleted file mode 100644 index 422e3d70b2..0000000000 --- a/library/jquery_ac/README +++ /dev/null @@ -1,4 +0,0 @@ -This is jquery.autocomplete from - -http://www.devbridge.com/projects/autocomplete/jquery/ - diff --git a/library/jquery_ac/friendica.complete.js b/library/jquery_ac/friendica.complete.js deleted file mode 100644 index 81eba564e1..0000000000 --- a/library/jquery_ac/friendica.complete.js +++ /dev/null @@ -1,395 +0,0 @@ -/** -* Ajax Autocomplete for jQuery, version 1.1.3 -* (c) 2010 Tomas Kirda -* -* Ajax Autocomplete for jQuery is freely distributable under the terms of an MIT-style license. -* For details, see the web site: http://www.devbridge.com/projects/autocomplete/jquery/ -* -* Last Review: 04/19/2010 -* Heavily modified for contact completion in Friendica (add photos, hover tips. etc.) 11-May-2012 mike@macgirvin.com -*/ - -/*jslint onevar: true, evil: true, nomen: true, eqeqeq: true, bitwise: true, regexp: true, newcap: true, immed: true */ -/*global window: true, document: true, clearInterval: true, setInterval: true, jQuery: true */ - -(function($) { - - var reEscape = new RegExp('(\\' + ['/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\'].join('|\\') + ')', 'g'); - - function fnFormatResult(value, data, currentValue) { - var pattern = '(' + currentValue.replace(reEscape, '\\$1') + ')'; - return value.replace(new RegExp(pattern, 'gi'), '$1<\/strong>'); - } - - function Autocomplete(el, options) { - this.el = $(el); - this.el.attr('autocomplete', 'off'); - this.suggestions = []; - this.data = []; - this.badQueries = []; - this.selectedIndex = -1; - this.currentValue = this.el.val(); - this.intervalId = 0; - this.cachedResponse = []; - this.onChangeInterval = null; - this.ignoreValueChange = false; - this.serviceUrl = options.serviceUrl; - this.isLocal = false; - this.options = { - autoSubmit: false, - minChars: 1, - maxHeight: 300, - deferRequestBy: 0, - width: 0, - highlight: true, - params: {}, - fnFormatResult: fnFormatResult, - delimiter: null, - zIndex: 9999 - }; - this.initialize(); - this.setOptions(options); - } - - $.fn.autocomplete = function(options) { - return new Autocomplete(this.get(0)||$(''), options); - }; - - - Autocomplete.prototype = { - - killerFn: null, - - initialize: function() { - - var me, uid, autocompleteElId; - me = this; - uid = Math.floor(Math.random()*0x100000).toString(16); - autocompleteElId = 'Autocomplete_' + uid; - - this.killerFn = function(e) { - if ($(e.target).parents('.autocomplete').size() === 0) { - me.killSuggestions(); - me.disableKillerFn(); - } - }; - - if (!this.options.width) { this.options.width = this.el.width(); } - this.mainContainerId = 'AutocompleteContainter_' + uid; - - $('
    ').appendTo('body'); - - this.container = $('#' + autocompleteElId); - this.fixPosition(); - if (window.opera) { - this.el.keypress(function(e) { me.onKeyPress(e); }); - } else { - this.el.keydown(function(e) { me.onKeyPress(e); }); - } - this.el.keyup(function(e) { me.onKeyUp(e); }); - this.el.blur(function() { me.enableKillerFn(); }); - this.el.focus(function() { me.fixPosition(); }); - }, - - setOptions: function(options){ - var o = this.options; - $.extend(o, options); - if(o.lookup){ - this.isLocal = true; - if($.isArray(o.lookup)){ o.lookup = { suggestions:o.lookup, data:[] }; } - } - $('#'+this.mainContainerId).css({ zIndex:o.zIndex }); - this.container.css({ maxHeight: o.maxHeight + 'px', width:o.width }); - }, - - clearCache: function(){ - this.cachedResponse = []; - this.badQueries = []; - }, - - disable: function(){ - this.disabled = true; - }, - - enable: function(){ - this.disabled = false; - }, - - fixPosition: function() { - var offset = this.el.offset(); - $('#' + this.mainContainerId).css({ top: (offset.top + this.el.innerHeight()) + 'px', left: offset.left + 'px' }); - }, - - enableKillerFn: function() { - var me = this; - $(document).bind('click', me.killerFn); - }, - - disableKillerFn: function() { - var me = this; - $(document).unbind('click', me.killerFn); - }, - - killSuggestions: function() { - var me = this; - this.stopKillSuggestions(); - this.intervalId = window.setInterval(function() { me.hide(); me.stopKillSuggestions(); }, 300); - }, - - stopKillSuggestions: function() { - window.clearInterval(this.intervalId); - }, - - onKeyPress: function(e) { - if (this.disabled || !this.enabled) { return; } - // return will exit the function - // and event will not be prevented - switch (e.keyCode) { - case 27: //KEY_ESC: - this.el.val(this.currentValue); - this.hide(); - break; - case 9: //KEY_TAB: - case 13: //KEY_RETURN: - if (this.selectedIndex === -1) { - this.hide(); - return; - } - this.select(this.selectedIndex); - if(e.keyCode === 9){ return; } - break; - case 38: //KEY_UP: - this.moveUp(); - break; - case 40: //KEY_DOWN: - this.moveDown(); - break; - default: - return; - } - e.stopImmediatePropagation(); - e.preventDefault(); - }, - - onKeyUp: function(e) { - if(this.disabled){ return; } - switch (e.keyCode) { - case 38: //KEY_UP: - case 40: //KEY_DOWN: - return; - } - clearInterval(this.onChangeInterval); - if (this.currentValue !== this.el.val()) { - if (this.options.deferRequestBy > 0) { - // Defer lookup in case when value changes very quickly: - var me = this; - this.onChangeInterval = setInterval(function() { me.onValueChange(); }, this.options.deferRequestBy); - } else { - this.onValueChange(); - } - } - }, - - onValueChange: function() { - clearInterval(this.onChangeInterval); - this.currentValue = this.el.val(); - var q = this.getQuery(this.currentValue); - this.selectedIndex = -1; - if (this.ignoreValueChange) { - this.ignoreValueChange = false; - return; - } - if (q === '' || q.length < this.options.minChars) { - this.hide(); - } else { - this.getSuggestions(q); - } - }, - - getQuery: function(val) { - var d, arr; - d = this.options.delimiter; - if (!d) { return $.trim(val); } - arr = val.split(d); - return $.trim(arr[arr.length - 1]); - }, - - getSuggestionsLocal: function(q) { - var ret, arr, len, val, i; - arr = this.options.lookup; - len = arr.suggestions.length; - ret = { suggestions:[], data:[] }; - q = q.toLowerCase(); - for(i=0; i< len; i++){ - val = arr.suggestions[i]; - if(val.toLowerCase().indexOf(q) === 0){ - ret.suggestions.push(val); - ret.data.push(arr.data[i]); - } - } - return ret; - }, - - getSuggestions: function(q) { - var cr, me; - cr = this.isLocal ? this.getSuggestionsLocal(q) : this.cachedResponse[q]; - if (cr && $.isArray(cr.suggestions)) { - this.suggestions = cr.suggestions; - this.data = cr.data; - this.suggest(); - } else if (!this.isBadQuery(q)) { - me = this; - me.options.params.query = q; - $.get(this.serviceUrl, me.options.params, function(txt) { me.processResponse(txt); }, 'text'); - } - }, - - isBadQuery: function(q) { - var i = this.badQueries.length; - while (i--) { - if (q.indexOf(this.badQueries[i]) === 0) { return true; } - } - return false; - }, - - hide: function() { - this.enabled = false; - this.selectedIndex = -1; - this.container.hide(); - }, - - suggest: function() { - if (this.suggestions.length === 0) { - this.hide(); - return; - } - - var me, len, div, f, v, i, s, mOver, mClick, l, img; - me = this; - len = this.suggestions.length; - f = this.options.fnFormatResult; - v = this.getQuery(this.currentValue); - mOver = function(xi) { return function() { me.activate(xi); }; }; - mClick = function(xi) { return function() { me.select(xi); }; }; - this.container.hide().empty(); - for (i = 0; i < len; i++) { - s = this.suggestions[i]; - l = this.links[i]; - img = '' + s + ' '; - div = $((me.selectedIndex === i ? '
    ' + img + f(s, this.data[i], v) + '
    '); - div.mouseover(mOver(i)); - div.click(mClick(i)); - this.container.append(div); - } - this.enabled = true; - this.container.show(); - }, - - processResponse: function(text) { - var response; - try { - response = eval('(' + text + ')'); - } catch (err) { return; } - if (!$.isArray(response.data)) { response.data = []; } - if(!this.options.noCache){ - this.cachedResponse[response.query] = response; - if (response.suggestions.length === 0) { this.badQueries.push(response.query); } - } - if (response.query === this.getQuery(this.currentValue)) { - this.photos = response.photos; - this.links = response.links; - this.suggestions = response.suggestions; - this.data = response.data; - this.suggest(); - } - }, - - activate: function(index) { - var divs, activeItem; - divs = this.container.children(); - // Clear previous selection: - if (this.selectedIndex !== -1 && divs.length > this.selectedIndex) { - $(divs.get(this.selectedIndex)).removeClass(); - } - this.selectedIndex = index; - if (this.selectedIndex !== -1 && divs.length > this.selectedIndex) { - activeItem = divs.get(this.selectedIndex); - $(activeItem).addClass('selected'); - } - return activeItem; - }, - - deactivate: function(div, index) { - div.className = ''; - if (this.selectedIndex === index) { this.selectedIndex = -1; } - }, - - select: function(i) { - var selectedValue, f; - selectedValue = this.suggestions[i]; - if (selectedValue) { - this.el.val(selectedValue); - if (this.options.autoSubmit) { - f = this.el.parents('form'); - if (f.length > 0) { f.get(0).submit(); } - } - this.ignoreValueChange = true; - this.hide(); - this.onSelect(i); - } - }, - - moveUp: function() { - if (this.selectedIndex === -1) { return; } - if (this.selectedIndex === 0) { - this.container.children().get(0).className = ''; - this.selectedIndex = -1; - this.el.val(this.currentValue); - return; - } - this.adjustScroll(this.selectedIndex - 1); - }, - - moveDown: function() { - if (this.selectedIndex === (this.suggestions.length - 1)) { return; } - this.adjustScroll(this.selectedIndex + 1); - }, - - adjustScroll: function(i) { - var activeItem, offsetTop, upperBound, lowerBound; - activeItem = this.activate(i); - offsetTop = activeItem.offsetTop; - upperBound = this.container.scrollTop(); - lowerBound = upperBound + this.options.maxHeight - 25; - if (offsetTop < upperBound) { - this.container.scrollTop(offsetTop); - } else if (offsetTop > lowerBound) { - this.container.scrollTop(offsetTop - this.options.maxHeight + 25); - } - this.el.val(this.getValue(this.suggestions[i])); - }, - - onSelect: function(i) { - var me, fn, s, d; - me = this; - fn = me.options.onSelect; - s = me.suggestions[i]; - d = me.data[i]; - me.el.val(me.getValue(s)); - if ($.isFunction(fn)) { fn(s, d, me.el); } - }, - - getValue: function(value){ - var del, currVal, arr, me; - me = this; - del = me.options.delimiter; - if (!del) { return value; } - currVal = me.currentValue; - arr = currVal.split(del); - if (arr.length === 1) { return value; } - return currVal.substr(0, currVal.length - arr[arr.length - 1].length) + value; - } - - }; - -}(jQuery)); diff --git a/library/jquery_ac/friendica.complete.min.js b/library/jquery_ac/friendica.complete.min.js deleted file mode 100644 index c879832eb4..0000000000 --- a/library/jquery_ac/friendica.complete.min.js +++ /dev/null @@ -1,10 +0,0 @@ -/** -* Ajax Autocomplete for jQuery, version 1.1.3 -* (c) 2010 Tomas Kirda -* -* Ajax Autocomplete for jQuery is freely distributable under the terms of an MIT-style license. -* For details, see the web site: http://www.devbridge.com/projects/autocomplete/jquery/ -* -* Last Review: 04/19/2010 -* Heavily modified for contact completion in Friendica (add photos, hover tips. etc.) 11-May-2012 mike@macgirvin.com -*//*jslint onevar: true, evil: true, nomen: true, eqeqeq: true, bitwise: true, regexp: true, newcap: true, immed: true *//*global window: true, document: true, clearInterval: true, setInterval: true, jQuery: true */(function($){function fnFormatResult(value,data,currentValue){var pattern="("+currentValue.replace(reEscape,"\\$1")+")";return value.replace(new RegExp(pattern,"gi"),"$1")}function Autocomplete(el,options){this.el=$(el),this.el.attr("autocomplete","off"),this.suggestions=[],this.data=[],this.badQueries=[],this.selectedIndex=-1,this.currentValue=this.el.val(),this.intervalId=0,this.cachedResponse=[],this.onChangeInterval=null,this.ignoreValueChange=!1,this.serviceUrl=options.serviceUrl,this.isLocal=!1,this.options={autoSubmit:!1,minChars:1,maxHeight:300,deferRequestBy:0,width:0,highlight:!0,params:{},fnFormatResult:fnFormatResult,delimiter:null,zIndex:9999},this.initialize(),this.setOptions(options)}var reEscape=new RegExp("(\\"+["/",".","*","+","?","|","(",")","[","]","{","}","\\"].join("|\\")+")","g");$.fn.autocomplete=function(options){return new Autocomplete(this.get(0)||$(""),options)},Autocomplete.prototype={killerFn:null,initialize:function(){var me,uid,autocompleteElId;me=this,uid=Math.floor(Math.random()*1048576).toString(16),autocompleteElId="Autocomplete_"+uid,this.killerFn=function(e){$(e.target).parents(".autocomplete").size()===0&&(me.killSuggestions(),me.disableKillerFn())},this.options.width||(this.options.width=this.el.width()),this.mainContainerId="AutocompleteContainter_"+uid,$('
    ').appendTo("body"),this.container=$("#"+autocompleteElId),this.fixPosition(),window.opera?this.el.keypress(function(e){me.onKeyPress(e)}):this.el.keydown(function(e){me.onKeyPress(e)}),this.el.keyup(function(e){me.onKeyUp(e)}),this.el.blur(function(){me.enableKillerFn()}),this.el.focus(function(){me.fixPosition()})},setOptions:function(options){var o=this.options;$.extend(o,options),o.lookup&&(this.isLocal=!0,$.isArray(o.lookup)&&(o.lookup={suggestions:o.lookup,data:[]})),$("#"+this.mainContainerId).css({zIndex:o.zIndex}),this.container.css({maxHeight:o.maxHeight+"px",width:o.width})},clearCache:function(){this.cachedResponse=[],this.badQueries=[]},disable:function(){this.disabled=!0},enable:function(){this.disabled=!1},fixPosition:function(){var offset=this.el.offset();$("#"+this.mainContainerId).css({top:offset.top+this.el.innerHeight()+"px",left:offset.left+"px"})},enableKillerFn:function(){var me=this;$(document).bind("click",me.killerFn)},disableKillerFn:function(){var me=this;$(document).unbind("click",me.killerFn)},killSuggestions:function(){var me=this;this.stopKillSuggestions(),this.intervalId=window.setInterval(function(){me.hide(),me.stopKillSuggestions()},300)},stopKillSuggestions:function(){window.clearInterval(this.intervalId)},onKeyPress:function(e){if(this.disabled||!this.enabled)return;switch(e.keyCode){case 27:this.el.val(this.currentValue),this.hide();break;case 9:case 13:if(this.selectedIndex===-1){this.hide();return}this.select(this.selectedIndex);if(e.keyCode===9)return;break;case 38:this.moveUp();break;case 40:this.moveDown();break;default:return}e.stopImmediatePropagation(),e.preventDefault()},onKeyUp:function(e){if(this.disabled)return;switch(e.keyCode){case 38:case 40:return}clearInterval(this.onChangeInterval);if(this.currentValue!==this.el.val())if(this.options.deferRequestBy>0){var me=this;this.onChangeInterval=setInterval(function(){me.onValueChange()},this.options.deferRequestBy)}else this.onValueChange()},onValueChange:function(){clearInterval(this.onChangeInterval),this.currentValue=this.el.val();var q=this.getQuery(this.currentValue);this.selectedIndex=-1;if(this.ignoreValueChange){this.ignoreValueChange=!1;return}q===""||q.length ',div=$((me.selectedIndex===i?'
    '+img+f(s,this.data[i],v)+"
    "),div.mouseover(mOver(i)),div.click(mClick(i)),this.container.append(div);this.enabled=!0,this.container.show()},processResponse:function(text){var response;try{response=eval("("+text+")")}catch(err){return}$.isArray(response.data)||(response.data=[]),this.options.noCache||(this.cachedResponse[response.query]=response,response.suggestions.length===0&&this.badQueries.push(response.query)),response.query===this.getQuery(this.currentValue)&&(this.photos=response.photos,this.links=response.links,this.suggestions=response.suggestions,this.data=response.data,this.suggest())},activate:function(index){var divs,activeItem;return divs=this.container.children(),this.selectedIndex!==-1&&divs.length>this.selectedIndex&&$(divs.get(this.selectedIndex)).removeClass(),this.selectedIndex=index,this.selectedIndex!==-1&&divs.length>this.selectedIndex&&(activeItem=divs.get(this.selectedIndex),$(activeItem).addClass("selected")),activeItem},deactivate:function(div,index){div.className="",this.selectedIndex===index&&(this.selectedIndex=-1)},select:function(i){var selectedValue,f;selectedValue=this.suggestions[i],selectedValue&&(this.el.val(selectedValue),this.options.autoSubmit&&(f=this.el.parents("form"),f.length>0&&f.get(0).submit()),this.ignoreValueChange=!0,this.hide(),this.onSelect(i))},moveUp:function(){if(this.selectedIndex===-1)return;if(this.selectedIndex===0){this.container.children().get(0).className="",this.selectedIndex=-1,this.el.val(this.currentValue);return}this.adjustScroll(this.selectedIndex-1)},moveDown:function(){if(this.selectedIndex===this.suggestions.length-1)return;this.adjustScroll(this.selectedIndex+1)},adjustScroll:function(i){var activeItem,offsetTop,upperBound,lowerBound;activeItem=this.activate(i),offsetTop=activeItem.offsetTop,upperBound=this.container.scrollTop(),lowerBound=upperBound+this.options.maxHeight-25,offsetToplowerBound&&this.container.scrollTop(offsetTop-this.options.maxHeight+25),this.el.val(this.getValue(this.suggestions[i]))},onSelect:function(i){var me,fn,s,d;me=this,fn=me.options.onSelect,s=me.suggestions[i],d=me.data[i],me.el.val(me.getValue(s)),$.isFunction(fn)&&fn(s,d,me.el)},getValue:function(value){var del,currVal,arr,me;return me=this,del=me.options.delimiter,del?(currVal=me.currentValue,arr=currVal.split(del),arr.length===1?value:currVal.substr(0,currVal.length-arr[arr.length-1].length)+value):value}}})(jQuery); \ No newline at end of file diff --git a/library/jquery_ac/jquery-1.3.2.min.js b/library/jquery_ac/jquery-1.3.2.min.js deleted file mode 100644 index b1ae21d8b2..0000000000 --- a/library/jquery_ac/jquery-1.3.2.min.js +++ /dev/null @@ -1,19 +0,0 @@ -/* - * jQuery JavaScript Library v1.3.2 - * http://jquery.com/ - * - * Copyright (c) 2009 John Resig - * Dual licensed under the MIT and GPL licenses. - * http://docs.jquery.com/License - * - * Date: 2009-02-19 17:34:21 -0500 (Thu, 19 Feb 2009) - * Revision: 6246 - */ -(function(){var l=this,g,y=l.jQuery,p=l.$,o=l.jQuery=l.$=function(E,F){return new o.fn.init(E,F)},D=/^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/,f=/^.[^:#\[\.,]*$/;o.fn=o.prototype={init:function(E,H){E=E||document;if(E.nodeType){this[0]=E;this.length=1;this.context=E;return this}if(typeof E==="string"){var G=D.exec(E);if(G&&(G[1]||!H)){if(G[1]){E=o.clean([G[1]],H)}else{var I=document.getElementById(G[3]);if(I&&I.id!=G[3]){return o().find(E)}var F=o(I||[]);F.context=document;F.selector=E;return F}}else{return o(H).find(E)}}else{if(o.isFunction(E)){return o(document).ready(E)}}if(E.selector&&E.context){this.selector=E.selector;this.context=E.context}return this.setArray(o.isArray(E)?E:o.makeArray(E))},selector:"",jquery:"1.3.2",size:function(){return this.length},get:function(E){return E===g?Array.prototype.slice.call(this):this[E]},pushStack:function(F,H,E){var G=o(F);G.prevObject=this;G.context=this.context;if(H==="find"){G.selector=this.selector+(this.selector?" ":"")+E}else{if(H){G.selector=this.selector+"."+H+"("+E+")"}}return G},setArray:function(E){this.length=0;Array.prototype.push.apply(this,E);return this},each:function(F,E){return o.each(this,F,E)},index:function(E){return o.inArray(E&&E.jquery?E[0]:E,this)},attr:function(F,H,G){var E=F;if(typeof F==="string"){if(H===g){return this[0]&&o[G||"attr"](this[0],F)}else{E={};E[F]=H}}return this.each(function(I){for(F in E){o.attr(G?this.style:this,F,o.prop(this,E[F],G,I,F))}})},css:function(E,F){if((E=="width"||E=="height")&&parseFloat(F)<0){F=g}return this.attr(E,F,"curCSS")},text:function(F){if(typeof F!=="object"&&F!=null){return this.empty().append((this[0]&&this[0].ownerDocument||document).createTextNode(F))}var E="";o.each(F||this,function(){o.each(this.childNodes,function(){if(this.nodeType!=8){E+=this.nodeType!=1?this.nodeValue:o.fn.text([this])}})});return E},wrapAll:function(E){if(this[0]){var F=o(E,this[0].ownerDocument).clone();if(this[0].parentNode){F.insertBefore(this[0])}F.map(function(){var G=this;while(G.firstChild){G=G.firstChild}return G}).append(this)}return this},wrapInner:function(E){return this.each(function(){o(this).contents().wrapAll(E)})},wrap:function(E){return this.each(function(){o(this).wrapAll(E)})},append:function(){return this.domManip(arguments,true,function(E){if(this.nodeType==1){this.appendChild(E)}})},prepend:function(){return this.domManip(arguments,true,function(E){if(this.nodeType==1){this.insertBefore(E,this.firstChild)}})},before:function(){return this.domManip(arguments,false,function(E){this.parentNode.insertBefore(E,this)})},after:function(){return this.domManip(arguments,false,function(E){this.parentNode.insertBefore(E,this.nextSibling)})},end:function(){return this.prevObject||o([])},push:[].push,sort:[].sort,splice:[].splice,find:function(E){if(this.length===1){var F=this.pushStack([],"find",E);F.length=0;o.find(E,this[0],F);return F}else{return this.pushStack(o.unique(o.map(this,function(G){return o.find(E,G)})),"find",E)}},clone:function(G){var E=this.map(function(){if(!o.support.noCloneEvent&&!o.isXMLDoc(this)){var I=this.outerHTML;if(!I){var J=this.ownerDocument.createElement("div");J.appendChild(this.cloneNode(true));I=J.innerHTML}return o.clean([I.replace(/ jQuery\d+="(?:\d+|null)"/g,"").replace(/^\s*/,"")])[0]}else{return this.cloneNode(true)}});if(G===true){var H=this.find("*").andSelf(),F=0;E.find("*").andSelf().each(function(){if(this.nodeName!==H[F].nodeName){return}var I=o.data(H[F],"events");for(var K in I){for(var J in I[K]){o.event.add(this,K,I[K][J],I[K][J].data)}}F++})}return E},filter:function(E){return this.pushStack(o.isFunction(E)&&o.grep(this,function(G,F){return E.call(G,F)})||o.multiFilter(E,o.grep(this,function(F){return F.nodeType===1})),"filter",E)},closest:function(E){var G=o.expr.match.POS.test(E)?o(E):null,F=0;return this.map(function(){var H=this;while(H&&H.ownerDocument){if(G?G.index(H)>-1:o(H).is(E)){o.data(H,"closest",F);return H}H=H.parentNode;F++}})},not:function(E){if(typeof E==="string"){if(f.test(E)){return this.pushStack(o.multiFilter(E,this,true),"not",E)}else{E=o.multiFilter(E,this)}}var F=E.length&&E[E.length-1]!==g&&!E.nodeType;return this.filter(function(){return F?o.inArray(this,E)<0:this!=E})},add:function(E){return this.pushStack(o.unique(o.merge(this.get(),typeof E==="string"?o(E):o.makeArray(E))))},is:function(E){return !!E&&o.multiFilter(E,this).length>0},hasClass:function(E){return !!E&&this.is("."+E)},val:function(K){if(K===g){var E=this[0];if(E){if(o.nodeName(E,"option")){return(E.attributes.value||{}).specified?E.value:E.text}if(o.nodeName(E,"select")){var I=E.selectedIndex,L=[],M=E.options,H=E.type=="select-one";if(I<0){return null}for(var F=H?I:0,J=H?I+1:M.length;F=0||o.inArray(this.name,K)>=0)}else{if(o.nodeName(this,"select")){var N=o.makeArray(K);o("option",this).each(function(){this.selected=(o.inArray(this.value,N)>=0||o.inArray(this.text,N)>=0)});if(!N.length){this.selectedIndex=-1}}else{this.value=K}}})},html:function(E){return E===g?(this[0]?this[0].innerHTML.replace(/ jQuery\d+="(?:\d+|null)"/g,""):null):this.empty().append(E)},replaceWith:function(E){return this.after(E).remove()},eq:function(E){return this.slice(E,+E+1)},slice:function(){return this.pushStack(Array.prototype.slice.apply(this,arguments),"slice",Array.prototype.slice.call(arguments).join(","))},map:function(E){return this.pushStack(o.map(this,function(G,F){return E.call(G,F,G)}))},andSelf:function(){return this.add(this.prevObject)},domManip:function(J,M,L){if(this[0]){var I=(this[0].ownerDocument||this[0]).createDocumentFragment(),F=o.clean(J,(this[0].ownerDocument||this[0]),I),H=I.firstChild;if(H){for(var G=0,E=this.length;G1||G>0?I.cloneNode(true):I)}}if(F){o.each(F,z)}}return this;function K(N,O){return M&&o.nodeName(N,"table")&&o.nodeName(O,"tr")?(N.getElementsByTagName("tbody")[0]||N.appendChild(N.ownerDocument.createElement("tbody"))):N}}};o.fn.init.prototype=o.fn;function z(E,F){if(F.src){o.ajax({url:F.src,async:false,dataType:"script"})}else{o.globalEval(F.text||F.textContent||F.innerHTML||"")}if(F.parentNode){F.parentNode.removeChild(F)}}function e(){return +new Date}o.extend=o.fn.extend=function(){var J=arguments[0]||{},H=1,I=arguments.length,E=false,G;if(typeof J==="boolean"){E=J;J=arguments[1]||{};H=2}if(typeof J!=="object"&&!o.isFunction(J)){J={}}if(I==H){J=this;--H}for(;H-1}},swap:function(H,G,I){var E={};for(var F in G){E[F]=H.style[F];H.style[F]=G[F]}I.call(H);for(var F in G){H.style[F]=E[F]}},css:function(H,F,J,E){if(F=="width"||F=="height"){var L,G={position:"absolute",visibility:"hidden",display:"block"},K=F=="width"?["Left","Right"]:["Top","Bottom"];function I(){L=F=="width"?H.offsetWidth:H.offsetHeight;if(E==="border"){return}o.each(K,function(){if(!E){L-=parseFloat(o.curCSS(H,"padding"+this,true))||0}if(E==="margin"){L+=parseFloat(o.curCSS(H,"margin"+this,true))||0}else{L-=parseFloat(o.curCSS(H,"border"+this+"Width",true))||0}})}if(H.offsetWidth!==0){I()}else{o.swap(H,G,I)}return Math.max(0,Math.round(L))}return o.curCSS(H,F,J)},curCSS:function(I,F,G){var L,E=I.style;if(F=="opacity"&&!o.support.opacity){L=o.attr(E,"opacity");return L==""?"1":L}if(F.match(/float/i)){F=w}if(!G&&E&&E[F]){L=E[F]}else{if(q.getComputedStyle){if(F.match(/float/i)){F="float"}F=F.replace(/([A-Z])/g,"-$1").toLowerCase();var M=q.getComputedStyle(I,null);if(M){L=M.getPropertyValue(F)}if(F=="opacity"&&L==""){L="1"}}else{if(I.currentStyle){var J=F.replace(/\-(\w)/g,function(N,O){return O.toUpperCase()});L=I.currentStyle[F]||I.currentStyle[J];if(!/^\d+(px)?$/i.test(L)&&/^\d/.test(L)){var H=E.left,K=I.runtimeStyle.left;I.runtimeStyle.left=I.currentStyle.left;E.left=L||0;L=E.pixelLeft+"px";E.left=H;I.runtimeStyle.left=K}}}}return L},clean:function(F,K,I){K=K||document;if(typeof K.createElement==="undefined"){K=K.ownerDocument||K[0]&&K[0].ownerDocument||document}if(!I&&F.length===1&&typeof F[0]==="string"){var H=/^<(\w+)\s*\/?>$/.exec(F[0]);if(H){return[K.createElement(H[1])]}}var G=[],E=[],L=K.createElement("div");o.each(F,function(P,S){if(typeof S==="number"){S+=""}if(!S){return}if(typeof S==="string"){S=S.replace(/(<(\w+)[^>]*?)\/>/g,function(U,V,T){return T.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i)?U:V+">"});var O=S.replace(/^\s+/,"").substring(0,10).toLowerCase();var Q=!O.indexOf("",""]||!O.indexOf("",""]||O.match(/^<(thead|tbody|tfoot|colg|cap)/)&&[1,"","
    "]||!O.indexOf("",""]||(!O.indexOf("",""]||!O.indexOf("",""]||!o.support.htmlSerialize&&[1,"div
    ","
    "]||[0,"",""];L.innerHTML=Q[1]+S+Q[2];while(Q[0]--){L=L.lastChild}if(!o.support.tbody){var R=/"&&!R?L.childNodes:[];for(var M=N.length-1;M>=0;--M){if(o.nodeName(N[M],"tbody")&&!N[M].childNodes.length){N[M].parentNode.removeChild(N[M])}}}if(!o.support.leadingWhitespace&&/^\s/.test(S)){L.insertBefore(K.createTextNode(S.match(/^\s*/)[0]),L.firstChild)}S=o.makeArray(L.childNodes)}if(S.nodeType){G.push(S)}else{G=o.merge(G,S)}});if(I){for(var J=0;G[J];J++){if(o.nodeName(G[J],"script")&&(!G[J].type||G[J].type.toLowerCase()==="text/javascript")){E.push(G[J].parentNode?G[J].parentNode.removeChild(G[J]):G[J])}else{if(G[J].nodeType===1){G.splice.apply(G,[J+1,0].concat(o.makeArray(G[J].getElementsByTagName("script"))))}I.appendChild(G[J])}}return E}return G},attr:function(J,G,K){if(!J||J.nodeType==3||J.nodeType==8){return g}var H=!o.isXMLDoc(J),L=K!==g;G=H&&o.props[G]||G;if(J.tagName){var F=/href|src|style/.test(G);if(G=="selected"&&J.parentNode){J.parentNode.selectedIndex}if(G in J&&H&&!F){if(L){if(G=="type"&&o.nodeName(J,"input")&&J.parentNode){throw"type property can't be changed"}J[G]=K}if(o.nodeName(J,"form")&&J.getAttributeNode(G)){return J.getAttributeNode(G).nodeValue}if(G=="tabIndex"){var I=J.getAttributeNode("tabIndex");return I&&I.specified?I.value:J.nodeName.match(/(button|input|object|select|textarea)/i)?0:J.nodeName.match(/^(a|area)$/i)&&J.href?0:g}return J[G]}if(!o.support.style&&H&&G=="style"){return o.attr(J.style,"cssText",K)}if(L){J.setAttribute(G,""+K)}var E=!o.support.hrefNormalized&&H&&F?J.getAttribute(G,2):J.getAttribute(G);return E===null?g:E}if(!o.support.opacity&&G=="opacity"){if(L){J.zoom=1;J.filter=(J.filter||"").replace(/alpha\([^)]*\)/,"")+(parseInt(K)+""=="NaN"?"":"alpha(opacity="+K*100+")")}return J.filter&&J.filter.indexOf("opacity=")>=0?(parseFloat(J.filter.match(/opacity=([^)]*)/)[1])/100)+"":""}G=G.replace(/-([a-z])/ig,function(M,N){return N.toUpperCase()});if(L){J[G]=K}return J[G]},trim:function(E){return(E||"").replace(/^\s+|\s+$/g,"")},makeArray:function(G){var E=[];if(G!=null){var F=G.length;if(F==null||typeof G==="string"||o.isFunction(G)||G.setInterval){E[0]=G}else{while(F){E[--F]=G[F]}}}return E},inArray:function(G,H){for(var E=0,F=H.length;E0?this.clone(true):this).get();o.fn[F].apply(o(L[K]),I);J=J.concat(I)}return this.pushStack(J,E,G)}});o.each({removeAttr:function(E){o.attr(this,E,"");if(this.nodeType==1){this.removeAttribute(E)}},addClass:function(E){o.className.add(this,E)},removeClass:function(E){o.className.remove(this,E)},toggleClass:function(F,E){if(typeof E!=="boolean"){E=!o.className.has(this,F)}o.className[E?"add":"remove"](this,F)},remove:function(E){if(!E||o.filter(E,[this]).length){o("*",this).add([this]).each(function(){o.event.remove(this);o.removeData(this)});if(this.parentNode){this.parentNode.removeChild(this)}}},empty:function(){o(this).children().remove();while(this.firstChild){this.removeChild(this.firstChild)}}},function(E,F){o.fn[E]=function(){return this.each(F,arguments)}});function j(E,F){return E[0]&&parseInt(o.curCSS(E[0],F,true),10)||0}var h="jQuery"+e(),v=0,A={};o.extend({cache:{},data:function(F,E,G){F=F==l?A:F;var H=F[h];if(!H){H=F[h]=++v}if(E&&!o.cache[H]){o.cache[H]={}}if(G!==g){o.cache[H][E]=G}return E?o.cache[H][E]:H},removeData:function(F,E){F=F==l?A:F;var H=F[h];if(E){if(o.cache[H]){delete o.cache[H][E];E="";for(E in o.cache[H]){break}if(!E){o.removeData(F)}}}else{try{delete F[h]}catch(G){if(F.removeAttribute){F.removeAttribute(h)}}delete o.cache[H]}},queue:function(F,E,H){if(F){E=(E||"fx")+"queue";var G=o.data(F,E);if(!G||o.isArray(H)){G=o.data(F,E,o.makeArray(H))}else{if(H){G.push(H)}}}return G},dequeue:function(H,G){var E=o.queue(H,G),F=E.shift();if(!G||G==="fx"){F=E[0]}if(F!==g){F.call(H)}}});o.fn.extend({data:function(E,G){var H=E.split(".");H[1]=H[1]?"."+H[1]:"";if(G===g){var F=this.triggerHandler("getData"+H[1]+"!",[H[0]]);if(F===g&&this.length){F=o.data(this[0],E)}return F===g&&H[1]?this.data(H[0]):F}else{return this.trigger("setData"+H[1]+"!",[H[0],G]).each(function(){o.data(this,E,G)})}},removeData:function(E){return this.each(function(){o.removeData(this,E)})},queue:function(E,F){if(typeof E!=="string"){F=E;E="fx"}if(F===g){return o.queue(this[0],E)}return this.each(function(){var G=o.queue(this,E,F);if(E=="fx"&&G.length==1){G[0].call(this)}})},dequeue:function(E){return this.each(function(){o.dequeue(this,E)})}}); -/* - * Sizzle CSS Selector Engine - v0.9.3 - * Copyright 2009, The Dojo Foundation - * Released under the MIT, BSD, and GPL Licenses. - * More information: http://sizzlejs.com/ - */ -(function(){var R=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?/g,L=0,H=Object.prototype.toString;var F=function(Y,U,ab,ac){ab=ab||[];U=U||document;if(U.nodeType!==1&&U.nodeType!==9){return[]}if(!Y||typeof Y!=="string"){return ab}var Z=[],W,af,ai,T,ad,V,X=true;R.lastIndex=0;while((W=R.exec(Y))!==null){Z.push(W[1]);if(W[2]){V=RegExp.rightContext;break}}if(Z.length>1&&M.exec(Y)){if(Z.length===2&&I.relative[Z[0]]){af=J(Z[0]+Z[1],U)}else{af=I.relative[Z[0]]?[U]:F(Z.shift(),U);while(Z.length){Y=Z.shift();if(I.relative[Y]){Y+=Z.shift()}af=J(Y,af)}}}else{var ae=ac?{expr:Z.pop(),set:E(ac)}:F.find(Z.pop(),Z.length===1&&U.parentNode?U.parentNode:U,Q(U));af=F.filter(ae.expr,ae.set);if(Z.length>0){ai=E(af)}else{X=false}while(Z.length){var ah=Z.pop(),ag=ah;if(!I.relative[ah]){ah=""}else{ag=Z.pop()}if(ag==null){ag=U}I.relative[ah](ai,ag,Q(U))}}if(!ai){ai=af}if(!ai){throw"Syntax error, unrecognized expression: "+(ah||Y)}if(H.call(ai)==="[object Array]"){if(!X){ab.push.apply(ab,ai)}else{if(U.nodeType===1){for(var aa=0;ai[aa]!=null;aa++){if(ai[aa]&&(ai[aa]===true||ai[aa].nodeType===1&&K(U,ai[aa]))){ab.push(af[aa])}}}else{for(var aa=0;ai[aa]!=null;aa++){if(ai[aa]&&ai[aa].nodeType===1){ab.push(af[aa])}}}}}else{E(ai,ab)}if(V){F(V,U,ab,ac);if(G){hasDuplicate=false;ab.sort(G);if(hasDuplicate){for(var aa=1;aa":function(Z,U,aa){var X=typeof U==="string";if(X&&!/\W/.test(U)){U=aa?U:U.toUpperCase();for(var V=0,T=Z.length;V=0)){if(!V){T.push(Y)}}else{if(V){U[X]=false}}}}return false},ID:function(T){return T[1].replace(/\\/g,"")},TAG:function(U,T){for(var V=0;T[V]===false;V++){}return T[V]&&Q(T[V])?U[1]:U[1].toUpperCase()},CHILD:function(T){if(T[1]=="nth"){var U=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(T[2]=="even"&&"2n"||T[2]=="odd"&&"2n+1"||!/\D/.test(T[2])&&"0n+"+T[2]||T[2]);T[2]=(U[1]+(U[2]||1))-0;T[3]=U[3]-0}T[0]=L++;return T},ATTR:function(X,U,V,T,Y,Z){var W=X[1].replace(/\\/g,"");if(!Z&&I.attrMap[W]){X[1]=I.attrMap[W]}if(X[2]==="~="){X[4]=" "+X[4]+" "}return X},PSEUDO:function(X,U,V,T,Y){if(X[1]==="not"){if(X[3].match(R).length>1||/^\w/.test(X[3])){X[3]=F(X[3],null,null,U)}else{var W=F.filter(X[3],U,V,true^Y);if(!V){T.push.apply(T,W)}return false}}else{if(I.match.POS.test(X[0])||I.match.CHILD.test(X[0])){return true}}return X},POS:function(T){T.unshift(true);return T}},filters:{enabled:function(T){return T.disabled===false&&T.type!=="hidden"},disabled:function(T){return T.disabled===true},checked:function(T){return T.checked===true},selected:function(T){T.parentNode.selectedIndex;return T.selected===true},parent:function(T){return !!T.firstChild},empty:function(T){return !T.firstChild},has:function(V,U,T){return !!F(T[3],V).length},header:function(T){return/h\d/i.test(T.nodeName)},text:function(T){return"text"===T.type},radio:function(T){return"radio"===T.type},checkbox:function(T){return"checkbox"===T.type},file:function(T){return"file"===T.type},password:function(T){return"password"===T.type},submit:function(T){return"submit"===T.type},image:function(T){return"image"===T.type},reset:function(T){return"reset"===T.type},button:function(T){return"button"===T.type||T.nodeName.toUpperCase()==="BUTTON"},input:function(T){return/input|select|textarea|button/i.test(T.nodeName)}},setFilters:{first:function(U,T){return T===0},last:function(V,U,T,W){return U===W.length-1},even:function(U,T){return T%2===0},odd:function(U,T){return T%2===1},lt:function(V,U,T){return UT[3]-0},nth:function(V,U,T){return T[3]-0==U},eq:function(V,U,T){return T[3]-0==U}},filter:{PSEUDO:function(Z,V,W,aa){var U=V[1],X=I.filters[U];if(X){return X(Z,W,V,aa)}else{if(U==="contains"){return(Z.textContent||Z.innerText||"").indexOf(V[3])>=0}else{if(U==="not"){var Y=V[3];for(var W=0,T=Y.length;W=0)}}},ID:function(U,T){return U.nodeType===1&&U.getAttribute("id")===T},TAG:function(U,T){return(T==="*"&&U.nodeType===1)||U.nodeName===T},CLASS:function(U,T){return(" "+(U.className||U.getAttribute("class"))+" ").indexOf(T)>-1},ATTR:function(Y,W){var V=W[1],T=I.attrHandle[V]?I.attrHandle[V](Y):Y[V]!=null?Y[V]:Y.getAttribute(V),Z=T+"",X=W[2],U=W[4];return T==null?X==="!=":X==="="?Z===U:X==="*="?Z.indexOf(U)>=0:X==="~="?(" "+Z+" ").indexOf(U)>=0:!U?Z&&T!==false:X==="!="?Z!=U:X==="^="?Z.indexOf(U)===0:X==="$="?Z.substr(Z.length-U.length)===U:X==="|="?Z===U||Z.substr(0,U.length+1)===U+"-":false},POS:function(X,U,V,Y){var T=U[2],W=I.setFilters[T];if(W){return W(X,V,U,Y)}}}};var M=I.match.POS;for(var O in I.match){I.match[O]=RegExp(I.match[O].source+/(?![^\[]*\])(?![^\(]*\))/.source)}var E=function(U,T){U=Array.prototype.slice.call(U);if(T){T.push.apply(T,U);return T}return U};try{Array.prototype.slice.call(document.documentElement.childNodes)}catch(N){E=function(X,W){var U=W||[];if(H.call(X)==="[object Array]"){Array.prototype.push.apply(U,X)}else{if(typeof X.length==="number"){for(var V=0,T=X.length;V";var T=document.documentElement;T.insertBefore(U,T.firstChild);if(!!document.getElementById(V)){I.find.ID=function(X,Y,Z){if(typeof Y.getElementById!=="undefined"&&!Z){var W=Y.getElementById(X[1]);return W?W.id===X[1]||typeof W.getAttributeNode!=="undefined"&&W.getAttributeNode("id").nodeValue===X[1]?[W]:g:[]}};I.filter.ID=function(Y,W){var X=typeof Y.getAttributeNode!=="undefined"&&Y.getAttributeNode("id");return Y.nodeType===1&&X&&X.nodeValue===W}}T.removeChild(U)})();(function(){var T=document.createElement("div");T.appendChild(document.createComment(""));if(T.getElementsByTagName("*").length>0){I.find.TAG=function(U,Y){var X=Y.getElementsByTagName(U[1]);if(U[1]==="*"){var W=[];for(var V=0;X[V];V++){if(X[V].nodeType===1){W.push(X[V])}}X=W}return X}}T.innerHTML="";if(T.firstChild&&typeof T.firstChild.getAttribute!=="undefined"&&T.firstChild.getAttribute("href")!=="#"){I.attrHandle.href=function(U){return U.getAttribute("href",2)}}})();if(document.querySelectorAll){(function(){var T=F,U=document.createElement("div");U.innerHTML="

    ";if(U.querySelectorAll&&U.querySelectorAll(".TEST").length===0){return}F=function(Y,X,V,W){X=X||document;if(!W&&X.nodeType===9&&!Q(X)){try{return E(X.querySelectorAll(Y),V)}catch(Z){}}return T(Y,X,V,W)};F.find=T.find;F.filter=T.filter;F.selectors=T.selectors;F.matches=T.matches})()}if(document.getElementsByClassName&&document.documentElement.getElementsByClassName){(function(){var T=document.createElement("div");T.innerHTML="
    ";if(T.getElementsByClassName("e").length===0){return}T.lastChild.className="e";if(T.getElementsByClassName("e").length===1){return}I.order.splice(1,0,"CLASS");I.find.CLASS=function(U,V,W){if(typeof V.getElementsByClassName!=="undefined"&&!W){return V.getElementsByClassName(U[1])}}})()}function P(U,Z,Y,ad,aa,ac){var ab=U=="previousSibling"&&!ac;for(var W=0,V=ad.length;W0){X=T;break}}}T=T[U]}ad[W]=X}}}var K=document.compareDocumentPosition?function(U,T){return U.compareDocumentPosition(T)&16}:function(U,T){return U!==T&&(U.contains?U.contains(T):true)};var Q=function(T){return T.nodeType===9&&T.documentElement.nodeName!=="HTML"||!!T.ownerDocument&&Q(T.ownerDocument)};var J=function(T,aa){var W=[],X="",Y,V=aa.nodeType?[aa]:aa;while((Y=I.match.PSEUDO.exec(T))){X+=Y[0];T=T.replace(I.match.PSEUDO,"")}T=I.relative[T]?T+"*":T;for(var Z=0,U=V.length;Z0||T.offsetHeight>0};F.selectors.filters.animated=function(T){return o.grep(o.timers,function(U){return T===U.elem}).length};o.multiFilter=function(V,T,U){if(U){V=":not("+V+")"}return F.matches(V,T)};o.dir=function(V,U){var T=[],W=V[U];while(W&&W!=document){if(W.nodeType==1){T.push(W)}W=W[U]}return T};o.nth=function(X,T,V,W){T=T||1;var U=0;for(;X;X=X[V]){if(X.nodeType==1&&++U==T){break}}return X};o.sibling=function(V,U){var T=[];for(;V;V=V.nextSibling){if(V.nodeType==1&&V!=U){T.push(V)}}return T};return;l.Sizzle=F})();o.event={add:function(I,F,H,K){if(I.nodeType==3||I.nodeType==8){return}if(I.setInterval&&I!=l){I=l}if(!H.guid){H.guid=this.guid++}if(K!==g){var G=H;H=this.proxy(G);H.data=K}var E=o.data(I,"events")||o.data(I,"events",{}),J=o.data(I,"handle")||o.data(I,"handle",function(){return typeof o!=="undefined"&&!o.event.triggered?o.event.handle.apply(arguments.callee.elem,arguments):g});J.elem=I;o.each(F.split(/\s+/),function(M,N){var O=N.split(".");N=O.shift();H.type=O.slice().sort().join(".");var L=E[N];if(o.event.specialAll[N]){o.event.specialAll[N].setup.call(I,K,O)}if(!L){L=E[N]={};if(!o.event.special[N]||o.event.special[N].setup.call(I,K,O)===false){if(I.addEventListener){I.addEventListener(N,J,false)}else{if(I.attachEvent){I.attachEvent("on"+N,J)}}}}L[H.guid]=H;o.event.global[N]=true});I=null},guid:1,global:{},remove:function(K,H,J){if(K.nodeType==3||K.nodeType==8){return}var G=o.data(K,"events"),F,E;if(G){if(H===g||(typeof H==="string"&&H.charAt(0)==".")){for(var I in G){this.remove(K,I+(H||""))}}else{if(H.type){J=H.handler;H=H.type}o.each(H.split(/\s+/),function(M,O){var Q=O.split(".");O=Q.shift();var N=RegExp("(^|\\.)"+Q.slice().sort().join(".*\\.")+"(\\.|$)");if(G[O]){if(J){delete G[O][J.guid]}else{for(var P in G[O]){if(N.test(G[O][P].type)){delete G[O][P]}}}if(o.event.specialAll[O]){o.event.specialAll[O].teardown.call(K,Q)}for(F in G[O]){break}if(!F){if(!o.event.special[O]||o.event.special[O].teardown.call(K,Q)===false){if(K.removeEventListener){K.removeEventListener(O,o.data(K,"handle"),false)}else{if(K.detachEvent){K.detachEvent("on"+O,o.data(K,"handle"))}}}F=null;delete G[O]}}})}for(F in G){break}if(!F){var L=o.data(K,"handle");if(L){L.elem=null}o.removeData(K,"events");o.removeData(K,"handle")}}},trigger:function(I,K,H,E){var G=I.type||I;if(!E){I=typeof I==="object"?I[h]?I:o.extend(o.Event(G),I):o.Event(G);if(G.indexOf("!")>=0){I.type=G=G.slice(0,-1);I.exclusive=true}if(!H){I.stopPropagation();if(this.global[G]){o.each(o.cache,function(){if(this.events&&this.events[G]){o.event.trigger(I,K,this.handle.elem)}})}}if(!H||H.nodeType==3||H.nodeType==8){return g}I.result=g;I.target=H;K=o.makeArray(K);K.unshift(I)}I.currentTarget=H;var J=o.data(H,"handle");if(J){J.apply(H,K)}if((!H[G]||(o.nodeName(H,"a")&&G=="click"))&&H["on"+G]&&H["on"+G].apply(H,K)===false){I.result=false}if(!E&&H[G]&&!I.isDefaultPrevented()&&!(o.nodeName(H,"a")&&G=="click")){this.triggered=true;try{H[G]()}catch(L){}}this.triggered=false;if(!I.isPropagationStopped()){var F=H.parentNode||H.ownerDocument;if(F){o.event.trigger(I,K,F,true)}}},handle:function(K){var J,E;K=arguments[0]=o.event.fix(K||l.event);K.currentTarget=this;var L=K.type.split(".");K.type=L.shift();J=!L.length&&!K.exclusive;var I=RegExp("(^|\\.)"+L.slice().sort().join(".*\\.")+"(\\.|$)");E=(o.data(this,"events")||{})[K.type];for(var G in E){var H=E[G];if(J||I.test(H.type)){K.handler=H;K.data=H.data;var F=H.apply(this,arguments);if(F!==g){K.result=F;if(F===false){K.preventDefault();K.stopPropagation()}}if(K.isImmediatePropagationStopped()){break}}}},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),fix:function(H){if(H[h]){return H}var F=H;H=o.Event(F);for(var G=this.props.length,J;G;){J=this.props[--G];H[J]=F[J]}if(!H.target){H.target=H.srcElement||document}if(H.target.nodeType==3){H.target=H.target.parentNode}if(!H.relatedTarget&&H.fromElement){H.relatedTarget=H.fromElement==H.target?H.toElement:H.fromElement}if(H.pageX==null&&H.clientX!=null){var I=document.documentElement,E=document.body;H.pageX=H.clientX+(I&&I.scrollLeft||E&&E.scrollLeft||0)-(I.clientLeft||0);H.pageY=H.clientY+(I&&I.scrollTop||E&&E.scrollTop||0)-(I.clientTop||0)}if(!H.which&&((H.charCode||H.charCode===0)?H.charCode:H.keyCode)){H.which=H.charCode||H.keyCode}if(!H.metaKey&&H.ctrlKey){H.metaKey=H.ctrlKey}if(!H.which&&H.button){H.which=(H.button&1?1:(H.button&2?3:(H.button&4?2:0)))}return H},proxy:function(F,E){E=E||function(){return F.apply(this,arguments)};E.guid=F.guid=F.guid||E.guid||this.guid++;return E},special:{ready:{setup:B,teardown:function(){}}},specialAll:{live:{setup:function(E,F){o.event.add(this,F[0],c)},teardown:function(G){if(G.length){var E=0,F=RegExp("(^|\\.)"+G[0]+"(\\.|$)");o.each((o.data(this,"events").live||{}),function(){if(F.test(this.type)){E++}});if(E<1){o.event.remove(this,G[0],c)}}}}}};o.Event=function(E){if(!this.preventDefault){return new o.Event(E)}if(E&&E.type){this.originalEvent=E;this.type=E.type}else{this.type=E}this.timeStamp=e();this[h]=true};function k(){return false}function u(){return true}o.Event.prototype={preventDefault:function(){this.isDefaultPrevented=u;var E=this.originalEvent;if(!E){return}if(E.preventDefault){E.preventDefault()}E.returnValue=false},stopPropagation:function(){this.isPropagationStopped=u;var E=this.originalEvent;if(!E){return}if(E.stopPropagation){E.stopPropagation()}E.cancelBubble=true},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=u;this.stopPropagation()},isDefaultPrevented:k,isPropagationStopped:k,isImmediatePropagationStopped:k};var a=function(F){var E=F.relatedTarget;while(E&&E!=this){try{E=E.parentNode}catch(G){E=this}}if(E!=this){F.type=F.data;o.event.handle.apply(this,arguments)}};o.each({mouseover:"mouseenter",mouseout:"mouseleave"},function(F,E){o.event.special[E]={setup:function(){o.event.add(this,F,a,E)},teardown:function(){o.event.remove(this,F,a)}}});o.fn.extend({bind:function(F,G,E){return F=="unload"?this.one(F,G,E):this.each(function(){o.event.add(this,F,E||G,E&&G)})},one:function(G,H,F){var E=o.event.proxy(F||H,function(I){o(this).unbind(I,E);return(F||H).apply(this,arguments)});return this.each(function(){o.event.add(this,G,E,F&&H)})},unbind:function(F,E){return this.each(function(){o.event.remove(this,F,E)})},trigger:function(E,F){return this.each(function(){o.event.trigger(E,F,this)})},triggerHandler:function(E,G){if(this[0]){var F=o.Event(E);F.preventDefault();F.stopPropagation();o.event.trigger(F,G,this[0]);return F.result}},toggle:function(G){var E=arguments,F=1;while(F=0){var E=G.slice(I,G.length);G=G.slice(0,I)}var H="GET";if(J){if(o.isFunction(J)){K=J;J=null}else{if(typeof J==="object"){J=o.param(J);H="POST"}}}var F=this;o.ajax({url:G,type:H,dataType:"html",data:J,complete:function(M,L){if(L=="success"||L=="notmodified"){F.html(E?o("
    ").append(M.responseText.replace(//g,"")).find(E):M.responseText)}if(K){F.each(K,[M.responseText,L,M])}}});return this},serialize:function(){return o.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?o.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||/select|textarea/i.test(this.nodeName)||/text|hidden|password|search/i.test(this.type))}).map(function(E,F){var G=o(this).val();return G==null?null:o.isArray(G)?o.map(G,function(I,H){return{name:F.name,value:I}}):{name:F.name,value:G}}).get()}});o.each("ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","),function(E,F){o.fn[F]=function(G){return this.bind(F,G)}});var r=e();o.extend({get:function(E,G,H,F){if(o.isFunction(G)){H=G;G=null}return o.ajax({type:"GET",url:E,data:G,success:H,dataType:F})},getScript:function(E,F){return o.get(E,null,F,"script")},getJSON:function(E,F,G){return o.get(E,F,G,"json")},post:function(E,G,H,F){if(o.isFunction(G)){H=G;G={}}return o.ajax({type:"POST",url:E,data:G,success:H,dataType:F})},ajaxSetup:function(E){o.extend(o.ajaxSettings,E)},ajaxSettings:{url:location.href,global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:function(){return l.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):new XMLHttpRequest()},accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},ajax:function(M){M=o.extend(true,M,o.extend(true,{},o.ajaxSettings,M));var W,F=/=\?(&|$)/g,R,V,G=M.type.toUpperCase();if(M.data&&M.processData&&typeof M.data!=="string"){M.data=o.param(M.data)}if(M.dataType=="jsonp"){if(G=="GET"){if(!M.url.match(F)){M.url+=(M.url.match(/\?/)?"&":"?")+(M.jsonp||"callback")+"=?"}}else{if(!M.data||!M.data.match(F)){M.data=(M.data?M.data+"&":"")+(M.jsonp||"callback")+"=?"}}M.dataType="json"}if(M.dataType=="json"&&(M.data&&M.data.match(F)||M.url.match(F))){W="jsonp"+r++;if(M.data){M.data=(M.data+"").replace(F,"="+W+"$1")}M.url=M.url.replace(F,"="+W+"$1");M.dataType="script";l[W]=function(X){V=X;I();L();l[W]=g;try{delete l[W]}catch(Y){}if(H){H.removeChild(T)}}}if(M.dataType=="script"&&M.cache==null){M.cache=false}if(M.cache===false&&G=="GET"){var E=e();var U=M.url.replace(/(\?|&)_=.*?(&|$)/,"$1_="+E+"$2");M.url=U+((U==M.url)?(M.url.match(/\?/)?"&":"?")+"_="+E:"")}if(M.data&&G=="GET"){M.url+=(M.url.match(/\?/)?"&":"?")+M.data;M.data=null}if(M.global&&!o.active++){o.event.trigger("ajaxStart")}var Q=/^(\w+:)?\/\/([^\/?#]+)/.exec(M.url);if(M.dataType=="script"&&G=="GET"&&Q&&(Q[1]&&Q[1]!=location.protocol||Q[2]!=location.host)){var H=document.getElementsByTagName("head")[0];var T=document.createElement("script");T.src=M.url;if(M.scriptCharset){T.charset=M.scriptCharset}if(!W){var O=false;T.onload=T.onreadystatechange=function(){if(!O&&(!this.readyState||this.readyState=="loaded"||this.readyState=="complete")){O=true;I();L();T.onload=T.onreadystatechange=null;H.removeChild(T)}}}H.appendChild(T);return g}var K=false;var J=M.xhr();if(M.username){J.open(G,M.url,M.async,M.username,M.password)}else{J.open(G,M.url,M.async)}try{if(M.data){J.setRequestHeader("Content-Type",M.contentType)}if(M.ifModified){J.setRequestHeader("If-Modified-Since",o.lastModified[M.url]||"Thu, 01 Jan 1970 00:00:00 GMT")}J.setRequestHeader("X-Requested-With","XMLHttpRequest");J.setRequestHeader("Accept",M.dataType&&M.accepts[M.dataType]?M.accepts[M.dataType]+", */*":M.accepts._default)}catch(S){}if(M.beforeSend&&M.beforeSend(J,M)===false){if(M.global&&!--o.active){o.event.trigger("ajaxStop")}J.abort();return false}if(M.global){o.event.trigger("ajaxSend",[J,M])}var N=function(X){if(J.readyState==0){if(P){clearInterval(P);P=null;if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}}else{if(!K&&J&&(J.readyState==4||X=="timeout")){K=true;if(P){clearInterval(P);P=null}R=X=="timeout"?"timeout":!o.httpSuccess(J)?"error":M.ifModified&&o.httpNotModified(J,M.url)?"notmodified":"success";if(R=="success"){try{V=o.httpData(J,M.dataType,M)}catch(Z){R="parsererror"}}if(R=="success"){var Y;try{Y=J.getResponseHeader("Last-Modified")}catch(Z){}if(M.ifModified&&Y){o.lastModified[M.url]=Y}if(!W){I()}}else{o.handleError(M,J,R)}L();if(X){J.abort()}if(M.async){J=null}}}};if(M.async){var P=setInterval(N,13);if(M.timeout>0){setTimeout(function(){if(J&&!K){N("timeout")}},M.timeout)}}try{J.send(M.data)}catch(S){o.handleError(M,J,null,S)}if(!M.async){N()}function I(){if(M.success){M.success(V,R)}if(M.global){o.event.trigger("ajaxSuccess",[J,M])}}function L(){if(M.complete){M.complete(J,R)}if(M.global){o.event.trigger("ajaxComplete",[J,M])}if(M.global&&!--o.active){o.event.trigger("ajaxStop")}}return J},handleError:function(F,H,E,G){if(F.error){F.error(H,E,G)}if(F.global){o.event.trigger("ajaxError",[H,F,G])}},active:0,httpSuccess:function(F){try{return !F.status&&location.protocol=="file:"||(F.status>=200&&F.status<300)||F.status==304||F.status==1223}catch(E){}return false},httpNotModified:function(G,E){try{var H=G.getResponseHeader("Last-Modified");return G.status==304||H==o.lastModified[E]}catch(F){}return false},httpData:function(J,H,G){var F=J.getResponseHeader("content-type"),E=H=="xml"||!H&&F&&F.indexOf("xml")>=0,I=E?J.responseXML:J.responseText;if(E&&I.documentElement.tagName=="parsererror"){throw"parsererror"}if(G&&G.dataFilter){I=G.dataFilter(I,H)}if(typeof I==="string"){if(H=="script"){o.globalEval(I)}if(H=="json"){I=l["eval"]("("+I+")")}}return I},param:function(E){var G=[];function H(I,J){G[G.length]=encodeURIComponent(I)+"="+encodeURIComponent(J)}if(o.isArray(E)||E.jquery){o.each(E,function(){H(this.name,this.value)})}else{for(var F in E){if(o.isArray(E[F])){o.each(E[F],function(){H(F,this)})}else{H(F,o.isFunction(E[F])?E[F]():E[F])}}}return G.join("&").replace(/%20/g,"+")}});var m={},n,d=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];function t(F,E){var G={};o.each(d.concat.apply([],d.slice(0,E)),function(){G[this]=F});return G}o.fn.extend({show:function(J,L){if(J){return this.animate(t("show",3),J,L)}else{for(var H=0,F=this.length;H").appendTo("body");K=I.css("display");if(K==="none"){K="block"}I.remove();m[G]=K}o.data(this[H],"olddisplay",K)}}for(var H=0,F=this.length;H=0;H--){if(G[H].elem==this){if(E){G[H](true)}G.splice(H,1)}}});if(!E){this.dequeue()}return this}});o.each({slideDown:t("show",1),slideUp:t("hide",1),slideToggle:t("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"}},function(E,F){o.fn[E]=function(G,H){return this.animate(F,G,H)}});o.extend({speed:function(G,H,F){var E=typeof G==="object"?G:{complete:F||!F&&H||o.isFunction(G)&&G,duration:G,easing:F&&H||H&&!o.isFunction(H)&&H};E.duration=o.fx.off?0:typeof E.duration==="number"?E.duration:o.fx.speeds[E.duration]||o.fx.speeds._default;E.old=E.complete;E.complete=function(){if(E.queue!==false){o(this).dequeue()}if(o.isFunction(E.old)){E.old.call(this)}};return E},easing:{linear:function(G,H,E,F){return E+F*G},swing:function(G,H,E,F){return((-Math.cos(G*Math.PI)/2)+0.5)*F+E}},timers:[],fx:function(F,E,G){this.options=E;this.elem=F;this.prop=G;if(!E.orig){E.orig={}}}});o.fx.prototype={update:function(){if(this.options.step){this.options.step.call(this.elem,this.now,this)}(o.fx.step[this.prop]||o.fx.step._default)(this);if((this.prop=="height"||this.prop=="width")&&this.elem.style){this.elem.style.display="block"}},cur:function(F){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null)){return this.elem[this.prop]}var E=parseFloat(o.css(this.elem,this.prop,F));return E&&E>-10000?E:parseFloat(o.curCSS(this.elem,this.prop))||0},custom:function(I,H,G){this.startTime=e();this.start=I;this.end=H;this.unit=G||this.unit||"px";this.now=this.start;this.pos=this.state=0;var E=this;function F(J){return E.step(J)}F.elem=this.elem;if(F()&&o.timers.push(F)&&!n){n=setInterval(function(){var K=o.timers;for(var J=0;J=this.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;var E=true;for(var F in this.options.curAnim){if(this.options.curAnim[F]!==true){E=false}}if(E){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;this.elem.style.display=this.options.display;if(o.css(this.elem,"display")=="none"){this.elem.style.display="block"}}if(this.options.hide){o(this.elem).hide()}if(this.options.hide||this.options.show){for(var I in this.options.curAnim){o.attr(this.elem.style,I,this.options.orig[I])}}this.options.complete.call(this.elem)}return false}else{var J=G-this.startTime;this.state=J/this.options.duration;this.pos=o.easing[this.options.easing||(o.easing.swing?"swing":"linear")](this.state,J,0,1,this.options.duration);this.now=this.start+((this.end-this.start)*this.pos);this.update()}return true}};o.extend(o.fx,{speeds:{slow:600,fast:200,_default:400},step:{opacity:function(E){o.attr(E.elem.style,"opacity",E.now)},_default:function(E){if(E.elem.style&&E.elem.style[E.prop]!=null){E.elem.style[E.prop]=E.now+E.unit}else{E.elem[E.prop]=E.now}}}});if(document.documentElement.getBoundingClientRect){o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}var G=this[0].getBoundingClientRect(),J=this[0].ownerDocument,F=J.body,E=J.documentElement,L=E.clientTop||F.clientTop||0,K=E.clientLeft||F.clientLeft||0,I=G.top+(self.pageYOffset||o.boxModel&&E.scrollTop||F.scrollTop)-L,H=G.left+(self.pageXOffset||o.boxModel&&E.scrollLeft||F.scrollLeft)-K;return{top:I,left:H}}}else{o.fn.offset=function(){if(!this[0]){return{top:0,left:0}}if(this[0]===this[0].ownerDocument.body){return o.offset.bodyOffset(this[0])}o.offset.initialized||o.offset.initialize();var J=this[0],G=J.offsetParent,F=J,O=J.ownerDocument,M,H=O.documentElement,K=O.body,L=O.defaultView,E=L.getComputedStyle(J,null),N=J.offsetTop,I=J.offsetLeft;while((J=J.parentNode)&&J!==K&&J!==H){M=L.getComputedStyle(J,null);N-=J.scrollTop,I-=J.scrollLeft;if(J===G){N+=J.offsetTop,I+=J.offsetLeft;if(o.offset.doesNotAddBorder&&!(o.offset.doesAddBorderForTableAndCells&&/^t(able|d|h)$/i.test(J.tagName))){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}F=G,G=J.offsetParent}if(o.offset.subtractsBorderForOverflowNotVisible&&M.overflow!=="visible"){N+=parseInt(M.borderTopWidth,10)||0,I+=parseInt(M.borderLeftWidth,10)||0}E=M}if(E.position==="relative"||E.position==="static"){N+=K.offsetTop,I+=K.offsetLeft}if(E.position==="fixed"){N+=Math.max(H.scrollTop,K.scrollTop),I+=Math.max(H.scrollLeft,K.scrollLeft)}return{top:N,left:I}}}o.offset={initialize:function(){if(this.initialized){return}var L=document.body,F=document.createElement("div"),H,G,N,I,M,E,J=L.style.marginTop,K='
    ';M={position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",height:"1px",visibility:"hidden"};for(E in M){F.style[E]=M[E]}F.innerHTML=K;L.insertBefore(F,L.firstChild);H=F.firstChild,G=H.firstChild,I=H.nextSibling.firstChild.firstChild;this.doesNotAddBorder=(G.offsetTop!==5);this.doesAddBorderForTableAndCells=(I.offsetTop===5);H.style.overflow="hidden",H.style.position="relative";this.subtractsBorderForOverflowNotVisible=(G.offsetTop===-5);L.style.marginTop="1px";this.doesNotIncludeMarginInBodyOffset=(L.offsetTop===0);L.style.marginTop=J;L.removeChild(F);this.initialized=true},bodyOffset:function(E){o.offset.initialized||o.offset.initialize();var G=E.offsetTop,F=E.offsetLeft;if(o.offset.doesNotIncludeMarginInBodyOffset){G+=parseInt(o.curCSS(E,"marginTop",true),10)||0,F+=parseInt(o.curCSS(E,"marginLeft",true),10)||0}return{top:G,left:F}}};o.fn.extend({position:function(){var I=0,H=0,F;if(this[0]){var G=this.offsetParent(),J=this.offset(),E=/^body|html$/i.test(G[0].tagName)?{top:0,left:0}:G.offset();J.top-=j(this,"marginTop");J.left-=j(this,"marginLeft");E.top+=j(G,"borderTopWidth");E.left+=j(G,"borderLeftWidth");F={top:J.top-E.top,left:J.left-E.left}}return F},offsetParent:function(){var E=this[0].offsetParent||document.body;while(E&&(!/^body|html$/i.test(E.tagName)&&o.css(E,"position")=="static")){E=E.offsetParent}return o(E)}});o.each(["Left","Top"],function(F,E){var G="scroll"+E;o.fn[G]=function(H){if(!this[0]){return null}return H!==g?this.each(function(){this==l||this==document?l.scrollTo(!F?H:o(l).scrollLeft(),F?H:o(l).scrollTop()):this[G]=H}):this[0]==l||this[0]==document?self[F?"pageYOffset":"pageXOffset"]||o.boxModel&&document.documentElement[G]||document.body[G]:this[0][G]}});o.each(["Height","Width"],function(I,G){var E=I?"Left":"Top",H=I?"Right":"Bottom",F=G.toLowerCase();o.fn["inner"+G]=function(){return this[0]?o.css(this[0],F,false,"padding"):null};o.fn["outer"+G]=function(K){return this[0]?o.css(this[0],F,false,K?"margin":"border"):null};var J=G.toLowerCase();o.fn[J]=function(K){return this[0]==l?document.compatMode=="CSS1Compat"&&document.documentElement["client"+G]||document.body["client"+G]:this[0]==document?Math.max(document.documentElement["client"+G],document.body["scroll"+G],document.documentElement["scroll"+G],document.body["offset"+G],document.documentElement["offset"+G]):K===g?(this.length?o.css(this[0],J):null):this.css(J,typeof K==="string"?K:K+"px")}})})(); \ No newline at end of file diff --git a/library/jquery_ac/jquery.autocomplete-min.js b/library/jquery_ac/jquery.autocomplete-min.js deleted file mode 100644 index 7018fd00aa..0000000000 --- a/library/jquery_ac/jquery.autocomplete-min.js +++ /dev/null @@ -1,11 +0,0 @@ -/** -* Ajax Autocomplete for jQuery, version 1.1.3 -* (c) 2010 Tomas Kirda -* -* Ajax Autocomplete for jQuery is freely distributable under the terms of an MIT-style license. -* For details, see the web site: http://www.devbridge.com/projects/autocomplete/jquery/ -* -* Last Review: 04/19/2010 -*/ - -(function(d){function l(b,a,c){a="("+c.replace(m,"\\$1")+")";return b.replace(new RegExp(a,"gi"),"$1")}function i(b,a){this.el=d(b);this.el.attr("autocomplete","off");this.suggestions=[];this.data=[];this.badQueries=[];this.selectedIndex=-1;this.currentValue=this.el.val();this.intervalId=0;this.cachedResponse=[];this.onChangeInterval=null;this.ignoreValueChange=false;this.serviceUrl=a.serviceUrl;this.isLocal=false;this.options={autoSubmit:false,minChars:1,maxHeight:300,deferRequestBy:0, width:0,highlight:true,params:{},fnFormatResult:l,delimiter:null,zIndex:9999};this.initialize();this.setOptions(a)}var m=new RegExp("(\\/|\\.|\\*|\\+|\\?|\\||\\(|\\)|\\[|\\]|\\{|\\}|\\\\)","g");d.fn.autocomplete=function(b){return new i(this.get(0)||d(""),b)};i.prototype={killerFn:null,initialize:function(){var b,a,c;b=this;a=Math.floor(Math.random()*1048576).toString(16);c="Autocomplete_"+a;this.killerFn=function(e){if(d(e.target).parents(".autocomplete").size()===0){b.killSuggestions(); b.disableKillerFn()}};if(!this.options.width)this.options.width=this.el.width();this.mainContainerId="AutocompleteContainter_"+a;d('
    ').appendTo("body");this.container=d("#"+c);this.fixPosition();window.opera?this.el.keypress(function(e){b.onKeyPress(e)}):this.el.keydown(function(e){b.onKeyPress(e)});this.el.keyup(function(e){b.onKeyUp(e)}); this.el.blur(function(){b.enableKillerFn()});this.el.focus(function(){b.fixPosition()})},setOptions:function(b){var a=this.options;d.extend(a,b);if(a.lookup){this.isLocal=true;if(d.isArray(a.lookup))a.lookup={suggestions:a.lookup,data:[]}}d("#"+this.mainContainerId).css({zIndex:a.zIndex});this.container.css({maxHeight:a.maxHeight+"px",width:a.width})},clearCache:function(){this.cachedResponse=[];this.badQueries=[]},disable:function(){this.disabled=true},enable:function(){this.disabled=false},fixPosition:function(){var b= this.el.offset();d("#"+this.mainContainerId).css({top:b.top+this.el.innerHeight()+"px",left:b.left+"px"})},enableKillerFn:function(){d(document).bind("click",this.killerFn)},disableKillerFn:function(){d(document).unbind("click",this.killerFn)},killSuggestions:function(){var b=this;this.stopKillSuggestions();this.intervalId=window.setInterval(function(){b.hide();b.stopKillSuggestions()},300)},stopKillSuggestions:function(){window.clearInterval(this.intervalId)},onKeyPress:function(b){if(!(this.disabled|| !this.enabled)){switch(b.keyCode){case 27:this.el.val(this.currentValue);this.hide();break;case 9:case 13:if(this.selectedIndex===-1){this.hide();return}this.select(this.selectedIndex);if(b.keyCode===9)return;break;case 38:this.moveUp();break;case 40:this.moveDown();break;default:return}b.stopImmediatePropagation();b.preventDefault()}},onKeyUp:function(b){if(!this.disabled){switch(b.keyCode){case 38:case 40:return}clearInterval(this.onChangeInterval);if(this.currentValue!==this.el.val())if(this.options.deferRequestBy> 0){var a=this;this.onChangeInterval=setInterval(function(){a.onValueChange()},this.options.deferRequestBy)}else this.onValueChange()}},onValueChange:function(){clearInterval(this.onChangeInterval);this.currentValue=this.el.val();var b=this.getQuery(this.currentValue);this.selectedIndex=-1;if(this.ignoreValueChange)this.ignoreValueChange=false;else b===""||b.length'+e(c,this.data[f],g)+"
    ");c.mouseover(j(f));c.click(k(f));this.container.append(c)}this.enabled=true;this.container.show()}},processResponse:function(b){var a;try{a=eval("("+b+")")}catch(c){return}if(!d.isArray(a.data))a.data=[];if(!this.options.noCache){this.cachedResponse[a.query]= a;a.suggestions.length===0&&this.badQueries.push(a.query)}if(a.query===this.getQuery(this.currentValue)){this.suggestions=a.suggestions;this.data=a.data;this.suggest()}},activate:function(b){var a,c;a=this.container.children();this.selectedIndex!==-1&&a.length>this.selectedIndex&&d(a.get(this.selectedIndex)).removeClass();this.selectedIndex=b;if(this.selectedIndex!==-1&&a.length>this.selectedIndex){c=a.get(this.selectedIndex);d(c).addClass("selected")}return c},deactivate:function(b,a){b.className= "";if(this.selectedIndex===a)this.selectedIndex=-1},select:function(b){var a;if(a=this.suggestions[b]){this.el.val(a);if(this.options.autoSubmit){a=this.el.parents("form");a.length>0&&a.get(0).submit()}this.ignoreValueChange=true;this.hide();this.onSelect(b)}},moveUp:function(){if(this.selectedIndex!==-1)if(this.selectedIndex===0){this.container.children().get(0).className="";this.selectedIndex=-1;this.el.val(this.currentValue)}else this.adjustScroll(this.selectedIndex-1)},moveDown:function(){this.selectedIndex!== this.suggestions.length-1&&this.adjustScroll(this.selectedIndex+1)},adjustScroll:function(b){var a,c,e;a=this.activate(b).offsetTop;c=this.container.scrollTop();e=c+this.options.maxHeight-25;if(ae&&this.container.scrollTop(a-this.options.maxHeight+25);this.el.val(this.getValue(this.suggestions[b]))},onSelect:function(b){var a,c;a=this.options.onSelect;c=this.suggestions[b];b=this.data[b];this.el.val(this.getValue(c));d.isFunction(a)&&a(c,b,this.el)},getValue:function(b){var a, c;a=this.options.delimiter;if(!a)return b;c=this.currentValue;a=c.split(a);if(a.length===1)return b;return c.substr(0,c.length-a[a.length-1].length)+b}}})(jQuery); \ No newline at end of file diff --git a/library/jquery_ac/jquery.autocomplete.js b/library/jquery_ac/jquery.autocomplete.js deleted file mode 100644 index 6a7ce87872..0000000000 --- a/library/jquery_ac/jquery.autocomplete.js +++ /dev/null @@ -1,390 +0,0 @@ -/** -* Ajax Autocomplete for jQuery, version 1.1.3 -* (c) 2010 Tomas Kirda -* -* Ajax Autocomplete for jQuery is freely distributable under the terms of an MIT-style license. -* For details, see the web site: http://www.devbridge.com/projects/autocomplete/jquery/ -* -* Last Review: 04/19/2010 -*/ - -/*jslint onevar: true, evil: true, nomen: true, eqeqeq: true, bitwise: true, regexp: true, newcap: true, immed: true */ -/*global window: true, document: true, clearInterval: true, setInterval: true, jQuery: true */ - -(function($) { - - var reEscape = new RegExp('(\\' + ['/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\'].join('|\\') + ')', 'g'); - - function fnFormatResult(value, data, currentValue) { - var pattern = '(' + currentValue.replace(reEscape, '\\$1') + ')'; - return value.replace(new RegExp(pattern, 'gi'), '$1<\/strong>'); - } - - function Autocomplete(el, options) { - this.el = $(el); - this.el.attr('autocomplete', 'off'); - this.suggestions = []; - this.data = []; - this.badQueries = []; - this.selectedIndex = -1; - this.currentValue = this.el.val(); - this.intervalId = 0; - this.cachedResponse = []; - this.onChangeInterval = null; - this.ignoreValueChange = false; - this.serviceUrl = options.serviceUrl; - this.isLocal = false; - this.options = { - autoSubmit: false, - minChars: 1, - maxHeight: 300, - deferRequestBy: 0, - width: 0, - highlight: true, - params: {}, - fnFormatResult: fnFormatResult, - delimiter: null, - zIndex: 9999 - }; - this.initialize(); - this.setOptions(options); - } - - $.fn.autocomplete = function(options) { - return new Autocomplete(this.get(0)||$(''), options); - }; - - - Autocomplete.prototype = { - - killerFn: null, - - initialize: function() { - - var me, uid, autocompleteElId; - me = this; - uid = Math.floor(Math.random()*0x100000).toString(16); - autocompleteElId = 'Autocomplete_' + uid; - - this.killerFn = function(e) { - if ($(e.target).parents('.autocomplete').size() === 0) { - me.killSuggestions(); - me.disableKillerFn(); - } - }; - - if (!this.options.width) { this.options.width = this.el.width(); } - this.mainContainerId = 'AutocompleteContainter_' + uid; - - $('
    ').appendTo('body'); - - this.container = $('#' + autocompleteElId); - this.fixPosition(); - if (window.opera) { - this.el.keypress(function(e) { me.onKeyPress(e); }); - } else { - this.el.keydown(function(e) { me.onKeyPress(e); }); - } - this.el.keyup(function(e) { me.onKeyUp(e); }); - this.el.blur(function() { me.enableKillerFn(); }); - this.el.focus(function() { me.fixPosition(); }); - }, - - setOptions: function(options){ - var o = this.options; - $.extend(o, options); - if(o.lookup){ - this.isLocal = true; - if($.isArray(o.lookup)){ o.lookup = { suggestions:o.lookup, data:[] }; } - } - $('#'+this.mainContainerId).css({ zIndex:o.zIndex }); - this.container.css({ maxHeight: o.maxHeight + 'px', width:o.width }); - }, - - clearCache: function(){ - this.cachedResponse = []; - this.badQueries = []; - }, - - disable: function(){ - this.disabled = true; - }, - - enable: function(){ - this.disabled = false; - }, - - fixPosition: function() { - var offset = this.el.offset(); - $('#' + this.mainContainerId).css({ top: (offset.top + this.el.innerHeight()) + 'px', left: offset.left + 'px' }); - }, - - enableKillerFn: function() { - var me = this; - $(document).bind('click', me.killerFn); - }, - - disableKillerFn: function() { - var me = this; - $(document).unbind('click', me.killerFn); - }, - - killSuggestions: function() { - var me = this; - this.stopKillSuggestions(); - this.intervalId = window.setInterval(function() { me.hide(); me.stopKillSuggestions(); }, 300); - }, - - stopKillSuggestions: function() { - window.clearInterval(this.intervalId); - }, - - onKeyPress: function(e) { - if (this.disabled || !this.enabled) { return; } - // return will exit the function - // and event will not be prevented - switch (e.keyCode) { - case 27: //KEY_ESC: - this.el.val(this.currentValue); - this.hide(); - break; - case 9: //KEY_TAB: - case 13: //KEY_RETURN: - if (this.selectedIndex === -1) { - this.hide(); - return; - } - this.select(this.selectedIndex); - if(e.keyCode === 9){ return; } - break; - case 38: //KEY_UP: - this.moveUp(); - break; - case 40: //KEY_DOWN: - this.moveDown(); - break; - default: - return; - } - e.stopImmediatePropagation(); - e.preventDefault(); - }, - - onKeyUp: function(e) { - if(this.disabled){ return; } - switch (e.keyCode) { - case 38: //KEY_UP: - case 40: //KEY_DOWN: - return; - } - clearInterval(this.onChangeInterval); - if (this.currentValue !== this.el.val()) { - if (this.options.deferRequestBy > 0) { - // Defer lookup in case when value changes very quickly: - var me = this; - this.onChangeInterval = setInterval(function() { me.onValueChange(); }, this.options.deferRequestBy); - } else { - this.onValueChange(); - } - } - }, - - onValueChange: function() { - clearInterval(this.onChangeInterval); - this.currentValue = this.el.val(); - var q = this.getQuery(this.currentValue); - this.selectedIndex = -1; - if (this.ignoreValueChange) { - this.ignoreValueChange = false; - return; - } - if (q === '' || q.length < this.options.minChars) { - this.hide(); - } else { - this.getSuggestions(q); - } - }, - - getQuery: function(val) { - var d, arr; - d = this.options.delimiter; - if (!d) { return $.trim(val); } - arr = val.split(d); - return $.trim(arr[arr.length - 1]); - }, - - getSuggestionsLocal: function(q) { - var ret, arr, len, val, i; - arr = this.options.lookup; - len = arr.suggestions.length; - ret = { suggestions:[], data:[] }; - q = q.toLowerCase(); - for(i=0; i< len; i++){ - val = arr.suggestions[i]; - if(val.toLowerCase().indexOf(q) === 0){ - ret.suggestions.push(val); - ret.data.push(arr.data[i]); - } - } - return ret; - }, - - getSuggestions: function(q) { - var cr, me; - cr = this.isLocal ? this.getSuggestionsLocal(q) : this.cachedResponse[q]; - if (cr && $.isArray(cr.suggestions)) { - this.suggestions = cr.suggestions; - this.data = cr.data; - this.suggest(); - } else if (!this.isBadQuery(q)) { - me = this; - me.options.params.query = q; - $.get(this.serviceUrl, me.options.params, function(txt) { me.processResponse(txt); }, 'text'); - } - }, - - isBadQuery: function(q) { - var i = this.badQueries.length; - while (i--) { - if (q.indexOf(this.badQueries[i]) === 0) { return true; } - } - return false; - }, - - hide: function() { - this.enabled = false; - this.selectedIndex = -1; - this.container.hide(); - }, - - suggest: function() { - if (this.suggestions.length === 0) { - this.hide(); - return; - } - - var me, len, div, f, v, i, s, mOver, mClick; - me = this; - len = this.suggestions.length; - f = this.options.fnFormatResult; - v = this.getQuery(this.currentValue); - mOver = function(xi) { return function() { me.activate(xi); }; }; - mClick = function(xi) { return function() { me.select(xi); }; }; - this.container.hide().empty(); - for (i = 0; i < len; i++) { - s = this.suggestions[i]; - div = $((me.selectedIndex === i ? '
    ' + f(s, this.data[i], v) + '
    '); - div.mouseover(mOver(i)); - div.click(mClick(i)); - this.container.append(div); - } - this.enabled = true; - this.container.show(); - }, - - processResponse: function(text) { - var response; - try { - response = eval('(' + text + ')'); - } catch (err) { return; } - if (!$.isArray(response.data)) { response.data = []; } - if(!this.options.noCache){ - this.cachedResponse[response.query] = response; - if (response.suggestions.length === 0) { this.badQueries.push(response.query); } - } - if (response.query === this.getQuery(this.currentValue)) { - this.suggestions = response.suggestions; - this.data = response.data; - this.suggest(); - } - }, - - activate: function(index) { - var divs, activeItem; - divs = this.container.children(); - // Clear previous selection: - if (this.selectedIndex !== -1 && divs.length > this.selectedIndex) { - $(divs.get(this.selectedIndex)).removeClass(); - } - this.selectedIndex = index; - if (this.selectedIndex !== -1 && divs.length > this.selectedIndex) { - activeItem = divs.get(this.selectedIndex); - $(activeItem).addClass('selected'); - } - return activeItem; - }, - - deactivate: function(div, index) { - div.className = ''; - if (this.selectedIndex === index) { this.selectedIndex = -1; } - }, - - select: function(i) { - var selectedValue, f; - selectedValue = this.suggestions[i]; - if (selectedValue) { - this.el.val(selectedValue); - if (this.options.autoSubmit) { - f = this.el.parents('form'); - if (f.length > 0) { f.get(0).submit(); } - } - this.ignoreValueChange = true; - this.hide(); - this.onSelect(i); - } - }, - - moveUp: function() { - if (this.selectedIndex === -1) { return; } - if (this.selectedIndex === 0) { - this.container.children().get(0).className = ''; - this.selectedIndex = -1; - this.el.val(this.currentValue); - return; - } - this.adjustScroll(this.selectedIndex - 1); - }, - - moveDown: function() { - if (this.selectedIndex === (this.suggestions.length - 1)) { return; } - this.adjustScroll(this.selectedIndex + 1); - }, - - adjustScroll: function(i) { - var activeItem, offsetTop, upperBound, lowerBound; - activeItem = this.activate(i); - offsetTop = activeItem.offsetTop; - upperBound = this.container.scrollTop(); - lowerBound = upperBound + this.options.maxHeight - 25; - if (offsetTop < upperBound) { - this.container.scrollTop(offsetTop); - } else if (offsetTop > lowerBound) { - this.container.scrollTop(offsetTop - this.options.maxHeight + 25); - } - this.el.val(this.getValue(this.suggestions[i])); - }, - - onSelect: function(i) { - var me, fn, s, d; - me = this; - fn = me.options.onSelect; - s = me.suggestions[i]; - d = me.data[i]; - me.el.val(me.getValue(s)); - if ($.isFunction(fn)) { fn(s, d, me.el); } - }, - - getValue: function(value){ - var del, currVal, arr, me; - me = this; - del = me.options.delimiter; - if (!del) { return value; } - currVal = me.currentValue; - arr = currVal.split(del); - if (arr.length === 1) { return value; } - return currVal.substr(0, currVal.length - arr[arr.length - 1].length) + value; - } - - }; - -}(jQuery)); diff --git a/library/jquery_ac/shadow.png b/library/jquery_ac/shadow.png deleted file mode 100644 index a2561df971728d988424100c74c817916eca1979..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3403 zcmeAS@N?(olHy`uVBq!ia0y~yV738bR}L1Sh{z?w93Z7#;u=xnT$Gwvl9`{U5R#dj z$`F!Ks$i<%mYSqsWME*TU}$J%1Vly(x&~$j21QvmX+Ul4C7!;n>{pmr1x4l8+mz-4 zg*Xd5B8wRqxP?HN@zUM8KR`j2bVpxD28NCO+HqZdXMAQjaPeGm)a##I4DP>8^|Q}*osX?x zu(w4E^8SQ>2<4nWJ;;$Ch1?$dLgm)?6;Yr9_CdHMW*W(@x+ip*R06EH_T4pml(Y0_%+Z_b^VZki z+Ig-L)GH{z-FHJSJv;l^O{dMW8|Geryoiy(edpWebG3Q>oX-o7q}^hCpz*y@X6IS? ZpGWQ1{0Pup3+%oyc)I$ztaD0e0swh%>OlYi diff --git a/library/jquery_ac/styles.css b/library/jquery_ac/styles.css deleted file mode 100644 index e056d5cf9c..0000000000 --- a/library/jquery_ac/styles.css +++ /dev/null @@ -1,6 +0,0 @@ - -.autocomplete-w1 { background:url(img/shadow.png) no-repeat bottom right; position:absolute; top:0px; left:0px; margin:8px 0 0 6px; /* IE6 fix: */ _background:none; _margin:0; } -.autocomplete { border:1px solid #999; background:#FFF; cursor:default; text-align:left; max-height:350px; overflow:auto; margin:-6px 6px 6px -6px; /* IE6 specific: */ _height:350px; _margin:0; _overflow-x:hidden; } -.autocomplete .selected { background:#F0F0F0; } -.autocomplete div { padding:2px 5px; white-space:nowrap; } -.autocomplete strong { font-weight:normal; color:#3399FF; } diff --git a/view/templates/contacts-head.tpl b/view/templates/contacts-head.tpl index f054fdce27..25055d405b 100644 --- a/view/templates/contacts-head.tpl +++ b/view/templates/contacts-head.tpl @@ -1,18 +1,19 @@ - - - diff --git a/view/templates/head.tpl b/view/templates/head.tpl index deac208d18..733f8917a6 100644 --- a/view/templates/head.tpl +++ b/view/templates/head.tpl @@ -35,7 +35,6 @@ - diff --git a/view/templates/message-head.tpl b/view/templates/message-head.tpl index 875664628d..49f268c42b 100644 --- a/view/templates/message-head.tpl +++ b/view/templates/message-head.tpl @@ -1,18 +1,8 @@ - - - - diff --git a/view/templates/poke_head.tpl b/view/templates/poke_head.tpl index 6e30eb7fee..7de98d2212 100644 --- a/view/templates/poke_head.tpl +++ b/view/templates/poke_head.tpl @@ -1,18 +1,8 @@ - - \ No newline at end of file + diff --git a/view/theme/frost-mobile/js/theme.js b/view/theme/frost-mobile/js/theme.js index c021370874..7d7e8259c6 100644 --- a/view/theme/frost-mobile/js/theme.js +++ b/view/theme/frost-mobile/js/theme.js @@ -103,22 +103,24 @@ $(document).ready(function() { switch(window.autocompleteType) { case 'msg-header': - var a = $("#recip").autocomplete({ - serviceUrl: baseurl + '/acl', - minChars: 2, - width: 350, - onSelect: function(value,data) { - $("#recip-complete").val(data); - } + $("#recip").name_autocomplete(baseurl + '/acl', '', false, function(data) { + $("#recip-complete").val(data.id); }); break; case 'contacts-head': - var a = $("#contacts-search").autocomplete({ - serviceUrl: baseurl + '/acl', - minChars: 2, - width: 350, + $("#contacts-search").contact_autocomplete(baseurl + '/acl', 'a', true); + + + $("#contacts-search").keyup(function(event){ + if(event.keyCode == 13){ + $("#contacts-search").click(); + } + }); + $(".autocomplete-w1 .selected").keyup(function(event){ + if(event.keyCode == 13){ + $("#contacts-search").click(); + } }); - a.setOptions({ params: { type: 'a' }}); break; case 'display-head': $(".comment-wwedit-wrapper textarea").editor_autocomplete(baseurl+"/acl"); diff --git a/view/theme/frost-mobile/templates/contacts-end.tpl b/view/theme/frost-mobile/templates/contacts-end.tpl index 5416f872c8..139597f9cb 100644 --- a/view/theme/frost-mobile/templates/contacts-end.tpl +++ b/view/theme/frost-mobile/templates/contacts-end.tpl @@ -1,5 +1,2 @@ - - - diff --git a/view/theme/frost-mobile/templates/message-end.tpl b/view/theme/frost-mobile/templates/message-end.tpl index 5416f872c8..139597f9cb 100644 --- a/view/theme/frost-mobile/templates/message-end.tpl +++ b/view/theme/frost-mobile/templates/message-end.tpl @@ -1,5 +1,2 @@ - - - diff --git a/view/theme/frost/js/theme.js b/view/theme/frost/js/theme.js index 4418c1f3c4..fc1bb643c0 100644 --- a/view/theme/frost/js/theme.js +++ b/view/theme/frost/js/theme.js @@ -214,22 +214,24 @@ $(document).ready(function() { switch(window.autocompleteType) { case 'msg-header': - var a = $("#recip").autocomplete({ - serviceUrl: baseurl + '/acl', - minChars: 2, - width: 350, - onSelect: function(value,data) { - $("#recip-complete").val(data); - } + $("#recip").name_autocomplete(baseurl + '/acl', '', false, function(data) { + $("#recip-complete").val(data.id); }); break; case 'contacts-head': - var a = $("#contacts-search").autocomplete({ - serviceUrl: baseurl + '/acl', - minChars: 2, - width: 350, + $("#contacts-search").contact_autocomplete(baseurl + '/acl', 'a', true); + + + $("#contacts-search").keyup(function(event){ + if(event.keyCode == 13){ + $("#contacts-search").click(); + } + }); + $(".autocomplete-w1 .selected").keyup(function(event){ + if(event.keyCode == 13){ + $("#contacts-search").click(); + } }); - a.setOptions({ params: { type: 'a' }}); break; case 'display-head': $(".comment-wwedit-wrapper textarea").editor_autocomplete(baseurl+"/acl"); diff --git a/view/theme/frost/templates/contacts-end.tpl b/view/theme/frost/templates/contacts-end.tpl index 5416f872c8..139597f9cb 100644 --- a/view/theme/frost/templates/contacts-end.tpl +++ b/view/theme/frost/templates/contacts-end.tpl @@ -1,5 +1,2 @@ - - - diff --git a/view/theme/frost/templates/message-end.tpl b/view/theme/frost/templates/message-end.tpl index 5416f872c8..139597f9cb 100644 --- a/view/theme/frost/templates/message-end.tpl +++ b/view/theme/frost/templates/message-end.tpl @@ -1,5 +1,2 @@ - - - From 586b251539b61695b67c0f925cc90cbbeb61fb0e Mon Sep 17 00:00:00 2001 From: rabuzarus <> Date: Sun, 24 Jan 2016 19:56:23 +0100 Subject: [PATCH 006/211] rework autocomplete: some cleanup and docu --- include/acl_selectors.php | 45 +++++++--------- include/dir_fns.php | 18 +++++-- include/nav.php | 59 ++++++++++---------- view/global.css | 2 - view/templates/message-head.tpl | 2 +- view/templates/nav_head.tpl | 0 view/theme/vier/breathe.css | 2 +- view/theme/vier/narrow.css | 2 +- view/theme/vier/plus.css | 2 +- view/theme/vier/shadow.css | 2 +- view/theme/vier/style.css | 74 ++++++++++++++------------ view/theme/vier/templates/nav.tpl | 4 +- view/theme/vier/templates/nav_head.tpl | 6 +++ 13 files changed, 111 insertions(+), 107 deletions(-) create mode 100644 view/templates/nav_head.tpl create mode 100644 view/theme/vier/templates/nav_head.tpl diff --git a/include/acl_selectors.php b/include/acl_selectors.php index d78393aeee..46f737e4c2 100644 --- a/include/acl_selectors.php +++ b/include/acl_selectors.php @@ -1,13 +1,15 @@ pager['page'] != 1) ? '&p=' . $a->pager['page'] : ''); - $x = z_fetch_url(get_server().'/lsearch?f=' . $p . '&search=' . urlencode($search)); if($x['success']) { diff --git a/include/dir_fns.php b/include/dir_fns.php index a362732b7b..2e39e7ddbf 100644 --- a/include/dir_fns.php +++ b/include/dir_fns.php @@ -1,11 +1,20 @@ page,'nav'))) $a->page['nav'] = ''; - $base = z_root(); - /** + $a->page['htmlhead'] .= replace_macros(get_markup_template('nav_head.tpl'), array()); + + /* * Placeholder div for popup panel */ - $a->page['htmlhead'] .= <<< EOT - - -EOT; $a->page['nav'] .= '' ; $nav_info = nav_info($a); - /** + /* * Build the page */ @@ -37,12 +31,13 @@ EOT; '$baseurl' => $a->get_baseurl(), '$sitelocation' => $nav_info['sitelocation'], '$nav' => $nav_info['nav'], - '$banner' => $nav_info['banner'], + '$banner' => $nav_info['banner'], '$emptynotifications' => t('Nothing new here'), '$userinfo' => $nav_info['userinfo'], - '$sel' => $a->nav_sel, + '$sel' => $a->nav_sel, '$apps' => $a->apps, - '$clear_notifs' => t('Clear notifications') + '$clear_notifs' => t('Clear notifications'), + '$search_hint' => t('@name, !forum, #tags, content') )); call_hooks('page_header', $a->page['nav']); @@ -53,7 +48,7 @@ function nav_info(&$a) { $ssl_state = ((local_user()) ? true : false); - /** + /* * * Our network is distributed, and as you visit friends some of the * sites look exactly the same - it isn't always easy to know where you are. @@ -69,7 +64,7 @@ function nav_info(&$a) { // nav links: array of array('href', 'text', 'extra css classes', 'title') $nav = Array(); - /** + /* * Display login or logout */ @@ -100,7 +95,7 @@ function nav_info(&$a) { } - /** + /* * "Home" should also take you home from an authenticated remote profile connection */ @@ -151,7 +146,7 @@ function nav_info(&$a) { $nav['about'] = Array('friendica', t('Information'), "", t('Information about this friendica instance')); - /** + /* * * The following nav links are only show to logged in users * @@ -195,7 +190,7 @@ function nav_info(&$a) { $nav['contacts'] = array('contacts', t('Contacts'),"", t('Manage/edit friends and contacts')); } - /** + /* * Admin page */ if (is_site_admin()){ @@ -206,7 +201,7 @@ function nav_info(&$a) { $nav['navigation'] = array('navigation/', t('Navigation'), "", t('Site map')); - /** + /* * * Provide a banner/logo/whatever * @@ -229,26 +224,26 @@ function nav_info(&$a) { } -/* +/** * Set a menu item in navbar as selected * */ function nav_set_selected($item){ $a = get_app(); - $a->nav_sel = array( + $a->nav_sel = array( 'community' => null, - 'network' => null, - 'home' => null, - 'profiles' => null, + 'network' => null, + 'home' => null, + 'profiles' => null, 'introductions' => null, 'notifications' => null, - 'messages' => null, - 'directory' => null, - 'settings' => null, - 'contacts' => null, - 'manage' => null, - 'events' => null, - 'register' => null, + 'messages' => null, + 'directory' => null, + 'settings' => null, + 'contacts' => null, + 'manage' => null, + 'events' => null, + 'register' => null, ); $a->nav_sel[$item] = 'selected'; } diff --git a/view/global.css b/view/global.css index 0857cba27b..f375e811b2 100644 --- a/view/global.css +++ b/view/global.css @@ -210,7 +210,6 @@ key { display: inline; background-color: #eee; color: #666; padding:0.2em; font- .notify-unseen { background-color: #cceeFF; } /* autocomplete popup */ - ul.acpopup { list-style: none; float: left; @@ -220,7 +219,6 @@ ul.acpopup { } nav .acpopup { width: 290px; - margin-left: -35px; max-height: 450px; max-width: 300px; overflow-y: auto; diff --git a/view/templates/message-head.tpl b/view/templates/message-head.tpl index 49f268c42b..3fabebc0d2 100644 --- a/view/templates/message-head.tpl +++ b/view/templates/message-head.tpl @@ -1,7 +1,7 @@ From 1a7ec34e714667f86d6c60694140e993afcfbf96 Mon Sep 17 00:00:00 2001 From: rabuzarus <> Date: Sat, 30 Jan 2016 16:42:46 +0100 Subject: [PATCH 007/211] rework autocomplete: update query-textcomplete --- .../jquery.textcomplete.js | 281 +++++++++++++----- 1 file changed, 202 insertions(+), 79 deletions(-) diff --git a/library/jquery-textcomplete/jquery.textcomplete.js b/library/jquery-textcomplete/jquery.textcomplete.js index 3df84f3b43..2003101750 100644 --- a/library/jquery-textcomplete/jquery.textcomplete.js +++ b/library/jquery-textcomplete/jquery.textcomplete.js @@ -1,3 +1,16 @@ +(function (factory) { + if (typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(['jquery'], factory); + } else if (typeof module === "object" && module.exports) { + var $ = require('jquery'); + module.exports = factory($); + } else { + // Browser globals + factory(jQuery); + } +}(function (jQuery) { + /*! * jQuery.textcomplete * @@ -17,13 +30,18 @@ if (typeof jQuery === 'undefined') { if (console.warn) { console.warn(message); } }; + var id = 1; + $.fn.textcomplete = function (strategies, option) { var args = Array.prototype.slice.call(arguments); return this.each(function () { + var self = this; var $this = $(this); var completer = $this.data('textComplete'); if (!completer) { - completer = new $.fn.textcomplete.Completer(this, option || {}); + option || (option = {}); + option._oid = id++; // unique object id + completer = new $.fn.textcomplete.Completer(this, option); $this.data('textComplete', completer); } if (typeof strategies === 'string') { @@ -45,7 +63,10 @@ if (typeof jQuery === 'undefined') { } }); }); - completer.register($.fn.textcomplete.Strategy.parse(strategies)); + completer.register($.fn.textcomplete.Strategy.parse(strategies, { + el: self, + $el: $this + })); } }); }; @@ -115,6 +136,10 @@ if (typeof jQuery === 'undefined') { return Object.prototype.toString.call(obj) === '[object String]'; }; + var isFunction = function (obj) { + return Object.prototype.toString.call(obj) === '[object Function]'; + }; + var uniqueId = 0; function Completer(element, option) { @@ -224,8 +249,10 @@ if (typeof jQuery === 'undefined') { // // value - The selected element of the array callbacked from search func. // strategy - The Strategy object. - select: function (value, strategy) { - this.adapter.select(value, strategy); + // e - Click or keydown event object. + select: function (value, strategy, e) { + this._term = null; + this.adapter.select(value, strategy, e); this.fire('change').fire('textComplete:select', value, strategy); this.adapter.focus(); }, @@ -248,8 +275,9 @@ if (typeof jQuery === 'undefined') { var strategy = this.strategies[i]; var context = strategy.context(text); if (context || context === '') { + var matchRegexp = isFunction(strategy.match) ? strategy.match(text) : strategy.match; if (isString(context)) { text = context; } - var match = text.match(strategy.match); + var match = text.match(matchRegexp); if (match) { return [strategy, match[strategy.index], match]; } } } @@ -262,14 +290,14 @@ if (typeof jQuery === 'undefined') { strategy.search(term, function (data, stillSearching) { if (!self.dropdown.shown) { self.dropdown.activate(); - self.dropdown.setPosition(self.adapter.getCaretPosition()); } if (self._clearAtNext) { // The first callback in the current lock. self.dropdown.clear(); self._clearAtNext = false; } - self.dropdown.render(self._zip(data, strategy)); + self.dropdown.setPosition(self.adapter.getCaretPosition()); + self.dropdown.render(self._zip(data, strategy, term)); if (!stillSearching) { // The last callback in the current lock. free(); @@ -284,9 +312,9 @@ if (typeof jQuery === 'undefined') { // // this._zip(['a', 'b'], 's'); // //=> [{ value: 'a', strategy: 's' }, { value: 'b', strategy: 's' }] - _zip: function (data, strategy) { + _zip: function (data, strategy, term) { return $.map(data, function (value) { - return { value: value, strategy: strategy }; + return { value: value, strategy: strategy, term: term }; }); } }); @@ -297,6 +325,8 @@ if (typeof jQuery === 'undefined') { +function ($) { 'use strict'; + var $window = $(window); + var include = function (zippedData, datum) { var i, elem; var idProperty = datum.strategy.idProperty @@ -320,6 +350,16 @@ if (typeof jQuery === 'undefined') { }); }); + var commands = { + SKIP_DEFAULT: 0, + KEY_UP: 1, + KEY_DOWN: 2, + KEY_ENTER: 3, + KEY_PAGEUP: 4, + KEY_PAGEDOWN: 5, + KEY_ESCAPE: 6 + }; + // Dropdown view // ============= @@ -327,7 +367,7 @@ if (typeof jQuery === 'undefined') { // // element - Textarea or contenteditable element. function Dropdown(element, completer, option) { - this.$el = Dropdown.findOrCreateElement(option); + this.$el = Dropdown.createElement(option); this.completer = completer; this.id = completer.id + 'dropdown'; this._data = []; // zipped data. @@ -338,7 +378,7 @@ if (typeof jQuery === 'undefined') { if (option.listPosition) { this.setPosition = option.listPosition; } if (option.height) { this.$el.height(option.height); } var self = this; - $.each(['maxCount', 'placement', 'footer', 'header', 'className'], function (_i, name) { + $.each(['maxCount', 'placement', 'footer', 'header', 'noResultsMessage', 'className'], function (_i, name) { if (option[name] != null) { self[name] = option[name]; } }); this._bindEvents(element); @@ -349,18 +389,19 @@ if (typeof jQuery === 'undefined') { // Class methods // ------------- - findOrCreateElement: function (option) { + createElement: function (option) { var $parent = option.appendTo; if (!($parent instanceof $)) { $parent = $($parent); } - var $el = $parent.children('.dropdown-menu') - if (!$el.length) { - $el = $('').css({ + var $el = $('
      ') + .addClass('dropdown-menu textcomplete-dropdown') + .attr('id', 'textcomplete-dropdown-' + option._oid) + .css({ display: 'none', left: 0, position: 'absolute', zIndex: option.zIndex - }).appendTo($parent); - } + }) + .appendTo($parent); return $el; } }); @@ -403,23 +444,26 @@ if (typeof jQuery === 'undefined') { this._renderFooter(unzippedData); if (contentsHtml) { this._renderContents(contentsHtml); + this._fitToBottom(); this._activateIndexedItem(); } this._setScroll(); + } else if (this.noResultsMessage) { + this._renderNoResultsMessage(unzippedData); } else if (this.shown) { this.deactivate(); } }, - setPosition: function (position) { - this.$el.css(this._applyPlacement(position)); + setPosition: function (pos) { + this.$el.css(this._applyPlacement(pos)); // Make the dropdown fixed if the input is also fixed // This can't be done during init, as textcomplete may be used on multiple elements on the same page // Because the same dropdown is reused behind the scenes, we need to recheck every time the dropdown is showed var position = 'absolute'; // Check if input or one of its parents has positioning we need to care about - this.$inputEl.add(this.$inputEl.parents()).each(function() { + this.$inputEl.add(this.$inputEl.parents()).each(function() { if($(this).css('position') === 'absolute') // The element has absolute positioning, so it's all OK return false; if($(this).css('position') === 'fixed') { @@ -436,7 +480,7 @@ if (typeof jQuery === 'undefined') { this.$el.html(''); this.data = []; this._index = 0; - this._$header = this._$footer = null; + this._$header = this._$footer = this._$noResultsMessage = null; }, activate: function () { @@ -481,19 +525,25 @@ if (typeof jQuery === 'undefined') { return e.keyCode === 34; // PAGEDOWN }, + isEscape: function (e) { + return e.keyCode === 27; // ESCAPE + }, + // Private properties // ------------------ _data: null, // Currently shown zipped data. _index: null, _$header: null, + _$noResultsMessage: null, _$footer: null, // Private methods // --------------- _bindEvents: function () { - this.$el.on('mousedown.' + this.id, '.textcomplete-item', $.proxy(this._onClick, this)) + this.$el.on('mousedown.' + this.id, '.textcomplete-item', $.proxy(this._onClick, this)); + this.$el.on('touchstart.' + this.id, '.textcomplete-item', $.proxy(this._onClick, this)); this.$el.on('mouseover.' + this.id, '.textcomplete-item', $.proxy(this._onMouseover, this)); this.$inputEl.on('keydown.' + this.id, $.proxy(this._onKeydown, this)); }, @@ -506,11 +556,16 @@ if (typeof jQuery === 'undefined') { $el = $el.closest('.textcomplete-item'); } var datum = this.data[parseInt($el.data('index'), 10)]; - this.completer.select(datum.value, datum.strategy); + this.completer.select(datum.value, datum.strategy, e); var self = this; // Deactive at next tick to allow other event handlers to know whether // the dropdown has been shown or not. - setTimeout(function () { self.deactivate(); }, 0); + setTimeout(function () { + self.deactivate(); + if (e.type === 'touchstart') { + self.$inputEl.focus(); + } + }, 0); }, // Activate hovered item. @@ -526,21 +581,58 @@ if (typeof jQuery === 'undefined') { _onKeydown: function (e) { if (!this.shown) { return; } + + var command; + + if ($.isFunction(this.option.onKeydown)) { + command = this.option.onKeydown(e, commands); + } + + if (command == null) { + command = this._defaultKeydown(e); + } + + switch (command) { + case commands.KEY_UP: + e.preventDefault(); + this._up(); + break; + case commands.KEY_DOWN: + e.preventDefault(); + this._down(); + break; + case commands.KEY_ENTER: + e.preventDefault(); + this._enter(e); + break; + case commands.KEY_PAGEUP: + e.preventDefault(); + this._pageup(); + break; + case commands.KEY_PAGEDOWN: + e.preventDefault(); + this._pagedown(); + break; + case commands.KEY_ESCAPE: + e.preventDefault(); + this.deactivate(); + break; + } + }, + + _defaultKeydown: function (e) { if (this.isUp(e)) { - e.preventDefault(); - this._up(); + return commands.KEY_UP; } else if (this.isDown(e)) { - e.preventDefault(); - this._down(); + return commands.KEY_DOWN; } else if (this.isEnter(e)) { - e.preventDefault(); - this._enter(); + return commands.KEY_ENTER; } else if (this.isPageup(e)) { - e.preventDefault(); - this._pageup(); + return commands.KEY_PAGEUP; } else if (this.isPagedown(e)) { - e.preventDefault(); - this._pagedown(); + return commands.KEY_PAGEDOWN; + } else if (this.isEscape(e)) { + return commands.KEY_ESCAPE; } }, @@ -564,10 +656,10 @@ if (typeof jQuery === 'undefined') { this._setScroll(); }, - _enter: function () { + _enter: function (e) { var datum = this.data[parseInt(this._getActiveElement().data('index'), 10)]; - this.completer.select(datum.value, datum.strategy); - this._setScroll(); + this.completer.select(datum.value, datum.strategy, e); + this.deactivate(); }, _pageup: function () { @@ -630,7 +722,7 @@ if (typeof jQuery === 'undefined') { index = this.data.length; this.data.push(datum); html += '
    • '; - html += datum.strategy.template(datum.value); + html += datum.strategy.template(datum.value, datum.term); html += '
    • '; } return html; @@ -656,6 +748,16 @@ if (typeof jQuery === 'undefined') { } }, + _renderNoResultsMessage: function (unzippedData) { + if (this.noResultsMessage) { + if (!this._$noResultsMessage) { + this._$noResultsMessage = $('
    • ').appendTo(this.$el); + } + var html = $.isFunction(this.noResultsMessage) ? this.noResultsMessage(unzippedData) : this.noResultsMessage; + this._$noResultsMessage.html(html); + } + }, + _renderContents: function (html) { if (this._$footer) { this._$footer.before(html); @@ -664,7 +766,15 @@ if (typeof jQuery === 'undefined') { } }, - _applyPlacement: function (position) { + _fitToBottom: function() { + var windowScrollBottom = $window.scrollTop() + $window.height(); + var height = this.$el.height(); + if ((this.$el.position().top + height) > windowScrollBottom) { + this.$el.offset({top: windowScrollBottom - height}); + } + }, + + _applyPlacement: function (position) { // If the 'placement' option set to 'top', move the position above the element. if (this.placement.indexOf('top') !== -1) { // Overwrite the position object to set the 'bottom' property instead of the top. @@ -688,6 +798,7 @@ if (typeof jQuery === 'undefined') { }); $.fn.textcomplete.Dropdown = Dropdown; + $.extend($.fn.textcomplete, commands); }(jQuery); +function ($) { @@ -713,9 +824,12 @@ if (typeof jQuery === 'undefined') { if (this.cache) { this.search = memoize(this.search); } } - Strategy.parse = function (optionsArray) { - return $.map(optionsArray, function (options) { - return new Strategy(options); + Strategy.parse = function (strategiesArray, params) { + return $.map(strategiesArray, function (strategy) { + var strategyObj = new Strategy(strategy); + strategyObj.el = params.el; + strategyObj.$el = params.$el; + return strategyObj; }); }; @@ -848,6 +962,7 @@ if (typeof jQuery === 'undefined') { // Suppress searching if it returns true. _skipSearch: function (clickEvent) { switch (clickEvent.keyCode) { + case 13: // ENTER case 40: // DOWN case 38: // UP return true; @@ -894,17 +1009,19 @@ if (typeof jQuery === 'undefined') { // -------------- // Update the textarea with the given value and strategy. - select: function (value, strategy) { + select: function (value, strategy, e) { var pre = this.getTextFromHeadToCaret(); var post = this.el.value.substring(this.el.selectionEnd); - var newSubstr = strategy.replace(value); - if ($.isArray(newSubstr)) { - post = newSubstr[1] + post; - newSubstr = newSubstr[0]; + var newSubstr = strategy.replace(value, e); + if (typeof newSubstr !== 'undefined') { + if ($.isArray(newSubstr)) { + post = newSubstr[1] + post; + newSubstr = newSubstr[0]; + } + pre = pre.replace(strategy.match, newSubstr); + this.$el.val(pre + post); + this.el.selectionStart = this.el.selectionEnd = pre.length; } - pre = pre.replace(strategy.match, newSubstr); - this.$el.val(pre + post); - this.el.selectionStart = this.el.selectionEnd = pre.length; }, // Private methods @@ -981,22 +1098,24 @@ if (typeof jQuery === 'undefined') { // Public methods // -------------- - select: function (value, strategy) { + select: function (value, strategy, e) { var pre = this.getTextFromHeadToCaret(); var post = this.el.value.substring(pre.length); - var newSubstr = strategy.replace(value); - if ($.isArray(newSubstr)) { - post = newSubstr[1] + post; - newSubstr = newSubstr[0]; + var newSubstr = strategy.replace(value, e); + if (typeof newSubstr !== 'undefined') { + if ($.isArray(newSubstr)) { + post = newSubstr[1] + post; + newSubstr = newSubstr[0]; + } + pre = pre.replace(strategy.match, newSubstr); + this.$el.val(pre + post); + this.el.focus(); + var range = this.el.createTextRange(); + range.collapse(true); + range.moveEnd('character', pre.length); + range.moveStart('character', pre.length); + range.select(); } - pre = pre.replace(strategy.match, newSubstr); - this.$el.val(pre + post); - this.el.focus(); - var range = this.el.createTextRange(); - range.collapse(true); - range.moveEnd('character', pre.length); - range.moveStart('character', pre.length); - range.select(); }, getTextFromHeadToCaret: function () { @@ -1032,7 +1151,7 @@ if (typeof jQuery === 'undefined') { // Update the content with the given value and strategy. // When an dropdown item is selected, it is executed. - select: function (value, strategy) { + select: function (value, strategy, e) { var pre = this.getTextFromHeadToCaret(); var sel = window.getSelection() var range = sel.getRangeAt(0); @@ -1040,20 +1159,22 @@ if (typeof jQuery === 'undefined') { selection.selectNodeContents(range.startContainer); var content = selection.toString(); var post = content.substring(range.startOffset); - var newSubstr = strategy.replace(value); - if ($.isArray(newSubstr)) { - post = newSubstr[1] + post; - newSubstr = newSubstr[0]; + var newSubstr = strategy.replace(value, e); + if (typeof newSubstr !== 'undefined') { + if ($.isArray(newSubstr)) { + post = newSubstr[1] + post; + newSubstr = newSubstr[0]; + } + pre = pre.replace(strategy.match, newSubstr); + range.selectNodeContents(range.startContainer); + range.deleteContents(); + var node = document.createTextNode(pre + post); + range.insertNode(node); + range.setStart(node, pre.length); + range.collapse(true); + sel.removeAllRanges(); + sel.addRange(range); } - pre = pre.replace(strategy.match, newSubstr); - range.selectNodeContents(range.startContainer); - range.deleteContents(); - var node = document.createTextNode(pre + post); - range.insertNode(node); - range.setStart(node, pre.length); - range.collapse(true); - sel.removeAllRanges(); - sel.addRange(range); }, // Private methods @@ -1079,8 +1200,7 @@ if (typeof jQuery === 'undefined') { position.left -= this.$el.offset().left; position.top += $node.height() - this.$el.offset().top; position.lineHeight = $node.height(); - var dir = this.$el.attr('dir') || this.$el.css('direction'); - if (dir === 'rtl') { position.left -= this.listView.$el.width(); } + $node.remove(); return position; }, @@ -1102,3 +1222,6 @@ if (typeof jQuery === 'undefined') { $.fn.textcomplete.ContentEditable = ContentEditable; }(jQuery); + +return jQuery; +})); From 12479fb47809544557c674cfde44959f92adb16a Mon Sep 17 00:00:00 2001 From: rabuzarus <> Date: Mon, 1 Feb 2016 17:43:12 +0100 Subject: [PATCH 008/211] rework autocomplete: Access list of smilies over JSON --- include/network.php | 15 ++++ include/smilies.php | 179 ++++++++++++++++++++++++++++++++++++++++++++ include/text.php | 157 +------------------------------------- mod/smilies.php | 20 ++++- 4 files changed, 215 insertions(+), 156 deletions(-) create mode 100644 include/smilies.php diff --git a/include/network.php b/include/network.php index c6379e407b..84f1297e03 100644 --- a/include/network.php +++ b/include/network.php @@ -1344,3 +1344,18 @@ function short_link($url) { } return $slinky->short(); }}; + +/** + * @brief Encodes content to json + * + * This function encodes an array to json format + * and adds an application/json HTTP header to the output. + * After finishing the process is getting killed. + * + * @param array $x + */ +function json_return_and_die($x) { + header("content-type: application/json"); + echo json_encode($x); + killme(); +} diff --git a/include/smilies.php b/include/smilies.php new file mode 100644 index 0000000000..7b3feefca8 --- /dev/null +++ b/include/smilies.php @@ -0,0 +1,179 @@ + smilie shortcut + * 'icons' => icon in html + * + * @hook smilie ('texts' => smilies texts array, 'icons' => smilies html array) + */ + public static function list_smilies() { + + $texts = array( + '<3', + '</3', + '<\\3', + ':-)', + ';-)', + ':-(', + ':-P', + ':-p', + ':-"', + ':-"', + ':-x', + ':-X', + ':-D', + '8-|', + '8-O', + ':-O', + '\\o/', + 'o.O', + 'O.o', + 'o_O', + 'O_o', + ":'(", + ":-!", + ":-/", + ":-[", + "8-)", + ':beer', + ':homebrew', + ':coffee', + ':facepalm', + ':like', + ':dislike', + '~friendica', + 'red#', + 'red#matrix' + + ); + + $icons = array( + '<3', + '</3', + '<\\3', + ':-)', + ';-)', + ':-(', + ':-P', + ':-p', + ':-\', + ':-\', + ':-x', + ':-X', + ':-D', + '8-|', + '8-O', + ':-O', + '\\o/', + 'o.O', + 'O.o', + 'o_O', + 'O_o', + ':\'(', + ':-!', + ':-/', + ':-[', + '8-)', + ':beer', + ':homebrew', + ':coffee', + ':facepalm', + ':like', + ':dislike', + '~friendica ~friendica', + 'redredmatrix', + 'redredmatrix' + ); + + $params = array('texts' => $texts, 'icons' => $icons); + call_hooks('smilie', $params); + + return $params; + + } + + /** + * @brief Replaces text emoticons with graphical images + * + * It is expected that this function will be called using HTML text. + * We will escape text between HTML pre and code blocks from being + * processed. + * + * At a higher level, the bbcode [nosmile] tag can be used to prevent this + * function from being executed by the prepare_text() routine when preparing + * bbcode source for HTML display + * + * @param string $s + * @param boolean $sample + * + * @return string + */ + public static function replace($s, $sample = false) { + if(intval(get_config('system','no_smilies')) + || (local_user() && intval(get_pconfig(local_user(),'system','no_smilies')))) + return $s; + + $s = preg_replace_callback('/
      (.*?)<\/pre>/ism','self::smile_encode',$s);
      +		$s = preg_replace_callback('/(.*?)<\/code>/ism','self::smile_encode',$s);
      +
      +		$params = self::list_smilies();
      +		$params['string'] = $s;
      +
      +		if($sample) {
      +			$s = '
      '; + for($x = 0; $x < count($params['texts']); $x ++) { + $s .= '
      ' . $params['texts'][$x] . '
      ' . $params['icons'][$x] . '
      '; + } + } + else { + $params['string'] = preg_replace_callback('/<(3+)/','self::preg_heart',$params['string']); + $s = str_replace($params['texts'],$params['icons'],$params['string']); + } + + $s = preg_replace_callback('/
      (.*?)<\/pre>/ism','self::smile_decode',$s);
      +		$s = preg_replace_callback('/(.*?)<\/code>/ism','self::smile_decode',$s);
      +
      +		return $s;
      +	}
      +
      +	private function smile_encode($m) {
      +		return(str_replace($m[1],base64url_encode($m[1]),$m[0]));
      +	}
      +
      +	private function smile_decode($m) {
      +		return(str_replace($m[1],base64url_decode($m[1]),$m[0]));
      +	}
      +
      +
      +	/**
      +	 * @brief expand <3333 to the correct number of hearts
      +	 *
      +	 * @param string $x
      +	 * @return string
      +	 */
      +	private function preg_heart($x) {
      +		if(strlen($x[1]) == 1)
      +			return $x[0];
      +		$t = '';
      +		for($cnt = 0; $cnt < strlen($x[1]); $cnt ++)
      +			$t .= '<3';
      +		$r =  str_replace($x[0],$t,$x[0]);
      +		return $r;
      +	}
      +
      +}
      diff --git a/include/text.php b/include/text.php
      index 07524e851d..4e56ad1058 100644
      --- a/include/text.php
      +++ b/include/text.php
      @@ -2,6 +2,7 @@
       
       require_once("include/template_processor.php");
       require_once("include/friendica_smarty.php");
      +require_once("include/smilies.php");
       require_once("include/map.php");
       require_once("mod/proxy.php");
       
      @@ -1079,160 +1080,6 @@ function get_mood_verbs() {
       	return $arr;
       }
       
      -
      -
      -if(! function_exists('smilies')) {
      -/**
      - * Replaces text emoticons with graphical images
      - *
      - * It is expected that this function will be called using HTML text.
      - * We will escape text between HTML pre and code blocks from being
      - * processed.
      - *
      - * At a higher level, the bbcode [nosmile] tag can be used to prevent this
      - * function from being executed by the prepare_text() routine when preparing
      - * bbcode source for HTML display
      - *
      - * @param string $s
      - * @param boolean $sample
      - * @return string
      - * @hook smilie ('texts' => smilies texts array, 'icons' => smilies html array, 'string' => $s)
      - */
      -function smilies($s, $sample = false) {
      -	$a = get_app();
      -
      -	if(intval(get_config('system','no_smilies'))
      -		|| (local_user() && intval(get_pconfig(local_user(),'system','no_smilies'))))
      -		return $s;
      -
      -	$s = preg_replace_callback('/
      (.*?)<\/pre>/ism','smile_encode',$s);
      -	$s = preg_replace_callback('/(.*?)<\/code>/ism','smile_encode',$s);
      -
      -	$texts =  array(
      -		'<3',
      -		'</3',
      -		'<\\3',
      -		':-)',
      -		';-)',
      -		':-(',
      -		':-P',
      -		':-p',
      -		':-"',
      -		':-"',
      -		':-x',
      -		':-X',
      -		':-D',
      -		'8-|',
      -		'8-O',
      -		':-O',
      -		'\\o/',
      -		'o.O',
      -		'O.o',
      -		'o_O',
      -		'O_o',
      -		":'(",
      -		":-!",
      -		":-/",
      -		":-[",
      -		"8-)",
      -		':beer',
      -		':homebrew',
      -		':coffee',
      -		':facepalm',
      -		':like',
      -		':dislike',
      -		'~friendica',
      -		'red#',
      -		'red#matrix'
      -
      -	);
      -
      -	$icons = array(
      -		'<3',
      -		'</3',
      -		'<\\3',
      -		':-)',
      -		';-)',
      -		':-(',
      -		':-P',
      -		':-p',
      -		':-\',
      -		':-\',
      -		':-x',
      -		':-X',
      -		':-D',
      -		'8-|',
      -		'8-O',
      -		':-O',
      -		'\\o/',
      -		'o.O',
      -		'O.o',
      -		'o_O',
      -		'O_o',
      -		':\'(',
      -		':-!',
      -		':-/',
      -		':-[',
      -		'8-)',
      -		':beer',
      -		':homebrew',
      -		':coffee',
      -		':facepalm',
      -		':like',
      -		':dislike',
      -		'~friendica ~friendica',
      -		'redredmatrix',
      -		'redredmatrix'
      -	);
      -
      -	$params = array('texts' => $texts, 'icons' => $icons, 'string' => $s);
      -	call_hooks('smilie', $params);
      -
      -	if($sample) {
      -		$s = '
      '; - for($x = 0; $x < count($params['texts']); $x ++) { - $s .= '
      ' . $params['texts'][$x] . '
      ' . $params['icons'][$x] . '
      '; - } - } - else { - $params['string'] = preg_replace_callback('/<(3+)/','preg_heart',$params['string']); - $s = str_replace($params['texts'],$params['icons'],$params['string']); - } - - $s = preg_replace_callback('/
      (.*?)<\/pre>/ism','smile_decode',$s);
      -	$s = preg_replace_callback('/(.*?)<\/code>/ism','smile_decode',$s);
      -
      -	return $s;
      -
      -}}
      -
      -function smile_encode($m) {
      -	return(str_replace($m[1],base64url_encode($m[1]),$m[0]));
      -}
      -
      -function smile_decode($m) {
      -	return(str_replace($m[1],base64url_decode($m[1]),$m[0]));
      -}
      -
      -
      -/**
      - * expand <3333 to the correct number of hearts
      - *
      - * @param string $x
      - * @return string
      - */
      -function preg_heart($x) {
      -	$a = get_app();
      -	if(strlen($x[1]) == 1)
      -		return $x[0];
      -	$t = '';
      -	for($cnt = 0; $cnt < strlen($x[1]); $cnt ++)
      -		$t .= '<3';
      -	$r =  str_replace($x[0],$t,$x[0]);
      -	return $r;
      -}
      -
      -
       if(! function_exists('day_translate')) {
       /**
        * Translate days and months names
      @@ -1549,7 +1396,7 @@ function prepare_text($text) {
       	if(stristr($text,'[nosmile]'))
       		$s = bbcode($text);
       	else
      -		$s = smilies(bbcode($text));
      +		$s = smilies::replace(bbcode($text));
       
       	return trim($s);
       }}
      diff --git a/mod/smilies.php b/mod/smilies.php
      index c47f95da76..5a157e3686 100644
      --- a/mod/smilies.php
      +++ b/mod/smilies.php
      @@ -1,3 +1,21 @@
       argv[1]==="json"){
      +		$tmp = smilies::list_smilies();
      +		$results = array();
      +		for($i = 0; $i < count($tmp['texts']); $i++) {
      +			$results[] = array('text' => $tmp['texts'][$i], 'icon' => $tmp['icons'][$i]);
      +		}
      +		json_return_and_die($results);
      +	}
      +	else {
      +		return smilies('',true);
      +	}
      +}
      
      From 341a22800dc73c0a3371e1835e33ad91722aeca6 Mon Sep 17 00:00:00 2001
      From: rabuzarus <>
      Date: Mon, 1 Feb 2016 18:21:29 +0100
      Subject: [PATCH 009/211] rework autocomplete: add class dir and static
       function
      
      ---
       include/acl_selectors.php        |  4 +-
       include/dir_fns.php              | 74 +++++++++++++++++---------------
       view/templates/contacts-head.tpl | 12 ------
       3 files changed, 42 insertions(+), 48 deletions(-)
      
      diff --git a/include/acl_selectors.php b/include/acl_selectors.php
      index 46f737e4c2..b5c43e38e6 100644
      --- a/include/acl_selectors.php
      +++ b/include/acl_selectors.php
      @@ -659,7 +659,7 @@ function acl_lookup(&$a, $out_type = 'json') {
        * @brief Searching for global contacts for autocompletion
        * 
        * @param App $a
      - * @return type
      + * @return array
        */
       function navbar_complete(&$a) {
       
      @@ -682,7 +682,7 @@ function navbar_complete(&$a) {
       		$search = substr($search,1);
       
       	if($localsearch) {
      -		$x = dirsearch_global_by_name($search);
      +		$x = dir::global_search_by_name($search);
       		return $x;
       	}
       
      diff --git a/include/dir_fns.php b/include/dir_fns.php
      index 2e39e7ddbf..b85cb5b2d0 100644
      --- a/include/dir_fns.php
      +++ b/include/dir_fns.php
      @@ -2,45 +2,51 @@
       
       /**
        * @file include/dir_fns.php
      - * @brief Functions for directory
        */
       
      +
       /**
      - * @brief Search global contact table by nick or name
      - *  * 
      - * @param string $search
      - * @return array
      + * @brief This class handels directory related functions
        */
      -function dirsearch_global_by_name($search) {
      +class dir {
       
      -	if($search) {
      -		// check supported networks
      -		if (get_config('system','diaspora_enabled'))
      -			$diaspora = NETWORK_DIASPORA;
      -		else
      -			$diaspora = NETWORK_DFRN;
      +	/**
      +	 * @brief Search global contact table by nick or name
      +	 *  * 
      +	 * @param string $search Name or nick
      +	 * @return array
      +	 */
      +	public static function global_search_by_name($search) {
       
      -		if (!get_config('system','ostatus_disabled'))
      -			$ostatus = NETWORK_OSTATUS;
      -		else
      -			$ostatus = NETWORK_DFRN;
      +		if($search) {
      +			// check supported networks
      +			if (get_config('system','diaspora_enabled'))
      +				$diaspora = NETWORK_DIASPORA;
      +			else
      +				$diaspora = NETWORK_DFRN;
      +
      +			if (!get_config('system','ostatus_disabled'))
      +				$ostatus = NETWORK_OSTATUS;
      +			else
      +				$ostatus = NETWORK_DFRN;
      +
      +			$results = q("SELECT `contact`.`id` AS `cid`, `gcontact`.`url`, `gcontact`.`name`, `gcontact`.`nick`, `gcontact`.`photo`,
      +							`gcontact`.`network`, `gcontact`.`keywords`, `gcontact`.`addr`
      +						FROM `gcontact`
      +						LEFT JOIN `contact` ON `contact`.`nurl` = `gcontact`.`nurl`
      +							AND `contact`.`uid` = %d AND NOT `contact`.`blocked`
      +							AND NOT `contact`.`pending` AND `contact`.`rel` IN ('%s', '%s')
      +						WHERE (`contact`.`id` > 0 OR (NOT `gcontact`.`hide` AND `gcontact`.`network` IN ('%s', '%s', '%s') AND
      +						((`gcontact`.`last_contact` >= `gcontact`.`last_failure`) OR (`gcontact`.`updated` >= `gcontact`.`last_failure`)))) AND
      +						(`gcontact`.`url` REGEXP '%s' OR `gcontact`.`name` REGEXP '%s' OR `gcontact`.`nick` REGEXP '%s'
      +							) 
      +							GROUP BY `gcontact`.`nurl`
      +							ORDER BY `gcontact`.`updated` DESC ",
      +						intval(local_user()), dbesc(CONTACT_IS_SHARING), dbesc(CONTACT_IS_FRIEND),
      +						dbesc(NETWORK_DFRN), dbesc($ostatus), dbesc($diaspora),
      +						dbesc(escape_tags($search)), dbesc(escape_tags($search)), dbesc(escape_tags($search)));
      +			return $results;
      +		}
       
      -		$results = q("SELECT `contact`.`id` AS `cid`, `gcontact`.`url`, `gcontact`.`name`, `gcontact`.`nick`, `gcontact`.`photo`,
      -						`gcontact`.`network`, `gcontact`.`keywords`, `gcontact`.`addr`
      -					FROM `gcontact`
      -					LEFT JOIN `contact` ON `contact`.`nurl` = `gcontact`.`nurl`
      -						AND `contact`.`uid` = %d AND NOT `contact`.`blocked`
      -						AND NOT `contact`.`pending` AND `contact`.`rel` IN ('%s', '%s')
      -					WHERE (`contact`.`id` > 0 OR (NOT `gcontact`.`hide` AND `gcontact`.`network` IN ('%s', '%s', '%s') AND
      -					((`gcontact`.`last_contact` >= `gcontact`.`last_failure`) OR (`gcontact`.`updated` >= `gcontact`.`last_failure`)))) AND
      -					(`gcontact`.`url` REGEXP '%s' OR `gcontact`.`name` REGEXP '%s' OR `gcontact`.`nick` REGEXP '%s'
      -						) 
      -						GROUP BY `gcontact`.`nurl`
      -						ORDER BY `gcontact`.`updated` DESC ",
      -					intval(local_user()), dbesc(CONTACT_IS_SHARING), dbesc(CONTACT_IS_FRIEND),
      -					dbesc(NETWORK_DFRN), dbesc($ostatus), dbesc($diaspora),
      -					dbesc(escape_tags($search)), dbesc(escape_tags($search)), dbesc(escape_tags($search)));
      -		return $results;
       	}
      -		
      -}
      \ No newline at end of file
      +}
      diff --git a/view/templates/contacts-head.tpl b/view/templates/contacts-head.tpl
      index 25055d405b..ea562233f4 100644
      --- a/view/templates/contacts-head.tpl
      +++ b/view/templates/contacts-head.tpl
      @@ -2,18 +2,6 @@
       
       
      
      From 3b305ad8efddbd3c77986f41a00485c47bb2bfde Mon Sep 17 00:00:00 2001
      From: rabuzarus <>
      Date: Mon, 1 Feb 2016 18:22:26 +0100
      Subject: [PATCH 010/211] rework autocomplete: add bbcode autocompletion to
       editor
      
      ---
       js/autocomplete.js | 25 +++++++++++++++++++++----
       1 file changed, 21 insertions(+), 4 deletions(-)
      
      diff --git a/js/autocomplete.js b/js/autocomplete.js
      index 1f7df011d3..6c75f17ca6 100644
      --- a/js/autocomplete.js
      +++ b/js/autocomplete.js
      @@ -1,8 +1,15 @@
       /**
      - * Friendica people autocomplete
      + * @brief Friendica people autocomplete
        *
        * require jQuery, jquery.textcomplete
      + * 
      + * for further documentation look at:
      + * http://yuku-t.com/jquery-textcomplete/
      + * 
      + * https://github.com/yuku-t/jquery-textcomplete/blob/master/doc/how_to_use.md
        */
      +
      +
       function contact_search(term, callback, backend_url, type) {
       
       	// Check if there is a conversation id to include the unkonwn contacts of the conversation
      @@ -107,6 +114,9 @@ function submit_form(e) {
       (function( $ ) {
       	$.fn.editor_autocomplete = function(backend_url) {
       
      +		// list of supported bbtags
      +		var bbelements = ['b', 'u', 'i', 'img', 'url', 'quote', 'code', 'spoiler', 'audio', 'video', 'youtube', 'map', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 's', 'o', 'list', 'center', 'nosmile', 'vimeo' ];
      +
       		// Autocomplete contacts
       		contacts = {
       			match: /(^|\s)(@\!*)([^ \n]+)$/,
      @@ -119,12 +129,19 @@ function submit_form(e) {
       		smilies = {
       			match: /(^|\s)(:[a-z]{2,})$/,
       			index: 2,
      -			search: function(term, callback) { $.getJSON('/smilies/json').done(function(data) { callback($.map(data, function(entry) { return entry.text.indexOf(term) === 0 ? entry : null; })); }); },
      -			template: function(item) { return item.icon + item.text; },
      +			search: function(term, callback) { $.getJSON('smilies/json').done(function(data) { callback($.map(data, function(entry) { return entry.text.indexOf(term) === 0 ? entry : null; })); }); },
      +			template: function(item) { return item.icon + ' ' + item.text; },
       			replace: function(item) { return "$1" + item.text + ' '; },
       		};
      +
      +		bbtags = {
      +			match: /\[(\w*)$/,
      +			index: 1,
      +			search: function (term, callback) { callback($.map(bbelements, function (element) { return element.indexOf(term) === 0 ? element : null; })); },
      +			replace: function (element) { return ['[' + element + ']', '[/' + element + ']']; },
      +		};
       		this.attr('autocomplete','off');
      -		this.textcomplete([contacts,smilies], {className:'acpopup', zIndex:1020});
      +		this.textcomplete([contacts,smilies, bbtags], {className:'acpopup', zIndex:1020});
       	};
       })( jQuery );
       
      
      From ac912eaadfe49cd9b9f8ac903d64685706dcb717 Mon Sep 17 00:00:00 2001
      From: rabuzarus <>
      Date: Tue, 2 Feb 2016 22:33:14 +0100
      Subject: [PATCH 011/211] rework autocomplete: add NavBar forum search
      
      ---
       include/acl_selectors.php | 14 ++++++++------
       include/dir_fns.php       | 14 ++++++++++----
       js/autocomplete.js        | 21 +++++++++++++++++----
       3 files changed, 35 insertions(+), 14 deletions(-)
      
      diff --git a/include/acl_selectors.php b/include/acl_selectors.php
      index b5c43e38e6..9e1a2642a4 100644
      --- a/include/acl_selectors.php
      +++ b/include/acl_selectors.php
      @@ -395,11 +395,12 @@ function acl_lookup(&$a, $out_type = 'json') {
       	if(!local_user())
       		return "";
       
      -	$start = (x($_REQUEST,'start')?$_REQUEST['start']:0);
      -	$count = (x($_REQUEST,'count')?$_REQUEST['count']:100);
      -	$search = (x($_REQUEST,'search')?$_REQUEST['search']:"");
      -	$type = (x($_REQUEST,'type')?$_REQUEST['type']:"");
      -	$conv_id = (x($_REQUEST,'conversation')?$_REQUEST['conversation']:null);
      +	$start	=	(x($_REQUEST,'start')		? $_REQUEST['start']		: 0);
      +	$count	=	(x($_REQUEST,'count')		? $_REQUEST['count']		: 100);
      +	$search	 =	(x($_REQUEST,'search')		? $_REQUEST['search']		: "");
      +	$type	=	(x($_REQUEST,'type')		? $_REQUEST['type']		: "");
      +	$mode	=	(x($_REQUEST,'mode')		? $_REQUEST['mode']		: "");
      +	$conv_id =	(x($_REQUEST,'conversation')	? $_REQUEST['conversation']	: null);
       
       	// For use with jquery.textcomplete for private mail completion
       
      @@ -673,6 +674,7 @@ function navbar_complete(&$a) {
       	$localsearch = get_config('system','poco_local_search');
       
       	$search = $prefix.notags(trim($_REQUEST['search']));
      +	$mode = $_REQUEST['mode'];
       
       	// don't search if search term has less than 2 characters
       	if(! $search || mb_strlen($search) < 2)
      @@ -682,7 +684,7 @@ function navbar_complete(&$a) {
       		$search = substr($search,1);
       
       	if($localsearch) {
      -		$x = dir::global_search_by_name($search);
      +		$x = dir::global_search_by_name($search, $mode);
       		return $x;
       	}
       
      diff --git a/include/dir_fns.php b/include/dir_fns.php
      index b85cb5b2d0..d258058763 100644
      --- a/include/dir_fns.php
      +++ b/include/dir_fns.php
      @@ -14,9 +14,10 @@ class dir {
       	 * @brief Search global contact table by nick or name
       	 *  * 
       	 * @param string $search Name or nick
      +	 * @param string $mode Search mode 
       	 * @return array
       	 */
      -	public static function global_search_by_name($search) {
      +	public static function global_search_by_name($search, $mode = '') {
       
       		if($search) {
       			// check supported networks
      @@ -30,6 +31,12 @@ class dir {
       			else
       				$ostatus = NETWORK_DFRN;
       
      +			// check if fo
      +			if($mode === "community")
      +				$extra_sql = " AND `community`";
      +			else
      +				$extra_sql = "";
      +
       			$results = q("SELECT `contact`.`id` AS `cid`, `gcontact`.`url`, `gcontact`.`name`, `gcontact`.`nick`, `gcontact`.`photo`,
       							`gcontact`.`network`, `gcontact`.`keywords`, `gcontact`.`addr`
       						FROM `gcontact`
      @@ -38,13 +45,12 @@ class dir {
       							AND NOT `contact`.`pending` AND `contact`.`rel` IN ('%s', '%s')
       						WHERE (`contact`.`id` > 0 OR (NOT `gcontact`.`hide` AND `gcontact`.`network` IN ('%s', '%s', '%s') AND
       						((`gcontact`.`last_contact` >= `gcontact`.`last_failure`) OR (`gcontact`.`updated` >= `gcontact`.`last_failure`)))) AND
      -						(`gcontact`.`url` REGEXP '%s' OR `gcontact`.`name` REGEXP '%s' OR `gcontact`.`nick` REGEXP '%s'
      -							) 
      +						(`gcontact`.`name` REGEXP '%s' OR `gcontact`.`nick` REGEXP '%s') $extra_sql
       							GROUP BY `gcontact`.`nurl`
       							ORDER BY `gcontact`.`updated` DESC ",
       						intval(local_user()), dbesc(CONTACT_IS_SHARING), dbesc(CONTACT_IS_FRIEND),
       						dbesc(NETWORK_DFRN), dbesc($ostatus), dbesc($diaspora),
      -						dbesc(escape_tags($search)), dbesc(escape_tags($search)), dbesc(escape_tags($search)));
      +						dbesc(escape_tags($search)), dbesc(escape_tags($search)));
       			return $results;
       		}
       
      diff --git a/js/autocomplete.js b/js/autocomplete.js
      index 6c75f17ca6..aa8b6836c5 100644
      --- a/js/autocomplete.js
      +++ b/js/autocomplete.js
      @@ -10,12 +10,11 @@
        */
       
       
      -function contact_search(term, callback, backend_url, type) {
      +function contact_search(term, callback, backend_url, type, mode) {
       
       	// Check if there is a conversation id to include the unkonwn contacts of the conversation
       	var conv_id = document.activeElement.id.match(/\d+$/);
       
      -
       	// Check if there is a cached result that contains the same information we would get with a full server-side search
       	var bt = backend_url+type;
       	if(!(bt in contact_search.cache)) contact_search.cache[bt] = {};
      @@ -41,6 +40,9 @@ function contact_search(term, callback, backend_url, type) {
       	if(conv_id !== null)
       		postdata['conversation'] = conv_id[0];
       
      +	if(mode !== null)
      +		postdata['mode'] = mode;
      +
       
       	$.ajax({
       		type:'POST',
      @@ -126,6 +128,7 @@ function submit_form(e) {
       			template: contact_format,
       		};
       
      +		// Autocomplete smilies e.g. ":like"
       		smilies = {
       			match: /(^|\s)(:[a-z]{2,})$/,
       			index: 2,
      @@ -134,6 +137,7 @@ function submit_form(e) {
       			replace: function(item) { return "$1" + item.text + ' '; },
       		};
       
      +		// Autocomplete BBTags
       		bbtags = {
       			match: /\[(\w*)$/,
       			index: 1,
      @@ -154,12 +158,21 @@ function submit_form(e) {
       		contacts = {
       			match: /(^@)([^\n]{2,})$/,
       			index: 2,
      -			search: function(term, callback) { contact_search(term, callback, backend_url, 'x'); },
      +			search: function(term, callback) { contact_search(term, callback, backend_url, 'x', 'contact'); },
      +			replace: basic_replace,
      +			template: contact_format,
      +		};
      +
      +		// Autocomplete forum accounts
      +		community = {
      +			match: /(^!)([^\n]{2,})$/,
      +			index: 2,
      +			search: function(term, callback) { contact_search(term, callback, backend_url, 'x', 'community'); },
       			replace: basic_replace,
       			template: contact_format,
       		};
       		this.attr('autocomplete', 'off');
      -		var a = this.textcomplete([contacts], {className:'acpopup', maxCount:100, zIndex: 1020, appendTo:'nav'});
      +		var a = this.textcomplete([contacts, community], {className:'acpopup', maxCount:100, zIndex: 1020, appendTo:'#nav-search-box'});
       		a.on('textComplete:select', function(e, value, strategy) { submit_form(this); });
       	};
       })( jQuery );
      
      From 2bd60075aee57eebd6577f5eb7c24f750bc39c07 Mon Sep 17 00:00:00 2001
      From: rabuzarus <>
      Date: Wed, 3 Feb 2016 00:25:33 +0100
      Subject: [PATCH 012/211] rework autocomplete: some styling if contact is forum
      
      ---
       include/acl_selectors.php | 11 +++++++----
       include/dir_fns.php       |  2 +-
       js/autocomplete.js        |  5 +++--
       view/theme/vier/style.css | 11 +++++++++++
       4 files changed, 22 insertions(+), 7 deletions(-)
      
      diff --git a/include/acl_selectors.php b/include/acl_selectors.php
      index 9e1a2642a4..99848aa193 100644
      --- a/include/acl_selectors.php
      +++ b/include/acl_selectors.php
      @@ -509,7 +509,7 @@ function acl_lookup(&$a, $out_type = 'json') {
       
       	if ($type==''){
       
      -		$r = q("SELECT `id`, `name`, `nick`, `micro`, `network`, `url`, `attag`, `forum` FROM `contact`
      +		$r = q("SELECT `id`, `name`, `nick`, `micro`, `network`, `url`, `attag`, `forum`, `prv` FROM `contact`
       			WHERE `uid` = %d AND `self` = 0 AND `blocked` = 0 AND `pending` = 0 AND `archive` = 0 AND `notify` != ''
       			AND NOT (`network` IN ('%s', '%s'))
       			$sql_extra2
      @@ -520,7 +520,7 @@ function acl_lookup(&$a, $out_type = 'json') {
       	}
       	elseif ($type=='c'){
       
      -		$r = q("SELECT `id`, `name`, `nick`, `micro`, `network`, `url`, `attag`, `forum` FROM `contact`
      +		$r = q("SELECT `id`, `name`, `nick`, `micro`, `network`, `url`, `attag`, `forum`, `prv` FROM `contact`
       			WHERE `uid` = %d AND `self` = 0 AND `blocked` = 0 AND `pending` = 0 AND `archive` = 0 AND `notify` != ''
       			AND NOT (`network` IN ('%s'))
       			$sql_extra2
      @@ -542,7 +542,7 @@ function acl_lookup(&$a, $out_type = 'json') {
       		);
       	}
       	elseif($type == 'a') {
      -		$r = q("SELECT `id`, `name`, `nick`, `micro`, `network`, `url`, `attag` FROM `contact`
      +		$r = q("SELECT `id`, `name`, `nick`, `micro`, `network`, `url`, `attag`, `forum`, `prv` FROM `contact`
       			WHERE `uid` = %d AND `pending` = 0
       			$sql_extra2
       			ORDER BY `name` ASC ",
      @@ -559,6 +559,9 @@ function acl_lookup(&$a, $out_type = 'json') {
       					"photo"    => $g['photo'],
       					"name"     => $g['name'],
       					"nick"     => (x($g['addr']) ? $g['addr'] : $g['url']),
      +					"network" => $g['network'],
      +					"link" => $g['url'],
      +					"forum"	   => (x($g['community']) ? 1 : 0),
       				);
       			}
       		}
      @@ -584,7 +587,7 @@ function acl_lookup(&$a, $out_type = 'json') {
       				"network" => $g['network'],
       				"link" => $g['url'],
       				"nick" => htmlentities(($g['attag']) ? $g['attag'] : $g['nick']),
      -				"forum" => $g['forum']
      +				"forum" => ((x($g['forum']) || x($g['prv'])) ? 1 : 0),
       			);
       		}
       	}
      diff --git a/include/dir_fns.php b/include/dir_fns.php
      index d258058763..5f018ed8cf 100644
      --- a/include/dir_fns.php
      +++ b/include/dir_fns.php
      @@ -38,7 +38,7 @@ class dir {
       				$extra_sql = "";
       
       			$results = q("SELECT `contact`.`id` AS `cid`, `gcontact`.`url`, `gcontact`.`name`, `gcontact`.`nick`, `gcontact`.`photo`,
      -							`gcontact`.`network`, `gcontact`.`keywords`, `gcontact`.`addr`
      +							`gcontact`.`network`, `gcontact`.`keywords`, `gcontact`.`addr`, `gcontact`.`community`
       						FROM `gcontact`
       						LEFT JOIN `contact` ON `contact`.`nurl` = `gcontact`.`nurl`
       							AND `contact`.`uid` = %d AND NOT `contact`.`blocked`
      diff --git a/js/autocomplete.js b/js/autocomplete.js
      index aa8b6836c5..f31161ff83 100644
      --- a/js/autocomplete.js
      +++ b/js/autocomplete.js
      @@ -24,7 +24,7 @@ function contact_search(term, callback, backend_url, type, mode) {
       		if(lterm.indexOf(t) >= 0) { // A more broad search has been performed already, so use those results
       			// Filter old results locally
       			var matching = contact_search.cache[bt][t].filter(function (x) { return (x.name.toLowerCase().indexOf(lterm) >= 0 || (typeof x.nick !== 'undefined' && x.nick.toLowerCase().indexOf(lterm) >= 0)); }); // Need to check that nick exists because groups don't have one
      -			matching.unshift({taggable:false, text: term, replace: term});
      +			matching.unshift({forum:false, text: term, replace: term});
       			setTimeout(function() { callback(matching); } , 1); // Use "pseudo-thread" to avoid some problems
       			return;
       		}
      @@ -68,9 +68,10 @@ function contact_format(item) {
       	// Show contact information if not explicitly told to show something else
       	if(typeof item.text === 'undefined') {
       		var desc = ((item.label) ? item.nick + ' ' + item.label : item.nick);
      +		var forum = ((item.forum) ? 'forum' : '');
       		if(typeof desc === 'undefined') desc = '';
       		if(desc) desc = ' ('+desc+')';
      -		return "
      {2}{3}
      ".format(item.taggable, item.photo, item.name, desc, item.link); + return "
      {2}{3}
      ".format(forum, item.photo, item.name, desc, item.link); } else return "
      " + item.text + "
      "; diff --git a/view/theme/vier/style.css b/view/theme/vier/style.css index c27e5dfc81..ff25ff8b2d 100644 --- a/view/theme/vier/style.css +++ b/view/theme/vier/style.css @@ -935,8 +935,19 @@ nav .acpopup { color: #737373; } .textcomplete-item a:hover { + color: #000; padding: 3px 20px; } +.textcomplete-item a .forum, .forum .acpopup-sub-text { + color: #36C; + opacity: 0.8; +} +.textcomplete-item a .forum:hover { + opacity: 1.0; +} +img.acpopup-img { + border-radius: 4px; +} #nav-notifications-menu { width: 400px; From 0e531e148e0693f305f3062046de27ae349262c4 Mon Sep 17 00:00:00 2001 From: rabuzarus <> Date: Fri, 5 Feb 2016 14:26:22 +0100 Subject: [PATCH 013/211] rework autocomplete: polishing class and methods naming --- include/{dir_fns.php => DirSearch.php} | 4 ++-- include/{smilies.php => Smilies.php} | 18 +++++++++--------- include/acl_selectors.php | 4 ++-- include/text.php | 4 ++-- mod/smilies.php | 4 ++-- 5 files changed, 17 insertions(+), 17 deletions(-) rename include/{dir_fns.php => DirSearch.php} (97%) rename include/{smilies.php => Smilies.php} (92%) diff --git a/include/dir_fns.php b/include/DirSearch.php similarity index 97% rename from include/dir_fns.php rename to include/DirSearch.php index 5f018ed8cf..a6a0b59fcb 100644 --- a/include/dir_fns.php +++ b/include/DirSearch.php @@ -1,14 +1,14 @@ smilies texts array, 'icons' => smilies html array) */ - public static function list_smilies() { + public static function get_list() { $texts = array( '<3', @@ -128,10 +128,10 @@ class smilies { || (local_user() && intval(get_pconfig(local_user(),'system','no_smilies')))) return $s; - $s = preg_replace_callback('/
      (.*?)<\/pre>/ism','self::smile_encode',$s);
      -		$s = preg_replace_callback('/(.*?)<\/code>/ism','self::smile_encode',$s);
      +		$s = preg_replace_callback('/
      (.*?)<\/pre>/ism','self::encode',$s);
      +		$s = preg_replace_callback('/(.*?)<\/code>/ism','self::encode',$s);
       
      -		$params = self::list_smilies();
      +		$params = self::get_list();
       		$params['string'] = $s;
       
       		if($sample) {
      @@ -145,17 +145,17 @@ class smilies {
       			$s = str_replace($params['texts'],$params['icons'],$params['string']);
       		}
       
      -		$s = preg_replace_callback('/
      (.*?)<\/pre>/ism','self::smile_decode',$s);
      -		$s = preg_replace_callback('/(.*?)<\/code>/ism','self::smile_decode',$s);
      +		$s = preg_replace_callback('/
      (.*?)<\/pre>/ism','self::decode',$s);
      +		$s = preg_replace_callback('/(.*?)<\/code>/ism','self::decode',$s);
       
       		return $s;
       	}
       
      -	private function smile_encode($m) {
      +	private function encode($m) {
       		return(str_replace($m[1],base64url_encode($m[1]),$m[0]));
       	}
       
      -	private function smile_decode($m) {
      +	private function decode($m) {
       		return(str_replace($m[1],base64url_decode($m[1]),$m[0]));
       	}
       
      diff --git a/include/acl_selectors.php b/include/acl_selectors.php
      index 99848aa193..4a35fd0f08 100644
      --- a/include/acl_selectors.php
      +++ b/include/acl_selectors.php
      @@ -6,7 +6,7 @@
       
       require_once("include/contact_selectors.php");
       require_once("include/contact_widgets.php");
      -require_once("include/dir_fns.php");
      +require_once("include/DirSearch.php");
       require_once("include/features.php");
       require_once("mod/proxy.php");
       
      @@ -687,7 +687,7 @@ function navbar_complete(&$a) {
       		$search = substr($search,1);
       
       	if($localsearch) {
      -		$x = dir::global_search_by_name($search, $mode);
      +		$x = DirSearch::global_search_by_name($search, $mode);
       		return $x;
       	}
       
      diff --git a/include/text.php b/include/text.php
      index 4e56ad1058..956344d63d 100644
      --- a/include/text.php
      +++ b/include/text.php
      @@ -2,7 +2,7 @@
       
       require_once("include/template_processor.php");
       require_once("include/friendica_smarty.php");
      -require_once("include/smilies.php");
      +require_once("include/Smilies.php");
       require_once("include/map.php");
       require_once("mod/proxy.php");
       
      @@ -1396,7 +1396,7 @@ function prepare_text($text) {
       	if(stristr($text,'[nosmile]'))
       		$s = bbcode($text);
       	else
      -		$s = smilies::replace(bbcode($text));
      +		$s = Smilies::replace(bbcode($text));
       
       	return trim($s);
       }}
      diff --git a/mod/smilies.php b/mod/smilies.php
      index 5a157e3686..b3d86cb3fe 100644
      --- a/mod/smilies.php
      +++ b/mod/smilies.php
      @@ -4,11 +4,11 @@
        * @file mod/smilies.php
        */
       
      -require_once("include/smilies.php");
      +require_once("include/Smilies.php");
       
       function smilies_content(&$a) {
       	if ($a->argv[1]==="json"){
      -		$tmp = smilies::list_smilies();
      +		$tmp = Smilies::get_list();
       		$results = array();
       		for($i = 0; $i < count($tmp['texts']); $i++) {
       			$results[] = array('text' => $tmp['texts'][$i], 'icon' => $tmp['icons'][$i]);
      
      From 37e25313a405c5dbdd6dcff75bfd7cc590510b84 Mon Sep 17 00:00:00 2001
      From: Michael Vogel 
      Date: Sat, 27 Feb 2016 23:54:17 +0100
      Subject: [PATCH 014/211] New Diaspora code
      
      ---
       include/diaspora.php  |  37 +++
       include/diaspora2.php | 638 ++++++++++++++++++++++++++++++++++++++++++
       2 files changed, 675 insertions(+)
       create mode 100644 include/diaspora2.php
      
      diff --git a/include/diaspora.php b/include/diaspora.php
      index 635c1aabc3..f781ff80ad 100644
      --- a/include/diaspora.php
      +++ b/include/diaspora.php
      @@ -58,6 +58,8 @@ function diaspora_dispatch($importer,$msg,$attempt=1) {
       		return;
       	}
       
      +	$data = $msg;
      +
       	// php doesn't like dashes in variable names
       
       	$msg['message'] = str_replace(
      @@ -74,48 +76,83 @@ function diaspora_dispatch($importer,$msg,$attempt=1) {
       
       
       	if($xmlbase->request) {
      +		$tempfile = tempnam(get_temppath(), "diaspora-request");
      +		file_put_contents($tempfile, json_encode($data));
       		$ret = diaspora_request($importer,$xmlbase->request);
       	}
       	elseif($xmlbase->status_message) {
      +		//$tempfile = tempnam(get_temppath(), "diaspora-status_message");
      +		//file_put_contents($tempfile, json_encode($data));
       		$ret = diaspora_post($importer,$xmlbase->status_message,$msg);
       	}
       	elseif($xmlbase->profile) {
      +		//$tempfile = tempnam(get_temppath(), "diaspora-profile");
      +		//file_put_contents($tempfile, json_encode($data));
       		$ret = diaspora_profile($importer,$xmlbase->profile,$msg);
       	}
       	elseif($xmlbase->comment) {
      +		//$tempfile = tempnam(get_temppath(), "diaspora-comment");
      +		//file_put_contents($tempfile, json_encode($data));
       		$ret = diaspora_comment($importer,$xmlbase->comment,$msg);
       	}
       	elseif($xmlbase->like) {
      +		//$tempfile = tempnam(get_temppath(), "diaspora-like");
      +		//file_put_contents($tempfile, json_encode($data));
       		$ret = diaspora_like($importer,$xmlbase->like,$msg);
       	}
       	elseif($xmlbase->asphoto) {
      +		$tempfile = tempnam(get_temppath(), "diaspora-asphoto");
      +		file_put_contents($tempfile, json_encode($data));
       		$ret = diaspora_asphoto($importer,$xmlbase->asphoto,$msg);
       	}
       	elseif($xmlbase->reshare) {
      +		//$tempfile = tempnam(get_temppath(), "diaspora-reshare");
      +		//file_put_contents($tempfile, json_encode($data));
       		$ret = diaspora_reshare($importer,$xmlbase->reshare,$msg);
       	}
       	elseif($xmlbase->retraction) {
      +		$tempfile = tempnam(get_temppath(), "diaspora-retraction");
      +		file_put_contents($tempfile, json_encode($data));
       		$ret = diaspora_retraction($importer,$xmlbase->retraction,$msg);
       	}
       	elseif($xmlbase->signed_retraction) {
      +		$tempfile = tempnam(get_temppath(), "diaspora-signed_retraction");
      +		file_put_contents($tempfile, json_encode($data));
       		$ret = diaspora_signed_retraction($importer,$xmlbase->signed_retraction,$msg);
       	}
       	elseif($xmlbase->relayable_retraction) {
      +		//$tempfile = tempnam(get_temppath(), "diaspora-relayable_retraction");
      +		//file_put_contents($tempfile, json_encode($data));
       		$ret = diaspora_signed_retraction($importer,$xmlbase->relayable_retraction,$msg);
       	}
       	elseif($xmlbase->photo) {
      +		//$tempfile = tempnam(get_temppath(), "diaspora-photo");
      +		//file_put_contents($tempfile, json_encode($data));
       		$ret = diaspora_photo($importer,$xmlbase->photo,$msg,$attempt);
       	}
       	elseif($xmlbase->conversation) {
      +		$tempfile = tempnam(get_temppath(), "diaspora-conversation");
      +		file_put_contents($tempfile, json_encode($data));
       		$ret = diaspora_conversation($importer,$xmlbase->conversation,$msg);
       	}
       	elseif($xmlbase->message) {
      +		$tempfile = tempnam(get_temppath(), "diaspora-message");
      +		file_put_contents($tempfile, json_encode($data));
       		$ret = diaspora_message($importer,$xmlbase->message,$msg);
       	}
       	elseif($xmlbase->participation) {
      +		//$tempfile = tempnam(get_temppath(), "diaspora-participation");
      +		//file_put_contents($tempfile, json_encode($data));
      +		$ret = diaspora_participation($importer,$xmlbase->participation);
      +	}
      +	elseif($xmlbase->poll_participation) {
      +		$tempfile = tempnam(get_temppath(), "diaspora-poll_participation");
      +		file_put_contents($tempfile, json_encode($data));
       		$ret = diaspora_participation($importer,$xmlbase->participation);
       	}
       	else {
      +		$tempfile = tempnam(get_temppath(), "diaspora-unknown");
      +		file_put_contents($tempfile, json_encode($data));
       		logger('diaspora_dispatch: unknown message type: ' . print_r($xmlbase,true));
       	}
       	return $ret;
      diff --git a/include/diaspora2.php b/include/diaspora2.php
      new file mode 100644
      index 0000000000..690e54aa41
      --- /dev/null
      +++ b/include/diaspora2.php
      @@ -0,0 +1,638 @@
      + $value) {
      +			$root = new SimpleXMLElement('<'.$key.'/>');
      +			array_to_xml($value, $root);
      +
      +			$dom = dom_import_simplexml($root)->ownerDocument;
      +			$dom->formatOutput = true;
      +			return $dom->saveXML();
      +		}
      +	}
      +
      +	foreach($array as $key => $value) {
      +		if (!is_array($value) AND !is_numeric($key))
      +			$xml->addChild($key, $value);
      +		elseif (is_array($value))
      +			array_to_xml($value, $xml->addChild($key));
      +	}
      +}
      +
      +/**
      + * @brief This class contain functions to create and send DFRN XML files
      + *
      + */
      +class diaspora {
      +
      +	public static function dispatch_public($msg) {
      +
      +		$enabled = intval(get_config("system", "diaspora_enabled"));
      +		if (!$enabled) {
      +			logger('diaspora is disabled');
      +			return false;
      +		}
      +
      +		// Use a dummy importer to import the data for the public copy
      +		$importer = array("uid" => 0, "page-flags" => PAGE_FREELOVE);
      +		self::dispatch($importer,$msg);
      +
      +		// Now distribute it to the followers
      +		$r = q("SELECT `user`.* FROM `user` WHERE `user`.`uid` IN
      +			(SELECT `contact`.`uid` FROM `contact` WHERE `contact`.`network` = '%s' AND `contact`.`addr` = '%s')
      +			AND NOT `account_expired` AND NOT `account_removed`",
      +			dbesc(NETWORK_DIASPORA),
      +			dbesc($msg["author"])
      +		);
      +		if(count($r)) {
      +			foreach($r as $rr) {
      +				logger("delivering to: ".$rr["username"]);
      +				self::dispatch($rr,$msg);
      +			}
      +		} else
      +			logger("No subscribers for ".$msg["author"]." ".print_r($msg, true));
      +	}
      +
      +	public static function dispatch($importer, $msg) {
      +
      +		// The sender is the handle of the contact that sent the message.
      +		// This will often be different with relayed messages (for example "like" and "comment")
      +		$sender = $msg->author;
      +
      +		if (!diaspora::valid_posting($msg, $fields)) {
      +			logger("Invalid posting");
      +			return false;
      +		}
      +
      +		$type = $fields->getName();
      +
      +		switch ($type) {
      +			case "account_deletion":
      +				return self::import_account_deletion($importer, $fields);
      +
      +			case "comment":
      +				return self::import_comment($importer, $sender, $fields);
      +
      +			case "conversation":
      +				return self::import_conversation($importer, $fields);
      +
      +			case "like":
      +				return self::import_like($importer, $sender, $fields);
      +
      +			case "message":
      +				return self::import_message($importer, $fields);
      +
      +			case "participation":
      +				return self::import_participation($importer, $fields);
      +
      +			case "photo":
      +				return self::import_photo($importer, $fields);
      +
      +			case "poll_participation":
      +				return self::import_poll_participation($importer, $fields);
      +
      +			case "profile":
      +				return self::import_profile($importer, $fields);
      +
      +			case "request":
      +				return self::import_request($importer, $fields);
      +
      +			case "reshare":
      +				return self::import_reshare($importer, $fields);
      +
      +			case "retraction":
      +				return self::import_retraction($importer, $fields);
      +
      +			case "status_message":
      +				return self::import_status_message($importer, $fields);
      +
      +			default:
      +				logger("Unknown message type ".$type);
      +				return false;
      +		}
      +
      +		return true;
      +	}
      +
      +	/**
      +	 * @brief Checks if a posting is valid and fetches the data fields.
      +	 *
      +	 * This function does not only check the signature.
      +	 * It also does the conversion between the old and the new diaspora format.
      +	 *
      +	 * @param array $msg Array with the XML, the sender handle and the sender signature
      +	 * @param object $fields SimpleXML object that contains the posting
      +	 *
      +	 * @return bool Is the posting valid?
      +	 */
      +	private function valid_posting($msg, &$fields) {
      +
      +		$data = parse_xml_string($msg->message, false);
      +
      +		$first_child = $data->getName();
      +
      +		if ($data->getName() == "XML") {
      +			$oldXML = true;
      +			foreach ($data->post->children() as $child)
      +				$element = $child;
      +		} else {
      +			$oldXML = false;
      +			$element = $data;
      +		}
      +
      +		$type = $element->getName();
      +
      +		if (in_array($type, array("signed_retraction", "relayable_retraction")))
      +			$type = "retraction";
      +
      +		$fields = new SimpleXMLElement("<".$type."/>");
      +
      +		$signed_data = "";
      +
      +		foreach ($element->children() AS $fieldname => $data) {
      +
      +			if ($oldXML) {
      +				// Translation for the old XML structure
      +				if ($fieldname == "diaspora_handle")
      +					$fieldname = "author";
      +
      +				if ($fieldname == "participant_handles")
      +					$fieldname = "participants";
      +
      +				if (in_array($type, array("like", "participation"))) {
      +					if ($fieldname == "target_type")
      +						$fieldname = "parent_type";
      +				}
      +
      +				if ($fieldname == "sender_handle")
      +					$fieldname = "author";
      +
      +				if ($fieldname == "recipient_handle")
      +					$fieldname = "recipient";
      +
      +				if ($fieldname == "root_diaspora_id")
      +					$fieldname = "root_author";
      +
      +				if ($type == "retraction") {
      +					if ($fieldname == "post_guid")
      +						$fieldname = "target_guid";
      +
      +					if ($fieldname == "type")
      +						$fieldname = "target_type";
      +				}
      +			}
      +
      +			if ($fieldname == "author_signature")
      +				$author_signature = base64_decode($data);
      +			elseif ($fieldname == "parent_author_signature")
      +				$parent_author_signature = base64_decode($data);
      +			elseif ($fieldname != "target_author_signature") {
      +				if ($signed_data != "") {
      +					$signed_data .= ";";
      +					$signed_data_parent .= ";";
      +				}
      +
      +				$signed_data .= $data;
      +				$fields->$fieldname = $data;
      +			}
      +		}
      +
      +		if (in_array($type, array("status_message", "reshare")))
      +			if ($msg->author != $fields->author) {
      +				logger("Message handle is not the same as envelope sender. Quitting this message.");
      +				return false;
      +			}
      +
      +		if (!in_array($type, array("comment", "conversation", "message", "like")))
      +			return true;
      +
      +		if (!isset($author_signature))
      +			return false;
      +
      +		if (isset($parent_author_signature)) {
      +			$key = self::get_key($msg->author);
      +
      +			if (!rsa_verify($signed_data, $parent_author_signature, $key, "sha256"))
      +				return false;
      +		}
      +
      +		$key = self::get_key($fields->author);
      +
      +		return rsa_verify($signed_data, $author_signature, $key, "sha256");
      +	}
      +
      +	private function get_key($handle) {
      +		logger("Fetching diaspora key for: ".$handle);
      +
      +		$r = self::get_person_by_handle($handle);
      +		if($r)
      +			return $r["pubkey"];
      +
      +		return "";
      +	}
      +
      +	private function get_person_by_handle($handle) {
      +
      +		$r = q("SELECT * FROM `fcontact` WHERE `network` = '%s' AND `addr` = '%s' LIMIT 1",
      +			dbesc(NETWORK_DIASPORA),
      +			dbesc($handle)
      +		);
      +		if (count($r)) {
      +			$person = $r[0];
      +			logger("In cache ".print_r($r,true), LOGGER_DEBUG);
      +
      +			// update record occasionally so it doesn't get stale
      +			$d = strtotime($person["updated"]." +00:00");
      +			if ($d < strtotime("now - 14 days"))
      +				$update = true;
      +		}
      +
      +		if (!$person OR $update) {
      +			logger("create or refresh", LOGGER_DEBUG);
      +			$r = probe_url($handle, PROBE_DIASPORA);
      +
      +			// Note that Friendica contacts will return a "Diaspora person"
      +			// if Diaspora connectivity is enabled on their server
      +			if (count($r) AND ($r["network"] === NETWORK_DIASPORA)) {
      +				self::add_fcontact($r, $update);
      +				$person = $r;
      +			}
      +		}
      +		return $person;
      +	}
      +
      +	private function add_fcontact($arr, $update = false) {
      +		/// @todo Remove this function from include/network.php
      +
      +		if($update) {
      +			$r = q("UPDATE `fcontact` SET
      +					`name` = '%s',
      +					`photo` = '%s',
      +					`request` = '%s',
      +					`nick` = '%s',
      +					`addr` = '%s',
      +					`batch` = '%s',
      +					`notify` = '%s',
      +					`poll` = '%s',
      +					`confirm` = '%s',
      +					`alias` = '%s',
      +					`pubkey` = '%s',
      +					`updated` = '%s'
      +				WHERE `url` = '%s' AND `network` = '%s'",
      +					dbesc($arr["name"]),
      +					dbesc($arr["photo"]),
      +					dbesc($arr["request"]),
      +					dbesc($arr["nick"]),
      +					dbesc($arr["addr"]),
      +					dbesc($arr["batch"]),
      +					dbesc($arr["notify"]),
      +					dbesc($arr["poll"]),
      +					dbesc($arr["confirm"]),
      +					dbesc($arr["alias"]),
      +					dbesc($arr["pubkey"]),
      +					dbesc(datetime_convert()),
      +					dbesc($arr["url"]),
      +					dbesc($arr["network"])
      +				);
      +		} else {
      +			$r = q("INSERT INTO `fcontact` (`url`,`name`,`photo`,`request`,`nick`,`addr`,
      +					`batch`, `notify`,`poll`,`confirm`,`network`,`alias`,`pubkey`,`updated`)
      +				VALUES ('%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s')",
      +					dbesc($arr["url"]),
      +					dbesc($arr["name"]),
      +					dbesc($arr["photo"]),
      +					dbesc($arr["request"]),
      +					dbesc($arr["nick"]),
      +					dbesc($arr["addr"]),
      +					dbesc($arr["batch"]),
      +					dbesc($arr["notify"]),
      +					dbesc($arr["poll"]),
      +					dbesc($arr["confirm"]),
      +					dbesc($arr["network"]),
      +					dbesc($arr["alias"]),
      +					dbesc($arr["pubkey"]),
      +					dbesc(datetime_convert())
      +				);
      +		}
      +
      +		return $r;
      +	}
      +
      +	private function get_contact_by_handle($uid, $handle) {
      +		$r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `addr` = '%s' LIMIT 1",
      +			intval($uid),
      +			dbesc($handle)
      +		);
      +
      +		if ($r AND count($r))
      +			return $r[0];
      +
      +		$handle_parts = explode("@", $handle);
      +		$nurl_sql = '%%://' . $handle_parts[1] . '%%/profile/' . $handle_parts[0];
      +		$r = q("SELECT * FROM `contact` WHERE `network` = '%s' AND `uid` = %d AND `nurl` LIKE '%s' LIMIT 1",
      +			dbesc(NETWORK_DFRN),
      +			intval($uid),
      +			dbesc($nurl_sql)
      +		);
      +		if($r AND count($r))
      +			return $r[0];
      +
      +		return false;
      +	}
      +
      +/*
      +function DiasporaFetchGuid($item) {
      +        preg_replace_callback("&\[url=/posts/([^\[\]]*)\](.*)\[\/url\]&Usi",
      +                function ($match) use ($item){
      +                        return(DiasporaFetchGuidSub($match, $item));
      +                },$item["body"]);
      +}
      +
      +function DiasporaFetchGuidSub($match, $item) {
      +        $a = get_app();
      +
      +        if (!diaspora_store_by_guid($match[1], $item["author-link"]))
      +                diaspora_store_by_guid($match[1], $item["owner-link"]);
      +}
      +
      +function diaspora_store_by_guid($guid, $server, $uid = 0) {
      +        require_once("include/Contact.php");
      +
      +        $serverparts = parse_url($server);
      +        $server = $serverparts["scheme"]."://".$serverparts["host"];
      +
      +        logger("Trying to fetch item ".$guid." from ".$server, LOGGER_DEBUG);
      +
      +        $item = diaspora_fetch_message($guid, $server);
      +
      +        if (!$item)
      +                return false;
      +
      +        logger("Successfully fetched item ".$guid." from ".$server, LOGGER_DEBUG);
      +
      +        $body = $item["body"];
      +        $str_tags = $item["tag"];
      +        $app = $item["app"];
      +        $created = $item["created"];
      +        $author = $item["author"];
      +        $guid = $item["guid"];
      +        $private = $item["private"];
      +        $object = $item["object"];
      +        $objecttype = $item["object-type"];
      +
      +        $message_id = $author.':'.$guid;
      +        $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1",
      +                intval($uid),
      +                dbesc($guid)
      +        );
      +        if(count($r))
      +                return $r[0]["id"];
      +
      +        $person = find_diaspora_person_by_handle($author);
      +
      +        $contact_id = get_contact($person['url'], $uid);
      +
      +        $contacts = q("SELECT * FROM `contact` WHERE `id` = %d", intval($contact_id));
      +        $importers = q("SELECT * FROM `user` WHERE `uid` = %d", intval($uid));
      +
      +        if ($contacts AND $importers)
      +                if(!diaspora_post_allow($importers[0],$contacts[0], false)) {
      +                        logger('Ignoring author '.$person['url'].' for uid '.$uid);
      +                        return false;
      +                } else
      +                        logger('Author '.$person['url'].' is allowed for uid '.$uid);
      +
      +        $datarray = array();
      +        $datarray['uid'] = $uid;
      +        $datarray['contact-id'] = $contact_id;
      +        $datarray['wall'] = 0;
      +        $datarray['network'] = NETWORK_DIASPORA;
      +        $datarray['guid'] = $guid;
      +        $datarray['uri'] = $datarray['parent-uri'] = $message_id;
      +        $datarray['changed'] = $datarray['created'] = $datarray['edited'] = datetime_convert('UTC','UTC',$created);
      +        $datarray['private'] = $private;
      +        $datarray['parent'] = 0;
      +        $datarray['plink'] = diaspora_plink($author, $guid);
      +        $datarray['author-name'] = $person['name'];
      +        $datarray['author-link'] = $person['url'];
      +        $datarray['author-avatar'] = ((x($person,'thumb')) ? $person['thumb'] : $person['photo']);
      +        $datarray['owner-name'] = $datarray['author-name'];
      +        $datarray['owner-link'] = $datarray['author-link'];
      +        $datarray['owner-avatar'] = $datarray['author-avatar'];
      +        $datarray['body'] = $body;
      +        $datarray['tag'] = $str_tags;
      +        $datarray['app']  = $app;
      +        $datarray['visible'] = ((strlen($body)) ? 1 : 0);
      +        $datarray['object'] = $object;
      +        $datarray['object-type'] = $objecttype;
      +
      +        if ($datarray['contact-id'] == 0)
      +                return false;
      +
      +        DiasporaFetchGuid($datarray);
      +        $message_id = item_store($datarray);
      +
      +        /// @TODO
      +        /// Looking if there is some subscribe mechanism in Diaspora to get all comments for this post
      +
      +        return $message_id;
      +}
      +*/
      +
      +	private function import_account_deletion($importer, $data) {
      +		return true;
      +	}
      +
      +	private function import_comment($importer, $sender, $data) {
      +		$guid = notags(unxmlify($data->guid));
      +		$parent_guid = notags(unxmlify($data->parent_guid));
      +		$text = unxmlify($data->text);
      +		$author = notags(unxmlify($data->author));
      +
      +		$contact = self::get_contact_by_handle($importer["uid"], $sender);
      +		if (!$contact) {
      +			logger("cannot find contact for sender: ".$sender);
      +			return false;
      +		}
      +/*
      +        if(! diaspora_post_allow($importer,$contact, true)) {
      +                logger('diaspora_comment: Ignoring this author.');
      +                return 202;
      +        }
      +
      +        $r = q("SELECT * FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1",
      +                intval($importer['uid']),
      +                dbesc($guid)
      +        );
      +        if(count($r)) {
      +                logger('diaspora_comment: our comment just got relayed back to us (or there was a guid collision) : ' . $guid);
      +                return;
      +        }
      +
      +        $r = q("SELECT * FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1",
      +                intval($importer['uid']),
      +                dbesc($parent_guid)
      +        );
      +
      +        if(!count($r)) {
      +                $result = diaspora_store_by_guid($parent_guid, $contact['url'], $importer['uid']);
      +
      +                if (!$result) {
      +                        $person = find_diaspora_person_by_handle($diaspora_handle);
      +                        $result = diaspora_store_by_guid($parent_guid, $person['url'], $importer['uid']);
      +                }
      +
      +                if ($result) {
      +                        logger("Fetched missing item ".$parent_guid." - result: ".$result, LOGGER_DEBUG);
      +
      +                        $r = q("SELECT * FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1",
      +                                intval($importer['uid']),
      +                                dbesc($parent_guid)
      +                        );
      +                }
      +        }
      +
      +        if(! count($r)) {
      +                logger('diaspora_comment: parent item not found: parent: ' . $parent_guid . ' item: ' . $guid);
      +                return;
      +        }
      +        $parent_item = $r[0];
      +
      +        // Find the original comment author information.
      +        // We need this to make sure we display the comment author
      +        // information (name and avatar) correctly.
      +        if(strcasecmp($diaspora_handle,$msg['author']) == 0)
      +                $person = $contact;
      +        else {
      +                $person = find_diaspora_person_by_handle($diaspora_handle);
      +
      +                if(! is_array($person)) {
      +                        logger('diaspora_comment: unable to find author details');
      +                        return;
      +                }
      +        }
      +
      +        // Fetch the contact id - if we know this contact
      +        $r = q("SELECT `id`, `network` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d LIMIT 1",
      +                dbesc(normalise_link($person['url'])), intval($importer['uid']));
      +        if ($r) {
      +                $cid = $r[0]['id'];
      +                $network = $r[0]['network'];
      +        } else {
      +                $cid = $contact['id'];
      +                $network = NETWORK_DIASPORA;
      +        }
      +
      +        $body = diaspora2bb($text);
      +        $message_id = $diaspora_handle . ':' . $guid;
      +
      +        $datarray = array();
      +
      +        $datarray['uid'] = $importer['uid'];
      +        $datarray['contact-id'] = $cid;
      +        $datarray['type'] = 'remote-comment';
      +        $datarray['wall'] = $parent_item['wall'];
      +        $datarray['network']  = $network;
      +        $datarray['verb'] = ACTIVITY_POST;
      +        $datarray['gravity'] = GRAVITY_COMMENT;
      +        $datarray['guid'] = $guid;
      +        $datarray['uri'] = $message_id;
      +        $datarray['parent-uri'] = $parent_item['uri'];
      +
      +        // No timestamps for comments? OK, we'll the use current time.
      +        $datarray['changed'] = $datarray['created'] = $datarray['edited'] = datetime_convert();
      +        $datarray['private'] = $parent_item['private'];
      +
      +        $datarray['owner-name'] = $parent_item['owner-name'];
      +        $datarray['owner-link'] = $parent_item['owner-link'];
      +        $datarray['owner-avatar'] = $parent_item['owner-avatar'];
      +
      +        $datarray['author-name'] = $person['name'];
      +        $datarray['author-link'] = $person['url'];
      +        $datarray['author-avatar'] = ((x($person,'thumb')) ? $person['thumb'] : $person['photo']);
      +        $datarray['body'] = $body;
      +        $datarray["object"] = json_encode($xml);
      +        $datarray["object-type"] = ACTIVITY_OBJ_COMMENT;
      +
      +        // We can't be certain what the original app is if the message is relayed.
      +        if(($parent_item['origin']) && (! $parent_author_signature))
      +                $datarray['app']  = 'Diaspora';
      +
      +        DiasporaFetchGuid($datarray);
      +        $message_id = item_store($datarray);
      +
      +        $datarray['id'] = $message_id;
      +
      +        // 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($author_signature_base64),
      +                        dbesc($diaspora_handle)
      +                );
      +
      +                // notify others
      +                proc_run('php','include/notifier.php','comment-import',$message_id);
      +        }
      +*/
      +		return true;
      +	}
      +
      +	private function import_conversation($importer, $data) {
      +		return true;
      +	}
      +
      +	private function import_like($importer, $sender, $data) {
      +		return true;
      +	}
      +
      +	private function import_message($importer, $data) {
      +		return true;
      +	}
      +
      +	private function import_participation($importer, $data) {
      +		return true;
      +	}
      +
      +	private function import_photo($importer, $data) {
      +		return true;
      +	}
      +
      +	private function import_poll_participation($importer, $data) {
      +		return true;
      +	}
      +
      +	private function import_profile($importer, $data) {
      +		return true;
      +	}
      +
      +	private function import_request($importer, $data) {
      +		return true;
      +	}
      +
      +	private function import_reshare($importer, $data) {
      +		return true;
      +	}
      +
      +	private function import_retraction($importer, $data) {
      +		return true;
      +	}
      +
      +	private function import_status_message($importer, $data) {
      +		return true;
      +	}
      +}
      +?>
      
      From 859c9501f54c9a7b3370e69c0b0d52bc300d0c74 Mon Sep 17 00:00:00 2001
      From: Michael Vogel 
      Date: Sun, 28 Feb 2016 19:05:23 +0100
      Subject: [PATCH 015/211] Like and Comment could work (partially)
      
      ---
       include/diaspora2.php | 589 ++++++++++++++++++++++++++----------------
       1 file changed, 372 insertions(+), 217 deletions(-)
      
      diff --git a/include/diaspora2.php b/include/diaspora2.php
      index 690e54aa41..e6e2d74bf6 100644
      --- a/include/diaspora2.php
      +++ b/include/diaspora2.php
      @@ -4,8 +4,9 @@
        * @brief The implementation of the diaspora protocol
        */
       
      -require_once("include/diaspora.php");
      +require_once("include/bb2diaspora.php");
       require_once("include/Scrape.php");
      +require_once("include/Contact.php");
       
       function array_to_xml($array, &$xml) {
       
      @@ -66,7 +67,7 @@ class diaspora {
       
       		// The sender is the handle of the contact that sent the message.
       		// This will often be different with relayed messages (for example "like" and "comment")
      -		$sender = $msg->author;
      +		$sender = $msg["author"];
       
       		if (!diaspora::valid_posting($msg, $fields)) {
       			logger("Invalid posting");
      @@ -80,12 +81,14 @@ class diaspora {
       				return self::import_account_deletion($importer, $fields);
       
       			case "comment":
      -				return self::import_comment($importer, $sender, $fields);
      +				return true;
      +			//	return self::import_comment($importer, $sender, $fields);
       
       			case "conversation":
       				return self::import_conversation($importer, $fields);
       
       			case "like":
      +			//	return true;
       				return self::import_like($importer, $sender, $fields);
       
       			case "message":
      @@ -136,7 +139,7 @@ class diaspora {
       	 */
       	private function valid_posting($msg, &$fields) {
       
      -		$data = parse_xml_string($msg->message, false);
      +		$data = parse_xml_string($msg["message"], false);
       
       		$first_child = $data->getName();
       
      @@ -202,12 +205,13 @@ class diaspora {
       				}
       
       				$signed_data .= $data;
      -				$fields->$fieldname = $data;
       			}
      +			if (!in_array($fieldname, array("parent_author_signature", "target_author_signature")))
      +				$fields->$fieldname = $data;
       		}
       
       		if (in_array($type, array("status_message", "reshare")))
      -			if ($msg->author != $fields->author) {
      +			if ($msg["author"] != $fields->author) {
       				logger("Message handle is not the same as envelope sender. Quitting this message.");
       				return false;
       			}
      @@ -219,7 +223,7 @@ class diaspora {
       			return false;
       
       		if (isset($parent_author_signature)) {
      -			$key = self::get_key($msg->author);
      +			$key = self::get_key($msg["author"]);
       
       			if (!rsa_verify($signed_data, $parent_author_signature, $key, "sha256"))
       				return false;
      @@ -349,104 +353,166 @@ class diaspora {
       		return false;
       	}
       
      +	private function fetch_guid($item) {
      +		preg_replace_callback("&\[url=/posts/([^\[\]]*)\](.*)\[\/url\]&Usi",
      +			function ($match) use ($item){
      +				return(self::fetch_guid_sub($match, $item));
      +			},$item["body"]);
      +	}
      +
      +	private function fetch_guid_sub($match, $item) {
      +		$a = get_app();
      +
      +		if (!self::store_by_guid($match[1], $item["author-link"]))
      +			self::store_by_guid($match[1], $item["owner-link"]);
      +	}
      +
      +	private function store_by_guid($guid, $server, $uid = 0) {
      +		$serverparts = parse_url($server);
      +		$server = $serverparts["scheme"]."://".$serverparts["host"];
      +
      +		logger("Trying to fetch item ".$guid." from ".$server, LOGGER_DEBUG);
      +
      +/// @todo		$item = self::fetch_message($guid, $server);
      +
      +		if (!$item)
      +			return false;
      +
      +		logger("Successfully fetched item ".$guid." from ".$server, LOGGER_DEBUG);
      +
      +// @todo - neue Funktion import_status... nutzen
      +print_r($item);
      +die();
      +		return self::import_status_message($importer, $data);
      +
       /*
      -function DiasporaFetchGuid($item) {
      -        preg_replace_callback("&\[url=/posts/([^\[\]]*)\](.*)\[\/url\]&Usi",
      -                function ($match) use ($item){
      -                        return(DiasporaFetchGuidSub($match, $item));
      -                },$item["body"]);
      -}
      +		$body = $item["body"];
      +		$str_tags = $item["tag"];
      +		$app = $item["app"];
      +		$created = $item["created"];
      +		$author = $item["author"];
      +		$guid = $item["guid"];
      +		$private = $item["private"];
      +		$object = $item["object"];
      +		$objecttype = $item["object-type"];
       
      -function DiasporaFetchGuidSub($match, $item) {
      -        $a = get_app();
      +		$r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1",
      +			intval($uid),
      +			dbesc($guid)
      +		);
      +		if(count($r))
      +			return $r[0]["id"];
       
      -        if (!diaspora_store_by_guid($match[1], $item["author-link"]))
      -                diaspora_store_by_guid($match[1], $item["owner-link"]);
      -}
      +		$person = self::get_person_by_handle($author);
       
      -function diaspora_store_by_guid($guid, $server, $uid = 0) {
      -        require_once("include/Contact.php");
      +		$contact_id = get_contact($person["url"], $uid);
       
      -        $serverparts = parse_url($server);
      -        $server = $serverparts["scheme"]."://".$serverparts["host"];
      +		$contacts = q("SELECT * FROM `contact` WHERE `id` = %d", intval($contact_id));
      +		$importers = q("SELECT * FROM `user` WHERE `uid` = %d", intval($uid));
       
      -        logger("Trying to fetch item ".$guid." from ".$server, LOGGER_DEBUG);
      +		if ($contacts AND $importers)
      +			if (!self::post_allow($importers[0],$contacts[0], false)) {
      +				logger("Ignoring author ".$person["url"]." for uid ".$uid);
      +				return false;
      +			} else
      +				logger("Author ".$person["url"]." is allowed for uid ".$uid);
       
      -        $item = diaspora_fetch_message($guid, $server);
      +		$datarray = array();
      +		$datarray["uid"] = $uid;
      +		$datarray["contact-id"] = $contact_id;
      +		$datarray["wall"] = 0;
      +		$datarray["network"] = NETWORK_DIASPORA;
      +		$datarray["guid"] = $guid;
      +		$datarray["uri"] = $datarray["parent-uri"] = $author.":".$guid;
      +		$datarray["changed"] = $datarray["created"] = $datarray["edited"] = datetime_convert('UTC','UTC',$created);
      +		$datarray["private"] = $private;
      +		$datarray["parent"] = 0;
      +		$datarray["plink"] = self::plink($author, $guid);
      +		$datarray["author-name"] = $person["name"];
      +		$datarray["author-link"] = $person["url"];
      +		$datarray["author-avatar"] = ((x($person,'thumb')) ? $person["thumb"] : $person["photo"]);
      +		$datarray["owner-name"] = $datarray["author-name"];
      +		$datarray["owner-link"] = $datarray["author-link"];
      +		$datarray["owner-avatar"] = $datarray["author-avatar"];
      +		$datarray["body"] = $body;
      +		$datarray["tag"] = $str_tags;
      +		$datarray["app"]  = $app;
      +		$datarray["visible"] = ((strlen($body)) ? 1 : 0);
      +		$datarray["object"] = $object;
      +		$datarray["object-type"] = $objecttype;
       
      -        if (!$item)
      -                return false;
      +		if ($datarray["contact-id"] == 0)
      +			return false;
       
      -        logger("Successfully fetched item ".$guid." from ".$server, LOGGER_DEBUG);
      +		self::fetch_guid($datarray);
      +		$message_id = item_store($datarray);
       
      -        $body = $item["body"];
      -        $str_tags = $item["tag"];
      -        $app = $item["app"];
      -        $created = $item["created"];
      -        $author = $item["author"];
      -        $guid = $item["guid"];
      -        $private = $item["private"];
      -        $object = $item["object"];
      -        $objecttype = $item["object-type"];
      +		/// @TODO
      +		/// Looking if there is some subscribe mechanism in Diaspora to get all comments for this post
       
      -        $message_id = $author.':'.$guid;
      -        $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1",
      -                intval($uid),
      -                dbesc($guid)
      -        );
      -        if(count($r))
      -                return $r[0]["id"];
      -
      -        $person = find_diaspora_person_by_handle($author);
      -
      -        $contact_id = get_contact($person['url'], $uid);
      -
      -        $contacts = q("SELECT * FROM `contact` WHERE `id` = %d", intval($contact_id));
      -        $importers = q("SELECT * FROM `user` WHERE `uid` = %d", intval($uid));
      -
      -        if ($contacts AND $importers)
      -                if(!diaspora_post_allow($importers[0],$contacts[0], false)) {
      -                        logger('Ignoring author '.$person['url'].' for uid '.$uid);
      -                        return false;
      -                } else
      -                        logger('Author '.$person['url'].' is allowed for uid '.$uid);
      -
      -        $datarray = array();
      -        $datarray['uid'] = $uid;
      -        $datarray['contact-id'] = $contact_id;
      -        $datarray['wall'] = 0;
      -        $datarray['network'] = NETWORK_DIASPORA;
      -        $datarray['guid'] = $guid;
      -        $datarray['uri'] = $datarray['parent-uri'] = $message_id;
      -        $datarray['changed'] = $datarray['created'] = $datarray['edited'] = datetime_convert('UTC','UTC',$created);
      -        $datarray['private'] = $private;
      -        $datarray['parent'] = 0;
      -        $datarray['plink'] = diaspora_plink($author, $guid);
      -        $datarray['author-name'] = $person['name'];
      -        $datarray['author-link'] = $person['url'];
      -        $datarray['author-avatar'] = ((x($person,'thumb')) ? $person['thumb'] : $person['photo']);
      -        $datarray['owner-name'] = $datarray['author-name'];
      -        $datarray['owner-link'] = $datarray['author-link'];
      -        $datarray['owner-avatar'] = $datarray['author-avatar'];
      -        $datarray['body'] = $body;
      -        $datarray['tag'] = $str_tags;
      -        $datarray['app']  = $app;
      -        $datarray['visible'] = ((strlen($body)) ? 1 : 0);
      -        $datarray['object'] = $object;
      -        $datarray['object-type'] = $objecttype;
      -
      -        if ($datarray['contact-id'] == 0)
      -                return false;
      -
      -        DiasporaFetchGuid($datarray);
      -        $message_id = item_store($datarray);
      -
      -        /// @TODO
      -        /// Looking if there is some subscribe mechanism in Diaspora to get all comments for this post
      -
      -        return $message_id;
      -}
      +		return $message_id;
       */
      +	}
      +
      +	private function post_allow($importer, $contact, $is_comment = false) {
      +
      +		// perhaps we were already sharing with this person. Now they're sharing with us.
      +		// That makes us friends.
      +		// Normally this should have handled by getting a request - but this could get lost
      +		if($contact["rel"] == CONTACT_IS_FOLLOWER && in_array($importer["page-flags"], array(PAGE_FREELOVE))) {
      +			q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
      +				intval(CONTACT_IS_FRIEND),
      +				intval($contact["id"]),
      +				intval($importer["uid"])
      +			);
      +			$contact["rel"] = CONTACT_IS_FRIEND;
      +			logger("defining user ".$contact["nick"]." as friend");
      +		}
      +
      +		if(($contact["blocked"]) || ($contact["readonly"]) || ($contact["archive"]))
      +			return false;
      +		if($contact["rel"] == CONTACT_IS_SHARING || $contact["rel"] == CONTACT_IS_FRIEND)
      +			return true;
      +		if($contact["rel"] == CONTACT_IS_FOLLOWER)
      +			if(($importer["page-flags"] == PAGE_COMMUNITY) OR $is_comment)
      +				return true;
      +
      +		// Messages for the global users are always accepted
      +		if ($importer["uid"] == 0)
      +			return true;
      +
      +		return false;
      +	}
      +
      +	private function fetch_parent_item($uid, $guid, $author, $contact) {
      +		$r = q("SELECT `id`, `body`, `wall`, `uri`, `private`, `owner-name`, `owner-link`, `owner-avatar`, `origin`
      +			FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1",
      +			intval($uid), dbesc($guid));
      +
      +		if(!count($r)) {
      +			$result = self::store_by_guid($guid, $contact["url"], $uid);
      +
      +			if (!$result) {
      +				$person = self::get_person_by_handle($author);
      +				$result = self::store_by_guid($guid, $person["url"], $uid);
      +			}
      +
      +			if ($result) {
      +				logger("Fetched missing item ".$guid." - result: ".$result, LOGGER_DEBUG);
      +
      +				$r = q("SELECT `id`, `body`, `wall`, `uri`, `private`,
      +						`owner-name`, `owner-link`, `owner-avatar`, `origin`
      +					FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1",
      +					intval($uid), dbesc($guid));
      +			}
      +		}
      +
      +		if (!count($r)) {
      +			logger("parent item not found: parent: ".$guid." item: ".$guid);
      +			return false;
      +		} else
      +			return $r[0];
      +	}
       
       	private function import_account_deletion($importer, $data) {
       		return true;
      @@ -463,132 +529,93 @@ function diaspora_store_by_guid($guid, $server, $uid = 0) {
       			logger("cannot find contact for sender: ".$sender);
       			return false;
       		}
      +
      +		if (!self::post_allow($importer,$contact, true)) {
      +			logger("Ignoring the author ".$sender);
      +			return false;
      +		}
       /*
      -        if(! diaspora_post_allow($importer,$contact, true)) {
      -                logger('diaspora_comment: Ignoring this author.');
      -                return 202;
      -        }
      -
      -        $r = q("SELECT * FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1",
      -                intval($importer['uid']),
      -                dbesc($guid)
      -        );
      -        if(count($r)) {
      -                logger('diaspora_comment: our comment just got relayed back to us (or there was a guid collision) : ' . $guid);
      -                return;
      -        }
      -
      -        $r = q("SELECT * FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1",
      -                intval($importer['uid']),
      -                dbesc($parent_guid)
      -        );
      -
      -        if(!count($r)) {
      -                $result = diaspora_store_by_guid($parent_guid, $contact['url'], $importer['uid']);
      -
      -                if (!$result) {
      -                        $person = find_diaspora_person_by_handle($diaspora_handle);
      -                        $result = diaspora_store_by_guid($parent_guid, $person['url'], $importer['uid']);
      -                }
      -
      -                if ($result) {
      -                        logger("Fetched missing item ".$parent_guid." - result: ".$result, LOGGER_DEBUG);
      -
      -                        $r = q("SELECT * FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1",
      -                                intval($importer['uid']),
      -                                dbesc($parent_guid)
      -                        );
      -                }
      -        }
      -
      -        if(! count($r)) {
      -                logger('diaspora_comment: parent item not found: parent: ' . $parent_guid . ' item: ' . $guid);
      -                return;
      -        }
      -        $parent_item = $r[0];
      -
      -        // Find the original comment author information.
      -        // We need this to make sure we display the comment author
      -        // information (name and avatar) correctly.
      -        if(strcasecmp($diaspora_handle,$msg['author']) == 0)
      -                $person = $contact;
      -        else {
      -                $person = find_diaspora_person_by_handle($diaspora_handle);
      -
      -                if(! is_array($person)) {
      -                        logger('diaspora_comment: unable to find author details');
      -                        return;
      -                }
      -        }
      -
      -        // Fetch the contact id - if we know this contact
      -        $r = q("SELECT `id`, `network` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d LIMIT 1",
      -                dbesc(normalise_link($person['url'])), intval($importer['uid']));
      -        if ($r) {
      -                $cid = $r[0]['id'];
      -                $network = $r[0]['network'];
      -        } else {
      -                $cid = $contact['id'];
      -                $network = NETWORK_DIASPORA;
      -        }
      -
      -        $body = diaspora2bb($text);
      -        $message_id = $diaspora_handle . ':' . $guid;
      -
      -        $datarray = array();
      -
      -        $datarray['uid'] = $importer['uid'];
      -        $datarray['contact-id'] = $cid;
      -        $datarray['type'] = 'remote-comment';
      -        $datarray['wall'] = $parent_item['wall'];
      -        $datarray['network']  = $network;
      -        $datarray['verb'] = ACTIVITY_POST;
      -        $datarray['gravity'] = GRAVITY_COMMENT;
      -        $datarray['guid'] = $guid;
      -        $datarray['uri'] = $message_id;
      -        $datarray['parent-uri'] = $parent_item['uri'];
      -
      -        // No timestamps for comments? OK, we'll the use current time.
      -        $datarray['changed'] = $datarray['created'] = $datarray['edited'] = datetime_convert();
      -        $datarray['private'] = $parent_item['private'];
      -
      -        $datarray['owner-name'] = $parent_item['owner-name'];
      -        $datarray['owner-link'] = $parent_item['owner-link'];
      -        $datarray['owner-avatar'] = $parent_item['owner-avatar'];
      -
      -        $datarray['author-name'] = $person['name'];
      -        $datarray['author-link'] = $person['url'];
      -        $datarray['author-avatar'] = ((x($person,'thumb')) ? $person['thumb'] : $person['photo']);
      -        $datarray['body'] = $body;
      -        $datarray["object"] = json_encode($xml);
      -        $datarray["object-type"] = ACTIVITY_OBJ_COMMENT;
      -
      -        // We can't be certain what the original app is if the message is relayed.
      -        if(($parent_item['origin']) && (! $parent_author_signature))
      -                $datarray['app']  = 'Diaspora';
      -
      -        DiasporaFetchGuid($datarray);
      -        $message_id = item_store($datarray);
      -
      -        $datarray['id'] = $message_id;
      -
      -        // 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($author_signature_base64),
      -                        dbesc($diaspora_handle)
      -                );
      -
      -                // notify others
      -                proc_run('php','include/notifier.php','comment-import',$message_id);
      -        }
      +		$r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1",
      +			intval($importer["uid"]),
      +			dbesc($guid)
      +		);
      +		if(count($r)) {
      +			logger("The comment already exists: ".$guid);
      +			return;
      +		}
       */
      -		return true;
      +		$parent_item = self::fetch_parent_item($importer["uid"], $parent_guid, $author, $contact);
      +		if (!$parent_item)
      +			return false;
      +
      +		$person = self::get_person_by_handle($author);
      +		if (!is_array($person)) {
      +			logger("unable to find author details");
      +			return false;
      +		}
      +
      +		// Fetch the contact id - if we know this contact
      +		$r = q("SELECT `id`, `network` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d LIMIT 1",
      +			dbesc(normalise_link($person["url"])), intval($importer["uid"]));
      +		if ($r) {
      +			$cid = $r[0]["id"];
      +			$network = $r[0]["network"];
      +		} else {
      +			$cid = $contact["id"];
      +			$network = NETWORK_DIASPORA;
      +		}
      +
      +		$body = diaspora2bb($text);
      +
      +		$datarray = array();
      +
      +		$datarray["uid"] = $importer["uid"];
      +		$datarray["contact-id"] = $cid;
      +		$datarray["type"] = 'remote-comment';
      +		$datarray["wall"] = $parent_item["wall"];
      +		$datarray["network"]  = $network;
      +		$datarray["verb"] = ACTIVITY_POST;
      +		$datarray["gravity"] = GRAVITY_COMMENT;
      +		$datarray["guid"] = $guid;
      +		$datarray["uri"] = $author.":".$guid;
      +		$datarray["parent-uri"] = $parent_item["uri"];
      +
      +		// The old Diaspora protocol doesn't have a timestamp for comments
      +		$datarray["changed"] = $datarray["created"] = $datarray["edited"] = datetime_convert();
      +		$datarray["private"] = $parent_item["private"];
      +
      +		$datarray["owner-name"] = $contact["name"];
      +		$datarray["owner-link"] = $contact["url"];
      +		$datarray["owner-avatar"] = ((x($contact,"thumb")) ? $contact["thumb"] : $contact["photo"]);
      +
      +		$datarray["author-name"] = $person["name"];
      +		$datarray["author-link"] = $person["url"];
      +		$datarray["author-avatar"] = ((x($person,"thumb")) ? $person["thumb"] : $person["photo"]);
      +		$datarray["body"] = $body;
      +		$datarray["object"] = json_encode($data);
      +		$datarray["object-type"] = ACTIVITY_OBJ_COMMENT;
      +
      +		self::fetch_guid($datarray);
      +
      +//		$message_id = item_store($datarray);
      +print_r($datarray);
      +		$datarray["id"] = $message_id;
      +
      +		// If we are the origin of the parent we store the original data and notify our followers
      +		if($message_id AND $parent_item["origin"]) {
      +
      +			// Formerly we stored the signed text, the signature and the author in different fields.
      +			// The new Diaspora protocol can have variable fields. We now store the data in correct order in a single field.
      +			q("INSERT INTO `sign` (`iid`,`signed_text`) VALUES (%d,'%s')",
      +				intval($message_id),
      +				dbesc(json_encode($data))
      +			);
      +
      +			// notify others
      +			proc_run("php", "include/notifier.php", "comment-import", $message_id);
      +		}
      +
      +		return $message_id;
       	}
       
       	private function import_conversation($importer, $data) {
      @@ -596,6 +623,134 @@ function diaspora_store_by_guid($guid, $server, $uid = 0) {
       	}
       
       	private function import_like($importer, $sender, $data) {
      +		$positive = notags(unxmlify($data->positive));
      +		$guid = notags(unxmlify($data->guid));
      +		$parent_type = notags(unxmlify($data->parent_type));
      +		$parent_guid = notags(unxmlify($data->parent_guid));
      +		$author = notags(unxmlify($data->author));
      +
      +		// likes on comments aren't supported by Diaspora - only on posts
      +		if ($parent_type !== "Post")
      +			return false;
      +
      +		// "positive" = "false" doesn't seem to be supported by Diaspora
      +		if ($positive === "false") {
      +			logger("Received a like with positive set to 'false' - this shouldn't exist at all");
      +			return false;
      +		}
      +
      +		$contact = self::get_contact_by_handle($importer["uid"], $sender);
      +		if (!$contact) {
      +			logger("cannot find contact for sender: ".$sender);
      +			return false;
      +		}
      +
      +		if (!self::post_allow($importer,$contact, true)) {
      +			logger("Ignoring the author ".$sender);
      +			return false;
      +		}
      +/*
      +		$r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1",
      +			intval($importer["uid"]),
      +			dbesc($guid)
      +		);
      +		if(count($r)) {
      +			logger("The like already exists: ".$guid);
      +			return false;
      +		}
      +*/
      +		$parent_item = self::fetch_parent_item($importer["uid"], $parent_guid, $author, $contact);
      +		if (!$parent_item)
      +			return false;
      +
      +		$person = self::get_person_by_handle($author);
      +		if (!is_array($person)) {
      +			logger("unable to find author details");
      +			return false;
      +		}
      +
      +		// Fetch the contact id - if we know this contact
      +		$r = q("SELECT `id`, `network` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d LIMIT 1",
      +			dbesc(normalise_link($person["url"])), intval($importer["uid"]));
      +		if ($r) {
      +			$cid = $r[0]["id"];
      +			$network = $r[0]["network"];
      +		} else {
      +			$cid = $contact["id"];
      +			$network = NETWORK_DIASPORA;
      +		}
      +
      +// ------------------------------------------------
      +		$objtype = ACTIVITY_OBJ_NOTE;
      +		$link = xmlify(''."\n") ;
      +		$parent_body = $parent_item["body"];
      +
      +		$obj = <<< EOT
      +
      +		
      +			$objtype
      +			1
      +			{$parent_item["uri"]}
      +			$link
      +			
      +			$parent_body
      +		
      +EOT;
      +		$bodyverb = t('%1$s likes %2$s\'s %3$s');
      +
      +		$ulink = "[url=".$contact["url"]."]".$contact["name"]."[/url]";
      +		$alink = "[url=".$parent_item["author-link"]."]".$parent_item["author-name"]."[/url]";
      +		$plink = "[url=".App::get_baseurl()."/display/".urlencode($guid)."]".t("status")."[/url]";
      +		$body = sprintf($bodyverb, $ulink, $alink, $plink);
      +// ------------------------------------------------
      +
      +		$datarray = array();
      +
      +		$datarray["uri"] = $author.":".$guid;
      +		$datarray["uid"] = $importer["uid"];
      +		$datarray["guid"] = $guid;
      +		$datarray["network"]  = $network;
      +		$datarray["contact-id"] = $cid;
      +		$datarray["type"] = "activity";
      +		$datarray["wall"] = $parent_item["wall"];
      +		$datarray["gravity"] = GRAVITY_LIKE;
      +		$datarray["parent"] = $parent_item["id"];
      +		$datarray["parent-uri"] = $parent_item["uri"];
      +
      +		$datarray["owner-name"] = $contact["name"];
      +		$datarray["owner-link"] = $contact["url"];
      +		$datarray["owner-avatar"] = ((x($contact,"thumb")) ? $contact["thumb"] : $contact["photo"]);
      +
      +		$datarray["author-name"] = $person["name"];
      +		$datarray["author-link"] = $person["url"];
      +		$datarray["author-avatar"] = ((x($person,"thumb")) ? $person["thumb"] : $person["photo"]);
      +
      +		$datarray["body"] = $body;
      +		$datarray["private"] = $parent_item["private"];
      +		$datarray["verb"] = ACTIVITY_LIKE;
      +		$datarray["object-type"] = $objtype;
      +		$datarray["object"] = $obj;
      +		$datarray["visible"] = 1;
      +		$datarray["unseen"] = 1;
      +		$datarray["last-child"] = 0;
      +
      +print_r($datarray);
      +//		$message_id = item_store($datarray);
      +
      +		// If we are the origin of the parent we store the original data and notify our followers
      +		if($message_id AND $parent_item["origin"]) {
      +
      +			// Formerly we stored the signed text, the signature and the author in different fields.
      +			// The new Diaspora protocol can have variable fields. We now store the data in correct order in a single field.
      +			q("INSERT INTO `sign` (`iid`,`signed_text`) VALUES (%d,'%s')",
      +				intval($message_id),
      +				dbesc(json_encode($data))
      +			);
      +
      +			// notify others
      +			proc_run("php", "include/notifier.php", "comment-import", $message_id);
      +		}
      +
       		return true;
       	}
       
      
      From 81f5a359bf685dcaab0845bab243bbf47d65bb3c Mon Sep 17 00:00:00 2001
      From: Michael Vogel 
      Date: Mon, 29 Feb 2016 08:02:50 +0100
      Subject: [PATCH 016/211] "profile". "like" and "comment" could work, status
       messages only partly.
      
      ---
       include/diaspora.php  |    4 +-
       include/diaspora2.php | 1055 +++++++++++++++++++++++++++++++++--------
       2 files changed, 856 insertions(+), 203 deletions(-)
      
      diff --git a/include/diaspora.php b/include/diaspora.php
      index f781ff80ad..f139418fe4 100644
      --- a/include/diaspora.php
      +++ b/include/diaspora.php
      @@ -116,8 +116,8 @@ function diaspora_dispatch($importer,$msg,$attempt=1) {
       		$ret = diaspora_retraction($importer,$xmlbase->retraction,$msg);
       	}
       	elseif($xmlbase->signed_retraction) {
      -		$tempfile = tempnam(get_temppath(), "diaspora-signed_retraction");
      -		file_put_contents($tempfile, json_encode($data));
      +		//$tempfile = tempnam(get_temppath(), "diaspora-signed_retraction");
      +		//file_put_contents($tempfile, json_encode($data));
       		$ret = diaspora_signed_retraction($importer,$xmlbase->signed_retraction,$msg);
       	}
       	elseif($xmlbase->relayable_retraction) {
      diff --git a/include/diaspora2.php b/include/diaspora2.php
      index e6e2d74bf6..578a496c0a 100644
      --- a/include/diaspora2.php
      +++ b/include/diaspora2.php
      @@ -7,30 +7,43 @@
       require_once("include/bb2diaspora.php");
       require_once("include/Scrape.php");
       require_once("include/Contact.php");
      +require_once("include/Photo.php");
      +require_once("include/socgraph.php");
       
      -function array_to_xml($array, &$xml) {
      +class xml {
      +	function from_array($array, &$xml) {
      +
      +		if (!is_object($xml)) {
      +			foreach($array as $key => $value) {
      +				$root = new SimpleXMLElement('<'.$key.'/>');
      +				array_to_xml($value, $root);
      +
      +				$dom = dom_import_simplexml($root)->ownerDocument;
      +				$dom->formatOutput = true;
      +				return $dom->saveXML();
      +			}
      +		}
       
      -	if (!is_object($xml)) {
       		foreach($array as $key => $value) {
      -			$root = new SimpleXMLElement('<'.$key.'/>');
      -			array_to_xml($value, $root);
      -
      -			$dom = dom_import_simplexml($root)->ownerDocument;
      -			$dom->formatOutput = true;
      -			return $dom->saveXML();
      +			if (!is_array($value) AND !is_numeric($key))
      +				$xml->addChild($key, $value);
      +			elseif (is_array($value))
      +				array_to_xml($value, $xml->addChild($key));
       		}
       	}
       
      -	foreach($array as $key => $value) {
      -		if (!is_array($value) AND !is_numeric($key))
      -			$xml->addChild($key, $value);
      -		elseif (is_array($value))
      -			array_to_xml($value, $xml->addChild($key));
      +	function copy(&$source, &$target, $elementname) {
      +		if (count($source->children()) == 0)
      +			$target->addChild($elementname, $source);
      +		else {
      +			$child = $target->addChild($elementname);
      +			foreach ($source->children() AS $childfield => $childentry)
      +				self::copy($childentry, $child, $childfield);
      +		}
       	}
       }
      -
       /**
      - * @brief This class contain functions to create and send DFRN XML files
      + * @brief This class contain functions to create and send Diaspora XML files
        *
        */
       class diaspora {
      @@ -39,7 +52,7 @@ class diaspora {
       
       		$enabled = intval(get_config("system", "diaspora_enabled"));
       		if (!$enabled) {
      -			logger('diaspora is disabled');
      +			logger("diaspora is disabled");
       			return false;
       		}
       
      @@ -69,7 +82,7 @@ class diaspora {
       		// This will often be different with relayed messages (for example "like" and "comment")
       		$sender = $msg["author"];
       
      -		if (!diaspora::valid_posting($msg, $fields)) {
      +		if (!diaspora::valid_posting($msg, $fields, $data2)) {
       			logger("Invalid posting");
       			return false;
       		}
      @@ -77,34 +90,36 @@ class diaspora {
       		$type = $fields->getName();
       
       		switch ($type) {
      -			case "account_deletion":
      +			case "account_deletion": // Not implemented
       				return self::import_account_deletion($importer, $fields);
       
       			case "comment":
       				return true;
      -			//	return self::import_comment($importer, $sender, $fields);
      +				//return self::import_comment($importer, $sender, $fields);
       
       			case "conversation":
       				return self::import_conversation($importer, $fields);
       
       			case "like":
      -			//	return true;
      -				return self::import_like($importer, $sender, $fields);
      +				return true;
      +				//return self::import_like($importer, $sender, $fields);
       
       			case "message":
      -				return self::import_message($importer, $fields);
      +				return true;
      +				//return self::import_message($importer, $fields);
       
      -			case "participation":
      +			case "participation": // Not implemented
       				return self::import_participation($importer, $fields);
       
       			case "photo":
       				return self::import_photo($importer, $fields);
       
      -			case "poll_participation":
      +			case "poll_participation": // Not implemented
       				return self::import_poll_participation($importer, $fields);
       
       			case "profile":
      -				return self::import_profile($importer, $fields);
      +				return true;
      +				//return self::import_profile($importer, $fields);
       
       			case "request":
       				return self::import_request($importer, $fields);
      @@ -116,7 +131,7 @@ class diaspora {
       				return self::import_retraction($importer, $fields);
       
       			case "status_message":
      -				return self::import_status_message($importer, $fields);
      +				return self::import_status_message($importer, $fields, $msg, $data2);
       
       			default:
       				logger("Unknown message type ".$type);
      @@ -133,16 +148,20 @@ class diaspora {
       	 * It also does the conversion between the old and the new diaspora format.
       	 *
       	 * @param array $msg Array with the XML, the sender handle and the sender signature
      -	 * @param object $fields SimpleXML object that contains the posting
      +	 * @param object $fields SimpleXML object that contains the posting when it is valid
       	 *
       	 * @return bool Is the posting valid?
       	 */
      -	private function valid_posting($msg, &$fields) {
      +	private function valid_posting($msg, &$fields, &$element) {
       
       		$data = parse_xml_string($msg["message"], false);
       
      +		if (!is_object($data))
      +			return false;
      +
       		$first_child = $data->getName();
       
      +		// Is this the new or the old version?
       		if ($data->getName() == "XML") {
       			$oldXML = true;
       			foreach ($data->post->children() as $child)
      @@ -154,6 +173,8 @@ class diaspora {
       
       		$type = $element->getName();
       
      +		// All retractions are handled identically from now on.
      +		// In the new version there will only be "retraction".
       		if (in_array($type, array("signed_retraction", "relayable_retraction")))
       			$type = "retraction";
       
      @@ -161,8 +182,7 @@ class diaspora {
       
       		$signed_data = "";
       
      -		foreach ($element->children() AS $fieldname => $data) {
      -
      +		foreach ($element->children() AS $fieldname => $entry) {
       			if ($oldXML) {
       				// Translation for the old XML structure
       				if ($fieldname == "diaspora_handle")
      @@ -195,30 +215,33 @@ class diaspora {
       			}
       
       			if ($fieldname == "author_signature")
      -				$author_signature = base64_decode($data);
      +				$author_signature = base64_decode($entry);
       			elseif ($fieldname == "parent_author_signature")
      -				$parent_author_signature = base64_decode($data);
      +				$parent_author_signature = base64_decode($entry);
       			elseif ($fieldname != "target_author_signature") {
       				if ($signed_data != "") {
       					$signed_data .= ";";
       					$signed_data_parent .= ";";
       				}
       
      -				$signed_data .= $data;
      +				$signed_data .= $entry;
       			}
       			if (!in_array($fieldname, array("parent_author_signature", "target_author_signature")))
      -				$fields->$fieldname = $data;
      +				xml::copy($entry, $fields, $fieldname);
       		}
       
      -		if (in_array($type, array("status_message", "reshare")))
      +		// This is something that shouldn't happen at all.
      +		if (in_array($type, array("status_message", "reshare", "profile")))
       			if ($msg["author"] != $fields->author) {
       				logger("Message handle is not the same as envelope sender. Quitting this message.");
       				return false;
       			}
       
      +		// Only some message types have signatures. So we quit here for the other types.
       		if (!in_array($type, array("comment", "conversation", "message", "like")))
       			return true;
       
      +		// No author_signature? This is a must, so we quit.
       		if (!isset($author_signature))
       			return false;
       
      @@ -373,85 +396,57 @@ class diaspora {
       
       		logger("Trying to fetch item ".$guid." from ".$server, LOGGER_DEBUG);
       
      -/// @todo		$item = self::fetch_message($guid, $server);
      +		$msg = self::fetch_message($guid, $server);
       
      -		if (!$item)
      +		if (!$msg)
       			return false;
       
       		logger("Successfully fetched item ".$guid." from ".$server, LOGGER_DEBUG);
       
      -// @todo - neue Funktion import_status... nutzen
      -print_r($item);
      -die();
      -		return self::import_status_message($importer, $data);
      +		// Now call the dispatcher
      +		return self::dispatch_public($msg);
      +	}
       
      -/*
      -		$body = $item["body"];
      -		$str_tags = $item["tag"];
      -		$app = $item["app"];
      -		$created = $item["created"];
      -		$author = $item["author"];
      -		$guid = $item["guid"];
      -		$private = $item["private"];
      -		$object = $item["object"];
      -		$objecttype = $item["object-type"];
      +	private function fetch_message($guid, $server, $level = 0) {
       
      -		$r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1",
      -			intval($uid),
      -			dbesc($guid)
      -		);
      -		if(count($r))
      -			return $r[0]["id"];
      -
      -		$person = self::get_person_by_handle($author);
      -
      -		$contact_id = get_contact($person["url"], $uid);
      -
      -		$contacts = q("SELECT * FROM `contact` WHERE `id` = %d", intval($contact_id));
      -		$importers = q("SELECT * FROM `user` WHERE `uid` = %d", intval($uid));
      -
      -		if ($contacts AND $importers)
      -			if (!self::post_allow($importers[0],$contacts[0], false)) {
      -				logger("Ignoring author ".$person["url"]." for uid ".$uid);
      -				return false;
      -			} else
      -				logger("Author ".$person["url"]." is allowed for uid ".$uid);
      -
      -		$datarray = array();
      -		$datarray["uid"] = $uid;
      -		$datarray["contact-id"] = $contact_id;
      -		$datarray["wall"] = 0;
      -		$datarray["network"] = NETWORK_DIASPORA;
      -		$datarray["guid"] = $guid;
      -		$datarray["uri"] = $datarray["parent-uri"] = $author.":".$guid;
      -		$datarray["changed"] = $datarray["created"] = $datarray["edited"] = datetime_convert('UTC','UTC',$created);
      -		$datarray["private"] = $private;
      -		$datarray["parent"] = 0;
      -		$datarray["plink"] = self::plink($author, $guid);
      -		$datarray["author-name"] = $person["name"];
      -		$datarray["author-link"] = $person["url"];
      -		$datarray["author-avatar"] = ((x($person,'thumb')) ? $person["thumb"] : $person["photo"]);
      -		$datarray["owner-name"] = $datarray["author-name"];
      -		$datarray["owner-link"] = $datarray["author-link"];
      -		$datarray["owner-avatar"] = $datarray["author-avatar"];
      -		$datarray["body"] = $body;
      -		$datarray["tag"] = $str_tags;
      -		$datarray["app"]  = $app;
      -		$datarray["visible"] = ((strlen($body)) ? 1 : 0);
      -		$datarray["object"] = $object;
      -		$datarray["object-type"] = $objecttype;
      -
      -		if ($datarray["contact-id"] == 0)
      +		if ($level > 5)
       			return false;
       
      -		self::fetch_guid($datarray);
      -		$message_id = item_store($datarray);
      +		// This will not work if the server is not a Diaspora server
      +		$source_url = $server."/p/".$guid.".xml";
      +		$x = fetch_url($source_url);
      +		if(!$x)
      +			return false;
       
      -		/// @TODO
      -		/// Looking if there is some subscribe mechanism in Diaspora to get all comments for this post
      +		/// @todo - should maybe solved by the dispatcher
      +		$source_xml = parse_xml_string($x, false);
       
      -		return $message_id;
      -*/
      +		if (!is_object($source_xml))
      +			return false;
      +
      +		if ($source_xml->post->reshare) {
      +			// Reshare of a reshare - old Diaspora version
      +			return self::fetch_message($source_xml->post->reshare->root_guid, $server, ++$level);
      +		} elseif ($source_xml->getName() == "reshare") {
      +			// Reshare of a reshare - new Diaspora version
      +			return self::fetch_message($source_xml->root_guid, $server, ++$level);
      +		}
      +
      +		// Fetch the author - for the old and the new Diaspora version
      +		if ($source_xml->post->status_message->diaspora_handle)
      +			$author = (string)$source_xml->post->status_message->diaspora_handle;
      +		elseif ($source_xml->author)
      +			$author = (string)$source_xml->author;
      +
      +		if (!$author)
      +			return false;
      +
      +		$msg = array("message" => $x, "author" => $author);
      +
      +		// We don't really need this, but until the work is unfinished we better will keep this
      +		$msg["key"] = self::get_key($msg["author"]);
      +
      +		return $msg;
       	}
       
       	private function post_allow($importer, $contact, $is_comment = false) {
      @@ -485,7 +480,9 @@ die();
       	}
       
       	private function fetch_parent_item($uid, $guid, $author, $contact) {
      -		$r = q("SELECT `id`, `body`, `wall`, `uri`, `private`, `owner-name`, `owner-link`, `owner-avatar`, `origin`
      +		$r = q("SELECT `id`, `body`, `wall`, `uri`, `private`, `origin`,
      +				`author-name`, `author-link`, `author-avatar`,
      +				`owner-name`, `owner-link`, `owner-avatar`
       			FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1",
       			intval($uid), dbesc($guid));
       
      @@ -500,8 +497,9 @@ die();
       			if ($result) {
       				logger("Fetched missing item ".$guid." - result: ".$result, LOGGER_DEBUG);
       
      -				$r = q("SELECT `id`, `body`, `wall`, `uri`, `private`,
      -						`owner-name`, `owner-link`, `owner-avatar`, `origin`
      +				$r = q("SELECT `id`, `body`, `wall`, `uri`, `private`, `origin`,
      +						`author-name`, `author-link`, `author-avatar`,
      +						`owner-name`, `owner-link`, `owner-avatar`
       					FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1",
       					intval($uid), dbesc($guid));
       			}
      @@ -514,7 +512,49 @@ die();
       			return $r[0];
       	}
       
      +	private function get_author_contact_by_url($contact, $person, $uid) {
      +
      +		$r = q("SELECT `id`, `network` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d LIMIT 1",
      +			dbesc(normalise_link($person["url"])), intval($uid));
      +		if ($r) {
      +			$cid = $r[0]["id"];
      +			$network = $r[0]["network"];
      +		} else {
      +			$cid = $contact["id"];
      +			$network = NETWORK_DIASPORA;
      +		}
      +
      +		return (array("cid" => $cid, "network" => $network));
      +	}
      +
      +	public static function is_redmatrix($url) {
      +		return(strstr($url, "/channel/"));
      +	}
      +
      +	private function plink($addr, $guid) {
      +	        $r = q("SELECT `url`, `nick`, `network` FROM `fcontact` WHERE `addr`='%s' LIMIT 1", dbesc($addr));
      +
      +	        // Fallback
      +	        if (!$r)
      +	                return "https://".substr($addr,strpos($addr,"@")+1)."/posts/".$guid;
      +
      +	        // Friendica contacts are often detected as Diaspora contacts in the "fcontact" table
      +	        // So we try another way as well.
      +	        $s = q("SELECT `network` FROM `gcontact` WHERE `nurl`='%s' LIMIT 1", dbesc(normalise_link($r[0]["url"])));
      +	        if ($s)
      +	                $r[0]["network"] = $s[0]["network"];
      +
      +	        if ($r[0]["network"] == NETWORK_DFRN)
      +	                return(str_replace("/profile/".$r[0]["nick"]."/", "/display/".$guid, $r[0]["url"]."/"));
      +
      +	        if (self::is_redmatrix($r[0]["url"]))
      +	                return $r[0]["url"]."/?f=&mid=".$guid;
      +
      +	        return "https://".substr($addr,strpos($addr,"@")+1)."/posts/".$guid;
      +	}
      +
       	private function import_account_deletion($importer, $data) {
      +		// Not supported by now. We are waiting for sample data
       		return true;
       	}
       
      @@ -534,16 +574,16 @@ die();
       			logger("Ignoring the author ".$sender);
       			return false;
       		}
      -/*
      +
       		$r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1",
       			intval($importer["uid"]),
       			dbesc($guid)
       		);
       		if(count($r)) {
       			logger("The comment already exists: ".$guid);
      -			return;
      +			return false;
       		}
      -*/
      +
       		$parent_item = self::fetch_parent_item($importer["uid"], $parent_guid, $author, $contact);
       		if (!$parent_item)
       			return false;
      @@ -555,51 +595,39 @@ die();
       		}
       
       		// Fetch the contact id - if we know this contact
      -		$r = q("SELECT `id`, `network` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d LIMIT 1",
      -			dbesc(normalise_link($person["url"])), intval($importer["uid"]));
      -		if ($r) {
      -			$cid = $r[0]["id"];
      -			$network = $r[0]["network"];
      -		} else {
      -			$cid = $contact["id"];
      -			$network = NETWORK_DIASPORA;
      -		}
      -
      -		$body = diaspora2bb($text);
      +		$author_contact = self::get_author_contact_by_url($contact, $person, $importer["uid"]);
       
       		$datarray = array();
       
       		$datarray["uid"] = $importer["uid"];
      -		$datarray["contact-id"] = $cid;
      -		$datarray["type"] = 'remote-comment';
      -		$datarray["wall"] = $parent_item["wall"];
      -		$datarray["network"]  = $network;
      -		$datarray["verb"] = ACTIVITY_POST;
      -		$datarray["gravity"] = GRAVITY_COMMENT;
      -		$datarray["guid"] = $guid;
      -		$datarray["uri"] = $author.":".$guid;
      -		$datarray["parent-uri"] = $parent_item["uri"];
      +		$datarray["contact-id"] = $author_contact["cid"];
      +		$datarray["network"]  = $author_contact["network"];
       
      -		// The old Diaspora protocol doesn't have a timestamp for comments
      -		$datarray["changed"] = $datarray["created"] = $datarray["edited"] = datetime_convert();
      -		$datarray["private"] = $parent_item["private"];
      +		$datarray["author-name"] = $person["name"];
      +		$datarray["author-link"] = $person["url"];
      +		$datarray["author-avatar"] = ((x($person,"thumb")) ? $person["thumb"] : $person["photo"]);
       
       		$datarray["owner-name"] = $contact["name"];
       		$datarray["owner-link"] = $contact["url"];
       		$datarray["owner-avatar"] = ((x($contact,"thumb")) ? $contact["thumb"] : $contact["photo"]);
       
      -		$datarray["author-name"] = $person["name"];
      -		$datarray["author-link"] = $person["url"];
      -		$datarray["author-avatar"] = ((x($person,"thumb")) ? $person["thumb"] : $person["photo"]);
      -		$datarray["body"] = $body;
      -		$datarray["object"] = json_encode($data);
      +		$datarray["guid"] = $guid;
      +		$datarray["uri"] = $author.":".$guid;
      +
      +		$datarray["type"] = "remote-comment";
      +		$datarray["verb"] = ACTIVITY_POST;
      +		$datarray["gravity"] = GRAVITY_COMMENT;
      +		$datarray["parent-uri"] = $parent_item["uri"];
      +
       		$datarray["object-type"] = ACTIVITY_OBJ_COMMENT;
      +		$datarray["object"] = json_encode($data);
      +
      +		$datarray["body"] = diaspora2bb($text);
       
       		self::fetch_guid($datarray);
       
      -//		$message_id = item_store($datarray);
      -print_r($datarray);
      -		$datarray["id"] = $message_id;
      +		$message_id = item_store($datarray);
      +		// print_r($datarray);
       
       		// If we are the origin of the parent we store the original data and notify our followers
       		if($message_id AND $parent_item["origin"]) {
      @@ -622,6 +650,36 @@ print_r($datarray);
       		return true;
       	}
       
      +	private function construct_like_body($contact, $parent_item, $guid) {
      +		$bodyverb = t('%1$s likes %2$s\'s %3$s');
      +
      +		$ulink = "[url=".$contact["url"]."]".$contact["name"]."[/url]";
      +		$alink = "[url=".$parent_item["author-link"]."]".$parent_item["author-name"]."[/url]";
      +		$plink = "[url=".App::get_baseurl()."/display/".urlencode($guid)."]".t("status")."[/url]";
      +
      +		return sprintf($bodyverb, $ulink, $alink, $plink);
      +	}
      +
      +	private function construct_like_object($importer, $parent_item) {
      +		$objtype = ACTIVITY_OBJ_NOTE;
      +		$link = xmlify(''."\n") ;
      +		$parent_body = $parent_item["body"];
      +
      +		$obj = <<< EOT
      +
      +		
      +			$objtype
      +			1
      +			{$parent_item["uri"]}
      +			$link
      +			
      +			$parent_body
      +		
      +EOT;
      +
      +		return $obj;
      +	}
      +
       	private function import_like($importer, $sender, $data) {
       		$positive = notags(unxmlify($data->positive));
       		$guid = notags(unxmlify($data->guid));
      @@ -649,7 +707,7 @@ print_r($datarray);
       			logger("Ignoring the author ".$sender);
       			return false;
       		}
      -/*
      +
       		$r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1",
       			intval($importer["uid"]),
       			dbesc($guid)
      @@ -658,7 +716,7 @@ print_r($datarray);
       			logger("The like already exists: ".$guid);
       			return false;
       		}
      -*/
      +
       		$parent_item = self::fetch_parent_item($importer["uid"], $parent_guid, $author, $contact);
       		if (!$parent_item)
       			return false;
      @@ -670,72 +728,37 @@ print_r($datarray);
       		}
       
       		// Fetch the contact id - if we know this contact
      -		$r = q("SELECT `id`, `network` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d LIMIT 1",
      -			dbesc(normalise_link($person["url"])), intval($importer["uid"]));
      -		if ($r) {
      -			$cid = $r[0]["id"];
      -			$network = $r[0]["network"];
      -		} else {
      -			$cid = $contact["id"];
      -			$network = NETWORK_DIASPORA;
      -		}
      -
      -// ------------------------------------------------
      -		$objtype = ACTIVITY_OBJ_NOTE;
      -		$link = xmlify(''."\n") ;
      -		$parent_body = $parent_item["body"];
      -
      -		$obj = <<< EOT
      -
      -		
      -			$objtype
      -			1
      -			{$parent_item["uri"]}
      -			$link
      -			
      -			$parent_body
      -		
      -EOT;
      -		$bodyverb = t('%1$s likes %2$s\'s %3$s');
      -
      -		$ulink = "[url=".$contact["url"]."]".$contact["name"]."[/url]";
      -		$alink = "[url=".$parent_item["author-link"]."]".$parent_item["author-name"]."[/url]";
      -		$plink = "[url=".App::get_baseurl()."/display/".urlencode($guid)."]".t("status")."[/url]";
      -		$body = sprintf($bodyverb, $ulink, $alink, $plink);
      -// ------------------------------------------------
      +		$author_contact = self::get_author_contact_by_url($contact, $person, $importer["uid"]);
       
       		$datarray = array();
       
      -		$datarray["uri"] = $author.":".$guid;
       		$datarray["uid"] = $importer["uid"];
      -		$datarray["guid"] = $guid;
      -		$datarray["network"]  = $network;
      -		$datarray["contact-id"] = $cid;
      -		$datarray["type"] = "activity";
      -		$datarray["wall"] = $parent_item["wall"];
      -		$datarray["gravity"] = GRAVITY_LIKE;
      -		$datarray["parent"] = $parent_item["id"];
      -		$datarray["parent-uri"] = $parent_item["uri"];
      -
      -		$datarray["owner-name"] = $contact["name"];
      -		$datarray["owner-link"] = $contact["url"];
      -		$datarray["owner-avatar"] = ((x($contact,"thumb")) ? $contact["thumb"] : $contact["photo"]);
      +		$datarray["contact-id"] = $author_contact["cid"];
      +		$datarray["network"]  = $author_contact["network"];
       
       		$datarray["author-name"] = $person["name"];
       		$datarray["author-link"] = $person["url"];
       		$datarray["author-avatar"] = ((x($person,"thumb")) ? $person["thumb"] : $person["photo"]);
       
      -		$datarray["body"] = $body;
      -		$datarray["private"] = $parent_item["private"];
      -		$datarray["verb"] = ACTIVITY_LIKE;
      -		$datarray["object-type"] = $objtype;
      -		$datarray["object"] = $obj;
      -		$datarray["visible"] = 1;
      -		$datarray["unseen"] = 1;
      -		$datarray["last-child"] = 0;
      +		$datarray["owner-name"] = $contact["name"];
      +		$datarray["owner-link"] = $contact["url"];
      +		$datarray["owner-avatar"] = ((x($contact,"thumb")) ? $contact["thumb"] : $contact["photo"]);
       
      -print_r($datarray);
      -//		$message_id = item_store($datarray);
      +		$datarray["guid"] = $guid;
      +		$datarray["uri"] = $author.":".$guid;
      +
      +		$datarray["type"] = "activity";
      +		$datarray["verb"] = ACTIVITY_LIKE;
      +		$datarray["gravity"] = GRAVITY_LIKE;
      +		$datarray["parent-uri"] = $parent_item["uri"];
      +
      +		$datarray["object-type"] = ACTIVITY_OBJ_NOTE;
      +		$datarray["object"] = self::construct_like_object($importer, $parent_item);
      +
      +		$datarray["body"] = self::construct_like_body($contact, $parent_item, $guid);
      +
      +		$message_id = item_store($datarray);
      +		//print_r($datarray);
       
       		// If we are the origin of the parent we store the original data and notify our followers
       		if($message_id AND $parent_item["origin"]) {
      @@ -751,10 +774,86 @@ print_r($datarray);
       			proc_run("php", "include/notifier.php", "comment-import", $message_id);
       		}
       
      -		return true;
      +		return $message_id;
       	}
       
       	private function import_message($importer, $data) {
      +		$guid = notags(unxmlify($data->guid));
      +		$parent_guid = notags(unxmlify($data->parent_guid));
      +		$text = unxmlify($data->text);
      +		$created_at = datetime_convert("UTC", "UTC", notags(unxmlify($data->created_at)));
      +		$author = notags(unxmlify($data->author));
      +		$conversation_guid = notags(unxmlify($data->conversation_guid));
      +
      +		$parent_uri = $author.":".$parent_guid;
      +
      +		$contact = self::get_contact_by_handle($importer["uid"], $author);
      +		if (!$contact) {
      +			logger("cannot find contact: ".$author);
      +			return false;
      +		}
      +
      +		if(($contact["rel"] == CONTACT_IS_FOLLOWER) || ($contact["blocked"]) || ($contact["readonly"])) {
      +			logger("Ignoring this author.");
      +			return false;
      +		}
      +
      +		$conversation = null;
      +
      +		$c = q("SELECT * FROM `conv` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1",
      +			intval($importer["uid"]),
      +			dbesc($conversation_guid)
      +		);
      +		if(count($c))
      +			$conversation = $c[0];
      +		else {
      +			logger("conversation not available.");
      +			return false;
      +		}
      +
      +		$reply = 0;
      +
      +		$body = diaspora2bb($text);
      +		$message_id = $author.":".$guid;
      +
      +		$person = self::get_person_by_handle($author);
      +		if (!$person) {
      +			logger("unable to find author details");
      +			return false;
      +		}
      +
      +		$r = q("SELECT `id` FROM `mail` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
      +			dbesc($message_id),
      +			intval($importer["uid"])
      +		);
      +		if(count($r)) {
      +			logger("duplicate message already delivered.", LOGGER_DEBUG);
      +			return false;
      +		}
      +
      +		q("INSERT INTO `mail` (`uid`, `guid`, `convid`, `from-name`,`from-photo`,`from-url`,`contact-id`,`title`,`body`,`seen`,`reply`,`uri`,`parent-uri`,`created`)
      +				VALUES ( %d, '%s', %d, '%s', '%s', '%s', %d, '%s', '%s', %d, %d, '%s','%s','%s')",
      +			intval($importer["uid"]),
      +			dbesc($guid),
      +			intval($conversation["id"]),
      +			dbesc($person["name"]),
      +			dbesc($person["photo"]),
      +			dbesc($person["url"]),
      +			intval($contact["id"]),
      +			dbesc($conversation["subject"]),
      +			dbesc($body),
      +			0,
      +			1,
      +			dbesc($message_id),
      +			dbesc($parent_uri),
      +			dbesc($created_at)
      +		);
      +
      +		q("UPDATE `conv` SET `updated` = '%s' WHERE `id` = %d",
      +			dbesc(datetime_convert()),
      +			intval($conversation["id"])
      +		);
      +
       		return true;
       	}
       
      @@ -771,14 +870,445 @@ print_r($datarray);
       	}
       
       	private function import_profile($importer, $data) {
      +		$author = notags(unxmlify($data->author));
      +
      +		$contact = self::get_contact_by_handle($importer["uid"], $author);
      +		if (!$contact)
      +			return;
      +
      +		$name = unxmlify($data->first_name).((strlen($data->last_name)) ? " ".unxmlify($data->last_name) : "");
      +		$image_url = unxmlify($data->image_url);
      +		$birthday = unxmlify($data->birthday);
      +		$location = diaspora2bb(unxmlify($data->location));
      +		$about = diaspora2bb(unxmlify($data->bio));
      +		$gender = unxmlify($data->gender);
      +		$searchable = (unxmlify($data->searchable) == "true");
      +		$nsfw = (unxmlify($data->nsfw) == "true");
      +		$tags = unxmlify($data->tag_string);
      +
      +		$tags = explode("#", $tags);
      +
      +		$keywords = array();
      +		foreach ($tags as $tag) {
      +			$tag = trim(strtolower($tag));
      +			if ($tag != "")
      +				$keywords[] = $tag;
      +		}
      +
      +		$keywords = implode(", ", $keywords);
      +
      +		$handle_parts = explode("@", $author);
      +		$nick = $handle_parts[0];
      +
      +		if($name === "")
      +			$name = $handle_parts[0];
      +
      +		if( preg_match("|^https?://|", $image_url) === 0)
      +			$image_url = "http://".$handle_parts[1].$image_url;
      +
      +		update_contact_avatar($image_url, $importer["uid"], $contact["id"]);
      +
      +		// Generic birthday. We don't know the timezone. The year is irrelevant.
      +
      +		$birthday = str_replace("1000", "1901", $birthday);
      +
      +		if ($birthday != "")
      +			$birthday = datetime_convert("UTC", "UTC", $birthday, "Y-m-d");
      +
      +		// this is to prevent multiple birthday notifications in a single year
      +		// if we already have a stored birthday and the 'm-d' part hasn't changed, preserve the entry, which will preserve the notify year
      +
      +		if(substr($birthday,5) === substr($contact["bd"],5))
      +			$birthday = $contact["bd"];
      +
      +		$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($nick),
      +			dbesc($author),
      +			dbesc(datetime_convert()),
      +			dbesc($birthday),
      +			dbesc($location),
      +			dbesc($about),
      +			dbesc($keywords),
      +			dbesc($gender),
      +			intval($contact["id"]),
      +			intval($importer["uid"])
      +		);
      +
      +		if ($searchable) {
      +			poco_check($contact["url"], $name, NETWORK_DIASPORA, $image_url, $about, $location, $gender, $keywords, "",
      +				datetime_convert(), 2, $contact["id"], $importer["uid"]);
      +		}
      +
      +		$gcontact = array("url" => $contact["url"], "network" => NETWORK_DIASPORA, "generation" => 2,
      +					"photo" => $image_url, "name" => $name, "location" => $location,
      +					"about" => $about, "birthday" => $birthday, "gender" => $gender,
      +					"addr" => $author, "nick" => $nick, "keywords" => $keywords,
      +					"hide" => !$searchable, "nsfw" => $nsfw);
      +
      +		update_gcontact($gcontact);
      +
       		return true;
       	}
       
       	private function import_request($importer, $data) {
      +print_r($data);
      +/*
      +	$author = unxmlify($xml->author);
      +	$recipient = unxmlify($xml->recipient);
      +
      +	if (!$author || !$recipient)
      +		return;
      +
      +	$contact = self::get_contact_by_handle($importer["uid"],$author);
      +
      +	if($contact) {
      +
      +		// perhaps we were already sharing with this person. Now they're sharing with us.
      +		// That makes us friends.
      +
      +		if($contact["rel"] == CONTACT_IS_FOLLOWER && in_array($importer["page-flags"], array(PAGE_FREELOVE))) {
      +			q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
      +				intval(CONTACT_IS_FRIEND),
      +				intval($contact["id"]),
      +				intval($importer["uid"])
      +			);
      +		}
      +		// send notification
      +
      +		$r = q("SELECT `hide-friends` FROM `profile` WHERE `uid` = %d AND `is-default` = 1 LIMIT 1",
      +			intval($importer["uid"])
      +		);
      +
      +		if((count($r)) && (!$r[0]["hide-friends"]) && (!$contact["hidden"]) && intval(get_pconfig($importer["uid"],'system','post_newfriend'))) {
      +			require_once('include/items.php');
      +
      +			$self = q("SELECT * FROM `contact` WHERE `self` = 1 AND `uid` = %d LIMIT 1",
      +				intval($importer["uid"])
      +			);
      +
      +			// they are not CONTACT_IS_FOLLOWER anymore but that's what we have in the array
      +
      +			if(count($self) && $contact["rel"] == CONTACT_IS_FOLLOWER) {
      +
      +				$arr = array();
      +				$arr["uri"] = $arr["parent-uri"] = item_new_uri($a->get_hostname(), $importer["uid"]);
      +				$arr["uid"] = $importer["uid"];
      +				$arr["contact-id"] = $self[0]["id"];
      +				$arr["wall"] = 1;
      +				$arr["type"] = 'wall';
      +				$arr["gravity"] = 0;
      +				$arr["origin"] = 1;
      +				$arr["author-name"] = $arr["owner-name"] = $self[0]["name"];
      +				$arr["author-link"] = $arr["owner-link"] = $self[0]["url"];
      +				$arr["author-avatar"] = $arr["owner-avatar"] = $self[0]["thumb"];
      +				$arr["verb"] = ACTIVITY_FRIEND;
      +				$arr["object-type"] = ACTIVITY_OBJ_PERSON;
      +
      +				$A = '[url=' . $self[0]["url"] . "]' . $self[0]["name"] . '[/url]';
      +				$B = '[url=' . $contact["url"] . "]' . $contact["name"] . '[/url]';
      +				$BPhoto = '[url=' . $contact["url"] . "]' . '[img]' . $contact["thumb"] . '[/img][/url]';
      +				$arr["body"] =  sprintf( t('%1$s is now friends with %2$s'), $A, $B)."\n\n\n".$Bphoto;
      +
      +				$arr["object"] = '' . ACTIVITY_OBJ_PERSON . '' . $contact["name"] . ''
      +					. '' . $contact["url"] . '/' . $contact["name"] . '';
      +				$arr["object"] .= '' . xmlify('' . "\n")
      +;
      +				$arr["object"] .= xmlify('' . "\n");
      +				$arr["object"] .= '' . "\n";
      +				$arr["last-child"] = 1;
      +
      +				$arr["allow_cid"] = $user[0]["allow_cid"];
      +				$arr["allow_gid"] = $user[0]["allow_gid"];
      +				$arr["deny_cid"]  = $user[0]["deny_cid"];
      +				$arr["deny_gid"]  = $user[0]["deny_gid"];
      +
      +				$i = item_store($arr);
      +				if($i)
      +				proc_run('php',"include/notifier.php","activity","$i");
      +
      +			}
      +
      +		}
      +
      +		return;
      +	}
      +
      +	$ret = self::get_person_by_handle($author);
      +
      +
      +	if((! count($ret)) || ($ret["network"] != NETWORK_DIASPORA)) {
      +		logger('diaspora_request: Cannot resolve diaspora handle ' . $author . ' for ' . $recipient);
      +		return;
      +	}
      +
      +	$batch = (($ret["batch"]) ? $ret["batch"] : implode('/', array_slice(explode('/',$ret["url"]),0,3)) . '/receive/public');
      +
      +
      +
      +	$r = q("INSERT INTO `contact` (`uid`, `network`,`addr`,`created`,`url`,`nurl`,`batch`,`name`,`nick`,`photo`,`pubkey`,`notify`,`poll`,`blocked`,`priority`)
      +		VALUES ( %d, '%s', '%s', '%s', '%s','%s','%s','%s','%s','%s','%s','%s','%s',%d,%d) ",
      +		intval($importer["uid"]),
      +		dbesc($ret["network"]),
      +		dbesc($ret["addr"]),
      +		datetime_convert(),
      +		dbesc($ret["url"]),
      +		dbesc(normalise_link($ret["url"])),
      +		dbesc($batch),
      +		dbesc($ret["name"]),
      +		dbesc($ret["nick"]),
      +		dbesc($ret["photo"]),
      +		dbesc($ret["pubkey"]),
      +		dbesc($ret["notify"]),
      +		dbesc($ret["poll"]),
      +		1,
      +		2
      +	);
      +
      +	// find the contact record we just created
      +
      +	$contact_record = diaspora_get_contact_by_handle($importer["uid"],$author);
      +
      +	if(! $contact_record) {
      +		logger('diaspora_request: unable to locate newly created contact record.');
      +		return;
      +	}
      +
      +	$g = q("select def_gid from user where uid = %d limit 1",
      +		intval($importer["uid"])
      +	);
      +	if($g && intval($g[0]["def_gid"])) {
      +		require_once('include/group.php');
      +		group_add_member($importer["uid"],'',$contact_record["id"],$g[0]["def_gid"]);
      +	}
      +
      +	if($importer["page-flags"] == PAGE_NORMAL) {
      +
      +		$hash = random_string() . (string) time();   // Generate a confirm_key
      +
      +		$ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `note`, `hash`, `datetime` )
      +			VALUES ( %d, %d, %d, %d, '%s', '%s', '%s' )",
      +			intval($importer["uid"]),
      +			intval($contact_record["id"]),
      +			0,
      +			0,
      +			dbesc( t('Sharing notification from Diaspora network')),
      +			dbesc($hash),
      +			dbesc(datetime_convert())
      +		);
      +	}
      +	else {
      +
      +		// automatic friend approval
      +
      +		require_once('include/Photo.php');
      +
      +		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
      +		// we are going to change the relationship and make them a follower.
      +
      +		if($importer["page-flags"] == PAGE_FREELOVE)
      +			$new_relation = CONTACT_IS_FRIEND;
      +		else
      +			$new_relation = CONTACT_IS_FOLLOWER;
      +
      +		$r = q("UPDATE `contact` SET `rel` = %d,
      +			`name-date` = '%s',
      +			`uri-date` = '%s',
      +			`blocked` = 0,
      +			`pending` = 0,
      +			`writable` = 1
      +			WHERE `id` = %d
      +			",
      +			intval($new_relation),
      +			dbesc(datetime_convert()),
      +			dbesc(datetime_convert()),
      +			intval($contact_record["id"])
      +		);
      +
      +		$u = q("select * from user where uid = %d limit 1",intval($importer["uid"]));
      +		if($u)
      +			$ret = diaspora_share($u[0],$contact_record);
      +	}
      +*/
       		return true;
       	}
       
       	private function import_reshare($importer, $data) {
      +/*
      +	$guid = notags(unxmlify($xml->guid));
      +	$author = notags(unxmlify($xml->author));
      +
      +
      +	if($author != $msg["author"]) {
      +		logger('diaspora_post: Potential forgery. Message handle is not the same as envelope sender.');
      +		return 202;
      +	}
      +
      +	$contact = diaspora_get_contact_by_handle($importer["uid"],$author);
      +	if(! $contact)
      +		return;
      +
      +	if(! diaspora_post_allow($importer,$contact, false)) {
      +		logger('diaspora_reshare: Ignoring this author: ' . $author . ' ' . print_r($xml,true));
      +		return 202;
      +	}
      +
      +	$message_id = $author . ':' . $guid;
      +	$r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1",
      +		intval($importer["uid"]),
      +		dbesc($guid)
      +	);
      +	if(count($r)) {
      +		logger('diaspora_reshare: message exists: ' . $guid);
      +		return;
      +	}
      +
      +	$orig_author = notags(unxmlify($xml->root_diaspora_id));
      +	$orig_guid = notags(unxmlify($xml->root_guid));
      +	$orig_url = $a->get_baseurl()."/display/".$orig_guid;
      +
      +	$create_original_post = false;
      +
      +	// Do we already have this item?
      +	$r = q("SELECT `body`, `tag`, `app`, `created`, `plink`, `object`, `object-type`, `uri` FROM `item` WHERE `guid` = '%s' AND `visible` AND NOT 
      +`deleted` AND `body` != '' LIMIT 1",
      +		dbesc($orig_guid),
      +		dbesc(NETWORK_DIASPORA)
      +	);
      +	if(count($r)) {
      +		logger('reshared message '.$orig_guid." reshared by ".$guid.' already exists on system.');
      +
      +		// Maybe it is already a reshared item?
      +		// Then refetch the content, since there can be many side effects with reshared posts from other networks or reshares from reshares
      +		require_once('include/api.php');
      +		if (api_share_as_retweet($r[0]))
      +			$r = array();
      +		else {
      +			$body = $r[0]["body"];
      +			$str_tags = $r[0]["tag"];
      +			$app = $r[0]["app"];
      +			$orig_created = $r[0]["created"];
      +			$orig_plink = $r[0]["plink"];
      +			$orig_uri = $r[0]["uri"];
      +			$object = $r[0]["object"];
      +			$objecttype = $r[0]["object-type"];
      +		}
      +	}
      +
      +	if (!count($r)) {
      +		$body = "";
      +		$str_tags = "";
      +		$app = "";
      +
      +		$server = 'https://'.substr($orig_author,strpos($orig_author,'@')+1);
      +		logger('1st try: reshared message '.$orig_guid." reshared by ".$guid.' will be fetched from original server: '.$server);
      +		$item = diaspora_fetch_message($orig_guid, $server);
      +
      +		if (!$item) {
      +			$server = 'https://'.substr($author,strpos($author,'@')+1);
      +			logger('2nd try: reshared message '.$orig_guid." reshared by ".$guid." will be fetched from sharer's server: ".$server);
      +			$item = diaspora_fetch_message($orig_guid, $server);
      +		}
      +		if (!$item) {
      +			$server = 'http://'.substr($orig_author,strpos($orig_author,'@')+1);
      +			logger('3rd try: reshared message '.$orig_guid." reshared by ".$guid.' will be fetched from original server: '.$server);
      +			$item = diaspora_fetch_message($orig_guid, $server);
      +		}
      +		if (!$item) {
      +			$server = 'http://'.substr($author,strpos($author,'@')+1);
      +			logger('4th try: reshared message '.$orig_guid." reshared by ".$guid." will be fetched from sharer's server: ".$server);
      +			$item = diaspora_fetch_message($orig_guid, $server);
      +		}
      +
      +		if ($item) {
      +			$body = $item["body"];
      +			$str_tags = $item["tag"];
      +			$app = $item["app"];
      +			$orig_created = $item["created"];
      +			$orig_author = $item["author"];
      +			$orig_guid = $item["guid"];
      +			$orig_plink = diaspora_plink($orig_author, $orig_guid);
      +			$orig_uri = $orig_author.':'.$orig_guid;
      +			$create_original_post = ($body != "");
      +			$object = $item["object"];
      +			$objecttype = $item["object-type"];
      +		}
      +	}
      +
      +	$plink = diaspora_plink($author, $guid);
      +
      +	$person = find_diaspora_person_by_handle($orig_author);
      +
      +	$created = unxmlify($xml->created_at);
      +	$private = ((unxmlify($xml->public) == 'false') ? 1 : 0);
      +
      +	$datarray = array();
      +
      +	$datarray["uid"] = $importer["uid"];
      +	$datarray["contact-id"] = $contact["id"];
      +	$datarray["wall"] = 0;
      +	$datarray["network"]  = NETWORK_DIASPORA;
      +	$datarray["guid"] = $guid;
      +	$datarray["uri"] = $datarray["parent-uri"] = $message_id;
      +	$datarray["changed"] = $datarray["created"] = $datarray["edited"] = datetime_convert('UTC','UTC',$created);
      +	$datarray["private"] = $private;
      +	$datarray["parent"] = 0;
      +	$datarray["plink"] = $plink;
      +	$datarray["owner-name"] = $contact["name"];
      +	$datarray["owner-link"] = $contact["url"];
      +	$datarray["owner-avatar"] = ((x($contact,'thumb')) ? $contact["thumb"] : $contact["photo"]);
      +	      $prefix = share_header($person["name"], $person["url"], ((x($person,'thumb')) ? $person["thumb"] : $person["photo"]), $orig_guid, $orig_created, $orig_url);
      +
      +		$datarray["author-name"] = $contact["name"];
      +		$datarray["author-link"] = $contact["url"];
      +		$datarray["author-avatar"] = $contact["thumb"];
      +		$datarray["body"] = $prefix.$body."[/share]";
      +
      +	$datarray["object"] = json_encode($xml);
      +	$datarray["object-type"] = $objecttype;
      +
      +	$datarray["tag"] = $str_tags;
      +	$datarray["app"]  = $app;
      +
      +	// if empty content it might be a photo that hasn't arrived yet. If a photo arrives, we'll make it visible. (testing)
      +	$datarray["visible"] = ((strlen($body)) ? 1 : 0);
      +
      +	// Store the original item of a reshare
      +	if ($create_original_post) {
      +		require_once("include/Contact.php");
      +
      +		$datarray2 = $datarray;
      +
      +		$datarray2["uid"] = 0;
      +		$datarray2["contact-id"] = get_contact($person["url"], 0);
      +		$datarray2["guid"] = $orig_guid;
      +		$datarray2["uri"] = $datarray2["parent-uri"] = $orig_uri;
      +		$datarray2["changed"] = $datarray2["created"] = $datarray2["edited"] = $datarray2["commented"] = $datarray2["received"] = datetime_convert('UTC','UTC',$orig_created);
      +		$datarray2["parent"] = 0;
      +		$datarray2["plink"] = $orig_plink;
      +
      +		$datarray2["author-name"] = $person["name"];
      +		$datarray2["author-link"] = $person["url"];
      +		$datarray2["author-avatar"] = ((x($person,'thumb')) ? $person["thumb"] : $person["photo"]);
      +		$datarray2["owner-name"] = $datarray2["author-name"];
      +		$datarray2["owner-link"] = $datarray2["author-link"];
      +		$datarray2["owner-avatar"] = $datarray2["author-avatar"];
      +		$datarray2["body"] = $body;
      +		$datarray2["object"] = $object;
      +
      +		DiasporaFetchGuid($datarray2);
      +		$message_id = item_store($datarray2);
      +
      +		logger("Store original item ".$orig_guid." under message id ".$message_id);
      +	}
      +
      +	DiasporaFetchGuid($datarray);
      +	$message_id = item_store($datarray);
      +*/
       		return true;
       	}
       
      @@ -786,8 +1316,131 @@ print_r($datarray);
       		return true;
       	}
       
      -	private function import_status_message($importer, $data) {
      -		return true;
      +	private function import_status_message($importer, $data, $msg, $data2) {
      +
      +		$raw_message = unxmlify($data->raw_message);
      +		$guid = notags(unxmlify($data->guid));
      +		$author = notags(unxmlify($data->author));
      +		$public = notags(unxmlify($data->public));
      +		$created_at = notags(unxmlify($data->created_at));
      +		$provider_display_name = notags(unxmlify($data->provider_display_name));
      +
      +		foreach ($data->children() AS $name => $entry)
      +			if (count($entry->children()))
      +				if (!in_array($name, array("location", "photo", "poll")))
      +					die("Kinder: ".$name."\n");
      +/*
      +		if ($data->location) {
      +			print_r($location);
      +			foreach ($data->location->children() AS $fieldname => $data)
      +				echo $fieldname." - ".$data."\n";
      +			die("Location!\n");
      +		}
      +*/
      +/*
      +		if ($data->photo) {
      +			print_r($data->photo);
      +			foreach ($data->photo->children() AS $fieldname => $data)
      +				echo $fieldname." - ".$data."\n";
      +			die("Photo!\n");
      +		}
      +*/
      +
      +		if ($data->poll) {
      +			print_r($data2);
      +			print_r($data);
      +			die("poll!\n");
      +		}
      +
      +
      +		$contact = self::get_contact_by_handle($importer["uid"], $author);
      +		if (!$contact) {
      +			logger("A Contact for handle ".$author." and user ".$importer["uid"]." was not found");
      +			return false;
      +		}
      +
      +		if (!self::post_allow($importer, $contact, false)) {
      +			logger("Ignoring this author.");
      +			return false;
      +		}
      +/*
      +		$r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1",
      +			intval($importer["uid"]),
      +			dbesc($guid)
      +		);
      +		if(count($r)) {
      +			logger("message exists: ".$guid);
      +			return false;
      +		}
      +*/
      +		$private = (($public == "false") ? 1 : 0);
      +
      +		$body = diaspora2bb($raw_message);
      +
      +		$datarray = array();
      +
      +		if($data->photo->remote_photo_path AND $data->photo->remote_photo_name)
      +			$datarray["object-type"] = ACTIVITY_OBJ_PHOTO;
      +		else {
      +			$datarray["object-type"] = ACTIVITY_OBJ_NOTE;
      +			// Add OEmbed and other information to the body
      +			if (!self::is_redmatrix($contact["url"]))
      +				$body = add_page_info_to_body($body, false, true);
      +		}
      +
      +		$str_tags = "";
      +
      +		$cnt = preg_match_all("/@\[url=(.*?)\[\/url\]/ism", $body, $matches, PREG_SET_ORDER);
      +		if($cnt) {
      +			foreach($matches as $mtch) {
      +				if(strlen($str_tags))
      +					$str_tags .= ",";
      +				$str_tags .= "@[url=".$mtch[1]."[/url]";
      +			}
      +		}
      +		$plink = self::plink($author, $guid);
      +
      +		$datarray["uid"] = $importer["uid"];
      +		$datarray["contact-id"] = $contact["id"];
      +		$datarray["network"] = NETWORK_DIASPORA;
      +
      +		$datarray["author-name"] = $contact["name"];
      +		$datarray["author-link"] = $contact["url"];
      +		$datarray["author-avatar"] = ((x($contact,"thumb")) ? $contact["thumb"] : $contact["photo"]);
      +
      +		$datarray["owner-name"] = $datarray["author-name"];
      +		$datarray["owner-link"] = $datarray["author-link"];
      +		$datarray["owner-avatar"] = $datarray["author-avatar"];
      +
      +		$datarray["guid"] = $guid;
      +		$datarray["uri"] = $datarray["parent-uri"] = $author.":".$guid;
      +
      +		$datarray["verb"] = ACTIVITY_POST;
      +		$datarray["gravity"] = GRAVITY_PARENT;
      +
      +		$datarray["object"] = json_encode($data);
      +
      +		$datarray["body"] = $body;
      +
      +		$datarray["tag"] = $str_tags;
      +		if ($provider_display_name != "")
      +			$datarray["app"] = $provider_display_name;
      +
      +		$datarray["plink"] = $plink;
      +		$datarray["private"] = $private;
      +		$datarray["changed"] = $datarray["created"] = $datarray["edited"] = datetime_convert("UTC", "UTC", $created_at);
      +
      +		// if empty content it might be a photo that hasn't arrived yet. If a photo arrives, we'll make it visible.
      +
      +		$datarray["visible"] = ((strlen($body)) ? 1 : 0);
      +
      +		self::fetch_guid($datarray);
      +		//$message_id = item_store($datarray);
      +		print_r($datarray);
      +
      +		logger("Stored item with message id ".$message_id, LOGGER_DEBUG);
      +
      +		return $message_id;
       	}
       }
       ?>
      
      From f06f27c93dbcc677453715d35175f51131a46adc Mon Sep 17 00:00:00 2001
      From: Michael Vogel 
      Date: Mon, 29 Feb 2016 23:54:25 +0100
      Subject: [PATCH 017/211] Account deletion should work, status messages works -
       reshares are half done.
      
      ---
       include/diaspora2.php | 519 ++++++++++++++++++++++++++----------------
       1 file changed, 328 insertions(+), 191 deletions(-)
      
      diff --git a/include/diaspora2.php b/include/diaspora2.php
      index 578a496c0a..59514ebc76 100644
      --- a/include/diaspora2.php
      +++ b/include/diaspora2.php
      @@ -58,7 +58,7 @@ class diaspora {
       
       		// Use a dummy importer to import the data for the public copy
       		$importer = array("uid" => 0, "page-flags" => PAGE_FREELOVE);
      -		self::dispatch($importer,$msg);
      +		$item_id = self::dispatch($importer,$msg);
       
       		// Now distribute it to the followers
       		$r = q("SELECT `user`.* FROM `user` WHERE `user`.`uid` IN
      @@ -74,6 +74,8 @@ class diaspora {
       			}
       		} else
       			logger("No subscribers for ".$msg["author"]." ".print_r($msg, true));
      +
      +		return $item_id;
       	}
       
       	public static function dispatch($importer, $msg) {
      @@ -82,7 +84,7 @@ class diaspora {
       		// This will often be different with relayed messages (for example "like" and "comment")
       		$sender = $msg["author"];
       
      -		if (!diaspora::valid_posting($msg, $fields, $data2)) {
      +		if (!diaspora::valid_posting($msg, $fields)) {
       			logger("Invalid posting");
       			return false;
       		}
      @@ -90,8 +92,9 @@ class diaspora {
       		$type = $fields->getName();
       
       		switch ($type) {
      -			case "account_deletion": // Not implemented
      -				return self::import_account_deletion($importer, $fields);
      +			case "account_deletion":
      +				return true;
      +				//return self::import_account_deletion($importer, $fields);
       
       			case "comment":
       				return true;
      @@ -131,7 +134,8 @@ class diaspora {
       				return self::import_retraction($importer, $fields);
       
       			case "status_message":
      -				return self::import_status_message($importer, $fields, $msg, $data2);
      +				return true;
      +				//return self::import_status_message($importer, $fields, $msg, $data2);
       
       			default:
       				logger("Unknown message type ".$type);
      @@ -152,7 +156,7 @@ class diaspora {
       	 *
       	 * @return bool Is the posting valid?
       	 */
      -	private function valid_posting($msg, &$fields, &$element) {
      +	private function valid_posting($msg, &$fields) {
       
       		$data = parse_xml_string($msg["message"], false);
       
      @@ -554,7 +558,16 @@ class diaspora {
       	}
       
       	private function import_account_deletion($importer, $data) {
      -		// Not supported by now. We are waiting for sample data
      +		$author = notags(unxmlify($data->author));
      +
      +		$contact = self::get_contact_by_handle($importer["uid"], $author);
      +		if (!$contact) {
      +			logger("cannot find contact for sender: ".$sender);
      +			return false;
      +		}
      +
      +		// We now remove the contact
      +		contact_remove($contact["id"]);
       		return true;
       	}
       
      @@ -647,6 +660,167 @@ class diaspora {
       	}
       
       	private function import_conversation($importer, $data) {
      +/*
      +        $guid = notags(unxmlify($xml->guid));
      +        $subject = notags(unxmlify($xml->subject));
      +        $diaspora_handle = notags(unxmlify($xml->diaspora_handle));
      +        $participant_handles = notags(unxmlify($xml->participant_handles));
      +        $created_at = datetime_convert('UTC','UTC',notags(unxmlify($xml->created_at)));
      +
      +        $parent_uri = $diaspora_handle . ':' . $guid;
      +
      +        $messages = $xml->message;
      +
      +        if(! count($messages)) {
      +                logger('diaspora_conversation: empty conversation');
      +                return;
      +        }
      +
      +        $contact = diaspora_get_contact_by_handle($importer['uid'],$msg['author']);
      +        if(! $contact) {
      +                logger('diaspora_conversation: cannot find contact: ' . $msg['author']);
      +                return;
      +        }
      +
      +        if(($contact['rel'] == CONTACT_IS_FOLLOWER) || ($contact['blocked']) || ($contact['readonly'])) {
      +                logger('diaspora_conversation: Ignoring this author.');
      +                return 202;
      +        }
      +
      +        $conversation = null;
      +
      +        $c = q("select * from conv where uid = %d and guid = '%s' limit 1",
      +                intval($importer['uid']),
      +                dbesc($guid)
      +        );
      +        if(count($c))
      +                $conversation = $c[0];
      +        else {
      +                $r = q("insert into conv (uid,guid,creator,created,updated,subject,recips) values(%d, '%s', '%s', '%s', '%s', '%s', '%s') ",
      +                        intval($importer['uid']),
      +                        dbesc($guid),
      +                        dbesc($diaspora_handle),
      +                        dbesc(datetime_convert('UTC','UTC',$created_at)),
      +                        dbesc(datetime_convert()),
      +                        dbesc($subject),
      +                        dbesc($participant_handles)
      +                );
      +                if($r)
      +                        $c = q("select * from conv where uid = %d and guid = '%s' limit 1",
      +                intval($importer['uid']),
      +            dbesc($guid)
      +        );
      +            if(count($c))
      +            $conversation = $c[0];
      +        }
      +        if(! $conversation) {
      +                logger('diaspora_conversation: unable to create conversation.');
      +                return;
      +        }
      +
      +        foreach($messages as $mesg) {
      +
      +                $reply = 0;
      +
      +                $msg_guid = notags(unxmlify($mesg->guid));
      +                $msg_parent_guid = notags(unxmlify($mesg->parent_guid));
      +                $msg_parent_author_signature = notags(unxmlify($mesg->parent_author_signature));
      +                $msg_author_signature = notags(unxmlify($mesg->author_signature));
      +                $msg_text = unxmlify($mesg->text);
      +                $msg_created_at = datetime_convert('UTC','UTC',notags(unxmlify($mesg->created_at)));
      +                $msg_diaspora_handle = notags(unxmlify($mesg->diaspora_handle));
      +                $msg_conversation_guid = notags(unxmlify($mesg->conversation_guid));
      +                if($msg_conversation_guid != $guid) {
      +                        logger('diaspora_conversation: message conversation guid does not belong to the current conversation. ' . $xml);
      +                        continue;
      +                }
      +
      +                $body = diaspora2bb($msg_text);
      +                $message_id = $msg_diaspora_handle . ':' . $msg_guid;
      +
      +                $author_signed_data = $msg_guid . ';' . $msg_parent_guid . ';' . $msg_text . ';' . unxmlify($mesg->created_at) . ';' . $msg_diaspora_handle . ';' . $msg_conversation_guid;
      +
      +                $author_signature = base64_decode($msg_author_signature);
      +
      +                if(strcasecmp($msg_diaspora_handle,$msg['author']) == 0) {
      +                        $person = $contact;
      +                        $key = $msg['key'];
      +                }
      +                else {
      +                        $person = find_diaspora_person_by_handle($msg_diaspora_handle); 
      +
      +                        if(is_array($person) && x($person,'pubkey'))
      +                                $key = $person['pubkey'];
      +                        else {
      +                                logger('diaspora_conversation: unable to find author details');
      +                                continue;
      +                        }
      +                }
      +
      +                if(! rsa_verify($author_signed_data,$author_signature,$key,'sha256')) {
      +                        logger('diaspora_conversation: verification failed.');
      +                        continue;
      +                }
      +
      +                if($msg_parent_author_signature) {
      +                        $owner_signed_data = $msg_guid . ';' . $msg_parent_guid . ';' . $msg_text . ';' . unxmlify($mesg->created_at) . ';' . $msg_diaspora_handle . ';' . $msg_conversation_guid;
      +
      +                        $parent_author_signature = base64_decode($msg_parent_author_signature);
      +
      +                        $key = $msg['key'];
      +
      +                        if(! rsa_verify($owner_signed_data,$parent_author_signature,$key,'sha256')) {
      +                                logger('diaspora_conversation: owner verification failed.');
      +                                continue;
      +                        }
      +                }
      +
      +                $r = q("select id from mail where `uri` = '%s' limit 1",
      +                        dbesc($message_id)
      +                );
      +                if(count($r)) {
      +                        logger('diaspora_conversation: duplicate message already delivered.', LOGGER_DEBUG);
      +                        continue;
      +                }
      +
      +                q("insert into mail ( `uid`, `guid`, `convid`, `from-name`,`from-photo`,`from-url`,`contact-id`,`title`,`body`,`seen`,`reply`,`uri`,`parent-uri`,`created`) values ( %d, '%s', %d, '%s', '%s', '%s', %d, '%s', '%s', %d, %d, '%s','%s','%s')",
      +                        intval($importer['uid']),
      +                        dbesc($msg_guid),
      +                        intval($conversation['id']),
      +                        dbesc($person['name']),
      +                        dbesc($person['photo']),
      +                        dbesc($person['url']),
      +                        intval($contact['id']),
      +                        dbesc($subject),
      +                        dbesc($body),
      +                        0,
      +                        0,
      +                        dbesc($message_id),
      +                        dbesc($parent_uri),
      +                        dbesc($msg_created_at)
      +                );
      +
      +                q("update conv set updated = '%s' where id = %d",
      +                        dbesc(datetime_convert()),
      +                        intval($conversation['id'])
      +                );
      +
      +                notification(array(
      +                        'type' => NOTIFY_MAIL,
      +                        'notify_flags' => $importer['notify-flags'],
      +                        'language' => $importer['language'],
      +                        'to_name' => $importer['username'],
      +                        'to_email' => $importer['email'],
      +                        'uid' =>$importer['uid'],
      +                        'item' => array('subject' => $subject, 'body' => $body),
      +                        'source_name' => $person['name'],
      +                        'source_link' => $person['url'],
      +                        'source_photo' => $person['thumb'],
      +                        'verb' => ACTIVITY_POST,
      +                        'otype' => 'mail'
      +                ));
      +        }
      +*/
       		return true;
       	}
       
      @@ -858,14 +1032,17 @@ EOT;
       	}
       
       	private function import_participation($importer, $data) {
      +		// I'm not sure if we can fully support this message type
       		return true;
       	}
       
       	private function import_photo($importer, $data) {
      +		// There doesn't seem to be a reason for this function, since the photo data is transmitted in the status message as well
       		return true;
       	}
       
       	private function import_poll_participation($importer, $data) {
      +		// We don't support polls by now
       		return true;
       	}
       
      @@ -1138,185 +1315,156 @@ print_r($data);
       	}
       
       	private function import_reshare($importer, $data) {
      +		$root_author = notags(unxmlify($data->root_author));
      +		$root_guid = notags(unxmlify($data->root_guid));
      +		$guid = notags(unxmlify($data->guid));
      +		$author = notags(unxmlify($data->author));
      +		$public = notags(unxmlify($data->public));
      +		$created_at = notags(unxmlify($data->created_at));
      +
      +		$contact = self::get_contact_by_handle($importer["uid"], $author);
      +		if (!$contact)
      +			return false;
      +
      +		if (!self::post_allow($importer, $contact, false)) {
      +			logger("Ignoring this author: ".$author." ".print_r($data,true));
      +			return false;
      +		}
       /*
      -	$guid = notags(unxmlify($xml->guid));
      -	$author = notags(unxmlify($xml->author));
      -
      -
      -	if($author != $msg["author"]) {
      -		logger('diaspora_post: Potential forgery. Message handle is not the same as envelope sender.');
      -		return 202;
      -	}
      -
      -	$contact = diaspora_get_contact_by_handle($importer["uid"],$author);
      -	if(! $contact)
      -		return;
      -
      -	if(! diaspora_post_allow($importer,$contact, false)) {
      -		logger('diaspora_reshare: Ignoring this author: ' . $author . ' ' . print_r($xml,true));
      -		return 202;
      -	}
      -
      -	$message_id = $author . ':' . $guid;
      -	$r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1",
      -		intval($importer["uid"]),
      -		dbesc($guid)
      -	);
      -	if(count($r)) {
      -		logger('diaspora_reshare: message exists: ' . $guid);
      -		return;
      -	}
      -
      -	$orig_author = notags(unxmlify($xml->root_diaspora_id));
      -	$orig_guid = notags(unxmlify($xml->root_guid));
      -	$orig_url = $a->get_baseurl()."/display/".$orig_guid;
      -
      -	$create_original_post = false;
      -
      -	// Do we already have this item?
      -	$r = q("SELECT `body`, `tag`, `app`, `created`, `plink`, `object`, `object-type`, `uri` FROM `item` WHERE `guid` = '%s' AND `visible` AND NOT 
      -`deleted` AND `body` != '' LIMIT 1",
      -		dbesc($orig_guid),
      -		dbesc(NETWORK_DIASPORA)
      -	);
      -	if(count($r)) {
      -		logger('reshared message '.$orig_guid." reshared by ".$guid.' already exists on system.');
      -
      -		// Maybe it is already a reshared item?
      -		// Then refetch the content, since there can be many side effects with reshared posts from other networks or reshares from reshares
      -		require_once('include/api.php');
      -		if (api_share_as_retweet($r[0]))
      -			$r = array();
      -		else {
      -			$body = $r[0]["body"];
      -			$str_tags = $r[0]["tag"];
      -			$app = $r[0]["app"];
      -			$orig_created = $r[0]["created"];
      -			$orig_plink = $r[0]["plink"];
      -			$orig_uri = $r[0]["uri"];
      -			$object = $r[0]["object"];
      -			$objecttype = $r[0]["object-type"];
      +		$r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1",
      +			intval($importer["uid"]),
      +			dbesc($guid)
      +		);
      +		if(count($r)) {
      +			logger("message exists: ".$guid);
      +			return;
       		}
      -	}
      +*/
      +		$orig_author = $root_author;
      +		$orig_guid = $root_guid;
      +		$orig_url = App::get_baseurl()."/display/".$guid;
       
      -	if (!count($r)) {
      -		$body = "";
      -		$str_tags = "";
      -		$app = "";
      +		$create_original_post = false;
       
      -		$server = 'https://'.substr($orig_author,strpos($orig_author,'@')+1);
      -		logger('1st try: reshared message '.$orig_guid." reshared by ".$guid.' will be fetched from original server: '.$server);
      -		$item = diaspora_fetch_message($orig_guid, $server);
      +		// Do we already have this item?
      +		$r = q("SELECT `body`, `tag`, `app`, `created`, `plink`, `object`, `object-type`, `uri` FROM `item` WHERE `guid` = '%s' AND `visible` AND NOT `deleted` AND `body` != '' LIMIT 1",
      +			dbesc($orig_guid),
      +			dbesc(NETWORK_DIASPORA)
      +		);
      +		if(count($r)) {
      +			logger("reshared message ".$orig_guid." reshared by ".$guid." already exists on system.");
       
      -		if (!$item) {
      -			$server = 'https://'.substr($author,strpos($author,'@')+1);
      -			logger('2nd try: reshared message '.$orig_guid." reshared by ".$guid." will be fetched from sharer's server: ".$server);
      -			$item = diaspora_fetch_message($orig_guid, $server);
      -		}
      -		if (!$item) {
      -			$server = 'http://'.substr($orig_author,strpos($orig_author,'@')+1);
      -			logger('3rd try: reshared message '.$orig_guid." reshared by ".$guid.' will be fetched from original server: '.$server);
      -			$item = diaspora_fetch_message($orig_guid, $server);
      -		}
      -		if (!$item) {
      -			$server = 'http://'.substr($author,strpos($author,'@')+1);
      -			logger('4th try: reshared message '.$orig_guid." reshared by ".$guid." will be fetched from sharer's server: ".$server);
      -			$item = diaspora_fetch_message($orig_guid, $server);
      +			// Maybe it is already a reshared item?
      +			// Then refetch the content, since there can be many side effects with reshared posts from other networks or reshares from reshares
      +			require_once('include/api.php');
      +			if (api_share_as_retweet($r[0]))
      +				$r = array();
      +			else {
      +				$body = $r[0]["body"];
      +				$str_tags = $r[0]["tag"];
      +				$app = $r[0]["app"];
      +				$orig_created = $r[0]["created"];
      +				$orig_plink = $r[0]["plink"];
      +				$orig_uri = $r[0]["uri"];
      +				$object = $r[0]["object"];
      +				$objecttype = $r[0]["object-type"];
      +			}
       		}
       
      -		if ($item) {
      -			$body = $item["body"];
      -			$str_tags = $item["tag"];
      -			$app = $item["app"];
      -			$orig_created = $item["created"];
      -			$orig_author = $item["author"];
      -			$orig_guid = $item["guid"];
      -			$orig_plink = diaspora_plink($orig_author, $orig_guid);
      -			$orig_uri = $orig_author.':'.$orig_guid;
      -			$create_original_post = ($body != "");
      -			$object = $item["object"];
      -			$objecttype = $item["object-type"];
      +/* @todo
      +		if (!count($r)) {
      +			$body = "";
      +			$str_tags = "";
      +			$app = "";
      +
      +			$server = 'https://'.substr($orig_author,strpos($orig_author,'@')+1);
      +			logger('1st try: reshared message '.$orig_guid." reshared by ".$guid.' will be fetched from original server: '.$server);
      +			$item = self::fetch_message($orig_guid, $server);
      +
      +			if (!$item) {
      +				$server = 'https://'.substr($author,strpos($author,'@')+1);
      +				logger('2nd try: reshared message '.$orig_guid." reshared by ".$guid." will be fetched from sharer's server: ".$server);
      +				$item = diaspora_fetch_message($orig_guid, $server);
      +			}
      +			if (!$item) {
      +				$server = 'http://'.substr($orig_author,strpos($orig_author,'@')+1);
      +				logger('3rd try: reshared message '.$orig_guid." reshared by ".$guid.' will be fetched from original server: '.$server);
      +				$item = diaspora_fetch_message($orig_guid, $server);
      +			}
      +			if (!$item) {
      +				$server = 'http://'.substr($author,strpos($author,'@')+1);
      +				logger('4th try: reshared message '.$orig_guid." reshared by ".$guid." will be fetched from sharer's server: ".$server);
      +				$item = diaspora_fetch_message($orig_guid, $server);
      +			}
      +
      +			if ($item) {
      +				$body = $item["body"];
      +				$str_tags = $item["tag"];
      +				$app = $item["app"];
      +				$orig_created = $item["created"];
      +				$orig_author = $item["author"];
      +				$orig_guid = $item["guid"];
      +				$orig_plink = diaspora_plink($orig_author, $orig_guid);
      +				$orig_uri = $orig_author.":".$orig_guid;
      +				$create_original_post = ($body != "");
      +				$object = $item["object"];
      +				$objecttype = $item["object-type"];
      +			}
       		}
      -	}
      +*/
      +		$plink = self::plink($author, $guid);
       
      -	$plink = diaspora_plink($author, $guid);
      +		$person = self::get_person_by_handle($orig_author);
       
      -	$person = find_diaspora_person_by_handle($orig_author);
      +		$private = (($public == "false") ? 1 : 0);
       
      -	$created = unxmlify($xml->created_at);
      -	$private = ((unxmlify($xml->public) == 'false') ? 1 : 0);
      +		$datarray = array();
       
      -	$datarray = array();
      -
      -	$datarray["uid"] = $importer["uid"];
      -	$datarray["contact-id"] = $contact["id"];
      -	$datarray["wall"] = 0;
      -	$datarray["network"]  = NETWORK_DIASPORA;
      -	$datarray["guid"] = $guid;
      -	$datarray["uri"] = $datarray["parent-uri"] = $message_id;
      -	$datarray["changed"] = $datarray["created"] = $datarray["edited"] = datetime_convert('UTC','UTC',$created);
      -	$datarray["private"] = $private;
      -	$datarray["parent"] = 0;
      -	$datarray["plink"] = $plink;
      -	$datarray["owner-name"] = $contact["name"];
      -	$datarray["owner-link"] = $contact["url"];
      -	$datarray["owner-avatar"] = ((x($contact,'thumb')) ? $contact["thumb"] : $contact["photo"]);
      -	      $prefix = share_header($person["name"], $person["url"], ((x($person,'thumb')) ? $person["thumb"] : $person["photo"]), $orig_guid, $orig_created, $orig_url);
      +		$datarray["uid"] = $importer["uid"];
      +		$datarray["contact-id"] = $contact["id"];
      +		$datarray["network"]  = NETWORK_DIASPORA;
       
       		$datarray["author-name"] = $contact["name"];
       		$datarray["author-link"] = $contact["url"];
      -		$datarray["author-avatar"] = $contact["thumb"];
      +		$datarray["author-avatar"] = ((x($contact,"thumb")) ? $contact["thumb"] : $contact["photo"]);
      +
      +		$datarray["owner-name"] = $datarray["author-name"];
      +		$datarray["owner-link"] = $datarray["author-link"];
      +		$datarray["owner-avatar"] = $datarray["author-avatar"];
      +
      +		$datarray["guid"] = $guid;
      +		$datarray["uri"] = $datarray["parent-uri"] = $author.":".$guid;
      +
      +		$datarray["verb"] = ACTIVITY_POST;
      +		$datarray["gravity"] = GRAVITY_PARENT;
      +
      +		$datarray["object"] = json_encode($data);
      +
      +		$prefix = share_header($person["name"], $person["url"], ((x($person,'thumb')) ? $person["thumb"] : $person["photo"]),
      +					$orig_guid, $orig_created, $orig_url);
       		$datarray["body"] = $prefix.$body."[/share]";
       
      -	$datarray["object"] = json_encode($xml);
      -	$datarray["object-type"] = $objecttype;
      +		$datarray["tag"] = $str_tags;
      +		$datarray["app"]  = $app;
       
      -	$datarray["tag"] = $str_tags;
      -	$datarray["app"]  = $app;
      +		$datarray["plink"] = $plink;
      +		$datarray["private"] = $private;
      +		$datarray["changed"] = $datarray["created"] = $datarray["edited"] = datetime_convert("UTC", "UTC", $created_at);
       
      -	// if empty content it might be a photo that hasn't arrived yet. If a photo arrives, we'll make it visible. (testing)
      -	$datarray["visible"] = ((strlen($body)) ? 1 : 0);
      +		$datarray["object-type"] = $objecttype;
       
      -	// Store the original item of a reshare
      -	if ($create_original_post) {
      -		require_once("include/Contact.php");
      +		self::fetch_guid($datarray);
      +		//$message_id = item_store($datarray);
      +		print_r($datarray);
       
      -		$datarray2 = $datarray;
      -
      -		$datarray2["uid"] = 0;
      -		$datarray2["contact-id"] = get_contact($person["url"], 0);
      -		$datarray2["guid"] = $orig_guid;
      -		$datarray2["uri"] = $datarray2["parent-uri"] = $orig_uri;
      -		$datarray2["changed"] = $datarray2["created"] = $datarray2["edited"] = $datarray2["commented"] = $datarray2["received"] = datetime_convert('UTC','UTC',$orig_created);
      -		$datarray2["parent"] = 0;
      -		$datarray2["plink"] = $orig_plink;
      -
      -		$datarray2["author-name"] = $person["name"];
      -		$datarray2["author-link"] = $person["url"];
      -		$datarray2["author-avatar"] = ((x($person,'thumb')) ? $person["thumb"] : $person["photo"]);
      -		$datarray2["owner-name"] = $datarray2["author-name"];
      -		$datarray2["owner-link"] = $datarray2["author-link"];
      -		$datarray2["owner-avatar"] = $datarray2["author-avatar"];
      -		$datarray2["body"] = $body;
      -		$datarray2["object"] = $object;
      -
      -		DiasporaFetchGuid($datarray2);
      -		$message_id = item_store($datarray2);
      -
      -		logger("Store original item ".$orig_guid." under message id ".$message_id);
      -	}
      -
      -	DiasporaFetchGuid($datarray);
      -	$message_id = item_store($datarray);
      -*/
      -		return true;
      +		return $message_id;
       	}
       
       	private function import_retraction($importer, $data) {
       		return true;
       	}
       
      -	private function import_status_message($importer, $data, $msg, $data2) {
      +	private function import_status_message($importer, $data) {
       
       		$raw_message = unxmlify($data->raw_message);
       		$guid = notags(unxmlify($data->guid));
      @@ -1325,34 +1473,6 @@ print_r($data);
       		$created_at = notags(unxmlify($data->created_at));
       		$provider_display_name = notags(unxmlify($data->provider_display_name));
       
      -		foreach ($data->children() AS $name => $entry)
      -			if (count($entry->children()))
      -				if (!in_array($name, array("location", "photo", "poll")))
      -					die("Kinder: ".$name."\n");
      -/*
      -		if ($data->location) {
      -			print_r($location);
      -			foreach ($data->location->children() AS $fieldname => $data)
      -				echo $fieldname." - ".$data."\n";
      -			die("Location!\n");
      -		}
      -*/
      -/*
      -		if ($data->photo) {
      -			print_r($data->photo);
      -			foreach ($data->photo->children() AS $fieldname => $data)
      -				echo $fieldname." - ".$data."\n";
      -			die("Photo!\n");
      -		}
      -*/
      -
      -		if ($data->poll) {
      -			print_r($data2);
      -			print_r($data);
      -			die("poll!\n");
      -		}
      -
      -
       		$contact = self::get_contact_by_handle($importer["uid"], $author);
       		if (!$contact) {
       			logger("A Contact for handle ".$author." and user ".$importer["uid"]." was not found");
      @@ -1363,7 +1483,7 @@ print_r($data);
       			logger("Ignoring this author.");
       			return false;
       		}
      -/*
      +
       		$r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1",
       			intval($importer["uid"]),
       			dbesc($guid)
      @@ -1372,11 +1492,26 @@ print_r($data);
       			logger("message exists: ".$guid);
       			return false;
       		}
      -*/
      +
      +		/// @todo enable support for polls
      +		// if ($data->poll) {
      +		//	print_r($data->poll);
      +		//	die("poll!\n");
      +		// }
      +
      +		$address = array();
      +		if ($data->location)
      +			foreach ($data->location->children() AS $fieldname => $data)
      +				$address[$fieldname] = notags(unxmlify($data));
      +
       		$private = (($public == "false") ? 1 : 0);
       
       		$body = diaspora2bb($raw_message);
       
      +		if ($data->photo)
      +			for ($i = 0; $i < count($data->photo); $i++)
      +				$body = "[img]".$data->photo[$i]->remote_photo_path.$data->photo[$i]->remote_photo_name."[/img]\n".$body;
      +
       		$datarray = array();
       
       		if($data->photo->remote_photo_path AND $data->photo->remote_photo_name)
      @@ -1430,9 +1565,11 @@ print_r($data);
       		$datarray["private"] = $private;
       		$datarray["changed"] = $datarray["created"] = $datarray["edited"] = datetime_convert("UTC", "UTC", $created_at);
       
      -		// if empty content it might be a photo that hasn't arrived yet. If a photo arrives, we'll make it visible.
      +		if (isset($address["address"]))
      +			$datarray["location"] = $address["address"];
       
      -		$datarray["visible"] = ((strlen($body)) ? 1 : 0);
      +		if (isset($address["lat"]) AND isset($address["lng"]))
      +			$datarray["coord"] = $address["lat"]." ".$address["lng"];
       
       		self::fetch_guid($datarray);
       		//$message_id = item_store($datarray);
      
      From 558f13e32253adae34ab9b320d6530801e05db0b Mon Sep 17 00:00:00 2001
      From: Michael Vogel 
      Date: Tue, 1 Mar 2016 07:57:26 +0100
      Subject: [PATCH 018/211] Reshares could work now, code is cleaned
      
      ---
       include/diaspora2.php | 356 +++++++++++++++++-------------------------
       1 file changed, 147 insertions(+), 209 deletions(-)
      
      diff --git a/include/diaspora2.php b/include/diaspora2.php
      index 59514ebc76..a1297c5193 100644
      --- a/include/diaspora2.php
      +++ b/include/diaspora2.php
      @@ -4,11 +4,14 @@
        * @brief The implementation of the diaspora protocol
        */
       
      +require_once("include/items.php");
       require_once("include/bb2diaspora.php");
       require_once("include/Scrape.php");
       require_once("include/Contact.php");
       require_once("include/Photo.php");
       require_once("include/socgraph.php");
      +require_once("include/group.php");
      +require_once("include/api.php");
       
       class xml {
       	function from_array($array, &$xml) {
      @@ -380,6 +383,64 @@ class diaspora {
       		return false;
       	}
       
      +	private function post_allow($importer, $contact, $is_comment = false) {
      +
      +		// perhaps we were already sharing with this person. Now they're sharing with us.
      +		// That makes us friends.
      +		// Normally this should have handled by getting a request - but this could get lost
      +		if($contact["rel"] == CONTACT_IS_FOLLOWER && in_array($importer["page-flags"], array(PAGE_FREELOVE))) {
      +			q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
      +				intval(CONTACT_IS_FRIEND),
      +				intval($contact["id"]),
      +				intval($importer["uid"])
      +			);
      +			$contact["rel"] = CONTACT_IS_FRIEND;
      +			logger("defining user ".$contact["nick"]." as friend");
      +		}
      +
      +		if(($contact["blocked"]) || ($contact["readonly"]) || ($contact["archive"]))
      +			return false;
      +		if($contact["rel"] == CONTACT_IS_SHARING || $contact["rel"] == CONTACT_IS_FRIEND)
      +			return true;
      +		if($contact["rel"] == CONTACT_IS_FOLLOWER)
      +			if(($importer["page-flags"] == PAGE_COMMUNITY) OR $is_comment)
      +				return true;
      +
      +		// Messages for the global users are always accepted
      +		if ($importer["uid"] == 0)
      +			return true;
      +
      +		return false;
      +	}
      +
      +	private function get_allowed_contact_by_handle($importer, $handle, $is_comment = false) {
      +		$contact = self::get_contact_by_handle($importer["uid"], $handle);
      +		if (!$contact) {
      +			logger("A Contact for handle ".$handle." and user ".$importer["uid"]." was not found");
      +			return false;
      +		}
      +
      +		if (!self::post_allow($importer, $contact, false)) {
      +			logger("The handle: ".$handle." is not allowed to post to user ".$importer["uid"]);
      +			return false;
      +		}
      +		return $contact;
      +	}
      +
      +	private function message_exists($uid, $guid) {
      +		$r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1",
      +			intval($uid),
      +			dbesc($guid)
      +		);
      +
      +		if(count($r)) {
      +			logger("message ".$guid." already exists for user ".$uid);
      +			return false;
      +		}
      +
      +		return true;
      +	}
      +
       	private function fetch_guid($item) {
       		preg_replace_callback("&\[url=/posts/([^\[\]]*)\](.*)\[\/url\]&Usi",
       			function ($match) use ($item){
      @@ -388,8 +449,6 @@ class diaspora {
       	}
       
       	private function fetch_guid_sub($match, $item) {
      -		$a = get_app();
      -
       		if (!self::store_by_guid($match[1], $item["author-link"]))
       			self::store_by_guid($match[1], $item["owner-link"]);
       	}
      @@ -453,36 +512,6 @@ class diaspora {
       		return $msg;
       	}
       
      -	private function post_allow($importer, $contact, $is_comment = false) {
      -
      -		// perhaps we were already sharing with this person. Now they're sharing with us.
      -		// That makes us friends.
      -		// Normally this should have handled by getting a request - but this could get lost
      -		if($contact["rel"] == CONTACT_IS_FOLLOWER && in_array($importer["page-flags"], array(PAGE_FREELOVE))) {
      -			q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
      -				intval(CONTACT_IS_FRIEND),
      -				intval($contact["id"]),
      -				intval($importer["uid"])
      -			);
      -			$contact["rel"] = CONTACT_IS_FRIEND;
      -			logger("defining user ".$contact["nick"]." as friend");
      -		}
      -
      -		if(($contact["blocked"]) || ($contact["readonly"]) || ($contact["archive"]))
      -			return false;
      -		if($contact["rel"] == CONTACT_IS_SHARING || $contact["rel"] == CONTACT_IS_FRIEND)
      -			return true;
      -		if($contact["rel"] == CONTACT_IS_FOLLOWER)
      -			if(($importer["page-flags"] == PAGE_COMMUNITY) OR $is_comment)
      -				return true;
      -
      -		// Messages for the global users are always accepted
      -		if ($importer["uid"] == 0)
      -			return true;
      -
      -		return false;
      -	}
      -
       	private function fetch_parent_item($uid, $guid, $author, $contact) {
       		$r = q("SELECT `id`, `body`, `wall`, `uri`, `private`, `origin`,
       				`author-name`, `author-link`, `author-avatar`,
      @@ -562,7 +591,7 @@ class diaspora {
       
       		$contact = self::get_contact_by_handle($importer["uid"], $author);
       		if (!$contact) {
      -			logger("cannot find contact for sender: ".$sender);
      +			logger("cannot find contact for author: ".$author);
       			return false;
       		}
       
      @@ -577,25 +606,12 @@ class diaspora {
       		$text = unxmlify($data->text);
       		$author = notags(unxmlify($data->author));
       
      -		$contact = self::get_contact_by_handle($importer["uid"], $sender);
      -		if (!$contact) {
      -			logger("cannot find contact for sender: ".$sender);
      +		$contact = self::get_allowed_contact_by_handle($importer, $sender, true);
      +		if (!$contact)
       			return false;
      -		}
       
      -		if (!self::post_allow($importer,$contact, true)) {
      -			logger("Ignoring the author ".$sender);
      +		if (self::message_exists($importer["uid"], $guid))
       			return false;
      -		}
      -
      -		$r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1",
      -			intval($importer["uid"]),
      -			dbesc($guid)
      -		);
      -		if(count($r)) {
      -			logger("The comment already exists: ".$guid);
      -			return false;
      -		}
       
       		$parent_item = self::fetch_parent_item($importer["uid"], $parent_guid, $author, $contact);
       		if (!$parent_item)
      @@ -676,16 +692,9 @@ class diaspora {
                       return;
               }
       
      -        $contact = diaspora_get_contact_by_handle($importer['uid'],$msg['author']);
      -        if(! $contact) {
      -                logger('diaspora_conversation: cannot find contact: ' . $msg['author']);
      -                return;
      -        }
      -
      -        if(($contact['rel'] == CONTACT_IS_FOLLOWER) || ($contact['blocked']) || ($contact['readonly'])) {
      -                logger('diaspora_conversation: Ignoring this author.');
      -                return 202;
      -        }
      +		$contact = self::get_allowed_contact_by_handle($importer, $sender, true)
      +		if (!$contact)
      +			return false;
       
               $conversation = null;
       
      @@ -871,25 +880,12 @@ EOT;
       			return false;
       		}
       
      -		$contact = self::get_contact_by_handle($importer["uid"], $sender);
      -		if (!$contact) {
      -			logger("cannot find contact for sender: ".$sender);
      +		$contact = self::get_allowed_contact_by_handle($importer, $sender, true);
      +		if (!$contact)
       			return false;
      -		}
       
      -		if (!self::post_allow($importer,$contact, true)) {
      -			logger("Ignoring the author ".$sender);
      +		if (self::message_exists($importer["uid"], $guid))
       			return false;
      -		}
      -
      -		$r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1",
      -			intval($importer["uid"]),
      -			dbesc($guid)
      -		);
      -		if(count($r)) {
      -			logger("The like already exists: ".$guid);
      -			return false;
      -		}
       
       		$parent_item = self::fetch_parent_item($importer["uid"], $parent_guid, $author, $contact);
       		if (!$parent_item)
      @@ -961,16 +957,9 @@ EOT;
       
       		$parent_uri = $author.":".$parent_guid;
       
      -		$contact = self::get_contact_by_handle($importer["uid"], $author);
      -		if (!$contact) {
      -			logger("cannot find contact: ".$author);
      +		$contact = self::get_allowed_contact_by_handle($importer, $author, true);
      +		if (!$contact)
       			return false;
      -		}
      -
      -		if(($contact["rel"] == CONTACT_IS_FOLLOWER) || ($contact["blocked"]) || ($contact["readonly"])) {
      -			logger("Ignoring this author.");
      -			return false;
      -		}
       
       		$conversation = null;
       
      @@ -1159,7 +1148,6 @@ print_r($data);
       		);
       
       		if((count($r)) && (!$r[0]["hide-friends"]) && (!$contact["hidden"]) && intval(get_pconfig($importer["uid"],'system','post_newfriend'))) {
      -			require_once('include/items.php');
       
       			$self = q("SELECT * FROM `contact` WHERE `self` = 1 AND `uid` = %d LIMIT 1",
       				intval($importer["uid"])
      @@ -1170,7 +1158,7 @@ print_r($data);
       			if(count($self) && $contact["rel"] == CONTACT_IS_FOLLOWER) {
       
       				$arr = array();
      -				$arr["uri"] = $arr["parent-uri"] = item_new_uri($a->get_hostname(), $importer["uid"]);
      +				$arr["uri"] = $arr["parent-uri"] = item_new_uri(App::get_hostname(), $importer["uid"]);
       				$arr["uid"] = $importer["uid"];
       				$arr["contact-id"] = $self[0]["id"];
       				$arr["wall"] = 1;
      @@ -1256,7 +1244,6 @@ print_r($data);
       		intval($importer["uid"])
       	);
       	if($g && intval($g[0]["def_gid"])) {
      -		require_once('include/group.php');
       		group_add_member($importer["uid"],'',$contact_record["id"],$g[0]["def_gid"]);
       	}
       
      @@ -1279,8 +1266,6 @@ print_r($data);
       
       		// automatic friend approval
       
      -		require_once('include/Photo.php');
      -
       		update_contact_avatar($contact_record["photo"],$importer["uid"],$contact_record["id"]);
       
       		// technically they are sharing with us (CONTACT_IS_SHARING),
      @@ -1314,6 +1299,60 @@ print_r($data);
       		return true;
       	}
       
      +	private function get_original_item($guid, $orig_author, $author) {
      +
      +		// Do we already have this item?
      +		$r = q("SELECT `body`, `tag`, `app`, `created`, `object-type`, `uri`, `guid`,
      +				`author-name`, `author-link`, `author-avatar`
      +				FROM `item` WHERE `guid` = '%s' AND `visible` AND NOT `deleted` AND `body` != '' LIMIT 1",
      +			dbesc($guid));
      +
      +		if(count($r)) {
      +			logger("reshared message ".$guid." already exists on system.");
      +
      +			// Maybe it is already a reshared item?
      +			// Then refetch the content, since there can be many side effects with reshared posts from other networks or reshares from reshares
      +			if (api_share_as_retweet($r[0]))
      +				$r = array();
      +			else
      +				return $r[0];
      +		}
      +
      +		if (!count($r)) {
      +			$server = 'https://'.substr($orig_author,strpos($orig_author,'@')+1);
      +			logger("1st try: reshared message ".$guid." will be fetched from original server: ".$server);
      +			$item_id = self::store_by_guid($guid, $server);
      +
      +			if (!$item_id) {
      +				$server = 'https://'.substr($author,strpos($author,'@')+1);
      +				logger("2nd try: reshared message ".$guid." will be fetched from sharer's server: ".$server);
      +				$item = self::store_by_guid($guid, $server);
      +			}
      +			if (!$item_id) {
      +				$server = 'http://'.substr($orig_author,strpos($orig_author,'@')+1);
      +				logger("3rd try: reshared message ".$guid." will be fetched from original server: ".$server);
      +				$item = self::store_by_guid($guid, $server);
      +			}
      +			if (!$item_id) {
      +				$server = 'http://'.substr($author,strpos($author,'@')+1);
      +				logger("4th try: reshared message ".$guid." will be fetched from sharer's server: ".$server);
      +				$item = self::store_by_guid($guid, $server);
      +			}
      +
      +			if ($item_id) {
      +				$r = q("SELECT `body`, `tag`, `app`, `created`, `object-type`, `uri`, `guid`,
      +						`author-name`, `author-link`, `author-avatar`
      +					FROM `item` WHERE `id` = %d AND `visible` AND NOT `deleted` AND `body` != '' LIMIT 1",
      +					intval($item_id));
      +
      +				if ($r)
      +					return $r[0];
      +
      +			}
      +		}
      +		return false;
      +	}
      +
       	private function import_reshare($importer, $data) {
       		$root_author = notags(unxmlify($data->root_author));
       		$root_guid = notags(unxmlify($data->root_guid));
      @@ -1322,101 +1361,16 @@ print_r($data);
       		$public = notags(unxmlify($data->public));
       		$created_at = notags(unxmlify($data->created_at));
       
      -		$contact = self::get_contact_by_handle($importer["uid"], $author);
      +		$contact = self::get_allowed_contact_by_handle($importer, $author, false);
       		if (!$contact)
       			return false;
       
      -		if (!self::post_allow($importer, $contact, false)) {
      -			logger("Ignoring this author: ".$author." ".print_r($data,true));
      +//		if (self::message_exists($importer["uid"], $guid))
      +//			return false;
      +
      +		$original_item = self::get_original_item($root_guid, $root_author, $author);
      +		if (!$original_item)
       			return false;
      -		}
      -/*
      -		$r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1",
      -			intval($importer["uid"]),
      -			dbesc($guid)
      -		);
      -		if(count($r)) {
      -			logger("message exists: ".$guid);
      -			return;
      -		}
      -*/
      -		$orig_author = $root_author;
      -		$orig_guid = $root_guid;
      -		$orig_url = App::get_baseurl()."/display/".$guid;
      -
      -		$create_original_post = false;
      -
      -		// Do we already have this item?
      -		$r = q("SELECT `body`, `tag`, `app`, `created`, `plink`, `object`, `object-type`, `uri` FROM `item` WHERE `guid` = '%s' AND `visible` AND NOT `deleted` AND `body` != '' LIMIT 1",
      -			dbesc($orig_guid),
      -			dbesc(NETWORK_DIASPORA)
      -		);
      -		if(count($r)) {
      -			logger("reshared message ".$orig_guid." reshared by ".$guid." already exists on system.");
      -
      -			// Maybe it is already a reshared item?
      -			// Then refetch the content, since there can be many side effects with reshared posts from other networks or reshares from reshares
      -			require_once('include/api.php');
      -			if (api_share_as_retweet($r[0]))
      -				$r = array();
      -			else {
      -				$body = $r[0]["body"];
      -				$str_tags = $r[0]["tag"];
      -				$app = $r[0]["app"];
      -				$orig_created = $r[0]["created"];
      -				$orig_plink = $r[0]["plink"];
      -				$orig_uri = $r[0]["uri"];
      -				$object = $r[0]["object"];
      -				$objecttype = $r[0]["object-type"];
      -			}
      -		}
      -
      -/* @todo
      -		if (!count($r)) {
      -			$body = "";
      -			$str_tags = "";
      -			$app = "";
      -
      -			$server = 'https://'.substr($orig_author,strpos($orig_author,'@')+1);
      -			logger('1st try: reshared message '.$orig_guid." reshared by ".$guid.' will be fetched from original server: '.$server);
      -			$item = self::fetch_message($orig_guid, $server);
      -
      -			if (!$item) {
      -				$server = 'https://'.substr($author,strpos($author,'@')+1);
      -				logger('2nd try: reshared message '.$orig_guid." reshared by ".$guid." will be fetched from sharer's server: ".$server);
      -				$item = diaspora_fetch_message($orig_guid, $server);
      -			}
      -			if (!$item) {
      -				$server = 'http://'.substr($orig_author,strpos($orig_author,'@')+1);
      -				logger('3rd try: reshared message '.$orig_guid." reshared by ".$guid.' will be fetched from original server: '.$server);
      -				$item = diaspora_fetch_message($orig_guid, $server);
      -			}
      -			if (!$item) {
      -				$server = 'http://'.substr($author,strpos($author,'@')+1);
      -				logger('4th try: reshared message '.$orig_guid." reshared by ".$guid." will be fetched from sharer's server: ".$server);
      -				$item = diaspora_fetch_message($orig_guid, $server);
      -			}
      -
      -			if ($item) {
      -				$body = $item["body"];
      -				$str_tags = $item["tag"];
      -				$app = $item["app"];
      -				$orig_created = $item["created"];
      -				$orig_author = $item["author"];
      -				$orig_guid = $item["guid"];
      -				$orig_plink = diaspora_plink($orig_author, $orig_guid);
      -				$orig_uri = $orig_author.":".$orig_guid;
      -				$create_original_post = ($body != "");
      -				$object = $item["object"];
      -				$objecttype = $item["object-type"];
      -			}
      -		}
      -*/
      -		$plink = self::plink($author, $guid);
      -
      -		$person = self::get_person_by_handle($orig_author);
      -
      -		$private = (($public == "false") ? 1 : 0);
       
       		$datarray = array();
       
      @@ -1440,18 +1394,18 @@ print_r($data);
       
       		$datarray["object"] = json_encode($data);
       
      -		$prefix = share_header($person["name"], $person["url"], ((x($person,'thumb')) ? $person["thumb"] : $person["photo"]),
      -					$orig_guid, $orig_created, $orig_url);
      -		$datarray["body"] = $prefix.$body."[/share]";
      +		$prefix = share_header($original_item["author-name"], $original_item["author-link"], $original_item["author-avatar"],
      +					$original_item["guid"], $original_item["created"], $original_item["uri"]);
      +		$datarray["body"] = $prefix.$original_item["body"]."[/share]";
       
      -		$datarray["tag"] = $str_tags;
      -		$datarray["app"]  = $app;
      +		$datarray["tag"] = $original_item["tag"];
      +		$datarray["app"]  = $original_item["app"];
       
      -		$datarray["plink"] = $plink;
      -		$datarray["private"] = $private;
      +		$datarray["plink"] = self::plink($author, $guid);
      +		$datarray["private"] = (($public == "false") ? 1 : 0);
       		$datarray["changed"] = $datarray["created"] = $datarray["edited"] = datetime_convert("UTC", "UTC", $created_at);
       
      -		$datarray["object-type"] = $objecttype;
      +		$datarray["object-type"] = $original_item["object-type"];
       
       		self::fetch_guid($datarray);
       		//$message_id = item_store($datarray);
      @@ -1473,25 +1427,12 @@ print_r($data);
       		$created_at = notags(unxmlify($data->created_at));
       		$provider_display_name = notags(unxmlify($data->provider_display_name));
       
      -		$contact = self::get_contact_by_handle($importer["uid"], $author);
      -		if (!$contact) {
      -			logger("A Contact for handle ".$author." and user ".$importer["uid"]." was not found");
      +		$contact = self::get_allowed_contact_by_handle($importer, $author, false);
      +		if (!$contact)
       			return false;
      -		}
       
      -		if (!self::post_allow($importer, $contact, false)) {
      -			logger("Ignoring this author.");
      +		if (self::message_exists($importer["uid"], $guid))
       			return false;
      -		}
      -
      -		$r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1",
      -			intval($importer["uid"]),
      -			dbesc($guid)
      -		);
      -		if(count($r)) {
      -			logger("message exists: ".$guid);
      -			return false;
      -		}
       
       		/// @todo enable support for polls
       		// if ($data->poll) {
      @@ -1504,8 +1445,6 @@ print_r($data);
       			foreach ($data->location->children() AS $fieldname => $data)
       				$address[$fieldname] = notags(unxmlify($data));
       
      -		$private = (($public == "false") ? 1 : 0);
      -
       		$body = diaspora2bb($raw_message);
       
       		if ($data->photo)
      @@ -1533,7 +1472,6 @@ print_r($data);
       				$str_tags .= "@[url=".$mtch[1]."[/url]";
       			}
       		}
      -		$plink = self::plink($author, $guid);
       
       		$datarray["uid"] = $importer["uid"];
       		$datarray["contact-id"] = $contact["id"];
      @@ -1561,8 +1499,8 @@ print_r($data);
       		if ($provider_display_name != "")
       			$datarray["app"] = $provider_display_name;
       
      -		$datarray["plink"] = $plink;
      -		$datarray["private"] = $private;
      +		$datarray["plink"] = self::plink($author, $guid);
      +		$datarray["private"] = (($public == "false") ? 1 : 0);
       		$datarray["changed"] = $datarray["created"] = $datarray["edited"] = datetime_convert("UTC", "UTC", $created_at);
       
       		if (isset($address["address"]))
      
      From c88fd73b80db50c60304fe8924a4679f42c3961a Mon Sep 17 00:00:00 2001
      From: Michael Vogel 
      Date: Tue, 1 Mar 2016 19:10:35 +0100
      Subject: [PATCH 019/211] Just another commit message :-)
      
      ---
       include/diaspora.php  | 6 +++---
       include/diaspora2.php | 8 +++++---
       2 files changed, 8 insertions(+), 6 deletions(-)
      
      diff --git a/include/diaspora.php b/include/diaspora.php
      index f139418fe4..4cd36e9a02 100644
      --- a/include/diaspora.php
      +++ b/include/diaspora.php
      @@ -146,9 +146,9 @@ function diaspora_dispatch($importer,$msg,$attempt=1) {
       		$ret = diaspora_participation($importer,$xmlbase->participation);
       	}
       	elseif($xmlbase->poll_participation) {
      -		$tempfile = tempnam(get_temppath(), "diaspora-poll_participation");
      -		file_put_contents($tempfile, json_encode($data));
      -		$ret = diaspora_participation($importer,$xmlbase->participation);
      +		//$tempfile = tempnam(get_temppath(), "diaspora-poll_participation");
      +		//file_put_contents($tempfile, json_encode($data));
      +		$ret = diaspora_participation($importer,$xmlbase->poll_participation);
       	}
       	else {
       		$tempfile = tempnam(get_temppath(), "diaspora-unknown");
      diff --git a/include/diaspora2.php b/include/diaspora2.php
      index a1297c5193..e8ed80ee80 100644
      --- a/include/diaspora2.php
      +++ b/include/diaspora2.php
      @@ -131,7 +131,8 @@ class diaspora {
       				return self::import_request($importer, $fields);
       
       			case "reshare":
      -				return self::import_reshare($importer, $fields);
      +				return true;
      +				//return self::import_reshare($importer, $fields);
       
       			case "retraction":
       				return self::import_retraction($importer, $fields);
      @@ -676,6 +677,8 @@ class diaspora {
       	}
       
       	private function import_conversation($importer, $data) {
      +		print_r($data);
      +		die();
       /*
               $guid = notags(unxmlify($xml->guid));
               $subject = notags(unxmlify($xml->subject));
      @@ -874,9 +877,8 @@ EOT;
       		if ($parent_type !== "Post")
       			return false;
       
      -		// "positive" = "false" doesn't seem to be supported by Diaspora
      +		// "positive" = "false" would be a Dislike - wich isn't currently supported by Diaspora
       		if ($positive === "false") {
      -			logger("Received a like with positive set to 'false' - this shouldn't exist at all");
       			return false;
       		}
       
      
      From f70ee94fa0130aeac05416afb642d68e461b4395 Mon Sep 17 00:00:00 2001
      From: Michael Vogel 
      Date: Tue, 1 Mar 2016 23:21:56 +0100
      Subject: [PATCH 020/211] Likes would now work with unlikes and with likes on
       comments.
      
      ---
       include/diaspora2.php | 48 +++++++++++++++++++++++--------------------
       1 file changed, 26 insertions(+), 22 deletions(-)
      
      diff --git a/include/diaspora2.php b/include/diaspora2.php
      index e8ed80ee80..97d22b4b97 100644
      --- a/include/diaspora2.php
      +++ b/include/diaspora2.php
      @@ -104,7 +104,8 @@ class diaspora {
       				//return self::import_comment($importer, $sender, $fields);
       
       			case "conversation":
      -				return self::import_conversation($importer, $fields);
      +				return true;
      +				//return self::import_conversation($importer, $fields);
       
       			case "like":
       				return true;
      @@ -138,8 +139,8 @@ class diaspora {
       				return self::import_retraction($importer, $fields);
       
       			case "status_message":
      -				return true;
      -				//return self::import_status_message($importer, $fields, $msg, $data2);
      +				//return true;
      +				return self::import_status_message($importer, $fields);
       
       			default:
       				logger("Unknown message type ".$type);
      @@ -246,7 +247,7 @@ class diaspora {
       			}
       
       		// Only some message types have signatures. So we quit here for the other types.
      -		if (!in_array($type, array("comment", "conversation", "message", "like")))
      +		if (!in_array($type, array("comment", "message", "like")))
       			return true;
       
       		// No author_signature? This is a must, so we quit.
      @@ -691,7 +692,7 @@ class diaspora {
               $messages = $xml->message;
       
               if(! count($messages)) {
      -                logger('diaspora_conversation: empty conversation');
      +                logger('empty conversation');
                       return;
               }
       
      @@ -874,14 +875,10 @@ EOT;
       		$author = notags(unxmlify($data->author));
       
       		// likes on comments aren't supported by Diaspora - only on posts
      -		if ($parent_type !== "Post")
      +		// But maybe this will be supported in the future, so we will accept it.
      +		if (!in_array($parent_type, array("Post", "Comment")))
       			return false;
       
      -		// "positive" = "false" would be a Dislike - wich isn't currently supported by Diaspora
      -		if ($positive === "false") {
      -			return false;
      -		}
      -
       		$contact = self::get_allowed_contact_by_handle($importer, $sender, true);
       		if (!$contact)
       			return false;
      @@ -902,6 +899,13 @@ EOT;
       		// Fetch the contact id - if we know this contact
       		$author_contact = self::get_author_contact_by_url($contact, $person, $importer["uid"]);
       
      +		// "positive" = "false" would be a Dislike - wich isn't currently supported by Diaspora
      +		// We would accept this anyhow.
      +		if ($positive === "true")
      +			$verb = ACTIVITY_LIKE;
      +		else
      +			$verb = ACTIVITY_DISLIKE;
      +
       		$datarray = array();
       
       		$datarray["uid"] = $importer["uid"];
      @@ -920,7 +924,7 @@ EOT;
       		$datarray["uri"] = $author.":".$guid;
       
       		$datarray["type"] = "activity";
      -		$datarray["verb"] = ACTIVITY_LIKE;
      +		$datarray["verb"] = $verb;
       		$datarray["gravity"] = GRAVITY_LIKE;
       		$datarray["parent-uri"] = $parent_item["uri"];
       
      @@ -1429,18 +1433,18 @@ print_r($data);
       		$created_at = notags(unxmlify($data->created_at));
       		$provider_display_name = notags(unxmlify($data->provider_display_name));
       
      +		/// @todo enable support for polls
      +		if ($data->poll) {
      +			foreach ($data->poll AS $poll)
      +				print_r($poll);
      +			die("poll!\n");
      +		}
       		$contact = self::get_allowed_contact_by_handle($importer, $author, false);
       		if (!$contact)
       			return false;
       
      -		if (self::message_exists($importer["uid"], $guid))
      -			return false;
      -
      -		/// @todo enable support for polls
      -		// if ($data->poll) {
      -		//	print_r($data->poll);
      -		//	die("poll!\n");
      -		// }
      +		//if (self::message_exists($importer["uid"], $guid))
      +		//	return false;
       
       		$address = array();
       		if ($data->location)
      @@ -1450,8 +1454,8 @@ print_r($data);
       		$body = diaspora2bb($raw_message);
       
       		if ($data->photo)
      -			for ($i = 0; $i < count($data->photo); $i++)
      -				$body = "[img]".$data->photo[$i]->remote_photo_path.$data->photo[$i]->remote_photo_name."[/img]\n".$body;
      +			foreach ($data->photo AS $photo)
      +				$body = "[img]".$photo->remote_photo_path.$photo->remote_photo_name."[/img]\n".$body;
       
       		$datarray = array();
       
      
      From 599d242e00da845080e0ab8b65e677876f663dc7 Mon Sep 17 00:00:00 2001
      From: Michael Vogel 
      Date: Wed, 2 Mar 2016 23:28:20 +0100
      Subject: [PATCH 021/211] Some work at the retractions.
      
      ---
       include/diaspora.php  |   8 +-
       include/diaspora2.php | 381 +++++++++++++++++++++++++-----------------
       2 files changed, 230 insertions(+), 159 deletions(-)
      
      diff --git a/include/diaspora.php b/include/diaspora.php
      index 4cd36e9a02..78ba520790 100644
      --- a/include/diaspora.php
      +++ b/include/diaspora.php
      @@ -116,13 +116,13 @@ function diaspora_dispatch($importer,$msg,$attempt=1) {
       		$ret = diaspora_retraction($importer,$xmlbase->retraction,$msg);
       	}
       	elseif($xmlbase->signed_retraction) {
      -		//$tempfile = tempnam(get_temppath(), "diaspora-signed_retraction");
      -		//file_put_contents($tempfile, json_encode($data));
      +		$tempfile = tempnam(get_temppath(), "diaspora-signed_retraction");
      +		file_put_contents($tempfile, json_encode($data));
       		$ret = diaspora_signed_retraction($importer,$xmlbase->signed_retraction,$msg);
       	}
       	elseif($xmlbase->relayable_retraction) {
      -		//$tempfile = tempnam(get_temppath(), "diaspora-relayable_retraction");
      -		//file_put_contents($tempfile, json_encode($data));
      +		$tempfile = tempnam(get_temppath(), "diaspora-relayable_retraction");
      +		file_put_contents($tempfile, json_encode($data));
       		$ret = diaspora_signed_retraction($importer,$xmlbase->relayable_retraction,$msg);
       	}
       	elseif($xmlbase->photo) {
      diff --git a/include/diaspora2.php b/include/diaspora2.php
      index 97d22b4b97..baf117b102 100644
      --- a/include/diaspora2.php
      +++ b/include/diaspora2.php
      @@ -95,11 +95,11 @@ class diaspora {
       		$type = $fields->getName();
       
       		switch ($type) {
      -			case "account_deletion":
      +			case "account_deletion": // Done
       				return true;
       				//return self::import_account_deletion($importer, $fields);
       
      -			case "comment":
      +			case "comment": // Done
       				return true;
       				//return self::import_comment($importer, $sender, $fields);
       
      @@ -107,40 +107,40 @@ class diaspora {
       				return true;
       				//return self::import_conversation($importer, $fields);
       
      -			case "like":
      +			case "like": // Done
       				return true;
       				//return self::import_like($importer, $sender, $fields);
       
      -			case "message":
      +			case "message": // Done
       				return true;
       				//return self::import_message($importer, $fields);
       
       			case "participation": // Not implemented
       				return self::import_participation($importer, $fields);
       
      -			case "photo":
      +			case "photo": // Not needed
       				return self::import_photo($importer, $fields);
       
       			case "poll_participation": // Not implemented
       				return self::import_poll_participation($importer, $fields);
       
      -			case "profile":
      +			case "profile": // Done
       				return true;
       				//return self::import_profile($importer, $fields);
       
       			case "request":
       				return self::import_request($importer, $fields);
       
      -			case "reshare":
      +			case "reshare": // Done
       				return true;
       				//return self::import_reshare($importer, $fields);
       
       			case "retraction":
       				return self::import_retraction($importer, $fields);
       
      -			case "status_message":
      -				//return true;
      -				return self::import_status_message($importer, $fields);
      +			case "status_message": // Done
      +				return true;
      +				//return self::import_status_message($importer, $fields);
       
       			default:
       				logger("Unknown message type ".$type);
      @@ -567,25 +567,25 @@ class diaspora {
       	}
       
       	private function plink($addr, $guid) {
      -	        $r = q("SELECT `url`, `nick`, `network` FROM `fcontact` WHERE `addr`='%s' LIMIT 1", dbesc($addr));
      +		$r = q("SELECT `url`, `nick`, `network` FROM `fcontact` WHERE `addr`='%s' LIMIT 1", dbesc($addr));
       
      -	        // Fallback
      -	        if (!$r)
      -	                return "https://".substr($addr,strpos($addr,"@")+1)."/posts/".$guid;
      +		// Fallback
      +		if (!$r)
      +			return "https://".substr($addr,strpos($addr,"@")+1)."/posts/".$guid;
       
      -	        // Friendica contacts are often detected as Diaspora contacts in the "fcontact" table
      -	        // So we try another way as well.
      -	        $s = q("SELECT `network` FROM `gcontact` WHERE `nurl`='%s' LIMIT 1", dbesc(normalise_link($r[0]["url"])));
      -	        if ($s)
      -	                $r[0]["network"] = $s[0]["network"];
      +		// Friendica contacts are often detected as Diaspora contacts in the "fcontact" table
      +		// So we try another way as well.
      +		$s = q("SELECT `network` FROM `gcontact` WHERE `nurl`='%s' LIMIT 1", dbesc(normalise_link($r[0]["url"])));
      +		if ($s)
      +			$r[0]["network"] = $s[0]["network"];
       
      -	        if ($r[0]["network"] == NETWORK_DFRN)
      -	                return(str_replace("/profile/".$r[0]["nick"]."/", "/display/".$guid, $r[0]["url"]."/"));
      +		if ($r[0]["network"] == NETWORK_DFRN)
      +			return(str_replace("/profile/".$r[0]["nick"]."/", "/display/".$guid, $r[0]["url"]."/"));
       
      -	        if (self::is_redmatrix($r[0]["url"]))
      -	                return $r[0]["url"]."/?f=&mid=".$guid;
      +		if (self::is_redmatrix($r[0]["url"]))
      +			return $r[0]["url"]."/?f=&mid=".$guid;
       
      -	        return "https://".substr($addr,strpos($addr,"@")+1)."/posts/".$guid;
      +		return "https://".substr($addr,strpos($addr,"@")+1)."/posts/".$guid;
       	}
       
       	private function import_account_deletion($importer, $data) {
      @@ -681,158 +681,158 @@ class diaspora {
       		print_r($data);
       		die();
       /*
      -        $guid = notags(unxmlify($xml->guid));
      -        $subject = notags(unxmlify($xml->subject));
      -        $diaspora_handle = notags(unxmlify($xml->diaspora_handle));
      -        $participant_handles = notags(unxmlify($xml->participant_handles));
      -        $created_at = datetime_convert('UTC','UTC',notags(unxmlify($xml->created_at)));
      +	$guid = notags(unxmlify($xml->guid));
      +	$subject = notags(unxmlify($xml->subject));
      +	$diaspora_handle = notags(unxmlify($xml->diaspora_handle));
      +	$participant_handles = notags(unxmlify($xml->participant_handles));
      +	$created_at = datetime_convert('UTC','UTC',notags(unxmlify($xml->created_at)));
       
      -        $parent_uri = $diaspora_handle . ':' . $guid;
      +	$parent_uri = $diaspora_handle . ':' . $guid;
       
      -        $messages = $xml->message;
      +	$messages = $xml->message;
       
      -        if(! count($messages)) {
      -                logger('empty conversation');
      -                return;
      -        }
      +	if(! count($messages)) {
      +		logger('empty conversation');
      +		return;
      +	}
       
       		$contact = self::get_allowed_contact_by_handle($importer, $sender, true)
       		if (!$contact)
       			return false;
       
      -        $conversation = null;
      +	$conversation = null;
       
      -        $c = q("select * from conv where uid = %d and guid = '%s' limit 1",
      -                intval($importer['uid']),
      -                dbesc($guid)
      -        );
      -        if(count($c))
      -                $conversation = $c[0];
      -        else {
      -                $r = q("insert into conv (uid,guid,creator,created,updated,subject,recips) values(%d, '%s', '%s', '%s', '%s', '%s', '%s') ",
      -                        intval($importer['uid']),
      -                        dbesc($guid),
      -                        dbesc($diaspora_handle),
      -                        dbesc(datetime_convert('UTC','UTC',$created_at)),
      -                        dbesc(datetime_convert()),
      -                        dbesc($subject),
      -                        dbesc($participant_handles)
      -                );
      -                if($r)
      -                        $c = q("select * from conv where uid = %d and guid = '%s' limit 1",
      -                intval($importer['uid']),
      -            dbesc($guid)
      -        );
      -            if(count($c))
      -            $conversation = $c[0];
      -        }
      -        if(! $conversation) {
      -                logger('diaspora_conversation: unable to create conversation.');
      -                return;
      -        }
      +	$c = q("select * from conv where uid = %d and guid = '%s' limit 1",
      +		intval($importer["uid"]),
      +		dbesc($guid)
      +	);
      +	if(count($c))
      +		$conversation = $c[0];
      +	else {
      +		$r = q("insert into conv (uid,guid,creator,created,updated,subject,recips) values(%d, '%s', '%s', '%s', '%s', '%s', '%s') ",
      +			intval($importer["uid"]),
      +			dbesc($guid),
      +			dbesc($diaspora_handle),
      +			dbesc(datetime_convert('UTC','UTC',$created_at)),
      +			dbesc(datetime_convert()),
      +			dbesc($subject),
      +			dbesc($participant_handles)
      +		);
      +		if($r)
      +			$c = q("select * from conv where uid = %d and guid = '%s' limit 1",
      +		intval($importer["uid"]),
      +	    dbesc($guid)
      +	);
      +	    if(count($c))
      +	    $conversation = $c[0];
      +	}
      +	if(! $conversation) {
      +		logger('diaspora_conversation: unable to create conversation.');
      +		return;
      +	}
       
      -        foreach($messages as $mesg) {
      +	foreach($messages as $mesg) {
       
      -                $reply = 0;
      +		$reply = 0;
       
      -                $msg_guid = notags(unxmlify($mesg->guid));
      -                $msg_parent_guid = notags(unxmlify($mesg->parent_guid));
      -                $msg_parent_author_signature = notags(unxmlify($mesg->parent_author_signature));
      -                $msg_author_signature = notags(unxmlify($mesg->author_signature));
      -                $msg_text = unxmlify($mesg->text);
      -                $msg_created_at = datetime_convert('UTC','UTC',notags(unxmlify($mesg->created_at)));
      -                $msg_diaspora_handle = notags(unxmlify($mesg->diaspora_handle));
      -                $msg_conversation_guid = notags(unxmlify($mesg->conversation_guid));
      -                if($msg_conversation_guid != $guid) {
      -                        logger('diaspora_conversation: message conversation guid does not belong to the current conversation. ' . $xml);
      -                        continue;
      -                }
      +		$msg_guid = notags(unxmlify($mesg->guid));
      +		$msg_parent_guid = notags(unxmlify($mesg->parent_guid));
      +		$msg_parent_author_signature = notags(unxmlify($mesg->parent_author_signature));
      +		$msg_author_signature = notags(unxmlify($mesg->author_signature));
      +		$msg_text = unxmlify($mesg->text);
      +		$msg_created_at = datetime_convert('UTC','UTC',notags(unxmlify($mesg->created_at)));
      +		$msg_diaspora_handle = notags(unxmlify($mesg->diaspora_handle));
      +		$msg_conversation_guid = notags(unxmlify($mesg->conversation_guid));
      +		if($msg_conversation_guid != $guid) {
      +			logger('diaspora_conversation: message conversation guid does not belong to the current conversation. ' . $xml);
      +			continue;
      +		}
       
      -                $body = diaspora2bb($msg_text);
      -                $message_id = $msg_diaspora_handle . ':' . $msg_guid;
      +		$body = diaspora2bb($msg_text);
      +		$message_id = $msg_diaspora_handle . ':' . $msg_guid;
       
      -                $author_signed_data = $msg_guid . ';' . $msg_parent_guid . ';' . $msg_text . ';' . unxmlify($mesg->created_at) . ';' . $msg_diaspora_handle . ';' . $msg_conversation_guid;
      +		$author_signed_data = $msg_guid . ';' . $msg_parent_guid . ';' . $msg_text . ';' . unxmlify($mesg->created_at) . ';' . $msg_diaspora_handle . ';' . $msg_conversation_guid;
       
      -                $author_signature = base64_decode($msg_author_signature);
      +		$author_signature = base64_decode($msg_author_signature);
       
      -                if(strcasecmp($msg_diaspora_handle,$msg['author']) == 0) {
      -                        $person = $contact;
      -                        $key = $msg['key'];
      -                }
      -                else {
      -                        $person = find_diaspora_person_by_handle($msg_diaspora_handle); 
      +		if(strcasecmp($msg_diaspora_handle,$msg["author"]) == 0) {
      +			$person = $contact;
      +			$key = $msg["key"];
      +		}
      +		else {
      +			$person = find_diaspora_person_by_handle($msg_diaspora_handle); 
       
      -                        if(is_array($person) && x($person,'pubkey'))
      -                                $key = $person['pubkey'];
      -                        else {
      -                                logger('diaspora_conversation: unable to find author details');
      -                                continue;
      -                        }
      -                }
      +			if(is_array($person) && x($person,'pubkey'))
      +				$key = $person["pubkey"];
      +			else {
      +				logger('diaspora_conversation: unable to find author details');
      +				continue;
      +			}
      +		}
       
      -                if(! rsa_verify($author_signed_data,$author_signature,$key,'sha256')) {
      -                        logger('diaspora_conversation: verification failed.');
      -                        continue;
      -                }
      +		if(! rsa_verify($author_signed_data,$author_signature,$key,'sha256')) {
      +			logger('diaspora_conversation: verification failed.');
      +			continue;
      +		}
       
      -                if($msg_parent_author_signature) {
      -                        $owner_signed_data = $msg_guid . ';' . $msg_parent_guid . ';' . $msg_text . ';' . unxmlify($mesg->created_at) . ';' . $msg_diaspora_handle . ';' . $msg_conversation_guid;
      +		if($msg_parent_author_signature) {
      +			$owner_signed_data = $msg_guid . ';' . $msg_parent_guid . ';' . $msg_text . ';' . unxmlify($mesg->created_at) . ';' . $msg_diaspora_handle . ';' . $msg_conversation_guid;
       
      -                        $parent_author_signature = base64_decode($msg_parent_author_signature);
      +			$parent_author_signature = base64_decode($msg_parent_author_signature);
       
      -                        $key = $msg['key'];
      +			$key = $msg["key"];
       
      -                        if(! rsa_verify($owner_signed_data,$parent_author_signature,$key,'sha256')) {
      -                                logger('diaspora_conversation: owner verification failed.');
      -                                continue;
      -                        }
      -                }
      +			if(! rsa_verify($owner_signed_data,$parent_author_signature,$key,'sha256')) {
      +				logger('diaspora_conversation: owner verification failed.');
      +				continue;
      +			}
      +		}
       
      -                $r = q("select id from mail where `uri` = '%s' limit 1",
      -                        dbesc($message_id)
      -                );
      -                if(count($r)) {
      -                        logger('diaspora_conversation: duplicate message already delivered.', LOGGER_DEBUG);
      -                        continue;
      -                }
      +		$r = q("select id from mail where `uri` = '%s' limit 1",
      +			dbesc($message_id)
      +		);
      +		if(count($r)) {
      +			logger('diaspora_conversation: duplicate message already delivered.', LOGGER_DEBUG);
      +			continue;
      +		}
       
      -                q("insert into mail ( `uid`, `guid`, `convid`, `from-name`,`from-photo`,`from-url`,`contact-id`,`title`,`body`,`seen`,`reply`,`uri`,`parent-uri`,`created`) values ( %d, '%s', %d, '%s', '%s', '%s', %d, '%s', '%s', %d, %d, '%s','%s','%s')",
      -                        intval($importer['uid']),
      -                        dbesc($msg_guid),
      -                        intval($conversation['id']),
      -                        dbesc($person['name']),
      -                        dbesc($person['photo']),
      -                        dbesc($person['url']),
      -                        intval($contact['id']),
      -                        dbesc($subject),
      -                        dbesc($body),
      -                        0,
      -                        0,
      -                        dbesc($message_id),
      -                        dbesc($parent_uri),
      -                        dbesc($msg_created_at)
      -                );
      +		q("insert into mail ( `uid`, `guid`, `convid`, `from-name`,`from-photo`,`from-url`,`contact-id`,`title`,`body`,`seen`,`reply`,`uri`,`parent-uri`,`created`) values ( %d, '%s', %d, '%s', '%s', '%s', %d, '%s', '%s', %d, %d, '%s','%s','%s')",
      +			intval($importer["uid"]),
      +			dbesc($msg_guid),
      +			intval($conversation["id"]),
      +			dbesc($person["name"]),
      +			dbesc($person["photo"]),
      +			dbesc($person["url"]),
      +			intval($contact["id"]),
      +			dbesc($subject),
      +			dbesc($body),
      +			0,
      +			0,
      +			dbesc($message_id),
      +			dbesc($parent_uri),
      +			dbesc($msg_created_at)
      +		);
       
      -                q("update conv set updated = '%s' where id = %d",
      -                        dbesc(datetime_convert()),
      -                        intval($conversation['id'])
      -                );
      +		q("update conv set updated = '%s' where id = %d",
      +			dbesc(datetime_convert()),
      +			intval($conversation["id"])
      +		);
       
      -                notification(array(
      -                        'type' => NOTIFY_MAIL,
      -                        'notify_flags' => $importer['notify-flags'],
      -                        'language' => $importer['language'],
      -                        'to_name' => $importer['username'],
      -                        'to_email' => $importer['email'],
      -                        'uid' =>$importer['uid'],
      -                        'item' => array('subject' => $subject, 'body' => $body),
      -                        'source_name' => $person['name'],
      -                        'source_link' => $person['url'],
      -                        'source_photo' => $person['thumb'],
      -                        'verb' => ACTIVITY_POST,
      -                        'otype' => 'mail'
      -                ));
      -        }
      +		notification(array(
      +			'type' => NOTIFY_MAIL,
      +			'notify_flags' => $importer["notify-flags"],
      +			'language' => $importer["language"],
      +			'to_name' => $importer["username"],
      +			'to_email' => $importer["email"],
      +			'uid' =>$importer["uid"],
      +			'item' => array('subject' => $subject, 'body' => $body),
      +			'source_name' => $person["name"],
      +			'source_link' => $person["url"],
      +			'source_photo' => $person["thumb"],
      +			'verb' => ACTIVITY_POST,
      +			'otype' => 'mail'
      +		));
      +	}
       */
       		return true;
       	}
      @@ -1420,7 +1420,78 @@ print_r($data);
       		return $message_id;
       	}
       
      +	private function item_retraction($importer, $contact, $data) {
      +		$target_guid = notags(unxmlify($data->target_guid));
      +
      +		$r = q("SELECT `id`, `parent`, `parent-uri`, `author-link` FROM `item` WHERE `guid` = '%s' AND `uid` = %d AND NOT `file` LIKE '%%[%%' LIMIT 1",
      +			dbesc($target_guid),
      +			intval($importer["uid"])
      +		);
      +		if (!$r)
      +			return false;
      +
      +		// Only delete it if the author really fits
      +		if (!link_compare($r[0]["author-link"],$contact["url"]))
      +			return false;
      +
      +		// Currently we don't have a central deletion function that we could use in this case. The function "item_drop" doesn't work for that case
      +		q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = '' WHERE `id` = %d",
      +			dbesc(datetime_convert()),
      +			dbesc(datetime_convert()),
      +			intval($r[0]["id"])
      +		);
      +		delete_thread($r[0]["id"], $r[0]["parent-uri"]);
      +
      +		// Now check if the retraction needs to be relayed by us
      +		//
      +		// The first item in the `item` table with the parent id is the parent. However, MySQL doesn't always
      +		// return the items ordered by `item`.`id`, in which case the wrong item is chosen as the parent.
      +		// The only item with `parent` and `id` as the parent id is the parent item.
      +		$p = q("SELECT `origin` FROM `item` WHERE `parent` = %d AND `id` = %d LIMIT 1",
      +			intval($r[0]["parent"]),
      +			intval($r[0]["parent"])
      +		);
      +		if(count($p)) {
      +			if($p[0]["origin"]) {
      +
      +	                        // Formerly we stored the signed text, the signature and the author in different fields.
      +	                        // The new Diaspora protocol can have variable fields. We now store the data in correct order in a single field.
      +	                        q("INSERT INTO `sign` (`iid`,`signed_text`) VALUES (%d,'%s')",
      +					intval($r[0]["id"]),
      +	                                dbesc(json_encode($data))
      +	                        );
      +
      +				// the existence of parent_author_signature would have meant the parent_author or owner
      +				// is already relaying.
      +				logger("relaying retraction");
      +
      +				proc_run("php", "include/notifier.php", "drop", $r[0]["id"]);
      +			}
      +		}
      +	}
      +
       	private function import_retraction($importer, $data) {
      +		$target_type = notags(unxmlify($data->target_type));
      +		$author = notags(unxmlify($data->author));
      +
      +		$contact = self::get_contact_by_handle($importer["uid"], $author);
      +		if (!$contact) {
      +			logger("cannot find contact for author: ".$author);
      +			return false;
      +		}
      +
      +		switch ($target_type) {
      +			case "Comment": case "Like": case "StatusMessage":
      +				self::item_retraction($importer, $contact, $data);
      +				break;
      +
      +			case "Person":
      +				contact_remove($contact["id"]);
      +				return true;
      +
      +			default:
      +				logger("Unknown target type ".$target_type);
      +		}
       		return true;
       	}
       
      @@ -1434,11 +1505,11 @@ print_r($data);
       		$provider_display_name = notags(unxmlify($data->provider_display_name));
       
       		/// @todo enable support for polls
      -		if ($data->poll) {
      -			foreach ($data->poll AS $poll)
      -				print_r($poll);
      -			die("poll!\n");
      -		}
      +		//if ($data->poll) {
      +		//	foreach ($data->poll AS $poll)
      +		//		print_r($poll);
      +		//	die("poll!\n");
      +		//}
       		$contact = self::get_allowed_contact_by_handle($importer, $author, false);
       		if (!$contact)
       			return false;
      
      From bad5a6e84abbca86257170094351f0d2ebec40ea Mon Sep 17 00:00:00 2001
      From: Michael Vogel 
      Date: Wed, 2 Mar 2016 23:39:50 +0100
      Subject: [PATCH 022/211] Some beautification
      
      ---
       include/diaspora2.php | 11 ++++++-----
       1 file changed, 6 insertions(+), 5 deletions(-)
      
      diff --git a/include/diaspora2.php b/include/diaspora2.php
      index baf117b102..4d0e4c2cb8 100644
      --- a/include/diaspora2.php
      +++ b/include/diaspora2.php
      @@ -1524,16 +1524,16 @@ print_r($data);
       
       		$body = diaspora2bb($raw_message);
       
      -		if ($data->photo)
      +		$datarray = array();
      +
      +		if ($data->photo) {
       			foreach ($data->photo AS $photo)
       				$body = "[img]".$photo->remote_photo_path.$photo->remote_photo_name."[/img]\n".$body;
       
      -		$datarray = array();
      -
      -		if($data->photo->remote_photo_path AND $data->photo->remote_photo_name)
       			$datarray["object-type"] = ACTIVITY_OBJ_PHOTO;
      -		else {
      +		} else {
       			$datarray["object-type"] = ACTIVITY_OBJ_NOTE;
      +
       			// Add OEmbed and other information to the body
       			if (!self::is_redmatrix($contact["url"]))
       				$body = add_page_info_to_body($body, false, true);
      @@ -1541,6 +1541,7 @@ print_r($data);
       
       		$str_tags = "";
       
      +		// This doesn't work. @todo Check if the "tag" field is filled in the "item_store" function.
       		$cnt = preg_match_all("/@\[url=(.*?)\[\/url\]/ism", $body, $matches, PREG_SET_ORDER);
       		if($cnt) {
       			foreach($matches as $mtch) {
      
      From b1dc4cc5282ce9157617f975ec051be9829e89cb Mon Sep 17 00:00:00 2001
      From: Michael Vogel 
      Date: Thu, 3 Mar 2016 23:34:17 +0100
      Subject: [PATCH 023/211] Retractions could work now.
      
      ---
       include/diaspora2.php | 170 ++++++++++++++++++++++++++----------------
       1 file changed, 105 insertions(+), 65 deletions(-)
      
      diff --git a/include/diaspora2.php b/include/diaspora2.php
      index 4d0e4c2cb8..59a5c372db 100644
      --- a/include/diaspora2.php
      +++ b/include/diaspora2.php
      @@ -13,6 +13,10 @@ require_once("include/socgraph.php");
       require_once("include/group.php");
       require_once("include/api.php");
       
      +/**
      + * @brief This class contain functions to work with XML data
      + *
      + */
       class xml {
       	function from_array($array, &$xml) {
       
      @@ -45,12 +49,20 @@ class xml {
       		}
       	}
       }
      +
       /**
        * @brief This class contain functions to create and send Diaspora XML files
        *
        */
       class diaspora {
       
      +	/**
      +	 * @brief Dispatches public messages and find the fitting receivers
      +	 *
      +	 * @param array $msg The post that will be dispatched
      +	 *
      +	 * @return bool Was the message accepted?
      +	 */
       	public static function dispatch_public($msg) {
       
       		$enabled = intval(get_config("system", "diaspora_enabled"));
      @@ -81,6 +93,14 @@ class diaspora {
       		return $item_id;
       	}
       
      +	/**
      +	 * @brief Dispatches the different message types to the different functions
      +	 *
      +	 * @param array $importer Array of the importer user
      +	 * @param array $msg The post that will be dispatched
      +	 *
      +	 * @return bool Was the message accepted?
      +	 */
       	public static function dispatch($importer, $msg) {
       
       		// The sender is the handle of the contact that sent the message.
      @@ -104,8 +124,8 @@ class diaspora {
       				//return self::import_comment($importer, $sender, $fields);
       
       			case "conversation":
      -				return true;
      -				//return self::import_conversation($importer, $fields);
      +				//return true;
      +				return self::import_conversation($importer, $fields);
       
       			case "like": // Done
       				return true;
      @@ -129,18 +149,20 @@ class diaspora {
       				//return self::import_profile($importer, $fields);
       
       			case "request":
      +				//return true;
       				return self::import_request($importer, $fields);
       
       			case "reshare": // Done
       				return true;
       				//return self::import_reshare($importer, $fields);
       
      -			case "retraction":
      -				return self::import_retraction($importer, $fields);
      -
      -			case "status_message": // Done
      +			case "retraction": // Done
       				return true;
      -				//return self::import_status_message($importer, $fields);
      +				//return self::import_retraction($importer, $sender, $fields);
      +
      +			case "status_message":
      +				//return true;
      +				return self::import_status_message($importer, $fields);
       
       			default:
       				logger("Unknown message type ".$type);
      @@ -181,6 +203,7 @@ class diaspora {
       		}
       
       		$type = $element->getName();
      +		$orig_type = $type;
       
       		// All retractions are handled identically from now on.
       		// In the new version there will only be "retraction".
      @@ -235,7 +258,8 @@ class diaspora {
       
       				$signed_data .= $entry;
       			}
      -			if (!in_array($fieldname, array("parent_author_signature", "target_author_signature")))
      +			if (!in_array($fieldname, array("parent_author_signature", "target_author_signature")) OR
      +				($orig_type == "relayable_retraction"))
       				xml::copy($entry, $fields, $fieldname);
       		}
       
      @@ -266,6 +290,13 @@ class diaspora {
       		return rsa_verify($signed_data, $author_signature, $key, "sha256");
       	}
       
      +	/**
      +	 * @brief Fetches the public key for a given handle
      +	 *
      +	 * @param string $handle The handle
      +	 *
      +	 * @return string The public key
      +	 */
       	private function get_key($handle) {
       		logger("Fetching diaspora key for: ".$handle);
       
      @@ -276,6 +307,13 @@ class diaspora {
       		return "";
       	}
       
      +	/**
      +	 * @brief Fetches data for a given handle
      +	 *
      +	 * @param string $handle The handle
      +	 *
      +	 * @return array the queried data
      +	 */
       	private function get_person_by_handle($handle) {
       
       		$r = q("SELECT * FROM `fcontact` WHERE `network` = '%s' AND `addr` = '%s' LIMIT 1",
      @@ -306,6 +344,14 @@ class diaspora {
       		return $person;
       	}
       
      +	/**
      +	 * @brief Updates the fcontact table
      +	 *
      +	 * @param array $arr The fcontact data
      +	 * @param bool $update Update or insert?
      +	 *
      +	 * @return string The id of the fcontact entry
      +	 */
       	private function add_fcontact($arr, $update = false) {
       		/// @todo Remove this function from include/network.php
       
      @@ -477,13 +523,12 @@ class diaspora {
       		if ($level > 5)
       			return false;
       
      -		// This will not work if the server is not a Diaspora server
      +		// This will work for Diaspora and newer Friendica servers
       		$source_url = $server."/p/".$guid.".xml";
       		$x = fetch_url($source_url);
       		if(!$x)
       			return false;
       
      -		/// @todo - should maybe solved by the dispatcher
       		$source_xml = parse_xml_string($x, false);
       
       		if (!is_object($source_xml))
      @@ -664,7 +709,7 @@ class diaspora {
       		if($message_id AND $parent_item["origin"]) {
       
       			// Formerly we stored the signed text, the signature and the author in different fields.
      -			// The new Diaspora protocol can have variable fields. We now store the data in correct order in a single field.
      +			// We now store the raw data so that we are more flexible.
       			q("INSERT INTO `sign` (`iid`,`signed_text`) VALUES (%d,'%s')",
       				intval($message_id),
       				dbesc(json_encode($data))
      @@ -678,6 +723,7 @@ class diaspora {
       	}
       
       	private function import_conversation($importer, $data) {
      +		// @todo
       		print_r($data);
       		die();
       /*
      @@ -934,13 +980,13 @@ EOT;
       		$datarray["body"] = self::construct_like_body($contact, $parent_item, $guid);
       
       		$message_id = item_store($datarray);
      -		//print_r($datarray);
      +		// print_r($datarray);
       
       		// If we are the origin of the parent we store the original data and notify our followers
       		if($message_id AND $parent_item["origin"]) {
       
       			// Formerly we stored the signed text, the signature and the author in different fields.
      -			// The new Diaspora protocol can have variable fields. We now store the data in correct order in a single field.
      +			// We now store the raw data so that we are more flexible.
       			q("INSERT INTO `sign` (`iid`,`signed_text`) VALUES (%d,'%s')",
       				intval($message_id),
       				dbesc(json_encode($data))
      @@ -1125,7 +1171,8 @@ EOT;
       	}
       
       	private function import_request($importer, $data) {
      -print_r($data);
      +		// @todo
      +		print_r($data);
       /*
       	$author = unxmlify($xml->author);
       	$recipient = unxmlify($xml->recipient);
      @@ -1371,8 +1418,8 @@ print_r($data);
       		if (!$contact)
       			return false;
       
      -//		if (self::message_exists($importer["uid"], $guid))
      -//			return false;
      +		if (self::message_exists($importer["uid"], $guid))
      +			return false;
       
       		$original_item = self::get_original_item($root_guid, $root_author, $author);
       		if (!$original_item)
      @@ -1414,14 +1461,22 @@ print_r($data);
       		$datarray["object-type"] = $original_item["object-type"];
       
       		self::fetch_guid($datarray);
      -		//$message_id = item_store($datarray);
      -		print_r($datarray);
      +		$message_id = item_store($datarray);
      +		// print_r($datarray);
       
       		return $message_id;
       	}
       
       	private function item_retraction($importer, $contact, $data) {
      +		$target_type = notags(unxmlify($data->target_type));
       		$target_guid = notags(unxmlify($data->target_guid));
      +		$author = notags(unxmlify($data->author));
      +
      +		$person = self::get_person_by_handle($author);
      +		if (!is_array($person)) {
      +			logger("unable to find author detail for ".$author);
      +			return false;
      +		}
       
       		$r = q("SELECT `id`, `parent`, `parent-uri`, `author-link` FROM `item` WHERE `guid` = '%s' AND `uid` = %d AND NOT `file` LIKE '%%[%%' LIMIT 1",
       			dbesc($target_guid),
      @@ -1431,7 +1486,15 @@ print_r($data);
       			return false;
       
       		// Only delete it if the author really fits
      -		if (!link_compare($r[0]["author-link"],$contact["url"]))
      +		if (!link_compare($r[0]["author-link"],$person["url"]))
      +			return false;
      +
      +		// Check if the sender is the thread owner
      +		$p = q("SELECT `author-link`, `origin` FROM `item` WHERE `id` = %d",
      +			intval($r[0]["parent"]));
      +
      +		// Only delete it if the parent author really fits
      +		if (!link_compare($p[0]["author-link"], $contact["url"]))
       			return false;
       
       		// Currently we don't have a central deletion function that we could use in this case. The function "item_drop" doesn't work for that case
      @@ -1443,47 +1506,36 @@ print_r($data);
       		delete_thread($r[0]["id"], $r[0]["parent-uri"]);
       
       		// Now check if the retraction needs to be relayed by us
      -		//
      -		// The first item in the `item` table with the parent id is the parent. However, MySQL doesn't always
      -		// return the items ordered by `item`.`id`, in which case the wrong item is chosen as the parent.
      -		// The only item with `parent` and `id` as the parent id is the parent item.
      -		$p = q("SELECT `origin` FROM `item` WHERE `parent` = %d AND `id` = %d LIMIT 1",
      -			intval($r[0]["parent"]),
      -			intval($r[0]["parent"])
      -		);
      -		if(count($p)) {
      -			if($p[0]["origin"]) {
      +		if($p[0]["origin"]) {
       
      -	                        // Formerly we stored the signed text, the signature and the author in different fields.
      -	                        // The new Diaspora protocol can have variable fields. We now store the data in correct order in a single field.
      -	                        q("INSERT INTO `sign` (`iid`,`signed_text`) VALUES (%d,'%s')",
      -					intval($r[0]["id"]),
      -	                                dbesc(json_encode($data))
      -	                        );
      +			// Formerly we stored the signed text, the signature and the author in different fields.
      +			// We now store the raw data so that we are more flexible.
      +			q("INSERT INTO `sign` (`iid`,`signed_text`) VALUES (%d,'%s')",
      +				intval($r[0]["id"]),
      +				dbesc(json_encode($data))
      +			);
       
      -				// the existence of parent_author_signature would have meant the parent_author or owner
      -				// is already relaying.
      -				logger("relaying retraction");
      -
      -				proc_run("php", "include/notifier.php", "drop", $r[0]["id"]);
      -			}
      +			// notify others
      +			proc_run("php", "include/notifier.php", "drop", $r[0]["id"]);
       		}
       	}
       
      -	private function import_retraction($importer, $data) {
      +	private function import_retraction($importer, $sender, $data) {
       		$target_type = notags(unxmlify($data->target_type));
      -		$author = notags(unxmlify($data->author));
       
      -		$contact = self::get_contact_by_handle($importer["uid"], $author);
      +		$contact = self::get_contact_by_handle($importer["uid"], $sender);
       		if (!$contact) {
      -			logger("cannot find contact for author: ".$author);
      +			logger("cannot find contact for sender: ".$sender." and user ".$importer["uid"]);
       			return false;
       		}
       
       		switch ($target_type) {
      -			case "Comment": case "Like": case "StatusMessage":
      -				self::item_retraction($importer, $contact, $data);
      -				break;
      +			case "Comment":
      +			case "Like":
      +			case "Post": // "Post" will be supported in a future version
      +			case "Reshare":
      +			case "StatusMessage":
      +				return self::item_retraction($importer, $contact, $data);;
       
       			case "Person":
       				contact_remove($contact["id"]);
      @@ -1491,6 +1543,7 @@ print_r($data);
       
       			default:
       				logger("Unknown target type ".$target_type);
      +				return false;
       		}
       		return true;
       	}
      @@ -1514,8 +1567,8 @@ print_r($data);
       		if (!$contact)
       			return false;
       
      -		//if (self::message_exists($importer["uid"], $guid))
      -		//	return false;
      +		if (self::message_exists($importer["uid"], $guid))
      +			return false;
       
       		$address = array();
       		if ($data->location)
      @@ -1539,18 +1592,6 @@ print_r($data);
       				$body = add_page_info_to_body($body, false, true);
       		}
       
      -		$str_tags = "";
      -
      -		// This doesn't work. @todo Check if the "tag" field is filled in the "item_store" function.
      -		$cnt = preg_match_all("/@\[url=(.*?)\[\/url\]/ism", $body, $matches, PREG_SET_ORDER);
      -		if($cnt) {
      -			foreach($matches as $mtch) {
      -				if(strlen($str_tags))
      -					$str_tags .= ",";
      -				$str_tags .= "@[url=".$mtch[1]."[/url]";
      -			}
      -		}
      -
       		$datarray["uid"] = $importer["uid"];
       		$datarray["contact-id"] = $contact["id"];
       		$datarray["network"] = NETWORK_DIASPORA;
      @@ -1573,7 +1614,6 @@ print_r($data);
       
       		$datarray["body"] = $body;
       
      -		$datarray["tag"] = $str_tags;
       		if ($provider_display_name != "")
       			$datarray["app"] = $provider_display_name;
       
      @@ -1588,8 +1628,8 @@ print_r($data);
       			$datarray["coord"] = $address["lat"]." ".$address["lng"];
       
       		self::fetch_guid($datarray);
      -		//$message_id = item_store($datarray);
      -		print_r($datarray);
      +		$message_id = item_store($datarray);
      +		// print_r($datarray);
       
       		logger("Stored item with message id ".$message_id, LOGGER_DEBUG);
       
      
      From 8521d79606a2a12aa6763418b24ce90afbe30d81 Mon Sep 17 00:00:00 2001
      From: Michael Vogel 
      Date: Fri, 4 Mar 2016 23:28:43 +0100
      Subject: [PATCH 024/211] Conversations should work now too.
      
      ---
       include/diaspora2.php | 282 +++++++++++++++++++++---------------------
       1 file changed, 141 insertions(+), 141 deletions(-)
      
      diff --git a/include/diaspora2.php b/include/diaspora2.php
      index 59a5c372db..b834e3deb9 100644
      --- a/include/diaspora2.php
      +++ b/include/diaspora2.php
      @@ -22,7 +22,7 @@ class xml {
       
       		if (!is_object($xml)) {
       			foreach($array as $key => $value) {
      -				$root = new SimpleXMLElement('<'.$key.'/>');
      +				$root = new SimpleXMLElement("<".$key."/>");
       				array_to_xml($value, $root);
       
       				$dom = dom_import_simplexml($root)->ownerDocument;
      @@ -82,7 +82,7 @@ class diaspora {
       			dbesc(NETWORK_DIASPORA),
       			dbesc($msg["author"])
       		);
      -		if(count($r)) {
      +		if($r) {
       			foreach($r as $rr) {
       				logger("delivering to: ".$rr["username"]);
       				self::dispatch($rr,$msg);
      @@ -125,7 +125,7 @@ class diaspora {
       
       			case "conversation":
       				//return true;
      -				return self::import_conversation($importer, $fields);
      +				return self::import_conversation($importer, $msg, $fields);
       
       			case "like": // Done
       				return true;
      @@ -320,7 +320,7 @@ class diaspora {
       			dbesc(NETWORK_DIASPORA),
       			dbesc($handle)
       		);
      -		if (count($r)) {
      +		if ($r) {
       			$person = $r[0];
       			logger("In cache ".print_r($r,true), LOGGER_DEBUG);
       
      @@ -336,7 +336,7 @@ class diaspora {
       
       			// Note that Friendica contacts will return a "Diaspora person"
       			// if Diaspora connectivity is enabled on their server
      -			if (count($r) AND ($r["network"] === NETWORK_DIASPORA)) {
      +			if ($r AND ($r["network"] === NETWORK_DIASPORA)) {
       				self::add_fcontact($r, $update);
       				$person = $r;
       			}
      @@ -415,17 +415,17 @@ class diaspora {
       			dbesc($handle)
       		);
       
      -		if ($r AND count($r))
      +		if ($r)
       			return $r[0];
       
       		$handle_parts = explode("@", $handle);
      -		$nurl_sql = '%%://' . $handle_parts[1] . '%%/profile/' . $handle_parts[0];
      +		$nurl_sql = "%%://".$handle_parts[1]."%%/profile/".$handle_parts[0];
       		$r = q("SELECT * FROM `contact` WHERE `network` = '%s' AND `uid` = %d AND `nurl` LIKE '%s' LIMIT 1",
       			dbesc(NETWORK_DFRN),
       			intval($uid),
       			dbesc($nurl_sql)
       		);
      -		if($r AND count($r))
      +		if($r)
       			return $r[0];
       
       		return false;
      @@ -481,7 +481,7 @@ class diaspora {
       			dbesc($guid)
       		);
       
      -		if(count($r)) {
      +		if($r) {
       			logger("message ".$guid." already exists for user ".$uid);
       			return false;
       		}
      @@ -566,7 +566,7 @@ class diaspora {
       			FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1",
       			intval($uid), dbesc($guid));
       
      -		if(!count($r)) {
      +		if(!$r) {
       			$result = self::store_by_guid($guid, $contact["url"], $uid);
       
       			if (!$result) {
      @@ -585,7 +585,7 @@ class diaspora {
       			}
       		}
       
      -		if (!count($r)) {
      +		if (!$r) {
       			logger("parent item not found: parent: ".$guid." item: ".$guid);
       			return false;
       		} else
      @@ -722,62 +722,10 @@ class diaspora {
       		return $message_id;
       	}
       
      -	private function import_conversation($importer, $data) {
      -		// @todo
      -		print_r($data);
      -		die();
      -/*
      -	$guid = notags(unxmlify($xml->guid));
      -	$subject = notags(unxmlify($xml->subject));
      -	$diaspora_handle = notags(unxmlify($xml->diaspora_handle));
      -	$participant_handles = notags(unxmlify($xml->participant_handles));
      -	$created_at = datetime_convert('UTC','UTC',notags(unxmlify($xml->created_at)));
      -
      -	$parent_uri = $diaspora_handle . ':' . $guid;
      -
      -	$messages = $xml->message;
      -
      -	if(! count($messages)) {
      -		logger('empty conversation');
      -		return;
      -	}
      -
      -		$contact = self::get_allowed_contact_by_handle($importer, $sender, true)
      -		if (!$contact)
      -			return false;
      -
      -	$conversation = null;
      -
      -	$c = q("select * from conv where uid = %d and guid = '%s' limit 1",
      -		intval($importer["uid"]),
      -		dbesc($guid)
      -	);
      -	if(count($c))
      -		$conversation = $c[0];
      -	else {
      -		$r = q("insert into conv (uid,guid,creator,created,updated,subject,recips) values(%d, '%s', '%s', '%s', '%s', '%s', '%s') ",
      -			intval($importer["uid"]),
      -			dbesc($guid),
      -			dbesc($diaspora_handle),
      -			dbesc(datetime_convert('UTC','UTC',$created_at)),
      -			dbesc(datetime_convert()),
      -			dbesc($subject),
      -			dbesc($participant_handles)
      -		);
      -		if($r)
      -			$c = q("select * from conv where uid = %d and guid = '%s' limit 1",
      -		intval($importer["uid"]),
      -	    dbesc($guid)
      -	);
      -	    if(count($c))
      -	    $conversation = $c[0];
      -	}
      -	if(! $conversation) {
      -		logger('diaspora_conversation: unable to create conversation.');
      -		return;
      -	}
      -
      -	foreach($messages as $mesg) {
      +	private function import_conversation_message($importer, $contact, $data, $msg, $mesg) {
      +		$guid = notags(unxmlify($data->guid));
      +		$subject = notags(unxmlify($data->subject));
      +		$author = notags(unxmlify($data->author));
       
       		$reply = 0;
       
      @@ -786,63 +734,64 @@ class diaspora {
       		$msg_parent_author_signature = notags(unxmlify($mesg->parent_author_signature));
       		$msg_author_signature = notags(unxmlify($mesg->author_signature));
       		$msg_text = unxmlify($mesg->text);
      -		$msg_created_at = datetime_convert('UTC','UTC',notags(unxmlify($mesg->created_at)));
      -		$msg_diaspora_handle = notags(unxmlify($mesg->diaspora_handle));
      +		$msg_created_at = datetime_convert("UTC", "UTC", notags(unxmlify($mesg->created_at)));
      +		$msg_author = notags(unxmlify($mesg->diaspora_handle));
       		$msg_conversation_guid = notags(unxmlify($mesg->conversation_guid));
      +
       		if($msg_conversation_guid != $guid) {
      -			logger('diaspora_conversation: message conversation guid does not belong to the current conversation. ' . $xml);
      -			continue;
      +			logger("message conversation guid does not belong to the current conversation.");
      +			return false;
       		}
       
       		$body = diaspora2bb($msg_text);
      -		$message_id = $msg_diaspora_handle . ':' . $msg_guid;
      +		$message_uri = $msg_author.":".$msg_guid;
       
      -		$author_signed_data = $msg_guid . ';' . $msg_parent_guid . ';' . $msg_text . ';' . unxmlify($mesg->created_at) . ';' . $msg_diaspora_handle . ';' . $msg_conversation_guid;
      +		$author_signed_data = $msg_guid.";".$msg_parent_guid.";".$msg_text.";".unxmlify($mesg->created_at).";".$msg_author.";".$msg_conversation_guid;
       
       		$author_signature = base64_decode($msg_author_signature);
       
      -		if(strcasecmp($msg_diaspora_handle,$msg["author"]) == 0) {
      +		if(strcasecmp($msg_author,$msg["author"]) == 0) {
       			$person = $contact;
       			$key = $msg["key"];
      -		}
      -		else {
      -			$person = find_diaspora_person_by_handle($msg_diaspora_handle); 
      +		} else {
      +			$person = self::get_person_by_handle($msg_author);
       
      -			if(is_array($person) && x($person,'pubkey'))
      +			if (is_array($person) && x($person, "pubkey"))
       				$key = $person["pubkey"];
       			else {
      -				logger('diaspora_conversation: unable to find author details');
      -				continue;
      +				logger("unable to find author details");
      +					return false;
       			}
       		}
       
      -		if(! rsa_verify($author_signed_data,$author_signature,$key,'sha256')) {
      -			logger('diaspora_conversation: verification failed.');
      -			continue;
      +		if (!rsa_verify($author_signed_data, $author_signature, $key, "sha256")) {
      +			logger("verification failed.");
      +			return false;
       		}
       
       		if($msg_parent_author_signature) {
      -			$owner_signed_data = $msg_guid . ';' . $msg_parent_guid . ';' . $msg_text . ';' . unxmlify($mesg->created_at) . ';' . $msg_diaspora_handle . ';' . $msg_conversation_guid;
      +			$owner_signed_data = $msg_guid.";".$msg_parent_guid.";".$msg_text.";".unxmlify($mesg->created_at).";".$msg_author.";".$msg_conversation_guid;
       
       			$parent_author_signature = base64_decode($msg_parent_author_signature);
       
       			$key = $msg["key"];
       
      -			if(! rsa_verify($owner_signed_data,$parent_author_signature,$key,'sha256')) {
      -				logger('diaspora_conversation: owner verification failed.');
      -				continue;
      +			if (!rsa_verify($owner_signed_data, $parent_author_signature, $key, "sha256")) {
      +				logger("owner verification failed.");
      +				return false;
       			}
       		}
       
      -		$r = q("select id from mail where `uri` = '%s' limit 1",
      -			dbesc($message_id)
      +		$r = q("SELECT `id` FROM `mail` WHERE `uri` = '%s' LIMIT 1",
      +			dbesc($message_uri)
       		);
      -		if(count($r)) {
      -			logger('diaspora_conversation: duplicate message already delivered.', LOGGER_DEBUG);
      -			continue;
      +		if($r) {
      +			logger("duplicate message already delivered.", LOGGER_DEBUG);
      +			return false;
       		}
       
      -		q("insert into mail ( `uid`, `guid`, `convid`, `from-name`,`from-photo`,`from-url`,`contact-id`,`title`,`body`,`seen`,`reply`,`uri`,`parent-uri`,`created`) values ( %d, '%s', %d, '%s', '%s', '%s', %d, '%s', '%s', %d, %d, '%s','%s','%s')",
      +		q("INSERT INTO `mail` (`uid`, `guid`, `convid`, `from-name`,`from-photo`,`from-url`,`contact-id`,`title`,`body`,`seen`,`reply`,`uri`,`parent-uri`,`created`)
      +			VALUES (%d, '%s', %d, '%s', '%s', '%s', %d, '%s', '%s', %d, %d, '%s','%s','%s')",
       			intval($importer["uid"]),
       			dbesc($msg_guid),
       			intval($conversation["id"]),
      @@ -854,32 +803,86 @@ class diaspora {
       			dbesc($body),
       			0,
       			0,
      -			dbesc($message_id),
      -			dbesc($parent_uri),
      +			dbesc($message_uri),
      +			dbesc($author.":".$guid),
       			dbesc($msg_created_at)
       		);
       
      -		q("update conv set updated = '%s' where id = %d",
      +		q("UPDATE `conv` SET `updated` = '%s' WHERE `id` = %d",
       			dbesc(datetime_convert()),
       			intval($conversation["id"])
       		);
       
       		notification(array(
      -			'type' => NOTIFY_MAIL,
      -			'notify_flags' => $importer["notify-flags"],
      -			'language' => $importer["language"],
      -			'to_name' => $importer["username"],
      -			'to_email' => $importer["email"],
      -			'uid' =>$importer["uid"],
      -			'item' => array('subject' => $subject, 'body' => $body),
      -			'source_name' => $person["name"],
      -			'source_link' => $person["url"],
      -			'source_photo' => $person["thumb"],
      -			'verb' => ACTIVITY_POST,
      -			'otype' => 'mail'
      +			"type" => NOTIFY_MAIL,
      +			"notify_flags" => $importer["notify-flags"],
      +			"language" => $importer["language"],
      +			"to_name" => $importer["username"],
      +			"to_email" => $importer["email"],
      +			"uid" =>$importer["uid"],
      +			"item" => array("subject" => $subject, "body" => $body),
      +			"source_name" => $person["name"],
      +			"source_link" => $person["url"],
      +			"source_photo" => $person["thumb"],
      +			"verb" => ACTIVITY_POST,
      +			"otype" => "mail"
       		));
       	}
      -*/
      +
      +	private function import_conversation($importer, $msg, $data) {
      +		$guid = notags(unxmlify($data->guid));
      +		$subject = notags(unxmlify($data->subject));
      +		$created_at = datetime_convert("UTC", "UTC", notags(unxmlify($data->created_at)));
      +		$author = notags(unxmlify($data->author));
      +		$participants = notags(unxmlify($data->participants));
      +
      +		$messages = $data->message;
      +
      +		if (!count($messages)) {
      +			logger("empty conversation");
      +			return false;
      +		}
      +
      +		$contact = self::get_allowed_contact_by_handle($importer, $msg["author"], true);
      +		if (!$contact)
      +			return false;
      +
      +		$conversation = null;
      +
      +		$c = q("SELECT * FROM `conv` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1",
      +			intval($importer["uid"]),
      +			dbesc($guid)
      +		);
      +		if($c)
      +			$conversation = $c[0];
      +		else {
      +			$r = q("INSERT INTO `conv` (`uid`, `guid`, `creator`, `created`, `updated`, `subject`, `recips`)
      +				VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s')",
      +				intval($importer["uid"]),
      +				dbesc($guid),
      +				dbesc($author),
      +				dbesc(datetime_convert("UTC", "UTC", $created_at)),
      +				dbesc(datetime_convert()),
      +				dbesc($subject),
      +				dbesc($participants)
      +			);
      +			if($r)
      +				$c = q("SELECT * FROM `conv` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1",
      +					intval($importer["uid"]),
      +					dbesc($guid)
      +				);
      +
      +			if($c)
      +				$conversation = $c[0];
      +		}
      +		if (!$conversation) {
      +			logger("unable to create conversation.");
      +			return;
      +		}
      +
      +		foreach($messages as $mesg)
      +			self::import_conversation_message($importer, $contact, $data, $msg, $mesg);
      +
       		return true;
       	}
       
      @@ -1007,8 +1010,6 @@ EOT;
       		$author = notags(unxmlify($data->author));
       		$conversation_guid = notags(unxmlify($data->conversation_guid));
       
      -		$parent_uri = $author.":".$parent_guid;
      -
       		$contact = self::get_allowed_contact_by_handle($importer, $author, true);
       		if (!$contact)
       			return false;
      @@ -1019,7 +1020,7 @@ EOT;
       			intval($importer["uid"]),
       			dbesc($conversation_guid)
       		);
      -		if(count($c))
      +		if($c)
       			$conversation = $c[0];
       		else {
       			logger("conversation not available.");
      @@ -1029,7 +1030,7 @@ EOT;
       		$reply = 0;
       
       		$body = diaspora2bb($text);
      -		$message_id = $author.":".$guid;
      +		$message_uri = $author.":".$guid;
       
       		$person = self::get_person_by_handle($author);
       		if (!$person) {
      @@ -1038,10 +1039,10 @@ EOT;
       		}
       
       		$r = q("SELECT `id` FROM `mail` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1",
      -			dbesc($message_id),
      +			dbesc($message_uri),
       			intval($importer["uid"])
       		);
      -		if(count($r)) {
      +		if($r) {
       			logger("duplicate message already delivered.", LOGGER_DEBUG);
       			return false;
       		}
      @@ -1059,8 +1060,8 @@ EOT;
       			dbesc($body),
       			0,
       			1,
      -			dbesc($message_id),
      -			dbesc($parent_uri),
      +			dbesc($message_uri),
      +			dbesc($author.":".$parent_guid),
       			dbesc($created_at)
       		);
       
      @@ -1174,8 +1175,8 @@ EOT;
       		// @todo
       		print_r($data);
       /*
      -	$author = unxmlify($xml->author);
      -	$recipient = unxmlify($xml->recipient);
      +	$author = unxmlify($data->author);
      +	$recipient = unxmlify($data->recipient);
       
       	if (!$author || !$recipient)
       		return;
      @@ -1200,7 +1201,7 @@ EOT;
       			intval($importer["uid"])
       		);
       
      -		if((count($r)) && (!$r[0]["hide-friends"]) && (!$contact["hidden"]) && intval(get_pconfig($importer["uid"],'system','post_newfriend'))) {
      +		if($r && !$r[0]["hide-friends"] && !$contact["hidden"] && intval(get_pconfig($importer["uid"],'system','post_newfriend'))) {
       
       			$self = q("SELECT * FROM `contact` WHERE `self` = 1 AND `uid` = %d LIMIT 1",
       				intval($importer["uid"])
      @@ -1208,7 +1209,7 @@ EOT;
       
       			// they are not CONTACT_IS_FOLLOWER anymore but that's what we have in the array
       
      -			if(count($self) && $contact["rel"] == CONTACT_IS_FOLLOWER) {
      +			if($self && $contact["rel"] == CONTACT_IS_FOLLOWER) {
       
       				$arr = array();
       				$arr["uri"] = $arr["parent-uri"] = item_new_uri(App::get_hostname(), $importer["uid"]);
      @@ -1224,17 +1225,16 @@ EOT;
       				$arr["verb"] = ACTIVITY_FRIEND;
       				$arr["object-type"] = ACTIVITY_OBJ_PERSON;
       
      -				$A = '[url=' . $self[0]["url"] . "]' . $self[0]["name"] . '[/url]';
      -				$B = '[url=' . $contact["url"] . "]' . $contact["name"] . '[/url]';
      -				$BPhoto = '[url=' . $contact["url"] . "]' . '[img]' . $contact["thumb"] . '[/img][/url]';
      +				$A = '[url='.$self[0]["url"] . "]'.$self[0]["name"] .'[/url]';
      +				$B = '[url='.$contact["url"] . "]'.$contact["name"] .'[/url]';
      +				$BPhoto = '[url='.$contact["url"] . "]'.'[img]'.$contact["thumb"] .'[/img][/url]';
       				$arr["body"] =  sprintf( t('%1$s is now friends with %2$s'), $A, $B)."\n\n\n".$Bphoto;
       
      -				$arr["object"] = '' . ACTIVITY_OBJ_PERSON . '' . $contact["name"] . ''
      -					. '' . $contact["url"] . '/' . $contact["name"] . '';
      -				$arr["object"] .= '' . xmlify('' . "\n")
      -;
      -				$arr["object"] .= xmlify('' . "\n");
      -				$arr["object"] .= '' . "\n";
      +				$arr["object"] = ''. ACTIVITY_OBJ_PERSON .''.$contact["name"] .''
      +					.''.$contact["url"] .'/'.$contact["name"] .'';
      +				$arr["object"] .= ''. xmlify(''. "\n");
      +				$arr["object"] .= xmlify(''. "\n");
      +				$arr["object"] .= ''. "\n";
       				$arr["last-child"] = 1;
       
       				$arr["allow_cid"] = $user[0]["allow_cid"];
      @@ -1256,12 +1256,12 @@ EOT;
       	$ret = self::get_person_by_handle($author);
       
       
      -	if((! count($ret)) || ($ret["network"] != NETWORK_DIASPORA)) {
      -		logger('diaspora_request: Cannot resolve diaspora handle ' . $author . ' for ' . $recipient);
      +	if (!$ret || ($ret["network"] != NETWORK_DIASPORA)) {
      +		logger('Cannot resolve diaspora handle '.$author .' for '.$recipient);
       		return;
       	}
       
      -	$batch = (($ret["batch"]) ? $ret["batch"] : implode('/', array_slice(explode('/',$ret["url"]),0,3)) . '/receive/public');
      +	$batch = (($ret["batch"]) ? $ret["batch"] : implode('/', array_slice(explode('/',$ret["url"]),0,3)) .'/receive/public');
       
       
       
      @@ -1286,10 +1286,10 @@ EOT;
       
       	// find the contact record we just created
       
      -	$contact_record = diaspora_get_contact_by_handle($importer["uid"],$author);
      +	$contact_record = self::get_contact_by_handle($importer["uid"],$author);
       
       	if(! $contact_record) {
      -		logger('diaspora_request: unable to locate newly created contact record.');
      +		logger('unable to locate newly created contact record.');
       		return;
       	}
       
      @@ -1360,7 +1360,7 @@ EOT;
       				FROM `item` WHERE `guid` = '%s' AND `visible` AND NOT `deleted` AND `body` != '' LIMIT 1",
       			dbesc($guid));
       
      -		if(count($r)) {
      +		if($r) {
       			logger("reshared message ".$guid." already exists on system.");
       
       			// Maybe it is already a reshared item?
      @@ -1371,23 +1371,23 @@ EOT;
       				return $r[0];
       		}
       
      -		if (!count($r)) {
      -			$server = 'https://'.substr($orig_author,strpos($orig_author,'@')+1);
      +		if (!$r) {
      +			$server = "https://".substr($orig_author, strpos($orig_author, "@") + 1);
       			logger("1st try: reshared message ".$guid." will be fetched from original server: ".$server);
       			$item_id = self::store_by_guid($guid, $server);
       
       			if (!$item_id) {
      -				$server = 'https://'.substr($author,strpos($author,'@')+1);
      +				$server = "https://".substr($author, strpos($author, "@") + 1);
       				logger("2nd try: reshared message ".$guid." will be fetched from sharer's server: ".$server);
       				$item = self::store_by_guid($guid, $server);
       			}
       			if (!$item_id) {
      -				$server = 'http://'.substr($orig_author,strpos($orig_author,'@')+1);
      +				$server = "http://".substr($orig_author, strpos($orig_author, "@") + 1);
       				logger("3rd try: reshared message ".$guid." will be fetched from original server: ".$server);
       				$item = self::store_by_guid($guid, $server);
       			}
       			if (!$item_id) {
      -				$server = 'http://'.substr($author,strpos($author,'@')+1);
      +				$server = "http://".substr($author, strpos($author, "@") + 1);
       				logger("4th try: reshared message ".$guid." will be fetched from sharer's server: ".$server);
       				$item = self::store_by_guid($guid, $server);
       			}
      
      From 1424ff9fea30bdbe5e4541115e19ef8dd6bb4d18 Mon Sep 17 00:00:00 2001
      From: Michael Vogel 
      Date: Sat, 5 Mar 2016 00:27:44 +0100
      Subject: [PATCH 025/211] "import" is now "receive"
      
      ---
       include/diaspora2.php | 311 +++++++++++++++++++++---------------------
       1 file changed, 157 insertions(+), 154 deletions(-)
      
      diff --git a/include/diaspora2.php b/include/diaspora2.php
      index b834e3deb9..031058ab75 100644
      --- a/include/diaspora2.php
      +++ b/include/diaspora2.php
      @@ -116,53 +116,53 @@ class diaspora {
       
       		switch ($type) {
       			case "account_deletion": // Done
      -				return true;
      -				//return self::import_account_deletion($importer, $fields);
      +				//return true;
      +				return self::receive_account_deletion($importer, $fields);
       
       			case "comment": // Done
      -				return true;
      -				//return self::import_comment($importer, $sender, $fields);
      -
      -			case "conversation":
       				//return true;
      -				return self::import_conversation($importer, $msg, $fields);
      +				return self::receive_comment($importer, $sender, $fields);
      +
      +			case "conversation": // Done
      +				//return true;
      +				return self::receive_conversation($importer, $msg, $fields);
       
       			case "like": // Done
      -				return true;
      -				//return self::import_like($importer, $sender, $fields);
      +				//return true;
      +				return self::receive_like($importer, $sender, $fields);
       
       			case "message": // Done
      -				return true;
      -				//return self::import_message($importer, $fields);
      +				//return true;
      +				return self::receive_message($importer, $fields);
       
       			case "participation": // Not implemented
      -				return self::import_participation($importer, $fields);
      +				return self::receive_participation($importer, $fields);
       
       			case "photo": // Not needed
      -				return self::import_photo($importer, $fields);
      +				return self::receive_photo($importer, $fields);
       
       			case "poll_participation": // Not implemented
      -				return self::import_poll_participation($importer, $fields);
      +				return self::receive_poll_participation($importer, $fields);
       
       			case "profile": // Done
      -				return true;
      -				//return self::import_profile($importer, $fields);
      +				//return true;
      +				return self::receive_profile($importer, $fields);
       
       			case "request":
       				//return true;
      -				return self::import_request($importer, $fields);
      +				return self::receive_request($importer, $fields);
       
       			case "reshare": // Done
      -				return true;
      -				//return self::import_reshare($importer, $fields);
      +				//return true;
      +				return self::receive_reshare($importer, $fields);
       
       			case "retraction": // Done
      -				return true;
      -				//return self::import_retraction($importer, $sender, $fields);
      -
      -			case "status_message":
       				//return true;
      -				return self::import_status_message($importer, $fields);
      +				return self::receive_retraction($importer, $sender, $fields);
      +
      +			case "status_message": // Done
      +				//return true;
      +				return self::receive_status_message($importer, $fields);
       
       			default:
       				logger("Unknown message type ".$type);
      @@ -633,7 +633,7 @@ class diaspora {
       		return "https://".substr($addr,strpos($addr,"@")+1)."/posts/".$guid;
       	}
       
      -	private function import_account_deletion($importer, $data) {
      +	private function receive_account_deletion($importer, $data) {
       		$author = notags(unxmlify($data->author));
       
       		$contact = self::get_contact_by_handle($importer["uid"], $author);
      @@ -647,7 +647,7 @@ class diaspora {
       		return true;
       	}
       
      -	private function import_comment($importer, $sender, $data) {
      +	private function receive_comment($importer, $sender, $data) {
       		$guid = notags(unxmlify($data->guid));
       		$parent_guid = notags(unxmlify($data->parent_guid));
       		$text = unxmlify($data->text);
      @@ -722,7 +722,7 @@ class diaspora {
       		return $message_id;
       	}
       
      -	private function import_conversation_message($importer, $contact, $data, $msg, $mesg) {
      +	private function receive_conversation_message($importer, $contact, $data, $msg, $mesg) {
       		$guid = notags(unxmlify($data->guid));
       		$subject = notags(unxmlify($data->subject));
       		$author = notags(unxmlify($data->author));
      @@ -735,7 +735,14 @@ class diaspora {
       		$msg_author_signature = notags(unxmlify($mesg->author_signature));
       		$msg_text = unxmlify($mesg->text);
       		$msg_created_at = datetime_convert("UTC", "UTC", notags(unxmlify($mesg->created_at)));
      -		$msg_author = notags(unxmlify($mesg->diaspora_handle));
      +
      +		if ($mesg->diaspora_handle)
      +			$msg_author = notags(unxmlify($mesg->diaspora_handle));
      +		elseif ($mesg->author)
      +			$msg_author = notags(unxmlify($mesg->author));
      +		else
      +			return false;
      +
       		$msg_conversation_guid = notags(unxmlify($mesg->conversation_guid));
       
       		if($msg_conversation_guid != $guid) {
      @@ -829,7 +836,7 @@ class diaspora {
       		));
       	}
       
      -	private function import_conversation($importer, $msg, $data) {
      +	private function receive_conversation($importer, $msg, $data) {
       		$guid = notags(unxmlify($data->guid));
       		$subject = notags(unxmlify($data->subject));
       		$created_at = datetime_convert("UTC", "UTC", notags(unxmlify($data->created_at)));
      @@ -881,7 +888,7 @@ class diaspora {
       		}
       
       		foreach($messages as $mesg)
      -			self::import_conversation_message($importer, $contact, $data, $msg, $mesg);
      +			self::receive_conversation_message($importer, $contact, $data, $msg, $mesg);
       
       		return true;
       	}
      @@ -916,7 +923,7 @@ EOT;
       		return $obj;
       	}
       
      -	private function import_like($importer, $sender, $data) {
      +	private function receive_like($importer, $sender, $data) {
       		$positive = notags(unxmlify($data->positive));
       		$guid = notags(unxmlify($data->guid));
       		$parent_type = notags(unxmlify($data->parent_type));
      @@ -1002,7 +1009,7 @@ EOT;
       		return $message_id;
       	}
       
      -	private function import_message($importer, $data) {
      +	private function receive_message($importer, $data) {
       		$guid = notags(unxmlify($data->guid));
       		$parent_guid = notags(unxmlify($data->parent_guid));
       		$text = unxmlify($data->text);
      @@ -1073,22 +1080,22 @@ EOT;
       		return true;
       	}
       
      -	private function import_participation($importer, $data) {
      +	private function receive_participation($importer, $data) {
       		// I'm not sure if we can fully support this message type
       		return true;
       	}
       
      -	private function import_photo($importer, $data) {
      +	private function receive_photo($importer, $data) {
       		// There doesn't seem to be a reason for this function, since the photo data is transmitted in the status message as well
       		return true;
       	}
       
      -	private function import_poll_participation($importer, $data) {
      +	private function receive_poll_participation($importer, $data) {
       		// We don't support polls by now
       		return true;
       	}
       
      -	private function import_profile($importer, $data) {
      +	private function receive_profile($importer, $data) {
       		$author = notags(unxmlify($data->author));
       
       		$contact = self::get_contact_by_handle($importer["uid"], $author);
      @@ -1171,23 +1178,7 @@ EOT;
       		return true;
       	}
       
      -	private function import_request($importer, $data) {
      -		// @todo
      -		print_r($data);
      -/*
      -	$author = unxmlify($data->author);
      -	$recipient = unxmlify($data->recipient);
      -
      -	if (!$author || !$recipient)
      -		return;
      -
      -	$contact = self::get_contact_by_handle($importer["uid"],$author);
      -
      -	if($contact) {
      -
      -		// perhaps we were already sharing with this person. Now they're sharing with us.
      -		// That makes us friends.
      -
      +	private function receive_request_make_friend($importer, $contact) {
       		if($contact["rel"] == CONTACT_IS_FOLLOWER && in_array($importer["page-flags"], array(PAGE_FREELOVE))) {
       			q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
       				intval(CONTACT_IS_FRIEND),
      @@ -1201,9 +1192,9 @@ EOT;
       			intval($importer["uid"])
       		);
       
      -		if($r && !$r[0]["hide-friends"] && !$contact["hidden"] && intval(get_pconfig($importer["uid"],'system','post_newfriend'))) {
      +		if($r && !$r[0]["hide-friends"] && !$contact["hidden"] && intval(get_pconfig($importer["uid"], "system", "post_newfriend"))) {
       
      -			$self = q("SELECT * FROM `contact` WHERE `self` = 1 AND `uid` = %d LIMIT 1",
      +			$self = q("SELECT * FROM `contact` WHERE `self` AND `uid` = %d LIMIT 1",
       				intval($importer["uid"])
       			);
       
      @@ -1225,16 +1216,16 @@ EOT;
       				$arr["verb"] = ACTIVITY_FRIEND;
       				$arr["object-type"] = ACTIVITY_OBJ_PERSON;
       
      -				$A = '[url='.$self[0]["url"] . "]'.$self[0]["name"] .'[/url]';
      -				$B = '[url='.$contact["url"] . "]'.$contact["name"] .'[/url]';
      -				$BPhoto = '[url='.$contact["url"] . "]'.'[img]'.$contact["thumb"] .'[/img][/url]';
      -				$arr["body"] =  sprintf( t('%1$s is now friends with %2$s'), $A, $B)."\n\n\n".$Bphoto;
      +				$A = "[url=".$self[0]["url"]."]".$self[0]["name"]."[/url]";
      +				$B = "[url=".$contact["url"]."]".$contact["name"]."[/url]";
      +				$BPhoto = "[url=".$contact["url"]."][img]".$contact["thumb"]."[/img][/url]";
      +				$arr["body"] = sprintf(t("%1$s is now friends with %2$s"), $A, $B)."\n\n\n".$Bphoto;
       
      -				$arr["object"] = ''. ACTIVITY_OBJ_PERSON .''.$contact["name"] .''
      -					.''.$contact["url"] .'/'.$contact["name"] .'';
      -				$arr["object"] .= ''. xmlify(''. "\n");
      -				$arr["object"] .= xmlify(''. "\n");
      -				$arr["object"] .= ''. "\n";
      +				$arr["object"] = "".ACTIVITY_OBJ_PERSON."".$contact["name"].""
      +					."".$contact["url"]."/".$contact["name"]."";
      +				$arr["object"] .= "".xmlify(''."\n");
      +				$arr["object"] .= xmlify(''."\n");
      +				$arr["object"] .= "\n";
       				$arr["last-child"] = 1;
       
       				$arr["allow_cid"] = $user[0]["allow_cid"];
      @@ -1244,111 +1235,123 @@ EOT;
       
       				$i = item_store($arr);
       				if($i)
      -				proc_run('php',"include/notifier.php","activity","$i");
      +					proc_run("php", "include/notifier.php", "activity", $i);
       
       			}
       
       		}
      -
      -		return;
       	}
       
      -	$ret = self::get_person_by_handle($author);
      +	private function receive_request($importer, $data) {
      +		$author = unxmlify($data->author);
      +		$recipient = unxmlify($data->recipient);
       
      +		if (!$author || !$recipient)
      +			return;
       
      -	if (!$ret || ($ret["network"] != NETWORK_DIASPORA)) {
      -		logger('Cannot resolve diaspora handle '.$author .' for '.$recipient);
      -		return;
      -	}
      +		$contact = self::get_contact_by_handle($importer["uid"],$author);
       
      -	$batch = (($ret["batch"]) ? $ret["batch"] : implode('/', array_slice(explode('/',$ret["url"]),0,3)) .'/receive/public');
      +		if($contact) {
       
      +			// perhaps we were already sharing with this person. Now they're sharing with us.
      +			// That makes us friends.
       
      +			self::receive_request_make_friend($importer, $contact);
      +			return true;
      +		}
       
      -	$r = q("INSERT INTO `contact` (`uid`, `network`,`addr`,`created`,`url`,`nurl`,`batch`,`name`,`nick`,`photo`,`pubkey`,`notify`,`poll`,`blocked`,`priority`)
      -		VALUES ( %d, '%s', '%s', '%s', '%s','%s','%s','%s','%s','%s','%s','%s','%s',%d,%d) ",
      -		intval($importer["uid"]),
      -		dbesc($ret["network"]),
      -		dbesc($ret["addr"]),
      -		datetime_convert(),
      -		dbesc($ret["url"]),
      -		dbesc(normalise_link($ret["url"])),
      -		dbesc($batch),
      -		dbesc($ret["name"]),
      -		dbesc($ret["nick"]),
      -		dbesc($ret["photo"]),
      -		dbesc($ret["pubkey"]),
      -		dbesc($ret["notify"]),
      -		dbesc($ret["poll"]),
      -		1,
      -		2
      -	);
      +		$ret = self::get_person_by_handle($author);
       
      -	// find the contact record we just created
      +		if (!$ret || ($ret["network"] != NETWORK_DIASPORA)) {
      +			logger("Cannot resolve diaspora handle ".$author ." for ".$recipient);
      +			return false;
      +		}
       
      -	$contact_record = self::get_contact_by_handle($importer["uid"],$author);
      +		$batch = (($ret["batch"]) ? $ret["batch"] : implode("/", array_slice(explode("/", $ret["url"]), 0, 3))."/receive/public");
       
      -	if(! $contact_record) {
      -		logger('unable to locate newly created contact record.');
      -		return;
      -	}
      -
      -	$g = q("select def_gid from user where uid = %d limit 1",
      -		intval($importer["uid"])
      -	);
      -	if($g && intval($g[0]["def_gid"])) {
      -		group_add_member($importer["uid"],'',$contact_record["id"],$g[0]["def_gid"]);
      -	}
      -
      -	if($importer["page-flags"] == PAGE_NORMAL) {
      -
      -		$hash = random_string() . (string) time();   // Generate a confirm_key
      -
      -		$ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `note`, `hash`, `datetime` )
      -			VALUES ( %d, %d, %d, %d, '%s', '%s', '%s' )",
      +		$r = q("INSERT INTO `contact` (`uid`, `network`,`addr`,`created`,`url`,`nurl`,`batch`,`name`,`nick`,`photo`,`pubkey`,`notify`,`poll`,`blocked`,`priority`)
      +			VALUES (%d, '%s', '%s', '%s', '%s','%s','%s','%s','%s','%s','%s','%s','%s',%d,%d)",
       			intval($importer["uid"]),
      -			intval($contact_record["id"]),
      -			0,
      -			0,
      -			dbesc( t('Sharing notification from Diaspora network')),
      -			dbesc($hash),
      -			dbesc(datetime_convert())
      -		);
      -	}
      -	else {
      -
      -		// automatic friend approval
      -
      -		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
      -		// we are going to change the relationship and make them a follower.
      -
      -		if($importer["page-flags"] == PAGE_FREELOVE)
      -			$new_relation = CONTACT_IS_FRIEND;
      -		else
      -			$new_relation = CONTACT_IS_FOLLOWER;
      -
      -		$r = q("UPDATE `contact` SET `rel` = %d,
      -			`name-date` = '%s',
      -			`uri-date` = '%s',
      -			`blocked` = 0,
      -			`pending` = 0,
      -			`writable` = 1
      -			WHERE `id` = %d
      -			",
      -			intval($new_relation),
      -			dbesc(datetime_convert()),
      -			dbesc(datetime_convert()),
      -			intval($contact_record["id"])
      +			dbesc($ret["network"]),
      +			dbesc($ret["addr"]),
      +			datetime_convert(),
      +			dbesc($ret["url"]),
      +			dbesc(normalise_link($ret["url"])),
      +			dbesc($batch),
      +			dbesc($ret["name"]),
      +			dbesc($ret["nick"]),
      +			dbesc($ret["photo"]),
      +			dbesc($ret["pubkey"]),
      +			dbesc($ret["notify"]),
      +			dbesc($ret["poll"]),
      +			1,
      +			2
       		);
       
      -		$u = q("select * from user where uid = %d limit 1",intval($importer["uid"]));
      -		if($u)
      -			$ret = diaspora_share($u[0],$contact_record);
      -	}
      -*/
      +		// find the contact record we just created
      +
      +		$contact_record = self::get_contact_by_handle($importer["uid"],$author);
      +
      +		if (!$contact_record) {
      +			logger("unable to locate newly created contact record.");
      +			return;
      +		}
      +
      +		$g = q("SELECT `def_gid` FROM `user` WHERE `uid` = %d LIMIT 1",
      +			intval($importer["uid"])
      +		);
      +
      +		if($g && intval($g[0]["def_gid"]))
      +			group_add_member($importer["uid"], "", $contact_record["id"], $g[0]["def_gid"]);
      +
      +		if($importer["page-flags"] == PAGE_NORMAL) {
      +
      +			$hash = random_string().(string)time();   // Generate a confirm_key
      +
      +			$ret = q("INSERT INTO `intro` (`uid`, `contact-id`, `blocked`, `knowyou`, `note`, `hash`, `datetime`)
      +				VALUES (%d, %d, %d, %d, '%s', '%s', '%s')",
      +				intval($importer["uid"]),
      +				intval($contact_record["id"]),
      +				0,
      +				0,
      +				dbesc(t("Sharing notification from Diaspora network")),
      +				dbesc($hash),
      +				dbesc(datetime_convert())
      +			);
      +		} else {
      +
      +			// automatic friend approval
      +
      +			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
      +			// we are going to change the relationship and make them a follower.
      +
      +			if($importer["page-flags"] == PAGE_FREELOVE)
      +				$new_relation = CONTACT_IS_FRIEND;
      +			else
      +				$new_relation = CONTACT_IS_FOLLOWER;
      +
      +			$r = q("UPDATE `contact` SET `rel` = %d,
      +				`name-date` = '%s',
      +				`uri-date` = '%s',
      +				`blocked` = 0,
      +				`pending` = 0,
      +				`writable` = 1
      +				WHERE `id` = %d
      +				",
      +				intval($new_relation),
      +				dbesc(datetime_convert()),
      +				dbesc(datetime_convert()),
      +				intval($contact_record["id"])
      +			);
      +
      +			$u = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1", intval($importer["uid"]));
      +			if($u)
      +				$ret = diaspora_share($u[0], $contact_record);
      +		}
      +
       		return true;
       	}
       
      @@ -1406,7 +1409,7 @@ EOT;
       		return false;
       	}
       
      -	private function import_reshare($importer, $data) {
      +	private function receive_reshare($importer, $data) {
       		$root_author = notags(unxmlify($data->root_author));
       		$root_guid = notags(unxmlify($data->root_guid));
       		$guid = notags(unxmlify($data->guid));
      @@ -1520,7 +1523,7 @@ EOT;
       		}
       	}
       
      -	private function import_retraction($importer, $sender, $data) {
      +	private function receive_retraction($importer, $sender, $data) {
       		$target_type = notags(unxmlify($data->target_type));
       
       		$contact = self::get_contact_by_handle($importer["uid"], $sender);
      @@ -1548,7 +1551,7 @@ EOT;
       		return true;
       	}
       
      -	private function import_status_message($importer, $data) {
      +	private function receive_status_message($importer, $data) {
       
       		$raw_message = unxmlify($data->raw_message);
       		$guid = notags(unxmlify($data->guid));
      
      From 49e8528e52b24f504a667980568ac1a73c0b7a92 Mon Sep 17 00:00:00 2001
      From: Michael Vogel 
      Date: Sat, 5 Mar 2016 01:30:49 +0100
      Subject: [PATCH 026/211] Small cleanup
      
      ---
       include/diaspora2.php | 9 +++++----
       1 file changed, 5 insertions(+), 4 deletions(-)
      
      diff --git a/include/diaspora2.php b/include/diaspora2.php
      index 031058ab75..939a816f40 100644
      --- a/include/diaspora2.php
      +++ b/include/diaspora2.php
      @@ -553,7 +553,6 @@ class diaspora {
       
       		$msg = array("message" => $x, "author" => $author);
       
      -		// We don't really need this, but until the work is unfinished we better will keep this
       		$msg["key"] = self::get_key($msg["author"]);
       
       		return $msg;
      @@ -736,10 +735,12 @@ class diaspora {
       		$msg_text = unxmlify($mesg->text);
       		$msg_created_at = datetime_convert("UTC", "UTC", notags(unxmlify($mesg->created_at)));
       
      -		if ($mesg->diaspora_handle)
      -			$msg_author = notags(unxmlify($mesg->diaspora_handle));
      -		elseif ($mesg->author)
      +		// "diaspora_handle" is the element name from the old version
      +		// "author" is the element name from the new version
      +		if ($mesg->author)
       			$msg_author = notags(unxmlify($mesg->author));
      +		elseif ($mesg->diaspora_handle)
      +			$msg_author = notags(unxmlify($mesg->diaspora_handle));
       		else
       			return false;
       
      
      From 265af9c99b450a1fe3a4160f50f1e495f78f9ac6 Mon Sep 17 00:00:00 2001
      From: Michael Vogel 
      Date: Sun, 6 Mar 2016 00:53:30 +0100
      Subject: [PATCH 027/211] Receiving should be complete, sending partially works
      
      ---
       include/diaspora2.php | 539 ++++++++++++++++++++++++++++++++++++++----
       include/xml.php       |  39 +++
       2 files changed, 538 insertions(+), 40 deletions(-)
       create mode 100644 include/xml.php
      
      diff --git a/include/diaspora2.php b/include/diaspora2.php
      index 939a816f40..197cb1da11 100644
      --- a/include/diaspora2.php
      +++ b/include/diaspora2.php
      @@ -11,44 +11,8 @@ require_once("include/Contact.php");
       require_once("include/Photo.php");
       require_once("include/socgraph.php");
       require_once("include/group.php");
      -require_once("include/api.php");
      -
      -/**
      - * @brief This class contain functions to work with XML data
      - *
      - */
      -class xml {
      -	function from_array($array, &$xml) {
      -
      -		if (!is_object($xml)) {
      -			foreach($array as $key => $value) {
      -				$root = new SimpleXMLElement("<".$key."/>");
      -				array_to_xml($value, $root);
      -
      -				$dom = dom_import_simplexml($root)->ownerDocument;
      -				$dom->formatOutput = true;
      -				return $dom->saveXML();
      -			}
      -		}
      -
      -		foreach($array as $key => $value) {
      -			if (!is_array($value) AND !is_numeric($key))
      -				$xml->addChild($key, $value);
      -			elseif (is_array($value))
      -				array_to_xml($value, $xml->addChild($key));
      -		}
      -	}
      -
      -	function copy(&$source, &$target, $elementname) {
      -		if (count($source->children()) == 0)
      -			$target->addChild($elementname, $source);
      -		else {
      -			$child = $target->addChild($elementname);
      -			foreach ($source->children() AS $childfield => $childentry)
      -				self::copy($childentry, $child, $childfield);
      -		}
      -	}
      -}
      +require_once("include/xml.php");
      +require_once("include/datetime.php");
       
       /**
        * @brief This class contain functions to create and send Diaspora XML files
      @@ -56,6 +20,50 @@ class xml {
        */
       class diaspora {
       
      +	public static function fetch_relay() {
      +
      +		$serverdata = get_config("system", "relay_server");
      +		if ($serverdata == "")
      +			return array();
      +
      +		$relay = array();
      +
      +		$servers = explode(",", $serverdata);
      +
      +		foreach($servers AS $server) {
      +			$server = trim($server);
      +			$batch = $server."/receive/public";
      +
      +			$relais = q("SELECT `batch`, `id`, `name`,`network` FROM `contact` WHERE `uid` = 0 AND `batch` = '%s' LIMIT 1", dbesc($batch));
      +
      +			if (!$relais) {
      +				$addr = "relay@".str_replace("http://", "", normalise_link($server));
      +
      +				$r = q("INSERT INTO `contact` (`uid`, `created`, `name`, `nick`, `addr`, `url`, `nurl`, `batch`, `network`, `rel`, `blocked`, `pending`, `writable`, `name-date`, `uri-date`, `avatar-date`)
      +					VALUES (0, '%s', '%s', 'relay', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, '%s', '%s', '%s')",
      +					datetime_convert(),
      +					dbesc($addr),
      +					dbesc($addr),
      +					dbesc($server),
      +					dbesc(normalise_link($server)),
      +					dbesc($batch),
      +					dbesc(NETWORK_DIASPORA),
      +					intval(CONTACT_IS_FOLLOWER),
      +					dbesc(datetime_convert()),
      +					dbesc(datetime_convert()),
      +					dbesc(datetime_convert())
      +				);
      +
      +				$relais = q("SELECT `batch`, `id`, `name`,`network` FROM `contact` WHERE `uid` = 0 AND `batch` = '%s' LIMIT 1", dbesc($batch));
      +				if ($relais)
      +					$relay[] = $relais[0];
      +			} else
      +				$relay[] = $relais[0];
      +		}
      +
      +		return $relay;
      +	}
      +
       	/**
       	 * @brief Dispatches public messages and find the fitting receivers
       	 *
      @@ -1180,6 +1188,9 @@ EOT;
       	}
       
       	private function receive_request_make_friend($importer, $contact) {
      +
      +		$a = get_app();
      +
       		if($contact["rel"] == CONTACT_IS_FOLLOWER && in_array($importer["page-flags"], array(PAGE_FREELOVE))) {
       			q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d",
       				intval(CONTACT_IS_FRIEND),
      @@ -1204,7 +1215,7 @@ EOT;
       			if($self && $contact["rel"] == CONTACT_IS_FOLLOWER) {
       
       				$arr = array();
      -				$arr["uri"] = $arr["parent-uri"] = item_new_uri(App::get_hostname(), $importer["uid"]);
      +				$arr["uri"] = $arr["parent-uri"] = item_new_uri($a->get_hostname(), $importer["uid"]);
       				$arr["uid"] = $importer["uid"];
       				$arr["contact-id"] = $self[0]["id"];
       				$arr["wall"] = 1;
      @@ -1369,7 +1380,7 @@ EOT;
       
       			// Maybe it is already a reshared item?
       			// Then refetch the content, since there can be many side effects with reshared posts from other networks or reshares from reshares
      -			if (api_share_as_retweet($r[0]))
      +			if (self::is_reshare($r[0]["body"]))
       				$r = array();
       			else
       				return $r[0];
      @@ -1639,5 +1650,453 @@ EOT;
       
       		return $message_id;
       	}
      +
      +	/*******************************************************************************************
      +	 * Here come all the functions that are needed to transmit data with the Diaspora protocol *
      +	 *******************************************************************************************/
      +
      +	private function get_my_handle($me) {
      +		if ($contact["addr"] != "")
      +			return $contact["addr"];
      +
      +		// Normally we should have a filled "addr" field - but in the past this wasn't the case
      +		// So - just in case - we build the the address here.
      +		return $me["nickname"]."@".substr(App::get_baseurl(), strpos(App::get_baseurl(),"://") + 3);
      +	}
      +
      +	function build_public_message($msg, $user, $contact, $prvkey, $pubkey) {
      +
      +		logger("Message: ".$msg, LOGGER_DATA);
      +
      +		$handle = self::get_my_handle($user);
      +
      +		$b64url_data = base64url_encode($msg);
      +
      +		$data = str_replace(array("\n", "\r", " ", "\t"), array("", "", "", ""), $b64url_data);
      +
      +		$type = "application/xml";
      +		$encoding = "base64url";
      +		$alg = "RSA-SHA256";
      +
      +		$signable_data = $data.".".base64url_encode($type).".".base64url_encode($encoding).".".base64url_encode($alg);
      +
      +		$signature = rsa_sign($signable_data,$prvkey);
      +		$sig = base64url_encode($signature);
      +
      +$magic_env = <<< EOT
      +
      +
      +  
      + $handle +
      + + base64url + RSA-SHA256 + $data + $sig + +
      +EOT; + + logger("magic_env: ".$magic_env, LOGGER_DATA); + return $magic_env; + } + + private function build_private_message($msg, $user, $contact, $prvkey, $pubkey) { + + logger("Message: ".$msg, LOGGER_DATA); + + // without a public key nothing will work + + if (!$pubkey) { + logger("pubkey missing: contact id: ".$contact["id"]); + return false; + } + + $inner_aes_key = random_string(32); + $b_inner_aes_key = base64_encode($inner_aes_key); + $inner_iv = random_string(16); + $b_inner_iv = base64_encode($inner_iv); + + $outer_aes_key = random_string(32); + $b_outer_aes_key = base64_encode($outer_aes_key); + $outer_iv = random_string(16); + $b_outer_iv = base64_encode($outer_iv); + + $handle = self::get_my_handle($user); + + $padded_data = pkcs5_pad($msg,16); + $inner_encrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $inner_aes_key, $padded_data, MCRYPT_MODE_CBC, $inner_iv); + + $b64_data = base64_encode($inner_encrypted); + + + $b64url_data = base64url_encode($b64_data); + $data = str_replace(array("\n", "\r", " ", "\t"), array("", "", "", ""), $b64url_data); + + $type = "application/xml"; + $encoding = "base64url"; + $alg = "RSA-SHA256"; + + $signable_data = $data.".".base64url_encode($type).".".base64url_encode($encoding).".".base64url_encode($alg); + + $signature = rsa_sign($signable_data,$prvkey); + $sig = base64url_encode($signature); + +$decrypted_header = <<< EOT + + $b_inner_iv + $b_inner_aes_key + $handle + +EOT; + + $decrypted_header = pkcs5_pad($decrypted_header,16); + + $ciphertext = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $outer_aes_key, $decrypted_header, MCRYPT_MODE_CBC, $outer_iv); + + $outer_json = json_encode(array("iv" => $b_outer_iv, "key" => $b_outer_aes_key)); + + $encrypted_outer_key_bundle = ""; + openssl_public_encrypt($outer_json, $encrypted_outer_key_bundle, $pubkey); + + $b64_encrypted_outer_key_bundle = base64_encode($encrypted_outer_key_bundle); + + logger("outer_bundle: ".$b64_encrypted_outer_key_bundle." key: ".$pubkey, LOGGER_DATA); + + $encrypted_header_json_object = json_encode(array("aes_key" => base64_encode($encrypted_outer_key_bundle), + "ciphertext" => base64_encode($ciphertext))); + $cipher_json = base64_encode($encrypted_header_json_object); + + $encrypted_header = "".$cipher_json.""; + +$magic_env = <<< EOT + + + $encrypted_header + + base64url + RSA-SHA256 + $data + $sig + + +EOT; + + logger("magic_env: ".$magic_env, LOGGER_DATA); + return $magic_env; + } + + private function build_message($msg, $user, $contact, $prvkey, $pubkey, $public = false) { + + if ($public) + $magic_env = self::build_public_message($msg,$user,$contact,$prvkey,$pubkey); + else + $magic_env = self::build_private_message($msg,$user,$contact,$prvkey,$pubkey); + + // The data that will be transmitted is double encoded via "urlencode", strange ... + $slap = "xml=".urlencode(urlencode($magic_env)); + return $slap; + } + + private function transmit($owner, $contact, $slap, $public_batch, $queue_run=false, $guid = "") { + + $a = get_app(); + + $enabled = intval(get_config("system", "diaspora_enabled")); + if(!$enabled) + return 200; + + $logid = random_string(4); + $dest_url = (($public_batch) ? $contact["batch"] : $contact["notify"]); + if (!$dest_url) { + logger("no url for contact: ".$contact["id"]." batch mode =".$public_batch); + return 0; + } + + logger("transmit: ".$logid."-".$guid." ".$dest_url); + + if (!$queue_run && was_recently_delayed($contact["id"])) { + $return_code = 0; + } else { + if (!intval(get_config("system", "diaspora_test"))) { + post_url($dest_url."/", $slap); + $return_code = $a->get_curl_code(); + } else { + logger("test_mode"); + return 200; + } + } + + logger("transmit: ".$logid."-".$guid." returns: ".$return_code); + + if(!$return_code || (($return_code == 503) && (stristr($a->get_curl_headers(), "retry-after")))) { + logger("queue message"); + + $r = q("SELECT `id` FROM `queue` WHERE `cid` = %d AND `network` = '%s' AND `content` = '%s' AND `batch` = %d LIMIT 1", + intval($contact["id"]), + dbesc(NETWORK_DIASPORA), + dbesc($slap), + intval($public_batch) + ); + if(count($r)) { + logger("add_to_queue ignored - identical item already in queue"); + } else { + // queue message for redelivery + add_to_queue($contact["id"], NETWORK_DIASPORA, $slap, $public_batch); + } + } + + + return(($return_code) ? $return_code : (-1)); + } + + public static function send_share($me,$contact) { + $myaddr = self::get_my_handle($me); + $theiraddr = $contact["addr"]; + + $data = array("XML" => array("post" => array("request" => array( + "sender_handle" => $myaddr, + "recipient_handle" => $theiraddr + )))); + + $msg = xml::from_array($data, $xml); + + $slap = self::build_message($msg, $me, $contact, $me["prvkey"], $contact["pubkey"]); + + return(self::transmit($owner,$contact,$slap, false)); + + } + + public static function send_unshare($me,$contact) { + $myaddr = self::get_my_handle($me); + + $data = array("XML" => array("post" => array("retraction" => array( + "post_guid" => $me["guid"], + "diaspora_handle" => $myaddr, + "type" => "Person" + )))); + + $msg = xml::from_array($data, $xml); + + $slap = self::build_message($msg, $me, $contact, $me["prvkey"], $contact["pubkey"]); + + return(self::transmit($owner,$contact,$slap, false)); + } + + function is_reshare($body) { + $body = trim($body); + + // Skip if it isn't a pure repeated messages + // Does it start with a share? + if (strpos($body, "[share") > 0) + return(false); + + // Does it end with a share? + if (strlen($body) > (strrpos($body, "[/share]") + 8)) + return(false); + + $attributes = preg_replace("/\[share(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism","$1",$body); + // Skip if there is no shared message in there + if ($body == $attributes) + return(false); + + $guid = ""; + preg_match("/guid='(.*?)'/ism", $attributes, $matches); + if ($matches[1] != "") + $guid = $matches[1]; + + preg_match('/guid="(.*?)"/ism', $attributes, $matches); + if ($matches[1] != "") + $guid = $matches[1]; + + if ($guid != "") { + $r = q("SELECT `contact-id` FROM `item` WHERE `guid` = '%s' AND `network` IN ('%s', '%s') LIMIT 1", + dbesc($guid), NETWORK_DFRN, NETWORK_DIASPORA); + if ($r) { + $ret= array(); + $ret["root_handle"] = diaspora_handle_from_contact($r[0]["contact-id"]); + $ret["root_guid"] = $guid; + return($ret); + } + } + + $profile = ""; + preg_match("/profile='(.*?)'/ism", $attributes, $matches); + if ($matches[1] != "") + $profile = $matches[1]; + + preg_match('/profile="(.*?)"/ism', $attributes, $matches); + if ($matches[1] != "") + $profile = $matches[1]; + + $ret= array(); + + $ret["root_handle"] = preg_replace("=https?://(.*)/u/(.*)=ism", "$2@$1", $profile); + if (($ret["root_handle"] == $profile) OR ($ret["root_handle"] == "")) + return(false); + + $link = ""; + preg_match("/link='(.*?)'/ism", $attributes, $matches); + if ($matches[1] != "") + $link = $matches[1]; + + preg_match('/link="(.*?)"/ism', $attributes, $matches); + if ($matches[1] != "") + $link = $matches[1]; + + $ret["root_guid"] = preg_replace("=https?://(.*)/posts/(.*)=ism", "$2", $link); + if (($ret["root_guid"] == $link) OR ($ret["root_guid"] == "")) + return(false); + return($ret); + } + + function send_status($item, $owner, $contact, $public_batch = false) { + + $myaddr = self::get_my_handle($owner); + $theiraddr = $contact["addr"]; + + $title = $item["title"]; + $body = $item["body"]; + + // convert to markdown + $body = html_entity_decode(bb2diaspora($body)); + + // Adding the title + if(strlen($title)) + $body = "## ".html_entity_decode($title)."\n\n".$body; + + if ($item["attach"]) { + $cnt = preg_match_all('/href=\"(.*?)\"(.*?)title=\"(.*?)\"/ism', $item["attach"], $matches, PREG_SET_ORDER); + if(cnt) { + $body .= "\n".t("Attachments:")."\n"; + foreach($matches as $mtch) + $body .= "[".$mtch[3]."](".$mtch[1].")\n"; + } + } + + + $public = (($item["private"]) ? "false" : "true"); + + $created = datetime_convert("UTC", "UTC", $item["created"], 'Y-m-d H:i:s \U\T\C'); + + // Detect a share element and do a reshare + if (!$item['private'] AND ($ret = self::is_reshare($item["body"]))) { + $message = array("root_diaspora_id" => $ret["root_handle"], + "root_guid" => $ret["root_guid"], + "guid" => $item["guid"], + "diaspora_handle" => $myaddr, + "public" => $public, + "created_at" => $created, + "provider_display_name" => $item["app"]); + + $data = array("XML" => array("post" => array("reshare" => $message))); + } else { + $location = array(); + + if ($item["location"] != "") + $location["address"] = $item["location"]; + + if ($item["coord"] != "") { + $coord = explode(" ", $item["coord"]); + $location["lat"] = $coord[0]; + $location["lng"] = $coord[1]; + } + + $message = array("raw_message" => $body, + "location" => $location, + "guid" => $item["guid"], + "diaspora_handle" => $myaddr, + "public" => $public, + "created_at" => $created, + "provider_display_name" => $item["app"]); + + if (count($location) == 0) + unset($message["location"]); + + $data = array("XML" => array("post" => array("status_message" => $message))); + } + + $msg = xml::from_array($data, $xml); + + logger("status: ".$owner["username"]." -> ".$contact["name"]." base message: ".$msg, LOGGER_DATA); + logger("send guid ".$item["guid"], LOGGER_DEBUG); + + $slap = self::build_message($msg, $owner, $contact, $owner["uprvkey"], $contact["pubkey"], $public_batch); + + $return_code = self::transmit($owner,$contact,$slap, false); + + logger("guid: ".$item["guid"]." result ".$return_code, LOGGER_DEBUG); + + return $return_code; + } + + function send_mail($item,$owner,$contact) { + + $myaddr = self::get_my_handle($owner); + + $r = q("SELECT * FROM `conv` WHERE `id` = %d AND `uid` = %d LIMIT 1", + intval($item["convid"]), + intval($item["uid"]) + ); + + if (!count($r)) { + logger("conversation not found."); + return; + } + $cnv = $r[0]; + + $conv = array( + "guid" => $cnv["guid"], + "subject" => $cnv["subject"], + "created_at" => datetime_convert("UTC", "UTC", $cnv['created'], 'Y-m-d H:i:s \U\T\C'), + "diaspora_handle" => $cnv["creator"], + "participant_handles" => $cnv["recips"] + ); + + $body = bb2diaspora($item["body"]); + $created = datetime_convert("UTC", "UTC", $item["created"], 'Y-m-d H:i:s \U\T\C'); + + $signed_text = $item["guid"].";".$cnv["guid"].";".$body.";".$created.";".$myaddr.";".$cnv['guid']; + + $sig = base64_encode(rsa_sign($signed_text, $owner["uprvkey"], "sha256")); + + $msg = array( + "guid" => $item["guid"], + "parent_guid" => $cnv["guid"], + "parent_author_signature" => $sig, + "author_signature" => $sig, + "text" => $body, + "created_at" => $created, + "diaspora_handle" => $myaddr, + "conversation_guid" => $cnv["guid"] + ); + + if ($item["reply"]) + $data = array("XML" => array("post" => array("message" => $msg))); + else { + $message = array("guid" => $cnv["guid"], + "subject" => $cnv["subject"], + "created_at" => datetime_convert("UTC", "UTC", $cnv['created'], 'Y-m-d H:i:s \U\T\C'), + "message" => $msg, + "diaspora_handle" => $cnv["creator"], + "participant_handles" => $cnv["recips"]); + + $data = array("XML" => array("post" => array("conversation" => $message))); + } + + $xmsg = xml::from_array($data, $xml); + + logger("conversation: ".print_r($xmsg,true), LOGGER_DATA); + logger("send guid ".$item["guid"], LOGGER_DEBUG); + + $slap = self::build_message($xmsg, $owner, $contact, $owner["uprvkey"], $contact["pubkey"], false); + + $return_code = self::transmit($owner, $contact, $slap, false, false, $item["guid"]); + + logger("guid: ".$item["guid"]." result ".$return_code, LOGGER_DEBUG); + + return $return_code; + } } ?> diff --git a/include/xml.php b/include/xml.php new file mode 100644 index 0000000000..e46f53acc0 --- /dev/null +++ b/include/xml.php @@ -0,0 +1,39 @@ + $value) { + $root = new SimpleXMLElement("<".$key."/>"); + self::from_array($value, $root); + + $dom = dom_import_simplexml($root)->ownerDocument; + $dom->formatOutput = true; + $xml = $dom; + return $dom->saveXML(); + } + } + + foreach($array as $key => $value) { + if (!is_array($value) AND !is_numeric($key)) + $xml->addChild($key, $value); + elseif (is_array($value)) + self::from_array($value, $xml->addChild($key)); + } + } + + function copy(&$source, &$target, $elementname) { + if (count($source->children()) == 0) + $target->addChild($elementname, $source); + else { + $child = $target->addChild($elementname); + foreach ($source->children() AS $childfield => $childentry) + self::copy($childentry, $child, $childfield); + } + } +} +?> From cd57c4960033d2b49ba7fc0cca4361a22177e048 Mon Sep 17 00:00:00 2001 From: Michael Vogel Date: Sun, 6 Mar 2016 01:37:47 +0100 Subject: [PATCH 028/211] Retraction could work now as well. --- include/diaspora2.php | 34 ++++++++++++++++++++ include/xml.php | 2 +- view/templates/diaspora_relay_retraction.tpl | 5 ++- 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/include/diaspora2.php b/include/diaspora2.php index 197cb1da11..c745ab8366 100644 --- a/include/diaspora2.php +++ b/include/diaspora2.php @@ -2031,6 +2031,40 @@ EOT; return $return_code; } + function send_retraction($item, $owner, $contact, $public_batch = false) { + + $myaddr = self::get_my_handle($owner); + + // Check whether the retraction is for a top-level post or whether it's a relayable + if ($item["uri"] !== $item["parent-uri"]) { + $msg_type = "relayable_retraction"; + $target_type = (($item["verb"] === ACTIVITY_LIKE) ? "Like" : "Comment"); + } else { + $msg_type = "signed_retraction"; + $target_type = "StatusMessage"; + } + + $signed_text = $item["guid"].";".$target_type; + + $message = array("target_guid" => $item['guid'], + "target_type" => $target_type, + "sender_handle" => $myaddr, + "target_author_signature" => base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256'))); + + $data = array("XML" => array("post" => array($msg_type => $message))); + $msg = xml::from_array($data, $xml); + + logger("send guid ".$item["guid"], LOGGER_DEBUG); + + $slap = self::build_message($msg, $owner, $contact, $owner["uprvkey"], $contact["pubkey"], $public_batch); + + $return_code = self::transmit($owner, $contact, $slap, $public_batch, false, $item["guid"]); + + logger("guid: ".$item["guid"]." result ".$return_code, LOGGER_DEBUG); + + return $return_code; + } + function send_mail($item,$owner,$contact) { $myaddr = self::get_my_handle($owner); diff --git a/include/xml.php b/include/xml.php index e46f53acc0..9c458dab12 100644 --- a/include/xml.php +++ b/include/xml.php @@ -20,7 +20,7 @@ class xml { foreach($array as $key => $value) { if (!is_array($value) AND !is_numeric($key)) - $xml->addChild($key, $value); + $xml->addChild($key, xmlify($value)); elseif (is_array($value)) self::from_array($value, $xml->addChild($key)); } diff --git a/view/templates/diaspora_relay_retraction.tpl b/view/templates/diaspora_relay_retraction.tpl index b3f97a2e13..c4b44cd05f 100644 --- a/view/templates/diaspora_relay_retraction.tpl +++ b/view/templates/diaspora_relay_retraction.tpl @@ -1,11 +1,10 @@ - - {{$type}} {{$guid}} - {{$signature}} + {{$type}} {{$handle}} + {{$signature}} From c5c1237d3e0b2ec3b3a1d7edbb0d8e4bb359a5b8 Mon Sep 17 00:00:00 2001 From: Michael Vogel Date: Sun, 6 Mar 2016 11:41:51 +0100 Subject: [PATCH 029/211] Preparation for followups --- include/diaspora2.php | 77 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 72 insertions(+), 5 deletions(-) diff --git a/include/diaspora2.php b/include/diaspora2.php index c745ab8366..ef1b1c3801 100644 --- a/include/diaspora2.php +++ b/include/diaspora2.php @@ -1664,7 +1664,7 @@ EOT; return $me["nickname"]."@".substr(App::get_baseurl(), strpos(App::get_baseurl(),"://") + 3); } - function build_public_message($msg, $user, $contact, $prvkey, $pubkey) { + private function build_public_message($msg, $user, $contact, $prvkey, $pubkey) { logger("Message: ".$msg, LOGGER_DATA); @@ -1884,7 +1884,7 @@ EOT; return(self::transmit($owner,$contact,$slap, false)); } - function is_reshare($body) { + private function is_reshare($body) { $body = trim($body); // Skip if it isn't a pure repeated messages @@ -1951,7 +1951,7 @@ EOT; return($ret); } - function send_status($item, $owner, $contact, $public_batch = false) { + public static function send_status($item, $owner, $contact, $public_batch = false) { $myaddr = self::get_my_handle($owner); $theiraddr = $contact["addr"]; @@ -2030,8 +2030,75 @@ EOT; return $return_code; } +/* + public static function diaspora_send_followup($item,$owner,$contact,$public_batch = false) { - function send_retraction($item, $owner, $contact, $public_batch = false) { + $myaddr = self::get_my_handle($owner); + + // Diaspora doesn't support threaded comments, but some + // versions of Diaspora (i.e. Diaspora-pistos) support + // likes on comments + if($item['verb'] === ACTIVITY_LIKE && $item['thr-parent']) { + $p = q("select guid, type, uri, `parent-uri` from item where uri = '%s' limit 1", + dbesc($item['thr-parent']) + ); + } else { + // The first item in the `item` table with the parent id is the parent. However, MySQL doesn't always + // return the items ordered by `item`.`id`, in which case the wrong item is chosen as the parent. + // The only item with `parent` and `id` as the parent id is the parent item. + $p = q("select guid, type, uri, `parent-uri` from item where parent = %d and id = %d limit 1", + intval($item['parent']), + intval($item['parent']) + ); + } + if(count($p)) + $parent = $p[0]; + else + return; + + if($item['verb'] === ACTIVITY_LIKE) { + $tpl = get_markup_template('diaspora_like.tpl'); + $like = true; + $target_type = ( $parent['uri'] === $parent['parent-uri'] ? 'Post' : 'Comment'); + $positive = 'true'; + + if(($item['deleted'])) + logger('diaspora_send_followup: received deleted "like". Those should go to diaspora_send_retraction'); + } else { + $tpl = get_markup_template('diaspora_comment.tpl'); + $like = false; + } + + $text = html_entity_decode(bb2diaspora($item['body'])); + + // sign it + + if($like) + $signed_text = $positive . ';' . $item['guid'] . ';' . $target_type . ';' . $parent['guid'] . ';' . $myaddr; + else + $signed_text = $item['guid'] . ';' . $parent['guid'] . ';' . $text . ';' . $myaddr; + + $authorsig = base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256')); + + $msg = replace_macros($tpl,array( + '$guid' => xmlify($item['guid']), + '$parent_guid' => xmlify($parent['guid']), + '$target_type' =>xmlify($target_type), + '$authorsig' => xmlify($authorsig), + '$body' => xmlify($text), + '$positive' => xmlify($positive), + '$handle' => xmlify($myaddr) + )); + + logger('diaspora_followup: base message: ' . $msg, LOGGER_DATA); + logger('send guid '.$item['guid'], LOGGER_DEBUG); + + $slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($msg,$owner,$contact,$owner['uprvkey'],$contact['pubkey'],$public_batch))); + + return(diaspora_transmit($owner,$contact,$slap,$public_batch,false,$item['guid'])); + } +*/ + public static function send_retraction($item, $owner, $contact, $public_batch = false) { $myaddr = self::get_my_handle($owner); @@ -2065,7 +2132,7 @@ EOT; return $return_code; } - function send_mail($item,$owner,$contact) { + public static function send_mail($item,$owner,$contact) { $myaddr = self::get_my_handle($owner); From ffbb17a91990cc61961e1c954b6194053d7ec63f Mon Sep 17 00:00:00 2001 From: Michael Vogel Date: Sun, 6 Mar 2016 22:49:50 +0100 Subject: [PATCH 030/211] Sending like and comment should work now. --- include/diaspora2.php | 131 ++++++++++++++++++++++++------------------ 1 file changed, 75 insertions(+), 56 deletions(-) diff --git a/include/diaspora2.php b/include/diaspora2.php index ef1b1c3801..0ab28d7b2c 100644 --- a/include/diaspora2.php +++ b/include/diaspora2.php @@ -2030,74 +2030,93 @@ EOT; return $return_code; } -/* - public static function diaspora_send_followup($item,$owner,$contact,$public_batch = false) { + + private function construct_like($item,$owner,$contact,$public_batch = false) { $myaddr = self::get_my_handle($owner); - // Diaspora doesn't support threaded comments, but some - // versions of Diaspora (i.e. Diaspora-pistos) support - // likes on comments - if($item['verb'] === ACTIVITY_LIKE && $item['thr-parent']) { - $p = q("select guid, type, uri, `parent-uri` from item where uri = '%s' limit 1", - dbesc($item['thr-parent']) - ); - } else { - // The first item in the `item` table with the parent id is the parent. However, MySQL doesn't always - // return the items ordered by `item`.`id`, in which case the wrong item is chosen as the parent. - // The only item with `parent` and `id` as the parent id is the parent item. - $p = q("select guid, type, uri, `parent-uri` from item where parent = %d and id = %d limit 1", - intval($item['parent']), - intval($item['parent']) - ); - } - if(count($p)) - $parent = $p[0]; - else - return; + $p = q("SELECT `guid`, `uri`, `parent-uri` FROM `item` WHERE `uri` = '%s' LIMIT 1", + dbesc($item["thr-parent"]) + ); + if(!$p) + return false; - if($item['verb'] === ACTIVITY_LIKE) { - $tpl = get_markup_template('diaspora_like.tpl'); - $like = true; - $target_type = ( $parent['uri'] === $parent['parent-uri'] ? 'Post' : 'Comment'); - $positive = 'true'; + $parent = $p[0]; - if(($item['deleted'])) - logger('diaspora_send_followup: received deleted "like". Those should go to diaspora_send_retraction'); - } else { - $tpl = get_markup_template('diaspora_comment.tpl'); - $like = false; - } - - $text = html_entity_decode(bb2diaspora($item['body'])); + $target_type = ($parent["uri"] === $parent["parent-uri"] ? "Post" : "Comment"); + $positive = "true"; // sign it + $signed_text = $positive.";".$item["guid"].";".$target_type.";".$parent["guid"].";".$myaddr; - if($like) - $signed_text = $positive . ';' . $item['guid'] . ';' . $target_type . ';' . $parent['guid'] . ';' . $myaddr; - else - $signed_text = $item['guid'] . ';' . $parent['guid'] . ';' . $text . ';' . $myaddr; + $authorsig = base64_encode(rsa_sign($signed_text, $owner["uprvkey"], "sha256")); - $authorsig = base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256')); + $message = array("positive" => $positive, + "guid" => $item["guid"], + "target_type" => $item["guid"], + "parent_guid" => $parent["guid"], + "author_signature" => $authorsig, + "diaspora_handle" => $myaddr); - $msg = replace_macros($tpl,array( - '$guid' => xmlify($item['guid']), - '$parent_guid' => xmlify($parent['guid']), - '$target_type' =>xmlify($target_type), - '$authorsig' => xmlify($authorsig), - '$body' => xmlify($text), - '$positive' => xmlify($positive), - '$handle' => xmlify($myaddr) - )); + $data = array("XML" => array("post" => array("like" => $message))); - 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))); - - return(diaspora_transmit($owner,$contact,$slap,$public_batch,false,$item['guid'])); + return xml::from_array($data, $xml); } -*/ + + private function construct_comment($item,$owner,$contact,$public_batch = false) { + + $myaddr = self::get_my_handle($owner); + + $p = q("SELECT `guid` FROM `item` WHERE `parent` = %d AND `id` = %d LIMIT 1", + intval($item["parent"]), + intval($item["parent"]) + ); + + if (!$p) + return false; + + $parent = $p[0]; + + $text = html_entity_decode(bb2diaspora($item["body"])); + + // sign it + $signed_text = $item["guid"].";".$parent["guid"].";".$text.";".$myaddr; + + $authorsig = base64_encode(rsa_sign($signed_text, $owner["uprvkey"], "sha256")); + + $message = array("guid" => $item["guid"], + "parent_guid" => $parent["guid"], + "author_signature" => $authorsig, + "text" => $text, + "diaspora_handle" => $myaddr); + + $data = array("XML" => array("post" => array("comment" => $message))); + + return xml::from_array($data, $xml); + } + + public static function send_followup($item,$owner,$contact,$public_batch = false) { + + if($item['verb'] === ACTIVITY_LIKE) + $msg = self::construct_like($item, $owner, $contact, $public_batch); + else + $msg = self::construct_comment($item, $owner, $contact, $public_batch); + + if (!$msg) + return $msg; + + logger("base message: ".$msg, LOGGER_DATA); + logger("send guid ".$item["guid"], LOGGER_DEBUG); + + $slap = self::build_message($msg, $owner, $contact, $owner["uprvkey"], $contact["pubkey"], $public_batch); + + $return_code = self::transmit($owner, $contact, $slap, $public_batch, false, $item["guid"]); + + logger("guid: ".$item["guid"]." result ".$return_code, LOGGER_DEBUG); + + return $return_code; + } + public static function send_retraction($item, $owner, $contact, $public_batch = false) { $myaddr = self::get_my_handle($owner); From f3206bc38298cd4829999c0354d132d0543a5e3d Mon Sep 17 00:00:00 2001 From: Michael Vogel Date: Mon, 7 Mar 2016 01:34:06 +0100 Subject: [PATCH 031/211] Relaying is nearly done --- include/diaspora2.php | 219 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 203 insertions(+), 16 deletions(-) diff --git a/include/diaspora2.php b/include/diaspora2.php index 0ab28d7b2c..1cc6e55e3b 100644 --- a/include/diaspora2.php +++ b/include/diaspora2.php @@ -2063,32 +2063,45 @@ EOT; return xml::from_array($data, $xml); } - private function construct_comment($item,$owner,$contact,$public_batch = false) { + private function construct_comment($item,$owner,$contact,$public_batch = false, $data = null) { - $myaddr = self::get_my_handle($owner); + if (is_array($data)) + $message = $data; + else { + $myaddr = self::get_my_handle($owner); - $p = q("SELECT `guid` FROM `item` WHERE `parent` = %d AND `id` = %d LIMIT 1", - intval($item["parent"]), - intval($item["parent"]) - ); + $p = q("SELECT `guid` FROM `item` WHERE `parent` = %d AND `id` = %d LIMIT 1", + intval($item["parent"]), + intval($item["parent"]) + ); - if (!$p) - return false; + if (!$p) + return false; - $parent = $p[0]; + $parent = $p[0]; - $text = html_entity_decode(bb2diaspora($item["body"])); + $text = html_entity_decode(bb2diaspora($item["body"])); + + $message = array("guid" => $item["guid"], + "parent_guid" => $parent["guid"], + "author_signature" => "", + "text" => $text, + "diaspora_handle" => $myaddr); + } // sign it - $signed_text = $item["guid"].";".$parent["guid"].";".$text.";".$myaddr; + $sigmsg = $message; + unset($sigmsg["author_signature"]); + unset($sigmsg["parent_author_signature"]); + + $signed_text = implode(";", $sigmsg); $authorsig = base64_encode(rsa_sign($signed_text, $owner["uprvkey"], "sha256")); - $message = array("guid" => $item["guid"], - "parent_guid" => $parent["guid"], - "author_signature" => $authorsig, - "text" => $text, - "diaspora_handle" => $myaddr); + if ($message["author_signature"] == "") + $message["author_signature"] = $authorsig; + else + $message["parent_author_signature"] = $authorsig; $data = array("XML" => array("post" => array("comment" => $message))); @@ -2117,6 +2130,180 @@ EOT; return $return_code; } + function send_relay($item, $owner, $contact, $public_batch = false) { + + if ($item["deleted"]) + $sql_sign_id = "retract_iid"; + else + $sql_sign_id = "iid"; + + // fetch the original signature if the relayable was created by a Diaspora + // or DFRN user. + + $r = q("SELECT `signed_text`, `signature`, `signer` FROM `sign` WHERE `".$sql_sign_id."` = %d LIMIT 1", + intval($item["id"]) + ); + + if(count($r)) { + $orig_sign = $r[0]; + $signed_text = $orig_sign['signed_text']; + $authorsig = $orig_sign['signature']; + $handle = $orig_sign['signer']; + + // Split the signed text + $signed_parts = explode(";", $signed_text); + + // Remove the comment guid + $guid = array_shift($signed_parts); + + // Remove the parent guid + $parent_guid = array_shift($signed_parts); + + // Remove the handle + $handle = array_pop($signed_parts); + + // Glue the parts together + $text = implode(";", $signed_parts); + + $data = array("guid" => $guid, + "parent_guid" => $parent_guid, + "parent_author_signature" => "", + "author_signature" => $orig_sign['signature'], + "text" => implode(";", $signed_parts), + "diaspora_handle" => $handle); + } + + + $myaddr = self::get_my_handle($owner); + + if ($item['deleted']) + ; // Retraction + elseif($item['verb'] === ACTIVITY_LIKE) + $msg = self::construct_like($item, $owner, $contact, $public_batch); + else + $msg = self::construct_comment($item, $owner, $contact, $public_batch, $data); +die($msg); +/* + // Diaspora doesn't support threaded comments, but some + // versions of Diaspora (i.e. Diaspora-pistos) support + // likes on comments + if($item['verb'] === ACTIVITY_LIKE && $item['thr-parent']) { + $p = q("select guid, type, uri, `parent-uri` from item where uri = '%s' limit 1", + dbesc($item['thr-parent']) + ); + } + else { + // The first item in the `item` table with the parent id is the parent. However, MySQL doesn't always + // return the items ordered by `item`.`id`, in which case the wrong item is chosen as the parent. + // The only item with `parent` and `id` as the parent id is the parent item. + $p = q("select guid, type, uri, `parent-uri` from item where parent = %d and id = %d limit 1", + intval($item['parent']), + intval($item['parent']) + ); + } + if(count($p)) + $parent = $p[0]; + else + return; + + $like = false; + $relay_retract = false; + $sql_sign_id = 'iid'; + if( $item['deleted']) { + $relay_retract = true; + + $target_type = ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment'); + + $sql_sign_id = 'retract_iid'; + $tpl = get_markup_template('diaspora_relayable_retraction.tpl'); + } + elseif($item['verb'] === ACTIVITY_LIKE) { + $like = true; + + $target_type = ( $parent['uri'] === $parent['parent-uri'] ? 'Post' : 'Comment'); +// $positive = (($item['deleted']) ? 'false' : 'true'); + $positive = 'true'; + + $tpl = get_markup_template('diaspora_like_relay.tpl'); + } + else { // item is a comment + $tpl = get_markup_template('diaspora_comment_relay.tpl'); + } + + + // fetch the original signature if the relayable was created by a Diaspora + // or DFRN user. Relayables for other networks are not supported. + + $r = q("SELECT `signed_text`, `signature`, `signer` FROM `sign` WHERE " . $sql_sign_id . " = %d LIMIT 1", + intval($item['id']) + ); + if(count($r)) { + $orig_sign = $r[0]; + $signed_text = $orig_sign['signed_text']; + $authorsig = $orig_sign['signature']; + $handle = $orig_sign['signer']; + + // 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 + + $body = $item['body']; + $text = html_entity_decode(bb2diaspora($body)); + + $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; + + $authorsig = base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256')); + } + + // Sign the relayable with the top-level owner's signature + $parentauthorsig = base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256')); + + $msg = replace_macros($tpl,array( + '$guid' => xmlify($item['guid']), + '$parent_guid' => xmlify($parent['guid']), + '$target_type' =>xmlify($target_type), + '$authorsig' => xmlify($authorsig), + '$parentsig' => xmlify($parentauthorsig), + '$body' => xmlify($text), + '$positive' => xmlify($positive), + '$handle' => xmlify($handle) + )); + + 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,false,$item['guid'])); +*/ + } + + public static function send_retraction($item, $owner, $contact, $public_batch = false) { $myaddr = self::get_my_handle($owner); From 41b0ff929d6933c14fe65575fceae02afdefc90c Mon Sep 17 00:00:00 2001 From: Michael Vogel Date: Mon, 7 Mar 2016 08:17:21 +0100 Subject: [PATCH 032/211] Relayed Likes and relayed comments should work, code needs beautification --- include/diaspora2.php | 122 +++++++++++++++++++++++++----------------- 1 file changed, 74 insertions(+), 48 deletions(-) diff --git a/include/diaspora2.php b/include/diaspora2.php index 1cc6e55e3b..48093cc5b9 100644 --- a/include/diaspora2.php +++ b/include/diaspora2.php @@ -1799,6 +1799,16 @@ EOT; return $slap; } + private function get_signature($owner, $message) { + $sigmsg = $message; + unset($sigmsg["author_signature"]); + unset($sigmsg["parent_author_signature"]); + + $signed_text = implode(";", $sigmsg); + + return base64_encode(rsa_sign($signed_text, $owner["uprvkey"], "sha256")); + } + private function transmit($owner, $contact, $slap, $public_batch, $queue_run=false, $guid = "") { $a = get_app(); @@ -2031,32 +2041,37 @@ EOT; return $return_code; } - private function construct_like($item,$owner,$contact,$public_batch = false) { + private function construct_like($item,$owner,$contact,$public_batch = false, $data = null) { - $myaddr = self::get_my_handle($owner); + if (is_array($data)) + $message = $data; + else { + $myaddr = self::get_my_handle($owner); - $p = q("SELECT `guid`, `uri`, `parent-uri` FROM `item` WHERE `uri` = '%s' LIMIT 1", - dbesc($item["thr-parent"]) - ); - if(!$p) - return false; + $p = q("SELECT `guid`, `uri`, `parent-uri` FROM `item` WHERE `uri` = '%s' LIMIT 1", + dbesc($item["thr-parent"])); + if(!$p) + return false; - $parent = $p[0]; + $parent = $p[0]; - $target_type = ($parent["uri"] === $parent["parent-uri"] ? "Post" : "Comment"); - $positive = "true"; + $target_type = ($parent["uri"] === $parent["parent-uri"] ? "Post" : "Comment"); + $positive = "true"; - // sign it - $signed_text = $positive.";".$item["guid"].";".$target_type.";".$parent["guid"].";".$myaddr; + $message = array("positive" => $positive, + "guid" => $item["guid"], + "target_type" => $target_type, + "parent_guid" => $parent["guid"], + "author_signature" => $authorsig, + "diaspora_handle" => $myaddr); + } - $authorsig = base64_encode(rsa_sign($signed_text, $owner["uprvkey"], "sha256")); + $authorsig = self::get_signature($owner, $message); - $message = array("positive" => $positive, - "guid" => $item["guid"], - "target_type" => $item["guid"], - "parent_guid" => $parent["guid"], - "author_signature" => $authorsig, - "diaspora_handle" => $myaddr); + if ($message["author_signature"] == "") + $message["author_signature"] = $authorsig; + else + $message["parent_author_signature"] = $authorsig; $data = array("XML" => array("post" => array("like" => $message))); @@ -2089,14 +2104,7 @@ EOT; "diaspora_handle" => $myaddr); } - // sign it - $sigmsg = $message; - unset($sigmsg["author_signature"]); - unset($sigmsg["parent_author_signature"]); - - $signed_text = implode(";", $sigmsg); - - $authorsig = base64_encode(rsa_sign($signed_text, $owner["uprvkey"], "sha256")); + $authorsig = self::get_signature($owner, $message); if ($message["author_signature"] == "") $message["author_signature"] = $authorsig; @@ -2153,36 +2161,56 @@ EOT; // Split the signed text $signed_parts = explode(";", $signed_text); - // Remove the comment guid - $guid = array_shift($signed_parts); + if ($item['verb'] === ACTIVITY_LIKE) { + $data = array("positive" => $signed_parts[0], + "guid" => $signed_parts[1], + "target_type" => $signed_parts[2], + "parent_guid" => $signed_parts[3], + "parent_author_signature" => "", + "author_signature" => $orig_sign['signature'], + "diaspora_handle" => $signed_parts[4]); + } else { + // Remove the comment guid + $guid = array_shift($signed_parts); - // Remove the parent guid - $parent_guid = array_shift($signed_parts); + // Remove the parent guid + $parent_guid = array_shift($signed_parts); - // Remove the handle - $handle = array_pop($signed_parts); + // Remove the handle + $handle = array_pop($signed_parts); - // Glue the parts together - $text = implode(";", $signed_parts); + // Glue the parts together + $text = implode(";", $signed_parts); - $data = array("guid" => $guid, - "parent_guid" => $parent_guid, - "parent_author_signature" => "", - "author_signature" => $orig_sign['signature'], - "text" => implode(";", $signed_parts), - "diaspora_handle" => $handle); + $data = array("guid" => $guid, + "parent_guid" => $parent_guid, + "parent_author_signature" => "", + "author_signature" => $orig_sign['signature'], + "text" => implode(";", $signed_parts), + "diaspora_handle" => $handle); + } } - - $myaddr = self::get_my_handle($owner); - if ($item['deleted']) - ; // Retraction + ; // Relayed Retraction elseif($item['verb'] === ACTIVITY_LIKE) - $msg = self::construct_like($item, $owner, $contact, $public_batch); + $msg = self::construct_like($item, $owner, $contact, $public_batch, $data); else $msg = self::construct_comment($item, $owner, $contact, $public_batch, $data); die($msg); + + logger('base message: '.$msg, LOGGER_DATA); + logger('send guid '.$item['guid'], LOGGER_DEBUG); + + $slap = self::build_message($msg,$owner, $contact, $owner['uprvkey'], $contact['pubkey'], $public_batch); + + $return_code = self::transmit($owner, $contact, $slap, $public_batch, false, $item['guid']); + + logger("guid: ".$item["guid"]." result ".$return_code, LOGGER_DEBUG); + + return $return_code; + } + /* // Diaspora doesn't support threaded comments, but some // versions of Diaspora (i.e. Diaspora-pistos) support @@ -2301,8 +2329,6 @@ die($msg); return(diaspora_transmit($owner,$contact,$slap,$public_batch,false,$item['guid'])); */ - } - public static function send_retraction($item, $owner, $contact, $public_batch = false) { From b34d230f133e2b945c934b75fe45357353e5e34f Mon Sep 17 00:00:00 2001 From: Michael Vogel Date: Mon, 7 Mar 2016 15:20:48 +0100 Subject: [PATCH 033/211] Small things ... --- include/diaspora2.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/diaspora2.php b/include/diaspora2.php index 48093cc5b9..4c9d79912e 100644 --- a/include/diaspora2.php +++ b/include/diaspora2.php @@ -1525,7 +1525,7 @@ EOT; // Formerly we stored the signed text, the signature and the author in different fields. // We now store the raw data so that we are more flexible. - q("INSERT INTO `sign` (`iid`,`signed_text`) VALUES (%d,'%s')", + q("INSERT INTO `sign` (`retract_iid`,`signed_text`) VALUES (%d,'%s')", intval($r[0]["id"]), dbesc(json_encode($data)) ); @@ -1552,7 +1552,7 @@ EOT; case "StatusMessage": return self::item_retraction($importer, $contact, $data);; - case "Person": + case "Person": /// @todo an "unshare" shouldn't remove the contact contact_remove($contact["id"]); return true; From 5ec00840ba9d534a66bf023ed56a687e1ad8555e Mon Sep 17 00:00:00 2001 From: Michael Vogel Date: Wed, 9 Mar 2016 19:30:04 +0100 Subject: [PATCH 034/211] Some fixes for the fetching of postings by using /p/ --- include/diaspora2.php | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/include/diaspora2.php b/include/diaspora2.php index 4c9d79912e..7aa0fc6989 100644 --- a/include/diaspora2.php +++ b/include/diaspora2.php @@ -550,12 +550,15 @@ class diaspora { return self::fetch_message($source_xml->root_guid, $server, ++$level); } + $author = ""; + // Fetch the author - for the old and the new Diaspora version if ($source_xml->post->status_message->diaspora_handle) $author = (string)$source_xml->post->status_message->diaspora_handle; - elseif ($source_xml->author) + elseif ($source_xml->author AND ($source_xml->getName() == "status_message")) $author = (string)$source_xml->author; + // If this isn't a "status_message" then quit if (!$author) return false; @@ -1391,22 +1394,24 @@ EOT; logger("1st try: reshared message ".$guid." will be fetched from original server: ".$server); $item_id = self::store_by_guid($guid, $server); - if (!$item_id) { - $server = "https://".substr($author, strpos($author, "@") + 1); - logger("2nd try: reshared message ".$guid." will be fetched from sharer's server: ".$server); - $item = self::store_by_guid($guid, $server); - } if (!$item_id) { $server = "http://".substr($orig_author, strpos($orig_author, "@") + 1); - logger("3rd try: reshared message ".$guid." will be fetched from original server: ".$server); - $item = self::store_by_guid($guid, $server); + logger("2nd try: reshared message ".$guid." will be fetched from original server: ".$server); + $item_id = self::store_by_guid($guid, $server); + } + + // Deactivated by now since there is a risk that someone could manipulate postings through this method +/* if (!$item_id) { + $server = "https://".substr($author, strpos($author, "@") + 1); + logger("3rd try: reshared message ".$guid." will be fetched from sharer's server: ".$server); + $item_id = self::store_by_guid($guid, $server); } if (!$item_id) { $server = "http://".substr($author, strpos($author, "@") + 1); logger("4th try: reshared message ".$guid." will be fetched from sharer's server: ".$server); - $item = self::store_by_guid($guid, $server); + $item_id = self::store_by_guid($guid, $server); } - +*/ if ($item_id) { $r = q("SELECT `body`, `tag`, `app`, `created`, `object-type`, `uri`, `guid`, `author-name`, `author-link`, `author-avatar` From c9100e1102114ebb4299e4252eb0a0a2095efeb3 Mon Sep 17 00:00:00 2001 From: Michael Vogel Date: Sat, 12 Mar 2016 19:19:20 +0100 Subject: [PATCH 035/211] Everything could work - needs some beautification and documentation --- include/diaspora2.php | 473 +++++++++++++----------------------------- 1 file changed, 142 insertions(+), 331 deletions(-) diff --git a/include/diaspora2.php b/include/diaspora2.php index 7aa0fc6989..3d12ef5bbf 100644 --- a/include/diaspora2.php +++ b/include/diaspora2.php @@ -123,53 +123,43 @@ class diaspora { $type = $fields->getName(); switch ($type) { - case "account_deletion": // Done - //return true; + case "account_deletion": return self::receive_account_deletion($importer, $fields); - case "comment": // Done - //return true; + case "comment": return self::receive_comment($importer, $sender, $fields); - case "conversation": // Done - //return true; + case "conversation": return self::receive_conversation($importer, $msg, $fields); - case "like": // Done - //return true; + case "like": return self::receive_like($importer, $sender, $fields); - case "message": // Done - //return true; + case "message": return self::receive_message($importer, $fields); case "participation": // Not implemented return self::receive_participation($importer, $fields); - case "photo": // Not needed + case "photo": // Not implemented return self::receive_photo($importer, $fields); case "poll_participation": // Not implemented return self::receive_poll_participation($importer, $fields); - case "profile": // Done - //return true; + case "profile": return self::receive_profile($importer, $fields); case "request": - //return true; return self::receive_request($importer, $fields); - case "reshare": // Done - //return true; + case "reshare": return self::receive_reshare($importer, $fields); - case "retraction": // Done - //return true; + case "retraction": return self::receive_retraction($importer, $sender, $fields); - case "status_message": // Done - //return true; + case "status_message": return self::receive_status_message($importer, $fields); default: @@ -1364,7 +1354,7 @@ EOT; $u = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1", intval($importer["uid"])); if($u) - $ret = diaspora_share($u[0], $contact_record); + $ret = self::send_share($u[0], $contact_record); } return true; @@ -1557,8 +1547,10 @@ EOT; case "StatusMessage": return self::item_retraction($importer, $contact, $data);; - case "Person": /// @todo an "unshare" shouldn't remove the contact - contact_remove($contact["id"]); + case "Person": + /// @todo What should we do with an "unshare"? + // Removing the contact isn't correct since we still can read the public items + //contact_remove($contact["id"]); return true; default: @@ -1862,41 +1854,43 @@ EOT; } } - return(($return_code) ? $return_code : (-1)); } - public static function send_share($me,$contact) { - $myaddr = self::get_my_handle($me); - $theiraddr = $contact["addr"]; - $data = array("XML" => array("post" => array("request" => array( - "sender_handle" => $myaddr, - "recipient_handle" => $theiraddr - )))); + private function build_and_transmit($owner, $contact, $type, $message, $public_batch = false, $guid = "") { + + $data = array("XML" => array("post" => array($type => $message))); $msg = xml::from_array($data, $xml); - $slap = self::build_message($msg, $me, $contact, $me["prvkey"], $contact["pubkey"]); + logger('message: '.$msg, LOGGER_DATA); + logger('send guid '.$guid, LOGGER_DEBUG); - return(self::transmit($owner,$contact,$slap, false)); + $slap = self::build_message($msg, $owner, $contact, $owner['uprvkey'], $contact['pubkey'], $public_batch); +die($slap); + $return_code = self::transmit($owner, $contact, $slap, $public_batch, false, $guid); + logger("guid: ".$item["guid"]." result ".$return_code, LOGGER_DEBUG); + + return $return_code; } - public static function send_unshare($me,$contact) { - $myaddr = self::get_my_handle($me); + public static function send_share($owner,$contact) { - $data = array("XML" => array("post" => array("retraction" => array( - "post_guid" => $me["guid"], - "diaspora_handle" => $myaddr, - "type" => "Person" - )))); + $message = array("sender_handle" => self::get_my_handle($owner), + "recipient_handle" => $contact["addr"]); - $msg = xml::from_array($data, $xml); + return self::build_and_transmit($owner, $contact, "request", $message); + } - $slap = self::build_message($msg, $me, $contact, $me["prvkey"], $contact["pubkey"]); + public static function send_unshare($owner,$contact) { - return(self::transmit($owner,$contact,$slap, false)); + $message = array("post_guid" => $owner["guid"], + "diaspora_handle" => self::get_my_handle($owner), + "type" => "Person"); + + return self::build_and_transmit($owner, $contact, "retraction", $message); } private function is_reshare($body) { @@ -1969,27 +1963,6 @@ EOT; public static function send_status($item, $owner, $contact, $public_batch = false) { $myaddr = self::get_my_handle($owner); - $theiraddr = $contact["addr"]; - - $title = $item["title"]; - $body = $item["body"]; - - // convert to markdown - $body = html_entity_decode(bb2diaspora($body)); - - // Adding the title - if(strlen($title)) - $body = "## ".html_entity_decode($title)."\n\n".$body; - - if ($item["attach"]) { - $cnt = preg_match_all('/href=\"(.*?)\"(.*?)title=\"(.*?)\"/ism', $item["attach"], $matches, PREG_SET_ORDER); - if(cnt) { - $body .= "\n".t("Attachments:")."\n"; - foreach($matches as $mtch) - $body .= "[".$mtch[3]."](".$mtch[1].")\n"; - } - } - $public = (($item["private"]) ? "false" : "true"); @@ -2005,8 +1978,27 @@ EOT; "created_at" => $created, "provider_display_name" => $item["app"]); - $data = array("XML" => array("post" => array("reshare" => $message))); + $type = "reshare"; } else { + $title = $item["title"]; + $body = $item["body"]; + + // convert to markdown + $body = html_entity_decode(bb2diaspora($body)); + + // Adding the title + if(strlen($title)) + $body = "## ".html_entity_decode($title)."\n\n".$body; + + if ($item["attach"]) { + $cnt = preg_match_all('/href=\"(.*?)\"(.*?)title=\"(.*?)\"/ism', $item["attach"], $matches, PREG_SET_ORDER); + if(cnt) { + $body .= "\n".t("Attachments:")."\n"; + foreach($matches as $mtch) + $body .= "[".$mtch[3]."](".$mtch[1].")\n"; + } + } + $location = array(); if ($item["location"] != "") @@ -2029,126 +2021,87 @@ EOT; if (count($location) == 0) unset($message["location"]); - $data = array("XML" => array("post" => array("status_message" => $message))); + $type = "status_message"; } - $msg = xml::from_array($data, $xml); - - logger("status: ".$owner["username"]." -> ".$contact["name"]." base message: ".$msg, LOGGER_DATA); - logger("send guid ".$item["guid"], LOGGER_DEBUG); - - $slap = self::build_message($msg, $owner, $contact, $owner["uprvkey"], $contact["pubkey"], $public_batch); - - $return_code = self::transmit($owner,$contact,$slap, false); - - logger("guid: ".$item["guid"]." result ".$return_code, LOGGER_DEBUG); - - return $return_code; + return self::build_and_transmit($owner, $contact, $type, $message, $public_batch, $item["guid"]); } - private function construct_like($item,$owner,$contact,$public_batch = false, $data = null) { + private function construct_like($item, $owner) { - if (is_array($data)) - $message = $data; - else { - $myaddr = self::get_my_handle($owner); + $myaddr = self::get_my_handle($owner); - $p = q("SELECT `guid`, `uri`, `parent-uri` FROM `item` WHERE `uri` = '%s' LIMIT 1", - dbesc($item["thr-parent"])); - if(!$p) - return false; + $p = q("SELECT `guid`, `uri`, `parent-uri` FROM `item` WHERE `uri` = '%s' LIMIT 1", + dbesc($item["thr-parent"])); + if(!$p) + return false; - $parent = $p[0]; + $parent = $p[0]; - $target_type = ($parent["uri"] === $parent["parent-uri"] ? "Post" : "Comment"); - $positive = "true"; + $target_type = ($parent["uri"] === $parent["parent-uri"] ? "Post" : "Comment"); + $positive = "true"; - $message = array("positive" => $positive, - "guid" => $item["guid"], - "target_type" => $target_type, - "parent_guid" => $parent["guid"], - "author_signature" => $authorsig, - "diaspora_handle" => $myaddr); - } - - $authorsig = self::get_signature($owner, $message); - - if ($message["author_signature"] == "") - $message["author_signature"] = $authorsig; - else - $message["parent_author_signature"] = $authorsig; - - $data = array("XML" => array("post" => array("like" => $message))); - - return xml::from_array($data, $xml); + return(array("positive" => $positive, + "guid" => $item["guid"], + "target_type" => $target_type, + "parent_guid" => $parent["guid"], + "author_signature" => $authorsig, + "diaspora_handle" => $myaddr)); } - private function construct_comment($item,$owner,$contact,$public_batch = false, $data = null) { + private function construct_comment($item, $owner) { - if (is_array($data)) - $message = $data; - else { - $myaddr = self::get_my_handle($owner); + $myaddr = self::get_my_handle($owner); - $p = q("SELECT `guid` FROM `item` WHERE `parent` = %d AND `id` = %d LIMIT 1", - intval($item["parent"]), - intval($item["parent"]) - ); + $p = q("SELECT `guid` FROM `item` WHERE `parent` = %d AND `id` = %d LIMIT 1", + intval($item["parent"]), + intval($item["parent"]) + ); - if (!$p) - return false; + if (!$p) + return false; - $parent = $p[0]; + $parent = $p[0]; - $text = html_entity_decode(bb2diaspora($item["body"])); + $text = html_entity_decode(bb2diaspora($item["body"])); - $message = array("guid" => $item["guid"], - "parent_guid" => $parent["guid"], - "author_signature" => "", - "text" => $text, - "diaspora_handle" => $myaddr); - } - - $authorsig = self::get_signature($owner, $message); - - if ($message["author_signature"] == "") - $message["author_signature"] = $authorsig; - else - $message["parent_author_signature"] = $authorsig; - - $data = array("XML" => array("post" => array("comment" => $message))); - - return xml::from_array($data, $xml); + return(array("guid" => $item["guid"], + "parent_guid" => $parent["guid"], + "author_signature" => "", + "text" => $text, + "diaspora_handle" => $myaddr)); } public static function send_followup($item,$owner,$contact,$public_batch = false) { - if($item['verb'] === ACTIVITY_LIKE) - $msg = self::construct_like($item, $owner, $contact, $public_batch); - else - $msg = self::construct_comment($item, $owner, $contact, $public_batch); + if($item['verb'] === ACTIVITY_LIKE) { + $message = self::construct_like($item, $owner); + $type = "like"; + } else { + $message = self::construct_comment($item, $owner); + $type = "comment"; + } - if (!$msg) - return $msg; + if (!$message) + return false; - logger("base message: ".$msg, LOGGER_DATA); - logger("send guid ".$item["guid"], LOGGER_DEBUG); + $message["author_signature"] = self::get_signature($owner, $message); - $slap = self::build_message($msg, $owner, $contact, $owner["uprvkey"], $contact["pubkey"], $public_batch); - - $return_code = self::transmit($owner, $contact, $slap, $public_batch, false, $item["guid"]); - - logger("guid: ".$item["guid"]." result ".$return_code, LOGGER_DEBUG); - - return $return_code; + return self::build_and_transmit($owner, $contact, $type, $message, $public_batch, $item["guid"]); } function send_relay($item, $owner, $contact, $public_batch = false) { - if ($item["deleted"]) + if ($item["deleted"]) { $sql_sign_id = "retract_iid"; - else + $type = "relayable_retraction"; + } elseif ($item['verb'] === ACTIVITY_LIKE) { $sql_sign_id = "iid"; + $type = "like"; + } else { + $sql_sign_id = "iid"; + $type = "comment"; + } // fetch the original signature if the relayable was created by a Diaspora // or DFRN user. @@ -2157,24 +2110,32 @@ EOT; intval($item["id"]) ); - if(count($r)) { - $orig_sign = $r[0]; - $signed_text = $orig_sign['signed_text']; - $authorsig = $orig_sign['signature']; - $handle = $orig_sign['signer']; + if (!$r) + return self::send_followup($item, $owner, $contact, $public_batch); + + $orig_sign = $r[0]; + + // Old way - can be removed for the master branch + if ($orig_sign['signed_text'] AND $orig_sign['signature'] AND $orig_sign['signer']) { // Split the signed text - $signed_parts = explode(";", $signed_text); + $signed_parts = explode(";", $orig_sign['signed_text']); - if ($item['verb'] === ACTIVITY_LIKE) { - $data = array("positive" => $signed_parts[0], + if ($item["deleted"]) + $message = array("parent_author_signature" => "", + "target_guid" => $signed_parts[0], + "target_type" => $signed_parts[1], + "sender_handle" => $orig_sign['signer'], + "target_author_signature" => $orig_sign['signature']); + elseif ($item['verb'] === ACTIVITY_LIKE) + $message = array("positive" => $signed_parts[0], "guid" => $signed_parts[1], "target_type" => $signed_parts[2], "parent_guid" => $signed_parts[3], "parent_author_signature" => "", "author_signature" => $orig_sign['signature'], "diaspora_handle" => $signed_parts[4]); - } else { + else { // Remove the comment guid $guid = array_shift($signed_parts); @@ -2187,154 +2148,26 @@ EOT; // Glue the parts together $text = implode(";", $signed_parts); - $data = array("guid" => $guid, + $message = array("guid" => $guid, "parent_guid" => $parent_guid, "parent_author_signature" => "", "author_signature" => $orig_sign['signature'], "text" => implode(";", $signed_parts), "diaspora_handle" => $handle); } + } else { // New way + $message = json_decode($orig_sign['signed_text']); } - if ($item['deleted']) - ; // Relayed Retraction - elseif($item['verb'] === ACTIVITY_LIKE) - $msg = self::construct_like($item, $owner, $contact, $public_batch, $data); - else - $msg = self::construct_comment($item, $owner, $contact, $public_batch, $data); -die($msg); + if ($item["deleted"]) { + $signed_text = $message["target_guid"].';'.$message["target_type"]; + $message["parent_author_signature"] = base64_encode(rsa_sign($signed_text, $owner["uprvkey"], "sha256")); + } else + $message["parent_author_signature"] = self::get_signature($owner, $message); - logger('base message: '.$msg, LOGGER_DATA); - logger('send guid '.$item['guid'], LOGGER_DEBUG); - - $slap = self::build_message($msg,$owner, $contact, $owner['uprvkey'], $contact['pubkey'], $public_batch); - - $return_code = self::transmit($owner, $contact, $slap, $public_batch, false, $item['guid']); - - logger("guid: ".$item["guid"]." result ".$return_code, LOGGER_DEBUG); - - return $return_code; + return self::build_and_transmit($owner, $contact, $type, $message, $public_batch, $item["guid"]); } -/* - // Diaspora doesn't support threaded comments, but some - // versions of Diaspora (i.e. Diaspora-pistos) support - // likes on comments - if($item['verb'] === ACTIVITY_LIKE && $item['thr-parent']) { - $p = q("select guid, type, uri, `parent-uri` from item where uri = '%s' limit 1", - dbesc($item['thr-parent']) - ); - } - else { - // The first item in the `item` table with the parent id is the parent. However, MySQL doesn't always - // return the items ordered by `item`.`id`, in which case the wrong item is chosen as the parent. - // The only item with `parent` and `id` as the parent id is the parent item. - $p = q("select guid, type, uri, `parent-uri` from item where parent = %d and id = %d limit 1", - intval($item['parent']), - intval($item['parent']) - ); - } - if(count($p)) - $parent = $p[0]; - else - return; - - $like = false; - $relay_retract = false; - $sql_sign_id = 'iid'; - if( $item['deleted']) { - $relay_retract = true; - - $target_type = ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment'); - - $sql_sign_id = 'retract_iid'; - $tpl = get_markup_template('diaspora_relayable_retraction.tpl'); - } - elseif($item['verb'] === ACTIVITY_LIKE) { - $like = true; - - $target_type = ( $parent['uri'] === $parent['parent-uri'] ? 'Post' : 'Comment'); -// $positive = (($item['deleted']) ? 'false' : 'true'); - $positive = 'true'; - - $tpl = get_markup_template('diaspora_like_relay.tpl'); - } - else { // item is a comment - $tpl = get_markup_template('diaspora_comment_relay.tpl'); - } - - - // fetch the original signature if the relayable was created by a Diaspora - // or DFRN user. Relayables for other networks are not supported. - - $r = q("SELECT `signed_text`, `signature`, `signer` FROM `sign` WHERE " . $sql_sign_id . " = %d LIMIT 1", - intval($item['id']) - ); - if(count($r)) { - $orig_sign = $r[0]; - $signed_text = $orig_sign['signed_text']; - $authorsig = $orig_sign['signature']; - $handle = $orig_sign['signer']; - - // 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 - - $body = $item['body']; - $text = html_entity_decode(bb2diaspora($body)); - - $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; - - $authorsig = base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256')); - } - - // Sign the relayable with the top-level owner's signature - $parentauthorsig = base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256')); - - $msg = replace_macros($tpl,array( - '$guid' => xmlify($item['guid']), - '$parent_guid' => xmlify($parent['guid']), - '$target_type' =>xmlify($target_type), - '$authorsig' => xmlify($authorsig), - '$parentsig' => xmlify($parentauthorsig), - '$body' => xmlify($text), - '$positive' => xmlify($positive), - '$handle' => xmlify($handle) - )); - - 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,false,$item['guid'])); -*/ - public static function send_retraction($item, $owner, $contact, $public_batch = false) { $myaddr = self::get_my_handle($owner); @@ -2355,21 +2188,10 @@ die($msg); "sender_handle" => $myaddr, "target_author_signature" => base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256'))); - $data = array("XML" => array("post" => array($msg_type => $message))); - $msg = xml::from_array($data, $xml); - - logger("send guid ".$item["guid"], LOGGER_DEBUG); - - $slap = self::build_message($msg, $owner, $contact, $owner["uprvkey"], $contact["pubkey"], $public_batch); - - $return_code = self::transmit($owner, $contact, $slap, $public_batch, false, $item["guid"]); - - logger("guid: ".$item["guid"]." result ".$return_code, LOGGER_DEBUG); - - return $return_code; + return self::build_and_transmit($owner, $contact, $msg_type, $message, $public_batch, $item["guid"]); } - public static function send_mail($item,$owner,$contact) { + public static function send_mail($item, $owner, $contact) { $myaddr = self::get_my_handle($owner); @@ -2396,7 +2218,6 @@ die($msg); $created = datetime_convert("UTC", "UTC", $item["created"], 'Y-m-d H:i:s \U\T\C'); $signed_text = $item["guid"].";".$cnv["guid"].";".$body.";".$created.";".$myaddr.";".$cnv['guid']; - $sig = base64_encode(rsa_sign($signed_text, $owner["uprvkey"], "sha256")); $msg = array( @@ -2410,9 +2231,10 @@ die($msg); "conversation_guid" => $cnv["guid"] ); - if ($item["reply"]) - $data = array("XML" => array("post" => array("message" => $msg))); - else { + if ($item["reply"]) { + $message = $msg; + $type = "message"; + } else { $message = array("guid" => $cnv["guid"], "subject" => $cnv["subject"], "created_at" => datetime_convert("UTC", "UTC", $cnv['created'], 'Y-m-d H:i:s \U\T\C'), @@ -2420,21 +2242,10 @@ die($msg); "diaspora_handle" => $cnv["creator"], "participant_handles" => $cnv["recips"]); - $data = array("XML" => array("post" => array("conversation" => $message))); + $type = "conversation"; } - $xmsg = xml::from_array($data, $xml); - - logger("conversation: ".print_r($xmsg,true), LOGGER_DATA); - logger("send guid ".$item["guid"], LOGGER_DEBUG); - - $slap = self::build_message($xmsg, $owner, $contact, $owner["uprvkey"], $contact["pubkey"], false); - - $return_code = self::transmit($owner, $contact, $slap, false, false, $item["guid"]); - - logger("guid: ".$item["guid"]." result ".$return_code, LOGGER_DEBUG); - - return $return_code; + return self::build_and_transmit($owner, $contact, $type, $message, false, $item["guid"]); } } ?> From 3e299aa7bf42f1645fddeb426ff9b7140fdbc492 Mon Sep 17 00:00:00 2001 From: Michael Vogel Date: Sun, 13 Mar 2016 07:10:24 +0100 Subject: [PATCH 036/211] Some code cleaning, changes to the xml generation --- include/diaspora2.php | 183 ++++++++++++++++++++++++------------------ include/xml.php | 10 ++- 2 files changed, 114 insertions(+), 79 deletions(-) diff --git a/include/diaspora2.php b/include/diaspora2.php index 3d12ef5bbf..97d5ecee9f 100644 --- a/include/diaspora2.php +++ b/include/diaspora2.php @@ -20,7 +20,7 @@ require_once("include/datetime.php"); */ class diaspora { - public static function fetch_relay() { + public static function relay_list() { $serverdata = get_config("system", "relay_server"); if ($serverdata == "") @@ -277,13 +277,13 @@ class diaspora { return false; if (isset($parent_author_signature)) { - $key = self::get_key($msg["author"]); + $key = self::key($msg["author"]); if (!rsa_verify($signed_data, $parent_author_signature, $key, "sha256")) return false; } - $key = self::get_key($fields->author); + $key = self::key($fields->author); return rsa_verify($signed_data, $author_signature, $key, "sha256"); } @@ -295,10 +295,10 @@ class diaspora { * * @return string The public key */ - private function get_key($handle) { + private function key($handle) { logger("Fetching diaspora key for: ".$handle); - $r = self::get_person_by_handle($handle); + $r = self::person_by_handle($handle); if($r) return $r["pubkey"]; @@ -312,7 +312,7 @@ class diaspora { * * @return array the queried data */ - private function get_person_by_handle($handle) { + private function person_by_handle($handle) { $r = q("SELECT * FROM `fcontact` WHERE `network` = '%s' AND `addr` = '%s' LIMIT 1", dbesc(NETWORK_DIASPORA), @@ -407,7 +407,33 @@ class diaspora { return $r; } - private function get_contact_by_handle($uid, $handle) { + public static function handle_from_contact($contact_id) { + $handle = False; + + logger("contact id is ".$contact_id, LOGGER_DEBUG); + + $r = q("SELECT `network`, `addr`, `self`, `url`, `nick` FROM `contact` WHERE `id` = %d", + intval($contact_id) + ); + if($r) { + $contact = $r[0]; + + logger("contact 'self' = ".$contact['self']." 'url' = ".$contact['url'], LOGGER_DEBUG); + + if($contact['addr'] != "") + $handle = $contact['addr']; + elseif(($contact['network'] === NETWORK_DFRN) || ($contact['self'] == 1)) { + $baseurl_start = strpos($contact['url'],'://') + 3; + $baseurl_length = strpos($contact['url'],'/profile') - $baseurl_start; // allows installations in a subdirectory--not sure how Diaspora will handle + $baseurl = substr($contact['url'], $baseurl_start, $baseurl_length); + $handle = $contact['nick'].'@'.$baseurl; + } + } + + return $handle; + } + + private function contact_by_handle($uid, $handle) { $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `addr` = '%s' LIMIT 1", intval($uid), dbesc($handle) @@ -459,8 +485,8 @@ class diaspora { return false; } - private function get_allowed_contact_by_handle($importer, $handle, $is_comment = false) { - $contact = self::get_contact_by_handle($importer["uid"], $handle); + private function allowed_contact_by_handle($importer, $handle, $is_comment = false) { + $contact = self::contact_by_handle($importer["uid"], $handle); if (!$contact) { logger("A Contact for handle ".$handle." and user ".$importer["uid"]." was not found"); return false; @@ -505,7 +531,7 @@ class diaspora { logger("Trying to fetch item ".$guid." from ".$server, LOGGER_DEBUG); - $msg = self::fetch_message($guid, $server); + $msg = self::message($guid, $server); if (!$msg) return false; @@ -516,7 +542,7 @@ class diaspora { return self::dispatch_public($msg); } - private function fetch_message($guid, $server, $level = 0) { + private function message($guid, $server, $level = 0) { if ($level > 5) return false; @@ -534,10 +560,10 @@ class diaspora { if ($source_xml->post->reshare) { // Reshare of a reshare - old Diaspora version - return self::fetch_message($source_xml->post->reshare->root_guid, $server, ++$level); + return self::message($source_xml->post->reshare->root_guid, $server, ++$level); } elseif ($source_xml->getName() == "reshare") { // Reshare of a reshare - new Diaspora version - return self::fetch_message($source_xml->root_guid, $server, ++$level); + return self::message($source_xml->root_guid, $server, ++$level); } $author = ""; @@ -554,12 +580,12 @@ class diaspora { $msg = array("message" => $x, "author" => $author); - $msg["key"] = self::get_key($msg["author"]); + $msg["key"] = self::key($msg["author"]); return $msg; } - private function fetch_parent_item($uid, $guid, $author, $contact) { + private function parent_item($uid, $guid, $author, $contact) { $r = q("SELECT `id`, `body`, `wall`, `uri`, `private`, `origin`, `author-name`, `author-link`, `author-avatar`, `owner-name`, `owner-link`, `owner-avatar` @@ -570,7 +596,7 @@ class diaspora { $result = self::store_by_guid($guid, $contact["url"], $uid); if (!$result) { - $person = self::get_person_by_handle($author); + $person = self::person_by_handle($author); $result = self::store_by_guid($guid, $person["url"], $uid); } @@ -592,7 +618,7 @@ class diaspora { return $r[0]; } - private function get_author_contact_by_url($contact, $person, $uid) { + private function author_contact_by_url($contact, $person, $uid) { $r = q("SELECT `id`, `network` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d LIMIT 1", dbesc(normalise_link($person["url"])), intval($uid)); @@ -636,7 +662,7 @@ class diaspora { private function receive_account_deletion($importer, $data) { $author = notags(unxmlify($data->author)); - $contact = self::get_contact_by_handle($importer["uid"], $author); + $contact = self::contact_by_handle($importer["uid"], $author); if (!$contact) { logger("cannot find contact for author: ".$author); return false; @@ -653,25 +679,25 @@ class diaspora { $text = unxmlify($data->text); $author = notags(unxmlify($data->author)); - $contact = self::get_allowed_contact_by_handle($importer, $sender, true); + $contact = self::allowed_contact_by_handle($importer, $sender, true); if (!$contact) return false; if (self::message_exists($importer["uid"], $guid)) return false; - $parent_item = self::fetch_parent_item($importer["uid"], $parent_guid, $author, $contact); + $parent_item = self::parent_item($importer["uid"], $parent_guid, $author, $contact); if (!$parent_item) return false; - $person = self::get_person_by_handle($author); + $person = self::person_by_handle($author); if (!is_array($person)) { logger("unable to find author details"); return false; } // Fetch the contact id - if we know this contact - $author_contact = self::get_author_contact_by_url($contact, $person, $importer["uid"]); + $author_contact = self::author_contact_by_url($contact, $person, $importer["uid"]); $datarray = array(); @@ -763,7 +789,7 @@ class diaspora { $person = $contact; $key = $msg["key"]; } else { - $person = self::get_person_by_handle($msg_author); + $person = self::person_by_handle($msg_author); if (is_array($person) && x($person, "pubkey")) $key = $person["pubkey"]; @@ -852,7 +878,7 @@ class diaspora { return false; } - $contact = self::get_allowed_contact_by_handle($importer, $msg["author"], true); + $contact = self::allowed_contact_by_handle($importer, $msg["author"], true); if (!$contact) return false; @@ -907,22 +933,17 @@ class diaspora { private function construct_like_object($importer, $parent_item) { $objtype = ACTIVITY_OBJ_NOTE; - $link = xmlify(''."\n") ; + $link = ''; $parent_body = $parent_item["body"]; - $obj = <<< EOT + $xmldata = array("object" => array("type" => $objtype, + "local" => "1", + "id" => $parent_item["uri"], + "link" => $link, + "title" => "", + "content" => $parent_body)); - - $objtype - 1 - {$parent_item["uri"]} - $link - - $parent_body - -EOT; - - return $obj; + return xml::from_array($xmldata, $xml, true); } private function receive_like($importer, $sender, $data) { @@ -937,25 +958,25 @@ EOT; if (!in_array($parent_type, array("Post", "Comment"))) return false; - $contact = self::get_allowed_contact_by_handle($importer, $sender, true); + $contact = self::allowed_contact_by_handle($importer, $sender, true); if (!$contact) return false; if (self::message_exists($importer["uid"], $guid)) return false; - $parent_item = self::fetch_parent_item($importer["uid"], $parent_guid, $author, $contact); + $parent_item = self::parent_item($importer["uid"], $parent_guid, $author, $contact); if (!$parent_item) return false; - $person = self::get_person_by_handle($author); + $person = self::person_by_handle($author); if (!is_array($person)) { logger("unable to find author details"); return false; } // Fetch the contact id - if we know this contact - $author_contact = self::get_author_contact_by_url($contact, $person, $importer["uid"]); + $author_contact = self::author_contact_by_url($contact, $person, $importer["uid"]); // "positive" = "false" would be a Dislike - wich isn't currently supported by Diaspora // We would accept this anyhow. @@ -1019,7 +1040,7 @@ EOT; $author = notags(unxmlify($data->author)); $conversation_guid = notags(unxmlify($data->conversation_guid)); - $contact = self::get_allowed_contact_by_handle($importer, $author, true); + $contact = self::allowed_contact_by_handle($importer, $author, true); if (!$contact) return false; @@ -1041,7 +1062,7 @@ EOT; $body = diaspora2bb($text); $message_uri = $author.":".$guid; - $person = self::get_person_by_handle($author); + $person = self::person_by_handle($author); if (!$person) { logger("unable to find author details"); return false; @@ -1100,7 +1121,7 @@ EOT; private function receive_profile($importer, $data) { $author = notags(unxmlify($data->author)); - $contact = self::get_contact_by_handle($importer["uid"], $author); + $contact = self::contact_by_handle($importer["uid"], $author); if (!$contact) return; @@ -1254,7 +1275,7 @@ EOT; if (!$author || !$recipient) return; - $contact = self::get_contact_by_handle($importer["uid"],$author); + $contact = self::contact_by_handle($importer["uid"],$author); if($contact) { @@ -1265,7 +1286,7 @@ EOT; return true; } - $ret = self::get_person_by_handle($author); + $ret = self::person_by_handle($author); if (!$ret || ($ret["network"] != NETWORK_DIASPORA)) { logger("Cannot resolve diaspora handle ".$author ." for ".$recipient); @@ -1295,7 +1316,7 @@ EOT; // find the contact record we just created - $contact_record = self::get_contact_by_handle($importer["uid"],$author); + $contact_record = self::contact_by_handle($importer["uid"],$author); if (!$contact_record) { logger("unable to locate newly created contact record."); @@ -1360,7 +1381,7 @@ EOT; return true; } - private function get_original_item($guid, $orig_author, $author) { + private function original_item($guid, $orig_author, $author) { // Do we already have this item? $r = q("SELECT `body`, `tag`, `app`, `created`, `object-type`, `uri`, `guid`, @@ -1424,14 +1445,14 @@ EOT; $public = notags(unxmlify($data->public)); $created_at = notags(unxmlify($data->created_at)); - $contact = self::get_allowed_contact_by_handle($importer, $author, false); + $contact = self::allowed_contact_by_handle($importer, $author, false); if (!$contact) return false; if (self::message_exists($importer["uid"], $guid)) return false; - $original_item = self::get_original_item($root_guid, $root_author, $author); + $original_item = self::original_item($root_guid, $root_author, $author); if (!$original_item) return false; @@ -1482,7 +1503,7 @@ EOT; $target_guid = notags(unxmlify($data->target_guid)); $author = notags(unxmlify($data->author)); - $person = self::get_person_by_handle($author); + $person = self::person_by_handle($author); if (!is_array($person)) { logger("unable to find author detail for ".$author); return false; @@ -1533,7 +1554,7 @@ EOT; private function receive_retraction($importer, $sender, $data) { $target_type = notags(unxmlify($data->target_type)); - $contact = self::get_contact_by_handle($importer["uid"], $sender); + $contact = self::contact_by_handle($importer["uid"], $sender); if (!$contact) { logger("cannot find contact for sender: ".$sender." and user ".$importer["uid"]); return false; @@ -1575,7 +1596,7 @@ EOT; // print_r($poll); // die("poll!\n"); //} - $contact = self::get_allowed_contact_by_handle($importer, $author, false); + $contact = self::allowed_contact_by_handle($importer, $author, false); if (!$contact) return false; @@ -1652,7 +1673,7 @@ EOT; * Here come all the functions that are needed to transmit data with the Diaspora protocol * *******************************************************************************************/ - private function get_my_handle($me) { + private function my_handle($me) { if ($contact["addr"] != "") return $contact["addr"]; @@ -1665,7 +1686,7 @@ EOT; logger("Message: ".$msg, LOGGER_DATA); - $handle = self::get_my_handle($user); + $handle = self::my_handle($user); $b64url_data = base64url_encode($msg); @@ -1694,7 +1715,7 @@ $magic_env = <<< EOT EOT; - +die($magic_env."\n"); logger("magic_env: ".$magic_env, LOGGER_DATA); return $magic_env; } @@ -1720,7 +1741,7 @@ EOT; $outer_iv = random_string(16); $b_outer_iv = base64_encode($outer_iv); - $handle = self::get_my_handle($user); + $handle = self::my_handle($user); $padded_data = pkcs5_pad($msg,16); $inner_encrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $inner_aes_key, $padded_data, MCRYPT_MODE_CBC, $inner_iv); @@ -1740,14 +1761,11 @@ EOT; $signature = rsa_sign($signable_data,$prvkey); $sig = base64url_encode($signature); -$decrypted_header = <<< EOT - - $b_inner_iv - $b_inner_aes_key - $handle - -EOT; + $xmldata = array("decrypted_header" => array("iv" => $b_inner_iv, + "aes_key" => $b_inner_aes_key, + "author_id" => $handle)); + $decrypted_header = xml::from_array($xmldata, $xml, true); $decrypted_header = pkcs5_pad($decrypted_header,16); $ciphertext = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $outer_aes_key, $decrypted_header, MCRYPT_MODE_CBC, $outer_iv); @@ -1765,6 +1783,15 @@ EOT; "ciphertext" => base64_encode($ciphertext))); $cipher_json = base64_encode($encrypted_header_json_object); + $xml = nul; + $xmldata = array("diaspora" => array("encrypted_header" => $cipher_json, + "me:env" => array("me:encoding" => "base64url", + "me:alg" => "RSA-SHA256", + "me:data" => $data, + "me:sig" => $sig))); + $encrypted_header = xml::from_array($xmldata, $xml, true); +echo $encrypted_header."\n"; + $encrypted_header = "".$cipher_json.""; $magic_env = <<< EOT @@ -1779,6 +1806,7 @@ $magic_env = <<< EOT EOT; +die($magic_env."\n"); logger("magic_env: ".$magic_env, LOGGER_DATA); return $magic_env; @@ -1796,7 +1824,7 @@ EOT; return $slap; } - private function get_signature($owner, $message) { + private function signature($owner, $message) { $sigmsg = $message; unset($sigmsg["author_signature"]); unset($sigmsg["parent_author_signature"]); @@ -1806,7 +1834,7 @@ EOT; return base64_encode(rsa_sign($signed_text, $owner["uprvkey"], "sha256")); } - private function transmit($owner, $contact, $slap, $public_batch, $queue_run=false, $guid = "") { + public static function transmit($owner, $contact, $slap, $public_batch, $queue_run=false, $guid = "") { $a = get_app(); @@ -1878,7 +1906,7 @@ die($slap); public static function send_share($owner,$contact) { - $message = array("sender_handle" => self::get_my_handle($owner), + $message = array("sender_handle" => self::my_handle($owner), "recipient_handle" => $contact["addr"]); return self::build_and_transmit($owner, $contact, "request", $message); @@ -1887,7 +1915,7 @@ die($slap); public static function send_unshare($owner,$contact) { $message = array("post_guid" => $owner["guid"], - "diaspora_handle" => self::get_my_handle($owner), + "diaspora_handle" => self::my_handle($owner), "type" => "Person"); return self::build_and_transmit($owner, $contact, "retraction", $message); @@ -1924,7 +1952,7 @@ die($slap); dbesc($guid), NETWORK_DFRN, NETWORK_DIASPORA); if ($r) { $ret= array(); - $ret["root_handle"] = diaspora_handle_from_contact($r[0]["contact-id"]); + $ret["root_handle"] = self::handle_from_contact($r[0]["contact-id"]); $ret["root_guid"] = $guid; return($ret); } @@ -1962,7 +1990,7 @@ die($slap); public static function send_status($item, $owner, $contact, $public_batch = false) { - $myaddr = self::get_my_handle($owner); + $myaddr = self::my_handle($owner); $public = (($item["private"]) ? "false" : "true"); @@ -2029,7 +2057,7 @@ die($slap); private function construct_like($item, $owner) { - $myaddr = self::get_my_handle($owner); + $myaddr = self::my_handle($owner); $p = q("SELECT `guid`, `uri`, `parent-uri` FROM `item` WHERE `uri` = '%s' LIMIT 1", dbesc($item["thr-parent"])); @@ -2051,7 +2079,7 @@ die($slap); private function construct_comment($item, $owner) { - $myaddr = self::get_my_handle($owner); + $myaddr = self::my_handle($owner); $p = q("SELECT `guid` FROM `item` WHERE `parent` = %d AND `id` = %d LIMIT 1", intval($item["parent"]), @@ -2085,7 +2113,7 @@ die($slap); if (!$message) return false; - $message["author_signature"] = self::get_signature($owner, $message); + $message["author_signature"] = self::signature($owner, $message); return self::build_and_transmit($owner, $contact, $type, $message, $public_batch, $item["guid"]); } @@ -2115,7 +2143,8 @@ die($slap); $orig_sign = $r[0]; - // Old way - can be removed for the master branch + // Old way - is used by the internal Friendica functions + /// @todo Change all signatur storing functions to the new format if ($orig_sign['signed_text'] AND $orig_sign['signature'] AND $orig_sign['signer']) { // Split the signed text @@ -2163,14 +2192,14 @@ die($slap); $signed_text = $message["target_guid"].';'.$message["target_type"]; $message["parent_author_signature"] = base64_encode(rsa_sign($signed_text, $owner["uprvkey"], "sha256")); } else - $message["parent_author_signature"] = self::get_signature($owner, $message); + $message["parent_author_signature"] = self::signature($owner, $message); return self::build_and_transmit($owner, $contact, $type, $message, $public_batch, $item["guid"]); } public static function send_retraction($item, $owner, $contact, $public_batch = false) { - $myaddr = self::get_my_handle($owner); + $myaddr = self::my_handle($owner); // Check whether the retraction is for a top-level post or whether it's a relayable if ($item["uri"] !== $item["parent-uri"]) { @@ -2193,7 +2222,7 @@ die($slap); public static function send_mail($item, $owner, $contact) { - $myaddr = self::get_my_handle($owner); + $myaddr = self::my_handle($owner); $r = q("SELECT * FROM `conv` WHERE `id` = %d AND `uid` = %d LIMIT 1", intval($item["convid"]), diff --git a/include/xml.php b/include/xml.php index 9c458dab12..91480dc03b 100644 --- a/include/xml.php +++ b/include/xml.php @@ -4,7 +4,7 @@ * */ class xml { - function from_array($array, &$xml) { + function from_array($array, &$xml, $remove_header = false) { if (!is_object($xml)) { foreach($array as $key => $value) { @@ -14,7 +14,13 @@ class xml { $dom = dom_import_simplexml($root)->ownerDocument; $dom->formatOutput = true; $xml = $dom; - return $dom->saveXML(); + + $xml_text = $dom->saveXML(); + + if ($remove_header) + $xml_text = trim(substr($xml_text, 21)); + + return $xml_text; } } From 7dcf6c21165d37e6b4a49d30a65233842e8a117c Mon Sep 17 00:00:00 2001 From: Michael Vogel Date: Sun, 13 Mar 2016 09:57:44 +0100 Subject: [PATCH 037/211] XML generation is now improved --- include/diaspora2.php | 158 +++++++++++++++++++----------------------- include/xml.php | 42 +++++++++-- 2 files changed, 105 insertions(+), 95 deletions(-) diff --git a/include/diaspora2.php b/include/diaspora2.php index 97d5ecee9f..da772d68bd 100644 --- a/include/diaspora2.php +++ b/include/diaspora2.php @@ -729,7 +729,6 @@ class diaspora { self::fetch_guid($datarray); $message_id = item_store($datarray); - // print_r($datarray); // If we are the origin of the parent we store the original data and notify our followers if($message_id AND $parent_item["origin"]) { @@ -1013,7 +1012,6 @@ class diaspora { $datarray["body"] = self::construct_like_body($contact, $parent_item, $guid); $message_id = item_store($datarray); - // print_r($datarray); // If we are the origin of the parent we store the original data and notify our followers if($message_id AND $parent_item["origin"]) { @@ -1493,7 +1491,6 @@ class diaspora { self::fetch_guid($datarray); $message_id = item_store($datarray); - // print_r($datarray); return $message_id; } @@ -1662,16 +1659,15 @@ class diaspora { self::fetch_guid($datarray); $message_id = item_store($datarray); - // print_r($datarray); logger("Stored item with message id ".$message_id, LOGGER_DEBUG); return $message_id; } - /******************************************************************************************* - * Here come all the functions that are needed to transmit data with the Diaspora protocol * - *******************************************************************************************/ + /****************************************************************************************** + * Here are all the functions that are needed to transmit data with the Diaspora protocol * + ******************************************************************************************/ private function my_handle($me) { if ($contact["addr"] != "") @@ -1701,21 +1697,18 @@ class diaspora { $signature = rsa_sign($signable_data,$prvkey); $sig = base64url_encode($signature); -$magic_env = <<< EOT - - -
      - $handle -
      - - base64url - RSA-SHA256 - $data - $sig - -
      -EOT; -die($magic_env."\n"); + $xmldata = array("diaspora" => array("header" => array("author_id" => $handle), + "me:env" => array("me:encoding" => "base64url", + "me:alg" => "RSA-SHA256", + "me:data" => $data, + "@attributes" => array("type" => "application/xml"), + "me:sig" => $sig))); + + $namespaces = array("" => "https://joindiaspora.com/protocol", + "me" => "http://salmon-protocol.org/ns/magic-env"); + + $magic_env = xml::from_array($xmldata, $xml, false, $namespaces); + logger("magic_env: ".$magic_env, LOGGER_DATA); return $magic_env; } @@ -1783,30 +1776,17 @@ die($magic_env."\n"); "ciphertext" => base64_encode($ciphertext))); $cipher_json = base64_encode($encrypted_header_json_object); - $xml = nul; $xmldata = array("diaspora" => array("encrypted_header" => $cipher_json, "me:env" => array("me:encoding" => "base64url", "me:alg" => "RSA-SHA256", "me:data" => $data, + "@attributes" => array("type" => "application/xml"), "me:sig" => $sig))); - $encrypted_header = xml::from_array($xmldata, $xml, true); -echo $encrypted_header."\n"; - $encrypted_header = "".$cipher_json.""; + $namespaces = array("" => "https://joindiaspora.com/protocol", + "me" => "http://salmon-protocol.org/ns/magic-env"); -$magic_env = <<< EOT - - - $encrypted_header - - base64url - RSA-SHA256 - $data - $sig - - -EOT; -die($magic_env."\n"); + $magic_env = xml::from_array($xmldata, $xml, false, $namespaces); logger("magic_env: ".$magic_env, LOGGER_DATA); return $magic_env; @@ -1896,7 +1876,7 @@ die($magic_env."\n"); logger('send guid '.$guid, LOGGER_DEBUG); $slap = self::build_message($msg, $owner, $contact, $owner['uprvkey'], $contact['pubkey'], $public_batch); -die($slap); + $return_code = self::transmit($owner, $contact, $slap, $public_batch, false, $guid); logger("guid: ".$item["guid"]." result ".$return_code, LOGGER_DEBUG); @@ -2118,7 +2098,49 @@ die($slap); return self::build_and_transmit($owner, $contact, $type, $message, $public_batch, $item["guid"]); } - function send_relay($item, $owner, $contact, $public_batch = false) { + private function message_from_signatur($item, $signature) { + + // Split the signed text + $signed_parts = explode(";", $signature['signed_text']); + + if ($item["deleted"]) + $message = array("parent_author_signature" => "", + "target_guid" => $signed_parts[0], + "target_type" => $signed_parts[1], + "sender_handle" => $signature['signer'], + "target_author_signature" => $signature['signature']); + elseif ($item['verb'] === ACTIVITY_LIKE) + $message = array("positive" => $signed_parts[0], + "guid" => $signed_parts[1], + "target_type" => $signed_parts[2], + "parent_guid" => $signed_parts[3], + "parent_author_signature" => "", + "author_signature" => $signature['signature'], + "diaspora_handle" => $signed_parts[4]); + else { + // Remove the comment guid + $guid = array_shift($signed_parts); + + // Remove the parent guid + $parent_guid = array_shift($signed_parts); + + // Remove the handle + $handle = array_pop($signed_parts); + + // Glue the parts together + $text = implode(";", $signed_parts); + + $message = array("guid" => $guid, + "parent_guid" => $parent_guid, + "parent_author_signature" => "", + "author_signature" => $signature['signature'], + "text" => implode(";", $signed_parts), + "diaspora_handle" => $handle); + } + return $message; + } + + public static function send_relay($item, $owner, $contact, $public_batch = false) { if ($item["deleted"]) { $sql_sign_id = "retract_iid"; @@ -2131,62 +2153,22 @@ die($slap); $type = "comment"; } - // fetch the original signature if the relayable was created by a Diaspora - // or DFRN user. + // fetch the original signature $r = q("SELECT `signed_text`, `signature`, `signer` FROM `sign` WHERE `".$sql_sign_id."` = %d LIMIT 1", - intval($item["id"]) - ); + intval($item["id"])); if (!$r) return self::send_followup($item, $owner, $contact, $public_batch); - $orig_sign = $r[0]; + $signature = $r[0]; // Old way - is used by the internal Friendica functions /// @todo Change all signatur storing functions to the new format - if ($orig_sign['signed_text'] AND $orig_sign['signature'] AND $orig_sign['signer']) { - - // Split the signed text - $signed_parts = explode(";", $orig_sign['signed_text']); - - if ($item["deleted"]) - $message = array("parent_author_signature" => "", - "target_guid" => $signed_parts[0], - "target_type" => $signed_parts[1], - "sender_handle" => $orig_sign['signer'], - "target_author_signature" => $orig_sign['signature']); - elseif ($item['verb'] === ACTIVITY_LIKE) - $message = array("positive" => $signed_parts[0], - "guid" => $signed_parts[1], - "target_type" => $signed_parts[2], - "parent_guid" => $signed_parts[3], - "parent_author_signature" => "", - "author_signature" => $orig_sign['signature'], - "diaspora_handle" => $signed_parts[4]); - else { - // Remove the comment guid - $guid = array_shift($signed_parts); - - // Remove the parent guid - $parent_guid = array_shift($signed_parts); - - // Remove the handle - $handle = array_pop($signed_parts); - - // Glue the parts together - $text = implode(";", $signed_parts); - - $message = array("guid" => $guid, - "parent_guid" => $parent_guid, - "parent_author_signature" => "", - "author_signature" => $orig_sign['signature'], - "text" => implode(";", $signed_parts), - "diaspora_handle" => $handle); - } - } else { // New way - $message = json_decode($orig_sign['signed_text']); - } + if ($signature['signed_text'] AND $signature['signature'] AND $signature['signer']) + $message = self::message_from_signatur($item, $signature); + else // New way + $message = json_decode($signature['signed_text']); if ($item["deleted"]) { $signed_text = $message["target_guid"].';'.$message["target_type"]; diff --git a/include/xml.php b/include/xml.php index 91480dc03b..c2313648ce 100644 --- a/include/xml.php +++ b/include/xml.php @@ -4,12 +4,15 @@ * */ class xml { - function from_array($array, &$xml, $remove_header = false) { + function from_array($array, &$xml, $remove_header = false, $namespaces = array(), $root = true) { - if (!is_object($xml)) { + if ($root) { foreach($array as $key => $value) { + foreach ($namespaces AS $nskey => $nsvalue) + $key .= " xmlns".($nskey == "" ? "":":").$nskey.'="'.$nsvalue.'"'; + $root = new SimpleXMLElement("<".$key."/>"); - self::from_array($value, $root); + self::from_array($value, $root, $remove_header, $namespaces, false); $dom = dom_import_simplexml($root)->ownerDocument; $dom->formatOutput = true; @@ -25,10 +28,35 @@ class xml { } foreach($array as $key => $value) { - if (!is_array($value) AND !is_numeric($key)) - $xml->addChild($key, xmlify($value)); - elseif (is_array($value)) - self::from_array($value, $xml->addChild($key)); + if ($key == "@attributes") { + if (!isset($element) OR !is_array($value)) + continue; + + foreach ($value as $attr_key => $attr_value) { + $element_parts = explode(":", $attr_key); + if ((count($element_parts) > 1) AND isset($namespaces[$element_parts[0]])) + $namespace = $namespaces[$element_parts[0]]; + else + $namespace = NULL; + + $element->addAttribute ($attr_key, $attr_value, $namespace); + } + + continue; + } + + $element_parts = explode(":", $key); + if ((count($element_parts) > 1) AND isset($namespaces[$element_parts[0]])) + $namespace = $namespaces[$element_parts[0]]; + else + $namespace = NULL; + + if (!is_array($value)) + $element = $xml->addChild($key, xmlify($value), $namespace); + elseif (is_array($value)) { + $element = $xml->addChild($key, NULL, $namespace); + self::from_array($value, $element, $remove_header, $namespaces, false); + } } } From 7a0edc86ae685f65598577d93a62648070407870 Mon Sep 17 00:00:00 2001 From: Michael Vogel Date: Sun, 13 Mar 2016 13:04:12 +0100 Subject: [PATCH 038/211] The display contained bad sql queries --- mod/display.php | 115 ++++++++++++++++++++++++------------------------ 1 file changed, 58 insertions(+), 57 deletions(-) diff --git a/mod/display.php b/mod/display.php index 97261e267d..e53f9e2066 100644 --- a/mod/display.php +++ b/mod/display.php @@ -17,7 +17,7 @@ function display_init(&$a) { // Does the local user have this item? if (local_user()) { $r = q("SELECT `id`, `parent`, `author-name`, `author-link`, `author-avatar`, `network`, `body`, `uid` FROM `item` - WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0 + WHERE `item`.`visible` AND NOT `item`.`deleted` AND NOT `item`.`moderated` AND `guid` = '%s' AND `uid` = %d", dbesc($a->argv[1]), local_user()); if (count($r)) { $nick = $a->user["nickname"]; @@ -30,12 +30,12 @@ function display_init(&$a) { $r = q("SELECT `user`.`nickname`, `item`.`id`, `item`.`parent`, `item`.`author-name`, `item`.`author-link`, `item`.`author-avatar`, `item`.`network`, `item`.`uid`, `item`.`body` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid` - WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0 + WHERE `item`.`visible` AND NOT `item`.`deleted` AND NOT `item`.`moderated` AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = '' AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = '' - AND `item`.`private` = 0 AND NOT `user`.`hidewall` + AND NOT `item`.`private` AND NOT `user`.`hidewall` AND `item`.`guid` = '%s'", dbesc($a->argv[1])); - // AND `item`.`private` = 0 AND `item`.`wall` = 1 + // AND NOT `item`.`private` AND `item`.`wall` if (count($r)) { $nick = $r[0]["nickname"]; $itemuid = $r[0]["uid"]; @@ -46,17 +46,17 @@ function display_init(&$a) { if ($nick == "") { $r = q("SELECT `item`.`id`, `item`.`parent`, `item`.`author-name`, `item`.`author-link`, `item`.`author-avatar`, `item`.`network`, `item`.`uid`, `item`.`body` - FROM `item` WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0 + FROM `item` WHERE `item`.`visible` AND NOT `item`.`deleted` AND NOT `item`.`moderated` AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = '' AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = '' - AND `item`.`private` = 0 AND `item`.`uid` = 0 + AND NOT `item`.`private` AND `item`.`uid` = 0 AND `item`.`guid` = '%s'", dbesc($a->argv[1])); - // AND `item`.`private` = 0 AND `item`.`wall` = 1 + // AND NOT `item`.`private` AND `item`.`wall` } if (count($r)) { if ($r[0]["id"] != $r[0]["parent"]) $r = q("SELECT `id`, `author-name`, `author-link`, `author-avatar`, `network`, `body`, `uid` FROM `item` - WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0 + WHERE `item`.`visible` AND NOT `item`.`deleted` AND NOT `item`.`moderated` AND `id` = %d", $r[0]["parent"]); $profiledata = display_fetchauthor($a, $r[0]); @@ -67,7 +67,7 @@ function display_init(&$a) { if (($nickname != $a->user["nickname"])) { $r = q("SELECT `profile`.`uid` AS `profile_uid`, `profile`.* , `contact`.`avatar-date` AS picdate, `user`.* FROM `profile` INNER JOIN `contact` on `contact`.`uid` = `profile`.`uid` INNER JOIN `user` ON `profile`.`uid` = `user`.`uid` - WHERE `user`.`nickname` = '%s' AND `profile`.`is-default` = 1 and `contact`.`self` = 1 LIMIT 1", + WHERE `user`.`nickname` = '%s' AND `profile`.`is-default` AND `contact`.`self` LIMIT 1", dbesc($nickname) ); if (count($r)) @@ -120,27 +120,27 @@ function display_fetchauthor($a, $item) { } if (!$skip) { - $author = ""; - preg_match("/author='(.*?)'/ism", $attributes, $matches); - if ($matches[1] != "") + $author = ""; + preg_match("/author='(.*?)'/ism", $attributes, $matches); + if ($matches[1] != "") $profiledata["name"] = html_entity_decode($matches[1],ENT_QUOTES,'UTF-8'); - preg_match('/author="(.*?)"/ism', $attributes, $matches); - if ($matches[1] != "") + preg_match('/author="(.*?)"/ism', $attributes, $matches); + if ($matches[1] != "") $profiledata["name"] = html_entity_decode($matches[1],ENT_QUOTES,'UTF-8'); - $profile = ""; - preg_match("/profile='(.*?)'/ism", $attributes, $matches); - if ($matches[1] != "") + $profile = ""; + preg_match("/profile='(.*?)'/ism", $attributes, $matches); + if ($matches[1] != "") $profiledata["url"] = $matches[1]; - preg_match('/profile="(.*?)"/ism', $attributes, $matches); - if ($matches[1] != "") + preg_match('/profile="(.*?)"/ism', $attributes, $matches); + if ($matches[1] != "") $profiledata["url"] = $matches[1]; - $avatar = ""; - preg_match("/avatar='(.*?)'/ism", $attributes, $matches); - if ($matches[1] != "") + $avatar = ""; + preg_match("/avatar='(.*?)'/ism", $attributes, $matches); + if ($matches[1] != "") $profiledata["photo"] = $matches[1]; preg_match('/avatar="(.*?)"/ism', $attributes, $matches); @@ -257,7 +257,7 @@ function display_content(&$a, $update = 0) { if (local_user()) { $r = q("SELECT `id` FROM `item` - WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0 + WHERE `item`.`visible` AND NOT `item`.`deleted` AND NOT `item`.`moderated` AND `guid` = '%s' AND `uid` = %d", dbesc($a->argv[1]), local_user()); if (count($r)) { $item_id = $r[0]["id"]; @@ -267,12 +267,12 @@ function display_content(&$a, $update = 0) { if ($nick == "") { $r = q("SELECT `user`.`nickname`, `item`.`id` FROM `item` INNER JOIN `user` ON `user`.`uid` = `item`.`uid` - WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0 + WHERE `item`.`visible` AND NOT `item`.`deleted` AND NOT `item`.`moderated` AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = '' AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = '' - AND `item`.`private` = 0 AND NOT `user`.`hidewall` + AND NOT `item`.`private` AND NOT `user`.`hidewall` AND `item`.`guid` = '%s'", dbesc($a->argv[1])); - // AND `item`.`private` = 0 AND `item`.`wall` = 1 + // AND NOT `item`.`private` AND `item`.`wall` if (count($r)) { $item_id = $r[0]["id"]; $nick = $r[0]["nickname"]; @@ -280,12 +280,12 @@ function display_content(&$a, $update = 0) { } if ($nick == "") { $r = q("SELECT `item`.`id` FROM `item` - WHERE `item`.`visible` = 1 AND `item`.`deleted` = 0 and `item`.`moderated` = 0 + WHERE `item`.`visible` AND NOT `item`.`deleted` AND NOT `item`.`moderated` AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = '' AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = '' - AND `item`.`private` = 0 AND `item`.`uid` = 0 + AND NOT `item`.`private` AND `item`.`uid` = 0 AND `item`.`guid` = '%s'", dbesc($a->argv[1])); - // AND `item`.`private` = 0 AND `item`.`wall` = 1 + // AND NOT `item`.`private` AND `item`.`wall` if (count($r)) { $item_id = $r[0]["id"]; } @@ -293,12 +293,22 @@ function display_content(&$a, $update = 0) { } } - if(! $item_id) { + if ($item_id AND !is_numeric($item_id)) { + $r = q("SELECT `id` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", + dbesc($item_id), intval($a->profile['uid'])); + if ($r) + $item_id = $r[0]["id"]; + else + $item_id = false; + } + + if (!$item_id) { $a->error = 404; - notice( t('Item not found.') . EOL); + notice(t('Item not found.').EOL); return; } + $groups = array(); $contact = null; @@ -334,7 +344,7 @@ function display_content(&$a, $update = 0) { } } - $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `self` = 1 LIMIT 1", + $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `self` LIMIT 1", intval($a->profile['uid']) ); if(count($r)) @@ -367,62 +377,53 @@ function display_content(&$a, $update = 0) { $sql_extra = item_permissions_sql($a->profile['uid'],$remote_contact,$groups); - // AND `item`.`parent` = ( SELECT `parent` FROM `item` FORCE INDEX (PRIMARY, `uri`) WHERE ( `id` = '%s' OR `uri` = '%s' )) - if($update) { - $r = q("SELECT id FROM item WHERE item.uid = %d - AND `item`.`parent` = (SELECT `parent` FROM `item` WHERE (`id` = '%s' OR `uri` = '%s')) - $sql_extra AND unseen = 1", - intval($a->profile['uid']), - dbesc($item_id), - dbesc($item_id) + $r = q("SELECT `id` FROM `item` WHERE `item`.`uid` = %d + AND `item`.`parent` = (SELECT `parent` FROM `item` WHERE `id` = %d) + $sql_extra AND `unseen`", + intval($a->profile['uid']), + intval($item_id) ); if(!$r) return ''; } - // AND `item`.`parent` = ( SELECT `parent` FROM `item` FORCE INDEX (PRIMARY, `uri`) WHERE ( `id` = '%s' OR `uri` = '%s' ) - $r = q("SELECT `item`.*, `item`.`id` AS `item_id`, `item`.`network` AS `item_network`, `contact`.`name`, `contact`.`photo`, `contact`.`url`, `contact`.`rel`, `contact`.`network`, `contact`.`thumb`, `contact`.`self`, `contact`.`writable`, `contact`.`id` AS `cid`, `contact`.`uid` AS `contact-uid` FROM `item` INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id` - AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0 - WHERE `item`.`uid` = %d AND `item`.`visible` = 1 AND `item`.`deleted` = 0 - and `item`.`moderated` = 0 - AND `item`.`parent` = (SELECT `parent` FROM `item` WHERE (`id` = '%s' OR `uri` = '%s') - AND uid = %d) + AND NOT `contact`.`blocked` AND NOT `contact`.`pending` + WHERE `item`.`uid` = %d AND `item`.`visible` AND NOT `item`.`deleted` + AND NOT `item`.`moderated` + AND `item`.`parent` = (SELECT `parent` FROM `item` WHERE `id` = %d) $sql_extra ORDER BY `parent` DESC, `gravity` ASC, `id` ASC", intval($a->profile['uid']), - dbesc($item_id), - dbesc($item_id), - intval($a->profile['uid']) + intval($item_id) ); if(!$r && local_user()) { // Check if this is another person's link to a post that we have $r = q("SELECT `item`.uri FROM `item` - WHERE (`item`.`id` = '%s' OR `item`.`uri` = '%s' ) + WHERE (`item`.`id` = %d OR `item`.`uri` = '%s') LIMIT 1", - dbesc($item_id), + intval($item_id), dbesc($item_id) ); if($r) { $item_uri = $r[0]['uri']; - // AND `item`.`parent` = ( SELECT `parent` FROM `item` FORCE INDEX (PRIMARY, `uri`) WHERE `uri` = '%s' AND uid = %d ) $r = q("SELECT `item`.*, `item`.`id` AS `item_id`, `item`.`network` AS `item_network`, `contact`.`name`, `contact`.`photo`, `contact`.`url`, `contact`.`rel`, `contact`.`network`, `contact`.`thumb`, `contact`.`self`, `contact`.`writable`, `contact`.`id` AS `cid`, `contact`.`uid` AS `contact-uid` FROM `item` INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id` - AND `contact`.`blocked` = 0 AND `contact`.`pending` = 0 - WHERE `item`.`uid` = %d AND `item`.`visible` = 1 AND `item`.`deleted` = 0 - and `item`.`moderated` = 0 + AND NOT `contact`.`blocked` AND NOT `contact`.`pending` + WHERE `item`.`uid` = %d AND `item`.`visible` AND NOT `item`.`deleted` + AND NOT `item`.`moderated` AND `item`.`parent` = (SELECT `parent` FROM `item` WHERE `uri` = '%s' AND uid = %d) ORDER BY `parent` DESC, `gravity` ASC, `id` ASC ", intval(local_user()), @@ -437,7 +438,7 @@ function display_content(&$a, $update = 0) { if((local_user()) && (local_user() == $a->profile['uid'])) { q("UPDATE `item` SET `unseen` = 0 - WHERE `parent` = %d AND `unseen` = 1", + WHERE `parent` = %d AND `unseen`", intval($r[0]['parent']) ); } From e90af0be68c7bc3666b13cedc54c26b2a65b7f81 Mon Sep 17 00:00:00 2001 From: Michael Vogel Date: Sun, 13 Mar 2016 16:14:51 +0100 Subject: [PATCH 039/211] Decode function is now there as well. --- include/diaspora.php | 15 ++-- include/diaspora2.php | 168 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 174 insertions(+), 9 deletions(-) diff --git a/include/diaspora.php b/include/diaspora.php index 78ba520790..2b85befa8c 100644 --- a/include/diaspora.php +++ b/include/diaspora.php @@ -111,18 +111,18 @@ function diaspora_dispatch($importer,$msg,$attempt=1) { $ret = diaspora_reshare($importer,$xmlbase->reshare,$msg); } elseif($xmlbase->retraction) { - $tempfile = tempnam(get_temppath(), "diaspora-retraction"); - file_put_contents($tempfile, json_encode($data)); + //$tempfile = tempnam(get_temppath(), "diaspora-retraction"); + //file_put_contents($tempfile, json_encode($data)); $ret = diaspora_retraction($importer,$xmlbase->retraction,$msg); } elseif($xmlbase->signed_retraction) { - $tempfile = tempnam(get_temppath(), "diaspora-signed_retraction"); - file_put_contents($tempfile, json_encode($data)); + //$tempfile = tempnam(get_temppath(), "diaspora-signed_retraction"); + //file_put_contents($tempfile, json_encode($data)); $ret = diaspora_signed_retraction($importer,$xmlbase->signed_retraction,$msg); } elseif($xmlbase->relayable_retraction) { - $tempfile = tempnam(get_temppath(), "diaspora-relayable_retraction"); - file_put_contents($tempfile, json_encode($data)); + //$tempfile = tempnam(get_temppath(), "diaspora-relayable_retraction"); + //file_put_contents($tempfile, json_encode($data)); $ret = diaspora_signed_retraction($importer,$xmlbase->relayable_retraction,$msg); } elseif($xmlbase->photo) { @@ -468,6 +468,9 @@ EOT; function diaspora_decode($importer,$xml) { + $tempfile = tempnam(get_temppath(), "diaspora-decode"); + file_put_contents($tempfile, json_encode(array("importer" => $importer, "xml" => $xml))); + $public = false; $basedom = parse_xml_string($xml); diff --git a/include/diaspora2.php b/include/diaspora2.php index da772d68bd..081eaf153a 100644 --- a/include/diaspora2.php +++ b/include/diaspora2.php @@ -64,6 +64,168 @@ class diaspora { return $relay; } + function 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 = self::repair_signature($signature, $handle, ++$level); + } + + return($signature); + } + + /** + * @brief: Decodes incoming Diaspora message + * + * @param array $importer from user table + * @param string $xml urldecoded Diaspora salmon + * + * @return array + * 'message' -> decoded Diaspora XML message + * 'author' -> author diaspora handle + * 'key' -> author public key (converted to pkcs#8) + */ + function decode($importer, $xml) { + + $public = false; + $basedom = parse_xml_string($xml); + + if (!is_object($basedom)) + return false; + + $children = $basedom->children('https://joindiaspora.com/protocol'); + + if($children->header) { + $public = true; + $author_link = str_replace('acct:','',$children->header->author_id); + } else { + + $encrypted_header = json_decode(base64_decode($children->encrypted_header)); + + $encrypted_aes_key_bundle = base64_decode($encrypted_header->aes_key); + $ciphertext = base64_decode($encrypted_header->ciphertext); + + $outer_key_bundle = ''; + openssl_private_decrypt($encrypted_aes_key_bundle,$outer_key_bundle,$importer['prvkey']); + + $j_outer_key_bundle = json_decode($outer_key_bundle); + + $outer_iv = base64_decode($j_outer_key_bundle->iv); + $outer_key = base64_decode($j_outer_key_bundle->key); + + $decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $outer_key, $ciphertext, MCRYPT_MODE_CBC, $outer_iv); + + + $decrypted = pkcs5_unpad($decrypted); + + /** + * $decrypted now contains something like + * + * + * 8e+G2+ET8l5BPuW0sVTnQw== + * UvSMb4puPeB14STkcDWq+4QE302Edu15oaprAQSkLKU= + * galaxor@diaspora.priateship.org + * + */ + + logger('decrypted: '.$decrypted, LOGGER_DEBUG); + $idom = parse_xml_string($decrypted,false); + + $inner_iv = base64_decode($idom->iv); + $inner_aes_key = base64_decode($idom->aes_key); + + $author_link = str_replace('acct:','',$idom->author_id); + } + + $dom = $basedom->children(NAMESPACE_SALMON_ME); + + // figure out where in the DOM tree our data is hiding + + if($dom->provenance->data) + $base = $dom->provenance; + elseif($dom->env->data) + $base = $dom->env; + elseif($dom->data) + $base = $dom; + + if (!$base) { + logger('unable to locate salmon data in xml'); + http_status_exit(400); + } + + + // Stash the signature away for now. We have to find their key or it won't be good for anything. + $signature = base64url_decode($base->sig); + + // unpack the data + + // strip whitespace so our data element will return to one big base64 blob + $data = str_replace(array(" ","\t","\r","\n"),array("","","",""),$base->data); + + + // stash away some other stuff for later + + $type = $base->data[0]->attributes()->type[0]; + $keyhash = $base->sig[0]->attributes()->keyhash[0]; + $encoding = $base->encoding; + $alg = $base->alg; + + + $signed_data = $data.'.'.base64url_encode($type).'.'.base64url_encode($encoding).'.'.base64url_encode($alg); + + + // decode the data + $data = base64url_decode($data); + + + if($public) + $inner_decrypted = $data; + else { + + // Decode the encrypted blob + + $inner_encrypted = base64_decode($data); + $inner_decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $inner_aes_key, $inner_encrypted, MCRYPT_MODE_CBC, $inner_iv); + $inner_decrypted = pkcs5_unpad($inner_decrypted); + } + + if (!$author_link) { + logger('Could not retrieve author URI.'); + http_status_exit(400); + } + // Once we have the author URI, go to the web and try to find their public key + // (first this will look it up locally if it is in the fcontact cache) + // This will also convert diaspora public key from pkcs#1 to pkcs#8 + + logger('Fetching key for '.$author_link); + $key = self::key($author_link); + + if (!$key) { + logger('Could not retrieve author key.'); + http_status_exit(400); + } + + $verify = rsa_verify($signed_data,$signature,$key); + + if (!$verify) { + logger('Message did not verify. Discarding.'); + http_status_exit(400); + } + + logger('Message verified.'); + + return array('message' => $inner_decrypted, 'author' => $author_link, 'key' => $key); + + } + + /** * @brief Dispatches public messages and find the fitting receivers * @@ -1287,7 +1449,7 @@ class diaspora { $ret = self::person_by_handle($author); if (!$ret || ($ret["network"] != NETWORK_DIASPORA)) { - logger("Cannot resolve diaspora handle ".$author ." for ".$recipient); + logger("Cannot resolve diaspora handle ".$author." for ".$recipient); return false; } @@ -1854,7 +2016,7 @@ class diaspora { dbesc($slap), intval($public_batch) ); - if(count($r)) { + if($r) { logger("add_to_queue ignored - identical item already in queue"); } else { // queue message for redelivery @@ -2211,7 +2373,7 @@ class diaspora { intval($item["uid"]) ); - if (!count($r)) { + if (!$r) { logger("conversation not found."); return; } From d176fff2147696e55c8a6fe99f4457d06927b4de Mon Sep 17 00:00:00 2001 From: Michael Vogel Date: Sun, 13 Mar 2016 19:47:02 +0100 Subject: [PATCH 040/211] New implementation is now live. --- include/delivery.php | 182 +++++++++++++++++++++--------------------- include/diaspora.php | 1 + include/diaspora2.php | 49 ++++++++++-- mod/receive.php | 14 ++-- 4 files changed, 144 insertions(+), 102 deletions(-) diff --git a/include/delivery.php b/include/delivery.php index e5ca0946b3..d184fe12e1 100644 --- a/include/delivery.php +++ b/include/delivery.php @@ -10,11 +10,11 @@ require_once("include/dfrn.php"); function delivery_run(&$argv, &$argc){ global $a, $db; - if(is_null($a)){ + if (is_null($a)){ $a = new App; } - if(is_null($db)) { + if (is_null($db)) { @include(".htconfig.php"); require_once("include/dba.php"); $db = new dba($db_host, $db_user, $db_pass, $db_data); @@ -32,12 +32,12 @@ function delivery_run(&$argv, &$argc){ load_hooks(); - if($argc < 3) + if ($argc < 3) return; $a->set_baseurl(get_config('system','url')); - logger('delivery: invoked: ' . print_r($argv,true), LOGGER_DEBUG); + logger('delivery: invoked: '. print_r($argv,true), LOGGER_DEBUG); $cmd = $argv[1]; $item_id = intval($argv[2]); @@ -53,7 +53,7 @@ function delivery_run(&$argv, &$argc){ dbesc($item_id), dbesc($contact_id) ); - if(! count($r)) { + if (!count($r)) { continue; } @@ -68,7 +68,7 @@ function delivery_run(&$argv, &$argc){ dbesc($contact_id) ); - if((! $item_id) || (! $contact_id)) + if (!$item_id || !$contact_id) continue; $expire = false; @@ -84,20 +84,20 @@ function delivery_run(&$argv, &$argc){ $recipients[] = $contact_id; - if($cmd === 'mail') { + if ($cmd === 'mail') { $normal_mode = false; $mail = true; $message = q("SELECT * FROM `mail` WHERE `id` = %d LIMIT 1", intval($item_id) ); - if(! count($message)){ + if (!count($message)){ return; } $uid = $message[0]['uid']; $recipients[] = $message[0]['contact-id']; $item = $message[0]; } - elseif($cmd === 'expire') { + elseif ($cmd === 'expire') { $normal_mode = false; $expire = true; $items = q("SELECT * FROM `item` WHERE `uid` = %d AND `wall` = 1 @@ -106,22 +106,22 @@ function delivery_run(&$argv, &$argc){ ); $uid = $item_id; $item_id = 0; - if(! count($items)) + if (!count($items)) continue; } - elseif($cmd === 'suggest') { + elseif ($cmd === 'suggest') { $normal_mode = false; $fsuggest = true; $suggest = q("SELECT * FROM `fsuggest` WHERE `id` = %d LIMIT 1", intval($item_id) ); - if(! count($suggest)) + if (!count($suggest)) return; $uid = $suggest[0]['uid']; $recipients[] = $suggest[0]['cid']; $item = $suggest[0]; - } elseif($cmd === 'relocate') { + } elseif ($cmd === 'relocate') { $normal_mode = false; $relocate = true; $uid = $item_id; @@ -131,7 +131,7 @@ function delivery_run(&$argv, &$argc){ intval($item_id) ); - if((! count($r)) || (! intval($r[0]['parent']))) { + if ((!count($r)) || (!intval($r[0]['parent']))) { continue; } @@ -145,32 +145,32 @@ function delivery_run(&$argv, &$argc){ intval($parent_id) ); - if(! count($items)) { + if (!count($items)) { continue; } $icontacts = null; $contacts_arr = array(); foreach($items as $item) - if(! in_array($item['contact-id'],$contacts_arr)) + if (!in_array($item['contact-id'],$contacts_arr)) $contacts_arr[] = intval($item['contact-id']); - if(count($contacts_arr)) { + if (count($contacts_arr)) { $str_contacts = implode(',',$contacts_arr); $icontacts = q("SELECT * FROM `contact` WHERE `id` IN ( $str_contacts ) " ); } - if( ! ($icontacts && count($icontacts))) + if ( !($icontacts && count($icontacts))) continue; // avoid race condition with deleting entries - if($items[0]['deleted']) { + if ($items[0]['deleted']) { foreach($items as $item) $item['deleted'] = 1; } - if((count($items) == 1) && ($items[0]['uri'] === $items[0]['parent-uri'])) { + if ((count($items) == 1) && ($items[0]['uri'] === $items[0]['parent-uri'])) { logger('delivery: top level post'); $top_level = true; } @@ -184,7 +184,7 @@ function delivery_run(&$argv, &$argc){ intval($uid) ); - if(! count($r)) + if (!count($r)) continue; $owner = $r[0]; @@ -193,7 +193,7 @@ function delivery_run(&$argv, &$argc){ $public_message = true; - if(! ($mail || $fsuggest || $relocate)) { + if (!($mail || $fsuggest || $relocate)) { require_once('include/group.php'); $parent = $items[0]; @@ -217,7 +217,7 @@ function delivery_run(&$argv, &$argc){ $localhost = $a->get_hostname(); - if(strpos($localhost,':')) + if (strpos($localhost,':')) $localhost = substr($localhost,0,strpos($localhost,':')); /** @@ -230,17 +230,17 @@ function delivery_run(&$argv, &$argc){ $relay_to_owner = false; - if((! $top_level) && ($parent['wall'] == 0) && (! $expire) && (stristr($target_item['uri'],$localhost))) { + if (!$top_level && ($parent['wall'] == 0) && !$expire && stristr($target_item['uri'],$localhost)) { $relay_to_owner = true; } - if($relay_to_owner) { + if ($relay_to_owner) { logger('followup '.$target_item["guid"], LOGGER_DEBUG); // local followup to remote post $followup = true; } - if((strlen($parent['allow_cid'])) + if ((strlen($parent['allow_cid'])) || (strlen($parent['allow_gid'])) || (strlen($parent['deny_cid'])) || (strlen($parent['deny_gid']))) { @@ -253,10 +253,10 @@ function delivery_run(&$argv, &$argc){ intval($contact_id) ); - if(count($r)) + if (count($r)) $contact = $r[0]; - if($contact['self']) + if ($contact['self']) continue; $deliver_status = 0; @@ -266,7 +266,7 @@ function delivery_run(&$argv, &$argc){ switch($contact['network']) { case NETWORK_DFRN: - logger('notifier: '.$target_item["guid"].' dfrndelivery: ' . $contact['name']); + logger('notifier: '.$target_item["guid"].' dfrndelivery: '.$contact['name']); if ($mail) { $item['body'] = fix_private_photos($item['body'],$owner['uid'],null,$message[0]['contact-id']); @@ -276,13 +276,13 @@ function delivery_run(&$argv, &$argc){ q("DELETE FROM `fsuggest` WHERE `id` = %d LIMIT 1", intval($item['id'])); } elseif ($relocate) $atom = dfrn::relocate($owner, $uid); - elseif($followup) { + 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); + if ($item['id'] == $item_id) { + logger('followup: item: '. print_r($item,true), LOGGER_DATA); $msgitems[] = $item; } } @@ -290,19 +290,19 @@ function delivery_run(&$argv, &$argc){ } 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']) + 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']) { + if ($normal_mode) { + if ($item_id == $item['id'] || $item['id'] == $item['parent']) { $item["entry:comment-allow"] = true; $item["entry:cid"] = (($top_level) ? $contact['id'] : 0); $msgitems[] = $item; @@ -317,15 +317,15 @@ function delivery_run(&$argv, &$argc){ logger('notifier entry: '.$contact["url"].' '.$target_item["guid"].' entry: '.$atom, LOGGER_DEBUG); - logger('notifier: ' . $atom, LOGGER_DATA); + logger('notifier: '.$atom, LOGGER_DATA); $basepath = implode('/', array_slice(explode('/',$contact['url']),0,3)); // perform local delivery if we are on the same site - if(link_compare($basepath,$a->get_baseurl())) { + if (link_compare($basepath,$a->get_baseurl())) { $nickname = basename($contact['url']); - if($contact['issued-id']) + if ($contact['issued-id']) $sql_extra = sprintf(" AND `dfrn-id` = '%s' ", dbesc($contact['issued-id'])); else $sql_extra = sprintf(" AND `issued-id` = '%s' ", dbesc($contact['dfrn-id'])); @@ -347,10 +347,10 @@ function delivery_run(&$argv, &$argc){ dbesc($nickname) ); - if($x && count($x)) { + if ($x && count($x)) { $write_flag = ((($x[0]['rel']) && ($x[0]['rel'] != CONTACT_IS_SHARING)) ? true : false); - if((($owner['page-flags'] == PAGE_COMMUNITY) || ($write_flag)) && (! $x[0]['writable'])) { - q("update contact set writable = 1 where id = %d", + if ((($owner['page-flags'] == PAGE_COMMUNITY) || $write_flag) && !$x[0]['writable']) { + q("UPDATE `contact` SET `writable` = 1 WHERE `id` = %d", intval($x[0]['id']) ); $x[0]['writable'] = 1; @@ -370,14 +370,14 @@ function delivery_run(&$argv, &$argc){ } } - if(! was_recently_delayed($contact['id'])) + if (!was_recently_delayed($contact['id'])) $deliver_status = dfrn::deliver($owner,$contact,$atom); else $deliver_status = (-1); logger('notifier: dfrn_delivery to '.$contact["url"].' with guid '.$target_item["guid"].' returns '.$deliver_status); - if($deliver_status == (-1)) { + if ($deliver_status == (-1)) { logger('notifier: delivery failed: queuing message'); add_to_queue($contact['id'],NETWORK_DFRN,$atom); } @@ -385,9 +385,9 @@ function delivery_run(&$argv, &$argc){ case NETWORK_OSTATUS: // Do not send to otatus if we are not configured to send to public networks - if($owner['prvnets']) + if ($owner['prvnets']) break; - if(get_config('system','ostatus_disabled') || get_config('system','dfrn_only')) + if (get_config('system','ostatus_disabled') || get_config('system','dfrn_only')) break; // There is currently no code here to distribute anything to OStatus. @@ -397,67 +397,67 @@ function delivery_run(&$argv, &$argc){ case NETWORK_MAIL: case NETWORK_MAIL2: - if(get_config('system','dfrn_only')) + if (get_config('system','dfrn_only')) break; // WARNING: does not currently convert to RFC2047 header encodings, etc. $addr = $contact['addr']; - if(! strlen($addr)) + if (!strlen($addr)) break; - if($cmd === 'wall-new' || $cmd === 'comment-new') { + if ($cmd === 'wall-new' || $cmd === 'comment-new') { $it = null; - if($cmd === 'wall-new') + if ($cmd === 'wall-new') $it = $items[0]; else { $r = q("SELECT * FROM `item` WHERE `id` = %d AND `uid` = %d LIMIT 1", intval($argv[2]), intval($uid) ); - if(count($r)) + if (count($r)) $it = $r[0]; } - if(! $it) + if (!$it) break; $local_user = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1", intval($uid) ); - if(! count($local_user)) + if (!count($local_user)) break; $reply_to = ''; $r1 = q("SELECT * FROM `mailacct` WHERE `uid` = %d LIMIT 1", intval($uid) ); - if($r1 && $r1[0]['reply_to']) + if ($r1 && $r1[0]['reply_to']) $reply_to = $r1[0]['reply_to']; $subject = (($it['title']) ? email_header_encode($it['title'],'UTF-8') : t("\x28no subject\x29")) ; // only expose our real email address to true friends - if(($contact['rel'] == CONTACT_IS_FRIEND) && (! $contact['blocked'])) { - if($reply_to) { + if (($contact['rel'] == CONTACT_IS_FRIEND) && !$contact['blocked']) { + if ($reply_to) { $headers = 'From: '.email_header_encode($local_user[0]['username'],'UTF-8').' <'.$reply_to.'>'."\n"; $headers .= 'Sender: '.$local_user[0]['email']."\n"; } else $headers = 'From: '.email_header_encode($local_user[0]['username'],'UTF-8').' <'.$local_user[0]['email'].'>'."\n"; } else - $headers = 'From: ' . email_header_encode($local_user[0]['username'],'UTF-8') . ' <' . t('noreply') . '@' . $a->get_hostname() . '>' . "\n"; + $headers = 'From: '. email_header_encode($local_user[0]['username'],'UTF-8') .' <'. t('noreply') .'@'.$a->get_hostname() .'>'. "\n"; - //if($reply_to) - // $headers .= 'Reply-to: ' . $reply_to . "\n"; + //if ($reply_to) + // $headers .= 'Reply-to: '.$reply_to . "\n"; - $headers .= 'Message-Id: <' . iri2msgid($it['uri']). '>' . "\n"; + $headers .= 'Message-Id: <'. iri2msgid($it['uri']).'>'. "\n"; //logger("Mail: uri: ".$it['uri']." parent-uri ".$it['parent-uri'], LOGGER_DEBUG); //logger("Mail: Data: ".print_r($it, true), LOGGER_DEBUG); //logger("Mail: Data: ".print_r($it, true), LOGGER_DATA); - if($it['uri'] !== $it['parent-uri']) { + if ($it['uri'] !== $it['parent-uri']) { $headers .= "References: <".iri2msgid($it["parent-uri"]).">"; // If Threading is enabled, write down the correct parent @@ -465,23 +465,23 @@ function delivery_run(&$argv, &$argc){ $headers .= " <".iri2msgid($it["thr-parent"]).">"; $headers .= "\n"; - if(!$it['title']) { + if (!$it['title']) { $r = q("SELECT `title` FROM `item` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", dbesc($it['parent-uri']), intval($uid)); - if(count($r) AND ($r[0]['title'] != '')) + if (count($r) AND ($r[0]['title'] != '')) $subject = $r[0]['title']; else { $r = q("SELECT `title` FROM `item` WHERE `parent-uri` = '%s' AND `uid` = %d LIMIT 1", dbesc($it['parent-uri']), intval($uid)); - if(count($r) AND ($r[0]['title'] != '')) + if (count($r) AND ($r[0]['title'] != '')) $subject = $r[0]['title']; } } - if(strncasecmp($subject,'RE:',3)) + if (strncasecmp($subject,'RE:',3)) $subject = 'Re: '.$subject; } email_send($addr, $subject, $headers, $it); @@ -489,60 +489,64 @@ function delivery_run(&$argv, &$argc){ break; case NETWORK_DIASPORA: - if($public_message) - $loc = 'public batch ' . $contact['batch']; + if ($public_message) + $loc = 'public batch '.$contact['batch']; else $loc = $contact['name']; - logger('delivery: diaspora batch deliver: ' . $loc); + logger('delivery: diaspora batch deliver: '.$loc); - if(get_config('system','dfrn_only') || (!get_config('system','diaspora_enabled'))) + if (get_config('system','dfrn_only') || (!get_config('system','diaspora_enabled'))) break; - if($mail) { - diaspora_send_mail($item,$owner,$contact); + if ($mail) { + diaspora::send_mail($item,$owner,$contact); + //diaspora_send_mail($item,$owner,$contact); break; } - if(!$normal_mode) + if (!$normal_mode) break; - if((! $contact['pubkey']) && (! $public_message)) + if (!$contact['pubkey'] && !$public_message) break; $unsupported_activities = array(ACTIVITY_DISLIKE, ACTIVITY_ATTEND, ACTIVITY_ATTENDNO, ACTIVITY_ATTENDMAYBE); //don't transmit activities which are not supported by diaspora foreach($unsupported_activities as $act) { - if(activity_match($target_item['verb'],$act)) { + if (activity_match($target_item['verb'],$act)) { break 2; } } - if(($target_item['deleted']) && (($target_item['uri'] === $target_item['parent-uri']) || $followup)) { + if (($target_item['deleted']) && (($target_item['uri'] === $target_item['parent-uri']) || $followup)) { // top-level retraction - logger('delivery: diaspora retract: ' . $loc); + logger('delivery: diaspora retract: '.$loc); - diaspora_send_retraction($target_item,$owner,$contact,$public_message); + diaspora::send_retraction($target_item,$owner,$contact,$public_message); + //diaspora_send_retraction($target_item,$owner,$contact,$public_message); break; - } elseif($followup) { + } elseif ($followup) { // send comments and likes to owner to relay - diaspora_send_followup($target_item,$owner,$contact,$public_message); + diaspora::send_followup($target_item,$owner,$contact,$public_message); + //diaspora_send_followup($target_item,$owner,$contact,$public_message); break; - } elseif($target_item['uri'] !== $target_item['parent-uri']) { + } elseif ($target_item['uri'] !== $target_item['parent-uri']) { // we are the relay - send comments, likes and relayable_retractions to our conversants - logger('delivery: diaspora relay: ' . $loc); - - diaspora_send_relay($target_item,$owner,$contact,$public_message); + logger('delivery: diaspora relay: '.$loc); + diaspora::send_relay($target_item,$owner,$contact,$public_message); + //diaspora_send_relay($target_item,$owner,$contact,$public_message); break; - } elseif(($top_level) && (! $walltowall)) { + } elseif ($top_level && !$walltowall) { // currently no workable solution for sending walltowall - logger('delivery: diaspora status: ' . $loc); - diaspora_send_status($target_item,$owner,$contact,$public_message); + logger('delivery: diaspora status: '.$loc); + diaspora::send_status($target_item,$owner,$contact,$public_message); + //diaspora_send_status($target_item,$owner,$contact,$public_message); break; } - logger('delivery: diaspora unknown mode: ' . $contact['name']); + logger('delivery: diaspora unknown mode: '.$contact['name']); break; diff --git a/include/diaspora.php b/include/diaspora.php index 2b85befa8c..11fe2c9b57 100644 --- a/include/diaspora.php +++ b/include/diaspora.php @@ -15,6 +15,7 @@ require_once('include/lock.php'); require_once('include/threads.php'); require_once('mod/share.php'); require_once('include/enotify.php'); +require_once('include/diaspora2.php'); function diaspora_dispatch_public($msg) { diff --git a/include/diaspora2.php b/include/diaspora2.php index 081eaf153a..b031651675 100644 --- a/include/diaspora2.php +++ b/include/diaspora2.php @@ -284,6 +284,8 @@ class diaspora { $type = $fields->getName(); + logger("Received message type ".$type." from ".$sender." for user ".$importer["uid"], LOGGER_DEBUG); + switch ($type) { case "account_deletion": return self::receive_account_deletion($importer, $fields); @@ -654,7 +656,7 @@ class diaspora { return false; } - if (!self::post_allow($importer, $contact, false)) { + if (!self::post_allow($importer, $contact, $is_comment)) { logger("The handle: ".$handle." is not allowed to post to user ".$importer["uid"]); return false; } @@ -669,10 +671,10 @@ class diaspora { if($r) { logger("message ".$guid." already exists for user ".$uid); - return false; + return true; } - return true; + return false; } private function fetch_guid($item) { @@ -774,10 +776,12 @@ class diaspora { } if (!$r) { - logger("parent item not found: parent: ".$guid." item: ".$guid); + logger("parent item not found: parent: ".$guid." - user: ".$uid); return false; - } else + } else { + logger("parent item found: parent: ".$guid." - user: ".$uid); return $r[0]; + } } private function author_contact_by_url($contact, $person, $uid) { @@ -892,6 +896,9 @@ class diaspora { $message_id = item_store($datarray); + if ($message_id) + logger("Stored comment ".$datarray["guid"]." with message id ".$message_id, LOGGER_DEBUG); + // If we are the origin of the parent we store the original data and notify our followers if($message_id AND $parent_item["origin"]) { @@ -1175,6 +1182,9 @@ class diaspora { $message_id = item_store($datarray); + if ($message_id) + logger("Stored like ".$datarray["guid"]." with message id ".$message_id, LOGGER_DEBUG); + // If we are the origin of the parent we store the original data and notify our followers if($message_id AND $parent_item["origin"]) { @@ -1358,6 +1368,8 @@ class diaspora { update_gcontact($gcontact); + logger("Profile of contact ".$contact["id"]." stored for user ".$importer["uid"], LOGGER_DEBUG); + return true; } @@ -1654,6 +1666,9 @@ class diaspora { self::fetch_guid($datarray); $message_id = item_store($datarray); + if ($message_id) + logger("Stored reshare ".$datarray["guid"]." with message id ".$message_id, LOGGER_DEBUG); + return $message_id; } @@ -1695,6 +1710,8 @@ class diaspora { ); delete_thread($r[0]["id"], $r[0]["parent-uri"]); + logger("Deleted target ".$target_guid." from user ".$importer["uid"], LOGGER_DEBUG); + // Now check if the retraction needs to be relayed by us if($p[0]["origin"]) { @@ -1822,7 +1839,8 @@ class diaspora { self::fetch_guid($datarray); $message_id = item_store($datarray); - logger("Stored item with message id ".$message_id, LOGGER_DEBUG); + if ($message_id) + logger("Stored item ".$datarray["guid"]." with message id ".$message_id, LOGGER_DEBUG); return $message_id; } @@ -2329,8 +2347,21 @@ class diaspora { /// @todo Change all signatur storing functions to the new format if ($signature['signed_text'] AND $signature['signature'] AND $signature['signer']) $message = self::message_from_signatur($item, $signature); - else // New way - $message = json_decode($signature['signed_text']); + else {// New way + $msg = json_decode($signature['signed_text'], true); + + $message = array(); + foreach ($msg AS $field => $data) { + if (!$item["deleted"]) { + if ($field == "author") + $field = "diaspora_handle"; + if ($field == "parent_type") + $field = "target_type"; + } + + $message[$field] = $data; + } + } if ($item["deleted"]) { $signed_text = $message["target_guid"].';'.$message["target_type"]; @@ -2338,6 +2369,8 @@ class diaspora { } else $message["parent_author_signature"] = self::signature($owner, $message); + logger("Relayed data ".print_r($message, true), LOGGER_DEBUG); + return self::build_and_transmit($owner, $contact, $type, $message, $public_batch, $item["guid"]); } diff --git a/mod/receive.php b/mod/receive.php index 95a5101675..051ea8c25c 100644 --- a/mod/receive.php +++ b/mod/receive.php @@ -53,7 +53,8 @@ function receive_post(&$a) { logger('mod-diaspora: message is okay', LOGGER_DEBUG); - $msg = diaspora_decode($importer,$xml); + $msg = diaspora::decode($importer,$xml); + //$msg = diaspora_decode($importer,$xml); logger('mod-diaspora: decoded', LOGGER_DEBUG); @@ -65,10 +66,13 @@ function receive_post(&$a) { logger('mod-diaspora: dispatching', LOGGER_DEBUG); $ret = 0; - if($public) - diaspora_dispatch_public($msg); - else - $ret = diaspora_dispatch($importer,$msg); + if($public) { + diaspora::dispatch_public($msg); + //diaspora_dispatch_public($msg); + } else { + $ret = diaspora::dispatch($importer,$msg); + //$ret = diaspora_dispatch($importer,$msg); + } http_status_exit(($ret) ? $ret : 200); // NOTREACHED From 77dbb4bfbc8ef41754be01b057fef8df5c9c2e2f Mon Sep 17 00:00:00 2001 From: Michael Vogel Date: Sun, 13 Mar 2016 21:11:48 +0100 Subject: [PATCH 041/211] Retraction and reshares work --- include/diaspora2.php | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/include/diaspora2.php b/include/diaspora2.php index b031651675..f6b8b9a704 100644 --- a/include/diaspora2.php +++ b/include/diaspora2.php @@ -1628,6 +1628,8 @@ class diaspora { if (!$original_item) return false; + $orig_url = App::get_baseurl()."/display/".$original_item["guid"]; + $datarray = array(); $datarray["uid"] = $importer["uid"]; @@ -1651,7 +1653,7 @@ class diaspora { $datarray["object"] = json_encode($data); $prefix = share_header($original_item["author-name"], $original_item["author-link"], $original_item["author-avatar"], - $original_item["guid"], $original_item["created"], $original_item["uri"]); + $original_item["guid"], $original_item["created"], $orig_url); $datarray["body"] = $prefix.$original_item["body"]."[/share]"; $datarray["tag"] = $original_item["tag"]; @@ -1691,16 +1693,20 @@ class diaspora { return false; // Only delete it if the author really fits - if (!link_compare($r[0]["author-link"],$person["url"])) + if (!link_compare($r[0]["author-link"], $person["url"])) { + logger("Item author ".$r[0]["author-link"]." doesn't fit to expected contact ".$person["url"], LOGGER_DEBUG); return false; + } // Check if the sender is the thread owner $p = q("SELECT `author-link`, `origin` FROM `item` WHERE `id` = %d", intval($r[0]["parent"])); // Only delete it if the parent author really fits - if (!link_compare($p[0]["author-link"], $contact["url"])) + if (!link_compare($p[0]["author-link"], $contact["url"]) AND !link_compare($r[0]["author-link"], $contact["url"])) { + logger("Thread author ".$p[0]["author-link"]." and item author ".$r[0]["author-link"]." don't fit to expected contact ".$contact["url"], LOGGER_DEBUG); return false; + } // Currently we don't have a central deletion function that we could use in this case. The function "item_drop" doesn't work for that case q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = '' WHERE `id` = %d", @@ -1736,6 +1742,8 @@ class diaspora { return false; } + logger("Got retraction for ".$target_type.", sender ".$sender." and user ".$importer["uid"], LOGGER_DEBUG); + switch ($target_type) { case "Comment": case "Like": From 3e79a19e579b19d04ec3d28d59799d76cf685735 Mon Sep 17 00:00:00 2001 From: Michael Vogel Date: Mon, 14 Mar 2016 08:11:14 +0100 Subject: [PATCH 042/211] Some more function calls changed to the new class --- include/Contact.php | 2 +- include/contact_selectors.php | 2 +- include/delivery.php | 8 ++++---- include/diaspora2.php | 10 ++++++++-- include/follow.php | 2 +- include/notifier.php | 6 +++--- include/queue.php | 2 +- 7 files changed, 19 insertions(+), 13 deletions(-) diff --git a/include/Contact.php b/include/Contact.php index 3799e0b189..d76c8f826c 100644 --- a/include/Contact.php +++ b/include/Contact.php @@ -129,7 +129,7 @@ function terminate_friendship($user,$self,$contact) { } elseif($contact['network'] === NETWORK_DIASPORA) { require_once('include/diaspora.php'); - diaspora_unshare($user,$contact); + diaspora::send_unshare($user,$contact); } elseif($contact['network'] === NETWORK_DFRN) { require_once('include/dfrn.php'); diff --git a/include/contact_selectors.php b/include/contact_selectors.php index a884a6b52b..3bf68f764e 100644 --- a/include/contact_selectors.php +++ b/include/contact_selectors.php @@ -99,7 +99,7 @@ function network_to_name($s, $profile = "") { $networkname = str_replace($search,$replace,$s); - if (($s == NETWORK_DIASPORA) AND ($profile != "") AND diaspora_is_redmatrix($profile)) { + if (($s == NETWORK_DIASPORA) AND ($profile != "") AND diaspora::is_redmatrix($profile)) { $networkname = t("Hubzilla/Redmatrix"); $r = q("SELECT `gserver`.`platform` FROM `gcontact` diff --git a/include/delivery.php b/include/delivery.php index d184fe12e1..1e1dadcd93 100644 --- a/include/delivery.php +++ b/include/delivery.php @@ -522,25 +522,25 @@ function delivery_run(&$argv, &$argc){ if (($target_item['deleted']) && (($target_item['uri'] === $target_item['parent-uri']) || $followup)) { // top-level retraction - logger('delivery: diaspora retract: '.$loc); - + logger('diaspora retract: '.$loc); diaspora::send_retraction($target_item,$owner,$contact,$public_message); //diaspora_send_retraction($target_item,$owner,$contact,$public_message); break; } elseif ($followup) { // send comments and likes to owner to relay + logger('diaspora followup: '.$loc); diaspora::send_followup($target_item,$owner,$contact,$public_message); //diaspora_send_followup($target_item,$owner,$contact,$public_message); break; } elseif ($target_item['uri'] !== $target_item['parent-uri']) { // we are the relay - send comments, likes and relayable_retractions to our conversants - logger('delivery: diaspora relay: '.$loc); + logger('diaspora relay: '.$loc); diaspora::send_relay($target_item,$owner,$contact,$public_message); //diaspora_send_relay($target_item,$owner,$contact,$public_message); break; } elseif ($top_level && !$walltowall) { // currently no workable solution for sending walltowall - logger('delivery: diaspora status: '.$loc); + logger('diaspora status: '.$loc); diaspora::send_status($target_item,$owner,$contact,$public_message); //diaspora_send_status($target_item,$owner,$contact,$public_message); break; diff --git a/include/diaspora2.php b/include/diaspora2.php index f6b8b9a704..5c17754e80 100644 --- a/include/diaspora2.php +++ b/include/diaspora2.php @@ -460,6 +460,8 @@ class diaspora { * @return string The public key */ private function key($handle) { + $handle = strval($handle); + logger("Fetching diaspora key for: ".$handle); $r = self::person_by_handle($handle); @@ -1699,7 +1701,7 @@ class diaspora { } // Check if the sender is the thread owner - $p = q("SELECT `author-link`, `origin` FROM `item` WHERE `id` = %d", + $p = q("SELECT `id`, `author-link`, `origin` FROM `item` WHERE `id` = %d", intval($r[0]["parent"])); // Only delete it if the parent author really fits @@ -1716,7 +1718,7 @@ class diaspora { ); delete_thread($r[0]["id"], $r[0]["parent-uri"]); - logger("Deleted target ".$target_guid." from user ".$importer["uid"], LOGGER_DEBUG); + logger("Deleted target ".$target_guid." (".$r[0]["id"].") from user ".$importer["uid"]." parent: ".$p[0]["id"], LOGGER_DEBUG); // Now check if the retraction needs to be relayed by us if($p[0]["origin"]) { @@ -1727,6 +1729,8 @@ class diaspora { intval($r[0]["id"]), dbesc(json_encode($data)) ); + $s = q("select * from sign where retract_iid = %d", intval($r[0]["id"])); + logger("Stored signatur for item ".$r[0]["id"]." - ".print_r($s, true), LOGGER_DEBUG); // notify others proc_run("php", "include/notifier.php", "drop", $r[0]["id"]); @@ -2341,6 +2345,8 @@ class diaspora { $type = "comment"; } + logger("Got relayable data ".$type." for item ".$item["guid"]." (".$item["id"].")", LOGGER_DEBUG); + // fetch the original signature $r = q("SELECT `signed_text`, `signature`, `signer` FROM `sign` WHERE `".$sql_sign_id."` = %d LIMIT 1", diff --git a/include/follow.php b/include/follow.php index 3af629536d..6eab7e12fa 100644 --- a/include/follow.php +++ b/include/follow.php @@ -303,7 +303,7 @@ function new_contact($uid,$url,$interactive = false) { } if($contact['network'] == NETWORK_DIASPORA) { require_once('include/diaspora.php'); - $ret = diaspora_share($a->user,$contact); + $ret = diaspora::send_share($a->user,$contact); logger('mod_follow: diaspora_share returns: ' . $ret); } } diff --git a/include/notifier.php b/include/notifier.php index 6c42f19c6a..e65da3adf2 100644 --- a/include/notifier.php +++ b/include/notifier.php @@ -536,7 +536,7 @@ function notifier_run(&$argv, &$argc){ if($public_message) { if (!$followup AND $top_level) - $r0 = diaspora_fetch_relay(); + $r0 = diaspora::relay_list(); else $r0 = array(); @@ -629,11 +629,11 @@ function notifier_run(&$argv, &$argc){ } // If the item was deleted, clean up the `sign` table - if($target_item['deleted']) { + /* if($target_item['deleted']) { $r = q("DELETE FROM sign where `retract_iid` = %d", intval($target_item['id']) ); - } + } */ logger('notifier: calling hooks', LOGGER_DEBUG); diff --git a/include/queue.php b/include/queue.php index 183ce0f9cd..878c149731 100644 --- a/include/queue.php +++ b/include/queue.php @@ -193,7 +193,7 @@ function queue_run(&$argv, &$argc){ case NETWORK_DIASPORA: if($contact['notify']) { logger('queue: diaspora_delivery: item '.$q_item['id'].' for '.$contact['name'].' <'.$contact['url'].'>'); - $deliver_status = diaspora_transmit($owner,$contact,$data,$public,true); + $deliver_status = diaspora::transmit($owner,$contact,$data,$public,true); if($deliver_status == (-1)) { update_queue_time($q_item['id']); From 2446c56e5099a2909c17a1bfec874d888385d454 Mon Sep 17 00:00:00 2001 From: Michael Vogel Date: Mon, 14 Mar 2016 20:04:17 +0100 Subject: [PATCH 043/211] Conversations are working now too --- include/diaspora2.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/include/diaspora2.php b/include/diaspora2.php index 5c17754e80..c0e054c384 100644 --- a/include/diaspora2.php +++ b/include/diaspora2.php @@ -918,7 +918,7 @@ class diaspora { return $message_id; } - private function receive_conversation_message($importer, $contact, $data, $msg, $mesg) { + private function receive_conversation_message($importer, $contact, $data, $msg, $mesg, $conversation) { $guid = notags(unxmlify($data->guid)); $subject = notags(unxmlify($data->subject)); $author = notags(unxmlify($data->author)); @@ -1086,7 +1086,7 @@ class diaspora { } foreach($messages as $mesg) - self::receive_conversation_message($importer, $contact, $data, $msg, $mesg); + self::receive_conversation_message($importer, $contact, $data, $msg, $mesg, $conversation); return true; } From 07bd89c35ff6f22ea46c58e5127acb0cf6f4f8f5 Mon Sep 17 00:00:00 2001 From: Michael Vogel Date: Mon, 14 Mar 2016 20:53:44 +0100 Subject: [PATCH 044/211] Removed just more old diaspora function calls. --- database.sql | 11 ------- doc/database.md | 1 - doc/database/db_dsprphotoq.md | 11 ------- include/cron.php | 4 --- include/dbstructure.php | 11 ------- include/delivery.php | 5 ---- include/diaspora2.php | 2 +- include/dsprphotoq.php | 55 ----------------------------------- include/follow.php | 2 +- mod/dfrn_confirm.php | 4 +-- mod/p.php | 6 ++-- mod/receive.php | 3 -- object/Item.php | 2 +- 13 files changed, 8 insertions(+), 109 deletions(-) delete mode 100644 doc/database/db_dsprphotoq.md delete mode 100644 include/dsprphotoq.php diff --git a/database.sql b/database.sql index 25faf0f4c0..02e5c9b378 100644 --- a/database.sql +++ b/database.sql @@ -201,17 +201,6 @@ CREATE TABLE IF NOT EXISTS `deliverq` ( PRIMARY KEY(`id`) ) DEFAULT CHARSET=utf8; --- --- TABLE dsprphotoq --- -CREATE TABLE IF NOT EXISTS `dsprphotoq` ( - `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, - PRIMARY KEY(`id`) -) DEFAULT CHARSET=utf8; - -- -- TABLE event -- diff --git a/doc/database.md b/doc/database.md index e37df05e09..f48404c17d 100644 --- a/doc/database.md +++ b/doc/database.md @@ -15,7 +15,6 @@ Database Tables | [contact](help/database/db_contact) | contact table | | [conv](help/database/db_conv) | private messages | | [deliverq](help/database/db_deliverq) | | -| [dsprphotoq](help/database/db_dsprphotoq) | | | [event](help/database/db_event) | Events | | [fcontact](help/database/db_fcontact) | friend suggestion stuff | | [ffinder](help/database/db_ffinder) | friend suggestion stuff | diff --git a/doc/database/db_dsprphotoq.md b/doc/database/db_dsprphotoq.md deleted file mode 100644 index 6af4d030e0..0000000000 --- a/doc/database/db_dsprphotoq.md +++ /dev/null @@ -1,11 +0,0 @@ -Table dsprphotoq -================ - -| Field | Description | Type | Null | Key | Default | Extra | -|---------|------------------|------------------|------|-----|---------|----------------| -| id | sequential ID | int(10) unsigned | NO | PRI | NULL | auto_increment | -| uid | | int(11) | NO | | 0 | | -| msg | | mediumtext | NO | | NULL | | -| attempt | | tinyint(4) | NO | | 0 | | - -Return to [database documentation](help/database) diff --git a/include/cron.php b/include/cron.php index db7d44be0b..60c62786e6 100644 --- a/include/cron.php +++ b/include/cron.php @@ -71,10 +71,6 @@ function cron_run(&$argv, &$argc){ proc_run('php',"include/queue.php"); - // run diaspora photo queue process in the background - - proc_run('php',"include/dsprphotoq.php"); - // run the process to discover global contacts in the background proc_run('php',"include/discover_poco.php"); diff --git a/include/dbstructure.php b/include/dbstructure.php index ddf036f2c1..e5e748bb24 100644 --- a/include/dbstructure.php +++ b/include/dbstructure.php @@ -537,17 +537,6 @@ function db_definition() { "PRIMARY" => array("id"), ) ); - $database["dsprphotoq"] = array( - "fields" => array( - "id" => array("type" => "int(10) unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1"), - "uid" => array("type" => "int(11)", "not null" => "1", "default" => "0"), - "msg" => array("type" => "mediumtext", "not null" => "1"), - "attempt" => array("type" => "tinyint(4)", "not null" => "1", "default" => "0"), - ), - "indexes" => array( - "PRIMARY" => array("id"), - ) - ); $database["event"] = array( "fields" => array( "id" => array("type" => "int(11)", "not null" => "1", "extra" => "auto_increment", "primary" => "1"), diff --git a/include/delivery.php b/include/delivery.php index 1e1dadcd93..9ac9f2391b 100644 --- a/include/delivery.php +++ b/include/delivery.php @@ -501,7 +501,6 @@ function delivery_run(&$argv, &$argc){ if ($mail) { diaspora::send_mail($item,$owner,$contact); - //diaspora_send_mail($item,$owner,$contact); break; } @@ -524,25 +523,21 @@ function delivery_run(&$argv, &$argc){ // top-level retraction logger('diaspora retract: '.$loc); diaspora::send_retraction($target_item,$owner,$contact,$public_message); - //diaspora_send_retraction($target_item,$owner,$contact,$public_message); break; } elseif ($followup) { // send comments and likes to owner to relay logger('diaspora followup: '.$loc); diaspora::send_followup($target_item,$owner,$contact,$public_message); - //diaspora_send_followup($target_item,$owner,$contact,$public_message); break; } elseif ($target_item['uri'] !== $target_item['parent-uri']) { // we are the relay - send comments, likes and relayable_retractions to our conversants logger('diaspora relay: '.$loc); diaspora::send_relay($target_item,$owner,$contact,$public_message); - //diaspora_send_relay($target_item,$owner,$contact,$public_message); break; } elseif ($top_level && !$walltowall) { // currently no workable solution for sending walltowall logger('diaspora status: '.$loc); diaspora::send_status($target_item,$owner,$contact,$public_message); - //diaspora_send_status($target_item,$owner,$contact,$public_message); break; } diff --git a/include/diaspora2.php b/include/diaspora2.php index c0e054c384..1a354e9cf2 100644 --- a/include/diaspora2.php +++ b/include/diaspora2.php @@ -2093,7 +2093,7 @@ class diaspora { return self::build_and_transmit($owner, $contact, "retraction", $message); } - private function is_reshare($body) { + public static function is_reshare($body) { $body = trim($body); // Skip if it isn't a pure repeated messages diff --git a/include/dsprphotoq.php b/include/dsprphotoq.php deleted file mode 100644 index 0d8088d4bd..0000000000 --- a/include/dsprphotoq.php +++ /dev/null @@ -1,55 +0,0 @@ - 0, "page-flags" => PAGE_FREELOVE); - else - $r = q("SELECT * FROM user WHERE uid = %d", - intval($dphoto['uid'])); - - if(!$r) { - logger("diaspora photo queue: user " . $dphoto['uid'] . " not found"); - return; - } - - $ret = diaspora_dispatch($r[0],unserialize($dphoto['msg']),$dphoto['attempt']); - q("DELETE FROM dsprphotoq WHERE id = %d", - intval($dphoto['id']) - ); - } -} - - -if (array_search(__file__,get_included_files())===0){ - dsprphotoq_run($_SERVER["argv"],$_SERVER["argc"]); - killme(); -} diff --git a/include/follow.php b/include/follow.php index 6eab7e12fa..d0411a466a 100644 --- a/include/follow.php +++ b/include/follow.php @@ -304,7 +304,7 @@ function new_contact($uid,$url,$interactive = false) { if($contact['network'] == NETWORK_DIASPORA) { require_once('include/diaspora.php'); $ret = diaspora::send_share($a->user,$contact); - logger('mod_follow: diaspora_share returns: ' . $ret); + logger('share returns: '.$ret); } } diff --git a/mod/dfrn_confirm.php b/mod/dfrn_confirm.php index 68950ec285..cc09021dca 100644 --- a/mod/dfrn_confirm.php +++ b/mod/dfrn_confirm.php @@ -427,8 +427,8 @@ function dfrn_confirm_post(&$a,$handsfree = null) { if(($contact) && ($contact['network'] === NETWORK_DIASPORA)) { require_once('include/diaspora.php'); - $ret = diaspora_share($user[0],$r[0]); - logger('mod_follow: diaspora_share returns: ' . $ret); + $ret = diaspora::send_share($user[0],$r[0]); + logger('share returns: ' . $ret); } // Send a new friend post if we are allowed to... diff --git a/mod/p.php b/mod/p.php index 92b72dc1ce..20d6cfdbaf 100644 --- a/mod/p.php +++ b/mod/p.php @@ -28,14 +28,14 @@ function p_init($a){ $post = array(); - $reshared = diaspora_is_reshare($item[0]["body"]); + $reshared = diaspora::is_reshare($item[0]["body"]); if ($reshared) { $nodename = "reshare"; $post["root_diaspora_id"] = $reshared["root_handle"]; $post["root_guid"] = $reshared["root_guid"]; $post["guid"] = $item[0]["guid"]; - $post["diaspora_handle"] = diaspora_handle_from_contact($item[0]["contact-id"]); + $post["diaspora_handle"] = diaspora::handle_from_contact($item[0]["contact-id"]); $post["public"] = (!$item[0]["private"] ? 'true':'false'); $post["created_at"] = datetime_convert('UTC','UTC',$item[0]["created"]); } else { @@ -48,7 +48,7 @@ function p_init($a){ $nodename = "status_message"; $post["raw_message"] = str_replace("&", "&", $body); $post["guid"] = $item[0]["guid"]; - $post["diaspora_handle"] = diaspora_handle_from_contact($item[0]["contact-id"]); + $post["diaspora_handle"] = diaspora::handle_from_contact($item[0]["contact-id"]); $post["public"] = (!$item[0]["private"] ? 'true':'false'); $post["created_at"] = datetime_convert('UTC','UTC',$item[0]["created"]); $post["provider_display_name"] = $item[0]["app"]; diff --git a/mod/receive.php b/mod/receive.php index 051ea8c25c..4991ac47e8 100644 --- a/mod/receive.php +++ b/mod/receive.php @@ -54,7 +54,6 @@ function receive_post(&$a) { logger('mod-diaspora: message is okay', LOGGER_DEBUG); $msg = diaspora::decode($importer,$xml); - //$msg = diaspora_decode($importer,$xml); logger('mod-diaspora: decoded', LOGGER_DEBUG); @@ -68,10 +67,8 @@ function receive_post(&$a) { $ret = 0; if($public) { diaspora::dispatch_public($msg); - //diaspora_dispatch_public($msg); } else { $ret = diaspora::dispatch($importer,$msg); - //$ret = diaspora_dispatch($importer,$msg); } http_status_exit(($ret) ? $ret : 200); diff --git a/object/Item.php b/object/Item.php index e9c96cf159..59659cdaff 100644 --- a/object/Item.php +++ b/object/Item.php @@ -324,7 +324,7 @@ class Item extends BaseObject { // Diaspora isn't able to do likes on comments - but red does if (($item["item_network"] == NETWORK_DIASPORA) AND ($indent == 'comment') AND - !diaspora_is_redmatrix($item["owner-link"]) AND isset($buttons["like"])) + !diaspora::is_redmatrix($item["owner-link"]) AND isset($buttons["like"])) unset($buttons["like"]); // Diaspora doesn't has multithreaded comments From cdbf1a7556ff9c4b59b107f50974b98e183676ae Mon Sep 17 00:00:00 2001 From: Michael Vogel Date: Mon, 14 Mar 2016 22:10:26 +0100 Subject: [PATCH 045/211] Profile update is now working with the new function as well --- include/diaspora2.php | 86 ++++++++++++++++++++++++++++++++++- include/profile_update.php | 92 +------------------------------------- 2 files changed, 85 insertions(+), 93 deletions(-) diff --git a/include/diaspora2.php b/include/diaspora2.php index 1a354e9cf2..75cedeccd1 100644 --- a/include/diaspora2.php +++ b/include/diaspora2.php @@ -2058,7 +2058,7 @@ class diaspora { } - private function build_and_transmit($owner, $contact, $type, $message, $public_batch = false, $guid = "") { + private function build_and_transmit($owner, $contact, $type, $message, $public_batch = false, $guid = "", $spool = false) { $data = array("XML" => array("post" => array($type => $message))); @@ -2069,7 +2069,11 @@ class diaspora { $slap = self::build_message($msg, $owner, $contact, $owner['uprvkey'], $contact['pubkey'], $public_batch); - $return_code = self::transmit($owner, $contact, $slap, $public_batch, false, $guid); + if ($spool) { + add_to_queue($contact['id'], NETWORK_DIASPORA, $slap, $public_batch); + return true; + } else + $return_code = self::transmit($owner, $contact, $slap, $public_batch, false, $guid); logger("guid: ".$item["guid"]." result ".$return_code, LOGGER_DEBUG); @@ -2467,5 +2471,83 @@ class diaspora { return self::build_and_transmit($owner, $contact, $type, $message, false, $item["guid"]); } + + public static function send_profile($uid) { + + if (!$uid) + return; + + $recips = q("SELECT `id`,`name`,`network`,`pubkey`,`notify` FROM `contact` WHERE `network` = '%s' + AND `uid` = %d AND `rel` != %d", + dbesc(NETWORK_DIASPORA), + intval($uid), + intval(CONTACT_IS_SHARING) + ); + if (!$recips) + return; + + $r = q("SELECT `profile`.`uid` AS `profile_uid`, `profile`.* , `user`.*, `user`.`prvkey` AS `uprvkey`, `contact`.`addr` + FROM `profile` + INNER JOIN `user` ON `profile`.`uid` = `user`.`uid` + INNER JOIN `contact` ON `profile`.`uid` = `contact`.`uid` + WHERE `user`.`uid` = %d AND `profile`.`is-default` AND `contact`.`self` LIMIT 1", + intval($uid) + ); + + if (!$r) + return; + + $profile = $r[0]; + + $handle = $profile["addr"]; + $first = ((strpos($profile['name'],' ') + ? trim(substr($profile['name'],0,strpos($profile['name'],' '))) : $profile['name'])); + $last = (($first === $profile['name']) ? '' : trim(substr($profile['name'], strlen($first)))); + $large = App::get_baseurl().'/photo/custom/300/'.$profile['uid'].'.jpg'; + $medium = App::get_baseurl().'/photo/custom/100/'.$profile['uid'].'.jpg'; + $small = App::get_baseurl().'/photo/custom/50/' .$profile['uid'].'.jpg'; + $searchable = (($profile['publish'] && $profile['net-publish']) ? 'true' : 'false'); + + if ($searchable === 'true') { + $dob = '1000-00-00'; + + if (($profile['dob']) && ($profile['dob'] != '0000-00-00')) + $dob = ((intval($profile['dob'])) ? intval($profile['dob']) : '1000') .'-'. datetime_convert('UTC','UTC',$profile['dob'],'m-d'); + + $about = $profile['about']; + $about = strip_tags(bbcode($about)); + + $location = formatted_location($profile); + $tags = ''; + if ($profile['pub_keywords']) { + $kw = str_replace(',',' ',$profile['pub_keywords']); + $kw = str_replace(' ',' ',$kw); + $arr = explode(' ',$profile['pub_keywords']); + if (count($arr)) { + for($x = 0; $x < 5; $x ++) { + if (trim($arr[$x])) + $tags .= '#'. trim($arr[$x]) .' '; + } + } + } + $tags = trim($tags); + } + + $message = array("diaspora_handle" => $handle, + "first_name" => $first, + "last_name" => $last, + "image_url" => $large, + "image_url_medium" => $medium, + "image_url_small" => $small, + "birthday" => $dob, + "gender" => $profile['gender'], + "bio" => $about, + "location" => $location, + "searchable" => $searchable, + "tag_string" => $tags); + + foreach($recips as $recip) + self::build_and_transmit($profile, $recip, "profile", $message, false, "", true); + } } ?> diff --git a/include/profile_update.php b/include/profile_update.php index 7cc72cc866..399150f21c 100644 --- a/include/profile_update.php +++ b/include/profile_update.php @@ -1,96 +1,6 @@ get_baseurl() . '/profile/' . $a->user['nickname']; -// if($url && strlen(get_config('system','directory'))) -// proc_run('php',"include/directory.php","$url"); - - $recips = q("SELECT `id`,`name`,`network`,`pubkey`,`notify` FROM `contact` WHERE `network` = '%s' - AND `uid` = %d AND `rel` != %d ", - dbesc(NETWORK_DIASPORA), - intval(local_user()), - intval(CONTACT_IS_SHARING) - ); - if(! count($recips)) - return; - - $r = q("SELECT `profile`.`uid` AS `profile_uid`, `profile`.* , `user`.* FROM `profile` - INNER JOIN `user` ON `profile`.`uid` = `user`.`uid` - WHERE `user`.`uid` = %d AND `profile`.`is-default` = 1 LIMIT 1", - intval(local_user()) - ); - - if(! count($r)) - return; - $profile = $r[0]; - - $handle = xmlify($a->user['nickname'] . '@' . substr($a->get_baseurl(), strpos($a->get_baseurl(),'://') + 3)); - $first = xmlify(((strpos($profile['name'],' ')) - ? trim(substr($profile['name'],0,strpos($profile['name'],' '))) : $profile['name'])); - $last = xmlify((($first === $profile['name']) ? '' : trim(substr($profile['name'],strlen($first))))); - $large = xmlify($a->get_baseurl() . '/photo/custom/300/' . $profile['uid'] . '.jpg'); - $medium = xmlify($a->get_baseurl() . '/photo/custom/100/' . $profile['uid'] . '.jpg'); - $small = xmlify($a->get_baseurl() . '/photo/custom/50/' . $profile['uid'] . '.jpg'); - $searchable = xmlify((($profile['publish'] && $profile['net-publish']) ? 'true' : 'false' )); -// $searchable = 'true'; - - if($searchable === 'true') { - $dob = '1000-00-00'; - - if(($profile['dob']) && ($profile['dob'] != '0000-00-00')) - $dob = ((intval($profile['dob'])) ? intval($profile['dob']) : '1000') . '-' . datetime_convert('UTC','UTC',$profile['dob'],'m-d'); - $gender = xmlify($profile['gender']); - $about = xmlify($profile['about']); - require_once('include/bbcode.php'); - $about = xmlify(strip_tags(bbcode($about))); - $location = formatted_location($profile); - $location = xmlify($location); - $tags = ''; - if($profile['pub_keywords']) { - $kw = str_replace(',',' ',$profile['pub_keywords']); - $kw = str_replace(' ',' ',$kw); - $arr = explode(' ',$profile['pub_keywords']); - if(count($arr)) { - for($x = 0; $x < 5; $x ++) { - if(trim($arr[$x])) - $tags .= '#' . trim($arr[$x]) . ' '; - } - } - } - $tags = xmlify(trim($tags)); - } - - $tpl = get_markup_template('diaspora_profile.tpl'); - - $msg = replace_macros($tpl,array( - '$handle' => $handle, - '$first' => $first, - '$last' => $last, - '$large' => $large, - '$medium' => $medium, - '$small' => $small, - '$dob' => $dob, - '$gender' => $gender, - '$about' => $about, - '$location' => $location, - '$searchable' => $searchable, - '$tags' => $tags - )); - logger('profile_change: ' . $msg, LOGGER_ALL); - - foreach($recips as $recip) { - $msgtosend = 'xml=' . urlencode(urlencode(diaspora_msg_build($msg,$a->user,$recip,$a->user['prvkey'],$recip['pubkey'],false))); - add_to_queue($recip['id'],NETWORK_DIASPORA,$msgtosend,false); - } + diaspora::send_profile(local_user()); } From 2898749cc6c453a902a94182b5e4e34ec543749c Mon Sep 17 00:00:00 2001 From: Michael Vogel Date: Mon, 14 Mar 2016 23:11:43 +0100 Subject: [PATCH 046/211] The Diaspora class is now productive --- include/diaspora.php | 5338 ++++++++++++++++++----------------------- include/diaspora2.php | 2553 -------------------- 2 files changed, 2359 insertions(+), 5532 deletions(-) delete mode 100644 include/diaspora2.php diff --git a/include/diaspora.php b/include/diaspora.php index 11fe2c9b57..75cedeccd1 100644 --- a/include/diaspora.php +++ b/include/diaspora.php @@ -1,1702 +1,927 @@ 0, "page-flags" => PAGE_FREELOVE); - $result = diaspora_dispatch($importer,$msg); - logger("Dispatcher reported ".$result, LOGGER_DEBUG); + $serverdata = get_config("system", "relay_server"); + if ($serverdata == "") + return array(); - // Now distribute it to the followers - $r = q("SELECT `user`.* FROM `user` WHERE `user`.`uid` IN - ( SELECT `contact`.`uid` FROM `contact` WHERE `contact`.`network` = '%s' AND `contact`.`addr` = '%s' ) - AND `account_expired` = 0 AND `account_removed` = 0 ", - dbesc(NETWORK_DIASPORA), - dbesc($msg['author']) - ); - if(count($r)) { - foreach($r as $rr) { - logger('diaspora_public: delivering to: ' . $rr['username']); - diaspora_dispatch($rr,$msg); + $relay = array(); + + $servers = explode(",", $serverdata); + + foreach($servers AS $server) { + $server = trim($server); + $batch = $server."/receive/public"; + + $relais = q("SELECT `batch`, `id`, `name`,`network` FROM `contact` WHERE `uid` = 0 AND `batch` = '%s' LIMIT 1", dbesc($batch)); + + if (!$relais) { + $addr = "relay@".str_replace("http://", "", normalise_link($server)); + + $r = q("INSERT INTO `contact` (`uid`, `created`, `name`, `nick`, `addr`, `url`, `nurl`, `batch`, `network`, `rel`, `blocked`, `pending`, `writable`, `name-date`, `uri-date`, `avatar-date`) + VALUES (0, '%s', '%s', 'relay', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, '%s', '%s', '%s')", + datetime_convert(), + dbesc($addr), + dbesc($addr), + dbesc($server), + dbesc(normalise_link($server)), + dbesc($batch), + dbesc(NETWORK_DIASPORA), + intval(CONTACT_IS_FOLLOWER), + dbesc(datetime_convert()), + dbesc(datetime_convert()), + dbesc(datetime_convert()) + ); + + $relais = q("SELECT `batch`, `id`, `name`,`network` FROM `contact` WHERE `uid` = 0 AND `batch` = '%s' LIMIT 1", dbesc($batch)); + if ($relais) + $relay[] = $relais[0]; + } else + $relay[] = $relais[0]; } - } - else - logger('diaspora_public: no subscribers for '.$msg["author"].' '.print_r($msg, true)); -} - - -function diaspora_dispatch($importer,$msg,$attempt=1) { - - $ret = 0; - - $enabled = intval(get_config('system','diaspora_enabled')); - if(! $enabled) { - logger('mod-diaspora: disabled'); - return; + return $relay; } - $data = $msg; + function repair_signature($signature, $handle = "", $level = 1) { - // php doesn't like dashes in variable names + if ($signature == "") + return ($signature); - $msg['message'] = str_replace( - array('',''), - array('',''), - $msg['message']); + 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); - - $parsed_xml = parse_xml_string($msg['message'],false); - - $xmlbase = $parsed_xml->post; - - logger('diaspora_dispatch: ' . print_r($xmlbase,true), LOGGER_DEBUG); - - - if($xmlbase->request) { - $tempfile = tempnam(get_temppath(), "diaspora-request"); - file_put_contents($tempfile, json_encode($data)); - $ret = diaspora_request($importer,$xmlbase->request); - } - elseif($xmlbase->status_message) { - //$tempfile = tempnam(get_temppath(), "diaspora-status_message"); - //file_put_contents($tempfile, json_encode($data)); - $ret = diaspora_post($importer,$xmlbase->status_message,$msg); - } - elseif($xmlbase->profile) { - //$tempfile = tempnam(get_temppath(), "diaspora-profile"); - //file_put_contents($tempfile, json_encode($data)); - $ret = diaspora_profile($importer,$xmlbase->profile,$msg); - } - elseif($xmlbase->comment) { - //$tempfile = tempnam(get_temppath(), "diaspora-comment"); - //file_put_contents($tempfile, json_encode($data)); - $ret = diaspora_comment($importer,$xmlbase->comment,$msg); - } - elseif($xmlbase->like) { - //$tempfile = tempnam(get_temppath(), "diaspora-like"); - //file_put_contents($tempfile, json_encode($data)); - $ret = diaspora_like($importer,$xmlbase->like,$msg); - } - elseif($xmlbase->asphoto) { - $tempfile = tempnam(get_temppath(), "diaspora-asphoto"); - file_put_contents($tempfile, json_encode($data)); - $ret = diaspora_asphoto($importer,$xmlbase->asphoto,$msg); - } - elseif($xmlbase->reshare) { - //$tempfile = tempnam(get_temppath(), "diaspora-reshare"); - //file_put_contents($tempfile, json_encode($data)); - $ret = diaspora_reshare($importer,$xmlbase->reshare,$msg); - } - elseif($xmlbase->retraction) { - //$tempfile = tempnam(get_temppath(), "diaspora-retraction"); - //file_put_contents($tempfile, json_encode($data)); - $ret = diaspora_retraction($importer,$xmlbase->retraction,$msg); - } - elseif($xmlbase->signed_retraction) { - //$tempfile = tempnam(get_temppath(), "diaspora-signed_retraction"); - //file_put_contents($tempfile, json_encode($data)); - $ret = diaspora_signed_retraction($importer,$xmlbase->signed_retraction,$msg); - } - elseif($xmlbase->relayable_retraction) { - //$tempfile = tempnam(get_temppath(), "diaspora-relayable_retraction"); - //file_put_contents($tempfile, json_encode($data)); - $ret = diaspora_signed_retraction($importer,$xmlbase->relayable_retraction,$msg); - } - elseif($xmlbase->photo) { - //$tempfile = tempnam(get_temppath(), "diaspora-photo"); - //file_put_contents($tempfile, json_encode($data)); - $ret = diaspora_photo($importer,$xmlbase->photo,$msg,$attempt); - } - elseif($xmlbase->conversation) { - $tempfile = tempnam(get_temppath(), "diaspora-conversation"); - file_put_contents($tempfile, json_encode($data)); - $ret = diaspora_conversation($importer,$xmlbase->conversation,$msg); - } - elseif($xmlbase->message) { - $tempfile = tempnam(get_temppath(), "diaspora-message"); - file_put_contents($tempfile, json_encode($data)); - $ret = diaspora_message($importer,$xmlbase->message,$msg); - } - elseif($xmlbase->participation) { - //$tempfile = tempnam(get_temppath(), "diaspora-participation"); - //file_put_contents($tempfile, json_encode($data)); - $ret = diaspora_participation($importer,$xmlbase->participation); - } - elseif($xmlbase->poll_participation) { - //$tempfile = tempnam(get_temppath(), "diaspora-poll_participation"); - //file_put_contents($tempfile, json_encode($data)); - $ret = diaspora_participation($importer,$xmlbase->poll_participation); - } - else { - $tempfile = tempnam(get_temppath(), "diaspora-unknown"); - file_put_contents($tempfile, json_encode($data)); - logger('diaspora_dispatch: unknown message type: ' . print_r($xmlbase,true)); - } - return $ret; -} - -function diaspora_handle_from_contact($contact_id) { - $handle = False; - - logger("diaspora_handle_from_contact: contact id is " . $contact_id, LOGGER_DEBUG); - - $r = q("SELECT network, addr, self, url, nick FROM contact WHERE id = %d", - intval($contact_id) - ); - if($r) { - $contact = $r[0]; - - logger("diaspora_handle_from_contact: contact 'self' = " . $contact['self'] . " 'url' = " . $contact['url'], LOGGER_DEBUG); - - if($contact['network'] === NETWORK_DIASPORA) { - $handle = $contact['addr']; - -// logger("diaspora_handle_from_contact: contact id is a Diaspora person, handle = " . $handle, LOGGER_DEBUG); + // Do a recursive call to be able to fix even multiple levels + if ($level < 10) + $signature = self::repair_signature($signature, $handle, ++$level); } - elseif(($contact['network'] === NETWORK_DFRN) || ($contact['self'] == 1)) { - $baseurl_start = strpos($contact['url'],'://') + 3; - $baseurl_length = strpos($contact['url'],'/profile') - $baseurl_start; // allows installations in a subdirectory--not sure how Diaspora will handle - $baseurl = substr($contact['url'], $baseurl_start, $baseurl_length); - $handle = $contact['nick'] . '@' . $baseurl; -// logger("diaspora_handle_from_contact: contact id is a DFRN person, handle = " . $handle, LOGGER_DEBUG); - } + return($signature); } - return $handle; -} + /** + * @brief: Decodes incoming Diaspora message + * + * @param array $importer from user table + * @param string $xml urldecoded Diaspora salmon + * + * @return array + * 'message' -> decoded Diaspora XML message + * 'author' -> author diaspora handle + * 'key' -> author public key (converted to pkcs#8) + */ + function decode($importer, $xml) { -function diaspora_get_contact_by_handle($uid,$handle) { - $r = q("SELECT * FROM `contact` WHERE `network` = '%s' AND `uid` = %d AND `addr` = '%s' LIMIT 1", - dbesc(NETWORK_DIASPORA), - intval($uid), - dbesc($handle) - ); - if($r && count($r)) - return $r[0]; + $public = false; + $basedom = parse_xml_string($xml); - $handle_parts = explode("@", $handle); - $nurl_sql = '%%://' . $handle_parts[1] . '%%/profile/' . $handle_parts[0]; - $r = q("SELECT * FROM contact WHERE network = '%s' AND uid = %d AND nurl LIKE '%s' LIMIT 1", - dbesc(NETWORK_DFRN), - intval($uid), - dbesc($nurl_sql) - ); - if($r && count($r)) - return $r[0]; + if (!is_object($basedom)) + return false; - return false; -} + $children = $basedom->children('https://joindiaspora.com/protocol'); -function find_diaspora_person_by_handle($handle) { + if($children->header) { + $public = true; + $author_link = str_replace('acct:','',$children->header->author_id); + } else { - $person = false; - $update = false; - $got_lock = false; + $encrypted_header = json_decode(base64_decode($children->encrypted_header)); - $endlessloop = 0; - $maxloops = 10; + $encrypted_aes_key_bundle = base64_decode($encrypted_header->aes_key); + $ciphertext = base64_decode($encrypted_header->ciphertext); - do { - $r = q("select * from fcontact where network = '%s' and addr = '%s' limit 1", + $outer_key_bundle = ''; + openssl_private_decrypt($encrypted_aes_key_bundle,$outer_key_bundle,$importer['prvkey']); + + $j_outer_key_bundle = json_decode($outer_key_bundle); + + $outer_iv = base64_decode($j_outer_key_bundle->iv); + $outer_key = base64_decode($j_outer_key_bundle->key); + + $decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $outer_key, $ciphertext, MCRYPT_MODE_CBC, $outer_iv); + + + $decrypted = pkcs5_unpad($decrypted); + + /** + * $decrypted now contains something like + * + * + * 8e+G2+ET8l5BPuW0sVTnQw== + * UvSMb4puPeB14STkcDWq+4QE302Edu15oaprAQSkLKU= + * galaxor@diaspora.priateship.org + * + */ + + logger('decrypted: '.$decrypted, LOGGER_DEBUG); + $idom = parse_xml_string($decrypted,false); + + $inner_iv = base64_decode($idom->iv); + $inner_aes_key = base64_decode($idom->aes_key); + + $author_link = str_replace('acct:','',$idom->author_id); + } + + $dom = $basedom->children(NAMESPACE_SALMON_ME); + + // figure out where in the DOM tree our data is hiding + + if($dom->provenance->data) + $base = $dom->provenance; + elseif($dom->env->data) + $base = $dom->env; + elseif($dom->data) + $base = $dom; + + if (!$base) { + logger('unable to locate salmon data in xml'); + http_status_exit(400); + } + + + // Stash the signature away for now. We have to find their key or it won't be good for anything. + $signature = base64url_decode($base->sig); + + // unpack the data + + // strip whitespace so our data element will return to one big base64 blob + $data = str_replace(array(" ","\t","\r","\n"),array("","","",""),$base->data); + + + // stash away some other stuff for later + + $type = $base->data[0]->attributes()->type[0]; + $keyhash = $base->sig[0]->attributes()->keyhash[0]; + $encoding = $base->encoding; + $alg = $base->alg; + + + $signed_data = $data.'.'.base64url_encode($type).'.'.base64url_encode($encoding).'.'.base64url_encode($alg); + + + // decode the data + $data = base64url_decode($data); + + + if($public) + $inner_decrypted = $data; + else { + + // Decode the encrypted blob + + $inner_encrypted = base64_decode($data); + $inner_decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $inner_aes_key, $inner_encrypted, MCRYPT_MODE_CBC, $inner_iv); + $inner_decrypted = pkcs5_unpad($inner_decrypted); + } + + if (!$author_link) { + logger('Could not retrieve author URI.'); + http_status_exit(400); + } + // Once we have the author URI, go to the web and try to find their public key + // (first this will look it up locally if it is in the fcontact cache) + // This will also convert diaspora public key from pkcs#1 to pkcs#8 + + logger('Fetching key for '.$author_link); + $key = self::key($author_link); + + if (!$key) { + logger('Could not retrieve author key.'); + http_status_exit(400); + } + + $verify = rsa_verify($signed_data,$signature,$key); + + if (!$verify) { + logger('Message did not verify. Discarding.'); + http_status_exit(400); + } + + logger('Message verified.'); + + return array('message' => $inner_decrypted, 'author' => $author_link, 'key' => $key); + + } + + + /** + * @brief Dispatches public messages and find the fitting receivers + * + * @param array $msg The post that will be dispatched + * + * @return bool Was the message accepted? + */ + public static function dispatch_public($msg) { + + $enabled = intval(get_config("system", "diaspora_enabled")); + if (!$enabled) { + logger("diaspora is disabled"); + return false; + } + + // Use a dummy importer to import the data for the public copy + $importer = array("uid" => 0, "page-flags" => PAGE_FREELOVE); + $item_id = self::dispatch($importer,$msg); + + // Now distribute it to the followers + $r = q("SELECT `user`.* FROM `user` WHERE `user`.`uid` IN + (SELECT `contact`.`uid` FROM `contact` WHERE `contact`.`network` = '%s' AND `contact`.`addr` = '%s') + AND NOT `account_expired` AND NOT `account_removed`", + dbesc(NETWORK_DIASPORA), + dbesc($msg["author"]) + ); + if($r) { + foreach($r as $rr) { + logger("delivering to: ".$rr["username"]); + self::dispatch($rr,$msg); + } + } else + logger("No subscribers for ".$msg["author"]." ".print_r($msg, true)); + + return $item_id; + } + + /** + * @brief Dispatches the different message types to the different functions + * + * @param array $importer Array of the importer user + * @param array $msg The post that will be dispatched + * + * @return bool Was the message accepted? + */ + public static function dispatch($importer, $msg) { + + // The sender is the handle of the contact that sent the message. + // This will often be different with relayed messages (for example "like" and "comment") + $sender = $msg["author"]; + + if (!diaspora::valid_posting($msg, $fields)) { + logger("Invalid posting"); + return false; + } + + $type = $fields->getName(); + + logger("Received message type ".$type." from ".$sender." for user ".$importer["uid"], LOGGER_DEBUG); + + switch ($type) { + case "account_deletion": + return self::receive_account_deletion($importer, $fields); + + case "comment": + return self::receive_comment($importer, $sender, $fields); + + case "conversation": + return self::receive_conversation($importer, $msg, $fields); + + case "like": + return self::receive_like($importer, $sender, $fields); + + case "message": + return self::receive_message($importer, $fields); + + case "participation": // Not implemented + return self::receive_participation($importer, $fields); + + case "photo": // Not implemented + return self::receive_photo($importer, $fields); + + case "poll_participation": // Not implemented + return self::receive_poll_participation($importer, $fields); + + case "profile": + return self::receive_profile($importer, $fields); + + case "request": + return self::receive_request($importer, $fields); + + case "reshare": + return self::receive_reshare($importer, $fields); + + case "retraction": + return self::receive_retraction($importer, $sender, $fields); + + case "status_message": + return self::receive_status_message($importer, $fields); + + default: + logger("Unknown message type ".$type); + return false; + } + + return true; + } + + /** + * @brief Checks if a posting is valid and fetches the data fields. + * + * This function does not only check the signature. + * It also does the conversion between the old and the new diaspora format. + * + * @param array $msg Array with the XML, the sender handle and the sender signature + * @param object $fields SimpleXML object that contains the posting when it is valid + * + * @return bool Is the posting valid? + */ + private function valid_posting($msg, &$fields) { + + $data = parse_xml_string($msg["message"], false); + + if (!is_object($data)) + return false; + + $first_child = $data->getName(); + + // Is this the new or the old version? + if ($data->getName() == "XML") { + $oldXML = true; + foreach ($data->post->children() as $child) + $element = $child; + } else { + $oldXML = false; + $element = $data; + } + + $type = $element->getName(); + $orig_type = $type; + + // All retractions are handled identically from now on. + // In the new version there will only be "retraction". + if (in_array($type, array("signed_retraction", "relayable_retraction"))) + $type = "retraction"; + + $fields = new SimpleXMLElement("<".$type."/>"); + + $signed_data = ""; + + foreach ($element->children() AS $fieldname => $entry) { + if ($oldXML) { + // Translation for the old XML structure + if ($fieldname == "diaspora_handle") + $fieldname = "author"; + + if ($fieldname == "participant_handles") + $fieldname = "participants"; + + if (in_array($type, array("like", "participation"))) { + if ($fieldname == "target_type") + $fieldname = "parent_type"; + } + + if ($fieldname == "sender_handle") + $fieldname = "author"; + + if ($fieldname == "recipient_handle") + $fieldname = "recipient"; + + if ($fieldname == "root_diaspora_id") + $fieldname = "root_author"; + + if ($type == "retraction") { + if ($fieldname == "post_guid") + $fieldname = "target_guid"; + + if ($fieldname == "type") + $fieldname = "target_type"; + } + } + + if ($fieldname == "author_signature") + $author_signature = base64_decode($entry); + elseif ($fieldname == "parent_author_signature") + $parent_author_signature = base64_decode($entry); + elseif ($fieldname != "target_author_signature") { + if ($signed_data != "") { + $signed_data .= ";"; + $signed_data_parent .= ";"; + } + + $signed_data .= $entry; + } + if (!in_array($fieldname, array("parent_author_signature", "target_author_signature")) OR + ($orig_type == "relayable_retraction")) + xml::copy($entry, $fields, $fieldname); + } + + // This is something that shouldn't happen at all. + if (in_array($type, array("status_message", "reshare", "profile"))) + if ($msg["author"] != $fields->author) { + logger("Message handle is not the same as envelope sender. Quitting this message."); + return false; + } + + // Only some message types have signatures. So we quit here for the other types. + if (!in_array($type, array("comment", "message", "like"))) + return true; + + // No author_signature? This is a must, so we quit. + if (!isset($author_signature)) + return false; + + if (isset($parent_author_signature)) { + $key = self::key($msg["author"]); + + if (!rsa_verify($signed_data, $parent_author_signature, $key, "sha256")) + return false; + } + + $key = self::key($fields->author); + + return rsa_verify($signed_data, $author_signature, $key, "sha256"); + } + + /** + * @brief Fetches the public key for a given handle + * + * @param string $handle The handle + * + * @return string The public key + */ + private function key($handle) { + $handle = strval($handle); + + logger("Fetching diaspora key for: ".$handle); + + $r = self::person_by_handle($handle); + if($r) + return $r["pubkey"]; + + return ""; + } + + /** + * @brief Fetches data for a given handle + * + * @param string $handle The handle + * + * @return array the queried data + */ + private function person_by_handle($handle) { + + $r = q("SELECT * FROM `fcontact` WHERE `network` = '%s' AND `addr` = '%s' LIMIT 1", dbesc(NETWORK_DIASPORA), dbesc($handle) ); - if(count($r)) { + if ($r) { $person = $r[0]; - logger('find_diaspora_person_by handle: in cache ' . print_r($r,true), LOGGER_DEBUG); + logger("In cache ".print_r($r,true), LOGGER_DEBUG); // update record occasionally so it doesn't get stale - $d = strtotime($person['updated'] . ' +00:00'); - if($d < strtotime('now - 14 days')) + $d = strtotime($person["updated"]." +00:00"); + if ($d < strtotime("now - 14 days")) $update = true; } + if (!$person OR $update) { + logger("create or refresh", LOGGER_DEBUG); + $r = probe_url($handle, PROBE_DIASPORA); - // FETCHING PERSON INFORMATION FROM REMOTE SERVER - // - // If the person isn't in our 'fcontact' table, or if he/she is but - // his/her information hasn't been updated for more than 14 days, then - // we want to fetch the person's information from the remote server. - // - // Note that $person isn't changed by this block of code unless the - // person's information has been successfully fetched from the remote - // server. So if $person was 'false' to begin with (because he/she wasn't - // in the local cache), it'll stay false, and if $person held the local - // cache information to begin with, it'll keep that information. That way - // if there's a problem with the remote fetch, we can at least use our - // cached information--it's better than nothing. - - if((! $person) || ($update)) { - // Lock the function to prevent race conditions if multiple items - // come in at the same time from a person who doesn't exist in - // fcontact - // - // Don't loop forever. On the last loop, try to create the contact - // whether the function is locked or not. Maybe the locking thread - // has died or something. At any rate, a duplicate in 'fcontact' - // is a much smaller problem than a deadlocked thread - $got_lock = lock_function('find_diaspora_person_by_handle', false); - if(($endlessloop + 1) >= $maxloops) - $got_lock = true; - - if($got_lock) { - logger('find_diaspora_person_by_handle: create or refresh', LOGGER_DEBUG); - require_once('include/Scrape.php'); - $r = probe_url($handle, PROBE_DIASPORA); - - // Note that Friendica contacts can return a "Diaspora person" - // if Diaspora connectivity is enabled on their server - if((count($r)) && ($r['network'] === NETWORK_DIASPORA)) { - add_fcontact($r,$update); - $person = ($r); - } - - unlock_function('find_diaspora_person_by_handle'); - } - else { - logger('find_diaspora_person_by_handle: couldn\'t lock function', LOGGER_DEBUG); - if(! $person) - block_on_function_lock('find_diaspora_person_by_handle'); + // Note that Friendica contacts will return a "Diaspora person" + // if Diaspora connectivity is enabled on their server + if ($r AND ($r["network"] === NETWORK_DIASPORA)) { + self::add_fcontact($r, $update); + $person = $r; } } - } while((! $person) && (! $got_lock) && (++$endlessloop < $maxloops)); - // We need to try again if the person wasn't in 'fcontact' but the function was locked. - // The fact that the function was locked may mean that another process was creating the - // person's record. It could also mean another process was creating or updating an unrelated - // person. - // - // At any rate, we need to keep trying until we've either got the person or had a chance to - // try to fetch his/her remote information. But we don't want to block on locking the - // function, because if the other process is creating the record, then when we acquire the lock - // we'll dive right into creating another, duplicate record. We DO want to at least wait - // until the lock is released, so we don't flood the database with requests. - // - // If the person was in the 'fcontact' table, don't try again. It's not worth the time, since - // we do have some information for the person - - return $person; -} - - -function get_diaspora_key($uri) { - logger('Fetching diaspora key for: ' . $uri); - - $r = find_diaspora_person_by_handle($uri); - if($r) - return $r['pubkey']; - return ''; -} - - -function diaspora_pubmsg_build($msg,$user,$contact,$prvkey,$pubkey) { - $a = get_app(); - - logger('diaspora_pubmsg_build: ' . $msg, LOGGER_DATA); - - - $handle = $user['nickname'] . '@' . substr($a->get_baseurl(), strpos($a->get_baseurl(),'://') + 3); - -// $b64_data = base64_encode($msg); -// $b64url_data = base64url_encode($b64_data); - - $b64url_data = base64url_encode($msg); - - $data = str_replace(array("\n","\r"," ","\t"),array('','','',''),$b64url_data); - - $type = 'application/xml'; - $encoding = 'base64url'; - $alg = 'RSA-SHA256'; - - $signable_data = $data . '.' . base64url_encode($type) . '.' - . base64url_encode($encoding) . '.' . base64url_encode($alg) ; - - $signature = rsa_sign($signable_data,$prvkey); - $sig = base64url_encode($signature); - -$magic_env = <<< EOT - - -
      - $handle -
      - - base64url - RSA-SHA256 - $data - $sig - -
      -EOT; - - logger('diaspora_pubmsg_build: magic_env: ' . $magic_env, LOGGER_DATA); - return $magic_env; - -} - - - - -function diaspora_msg_build($msg,$user,$contact,$prvkey,$pubkey,$public = false) { - $a = get_app(); - - if($public) - return diaspora_pubmsg_build($msg,$user,$contact,$prvkey,$pubkey); - - logger('diaspora_msg_build: ' . $msg, LOGGER_DATA); - - // without a public key nothing will work - - if(! $pubkey) { - logger('diaspora_msg_build: pubkey missing: contact id: ' . $contact['id']); - return ''; + return $person; } - $inner_aes_key = random_string(32); - $b_inner_aes_key = base64_encode($inner_aes_key); - $inner_iv = random_string(16); - $b_inner_iv = base64_encode($inner_iv); + /** + * @brief Updates the fcontact table + * + * @param array $arr The fcontact data + * @param bool $update Update or insert? + * + * @return string The id of the fcontact entry + */ + private function add_fcontact($arr, $update = false) { + /// @todo Remove this function from include/network.php - $outer_aes_key = random_string(32); - $b_outer_aes_key = base64_encode($outer_aes_key); - $outer_iv = random_string(16); - $b_outer_iv = base64_encode($outer_iv); - - $handle = $user['nickname'] . '@' . substr($a->get_baseurl(), strpos($a->get_baseurl(),'://') + 3); - - $padded_data = pkcs5_pad($msg,16); - $inner_encrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $inner_aes_key, $padded_data, MCRYPT_MODE_CBC, $inner_iv); - - $b64_data = base64_encode($inner_encrypted); - - - $b64url_data = base64url_encode($b64_data); - $data = str_replace(array("\n","\r"," ","\t"),array('','','',''),$b64url_data); - - $type = 'application/xml'; - $encoding = 'base64url'; - $alg = 'RSA-SHA256'; - - $signable_data = $data . '.' . base64url_encode($type) . '.' - . base64url_encode($encoding) . '.' . base64url_encode($alg) ; - - $signature = rsa_sign($signable_data,$prvkey); - $sig = base64url_encode($signature); - -$decrypted_header = <<< EOT - - $b_inner_iv - $b_inner_aes_key - $handle - -EOT; - - $decrypted_header = pkcs5_pad($decrypted_header,16); - - $ciphertext = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $outer_aes_key, $decrypted_header, MCRYPT_MODE_CBC, $outer_iv); - - $outer_json = json_encode(array('iv' => $b_outer_iv,'key' => $b_outer_aes_key)); - - $encrypted_outer_key_bundle = ''; - openssl_public_encrypt($outer_json,$encrypted_outer_key_bundle,$pubkey); - - $b64_encrypted_outer_key_bundle = base64_encode($encrypted_outer_key_bundle); - - logger('outer_bundle: ' . $b64_encrypted_outer_key_bundle . ' key: ' . $pubkey, LOGGER_DATA); - - $encrypted_header_json_object = json_encode(array('aes_key' => base64_encode($encrypted_outer_key_bundle), - 'ciphertext' => base64_encode($ciphertext))); - $cipher_json = base64_encode($encrypted_header_json_object); - - $encrypted_header = '' . $cipher_json . ''; - -$magic_env = <<< EOT - - - $encrypted_header - - base64url - RSA-SHA256 - $data - $sig - - -EOT; - - logger('diaspora_msg_build: magic_env: ' . $magic_env, LOGGER_DATA); - return $magic_env; - -} - -/** - * - * diaspora_decode($importer,$xml) - * array $importer -> from user table - * string $xml -> urldecoded Diaspora salmon - * - * Returns array - * 'message' -> decoded Diaspora XML message - * 'author' -> author diaspora handle - * 'key' -> author public key (converted to pkcs#8) - * - * Author and key are used elsewhere to save a lookup for verifying replies and likes - */ - - -function diaspora_decode($importer,$xml) { - - $tempfile = tempnam(get_temppath(), "diaspora-decode"); - file_put_contents($tempfile, json_encode(array("importer" => $importer, "xml" => $xml))); - - $public = false; - $basedom = parse_xml_string($xml); - - $children = $basedom->children('https://joindiaspora.com/protocol'); - - if($children->header) { - $public = true; - $author_link = str_replace('acct:','',$children->header->author_id); - } - else { - - $encrypted_header = json_decode(base64_decode($children->encrypted_header)); - - $encrypted_aes_key_bundle = base64_decode($encrypted_header->aes_key); - $ciphertext = base64_decode($encrypted_header->ciphertext); - - $outer_key_bundle = ''; - openssl_private_decrypt($encrypted_aes_key_bundle,$outer_key_bundle,$importer['prvkey']); - - $j_outer_key_bundle = json_decode($outer_key_bundle); - - $outer_iv = base64_decode($j_outer_key_bundle->iv); - $outer_key = base64_decode($j_outer_key_bundle->key); - - $decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $outer_key, $ciphertext, MCRYPT_MODE_CBC, $outer_iv); - - - $decrypted = pkcs5_unpad($decrypted); - - /** - * $decrypted now contains something like - * - * - * 8e+G2+ET8l5BPuW0sVTnQw== - * UvSMb4puPeB14STkcDWq+4QE302Edu15oaprAQSkLKU= - -***** OBSOLETE - - * - * Ryan Hughes - * acct:galaxor@diaspora.pirateship.org - * - -***** CURRENT - - * galaxor@diaspora.priateship.org - -***** END DIFFS - - * - */ - - logger('decrypted: ' . $decrypted, LOGGER_DEBUG); - $idom = parse_xml_string($decrypted,false); - - $inner_iv = base64_decode($idom->iv); - $inner_aes_key = base64_decode($idom->aes_key); - - $author_link = str_replace('acct:','',$idom->author_id); + if($update) { + $r = q("UPDATE `fcontact` SET + `name` = '%s', + `photo` = '%s', + `request` = '%s', + `nick` = '%s', + `addr` = '%s', + `batch` = '%s', + `notify` = '%s', + `poll` = '%s', + `confirm` = '%s', + `alias` = '%s', + `pubkey` = '%s', + `updated` = '%s' + WHERE `url` = '%s' AND `network` = '%s'", + dbesc($arr["name"]), + dbesc($arr["photo"]), + dbesc($arr["request"]), + dbesc($arr["nick"]), + dbesc($arr["addr"]), + dbesc($arr["batch"]), + dbesc($arr["notify"]), + dbesc($arr["poll"]), + dbesc($arr["confirm"]), + dbesc($arr["alias"]), + dbesc($arr["pubkey"]), + dbesc(datetime_convert()), + dbesc($arr["url"]), + dbesc($arr["network"]) + ); + } else { + $r = q("INSERT INTO `fcontact` (`url`,`name`,`photo`,`request`,`nick`,`addr`, + `batch`, `notify`,`poll`,`confirm`,`network`,`alias`,`pubkey`,`updated`) + VALUES ('%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s')", + dbesc($arr["url"]), + dbesc($arr["name"]), + dbesc($arr["photo"]), + dbesc($arr["request"]), + dbesc($arr["nick"]), + dbesc($arr["addr"]), + dbesc($arr["batch"]), + dbesc($arr["notify"]), + dbesc($arr["poll"]), + dbesc($arr["confirm"]), + dbesc($arr["network"]), + dbesc($arr["alias"]), + dbesc($arr["pubkey"]), + dbesc(datetime_convert()) + ); + } + return $r; } - $dom = $basedom->children(NAMESPACE_SALMON_ME); + public static function handle_from_contact($contact_id) { + $handle = False; - // figure out where in the DOM tree our data is hiding + logger("contact id is ".$contact_id, LOGGER_DEBUG); - if($dom->provenance->data) - $base = $dom->provenance; - elseif($dom->env->data) - $base = $dom->env; - elseif($dom->data) - $base = $dom; + $r = q("SELECT `network`, `addr`, `self`, `url`, `nick` FROM `contact` WHERE `id` = %d", + intval($contact_id) + ); + if($r) { + $contact = $r[0]; - if(! $base) { - logger('mod-diaspora: unable to locate salmon data in xml '); - http_status_exit(400); + logger("contact 'self' = ".$contact['self']." 'url' = ".$contact['url'], LOGGER_DEBUG); + + if($contact['addr'] != "") + $handle = $contact['addr']; + elseif(($contact['network'] === NETWORK_DFRN) || ($contact['self'] == 1)) { + $baseurl_start = strpos($contact['url'],'://') + 3; + $baseurl_length = strpos($contact['url'],'/profile') - $baseurl_start; // allows installations in a subdirectory--not sure how Diaspora will handle + $baseurl = substr($contact['url'], $baseurl_start, $baseurl_length); + $handle = $contact['nick'].'@'.$baseurl; + } + } + + return $handle; } + private function contact_by_handle($uid, $handle) { + $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `addr` = '%s' LIMIT 1", + intval($uid), + dbesc($handle) + ); - // Stash the signature away for now. We have to find their key or it won't be good for anything. - $signature = base64url_decode($base->sig); + if ($r) + return $r[0]; - // unpack the data + $handle_parts = explode("@", $handle); + $nurl_sql = "%%://".$handle_parts[1]."%%/profile/".$handle_parts[0]; + $r = q("SELECT * FROM `contact` WHERE `network` = '%s' AND `uid` = %d AND `nurl` LIKE '%s' LIMIT 1", + dbesc(NETWORK_DFRN), + intval($uid), + dbesc($nurl_sql) + ); + if($r) + return $r[0]; - // strip whitespace so our data element will return to one big base64 blob - $data = str_replace(array(" ","\t","\r","\n"),array("","","",""),$base->data); - - - // stash away some other stuff for later - - $type = $base->data[0]->attributes()->type[0]; - $keyhash = $base->sig[0]->attributes()->keyhash[0]; - $encoding = $base->encoding; - $alg = $base->alg; - - - $signed_data = $data . '.' . base64url_encode($type) . '.' . base64url_encode($encoding) . '.' . base64url_encode($alg); - - - // decode the data - $data = base64url_decode($data); - - - if($public) { - $inner_decrypted = $data; - } - else { - - // Decode the encrypted blob - - $inner_encrypted = base64_decode($data); - $inner_decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $inner_aes_key, $inner_encrypted, MCRYPT_MODE_CBC, $inner_iv); - $inner_decrypted = pkcs5_unpad($inner_decrypted); + return false; } - if(! $author_link) { - logger('mod-diaspora: Could not retrieve author URI.'); - http_status_exit(400); - } - - // Once we have the author URI, go to the web and try to find their public key - // (first this will look it up locally if it is in the fcontact cache) - // This will also convert diaspora public key from pkcs#1 to pkcs#8 - - logger('mod-diaspora: Fetching key for ' . $author_link ); - $key = get_diaspora_key($author_link); - - if(! $key) { - logger('mod-diaspora: Could not retrieve author key.'); - http_status_exit(400); - } - - $verify = rsa_verify($signed_data,$signature,$key); - - if(! $verify) { - logger('mod-diaspora: Message did not verify. Discarding.'); - http_status_exit(400); - } - - logger('mod-diaspora: Message verified.'); - - return array('message' => $inner_decrypted, 'author' => $author_link, 'key' => $key); - -} - - -function diaspora_request($importer,$xml) { - - $a = get_app(); - - $sender_handle = unxmlify($xml->sender_handle); - $recipient_handle = unxmlify($xml->recipient_handle); - - if(! $sender_handle || ! $recipient_handle) - return; - - $contact = diaspora_get_contact_by_handle($importer['uid'],$sender_handle); - - if($contact) { + private function post_allow($importer, $contact, $is_comment = false) { // perhaps we were already sharing with this person. Now they're sharing with us. // That makes us friends. - - if($contact['rel'] == CONTACT_IS_FOLLOWER && in_array($importer['page-flags'], array(PAGE_FREELOVE))) { + // Normally this should have handled by getting a request - but this could get lost + if($contact["rel"] == CONTACT_IS_FOLLOWER && in_array($importer["page-flags"], array(PAGE_FREELOVE))) { q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d", intval(CONTACT_IS_FRIEND), - intval($contact['id']), - intval($importer['uid']) + intval($contact["id"]), + intval($importer["uid"]) ); - } - // send notification - - $r = q("SELECT `hide-friends` FROM `profile` WHERE `uid` = %d AND `is-default` = 1 LIMIT 1", - intval($importer['uid']) - ); - - if((count($r)) && (!$r[0]['hide-friends']) && (!$contact['hidden']) && intval(get_pconfig($importer['uid'],'system','post_newfriend'))) { - require_once('include/items.php'); - - $self = q("SELECT * FROM `contact` WHERE `self` = 1 AND `uid` = %d LIMIT 1", - intval($importer['uid']) - ); - - // they are not CONTACT_IS_FOLLOWER anymore but that's what we have in the array - - if(count($self) && $contact['rel'] == CONTACT_IS_FOLLOWER) { - - $arr = array(); - $arr['uri'] = $arr['parent-uri'] = item_new_uri($a->get_hostname(), $importer['uid']); - $arr['uid'] = $importer['uid']; - $arr['contact-id'] = $self[0]['id']; - $arr['wall'] = 1; - $arr['type'] = 'wall'; - $arr['gravity'] = 0; - $arr['origin'] = 1; - $arr['author-name'] = $arr['owner-name'] = $self[0]['name']; - $arr['author-link'] = $arr['owner-link'] = $self[0]['url']; - $arr['author-avatar'] = $arr['owner-avatar'] = $self[0]['thumb']; - $arr['verb'] = ACTIVITY_FRIEND; - $arr['object-type'] = ACTIVITY_OBJ_PERSON; - - $A = '[url=' . $self[0]['url'] . ']' . $self[0]['name'] . '[/url]'; - $B = '[url=' . $contact['url'] . ']' . $contact['name'] . '[/url]'; - $BPhoto = '[url=' . $contact['url'] . ']' . '[img]' . $contact['thumb'] . '[/img][/url]'; - $arr['body'] = sprintf( t('%1$s is now friends with %2$s'), $A, $B)."\n\n\n".$Bphoto; - - $arr['object'] = '' . ACTIVITY_OBJ_PERSON . '' . $contact['name'] . '' - . '' . $contact['url'] . '/' . $contact['name'] . ''; - $arr['object'] .= '' . xmlify('' . "\n"); - $arr['object'] .= xmlify('' . "\n"); - $arr['object'] .= '' . "\n"; - $arr['last-child'] = 1; - - $arr['allow_cid'] = $user[0]['allow_cid']; - $arr['allow_gid'] = $user[0]['allow_gid']; - $arr['deny_cid'] = $user[0]['deny_cid']; - $arr['deny_gid'] = $user[0]['deny_gid']; - - $i = item_store($arr); - if($i) - proc_run('php',"include/notifier.php","activity","$i"); - - } - + $contact["rel"] = CONTACT_IS_FRIEND; + logger("defining user ".$contact["nick"]." as friend"); } - return; - } + if(($contact["blocked"]) || ($contact["readonly"]) || ($contact["archive"])) + return false; + if($contact["rel"] == CONTACT_IS_SHARING || $contact["rel"] == CONTACT_IS_FRIEND) + return true; + if($contact["rel"] == CONTACT_IS_FOLLOWER) + if(($importer["page-flags"] == PAGE_COMMUNITY) OR $is_comment) + return true; - $ret = find_diaspora_person_by_handle($sender_handle); - - - if((! count($ret)) || ($ret['network'] != NETWORK_DIASPORA)) { - logger('diaspora_request: Cannot resolve diaspora handle ' . $sender_handle . ' for ' . $recipient_handle); - return; - } - - $batch = (($ret['batch']) ? $ret['batch'] : implode('/', array_slice(explode('/',$ret['url']),0,3)) . '/receive/public'); - - - - $r = q("INSERT INTO `contact` (`uid`, `network`,`addr`,`created`,`url`,`nurl`,`batch`,`name`,`nick`,`photo`,`pubkey`,`notify`,`poll`,`blocked`,`priority`) - VALUES ( %d, '%s', '%s', '%s', '%s','%s','%s','%s','%s','%s','%s','%s','%s',%d,%d) ", - intval($importer['uid']), - dbesc($ret['network']), - dbesc($ret['addr']), - datetime_convert(), - dbesc($ret['url']), - dbesc(normalise_link($ret['url'])), - dbesc($batch), - dbesc($ret['name']), - dbesc($ret['nick']), - dbesc($ret['photo']), - dbesc($ret['pubkey']), - dbesc($ret['notify']), - dbesc($ret['poll']), - 1, - 2 - ); - - // find the contact record we just created - - $contact_record = diaspora_get_contact_by_handle($importer['uid'],$sender_handle); - - if(! $contact_record) { - logger('diaspora_request: unable to locate newly created contact record.'); - return; - } - - $def_gid = get_default_group($importer['uid'], $ret["network"]); - if (intval($def_gid)) { - require_once('include/group.php'); - group_add_member($importer['uid'], '', $contact_record['id'], $def_gid); - } - - if($importer['page-flags'] == PAGE_NORMAL) { - - $hash = random_string() . (string) time(); // Generate a confirm_key - - $ret = q("INSERT INTO `intro` ( `uid`, `contact-id`, `blocked`, `knowyou`, `note`, `hash`, `datetime` ) - VALUES ( %d, %d, %d, %d, '%s', '%s', '%s' )", - intval($importer['uid']), - intval($contact_record['id']), - 0, - 0, - dbesc( t('Sharing notification from Diaspora network')), - dbesc($hash), - dbesc(datetime_convert()) - ); - } - else { - - // automatic friend approval - - require_once('include/Photo.php'); - - 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 - // we are going to change the relationship and make them a follower. - - if($importer['page-flags'] == PAGE_FREELOVE) - $new_relation = CONTACT_IS_FRIEND; - else - $new_relation = CONTACT_IS_FOLLOWER; - - $r = q("UPDATE `contact` SET `rel` = %d, - `name-date` = '%s', - `uri-date` = '%s', - `blocked` = 0, - `pending` = 0, - `writable` = 1 - WHERE `id` = %d - ", - intval($new_relation), - dbesc(datetime_convert()), - dbesc(datetime_convert()), - intval($contact_record['id']) - ); - - $u = q("select * from user where uid = %d limit 1",intval($importer['uid'])); - if($u) - $ret = diaspora_share($u[0],$contact_record); - } - - return; -} - -function diaspora_post_allow($importer,$contact, $is_comment = false) { - - // perhaps we were already sharing with this person. Now they're sharing with us. - // That makes us friends. - // Normally this should have handled by getting a request - but this could get lost - if($contact['rel'] == CONTACT_IS_FOLLOWER && in_array($importer['page-flags'], array(PAGE_FREELOVE))) { - q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d", - intval(CONTACT_IS_FRIEND), - intval($contact['id']), - intval($importer['uid']) - ); - $contact['rel'] = CONTACT_IS_FRIEND; - logger('diaspora_post_allow: defining user '.$contact["nick"].' as friend'); - } - - if(($contact['blocked']) || ($contact['readonly']) || ($contact['archive'])) - return false; - if($contact['rel'] == CONTACT_IS_SHARING || $contact['rel'] == CONTACT_IS_FRIEND) - return true; - if($contact['rel'] == CONTACT_IS_FOLLOWER) - if(($importer['page-flags'] == PAGE_COMMUNITY) OR $is_comment) + // Messages for the global users are always accepted + if ($importer["uid"] == 0) return true; - // Messages for the global users are always accepted - if ($importer['uid'] == 0) - return true; - - return false; -} - -function diaspora_is_redmatrix($url) { - return(strstr($url, "/channel/")); -} - -function diaspora_plink($addr, $guid) { - $r = q("SELECT `url`, `nick`, `network` FROM `fcontact` WHERE `addr`='%s' LIMIT 1", dbesc($addr)); - - // Fallback - if (!$r) - return 'https://'.substr($addr,strpos($addr,'@')+1).'/posts/'.$guid; - - // Friendica contacts are often detected as Diaspora contacts in the "fcontact" table - // So we try another way as well. - $s = q("SELECT `network` FROM `gcontact` WHERE `nurl`='%s' LIMIT 1", dbesc(normalise_link($r[0]["url"]))); - if ($s) - $r[0]["network"] = $s[0]["network"]; - - if ($r[0]["network"] == NETWORK_DFRN) - return(str_replace("/profile/".$r[0]["nick"]."/", "/display/".$guid, $r[0]["url"]."/")); - - if (diaspora_is_redmatrix($r[0]["url"])) - return $r[0]["url"]."/?f=&mid=".$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(); - $guid = notags(unxmlify($xml->guid)); - $diaspora_handle = notags(unxmlify($xml->diaspora_handle)); - - if($diaspora_handle != $msg['author']) { - logger('diaspora_post: Potential forgery. Message handle is not the same as envelope sender.'); - return 202; - } - - $contact = diaspora_get_contact_by_handle($importer['uid'],$diaspora_handle); - if(! $contact) { - logger('diaspora_post: A Contact for handle '.$diaspora_handle.' and user '.$importer['uid'].' was not found'); - return 203; - } - - if(! diaspora_post_allow($importer,$contact, false)) { - logger('diaspora_post: Ignoring this author.'); - return 202; - } - - $message_id = $diaspora_handle . ':' . $guid; - $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", - intval($importer['uid']), - dbesc($guid) - ); - if(count($r)) { - logger('diaspora_post: message exists: ' . $guid); - return 208; - } - - $created = unxmlify($xml->created_at); - $private = ((unxmlify($xml->public) == 'false') ? 1 : 0); - - $body = diaspora2bb($xml->raw_message); - - $datarray = array(); - - $datarray["object"] = json_encode($xml); - - if($xml->photo->remote_photo_path AND $xml->photo->remote_photo_name) - $datarray["object-type"] = ACTIVITY_OBJ_PHOTO; - else { - $datarray['object-type'] = ACTIVITY_OBJ_NOTE; - // Add OEmbed and other information to the body - if (!diaspora_is_redmatrix($contact['url'])) - $body = add_page_info_to_body($body, false, true); - } - - $str_tags = ''; - - $cnt = preg_match_all('/@\[url=(.*?)\[\/url\]/ism',$body,$matches,PREG_SET_ORDER); - if($cnt) { - foreach($matches as $mtch) { - if(strlen($str_tags)) - $str_tags .= ','; - $str_tags .= '@[url=' . $mtch[1] . '[/url]'; - } - } - - $plink = diaspora_plink($diaspora_handle, $guid); - - $datarray['uid'] = $importer['uid']; - $datarray['contact-id'] = $contact['id']; - $datarray['wall'] = 0; - $datarray['network'] = NETWORK_DIASPORA; - $datarray['verb'] = ACTIVITY_POST; - $datarray['guid'] = $guid; - $datarray['uri'] = $datarray['parent-uri'] = $message_id; - $datarray['changed'] = $datarray['created'] = $datarray['edited'] = datetime_convert('UTC','UTC',$created); - $datarray['private'] = $private; - $datarray['parent'] = 0; - $datarray['plink'] = $plink; - $datarray['owner-name'] = $contact['name']; - $datarray['owner-link'] = $contact['url']; - //$datarray['owner-avatar'] = $contact['thumb']; - $datarray['owner-avatar'] = ((x($contact,'thumb')) ? $contact['thumb'] : $contact['photo']); - $datarray['author-name'] = $contact['name']; - $datarray['author-link'] = $contact['url']; - $datarray['author-avatar'] = $contact['thumb']; - $datarray['body'] = $body; - $datarray['tag'] = $str_tags; - if ($xml->provider_display_name) - $datarray["app"] = unxmlify($xml->provider_display_name); - else - $datarray['app'] = 'Diaspora'; - - // if empty content it might be a photo that hasn't arrived yet. If a photo arrives, we'll make it visible. - - $datarray['visible'] = ((strlen($body)) ? 1 : 0); - - DiasporaFetchGuid($datarray); - $message_id = item_store($datarray); - - logger("Stored item with message id ".$message_id, LOGGER_DEBUG); - - return 201; - -} - -function DiasporaFetchGuid($item) { - preg_replace_callback("&\[url=/posts/([^\[\]]*)\](.*)\[\/url\]&Usi", - function ($match) use ($item){ - return(DiasporaFetchGuidSub($match, $item)); - },$item["body"]); -} - -function DiasporaFetchGuidSub($match, $item) { - $a = get_app(); - - if (!diaspora_store_by_guid($match[1], $item["author-link"])) - diaspora_store_by_guid($match[1], $item["owner-link"]); -} - -function diaspora_store_by_guid($guid, $server, $uid = 0) { - require_once("include/Contact.php"); - - $serverparts = parse_url($server); - $server = $serverparts["scheme"]."://".$serverparts["host"]; - - logger("Trying to fetch item ".$guid." from ".$server, LOGGER_DEBUG); - - $item = diaspora_fetch_message($guid, $server); - - if (!$item) return false; + } - logger("Successfully fetched item ".$guid." from ".$server, LOGGER_DEBUG); - - $body = $item["body"]; - $str_tags = $item["tag"]; - $app = $item["app"]; - $created = $item["created"]; - $author = $item["author"]; - $guid = $item["guid"]; - $private = $item["private"]; - $object = $item["object"]; - $objecttype = $item["object-type"]; - - $message_id = $author.':'.$guid; - $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", - intval($uid), - dbesc($guid) - ); - if(count($r)) - return $r[0]["id"]; - - $person = find_diaspora_person_by_handle($author); - - $contact_id = get_contact($person['url'], $uid); - - $contacts = q("SELECT * FROM `contact` WHERE `id` = %d", intval($contact_id)); - $importers = q("SELECT * FROM `user` WHERE `uid` = %d", intval($uid)); - - if ($contacts AND $importers) - if(!diaspora_post_allow($importers[0],$contacts[0], false)) { - logger('Ignoring author '.$person['url'].' for uid '.$uid); + private function allowed_contact_by_handle($importer, $handle, $is_comment = false) { + $contact = self::contact_by_handle($importer["uid"], $handle); + if (!$contact) { + logger("A Contact for handle ".$handle." and user ".$importer["uid"]." was not found"); return false; - } else - logger('Author '.$person['url'].' is allowed for uid '.$uid); + } - $datarray = array(); - $datarray['uid'] = $uid; - $datarray['contact-id'] = $contact_id; - $datarray['wall'] = 0; - $datarray['network'] = NETWORK_DIASPORA; - $datarray['guid'] = $guid; - $datarray['uri'] = $datarray['parent-uri'] = $message_id; - $datarray['changed'] = $datarray['created'] = $datarray['edited'] = datetime_convert('UTC','UTC',$created); - $datarray['private'] = $private; - $datarray['parent'] = 0; - $datarray['plink'] = diaspora_plink($author, $guid); - $datarray['author-name'] = $person['name']; - $datarray['author-link'] = $person['url']; - $datarray['author-avatar'] = ((x($person,'thumb')) ? $person['thumb'] : $person['photo']); - $datarray['owner-name'] = $datarray['author-name']; - $datarray['owner-link'] = $datarray['author-link']; - $datarray['owner-avatar'] = $datarray['author-avatar']; - $datarray['body'] = $body; - $datarray['tag'] = $str_tags; - $datarray['app'] = $app; - $datarray['visible'] = ((strlen($body)) ? 1 : 0); - $datarray['object'] = $object; - $datarray['object-type'] = $objecttype; + if (!self::post_allow($importer, $contact, $is_comment)) { + logger("The handle: ".$handle." is not allowed to post to user ".$importer["uid"]); + return false; + } + return $contact; + } - if ($datarray['contact-id'] == 0) - return false; + private function message_exists($uid, $guid) { + $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", + intval($uid), + dbesc($guid) + ); - DiasporaFetchGuid($datarray); - $message_id = item_store($datarray); + if($r) { + logger("message ".$guid." already exists for user ".$uid); + return true; + } - /// @TODO - /// Looking if there is some subscribe mechanism in Diaspora to get all comments for this post - - return $message_id; -} - -function diaspora_fetch_message($guid, $server, $level = 0) { - - if ($level > 5) - return false; - - $a = get_app(); - - // This will not work if the server is not a Diaspora server - $source_url = $server.'/p/'.$guid.'.xml'; - $x = fetch_url($source_url); - if(!$x) - return false; - - $x = str_replace(array('',''),array('',''),$x); - $source_xml = parse_xml_string($x,false); - - $item = array(); - $item["app"] = 'Diaspora'; - $item["guid"] = $guid; - $body = ""; - - if ($source_xml->post->status_message->created_at) - $item["created"] = unxmlify($source_xml->post->status_message->created_at); - - if ($source_xml->post->status_message->provider_display_name) - $item["app"] = unxmlify($source_xml->post->status_message->provider_display_name); - - if ($source_xml->post->status_message->diaspora_handle) - $item["author"] = unxmlify($source_xml->post->status_message->diaspora_handle); - - if ($source_xml->post->status_message->guid) - $item["guid"] = unxmlify($source_xml->post->status_message->guid); - - $item["private"] = (unxmlify($source_xml->post->status_message->public) == 'false'); - $item["object"] = json_encode($source_xml->post); - - if(strlen($source_xml->post->asphoto->objectId) && ($source_xml->post->asphoto->objectId != 0) && ($source_xml->post->asphoto->image_url)) { - $item["object-type"] = ACTIVITY_OBJ_PHOTO; - $body = '[url=' . notags(unxmlify($source_xml->post->asphoto->image_url)) . '][img]' . notags(unxmlify($source_xml->post->asphoto->objectId)) . '[/img][/url]' . "\n"; - $body = scale_external_images($body,false); - } elseif($source_xml->post->asphoto->image_url) { - $item["object-type"] = ACTIVITY_OBJ_PHOTO; - $body = '[img]' . notags(unxmlify($source_xml->post->asphoto->image_url)) . '[/img]' . "\n"; - $body = scale_external_images($body); - } elseif($source_xml->post->status_message) { - $body = diaspora2bb($source_xml->post->status_message->raw_message); - - // Checking for embedded pictures - if($source_xml->post->status_message->photo->remote_photo_path AND - $source_xml->post->status_message->photo->remote_photo_name) { - - $item["object-type"] = ACTIVITY_OBJ_PHOTO; - - $remote_photo_path = notags(unxmlify($source_xml->post->status_message->photo->remote_photo_path)); - $remote_photo_name = notags(unxmlify($source_xml->post->status_message->photo->remote_photo_name)); - - $body = '[img]'.$remote_photo_path.$remote_photo_name.'[/img]'."\n".$body; - - logger('embedded picture link found: '.$body, LOGGER_DEBUG); - } else - $item["object-type"] = ACTIVITY_OBJ_NOTE; - - $body = scale_external_images($body); - - // Add OEmbed and other information to the body - /// @TODO It could be a repeated redmatrix item - /// Then we shouldn't add further data to it - if ($item["object-type"] == ACTIVITY_OBJ_NOTE) - $body = add_page_info_to_body($body, false, true); - - } elseif($source_xml->post->reshare) { - // Reshare of a reshare - return diaspora_fetch_message($source_xml->post->reshare->root_guid, $server, ++$level); - } else { - // Maybe it is a reshare of a photo that will be delivered at a later time (testing) - logger('no content found: '.print_r($source_xml,true)); return false; } - if (trim($body) == "") - return false; - - $item["tag"] = ''; - $item["body"] = $body; - - return $item; -} - -function diaspora_reshare($importer,$xml,$msg) { - - logger('diaspora_reshare: init: ' . print_r($xml,true)); - - $a = get_app(); - $guid = notags(unxmlify($xml->guid)); - $diaspora_handle = notags(unxmlify($xml->diaspora_handle)); - - - if($diaspora_handle != $msg['author']) { - logger('diaspora_post: Potential forgery. Message handle is not the same as envelope sender.'); - return 202; + private function fetch_guid($item) { + preg_replace_callback("&\[url=/posts/([^\[\]]*)\](.*)\[\/url\]&Usi", + function ($match) use ($item){ + return(self::fetch_guid_sub($match, $item)); + },$item["body"]); } - $contact = diaspora_get_contact_by_handle($importer['uid'],$diaspora_handle); - if(! $contact) - return; - - if(! diaspora_post_allow($importer,$contact, false)) { - logger('diaspora_reshare: Ignoring this author: ' . $diaspora_handle . ' ' . print_r($xml,true)); - return 202; + private function fetch_guid_sub($match, $item) { + if (!self::store_by_guid($match[1], $item["author-link"])) + self::store_by_guid($match[1], $item["owner-link"]); } - $message_id = $diaspora_handle . ':' . $guid; - $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", - intval($importer['uid']), - dbesc($guid) - ); - if(count($r)) { - logger('diaspora_reshare: message exists: ' . $guid); - return; + private function store_by_guid($guid, $server, $uid = 0) { + $serverparts = parse_url($server); + $server = $serverparts["scheme"]."://".$serverparts["host"]; + + logger("Trying to fetch item ".$guid." from ".$server, LOGGER_DEBUG); + + $msg = self::message($guid, $server); + + if (!$msg) + return false; + + logger("Successfully fetched item ".$guid." from ".$server, LOGGER_DEBUG); + + // Now call the dispatcher + return self::dispatch_public($msg); } - $orig_author = notags(unxmlify($xml->root_diaspora_id)); - $orig_guid = notags(unxmlify($xml->root_guid)); - $orig_url = $a->get_baseurl()."/display/".$orig_guid; + private function message($guid, $server, $level = 0) { - $create_original_post = false; + if ($level > 5) + return false; - // Do we already have this item? - $r = q("SELECT `body`, `tag`, `app`, `created`, `plink`, `object`, `object-type`, `uri` FROM `item` WHERE `guid` = '%s' AND `visible` AND NOT `deleted` AND `body` != '' LIMIT 1", - dbesc($orig_guid), - dbesc(NETWORK_DIASPORA) - ); - if(count($r)) { - logger('reshared message '.$orig_guid." reshared by ".$guid.' already exists on system.'); + // This will work for Diaspora and newer Friendica servers + $source_url = $server."/p/".$guid.".xml"; + $x = fetch_url($source_url); + if(!$x) + return false; - // Maybe it is already a reshared item? - // Then refetch the content, since there can be many side effects with reshared posts from other networks or reshares from reshares - require_once('include/api.php'); - if (api_share_as_retweet($r[0])) - $r = array(); - else { - $body = $r[0]["body"]; - $str_tags = $r[0]["tag"]; - $app = $r[0]["app"]; - $orig_created = $r[0]["created"]; - $orig_plink = $r[0]["plink"]; - $orig_uri = $r[0]["uri"]; - $object = $r[0]["object"]; - $objecttype = $r[0]["object-type"]; + $source_xml = parse_xml_string($x, false); + + if (!is_object($source_xml)) + return false; + + if ($source_xml->post->reshare) { + // Reshare of a reshare - old Diaspora version + return self::message($source_xml->post->reshare->root_guid, $server, ++$level); + } elseif ($source_xml->getName() == "reshare") { + // Reshare of a reshare - new Diaspora version + return self::message($source_xml->root_guid, $server, ++$level); + } + + $author = ""; + + // Fetch the author - for the old and the new Diaspora version + if ($source_xml->post->status_message->diaspora_handle) + $author = (string)$source_xml->post->status_message->diaspora_handle; + elseif ($source_xml->author AND ($source_xml->getName() == "status_message")) + $author = (string)$source_xml->author; + + // If this isn't a "status_message" then quit + if (!$author) + return false; + + $msg = array("message" => $x, "author" => $author); + + $msg["key"] = self::key($msg["author"]); + + return $msg; + } + + private function parent_item($uid, $guid, $author, $contact) { + $r = q("SELECT `id`, `body`, `wall`, `uri`, `private`, `origin`, + `author-name`, `author-link`, `author-avatar`, + `owner-name`, `owner-link`, `owner-avatar` + FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", + intval($uid), dbesc($guid)); + + if(!$r) { + $result = self::store_by_guid($guid, $contact["url"], $uid); + + if (!$result) { + $person = self::person_by_handle($author); + $result = self::store_by_guid($guid, $person["url"], $uid); + } + + if ($result) { + logger("Fetched missing item ".$guid." - result: ".$result, LOGGER_DEBUG); + + $r = q("SELECT `id`, `body`, `wall`, `uri`, `private`, `origin`, + `author-name`, `author-link`, `author-avatar`, + `owner-name`, `owner-link`, `owner-avatar` + FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", + intval($uid), dbesc($guid)); + } + } + + if (!$r) { + logger("parent item not found: parent: ".$guid." - user: ".$uid); + return false; + } else { + logger("parent item found: parent: ".$guid." - user: ".$uid); + return $r[0]; } } - if (!count($r)) { - $body = ""; - $str_tags = ""; - $app = ""; + private function author_contact_by_url($contact, $person, $uid) { - $server = 'https://'.substr($orig_author,strpos($orig_author,'@')+1); - logger('1st try: reshared message '.$orig_guid." reshared by ".$guid.' will be fetched from original server: '.$server); - $item = diaspora_fetch_message($orig_guid, $server); - - if (!$item) { - $server = 'https://'.substr($diaspora_handle,strpos($diaspora_handle,'@')+1); - logger('2nd try: reshared message '.$orig_guid." reshared by ".$guid." will be fetched from sharer's server: ".$server); - $item = diaspora_fetch_message($orig_guid, $server); - } - if (!$item) { - $server = 'http://'.substr($orig_author,strpos($orig_author,'@')+1); - logger('3rd try: reshared message '.$orig_guid." reshared by ".$guid.' will be fetched from original server: '.$server); - $item = diaspora_fetch_message($orig_guid, $server); - } - if (!$item) { - $server = 'http://'.substr($diaspora_handle,strpos($diaspora_handle,'@')+1); - logger('4th try: reshared message '.$orig_guid." reshared by ".$guid." will be fetched from sharer's server: ".$server); - $item = diaspora_fetch_message($orig_guid, $server); + $r = q("SELECT `id`, `network` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d LIMIT 1", + dbesc(normalise_link($person["url"])), intval($uid)); + if ($r) { + $cid = $r[0]["id"]; + $network = $r[0]["network"]; + } else { + $cid = $contact["id"]; + $network = NETWORK_DIASPORA; } - if ($item) { - $body = $item["body"]; - $str_tags = $item["tag"]; - $app = $item["app"]; - $orig_created = $item["created"]; - $orig_author = $item["author"]; - $orig_guid = $item["guid"]; - $orig_plink = diaspora_plink($orig_author, $orig_guid); - $orig_uri = $orig_author.':'.$orig_guid; - $create_original_post = ($body != ""); - $object = $item["object"]; - $objecttype = $item["object-type"]; - } + return (array("cid" => $cid, "network" => $network)); } - $plink = diaspora_plink($diaspora_handle, $guid); - - $person = find_diaspora_person_by_handle($orig_author); - - $created = unxmlify($xml->created_at); - $private = ((unxmlify($xml->public) == 'false') ? 1 : 0); - - $datarray = array(); - - $datarray['uid'] = $importer['uid']; - $datarray['contact-id'] = $contact['id']; - $datarray['wall'] = 0; - $datarray['network'] = NETWORK_DIASPORA; - $datarray['guid'] = $guid; - $datarray['uri'] = $datarray['parent-uri'] = $message_id; - $datarray['changed'] = $datarray['created'] = $datarray['edited'] = datetime_convert('UTC','UTC',$created); - $datarray['private'] = $private; - $datarray['parent'] = 0; - $datarray['plink'] = $plink; - $datarray['owner-name'] = $contact['name']; - $datarray['owner-link'] = $contact['url']; - $datarray['owner-avatar'] = ((x($contact,'thumb')) ? $contact['thumb'] : $contact['photo']); - if (!intval(get_config('system','wall-to-wall_share'))) { - $prefix = share_header($person['name'], $person['url'], ((x($person,'thumb')) ? $person['thumb'] : $person['photo']), $orig_guid, $orig_created, $orig_url); - - $datarray['author-name'] = $contact['name']; - $datarray['author-link'] = $contact['url']; - $datarray['author-avatar'] = $contact['thumb']; - $datarray['body'] = $prefix.$body."[/share]"; - } else { - // Let reshared messages look like wall-to-wall posts - $datarray['author-name'] = $person['name']; - $datarray['author-link'] = $person['url']; - $datarray['author-avatar'] = ((x($person,'thumb')) ? $person['thumb'] : $person['photo']); - $datarray['body'] = $body; + public static function is_redmatrix($url) { + return(strstr($url, "/channel/")); } - $datarray["object"] = json_encode($xml); - $datarray['object-type'] = $objecttype; + private function plink($addr, $guid) { + $r = q("SELECT `url`, `nick`, `network` FROM `fcontact` WHERE `addr`='%s' LIMIT 1", dbesc($addr)); - $datarray['tag'] = $str_tags; - $datarray['app'] = $app; + // Fallback + if (!$r) + return "https://".substr($addr,strpos($addr,"@")+1)."/posts/".$guid; - // if empty content it might be a photo that hasn't arrived yet. If a photo arrives, we'll make it visible. (testing) - $datarray['visible'] = ((strlen($body)) ? 1 : 0); + // Friendica contacts are often detected as Diaspora contacts in the "fcontact" table + // So we try another way as well. + $s = q("SELECT `network` FROM `gcontact` WHERE `nurl`='%s' LIMIT 1", dbesc(normalise_link($r[0]["url"]))); + if ($s) + $r[0]["network"] = $s[0]["network"]; - // Store the original item of a reshare - if ($create_original_post) { - require_once("include/Contact.php"); + if ($r[0]["network"] == NETWORK_DFRN) + return(str_replace("/profile/".$r[0]["nick"]."/", "/display/".$guid, $r[0]["url"]."/")); - $datarray2 = $datarray; + if (self::is_redmatrix($r[0]["url"])) + return $r[0]["url"]."/?f=&mid=".$guid; - $datarray2['uid'] = 0; - $datarray2['contact-id'] = get_contact($person['url'], 0); - $datarray2['guid'] = $orig_guid; - $datarray2['uri'] = $datarray2['parent-uri'] = $orig_uri; - $datarray2['changed'] = $datarray2['created'] = $datarray2['edited'] = $datarray2['commented'] = $datarray2['received'] = datetime_convert('UTC','UTC',$orig_created); - $datarray2['parent'] = 0; - $datarray2['plink'] = $orig_plink; - - $datarray2['author-name'] = $person['name']; - $datarray2['author-link'] = $person['url']; - $datarray2['author-avatar'] = ((x($person,'thumb')) ? $person['thumb'] : $person['photo']); - $datarray2['owner-name'] = $datarray2['author-name']; - $datarray2['owner-link'] = $datarray2['author-link']; - $datarray2['owner-avatar'] = $datarray2['author-avatar']; - $datarray2['body'] = $body; - $datarray2["object"] = $object; - - DiasporaFetchGuid($datarray2); - $message_id = item_store($datarray2); - - logger("Store original item ".$orig_guid." under message id ".$message_id); + return "https://".substr($addr,strpos($addr,"@")+1)."/posts/".$guid; } - DiasporaFetchGuid($datarray); - $message_id = item_store($datarray); + private function receive_account_deletion($importer, $data) { + $author = notags(unxmlify($data->author)); - return; - -} - - -function diaspora_asphoto($importer,$xml,$msg) { - logger('diaspora_asphoto called'); - - $a = get_app(); - $guid = notags(unxmlify($xml->guid)); - $diaspora_handle = notags(unxmlify($xml->diaspora_handle)); - - if($diaspora_handle != $msg['author']) { - logger('diaspora_post: Potential forgery. Message handle is not the same as envelope sender.'); - return 202; - } - - $contact = diaspora_get_contact_by_handle($importer['uid'],$diaspora_handle); - if(! $contact) - return; - - if(! diaspora_post_allow($importer,$contact, false)) { - logger('diaspora_asphoto: Ignoring this author.'); - return 202; - } - - $message_id = $diaspora_handle . ':' . $guid; - $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", - intval($importer['uid']), - dbesc($guid) - ); - if(count($r)) { - logger('diaspora_asphoto: message exists: ' . $guid); - return; - } - - $created = unxmlify($xml->created_at); - $private = ((unxmlify($xml->public) == 'false') ? 1 : 0); - - if(strlen($xml->objectId) && ($xml->objectId != 0) && ($xml->image_url)) { - $body = '[url=' . notags(unxmlify($xml->image_url)) . '][img]' . notags(unxmlify($xml->objectId)) . '[/img][/url]' . "\n"; - $body = scale_external_images($body,false); - } - elseif($xml->image_url) { - $body = '[img]' . notags(unxmlify($xml->image_url)) . '[/img]' . "\n"; - $body = scale_external_images($body); - } - else { - logger('diaspora_asphoto: no photo url found.'); - return; - } - - $plink = diaspora_plink($diaspora_handle, $guid); - - $datarray = array(); - - $datarray['uid'] = $importer['uid']; - $datarray['contact-id'] = $contact['id']; - $datarray['wall'] = 0; - $datarray['network'] = NETWORK_DIASPORA; - $datarray['guid'] = $guid; - $datarray['uri'] = $datarray['parent-uri'] = $message_id; - $datarray['changed'] = $datarray['created'] = $datarray['edited'] = datetime_convert('UTC','UTC',$created); - $datarray['private'] = $private; - $datarray['parent'] = 0; - $datarray['plink'] = $plink; - $datarray['owner-name'] = $contact['name']; - $datarray['owner-link'] = $contact['url']; - //$datarray['owner-avatar'] = $contact['thumb']; - $datarray['owner-avatar'] = ((x($contact,'thumb')) ? $contact['thumb'] : $contact['photo']); - $datarray['author-name'] = $contact['name']; - $datarray['author-link'] = $contact['url']; - $datarray['author-avatar'] = $contact['thumb']; - $datarray['body'] = $body; - $datarray["object"] = json_encode($xml); - $datarray['object-type'] = ACTIVITY_OBJ_PHOTO; - - $datarray['app'] = 'Diaspora/Cubbi.es'; - - DiasporaFetchGuid($datarray); - $message_id = item_store($datarray); - - //if($message_id) { - // q("update item set plink = '%s' where id = %d", - // dbesc($a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $message_id), - // intval($message_id) - // ); - //} - - return; - -} - -function diaspora_comment($importer,$xml,$msg) { - - $a = get_app(); - $guid = notags(unxmlify($xml->guid)); - $parent_guid = notags(unxmlify($xml->parent_guid)); - $diaspora_handle = notags(unxmlify($xml->diaspora_handle)); - $target_type = notags(unxmlify($xml->target_type)); - $text = unxmlify($xml->text); - $author_signature = notags(unxmlify($xml->author_signature)); - - $parent_author_signature = (($xml->parent_author_signature) ? notags(unxmlify($xml->parent_author_signature)) : ''); - - $contact = diaspora_get_contact_by_handle($importer['uid'],$msg['author']); - if(! $contact) { - logger('diaspora_comment: cannot find contact: ' . $msg['author']); - return; - } - - if(! diaspora_post_allow($importer,$contact, true)) { - logger('diaspora_comment: Ignoring this author.'); - return 202; - } - - $r = q("SELECT * FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", - intval($importer['uid']), - dbesc($guid) - ); - if(count($r)) { - logger('diaspora_comment: our comment just got relayed back to us (or there was a guid collision) : ' . $guid); - return; - } - - $r = q("SELECT * FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", - intval($importer['uid']), - dbesc($parent_guid) - ); - - if(!count($r)) { - $result = diaspora_store_by_guid($parent_guid, $contact['url'], $importer['uid']); - - if (!$result) { - $person = find_diaspora_person_by_handle($diaspora_handle); - $result = diaspora_store_by_guid($parent_guid, $person['url'], $importer['uid']); + $contact = self::contact_by_handle($importer["uid"], $author); + if (!$contact) { + logger("cannot find contact for author: ".$author); + return false; } - if ($result) { - logger("Fetched missing item ".$parent_guid." - result: ".$result, LOGGER_DEBUG); + // We now remove the contact + contact_remove($contact["id"]); + return true; + } - $r = q("SELECT * FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", - intval($importer['uid']), - dbesc($parent_guid) + private function receive_comment($importer, $sender, $data) { + $guid = notags(unxmlify($data->guid)); + $parent_guid = notags(unxmlify($data->parent_guid)); + $text = unxmlify($data->text); + $author = notags(unxmlify($data->author)); + + $contact = self::allowed_contact_by_handle($importer, $sender, true); + if (!$contact) + return false; + + if (self::message_exists($importer["uid"], $guid)) + return false; + + $parent_item = self::parent_item($importer["uid"], $parent_guid, $author, $contact); + if (!$parent_item) + return false; + + $person = self::person_by_handle($author); + if (!is_array($person)) { + logger("unable to find author details"); + return false; + } + + // Fetch the contact id - if we know this contact + $author_contact = self::author_contact_by_url($contact, $person, $importer["uid"]); + + $datarray = array(); + + $datarray["uid"] = $importer["uid"]; + $datarray["contact-id"] = $author_contact["cid"]; + $datarray["network"] = $author_contact["network"]; + + $datarray["author-name"] = $person["name"]; + $datarray["author-link"] = $person["url"]; + $datarray["author-avatar"] = ((x($person,"thumb")) ? $person["thumb"] : $person["photo"]); + + $datarray["owner-name"] = $contact["name"]; + $datarray["owner-link"] = $contact["url"]; + $datarray["owner-avatar"] = ((x($contact,"thumb")) ? $contact["thumb"] : $contact["photo"]); + + $datarray["guid"] = $guid; + $datarray["uri"] = $author.":".$guid; + + $datarray["type"] = "remote-comment"; + $datarray["verb"] = ACTIVITY_POST; + $datarray["gravity"] = GRAVITY_COMMENT; + $datarray["parent-uri"] = $parent_item["uri"]; + + $datarray["object-type"] = ACTIVITY_OBJ_COMMENT; + $datarray["object"] = json_encode($data); + + $datarray["body"] = diaspora2bb($text); + + self::fetch_guid($datarray); + + $message_id = item_store($datarray); + + if ($message_id) + logger("Stored comment ".$datarray["guid"]." with message id ".$message_id, LOGGER_DEBUG); + + // If we are the origin of the parent we store the original data and notify our followers + if($message_id AND $parent_item["origin"]) { + + // Formerly we stored the signed text, the signature and the author in different fields. + // We now store the raw data so that we are more flexible. + q("INSERT INTO `sign` (`iid`,`signed_text`) VALUES (%d,'%s')", + intval($message_id), + dbesc(json_encode($data)) ); + + // notify others + proc_run("php", "include/notifier.php", "comment-import", $message_id); } + + return $message_id; } - if(! count($r)) { - logger('diaspora_comment: parent item not found: parent: ' . $parent_guid . ' item: ' . $guid); - return; - } - $parent_item = $r[0]; - - - /* How Diaspora performs comment signature checking: - - - If an item has been sent by the comment author to the top-level post owner to relay on - to the rest of the contacts on the top-level post, the top-level post owner should check - the author_signature, then create a parent_author_signature before relaying the comment on - - If an item has been relayed on by the top-level post owner, the contacts who receive it - check only the parent_author_signature. Basically, they trust that the top-level post - owner has already verified the authenticity of anything he/she sends out - - In either case, the signature that get checked is the signature created by the person - who sent the salmon - */ - - $signed_data = $guid . ';' . $parent_guid . ';' . $text . ';' . $diaspora_handle; - $key = $msg['key']; - - if($parent_author_signature) { - // If a parent_author_signature exists, then we've received the comment - // relayed from the top-level post owner. There's no need to check the - // author_signature if the parent_author_signature is valid - - $parent_author_signature = base64_decode($parent_author_signature); - - if(! rsa_verify($signed_data,$parent_author_signature,$key,'sha256')) { - logger('diaspora_comment: top-level owner verification failed.'); - return; - } - } - else { - // If there's no parent_author_signature, then we've received the comment - // from the comment creator. In that case, the person is commenting on - // our post, so he/she must be a contact of ours and his/her public key - // should be in $msg['key'] - - $author_signature = base64_decode($author_signature); - - if(! rsa_verify($signed_data,$author_signature,$key,'sha256')) { - logger('diaspora_comment: comment author verification failed.'); - return; - } - } - - // Phew! Everything checks out. Now create an item. - - // Find the original comment author information. - // We need this to make sure we display the comment author - // information (name and avatar) correctly. - if(strcasecmp($diaspora_handle,$msg['author']) == 0) - $person = $contact; - else { - $person = find_diaspora_person_by_handle($diaspora_handle); - - if(! is_array($person)) { - logger('diaspora_comment: unable to find author details'); - return; - } - } - - // Fetch the contact id - if we know this contact - $r = q("SELECT `id`, `network` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d LIMIT 1", - dbesc(normalise_link($person['url'])), intval($importer['uid'])); - if ($r) { - $cid = $r[0]['id']; - $network = $r[0]['network']; - } else { - $cid = $contact['id']; - $network = NETWORK_DIASPORA; - } - - $body = diaspora2bb($text); - $message_id = $diaspora_handle . ':' . $guid; - - $datarray = array(); - - $datarray['uid'] = $importer['uid']; - $datarray['contact-id'] = $cid; - $datarray['type'] = 'remote-comment'; - $datarray['wall'] = $parent_item['wall']; - $datarray['network'] = $network; - $datarray['verb'] = ACTIVITY_POST; - $datarray['gravity'] = GRAVITY_COMMENT; - $datarray['guid'] = $guid; - $datarray['uri'] = $message_id; - $datarray['parent-uri'] = $parent_item['uri']; - - // No timestamps for comments? OK, we'll the use current time. - $datarray['changed'] = $datarray['created'] = $datarray['edited'] = datetime_convert(); - $datarray['private'] = $parent_item['private']; - - $datarray['owner-name'] = $parent_item['owner-name']; - $datarray['owner-link'] = $parent_item['owner-link']; - $datarray['owner-avatar'] = $parent_item['owner-avatar']; - - $datarray['author-name'] = $person['name']; - $datarray['author-link'] = $person['url']; - $datarray['author-avatar'] = ((x($person,'thumb')) ? $person['thumb'] : $person['photo']); - $datarray['body'] = $body; - $datarray["object"] = json_encode($xml); - $datarray["object-type"] = ACTIVITY_OBJ_COMMENT; - - // We can't be certain what the original app is if the message is relayed. - if(($parent_item['origin']) && (! $parent_author_signature)) - $datarray['app'] = 'Diaspora'; - - DiasporaFetchGuid($datarray); - $message_id = item_store($datarray); - - $datarray['id'] = $message_id; - - //if($message_id) { - //q("update item set plink = '%s' where id = %d", - // //dbesc($a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $message_id), - // dbesc($a->get_baseurl().'/display/'.$datarray['guid']), - // intval($message_id) - //); - //} - - // If 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($author_signature_base64), - dbesc($diaspora_handle) - ); - - // notify others - proc_run('php','include/notifier.php','comment-import',$message_id); - } - - return; -} - - - - -function diaspora_conversation($importer,$xml,$msg) { - - $a = get_app(); - - $guid = notags(unxmlify($xml->guid)); - $subject = notags(unxmlify($xml->subject)); - $diaspora_handle = notags(unxmlify($xml->diaspora_handle)); - $participant_handles = notags(unxmlify($xml->participant_handles)); - $created_at = datetime_convert('UTC','UTC',notags(unxmlify($xml->created_at))); - - $parent_uri = $diaspora_handle . ':' . $guid; - - $messages = $xml->message; - - if(! count($messages)) { - logger('diaspora_conversation: empty conversation'); - return; - } - - $contact = diaspora_get_contact_by_handle($importer['uid'],$msg['author']); - if(! $contact) { - logger('diaspora_conversation: cannot find contact: ' . $msg['author']); - return; - } - - if(($contact['rel'] == CONTACT_IS_FOLLOWER) || ($contact['blocked']) || ($contact['readonly'])) { - logger('diaspora_conversation: Ignoring this author.'); - return 202; - } - - $conversation = null; - - $c = q("select * from conv where uid = %d and guid = '%s' limit 1", - intval($importer['uid']), - dbesc($guid) - ); - if(count($c)) - $conversation = $c[0]; - else { - $r = q("insert into conv (uid,guid,creator,created,updated,subject,recips) values(%d, '%s', '%s', '%s', '%s', '%s', '%s') ", - intval($importer['uid']), - dbesc($guid), - dbesc($diaspora_handle), - dbesc(datetime_convert('UTC','UTC',$created_at)), - dbesc(datetime_convert()), - dbesc($subject), - dbesc($participant_handles) - ); - if($r) - $c = q("select * from conv where uid = %d and guid = '%s' limit 1", - intval($importer['uid']), - dbesc($guid) - ); - if(count($c)) - $conversation = $c[0]; - } - if(! $conversation) { - logger('diaspora_conversation: unable to create conversation.'); - return; - } - - foreach($messages as $mesg) { + private function receive_conversation_message($importer, $contact, $data, $msg, $mesg, $conversation) { + $guid = notags(unxmlify($data->guid)); + $subject = notags(unxmlify($data->subject)); + $author = notags(unxmlify($data->author)); $reply = 0; @@ -1705,1469 +930,1624 @@ function diaspora_conversation($importer,$xml,$msg) { $msg_parent_author_signature = notags(unxmlify($mesg->parent_author_signature)); $msg_author_signature = notags(unxmlify($mesg->author_signature)); $msg_text = unxmlify($mesg->text); - $msg_created_at = datetime_convert('UTC','UTC',notags(unxmlify($mesg->created_at))); - $msg_diaspora_handle = notags(unxmlify($mesg->diaspora_handle)); + $msg_created_at = datetime_convert("UTC", "UTC", notags(unxmlify($mesg->created_at))); + + // "diaspora_handle" is the element name from the old version + // "author" is the element name from the new version + if ($mesg->author) + $msg_author = notags(unxmlify($mesg->author)); + elseif ($mesg->diaspora_handle) + $msg_author = notags(unxmlify($mesg->diaspora_handle)); + else + return false; + $msg_conversation_guid = notags(unxmlify($mesg->conversation_guid)); + if($msg_conversation_guid != $guid) { - logger('diaspora_conversation: message conversation guid does not belong to the current conversation. ' . $xml); - continue; + logger("message conversation guid does not belong to the current conversation."); + return false; } $body = diaspora2bb($msg_text); - $message_id = $msg_diaspora_handle . ':' . $msg_guid; + $message_uri = $msg_author.":".$msg_guid; - $author_signed_data = $msg_guid . ';' . $msg_parent_guid . ';' . $msg_text . ';' . unxmlify($mesg->created_at) . ';' . $msg_diaspora_handle . ';' . $msg_conversation_guid; + $author_signed_data = $msg_guid.";".$msg_parent_guid.";".$msg_text.";".unxmlify($mesg->created_at).";".$msg_author.";".$msg_conversation_guid; $author_signature = base64_decode($msg_author_signature); - if(strcasecmp($msg_diaspora_handle,$msg['author']) == 0) { + if(strcasecmp($msg_author,$msg["author"]) == 0) { $person = $contact; - $key = $msg['key']; - } - else { - $person = find_diaspora_person_by_handle($msg_diaspora_handle); + $key = $msg["key"]; + } else { + $person = self::person_by_handle($msg_author); - if(is_array($person) && x($person,'pubkey')) - $key = $person['pubkey']; + if (is_array($person) && x($person, "pubkey")) + $key = $person["pubkey"]; else { - logger('diaspora_conversation: unable to find author details'); - continue; + logger("unable to find author details"); + return false; } } - if(! rsa_verify($author_signed_data,$author_signature,$key,'sha256')) { - logger('diaspora_conversation: verification failed.'); - continue; + if (!rsa_verify($author_signed_data, $author_signature, $key, "sha256")) { + logger("verification failed."); + return false; } if($msg_parent_author_signature) { - $owner_signed_data = $msg_guid . ';' . $msg_parent_guid . ';' . $msg_text . ';' . unxmlify($mesg->created_at) . ';' . $msg_diaspora_handle . ';' . $msg_conversation_guid; + $owner_signed_data = $msg_guid.";".$msg_parent_guid.";".$msg_text.";".unxmlify($mesg->created_at).";".$msg_author.";".$msg_conversation_guid; $parent_author_signature = base64_decode($msg_parent_author_signature); - $key = $msg['key']; + $key = $msg["key"]; - if(! rsa_verify($owner_signed_data,$parent_author_signature,$key,'sha256')) { - logger('diaspora_conversation: owner verification failed.'); - continue; + if (!rsa_verify($owner_signed_data, $parent_author_signature, $key, "sha256")) { + logger("owner verification failed."); + return false; } } - $r = q("select id from mail where `uri` = '%s' limit 1", - dbesc($message_id) + $r = q("SELECT `id` FROM `mail` WHERE `uri` = '%s' LIMIT 1", + dbesc($message_uri) ); - if(count($r)) { - logger('diaspora_conversation: duplicate message already delivered.', LOGGER_DEBUG); - continue; + if($r) { + logger("duplicate message already delivered.", LOGGER_DEBUG); + return false; } - q("insert into mail ( `uid`, `guid`, `convid`, `from-name`,`from-photo`,`from-url`,`contact-id`,`title`,`body`,`seen`,`reply`,`uri`,`parent-uri`,`created`) values ( %d, '%s', %d, '%s', '%s', '%s', %d, '%s', '%s', %d, %d, '%s','%s','%s')", - intval($importer['uid']), + q("INSERT INTO `mail` (`uid`, `guid`, `convid`, `from-name`,`from-photo`,`from-url`,`contact-id`,`title`,`body`,`seen`,`reply`,`uri`,`parent-uri`,`created`) + VALUES (%d, '%s', %d, '%s', '%s', '%s', %d, '%s', '%s', %d, %d, '%s','%s','%s')", + intval($importer["uid"]), dbesc($msg_guid), - intval($conversation['id']), - dbesc($person['name']), - dbesc($person['photo']), - dbesc($person['url']), - intval($contact['id']), + intval($conversation["id"]), + dbesc($person["name"]), + dbesc($person["photo"]), + dbesc($person["url"]), + intval($contact["id"]), dbesc($subject), dbesc($body), 0, 0, - dbesc($message_id), - dbesc($parent_uri), + dbesc($message_uri), + dbesc($author.":".$guid), dbesc($msg_created_at) ); - q("update conv set updated = '%s' where id = %d", + q("UPDATE `conv` SET `updated` = '%s' WHERE `id` = %d", dbesc(datetime_convert()), - intval($conversation['id']) + intval($conversation["id"]) ); notification(array( - 'type' => NOTIFY_MAIL, - 'notify_flags' => $importer['notify-flags'], - 'language' => $importer['language'], - 'to_name' => $importer['username'], - 'to_email' => $importer['email'], - 'uid' =>$importer['uid'], - 'item' => array('subject' => $subject, 'body' => $body), - 'source_name' => $person['name'], - 'source_link' => $person['url'], - 'source_photo' => $person['thumb'], - 'verb' => ACTIVITY_POST, - 'otype' => 'mail' + "type" => NOTIFY_MAIL, + "notify_flags" => $importer["notify-flags"], + "language" => $importer["language"], + "to_name" => $importer["username"], + "to_email" => $importer["email"], + "uid" =>$importer["uid"], + "item" => array("subject" => $subject, "body" => $body), + "source_name" => $person["name"], + "source_link" => $person["url"], + "source_photo" => $person["thumb"], + "verb" => ACTIVITY_POST, + "otype" => "mail" )); } - return; -} + private function receive_conversation($importer, $msg, $data) { + $guid = notags(unxmlify($data->guid)); + $subject = notags(unxmlify($data->subject)); + $created_at = datetime_convert("UTC", "UTC", notags(unxmlify($data->created_at))); + $author = notags(unxmlify($data->author)); + $participants = notags(unxmlify($data->participants)); -function diaspora_message($importer,$xml,$msg) { + $messages = $data->message; - $a = get_app(); - - $msg_guid = notags(unxmlify($xml->guid)); - $msg_parent_guid = notags(unxmlify($xml->parent_guid)); - $msg_parent_author_signature = notags(unxmlify($xml->parent_author_signature)); - $msg_author_signature = notags(unxmlify($xml->author_signature)); - $msg_text = unxmlify($xml->text); - $msg_created_at = datetime_convert('UTC','UTC',notags(unxmlify($xml->created_at))); - $msg_diaspora_handle = notags(unxmlify($xml->diaspora_handle)); - $msg_conversation_guid = notags(unxmlify($xml->conversation_guid)); - - $parent_uri = $msg_diaspora_handle . ':' . $msg_parent_guid; - - $contact = diaspora_get_contact_by_handle($importer['uid'],$msg_diaspora_handle); - if(! $contact) { - logger('diaspora_message: cannot find contact: ' . $msg_diaspora_handle); - return; - } - - if(($contact['rel'] == CONTACT_IS_FOLLOWER) || ($contact['blocked']) || ($contact['readonly'])) { - logger('diaspora_message: Ignoring this author.'); - return 202; - } - - $conversation = null; - - $c = q("select * from conv where uid = %d and guid = '%s' limit 1", - intval($importer['uid']), - dbesc($msg_conversation_guid) - ); - if(count($c)) - $conversation = $c[0]; - else { - logger('diaspora_message: conversation not available.'); - return; - } - - $reply = 0; - - $body = diaspora2bb($msg_text); - $message_id = $msg_diaspora_handle . ':' . $msg_guid; - - $author_signed_data = $msg_guid . ';' . $msg_parent_guid . ';' . $msg_text . ';' . unxmlify($xml->created_at) . ';' . $msg_diaspora_handle . ';' . $msg_conversation_guid; - - - $author_signature = base64_decode($msg_author_signature); - - $person = find_diaspora_person_by_handle($msg_diaspora_handle); - if(is_array($person) && x($person,'pubkey')) - $key = $person['pubkey']; - else { - logger('diaspora_message: unable to find author details'); - return; - } - - if(! rsa_verify($author_signed_data,$author_signature,$key,'sha256')) { - logger('diaspora_message: verification failed.'); - return; - } - - $r = q("select id from mail where `uri` = '%s' and uid = %d limit 1", - dbesc($message_id), - intval($importer['uid']) - ); - if(count($r)) { - logger('diaspora_message: duplicate message already delivered.', LOGGER_DEBUG); - return; - } - - q("insert into mail ( `uid`, `guid`, `convid`, `from-name`,`from-photo`,`from-url`,`contact-id`,`title`,`body`,`seen`,`reply`,`uri`,`parent-uri`,`created`) values ( %d, '%s', %d, '%s', '%s', '%s', %d, '%s', '%s', %d, %d, '%s','%s','%s')", - intval($importer['uid']), - dbesc($msg_guid), - intval($conversation['id']), - dbesc($person['name']), - dbesc($person['photo']), - dbesc($person['url']), - intval($contact['id']), - dbesc($conversation['subject']), - dbesc($body), - 0, - 1, - dbesc($message_id), - dbesc($parent_uri), - dbesc($msg_created_at) - ); - - q("update conv set updated = '%s' where id = %d", - dbesc(datetime_convert()), - intval($conversation['id']) - ); - - return; -} - -function diaspora_participation($importer,$xml) { - logger("Unsupported message type 'participation' ".print_r($xml, true)); -} - -function diaspora_photo($importer,$xml,$msg,$attempt=1) { - - $a = get_app(); - - logger('diaspora_photo: init',LOGGER_DEBUG); - - $remote_photo_path = notags(unxmlify($xml->remote_photo_path)); - - $remote_photo_name = notags(unxmlify($xml->remote_photo_name)); - - $status_message_guid = notags(unxmlify($xml->status_message_guid)); - - $guid = notags(unxmlify($xml->guid)); - - $diaspora_handle = notags(unxmlify($xml->diaspora_handle)); - - $public = notags(unxmlify($xml->public)); - - $created_at = notags(unxmlify($xml_created_at)); - - logger('diaspora_photo: status_message_guid: ' . $status_message_guid, LOGGER_DEBUG); - - $contact = diaspora_get_contact_by_handle($importer['uid'],$msg['author']); - if(! $contact) { - logger('diaspora_photo: contact record not found: ' . $msg['author'] . ' handle: ' . $diaspora_handle); - return; - } - - if(! diaspora_post_allow($importer,$contact, false)) { - logger('diaspora_photo: Ignoring this author.'); - return 202; - } - - $r = q("SELECT * FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", - intval($importer['uid']), - dbesc($status_message_guid) - ); - -/* deactivated by now since it can lead to multiplicated pictures in posts. - if(!count($r)) { - $result = diaspora_store_by_guid($status_message_guid, $contact['url'], $importer['uid']); - - if (!$result) { - $person = find_diaspora_person_by_handle($diaspora_handle); - $result = diaspora_store_by_guid($status_message_guid, $person['url'], $importer['uid']); + if (!count($messages)) { + logger("empty conversation"); + return false; } - if ($result) { - logger("Fetched missing item ".$status_message_guid." - result: ".$result, LOGGER_DEBUG); + $contact = self::allowed_contact_by_handle($importer, $msg["author"], true); + if (!$contact) + return false; - $r = q("SELECT * FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", - intval($importer['uid']), - dbesc($status_message_guid) + $conversation = null; + + $c = q("SELECT * FROM `conv` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", + intval($importer["uid"]), + dbesc($guid) + ); + if($c) + $conversation = $c[0]; + else { + $r = q("INSERT INTO `conv` (`uid`, `guid`, `creator`, `created`, `updated`, `subject`, `recips`) + VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s')", + intval($importer["uid"]), + dbesc($guid), + dbesc($author), + dbesc(datetime_convert("UTC", "UTC", $created_at)), + dbesc(datetime_convert()), + dbesc($subject), + dbesc($participants) + ); + if($r) + $c = q("SELECT * FROM `conv` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", + intval($importer["uid"]), + dbesc($guid) + ); + + if($c) + $conversation = $c[0]; + } + if (!$conversation) { + logger("unable to create conversation."); + return; + } + + foreach($messages as $mesg) + self::receive_conversation_message($importer, $contact, $data, $msg, $mesg, $conversation); + + return true; + } + + private function construct_like_body($contact, $parent_item, $guid) { + $bodyverb = t('%1$s likes %2$s\'s %3$s'); + + $ulink = "[url=".$contact["url"]."]".$contact["name"]."[/url]"; + $alink = "[url=".$parent_item["author-link"]."]".$parent_item["author-name"]."[/url]"; + $plink = "[url=".App::get_baseurl()."/display/".urlencode($guid)."]".t("status")."[/url]"; + + return sprintf($bodyverb, $ulink, $alink, $plink); + } + + private function construct_like_object($importer, $parent_item) { + $objtype = ACTIVITY_OBJ_NOTE; + $link = ''; + $parent_body = $parent_item["body"]; + + $xmldata = array("object" => array("type" => $objtype, + "local" => "1", + "id" => $parent_item["uri"], + "link" => $link, + "title" => "", + "content" => $parent_body)); + + return xml::from_array($xmldata, $xml, true); + } + + private function receive_like($importer, $sender, $data) { + $positive = notags(unxmlify($data->positive)); + $guid = notags(unxmlify($data->guid)); + $parent_type = notags(unxmlify($data->parent_type)); + $parent_guid = notags(unxmlify($data->parent_guid)); + $author = notags(unxmlify($data->author)); + + // likes on comments aren't supported by Diaspora - only on posts + // But maybe this will be supported in the future, so we will accept it. + if (!in_array($parent_type, array("Post", "Comment"))) + return false; + + $contact = self::allowed_contact_by_handle($importer, $sender, true); + if (!$contact) + return false; + + if (self::message_exists($importer["uid"], $guid)) + return false; + + $parent_item = self::parent_item($importer["uid"], $parent_guid, $author, $contact); + if (!$parent_item) + return false; + + $person = self::person_by_handle($author); + if (!is_array($person)) { + logger("unable to find author details"); + return false; + } + + // Fetch the contact id - if we know this contact + $author_contact = self::author_contact_by_url($contact, $person, $importer["uid"]); + + // "positive" = "false" would be a Dislike - wich isn't currently supported by Diaspora + // We would accept this anyhow. + if ($positive === "true") + $verb = ACTIVITY_LIKE; + else + $verb = ACTIVITY_DISLIKE; + + $datarray = array(); + + $datarray["uid"] = $importer["uid"]; + $datarray["contact-id"] = $author_contact["cid"]; + $datarray["network"] = $author_contact["network"]; + + $datarray["author-name"] = $person["name"]; + $datarray["author-link"] = $person["url"]; + $datarray["author-avatar"] = ((x($person,"thumb")) ? $person["thumb"] : $person["photo"]); + + $datarray["owner-name"] = $contact["name"]; + $datarray["owner-link"] = $contact["url"]; + $datarray["owner-avatar"] = ((x($contact,"thumb")) ? $contact["thumb"] : $contact["photo"]); + + $datarray["guid"] = $guid; + $datarray["uri"] = $author.":".$guid; + + $datarray["type"] = "activity"; + $datarray["verb"] = $verb; + $datarray["gravity"] = GRAVITY_LIKE; + $datarray["parent-uri"] = $parent_item["uri"]; + + $datarray["object-type"] = ACTIVITY_OBJ_NOTE; + $datarray["object"] = self::construct_like_object($importer, $parent_item); + + $datarray["body"] = self::construct_like_body($contact, $parent_item, $guid); + + $message_id = item_store($datarray); + + if ($message_id) + logger("Stored like ".$datarray["guid"]." with message id ".$message_id, LOGGER_DEBUG); + + // If we are the origin of the parent we store the original data and notify our followers + if($message_id AND $parent_item["origin"]) { + + // Formerly we stored the signed text, the signature and the author in different fields. + // We now store the raw data so that we are more flexible. + q("INSERT INTO `sign` (`iid`,`signed_text`) VALUES (%d,'%s')", + intval($message_id), + dbesc(json_encode($data)) + ); + + // notify others + proc_run("php", "include/notifier.php", "comment-import", $message_id); + } + + return $message_id; + } + + private function receive_message($importer, $data) { + $guid = notags(unxmlify($data->guid)); + $parent_guid = notags(unxmlify($data->parent_guid)); + $text = unxmlify($data->text); + $created_at = datetime_convert("UTC", "UTC", notags(unxmlify($data->created_at))); + $author = notags(unxmlify($data->author)); + $conversation_guid = notags(unxmlify($data->conversation_guid)); + + $contact = self::allowed_contact_by_handle($importer, $author, true); + if (!$contact) + return false; + + $conversation = null; + + $c = q("SELECT * FROM `conv` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", + intval($importer["uid"]), + dbesc($conversation_guid) + ); + if($c) + $conversation = $c[0]; + else { + logger("conversation not available."); + return false; + } + + $reply = 0; + + $body = diaspora2bb($text); + $message_uri = $author.":".$guid; + + $person = self::person_by_handle($author); + if (!$person) { + logger("unable to find author details"); + return false; + } + + $r = q("SELECT `id` FROM `mail` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", + dbesc($message_uri), + intval($importer["uid"]) + ); + if($r) { + logger("duplicate message already delivered.", LOGGER_DEBUG); + return false; + } + + q("INSERT INTO `mail` (`uid`, `guid`, `convid`, `from-name`,`from-photo`,`from-url`,`contact-id`,`title`,`body`,`seen`,`reply`,`uri`,`parent-uri`,`created`) + VALUES ( %d, '%s', %d, '%s', '%s', '%s', %d, '%s', '%s', %d, %d, '%s','%s','%s')", + intval($importer["uid"]), + dbesc($guid), + intval($conversation["id"]), + dbesc($person["name"]), + dbesc($person["photo"]), + dbesc($person["url"]), + intval($contact["id"]), + dbesc($conversation["subject"]), + dbesc($body), + 0, + 1, + dbesc($message_uri), + dbesc($author.":".$parent_guid), + dbesc($created_at) + ); + + q("UPDATE `conv` SET `updated` = '%s' WHERE `id` = %d", + dbesc(datetime_convert()), + intval($conversation["id"]) + ); + + return true; + } + + private function receive_participation($importer, $data) { + // I'm not sure if we can fully support this message type + return true; + } + + private function receive_photo($importer, $data) { + // There doesn't seem to be a reason for this function, since the photo data is transmitted in the status message as well + return true; + } + + private function receive_poll_participation($importer, $data) { + // We don't support polls by now + return true; + } + + private function receive_profile($importer, $data) { + $author = notags(unxmlify($data->author)); + + $contact = self::contact_by_handle($importer["uid"], $author); + if (!$contact) + return; + + $name = unxmlify($data->first_name).((strlen($data->last_name)) ? " ".unxmlify($data->last_name) : ""); + $image_url = unxmlify($data->image_url); + $birthday = unxmlify($data->birthday); + $location = diaspora2bb(unxmlify($data->location)); + $about = diaspora2bb(unxmlify($data->bio)); + $gender = unxmlify($data->gender); + $searchable = (unxmlify($data->searchable) == "true"); + $nsfw = (unxmlify($data->nsfw) == "true"); + $tags = unxmlify($data->tag_string); + + $tags = explode("#", $tags); + + $keywords = array(); + foreach ($tags as $tag) { + $tag = trim(strtolower($tag)); + if ($tag != "") + $keywords[] = $tag; + } + + $keywords = implode(", ", $keywords); + + $handle_parts = explode("@", $author); + $nick = $handle_parts[0]; + + if($name === "") + $name = $handle_parts[0]; + + if( preg_match("|^https?://|", $image_url) === 0) + $image_url = "http://".$handle_parts[1].$image_url; + + update_contact_avatar($image_url, $importer["uid"], $contact["id"]); + + // Generic birthday. We don't know the timezone. The year is irrelevant. + + $birthday = str_replace("1000", "1901", $birthday); + + if ($birthday != "") + $birthday = datetime_convert("UTC", "UTC", $birthday, "Y-m-d"); + + // this is to prevent multiple birthday notifications in a single year + // if we already have a stored birthday and the 'm-d' part hasn't changed, preserve the entry, which will preserve the notify year + + if(substr($birthday,5) === substr($contact["bd"],5)) + $birthday = $contact["bd"]; + + $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($nick), + dbesc($author), + dbesc(datetime_convert()), + dbesc($birthday), + dbesc($location), + dbesc($about), + dbesc($keywords), + dbesc($gender), + intval($contact["id"]), + intval($importer["uid"]) + ); + + if ($searchable) { + poco_check($contact["url"], $name, NETWORK_DIASPORA, $image_url, $about, $location, $gender, $keywords, "", + datetime_convert(), 2, $contact["id"], $importer["uid"]); + } + + $gcontact = array("url" => $contact["url"], "network" => NETWORK_DIASPORA, "generation" => 2, + "photo" => $image_url, "name" => $name, "location" => $location, + "about" => $about, "birthday" => $birthday, "gender" => $gender, + "addr" => $author, "nick" => $nick, "keywords" => $keywords, + "hide" => !$searchable, "nsfw" => $nsfw); + + update_gcontact($gcontact); + + logger("Profile of contact ".$contact["id"]." stored for user ".$importer["uid"], LOGGER_DEBUG); + + return true; + } + + private function receive_request_make_friend($importer, $contact) { + + $a = get_app(); + + if($contact["rel"] == CONTACT_IS_FOLLOWER && in_array($importer["page-flags"], array(PAGE_FREELOVE))) { + q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d", + intval(CONTACT_IS_FRIEND), + intval($contact["id"]), + intval($importer["uid"]) ); } + // send notification + + $r = q("SELECT `hide-friends` FROM `profile` WHERE `uid` = %d AND `is-default` = 1 LIMIT 1", + intval($importer["uid"]) + ); + + if($r && !$r[0]["hide-friends"] && !$contact["hidden"] && intval(get_pconfig($importer["uid"], "system", "post_newfriend"))) { + + $self = q("SELECT * FROM `contact` WHERE `self` AND `uid` = %d LIMIT 1", + intval($importer["uid"]) + ); + + // they are not CONTACT_IS_FOLLOWER anymore but that's what we have in the array + + if($self && $contact["rel"] == CONTACT_IS_FOLLOWER) { + + $arr = array(); + $arr["uri"] = $arr["parent-uri"] = item_new_uri($a->get_hostname(), $importer["uid"]); + $arr["uid"] = $importer["uid"]; + $arr["contact-id"] = $self[0]["id"]; + $arr["wall"] = 1; + $arr["type"] = 'wall'; + $arr["gravity"] = 0; + $arr["origin"] = 1; + $arr["author-name"] = $arr["owner-name"] = $self[0]["name"]; + $arr["author-link"] = $arr["owner-link"] = $self[0]["url"]; + $arr["author-avatar"] = $arr["owner-avatar"] = $self[0]["thumb"]; + $arr["verb"] = ACTIVITY_FRIEND; + $arr["object-type"] = ACTIVITY_OBJ_PERSON; + + $A = "[url=".$self[0]["url"]."]".$self[0]["name"]."[/url]"; + $B = "[url=".$contact["url"]."]".$contact["name"]."[/url]"; + $BPhoto = "[url=".$contact["url"]."][img]".$contact["thumb"]."[/img][/url]"; + $arr["body"] = sprintf(t("%1$s is now friends with %2$s"), $A, $B)."\n\n\n".$Bphoto; + + $arr["object"] = "".ACTIVITY_OBJ_PERSON."".$contact["name"]."" + ."".$contact["url"]."/".$contact["name"].""; + $arr["object"] .= "".xmlify(''."\n"); + $arr["object"] .= xmlify(''."\n"); + $arr["object"] .= "\n"; + $arr["last-child"] = 1; + + $arr["allow_cid"] = $user[0]["allow_cid"]; + $arr["allow_gid"] = $user[0]["allow_gid"]; + $arr["deny_cid"] = $user[0]["deny_cid"]; + $arr["deny_gid"] = $user[0]["deny_gid"]; + + $i = item_store($arr); + if($i) + proc_run("php", "include/notifier.php", "activity", $i); + + } + + } } + + private function receive_request($importer, $data) { + $author = unxmlify($data->author); + $recipient = unxmlify($data->recipient); + + if (!$author || !$recipient) + return; + + $contact = self::contact_by_handle($importer["uid"],$author); + + if($contact) { + + // perhaps we were already sharing with this person. Now they're sharing with us. + // That makes us friends. + + self::receive_request_make_friend($importer, $contact); + return true; + } + + $ret = self::person_by_handle($author); + + if (!$ret || ($ret["network"] != NETWORK_DIASPORA)) { + logger("Cannot resolve diaspora handle ".$author." for ".$recipient); + return false; + } + + $batch = (($ret["batch"]) ? $ret["batch"] : implode("/", array_slice(explode("/", $ret["url"]), 0, 3))."/receive/public"); + + $r = q("INSERT INTO `contact` (`uid`, `network`,`addr`,`created`,`url`,`nurl`,`batch`,`name`,`nick`,`photo`,`pubkey`,`notify`,`poll`,`blocked`,`priority`) + VALUES (%d, '%s', '%s', '%s', '%s','%s','%s','%s','%s','%s','%s','%s','%s',%d,%d)", + intval($importer["uid"]), + dbesc($ret["network"]), + dbesc($ret["addr"]), + datetime_convert(), + dbesc($ret["url"]), + dbesc(normalise_link($ret["url"])), + dbesc($batch), + dbesc($ret["name"]), + dbesc($ret["nick"]), + dbesc($ret["photo"]), + dbesc($ret["pubkey"]), + dbesc($ret["notify"]), + dbesc($ret["poll"]), + 1, + 2 + ); + + // find the contact record we just created + + $contact_record = self::contact_by_handle($importer["uid"],$author); + + if (!$contact_record) { + logger("unable to locate newly created contact record."); + return; + } + + $g = q("SELECT `def_gid` FROM `user` WHERE `uid` = %d LIMIT 1", + intval($importer["uid"]) + ); + + if($g && intval($g[0]["def_gid"])) + group_add_member($importer["uid"], "", $contact_record["id"], $g[0]["def_gid"]); + + if($importer["page-flags"] == PAGE_NORMAL) { + + $hash = random_string().(string)time(); // Generate a confirm_key + + $ret = q("INSERT INTO `intro` (`uid`, `contact-id`, `blocked`, `knowyou`, `note`, `hash`, `datetime`) + VALUES (%d, %d, %d, %d, '%s', '%s', '%s')", + intval($importer["uid"]), + intval($contact_record["id"]), + 0, + 0, + dbesc(t("Sharing notification from Diaspora network")), + dbesc($hash), + dbesc(datetime_convert()) + ); + } else { + + // automatic friend approval + + 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 + // we are going to change the relationship and make them a follower. + + if($importer["page-flags"] == PAGE_FREELOVE) + $new_relation = CONTACT_IS_FRIEND; + else + $new_relation = CONTACT_IS_FOLLOWER; + + $r = q("UPDATE `contact` SET `rel` = %d, + `name-date` = '%s', + `uri-date` = '%s', + `blocked` = 0, + `pending` = 0, + `writable` = 1 + WHERE `id` = %d + ", + intval($new_relation), + dbesc(datetime_convert()), + dbesc(datetime_convert()), + intval($contact_record["id"]) + ); + + $u = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1", intval($importer["uid"])); + if($u) + $ret = self::send_share($u[0], $contact_record); + } + + return true; + } + + private function original_item($guid, $orig_author, $author) { + + // Do we already have this item? + $r = q("SELECT `body`, `tag`, `app`, `created`, `object-type`, `uri`, `guid`, + `author-name`, `author-link`, `author-avatar` + FROM `item` WHERE `guid` = '%s' AND `visible` AND NOT `deleted` AND `body` != '' LIMIT 1", + dbesc($guid)); + + if($r) { + logger("reshared message ".$guid." already exists on system."); + + // Maybe it is already a reshared item? + // Then refetch the content, since there can be many side effects with reshared posts from other networks or reshares from reshares + if (self::is_reshare($r[0]["body"])) + $r = array(); + else + return $r[0]; + } + + if (!$r) { + $server = "https://".substr($orig_author, strpos($orig_author, "@") + 1); + logger("1st try: reshared message ".$guid." will be fetched from original server: ".$server); + $item_id = self::store_by_guid($guid, $server); + + if (!$item_id) { + $server = "http://".substr($orig_author, strpos($orig_author, "@") + 1); + logger("2nd try: reshared message ".$guid." will be fetched from original server: ".$server); + $item_id = self::store_by_guid($guid, $server); + } + + // Deactivated by now since there is a risk that someone could manipulate postings through this method +/* if (!$item_id) { + $server = "https://".substr($author, strpos($author, "@") + 1); + logger("3rd try: reshared message ".$guid." will be fetched from sharer's server: ".$server); + $item_id = self::store_by_guid($guid, $server); + } + if (!$item_id) { + $server = "http://".substr($author, strpos($author, "@") + 1); + logger("4th try: reshared message ".$guid." will be fetched from sharer's server: ".$server); + $item_id = self::store_by_guid($guid, $server); + } */ - if(!count($r)) { - if($attempt <= 3) { - q("INSERT INTO dsprphotoq (uid, msg, attempt) VALUES (%d, '%s', %d)", - intval($importer['uid']), - dbesc(serialize($msg)), - intval($attempt + 1) - ); - } + if ($item_id) { + $r = q("SELECT `body`, `tag`, `app`, `created`, `object-type`, `uri`, `guid`, + `author-name`, `author-link`, `author-avatar` + FROM `item` WHERE `id` = %d AND `visible` AND NOT `deleted` AND `body` != '' LIMIT 1", + intval($item_id)); - logger('diaspora_photo: attempt = ' . $attempt . '; status message not found: ' . $status_message_guid . ' for photo: ' . $guid); - return; - } + if ($r) + return $r[0]; - $parent_item = $r[0]; - - $link_text = '[img]' . $remote_photo_path . $remote_photo_name . '[/img]' . "\n"; - - $link_text = scale_external_images($link_text, true, - array($remote_photo_name, 'scaled_full_' . $remote_photo_name)); - - if(strpos($parent_item['body'],$link_text) === false) { - - $parent_item['body'] = $link_text . $parent_item['body']; - - $r = q("UPDATE `item` SET `body` = '%s', `visible` = 1 WHERE `id` = %d AND `uid` = %d", - dbesc($parent_item['body']), - intval($parent_item['id']), - intval($parent_item['uid']) - ); - put_item_in_cache($parent_item, true); - update_thread($parent_item['id']); - } - - return; -} - - - - -function diaspora_like($importer,$xml,$msg) { - - $a = get_app(); - $guid = notags(unxmlify($xml->guid)); - $parent_guid = notags(unxmlify($xml->parent_guid)); - $diaspora_handle = notags(unxmlify($xml->diaspora_handle)); - $target_type = notags(unxmlify($xml->target_type)); - $positive = notags(unxmlify($xml->positive)); - $author_signature = notags(unxmlify($xml->author_signature)); - - $parent_author_signature = (($xml->parent_author_signature) ? notags(unxmlify($xml->parent_author_signature)) : ''); - - // likes on comments not supported here and likes on photos not supported by Diaspora - -// if($target_type !== 'Post') -// return; - - $contact = diaspora_get_contact_by_handle($importer['uid'],$msg['author']); - if(! $contact) { - logger('diaspora_like: cannot find contact: ' . $msg['author']); - return; - } - - if(! diaspora_post_allow($importer,$contact, false)) { - logger('diaspora_like: Ignoring this author.'); - return 202; - } - - $r = q("SELECT * FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", - intval($importer['uid']), - dbesc($parent_guid) - ); - - if(!count($r)) { - $result = diaspora_store_by_guid($parent_guid, $contact['url'], $importer['uid']); - - if (!$result) { - $person = find_diaspora_person_by_handle($diaspora_handle); - $result = diaspora_store_by_guid($parent_guid, $person['url'], $importer['uid']); - } - - if ($result) { - logger("Fetched missing item ".$parent_guid." - result: ".$result, LOGGER_DEBUG); - - $r = q("SELECT * FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", - intval($importer['uid']), - dbesc($parent_guid) - ); - } - } - - if(! count($r)) { - logger('diaspora_like: parent item not found: ' . $guid); - return; - } - - $parent_item = $r[0]; - - $r = q("SELECT * FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", - intval($importer['uid']), - dbesc($guid) - ); - if(count($r)) { - if($positive === 'true') { - logger('diaspora_like: duplicate like: ' . $guid); - return; - } - // Note: I don't think "Like" objects with positive = "false" are ever actually used - // It looks like "RelayableRetractions" are used for "unlike" instead - if($positive === 'false') { - logger('diaspora_like: received a like with positive set to "false"...ignoring'); -/* q("UPDATE `item` SET `deleted` = 1 WHERE `id` = %d AND `uid` = %d", - intval($r[0]['id']), - intval($importer['uid']) - );*/ - // FIXME--actually don't unless it turns out that Diaspora does indeed send out "false" likes - // send notification via proc_run() - return; - } - } - // Note: I don't think "Like" objects with positive = "false" are ever actually used - // It looks like "RelayableRetractions" are used for "unlike" instead - if($positive === 'false') { - logger('diaspora_like: received a like with positive set to "false"'); - logger('diaspora_like: unlike received with no corresponding like...ignoring'); - return; - } - - - /* How Diaspora performs "like" signature checking: - - - If an item has been sent by the like author to the top-level post owner to relay on - to the rest of the contacts on the top-level post, the top-level post owner should check - the author_signature, then create a parent_author_signature before relaying the like on - - If an item has been relayed on by the top-level post owner, the contacts who receive it - check only the parent_author_signature. Basically, they trust that the top-level post - owner has already verified the authenticity of anything he/she sends out - - In either case, the signature that get checked is the signature created by the person - who sent the salmon - */ - - // Diaspora has changed the way they are signing the likes. - // Just to make sure that we don't miss any likes we will check the old and the current way. - $old_signed_data = $guid . ';' . $target_type . ';' . $parent_guid . ';' . $positive . ';' . $diaspora_handle; - - $signed_data = $positive . ';' . $guid . ';' . $target_type . ';' . $parent_guid . ';' . $diaspora_handle; - - $key = $msg['key']; - - if ($parent_author_signature) { - // If a parent_author_signature exists, then we've received the like - // relayed from the top-level post owner. There's no need to check the - // author_signature if the parent_author_signature is valid - - $parent_author_signature = base64_decode($parent_author_signature); - - if (!rsa_verify($signed_data,$parent_author_signature,$key,'sha256') AND - !rsa_verify($old_signed_data,$parent_author_signature,$key,'sha256')) { - - logger('diaspora_like: top-level owner verification failed.'); - return; - } - } else { - // If there's no parent_author_signature, then we've received the like - // from the like creator. In that case, the person is "like"ing - // our post, so he/she must be a contact of ours and his/her public key - // should be in $msg['key'] - - $author_signature = base64_decode($author_signature); - - if (!rsa_verify($signed_data,$author_signature,$key,'sha256') AND - !rsa_verify($old_signed_data,$author_signature,$key,'sha256')) { - - logger('diaspora_like: like creator verification failed.'); - return; - } - } - - // Phew! Everything checks out. Now create an item. - - // Find the original comment author information. - // We need this to make sure we display the comment author - // information (name and avatar) correctly. - if(strcasecmp($diaspora_handle,$msg['author']) == 0) - $person = $contact; - else { - $person = find_diaspora_person_by_handle($diaspora_handle); - - if(! is_array($person)) { - logger('diaspora_like: unable to find author details'); - return; - } - } - - $uri = $diaspora_handle . ':' . $guid; - - $activity = ACTIVITY_LIKE; - $post_type = (($parent_item['resource-id']) ? t('photo') : t('status')); - $objtype = (($parent_item['resource-id']) ? ACTIVITY_OBJ_PHOTO : ACTIVITY_OBJ_NOTE ); - $link = xmlify('' . "\n") ; - $body = $parent_item['body']; - - $obj = <<< EOT - - - $objtype - 1 - {$parent_item['uri']} - $link - - $body - -EOT; - $bodyverb = t('%1$s likes %2$s\'s %3$s'); - - // Fetch the contact id - if we know this contact - $r = q("SELECT `id`, `network` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d LIMIT 1", - dbesc(normalise_link($person['url'])), intval($importer['uid'])); - if ($r) { - $cid = $r[0]['id']; - $network = $r[0]['network']; - } else { - $cid = $contact['id']; - $network = NETWORK_DIASPORA; - } - - $arr = array(); - - $arr['uri'] = $uri; - $arr['uid'] = $importer['uid']; - $arr['guid'] = $guid; - $arr['network'] = $network; - $arr['contact-id'] = $cid; - $arr['type'] = 'activity'; - $arr['wall'] = $parent_item['wall']; - $arr['gravity'] = GRAVITY_LIKE; - $arr['parent'] = $parent_item['id']; - $arr['parent-uri'] = $parent_item['uri']; - - $arr['owner-name'] = $parent_item['name']; - $arr['owner-link'] = $parent_item['url']; - //$arr['owner-avatar'] = $parent_item['thumb']; - $arr['owner-avatar'] = ((x($parent_item,'thumb')) ? $parent_item['thumb'] : $parent_item['photo']); - - $arr['author-name'] = $person['name']; - $arr['author-link'] = $person['url']; - $arr['author-avatar'] = ((x($person,'thumb')) ? $person['thumb'] : $person['photo']); - - $ulink = '[url=' . $contact['url'] . ']' . $contact['name'] . '[/url]'; - $alink = '[url=' . $parent_item['author-link'] . ']' . $parent_item['author-name'] . '[/url]'; - //$plink = '[url=' . $a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $parent_item['id'] . ']' . $post_type . '[/url]'; - $plink = '[url='.$a->get_baseurl().'/display/'.urlencode($guid).']'.$post_type.'[/url]'; - $arr['body'] = sprintf( $bodyverb, $ulink, $alink, $plink ); - - $arr['app'] = 'Diaspora'; - - $arr['private'] = $parent_item['private']; - $arr['verb'] = $activity; - $arr['object-type'] = $objtype; - $arr['object'] = $obj; - $arr['visible'] = 1; - $arr['unseen'] = 1; - $arr['last-child'] = 0; - - $message_id = item_store($arr); - - - //if($message_id) { - // q("update item set plink = '%s' where id = %d", - // //dbesc($a->get_baseurl() . '/display/' . $importer['nickname'] . '/' . $message_id), - // dbesc($a->get_baseurl().'/display/'.$guid), - // intval($message_id) - // ); - //} - - // If 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($author_signature_base64), - dbesc($diaspora_handle) - ); - - // notify others - proc_run('php','include/notifier.php','comment-import',$message_id); - } - - return; -} - -function diaspora_retraction($importer,$xml) { - - - $guid = notags(unxmlify($xml->guid)); - $diaspora_handle = notags(unxmlify($xml->diaspora_handle)); - $type = notags(unxmlify($xml->type)); - - $contact = diaspora_get_contact_by_handle($importer['uid'],$diaspora_handle); - if(! $contact) - return; - - if($type === 'Person') { - require_once('include/Contact.php'); - contact_remove($contact['id']); - } elseif($type === 'StatusMessage') { - $guid = notags(unxmlify($xml->post_guid)); - - $r = q("SELECT * FROM `item` WHERE `guid` = '%s' AND `uid` = %d AND NOT `file` LIKE '%%[%%' LIMIT 1", - dbesc($guid), - intval($importer['uid']) - ); - if(count($r)) { - if(link_compare($r[0]['author-link'],$contact['url'])) { - q("UPDATE `item` SET `deleted` = 1, `changed` = '%s' WHERE `id` = %d", - dbesc(datetime_convert()), - intval($r[0]['id']) - ); - delete_thread($r[0]['id'], $r[0]['parent-uri']); } } - } elseif($type === 'Post') { - $r = q("select * from item where guid = '%s' and uid = %d and not file like '%%[%%' limit 1", - dbesc('guid'), - intval($importer['uid']) + return false; + } + + private function receive_reshare($importer, $data) { + $root_author = notags(unxmlify($data->root_author)); + $root_guid = notags(unxmlify($data->root_guid)); + $guid = notags(unxmlify($data->guid)); + $author = notags(unxmlify($data->author)); + $public = notags(unxmlify($data->public)); + $created_at = notags(unxmlify($data->created_at)); + + $contact = self::allowed_contact_by_handle($importer, $author, false); + if (!$contact) + return false; + + if (self::message_exists($importer["uid"], $guid)) + return false; + + $original_item = self::original_item($root_guid, $root_author, $author); + if (!$original_item) + return false; + + $orig_url = App::get_baseurl()."/display/".$original_item["guid"]; + + $datarray = array(); + + $datarray["uid"] = $importer["uid"]; + $datarray["contact-id"] = $contact["id"]; + $datarray["network"] = NETWORK_DIASPORA; + + $datarray["author-name"] = $contact["name"]; + $datarray["author-link"] = $contact["url"]; + $datarray["author-avatar"] = ((x($contact,"thumb")) ? $contact["thumb"] : $contact["photo"]); + + $datarray["owner-name"] = $datarray["author-name"]; + $datarray["owner-link"] = $datarray["author-link"]; + $datarray["owner-avatar"] = $datarray["author-avatar"]; + + $datarray["guid"] = $guid; + $datarray["uri"] = $datarray["parent-uri"] = $author.":".$guid; + + $datarray["verb"] = ACTIVITY_POST; + $datarray["gravity"] = GRAVITY_PARENT; + + $datarray["object"] = json_encode($data); + + $prefix = share_header($original_item["author-name"], $original_item["author-link"], $original_item["author-avatar"], + $original_item["guid"], $original_item["created"], $orig_url); + $datarray["body"] = $prefix.$original_item["body"]."[/share]"; + + $datarray["tag"] = $original_item["tag"]; + $datarray["app"] = $original_item["app"]; + + $datarray["plink"] = self::plink($author, $guid); + $datarray["private"] = (($public == "false") ? 1 : 0); + $datarray["changed"] = $datarray["created"] = $datarray["edited"] = datetime_convert("UTC", "UTC", $created_at); + + $datarray["object-type"] = $original_item["object-type"]; + + self::fetch_guid($datarray); + $message_id = item_store($datarray); + + if ($message_id) + logger("Stored reshare ".$datarray["guid"]." with message id ".$message_id, LOGGER_DEBUG); + + return $message_id; + } + + private function item_retraction($importer, $contact, $data) { + $target_type = notags(unxmlify($data->target_type)); + $target_guid = notags(unxmlify($data->target_guid)); + $author = notags(unxmlify($data->author)); + + $person = self::person_by_handle($author); + if (!is_array($person)) { + logger("unable to find author detail for ".$author); + return false; + } + + $r = q("SELECT `id`, `parent`, `parent-uri`, `author-link` FROM `item` WHERE `guid` = '%s' AND `uid` = %d AND NOT `file` LIKE '%%[%%' LIMIT 1", + dbesc($target_guid), + intval($importer["uid"]) ); - if(count($r)) { - if(link_compare($r[0]['author-link'],$contact['url'])) { - q("update item set `deleted` = 1, `changed` = '%s' where `id` = %d", - dbesc(datetime_convert()), - intval($r[0]['id']) - ); - delete_thread($r[0]['id'], $r[0]['parent-uri']); + if (!$r) + return false; + + // Only delete it if the author really fits + if (!link_compare($r[0]["author-link"], $person["url"])) { + logger("Item author ".$r[0]["author-link"]." doesn't fit to expected contact ".$person["url"], LOGGER_DEBUG); + return false; + } + + // Check if the sender is the thread owner + $p = q("SELECT `id`, `author-link`, `origin` FROM `item` WHERE `id` = %d", + intval($r[0]["parent"])); + + // Only delete it if the parent author really fits + if (!link_compare($p[0]["author-link"], $contact["url"]) AND !link_compare($r[0]["author-link"], $contact["url"])) { + logger("Thread author ".$p[0]["author-link"]." and item author ".$r[0]["author-link"]." don't fit to expected contact ".$contact["url"], LOGGER_DEBUG); + return false; + } + + // Currently we don't have a central deletion function that we could use in this case. The function "item_drop" doesn't work for that case + q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = '' WHERE `id` = %d", + dbesc(datetime_convert()), + dbesc(datetime_convert()), + intval($r[0]["id"]) + ); + delete_thread($r[0]["id"], $r[0]["parent-uri"]); + + logger("Deleted target ".$target_guid." (".$r[0]["id"].") from user ".$importer["uid"]." parent: ".$p[0]["id"], LOGGER_DEBUG); + + // Now check if the retraction needs to be relayed by us + if($p[0]["origin"]) { + + // Formerly we stored the signed text, the signature and the author in different fields. + // We now store the raw data so that we are more flexible. + q("INSERT INTO `sign` (`retract_iid`,`signed_text`) VALUES (%d,'%s')", + intval($r[0]["id"]), + dbesc(json_encode($data)) + ); + $s = q("select * from sign where retract_iid = %d", intval($r[0]["id"])); + logger("Stored signatur for item ".$r[0]["id"]." - ".print_r($s, true), LOGGER_DEBUG); + + // notify others + proc_run("php", "include/notifier.php", "drop", $r[0]["id"]); + } + } + + private function receive_retraction($importer, $sender, $data) { + $target_type = notags(unxmlify($data->target_type)); + + $contact = self::contact_by_handle($importer["uid"], $sender); + if (!$contact) { + logger("cannot find contact for sender: ".$sender." and user ".$importer["uid"]); + return false; + } + + logger("Got retraction for ".$target_type.", sender ".$sender." and user ".$importer["uid"], LOGGER_DEBUG); + + switch ($target_type) { + case "Comment": + case "Like": + case "Post": // "Post" will be supported in a future version + case "Reshare": + case "StatusMessage": + return self::item_retraction($importer, $contact, $data);; + + case "Person": + /// @todo What should we do with an "unshare"? + // Removing the contact isn't correct since we still can read the public items + //contact_remove($contact["id"]); + return true; + + default: + logger("Unknown target type ".$target_type); + return false; + } + return true; + } + + private function receive_status_message($importer, $data) { + + $raw_message = unxmlify($data->raw_message); + $guid = notags(unxmlify($data->guid)); + $author = notags(unxmlify($data->author)); + $public = notags(unxmlify($data->public)); + $created_at = notags(unxmlify($data->created_at)); + $provider_display_name = notags(unxmlify($data->provider_display_name)); + + /// @todo enable support for polls + //if ($data->poll) { + // foreach ($data->poll AS $poll) + // print_r($poll); + // die("poll!\n"); + //} + $contact = self::allowed_contact_by_handle($importer, $author, false); + if (!$contact) + return false; + + if (self::message_exists($importer["uid"], $guid)) + return false; + + $address = array(); + if ($data->location) + foreach ($data->location->children() AS $fieldname => $data) + $address[$fieldname] = notags(unxmlify($data)); + + $body = diaspora2bb($raw_message); + + $datarray = array(); + + if ($data->photo) { + foreach ($data->photo AS $photo) + $body = "[img]".$photo->remote_photo_path.$photo->remote_photo_name."[/img]\n".$body; + + $datarray["object-type"] = ACTIVITY_OBJ_PHOTO; + } else { + $datarray["object-type"] = ACTIVITY_OBJ_NOTE; + + // Add OEmbed and other information to the body + if (!self::is_redmatrix($contact["url"])) + $body = add_page_info_to_body($body, false, true); + } + + $datarray["uid"] = $importer["uid"]; + $datarray["contact-id"] = $contact["id"]; + $datarray["network"] = NETWORK_DIASPORA; + + $datarray["author-name"] = $contact["name"]; + $datarray["author-link"] = $contact["url"]; + $datarray["author-avatar"] = ((x($contact,"thumb")) ? $contact["thumb"] : $contact["photo"]); + + $datarray["owner-name"] = $datarray["author-name"]; + $datarray["owner-link"] = $datarray["author-link"]; + $datarray["owner-avatar"] = $datarray["author-avatar"]; + + $datarray["guid"] = $guid; + $datarray["uri"] = $datarray["parent-uri"] = $author.":".$guid; + + $datarray["verb"] = ACTIVITY_POST; + $datarray["gravity"] = GRAVITY_PARENT; + + $datarray["object"] = json_encode($data); + + $datarray["body"] = $body; + + if ($provider_display_name != "") + $datarray["app"] = $provider_display_name; + + $datarray["plink"] = self::plink($author, $guid); + $datarray["private"] = (($public == "false") ? 1 : 0); + $datarray["changed"] = $datarray["created"] = $datarray["edited"] = datetime_convert("UTC", "UTC", $created_at); + + if (isset($address["address"])) + $datarray["location"] = $address["address"]; + + if (isset($address["lat"]) AND isset($address["lng"])) + $datarray["coord"] = $address["lat"]." ".$address["lng"]; + + self::fetch_guid($datarray); + $message_id = item_store($datarray); + + if ($message_id) + logger("Stored item ".$datarray["guid"]." with message id ".$message_id, LOGGER_DEBUG); + + return $message_id; + } + + /****************************************************************************************** + * Here are all the functions that are needed to transmit data with the Diaspora protocol * + ******************************************************************************************/ + + private function my_handle($me) { + if ($contact["addr"] != "") + return $contact["addr"]; + + // Normally we should have a filled "addr" field - but in the past this wasn't the case + // So - just in case - we build the the address here. + return $me["nickname"]."@".substr(App::get_baseurl(), strpos(App::get_baseurl(),"://") + 3); + } + + private function build_public_message($msg, $user, $contact, $prvkey, $pubkey) { + + logger("Message: ".$msg, LOGGER_DATA); + + $handle = self::my_handle($user); + + $b64url_data = base64url_encode($msg); + + $data = str_replace(array("\n", "\r", " ", "\t"), array("", "", "", ""), $b64url_data); + + $type = "application/xml"; + $encoding = "base64url"; + $alg = "RSA-SHA256"; + + $signable_data = $data.".".base64url_encode($type).".".base64url_encode($encoding).".".base64url_encode($alg); + + $signature = rsa_sign($signable_data,$prvkey); + $sig = base64url_encode($signature); + + $xmldata = array("diaspora" => array("header" => array("author_id" => $handle), + "me:env" => array("me:encoding" => "base64url", + "me:alg" => "RSA-SHA256", + "me:data" => $data, + "@attributes" => array("type" => "application/xml"), + "me:sig" => $sig))); + + $namespaces = array("" => "https://joindiaspora.com/protocol", + "me" => "http://salmon-protocol.org/ns/magic-env"); + + $magic_env = xml::from_array($xmldata, $xml, false, $namespaces); + + logger("magic_env: ".$magic_env, LOGGER_DATA); + return $magic_env; + } + + private function build_private_message($msg, $user, $contact, $prvkey, $pubkey) { + + logger("Message: ".$msg, LOGGER_DATA); + + // without a public key nothing will work + + if (!$pubkey) { + logger("pubkey missing: contact id: ".$contact["id"]); + return false; + } + + $inner_aes_key = random_string(32); + $b_inner_aes_key = base64_encode($inner_aes_key); + $inner_iv = random_string(16); + $b_inner_iv = base64_encode($inner_iv); + + $outer_aes_key = random_string(32); + $b_outer_aes_key = base64_encode($outer_aes_key); + $outer_iv = random_string(16); + $b_outer_iv = base64_encode($outer_iv); + + $handle = self::my_handle($user); + + $padded_data = pkcs5_pad($msg,16); + $inner_encrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $inner_aes_key, $padded_data, MCRYPT_MODE_CBC, $inner_iv); + + $b64_data = base64_encode($inner_encrypted); + + + $b64url_data = base64url_encode($b64_data); + $data = str_replace(array("\n", "\r", " ", "\t"), array("", "", "", ""), $b64url_data); + + $type = "application/xml"; + $encoding = "base64url"; + $alg = "RSA-SHA256"; + + $signable_data = $data.".".base64url_encode($type).".".base64url_encode($encoding).".".base64url_encode($alg); + + $signature = rsa_sign($signable_data,$prvkey); + $sig = base64url_encode($signature); + + $xmldata = array("decrypted_header" => array("iv" => $b_inner_iv, + "aes_key" => $b_inner_aes_key, + "author_id" => $handle)); + + $decrypted_header = xml::from_array($xmldata, $xml, true); + $decrypted_header = pkcs5_pad($decrypted_header,16); + + $ciphertext = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $outer_aes_key, $decrypted_header, MCRYPT_MODE_CBC, $outer_iv); + + $outer_json = json_encode(array("iv" => $b_outer_iv, "key" => $b_outer_aes_key)); + + $encrypted_outer_key_bundle = ""; + openssl_public_encrypt($outer_json, $encrypted_outer_key_bundle, $pubkey); + + $b64_encrypted_outer_key_bundle = base64_encode($encrypted_outer_key_bundle); + + logger("outer_bundle: ".$b64_encrypted_outer_key_bundle." key: ".$pubkey, LOGGER_DATA); + + $encrypted_header_json_object = json_encode(array("aes_key" => base64_encode($encrypted_outer_key_bundle), + "ciphertext" => base64_encode($ciphertext))); + $cipher_json = base64_encode($encrypted_header_json_object); + + $xmldata = array("diaspora" => array("encrypted_header" => $cipher_json, + "me:env" => array("me:encoding" => "base64url", + "me:alg" => "RSA-SHA256", + "me:data" => $data, + "@attributes" => array("type" => "application/xml"), + "me:sig" => $sig))); + + $namespaces = array("" => "https://joindiaspora.com/protocol", + "me" => "http://salmon-protocol.org/ns/magic-env"); + + $magic_env = xml::from_array($xmldata, $xml, false, $namespaces); + + logger("magic_env: ".$magic_env, LOGGER_DATA); + return $magic_env; + } + + private function build_message($msg, $user, $contact, $prvkey, $pubkey, $public = false) { + + if ($public) + $magic_env = self::build_public_message($msg,$user,$contact,$prvkey,$pubkey); + else + $magic_env = self::build_private_message($msg,$user,$contact,$prvkey,$pubkey); + + // The data that will be transmitted is double encoded via "urlencode", strange ... + $slap = "xml=".urlencode(urlencode($magic_env)); + return $slap; + } + + private function signature($owner, $message) { + $sigmsg = $message; + unset($sigmsg["author_signature"]); + unset($sigmsg["parent_author_signature"]); + + $signed_text = implode(";", $sigmsg); + + return base64_encode(rsa_sign($signed_text, $owner["uprvkey"], "sha256")); + } + + public static function transmit($owner, $contact, $slap, $public_batch, $queue_run=false, $guid = "") { + + $a = get_app(); + + $enabled = intval(get_config("system", "diaspora_enabled")); + if(!$enabled) + return 200; + + $logid = random_string(4); + $dest_url = (($public_batch) ? $contact["batch"] : $contact["notify"]); + if (!$dest_url) { + logger("no url for contact: ".$contact["id"]." batch mode =".$public_batch); + return 0; + } + + logger("transmit: ".$logid."-".$guid." ".$dest_url); + + if (!$queue_run && was_recently_delayed($contact["id"])) { + $return_code = 0; + } else { + if (!intval(get_config("system", "diaspora_test"))) { + post_url($dest_url."/", $slap); + $return_code = $a->get_curl_code(); + } else { + logger("test_mode"); + return 200; } } - } - return 202; - // NOTREACHED -} + logger("transmit: ".$logid."-".$guid." returns: ".$return_code); -function diaspora_signed_retraction($importer,$xml,$msg) { + if(!$return_code || (($return_code == 503) && (stristr($a->get_curl_headers(), "retry-after")))) { + logger("queue message"); - - $guid = notags(unxmlify($xml->target_guid)); - $diaspora_handle = notags(unxmlify($xml->sender_handle)); - $type = notags(unxmlify($xml->target_type)); - $sig = notags(unxmlify($xml->target_author_signature)); - - $parent_author_signature = (($xml->parent_author_signature) ? notags(unxmlify($xml->parent_author_signature)) : ''); - - $contact = diaspora_get_contact_by_handle($importer['uid'],$diaspora_handle); - if(! $contact) { - logger('diaspora_signed_retraction: no contact ' . $diaspora_handle . ' for ' . $importer['uid']); - return; - } - - - $signed_data = $guid . ';' . $type ; - $key = $msg['key']; - - /* How Diaspora performs relayable_retraction signature checking: - - - If an item has been sent by the item author to the top-level post owner to relay on - to the rest of the contacts on the top-level post, the top-level post owner checks - the author_signature, then creates a parent_author_signature before relaying the item on - - If an item has been relayed on by the top-level post owner, the contacts who receive it - check only the parent_author_signature. Basically, they trust that the top-level post - owner has already verified the authenticity of anything he/she sends out - - In either case, the signature that get checked is the signature created by the person - who sent the salmon - */ - - if($parent_author_signature) { - - $parent_author_signature = base64_decode($parent_author_signature); - - if(! rsa_verify($signed_data,$parent_author_signature,$key,'sha256')) { - logger('diaspora_signed_retraction: top-level post owner verification failed'); - return; + $r = q("SELECT `id` FROM `queue` WHERE `cid` = %d AND `network` = '%s' AND `content` = '%s' AND `batch` = %d LIMIT 1", + intval($contact["id"]), + dbesc(NETWORK_DIASPORA), + dbesc($slap), + intval($public_batch) + ); + if($r) { + logger("add_to_queue ignored - identical item already in queue"); + } else { + // queue message for redelivery + add_to_queue($contact["id"], NETWORK_DIASPORA, $slap, $public_batch); + } } - } else { - - $sig_decode = base64_decode($sig); - - if(! rsa_verify($signed_data,$sig_decode,$key,'sha256')) { - logger('diaspora_signed_retraction: retraction owner verification failed.' . print_r($msg,true)); - return; - } + return(($return_code) ? $return_code : (-1)); } - if($type === 'StatusMessage' || $type === 'Comment' || $type === 'Like') { - $r = q("select * from item where guid = '%s' and uid = %d and not file like '%%[%%' limit 1", - dbesc($guid), - intval($importer['uid']) + + private function build_and_transmit($owner, $contact, $type, $message, $public_batch = false, $guid = "", $spool = false) { + + $data = array("XML" => array("post" => array($type => $message))); + + $msg = xml::from_array($data, $xml); + + logger('message: '.$msg, LOGGER_DATA); + logger('send guid '.$guid, LOGGER_DEBUG); + + $slap = self::build_message($msg, $owner, $contact, $owner['uprvkey'], $contact['pubkey'], $public_batch); + + if ($spool) { + add_to_queue($contact['id'], NETWORK_DIASPORA, $slap, $public_batch); + return true; + } else + $return_code = self::transmit($owner, $contact, $slap, $public_batch, false, $guid); + + logger("guid: ".$item["guid"]." result ".$return_code, LOGGER_DEBUG); + + return $return_code; + } + + public static function send_share($owner,$contact) { + + $message = array("sender_handle" => self::my_handle($owner), + "recipient_handle" => $contact["addr"]); + + return self::build_and_transmit($owner, $contact, "request", $message); + } + + public static function send_unshare($owner,$contact) { + + $message = array("post_guid" => $owner["guid"], + "diaspora_handle" => self::my_handle($owner), + "type" => "Person"); + + return self::build_and_transmit($owner, $contact, "retraction", $message); + } + + public static function is_reshare($body) { + $body = trim($body); + + // Skip if it isn't a pure repeated messages + // Does it start with a share? + if (strpos($body, "[share") > 0) + return(false); + + // Does it end with a share? + if (strlen($body) > (strrpos($body, "[/share]") + 8)) + return(false); + + $attributes = preg_replace("/\[share(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism","$1",$body); + // Skip if there is no shared message in there + if ($body == $attributes) + return(false); + + $guid = ""; + preg_match("/guid='(.*?)'/ism", $attributes, $matches); + if ($matches[1] != "") + $guid = $matches[1]; + + preg_match('/guid="(.*?)"/ism', $attributes, $matches); + if ($matches[1] != "") + $guid = $matches[1]; + + if ($guid != "") { + $r = q("SELECT `contact-id` FROM `item` WHERE `guid` = '%s' AND `network` IN ('%s', '%s') LIMIT 1", + dbesc($guid), NETWORK_DFRN, NETWORK_DIASPORA); + if ($r) { + $ret= array(); + $ret["root_handle"] = self::handle_from_contact($r[0]["contact-id"]); + $ret["root_guid"] = $guid; + return($ret); + } + } + + $profile = ""; + preg_match("/profile='(.*?)'/ism", $attributes, $matches); + if ($matches[1] != "") + $profile = $matches[1]; + + preg_match('/profile="(.*?)"/ism', $attributes, $matches); + if ($matches[1] != "") + $profile = $matches[1]; + + $ret= array(); + + $ret["root_handle"] = preg_replace("=https?://(.*)/u/(.*)=ism", "$2@$1", $profile); + if (($ret["root_handle"] == $profile) OR ($ret["root_handle"] == "")) + return(false); + + $link = ""; + preg_match("/link='(.*?)'/ism", $attributes, $matches); + if ($matches[1] != "") + $link = $matches[1]; + + preg_match('/link="(.*?)"/ism', $attributes, $matches); + if ($matches[1] != "") + $link = $matches[1]; + + $ret["root_guid"] = preg_replace("=https?://(.*)/posts/(.*)=ism", "$2", $link); + if (($ret["root_guid"] == $link) OR ($ret["root_guid"] == "")) + return(false); + return($ret); + } + + public static function send_status($item, $owner, $contact, $public_batch = false) { + + $myaddr = self::my_handle($owner); + + $public = (($item["private"]) ? "false" : "true"); + + $created = datetime_convert("UTC", "UTC", $item["created"], 'Y-m-d H:i:s \U\T\C'); + + // Detect a share element and do a reshare + if (!$item['private'] AND ($ret = self::is_reshare($item["body"]))) { + $message = array("root_diaspora_id" => $ret["root_handle"], + "root_guid" => $ret["root_guid"], + "guid" => $item["guid"], + "diaspora_handle" => $myaddr, + "public" => $public, + "created_at" => $created, + "provider_display_name" => $item["app"]); + + $type = "reshare"; + } else { + $title = $item["title"]; + $body = $item["body"]; + + // convert to markdown + $body = html_entity_decode(bb2diaspora($body)); + + // Adding the title + if(strlen($title)) + $body = "## ".html_entity_decode($title)."\n\n".$body; + + if ($item["attach"]) { + $cnt = preg_match_all('/href=\"(.*?)\"(.*?)title=\"(.*?)\"/ism', $item["attach"], $matches, PREG_SET_ORDER); + if(cnt) { + $body .= "\n".t("Attachments:")."\n"; + foreach($matches as $mtch) + $body .= "[".$mtch[3]."](".$mtch[1].")\n"; + } + } + + $location = array(); + + if ($item["location"] != "") + $location["address"] = $item["location"]; + + if ($item["coord"] != "") { + $coord = explode(" ", $item["coord"]); + $location["lat"] = $coord[0]; + $location["lng"] = $coord[1]; + } + + $message = array("raw_message" => $body, + "location" => $location, + "guid" => $item["guid"], + "diaspora_handle" => $myaddr, + "public" => $public, + "created_at" => $created, + "provider_display_name" => $item["app"]); + + if (count($location) == 0) + unset($message["location"]); + + $type = "status_message"; + } + + return self::build_and_transmit($owner, $contact, $type, $message, $public_batch, $item["guid"]); + } + + private function construct_like($item, $owner) { + + $myaddr = self::my_handle($owner); + + $p = q("SELECT `guid`, `uri`, `parent-uri` FROM `item` WHERE `uri` = '%s' LIMIT 1", + dbesc($item["thr-parent"])); + if(!$p) + return false; + + $parent = $p[0]; + + $target_type = ($parent["uri"] === $parent["parent-uri"] ? "Post" : "Comment"); + $positive = "true"; + + return(array("positive" => $positive, + "guid" => $item["guid"], + "target_type" => $target_type, + "parent_guid" => $parent["guid"], + "author_signature" => $authorsig, + "diaspora_handle" => $myaddr)); + } + + private function construct_comment($item, $owner) { + + $myaddr = self::my_handle($owner); + + $p = q("SELECT `guid` FROM `item` WHERE `parent` = %d AND `id` = %d LIMIT 1", + intval($item["parent"]), + intval($item["parent"]) ); - if(count($r)) { - if(link_compare($r[0]['author-link'],$contact['url'])) { - q("update item set `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = '' where `id` = %d", - dbesc(datetime_convert()), - dbesc(datetime_convert()), - intval($r[0]['id']) - ); - delete_thread($r[0]['id'], $r[0]['parent-uri']); - // Now check if the retraction needs to be relayed by us - // - // The first item in the `item` table with the parent id is the parent. However, MySQL doesn't always - // return the items ordered by `item`.`id`, in which case the wrong item is chosen as the parent. - // The only item with `parent` and `id` as the parent id is the parent item. - $p = q("SELECT `origin` FROM `item` WHERE `parent` = %d AND `id` = %d LIMIT 1", - intval($r[0]['parent']), - intval($r[0]['parent']) - ); - if(count($p)) { - 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), - dbesc($sig), - dbesc($diaspora_handle) - ); + if (!$p) + return false; - // the existence of parent_author_signature would have meant the parent_author or owner - // is already relaying. - logger('diaspora_signed_retraction: relaying relayable_retraction'); + $parent = $p[0]; - proc_run('php','include/notifier.php','drop',$r[0]['id']); + $text = html_entity_decode(bb2diaspora($item["body"])); + + return(array("guid" => $item["guid"], + "parent_guid" => $parent["guid"], + "author_signature" => "", + "text" => $text, + "diaspora_handle" => $myaddr)); + } + + public static function send_followup($item,$owner,$contact,$public_batch = false) { + + if($item['verb'] === ACTIVITY_LIKE) { + $message = self::construct_like($item, $owner); + $type = "like"; + } else { + $message = self::construct_comment($item, $owner); + $type = "comment"; + } + + if (!$message) + return false; + + $message["author_signature"] = self::signature($owner, $message); + + return self::build_and_transmit($owner, $contact, $type, $message, $public_batch, $item["guid"]); + } + + private function message_from_signatur($item, $signature) { + + // Split the signed text + $signed_parts = explode(";", $signature['signed_text']); + + if ($item["deleted"]) + $message = array("parent_author_signature" => "", + "target_guid" => $signed_parts[0], + "target_type" => $signed_parts[1], + "sender_handle" => $signature['signer'], + "target_author_signature" => $signature['signature']); + elseif ($item['verb'] === ACTIVITY_LIKE) + $message = array("positive" => $signed_parts[0], + "guid" => $signed_parts[1], + "target_type" => $signed_parts[2], + "parent_guid" => $signed_parts[3], + "parent_author_signature" => "", + "author_signature" => $signature['signature'], + "diaspora_handle" => $signed_parts[4]); + else { + // Remove the comment guid + $guid = array_shift($signed_parts); + + // Remove the parent guid + $parent_guid = array_shift($signed_parts); + + // Remove the handle + $handle = array_pop($signed_parts); + + // Glue the parts together + $text = implode(";", $signed_parts); + + $message = array("guid" => $guid, + "parent_guid" => $parent_guid, + "parent_author_signature" => "", + "author_signature" => $signature['signature'], + "text" => implode(";", $signed_parts), + "diaspora_handle" => $handle); + } + return $message; + } + + public static function send_relay($item, $owner, $contact, $public_batch = false) { + + if ($item["deleted"]) { + $sql_sign_id = "retract_iid"; + $type = "relayable_retraction"; + } elseif ($item['verb'] === ACTIVITY_LIKE) { + $sql_sign_id = "iid"; + $type = "like"; + } else { + $sql_sign_id = "iid"; + $type = "comment"; + } + + logger("Got relayable data ".$type." for item ".$item["guid"]." (".$item["id"].")", LOGGER_DEBUG); + + // fetch the original signature + + $r = q("SELECT `signed_text`, `signature`, `signer` FROM `sign` WHERE `".$sql_sign_id."` = %d LIMIT 1", + intval($item["id"])); + + if (!$r) + return self::send_followup($item, $owner, $contact, $public_batch); + + $signature = $r[0]; + + // Old way - is used by the internal Friendica functions + /// @todo Change all signatur storing functions to the new format + if ($signature['signed_text'] AND $signature['signature'] AND $signature['signer']) + $message = self::message_from_signatur($item, $signature); + else {// New way + $msg = json_decode($signature['signed_text'], true); + + $message = array(); + foreach ($msg AS $field => $data) { + if (!$item["deleted"]) { + if ($field == "author") + $field = "diaspora_handle"; + if ($field == "parent_type") + $field = "target_type"; + } + + $message[$field] = $data; + } + } + + if ($item["deleted"]) { + $signed_text = $message["target_guid"].';'.$message["target_type"]; + $message["parent_author_signature"] = base64_encode(rsa_sign($signed_text, $owner["uprvkey"], "sha256")); + } else + $message["parent_author_signature"] = self::signature($owner, $message); + + logger("Relayed data ".print_r($message, true), LOGGER_DEBUG); + + return self::build_and_transmit($owner, $contact, $type, $message, $public_batch, $item["guid"]); + } + + public static function send_retraction($item, $owner, $contact, $public_batch = false) { + + $myaddr = self::my_handle($owner); + + // Check whether the retraction is for a top-level post or whether it's a relayable + if ($item["uri"] !== $item["parent-uri"]) { + $msg_type = "relayable_retraction"; + $target_type = (($item["verb"] === ACTIVITY_LIKE) ? "Like" : "Comment"); + } else { + $msg_type = "signed_retraction"; + $target_type = "StatusMessage"; + } + + $signed_text = $item["guid"].";".$target_type; + + $message = array("target_guid" => $item['guid'], + "target_type" => $target_type, + "sender_handle" => $myaddr, + "target_author_signature" => base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256'))); + + return self::build_and_transmit($owner, $contact, $msg_type, $message, $public_batch, $item["guid"]); + } + + public static function send_mail($item, $owner, $contact) { + + $myaddr = self::my_handle($owner); + + $r = q("SELECT * FROM `conv` WHERE `id` = %d AND `uid` = %d LIMIT 1", + intval($item["convid"]), + intval($item["uid"]) + ); + + if (!$r) { + logger("conversation not found."); + return; + } + $cnv = $r[0]; + + $conv = array( + "guid" => $cnv["guid"], + "subject" => $cnv["subject"], + "created_at" => datetime_convert("UTC", "UTC", $cnv['created'], 'Y-m-d H:i:s \U\T\C'), + "diaspora_handle" => $cnv["creator"], + "participant_handles" => $cnv["recips"] + ); + + $body = bb2diaspora($item["body"]); + $created = datetime_convert("UTC", "UTC", $item["created"], 'Y-m-d H:i:s \U\T\C'); + + $signed_text = $item["guid"].";".$cnv["guid"].";".$body.";".$created.";".$myaddr.";".$cnv['guid']; + $sig = base64_encode(rsa_sign($signed_text, $owner["uprvkey"], "sha256")); + + $msg = array( + "guid" => $item["guid"], + "parent_guid" => $cnv["guid"], + "parent_author_signature" => $sig, + "author_signature" => $sig, + "text" => $body, + "created_at" => $created, + "diaspora_handle" => $myaddr, + "conversation_guid" => $cnv["guid"] + ); + + if ($item["reply"]) { + $message = $msg; + $type = "message"; + } else { + $message = array("guid" => $cnv["guid"], + "subject" => $cnv["subject"], + "created_at" => datetime_convert("UTC", "UTC", $cnv['created'], 'Y-m-d H:i:s \U\T\C'), + "message" => $msg, + "diaspora_handle" => $cnv["creator"], + "participant_handles" => $cnv["recips"]); + + $type = "conversation"; + } + + return self::build_and_transmit($owner, $contact, $type, $message, false, $item["guid"]); + } + + public static function send_profile($uid) { + + if (!$uid) + return; + + $recips = q("SELECT `id`,`name`,`network`,`pubkey`,`notify` FROM `contact` WHERE `network` = '%s' + AND `uid` = %d AND `rel` != %d", + dbesc(NETWORK_DIASPORA), + intval($uid), + intval(CONTACT_IS_SHARING) + ); + if (!$recips) + return; + + $r = q("SELECT `profile`.`uid` AS `profile_uid`, `profile`.* , `user`.*, `user`.`prvkey` AS `uprvkey`, `contact`.`addr` + FROM `profile` + INNER JOIN `user` ON `profile`.`uid` = `user`.`uid` + INNER JOIN `contact` ON `profile`.`uid` = `contact`.`uid` + WHERE `user`.`uid` = %d AND `profile`.`is-default` AND `contact`.`self` LIMIT 1", + intval($uid) + ); + + if (!$r) + return; + + $profile = $r[0]; + + $handle = $profile["addr"]; + $first = ((strpos($profile['name'],' ') + ? trim(substr($profile['name'],0,strpos($profile['name'],' '))) : $profile['name'])); + $last = (($first === $profile['name']) ? '' : trim(substr($profile['name'], strlen($first)))); + $large = App::get_baseurl().'/photo/custom/300/'.$profile['uid'].'.jpg'; + $medium = App::get_baseurl().'/photo/custom/100/'.$profile['uid'].'.jpg'; + $small = App::get_baseurl().'/photo/custom/50/' .$profile['uid'].'.jpg'; + $searchable = (($profile['publish'] && $profile['net-publish']) ? 'true' : 'false'); + + if ($searchable === 'true') { + $dob = '1000-00-00'; + + if (($profile['dob']) && ($profile['dob'] != '0000-00-00')) + $dob = ((intval($profile['dob'])) ? intval($profile['dob']) : '1000') .'-'. datetime_convert('UTC','UTC',$profile['dob'],'m-d'); + + $about = $profile['about']; + $about = strip_tags(bbcode($about)); + + $location = formatted_location($profile); + $tags = ''; + if ($profile['pub_keywords']) { + $kw = str_replace(',',' ',$profile['pub_keywords']); + $kw = str_replace(' ',' ',$kw); + $arr = explode(' ',$profile['pub_keywords']); + if (count($arr)) { + for($x = 0; $x < 5; $x ++) { + if (trim($arr[$x])) + $tags .= '#'. trim($arr[$x]) .' '; } } } + $tags = trim($tags); } - } - else - logger('diaspora_signed_retraction: unknown type: ' . $type); - return 202; - // NOTREACHED -} - -function diaspora_profile($importer,$xml,$msg) { - - $a = get_app(); - $diaspora_handle = notags(unxmlify($xml->diaspora_handle)); - - - if($diaspora_handle != $msg['author']) { - logger('diaspora_post: Potential forgery. Message handle is not the same as envelope sender.'); - return 202; - } - - $contact = diaspora_get_contact_by_handle($importer['uid'],$diaspora_handle); - if(! $contact) - return; - - //if($contact['blocked']) { - // logger('diaspora_post: Ignoring this author.'); - // return 202; - //} - - $name = unxmlify($xml->first_name) . ((strlen($xml->last_name)) ? ' ' . unxmlify($xml->last_name) : ''); - $image_url = unxmlify($xml->image_url); - $birthday = unxmlify($xml->birthday); - $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); - - $keywords = array(); - foreach ($tags as $tag) { - $tag = trim(strtolower($tag)); - if ($tag != "") - $keywords[] = $tag; - } - - $keywords = implode(", ", $keywords); - - $handle_parts = explode("@", $diaspora_handle); - $nick = $handle_parts[0]; - - if($name === '') { - $name = $handle_parts[0]; - } - - if( preg_match("|^https?://|", $image_url) === 0) { - $image_url = "http://" . $handle_parts[1] . $image_url; - } - -/* $r = q("SELECT DISTINCT ( `resource-id` ) FROM `photo` WHERE `uid` = %d AND `contact-id` = %d AND `album` = 'Contact Photos' ", - intval($importer['uid']), - intval($contact['id']) - ); - $oldphotos = ((count($r)) ? $r : null);*/ - - require_once('include/Photo.php'); - - update_contact_avatar($image_url,$importer['uid'],$contact['id']); - - // Generic birthday. We don't know the timezone. The year is irrelevant. - - $birthday = str_replace('1000','1901',$birthday); - - if ($birthday != "") - $birthday = datetime_convert('UTC','UTC',$birthday,'Y-m-d'); - - // this is to prevent multiple birthday notifications in a single year - // if we already have a stored birthday and the 'm-d' part hasn't changed, preserve the entry, which will preserve the notify year - - if(substr($birthday,5) === substr($contact['bd'],5)) - $birthday = $contact['bd']; - - /// @TODO Update name on item['author-name'] if the name changed. See consume_feed() - /// (Not doing this currently because D* protocol is scheduled for revision soon). - - $r = q("UPDATE `contact` SET `name` = '%s', `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($nick), - dbesc($diaspora_handle), - dbesc(datetime_convert()), - dbesc($birthday), - dbesc($location), - dbesc($about), - dbesc($keywords), - dbesc($gender), - intval($contact['id']), - intval($importer['uid']) - ); - - if ($searchable) { - require_once('include/socgraph.php'); - poco_check($contact['url'], $name, NETWORK_DIASPORA, $image_url, $about, $location, $gender, $keywords, "", - datetime_convert(), 2, $contact['id'], $importer['uid']); - } - - 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) { - foreach($oldphotos as $ph) { - q("DELETE FROM `photo` WHERE `uid` = %d AND `contact-id` = %d AND `album` = 'Contact Photos' AND `resource-id` = '%s' ", - intval($importer['uid']), - intval($contact['id']), - dbesc($ph['resource-id']) - ); - } - } - } */ - - return; - -} - -function diaspora_share($me,$contact) { - $a = get_app(); - $myaddr = $me['nickname'] . '@' . substr($a->get_baseurl(), strpos($a->get_baseurl(),'://') + 3); - $theiraddr = $contact['addr']; - - $tpl = get_markup_template('diaspora_share.tpl'); - $msg = replace_macros($tpl, array( - '$sender' => $myaddr, - '$recipient' => $theiraddr - )); - - $slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($msg,$me,$contact,$me['prvkey'],$contact['pubkey']))); - //$slap = 'xml=' . urlencode(diaspora_msg_build($msg,$me,$contact,$me['prvkey'],$contact['pubkey'])); - - return(diaspora_transmit($owner,$contact,$slap, false)); -} - -function diaspora_unshare($me,$contact) { - - $a = get_app(); - $myaddr = $me['nickname'] . '@' . substr($a->get_baseurl(), strpos($a->get_baseurl(),'://') + 3); - - $tpl = get_markup_template('diaspora_retract.tpl'); - $msg = replace_macros($tpl, array( - '$guid' => $me['guid'], - '$type' => 'Person', - '$handle' => $myaddr - )); - - $slap = 'xml=' . urlencode(urlencode(diaspora_msg_build($msg,$me,$contact,$me['prvkey'],$contact['pubkey']))); - //$slap = 'xml=' . urlencode(diaspora_msg_build($msg,$me,$contact,$me['prvkey'],$contact['pubkey'])); - - return(diaspora_transmit($owner,$contact,$slap, false)); - -} - - -function diaspora_send_status($item,$owner,$contact,$public_batch = false) { - - $a = get_app(); - $myaddr = $owner['nickname'] . '@' . substr($a->get_baseurl(), strpos($a->get_baseurl(),'://') + 3); - $theiraddr = $contact['addr']; - - $images = array(); - - $title = $item['title']; - $body = $item['body']; - -/* - // We're trying to match Diaspora's split message/photo protocol but - // all the photos are displayed on D* as links and not img's - even - // though we're sending pretty much precisely what they send us when - // doing the same operation. - // Commented out for now, we'll use bb2diaspora to convert photos to markdown - // which seems to get through intact. - - $cnt = preg_match_all('|\[img\](.*?)\[\/img\]|',$body,$matches,PREG_SET_ORDER); - if($cnt) { - foreach($matches as $mtch) { - $detail = array(); - $detail['str'] = $mtch[0]; - $detail['path'] = dirname($mtch[1]) . '/'; - $detail['file'] = basename($mtch[1]); - $detail['guid'] = $item['guid']; - $detail['handle'] = $myaddr; - $images[] = $detail; - $body = str_replace($detail['str'],$mtch[1],$body); - } - } -*/ - - //if(strlen($title)) - // $body = "[b]".html_entity_decode($title)."[/b]\n\n".$body; - - // convert to markdown - $body = xmlify(html_entity_decode(bb2diaspora($body))); - //$body = bb2diaspora($body); - - // Adding the title - if(strlen($title)) - $body = "## ".html_entity_decode($title)."\n\n".$body; - - if($item['attach']) { - $cnt = preg_match_all('/href=\"(.*?)\"(.*?)title=\"(.*?)\"/ism',$item['attach'],$matches,PREG_SET_ORDER); - if(cnt) { - $body .= "\n" . t('Attachments:') . "\n"; - foreach($matches as $mtch) { - $body .= '[' . $mtch[3] . '](' . $mtch[1] . ')' . "\n"; - } - } - } - - - $public = (($item['private']) ? 'false' : 'true'); - - require_once('include/datetime.php'); - $created = datetime_convert('UTC','UTC',$item['created'],'Y-m-d H:i:s \U\T\C'); - - // Detect a share element and do a reshare - // see: https://github.com/Raven24/diaspora-federation/blob/master/lib/diaspora-federation/entities/reshare.rb - if (!$item['private'] AND ($ret = diaspora_is_reshare($item["body"]))) { - $tpl = get_markup_template('diaspora_reshare.tpl'); - $msg = replace_macros($tpl, array( - '$root_handle' => xmlify($ret['root_handle']), - '$root_guid' => $ret['root_guid'], - '$guid' => $item['guid'], - '$handle' => xmlify($myaddr), - '$public' => $public, - '$created' => $created, - '$provider' => $item["app"] - )); - } else { - $tpl = get_markup_template('diaspora_post.tpl'); - $msg = replace_macros($tpl, array( - '$body' => $body, - '$guid' => $item['guid'], - '$handle' => xmlify($myaddr), - '$public' => $public, - '$created' => $created, - '$provider' => $item["app"] - )); - } - - logger('diaspora_send_status: '.$owner['username'].' -> '.$contact['name'].' base message: '.$msg, LOGGER_DATA); - 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,false,$item['guid']); - - logger('diaspora_send_status: guid: '.$item['guid'].' result '.$return_code, LOGGER_DEBUG); - - if(count($images)) { - diaspora_send_images($item,$owner,$contact,$images,$public_batch); - } - - return $return_code; -} - -function diaspora_is_reshare($body) { - $body = trim($body); - - // Skip if it isn't a pure repeated messages - // Does it start with a share? - if (strpos($body, "[share") > 0) - return(false); - - // Does it end with a share? - if (strlen($body) > (strrpos($body, "[/share]") + 8)) - return(false); - - $attributes = preg_replace("/\[share(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism","$1",$body); - // Skip if there is no shared message in there - if ($body == $attributes) - return(false); - - $guid = ""; - preg_match("/guid='(.*?)'/ism", $attributes, $matches); - if ($matches[1] != "") - $guid = $matches[1]; - - preg_match('/guid="(.*?)"/ism', $attributes, $matches); - if ($matches[1] != "") - $guid = $matches[1]; - - if ($guid != "") { - $r = q("SELECT `contact-id` FROM `item` WHERE `guid` = '%s' AND `network` IN ('%s', '%s') LIMIT 1", - dbesc($guid), NETWORK_DFRN, NETWORK_DIASPORA); - if ($r) { - $ret= array(); - $ret["root_handle"] = diaspora_handle_from_contact($r[0]["contact-id"]); - $ret["root_guid"] = $guid; - return($ret); - } - } - - $profile = ""; - preg_match("/profile='(.*?)'/ism", $attributes, $matches); - if ($matches[1] != "") - $profile = $matches[1]; - - preg_match('/profile="(.*?)"/ism', $attributes, $matches); - if ($matches[1] != "") - $profile = $matches[1]; - - $ret= array(); - - $ret["root_handle"] = preg_replace("=https?://(.*)/u/(.*)=ism", "$2@$1", $profile); - if (($ret["root_handle"] == $profile) OR ($ret["root_handle"] == "")) - return(false); - - $link = ""; - preg_match("/link='(.*?)'/ism", $attributes, $matches); - if ($matches[1] != "") - $link = $matches[1]; - - preg_match('/link="(.*?)"/ism', $attributes, $matches); - if ($matches[1] != "") - $link = $matches[1]; - - $ret["root_guid"] = preg_replace("=https?://(.*)/posts/(.*)=ism", "$2", $link); - if (($ret["root_guid"] == $link) OR ($ret["root_guid"] == "")) - return(false); - - return($ret); -} - -function diaspora_send_images($item,$owner,$contact,$images,$public_batch = false) { - $a = get_app(); - if(! count($images)) - return; - $mysite = substr($a->get_baseurl(),strpos($a->get_baseurl(),'://') + 3) . '/photo'; - - $tpl = get_markup_template('diaspora_photo.tpl'); - foreach($images as $image) { - if(! stristr($image['path'],$mysite)) - continue; - $resource = str_replace('.jpg','',$image['file']); - $resource = substr($resource,0,strpos($resource,'-')); - - $r = q("select * from photo where `resource-id` = '%s' and `uid` = %d limit 1", - dbesc($resource), - intval($owner['uid']) - ); - if(! count($r)) - continue; - $public = (($r[0]['allow_cid'] || $r[0]['allow_gid'] || $r[0]['deny_cid'] || $r[0]['deny_gid']) ? 'false' : 'true' ); - $msg = replace_macros($tpl,array( - '$path' => xmlify($image['path']), - '$filename' => xmlify($image['file']), - '$msg_guid' => xmlify($image['guid']), - '$guid' => xmlify($r[0]['guid']), - '$handle' => xmlify($image['handle']), - '$public' => xmlify($public), - '$created_at' => xmlify(datetime_convert('UTC','UTC',$r[0]['created'],'Y-m-d H:i:s \U\T\C')) - )); - - - logger('diaspora_send_photo: base message: ' . $msg, LOGGER_DATA); - 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,false,$r[0]['guid']); - } - -} - -function diaspora_send_followup($item,$owner,$contact,$public_batch = false) { - - $a = get_app(); - $myaddr = $owner['nickname'] . '@' . substr($a->get_baseurl(), strpos($a->get_baseurl(),'://') + 3); -// $theiraddr = $contact['addr']; - - // Diaspora doesn't support threaded comments, but some - // versions of Diaspora (i.e. Diaspora-pistos) support - // likes on comments - if($item['verb'] === ACTIVITY_LIKE && $item['thr-parent']) { - $p = q("select guid, type, uri, `parent-uri` from item where uri = '%s' limit 1", - dbesc($item['thr-parent']) - ); - } - else { - // The first item in the `item` table with the parent id is the parent. However, MySQL doesn't always - // return the items ordered by `item`.`id`, in which case the wrong item is chosen as the parent. - // The only item with `parent` and `id` as the parent id is the parent item. - $p = q("select guid, type, uri, `parent-uri` from item where parent = %d and id = %d limit 1", - intval($item['parent']), - intval($item['parent']) - ); - } - if(count($p)) - $parent = $p[0]; - else - return; - - if($item['verb'] === ACTIVITY_LIKE) { - $tpl = get_markup_template('diaspora_like.tpl'); - $like = true; - $target_type = ( $parent['uri'] === $parent['parent-uri'] ? 'Post' : 'Comment'); -// $target_type = (strpos($parent['type'], 'comment') ? 'Comment' : 'Post'); -// $positive = (($item['deleted']) ? 'false' : 'true'); - $positive = 'true'; - - if(($item['deleted'])) - logger('diaspora_send_followup: received deleted "like". Those should go to diaspora_send_retraction'); - } - else { - $tpl = get_markup_template('diaspora_comment.tpl'); - $like = false; - } - - $text = html_entity_decode(bb2diaspora($item['body'])); - - // sign it - - if($like) - $signed_text = $positive . ';' . $item['guid'] . ';' . $target_type . ';' . $parent['guid'] . ';' . $myaddr; - else - $signed_text = $item['guid'] . ';' . $parent['guid'] . ';' . $text . ';' . $myaddr; - - $authorsig = base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256')); - - $msg = replace_macros($tpl,array( - '$guid' => xmlify($item['guid']), - '$parent_guid' => xmlify($parent['guid']), - '$target_type' =>xmlify($target_type), - '$authorsig' => xmlify($authorsig), - '$body' => xmlify($text), - '$positive' => xmlify($positive), - '$handle' => xmlify($myaddr) - )); - - logger('diaspora_followup: base message: ' . $msg, LOGGER_DATA); - 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,false,$item['guid'])); -} - - -function diaspora_send_relay($item,$owner,$contact,$public_batch = false) { - - - $a = get_app(); - $myaddr = $owner['nickname'] . '@' . substr($a->get_baseurl(), strpos($a->get_baseurl(),'://') + 3); -// $theiraddr = $contact['addr']; - - // Diaspora doesn't support threaded comments, but some - // versions of Diaspora (i.e. Diaspora-pistos) support - // likes on comments - if($item['verb'] === ACTIVITY_LIKE && $item['thr-parent']) { - $p = q("select guid, type, uri, `parent-uri` from item where uri = '%s' limit 1", - dbesc($item['thr-parent']) - ); - } - else { - // The first item in the `item` table with the parent id is the parent. However, MySQL doesn't always - // return the items ordered by `item`.`id`, in which case the wrong item is chosen as the parent. - // The only item with `parent` and `id` as the parent id is the parent item. - $p = q("select guid, type, uri, `parent-uri` from item where parent = %d and id = %d limit 1", - intval($item['parent']), - intval($item['parent']) - ); - } - if(count($p)) - $parent = $p[0]; - else - return; - - $like = false; - $relay_retract = false; - $sql_sign_id = 'iid'; - if( $item['deleted']) { - $relay_retract = true; - - $target_type = ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment'); - - $sql_sign_id = 'retract_iid'; - $tpl = get_markup_template('diaspora_relayable_retraction.tpl'); - } - elseif($item['verb'] === ACTIVITY_LIKE) { - $like = true; - - $target_type = ( $parent['uri'] === $parent['parent-uri'] ? 'Post' : 'Comment'); -// $positive = (($item['deleted']) ? 'false' : 'true'); - $positive = 'true'; - - $tpl = get_markup_template('diaspora_like_relay.tpl'); - } - else { // item is a comment - $tpl = get_markup_template('diaspora_comment_relay.tpl'); - } - - - // fetch the original signature if the relayable was created by a Diaspora - // or DFRN user. Relayables for other networks are not supported. - - $r = q("SELECT `signed_text`, `signature`, `signer` FROM `sign` WHERE " . $sql_sign_id . " = %d LIMIT 1", - intval($item['id']) - ); - if(count($r)) { - $orig_sign = $r[0]; - $signed_text = $orig_sign['signed_text']; - $authorsig = $orig_sign['signature']; - $handle = $orig_sign['signer']; - - // 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 - - $body = $item['body']; - $text = html_entity_decode(bb2diaspora($body)); - - $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; - - $authorsig = base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256')); - } - - // Sign the relayable with the top-level owner's signature - $parentauthorsig = base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256')); - - $msg = replace_macros($tpl,array( - '$guid' => xmlify($item['guid']), - '$parent_guid' => xmlify($parent['guid']), - '$target_type' =>xmlify($target_type), - '$authorsig' => xmlify($authorsig), - '$parentsig' => xmlify($parentauthorsig), - '$body' => xmlify($text), - '$positive' => xmlify($positive), - '$handle' => xmlify($handle) - )); - - 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,false,$item['guid'])); - -} - - - -function diaspora_send_retraction($item,$owner,$contact,$public_batch = false) { - - $a = get_app(); - $myaddr = $owner['nickname'] . '@' . substr($a->get_baseurl(), strpos($a->get_baseurl(),'://') + 3); - - // Check whether the retraction is for a top-level post or whether it's a relayable - if( $item['uri'] !== $item['parent-uri'] ) { - - $tpl = get_markup_template('diaspora_relay_retraction.tpl'); - $target_type = (($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment'); - } - else { - - $tpl = get_markup_template('diaspora_signed_retract.tpl'); - $target_type = 'StatusMessage'; - } - - $signed_text = $item['guid'] . ';' . $target_type; - - $msg = replace_macros($tpl, array( - '$guid' => xmlify($item['guid']), - '$type' => xmlify($target_type), - '$handle' => xmlify($myaddr), - '$signature' => xmlify(base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256'))) - )); - - 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,false,$item['guid'])); -} - -function diaspora_send_mail($item,$owner,$contact) { - - $a = get_app(); - $myaddr = $owner['nickname'] . '@' . substr($a->get_baseurl(), strpos($a->get_baseurl(),'://') + 3); - - $r = q("select * from conv where id = %d and uid = %d limit 1", - intval($item['convid']), - intval($item['uid']) - ); - - if(! count($r)) { - logger('diaspora_send_mail: conversation not found.'); - return; - } - $cnv = $r[0]; - - $conv = array( - 'guid' => xmlify($cnv['guid']), - 'subject' => xmlify($cnv['subject']), - 'created_at' => xmlify(datetime_convert('UTC','UTC',$cnv['created'],'Y-m-d H:i:s \U\T\C')), - 'diaspora_handle' => xmlify($cnv['creator']), - 'participant_handles' => xmlify($cnv['recips']) - ); - - $body = bb2diaspora($item['body']); - $created = datetime_convert('UTC','UTC',$item['created'],'Y-m-d H:i:s \U\T\C'); - - $signed_text = $item['guid'] . ';' . $cnv['guid'] . ';' . $body . ';' - . $created . ';' . $myaddr . ';' . $cnv['guid']; - - $sig = base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256')); - - $msg = array( - 'guid' => xmlify($item['guid']), - 'parent_guid' => xmlify($cnv['guid']), - 'parent_author_signature' => xmlify($sig), - 'author_signature' => xmlify($sig), - 'text' => xmlify($body), - 'created_at' => xmlify($created), - 'diaspora_handle' => xmlify($myaddr), - 'conversation_guid' => xmlify($cnv['guid']) - ); - - if($item['reply']) { - $tpl = get_markup_template('diaspora_message.tpl'); - $xmsg = replace_macros($tpl, array('$msg' => $msg)); - } - else { - $conv['messages'] = array($msg); - $tpl = get_markup_template('diaspora_conversation.tpl'); - $xmsg = replace_macros($tpl, array('$conv' => $conv)); - } - - logger('diaspora_conversation: ' . print_r($xmsg,true), LOGGER_DATA); - 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,false,$item['guid'])); - - -} - -function diaspora_transmit($owner,$contact,$slap,$public_batch,$queue_run=false,$guid = "") { - - $enabled = intval(get_config('system','diaspora_enabled')); - if(! $enabled) { - return 200; - } - - $a = get_app(); - $logid = random_string(4); - $dest_url = (($public_batch) ? $contact['batch'] : $contact['notify']); - if(! $dest_url) { - logger('diaspora_transmit: no url for contact: ' . $contact['id'] . ' batch mode =' . $public_batch); - return 0; - } - - logger('diaspora_transmit: '.$logid.'-'.$guid.' '.$dest_url); - - if( (! $queue_run) && (was_recently_delayed($contact['id'])) ) { - $return_code = 0; - } - else { - if (!intval(get_config('system','diaspora_test'))) { - post_url($dest_url . '/', $slap); - $return_code = $a->get_curl_code(); - } else { - logger('diaspora_transmit: test_mode'); - return 200; - } - } - - logger('diaspora_transmit: '.$logid.'-'.$guid.' returns: '.$return_code); - - if((! $return_code) || (($return_code == 503) && (stristr($a->get_curl_headers(),'retry-after')))) { - logger('diaspora_transmit: queue message'); - - $r = q("SELECT id from queue where cid = %d and network = '%s' and content = '%s' and batch = %d limit 1", - intval($contact['id']), - dbesc(NETWORK_DIASPORA), - dbesc($slap), - intval($public_batch) - ); - if(count($r)) { - logger('diaspora_transmit: add_to_queue ignored - identical item already in queue'); - } - else { - // queue message for redelivery - add_to_queue($contact['id'],NETWORK_DIASPORA,$slap,$public_batch); - } - } - - - return(($return_code) ? $return_code : (-1)); -} - -function diaspora_fetch_relay() { - - $serverdata = get_config("system", "relay_server"); - if ($serverdata == "") - return array(); - - $relay = array(); - - $servers = explode(",", $serverdata); - - foreach($servers AS $server) { - $server = trim($server); - $batch = $server."/receive/public"; - - $relais = q("SELECT `batch`, `id`, `name`,`network` FROM `contact` WHERE `uid` = 0 AND `batch` = '%s' LIMIT 1", dbesc($batch)); - - if (!$relais) { - $addr = "relay@".str_replace("http://", "", normalise_link($server)); - - $r = q("INSERT INTO `contact` (`uid`, `created`, `name`, `nick`, `addr`, `url`, `nurl`, `batch`, `network`, `rel`, `blocked`, `pending`, `writable`, `name-date`, `uri-date`, `avatar-date`) - VALUES (0, '%s', '%s', 'relay', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, '%s', '%s', '%s')", - datetime_convert(), - dbesc($addr), - dbesc($addr), - dbesc($server), - dbesc(normalise_link($server)), - dbesc($batch), - dbesc(NETWORK_DIASPORA), - intval(CONTACT_IS_FOLLOWER), - dbesc(datetime_convert()), - dbesc(datetime_convert()), - dbesc(datetime_convert()) - ); - - $relais = q("SELECT `batch`, `id`, `name`,`network` FROM `contact` WHERE `uid` = 0 AND `batch` = '%s' LIMIT 1", dbesc($batch)); - if ($relais) - $relay[] = $relais[0]; - } else - $relay[] = $relais[0]; - } - - return $relay; + $message = array("diaspora_handle" => $handle, + "first_name" => $first, + "last_name" => $last, + "image_url" => $large, + "image_url_medium" => $medium, + "image_url_small" => $small, + "birthday" => $dob, + "gender" => $profile['gender'], + "bio" => $about, + "location" => $location, + "searchable" => $searchable, + "tag_string" => $tags); + + foreach($recips as $recip) + self::build_and_transmit($profile, $recip, "profile", $message, false, "", true); + } } +?> diff --git a/include/diaspora2.php b/include/diaspora2.php deleted file mode 100644 index 75cedeccd1..0000000000 --- a/include/diaspora2.php +++ /dev/null @@ -1,2553 +0,0 @@ - decoded Diaspora XML message - * 'author' -> author diaspora handle - * 'key' -> author public key (converted to pkcs#8) - */ - function decode($importer, $xml) { - - $public = false; - $basedom = parse_xml_string($xml); - - if (!is_object($basedom)) - return false; - - $children = $basedom->children('https://joindiaspora.com/protocol'); - - if($children->header) { - $public = true; - $author_link = str_replace('acct:','',$children->header->author_id); - } else { - - $encrypted_header = json_decode(base64_decode($children->encrypted_header)); - - $encrypted_aes_key_bundle = base64_decode($encrypted_header->aes_key); - $ciphertext = base64_decode($encrypted_header->ciphertext); - - $outer_key_bundle = ''; - openssl_private_decrypt($encrypted_aes_key_bundle,$outer_key_bundle,$importer['prvkey']); - - $j_outer_key_bundle = json_decode($outer_key_bundle); - - $outer_iv = base64_decode($j_outer_key_bundle->iv); - $outer_key = base64_decode($j_outer_key_bundle->key); - - $decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $outer_key, $ciphertext, MCRYPT_MODE_CBC, $outer_iv); - - - $decrypted = pkcs5_unpad($decrypted); - - /** - * $decrypted now contains something like - * - * - * 8e+G2+ET8l5BPuW0sVTnQw== - * UvSMb4puPeB14STkcDWq+4QE302Edu15oaprAQSkLKU= - * galaxor@diaspora.priateship.org - * - */ - - logger('decrypted: '.$decrypted, LOGGER_DEBUG); - $idom = parse_xml_string($decrypted,false); - - $inner_iv = base64_decode($idom->iv); - $inner_aes_key = base64_decode($idom->aes_key); - - $author_link = str_replace('acct:','',$idom->author_id); - } - - $dom = $basedom->children(NAMESPACE_SALMON_ME); - - // figure out where in the DOM tree our data is hiding - - if($dom->provenance->data) - $base = $dom->provenance; - elseif($dom->env->data) - $base = $dom->env; - elseif($dom->data) - $base = $dom; - - if (!$base) { - logger('unable to locate salmon data in xml'); - http_status_exit(400); - } - - - // Stash the signature away for now. We have to find their key or it won't be good for anything. - $signature = base64url_decode($base->sig); - - // unpack the data - - // strip whitespace so our data element will return to one big base64 blob - $data = str_replace(array(" ","\t","\r","\n"),array("","","",""),$base->data); - - - // stash away some other stuff for later - - $type = $base->data[0]->attributes()->type[0]; - $keyhash = $base->sig[0]->attributes()->keyhash[0]; - $encoding = $base->encoding; - $alg = $base->alg; - - - $signed_data = $data.'.'.base64url_encode($type).'.'.base64url_encode($encoding).'.'.base64url_encode($alg); - - - // decode the data - $data = base64url_decode($data); - - - if($public) - $inner_decrypted = $data; - else { - - // Decode the encrypted blob - - $inner_encrypted = base64_decode($data); - $inner_decrypted = mcrypt_decrypt(MCRYPT_RIJNDAEL_128, $inner_aes_key, $inner_encrypted, MCRYPT_MODE_CBC, $inner_iv); - $inner_decrypted = pkcs5_unpad($inner_decrypted); - } - - if (!$author_link) { - logger('Could not retrieve author URI.'); - http_status_exit(400); - } - // Once we have the author URI, go to the web and try to find their public key - // (first this will look it up locally if it is in the fcontact cache) - // This will also convert diaspora public key from pkcs#1 to pkcs#8 - - logger('Fetching key for '.$author_link); - $key = self::key($author_link); - - if (!$key) { - logger('Could not retrieve author key.'); - http_status_exit(400); - } - - $verify = rsa_verify($signed_data,$signature,$key); - - if (!$verify) { - logger('Message did not verify. Discarding.'); - http_status_exit(400); - } - - logger('Message verified.'); - - return array('message' => $inner_decrypted, 'author' => $author_link, 'key' => $key); - - } - - - /** - * @brief Dispatches public messages and find the fitting receivers - * - * @param array $msg The post that will be dispatched - * - * @return bool Was the message accepted? - */ - public static function dispatch_public($msg) { - - $enabled = intval(get_config("system", "diaspora_enabled")); - if (!$enabled) { - logger("diaspora is disabled"); - return false; - } - - // Use a dummy importer to import the data for the public copy - $importer = array("uid" => 0, "page-flags" => PAGE_FREELOVE); - $item_id = self::dispatch($importer,$msg); - - // Now distribute it to the followers - $r = q("SELECT `user`.* FROM `user` WHERE `user`.`uid` IN - (SELECT `contact`.`uid` FROM `contact` WHERE `contact`.`network` = '%s' AND `contact`.`addr` = '%s') - AND NOT `account_expired` AND NOT `account_removed`", - dbesc(NETWORK_DIASPORA), - dbesc($msg["author"]) - ); - if($r) { - foreach($r as $rr) { - logger("delivering to: ".$rr["username"]); - self::dispatch($rr,$msg); - } - } else - logger("No subscribers for ".$msg["author"]." ".print_r($msg, true)); - - return $item_id; - } - - /** - * @brief Dispatches the different message types to the different functions - * - * @param array $importer Array of the importer user - * @param array $msg The post that will be dispatched - * - * @return bool Was the message accepted? - */ - public static function dispatch($importer, $msg) { - - // The sender is the handle of the contact that sent the message. - // This will often be different with relayed messages (for example "like" and "comment") - $sender = $msg["author"]; - - if (!diaspora::valid_posting($msg, $fields)) { - logger("Invalid posting"); - return false; - } - - $type = $fields->getName(); - - logger("Received message type ".$type." from ".$sender." for user ".$importer["uid"], LOGGER_DEBUG); - - switch ($type) { - case "account_deletion": - return self::receive_account_deletion($importer, $fields); - - case "comment": - return self::receive_comment($importer, $sender, $fields); - - case "conversation": - return self::receive_conversation($importer, $msg, $fields); - - case "like": - return self::receive_like($importer, $sender, $fields); - - case "message": - return self::receive_message($importer, $fields); - - case "participation": // Not implemented - return self::receive_participation($importer, $fields); - - case "photo": // Not implemented - return self::receive_photo($importer, $fields); - - case "poll_participation": // Not implemented - return self::receive_poll_participation($importer, $fields); - - case "profile": - return self::receive_profile($importer, $fields); - - case "request": - return self::receive_request($importer, $fields); - - case "reshare": - return self::receive_reshare($importer, $fields); - - case "retraction": - return self::receive_retraction($importer, $sender, $fields); - - case "status_message": - return self::receive_status_message($importer, $fields); - - default: - logger("Unknown message type ".$type); - return false; - } - - return true; - } - - /** - * @brief Checks if a posting is valid and fetches the data fields. - * - * This function does not only check the signature. - * It also does the conversion between the old and the new diaspora format. - * - * @param array $msg Array with the XML, the sender handle and the sender signature - * @param object $fields SimpleXML object that contains the posting when it is valid - * - * @return bool Is the posting valid? - */ - private function valid_posting($msg, &$fields) { - - $data = parse_xml_string($msg["message"], false); - - if (!is_object($data)) - return false; - - $first_child = $data->getName(); - - // Is this the new or the old version? - if ($data->getName() == "XML") { - $oldXML = true; - foreach ($data->post->children() as $child) - $element = $child; - } else { - $oldXML = false; - $element = $data; - } - - $type = $element->getName(); - $orig_type = $type; - - // All retractions are handled identically from now on. - // In the new version there will only be "retraction". - if (in_array($type, array("signed_retraction", "relayable_retraction"))) - $type = "retraction"; - - $fields = new SimpleXMLElement("<".$type."/>"); - - $signed_data = ""; - - foreach ($element->children() AS $fieldname => $entry) { - if ($oldXML) { - // Translation for the old XML structure - if ($fieldname == "diaspora_handle") - $fieldname = "author"; - - if ($fieldname == "participant_handles") - $fieldname = "participants"; - - if (in_array($type, array("like", "participation"))) { - if ($fieldname == "target_type") - $fieldname = "parent_type"; - } - - if ($fieldname == "sender_handle") - $fieldname = "author"; - - if ($fieldname == "recipient_handle") - $fieldname = "recipient"; - - if ($fieldname == "root_diaspora_id") - $fieldname = "root_author"; - - if ($type == "retraction") { - if ($fieldname == "post_guid") - $fieldname = "target_guid"; - - if ($fieldname == "type") - $fieldname = "target_type"; - } - } - - if ($fieldname == "author_signature") - $author_signature = base64_decode($entry); - elseif ($fieldname == "parent_author_signature") - $parent_author_signature = base64_decode($entry); - elseif ($fieldname != "target_author_signature") { - if ($signed_data != "") { - $signed_data .= ";"; - $signed_data_parent .= ";"; - } - - $signed_data .= $entry; - } - if (!in_array($fieldname, array("parent_author_signature", "target_author_signature")) OR - ($orig_type == "relayable_retraction")) - xml::copy($entry, $fields, $fieldname); - } - - // This is something that shouldn't happen at all. - if (in_array($type, array("status_message", "reshare", "profile"))) - if ($msg["author"] != $fields->author) { - logger("Message handle is not the same as envelope sender. Quitting this message."); - return false; - } - - // Only some message types have signatures. So we quit here for the other types. - if (!in_array($type, array("comment", "message", "like"))) - return true; - - // No author_signature? This is a must, so we quit. - if (!isset($author_signature)) - return false; - - if (isset($parent_author_signature)) { - $key = self::key($msg["author"]); - - if (!rsa_verify($signed_data, $parent_author_signature, $key, "sha256")) - return false; - } - - $key = self::key($fields->author); - - return rsa_verify($signed_data, $author_signature, $key, "sha256"); - } - - /** - * @brief Fetches the public key for a given handle - * - * @param string $handle The handle - * - * @return string The public key - */ - private function key($handle) { - $handle = strval($handle); - - logger("Fetching diaspora key for: ".$handle); - - $r = self::person_by_handle($handle); - if($r) - return $r["pubkey"]; - - return ""; - } - - /** - * @brief Fetches data for a given handle - * - * @param string $handle The handle - * - * @return array the queried data - */ - private function person_by_handle($handle) { - - $r = q("SELECT * FROM `fcontact` WHERE `network` = '%s' AND `addr` = '%s' LIMIT 1", - dbesc(NETWORK_DIASPORA), - dbesc($handle) - ); - if ($r) { - $person = $r[0]; - logger("In cache ".print_r($r,true), LOGGER_DEBUG); - - // update record occasionally so it doesn't get stale - $d = strtotime($person["updated"]." +00:00"); - if ($d < strtotime("now - 14 days")) - $update = true; - } - - if (!$person OR $update) { - logger("create or refresh", LOGGER_DEBUG); - $r = probe_url($handle, PROBE_DIASPORA); - - // Note that Friendica contacts will return a "Diaspora person" - // if Diaspora connectivity is enabled on their server - if ($r AND ($r["network"] === NETWORK_DIASPORA)) { - self::add_fcontact($r, $update); - $person = $r; - } - } - return $person; - } - - /** - * @brief Updates the fcontact table - * - * @param array $arr The fcontact data - * @param bool $update Update or insert? - * - * @return string The id of the fcontact entry - */ - private function add_fcontact($arr, $update = false) { - /// @todo Remove this function from include/network.php - - if($update) { - $r = q("UPDATE `fcontact` SET - `name` = '%s', - `photo` = '%s', - `request` = '%s', - `nick` = '%s', - `addr` = '%s', - `batch` = '%s', - `notify` = '%s', - `poll` = '%s', - `confirm` = '%s', - `alias` = '%s', - `pubkey` = '%s', - `updated` = '%s' - WHERE `url` = '%s' AND `network` = '%s'", - dbesc($arr["name"]), - dbesc($arr["photo"]), - dbesc($arr["request"]), - dbesc($arr["nick"]), - dbesc($arr["addr"]), - dbesc($arr["batch"]), - dbesc($arr["notify"]), - dbesc($arr["poll"]), - dbesc($arr["confirm"]), - dbesc($arr["alias"]), - dbesc($arr["pubkey"]), - dbesc(datetime_convert()), - dbesc($arr["url"]), - dbesc($arr["network"]) - ); - } else { - $r = q("INSERT INTO `fcontact` (`url`,`name`,`photo`,`request`,`nick`,`addr`, - `batch`, `notify`,`poll`,`confirm`,`network`,`alias`,`pubkey`,`updated`) - VALUES ('%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s')", - dbesc($arr["url"]), - dbesc($arr["name"]), - dbesc($arr["photo"]), - dbesc($arr["request"]), - dbesc($arr["nick"]), - dbesc($arr["addr"]), - dbesc($arr["batch"]), - dbesc($arr["notify"]), - dbesc($arr["poll"]), - dbesc($arr["confirm"]), - dbesc($arr["network"]), - dbesc($arr["alias"]), - dbesc($arr["pubkey"]), - dbesc(datetime_convert()) - ); - } - - return $r; - } - - public static function handle_from_contact($contact_id) { - $handle = False; - - logger("contact id is ".$contact_id, LOGGER_DEBUG); - - $r = q("SELECT `network`, `addr`, `self`, `url`, `nick` FROM `contact` WHERE `id` = %d", - intval($contact_id) - ); - if($r) { - $contact = $r[0]; - - logger("contact 'self' = ".$contact['self']." 'url' = ".$contact['url'], LOGGER_DEBUG); - - if($contact['addr'] != "") - $handle = $contact['addr']; - elseif(($contact['network'] === NETWORK_DFRN) || ($contact['self'] == 1)) { - $baseurl_start = strpos($contact['url'],'://') + 3; - $baseurl_length = strpos($contact['url'],'/profile') - $baseurl_start; // allows installations in a subdirectory--not sure how Diaspora will handle - $baseurl = substr($contact['url'], $baseurl_start, $baseurl_length); - $handle = $contact['nick'].'@'.$baseurl; - } - } - - return $handle; - } - - private function contact_by_handle($uid, $handle) { - $r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `addr` = '%s' LIMIT 1", - intval($uid), - dbesc($handle) - ); - - if ($r) - return $r[0]; - - $handle_parts = explode("@", $handle); - $nurl_sql = "%%://".$handle_parts[1]."%%/profile/".$handle_parts[0]; - $r = q("SELECT * FROM `contact` WHERE `network` = '%s' AND `uid` = %d AND `nurl` LIKE '%s' LIMIT 1", - dbesc(NETWORK_DFRN), - intval($uid), - dbesc($nurl_sql) - ); - if($r) - return $r[0]; - - return false; - } - - private function post_allow($importer, $contact, $is_comment = false) { - - // perhaps we were already sharing with this person. Now they're sharing with us. - // That makes us friends. - // Normally this should have handled by getting a request - but this could get lost - if($contact["rel"] == CONTACT_IS_FOLLOWER && in_array($importer["page-flags"], array(PAGE_FREELOVE))) { - q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d", - intval(CONTACT_IS_FRIEND), - intval($contact["id"]), - intval($importer["uid"]) - ); - $contact["rel"] = CONTACT_IS_FRIEND; - logger("defining user ".$contact["nick"]." as friend"); - } - - if(($contact["blocked"]) || ($contact["readonly"]) || ($contact["archive"])) - return false; - if($contact["rel"] == CONTACT_IS_SHARING || $contact["rel"] == CONTACT_IS_FRIEND) - return true; - if($contact["rel"] == CONTACT_IS_FOLLOWER) - if(($importer["page-flags"] == PAGE_COMMUNITY) OR $is_comment) - return true; - - // Messages for the global users are always accepted - if ($importer["uid"] == 0) - return true; - - return false; - } - - private function allowed_contact_by_handle($importer, $handle, $is_comment = false) { - $contact = self::contact_by_handle($importer["uid"], $handle); - if (!$contact) { - logger("A Contact for handle ".$handle." and user ".$importer["uid"]." was not found"); - return false; - } - - if (!self::post_allow($importer, $contact, $is_comment)) { - logger("The handle: ".$handle." is not allowed to post to user ".$importer["uid"]); - return false; - } - return $contact; - } - - private function message_exists($uid, $guid) { - $r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", - intval($uid), - dbesc($guid) - ); - - if($r) { - logger("message ".$guid." already exists for user ".$uid); - return true; - } - - return false; - } - - private function fetch_guid($item) { - preg_replace_callback("&\[url=/posts/([^\[\]]*)\](.*)\[\/url\]&Usi", - function ($match) use ($item){ - return(self::fetch_guid_sub($match, $item)); - },$item["body"]); - } - - private function fetch_guid_sub($match, $item) { - if (!self::store_by_guid($match[1], $item["author-link"])) - self::store_by_guid($match[1], $item["owner-link"]); - } - - private function store_by_guid($guid, $server, $uid = 0) { - $serverparts = parse_url($server); - $server = $serverparts["scheme"]."://".$serverparts["host"]; - - logger("Trying to fetch item ".$guid." from ".$server, LOGGER_DEBUG); - - $msg = self::message($guid, $server); - - if (!$msg) - return false; - - logger("Successfully fetched item ".$guid." from ".$server, LOGGER_DEBUG); - - // Now call the dispatcher - return self::dispatch_public($msg); - } - - private function message($guid, $server, $level = 0) { - - if ($level > 5) - return false; - - // This will work for Diaspora and newer Friendica servers - $source_url = $server."/p/".$guid.".xml"; - $x = fetch_url($source_url); - if(!$x) - return false; - - $source_xml = parse_xml_string($x, false); - - if (!is_object($source_xml)) - return false; - - if ($source_xml->post->reshare) { - // Reshare of a reshare - old Diaspora version - return self::message($source_xml->post->reshare->root_guid, $server, ++$level); - } elseif ($source_xml->getName() == "reshare") { - // Reshare of a reshare - new Diaspora version - return self::message($source_xml->root_guid, $server, ++$level); - } - - $author = ""; - - // Fetch the author - for the old and the new Diaspora version - if ($source_xml->post->status_message->diaspora_handle) - $author = (string)$source_xml->post->status_message->diaspora_handle; - elseif ($source_xml->author AND ($source_xml->getName() == "status_message")) - $author = (string)$source_xml->author; - - // If this isn't a "status_message" then quit - if (!$author) - return false; - - $msg = array("message" => $x, "author" => $author); - - $msg["key"] = self::key($msg["author"]); - - return $msg; - } - - private function parent_item($uid, $guid, $author, $contact) { - $r = q("SELECT `id`, `body`, `wall`, `uri`, `private`, `origin`, - `author-name`, `author-link`, `author-avatar`, - `owner-name`, `owner-link`, `owner-avatar` - FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", - intval($uid), dbesc($guid)); - - if(!$r) { - $result = self::store_by_guid($guid, $contact["url"], $uid); - - if (!$result) { - $person = self::person_by_handle($author); - $result = self::store_by_guid($guid, $person["url"], $uid); - } - - if ($result) { - logger("Fetched missing item ".$guid." - result: ".$result, LOGGER_DEBUG); - - $r = q("SELECT `id`, `body`, `wall`, `uri`, `private`, `origin`, - `author-name`, `author-link`, `author-avatar`, - `owner-name`, `owner-link`, `owner-avatar` - FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", - intval($uid), dbesc($guid)); - } - } - - if (!$r) { - logger("parent item not found: parent: ".$guid." - user: ".$uid); - return false; - } else { - logger("parent item found: parent: ".$guid." - user: ".$uid); - return $r[0]; - } - } - - private function author_contact_by_url($contact, $person, $uid) { - - $r = q("SELECT `id`, `network` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d LIMIT 1", - dbesc(normalise_link($person["url"])), intval($uid)); - if ($r) { - $cid = $r[0]["id"]; - $network = $r[0]["network"]; - } else { - $cid = $contact["id"]; - $network = NETWORK_DIASPORA; - } - - return (array("cid" => $cid, "network" => $network)); - } - - public static function is_redmatrix($url) { - return(strstr($url, "/channel/")); - } - - private function plink($addr, $guid) { - $r = q("SELECT `url`, `nick`, `network` FROM `fcontact` WHERE `addr`='%s' LIMIT 1", dbesc($addr)); - - // Fallback - if (!$r) - return "https://".substr($addr,strpos($addr,"@")+1)."/posts/".$guid; - - // Friendica contacts are often detected as Diaspora contacts in the "fcontact" table - // So we try another way as well. - $s = q("SELECT `network` FROM `gcontact` WHERE `nurl`='%s' LIMIT 1", dbesc(normalise_link($r[0]["url"]))); - if ($s) - $r[0]["network"] = $s[0]["network"]; - - if ($r[0]["network"] == NETWORK_DFRN) - return(str_replace("/profile/".$r[0]["nick"]."/", "/display/".$guid, $r[0]["url"]."/")); - - if (self::is_redmatrix($r[0]["url"])) - return $r[0]["url"]."/?f=&mid=".$guid; - - return "https://".substr($addr,strpos($addr,"@")+1)."/posts/".$guid; - } - - private function receive_account_deletion($importer, $data) { - $author = notags(unxmlify($data->author)); - - $contact = self::contact_by_handle($importer["uid"], $author); - if (!$contact) { - logger("cannot find contact for author: ".$author); - return false; - } - - // We now remove the contact - contact_remove($contact["id"]); - return true; - } - - private function receive_comment($importer, $sender, $data) { - $guid = notags(unxmlify($data->guid)); - $parent_guid = notags(unxmlify($data->parent_guid)); - $text = unxmlify($data->text); - $author = notags(unxmlify($data->author)); - - $contact = self::allowed_contact_by_handle($importer, $sender, true); - if (!$contact) - return false; - - if (self::message_exists($importer["uid"], $guid)) - return false; - - $parent_item = self::parent_item($importer["uid"], $parent_guid, $author, $contact); - if (!$parent_item) - return false; - - $person = self::person_by_handle($author); - if (!is_array($person)) { - logger("unable to find author details"); - return false; - } - - // Fetch the contact id - if we know this contact - $author_contact = self::author_contact_by_url($contact, $person, $importer["uid"]); - - $datarray = array(); - - $datarray["uid"] = $importer["uid"]; - $datarray["contact-id"] = $author_contact["cid"]; - $datarray["network"] = $author_contact["network"]; - - $datarray["author-name"] = $person["name"]; - $datarray["author-link"] = $person["url"]; - $datarray["author-avatar"] = ((x($person,"thumb")) ? $person["thumb"] : $person["photo"]); - - $datarray["owner-name"] = $contact["name"]; - $datarray["owner-link"] = $contact["url"]; - $datarray["owner-avatar"] = ((x($contact,"thumb")) ? $contact["thumb"] : $contact["photo"]); - - $datarray["guid"] = $guid; - $datarray["uri"] = $author.":".$guid; - - $datarray["type"] = "remote-comment"; - $datarray["verb"] = ACTIVITY_POST; - $datarray["gravity"] = GRAVITY_COMMENT; - $datarray["parent-uri"] = $parent_item["uri"]; - - $datarray["object-type"] = ACTIVITY_OBJ_COMMENT; - $datarray["object"] = json_encode($data); - - $datarray["body"] = diaspora2bb($text); - - self::fetch_guid($datarray); - - $message_id = item_store($datarray); - - if ($message_id) - logger("Stored comment ".$datarray["guid"]." with message id ".$message_id, LOGGER_DEBUG); - - // If we are the origin of the parent we store the original data and notify our followers - if($message_id AND $parent_item["origin"]) { - - // Formerly we stored the signed text, the signature and the author in different fields. - // We now store the raw data so that we are more flexible. - q("INSERT INTO `sign` (`iid`,`signed_text`) VALUES (%d,'%s')", - intval($message_id), - dbesc(json_encode($data)) - ); - - // notify others - proc_run("php", "include/notifier.php", "comment-import", $message_id); - } - - return $message_id; - } - - private function receive_conversation_message($importer, $contact, $data, $msg, $mesg, $conversation) { - $guid = notags(unxmlify($data->guid)); - $subject = notags(unxmlify($data->subject)); - $author = notags(unxmlify($data->author)); - - $reply = 0; - - $msg_guid = notags(unxmlify($mesg->guid)); - $msg_parent_guid = notags(unxmlify($mesg->parent_guid)); - $msg_parent_author_signature = notags(unxmlify($mesg->parent_author_signature)); - $msg_author_signature = notags(unxmlify($mesg->author_signature)); - $msg_text = unxmlify($mesg->text); - $msg_created_at = datetime_convert("UTC", "UTC", notags(unxmlify($mesg->created_at))); - - // "diaspora_handle" is the element name from the old version - // "author" is the element name from the new version - if ($mesg->author) - $msg_author = notags(unxmlify($mesg->author)); - elseif ($mesg->diaspora_handle) - $msg_author = notags(unxmlify($mesg->diaspora_handle)); - else - return false; - - $msg_conversation_guid = notags(unxmlify($mesg->conversation_guid)); - - if($msg_conversation_guid != $guid) { - logger("message conversation guid does not belong to the current conversation."); - return false; - } - - $body = diaspora2bb($msg_text); - $message_uri = $msg_author.":".$msg_guid; - - $author_signed_data = $msg_guid.";".$msg_parent_guid.";".$msg_text.";".unxmlify($mesg->created_at).";".$msg_author.";".$msg_conversation_guid; - - $author_signature = base64_decode($msg_author_signature); - - if(strcasecmp($msg_author,$msg["author"]) == 0) { - $person = $contact; - $key = $msg["key"]; - } else { - $person = self::person_by_handle($msg_author); - - if (is_array($person) && x($person, "pubkey")) - $key = $person["pubkey"]; - else { - logger("unable to find author details"); - return false; - } - } - - if (!rsa_verify($author_signed_data, $author_signature, $key, "sha256")) { - logger("verification failed."); - return false; - } - - if($msg_parent_author_signature) { - $owner_signed_data = $msg_guid.";".$msg_parent_guid.";".$msg_text.";".unxmlify($mesg->created_at).";".$msg_author.";".$msg_conversation_guid; - - $parent_author_signature = base64_decode($msg_parent_author_signature); - - $key = $msg["key"]; - - if (!rsa_verify($owner_signed_data, $parent_author_signature, $key, "sha256")) { - logger("owner verification failed."); - return false; - } - } - - $r = q("SELECT `id` FROM `mail` WHERE `uri` = '%s' LIMIT 1", - dbesc($message_uri) - ); - if($r) { - logger("duplicate message already delivered.", LOGGER_DEBUG); - return false; - } - - q("INSERT INTO `mail` (`uid`, `guid`, `convid`, `from-name`,`from-photo`,`from-url`,`contact-id`,`title`,`body`,`seen`,`reply`,`uri`,`parent-uri`,`created`) - VALUES (%d, '%s', %d, '%s', '%s', '%s', %d, '%s', '%s', %d, %d, '%s','%s','%s')", - intval($importer["uid"]), - dbesc($msg_guid), - intval($conversation["id"]), - dbesc($person["name"]), - dbesc($person["photo"]), - dbesc($person["url"]), - intval($contact["id"]), - dbesc($subject), - dbesc($body), - 0, - 0, - dbesc($message_uri), - dbesc($author.":".$guid), - dbesc($msg_created_at) - ); - - q("UPDATE `conv` SET `updated` = '%s' WHERE `id` = %d", - dbesc(datetime_convert()), - intval($conversation["id"]) - ); - - notification(array( - "type" => NOTIFY_MAIL, - "notify_flags" => $importer["notify-flags"], - "language" => $importer["language"], - "to_name" => $importer["username"], - "to_email" => $importer["email"], - "uid" =>$importer["uid"], - "item" => array("subject" => $subject, "body" => $body), - "source_name" => $person["name"], - "source_link" => $person["url"], - "source_photo" => $person["thumb"], - "verb" => ACTIVITY_POST, - "otype" => "mail" - )); - } - - private function receive_conversation($importer, $msg, $data) { - $guid = notags(unxmlify($data->guid)); - $subject = notags(unxmlify($data->subject)); - $created_at = datetime_convert("UTC", "UTC", notags(unxmlify($data->created_at))); - $author = notags(unxmlify($data->author)); - $participants = notags(unxmlify($data->participants)); - - $messages = $data->message; - - if (!count($messages)) { - logger("empty conversation"); - return false; - } - - $contact = self::allowed_contact_by_handle($importer, $msg["author"], true); - if (!$contact) - return false; - - $conversation = null; - - $c = q("SELECT * FROM `conv` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", - intval($importer["uid"]), - dbesc($guid) - ); - if($c) - $conversation = $c[0]; - else { - $r = q("INSERT INTO `conv` (`uid`, `guid`, `creator`, `created`, `updated`, `subject`, `recips`) - VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s')", - intval($importer["uid"]), - dbesc($guid), - dbesc($author), - dbesc(datetime_convert("UTC", "UTC", $created_at)), - dbesc(datetime_convert()), - dbesc($subject), - dbesc($participants) - ); - if($r) - $c = q("SELECT * FROM `conv` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", - intval($importer["uid"]), - dbesc($guid) - ); - - if($c) - $conversation = $c[0]; - } - if (!$conversation) { - logger("unable to create conversation."); - return; - } - - foreach($messages as $mesg) - self::receive_conversation_message($importer, $contact, $data, $msg, $mesg, $conversation); - - return true; - } - - private function construct_like_body($contact, $parent_item, $guid) { - $bodyverb = t('%1$s likes %2$s\'s %3$s'); - - $ulink = "[url=".$contact["url"]."]".$contact["name"]."[/url]"; - $alink = "[url=".$parent_item["author-link"]."]".$parent_item["author-name"]."[/url]"; - $plink = "[url=".App::get_baseurl()."/display/".urlencode($guid)."]".t("status")."[/url]"; - - return sprintf($bodyverb, $ulink, $alink, $plink); - } - - private function construct_like_object($importer, $parent_item) { - $objtype = ACTIVITY_OBJ_NOTE; - $link = ''; - $parent_body = $parent_item["body"]; - - $xmldata = array("object" => array("type" => $objtype, - "local" => "1", - "id" => $parent_item["uri"], - "link" => $link, - "title" => "", - "content" => $parent_body)); - - return xml::from_array($xmldata, $xml, true); - } - - private function receive_like($importer, $sender, $data) { - $positive = notags(unxmlify($data->positive)); - $guid = notags(unxmlify($data->guid)); - $parent_type = notags(unxmlify($data->parent_type)); - $parent_guid = notags(unxmlify($data->parent_guid)); - $author = notags(unxmlify($data->author)); - - // likes on comments aren't supported by Diaspora - only on posts - // But maybe this will be supported in the future, so we will accept it. - if (!in_array($parent_type, array("Post", "Comment"))) - return false; - - $contact = self::allowed_contact_by_handle($importer, $sender, true); - if (!$contact) - return false; - - if (self::message_exists($importer["uid"], $guid)) - return false; - - $parent_item = self::parent_item($importer["uid"], $parent_guid, $author, $contact); - if (!$parent_item) - return false; - - $person = self::person_by_handle($author); - if (!is_array($person)) { - logger("unable to find author details"); - return false; - } - - // Fetch the contact id - if we know this contact - $author_contact = self::author_contact_by_url($contact, $person, $importer["uid"]); - - // "positive" = "false" would be a Dislike - wich isn't currently supported by Diaspora - // We would accept this anyhow. - if ($positive === "true") - $verb = ACTIVITY_LIKE; - else - $verb = ACTIVITY_DISLIKE; - - $datarray = array(); - - $datarray["uid"] = $importer["uid"]; - $datarray["contact-id"] = $author_contact["cid"]; - $datarray["network"] = $author_contact["network"]; - - $datarray["author-name"] = $person["name"]; - $datarray["author-link"] = $person["url"]; - $datarray["author-avatar"] = ((x($person,"thumb")) ? $person["thumb"] : $person["photo"]); - - $datarray["owner-name"] = $contact["name"]; - $datarray["owner-link"] = $contact["url"]; - $datarray["owner-avatar"] = ((x($contact,"thumb")) ? $contact["thumb"] : $contact["photo"]); - - $datarray["guid"] = $guid; - $datarray["uri"] = $author.":".$guid; - - $datarray["type"] = "activity"; - $datarray["verb"] = $verb; - $datarray["gravity"] = GRAVITY_LIKE; - $datarray["parent-uri"] = $parent_item["uri"]; - - $datarray["object-type"] = ACTIVITY_OBJ_NOTE; - $datarray["object"] = self::construct_like_object($importer, $parent_item); - - $datarray["body"] = self::construct_like_body($contact, $parent_item, $guid); - - $message_id = item_store($datarray); - - if ($message_id) - logger("Stored like ".$datarray["guid"]." with message id ".$message_id, LOGGER_DEBUG); - - // If we are the origin of the parent we store the original data and notify our followers - if($message_id AND $parent_item["origin"]) { - - // Formerly we stored the signed text, the signature and the author in different fields. - // We now store the raw data so that we are more flexible. - q("INSERT INTO `sign` (`iid`,`signed_text`) VALUES (%d,'%s')", - intval($message_id), - dbesc(json_encode($data)) - ); - - // notify others - proc_run("php", "include/notifier.php", "comment-import", $message_id); - } - - return $message_id; - } - - private function receive_message($importer, $data) { - $guid = notags(unxmlify($data->guid)); - $parent_guid = notags(unxmlify($data->parent_guid)); - $text = unxmlify($data->text); - $created_at = datetime_convert("UTC", "UTC", notags(unxmlify($data->created_at))); - $author = notags(unxmlify($data->author)); - $conversation_guid = notags(unxmlify($data->conversation_guid)); - - $contact = self::allowed_contact_by_handle($importer, $author, true); - if (!$contact) - return false; - - $conversation = null; - - $c = q("SELECT * FROM `conv` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1", - intval($importer["uid"]), - dbesc($conversation_guid) - ); - if($c) - $conversation = $c[0]; - else { - logger("conversation not available."); - return false; - } - - $reply = 0; - - $body = diaspora2bb($text); - $message_uri = $author.":".$guid; - - $person = self::person_by_handle($author); - if (!$person) { - logger("unable to find author details"); - return false; - } - - $r = q("SELECT `id` FROM `mail` WHERE `uri` = '%s' AND `uid` = %d LIMIT 1", - dbesc($message_uri), - intval($importer["uid"]) - ); - if($r) { - logger("duplicate message already delivered.", LOGGER_DEBUG); - return false; - } - - q("INSERT INTO `mail` (`uid`, `guid`, `convid`, `from-name`,`from-photo`,`from-url`,`contact-id`,`title`,`body`,`seen`,`reply`,`uri`,`parent-uri`,`created`) - VALUES ( %d, '%s', %d, '%s', '%s', '%s', %d, '%s', '%s', %d, %d, '%s','%s','%s')", - intval($importer["uid"]), - dbesc($guid), - intval($conversation["id"]), - dbesc($person["name"]), - dbesc($person["photo"]), - dbesc($person["url"]), - intval($contact["id"]), - dbesc($conversation["subject"]), - dbesc($body), - 0, - 1, - dbesc($message_uri), - dbesc($author.":".$parent_guid), - dbesc($created_at) - ); - - q("UPDATE `conv` SET `updated` = '%s' WHERE `id` = %d", - dbesc(datetime_convert()), - intval($conversation["id"]) - ); - - return true; - } - - private function receive_participation($importer, $data) { - // I'm not sure if we can fully support this message type - return true; - } - - private function receive_photo($importer, $data) { - // There doesn't seem to be a reason for this function, since the photo data is transmitted in the status message as well - return true; - } - - private function receive_poll_participation($importer, $data) { - // We don't support polls by now - return true; - } - - private function receive_profile($importer, $data) { - $author = notags(unxmlify($data->author)); - - $contact = self::contact_by_handle($importer["uid"], $author); - if (!$contact) - return; - - $name = unxmlify($data->first_name).((strlen($data->last_name)) ? " ".unxmlify($data->last_name) : ""); - $image_url = unxmlify($data->image_url); - $birthday = unxmlify($data->birthday); - $location = diaspora2bb(unxmlify($data->location)); - $about = diaspora2bb(unxmlify($data->bio)); - $gender = unxmlify($data->gender); - $searchable = (unxmlify($data->searchable) == "true"); - $nsfw = (unxmlify($data->nsfw) == "true"); - $tags = unxmlify($data->tag_string); - - $tags = explode("#", $tags); - - $keywords = array(); - foreach ($tags as $tag) { - $tag = trim(strtolower($tag)); - if ($tag != "") - $keywords[] = $tag; - } - - $keywords = implode(", ", $keywords); - - $handle_parts = explode("@", $author); - $nick = $handle_parts[0]; - - if($name === "") - $name = $handle_parts[0]; - - if( preg_match("|^https?://|", $image_url) === 0) - $image_url = "http://".$handle_parts[1].$image_url; - - update_contact_avatar($image_url, $importer["uid"], $contact["id"]); - - // Generic birthday. We don't know the timezone. The year is irrelevant. - - $birthday = str_replace("1000", "1901", $birthday); - - if ($birthday != "") - $birthday = datetime_convert("UTC", "UTC", $birthday, "Y-m-d"); - - // this is to prevent multiple birthday notifications in a single year - // if we already have a stored birthday and the 'm-d' part hasn't changed, preserve the entry, which will preserve the notify year - - if(substr($birthday,5) === substr($contact["bd"],5)) - $birthday = $contact["bd"]; - - $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($nick), - dbesc($author), - dbesc(datetime_convert()), - dbesc($birthday), - dbesc($location), - dbesc($about), - dbesc($keywords), - dbesc($gender), - intval($contact["id"]), - intval($importer["uid"]) - ); - - if ($searchable) { - poco_check($contact["url"], $name, NETWORK_DIASPORA, $image_url, $about, $location, $gender, $keywords, "", - datetime_convert(), 2, $contact["id"], $importer["uid"]); - } - - $gcontact = array("url" => $contact["url"], "network" => NETWORK_DIASPORA, "generation" => 2, - "photo" => $image_url, "name" => $name, "location" => $location, - "about" => $about, "birthday" => $birthday, "gender" => $gender, - "addr" => $author, "nick" => $nick, "keywords" => $keywords, - "hide" => !$searchable, "nsfw" => $nsfw); - - update_gcontact($gcontact); - - logger("Profile of contact ".$contact["id"]." stored for user ".$importer["uid"], LOGGER_DEBUG); - - return true; - } - - private function receive_request_make_friend($importer, $contact) { - - $a = get_app(); - - if($contact["rel"] == CONTACT_IS_FOLLOWER && in_array($importer["page-flags"], array(PAGE_FREELOVE))) { - q("UPDATE `contact` SET `rel` = %d, `writable` = 1 WHERE `id` = %d AND `uid` = %d", - intval(CONTACT_IS_FRIEND), - intval($contact["id"]), - intval($importer["uid"]) - ); - } - // send notification - - $r = q("SELECT `hide-friends` FROM `profile` WHERE `uid` = %d AND `is-default` = 1 LIMIT 1", - intval($importer["uid"]) - ); - - if($r && !$r[0]["hide-friends"] && !$contact["hidden"] && intval(get_pconfig($importer["uid"], "system", "post_newfriend"))) { - - $self = q("SELECT * FROM `contact` WHERE `self` AND `uid` = %d LIMIT 1", - intval($importer["uid"]) - ); - - // they are not CONTACT_IS_FOLLOWER anymore but that's what we have in the array - - if($self && $contact["rel"] == CONTACT_IS_FOLLOWER) { - - $arr = array(); - $arr["uri"] = $arr["parent-uri"] = item_new_uri($a->get_hostname(), $importer["uid"]); - $arr["uid"] = $importer["uid"]; - $arr["contact-id"] = $self[0]["id"]; - $arr["wall"] = 1; - $arr["type"] = 'wall'; - $arr["gravity"] = 0; - $arr["origin"] = 1; - $arr["author-name"] = $arr["owner-name"] = $self[0]["name"]; - $arr["author-link"] = $arr["owner-link"] = $self[0]["url"]; - $arr["author-avatar"] = $arr["owner-avatar"] = $self[0]["thumb"]; - $arr["verb"] = ACTIVITY_FRIEND; - $arr["object-type"] = ACTIVITY_OBJ_PERSON; - - $A = "[url=".$self[0]["url"]."]".$self[0]["name"]."[/url]"; - $B = "[url=".$contact["url"]."]".$contact["name"]."[/url]"; - $BPhoto = "[url=".$contact["url"]."][img]".$contact["thumb"]."[/img][/url]"; - $arr["body"] = sprintf(t("%1$s is now friends with %2$s"), $A, $B)."\n\n\n".$Bphoto; - - $arr["object"] = "".ACTIVITY_OBJ_PERSON."".$contact["name"]."" - ."".$contact["url"]."/".$contact["name"].""; - $arr["object"] .= "".xmlify(''."\n"); - $arr["object"] .= xmlify(''."\n"); - $arr["object"] .= "\n"; - $arr["last-child"] = 1; - - $arr["allow_cid"] = $user[0]["allow_cid"]; - $arr["allow_gid"] = $user[0]["allow_gid"]; - $arr["deny_cid"] = $user[0]["deny_cid"]; - $arr["deny_gid"] = $user[0]["deny_gid"]; - - $i = item_store($arr); - if($i) - proc_run("php", "include/notifier.php", "activity", $i); - - } - - } - } - - private function receive_request($importer, $data) { - $author = unxmlify($data->author); - $recipient = unxmlify($data->recipient); - - if (!$author || !$recipient) - return; - - $contact = self::contact_by_handle($importer["uid"],$author); - - if($contact) { - - // perhaps we were already sharing with this person. Now they're sharing with us. - // That makes us friends. - - self::receive_request_make_friend($importer, $contact); - return true; - } - - $ret = self::person_by_handle($author); - - if (!$ret || ($ret["network"] != NETWORK_DIASPORA)) { - logger("Cannot resolve diaspora handle ".$author." for ".$recipient); - return false; - } - - $batch = (($ret["batch"]) ? $ret["batch"] : implode("/", array_slice(explode("/", $ret["url"]), 0, 3))."/receive/public"); - - $r = q("INSERT INTO `contact` (`uid`, `network`,`addr`,`created`,`url`,`nurl`,`batch`,`name`,`nick`,`photo`,`pubkey`,`notify`,`poll`,`blocked`,`priority`) - VALUES (%d, '%s', '%s', '%s', '%s','%s','%s','%s','%s','%s','%s','%s','%s',%d,%d)", - intval($importer["uid"]), - dbesc($ret["network"]), - dbesc($ret["addr"]), - datetime_convert(), - dbesc($ret["url"]), - dbesc(normalise_link($ret["url"])), - dbesc($batch), - dbesc($ret["name"]), - dbesc($ret["nick"]), - dbesc($ret["photo"]), - dbesc($ret["pubkey"]), - dbesc($ret["notify"]), - dbesc($ret["poll"]), - 1, - 2 - ); - - // find the contact record we just created - - $contact_record = self::contact_by_handle($importer["uid"],$author); - - if (!$contact_record) { - logger("unable to locate newly created contact record."); - return; - } - - $g = q("SELECT `def_gid` FROM `user` WHERE `uid` = %d LIMIT 1", - intval($importer["uid"]) - ); - - if($g && intval($g[0]["def_gid"])) - group_add_member($importer["uid"], "", $contact_record["id"], $g[0]["def_gid"]); - - if($importer["page-flags"] == PAGE_NORMAL) { - - $hash = random_string().(string)time(); // Generate a confirm_key - - $ret = q("INSERT INTO `intro` (`uid`, `contact-id`, `blocked`, `knowyou`, `note`, `hash`, `datetime`) - VALUES (%d, %d, %d, %d, '%s', '%s', '%s')", - intval($importer["uid"]), - intval($contact_record["id"]), - 0, - 0, - dbesc(t("Sharing notification from Diaspora network")), - dbesc($hash), - dbesc(datetime_convert()) - ); - } else { - - // automatic friend approval - - 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 - // we are going to change the relationship and make them a follower. - - if($importer["page-flags"] == PAGE_FREELOVE) - $new_relation = CONTACT_IS_FRIEND; - else - $new_relation = CONTACT_IS_FOLLOWER; - - $r = q("UPDATE `contact` SET `rel` = %d, - `name-date` = '%s', - `uri-date` = '%s', - `blocked` = 0, - `pending` = 0, - `writable` = 1 - WHERE `id` = %d - ", - intval($new_relation), - dbesc(datetime_convert()), - dbesc(datetime_convert()), - intval($contact_record["id"]) - ); - - $u = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1", intval($importer["uid"])); - if($u) - $ret = self::send_share($u[0], $contact_record); - } - - return true; - } - - private function original_item($guid, $orig_author, $author) { - - // Do we already have this item? - $r = q("SELECT `body`, `tag`, `app`, `created`, `object-type`, `uri`, `guid`, - `author-name`, `author-link`, `author-avatar` - FROM `item` WHERE `guid` = '%s' AND `visible` AND NOT `deleted` AND `body` != '' LIMIT 1", - dbesc($guid)); - - if($r) { - logger("reshared message ".$guid." already exists on system."); - - // Maybe it is already a reshared item? - // Then refetch the content, since there can be many side effects with reshared posts from other networks or reshares from reshares - if (self::is_reshare($r[0]["body"])) - $r = array(); - else - return $r[0]; - } - - if (!$r) { - $server = "https://".substr($orig_author, strpos($orig_author, "@") + 1); - logger("1st try: reshared message ".$guid." will be fetched from original server: ".$server); - $item_id = self::store_by_guid($guid, $server); - - if (!$item_id) { - $server = "http://".substr($orig_author, strpos($orig_author, "@") + 1); - logger("2nd try: reshared message ".$guid." will be fetched from original server: ".$server); - $item_id = self::store_by_guid($guid, $server); - } - - // Deactivated by now since there is a risk that someone could manipulate postings through this method -/* if (!$item_id) { - $server = "https://".substr($author, strpos($author, "@") + 1); - logger("3rd try: reshared message ".$guid." will be fetched from sharer's server: ".$server); - $item_id = self::store_by_guid($guid, $server); - } - if (!$item_id) { - $server = "http://".substr($author, strpos($author, "@") + 1); - logger("4th try: reshared message ".$guid." will be fetched from sharer's server: ".$server); - $item_id = self::store_by_guid($guid, $server); - } -*/ - if ($item_id) { - $r = q("SELECT `body`, `tag`, `app`, `created`, `object-type`, `uri`, `guid`, - `author-name`, `author-link`, `author-avatar` - FROM `item` WHERE `id` = %d AND `visible` AND NOT `deleted` AND `body` != '' LIMIT 1", - intval($item_id)); - - if ($r) - return $r[0]; - - } - } - return false; - } - - private function receive_reshare($importer, $data) { - $root_author = notags(unxmlify($data->root_author)); - $root_guid = notags(unxmlify($data->root_guid)); - $guid = notags(unxmlify($data->guid)); - $author = notags(unxmlify($data->author)); - $public = notags(unxmlify($data->public)); - $created_at = notags(unxmlify($data->created_at)); - - $contact = self::allowed_contact_by_handle($importer, $author, false); - if (!$contact) - return false; - - if (self::message_exists($importer["uid"], $guid)) - return false; - - $original_item = self::original_item($root_guid, $root_author, $author); - if (!$original_item) - return false; - - $orig_url = App::get_baseurl()."/display/".$original_item["guid"]; - - $datarray = array(); - - $datarray["uid"] = $importer["uid"]; - $datarray["contact-id"] = $contact["id"]; - $datarray["network"] = NETWORK_DIASPORA; - - $datarray["author-name"] = $contact["name"]; - $datarray["author-link"] = $contact["url"]; - $datarray["author-avatar"] = ((x($contact,"thumb")) ? $contact["thumb"] : $contact["photo"]); - - $datarray["owner-name"] = $datarray["author-name"]; - $datarray["owner-link"] = $datarray["author-link"]; - $datarray["owner-avatar"] = $datarray["author-avatar"]; - - $datarray["guid"] = $guid; - $datarray["uri"] = $datarray["parent-uri"] = $author.":".$guid; - - $datarray["verb"] = ACTIVITY_POST; - $datarray["gravity"] = GRAVITY_PARENT; - - $datarray["object"] = json_encode($data); - - $prefix = share_header($original_item["author-name"], $original_item["author-link"], $original_item["author-avatar"], - $original_item["guid"], $original_item["created"], $orig_url); - $datarray["body"] = $prefix.$original_item["body"]."[/share]"; - - $datarray["tag"] = $original_item["tag"]; - $datarray["app"] = $original_item["app"]; - - $datarray["plink"] = self::plink($author, $guid); - $datarray["private"] = (($public == "false") ? 1 : 0); - $datarray["changed"] = $datarray["created"] = $datarray["edited"] = datetime_convert("UTC", "UTC", $created_at); - - $datarray["object-type"] = $original_item["object-type"]; - - self::fetch_guid($datarray); - $message_id = item_store($datarray); - - if ($message_id) - logger("Stored reshare ".$datarray["guid"]." with message id ".$message_id, LOGGER_DEBUG); - - return $message_id; - } - - private function item_retraction($importer, $contact, $data) { - $target_type = notags(unxmlify($data->target_type)); - $target_guid = notags(unxmlify($data->target_guid)); - $author = notags(unxmlify($data->author)); - - $person = self::person_by_handle($author); - if (!is_array($person)) { - logger("unable to find author detail for ".$author); - return false; - } - - $r = q("SELECT `id`, `parent`, `parent-uri`, `author-link` FROM `item` WHERE `guid` = '%s' AND `uid` = %d AND NOT `file` LIKE '%%[%%' LIMIT 1", - dbesc($target_guid), - intval($importer["uid"]) - ); - if (!$r) - return false; - - // Only delete it if the author really fits - if (!link_compare($r[0]["author-link"], $person["url"])) { - logger("Item author ".$r[0]["author-link"]." doesn't fit to expected contact ".$person["url"], LOGGER_DEBUG); - return false; - } - - // Check if the sender is the thread owner - $p = q("SELECT `id`, `author-link`, `origin` FROM `item` WHERE `id` = %d", - intval($r[0]["parent"])); - - // Only delete it if the parent author really fits - if (!link_compare($p[0]["author-link"], $contact["url"]) AND !link_compare($r[0]["author-link"], $contact["url"])) { - logger("Thread author ".$p[0]["author-link"]." and item author ".$r[0]["author-link"]." don't fit to expected contact ".$contact["url"], LOGGER_DEBUG); - return false; - } - - // Currently we don't have a central deletion function that we could use in this case. The function "item_drop" doesn't work for that case - q("UPDATE `item` SET `deleted` = 1, `edited` = '%s', `changed` = '%s', `body` = '' , `title` = '' WHERE `id` = %d", - dbesc(datetime_convert()), - dbesc(datetime_convert()), - intval($r[0]["id"]) - ); - delete_thread($r[0]["id"], $r[0]["parent-uri"]); - - logger("Deleted target ".$target_guid." (".$r[0]["id"].") from user ".$importer["uid"]." parent: ".$p[0]["id"], LOGGER_DEBUG); - - // Now check if the retraction needs to be relayed by us - if($p[0]["origin"]) { - - // Formerly we stored the signed text, the signature and the author in different fields. - // We now store the raw data so that we are more flexible. - q("INSERT INTO `sign` (`retract_iid`,`signed_text`) VALUES (%d,'%s')", - intval($r[0]["id"]), - dbesc(json_encode($data)) - ); - $s = q("select * from sign where retract_iid = %d", intval($r[0]["id"])); - logger("Stored signatur for item ".$r[0]["id"]." - ".print_r($s, true), LOGGER_DEBUG); - - // notify others - proc_run("php", "include/notifier.php", "drop", $r[0]["id"]); - } - } - - private function receive_retraction($importer, $sender, $data) { - $target_type = notags(unxmlify($data->target_type)); - - $contact = self::contact_by_handle($importer["uid"], $sender); - if (!$contact) { - logger("cannot find contact for sender: ".$sender." and user ".$importer["uid"]); - return false; - } - - logger("Got retraction for ".$target_type.", sender ".$sender." and user ".$importer["uid"], LOGGER_DEBUG); - - switch ($target_type) { - case "Comment": - case "Like": - case "Post": // "Post" will be supported in a future version - case "Reshare": - case "StatusMessage": - return self::item_retraction($importer, $contact, $data);; - - case "Person": - /// @todo What should we do with an "unshare"? - // Removing the contact isn't correct since we still can read the public items - //contact_remove($contact["id"]); - return true; - - default: - logger("Unknown target type ".$target_type); - return false; - } - return true; - } - - private function receive_status_message($importer, $data) { - - $raw_message = unxmlify($data->raw_message); - $guid = notags(unxmlify($data->guid)); - $author = notags(unxmlify($data->author)); - $public = notags(unxmlify($data->public)); - $created_at = notags(unxmlify($data->created_at)); - $provider_display_name = notags(unxmlify($data->provider_display_name)); - - /// @todo enable support for polls - //if ($data->poll) { - // foreach ($data->poll AS $poll) - // print_r($poll); - // die("poll!\n"); - //} - $contact = self::allowed_contact_by_handle($importer, $author, false); - if (!$contact) - return false; - - if (self::message_exists($importer["uid"], $guid)) - return false; - - $address = array(); - if ($data->location) - foreach ($data->location->children() AS $fieldname => $data) - $address[$fieldname] = notags(unxmlify($data)); - - $body = diaspora2bb($raw_message); - - $datarray = array(); - - if ($data->photo) { - foreach ($data->photo AS $photo) - $body = "[img]".$photo->remote_photo_path.$photo->remote_photo_name."[/img]\n".$body; - - $datarray["object-type"] = ACTIVITY_OBJ_PHOTO; - } else { - $datarray["object-type"] = ACTIVITY_OBJ_NOTE; - - // Add OEmbed and other information to the body - if (!self::is_redmatrix($contact["url"])) - $body = add_page_info_to_body($body, false, true); - } - - $datarray["uid"] = $importer["uid"]; - $datarray["contact-id"] = $contact["id"]; - $datarray["network"] = NETWORK_DIASPORA; - - $datarray["author-name"] = $contact["name"]; - $datarray["author-link"] = $contact["url"]; - $datarray["author-avatar"] = ((x($contact,"thumb")) ? $contact["thumb"] : $contact["photo"]); - - $datarray["owner-name"] = $datarray["author-name"]; - $datarray["owner-link"] = $datarray["author-link"]; - $datarray["owner-avatar"] = $datarray["author-avatar"]; - - $datarray["guid"] = $guid; - $datarray["uri"] = $datarray["parent-uri"] = $author.":".$guid; - - $datarray["verb"] = ACTIVITY_POST; - $datarray["gravity"] = GRAVITY_PARENT; - - $datarray["object"] = json_encode($data); - - $datarray["body"] = $body; - - if ($provider_display_name != "") - $datarray["app"] = $provider_display_name; - - $datarray["plink"] = self::plink($author, $guid); - $datarray["private"] = (($public == "false") ? 1 : 0); - $datarray["changed"] = $datarray["created"] = $datarray["edited"] = datetime_convert("UTC", "UTC", $created_at); - - if (isset($address["address"])) - $datarray["location"] = $address["address"]; - - if (isset($address["lat"]) AND isset($address["lng"])) - $datarray["coord"] = $address["lat"]." ".$address["lng"]; - - self::fetch_guid($datarray); - $message_id = item_store($datarray); - - if ($message_id) - logger("Stored item ".$datarray["guid"]." with message id ".$message_id, LOGGER_DEBUG); - - return $message_id; - } - - /****************************************************************************************** - * Here are all the functions that are needed to transmit data with the Diaspora protocol * - ******************************************************************************************/ - - private function my_handle($me) { - if ($contact["addr"] != "") - return $contact["addr"]; - - // Normally we should have a filled "addr" field - but in the past this wasn't the case - // So - just in case - we build the the address here. - return $me["nickname"]."@".substr(App::get_baseurl(), strpos(App::get_baseurl(),"://") + 3); - } - - private function build_public_message($msg, $user, $contact, $prvkey, $pubkey) { - - logger("Message: ".$msg, LOGGER_DATA); - - $handle = self::my_handle($user); - - $b64url_data = base64url_encode($msg); - - $data = str_replace(array("\n", "\r", " ", "\t"), array("", "", "", ""), $b64url_data); - - $type = "application/xml"; - $encoding = "base64url"; - $alg = "RSA-SHA256"; - - $signable_data = $data.".".base64url_encode($type).".".base64url_encode($encoding).".".base64url_encode($alg); - - $signature = rsa_sign($signable_data,$prvkey); - $sig = base64url_encode($signature); - - $xmldata = array("diaspora" => array("header" => array("author_id" => $handle), - "me:env" => array("me:encoding" => "base64url", - "me:alg" => "RSA-SHA256", - "me:data" => $data, - "@attributes" => array("type" => "application/xml"), - "me:sig" => $sig))); - - $namespaces = array("" => "https://joindiaspora.com/protocol", - "me" => "http://salmon-protocol.org/ns/magic-env"); - - $magic_env = xml::from_array($xmldata, $xml, false, $namespaces); - - logger("magic_env: ".$magic_env, LOGGER_DATA); - return $magic_env; - } - - private function build_private_message($msg, $user, $contact, $prvkey, $pubkey) { - - logger("Message: ".$msg, LOGGER_DATA); - - // without a public key nothing will work - - if (!$pubkey) { - logger("pubkey missing: contact id: ".$contact["id"]); - return false; - } - - $inner_aes_key = random_string(32); - $b_inner_aes_key = base64_encode($inner_aes_key); - $inner_iv = random_string(16); - $b_inner_iv = base64_encode($inner_iv); - - $outer_aes_key = random_string(32); - $b_outer_aes_key = base64_encode($outer_aes_key); - $outer_iv = random_string(16); - $b_outer_iv = base64_encode($outer_iv); - - $handle = self::my_handle($user); - - $padded_data = pkcs5_pad($msg,16); - $inner_encrypted = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $inner_aes_key, $padded_data, MCRYPT_MODE_CBC, $inner_iv); - - $b64_data = base64_encode($inner_encrypted); - - - $b64url_data = base64url_encode($b64_data); - $data = str_replace(array("\n", "\r", " ", "\t"), array("", "", "", ""), $b64url_data); - - $type = "application/xml"; - $encoding = "base64url"; - $alg = "RSA-SHA256"; - - $signable_data = $data.".".base64url_encode($type).".".base64url_encode($encoding).".".base64url_encode($alg); - - $signature = rsa_sign($signable_data,$prvkey); - $sig = base64url_encode($signature); - - $xmldata = array("decrypted_header" => array("iv" => $b_inner_iv, - "aes_key" => $b_inner_aes_key, - "author_id" => $handle)); - - $decrypted_header = xml::from_array($xmldata, $xml, true); - $decrypted_header = pkcs5_pad($decrypted_header,16); - - $ciphertext = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $outer_aes_key, $decrypted_header, MCRYPT_MODE_CBC, $outer_iv); - - $outer_json = json_encode(array("iv" => $b_outer_iv, "key" => $b_outer_aes_key)); - - $encrypted_outer_key_bundle = ""; - openssl_public_encrypt($outer_json, $encrypted_outer_key_bundle, $pubkey); - - $b64_encrypted_outer_key_bundle = base64_encode($encrypted_outer_key_bundle); - - logger("outer_bundle: ".$b64_encrypted_outer_key_bundle." key: ".$pubkey, LOGGER_DATA); - - $encrypted_header_json_object = json_encode(array("aes_key" => base64_encode($encrypted_outer_key_bundle), - "ciphertext" => base64_encode($ciphertext))); - $cipher_json = base64_encode($encrypted_header_json_object); - - $xmldata = array("diaspora" => array("encrypted_header" => $cipher_json, - "me:env" => array("me:encoding" => "base64url", - "me:alg" => "RSA-SHA256", - "me:data" => $data, - "@attributes" => array("type" => "application/xml"), - "me:sig" => $sig))); - - $namespaces = array("" => "https://joindiaspora.com/protocol", - "me" => "http://salmon-protocol.org/ns/magic-env"); - - $magic_env = xml::from_array($xmldata, $xml, false, $namespaces); - - logger("magic_env: ".$magic_env, LOGGER_DATA); - return $magic_env; - } - - private function build_message($msg, $user, $contact, $prvkey, $pubkey, $public = false) { - - if ($public) - $magic_env = self::build_public_message($msg,$user,$contact,$prvkey,$pubkey); - else - $magic_env = self::build_private_message($msg,$user,$contact,$prvkey,$pubkey); - - // The data that will be transmitted is double encoded via "urlencode", strange ... - $slap = "xml=".urlencode(urlencode($magic_env)); - return $slap; - } - - private function signature($owner, $message) { - $sigmsg = $message; - unset($sigmsg["author_signature"]); - unset($sigmsg["parent_author_signature"]); - - $signed_text = implode(";", $sigmsg); - - return base64_encode(rsa_sign($signed_text, $owner["uprvkey"], "sha256")); - } - - public static function transmit($owner, $contact, $slap, $public_batch, $queue_run=false, $guid = "") { - - $a = get_app(); - - $enabled = intval(get_config("system", "diaspora_enabled")); - if(!$enabled) - return 200; - - $logid = random_string(4); - $dest_url = (($public_batch) ? $contact["batch"] : $contact["notify"]); - if (!$dest_url) { - logger("no url for contact: ".$contact["id"]." batch mode =".$public_batch); - return 0; - } - - logger("transmit: ".$logid."-".$guid." ".$dest_url); - - if (!$queue_run && was_recently_delayed($contact["id"])) { - $return_code = 0; - } else { - if (!intval(get_config("system", "diaspora_test"))) { - post_url($dest_url."/", $slap); - $return_code = $a->get_curl_code(); - } else { - logger("test_mode"); - return 200; - } - } - - logger("transmit: ".$logid."-".$guid." returns: ".$return_code); - - if(!$return_code || (($return_code == 503) && (stristr($a->get_curl_headers(), "retry-after")))) { - logger("queue message"); - - $r = q("SELECT `id` FROM `queue` WHERE `cid` = %d AND `network` = '%s' AND `content` = '%s' AND `batch` = %d LIMIT 1", - intval($contact["id"]), - dbesc(NETWORK_DIASPORA), - dbesc($slap), - intval($public_batch) - ); - if($r) { - logger("add_to_queue ignored - identical item already in queue"); - } else { - // queue message for redelivery - add_to_queue($contact["id"], NETWORK_DIASPORA, $slap, $public_batch); - } - } - - return(($return_code) ? $return_code : (-1)); - } - - - private function build_and_transmit($owner, $contact, $type, $message, $public_batch = false, $guid = "", $spool = false) { - - $data = array("XML" => array("post" => array($type => $message))); - - $msg = xml::from_array($data, $xml); - - logger('message: '.$msg, LOGGER_DATA); - logger('send guid '.$guid, LOGGER_DEBUG); - - $slap = self::build_message($msg, $owner, $contact, $owner['uprvkey'], $contact['pubkey'], $public_batch); - - if ($spool) { - add_to_queue($contact['id'], NETWORK_DIASPORA, $slap, $public_batch); - return true; - } else - $return_code = self::transmit($owner, $contact, $slap, $public_batch, false, $guid); - - logger("guid: ".$item["guid"]." result ".$return_code, LOGGER_DEBUG); - - return $return_code; - } - - public static function send_share($owner,$contact) { - - $message = array("sender_handle" => self::my_handle($owner), - "recipient_handle" => $contact["addr"]); - - return self::build_and_transmit($owner, $contact, "request", $message); - } - - public static function send_unshare($owner,$contact) { - - $message = array("post_guid" => $owner["guid"], - "diaspora_handle" => self::my_handle($owner), - "type" => "Person"); - - return self::build_and_transmit($owner, $contact, "retraction", $message); - } - - public static function is_reshare($body) { - $body = trim($body); - - // Skip if it isn't a pure repeated messages - // Does it start with a share? - if (strpos($body, "[share") > 0) - return(false); - - // Does it end with a share? - if (strlen($body) > (strrpos($body, "[/share]") + 8)) - return(false); - - $attributes = preg_replace("/\[share(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism","$1",$body); - // Skip if there is no shared message in there - if ($body == $attributes) - return(false); - - $guid = ""; - preg_match("/guid='(.*?)'/ism", $attributes, $matches); - if ($matches[1] != "") - $guid = $matches[1]; - - preg_match('/guid="(.*?)"/ism', $attributes, $matches); - if ($matches[1] != "") - $guid = $matches[1]; - - if ($guid != "") { - $r = q("SELECT `contact-id` FROM `item` WHERE `guid` = '%s' AND `network` IN ('%s', '%s') LIMIT 1", - dbesc($guid), NETWORK_DFRN, NETWORK_DIASPORA); - if ($r) { - $ret= array(); - $ret["root_handle"] = self::handle_from_contact($r[0]["contact-id"]); - $ret["root_guid"] = $guid; - return($ret); - } - } - - $profile = ""; - preg_match("/profile='(.*?)'/ism", $attributes, $matches); - if ($matches[1] != "") - $profile = $matches[1]; - - preg_match('/profile="(.*?)"/ism', $attributes, $matches); - if ($matches[1] != "") - $profile = $matches[1]; - - $ret= array(); - - $ret["root_handle"] = preg_replace("=https?://(.*)/u/(.*)=ism", "$2@$1", $profile); - if (($ret["root_handle"] == $profile) OR ($ret["root_handle"] == "")) - return(false); - - $link = ""; - preg_match("/link='(.*?)'/ism", $attributes, $matches); - if ($matches[1] != "") - $link = $matches[1]; - - preg_match('/link="(.*?)"/ism', $attributes, $matches); - if ($matches[1] != "") - $link = $matches[1]; - - $ret["root_guid"] = preg_replace("=https?://(.*)/posts/(.*)=ism", "$2", $link); - if (($ret["root_guid"] == $link) OR ($ret["root_guid"] == "")) - return(false); - return($ret); - } - - public static function send_status($item, $owner, $contact, $public_batch = false) { - - $myaddr = self::my_handle($owner); - - $public = (($item["private"]) ? "false" : "true"); - - $created = datetime_convert("UTC", "UTC", $item["created"], 'Y-m-d H:i:s \U\T\C'); - - // Detect a share element and do a reshare - if (!$item['private'] AND ($ret = self::is_reshare($item["body"]))) { - $message = array("root_diaspora_id" => $ret["root_handle"], - "root_guid" => $ret["root_guid"], - "guid" => $item["guid"], - "diaspora_handle" => $myaddr, - "public" => $public, - "created_at" => $created, - "provider_display_name" => $item["app"]); - - $type = "reshare"; - } else { - $title = $item["title"]; - $body = $item["body"]; - - // convert to markdown - $body = html_entity_decode(bb2diaspora($body)); - - // Adding the title - if(strlen($title)) - $body = "## ".html_entity_decode($title)."\n\n".$body; - - if ($item["attach"]) { - $cnt = preg_match_all('/href=\"(.*?)\"(.*?)title=\"(.*?)\"/ism', $item["attach"], $matches, PREG_SET_ORDER); - if(cnt) { - $body .= "\n".t("Attachments:")."\n"; - foreach($matches as $mtch) - $body .= "[".$mtch[3]."](".$mtch[1].")\n"; - } - } - - $location = array(); - - if ($item["location"] != "") - $location["address"] = $item["location"]; - - if ($item["coord"] != "") { - $coord = explode(" ", $item["coord"]); - $location["lat"] = $coord[0]; - $location["lng"] = $coord[1]; - } - - $message = array("raw_message" => $body, - "location" => $location, - "guid" => $item["guid"], - "diaspora_handle" => $myaddr, - "public" => $public, - "created_at" => $created, - "provider_display_name" => $item["app"]); - - if (count($location) == 0) - unset($message["location"]); - - $type = "status_message"; - } - - return self::build_and_transmit($owner, $contact, $type, $message, $public_batch, $item["guid"]); - } - - private function construct_like($item, $owner) { - - $myaddr = self::my_handle($owner); - - $p = q("SELECT `guid`, `uri`, `parent-uri` FROM `item` WHERE `uri` = '%s' LIMIT 1", - dbesc($item["thr-parent"])); - if(!$p) - return false; - - $parent = $p[0]; - - $target_type = ($parent["uri"] === $parent["parent-uri"] ? "Post" : "Comment"); - $positive = "true"; - - return(array("positive" => $positive, - "guid" => $item["guid"], - "target_type" => $target_type, - "parent_guid" => $parent["guid"], - "author_signature" => $authorsig, - "diaspora_handle" => $myaddr)); - } - - private function construct_comment($item, $owner) { - - $myaddr = self::my_handle($owner); - - $p = q("SELECT `guid` FROM `item` WHERE `parent` = %d AND `id` = %d LIMIT 1", - intval($item["parent"]), - intval($item["parent"]) - ); - - if (!$p) - return false; - - $parent = $p[0]; - - $text = html_entity_decode(bb2diaspora($item["body"])); - - return(array("guid" => $item["guid"], - "parent_guid" => $parent["guid"], - "author_signature" => "", - "text" => $text, - "diaspora_handle" => $myaddr)); - } - - public static function send_followup($item,$owner,$contact,$public_batch = false) { - - if($item['verb'] === ACTIVITY_LIKE) { - $message = self::construct_like($item, $owner); - $type = "like"; - } else { - $message = self::construct_comment($item, $owner); - $type = "comment"; - } - - if (!$message) - return false; - - $message["author_signature"] = self::signature($owner, $message); - - return self::build_and_transmit($owner, $contact, $type, $message, $public_batch, $item["guid"]); - } - - private function message_from_signatur($item, $signature) { - - // Split the signed text - $signed_parts = explode(";", $signature['signed_text']); - - if ($item["deleted"]) - $message = array("parent_author_signature" => "", - "target_guid" => $signed_parts[0], - "target_type" => $signed_parts[1], - "sender_handle" => $signature['signer'], - "target_author_signature" => $signature['signature']); - elseif ($item['verb'] === ACTIVITY_LIKE) - $message = array("positive" => $signed_parts[0], - "guid" => $signed_parts[1], - "target_type" => $signed_parts[2], - "parent_guid" => $signed_parts[3], - "parent_author_signature" => "", - "author_signature" => $signature['signature'], - "diaspora_handle" => $signed_parts[4]); - else { - // Remove the comment guid - $guid = array_shift($signed_parts); - - // Remove the parent guid - $parent_guid = array_shift($signed_parts); - - // Remove the handle - $handle = array_pop($signed_parts); - - // Glue the parts together - $text = implode(";", $signed_parts); - - $message = array("guid" => $guid, - "parent_guid" => $parent_guid, - "parent_author_signature" => "", - "author_signature" => $signature['signature'], - "text" => implode(";", $signed_parts), - "diaspora_handle" => $handle); - } - return $message; - } - - public static function send_relay($item, $owner, $contact, $public_batch = false) { - - if ($item["deleted"]) { - $sql_sign_id = "retract_iid"; - $type = "relayable_retraction"; - } elseif ($item['verb'] === ACTIVITY_LIKE) { - $sql_sign_id = "iid"; - $type = "like"; - } else { - $sql_sign_id = "iid"; - $type = "comment"; - } - - logger("Got relayable data ".$type." for item ".$item["guid"]." (".$item["id"].")", LOGGER_DEBUG); - - // fetch the original signature - - $r = q("SELECT `signed_text`, `signature`, `signer` FROM `sign` WHERE `".$sql_sign_id."` = %d LIMIT 1", - intval($item["id"])); - - if (!$r) - return self::send_followup($item, $owner, $contact, $public_batch); - - $signature = $r[0]; - - // Old way - is used by the internal Friendica functions - /// @todo Change all signatur storing functions to the new format - if ($signature['signed_text'] AND $signature['signature'] AND $signature['signer']) - $message = self::message_from_signatur($item, $signature); - else {// New way - $msg = json_decode($signature['signed_text'], true); - - $message = array(); - foreach ($msg AS $field => $data) { - if (!$item["deleted"]) { - if ($field == "author") - $field = "diaspora_handle"; - if ($field == "parent_type") - $field = "target_type"; - } - - $message[$field] = $data; - } - } - - if ($item["deleted"]) { - $signed_text = $message["target_guid"].';'.$message["target_type"]; - $message["parent_author_signature"] = base64_encode(rsa_sign($signed_text, $owner["uprvkey"], "sha256")); - } else - $message["parent_author_signature"] = self::signature($owner, $message); - - logger("Relayed data ".print_r($message, true), LOGGER_DEBUG); - - return self::build_and_transmit($owner, $contact, $type, $message, $public_batch, $item["guid"]); - } - - public static function send_retraction($item, $owner, $contact, $public_batch = false) { - - $myaddr = self::my_handle($owner); - - // Check whether the retraction is for a top-level post or whether it's a relayable - if ($item["uri"] !== $item["parent-uri"]) { - $msg_type = "relayable_retraction"; - $target_type = (($item["verb"] === ACTIVITY_LIKE) ? "Like" : "Comment"); - } else { - $msg_type = "signed_retraction"; - $target_type = "StatusMessage"; - } - - $signed_text = $item["guid"].";".$target_type; - - $message = array("target_guid" => $item['guid'], - "target_type" => $target_type, - "sender_handle" => $myaddr, - "target_author_signature" => base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256'))); - - return self::build_and_transmit($owner, $contact, $msg_type, $message, $public_batch, $item["guid"]); - } - - public static function send_mail($item, $owner, $contact) { - - $myaddr = self::my_handle($owner); - - $r = q("SELECT * FROM `conv` WHERE `id` = %d AND `uid` = %d LIMIT 1", - intval($item["convid"]), - intval($item["uid"]) - ); - - if (!$r) { - logger("conversation not found."); - return; - } - $cnv = $r[0]; - - $conv = array( - "guid" => $cnv["guid"], - "subject" => $cnv["subject"], - "created_at" => datetime_convert("UTC", "UTC", $cnv['created'], 'Y-m-d H:i:s \U\T\C'), - "diaspora_handle" => $cnv["creator"], - "participant_handles" => $cnv["recips"] - ); - - $body = bb2diaspora($item["body"]); - $created = datetime_convert("UTC", "UTC", $item["created"], 'Y-m-d H:i:s \U\T\C'); - - $signed_text = $item["guid"].";".$cnv["guid"].";".$body.";".$created.";".$myaddr.";".$cnv['guid']; - $sig = base64_encode(rsa_sign($signed_text, $owner["uprvkey"], "sha256")); - - $msg = array( - "guid" => $item["guid"], - "parent_guid" => $cnv["guid"], - "parent_author_signature" => $sig, - "author_signature" => $sig, - "text" => $body, - "created_at" => $created, - "diaspora_handle" => $myaddr, - "conversation_guid" => $cnv["guid"] - ); - - if ($item["reply"]) { - $message = $msg; - $type = "message"; - } else { - $message = array("guid" => $cnv["guid"], - "subject" => $cnv["subject"], - "created_at" => datetime_convert("UTC", "UTC", $cnv['created'], 'Y-m-d H:i:s \U\T\C'), - "message" => $msg, - "diaspora_handle" => $cnv["creator"], - "participant_handles" => $cnv["recips"]); - - $type = "conversation"; - } - - return self::build_and_transmit($owner, $contact, $type, $message, false, $item["guid"]); - } - - public static function send_profile($uid) { - - if (!$uid) - return; - - $recips = q("SELECT `id`,`name`,`network`,`pubkey`,`notify` FROM `contact` WHERE `network` = '%s' - AND `uid` = %d AND `rel` != %d", - dbesc(NETWORK_DIASPORA), - intval($uid), - intval(CONTACT_IS_SHARING) - ); - if (!$recips) - return; - - $r = q("SELECT `profile`.`uid` AS `profile_uid`, `profile`.* , `user`.*, `user`.`prvkey` AS `uprvkey`, `contact`.`addr` - FROM `profile` - INNER JOIN `user` ON `profile`.`uid` = `user`.`uid` - INNER JOIN `contact` ON `profile`.`uid` = `contact`.`uid` - WHERE `user`.`uid` = %d AND `profile`.`is-default` AND `contact`.`self` LIMIT 1", - intval($uid) - ); - - if (!$r) - return; - - $profile = $r[0]; - - $handle = $profile["addr"]; - $first = ((strpos($profile['name'],' ') - ? trim(substr($profile['name'],0,strpos($profile['name'],' '))) : $profile['name'])); - $last = (($first === $profile['name']) ? '' : trim(substr($profile['name'], strlen($first)))); - $large = App::get_baseurl().'/photo/custom/300/'.$profile['uid'].'.jpg'; - $medium = App::get_baseurl().'/photo/custom/100/'.$profile['uid'].'.jpg'; - $small = App::get_baseurl().'/photo/custom/50/' .$profile['uid'].'.jpg'; - $searchable = (($profile['publish'] && $profile['net-publish']) ? 'true' : 'false'); - - if ($searchable === 'true') { - $dob = '1000-00-00'; - - if (($profile['dob']) && ($profile['dob'] != '0000-00-00')) - $dob = ((intval($profile['dob'])) ? intval($profile['dob']) : '1000') .'-'. datetime_convert('UTC','UTC',$profile['dob'],'m-d'); - - $about = $profile['about']; - $about = strip_tags(bbcode($about)); - - $location = formatted_location($profile); - $tags = ''; - if ($profile['pub_keywords']) { - $kw = str_replace(',',' ',$profile['pub_keywords']); - $kw = str_replace(' ',' ',$kw); - $arr = explode(' ',$profile['pub_keywords']); - if (count($arr)) { - for($x = 0; $x < 5; $x ++) { - if (trim($arr[$x])) - $tags .= '#'. trim($arr[$x]) .' '; - } - } - } - $tags = trim($tags); - } - - $message = array("diaspora_handle" => $handle, - "first_name" => $first, - "last_name" => $last, - "image_url" => $large, - "image_url_medium" => $medium, - "image_url_small" => $small, - "birthday" => $dob, - "gender" => $profile['gender'], - "bio" => $about, - "location" => $location, - "searchable" => $searchable, - "tag_string" => $tags); - - foreach($recips as $recip) - self::build_and_transmit($profile, $recip, "profile", $message, false, "", true); - } -} -?> From 357c48076d9ebd45938dd0a4a384dd1ed9dc637e Mon Sep 17 00:00:00 2001 From: Michael Vogel Date: Mon, 14 Mar 2016 23:54:01 +0100 Subject: [PATCH 047/211] Bugfix: XML copy had problems with "&" --- include/diaspora.php | 12 ++++++------ include/xml.php | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/include/diaspora.php b/include/diaspora.php index 75cedeccd1..4c3cb41726 100644 --- a/include/diaspora.php +++ b/include/diaspora.php @@ -291,7 +291,7 @@ class diaspora { return self::receive_account_deletion($importer, $fields); case "comment": - return self::receive_comment($importer, $sender, $fields); + return self::receive_comment($importer, $sender, $fields, $msg["message"]); case "conversation": return self::receive_conversation($importer, $msg, $fields); @@ -324,7 +324,7 @@ class diaspora { return self::receive_retraction($importer, $sender, $fields); case "status_message": - return self::receive_status_message($importer, $fields); + return self::receive_status_message($importer, $fields, $msg["message"]); default: logger("Unknown message type ".$type); @@ -841,7 +841,7 @@ class diaspora { return true; } - private function receive_comment($importer, $sender, $data) { + private function receive_comment($importer, $sender, $data, $xml) { $guid = notags(unxmlify($data->guid)); $parent_guid = notags(unxmlify($data->parent_guid)); $text = unxmlify($data->text); @@ -890,7 +890,7 @@ class diaspora { $datarray["parent-uri"] = $parent_item["uri"]; $datarray["object-type"] = ACTIVITY_OBJ_COMMENT; - $datarray["object"] = json_encode($data); + $datarray["object"] = $xml; $datarray["body"] = diaspora2bb($text); @@ -1769,7 +1769,7 @@ class diaspora { return true; } - private function receive_status_message($importer, $data) { + private function receive_status_message($importer, $data, $xml) { $raw_message = unxmlify($data->raw_message); $guid = notags(unxmlify($data->guid)); @@ -1831,7 +1831,7 @@ class diaspora { $datarray["verb"] = ACTIVITY_POST; $datarray["gravity"] = GRAVITY_PARENT; - $datarray["object"] = json_encode($data); + $datarray["object"] = $xml; $datarray["body"] = $body; diff --git a/include/xml.php b/include/xml.php index c2313648ce..c74c23c473 100644 --- a/include/xml.php +++ b/include/xml.php @@ -62,7 +62,7 @@ class xml { function copy(&$source, &$target, $elementname) { if (count($source->children()) == 0) - $target->addChild($elementname, $source); + $target->addChild($elementname, xmlify($source)); else { $child = $target->addChild($elementname); foreach ($source->children() AS $childfield => $childentry) From d87e78a488e2b2b896d05be2fdce9cc1ba913802 Mon Sep 17 00:00:00 2001 From: Michael Vogel Date: Tue, 15 Mar 2016 00:26:28 +0100 Subject: [PATCH 048/211] Reshares now store the original XML as well. --- include/diaspora.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/diaspora.php b/include/diaspora.php index 4c3cb41726..3c3f5cb25a 100644 --- a/include/diaspora.php +++ b/include/diaspora.php @@ -318,7 +318,7 @@ class diaspora { return self::receive_request($importer, $fields); case "reshare": - return self::receive_reshare($importer, $fields); + return self::receive_reshare($importer, $fields, $msg["message"]); case "retraction": return self::receive_retraction($importer, $sender, $fields); @@ -1611,7 +1611,7 @@ class diaspora { return false; } - private function receive_reshare($importer, $data) { + private function receive_reshare($importer, $data, $xml) { $root_author = notags(unxmlify($data->root_author)); $root_guid = notags(unxmlify($data->root_guid)); $guid = notags(unxmlify($data->guid)); @@ -1652,7 +1652,7 @@ class diaspora { $datarray["verb"] = ACTIVITY_POST; $datarray["gravity"] = GRAVITY_PARENT; - $datarray["object"] = json_encode($data); + $datarray["object"] = $xml; $prefix = share_header($original_item["author-name"], $original_item["author-link"], $original_item["author-avatar"], $original_item["guid"], $original_item["created"], $orig_url); From 887f2e92a5c9ce7d3107e673e11c2b2314770413 Mon Sep 17 00:00:00 2001 From: Michael Vogel Date: Tue, 15 Mar 2016 07:05:10 +0100 Subject: [PATCH 049/211] Unused files are removed --- view/templates/diasp_dec_hdr.tpl | 9 ------ view/templates/diaspora_comment.tpl | 12 -------- view/templates/diaspora_comment_relay.tpl | 13 -------- view/templates/diaspora_conversation.tpl | 30 ------------------- view/templates/diaspora_like.tpl | 13 -------- view/templates/diaspora_like_relay.tpl | 14 --------- view/templates/diaspora_message.tpl | 17 ----------- view/templates/diaspora_photo.tpl | 14 --------- view/templates/diaspora_post.tpl | 13 -------- view/templates/diaspora_profile.tpl | 17 ----------- view/templates/diaspora_relay_retraction.tpl | 10 ------- .../diaspora_relayable_retraction.tpl | 12 -------- view/templates/diaspora_reshare.tpl | 14 --------- view/templates/diaspora_retract.tpl | 10 ------- view/templates/diaspora_share.tpl | 9 ------ view/templates/diaspora_signed_retract.tpl | 11 ------- 16 files changed, 218 deletions(-) delete mode 100644 view/templates/diasp_dec_hdr.tpl delete mode 100644 view/templates/diaspora_comment.tpl delete mode 100644 view/templates/diaspora_comment_relay.tpl delete mode 100644 view/templates/diaspora_conversation.tpl delete mode 100644 view/templates/diaspora_like.tpl delete mode 100644 view/templates/diaspora_like_relay.tpl delete mode 100644 view/templates/diaspora_message.tpl delete mode 100644 view/templates/diaspora_photo.tpl delete mode 100644 view/templates/diaspora_post.tpl delete mode 100644 view/templates/diaspora_profile.tpl delete mode 100644 view/templates/diaspora_relay_retraction.tpl delete mode 100644 view/templates/diaspora_relayable_retraction.tpl delete mode 100644 view/templates/diaspora_reshare.tpl delete mode 100644 view/templates/diaspora_retract.tpl delete mode 100644 view/templates/diaspora_share.tpl delete mode 100644 view/templates/diaspora_signed_retract.tpl diff --git a/view/templates/diasp_dec_hdr.tpl b/view/templates/diasp_dec_hdr.tpl deleted file mode 100644 index 136d1ca302..0000000000 --- a/view/templates/diasp_dec_hdr.tpl +++ /dev/null @@ -1,9 +0,0 @@ - - - {{$inner_iv}} - {{$inner_key}} - - {{$author_name}} - {{$author_uri}} - - diff --git a/view/templates/diaspora_comment.tpl b/view/templates/diaspora_comment.tpl deleted file mode 100644 index 107cc73022..0000000000 --- a/view/templates/diaspora_comment.tpl +++ /dev/null @@ -1,12 +0,0 @@ - - - - - {{$guid}} - {{$parent_guid}} - {{$authorsig}} - {{$body}} - {{$handle}} - - - \ No newline at end of file diff --git a/view/templates/diaspora_comment_relay.tpl b/view/templates/diaspora_comment_relay.tpl deleted file mode 100644 index b4f84dc84e..0000000000 --- a/view/templates/diaspora_comment_relay.tpl +++ /dev/null @@ -1,13 +0,0 @@ - - - - - {{$guid}} - {{$parent_guid}} - {{$parentsig}} - {{$authorsig}} - {{$body}} - {{$handle}} - - - \ No newline at end of file diff --git a/view/templates/diaspora_conversation.tpl b/view/templates/diaspora_conversation.tpl deleted file mode 100644 index 28e4cdb98f..0000000000 --- a/view/templates/diaspora_conversation.tpl +++ /dev/null @@ -1,30 +0,0 @@ - - - - - {{$conv.guid}} - {{$conv.subject}} - {{$conv.created_at}} - - {{foreach $conv.messages as $msg}} - - - {{$msg.guid}} - {{$msg.parent_guid}} - {{if $msg.parent_author_signature}} - {{$msg.parent_author_signature}} - {{/if}} - {{$msg.author_signature}} - {{$msg.text}} - {{$msg.created_at}} - {{$msg.diaspora_handle}} - {{$msg.conversation_guid}} - - - {{/foreach}} - - {{$conv.diaspora_handle}} - {{$conv.participant_handles}} - - - diff --git a/view/templates/diaspora_like.tpl b/view/templates/diaspora_like.tpl deleted file mode 100644 index 165b0f5f7b..0000000000 --- a/view/templates/diaspora_like.tpl +++ /dev/null @@ -1,13 +0,0 @@ - - - - - {{$positive}} - {{$guid}} - {{$target_type}} - {{$parent_guid}} - {{$authorsig}} - {{$handle}} - - - diff --git a/view/templates/diaspora_like_relay.tpl b/view/templates/diaspora_like_relay.tpl deleted file mode 100644 index e1696e722a..0000000000 --- a/view/templates/diaspora_like_relay.tpl +++ /dev/null @@ -1,14 +0,0 @@ - - - - - {{$positive}} - {{$guid}} - {{$target_type}} - {{$parent_guid}} - {{$parentsig}} - {{$authorsig}} - {{$handle}} - - - diff --git a/view/templates/diaspora_message.tpl b/view/templates/diaspora_message.tpl deleted file mode 100644 index f9adb833b6..0000000000 --- a/view/templates/diaspora_message.tpl +++ /dev/null @@ -1,17 +0,0 @@ - - - - - {{$msg.guid}} - {{$msg.parent_guid}} - {{if $msg.parent_author_signature}} - {{$msg.parent_author_signature}} - {{/if}} - {{$msg.author_signature}} - {{$msg.text}} - {{$msg.created_at}} - {{$msg.diaspora_handle}} - {{$msg.conversation_guid}} - - - diff --git a/view/templates/diaspora_photo.tpl b/view/templates/diaspora_photo.tpl deleted file mode 100644 index 0330499577..0000000000 --- a/view/templates/diaspora_photo.tpl +++ /dev/null @@ -1,14 +0,0 @@ - - - - - {{$guid}} - {{$handle}} - {{$public}} - {{$created_at}} - {{$path}} - {{$filename}} - {{$msg_guid}} - - - diff --git a/view/templates/diaspora_post.tpl b/view/templates/diaspora_post.tpl deleted file mode 100644 index d6ba97327c..0000000000 --- a/view/templates/diaspora_post.tpl +++ /dev/null @@ -1,13 +0,0 @@ - - - - - {{$body}} - {{$guid}} - {{$handle}} - {{$public}} - {{$created}} - {{$provider}} - - - diff --git a/view/templates/diaspora_profile.tpl b/view/templates/diaspora_profile.tpl deleted file mode 100644 index afbbb1e1a2..0000000000 --- a/view/templates/diaspora_profile.tpl +++ /dev/null @@ -1,17 +0,0 @@ - - - - {{$handle}} - {{$first}} - {{$last}} - {{$large}} - {{$medium}} - {{$small}} - {{$dob}} - {{$gender}} - {{$about}} - {{$location}} - {{$searchable}} - {{$tags}} - - diff --git a/view/templates/diaspora_relay_retraction.tpl b/view/templates/diaspora_relay_retraction.tpl deleted file mode 100644 index c4b44cd05f..0000000000 --- a/view/templates/diaspora_relay_retraction.tpl +++ /dev/null @@ -1,10 +0,0 @@ - - - - {{$guid}} - {{$type}} - {{$handle}} - {{$signature}} - - - diff --git a/view/templates/diaspora_relayable_retraction.tpl b/view/templates/diaspora_relayable_retraction.tpl deleted file mode 100644 index 2ae776d1d0..0000000000 --- a/view/templates/diaspora_relayable_retraction.tpl +++ /dev/null @@ -1,12 +0,0 @@ - - - - - {{$parentsig}} - {{$guid}} - {{$target_type}} - {{$handle}} - {{$authorsig}} - - - diff --git a/view/templates/diaspora_reshare.tpl b/view/templates/diaspora_reshare.tpl deleted file mode 100644 index 6a4776b1bc..0000000000 --- a/view/templates/diaspora_reshare.tpl +++ /dev/null @@ -1,14 +0,0 @@ - - - - - {{$root_handle}} - {{$root_guid}} - {{$guid}} - {{$handle}} - {{$public}} - {{$created}} - {{$provider}} - - - diff --git a/view/templates/diaspora_retract.tpl b/view/templates/diaspora_retract.tpl deleted file mode 100644 index 3ddfcabfa7..0000000000 --- a/view/templates/diaspora_retract.tpl +++ /dev/null @@ -1,10 +0,0 @@ - - - - - {{$guid}} - {{$handle}} - {{$type}} - - - diff --git a/view/templates/diaspora_share.tpl b/view/templates/diaspora_share.tpl deleted file mode 100644 index 0142ab36c5..0000000000 --- a/view/templates/diaspora_share.tpl +++ /dev/null @@ -1,9 +0,0 @@ - - - - - {{$sender}} - {{$recipient}} - - - \ No newline at end of file diff --git a/view/templates/diaspora_signed_retract.tpl b/view/templates/diaspora_signed_retract.tpl deleted file mode 100644 index 83d0def4d2..0000000000 --- a/view/templates/diaspora_signed_retract.tpl +++ /dev/null @@ -1,11 +0,0 @@ - - - - - {{$guid}} - {{$type}} - {{$handle}} - {{$signature}} - - - From 79fb05ff80844a576984f9735328cc12986a3a84 Mon Sep 17 00:00:00 2001 From: Michael Vogel Date: Tue, 15 Mar 2016 07:18:11 +0100 Subject: [PATCH 050/211] Removed moved function --- include/diaspora.php | 1 - include/network.php | 58 -------------------------------------------- 2 files changed, 59 deletions(-) diff --git a/include/diaspora.php b/include/diaspora.php index 3c3f5cb25a..d5f2a21d9e 100644 --- a/include/diaspora.php +++ b/include/diaspora.php @@ -517,7 +517,6 @@ class diaspora { * @return string The id of the fcontact entry */ private function add_fcontact($arr, $update = false) { - /// @todo Remove this function from include/network.php if($update) { $r = q("UPDATE `fcontact` SET diff --git a/include/network.php b/include/network.php index 84f1297e03..5fd73bfef9 100644 --- a/include/network.php +++ b/include/network.php @@ -862,64 +862,6 @@ function parse_xml_string($s,$strict = true) { return $x; }} -function add_fcontact($arr,$update = false) { - - if($update) { - $r = q("UPDATE `fcontact` SET - `name` = '%s', - `photo` = '%s', - `request` = '%s', - `nick` = '%s', - `addr` = '%s', - `batch` = '%s', - `notify` = '%s', - `poll` = '%s', - `confirm` = '%s', - `alias` = '%s', - `pubkey` = '%s', - `updated` = '%s' - WHERE `url` = '%s' AND `network` = '%s'", - dbesc($arr['name']), - dbesc($arr['photo']), - dbesc($arr['request']), - dbesc($arr['nick']), - dbesc($arr['addr']), - dbesc($arr['batch']), - dbesc($arr['notify']), - dbesc($arr['poll']), - dbesc($arr['confirm']), - dbesc($arr['alias']), - dbesc($arr['pubkey']), - dbesc(datetime_convert()), - dbesc($arr['url']), - dbesc($arr['network']) - ); - } - else { - $r = q("insert into fcontact ( `url`,`name`,`photo`,`request`,`nick`,`addr`, - `batch`, `notify`,`poll`,`confirm`,`network`,`alias`,`pubkey`,`updated` ) - values('%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s')", - dbesc($arr['url']), - dbesc($arr['name']), - dbesc($arr['photo']), - dbesc($arr['request']), - dbesc($arr['nick']), - dbesc($arr['addr']), - dbesc($arr['batch']), - dbesc($arr['notify']), - dbesc($arr['poll']), - dbesc($arr['confirm']), - dbesc($arr['network']), - dbesc($arr['alias']), - dbesc($arr['pubkey']), - dbesc(datetime_convert()) - ); - } - - return $r; -} - - function scale_external_images($srctext, $include_link = true, $scale_replace = false) { // Suppress "view full size" From b84ed78f8f49d75d5f08a7e051fe0d7c2e2d6970 Mon Sep 17 00:00:00 2001 From: Michael Vogel Date: Tue, 15 Mar 2016 20:14:08 +0100 Subject: [PATCH 051/211] Values are sanitized, messages are not relayed when there is no signature --- include/diaspora.php | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/include/diaspora.php b/include/diaspora.php index d5f2a21d9e..f4e3132959 100644 --- a/include/diaspora.php +++ b/include/diaspora.php @@ -221,7 +221,9 @@ class diaspora { logger('Message verified.'); - return array('message' => $inner_decrypted, 'author' => $author_link, 'key' => $key); + return array('message' => (string)$inner_decrypted, + 'author' => unxmlify($author_link), + 'key' => (string)$key); } @@ -1801,7 +1803,8 @@ class diaspora { if ($data->photo) { foreach ($data->photo AS $photo) - $body = "[img]".$photo->remote_photo_path.$photo->remote_photo_name."[/img]\n".$body; + $body = "[img]".unxmlify($photo->remote_photo_path). + unxmlify($photo->remote_photo_name)."[/img]\n".$body; $datarray["object-type"] = ACTIVITY_OBJ_PHOTO; } else { @@ -2355,8 +2358,10 @@ class diaspora { $r = q("SELECT `signed_text`, `signature`, `signer` FROM `sign` WHERE `".$sql_sign_id."` = %d LIMIT 1", intval($item["id"])); - if (!$r) - return self::send_followup($item, $owner, $contact, $public_batch); + if (!$r) { + logger("Couldn't fetch signatur for contact ".$contact["addr"]." at item ".$item["guid"]." (".$item["id"].")", LOGGER_DEBUG); + return false; + } $signature = $r[0]; From 8b06893488c4552ca7206cd3233f83b06f33e567 Mon Sep 17 00:00:00 2001 From: Michael Vogel Date: Wed, 16 Mar 2016 11:44:45 +0100 Subject: [PATCH 052/211] DFRN: Mentions were imported as hash tags --- include/dfrn.php | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/include/dfrn.php b/include/dfrn.php index ad04a91295..1c5ac2b012 100644 --- a/include/dfrn.php +++ b/include/dfrn.php @@ -2022,14 +2022,28 @@ class dfrn { $categories = $xpath->query("atom:category", $entry); if ($categories) { foreach ($categories AS $category) { - foreach($category->attributes AS $attributes) - if ($attributes->name == "term") { + $term = ""; + $scheme = ""; + foreach($category->attributes AS $attributes) { + if ($attributes->name == "term") $term = $attributes->textContent; + + if ($attributes->name == "scheme") + $scheme = $attributes->textContent; + } + + if (($term != "") AND ($scheme != "")) { + $parts = explode(":", $scheme); + if ((count($parts) >= 4) AND (array_shift($parts) == "X-DFRN")) { + $termhash = array_shift($parts); + $termurl = implode(":", $parts); + if(strlen($item["tag"])) $item["tag"] .= ","; - $item["tag"] .= "#[url=".App::get_baseurl()."/search?tag=".$term."]".$term."[/url]"; + $item["tag"] .= $termhash."[url=".$termurl."]".$term."[/url]"; } + } } } From 35d5f4caee9e51f0935c8cf58e53195f351abffd Mon Sep 17 00:00:00 2001 From: Michael Vogel Date: Wed, 16 Mar 2016 16:49:54 +0100 Subject: [PATCH 053/211] Missing include --- include/diaspora.php | 1 + 1 file changed, 1 insertion(+) diff --git a/include/diaspora.php b/include/diaspora.php index f4e3132959..da69cdc799 100644 --- a/include/diaspora.php +++ b/include/diaspora.php @@ -13,6 +13,7 @@ require_once("include/socgraph.php"); require_once("include/group.php"); require_once("include/xml.php"); require_once("include/datetime.php"); +require_once("include/queue_fn.php"); /** * @brief This class contain functions to create and send Diaspora XML files From 3d8770a9daf629ac362a1ea8d8f56ed53d82ffc5 Mon Sep 17 00:00:00 2001 From: Michael Vogel Date: Wed, 16 Mar 2016 19:30:46 +0100 Subject: [PATCH 054/211] Added checklist --- include/diaspora.php | 51 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/include/diaspora.php b/include/diaspora.php index da69cdc799..f7c38c2271 100644 --- a/include/diaspora.php +++ b/include/diaspora.php @@ -2,6 +2,36 @@ /** * @file include/diaspora.php * @brief The implementation of the diaspora protocol + * + * Checklist: + * + * Checked: + * - send status + * - send comment + * - send like + * - send mail + * - receive status + * - receive reshare + * - receive comment + * - receive like + * - receive connect request + * - receive profile data + * - receive mail + * - relay comment + * - relay like + * - + * - + * + * Unchecked: + * - receive account deletion + * - send share + * - send unshare + * - send status retraction + * - send comment retraction + * - send like retraction + * - relay comment retraction + * - relay like retraction + * - */ require_once("include/items.php"); @@ -2374,16 +2404,19 @@ class diaspora { $msg = json_decode($signature['signed_text'], true); $message = array(); - foreach ($msg AS $field => $data) { - if (!$item["deleted"]) { - if ($field == "author") - $field = "diaspora_handle"; - if ($field == "parent_type") - $field = "target_type"; - } + if (is_array($msg)) { + foreach ($msg AS $field => $data) { + if (!$item["deleted"]) { + if ($field == "author") + $field = "diaspora_handle"; + if ($field == "parent_type") + $field = "target_type"; + } - $message[$field] = $data; - } + $message[$field] = $data; + } + } else + logger("Signature text for item ".$item["guid"]." (".$item["id"].") couldn't be extracted: ".$signature['signed_text'], LOGGER_DEBUG); } if ($item["deleted"]) { From cc7b34b7e649d95342d86136237f64ef7d521bfb Mon Sep 17 00:00:00 2001 From: Michael Vogel Date: Wed, 16 Mar 2016 21:27:07 +0100 Subject: [PATCH 055/211] Better reshare detection --- include/diaspora.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/include/diaspora.php b/include/diaspora.php index f7c38c2271..4a6cbad131 100644 --- a/include/diaspora.php +++ b/include/diaspora.php @@ -1600,7 +1600,7 @@ class diaspora { // Maybe it is already a reshared item? // Then refetch the content, since there can be many side effects with reshared posts from other networks or reshares from reshares - if (self::is_reshare($r[0]["body"])) + if (self::is_reshare($r[0]["body"], false)) $r = array(); else return $r[0]; @@ -2130,7 +2130,7 @@ class diaspora { return self::build_and_transmit($owner, $contact, "retraction", $message); } - public static function is_reshare($body) { + public static function is_reshare($body, $complete = true) { $body = trim($body); // Skip if it isn't a pure repeated messages @@ -2147,6 +2147,10 @@ class diaspora { if ($body == $attributes) return(false); + // If we don't do the complete check we quit here + if (!$complete) + return true; + $guid = ""; preg_match("/guid='(.*?)'/ism", $attributes, $matches); if ($matches[1] != "") From f7c9ee5848e2a1ccadf3c0c2fc23c017eb15c4c1 Mon Sep 17 00:00:00 2001 From: Michael Vogel Date: Thu, 17 Mar 2016 00:37:44 +0100 Subject: [PATCH 056/211] Everything tested, one open to-do --- include/delivery.php | 3 ++- include/diaspora.php | 40 +++++++++++++++++++++++++++++----------- 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/include/delivery.php b/include/delivery.php index 9ac9f2391b..fe33774382 100644 --- a/include/delivery.php +++ b/include/delivery.php @@ -243,7 +243,8 @@ function delivery_run(&$argv, &$argc){ if ((strlen($parent['allow_cid'])) || (strlen($parent['allow_gid'])) || (strlen($parent['deny_cid'])) - || (strlen($parent['deny_gid']))) { + || (strlen($parent['deny_gid'])) + || $parent["private"]) { $public_message = false; // private recipients, not public } diff --git a/include/diaspora.php b/include/diaspora.php index 4a6cbad131..870466497d 100644 --- a/include/diaspora.php +++ b/include/diaspora.php @@ -10,6 +10,11 @@ * - send comment * - send like * - send mail + * - send status retraction + * - send comment retraction on own post + * - send comment retraction on diaspora post + * - send like retraction on own post + * - send like retraction on diaspora post * - receive status * - receive reshare * - receive comment @@ -17,21 +22,21 @@ * - receive connect request * - receive profile data * - receive mail + * - receive comment retraction + * - receive like retraction * - relay comment * - relay like - * - - * - + * - relay comment retraction from diaspora + * - relay comment retraction from friendica + * - relay like retraction from diaspora + * - relay like retraction from friendica * - * Unchecked: + * Should work: * - receive account deletion * - send share * - send unshare - * - send status retraction - * - send comment retraction - * - send like retraction - * - relay comment retraction - * - relay like retraction - * - + * + * Unchecked: */ require_once("include/items.php"); @@ -2394,7 +2399,10 @@ class diaspora { intval($item["id"])); if (!$r) { - logger("Couldn't fetch signatur for contact ".$contact["addr"]." at item ".$item["guid"]." (".$item["id"].")", LOGGER_DEBUG); + if ($item["deleted"]) + return self::send_retraction($item, $owner, $contact, $public_batch); + + logger("Couldn't fetch signatur for item ".$item["guid"]." (".$item["id"].")", LOGGER_DEBUG); return false; } @@ -2436,6 +2444,9 @@ class diaspora { public static function send_retraction($item, $owner, $contact, $public_batch = false) { + /// @todo Fetch handle from every contact (via gcontact) + $itemaddr = self::handle_from_contact($item["contact-id"]); + $myaddr = self::my_handle($owner); // Check whether the retraction is for a top-level post or whether it's a relayable @@ -2451,9 +2462,16 @@ class diaspora { $message = array("target_guid" => $item['guid'], "target_type" => $target_type, - "sender_handle" => $myaddr, + "sender_handle" => $itemaddr, "target_author_signature" => base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256'))); + if ($itemaddr != $myaddr) { + $message["parent_author_signature"] = $message["target_author_signature"]; + unset($message["target_author_signature"]); + } + + logger("Got message ".print_r($message, true), LOGGER_DEBUG); + return self::build_and_transmit($owner, $contact, $msg_type, $message, $public_batch, $item["guid"]); } From 22c889c3196f95bc376716a500e72f97b0e89c2b Mon Sep 17 00:00:00 2001 From: Michael Vogel Date: Thu, 17 Mar 2016 12:24:23 +0100 Subject: [PATCH 057/211] Retraction do work as well --- database.sql | 4 +-- doc/database/db_sign.md | 1 - include/dbstructure.php | 2 -- include/diaspora.php | 73 ++++++++++++++++------------------------- include/items.php | 51 ---------------------------- include/like.php | 69 -------------------------------------- include/notifier.php | 7 ---- 7 files changed, 29 insertions(+), 178 deletions(-) diff --git a/database.sql b/database.sql index 02e5c9b378..89b821e23a 100644 --- a/database.sql +++ b/database.sql @@ -901,13 +901,11 @@ CREATE TABLE IF NOT EXISTS `session` ( CREATE TABLE IF NOT EXISTS `sign` ( `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`) + INDEX `iid` (`iid`) ) DEFAULT CHARSET=utf8; -- diff --git a/doc/database/db_sign.md b/doc/database/db_sign.md index 8de59ac675..6986613e59 100644 --- a/doc/database/db_sign.md +++ b/doc/database/db_sign.md @@ -5,7 +5,6 @@ Table sign | ------------ | ------------- | ---------------- | ---- | --- | ------- | --------------- | | id | sequential ID | int(10) unsigned | NO | PRI | NULL | auto_increment | | iid | item.id | int(10) unsigned | NO | MUL | 0 | | -| retract_iid | | int(10) unsigned | NO | MUL | 0 | | | signed_text | | mediumtext | NO | | NULL | | | signature | | text | NO | | NULL | | | signer | | varchar(255) | NO | | | | diff --git a/include/dbstructure.php b/include/dbstructure.php index e5e748bb24..e34e409023 100644 --- a/include/dbstructure.php +++ b/include/dbstructure.php @@ -1235,7 +1235,6 @@ function db_definition() { "fields" => array( "id" => array("type" => "int(10) unsigned", "not null" => "1", "extra" => "auto_increment", "primary" => "1"), "iid" => array("type" => "int(10) unsigned", "not null" => "1", "default" => "0"), - "retract_iid" => array("type" => "int(10) unsigned", "not null" => "1", "default" => "0"), "signed_text" => array("type" => "mediumtext", "not null" => "1"), "signature" => array("type" => "text", "not null" => "1"), "signer" => array("type" => "varchar(255)", "not null" => "1", "default" => ""), @@ -1243,7 +1242,6 @@ function db_definition() { "indexes" => array( "PRIMARY" => array("id"), "iid" => array("iid"), - "retract_iid" => array("retract_iid"), ) ); $database["spam"] = array( diff --git a/include/diaspora.php b/include/diaspora.php index 870466497d..c888959d71 100644 --- a/include/diaspora.php +++ b/include/diaspora.php @@ -12,8 +12,8 @@ * - send mail * - send status retraction * - send comment retraction on own post - * - send comment retraction on diaspora post * - send like retraction on own post + * - send comment retraction on diaspora post * - send like retraction on diaspora post * - receive status * - receive reshare @@ -30,10 +30,10 @@ * - relay comment retraction from friendica * - relay like retraction from diaspora * - relay like retraction from friendica + * - send share * * Should work: * - receive account deletion - * - send share * - send unshare * * Unchecked: @@ -610,15 +610,21 @@ class diaspora { return $r; } - public static function handle_from_contact($contact_id) { + public static function handle_from_contact($contact_id, $gcontact_id = 0) { $handle = False; - logger("contact id is ".$contact_id, LOGGER_DEBUG); + logger("contact id is ".$contact_id." - gcontact id is ".$gcontact_id, LOGGER_DEBUG); + + if ($gcontact_id != 0) { + $r = q("SELECT `addr` FROM `gcontact` WHERE `id` = %d AND `addr` != ''", + intval($gcontact_id)); + if ($r) + return $r[0]["addr"]; + } $r = q("SELECT `network`, `addr`, `self`, `url`, `nick` FROM `contact` WHERE `id` = %d", - intval($contact_id) - ); - if($r) { + intval($contact_id)); + if ($r) { $contact = $r[0]; logger("contact 'self' = ".$contact['self']." 'url' = ".$contact['url'], LOGGER_DEBUG); @@ -1759,16 +1765,6 @@ class diaspora { // Now check if the retraction needs to be relayed by us if($p[0]["origin"]) { - - // Formerly we stored the signed text, the signature and the author in different fields. - // We now store the raw data so that we are more flexible. - q("INSERT INTO `sign` (`retract_iid`,`signed_text`) VALUES (%d,'%s')", - intval($r[0]["id"]), - dbesc(json_encode($data)) - ); - $s = q("select * from sign where retract_iid = %d", intval($r[0]["id"])); - logger("Stored signatur for item ".$r[0]["id"]." - ".print_r($s, true), LOGGER_DEBUG); - // notify others proc_run("php", "include/notifier.php", "drop", $r[0]["id"]); } @@ -2380,28 +2376,21 @@ class diaspora { public static function send_relay($item, $owner, $contact, $public_batch = false) { - if ($item["deleted"]) { - $sql_sign_id = "retract_iid"; - $type = "relayable_retraction"; - } elseif ($item['verb'] === ACTIVITY_LIKE) { - $sql_sign_id = "iid"; + if ($item["deleted"]) + return self::send_retraction($item, $owner, $contact, $public_batch, true); + elseif ($item['verb'] === ACTIVITY_LIKE) $type = "like"; - } else { - $sql_sign_id = "iid"; + else $type = "comment"; - } logger("Got relayable data ".$type." for item ".$item["guid"]." (".$item["id"].")", LOGGER_DEBUG); // fetch the original signature - $r = q("SELECT `signed_text`, `signature`, `signer` FROM `sign` WHERE `".$sql_sign_id."` = %d LIMIT 1", + $r = q("SELECT `signed_text`, `signature`, `signer` FROM `sign` WHERE `iid` = %d LIMIT 1", intval($item["id"])); if (!$r) { - if ($item["deleted"]) - return self::send_retraction($item, $owner, $contact, $public_batch); - logger("Couldn't fetch signatur for item ".$item["guid"]." (".$item["id"].")", LOGGER_DEBUG); return false; } @@ -2431,23 +2420,17 @@ class diaspora { logger("Signature text for item ".$item["guid"]." (".$item["id"].") couldn't be extracted: ".$signature['signed_text'], LOGGER_DEBUG); } - if ($item["deleted"]) { - $signed_text = $message["target_guid"].';'.$message["target_type"]; - $message["parent_author_signature"] = base64_encode(rsa_sign($signed_text, $owner["uprvkey"], "sha256")); - } else - $message["parent_author_signature"] = self::signature($owner, $message); + $message["parent_author_signature"] = self::signature($owner, $message); logger("Relayed data ".print_r($message, true), LOGGER_DEBUG); return self::build_and_transmit($owner, $contact, $type, $message, $public_batch, $item["guid"]); } - public static function send_retraction($item, $owner, $contact, $public_batch = false) { + public static function send_retraction($item, $owner, $contact, $public_batch = false, $relay = false) { - /// @todo Fetch handle from every contact (via gcontact) - $itemaddr = self::handle_from_contact($item["contact-id"]); - - $myaddr = self::my_handle($owner); + $itemaddr = self::handle_from_contact($item["contact-id"], $item["gcontact-id"]); + //$myaddr = self::my_handle($owner); // Check whether the retraction is for a top-level post or whether it's a relayable if ($item["uri"] !== $item["parent-uri"]) { @@ -2458,17 +2441,17 @@ class diaspora { $target_type = "StatusMessage"; } + if ($relay AND ($item["uri"] !== $item["parent-uri"])) + $signature = "parent_author_signature"; + else + $signature = "target_author_signature"; + $signed_text = $item["guid"].";".$target_type; $message = array("target_guid" => $item['guid'], "target_type" => $target_type, "sender_handle" => $itemaddr, - "target_author_signature" => base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256'))); - - if ($itemaddr != $myaddr) { - $message["parent_author_signature"] = $message["target_author_signature"]; - unset($message["target_author_signature"]); - } + $signature => base64_encode(rsa_sign($signed_text,$owner['uprvkey'],'sha256'))); logger("Got message ".print_r($message, true), LOGGER_DEBUG); diff --git a/include/items.php b/include/items.php index 8d6b5b471c..f8c3149d58 100644 --- a/include/items.php +++ b/include/items.php @@ -1980,9 +1980,6 @@ function drop_item($id,$interactive = true) { intval($r[0]['id']) ); } - - // Add a relayable_retraction signature for Diaspora. - store_diaspora_retract_sig($item, $a->user, $a->get_baseurl()); } $drop_id = intval($item['id']); @@ -2115,51 +2112,3 @@ function posted_date_widget($url,$uid,$wall) { )); return $o; } - -function store_diaspora_retract_sig($item, $user, $baseurl) { - // Note that we can't add a target_author_signature - // if the comment was deleted by a remote user. That should be ok, because if a remote user is deleting - // the comment, that means we're the home of the post, and Diaspora will only - // check the parent_author_signature of retractions that it doesn't have to relay further - // - // I don't think this function gets called for an "unlike," but I'll check anyway - - $enabled = intval(get_config('system','diaspora_enabled')); - if(! $enabled) { - logger('drop_item: diaspora support disabled, not storing retraction signature', LOGGER_DEBUG); - return; - } - - logger('drop_item: storing diaspora retraction signature'); - - $signed_text = $item['guid'] . ';' . ( ($item['verb'] === ACTIVITY_LIKE) ? 'Like' : 'Comment'); - - if(local_user() == $item['uid']) { - - $handle = $user['nickname'] . '@' . substr($baseurl, strpos($baseurl,'://') + 3); - $authorsig = base64_encode(rsa_sign($signed_text,$user['prvkey'],'sha256')); - } - else { - $r = q("SELECT `nick`, `url` FROM `contact` WHERE `id` = '%d' LIMIT 1", - $item['contact-id'] // If this function gets called, drop_item() has already checked remote_user() == $item['contact-id'] - ); - if(count($r)) { - // The below handle only works for NETWORK_DFRN. I think that's ok, because this function - // only handles DFRN deletes - $handle_baseurl_start = strpos($r['url'],'://') + 3; - $handle_baseurl_length = strpos($r['url'],'/profile') - $handle_baseurl_start; - $handle = $r['nick'] . '@' . substr($r['url'], $handle_baseurl_start, $handle_baseurl_length); - $authorsig = ''; - } - } - - if(isset($handle)) - q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ", - intval($item['id']), - dbesc($signed_text), - dbesc($authorsig), - dbesc($handle) - ); - - return; -} diff --git a/include/like.php b/include/like.php index 646e0727be..2e5367e51e 100644 --- a/include/like.php +++ b/include/like.php @@ -151,9 +151,6 @@ function do_like($item_id, $verb) { intval($like_item['id']) ); - // Save the author information for the unlike in case we need to relay to Diaspora - store_diaspora_like_retract_sig($activity, $item, $like_item, $contact); - $like_item_id = $like_item['id']; proc_run('php',"include/notifier.php","like","$like_item_id"); @@ -251,72 +248,6 @@ EOT; return true; } - - -function store_diaspora_like_retract_sig($activity, $item, $like_item, $contact) { - // Note that we can only create a signature for a user of the local server. We don't have - // a key for remote users. That is ok, because if a remote user is "unlike"ing a post, it - // means we are the relay, and for relayable_retractions, Diaspora - // only checks the parent_author_signature if it doesn't have to relay further - // - // If $item['resource-id'] exists, it means the item is a photo. Diaspora doesn't support - // likes on photos, so don't bother. - - $enabled = intval(get_config('system','diaspora_enabled')); - if(! $enabled) { - logger('mod_like: diaspora support disabled, not storing like retraction signature', LOGGER_DEBUG); - return; - } - - logger('mod_like: storing diaspora like retraction signature'); - - if(($activity === ACTIVITY_LIKE) && (! $item['resource-id'])) { - $signed_text = $like_item['guid'] . ';' . 'Like'; - - // Only works for NETWORK_DFRN - $contact_baseurl_start = strpos($contact['url'],'://') + 3; - $contact_baseurl_length = strpos($contact['url'],'/profile') - $contact_baseurl_start; - $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']) - ); - - if( $r) { - $contact_uid = $r['uid']; - $r = q("SELECT prvkey FROM user WHERE uid = %d LIMIT 1", - intval($contact_uid) - ); -*/ - // 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) - $authorsig = base64_encode(rsa_sign($signed_text,$r[0]['prvkey'],'sha256')); - } - - if(! isset($authorsig)) - $authorsig = ''; - - q("insert into sign (`retract_iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ", - intval($like_item['id']), - dbesc($signed_text), - dbesc($authorsig), - dbesc($diaspora_handle) - ); - } - - return; -} - function store_diaspora_like_sig($activity, $post_type, $contact, $post_id) { // Note that we can only create a signature for a user of the local server. We don't have // a key for remote users. That is ok, because if a remote user is "unlike"ing a post, it diff --git a/include/notifier.php b/include/notifier.php index e65da3adf2..a46744f070 100644 --- a/include/notifier.php +++ b/include/notifier.php @@ -628,13 +628,6 @@ function notifier_run(&$argv, &$argc){ proc_run('php','include/pubsubpublish.php'); } - // If the item was deleted, clean up the `sign` table - /* if($target_item['deleted']) { - $r = q("DELETE FROM sign where `retract_iid` = %d", - intval($target_item['id']) - ); - } */ - logger('notifier: calling hooks', LOGGER_DEBUG); if($normal_mode) From c7d8102afe4fd306bf30d3f59dd2780dd5bad432 Mon Sep 17 00:00:00 2001 From: Tobias Diekershoff Date: Thu, 17 Mar 2016 17:41:06 +0100 Subject: [PATCH 058/211] added title element to emoji images in main repository --- include/text.php | 153 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 153 insertions(+) diff --git a/include/text.php b/include/text.php index 956344d63d..e2d2e05d37 100644 --- a/include/text.php +++ b/include/text.php @@ -1080,6 +1080,159 @@ function get_mood_verbs() { return $arr; } + +if(! function_exists('smilies')) { +/** + * Replaces text emoticons with graphical images + * + * It is expected that this function will be called using HTML text. + * We will escape text between HTML pre and code blocks from being + * processed. + * + * At a higher level, the bbcode [nosmile] tag can be used to prevent this + * function from being executed by the prepare_text() routine when preparing + * bbcode source for HTML display + * + * @param string $s + * @param boolean $sample + * @return string + * @hook smilie ('texts' => smilies texts array, 'icons' => smilies html array, 'string' => $s) + */ +function smilies($s, $sample = false) { + $a = get_app(); + + if(intval(get_config('system','no_smilies')) + || (local_user() && intval(get_pconfig(local_user(),'system','no_smilies')))) + return $s; + + $s = preg_replace_callback('/
      (.*?)<\/pre>/ism','smile_encode',$s);
      +	$s = preg_replace_callback('/(.*?)<\/code>/ism','smile_encode',$s);
      +
      +	$texts =  array(
      +		'<3',
      +		'</3',
      +		'<\\3',
      +		':-)',
      +		';-)',
      +		':-(',
      +		':-P',
      +		':-p',
      +		':-"',
      +		':-"',
      +		':-x',
      +		':-X',
      +		':-D',
      +		'8-|',
      +		'8-O',
      +		':-O',
      +		'\\o/',
      +		'o.O',
      +		'O.o',
      +		'o_O',
      +		'O_o',
      +		":'(",
      +		":-!",
      +		":-/",
      +		":-[",
      +		"8-)",
      +		':beer',
      +		':homebrew',
      +		':coffee',
      +		':facepalm',
      +		':like',
      +		':dislike',
      +		'~friendica',
      +		'red#',
      +		'red#matrix'
      +
      +	);
      +
      +	$icons = array(
      +		'<3',
      +		'</3',
      +		'<\\3',
      +		':-)',
      +		';-)',
      +		':-(',
      +		':-P',
      +		':-p',
      +		':-\',
      +		':-\',
      +		':-x',
      +		':-X',
      +		':-D',
      +		'8-|',
      +		'8-O',
      +		':-O',
      +		'\\o/',
      +		'o.O',
      +		'O.o',
      +		'o_O',
      +		'O_o',
      +		':\'(',
      +		':-!',
      +		':-/',
      +		':-[',
      +		'8-)',
      +		':beer',
      +		':homebrew',
      +		':coffee',
      +		':facepalm',
      +		':like',
      +		':dislike',
      +		'~friendica ~friendica',
      +		'redred#matrix',
      +		'redred#matrixmatrix'
      +	);
      +
      +	$params = array('texts' => $texts, 'icons' => $icons, 'string' => $s);
      +	call_hooks('smilie', $params);
      +
      +	if($sample) {
      +		$s = '
      '; + for($x = 0; $x < count($params['texts']); $x ++) { + $s .= '
      ' . $params['texts'][$x] . '
      ' . $params['icons'][$x] . '
      '; + } + } + else { + $params['string'] = preg_replace_callback('/<(3+)/','preg_heart',$params['string']); + $s = str_replace($params['texts'],$params['icons'],$params['string']); + } + + $s = preg_replace_callback('/
      (.*?)<\/pre>/ism','smile_decode',$s);
      +	$s = preg_replace_callback('/(.*?)<\/code>/ism','smile_decode',$s);
      +
      +	return $s;
      +
      +}}
      +
      +function smile_encode($m) {
      +	return(str_replace($m[1],base64url_encode($m[1]),$m[0]));
      +}
      +
      +function smile_decode($m) {
      +	return(str_replace($m[1],base64url_decode($m[1]),$m[0]));
      +}
      +
      +
      +/**
      + * expand <3333 to the correct number of hearts
      + *
      + * @param string $x
      + * @return string
      + */
      +function preg_heart($x) {
      +	$a = get_app();
      +	if(strlen($x[1]) == 1)
      +		return $x[0];
      +	$t = '';
      +	for($cnt = 0; $cnt < strlen($x[1]); $cnt ++)
      +		$t .= '<3';
      +	$r =  str_replace($x[0],$t,$x[0]);
      +	return $r;
      +}
      +
      +
       if(! function_exists('day_translate')) {
       /**
        * Translate days and months names
      
      From f282748c52f609158ab54123abb77713616baf4e Mon Sep 17 00:00:00 2001
      From: Michael Vogel 
      Date: Thu, 17 Mar 2016 23:44:18 +0100
      Subject: [PATCH 059/211] Added some documentation, script to generate a basic
       doxygen header
      
      ---
       include/diaspora.php   | 479 ++++++++++++++++++++++++++++++++++++++++-
       util/createdoxygen.php |  82 +++++++
       2 files changed, 552 insertions(+), 9 deletions(-)
       create mode 100755 util/createdoxygen.php
      
      diff --git a/include/diaspora.php b/include/diaspora.php
      index c888959d71..dd0efa1d73 100644
      --- a/include/diaspora.php
      +++ b/include/diaspora.php
      @@ -56,6 +56,13 @@ require_once("include/queue_fn.php");
        */
       class diaspora {
       
      +	/**
      +	 * @brief Return a list of relay servers
      +	 *
      +	 * This is an experimental Diaspora feature.
      +	 *
      +	 * @return array of relay servers
      +	 */
       	public static function relay_list() {
       
       		$serverdata = get_config("system", "relay_server");
      @@ -100,6 +107,15 @@ class diaspora {
       		return $relay;
       	}
       
      +	/**
      +	 * @brief repairs a signature that was double encoded
      +	 *
      +	 * @param string $signature The signature
      +	 * @param string $handle The handle of the signature owner
      +	 * @param integer $level This value is only set inside this function to avoid endless loops
      +	 *
      +	 * @return the repaired signature
      +	 */
       	function repair_signature($signature, $handle = "", $level = 1) {
       
       		if ($signature == "")
      @@ -120,7 +136,7 @@ class diaspora {
       	/**
       	 * @brief: Decodes incoming Diaspora message
       	 *
      -	 * @param array $importer from user table
      +	 * @param array $importer Array of the importer user
       	 * @param string $xml urldecoded Diaspora salmon
       	 *
       	 * @return array
      @@ -610,6 +626,14 @@ class diaspora {
       		return $r;
       	}
       
      +	/**
      +	 * @brief get a handle (user@domain.tld) from a given contact id or gcontact id
      +	 *
      +	 * @param int $contact_id The id in the contact table
      +	 * @param int $gcontact_id The id in the gcontact table
      +	 *
      +	 * @return string the handle
      +	 */
       	public static function handle_from_contact($contact_id, $gcontact_id = 0) {
       		$handle = False;
       
      @@ -642,6 +666,14 @@ class diaspora {
       		return $handle;
       	}
       
      +	/**
      +	 * @brief Get a contact id for a given handle
      +	 *
      +	 * @param int $uid The user id
      +	 * @param string $handle The handle in the format user@domain.tld
      +	 *
      +	 * @return The contact id
      +	 */
       	private function contact_by_handle($uid, $handle) {
       		$r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `addr` = '%s' LIMIT 1",
       			intval($uid),
      @@ -664,6 +696,15 @@ class diaspora {
       		return false;
       	}
       
      +	/**
      +	 * @brief Check if posting is allowed for this contact
      +	 *
      +	 * @param array $importer Array of the importer user
      +	 * @param array $contact The contact that is checked
      +	 * @param bool $is_comment Is the check for a comment?
      +	 *
      +	 * @return bool is the contact allowed to post?
      +	 */
       	private function post_allow($importer, $contact, $is_comment = false) {
       
       		// perhaps we were already sharing with this person. Now they're sharing with us.
      @@ -694,6 +735,15 @@ class diaspora {
       		return false;
       	}
       
      +	/**
      +	 * @brief Fetches the contact id for a handle and checks if posting is allowed
      +	 *
      +	 * @param array $importer Array of the importer user
      +	 * @param string $handle The checked handle in the format user@domain.tld
      +	 * @param bool $is_comment Is the check for a comment?
      +	 *
      +	 * @return bool is posting allowed?
      +	 */
       	private function allowed_contact_by_handle($importer, $handle, $is_comment = false) {
       		$contact = self::contact_by_handle($importer["uid"], $handle);
       		if (!$contact) {
      @@ -708,6 +758,14 @@ class diaspora {
       		return $contact;
       	}
       
      +	/**
      +	 * @brief Does the message already exists on the system?
      +	 *
      +	 * @param int $uid The user id
      +	 * @param string $guid The guid of the message
      +	 *
      +	 * @return bool "true" if the message already was stored into the system
      +	 */
       	private function message_exists($uid, $guid) {
       		$r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1",
       			intval($uid),
      @@ -722,6 +780,11 @@ class diaspora {
       		return false;
       	}
       
      +	/**
      +	 * @brief Checks for links to posts in a message
      +	 *
      +	 * @param array $item The item array
      +	 */
       	private function fetch_guid($item) {
       		preg_replace_callback("&\[url=/posts/([^\[\]]*)\](.*)\[\/url\]&Usi",
       			function ($match) use ($item){
      @@ -729,11 +792,26 @@ class diaspora {
       			},$item["body"]);
       	}
       
      +	/**
      +	 * @brief sub function of "fetch_guid"
      +	 *
      +	 * @param array $match array containing a link that has to be checked for a message link
      +	 * @param array $item The item array
      +	 */
       	private function fetch_guid_sub($match, $item) {
       		if (!self::store_by_guid($match[1], $item["author-link"]))
       			self::store_by_guid($match[1], $item["owner-link"]);
       	}
       
      +	/**
      +	 * @brief Fetches an item with a given guid from a given server
      +	 *
      +	 * @param string $guid the message guid
      +	 * @param string $server The server address
      +	 * @param int $uid The user id of the user
      +	 *
      +	 * @return int the message id of the stored message or false
      +	 */
       	private function store_by_guid($guid, $server, $uid = 0) {
       		$serverparts = parse_url($server);
       		$server = $serverparts["scheme"]."://".$serverparts["host"];
      @@ -751,6 +829,15 @@ class diaspora {
       		return self::dispatch_public($msg);
       	}
       
      +	/**
      +	 * @brief 
      +	 *
      +	 * @param string $guid message guid
      +	 * @param $server
      +	 * @param $level
      +	 *
      +	 * @return 
      +	 */
       	private function message($guid, $server, $level = 0) {
       
       		if ($level > 5)
      @@ -794,6 +881,16 @@ class diaspora {
       		return $msg;
       	}
       
      +	/**
      +	 * @brief 
      +	 *
      +	 * @param int $uid The user id
      +	 * @param string $guid message guid
      +	 * @param $author
      +	 * @param array $contact The contact that is checked
      +	 *
      +	 * @return 
      +	 */
       	private function parent_item($uid, $guid, $author, $contact) {
       		$r = q("SELECT `id`, `body`, `wall`, `uri`, `private`, `origin`,
       				`author-name`, `author-link`, `author-avatar`,
      @@ -829,6 +926,15 @@ class diaspora {
       		}
       	}
       
      +	/**
      +	 * @brief 
      +	 *
      +	 * @param array $contact The contact that is checked
      +	 * @param $person
      +	 * @param int $uid The user id
      +	 *
      +	 * @return 
      +	 */
       	private function author_contact_by_url($contact, $person, $uid) {
       
       		$r = q("SELECT `id`, `network` FROM `contact` WHERE `nurl` = '%s' AND `uid` = %d LIMIT 1",
      @@ -844,10 +950,25 @@ class diaspora {
       		return (array("cid" => $cid, "network" => $network));
       	}
       
      +	/**
      +	 * @brief Is the profile a hubzilla profile?
      +	 *
      +	 * @param string $url The profile link
      +	 *
      +	 * @return bool is it a hubzilla server?
      +	 */
       	public static function is_redmatrix($url) {
       		return(strstr($url, "/channel/"));
       	}
       
      +	/**
      +	 * @brief Generate a post link with a given handle and message guid
      +	 *
      +	 * @param $addr
      +	 * @param string $guid message guid
      +	 *
      +	 * @return string the post link
      +	 */
       	private function plink($addr, $guid) {
       		$r = q("SELECT `url`, `nick`, `network` FROM `fcontact` WHERE `addr`='%s' LIMIT 1", dbesc($addr));
       
      @@ -870,6 +991,14 @@ class diaspora {
       		return "https://".substr($addr,strpos($addr,"@")+1)."/posts/".$guid;
       	}
       
      +	/**
      +	 * @brief 
      +	 *
      +	 * @param array $importer Array of the importer user
      +	 * @param object $data The message object
      +	 *
      +	 * @return 
      +	 */
       	private function receive_account_deletion($importer, $data) {
       		$author = notags(unxmlify($data->author));
       
      @@ -884,6 +1013,16 @@ class diaspora {
       		return true;
       	}
       
      +	/**
      +	 * @brief 
      +	 *
      +	 * @param array $importer Array of the importer user
      +	 * @param string $sender The sender of the message
      +	 * @param object $data The message object
      +	 * @param string $xml The original XML of the message
      +	 *
      +	 * @return int The message id of the generated comment or "false" if there was an error
      +	 */
       	private function receive_comment($importer, $sender, $data, $xml) {
       		$guid = notags(unxmlify($data->guid));
       		$parent_guid = notags(unxmlify($data->parent_guid));
      @@ -961,6 +1100,18 @@ class diaspora {
       		return $message_id;
       	}
       
      +	/**
      +	 * @brief 
      +	 *
      +	 * @param array $importer Array of the importer user
      +	 * @param array $contact The contact that is checked
      +	 * @param object $data The message object
      +	 * @param $msg
      +	 * @param $mesg
      +	 * @param $conversation
      +	 *
      +	 * @return 
      +	 */
       	private function receive_conversation_message($importer, $contact, $data, $msg, $mesg, $conversation) {
       		$guid = notags(unxmlify($data->guid));
       		$subject = notags(unxmlify($data->subject));
      @@ -1077,6 +1228,15 @@ class diaspora {
       		));
       	}
       
      +	/**
      +	 * @brief 
      +	 *
      +	 * @param array $importer Array of the importer user
      +	 * @param $msg
      +	 * @param object $data The message object
      +	 *
      +	 * @return 
      +	 */
       	private function receive_conversation($importer, $msg, $data) {
       		$guid = notags(unxmlify($data->guid));
       		$subject = notags(unxmlify($data->subject));
      @@ -1134,6 +1294,15 @@ class diaspora {
       		return true;
       	}
       
      +	/**
      +	 * @brief 
      +	 *
      +	 * @param array $contact The contact that is checked
      +	 * @param $parent_item
      +	 * @param string $guid message guid
      +	 *
      +	 * @return 
      +	 */
       	private function construct_like_body($contact, $parent_item, $guid) {
       		$bodyverb = t('%1$s likes %2$s\'s %3$s');
       
      @@ -1144,6 +1313,14 @@ class diaspora {
       		return sprintf($bodyverb, $ulink, $alink, $plink);
       	}
       
      +	/**
      +	 * @brief 
      +	 *
      +	 * @param array $importer Array of the importer user
      +	 * @param $parent_item
      +	 *
      +	 * @return 
      +	 */
       	private function construct_like_object($importer, $parent_item) {
       		$objtype = ACTIVITY_OBJ_NOTE;
       		$link = '';
      @@ -1159,6 +1336,15 @@ class diaspora {
       		return xml::from_array($xmldata, $xml, true);
       	}
       
      +	/**
      +	 * @brief 
      +	 *
      +	 * @param array $importer Array of the importer user
      +	 * @param string $sender The sender of the message
      +	 * @param object $data The message object
      +	 *
      +	 * @return int The message id of the generated like or "false" if there was an error
      +	 */
       	private function receive_like($importer, $sender, $data) {
       		$positive = notags(unxmlify($data->positive));
       		$guid = notags(unxmlify($data->guid));
      @@ -1247,6 +1433,14 @@ class diaspora {
       		return $message_id;
       	}
       
      +	/**
      +	 * @brief 
      +	 *
      +	 * @param array $importer Array of the importer user
      +	 * @param object $data The message object
      +	 *
      +	 * @return 
      +	 */
       	private function receive_message($importer, $data) {
       		$guid = notags(unxmlify($data->guid));
       		$parent_guid = notags(unxmlify($data->parent_guid));
      @@ -1318,27 +1512,59 @@ class diaspora {
       		return true;
       	}
       
      +	/**
      +	 * @brief 
      +	 *
      +	 * @param array $importer Array of the importer user
      +	 * @param object $data The message object
      +	 *
      +	 * @return bool always true
      +	 */
       	private function receive_participation($importer, $data) {
       		// I'm not sure if we can fully support this message type
       		return true;
       	}
       
      +	/**
      +	 * @brief 
      +	 *
      +	 * @param array $importer Array of the importer user
      +	 * @param object $data The message object
      +	 *
      +	 * @return 
      +	 */
       	private function receive_photo($importer, $data) {
       		// There doesn't seem to be a reason for this function, since the photo data is transmitted in the status message as well
       		return true;
       	}
       
      +	/**
      +	 * @brief 
      +	 *
      +	 * @param array $importer Array of the importer user
      +	 * @param object $data The message object
      +	 *
      +	 * @return 
      +	 */
       	private function receive_poll_participation($importer, $data) {
       		// We don't support polls by now
       		return true;
       	}
       
      +	/**
      +	 * @brief 
      +	 *
      +	 * @param array $importer Array of the importer user
      +	 * @param object $data The message object
      +	 *
      +	 * @return 
      +	 */
       	private function receive_profile($importer, $data) {
       		$author = notags(unxmlify($data->author));
       
       		$contact = self::contact_by_handle($importer["uid"], $author);
       		if (!$contact)
      -			return;
      +			return false;
       
       		$name = unxmlify($data->first_name).((strlen($data->last_name)) ? " ".unxmlify($data->last_name) : "");
       		$image_url = unxmlify($data->image_url);
      @@ -1418,6 +1644,14 @@ class diaspora {
       		return true;
       	}
       
      +	/**
      +	 * @brief 
      +	 *
      +	 * @param array $importer Array of the importer user
      +	 * @param array $contact The contact that is checked
      +	 *
      +	 * @return 
      +	 */
       	private function receive_request_make_friend($importer, $contact) {
       
       		$a = get_app();
      @@ -1485,6 +1719,14 @@ class diaspora {
       		}
       	}
       
      +	/**
      +	 * @brief 
      +	 *
      +	 * @param array $importer Array of the importer user
      +	 * @param object $data The message object
      +	 *
      +	 * @return 
      +	 */
       	private function receive_request($importer, $data) {
       		$author = unxmlify($data->author);
       		$recipient = unxmlify($data->recipient);
      @@ -1598,6 +1840,15 @@ class diaspora {
       		return true;
       	}
       
      +	/**
      +	 * @brief 
      +	 *
      +	 * @param string $guid message guid
      +	 * @param $orig_author
      +	 * @param $author
      +	 *
      +	 * @return 
      +	 */
       	private function original_item($guid, $orig_author, $author) {
       
       		// Do we already have this item?
      @@ -1654,6 +1905,15 @@ class diaspora {
       		return false;
       	}
       
      +	/**
      +	 * @brief 
      +	 *
      +	 * @param array $importer Array of the importer user
      +	 * @param object $data The message object
      +	 * @param string $xml The original XML of the message
      +	 *
      +	 * @return 
      +	 */
       	private function receive_reshare($importer, $data, $xml) {
       		$root_author = notags(unxmlify($data->root_author));
       		$root_guid = notags(unxmlify($data->root_guid));
      @@ -1719,6 +1979,15 @@ class diaspora {
       		return $message_id;
       	}
       
      +	/**
      +	 * @brief 
      +	 *
      +	 * @param array $importer Array of the importer user
      +	 * @param array $contact The contact that is checked
      +	 * @param object $data The message object
      +	 *
      +	 * @return 
      +	 */
       	private function item_retraction($importer, $contact, $data) {
       		$target_type = notags(unxmlify($data->target_type));
       		$target_guid = notags(unxmlify($data->target_guid));
      @@ -1770,6 +2039,15 @@ class diaspora {
       		}
       	}
       
      +	/**
      +	 * @brief 
      +	 *
      +	 * @param array $importer Array of the importer user
      +	 * @param string $sender The sender of the message
      +	 * @param object $data The message object
      +	 *
      +	 * @return 
      +	 */
       	private function receive_retraction($importer, $sender, $data) {
       		$target_type = notags(unxmlify($data->target_type));
       
      @@ -1802,6 +2080,15 @@ class diaspora {
       		return true;
       	}
       
      +	/**
      +	 * @brief 
      +	 *
      +	 * @param array $importer Array of the importer user
      +	 * @param object $data The message object
      +	 * @param string $xml The original XML of the message
      +	 *
      +	 * @return 
      +	 */
       	private function receive_status_message($importer, $data, $xml) {
       
       		$raw_message = unxmlify($data->raw_message);
      @@ -1895,6 +2182,13 @@ class diaspora {
       	 * Here are all the functions that are needed to transmit data with the Diaspora protocol *
       	 ******************************************************************************************/
       
      +	/**
      +	 * @brief 
      +	 *
      +	 * @param $me
      +	 *
      +	 * @return 
      +	 */
       	private function my_handle($me) {
       		if ($contact["addr"] != "")
       			return $contact["addr"];
      @@ -1904,6 +2198,17 @@ class diaspora {
       		return $me["nickname"]."@".substr(App::get_baseurl(), strpos(App::get_baseurl(),"://") + 3);
       	}
       
      +	/**
      +	 * @brief 
      +	 *
      +	 * @param $msg
      +	 * @param $user
      +	 * @param array $contact The contact that is checked
      +	 * @param $prvkey
      +	 * @param $pubkey
      +	 *
      +	 * @return 
      +	 */
       	private function build_public_message($msg, $user, $contact, $prvkey, $pubkey) {
       
       		logger("Message: ".$msg, LOGGER_DATA);
      @@ -1939,6 +2244,17 @@ class diaspora {
       		return $magic_env;
       	}
       
      +	/**
      +	 * @brief 
      +	 *
      +	 * @param $msg
      +	 * @param $user
      +	 * @param array $contact The contact that is checked
      +	 * @param $prvkey
      +	 * @param $pubkey
      +	 *
      +	 * @return 
      +	 */
       	private function build_private_message($msg, $user, $contact, $prvkey, $pubkey) {
       
       		logger("Message: ".$msg, LOGGER_DATA);
      @@ -2018,6 +2334,18 @@ class diaspora {
       		return $magic_env;
       	}
       
      +	/**
      +	 * @brief 
      +	 *
      +	 * @param $msg
      +	 * @param $user
      +	 * @param array $contact The contact that is checked
      +	 * @param $prvkey
      +	 * @param $pubkey
      +	 * @param $public
      +	 *
      +	 * @return 
      +	 */
       	private function build_message($msg, $user, $contact, $prvkey, $pubkey, $public = false) {
       
       		if ($public)
      @@ -2030,6 +2358,14 @@ class diaspora {
       		return $slap;
       	}
       
      +	/**
      +	 * @brief 
      +	 *
      +	 * @param array $owner the array of the item owner
      +	 * @param $message
      +	 *
      +	 * @return 
      +	 */
       	private function signature($owner, $message) {
       		$sigmsg = $message;
       		unset($sigmsg["author_signature"]);
      @@ -2040,6 +2376,18 @@ class diaspora {
       		return base64_encode(rsa_sign($signed_text, $owner["uprvkey"], "sha256"));
       	}
       
      +	/**
      +	 * @brief 
      +	 *
      +	 * @param array $owner the array of the item owner
      +	 * @param array $contact The contact that is checked
      +	 * @param $slap
      +	 * @param bool $public_batch Is it a public post?
      +	 * @param $queue_run
      +	 * @param string $guid message guid
      +	 *
      +	 * @return 
      +	 */
       	public static function transmit($owner, $contact, $slap, $public_batch, $queue_run=false, $guid = "") {
       
       		$a = get_app();
      @@ -2092,6 +2440,19 @@ class diaspora {
       	}
       
       
      +	/**
      +	 * @brief 
      +	 *
      +	 * @param array $owner the array of the item owner
      +	 * @param array $contact The contact that is checked
      +	 * @param $type
      +	 * @param $message
      +	 * @param bool $public_batch Is it a public post?
      +	 * @param string $guid message guid
      +	 * @param $spool
      +	 *
      +	 * @return 
      +	 */
       	private function build_and_transmit($owner, $contact, $type, $message, $public_batch = false, $guid = "", $spool = false) {
       
       		$data = array("XML" => array("post" => array($type => $message)));
      @@ -2114,6 +2475,14 @@ class diaspora {
       		return $return_code;
       	}
       
      +	/**
      +	 * @brief 
      +	 *
      +	 * @param array $owner the array of the item owner
      +	 * @param array $contact The contact that is checked
      +	 *
      +	 * @return int The result of the transmission
      +	 */
       	public static function send_share($owner,$contact) {
       
       		$message = array("sender_handle" => self::my_handle($owner),
      @@ -2122,6 +2491,14 @@ class diaspora {
       		return self::build_and_transmit($owner, $contact, "request", $message);
       	}
       
      +	/**
      +	 * @brief 
      +	 *
      +	 * @param array $owner the array of the item owner
      +	 * @param array $contact The contact that is checked
      +	 *
      +	 * @return int The result of the transmission
      +	 */
       	public static function send_unshare($owner,$contact) {
       
       		$message = array("post_guid" => $owner["guid"],
      @@ -2131,6 +2508,14 @@ class diaspora {
       		return self::build_and_transmit($owner, $contact, "retraction", $message);
       	}
       
      +	/**
      +	 * @brief 
      +	 *
      +	 * @param $body
      +	 * @param $complete
      +	 *
      +	 * @return 
      +	 */
       	public static function is_reshare($body, $complete = true) {
       		$body = trim($body);
       
      @@ -2202,6 +2587,16 @@ class diaspora {
       		return($ret);
       	}
       
      +	/**
      +	 * @brief 
      +	 *
      +	 * @param array $item The item that will be exported
      +	 * @param array $owner the array of the item owner
      +	 * @param array $contact The contact that is checked
      +	 * @param bool $public_batch Is it a public post?
      +	 *
      +	 * @return int The result of the transmission
      +	 */
       	public static function send_status($item, $owner, $contact, $public_batch = false) {
       
       		$myaddr = self::my_handle($owner);
      @@ -2269,10 +2664,16 @@ class diaspora {
       		return self::build_and_transmit($owner, $contact, $type, $message, $public_batch, $item["guid"]);
       	}
       
      +	/**
      +	 * @brief 
      +	 *
      +	 * @param array $item The item that will be exported
      +	 * @param array $owner the array of the item owner
      +	 *
      +	 * @return 
      +	 */
       	private function construct_like($item, $owner) {
       
      -		$myaddr = self::my_handle($owner);
      -
       		$p = q("SELECT `guid`, `uri`, `parent-uri` FROM `item` WHERE `uri` = '%s' LIMIT 1",
       			dbesc($item["thr-parent"]));
       		if(!$p)
      @@ -2288,13 +2689,19 @@ class diaspora {
       				"target_type" => $target_type,
       				"parent_guid" => $parent["guid"],
       				"author_signature" => $authorsig,
      -				"diaspora_handle" => $myaddr));
      +				"diaspora_handle" => self::my_handle($owner)));
       	}
       
      +	/**
      +	 * @brief 
      +	 *
      +	 * @param array $item The item that will be exported
      +	 * @param array $owner the array of the item owner
      +	 *
      +	 * @return 
      +	 */
       	private function construct_comment($item, $owner) {
       
      -		$myaddr = self::my_handle($owner);
      -
       		$p = q("SELECT `guid` FROM `item` WHERE `parent` = %d AND `id` = %d LIMIT 1",
       			intval($item["parent"]),
       			intval($item["parent"])
      @@ -2311,9 +2718,19 @@ class diaspora {
       				"parent_guid" => $parent["guid"],
       				"author_signature" => "",
       				"text" => $text,
      -				"diaspora_handle" => $myaddr));
      +				"diaspora_handle" => self::my_handle($owner)));
       	}
       
      +	/**
      +	 * @brief Send a like or a comment
      +	 *
      +	 * @param array $item The item that will be exported
      +	 * @param array $owner the array of the item owner
      +	 * @param array $contact The contact that is checked
      +	 * @param bool $public_batch Is it a public post?
      +	 *
      +	 * @return int The result of the transmission
      +	 */
       	public static function send_followup($item,$owner,$contact,$public_batch = false) {
       
       		if($item['verb'] === ACTIVITY_LIKE) {
      @@ -2332,6 +2749,14 @@ class diaspora {
       		return self::build_and_transmit($owner, $contact, $type, $message, $public_batch, $item["guid"]);
       	}
       
      +	/**
      +	 * @brief 
      +	 *
      +	 * @param array $item The item that will be exported
      +	 * @param $signature
      +	 *
      +	 * @return int The result of the transmission
      +	 */
       	private function message_from_signatur($item, $signature) {
       
       		// Split the signed text
      @@ -2374,6 +2799,16 @@ class diaspora {
       		return $message;
       	}
       
      +	/**
      +	 * @brief 
      +	 *
      +	 * @param array $item The item that will be exported
      +	 * @param array $owner the array of the item owner
      +	 * @param array $contact The contact that is checked
      +	 * @param bool $public_batch Is it a public post?
      +	 *
      +	 * @return int The result of the transmission
      +	 */
       	public static function send_relay($item, $owner, $contact, $public_batch = false) {
       
       		if ($item["deleted"])
      @@ -2427,10 +2862,20 @@ class diaspora {
       		return self::build_and_transmit($owner, $contact, $type, $message, $public_batch, $item["guid"]);
       	}
       
      +	/**
      +	 * @brief 
      +	 *
      +	 * @param array $item The item that will be exported
      +	 * @param array $owner the array of the item owner
      +	 * @param array $contact The contact that is checked
      +	 * @param bool $public_batch Is it a public post?
      +	 * @param $relay
      +	 *
      +	 * @return int The result of the transmission
      +	 */
       	public static function send_retraction($item, $owner, $contact, $public_batch = false, $relay = false) {
       
       		$itemaddr = self::handle_from_contact($item["contact-id"], $item["gcontact-id"]);
      -		//$myaddr = self::my_handle($owner);
       
       		// Check whether the retraction is for a top-level post or whether it's a relayable
       		if ($item["uri"] !== $item["parent-uri"]) {
      @@ -2458,6 +2903,15 @@ class diaspora {
       		return self::build_and_transmit($owner, $contact, $msg_type, $message, $public_batch, $item["guid"]);
       	}
       
      +	/**
      +	 * @brief 
      +	 *
      +	 * @param array $item The item that will be exported
      +	 * @param array $owner The owner
      +	 * @param array $contact The contact that is checked
      +	 *
      +	 * @return int The result of the transmission
      +	 */
       	public static function send_mail($item, $owner, $contact) {
       
       		$myaddr = self::my_handle($owner);
      @@ -2515,6 +2969,13 @@ class diaspora {
       		return self::build_and_transmit($owner, $contact, $type, $message, false, $item["guid"]);
       	}
       
      +	/**
      +	 * @brief 
      +	 *
      +	 * @param int $uid The user id
      +	 *
      +	 * @return int The result of the transmission
      +	 */
       	public static function send_profile($uid) {
       
       		if (!$uid)
      diff --git a/util/createdoxygen.php b/util/createdoxygen.php
      new file mode 100755
      index 0000000000..d48114b671
      --- /dev/null
      +++ b/util/createdoxygen.php
      @@ -0,0 +1,82 @@
      +#!/usr/bin/php
      + 0)
      +			$block .= $space." *\n";
      +	}
      +
      +	$block .= $space." * @return \n".
      +		$space." */\n";
      +
      +	return $block;
      +}
      +?>
      
      From 4aec4f5bd49266bdc4d73de7b1faa3309e734ec5 Mon Sep 17 00:00:00 2001
      From: Michael Vogel 
      Date: Fri, 18 Mar 2016 08:07:23 +0100
      Subject: [PATCH 060/211] Just some more documentation
      
      ---
       include/diaspora.php | 149 ++++++++++++++++++++++---------------------
       1 file changed, 75 insertions(+), 74 deletions(-)
      
      diff --git a/include/diaspora.php b/include/diaspora.php
      index dd0efa1d73..14ff6e42f8 100644
      --- a/include/diaspora.php
      +++ b/include/diaspora.php
      @@ -830,13 +830,13 @@ class diaspora {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Fetches a message from a server
       	 *
       	 * @param string $guid message guid
      -	 * @param $server
      -	 * @param $level
      +	 * @param string $server The url of the server
      +	 * @param int $level Endless loop prevention
       	 *
      -	 * @return 
      +	 * @return array of message, author and public key
       	 */
       	private function message($guid, $server, $level = 0) {
       
      @@ -882,14 +882,14 @@ class diaspora {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Fetches the item record of a given guid
       	 *
       	 * @param int $uid The user id
       	 * @param string $guid message guid
      -	 * @param $author
      +	 * @param string $author The handle of the item
       	 * @param array $contact The contact that is checked
       	 *
      -	 * @return 
      +	 * @return array the item record
       	 */
       	private function parent_item($uid, $guid, $author, $contact) {
       		$r = q("SELECT `id`, `body`, `wall`, `uri`, `private`, `origin`,
      @@ -927,13 +927,13 @@ class diaspora {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief returns contact details
       	 *
      -	 * @param array $contact The contact that is checked
      -	 * @param $person
      +	 * @param array $contact The default contact if the person isn't found
      +	 * @param array $person The record of the person
       	 * @param int $uid The user id
       	 *
      -	 * @return 
      +	 * @return array of contact id and network type
       	 */
       	private function author_contact_by_url($contact, $person, $uid) {
       
      @@ -964,7 +964,7 @@ class diaspora {
       	/**
       	 * @brief Generate a post link with a given handle and message guid
       	 *
      -	 * @param $addr
      +	 * @param string $addr The user handle
       	 * @param string $guid message guid
       	 *
       	 * @return string the post link
      @@ -1101,16 +1101,16 @@ class diaspora {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief processes and stores private messages
       	 *
       	 * @param array $importer Array of the importer user
       	 * @param array $contact The contact that is checked
       	 * @param object $data The message object
      -	 * @param $msg
      -	 * @param $mesg
      -	 * @param $conversation
      +	 * @param array $msg Array of the processed message, author handle and key
      +	 * @param object $mesg The private message
      +	 * @param array $conversation The conversation record to which this message belongs
       	 *
      -	 * @return 
      +	 * @return bool "true" if it was successful
       	 */
       	private function receive_conversation_message($importer, $contact, $data, $msg, $mesg, $conversation) {
       		$guid = notags(unxmlify($data->guid));
      @@ -1226,13 +1226,14 @@ class diaspora {
       			"verb" => ACTIVITY_POST,
       			"otype" => "mail"
       		));
      +		return true;
       	}
       
       	/**
       	 * @brief 
       	 *
       	 * @param array $importer Array of the importer user
      -	 * @param $msg
      +	 * @param array $msg Array of the processed message, author handle and key
       	 * @param object $data The message object
       	 *
       	 * @return 
      @@ -2183,11 +2184,11 @@ class diaspora {
       	 ******************************************************************************************/
       
       	/**
      -	 * @brief 
      +	 * @brief returnes the handle of a contact
       	 *
      -	 * @param $me
      +	 * @param array $me contact array
       	 *
      -	 * @return 
      +	 * @return string the handle in the format user@domain.tld
       	 */
       	private function my_handle($me) {
       		if ($contact["addr"] != "")
      @@ -2199,15 +2200,15 @@ class diaspora {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Creates the envelope for a public message
       	 *
      -	 * @param $msg
      -	 * @param $user
      -	 * @param array $contact The contact that is checked
      -	 * @param $prvkey
      -	 * @param $pubkey
      +	 * @param string $msg The message that is to be transmitted
      +	 * @param array $user The record of the sender
      +	 * @param array $contact Target of the communication
      +	 * @param string $prvkey The private key of the sender
      +	 * @param string $pubkey The public key of the receiver
       	 *
      -	 * @return 
      +	 * @return string The envelope
       	 */
       	private function build_public_message($msg, $user, $contact, $prvkey, $pubkey) {
       
      @@ -2245,15 +2246,15 @@ class diaspora {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Creates the envelope for a private message
       	 *
      -	 * @param $msg
      -	 * @param $user
      -	 * @param array $contact The contact that is checked
      -	 * @param $prvkey
      -	 * @param $pubkey
      +	 * @param string $msg The message that is to be transmitted
      +	 * @param array $user The record of the sender
      +	 * @param array $contact Target of the communication
      +	 * @param string $prvkey The private key of the sender
      +	 * @param string $pubkey The public key of the receiver
       	 *
      -	 * @return 
      +	 * @return string The envelope
       	 */
       	private function build_private_message($msg, $user, $contact, $prvkey, $pubkey) {
       
      @@ -2335,14 +2336,14 @@ class diaspora {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Create the envelope for a message
       	 *
      -	 * @param $msg
      -	 * @param $user
      -	 * @param array $contact The contact that is checked
      -	 * @param $prvkey
      -	 * @param $pubkey
      -	 * @param $public
      +	 * @param string $msg The message that is to be transmitted
      +	 * @param array $user The record of the sender
      +	 * @param array $contact Target of the communication
      +	 * @param string $prvkey The private key of the sender
      +	 * @param string $pubkey The public key of the receiver
      +	 * @param bool $public Is the message public?
       	 *
       	 * @return 
       	 */
      @@ -2359,12 +2360,12 @@ class diaspora {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Creates a signature for a message
       	 *
      -	 * @param array $owner the array of the item owner
      -	 * @param $message
      +	 * @param array $owner the array of the owner of the message
      +	 * @param array $message The message that is to be signed
       	 *
      -	 * @return 
      +	 * @return string The signature
       	 */
       	private function signature($owner, $message) {
       		$sigmsg = $message;
      @@ -2377,16 +2378,16 @@ class diaspora {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Transmit a message to a target server
       	 *
       	 * @param array $owner the array of the item owner
      -	 * @param array $contact The contact that is checked
      -	 * @param $slap
      +	 * @param array $contact Target of the communication
      +	 * @param string $slap The message that is to be transmitted
       	 * @param bool $public_batch Is it a public post?
      -	 * @param $queue_run
      +	 * @param bool $queue_run Is the transmission called from the queue?
       	 * @param string $guid message guid
       	 *
      -	 * @return 
      +	 * @return int Result of the transmission
       	 */
       	public static function transmit($owner, $contact, $slap, $public_batch, $queue_run=false, $guid = "") {
       
      @@ -2444,14 +2445,14 @@ class diaspora {
       	 * @brief 
       	 *
       	 * @param array $owner the array of the item owner
      -	 * @param array $contact The contact that is checked
      -	 * @param $type
      -	 * @param $message
      +	 * @param array $contact Target of the communication
      +	 * @param string $type The message type
      +	 * @param array $message The message data
       	 * @param bool $public_batch Is it a public post?
       	 * @param string $guid message guid
      -	 * @param $spool
      +	 * @param bool $spool Should the transmission be spooled or transmitted?
       	 *
      -	 * @return 
      +	 * @return int Result of the transmission
       	 */
       	private function build_and_transmit($owner, $contact, $type, $message, $public_batch = false, $guid = "", $spool = false) {
       
      @@ -2479,7 +2480,7 @@ class diaspora {
       	 * @brief 
       	 *
       	 * @param array $owner the array of the item owner
      -	 * @param array $contact The contact that is checked
      +	 * @param array $contact Target of the communication
       	 *
       	 * @return int The result of the transmission
       	 */
      @@ -2495,7 +2496,7 @@ class diaspora {
       	 * @brief 
       	 *
       	 * @param array $owner the array of the item owner
      -	 * @param array $contact The contact that is checked
      +	 * @param array $contact Target of the communication
       	 *
       	 * @return int The result of the transmission
       	 */
      @@ -2509,12 +2510,12 @@ class diaspora {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Checks a message body if it is a reshare
       	 *
      -	 * @param $body
      -	 * @param $complete
      +	 * @param string $body The message body that is to be check
      +	 * @param bool $complete Should it be a complete check or a simple check?
       	 *
      -	 * @return 
      +	 * @return array|bool Reshare details or "false" if no reshare
       	 */
       	public static function is_reshare($body, $complete = true) {
       		$body = trim($body);
      @@ -2592,7 +2593,7 @@ class diaspora {
       	 *
       	 * @param array $item The item that will be exported
       	 * @param array $owner the array of the item owner
      -	 * @param array $contact The contact that is checked
      +	 * @param array $contact Target of the communication
       	 * @param bool $public_batch Is it a public post?
       	 *
       	 * @return int The result of the transmission
      @@ -2726,7 +2727,7 @@ class diaspora {
       	 *
       	 * @param array $item The item that will be exported
       	 * @param array $owner the array of the item owner
      -	 * @param array $contact The contact that is checked
      +	 * @param array $contact Target of the communication
       	 * @param bool $public_batch Is it a public post?
       	 *
       	 * @return int The result of the transmission
      @@ -2750,14 +2751,14 @@ class diaspora {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Creates a message from a signature record entry
       	 *
       	 * @param array $item The item that will be exported
      -	 * @param $signature
      +	 * @param array $signature The entry of the "sign" record
       	 *
      -	 * @return int The result of the transmission
      +	 * @return string The message
       	 */
      -	private function message_from_signatur($item, $signature) {
      +	private function message_from_signature($item, $signature) {
       
       		// Split the signed text
       		$signed_parts = explode(";", $signature['signed_text']);
      @@ -2804,7 +2805,7 @@ class diaspora {
       	 *
       	 * @param array $item The item that will be exported
       	 * @param array $owner the array of the item owner
      -	 * @param array $contact The contact that is checked
      +	 * @param array $contact Target of the communication
       	 * @param bool $public_batch Is it a public post?
       	 *
       	 * @return int The result of the transmission
      @@ -2835,7 +2836,7 @@ class diaspora {
       		// Old way - is used by the internal Friendica functions
       		/// @todo Change all signatur storing functions to the new format
       		if ($signature['signed_text'] AND $signature['signature'] AND $signature['signer'])
      -			$message = self::message_from_signatur($item, $signature);
      +			$message = self::message_from_signature($item, $signature);
       		else {// New way
       			$msg = json_decode($signature['signed_text'], true);
       
      @@ -2863,13 +2864,13 @@ class diaspora {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Sends a retraction (deletion) of a message, like or comment
       	 *
       	 * @param array $item The item that will be exported
       	 * @param array $owner the array of the item owner
      -	 * @param array $contact The contact that is checked
      +	 * @param array $contact Target of the communication
       	 * @param bool $public_batch Is it a public post?
      -	 * @param $relay
      +	 * @param bool $relay Is the retraction transmitted from a relay?
       	 *
       	 * @return int The result of the transmission
       	 */
      @@ -2908,7 +2909,7 @@ class diaspora {
       	 *
       	 * @param array $item The item that will be exported
       	 * @param array $owner The owner
      -	 * @param array $contact The contact that is checked
      +	 * @param array $contact Target of the communication
       	 *
       	 * @return int The result of the transmission
       	 */
      
      From 99bf63dbdf492892bc2f29c2526de75436742c7c Mon Sep 17 00:00:00 2001
      From: Michael Vogel 
      Date: Fri, 18 Mar 2016 16:42:10 +0100
      Subject: [PATCH 061/211] There is no table "sess_data" - this database call is
       useless
      
      ---
       include/session.php | 2 +-
       1 file changed, 1 insertion(+), 1 deletion(-)
      
      diff --git a/include/session.php b/include/session.php
      index 11641d6cea..12551efc42 100644
      --- a/include/session.php
      +++ b/include/session.php
      @@ -69,7 +69,7 @@ function ref_session_destroy ($id) {
       if(! function_exists('ref_session_gc')) {
       function ref_session_gc($expire) {
       	q("DELETE FROM `session` WHERE `expire` < %d", dbesc(time()));
      -	q("OPTIMIZE TABLE `sess_data`");
      +	//q("OPTIMIZE TABLE `sess_data`");
       	return true;
       }}
       
      
      From f5ac69a0cf71096a62131aa7de4f3068063eb666 Mon Sep 17 00:00:00 2001
      From: Michael Vogel 
      Date: Fri, 18 Mar 2016 22:28:20 +0100
      Subject: [PATCH 062/211] The documentation should now be complete
      
      ---
       include/diaspora.php | 118 +++++++++++++++++++++----------------------
       1 file changed, 58 insertions(+), 60 deletions(-)
      
      diff --git a/include/diaspora.php b/include/diaspora.php
      index 14ff6e42f8..4e1b300507 100644
      --- a/include/diaspora.php
      +++ b/include/diaspora.php
      @@ -110,6 +110,8 @@ class diaspora {
       	/**
       	 * @brief repairs a signature that was double encoded
       	 *
      +	 * The function is unused at the moment. It was copied from the old implementation.
      +	 *
       	 * @param string $signature The signature
       	 * @param string $handle The handle of the signature owner
       	 * @param integer $level This value is only set inside this function to avoid endless loops
      @@ -887,7 +889,7 @@ class diaspora {
       	 * @param int $uid The user id
       	 * @param string $guid message guid
       	 * @param string $author The handle of the item
      -	 * @param array $contact The contact that is checked
      +	 * @param array $contact The contact of the item owner
       	 *
       	 * @return array the item record
       	 */
      @@ -992,12 +994,12 @@ class diaspora {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Processes an account deletion
       	 *
       	 * @param array $importer Array of the importer user
       	 * @param object $data The message object
       	 *
      -	 * @return 
      +	 * @return bool Success
       	 */
       	private function receive_account_deletion($importer, $data) {
       		$author = notags(unxmlify($data->author));
      @@ -1014,7 +1016,7 @@ class diaspora {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Processes an incoming comment
       	 *
       	 * @param array $importer Array of the importer user
       	 * @param string $sender The sender of the message
      @@ -1104,7 +1106,7 @@ class diaspora {
       	 * @brief processes and stores private messages
       	 *
       	 * @param array $importer Array of the importer user
      -	 * @param array $contact The contact that is checked
      +	 * @param array $contact The contact of the message
       	 * @param object $data The message object
       	 * @param array $msg Array of the processed message, author handle and key
       	 * @param object $mesg The private message
      @@ -1230,13 +1232,13 @@ class diaspora {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Processes new private messages (answers to private messages are processed elsewhere)
       	 *
       	 * @param array $importer Array of the importer user
       	 * @param array $msg Array of the processed message, author handle and key
       	 * @param object $data The message object
       	 *
      -	 * @return 
      +	 * @return bool Success
       	 */
       	private function receive_conversation($importer, $msg, $data) {
       		$guid = notags(unxmlify($data->guid));
      @@ -1296,13 +1298,13 @@ class diaspora {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Creates the body for a "like" message
       	 *
      -	 * @param array $contact The contact that is checked
      -	 * @param $parent_item
      +	 * @param array $contact The contact that send us the "like"
      +	 * @param array $parent_item The item array of the parent item
       	 * @param string $guid message guid
       	 *
      -	 * @return 
      +	 * @return string the body
       	 */
       	private function construct_like_body($contact, $parent_item, $guid) {
       		$bodyverb = t('%1$s likes %2$s\'s %3$s');
      @@ -1315,12 +1317,12 @@ class diaspora {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Creates a XML object for a "like"
       	 *
       	 * @param array $importer Array of the importer user
      -	 * @param $parent_item
      +	 * @param array $parent_item The item array of the parent item
       	 *
      -	 * @return 
      +	 * @return string The XML
       	 */
       	private function construct_like_object($importer, $parent_item) {
       		$objtype = ACTIVITY_OBJ_NOTE;
      @@ -1338,7 +1340,7 @@ class diaspora {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Processes "like" messages
       	 *
       	 * @param array $importer Array of the importer user
       	 * @param string $sender The sender of the message
      @@ -1435,12 +1437,12 @@ class diaspora {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Processes private messages
       	 *
       	 * @param array $importer Array of the importer user
       	 * @param object $data The message object
       	 *
      -	 * @return 
      +	 * @return bool Success?
       	 */
       	private function receive_message($importer, $data) {
       		$guid = notags(unxmlify($data->guid));
      @@ -1514,7 +1516,7 @@ class diaspora {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Processes participations - unsupported by now
       	 *
       	 * @param array $importer Array of the importer user
       	 * @param object $data The message object
      @@ -1527,12 +1529,12 @@ class diaspora {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Processes photos - unneeded
       	 *
       	 * @param array $importer Array of the importer user
       	 * @param object $data The message object
       	 *
      -	 * @return 
      +	 * @return bool always true
       	 */
       	private function receive_photo($importer, $data) {
       		// There doesn't seem to be a reason for this function, since the photo data is transmitted in the status message as well
      @@ -1540,12 +1542,12 @@ class diaspora {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Processes poll participations - unssupported
       	 *
       	 * @param array $importer Array of the importer user
       	 * @param object $data The message object
       	 *
      -	 * @return 
      +	 * @return bool always true
       	 */
       	private function receive_poll_participation($importer, $data) {
       		// We don't support polls by now
      @@ -1553,12 +1555,12 @@ class diaspora {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Processes incoming profile updates
       	 *
       	 * @param array $importer Array of the importer user
       	 * @param object $data The message object
       	 *
      -	 * @return 
      +	 * @return bool Success
       	 */
       	private function receive_profile($importer, $data) {
       		$author = notags(unxmlify($data->author));
      @@ -1646,12 +1648,10 @@ class diaspora {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Processes incoming friend requests
       	 *
       	 * @param array $importer Array of the importer user
      -	 * @param array $contact The contact that is checked
      -	 *
      -	 * @return 
      +	 * @param array $contact The contact that send the request
       	 */
       	private function receive_request_make_friend($importer, $contact) {
       
      @@ -1714,26 +1714,24 @@ class diaspora {
       				$i = item_store($arr);
       				if($i)
       					proc_run("php", "include/notifier.php", "activity", $i);
      -
       			}
      -
       		}
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Processes incoming sharing notification
       	 *
       	 * @param array $importer Array of the importer user
       	 * @param object $data The message object
       	 *
      -	 * @return 
      +	 * @return bool Success
       	 */
       	private function receive_request($importer, $data) {
       		$author = unxmlify($data->author);
       		$recipient = unxmlify($data->recipient);
       
       		if (!$author || !$recipient)
      -			return;
      +			return false;
       
       		$contact = self::contact_by_handle($importer["uid"],$author);
       
      @@ -1842,13 +1840,13 @@ class diaspora {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Fetches a message with a given guid
       	 *
       	 * @param string $guid message guid
      -	 * @param $orig_author
      -	 * @param $author
      +	 * @param string $orig_author handle of the original post
      +	 * @param string $author handle of the sharer
       	 *
      -	 * @return 
      +	 * @return array The fetched item
       	 */
       	private function original_item($guid, $orig_author, $author) {
       
      @@ -1907,13 +1905,13 @@ class diaspora {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Processes a reshare message
       	 *
       	 * @param array $importer Array of the importer user
       	 * @param object $data The message object
       	 * @param string $xml The original XML of the message
       	 *
      -	 * @return 
      +	 * @return int the message id
       	 */
       	private function receive_reshare($importer, $data, $xml) {
       		$root_author = notags(unxmlify($data->root_author));
      @@ -1981,13 +1979,13 @@ class diaspora {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Processes retractions
       	 *
       	 * @param array $importer Array of the importer user
      -	 * @param array $contact The contact that is checked
      +	 * @param array $contact The contact of the item owner
       	 * @param object $data The message object
       	 *
      -	 * @return 
      +	 * @return bool success
       	 */
       	private function item_retraction($importer, $contact, $data) {
       		$target_type = notags(unxmlify($data->target_type));
      @@ -2038,16 +2036,18 @@ class diaspora {
       			// notify others
       			proc_run("php", "include/notifier.php", "drop", $r[0]["id"]);
       		}
      +
      +		return true;
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Receives retraction messages
       	 *
       	 * @param array $importer Array of the importer user
       	 * @param string $sender The sender of the message
       	 * @param object $data The message object
       	 *
      -	 * @return 
      +	 * @return bool Success
       	 */
       	private function receive_retraction($importer, $sender, $data) {
       		$target_type = notags(unxmlify($data->target_type));
      @@ -2082,13 +2082,13 @@ class diaspora {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Receives status messages
       	 *
       	 * @param array $importer Array of the importer user
       	 * @param object $data The message object
       	 * @param string $xml The original XML of the message
       	 *
      -	 * @return 
      +	 * @return int The message id of the newly created item
       	 */
       	private function receive_status_message($importer, $data, $xml) {
       
      @@ -2345,7 +2345,7 @@ class diaspora {
       	 * @param string $pubkey The public key of the receiver
       	 * @param bool $public Is the message public?
       	 *
      -	 * @return 
      +	 * @return string The message that will be transmitted to other servers
       	 */
       	private function build_message($msg, $user, $contact, $prvkey, $pubkey, $public = false) {
       
      @@ -2442,7 +2442,7 @@ class diaspora {
       
       
       	/**
      -	 * @brief 
      +	 * @brief Builds and transmit messages
       	 *
       	 * @param array $owner the array of the item owner
       	 * @param array $contact Target of the communication
      @@ -2477,7 +2477,7 @@ class diaspora {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Sends a "share" message
       	 *
       	 * @param array $owner the array of the item owner
       	 * @param array $contact Target of the communication
      @@ -2493,7 +2493,7 @@ class diaspora {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief sends an "unshare"
       	 *
       	 * @param array $owner the array of the item owner
       	 * @param array $contact Target of the communication
      @@ -2589,7 +2589,7 @@ class diaspora {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Sends a post
       	 *
       	 * @param array $item The item that will be exported
       	 * @param array $owner the array of the item owner
      @@ -2666,12 +2666,12 @@ class diaspora {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Creates a "like" object
       	 *
       	 * @param array $item The item that will be exported
       	 * @param array $owner the array of the item owner
       	 *
      -	 * @return 
      +	 * @return array The data for a "like"
       	 */
       	private function construct_like($item, $owner) {
       
      @@ -2694,12 +2694,12 @@ class diaspora {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Creates the object for a comment
       	 *
       	 * @param array $item The item that will be exported
       	 * @param array $owner the array of the item owner
       	 *
      -	 * @return 
      +	 * @return array The data for a comment
       	 */
       	private function construct_comment($item, $owner) {
       
      @@ -2801,7 +2801,7 @@ class diaspora {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Relays messages (like, comment, retraction) to other servers if we are the thread owner
       	 *
       	 * @param array $item The item that will be exported
       	 * @param array $owner the array of the item owner
      @@ -2905,7 +2905,7 @@ class diaspora {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Sends a mail
       	 *
       	 * @param array $item The item that will be exported
       	 * @param array $owner The owner
      @@ -2971,11 +2971,9 @@ class diaspora {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Sends profile data
       	 *
       	 * @param int $uid The user id
      -	 *
      -	 * @return int The result of the transmission
       	 */
       	public static function send_profile($uid) {
       
      
      From 8d82bf922e88183bb8f14f41cfbb7712989d22c3 Mon Sep 17 00:00:00 2001
      From: Michael Vogel 
      Date: Sat, 19 Mar 2016 15:49:47 +0100
      Subject: [PATCH 063/211] The signature creation now moved into the Diaspora
       class. That's much cleaner.
      
      ---
       include/diaspora.php | 124 +++++++++++++++++++++++++++++++++++++++++--
       include/like.php     |  83 +----------------------------
       mod/item.php         |  42 +--------------
       3 files changed, 125 insertions(+), 124 deletions(-)
      
      diff --git a/include/diaspora.php b/include/diaspora.php
      index 4e1b300507..59bad946e2 100644
      --- a/include/diaspora.php
      +++ b/include/diaspora.php
      @@ -2190,13 +2190,18 @@ class diaspora {
       	 *
       	 * @return string the handle in the format user@domain.tld
       	 */
      -	private function my_handle($me) {
      +	private function my_handle($contact) {
       		if ($contact["addr"] != "")
       			return $contact["addr"];
       
       		// Normally we should have a filled "addr" field - but in the past this wasn't the case
       		// So - just in case - we build the the address here.
      -		return $me["nickname"]."@".substr(App::get_baseurl(), strpos(App::get_baseurl(),"://") + 3);
      +		if ($contact["nickname"] != "")
      +			$nick = $contact["nickname"];
      +		else
      +			$nick = $contact["nick"];
      +
      +		return $nick."@".substr(App::get_baseurl(), strpos(App::get_baseurl(),"://") + 3);
       	}
       
       	/**
      @@ -2689,7 +2694,7 @@ class diaspora {
       				"guid" => $item["guid"],
       				"target_type" => $target_type,
       				"parent_guid" => $parent["guid"],
      -				"author_signature" => $authorsig,
      +				"author_signature" => "",
       				"diaspora_handle" => self::my_handle($owner)));
       	}
       
      @@ -3052,5 +3057,118 @@ class diaspora {
       		foreach($recips as $recip)
       			self::build_and_transmit($profile, $recip, "profile", $message, false, "", true);
       	}
      +
      +	/**
      +	 * @brief Stores the signature for likes that are created on our system
      +	 *
      +	 * @param array $contact The contact array of the "like"
      +	 * @param int $post_id The post id of the "like"
      +	 *
      +	 * @return bool Success
      +	 */
      +	function store_like_signature($contact, $post_id) {
      +
      +		$enabled = intval(get_config('system','diaspora_enabled'));
      +		if (!$enabled) {
      +			logger('Diaspora support disabled, not storing like signature', LOGGER_DEBUG);
      +			return false;
      +		}
      +
      +		// Is the contact the owner? Then fetch the private key
      +		if (!$contact['self'] OR ($contact['uid'] == 0)) {
      +			logger("No owner post, so not storing signature", LOGGER_DEBUG);
      +			return false;
      +		}
      +
      +		$r = q("SELECT `prvkey` FROM `user` WHERE `uid` = %d LIMIT 1", intval($contact['uid']));
      +		if(!$r)
      +			return false;
      +
      +		$contact["uprvkey"] = $r[0]['prvkey'];
      +
      +		$r = q("SELECT * FROM `item` WHERE `id` = %d LIMIT 1", intval($post_id));
      +		if (!$r)
      +			return false;
      +
      +		if (!in_array($r[0]["verb"], array(ACTIVITY_LIKE, ACTIVITY_DISLIKE)))
      +			return false;
      +
      +		$message = self::construct_like($r[0], $contact);
      +		$message["author_signature"] = self::signature($contact, $message);
      +
      +		// In the future we will store the signature more flexible to support new fields.
      +		// Right now we cannot change this since old Friendica versions (prior to 3.5) can only handle this format.
      +		// (We are transmitting this data here via DFRN)
      +
      +		$signed_text = $message["positive"].";".$message["guid"].";".$message["target_type"].";".
      +				$message["parent_guid"].";".$message["diaspora_handle"];
      +
      +		q("INSERT INTO `sign` (`iid`,`signed_text`,`signature`,`signer`) VALUES (%d,'%s','%s','%s')",
      +			intval($post_id),
      +			dbesc($signed_text),
      +			dbesc($message["author_signature"]),
      +			dbesc($message["diaspora_handle"])
      +		);
      +
      +		// This here will replace the lines above, once Diaspora changed its protocol
      +		//q("INSERT INTO `sign` (`iid`,`signed_text`) VALUES (%d,'%s')",
      +		//	intval($message_id),
      +		//	dbesc(json_encode($message))
      +		//);
      +
      +		logger('Stored diaspora like signature');
      +		return true;
      +	}
      +
      +	/**
      +	 * @brief Stores the signature for comments that are created on our system
      +	 *
      +	 * @param array $item The item array of the comment
      +	 * @param array $contact The contact array of the item owner
      +	 * @param string $uprvkey The private key of the sender
      +	 * @param int $message_id The message id of the comment
      +	 *
      +	 * @return bool Success
      +	 */
      +	function store_comment_signature($item, $contact, $uprvkey, $message_id) {
      +
      +		if ($uprvkey == "") {
      +			logger('No private key, so not storing comment signature', LOGGER_DEBUG);
      +			return false;
      +		}
      +
      +		$enabled = intval(get_config('system','diaspora_enabled'));
      +		if (!$enabled) {
      +			logger('Diaspora support disabled, not storing comment signature', LOGGER_DEBUG);
      +			return false;
      +		}
      +
      +		$contact["uprvkey"] = $uprvkey;
      +
      +		$message = self::construct_comment($item, $contact);
      +		$message["author_signature"] = self::signature($contact, $message);
      +
      +		// In the future we will store the signature more flexible to support new fields.
      +		// Right now we cannot change this since old Friendica versions (prior to 3.5) can only handle this format.
      +		// (We are transmitting this data here via DFRN)
      +		$signed_text = $message["guid"].";".$message["parent_guid"].";".
      +				$message["text"].";".$message["diaspora_handle"];
      +
      +		q("INSERT INTO `sign` (`iid`,`signed_text`,`signature`,`signer`) VALUES (%d,'%s','%s','%s')",
      +			intval($message_id),
      +			dbesc($signed_text),
      +			dbesc($message["author_signature"]),
      +			dbesc($message["diaspora_handle"])
      +		);
      +
      +		// This here will replace the lines above, once Diaspora changed its protocol
      +		//q("INSERT INTO `sign` (`iid`,`signed_text`) VALUES (%d,'%s')",
      +		//	intval($message_id),
      +		//	dbesc(json_encode($message))
      +		//);
      +
      +		logger('Stored diaspora comment signature');
      +		return true;
      +	}
       }
       ?>
      diff --git a/include/like.php b/include/like.php
      index 2e5367e51e..49534ea613 100644
      --- a/include/like.php
      +++ b/include/like.php
      @@ -1,4 +1,5 @@
        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)
      -		);
      -		if( $r) {
      -			$p = q("SELECT guid FROM `item` WHERE id = %d AND parent = %d LIMIT 1",
      -				intval($r[0]['parent']),
      -				intval($r[0]['parent'])
      -			);
      -			if( $p) {
      -				$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'));
      -				else
      -					$authorsig = '';
      -
      -				q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
      -					intval($post_id),
      -					dbesc($signed_text),
      -					dbesc($authorsig),
      -					dbesc($diaspora_handle)
      -				);
      -			}
      -		}
      -	}
      -
      -	return;
      -}
      diff --git a/mod/item.php b/mod/item.php
      index 2ade524a05..14c8203c98 100644
      --- a/mod/item.php
      +++ b/mod/item.php
      @@ -24,6 +24,7 @@ require_once('include/threads.php');
       require_once('include/text.php');
       require_once('include/items.php');
       require_once('include/Scrape.php');
      +require_once('include/diaspora.php');
       
       function item_post(&$a) {
       
      @@ -900,7 +901,7 @@ function item_post(&$a) {
       
       
       		// Store the comment signature information in case we need to relay to Diaspora
      -		store_diaspora_comment_sig($datarray, $author, ($self ? $user['prvkey'] : false), $parent_item, $post_id);
      +		diaspora::store_comment_signature($datarray, $author, ($self ? $user['prvkey'] : false), $post_id);
       
       	} else {
       		$parent = $post_id;
      @@ -1245,42 +1246,3 @@ function handle_tag($a, &$body, &$inform, &$str_tags, $profile_uid, $tag, $netwo
       
       	return array('replaced' => $replaced, 'contact' => $r[0]);
       }
      -
      -
      -function store_diaspora_comment_sig($datarray, $author, $uprvkey, $parent_item, $post_id) {
      -	// We won't be able to sign Diaspora comments for authenticated visitors - we don't have their private key
      -
      -	$enabled = intval(get_config('system','diaspora_enabled'));
      -	if(! $enabled) {
      -		logger('mod_item: diaspora support disabled, not storing comment signature', LOGGER_DEBUG);
      -		return;
      -	}
      -
      -
      -	logger('mod_item: storing diaspora comment signature');
      -
      -	require_once('include/bb2diaspora.php');
      -	$signed_body = html_entity_decode(bb2diaspora($datarray['body']));
      -
      -	// Only works for NETWORK_DFRN
      -	$contact_baseurl_start = strpos($author['url'],'://') + 3;
      -	$contact_baseurl_length = strpos($author['url'],'/profile') - $contact_baseurl_start;
      -	$contact_baseurl = substr($author['url'], $contact_baseurl_start, $contact_baseurl_length);
      -	$diaspora_handle = $author['nick'] . '@' . $contact_baseurl;
      -
      -	$signed_text = $datarray['guid'] . ';' . $parent_item['guid'] . ';' . $signed_body . ';' . $diaspora_handle;
      -
      -	if( $uprvkey !== false )
      -		$authorsig = rsa_sign($signed_text,$uprvkey,'sha256');
      -	else
      -		$authorsig = '';
      -
      -	q("insert into sign (`iid`,`signed_text`,`signature`,`signer`) values (%d,'%s','%s','%s') ",
      -		intval($post_id),
      -		dbesc($signed_text),
      -		dbesc(base64_encode($authorsig)),
      -		dbesc($diaspora_handle)
      -	);
      -
      -	return;
      -}
      
      From 6786da1049f738b3feaca73a24f7d9ccd8da7b7b Mon Sep 17 00:00:00 2001
      From: Michael Vogel 
      Date: Sun, 20 Mar 2016 10:30:06 +0100
      Subject: [PATCH 064/211] Take the second largest picture as preview - not the
       smallest one
      
      ---
       mod/fbrowser.php | 10 +++++++++-
       1 file changed, 9 insertions(+), 1 deletion(-)
      
      diff --git a/mod/fbrowser.php b/mod/fbrowser.php
      index 0a2a7dead5..5836efbe52 100644
      --- a/mod/fbrowser.php
      +++ b/mod/fbrowser.php
      @@ -74,10 +74,18 @@ function fbrowser_content($a){
       					$filename_e = $rr['filename'];
       				}
       
      +				// Take the second largest picture as preview
      +				$p = q("SELECT `scale` FROM `photo` WHERE `resource-id` = '%s' AND `scale` > %d ORDER BY `resource-id`, `scale` LIMIT 1",
      +					dbesc($rr['resource-id']), intval($rr['hiq']));
      +				if ($p)
      +					$scale = $p[0]["scale"];
      +				else
      +					$scale = $rr['loq'];
      +
       				return array(
       					$a->get_baseurl() . '/photos/' . $a->user['nickname'] . '/image/' . $rr['resource-id'],
       					$filename_e,
      -					$a->get_baseurl() . '/photo/' . $rr['resource-id'] . '-' . $rr['loq'] . '.'. $ext
      +					$a->get_baseurl() . '/photo/' . $rr['resource-id'] . '-' . $scale . '.'. $ext
       				);
       			}
       			$files = array_map("_map_files1", $r);
      
      From 5a90f865edefb77a76eaada7da9a5a0f4b146db8 Mon Sep 17 00:00:00 2001
      From: Michael Vogel 
      Date: Sun, 20 Mar 2016 15:01:50 +0100
      Subject: [PATCH 065/211] Add the guid to items that we create locally
      
      ---
       include/like.php     | 1 +
       mod/dfrn_confirm.php | 3 ++-
       mod/mood.php         | 2 +-
       mod/photos.php       | 5 +++--
       mod/poke.php         | 1 +
       mod/profiles.php     | 2 ++
       mod/subthread.php    | 5 +++--
       mod/tagger.php       | 5 +++--
       8 files changed, 16 insertions(+), 8 deletions(-)
      
      diff --git a/include/like.php b/include/like.php
      index 49534ea613..15633fc767 100644
      --- a/include/like.php
      +++ b/include/like.php
      @@ -194,6 +194,7 @@ EOT;
       
       	$arr = array();
       
      +	$arr['guid'] = get_guid(32);
       	$arr['uri'] = $uri;
       	$arr['uid'] = $owner_uid;
       	$arr['contact-id'] = $contact['id'];
      diff --git a/mod/dfrn_confirm.php b/mod/dfrn_confirm.php
      index cc09021dca..5e0e5c85c5 100644
      --- a/mod/dfrn_confirm.php
      +++ b/mod/dfrn_confirm.php
      @@ -448,6 +448,7 @@ function dfrn_confirm_post(&$a,$handsfree = null) {
       				if(count($self)) {
       
       					$arr = array();
      +					$arr['guid'] = get_guid(32);
       					$arr['uri'] = $arr['parent-uri'] = item_new_uri($a->get_hostname(), $uid);
       					$arr['uid'] = $uid;
       					$arr['contact-id'] = $self[0]['id'];
      @@ -466,7 +467,7 @@ function dfrn_confirm_post(&$a,$handsfree = null) {
       					$BPhoto = '[url=' . $contact['url'] . ']' . '[img]' . $contact['thumb'] . '[/img][/url]';
       
       					$arr['verb'] = ACTIVITY_FRIEND;
      -				    $arr['object-type'] = ACTIVITY_OBJ_PERSON;
      +					$arr['object-type'] = ACTIVITY_OBJ_PERSON;
       					$arr['body'] =  sprintf( t('%1$s is now friends with %2$s'), $A, $B)."\n\n\n".$BPhoto;
       
       					$arr['object'] = '' . ACTIVITY_OBJ_PERSON . '' . $contact['name'] . ''
      diff --git a/mod/mood.php b/mod/mood.php
      index eee11e20c5..5e6ca0fcfc 100644
      --- a/mod/mood.php
      +++ b/mod/mood.php
      @@ -62,7 +62,7 @@ function mood_init(&$a) {
       	$action = sprintf( t('%1$s is currently %2$s'), '[url=' . $poster['url'] . ']' . $poster['name'] . '[/url]' , $verbs[$verb]); 
       
       	$arr = array();
      -
      +	$arr['guid']          = get_guid(32);
       	$arr['uid']           = $uid;
       	$arr['uri']           = $uri;
       	$arr['parent-uri']    = (($parent_uri) ? $parent_uri : $uri);
      diff --git a/mod/photos.php b/mod/photos.php
      index 2257a96653..4761b627d8 100644
      --- a/mod/photos.php
      +++ b/mod/photos.php
      @@ -488,7 +488,7 @@ function photos_post(&$a) {
       			$uri = item_new_uri($a->get_hostname(),$page_owner_uid);
       
       			$arr = array();
      -
      +			$arr['guid']          = get_guid(32);
       			$arr['uid']           = $page_owner_uid;
       			$arr['uri']           = $uri;
       			$arr['parent-uri']    = $uri;
      @@ -677,7 +677,7 @@ function photos_post(&$a) {
       					$uri = item_new_uri($a->get_hostname(),$page_owner_uid);
       
       					$arr = array();
      -
      +					$arr['guid']          = get_guid(32);
       					$arr['uid']           = $page_owner_uid;
       					$arr['uri']           = $uri;
       					$arr['parent-uri']    = $uri;
      @@ -904,6 +904,7 @@ function photos_post(&$a) {
       	if($lat && $lon)
       		$arr['coord'] = $lat . ' ' . $lon;
       
      +	$arr['guid']          = get_guid(32);
       	$arr['uid']           = $page_owner_uid;
       	$arr['uri']           = $uri;
       	$arr['parent-uri']    = $uri;
      diff --git a/mod/poke.php b/mod/poke.php
      index 45a577cda6..4a643435be 100644
      --- a/mod/poke.php
      +++ b/mod/poke.php
      @@ -91,6 +91,7 @@ function poke_init(&$a) {
       
       	$arr = array();
       
      +	$arr['guid']          = get_guid(32);
       	$arr['uid']           = $uid;
       	$arr['uri']           = $uri;
       	$arr['parent-uri']    = (($parent_uri) ? $parent_uri : $uri);
      diff --git a/mod/profiles.php b/mod/profiles.php
      index 0b8261422f..39382fbdd5 100644
      --- a/mod/profiles.php
      +++ b/mod/profiles.php
      @@ -526,6 +526,8 @@ function profile_activity($changed, $value) {
       		return;
       
       	$arr = array();
      +
      +	$arr['guid'] = get_guid(32);
       	$arr['uri'] = $arr['parent-uri'] = item_new_uri($a->get_hostname(), local_user());
       	$arr['uid'] = local_user();
       	$arr['contact-id'] = $self[0]['id'];
      diff --git a/mod/subthread.php b/mod/subthread.php
      index 1486a33b42..33cf7489c1 100644
      --- a/mod/subthread.php
      +++ b/mod/subthread.php
      @@ -103,10 +103,11 @@ EOT;
       	$bodyverb = t('%1$s is following %2$s\'s %3$s');
       
       	if(! isset($bodyverb))
      -			return; 
      +			return;
       
       	$arr = array();
       
      +	$arr['guid'] = get_guid(32);
       	$arr['uri'] = $uri;
       	$arr['uid'] = $owner_uid;
       	$arr['contact-id'] = $contact['id'];
      @@ -123,7 +124,7 @@ EOT;
       	$arr['author-name'] = $contact['name'];
       	$arr['author-link'] = $contact['url'];
       	$arr['author-avatar'] = $contact['thumb'];
      -	
      +
       	$ulink = '[url=' . $contact['url'] . ']' . $contact['name'] . '[/url]';
       	$alink = '[url=' . $item['author-link'] . ']' . $item['author-name'] . '[/url]';
       	$plink = '[url=' . $a->get_baseurl() . '/display/' . $owner['nickname'] . '/' . $item['id'] . ']' . $post_type . '[/url]';
      diff --git a/mod/tagger.php b/mod/tagger.php
      index 2c469a58bb..26166a3cc0 100644
      --- a/mod/tagger.php
      +++ b/mod/tagger.php
      @@ -95,12 +95,13 @@ EOT;
       	$bodyverb = t('%1$s tagged %2$s\'s %3$s with %4$s');
       
       	if(! isset($bodyverb))
      -			return; 
      +			return;
       
       	$termlink = html_entity_decode('⌗') . '[url=' . $a->get_baseurl() . '/search?tag=' . urlencode($term) . ']'. $term . '[/url]';
       
       	$arr = array();
       
      +	$arr['guid'] = get_guid(32);
       	$arr['uri'] = $uri;
       	$arr['uid'] = $owner_uid;
       	$arr['contact-id'] = $contact['id'];
      @@ -115,7 +116,7 @@ EOT;
       	$arr['author-name'] = $contact['name'];
       	$arr['author-link'] = $contact['url'];
       	$arr['author-avatar'] = $contact['thumb'];
      -	
      +
       	$ulink = '[url=' . $contact['url'] . ']' . $contact['name'] . '[/url]';
       	$alink = '[url=' . $item['author-link'] . ']' . $item['author-name'] . '[/url]';
       	$plink = '[url=' . $item['plink'] . ']' . $post_type . '[/url]';
      
      From debc6a86c627a15b09a66203b5a2f1ede30244a3 Mon Sep 17 00:00:00 2001
      From: Michael Vogel 
      Date: Sun, 20 Mar 2016 15:53:37 +0100
      Subject: [PATCH 066/211] DFRN: Remote tagging works now
      
      ---
       include/dfrn.php | 31 ++++++++++++++++++-------------
       1 file changed, 18 insertions(+), 13 deletions(-)
      
      diff --git a/include/dfrn.php b/include/dfrn.php
      index 1c5ac2b012..d96805a56b 100644
      --- a/include/dfrn.php
      +++ b/include/dfrn.php
      @@ -85,7 +85,7 @@ class dfrn {
       					$converse = true;
       				if($a->argv[$x] == 'starred')
       					$starred = true;
      -				if($a->argv[$x] === 'category' && $a->argc > ($x + 1) && strlen($a->argv[$x+1]))
      +				if($a->argv[$x] == 'category' && $a->argc > ($x + 1) && strlen($a->argv[$x+1]))
       					$category = $a->argv[$x+1];
       			}
       		}
      @@ -244,7 +244,7 @@ class dfrn {
       		foreach($items as $item) {
       
       			// prevent private email from leaking.
      -			if($item['network'] === NETWORK_MAIL)
      +			if($item['network'] == NETWORK_MAIL)
       				continue;
       
       			// public feeds get html, our own nodes use bbcode
      @@ -628,7 +628,7 @@ class dfrn {
       			if($r->title)
       				xml_add_element($doc, $entry, "title", $r->title);
       			if($r->link) {
      -				if(substr($r->link,0,1) === '<') {
      +				if(substr($r->link,0,1) == '<') {
       					if(strstr($r->link,'&') && (! strstr($r->link,'&')))
       						$r->link = str_replace('&','&', $r->link);
       
      @@ -759,7 +759,7 @@ class dfrn {
       
       		// 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));
      +		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",
      @@ -1773,6 +1773,9 @@ class dfrn {
       	 * @return bool Should the processing of the entries be continued?
       	 */
       	private function process_verbs($entrytype, $importer, &$item, &$is_like) {
      +
      +		logger("Process verb ".$item["verb"]." and object-type ".$item["object-type"]." for entrytype ".$entrytype, LOGGER_DEBUG);
      +
       		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
      @@ -1803,11 +1806,11 @@ class dfrn {
       				return false;
       			}
       		} else {
      -			if(($item["verb"] === ACTIVITY_LIKE)
      -				|| ($item["verb"] === ACTIVITY_DISLIKE)
      -				|| ($item["verb"] === ACTIVITY_ATTEND)
      -				|| ($item["verb"] === ACTIVITY_ATTENDNO)
      -				|| ($item["verb"] === ACTIVITY_ATTENDMAYBE)) {
      +			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;
      @@ -1833,7 +1836,7 @@ class dfrn {
       			} else
       				$is_like = false;
       
      -			if(($item["verb"] === ACTIVITY_TAG) && ($item["object-type"] === ACTIVITY_OBJ_TAGTERM)) {
      +			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);
      @@ -2261,15 +2264,17 @@ class dfrn {
       			else
       				return;
       
      -			if($item["object-type"] === ACTIVITY_OBJ_EVENT) {
      +			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)) {
      +			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) {
      +
      +				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"])
      
      From 3981ed4cda066cd334fe653da65f50192ec24fa4 Mon Sep 17 00:00:00 2001
      From: Michael Vogel 
      Date: Sun, 20 Mar 2016 16:16:15 +0100
      Subject: [PATCH 067/211] Added documentation
      
      ---
       include/xml.php | 17 +++++++++++++++++
       1 file changed, 17 insertions(+)
      
      diff --git a/include/xml.php b/include/xml.php
      index c74c23c473..2aed3fe8ed 100644
      --- a/include/xml.php
      +++ b/include/xml.php
      @@ -4,6 +4,16 @@
        *
        */
       class xml {
      +	/**
      +	 * @brief Creates an XML structure out of a given array
      +	 *
      +	 * @param array $array The array of the XML structure that will be generated
      +	 * @param object $xml The createdXML will be returned by reference
      +	 * @param bool $remove_header Should the XML header be removed or not?
      +	 * @param array $namespaces List of namespaces
      +	 *
      +	 * @return string The created XML
      +	 */
       	function from_array($array, &$xml, $remove_header = false, $namespaces = array(), $root = true) {
       
       		if ($root) {
      @@ -60,6 +70,13 @@ class xml {
       		}
       	}
       
      +	/**
      +	 * @brief Copies an XML object
      +	 *
      +	 * @param object $source The XML source
      +	 * @param object $target The XML target
      +	 * @param string $elementname Name of the XML element of the target
      +	 */
       	function copy(&$source, &$target, $elementname) {
       		if (count($source->children()) == 0)
       			$target->addChild($elementname, xmlify($source));
      
      From eb511b3fefb94c2852f39a8cb96192feebc0ba1e Mon Sep 17 00:00:00 2001
      From: Michael Vogel 
      Date: Mon, 21 Mar 2016 19:58:45 +0100
      Subject: [PATCH 068/211] Some more documentation - to make @rabuzarus happy
      
      ---
       include/diaspora.php | 19 ++++++-------------
       1 file changed, 6 insertions(+), 13 deletions(-)
      
      diff --git a/include/diaspora.php b/include/diaspora.php
      index 59bad946e2..6f30ab9247 100644
      --- a/include/diaspora.php
      +++ b/include/diaspora.php
      @@ -116,7 +116,7 @@ class diaspora {
       	 * @param string $handle The handle of the signature owner
       	 * @param integer $level This value is only set inside this function to avoid endless loops
       	 *
      -	 * @return the repaired signature
      +	 * @return string the repaired signature
       	 */
       	function repair_signature($signature, $handle = "", $level = 1) {
       
      @@ -179,16 +179,6 @@ class diaspora {
       
       			$decrypted = pkcs5_unpad($decrypted);
       
      -			/**
      -			 * $decrypted now contains something like
      -			 *
      -			 *  
      -			 *     8e+G2+ET8l5BPuW0sVTnQw==
      -			 *     UvSMb4puPeB14STkcDWq+4QE302Edu15oaprAQSkLKU=
      -			 *     galaxor@diaspora.priateship.org
      -			 *  
      -			 */
      -
       			logger('decrypted: '.$decrypted, LOGGER_DEBUG);
       			$idom = parse_xml_string($decrypted,false);
       
      @@ -795,7 +785,7 @@ class diaspora {
       	}
       
       	/**
      -	 * @brief sub function of "fetch_guid"
      +	 * @brief sub function of "fetch_guid" which checks for links in messages
       	 *
       	 * @param array $match array containing a link that has to be checked for a message link
       	 * @param array $item The item array
      @@ -838,7 +828,10 @@ class diaspora {
       	 * @param string $server The url of the server
       	 * @param int $level Endless loop prevention
       	 *
      -	 * @return array of message, author and public key
      +	 * @return array
      +	 *      'message' => The message XML
      +	 *      'author' => The author handle
      +	 *      'key' => The public key of the author
       	 */
       	private function message($guid, $server, $level = 0) {
       
      
      From 3414328673e5594323c3b91153b998ef88631e48 Mon Sep 17 00:00:00 2001
      From: Michael Vogel 
      Date: Tue, 22 Mar 2016 07:13:56 +0100
      Subject: [PATCH 069/211] Avoid an empty handle
      
      ---
       include/diaspora.php | 2 +-
       1 file changed, 1 insertion(+), 1 deletion(-)
      
      diff --git a/include/diaspora.php b/include/diaspora.php
      index 6f30ab9247..632e3782c7 100644
      --- a/include/diaspora.php
      +++ b/include/diaspora.php
      @@ -647,7 +647,7 @@ class diaspora {
       
       			if($contact['addr'] != "")
       				$handle = $contact['addr'];
      -			elseif(($contact['network'] === NETWORK_DFRN) || ($contact['self'] == 1)) {
      +			else {
       				$baseurl_start = strpos($contact['url'],'://') + 3;
       				$baseurl_length = strpos($contact['url'],'/profile') - $baseurl_start; // allows installations in a subdirectory--not sure how Diaspora will handle
       				$baseurl = substr($contact['url'], $baseurl_start, $baseurl_length);
      
      From 0f71d0bd07b418dcc0d8537948206139955ce945 Mon Sep 17 00:00:00 2001
      From: Michael Vogel 
      Date: Tue, 22 Mar 2016 23:00:42 +0100
      Subject: [PATCH 070/211] Reshare of reshares now work.
      
      ---
       include/diaspora.php | 10 +++++++---
       1 file changed, 7 insertions(+), 3 deletions(-)
      
      diff --git a/include/diaspora.php b/include/diaspora.php
      index 632e3782c7..289f717708 100644
      --- a/include/diaspora.php
      +++ b/include/diaspora.php
      @@ -1853,10 +1853,14 @@ class diaspora {
       			logger("reshared message ".$guid." already exists on system.");
       
       			// Maybe it is already a reshared item?
      -			// Then refetch the content, since there can be many side effects with reshared posts from other networks or reshares from reshares
      -			if (self::is_reshare($r[0]["body"], false))
      +			// Then refetch the content, if it is a reshare from a reshare.
      +			// If it is a reshared post from another network then reformat to avoid display problems with two share elements
      +			if (self::is_reshare($r[0]["body"], true))
       				$r = array();
      -			else
      +			elseif (self::is_reshare($r[0]["body"], false)) {
      +				$r[0]["body"] = diaspora2bb(bb2diaspora($r[0]["body"]));
      +				return $r[0];
      +			} else
       				return $r[0];
       		}
       
      
      From ab0325b9a9f3de95df16e2baaefb45aadbaaf240 Mon Sep 17 00:00:00 2001
      From: Michael Vogel 
      Date: Tue, 22 Mar 2016 23:24:07 +0100
      Subject: [PATCH 071/211] Bugfix: Avoid warning with non object OEmbed data
      
      ---
       include/bbcode.php | 3 +++
       1 file changed, 3 insertions(+)
      
      diff --git a/include/bbcode.php b/include/bbcode.php
      index c1156e3afe..8545b2ff82 100644
      --- a/include/bbcode.php
      +++ b/include/bbcode.php
      @@ -311,6 +311,9 @@ function tryoembed($match){
       
       	$o = oembed_fetch_url($url);
       
      +	if (!is_object($o))
      +		return $match[0];
      +
       	if (isset($match[2]))
       		$o->title = $match[2];
       
      
      From a0c0881e5476b73df74fe55054a228d703c2ad52 Mon Sep 17 00:00:00 2001
      From: Michael Vogel 
      Date: Wed, 23 Mar 2016 07:36:17 +0100
      Subject: [PATCH 072/211] Add OEmbed data to the body of reshares
      
      ---
       include/diaspora.php | 4 ++++
       1 file changed, 4 insertions(+)
      
      diff --git a/include/diaspora.php b/include/diaspora.php
      index 289f717708..b339e73157 100644
      --- a/include/diaspora.php
      +++ b/include/diaspora.php
      @@ -1859,6 +1859,10 @@ class diaspora {
       				$r = array();
       			elseif (self::is_reshare($r[0]["body"], false)) {
       				$r[0]["body"] = diaspora2bb(bb2diaspora($r[0]["body"]));
      +
      +				// Add OEmbed and other information to the body
      +				$r[0]["body"] = add_page_info_to_body($r[0]["body"], false, true);
      +
       				return $r[0];
       			} else
       				return $r[0];
      
      From 2a84e7fac16b0beb8f9d94e6625b401250500b7c Mon Sep 17 00:00:00 2001
      From: Michael Vogel 
      Date: Wed, 23 Mar 2016 09:22:59 +0100
      Subject: [PATCH 073/211] If the message already exists then the message id
       should be returned
      
      ---
       include/diaspora.php | 26 +++++++++++++++-----------
       1 file changed, 15 insertions(+), 11 deletions(-)
      
      diff --git a/include/diaspora.php b/include/diaspora.php
      index b339e73157..308a799118 100644
      --- a/include/diaspora.php
      +++ b/include/diaspora.php
      @@ -755,8 +755,8 @@ class diaspora {
       	 *
       	 * @param int $uid The user id
       	 * @param string $guid The guid of the message
      -	 *
      -	 * @return bool "true" if the message already was stored into the system
      +y	 *
      +	 * @return int|bool message id if the message already was stored into the system - or false.
       	 */
       	private function message_exists($uid, $guid) {
       		$r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1",
      @@ -766,7 +766,7 @@ class diaspora {
       
       		if($r) {
       			logger("message ".$guid." already exists for user ".$uid);
      -			return true;
      +			return $r[0]["id"];
       		}
       
       		return false;
      @@ -1028,8 +1028,9 @@ class diaspora {
       		if (!$contact)
       			return false;
       
      -		if (self::message_exists($importer["uid"], $guid))
      -			return false;
      +		$message_id = self::message_exists($importer["uid"], $guid);
      +		if ($message_id)
      +			return $message_id;
       
       		$parent_item = self::parent_item($importer["uid"], $parent_guid, $author, $contact);
       		if (!$parent_item)
      @@ -1357,8 +1358,9 @@ class diaspora {
       		if (!$contact)
       			return false;
       
      -		if (self::message_exists($importer["uid"], $guid))
      -			return false;
      +		$message_id = self::message_exists($importer["uid"], $guid);
      +		if ($message_id)
      +			return $message_id;
       
       		$parent_item = self::parent_item($importer["uid"], $parent_guid, $author, $contact);
       		if (!$parent_item)
      @@ -1926,8 +1928,9 @@ class diaspora {
       		if (!$contact)
       			return false;
       
      -		if (self::message_exists($importer["uid"], $guid))
      -			return false;
      +		$message_id = self::message_exists($importer["uid"], $guid);
      +		if ($message_id)
      +			return $message_id;
       
       		$original_item = self::original_item($root_guid, $root_author, $author);
       		if (!$original_item)
      @@ -2110,8 +2113,9 @@ class diaspora {
       		if (!$contact)
       			return false;
       
      -		if (self::message_exists($importer["uid"], $guid))
      -			return false;
      +		$message_id = self::message_exists($importer["uid"], $guid);
      +		if ($message_id)
      +			return $message_id;
       
       		$address = array();
       		if ($data->location)
      
      From 0ed299a4950edf5c7c9a22cdebef1d778bcedff9 Mon Sep 17 00:00:00 2001
      From: Michael Vogel 
      Date: Wed, 23 Mar 2016 10:24:01 +0100
      Subject: [PATCH 074/211] API: Support for the conversation api call from GNU
       Social
      
      ---
       doc/api.md      | 12 ++++++++++++
       include/api.php |  1 +
       2 files changed, 13 insertions(+)
      
      diff --git a/doc/api.md b/doc/api.md
      index bf287585d3..7d6f440c58 100644
      --- a/doc/api.md
      +++ b/doc/api.md
      @@ -388,6 +388,18 @@ Friendica doesn't allow showing friends of other users.
       ---
       ### statusnet/config (*)
       
      +---
      +### statusnet/conversation (*; AUTH)
      +It shows all direct answers (excluding the original post) to a given id.
      +
      +#### Parameter
      +* id: id of the post
      +* count: Items per page (default: 20)
      +* page: page number
      +* since_id: minimal id
      +* max_id: maximum id
      +* include_entities: "true" shows entities for pictures and links (Default: false)
      +
       ---
       ### statusnet/version (*)
       
      diff --git a/include/api.php b/include/api.php
      index 699b066d25..a494e3cdd9 100644
      --- a/include/api.php
      +++ b/include/api.php
      @@ -1550,6 +1550,7 @@
       		return api_apply_template("timeline", $type, $data);
       	}
       	api_register_func('api/conversation/show','api_conversation_show', true);
      +	api_register_func('api/statusnet/conversation','api_conversation_show', true);
       
       
       	/**
      
      From df961742dabb83e586b2947ccb9d5f575925297f Mon Sep 17 00:00:00 2001
      From: Michael Vogel 
      Date: Wed, 23 Mar 2016 22:12:08 +0100
      Subject: [PATCH 075/211] Some more code cleaning
      
      ---
       include/diaspora.php | 35 ++++++++++++++++++++++++++---------
       include/xml.php      |  1 +
       2 files changed, 27 insertions(+), 9 deletions(-)
      
      diff --git a/include/diaspora.php b/include/diaspora.php
      index 308a799118..a608516622 100644
      --- a/include/diaspora.php
      +++ b/include/diaspora.php
      @@ -1694,11 +1694,8 @@ y	 *
       				$BPhoto = "[url=".$contact["url"]."][img]".$contact["thumb"]."[/img][/url]";
       				$arr["body"] = sprintf(t("%1$s is now friends with %2$s"), $A, $B)."\n\n\n".$Bphoto;
       
      -				$arr["object"] = "".ACTIVITY_OBJ_PERSON."".$contact["name"].""
      -					."".$contact["url"]."/".$contact["name"]."";
      -				$arr["object"] .= "".xmlify(''."\n");
      -				$arr["object"] .= xmlify(''."\n");
      -				$arr["object"] .= "\n";
      +				$arr["object"] = self::construct_new_friend_object($contact);
      +
       				$arr["last-child"] = 1;
       
       				$arr["allow_cid"] = $user[0]["allow_cid"];
      @@ -1713,6 +1710,26 @@ y	 *
       		}
       	}
       
      +	/**
      +	 * @brief Creates a XML object for a "new friend" message
      +	 *
      +	 * @param array $contact Array of the contact
      +	 *
      +	 * @return string The XML
      +	 */
      +        private function construct_new_friend_object($contact) {
      +                $objtype = ACTIVITY_OBJ_PERSON;
      +                $link = ''."\n".
      +                        ''."\n";
      +
      +                $xmldata = array("object" => array("type" => $objtype,
      +                                                "title" => $contact["name"],
      +                                                "id" => $contact["url"]."/".$contact["name"],
      +                                                "link" => $link));
      +
      +                return xml::from_array($xmldata, $xml, true);
      +        }
      +
       	/**
       	 * @brief Processes incoming sharing notification
       	 *
      @@ -2184,9 +2201,9 @@ y	 *
       		return $message_id;
       	}
       
      -	/******************************************************************************************
      +	/* ************************************************************************************** *
       	 * Here are all the functions that are needed to transmit data with the Diaspora protocol *
      -	 ******************************************************************************************/
      +	 * ************************************************************************************** */
       
       	/**
       	 * @brief returnes the handle of a contact
      @@ -3071,7 +3088,7 @@ y	 *
       	 *
       	 * @return bool Success
       	 */
      -	function store_like_signature($contact, $post_id) {
      +	public static function store_like_signature($contact, $post_id) {
       
       		$enabled = intval(get_config('system','diaspora_enabled'));
       		if (!$enabled) {
      @@ -3135,7 +3152,7 @@ y	 *
       	 *
       	 * @return bool Success
       	 */
      -	function store_comment_signature($item, $contact, $uprvkey, $message_id) {
      +	public static function store_comment_signature($item, $contact, $uprvkey, $message_id) {
       
       		if ($uprvkey == "") {
       			logger('No private key, so not storing comment signature', LOGGER_DEBUG);
      diff --git a/include/xml.php b/include/xml.php
      index 2aed3fe8ed..47a2f6f7d5 100644
      --- a/include/xml.php
      +++ b/include/xml.php
      @@ -1,5 +1,6 @@
       
      Date: Thu, 24 Mar 2016 08:35:06 +0100
      Subject: [PATCH 076/211] Scrape: Always take the first alias
      
      ---
       include/Scrape.php | 2 +-
       1 file changed, 1 insertion(+), 1 deletion(-)
      
      diff --git a/include/Scrape.php b/include/Scrape.php
      index e8e9a97a16..03d21047e7 100644
      --- a/include/Scrape.php
      +++ b/include/Scrape.php
      @@ -444,7 +444,7 @@ function probe_url($url, $mode = PROBE_NORMAL, $level = 1) {
       				if($link['@attributes']['rel'] === 'alias') {
       					if(strpos($link['@attributes']['href'],'@') === false) {
       						if(isset($profile)) {
      -							if($link['@attributes']['href'] !== $profile)
      +							if(($link['@attributes']['href'] !== $profile) AND ($alias == ""))
       								$alias = unamp($link['@attributes']['href']);
       						}
       						else
      
      From 721b2edb33c8a1fdd1e2831dd38baeda91277f14 Mon Sep 17 00:00:00 2001
      From: Michael Vogel 
      Date: Thu, 24 Mar 2016 15:59:53 +0100
      Subject: [PATCH 077/211] Some more documentation - again.
      
      ---
       include/diaspora.php | 3 ++-
       1 file changed, 2 insertions(+), 1 deletion(-)
      
      diff --git a/include/diaspora.php b/include/diaspora.php
      index a608516622..c5d6943e59 100644
      --- a/include/diaspora.php
      +++ b/include/diaspora.php
      @@ -755,7 +755,7 @@ class diaspora {
       	 *
       	 * @param int $uid The user id
       	 * @param string $guid The guid of the message
      -y	 *
      +	 *
       	 * @return int|bool message id if the message already was stored into the system - or false.
       	 */
       	private function message_exists($uid, $guid) {
      @@ -2143,6 +2143,7 @@ y	 *
       
       		$datarray = array();
       
      +		// Attach embedded pictures to the body
       		if ($data->photo) {
       			foreach ($data->photo AS $photo)
       				$body = "[img]".unxmlify($photo->remote_photo_path).
      
      From 617e1768e174a94ca94cf6d7f48029618cd2f7a7 Mon Sep 17 00:00:00 2001
      From: Michael Vogel 
      Date: Thu, 24 Mar 2016 21:32:55 +0100
      Subject: [PATCH 078/211] Avoid a guid whith spaces.
      
      ---
       include/diaspora.php | 3 ++-
       1 file changed, 2 insertions(+), 1 deletion(-)
      
      diff --git a/include/diaspora.php b/include/diaspora.php
      index c5d6943e59..32190bc7d6 100644
      --- a/include/diaspora.php
      +++ b/include/diaspora.php
      @@ -2611,8 +2611,9 @@ class diaspora {
       			$link = $matches[1];
       
       		$ret["root_guid"] = preg_replace("=https?://(.*)/posts/(.*)=ism", "$2", $link);
      -		if (($ret["root_guid"] == $link) OR ($ret["root_guid"] == ""))
      +		if (($ret["root_guid"] == $link) OR (trim($ret["root_guid"]) == ""))
       			return(false);
      +
       		return($ret);
       	}
       
      
      From 805f1287d05d3c2b6a5103a93c50bba72e6c9c06 Mon Sep 17 00:00:00 2001
      From: Michael Vogel 
      Date: Fri, 25 Mar 2016 00:49:18 +0100
      Subject: [PATCH 079/211] Support for the new contact request data type
      
      ---
       include/diaspora.php | 50 +++++++++++++++++++++++++++++++++-----------
       1 file changed, 38 insertions(+), 12 deletions(-)
      
      diff --git a/include/diaspora.php b/include/diaspora.php
      index 32190bc7d6..277eb6f8e8 100644
      --- a/include/diaspora.php
      +++ b/include/diaspora.php
      @@ -339,6 +339,9 @@ class diaspora {
       			case "comment":
       				return self::receive_comment($importer, $sender, $fields, $msg["message"]);
       
      +			case "contact":
      +				return self::receive_contact_request($importer, $fields);
      +
       			case "conversation":
       				return self::receive_conversation($importer, $msg, $fields);
       
      @@ -360,9 +363,6 @@ class diaspora {
       			case "profile":
       				return self::receive_profile($importer, $fields);
       
      -			case "request":
      -				return self::receive_request($importer, $fields);
      -
       			case "reshare":
       				return self::receive_reshare($importer, $fields, $msg["message"]);
       
      @@ -418,6 +418,9 @@ class diaspora {
       		if (in_array($type, array("signed_retraction", "relayable_retraction")))
       			$type = "retraction";
       
      +		if ($type == "request")
      +			$type = "contact";
      +
       		$fields = new SimpleXMLElement("<".$type."/>");
       
       		$signed_data = "";
      @@ -1377,7 +1380,7 @@ class diaspora {
       
       		// "positive" = "false" would be a Dislike - wich isn't currently supported by Diaspora
       		// We would accept this anyhow.
      -		if ($positive === "true")
      +		if ($positive == "true")
       			$verb = ACTIVITY_LIKE;
       		else
       			$verb = ACTIVITY_DISLIKE;
      @@ -1738,22 +1741,43 @@ class diaspora {
       	 *
       	 * @return bool Success
       	 */
      -	private function receive_request($importer, $data) {
      +	private function receive_contact_request($importer, $data) {
       		$author = unxmlify($data->author);
       		$recipient = unxmlify($data->recipient);
       
       		if (!$author || !$recipient)
       			return false;
       
      +		// the current protocol version doesn't know these fields
      +		// That means that we will assume their existance
      +		if (isset($data->following))
      +			$following = (unxmlify($data->following) == "true");
      +		else
      +			$following = true;
      +
      +		if (isset($data->sharing))
      +			$sharing = (unxmlify($data->sharing) == "true");
      +		else
      +			$sharing = true;
      +
       		$contact = self::contact_by_handle($importer["uid"],$author);
       
      -		if($contact) {
      +		// perhaps we were already sharing with this person. Now they're sharing with us.
      +		// That makes us friends.
      +		if ($contact) {
      +			if ($following AND $sharing) {
      +				self::receive_request_make_friend($importer, $contact);
      +				return true;
      +			} else /// @todo Handle all possible variations of adding and retracting of permissions
      +				return false;
      +		}
       
      -			// perhaps we were already sharing with this person. Now they're sharing with us.
      -			// That makes us friends.
      -
      -			self::receive_request_make_friend($importer, $contact);
      -			return true;
      +		if (!$following AND $sharing AND in_array($importer["page-flags"], array(PAGE_SOAPBOX, PAGE_NORMAL))) {
      +			logger("Author ".$author." wants to share with us - but doesn't want to listen. Request is ignored.", LOGGER_DEBUG);
      +			return false;
      +		} elseif (!$following AND !$sharing) {
      +			logger("Author ".$author." doesn't want anything - and we don't know the author. Request is ignored.", LOGGER_DEBUG);
      +			return false;
       		}
       
       		$ret = self::person_by_handle($author);
      @@ -1824,8 +1848,10 @@ class diaspora {
       			// but if our page-type is PAGE_COMMUNITY or PAGE_SOAPBOX
       			// we are going to change the relationship and make them a follower.
       
      -			if($importer["page-flags"] == PAGE_FREELOVE)
      +			if (($importer["page-flags"] == PAGE_FREELOVE) AND $sharing AND $following)
       				$new_relation = CONTACT_IS_FRIEND;
      +			elseif (($importer["page-flags"] == PAGE_FREELOVE) AND $sharing)
      +				$new_relation = CONTACT_IS_SHARING;
       			else
       				$new_relation = CONTACT_IS_FOLLOWER;
       
      
      From b198e73ef0be7e63db8443ac7cca1ddc7f209cea Mon Sep 17 00:00:00 2001
      From: Michael Vogel 
      Date: Sun, 27 Mar 2016 23:25:32 +0200
      Subject: [PATCH 080/211] Some more small documentation stuff
      
      ---
       include/diaspora.php | 4 ++--
       include/xml.php      | 4 ++--
       2 files changed, 4 insertions(+), 4 deletions(-)
      
      diff --git a/include/diaspora.php b/include/diaspora.php
      index 277eb6f8e8..d2a90fc983 100644
      --- a/include/diaspora.php
      +++ b/include/diaspora.php
      @@ -118,7 +118,7 @@ class diaspora {
       	 *
       	 * @return string the repaired signature
       	 */
      -	function repair_signature($signature, $handle = "", $level = 1) {
      +	private function repair_signature($signature, $handle = "", $level = 1) {
       
       		if ($signature == "")
       			return ($signature);
      @@ -146,7 +146,7 @@ class diaspora {
       	 * 'author' -> author diaspora handle
       	 * 'key' -> author public key (converted to pkcs#8)
       	 */
      -	function decode($importer, $xml) {
      +	public static function decode($importer, $xml) {
       
       		$public = false;
       		$basedom = parse_xml_string($xml);
      diff --git a/include/xml.php b/include/xml.php
      index 47a2f6f7d5..2bcc73b8f7 100644
      --- a/include/xml.php
      +++ b/include/xml.php
      @@ -15,7 +15,7 @@ class xml {
       	 *
       	 * @return string The created XML
       	 */
      -	function from_array($array, &$xml, $remove_header = false, $namespaces = array(), $root = true) {
      +	public static function from_array($array, &$xml, $remove_header = false, $namespaces = array(), $root = true) {
       
       		if ($root) {
       			foreach($array as $key => $value) {
      @@ -78,7 +78,7 @@ class xml {
       	 * @param object $target The XML target
       	 * @param string $elementname Name of the XML element of the target
       	 */
      -	function copy(&$source, &$target, $elementname) {
      +	public static function copy(&$source, &$target, $elementname) {
       		if (count($source->children()) == 0)
       			$target->addChild($elementname, xmlify($source));
       		else {
      
      From ce603cbb49652f84cda4594e1caebcda8ab41a29 Mon Sep 17 00:00:00 2001
      From: Michael Vogel 
      Date: Sun, 27 Mar 2016 23:38:35 +0200
      Subject: [PATCH 081/211] And some more doc stuff
      
      ---
       include/diaspora.php | 6 ++++--
       1 file changed, 4 insertions(+), 2 deletions(-)
      
      diff --git a/include/diaspora.php b/include/diaspora.php
      index d2a90fc983..3795def479 100644
      --- a/include/diaspora.php
      +++ b/include/diaspora.php
      @@ -737,7 +737,7 @@ class diaspora {
       	 * @param string $handle The checked handle in the format user@domain.tld
       	 * @param bool $is_comment Is the check for a comment?
       	 *
      -	 * @return bool is posting allowed?
      +	 * @return array The contact data
       	 */
       	private function allowed_contact_by_handle($importer, $handle, $is_comment = false) {
       		$contact = self::contact_by_handle($importer["uid"], $handle);
      @@ -931,7 +931,9 @@ class diaspora {
       	 * @param array $person The record of the person
       	 * @param int $uid The user id
       	 *
      -	 * @return array of contact id and network type
      +	 * @return array
      +	 *      'cid' => contact id
      +	 *      'network' => network type
       	 */
       	private function author_contact_by_url($contact, $person, $uid) {
       
      
      From a7f656c04185d784b8afd3f88e92796def11aeb5 Mon Sep 17 00:00:00 2001
      From: Michael Vogel 
      Date: Mon, 28 Mar 2016 00:06:46 +0200
      Subject: [PATCH 082/211] And wow ... some more documentation
      
      ---
       include/xml.php | 1 +
       1 file changed, 1 insertion(+)
      
      diff --git a/include/xml.php b/include/xml.php
      index 2bcc73b8f7..aa74cf65cf 100644
      --- a/include/xml.php
      +++ b/include/xml.php
      @@ -12,6 +12,7 @@ class xml {
       	 * @param object $xml The createdXML will be returned by reference
       	 * @param bool $remove_header Should the XML header be removed or not?
       	 * @param array $namespaces List of namespaces
      +	 * @param bool $root - interally used parameter. Mustn't be used from outside.
       	 *
       	 * @return string The created XML
       	 */
      
      From 0e74dc6db3b616b6bb1570ec4460a101de62f99e Mon Sep 17 00:00:00 2001
      From: Michael Vogel 
      Date: Mon, 28 Mar 2016 22:21:14 +0200
      Subject: [PATCH 083/211] Guesss what? Yeah, some documentation
      
      ---
       util/createdoxygen.php | 12 ++++++++++++
       1 file changed, 12 insertions(+)
       mode change 100755 => 100644 util/createdoxygen.php
      
      diff --git a/util/createdoxygen.php b/util/createdoxygen.php
      old mode 100755
      new mode 100644
      index d48114b671..163c94bb97
      --- a/util/createdoxygen.php
      +++ b/util/createdoxygen.php
      @@ -1,5 +1,10 @@
       #!/usr/bin/php
       
      Date: Mon, 28 Mar 2016 22:35:11 +0200
      Subject: [PATCH 084/211] Some changed doxygen header stuff
      
      ---
       include/diaspora.php | 8 ++++----
       1 file changed, 4 insertions(+), 4 deletions(-)
      
      diff --git a/include/diaspora.php b/include/diaspora.php
      index 3795def479..e3a3dcd78c 100644
      --- a/include/diaspora.php
      +++ b/include/diaspora.php
      @@ -277,7 +277,7 @@ class diaspora {
       	 *
       	 * @param array $msg The post that will be dispatched
       	 *
      -	 * @return bool Was the message accepted?
      +	 * @return int The message id of the generated message, "true" or "false" if there was an error
       	 */
       	public static function dispatch_public($msg) {
       
      @@ -289,7 +289,7 @@ class diaspora {
       
       		// Use a dummy importer to import the data for the public copy
       		$importer = array("uid" => 0, "page-flags" => PAGE_FREELOVE);
      -		$item_id = self::dispatch($importer,$msg);
      +		$message_id = self::dispatch($importer,$msg);
       
       		// Now distribute it to the followers
       		$r = q("SELECT `user`.* FROM `user` WHERE `user`.`uid` IN
      @@ -306,7 +306,7 @@ class diaspora {
       		} else
       			logger("No subscribers for ".$msg["author"]." ".print_r($msg, true));
       
      -		return $item_id;
      +		return $message_id;
       	}
       
       	/**
      @@ -315,7 +315,7 @@ class diaspora {
       	 * @param array $importer Array of the importer user
       	 * @param array $msg The post that will be dispatched
       	 *
      -	 * @return bool Was the message accepted?
      +	 * @return int The message id of the generated message, "true" or "false" if there was an error
       	 */
       	public static function dispatch($importer, $msg) {
       
      
      From 9d322d7473fe0cc9c13d5c107fb516836fb4dd79 Mon Sep 17 00:00:00 2001
      From: Michael Vogel 
      Date: Tue, 29 Mar 2016 17:54:36 +0200
      Subject: [PATCH 085/211] Not sure if that is correct ...
      
      ---
       include/xml.php | 4 ++++
       1 file changed, 4 insertions(+)
      
      diff --git a/include/xml.php b/include/xml.php
      index aa74cf65cf..a454e61566 100644
      --- a/include/xml.php
      +++ b/include/xml.php
      @@ -1,6 +1,10 @@
       
      Date: Wed, 30 Mar 2016 12:43:15 +0200
      Subject: [PATCH 086/211] New OStatus implementation
      
      ---
       include/items.php         |    6 +-
       include/notifier.php      |    2 +-
       include/ostatus.php       |    1 +
       include/ostatus2.php      | 1778 +++++++++++++++++++++++++++++++++++++
       include/pubsubpublish.php |    2 +-
       mod/salmon.php            |    4 +-
       6 files changed, 1786 insertions(+), 7 deletions(-)
       create mode 100644 include/ostatus2.php
      
      diff --git a/include/items.php b/include/items.php
      index f8c3149d58..233d72d133 100644
      --- a/include/items.php
      +++ b/include/items.php
      @@ -383,9 +383,9 @@ function item_store($arr,$force_parent = false, $notify = false, $dontcache = fa
       	// Converting the plink
       	if ($arr['network'] == NETWORK_OSTATUS) {
       		if (isset($arr['plink']))
      -			$arr['plink'] = ostatus_convert_href($arr['plink']);
      +			$arr['plink'] = ostatus::convert_href($arr['plink']);
       		elseif (isset($arr['uri']))
      -			$arr['plink'] = ostatus_convert_href($arr['uri']);
      +			$arr['plink'] = ostatus::convert_href($arr['uri']);
       	}
       
       	if(x($arr, 'gravity'))
      @@ -1243,7 +1243,7 @@ function consume_feed($xml,$importer,&$contact, &$hub, $datedir = 0, $pass = 0)
       			//$tempfile = tempnam(get_temppath(), "ostatus2");
       			//file_put_contents($tempfile, $xml);
       			logger("Consume OStatus messages ", LOGGER_DEBUG);
      -			ostatus_import($xml,$importer,$contact, $hub);
      +			ostatus::import($xml,$importer,$contact, $hub);
       		}
       		return;
       	}
      diff --git a/include/notifier.php b/include/notifier.php
      index a46744f070..18a617ac2f 100644
      --- a/include/notifier.php
      +++ b/include/notifier.php
      @@ -223,7 +223,7 @@ function notifier_run(&$argv, &$argc){
       
       	if(! ($mail || $fsuggest || $relocate)) {
       
      -		$slap = ostatus_salmon($target_item,$owner);
      +		$slap = ostatus::salmon($target_item,$owner);
       
       		require_once('include/group.php');
       
      diff --git a/include/ostatus.php b/include/ostatus.php
      index 5ba9f0e83c..02447e3ade 100644
      --- a/include/ostatus.php
      +++ b/include/ostatus.php
      @@ -12,6 +12,7 @@ require_once("include/Scrape.php");
       require_once("include/follow.php");
       require_once("include/api.php");
       require_once("mod/proxy.php");
      +require_once("include/ostatus2.php");
       
       define('OSTATUS_DEFAULT_POLL_INTERVAL', 30); // given in minutes
       define('OSTATUS_DEFAULT_POLL_TIMEFRAME', 1440); // given in minutes
      diff --git a/include/ostatus2.php b/include/ostatus2.php
      new file mode 100644
      index 0000000000..53c279f254
      --- /dev/null
      +++ b/include/ostatus2.php
      @@ -0,0 +1,1778 @@
      +createElement($element, xmlify($value));
      +
      +		foreach ($attributes AS $key => $value) {
      +			$attribute = $doc->createAttribute($key);
      +			$attribute->value = xmlify($value);
      +			$element->appendChild($attribute);
      +		}
      +		return $element;
      +	}
      +
      +	public static function add_element($doc, $parent, $element, $value = "", $attributes = array()) {
      +		$element = self::create_element($doc, $element, $value, $attributes);
      +		$parent->appendChild($element);
      +	}
      +}
      +
      +class ostatus {
      +	const OSTATUS_DEFAULT_POLL_INTERVAL = 30; // given in minutes
      +	const OSTATUS_DEFAULT_POLL_TIMEFRAME = 1440; // given in minutes
      +	const OSTATUS_DEFAULT_POLL_TIMEFRAME_MENTIONS = 14400; // given in minutes
      +
      +	private function fetchauthor($xpath, $context, $importer, &$contact, $onlyfetch) {
      +
      +		$author = array();
      +		$author["author-link"] = $xpath->evaluate('atom:author/atom:uri/text()', $context)->item(0)->nodeValue;
      +		$author["author-name"] = $xpath->evaluate('atom:author/atom:name/text()', $context)->item(0)->nodeValue;
      +
      +		// Preserve the value
      +		$authorlink = $author["author-link"];
      +
      +		$alternate = $xpath->query("atom:author/atom:link[@rel='alternate']", $context)->item(0)->attributes;
      +		if (is_object($alternate))
      +			foreach($alternate AS $attributes)
      +				if ($attributes->name == "href")
      +					$author["author-link"] = $attributes->textContent;
      +
      +		$r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `nurl` IN ('%s', '%s') AND `network` != '%s'",
      +			intval($importer["uid"]), dbesc(normalise_link($author["author-link"])),
      +			dbesc(normalise_link($authorlink)), dbesc(NETWORK_STATUSNET));
      +		if ($r) {
      +			$contact = $r[0];
      +			$author["contact-id"] = $r[0]["id"];
      +		} else
      +			$author["contact-id"] = $contact["id"];
      +
      +		$avatarlist = array();
      +		$avatars = $xpath->query("atom:author/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 (($width > 0) AND ($href != ""))
      +				$avatarlist[$width] = $href;
      +		}
      +		if (count($avatarlist) > 0) {
      +			krsort($avatarlist);
      +			$author["author-avatar"] = current($avatarlist);
      +		}
      +
      +		$displayname = $xpath->evaluate('atom:author/poco:displayName/text()', $context)->item(0)->nodeValue;
      +		if ($displayname != "")
      +			$author["author-name"] = $displayname;
      +
      +		$author["owner-name"] = $author["author-name"];
      +		$author["owner-link"] = $author["author-link"];
      +		$author["owner-avatar"] = $author["author-avatar"];
      +
      +		// Only update the contacts if it is an OStatus contact
      +		if ($r AND !$onlyfetch AND ($contact["network"] == NETWORK_OSTATUS)) {
      +			// Update contact data
      +
      +			$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);
      +
      +				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"]));
      +
      +				poco_check($contact["url"], $contact["name"], $contact["network"], $author["author-avatar"], $contact["about"], $contact["location"],
      +							"", "", "", datetime_convert(), 2, $contact["id"], $contact["uid"]);
      +			}
      +
      +			if (isset($author["author-avatar"]) AND ($author["author-avatar"] != $r[0]['avatar'])) {
      +				logger("Update profile picture for contact ".$contact["id"], LOGGER_DEBUG);
      +
      +				update_contact_avatar($author["author-avatar"], $importer["uid"], $contact["id"]);
      +			}
      +
      +			$contact["generation"] = 2;
      +			$contact["photo"] = $author["author-avatar"];
      +			update_gcontact($contact);
      +		}
      +
      +		return($author);
      +	}
      +
      +	public static function salmon_author($xml, $importer) {
      +
      +		if ($xml == "")
      +			return;
      +
      +		$doc = new DOMDocument();
      +		@$doc->loadXML($xml);
      +
      +		$xpath = new DomXPath($doc);
      +		$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');
      +
      +		foreach ($entries AS $entry) {
      +			// fetch the author
      +			$author = self::fetchauthor($xpath, $entry, $importer, $contact, true);
      +			return $author;
      +		}
      +	}
      +
      +	public static function import($xml,$importer,&$contact, &$hub) {
      +
      +		logger("Import OStatus message", LOGGER_DEBUG);
      +
      +		if ($xml == "")
      +			return;
      +
      +		//$tempfile = tempnam(get_temppath(), "import");
      +		//file_put_contents($tempfile, $xml);
      +
      +		$doc = new DOMDocument();
      +		@$doc->loadXML($xml);
      +
      +		$xpath = new DomXPath($doc);
      +		$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;
      +		if (is_object($hub_attributes))
      +			foreach($hub_attributes AS $hub_attribute)
      +				if ($hub_attribute->name == "href") {
      +					$hub = $hub_attribute->textContent;
      +					logger("Found hub ".$hub, LOGGER_DEBUG);
      +				}
      +
      +		$header = array();
      +		$header["uid"] = $importer["uid"];
      +		$header["network"] = NETWORK_OSTATUS;
      +		$header["type"] = "remote";
      +		$header["wall"] = 0;
      +		$header["origin"] = 0;
      +		$header["gravity"] = GRAVITY_PARENT;
      +
      +		// it could either be a received post or a post we fetched by ourselves
      +		// depending on that, the first node is different
      +		$first_child = $doc->firstChild->tagName;
      +
      +		if ($first_child == "feed")
      +			$entries = $xpath->query('/atom:feed/atom:entry');
      +		else
      +			$entries = $xpath->query('/atom:entry');
      +
      +		$conversation = "";
      +		$conversationlist = array();
      +		$item_id = 0;
      +
      +		// Reverse the order of the entries
      +		$entrylist = array();
      +
      +		foreach ($entries AS $entry)
      +			$entrylist[] = $entry;
      +
      +		foreach (array_reverse($entrylist) AS $entry) {
      +
      +			$mention = false;
      +
      +			// fetch the author
      +			if ($first_child == "feed")
      +				$author = self::fetchauthor($xpath, $doc->firstChild, $importer, $contact, false);
      +			else
      +				$author = self::fetchauthor($xpath, $entry, $importer, $contact, false);
      +
      +			$value = $xpath->evaluate('atom:author/poco:preferredUsername/text()', $context)->item(0)->nodeValue;
      +			if ($value != "")
      +				$nickname = $value;
      +			else
      +				$nickname = $author["author-name"];
      +
      +			$item = array_merge($header, $author);
      +
      +			// Now get the item
      +			$item["uri"] = $xpath->query('atom:id/text()', $entry)->item(0)->nodeValue;
      +
      +			$r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s'",
      +				intval($importer["uid"]), dbesc($item["uri"]));
      +			if ($r) {
      +				logger("Item with uri ".$item["uri"]." for user ".$importer["uid"]." already existed under id ".$r[0]["id"], LOGGER_DEBUG);
      +				continue;
      +			}
      +
      +			$item["body"] = add_page_info_to_body(html2bbcode($xpath->query('atom:content/text()', $entry)->item(0)->nodeValue));
      +			$item["object-type"] = $xpath->query('activity:object-type/text()', $entry)->item(0)->nodeValue;
      +
      +			if (($item["object-type"] == ACTIVITY_OBJ_BOOKMARK) OR ($item["object-type"] == ACTIVITY_OBJ_EVENT)) {
      +				$item["title"] = $xpath->query('atom:title/text()', $entry)->item(0)->nodeValue;
      +				$item["body"] = $xpath->query('atom:summary/text()', $entry)->item(0)->nodeValue;
      +			} elseif ($item["object-type"] == ACTIVITY_OBJ_QUESTION)
      +				$item["title"] = $xpath->query('atom:title/text()', $entry)->item(0)->nodeValue;
      +
      +			$item["object"] = $xml;
      +			$item["verb"] = $xpath->query('activity:verb/text()', $entry)->item(0)->nodeValue;
      +
      +			/// @TODO
      +			/// Delete a message
      +			if ($item["verb"] == "qvitter-delete-notice") {
      +				// ignore "Delete" messages (by now)
      +				logger("Ignore delete message ".print_r($item, true));
      +				continue;
      +			}
      +
      +			if ($item["verb"] == ACTIVITY_JOIN) {
      +				// ignore "Join" messages
      +				logger("Ignore join message ".print_r($item, true));
      +				continue;
      +			}
      +
      +			if ($item["verb"] == ACTIVITY_FOLLOW) {
      +				new_follower($importer, $contact, $item, $nickname);
      +				continue;
      +			}
      +
      +			if ($item["verb"] == NAMESPACE_OSTATUS."/unfollow") {
      +				lose_follower($importer, $contact, $item, $dummy);
      +				continue;
      +			}
      +
      +			if ($item["verb"] == ACTIVITY_FAVORITE) {
      +				$orig_uri = $xpath->query("activity:object/atom:id", $entry)->item(0)->nodeValue;
      +				logger("Favorite ".$orig_uri." ".print_r($item, true));
      +
      +				$item["verb"] = ACTIVITY_LIKE;
      +				$item["parent-uri"] = $orig_uri;
      +				$item["gravity"] = GRAVITY_LIKE;
      +			}
      +
      +			if ($item["verb"] == NAMESPACE_OSTATUS."/unfavorite") {
      +				// Ignore "Unfavorite" message
      +				logger("Ignore unfavorite message ".print_r($item, true));
      +				continue;
      +			}
      +
      +			// http://activitystrea.ms/schema/1.0/rsvp-yes
      +			if (!in_array($item["verb"], array(ACTIVITY_POST, ACTIVITY_LIKE, ACTIVITY_SHARE)))
      +				logger("Unhandled verb ".$item["verb"]." ".print_r($item, true));
      +
      +			$item["created"] = $xpath->query('atom:published/text()', $entry)->item(0)->nodeValue;
      +			$item["edited"] = $xpath->query('atom:updated/text()', $entry)->item(0)->nodeValue;
      +			$conversation = $xpath->query('ostatus:conversation/text()', $entry)->item(0)->nodeValue;
      +
      +			$related = "";
      +
      +			$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;
      +					if ($attributes->name == "href")
      +						$related = $attributes->textContent;
      +				}
      +			}
      +
      +			$georsspoint = $xpath->query('georss:point', $entry);
      +			if ($georsspoint)
      +				$item["coord"] = $georsspoint->item(0)->nodeValue;
      +
      +			$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]";
      +						}
      +				}
      +			}
      +
      +			$self = "";
      +			$enclosure = "";
      +
      +			$links = $xpath->query('atom:link', $entry);
      +			if ($links) {
      +				$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;
      +								if (($item["object-type"] == ACTIVITY_OBJ_QUESTION) OR
      +									($item["object-type"] == ACTIVITY_OBJ_EVENT))
      +									$item["body"] .= add_page_info($href);
      +								break;
      +							case "ostatus:conversation":
      +								$conversation = $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;
      +							case "related":
      +								if ($item["object-type"] != ACTIVITY_OBJ_BOOKMARK) {
      +									if (!isset($item["parent-uri"]))
      +										$item["parent-uri"] = $href;
      +
      +									if ($related == "")
      +										$related = $href;
      +								} else
      +									$item["body"] .= add_page_info($href);
      +								break;
      +							case "self":
      +								$self = $href;
      +								break;
      +							case "mentioned":
      +								// Notification check
      +								if ($importer["nurl"] == normalise_link($href))
      +									$mention = true;
      +								break;
      +						}
      +				}
      +			}
      +
      +			$local_id = "";
      +			$repeat_of = "";
      +
      +			$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);
      +					if ($attributes->name == "local_id")
      +						$local_id = $attributes->textContent;
      +					if ($attributes->name == "repeat_of")
      +						$repeat_of = $attributes->textContent;
      +				}
      +			}
      +
      +			// Is it a repeated post?
      +			if ($repeat_of != "") {
      +				$activityobjects = $xpath->query('activity:object', $entry)->item(0);
      +
      +				if (is_object($activityobjects)) {
      +
      +					$orig_uri = $xpath->query("activity:object/atom:id", $activityobjects)->item(0)->nodeValue;
      +					if (!isset($orig_uri))
      +						$orig_uri = $xpath->query('atom:id/text()', $activityobjects)->item(0)->nodeValue;
      +
      +					$orig_links = $xpath->query("activity:object/atom:link[@rel='alternate']", $activityobjects);
      +					if ($orig_links AND ($orig_links->length > 0))
      +						foreach($orig_links->item(0)->attributes AS $attributes)
      +							if ($attributes->name == "href")
      +								$orig_link = $attributes->textContent;
      +
      +					if (!isset($orig_link))
      +						$orig_link = $xpath->query("atom:link[@rel='alternate']", $activityobjects)->item(0)->nodeValue;
      +
      +					if (!isset($orig_link))
      +						$orig_link =  self::convert_href($orig_uri);
      +
      +					$orig_body = $xpath->query('activity:object/atom:content/text()', $activityobjects)->item(0)->nodeValue;
      +					if (!isset($orig_body))
      +						$orig_body = $xpath->query('atom:content/text()', $activityobjects)->item(0)->nodeValue;
      +
      +					$orig_created = $xpath->query('atom:published/text()', $activityobjects)->item(0)->nodeValue;
      +
      +					$orig_contact = $contact;
      +					$orig_author = self::fetchauthor($xpath, $activityobjects, $importer, $orig_contact, false);
      +
      +					$item["author-name"] = $orig_author["author-name"];
      +					$item["author-link"] = $orig_author["author-link"];
      +					$item["author-avatar"] = $orig_author["author-avatar"];
      +					$item["body"] = add_page_info_to_body(html2bbcode($orig_body));
      +					$item["created"] = $orig_created;
      +
      +					$item["uri"] = $orig_uri;
      +					$item["plink"] = $orig_link;
      +
      +					$item["verb"] = $xpath->query('activity:verb/text()', $activityobjects)->item(0)->nodeValue;
      +
      +					$item["object-type"] = $xpath->query('activity:object/activity:object-type/text()', $activityobjects)->item(0)->nodeValue;
      +					if (!isset($item["object-type"]))
      +						$item["object-type"] = $xpath->query('activity:object-type/text()', $activityobjects)->item(0)->nodeValue;
      +				}
      +			}
      +
      +			//if ($enclosure != "")
      +			//	$item["body"] .= add_page_info($enclosure);
      +
      +			if (isset($item["parent-uri"])) {
      +				$r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s'",
      +					intval($importer["uid"]), dbesc($item["parent-uri"]));
      +
      +				if (!$r AND ($related != "")) {
      +					$reply_path = str_replace("/notice/", "/api/statuses/show/", $related).".atom";
      +
      +					if ($reply_path != $related) {
      +						logger("Fetching related items for user ".$importer["uid"]." from ".$reply_path, LOGGER_DEBUG);
      +						$reply_xml = fetch_url($reply_path);
      +
      +						$reply_contact = $contact;
      +						self::import($reply_xml,$importer,$reply_contact, $reply_hub);
      +
      +						// After the import try to fetch the parent item again
      +						$r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s'",
      +							intval($importer["uid"]), dbesc($item["parent-uri"]));
      +					}
      +				}
      +				if ($r) {
      +					$item["type"] = 'remote-comment';
      +					$item["gravity"] = GRAVITY_COMMENT;
      +				}
      +			} else
      +				$item["parent-uri"] = $item["uri"];
      +
      +			$item_id = self::completion($conversation, $importer["uid"], $item, $self);
      +
      +			if (!$item_id) {
      +				logger("Error storing item", LOGGER_DEBUG);
      +				continue;
      +			}
      +
      +			logger("Item was stored with id ".$item_id, LOGGER_DEBUG);
      +		}
      +	}
      +
      +	public static function convert_href($href) {
      +		$elements = explode(":",$href);
      +
      +		if ((count($elements) <= 2) OR ($elements[0] != "tag"))
      +			return $href;
      +
      +		$server = explode(",", $elements[1]);
      +		$conversation = explode("=", $elements[2]);
      +
      +		if ((count($elements) == 4) AND ($elements[2] == "post"))
      +			return "http://".$server[0]."/notice/".$elements[3];
      +
      +		if ((count($conversation) != 2) OR ($conversation[1] ==""))
      +			return $href;
      +
      +		if ($elements[3] == "objectType=thread")
      +			return "http://".$server[0]."/conversation/".$conversation[1];
      +		else
      +			return "http://".$server[0]."/notice/".$conversation[1];
      +
      +		return $href;
      +	}
      +
      +	public static function check_conversations($mentions = false, $override = false) {
      +		$last = get_config('system','ostatus_last_poll');
      +
      +		$poll_interval = intval(get_config('system','ostatus_poll_interval'));
      +		if(! $poll_interval)
      +			$poll_interval = OSTATUS_DEFAULT_POLL_INTERVAL;
      +
      +		// Don't poll if the interval is set negative
      +		if (($poll_interval < 0) AND !$override)
      +			return;
      +
      +		if (!$mentions) {
      +			$poll_timeframe = intval(get_config('system','ostatus_poll_timeframe'));
      +			if (!$poll_timeframe)
      +				$poll_timeframe = OSTATUS_DEFAULT_POLL_TIMEFRAME;
      +		} else {
      +			$poll_timeframe = intval(get_config('system','ostatus_poll_timeframe'));
      +			if (!$poll_timeframe)
      +				$poll_timeframe = OSTATUS_DEFAULT_POLL_TIMEFRAME_MENTIONS;
      +		}
      +
      +
      +		if ($last AND !$override) {
      +			$next = $last + ($poll_interval * 60);
      +			if ($next > time()) {
      +				logger('poll interval not reached');
      +				return;
      +			}
      +		}
      +
      +		logger('cron_start');
      +
      +		$start = date("Y-m-d H:i:s", time() - ($poll_timeframe * 60));
      +
      +		if ($mentions)
      +			$conversations = q("SELECT `term`.`oid`, `term`.`url`, `term`.`uid` FROM `term`
      +						STRAIGHT_JOIN `thread` ON `thread`.`iid` = `term`.`oid` AND `thread`.`uid` = `term`.`uid`
      +						WHERE `term`.`type` = 7 AND `term`.`term` > '%s' AND `thread`.`mention`
      +						GROUP BY `term`.`url`, `term`.`uid` ORDER BY `term`.`term` DESC", dbesc($start));
      +		else
      +			$conversations = q("SELECT `oid`, `url`, `uid` FROM `term`
      +						WHERE `type` = 7 AND `term` > '%s'
      +						GROUP BY `url`, `uid` ORDER BY `term` DESC", dbesc($start));
      +
      +		foreach ($conversations AS $conversation) {
      +			self::completion($conversation['url'], $conversation['uid']);
      +		}
      +
      +		logger('cron_end');
      +
      +		set_config('system','ostatus_last_poll', time());
      +	}
      +
      +	/**
      +	 * @brief Updates the gcontact table with actor data from the conversation
      +	 *
      +	 * @param object $actor The actor object that contains the contact data
      +	 */
      +	private function conv_fetch_actor($actor) {
      +
      +		// We set the generation to "3" since the data here is not as reliable as the data we get on other occasions
      +		$contact = array("network" => NETWORK_OSTATUS, "generation" => 3);
      +
      +		if (isset($actor->url))
      +			$contact["url"] = $actor->url;
      +
      +		if (isset($actor->displayName))
      +			$contact["name"] = $actor->displayName;
      +
      +		if (isset($actor->portablecontacts_net->displayName))
      +			$contact["name"] = $actor->portablecontacts_net->displayName;
      +
      +		if (isset($actor->portablecontacts_net->preferredUsername))
      +			$contact["nick"] = $actor->portablecontacts_net->preferredUsername;
      +
      +		if (isset($actor->id))
      +			$contact["alias"] = $actor->id;
      +
      +		if (isset($actor->summary))
      +			$contact["about"] = $actor->summary;
      +
      +		if (isset($actor->portablecontacts_net->note))
      +			$contact["about"] = $actor->portablecontacts_net->note;
      +
      +		if (isset($actor->portablecontacts_net->addresses->formatted))
      +			$contact["location"] = $actor->portablecontacts_net->addresses->formatted;
      +
      +
      +		if (isset($actor->image->url))
      +			$contact["photo"] = $actor->image->url;
      +
      +		if (isset($actor->image->width))
      +			$avatarwidth = $actor->image->width;
      +
      +		if (is_array($actor->status_net->avatarLinks))
      +			foreach ($actor->status_net->avatarLinks AS $avatar) {
      +				if ($avatarsize < $avatar->width) {
      +					$contact["photo"] = $avatar->url;
      +					$avatarsize = $avatar->width;
      +				}
      +			}
      +
      +		update_gcontact($contact);
      +	}
      +
      +	/**
      +	 * @brief Fetches the conversation url for a given item link or conversation id
      +	 *
      +	 * @param string $self The link to the posting
      +	 * @param string $conversation_id The conversation id
      +	 *
      +	 * @return string The conversation url
      +	 */
      +	private function fetch_conversation($self, $conversation_id = "") {
      +
      +		if ($conversation_id != "") {
      +			$elements = explode(":", $conversation_id);
      +
      +			if ((count($elements) <= 2) OR ($elements[0] != "tag"))
      +				return $conversation_id;
      +		}
      +
      +		if ($self == "")
      +			return "";
      +
      +		$json = str_replace(".atom", ".json", $self);
      +
      +		$raw = fetch_url($json);
      +		if ($raw == "")
      +			return "";
      +
      +		$data = json_decode($raw);
      +		if (!is_object($data))
      +			return "";
      +
      +		$conversation_id = $data->statusnet_conversation_id;
      +
      +		$pos = strpos($self, "/api/statuses/show/");
      +		$base_url = substr($self, 0, $pos);
      +
      +		return $base_url."/conversation/".$conversation_id;
      +	}
      +
      +	/**
      +	 * @brief Fetches actor details of a given actor and user id
      +	 *
      +	 * @param string $actor The actor url
      +	 * @param int $uid The user id
      +	 * @param int $contact_id The default contact-id
      +	 *
      +	 * @return array Array with actor details
      +	 */
      +	private function get_actor_details($actor, $uid, $contact_id) {
      +
      +		$details = array();
      +
      +		$contact = q("SELECT `id`, `rel`, `network` FROM `contact` WHERE `uid` = %d AND `nurl` = '%s' AND `network` != '%s'",
      +					$uid, normalise_link($actor), NETWORK_STATUSNET);
      +
      +		if (!$contact)
      +			$contact = q("SELECT `id`, `rel`, `network` FROM `contact` WHERE `uid` = %d AND `alias` IN ('%s', '%s') AND `network` != '%s'",
      +					$uid, $actor, normalise_link($actor), NETWORK_STATUSNET);
      +
      +		if ($contact) {
      +			logger("Found contact for url ".$actor, LOGGER_DEBUG);
      +			$details["contact_id"] = $contact[0]["id"];
      +			$details["network"] = $contact[0]["network"];
      +
      +			$details["not_following"] = !in_array($contact[0]["rel"], array(CONTACT_IS_SHARING, CONTACT_IS_FRIEND));
      +		} else {
      +			logger("No contact found for user ".$uid." and url ".$actor, LOGGER_DEBUG);
      +
      +			// Adding a global contact
      +			/// @TODO Use this data for the post
      +			$details["global_contact_id"] = get_contact($actor, 0);
      +
      +			logger("Global contact ".$global_contact_id." found for url ".$actor, LOGGER_DEBUG);
      +
      +			$details["contact_id"] = $contact_id;
      +			$details["network"] = NETWORK_OSTATUS;
      +
      +			$details["not_following"] = true;
      +		}
      +
      +		return $details;
      +	}
      +
      +	private function completion($conversation_url, $uid, $item = array(), $self = "") {
      +
      +
      +		$item_stored = -1;
      +
      +		$conversation_url = self::fetch_conversation($self, $conversation_url);
      +
      +		// If the thread shouldn't be completed then store the item and go away
      +		// Don't do a completion on liked content
      +		if (((intval(get_config('system','ostatus_poll_interval')) == -2) AND (count($item) > 0)) OR
      +			($item["verb"] == ACTIVITY_LIKE) OR ($conversation_url == "")) {
      +			$item_stored = item_store($item, true);
      +			return($item_stored);
      +		}
      +
      +		// Get the parent
      +		$parents = q("SELECT `id`, `parent`, `uri`, `contact-id`, `type`, `verb`, `visible` FROM `item` WHERE `id` IN
      +				(SELECT `parent` FROM `item` WHERE `id` IN
      +					(SELECT `oid` FROM `term` WHERE `uid` = %d AND `otype` = %d AND `type` = %d AND `url` = '%s'))",
      +				intval($uid), intval(TERM_OBJ_POST), intval(TERM_CONVERSATION), dbesc($conversation_url));
      +
      +		if ($parents)
      +			$parent = $parents[0];
      +		elseif (count($item) > 0) {
      +			$parent = $item;
      +			$parent["type"] = "remote";
      +			$parent["verb"] = ACTIVITY_POST;
      +			$parent["visible"] = 1;
      +		} else {
      +			// Preset the parent
      +			$r = q("SELECT `id` FROM `contact` WHERE `self` AND `uid`=%d", $uid);
      +			if (!$r)
      +				return(-2);
      +
      +			$parent = array();
      +			$parent["id"] = 0;
      +			$parent["parent"] = 0;
      +			$parent["uri"] = "";
      +			$parent["contact-id"] = $r[0]["id"];
      +			$parent["type"] = "remote";
      +			$parent["verb"] = ACTIVITY_POST;
      +			$parent["visible"] = 1;
      +		}
      +
      +		$conv = str_replace("/conversation/", "/api/statusnet/conversation/", $conversation_url).".as";
      +		$pageno = 1;
      +		$items = array();
      +
      +		logger('fetching conversation url '.$conv.' (Self: '.$self.') for user '.$uid);
      +
      +		do {
      +			$conv_arr = z_fetch_url($conv."?page=".$pageno);
      +
      +			// If it is a non-ssl site and there is an error, then try ssl or vice versa
      +			if (!$conv_arr["success"] AND (substr($conv, 0, 7) == "http://")) {
      +				$conv = str_replace("http://", "https://", $conv);
      +				$conv_as = fetch_url($conv."?page=".$pageno);
      +			} elseif (!$conv_arr["success"] AND (substr($conv, 0, 8) == "https://")) {
      +				$conv = str_replace("https://", "http://", $conv);
      +				$conv_as = fetch_url($conv."?page=".$pageno);
      +			} else
      +				$conv_as = $conv_arr["body"];
      +
      +			$conv_as = str_replace(',"statusnet:notice_info":', ',"statusnet_notice_info":', $conv_as);
      +			$conv_as = json_decode($conv_as);
      +
      +			$no_of_items = sizeof($items);
      +
      +			if (@is_array($conv_as->items))
      +				foreach ($conv_as->items AS $single_item)
      +					$items[$single_item->id] = $single_item;
      +
      +			if ($no_of_items == sizeof($items))
      +				break;
      +
      +			$pageno++;
      +
      +		} while (true);
      +
      +		logger('fetching conversation done. Found '.count($items).' items');
      +
      +		if (!sizeof($items)) {
      +			if (count($item) > 0) {
      +				$item_stored = item_store($item, true);
      +
      +				if ($item_stored) {
      +					logger("Conversation ".$conversation_url." couldn't be fetched. Item uri ".$item["uri"]." stored: ".$item_stored, LOGGER_DEBUG);
      +					self::store_conversation($item_id, $conversation_url);
      +				}
      +
      +				return($item_stored);
      +			} else
      +				return(-3);
      +		}
      +
      +		$items = array_reverse($items);
      +
      +		$r = q("SELECT `nurl` FROM `contact` WHERE `uid` = %d AND `self`", intval($uid));
      +		$importer = $r[0];
      +
      +		$new_parent = true;
      +
      +		foreach ($items as $single_conv) {
      +
      +			// Update the gcontact table
      +			self::conv_fetch_actor($single_conv->actor);
      +
      +			// Test - remove before flight
      +			//$tempfile = tempnam(get_temppath(), "conversation");
      +			//file_put_contents($tempfile, json_encode($single_conv));
      +
      +			$mention = false;
      +
      +			if (isset($single_conv->object->id))
      +				$single_conv->id = $single_conv->object->id;
      +
      +			$plink = self::convert_href($single_conv->id);
      +			if (isset($single_conv->object->url))
      +				$plink = self::convert_href($single_conv->object->url);
      +
      +			if (@!$single_conv->id)
      +				continue;
      +
      +			logger("Got id ".$single_conv->id, LOGGER_DEBUG);
      +
      +			if ($first_id == "") {
      +				$first_id = $single_conv->id;
      +
      +				// The first post of the conversation isn't our first post. There are three options:
      +				// 1. Our conversation hasn't the "real" thread starter
      +				// 2. This first post is a post inside our thread
      +				// 3. This first post is a post inside another thread
      +				if (($first_id != $parent["uri"]) AND ($parent["uri"] != "")) {
      +
      +					$new_parent = true;
      +
      +					$new_parents = q("SELECT `id`, `parent`, `uri`, `contact-id`, `type`, `verb`, `visible` FROM `item` WHERE `id` IN
      +								(SELECT `parent` FROM `item`
      +									WHERE `uid` = %d AND `uri` = '%s' AND `network` IN ('%s','%s')) LIMIT 1",
      +						intval($uid), dbesc($first_id), dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN));
      +					if ($new_parents) {
      +						if ($new_parents[0]["parent"] == $parent["parent"]) {
      +							// Option 2: This post is already present inside our thread - but not as thread starter
      +							logger("Option 2: uri present in our thread: ".$first_id, LOGGER_DEBUG);
      +							$first_id = $parent["uri"];
      +						} else {
      +							// Option 3: Not so good. We have mixed parents. We have to see how to clean this up.
      +							// For now just take the new parent.
      +							$parent = $new_parents[0];
      +							$first_id = $parent["uri"];
      +							logger("Option 3: mixed parents for uri ".$first_id, LOGGER_DEBUG);
      +						}
      +					} else {
      +						// Option 1: We hadn't got the real thread starter
      +						// We have to clean up our existing messages.
      +						$parent["id"] = 0;
      +						$parent["uri"] = $first_id;
      +						logger("Option 1: we have a new parent: ".$first_id, LOGGER_DEBUG);
      +					}
      +				} elseif ($parent["uri"] == "") {
      +					$parent["id"] = 0;
      +					$parent["uri"] = $first_id;
      +				}
      +			}
      +
      +			$parent_uri = $parent["uri"];
      +
      +			// "context" only seems to exist on older servers
      +			if (isset($single_conv->context->inReplyTo->id)) {
      +				$parent_exists = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s' AND `network` IN ('%s','%s') LIMIT 1",
      +							intval($uid), dbesc($single_conv->context->inReplyTo->id), dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN));
      +				if ($parent_exists)
      +					$parent_uri = $single_conv->context->inReplyTo->id;
      +			}
      +
      +			// This is the current way
      +			if (isset($single_conv->object->inReplyTo->id)) {
      +				$parent_exists = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s' AND `network` IN ('%s','%s') LIMIT 1",
      +							intval($uid), dbesc($single_conv->object->inReplyTo->id), dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN));
      +				if ($parent_exists)
      +					$parent_uri = $single_conv->object->inReplyTo->id;
      +			}
      +
      +			$message_exists = q("SELECT `id`, `parent`, `uri` FROM `item` WHERE `uid` = %d AND `uri` = '%s' AND `network` IN ('%s','%s') LIMIT 1",
      +							intval($uid), dbesc($single_conv->id),
      +							dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN));
      +			if ($message_exists) {
      +				logger("Message ".$single_conv->id." already existed on the system", LOGGER_DEBUG);
      +
      +				if ($parent["id"] != 0) {
      +					$existing_message = $message_exists[0];
      +
      +					// We improved the way we fetch OStatus messages, this shouldn't happen very often now
      +					/// @TODO We have to change the shadow copies as well. This way here is really ugly.
      +					if ($existing_message["parent"] != $parent["id"]) {
      +						logger('updating id '.$existing_message["id"].' with parent '.$existing_message["parent"].' to parent '.$parent["id"].' uri '.$parent["uri"].' thread '.$parent_uri, LOGGER_DEBUG);
      +
      +						// Update the parent id of the selected item
      +						$r = q("UPDATE `item` SET `parent` = %d, `parent-uri` = '%s' WHERE `id` = %d",
      +							intval($parent["id"]), dbesc($parent["uri"]), intval($existing_message["id"]));
      +
      +						// Update the parent uri in the thread - but only if it points to itself
      +						$r = q("UPDATE `item` SET `thr-parent` = '%s' WHERE `id` = %d AND `uri` = `thr-parent`",
      +							dbesc($parent_uri), intval($existing_message["id"]));
      +
      +						// try to change all items of the same parent
      +						$r = q("UPDATE `item` SET `parent` = %d, `parent-uri` = '%s' WHERE `parent` = %d",
      +							intval($parent["id"]), dbesc($parent["uri"]), intval($existing_message["parent"]));
      +
      +						// Update the parent uri in the thread - but only if it points to itself
      +						$r = q("UPDATE `item` SET `thr-parent` = '%s' WHERE (`parent` = %d) AND (`uri` = `thr-parent`)",
      +							dbesc($parent["uri"]), intval($existing_message["parent"]));
      +
      +						// Now delete the thread
      +						delete_thread($existing_message["parent"]);
      +					}
      +				}
      +
      +				// The item we are having on the system is the one that we wanted to store via the item array
      +				if (isset($item["uri"]) AND ($item["uri"] == $existing_message["uri"])) {
      +					$item = array();
      +					$item_stored = 0;
      +				}
      +
      +				continue;
      +			}
      +
      +			if (is_array($single_conv->to))
      +				foreach($single_conv->to AS $to)
      +					if ($importer["nurl"] == normalise_link($to->id))
      +						$mention = true;
      +
      +			$actor = $single_conv->actor->id;
      +			if (isset($single_conv->actor->url))
      +				$actor = $single_conv->actor->url;
      +
      +			$details = self::get_actor_details($actor, $uid, $parent["contact-id"]);
      +
      +			// Do we only want to import threads that were started by our contacts?
      +			if ($details["not_following"] AND $new_parent AND get_config('system','ostatus_full_threads')) {
      +				logger("Don't import uri ".$first_id." because user ".$uid." doesn't follow the person ".$actor, LOGGER_DEBUG);
      +				continue;
      +			}
      +
      +			$arr = array();
      +			$arr["network"] = $details["network"];
      +			$arr["uri"] = $single_conv->id;
      +			$arr["plink"] = $plink;
      +			$arr["uid"] = $uid;
      +			$arr["contact-id"] = $details["contact_id"];
      +			$arr["parent-uri"] = $parent_uri;
      +			$arr["created"] = $single_conv->published;
      +			$arr["edited"] = $single_conv->published;
      +			$arr["owner-name"] = $single_conv->actor->displayName;
      +			if ($arr["owner-name"] == '')
      +				$arr["owner-name"] = $single_conv->actor->contact->displayName;
      +			if ($arr["owner-name"] == '')
      +				$arr["owner-name"] = $single_conv->actor->portablecontacts_net->displayName;
      +
      +			$arr["owner-link"] = $actor;
      +			$arr["owner-avatar"] = $single_conv->actor->image->url;
      +			$arr["author-name"] = $arr["owner-name"];
      +			$arr["author-link"] = $actor;
      +			$arr["author-avatar"] = $single_conv->actor->image->url;
      +			$arr["body"] = add_page_info_to_body(html2bbcode($single_conv->content));
      +
      +			if (isset($single_conv->status_net->notice_info->source))
      +				$arr["app"] = strip_tags($single_conv->status_net->notice_info->source);
      +			elseif (isset($single_conv->statusnet->notice_info->source))
      +				$arr["app"] = strip_tags($single_conv->statusnet->notice_info->source);
      +			elseif (isset($single_conv->statusnet_notice_info->source))
      +				$arr["app"] = strip_tags($single_conv->statusnet_notice_info->source);
      +			elseif (isset($single_conv->provider->displayName))
      +				$arr["app"] = $single_conv->provider->displayName;
      +			else
      +				$arr["app"] = "OStatus";
      +
      +
      +			$arr["object"] = json_encode($single_conv);
      +			$arr["verb"] = $parent["verb"];
      +			$arr["visible"] = $parent["visible"];
      +			$arr["location"] = $single_conv->location->displayName;
      +			$arr["coord"] = trim($single_conv->location->lat." ".$single_conv->location->lon);
      +
      +			// Is it a reshared item?
      +			if (isset($single_conv->verb) AND ($single_conv->verb == "share") AND isset($single_conv->object)) {
      +				if (is_array($single_conv->object))
      +					$single_conv->object = $single_conv->object[0];
      +
      +				logger("Found reshared item ".$single_conv->object->id);
      +
      +				// $single_conv->object->context->conversation;
      +
      +				if (isset($single_conv->object->object->id))
      +					$arr["uri"] = $single_conv->object->object->id;
      +				else
      +					$arr["uri"] = $single_conv->object->id;
      +
      +				if (isset($single_conv->object->object->url))
      +					$plink = self::convert_href($single_conv->object->object->url);
      +				else
      +					$plink = self::convert_href($single_conv->object->url);
      +
      +				if (isset($single_conv->object->object->content))
      +					$arr["body"] = add_page_info_to_body(html2bbcode($single_conv->object->object->content));
      +				else
      +					$arr["body"] = add_page_info_to_body(html2bbcode($single_conv->object->content));
      +
      +				$arr["plink"] = $plink;
      +
      +				$arr["created"] = $single_conv->object->published;
      +				$arr["edited"] = $single_conv->object->published;
      +
      +				$arr["author-name"] = $single_conv->object->actor->displayName;
      +				if ($arr["owner-name"] == '')
      +					$arr["author-name"] = $single_conv->object->actor->contact->displayName;
      +
      +				$arr["author-link"] = $single_conv->object->actor->url;
      +				$arr["author-avatar"] = $single_conv->object->actor->image->url;
      +
      +				$arr["app"] = $single_conv->object->provider->displayName."#";
      +				//$arr["verb"] = $single_conv->object->verb;
      +
      +				$arr["location"] = $single_conv->object->location->displayName;
      +				$arr["coord"] = trim($single_conv->object->location->lat." ".$single_conv->object->location->lon);
      +			}
      +
      +			if ($arr["location"] == "")
      +				unset($arr["location"]);
      +
      +			if ($arr["coord"] == "")
      +				unset($arr["coord"]);
      +
      +			// Copy fields from given item array
      +			if (isset($item["uri"]) AND (($item["uri"] == $arr["uri"]) OR ($item["uri"] ==  $single_conv->id))) {
      +				$copy_fields = array("owner-name", "owner-link", "owner-avatar", "author-name", "author-link", "author-avatar",
      +							"gravity", "body", "object-type", "object", "verb", "created", "edited", "coord", "tag",
      +							"title", "attach", "app", "type", "location", "contact-id", "uri");
      +				foreach ($copy_fields AS $field)
      +					if (isset($item[$field]))
      +						$arr[$field] = $item[$field];
      +
      +			}
      +
      +			$newitem = item_store($arr);
      +			if (!$newitem) {
      +				logger("Item wasn't stored ".print_r($arr, true), LOGGER_DEBUG);
      +				continue;
      +			}
      +
      +			if (isset($item["uri"]) AND ($item["uri"] == $arr["uri"])) {
      +				$item = array();
      +				$item_stored = $newitem;
      +			}
      +
      +			logger('Stored new item '.$plink.' for parent '.$arr["parent-uri"].' under id '.$newitem, LOGGER_DEBUG);
      +
      +			// Add the conversation entry (but don't fetch the whole conversation)
      +			self::store_conversation($newitem, $conversation_url);
      +
      +			// 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) {
      +				logger('setting new parent to id '.$newitem);
      +				$new_parents = q("SELECT `id`, `uri`, `contact-id`, `type`, `verb`, `visible` FROM `item` WHERE `uid` = %d AND `id` = %d LIMIT 1",
      +					intval($uid), intval($newitem));
      +				if ($new_parents)
      +					$parent = $new_parents[0];
      +			}
      +		}
      +
      +		if (($item_stored < 0) AND (count($item) > 0)) {
      +
      +			if (get_config('system','ostatus_full_threads')) {
      +				$details = self::get_actor_details($item["owner-link"], $uid, $item["contact-id"]);
      +				if ($details["not_following"]) {
      +					logger("Don't import uri ".$item["uri"]." because user ".$uid." doesn't follow the person ".$item["owner-link"], LOGGER_DEBUG);
      +					return false;
      +				}
      +			}
      +
      +			$item_stored = item_store($item, true);
      +			if ($item_stored) {
      +				logger("Uri ".$item["uri"]." wasn't found in conversation ".$conversation_url, LOGGER_DEBUG);
      +				self::store_conversation($item_stored, $conversation_url);
      +			}
      +		}
      +
      +		return($item_stored);
      +	}
      +
      +	private function store_conversation($itemid, $conversation_url) {
      +
      +		$conversation_url = self::convert_href($conversation_url);
      +
      +		$messages = q("SELECT `uid`, `parent`, `created`, `received`, `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($itemid));
      +		if (!$messages)
      +			return;
      +		$message = $messages[0];
      +
      +		// Store conversation url if not done before
      +		$conversation = q("SELECT `url` FROM `term` WHERE `uid` = %d AND `oid` = %d AND `otype` = %d AND `type` = %d",
      +			intval($message["uid"]), intval($itemid), intval(TERM_OBJ_POST), intval(TERM_CONVERSATION));
      +
      +		if (!$conversation) {
      +			$r = q("INSERT INTO `term` (`uid`, `oid`, `otype`, `type`, `term`, `url`, `created`, `received`, `guid`) VALUES (%d, %d, %d, %d, '%s', '%s', '%s', '%s', '%s')",
      +				intval($message["uid"]), intval($itemid), intval(TERM_OBJ_POST), intval(TERM_CONVERSATION),
      +				dbesc($message["created"]), dbesc($conversation_url), dbesc($message["created"]), dbesc($message["received"]), dbesc($message["guid"]));
      +			logger('Storing conversation url '.$conversation_url.' for id '.$itemid);
      +		}
      +	}
      +
      +	private function get_reshared_guid($item) {
      +		$body = trim($item["body"]);
      +
      +		// Skip if it isn't a pure repeated messages
      +		// Does it start with a share?
      +		if (strpos($body, "[share") > 0)
      +			return("");
      +
      +		// Does it end with a share?
      +		if (strlen($body) > (strrpos($body, "[/share]") + 8))
      +			return("");
      +
      +		$attributes = preg_replace("/\[share(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism","$1",$body);
      +		// Skip if there is no shared message in there
      +		if ($body == $attributes)
      +			return(false);
      +
      +		$guid = "";
      +		preg_match("/guid='(.*?)'/ism", $attributes, $matches);
      +		if ($matches[1] != "")
      +			$guid = $matches[1];
      +
      +		preg_match('/guid="(.*?)"/ism', $attributes, $matches);
      +		if ($matches[1] != "")
      +			$guid = $matches[1];
      +
      +		return $guid;
      +	}
      +
      +	private function format_picture_post($body) {
      +		$siteinfo = get_attached_data($body);
      +
      +		if (($siteinfo["type"] == "photo")) {
      +			if (isset($siteinfo["preview"]))
      +				$preview = $siteinfo["preview"];
      +			else
      +				$preview = $siteinfo["image"];
      +
      +			// Is it a remote picture? Then make a smaller preview here
      +			$preview = proxy_url($preview, false, PROXY_SIZE_SMALL);
      +
      +			// Is it a local picture? Then make it smaller here
      +			$preview = str_replace(array("-0.jpg", "-0.png"), array("-2.jpg", "-2.png"), $preview);
      +			$preview = str_replace(array("-1.jpg", "-1.png"), array("-2.jpg", "-2.png"), $preview);
      +
      +			if (isset($siteinfo["url"]))
      +				$url = $siteinfo["url"];
      +			else
      +				$url = $siteinfo["image"];
      +
      +			$body = trim($siteinfo["text"])." [url]".$url."[/url]\n[img]".$preview."[/img]";
      +		}
      +
      +		return $body;
      +	}
      +
      +	private function add_header($doc, $owner) {
      +
      +		$a = get_app();
      +
      +		$root = $doc->createElementNS(NAMESPACE_ATOM1, 'feed');
      +		$doc->appendChild($root);
      +
      +		$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);
      +		xml2::add_element($doc, $root, "generator", FRIENDICA_PLATFORM, $attributes);
      +		xml2::add_element($doc, $root, "id", App::get_baseurl()."/profile/".$owner["nick"]);
      +		xml2::add_element($doc, $root, "title", sprintf("%s timeline", $owner["name"]));
      +		xml2::add_element($doc, $root, "subtitle", sprintf("Updates from %s on %s", $owner["name"], $a->config["sitename"]));
      +		xml2::add_element($doc, $root, "logo", $owner["photo"]);
      +		xml2::add_element($doc, $root, "updated", datetime_convert("UTC", "UTC", "now", ATOM_TIME));
      +
      +		$author = self::add_author($doc, $owner);
      +		$root->appendChild($author);
      +
      +		$attributes = array("href" => $owner["url"], "rel" => "alternate", "type" => "text/html");
      +		xml2::add_element($doc, $root, "link", "", $attributes);
      +
      +		/// @TODO We have to find out what this is
      +		/// $attributes = array("href" => App::get_baseurl()."/sup",
      +		///		"rel" => "http://api.friendfeed.com/2008/03#sup",
      +		///		"type" => "application/json");
      +		/// xml2::add_element($doc, $root, "link", "", $attributes);
      +
      +		self::hublinks($doc, $root);
      +
      +		$attributes = array("href" => App::get_baseurl()."/salmon/".$owner["nick"], "rel" => "salmon");
      +		xml2::add_element($doc, $root, "link", "", $attributes);
      +
      +		$attributes = array("href" => App::get_baseurl()."/salmon/".$owner["nick"], "rel" => "http://salmon-protocol.org/ns/salmon-replies");
      +		xml2::add_element($doc, $root, "link", "", $attributes);
      +
      +		$attributes = array("href" => App::get_baseurl()."/salmon/".$owner["nick"], "rel" => "http://salmon-protocol.org/ns/salmon-mention");
      +		xml2::add_element($doc, $root, "link", "", $attributes);
      +
      +		$attributes = array("href" => App::get_baseurl()."/api/statuses/user_timeline/".$owner["nick"].".atom",
      +				"rel" => "self", "type" => "application/atom+xml");
      +		xml2::add_element($doc, $root, "link", "", $attributes);
      +
      +		return $root;
      +	}
      +
      +	public static function hublinks($doc, $root) {
      +		$hub = get_config('system','huburl');
      +
      +		$hubxml = '';
      +		if(strlen($hub)) {
      +			$hubs = explode(',', $hub);
      +			if(count($hubs)) {
      +				foreach($hubs as $h) {
      +					$h = trim($h);
      +					if(! strlen($h))
      +						continue;
      +					if ($h === '[internal]')
      +						$h = App::get_baseurl() . '/pubsubhubbub';
      +					xml2::add_element($doc, $root, "link", "", array("href" => $h, "rel" => "hub"));
      +				}
      +			}
      +		}
      +	}
      +
      +	private function get_attachment($doc, $root, $item) {
      +		$o = "";
      +		$siteinfo = get_attached_data($item["body"]);
      +
      +		switch($siteinfo["type"]) {
      +			case 'link':
      +				$attributes = array("rel" => "enclosure",
      +						"href" => $siteinfo["url"],
      +						"type" => "text/html; charset=UTF-8",
      +						"length" => "",
      +						"title" => $siteinfo["title"]);
      +				xml2::add_element($doc, $root, "link", "", $attributes);
      +				break;
      +			case 'photo':
      +				$imgdata = get_photo_info($siteinfo["image"]);
      +				$attributes = array("rel" => "enclosure",
      +						"href" => $siteinfo["image"],
      +						"type" => $imgdata["mime"],
      +						"length" => intval($imgdata["size"]));
      +				xml2::add_element($doc, $root, "link", "", $attributes);
      +				break;
      +			case 'video':
      +				$attributes = array("rel" => "enclosure",
      +						"href" => $siteinfo["url"],
      +						"type" => "text/html; charset=UTF-8",
      +						"length" => "",
      +						"title" => $siteinfo["title"]);
      +				xml2::add_element($doc, $root, "link", "", $attributes);
      +				break;
      +			default:
      +				break;
      +		}
      +
      +		if (($siteinfo["type"] != "photo") AND isset($siteinfo["image"])) {
      +			$photodata = get_photo_info($siteinfo["image"]);
      +
      +			$attributes = array("rel" => "preview", "href" => $siteinfo["image"], "media:width" => $photodata[0], "media:height" => $photodata[1]);
      +			xml2::add_element($doc, $root, "link", "", $attributes);
      +		}
      +
      +
      +		$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]);
      +
      +					xml2::add_element($doc, $root, "link", "", $attributes);
      +				}
      +			}
      +		}
      +	}
      +
      +	private function add_author($doc, $owner) {
      +
      +		$r = q("SELECT `homepage` FROM `profile` WHERE `uid` = %d AND `is-default` LIMIT 1", intval($owner["uid"]));
      +		if ($r)
      +			$profile = $r[0];
      +
      +		$author = $doc->createElement("author");
      +		xml2::add_element($doc, $author, "activity:object-type", ACTIVITY_OBJ_PERSON);
      +		xml2::add_element($doc, $author, "uri", $owner["url"]);
      +		xml2::add_element($doc, $author, "name", $owner["name"]);
      +		xml2::add_element($doc, $author, "summary", bbcode($owner["about"], false, false, 7));
      +
      +		$attributes = array("rel" => "alternate", "type" => "text/html", "href" => $owner["url"]);
      +		xml2::add_element($doc, $author, "link", "", $attributes);
      +
      +		$attributes = array(
      +				"rel" => "avatar",
      +				"type" => "image/jpeg", // To-Do?
      +				"media:width" => 175,
      +				"media:height" => 175,
      +				"href" => $owner["photo"]);
      +		xml2::add_element($doc, $author, "link", "", $attributes);
      +
      +		if (isset($owner["thumb"])) {
      +			$attributes = array(
      +					"rel" => "avatar",
      +					"type" => "image/jpeg", // To-Do?
      +					"media:width" => 80,
      +					"media:height" => 80,
      +					"href" => $owner["thumb"]);
      +			xml2::add_element($doc, $author, "link", "", $attributes);
      +		}
      +
      +		xml2::add_element($doc, $author, "poco:preferredUsername", $owner["nick"]);
      +		xml2::add_element($doc, $author, "poco:displayName", $owner["name"]);
      +		xml2::add_element($doc, $author, "poco:note", bbcode($owner["about"], false, false, 7));
      +
      +		if (trim($owner["location"]) != "") {
      +			$element = $doc->createElement("poco:address");
      +			xml2::add_element($doc, $element, "poco:formatted", $owner["location"]);
      +			$author->appendChild($element);
      +		}
      +
      +		if (trim($profile["homepage"]) != "") {
      +			$urls = $doc->createElement("poco:urls");
      +			xml2::add_element($doc, $urls, "poco:type", "homepage");
      +			xml2::add_element($doc, $urls, "poco:value", $profile["homepage"]);
      +			xml2::add_element($doc, $urls, "poco:primary", "true");
      +			$author->appendChild($urls);
      +		}
      +
      +		if (count($profile)) {
      +			xml2::add_element($doc, $author, "followers", "", array("url" => App::get_baseurl()."/viewcontacts/".$owner["nick"]));
      +			xml2::add_element($doc, $author, "statusnet:profile_info", "", array("local_id" => $owner["uid"]));
      +		}
      +
      +		return $author;
      +	}
      +
      +	/**
      +	 * @TODO Picture attachments should look like this:
      +	 *	https://status.pirati.ca/attachment/572819
      +	 *
      +	*/
      +
      +	function construct_verb($item) {
      +		if ($item['verb'])
      +			return $item['verb'];
      +		return ACTIVITY_POST;
      +	}
      +
      +	function construct_objecttype($item) {
      +		if (in_array($item['object-type'], array(ACTIVITY_OBJ_NOTE, ACTIVITY_OBJ_COMMENT)))
      +			return $item['object-type'];
      +		return ACTIVITY_OBJ_NOTE;
      +	}
      +
      +	private function entry($doc, $item, $owner, $toplevel = false) {
      +		$repeated_guid = self::get_reshared_guid($item);
      +		if ($repeated_guid != "")
      +			$xml = self::reshare_entry($doc, $item, $owner, $repeated_guid, $toplevel);
      +
      +		if ($xml)
      +			return $xml;
      +
      +		if ($item["verb"] == ACTIVITY_LIKE)
      +			return self::like_entry($doc, $item, $owner, $toplevel);
      +		else
      +			return self::note_entry($doc, $item, $owner, $toplevel);
      +	}
      +
      +	private function source_entry($doc, $contact) {
      +		$source = $doc->createElement("source");
      +		xml2::add_element($doc, $source, "id", $contact["poll"]);
      +		xml2::add_element($doc, $source, "title", $contact["name"]);
      +		xml2::add_element($doc, $source, "link", "", array("rel" => "alternate",
      +								"type" => "text/html",
      +								"href" => $contact["alias"]));
      +		xml2::add_element($doc, $source, "link", "", array("rel" => "self",
      +								"type" => "application/atom+xml",
      +								"href" => $contact["poll"]));
      +		xml2::add_element($doc, $source, "icon", $contact["photo"]);
      +		xml2::add_element($doc, $source, "updated", datetime_convert("UTC","UTC",$contact["success_update"]."+00:00",ATOM_TIME));
      +
      +		return $source;
      +	}
      +
      +	private function contact_entry($url, $owner) {
      +
      +		$r = q("SELECT * FROM `contact` WHERE `nurl` = '%s' AND `uid` IN (0, %d) ORDER BY `uid` DESC LIMIT 1",
      +			dbesc(normalise_link($url)), intval($owner["uid"]));
      +		if ($r) {
      +			$contact = $r[0];
      +			$contact["uid"] = -1;
      +		}
      +
      +		if (!$r) {
      +			$r = q("SELECT * FROM `gcontact` WHERE `nurl` = '%s' LIMIT 1",
      +				dbesc(normalise_link($url)));
      +			if ($r) {
      +				$contact = $r[0];
      +				$contact["uid"] = -1;
      +				$contact["success_update"] = $contact["updated"];
      +			}
      +		}
      +
      +		if (!$r)
      +			$contact = owner;
      +
      +		if (!isset($contact["poll"])) {
      +			$data = probe_url($url);
      +			$contact["alias"] = $data["alias"];
      +			$contact["poll"] = $data["poll"];
      +		}
      +
      +		if (!isset($contact["alias"]))
      +			$contact["alias"] = $contact["url"];
      +
      +		return $contact;
      +	}
      +
      +	private function reshare_entry($doc, $item, $owner, $repeated_guid, $toplevel) {
      +
      +		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);
      +		}
      +
      +		$title = self::entry_header($doc, $entry, $owner, $toplevel);
      +
      +		$r = q("SELECT * FROM `item` WHERE `uid` = %d AND `guid` = '%s' AND NOT `private` AND `network` IN ('%s', '%s', '%s') LIMIT 1",
      +			intval($owner["uid"]), dbesc($repeated_guid),
      +			dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS));
      +		if ($r)
      +			$repeated_item = $r[0];
      +		else
      +			return false;
      +
      +		$contact = self::contact_entry($repeated_item['author-link'], $owner);
      +
      +		$parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
      +
      +		$title = $owner["nick"]." repeated a notice by ".$contact["nick"];
      +
      +		self::entry_content($doc, $entry, $item, $owner, $title, ACTIVITY_SHARE, false);
      +
      +		$as_object = $doc->createElement("activity:object");
      +
      +// ostatusWaEeYs
      +// ostatusogu9zg - besser
      +		xml2::add_element($doc, $as_object, "activity:object-type", NAMESPACE_ACTIVITY_SCHEMA."activity");
      +
      +		self::entry_content($doc, $as_object, $repeated_item, $owner, "", "", false);
      +
      +		$author = self::add_author($doc, $contact);
      +                $as_object->appendChild($author);
      +
      +		$as_object2 = $doc->createElement("activity:object");
      +
      +		xml2::add_element($doc, $as_object2, "activity:object-type", self::construct_objecttype($repeated_item));
      +
      +		$title = sprintf("New comment by %s", $contact["nick"]);
      +
      +		self::entry_content($doc, $as_object2, $repeated_item, $owner, $title);
      +
      +		$as_object->appendChild($as_object2);
      +
      +		self::entry_footer($doc, $as_object, $item, $owner, false);
      +
      +		$source = self::source_entry($doc, $contact);
      +
      +		$as_object->appendChild($source);
      +
      +		$entry->appendChild($as_object);
      +
      +		self::entry_footer($doc, $entry, $item, $owner);
      +
      +		return $entry;
      +	}
      +
      +	private function like_entry($doc, $item, $owner, $toplevel) {
      +
      +		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);
      +		}
      +
      +		$title = self::entry_header($doc, $entry, $owner, $toplevel);
      +
      +		$verb = NAMESPACE_ACTIVITY_SCHEMA."favorite";
      +		self::entry_content($doc, $entry, $item, $owner, "Favorite", $verb, false);
      +
      +		$as_object = $doc->createElement("activity:object");
      +
      +		$parent = q("SELECT * FROM `item` WHERE `id` = %d", intval($item["parent"]));
      +		$parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
      +
      +		xml2::add_element($doc, $as_object, "activity:object-type", self::construct_objecttype($parent[0]));
      +
      +		self::entry_content($doc, $as_object, $parent[0], $owner, "New entry");
      +
      +		$entry->appendChild($as_object);
      +
      +		self::entry_footer($doc, $entry, $item, $owner);
      +
      +		return $entry;
      +	}
      +
      +	private function note_entry($doc, $item, $owner, $toplevel) {
      +
      +		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);
      +		}
      +
      +		$title = self::entry_header($doc, $entry, $owner, $toplevel);
      +
      +		xml2::add_element($doc, $entry, "activity:object-type", ACTIVITY_OBJ_NOTE);
      +
      +		self::entry_content($doc, $entry, $item, $owner, $title);
      +
      +		self::entry_footer($doc, $entry, $item, $owner);
      +
      +		return $entry;
      +	}
      +
      +	private function entry_header($doc, &$entry, $owner, $toplevel) {
      +		if (!$toplevel) {
      +			$entry = $doc->createElement("entry");
      +			$title = sprintf("New note by %s", $owner["nick"]);
      +		} else {
      +			$entry = $doc->createElementNS(NAMESPACE_ATOM1, "entry");
      +
      +			$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 = self::add_author($doc, $owner);
      +			$entry->appendChild($author);
      +
      +			$title = sprintf("New comment by %s", $owner["nick"]);
      +		}
      +		return $title;
      +	}
      +
      +	private function entry_content($doc, $entry, $item, $owner, $title, $verb = "", $complete = true) {
      +
      +		if ($verb == "")
      +			$verb = self::construct_verb($item);
      +
      +		xml2::add_element($doc, $entry, "id", $item["uri"]);
      +		xml2::add_element($doc, $entry, "title", $title);
      +
      +		$body = self::format_picture_post($item['body']);
      +
      +		if ($item['title'] != "")
      +			$body = "[b]".$item['title']."[/b]\n\n".$body;
      +
      +		$body = bbcode($body, false, false, 7);
      +
      +		xml2::add_element($doc, $entry, "content", $body, array("type" => "html"));
      +
      +		xml2::add_element($doc, $entry, "link", "", array("rel" => "alternate", "type" => "text/html",
      +								"href" => App::get_baseurl()."/display/".$item["guid"]));
      +
      +		if ($complete)
      +			xml2::add_element($doc, $entry, "status_net", "", array("notice_id" => $item["id"]));
      +
      +		xml2::add_element($doc, $entry, "activity:verb", $verb);
      +
      +		xml2::add_element($doc, $entry, "published", datetime_convert("UTC","UTC",$item["created"]."+00:00",ATOM_TIME));
      +		xml2::add_element($doc, $entry, "updated", datetime_convert("UTC","UTC",$item["edited"]."+00:00",ATOM_TIME));
      +	}
      +
      +	private function entry_footer($doc, $entry, $item, $owner, $complete = true) {
      +
      +		$mentioned = array();
      +
      +		if (($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
      +			$parent = q("SELECT `guid`, `author-link`, `owner-link` 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"]);
      +			xml2::add_element($doc, $entry, "thr:in-reply-to", "", $attributes);
      +
      +			$attributes = array(
      +					"rel" => "related",
      +					"href" => App::get_baseurl()."/display/".$parent[0]["guid"]);
      +			xml2::add_element($doc, $entry, "link", "", $attributes);
      +
      +			$mentioned[$parent[0]["author-link"]] = $parent[0]["author-link"];
      +			$mentioned[$parent[0]["owner-link"]] = $parent[0]["owner-link"];
      +
      +			$thrparent = q("SELECT `guid`, `author-link`, `owner-link` FROM `item` WHERE `uid` = %d AND `uri` = '%s'",
      +					intval($owner["uid"]),
      +					dbesc($parent_item));
      +			if ($thrparent) {
      +				$mentioned[$thrparent[0]["author-link"]] = $thrparent[0]["author-link"];
      +				$mentioned[$thrparent[0]["owner-link"]] = $thrparent[0]["owner-link"];
      +			}
      +		}
      +
      +		xml2::add_element($doc, $entry, "link", "", array("rel" => "ostatus:conversation",
      +							"href" => App::get_baseurl()."/display/".$owner["nick"]."/".$item["parent"]));
      +		xml2::add_element($doc, $entry, "ostatus:conversation", App::get_baseurl()."/display/".$owner["nick"]."/".$item["parent"]);
      +
      +		$tags = item_getfeedtags($item);
      +
      +		if(count($tags))
      +			foreach($tags as $t)
      +				if ($t[0] == "@")
      +					$mentioned[$t[1]] = $t[1];
      +
      +		// Make sure that mentions are accepted (GNU Social has problems with mixing HTTP and HTTPS)
      +		$newmentions = array();
      +		foreach ($mentioned AS $mention) {
      +			$newmentions[str_replace("http://", "https://", $mention)] = str_replace("http://", "https://", $mention);
      +			$newmentions[str_replace("https://", "http://", $mention)] = str_replace("https://", "http://", $mention);
      +		}
      +		$mentioned = $newmentions;
      +
      +		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"])
      +				xml2::add_element($doc, $entry, "link", "", array("rel" => "mentioned",
      +											"ostatus:object-type" => ACTIVITY_OBJ_GROUP,
      +											"href" => $mention));
      +			else
      +				xml2::add_element($doc, $entry, "link", "", array("rel" => "mentioned",
      +											"ostatus:object-type" => ACTIVITY_OBJ_PERSON,
      +											"href" => $mention));
      +		}
      +
      +		if (!$item["private"]) {
      +			xml2::add_element($doc, $entry, "link", "", array("rel" => "ostatus:attention",
      +									"href" => "http://activityschema.org/collection/public"));
      +			xml2::add_element($doc, $entry, "link", "", array("rel" => "mentioned",
      +									"ostatus:object-type" => "http://activitystrea.ms/schema/1.0/collection",
      +									"href" => "http://activityschema.org/collection/public"));
      +		}
      +
      +		if(count($tags))
      +			foreach($tags as $t)
      +				if ($t[0] != "@")
      +					xml2::add_element($doc, $entry, "category", "", array("term" => $t[2]));
      +
      +		self::get_attachment($doc, $entry, $item);
      +
      +		if ($complete) {
      +			$app = $item["app"];
      +			if ($app == "")
      +				$app = "web";
      +
      +			$attributes = array("local_id" => $item["id"], "source" => $app);
      +
      +			if (isset($parent["id"]))
      +				$attributes["repeat_of"] = $parent["id"];
      +
      +			if ($item["coord"] != "")
      +				xml2::add_element($doc, $entry, "georss:point", $item["coord"]);
      +
      +			xml2::add_element($doc, $entry, "statusnet:notice_info", "", $attributes);
      +		}
      +	}
      +
      +	public static function feed(&$a, $owner_nick, $last_update) {
      +
      +		$r = q("SELECT `contact`.*, `user`.`nickname`, `user`.`timezone`, `user`.`page-flags`
      +				FROM `contact` INNER JOIN `user` ON `user`.`uid` = `contact`.`uid`
      +				WHERE `contact`.`self` AND `user`.`nickname` = '%s' LIMIT 1",
      +				dbesc($owner_nick));
      +		if (!$r)
      +			return;
      +
      +		$owner = $r[0];
      +
      +		if(!strlen($last_update))
      +			$last_update = 'now -30 days';
      +
      +		$check_date = datetime_convert('UTC','UTC',$last_update,'Y-m-d H:i:s');
      +
      +		$items = q("SELECT STRAIGHT_JOIN `item`.*, `item`.`id` AS `item_id` FROM `item`
      +				INNER JOIN `thread` ON `thread`.`iid` = `item`.`parent`
      +				LEFT JOIN `item` AS `thritem` ON `thritem`.`uri`=`item`.`thr-parent` AND `thritem`.`uid`=`item`.`uid`
      +				WHERE `item`.`uid` = %d AND `item`.`received` > '%s' AND NOT `item`.`private` AND NOT `item`.`deleted`
      +					AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = '' AND `item`.`deny_cid`  = '' AND `item`.`deny_gid`  = ''
      +					AND ((`item`.`wall` AND (`item`.`parent` = `item`.`id`))
      +						OR (`item`.`network` = '%s' AND ((`thread`.`network` IN ('%s', '%s')) OR (`thritem`.`network` IN ('%s', '%s')))) AND `thread`.`mention`)
      +					AND ((`item`.`owner-link` IN ('%s', '%s') AND (`item`.`parent` = `item`.`id`))
      +						OR (`item`.`author-link` IN ('%s', '%s')))
      +				ORDER BY `item`.`received` DESC
      +				LIMIT 0, 300",
      +				intval($owner["uid"]), dbesc($check_date), dbesc(NETWORK_DFRN),
      +				//dbesc(NETWORK_OSTATUS), dbesc(NETWORK_OSTATUS),
      +				//dbesc(NETWORK_OSTATUS), dbesc(NETWORK_OSTATUS),
      +				dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN),
      +				dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN),
      +				dbesc($owner["nurl"]), dbesc(str_replace("http://", "https://", $owner["nurl"])),
      +				dbesc($owner["nurl"]), dbesc(str_replace("http://", "https://", $owner["nurl"]))
      +			);
      +
      +		$doc = new DOMDocument('1.0', 'utf-8');
      +		$doc->formatOutput = true;
      +
      +		$root = self::add_header($doc, $owner);
      +
      +		foreach ($items AS $item) {
      +			$entry = self::entry($doc, $item, $owner);
      +			$root->appendChild($entry);
      +		}
      +
      +		return(trim($doc->saveXML()));
      +	}
      +
      +	public static function salmon($item,$owner) {
      +
      +		$doc = new DOMDocument('1.0', 'utf-8');
      +		$doc->formatOutput = true;
      +
      +		$entry = self::entry($doc, $item, $owner, true);
      +
      +		$doc->appendChild($entry);
      +
      +		return(trim($doc->saveXML()));
      +	}
      +}
      +?>
      diff --git a/include/pubsubpublish.php b/include/pubsubpublish.php
      index 4fbb505146..625eefc261 100644
      --- a/include/pubsubpublish.php
      +++ b/include/pubsubpublish.php
      @@ -16,7 +16,7 @@ function handle_pubsubhubbub() {
       
       		logger("Generate feed for user ".$rr['nickname']." - last updated ".$rr['last_update'], LOGGER_DEBUG);
       
      -		$params = ostatus_feed($a, $rr['nickname'], $rr['last_update']);
      +		$params = ostatus::feed($a, $rr['nickname'], $rr['last_update']);
       		$hmac_sig = hash_hmac("sha1", $params, $rr['secret']);
       
       		$headers = array("Content-type: application/atom+xml",
      diff --git a/mod/salmon.php b/mod/salmon.php
      index 9c22e42d11..37230a5573 100644
      --- a/mod/salmon.php
      +++ b/mod/salmon.php
      @@ -84,7 +84,7 @@ function salmon_post(&$a) {
       	// decode the data
       	$data = base64url_decode($data);
       
      -	$author = ostatus_salmon_author($data,$importer);
      +	$author = ostatus::salmon_author($data,$importer);
       	$author_link = $author["author-link"];
       
       	if(! $author_link) {
      @@ -181,7 +181,7 @@ function salmon_post(&$a) {
       
       	$contact_rec = ((count($r)) ? $r[0] : null);
       
      -	ostatus_import($data,$importer,$contact_rec, $hub);
      +	ostatus::import($data,$importer,$contact_rec, $hub);
       
       	http_status_exit(200);
       }
      
      From abf6fb10a5e86fda973882f226587de8a45e677b Mon Sep 17 00:00:00 2001
      From: Michael Vogel 
      Date: Wed, 30 Mar 2016 12:46:10 +0200
      Subject: [PATCH 087/211] "Scrape" now respects the new url formats with
       "index.php"
      
      ---
       include/Scrape.php | 49 ++++++++++++++++++++++++++++++++++++++++------
       1 file changed, 43 insertions(+), 6 deletions(-)
      
      diff --git a/include/Scrape.php b/include/Scrape.php
      index 03d21047e7..deff0b080f 100644
      --- a/include/Scrape.php
      +++ b/include/Scrape.php
      @@ -356,7 +356,7 @@ function probe_url($url, $mode = PROBE_NORMAL, $level = 1) {
       
       	$result = array();
       
      -	if(! $url)
      +	if (!$url)
       		return $result;
       
       	$result = Cache::get("probe_url:".$mode.":".$url);
      @@ -365,6 +365,7 @@ function probe_url($url, $mode = PROBE_NORMAL, $level = 1) {
       		return $result;
       	}
       
      +	$original_url = $url;
       	$network = null;
       	$diaspora = false;
       	$diaspora_base = '';
      @@ -393,7 +394,12 @@ function probe_url($url, $mode = PROBE_NORMAL, $level = 1) {
       		else
       			$links = lrdd($url);
       
      -		if(count($links)) {
      +		if ((count($links) == 0) AND strstr($url, "/index.php")) {
      +			$url = str_replace("/index.php", "", $url);
      +			$links = lrdd($url);
      +		}
      +
      +		if (count($links)) {
       			$has_lrdd = true;
       
       			logger('probe_url: found lrdd links: ' . print_r($links,true), LOGGER_DATA);
      @@ -440,12 +446,21 @@ function probe_url($url, $mode = PROBE_NORMAL, $level = 1) {
       			// aliases, let's hope we're lucky and get one that matches the feed author-uri because
       			// otherwise we're screwed.
       
      +			$backup_alias = "";
      +
       			foreach($links as $link) {
       				if($link['@attributes']['rel'] === 'alias') {
       					if(strpos($link['@attributes']['href'],'@') === false) {
       						if(isset($profile)) {
      -							if(($link['@attributes']['href'] !== $profile) AND ($alias == ""))
      -								$alias = unamp($link['@attributes']['href']);
      +							$alias_url = $link['@attributes']['href'];
      +
      +							if(($alias_url !== $profile) AND ($backup_alias == "") AND
      +								($alias_url !== str_replace("/index.php", "", $profile)))
      +								$backup_alias = $alias_url;
      +
      +							if(($alias_url !== $profile) AND !strstr($alias_url, "index.php") AND
      +								($alias_url !== str_replace("/index.php", "", $profile)))
      +								$alias = $alias_url;
       						}
       						else
       							$profile = unamp($link['@attributes']['href']);
      @@ -453,6 +468,9 @@ function probe_url($url, $mode = PROBE_NORMAL, $level = 1) {
       				}
       			}
       
      +			if ($alias == "")
      +				$alias = $backup_alias;
      +
       			// If the profile is different from the url then the url is abviously an alias
       			if (($alias == "") AND ($profile != "") AND !$at_addr AND (normalise_link($profile) != normalise_link($url)))
       				$alias = $url;
      @@ -769,6 +787,9 @@ function probe_url($url, $mode = PROBE_NORMAL, $level = 1) {
       	if (($baseurl == "") AND ($poll != ""))
       		$baseurl = matching_url(normalise_link($profile), normalise_link($poll));
       
      +	if (substr($baseurl, -10) == "/index.php")
      +		$baseurl = str_replace("/index.php", "", $baseurl);
      +
       	$baseurl = rtrim($baseurl, "/");
       
       	if(strpos($url,'@') AND ($addr == "") AND ($network == NETWORK_DFRN))
      @@ -816,8 +837,24 @@ function probe_url($url, $mode = PROBE_NORMAL, $level = 1) {
       	}
       
       	// Only store into the cache if the value seems to be valid
      -	if ($result['network'] != NETWORK_PHANTOM)
      -		Cache::set("probe_url:".$mode.":".$url,serialize($result), CACHE_DAY);
      +	if ($result['network'] != NETWORK_PHANTOM) {
      +		Cache::set("probe_url:".$mode.":".$original_url,serialize($result), CACHE_DAY);
      +
      +		/// @todo temporary fix - we need a real contact update function that updates only changing fields
      +		/// The biggest problem is the avatar picture that could have a reduced image size.
      +		/// It should only be updated if the existing picture isn't existing anymore.
      +		if (($result['network'] != NETWORK_FEED) AND $result["addr"] AND $result["name"] AND $result["nick"])
      +			q("UPDATE `contact` SET `addr` = '%s', `alias` = '%s', `name` = '%s', `nick` = '%s',
      +				`name-date` = '%s', `uri-date` = '%s' WHERE `nurl` = '%s' AND NOT `self`",
      +				dbesc($result["addr"]),
      +				dbesc($result["alias"]),
      +				dbesc($result["name"]),
      +				dbesc($result["nick"]),
      +				dbesc(datetime_convert()),
      +				dbesc(datetime_convert()),
      +				dbesc(normalise_link($result['url']))
      +		);
      +	}
       
       	return $result;
       }
      
      From ebd1b31473880578203d55f55e9400b23f808b86 Mon Sep 17 00:00:00 2001
      From: Michael Vogel 
      Date: Wed, 30 Mar 2016 23:25:20 +0200
      Subject: [PATCH 088/211] OStatus class is now moved into the right place
      
      ---
       include/cron.php    |    4 +-
       include/dfrn.php    |  191 +--
       include/ostatus.php | 3081 ++++++++++++++++++++++---------------------
       include/xml.php     |   35 +
       4 files changed, 1688 insertions(+), 1623 deletions(-)
      
      diff --git a/include/cron.php b/include/cron.php
      index 60c62786e6..c60284b738 100644
      --- a/include/cron.php
      +++ b/include/cron.php
      @@ -101,10 +101,10 @@ function cron_run(&$argv, &$argc){
       
       	// Check OStatus conversations
       	// Check only conversations with mentions (for a longer time)
      -	check_conversations(true);
      +	ostatus::check_conversations(true);
       
       	// Check every conversation
      -	check_conversations(false);
      +	ostatus::check_conversations(false);
       
       	// Set the gcontact-id in the item table if missing
       	item_set_gcontact();
      diff --git a/include/dfrn.php b/include/dfrn.php
      index d96805a56b..14be747305 100644
      --- a/include/dfrn.php
      +++ b/include/dfrn.php
      @@ -19,6 +19,7 @@ require_once("include/text.php");
       require_once("include/oembed.php");
       require_once("include/html2bbcode.php");
       require_once("include/bbcode.php");
      +require_once("include/xml.php");
       
       /**
        * @brief This class contain functions to create and send DFRN XML files
      @@ -286,17 +287,17 @@ class dfrn {
       		$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']);
      +		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']);
      +		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);
       
      @@ -319,11 +320,11 @@ class dfrn {
       
       		$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']);
      +		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);
       
      @@ -365,16 +366,16 @@ class dfrn {
       
       		$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'));
      +		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);
       
      @@ -410,39 +411,39 @@ class dfrn {
       		$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"]);
      +		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);
      +		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);
      +		xml::add_element($doc, $root, "link", "", $attributes);
       
       		$attributes = array("rel" => "alternate", "type" => "text/html", "href" => $alternatelink);
      -		xml_add_element($doc, $root, "link", "", $attributes);
      +		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);
      +			ostatus::hublinks($doc, $root);
       
       			$attributes = array("rel" => "salmon", "href" => app::get_baseurl()."/salmon/".$owner["nick"]);
      -			xml_add_element($doc, $root, "link", "", $attributes);
      +			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);
      +			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);
      +			xml::add_element($doc, $root, "link", "", $attributes);
       		}
       
       		if ($owner['page-flags'] == PAGE_COMMUNITY)
      -			xml_add_element($doc, $root, "dfrn:community", 1);
      +			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));
      +		xml::add_element($doc, $root, "updated", datetime_convert("UTC", "UTC", "now", ATOM_TIME));
       
       		$author = self::add_author($doc, $owner, $authorelement, $public);
       		$root->appendChild($author);
      @@ -468,26 +469,26 @@ class dfrn {
       		$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);
      +		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);
      +		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);
      +		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);
      +		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);
      +		xml::add_element($doc, $author, "link", "", $attributes);
       
       		$birthday = feed_birthday($owner['uid'], $owner['timezone']);
       
       		if ($birthday)
      -			xml_add_element($doc, $author, "dfrn:birthday", $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)
      @@ -502,25 +503,25 @@ class dfrn {
       			intval($owner['uid']));
       		if ($r) {
       			$profile = $r[0];
      -			xml_add_element($doc, $author, "poco:displayName", $profile["name"]);
      -			xml_add_element($doc, $author, "poco:updated", $namdate);
      +			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: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"]);
      +			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"));
      +			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");
      +				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);
       			}
       
      @@ -528,7 +529,7 @@ class dfrn {
       				$keywords = explode(",", $profile["pub_keywords"]);
       
       				foreach ($keywords AS $keyword)
      -					xml_add_element($doc, $author, "poco:tags", trim($keyword));
      +					xml::add_element($doc, $author, "poco:tags", trim($keyword));
       
       			}
       
      @@ -536,25 +537,25 @@ class dfrn {
       			$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");
      +				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));
      +				xml::add_element($doc, $element, "poco:formatted", formatted_location($profile));
       
       				if (trim($profile["locality"]) != "")
      -					xml_add_element($doc, $element, "poco:locality", $profile["locality"]);
      +					xml::add_element($doc, $element, "poco:locality", $profile["locality"]);
       
       				if (trim($profile["region"]) != "")
      -					xml_add_element($doc, $element, "poco:region", $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"]);
      +					xml::add_element($doc, $element, "poco:country", $profile["country-name"]);
       
       				$author->appendChild($element);
       			}
      @@ -578,9 +579,9 @@ class dfrn {
       		$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"]);
      +		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
      @@ -591,7 +592,7 @@ class dfrn {
       				"media:width" => 80,
       				"media:height" => 80,
       				"href" => $contact["photo"]);
      -		xml_add_element($doc, $author, "link", "", $attributes);
      +		xml::add_element($doc, $author, "link", "", $attributes);
       
       		$attributes = array(
       				"rel" => "avatar",
      @@ -599,7 +600,7 @@ class dfrn {
       				"media:width" => 80,
       				"media:height" => 80,
       				"href" => $contact["photo"]);
      -		xml_add_element($doc, $author, "link", "", $attributes);
      +		xml::add_element($doc, $author, "link", "", $attributes);
       
       		return $author;
       	}
      @@ -622,11 +623,11 @@ class dfrn {
       			if(!$r)
       				return false;
       			if($r->type)
      -				xml_add_element($doc, $entry, "activity:object-type", $r->type);
      +				xml::add_element($doc, $entry, "activity:object-type", $r->type);
       			if($r->id)
      -				xml_add_element($doc, $entry, "id", $r->id);
      +				xml::add_element($doc, $entry, "id", $r->id);
       			if($r->title)
      -				xml_add_element($doc, $entry, "title", $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,'&')))
      @@ -641,16 +642,16 @@ class dfrn {
       							$attributes = array();
       							foreach ($link->attributes() AS $parameter => $value)
       								$attributes[$parameter] = $value;
      -							xml_add_element($doc, $entry, "link", "", $attributes);
      +							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);
      +					xml::add_element($doc, $entry, "link", "", $attributes);
       				}
       			}
       			if($r->content)
      -				xml_add_element($doc, $entry, "content", bbcode($r->content), array("type" => "html"));
      +				xml::add_element($doc, $entry, "content", bbcode($r->content), array("type" => "html"));
       
       			return $entry;
       		}
      @@ -684,7 +685,7 @@ class dfrn {
       					if(trim($matches[4]) != "")
       						$attributes["title"] = trim($matches[4]);
       
      -					xml_add_element($doc, $root, "link", "", $attributes);
      +					xml::add_element($doc, $root, "link", "", $attributes);
       				}
       			}
       		}
      @@ -711,7 +712,7 @@ class dfrn {
       
       		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);
      +			return xml::create_element($doc, "at:deleted-entry", "", $attributes);
       		}
       
       		$entry = $doc->createElement("entry");
      @@ -745,66 +746,66 @@ class dfrn {
       			$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, "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, "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));
      +		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));
      +		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));
      +		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",
      +		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']));
      +			xml::add_element($doc, $entry, "dfrn:comment-allow", intval($item['last-child']));
       
       		if($item['location'])
      -			xml_add_element($doc, $entry, "dfrn:location", $item['location']);
      +			xml::add_element($doc, $entry, "dfrn:location", $item['location']);
       
       		if($item['coord'])
      -			xml_add_element($doc, $entry, "georss:point", $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));
      +			xml::add_element($doc, $entry, "dfrn:private", (($item['private']) ? $item['private'] : 1));
       
       		if($item['extid'])
      -			xml_add_element($doc, $entry, "dfrn:extid", $item['extid']);
      +			xml::add_element($doc, $entry, "dfrn:extid", $item['extid']);
       
       		if($item['bookmark'])
      -			xml_add_element($doc, $entry, "dfrn:bookmark", "true");
      +			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, "statusnet:notice_info", "", array("local_id" => $item['id'], "source" => $item['app']));
       
      -		xml_add_element($doc, $entry, "dfrn:diaspora_guid", $item["guid"]);
      +		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, "dfrn:diaspora_signature", $sign);
       		}
       
      -		xml_add_element($doc, $entry, "activity:verb", construct_verb($item));
      +		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']);
      +			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);
      +			xml::add_element($doc, $entry, "activity:object-type", ACTIVITY_OBJ_NOTE);
       		else
      -			xml_add_element($doc, $entry, "activity:object-type", ACTIVITY_OBJ_COMMENT);
      +			xml::add_element($doc, $entry, "activity:object-type", ACTIVITY_OBJ_COMMENT);
       
       		$actobj = self::create_activity($doc, "activity:object", $item['object']);
       		if ($actobj)
      @@ -819,7 +820,7 @@ class dfrn {
       		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]));
      +					xml::add_element($doc, $entry, "category", "", array("scheme" => "X-DFRN:".$t[0].":".$t[1], "term" => $t[2]));
       		}
       
       		if(count($tags))
      @@ -832,11 +833,11 @@ class dfrn {
       				intval($owner["uid"]),
       				dbesc(normalise_link($mention)));
       			if ($r[0]["forum"] OR $r[0]["prv"])
      -				xml_add_element($doc, $entry, "link", "", array("rel" => "mentioned",
      +				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",
      +				xml::add_element($doc, $entry, "link", "", array("rel" => "mentioned",
       											"ostatus:object-type" => ACTIVITY_OBJ_PERSON,
       											"href" => $mention));
       		}
      @@ -1323,7 +1324,7 @@ class dfrn {
       		$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);
      +		xml::add_element($obj_doc, $obj_element, "type", $activity_type);
       
       		$id = $xpath->query("atom:id", $activity)->item(0);
       		if (is_object($id))
      diff --git a/include/ostatus.php b/include/ostatus.php
      index 02447e3ade..ba5b80cd5d 100644
      --- a/include/ostatus.php
      +++ b/include/ostatus.php
      @@ -12,485 +12,429 @@ require_once("include/Scrape.php");
       require_once("include/follow.php");
       require_once("include/api.php");
       require_once("mod/proxy.php");
      -require_once("include/ostatus2.php");
      +require_once("include/xml.php");
       
      -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
      +class ostatus {
      +	const OSTATUS_DEFAULT_POLL_INTERVAL = 30; // given in minutes
      +	const OSTATUS_DEFAULT_POLL_TIMEFRAME = 1440; // given in minutes
      +	const OSTATUS_DEFAULT_POLL_TIMEFRAME_MENTIONS = 14400; // given in minutes
       
      -function ostatus_check_follow_friends() {
      -	$r = q("SELECT `uid`,`v` FROM `pconfig` WHERE `cat`='system' AND `k`='ostatus_legacy_contact' AND `v` != ''");
      +	private function fetchauthor($xpath, $context, $importer, &$contact, $onlyfetch) {
       
      -	if (!$r)
      -		return;
      +		$author = array();
      +		$author["author-link"] = $xpath->evaluate('atom:author/atom:uri/text()', $context)->item(0)->nodeValue;
      +		$author["author-name"] = $xpath->evaluate('atom:author/atom:name/text()', $context)->item(0)->nodeValue;
       
      -	foreach ($r AS $contact) {
      -		ostatus_follow_friends($contact["uid"], $contact["v"]);
      -		set_pconfig($contact["uid"], "system", "ostatus_legacy_contact", "");
      -	}
      -}
      +		// Preserve the value
      +		$authorlink = $author["author-link"];
       
      -// This function doesn't work reliable by now.
      -function ostatus_follow_friends($uid, $url) {
      -	$contact = probe_url($url);
      +		$alternate = $xpath->query("atom:author/atom:link[@rel='alternate']", $context)->item(0)->attributes;
      +		if (is_object($alternate))
      +			foreach($alternate AS $attributes)
      +				if ($attributes->name == "href")
      +					$author["author-link"] = $attributes->textContent;
       
      -	if (!$contact)
      -		return;
      +		$r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `nurl` IN ('%s', '%s') AND `network` != '%s'",
      +			intval($importer["uid"]), dbesc(normalise_link($author["author-link"])),
      +			dbesc(normalise_link($authorlink)), dbesc(NETWORK_STATUSNET));
      +		if ($r) {
      +			$contact = $r[0];
      +			$author["contact-id"] = $r[0]["id"];
      +		} else
      +			$author["contact-id"] = $contact["id"];
       
      -	$api = $contact["baseurl"]."/api/";
      -
      -	// Fetching friends
      -	$data = z_fetch_url($api."statuses/friends.json?screen_name=".$contact["nick"]);
      -
      -	if (!$data["success"])
      -		return;
      -
      -	$friends = json_decode($data["body"]);
      -
      -	foreach ($friends AS $friend) {
      -		$url = $friend->statusnet_profile_url;
      -		$r = q("SELECT `url` FROM `contact` WHERE `uid` = %d AND
      -			(`nurl` = '%s' OR `alias` = '%s' OR `alias` = '%s') AND
      -			`network` != '%s' LIMIT 1",
      -			intval($uid), dbesc(normalise_link($url)),
      -			dbesc(normalise_link($url)), dbesc($url), dbesc(NETWORK_STATUSNET));
      -		if (!$r) {
      -			$data = probe_url($friend->statusnet_profile_url);
      -			if ($data["network"] == NETWORK_OSTATUS) {
      -				$result = new_contact($uid,$friend->statusnet_profile_url);
      -				if ($result["success"])
      -					logger($friend->name." ".$url." - success", LOGGER_DEBUG);
      -				else
      -					logger($friend->name." ".$url." - failed", LOGGER_DEBUG);
      -			} else
      -				logger($friend->name." ".$url." - not OStatus", LOGGER_DEBUG);
      +		$avatarlist = array();
      +		$avatars = $xpath->query("atom:author/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 (($width > 0) AND ($href != ""))
      +				$avatarlist[$width] = $href;
       		}
      -	}
      -}
      -
      -function ostatus_fetchauthor($xpath, $context, $importer, &$contact, $onlyfetch) {
      -
      -	$author = array();
      -	$author["author-link"] = $xpath->evaluate('atom:author/atom:uri/text()', $context)->item(0)->nodeValue;
      -	$author["author-name"] = $xpath->evaluate('atom:author/atom:name/text()', $context)->item(0)->nodeValue;
      -
      -	// Preserve the value
      -	$authorlink = $author["author-link"];
      -
      -	$alternate = $xpath->query("atom:author/atom:link[@rel='alternate']", $context)->item(0)->attributes;
      -	if (is_object($alternate))
      -		foreach($alternate AS $attributes)
      -			if ($attributes->name == "href")
      -				$author["author-link"] = $attributes->textContent;
      -
      -	$r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `nurl` IN ('%s', '%s') AND `network` != '%s'",
      -		intval($importer["uid"]), dbesc(normalise_link($author["author-link"])),
      -		dbesc(normalise_link($authorlink)), dbesc(NETWORK_STATUSNET));
      -	if ($r) {
      -		$contact = $r[0];
      -		$author["contact-id"] = $r[0]["id"];
      -	} else
      -		$author["contact-id"] = $contact["id"];
      -
      -	$avatarlist = array();
      -	$avatars = $xpath->query("atom:author/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 (($width > 0) AND ($href != ""))
      -			$avatarlist[$width] = $href;
      -	}
      -	if (count($avatarlist) > 0) {
      -		krsort($avatarlist);
      -		$author["author-avatar"] = current($avatarlist);
      -	}
      -
      -	$displayname = $xpath->evaluate('atom:author/poco:displayName/text()', $context)->item(0)->nodeValue;
      -	if ($displayname != "")
      -		$author["author-name"] = $displayname;
      -
      -	$author["owner-name"] = $author["author-name"];
      -	$author["owner-link"] = $author["author-link"];
      -	$author["owner-avatar"] = $author["author-avatar"];
      -
      -	// Only update the contacts if it is an OStatus contact
      -	if ($r AND !$onlyfetch AND ($contact["network"] == NETWORK_OSTATUS)) {
      -		// Update contact data
      -
      -		$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);
      -
      -			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"]));
      -
      -			poco_check($contact["url"], $contact["name"], $contact["network"], $author["author-avatar"], $contact["about"], $contact["location"],
      -						"", "", "", datetime_convert(), 2, $contact["id"], $contact["uid"]);
      +		if (count($avatarlist) > 0) {
      +			krsort($avatarlist);
      +			$author["author-avatar"] = current($avatarlist);
       		}
       
      -		if (isset($author["author-avatar"]) AND ($author["author-avatar"] != $r[0]['avatar'])) {
      -			logger("Update profile picture for contact ".$contact["id"], LOGGER_DEBUG);
      +		$displayname = $xpath->evaluate('atom:author/poco:displayName/text()', $context)->item(0)->nodeValue;
      +		if ($displayname != "")
      +			$author["author-name"] = $displayname;
       
      -			update_contact_avatar($author["author-avatar"], $importer["uid"], $contact["id"]);
      -		}
      +		$author["owner-name"] = $author["author-name"];
      +		$author["owner-link"] = $author["author-link"];
      +		$author["owner-avatar"] = $author["author-avatar"];
       
      -		$contact["generation"] = 2;
      -		$contact["photo"] = $author["author-avatar"];
      -		update_gcontact($contact);
      -	}
      +		// Only update the contacts if it is an OStatus contact
      +		if ($r AND !$onlyfetch AND ($contact["network"] == NETWORK_OSTATUS)) {
      +			// Update contact data
       
      -	return($author);
      -}
      +			$value = $xpath->query("atom:link[@rel='salmon']", $context)->item(0)->nodeValue;
      +			if ($value != "")
      +				$contact["notify"] = $value;
       
      -function ostatus_salmon_author($xml, $importer) {
      -	$a = get_app();
      +			$value = $xpath->evaluate('atom:author/uri/text()', $context)->item(0)->nodeValue;
      +			if ($value != "")
      +				$contact["alias"] = $value;
       
      -	if ($xml == "")
      -		return;
      +			$value = $xpath->evaluate('atom:author/poco:displayName/text()', $context)->item(0)->nodeValue;
      +			if ($value != "")
      +				$contact["name"] = $value;
       
      -	$doc = new DOMDocument();
      -	@$doc->loadXML($xml);
      +			$value = $xpath->evaluate('atom:author/poco:preferredUsername/text()', $context)->item(0)->nodeValue;
      +			if ($value != "")
      +				$contact["nick"] = $value;
       
      -	$xpath = new DomXPath($doc);
      -	$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);
      +			$value = $xpath->evaluate('atom:author/poco:note/text()', $context)->item(0)->nodeValue;
      +			if ($value != "")
      +				$contact["about"] = html2bbcode($value);
       
      -	$entries = $xpath->query('/atom:entry');
      +			$value = $xpath->evaluate('atom:author/poco:address/poco:formatted/text()', $context)->item(0)->nodeValue;
      +			if ($value != "")
      +				$contact["location"] = $value;
       
      -	foreach ($entries AS $entry) {
      -		// fetch the author
      -		$author = ostatus_fetchauthor($xpath, $entry, $importer, $contact, true);
      -		return $author;
      -	}
      -}
      +			if (($contact["name"] != $r[0]["name"]) OR ($contact["nick"] != $r[0]["nick"]) OR ($contact["about"] != $r[0]["about"]) OR ($contact["location"] != $r[0]["location"])) {
       
      -function ostatus_import($xml,$importer,&$contact, &$hub) {
      +				logger("Update contact data for contact ".$contact["id"], LOGGER_DEBUG);
       
      -	$a = get_app();
      +				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"]));
       
      -	logger("Import OStatus message", LOGGER_DEBUG);
      -
      -	if ($xml == "")
      -		return;
      -
      -	$doc = new DOMDocument();
      -	@$doc->loadXML($xml);
      -
      -	$xpath = new DomXPath($doc);
      -	$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;
      -	if (is_object($hub_attributes))
      -		foreach($hub_attributes AS $hub_attribute)
      -			if ($hub_attribute->name == "href") {
      -				$hub = $hub_attribute->textContent;
      -				logger("Found hub ".$hub, LOGGER_DEBUG);
      +				poco_check($contact["url"], $contact["name"], $contact["network"], $author["author-avatar"], $contact["about"], $contact["location"],
      +							"", "", "", datetime_convert(), 2, $contact["id"], $contact["uid"]);
       			}
       
      -	$header = array();
      -	$header["uid"] = $importer["uid"];
      -	$header["network"] = NETWORK_OSTATUS;
      -	$header["type"] = "remote";
      -	$header["wall"] = 0;
      -	$header["origin"] = 0;
      -	$header["gravity"] = GRAVITY_PARENT;
      +			if (isset($author["author-avatar"]) AND ($author["author-avatar"] != $r[0]['avatar'])) {
      +				logger("Update profile picture for contact ".$contact["id"], LOGGER_DEBUG);
       
      -	// it could either be a received post or a post we fetched by ourselves
      -	// depending on that, the first node is different
      -	$first_child = $doc->firstChild->tagName;
      +				update_contact_avatar($author["author-avatar"], $importer["uid"], $contact["id"]);
      +			}
      +
      +			$contact["generation"] = 2;
      +			$contact["photo"] = $author["author-avatar"];
      +			update_gcontact($contact);
      +		}
      +
      +		return($author);
      +	}
      +
      +	public static function salmon_author($xml, $importer) {
      +
      +		if ($xml == "")
      +			return;
      +
      +		$doc = new DOMDocument();
      +		@$doc->loadXML($xml);
      +
      +		$xpath = new DomXPath($doc);
      +		$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);
       
      -	if ($first_child == "feed")
      -		$entries = $xpath->query('/atom:feed/atom:entry');
      -	else
       		$entries = $xpath->query('/atom:entry');
       
      -	$conversation = "";
      -	$conversationlist = array();
      -	$item_id = 0;
      -
      -	// Reverse the order of the entries
      -	$entrylist = array();
      -
      -	foreach ($entries AS $entry)
      -		$entrylist[] = $entry;
      -
      -	foreach (array_reverse($entrylist) AS $entry) {
      -
      -		$mention = false;
      -
      -		// fetch the author
      -		if ($first_child == "feed")
      -			$author = ostatus_fetchauthor($xpath, $doc->firstChild, $importer, $contact, false);
      -		else
      -			$author = ostatus_fetchauthor($xpath, $entry, $importer, $contact, false);
      -
      -		$value = $xpath->evaluate('atom:author/poco:preferredUsername/text()', $context)->item(0)->nodeValue;
      -		if ($value != "")
      -			$nickname = $value;
      -		else
      -			$nickname = $author["author-name"];
      -
      -		$item = array_merge($header, $author);
      -
      -		// Now get the item
      -		$item["uri"] = $xpath->query('atom:id/text()', $entry)->item(0)->nodeValue;
      -
      -		$r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s'",
      -			intval($importer["uid"]), dbesc($item["uri"]));
      -		if ($r) {
      -			logger("Item with uri ".$item["uri"]." for user ".$importer["uid"]." already existed under id ".$r[0]["id"], LOGGER_DEBUG);
      -			continue;
      +		foreach ($entries AS $entry) {
      +			// fetch the author
      +			$author = self::fetchauthor($xpath, $entry, $importer, $contact, true);
      +			return $author;
       		}
      +	}
       
      -		$item["body"] = add_page_info_to_body(html2bbcode($xpath->query('atom:content/text()', $entry)->item(0)->nodeValue));
      -		$item["object-type"] = $xpath->query('activity:object-type/text()', $entry)->item(0)->nodeValue;
      +	public static function import($xml,$importer,&$contact, &$hub) {
       
      -		if (($item["object-type"] == ACTIVITY_OBJ_BOOKMARK) OR ($item["object-type"] == ACTIVITY_OBJ_EVENT)) {
      -			$item["title"] = $xpath->query('atom:title/text()', $entry)->item(0)->nodeValue;
      -			$item["body"] = $xpath->query('atom:summary/text()', $entry)->item(0)->nodeValue;
      -		} elseif ($item["object-type"] == ACTIVITY_OBJ_QUESTION)
      -			$item["title"] = $xpath->query('atom:title/text()', $entry)->item(0)->nodeValue;
      +		logger("Import OStatus message", LOGGER_DEBUG);
       
      -		$item["object"] = $xml;
      -		$item["verb"] = $xpath->query('activity:verb/text()', $entry)->item(0)->nodeValue;
      +		if ($xml == "")
      +			return;
       
      -		/// @TODO
      -		/// Delete a message
      -		if ($item["verb"] == "qvitter-delete-notice") {
      -			// ignore "Delete" messages (by now)
      -			logger("Ignore delete message ".print_r($item, true));
      -			continue;
      -		}
      +		//$tempfile = tempnam(get_temppath(), "import");
      +		//file_put_contents($tempfile, $xml);
       
      -		if ($item["verb"] == ACTIVITY_JOIN) {
      -			// ignore "Join" messages
      -			logger("Ignore join message ".print_r($item, true));
      -			continue;
      -		}
      +		$doc = new DOMDocument();
      +		@$doc->loadXML($xml);
       
      -		if ($item["verb"] == ACTIVITY_FOLLOW) {
      -			new_follower($importer, $contact, $item, $nickname);
      -			continue;
      -		}
      +		$xpath = new DomXPath($doc);
      +		$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);
       
      -		if ($item["verb"] == NAMESPACE_OSTATUS."/unfollow") {
      -			lose_follower($importer, $contact, $item, $dummy);
      -			continue;
      -		}
      -
      -		if ($item["verb"] == ACTIVITY_FAVORITE) {
      -			$orig_uri = $xpath->query("activity:object/atom:id", $entry)->item(0)->nodeValue;
      -			logger("Favorite ".$orig_uri." ".print_r($item, true));
      -
      -			$item["verb"] = ACTIVITY_LIKE;
      -			$item["parent-uri"] = $orig_uri;
      -			$item["gravity"] = GRAVITY_LIKE;
      -		}
      -
      -		if ($item["verb"] == NAMESPACE_OSTATUS."/unfavorite") {
      -			// Ignore "Unfavorite" message
      -			logger("Ignore unfavorite message ".print_r($item, true));
      -			continue;
      -		}
      -
      -		// http://activitystrea.ms/schema/1.0/rsvp-yes
      -		if (!in_array($item["verb"], array(ACTIVITY_POST, ACTIVITY_LIKE, ACTIVITY_SHARE)))
      -			logger("Unhandled verb ".$item["verb"]." ".print_r($item, true));
      -
      -		$item["created"] = $xpath->query('atom:published/text()', $entry)->item(0)->nodeValue;
      -		$item["edited"] = $xpath->query('atom:updated/text()', $entry)->item(0)->nodeValue;
      -		$conversation = $xpath->query('ostatus:conversation/text()', $entry)->item(0)->nodeValue;
      -
      -		$related = "";
      -
      -		$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;
      -				if ($attributes->name == "href")
      -					$related = $attributes->textContent;
      -			}
      -		}
      -
      -		$georsspoint = $xpath->query('georss:point', $entry);
      -		if ($georsspoint)
      -			$item["coord"] = $georsspoint->item(0)->nodeValue;
      -
      -		/// @TODO
      -		/// $item["location"] =
      -
      -		$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=".$a->get_baseurl()."/search?tag=".$term."]".$term."[/url]";
      -					}
      -			}
      -		}
      -
      -		$self = "";
      -		$enclosure = "";
      -
      -		$links = $xpath->query('atom:link', $entry);
      -		if ($links) {
      -			$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;
      +		$gub = "";
      +		$hub_attributes = $xpath->query("/atom:feed/atom:link[@rel='hub']")->item(0)->attributes;
      +		if (is_object($hub_attributes))
      +			foreach($hub_attributes AS $hub_attribute)
      +				if ($hub_attribute->name == "href") {
      +					$hub = $hub_attribute->textContent;
      +					logger("Found hub ".$hub, LOGGER_DEBUG);
       				}
      -				if (($rel != "") AND ($href != ""))
      -					switch($rel) {
      -						case "alternate":
      -							$item["plink"] = $href;
      -							if (($item["object-type"] == ACTIVITY_OBJ_QUESTION) OR
      -								($item["object-type"] == ACTIVITY_OBJ_EVENT))
      -								$item["body"] .= add_page_info($href);
      -							break;
      -						case "ostatus:conversation":
      -							$conversation = $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;
      -						case "related":
      -							if ($item["object-type"] != ACTIVITY_OBJ_BOOKMARK) {
      -								if (!isset($item["parent-uri"]))
      -									$item["parent-uri"] = $href;
      +		$header = array();
      +		$header["uid"] = $importer["uid"];
      +		$header["network"] = NETWORK_OSTATUS;
      +		$header["type"] = "remote";
      +		$header["wall"] = 0;
      +		$header["origin"] = 0;
      +		$header["gravity"] = GRAVITY_PARENT;
       
      -								if ($related == "")
      -									$related = $href;
      -							} else
      -								$item["body"] .= add_page_info($href);
      -							break;
      -						case "self":
      -							$self = $href;
      -							break;
      -						case "mentioned":
      -							// Notification check
      -							if ($importer["nurl"] == normalise_link($href))
      -								$mention = true;
      -							break;
      -					}
      +		// it could either be a received post or a post we fetched by ourselves
      +		// depending on that, the first node is different
      +		$first_child = $doc->firstChild->tagName;
      +
      +		if ($first_child == "feed")
      +			$entries = $xpath->query('/atom:feed/atom:entry');
      +		else
      +			$entries = $xpath->query('/atom:entry');
      +
      +		$conversation = "";
      +		$conversationlist = array();
      +		$item_id = 0;
      +
      +		// Reverse the order of the entries
      +		$entrylist = array();
      +
      +		foreach ($entries AS $entry)
      +			$entrylist[] = $entry;
      +
      +		foreach (array_reverse($entrylist) AS $entry) {
      +
      +			$mention = false;
      +
      +			// fetch the author
      +			if ($first_child == "feed")
      +				$author = self::fetchauthor($xpath, $doc->firstChild, $importer, $contact, false);
      +			else
      +				$author = self::fetchauthor($xpath, $entry, $importer, $contact, false);
      +
      +			$value = $xpath->evaluate('atom:author/poco:preferredUsername/text()', $context)->item(0)->nodeValue;
      +			if ($value != "")
      +				$nickname = $value;
      +			else
      +				$nickname = $author["author-name"];
      +
      +			$item = array_merge($header, $author);
      +
      +			// Now get the item
      +			$item["uri"] = $xpath->query('atom:id/text()', $entry)->item(0)->nodeValue;
      +
      +			$r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s'",
      +				intval($importer["uid"]), dbesc($item["uri"]));
      +			if ($r) {
      +				logger("Item with uri ".$item["uri"]." for user ".$importer["uid"]." already existed under id ".$r[0]["id"], LOGGER_DEBUG);
      +				continue;
       			}
      -		}
       
      -		$local_id = "";
      -		$repeat_of = "";
      +			$item["body"] = add_page_info_to_body(html2bbcode($xpath->query('atom:content/text()', $entry)->item(0)->nodeValue));
      +			$item["object-type"] = $xpath->query('activity:object-type/text()', $entry)->item(0)->nodeValue;
       
      -		$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);
      -				if ($attributes->name == "local_id")
      -					$local_id = $attributes->textContent;
      -				if ($attributes->name == "repeat_of")
      -					$repeat_of = $attributes->textContent;
      +			if (($item["object-type"] == ACTIVITY_OBJ_BOOKMARK) OR ($item["object-type"] == ACTIVITY_OBJ_EVENT)) {
      +				$item["title"] = $xpath->query('atom:title/text()', $entry)->item(0)->nodeValue;
      +				$item["body"] = $xpath->query('atom:summary/text()', $entry)->item(0)->nodeValue;
      +			} elseif ($item["object-type"] == ACTIVITY_OBJ_QUESTION)
      +				$item["title"] = $xpath->query('atom:title/text()', $entry)->item(0)->nodeValue;
      +
      +			$item["object"] = $xml;
      +			$item["verb"] = $xpath->query('activity:verb/text()', $entry)->item(0)->nodeValue;
      +
      +			/// @TODO
      +			/// Delete a message
      +			if ($item["verb"] == "qvitter-delete-notice") {
      +				// ignore "Delete" messages (by now)
      +				logger("Ignore delete message ".print_r($item, true));
      +				continue;
       			}
      -		}
       
      -		// Is it a repeated post?
      -		if ($repeat_of != "") {
      -			$activityobjects = $xpath->query('activity:object', $entry)->item(0);
      +			if ($item["verb"] == ACTIVITY_JOIN) {
      +				// ignore "Join" messages
      +				logger("Ignore join message ".print_r($item, true));
      +				continue;
      +			}
       
      -			if (is_object($activityobjects)) {
      +			if ($item["verb"] == ACTIVITY_FOLLOW) {
      +				new_follower($importer, $contact, $item, $nickname);
      +				continue;
      +			}
       
      -				$orig_uri = $xpath->query("activity:object/atom:id", $activityobjects)->item(0)->nodeValue;
      -				if (!isset($orig_uri))
      -					$orig_uri = $xpath->query('atom:id/text()', $activityobjects)->item(0)->nodeValue;
      +			if ($item["verb"] == NAMESPACE_OSTATUS."/unfollow") {
      +				lose_follower($importer, $contact, $item, $dummy);
      +				continue;
      +			}
       
      -				$orig_links = $xpath->query("activity:object/atom:link[@rel='alternate']", $activityobjects);
      -				if ($orig_links AND ($orig_links->length > 0))
      -					foreach($orig_links->item(0)->attributes AS $attributes)
      +			if ($item["verb"] == ACTIVITY_FAVORITE) {
      +				$orig_uri = $xpath->query("activity:object/atom:id", $entry)->item(0)->nodeValue;
      +				logger("Favorite ".$orig_uri." ".print_r($item, true));
      +
      +				$item["verb"] = ACTIVITY_LIKE;
      +				$item["parent-uri"] = $orig_uri;
      +				$item["gravity"] = GRAVITY_LIKE;
      +			}
      +
      +			if ($item["verb"] == NAMESPACE_OSTATUS."/unfavorite") {
      +				// Ignore "Unfavorite" message
      +				logger("Ignore unfavorite message ".print_r($item, true));
      +				continue;
      +			}
      +
      +			// http://activitystrea.ms/schema/1.0/rsvp-yes
      +			if (!in_array($item["verb"], array(ACTIVITY_POST, ACTIVITY_LIKE, ACTIVITY_SHARE)))
      +				logger("Unhandled verb ".$item["verb"]." ".print_r($item, true));
      +
      +			$item["created"] = $xpath->query('atom:published/text()', $entry)->item(0)->nodeValue;
      +			$item["edited"] = $xpath->query('atom:updated/text()', $entry)->item(0)->nodeValue;
      +			$conversation = $xpath->query('ostatus:conversation/text()', $entry)->item(0)->nodeValue;
      +
      +			$related = "";
      +
      +			$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;
      +					if ($attributes->name == "href")
      +						$related = $attributes->textContent;
      +				}
      +			}
      +
      +			$georsspoint = $xpath->query('georss:point', $entry);
      +			if ($georsspoint)
      +				$item["coord"] = $georsspoint->item(0)->nodeValue;
      +
      +			$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]";
      +						}
      +				}
      +			}
      +
      +			$self = "";
      +			$enclosure = "";
      +
      +			$links = $xpath->query('atom:link', $entry);
      +			if ($links) {
      +				$rel = "";
      +				$href = "";
      +				$type = "";
      +				$length = "0";
      +				$title = "";
      +				foreach ($links AS $link) {
      +					foreach($link->attributes AS $attributes) {
       						if ($attributes->name == "href")
      -							$orig_link = $attributes->textContent;
      +							$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;
      +								if (($item["object-type"] == ACTIVITY_OBJ_QUESTION) OR
      +									($item["object-type"] == ACTIVITY_OBJ_EVENT))
      +									$item["body"] .= add_page_info($href);
      +								break;
      +							case "ostatus:conversation":
      +								$conversation = $href;
      +								break;
      +							case "enclosure":
      +								$enclosure = $href;
      +								if(strlen($item["attach"]))
      +									$item["attach"] .= ',';
       
      -				if (!isset($orig_link))
      -					$orig_link = $xpath->query("atom:link[@rel='alternate']", $activityobjects)->item(0)->nodeValue;
      +								$item["attach"] .= '[attach]href="'.$href.'" length="'.$length.'" type="'.$type.'" title="'.$title.'"[/attach]';
      +								break;
      +							case "related":
      +								if ($item["object-type"] != ACTIVITY_OBJ_BOOKMARK) {
      +									if (!isset($item["parent-uri"]))
      +										$item["parent-uri"] = $href;
       
      -				if (!isset($orig_link))
      -					$orig_link =  ostatus_convert_href($orig_uri);
      +									if ($related == "")
      +										$related = $href;
      +								} else
      +									$item["body"] .= add_page_info($href);
      +								break;
      +							case "self":
      +								$self = $href;
      +								break;
      +							case "mentioned":
      +								// Notification check
      +								if ($importer["nurl"] == normalise_link($href))
      +									$mention = true;
      +								break;
      +						}
      +				}
      +			}
       
      -				$orig_body = $xpath->query('activity:object/atom:content/text()', $activityobjects)->item(0)->nodeValue;
      -				if (!isset($orig_body))
      -					$orig_body = $xpath->query('atom:content/text()', $activityobjects)->item(0)->nodeValue;
      +			$local_id = "";
      +			$repeat_of = "";
       
      -				$orig_created = $xpath->query('atom:published/text()', $activityobjects)->item(0)->nodeValue;
      +			$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);
      +					if ($attributes->name == "local_id")
      +						$local_id = $attributes->textContent;
      +					if ($attributes->name == "repeat_of")
      +						$repeat_of = $attributes->textContent;
      +				}
      +			}
       
      -				$orig_contact = $contact;
      -				$orig_author = ostatus_fetchauthor($xpath, $activityobjects, $importer, $orig_contact, false);
      +			// Is it a repeated post?
      +			if ($repeat_of != "") {
      +				$activityobjects = $xpath->query('activity:object', $entry)->item(0);
      +
      +				if (is_object($activityobjects)) {
      +
      +					$orig_uri = $xpath->query("activity:object/atom:id", $activityobjects)->item(0)->nodeValue;
      +					if (!isset($orig_uri))
      +						$orig_uri = $xpath->query('atom:id/text()', $activityobjects)->item(0)->nodeValue;
      +
      +					$orig_links = $xpath->query("activity:object/atom:link[@rel='alternate']", $activityobjects);
      +					if ($orig_links AND ($orig_links->length > 0))
      +						foreach($orig_links->item(0)->attributes AS $attributes)
      +							if ($attributes->name == "href")
      +								$orig_link = $attributes->textContent;
      +
      +					if (!isset($orig_link))
      +						$orig_link = $xpath->query("atom:link[@rel='alternate']", $activityobjects)->item(0)->nodeValue;
      +
      +					if (!isset($orig_link))
      +						$orig_link =  self::convert_href($orig_uri);
      +
      +					$orig_body = $xpath->query('activity:object/atom:content/text()', $activityobjects)->item(0)->nodeValue;
      +					if (!isset($orig_body))
      +						$orig_body = $xpath->query('atom:content/text()', $activityobjects)->item(0)->nodeValue;
      +
      +					$orig_created = $xpath->query('atom:published/text()', $activityobjects)->item(0)->nodeValue;
      +
      +					$orig_contact = $contact;
      +					$orig_author = self::fetchauthor($xpath, $activityobjects, $importer, $orig_contact, false);
       
      -				//if (!intval(get_config('system','wall-to-wall_share'))) {
      -				//	$prefix = share_header($orig_author['author-name'], $orig_author['author-link'], $orig_author['author-avatar'], "", $orig_created, $orig_link);
      -				//	$item["body"] = $prefix.add_page_info_to_body(html2bbcode($orig_body))."[/share]";
      -				//} else {
       					$item["author-name"] = $orig_author["author-name"];
       					$item["author-link"] = $orig_author["author-link"];
       					$item["author-avatar"] = $orig_author["author-avatar"];
      @@ -499,1234 +443,1319 @@ function ostatus_import($xml,$importer,&$contact, &$hub) {
       
       					$item["uri"] = $orig_uri;
       					$item["plink"] = $orig_link;
      -				//}
       
      -				$item["verb"] = $xpath->query('activity:verb/text()', $activityobjects)->item(0)->nodeValue;
      +					$item["verb"] = $xpath->query('activity:verb/text()', $activityobjects)->item(0)->nodeValue;
       
      -				$item["object-type"] = $xpath->query('activity:object/activity:object-type/text()', $activityobjects)->item(0)->nodeValue;
      -				if (!isset($item["object-type"]))
      -					$item["object-type"] = $xpath->query('activity:object-type/text()', $activityobjects)->item(0)->nodeValue;
      -			}
      -		}
      -
      -		//if ($enclosure != "")
      -		//	$item["body"] .= add_page_info($enclosure);
      -
      -		if (isset($item["parent-uri"])) {
      -			$r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s'",
      -				intval($importer["uid"]), dbesc($item["parent-uri"]));
      -
      -			if (!$r AND ($related != "")) {
      -				$reply_path = str_replace("/notice/", "/api/statuses/show/", $related).".atom";
      -
      -				if ($reply_path != $related) {
      -					logger("Fetching related items for user ".$importer["uid"]." from ".$reply_path, LOGGER_DEBUG);
      -					$reply_xml = fetch_url($reply_path);
      -
      -					$reply_contact = $contact;
      -					ostatus_import($reply_xml,$importer,$reply_contact, $reply_hub);
      -
      -					// After the import try to fetch the parent item again
      -					$r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s'",
      -						intval($importer["uid"]), dbesc($item["parent-uri"]));
      +					$item["object-type"] = $xpath->query('activity:object/activity:object-type/text()', $activityobjects)->item(0)->nodeValue;
      +					if (!isset($item["object-type"]))
      +						$item["object-type"] = $xpath->query('activity:object-type/text()', $activityobjects)->item(0)->nodeValue;
       				}
       			}
      -			if ($r) {
      -				$item["type"] = 'remote-comment';
      -				$item["gravity"] = GRAVITY_COMMENT;
      +
      +			//if ($enclosure != "")
      +			//	$item["body"] .= add_page_info($enclosure);
      +
      +			if (isset($item["parent-uri"])) {
      +				$r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s'",
      +					intval($importer["uid"]), dbesc($item["parent-uri"]));
      +
      +				if (!$r AND ($related != "")) {
      +					$reply_path = str_replace("/notice/", "/api/statuses/show/", $related).".atom";
      +
      +					if ($reply_path != $related) {
      +						logger("Fetching related items for user ".$importer["uid"]." from ".$reply_path, LOGGER_DEBUG);
      +						$reply_xml = fetch_url($reply_path);
      +
      +						$reply_contact = $contact;
      +						self::import($reply_xml,$importer,$reply_contact, $reply_hub);
      +
      +						// After the import try to fetch the parent item again
      +						$r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s'",
      +							intval($importer["uid"]), dbesc($item["parent-uri"]));
      +					}
      +				}
      +				if ($r) {
      +					$item["type"] = 'remote-comment';
      +					$item["gravity"] = GRAVITY_COMMENT;
      +				}
      +			} else
      +				$item["parent-uri"] = $item["uri"];
      +
      +			$item_id = self::completion($conversation, $importer["uid"], $item, $self);
      +
      +			if (!$item_id) {
      +				logger("Error storing item", LOGGER_DEBUG);
      +				continue;
       			}
      -		} else
      -			$item["parent-uri"] = $item["uri"];
       
      -		$item_id = ostatus_completion($conversation, $importer["uid"], $item, $self);
      -
      -		if (!$item_id) {
      -			logger("Error storing item", LOGGER_DEBUG);
      -			continue;
      -		}
      -
      -		logger("Item was stored with id ".$item_id, LOGGER_DEBUG);
      -	}
      -}
      -
      -function ostatus_convert_href($href) {
      -	$elements = explode(":",$href);
      -
      -	if ((count($elements) <= 2) OR ($elements[0] != "tag"))
      -		return $href;
      -
      -	$server = explode(",", $elements[1]);
      -	$conversation = explode("=", $elements[2]);
      -
      -	if ((count($elements) == 4) AND ($elements[2] == "post"))
      -		return "http://".$server[0]."/notice/".$elements[3];
      -
      -	if ((count($conversation) != 2) OR ($conversation[1] ==""))
      -		return $href;
      -
      -	if ($elements[3] == "objectType=thread")
      -		return "http://".$server[0]."/conversation/".$conversation[1];
      -	else
      -		return "http://".$server[0]."/notice/".$conversation[1];
      -
      -	return $href;
      -}
      -
      -function check_conversations($mentions = false, $override = false) {
      -	$last = get_config('system','ostatus_last_poll');
      -
      -	$poll_interval = intval(get_config('system','ostatus_poll_interval'));
      -	if(! $poll_interval)
      -		$poll_interval = OSTATUS_DEFAULT_POLL_INTERVAL;
      -
      -	// Don't poll if the interval is set negative
      -	if (($poll_interval < 0) AND !$override)
      -		return;
      -
      -	if (!$mentions) {
      -		$poll_timeframe = intval(get_config('system','ostatus_poll_timeframe'));
      -		if (!$poll_timeframe)
      -			$poll_timeframe = OSTATUS_DEFAULT_POLL_TIMEFRAME;
      -	} else {
      -		$poll_timeframe = intval(get_config('system','ostatus_poll_timeframe'));
      -		if (!$poll_timeframe)
      -			$poll_timeframe = OSTATUS_DEFAULT_POLL_TIMEFRAME_MENTIONS;
      -	}
      -
      -
      -	if ($last AND !$override) {
      -		$next = $last + ($poll_interval * 60);
      -		if ($next > time()) {
      -			logger('poll interval not reached');
      -			return;
      +			logger("Item was stored with id ".$item_id, LOGGER_DEBUG);
       		}
       	}
       
      -	logger('cron_start');
      -
      -	$start = date("Y-m-d H:i:s", time() - ($poll_timeframe * 60));
      -
      -	if ($mentions)
      -		$conversations = q("SELECT `term`.`oid`, `term`.`url`, `term`.`uid` FROM `term`
      -					STRAIGHT_JOIN `thread` ON `thread`.`iid` = `term`.`oid` AND `thread`.`uid` = `term`.`uid`
      -					WHERE `term`.`type` = 7 AND `term`.`term` > '%s' AND `thread`.`mention`
      -					GROUP BY `term`.`url`, `term`.`uid` ORDER BY `term`.`term` DESC", dbesc($start));
      -	else
      -		$conversations = q("SELECT `oid`, `url`, `uid` FROM `term`
      -					WHERE `type` = 7 AND `term` > '%s'
      -					GROUP BY `url`, `uid` ORDER BY `term` DESC", dbesc($start));
      -
      -	foreach ($conversations AS $conversation) {
      -		ostatus_completion($conversation['url'], $conversation['uid']);
      -	}
      -
      -	logger('cron_end');
      -
      -	set_config('system','ostatus_last_poll', time());
      -}
      -
      -/**
      - * @brief Updates the gcontact table with actor data from the conversation
      - *
      - * @param object $actor The actor object that contains the contact data
      - */
      -function ostatus_conv_fetch_actor($actor) {
      -
      -	// We set the generation to "3" since the data here is not as reliable as the data we get on other occasions
      -	$contact = array("network" => NETWORK_OSTATUS, "generation" => 3);
      -
      -	if (isset($actor->url))
      -		$contact["url"] = $actor->url;
      -
      -	if (isset($actor->displayName))
      -		$contact["name"] = $actor->displayName;
      -
      -	if (isset($actor->portablecontacts_net->displayName))
      -		$contact["name"] = $actor->portablecontacts_net->displayName;
      -
      -	if (isset($actor->portablecontacts_net->preferredUsername))
      -		$contact["nick"] = $actor->portablecontacts_net->preferredUsername;
      -
      -	if (isset($actor->id))
      -		$contact["alias"] = $actor->id;
      -
      -	if (isset($actor->summary))
      -		$contact["about"] = $actor->summary;
      -
      -	if (isset($actor->portablecontacts_net->note))
      -		$contact["about"] = $actor->portablecontacts_net->note;
      -
      -	if (isset($actor->portablecontacts_net->addresses->formatted))
      -		$contact["location"] = $actor->portablecontacts_net->addresses->formatted;
      -
      -
      -	if (isset($actor->image->url))
      -		$contact["photo"] = $actor->image->url;
      -
      -	if (isset($actor->image->width))
      -		$avatarwidth = $actor->image->width;
      -
      -	if (is_array($actor->status_net->avatarLinks))
      -		foreach ($actor->status_net->avatarLinks AS $avatar) {
      -			if ($avatarsize < $avatar->width) {
      -				$contact["photo"] = $avatar->url;
      -				$avatarsize = $avatar->width;
      -			}
      -		}
      -
      -	update_gcontact($contact);
      -}
      -
      -/**
      - * @brief Fetches the conversation url for a given item link or conversation id
      - *
      - * @param string $self The link to the posting
      - * @param string $conversation_id The conversation id
      - *
      - * @return string The conversation url
      - */
      -function ostatus_fetch_conversation($self, $conversation_id = "") {
      -
      -	if ($conversation_id != "") {
      -		$elements = explode(":", $conversation_id);
      +	public static function convert_href($href) {
      +		$elements = explode(":",$href);
       
       		if ((count($elements) <= 2) OR ($elements[0] != "tag"))
      -			return $conversation_id;
      +			return $href;
      +
      +		$server = explode(",", $elements[1]);
      +		$conversation = explode("=", $elements[2]);
      +
      +		if ((count($elements) == 4) AND ($elements[2] == "post"))
      +			return "http://".$server[0]."/notice/".$elements[3];
      +
      +		if ((count($conversation) != 2) OR ($conversation[1] ==""))
      +			return $href;
      +
      +		if ($elements[3] == "objectType=thread")
      +			return "http://".$server[0]."/conversation/".$conversation[1];
      +		else
      +			return "http://".$server[0]."/notice/".$conversation[1];
      +
      +		return $href;
       	}
       
      -	if ($self == "")
      -		return "";
      +	public static function check_conversations($mentions = false, $override = false) {
      +		$last = get_config('system','ostatus_last_poll');
       
      -	$json = str_replace(".atom", ".json", $self);
      +		$poll_interval = intval(get_config('system','ostatus_poll_interval'));
      +		if(! $poll_interval)
      +			$poll_interval = OSTATUS_DEFAULT_POLL_INTERVAL;
       
      -	$raw = fetch_url($json);
      -	if ($raw == "")
      -		return "";
      +		// Don't poll if the interval is set negative
      +		if (($poll_interval < 0) AND !$override)
      +			return;
       
      -	$data = json_decode($raw);
      -	if (!is_object($data))
      -		return "";
      +		if (!$mentions) {
      +			$poll_timeframe = intval(get_config('system','ostatus_poll_timeframe'));
      +			if (!$poll_timeframe)
      +				$poll_timeframe = OSTATUS_DEFAULT_POLL_TIMEFRAME;
      +		} else {
      +			$poll_timeframe = intval(get_config('system','ostatus_poll_timeframe'));
      +			if (!$poll_timeframe)
      +				$poll_timeframe = OSTATUS_DEFAULT_POLL_TIMEFRAME_MENTIONS;
      +		}
       
      -	$conversation_id = $data->statusnet_conversation_id;
       
      -	$pos = strpos($self, "/api/statuses/show/");
      -	$base_url = substr($self, 0, $pos);
      +		if ($last AND !$override) {
      +			$next = $last + ($poll_interval * 60);
      +			if ($next > time()) {
      +				logger('poll interval not reached');
      +				return;
      +			}
      +		}
       
      -	return $base_url."/conversation/".$conversation_id;
      -}
      +		logger('cron_start');
       
      -/**
      - * @brief Fetches actor details of a given actor and user id
      - *
      - * @param string $actor The actor url
      - * @param int $uid The user id
      - * @param int $contact_id The default contact-id
      - *
      - * @return array Array with actor details
      - */
      -function ostatus_get_actor_details($actor, $uid, $contact_id) {
      +		$start = date("Y-m-d H:i:s", time() - ($poll_timeframe * 60));
       
      -	$details = array();
      +		if ($mentions)
      +			$conversations = q("SELECT `term`.`oid`, `term`.`url`, `term`.`uid` FROM `term`
      +						STRAIGHT_JOIN `thread` ON `thread`.`iid` = `term`.`oid` AND `thread`.`uid` = `term`.`uid`
      +						WHERE `term`.`type` = 7 AND `term`.`term` > '%s' AND `thread`.`mention`
      +						GROUP BY `term`.`url`, `term`.`uid` ORDER BY `term`.`term` DESC", dbesc($start));
      +		else
      +			$conversations = q("SELECT `oid`, `url`, `uid` FROM `term`
      +						WHERE `type` = 7 AND `term` > '%s'
      +						GROUP BY `url`, `uid` ORDER BY `term` DESC", dbesc($start));
       
      -	$contact = q("SELECT `id`, `rel`, `network` FROM `contact` WHERE `uid` = %d AND `nurl` = '%s' AND `network` != '%s'",
      -				$uid, normalise_link($actor), NETWORK_STATUSNET);
      +		foreach ($conversations AS $conversation) {
      +			self::completion($conversation['url'], $conversation['uid']);
      +		}
       
      -	if (!$contact)
      -		$contact = q("SELECT `id`, `rel`, `network` FROM `contact` WHERE `uid` = %d AND `alias` IN ('%s', '%s') AND `network` != '%s'",
      -				$uid, $actor, normalise_link($actor), NETWORK_STATUSNET);
      +		logger('cron_end');
       
      -	if ($contact) {
      -		logger("Found contact for url ".$actor, LOGGER_DEBUG);
      -		$details["contact_id"] = $contact[0]["id"];
      -		$details["network"] = $contact[0]["network"];
      -
      -		$details["not_following"] = !in_array($contact[0]["rel"], array(CONTACT_IS_SHARING, CONTACT_IS_FRIEND));
      -	} else {
      -		logger("No contact found for user ".$uid." and url ".$actor, LOGGER_DEBUG);
      -
      -		// Adding a global contact
      -		/// @TODO Use this data for the post
      -		$details["global_contact_id"] = get_contact($actor, 0);
      -
      -		logger("Global contact ".$global_contact_id." found for url ".$actor, LOGGER_DEBUG);
      -
      -		$details["contact_id"] = $contact_id;
      -		$details["network"] = NETWORK_OSTATUS;
      -
      -		$details["not_following"] = true;
      +		set_config('system','ostatus_last_poll', time());
       	}
       
      -	return $details;
      -}
      +	/**
      +	 * @brief Updates the gcontact table with actor data from the conversation
      +	 *
      +	 * @param object $actor The actor object that contains the contact data
      +	 */
      +	private function conv_fetch_actor($actor) {
       
      -function ostatus_completion($conversation_url, $uid, $item = array(), $self = "") {
      +		// We set the generation to "3" since the data here is not as reliable as the data we get on other occasions
      +		$contact = array("network" => NETWORK_OSTATUS, "generation" => 3);
       
      -	$a = get_app();
      +		if (isset($actor->url))
      +			$contact["url"] = $actor->url;
       
      -	$item_stored = -1;
      +		if (isset($actor->displayName))
      +			$contact["name"] = $actor->displayName;
       
      -	//$conversation_url = ostatus_convert_href($conversation_url);
      -	$conversation_url = ostatus_fetch_conversation($self, $conversation_url);
      +		if (isset($actor->portablecontacts_net->displayName))
      +			$contact["name"] = $actor->portablecontacts_net->displayName;
      +
      +		if (isset($actor->portablecontacts_net->preferredUsername))
      +			$contact["nick"] = $actor->portablecontacts_net->preferredUsername;
      +
      +		if (isset($actor->id))
      +			$contact["alias"] = $actor->id;
      +
      +		if (isset($actor->summary))
      +			$contact["about"] = $actor->summary;
      +
      +		if (isset($actor->portablecontacts_net->note))
      +			$contact["about"] = $actor->portablecontacts_net->note;
      +
      +		if (isset($actor->portablecontacts_net->addresses->formatted))
      +			$contact["location"] = $actor->portablecontacts_net->addresses->formatted;
      +
      +
      +		if (isset($actor->image->url))
      +			$contact["photo"] = $actor->image->url;
      +
      +		if (isset($actor->image->width))
      +			$avatarwidth = $actor->image->width;
      +
      +		if (is_array($actor->status_net->avatarLinks))
      +			foreach ($actor->status_net->avatarLinks AS $avatar) {
      +				if ($avatarsize < $avatar->width) {
      +					$contact["photo"] = $avatar->url;
      +					$avatarsize = $avatar->width;
      +				}
      +			}
      +
      +		update_gcontact($contact);
      +	}
      +
      +	/**
      +	 * @brief Fetches the conversation url for a given item link or conversation id
      +	 *
      +	 * @param string $self The link to the posting
      +	 * @param string $conversation_id The conversation id
      +	 *
      +	 * @return string The conversation url
      +	 */
      +	private function fetch_conversation($self, $conversation_id = "") {
      +
      +		if ($conversation_id != "") {
      +			$elements = explode(":", $conversation_id);
      +
      +			if ((count($elements) <= 2) OR ($elements[0] != "tag"))
      +				return $conversation_id;
      +		}
      +
      +		if ($self == "")
      +			return "";
      +
      +		$json = str_replace(".atom", ".json", $self);
      +
      +		$raw = fetch_url($json);
      +		if ($raw == "")
      +			return "";
      +
      +		$data = json_decode($raw);
      +		if (!is_object($data))
      +			return "";
      +
      +		$conversation_id = $data->statusnet_conversation_id;
      +
      +		$pos = strpos($self, "/api/statuses/show/");
      +		$base_url = substr($self, 0, $pos);
      +
      +		return $base_url."/conversation/".$conversation_id;
      +	}
      +
      +	/**
      +	 * @brief Fetches actor details of a given actor and user id
      +	 *
      +	 * @param string $actor The actor url
      +	 * @param int $uid The user id
      +	 * @param int $contact_id The default contact-id
      +	 *
      +	 * @return array Array with actor details
      +	 */
      +	private function get_actor_details($actor, $uid, $contact_id) {
      +
      +		$details = array();
      +
      +		$contact = q("SELECT `id`, `rel`, `network` FROM `contact` WHERE `uid` = %d AND `nurl` = '%s' AND `network` != '%s'",
      +					$uid, normalise_link($actor), NETWORK_STATUSNET);
      +
      +		if (!$contact)
      +			$contact = q("SELECT `id`, `rel`, `network` FROM `contact` WHERE `uid` = %d AND `alias` IN ('%s', '%s') AND `network` != '%s'",
      +					$uid, $actor, normalise_link($actor), NETWORK_STATUSNET);
      +
      +		if ($contact) {
      +			logger("Found contact for url ".$actor, LOGGER_DEBUG);
      +			$details["contact_id"] = $contact[0]["id"];
      +			$details["network"] = $contact[0]["network"];
      +
      +			$details["not_following"] = !in_array($contact[0]["rel"], array(CONTACT_IS_SHARING, CONTACT_IS_FRIEND));
      +		} else {
      +			logger("No contact found for user ".$uid." and url ".$actor, LOGGER_DEBUG);
      +
      +			// Adding a global contact
      +			/// @TODO Use this data for the post
      +			$details["global_contact_id"] = get_contact($actor, 0);
      +
      +			logger("Global contact ".$global_contact_id." found for url ".$actor, LOGGER_DEBUG);
      +
      +			$details["contact_id"] = $contact_id;
      +			$details["network"] = NETWORK_OSTATUS;
      +
      +			$details["not_following"] = true;
      +		}
      +
      +		return $details;
      +	}
      +
      +	private function completion($conversation_url, $uid, $item = array(), $self = "") {
      +
      +
      +		$item_stored = -1;
      +
      +		$conversation_url = self::fetch_conversation($self, $conversation_url);
      +
      +		// If the thread shouldn't be completed then store the item and go away
      +		// Don't do a completion on liked content
      +		if (((intval(get_config('system','ostatus_poll_interval')) == -2) AND (count($item) > 0)) OR
      +			($item["verb"] == ACTIVITY_LIKE) OR ($conversation_url == "")) {
      +			$item_stored = item_store($item, true);
      +			return($item_stored);
      +		}
      +
      +		// Get the parent
      +		$parents = q("SELECT `id`, `parent`, `uri`, `contact-id`, `type`, `verb`, `visible` FROM `item` WHERE `id` IN
      +				(SELECT `parent` FROM `item` WHERE `id` IN
      +					(SELECT `oid` FROM `term` WHERE `uid` = %d AND `otype` = %d AND `type` = %d AND `url` = '%s'))",
      +				intval($uid), intval(TERM_OBJ_POST), intval(TERM_CONVERSATION), dbesc($conversation_url));
      +
      +		if ($parents)
      +			$parent = $parents[0];
      +		elseif (count($item) > 0) {
      +			$parent = $item;
      +			$parent["type"] = "remote";
      +			$parent["verb"] = ACTIVITY_POST;
      +			$parent["visible"] = 1;
      +		} else {
      +			// Preset the parent
      +			$r = q("SELECT `id` FROM `contact` WHERE `self` AND `uid`=%d", $uid);
      +			if (!$r)
      +				return(-2);
      +
      +			$parent = array();
      +			$parent["id"] = 0;
      +			$parent["parent"] = 0;
      +			$parent["uri"] = "";
      +			$parent["contact-id"] = $r[0]["id"];
      +			$parent["type"] = "remote";
      +			$parent["verb"] = ACTIVITY_POST;
      +			$parent["visible"] = 1;
      +		}
      +
      +		$conv = str_replace("/conversation/", "/api/statusnet/conversation/", $conversation_url).".as";
      +		$pageno = 1;
      +		$items = array();
      +
      +		logger('fetching conversation url '.$conv.' (Self: '.$self.') for user '.$uid);
      +
      +		do {
      +			$conv_arr = z_fetch_url($conv."?page=".$pageno);
      +
      +			// If it is a non-ssl site and there is an error, then try ssl or vice versa
      +			if (!$conv_arr["success"] AND (substr($conv, 0, 7) == "http://")) {
      +				$conv = str_replace("http://", "https://", $conv);
      +				$conv_as = fetch_url($conv."?page=".$pageno);
      +			} elseif (!$conv_arr["success"] AND (substr($conv, 0, 8) == "https://")) {
      +				$conv = str_replace("https://", "http://", $conv);
      +				$conv_as = fetch_url($conv."?page=".$pageno);
      +			} else
      +				$conv_as = $conv_arr["body"];
      +
      +			$conv_as = str_replace(',"statusnet:notice_info":', ',"statusnet_notice_info":', $conv_as);
      +			$conv_as = json_decode($conv_as);
      +
      +			$no_of_items = sizeof($items);
      +
      +			if (@is_array($conv_as->items))
      +				foreach ($conv_as->items AS $single_item)
      +					$items[$single_item->id] = $single_item;
      +
      +			if ($no_of_items == sizeof($items))
      +				break;
      +
      +			$pageno++;
      +
      +		} while (true);
      +
      +		logger('fetching conversation done. Found '.count($items).' items');
      +
      +		if (!sizeof($items)) {
      +			if (count($item) > 0) {
      +				$item_stored = item_store($item, true);
      +
      +				if ($item_stored) {
      +					logger("Conversation ".$conversation_url." couldn't be fetched. Item uri ".$item["uri"]." stored: ".$item_stored, LOGGER_DEBUG);
      +					self::store_conversation($item_id, $conversation_url);
      +				}
      +
      +				return($item_stored);
      +			} else
      +				return(-3);
      +		}
      +
      +		$items = array_reverse($items);
      +
      +		$r = q("SELECT `nurl` FROM `contact` WHERE `uid` = %d AND `self`", intval($uid));
      +		$importer = $r[0];
      +
      +		$new_parent = true;
      +
      +		foreach ($items as $single_conv) {
      +
      +			// Update the gcontact table
      +			self::conv_fetch_actor($single_conv->actor);
      +
      +			// Test - remove before flight
      +			//$tempfile = tempnam(get_temppath(), "conversation");
      +			//file_put_contents($tempfile, json_encode($single_conv));
      +
      +			$mention = false;
      +
      +			if (isset($single_conv->object->id))
      +				$single_conv->id = $single_conv->object->id;
      +
      +			$plink = self::convert_href($single_conv->id);
      +			if (isset($single_conv->object->url))
      +				$plink = self::convert_href($single_conv->object->url);
      +
      +			if (@!$single_conv->id)
      +				continue;
      +
      +			logger("Got id ".$single_conv->id, LOGGER_DEBUG);
      +
      +			if ($first_id == "") {
      +				$first_id = $single_conv->id;
      +
      +				// The first post of the conversation isn't our first post. There are three options:
      +				// 1. Our conversation hasn't the "real" thread starter
      +				// 2. This first post is a post inside our thread
      +				// 3. This first post is a post inside another thread
      +				if (($first_id != $parent["uri"]) AND ($parent["uri"] != "")) {
      +
      +					$new_parent = true;
      +
      +					$new_parents = q("SELECT `id`, `parent`, `uri`, `contact-id`, `type`, `verb`, `visible` FROM `item` WHERE `id` IN
      +								(SELECT `parent` FROM `item`
      +									WHERE `uid` = %d AND `uri` = '%s' AND `network` IN ('%s','%s')) LIMIT 1",
      +						intval($uid), dbesc($first_id), dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN));
      +					if ($new_parents) {
      +						if ($new_parents[0]["parent"] == $parent["parent"]) {
      +							// Option 2: This post is already present inside our thread - but not as thread starter
      +							logger("Option 2: uri present in our thread: ".$first_id, LOGGER_DEBUG);
      +							$first_id = $parent["uri"];
      +						} else {
      +							// Option 3: Not so good. We have mixed parents. We have to see how to clean this up.
      +							// For now just take the new parent.
      +							$parent = $new_parents[0];
      +							$first_id = $parent["uri"];
      +							logger("Option 3: mixed parents for uri ".$first_id, LOGGER_DEBUG);
      +						}
      +					} else {
      +						// Option 1: We hadn't got the real thread starter
      +						// We have to clean up our existing messages.
      +						$parent["id"] = 0;
      +						$parent["uri"] = $first_id;
      +						logger("Option 1: we have a new parent: ".$first_id, LOGGER_DEBUG);
      +					}
      +				} elseif ($parent["uri"] == "") {
      +					$parent["id"] = 0;
      +					$parent["uri"] = $first_id;
      +				}
      +			}
      +
      +			$parent_uri = $parent["uri"];
      +
      +			// "context" only seems to exist on older servers
      +			if (isset($single_conv->context->inReplyTo->id)) {
      +				$parent_exists = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s' AND `network` IN ('%s','%s') LIMIT 1",
      +							intval($uid), dbesc($single_conv->context->inReplyTo->id), dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN));
      +				if ($parent_exists)
      +					$parent_uri = $single_conv->context->inReplyTo->id;
      +			}
      +
      +			// This is the current way
      +			if (isset($single_conv->object->inReplyTo->id)) {
      +				$parent_exists = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s' AND `network` IN ('%s','%s') LIMIT 1",
      +							intval($uid), dbesc($single_conv->object->inReplyTo->id), dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN));
      +				if ($parent_exists)
      +					$parent_uri = $single_conv->object->inReplyTo->id;
      +			}
      +
      +			$message_exists = q("SELECT `id`, `parent`, `uri` FROM `item` WHERE `uid` = %d AND `uri` = '%s' AND `network` IN ('%s','%s') LIMIT 1",
      +							intval($uid), dbesc($single_conv->id),
      +							dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN));
      +			if ($message_exists) {
      +				logger("Message ".$single_conv->id." already existed on the system", LOGGER_DEBUG);
      +
      +				if ($parent["id"] != 0) {
      +					$existing_message = $message_exists[0];
      +
      +					// We improved the way we fetch OStatus messages, this shouldn't happen very often now
      +					/// @TODO We have to change the shadow copies as well. This way here is really ugly.
      +					if ($existing_message["parent"] != $parent["id"]) {
      +						logger('updating id '.$existing_message["id"].' with parent '.$existing_message["parent"].' to parent '.$parent["id"].' uri '.$parent["uri"].' thread '.$parent_uri, LOGGER_DEBUG);
      +
      +						// Update the parent id of the selected item
      +						$r = q("UPDATE `item` SET `parent` = %d, `parent-uri` = '%s' WHERE `id` = %d",
      +							intval($parent["id"]), dbesc($parent["uri"]), intval($existing_message["id"]));
      +
      +						// Update the parent uri in the thread - but only if it points to itself
      +						$r = q("UPDATE `item` SET `thr-parent` = '%s' WHERE `id` = %d AND `uri` = `thr-parent`",
      +							dbesc($parent_uri), intval($existing_message["id"]));
      +
      +						// try to change all items of the same parent
      +						$r = q("UPDATE `item` SET `parent` = %d, `parent-uri` = '%s' WHERE `parent` = %d",
      +							intval($parent["id"]), dbesc($parent["uri"]), intval($existing_message["parent"]));
      +
      +						// Update the parent uri in the thread - but only if it points to itself
      +						$r = q("UPDATE `item` SET `thr-parent` = '%s' WHERE (`parent` = %d) AND (`uri` = `thr-parent`)",
      +							dbesc($parent["uri"]), intval($existing_message["parent"]));
      +
      +						// Now delete the thread
      +						delete_thread($existing_message["parent"]);
      +					}
      +				}
      +
      +				// The item we are having on the system is the one that we wanted to store via the item array
      +				if (isset($item["uri"]) AND ($item["uri"] == $existing_message["uri"])) {
      +					$item = array();
      +					$item_stored = 0;
      +				}
      +
      +				continue;
      +			}
      +
      +			if (is_array($single_conv->to))
      +				foreach($single_conv->to AS $to)
      +					if ($importer["nurl"] == normalise_link($to->id))
      +						$mention = true;
      +
      +			$actor = $single_conv->actor->id;
      +			if (isset($single_conv->actor->url))
      +				$actor = $single_conv->actor->url;
      +
      +			$details = self::get_actor_details($actor, $uid, $parent["contact-id"]);
      +
      +			// Do we only want to import threads that were started by our contacts?
      +			if ($details["not_following"] AND $new_parent AND get_config('system','ostatus_full_threads')) {
      +				logger("Don't import uri ".$first_id." because user ".$uid." doesn't follow the person ".$actor, LOGGER_DEBUG);
      +				continue;
      +			}
      +
      +			$arr = array();
      +			$arr["network"] = $details["network"];
      +			$arr["uri"] = $single_conv->id;
      +			$arr["plink"] = $plink;
      +			$arr["uid"] = $uid;
      +			$arr["contact-id"] = $details["contact_id"];
      +			$arr["parent-uri"] = $parent_uri;
      +			$arr["created"] = $single_conv->published;
      +			$arr["edited"] = $single_conv->published;
      +			$arr["owner-name"] = $single_conv->actor->displayName;
      +			if ($arr["owner-name"] == '')
      +				$arr["owner-name"] = $single_conv->actor->contact->displayName;
      +			if ($arr["owner-name"] == '')
      +				$arr["owner-name"] = $single_conv->actor->portablecontacts_net->displayName;
      +
      +			$arr["owner-link"] = $actor;
      +			$arr["owner-avatar"] = $single_conv->actor->image->url;
      +			$arr["author-name"] = $arr["owner-name"];
      +			$arr["author-link"] = $actor;
      +			$arr["author-avatar"] = $single_conv->actor->image->url;
      +			$arr["body"] = add_page_info_to_body(html2bbcode($single_conv->content));
      +
      +			if (isset($single_conv->status_net->notice_info->source))
      +				$arr["app"] = strip_tags($single_conv->status_net->notice_info->source);
      +			elseif (isset($single_conv->statusnet->notice_info->source))
      +				$arr["app"] = strip_tags($single_conv->statusnet->notice_info->source);
      +			elseif (isset($single_conv->statusnet_notice_info->source))
      +				$arr["app"] = strip_tags($single_conv->statusnet_notice_info->source);
      +			elseif (isset($single_conv->provider->displayName))
      +				$arr["app"] = $single_conv->provider->displayName;
      +			else
      +				$arr["app"] = "OStatus";
      +
      +
      +			$arr["object"] = json_encode($single_conv);
      +			$arr["verb"] = $parent["verb"];
      +			$arr["visible"] = $parent["visible"];
      +			$arr["location"] = $single_conv->location->displayName;
      +			$arr["coord"] = trim($single_conv->location->lat." ".$single_conv->location->lon);
      +
      +			// Is it a reshared item?
      +			if (isset($single_conv->verb) AND ($single_conv->verb == "share") AND isset($single_conv->object)) {
      +				if (is_array($single_conv->object))
      +					$single_conv->object = $single_conv->object[0];
      +
      +				logger("Found reshared item ".$single_conv->object->id);
      +
      +				// $single_conv->object->context->conversation;
      +
      +				if (isset($single_conv->object->object->id))
      +					$arr["uri"] = $single_conv->object->object->id;
      +				else
      +					$arr["uri"] = $single_conv->object->id;
      +
      +				if (isset($single_conv->object->object->url))
      +					$plink = self::convert_href($single_conv->object->object->url);
      +				else
      +					$plink = self::convert_href($single_conv->object->url);
      +
      +				if (isset($single_conv->object->object->content))
      +					$arr["body"] = add_page_info_to_body(html2bbcode($single_conv->object->object->content));
      +				else
      +					$arr["body"] = add_page_info_to_body(html2bbcode($single_conv->object->content));
      +
      +				$arr["plink"] = $plink;
      +
      +				$arr["created"] = $single_conv->object->published;
      +				$arr["edited"] = $single_conv->object->published;
      +
      +				$arr["author-name"] = $single_conv->object->actor->displayName;
      +				if ($arr["owner-name"] == '')
      +					$arr["author-name"] = $single_conv->object->actor->contact->displayName;
      +
      +				$arr["author-link"] = $single_conv->object->actor->url;
      +				$arr["author-avatar"] = $single_conv->object->actor->image->url;
      +
      +				$arr["app"] = $single_conv->object->provider->displayName."#";
      +				//$arr["verb"] = $single_conv->object->verb;
      +
      +				$arr["location"] = $single_conv->object->location->displayName;
      +				$arr["coord"] = trim($single_conv->object->location->lat." ".$single_conv->object->location->lon);
      +			}
      +
      +			if ($arr["location"] == "")
      +				unset($arr["location"]);
      +
      +			if ($arr["coord"] == "")
      +				unset($arr["coord"]);
      +
      +			// Copy fields from given item array
      +			if (isset($item["uri"]) AND (($item["uri"] == $arr["uri"]) OR ($item["uri"] ==  $single_conv->id))) {
      +				$copy_fields = array("owner-name", "owner-link", "owner-avatar", "author-name", "author-link", "author-avatar",
      +							"gravity", "body", "object-type", "object", "verb", "created", "edited", "coord", "tag",
      +							"title", "attach", "app", "type", "location", "contact-id", "uri");
      +				foreach ($copy_fields AS $field)
      +					if (isset($item[$field]))
      +						$arr[$field] = $item[$field];
      +
      +			}
      +
      +			$newitem = item_store($arr);
      +			if (!$newitem) {
      +				logger("Item wasn't stored ".print_r($arr, true), LOGGER_DEBUG);
      +				continue;
      +			}
      +
      +			if (isset($item["uri"]) AND ($item["uri"] == $arr["uri"])) {
      +				$item = array();
      +				$item_stored = $newitem;
      +			}
      +
      +			logger('Stored new item '.$plink.' for parent '.$arr["parent-uri"].' under id '.$newitem, LOGGER_DEBUG);
      +
      +			// Add the conversation entry (but don't fetch the whole conversation)
      +			self::store_conversation($newitem, $conversation_url);
      +
      +			// 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) {
      +				logger('setting new parent to id '.$newitem);
      +				$new_parents = q("SELECT `id`, `uri`, `contact-id`, `type`, `verb`, `visible` FROM `item` WHERE `uid` = %d AND `id` = %d LIMIT 1",
      +					intval($uid), intval($newitem));
      +				if ($new_parents)
      +					$parent = $new_parents[0];
      +			}
      +		}
      +
      +		if (($item_stored < 0) AND (count($item) > 0)) {
      +
      +			if (get_config('system','ostatus_full_threads')) {
      +				$details = self::get_actor_details($item["owner-link"], $uid, $item["contact-id"]);
      +				if ($details["not_following"]) {
      +					logger("Don't import uri ".$item["uri"]." because user ".$uid." doesn't follow the person ".$item["owner-link"], LOGGER_DEBUG);
      +					return false;
      +				}
      +			}
      +
      +			$item_stored = item_store($item, true);
      +			if ($item_stored) {
      +				logger("Uri ".$item["uri"]." wasn't found in conversation ".$conversation_url, LOGGER_DEBUG);
      +				self::store_conversation($item_stored, $conversation_url);
      +			}
      +		}
       
      -	// If the thread shouldn't be completed then store the item and go away
      -	// Don't do a completion on liked content
      -	if (((intval(get_config('system','ostatus_poll_interval')) == -2) AND (count($item) > 0)) OR
      -		($item["verb"] == ACTIVITY_LIKE) OR ($conversation_url == "")) {
      -		//$arr["app"] .= " (OStatus-NoCompletion)";
      -		$item_stored = item_store($item, true);
       		return($item_stored);
       	}
       
      -	// Get the parent
      -	$parents = q("SELECT `id`, `parent`, `uri`, `contact-id`, `type`, `verb`, `visible` FROM `item` WHERE `id` IN
      -			(SELECT `parent` FROM `item` WHERE `id` IN
      -				(SELECT `oid` FROM `term` WHERE `uid` = %d AND `otype` = %d AND `type` = %d AND `url` = '%s'))",
      -			intval($uid), intval(TERM_OBJ_POST), intval(TERM_CONVERSATION), dbesc($conversation_url));
      +	private function store_conversation($itemid, $conversation_url) {
       
      -	if ($parents)
      -		$parent = $parents[0];
      -	elseif (count($item) > 0) {
      -		$parent = $item;
      -		$parent["type"] = "remote";
      -		$parent["verb"] = ACTIVITY_POST;
      -		$parent["visible"] = 1;
      -	} else {
      -		// Preset the parent
      -		$r = q("SELECT `id` FROM `contact` WHERE `self` AND `uid`=%d", $uid);
      -		if (!$r)
      -			return(-2);
      +		$conversation_url = self::convert_href($conversation_url);
       
      -		$parent = array();
      -		$parent["id"] = 0;
      -		$parent["parent"] = 0;
      -		$parent["uri"] = "";
      -		$parent["contact-id"] = $r[0]["id"];
      -		$parent["type"] = "remote";
      -		$parent["verb"] = ACTIVITY_POST;
      -		$parent["visible"] = 1;
      +		$messages = q("SELECT `uid`, `parent`, `created`, `received`, `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($itemid));
      +		if (!$messages)
      +			return;
      +		$message = $messages[0];
      +
      +		// Store conversation url if not done before
      +		$conversation = q("SELECT `url` FROM `term` WHERE `uid` = %d AND `oid` = %d AND `otype` = %d AND `type` = %d",
      +			intval($message["uid"]), intval($itemid), intval(TERM_OBJ_POST), intval(TERM_CONVERSATION));
      +
      +		if (!$conversation) {
      +			$r = q("INSERT INTO `term` (`uid`, `oid`, `otype`, `type`, `term`, `url`, `created`, `received`, `guid`) VALUES (%d, %d, %d, %d, '%s', '%s', '%s', '%s', '%s')",
      +				intval($message["uid"]), intval($itemid), intval(TERM_OBJ_POST), intval(TERM_CONVERSATION),
      +				dbesc($message["created"]), dbesc($conversation_url), dbesc($message["created"]), dbesc($message["received"]), dbesc($message["guid"]));
      +			logger('Storing conversation url '.$conversation_url.' for id '.$itemid);
      +		}
       	}
       
      -	$conv = str_replace("/conversation/", "/api/statusnet/conversation/", $conversation_url).".as";
      -	$pageno = 1;
      -	$items = array();
      +	private function get_reshared_guid($item) {
      +		$body = trim($item["body"]);
       
      -	logger('fetching conversation url '.$conv.' (Self: '.$self.') for user '.$uid);
      +		// Skip if it isn't a pure repeated messages
      +		// Does it start with a share?
      +		if (strpos($body, "[share") > 0)
      +			return("");
       
      -	do {
      -		$conv_arr = z_fetch_url($conv."?page=".$pageno);
      +		// Does it end with a share?
      +		if (strlen($body) > (strrpos($body, "[/share]") + 8))
      +			return("");
       
      -		// If it is a non-ssl site and there is an error, then try ssl or vice versa
      -		if (!$conv_arr["success"] AND (substr($conv, 0, 7) == "http://")) {
      -			$conv = str_replace("http://", "https://", $conv);
      -			$conv_as = fetch_url($conv."?page=".$pageno);
      -		} elseif (!$conv_arr["success"] AND (substr($conv, 0, 8) == "https://")) {
      -			$conv = str_replace("https://", "http://", $conv);
      -			$conv_as = fetch_url($conv."?page=".$pageno);
      -		} else
      -			$conv_as = $conv_arr["body"];
      +		$attributes = preg_replace("/\[share(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism","$1",$body);
      +		// Skip if there is no shared message in there
      +		if ($body == $attributes)
      +			return(false);
       
      -		$conv_as = str_replace(',"statusnet:notice_info":', ',"statusnet_notice_info":', $conv_as);
      -		$conv_as = json_decode($conv_as);
      +		$guid = "";
      +		preg_match("/guid='(.*?)'/ism", $attributes, $matches);
      +		if ($matches[1] != "")
      +			$guid = $matches[1];
       
      -		$no_of_items = sizeof($items);
      +		preg_match('/guid="(.*?)"/ism', $attributes, $matches);
      +		if ($matches[1] != "")
      +			$guid = $matches[1];
       
      -		if (@is_array($conv_as->items))
      -			foreach ($conv_as->items AS $single_item)
      -				$items[$single_item->id] = $single_item;
      -
      -		if ($no_of_items == sizeof($items))
      -			break;
      -
      -		$pageno++;
      -
      -	} while (true);
      -
      -	logger('fetching conversation done. Found '.count($items).' items');
      -
      -	if (!sizeof($items)) {
      -		if (count($item) > 0) {
      -			//$arr["app"] .= " (OStatus-NoConvFetched)";
      -			$item_stored = item_store($item, true);
      -
      -			if ($item_stored) {
      -				logger("Conversation ".$conversation_url." couldn't be fetched. Item uri ".$item["uri"]." stored: ".$item_stored, LOGGER_DEBUG);
      -				ostatus_store_conversation($item_id, $conversation_url);
      -			}
      -
      -			return($item_stored);
      -		} else
      -			return(-3);
      +		return $guid;
       	}
       
      -	$items = array_reverse($items);
      +	private function format_picture_post($body) {
      +		$siteinfo = get_attached_data($body);
       
      -	$r = q("SELECT `nurl` FROM `contact` WHERE `uid` = %d AND `self`", intval($uid));
      -	$importer = $r[0];
      +		if (($siteinfo["type"] == "photo")) {
      +			if (isset($siteinfo["preview"]))
      +				$preview = $siteinfo["preview"];
      +			else
      +				$preview = $siteinfo["image"];
       
      -	$new_parent = true;
      +			// Is it a remote picture? Then make a smaller preview here
      +			$preview = proxy_url($preview, false, PROXY_SIZE_SMALL);
       
      -	foreach ($items as $single_conv) {
      +			// Is it a local picture? Then make it smaller here
      +			$preview = str_replace(array("-0.jpg", "-0.png"), array("-2.jpg", "-2.png"), $preview);
      +			$preview = str_replace(array("-1.jpg", "-1.png"), array("-2.jpg", "-2.png"), $preview);
       
      -		// Update the gcontact table
      -		ostatus_conv_fetch_actor($single_conv->actor);
      +			if (isset($siteinfo["url"]))
      +				$url = $siteinfo["url"];
      +			else
      +				$url = $siteinfo["image"];
       
      -		// Test - remove before flight
      -		//$tempfile = tempnam(get_temppath(), "conversation");
      -		//file_put_contents($tempfile, json_encode($single_conv));
      -
      -		$mention = false;
      -
      -		if (isset($single_conv->object->id))
      -			$single_conv->id = $single_conv->object->id;
      -
      -		$plink = ostatus_convert_href($single_conv->id);
      -		if (isset($single_conv->object->url))
      -			$plink = ostatus_convert_href($single_conv->object->url);
      -
      -		if (@!$single_conv->id)
      -			continue;
      -
      -		logger("Got id ".$single_conv->id, LOGGER_DEBUG);
      -
      -		if ($first_id == "") {
      -			$first_id = $single_conv->id;
      -
      -			// The first post of the conversation isn't our first post. There are three options:
      -			// 1. Our conversation hasn't the "real" thread starter
      -			// 2. This first post is a post inside our thread
      -			// 3. This first post is a post inside another thread
      -			if (($first_id != $parent["uri"]) AND ($parent["uri"] != "")) {
      -
      -				$new_parent = true;
      -
      -				$new_parents = q("SELECT `id`, `parent`, `uri`, `contact-id`, `type`, `verb`, `visible` FROM `item` WHERE `id` IN
      -							(SELECT `parent` FROM `item`
      -								WHERE `uid` = %d AND `uri` = '%s' AND `network` IN ('%s','%s')) LIMIT 1",
      -					intval($uid), dbesc($first_id), dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN));
      -				if ($new_parents) {
      -					if ($new_parents[0]["parent"] == $parent["parent"]) {
      -						// Option 2: This post is already present inside our thread - but not as thread starter
      -						logger("Option 2: uri present in our thread: ".$first_id, LOGGER_DEBUG);
      -						$first_id = $parent["uri"];
      -					} else {
      -						// Option 3: Not so good. We have mixed parents. We have to see how to clean this up.
      -						// For now just take the new parent.
      -						$parent = $new_parents[0];
      -						$first_id = $parent["uri"];
      -						logger("Option 3: mixed parents for uri ".$first_id, LOGGER_DEBUG);
      -					}
      -				} else {
      -					// Option 1: We hadn't got the real thread starter
      -					// We have to clean up our existing messages.
      -					$parent["id"] = 0;
      -					$parent["uri"] = $first_id;
      -					logger("Option 1: we have a new parent: ".$first_id, LOGGER_DEBUG);
      -				}
      -			} elseif ($parent["uri"] == "") {
      -				$parent["id"] = 0;
      -				$parent["uri"] = $first_id;
      -			}
      +			$body = trim($siteinfo["text"])." [url]".$url."[/url]\n[img]".$preview."[/img]";
       		}
       
      -		$parent_uri = $parent["uri"];
      +		return $body;
      +	}
       
      -		// "context" only seems to exist on older servers
      -		if (isset($single_conv->context->inReplyTo->id)) {
      -			$parent_exists = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s' AND `network` IN ('%s','%s') LIMIT 1",
      -						intval($uid), dbesc($single_conv->context->inReplyTo->id), dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN));
      -			if ($parent_exists)
      -				$parent_uri = $single_conv->context->inReplyTo->id;
      -		}
      +	private function add_header($doc, $owner) {
       
      -		// This is the current way
      -		if (isset($single_conv->object->inReplyTo->id)) {
      -			$parent_exists = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s' AND `network` IN ('%s','%s') LIMIT 1",
      -						intval($uid), dbesc($single_conv->object->inReplyTo->id), dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN));
      -			if ($parent_exists)
      -				$parent_uri = $single_conv->object->inReplyTo->id;
      -		}
      +		$a = get_app();
       
      -		$message_exists = q("SELECT `id`, `parent`, `uri` FROM `item` WHERE `uid` = %d AND `uri` = '%s' AND `network` IN ('%s','%s') LIMIT 1",
      -						intval($uid), dbesc($single_conv->id),
      -						dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN));
      -		if ($message_exists) {
      -			logger("Message ".$single_conv->id." already existed on the system", LOGGER_DEBUG);
      +		$root = $doc->createElementNS(NAMESPACE_ATOM1, 'feed');
      +		$doc->appendChild($root);
       
      -			if ($parent["id"] != 0) {
      -				$existing_message = $message_exists[0];
      +		$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);
       
      -				// We improved the way we fetch OStatus messages, this shouldn't happen very often now
      -				/// @TODO We have to change the shadow copies as well. This way here is really ugly.
      -				if ($existing_message["parent"] != $parent["id"]) {
      -					logger('updating id '.$existing_message["id"].' with parent '.$existing_message["parent"].' to parent '.$parent["id"].' uri '.$parent["uri"].' thread '.$parent_uri, LOGGER_DEBUG);
      +		$attributes = array("uri" => "https://friendi.ca", "version" => FRIENDICA_VERSION."-".DB_UPDATE_VERSION);
      +		xml::add_element($doc, $root, "generator", FRIENDICA_PLATFORM, $attributes);
      +		xml::add_element($doc, $root, "id", App::get_baseurl()."/profile/".$owner["nick"]);
      +		xml::add_element($doc, $root, "title", sprintf("%s timeline", $owner["name"]));
      +		xml::add_element($doc, $root, "subtitle", sprintf("Updates from %s on %s", $owner["name"], $a->config["sitename"]));
      +		xml::add_element($doc, $root, "logo", $owner["photo"]);
      +		xml::add_element($doc, $root, "updated", datetime_convert("UTC", "UTC", "now", ATOM_TIME));
       
      -					// Update the parent id of the selected item
      -					$r = q("UPDATE `item` SET `parent` = %d, `parent-uri` = '%s' WHERE `id` = %d",
      -						intval($parent["id"]), dbesc($parent["uri"]), intval($existing_message["id"]));
      +		$author = self::add_author($doc, $owner);
      +		$root->appendChild($author);
       
      -					// Update the parent uri in the thread - but only if it points to itself
      -					$r = q("UPDATE `item` SET `thr-parent` = '%s' WHERE `id` = %d AND `uri` = `thr-parent`",
      -						dbesc($parent_uri), intval($existing_message["id"]));
      +		$attributes = array("href" => $owner["url"], "rel" => "alternate", "type" => "text/html");
      +		xml::add_element($doc, $root, "link", "", $attributes);
       
      -					// try to change all items of the same parent
      -					$r = q("UPDATE `item` SET `parent` = %d, `parent-uri` = '%s' WHERE `parent` = %d",
      -						intval($parent["id"]), dbesc($parent["uri"]), intval($existing_message["parent"]));
      +		/// @TODO We have to find out what this is
      +		/// $attributes = array("href" => App::get_baseurl()."/sup",
      +		///		"rel" => "http://api.friendfeed.com/2008/03#sup",
      +		///		"type" => "application/json");
      +		/// xml::add_element($doc, $root, "link", "", $attributes);
       
      -					// Update the parent uri in the thread - but only if it points to itself
      -					$r = q("UPDATE `item` SET `thr-parent` = '%s' WHERE (`parent` = %d) AND (`uri` = `thr-parent`)",
      -						dbesc($parent["uri"]), intval($existing_message["parent"]));
      +		self::hublinks($doc, $root);
       
      -					// Now delete the thread
      -					delete_thread($existing_message["parent"]);
      +		$attributes = array("href" => App::get_baseurl()."/salmon/".$owner["nick"], "rel" => "salmon");
      +		xml::add_element($doc, $root, "link", "", $attributes);
      +
      +		$attributes = array("href" => App::get_baseurl()."/salmon/".$owner["nick"], "rel" => "http://salmon-protocol.org/ns/salmon-replies");
      +		xml::add_element($doc, $root, "link", "", $attributes);
      +
      +		$attributes = array("href" => App::get_baseurl()."/salmon/".$owner["nick"], "rel" => "http://salmon-protocol.org/ns/salmon-mention");
      +		xml::add_element($doc, $root, "link", "", $attributes);
      +
      +		$attributes = array("href" => App::get_baseurl()."/api/statuses/user_timeline/".$owner["nick"].".atom",
      +				"rel" => "self", "type" => "application/atom+xml");
      +		xml::add_element($doc, $root, "link", "", $attributes);
      +
      +		return $root;
      +	}
      +
      +	public static function hublinks($doc, $root) {
      +		$hub = get_config('system','huburl');
      +
      +		$hubxml = '';
      +		if(strlen($hub)) {
      +			$hubs = explode(',', $hub);
      +			if(count($hubs)) {
      +				foreach($hubs as $h) {
      +					$h = trim($h);
      +					if(! strlen($h))
      +						continue;
      +					if ($h === '[internal]')
      +						$h = App::get_baseurl() . '/pubsubhubbub';
      +					xml::add_element($doc, $root, "link", "", array("href" => $h, "rel" => "hub"));
       				}
       			}
      -
      -			// The item we are having on the system is the one that we wanted to store via the item array
      -			if (isset($item["uri"]) AND ($item["uri"] == $existing_message["uri"])) {
      -				$item = array();
      -				$item_stored = 0;
      -			}
      -
      -			continue;
      -		}
      -
      -		if (is_array($single_conv->to))
      -			foreach($single_conv->to AS $to)
      -				if ($importer["nurl"] == normalise_link($to->id))
      -					$mention = true;
      -
      -		$actor = $single_conv->actor->id;
      -		if (isset($single_conv->actor->url))
      -			$actor = $single_conv->actor->url;
      -
      -		$details = ostatus_get_actor_details($actor, $uid, $parent["contact-id"]);
      -
      -		// Do we only want to import threads that were started by our contacts?
      -		if ($details["not_following"] AND $new_parent AND get_config('system','ostatus_full_threads')) {
      -			logger("Don't import uri ".$first_id." because user ".$uid." doesn't follow the person ".$actor, LOGGER_DEBUG);
      -			continue;
      -		}
      -
      -		$arr = array();
      -		$arr["network"] = $details["network"];
      -		$arr["uri"] = $single_conv->id;
      -		$arr["plink"] = $plink;
      -		$arr["uid"] = $uid;
      -		$arr["contact-id"] = $details["contact_id"];
      -		$arr["parent-uri"] = $parent_uri;
      -		$arr["created"] = $single_conv->published;
      -		$arr["edited"] = $single_conv->published;
      -		$arr["owner-name"] = $single_conv->actor->displayName;
      -		if ($arr["owner-name"] == '')
      -			$arr["owner-name"] = $single_conv->actor->contact->displayName;
      -		if ($arr["owner-name"] == '')
      -			$arr["owner-name"] = $single_conv->actor->portablecontacts_net->displayName;
      -
      -		$arr["owner-link"] = $actor;
      -		$arr["owner-avatar"] = $single_conv->actor->image->url;
      -		$arr["author-name"] = $arr["owner-name"];
      -		$arr["author-link"] = $actor;
      -		$arr["author-avatar"] = $single_conv->actor->image->url;
      -		$arr["body"] = add_page_info_to_body(html2bbcode($single_conv->content));
      -
      -		if (isset($single_conv->status_net->notice_info->source))
      -			$arr["app"] = strip_tags($single_conv->status_net->notice_info->source);
      -		elseif (isset($single_conv->statusnet->notice_info->source))
      -			$arr["app"] = strip_tags($single_conv->statusnet->notice_info->source);
      -		elseif (isset($single_conv->statusnet_notice_info->source))
      -			$arr["app"] = strip_tags($single_conv->statusnet_notice_info->source);
      -		elseif (isset($single_conv->provider->displayName))
      -			$arr["app"] = $single_conv->provider->displayName;
      -		else
      -			$arr["app"] = "OStatus";
      -
      -		//$arr["app"] .= " (Conversation)";
      -
      -		$arr["object"] = json_encode($single_conv);
      -		$arr["verb"] = $parent["verb"];
      -		$arr["visible"] = $parent["visible"];
      -		$arr["location"] = $single_conv->location->displayName;
      -		$arr["coord"] = trim($single_conv->location->lat." ".$single_conv->location->lon);
      -
      -		// Is it a reshared item?
      -		if (isset($single_conv->verb) AND ($single_conv->verb == "share") AND isset($single_conv->object)) {
      -			if (is_array($single_conv->object))
      -				$single_conv->object = $single_conv->object[0];
      -
      -			logger("Found reshared item ".$single_conv->object->id);
      -
      -			// $single_conv->object->context->conversation;
      -
      -			if (isset($single_conv->object->object->id))
      -				$arr["uri"] = $single_conv->object->object->id;
      -			else
      -				$arr["uri"] = $single_conv->object->id;
      -
      -			if (isset($single_conv->object->object->url))
      -				$plink = ostatus_convert_href($single_conv->object->object->url);
      -			else
      -				$plink = ostatus_convert_href($single_conv->object->url);
      -
      -			if (isset($single_conv->object->object->content))
      -				$arr["body"] = add_page_info_to_body(html2bbcode($single_conv->object->object->content));
      -			else
      -				$arr["body"] = add_page_info_to_body(html2bbcode($single_conv->object->content));
      -
      -			$arr["plink"] = $plink;
      -
      -			$arr["created"] = $single_conv->object->published;
      -			$arr["edited"] = $single_conv->object->published;
      -
      -			$arr["author-name"] = $single_conv->object->actor->displayName;
      -			if ($arr["owner-name"] == '')
      -				$arr["author-name"] = $single_conv->object->actor->contact->displayName;
      -
      -			$arr["author-link"] = $single_conv->object->actor->url;
      -			$arr["author-avatar"] = $single_conv->object->actor->image->url;
      -
      -			$arr["app"] = $single_conv->object->provider->displayName."#";
      -			//$arr["verb"] = $single_conv->object->verb;
      -
      -			$arr["location"] = $single_conv->object->location->displayName;
      -			$arr["coord"] = trim($single_conv->object->location->lat." ".$single_conv->object->location->lon);
      -		}
      -
      -		if ($arr["location"] == "")
      -			unset($arr["location"]);
      -
      -		if ($arr["coord"] == "")
      -			unset($arr["coord"]);
      -
      -		// Copy fields from given item array
      -		if (isset($item["uri"]) AND (($item["uri"] == $arr["uri"]) OR ($item["uri"] ==  $single_conv->id))) {
      -			$copy_fields = array("owner-name", "owner-link", "owner-avatar", "author-name", "author-link", "author-avatar",
      -						"gravity", "body", "object-type", "object", "verb", "created", "edited", "coord", "tag",
      -						"title", "attach", "app", "type", "location", "contact-id", "uri");
      -			foreach ($copy_fields AS $field)
      -				if (isset($item[$field]))
      -					$arr[$field] = $item[$field];
      -
      -			//$arr["app"] .= " (OStatus)";
      -		}
      -
      -		$newitem = item_store($arr);
      -		if (!$newitem) {
      -			logger("Item wasn't stored ".print_r($arr, true), LOGGER_DEBUG);
      -			continue;
      -		}
      -
      -		if (isset($item["uri"]) AND ($item["uri"] == $arr["uri"])) {
      -			$item = array();
      -			$item_stored = $newitem;
      -		}
      -
      -		logger('Stored new item '.$plink.' for parent '.$arr["parent-uri"].' under id '.$newitem, LOGGER_DEBUG);
      -
      -		// Add the conversation entry (but don't fetch the whole conversation)
      -		ostatus_store_conversation($newitem, $conversation_url);
      -
      -		// 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) {
      -			logger('setting new parent to id '.$newitem);
      -			$new_parents = q("SELECT `id`, `uri`, `contact-id`, `type`, `verb`, `visible` FROM `item` WHERE `uid` = %d AND `id` = %d LIMIT 1",
      -				intval($uid), intval($newitem));
      -			if ($new_parents)
      -				$parent = $new_parents[0];
       		}
       	}
       
      -	if (($item_stored < 0) AND (count($item) > 0)) {
      -		//$arr["app"] .= " (OStatus-NoConvFound)";
      +	private function get_attachment($doc, $root, $item) {
      +		$o = "";
      +		$siteinfo = get_attached_data($item["body"]);
       
      -		if (get_config('system','ostatus_full_threads')) {
      -			$details = ostatus_get_actor_details($item["owner-link"], $uid, $item["contact-id"]);
      -			if ($details["not_following"]) {
      -				logger("Don't import uri ".$item["uri"]." because user ".$uid." doesn't follow the person ".$item["owner-link"], LOGGER_DEBUG);
      -				return false;
      -			}
      -		}
      -
      -		$item_stored = item_store($item, true);
      -		if ($item_stored) {
      -			logger("Uri ".$item["uri"]." wasn't found in conversation ".$conversation_url, LOGGER_DEBUG);
      -			ostatus_store_conversation($item_stored, $conversation_url);
      -		}
      -	}
      -
      -	return($item_stored);
      -}
      -
      -function ostatus_store_conversation($itemid, $conversation_url) {
      -	global $a;
      -
      -	$conversation_url = ostatus_convert_href($conversation_url);
      -
      -	$messages = q("SELECT `uid`, `parent`, `created`, `received`, `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($itemid));
      -	if (!$messages)
      -		return;
      -	$message = $messages[0];
      -
      -	// Store conversation url if not done before
      -	$conversation = q("SELECT `url` FROM `term` WHERE `uid` = %d AND `oid` = %d AND `otype` = %d AND `type` = %d",
      -		intval($message["uid"]), intval($itemid), intval(TERM_OBJ_POST), intval(TERM_CONVERSATION));
      -
      -	if (!$conversation) {
      -		$r = q("INSERT INTO `term` (`uid`, `oid`, `otype`, `type`, `term`, `url`, `created`, `received`, `guid`) VALUES (%d, %d, %d, %d, '%s', '%s', '%s', '%s', '%s')",
      -			intval($message["uid"]), intval($itemid), intval(TERM_OBJ_POST), intval(TERM_CONVERSATION),
      -			dbesc($message["created"]), dbesc($conversation_url), dbesc($message["created"]), dbesc($message["received"]), dbesc($message["guid"]));
      -		logger('Storing conversation url '.$conversation_url.' for id '.$itemid);
      -	}
      -}
      -
      -function get_reshared_guid($item) {
      -	$body = trim($item["body"]);
      -
      -	// Skip if it isn't a pure repeated messages
      -	// Does it start with a share?
      -	if (strpos($body, "[share") > 0)
      -		return("");
      -
      -	// Does it end with a share?
      -	if (strlen($body) > (strrpos($body, "[/share]") + 8))
      -		return("");
      -
      -	$attributes = preg_replace("/\[share(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism","$1",$body);
      -	// Skip if there is no shared message in there
      -	if ($body == $attributes)
      -		return(false);
      -
      -	$guid = "";
      -	preg_match("/guid='(.*?)'/ism", $attributes, $matches);
      -	if ($matches[1] != "")
      -		$guid = $matches[1];
      -
      -	preg_match('/guid="(.*?)"/ism', $attributes, $matches);
      -	if ($matches[1] != "")
      -		$guid = $matches[1];
      -
      -	return $guid;
      -}
      -
      -function xml_create_element($doc, $element, $value = "", $attributes = array()) {
      -	$element = $doc->createElement($element, xmlify($value));
      -
      -	foreach ($attributes AS $key => $value) {
      -		$attribute = $doc->createAttribute($key);
      -		$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);
      -}
      -
      -function ostatus_format_picture_post($body) {
      -	$siteinfo = get_attached_data($body);
      -
      -	if (($siteinfo["type"] == "photo")) {
      -		if (isset($siteinfo["preview"]))
      -			$preview = $siteinfo["preview"];
      -		else
      -			$preview = $siteinfo["image"];
      -
      -		// Is it a remote picture? Then make a smaller preview here
      -		$preview = proxy_url($preview, false, PROXY_SIZE_SMALL);
      -
      -		// Is it a local picture? Then make it smaller here
      -		$preview = str_replace(array("-0.jpg", "-0.png"), array("-2.jpg", "-2.png"), $preview);
      -		$preview = str_replace(array("-1.jpg", "-1.png"), array("-2.jpg", "-2.png"), $preview);
      -
      -		if (isset($siteinfo["url"]))
      -			$url = $siteinfo["url"];
      -		else
      -			$url = $siteinfo["image"];
      -
      -		$body = trim($siteinfo["text"])." [url]".$url."[/url]\n[img]".$preview."[/img]";
      -	}
      -
      -	return $body;
      -}
      -
      -function ostatus_add_header($doc, $owner) {
      -	$a = get_app();
      -
      -	$root = $doc->createElementNS(NAMESPACE_ATOM1, 'feed');
      -	$doc->appendChild($root);
      -
      -	$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);
      -	xml_add_element($doc, $root, "id", $a->get_baseurl()."/profile/".$owner["nick"]);
      -	xml_add_element($doc, $root, "title", sprintf("%s timeline", $owner["name"]));
      -	xml_add_element($doc, $root, "subtitle", sprintf("Updates from %s on %s", $owner["name"], $a->config["sitename"]));
      -	xml_add_element($doc, $root, "logo", $owner["photo"]);
      -	xml_add_element($doc, $root, "updated", datetime_convert("UTC", "UTC", "now", ATOM_TIME));
      -
      -	$author = ostatus_add_author($doc, $owner);
      -	$root->appendChild($author);
      -
      -	$attributes = array("href" => $owner["url"], "rel" => "alternate", "type" => "text/html");
      -	xml_add_element($doc, $root, "link", "", $attributes);
      -
      -	/// @TODO We have to find out what this is
      -	/// $attributes = array("href" => $a->get_baseurl()."/sup",
      -	///		"rel" => "http://api.friendfeed.com/2008/03#sup",
      -	///		"type" => "application/json");
      -	/// xml_add_element($doc, $root, "link", "", $attributes);
      -
      -	ostatus_hublinks($doc, $root);
      -
      -	$attributes = array("href" => $a->get_baseurl()."/salmon/".$owner["nick"], "rel" => "salmon");
      -	xml_add_element($doc, $root, "link", "", $attributes);
      -
      -	$attributes = array("href" => $a->get_baseurl()."/salmon/".$owner["nick"], "rel" => "http://salmon-protocol.org/ns/salmon-replies");
      -	xml_add_element($doc, $root, "link", "", $attributes);
      -
      -	$attributes = array("href" => $a->get_baseurl()."/salmon/".$owner["nick"], "rel" => "http://salmon-protocol.org/ns/salmon-mention");
      -	xml_add_element($doc, $root, "link", "", $attributes);
      -
      -	$attributes = array("href" => $a->get_baseurl()."/api/statuses/user_timeline/".$owner["nick"].".atom",
      -			"rel" => "self", "type" => "application/atom+xml");
      -	xml_add_element($doc, $root, "link", "", $attributes);
      -
      -	return $root;
      -}
      -
      -function ostatus_hublinks($doc, $root) {
      -	$a = get_app();
      -	$hub = get_config('system','huburl');
      -
      -	$hubxml = '';
      -	if(strlen($hub)) {
      -		$hubs = explode(',', $hub);
      -		if(count($hubs)) {
      -			foreach($hubs as $h) {
      -				$h = trim($h);
      -				if(! strlen($h))
      -					continue;
      -				if ($h === '[internal]')
      -					$h = $a->get_baseurl() . '/pubsubhubbub';
      -				xml_add_element($doc, $root, "link", "", array("href" => $h, "rel" => "hub"));
      -			}
      -		}
      -	}
      -}
      -
      -function ostatus_get_attachment($doc, $root, $item) {
      -	$o = "";
      -	$siteinfo = get_attached_data($item["body"]);
      -
      -	switch($siteinfo["type"]) {
      -		case 'link':
      -			$attributes = array("rel" => "enclosure",
      -					"href" => $siteinfo["url"],
      -					"type" => "text/html; charset=UTF-8",
      -					"length" => "",
      -					"title" => $siteinfo["title"]);
      -			xml_add_element($doc, $root, "link", "", $attributes);
      -			break;
      -		case 'photo':
      -			$imgdata = get_photo_info($siteinfo["image"]);
      -			$attributes = array("rel" => "enclosure",
      -					"href" => $siteinfo["image"],
      -					"type" => $imgdata["mime"],
      -					"length" => intval($imgdata["size"]));
      -			xml_add_element($doc, $root, "link", "", $attributes);
      -			break;
      -		case 'video':
      -			$attributes = array("rel" => "enclosure",
      -					"href" => $siteinfo["url"],
      -					"type" => "text/html; charset=UTF-8",
      -					"length" => "",
      -					"title" => $siteinfo["title"]);
      -			xml_add_element($doc, $root, "link", "", $attributes);
      -			break;
      -		default:
      -			break;
      -	}
      -
      -	if (($siteinfo["type"] != "photo") AND isset($siteinfo["image"])) {
      -		$photodata = get_photo_info($siteinfo["image"]);
      -
      -		$attributes = array("rel" => "preview", "href" => $siteinfo["image"], "media:width" => $photodata[0], "media:height" => $photodata[1]);
      -		xml_add_element($doc, $root, "link", "", $attributes);
      -	}
      -
      -
      -	$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) {
      +		switch($siteinfo["type"]) {
      +			case 'link':
       				$attributes = array("rel" => "enclosure",
      -						"href" => $matches[1],
      -						"type" => $matches[3]);
      +						"href" => $siteinfo["url"],
      +						"type" => "text/html; charset=UTF-8",
      +						"length" => "",
      +						"title" => $siteinfo["title"]);
      +				xml::add_element($doc, $root, "link", "", $attributes);
      +				break;
      +			case 'photo':
      +				$imgdata = get_photo_info($siteinfo["image"]);
      +				$attributes = array("rel" => "enclosure",
      +						"href" => $siteinfo["image"],
      +						"type" => $imgdata["mime"],
      +						"length" => intval($imgdata["size"]));
      +				xml::add_element($doc, $root, "link", "", $attributes);
      +				break;
      +			case 'video':
      +				$attributes = array("rel" => "enclosure",
      +						"href" => $siteinfo["url"],
      +						"type" => "text/html; charset=UTF-8",
      +						"length" => "",
      +						"title" => $siteinfo["title"]);
      +				xml::add_element($doc, $root, "link", "", $attributes);
      +				break;
      +			default:
      +				break;
      +		}
       
      -				if(intval($matches[2]))
      -					$attributes["length"] = intval($matches[2]);
      +		if (($siteinfo["type"] != "photo") AND isset($siteinfo["image"])) {
      +			$photodata = get_photo_info($siteinfo["image"]);
       
      -				if(trim($matches[4]) != "")
      -					$attributes["title"] = trim($matches[4]);
      +			$attributes = array("rel" => "preview", "href" => $siteinfo["image"], "media:width" => $photodata[0], "media:height" => $photodata[1]);
      +			xml::add_element($doc, $root, "link", "", $attributes);
      +		}
       
      -				xml_add_element($doc, $root, "link", "", $attributes);
      +
      +		$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);
      +				}
       			}
       		}
       	}
      -}
       
      -function ostatus_add_author($doc, $owner) {
      -	$a = get_app();
      +	private function add_author($doc, $owner) {
       
      -	$r = q("SELECT `homepage` FROM `profile` WHERE `uid` = %d AND `is-default` LIMIT 1", intval($owner["uid"]));
      -	if ($r)
      -		$profile = $r[0];
      +		$r = q("SELECT `homepage` FROM `profile` WHERE `uid` = %d AND `is-default` LIMIT 1", intval($owner["uid"]));
      +		if ($r)
      +			$profile = $r[0];
       
      -	$author = $doc->createElement("author");
      -	xml_add_element($doc, $author, "activity:object-type", ACTIVITY_OBJ_PERSON);
      -	xml_add_element($doc, $author, "uri", $owner["url"]);
      -	xml_add_element($doc, $author, "name", $owner["name"]);
      +		$author = $doc->createElement("author");
      +		xml::add_element($doc, $author, "activity:object-type", ACTIVITY_OBJ_PERSON);
      +		xml::add_element($doc, $author, "uri", $owner["url"]);
      +		xml::add_element($doc, $author, "name", $owner["name"]);
      +		xml::add_element($doc, $author, "summary", bbcode($owner["about"], false, false, 7));
       
      -	$attributes = array("rel" => "alternate", "type" => "text/html", "href" => $owner["url"]);
      -	xml_add_element($doc, $author, "link", "", $attributes);
      +		$attributes = array("rel" => "alternate", "type" => "text/html", "href" => $owner["url"]);
      +		xml::add_element($doc, $author, "link", "", $attributes);
       
      -	$attributes = array(
      -			"rel" => "avatar",
      -			"type" => "image/jpeg", // To-Do?
      -			"media:width" => 175,
      -			"media:height" => 175,
      -			"href" => $owner["photo"]);
      -	xml_add_element($doc, $author, "link", "", $attributes);
      -
      -	if (isset($owner["thumb"])) {
       		$attributes = array(
       				"rel" => "avatar",
       				"type" => "image/jpeg", // To-Do?
      -				"media:width" => 80,
      -				"media:height" => 80,
      -				"href" => $owner["thumb"]);
      -		xml_add_element($doc, $author, "link", "", $attributes);
      +				"media:width" => 175,
      +				"media:height" => 175,
      +				"href" => $owner["photo"]);
      +		xml::add_element($doc, $author, "link", "", $attributes);
      +
      +		if (isset($owner["thumb"])) {
      +			$attributes = array(
      +					"rel" => "avatar",
      +					"type" => "image/jpeg", // To-Do?
      +					"media:width" => 80,
      +					"media:height" => 80,
      +					"href" => $owner["thumb"]);
      +			xml::add_element($doc, $author, "link", "", $attributes);
      +		}
      +
      +		xml::add_element($doc, $author, "poco:preferredUsername", $owner["nick"]);
      +		xml::add_element($doc, $author, "poco:displayName", $owner["name"]);
      +		xml::add_element($doc, $author, "poco:note", bbcode($owner["about"], false, false, 7));
      +
      +		if (trim($owner["location"]) != "") {
      +			$element = $doc->createElement("poco:address");
      +			xml::add_element($doc, $element, "poco:formatted", $owner["location"]);
      +			$author->appendChild($element);
      +		}
      +
      +		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 (count($profile)) {
      +			xml::add_element($doc, $author, "followers", "", array("url" => App::get_baseurl()."/viewcontacts/".$owner["nick"]));
      +			xml::add_element($doc, $author, "statusnet:profile_info", "", array("local_id" => $owner["uid"]));
      +		}
      +
      +		return $author;
       	}
       
      -	xml_add_element($doc, $author, "poco:preferredUsername", $owner["nick"]);
      -	xml_add_element($doc, $author, "poco:displayName", $owner["name"]);
      -	xml_add_element($doc, $author, "poco:note", $owner["about"]);
      +	/**
      +	 * @TODO Picture attachments should look like this:
      +	 *	https://status.pirati.ca/attachment/572819
      +	 *
      +	*/
       
      -	if (trim($owner["location"]) != "") {
      -		$element = $doc->createElement("poco:address");
      -		xml_add_element($doc, $element, "poco:formatted", $owner["location"]);
      -		$author->appendChild($element);
      +	function construct_verb($item) {
      +		if ($item['verb'])
      +			return $item['verb'];
      +		return ACTIVITY_POST;
       	}
       
      -	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);
      +	function construct_objecttype($item) {
      +		if (in_array($item['object-type'], array(ACTIVITY_OBJ_NOTE, ACTIVITY_OBJ_COMMENT)))
      +			return $item['object-type'];
      +		return ACTIVITY_OBJ_NOTE;
       	}
       
      -	if (count($profile)) {
      -		xml_add_element($doc, $author, "followers", "", array("url" => $a->get_baseurl()."/viewcontacts/".$owner["nick"]));
      -		xml_add_element($doc, $author, "statusnet:profile_info", "", array("local_id" => $owner["uid"]));
      +	private function entry($doc, $item, $owner, $toplevel = false) {
      +		$repeated_guid = self::get_reshared_guid($item);
      +		if ($repeated_guid != "")
      +			$xml = self::reshare_entry($doc, $item, $owner, $repeated_guid, $toplevel);
      +
      +		if ($xml)
      +			return $xml;
      +
      +		if ($item["verb"] == ACTIVITY_LIKE)
      +			return self::like_entry($doc, $item, $owner, $toplevel);
      +		else
      +			return self::note_entry($doc, $item, $owner, $toplevel);
       	}
       
      -	return $author;
      -}
      +	private function source_entry($doc, $contact) {
      +		$source = $doc->createElement("source");
      +		xml::add_element($doc, $source, "id", $contact["poll"]);
      +		xml::add_element($doc, $source, "title", $contact["name"]);
      +		xml::add_element($doc, $source, "link", "", array("rel" => "alternate",
      +								"type" => "text/html",
      +								"href" => $contact["alias"]));
      +		xml::add_element($doc, $source, "link", "", array("rel" => "self",
      +								"type" => "application/atom+xml",
      +								"href" => $contact["poll"]));
      +		xml::add_element($doc, $source, "icon", $contact["photo"]);
      +		xml::add_element($doc, $source, "updated", datetime_convert("UTC","UTC",$contact["success_update"]."+00:00",ATOM_TIME));
       
      -/** 
      - * @TODO Picture attachments should look like this:
      - *	https://status.pirati.ca/attachment/572819
      - * 
      -*/
      -
      -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);
      +		return $source;
       	}
       
      -	$is_repeat = false;
      +	private function contact_entry($url, $owner) {
       
      -/*	if (!$repeat) {
      -		$repeated_guid = get_reshared_guid($item);
      +		$r = q("SELECT * FROM `contact` WHERE `nurl` = '%s' AND `uid` IN (0, %d) ORDER BY `uid` DESC LIMIT 1",
      +			dbesc(normalise_link($url)), intval($owner["uid"]));
      +		if ($r) {
      +			$contact = $r[0];
      +			$contact["uid"] = -1;
      +		}
       
      -		if ($repeated_guid != "") {
      -			$r = q("SELECT * FROM `item` WHERE `uid` = %d AND `guid` = '%s' LIMIT 1",
      -				intval($owner["uid"]), dbesc($repeated_guid));
      +		if (!$r) {
      +			$r = q("SELECT * FROM `gcontact` WHERE `nurl` = '%s' LIMIT 1",
      +				dbesc(normalise_link($url)));
       			if ($r) {
      -				$repeated_item = $r[0];
      -				$is_repeat = true;
      +				$contact = $r[0];
      +				$contact["uid"] = -1;
      +				$contact["success_update"] = $contact["updated"];
       			}
       		}
      -	}
      -*/
      -	if (!$toplevel AND !$repeat) {
      -		$entry = $doc->createElement("entry");
      -		$title = sprintf("New note by %s", $owner["nick"]);
      -	} elseif (!$toplevel AND $repeat) {
      -		$entry = $doc->createElement("activity:object");
      -		$title = sprintf("New note by %s", $owner["nick"]);
      -	} else {
      -		$entry = $doc->createElementNS(NAMESPACE_ATOM1, "entry");
       
      -		$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);
      +		if (!$r)
      +			$contact = owner;
       
      -		$author = ostatus_add_author($doc, $owner);
      -		$entry->appendChild($author);
      -
      -		$title = sprintf("New comment by %s", $owner["nick"]);
      -	}
      -
      -	// To use the object-type "bookmark" we have to implement these elements:
      -	//
      -	// http://activitystrea.ms/schema/1.0/bookmark
      -	// Historic Rocket Landing
      -	// Nur ein Testbeitrag.
      -	// 
      -	// 
      -	//
      -	// But: it seems as if it doesn't federate well between the GS servers
      -	// So we just set it to "note" to be sure that it reaches their target systems
      -
      -	if (!$repeat)
      -		xml_add_element($doc, $entry, "activity:object-type", ACTIVITY_OBJ_NOTE);
      -	else
      -		xml_add_element($doc, $entry, "activity:object-type", NAMESPACE_ACTIVITY_SCHEMA.'activity');
      -
      -	xml_add_element($doc, $entry, "id", $item["uri"]);
      -	xml_add_element($doc, $entry, "title", $title);
      -
      -	if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid'])
      -		$body = fix_private_photos($item['body'],$owner['uid'],$item, 0);
      -	else
      -		$body = $item['body'];
      -
      -	$body = ostatus_format_picture_post($body);
      -
      -	if ($item['title'] != "")
      -		$body = "[b]".$item['title']."[/b]\n\n".$body;
      -
      -	//$body = bb_remove_share_information($body);
      -	$body = bbcode($body, false, false, 7);
      -
      -	xml_add_element($doc, $entry, "content", $body, array("type" => "html"));
      -
      -	xml_add_element($doc, $entry, "link", "", array("rel" => "alternate", "type" => "text/html",
      -							"href" => $a->get_baseurl()."/display/".$item["guid"]));
      -
      -	xml_add_element($doc, $entry, "status_net", "", array("notice_id" => $item["id"]));
      -
      -	if (!$is_repeat)
      -		xml_add_element($doc, $entry, "activity:verb", construct_verb($item));
      -	else
      -		xml_add_element($doc, $entry, "activity:verb", ACTIVITY_SHARE);
      -
      -	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));
      -
      -	if ($is_repeat) {
      -		$repeated_owner = array();
      -		$repeated_owner["name"] = $repeated_item["author-name"];
      -		$repeated_owner["url"] = $repeated_item["author-link"];
      -		$repeated_owner["photo"] = $repeated_item["author-avatar"];
      -		$repeated_owner["nick"] = $repeated_owner["name"];
      -		$repeated_owner["location"] = "";
      -		$repeated_owner["about"] = "";
      -		$repeated_owner["uid"] = 0;
      -
      -		// Fetch the missing data from the global contacts
      -		$r =q("SELECT * FROM `gcontact` WHERE `nurl` = '%s'", normalise_link($repeated_item["author-link"]));
      -		if ($r) {
      -			if ($r[0]["nick"] != "")
      -				$repeated_owner["nick"] = $r[0]["nick"];
      -
      -			$repeated_owner["location"] = $r[0]["location"];
      -			$repeated_owner["about"] = $r[0]["about"];
      +		if (!isset($contact["poll"])) {
      +			$data = probe_url($url);
      +			$contact["alias"] = $data["alias"];
      +			$contact["poll"] = $data["poll"];
       		}
       
      -		$entry_repeat = ostatus_entry($doc, $repeated_item, $repeated_owner, false, true);
      -		$entry->appendChild($entry_repeat);
      -	} elseif ($repeat) {
      -		$author = ostatus_add_author($doc, $owner);
      -		$entry->appendChild($author);
      +		if (!isset($contact["alias"]))
      +			$contact["alias"] = $contact["url"];
      +
      +		return $contact;
       	}
       
      -	$mentioned = array();
      +	private function reshare_entry($doc, $item, $owner, $repeated_guid, $toplevel) {
      +
      +		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);
      +		}
      +
      +		$title = self::entry_header($doc, $entry, $owner, $toplevel);
      +
      +		$r = q("SELECT * FROM `item` WHERE `uid` = %d AND `guid` = '%s' AND NOT `private` AND `network` IN ('%s', '%s', '%s') LIMIT 1",
      +			intval($owner["uid"]), dbesc($repeated_guid),
      +			dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS));
      +		if ($r)
      +			$repeated_item = $r[0];
      +		else
      +			return false;
      +
      +		$contact = self::contact_entry($repeated_item['author-link'], $owner);
       
      -	if (($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
      -		$parent = q("SELECT `guid`, `author-link`, `owner-link` 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" => $a->get_baseurl()."/display/".$parent[0]["guid"]);
      -		xml_add_element($doc, $entry, "thr:in-reply-to", "", $attributes);
      +		$title = $owner["nick"]." repeated a notice by ".$contact["nick"];
       
      -		$attributes = array(
      -				"rel" => "related",
      -				"href" => $a->get_baseurl()."/display/".$parent[0]["guid"]);
      -		xml_add_element($doc, $entry, "link", "", $attributes);
      +		self::entry_content($doc, $entry, $item, $owner, $title, ACTIVITY_SHARE, false);
       
      -		$mentioned[$parent[0]["author-link"]] = $parent[0]["author-link"];
      -		$mentioned[$parent[0]["owner-link"]] = $parent[0]["owner-link"];
      +		$as_object = $doc->createElement("activity:object");
       
      -		$thrparent = q("SELECT `guid`, `author-link`, `owner-link` FROM `item` WHERE `uid` = %d AND `uri` = '%s'",
      +// ostatusWaEeYs
      +// ostatusogu9zg - besser
      +		xml::add_element($doc, $as_object, "activity:object-type", NAMESPACE_ACTIVITY_SCHEMA."activity");
      +
      +		self::entry_content($doc, $as_object, $repeated_item, $owner, "", "", false);
      +
      +		$author = self::add_author($doc, $contact);
      +                $as_object->appendChild($author);
      +
      +		$as_object2 = $doc->createElement("activity:object");
      +
      +		xml::add_element($doc, $as_object2, "activity:object-type", self::construct_objecttype($repeated_item));
      +
      +		$title = sprintf("New comment by %s", $contact["nick"]);
      +
      +		self::entry_content($doc, $as_object2, $repeated_item, $owner, $title);
      +
      +		$as_object->appendChild($as_object2);
      +
      +		self::entry_footer($doc, $as_object, $item, $owner, false);
      +
      +		$source = self::source_entry($doc, $contact);
      +
      +		$as_object->appendChild($source);
      +
      +		$entry->appendChild($as_object);
      +
      +		self::entry_footer($doc, $entry, $item, $owner);
      +
      +		return $entry;
      +	}
      +
      +	private function like_entry($doc, $item, $owner, $toplevel) {
      +
      +		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);
      +		}
      +
      +		$title = self::entry_header($doc, $entry, $owner, $toplevel);
      +
      +		$verb = NAMESPACE_ACTIVITY_SCHEMA."favorite";
      +		self::entry_content($doc, $entry, $item, $owner, "Favorite", $verb, false);
      +
      +		$as_object = $doc->createElement("activity:object");
      +
      +		$parent = q("SELECT * FROM `item` WHERE `id` = %d", intval($item["parent"]));
      +		$parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
      +
      +		xml::add_element($doc, $as_object, "activity:object-type", self::construct_objecttype($parent[0]));
      +
      +		self::entry_content($doc, $as_object, $parent[0], $owner, "New entry");
      +
      +		$entry->appendChild($as_object);
      +
      +		self::entry_footer($doc, $entry, $item, $owner);
      +
      +		return $entry;
      +	}
      +
      +	private function note_entry($doc, $item, $owner, $toplevel) {
      +
      +		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);
      +		}
      +
      +		$title = self::entry_header($doc, $entry, $owner, $toplevel);
      +
      +		xml::add_element($doc, $entry, "activity:object-type", ACTIVITY_OBJ_NOTE);
      +
      +		self::entry_content($doc, $entry, $item, $owner, $title);
      +
      +		self::entry_footer($doc, $entry, $item, $owner);
      +
      +		return $entry;
      +	}
      +
      +	private function entry_header($doc, &$entry, $owner, $toplevel) {
      +		if (!$toplevel) {
      +			$entry = $doc->createElement("entry");
      +			$title = sprintf("New note by %s", $owner["nick"]);
      +		} else {
      +			$entry = $doc->createElementNS(NAMESPACE_ATOM1, "entry");
      +
      +			$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 = self::add_author($doc, $owner);
      +			$entry->appendChild($author);
      +
      +			$title = sprintf("New comment by %s", $owner["nick"]);
      +		}
      +		return $title;
      +	}
      +
      +	private function entry_content($doc, $entry, $item, $owner, $title, $verb = "", $complete = true) {
      +
      +		if ($verb == "")
      +			$verb = self::construct_verb($item);
      +
      +		xml::add_element($doc, $entry, "id", $item["uri"]);
      +		xml::add_element($doc, $entry, "title", $title);
      +
      +		$body = self::format_picture_post($item['body']);
      +
      +		if ($item['title'] != "")
      +			$body = "[b]".$item['title']."[/b]\n\n".$body;
      +
      +		$body = bbcode($body, false, false, 7);
      +
      +		xml::add_element($doc, $entry, "content", $body, array("type" => "html"));
      +
      +		xml::add_element($doc, $entry, "link", "", array("rel" => "alternate", "type" => "text/html",
      +								"href" => App::get_baseurl()."/display/".$item["guid"]));
      +
      +		if ($complete)
      +			xml::add_element($doc, $entry, "status_net", "", array("notice_id" => $item["id"]));
      +
      +		xml::add_element($doc, $entry, "activity:verb", $verb);
      +
      +		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));
      +	}
      +
      +	private function entry_footer($doc, $entry, $item, $owner, $complete = true) {
      +
      +		$mentioned = array();
      +
      +		if (($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
      +			$parent = q("SELECT `guid`, `author-link`, `owner-link` 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"]);
      +			xml::add_element($doc, $entry, "thr:in-reply-to", "", $attributes);
      +
      +			$attributes = array(
      +					"rel" => "related",
      +					"href" => App::get_baseurl()."/display/".$parent[0]["guid"]);
      +			xml::add_element($doc, $entry, "link", "", $attributes);
      +
      +			$mentioned[$parent[0]["author-link"]] = $parent[0]["author-link"];
      +			$mentioned[$parent[0]["owner-link"]] = $parent[0]["owner-link"];
      +
      +			$thrparent = q("SELECT `guid`, `author-link`, `owner-link` FROM `item` WHERE `uid` = %d AND `uri` = '%s'",
      +					intval($owner["uid"]),
      +					dbesc($parent_item));
      +			if ($thrparent) {
      +				$mentioned[$thrparent[0]["author-link"]] = $thrparent[0]["author-link"];
      +				$mentioned[$thrparent[0]["owner-link"]] = $thrparent[0]["owner-link"];
      +			}
      +		}
      +
      +		xml::add_element($doc, $entry, "link", "", array("rel" => "ostatus:conversation",
      +							"href" => App::get_baseurl()."/display/".$owner["nick"]."/".$item["parent"]));
      +		xml::add_element($doc, $entry, "ostatus:conversation", App::get_baseurl()."/display/".$owner["nick"]."/".$item["parent"]);
      +
      +		$tags = item_getfeedtags($item);
      +
      +		if(count($tags))
      +			foreach($tags as $t)
      +				if ($t[0] == "@")
      +					$mentioned[$t[1]] = $t[1];
      +
      +		// Make sure that mentions are accepted (GNU Social has problems with mixing HTTP and HTTPS)
      +		$newmentions = array();
      +		foreach ($mentioned AS $mention) {
      +			$newmentions[str_replace("http://", "https://", $mention)] = str_replace("http://", "https://", $mention);
      +			$newmentions[str_replace("https://", "http://", $mention)] = str_replace("https://", "http://", $mention);
      +		}
      +		$mentioned = $newmentions;
      +
      +		foreach ($mentioned AS $mention) {
      +			$r = q("SELECT `forum`, `prv` FROM `contact` WHERE `uid` = %d AND `nurl` = '%s'",
       				intval($owner["uid"]),
      -				dbesc($parent_item));
      -		if ($thrparent) {
      -			$mentioned[$thrparent[0]["author-link"]] = $thrparent[0]["author-link"];
      -			$mentioned[$thrparent[0]["owner-link"]] = $thrparent[0]["owner-link"];
      +				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));
      +		}
      +
      +		if (!$item["private"]) {
      +			xml::add_element($doc, $entry, "link", "", array("rel" => "ostatus:attention",
      +									"href" => "http://activityschema.org/collection/public"));
      +			xml::add_element($doc, $entry, "link", "", array("rel" => "mentioned",
      +									"ostatus:object-type" => "http://activitystrea.ms/schema/1.0/collection",
      +									"href" => "http://activityschema.org/collection/public"));
      +		}
      +
      +		if(count($tags))
      +			foreach($tags as $t)
      +				if ($t[0] != "@")
      +					xml::add_element($doc, $entry, "category", "", array("term" => $t[2]));
      +
      +		self::get_attachment($doc, $entry, $item);
      +
      +		if ($complete) {
      +			$app = $item["app"];
      +			if ($app == "")
      +				$app = "web";
      +
      +			$attributes = array("local_id" => $item["id"], "source" => $app);
      +
      +			if (isset($parent["id"]))
      +				$attributes["repeat_of"] = $parent["id"];
      +
      +			if ($item["coord"] != "")
      +				xml::add_element($doc, $entry, "georss:point", $item["coord"]);
      +
      +			xml::add_element($doc, $entry, "statusnet:notice_info", "", $attributes);
       		}
       	}
       
      -	xml_add_element($doc, $entry, "link", "", array("rel" => "ostatus:conversation",
      -							"href" => $a->get_baseurl()."/display/".$owner["nick"]."/".$item["parent"]));
      -	xml_add_element($doc, $entry, "ostatus:conversation", $a->get_baseurl()."/display/".$owner["nick"]."/".$item["parent"]);
      +	public static function feed(&$a, $owner_nick, $last_update) {
       
      -	$tags = item_getfeedtags($item);
      +		$r = q("SELECT `contact`.*, `user`.`nickname`, `user`.`timezone`, `user`.`page-flags`
      +				FROM `contact` INNER JOIN `user` ON `user`.`uid` = `contact`.`uid`
      +				WHERE `contact`.`self` AND `user`.`nickname` = '%s' LIMIT 1",
      +				dbesc($owner_nick));
      +		if (!$r)
      +			return;
       
      -	if(count($tags))
      -		foreach($tags as $t)
      -			if ($t[0] == "@")
      -				$mentioned[$t[1]] = $t[1];
      +		$owner = $r[0];
       
      -	// Make sure that mentions are accepted (GNU Social has problems with mixing HTTP and HTTPS)
      -	$newmentions = array();
      -	foreach ($mentioned AS $mention) {
      -		$newmentions[str_replace("http://", "https://", $mention)] = str_replace("http://", "https://", $mention);
      -		$newmentions[str_replace("https://", "http://", $mention)] = str_replace("https://", "http://", $mention);
      -	}
      -	$mentioned = $newmentions;
      +		if(!strlen($last_update))
      +			$last_update = 'now -30 days';
       
      -	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));
      +		$check_date = datetime_convert('UTC','UTC',$last_update,'Y-m-d H:i:s');
      +
      +		$items = q("SELECT STRAIGHT_JOIN `item`.*, `item`.`id` AS `item_id` FROM `item`
      +				INNER JOIN `thread` ON `thread`.`iid` = `item`.`parent`
      +				LEFT JOIN `item` AS `thritem` ON `thritem`.`uri`=`item`.`thr-parent` AND `thritem`.`uid`=`item`.`uid`
      +				WHERE `item`.`uid` = %d AND `item`.`received` > '%s' AND NOT `item`.`private` AND NOT `item`.`deleted`
      +					AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = '' AND `item`.`deny_cid`  = '' AND `item`.`deny_gid`  = ''
      +					AND ((`item`.`wall` AND (`item`.`parent` = `item`.`id`))
      +						OR (`item`.`network` = '%s' AND ((`thread`.`network` IN ('%s', '%s')) OR (`thritem`.`network` IN ('%s', '%s')))) AND `thread`.`mention`)
      +					AND ((`item`.`owner-link` IN ('%s', '%s') AND (`item`.`parent` = `item`.`id`))
      +						OR (`item`.`author-link` IN ('%s', '%s')))
      +				ORDER BY `item`.`received` DESC
      +				LIMIT 0, 300",
      +				intval($owner["uid"]), dbesc($check_date), dbesc(NETWORK_DFRN),
      +				//dbesc(NETWORK_OSTATUS), dbesc(NETWORK_OSTATUS),
      +				//dbesc(NETWORK_OSTATUS), dbesc(NETWORK_OSTATUS),
      +				dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN),
      +				dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN),
      +				dbesc($owner["nurl"]), dbesc(str_replace("http://", "https://", $owner["nurl"])),
      +				dbesc($owner["nurl"]), dbesc(str_replace("http://", "https://", $owner["nurl"]))
      +			);
      +
      +		$doc = new DOMDocument('1.0', 'utf-8');
      +		$doc->formatOutput = true;
      +
      +		$root = self::add_header($doc, $owner);
      +
      +		foreach ($items AS $item) {
      +			$entry = self::entry($doc, $item, $owner);
      +			$root->appendChild($entry);
      +		}
      +
      +		return(trim($doc->saveXML()));
       	}
       
      -	if (!$item["private"])
      -		xml_add_element($doc, $entry, "link", "", array("rel" => "mentioned",
      -								"ostatus:object-type" => "http://activitystrea.ms/schema/1.0/collection",
      -								"href" => "http://activityschema.org/collection/public"));
      +	public static function salmon($item,$owner) {
       
      -	if(count($tags))
      -		foreach($tags as $t)
      -			if ($t[0] != "@")
      -				xml_add_element($doc, $entry, "category", "", array("term" => $t[2]));
      +		$doc = new DOMDocument('1.0', 'utf-8');
      +		$doc->formatOutput = true;
       
      -	ostatus_get_attachment($doc, $entry, $item);
      +		$entry = self::entry($doc, $item, $owner, true);
       
      -	/// @TODO
      -	/// The API call has yet to be implemented
      -	//$attributes = array("href" => $a->get_baseurl()."/api/statuses/show/".$item["id"].".atom",
      -	//		"rel" => "self", "type" => "application/atom+xml");
      -	//xml_add_element($doc, $entry, "link", "", $attributes);
      +		$doc->appendChild($entry);
       
      -	//$attributes = array("href" => $a->get_baseurl()."/api/statuses/show/".$item["id"].".atom",
      -	//		"rel" => "edit", "type" => "application/atom+xml");
      -	//xml_add_element($doc, $entry, "link", "", $attributes);
      -
      -	$app = $item["app"];
      -	if ($app == "")
      -		$app = "web";
      -
      -
      -	$attributes = array("local_id" => $item["id"], "source" => $app);
      -	if ($is_repeat)
      -		$attributes["repeat_of"] = $repeated_item["id"];
      -
      -	xml_add_element($doc, $entry, "statusnet:notice_info", "", $attributes);
      -
      -	return $entry;
      -}
      -
      -function ostatus_feed(&$a, $owner_nick, $last_update) {
      -
      -	$r = q("SELECT `contact`.*, `user`.`nickname`, `user`.`timezone`, `user`.`page-flags`
      -			FROM `contact` INNER JOIN `user` ON `user`.`uid` = `contact`.`uid`
      -			WHERE `contact`.`self` AND `user`.`nickname` = '%s' LIMIT 1",
      -			dbesc($owner_nick));
      -	if (!$r)
      -		return;
      -
      -	$owner = $r[0];
      -
      -	if(!strlen($last_update))
      -		$last_update = 'now -30 days';
      -
      -	$check_date = datetime_convert('UTC','UTC',$last_update,'Y-m-d H:i:s');
      -
      -	$items = q("SELECT STRAIGHT_JOIN `item`.*, `item`.`id` AS `item_id` FROM `item`
      -			INNER JOIN `thread` ON `thread`.`iid` = `item`.`parent`
      -			LEFT JOIN `item` AS `thritem` ON `thritem`.`uri`=`item`.`thr-parent` AND `thritem`.`uid`=`item`.`uid`
      -			WHERE `item`.`uid` = %d AND `item`.`received` > '%s' AND NOT `item`.`private` AND NOT `item`.`deleted`
      -				AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = '' AND `item`.`deny_cid`  = '' AND `item`.`deny_gid`  = ''
      -				AND ((`item`.`wall` AND (`item`.`parent` = `item`.`id`))
      -					OR (`item`.`network` = '%s' AND ((`thread`.`network` IN ('%s', '%s')) OR (`thritem`.`network` IN ('%s', '%s')))) AND `thread`.`mention`)
      -				AND ((`item`.`owner-link` IN ('%s', '%s') AND (`item`.`parent` = `item`.`id`))
      -					OR (`item`.`author-link` IN ('%s', '%s')))
      -			ORDER BY `item`.`received` DESC
      -			LIMIT 0, 300",
      -			intval($owner["uid"]), dbesc($check_date), dbesc(NETWORK_DFRN),
      -			//dbesc(NETWORK_OSTATUS), dbesc(NETWORK_OSTATUS),
      -			//dbesc(NETWORK_OSTATUS), dbesc(NETWORK_OSTATUS),
      -			dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN),
      -			dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN),
      -			dbesc($owner["nurl"]), dbesc(str_replace("http://", "https://", $owner["nurl"])),
      -			dbesc($owner["nurl"]), dbesc(str_replace("http://", "https://", $owner["nurl"]))
      -		);
      -
      -	$doc = new DOMDocument('1.0', 'utf-8');
      -	$doc->formatOutput = true;
      -
      -	$root = ostatus_add_header($doc, $owner);
      -
      -	foreach ($items AS $item) {
      -		$entry = ostatus_entry($doc, $item, $owner);
      -		$root->appendChild($entry);
      +		return(trim($doc->saveXML()));
       	}
      -
      -	return(trim($doc->saveXML()));
      -}
      -
      -function ostatus_salmon($item,$owner) {
      -
      -	$doc = new DOMDocument('1.0', 'utf-8');
      -	$doc->formatOutput = true;
      -
      -	$entry = ostatus_entry($doc, $item, $owner, true);
      -
      -	$doc->appendChild($entry);
      -
      -	return(trim($doc->saveXML()));
       }
       ?>
      diff --git a/include/xml.php b/include/xml.php
      index a454e61566..76ad88cf48 100644
      --- a/include/xml.php
      +++ b/include/xml.php
      @@ -92,5 +92,40 @@ class xml {
       				self::copy($childentry, $child, $childfield);
       		}
       	}
      +
      +	/**
      +	 * @brief Create an XML element
      +	 *
      +	 * @param object $doc XML root
      +	 * @param string $element XML element name
      +	 * @param string $value XML value
      +	 * @param array $attributes array containing the attributes
      +	 *
      +	 * @return object XML element object
      +	 */
      +	public static function create_element($doc, $element, $value = "", $attributes = array()) {
      +		$element = $doc->createElement($element, xmlify($value));
      +
      +		foreach ($attributes AS $key => $value) {
      +			$attribute = $doc->createAttribute($key);
      +			$attribute->value = xmlify($value);
      +			$element->appendChild($attribute);
      +		}
      +		return $element;
      +	}
      +
      +	/**
      +	 * @brief Create an XML and append it to the parent object
      +	 *
      +	 * @param object $doc XML root
      +	 * @param object $parent parent object
      +	 * @param string $element XML element name
      +	 * @param string $value XML value
      +	 * @param array $attributes array containing the attributes
      +	 */
      +	public static function add_element($doc, $parent, $element, $value = "", $attributes = array()) {
      +		$element = self::create_element($doc, $element, $value, $attributes);
      +		$parent->appendChild($element);
      +	}
       }
       ?>
      
      From 93af22fcd2ba58f812b4c023f4969e79d9694c06 Mon Sep 17 00:00:00 2001
      From: Michael Vogel 
      Date: Wed, 30 Mar 2016 23:26:37 +0200
      Subject: [PATCH 089/211] Tempory file is deleted
      
      ---
       include/ostatus2.php | 1778 ------------------------------------------
       1 file changed, 1778 deletions(-)
       delete mode 100644 include/ostatus2.php
      
      diff --git a/include/ostatus2.php b/include/ostatus2.php
      deleted file mode 100644
      index 53c279f254..0000000000
      --- a/include/ostatus2.php
      +++ /dev/null
      @@ -1,1778 +0,0 @@
      -createElement($element, xmlify($value));
      -
      -		foreach ($attributes AS $key => $value) {
      -			$attribute = $doc->createAttribute($key);
      -			$attribute->value = xmlify($value);
      -			$element->appendChild($attribute);
      -		}
      -		return $element;
      -	}
      -
      -	public static function add_element($doc, $parent, $element, $value = "", $attributes = array()) {
      -		$element = self::create_element($doc, $element, $value, $attributes);
      -		$parent->appendChild($element);
      -	}
      -}
      -
      -class ostatus {
      -	const OSTATUS_DEFAULT_POLL_INTERVAL = 30; // given in minutes
      -	const OSTATUS_DEFAULT_POLL_TIMEFRAME = 1440; // given in minutes
      -	const OSTATUS_DEFAULT_POLL_TIMEFRAME_MENTIONS = 14400; // given in minutes
      -
      -	private function fetchauthor($xpath, $context, $importer, &$contact, $onlyfetch) {
      -
      -		$author = array();
      -		$author["author-link"] = $xpath->evaluate('atom:author/atom:uri/text()', $context)->item(0)->nodeValue;
      -		$author["author-name"] = $xpath->evaluate('atom:author/atom:name/text()', $context)->item(0)->nodeValue;
      -
      -		// Preserve the value
      -		$authorlink = $author["author-link"];
      -
      -		$alternate = $xpath->query("atom:author/atom:link[@rel='alternate']", $context)->item(0)->attributes;
      -		if (is_object($alternate))
      -			foreach($alternate AS $attributes)
      -				if ($attributes->name == "href")
      -					$author["author-link"] = $attributes->textContent;
      -
      -		$r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `nurl` IN ('%s', '%s') AND `network` != '%s'",
      -			intval($importer["uid"]), dbesc(normalise_link($author["author-link"])),
      -			dbesc(normalise_link($authorlink)), dbesc(NETWORK_STATUSNET));
      -		if ($r) {
      -			$contact = $r[0];
      -			$author["contact-id"] = $r[0]["id"];
      -		} else
      -			$author["contact-id"] = $contact["id"];
      -
      -		$avatarlist = array();
      -		$avatars = $xpath->query("atom:author/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 (($width > 0) AND ($href != ""))
      -				$avatarlist[$width] = $href;
      -		}
      -		if (count($avatarlist) > 0) {
      -			krsort($avatarlist);
      -			$author["author-avatar"] = current($avatarlist);
      -		}
      -
      -		$displayname = $xpath->evaluate('atom:author/poco:displayName/text()', $context)->item(0)->nodeValue;
      -		if ($displayname != "")
      -			$author["author-name"] = $displayname;
      -
      -		$author["owner-name"] = $author["author-name"];
      -		$author["owner-link"] = $author["author-link"];
      -		$author["owner-avatar"] = $author["author-avatar"];
      -
      -		// Only update the contacts if it is an OStatus contact
      -		if ($r AND !$onlyfetch AND ($contact["network"] == NETWORK_OSTATUS)) {
      -			// Update contact data
      -
      -			$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);
      -
      -				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"]));
      -
      -				poco_check($contact["url"], $contact["name"], $contact["network"], $author["author-avatar"], $contact["about"], $contact["location"],
      -							"", "", "", datetime_convert(), 2, $contact["id"], $contact["uid"]);
      -			}
      -
      -			if (isset($author["author-avatar"]) AND ($author["author-avatar"] != $r[0]['avatar'])) {
      -				logger("Update profile picture for contact ".$contact["id"], LOGGER_DEBUG);
      -
      -				update_contact_avatar($author["author-avatar"], $importer["uid"], $contact["id"]);
      -			}
      -
      -			$contact["generation"] = 2;
      -			$contact["photo"] = $author["author-avatar"];
      -			update_gcontact($contact);
      -		}
      -
      -		return($author);
      -	}
      -
      -	public static function salmon_author($xml, $importer) {
      -
      -		if ($xml == "")
      -			return;
      -
      -		$doc = new DOMDocument();
      -		@$doc->loadXML($xml);
      -
      -		$xpath = new DomXPath($doc);
      -		$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');
      -
      -		foreach ($entries AS $entry) {
      -			// fetch the author
      -			$author = self::fetchauthor($xpath, $entry, $importer, $contact, true);
      -			return $author;
      -		}
      -	}
      -
      -	public static function import($xml,$importer,&$contact, &$hub) {
      -
      -		logger("Import OStatus message", LOGGER_DEBUG);
      -
      -		if ($xml == "")
      -			return;
      -
      -		//$tempfile = tempnam(get_temppath(), "import");
      -		//file_put_contents($tempfile, $xml);
      -
      -		$doc = new DOMDocument();
      -		@$doc->loadXML($xml);
      -
      -		$xpath = new DomXPath($doc);
      -		$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;
      -		if (is_object($hub_attributes))
      -			foreach($hub_attributes AS $hub_attribute)
      -				if ($hub_attribute->name == "href") {
      -					$hub = $hub_attribute->textContent;
      -					logger("Found hub ".$hub, LOGGER_DEBUG);
      -				}
      -
      -		$header = array();
      -		$header["uid"] = $importer["uid"];
      -		$header["network"] = NETWORK_OSTATUS;
      -		$header["type"] = "remote";
      -		$header["wall"] = 0;
      -		$header["origin"] = 0;
      -		$header["gravity"] = GRAVITY_PARENT;
      -
      -		// it could either be a received post or a post we fetched by ourselves
      -		// depending on that, the first node is different
      -		$first_child = $doc->firstChild->tagName;
      -
      -		if ($first_child == "feed")
      -			$entries = $xpath->query('/atom:feed/atom:entry');
      -		else
      -			$entries = $xpath->query('/atom:entry');
      -
      -		$conversation = "";
      -		$conversationlist = array();
      -		$item_id = 0;
      -
      -		// Reverse the order of the entries
      -		$entrylist = array();
      -
      -		foreach ($entries AS $entry)
      -			$entrylist[] = $entry;
      -
      -		foreach (array_reverse($entrylist) AS $entry) {
      -
      -			$mention = false;
      -
      -			// fetch the author
      -			if ($first_child == "feed")
      -				$author = self::fetchauthor($xpath, $doc->firstChild, $importer, $contact, false);
      -			else
      -				$author = self::fetchauthor($xpath, $entry, $importer, $contact, false);
      -
      -			$value = $xpath->evaluate('atom:author/poco:preferredUsername/text()', $context)->item(0)->nodeValue;
      -			if ($value != "")
      -				$nickname = $value;
      -			else
      -				$nickname = $author["author-name"];
      -
      -			$item = array_merge($header, $author);
      -
      -			// Now get the item
      -			$item["uri"] = $xpath->query('atom:id/text()', $entry)->item(0)->nodeValue;
      -
      -			$r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s'",
      -				intval($importer["uid"]), dbesc($item["uri"]));
      -			if ($r) {
      -				logger("Item with uri ".$item["uri"]." for user ".$importer["uid"]." already existed under id ".$r[0]["id"], LOGGER_DEBUG);
      -				continue;
      -			}
      -
      -			$item["body"] = add_page_info_to_body(html2bbcode($xpath->query('atom:content/text()', $entry)->item(0)->nodeValue));
      -			$item["object-type"] = $xpath->query('activity:object-type/text()', $entry)->item(0)->nodeValue;
      -
      -			if (($item["object-type"] == ACTIVITY_OBJ_BOOKMARK) OR ($item["object-type"] == ACTIVITY_OBJ_EVENT)) {
      -				$item["title"] = $xpath->query('atom:title/text()', $entry)->item(0)->nodeValue;
      -				$item["body"] = $xpath->query('atom:summary/text()', $entry)->item(0)->nodeValue;
      -			} elseif ($item["object-type"] == ACTIVITY_OBJ_QUESTION)
      -				$item["title"] = $xpath->query('atom:title/text()', $entry)->item(0)->nodeValue;
      -
      -			$item["object"] = $xml;
      -			$item["verb"] = $xpath->query('activity:verb/text()', $entry)->item(0)->nodeValue;
      -
      -			/// @TODO
      -			/// Delete a message
      -			if ($item["verb"] == "qvitter-delete-notice") {
      -				// ignore "Delete" messages (by now)
      -				logger("Ignore delete message ".print_r($item, true));
      -				continue;
      -			}
      -
      -			if ($item["verb"] == ACTIVITY_JOIN) {
      -				// ignore "Join" messages
      -				logger("Ignore join message ".print_r($item, true));
      -				continue;
      -			}
      -
      -			if ($item["verb"] == ACTIVITY_FOLLOW) {
      -				new_follower($importer, $contact, $item, $nickname);
      -				continue;
      -			}
      -
      -			if ($item["verb"] == NAMESPACE_OSTATUS."/unfollow") {
      -				lose_follower($importer, $contact, $item, $dummy);
      -				continue;
      -			}
      -
      -			if ($item["verb"] == ACTIVITY_FAVORITE) {
      -				$orig_uri = $xpath->query("activity:object/atom:id", $entry)->item(0)->nodeValue;
      -				logger("Favorite ".$orig_uri." ".print_r($item, true));
      -
      -				$item["verb"] = ACTIVITY_LIKE;
      -				$item["parent-uri"] = $orig_uri;
      -				$item["gravity"] = GRAVITY_LIKE;
      -			}
      -
      -			if ($item["verb"] == NAMESPACE_OSTATUS."/unfavorite") {
      -				// Ignore "Unfavorite" message
      -				logger("Ignore unfavorite message ".print_r($item, true));
      -				continue;
      -			}
      -
      -			// http://activitystrea.ms/schema/1.0/rsvp-yes
      -			if (!in_array($item["verb"], array(ACTIVITY_POST, ACTIVITY_LIKE, ACTIVITY_SHARE)))
      -				logger("Unhandled verb ".$item["verb"]." ".print_r($item, true));
      -
      -			$item["created"] = $xpath->query('atom:published/text()', $entry)->item(0)->nodeValue;
      -			$item["edited"] = $xpath->query('atom:updated/text()', $entry)->item(0)->nodeValue;
      -			$conversation = $xpath->query('ostatus:conversation/text()', $entry)->item(0)->nodeValue;
      -
      -			$related = "";
      -
      -			$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;
      -					if ($attributes->name == "href")
      -						$related = $attributes->textContent;
      -				}
      -			}
      -
      -			$georsspoint = $xpath->query('georss:point', $entry);
      -			if ($georsspoint)
      -				$item["coord"] = $georsspoint->item(0)->nodeValue;
      -
      -			$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]";
      -						}
      -				}
      -			}
      -
      -			$self = "";
      -			$enclosure = "";
      -
      -			$links = $xpath->query('atom:link', $entry);
      -			if ($links) {
      -				$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;
      -								if (($item["object-type"] == ACTIVITY_OBJ_QUESTION) OR
      -									($item["object-type"] == ACTIVITY_OBJ_EVENT))
      -									$item["body"] .= add_page_info($href);
      -								break;
      -							case "ostatus:conversation":
      -								$conversation = $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;
      -							case "related":
      -								if ($item["object-type"] != ACTIVITY_OBJ_BOOKMARK) {
      -									if (!isset($item["parent-uri"]))
      -										$item["parent-uri"] = $href;
      -
      -									if ($related == "")
      -										$related = $href;
      -								} else
      -									$item["body"] .= add_page_info($href);
      -								break;
      -							case "self":
      -								$self = $href;
      -								break;
      -							case "mentioned":
      -								// Notification check
      -								if ($importer["nurl"] == normalise_link($href))
      -									$mention = true;
      -								break;
      -						}
      -				}
      -			}
      -
      -			$local_id = "";
      -			$repeat_of = "";
      -
      -			$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);
      -					if ($attributes->name == "local_id")
      -						$local_id = $attributes->textContent;
      -					if ($attributes->name == "repeat_of")
      -						$repeat_of = $attributes->textContent;
      -				}
      -			}
      -
      -			// Is it a repeated post?
      -			if ($repeat_of != "") {
      -				$activityobjects = $xpath->query('activity:object', $entry)->item(0);
      -
      -				if (is_object($activityobjects)) {
      -
      -					$orig_uri = $xpath->query("activity:object/atom:id", $activityobjects)->item(0)->nodeValue;
      -					if (!isset($orig_uri))
      -						$orig_uri = $xpath->query('atom:id/text()', $activityobjects)->item(0)->nodeValue;
      -
      -					$orig_links = $xpath->query("activity:object/atom:link[@rel='alternate']", $activityobjects);
      -					if ($orig_links AND ($orig_links->length > 0))
      -						foreach($orig_links->item(0)->attributes AS $attributes)
      -							if ($attributes->name == "href")
      -								$orig_link = $attributes->textContent;
      -
      -					if (!isset($orig_link))
      -						$orig_link = $xpath->query("atom:link[@rel='alternate']", $activityobjects)->item(0)->nodeValue;
      -
      -					if (!isset($orig_link))
      -						$orig_link =  self::convert_href($orig_uri);
      -
      -					$orig_body = $xpath->query('activity:object/atom:content/text()', $activityobjects)->item(0)->nodeValue;
      -					if (!isset($orig_body))
      -						$orig_body = $xpath->query('atom:content/text()', $activityobjects)->item(0)->nodeValue;
      -
      -					$orig_created = $xpath->query('atom:published/text()', $activityobjects)->item(0)->nodeValue;
      -
      -					$orig_contact = $contact;
      -					$orig_author = self::fetchauthor($xpath, $activityobjects, $importer, $orig_contact, false);
      -
      -					$item["author-name"] = $orig_author["author-name"];
      -					$item["author-link"] = $orig_author["author-link"];
      -					$item["author-avatar"] = $orig_author["author-avatar"];
      -					$item["body"] = add_page_info_to_body(html2bbcode($orig_body));
      -					$item["created"] = $orig_created;
      -
      -					$item["uri"] = $orig_uri;
      -					$item["plink"] = $orig_link;
      -
      -					$item["verb"] = $xpath->query('activity:verb/text()', $activityobjects)->item(0)->nodeValue;
      -
      -					$item["object-type"] = $xpath->query('activity:object/activity:object-type/text()', $activityobjects)->item(0)->nodeValue;
      -					if (!isset($item["object-type"]))
      -						$item["object-type"] = $xpath->query('activity:object-type/text()', $activityobjects)->item(0)->nodeValue;
      -				}
      -			}
      -
      -			//if ($enclosure != "")
      -			//	$item["body"] .= add_page_info($enclosure);
      -
      -			if (isset($item["parent-uri"])) {
      -				$r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s'",
      -					intval($importer["uid"]), dbesc($item["parent-uri"]));
      -
      -				if (!$r AND ($related != "")) {
      -					$reply_path = str_replace("/notice/", "/api/statuses/show/", $related).".atom";
      -
      -					if ($reply_path != $related) {
      -						logger("Fetching related items for user ".$importer["uid"]." from ".$reply_path, LOGGER_DEBUG);
      -						$reply_xml = fetch_url($reply_path);
      -
      -						$reply_contact = $contact;
      -						self::import($reply_xml,$importer,$reply_contact, $reply_hub);
      -
      -						// After the import try to fetch the parent item again
      -						$r = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s'",
      -							intval($importer["uid"]), dbesc($item["parent-uri"]));
      -					}
      -				}
      -				if ($r) {
      -					$item["type"] = 'remote-comment';
      -					$item["gravity"] = GRAVITY_COMMENT;
      -				}
      -			} else
      -				$item["parent-uri"] = $item["uri"];
      -
      -			$item_id = self::completion($conversation, $importer["uid"], $item, $self);
      -
      -			if (!$item_id) {
      -				logger("Error storing item", LOGGER_DEBUG);
      -				continue;
      -			}
      -
      -			logger("Item was stored with id ".$item_id, LOGGER_DEBUG);
      -		}
      -	}
      -
      -	public static function convert_href($href) {
      -		$elements = explode(":",$href);
      -
      -		if ((count($elements) <= 2) OR ($elements[0] != "tag"))
      -			return $href;
      -
      -		$server = explode(",", $elements[1]);
      -		$conversation = explode("=", $elements[2]);
      -
      -		if ((count($elements) == 4) AND ($elements[2] == "post"))
      -			return "http://".$server[0]."/notice/".$elements[3];
      -
      -		if ((count($conversation) != 2) OR ($conversation[1] ==""))
      -			return $href;
      -
      -		if ($elements[3] == "objectType=thread")
      -			return "http://".$server[0]."/conversation/".$conversation[1];
      -		else
      -			return "http://".$server[0]."/notice/".$conversation[1];
      -
      -		return $href;
      -	}
      -
      -	public static function check_conversations($mentions = false, $override = false) {
      -		$last = get_config('system','ostatus_last_poll');
      -
      -		$poll_interval = intval(get_config('system','ostatus_poll_interval'));
      -		if(! $poll_interval)
      -			$poll_interval = OSTATUS_DEFAULT_POLL_INTERVAL;
      -
      -		// Don't poll if the interval is set negative
      -		if (($poll_interval < 0) AND !$override)
      -			return;
      -
      -		if (!$mentions) {
      -			$poll_timeframe = intval(get_config('system','ostatus_poll_timeframe'));
      -			if (!$poll_timeframe)
      -				$poll_timeframe = OSTATUS_DEFAULT_POLL_TIMEFRAME;
      -		} else {
      -			$poll_timeframe = intval(get_config('system','ostatus_poll_timeframe'));
      -			if (!$poll_timeframe)
      -				$poll_timeframe = OSTATUS_DEFAULT_POLL_TIMEFRAME_MENTIONS;
      -		}
      -
      -
      -		if ($last AND !$override) {
      -			$next = $last + ($poll_interval * 60);
      -			if ($next > time()) {
      -				logger('poll interval not reached');
      -				return;
      -			}
      -		}
      -
      -		logger('cron_start');
      -
      -		$start = date("Y-m-d H:i:s", time() - ($poll_timeframe * 60));
      -
      -		if ($mentions)
      -			$conversations = q("SELECT `term`.`oid`, `term`.`url`, `term`.`uid` FROM `term`
      -						STRAIGHT_JOIN `thread` ON `thread`.`iid` = `term`.`oid` AND `thread`.`uid` = `term`.`uid`
      -						WHERE `term`.`type` = 7 AND `term`.`term` > '%s' AND `thread`.`mention`
      -						GROUP BY `term`.`url`, `term`.`uid` ORDER BY `term`.`term` DESC", dbesc($start));
      -		else
      -			$conversations = q("SELECT `oid`, `url`, `uid` FROM `term`
      -						WHERE `type` = 7 AND `term` > '%s'
      -						GROUP BY `url`, `uid` ORDER BY `term` DESC", dbesc($start));
      -
      -		foreach ($conversations AS $conversation) {
      -			self::completion($conversation['url'], $conversation['uid']);
      -		}
      -
      -		logger('cron_end');
      -
      -		set_config('system','ostatus_last_poll', time());
      -	}
      -
      -	/**
      -	 * @brief Updates the gcontact table with actor data from the conversation
      -	 *
      -	 * @param object $actor The actor object that contains the contact data
      -	 */
      -	private function conv_fetch_actor($actor) {
      -
      -		// We set the generation to "3" since the data here is not as reliable as the data we get on other occasions
      -		$contact = array("network" => NETWORK_OSTATUS, "generation" => 3);
      -
      -		if (isset($actor->url))
      -			$contact["url"] = $actor->url;
      -
      -		if (isset($actor->displayName))
      -			$contact["name"] = $actor->displayName;
      -
      -		if (isset($actor->portablecontacts_net->displayName))
      -			$contact["name"] = $actor->portablecontacts_net->displayName;
      -
      -		if (isset($actor->portablecontacts_net->preferredUsername))
      -			$contact["nick"] = $actor->portablecontacts_net->preferredUsername;
      -
      -		if (isset($actor->id))
      -			$contact["alias"] = $actor->id;
      -
      -		if (isset($actor->summary))
      -			$contact["about"] = $actor->summary;
      -
      -		if (isset($actor->portablecontacts_net->note))
      -			$contact["about"] = $actor->portablecontacts_net->note;
      -
      -		if (isset($actor->portablecontacts_net->addresses->formatted))
      -			$contact["location"] = $actor->portablecontacts_net->addresses->formatted;
      -
      -
      -		if (isset($actor->image->url))
      -			$contact["photo"] = $actor->image->url;
      -
      -		if (isset($actor->image->width))
      -			$avatarwidth = $actor->image->width;
      -
      -		if (is_array($actor->status_net->avatarLinks))
      -			foreach ($actor->status_net->avatarLinks AS $avatar) {
      -				if ($avatarsize < $avatar->width) {
      -					$contact["photo"] = $avatar->url;
      -					$avatarsize = $avatar->width;
      -				}
      -			}
      -
      -		update_gcontact($contact);
      -	}
      -
      -	/**
      -	 * @brief Fetches the conversation url for a given item link or conversation id
      -	 *
      -	 * @param string $self The link to the posting
      -	 * @param string $conversation_id The conversation id
      -	 *
      -	 * @return string The conversation url
      -	 */
      -	private function fetch_conversation($self, $conversation_id = "") {
      -
      -		if ($conversation_id != "") {
      -			$elements = explode(":", $conversation_id);
      -
      -			if ((count($elements) <= 2) OR ($elements[0] != "tag"))
      -				return $conversation_id;
      -		}
      -
      -		if ($self == "")
      -			return "";
      -
      -		$json = str_replace(".atom", ".json", $self);
      -
      -		$raw = fetch_url($json);
      -		if ($raw == "")
      -			return "";
      -
      -		$data = json_decode($raw);
      -		if (!is_object($data))
      -			return "";
      -
      -		$conversation_id = $data->statusnet_conversation_id;
      -
      -		$pos = strpos($self, "/api/statuses/show/");
      -		$base_url = substr($self, 0, $pos);
      -
      -		return $base_url."/conversation/".$conversation_id;
      -	}
      -
      -	/**
      -	 * @brief Fetches actor details of a given actor and user id
      -	 *
      -	 * @param string $actor The actor url
      -	 * @param int $uid The user id
      -	 * @param int $contact_id The default contact-id
      -	 *
      -	 * @return array Array with actor details
      -	 */
      -	private function get_actor_details($actor, $uid, $contact_id) {
      -
      -		$details = array();
      -
      -		$contact = q("SELECT `id`, `rel`, `network` FROM `contact` WHERE `uid` = %d AND `nurl` = '%s' AND `network` != '%s'",
      -					$uid, normalise_link($actor), NETWORK_STATUSNET);
      -
      -		if (!$contact)
      -			$contact = q("SELECT `id`, `rel`, `network` FROM `contact` WHERE `uid` = %d AND `alias` IN ('%s', '%s') AND `network` != '%s'",
      -					$uid, $actor, normalise_link($actor), NETWORK_STATUSNET);
      -
      -		if ($contact) {
      -			logger("Found contact for url ".$actor, LOGGER_DEBUG);
      -			$details["contact_id"] = $contact[0]["id"];
      -			$details["network"] = $contact[0]["network"];
      -
      -			$details["not_following"] = !in_array($contact[0]["rel"], array(CONTACT_IS_SHARING, CONTACT_IS_FRIEND));
      -		} else {
      -			logger("No contact found for user ".$uid." and url ".$actor, LOGGER_DEBUG);
      -
      -			// Adding a global contact
      -			/// @TODO Use this data for the post
      -			$details["global_contact_id"] = get_contact($actor, 0);
      -
      -			logger("Global contact ".$global_contact_id." found for url ".$actor, LOGGER_DEBUG);
      -
      -			$details["contact_id"] = $contact_id;
      -			$details["network"] = NETWORK_OSTATUS;
      -
      -			$details["not_following"] = true;
      -		}
      -
      -		return $details;
      -	}
      -
      -	private function completion($conversation_url, $uid, $item = array(), $self = "") {
      -
      -
      -		$item_stored = -1;
      -
      -		$conversation_url = self::fetch_conversation($self, $conversation_url);
      -
      -		// If the thread shouldn't be completed then store the item and go away
      -		// Don't do a completion on liked content
      -		if (((intval(get_config('system','ostatus_poll_interval')) == -2) AND (count($item) > 0)) OR
      -			($item["verb"] == ACTIVITY_LIKE) OR ($conversation_url == "")) {
      -			$item_stored = item_store($item, true);
      -			return($item_stored);
      -		}
      -
      -		// Get the parent
      -		$parents = q("SELECT `id`, `parent`, `uri`, `contact-id`, `type`, `verb`, `visible` FROM `item` WHERE `id` IN
      -				(SELECT `parent` FROM `item` WHERE `id` IN
      -					(SELECT `oid` FROM `term` WHERE `uid` = %d AND `otype` = %d AND `type` = %d AND `url` = '%s'))",
      -				intval($uid), intval(TERM_OBJ_POST), intval(TERM_CONVERSATION), dbesc($conversation_url));
      -
      -		if ($parents)
      -			$parent = $parents[0];
      -		elseif (count($item) > 0) {
      -			$parent = $item;
      -			$parent["type"] = "remote";
      -			$parent["verb"] = ACTIVITY_POST;
      -			$parent["visible"] = 1;
      -		} else {
      -			// Preset the parent
      -			$r = q("SELECT `id` FROM `contact` WHERE `self` AND `uid`=%d", $uid);
      -			if (!$r)
      -				return(-2);
      -
      -			$parent = array();
      -			$parent["id"] = 0;
      -			$parent["parent"] = 0;
      -			$parent["uri"] = "";
      -			$parent["contact-id"] = $r[0]["id"];
      -			$parent["type"] = "remote";
      -			$parent["verb"] = ACTIVITY_POST;
      -			$parent["visible"] = 1;
      -		}
      -
      -		$conv = str_replace("/conversation/", "/api/statusnet/conversation/", $conversation_url).".as";
      -		$pageno = 1;
      -		$items = array();
      -
      -		logger('fetching conversation url '.$conv.' (Self: '.$self.') for user '.$uid);
      -
      -		do {
      -			$conv_arr = z_fetch_url($conv."?page=".$pageno);
      -
      -			// If it is a non-ssl site and there is an error, then try ssl or vice versa
      -			if (!$conv_arr["success"] AND (substr($conv, 0, 7) == "http://")) {
      -				$conv = str_replace("http://", "https://", $conv);
      -				$conv_as = fetch_url($conv."?page=".$pageno);
      -			} elseif (!$conv_arr["success"] AND (substr($conv, 0, 8) == "https://")) {
      -				$conv = str_replace("https://", "http://", $conv);
      -				$conv_as = fetch_url($conv."?page=".$pageno);
      -			} else
      -				$conv_as = $conv_arr["body"];
      -
      -			$conv_as = str_replace(',"statusnet:notice_info":', ',"statusnet_notice_info":', $conv_as);
      -			$conv_as = json_decode($conv_as);
      -
      -			$no_of_items = sizeof($items);
      -
      -			if (@is_array($conv_as->items))
      -				foreach ($conv_as->items AS $single_item)
      -					$items[$single_item->id] = $single_item;
      -
      -			if ($no_of_items == sizeof($items))
      -				break;
      -
      -			$pageno++;
      -
      -		} while (true);
      -
      -		logger('fetching conversation done. Found '.count($items).' items');
      -
      -		if (!sizeof($items)) {
      -			if (count($item) > 0) {
      -				$item_stored = item_store($item, true);
      -
      -				if ($item_stored) {
      -					logger("Conversation ".$conversation_url." couldn't be fetched. Item uri ".$item["uri"]." stored: ".$item_stored, LOGGER_DEBUG);
      -					self::store_conversation($item_id, $conversation_url);
      -				}
      -
      -				return($item_stored);
      -			} else
      -				return(-3);
      -		}
      -
      -		$items = array_reverse($items);
      -
      -		$r = q("SELECT `nurl` FROM `contact` WHERE `uid` = %d AND `self`", intval($uid));
      -		$importer = $r[0];
      -
      -		$new_parent = true;
      -
      -		foreach ($items as $single_conv) {
      -
      -			// Update the gcontact table
      -			self::conv_fetch_actor($single_conv->actor);
      -
      -			// Test - remove before flight
      -			//$tempfile = tempnam(get_temppath(), "conversation");
      -			//file_put_contents($tempfile, json_encode($single_conv));
      -
      -			$mention = false;
      -
      -			if (isset($single_conv->object->id))
      -				$single_conv->id = $single_conv->object->id;
      -
      -			$plink = self::convert_href($single_conv->id);
      -			if (isset($single_conv->object->url))
      -				$plink = self::convert_href($single_conv->object->url);
      -
      -			if (@!$single_conv->id)
      -				continue;
      -
      -			logger("Got id ".$single_conv->id, LOGGER_DEBUG);
      -
      -			if ($first_id == "") {
      -				$first_id = $single_conv->id;
      -
      -				// The first post of the conversation isn't our first post. There are three options:
      -				// 1. Our conversation hasn't the "real" thread starter
      -				// 2. This first post is a post inside our thread
      -				// 3. This first post is a post inside another thread
      -				if (($first_id != $parent["uri"]) AND ($parent["uri"] != "")) {
      -
      -					$new_parent = true;
      -
      -					$new_parents = q("SELECT `id`, `parent`, `uri`, `contact-id`, `type`, `verb`, `visible` FROM `item` WHERE `id` IN
      -								(SELECT `parent` FROM `item`
      -									WHERE `uid` = %d AND `uri` = '%s' AND `network` IN ('%s','%s')) LIMIT 1",
      -						intval($uid), dbesc($first_id), dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN));
      -					if ($new_parents) {
      -						if ($new_parents[0]["parent"] == $parent["parent"]) {
      -							// Option 2: This post is already present inside our thread - but not as thread starter
      -							logger("Option 2: uri present in our thread: ".$first_id, LOGGER_DEBUG);
      -							$first_id = $parent["uri"];
      -						} else {
      -							// Option 3: Not so good. We have mixed parents. We have to see how to clean this up.
      -							// For now just take the new parent.
      -							$parent = $new_parents[0];
      -							$first_id = $parent["uri"];
      -							logger("Option 3: mixed parents for uri ".$first_id, LOGGER_DEBUG);
      -						}
      -					} else {
      -						// Option 1: We hadn't got the real thread starter
      -						// We have to clean up our existing messages.
      -						$parent["id"] = 0;
      -						$parent["uri"] = $first_id;
      -						logger("Option 1: we have a new parent: ".$first_id, LOGGER_DEBUG);
      -					}
      -				} elseif ($parent["uri"] == "") {
      -					$parent["id"] = 0;
      -					$parent["uri"] = $first_id;
      -				}
      -			}
      -
      -			$parent_uri = $parent["uri"];
      -
      -			// "context" only seems to exist on older servers
      -			if (isset($single_conv->context->inReplyTo->id)) {
      -				$parent_exists = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s' AND `network` IN ('%s','%s') LIMIT 1",
      -							intval($uid), dbesc($single_conv->context->inReplyTo->id), dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN));
      -				if ($parent_exists)
      -					$parent_uri = $single_conv->context->inReplyTo->id;
      -			}
      -
      -			// This is the current way
      -			if (isset($single_conv->object->inReplyTo->id)) {
      -				$parent_exists = q("SELECT `id` FROM `item` WHERE `uid` = %d AND `uri` = '%s' AND `network` IN ('%s','%s') LIMIT 1",
      -							intval($uid), dbesc($single_conv->object->inReplyTo->id), dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN));
      -				if ($parent_exists)
      -					$parent_uri = $single_conv->object->inReplyTo->id;
      -			}
      -
      -			$message_exists = q("SELECT `id`, `parent`, `uri` FROM `item` WHERE `uid` = %d AND `uri` = '%s' AND `network` IN ('%s','%s') LIMIT 1",
      -							intval($uid), dbesc($single_conv->id),
      -							dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN));
      -			if ($message_exists) {
      -				logger("Message ".$single_conv->id." already existed on the system", LOGGER_DEBUG);
      -
      -				if ($parent["id"] != 0) {
      -					$existing_message = $message_exists[0];
      -
      -					// We improved the way we fetch OStatus messages, this shouldn't happen very often now
      -					/// @TODO We have to change the shadow copies as well. This way here is really ugly.
      -					if ($existing_message["parent"] != $parent["id"]) {
      -						logger('updating id '.$existing_message["id"].' with parent '.$existing_message["parent"].' to parent '.$parent["id"].' uri '.$parent["uri"].' thread '.$parent_uri, LOGGER_DEBUG);
      -
      -						// Update the parent id of the selected item
      -						$r = q("UPDATE `item` SET `parent` = %d, `parent-uri` = '%s' WHERE `id` = %d",
      -							intval($parent["id"]), dbesc($parent["uri"]), intval($existing_message["id"]));
      -
      -						// Update the parent uri in the thread - but only if it points to itself
      -						$r = q("UPDATE `item` SET `thr-parent` = '%s' WHERE `id` = %d AND `uri` = `thr-parent`",
      -							dbesc($parent_uri), intval($existing_message["id"]));
      -
      -						// try to change all items of the same parent
      -						$r = q("UPDATE `item` SET `parent` = %d, `parent-uri` = '%s' WHERE `parent` = %d",
      -							intval($parent["id"]), dbesc($parent["uri"]), intval($existing_message["parent"]));
      -
      -						// Update the parent uri in the thread - but only if it points to itself
      -						$r = q("UPDATE `item` SET `thr-parent` = '%s' WHERE (`parent` = %d) AND (`uri` = `thr-parent`)",
      -							dbesc($parent["uri"]), intval($existing_message["parent"]));
      -
      -						// Now delete the thread
      -						delete_thread($existing_message["parent"]);
      -					}
      -				}
      -
      -				// The item we are having on the system is the one that we wanted to store via the item array
      -				if (isset($item["uri"]) AND ($item["uri"] == $existing_message["uri"])) {
      -					$item = array();
      -					$item_stored = 0;
      -				}
      -
      -				continue;
      -			}
      -
      -			if (is_array($single_conv->to))
      -				foreach($single_conv->to AS $to)
      -					if ($importer["nurl"] == normalise_link($to->id))
      -						$mention = true;
      -
      -			$actor = $single_conv->actor->id;
      -			if (isset($single_conv->actor->url))
      -				$actor = $single_conv->actor->url;
      -
      -			$details = self::get_actor_details($actor, $uid, $parent["contact-id"]);
      -
      -			// Do we only want to import threads that were started by our contacts?
      -			if ($details["not_following"] AND $new_parent AND get_config('system','ostatus_full_threads')) {
      -				logger("Don't import uri ".$first_id." because user ".$uid." doesn't follow the person ".$actor, LOGGER_DEBUG);
      -				continue;
      -			}
      -
      -			$arr = array();
      -			$arr["network"] = $details["network"];
      -			$arr["uri"] = $single_conv->id;
      -			$arr["plink"] = $plink;
      -			$arr["uid"] = $uid;
      -			$arr["contact-id"] = $details["contact_id"];
      -			$arr["parent-uri"] = $parent_uri;
      -			$arr["created"] = $single_conv->published;
      -			$arr["edited"] = $single_conv->published;
      -			$arr["owner-name"] = $single_conv->actor->displayName;
      -			if ($arr["owner-name"] == '')
      -				$arr["owner-name"] = $single_conv->actor->contact->displayName;
      -			if ($arr["owner-name"] == '')
      -				$arr["owner-name"] = $single_conv->actor->portablecontacts_net->displayName;
      -
      -			$arr["owner-link"] = $actor;
      -			$arr["owner-avatar"] = $single_conv->actor->image->url;
      -			$arr["author-name"] = $arr["owner-name"];
      -			$arr["author-link"] = $actor;
      -			$arr["author-avatar"] = $single_conv->actor->image->url;
      -			$arr["body"] = add_page_info_to_body(html2bbcode($single_conv->content));
      -
      -			if (isset($single_conv->status_net->notice_info->source))
      -				$arr["app"] = strip_tags($single_conv->status_net->notice_info->source);
      -			elseif (isset($single_conv->statusnet->notice_info->source))
      -				$arr["app"] = strip_tags($single_conv->statusnet->notice_info->source);
      -			elseif (isset($single_conv->statusnet_notice_info->source))
      -				$arr["app"] = strip_tags($single_conv->statusnet_notice_info->source);
      -			elseif (isset($single_conv->provider->displayName))
      -				$arr["app"] = $single_conv->provider->displayName;
      -			else
      -				$arr["app"] = "OStatus";
      -
      -
      -			$arr["object"] = json_encode($single_conv);
      -			$arr["verb"] = $parent["verb"];
      -			$arr["visible"] = $parent["visible"];
      -			$arr["location"] = $single_conv->location->displayName;
      -			$arr["coord"] = trim($single_conv->location->lat." ".$single_conv->location->lon);
      -
      -			// Is it a reshared item?
      -			if (isset($single_conv->verb) AND ($single_conv->verb == "share") AND isset($single_conv->object)) {
      -				if (is_array($single_conv->object))
      -					$single_conv->object = $single_conv->object[0];
      -
      -				logger("Found reshared item ".$single_conv->object->id);
      -
      -				// $single_conv->object->context->conversation;
      -
      -				if (isset($single_conv->object->object->id))
      -					$arr["uri"] = $single_conv->object->object->id;
      -				else
      -					$arr["uri"] = $single_conv->object->id;
      -
      -				if (isset($single_conv->object->object->url))
      -					$plink = self::convert_href($single_conv->object->object->url);
      -				else
      -					$plink = self::convert_href($single_conv->object->url);
      -
      -				if (isset($single_conv->object->object->content))
      -					$arr["body"] = add_page_info_to_body(html2bbcode($single_conv->object->object->content));
      -				else
      -					$arr["body"] = add_page_info_to_body(html2bbcode($single_conv->object->content));
      -
      -				$arr["plink"] = $plink;
      -
      -				$arr["created"] = $single_conv->object->published;
      -				$arr["edited"] = $single_conv->object->published;
      -
      -				$arr["author-name"] = $single_conv->object->actor->displayName;
      -				if ($arr["owner-name"] == '')
      -					$arr["author-name"] = $single_conv->object->actor->contact->displayName;
      -
      -				$arr["author-link"] = $single_conv->object->actor->url;
      -				$arr["author-avatar"] = $single_conv->object->actor->image->url;
      -
      -				$arr["app"] = $single_conv->object->provider->displayName."#";
      -				//$arr["verb"] = $single_conv->object->verb;
      -
      -				$arr["location"] = $single_conv->object->location->displayName;
      -				$arr["coord"] = trim($single_conv->object->location->lat." ".$single_conv->object->location->lon);
      -			}
      -
      -			if ($arr["location"] == "")
      -				unset($arr["location"]);
      -
      -			if ($arr["coord"] == "")
      -				unset($arr["coord"]);
      -
      -			// Copy fields from given item array
      -			if (isset($item["uri"]) AND (($item["uri"] == $arr["uri"]) OR ($item["uri"] ==  $single_conv->id))) {
      -				$copy_fields = array("owner-name", "owner-link", "owner-avatar", "author-name", "author-link", "author-avatar",
      -							"gravity", "body", "object-type", "object", "verb", "created", "edited", "coord", "tag",
      -							"title", "attach", "app", "type", "location", "contact-id", "uri");
      -				foreach ($copy_fields AS $field)
      -					if (isset($item[$field]))
      -						$arr[$field] = $item[$field];
      -
      -			}
      -
      -			$newitem = item_store($arr);
      -			if (!$newitem) {
      -				logger("Item wasn't stored ".print_r($arr, true), LOGGER_DEBUG);
      -				continue;
      -			}
      -
      -			if (isset($item["uri"]) AND ($item["uri"] == $arr["uri"])) {
      -				$item = array();
      -				$item_stored = $newitem;
      -			}
      -
      -			logger('Stored new item '.$plink.' for parent '.$arr["parent-uri"].' under id '.$newitem, LOGGER_DEBUG);
      -
      -			// Add the conversation entry (but don't fetch the whole conversation)
      -			self::store_conversation($newitem, $conversation_url);
      -
      -			// 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) {
      -				logger('setting new parent to id '.$newitem);
      -				$new_parents = q("SELECT `id`, `uri`, `contact-id`, `type`, `verb`, `visible` FROM `item` WHERE `uid` = %d AND `id` = %d LIMIT 1",
      -					intval($uid), intval($newitem));
      -				if ($new_parents)
      -					$parent = $new_parents[0];
      -			}
      -		}
      -
      -		if (($item_stored < 0) AND (count($item) > 0)) {
      -
      -			if (get_config('system','ostatus_full_threads')) {
      -				$details = self::get_actor_details($item["owner-link"], $uid, $item["contact-id"]);
      -				if ($details["not_following"]) {
      -					logger("Don't import uri ".$item["uri"]." because user ".$uid." doesn't follow the person ".$item["owner-link"], LOGGER_DEBUG);
      -					return false;
      -				}
      -			}
      -
      -			$item_stored = item_store($item, true);
      -			if ($item_stored) {
      -				logger("Uri ".$item["uri"]." wasn't found in conversation ".$conversation_url, LOGGER_DEBUG);
      -				self::store_conversation($item_stored, $conversation_url);
      -			}
      -		}
      -
      -		return($item_stored);
      -	}
      -
      -	private function store_conversation($itemid, $conversation_url) {
      -
      -		$conversation_url = self::convert_href($conversation_url);
      -
      -		$messages = q("SELECT `uid`, `parent`, `created`, `received`, `guid` FROM `item` WHERE `id` = %d LIMIT 1", intval($itemid));
      -		if (!$messages)
      -			return;
      -		$message = $messages[0];
      -
      -		// Store conversation url if not done before
      -		$conversation = q("SELECT `url` FROM `term` WHERE `uid` = %d AND `oid` = %d AND `otype` = %d AND `type` = %d",
      -			intval($message["uid"]), intval($itemid), intval(TERM_OBJ_POST), intval(TERM_CONVERSATION));
      -
      -		if (!$conversation) {
      -			$r = q("INSERT INTO `term` (`uid`, `oid`, `otype`, `type`, `term`, `url`, `created`, `received`, `guid`) VALUES (%d, %d, %d, %d, '%s', '%s', '%s', '%s', '%s')",
      -				intval($message["uid"]), intval($itemid), intval(TERM_OBJ_POST), intval(TERM_CONVERSATION),
      -				dbesc($message["created"]), dbesc($conversation_url), dbesc($message["created"]), dbesc($message["received"]), dbesc($message["guid"]));
      -			logger('Storing conversation url '.$conversation_url.' for id '.$itemid);
      -		}
      -	}
      -
      -	private function get_reshared_guid($item) {
      -		$body = trim($item["body"]);
      -
      -		// Skip if it isn't a pure repeated messages
      -		// Does it start with a share?
      -		if (strpos($body, "[share") > 0)
      -			return("");
      -
      -		// Does it end with a share?
      -		if (strlen($body) > (strrpos($body, "[/share]") + 8))
      -			return("");
      -
      -		$attributes = preg_replace("/\[share(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism","$1",$body);
      -		// Skip if there is no shared message in there
      -		if ($body == $attributes)
      -			return(false);
      -
      -		$guid = "";
      -		preg_match("/guid='(.*?)'/ism", $attributes, $matches);
      -		if ($matches[1] != "")
      -			$guid = $matches[1];
      -
      -		preg_match('/guid="(.*?)"/ism', $attributes, $matches);
      -		if ($matches[1] != "")
      -			$guid = $matches[1];
      -
      -		return $guid;
      -	}
      -
      -	private function format_picture_post($body) {
      -		$siteinfo = get_attached_data($body);
      -
      -		if (($siteinfo["type"] == "photo")) {
      -			if (isset($siteinfo["preview"]))
      -				$preview = $siteinfo["preview"];
      -			else
      -				$preview = $siteinfo["image"];
      -
      -			// Is it a remote picture? Then make a smaller preview here
      -			$preview = proxy_url($preview, false, PROXY_SIZE_SMALL);
      -
      -			// Is it a local picture? Then make it smaller here
      -			$preview = str_replace(array("-0.jpg", "-0.png"), array("-2.jpg", "-2.png"), $preview);
      -			$preview = str_replace(array("-1.jpg", "-1.png"), array("-2.jpg", "-2.png"), $preview);
      -
      -			if (isset($siteinfo["url"]))
      -				$url = $siteinfo["url"];
      -			else
      -				$url = $siteinfo["image"];
      -
      -			$body = trim($siteinfo["text"])." [url]".$url."[/url]\n[img]".$preview."[/img]";
      -		}
      -
      -		return $body;
      -	}
      -
      -	private function add_header($doc, $owner) {
      -
      -		$a = get_app();
      -
      -		$root = $doc->createElementNS(NAMESPACE_ATOM1, 'feed');
      -		$doc->appendChild($root);
      -
      -		$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);
      -		xml2::add_element($doc, $root, "generator", FRIENDICA_PLATFORM, $attributes);
      -		xml2::add_element($doc, $root, "id", App::get_baseurl()."/profile/".$owner["nick"]);
      -		xml2::add_element($doc, $root, "title", sprintf("%s timeline", $owner["name"]));
      -		xml2::add_element($doc, $root, "subtitle", sprintf("Updates from %s on %s", $owner["name"], $a->config["sitename"]));
      -		xml2::add_element($doc, $root, "logo", $owner["photo"]);
      -		xml2::add_element($doc, $root, "updated", datetime_convert("UTC", "UTC", "now", ATOM_TIME));
      -
      -		$author = self::add_author($doc, $owner);
      -		$root->appendChild($author);
      -
      -		$attributes = array("href" => $owner["url"], "rel" => "alternate", "type" => "text/html");
      -		xml2::add_element($doc, $root, "link", "", $attributes);
      -
      -		/// @TODO We have to find out what this is
      -		/// $attributes = array("href" => App::get_baseurl()."/sup",
      -		///		"rel" => "http://api.friendfeed.com/2008/03#sup",
      -		///		"type" => "application/json");
      -		/// xml2::add_element($doc, $root, "link", "", $attributes);
      -
      -		self::hublinks($doc, $root);
      -
      -		$attributes = array("href" => App::get_baseurl()."/salmon/".$owner["nick"], "rel" => "salmon");
      -		xml2::add_element($doc, $root, "link", "", $attributes);
      -
      -		$attributes = array("href" => App::get_baseurl()."/salmon/".$owner["nick"], "rel" => "http://salmon-protocol.org/ns/salmon-replies");
      -		xml2::add_element($doc, $root, "link", "", $attributes);
      -
      -		$attributes = array("href" => App::get_baseurl()."/salmon/".$owner["nick"], "rel" => "http://salmon-protocol.org/ns/salmon-mention");
      -		xml2::add_element($doc, $root, "link", "", $attributes);
      -
      -		$attributes = array("href" => App::get_baseurl()."/api/statuses/user_timeline/".$owner["nick"].".atom",
      -				"rel" => "self", "type" => "application/atom+xml");
      -		xml2::add_element($doc, $root, "link", "", $attributes);
      -
      -		return $root;
      -	}
      -
      -	public static function hublinks($doc, $root) {
      -		$hub = get_config('system','huburl');
      -
      -		$hubxml = '';
      -		if(strlen($hub)) {
      -			$hubs = explode(',', $hub);
      -			if(count($hubs)) {
      -				foreach($hubs as $h) {
      -					$h = trim($h);
      -					if(! strlen($h))
      -						continue;
      -					if ($h === '[internal]')
      -						$h = App::get_baseurl() . '/pubsubhubbub';
      -					xml2::add_element($doc, $root, "link", "", array("href" => $h, "rel" => "hub"));
      -				}
      -			}
      -		}
      -	}
      -
      -	private function get_attachment($doc, $root, $item) {
      -		$o = "";
      -		$siteinfo = get_attached_data($item["body"]);
      -
      -		switch($siteinfo["type"]) {
      -			case 'link':
      -				$attributes = array("rel" => "enclosure",
      -						"href" => $siteinfo["url"],
      -						"type" => "text/html; charset=UTF-8",
      -						"length" => "",
      -						"title" => $siteinfo["title"]);
      -				xml2::add_element($doc, $root, "link", "", $attributes);
      -				break;
      -			case 'photo':
      -				$imgdata = get_photo_info($siteinfo["image"]);
      -				$attributes = array("rel" => "enclosure",
      -						"href" => $siteinfo["image"],
      -						"type" => $imgdata["mime"],
      -						"length" => intval($imgdata["size"]));
      -				xml2::add_element($doc, $root, "link", "", $attributes);
      -				break;
      -			case 'video':
      -				$attributes = array("rel" => "enclosure",
      -						"href" => $siteinfo["url"],
      -						"type" => "text/html; charset=UTF-8",
      -						"length" => "",
      -						"title" => $siteinfo["title"]);
      -				xml2::add_element($doc, $root, "link", "", $attributes);
      -				break;
      -			default:
      -				break;
      -		}
      -
      -		if (($siteinfo["type"] != "photo") AND isset($siteinfo["image"])) {
      -			$photodata = get_photo_info($siteinfo["image"]);
      -
      -			$attributes = array("rel" => "preview", "href" => $siteinfo["image"], "media:width" => $photodata[0], "media:height" => $photodata[1]);
      -			xml2::add_element($doc, $root, "link", "", $attributes);
      -		}
      -
      -
      -		$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]);
      -
      -					xml2::add_element($doc, $root, "link", "", $attributes);
      -				}
      -			}
      -		}
      -	}
      -
      -	private function add_author($doc, $owner) {
      -
      -		$r = q("SELECT `homepage` FROM `profile` WHERE `uid` = %d AND `is-default` LIMIT 1", intval($owner["uid"]));
      -		if ($r)
      -			$profile = $r[0];
      -
      -		$author = $doc->createElement("author");
      -		xml2::add_element($doc, $author, "activity:object-type", ACTIVITY_OBJ_PERSON);
      -		xml2::add_element($doc, $author, "uri", $owner["url"]);
      -		xml2::add_element($doc, $author, "name", $owner["name"]);
      -		xml2::add_element($doc, $author, "summary", bbcode($owner["about"], false, false, 7));
      -
      -		$attributes = array("rel" => "alternate", "type" => "text/html", "href" => $owner["url"]);
      -		xml2::add_element($doc, $author, "link", "", $attributes);
      -
      -		$attributes = array(
      -				"rel" => "avatar",
      -				"type" => "image/jpeg", // To-Do?
      -				"media:width" => 175,
      -				"media:height" => 175,
      -				"href" => $owner["photo"]);
      -		xml2::add_element($doc, $author, "link", "", $attributes);
      -
      -		if (isset($owner["thumb"])) {
      -			$attributes = array(
      -					"rel" => "avatar",
      -					"type" => "image/jpeg", // To-Do?
      -					"media:width" => 80,
      -					"media:height" => 80,
      -					"href" => $owner["thumb"]);
      -			xml2::add_element($doc, $author, "link", "", $attributes);
      -		}
      -
      -		xml2::add_element($doc, $author, "poco:preferredUsername", $owner["nick"]);
      -		xml2::add_element($doc, $author, "poco:displayName", $owner["name"]);
      -		xml2::add_element($doc, $author, "poco:note", bbcode($owner["about"], false, false, 7));
      -
      -		if (trim($owner["location"]) != "") {
      -			$element = $doc->createElement("poco:address");
      -			xml2::add_element($doc, $element, "poco:formatted", $owner["location"]);
      -			$author->appendChild($element);
      -		}
      -
      -		if (trim($profile["homepage"]) != "") {
      -			$urls = $doc->createElement("poco:urls");
      -			xml2::add_element($doc, $urls, "poco:type", "homepage");
      -			xml2::add_element($doc, $urls, "poco:value", $profile["homepage"]);
      -			xml2::add_element($doc, $urls, "poco:primary", "true");
      -			$author->appendChild($urls);
      -		}
      -
      -		if (count($profile)) {
      -			xml2::add_element($doc, $author, "followers", "", array("url" => App::get_baseurl()."/viewcontacts/".$owner["nick"]));
      -			xml2::add_element($doc, $author, "statusnet:profile_info", "", array("local_id" => $owner["uid"]));
      -		}
      -
      -		return $author;
      -	}
      -
      -	/**
      -	 * @TODO Picture attachments should look like this:
      -	 *	https://status.pirati.ca/attachment/572819
      -	 *
      -	*/
      -
      -	function construct_verb($item) {
      -		if ($item['verb'])
      -			return $item['verb'];
      -		return ACTIVITY_POST;
      -	}
      -
      -	function construct_objecttype($item) {
      -		if (in_array($item['object-type'], array(ACTIVITY_OBJ_NOTE, ACTIVITY_OBJ_COMMENT)))
      -			return $item['object-type'];
      -		return ACTIVITY_OBJ_NOTE;
      -	}
      -
      -	private function entry($doc, $item, $owner, $toplevel = false) {
      -		$repeated_guid = self::get_reshared_guid($item);
      -		if ($repeated_guid != "")
      -			$xml = self::reshare_entry($doc, $item, $owner, $repeated_guid, $toplevel);
      -
      -		if ($xml)
      -			return $xml;
      -
      -		if ($item["verb"] == ACTIVITY_LIKE)
      -			return self::like_entry($doc, $item, $owner, $toplevel);
      -		else
      -			return self::note_entry($doc, $item, $owner, $toplevel);
      -	}
      -
      -	private function source_entry($doc, $contact) {
      -		$source = $doc->createElement("source");
      -		xml2::add_element($doc, $source, "id", $contact["poll"]);
      -		xml2::add_element($doc, $source, "title", $contact["name"]);
      -		xml2::add_element($doc, $source, "link", "", array("rel" => "alternate",
      -								"type" => "text/html",
      -								"href" => $contact["alias"]));
      -		xml2::add_element($doc, $source, "link", "", array("rel" => "self",
      -								"type" => "application/atom+xml",
      -								"href" => $contact["poll"]));
      -		xml2::add_element($doc, $source, "icon", $contact["photo"]);
      -		xml2::add_element($doc, $source, "updated", datetime_convert("UTC","UTC",$contact["success_update"]."+00:00",ATOM_TIME));
      -
      -		return $source;
      -	}
      -
      -	private function contact_entry($url, $owner) {
      -
      -		$r = q("SELECT * FROM `contact` WHERE `nurl` = '%s' AND `uid` IN (0, %d) ORDER BY `uid` DESC LIMIT 1",
      -			dbesc(normalise_link($url)), intval($owner["uid"]));
      -		if ($r) {
      -			$contact = $r[0];
      -			$contact["uid"] = -1;
      -		}
      -
      -		if (!$r) {
      -			$r = q("SELECT * FROM `gcontact` WHERE `nurl` = '%s' LIMIT 1",
      -				dbesc(normalise_link($url)));
      -			if ($r) {
      -				$contact = $r[0];
      -				$contact["uid"] = -1;
      -				$contact["success_update"] = $contact["updated"];
      -			}
      -		}
      -
      -		if (!$r)
      -			$contact = owner;
      -
      -		if (!isset($contact["poll"])) {
      -			$data = probe_url($url);
      -			$contact["alias"] = $data["alias"];
      -			$contact["poll"] = $data["poll"];
      -		}
      -
      -		if (!isset($contact["alias"]))
      -			$contact["alias"] = $contact["url"];
      -
      -		return $contact;
      -	}
      -
      -	private function reshare_entry($doc, $item, $owner, $repeated_guid, $toplevel) {
      -
      -		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);
      -		}
      -
      -		$title = self::entry_header($doc, $entry, $owner, $toplevel);
      -
      -		$r = q("SELECT * FROM `item` WHERE `uid` = %d AND `guid` = '%s' AND NOT `private` AND `network` IN ('%s', '%s', '%s') LIMIT 1",
      -			intval($owner["uid"]), dbesc($repeated_guid),
      -			dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS));
      -		if ($r)
      -			$repeated_item = $r[0];
      -		else
      -			return false;
      -
      -		$contact = self::contact_entry($repeated_item['author-link'], $owner);
      -
      -		$parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
      -
      -		$title = $owner["nick"]." repeated a notice by ".$contact["nick"];
      -
      -		self::entry_content($doc, $entry, $item, $owner, $title, ACTIVITY_SHARE, false);
      -
      -		$as_object = $doc->createElement("activity:object");
      -
      -// ostatusWaEeYs
      -// ostatusogu9zg - besser
      -		xml2::add_element($doc, $as_object, "activity:object-type", NAMESPACE_ACTIVITY_SCHEMA."activity");
      -
      -		self::entry_content($doc, $as_object, $repeated_item, $owner, "", "", false);
      -
      -		$author = self::add_author($doc, $contact);
      -                $as_object->appendChild($author);
      -
      -		$as_object2 = $doc->createElement("activity:object");
      -
      -		xml2::add_element($doc, $as_object2, "activity:object-type", self::construct_objecttype($repeated_item));
      -
      -		$title = sprintf("New comment by %s", $contact["nick"]);
      -
      -		self::entry_content($doc, $as_object2, $repeated_item, $owner, $title);
      -
      -		$as_object->appendChild($as_object2);
      -
      -		self::entry_footer($doc, $as_object, $item, $owner, false);
      -
      -		$source = self::source_entry($doc, $contact);
      -
      -		$as_object->appendChild($source);
      -
      -		$entry->appendChild($as_object);
      -
      -		self::entry_footer($doc, $entry, $item, $owner);
      -
      -		return $entry;
      -	}
      -
      -	private function like_entry($doc, $item, $owner, $toplevel) {
      -
      -		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);
      -		}
      -
      -		$title = self::entry_header($doc, $entry, $owner, $toplevel);
      -
      -		$verb = NAMESPACE_ACTIVITY_SCHEMA."favorite";
      -		self::entry_content($doc, $entry, $item, $owner, "Favorite", $verb, false);
      -
      -		$as_object = $doc->createElement("activity:object");
      -
      -		$parent = q("SELECT * FROM `item` WHERE `id` = %d", intval($item["parent"]));
      -		$parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
      -
      -		xml2::add_element($doc, $as_object, "activity:object-type", self::construct_objecttype($parent[0]));
      -
      -		self::entry_content($doc, $as_object, $parent[0], $owner, "New entry");
      -
      -		$entry->appendChild($as_object);
      -
      -		self::entry_footer($doc, $entry, $item, $owner);
      -
      -		return $entry;
      -	}
      -
      -	private function note_entry($doc, $item, $owner, $toplevel) {
      -
      -		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);
      -		}
      -
      -		$title = self::entry_header($doc, $entry, $owner, $toplevel);
      -
      -		xml2::add_element($doc, $entry, "activity:object-type", ACTIVITY_OBJ_NOTE);
      -
      -		self::entry_content($doc, $entry, $item, $owner, $title);
      -
      -		self::entry_footer($doc, $entry, $item, $owner);
      -
      -		return $entry;
      -	}
      -
      -	private function entry_header($doc, &$entry, $owner, $toplevel) {
      -		if (!$toplevel) {
      -			$entry = $doc->createElement("entry");
      -			$title = sprintf("New note by %s", $owner["nick"]);
      -		} else {
      -			$entry = $doc->createElementNS(NAMESPACE_ATOM1, "entry");
      -
      -			$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 = self::add_author($doc, $owner);
      -			$entry->appendChild($author);
      -
      -			$title = sprintf("New comment by %s", $owner["nick"]);
      -		}
      -		return $title;
      -	}
      -
      -	private function entry_content($doc, $entry, $item, $owner, $title, $verb = "", $complete = true) {
      -
      -		if ($verb == "")
      -			$verb = self::construct_verb($item);
      -
      -		xml2::add_element($doc, $entry, "id", $item["uri"]);
      -		xml2::add_element($doc, $entry, "title", $title);
      -
      -		$body = self::format_picture_post($item['body']);
      -
      -		if ($item['title'] != "")
      -			$body = "[b]".$item['title']."[/b]\n\n".$body;
      -
      -		$body = bbcode($body, false, false, 7);
      -
      -		xml2::add_element($doc, $entry, "content", $body, array("type" => "html"));
      -
      -		xml2::add_element($doc, $entry, "link", "", array("rel" => "alternate", "type" => "text/html",
      -								"href" => App::get_baseurl()."/display/".$item["guid"]));
      -
      -		if ($complete)
      -			xml2::add_element($doc, $entry, "status_net", "", array("notice_id" => $item["id"]));
      -
      -		xml2::add_element($doc, $entry, "activity:verb", $verb);
      -
      -		xml2::add_element($doc, $entry, "published", datetime_convert("UTC","UTC",$item["created"]."+00:00",ATOM_TIME));
      -		xml2::add_element($doc, $entry, "updated", datetime_convert("UTC","UTC",$item["edited"]."+00:00",ATOM_TIME));
      -	}
      -
      -	private function entry_footer($doc, $entry, $item, $owner, $complete = true) {
      -
      -		$mentioned = array();
      -
      -		if (($item['parent'] != $item['id']) || ($item['parent-uri'] !== $item['uri']) || (($item['thr-parent'] !== '') && ($item['thr-parent'] !== $item['uri']))) {
      -			$parent = q("SELECT `guid`, `author-link`, `owner-link` 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"]);
      -			xml2::add_element($doc, $entry, "thr:in-reply-to", "", $attributes);
      -
      -			$attributes = array(
      -					"rel" => "related",
      -					"href" => App::get_baseurl()."/display/".$parent[0]["guid"]);
      -			xml2::add_element($doc, $entry, "link", "", $attributes);
      -
      -			$mentioned[$parent[0]["author-link"]] = $parent[0]["author-link"];
      -			$mentioned[$parent[0]["owner-link"]] = $parent[0]["owner-link"];
      -
      -			$thrparent = q("SELECT `guid`, `author-link`, `owner-link` FROM `item` WHERE `uid` = %d AND `uri` = '%s'",
      -					intval($owner["uid"]),
      -					dbesc($parent_item));
      -			if ($thrparent) {
      -				$mentioned[$thrparent[0]["author-link"]] = $thrparent[0]["author-link"];
      -				$mentioned[$thrparent[0]["owner-link"]] = $thrparent[0]["owner-link"];
      -			}
      -		}
      -
      -		xml2::add_element($doc, $entry, "link", "", array("rel" => "ostatus:conversation",
      -							"href" => App::get_baseurl()."/display/".$owner["nick"]."/".$item["parent"]));
      -		xml2::add_element($doc, $entry, "ostatus:conversation", App::get_baseurl()."/display/".$owner["nick"]."/".$item["parent"]);
      -
      -		$tags = item_getfeedtags($item);
      -
      -		if(count($tags))
      -			foreach($tags as $t)
      -				if ($t[0] == "@")
      -					$mentioned[$t[1]] = $t[1];
      -
      -		// Make sure that mentions are accepted (GNU Social has problems with mixing HTTP and HTTPS)
      -		$newmentions = array();
      -		foreach ($mentioned AS $mention) {
      -			$newmentions[str_replace("http://", "https://", $mention)] = str_replace("http://", "https://", $mention);
      -			$newmentions[str_replace("https://", "http://", $mention)] = str_replace("https://", "http://", $mention);
      -		}
      -		$mentioned = $newmentions;
      -
      -		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"])
      -				xml2::add_element($doc, $entry, "link", "", array("rel" => "mentioned",
      -											"ostatus:object-type" => ACTIVITY_OBJ_GROUP,
      -											"href" => $mention));
      -			else
      -				xml2::add_element($doc, $entry, "link", "", array("rel" => "mentioned",
      -											"ostatus:object-type" => ACTIVITY_OBJ_PERSON,
      -											"href" => $mention));
      -		}
      -
      -		if (!$item["private"]) {
      -			xml2::add_element($doc, $entry, "link", "", array("rel" => "ostatus:attention",
      -									"href" => "http://activityschema.org/collection/public"));
      -			xml2::add_element($doc, $entry, "link", "", array("rel" => "mentioned",
      -									"ostatus:object-type" => "http://activitystrea.ms/schema/1.0/collection",
      -									"href" => "http://activityschema.org/collection/public"));
      -		}
      -
      -		if(count($tags))
      -			foreach($tags as $t)
      -				if ($t[0] != "@")
      -					xml2::add_element($doc, $entry, "category", "", array("term" => $t[2]));
      -
      -		self::get_attachment($doc, $entry, $item);
      -
      -		if ($complete) {
      -			$app = $item["app"];
      -			if ($app == "")
      -				$app = "web";
      -
      -			$attributes = array("local_id" => $item["id"], "source" => $app);
      -
      -			if (isset($parent["id"]))
      -				$attributes["repeat_of"] = $parent["id"];
      -
      -			if ($item["coord"] != "")
      -				xml2::add_element($doc, $entry, "georss:point", $item["coord"]);
      -
      -			xml2::add_element($doc, $entry, "statusnet:notice_info", "", $attributes);
      -		}
      -	}
      -
      -	public static function feed(&$a, $owner_nick, $last_update) {
      -
      -		$r = q("SELECT `contact`.*, `user`.`nickname`, `user`.`timezone`, `user`.`page-flags`
      -				FROM `contact` INNER JOIN `user` ON `user`.`uid` = `contact`.`uid`
      -				WHERE `contact`.`self` AND `user`.`nickname` = '%s' LIMIT 1",
      -				dbesc($owner_nick));
      -		if (!$r)
      -			return;
      -
      -		$owner = $r[0];
      -
      -		if(!strlen($last_update))
      -			$last_update = 'now -30 days';
      -
      -		$check_date = datetime_convert('UTC','UTC',$last_update,'Y-m-d H:i:s');
      -
      -		$items = q("SELECT STRAIGHT_JOIN `item`.*, `item`.`id` AS `item_id` FROM `item`
      -				INNER JOIN `thread` ON `thread`.`iid` = `item`.`parent`
      -				LEFT JOIN `item` AS `thritem` ON `thritem`.`uri`=`item`.`thr-parent` AND `thritem`.`uid`=`item`.`uid`
      -				WHERE `item`.`uid` = %d AND `item`.`received` > '%s' AND NOT `item`.`private` AND NOT `item`.`deleted`
      -					AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = '' AND `item`.`deny_cid`  = '' AND `item`.`deny_gid`  = ''
      -					AND ((`item`.`wall` AND (`item`.`parent` = `item`.`id`))
      -						OR (`item`.`network` = '%s' AND ((`thread`.`network` IN ('%s', '%s')) OR (`thritem`.`network` IN ('%s', '%s')))) AND `thread`.`mention`)
      -					AND ((`item`.`owner-link` IN ('%s', '%s') AND (`item`.`parent` = `item`.`id`))
      -						OR (`item`.`author-link` IN ('%s', '%s')))
      -				ORDER BY `item`.`received` DESC
      -				LIMIT 0, 300",
      -				intval($owner["uid"]), dbesc($check_date), dbesc(NETWORK_DFRN),
      -				//dbesc(NETWORK_OSTATUS), dbesc(NETWORK_OSTATUS),
      -				//dbesc(NETWORK_OSTATUS), dbesc(NETWORK_OSTATUS),
      -				dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN),
      -				dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DFRN),
      -				dbesc($owner["nurl"]), dbesc(str_replace("http://", "https://", $owner["nurl"])),
      -				dbesc($owner["nurl"]), dbesc(str_replace("http://", "https://", $owner["nurl"]))
      -			);
      -
      -		$doc = new DOMDocument('1.0', 'utf-8');
      -		$doc->formatOutput = true;
      -
      -		$root = self::add_header($doc, $owner);
      -
      -		foreach ($items AS $item) {
      -			$entry = self::entry($doc, $item, $owner);
      -			$root->appendChild($entry);
      -		}
      -
      -		return(trim($doc->saveXML()));
      -	}
      -
      -	public static function salmon($item,$owner) {
      -
      -		$doc = new DOMDocument('1.0', 'utf-8');
      -		$doc->formatOutput = true;
      -
      -		$entry = self::entry($doc, $item, $owner, true);
      -
      -		$doc->appendChild($entry);
      -
      -		return(trim($doc->saveXML()));
      -	}
      -}
      -?>
      
      From b640db2b17379836a2d7708355b30ccd19531f4c Mon Sep 17 00:00:00 2001
      From: Michael Vogel 
      Date: Thu, 31 Mar 2016 00:14:51 +0200
      Subject: [PATCH 090/211] Doxygen structure added
      
      ---
       include/ostatus.php | 240 +++++++++++++++++++++++++++++++++++++++++++-
       1 file changed, 238 insertions(+), 2 deletions(-)
      
      diff --git a/include/ostatus.php b/include/ostatus.php
      index ba5b80cd5d..dcbd91f415 100644
      --- a/include/ostatus.php
      +++ b/include/ostatus.php
      @@ -1,4 +1,8 @@
       createElement("source");
       		xml::add_element($doc, $source, "id", $contact["poll"]);
      @@ -1413,6 +1561,14 @@ class ostatus {
       		return $source;
       	}
       
      +	/**
      +	 * @brief 
      +	 *
      +	 * @param $url
      +	 * @param $owner
      +	 *
      +	 * @return 
      +	 */
       	private function contact_entry($url, $owner) {
       
       		$r = q("SELECT * FROM `contact` WHERE `nurl` = '%s' AND `uid` IN (0, %d) ORDER BY `uid` DESC LIMIT 1",
      @@ -1447,6 +1603,17 @@ class ostatus {
       		return $contact;
       	}
       
      +	/**
      +	 * @brief 
      +	 *
      +	 * @param $doc
      +	 * @param $item
      +	 * @param $owner
      +	 * @param $repeated_guid
      +	 * @param $toplevel
      +	 *
      +	 * @return 
      +	 */
       	private function reshare_entry($doc, $item, $owner, $repeated_guid, $toplevel) {
       
       		if (($item["id"] != $item["parent"]) AND (normalise_link($item["author-link"]) != normalise_link($owner["url"]))) {
      @@ -1473,8 +1640,6 @@ class ostatus {
       
       		$as_object = $doc->createElement("activity:object");
       
      -// ostatusWaEeYs
      -// ostatusogu9zg - besser
       		xml::add_element($doc, $as_object, "activity:object-type", NAMESPACE_ACTIVITY_SCHEMA."activity");
       
       		self::entry_content($doc, $as_object, $repeated_item, $owner, "", "", false);
      @@ -1505,6 +1670,16 @@ class ostatus {
       		return $entry;
       	}
       
      +	/**
      +	 * @brief 
      +	 *
      +	 * @param $doc
      +	 * @param $item
      +	 * @param $owner
      +	 * @param $toplevel
      +	 *
      +	 * @return 
      +	 */
       	private function like_entry($doc, $item, $owner, $toplevel) {
       
       		if (($item["id"] != $item["parent"]) AND (normalise_link($item["author-link"]) != normalise_link($owner["url"]))) {
      @@ -1532,6 +1707,16 @@ class ostatus {
       		return $entry;
       	}
       
      +	/**
      +	 * @brief 
      +	 *
      +	 * @param $doc
      +	 * @param $item
      +	 * @param $owner
      +	 * @param $toplevel
      +	 *
      +	 * @return 
      +	 */
       	private function note_entry($doc, $item, $owner, $toplevel) {
       
       		if (($item["id"] != $item["parent"]) AND (normalise_link($item["author-link"]) != normalise_link($owner["url"]))) {
      @@ -1549,6 +1734,16 @@ class ostatus {
       		return $entry;
       	}
       
      +	/**
      +	 * @brief 
      +	 *
      +	 * @param $doc
      +	 * @param $entry
      +	 * @param $owner
      +	 * @param $toplevel
      +	 *
      +	 * @return 
      +	 */
       	private function entry_header($doc, &$entry, $owner, $toplevel) {
       		if (!$toplevel) {
       			$entry = $doc->createElement("entry");
      @@ -1572,6 +1767,19 @@ class ostatus {
       		return $title;
       	}
       
      +	/**
      +	 * @brief 
      +	 *
      +	 * @param $doc
      +	 * @param $entry
      +	 * @param $item
      +	 * @param $owner
      +	 * @param $title
      +	 * @param $verb
      +	 * @param $complete
      +	 *
      +	 * @return 
      +	 */
       	private function entry_content($doc, $entry, $item, $owner, $title, $verb = "", $complete = true) {
       
       		if ($verb == "")
      @@ -1601,6 +1809,17 @@ class ostatus {
       		xml::add_element($doc, $entry, "updated", datetime_convert("UTC","UTC",$item["edited"]."+00:00",ATOM_TIME));
       	}
       
      +	/**
      +	 * @brief 
      +	 *
      +	 * @param $doc
      +	 * @param $entry
      +	 * @param $item
      +	 * @param $owner
      +	 * @param $complete
      +	 *
      +	 * @return 
      +	 */
       	private function entry_footer($doc, $entry, $item, $owner, $complete = true) {
       
       		$mentioned = array();
      @@ -1697,6 +1916,15 @@ class ostatus {
       		}
       	}
       
      +	/**
      +	 * @brief 
      +	 *
      +	 * @param $a
      +	 * @param $owner_nick
      +	 * @param $last_update
      +	 *
      +	 * @return 
      +	 */
       	public static function feed(&$a, $owner_nick, $last_update) {
       
       		$r = q("SELECT `contact`.*, `user`.`nickname`, `user`.`timezone`, `user`.`page-flags`
      @@ -1746,6 +1974,14 @@ class ostatus {
       		return(trim($doc->saveXML()));
       	}
       
      +	/**
      +	 * @brief 
      +	 *
      +	 * @param $item
      +	 * @param $owner
      +	 *
      +	 * @return 
      +	 */
       	public static function salmon($item,$owner) {
       
       		$doc = new DOMDocument('1.0', 'utf-8');
      
      From f76d24bb83ffbd95a03114566dc7e1a657099861 Mon Sep 17 00:00:00 2001
      From: Michael Vogel 
      Date: Thu, 31 Mar 2016 07:34:13 +0200
      Subject: [PATCH 091/211] Added some documentation
      
      ---
       include/ostatus.php | 126 +++++++++++++++++++++-----------------------
       1 file changed, 61 insertions(+), 65 deletions(-)
      
      diff --git a/include/ostatus.php b/include/ostatus.php
      index dcbd91f415..0796fcc3b7 100644
      --- a/include/ostatus.php
      +++ b/include/ostatus.php
      @@ -28,15 +28,15 @@ class ostatus {
       	const OSTATUS_DEFAULT_POLL_TIMEFRAME_MENTIONS = 14400; // given in minutes
       
       	/**
      -	 * @brief 
      +	 * @brief Fetches author data
       	 *
      -	 * @param $xpath
      -	 * @param $context
      -	 * @param $importer
      -	 * @param $contact
      -	 * @param $onlyfetch
      +	 * @param object $xpath The xpath object
      +	 * @param object $context The xml context of the author detals
      +	 * @param array $importer user record of the importing user
      +	 * @param array $contact Called by reference, will contain the fetched contact
      +	 * @param bool $onlyfetch Only fetch the header without updating the contact entries
       	 *
      -	 * @return 
      +	 * @return array Array of author related entries for the item
       	 */
       	private function fetchauthor($xpath, $context, $importer, &$contact, $onlyfetch) {
       
      @@ -147,7 +147,7 @@ class ostatus {
       	 * @brief 
       	 *
       	 * @param $xml
      -	 * @param $importer
      +	 * @param array $importer user record of the importing user
       	 *
       	 * @return 
       	 */
      @@ -179,14 +179,12 @@ class ostatus {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Imports an XML string containing OStatus elements
       	 *
      -	 * @param $xml
      -	 * @param $importer
      +	 * @param string $xml The XML
      +	 * @param array $importer user record of the importing user
       	 * @param $contact
      -	 * @param $hub
      -	 *
      -	 * @return 
      +	 * @param array $hub Called by reference, returns the fetched hub data
       	 */
       	public static function import($xml,$importer,&$contact, &$hub) {
       
      @@ -530,11 +528,11 @@ class ostatus {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Create an url out of an uri
       	 *
      -	 * @param $href
      +	 * @param string $href URI in the format "parameter1:parameter1:..."
       	 *
      -	 * @return 
      +	 * @return string URL in the format http(s)://....
       	 */
       	public static function convert_href($href) {
       		$elements = explode(":",$href);
      @@ -560,12 +558,10 @@ class ostatus {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Checks if there are entries in conversations that aren't present on our side
       	 *
      -	 * @param $mentions
      -	 * @param $override
      -	 *
      -	 * @return 
      +	 * @param bool $mentions Fetch conversations where we are mentioned
      +	 * @param bool $override Override the interval setting
       	 */
       	public static function check_conversations($mentions = false, $override = false) {
       		$last = get_config('system','ostatus_last_poll');
      @@ -759,7 +755,7 @@ class ostatus {
       	 *
       	 * @param $conversation_url
       	 * @param $uid
      -	 * @param $item
      +	 * @param array $item Data of the item that is to be posted
       	 *
       	 * @return 
       	 */
      @@ -1184,11 +1180,11 @@ class ostatus {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Checks if the current post is a reshare
       	 *
      -	 * @param $item
      +	 * @param array $item The item array of thw post
       	 *
      -	 * @return 
      +	 * @return string The guid if the post is a reshare
       	 */
       	private function get_reshared_guid($item) {
       		$body = trim($item["body"]);
      @@ -1220,11 +1216,11 @@ class ostatus {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Cleans the body of a post if it contains picture links
       	 *
      -	 * @param $body
      +	 * @param string $body The body
       	 *
      -	 * @return 
      +	 * @return string The cleaned body
       	 */
       	private function format_picture_post($body) {
       		$siteinfo = get_attached_data($body);
      @@ -1256,8 +1252,8 @@ class ostatus {
       	/**
       	 * @brief 
       	 *
      -	 * @param $doc
      -	 * @param $owner
      +	 * @param object $doc XML document
      +	 * @param array $owner Contact data of the poster
       	 *
       	 * @return 
       	 */
      @@ -1317,7 +1313,7 @@ class ostatus {
       	/**
       	 * @brief 
       	 *
      -	 * @param $doc
      +	 * @param object $doc XML document
       	 * @param $root
       	 *
       	 * @return 
      @@ -1344,9 +1340,9 @@ class ostatus {
       	/**
       	 * @brief 
       	 *
      -	 * @param $doc
      +	 * @param object $doc XML document
       	 * @param $root
      -	 * @param $item
      +	 * @param array $item Data of the item that is to be posted
       	 *
       	 * @return 
       	 */
      @@ -1416,8 +1412,8 @@ class ostatus {
       	/**
       	 * @brief 
       	 *
      -	 * @param $doc
      -	 * @param $owner
      +	 * @param object $doc XML document
      +	 * @param array $owner Contact data of the poster
       	 *
       	 * @return 
       	 */
      @@ -1490,7 +1486,7 @@ class ostatus {
       	/**
       	 * @brief 
       	 *
      -	 * @param $item
      +	 * @param array $item Data of the item that is to be posted
       	 *
       	 * @return 
       	 */
      @@ -1503,7 +1499,7 @@ class ostatus {
       	/**
       	 * @brief 
       	 *
      -	 * @param $item
      +	 * @param array $item Data of the item that is to be posted
       	 *
       	 * @return 
       	 */
      @@ -1516,9 +1512,9 @@ class ostatus {
       	/**
       	 * @brief 
       	 *
      -	 * @param $doc
      -	 * @param $item
      -	 * @param $owner
      +	 * @param object $doc XML document
      +	 * @param array $item Data of the item that is to be posted
      +	 * @param array $owner Contact data of the poster
       	 * @param $toplevel
       	 *
       	 * @return 
      @@ -1540,7 +1536,7 @@ class ostatus {
       	/**
       	 * @brief 
       	 *
      -	 * @param $doc
      +	 * @param object $doc XML document
       	 * @param $contact
       	 *
       	 * @return 
      @@ -1565,7 +1561,7 @@ class ostatus {
       	 * @brief 
       	 *
       	 * @param $url
      -	 * @param $owner
      +	 * @param array $owner Contact data of the poster
       	 *
       	 * @return 
       	 */
      @@ -1606,9 +1602,9 @@ class ostatus {
       	/**
       	 * @brief 
       	 *
      -	 * @param $doc
      -	 * @param $item
      -	 * @param $owner
      +	 * @param object $doc XML document
      +	 * @param array $item Data of the item that is to be posted
      +	 * @param array $owner Contact data of the poster
       	 * @param $repeated_guid
       	 * @param $toplevel
       	 *
      @@ -1673,12 +1669,12 @@ class ostatus {
       	/**
       	 * @brief 
       	 *
      -	 * @param $doc
      -	 * @param $item
      -	 * @param $owner
      +	 * @param object $doc XML document
      +	 * @param array $item Data of the item that is to be posted
      +	 * @param array $owner Contact data of the poster
       	 * @param $toplevel
       	 *
      -	 * @return 
      +	 * @return object
       	 */
       	private function like_entry($doc, $item, $owner, $toplevel) {
       
      @@ -1710,9 +1706,9 @@ class ostatus {
       	/**
       	 * @brief 
       	 *
      -	 * @param $doc
      -	 * @param $item
      -	 * @param $owner
      +	 * @param object $doc XML document
      +	 * @param array $item Data of the item that is to be posted
      +	 * @param array $owner Contact data of the poster
       	 * @param $toplevel
       	 *
       	 * @return 
      @@ -1737,9 +1733,9 @@ class ostatus {
       	/**
       	 * @brief 
       	 *
      -	 * @param $doc
      +	 * @param object $doc XML document
       	 * @param $entry
      -	 * @param $owner
      +	 * @param array $owner Contact data of the poster
       	 * @param $toplevel
       	 *
       	 * @return 
      @@ -1770,10 +1766,10 @@ class ostatus {
       	/**
       	 * @brief 
       	 *
      -	 * @param $doc
      +	 * @param object $doc XML document
       	 * @param $entry
      -	 * @param $item
      -	 * @param $owner
      +	 * @param array $item Data of the item that is to be posted
      +	 * @param array $owner Contact data of the poster
       	 * @param $title
       	 * @param $verb
       	 * @param $complete
      @@ -1812,10 +1808,10 @@ class ostatus {
       	/**
       	 * @brief 
       	 *
      -	 * @param $doc
      +	 * @param object $doc XML document
       	 * @param $entry
      -	 * @param $item
      -	 * @param $owner
      +	 * @param array $item Data of the item that is to be posted
      +	 * @param array $owner Contact data of the poster
       	 * @param $complete
       	 *
       	 * @return 
      @@ -1975,12 +1971,12 @@ class ostatus {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Creates the XML for a salmon message
       	 *
      -	 * @param $item
      -	 * @param $owner
      +	 * @param array $item Data of the item that is to be posted
      +	 * @param array $owner Contact data of the poster
       	 *
      -	 * @return 
      +	 * @return string XML for the salmon
       	 */
       	public static function salmon($item,$owner) {
       
      
      From 28e68f570427203fbd5b365f2761e4362c947eec Mon Sep 17 00:00:00 2001
      From: Michael Vogel 
      Date: Thu, 31 Mar 2016 22:01:56 +0200
      Subject: [PATCH 092/211] Some more documentation
      
      ---
       include/ostatus.php | 119 ++++++++++++++++++++------------------------
       1 file changed, 55 insertions(+), 64 deletions(-)
      
      diff --git a/include/ostatus.php b/include/ostatus.php
      index 0796fcc3b7..60db968f1d 100644
      --- a/include/ostatus.php
      +++ b/include/ostatus.php
      @@ -144,12 +144,12 @@ class ostatus {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Fetches author data from a given XML string
       	 *
      -	 * @param $xml
      +	 * @param string $xml The XML
       	 * @param array $importer user record of the importing user
       	 *
      -	 * @return 
      +	 * @return array Array of author related entries for the item
       	 */
       	public static function salmon_author($xml, $importer) {
       
      @@ -751,13 +751,13 @@ class ostatus {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Stores an item and completes the thread
       	 *
      -	 * @param $conversation_url
      -	 * @param $uid
      +	 * @param string $conversation_url The URI of the conversation
      +	 * @param integer $uid The user id
       	 * @param array $item Data of the item that is to be posted
       	 *
      -	 * @return 
      +	 * @return integer The item id of the posted item array
       	 */
       	private function completion($conversation_url, $uid, $item = array(), $self = "") {
       
      @@ -1151,12 +1151,10 @@ class ostatus {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Stores conversation data into the database
       	 *
      -	 * @param $itemid
      -	 * @param $conversation_url
      -	 *
      -	 * @return 
      +	 * @param integer $itemid The id of the item
      +	 * @param string $conversation_url The uri of the conversation
       	 */
       	private function store_conversation($itemid, $conversation_url) {
       
      @@ -1250,12 +1248,12 @@ class ostatus {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Adds the header elements to the XML document
       	 *
       	 * @param object $doc XML document
       	 * @param array $owner Contact data of the poster
       	 *
      -	 * @return 
      +	 * @return object header root element
       	 */
       	private function add_header($doc, $owner) {
       
      @@ -1311,12 +1309,10 @@ class ostatus {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Add the link to the push hubs to the XML document
       	 *
       	 * @param object $doc XML document
      -	 * @param $root
      -	 *
      -	 * @return 
      +	 * @param object $root XML root element where the hub links are added
       	 */
       	public static function hublinks($doc, $root) {
       		$hub = get_config('system','huburl');
      @@ -1338,13 +1334,11 @@ class ostatus {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Adds attachement data to the XML document
       	 *
       	 * @param object $doc XML document
      -	 * @param $root
      +	 * @param object $root XML root element where the hub links are added
       	 * @param array $item Data of the item that is to be posted
      -	 *
      -	 * @return 
       	 */
       	private function get_attachment($doc, $root, $item) {
       		$o = "";
      @@ -1410,12 +1404,12 @@ class ostatus {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Adds the author element to the XML document
       	 *
       	 * @param object $doc XML document
       	 * @param array $owner Contact data of the poster
       	 *
      -	 * @return 
      +	 * @return object author element
       	 */
       	private function add_author($doc, $owner) {
       
      @@ -1484,11 +1478,11 @@ class ostatus {
       	*/
       
       	/**
      -	 * @brief 
      +	 * @brief Returns the given activity if present - otherwise returns the "post" activity
       	 *
       	 * @param array $item Data of the item that is to be posted
       	 *
      -	 * @return 
      +	 * @return string activity
       	 */
       	function construct_verb($item) {
       		if ($item['verb'])
      @@ -1497,11 +1491,11 @@ class ostatus {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Returns the given object type if present - otherwise returns the "note" object type
       	 *
       	 * @param array $item Data of the item that is to be posted
       	 *
      -	 * @return 
      +	 * @return string Object type
       	 */
       	function construct_objecttype($item) {
       		if (in_array($item['object-type'], array(ACTIVITY_OBJ_NOTE, ACTIVITY_OBJ_COMMENT)))
      @@ -1510,14 +1504,14 @@ class ostatus {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Adds an entry element to the XML document
       	 *
       	 * @param object $doc XML document
       	 * @param array $item Data of the item that is to be posted
       	 * @param array $owner Contact data of the poster
      -	 * @param $toplevel
      +	 * @param bool $toplevel
       	 *
      -	 * @return 
      +	 * @return object Entry element
       	 */
       	private function entry($doc, $item, $owner, $toplevel = false) {
       		$repeated_guid = self::get_reshared_guid($item);
      @@ -1534,12 +1528,12 @@ class ostatus {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Adds a source entry to the XML document
       	 *
       	 * @param object $doc XML document
      -	 * @param $contact
      +	 * @param array $contact Array of the contact that is added
       	 *
      -	 * @return 
      +	 * @return object Source element
       	 */
       	private function source_entry($doc, $contact) {
       		$source = $doc->createElement("source");
      @@ -1558,12 +1552,12 @@ class ostatus {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Fetches contact data from the contact or the gcontact table
       	 *
      -	 * @param $url
      +	 * @param string $url URL of the contact
       	 * @param array $owner Contact data of the poster
       	 *
      -	 * @return 
      +	 * @return array Contact array
       	 */
       	private function contact_entry($url, $owner) {
       
      @@ -1600,15 +1594,15 @@ class ostatus {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Adds an entry element with reshared content
       	 *
       	 * @param object $doc XML document
       	 * @param array $item Data of the item that is to be posted
       	 * @param array $owner Contact data of the poster
       	 * @param $repeated_guid
      -	 * @param $toplevel
      +	 * @param bool $toplevel Is it for en entry element (false) or a feed entry (true)?
       	 *
      -	 * @return 
      +	 * @return object Entry element
       	 */
       	private function reshare_entry($doc, $item, $owner, $repeated_guid, $toplevel) {
       
      @@ -1667,14 +1661,14 @@ class ostatus {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Adds an entry element with a "like"
       	 *
       	 * @param object $doc XML document
       	 * @param array $item Data of the item that is to be posted
       	 * @param array $owner Contact data of the poster
      -	 * @param $toplevel
      +	 * @param bool $toplevel Is it for en entry element (false) or a feed entry (true)?
       	 *
      -	 * @return object
      +	 * @return object Entry element with "like"
       	 */
       	private function like_entry($doc, $item, $owner, $toplevel) {
       
      @@ -1704,14 +1698,14 @@ class ostatus {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Adds a regular entry element
       	 *
       	 * @param object $doc XML document
       	 * @param array $item Data of the item that is to be posted
       	 * @param array $owner Contact data of the poster
      -	 * @param $toplevel
      +	 * @param bool $toplevel Is it for en entry element (false) or a feed entry (true)?
       	 *
      -	 * @return 
      +	 * @return object Entry element
       	 */
       	private function note_entry($doc, $item, $owner, $toplevel) {
       
      @@ -1731,16 +1725,17 @@ class ostatus {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Adds a header element to the XML document
       	 *
       	 * @param object $doc XML document
      -	 * @param $entry
      +	 * @param object $entry Entry element
       	 * @param array $owner Contact data of the poster
      -	 * @param $toplevel
      +	 * @param bool $toplevel Is it for en entry element (false) or a feed entry (true)?
       	 *
      -	 * @return 
      +	 * @return string The title for the element
       	 */
       	private function entry_header($doc, &$entry, $owner, $toplevel) {
      +		/// @todo Check if this title stuff is really needed (I guess not)
       		if (!$toplevel) {
       			$entry = $doc->createElement("entry");
       			$title = sprintf("New note by %s", $owner["nick"]);
      @@ -1764,17 +1759,15 @@ class ostatus {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Adds elements to the XML document
       	 *
       	 * @param object $doc XML document
      -	 * @param $entry
      +	 * @param object $entry Entry element where the content is added
       	 * @param array $item Data of the item that is to be posted
       	 * @param array $owner Contact data of the poster
      -	 * @param $title
      -	 * @param $verb
      -	 * @param $complete
      -	 *
      -	 * @return 
      +	 * @param string $title Title for the post
      +	 * @param string $verb The activity verb
      +	 * @param bool $complete Add the "status_net" element?
       	 */
       	private function entry_content($doc, $entry, $item, $owner, $title, $verb = "", $complete = true) {
       
      @@ -1813,8 +1806,6 @@ class ostatus {
       	 * @param array $item Data of the item that is to be posted
       	 * @param array $owner Contact data of the poster
       	 * @param $complete
      -	 *
      -	 * @return 
       	 */
       	private function entry_footer($doc, $entry, $item, $owner, $complete = true) {
       
      @@ -1913,13 +1904,13 @@ class ostatus {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Creates the XML feed for a given nickname
       	 *
      -	 * @param $a
      -	 * @param $owner_nick
      -	 * @param $last_update
      +	 * @param app $a The application class
      +	 * @param string $owner_nick Nickname of the feed owner
      +	 * @param string $last_update Date of the last update
       	 *
      -	 * @return 
      +	 * @return string XML feed
       	 */
       	public static function feed(&$a, $owner_nick, $last_update) {
       
      
      From d49055c9801c2ba9c7f0ff199021f24c5564ce05 Mon Sep 17 00:00:00 2001
      From: Michael Vogel 
      Date: Thu, 31 Mar 2016 22:24:54 +0200
      Subject: [PATCH 093/211] Some more documentation
      
      ---
       include/ostatus.php | 8 +++++---
       1 file changed, 5 insertions(+), 3 deletions(-)
      
      diff --git a/include/ostatus.php b/include/ostatus.php
      index 60db968f1d..e31b4474da 100644
      --- a/include/ostatus.php
      +++ b/include/ostatus.php
      @@ -187,6 +187,7 @@ class ostatus {
       	 * @param array $hub Called by reference, returns the fetched hub data
       	 */
       	public static function import($xml,$importer,&$contact, &$hub) {
      +		/// @todo this function is too long. It has to be split in many parts
       
       		logger("Import OStatus message", LOGGER_DEBUG);
       
      @@ -761,6 +762,7 @@ class ostatus {
       	 */
       	private function completion($conversation_url, $uid, $item = array(), $self = "") {
       
      +		/// @todo This function is totally ugly and has to be rewritten totally
       
       		$item_stored = -1;
       
      @@ -1728,7 +1730,7 @@ class ostatus {
       	 * @brief Adds a header element to the XML document
       	 *
       	 * @param object $doc XML document
      -	 * @param object $entry Entry element
      +	 * @param object $entry The entry element where the elements are added
       	 * @param array $owner Contact data of the poster
       	 * @param bool $toplevel Is it for en entry element (false) or a feed entry (true)?
       	 *
      @@ -1799,10 +1801,10 @@ class ostatus {
       	}
       
       	/**
      -	 * @brief 
      +	 * @brief Adds the elements at the foot of an entry to the XML document
       	 *
       	 * @param object $doc XML document
      -	 * @param $entry
      +	 * @param object $entry The entry element where the elements are added
       	 * @param array $item Data of the item that is to be posted
       	 * @param array $owner Contact data of the poster
       	 * @param $complete
      
      From 783932000c61b56ae8d07f8c248a6f9ca6527263 Mon Sep 17 00:00:00 2001
      From: Michael Vogel 
      Date: Fri, 1 Apr 2016 21:09:52 +0200
      Subject: [PATCH 094/211] Don't optimize the tables when the maximum size is
       lower than zero
      
      ---
       include/cron.php | 46 ++++++++++++++++++++++++----------------------
       1 file changed, 24 insertions(+), 22 deletions(-)
      
      diff --git a/include/cron.php b/include/cron.php
      index c60284b738..a2482ff300 100644
      --- a/include/cron.php
      +++ b/include/cron.php
      @@ -335,35 +335,37 @@ function cron_clear_cache(&$a) {
       	if ($max_tablesize == 0)
       		$max_tablesize = 100 * 1000000; // Default are 100 MB
       
      -	// Minimum fragmentation level in percent
      -	$fragmentation_level = intval(get_config('system','optimize_fragmentation')) / 100;
      -	if ($fragmentation_level == 0)
      -		$fragmentation_level = 0.3; // Default value is 30%
      +	if ($max_tablesize > 0) {
      +		// Minimum fragmentation level in percent
      +		$fragmentation_level = intval(get_config('system','optimize_fragmentation')) / 100;
      +		if ($fragmentation_level == 0)
      +			$fragmentation_level = 0.3; // Default value is 30%
       
      -	// Optimize some tables that need to be optimized
      -	$r = q("SHOW TABLE STATUS");
      -	foreach($r as $table) {
      +		// Optimize some tables that need to be optimized
      +		$r = q("SHOW TABLE STATUS");
      +		foreach($r as $table) {
       
      -		// Don't optimize tables that are too large
      -		if ($table["Data_length"] > $max_tablesize)
      -			continue;
      +			// Don't optimize tables that are too large
      +			if ($table["Data_length"] > $max_tablesize)
      +				continue;
       
      -		// Don't optimize empty tables
      -		if ($table["Data_length"] == 0)
      -			continue;
      +			// Don't optimize empty tables
      +			if ($table["Data_length"] == 0)
      +				continue;
       
      -		// Calculate fragmentation
      -		$fragmentation = $table["Data_free"] / ($table["Data_length"] + $table["Index_length"]);
      +			// Calculate fragmentation
      +			$fragmentation = $table["Data_free"] / ($table["Data_length"] + $table["Index_length"]);
       
      -		logger("Table ".$table["Name"]." - Fragmentation level: ".round($fragmentation * 100, 2), LOGGER_DEBUG);
      +			logger("Table ".$table["Name"]." - Fragmentation level: ".round($fragmentation * 100, 2), LOGGER_DEBUG);
       
      -		// Don't optimize tables that needn't to be optimized
      -		if ($fragmentation < $fragmentation_level)
      -			continue;
      +			// Don't optimize tables that needn't to be optimized
      +			if ($fragmentation < $fragmentation_level)
      +				continue;
       
      -		// So optimize it
      -		logger("Optimize Table ".$table["Name"], LOGGER_DEBUG);
      -		q("OPTIMIZE TABLE `%s`", dbesc($table["Name"]));
      +			// So optimize it
      +			logger("Optimize Table ".$table["Name"], LOGGER_DEBUG);
      +			q("OPTIMIZE TABLE `%s`", dbesc($table["Name"]));
      +		}
       	}
       
       	set_config('system','cache_last_cleared', time());
      
      From fda9a88a31289049227a187fea85aeefdf86cbdd Mon Sep 17 00:00:00 2001
      From: Michael Vogel 
      Date: Fri, 1 Apr 2016 21:41:37 +0200
      Subject: [PATCH 095/211] Only update the contact entry with uid=0
      
      ---
       include/Scrape.php | 2 +-
       1 file changed, 1 insertion(+), 1 deletion(-)
      
      diff --git a/include/Scrape.php b/include/Scrape.php
      index deff0b080f..3fead0c415 100644
      --- a/include/Scrape.php
      +++ b/include/Scrape.php
      @@ -845,7 +845,7 @@ function probe_url($url, $mode = PROBE_NORMAL, $level = 1) {
       		/// It should only be updated if the existing picture isn't existing anymore.
       		if (($result['network'] != NETWORK_FEED) AND $result["addr"] AND $result["name"] AND $result["nick"])
       			q("UPDATE `contact` SET `addr` = '%s', `alias` = '%s', `name` = '%s', `nick` = '%s',
      -				`name-date` = '%s', `uri-date` = '%s' WHERE `nurl` = '%s' AND NOT `self`",
      +				`name-date` = '%s', `uri-date` = '%s' WHERE `nurl` = '%s' AND NOT `self` AND `uid` = 0",
       				dbesc($result["addr"]),
       				dbesc($result["alias"]),
       				dbesc($result["name"]),
      
      From 5e1652922d480fde608241c00cfb40231dfe680d Mon Sep 17 00:00:00 2001
      From: Michael Vogel 
      Date: Sat, 2 Apr 2016 00:24:56 +0200
      Subject: [PATCH 096/211] OStatus: Salmon now works with "likes"/The alias is
       stored now as well
      
      ---
       include/notifier.php | 16 +++++++++++++++-
       include/ostatus.php  | 13 +++++++------
       2 files changed, 22 insertions(+), 7 deletions(-)
      
      diff --git a/include/notifier.php b/include/notifier.php
      index 18a617ac2f..ffbb22e7bf 100644
      --- a/include/notifier.php
      +++ b/include/notifier.php
      @@ -229,7 +229,7 @@ function notifier_run(&$argv, &$argc){
       
       		$parent = $items[0];
       
      -		$thr_parent = q("SELECT `network` FROM `item` WHERE `uri` = '%s' AND `uid` = %d",
      +		$thr_parent = q("SELECT `network`, `author-link`, `owner-link` FROM `item` WHERE `uri` = '%s' AND `uid` = %d",
       			dbesc($target_item["thr-parent"]), intval($target_item["uid"]));
       
       		logger('Parent is '.$parent['network'].'. Thread parent is '.$thr_parent[0]['network'], LOGGER_DEBUG);
      @@ -390,6 +390,20 @@ function notifier_run(&$argv, &$argc){
       
       			logger('Some parent is OStatus for '.$target_item["guid"], LOGGER_DEBUG);
       
      +			// Send a salmon to the parent author
      +			$probed_contact = probe_url($thr_parent[0]['author-link']);
      +			if ($probed_contact["notify"] != "") {
      +				logger('Notify parent author '.$probed_contact["url"].': '.$probed_contact["notify"]);
      +				$url_recipients[$probed_contact["notify"]] = $probed_contact["notify"];
      +			}
      +
      +			// Send a salmon to the parent owner
      +			$probed_contact = probe_url($thr_parent[0]['owner-link']);
      +			if ($probed_contact["notify"] != "") {
      +				logger('Notify parent owner '.$probed_contact["url"].': '.$probed_contact["notify"]);
      +				$url_recipients[$probed_contact["notify"]] = $probed_contact["notify"];
      +			}
      +
       			// Send a salmon notification to every person we mentioned in the post
       			$arr = explode(',',$target_item['tag']);
       			foreach($arr as $x) {
      diff --git a/include/ostatus.php b/include/ostatus.php
      index e31b4474da..4775e2ccb9 100644
      --- a/include/ostatus.php
      +++ b/include/ostatus.php
      @@ -44,8 +44,7 @@ class ostatus {
       		$author["author-link"] = $xpath->evaluate('atom:author/atom:uri/text()', $context)->item(0)->nodeValue;
       		$author["author-name"] = $xpath->evaluate('atom:author/atom:name/text()', $context)->item(0)->nodeValue;
       
      -		// Preserve the value
      -		$authorlink = $author["author-link"];
      +		$aliaslink = $author["author-link"];
       
       		$alternate = $xpath->query("atom:author/atom:link[@rel='alternate']", $context)->item(0)->attributes;
       		if (is_object($alternate))
      @@ -55,7 +54,7 @@ class ostatus {
       
       		$r = q("SELECT * FROM `contact` WHERE `uid` = %d AND `nurl` IN ('%s', '%s') AND `network` != '%s'",
       			intval($importer["uid"]), dbesc(normalise_link($author["author-link"])),
      -			dbesc(normalise_link($authorlink)), dbesc(NETWORK_STATUSNET));
      +			dbesc(normalise_link($aliaslink)), dbesc(NETWORK_STATUSNET));
       		if ($r) {
       			$contact = $r[0];
       			$author["contact-id"] = $r[0]["id"];
      @@ -117,12 +116,14 @@ class ostatus {
       			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"])) {
      +			if (($contact["name"] != $r[0]["name"]) OR ($contact["nick"] != $r[0]["nick"]) OR ($contact["about"] != $r[0]["about"]) OR
      +				($contact["alias"] != $r[0]["alias"]) OR ($contact["location"] != $r[0]["location"])) {
       
       				logger("Update contact data for contact ".$contact["id"], LOGGER_DEBUG);
       
      -				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"]),
      +				q("UPDATE `contact` SET `name` = '%s', `nick` = '%s', `alias` = '%s', `about` = '%s', `location` = '%s', `name-date` = '%s' WHERE `id` = %d",
      +					dbesc($contact["name"]), dbesc($contact["nick"]), dbesc($contact["alias"]),
      +					dbesc($contact["about"]), dbesc($contact["location"]),
       					dbesc(datetime_convert()), intval($contact["id"]));
       
       				poco_check($contact["url"], $contact["name"], $contact["network"], $author["author-avatar"], $contact["about"], $contact["location"],
      
      From bacece897b52607711c8713545af740868ef4899 Mon Sep 17 00:00:00 2001
      From: Michael Vogel 
      Date: Sat, 2 Apr 2016 09:06:10 +0200
      Subject: [PATCH 097/211] Only update contact when scrape runs with
       "probe_normal"
      
      ---
       include/Scrape.php | 6 +++---
       1 file changed, 3 insertions(+), 3 deletions(-)
      
      diff --git a/include/Scrape.php b/include/Scrape.php
      index 3fead0c415..9913f360d6 100644
      --- a/include/Scrape.php
      +++ b/include/Scrape.php
      @@ -843,15 +843,15 @@ function probe_url($url, $mode = PROBE_NORMAL, $level = 1) {
       		/// @todo temporary fix - we need a real contact update function that updates only changing fields
       		/// The biggest problem is the avatar picture that could have a reduced image size.
       		/// It should only be updated if the existing picture isn't existing anymore.
      -		if (($result['network'] != NETWORK_FEED) AND $result["addr"] AND $result["name"] AND $result["nick"])
      +		if (($result['network'] != NETWORK_FEED) AND ($mode == PROBE_NORMAL) AND
      +			$result["addr"] AND $result["name"] AND $result["nick"])
       			q("UPDATE `contact` SET `addr` = '%s', `alias` = '%s', `name` = '%s', `nick` = '%s',
      -				`name-date` = '%s', `uri-date` = '%s' WHERE `nurl` = '%s' AND NOT `self` AND `uid` = 0",
      +				`success_update` = '%s' WHERE `nurl` = '%s' AND NOT `self` AND `uid` = 0",
       				dbesc($result["addr"]),
       				dbesc($result["alias"]),
       				dbesc($result["name"]),
       				dbesc($result["nick"]),
       				dbesc(datetime_convert()),
      -				dbesc(datetime_convert()),
       				dbesc(normalise_link($result['url']))
       		);
       	}
      
      From 7158390b9839683ba0c6bf96c568443f4179af68 Mon Sep 17 00:00:00 2001
      From: Michael Vogel 
      Date: Sat, 2 Apr 2016 14:10:40 +0200
      Subject: [PATCH 098/211] Update contact data for uid=0 at feed import
      
      ---
       include/ostatus.php | 37 ++++++++++++++++++++++++++++++++-----
       1 file changed, 32 insertions(+), 5 deletions(-)
      
      diff --git a/include/ostatus.php b/include/ostatus.php
      index 4775e2ccb9..d6e2e17735 100644
      --- a/include/ostatus.php
      +++ b/include/ostatus.php
      @@ -90,13 +90,20 @@ class ostatus {
       
       		// Only update the contacts if it is an OStatus contact
       		if ($r AND !$onlyfetch AND ($contact["network"] == NETWORK_OSTATUS)) {
      +
       			// Update contact data
       
      -			$value = $xpath->query("atom:link[@rel='salmon']", $context)->item(0)->nodeValue;
      -			if ($value != "")
      -				$contact["notify"] = $value;
      +			// This query doesn't seem to work
      +			// $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;
      +			// This query doesn't seem to work as well - I hate these queries
      +			// $value = $xpath->query("atom:link[@rel='self' and @type='application/atom+xml']", $context)->item(0)->nodeValue;
      +			// if ($value != "")
      +			//	$contact["poll"] = $value;
      +
      +			$value = $xpath->evaluate('atom:author/atom:uri/text()', $context)->item(0)->nodeValue;
       			if ($value != "")
       				$contact["alias"] = $value;
       
      @@ -136,6 +143,24 @@ class ostatus {
       				update_contact_avatar($author["author-avatar"], $importer["uid"], $contact["id"]);
       			}
       
      +			// Ensure that we are having this contact (with uid=0)
      +			$cid = get_contact($author["author-link"], 0);
      +
      +			if ($cid) {
      +				// Update it with the current values
      +				q("UPDATE `contact` SET `url` = '%s', `name` = '%s', `nick` = '%s', `alias` = '%s',
      +						`about` = '%s', `location` = '%s', `notify` = '%s', `poll` = '%s',
      +						`success_update` = '%s', `last-update` = '%s'
      +					WHERE `id` = %d",
      +					dbesc($author["author-link"]), dbesc($contact["name"]), dbesc($contact["nick"]),
      +					dbesc($contact["alias"]), dbesc($contact["about"]), dbesc($contact["location"]),
      +					dbesc($contact["notify"]), dbesc($contact["poll"]),
      +					dbesc(datetime_convert()), dbesc(datetime_convert()), intval($cid));
      +
      +				// Update the avatar
      +				update_contact_avatar($author["author-avatar"], 0, $cid);
      +			}
      +
       			$contact["generation"] = 2;
       			$contact["photo"] = $author["author-avatar"];
       			update_gcontact($contact);
      @@ -1586,8 +1611,10 @@ class ostatus {
       
       		if (!isset($contact["poll"])) {
       			$data = probe_url($url);
      -			$contact["alias"] = $data["alias"];
       			$contact["poll"] = $data["poll"];
      +
      +			if (!$contact["alias"])
      +				$contact["alias"] = $data["alias"];
       		}
       
       		if (!isset($contact["alias"]))
      
      From 0c6ff4202d2056c710e384175864b3cf7f6f92b3 Mon Sep 17 00:00:00 2001
      From: Michael Vogel 
      Date: Sat, 2 Apr 2016 15:41:55 +0200
      Subject: [PATCH 099/211] Bugfix: The nickname vanished/better way to fetch the
       alias
      
      ---
       include/Scrape.php | 21 +++++++++++++++------
       include/cron.php   |  3 +++
       include/feed.php   |  5 ++++-
       mod/noscrape.php   |  2 +-
       4 files changed, 23 insertions(+), 8 deletions(-)
      
      diff --git a/include/Scrape.php b/include/Scrape.php
      index 9913f360d6..ac95c0d5d3 100644
      --- a/include/Scrape.php
      +++ b/include/Scrape.php
      @@ -23,13 +23,15 @@ function scrape_dfrn($url, $dont_probe = false) {
       		if (is_array($noscrapedata)) {
       			if ($noscrapedata["nick"] != "")
       				return($noscrapedata);
      +			else
      +				unset($noscrapedata["nick"]);
       		} else
       			$noscrapedata = array();
       	}
       
       	$s = fetch_url($url);
       
      -	if(! $s)
      +	if (!$s)
       		return $ret;
       
       	if (!$dont_probe) {
      @@ -703,6 +705,9 @@ function probe_url($url, $mode = PROBE_NORMAL, $level = 1) {
       					if (($vcard["nick"] == "") AND ($data["header"]["author-nick"] != ""))
       						$vcard["nick"] = $data["header"]["author-nick"];
       
      +					if (($network == NETWORK_OSTATUS) AND ($data["header"]["author-id"] != ""))
      +						$alias = $data["header"]["author-id"];
      +
       					if(!$profile AND ($data["header"]["author-link"] != "") AND !in_array($network, array("", NETWORK_FEED)))
       						$profile = $data["header"]["author-link"];
       				}
      @@ -844,13 +849,17 @@ function probe_url($url, $mode = PROBE_NORMAL, $level = 1) {
       		/// The biggest problem is the avatar picture that could have a reduced image size.
       		/// It should only be updated if the existing picture isn't existing anymore.
       		if (($result['network'] != NETWORK_FEED) AND ($mode == PROBE_NORMAL) AND
      -			$result["addr"] AND $result["name"] AND $result["nick"])
      -			q("UPDATE `contact` SET `addr` = '%s', `alias` = '%s', `name` = '%s', `nick` = '%s',
      -				`success_update` = '%s' WHERE `nurl` = '%s' AND NOT `self` AND `uid` = 0",
      -				dbesc($result["addr"]),
      -				dbesc($result["alias"]),
      +			$result["name"] AND $result["nick"] AND $result["url"] AND $result["addr"] AND $result["poll"])
      +			q("UPDATE `contact` SET `name` = '%s', `nick` = '%s', `url` = '%s', `addr` = '%s',
      +					`notify` = '%s', `poll` = '%s', `alias` = '%s', `success_update` = '%s'
      +				WHERE `nurl` = '%s' AND NOT `self` AND `uid` = 0",
       				dbesc($result["name"]),
       				dbesc($result["nick"]),
      +				dbesc($result["url"]),
      +				dbesc($result["addr"]),
      +				dbesc($result["notify"]),
      +				dbesc($result["poll"]),
      +				dbesc($result["alias"]),
       				dbesc(datetime_convert()),
       				dbesc(normalise_link($result['url']))
       		);
      diff --git a/include/cron.php b/include/cron.php
      index a2482ff300..e2f4102804 100644
      --- a/include/cron.php
      +++ b/include/cron.php
      @@ -405,6 +405,9 @@ function cron_repair_database() {
       	// This call is very "cheap" so we can do it at any time without a problem
       	q("UPDATE `item` INNER JOIN `item` AS `parent` ON `parent`.`uri` = `item`.`parent-uri` AND `parent`.`uid` = `item`.`uid` SET `item`.`parent` = `parent`.`id` WHERE `item`.`parent` = 0");
       
      +	// There was an issue where the nick vanishes from the contact table
      +	q("UPDATE `contact` INNER JOIN `user` ON `contact`.`uid` = `user`.`uid` SET `nick` = `nickname` WHERE `self` AND `nick`=''");
      +
       	/// @todo
       	/// - remove thread entries without item
       	/// - remove sign entries without item
      diff --git a/include/feed.php b/include/feed.php
      index 04cfba75a6..293de3cc96 100644
      --- a/include/feed.php
      +++ b/include/feed.php
      @@ -54,8 +54,10 @@ function feed_import($xml,$importer,&$contact, &$hub, $simulate = false) {
       				if ($attributes->name == "href")
       					$author["author-link"] = $attributes->textContent;
       
      +		$author["author-id"] = $xpath->evaluate('/atom:feed/atom:author/atom:uri/text()')->item(0)->nodeValue;
      +
       		if ($author["author-link"] == "")
      -			$author["author-link"] = $xpath->evaluate('/atom:feed/atom:author/atom:uri/text()')->item(0)->nodeValue;
      +			$author["author-link"] = $author["author-id"];
       
       		if ($author["author-link"] == "") {
       			$self = $xpath->query("atom:link[@rel='self']")->item(0)->attributes;
      @@ -127,6 +129,7 @@ function feed_import($xml,$importer,&$contact, &$hub, $simulate = false) {
       
       		// This is no field in the item table. So we have to unset it.
       		unset($author["author-nick"]);
      +		unset($author["author-id"]);
       	}
       
       	$header = array();
      diff --git a/mod/noscrape.php b/mod/noscrape.php
      index 1f7105b769..4be1a740cd 100644
      --- a/mod/noscrape.php
      +++ b/mod/noscrape.php
      @@ -28,7 +28,7 @@ function noscrape_init(&$a) {
       	$json_info = array(
       		'fn' => $a->profile['name'],
       		'addr' => $a->profile['addr'],
      -		'nick' => $a->user['nickname'],
      +		'nick' => $which,
       		'key' => $a->profile['pubkey'],
       		'homepage' => $a->get_baseurl()."/profile/{$which}",
       		'comm' => (x($a->profile,'page-flags')) && ($a->profile['page-flags'] == PAGE_COMMUNITY),
      
      From e99ec7feebbc858000fbe4cb365e8d55e337d7b1 Mon Sep 17 00:00:00 2001
      From: Michael Vogel 
      Date: Sat, 2 Apr 2016 15:49:57 +0200
      Subject: [PATCH 100/211] Notify and poll aren't fetched at the moment
      
      ---
       include/ostatus.php | 3 +--
       1 file changed, 1 insertion(+), 2 deletions(-)
      
      diff --git a/include/ostatus.php b/include/ostatus.php
      index d6e2e17735..bf14536f9e 100644
      --- a/include/ostatus.php
      +++ b/include/ostatus.php
      @@ -149,12 +149,11 @@ class ostatus {
       			if ($cid) {
       				// Update it with the current values
       				q("UPDATE `contact` SET `url` = '%s', `name` = '%s', `nick` = '%s', `alias` = '%s',
      -						`about` = '%s', `location` = '%s', `notify` = '%s', `poll` = '%s',
      +						`about` = '%s', `location` = '%s',
       						`success_update` = '%s', `last-update` = '%s'
       					WHERE `id` = %d",
       					dbesc($author["author-link"]), dbesc($contact["name"]), dbesc($contact["nick"]),
       					dbesc($contact["alias"]), dbesc($contact["about"]), dbesc($contact["location"]),
      -					dbesc($contact["notify"]), dbesc($contact["poll"]),
       					dbesc(datetime_convert()), dbesc(datetime_convert()), intval($cid));
       
       				// Update the avatar
      
      From c8a62a4073ca297d916dd09d6c106a84776d838a Mon Sep 17 00:00:00 2001
      From: Michael Vogel 
      Date: Sat, 2 Apr 2016 17:43:53 +0200
      Subject: [PATCH 101/211] Small bugfix: We always liked the parent
      
      ---
       include/ostatus.php | 3 ++-
       1 file changed, 2 insertions(+), 1 deletion(-)
      
      diff --git a/include/ostatus.php b/include/ostatus.php
      index bf14536f9e..b798a605f9 100644
      --- a/include/ostatus.php
      +++ b/include/ostatus.php
      @@ -1712,7 +1712,8 @@ class ostatus {
       
       		$as_object = $doc->createElement("activity:object");
       
      -		$parent = q("SELECT * FROM `item` WHERE `id` = %d", intval($item["parent"]));
      +		$parent = q("SELECT * FROM `item` WHERE `uri` = '%s' AND `uid` = %d",
      +			dbesc($item["thr-parent"]), intval($item["uid"]));
       		$parent_item = (($item['thr-parent']) ? $item['thr-parent'] : $item['parent-uri']);
       
       		xml::add_element($doc, $as_object, "activity:object-type", self::construct_objecttype($parent[0]));
      
      From f711258183dcc1eeef3dbe08c0ea9d6b40a8e41b Mon Sep 17 00:00:00 2001
      From: Michael Vogel 
      Date: Sun, 3 Apr 2016 13:48:31 +0200
      Subject: [PATCH 102/211] New field in item table ("shadow") that indicates if
       there is a shadow entry
      
      ---
       boot.php                |   8 +--
       include/Contact.php     |  54 -----------------
       include/cron.php        |   5 +-
       include/dbstructure.php |   1 +
       include/post_update.php | 129 ++++++++++++++++++++++++++++++++++++++++
       include/threads.php     |   8 ++-
       update.php              |   2 +-
       7 files changed, 145 insertions(+), 62 deletions(-)
       create mode 100644 include/post_update.php
      
      diff --git a/boot.php b/boot.php
      index a9ff0c30c3..108d8d0ede 100644
      --- a/boot.php
      +++ b/boot.php
      @@ -6,11 +6,11 @@
       
       /**
        * Friendica
      - * 
      + *
        * Friendica is a communications platform for integrated social communications
        * utilising decentralised communications and linkage to several indie social
        * projects - as well as popular mainstream providers.
      - * 
      + *
        * Our mission is to free our friends and families from the clutches of
        * data-harvesting corporations, and pave the way to a future where social
        * communications are free and open and flow between alternate providers as
      @@ -18,7 +18,7 @@
        */
       
       require_once('include/autoloader.php');
      - 
      +
       require_once('include/config.php');
       require_once('include/network.php');
       require_once('include/plugin.php');
      @@ -38,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',      1194      );
      +define ( 'DB_UPDATE_VERSION',      1195      );
       
       /**
        * @brief Constant with a HTML line break.
      diff --git a/include/Contact.php b/include/Contact.php
      index d76c8f826c..79a14ab581 100644
      --- a/include/Contact.php
      +++ b/include/Contact.php
      @@ -555,60 +555,6 @@ function posts_from_gcontact($a, $gcontact_id) {
       	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
        *
      diff --git a/include/cron.php b/include/cron.php
      index e2f4102804..4eb76f1ce2 100644
      --- a/include/cron.php
      +++ b/include/cron.php
      @@ -35,6 +35,7 @@ function cron_run(&$argv, &$argc){
       	require_once('include/email.php');
       	require_once('include/socgraph.php');
       	require_once('mod/nodeinfo.php');
      +	require_once('include/post_update.php');
       
       	load_config('config');
       	load_config('system');
      @@ -106,8 +107,8 @@ function cron_run(&$argv, &$argc){
       	// Check every conversation
       	ostatus::check_conversations(false);
       
      -	// Set the gcontact-id in the item table if missing
      -	item_set_gcontact();
      +	// Do post update functions
      +	post_update();
       
       	// update nodeinfo data
       	nodeinfo_cron();
      diff --git a/include/dbstructure.php b/include/dbstructure.php
      index e34e409023..33e0c7dc8c 100644
      --- a/include/dbstructure.php
      +++ b/include/dbstructure.php
      @@ -783,6 +783,7 @@ function db_definition() {
       					"parent-uri" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
       					"extid" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
       					"thr-parent" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
      +					"shadow" => array("type" => "int(10) 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"),
      diff --git a/include/post_update.php b/include/post_update.php
      new file mode 100644
      index 0000000000..2143ac3d6f
      --- /dev/null
      +++ b/include/post_update.php
      @@ -0,0 +1,129 @@
      += 1192)
      +		return true;
      +
      +	// 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", 1192);
      +			return true;
      +		}
      +
      +		// 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 false;
      +}
      +
      +/**
      + * @brief Updates the "shadow" field in the item table
      + *
      + * @return bool "true" when the job is done
      + */
      +function post_update_1195() {
      +
      +	// Was the script completed?
      +	if (get_config("system", "post_update_version") >= 1195)
      +		return true;
      +
      +	$end_id = get_config("system", "post_update_1195_end");
      +	if (!$end_id) {
      +		$r = q("SELECT `id` FROM `item` WHERE `uid` != 0 ORDER BY `id` DESC LIMIT 1");
      +		if ($r) {
      +			set_config("system", "post_update_1195_end", $r[0]["id"]);
      +			$end_id = get_config("system", "post_update_1195_end");
      +		}
      +	}
      +
      +	$start_id = get_config("system", "post_update_1195_start");
      +
      +	$query1 = "SELECT `item`.`id` FROM `item` ";
      +
      +	$query2 = "INNER JOIN `item` AS `shadow` ON `item`.`uri` = `shadow`.`uri` AND `shadow`.`uid` = 0 ";
      +
      +	$query3 = "WHERE `item`.`uid` != 0 AND `item`.`id` >= %d AND `item`.`id` <= %d
      +			AND `item`.`visible` AND NOT `item`.`private`
      +			AND NOT `item`.`deleted` AND NOT `item`.`moderated`
      +			AND `item`.`network` IN ('%s', '%s', '%s', '')
      +			AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
      +			AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
      +			AND `item`.`shadow` = 0";
      +
      +	$r = q($query1.$query2.$query3."  ORDER BY `item`.`id` LIMIT 1",
      +		intval($start_id), intval($end_id),
      +		dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS));
      +	if (!$r) {
      +		set_config("system", "post_update_version", 1195);
      +		return true;
      +	} else {
      +		set_config("system", "post_update_1195_start", $r[0]["id"]);
      +		$start_id = get_config("system", "post_update_1195_start");
      +	}
      +
      +
      +	$r = q($query1.$query2.$query3."  ORDER BY `item`.`id` LIMIT 10000,1",
      +		intval($start_id), intval($end_id),
      +		dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS));
      +	if ($r)
      +		$pos_id = $r[0]["id"];
      +	else
      +		$pos_id = $end_id;
      +
      +	logger("Progress: Start: ".$start_id." position: ".$pos_id." end: ".$end_id, LOGGER_DEBUG);
      +
      +	$r = q("UPDATE `item` ".$query2." SET `item`.`shadow` = `shadow`.`id` ".$query3,
      +		intval($start_id), intval($pos_id),
      +		dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS));
      +}
      +?>
      diff --git a/include/threads.php b/include/threads.php
      index 0320eaa018..21fdb0df34 100644
      --- a/include/threads.php
      +++ b/include/threads.php
      @@ -65,13 +65,19 @@ function add_thread($itemid, $onlyshadow = false) {
       			require_once("include/Contact.php");
       
       			unset($item[0]['id']);
      +			unset($item[0]['shadow']);
       			$item[0]['uid'] = 0;
       			$item[0]['origin'] = 0;
       			$item[0]['contact-id'] = get_contact($item[0]['author-link'], 0);
       			$public_shadow = item_store($item[0], false, false, true);
       
       			logger("add_thread: Stored public shadow for post ".$itemid." under id ".$public_shadow, LOGGER_DEBUG);
      -		}
      +		} else
      +			$public_shadow = $r[0]["id"];
      +
      +		if ($public_shadow > 0)
      +			q("UPDATE `item` SET `shadow` = %d WHERE `id` = %d", intval($public_shadow), intval($itemid));
      +
       	}
       }
       
      diff --git a/update.php b/update.php
      index 0689aa19b3..dad94f271b 100644
      --- a/update.php
      +++ b/update.php
      @@ -1,6 +1,6 @@
       
      Date: Sun, 3 Apr 2016 16:36:05 +0200
      Subject: [PATCH 103/211] Post update ist now done.
      
      ---
       boot.php                |  2 +-
       include/dbstructure.php |  1 -
       include/items.php       |  4 ++--
       include/post_update.php | 40 ++++++++++++++++++++++++++--------------
       mod/search.php          |  5 ++---
       update.php              |  2 +-
       6 files changed, 32 insertions(+), 22 deletions(-)
      
      diff --git a/boot.php b/boot.php
      index 108d8d0ede..3ed88422e0 100644
      --- a/boot.php
      +++ b/boot.php
      @@ -38,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',      1195      );
      +define ( 'DB_UPDATE_VERSION',      1194      );
       
       /**
        * @brief Constant with a HTML line break.
      diff --git a/include/dbstructure.php b/include/dbstructure.php
      index 33e0c7dc8c..e34e409023 100644
      --- a/include/dbstructure.php
      +++ b/include/dbstructure.php
      @@ -783,7 +783,6 @@ function db_definition() {
       					"parent-uri" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
       					"extid" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
       					"thr-parent" => array("type" => "varchar(255)", "not null" => "1", "default" => ""),
      -					"shadow" => array("type" => "int(10) 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"),
      diff --git a/include/items.php b/include/items.php
      index 233d72d133..4627b10ca2 100644
      --- a/include/items.php
      +++ b/include/items.php
      @@ -707,9 +707,9 @@ function item_store($arr,$force_parent = false, $notify = false, $dontcache = fa
       	if ($arr["uid"] == 0) {
       		$arr["global"] = true;
       
      -		q("UPDATE `item` SET `global` = 1 WHERE `guid` = '%s'", dbesc($arr["guid"]));
      +		q("UPDATE `item` SET `global` = 1 WHERE `uri` = '%s'", dbesc($arr["uri"]));
       	}  else {
      -		$isglobal = q("SELECT `global` FROM `item` WHERE `uid` = 0 AND `guid` = '%s'", dbesc($arr["guid"]));
      +		$isglobal = q("SELECT `global` FROM `item` WHERE `uid` = 0 AND `uri` = '%s'", dbesc($arr["uri"]));
       
       		$arr["global"] = (count($isglobal) > 0);
       	}
      diff --git a/include/post_update.php b/include/post_update.php
      index 2143ac3d6f..2bdfe1f6fd 100644
      --- a/include/post_update.php
      +++ b/include/post_update.php
      @@ -1,4 +1,8 @@
       = 1195)
      +	if (get_config("system", "post_update_version") >= 1194)
       		return true;
       
      -	$end_id = get_config("system", "post_update_1195_end");
      +	logger("Start", LOGGER_DEBUG);
      +
      +	$end_id = get_config("system", "post_update_1194_end");
       	if (!$end_id) {
       		$r = q("SELECT `id` FROM `item` WHERE `uid` != 0 ORDER BY `id` DESC LIMIT 1");
       		if ($r) {
      -			set_config("system", "post_update_1195_end", $r[0]["id"]);
      -			$end_id = get_config("system", "post_update_1195_end");
      +			set_config("system", "post_update_1194_end", $r[0]["id"]);
      +			$end_id = get_config("system", "post_update_1194_end");
       		}
       	}
       
      -	$start_id = get_config("system", "post_update_1195_start");
      +	logger("End ID: ".$end_id, LOGGER_DEBUG);
      +
      +	$start_id = get_config("system", "post_update_1194_start");
       
       	$query1 = "SELECT `item`.`id` FROM `item` ";
       
      @@ -98,21 +106,23 @@ function post_update_1195() {
       			AND `item`.`network` IN ('%s', '%s', '%s', '')
       			AND `item`.`allow_cid` = '' AND `item`.`allow_gid` = ''
       			AND `item`.`deny_cid` = '' AND `item`.`deny_gid` = ''
      -			AND `item`.`shadow` = 0";
      +			AND NOT `item`.`global`";
       
       	$r = q($query1.$query2.$query3."  ORDER BY `item`.`id` LIMIT 1",
       		intval($start_id), intval($end_id),
       		dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS));
       	if (!$r) {
      -		set_config("system", "post_update_version", 1195);
      +		set_config("system", "post_update_version", 1194);
      +		logger("Update is done", LOGGER_DEBUG);
       		return true;
       	} else {
      -		set_config("system", "post_update_1195_start", $r[0]["id"]);
      -		$start_id = get_config("system", "post_update_1195_start");
      +		set_config("system", "post_update_1194_start", $r[0]["id"]);
      +		$start_id = get_config("system", "post_update_1194_start");
       	}
       
      +	logger("Start ID: ".$start_id, LOGGER_DEBUG);
       
      -	$r = q($query1.$query2.$query3."  ORDER BY `item`.`id` LIMIT 10000,1",
      +	$r = q($query1.$query2.$query3."  ORDER BY `item`.`id` LIMIT 1000,1",
       		intval($start_id), intval($end_id),
       		dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS));
       	if ($r)
      @@ -122,8 +132,10 @@ function post_update_1195() {
       
       	logger("Progress: Start: ".$start_id." position: ".$pos_id." end: ".$end_id, LOGGER_DEBUG);
       
      -	$r = q("UPDATE `item` ".$query2." SET `item`.`shadow` = `shadow`.`id` ".$query3,
      +	$r = q("UPDATE `item` ".$query2." SET `item`.`global` = 1 ".$query3,
       		intval($start_id), intval($pos_id),
       		dbesc(NETWORK_DFRN), dbesc(NETWORK_DIASPORA), dbesc(NETWORK_OSTATUS));
      +
      +	logger("Done", LOGGER_DEBUG);
       }
       ?>
      diff --git a/mod/search.php b/mod/search.php
      index 1776a92552..790f577ba6 100644
      --- a/mod/search.php
      +++ b/mod/search.php
      @@ -217,11 +217,10 @@ function search_content(&$a) {
       			FROM `item`
       				INNER JOIN `contact` ON `contact`.`id` = `item`.`contact-id` AND NOT `contact`.`blocked` AND NOT `contact`.`pending`
       			WHERE `item`.`visible` AND NOT `item`.`deleted` AND NOT `item`.`moderated`
      -				AND (`item`.`uid` = 0 OR (`item`.`uid` = %s AND (`item`.`private` OR NOT `item`.`network` IN ('%s', '%s', '%s'))))
      +				AND (`item`.`uid` = 0 OR (`item`.`uid` = %s AND NOT `item`.`global`))
       				$sql_extra
       			GROUP BY `item`.`uri` ORDER BY `item`.`id` DESC LIMIT %d , %d ",
      -				intval(local_user()), dbesc(NETWORK_DFRN), dbesc(NETWORK_OSTATUS), dbesc(NETWORK_DIASPORA),
      -				intval($a->pager['start']), intval($a->pager['itemspage']));
      +				intval(local_user()), intval($a->pager['start']), intval($a->pager['itemspage']));
       	}
       
       	if(! count($r)) {
      diff --git a/update.php b/update.php
      index dad94f271b..0689aa19b3 100644
      --- a/update.php
      +++ b/update.php
      @@ -1,6 +1,6 @@
       
      Date: Sun, 3 Apr 2016 16:38:32 +0200
      Subject: [PATCH 104/211] Some reverted stuff
      
      ---
       include/threads.php | 8 +-------
       1 file changed, 1 insertion(+), 7 deletions(-)
      
      diff --git a/include/threads.php b/include/threads.php
      index 21fdb0df34..0320eaa018 100644
      --- a/include/threads.php
      +++ b/include/threads.php
      @@ -65,19 +65,13 @@ function add_thread($itemid, $onlyshadow = false) {
       			require_once("include/Contact.php");
       
       			unset($item[0]['id']);
      -			unset($item[0]['shadow']);
       			$item[0]['uid'] = 0;
       			$item[0]['origin'] = 0;
       			$item[0]['contact-id'] = get_contact($item[0]['author-link'], 0);
       			$public_shadow = item_store($item[0], false, false, true);
       
       			logger("add_thread: Stored public shadow for post ".$itemid." under id ".$public_shadow, LOGGER_DEBUG);
      -		} else
      -			$public_shadow = $r[0]["id"];
      -
      -		if ($public_shadow > 0)
      -			q("UPDATE `item` SET `shadow` = %d WHERE `id` = %d", intval($public_shadow), intval($itemid));
      -
      +		}
       	}
       }
       
      
      From 761dac7ccd7219701971cea923cf2a39a7b26aec Mon Sep 17 00:00:00 2001
      From: Michael Vogel 
      Date: Sun, 3 Apr 2016 18:18:36 +0200
      Subject: [PATCH 105/211] issue 2247: Update more fields when relocation the
       server
      
      ---
       mod/admin.php | 15 ++++++++++++---
       1 file changed, 12 insertions(+), 3 deletions(-)
      
      diff --git a/mod/admin.php b/mod/admin.php
      index 28c8ed15c2..2fc9c48a78 100644
      --- a/mod/admin.php
      +++ b/mod/admin.php
      @@ -492,6 +492,10 @@ function admin_page_site_post(&$a) {
       
       		$old_url = $a->get_baseurl(true);
       
      +		// Generate host names for relocation the addresses in the format user@address.tld
      +		$new_host = str_replace("http://", "@", normalise_link($new_url));
      +		$old_host = str_replace("http://", "@", normalise_link($old_url));
      +
       		function update_table($table_name, $fields, $old_url, $new_url) {
       			global $db, $a;
       
      @@ -516,11 +520,16 @@ function admin_page_site_post(&$a) {
       		}
       
       		// update tables
      +		// update profile links in the format "http://server.tld"
       		update_table("profile", array('photo', 'thumb'), $old_url, $new_url);
       		update_table("term", array('url'), $old_url, $new_url);
      -		update_table("contact", array('photo','thumb','micro','url','nurl','request','notify','poll','confirm','poco'), $old_url, $new_url);
      -		update_table("gcontact", array('photo','url','nurl','server_url'), $old_url, $new_url);
      -		update_table("item", array('owner-link','owner-avatar','author-name','author-link','author-avatar','body','plink','tag'), $old_url, $new_url);
      +		update_table("contact", array('photo','thumb','micro','url','nurl','alias','request','notify','poll','confirm','poco', 'avatar'), $old_url, $new_url);
      +		update_table("gcontact", array('url','nurl','photo','server_url','notify','alias'), $old_url, $new_url);
      +		update_table("item", array('owner-link','owner-avatar','author-link','author-avatar','body','plink','tag'), $old_url, $new_url);
      +
      +		// update profile addresses in the format "user@server.tld"
      +		update_table("contact", array('addr'), $old_host, $new_host);
      +		update_table("gcontact", array('connect','addr'), $old_host, $new_host);
       
       		// update config
       		$a->set_baseurl($new_url);
      
      From af80d67f9cf9700b461d8949c15c1283c50e1887 Mon Sep 17 00:00:00 2001
      From: Michael Vogel 
      Date: Sun, 3 Apr 2016 18:49:41 +0200
      Subject: [PATCH 106/211] Some more documentation
      
      ---
       include/cron.php | 3 ++-
       1 file changed, 2 insertions(+), 1 deletion(-)
      
      diff --git a/include/cron.php b/include/cron.php
      index 4eb76f1ce2..00dd500704 100644
      --- a/include/cron.php
      +++ b/include/cron.php
      @@ -107,7 +107,8 @@ function cron_run(&$argv, &$argc){
       	// Check every conversation
       	ostatus::check_conversations(false);
       
      -	// Do post update functions
      +	// Call possible post update functions
      +	// see include/post_update.php for more details
       	post_update();
       
       	// update nodeinfo data
      
      From 71f93d0bdfef13ff0ed7d68ee71eb67117dc213e Mon Sep 17 00:00:00 2001
      From: Michael Vogel 
      Date: Sun, 3 Apr 2016 19:29:35 +0200
      Subject: [PATCH 107/211] Scrape: Changed detection for the profile link
      
      ---
       include/Scrape.php | 10 +++++++---
       1 file changed, 7 insertions(+), 3 deletions(-)
      
      diff --git a/include/Scrape.php b/include/Scrape.php
      index ac95c0d5d3..68926a997e 100644
      --- a/include/Scrape.php
      +++ b/include/Scrape.php
      @@ -705,10 +705,14 @@ function probe_url($url, $mode = PROBE_NORMAL, $level = 1) {
       					if (($vcard["nick"] == "") AND ($data["header"]["author-nick"] != ""))
       						$vcard["nick"] = $data["header"]["author-nick"];
       
      -					if (($network == NETWORK_OSTATUS) AND ($data["header"]["author-id"] != ""))
      -						$alias = $data["header"]["author-id"];
      +					if ($network == NETWORK_OSTATUS) {
      +						if ($data["header"]["author-id"] != "")
      +							$alias = $data["header"]["author-id"];
       
      -					if(!$profile AND ($data["header"]["author-link"] != "") AND !in_array($network, array("", NETWORK_FEED)))
      +						if ($data["header"]["author-link"] != "")
      +							$profile = $data["header"]["author-link"];
      +
      +					} elseif(!$profile AND ($data["header"]["author-link"] != "") AND !in_array($network, array("", NETWORK_FEED)))
       						$profile = $data["header"]["author-link"];
       				}
       			}
      
      From 9a6afaa30734cfcdd027a784df4385c6094185df Mon Sep 17 00:00:00 2001
      From: Tobias Diekershoff 
      Date: Sun, 3 Apr 2016 20:55:39 +0200
      Subject: [PATCH 108/211] set language in HTML header to selected UI language
      
      ---
       view/default.php | 2 +-
       1 file changed, 1 insertion(+), 1 deletion(-)
      
      diff --git a/view/default.php b/view/default.php
      index 121d5212c6..df9adbc392 100644
      --- a/view/default.php
      +++ b/view/default.php
      @@ -1,5 +1,5 @@
       
      -
      +
       
         <?php if(x($page,'title')) echo $page['title'] ?>
         
      
      From 5ce6d2c5bd450e4a1c94dba104329d0b29636cb9 Mon Sep 17 00:00:00 2001
      From: Tobias Diekershoff 
      Date: Sun, 3 Apr 2016 21:00:18 +0200
      Subject: [PATCH 109/211] added lang info to frost themes
      
      ---
       view/theme/frost-mobile/default.php | 2 +-
       view/theme/frost/default.php        | 2 +-
       2 files changed, 2 insertions(+), 2 deletions(-)
      
      diff --git a/view/theme/frost-mobile/default.php b/view/theme/frost-mobile/default.php
      index 3a424ad5f4..332291ca92 100644
      --- a/view/theme/frost-mobile/default.php
      +++ b/view/theme/frost-mobile/default.php
      @@ -1,5 +1,5 @@
       
      -
      +
       
         <?php if(x($page,'title')) echo $page['title'] ?>
         
      diff --git a/view/theme/frost/default.php b/view/theme/frost/default.php
      index c379955f79..c67bdcf20e 100644
      --- a/view/theme/frost/default.php
      +++ b/view/theme/frost/default.php
      @@ -1,5 +1,5 @@
       
      -
      +
       
         <?php if(x($page,'title')) echo $page['title'] ?>
         
      
      From bf3f31f701298b1cdc609ed4c12d0360cc242c6d Mon Sep 17 00:00:00 2001
      From: Tobias Diekershoff 
      Date: Mon, 4 Apr 2016 08:10:27 +0200
      Subject: [PATCH 110/211] added aria describedby elements refering to field
       help to the input templates
      
      ---
       view/templates/field_checkbox.tpl    | 4 ++--
       view/templates/field_combobox.tpl    | 4 ++--
       view/templates/field_input.tpl       | 4 ++--
       view/templates/field_intcheckbox.tpl | 4 ++--
       view/templates/field_openid.tpl      | 4 ++--
       view/templates/field_password.tpl    | 4 ++--
       view/templates/field_radio.tpl       | 4 ++--
       view/templates/field_richtext.tpl    | 4 ++--
       view/templates/field_select.tpl      | 4 ++--
       view/templates/field_select_raw.tpl  | 4 ++--
       view/templates/field_textarea.tpl    | 4 ++--
       view/templates/field_themeselect.tpl | 4 ++--
       view/templates/field_yesno.tpl       | 4 ++--
       view/templates/login.tpl             | 3 ++-
       14 files changed, 28 insertions(+), 27 deletions(-)
      
      diff --git a/view/templates/field_checkbox.tpl b/view/templates/field_checkbox.tpl
      index e476c07d72..06796376b6 100644
      --- a/view/templates/field_checkbox.tpl
      +++ b/view/templates/field_checkbox.tpl
      @@ -1,5 +1,5 @@
       	
      - - {{$field.3}} + + {{$field.3}}
      diff --git a/view/templates/field_combobox.tpl b/view/templates/field_combobox.tpl index a2f7c3f27e..4586550166 100644 --- a/view/templates/field_combobox.tpl +++ b/view/templates/field_combobox.tpl @@ -7,12 +7,12 @@ {{foreach $field.4 as $opt=>$val}}