Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
Total | |
100.00% |
1 / 1 |
|
100.00% |
4 / 4 |
CRAP | |
100.00% |
87 / 87 |
UserPanel | |
100.00% |
1 / 1 |
|
100.00% |
4 / 4 |
20 | |
100.00% |
87 / 87 |
all_settings | |
100.00% |
1 / 1 |
1 | |
100.00% |
7 / 7 |
|||
personal_info | |
100.00% |
1 / 1 |
1 | |
100.00% |
16 / 16 |
|||
account | |
100.00% |
1 / 1 |
7 | |
100.00% |
23 / 23 |
|||
notifications | |
100.00% |
1 / 1 |
11 | |
100.00% |
41 / 41 |
1 | <?php |
2 | |
3 | // {{{ License |
4 | |
5 | // This file is part of GNU social - https://www.gnu.org/software/social |
6 | // |
7 | // GNU social is free software: you can redistribute it and/or modify |
8 | // it under the terms of the GNU Affero General Public License as published by |
9 | // the Free Software Foundation, either version 3 of the License, or |
10 | // (at your option) any later version. |
11 | // |
12 | // GNU social 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 GNU social. If not, see <http://www.gnu.org/licenses/>. |
19 | |
20 | // }}} |
21 | |
22 | /** |
23 | * Handle network public feed |
24 | * |
25 | * @package GNUsocial |
26 | * @category Controller |
27 | * |
28 | * @author Hugo Sales <hugo@hsal.es> |
29 | * @author Eliseu Amaro <eliseu@fc.up.pt> |
30 | * @copyright 2020-2021 Free Software Foundation, Inc http://www.fsf.org |
31 | * @license https://www.gnu.org/licenses/agpl.html GNU AGPL v3 or later |
32 | */ |
33 | |
34 | namespace App\Controller; |
35 | |
36 | // {{{ Imports |
37 | |
38 | use App\Core\DB\DB; |
39 | use App\Core\Event; |
40 | use App\Core\Form; |
41 | use function App\Core\I18n\_m; |
42 | use App\Entity\UserNotificationPrefs; |
43 | use App\Util\Common; |
44 | use App\Util\Exception\AuthenticationException; |
45 | use App\Util\Exception\ServerException; |
46 | use App\Util\Form\ArrayTransformer; |
47 | use App\Util\Form\FormFields; |
48 | use App\Util\Formatting; |
49 | use Doctrine\DBAL\Types\Types; |
50 | use Exception; |
51 | use Functional as F; |
52 | use Misd\PhoneNumberBundle\Form\Type\PhoneNumberType; |
53 | use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; |
54 | use Symfony\Component\Form\Extension\Core\Type\CheckboxType; |
55 | use Symfony\Component\Form\Extension\Core\Type\LocaleType; |
56 | use Symfony\Component\Form\Extension\Core\Type\SubmitType; |
57 | use Symfony\Component\Form\Extension\Core\Type\TextareaType; |
58 | use Symfony\Component\Form\Extension\Core\Type\TextType; |
59 | use Symfony\Component\HttpFoundation\Request; |
60 | |
61 | // }}} Imports |
62 | |
63 | class UserPanel extends AbstractController |
64 | { |
65 | /** |
66 | * Return main settings page forms |
67 | * |
68 | * @param Request $request |
69 | * |
70 | * @throws Exception |
71 | * |
72 | * @return array |
73 | */ |
74 | public function all_settings(Request $request) |
75 | { |
76 | $account_form = $this->account($request); |
77 | $personal_form = $this->personal_info($request); |
78 | $notifications_form_array = $this->notifications($request); |
79 | |
80 | return [ |
81 | '_template' => 'settings/base.html.twig', |
82 | 'prof' => $personal_form->createView(), |
83 | 'acc' => $account_form->createView(), |
84 | 'tabbed_forms_notify' => $notifications_form_array, |
85 | ]; |
86 | } |
87 | |
88 | /** |
89 | * Local user personal information panel |
90 | */ |
91 | public function personal_info(Request $request) |
92 | { |
93 | $user = Common::user(); |
94 | $actor = $user->getActor(); |
95 | $extra = ['self_tags' => $actor->getSelfTags()]; |
96 | $form_definition = [ |
97 | ['nickname', TextType::class, ['label' => _m('Nickname'), 'required' => true, 'help' => _m('1-64 lowercase letters or numbers, no punctuation or spaces.')]], |
98 | ['full_name', TextType::class, ['label' => _m('Full Name'), 'required' => false, 'help' => _m('A full name is required, if empty it will be set to your nickname.')]], |
99 | ['homepage', TextType::class, ['label' => _m('Homepage'), 'required' => false, 'help' => _m('URL of your homepage, blog, or profile on another site.')]], |
100 | ['bio', TextareaType::class, ['label' => _m('Bio'), 'required' => false, 'help' => _m('Describe yourself and your interests.')]], |
101 | ['location', TextType::class, ['label' => _m('Location'), 'required' => false, 'help' => _m('Where you are, like "City, State (or Region), Country".')]], |
102 | ['self_tags', TextType::class, ['label' => _m('Self Tags'), 'required' => false, 'help' => _m('Tags for yourself (letters, numbers, -, ., and _), comma- or space-separated.'), 'transformer' => ArrayTransformer::class]], |
103 | ['save_personal_info', SubmitType::class, ['label' => _m('Save personal info')]], |
104 | ]; |
105 | $extra_step = function ($data, $extra_args) use ($user) { |
106 | $user->setNickname($data['nickname']); |
107 | }; |
108 | $form = Form::handle($form_definition, $request, $actor, $extra, $extra_step, [['self_tags' => $extra['self_tags']]]); |
109 | |
110 | return $form; |
111 | } |
112 | |
113 | /** |
114 | * Local user account information panel |
115 | */ |
116 | public function account(Request $request) |
117 | { |
118 | $user = Common::user(); |
119 | // TODO Add support missing settings |
120 | $form = Form::create([ |
121 | ['outgoing_email', TextType::class, ['label' => _m('Outgoing email'), 'required' => true, 'help' => _m('Change the email we use to contact you')]], |
122 | ['incoming_email', TextType::class, ['label' => _m('Incoming email'), 'required' => true, 'help' => _m('Change the email you use to contact us (for posting, for instance)')]], |
123 | ['old_password', TextType::class, ['label' => _m('Old password'), 'required' => false, 'help' => _m('Enter your old password for verification'), 'attr' => ['placeholder' => '********']]], |
124 | FormFields::repeated_password(['required' => false]), |
125 | ['language', LocaleType::class, ['label' => _m('Language'), 'required' => false, 'help' => _m('Your preferred language')]], |
126 | ['phone_number', PhoneNumberType::class, ['label' => _m('Phone number'), 'required' => false, 'help' => _m('Your phone number'), 'data_class' => null]], |
127 | ['save_account_info', SubmitType::class, ['label' => _m('Save account info')]], |
128 | ]); |
129 | |
130 | $form->handleRequest($request); |
131 | if ($form->isSubmitted() && $form->isValid()) { |
132 | $data = $form->getData(); |
133 | |
134 | if (!is_null($data['old_password'])) { |
135 | $data['password'] = $form->get('password')->getData(); |
136 | if (!($user->changePassword($data['old_password'], $data['password']))) { |
137 | throw new AuthenticationException(_m('The provided password is incorrect')); |
138 | } |
139 | } |
140 | |
141 | unset($data['old_password'], $data['password']); |
142 | |
143 | foreach ($data as $key => $val) { |
144 | $method = 'set' . ucfirst(Formatting::snakeCaseToCamelCase($key)); |
145 | if (method_exists($user, $method)) { |
146 | $user->{$method}($val); |
147 | } |
148 | } |
149 | DB::flush(); |
150 | } |
151 | |
152 | return $form; |
153 | } |
154 | |
155 | /** |
156 | * Local user notification settings tabbed panel |
157 | */ |
158 | public function notifications(Request $request) |
159 | { |
160 | $user = Common::user(); |
161 | $schema = DB::getConnection()->getSchemaManager(); |
162 | $platform = $schema->getDatabasePlatform(); |
163 | $columns = Common::arrayRemoveKeys($schema->listTableColumns('user_notification_prefs'), ['user_id', 'transport', 'created', 'modified']); |
164 | $form_defs = ['placeholder' => []]; |
165 | foreach ($columns as $name => $col) { |
166 | $type = $col->getType(); |
167 | $val = $type->convertToPHPValue($col->getDefault(), $platform); |
168 | $type_str = lcfirst(substr((string) $type, 1)); |
169 | $label = str_replace('_', ' ', ucfirst($name)); |
170 | |
171 | $labels = [ |
172 | 'target_gsactor_id' => 'Target Actors', |
173 | 'dm' => 'DM', |
174 | ]; |
175 | |
176 | $help = [ |
177 | 'target_gsactor_id' => 'If specified, these settings apply only to these profiles (comma- or space-separated list)', |
178 | 'activity_by_followed' => 'Notify me when someone I follow has new activity', |
179 | 'mention' => 'Notify me when mentions me in a notice', |
180 | 'reply' => 'Notify me when someone replies to a notice made by me', |
181 | 'follow' => 'Notify me when someone follows me or asks for permission to do so', |
182 | 'favorite' => 'Notify me when someone favorites one of my notices', |
183 | 'nudge' => 'Notify me when someone nudges me', |
184 | 'dm' => 'Notify me when someone sends me a direct message', |
185 | 'post_on_status_change' => 'Post a notice when my status in this service changes', |
186 | 'enable_posting' => 'Enable posting from this service', |
187 | ]; |
188 | |
189 | switch ($type_str) { |
190 | case Types::BOOLEAN: |
191 | $form_defs['placeholder'][$name] = [$name, CheckboxType::class, ['data' => $val, 'label' => _m($labels[$name] ?? $label), 'help' => _m($help[$name])]]; |
192 | break; |
193 | case Types::INTEGER: |
194 | if ($name == 'target_gsactor_id') { |
195 | $form_defs['placeholder'][$name] = [$name, TextType::class, ['data' => $val, 'label' => _m($labels[$name]), 'help' => _m($help[$name])], 'transformer' => ActorArrayTransformer::class]; |
196 | } |
197 | break; |
198 | default: |
199 | // @codeCoverageIgnoreStart |
200 | throw new ServerException(_m('Internal server error')); |
201 | Log::critical("Structure of table user_notification_prefs changed in a way not accounted to in notification settings ({$name}): " . $type_str); |
202 | // @codeCoverageIgnoreEnd |
203 | } |
204 | } |
205 | |
206 | $form_defs['placeholder']['save'] = fn (string $transport, string $form_name) => [$form_name, SubmitType::class, |
207 | ['label' => _m('Save notification settings for {transport}', ['transport' => $transport])], ]; |
208 | |
209 | Event::handle('AddNotificationTransport', [&$form_defs]); |
210 | unset($form_defs['placeholder']); |
211 | |
212 | $tabbed_forms = []; |
213 | foreach ($form_defs as $transport_name => $f) { |
214 | unset($f['save']); |
215 | $form = Form::create($f); |
216 | $tabbed_forms[$transport_name] = $form; |
217 | |
218 | $form->handleRequest($request); |
219 | if ($form->isSubmitted() && $form->isValid()) { |
220 | $data = $form->getData(); |
221 | unset($data['translation_domain']); |
222 | try { |
223 | [$ent, $is_update] = UserNotificationPrefs::createOrUpdate( |
224 | array_merge(['user_id' => $user->getId(), 'transport' => $transport_name], $data), |
225 | find_by_keys: ['user_id', 'transport'] |
226 | ); |
227 | if (!$is_update) { |
228 | DB::persist($ent); |
229 | } |
230 | DB::flush(); |
231 | // @codeCoverageIgnoreStart |
232 | } catch (\Exception $e) { |
233 | // Somehow, the exception doesn't bubble up in phpunit |
234 | dd($data, $e); |
235 | // @codeCoverageIgnoreEnd |
236 | } |
237 | } |
238 | } |
239 | |
240 | $tabbed_forms = F\map($tabbed_forms, function ($f) { |
241 | return $f->createView(); |
242 | }); |
243 | return $tabbed_forms; |
244 | } |
245 | } |