3 * @copyright Copyright (C) 2010-2023, the Friendica project
5 * @license GNU AGPL version 3 or any later version
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.
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.
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/>.
22 namespace Friendica\Module\Settings;
25 use Friendica\Content\Conversation\Collection\Timelines;
26 use Friendica\Content\Text\BBCode;
27 use Friendica\Content\Conversation\Factory\Channel as ChannelFactory;
28 use Friendica\Content\Conversation\Factory\Community as CommunityFactory;
29 use Friendica\Content\Conversation\Factory\Network as NetworkFactory;
30 use Friendica\Content\Conversation\Factory\Timeline as TimelineFactory;
31 use Friendica\Core\Config\Capability\IManageConfigValues;
32 use Friendica\Core\Hook;
33 use Friendica\Core\L10n;
34 use Friendica\Core\PConfig\Capability\IManagePersonalConfigValues;
35 use Friendica\Core\Renderer;
36 use Friendica\Core\Session\Capability\IHandleUserSessions;
37 use Friendica\Core\Theme;
38 use Friendica\Model\User;
39 use Friendica\Module\BaseSettings;
40 use Friendica\Module\Response;
41 use Friendica\Navigation\SystemMessages;
42 use Friendica\Network\HTTPException;
43 use Friendica\Util\Profiler;
44 use Psr\Log\LoggerInterface;
47 * Module to update user settings
49 class Display extends BaseSettings
51 /** @var IManageConfigValues */
53 /** @var IManagePersonalConfigValues */
57 /** @var SystemMessages */
58 private $systemMessages;
59 /** @var ChannelFactory */
61 /** @var CommunityFactory */
63 /** @var NetworkFactory */
65 /** @var TimelineFactory */
68 public function __construct(NetworkFactory $network, CommunityFactory $community, ChannelFactory $channel, TimelineFactory $timeline, SystemMessages $systemMessages, App $app, IManagePersonalConfigValues $pConfig, IManageConfigValues $config, IHandleUserSessions $session, App\Page $page, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, array $server, array $parameters = [])
70 parent::__construct($session, $page, $l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
72 $this->config = $config;
73 $this->pConfig = $pConfig;
75 $this->systemMessages = $systemMessages;
76 $this->timeline = $timeline;
77 $this->channel = $channel;
78 $this->community = $community;
79 $this->network = $network;
82 protected function post(array $request = [])
84 $uid = $this->session->getLocalUserId();
86 throw new HTTPException\ForbiddenException($this->t('Permission denied.'));
89 self::checkFormSecurityTokenRedirectOnError('/settings/display', 'settings_display');
91 $user = User::getById($uid);
93 $theme = !empty($request['theme']) ? trim($request['theme']) : $user['theme'];
94 $mobile_theme = !empty($request['mobile_theme']) ? trim($request['mobile_theme']) : '';
95 $enable_smile = !empty($request['enable_smile']) ? intval($request['enable_smile']) : 0;
96 $enable = !empty($request['enable']) ? $request['enable'] : [];
97 $bookmark = !empty($request['bookmark']) ? $request['bookmark'] : [];
98 $channel_languages = !empty($request['channel_languages']) ? $request['channel_languages'] : [];
99 $first_day_of_week = !empty($request['first_day_of_week']) ? intval($request['first_day_of_week']) : 0;
100 $calendar_default_view = !empty($request['calendar_default_view']) ? trim($request['calendar_default_view']) : 'month';
101 $infinite_scroll = !empty($request['infinite_scroll']) ? intval($request['infinite_scroll']) : 0;
102 $enable_smart_threading = !empty($request['enable_smart_threading']) ? intval($request['enable_smart_threading']) : 0;
103 $enable_dislike = !empty($request['enable_dislike']) ? intval($request['enable_dislike']) : 0;
104 $display_resharer = !empty($request['display_resharer']) ? intval($request['display_resharer']) : 0;
105 $stay_local = !empty($request['stay_local']) ? intval($request['stay_local']) : 0;
106 $preview_mode = !empty($request['preview_mode']) ? intval($request['preview_mode']) : 0;
107 $browser_update = !empty($request['browser_update']) ? intval($request['browser_update']) : 0;
108 if ($browser_update != -1) {
109 $browser_update = $browser_update * 1000;
110 if ($browser_update < 10000) {
111 $browser_update = 10000;
115 $enabled_timelines = [];
116 foreach ($enable as $code => $enabled) {
118 $enabled_timelines[] = $code;
122 $network_timelines = [];
123 foreach ($bookmark as $code => $bookmarked) {
125 $network_timelines[] = $code;
129 $itemspage_network = !empty($request['itemspage_network']) ?
130 intval($request['itemspage_network']) :
131 $this->config->get('system', 'itemspage_network');
132 if ($itemspage_network > 100) {
133 $itemspage_network = 100;
135 $itemspage_mobile_network = !empty($request['itemspage_mobile_network']) ?
136 intval($request['itemspage_mobile_network']) :
137 $this->config->get('system', 'itemspage_network_mobile');
138 if ($itemspage_mobile_network > 100) {
139 $itemspage_mobile_network = 100;
142 if ($mobile_theme !== '') {
143 $this->pConfig->set($uid, 'system', 'mobile_theme', $mobile_theme);
146 $this->pConfig->set($uid, 'system', 'itemspage_network' , $itemspage_network);
147 $this->pConfig->set($uid, 'system', 'itemspage_mobile_network', $itemspage_mobile_network);
148 $this->pConfig->set($uid, 'system', 'update_interval' , $browser_update);
149 $this->pConfig->set($uid, 'system', 'no_smilies' , !$enable_smile);
150 $this->pConfig->set($uid, 'system', 'infinite_scroll' , $infinite_scroll);
151 $this->pConfig->set($uid, 'system', 'no_smart_threading' , !$enable_smart_threading);
152 $this->pConfig->set($uid, 'system', 'hide_dislike' , !$enable_dislike);
153 $this->pConfig->set($uid, 'system', 'display_resharer' , $display_resharer);
154 $this->pConfig->set($uid, 'system', 'stay_local' , $stay_local);
155 $this->pConfig->set($uid, 'system', 'preview_mode' , $preview_mode);
157 $this->pConfig->set($uid, 'system', 'network_timelines' , $network_timelines);
158 $this->pConfig->set($uid, 'system', 'enabled_timelines' , $enabled_timelines);
159 $this->pConfig->set($uid, 'channel', 'languages' , $channel_languages);
161 $this->pConfig->set($uid, 'calendar', 'first_day_of_week' , $first_day_of_week);
162 $this->pConfig->set($uid, 'calendar', 'default_view' , $calendar_default_view);
164 if (in_array($theme, Theme::getAllowedList())) {
165 if ($theme == $user['theme']) {
166 // call theme_post only if theme has not been changed
167 if ($themeconfigfile = Theme::getConfigFile($theme)) {
168 require_once $themeconfigfile;
169 theme_post($this->app);
172 User::update(['theme' => $theme], $uid);
175 $this->systemMessages->addNotice($this->t('The theme you chose isn\'t available.'));
178 Hook::callAll('display_settings_post', $request);
180 $this->baseUrl->redirect('settings/display');
183 protected function content(array $request = []): string
187 $uid = $this->session->getLocalUserId();
189 throw new HTTPException\ForbiddenException($this->t('Permission denied.'));
192 $default_theme = $this->config->get('system', 'theme');
193 if (!$default_theme) {
194 $default_theme = 'default';
197 $default_mobile_theme = $this->config->get('system', 'mobile-theme');
198 if (!$default_mobile_theme) {
199 $default_mobile_theme = 'none';
202 $user = User::getById($uid);
204 $allowed_themes = Theme::getAllowedList();
207 $mobile_themes = ['---' => $this->t('No special theme for mobile devices')];
208 foreach ($allowed_themes as $theme) {
209 $is_experimental = file_exists('view/theme/' . $theme . '/experimental');
210 $is_unsupported = file_exists('view/theme/' . $theme . '/unsupported');
211 $is_mobile = file_exists('view/theme/' . $theme . '/mobile');
212 if (!$is_experimental || $this->config->get('experimental', 'exp_themes')) {
213 $theme_name = ucfirst($theme);
214 if ($is_unsupported) {
215 $theme_name = $this->t('%s - (Unsupported)', $theme_name);
216 } elseif ($is_experimental) {
217 $theme_name = $this->t('%s - (Experimental)', $theme_name);
221 $mobile_themes[$theme] = $theme_name;
223 $themes[$theme] = $theme_name;
228 $theme_selected = $user['theme'] ?: $default_theme;
229 $mobile_theme_selected = $this->session->get('mobile-theme', $default_mobile_theme);
231 $itemspage_network = intval($this->pConfig->get($uid, 'system', 'itemspage_network'));
232 $itemspage_network = (($itemspage_network > 0 && $itemspage_network < 101) ? $itemspage_network : $this->config->get('system', 'itemspage_network'));
233 $itemspage_mobile_network = intval($this->pConfig->get($uid, 'system', 'itemspage_mobile_network'));
234 $itemspage_mobile_network = (($itemspage_mobile_network > 0 && $itemspage_mobile_network < 101) ? $itemspage_mobile_network : $this->config->get('system', 'itemspage_network_mobile'));
236 $browser_update = intval($this->pConfig->get($uid, 'system', 'update_interval'));
237 if ($browser_update != -1) {
238 $browser_update = (($browser_update == 0) ? 40 : $browser_update / 1000); // default if not set: 40 seconds
241 $enable_smile = !$this->pConfig->get($uid, 'system', 'no_smilies', 0);
242 $infinite_scroll = $this->pConfig->get($uid, 'system', 'infinite_scroll', 0);
243 $enable_smart_threading = !$this->pConfig->get($uid, 'system', 'no_smart_threading', 0);
244 $enable_dislike = !$this->pConfig->get($uid, 'system', 'hide_dislike', 0);
245 $display_resharer = $this->pConfig->get($uid, 'system', 'display_resharer', 0);
246 $stay_local = $this->pConfig->get($uid, 'system', 'stay_local', 0);
248 $preview_mode = $this->pConfig->get($uid, 'system', 'preview_mode', BBCode::PREVIEW_LARGE);
250 BBCode::PREVIEW_NONE => $this->t('No preview'),
251 BBCode::PREVIEW_NO_IMAGE => $this->t('No image'),
252 BBCode::PREVIEW_SMALL => $this->t('Small Image'),
253 BBCode::PREVIEW_LARGE => $this->t('Large Image'),
256 $bookmarked_timelines = $this->pConfig->get($uid, 'system', 'network_timelines', $this->getAvailableTimelines($uid, true)->column('code'));
257 $enabled_timelines = $this->pConfig->get($uid, 'system', 'enabled_timelines', $this->getAvailableTimelines($uid, false)->column('code'));
258 $channel_languages = $this->pConfig->get($uid, 'channel', 'languages', [User::getLanguageCode($uid)]);
259 $languages = $this->l10n->getAvailableLanguages(true);
262 foreach ($this->getAvailableTimelines($uid) as $timeline) {
264 'label' => $timeline->label,
265 'description' => $timeline->description,
266 'enable' => ["enable{$timeline->code}", '', in_array($timeline->code, $enabled_timelines)],
267 'bookmark' => ["bookmark{$timeline->code}", '', in_array($timeline->code, $bookmarked_timelines)],
271 $first_day_of_week = $this->pConfig->get($uid, 'calendar', 'first_day_of_week', 0);
273 0 => $this->t('Sunday'),
274 1 => $this->t('Monday'),
275 2 => $this->t('Tuesday'),
276 3 => $this->t('Wednesday'),
277 4 => $this->t('Thursday'),
278 5 => $this->t('Friday'),
279 6 => $this->t('Saturday')
282 $calendar_default_view = $this->pConfig->get($uid, 'calendar', 'default_view', 'month');
284 'month' => $this->t('month'),
285 'agendaWeek' => $this->t('week'),
286 'agendaDay' => $this->t('day'),
287 'listMonth' => $this->t('list')
291 if ($themeconfigfile = Theme::getConfigFile($theme_selected)) {
292 require_once $themeconfigfile;
293 $theme_config = theme_content($this->app);
296 $tpl = Renderer::getMarkupTemplate('settings/display.tpl');
297 return Renderer::replaceMacros($tpl, [
298 '$ptitle' => $this->t('Display Settings'),
299 '$submit' => $this->t('Save Settings'),
300 '$d_tset' => $this->t('General Theme Settings'),
301 '$d_ctset' => $this->t('Custom Theme Settings'),
302 '$d_cset' => $this->t('Content Settings'),
303 '$stitle' => $this->t('Theme settings'),
304 '$timeline_title' => $this->t('Timelines'),
305 '$channel_title' => $this->t('Channels'),
306 '$calendar_title' => $this->t('Calendar'),
308 '$form_security_token' => self::getFormSecurityToken('settings_display'),
311 '$theme' => ['theme', $this->t('Display Theme:'), $theme_selected, '', $themes, true],
312 '$mobile_theme' => ['mobile_theme', $this->t('Mobile Theme:'), $mobile_theme_selected, '', $mobile_themes, false],
313 '$theme_config' => $theme_config,
315 '$itemspage_network' => ['itemspage_network' , $this->t('Number of items to display per page:'), $itemspage_network, $this->t('Maximum of 100 items')],
316 '$itemspage_mobile_network' => ['itemspage_mobile_network', $this->t('Number of items to display per page when viewed from mobile device:'), $itemspage_mobile_network, $this->t('Maximum of 100 items')],
317 '$ajaxint' => ['browser_update' , $this->t('Update browser every xx seconds'), $browser_update, $this->t('Minimum of 10 seconds. Enter -1 to disable it.')],
318 '$enable_smile' => ['enable_smile' , $this->t('Display emoticons'), $enable_smile, $this->t('When enabled, emoticons are replaced with matching symbols.')],
319 '$infinite_scroll' => ['infinite_scroll' , $this->t('Infinite scroll'), $infinite_scroll, $this->t('Automatic fetch new items when reaching the page end.')],
320 '$enable_smart_threading' => ['enable_smart_threading' , $this->t('Enable Smart Threading'), $enable_smart_threading, $this->t('Enable the automatic suppression of extraneous thread indentation.')],
321 '$enable_dislike' => ['enable_dislike' , $this->t('Display the Dislike feature'), $enable_dislike, $this->t('Display the Dislike button and dislike reactions on posts and comments.')],
322 '$display_resharer' => ['display_resharer' , $this->t('Display the resharer'), $display_resharer, $this->t('Display the first resharer as icon and text on a reshared item.')],
323 '$stay_local' => ['stay_local' , $this->t('Stay local'), $stay_local, $this->t("Don't go to a remote system when following a contact link.")],
324 '$preview_mode' => ['preview_mode' , $this->t('Link preview mode'), $preview_mode, $this->t('Appearance of the link preview that is added to each post with a link.'), $preview_modes, false],
326 '$timeline_label' => $this->t('Label'),
327 '$timeline_descriptiom' => $this->t('Description'),
328 '$timeline_enable' => $this->t('Enable'),
329 '$timeline_bookmark' => $this->t('Bookmark'),
330 '$timelines' => $timelines,
331 '$timeline_explanation' => $this->t('Enable timelines that you want to see in the channels widget. Bookmark timelines that you want to see in the top menu.'),
333 '$channel_languages' => ['channel_languages[]', $this->t('Channel languages:'), $channel_languages, $this->t('Select all languages that you want to see in your channels.'), $languages, 'multiple'],
335 '$first_day_of_week' => ['first_day_of_week' , $this->t('Beginning of week:') , $first_day_of_week , '', $weekdays , false],
336 '$calendar_default_view' => ['calendar_default_view', $this->t('Default calendar view:'), $calendar_default_view, '', $calendarViews, false],
340 private function getAvailableTimelines(int $uid, bool $only_network = false): Timelines
344 foreach ($this->network->getTimelines('') as $channel) {
345 $timelines[] = $channel;
349 return new Timelines($timelines);
352 foreach ($this->channel->getForUser($uid) as $channel) {
353 $timelines[] = $channel;
356 foreach ($this->community->getTimelines(true) as $community) {
357 $timelines[] = $community;
360 return new Timelines($timelines);