3 * @copyright Copyright (C) 2010-2022, 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/>.
23 use Friendica\BaseModule;
24 use Friendica\Content\Feature;
25 use Friendica\Content\Nav;
26 use Friendica\Core\ACL;
27 use Friendica\Core\Hook;
28 use Friendica\Core\Logger;
29 use Friendica\Core\Renderer;
30 use Friendica\Core\Worker;
31 use Friendica\Database\DBA;
33 use Friendica\Model\Group;
34 use Friendica\Model\Item;
35 use Friendica\Model\Notification;
36 use Friendica\Model\Profile;
37 use Friendica\Model\User;
38 use Friendica\Model\Verb;
39 use Friendica\Module\BaseSettings;
40 use Friendica\Module\Security\Login;
41 use Friendica\Protocol\Activity;
42 use Friendica\Protocol\Email;
43 use Friendica\Util\Temporal;
44 use Friendica\Worker\Delivery;
46 function settings_init(App $a)
49 notice(DI::l10n()->t('Permission denied.'));
53 BaseSettings::createAside();
56 function settings_post(App $a)
58 if (!$a->isLoggedIn()) {
59 notice(DI::l10n()->t('Permission denied.'));
63 if (!empty($_SESSION['submanage'])) {
67 if ((DI::args()->getArgc() > 1) && (DI::args()->getArgv()[1] == 'addon')) {
68 BaseModule::checkFormSecurityTokenRedirectOnError(DI::args()->getQueryString(), 'settings_addon');
70 Hook::callAll('addon_settings_post', $_POST);
71 DI::baseUrl()->redirect(DI::args()->getQueryString());
75 $user = User::getById($a->getLoggedInUserId());
77 if ((DI::args()->getArgc() > 1) && (DI::args()->getArgv()[1] == 'connectors')) {
78 BaseModule::checkFormSecurityTokenRedirectOnError(DI::args()->getQueryString(), 'settings_connectors');
80 if (!empty($_POST['general-submit'])) {
81 DI::pConfig()->set(local_user(), 'system', 'accept_only_sharer', intval($_POST['accept_only_sharer']));
82 DI::pConfig()->set(local_user(), 'system', 'disable_cw', !intval($_POST['enable_cw']));
83 DI::pConfig()->set(local_user(), 'system', 'no_intelligent_shortening', !intval($_POST['enable_smart_shortening']));
84 DI::pConfig()->set(local_user(), 'system', 'simple_shortening', intval($_POST['simple_shortening']));
85 DI::pConfig()->set(local_user(), 'system', 'attach_link_title', intval($_POST['attach_link_title']));
86 DI::pConfig()->set(local_user(), 'ostatus', 'legacy_contact', $_POST['legacy_contact']);
87 } elseif (!empty($_POST['mail-submit'])) {
88 $mail_server = $_POST['mail_server'] ?? '';
89 $mail_port = $_POST['mail_port'] ?? '';
90 $mail_ssl = strtolower(trim($_POST['mail_ssl'] ?? ''));
91 $mail_user = $_POST['mail_user'] ?? '';
92 $mail_pass = trim($_POST['mail_pass'] ?? '');
93 $mail_action = trim($_POST['mail_action'] ?? '');
94 $mail_movetofolder = trim($_POST['mail_movetofolder'] ?? '');
95 $mail_replyto = $_POST['mail_replyto'] ?? '';
96 $mail_pubmail = $_POST['mail_pubmail'] ?? '';
98 if (function_exists('imap_open') && !DI::config()->get('system', 'imap_disabled')) {
99 if (!DBA::exists('mailacct', ['uid' => local_user()])) {
100 DBA::insert('mailacct', ['uid' => local_user()]);
102 if (strlen($mail_pass)) {
104 openssl_public_encrypt($mail_pass, $pass, $user['pubkey']);
105 DBA::update('mailacct', ['pass' => bin2hex($pass)], ['uid' => local_user()]);
108 $r = DBA::update('mailacct', [
109 'server' => $mail_server,
110 'port' => $mail_port,
111 'ssltype' => $mail_ssl,
112 'user' => $mail_user,
113 'action' => $mail_action,
114 'movetofolder' => $mail_movetofolder,
115 'mailbox' => 'INBOX',
116 'reply_to' => $mail_replyto,
117 'pubmail' => $mail_pubmail
118 ], ['uid' => local_user()]);
120 Logger::notice('updating mailaccount', ['response' => $r]);
121 $mailacct = DBA::selectFirst('mailacct', [], ['uid' => local_user()]);
122 if (DBA::isResult($mailacct)) {
123 $mb = Email::constructMailboxName($mailacct);
125 if (strlen($mailacct['server'])) {
127 openssl_private_decrypt(hex2bin($mailacct['pass']), $dcrpass, $user['prvkey']);
128 $mbox = Email::connect($mb, $mail_user, $dcrpass);
131 notice(DI::l10n()->t('Failed to connect with email account using the settings provided.'));
138 Hook::callAll('connector_settings_post', $_POST);
139 DI::baseUrl()->redirect(DI::args()->getQueryString());
143 if ((DI::args()->getArgc() > 1) && (DI::args()->getArgv()[1] === 'features')) {
144 BaseModule::checkFormSecurityTokenRedirectOnError('/settings/features', 'settings_features');
145 foreach ($_POST as $k => $v) {
146 if (strpos($k, 'feature_') === 0) {
147 DI::pConfig()->set(local_user(), 'feature', substr($k, 8), ((intval($v)) ? 1 : 0));
154 function settings_content(App $a)
157 Nav::setSelected('settings');
160 //notice(DI::l10n()->t('Permission denied.'));
161 return Login::form();
164 if (!empty($_SESSION['submanage'])) {
165 notice(DI::l10n()->t('Permission denied.'));
169 if ((DI::args()->getArgc() > 1) && (DI::args()->getArgv()[1] === 'oauth')) {
170 if ((DI::args()->getArgc() > 3) && (DI::args()->getArgv()[2] === 'delete')) {
171 BaseModule::checkFormSecurityTokenRedirectOnError('/settings/oauth', 'settings_oauth', 't');
173 DBA::delete('application-token', ['application-id' => DI::args()->getArgv()[3], 'uid' => local_user()]);
174 DI::baseUrl()->redirect('settings/oauth/', true);
178 $applications = DBA::selectToArray('application-view', ['id', 'uid', 'name', 'website', 'scopes', 'created_at'], ['uid' => local_user()]);
180 $tpl = Renderer::getMarkupTemplate('settings/oauth.tpl');
181 $o .= Renderer::replaceMacros($tpl, [
182 '$form_security_token' => BaseModule::getFormSecurityToken("settings_oauth"),
183 '$baseurl' => DI::baseUrl()->get(true),
184 '$title' => DI::l10n()->t('Connected Apps'),
185 '$name' => DI::l10n()->t('Name'),
186 '$website' => DI::l10n()->t('Home Page'),
187 '$created_at' => DI::l10n()->t('Created'),
188 '$delete' => DI::l10n()->t('Remove authorization'),
189 '$apps' => $applications,
194 if ((DI::args()->getArgc() > 1) && (DI::args()->getArgv()[1] === 'addon')) {
195 $addon_settings_forms = [];
196 foreach (DI::dba()->selectToArray('hook', ['file', 'function'], ['hook' => 'addon_settings']) as $hook) {
198 Hook::callSingle(DI::app(), 'addon_settings', [$hook['file'], $hook['function']], $data);
200 if (!empty($data['href'])) {
201 $tpl = Renderer::getMarkupTemplate('settings/addon/link.tpl');
202 $addon_settings_forms[] = Renderer::replaceMacros($tpl, [
203 '$addon' => $data['addon'],
204 '$title' => $data['title'],
205 '$href' => $data['href'],
207 } elseif(!empty($data['addon'])) {
208 $tpl = Renderer::getMarkupTemplate('settings/addon/panel.tpl');
209 $addon_settings_forms[$data['addon']] = Renderer::replaceMacros($tpl, [
210 '$addon' => $data['addon'],
211 '$title' => $data['title'],
212 '$open' => (DI::args()->getArgv()[2] ?? '') === $data['addon'],
213 '$html' => $data['html'] ?? '',
214 '$submit' => $data['submit'] ?? DI::l10n()->t('Save Settings'),
219 $tpl = Renderer::getMarkupTemplate('settings/addons.tpl');
220 $o .= Renderer::replaceMacros($tpl, [
221 '$form_security_token' => BaseModule::getFormSecurityToken("settings_addon"),
222 '$title' => DI::l10n()->t('Addon Settings'),
223 '$no_addons_settings_configured' => DI::l10n()->t('No Addon settings configured'),
224 '$addon_settings_forms' => $addon_settings_forms,
229 if ((DI::args()->getArgc() > 1) && (DI::args()->getArgv()[1] === 'features')) {
232 $features = Feature::get();
233 foreach ($features as $fname => $fdata) {
235 $arr[$fname][0] = $fdata[0];
236 foreach (array_slice($fdata,1) as $f) {
237 $arr[$fname][1][] = ['feature_' . $f[0], $f[1], Feature::isEnabled(local_user(), $f[0]), $f[2]];
241 $tpl = Renderer::getMarkupTemplate('settings/features.tpl');
242 $o .= Renderer::replaceMacros($tpl, [
243 '$form_security_token' => BaseModule::getFormSecurityToken("settings_features"),
244 '$title' => DI::l10n()->t('Additional Features'),
246 '$submit' => DI::l10n()->t('Save Settings'),
251 if ((DI::args()->getArgc() > 1) && (DI::args()->getArgv()[1] === 'connectors')) {
252 $accept_only_sharer = intval(DI::pConfig()->get(local_user(), 'system', 'accept_only_sharer'));
253 $enable_cw = !intval(DI::pConfig()->get(local_user(), 'system', 'disable_cw'));
254 $enable_smart_shortening = !intval(DI::pConfig()->get(local_user(), 'system', 'no_intelligent_shortening'));
255 $simple_shortening = intval(DI::pConfig()->get(local_user(), 'system', 'simple_shortening'));
256 $attach_link_title = intval(DI::pConfig()->get(local_user(), 'system', 'attach_link_title'));
257 $legacy_contact = DI::pConfig()->get(local_user(), 'ostatus', 'legacy_contact');
259 if (!empty($legacy_contact)) {
260 /// @todo Isn't it supposed to be a $a->internalRedirect() call?
261 DI::page()['htmlhead'] = '<meta http-equiv="refresh" content="0; URL=' . DI::baseUrl().'/ostatus_subscribe?url=' . urlencode($legacy_contact) . '">';
264 $connector_settings_forms = [];
265 foreach (DI::dba()->selectToArray('hook', ['file', 'function'], ['hook' => 'connector_settings']) as $hook) {
267 Hook::callSingle(DI::app(), 'connector_settings', [$hook['file'], $hook['function']], $data);
269 $tpl = Renderer::getMarkupTemplate('settings/addon/connector.tpl');
270 $connector_settings_forms[$data['connector']] = Renderer::replaceMacros($tpl, [
271 '$connector' => $data['connector'],
272 '$title' => $data['title'],
273 '$image' => $data['image'] ?? '',
274 '$enabled' => $data['enabled'] ?? true,
275 '$open' => (DI::args()->getArgv()[2] ?? '') === $data['connector'],
276 '$html' => $data['html'] ?? '',
277 '$submit' => $data['submit'] ?? DI::l10n()->t('Save Settings'),
281 if ($a->isSiteAdmin()) {
282 $diasp_enabled = DI::l10n()->t('Built-in support for %s connectivity is %s', DI::l10n()->t('Diaspora (Socialhome, Hubzilla)'), ((DI::config()->get('system', 'diaspora_enabled')) ? DI::l10n()->t('enabled') : DI::l10n()->t('disabled')));
283 $ostat_enabled = DI::l10n()->t('Built-in support for %s connectivity is %s', DI::l10n()->t('OStatus (GNU Social)'), ((DI::config()->get('system', 'ostatus_disabled')) ? DI::l10n()->t('disabled') : DI::l10n()->t('enabled')));
289 $mail_disabled = ((function_exists('imap_open') && (!DI::config()->get('system', 'imap_disabled'))) ? 0 : 1);
290 if (!$mail_disabled) {
291 $mailacct = DBA::selectFirst('mailacct', [], ['uid' => local_user()]);
296 $mail_server = $mailacct['server'] ?? '';
297 $mail_port = (!empty($mailacct['port']) && is_numeric($mailacct['port'])) ? (int)$mailacct['port'] : '';
298 $mail_ssl = $mailacct['ssltype'] ?? '';
299 $mail_user = $mailacct['user'] ?? '';
300 $mail_replyto = $mailacct['reply_to'] ?? '';
301 $mail_pubmail = $mailacct['pubmail'] ?? 0;
302 $mail_action = $mailacct['action'] ?? 0;
303 $mail_movetofolder = $mailacct['movetofolder'] ?? '';
304 $mail_chk = $mailacct['last_check'] ?? DBA::NULL_DATETIME;
307 $tpl = Renderer::getMarkupTemplate('settings/connectors.tpl');
309 $mail_disabled_message = ($mail_disabled ? DI::l10n()->t('Email access is disabled on this site.') : '');
311 $ssl_options = ['TLS' => 'TLS', 'SSL' => 'SSL'];
313 if (DI::config()->get('system', 'insecure_imap')) {
314 $ssl_options['notls'] = DI::l10n()->t('None');
317 $o .= Renderer::replaceMacros($tpl, [
318 '$form_security_token' => BaseModule::getFormSecurityToken("settings_connectors"),
320 '$title' => DI::l10n()->t('Social Networks'),
322 '$diasp_enabled' => $diasp_enabled,
323 '$ostat_enabled' => $ostat_enabled,
325 '$general_settings' => DI::l10n()->t('General Social Media Settings'),
326 '$accept_only_sharer' => [
327 'accept_only_sharer',
328 DI::l10n()->t('Followed content scope'),
330 DI::l10n()->t('By default, conversations in which your follows participated but didn\'t start will be shown in your timeline. You can turn this behavior off, or expand it to the conversations in which your follows liked a post.'),
332 Item::COMPLETION_NONE => DI::l10n()->t('Only conversations my follows started'),
333 Item::COMPLETION_COMMENT => DI::l10n()->t('Conversations my follows started or commented on (default)'),
334 Item::COMPLETION_LIKE => DI::l10n()->t('Any conversation my follows interacted with, including likes'),
337 '$enable_cw' => ['enable_cw', DI::l10n()->t('Enable Content Warning'), $enable_cw, DI::l10n()->t('Users on networks like Mastodon or Pleroma are able to set a content warning field which collapse their post by default. This enables the automatic collapsing instead of setting the content warning as the post title. Doesn\'t affect any other content filtering you eventually set up.')],
338 '$enable_smart_shortening' => ['enable_smart_shortening', DI::l10n()->t('Enable intelligent shortening'), $enable_smart_shortening, DI::l10n()->t('Normally the system tries to find the best link to add to shortened posts. If disabled, every shortened post will always point to the original friendica post.')],
339 '$simple_shortening' => ['simple_shortening', DI::l10n()->t('Enable simple text shortening'), $simple_shortening, DI::l10n()->t('Normally the system shortens posts at the next line feed. If this option is enabled then the system will shorten the text at the maximum character limit.')],
340 '$attach_link_title' => ['attach_link_title', DI::l10n()->t('Attach the link title'), $attach_link_title, DI::l10n()->t('When activated, the title of the attached link will be added as a title on posts to Diaspora. This is mostly helpful with "remote-self" contacts that share feed content.')],
341 '$legacy_contact' => ['legacy_contact', DI::l10n()->t('Your legacy ActivityPub/GNU Social account'), $legacy_contact, DI::l10n()->t("If you enter your old account name from an ActivityPub based system or your GNU Social/Statusnet account name here (in the format user@domain.tld), your contacts will be added automatically. The field will be emptied when done.")],
343 '$repair_ostatus_url' => DI::baseUrl() . '/repair_ostatus',
344 '$repair_ostatus_text' => DI::l10n()->t('Repair OStatus subscriptions'),
346 '$connector_settings_forms' => $connector_settings_forms,
348 '$h_mail' => DI::l10n()->t('Email/Mailbox Setup'),
349 '$mail_desc' => DI::l10n()->t("If you wish to communicate with email contacts using this service \x28optional\x29, please specify how to connect to your mailbox."),
350 '$mail_lastcheck' => ['mail_lastcheck', DI::l10n()->t('Last successful email check:'), $mail_chk, ''],
351 '$mail_disabled' => $mail_disabled_message,
352 '$mail_server' => ['mail_server', DI::l10n()->t('IMAP server name:'), $mail_server, ''],
353 '$mail_port' => ['mail_port', DI::l10n()->t('IMAP port:'), $mail_port, ''],
354 '$mail_ssl' => ['mail_ssl', DI::l10n()->t('Security:'), strtoupper($mail_ssl), '', $ssl_options],
355 '$mail_user' => ['mail_user', DI::l10n()->t('Email login name:'), $mail_user, ''],
356 '$mail_pass' => ['mail_pass', DI::l10n()->t('Email password:'), '', ''],
357 '$mail_replyto' => ['mail_replyto', DI::l10n()->t('Reply-to address:'), $mail_replyto, 'Optional'],
358 '$mail_pubmail' => ['mail_pubmail', DI::l10n()->t('Send public posts to all email contacts:'), $mail_pubmail, ''],
359 '$mail_action' => ['mail_action', DI::l10n()->t('Action after import:'), $mail_action, '', [0 => DI::l10n()->t('None'), 1 => DI::l10n()->t('Delete'), 2 => DI::l10n()->t('Mark as seen'), 3 => DI::l10n()->t('Move to folder')]],
360 '$mail_movetofolder' => ['mail_movetofolder', DI::l10n()->t('Move to folder:'), $mail_movetofolder, ''],
361 '$submit' => DI::l10n()->t('Save Settings'),
364 Hook::callAll('display_settings', $o);