",
74 | * "

"
75 | * ];
76 | */
77 |
78 |
79 |
80 | function sanitizeInput(inputValue) {
81 | if (typeof inputValue !== 'string') {
82 | return '';
83 | }
84 |
85 | if (!/^[a-zA-Z0-9\s\-_]+$/.test(inputValue)) {
86 | return inputValue
87 | .replace(/&/g, '&')
88 | .replace(//g, '>')
90 | .replace(/"/g, '"')
91 | .replace(/'/g, ''');
92 | }
93 | return inputValue
94 |
95 | }
96 |
--------------------------------------------------------------------------------
/src/config/chatify.php:
--------------------------------------------------------------------------------
1 | env('CHATIFY_NAME', 'Chatify Messenger'),
10 |
11 | /*
12 | |-------------------------------------
13 | | The disk on which to store added
14 | | files and derived images by default.
15 | |-------------------------------------
16 | */
17 | 'storage_disk_name' => env('CHATIFY_STORAGE_DISK', 'public'),
18 |
19 | /*
20 | |-------------------------------------
21 | | Routes configurations
22 | |-------------------------------------
23 | */
24 | 'routes' => [
25 | 'custom' => env('CHATIFY_CUSTOM_ROUTES', false),
26 | 'prefix' => env('CHATIFY_ROUTES_PREFIX', 'chatify'),
27 | 'middleware' => env('CHATIFY_ROUTES_MIDDLEWARE', ['web','auth']),
28 | 'namespace' => env('CHATIFY_ROUTES_NAMESPACE', 'Chatify\Http\Controllers'),
29 | ],
30 | 'api_routes' => [
31 | 'prefix' => env('CHATIFY_API_ROUTES_PREFIX', 'chatify/api'),
32 | 'middleware' => env('CHATIFY_API_ROUTES_MIDDLEWARE', ['api']),
33 | 'namespace' => env('CHATIFY_API_ROUTES_NAMESPACE', 'Chatify\Http\Controllers\Api'),
34 | ],
35 |
36 | /*
37 | |-------------------------------------
38 | | Pusher API credentials
39 | |-------------------------------------
40 | */
41 | 'pusher' => [
42 | 'debug' => env('APP_DEBUG', false),
43 | 'key' => env('PUSHER_APP_KEY'),
44 | 'secret' => env('PUSHER_APP_SECRET'),
45 | 'app_id' => env('PUSHER_APP_ID'),
46 | 'options' => [
47 | 'cluster' => env('PUSHER_APP_CLUSTER', 'mt1'),
48 | 'host' => env('PUSHER_HOST') ?: 'api-'.env('PUSHER_APP_CLUSTER', 'mt1').'.pusher.com',
49 | 'port' => env('PUSHER_PORT', 443),
50 | 'scheme' => env('PUSHER_SCHEME', 'https'),
51 | 'encrypted' => true,
52 | 'useTLS' => env('PUSHER_SCHEME', 'https') === 'https',
53 | ],
54 | ],
55 |
56 | /*
57 | |-------------------------------------
58 | | User Avatar
59 | |-------------------------------------
60 | */
61 | 'user_avatar' => [
62 | 'folder' => 'users-avatar',
63 | 'default' => 'avatar.png',
64 | ],
65 |
66 | /*
67 | |-------------------------------------
68 | | Gravatar
69 | |
70 | | imageset property options:
71 | | [ 404 | mp | identicon (default) | monsterid | wavatar ]
72 | |-------------------------------------
73 | */
74 | 'gravatar' => [
75 | 'enabled' => true,
76 | 'image_size' => 200,
77 | 'imageset' => 'identicon'
78 | ],
79 |
80 | /*
81 | |-------------------------------------
82 | | Attachments
83 | |-------------------------------------
84 | */
85 | 'attachments' => [
86 | 'folder' => 'attachments',
87 | 'download_route_name' => 'attachments.download',
88 | 'allowed_images' => (array) ['png','jpg','jpeg','gif'],
89 | 'allowed_files' => (array) ['zip','rar','txt'],
90 | 'max_upload_size' => env('CHATIFY_MAX_FILE_SIZE', 150), // MB
91 | ],
92 |
93 | /*
94 | |-------------------------------------
95 | | Messenger's colors
96 | |-------------------------------------
97 | */
98 | 'colors' => (array) [
99 | '#2180f3',
100 | '#2196F3',
101 | '#00BCD4',
102 | '#3F51B5',
103 | '#673AB7',
104 | '#4CAF50',
105 | '#FFC107',
106 | '#FF9800',
107 | '#ff2522',
108 | '#9C27B0',
109 | ],
110 | /*
111 | |-------------------------------------
112 | | Sounds
113 | | You can enable/disable the sounds and
114 | | change sound's name/path placed at
115 | | `public/` directory of your app.
116 | |
117 | |-------------------------------------
118 | */
119 | 'sounds' => [
120 | 'enabled' => true,
121 | 'public_path' => 'sounds/chatify',
122 | 'new_message' => 'new-message-sound.mp3',
123 | ]
124 | ];
125 |
--------------------------------------------------------------------------------
/src/assets/css/dark.mode.css:
--------------------------------------------------------------------------------
1 | /*app scroll*/
2 | .app-scroll::-webkit-scrollbar-thumb,
3 | .app-scroll-thin::-webkit-scrollbar-thumb {
4 | background: var(--dark-scrollbar-thumb-color);
5 | }
6 | .app-scroll-thin::-webkit-scrollbar {
7 | background: var(--dark-secondary-bg-color);
8 | }
9 | .app-scroll::-webkit-scrollbar:hover,
10 | .app-scroll-thin::-webkit-scrollbar:hover {
11 | background: var(--dark-secondary-bg-color);
12 | }
13 | .messenger {
14 | background: var(--dark-primary-bg-color);
15 | }
16 | .messenger-search[type="text"] {
17 | background: var(--dark-secondary-bg-color);
18 | color: #fff;
19 | }
20 | .messenger-search[type="text"]::placeholder {
21 | color: #fff;
22 | }
23 | .messenger-listView {
24 | background: var(--dark-primary-bg-color);
25 | border: 1px solid var(--dark-border-color);
26 | }
27 | .messenger-listView-tabs {
28 | border-bottom: 1px solid var(--dark-border-color);
29 | }
30 | .messenger-listView-tabs a:hover,
31 | .messenger-listView-tabs a:focus {
32 | background-color: var(--dark-secondary-bg-color);
33 | }
34 | .messenger-favorites div.avatar {
35 | border: 2px solid var(--dark-primary-bg-color);
36 | }
37 | .messenger-list-item:hover {
38 | background: var(--dark-secondary-bg-color);
39 | }
40 | .messenger-messagingView {
41 | border-top: 1px solid var(--dark-secondary-bg-color);
42 | border-bottom: 1px solid var(--dark-secondary-bg-color);
43 | background: var(--dark-messagingView-bg-color);
44 | }
45 | .m-header-messaging {
46 | background: var(--dark-primary-bg-color);
47 | }
48 | .messenger-infoView {
49 | background: var(--dark-primary-bg-color);
50 | border: 1px solid var(--dark-border-color);
51 | }
52 | .messenger-infoView > p {
53 | color: #fff;
54 | }
55 | .divider {
56 | border-top: 1px solid var(--dark-border-color);
57 | }
58 | .messenger-sendCard {
59 | background: var(--dark-primary-bg-color);
60 | border-top: 1px solid var(--dark-border-color);
61 | }
62 | .attachment-preview > p {
63 | color: #fff;
64 | }
65 | .m-send {
66 | color: #fff;
67 | }
68 | .m-send::placeholder {
69 | color: #fff;
70 | }
71 | .message-card .message {
72 | background: var(--dark-message-card-color);
73 | color: #fff;
74 | }
75 | .m-li-divider {
76 | border-bottom: 1px solid var(--dark-border-color);
77 | }
78 | .m-header a,
79 | .m-header a:hover,
80 | .m-header a:focus {
81 | text-decoration: none;
82 | color: #fff;
83 | }
84 | .messenger-list-item td p {
85 | color: #fff;
86 | }
87 | .activeStatus {
88 | border: 2px solid var(--dark-border-color);
89 | }
90 | .messenger-list-item:hover .activeStatus {
91 | border-color: var(--dark-secondary-bg-color);
92 | }
93 | .messenger-favorites > div p {
94 | color: #ffffff;
95 | }
96 | .avatar {
97 | background-color: var(--dark-secondary-bg-color);
98 | border-color: var(--dark-border-color);
99 | }
100 | .messenger-sendCard svg {
101 | color: var(--dark-send-input-icons-color);
102 | }
103 | .messenger-title {
104 | color: #dbdbdb;
105 | }
106 | .messenger-title > span {
107 | background-color: var(--dark-primary-bg-color);
108 | }
109 | .messenger-title::before {
110 | background-color: var(--dark-border-color);
111 | }
112 | .message-hint span {
113 | background: var(--dark-message-hint-bg-color);
114 | color: var(--dark-message-hint-color);
115 | }
116 | .messenger-infoView > nav > p {
117 | color: #fff;
118 | }
119 | /*
120 | ***********************************************
121 | * Placeholder loading
122 | ***********************************************
123 | */
124 | .loadingPlaceholder-body div,
125 | .loadingPlaceholder-header tr td div {
126 | background: var(--dark-secondary-bg-color);
127 | background-image: -webkit-linear-gradient(
128 | left,
129 | var(--dark-secondary-bg-color) 0%,
130 | var(--dark-secondary-bg-color) 20%,
131 | var(--dark-secondary-bg-color) 40%,
132 | var(--dark-secondary-bg-color) 100%
133 | );
134 | }
135 |
136 | /*
137 | ***********************************************
138 | * App Modal
139 | ***********************************************
140 | */
141 |
142 | .app-modal-card {
143 | background: var(--dark-modal-bg-color);
144 | }
145 | .app-modal-header {
146 | color: #fff;
147 | }
148 | .app-modal-body {
149 | color: #fff;
150 | }
151 |
152 | .messages .message-time {
153 | color: #fff;
154 | }
155 |
156 | .message-card .actions .delete-btn {
157 | color: #fff;
158 | }
159 |
--------------------------------------------------------------------------------
/src/views/layouts/modals.blade.php:
--------------------------------------------------------------------------------
1 | {{-- ---------------------- Image modal box ---------------------- --}}
2 |
3 |
×
4 |
![]()
5 |
6 |
7 | {{-- ---------------------- Delete Modal ---------------------- --}}
8 |
9 |
10 |
11 |
12 |
You can not undo this action
13 |
17 |
18 |
19 |
20 | {{-- ---------------------- Alert Modal ---------------------- --}}
21 |
32 | {{-- ---------------------- Settings Modal ---------------------- --}}
33 |
76 |
--------------------------------------------------------------------------------
/src/assets/css/light.mode.css:
--------------------------------------------------------------------------------
1 | /*app scroll*/
2 | .app-scroll::-webkit-scrollbar-thumb,
3 | .app-scroll-thin::-webkit-scrollbar-thumb {
4 | background: var(--scrollbar-thumb-color);
5 | }
6 | .app-scroll-thin::-webkit-scrollbar {
7 | background: var(--secondary-bg-color);
8 | }
9 | .app-scroll::-webkit-scrollbar:hover,
10 | .app-scroll-thin::-webkit-scrollbar:hover {
11 | background: var(--secondary-bg-color);
12 | }
13 |
14 | .messenger {
15 | background: var(--primary-bg-color);
16 | }
17 | .messenger-search[type="text"] {
18 | background: var(--secondary-bg-color);
19 | color: #333;
20 | }
21 | .messenger-listView {
22 | background: var(--primary-bg-color);
23 | border: 1px solid var(--border-color);
24 | }
25 | .messenger-listView-tabs {
26 | border-bottom: 1px solid var(--border-color);
27 | }
28 | .messenger-listView-tabs a:hover,
29 | .messenger-listView-tabs a:focus {
30 | background-color: var(--secondary-bg-color);
31 | }
32 | .messenger-favorites div.avatar {
33 | border: 2px solid var(--primary-bg-color);
34 | }
35 |
36 | .messenger-list-item:hover {
37 | background: var(--secondary-bg-color);
38 | }
39 | .messenger-messagingView {
40 | border-top: 1px solid var(--secondary-bg-color);
41 | border-bottom: 1px solid var(--secondary-bg-color);
42 | background: var(--messagingView-bg-color);
43 | }
44 | .m-header-messaging {
45 | background: var(--primary-bg-color);
46 | }
47 | .messenger-infoView {
48 | background: var(--primary-bg-color);
49 | border: 1px solid var(--border-color);
50 | }
51 | .messenger-infoView > p {
52 | color: #000;
53 | }
54 | .divider {
55 | border-top: 1px solid var(--border-color);
56 | }
57 | .messenger-sendCard {
58 | background: var(--primary-bg-color);
59 | border-top: 1px solid var(--border-color);
60 | }
61 | .attachment-preview > p {
62 | color: #333;
63 | }
64 | .m-send {
65 | color: #333;
66 | }
67 | .message-card .message {
68 | background: var(--message-card-color);
69 | color: #656b75;
70 | box-shadow: 0px 6px 11px rgba(18, 67, 105, 0.03);
71 | }
72 | .m-li-divider {
73 | border-bottom: 1px solid var(--border-color);
74 | }
75 | .m-header a,
76 | .m-header a:hover,
77 | .m-header a:focus {
78 | text-decoration: none;
79 | color: #202020;
80 | }
81 | .messenger-list-item td p {
82 | color: #3c3c3c;
83 | }
84 | .messenger-list-item td span {
85 | color: #929292;
86 | }
87 | .activeStatus {
88 | border: 2px solid var(--primary-bg-color);
89 | }
90 | .messenger-list-item:hover .activeStatus {
91 | border-color: var(--secondary-bg-color);
92 | }
93 | .messenger-favorites > div p {
94 | color: #4a4a4a;
95 | }
96 |
97 | .avatar {
98 | background-color: var(--secondary-bg-color);
99 | border-color: var(--border-color);
100 | }
101 | .messenger-sendCard svg {
102 | color: var(--send-input-icons-color);
103 | }
104 | .messenger-title {
105 | color: #797979;
106 | }
107 | .messenger-title > span {
108 | background-color: var(--primary-bg-color);
109 | }
110 | .messenger-title::before {
111 | background-color: var(--border-color);
112 | }
113 | .message-hint span {
114 | background: var(--message-hint-bg-color);
115 | color: var(--message-hint-color);
116 | }
117 | /*
118 | ***********************************************
119 | * Placeholder loading
120 | ***********************************************
121 | */
122 | .loadingPlaceholder-body div,
123 | .loadingPlaceholder-header tr td div {
124 | background: var(--secondary-bg-color);
125 | background-image: -webkit-linear-gradient(
126 | left,
127 | var(--secondary-bg-color) 0%,
128 | var(--secondary-bg-color) 20%,
129 | var(--secondary-bg-color) 40%,
130 | var(--secondary-bg-color) 100%
131 | );
132 | }
133 | .messenger-infoView > nav > p {
134 | color: #333;
135 | }
136 | /*
137 | ***********************************************
138 | * App Modal
139 | ***********************************************
140 | */
141 |
142 | .app-modal-card {
143 | background: var(--modal-bg-color);
144 | }
145 | .app-modal-header {
146 | color: #000;
147 | }
148 | .app-modal-body {
149 | color: #000;
150 | }
151 |
152 | /*
153 | *****************************************
154 | * Responsive Design
155 | *****************************************
156 | */
157 | @media (max-width: 1060px) {
158 | .messenger-infoView {
159 | box-shadow: 0px 0px 20px rgba(18, 67, 105, 0.06);
160 | }
161 | }
162 | @media (max-width: 980px) {
163 | .messenger-listView {
164 | box-shadow: 0px 0px 20px rgba(18, 67, 105, 0.06);
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/src/Console/InstallCommand.php:
--------------------------------------------------------------------------------
1 | isV8 = explode('.',app()->version())[0] >= 8;
40 |
41 | $this->info('Installing Chatify...');
42 |
43 | $this->line('----------');
44 | $this->line('Configurations...');
45 | $this->modifyModelsPath('/../Http/Controllers/MessagesController.php','User');
46 | $this->modifyModelsPath('/../Http/Controllers/MessagesController.php','ChFavorite');
47 | $this->modifyModelsPath('/../Http/Controllers/MessagesController.php','ChMessage');
48 | $this->modifyModelsPath('/../Http/Controllers/Api/MessagesController.php','User');
49 | $this->modifyModelsPath('/../Http/Controllers/Api/MessagesController.php','ChFavorite');
50 | $this->modifyModelsPath('/../Http/Controllers/Api/MessagesController.php','ChMessage');
51 | $this->modifyModelsPath('/../ChatifyMessenger.php','ChFavorite');
52 | $this->modifyModelsPath('/../ChatifyMessenger.php','ChMessage');
53 | $this->modifyModelsPath('/../Models/ChFavorite.php');
54 | $this->modifyModelsPath('/../Models/ChMessage.php');
55 | $this->info('[✓] done');
56 |
57 | $assetsToBePublished = [
58 | 'config' => config_path('chatify.php'),
59 | 'views' => resource_path('views/vendor/Chatify'),
60 | 'assets' => public_path('css/chatify'),
61 | 'models' => app_path(($this->isV8 ? 'Models/' : '').'ChMessage.php'),
62 | 'migrations' => database_path('migrations/2019_09_22_192348_create_messages_table.php'),
63 | 'routes' => base_path('routes/chatify'),
64 | ];
65 |
66 | foreach ($assetsToBePublished as $target => $path) {
67 | $this->line('----------');
68 | $this->process($target, $path);
69 | }
70 |
71 | $this->line('----------');
72 | $this->line('Creating storage symlink...');
73 | Artisan::call('storage:link');
74 | $this->info('[✓] Storage linked.');
75 |
76 | $this->line('----------');
77 | $this->info('[✓] Chatify installed successfully');
78 | }
79 |
80 | /**
81 | * Modify models imports/namespace path according to Laravel version.
82 | *
83 | * @param string $targetFilePath
84 | * @param string $model
85 | * @return void
86 | */
87 | private function modifyModelsPath($targetFilePath, $model = null){
88 | $path = realpath(__DIR__.$targetFilePath);
89 | $contents = File::get($path);
90 | $model = !empty($model) ? '\\'.$model : ';';
91 | $contents = str_replace(
92 | (!$this->isV8 ? 'App\Models' : 'App').$model,
93 | ($this->isV8 ? 'App\Models' : 'App').$model,
94 | $contents
95 | );
96 | File::put($path, $contents);
97 | }
98 |
99 | /**
100 | * Check, publish, or overwrite the assets.
101 | *
102 | * @param string $target
103 | * @param string $path
104 | * @return void
105 | */
106 | private function process($target, $path)
107 | {
108 | $this->line('Publishing '.$target.'...');
109 | if (!File::exists($path)) {
110 | $this->publish($target);
111 | $this->info('[✓] '.$target.' published.');
112 | return;
113 | }
114 | if ($this->shouldOverwrite($target)) {
115 | $this->line('Overwriting '.$target.'...');
116 | $this->publish($target,true);
117 | $this->info('[✓] '.$target.' published.');
118 | return;
119 | }
120 | $this->line('[-] Ignored, The existing '.$target.' was not overwritten');
121 | }
122 |
123 | /**
124 | * Ask to overwrite.
125 | *
126 | * @param string $target
127 | * @return void
128 | */
129 | private function shouldOverwrite($target)
130 | {
131 | return $this->confirm(
132 | $target.' already exists. Do you want to overwrite it?',
133 | false
134 | );
135 | }
136 |
137 | /**
138 | * Call the publish command.
139 | *
140 | * @param string $tag
141 | * @param bool $forcePublish
142 | * @return void
143 | */
144 | private function publish($tag, $forcePublish = false)
145 | {
146 | $this->call('vendor:publish', [
147 | '--tag' => 'chatify-'.$tag,
148 | '--force' => $forcePublish,
149 | ]);
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/src/views/pages/app.blade.php:
--------------------------------------------------------------------------------
1 | @include('Chatify::layouts.headLinks')
2 |
3 | {{-- ----------------------Users/Groups lists side---------------------- --}}
4 |
5 | {{-- Header and search bar --}}
6 |
23 | {{-- tabs and lists --}}
24 |
25 | {{-- Lists [Users/Group] --}}
26 | {{-- ---------------- [ User Tab ] ---------------- --}}
27 |
40 | {{-- ---------------- [ Search Tab ] ---------------- --}}
41 |
48 |
49 |
50 |
51 | {{-- ----------------------Messaging side---------------------- --}}
52 |
53 | {{-- header title [conversation name] amd buttons --}}
54 |
77 |
78 | {{-- Messaging area --}}
79 |
80 |
81 |
Please select a chat to start messaging
82 |
83 | {{-- Typing indicator --}}
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 | {{-- Send Message Form --}}
98 | @include('Chatify::layouts.sendForm')
99 |
100 | {{-- ---------------------- Info side ---------------------- --}}
101 |
109 |
110 |
111 | @include('Chatify::layouts.modals')
112 | @include('Chatify::layouts.footerLinks')
113 |
--------------------------------------------------------------------------------
/src/ChatifyServiceProvider.php:
--------------------------------------------------------------------------------
1 | bind('ChatifyMessenger', function () {
20 | return new \Chatify\ChatifyMessenger;
21 | });
22 | }
23 |
24 | /**
25 | * Bootstrap services.
26 | *
27 | * @return void
28 | */
29 | public function boot()
30 | {
31 | // Load Views and Routes
32 | $this->loadViewsFrom(__DIR__ . '/views', 'Chatify');
33 | $this->loadRoutes();
34 |
35 | if ($this->app->runningInConsole()) {
36 | $this->commands([
37 | InstallCommand::class,
38 | PublishCommand::class,
39 | ]);
40 | $this->setPublishes();
41 | }
42 | }
43 |
44 | /**
45 | * Publishing the files that the user may override.
46 | *
47 | * @return void
48 | */
49 | protected function setPublishes()
50 | {
51 | // Load user's avatar folder from package's config
52 | $userAvatarFolder = json_decode(json_encode(include(__DIR__.'/config/chatify.php')))->user_avatar->folder;
53 |
54 | // Config
55 | $this->publishes([
56 | __DIR__ . '/config/chatify.php' => config_path('chatify.php')
57 | ], 'chatify-config');
58 |
59 | // Migrations
60 | $this->publishes([
61 | __DIR__ . '/database/migrations/2022_01_10_99999_add_active_status_to_users.php' => database_path('migrations/' . date('Y_m_d') . '_999999_add_active_status_to_users.php'),
62 | __DIR__ . '/database/migrations/2022_01_10_99999_add_avatar_to_users.php' => database_path('migrations/' . date('Y_m_d') . '_999999_add_avatar_to_users.php'),
63 | __DIR__ . '/database/migrations/2022_01_10_99999_add_dark_mode_to_users.php' => database_path('migrations/' . date('Y_m_d') . '_999999_add_dark_mode_to_users.php'),
64 | __DIR__ . '/database/migrations/2022_01_10_99999_add_messenger_color_to_users.php' => database_path('migrations/' . date('Y_m_d') . '_999999_add_messenger_color_to_users.php'),
65 | __DIR__ . '/database/migrations/2022_01_10_99999_create_chatify_favorites_table.php' => database_path('migrations/' . date('Y_m_d') . '_999999_create_chatify_favorites_table.php'),
66 | __DIR__ . '/database/migrations/2022_01_10_99999_create_chatify_messages_table.php' => database_path('migrations/' . date('Y_m_d') . '_999999_create_chatify_messages_table.php'),
67 | ], 'chatify-migrations');
68 |
69 | // Models
70 | $isV8 = explode('.', app()->version())[0] >= 8;
71 | $this->publishes([
72 | __DIR__ . '/Models' => app_path($isV8 ? 'Models' : '')
73 | ], 'chatify-models');
74 |
75 | // Controllers
76 | $this->publishes([
77 | __DIR__ . '/Http/Controllers' => app_path('Http/Controllers/vendor/Chatify')
78 | ], 'chatify-controllers');
79 |
80 | // Views
81 | $this->publishes([
82 | __DIR__ . '/views' => resource_path('views/vendor/Chatify')
83 | ], 'chatify-views');
84 |
85 | // Assets
86 | $this->publishes([
87 | // CSS
88 | __DIR__ . '/assets/css' => public_path('css/chatify'),
89 | // JavaScript
90 | __DIR__ . '/assets/js' => public_path('js/chatify'),
91 | // Images
92 | __DIR__ . '/assets/imgs' => storage_path('app/public/' . $userAvatarFolder),
93 | // CSS
94 | __DIR__ . '/assets/sounds' => public_path('sounds/chatify'),
95 | ], 'chatify-assets');
96 |
97 | // Routes (API and Web)
98 | $this->publishes([
99 | __DIR__ . '/routes' => base_path('routes/chatify')
100 | ], 'chatify-routes');
101 | }
102 |
103 | /**
104 | * Group the routes and set up configurations to load them.
105 | *
106 | * @return void
107 | */
108 | protected function loadRoutes()
109 | {
110 | if (config('chatify.routes.custom')) {
111 | Route::group($this->routesConfigurations(), function () {
112 | $this->loadRoutesFrom(base_path('routes/chatify/web.php'));
113 | });
114 | Route::group($this->apiRoutesConfigurations(), function () {
115 | $this->loadRoutesFrom(base_path('routes/chatify/api.php'));
116 | });
117 | } else {
118 | Route::group($this->routesConfigurations(), function () {
119 | $this->loadRoutesFrom(__DIR__ . '/routes/web.php');
120 | });
121 | Route::group($this->apiRoutesConfigurations(), function () {
122 | $this->loadRoutesFrom(__DIR__ . '/routes/api.php');
123 | });
124 | }
125 | }
126 |
127 | /**
128 | * Routes configurations.
129 | *
130 | * @return array
131 | */
132 | private function routesConfigurations()
133 | {
134 | return [
135 | 'prefix' => config('chatify.routes.prefix'),
136 | 'namespace' => config('chatify.routes.namespace'),
137 | 'middleware' => config('chatify.routes.middleware'),
138 | ];
139 | }
140 | /**
141 | * API routes configurations.
142 | *
143 | * @return array
144 | */
145 | private function apiRoutesConfigurations()
146 | {
147 | return [
148 | 'prefix' => config('chatify.api_routes.prefix'),
149 | 'namespace' => config('chatify.api_routes.namespace'),
150 | 'middleware' => config('chatify.api_routes.middleware'),
151 | ];
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Change log
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | ## v1.6.5 ()
6 |
7 | ### Fixed
8 | - Settings modal UI #351
9 | - Limit the data retrieved for the user #359
10 | - [FIX] - Init user conversation from URL #374
11 | - Sanitize inputs to prevent xss when sending message #377
12 |
13 | ## v1.6.4 (2024-04-27)
14 |
15 | ### Fixed
16 | - [fix bug] updateSelectedContact and IDinfo load if user_id != auth_id #339
17 |
18 | ## v1.6.3 (2024-03-17)
19 |
20 | ### Added
21 |
22 | - Support for a custom routes.
23 |
24 | ## v1.6.2 (2023-07-27)
25 |
26 | ### Added
27 |
28 | - Support for a custom WS server #291.
29 |
30 | ## v1.6.1 (2023-03-03)
31 |
32 | ### Fixed
33 |
34 | - Migration files issue (Cannot redeclare class...).
35 |
36 | ## v1.6.0 (2023-03-03)
37 |
38 | ### Added
39 |
40 | - Emoji's support.
41 | - Css variables.
42 | - Notification sounds.
43 | - Auto-time updates.
44 |
45 | ### Changed
46 |
47 | - Using UUIDs instead of random IDs on table primary column #243.
48 | - UI/UX changes and enhancements.
49 | - Code refactored (part of it).
50 | - Messenger primary color fallback.
51 |
52 | ### Fixed
53 |
54 | - Fetching messages multiple times at once on send/fetch requests.
55 | - Migrations duplicate class name.
56 | - Prevent chat for invalid user ids #246
57 | - Fix responsiveness when going to chat with specific ID #247.
58 | - App URL should be changed when click the `back to contacts` button on small screens.
59 | - Internet connection UI.
60 | - Prevent Users from updating each others statuses #254
61 | - Contact list realtime updates issues.
62 | - Delete messages issues.
63 | - Fix contact list error `Malformed UTF-8 characters, possibly incorrectly encoded`
64 | - Search multiple request on typing, debouncing used.
65 |
66 | ## v1.5.6 (2023-01-26)
67 |
68 | ### Fixed
69 |
70 | - Keyboard overlaping on input issue on mobile #202.
71 | - Security issue and code enhancements #240.
72 |
73 | ## v1.5.5 (2023-01-21)
74 |
75 | ### Fixed
76 |
77 | - message delete event channel #238.
78 |
79 | ## v1.5.4 (2022-12-05)
80 |
81 | ### Fixed
82 |
83 | - Channels auth secutiy issue #29
84 |
85 | ## v1.5.3 (2022-12-04)
86 |
87 | ### Fixed
88 |
89 | - Channels Secutiy issue #29
90 |
91 | ## v1.5.2 (2022-07-08)
92 |
93 | ### Fixed
94 |
95 | - MessageCard & fetchMessage methods@`ChatifyMessenger.php` fallback.
96 |
97 | ## v1.5.1 (2022-06-09)
98 |
99 | ### Fixed
100 |
101 | - Sync the `sending a message form`'s allowed files/images with the `config` file (Update sendForm.blade.php [#190](https://github.com/munafio/chatify/pull/190))
102 |
103 | ## v1.5.0 (2022-06-08)
104 |
105 | ### Added
106 |
107 | - Page/Document visibility Support which improves (seen) feature #183
108 |
109 | ### Fixed
110 |
111 | - fix: case insensitive file upload extension check #182
112 |
113 | ## v1.4.0 (2022-05-02)
114 |
115 | ### Added
116 |
117 | - [Gravatar](https:://gravatar.com) support (optional, can be changed at config/chatify.php).
118 | - Delete Message by ID.
119 | - Laravel's Storage disk now supported and can be changed from the config.
120 |
121 | ### Changed
122 |
123 | - File upload (user avatar & attachments) `allowed files` and `max size` now can be changed from one place which is (config/chatify.php).
124 |
125 | ### Fixed
126 |
127 | - Bugs and UI/UX design fixes/improvements.
128 |
129 | ## v1.3.4 (2022-02-04)
130 |
131 | ### Fixed
132 |
133 | - Fixed Installing errors on the migrations step. #163
134 |
135 | ## v1.3.3 (2022-01-10)
136 |
137 | ### Fixed
138 |
139 | - Fixed file upload size limit error message rephrase #160.
140 |
141 | ### Changed
142 |
143 | - Files max upload size changed & added to the config to be customizable.
144 | - Changed `Messenger colors` logic to be more flexible and customizable.
145 | - Migration files renamed, file date automatically will be changed to the publish/install date.
146 |
147 | ## v1.3.2 (2022-01-07)
148 |
149 | ### Fixed
150 |
151 | - Fixed CSS issue in FF with the contact list #157.
152 | - Correct misspelt of `updateContactItem` method (typo error) #159.
153 |
154 | ## v1.3.1 (2021-12-23)
155 |
156 | ### Fixed
157 |
158 | - Fixed migration's rollback, (ch\_) prefix added.
159 |
160 | ## v1.3.0 (2021-11-30)
161 |
162 | ### Fixed
163 |
164 | - UI/Ux fixes & improvements.
165 | - Backend fixes & improvements.
166 |
167 | ### Added
168 |
169 | - Messages, Contacts, and Search pagination.
170 | - API routes.
171 |
172 | ## v1.2.5 (2021-08-18)
173 |
174 | ### Fixed
175 |
176 | - Fixed a security issue on uploaded file-name, which is vulnerable with XSS.
177 |
178 | ## v1.2.4 (2021-07-15)
179 |
180 | ### Fixed
181 |
182 | - README updates.
183 | - Install Command fixes & improvements.
184 | - Contact list visible onLoad.
185 | - Settings’ modal responsive design.
186 |
187 | ### Added
188 |
189 | - UPGRADE.md added.
190 | - Publish command added.
191 | - Package.json additions & modifications.
192 |
193 | ## v1.2.3 - (2021-06-19)
194 |
195 | ### Fixed
196 |
197 | - XSS issue on inputs.
198 | - UI/UX fixes & improvements.
199 | - Send message fixes (UI & backend).
200 | - Update Profile Settings (upload file & error handling ….).
201 | - Shared photos not working issue.
202 | - Typo error fixes (Your `contatc` list is empty).
203 | - Rolling back migrations added.
204 | - Get Last message `orderBy` query duplication.
205 |
206 | ## v1.2.2 - (2021-06-01)
207 |
208 | ### Fixed
209 |
210 | - Migrate to database command removed.
211 | - Publishable asset `assets` avatar config issue.
212 | - Pusher encryption key option removed.
213 | - Settings button on click not working issue.
214 |
215 | ## v1.2.1 - (2021-05-30)
216 |
217 | ### Fixed
218 |
219 | - Publishable asset `assets`.
220 |
221 | ## v1.2.0 - (2021-05-30)
222 |
223 | ### FIxed
224 |
225 | - Security issues.
226 | - UI/UX issues.
227 | - Route [home] not defiend.
228 | - `$msg->attachment` issue #9.
229 | - Delete conversation issue #89.
230 |
231 | ### Added
232 |
233 | - Console commands.
234 | - `Models` added to assets to be published.
235 | - Laravel 8+ support.
236 |
237 | ### Changed
238 |
239 | - Project structure.
240 | - composer updated `pusher/pusher-php-server` to v^7.0.
241 | - Models & Migrations' tables names changed (added `ch` prefix to avoid duplication) solves issue #68.
242 | - Models changed to (`ChMessage`, `ChFavorite`)
243 | - Migrations' tables names (`ch_messages`, `ch_favorites`)
244 | - Configuration file `config/chatify.php`.
245 |
246 | ## v1.0.1 - (2020-09-30)
247 |
248 | ### FIxed
249 |
250 | - Security issues.
251 |
252 | ### Added
253 |
254 | - Routes' controllers namespace included in the configuration.
255 |
256 | ## v1.0.0 - (2019-12-30)
257 |
258 | - First release
259 |
--------------------------------------------------------------------------------
/src/assets/js/autosize.js:
--------------------------------------------------------------------------------
1 | /*
2 | ****************************************************************************
3 | * Text Area auto resize
4 | ****************************************************************************
5 | */
6 | (function (global, factory) {
7 | if (typeof define === "function" && define.amd) {
8 | define(['module', 'exports'], factory);
9 | } else if (typeof exports !== "undefined") {
10 | factory(module, exports);
11 | } else {
12 | var mod = {
13 | exports: {}
14 | };
15 | factory(mod, mod.exports);
16 | global.autosize = mod.exports;
17 | }
18 | })(this, function (module, exports) {
19 | 'use strict';
20 |
21 | var map = typeof Map === "function" ? new Map() : function () {
22 | var keys = [];
23 | var values = [];
24 |
25 | return {
26 | has: function has(key) {
27 | return keys.indexOf(key) > -1;
28 | },
29 | get: function get(key) {
30 | return values[keys.indexOf(key)];
31 | },
32 | set: function set(key, value) {
33 | if (keys.indexOf(key) === -1) {
34 | keys.push(key);
35 | values.push(value);
36 | }
37 | },
38 | delete: function _delete(key) {
39 | var index = keys.indexOf(key);
40 | if (index > -1) {
41 | keys.splice(index, 1);
42 | values.splice(index, 1);
43 | }
44 | }
45 | };
46 | }();
47 |
48 | var createEvent = function createEvent(name) {
49 | return new Event(name, { bubbles: true });
50 | };
51 | try {
52 | new Event('test');
53 | } catch (e) {
54 | // IE does not support `new Event()`
55 | createEvent = function createEvent(name) {
56 | var evt = document.createEvent('Event');
57 | evt.initEvent(name, true, false);
58 | return evt;
59 | };
60 | }
61 |
62 | function assign(ta) {
63 | if (!ta || !ta.nodeName || ta.nodeName !== 'TEXTAREA' || map.has(ta)) return;
64 |
65 | var heightOffset = null;
66 | var clientWidth = null;
67 | var cachedHeight = null;
68 |
69 | function init() {
70 | var style = window.getComputedStyle(ta, null);
71 |
72 | if (style.resize === 'vertical') {
73 | ta.style.resize = 'none';
74 | } else if (style.resize === 'both') {
75 | ta.style.resize = 'horizontal';
76 | }
77 |
78 | if (style.boxSizing === 'content-box') {
79 | heightOffset = -(parseFloat(style.paddingTop) + parseFloat(style.paddingBottom));
80 | } else {
81 | heightOffset = parseFloat(style.borderTopWidth) + parseFloat(style.borderBottomWidth);
82 | }
83 | // Fix when a textarea is not on document body and heightOffset is Not a Number
84 | if (isNaN(heightOffset)) {
85 | heightOffset = 0;
86 | }
87 |
88 | update();
89 | }
90 |
91 | function changeOverflow(value) {
92 | {
93 | // Chrome/Safari-specific fix:
94 | // When the textarea y-overflow is hidden, Chrome/Safari do not reflow the text to account for the space
95 | // made available by removing the scrollbar. The following forces the necessary text reflow.
96 | var width = ta.style.width;
97 | ta.style.width = '0px';
98 | // Force reflow:
99 | /* jshint ignore:start */
100 | ta.offsetWidth;
101 | /* jshint ignore:end */
102 | ta.style.width = width;
103 | }
104 |
105 | ta.style.overflowY = value;
106 | }
107 |
108 | function getParentOverflows(el) {
109 | var arr = [];
110 |
111 | while (el && el.parentNode && el.parentNode instanceof Element) {
112 | if (el.parentNode.scrollTop) {
113 | arr.push({
114 | node: el.parentNode,
115 | scrollTop: el.parentNode.scrollTop
116 | });
117 | }
118 | el = el.parentNode;
119 | }
120 |
121 | return arr;
122 | }
123 |
124 | function resize() {
125 | if (ta.scrollHeight === 0) {
126 | // If the scrollHeight is 0, then the element probably has display:none or is detached from the DOM.
127 | return;
128 | }
129 |
130 | var overflows = getParentOverflows(ta);
131 | var docTop = document.documentElement && document.documentElement.scrollTop; // Needed for Mobile IE (ticket #240)
132 |
133 | ta.style.height = '';
134 | ta.style.height = ta.scrollHeight + heightOffset + 'px';
135 |
136 | // used to check if an update is actually necessary on window.resize
137 | clientWidth = ta.clientWidth;
138 |
139 | // prevents scroll-position jumping
140 | overflows.forEach(function (el) {
141 | el.node.scrollTop = el.scrollTop;
142 | });
143 |
144 | if (docTop) {
145 | document.documentElement.scrollTop = docTop;
146 | }
147 | }
148 |
149 | function update() {
150 | resize();
151 |
152 | var styleHeight = Math.round(parseFloat(ta.style.height));
153 | var computed = window.getComputedStyle(ta, null);
154 |
155 | // Using offsetHeight as a replacement for computed.height in IE, because IE does not account use of border-box
156 | var actualHeight = computed.boxSizing === 'content-box' ? Math.round(parseFloat(computed.height)) : ta.offsetHeight;
157 |
158 | // The actual height not matching the style height (set via the resize method) indicates that
159 | // the max-height has been exceeded, in which case the overflow should be allowed.
160 | if (actualHeight < styleHeight) {
161 | if (computed.overflowY === 'hidden') {
162 | changeOverflow('scroll');
163 | resize();
164 | actualHeight = computed.boxSizing === 'content-box' ? Math.round(parseFloat(window.getComputedStyle(ta, null).height)) : ta.offsetHeight;
165 | }
166 | } else {
167 | // Normally keep overflow set to hidden, to avoid flash of scrollbar as the textarea expands.
168 | if (computed.overflowY !== 'hidden') {
169 | changeOverflow('hidden');
170 | resize();
171 | actualHeight = computed.boxSizing === 'content-box' ? Math.round(parseFloat(window.getComputedStyle(ta, null).height)) : ta.offsetHeight;
172 | }
173 | }
174 |
175 | if (cachedHeight !== actualHeight) {
176 | cachedHeight = actualHeight;
177 | var evt = createEvent('autosize:resized');
178 | try {
179 | ta.dispatchEvent(evt);
180 | } catch (err) {
181 | // Firefox will throw an error on dispatchEvent for a detached element
182 | // https://bugzilla.mozilla.org/show_bug.cgi?id=889376
183 | }
184 | }
185 | }
186 |
187 | var pageResize = function pageResize() {
188 | if (ta.clientWidth !== clientWidth) {
189 | update();
190 | }
191 | };
192 |
193 | var destroy = function (style) {
194 | window.removeEventListener('resize', pageResize, false);
195 | ta.removeEventListener('input', update, false);
196 | ta.removeEventListener('keyup', update, false);
197 | ta.removeEventListener('autosize:destroy', destroy, false);
198 | ta.removeEventListener('autosize:update', update, false);
199 |
200 | Object.keys(style).forEach(function (key) {
201 | ta.style[key] = style[key];
202 | });
203 |
204 | map.delete(ta);
205 | }.bind(ta, {
206 | height: ta.style.height,
207 | resize: ta.style.resize,
208 | overflowY: ta.style.overflowY,
209 | overflowX: ta.style.overflowX,
210 | wordWrap: ta.style.wordWrap
211 | });
212 |
213 | ta.addEventListener('autosize:destroy', destroy, false);
214 |
215 | // IE9 does not fire onpropertychange or oninput for deletions,
216 | // so binding to onkeyup to catch most of those events.
217 | // There is no way that I know of to detect something like 'cut' in IE9.
218 | if ('onpropertychange' in ta && 'oninput' in ta) {
219 | ta.addEventListener('keyup', update, false);
220 | }
221 |
222 | window.addEventListener('resize', pageResize, false);
223 | ta.addEventListener('input', update, false);
224 | ta.addEventListener('autosize:update', update, false);
225 | ta.style.overflowX = 'hidden';
226 | ta.style.wordWrap = 'break-word';
227 |
228 | map.set(ta, {
229 | destroy: destroy,
230 | update: update
231 | });
232 |
233 | init();
234 | }
235 |
236 | function destroy(ta) {
237 | var methods = map.get(ta);
238 | if (methods) {
239 | methods.destroy();
240 | }
241 | }
242 |
243 | function update(ta) {
244 | var methods = map.get(ta);
245 | if (methods) {
246 | methods.update();
247 | }
248 | }
249 |
250 | var autosize = null;
251 |
252 | // Do nothing in Node.js environment and IE8 (or lower)
253 | if (typeof window === 'undefined' || typeof window.getComputedStyle !== 'function') {
254 | autosize = function autosize(el) {
255 | return el;
256 | };
257 | autosize.destroy = function (el) {
258 | return el;
259 | };
260 | autosize.update = function (el) {
261 | return el;
262 | };
263 | } else {
264 | autosize = function autosize(el, options) {
265 | if (el) {
266 | Array.prototype.forEach.call(el.length ? el : [el], function (x) {
267 | return assign(x, options);
268 | });
269 | }
270 | return el;
271 | };
272 | autosize.destroy = function (el) {
273 | if (el) {
274 | Array.prototype.forEach.call(el.length ? el : [el], destroy);
275 | }
276 | return el;
277 | };
278 | autosize.update = function (el) {
279 | if (el) {
280 | Array.prototype.forEach.call(el.length ? el : [el], update);
281 | }
282 | return el;
283 | };
284 | }
285 |
286 | exports.default = autosize;
287 | module.exports = exports['default'];
288 | });
--------------------------------------------------------------------------------
/src/Http/Controllers/Api/MessagesController.php:
--------------------------------------------------------------------------------
1 | user(),
32 | Auth::user(),
33 | $request['channel_name'],
34 | $request['socket_id']
35 | );
36 | }
37 |
38 | /**
39 | * Fetch data by id for (user/group)
40 | *
41 | * @param Request $request
42 | * @return \Illuminate\Http\JsonResponse
43 | */
44 | public function idFetchData(Request $request)
45 | {
46 | return auth()->user();
47 | // Favorite
48 | $favorite = Chatify::inFavorite($request['id']);
49 |
50 | // User data
51 | if ($request['type'] == 'user') {
52 | $fetch = User::where('id', $request['id'])->first();
53 | if($fetch){
54 | $userAvatar = Chatify::getUserWithAvatar($fetch)->avatar;
55 | }
56 | }
57 |
58 | // send the response
59 | return Response::json([
60 | 'favorite' => $favorite,
61 | 'fetch' => $fetch ?? null,
62 | 'user_avatar' => $userAvatar ?? null,
63 | ]);
64 | }
65 |
66 | /**
67 | * This method to make a links for the attachments
68 | * to be downloadable.
69 | *
70 | * @param string $fileName
71 | * @return \Illuminate\Http\JsonResponse
72 | */
73 | public function download($fileName)
74 | {
75 | $path = config('chatify.attachments.folder') . '/' . $fileName;
76 | if (Chatify::storage()->exists($path)) {
77 | return response()->json([
78 | 'file_name' => $fileName,
79 | 'download_path' => Chatify::storage()->url($path)
80 | ], 200);
81 | } else {
82 | return response()->json([
83 | 'message'=>"Sorry, File does not exist in our server or may have been deleted!"
84 | ], 404);
85 | }
86 | }
87 |
88 | /**
89 | * Send a message to database
90 | *
91 | * @param Request $request
92 | * @return JSON response
93 | */
94 | public function send(Request $request)
95 | {
96 | // default variables
97 | $error = (object)[
98 | 'status' => 0,
99 | 'message' => null
100 | ];
101 | $attachment = null;
102 | $attachment_title = null;
103 |
104 | // if there is attachment [file]
105 | if ($request->hasFile('file')) {
106 | // allowed extensions
107 | $allowed_images = Chatify::getAllowedImages();
108 | $allowed_files = Chatify::getAllowedFiles();
109 | $allowed = array_merge($allowed_images, $allowed_files);
110 |
111 | $file = $request->file('file');
112 | // check file size
113 | if ($file->getSize() < Chatify::getMaxUploadSize()) {
114 | if (in_array(strtolower($file->extension()), $allowed)) {
115 | // get attachment name
116 | $attachment_title = $file->getClientOriginalName();
117 | // upload attachment and store the new name
118 | $attachment = Str::uuid() . "." . $file->extension();
119 | $file->storeAs(config('chatify.attachments.folder'), $attachment, config('chatify.storage_disk_name'));
120 | } else {
121 | $error->status = 1;
122 | $error->message = "File extension not allowed!";
123 | }
124 | } else {
125 | $error->status = 1;
126 | $error->message = "File size you are trying to upload is too large!";
127 | }
128 | }
129 |
130 | if (!$error->status) {
131 | // send to database
132 | $message = Chatify::newMessage([
133 | 'type' => $request['type'],
134 | 'from_id' => Auth::user()->id,
135 | 'to_id' => $request['id'],
136 | 'body' => htmlentities(trim($request['message']), ENT_QUOTES, 'UTF-8'),
137 | 'attachment' => ($attachment) ? json_encode((object)[
138 | 'new_name' => $attachment,
139 | 'old_name' => htmlentities(trim($attachment_title), ENT_QUOTES, 'UTF-8'),
140 | ]) : null,
141 | ]);
142 |
143 | // fetch message to send it with the response
144 | $messageData = Chatify::parseMessage($message);
145 |
146 | // send to user using pusher
147 | if (Auth::user()->id != $request['id']) {
148 | Chatify::push("private-chatify.".$request['id'], 'messaging', [
149 | 'from_id' => Auth::user()->id,
150 | 'to_id' => $request['id'],
151 | 'message' => $messageData
152 | ]);
153 | }
154 | }
155 |
156 | // send the response
157 | return Response::json([
158 | 'status' => '200',
159 | 'error' => $error,
160 | 'message' => $messageData ?? [],
161 | 'tempID' => $request['temporaryMsgId'],
162 | ]);
163 | }
164 |
165 | /**
166 | * fetch [user/group] messages from database
167 | *
168 | * @param Request $request
169 | * @return JSON response
170 | */
171 | public function fetch(Request $request)
172 | {
173 | $query = Chatify::fetchMessagesQuery($request['id'])->latest();
174 | $messages = $query->paginate($request->per_page ?? $this->perPage);
175 | $totalMessages = $messages->total();
176 | $lastPage = $messages->lastPage();
177 | $response = [
178 | 'total' => $totalMessages,
179 | 'last_page' => $lastPage,
180 | 'last_message_id' => collect($messages->items())->last()->id ?? null,
181 | 'messages' => $messages->items(),
182 | ];
183 | return Response::json($response);
184 | }
185 |
186 | /**
187 | * Make messages as seen
188 | *
189 | * @param Request $request
190 | * @return void
191 | */
192 | public function seen(Request $request)
193 | {
194 | // make as seen
195 | $seen = Chatify::makeSeen($request['id']);
196 | // send the response
197 | return Response::json([
198 | 'status' => $seen,
199 | ], 200);
200 | }
201 |
202 | /**
203 | * Get contacts list
204 | *
205 | * @param Request $request
206 | * @return \Illuminate\Http\JsonResponse response
207 | */
208 | public function getContacts(Request $request)
209 | {
210 | // get all users that received/sent message from/to [Auth user]
211 | $users = Message::join('users', function ($join) {
212 | $join->on('ch_messages.from_id', '=', 'users.id')
213 | ->orOn('ch_messages.to_id', '=', 'users.id');
214 | })
215 | ->where(function ($q) {
216 | $q->where('ch_messages.from_id', Auth::user()->id)
217 | ->orWhere('ch_messages.to_id', Auth::user()->id);
218 | })
219 | ->where('users.id','!=',Auth::user()->id)
220 | ->select('users.*',DB::raw('MAX(ch_messages.created_at) max_created_at'))
221 | ->orderBy('max_created_at', 'desc')
222 | ->groupBy('users.id')
223 | ->paginate($request->per_page ?? $this->perPage);
224 |
225 | return response()->json([
226 | 'contacts' => $users->items(),
227 | 'total' => $users->total() ?? 0,
228 | 'last_page' => $users->lastPage() ?? 1,
229 | ], 200);
230 | }
231 |
232 | /**
233 | * Put a user in the favorites list
234 | *
235 | * @param Request $request
236 | * @return void
237 | */
238 | public function favorite(Request $request)
239 | {
240 | $userId = $request['user_id'];
241 | // check action [star/unstar]
242 | $favoriteStatus = Chatify::inFavorite($userId) ? 0 : 1;
243 | Chatify::makeInFavorite($userId, $favoriteStatus);
244 |
245 | // send the response
246 | return Response::json([
247 | 'status' => @$favoriteStatus,
248 | ], 200);
249 | }
250 |
251 | /**
252 | * Get favorites list
253 | *
254 | * @param Request $request
255 | * @return void
256 | */
257 | public function getFavorites(Request $request)
258 | {
259 | $favorites = Favorite::where('user_id', Auth::user()->id)->get();
260 | foreach ($favorites as $favorite) {
261 | $favorite->user = User::where('id', $favorite->favorite_id)->first();
262 | }
263 | return Response::json([
264 | 'total' => count($favorites),
265 | 'favorites' => $favorites ?? [],
266 | ], 200);
267 | }
268 |
269 | /**
270 | * Search in messenger
271 | *
272 | * @param Request $request
273 | * @return \Illuminate\Http\JsonResponse
274 | */
275 | public function search(Request $request)
276 | {
277 | $input = trim(filter_var($request['input']));
278 | $records = User::where('id','!=',Auth::user()->id)
279 | ->where('name', 'LIKE', "%{$input}%")
280 | ->paginate($request->per_page ?? $this->perPage);
281 |
282 | foreach ($records->items() as $index => $record) {
283 | $records[$index] += Chatify::getUserWithAvatar($record);
284 | }
285 |
286 | return Response::json([
287 | 'records' => $records->items(),
288 | 'total' => $records->total(),
289 | 'last_page' => $records->lastPage()
290 | ], 200);
291 | }
292 |
293 | /**
294 | * Get shared photos
295 | *
296 | * @param Request $request
297 | * @return \Illuminate\Http\JsonResponse
298 | */
299 | public function sharedPhotos(Request $request)
300 | {
301 | $images = Chatify::getSharedPhotos($request['user_id']);
302 |
303 | foreach ($images as $image) {
304 | $image = asset(config('chatify.attachments.folder') . $image);
305 | }
306 | // send the response
307 | return Response::json([
308 | 'shared' => $images ?? [],
309 | ], 200);
310 | }
311 |
312 | /**
313 | * Delete conversation
314 | *
315 | * @param Request $request
316 | * @return void
317 | */
318 | public function deleteConversation(Request $request)
319 | {
320 | // delete
321 | $delete = Chatify::deleteConversation($request['id']);
322 |
323 | // send the response
324 | return Response::json([
325 | 'deleted' => $delete ? 1 : 0,
326 | ], 200);
327 | }
328 |
329 | public function updateSettings(Request $request)
330 | {
331 | $msg = null;
332 | $error = $success = 0;
333 |
334 | // dark mode
335 | if ($request['dark_mode']) {
336 | $request['dark_mode'] == "dark"
337 | ? User::where('id', Auth::user()->id)->update(['dark_mode' => 1]) // Make Dark
338 | : User::where('id', Auth::user()->id)->update(['dark_mode' => 0]); // Make Light
339 | }
340 |
341 | // If messenger color selected
342 | if ($request['messengerColor']) {
343 | $messenger_color = trim(filter_var($request['messengerColor']));
344 | User::where('id', Auth::user()->id)
345 | ->update(['messenger_color' => $messenger_color]);
346 | }
347 | // if there is a [file]
348 | if ($request->hasFile('avatar')) {
349 | // allowed extensions
350 | $allowed_images = Chatify::getAllowedImages();
351 |
352 | $file = $request->file('avatar');
353 | // check file size
354 | if ($file->getSize() < Chatify::getMaxUploadSize()) {
355 | if (in_array(strtolower($file->extension()), $allowed_images)) {
356 | // delete the older one
357 | if (Auth::user()->avatar != config('chatify.user_avatar.default')) {
358 | $path = Chatify::getUserAvatarUrl(Auth::user()->avatar);
359 | if (Chatify::storage()->exists($path)) {
360 | Chatify::storage()->delete($path);
361 | }
362 | }
363 | // upload
364 | $avatar = Str::uuid() . "." . $file->extension();
365 | $update = User::where('id', Auth::user()->id)->update(['avatar' => $avatar]);
366 | $file->storeAs(config('chatify.user_avatar.folder'), $avatar, config('chatify.storage_disk_name'));
367 | $success = $update ? 1 : 0;
368 | } else {
369 | $msg = "File extension not allowed!";
370 | $error = 1;
371 | }
372 | } else {
373 | $msg = "File size you are trying to upload is too large!";
374 | $error = 1;
375 | }
376 | }
377 |
378 | // send the response
379 | return Response::json([
380 | 'status' => $success ? 1 : 0,
381 | 'error' => $error ? 1 : 0,
382 | 'message' => $error ? $msg : 0,
383 | ], 200);
384 | }
385 |
386 | /**
387 | * Set user's active status
388 | *
389 | * @param Request $request
390 | * @return void
391 | */
392 | public function setActiveStatus(Request $request)
393 | {
394 | $activeStatus = $request['status'] > 0 ? 1 : 0;
395 | $status = User::where('id', Auth::user()->id)->update(['active_status' => $activeStatus]);
396 | return Response::json([
397 | 'status' => $status,
398 | ], 200);
399 | }
400 | }
401 |
--------------------------------------------------------------------------------
/src/ChatifyMessenger.php:
--------------------------------------------------------------------------------
1 | pusher = new Pusher(
29 | config('chatify.pusher.key'),
30 | config('chatify.pusher.secret'),
31 | config('chatify.pusher.app_id'),
32 | config('chatify.pusher.options'),
33 | );
34 | }
35 | /**
36 | * This method returns the allowed image extensions
37 | * to attach with the message.
38 | *
39 | * @return array
40 | */
41 | public function getAllowedImages()
42 | {
43 | return config('chatify.attachments.allowed_images');
44 | }
45 |
46 | /**
47 | * This method returns the allowed file extensions
48 | * to attach with the message.
49 | *
50 | * @return array
51 | */
52 | public function getAllowedFiles()
53 | {
54 | return config('chatify.attachments.allowed_files');
55 | }
56 |
57 | /**
58 | * Returns an array contains messenger's colors
59 | *
60 | * @return array
61 | */
62 | public function getMessengerColors()
63 | {
64 | return config('chatify.colors');
65 | }
66 |
67 | /**
68 | * Returns a fallback primary color.
69 | *
70 | * @return array
71 | */
72 | public function getFallbackColor()
73 | {
74 | $colors = $this->getMessengerColors();
75 | return count($colors) > 0 ? $colors[0] : '#000000';
76 | }
77 |
78 | /**
79 | * Trigger an event using Pusher
80 | *
81 | * @param string $channel
82 | * @param string $event
83 | * @param array $data
84 | * @return void
85 | */
86 | public function push($channel, $event, $data)
87 | {
88 | return $this->pusher->trigger($channel, $event, $data);
89 | }
90 |
91 | /**
92 | * Authentication for pusher
93 | *
94 | * @param User $requestUser
95 | * @param User $authUser
96 | * @param string $channelName
97 | * @param string $socket_id
98 | * @param array $data
99 | * @return void
100 | */
101 | public function pusherAuth($requestUser, $authUser, $channelName, $socket_id)
102 | {
103 | // Auth data
104 | $authData = json_encode([
105 | 'user_id' => $authUser->id,
106 | 'user_info' => [
107 | 'name' => $authUser->name
108 | ]
109 | ]);
110 | // check if user authenticated
111 | if (Auth::check()) {
112 | if($requestUser->id == $authUser->id){
113 | return $this->pusher->socket_auth(
114 | $channelName,
115 | $socket_id,
116 | $authData
117 | );
118 | }
119 | // if not authorized
120 | return response()->json(['message'=>'Unauthorized'], 401);
121 | }
122 | // if not authenticated
123 | return response()->json(['message'=>'Not authenticated'], 403);
124 | }
125 |
126 | /**
127 | * Fetch & parse message and return the message card
128 | * view as a response.
129 | *
130 | * @param Message $prefetchedMessage
131 | * @param int $id
132 | * @return array
133 | */
134 | public function parseMessage($prefetchedMessage = null, $id = null)
135 | {
136 | $msg = null;
137 | $attachment = null;
138 | $attachment_type = null;
139 | $attachment_title = null;
140 | if (!!$prefetchedMessage) {
141 | $msg = $prefetchedMessage;
142 | } else {
143 | $msg = Message::where('id', $id)->first();
144 | if(!$msg){
145 | return [];
146 | }
147 | }
148 | if (isset($msg->attachment)) {
149 | $attachmentOBJ = json_decode($msg->attachment);
150 | $attachment = $attachmentOBJ->new_name;
151 | $attachment_title = htmlentities(trim($attachmentOBJ->old_name), ENT_QUOTES, 'UTF-8');
152 | $ext = pathinfo($attachment, PATHINFO_EXTENSION);
153 | $attachment_type = in_array($ext, $this->getAllowedImages()) ? 'image' : 'file';
154 | }
155 | return [
156 | 'id' => $msg->id,
157 | 'from_id' => $msg->from_id,
158 | 'to_id' => $msg->to_id,
159 | 'message' => $msg->body,
160 | 'attachment' => (object) [
161 | 'file' => $attachment,
162 | 'title' => $attachment_title,
163 | 'type' => $attachment_type
164 | ],
165 | 'timeAgo' => $msg->created_at->diffForHumans(),
166 | 'created_at' => $msg->created_at->toIso8601String(),
167 | 'isSender' => ($msg->from_id == Auth::user()->id),
168 | 'seen' => $msg->seen,
169 | ];
170 | }
171 |
172 | /**
173 | * Return a message card with the given data.
174 | *
175 | * @param Message $data
176 | * @param boolean $isSender
177 | * @return string
178 | */
179 | public function messageCard($data, $renderDefaultCard = false)
180 | {
181 | if (!$data) {
182 | return '';
183 | }
184 | if($renderDefaultCard) {
185 | $data['isSender'] = false;
186 | }
187 | return view('Chatify::layouts.messageCard', $data)->render();
188 | }
189 |
190 | /**
191 | * Default fetch messages query between a Sender and Receiver.
192 | *
193 | * @param int $user_id
194 | * @return Message|\Illuminate\Database\Eloquent\Builder
195 | */
196 | public function fetchMessagesQuery($user_id)
197 | {
198 | return Message::where('from_id', Auth::user()->id)->where('to_id', $user_id)
199 | ->orWhere('from_id', $user_id)->where('to_id', Auth::user()->id);
200 | }
201 |
202 | /**
203 | * create a new message to database
204 | *
205 | * @param array $data
206 | * @return Message
207 | */
208 | public function newMessage($data)
209 | {
210 | $message = new Message();
211 | $message->from_id = $data['from_id'];
212 | $message->to_id = $data['to_id'];
213 | $message->body = $data['body'];
214 | $message->attachment = $data['attachment'];
215 | $message->save();
216 | return $message;
217 | }
218 |
219 | /**
220 | * Make messages between the sender [Auth user] and
221 | * the receiver [User id] as seen.
222 | *
223 | * @param int $user_id
224 | * @return bool
225 | */
226 | public function makeSeen($user_id)
227 | {
228 | Message::Where('from_id', $user_id)
229 | ->where('to_id', Auth::user()->id)
230 | ->where('seen', 0)
231 | ->update(['seen' => 1]);
232 | return 1;
233 | }
234 |
235 | /**
236 | * Get last message for a specific user
237 | *
238 | * @param int $user_id
239 | * @return Message|Collection|\Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Model|object|null
240 | */
241 | public function getLastMessageQuery($user_id)
242 | {
243 | return $this->fetchMessagesQuery($user_id)->latest()->first();
244 | }
245 |
246 | /**
247 | * Count Unseen messages
248 | *
249 | * @param int $user_id
250 | * @return Collection
251 | */
252 | public function countUnseenMessages($user_id)
253 | {
254 | return Message::where('from_id', $user_id)->where('to_id', Auth::user()->id)->where('seen', 0)->count();
255 | }
256 |
257 | /**
258 | * Get user list's item data [Contact Itme]
259 | * (e.g. User data, Last message, Unseen Counter...)
260 | *
261 | * @param int $messenger_id
262 | * @param Collection $user
263 | * @return string
264 | */
265 | public function getContactItem($user)
266 | {
267 | try {
268 | // get last message
269 | $lastMessage = $this->getLastMessageQuery($user->id);
270 | // Get Unseen messages counter
271 | $unseenCounter = $this->countUnseenMessages($user->id);
272 | if ($lastMessage) {
273 | $lastMessage->created_at = $lastMessage->created_at->toIso8601String();
274 | $lastMessage->timeAgo = $lastMessage->created_at->diffForHumans();
275 | }
276 | return view('Chatify::layouts.listItem', [
277 | 'get' => 'users',
278 | 'user' => $this->getUserWithAvatar($user),
279 | 'lastMessage' => $lastMessage,
280 | 'unseenCounter' => $unseenCounter,
281 | ])->render();
282 | } catch (\Throwable $th) {
283 | throw new Exception($th->getMessage());
284 | }
285 | }
286 |
287 | /**
288 | * Get user with avatar (formatted).
289 | *
290 | * @param Collection $user
291 | * @return Collection
292 | */
293 | public function getUserWithAvatar($user)
294 | {
295 | if ($user->avatar == 'avatar.png' && config('chatify.gravatar.enabled')) {
296 | $imageSize = config('chatify.gravatar.image_size');
297 | $imageset = config('chatify.gravatar.imageset');
298 | $user->avatar = 'https://www.gravatar.com/avatar/' . md5(strtolower(trim($user->email))) . '?s=' . $imageSize . '&d=' . $imageset;
299 | } else {
300 | $user->avatar = self::getUserAvatarUrl($user->avatar);
301 | }
302 | return $user;
303 | }
304 |
305 | /**
306 | * Check if a user in the favorite list
307 | *
308 | * @param int $user_id
309 | * @return boolean
310 | */
311 | public function inFavorite($user_id)
312 | {
313 | return Favorite::where('user_id', Auth::user()->id)
314 | ->where('favorite_id', $user_id)->count() > 0
315 | ? true : false;
316 | }
317 |
318 | /**
319 | * Make user in favorite list
320 | *
321 | * @param int $user_id
322 | * @param int $star
323 | * @return boolean
324 | */
325 | public function makeInFavorite($user_id, $action)
326 | {
327 | if ($action > 0) {
328 | // Star
329 | $star = new Favorite();
330 | $star->user_id = Auth::user()->id;
331 | $star->favorite_id = $user_id;
332 | $star->save();
333 | return $star ? true : false;
334 | } else {
335 | // UnStar
336 | $star = Favorite::where('user_id', Auth::user()->id)->where('favorite_id', $user_id)->delete();
337 | return $star ? true : false;
338 | }
339 | }
340 |
341 | /**
342 | * Get shared photos of the conversation
343 | *
344 | * @param int $user_id
345 | * @return array
346 | */
347 | public function getSharedPhotos($user_id)
348 | {
349 | $images = array(); // Default
350 | // Get messages
351 | $msgs = $this->fetchMessagesQuery($user_id)->orderBy('created_at', 'DESC');
352 | if ($msgs->count() > 0) {
353 | foreach ($msgs->get() as $msg) {
354 | // If message has attachment
355 | if ($msg->attachment) {
356 | $attachment = json_decode($msg->attachment);
357 | // determine the type of the attachment
358 | in_array(pathinfo($attachment->new_name, PATHINFO_EXTENSION), $this->getAllowedImages())
359 | ? array_push($images, $attachment->new_name) : '';
360 | }
361 | }
362 | }
363 | return $images;
364 | }
365 |
366 | /**
367 | * Delete Conversation
368 | *
369 | * @param int $user_id
370 | * @return boolean
371 | */
372 | public function deleteConversation($user_id)
373 | {
374 | try {
375 | foreach ($this->fetchMessagesQuery($user_id)->get() as $msg) {
376 | // delete file attached if exist
377 | if (isset($msg->attachment)) {
378 | $path = config('chatify.attachments.folder').'/'.json_decode($msg->attachment)->new_name;
379 | if (self::storage()->exists($path)) {
380 | self::storage()->delete($path);
381 | }
382 | }
383 | // delete from database
384 | $msg->delete();
385 | }
386 | return 1;
387 | } catch (Exception $e) {
388 | return 0;
389 | }
390 | }
391 |
392 | /**
393 | * Delete message by ID
394 | *
395 | * @param int $id
396 | * @return boolean
397 | */
398 | public function deleteMessage($id)
399 | {
400 | try {
401 | $msg = Message::where('from_id', auth()->id())->where('id', $id)->firstOrFail();
402 | if (isset($msg->attachment)) {
403 | $path = config('chatify.attachments.folder') . '/' . json_decode($msg->attachment)->new_name;
404 | if (self::storage()->exists($path)) {
405 | self::storage()->delete($path);
406 | }
407 | }
408 | $msg->delete();
409 | return 1;
410 | } catch (Exception $e) {
411 | throw new Exception($e->getMessage());
412 | }
413 | }
414 |
415 | /**
416 | * Return a storage instance with disk name specified in the config.
417 | *
418 | */
419 | public function storage()
420 | {
421 | return Storage::disk(config('chatify.storage_disk_name'));
422 | }
423 |
424 | /**
425 | * Get user avatar url.
426 | *
427 | * @param string $user_avatar_name
428 | * @return string
429 | */
430 | public function getUserAvatarUrl($user_avatar_name)
431 | {
432 | return self::storage()->url(config('chatify.user_avatar.folder') . '/' . $user_avatar_name);
433 | }
434 |
435 | /**
436 | * Get attachment's url.
437 | *
438 | * @param string $attachment_name
439 | * @return string
440 | */
441 | public function getAttachmentUrl($attachment_name)
442 | {
443 | return self::storage()->url(config('chatify.attachments.folder') . '/' . $attachment_name);
444 | }
445 | }
446 |
--------------------------------------------------------------------------------
/src/Http/Controllers/MessagesController.php:
--------------------------------------------------------------------------------
1 | user(),
31 | Auth::user(),
32 | $request['channel_name'],
33 | $request['socket_id']
34 | );
35 | }
36 |
37 | /**
38 | * Returning the view of the app with the required data.
39 | *
40 | * @param int $id
41 | * @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
42 | */
43 | public function index( $id = null)
44 | {
45 | $messenger_color = Auth::user()->messenger_color;
46 | return view('Chatify::pages.app', [
47 | 'id' => $id ?? 0,
48 | 'messengerColor' => $messenger_color ? $messenger_color : Chatify::getFallbackColor(),
49 | 'dark_mode' => Auth::user()->dark_mode < 1 ? 'light' : 'dark',
50 | ]);
51 | }
52 |
53 |
54 | /**
55 | * Fetch data (user, favorite.. etc).
56 | *
57 | * @param Request $request
58 | * @return JsonResponse
59 | */
60 | public function idFetchData(Request $request)
61 | {
62 | $favorite = Chatify::inFavorite($request['id']);
63 | $fetch = User::where('id', $request['id'])->select('id', 'name', 'email')->first();
64 | if($fetch){
65 | $userAvatar = Chatify::getUserWithAvatar($fetch)->avatar;
66 | }
67 | unset($fetch['email']);
68 | return Response::json([
69 | 'favorite' => $favorite,
70 | 'fetch' => $fetch ?? null,
71 | 'user_avatar' => $userAvatar ?? null,
72 | ]);
73 | }
74 |
75 | /**
76 | * This method to make a links for the attachments
77 | * to be downloadable.
78 | *
79 | * @param string $fileName
80 | * @return \Symfony\Component\HttpFoundation\StreamedResponse|void
81 | */
82 | public function download($fileName)
83 | {
84 | $filePath = config('chatify.attachments.folder') . '/' . $fileName;
85 | if (Chatify::storage()->exists($filePath)) {
86 | return Chatify::storage()->download($filePath);
87 | }
88 | return abort(404, "Sorry, File does not exist in our server or may have been deleted!");
89 | }
90 |
91 | /**
92 | * Send a message to database
93 | *
94 | * @param Request $request
95 | * @return JsonResponse
96 | */
97 | public function send(Request $request)
98 | {
99 | // default variables
100 | $error = (object)[
101 | 'status' => 0,
102 | 'message' => null
103 | ];
104 | $attachment = null;
105 | $attachment_title = null;
106 |
107 | // if there is attachment [file]
108 | if ($request->hasFile('file')) {
109 | // allowed extensions
110 | $allowed_images = Chatify::getAllowedImages();
111 | $allowed_files = Chatify::getAllowedFiles();
112 | $allowed = array_merge($allowed_images, $allowed_files);
113 |
114 | $file = $request->file('file');
115 | // check file size
116 | if ($file->getSize() < Chatify::getMaxUploadSize()) {
117 | if (in_array(strtolower($file->extension()), $allowed)) {
118 | // get attachment name
119 | $attachment_title = $file->getClientOriginalName();
120 | // upload attachment and store the new name
121 | $attachment = Str::uuid() . "." . $file->extension();
122 | $file->storeAs(config('chatify.attachments.folder'), $attachment, config('chatify.storage_disk_name'));
123 | } else {
124 | $error->status = 1;
125 | $error->message = "File extension not allowed!";
126 | }
127 | } else {
128 | $error->status = 1;
129 | $error->message = "File size you are trying to upload is too large!";
130 | }
131 | }
132 |
133 | if (!$error->status) {
134 | $message = Chatify::newMessage([
135 | 'from_id' => Auth::user()->id,
136 | 'to_id' => $request['id'],
137 | 'body' => htmlentities(trim($request['message']), ENT_QUOTES, 'UTF-8'),
138 | 'attachment' => ($attachment) ? json_encode((object)[
139 | 'new_name' => $attachment,
140 | 'old_name' => htmlentities(trim($attachment_title), ENT_QUOTES, 'UTF-8'),
141 | ]) : null,
142 | ]);
143 | $messageData = Chatify::parseMessage($message);
144 | if (Auth::user()->id != $request['id']) {
145 | Chatify::push("private-chatify.".$request['id'], 'messaging', [
146 | 'from_id' => Auth::user()->id,
147 | 'to_id' => $request['id'],
148 | 'message' => Chatify::messageCard($messageData, true)
149 | ]);
150 | }
151 | }
152 |
153 | // send the response
154 | return Response::json([
155 | 'status' => '200',
156 | 'error' => $error,
157 | 'message' => Chatify::messageCard(@$messageData),
158 | 'tempID' => $request['temporaryMsgId'],
159 | ]);
160 | }
161 |
162 | /**
163 | * fetch [user/group] messages from database
164 | *
165 | * @param Request $request
166 | * @return JsonResponse
167 | */
168 | public function fetch(Request $request)
169 | {
170 | $query = Chatify::fetchMessagesQuery($request['id'])->latest();
171 | $messages = $query->paginate($request->per_page ?? $this->perPage);
172 | $totalMessages = $messages->total();
173 | $lastPage = $messages->lastPage();
174 | $response = [
175 | 'total' => $totalMessages,
176 | 'last_page' => $lastPage,
177 | 'last_message_id' => collect($messages->items())->last()->id ?? null,
178 | 'messages' => '',
179 | ];
180 |
181 | // if there is no messages yet.
182 | if ($totalMessages < 1) {
183 | $response['messages'] ='
Say \'hi\' and start messaging
';
184 | return Response::json($response);
185 | }
186 | if (count($messages->items()) < 1) {
187 | $response['messages'] = '';
188 | return Response::json($response);
189 | }
190 | $allMessages = null;
191 | foreach ($messages->reverse() as $message) {
192 | $allMessages .= Chatify::messageCard(
193 | Chatify::parseMessage($message)
194 | );
195 | }
196 | $response['messages'] = $allMessages;
197 | return Response::json($response);
198 | }
199 |
200 | /**
201 | * Make messages as seen
202 | *
203 | * @param Request $request
204 | * @return JsonResponse|void
205 | */
206 | public function seen(Request $request)
207 | {
208 | // make as seen
209 | $seen = Chatify::makeSeen($request['id']);
210 | // send the response
211 | return Response::json([
212 | 'status' => $seen,
213 | ], 200);
214 | }
215 |
216 | /**
217 | * Get contacts list
218 | *
219 | * @param Request $request
220 | * @return JsonResponse
221 | */
222 | public function getContacts(Request $request)
223 | {
224 | // get all users that received/sent message from/to [Auth user]
225 | $users = Message::join('users', function ($join) {
226 | $join->on('ch_messages.from_id', '=', 'users.id')
227 | ->orOn('ch_messages.to_id', '=', 'users.id');
228 | })
229 | ->where(function ($q) {
230 | $q->where('ch_messages.from_id', Auth::user()->id)
231 | ->orWhere('ch_messages.to_id', Auth::user()->id);
232 | })
233 | ->where('users.id','!=',Auth::user()->id)
234 | ->select('users.*',DB::raw('MAX(ch_messages.created_at) max_created_at'))
235 | ->orderBy('max_created_at', 'desc')
236 | ->groupBy('users.id')
237 | ->paginate($request->per_page ?? $this->perPage);
238 |
239 | $usersList = $users->items();
240 |
241 | if (count($usersList) > 0) {
242 | $contacts = '';
243 | foreach ($usersList as $user) {
244 | $contacts .= Chatify::getContactItem($user);
245 | }
246 | } else {
247 | $contacts = '
Your contact list is empty
';
248 | }
249 |
250 | return Response::json([
251 | 'contacts' => $contacts,
252 | 'total' => $users->total() ?? 0,
253 | 'last_page' => $users->lastPage() ?? 1,
254 | ], 200);
255 | }
256 |
257 | /**
258 | * Update user's list item data
259 | *
260 | * @param Request $request
261 | * @return JsonResponse
262 | */
263 | public function updateContactItem(Request $request)
264 | {
265 | // Get user data
266 | $user = User::where('id', $request['user_id'])->first();
267 | if(!$user){
268 | return Response::json([
269 | 'message' => 'User not found!',
270 | ], 401);
271 | }
272 | $contactItem = Chatify::getContactItem($user);
273 |
274 | // send the response
275 | return Response::json([
276 | 'contactItem' => $contactItem,
277 | ], 200);
278 | }
279 |
280 | /**
281 | * Put a user in the favorites list
282 | *
283 | * @param Request $request
284 | * @return JsonResponse|void
285 | */
286 | public function favorite(Request $request)
287 | {
288 | $userId = $request['user_id'];
289 | // check action [star/unstar]
290 | $favoriteStatus = Chatify::inFavorite($userId) ? 0 : 1;
291 | Chatify::makeInFavorite($userId, $favoriteStatus);
292 |
293 | // send the response
294 | return Response::json([
295 | 'status' => @$favoriteStatus,
296 | ], 200);
297 | }
298 |
299 | /**
300 | * Get favorites list
301 | *
302 | * @param Request $request
303 | * @return JsonResponse|void
304 | */
305 | public function getFavorites(Request $request)
306 | {
307 | $favoritesList = null;
308 | $favorites = Favorite::where('user_id', Auth::user()->id);
309 | foreach ($favorites->get() as $favorite) {
310 | // get user data
311 | $user = User::where('id', $favorite->favorite_id)->first();
312 | $favoritesList .= view('Chatify::layouts.favorite', [
313 | 'user' => $user,
314 | ]);
315 | }
316 | // send the response
317 | return Response::json([
318 | 'count' => $favorites->count(),
319 | 'favorites' => $favorites->count() > 0
320 | ? $favoritesList
321 | : 0,
322 | ], 200);
323 | }
324 |
325 | /**
326 | * Search in messenger
327 | *
328 | * @param Request $request
329 | * @return JsonResponse|void
330 | */
331 | public function search(Request $request)
332 | {
333 | $getRecords = null;
334 | $input = trim(filter_var($request['input']));
335 | $records = User::where('id','!=',Auth::user()->id)
336 | ->where('name', 'LIKE', "%{$input}%")
337 | ->paginate($request->per_page ?? $this->perPage);
338 | foreach ($records->items() as $record) {
339 | $getRecords .= view('Chatify::layouts.listItem', [
340 | 'get' => 'search_item',
341 | 'user' => Chatify::getUserWithAvatar($record),
342 | ])->render();
343 | }
344 | if($records->total() < 1){
345 | $getRecords = '
Nothing to show.
';
346 | }
347 | // send the response
348 | return Response::json([
349 | 'records' => $getRecords,
350 | 'total' => $records->total(),
351 | 'last_page' => $records->lastPage()
352 | ], 200);
353 | }
354 |
355 | /**
356 | * Get shared photos
357 | *
358 | * @param Request $request
359 | * @return JsonResponse|void
360 | */
361 | public function sharedPhotos(Request $request)
362 | {
363 | $shared = Chatify::getSharedPhotos($request['user_id']);
364 | $sharedPhotos = null;
365 |
366 | // shared with its template
367 | for ($i = 0; $i < count($shared); $i++) {
368 | $sharedPhotos .= view('Chatify::layouts.listItem', [
369 | 'get' => 'sharedPhoto',
370 | 'image' => Chatify::getAttachmentUrl($shared[$i]),
371 | ])->render();
372 | }
373 | // send the response
374 | return Response::json([
375 | 'shared' => count($shared) > 0 ? $sharedPhotos : '
Nothing shared yet
',
376 | ], 200);
377 | }
378 |
379 | /**
380 | * Delete conversation
381 | *
382 | * @param Request $request
383 | * @return JsonResponse
384 | */
385 | public function deleteConversation(Request $request)
386 | {
387 | // delete
388 | $delete = Chatify::deleteConversation($request['id']);
389 |
390 | // send the response
391 | return Response::json([
392 | 'deleted' => $delete ? 1 : 0,
393 | ], 200);
394 | }
395 |
396 | /**
397 | * Delete message
398 | *
399 | * @param Request $request
400 | * @return JsonResponse
401 | */
402 | public function deleteMessage(Request $request)
403 | {
404 | // delete
405 | $delete = Chatify::deleteMessage($request['id']);
406 |
407 | // send the response
408 | return Response::json([
409 | 'deleted' => $delete ? 1 : 0,
410 | ], 200);
411 | }
412 |
413 | public function updateSettings(Request $request)
414 | {
415 | $msg = null;
416 | $error = $success = 0;
417 |
418 | // dark mode
419 | if ($request['dark_mode']) {
420 | $request['dark_mode'] == "dark"
421 | ? User::where('id', Auth::user()->id)->update(['dark_mode' => 1]) // Make Dark
422 | : User::where('id', Auth::user()->id)->update(['dark_mode' => 0]); // Make Light
423 | }
424 |
425 | // If messenger color selected
426 | if ($request['messengerColor']) {
427 | $messenger_color = trim(filter_var($request['messengerColor']));
428 | User::where('id', Auth::user()->id)
429 | ->update(['messenger_color' => $messenger_color]);
430 | }
431 | // if there is a [file]
432 | if ($request->hasFile('avatar')) {
433 | // allowed extensions
434 | $allowed_images = Chatify::getAllowedImages();
435 |
436 | $file = $request->file('avatar');
437 | // check file size
438 | if ($file->getSize() < Chatify::getMaxUploadSize()) {
439 | if (in_array(strtolower($file->extension()), $allowed_images)) {
440 | // delete the older one
441 | if (Auth::user()->avatar != config('chatify.user_avatar.default')) {
442 | $avatar = Auth::user()->avatar;
443 | if (Chatify::storage()->exists($avatar)) {
444 | Chatify::storage()->delete($avatar);
445 | }
446 | }
447 | // upload
448 | $avatar = Str::uuid() . "." . $file->extension();
449 | $update = User::where('id', Auth::user()->id)->update(['avatar' => $avatar]);
450 | $file->storeAs(config('chatify.user_avatar.folder'), $avatar, config('chatify.storage_disk_name'));
451 | $success = $update ? 1 : 0;
452 | } else {
453 | $msg = "File extension not allowed!";
454 | $error = 1;
455 | }
456 | } else {
457 | $msg = "File size you are trying to upload is too large!";
458 | $error = 1;
459 | }
460 | }
461 |
462 | // send the response
463 | return Response::json([
464 | 'status' => $success ? 1 : 0,
465 | 'error' => $error ? 1 : 0,
466 | 'message' => $error ? $msg : 0,
467 | ], 200);
468 | }
469 |
470 | /**
471 | * Set user's active status
472 | *
473 | * @param Request $request
474 | * @return JsonResponse
475 | */
476 | public function setActiveStatus(Request $request)
477 | {
478 | $activeStatus = $request['status'] > 0 ? 1 : 0;
479 | $status = User::where('id', Auth::user()->id)->update(['active_status' => $activeStatus]);
480 | return Response::json([
481 | 'status' => $status,
482 | ], 200);
483 | }
484 | }
485 |
--------------------------------------------------------------------------------
/src/assets/css/style.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | margin: 0;
4 | padding: 0;
5 | font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
6 | Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue", sans-serif;
7 | }
8 |
9 | :root {
10 | /*
11 | * --------------------------------------------------
12 | * NOTE: `--primary-color` variable set in
13 | * `headLinks.blade.php` view file.
14 | * --------------------------------------------------
15 | */
16 |
17 | /* General variables */
18 | --icon-size: 20px;
19 | --headers-padding: 1rem;
20 | --listView-header-height: 110px;
21 |
22 | /* Light theme variables */
23 | --primary-bg-color: #fff;
24 | --secondary-bg-color: #f7f7f7;
25 | --border-color: #eee;
26 | --messagingView-bg-color: #f6f7f9;
27 | --scrollbar-thumb-color: #cfcfcf;
28 | --modal-bg-color: #fff;
29 | --send-input-icons-color: #4b4b4b;
30 | --message-hint-bg-color: #ededed;
31 | --message-hint-color: #4b4b4b;
32 | --message-card-color: #fff;
33 |
34 | /* Dark theme variables */
35 | --dark-primary-bg-color: #121212;
36 | --dark-secondary-bg-color: #202020;
37 | --dark-border-color: #202020;
38 | --dark-messagingView-bg-color: #1b1b1b;
39 | --dark-scrollbar-thumb-color: #212121;
40 | --dark-modal-bg-color: #1a1a1a;
41 | --dark-send-input-icons-color: #c8c8c8;
42 | --dark-message-hint-bg-color: #292929;
43 | --dark-message-hint-color: #ffffff;
44 | --dark-message-card-color: #292929;
45 | }
46 |
47 | /* NProgress background */
48 | #nprogress .bar {
49 | background: var(--primary-color) !important;
50 | }
51 | #nprogress .peg {
52 | box-shadow: 0 0 10px var(--primary-color), 0 0 5px var(--primary-color) !important;
53 | }
54 | #nprogress .spinner-icon {
55 | border-top-color: var(--primary-color) !important;
56 | border-left-color: var(--primary-color) !important;
57 | }
58 |
59 | /*internet connection*/
60 | .internet-connection {
61 | display: none;
62 | background: rgba(0, 0, 0, 0.76);
63 | position: absolute;
64 | bottom: calc(
65 | -100% + (var(--headers-padding) + var(--headers-padding)) - 8px
66 | ); /* 8px = 4px padding-top + 4px padding-bottom */
67 | left: 0;
68 | right: 0;
69 | text-align: center;
70 | padding: 4px;
71 | color: #fff;
72 | z-index: 1;
73 | }
74 | .internet-connection span {
75 | display: none;
76 | }
77 |
78 | /*green background RGBA*/
79 | .successBG-rgba {
80 | background: rgba(54, 180, 36, 0.76) !important;
81 | }
82 |
83 | /* app scroll*/
84 | .app-scroll::-webkit-scrollbar {
85 | width: 5px;
86 | height: 5px;
87 | border-radius: 4px;
88 | background: transparent;
89 | transition: all 0.3s ease;
90 | }
91 | .app-scroll-hidden::-webkit-scrollbar {
92 | width: 0px;
93 | height: 0px;
94 | }
95 | .app-scroll::-webkit-scrollbar-thumb,
96 | .app-scroll-hidden::-webkit-scrollbar-thumb {
97 | border-radius: 0px;
98 | }
99 | .messenger-headTitle {
100 | margin: 0rem 0.7rem;
101 | }
102 | .messenger {
103 | display: inline-flex;
104 | width: 100%;
105 | height: 100%;
106 | font-family: sans-serif;
107 | }
108 | .messenger-listView {
109 | display: flex;
110 | flex-direction: column;
111 | gap: 5px;
112 | position: relative;
113 | top: 0px;
114 | left: 0px;
115 | right: 0px;
116 | z-index: 1;
117 | background: transparent;
118 | width: 45%;
119 | min-width: 200px;
120 | overflow: auto;
121 | }
122 | .messenger-listView .m-header {
123 | height: var(--listView-header-height);
124 | }
125 | .messenger-listView .m-header > nav {
126 | padding: var(--headers-padding);
127 | }
128 | .messenger-messagingView {
129 | display: flex;
130 | flex-direction: column;
131 | gap: 5px;
132 | overflow: hidden;
133 | width: 100%;
134 | }
135 | .messenger-messagingView .m-header {
136 | padding: var(--headers-padding);
137 | }
138 | .messenger-messagingView .m-body {
139 | position: relative;
140 | padding-top: 15px;
141 | overflow-x: hidden;
142 | overflow-y: auto;
143 | height: 100%;
144 | }
145 | .m-header {
146 | font-weight: 600;
147 | background: transparent;
148 | }
149 | .m-header-right {
150 | display: flex;
151 | align-items: center;
152 | gap: 1rem;
153 | float: right;
154 | }
155 | .m-header-messaging {
156 | position: relative;
157 | background: #fff;
158 | box-shadow: 0px 5px 6px rgba(0, 0, 0, 0.06);
159 | }
160 | .m-header svg {
161 | color: var(--primary-color);
162 | font-size: var(--icon-size);
163 | transition: transform 0.12s;
164 | }
165 | .m-header svg:active {
166 | transform: scale(0.9);
167 | }
168 | .messenger-search[type="text"] {
169 | margin: 0px 10px;
170 | width: calc(100% - 20px);
171 | border: none;
172 | padding: 8px 10px;
173 | border-radius: 6px;
174 | outline: none;
175 | }
176 | .messenger-listView-tabs {
177 | display: inline-flex;
178 | width: 100%;
179 | margin-top: 10px;
180 | background-color: transparent;
181 | box-shadow: 0px 5px 6px rgba(0, 0, 0, 0.06);
182 | }
183 | .messenger-listView-tabs a {
184 | display: flex;
185 | align-items: center;
186 | justify-content: center;
187 | gap: 1rem;
188 | width: 100%;
189 | text-align: center;
190 | padding: 10px;
191 | text-decoration: none;
192 | background-color: transparent;
193 | transition: background 0.3s;
194 | }
195 | .messenger-listView-tabs a:hover,
196 | .messenger-listView-tabs a:focus {
197 | text-decoration: none;
198 | }
199 | .messenger-listView-tabs a,
200 | .messenger-listView-tabs a:hover,
201 | .messenger-listView-tabs a:focus {
202 | color: var(--primary-color);
203 | }
204 | .active-tab {
205 | border-bottom: 2px solid var(--primary-color);
206 | }
207 | .messenger-tab {
208 | overflow: auto;
209 | height: calc(100vh - var(--listView-header-height) - 2px);
210 | display: none;
211 | position: relative;
212 | }
213 | .add-to-favorite {
214 | display: none;
215 | }
216 | .add-to-favorite svg {
217 | color: rgba(180, 180, 180, 0.52) !important;
218 | }
219 | .favorite-added svg {
220 | color: #ffc107 !important;
221 | }
222 | .favorite svg {
223 | color: #ffc107 !important;
224 | }
225 | .show {
226 | display: block;
227 | }
228 | .hide {
229 | display: none;
230 | }
231 | .messenger-list-item {
232 | margin: 0;
233 | width: 100%;
234 | cursor: pointer;
235 | transition: background 0.1s;
236 | }
237 | .m-list-active span,
238 | .m-list-active p {
239 | color: #fff !important;
240 | }
241 |
242 | .m-list-active,
243 | .m-list-active:hover,
244 | .m-list-active:focus {
245 | background: var(--primary-color) !important;
246 | }
247 | .m-list-active b {
248 | background: #fff !important;
249 | color: var(--primary-color) !important;
250 | }
251 | .m-list-active .activeStatus {
252 | border-color: var(--primary-color) !important;
253 | }
254 | .messenger-list-item td {
255 | padding: 10px;
256 | }
257 | .messenger-list-item tr > td:first-child {
258 | padding-right: 0;
259 | width: 55px;
260 | }
261 | .messenger-list-item td p {
262 | margin-bottom: 4px;
263 | font-size: 14px;
264 | }
265 | .messenger-list-item td p span {
266 | float: right;
267 | }
268 | .messenger-list-item td span {
269 | color: #cacaca;
270 | font-weight: 400;
271 | font-size: 12px;
272 | }
273 | .messenger-list-item td b {
274 | float: right;
275 | color: #fff;
276 | background: var(--primary-color);
277 | padding: 0px 4px;
278 | border-radius: 20px;
279 | font-size: 13px;
280 | width: auto;
281 | height: auto;
282 | text-align: center;
283 | }
284 | .avatar {
285 | text-align: center;
286 | border-radius: 100%;
287 | border: 1px solid;
288 | overflow: hidden;
289 | background-image: url("");
290 | background-repeat: no-repeat;
291 | background-size: cover;
292 | background-position: center center;
293 | }
294 | .av-l {
295 | width: 100px;
296 | height: 100px;
297 | }
298 | .av-m {
299 | width: 45px;
300 | height: 45px;
301 | }
302 | .av-s {
303 | width: 32px !important;
304 | height: 32px !important;
305 | }
306 | .saved-messages.avatar {
307 | background-color: transparent;
308 | text-align: center;
309 | display: flex;
310 | flex-direction: column;
311 | align-items: center;
312 | justify-content: center;
313 | }
314 | .saved-messages.avatar > svg {
315 | font-size: 22px;
316 | color: var(--primary-color);
317 | }
318 | .messenger-list-item.m-list-active .saved-messages.avatar > svg {
319 | color: #fff;
320 | }
321 | .messenger-list-item.m-list-active .saved-messages.avatar {
322 | border-color: #ffffff81;
323 | }
324 | .messenger-favorites {
325 | padding: 10px;
326 | overflow: auto;
327 | white-space: nowrap;
328 | }
329 | .messenger-favorites > div {
330 | display: inline-block;
331 | text-align: center;
332 | transition: transform 0.3s;
333 | cursor: pointer;
334 | }
335 | .messenger-favorites > div p {
336 | font-size: 12px;
337 | margin: 8px 0px;
338 | margin-bottom: 0px;
339 | }
340 | .messenger-favorites div.avatar {
341 | border: 2px solid #fff;
342 | margin: 0px 4px;
343 | box-shadow: 0px 0px 0px 2px var(--primary-color);
344 | }
345 | .messenger-favorites > div:active {
346 | transform: scale(0.9);
347 | }
348 | .messenger-title {
349 | position: relative;
350 | margin: 0;
351 | padding: 10px !important;
352 | text-transform: capitalize;
353 | font-size: 12px;
354 | text-align: center;
355 | z-index: 1;
356 | }
357 | .messenger-title > span {
358 | position: relative;
359 | padding: 0px 10px;
360 | z-index: 1;
361 | }
362 | .messenger-title::before {
363 | content: "";
364 | display: block;
365 | width: 100%;
366 | height: 1px;
367 | position: absolute;
368 | bottom: 50%;
369 | left: 0;
370 | right: 0;
371 | z-index: 0;
372 | }
373 | .messenger-infoView {
374 | display: block;
375 | overflow: auto;
376 | width: 40%;
377 | min-width: 200px;
378 | }
379 | .messenger-infoView nav {
380 | display: flex;
381 | align-items: center;
382 | justify-content: space-between;
383 | padding: var(--headers-padding);
384 | }
385 | .messenger-infoView nav a {
386 | color: var(--primary-color);
387 | text-decoration: none;
388 | font-size: var(--icon-size);
389 | }
390 | .messenger-infoView > div {
391 | margin: auto;
392 | margin-top: 8%;
393 | text-align: center;
394 | }
395 | .messenger-infoView > p {
396 | text-align: center;
397 | margin: auto;
398 | margin-top: 15px;
399 | font-size: 18px;
400 | font-weight: 600;
401 | }
402 | .messenger-infoView-btns a {
403 | display: block;
404 | text-decoration: none !important;
405 | padding: 5px 10px;
406 | margin: 0% 10%;
407 | border-radius: 3px;
408 | font-size: 14px;
409 | transition: background 0.3s;
410 | }
411 | .messenger-infoView-btns a.default {
412 | color: var(--primary-color);
413 | }
414 | .messenger-infoView-btns a.default:hover {
415 | background: #f0f6ff;
416 | }
417 | .messenger-infoView-btns a.danger {
418 | color: #ff5555;
419 | }
420 | .messenger-infoView-btns a.danger:hover {
421 | background: rgba(255, 85, 85, 0.11);
422 | }
423 | .shared-photo {
424 | border-radius: 3px;
425 | background: #f7f7f7;
426 | height: 120px;
427 | overflow: hidden;
428 | display: inline-block;
429 | margin: 0px 1px;
430 | width: calc(50% - 12px);
431 | background-position: center center;
432 | background-size: cover;
433 | background-repeat: no-repeat;
434 | cursor: pointer;
435 | }
436 | .shared-photo img {
437 | width: auto;
438 | height: 100%;
439 | }
440 | .messenger-infoView-shared {
441 | display: none;
442 | }
443 | .messenger-infoView-shared .messenger-title {
444 | padding-bottom: 10px;
445 | }
446 | .messenger-infoView-btns .delete-conversation {
447 | display: none;
448 | }
449 | .message-card {
450 | display: flex;
451 | flex-direction: row;
452 | gap: 0.5rem;
453 | align-items: center;
454 | width: 100%;
455 | margin: 2px 15px;
456 | width: calc(100% - 30px); /* 30px = 15px padding left + right */
457 | justify-content: flex-start;
458 | }
459 | .message-card .message-card-content {
460 | display: flex;
461 | flex-direction: column;
462 | gap: 4px;
463 | max-width: 60%;
464 | }
465 | .message-card.mc-sender .message-card-content {
466 | align-items: end;
467 | }
468 | .message-card .image-wrapper .image-file {
469 | position: relative;
470 | }
471 | .message-card .image-wrapper .image-file > div {
472 | display: none;
473 | position: absolute;
474 | bottom: 0;
475 | right: 0;
476 | left: 0;
477 | background: linear-gradient(
478 | 0deg,
479 | rgba(0, 0, 0, 1) 0%,
480 | rgba(0, 0, 0, 0.5) 100%
481 | );
482 | padding: 0.5rem;
483 | font-size: 11px;
484 | color: #fff;
485 | }
486 | .message-card-content:hover .image-wrapper .image-file > div {
487 | display: block;
488 | }
489 | .message-card div {
490 | margin-top: 0px;
491 | }
492 | .message-card .message {
493 | margin: 0;
494 | padding: 6px 15px;
495 | padding-bottom: 5px;
496 | width: fit-content;
497 | width: -webkit-fit-content;
498 | border-radius: 20px;
499 | word-break: break-word;
500 | display: table-cell;
501 | }
502 | .message-card .message-time {
503 | display: inline-block;
504 | font-size: 11px;
505 | }
506 | .message-card .message .message-time:before {
507 | content: "";
508 | background: transparent;
509 | width: 4px;
510 | height: 4px;
511 | display: inline-block;
512 | }
513 | .message-card.mc-sender {
514 | justify-content: flex-end;
515 | }
516 | .message-card.mc-sender .message {
517 | direction: ltr;
518 | color: #fff !important;
519 | background: var(--primary-color) !important;
520 | }
521 | .message-card.mc-sender .message .message-time {
522 | color: rgba(255, 255, 255, 0.67);
523 | }
524 |
525 | .mc-error .message {
526 | background: rgba(255, 0, 0, 0.27) !important;
527 | color: #ff0000 !important;
528 | }
529 | .mc-error .message .message-time {
530 | color: #ff0000 !important;
531 | }
532 | .messenger-sendCard .send-button svg {
533 | color: var(--primary-color);
534 | }
535 | .listView-x,
536 | .show-listView {
537 | display: none;
538 | }
539 | .messenger-sendCard {
540 | display: none;
541 | margin: 10px;
542 | margin-bottom: 1rem;
543 | border-radius: 8px;
544 | padding-left: 8px;
545 | padding-right: 8px;
546 | }
547 | .messenger-sendCard form {
548 | width: 100%;
549 | display: flex;
550 | align-items: center;
551 | justify-content: center;
552 | margin: 0;
553 | }
554 | .messenger-sendCard input[type="file"] {
555 | display: none;
556 | }
557 | .messenger-sendCard button,
558 | .messenger-sendCard button:active,
559 | .messenger-sendCard button:focus {
560 | border: none;
561 | outline: none;
562 | background: none;
563 | padding: 0;
564 | margin: 0;
565 | }
566 | .messenger-sendCard label {
567 | margin: 0;
568 | }
569 | .messenger-sendCard svg {
570 | margin: 9px 10px;
571 | color: #bdcbd6;
572 | cursor: pointer;
573 | font-size: 21px;
574 | transition: transform 0.15s;
575 | }
576 |
577 | .messenger-sendCard svg:active {
578 | transform: scale(0.9);
579 | }
580 | .m-send {
581 | font-size: 14px;
582 | width: 100%;
583 | border: none;
584 | padding: 10px;
585 | outline: none;
586 | resize: none;
587 | background: transparent;
588 | font-family: sans-serif;
589 | height: 44px;
590 | max-height: 200px;
591 | }
592 | .attachment-preview {
593 | position: relative;
594 | padding: 10px;
595 | }
596 |
597 | .attachment-preview > p {
598 | margin: 0;
599 | font-size: 12px;
600 | padding: 0px;
601 | padding-top: 10px;
602 | }
603 | .attachment-preview > p > svg {
604 | font-size: 16px;
605 | margin: 0;
606 | margin-bottom: -1px;
607 | color: #737373;
608 | }
609 | .attachment-preview svg:active {
610 | transform: none;
611 | }
612 | .message-card .image-file,
613 | .attachment-preview .image-file {
614 | cursor: pointer;
615 | width: 140px;
616 | height: 70px;
617 | border-radius: 6px;
618 | width: 260px;
619 | height: 170px;
620 | overflow: hidden;
621 | background-color: #f7f7f7;
622 | background-size: cover;
623 | background-repeat: no-repeat;
624 | background-position: center center;
625 | }
626 | .attachment-preview > svg:first-child {
627 | position: absolute;
628 | background: rgba(0, 0, 0, 0.33);
629 | width: 20px;
630 | height: 20px;
631 | padding: 3px;
632 | border-radius: 100%;
633 | font-size: 16px;
634 | margin: 0;
635 | top: 10px;
636 | color: #fff;
637 | }
638 | #message-form > button {
639 | height: 40px;
640 | }
641 | .file-download {
642 | font-size: 12px;
643 | display: block;
644 | color: #fff;
645 | text-decoration: none;
646 | font-weight: 600;
647 | border: 1px solid rgba(0, 0, 0, 0.08);
648 | background: rgba(0, 0, 0, 0.03);
649 | padding: 2px 8px;
650 | margin-top: 10px;
651 | border-radius: 20px;
652 | transition: transform 0.3s, background 0.3s;
653 | }
654 | .file-download:hover,
655 | .file-download:focus {
656 | color: #fff;
657 | text-decoration: none;
658 | background: rgba(0, 0, 0, 0.08);
659 | }
660 | .file-download:active {
661 | transform: scale(0.95);
662 | }
663 | .typing-indicator {
664 | display: none;
665 | }
666 | .messages {
667 | padding: 5px 0px;
668 | display: flex;
669 | flex-direction: column;
670 | gap: 4px;
671 | }
672 | .message-hint {
673 | margin: 0;
674 | text-align: center;
675 | }
676 | .center-el {
677 | position: absolute;
678 | left: 50%;
679 | top: 50%;
680 | transform: translate(-50%, -50%);
681 | }
682 | .message-hint span {
683 | padding: 3px 10px;
684 | border-radius: 20px;
685 | display: inline-block;
686 | }
687 | .upload-avatar-details {
688 | font-size: 14px;
689 | color: #949ba5;
690 | display: none;
691 | }
692 | .upload-avatar-preview {
693 | position: relative;
694 | border: 1px solid #e0e0e0;
695 | margin: 20px auto;
696 | }
697 | .upload-avatar-loading {
698 | position: absolute;
699 | top: calc(50% - 21px);
700 | margin: 0;
701 | left: calc(50% - 20px);
702 | }
703 | .divider {
704 | margin: 15px;
705 | }
706 | .update-messengerColor {
707 | margin: 1rem 0rem;
708 | }
709 | .update-messengerColor .color-btn {
710 | width: 30px;
711 | height: 30px;
712 | border-radius: 20px;
713 | display: inline-block;
714 | cursor: pointer;
715 | }
716 | .m-color-active {
717 | border: 3px solid rgba(255, 255, 255, 0.5);
718 | }
719 | .update-messengerColor .color-btn {
720 | transition: transform 0.15s, border 0.15s;
721 | }
722 | .update-messengerColor .color-btn:active {
723 | transform: scale(0.9);
724 | }
725 | .dark-mode-switch {
726 | margin: 0px 5px;
727 | cursor: pointer;
728 | color: var(--primary-color);
729 | }
730 | .activeStatus {
731 | width: 12px;
732 | height: 12px;
733 | background: #4caf50;
734 | border-radius: 20px;
735 | position: absolute;
736 | bottom: 12%;
737 | right: 6%;
738 | transition: border 0.1s;
739 | }
740 | .lastMessageIndicator {
741 | color: var(--primary-color) !important;
742 | }
743 |
744 | /*
745 | ***********************************************
746 | * App Buttons
747 | ***********************************************
748 | */
749 | .app-btn {
750 | cursor: pointer;
751 | border: none;
752 | padding: 3px 15px;
753 | border-radius: 20px;
754 | margin: 1px;
755 | font-size: 14px;
756 | display: inline-block;
757 | outline: none;
758 | text-decoration: none;
759 | transition: all 0.3s;
760 | color: rgb(33, 128, 243);
761 | }
762 | .app-btn:hover,
763 | .app-btn:focus {
764 | color: rgb(33, 128, 243);
765 | outline: none;
766 | text-decoration: none;
767 | }
768 | .app-btn:active {
769 | transform: scale(0.9);
770 | }
771 | .a-btn-light {
772 | background: #f1f1f1;
773 | color: #333;
774 | }
775 | .a-btn-light:hover,
776 | .a-btn-light:focus {
777 | color: #333;
778 | background: #e4e4e4;
779 | }
780 | .a-btn-primary {
781 | background: #0976d6;
782 | color: #fff;
783 | }
784 | .a-btn-primary:hover,
785 | .a-btn-primary:focus {
786 | background: #0085ef;
787 | color: #fff;
788 | }
789 | .a-btn-warning {
790 | background: #ffc107;
791 | color: #fff;
792 | }
793 | .a-btn-warning:hover,
794 | .a-btn-warning:focus {
795 | background: #ffa726;
796 | color: #fff;
797 | }
798 | .a-btn-success {
799 | background: #1e8a53 !important;
800 | color: #fff;
801 | }
802 | .a-btn-success:hover,
803 | .a-btn-success:focus {
804 | background: #2ecc71 !important;
805 | color: #fff;
806 | }
807 | .a-btn-danger {
808 | background: #ea1909 !important;
809 | color: #fff;
810 | }
811 | .a-btn-danger:hover,
812 | .a-btn-danger:focus {
813 | color: #fff;
814 | background: #b70d00 !important;
815 | }
816 | .btn-disabled {
817 | opacity: 0.5;
818 | }
819 | /*
820 | ***********************************************
821 | * App Modal
822 | ***********************************************
823 | */
824 | .app-modal {
825 | display: none;
826 | position: fixed;
827 | top: 0;
828 | bottom: 0;
829 | right: 0;
830 | left: 0;
831 | background: rgba(0, 0, 0, 0.53);
832 | z-index: 50;
833 | }
834 | .app-modal-container {
835 | position: absolute;
836 | left: 50%;
837 | top: 50%;
838 | transform: translate(-50%, -50%);
839 | }
840 | .app-modal-card {
841 | width: auto;
842 | max-width: 400px;
843 | margin: auto;
844 | border-radius: 5px;
845 | text-align: center;
846 | box-shadow: 0px 3px 15px rgba(0, 0, 0, 0.27);
847 | transform: scale(0);
848 | }
849 | .app-modal-form {
850 | padding: 20px 40px;
851 | }
852 | .app-modal-header {
853 | font-weight: 500;
854 | }
855 | .app-modal-footer {
856 | margin-top: 10px;
857 | }
858 | .app-show-modal {
859 | transform: scale(1);
860 | animation: show_modal 0.15s;
861 | }
862 | /* modal animation */
863 | @keyframes show_modal {
864 | from {
865 | transform: scale(0);
866 | }
867 | to {
868 | transform: scale(1);
869 | }
870 | }
871 |
872 | /*
873 | ***********************************************
874 | * Placeholder loading
875 | ***********************************************
876 | */
877 | .loadingPlaceholder-wrapper {
878 | position: relative;
879 | }
880 |
881 | .loadingPlaceholder-body div,
882 | .loadingPlaceholder-header tr td div {
883 | background-repeat: no-repeat;
884 | background-size: 800px 104px;
885 | height: 104px;
886 | position: relative;
887 | }
888 |
889 | .loadingPlaceholder-body div {
890 | position: absolute;
891 | right: 0px;
892 | left: 0px;
893 | top: 0px;
894 | }
895 |
896 | div.loadingPlaceholder-avatar {
897 | height: 45px !important;
898 | width: 45px;
899 | margin: 10px;
900 | border-radius: 60px;
901 | }
902 | div.loadingPlaceholder-name {
903 | height: 15px !important;
904 | margin-bottom: 10px;
905 | width: 150px;
906 | border-radius: 2px;
907 | }
908 |
909 | div.loadingPlaceholder-date {
910 | height: 10px !important;
911 | width: 106px;
912 | border-radius: 2px;
913 | }
914 | /*
915 | ***********************************************
916 | * Image modal box
917 | ***********************************************
918 | */
919 | .imageModal {
920 | display: none;
921 | position: fixed;
922 | z-index: 50;
923 | padding-top: 100px;
924 | left: 0;
925 | top: 0;
926 | width: 100%;
927 | height: 100%;
928 | overflow: auto;
929 | background-color: rgb(0, 0, 0);
930 | background-color: rgba(0, 0, 0, 0.9);
931 | }
932 | .imageModal-content {
933 | margin: auto;
934 | display: block;
935 | height: calc(100vh - 150px);
936 | }
937 | .imageModal-content {
938 | -webkit-animation-name: zoom;
939 | -webkit-animation-duration: 0.15s;
940 | animation-name: zoom;
941 | animation-duration: 0.15s;
942 | }
943 |
944 | @-webkit-keyframes zoom {
945 | from {
946 | -webkit-transform: scale(0);
947 | }
948 | to {
949 | -webkit-transform: scale(1);
950 | }
951 | }
952 | @keyframes zoom {
953 | from {
954 | transform: scale(0);
955 | }
956 | to {
957 | transform: scale(1);
958 | }
959 | }
960 |
961 | .imageModal-close {
962 | position: absolute;
963 | top: 15px;
964 | right: 35px;
965 | color: #f1f1f1;
966 | font-size: 40px;
967 | font-weight: bold;
968 | transition: 0.3s;
969 | }
970 |
971 | .imageModal-close:hover,
972 | .imageModal-close:focus {
973 | color: #bbb;
974 | text-decoration: none;
975 | cursor: pointer;
976 | }
977 |
978 | /*
979 | ***********************************************
980 | * Typing (jumping) dots animation and style
981 | ***********************************************
982 | */
983 | .dot {
984 | width: 8px;
985 | height: 8px;
986 | background: #bcc1c6;
987 | display: inline-block;
988 | border-radius: 50%;
989 | right: 0px;
990 | bottom: 0px;
991 | position: relative;
992 | animation: jump 1s infinite;
993 | }
994 |
995 | .typing-dots .dot-1 {
996 | -webkit-animation-delay: 100ms;
997 | animation-delay: 100ms;
998 | }
999 |
1000 | .typing-dots .dot-2 {
1001 | -webkit-animation-delay: 200ms;
1002 | animation-delay: 200ms;
1003 | }
1004 |
1005 | .typing-dots .dot-3 {
1006 | -webkit-animation-delay: 300ms;
1007 | animation-delay: 300ms;
1008 | }
1009 |
1010 | @keyframes jump {
1011 | 0% {
1012 | bottom: 0px;
1013 | }
1014 | 20% {
1015 | bottom: 5px;
1016 | }
1017 | 40% {
1018 | bottom: 0px;
1019 | }
1020 | }
1021 | /*
1022 | *****************************************
1023 | * Responsive Design
1024 | *****************************************
1025 | */
1026 | @media (max-width: 1060px) {
1027 | .messenger-infoView {
1028 | position: fixed;
1029 | right: 0;
1030 | top: 0;
1031 | bottom: 0;
1032 | max-width: 334px;
1033 | }
1034 | }
1035 | @media (max-width: 980px) {
1036 | .messenger-listView.conversation-active {
1037 | display: none;
1038 | }
1039 | .messenger-listView {
1040 | position: fixed;
1041 | left: 0;
1042 | top: 0;
1043 | bottom: 0;
1044 | max-width: 334px;
1045 | }
1046 | .listView-x {
1047 | display: block;
1048 | }
1049 | .show-listView {
1050 | display: inline-block;
1051 | }
1052 | }
1053 | @media (max-width: 680px) {
1054 | .messenger-messagingView {
1055 | position: fixed;
1056 | top: 0;
1057 | left: 0;
1058 | height: 100%;
1059 | }
1060 | .messenger-infoView {
1061 | display: none;
1062 | width: 100%;
1063 | max-width: unset;
1064 | }
1065 | .messenger-listView {
1066 | width: 100%;
1067 | max-width: unset;
1068 | }
1069 | .listView-x {
1070 | display: none;
1071 | }
1072 | .app-modal-container {
1073 | transform: unset;
1074 | }
1075 | .app-modal-card {
1076 | max-width: unset;
1077 | position: fixed;
1078 | left: 0;
1079 | right: 0;
1080 | top: 0;
1081 | bottom: 0;
1082 | width: 100%;
1083 | height: 100%;
1084 | border-radius: 0px;
1085 | }
1086 | }
1087 | @media (min-width: 680px) {
1088 | .messenger-listView {
1089 | display: unset;
1090 | }
1091 | }
1092 | @media only screen and (max-width: 700px) {
1093 | .imageModal-content {
1094 | width: 100%;
1095 | }
1096 | }
1097 |
1098 | @media (max-width: 576px) {
1099 | .user-name {
1100 | max-width: 150px;
1101 | white-space: nowrap;
1102 | overflow: hidden !important;
1103 | text-overflow: ellipsis;
1104 | }
1105 | .chatify-md-block {
1106 | display: block;
1107 | }
1108 | }
1109 |
1110 | .chatify-d-flex {
1111 | display: flex !important;
1112 | }
1113 |
1114 | .chatify-d-none {
1115 | display: none !important;
1116 | }
1117 |
1118 | .chatify-d-hidden {
1119 | visibility: hidden !important;
1120 | }
1121 |
1122 | .chatify-justify-content-between {
1123 | justify-content: space-between !important;
1124 | }
1125 |
1126 | .chatify-align-items-center {
1127 | align-items: center !important;
1128 | }
1129 |
1130 | .chat-message-wrapper {
1131 | display: flex;
1132 | flex-direction: column;
1133 | align-items: end;
1134 | unicode-bidi: bidi-override;
1135 | direction: ltr;
1136 | }
1137 |
1138 | .pb-3 {
1139 | padding-bottom: 0.75rem; /* 12px */
1140 | }
1141 |
1142 | .mb-2 {
1143 | margin-bottom: 0.5rem; /* 8px */
1144 | }
1145 |
1146 | .messenger [type="text"]:focus {
1147 | outline: 1px solid var(--primary-color);
1148 | border-color: var(--primary-color) !important;
1149 | border-color: var(--primary-color);
1150 | box-shadow: 0 0 2px var(--primary-color);
1151 | }
1152 |
1153 | .messenger textarea:focus {
1154 | outline: none;
1155 | border: none;
1156 | box-shadow: none;
1157 | }
1158 | .message-card .actions {
1159 | opacity: 0.6;
1160 | }
1161 | .message-card .actions .delete-btn {
1162 | display: none;
1163 | cursor: pointer;
1164 | color: #333333;
1165 | }
1166 |
1167 | .message-card:hover .actions .delete-btn {
1168 | display: block;
1169 | }
1170 |
1171 | /*
1172 | *****************************************
1173 | * Emoji Button scroll-bars
1174 | *****************************************
1175 | */
1176 | .emoji-picker__emojis::-webkit-scrollbar {
1177 | width: 5px;
1178 | height: 5px;
1179 | border-radius: 4px;
1180 | background: transparent;
1181 | transition: all 0.3s ease;
1182 | }
1183 | .emoji-picker__emojis::-webkit-scrollbar-thumb {
1184 | border-radius: 4px;
1185 | background: transparent;
1186 | }
1187 |
--------------------------------------------------------------------------------
/composer.lock:
--------------------------------------------------------------------------------
1 | {
2 | "_readme": [
3 | "This file locks the dependencies of your project to a known state",
4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
5 | "This file is @generated automatically"
6 | ],
7 | "content-hash": "c6ad250178ab650dd81c52f63abcfafd",
8 | "packages": [
9 | {
10 | "name": "guzzlehttp/guzzle",
11 | "version": "7.5.0",
12 | "source": {
13 | "type": "git",
14 | "url": "https://github.com/guzzle/guzzle.git",
15 | "reference": "b50a2a1251152e43f6a37f0fa053e730a67d25ba"
16 | },
17 | "dist": {
18 | "type": "zip",
19 | "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b50a2a1251152e43f6a37f0fa053e730a67d25ba",
20 | "reference": "b50a2a1251152e43f6a37f0fa053e730a67d25ba",
21 | "shasum": ""
22 | },
23 | "require": {
24 | "ext-json": "*",
25 | "guzzlehttp/promises": "^1.5",
26 | "guzzlehttp/psr7": "^1.9 || ^2.4",
27 | "php": "^7.2.5 || ^8.0",
28 | "psr/http-client": "^1.0",
29 | "symfony/deprecation-contracts": "^2.2 || ^3.0"
30 | },
31 | "provide": {
32 | "psr/http-client-implementation": "1.0"
33 | },
34 | "require-dev": {
35 | "bamarni/composer-bin-plugin": "^1.8.1",
36 | "ext-curl": "*",
37 | "php-http/client-integration-tests": "^3.0",
38 | "phpunit/phpunit": "^8.5.29 || ^9.5.23",
39 | "psr/log": "^1.1 || ^2.0 || ^3.0"
40 | },
41 | "suggest": {
42 | "ext-curl": "Required for CURL handler support",
43 | "ext-intl": "Required for Internationalized Domain Name (IDN) support",
44 | "psr/log": "Required for using the Log middleware"
45 | },
46 | "type": "library",
47 | "extra": {
48 | "bamarni-bin": {
49 | "bin-links": true,
50 | "forward-command": false
51 | },
52 | "branch-alias": {
53 | "dev-master": "7.5-dev"
54 | }
55 | },
56 | "autoload": {
57 | "files": [
58 | "src/functions_include.php"
59 | ],
60 | "psr-4": {
61 | "GuzzleHttp\\": "src/"
62 | }
63 | },
64 | "notification-url": "https://packagist.org/downloads/",
65 | "license": [
66 | "MIT"
67 | ],
68 | "authors": [
69 | {
70 | "name": "Graham Campbell",
71 | "email": "hello@gjcampbell.co.uk",
72 | "homepage": "https://github.com/GrahamCampbell"
73 | },
74 | {
75 | "name": "Michael Dowling",
76 | "email": "mtdowling@gmail.com",
77 | "homepage": "https://github.com/mtdowling"
78 | },
79 | {
80 | "name": "Jeremy Lindblom",
81 | "email": "jeremeamia@gmail.com",
82 | "homepage": "https://github.com/jeremeamia"
83 | },
84 | {
85 | "name": "George Mponos",
86 | "email": "gmponos@gmail.com",
87 | "homepage": "https://github.com/gmponos"
88 | },
89 | {
90 | "name": "Tobias Nyholm",
91 | "email": "tobias.nyholm@gmail.com",
92 | "homepage": "https://github.com/Nyholm"
93 | },
94 | {
95 | "name": "Márk Sági-Kazár",
96 | "email": "mark.sagikazar@gmail.com",
97 | "homepage": "https://github.com/sagikazarmark"
98 | },
99 | {
100 | "name": "Tobias Schultze",
101 | "email": "webmaster@tubo-world.de",
102 | "homepage": "https://github.com/Tobion"
103 | }
104 | ],
105 | "description": "Guzzle is a PHP HTTP client library",
106 | "keywords": [
107 | "client",
108 | "curl",
109 | "framework",
110 | "http",
111 | "http client",
112 | "psr-18",
113 | "psr-7",
114 | "rest",
115 | "web service"
116 | ],
117 | "support": {
118 | "issues": "https://github.com/guzzle/guzzle/issues",
119 | "source": "https://github.com/guzzle/guzzle/tree/7.5.0"
120 | },
121 | "funding": [
122 | {
123 | "url": "https://github.com/GrahamCampbell",
124 | "type": "github"
125 | },
126 | {
127 | "url": "https://github.com/Nyholm",
128 | "type": "github"
129 | },
130 | {
131 | "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle",
132 | "type": "tidelift"
133 | }
134 | ],
135 | "time": "2022-08-28T15:39:27+00:00"
136 | },
137 | {
138 | "name": "guzzlehttp/promises",
139 | "version": "1.5.2",
140 | "source": {
141 | "type": "git",
142 | "url": "https://github.com/guzzle/promises.git",
143 | "reference": "b94b2807d85443f9719887892882d0329d1e2598"
144 | },
145 | "dist": {
146 | "type": "zip",
147 | "url": "https://api.github.com/repos/guzzle/promises/zipball/b94b2807d85443f9719887892882d0329d1e2598",
148 | "reference": "b94b2807d85443f9719887892882d0329d1e2598",
149 | "shasum": ""
150 | },
151 | "require": {
152 | "php": ">=5.5"
153 | },
154 | "require-dev": {
155 | "symfony/phpunit-bridge": "^4.4 || ^5.1"
156 | },
157 | "type": "library",
158 | "extra": {
159 | "branch-alias": {
160 | "dev-master": "1.5-dev"
161 | }
162 | },
163 | "autoload": {
164 | "files": [
165 | "src/functions_include.php"
166 | ],
167 | "psr-4": {
168 | "GuzzleHttp\\Promise\\": "src/"
169 | }
170 | },
171 | "notification-url": "https://packagist.org/downloads/",
172 | "license": [
173 | "MIT"
174 | ],
175 | "authors": [
176 | {
177 | "name": "Graham Campbell",
178 | "email": "hello@gjcampbell.co.uk",
179 | "homepage": "https://github.com/GrahamCampbell"
180 | },
181 | {
182 | "name": "Michael Dowling",
183 | "email": "mtdowling@gmail.com",
184 | "homepage": "https://github.com/mtdowling"
185 | },
186 | {
187 | "name": "Tobias Nyholm",
188 | "email": "tobias.nyholm@gmail.com",
189 | "homepage": "https://github.com/Nyholm"
190 | },
191 | {
192 | "name": "Tobias Schultze",
193 | "email": "webmaster@tubo-world.de",
194 | "homepage": "https://github.com/Tobion"
195 | }
196 | ],
197 | "description": "Guzzle promises library",
198 | "keywords": [
199 | "promise"
200 | ],
201 | "support": {
202 | "issues": "https://github.com/guzzle/promises/issues",
203 | "source": "https://github.com/guzzle/promises/tree/1.5.2"
204 | },
205 | "funding": [
206 | {
207 | "url": "https://github.com/GrahamCampbell",
208 | "type": "github"
209 | },
210 | {
211 | "url": "https://github.com/Nyholm",
212 | "type": "github"
213 | },
214 | {
215 | "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises",
216 | "type": "tidelift"
217 | }
218 | ],
219 | "time": "2022-08-28T14:55:35+00:00"
220 | },
221 | {
222 | "name": "guzzlehttp/psr7",
223 | "version": "2.4.3",
224 | "source": {
225 | "type": "git",
226 | "url": "https://github.com/guzzle/psr7.git",
227 | "reference": "67c26b443f348a51926030c83481b85718457d3d"
228 | },
229 | "dist": {
230 | "type": "zip",
231 | "url": "https://api.github.com/repos/guzzle/psr7/zipball/67c26b443f348a51926030c83481b85718457d3d",
232 | "reference": "67c26b443f348a51926030c83481b85718457d3d",
233 | "shasum": ""
234 | },
235 | "require": {
236 | "php": "^7.2.5 || ^8.0",
237 | "psr/http-factory": "^1.0",
238 | "psr/http-message": "^1.0",
239 | "ralouphie/getallheaders": "^3.0"
240 | },
241 | "provide": {
242 | "psr/http-factory-implementation": "1.0",
243 | "psr/http-message-implementation": "1.0"
244 | },
245 | "require-dev": {
246 | "bamarni/composer-bin-plugin": "^1.8.1",
247 | "http-interop/http-factory-tests": "^0.9",
248 | "phpunit/phpunit": "^8.5.29 || ^9.5.23"
249 | },
250 | "suggest": {
251 | "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
252 | },
253 | "type": "library",
254 | "extra": {
255 | "bamarni-bin": {
256 | "bin-links": true,
257 | "forward-command": false
258 | },
259 | "branch-alias": {
260 | "dev-master": "2.4-dev"
261 | }
262 | },
263 | "autoload": {
264 | "psr-4": {
265 | "GuzzleHttp\\Psr7\\": "src/"
266 | }
267 | },
268 | "notification-url": "https://packagist.org/downloads/",
269 | "license": [
270 | "MIT"
271 | ],
272 | "authors": [
273 | {
274 | "name": "Graham Campbell",
275 | "email": "hello@gjcampbell.co.uk",
276 | "homepage": "https://github.com/GrahamCampbell"
277 | },
278 | {
279 | "name": "Michael Dowling",
280 | "email": "mtdowling@gmail.com",
281 | "homepage": "https://github.com/mtdowling"
282 | },
283 | {
284 | "name": "George Mponos",
285 | "email": "gmponos@gmail.com",
286 | "homepage": "https://github.com/gmponos"
287 | },
288 | {
289 | "name": "Tobias Nyholm",
290 | "email": "tobias.nyholm@gmail.com",
291 | "homepage": "https://github.com/Nyholm"
292 | },
293 | {
294 | "name": "Márk Sági-Kazár",
295 | "email": "mark.sagikazar@gmail.com",
296 | "homepage": "https://github.com/sagikazarmark"
297 | },
298 | {
299 | "name": "Tobias Schultze",
300 | "email": "webmaster@tubo-world.de",
301 | "homepage": "https://github.com/Tobion"
302 | },
303 | {
304 | "name": "Márk Sági-Kazár",
305 | "email": "mark.sagikazar@gmail.com",
306 | "homepage": "https://sagikazarmark.hu"
307 | }
308 | ],
309 | "description": "PSR-7 message implementation that also provides common utility methods",
310 | "keywords": [
311 | "http",
312 | "message",
313 | "psr-7",
314 | "request",
315 | "response",
316 | "stream",
317 | "uri",
318 | "url"
319 | ],
320 | "support": {
321 | "issues": "https://github.com/guzzle/psr7/issues",
322 | "source": "https://github.com/guzzle/psr7/tree/2.4.3"
323 | },
324 | "funding": [
325 | {
326 | "url": "https://github.com/GrahamCampbell",
327 | "type": "github"
328 | },
329 | {
330 | "url": "https://github.com/Nyholm",
331 | "type": "github"
332 | },
333 | {
334 | "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7",
335 | "type": "tidelift"
336 | }
337 | ],
338 | "time": "2022-10-26T14:07:24+00:00"
339 | },
340 | {
341 | "name": "paragonie/random_compat",
342 | "version": "v9.99.100",
343 | "source": {
344 | "type": "git",
345 | "url": "https://github.com/paragonie/random_compat.git",
346 | "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a"
347 | },
348 | "dist": {
349 | "type": "zip",
350 | "url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a",
351 | "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a",
352 | "shasum": ""
353 | },
354 | "require": {
355 | "php": ">= 7"
356 | },
357 | "require-dev": {
358 | "phpunit/phpunit": "4.*|5.*",
359 | "vimeo/psalm": "^1"
360 | },
361 | "suggest": {
362 | "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes."
363 | },
364 | "type": "library",
365 | "notification-url": "https://packagist.org/downloads/",
366 | "license": [
367 | "MIT"
368 | ],
369 | "authors": [
370 | {
371 | "name": "Paragon Initiative Enterprises",
372 | "email": "security@paragonie.com",
373 | "homepage": "https://paragonie.com"
374 | }
375 | ],
376 | "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7",
377 | "keywords": [
378 | "csprng",
379 | "polyfill",
380 | "pseudorandom",
381 | "random"
382 | ],
383 | "support": {
384 | "email": "info@paragonie.com",
385 | "issues": "https://github.com/paragonie/random_compat/issues",
386 | "source": "https://github.com/paragonie/random_compat"
387 | },
388 | "time": "2020-10-15T08:29:30+00:00"
389 | },
390 | {
391 | "name": "paragonie/sodium_compat",
392 | "version": "v1.19.0",
393 | "source": {
394 | "type": "git",
395 | "url": "https://github.com/paragonie/sodium_compat.git",
396 | "reference": "cb15e403ecbe6a6cc515f855c310eb6b1872a933"
397 | },
398 | "dist": {
399 | "type": "zip",
400 | "url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/cb15e403ecbe6a6cc515f855c310eb6b1872a933",
401 | "reference": "cb15e403ecbe6a6cc515f855c310eb6b1872a933",
402 | "shasum": ""
403 | },
404 | "require": {
405 | "paragonie/random_compat": ">=1",
406 | "php": "^5.2.4|^5.3|^5.4|^5.5|^5.6|^7|^8"
407 | },
408 | "require-dev": {
409 | "phpunit/phpunit": "^3|^4|^5|^6|^7|^8|^9"
410 | },
411 | "suggest": {
412 | "ext-libsodium": "PHP < 7.0: Better performance, password hashing (Argon2i), secure memory management (memzero), and better security.",
413 | "ext-sodium": "PHP >= 7.0: Better performance, password hashing (Argon2i), secure memory management (memzero), and better security."
414 | },
415 | "type": "library",
416 | "autoload": {
417 | "files": [
418 | "autoload.php"
419 | ]
420 | },
421 | "notification-url": "https://packagist.org/downloads/",
422 | "license": [
423 | "ISC"
424 | ],
425 | "authors": [
426 | {
427 | "name": "Paragon Initiative Enterprises",
428 | "email": "security@paragonie.com"
429 | },
430 | {
431 | "name": "Frank Denis",
432 | "email": "jedisct1@pureftpd.org"
433 | }
434 | ],
435 | "description": "Pure PHP implementation of libsodium; uses the PHP extension if it exists",
436 | "keywords": [
437 | "Authentication",
438 | "BLAKE2b",
439 | "ChaCha20",
440 | "ChaCha20-Poly1305",
441 | "Chapoly",
442 | "Curve25519",
443 | "Ed25519",
444 | "EdDSA",
445 | "Edwards-curve Digital Signature Algorithm",
446 | "Elliptic Curve Diffie-Hellman",
447 | "Poly1305",
448 | "Pure-PHP cryptography",
449 | "RFC 7748",
450 | "RFC 8032",
451 | "Salpoly",
452 | "Salsa20",
453 | "X25519",
454 | "XChaCha20-Poly1305",
455 | "XSalsa20-Poly1305",
456 | "Xchacha20",
457 | "Xsalsa20",
458 | "aead",
459 | "cryptography",
460 | "ecdh",
461 | "elliptic curve",
462 | "elliptic curve cryptography",
463 | "encryption",
464 | "libsodium",
465 | "php",
466 | "public-key cryptography",
467 | "secret-key cryptography",
468 | "side-channel resistant"
469 | ],
470 | "support": {
471 | "issues": "https://github.com/paragonie/sodium_compat/issues",
472 | "source": "https://github.com/paragonie/sodium_compat/tree/v1.19.0"
473 | },
474 | "time": "2022-09-26T03:40:35+00:00"
475 | },
476 | {
477 | "name": "psr/http-client",
478 | "version": "1.0.1",
479 | "source": {
480 | "type": "git",
481 | "url": "https://github.com/php-fig/http-client.git",
482 | "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621"
483 | },
484 | "dist": {
485 | "type": "zip",
486 | "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621",
487 | "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621",
488 | "shasum": ""
489 | },
490 | "require": {
491 | "php": "^7.0 || ^8.0",
492 | "psr/http-message": "^1.0"
493 | },
494 | "type": "library",
495 | "extra": {
496 | "branch-alias": {
497 | "dev-master": "1.0.x-dev"
498 | }
499 | },
500 | "autoload": {
501 | "psr-4": {
502 | "Psr\\Http\\Client\\": "src/"
503 | }
504 | },
505 | "notification-url": "https://packagist.org/downloads/",
506 | "license": [
507 | "MIT"
508 | ],
509 | "authors": [
510 | {
511 | "name": "PHP-FIG",
512 | "homepage": "http://www.php-fig.org/"
513 | }
514 | ],
515 | "description": "Common interface for HTTP clients",
516 | "homepage": "https://github.com/php-fig/http-client",
517 | "keywords": [
518 | "http",
519 | "http-client",
520 | "psr",
521 | "psr-18"
522 | ],
523 | "support": {
524 | "source": "https://github.com/php-fig/http-client/tree/master"
525 | },
526 | "time": "2020-06-29T06:28:15+00:00"
527 | },
528 | {
529 | "name": "psr/http-factory",
530 | "version": "1.0.1",
531 | "source": {
532 | "type": "git",
533 | "url": "https://github.com/php-fig/http-factory.git",
534 | "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be"
535 | },
536 | "dist": {
537 | "type": "zip",
538 | "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be",
539 | "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be",
540 | "shasum": ""
541 | },
542 | "require": {
543 | "php": ">=7.0.0",
544 | "psr/http-message": "^1.0"
545 | },
546 | "type": "library",
547 | "extra": {
548 | "branch-alias": {
549 | "dev-master": "1.0.x-dev"
550 | }
551 | },
552 | "autoload": {
553 | "psr-4": {
554 | "Psr\\Http\\Message\\": "src/"
555 | }
556 | },
557 | "notification-url": "https://packagist.org/downloads/",
558 | "license": [
559 | "MIT"
560 | ],
561 | "authors": [
562 | {
563 | "name": "PHP-FIG",
564 | "homepage": "http://www.php-fig.org/"
565 | }
566 | ],
567 | "description": "Common interfaces for PSR-7 HTTP message factories",
568 | "keywords": [
569 | "factory",
570 | "http",
571 | "message",
572 | "psr",
573 | "psr-17",
574 | "psr-7",
575 | "request",
576 | "response"
577 | ],
578 | "support": {
579 | "source": "https://github.com/php-fig/http-factory/tree/master"
580 | },
581 | "time": "2019-04-30T12:38:16+00:00"
582 | },
583 | {
584 | "name": "psr/http-message",
585 | "version": "1.0.1",
586 | "source": {
587 | "type": "git",
588 | "url": "https://github.com/php-fig/http-message.git",
589 | "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363"
590 | },
591 | "dist": {
592 | "type": "zip",
593 | "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363",
594 | "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363",
595 | "shasum": ""
596 | },
597 | "require": {
598 | "php": ">=5.3.0"
599 | },
600 | "type": "library",
601 | "extra": {
602 | "branch-alias": {
603 | "dev-master": "1.0.x-dev"
604 | }
605 | },
606 | "autoload": {
607 | "psr-4": {
608 | "Psr\\Http\\Message\\": "src/"
609 | }
610 | },
611 | "notification-url": "https://packagist.org/downloads/",
612 | "license": [
613 | "MIT"
614 | ],
615 | "authors": [
616 | {
617 | "name": "PHP-FIG",
618 | "homepage": "http://www.php-fig.org/"
619 | }
620 | ],
621 | "description": "Common interface for HTTP messages",
622 | "homepage": "https://github.com/php-fig/http-message",
623 | "keywords": [
624 | "http",
625 | "http-message",
626 | "psr",
627 | "psr-7",
628 | "request",
629 | "response"
630 | ],
631 | "support": {
632 | "source": "https://github.com/php-fig/http-message/tree/master"
633 | },
634 | "time": "2016-08-06T14:39:51+00:00"
635 | },
636 | {
637 | "name": "psr/log",
638 | "version": "3.0.0",
639 | "source": {
640 | "type": "git",
641 | "url": "https://github.com/php-fig/log.git",
642 | "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001"
643 | },
644 | "dist": {
645 | "type": "zip",
646 | "url": "https://api.github.com/repos/php-fig/log/zipball/fe5ea303b0887d5caefd3d431c3e61ad47037001",
647 | "reference": "fe5ea303b0887d5caefd3d431c3e61ad47037001",
648 | "shasum": ""
649 | },
650 | "require": {
651 | "php": ">=8.0.0"
652 | },
653 | "type": "library",
654 | "extra": {
655 | "branch-alias": {
656 | "dev-master": "3.x-dev"
657 | }
658 | },
659 | "autoload": {
660 | "psr-4": {
661 | "Psr\\Log\\": "src"
662 | }
663 | },
664 | "notification-url": "https://packagist.org/downloads/",
665 | "license": [
666 | "MIT"
667 | ],
668 | "authors": [
669 | {
670 | "name": "PHP-FIG",
671 | "homepage": "https://www.php-fig.org/"
672 | }
673 | ],
674 | "description": "Common interface for logging libraries",
675 | "homepage": "https://github.com/php-fig/log",
676 | "keywords": [
677 | "log",
678 | "psr",
679 | "psr-3"
680 | ],
681 | "support": {
682 | "source": "https://github.com/php-fig/log/tree/3.0.0"
683 | },
684 | "time": "2021-07-14T16:46:02+00:00"
685 | },
686 | {
687 | "name": "pusher/pusher-php-server",
688 | "version": "7.2.2",
689 | "source": {
690 | "type": "git",
691 | "url": "https://github.com/pusher/pusher-http-php.git",
692 | "reference": "4ace4873873b06c25cecb2dd6d9fdcbf2f20b640"
693 | },
694 | "dist": {
695 | "type": "zip",
696 | "url": "https://api.github.com/repos/pusher/pusher-http-php/zipball/4ace4873873b06c25cecb2dd6d9fdcbf2f20b640",
697 | "reference": "4ace4873873b06c25cecb2dd6d9fdcbf2f20b640",
698 | "shasum": ""
699 | },
700 | "require": {
701 | "ext-curl": "*",
702 | "ext-json": "*",
703 | "guzzlehttp/guzzle": "^7.2",
704 | "paragonie/sodium_compat": "^1.6",
705 | "php": "^7.3|^8.0",
706 | "psr/log": "^1.0|^2.0|^3.0"
707 | },
708 | "require-dev": {
709 | "overtrue/phplint": "^2.3",
710 | "phpunit/phpunit": "^9.3"
711 | },
712 | "type": "library",
713 | "extra": {
714 | "branch-alias": {
715 | "dev-master": "5.0-dev"
716 | }
717 | },
718 | "autoload": {
719 | "psr-4": {
720 | "Pusher\\": "src/"
721 | }
722 | },
723 | "notification-url": "https://packagist.org/downloads/",
724 | "license": [
725 | "MIT"
726 | ],
727 | "description": "Library for interacting with the Pusher REST API",
728 | "keywords": [
729 | "events",
730 | "messaging",
731 | "php-pusher-server",
732 | "publish",
733 | "push",
734 | "pusher",
735 | "real time",
736 | "real-time",
737 | "realtime",
738 | "rest",
739 | "trigger"
740 | ],
741 | "support": {
742 | "issues": "https://github.com/pusher/pusher-http-php/issues",
743 | "source": "https://github.com/pusher/pusher-http-php/tree/7.2.2"
744 | },
745 | "time": "2022-12-20T19:52:36+00:00"
746 | },
747 | {
748 | "name": "ralouphie/getallheaders",
749 | "version": "3.0.3",
750 | "source": {
751 | "type": "git",
752 | "url": "https://github.com/ralouphie/getallheaders.git",
753 | "reference": "120b605dfeb996808c31b6477290a714d356e822"
754 | },
755 | "dist": {
756 | "type": "zip",
757 | "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822",
758 | "reference": "120b605dfeb996808c31b6477290a714d356e822",
759 | "shasum": ""
760 | },
761 | "require": {
762 | "php": ">=5.6"
763 | },
764 | "require-dev": {
765 | "php-coveralls/php-coveralls": "^2.1",
766 | "phpunit/phpunit": "^5 || ^6.5"
767 | },
768 | "type": "library",
769 | "autoload": {
770 | "files": [
771 | "src/getallheaders.php"
772 | ]
773 | },
774 | "notification-url": "https://packagist.org/downloads/",
775 | "license": [
776 | "MIT"
777 | ],
778 | "authors": [
779 | {
780 | "name": "Ralph Khattar",
781 | "email": "ralph.khattar@gmail.com"
782 | }
783 | ],
784 | "description": "A polyfill for getallheaders.",
785 | "support": {
786 | "issues": "https://github.com/ralouphie/getallheaders/issues",
787 | "source": "https://github.com/ralouphie/getallheaders/tree/develop"
788 | },
789 | "time": "2019-03-08T08:55:37+00:00"
790 | },
791 | {
792 | "name": "symfony/deprecation-contracts",
793 | "version": "v3.2.0",
794 | "source": {
795 | "type": "git",
796 | "url": "https://github.com/symfony/deprecation-contracts.git",
797 | "reference": "1ee04c65529dea5d8744774d474e7cbd2f1206d3"
798 | },
799 | "dist": {
800 | "type": "zip",
801 | "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/1ee04c65529dea5d8744774d474e7cbd2f1206d3",
802 | "reference": "1ee04c65529dea5d8744774d474e7cbd2f1206d3",
803 | "shasum": ""
804 | },
805 | "require": {
806 | "php": ">=8.1"
807 | },
808 | "type": "library",
809 | "extra": {
810 | "branch-alias": {
811 | "dev-main": "3.3-dev"
812 | },
813 | "thanks": {
814 | "name": "symfony/contracts",
815 | "url": "https://github.com/symfony/contracts"
816 | }
817 | },
818 | "autoload": {
819 | "files": [
820 | "function.php"
821 | ]
822 | },
823 | "notification-url": "https://packagist.org/downloads/",
824 | "license": [
825 | "MIT"
826 | ],
827 | "authors": [
828 | {
829 | "name": "Nicolas Grekas",
830 | "email": "p@tchwork.com"
831 | },
832 | {
833 | "name": "Symfony Community",
834 | "homepage": "https://symfony.com/contributors"
835 | }
836 | ],
837 | "description": "A generic function and convention to trigger deprecation notices",
838 | "homepage": "https://symfony.com",
839 | "support": {
840 | "source": "https://github.com/symfony/deprecation-contracts/tree/v3.2.0"
841 | },
842 | "funding": [
843 | {
844 | "url": "https://symfony.com/sponsor",
845 | "type": "custom"
846 | },
847 | {
848 | "url": "https://github.com/fabpot",
849 | "type": "github"
850 | },
851 | {
852 | "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
853 | "type": "tidelift"
854 | }
855 | ],
856 | "time": "2022-11-25T10:21:52+00:00"
857 | }
858 | ],
859 | "packages-dev": [],
860 | "aliases": [],
861 | "minimum-stability": "dev",
862 | "stability-flags": [],
863 | "prefer-stable": true,
864 | "prefer-lowest": false,
865 | "platform": [],
866 | "platform-dev": [],
867 | "plugin-api-version": "2.0.0"
868 | }
869 |
--------------------------------------------------------------------------------