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 | } |