ILIAS  trunk Revision v11.0_alpha-3011-gc6b235a2e85
class.ilContactGUI.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
24use ILIAS\Refinery\Factory as Refinery;
28
36{
37 final public const string CONTACTS_VIEW_GALLERY = 'buddy_view_gallery';
38 final public const string CONTACTS_VIEW_TABLE = 'buddy_view_table';
39
40 private readonly \ILIAS\HTTP\GlobalHttpState $http;
43 protected ilLanguage $lng;
45 protected ilHelpGUI $help;
48 protected ilObjUser $user;
51 protected bool $has_sub_tabs = false;
52 protected Refinery $refinery;
53 protected \ILIAS\UI\Factory $ui_factory;
54 protected \ILIAS\UI\Renderer $ui_renderer;
57 private array $view_mode_options = [
58 self::CONTACTS_VIEW_TABLE => self::CONTACTS_VIEW_TABLE,
59 self::CONTACTS_VIEW_GALLERY => self::CONTACTS_VIEW_GALLERY,
60 ];
61
62 public function __construct(
63 string $format_mail_class = ilFormatMail::class,
64 string $relations_table_class = RelationsTable::class
65 ) {
66 global $DIC;
67
68 $this->tpl = $DIC['tpl'];
69 $this->ctrl = $DIC['ilCtrl'];
70 $this->lng = $DIC['lng'];
71 $this->tabs_gui = $DIC['ilTabs'];
72 $this->help = $DIC['ilHelp'];
73 $this->toolbar = $DIC['ilToolbar'];
74 $this->user = $DIC['ilUser'];
75 $this->error = $DIC['ilErr'];
76 $this->rbacsystem = $DIC['rbacsystem'];
77 $this->http = $DIC->http();
78 $this->refinery = $DIC->refinery();
79 $this->ui_factory = $DIC->ui()->factory();
80 $this->ui_renderer = $DIC->ui()->renderer();
81
82 $this->ctrl->saveParameter($this, "mobj_id");
83
84 $this->umail = new $format_mail_class($this->user->getId());
85 $this->relations_table = new $relations_table_class(
86 $this->ui_factory,
87 $this->lng,
88 $DIC->uiService(),
89 $this->http
90 );
91 $this->lng->loadLanguageModule('buddysystem');
92 }
93
94 public function getUnsafeGetCommands(): array
95 {
96 return [
97 'updateState'
98 ];
99 }
100
101 public function getSafePostCommands(): array
102 {
103 return [];
104 }
105
106 public function executeCommand(): bool
107 {
108 $this->showSubTabs();
109
110 $forward_class = $this->ctrl->getNextClass($this) ?? '';
111
112 $this->umail->persistToStage($this->user->getId(), [], '', '', '', '', '', false);
113
114 switch (strtolower($forward_class)) {
115 case strtolower(ilMailSearchCoursesGUI::class):
116 $this->activateTab('mail_my_courses');
117
118 $this->ctrl->setReturn($this, "showContacts");
119 $this->ctrl->forwardCommand(new ilMailSearchCoursesGUI());
120 break;
121
122 case strtolower(ilMailSearchGroupsGUI::class):
123 $this->activateTab('mail_my_groups');
124
125 $this->ctrl->setReturn($this, "showContacts");
126 $this->ctrl->forwardCommand(new ilMailSearchGroupsGUI());
127 break;
128
129 case strtolower(ilMailingListsGUI::class):
130 $this->activateTab('mail_my_mailing_lists');
131
132 $this->ctrl->setReturn($this, "showContacts");
133 $this->ctrl->forwardCommand(new ilMailingListsGUI());
134 break;
135
136 case strtolower(ilUsersGalleryGUI::class):
137 if (!ilBuddySystem::getInstance()->isEnabled()) {
138 $this->error->raiseError($this->lng->txt('msg_no_perm_read'), $this->error->MESSAGE);
139 }
140
141 $this->tabs_gui->activateSubTab('buddy_view_gallery');
142 $this->activateTab('my_contacts');
143 $this->ctrl->forwardCommand(new ilUsersGalleryGUI(new ilUsersGalleryContacts()));
144 $this->tpl->printToStdout();
145 break;
146
147 case strtolower(PublicProfileGUI::class):
148 $profile_gui = new PublicProfileGUI(
149 $this->http->wrapper()->query()->retrieve('user', $this->refinery->kindlyTo()->int())
150 );
151 $profile_gui->setBackUrl($this->ctrl->getLinkTarget($this, 'showContacts'));
152 $this->ctrl->forwardCommand($profile_gui);
153 $this->tpl->printToStdout();
154 break;
155
156 default:
157 $this->activateTab('mail_my_entries');
158
159 if (!($cmd = $this->ctrl->getCmd())) {
160 if (ilBuddySystem::getInstance()->isEnabled()) {
161 $cmd = 'showContacts';
162 } else {
163 $this->ctrl->redirectByClass(ilMailSearchCoursesGUI::class);
164 }
165 }
166
167 $this->$cmd();
168 break;
169 }
170 return true;
171 }
172
173
174 private function showSubTabs(): void
175 {
176 $galleryCmdClasses = array_map('strtolower', [ilUsersGalleryGUI::class, self::class]);
177 if ($this->tabs_gui->hasTabs()) {
178 if (ilBuddySystem::getInstance()->isEnabled()) {
179 $this->tabs_gui->addSubTab(
180 'my_contacts',
181 $this->lng->txt('my_contacts'),
182 $this->ctrl->getLinkTarget($this)
183 );
184
185 if (in_array(strtolower($this->ctrl->getCmdClass() ?? ''), $galleryCmdClasses, true)) {
186 $mode_options = array_combine(
187 array_map(
188 fn(string $mode): string => $this->lng->txt($mode),
189 array_keys($this->view_mode_options)
190 ),
191 array_map(
192 function (string $mode): string {
193 $this->ctrl->setParameter($this, 'contacts_view', $mode);
194 $url = $this->ctrl->getFormAction($this, 'changeContactsView');
195 $this->ctrl->setParameter($this, 'contacts_view', null);
196
197 return $url;
198 },
199 array_keys($this->view_mode_options)
200 ),
201 );
202
203 $active_mode = strtolower($this->ctrl->getCmdClass() ?? '') === strtolower(ilUsersGalleryGUI::class)
206
207 $sortViewControl = $this->ui_factory
208 ->viewControl()
209 ->mode($mode_options, $this->lng->txt($active_mode))
210 ->withActive($this->lng->txt($active_mode));
211 $this->toolbar->addComponent($sortViewControl);
212 }
213
214 if (
215 count(ilBuddyList::getInstanceByGlobalUser()->getLinkedRelations()) > 0 ||
216 (new ilMailingLists($this->user))->hasAny()
217 ) {
218 $this->tabs_gui->addSubTab(
219 'mail_my_mailing_lists',
220 $this->lng->txt('mail_my_mailing_lists'),
221 $this->ctrl->getLinkTargetByClass(ilMailingListsGUI::class)
222 );
223 }
224 }
225
226 $this->tabs_gui->addSubTab(
227 'mail_my_courses',
228 $this->lng->txt('mail_my_courses'),
229 $this->ctrl->getLinkTargetByClass(ilMailSearchCoursesGUI::class)
230 );
231 $this->tabs_gui->addSubTab(
232 'mail_my_groups',
233 $this->lng->txt('mail_my_groups'),
234 $this->ctrl->getLinkTargetByClass(ilMailSearchGroupsGUI::class)
235 );
236 $this->has_sub_tabs = true;
237 } else {
238 $this->tpl->setTitleIcon(ilUtil::getImagePath('standard/icon_cadm.svg'));
239
240 $this->help->setScreenIdComponent('contacts');
241
242 if (ilBuddySystem::getInstance()->isEnabled()) {
243 $this->tabs_gui->addTab(
244 'my_contacts',
245 $this->lng->txt('my_contacts'),
246 $this->ctrl->getLinkTarget($this)
247 );
248
249 if (in_array(strtolower($this->ctrl->getCmdClass() ?? ''), $galleryCmdClasses, true)) {
250 $this->tabs_gui->addSubTab(
251 'buddy_view_table',
252 $this->lng->txt('buddy_view_table'),
253 $this->ctrl->getLinkTarget($this)
254 );
255 $this->tabs_gui->addSubTab(
256 'buddy_view_gallery',
257 $this->lng->txt('buddy_view_gallery'),
258 $this->ctrl->getLinkTargetByClass(ilUsersGalleryGUI::class)
259 );
260 }
261
262 if (
263 count(ilBuddyList::getInstanceByGlobalUser()->getLinkedRelations()) > 0 ||
264 (new ilMailingLists($this->user))->hasAny()
265 ) {
266 $this->tabs_gui->addTab(
267 'mail_my_mailing_lists',
268 $this->lng->txt('mail_my_mailing_lists'),
269 $this->ctrl->getLinkTargetByClass(ilMailingListsGUI::class)
270 );
271 }
272 }
273
274 $this->tabs_gui->addTab(
275 'mail_my_courses',
276 $this->lng->txt('mail_my_courses'),
277 $this->ctrl->getLinkTargetByClass(ilMailSearchCoursesGUI::class)
278 );
279 $this->tabs_gui->addTab(
280 'mail_my_groups',
281 $this->lng->txt('mail_my_groups'),
282 $this->ctrl->getLinkTargetByClass(ilMailSearchGroupsGUI::class)
283 );
284 }
285 }
286
287 protected function activateTab(string $a_id): void
288 {
289 if ($this->has_sub_tabs) {
290 $this->tabs_gui->activateSubTab($a_id);
291 } else {
292 $this->tabs_gui->activateTab($a_id);
293 }
294 }
295
299 protected function changeContactsView(): void
300 {
301 if (!ilBuddySystem::getInstance()->isEnabled()) {
302 $this->error->raiseError($this->lng->txt('msg_no_perm_read'), $this->error->MESSAGE);
303 }
304
305 $contacts_view = $this->http->wrapper()->query()->retrieve(
306 'contacts_view',
307 $this->refinery->byTrying([
308 $this->refinery->kindlyTo()->string(),
309 $this->refinery->always(self::CONTACTS_VIEW_TABLE)
310 ])
311 );
312
313 switch ($contacts_view) {
315 $this->ctrl->redirectByClass(ilUsersGalleryGUI::class);
316
317 // no break
319 default:
320 $this->ctrl->redirect($this);
321 }
322 }
323
324 protected function showContacts(): void
325 {
326 if (!ilBuddySystem::getInstance()->isEnabled()) {
327 $this->error->raiseError($this->lng->txt('msg_no_perm_read'), $this->error->MESSAGE);
328 }
329
330 $this->tabs_gui->activateSubTab('buddy_view_table');
331 $this->activateTab('my_contacts');
332
333 $content = $this->chatroomInvitationMessage();
334 $action = $this->contactAction();
335 $chat_allowed = (bool) (new ilSetting('chatroom'))->get('chat_enabled', '0');
336 $mail_allowed = $this->rbacsystem->checkAccess(
337 'internal_mail',
339 );
340
341 $content = array_merge($content, $this->relations_table->build(array_merge(
342 $chat_allowed ? ['chat' => $action('standard', 'invite_to_chat', 'inviteToChat')] : [],
343 $mail_allowed ? ['mail' => $action('standard', 'send_mail', 'mailToUsers')] : [],
344 ), $this->ctrl->getLinkTarget($this, 'showContacts'), $action));
345
346 $this->tpl->setContent($this->ui_renderer->render($content));
347 $this->tpl->printToStdout();
348 }
349
350 private function updateState(): void
351 {
352 $get = $this->http->wrapper()->query()->retrieve(...);
353
354 $user_ids = $get('contact_user_ids', $this->refinery->byTrying([
355 $this->refinery->null(),
356 $this->refinery->kindlyTo()->listOf($this->refinery->byTrying([
357 $this->refinery->kindlyTo()->int(),
358 ])),
359 $this->refinery->custom()->transformation(
360 fn($s): array => is_array($s) && join('', $s) === 'ALL_OBJECTS' ?
361 array_column(RelationsTable::data(), 'user_id') :
362 throw new Exception('Nope')
363 ),
364 ]));
365
366 $action = $get('contact_action', $this->refinery->kindlyTo()->string());
367 if (!$user_ids) {
368 $this->tpl->setOnScreenMessage('info', $this->lng->txt('select_one'), true);
369 $this->ctrl->redirect($this);
370 }
371
372 if (in_array($action, ['inviteToChat', 'mailToUsers'], true)) {
373 $this->$action($user_ids);
374 return;
375 }
376
377 $this->updateRelationState(current($user_ids), $action);
378 }
379
380 private function updateRelationState(int $user, string $action): void
381 {
384 throw new ilBuddySystemException('You cannot perform a state transition for the anonymous user');
385 }
386 if (!$login) {
387 throw new ilBuddySystemException(sprintf(
388 'You cannot perform a state transition for a non existing user (id: %s)',
389 $user
390 ));
391 }
393 $relation = $list->getRelationByUserId($user);
394 if (
395 $relation->isUnlinked() &&
396 !ilUtil::yn2tf((string) ilObjUser::_lookupPref($relation->getBuddyUsrId(), 'bs_allow_to_contact_me'))
397 ) {
398 throw new ilException('The requested user does not want to get contact requests');
399 }
400
401 try {
402 $list->$action($relation);
404 $this->tpl->setOnScreenMessage('failure', sprintf($this->lng->txt($e->getMessage()), $login), true);
405 } catch (Exception) {
406 $this->tpl->setOnScreenMessage('failure', $this->lng->txt('buddy_bs_action_not_possible'), true);
407 }
408
409 $this->ctrl->redirect($this, 'showContacts');
410 }
411
415 protected function mailToUsers(array $usr_ids): void
416 {
417 if (!$this->rbacsystem->checkAccess('internal_mail', ilMailGlobalServices::getMailObjectRefId())) {
418 $this->error->raiseError($this->lng->txt('msg_no_perm_read'), $this->error->MESSAGE);
419 }
420
421 $logins = [];
422 $mail_data = $this->umail->retrieveFromStage();
423 foreach ($usr_ids as $usr_id) {
424 $login = ilObjUser::_lookupLogin($usr_id);
425 if (!$this->umail->existsRecipient($login, (string) $mail_data['rcp_to'])) {
426 $logins[] = $login;
427 }
428 }
429 $logins = array_filter($logins);
430
431 if ($logins !== []) {
432 $mail_data = $this->umail->appendSearchResult($logins, 'to');
433 $this->umail->persistToStage(
434 (int) $mail_data['user_id'],
435 $mail_data['attachments'],
436 $mail_data['rcp_to'],
437 $mail_data['rcp_cc'],
438 $mail_data['rcp_bcc'],
439 $mail_data['m_subject'],
440 $mail_data['m_message'],
441 $mail_data['use_placeholders'],
442 $mail_data['tpl_ctx_id'],
443 $mail_data['tpl_ctx_params']
444 );
445 }
446
447 $this->ctrl->redirectToURL('ilias.php?baseClass=ilMailGUI&type=search_res');
448 }
449
450 public function submitInvitation(): void
451 {
452 try {
453 $usr_ids = $this->http->wrapper()->post()->retrieve('usr_ids', $this->refinery->in()->series([
454 $this->refinery->kindlyTo()->string(),
455 $this->refinery->custom()->transformation(fn(string $s) => explode(',', $s)),
456 $this->refinery->kindlyTo()->listOf($this->refinery->kindlyTo()->int()),
457 $this->refinery->custom()->constraint(fn(array $a) => $a !== [], fn() => 'Empty array.'),
458 ]));
459 } catch (Exception) {
460 $this->tpl->setOnScreenMessage('info', $this->lng->txt('select_one'), true);
461 $this->ctrl->redirect($this);
462 }
463
464 try {
465 $room_id = $this->http->wrapper()->post()->retrieve('room_id', $this->refinery->kindlyTo()->int());
466 } catch (Exception) {
467 $this->tpl->setOnScreenMessage('info', $this->lng->txt('select_one'));
468 $this->inviteToChat($usr_ids);
469 return;
470 }
471
472 $room = ilChatroom::byRoomId($room_id, true);
473
474 $no_access = [];
475 $no_login = [];
476 $valid_users = [];
477 $ref_id = $room->getRefIdByRoomId($room_id);
478
479 foreach ($usr_ids as $usr_id) {
480 $login = ilObjUser::_lookupLogin($usr_id);
481 if ($login === '') {
482 $no_login[] = $usr_id;
483 } elseif (
484 !ilChatroom::checkPermissionsOfUser($usr_id, 'read', $ref_id) ||
485 $room->isUserBanned($usr_id)
486 ) {
487 $no_access[] = $login;
488 } else {
489 $valid_users[] = $usr_id;
490 }
491 }
492
493 $message = join('', [
494 $this->asErrorMessage($no_access, $this->lng->txt('chat_users_without_permission')),
495 $this->asErrorMessage($no_login, $this->lng->txt('chat_users_without_login')),
496 ]);
497
498 if ($message !== '') {
499 $this->tpl->setOnScreenMessage('failure', $message);
500 $this->inviteToChat($usr_ids);
501 return;
502 }
503
504 foreach ($valid_users as $id) {
505 $room->sendInvitationNotification(
506 null,
507 $this->user->getId(),
508 $id,
510 );
511 }
512
513 $this->ctrl->setParameter($this, 'inv_room_ref_id', $ref_id);
514 $this->ctrl->setParameter($this, 'inv_usr_ids', implode(',', $valid_users));
515
516 $this->ctrl->redirect($this);
517 }
518
522 protected function inviteToChat(array $usr_ids): void
523 {
524 $this->tabs_gui->activateSubTab('buddy_view_table');
525 $this->activateTab('my_contacts');
526
527 $this->lng->loadLanguageModule('chatroom');
528
529 $chat_rooms = (new ilChatroom())->getAccessibleRoomIdByTitleMap($this->user->getId());
530
531 $options = array_filter(
532 $chat_rooms,
533 fn(int $room_id) => !(ilChatroom::byRoomId($room_id))->isUserBanned($this->user->getId()),
534 ARRAY_FILTER_USE_KEY
535 );
536
537 asort($options);
538
539 $this->tpl->setTitle($this->lng->txt('mail_invite_users_to_chat'));
540 $this->tpl->setContent($this->inviteToChatForm($options, $usr_ids)->getHTML());
541 $this->tpl->printToStdout();
542 }
543
547 private function asErrorMessage(array $array, string $title): string
548 {
549 if ($array === []) {
550 return '';
551 }
552
553 $items = array_map(
554 fn($s) => '<li>' . htmlspecialchars((string) $s) . '</li>',
555 $array
556 );
557
558 return sprintf(
559 '%s<br><ul>%s</ul>',
560 $title,
561 join('', $items)
562 );
563 }
564
569 private function inviteToChatForm(array $options, array $usr_ids): ilPropertyFormGUI
570 {
571 $form = new ilPropertyFormGUI();
572 $form->setTitle($this->lng->txt('mail_invite_users_to_chat'));
573 $form->addCommandButton('submitInvitation', $this->lng->txt('submit'));
574 $form->addCommandButton('showContacts', $this->lng->txt('cancel'));
575 $form->setFormAction($this->ctrl->getFormAction($this, 'showContacts'));
576
577 $sel = new ilSelectInputGUI($this->lng->txt('chat_select_room'), 'room_id');
578 $sel->setOptions($options);
579 $form->addItem($sel);
580
581 $hidden = new ilHiddenInputGUI('usr_ids');
582 $hidden->setValue(implode(',', $usr_ids));
583 $form->addItem($hidden);
584
585 return $form;
586 }
587
591 private function chatroomInvitationMessage(): array
592 {
593 $has = $this->http->wrapper()->query()->has(...);
594 if (!$has('inv_room_ref_id') || !$has('inv_usr_ids')) {
595 return [];
596 }
597
598 $inv_room_ref_id = $this->http->wrapper()->query()->retrieve(
599 'inv_room_ref_id',
600 $this->refinery->kindlyTo()->int()
601 );
602 $inv_usr_ids = $this->http->wrapper()->query()->retrieve(
603 'inv_usr_ids',
604 $this->refinery->in()->series([
605 $this->refinery->kindlyTo()->string(),
606 $this->refinery->custom()->transformation(fn(string $s): array => explode(',', $s)),
607 $this->refinery->kindlyTo()->listOf($this->refinery->kindlyTo()->int()),
608 $this->refinery->custom()->constraint(fn(array $a): bool => $a !== [], fn(): string => 'Empty array.'),
609 ])
610 );
611
612 $userlist = array_map(ilObjUser::_lookupLogin(...), $inv_usr_ids);
613
614 $url = ilLink::_getStaticLink($inv_room_ref_id, 'chtr');
615
616 return [
617 $this->ui_factory->messageBox()->success(
618 $this->lng->txt('chat_users_have_been_invited') . $this->ui_renderer->render(
619 $this->ui_factory->listing()->unordered($userlist)
620 )
621 )->withButtons([
622 $this->ui_factory->button()->standard($this->lng->txt('goto_invitation_chat'), $url)
623 ])
624 ];
625 }
626
630 private function contactAction(): Closure
631 {
632 $url = new URLBuilder(new URI(rtrim(ILIAS_HTTP_PATH, '/') . '/' . $this->ctrl->getLinkTarget($this, 'updateState')));
633 [$url, $p, $token] = $url->acquireParameters(['contact'], 'action', 'user_ids');
634
635 return fn(string $type, string $lang_var, string $param): Action => $this->ui_factory->table()->action()->$type(
636 $this->lng->txt($lang_var),
637 $url->withParameter($p, $param),
638 $token
639 );
640 }
641}
$id
plugin.php for ilComponentBuildPluginInfoObjectiveTest::testAddPlugins
Definition: plugin.php:23
$relation
Builds data types.
Definition: Factory.php:36
The scope of this class is split ilias-conform URI's into components.
Definition: URI.php:35
GUI class for public user profile presentation.
error(string $a_errmsg)
static getInstanceByGlobalUser(?ilObjUser $user=null)
Class ilBuddySystemException.
Class ilChatroom.
static checkPermissionsOfUser(int $usr_id, $permissions, int $ref_id)
Checks user permissions in question for a given user id in relation to a given ref_id.
static byRoomId(int $room_id, bool $initObject=false)
mailToUsers(array $usr_ids)
getUnsafeGetCommands()
This method must return a list of unsafe GET commands.
readonly RelationsTable $relations_table
ilFormatMail $umail
ilErrorHandling $error
final const string CONTACTS_VIEW_TABLE
__construct(string $format_mail_class=ilFormatMail::class, string $relations_table_class=RelationsTable::class)
getSafePostCommands()
This method must return a list of safe POST commands.
ILIAS UI Factory $ui_factory
ilCtrlInterface $ctrl
readonly ILIAS HTTP GlobalHttpState $http
activateTab(string $a_id)
ilGlobalTemplateInterface $tpl
updateRelationState(int $user, string $action)
ilRbacSystem $rbacsystem
inviteToChatForm(array $options, array $usr_ids)
asErrorMessage(array $array, string $title)
ILIAS UI Renderer $ui_renderer
final const string CONTACTS_VIEW_GALLERY
ilToolbarGUI $toolbar
inviteToChat(array $usr_ids)
changeContactsView()
This method is used to switch the contacts view between gallery and table in the mail system.
Error Handling & global info handling.
Base class for ILIAS Exception handling.
Help GUI class.
This class represents a hidden form property in a property form.
language handling
User class.
static _isAnonymous(int $usr_id)
static _lookupPref(int $a_usr_id, string $a_keyword)
static _lookupLogin(int $a_user_id)
This class represents a property form user interface.
class ilRbacSystem system function like checkAccess, addActiveRole ... Supporting system functions ar...
This class represents a selection list property in a property form.
ILIAS Setting Class.
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
@ilCtrl_Calls ilUsersGalleryGUI: ILIAS\User\Profile\PublicProfileGUI @ilCtrl_isCalledBy ilUsersGaller...
static getImagePath(string $image_name, string $module_path="", string $mode="output", bool $offline=false)
get image path (for images located in a template directory)
static yn2tf(string $a_yn)
A component is the most general form of an entity in the UI.
Definition: Component.php:28
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
This file is part of ILIAS, a powerful learning management system published by ILIAS open source e-Le...
$ref_id
Definition: ltiauth.php:66
static http()
Fetches the global http state from ILIAS.
$a
thx to https://mlocati.github.io/php-cs-fixer-configurator for the examples
global $DIC
Definition: shib_login.php:26
$url
Definition: shib_logout.php:68
$message
Definition: xapiexit.php:31
$token
Definition: xapitoken.php:70
$param
Definition: xapitoken.php:46