diff --git a/composer.json b/composer.json index 82ab4d93ba..7f4e0a9b88 100644 --- a/composer.json +++ b/composer.json @@ -32,12 +32,14 @@ "ezyang/htmlpurifier": "~4.7.0", "friendica/json-ld": "^1.0", "league/html-to-markdown": "~4.8.0", + "level-2/dice": ">1.0", "lightopenid/lightopenid": "dev-master", "michelf/php-markdown": "^1.7", "mobiledetect/mobiledetectlib": "2.8.*", "monolog/monolog": "^1.24", "nikic/fast-route": "^1.3", "paragonie/hidden-string": "^1.0", + "pear/console_table": "^1.3", "pear/text_languagedetect": "1.*", "pragmarx/google2fa": "^5.0", "pragmarx/recovery": "^0.1.0", @@ -58,8 +60,7 @@ "npm-asset/fullcalendar": "^3.0.1", "npm-asset/cropperjs": "1.2.2", "npm-asset/imagesloaded": "4.1.4", - "pear/console_table": "^1.3", - "level-2/dice": ">1.0" + "npm-asset/typeahead.js": "^0.11.1" }, "repositories": [ { diff --git a/composer.lock b/composer.lock index e1cefeed8c..5b9013a0cc 100644 --- a/composer.lock +++ b/composer.lock @@ -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": "9f101b2b4a651f425e155d0029223774", + "content-hash": "8c88c86ebce0bbf672356d65fadc0008", "packages": [ { "name": "asika/simple-console", @@ -1076,7 +1076,6 @@ "dist": { "type": "tar", "url": "https://registry.npmjs.org/cropperjs/-/cropperjs-1.2.2.tgz", - "reference": null, "shasum": "30dc7a7ce872155b23a33bd10ad4c76c0d613f55" }, "require-dev": { @@ -1170,7 +1169,6 @@ "dist": { "type": "tar", "url": "https://registry.npmjs.org/ev-emitter/-/ev-emitter-1.1.1.tgz", - "reference": null, "shasum": "8f18b0ce5c76a5d18017f71c0a795c65b9138f2a" }, "type": "npm-asset-library", @@ -1213,7 +1211,6 @@ "dist": { "type": "tar", "url": "https://registry.npmjs.org/fullcalendar/-/fullcalendar-3.10.0.tgz", - "reference": null, "shasum": "cc5e87d518fd6550e142816a31dd191664847919" }, "type": "npm-asset-library", @@ -1260,28 +1257,11 @@ "dist": { "type": "tar", "url": "https://registry.npmjs.org/imagesloaded/-/imagesloaded-4.1.4.tgz", - "reference": null, "shasum": "1376efcd162bb768c34c3727ac89cc04051f3cc7" }, "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": { @@ -1324,17 +1304,8 @@ "dist": { "type": "tar", "url": "https://registry.npmjs.org/jgrowl/-/jgrowl-1.4.6.tgz", - "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": { @@ -1365,35 +1336,8 @@ "dist": { "type": "tar", "url": "https://registry.npmjs.org/jquery/-/jquery-2.2.4.tgz", - "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": { @@ -1436,7 +1380,6 @@ "dist": { "type": "tar", "url": "https://registry.npmjs.org/jquery-colorbox/-/jquery-colorbox-1.6.4.tgz", - "reference": null, "shasum": "799452523a6c494839224ef702e807deb9c06cc5" }, "require": { @@ -1483,7 +1426,6 @@ "dist": { "type": "tar", "url": "https://registry.npmjs.org/jquery-datetimepicker/-/jquery-datetimepicker-2.5.20.tgz", - "reference": null, "shasum": "687d6204b90b03dc93f725f8df036e1d061f37ac" }, "require": { @@ -1541,15 +1483,8 @@ "dist": { "type": "tar", "url": "https://registry.npmjs.org/jquery-mousewheel/-/jquery-mousewheel-3.1.13.tgz", - "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": { @@ -1596,7 +1531,6 @@ "dist": { "type": "tar", "url": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz", - "reference": null, "shasum": "3c257f9839fc0e93ff53149632239eb90783ff66" }, "type": "npm-asset-library", @@ -1707,6 +1641,58 @@ "homepage": "https://github.com/kartik-v/php-date-formatter", "time": "2018-07-13T06:56:46+00:00" }, + { + "name": "npm-asset/typeahead.js", + "version": "0.11.1", + "dist": { + "type": "tar", + "url": "https://registry.npmjs.org/typeahead.js/-/typeahead.js-0.11.1.tgz", + "shasum": "4e64e671b22310a8606f4aec805924ba84b015b8" + }, + "require": { + "npm-asset/jquery": ">=1.7" + }, + "type": "npm-asset-library", + "extra": { + "npm-asset-bugs": { + "url": "https://github.com/twitter/typeahead.js/issues" + }, + "npm-asset-main": "dist/typeahead.bundle.js", + "npm-asset-directories": [], + "npm-asset-repository": { + "type": "git", + "url": "https://github.com/twitter/typeahead.js.git" + }, + "npm-asset-scripts": { + "test": "./node_modules/karma/bin/karma start --single-run --browsers PhantomJS" + } + }, + "authors": [ + { + "name": "Twitter, Inc.", + "url": "https://twitter.com/twitteross" + }, + { + "name": "Jake Harding", + "url": "https://twitter.com/JakeHarding" + }, + { + "name": "Tim Trueman", + "url": "https://twitter.com/timtrueman" + }, + { + "name": "Veljko Skarich", + "url": "https://twitter.com/vskarich" + } + ], + "description": "fast and fully-featured autocomplete library", + "homepage": "http://twitter.github.com/typeahead.js", + "keywords": [ + "autocomplete", + "typeahead" + ], + "time": "2015-04-27T04:03:42+00:00" + }, { "name": "paragonie/certainty", "version": "v1.0.4", @@ -2883,7 +2869,7 @@ "time": "2017-03-25T17:14:26+00:00" }, { - "name": "mikey179/vfsStream", + "name": "mikey179/vfsstream", "version": "v1.6.5", "source": { "type": "git", @@ -2920,8 +2906,8 @@ "authors": [ { "name": "Frank Kleine", - "homepage": "http://frankkleine.de/", - "role": "Developer" + "role": "Developer", + "homepage": "http://frankkleine.de/" } ], "description": "Virtual file system to mock the real file system in unit tests.", @@ -3353,8 +3339,8 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" + "role": "lead", + "email": "sb@sebastian-bergmann.de" } ], "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", @@ -3621,8 +3607,8 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "role": "lead", + "email": "sebastian@phpunit.de" } ], "description": "The PHP Unit Testing framework.", @@ -3795,7 +3781,7 @@ } ], "description": "Provides the functionality to compare PHP values for equality", - "homepage": "https://github.com/sebastianbergmann/comparator", + "homepage": "http://www.github.com/sebastianbergmann/comparator", "keywords": [ "comparator", "compare", @@ -3897,7 +3883,7 @@ } ], "description": "Provides functionality to handle HHVM/PHP environments", - "homepage": "https://github.com/sebastianbergmann/environment", + "homepage": "http://www.github.com/sebastianbergmann/environment", "keywords": [ "Xdebug", "environment", @@ -3965,7 +3951,7 @@ } ], "description": "Provides the functionality to export PHP variables for visualization", - "homepage": "https://github.com/sebastianbergmann/exporter", + "homepage": "http://www.github.com/sebastianbergmann/exporter", "keywords": [ "export", "exporter" @@ -4017,7 +4003,7 @@ } ], "description": "Snapshotting of global state", - "homepage": "https://github.com/sebastianbergmann/global-state", + "homepage": "http://www.github.com/sebastianbergmann/global-state", "keywords": [ "global state" ], @@ -4119,7 +4105,7 @@ } ], "description": "Provides functionality to recursively process PHP variables", - "homepage": "https://github.com/sebastianbergmann/recursion-context", + "homepage": "http://www.github.com/sebastianbergmann/recursion-context", "time": "2016-11-19T07:33:16+00:00" }, { @@ -4209,16 +4195,16 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.11.0", + "version": "v1.9.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "82ebae02209c21113908c229e9883c419720738a" + "reference": "e3d826245268269cd66f8326bd8bc066687b4a19" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/82ebae02209c21113908c229e9883c419720738a", - "reference": "82ebae02209c21113908c229e9883c419720738a", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e3d826245268269cd66f8326bd8bc066687b4a19", + "reference": "e3d826245268269cd66f8326bd8bc066687b4a19", "shasum": "" }, "require": { @@ -4230,7 +4216,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.11-dev" + "dev-master": "1.9-dev" } }, "autoload": { @@ -4263,20 +4249,20 @@ "polyfill", "portable" ], - "time": "2019-02-06T07:57:58+00:00" + "time": "2018-08-06T14:22:27+00:00" }, { "name": "symfony/yaml", - "version": "v3.4.30", + "version": "v3.4.16", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "051d045c684148060ebfc9affb7e3f5e0899d40b" + "reference": "61973ecda60e9f3561e929e19c07d4878b960fc1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/051d045c684148060ebfc9affb7e3f5e0899d40b", - "reference": "051d045c684148060ebfc9affb7e3f5e0899d40b", + "url": "https://api.github.com/repos/symfony/yaml/zipball/61973ecda60e9f3561e929e19c07d4878b960fc1", + "reference": "61973ecda60e9f3561e929e19c07d4878b960fc1", "shasum": "" }, "require": { @@ -4322,7 +4308,7 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2019-07-24T13:01:31+00:00" + "time": "2018-09-24T08:15:45+00:00" }, { "name": "webmozart/assert", diff --git a/include/text.php b/include/text.php index 7bed8b4994..3d6bf6a563 100644 --- a/include/text.php +++ b/include/text.php @@ -4,11 +4,11 @@ */ use Friendica\App; -use Friendica\Content\Smilies; use Friendica\Content\Text\BBCode; use Friendica\Core\Protocol; use Friendica\Model\Contact; use Friendica\Model\FileTag; +use Friendica\Model\Group; use Friendica\Util\Strings; /** @@ -20,18 +20,9 @@ use Friendica\Util\Strings; function expand_acl($s) { // turn string array of angle-bracketed elements into numeric array // e.g. "<1><2><3>" => array(1,2,3); - $ret = []; + preg_match_all('/<(' . Group::FOLLOWERS . '|'. Group::MUTUALS . '|[0-9]+)>/', $s, $matches, PREG_PATTERN_ORDER); - if (strlen($s)) { - $t = str_replace('<', '', $s); - $a = explode('>', $t); - foreach ($a as $aa) { - if (intval($aa)) { - $ret[] = intval($aa); - } - } - } - return $ret; + return $matches[1]; } @@ -42,6 +33,8 @@ function expand_acl($s) { function sanitise_acl(&$item) { if (intval($item)) { $item = '<' . intval(Strings::escapeTags(trim($item))) . '>'; + } elseif (in_array($item, [Group::FOLLOWERS, Group::MUTUALS])) { + $item = '<' . $item . '>'; } else { unset($item); } diff --git a/mod/lockview.php b/mod/lockview.php index 35c4b04332..eede1b6a0d 100644 --- a/mod/lockview.php +++ b/mod/lockview.php @@ -6,6 +6,7 @@ use Friendica\App; use Friendica\Core\Hook; use Friendica\Core\L10n; use Friendica\Database\DBA; +use Friendica\Model\Group; use Friendica\Model\Item; function lockview_content(App $a) @@ -67,6 +68,19 @@ function lockview_content(App $a) $l = []; if (count($allowed_groups)) { + $key = array_search(Group::FOLLOWERS, $allowed_groups); + if ($key !== false) { + $l[] = '' . L10n::t('Followers') . ''; + unset($allowed_groups[$key]); + } + + $key = array_search(Group::MUTUALS, $allowed_groups); + if ($key !== false) { + $l[] = '' . L10n::t('Mutuals') . ''; + unset($allowed_groups[$key]); + } + + $r = q("SELECT `name` FROM `group` WHERE `id` IN ( %s )", DBA::escape(implode(', ', $allowed_groups)) ); @@ -89,6 +103,18 @@ function lockview_content(App $a) } if (count($deny_groups)) { + $key = array_search(Group::FOLLOWERS, $deny_groups); + if ($key !== false) { + $l[] = '' . L10n::t('Followers') . ''; + unset($deny_groups[$key]); + } + + $key = array_search(Group::MUTUALS, $deny_groups); + if ($key !== false) { + $l[] = '' . L10n::t('Mutuals') . ''; + unset($deny_groups[$key]); + } + $r = q("SELECT `name` FROM `group` WHERE `id` IN ( %s )", DBA::escape(implode(', ', $deny_groups)) ); diff --git a/mod/network.php b/mod/network.php index 7354667200..fddec60c8d 100644 --- a/mod/network.php +++ b/mod/network.php @@ -643,7 +643,7 @@ function networkThreadedView(App $a, $update, $parent) // NOTREACHED } - $contacts = Group::expand([$gid]); + $contacts = Group::expand(local_user(), [$gid]); if ((is_array($contacts)) && count($contacts)) { $contact_str_self = ''; diff --git a/src/App/Router.php b/src/App/Router.php index a54f3a711e..50b208792b 100644 --- a/src/App/Router.php +++ b/src/App/Router.php @@ -94,6 +94,7 @@ class Router $this->routeCollector->addRoute(['GET'], '/attach/{item:\d+}', Module\Attach::class); $this->routeCollector->addRoute(['GET'], '/babel', Module\Debug\Babel::class); $this->routeCollector->addRoute(['GET'], '/bookmarklet', Module\Bookmarklet::class); + $this->routeCollector->addRoute(['GET', 'POST'], '/compose[/{type}]', Module\Item\Compose::class); $this->routeCollector->addGroup('/contact', function (RouteCollector $collector) { $collector->addRoute(['GET'], '[/]', Module\Contact::class); $collector->addRoute(['GET', 'POST'], '/{id:\d+}[/]', Module\Contact::class); diff --git a/src/Core/Protocol.php b/src/Core/Protocol.php index 759a6e70e6..0ecc076a0f 100644 --- a/src/Core/Protocol.php +++ b/src/Core/Protocol.php @@ -25,6 +25,8 @@ class Protocol const FEDERATED = [self::DFRN, self::DIASPORA, self::OSTATUS, self::ACTIVITYPUB]; + const SUPPORT_PRIVATE = [self::DFRN, self::DIASPORA, self::MAIL, self::ACTIVITYPUB, self::PUMPIO]; + // Supported through a connector const DIASPORA2 = 'dspc'; // Diaspora connector const LINKEDIN = 'lnkd'; // LinkedIn diff --git a/src/Model/Group.php b/src/Model/Group.php index feff4661ab..50160baaf4 100644 --- a/src/Model/Group.php +++ b/src/Model/Group.php @@ -2,12 +2,14 @@ /** * @file src/Model/Group.php */ + namespace Friendica\Model; use Friendica\BaseModule; use Friendica\BaseObject; use Friendica\Core\L10n; use Friendica\Core\Logger; +use Friendica\Core\Protocol; use Friendica\Core\Renderer; use Friendica\Database\DBA; @@ -16,9 +18,21 @@ use Friendica\Database\DBA; */ class Group extends BaseObject { + const FOLLOWERS = '~'; + const MUTUALS = '&'; + + public static function getByUserId($uid, $includesDeleted = false) + { + $conditions = ['uid' => $uid]; + + if (!$includesDeleted) { + $conditions['deleted'] = false; + } + + return DBA::selectToArray('group', [], $conditions); + } + /** - * - * * @param int $group_id * @return bool * @throws \Exception @@ -76,8 +90,8 @@ class Group extends BaseObject /** * Update group information. * - * @param int $id Group ID - * @param string $name Group name + * @param int $id Group ID + * @param string $name Group name * * @return bool Was the update successful? * @throws \Exception @@ -96,14 +110,13 @@ class Group extends BaseObject */ public static function getIdsByContactId($cid) { - $condition = ['contact-id' => $cid]; - $stmt = DBA::select('group_member', ['gid'], $condition); - $return = []; + $stmt = DBA::select('group_member', ['gid'], ['contact-id' => $cid]); while ($group = DBA::fetch($stmt)) { $return[] = $group['gid']; } + DBA::close($stmt); return $return; } @@ -170,8 +183,9 @@ class Group extends BaseObject * @return boolean * @throws \Exception */ - public static function remove($gid) { - if (! $gid) { + public static function remove($gid) + { + if (!$gid) { return false; } @@ -215,14 +229,15 @@ class Group extends BaseObject /** * @brief Mark a group as deleted based on its name * - * @deprecated Use Group::remove instead - * * @param int $uid * @param string $name * @return bool * @throws \Exception + * @deprecated Use Group::remove instead + * */ - public static function removeByName($uid, $name) { + public static function removeByName($uid, $name) + { $return = false; if (!empty($uid) && !empty($name)) { $gid = self::getIdByName($uid, $name); @@ -280,13 +295,13 @@ class Group extends BaseObject /** * @brief Removes a contact from a group based on its name * - * @deprecated Use Group::removeMember instead - * * @param int $uid * @param string $name * @param int $cid * @return boolean * @throws \Exception + * @deprecated Use Group::removeMember instead + * */ public static function removeMemberByName($uid, $name, $cid) { @@ -300,23 +315,55 @@ class Group extends BaseObject /** * @brief Returns the combined list of contact ids from a group id list * + * @param int $uid * @param array $group_ids * @param boolean $check_dead * @return array * @throws \Exception */ - public static function expand($group_ids, $check_dead = false) + public static function expand($uid, array $group_ids, $check_dead = false) { if (!is_array($group_ids) || !count($group_ids)) { return []; } - $stmt = DBA::select('group_member', ['contact-id'], ['gid' => $group_ids]); - $return = []; - while($group_member = DBA::fetch($stmt)) { + + $key = array_search(self::FOLLOWERS, $group_ids); + if ($key !== false) { + $followers = Contact::selectToArray(['id'], [ + 'uid' => $uid, + 'rel' => [Contact::FOLLOWER, Contact::FRIEND], + 'protocol' => Protocol::SUPPORT_PRIVATE, + ]); + + foreach ($followers as $follower) { + $return[] = $follower['id']; + } + + unset($group_ids[$key]); + } + + $key = array_search(self::MUTUALS, $group_ids); + if ($key !== false) { + $mutuals = Contact::selectToArray(['id'], [ + 'uid' => $uid, + 'rel' => [Contact::FRIEND], + 'protocol' => Protocol::SUPPORT_PRIVATE, + ]); + + foreach ($mutuals as $mutual) { + $return[] = $mutual['id']; + } + + unset($group_ids[$key]); + } + + $stmt = DBA::select('group_member', ['contact-id'], ['gid' => $group_ids]); + while ($group_member = DBA::fetch($stmt)) { $return[] = $group_member['contact-id']; } + DBA::close($stmt); if ($check_dead) { Contact::pruneUnavailable($return); @@ -332,12 +379,10 @@ class Group extends BaseObject * @param int $gid An optional pre-selected group * @param string $label An optional label of the list * @return string - * @throws \Friendica\Network\HTTPException\InternalServerErrorException + * @throws \Exception */ public static function displayGroupSelection($uid, $gid = 0, $label = '') { - $stmt = DBA::select('group', [], ['deleted' => 0, 'uid' => $uid], ['order' => ['name']]); - $display_groups = [ [ 'name' => '', @@ -345,6 +390,8 @@ class Group extends BaseObject 'selected' => '' ] ]; + + $stmt = DBA::select('group', [], ['deleted' => 0, 'uid' => $uid], ['order' => ['name']]); while ($group = DBA::fetch($stmt)) { $display_groups[] = [ 'name' => $group['name'], @@ -352,7 +399,9 @@ class Group extends BaseObject 'selected' => $gid == $group['id'] ? 'true' : '' ]; } - Logger::log('groups: ' . print_r($display_groups, true)); + DBA::close($stmt); + + Logger::info('Got groups', $display_groups); if ($label == '') { $label = L10n::t('Default privacy group for new contacts'); @@ -377,7 +426,7 @@ class Group extends BaseObject * @param string $group_id * @param int $cid * @return string - * @throws \Friendica\Network\HTTPException\InternalServerErrorException + * @throws \Exception */ public static function sidebarWidget($every = 'contact', $each = 'group', $editmode = 'standard', $group_id = '', $cid = 0) { @@ -394,13 +443,12 @@ class Group extends BaseObject ] ]; - $stmt = DBA::select('group', [], ['deleted' => 0, 'uid' => local_user()], ['order' => ['name']]); - $member_of = []; if ($cid) { $member_of = self::getIdsByContactId($cid); } + $stmt = DBA::select('group', [], ['deleted' => 0, 'uid' => local_user()], ['order' => ['name']]); while ($group = DBA::fetch($stmt)) { $selected = (($group_id == $group['id']) ? ' group-selected' : ''); @@ -423,6 +471,7 @@ class Group extends BaseObject 'ismember' => in_array($group['id'], $member_of), ]; } + DBA::close($stmt); // Don't show the groups on the network page when there is only one if ((count($display_groups) <= 2) && ($each == 'network')) { @@ -445,7 +494,6 @@ class Group extends BaseObject '$form_security_token' => BaseModule::getFormSecurityToken('group_edit'), ]); - return $o; } } diff --git a/src/Model/Item.php b/src/Model/Item.php index ef461fe652..01c2c3459d 100644 --- a/src/Model/Item.php +++ b/src/Model/Item.php @@ -1246,7 +1246,6 @@ class Item extends BaseObject return $contact_id; } } - return $item['author-id']; } @@ -1472,6 +1471,9 @@ class Item extends BaseObject $item['plink'] = defaults($item, 'plink', System::baseUrl() . '/display/' . urlencode($item['guid'])); + // The contact-id should be set before "self::insert" was called - but there seems to be issues sometimes + $item["contact-id"] = self::contactId($item); + $default = ['url' => $item['author-link'], 'name' => $item['author-name'], 'photo' => $item['author-avatar'], 'network' => $item['network']]; @@ -1527,9 +1529,6 @@ class Item extends BaseObject unset($item['causer-id']); unset($item['causer-link']); - // The contact-id should be set before "self::insert" was called - but there seems to be issues sometimes - $item['contact-id'] = self::contactId($item); - if ($item['network'] == Protocol::PHANTOM) { $item['network'] = Protocol::DFRN; Logger::notice('Missing network, setting to {network}.', [ @@ -2804,7 +2803,7 @@ class Item extends BaseObject $replace = true; } } elseif ($item) { - if (self::samePermissions($item, $photo)) { + if (self::samePermissions($uid, $item, $photo)) { $replace = true; } } @@ -2854,7 +2853,7 @@ class Item extends BaseObject !empty($obj['deny_cid']) || !empty($obj['deny_gid']); } - private static function samePermissions($obj1, $obj2) + private static function samePermissions($uid, $obj1, $obj2) { // first part is easy. Check that these are exactly the same. if (($obj1['allow_cid'] == $obj2['allow_cid']) @@ -2875,12 +2874,12 @@ class Item extends BaseObject } // returns an array of contact-ids that are allowed to see this object - public static function enumeratePermissions($obj) + public static function enumeratePermissions(array $obj) { $allow_people = expand_acl($obj['allow_cid']); - $allow_groups = Group::expand(expand_acl($obj['allow_gid'])); + $allow_groups = Group::expand($obj['uid'], expand_acl($obj['allow_gid'])); $deny_people = expand_acl($obj['deny_cid']); - $deny_groups = Group::expand(expand_acl($obj['deny_gid'])); + $deny_groups = Group::expand($obj['uid'], expand_acl($obj['deny_gid'])); $recipients = array_unique(array_merge($allow_people, $allow_groups)); $deny = array_unique(array_merge($deny_people, $deny_groups)); $recipients = array_diff($recipients, $deny); diff --git a/src/Module/Item/Compose.php b/src/Module/Item/Compose.php new file mode 100644 index 0000000000..09668af230 --- /dev/null +++ b/src/Module/Item/Compose.php @@ -0,0 +1,217 @@ +getCurrentTheme() !== 'frio') { + throw new NotImplementedException(L10n::t('This feature is only available with the frio theme.')); + } + + /// @TODO Retrieve parameter from router + $posttype = $a->argv[1] ?? Item::PT_ARTICLE; + if (!in_array($posttype, [Item::PT_ARTICLE, Item::PT_PERSONAL_NOTE])) { + switch ($posttype) { + case 'note': + $posttype = Item::PT_PERSONAL_NOTE; + break; + default: + $posttype = Item::PT_ARTICLE; + break; + } + } + + $user = User::getById(local_user(), ['allow_cid', 'allow_gid', 'deny_cid', 'deny_gid', 'hidewall', 'default-location']); + + switch ($posttype) { + case Item::PT_PERSONAL_NOTE: + $compose_title = L10n::t('Compose new personal note'); + $type = 'note'; + $doesFederate = false; + $contact_allow = $a->contact['id']; + $group_allow = ''; + break; + default: + $compose_title = L10n::t('Compose new post'); + $type = 'post'; + $doesFederate = true; + $contact_allow = implode(',', expand_acl($user['allow_cid'])); + $group_allow = implode(',', expand_acl($user['allow_gid'])) ?: Group::FOLLOWERS; + break; + } + + $title = $_REQUEST['title'] ?? ''; + $category = $_REQUEST['category'] ?? ''; + $body = $_REQUEST['body'] ?? ''; + $location = $_REQUEST['location'] ?? $user['default-location']; + $wall = $_REQUEST['wall'] ?? $type == 'post'; + $contact_allow = $_REQUEST['contact_allow'] ?? $contact_allow; + $group_allow = $_REQUEST['group_allow'] ?? $group_allow; + $contact_deny = $_REQUEST['contact_deny'] ?? implode(',', expand_acl($user['deny_cid'])); + $group_deny = $_REQUEST['group_deny'] ?? implode(',', expand_acl($user['deny_gid'])); + $visibility = ($contact_allow . $user['allow_gid'] . $user['deny_cid'] . $user['deny_gid']) ? 'custom' : 'public'; + + $acl_contacts = Contact::selectToArray(['id', 'name', 'addr', 'micro'], ['uid' => local_user(), 'pending' => false, 'rel' => [Contact::FOLLOWER, Contact::FRIEND]]); + array_walk($acl_contacts, function (&$value) { + $value['type'] = 'contact'; + }); + + $acl_groups = [ + [ + 'id' => Group::FOLLOWERS, + 'name' => L10n::t('Followers'), + 'addr' => '', + 'micro' => 'images/twopeople.png', + 'type' => 'group', + ], + [ + 'id' => Group::MUTUALS, + 'name' => L10n::t('Mutuals'), + 'addr' => '', + 'micro' => 'images/twopeople.png', + 'type' => 'group', + ] + ]; + foreach (Group::getByUserId(local_user()) as $group) { + $acl_groups[] = [ + 'id' => $group['id'], + 'name' => $group['name'], + 'addr' => '', + 'micro' => 'images/twopeople.png', + 'type' => 'group', + ]; + } + + $acl = array_merge($acl_groups, $acl_contacts); + + $jotnets_fields = []; + $mail_enabled = false; + $pubmail_enabled = false; + if (function_exists('imap_open') && !Config::get('system', 'imap_disabled')) { + $mailacct = DBA::selectFirst('mailacct', ['pubmail'], ['`uid` = ? AND `server` != ""', local_user()]); + if (DBA::isResult($mailacct)) { + $mail_enabled = true; + $pubmail_enabled = !empty($mailacct['pubmail']); + } + } + + if (empty($user['hidewall'])) { + if ($mail_enabled) { + $jotnets_fields[] = [ + 'type' => 'checkbox', + 'field' => [ + 'pubmail_enable', + L10n::t('Post to Email'), + $pubmail_enabled + ] + ]; + } + + Hook::callAll('jot_networks', $jotnets_fields); + } + + $jotplugins = ''; + Hook::callAll('jot_tool', $jotplugins); + + // Output + + $a->registerFooterScript('view/js/ajaxupload.js'); + $a->registerFooterScript('view/js/linkPreview.js'); + $a->registerFooterScript('view/asset/typeahead.js/dist/typeahead.bundle.js'); + $a->registerFooterScript('view/theme/frio/frameworks/friendica-tagsinput/friendica-tagsinput.js'); + $a->registerStylesheet('view/theme/frio/frameworks/friendica-tagsinput/friendica-tagsinput.css'); + $a->registerStylesheet('view/theme/frio/frameworks/friendica-tagsinput/friendica-tagsinput-typeahead.css'); + + $tpl = Renderer::getMarkupTemplate('item/compose-footer.tpl'); + $a->page['footer'] .= Renderer::replaceMacros($tpl, [ + '$acl_contacts' => $acl_contacts, + '$acl_groups' => $acl_groups, + '$acl' => $acl, + ]); + + $tpl = Renderer::getMarkupTemplate('item/compose.tpl'); + return Renderer::replaceMacros($tpl, [ + '$compose_title'=> $compose_title, + '$id' => 0, + '$posttype' => $posttype, + '$type' => $type, + '$wall' => $wall, + '$default' => L10n::t(''), + '$mylink' => $a->removeBaseURL($a->contact['url']), + '$mytitle' => L10n::t('This is you'), + '$myphoto' => $a->removeBaseURL($a->contact['thumb']), + '$submit' => L10n::t('Submit'), + '$edbold' => L10n::t('Bold'), + '$editalic' => L10n::t('Italic'), + '$eduline' => L10n::t('Underline'), + '$edquote' => L10n::t('Quote'), + '$edcode' => L10n::t('Code'), + '$edimg' => L10n::t('Image'), + '$edurl' => L10n::t('Link'), + '$edattach' => L10n::t('Link or Media'), + '$prompttext' => L10n::t('Please enter a image/video/audio/webpage URL:'), + '$preview' => L10n::t('Preview'), + '$location_set' => L10n::t('Set your location'), + '$location_clear' => L10n::t('Clear the location'), + '$location_unavailable' => L10n::t('Location services are unavailable on your device'), + '$location_disabled' => L10n::t('Location services are disabled. Please check the website\'s permissions on your device'), + '$wait' => L10n::t('Please wait'), + '$placeholdertitle' => L10n::t('Set title'), + '$placeholdercategory' => (Feature::isEnabled(local_user(),'categories') ? L10n::t('Categories (comma-separated list)') : ''), + '$public_title' => L10n::t('Public'), + '$public_desc' => L10n::t('This post will be sent to all your followers and can be seen in the community pages and by anyone with its link.'), + '$custom_title' => L10n::t('Limited/Private'), + '$custom_desc' => L10n::t('This post will be sent only to the people in the first box, to the exception of the people mentioned in the second box. It won\'t appear anywhere public.'), + '$emailcc' => L10n::t('CC: email addresses'), + '$title' => $title, + '$category' => $category, + '$body' => $body, + '$location' => $location, + '$visibility' => $visibility, + '$contact_allow'=> $contact_allow, + '$group_allow' => $group_allow, + '$contact_deny' => $contact_deny, + '$group_deny' => $group_deny, + '$jotplugins' => $jotplugins, + '$doesFederate' => $doesFederate, + '$jotnets_fields'=> $jotnets_fields, + '$sourceapp' => L10n::t($a->sourcename), + '$rand_num' => Crypto::randomDigits(12) + ]); + } +} diff --git a/src/Protocol/ActivityPub/Processor.php b/src/Protocol/ActivityPub/Processor.php index 65dd6d3e62..7639d0f2a3 100644 --- a/src/Protocol/ActivityPub/Processor.php +++ b/src/Protocol/ActivityPub/Processor.php @@ -419,8 +419,8 @@ class Processor $item['contact-id'] = Contact::getIdForURL($activity['author'], $receiver, true); } - if (empty($item['contact-id'])) { - $item['contact-id'] = $item['author-id']; + if (($receiver != 0) && empty($item['contact-id'])) { + $item['contact-id'] = Contact::getIdForURL($activity['author'], 0, true); } if (!empty($activity['directmessage'])) { diff --git a/src/Worker/Notifier.php b/src/Worker/Notifier.php index 42ad0afb6d..4efd3e9126 100644 --- a/src/Worker/Notifier.php +++ b/src/Worker/Notifier.php @@ -271,9 +271,9 @@ class Notifier } $allow_people = expand_acl($parent['allow_cid']); - $allow_groups = Group::expand(expand_acl($parent['allow_gid']),true); + $allow_groups = Group::expand($uid, expand_acl($parent['allow_gid']),true); $deny_people = expand_acl($parent['deny_cid']); - $deny_groups = Group::expand(expand_acl($parent['deny_gid'])); + $deny_groups = Group::expand($uid, expand_acl($parent['deny_gid'])); // if our parent is a public forum (forum_mode == 1), uplink to the origional author causing // a delivery fork. private groups (forum_mode == 2) do not uplink diff --git a/tests/include/TextTest.php b/tests/include/TextTest.php index e516fe824a..5676da8f62 100644 --- a/tests/include/TextTest.php +++ b/tests/include/TextTest.php @@ -5,6 +5,7 @@ namespace Friendica\Test; +use Friendica\Model\Group; use PHPUnit\Framework\TestCase; /** @@ -55,8 +56,8 @@ class TextTest extends TestCase */ public function testExpandAclNormal() { - $text='<1><2><3>'; - $this->assertEquals(array(1, 2, 3), expand_acl($text)); + $text='<1><2><3><' . Group::FOLLOWERS . '><' . Group::MUTUALS . '>'; + $this->assertEquals(array('1', '2', '3', Group::FOLLOWERS, Group::MUTUALS), expand_acl($text)); } /** @@ -64,8 +65,8 @@ class TextTest extends TestCase */ public function testExpandAclBigNumber() { - $text='<1><'.PHP_INT_MAX.'><15>'; - $this->assertEquals(array(1, PHP_INT_MAX, 15), expand_acl($text)); + $text='<1><' . PHP_INT_MAX . '><15>'; + $this->assertEquals(array('1', (string)PHP_INT_MAX, '15'), expand_acl($text)); } /** @@ -76,7 +77,7 @@ class TextTest extends TestCase public function testExpandAclString() { $text="<1><279012>"; - $this->assertEquals(array(1, 279012), expand_acl($text)); + $this->assertEquals(array('1', '279012'), expand_acl($text)); } /** @@ -87,7 +88,7 @@ class TextTest extends TestCase public function testExpandAclSpace() { $text="<1><279 012><32>"; - $this->assertEquals(array(1, "279", "32"), expand_acl($text)); + $this->assertEquals(array('1', '32'), expand_acl($text)); } /** @@ -174,7 +175,7 @@ class TextTest extends TestCase public function testExpandAclEmptyMatch() { $text="<1><><3>"; - $this->assertEquals(array(1,3), expand_acl($text)); + $this->assertEquals(array('1', '3'), expand_acl($text)); } /** diff --git a/view/js/main.js b/view/js/main.js index a2e2698a82..8b1303e7d3 100644 --- a/view/js/main.js +++ b/view/js/main.js @@ -632,7 +632,6 @@ function post_comment(id) { unpause(); commentBusy = true; $('body').css('cursor', 'wait'); - $("#comment-preview-inp-" + id).val("0"); $.post( "item", $("#comment-edit-form-" + id).serialize(), @@ -661,11 +660,10 @@ function post_comment(id) { } function preview_comment(id) { - $("#comment-preview-inp-" + id).val("1"); $("#comment-edit-preview-" + id).show(); $.post( "item", - $("#comment-edit-form-" + id).serialize(), + $("#comment-edit-form-" + id).serialize() + '&preview=1', function(data) { if (data.preview) { $("#comment-edit-preview-" + id).html(data.preview); diff --git a/view/templates/comment_item.tpl b/view/templates/comment_item.tpl index 352ff417f0..977a5b75b2 100644 --- a/view/templates/comment_item.tpl +++ b/view/templates/comment_item.tpl @@ -10,7 +10,6 @@ {{**}} -
diff --git a/view/templates/item/compose-footer.tpl b/view/templates/item/compose-footer.tpl new file mode 100644 index 0000000000..697be6fd10 --- /dev/null +++ b/view/templates/item/compose-footer.tpl @@ -0,0 +1,251 @@ + diff --git a/view/templates/item/compose.tpl b/view/templates/item/compose.tpl new file mode 100644 index 0000000000..d18b1a2d36 --- /dev/null +++ b/view/templates/item/compose.tpl @@ -0,0 +1,156 @@ +
+

{{$compose_title}}

+
+
+ {{**}} + + + + +
+ +
+ {{if $placeholdercategory}} +
+ +
+ {{/if}} + +

+ + + + + + + + + + + +

+

+ +

+ +

+{{if $type == 'post'}} + + + + +{{/if}} + + + + + {{if $preview}} + + {{/if}} + +

+ + + + + + + +{{if $type == 'post'}} +

Visibility

+
+
+
+ +
+
+
+

{{$public_desc}}

+ {{if $doesFederate && $jotnets_fields}} + {{if $jotnets_fields|count < 3}} +
+ {{else}} +
+ {{$jotnets_summary}} + {{/if}} + + {{foreach $jotnets_fields as $jotnets_field}} + {{if $jotnets_field.type == 'checkbox'}} + {{include file="field_checkbox.tpl" field=$jotnets_field.field}} + {{elseif $jotnets_field.type == 'select'}} + {{include file="field_select.tpl" field=$jotnets_field.field}} + {{/if}} + {{/foreach}} + + {{if $jotnets_fields|count >= 3}} +
+ {{else}} +
+ {{/if}} + {{/if}} +
+
+
+
+ +
+
+

{{$custom_desc}}

+ +
+ + +
+ +
+ + +
+
+
+
+
+ {{if $doesFederate}} +
+ + +
+
+ {{/if}} +
+ {{$jotplugins nofilter}} +
+{{/if}} +
+
+
\ No newline at end of file diff --git a/view/templates/moderated_comment.tpl b/view/templates/moderated_comment.tpl index d52bf172a0..39dbbde477 100644 --- a/view/templates/moderated_comment.tpl +++ b/view/templates/moderated_comment.tpl @@ -6,7 +6,6 @@ -
{{$mytitle}} diff --git a/view/theme/duepuntozero/templates/comment_item.tpl b/view/theme/duepuntozero/templates/comment_item.tpl index bfa5f09388..cd14b253c5 100644 --- a/view/theme/duepuntozero/templates/comment_item.tpl +++ b/view/theme/duepuntozero/templates/comment_item.tpl @@ -10,7 +10,6 @@ {{**}} -
diff --git a/view/theme/duepuntozero/templates/moderated_comment.tpl b/view/theme/duepuntozero/templates/moderated_comment.tpl index e5c2e5b9ad..197d1281d3 100644 --- a/view/theme/duepuntozero/templates/moderated_comment.tpl +++ b/view/theme/duepuntozero/templates/moderated_comment.tpl @@ -6,7 +6,6 @@ -
{{$mytitle}} diff --git a/view/theme/frio/config.php b/view/theme/frio/config.php index 0f3eda4c94..df0f65a6b8 100644 --- a/view/theme/frio/config.php +++ b/view/theme/frio/config.php @@ -16,15 +16,16 @@ function theme_post(App $a) } if (isset($_POST['frio-settings-submit'])) { - PConfig::set(local_user(), 'frio', 'scheme', defaults($_POST, 'frio_scheme', '')); - PConfig::set(local_user(), 'frio', 'nav_bg', defaults($_POST, 'frio_nav_bg', '')); - PConfig::set(local_user(), 'frio', 'nav_icon_color', defaults($_POST, 'frio_nav_icon_color', '')); - PConfig::set(local_user(), 'frio', 'link_color', defaults($_POST, 'frio_link_color', '')); - PConfig::set(local_user(), 'frio', 'background_color', defaults($_POST, 'frio_background_color', '')); - PConfig::set(local_user(), 'frio', 'contentbg_transp', defaults($_POST, 'frio_contentbg_transp', '')); - PConfig::set(local_user(), 'frio', 'background_image', defaults($_POST, 'frio_background_image', '')); - PConfig::set(local_user(), 'frio', 'bg_image_option', defaults($_POST, 'frio_bg_image_option', '')); + PConfig::set(local_user(), 'frio', 'scheme', $_POST['frio_scheme'] ?? ''); + PConfig::set(local_user(), 'frio', 'nav_bg', $_POST['frio_nav_bg'] ?? ''); + PConfig::set(local_user(), 'frio', 'nav_icon_color', $_POST['frio_nav_icon_color'] ?? ''); + PConfig::set(local_user(), 'frio', 'link_color', $_POST['frio_link_color'] ?? ''); + PConfig::set(local_user(), 'frio', 'background_color', $_POST['frio_background_color'] ?? ''); + PConfig::set(local_user(), 'frio', 'contentbg_transp', $_POST['frio_contentbg_transp'] ?? ''); + PConfig::set(local_user(), 'frio', 'background_image', $_POST['frio_background_image'] ?? ''); + PConfig::set(local_user(), 'frio', 'bg_image_option', $_POST['frio_bg_image_option'] ?? ''); PConfig::set(local_user(), 'frio', 'css_modified', time()); + PConfig::set(local_user(), 'frio', 'enable_compose', $_POST['frio_enable_compose'] ?? 0); } } @@ -35,17 +36,18 @@ function theme_admin_post(App $a) } if (isset($_POST['frio-settings-submit'])) { - Config::set('frio', 'scheme', defaults($_POST, 'frio_scheme', '')); - Config::set('frio', 'nav_bg', defaults($_POST, 'frio_nav_bg', '')); - Config::set('frio', 'nav_icon_color', defaults($_POST, 'frio_nav_icon_color', '')); - Config::set('frio', 'link_color', defaults($_POST, 'frio_link_color', '')); - Config::set('frio', 'background_color', defaults($_POST, 'frio_background_color', '')); - Config::set('frio', 'contentbg_transp', defaults($_POST, 'frio_contentbg_transp', '')); - Config::set('frio', 'background_image', defaults($_POST, 'frio_background_image', '')); - Config::set('frio', 'bg_image_option', defaults($_POST, 'frio_bg_image_option', '')); - Config::set('frio', 'login_bg_image', defaults($_POST, 'frio_login_bg_image', '')); - Config::set('frio', 'login_bg_color', defaults($_POST, 'frio_login_bg_color', '')); + Config::set('frio', 'scheme', $_POST['frio_scheme'] ?? ''); + Config::set('frio', 'nav_bg', $_POST['frio_nav_bg'] ?? ''); + Config::set('frio', 'nav_icon_color', $_POST['frio_nav_icon_color'] ?? ''); + Config::set('frio', 'link_color', $_POST['frio_link_color'] ?? ''); + Config::set('frio', 'background_color', $_POST['frio_background_color'] ?? ''); + Config::set('frio', 'contentbg_transp', $_POST['frio_contentbg_transp'] ?? ''); + Config::set('frio', 'background_image', $_POST['frio_background_image'] ?? ''); + Config::set('frio', 'bg_image_option', $_POST['frio_bg_image_option'] ?? ''); + Config::set('frio', 'login_bg_image', $_POST['frio_login_bg_image'] ?? ''); + Config::set('frio', 'login_bg_color', $_POST['frio_login_bg_color'] ?? ''); Config::set('frio', 'css_modified', time()); + Config::set('frio', 'enable_compose', $_POST['frio_enable_compose'] ?? 0); } } @@ -67,6 +69,7 @@ function theme_content(App $a) $arr['contentbg_transp'] = PConfig::get(local_user(), 'frio', 'contentbg_transp', Config::get('frio', 'contentbg_transp')); $arr['background_image'] = PConfig::get(local_user(), 'frio', 'background_image', Config::get('frio', 'background_image')); $arr['bg_image_option'] = PConfig::get(local_user(), 'frio', 'bg_image_option' , Config::get('frio', 'bg_image_option')); + $arr['enable_compose'] = PConfig::get(local_user(), 'frio', 'enable_compose' , Config::get('frio', 'enable_compose')); return frio_form($arr); } @@ -78,7 +81,7 @@ function theme_admin(App $a) } $arr = []; - $arr['scheme'] = Config::get('frio', 'scheme', Config::get('frio', 'scheme')); + $arr['scheme'] = Config::get('frio', 'scheme', Config::get('frio', 'schema')); $arr['share_string'] = ''; $arr['nav_bg'] = Config::get('frio', 'nav_bg'); $arr['nav_icon_color'] = Config::get('frio', 'nav_icon_color'); @@ -89,6 +92,7 @@ function theme_admin(App $a) $arr['bg_image_option'] = Config::get('frio', 'bg_image_option'); $arr['login_bg_image'] = Config::get('frio', 'login_bg_image'); $arr['login_bg_color'] = Config::get('frio', 'login_bg_color'); + $arr['enable_compose'] = Config::get('frio', 'enable_compose'); return frio_form($arr); } @@ -132,6 +136,7 @@ function frio_form($arr) '$background_image' => array_key_exists('background_image', $disable) ? '' : ['frio_background_image', L10n::t('Set the background image'), $arr['background_image'], $background_image_help, false], '$bg_image_options_title' => L10n::t('Background image style'), '$bg_image_options' => Image::get_options($arr), + '$enable_compose' => ['frio_enable_compose', L10n::t('Enable Compose page'), $arr['enable_compose'], L10n::t('This replaces the jot modal window for writing new posts with a link to the new Compose page.')], ]; if (array_key_exists('login_bg_image', $arr) && !array_key_exists('login_bg_image', $disable)) { diff --git a/view/theme/frio/css/style.css b/view/theme/frio/css/style.css index a88276f15d..c986b7232c 100644 --- a/view/theme/frio/css/style.css +++ b/view/theme/frio/css/style.css @@ -192,7 +192,6 @@ blockquote { background-image: none; text-shadow: none; border-radius: 3px; - outline: 0!important; margin-bottom: 0; font-size: 14px; font-weight: 600; @@ -1314,7 +1313,8 @@ section ul.tabs { section #jotOpen { display: none; } -#jotOpen { +#jotOpen, +#composeOpen { margin-top: 3px; float: right; } @@ -1372,7 +1372,8 @@ section #jotOpen { #jot-text-wrap .preview textarea { width: 100%; } -#preview_profile-jot-text { +#preview_profile-jot-text, +.comment-edit-form .preview { position: relative; padding: 0px 10px; margin-top: -2px; @@ -1383,7 +1384,8 @@ section #jotOpen { background: #fff; color: #555; } -textarea#profile-jot-text:focus + #preview_profile-jot-text { +textarea#profile-jot-text:focus + #preview_profile-jot-text, +textarea.comment-edit-text:focus + .comment-edit-form .preview { border: 2px solid #6fdbe8; border-top: none; } @@ -3425,7 +3427,6 @@ section .profile-match-wrapper { background-image: none; text-shadow: none; border-radius: 3px; - outline: 0!important; margin-bottom: 0; font-size: 14px; font-weight: 600; @@ -3444,7 +3445,6 @@ section .profile-match-wrapper { background-image: none; text-shadow: none; border-radius: 3px; - outline: 0!important; margin-bottom: 0; font-size: 14px; font-weight: 600; diff --git a/view/theme/frio/frameworks/friendica-tagsinput/LICENSE b/view/theme/frio/frameworks/friendica-tagsinput/LICENSE new file mode 100644 index 0000000000..58bc985baf --- /dev/null +++ b/view/theme/frio/frameworks/friendica-tagsinput/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013 Tim Schlechter + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/view/theme/frio/frameworks/friendica-tagsinput/friendica-tagsinput-typeahead.css b/view/theme/frio/frameworks/friendica-tagsinput/friendica-tagsinput-typeahead.css new file mode 100644 index 0000000000..8cec654c47 --- /dev/null +++ b/view/theme/frio/frameworks/friendica-tagsinput/friendica-tagsinput-typeahead.css @@ -0,0 +1,54 @@ +/* + * friendica-tagsinput v0.8.0 + * + */ + +.twitter-typeahead .tt-query, +.twitter-typeahead .tt-hint { + margin-bottom: 0; +} + +.twitter-typeahead .tt-hint +{ + display: none; +} + +.tt-menu { + position: absolute; + top: 100%; + left: 0; + z-index: 1000; + display: none; + float: left; + min-width: 160px; + padding: 5px 0; + margin: 2px 0 0; + list-style: none; + font-size: 14px; + background-color: #ffffff; + border: 1px solid #cccccc; + border: 1px solid rgba(0, 0, 0, 0.15); + border-radius: 4px; + -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); + box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175); + background-clip: padding-box; + cursor: pointer; +} + +.tt-suggestion { + display: block; + padding: 3px 20px; + clear: both; + font-weight: normal; + line-height: 1.428571429; + color: #333333; + white-space: nowrap; +} + +.tt-suggestion:hover, +.tt-suggestion:focus { + color: #ffffff; + text-decoration: none; + outline: 0; + background-color: #428bca; +} diff --git a/view/theme/frio/frameworks/friendica-tagsinput/friendica-tagsinput.css b/view/theme/frio/frameworks/friendica-tagsinput/friendica-tagsinput.css new file mode 100644 index 0000000000..f2e1197a55 --- /dev/null +++ b/view/theme/frio/frameworks/friendica-tagsinput/friendica-tagsinput.css @@ -0,0 +1,72 @@ +/* + * friendica-tagsinput v0.8.0 + * + */ + +.friendica-tagsinput { + background-color: #fff; + border: 1px solid #ccc; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075); + display: inline-block; + padding: 4px 6px; + color: #555; + vertical-align: middle; + border-radius: 4px; + max-width: 100%; + line-height: 22px; + cursor: text; + height: auto; +} +.friendica-tagsinput.input-lg { + line-height: 27px; +} +.friendica-tagsinput input { + border: none; + box-shadow: none; + outline: none; + background-color: transparent; + padding: 0 6px; + margin: 0; + width: auto; + max-width: inherit; +} +.friendica-tagsinput.form-control input::-moz-placeholder { + color: #777; + opacity: 1; +} +.friendica-tagsinput.form-control input:-ms-input-placeholder { + color: #777; +} +.friendica-tagsinput.form-control input::-webkit-input-placeholder { + color: #777; +} +.friendica-tagsinput input:focus { + border: none; + box-shadow: none; +} +.friendica-tagsinput .tag { + margin: 0 2px 2px 0; + color: white; + font-weight: normal; +} +.friendica-tagsinput .tag img { + width: auto; + height: 1.5em; + vertical-align: text-top; + margin-right: 8px; +} +.friendica-tagsinput .tag [data-role="remove"] { + margin-left: 8px; + cursor: pointer; +} +.friendica-tagsinput .tag [data-role="remove"]:after { + content: "x"; + padding: 0px 2px; + font-weight: bold; +} +.friendica-tagsinput .tag [data-role="remove"]:hover { + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); +} +.friendica-tagsinput .tag [data-role="remove"]:hover:active { + box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); +} diff --git a/view/theme/frio/frameworks/friendica-tagsinput/friendica-tagsinput.js b/view/theme/frio/frameworks/friendica-tagsinput/friendica-tagsinput.js new file mode 100644 index 0000000000..2e83eedc64 --- /dev/null +++ b/view/theme/frio/frameworks/friendica-tagsinput/friendica-tagsinput.js @@ -0,0 +1,695 @@ +/* + * friendica-tagsinput v0.8.0 + * Based on bootstrap-tagsinput v0.8.0 + * + * Adds: + * - optional thumbnail + * - copying source input element class to the pseudo-input element + * + */ + +(function ($) { + "use strict"; + + var defaultOptions = { + tagClass: function(item) { + return 'label label-info'; + }, + focusClass: 'focus', + itemValue: function(item) { + return item ? item.toString() : item; + }, + itemText: function(item) { + return this.itemValue(item); + }, + itemTitle: function(item) { + return null; + }, + itemThumb: function(item) { + return null; + }, + freeInput: true, + addOnBlur: true, + maxTags: undefined, + maxChars: undefined, + confirmKeys: [13, 44], + delimiter: ',', + delimiterRegex: null, + cancelConfirmKeysOnEmpty: false, + onTagExists: function(item, $tag) { + $tag.hide().fadeIn(); + }, + trimValue: false, + allowDuplicates: false, + triggerChange: true + }; + + /** + * Constructor function + */ + function TagsInput(element, options) { + this.isInit = true; + this.itemsArray = []; + + this.$element = $(element); + this.$element.hide(); + + this.isSelect = (element.tagName === 'SELECT'); + this.multiple = (this.isSelect && element.hasAttribute('multiple')); + this.objectItems = options && options.itemValue; + this.placeholderText = element.hasAttribute('placeholder') ? this.$element.attr('placeholder') : ''; + this.inputSize = Math.max(1, this.placeholderText.length); + + this.$container = $('
'); + this.$container.addClass(this.$element.attr('class')); + this.$input = $('').appendTo(this.$container); + + this.$element.before(this.$container); + + this.build(options); + this.isInit = false; + } + + TagsInput.prototype = { + constructor: TagsInput, + + /** + * Adds the given item as a new tag. Pass true to dontPushVal to prevent + * updating the elements val() + */ + add: function(item, dontPushVal, options) { + let self = this; + + if (self.options.maxTags && self.itemsArray.length >= self.options.maxTags) + return; + + // Ignore falsey values, except false + if (item !== false && !item) + return; + + // Trim value + if (typeof item === "string" && self.options.trimValue) { + item = $.trim(item); + } + + // Throw an error when trying to add an object while the itemValue option was not set + if (typeof item === "object" && !self.objectItems) + throw("Can't add objects when itemValue option is not set"); + + // Ignore strings only containg whitespace + if (item.toString().match(/^\s*$/)) + return; + + // If SELECT but not multiple, remove current tag + if (self.isSelect && !self.multiple && self.itemsArray.length > 0) + self.remove(self.itemsArray[0]); + + if (typeof item === "string" && this.$element[0].tagName === 'INPUT') { + var delimiter = (self.options.delimiterRegex) ? self.options.delimiterRegex : self.options.delimiter; + var items = item.split(delimiter); + if (items.length > 1) { + for (var i = 0; i < items.length; i++) { + this.add(items[i], true); + } + + if (!dontPushVal) + self.pushVal(self.options.triggerChange); + return; + } + } + + var itemValue = self.options.itemValue(item), + itemText = self.options.itemText(item), + tagClass = self.options.tagClass(item), + itemTitle = self.options.itemTitle(item), + itemThumb = self.options.itemThumb(item); + + // Ignore items allready added + var existing = $.grep(self.itemsArray, function(item) { return self.options.itemValue(item) === itemValue; } )[0]; + if (existing && !self.options.allowDuplicates) { + // Invoke onTagExists + if (self.options.onTagExists) { + var $existingTag = $(".tag", self.$container).filter(function() { return $(this).data("item") === existing; }); + self.options.onTagExists(item, $existingTag); + } + return; + } + + // if length greater than limit + if (self.items().toString().length + item.length + 1 > self.options.maxInputLength) + return; + + // raise beforeItemAdd arg + var beforeItemAddEvent = $.Event('beforeItemAdd', { item: item, cancel: false, options: options}); + self.$element.trigger(beforeItemAddEvent); + if (beforeItemAddEvent.cancel) + return; + + // register item in internal array and map + self.itemsArray.push(item); + + // add a tag element + var $tag = $('' + + (itemThumb !== null ? '' : '') + + htmlEncode(itemText) + '' + + ''); + $tag.data('item', item); + self.findInputWrapper().before($tag); + $tag.after(' '); + + // Check to see if the tag exists in its raw or uri-encoded form + var optionExists = ( + $('option[value="' + encodeURIComponent(itemValue) + '"]', self.$element).length || + $('option[value="' + htmlEncode(itemValue) + '"]', self.$element).length + ); + + // add