5c047425ac88d779e30ba9bc89bbcce90adf62a9
[friendica.git/.git] / src / Content / Feature.php
1 <?php
2 /**
3  * @copyright Copyright (C) 2010-2024, the Friendica project
4  *
5  * @license GNU AGPL version 3 or any later version
6  *
7  * This program is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU Affero General Public License as
9  * published by the Free Software Foundation, either version 3 of the
10  * License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU Affero General Public License for more details.
16  *
17  * You should have received a copy of the GNU Affero General Public License
18  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
19  *
20  */
21
22 namespace Friendica\Content;
23
24 use Friendica\Core\Hook;
25 use Friendica\DI;
26
27 class Feature
28 {
29         const ACCOUNTS          = 'accounts';
30         const ADD_ABSTRACT      = 'add_abstract';
31         const ARCHIVE           = 'archive';
32         const CATEGORIES        = 'categories';
33         const CHANNELS          = 'channels';
34         const CIRCLES           = 'circles';
35         const COMMUNITY         = 'community';
36         const EXPLICIT_MENTIONS = 'explicit_mentions';
37         const FOLDERS           = 'folders';
38         const GROUPS            = 'forumlist_profile';
39         const MEMBER_SINCE      = 'profile_membersince';
40         const NETWORKS          = 'networks';
41         const NOSHARER          = 'nosharer';
42         const PHOTO_LOCATION    = 'photo_location';
43         const PUBLIC_CALENDAR   = 'public_calendar';
44         const SEARCHES          = 'searches';
45         const TAGCLOUD          = 'tagadelic';
46         const TRENDING_TAGS     = 'trending_tags';
47
48         /**
49          * check if feature is enabled
50          *
51          * @param integer $uid     user id
52          * @param string  $feature feature
53          * @return boolean
54          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
55          */
56         public static function isEnabled(int $uid, $feature): bool
57         {
58                 if (!DI::config()->get('feature_lock', $feature, false)) {
59                         $enabled = DI::config()->get('feature', $feature) ?? self::getDefault($feature);
60                         $enabled = DI::pConfig()->get($uid, 'feature', $feature) ?? $enabled;
61                 } else {
62                         $enabled = true;
63                 }
64
65                 $arr = ['uid' => $uid, 'feature' => $feature, 'enabled' => $enabled];
66                 Hook::callAll('isEnabled', $arr);
67                 return (bool)$arr['enabled'];
68         }
69
70         /**
71          * check if feature is enabled or disabled by default
72          *
73          * @param string $feature feature
74          * @return boolean
75          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
76          */
77         private static function getDefault($feature)
78         {
79                 foreach (self::get() as $cat) {
80                         foreach ($cat as $feat) {
81                                 if (is_array($feat) && $feat[0] === $feature) {
82                                         return $feat[3];
83                                 }
84                         }
85                 }
86                 return false;
87         }
88
89         /**
90          * Get a list of all available features
91          *
92          * The array includes the setting group, the setting name,
93          * explanations for the setting and if it's enabled or disabled
94          * by default
95          *
96          * @param bool $filtered True removes any locked features
97          *
98          * @return array
99          * @throws \Friendica\Network\HTTPException\InternalServerErrorException
100          */
101         public static function get($filtered = true)
102         {
103                 $arr = [
104
105                         // General
106                         'general' => [
107                                 DI::l10n()->t('General Features'),
108                                 //array('expire',         DI::l10n()->t('Content Expiration'),          DI::l10n()->t('Remove old posts/comments after a period of time')),
109                                 [self::PHOTO_LOCATION, DI::l10n()->t('Photo Location'), DI::l10n()->t("Photo metadata is normally stripped. This extracts the location \x28if present\x29 prior to stripping metadata and links it to a map."), false, DI::config()->get('feature_lock', self::PHOTO_LOCATION, false)],
110                                 [self::COMMUNITY, DI::l10n()->t('Display the community in the navigation'), DI::l10n()->t('If enabled, the community can be accessed via the navigation menu. Independant from this setting, the community timelines can always be accessed via the channels.'), true, DI::config()->get('feature_lock', self::COMMUNITY, false)],
111                         ],
112
113                         // Post composition
114                         'composition' => [
115                                 DI::l10n()->t('Post Composition Features'),
116                                 [self::EXPLICIT_MENTIONS, DI::l10n()->t('Explicit Mentions'), DI::l10n()->t('Add explicit mentions to comment box for manual control over who gets mentioned in replies.'), false, DI::config()->get('feature_lock', Feature::EXPLICIT_MENTIONS, false)],
117                                 [self::ADD_ABSTRACT,      DI::l10n()->t('Add an abstract from ActivityPub content warnings'), DI::l10n()->t('Add an abstract when commenting on ActivityPub posts with a content warning. Abstracts are displayed as content warning on systems like Mastodon or Pleroma.'), false, DI::config()->get('feature_lock', self::ADD_ABSTRACT, false)],
118                         ],
119
120                         // Item tools
121                         'tools' => [
122                                 DI::l10n()->t('Post/Comment Tools'),
123                                 [self::CATEGORIES, DI::l10n()->t('Post Categories'),         DI::l10n()->t('Add categories to your posts'), false, DI::config()->get('feature_lock', self::CATEGORIES, false)],
124                         ],
125
126                         // Widget visibility on the network stream
127                         'network' => [
128                                 DI::l10n()->t('Network Widgets'),
129                                 [self::CIRCLES, DI::l10n()->t('Circles'), DI::l10n()->t('Display posts that have been created by accounts of the selected circle.'), true, false, true],
130                                 [self::GROUPS, DI::l10n()->t('Groups'), DI::l10n()->t('Display posts that have been distributed by the selected group.'), true, false, true],
131                                 [self::ARCHIVE, DI::l10n()->t('Archives'), DI::l10n()->t('Display an archive where posts can be selected by month and year.'), true, false, true],
132                                 [self::NETWORKS, DI::l10n()->t('Protocols'), DI::l10n()->t('Display posts with the selected protocols.'), true, false, true],
133                                 [self::ACCOUNTS, DI::l10n()->t('Account Types'), DI::l10n()->t('Display posts done by accounts with the selected account type.'), true, false, true],
134                                 [self::CHANNELS, DI::l10n()->t('Channels'), DI::l10n()->t('Display posts in the system channels and user defined channels.'), true, false, true],
135                                 [self::SEARCHES, DI::l10n()->t('Saved Searches'), DI::l10n()->t('Display posts that contain subscribed hashtags.'), true, false, true],
136                                 [self::FOLDERS, DI::l10n()->t('Saved Folders'), DI::l10n()->t('Display a list of folders in which posts are stored.'), true, false, true],
137                                 [self::NOSHARER, DI::l10n()->t('Own Contacts'), DI::l10n()->t('Include or exclude posts from subscribed accounts. This widget is not visible on all channels.'), true, false, true],
138                                 [self::TRENDING_TAGS,  DI::l10n()->t('Trending Tags'), DI::l10n()->t('Display a list of the most popular tags in recent public posts.'), false, false, true],
139                         ],
140
141                         // Advanced Profile Settings
142                         'advanced_profile' => [
143                                 DI::l10n()->t('Advanced Profile Settings'),
144                                 [self::TAGCLOUD,     DI::l10n()->t('Tag Cloud'),               DI::l10n()->t('Provide a personal tag cloud on your profile page'), false, DI::config()->get('feature_lock', self::TAGCLOUD, false)],
145                                 [self::MEMBER_SINCE, DI::l10n()->t('Display Membership Date'), DI::l10n()->t('Display membership date in profile'), false, DI::config()->get('feature_lock', self::MEMBER_SINCE, false)],
146                         ],
147
148                         //Advanced Calendar Settings
149                         'advanced_calendar' => [
150                                 DI::l10n()->t('Advanced Calendar Settings'),
151                                 [self::PUBLIC_CALENDAR, DI::l10n()->t('Allow anonymous access to your calendar'), DI::l10n()->t('Allows anonymous visitors to consult your calendar and your public events. Contact birthday events are private to you.'), false, DI::config()->get('feature_lock', self::PUBLIC_CALENDAR, false)],
152                         ]
153                 ];
154
155                 // removed any locked features and remove the entire category if this makes it empty
156
157                 if ($filtered) {
158                         foreach ($arr as $k => $x) {
159                                 $has_items = false;
160                                 $kquantity = count($arr[$k]);
161                                 for ($y = 0; $y < $kquantity; $y ++) {
162                                         if (is_array($arr[$k][$y])) {
163                                                 if ($arr[$k][$y][4] === false) {
164                                                         $has_items = true;
165                                                 } else {
166                                                         unset($arr[$k][$y]);
167                                                 }
168                                         }
169                                 }
170                                 if (! $has_items) {
171                                         unset($arr[$k]);
172                                 }
173                         }
174                 }
175
176                 Hook::callAll('get', $arr);
177                 return $arr;
178         }
179 }