]> git.mxchange.org Git - friendica.git/commitdiff
Merge pull request #7200 from annando/tag-process
authorPhilipp <admin+Github@philipp.info>
Tue, 28 May 2019 17:43:23 +0000 (19:43 +0200)
committerGitHub <noreply@github.com>
Tue, 28 May 2019 17:43:23 +0000 (19:43 +0200)
Process incoming tag add requests

43 files changed:
composer.json
composer.lock
doc/Addons.md
include/api.php
include/items.php
mod/cal.php
mod/events.php
mod/network.php
mod/notes.php
mod/photos.php
mod/uexport.php
mod/videos.php
mod/viewcontacts.php [deleted file]
src/App/Router.php
src/Content/Widget.php
src/Content/Widget/ContactBlock.php
src/Core/UserImport.php
src/Factory/LoggerFactory.php
src/Model/Contact.php
src/Model/Profile.php
src/Module/Admin/Logs/Settings.php
src/Module/Admin/Summary.php
src/Module/Contact.php
src/Module/Profile.php
src/Module/Profile/Contacts.php [new file with mode: 0644]
src/Protocol/ActivityPub/Processor.php
src/Protocol/ActivityPub/Transmitter.php
src/Protocol/DFRN.php
src/Protocol/OStatus.php
src/Util/Emailer.php
src/Util/JsonLD.php
view/templates/head.tpl
view/templates/posted_date_widget.tpl [deleted file]
view/templates/profile/contacts.tpl [new file with mode: 0644]
view/templates/widget/posted_date.tpl [new file with mode: 0644]
view/theme/duepuntozero/style.css
view/theme/frio/css/style.css
view/theme/frio/js/hovercard.js
view/theme/frio/js/mod_events.js
view/theme/frio/js/theme.js
view/theme/frio/templates/head.tpl
view/theme/frio/templates/profile/contacts.tpl [new file with mode: 0644]
view/theme/smoothly/style.css

index c1366c6771d205a1f93f57c6b8d902e68724913b..a8b4a20d28fc4463c1c677cef0bae3a60cfe463f 100644 (file)
@@ -46,6 +46,7 @@
                "fxp/composer-asset-plugin": "~1.3",
                "bower-asset/base64": "^1.0",
                "bower-asset/chart-js": "^2.7",
+               "bower-asset/dompurify": "^1.0",
                "bower-asset/perfect-scrollbar": "^0.6",
                "bower-asset/vue": "^2.5",
                "npm-asset/jquery": "^2.0",
index 2fe210f5df67d8ec6cc7229693a4312e2d3cfddc..de76b4589a3e8e290afeb6fee21eb890596b82fb 100644 (file)
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "d7302553201de079b72871c0b2922ce7",
+    "content-hash": "350fdeacf9fcc039538e00a9a943f6d6",
     "packages": [
         {
             "name": "asika/simple-console",
             "description": "Base64 encoding and decoding",
             "time": "2017-03-25T21:16:21+00:00"
         },
+        {
+            "name": "bower-asset/dompurify",
+            "version": "1.0.10",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/cure53/DOMPurify.git",
+                "reference": "b537cab466329b1b077e0e5e3c14edad2b7142f7"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/cure53/DOMPurify/zipball/b537cab466329b1b077e0e5e3c14edad2b7142f7",
+                "reference": "b537cab466329b1b077e0e5e3c14edad2b7142f7",
+                "shasum": ""
+            },
+            "type": "bower-asset-library",
+            "extra": {
+                "bower-asset-main": "src/purify.js",
+                "bower-asset-ignore": [
+                    "**/.*",
+                    "demos",
+                    "scripts",
+                    "test",
+                    "website"
+                ]
+            },
+            "license": [
+                "MPL-2.0",
+                "Apache-2.0"
+            ],
+            "description": "A DOM-only, super-fast, uber-tolerant XSS sanitizer for HTML, MathML and SVG",
+            "keywords": [
+                "cross site scripting",
+                "dom",
+                "filter",
+                "html",
+                "mathml",
+                "sanitize",
+                "sanitizer",
+                "secure",
+                "security",
+                "svg",
+                "xss"
+            ],
+            "time": "2019-02-19T13:27:01+00:00"
+        },
         {
             "name": "bower-asset/perfect-scrollbar",
             "version": "0.6.16",
             "require": {
                 "npm-asset/ev-emitter": ">=1.0.0,<2.0.0"
             },
+            "require-dev": {
+                "npm-asset/chalk": ">=1.1.1,<2.0.0",
+                "npm-asset/cheerio": ">=0.19.0,<0.20.0",
+                "npm-asset/gulp": ">=3.9.0,<4.0.0",
+                "npm-asset/gulp-jshint": ">=1.11.2,<2.0.0",
+                "npm-asset/gulp-json-lint": ">=0.1.0,<0.2.0",
+                "npm-asset/gulp-rename": ">=1.2.2,<2.0.0",
+                "npm-asset/gulp-replace": ">=0.5.4,<0.6.0",
+                "npm-asset/gulp-requirejs-optimize": "dev-github:metafizzy/gulp-requirejs-optimize",
+                "npm-asset/gulp-uglify": ">=1.4.2,<2.0.0",
+                "npm-asset/gulp-util": ">=3.0.7,<4.0.0",
+                "npm-asset/highlight.js": ">=8.9.1,<9.0.0",
+                "npm-asset/marked": ">=0.3.5,<0.4.0",
+                "npm-asset/minimist": ">=1.2.0,<2.0.0",
+                "npm-asset/transfob": ">=1.0.0,<2.0.0"
+            },
             "type": "npm-asset-library",
             "extra": {
                 "npm-asset-bugs": {
                 "reference": null,
                 "shasum": "2736e332aaee73ccf0a14a5f0066391a0a13f4a3"
             },
+            "require-dev": {
+                "npm-asset/grunt": "~0.4.2",
+                "npm-asset/grunt-contrib-cssmin": "~0.9.0",
+                "npm-asset/grunt-contrib-jshint": "~0.6.3",
+                "npm-asset/grunt-contrib-less": "~0.11.0",
+                "npm-asset/grunt-contrib-uglify": "~0.4.0",
+                "npm-asset/grunt-contrib-watch": "~0.6.1"
+            },
             "type": "npm-asset-library",
             "extra": {
                 "npm-asset-bugs": {
                 "reference": null,
                 "shasum": "2c89d6889b5eac522a7eea32c14521559c6cbf02"
             },
+            "require-dev": {
+                "npm-asset/commitplease": "2.0.0",
+                "npm-asset/core-js": "0.9.17",
+                "npm-asset/grunt": "0.4.5",
+                "npm-asset/grunt-babel": "5.0.1",
+                "npm-asset/grunt-cli": "0.1.13",
+                "npm-asset/grunt-compare-size": "0.4.0",
+                "npm-asset/grunt-contrib-jshint": "0.11.2",
+                "npm-asset/grunt-contrib-uglify": "0.9.2",
+                "npm-asset/grunt-contrib-watch": "0.6.1",
+                "npm-asset/grunt-git-authors": "2.0.1",
+                "npm-asset/grunt-jscs": "2.1.0",
+                "npm-asset/grunt-jsonlint": "1.0.4",
+                "npm-asset/grunt-npmcopy": "0.1.0",
+                "npm-asset/gzip-js": "0.3.2",
+                "npm-asset/jsdom": "5.6.1",
+                "npm-asset/load-grunt-tasks": "1.0.0",
+                "npm-asset/qunit-assert-step": "1.0.3",
+                "npm-asset/qunitjs": "1.17.1",
+                "npm-asset/requirejs": "2.1.17",
+                "npm-asset/sinon": "1.10.3",
+                "npm-asset/sizzle": "2.2.1",
+                "npm-asset/strip-json-comments": "1.0.3",
+                "npm-asset/testswarm": "1.1.0",
+                "npm-asset/win-spawn": "2.0.0"
+            },
             "type": "npm-asset-library",
             "extra": {
                 "npm-asset-bugs": {
                 "reference": null,
                 "shasum": "06f0335f16e353a695e7206bf50503cb523a6ee5"
             },
+            "require-dev": {
+                "npm-asset/grunt": "~0.4.1",
+                "npm-asset/grunt-contrib-connect": "~0.5.0",
+                "npm-asset/grunt-contrib-jshint": "~0.7.1",
+                "npm-asset/grunt-contrib-uglify": "~0.2.7"
+            },
             "type": "npm-asset-library",
             "extra": {
                 "npm-asset-bugs": {
                 }
             ],
             "description": "Provides the functionality to compare PHP values for equality",
-            "homepage": "http://www.github.com/sebastianbergmann/comparator",
+            "homepage": "https://github.com/sebastianbergmann/comparator",
             "keywords": [
                 "comparator",
                 "compare",
                 }
             ],
             "description": "Provides functionality to handle HHVM/PHP environments",
-            "homepage": "http://www.github.com/sebastianbergmann/environment",
+            "homepage": "https://github.com/sebastianbergmann/environment",
             "keywords": [
                 "Xdebug",
                 "environment",
                 }
             ],
             "description": "Provides the functionality to export PHP variables for visualization",
-            "homepage": "http://www.github.com/sebastianbergmann/exporter",
+            "homepage": "https://github.com/sebastianbergmann/exporter",
             "keywords": [
                 "export",
                 "exporter"
                 }
             ],
             "description": "Snapshotting of global state",
-            "homepage": "http://www.github.com/sebastianbergmann/global-state",
+            "homepage": "https://github.com/sebastianbergmann/global-state",
             "keywords": [
                 "global state"
             ],
                 }
             ],
             "description": "Provides functionality to recursively process PHP variables",
-            "homepage": "http://www.github.com/sebastianbergmann/recursion-context",
+            "homepage": "https://github.com/sebastianbergmann/recursion-context",
             "time": "2016-11-19T07:33:16+00:00"
         },
         {
index 858d64355f5fc33a3149679cdb2cbfd6eb48e2fc..47d16085a1216595c44914695fb4d122014ed62e 100644 (file)
@@ -358,6 +358,7 @@ Called from `Emailer::send()` before building the mime message.
 - **htmlVersion**: html version of the message
 - **textVersion**: text only version of the message
 - **additionalMailHeader**: additions to the smtp mail header
+- **sent**: default false, if set to true in the hook, the default mailer will be skipped.
 
 ### emailer_send
 Called before calling PHP's `mail()`.
@@ -367,6 +368,7 @@ Called before calling PHP's `mail()`.
 - **subject**
 - **body**
 - **headers**
+- **sent**: default false, if set to true in the hook, the default mailer will be skipped.
 
 ### load_config
 Called during `App` initialization to allow addons to load their own configuration file(s) with `App::loadConfigFile()`.
index eccd77675e91125ed3eae26191bb31b7e678ea07..0fab1f47c164e02a64e944494827ae2c6d8467c5 100644 (file)
@@ -361,7 +361,7 @@ function api_call(App $a)
                        }
                }
 
-               Logger::warning(API_LOG_PREFIX . 'not implemented', ['module' => 'api', 'action' => 'call']);
+               Logger::warning(API_LOG_PREFIX . 'not implemented', ['module' => 'api', 'action' => 'call', 'query' => $a->query_string]);
                throw new NotImplementedException();
        } catch (HTTPException $e) {
                header("HTTP/1.1 {$e->getCode()} {$e->httpdesc}");
@@ -611,7 +611,7 @@ function api_get_user(App $a, $contact_id = null)
                                'name' => $contact["name"],
                                'screen_name' => (($contact['nick']) ? $contact['nick'] : $contact['name']),
                                'location' => ($contact["location"] != "") ? $contact["location"] : ContactSelector::networkToName($contact['network'], $contact['url']),
-                               'description' => $contact["about"],
+                               'description' => HTML::toPlaintext(BBCode::toPlaintext($contact["about"])),
                                'profile_image_url' => $contact["micro"],
                                'profile_image_url_https' => $contact["micro"],
                                'profile_image_url_profile_size' => $contact["thumb"],
@@ -690,7 +690,7 @@ function api_get_user(App $a, $contact_id = null)
                'name' => (($uinfo[0]['name']) ? $uinfo[0]['name'] : $uinfo[0]['nick']),
                'screen_name' => (($uinfo[0]['nick']) ? $uinfo[0]['nick'] : $uinfo[0]['name']),
                'location' => $location,
-               'description' => $description,
+               'description' => HTML::toPlaintext(BBCode::toPlaintext($description)),
                'profile_image_url' => $uinfo[0]['micro'],
                'profile_image_url_https' => $uinfo[0]['micro'],
                'profile_image_url_profile_size' => $uinfo[0]["thumb"],
@@ -1271,7 +1271,7 @@ function api_status_show($type, $item_id)
 function api_get_last_status($ownerId, $uid)
 {
        $condition = [
-               'owner-id' => $ownerId,
+               'author-id'=> $ownerId,
                'uid'      => $uid,
                'gravity'  => [GRAVITY_PARENT, GRAVITY_COMMENT],
                'private'  => false
index a28e19a05a2cb11b9f3c8eeae17101dbb3f51c3c..25c857f1156a615a0efbbf914b0f815a37fb7fef 100644 (file)
@@ -446,80 +446,3 @@ function drop_item($id, $return = '')
                //NOTREACHED
        }
 }
-
-/* arrange the list in years */
-function list_post_dates($uid, $wall)
-{
-       $dnow = DateTimeFormat::localNow('Y-m-d');
-
-       $dthen = Item::firstPostDate($uid, $wall);
-       if (!$dthen) {
-               return [];
-       }
-
-       // Set the start and end date to the beginning of the month
-       $dnow = substr($dnow, 0, 8) . '01';
-       $dthen = substr($dthen, 0, 8) . '01';
-
-       $ret = [];
-
-       /*
-        * Starting with the current month, get the first and last days of every
-        * month down to and including the month of the first post
-        */
-       while (substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
-               $dyear = intval(substr($dnow, 0, 4));
-               $dstart = substr($dnow, 0, 8) . '01';
-               $dend = substr($dnow, 0, 8) . Temporal::getDaysInMonth(intval($dnow), intval(substr($dnow, 5)));
-               $start_month = DateTimeFormat::utc($dstart, 'Y-m-d');
-               $end_month = DateTimeFormat::utc($dend, 'Y-m-d');
-               $str = L10n::getDay(DateTimeFormat::utc($dnow, 'F'));
-
-               if (empty($ret[$dyear])) {
-                       $ret[$dyear] = [];
-               }
-
-               $ret[$dyear][] = [$str, $end_month, $start_month];
-               $dnow = DateTimeFormat::utc($dnow . ' -1 month', 'Y-m-d');
-       }
-       return $ret;
-}
-
-function posted_date_widget($url, $uid, $wall)
-{
-       $o = '';
-
-       if (!Feature::isEnabled($uid, 'archives')) {
-               return $o;
-       }
-
-       // For former Facebook folks that left because of "timeline"
-       /*
-        * @TODO old-lost code?
-       if ($wall && intval(PConfig::get($uid, 'system', 'no_wall_archive_widget')))
-               return $o;
-       */
-
-       $visible_years = PConfig::get($uid, 'system', 'archive_visible_years', 5);
-
-       $ret = list_post_dates($uid, $wall);
-
-       if (!DBA::isResult($ret)) {
-               return $o;
-       }
-
-       $cutoff_year = intval(DateTimeFormat::localNow('Y')) - $visible_years;
-       $cutoff = ((array_key_exists($cutoff_year, $ret))? true : false);
-
-       $o = Renderer::replaceMacros(Renderer::getMarkupTemplate('posted_date_widget.tpl'),[
-               '$title' => L10n::t('Archives'),
-               '$size' => $visible_years,
-               '$cutoff_year' => $cutoff_year,
-               '$cutoff' => $cutoff,
-               '$url' => $url,
-               '$dates' => $ret,
-               '$showmore' => L10n::t('show more')
-
-       ]);
-       return $o;
-}
index 3f3cba466f5d8cbc5c63f55ab6cc3809daa917ac..0a2a02e53c49e1613b8c02c6702c34aeb3f6afe6 100644 (file)
@@ -147,7 +147,7 @@ function cal_content(App $a)
        $sql_extra = " AND `event`.`cid` = 0 " . $sql_perms;
 
        // get the tab navigation bar
-       $tabs = Profile::getTabs($a, false, $a->data['user']['nickname']);
+       $tabs = Profile::getTabs($a, 'cal', false, $a->data['user']['nickname']);
 
        // The view mode part is similiar to /mod/events.php
        if ($mode == 'view') {
index 6569653a063f01779e411c2f84a696a7632e2a62..8e9606b203cb8e15b236a6c45f28e494ab409635 100644 (file)
@@ -246,7 +246,7 @@ function events_content(App $a)
        $tabs = '';
        // tabs
        if ($a->theme_events_in_profile) {
-               $tabs = Profile::getTabs($a, true);
+               $tabs = Profile::getTabs($a, 'events', true);
        }
 
        $mode = 'view';
index 30e31cea3c6ec579379b7b44884c90eb9b0ec05d..c8786b2f7b372b47fd5640e468fa77718e5d7e44 100644 (file)
@@ -154,7 +154,7 @@ function network_init(App $a)
 
        $a->page['aside'] .= Group::sidebarWidget('network/0', 'network', 'standard', $group_id);
        $a->page['aside'] .= ForumManager::widget(local_user(), $cid);
-       $a->page['aside'] .= posted_date_widget('network', local_user(), false);
+       $a->page['aside'] .= Widget::postedByYear('network', local_user(), false);
        $a->page['aside'] .= Widget::networks('network', defaults($_GET, 'nets', '') );
        $a->page['aside'] .= saved_searches($search);
        $a->page['aside'] .= Widget::fileAs('network', defaults($_GET, 'file', '') );
index fdb12d6cc5a6d0300aabe03671fad0759f69499e..1f67e486d6a6d06ab98bddb4116dd70874575611 100644 (file)
@@ -28,7 +28,7 @@ function notes_content(App $a, $update = false)
                return;
        }
 
-       $o = Profile::getTabs($a, true);
+       $o = Profile::getTabs($a, 'notes', true);
 
        if (!$update) {
                $o .= '<h3>' . L10n::t('Personal Notes') . '</h3>';
index 7c0ca1b7badc4465b7276052100ad6530a8db8cf..b18c06e2a3a4e5bf4b410f47b1a83f3c910b1245 100644 (file)
@@ -29,8 +29,8 @@ use Friendica\Util\Crypto;
 use Friendica\Util\DateTimeFormat;
 use Friendica\Util\Map;
 use Friendica\Util\Security;
-use Friendica\Util\Temporal;
 use Friendica\Util\Strings;
+use Friendica\Util\Temporal;
 use Friendica\Util\XML;
 
 function photos_init(App $a) {
@@ -315,7 +315,7 @@ function photos_post(App $a)
                $str_group_deny    = !empty($_POST['group_deny'])    ? perms2str($_POST['group_deny'])    : '';
                $str_contact_deny  = !empty($_POST['contact_deny'])  ? perms2str($_POST['contact_deny'])  : '';
 
-               $resource_id = $a->argv[2];
+               $resource_id = $a->argv[3];
 
                if (!strlen($albname)) {
                        $albname = DateTimeFormat::localNow('Y');
@@ -418,10 +418,11 @@ function photos_post(App $a)
 
                if ($item_id) {
                        $item = Item::selectFirst(['tag', 'inform'], ['id' => $item_id, 'uid' => $page_owner_uid]);
-               }
-               if (DBA::isResult($item)) {
-                       $old_tag    = $item['tag'];
-                       $old_inform = $item['inform'];
+
+                       if (DBA::isResult($item)) {
+                               $old_tag    = $item['tag'];
+                               $old_inform = $item['inform'];
+                       }
                }
 
                if (strlen($rawtags)) {
@@ -524,13 +525,13 @@ function photos_post(App $a)
                                }
                        }
 
-                       $newtag = $old_tag;
+                       $newtag = $old_tag ?? '';
                        if (strlen($newtag) && strlen($str_tags)) {
                                $newtag .= ',';
                        }
                        $newtag .= $str_tags;
 
-                       $newinform = $old_inform;
+                       $newinform = $old_inform ?? '';
                        if (strlen($newinform) && strlen($inform)) {
                                $newinform .= ',';
                        }
@@ -735,7 +736,7 @@ function photos_post(App $a)
                @unlink($src);
                $foo = 0;
                Hook::callAll('photo_post_end',$foo);
-               exit();
+               return;
        }
 
        $exif = $image->orient($src);
@@ -761,7 +762,7 @@ function photos_post(App $a)
        if (!$r) {
                Logger::log('mod/photos.php: photos_post(): image store failed', Logger::DEBUG);
                notice(L10n::t('Image upload failed.') . EOL);
-               exit();
+               return;
        }
 
        if ($width > 640 || $height > 640) {
@@ -950,7 +951,7 @@ function photos_content(App $a)
 
        // tabs
        $is_owner = (local_user() && (local_user() == $owner_uid));
-       $o .= Profile::getTabs($a, $is_owner, $a->data['user']['nickname']);
+       $o .= Profile::getTabs($a, 'photos', $is_owner, $a->data['user']['nickname']);
 
        // Display upload form
        if ($datatype === 'upload') {
index c91309e74c37fec9eb4077a25dda78defc6da4ca..dfeb25abd7bacf446d4d0e1e6bc68d91c018a780 100644 (file)
@@ -2,20 +2,27 @@
 /**
  * @file mod/uexport.php
  */
+
 use Friendica\App;
 use Friendica\Core\Hook;
 use Friendica\Core\L10n;
 use Friendica\Core\Renderer;
 use Friendica\Core\System;
 use Friendica\Database\DBA;
+use Friendica\Database\DBStructure;
 
 function uexport_init(App $a) {
+       /// @todo Don't forget to move this global field as static field in src/Modules
+       global $dbStructure;
+
        if (!local_user()) {
                exit();
        }
 
        require_once("mod/settings.php");
        settings_init($a);
+
+       $dbStructure = DBStructure::definition($a->getBasePath());
 }
 
 function uexport_content(App $a) {
@@ -55,13 +62,25 @@ function uexport_content(App $a) {
 }
 
 function _uexport_multirow($query) {
+       global $dbStructure;
+
+       preg_match("/\s+from\s+`?([a-z\d_]+)`?/i", $query, $match);
+       $table = $match[1];
+
        $result = [];
        $r = q($query);
        if (DBA::isResult($r)) {
                foreach ($r as $rr) {
                        $p = [];
                        foreach ($rr as $k => $v) {
-                               $p[$k] = $v;
+                               switch ($dbStructure[$table]['fields'][$k]['type']) {
+                                       case 'datetime':
+                                               $p[$k] = $v ?? DBA::NULL_DATETIME;
+                                               break;
+                                       default:
+                                               $p[$k] = $v;
+                                               break;
+                               }
                        }
                        $result[] = $p;
                }
@@ -70,12 +89,25 @@ function _uexport_multirow($query) {
 }
 
 function _uexport_row($query) {
+       global $dbStructure;
+
+       preg_match("/\s+from\s+`?([a-z\d_]+)`?/i", $query, $match);
+       $table = $match[1];
+
        $result = [];
        $r = q($query);
        if (DBA::isResult($r)) {
+
                foreach ($r as $rr) {
                        foreach ($rr as $k => $v) {
-                               $result[$k] = $v;
+                               switch ($dbStructure[$table]['fields'][$k]['type']) {
+                                       case 'datetime':
+                                               $result[$k] = $v ?? DBA::NULL_DATETIME;
+                                               break;
+                                       default:
+                                               $result[$k] = $v;
+                                               break;
+                               }
                        }
                }
        }
index 3fb36a73e4d319e20b04d8d634c03e3e44cf4436..9e19ecf11744d4d53da9b6d163095007919b2e52 100644 (file)
@@ -217,7 +217,7 @@ function videos_content(App $a)
 
        // tabs
        $_is_owner = (local_user() && (local_user() == $owner_uid));
-       $o .= Profile::getTabs($a, $_is_owner, $a->data['user']['nickname']);
+       $o .= Profile::getTabs($a, 'videos', $_is_owner, $a->data['user']['nickname']);
 
        //
        // dispatch request
diff --git a/mod/viewcontacts.php b/mod/viewcontacts.php
deleted file mode 100644 (file)
index 1491982..0000000
+++ /dev/null
@@ -1,120 +0,0 @@
-<?php
-/**
- * @file mod/viewcontacts.php
- */
-
-use Friendica\App;
-use Friendica\Content\ContactSelector;
-use Friendica\Content\Nav;
-use Friendica\Content\Pager;
-use Friendica\Core\Config;
-use Friendica\Core\L10n;
-use Friendica\Core\Protocol;
-use Friendica\Core\Renderer;
-use Friendica\Core\System;
-use Friendica\Database\DBA;
-use Friendica\Model\Contact;
-use Friendica\Model\Profile;
-use Friendica\Util\Proxy as ProxyUtils;
-
-function viewcontacts_init(App $a)
-{
-       if (Config::get('system', 'block_public') && !local_user() && !remote_user()) {
-               throw new \Friendica\Network\HTTPException\ForbiddenException(L10n::t('Access denied.'));
-       }
-
-       if ($a->argc < 2) {
-               throw new \Friendica\Network\HTTPException\ForbiddenException(L10n::t('Access denied.'));
-       }
-
-       Nav::setSelected('home');
-
-       $user = DBA::selectFirst('user', [], ['nickname' => $a->argv[1], 'blocked' => false]);
-       if (!DBA::isResult($user)) {
-               throw new \Friendica\Network\HTTPException\NotFoundException();
-       }
-
-       $a->data['user'] = $user;
-       $a->profile_uid  = $user['uid'];
-
-       Profile::load($a, $a->argv[1]);
-}
-
-function viewcontacts_content(App $a)
-{
-       if (Config::get('system', 'block_public') && !local_user() && !remote_user()) {
-               notice(L10n::t('Public access denied.') . EOL);
-               return;
-       }
-
-       $is_owner = $a->profile['profile_uid'] == local_user();
-
-       // tabs
-       $o = Profile::getTabs($a, $is_owner, $a->data['user']['nickname']);
-
-       if (!count($a->profile) || $a->profile['hide-friends']) {
-               notice(L10n::t('Permission denied.') . EOL);
-               return $o;
-       }
-
-       $condition = [
-               'uid'     => $a->profile['uid'],
-               'blocked' => false,
-               'pending' => false,
-               'hidden'  => false,
-               'archive' => false,
-               'network' => [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS]
-       ];
-
-       $total = DBA::count('contact', $condition);
-
-       $pager = new Pager($a->query_string);
-
-       $params = ['order' => ['name' => false], 'limit' => [$pager->getStart(), $pager->getItemsPerPage()]];
-
-       $contacts_stmt = DBA::select('contact', [], $condition, $params);
-
-       if (!DBA::isResult($contacts_stmt)) {
-               info(L10n::t('No contacts.') . EOL);
-               return $o;
-       }
-
-       $contacts = [];
-
-       while ($contact = DBA::fetch($contacts_stmt)) {
-               /// @TODO This triggers an E_NOTICE if 'self' is not there
-               if ($contact['self']) {
-                       continue;
-               }
-
-               $contact_details = Contact::getDetailsByURL($contact['url'], $a->profile['uid'], $contact);
-
-               $contacts[] = [
-                       'id'           => $contact['id'],
-                       'img_hover'    => L10n::t('Visit %s\'s profile [%s]', $contact_details['name'], $contact['url']),
-                       'photo_menu'   => Contact::photoMenu($contact),
-                       'thumb'        => ProxyUtils::proxifyUrl($contact_details['thumb'], false, ProxyUtils::SIZE_THUMB),
-                       'name'         => substr($contact_details['name'], 0, 20),
-                       'username'     => $contact_details['name'],
-                       'details'      => $contact_details['location'],
-                       'tags'         => $contact_details['keywords'],
-                       'about'        => $contact_details['about'],
-                       'account_type' => Contact::getAccountType($contact_details),
-                       'url'          => Contact::magicLink($contact['url']),
-                       'sparkle'      => '',
-                       'itemurl'      => (($contact_details['addr'] != "") ? $contact_details['addr'] : $contact['url']),
-                       'network'      => ContactSelector::networkToName($contact['network'], $contact['url']),
-               ];
-       }
-
-       DBA::close($contacts_stmt);
-
-       $tpl = Renderer::getMarkupTemplate("viewcontact_template.tpl");
-       $o .= Renderer::replaceMacros($tpl, [
-               '$title'    => L10n::t('Contacts'),
-               '$contacts' => $contacts,
-               '$paginate' => $pager->renderFull($total),
-       ]);
-
-       return $o;
-}
index be81c143d1aa845c32f520573077b3fd7170b565..715ac929ea97e50e622f65873ceb6d85fda4cb0c 100644 (file)
@@ -187,6 +187,8 @@ class Router
                $this->routeCollector->addRoute(['GET'],         '/probe',               Module\Debug\Probe::class);
                $this->routeCollector->addGroup('/profile', function (RouteCollector $collector) {
                        $collector->addRoute(['GET'], '/{nickname}',                         Module\Profile::class);
+                       $collector->addRoute(['GET'], '/{nickname}/{to:\d{4}-\d{2}-\d{2}}/{from:\d{4}-\d{2}-\d{2}}', Module\Profile::class);
+                       $collector->addRoute(['GET'], '/{nickname}/contacts[/{type}]',       Module\Profile\Contacts::class);
                        $collector->addRoute(['GET'], '/{profile:\d+}/view',                 Module\Profile::class);
                });
                $this->routeCollector->addGroup('/proxy', function (RouteCollector $collector) {
index b5f83a803e1e12ccdab0af78c0f7ef0dd4d3feaf..066c17413a3a8db9b56da401f1b5e481a339cb08 100644 (file)
@@ -15,9 +15,12 @@ use Friendica\Database\DBA;
 use Friendica\Model\Contact;
 use Friendica\Model\FileTag;
 use Friendica\Model\GContact;
+use Friendica\Model\Item;
 use Friendica\Model\Profile;
+use Friendica\Util\DateTimeFormat;
 use Friendica\Util\Proxy as ProxyUtils;
 use Friendica\Util\Strings;
+use Friendica\Util\Temporal;
 use Friendica\Util\XML;
 
 class Widget
@@ -121,17 +124,28 @@ class Widget
        }
 
        /**
-        * @param string $type
+        * Display a generic filter widget based on a list of options
+        *
+        * The options array must be the following format:
+        * [
+        *    [
+        *      'ref' => {filter value},
+        *      'name' => {option name}
+        *    ],
+        *    ...
+        * ]
+        *
+        * @param string $type The filter query string key
         * @param string $title
         * @param string $desc
-        * @param string $all
-        * @param string $baseUrl
+        * @param string $all The no filter label
+        * @param string $baseUrl The full page request URI
         * @param array  $options
-        * @param string $selected
+        * @param string $selected The currently selected filter option value
         * @return string
         * @throws \Exception
         */
-       public static function filter($type, $title, $desc, $all, $baseUrl, array $options, $selected = null)
+       private static function filter($type, $title, $desc, $all, $baseUrl, array $options, $selected = null)
        {
                $queryString = parse_url($baseUrl, PHP_URL_QUERY);
                $queryArray = [];
@@ -160,6 +174,37 @@ class Widget
                ]);
        }
 
+       /**
+        * Return networks widget
+        *
+        * @param string $baseurl  baseurl
+        * @param string $selected optional, default empty
+        * @return string
+        * @throws \Exception
+        */
+       public static function contactRels($baseurl, $selected = '')
+       {
+               if (!local_user()) {
+                       return '';
+               }
+
+               $options = [
+                       ['ref' => 'followers', 'name' => L10n::t('Followers')],
+                       ['ref' => 'following', 'name' => L10n::t('Following')],
+                       ['ref' => 'mutuals', 'name' => L10n::t('Mutual friends')],
+               ];
+
+               return self::filter(
+                       'rel',
+                       L10n::t('Relationships'),
+                       '',
+                       L10n::t('All Contacts'),
+                       $baseurl,
+                       $options,
+                       $selected
+               );
+       }
+
        /**
         * Return networks widget
         *
@@ -402,4 +447,74 @@ class Widget
 
                return '';
        }
+
+       /**
+        * @param string $url Base page URL
+        * @param int    $uid User ID consulting/publishing posts
+        * @param bool   $wall True: Posted by User; False: Posted to User (network timeline)
+        * @return string
+        * @throws \Friendica\Network\HTTPException\InternalServerErrorException
+        */
+       public static function postedByYear(string $url, int $uid, bool $wall)
+       {
+               $o = '';
+
+               if (!Feature::isEnabled($uid, 'archives')) {
+                       return $o;
+               }
+
+               $visible_years = PConfig::get($uid, 'system', 'archive_visible_years', 5);
+
+               /* arrange the list in years */
+               $dnow = DateTimeFormat::localNow('Y-m-d');
+
+               $ret = [];
+
+               $dthen = Item::firstPostDate($uid, $wall);
+               if ($dthen) {
+                       // Set the start and end date to the beginning of the month
+                       $dnow = substr($dnow, 0, 8) . '01';
+                       $dthen = substr($dthen, 0, 8) . '01';
+
+                       /*
+                        * Starting with the current month, get the first and last days of every
+                        * month down to and including the month of the first post
+                        */
+                       while (substr($dnow, 0, 7) >= substr($dthen, 0, 7)) {
+                               $dyear = intval(substr($dnow, 0, 4));
+                               $dstart = substr($dnow, 0, 8) . '01';
+                               $dend = substr($dnow, 0, 8) . Temporal::getDaysInMonth(intval($dnow), intval(substr($dnow, 5)));
+                               $start_month = DateTimeFormat::utc($dstart, 'Y-m-d');
+                               $end_month = DateTimeFormat::utc($dend, 'Y-m-d');
+                               $str = L10n::getDay(DateTimeFormat::utc($dnow, 'F'));
+
+                               if (empty($ret[$dyear])) {
+                                       $ret[$dyear] = [];
+                               }
+
+                               $ret[$dyear][] = [$str, $end_month, $start_month];
+                               $dnow = DateTimeFormat::utc($dnow . ' -1 month', 'Y-m-d');
+                       }
+               }
+
+               if (!DBA::isResult($ret)) {
+                       return $o;
+               }
+
+
+               $cutoff_year = intval(DateTimeFormat::localNow('Y')) - $visible_years;
+               $cutoff = array_key_exists($cutoff_year, $ret);
+
+               $o = Renderer::replaceMacros(Renderer::getMarkupTemplate('widget/posted_date.tpl'),[
+                       '$title' => L10n::t('Archives'),
+                       '$size' => $visible_years,
+                       '$cutoff_year' => $cutoff_year,
+                       '$cutoff' => $cutoff,
+                       '$url' => $url,
+                       '$dates' => $ret,
+                       '$showmore' => L10n::t('show more')
+               ]);
+
+               return $o;
+       }
 }
index f4fdea2fb4c3b6be90212defe83ddadf87d1e19d..bc33b9c9c928672a24de0d9712bf0a52562a0c73 100644 (file)
@@ -52,7 +52,7 @@ class ContactBlock
                        'pending' => false,
                        'hidden' => false,
                        'archive' => false,
-                       'network' => [Protocol::DFRN, Protocol::ACTIVITYPUB, Protocol::OSTATUS, Protocol::DIASPORA],
+                       'network' => [Protocol::DFRN, Protocol::ACTIVITYPUB, Protocol::OSTATUS, Protocol::DIASPORA, Protocol::FEED],
                ]);
 
                $contacts_title = L10n::t('No contacts');
index 0a4223fecdf5f45241051631dcb23a0c5d10aab2..97a6e6028e3b4905e341506a5f4d540ccbbd219d 100644 (file)
@@ -39,14 +39,21 @@ class UserImport
                $tableColumns = DBStructure::getColumns($table);
 
                $tcols = [];
+               $ttype = [];
                // get a plain array of column names
                foreach ($tableColumns as $tcol) {
                        $tcols[] = $tcol['Field'];
+                       $ttype[$tcol['Field']] = $tcol['Type'];
                }
                // remove inexistent columns
                foreach ($arr as $icol => $ival) {
                        if (!in_array($icol, $tcols)) {
                                unset($arr[$icol]);
+                               continue;
+                       }
+
+                       if ($ttype[$icol] === 'datetime') {
+                               $arr[$icol] = $ival ?? DBA::NULL_DATETIME;
                        }
                }
        }
index 444a98cde5c48300b1e15df77037ac8d1991da70..bdd85cf3ae3e1d48aeeb221a9ccb33f2635abd89 100644 (file)
@@ -26,6 +26,7 @@ class LoggerFactory
 {
        /**
         * A list of classes, which shouldn't get logged
+        *
         * @var array
         */
        private static $ignoreClassList = [
@@ -37,8 +38,8 @@ class LoggerFactory
        /**
         * Creates a new PSR-3 compliant logger instances
         *
-        * @param string        $channel The channel of the logger instance
-        * @param Configuration $config  The config
+        * @param string        $channel  The channel of the logger instance
+        * @param Configuration $config   The config
         * @param Profiler      $profiler The profiler of the app
         *
         * @return LoggerInterface The PSR-3 compliant logger instance
@@ -55,8 +56,8 @@ class LoggerFactory
                }
 
                $introspection = new Introspection(self::$ignoreClassList);
-               $level = $config->get('system', 'loglevel');
-               $loglevel = self::mapLegacyConfigDebugLevel((string)$level);
+               $level         = $config->get('system', 'loglevel');
+               $loglevel      = self::mapLegacyConfigDebugLevel((string)$level);
 
                switch ($config->get('system', 'logger_config', 'stream')) {
                        case 'monolog':
@@ -71,7 +72,10 @@ class LoggerFactory
 
                                $stream = $config->get('system', 'logfile');
 
-                               static::addStreamHandler($logger, $stream, $loglevel);
+                               // just add a stream in case it's either writable or not file
+                               if (!is_file($stream) || is_writable($stream)) {
+                                       static::addStreamHandler($logger, $stream, $loglevel);
+                               }
                                break;
 
                        case 'syslog':
@@ -81,7 +85,12 @@ class LoggerFactory
                        case 'stream':
                        default:
                                $stream = $config->get('system', 'logfile');
-                               $logger = new StreamLogger($channel, $stream, $introspection, $loglevel);
+                               // just add a stream in case it's either writable or not file
+                               if (!is_file($stream) || is_writable($stream)) {
+                                       $logger = new StreamLogger($channel, $stream, $introspection, $loglevel);
+                               } else {
+                                       $logger = new VoidLogger();
+                               }
                                break;
                }
 
@@ -105,8 +114,8 @@ class LoggerFactory
         *
         * It should never get filled during normal usage of Friendica
         *
-        * @param string        $channel The channel of the logger instance
-        * @param Configuration $config  The config
+        * @param string        $channel  The channel of the logger instance
+        * @param Configuration $config   The config
         * @param Profiler      $profiler The profiler of the app
         *
         * @return LoggerInterface The PSR-3 compliant logger instance
@@ -120,7 +129,8 @@ class LoggerFactory
                $stream      = $config->get('system', 'dlogfile');
                $developerIp = $config->get('system', 'dlogip');
 
-               if (!isset($developerIp) || !$debugging) {
+               if ((!isset($developerIp) || !$debugging) &&
+                   (!is_file($stream) || is_writable($stream))) {
                        $logger = new VoidLogger();
                        Logger::setDevLogger($logger);
                        return $logger;
@@ -149,7 +159,7 @@ class LoggerFactory
                                break;
 
                        case 'syslog':
-                               $logger = new SyslogLogger($channel, $introspection,  LogLevel::DEBUG);
+                               $logger = new SyslogLogger($channel, $introspection, LogLevel::DEBUG);
                                break;
 
                        case 'stream':
@@ -172,6 +182,7 @@ class LoggerFactory
 
        /**
         * Mapping a legacy level to the PSR-3 compliant levels
+        *
         * @see https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-3-logger-interface.md#5-psrlogloglevel
         *
         * @param string $level the level to be mapped
@@ -208,9 +219,9 @@ class LoggerFactory
        /**
         * Adding a handler to a given logger instance
         *
-        * @param LoggerInterface $logger  The logger instance
-        * @param mixed           $stream  The stream which handles the logger output
-        * @param string          $level   The level, for which this handler at least should handle logging
+        * @param LoggerInterface $logger The logger instance
+        * @param mixed           $stream The stream which handles the logger output
+        * @param string          $level  The level, for which this handler at least should handle logging
         *
         * @return void
         *
index ce3aeac5608f357891ec14fb865c4f62541b13bd..a0bbdd10499f43abbf6138d976c7a145e6d66402 100644 (file)
@@ -2120,17 +2120,33 @@ class Contact extends BaseObject
                return $contact;
        }
 
-       public static function addRelationship($importer, $contact, $datarray, $item = '', $sharing = false, $note = '') {
+       /**
+        * @param array  $importer Owner (local user) data
+        * @param array  $contact  Existing owner-specific contact data we want to expand the relationship with. Optional.
+        * @param array  $datarray An item-like array with at least the 'author-id' and 'author-url' keys for the contact. Mandatory.
+        * @param bool   $sharing  True: Contact is now sharing with Owner; False: Contact is now following Owner (default)
+        * @param string $note     Introduction additional message
+        * @return bool|null True: follow request is accepted; False: relationship is rejected; Null: relationship is pending
+        * @throws \Friendica\Network\HTTPException\InternalServerErrorException
+        * @throws \ImagickException
+        */
+       public static function addRelationship(array $importer, array $contact, array $datarray, $sharing = false, $note = '')
+       {
                // Should always be set
                if (empty($datarray['author-id'])) {
-                       return;
+                       return false;
                }
 
-               $fields = ['url', 'name', 'nick', 'photo', 'network'];
+               $fields = ['url', 'name', 'nick', 'photo', 'network', 'blocked'];
                $pub_contact = DBA::selectFirst('contact', $fields, ['id' => $datarray['author-id']]);
                if (!DBA::isResult($pub_contact)) {
                        // Should never happen
-                       return;
+                       return false;
+               }
+
+               // Contact is blocked at node-level
+               if (self::isBlocked($datarray['author-id'])) {
+                       return false;
                }
 
                $url = defaults($datarray, 'author-link', $pub_contact['url']);
@@ -2139,44 +2155,45 @@ class Contact extends BaseObject
                $nick = $pub_contact['nick'];
                $network = $pub_contact['network'];
 
-               if (is_array($contact)) {
+               if (!empty($contact)) {
+            // Contact is blocked at user-level
+                   if (self::isBlockedByUser($contact['id'], $importer['id'])) {
+                       return false;
+            }
+
                        // Make sure that the existing contact isn't archived
                        self::unmarkForArchival($contact);
 
-                       $protocol = self::getProtocol($url, $contact['network']);
-
                        if (($contact['rel'] == self::SHARING)
                                || ($sharing && $contact['rel'] == self::FOLLOWER)) {
                                DBA::update('contact', ['rel' => self::FRIEND, 'writable' => true, 'pending' => false],
                                                ['id' => $contact['id'], 'uid' => $importer['uid']]);
                        }
 
-                       if ($protocol == Protocol::ACTIVITYPUB) {
-                               ActivityPub\Transmitter::sendContactAccept($contact['url'], $contact['hub-verify'], $importer['uid']);
-                       }
-
-                       // send email notification to owner?
+                       return true;
                } else {
-                       $protocol = self::getProtocol($url, $network);
-
+                       // send email notification to owner?
                        if (DBA::exists('contact', ['nurl' => Strings::normaliseLink($url), 'uid' => $importer['uid'], 'pending' => true])) {
                                Logger::log('ignoring duplicated connection request from pending contact ' . $url);
-                               return;
+                               return null;
                        }
+
                        // create contact record
-                       q("INSERT INTO `contact` (`uid`, `created`, `url`, `nurl`, `name`, `nick`, `photo`, `network`, `rel`,
-                               `blocked`, `readonly`, `pending`, `writable`)
-                               VALUES (%d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, 0, 0, 1, 1)",
-                               intval($importer['uid']),
-                               DBA::escape(DateTimeFormat::utcNow()),
-                               DBA::escape($url),
-                               DBA::escape(Strings::normaliseLink($url)),
-                               DBA::escape($name),
-                               DBA::escape($nick),
-                               DBA::escape($photo),
-                               DBA::escape($network),
-                               intval(self::FOLLOWER)
-                       );
+                       DBA::insert('contact', [
+                               'uid'      => $importer['uid'],
+                               'created'  => DateTimeFormat::utcNow(),
+                               'url'      => $url,
+                               'nurl'     => Strings::normaliseLink($url),
+                               'name'     => $name,
+                               'nick'     => $nick,
+                               'photo'    => $photo,
+                               'network'  => $network,
+                               'rel'      => self::FOLLOWER,
+                               'blocked'  => 0,
+                               'readonly' => 0,
+                               'pending'  => 1,
+                               'writable' => 1,
+                       ]);
 
                        $contact_record = [
                                'id' => DBA::lastInsertId(),
@@ -2220,20 +2237,16 @@ class Contact extends BaseObject
                                                'verb'         => ($sharing ? ACTIVITY_FRIEND : ACTIVITY_FOLLOW),
                                                'otype'        => 'intro'
                                        ]);
-
                                }
                        } elseif (DBA::isResult($user) && in_array($user['page-flags'], [User::PAGE_FLAGS_SOAPBOX, User::PAGE_FLAGS_FREELOVE, User::PAGE_FLAGS_COMMUNITY])) {
                                $condition = ['uid' => $importer['uid'], 'url' => $url, 'pending' => true];
                                DBA::update('contact', ['pending' => false], $condition);
 
-                               $contact = DBA::selectFirst('contact', ['url', 'network', 'hub-verify'], ['id' => $contact_record['id']]);
-                               $protocol = self::getProtocol($contact['url'], $contact['network']);
-
-                               if ($protocol == Protocol::ACTIVITYPUB) {
-                                       ActivityPub\Transmitter::sendContactAccept($contact['url'], $contact['hub-verify'], $importer['uid']);
-                               }
+                               return true;
                        }
                }
+
+               return null;
        }
 
        public static function removeFollower($importer, $contact, array $datarray = [], $item = "")
index a854b1c9e019d3c6c11e753eb1cd94a13aabf6bd..9068943dfe17dc0e58b65e012ced357db0eecfe0 100644 (file)
@@ -877,32 +877,35 @@ class Profile
                return '';
        }
 
-       public static function getTabs($a, $is_owner = false, $nickname = null)
+    /**
+     * @param App    $a
+     * @param string $current
+     * @param bool   $is_owner
+     * @param string $nickname
+     * @return string
+     * @throws \Friendica\Network\HTTPException\InternalServerErrorException
+     */
+       public static function getTabs(App $a, string $current, bool $is_owner, string $nickname = null)
        {
                if (is_null($nickname)) {
                        $nickname = $a->user['nickname'];
                }
 
-               $tab = false;
-               if (!empty($_GET['tab'])) {
-                       $tab = Strings::escapeTags(trim($_GET['tab']));
-               }
-
-               $url = System::baseUrl() . '/profile/' . $nickname;
+               $baseProfileUrl = System::baseUrl() . '/profile/' . $nickname;
 
                $tabs = [
                        [
                                'label' => L10n::t('Status'),
-                               'url'   => $url,
-                               'sel'   => !$tab && $a->argv[0] == 'profile' ? 'active' : '',
+                               'url'   => $baseProfileUrl,
+                               'sel'   => !$current ? 'active' : '',
                                'title' => L10n::t('Status Messages and Posts'),
                                'id'    => 'status-tab',
                                'accesskey' => 'm',
                        ],
                        [
                                'label' => L10n::t('Profile'),
-                               'url'   => $url . '/?tab=profile',
-                               'sel'   => $tab == 'profile' ? 'active' : '',
+                               'url'   => $baseProfileUrl . '/?tab=profile',
+                               'sel'   => $current == 'profile' ? 'active' : '',
                                'title' => L10n::t('Profile Details'),
                                'id'    => 'profile-tab',
                                'accesskey' => 'r',
@@ -910,7 +913,7 @@ class Profile
                        [
                                'label' => L10n::t('Photos'),
                                'url'   => System::baseUrl() . '/photos/' . $nickname,
-                               'sel'   => !$tab && $a->argv[0] == 'photos' ? 'active' : '',
+                               'sel'   => $current == 'photos' ? 'active' : '',
                                'title' => L10n::t('Photo Albums'),
                                'id'    => 'photo-tab',
                                'accesskey' => 'h',
@@ -918,7 +921,7 @@ class Profile
                        [
                                'label' => L10n::t('Videos'),
                                'url'   => System::baseUrl() . '/videos/' . $nickname,
-                               'sel'   => !$tab && $a->argv[0] == 'videos' ? 'active' : '',
+                               'sel'   => $current == 'videos' ? 'active' : '',
                                'title' => L10n::t('Videos'),
                                'id'    => 'video-tab',
                                'accesskey' => 'v',
@@ -930,7 +933,7 @@ class Profile
                        $tabs[] = [
                                'label' => L10n::t('Events'),
                                'url'   => System::baseUrl() . '/events',
-                               'sel'   => !$tab && $a->argv[0] == 'events' ? 'active' : '',
+                               'sel'   => $current == 'events' ? 'active' : '',
                                'title' => L10n::t('Events and Calendar'),
                                'id'    => 'events-tab',
                                'accesskey' => 'e',
@@ -941,7 +944,7 @@ class Profile
                        $tabs[] = [
                                'label' => L10n::t('Events'),
                                'url'   => System::baseUrl() . '/cal/' . $nickname,
-                               'sel'   => !$tab && $a->argv[0] == 'cal' ? 'active' : '',
+                               'sel'   => $current == 'cal' ? 'active' : '',
                                'title' => L10n::t('Events and Calendar'),
                                'id'    => 'events-tab',
                                'accesskey' => 'e',
@@ -952,7 +955,7 @@ class Profile
                        $tabs[] = [
                                'label' => L10n::t('Personal Notes'),
                                'url'   => System::baseUrl() . '/notes',
-                               'sel'   => !$tab && $a->argv[0] == 'notes' ? 'active' : '',
+                               'sel'   => $current == 'notes' ? 'active' : '',
                                'title' => L10n::t('Only You Can See This'),
                                'id'    => 'notes-tab',
                                'accesskey' => 't',
@@ -969,18 +972,18 @@ class Profile
                        ];
                }
 
-               if (!$is_owner && empty($a->profile['hide-friends'])) {
+               if ($is_owner || empty($a->profile['hide-friends'])) {
                        $tabs[] = [
                                'label' => L10n::t('Contacts'),
-                               'url'   => System::baseUrl() . '/viewcontacts/' . $nickname,
-                               'sel'   => !$tab && $a->argv[0] == 'viewcontacts' ? 'active' : '',
+                               'url'   => $baseProfileUrl . '/contacts',
+                               'sel'   => $current == 'contacts' ? 'active' : '',
                                'title' => L10n::t('Contacts'),
                                'id'    => 'viewcontacts-tab',
                                'accesskey' => 'k',
                        ];
                }
 
-               $arr = ['is_owner' => $is_owner, 'nickname' => $nickname, 'tab' => $tab, 'tabs' => $tabs];
+               $arr = ['is_owner' => $is_owner, 'nickname' => $nickname, 'tab' => $current, 'tabs' => $tabs];
                Hook::callAll('profile_tabs', $arr);
 
                $tpl = Renderer::getMarkupTemplate('common_tabs.tpl');
index d55618454f1a86c1cb5e4610eb81cc2db75d5f79..b9a5e3832d87b3d3dd6027a0c9cb1442a1eb723d 100644 (file)
@@ -22,6 +22,12 @@ class Settings extends BaseAdminModule
                        $debugging = !empty($_POST['debugging']);
                        $loglevel  = defaults($_POST, 'loglevel', LogLevel::ERROR);
 
+                       if (is_file($logfile) &&
+                       !is_writeable($logfile)) {
+                               notice(L10n::t('The logfile \'%s\' is not writable. No logging possible', $logfile));
+                               return;
+                       }
+
                        Config::set('system', 'logfile', $logfile);
                        Config::set('system', 'debugging', $debugging);
                        Config::set('system', 'loglevel', $loglevel);
index 98fd74fb32da214ab0af8d5b08c12a5dc4401a7c..6261ac75caf937057c7ffacf8906726e013d4b8e 100644 (file)
@@ -74,6 +74,23 @@ class Summary extends BaseAdminModule
                                $well_known, $well_known, $a->getBaseURL() . '/help/Install');
                }
 
+               // Check logfile permission
+               if (Config::get('system', 'debugging')) {
+                       $stream = Config::get('system', 'logfile');
+
+                       if (is_file($stream) &&
+                           !is_writeable($stream)) {
+                               $warningtext[] = L10n::t('The logfile \'%s\' is not writable. No logging possible', $stream);
+                       }
+
+                       $stream = Config::get('system', 'dlogfile');
+
+                       if (is_file($stream) &&
+                           !is_writeable($stream)) {
+                               $warningtext[] = L10n::t('The logfile \'%s\' is not writable. No logging possible', $stream);
+                       }
+               }
+
                // check legacy basepath settings
                $configLoader = new ConfigFileLoader($a->getBasePath(), $a->getMode());
                $configCache = new Config\Cache\ConfigCache();
index 8d50e964ea3620b147218a2b2d6d804abd1e1072..847455f498927f0d6444c5a88dcbc858cd780c18 100644 (file)
@@ -266,6 +266,7 @@ class Contact extends BaseModule
                $a = self::getApp();
 
                $nets = defaults($_GET, 'nets', '');
+               $rel  = defaults($_GET, 'rel' , '');
 
                if (empty($a->page['aside'])) {
                        $a->page['aside'] = '';
@@ -321,6 +322,7 @@ class Contact extends BaseModule
                        $findpeople_widget = '';
                        $follow_widget = '';
                        $networks_widget = '';
+                       $rel_widget = '';
                } else {
                        $vcard_widget = '';
                        $findpeople_widget = Widget::findPeople();
@@ -331,6 +333,7 @@ class Contact extends BaseModule
                        }
 
                        $networks_widget = Widget::networks($_SERVER['REQUEST_URI'], $nets);
+                       $rel_widget = Widget::contactRels($_SERVER['REQUEST_URI'], $rel);
                }
 
                if ($contact['uid'] != 0) {
@@ -339,7 +342,7 @@ class Contact extends BaseModule
                        $groups_widget = null;
                }
 
-               $a->page['aside'] .= $vcard_widget . $findpeople_widget . $follow_widget . $groups_widget . $networks_widget;
+               $a->page['aside'] .= $vcard_widget . $findpeople_widget . $follow_widget . $groups_widget . $networks_widget . $rel_widget;
 
                $tpl = Renderer::getMarkupTemplate('contacts-head.tpl');
                $a->page['htmlhead'] .= Renderer::replaceMacros($tpl, [
@@ -678,6 +681,7 @@ class Contact extends BaseModule
 
                $search = Strings::escapeTags(trim(defaults($_GET, 'search', '')));
                $nets   = Strings::escapeTags(trim(defaults($_GET, 'nets'  , '')));
+               $rel    = Strings::escapeTags(trim(defaults($_GET, 'rel'   , '')));
 
                $tabs = [
                        [
@@ -747,6 +751,12 @@ class Contact extends BaseModule
                        $sql_extra .= sprintf(" AND network = '%s' ", DBA::escape($nets));
                }
 
+               switch ($rel) {
+                       case 'followers': $sql_extra .= " AND `rel` IN (1, 3)"; break;
+                       case 'following': $sql_extra .= " AND `rel` IN (2, 3)"; break;
+                       case 'mutuals': $sql_extra .= " AND `rel` = 3"; break;
+               }
+
                $sql_extra .=  " AND NOT `deleted` ";
 
                $sql_extra2 = ((($sort_type > 0) && ($sort_type <= Model\Contact::FRIEND)) ? sprintf(" AND `rel` = %d ", intval($sort_type)) : '');
@@ -777,6 +787,13 @@ class Contact extends BaseModule
                        }
                }
 
+               switch ($rel) {
+                       case 'followers': $header = L10n::t('Followers'); break;
+                       case 'following': $header = L10n::t('Following'); break;
+                       case 'mutuals':   $header = L10n::t('Mutual friends'); break;
+                       default:          $header = L10n::t('Contacts');
+               }
+
                switch ($type) {
                        case 'blocked':  $header .= ' - ' . L10n::t('Blocked'); break;
                        case 'hidden':   $header .= ' - ' . L10n::t('Hidden'); break;
index fceea726b4cfe863d203673dbf11095810c0595b..afebede4d0dcdf155145cfa9c8c51d0340d6a943 100644 (file)
@@ -178,12 +178,9 @@ class Profile extends BaseModule
                }
 
                if (!$update) {
-                       $tab = false;
-                       if (!empty($_GET['tab'])) {
-                               $tab = Strings::escapeTags(trim($_GET['tab']));
-                       }
+            $tab = Strings::escapeTags(trim(defaults($_GET, 'tab', '')));
 
-                       $o .= ProfileModel::getTabs($a, $is_owner, $a->profile['nickname']);
+                       $o .= ProfileModel::getTabs($a, $tab, $is_owner, $a->profile['nickname']);
 
                        if ($tab === 'profile') {
                                $o .= ProfileModel::getAdvanced($a);
@@ -196,8 +193,8 @@ class Profile extends BaseModule
                        $commpage = $a->profile['page-flags'] == User::PAGE_FLAGS_COMMUNITY;
                        $commvisitor = $commpage && $remote_contact;
 
-                       $a->page['aside'] .= posted_date_widget(System::baseUrl(true) . '/profile/' . $a->profile['nickname'], $a->profile['profile_uid'], true);
-                       $a->page['aside'] .= Widget::categories(System::baseUrl(true) . '/profile/' . $a->profile['nickname'], (!empty($category) ? XML::escape($category) : ''));
+                       $a->page['aside'] .= Widget::postedByYear(System::baseUrl(true) . '/profile/' . $a->profile['nickname'], $a->profile['profile_uid'] ?? 0, true);
+                       $a->page['aside'] .= Widget::categories(System::baseUrl(true) . '/profile/' . $a->profile['nickname'], XML::escape($category));
                        $a->page['aside'] .= Widget::tagCloud();
 
                        if (Security::canWriteToUserWall($a->profile['profile_uid'])) {
diff --git a/src/Module/Profile/Contacts.php b/src/Module/Profile/Contacts.php
new file mode 100644 (file)
index 0000000..346622b
--- /dev/null
@@ -0,0 +1,136 @@
+<?php
+
+namespace Friendica\Module\Profile;
+
+use Friendica\BaseModule;
+use Friendica\Content\ContactSelector;
+use Friendica\Content\Nav;
+use Friendica\Content\Pager;
+use Friendica\Core\Config;
+use Friendica\Core\L10n;
+use Friendica\Core\Protocol;
+use Friendica\Core\Renderer;
+use Friendica\Database\DBA;
+use Friendica\Model\Contact;
+use Friendica\Model\Profile;
+use Friendica\Util\Proxy as ProxyUtils;
+
+class Contacts extends BaseModule
+{
+       public static function content()
+       {
+               if (Config::get('system', 'block_public') && !local_user() && !remote_user()) {
+                       throw new \Friendica\Network\HTTPException\NotFoundException(L10n::t('User not found.'));
+               }
+
+               $a = self::getApp();
+
+               //@TODO: Get value from router parameters
+               $nickname = $a->argv[1];
+               $type = defaults($a->argv, 3, 'all');
+
+               Nav::setSelected('home');
+
+               $user = DBA::selectFirst('user', [], ['nickname' => $nickname, 'blocked' => false]);
+               if (!DBA::isResult($user)) {
+                       throw new \Friendica\Network\HTTPException\NotFoundException(L10n::t('User not found.'));
+               }
+
+               $a->data['user'] = $user;
+               $a->profile_uid  = $user['uid'];
+
+               Profile::load($a, $nickname);
+
+               $is_owner = $a->profile['profile_uid'] == local_user();
+
+               // tabs
+               $o = Profile::getTabs($a, 'contacts', $is_owner, $nickname);
+
+               if (!count($a->profile) || $a->profile['hide-friends']) {
+                       notice(L10n::t('Permission denied.') . EOL);
+                       return $o;
+               }
+
+               $condition = [
+                       'uid'     => $a->profile['uid'],
+                       'blocked' => false,
+                       'pending' => false,
+                       'hidden'  => false,
+                       'archive' => false,
+                       'network' => [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS, Protocol::FEED]
+               ];
+
+               switch ($type) {
+                       case 'followers': $condition['rel'] = [1, 3]; break;
+                       case 'following': $condition['rel'] = [2, 3]; break;
+                       case 'mutuals': $condition['rel'] = 3; break;
+               }
+
+               $total = DBA::count('contact', $condition);
+
+               $pager = new Pager($a->query_string);
+
+               $params = ['order' => ['name' => false], 'limit' => [$pager->getStart(), $pager->getItemsPerPage()]];
+
+               $contacts_stmt = DBA::select('contact', [], $condition, $params);
+
+               if (!DBA::isResult($contacts_stmt)) {
+                       info(L10n::t('No contacts.') . EOL);
+                       return $o;
+               }
+
+               $contacts = [];
+
+               while ($contact = DBA::fetch($contacts_stmt)) {
+                       if ($contact['self']) {
+                               continue;
+                       }
+
+                       $contact_details = Contact::getDetailsByURL($contact['url'], $a->profile['uid'], $contact);
+
+                       $contacts[] = [
+                               'id'           => $contact['id'],
+                               'img_hover'    => L10n::t('Visit %s\'s profile [%s]', $contact_details['name'], $contact['url']),
+                               'photo_menu'   => Contact::photoMenu($contact),
+                               'thumb'        => ProxyUtils::proxifyUrl($contact_details['thumb'], false, ProxyUtils::SIZE_THUMB),
+                               'name'         => substr($contact_details['name'], 0, 20),
+                               'username'     => $contact_details['name'],
+                               'details'      => $contact_details['location'],
+                               'tags'         => $contact_details['keywords'],
+                               'about'        => $contact_details['about'],
+                               'account_type' => Contact::getAccountType($contact_details),
+                               'url'          => Contact::magicLink($contact['url']),
+                               'sparkle'      => '',
+                               'itemurl'      => $contact_details['addr'] ? : $contact['url'],
+                               'network'      => ContactSelector::networkToName($contact['network'], $contact['url']),
+                       ];
+               }
+
+               DBA::close($contacts_stmt);
+
+               switch ($type) {
+                       case 'followers':    $title = L10n::tt('Follower (%s)', 'Followers (%s)', $total); break;
+                       case 'following':    $title = L10n::tt('Following (%s)', 'Following (%s)', $total); break;
+                       case 'mutuals':      $title = L10n::tt('Mutual friend (%s)', 'Mutual friends (%s)', $total); break;
+
+                       case 'all': default: $title = L10n::tt('Contact (%s)', 'Contacts (%s)', $total); break;
+               }
+
+               $tpl = Renderer::getMarkupTemplate('profile/contacts.tpl');
+               $o .= Renderer::replaceMacros($tpl, [
+                       '$title'    => $title,
+                       '$nickname' => $nickname,
+                       '$type'     => $type,
+
+                       '$all_label' => L10n::t('All contacts'),
+                       '$followers_label' => L10n::t('Followers'),
+                       '$following_label' => L10n::t('Following'),
+                       '$mutuals_label' => L10n::t('Mutual friends'),
+
+                       '$contacts' => $contacts,
+                       '$paginate' => $pager->renderFull($total),
+               ]);
+
+               return $o;
+       }
+}
index a5f6607edcadda62f1d77fd5c7fb797e8955839e..9eb1506a2878deba0a71064205f189732960087d 100644 (file)
@@ -567,7 +567,7 @@ class Processor
                        DBA::update('contact', ['hub-verify' => $activity['id'], 'protocol' => Protocol::ACTIVITYPUB], ['id' => $cid]);
                        $contact = DBA::selectFirst('contact', [], ['id' => $cid, 'network' => Protocol::NATIVE_SUPPORT]);
                } else {
-                       $contact = false;
+                       $contact = [];
                }
 
                $item = ['author-id' => Contact::getIdForURL($activity['actor']),
@@ -578,7 +578,11 @@ class Processor
                // Ensure that the contact has got the right network type
                self::switchContact($item['author-id']);
 
-               Contact::addRelationship($owner, $contact, $item, '', false, $note);
+               $result = Contact::addRelationship($owner, $contact, $item, false, $note);
+               if ($result === true) {
+                       ActivityPub\Transmitter::sendContactAccept($item['author-link'], $item['author-id'], $owner['uid']);
+               }
+
                $cid = Contact::getIdForURL($activity['actor'], $uid);
                if (empty($cid)) {
                        return;
index 1a2c13533070440dbec2d5a7fd8e95102fe82cc7..06dbaee71f81f1c619a9e857dde8dd80c465cb01 100644 (file)
@@ -1567,13 +1567,16 @@ class Transmitter
                        'id' => System::baseUrl() . '/activity/' . System::createGUID(),
                        'type' => 'Accept',
                        'actor' => $owner['url'],
-                       'object' => ['id' => $id, 'type' => 'Follow',
+                       'object' => [
+                               'id' => (string)$id,
+                               'type' => 'Follow',
                                'actor' => $profile['url'],
-                               'object' => $owner['url']],
+                               'object' => $owner['url']
+                       ],
                        'instrument' => self::getService(),
                        'to' => [$profile['url']]];
 
-               Logger::log('Sending accept to ' . $target . ' for user ' . $uid . ' with id ' . $id, Logger::DEBUG);
+               Logger::debug('Sending accept to ' . $target . ' for user ' . $uid . ' with id ' . $id);
 
                $signed = LDSignature::sign($data, $owner);
                HTTPSignature::transmit($signed, $profile['inbox'], $uid);
@@ -1597,13 +1600,16 @@ class Transmitter
                        'id' => System::baseUrl() . '/activity/' . System::createGUID(),
                        'type' => 'Reject',
                        'actor' => $owner['url'],
-                       'object' => ['id' => $id, 'type' => 'Follow',
+                       'object' => [
+                               'id' => (string)$id,
+                               'type' => 'Follow',
                                'actor' => $profile['url'],
-                               'object' => $owner['url']],
+                               'object' => $owner['url']
+                       ],
                        'instrument' => self::getService(),
                        'to' => [$profile['url']]];
 
-               Logger::log('Sending reject to ' . $target . ' for user ' . $uid . ' with id ' . $id, Logger::DEBUG);
+               Logger::debug('Sending reject to ' . $target . ' for user ' . $uid . ' with id ' . $id);
 
                $signed = LDSignature::sign($data, $owner);
                HTTPSignature::transmit($signed, $profile['inbox'], $uid);
index f74b572949d3f140285cfdb5095ca9d9facbb38d..01159b1baeea7c8cfea232462be993b0c78acaa3 100644 (file)
@@ -2247,13 +2247,12 @@ class DFRN
                        // The functions below are partly used by ostatus.php as well - where we have this variable
                        $r = q("SELECT * FROM `contact` WHERE `id` = %d", intval($importer["id"]));
                        $contact = $r[0];
-                       $nickname = $contact["nick"];
 
                        // Big question: Do we need these functions? They were part of the "consume_feed" function.
                        // This function once was responsible for DFRN and OStatus.
                        if (activity_match($item["verb"], ACTIVITY_FOLLOW)) {
                                Logger::log("New follower");
-                               Contact::addRelationship($importer, $contact, $item, $nickname);
+                               Contact::addRelationship($importer, $contact, $item);
                                return false;
                        }
                        if (activity_match($item["verb"], ACTIVITY_UNFOLLOW)) {
@@ -2263,7 +2262,7 @@ class DFRN
                        }
                        if (activity_match($item["verb"], ACTIVITY_REQ_FRIEND)) {
                                Logger::log("New friend request");
-                               Contact::addRelationship($importer, $contact, $item, $nickname, true);
+                               Contact::addRelationship($importer, $contact, $item, true);
                                return false;
                        }
                        if (activity_match($item["verb"], ACTIVITY_UNFRIEND)) {
index dec5c4c80b021a572da0d951304a7234dcbab6bc..81e5c19db8cc42d73eaf2c5e7adc2f2f74c2cb53 100644 (file)
@@ -417,13 +417,6 @@ class OStatus
                                $author = self::fetchAuthor($xpath, $entry, $importer, $contact, $stored);
                        }
 
-                       $value = XML::getFirstNodeValue($xpath, 'atom:author/poco:preferredUsername/text()', $entry);
-                       if ($value != "") {
-                               $nickname = $value;
-                       } else {
-                               $nickname = $author["author-name"];
-                       }
-
                        $item = array_merge($header, $author);
 
                        $item["uri"] = XML::getFirstNodeValue($xpath, 'atom:id/text()', $entry);
@@ -463,7 +456,7 @@ class OStatus
                        }
 
                        if ($item["verb"] == ACTIVITY_FOLLOW) {
-                               Contact::addRelationship($importer, $contact, $item, $nickname);
+                               Contact::addRelationship($importer, $contact, $item);
                                continue;
                        }
 
@@ -1515,7 +1508,7 @@ class OStatus
                                $author->appendChild($urls);
                        }
 
-                       XML::addElement($doc, $author, "followers", "", ["url" => System::baseUrl()."/viewcontacts/".$owner["nick"]]);
+                       XML::addElement($doc, $author, "followers", "", ["url" => System::baseUrl() . "/profile/" . $owner["nick"] . "/contacts/followers"]);
                        XML::addElement($doc, $author, "statusnet:profile_info", "", ["local_id" => $owner["uid"]]);
 
                        if ($profile["publish"]) {
index 6a19e8e4587c929b80c0cf1895f187ad5383f033..4310046c234411b1f6fd6907f8613a4075ebc7c3 100644 (file)
@@ -32,10 +32,16 @@ class Emailer
         * @return bool
         * @throws \Friendica\Network\HTTPException\InternalServerErrorException
         */
-       public static function send($params)
+       public static function send(array $params)
        {
+               $params['sent'] = false;
+
                Hook::callAll('emailer_send_prepare', $params);
 
+               if ($params['sent']) {
+                       return true;
+               }
+
                $email_textonly = false;
                if (!empty($params['uid'])) {
                        $email_textonly = PConfig::get($params['uid'], "system", "email_textonly");
@@ -87,11 +93,16 @@ class Emailer
                        'subject' => $messageSubject,
                        'body' => $multipartMessageBody,
                        'headers' => $messageHeader,
-                       'parameters' => $sendmail_params
+                       'parameters' => $sendmail_params,
+                       'sent' => false,
                ];
 
                Hook::callAll("emailer_send", $hookdata);
 
+               if ($hookdata['sent']) {
+                       return true;
+               }
+
                $res = mail(
                        $hookdata['to'],
                        $hookdata['subject'],
index 69973f4febf7d95757189a7e36b8a3ffa4c04b10..926fa1437d46aa1cb4efac0c8d79fbdaf1527d2d 100644 (file)
@@ -68,9 +68,16 @@ class JsonLD
                }
                catch (Exception $e) {
                        $normalized = false;
-                       Logger::error('normalise error');
-                       // Sooner or later we should log some details as well - but currently this leads to memory issues
-                       // Logger::log('normalise error:' . substr(print_r($e, true), 0, 10000), Logger::DEBUG);
+                       $messages = [];
+                       $currentException = $e;
+                       do {
+                               $messages[] = $currentException->getMessage();
+                       } while($currentException = $currentException->getPrevious());
+
+                       Logger::warning('JsonLD normalize error');
+                       Logger::notice('JsonLD normalize error', ['messages' => $messages]);
+                       Logger::info('JsonLD normalize error', ['trace' => $e->getTraceAsString()]);
+                       Logger::debug('JsonLD normalize error', ['jsonobj' => $jsonobj]);
                }
 
                return $normalized;
index e02e4e4da2ede06fccbf8b67c0ab7c10b258c0f6..3d11f1ab8634b9550ef346fd0f72d110f46dc1bd 100644 (file)
@@ -44,6 +44,7 @@
 <script type="text/javascript" src="view/asset/imagesloaded/imagesloaded.pkgd.min.js"></script>
 <script type="text/javascript" src="view/js/acl.js" ></script>
 <script type="text/javascript" src="view/asset/base64/base64.min.js" ></script>
+<script type="text/javascript" src="view/asset/dompurify/dist/purify.min.js"></script>
 <script type="text/javascript" src="view/js/main.js" ></script>
 <script>
 
diff --git a/view/templates/posted_date_widget.tpl b/view/templates/posted_date_widget.tpl
deleted file mode 100644 (file)
index 7833aee..0000000
+++ /dev/null
@@ -1,50 +0,0 @@
-<script>
-
-function showHideDates() {
-       if( $('#posted-date-selector-drop').is(':visible')) {
-               $('#posted-date-selector-drop').hide();
-               $('#posted-date-collapse').html(window.showMore);
-
-       } else {
-               $('#posted-date-selector-drop').show();
-               $('#posted-date-collapse').html(window.showFewer);
-       }
-}
-
-function dateSubmit(dateurl) {
-       window.location.href = dateurl;
-}
-
-</script>
-
-
-<div id="datebrowse-sidebar" class="widget">
-       <h3>{{$title}}</h3>
-       <ul id="posted-date-selector" class="datebrowse-ul">
-               {{foreach $dates as $y => $arr}}
-
-               {{if $y == $cutoff_year}}
-               </ul>
-               <ul id="posted-date-selector-drop" class="datebrowse-ul" style="display: none;">
-               {{/if}} 
-
-               <li id="posted-date-selector-year-{{$y}}" class="tool">
-                       <a class="datebrowse-link" href="#" onclick="openClose('posted-date-selector-{{$y}}'); return false;">{{$y}}</a>
-               </li>
-               <li id="posted-date-selector-{{$y}}" class="tool posted-date-selector-months" style="display: none;">
-                       <ul class="datebrowse-ul">
-                               {{foreach $arr as $d}}
-                               <li class="tool">
-                                       <a class="datebrowse-link" href="#" onclick="dateSubmit('{{$url}}/{{$d.1}}/{{$d.2}}'); return false;">{{$d.0}}</a>
-                               </li>
-                               {{/foreach}}
-                       </ul>
-               </li>
-               {{/foreach}}
-       </ul>
-       {{if $cutoff}}
-       <ul class="datebrowse-ul">
-               <li onclick="showHideDates(); return false;" id="posted-date-collapse" class="fakelink tool">{{$showmore}}</li>
-       </ul>
-       {{/if}}
-</div>
diff --git a/view/templates/profile/contacts.tpl b/view/templates/profile/contacts.tpl
new file mode 100644 (file)
index 0000000..4e78a7a
--- /dev/null
@@ -0,0 +1,20 @@
+<div class="generic-page-wrapper">
+       {{include file="section_title.tpl"}}
+
+       <ul role="menubar" class="tabs">
+               <li role="menuitem"><a href="profile/{{$nickname}}/contacts" class="tab button{{if !$type || $type == 'all'}} active{{/if}}">{{$all_label}}</a></li>
+               <li role="menuitem"><a href="profile/{{$nickname}}/contacts/followers" class="tab button{{if $type == 'followers'}} active{{/if}}">{{$followers_label}}</a></li>
+               <li role="menuitem"><a href="profile/{{$nickname}}/contacts/following" class="tab button{{if $type == 'following'}} active{{/if}}">{{$following_label}}</a></li>
+               <li role="menuitem"><a href="profile/{{$nickname}}/contacts/mutuals" class="tab button{{if $type == 'mutuals'}} active{{/if}}">{{$mutuals_label}}</a></li>
+       </ul>
+
+       <div id="viewcontact_wrapper-{{$id}}">
+{{foreach $contacts as $contact}}
+               {{include file="contact_template.tpl"}}
+{{/foreach}}
+       </div>
+       <div class="clear"></div>
+       <div id="view-contact-end"></div>
+
+       {{$paginate nofilter}}
+</div>
diff --git a/view/templates/widget/posted_date.tpl b/view/templates/widget/posted_date.tpl
new file mode 100644 (file)
index 0000000..42c04d2
--- /dev/null
@@ -0,0 +1,43 @@
+<script>
+function showHideDates() {
+       if( $('#posted-date-selector-drop').is(':visible')) {
+               $('#posted-date-selector-drop').hide();
+               $('#posted-date-collapse').html(window.showMore);
+
+       } else {
+               $('#posted-date-selector-drop').show();
+               $('#posted-date-collapse').html(window.showFewer);
+       }
+}
+</script>
+
+<div id="datebrowse-sidebar" class="widget">
+       <h3>{{$title}}</h3>
+       <ul id="posted-date-selector" class="datebrowse-ul">
+               {{foreach $dates as $y => $arr}}
+
+               {{if $y == $cutoff_year}}
+               </ul>
+               <ul id="posted-date-selector-drop" class="datebrowse-ul" style="display: none;">
+               {{/if}} 
+
+               <li id="posted-date-selector-year-{{$y}}" class="tool">
+                       <a class="datebrowse-link" href="#" onclick="openClose('posted-date-selector-{{$y}}'); return false;">{{$y}}</a>
+               </li>
+               <li id="posted-date-selector-{{$y}}" class="tool posted-date-selector-months" style="display: none;">
+                       <ul class="datebrowse-ul">
+                               {{foreach $arr as $d}}
+                               <li class="tool">
+                                       <a class="datebrowse-link" href="{{$url}}/{{$d.1}}/{{$d.2}}">{{$d.0}}</a>
+                               </li>
+                               {{/foreach}}
+                       </ul>
+               </li>
+               {{/foreach}}
+       </ul>
+       {{if $cutoff}}
+       <ul class="datebrowse-ul">
+               <li onclick="showHideDates(); return false;" id="posted-date-collapse" class="fakelink tool">{{$showmore}}</li>
+       </ul>
+       {{/if}}
+</div>
index f586d6e04d740fe705200e2364a8d25502bdc38e..7e66570ad57cddd532e82669e4c2d49197892818 100644 (file)
@@ -680,10 +680,6 @@ input#dfrn-url {
        clear: both;
 }
 
-
-#viewcontacts {
-       margin-top: 15px;
-}
 #profile-edit-default-desc {
        color: #FF0000;
        border: 1px solid #FF8888;
index 4a72628352891da6a7c77a7e4e8fc3bce158012f..2a5706c85968c5098919904e444232b468510d2e 100644 (file)
@@ -2317,7 +2317,7 @@ ul.dropdown-menu li:hover {
 .manage-content-wrapper, .notes-content-wrapper,
 .message-content-wrapper, .apps-content-wrapper,
 #adminpage, .delegate-content-wrapper, .uexport-content-wrapper,
-.viewcontacts-content-wrapper, .dfrn_request-content-wrapper,
+.dfrn_request-content-wrapper,
 .friendica-content-wrapper, .credits-content-wrapper, .nogroup-content-wrapper,
 .profperm-content-wrapper, .invite-content-wrapper, .tos-content-wrapper,
 .fsuggest-content-wrapper {
@@ -3547,7 +3547,7 @@ section .profile-match-wrapper {
                right: 10px;
        }
 
-       .generic-page-wrapper, .profile_photo-content-wrapper, .videos-content-wrapper, .suggest-content-wrapper, .common-content-wrapper, .help-content-wrapper, .allfriends-content-wrapper, .match-content-wrapper, .dirfind-content-wrapper, .directory-content-wrapper, .manage-content-wrapper, .notes-content-wrapper, .message-content-wrapper, .apps-content-wrapper, #adminpage, .delegate-content-wrapper, .uexport-content-wrapper, .viewcontacts-content-wrapper, .dfrn_request-content-wrapper, .friendica-content-wrapper, .credits-content-wrapper, .nogroup-content-wrapper, .profperm-content-wrapper, .invite-content-wrapper, .tos-content-wrapper, .fsuggest-content-wrapper {
+       .generic-page-wrapper, .profile_photo-content-wrapper, .videos-content-wrapper, .suggest-content-wrapper, .common-content-wrapper, .help-content-wrapper, .allfriends-content-wrapper, .match-content-wrapper, .dirfind-content-wrapper, .directory-content-wrapper, .manage-content-wrapper, .notes-content-wrapper, .message-content-wrapper, .apps-content-wrapper, #adminpage, .delegate-content-wrapper, .uexport-content-wrapper, .dfrn_request-content-wrapper, .friendica-content-wrapper, .credits-content-wrapper, .nogroup-content-wrapper, .profperm-content-wrapper, .invite-content-wrapper, .tos-content-wrapper, .fsuggest-content-wrapper {
                border-radius: 0;
                padding: 10px;
        }
index c3396b7626aecc4eb2923d76d067008132066f48..4e6cc8f7bb56d45281f3427435e7f0dbd4e81f21 100644 (file)
@@ -55,7 +55,7 @@ $(document).ready(function(){
                                        var hctrigger = 'manual';
                        };
 
-                       // Timeoute until the hover-card does appear
+                       // Timeout until the hover-card does appear
                        setTimeout(function(){
                                if(targetElement.is(":hover") && parseInt(targetElement.attr('data-awaiting-hover-card'),10) == timeNow) {
                                        if($('.hovercard').length == 0) {       // no card if there already is one open
@@ -81,6 +81,9 @@ $(document).ready(function(){
                                                                        template: '<div class="popover hovercard" data-card-created="' + timeNow + '"><div class="arrow"></div><div class="popover-content hovercard-content"></div></div>',
                                                                        content: data,
                                                                        container: "body",
+                                                                       sanitizeFn: function (content) {
+                                                                               return DOMPurify.sanitize(content)
+                                                                       },
                                                                }).popover('show');
                                                        }
                                                });
index 9d10bbb8e7e25d16fb4a059c38c4e9f01f3df7d2..658987bdea236e28e80196ba36faff971768fc5b 100644 (file)
@@ -82,6 +82,9 @@ $(document).ready(function() {
                                trigger: "hover",
                                placement: "auto",
                                template: '<div class="popover hovercard event-card"><div class="arrow"></div><div class="popover-content hovercard-content"></div></div>',
+                               sanitizeFn: function (content) {
+                                       return DOMPurify.sanitize(content)
+                               },
                        });
 
                }
index d058cc4aa264e88812f48161b0fb6ff099f57a8f..594fa256346a96db8063cc5b5869c289a2020a2e 100644 (file)
@@ -106,7 +106,10 @@ $(document).ready(function(){
                delay: {
                        show: 500,
                        hide: 100
-               }
+               },
+               sanitizeFn: function (content) {
+                       return DOMPurify.sanitize(content)
+               },
        });
 
        // initialize the bootstrap-select
index b3c632b3d15ae6a3e02629dbc42842257a59bc13..97b150c1bdebe659b25754607ac918b5f53ec2ef 100644 (file)
@@ -65,6 +65,7 @@
 <script type="text/javascript" src="view/asset/imagesloaded/imagesloaded.pkgd.min.js"></script>
 <script type="text/javascript" src="view/js/acl.js"></script>
 <script type="text/javascript" src="view/asset/base64/base64.min.js"></script>
+<script type="text/javascript" src="view/asset/dompurify/dist/purify.min.js"></script>
 <script type="text/javascript" src="view/js/main.js"></script>
 
 <script type="text/javascript" src="view/theme/frio/frameworks/bootstrap/js/bootstrap.min.js"></script>
diff --git a/view/theme/frio/templates/profile/contacts.tpl b/view/theme/frio/templates/profile/contacts.tpl
new file mode 100644 (file)
index 0000000..69d34d0
--- /dev/null
@@ -0,0 +1,20 @@
+<div class="generic-page-wrapper">
+       {{include file="section_title.tpl"}}
+
+       <ul class="nav nav-tabs">
+               <li role="presentation"{{if !$type || $type == 'all'}} class="active"{{/if}}><a href="profile/{{$nickname}}/contacts">{{$all_label}}</a></li>
+               <li role="presentation"{{if $type == 'followers'}} class="active"{{/if}}><a href="profile/{{$nickname}}/contacts/followers">{{$followers_label}}</a></li>
+               <li role="presentation"{{if $type == 'following'}} class="active"{{/if}}><a href="profile/{{$nickname}}/contacts/following">{{$following_label}}</a></li>
+               <li role="presentation"{{if $type == 'mutuals'}} class="active"{{/if}}><a href="profile/{{$nickname}}/contacts/mutuals">{{$mutuals_label}}</a></li>
+       </ul>
+
+       <ul id="viewcontact_wrapper{{if $id}}-{{$id}}{{/if}}" class="viewcontact_wrapper media-list">
+{{foreach $contacts as $contact}}
+               <li>{{include file="contact_template.tpl"}}</li>
+{{/foreach}}
+       </ul>
+       <div class="clear"></div>
+       <div id="view-contact-end"></div>
+
+       {{$paginate nofilter}}
+</div>
index 9bbf00c78030af7d751e570a2d0e933a3c6aec0f..fec5d2bda57889c1d13e8c30062237e8ff94b4c6 100644 (file)
@@ -2834,10 +2834,6 @@ margin-left: 0px;
        clear: both;
 }
 
-#viewcontacts {
-       margin-top: 15px;
-}
-
 .contact-entry-wrapper .contact-entry-photo-wrapper {
        float: left;
        margin-right: 10px;