Uploaded From CV. Swandhana Server

This commit is contained in:
Duidev Software House
2025-01-27 08:16:55 +07:00
commit 6b3be42361
15186 changed files with 2328862 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
/vendor
composer.lock
+245
View File
@@ -0,0 +1,245 @@
# Change log
All notable changes to this project will be documented in this file.
## v1.6.3 (2024-03-17)
### Added
- Support for a custom routes.
## v1.6.2 (2023-07-27)
### Added
- Support for a custom WS server #291.
## v1.6.1 (2023-03-03)
### Fixed
- Migration files issue (Cannot redeclare class...).
## v1.6.0 (2023-03-03)
### Added
- Emoji's support.
- Css variables.
- Notification sounds.
- Auto-time updates.
### Changed
- Using UUIDs instead of random IDs on table primary column #243.
- UI/UX changes and enhancements.
- Code refactored (part of it).
- Messenger primary color fallback.
### Fixed
- Fetching messages multiple times at once on send/fetch requests.
- Migrations duplicate class name.
- Prevent chat for invalid user ids #246
- Fix responsiveness when going to chat with specific ID #247.
- App URL should be changed when click the `back to contacts` button on small screens.
- Internet connection UI.
- Prevent Users from updating each others statuses #254
- Contact list realtime updates issues.
- Delete messages issues.
- Fix contact list error `Malformed UTF-8 characters, possibly incorrectly encoded`
- Search multiple request on typing, debouncing used.
## v1.5.6 (2023-01-26)
### Fixed
- Keyboard overlaping on input issue on mobile #202.
- Security issue and code enhancements #240.
## v1.5.5 (2023-01-21)
### Fixed
- message delete event channel #238.
## v1.5.4 (2022-12-05)
### Fixed
- Channels auth secutiy issue #29
## v1.5.3 (2022-12-04)
### Fixed
- Channels Secutiy issue #29
## v1.5.2 (2022-07-08)
### Fixed
- MessageCard & fetchMessage methods@`ChatifyMessenger.php` fallback.
## v1.5.1 (2022-06-09)
### Fixed
- 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))
## v1.5.0 (2022-06-08)
### Added
- Page/Document visibility Support which improves (seen) feature #183
### Fixed
- fix: case insensitive file upload extension check #182
## v1.4.0 (2022-05-02)
### Added
- [Gravatar](https:://gravatar.com) support (optional, can be changed at config/chatify.php).
- Delete Message by ID.
- Laravel's Storage disk now supported and can be changed from the config.
### Changed
- File upload (user avatar & attachments) `allowed files` and `max size` now can be changed from one place which is (config/chatify.php).
### Fixed
- Bugs and UI/UX design fixes/improvements.
## v1.3.4 (2022-02-04)
### Fixed
- Fixed Installing errors on the migrations step. #163
## v1.3.3 (2022-01-10)
### Fixed
- Fixed file upload size limit error message rephrase #160.
### Changed
- Files max upload size changed & added to the config to be customizable.
- Changed `Messenger colors` logic to be more flexible and customizable.
- Migration files renamed, file date automatically will be changed to the publish/install date.
## v1.3.2 (2022-01-07)
### Fixed
- Fixed CSS issue in FF with the contact list #157.
- Correct misspelt of `updateContactItem` method (typo error) #159.
## v1.3.1 (2021-12-23)
### Fixed
- Fixed migration's rollback, (ch\_) prefix added.
## v1.3.0 (2021-11-30)
### Fixed
- UI/Ux fixes & improvements.
- Backend fixes & improvements.
### Added
- Messages, Contacts, and Search pagination.
- API routes.
## v1.2.5 (2021-08-18)
### Fixed
- Fixed a security issue on uploaded file-name, which is vulnerable with XSS.
## v1.2.4 (2021-07-15)
### Fixed
- README updates.
- Install Command fixes & improvements.
- Contact list visible onLoad.
- Settings modal responsive design.
### Added
- UPGRADE.md added.
- Publish command added.
- Package.json additions & modifications.
## v1.2.3 - (2021-06-19)
### Fixed
- XSS issue on inputs.
- UI/UX fixes & improvements.
- Send message fixes (UI & backend).
- Update Profile Settings (upload file & error handling ….).
- Shared photos not working issue.
- Typo error fixes (Your `contatc` list is empty).
- Rolling back migrations added.
- Get Last message `orderBy` query duplication.
## v1.2.2 - (2021-06-01)
### Fixed
- Migrate to database command removed.
- Publishable asset `assets` avatar config issue.
- Pusher encryption key option removed.
- Settings button on click not working issue.
## v1.2.1 - (2021-05-30)
### Fixed
- Publishable asset `assets`.
## v1.2.0 - (2021-05-30)
### FIxed
- Security issues.
- UI/UX issues.
- Route [home] not defiend.
- `$msg->attachment` issue #9.
- Delete conversation issue #89.
### Added
- Console commands.
- `Models` added to assets to be published.
- Laravel 8+ support.
### Changed
- Project structure.
- composer updated `pusher/pusher-php-server` to v^7.0.
- Models & Migrations' tables names changed (added `ch` prefix to avoid duplication) solves issue #68.
- Models changed to (`ChMessage`, `ChFavorite`)
- Migrations' tables names (`ch_messages`, `ch_favorites`)
- Configuration file `config/chatify.php`.
## v1.0.1 - (2020-09-30)
### FIxed
- Security issues.
### Added
- Routes' controllers namespace included in the configuration.
## v1.0.0 - (2019-12-30)
- First release
+59
View File
@@ -0,0 +1,59 @@
<p style="text-align:center;width:100%;"><img src="/art/preview.png" alt="Chatify Laravel Package"></p>
<p align="center">
<a href="https://github.com/laravel/telescope/actions"><img src="https://poser.pugx.org/munafio/chatify/v/stable?style=flat-square" alt="Build Status"></a>
<a href="https://packagist.org/packages/munafio/chatify"><img src="https://poser.pugx.org/munafio/chatify/downloads?style=flat-square" alt="Total Downloads"></a>
<a href="https://packagist.org/packages/munafio/chatify"><img src="https://poser.pugx.org/munafio/chatify/license?style=flat-square" alt="License"></a>
</p>
## Chatify Laravel Package
Laravel's #1 one-to-one chatting system package, helps you add a complete real-time chatting system to your new/existing Laravel application with only one command.
## Need a Help? 📣
I have created a server for **Chatify** on `Discord` to let you **up-to-date** and help you as much as I can .. so now you can chat with me, get a help, showcases, and most importantly to get announcements and updates about **Chatify**.
So, [join now](https://discord.gg/RaxyKVykYJ) and keep updated.
## Features
- One-to-one users chat system.
- Real-time contact list updates.
- Favorite users system (Like stories style).
- Saved Messages to save your messages online like Telegram messenger app.
- Search functionality.
- Contact item's last message indicator (e.g. You: ....).
- Real-time user's active status.
- Real-time typing indicator.
- Real-time message seen indicator.
- Real-time internet connection status.
- Upload attachments (Photo/File).
- Send Emoji's.
- User details panel (Shared photos, delete conversation..).
- Responsive design with all devices.
- User settings and chat customization : user's profile photo, dark mode and chat color.
with simple and wonderful UI design.
...and much more you have to discover it yourself.
## Demo
- Demo app - [Click Here](https://github.com/munafio/chatify-demo).
<!-- - Demo video on YouTube - [Click Here](https://youtu.be/gjo74FUJJPI) -->
## Official Documentation
The official documentation can be found [here](https://chatify.munafio.com)
## Change log
[CHANGELOG.md](https://github.com/munafio/chatify/blob/master/CHANGELOG.md)
## Author
- [Munaf A. Mahdi](https://www.munafio.com)
## License
Chatify is licensed under the [MIT license](https://choosealicense.com/licenses/mit/)
+28
View File
@@ -0,0 +1,28 @@
# Upgrade Guide
With every upgrade, make sure to re-publish Chatify's assets:
## For v1.2.3 and earlier versions
```
php artisan verndor:publish --tag=chatify-views --force
php artisan verndor:publish --tag=chatify-assets --force
```
If needed, you can re-publish the other assets the same way above by just replacing the name of the asset (chatify-NAME).
## For v1.2.4+ and higher vertions
To re-publish only `views` & `assets`:
```
php artisan chatify:publish
```
To re-publish all the assets (views, assets, config..):
```
php artisan chatify:publish --force
```
> This will overwrite all the assets, so all your changes will be overwritten.
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

+44
View File
@@ -0,0 +1,44 @@
{
"name": "munafio/chatify",
"description": "A package for Laravel PHP Framework to add a complete real-time chat system.",
"type": "library",
"keywords": [
"laravel",
"messenger",
"conversations",
"chat",
"php",
"pusher",
"realtime",
"real-time",
"chatify"
],
"license": "MIT",
"authors": [
{
"name": "Munaf A. Mahdi",
"email": "[email protected]"
}
],
"homepage": "https://github.com/munafio/chatify",
"minimum-stability": "dev",
"prefer-stable": true,
"require": {
"pusher/pusher-php-server": "^6.0|^7.0|^7.1"
},
"autoload": {
"psr-4": {
"Chatify\\": "src/"
}
},
"extra": {
"laravel": {
"providers": [
"Chatify\\ChatifyServiceProvider"
],
"aliases": {
"Chatify": "Chatify\\Facades\\ChatifyMessenger"
}
}
}
}
+445
View File
@@ -0,0 +1,445 @@
<?php
namespace Chatify;
use App\Models\ChMessage as Message;
use App\Models\ChFavorite as Favorite;
use Illuminate\Support\Facades\Storage;
use Pusher\Pusher;
use Illuminate\Support\Facades\Auth;
use Exception;
class ChatifyMessenger
{
public $pusher;
/**
* Get max file's upload size in MB.
*
* @return int
*/
public function getMaxUploadSize()
{
return config('chatify.attachments.max_upload_size') * 1048576;
}
public function __construct()
{
$this->pusher = new Pusher(
config('chatify.pusher.key'),
config('chatify.pusher.secret'),
config('chatify.pusher.app_id'),
config('chatify.pusher.options'),
);
}
/**
* This method returns the allowed image extensions
* to attach with the message.
*
* @return array
*/
public function getAllowedImages()
{
return config('chatify.attachments.allowed_images');
}
/**
* This method returns the allowed file extensions
* to attach with the message.
*
* @return array
*/
public function getAllowedFiles()
{
return config('chatify.attachments.allowed_files');
}
/**
* Returns an array contains messenger's colors
*
* @return array
*/
public function getMessengerColors()
{
return config('chatify.colors');
}
/**
* Returns a fallback primary color.
*
* @return array
*/
public function getFallbackColor()
{
$colors = $this->getMessengerColors();
return count($colors) > 0 ? $colors[0] : '#000000';
}
/**
* Trigger an event using Pusher
*
* @param string $channel
* @param string $event
* @param array $data
* @return void
*/
public function push($channel, $event, $data)
{
return $this->pusher->trigger($channel, $event, $data);
}
/**
* Authentication for pusher
*
* @param User $requestUser
* @param User $authUser
* @param string $channelName
* @param string $socket_id
* @param array $data
* @return void
*/
public function pusherAuth($requestUser, $authUser, $channelName, $socket_id)
{
// Auth data
$authData = json_encode([
'user_id' => $authUser->id,
'user_info' => [
'name' => $authUser->name
]
]);
// check if user authenticated
if (Auth::check()) {
if($requestUser->id == $authUser->id){
return $this->pusher->socket_auth(
$channelName,
$socket_id,
$authData
);
}
// if not authorized
return response()->json(['message'=>'Unauthorized'], 401);
}
// if not authenticated
return response()->json(['message'=>'Not authenticated'], 403);
}
/**
* Fetch & parse message and return the message card
* view as a response.
*
* @param Message $prefetchedMessage
* @param int $id
* @return array
*/
public function parseMessage($prefetchedMessage = null, $id = null)
{
$msg = null;
$attachment = null;
$attachment_type = null;
$attachment_title = null;
if (!!$prefetchedMessage) {
$msg = $prefetchedMessage;
} else {
$msg = Message::where('id', $id)->first();
if(!$msg){
return [];
}
}
if (isset($msg->attachment)) {
$attachmentOBJ = json_decode($msg->attachment);
$attachment = $attachmentOBJ->new_name;
$attachment_title = htmlentities(trim($attachmentOBJ->old_name), ENT_QUOTES, 'UTF-8');
$ext = pathinfo($attachment, PATHINFO_EXTENSION);
$attachment_type = in_array($ext, $this->getAllowedImages()) ? 'image' : 'file';
}
return [
'id' => $msg->id,
'from_id' => $msg->from_id,
'to_id' => $msg->to_id,
'message' => $msg->body,
'attachment' => (object) [
'file' => $attachment,
'title' => $attachment_title,
'type' => $attachment_type
],
'timeAgo' => $msg->created_at->diffForHumans(),
'created_at' => $msg->created_at->toIso8601String(),
'isSender' => ($msg->from_id == Auth::user()->id),
'seen' => $msg->seen,
];
}
/**
* Return a message card with the given data.
*
* @param Message $data
* @param boolean $isSender
* @return string
*/
public function messageCard($data, $renderDefaultCard = false)
{
if (!$data) {
return '';
}
if($renderDefaultCard) {
$data['isSender'] = false;
}
return view('Chatify::layouts.messageCard', $data)->render();
}
/**
* Default fetch messages query between a Sender and Receiver.
*
* @param int $user_id
* @return Message|\Illuminate\Database\Eloquent\Builder
*/
public function fetchMessagesQuery($user_id)
{
return Message::where('from_id', Auth::user()->id)->where('to_id', $user_id)
->orWhere('from_id', $user_id)->where('to_id', Auth::user()->id);
}
/**
* create a new message to database
*
* @param array $data
* @return Message
*/
public function newMessage($data)
{
$message = new Message();
$message->from_id = $data['from_id'];
$message->to_id = $data['to_id'];
$message->body = $data['body'];
$message->attachment = $data['attachment'];
$message->save();
return $message;
}
/**
* Make messages between the sender [Auth user] and
* the receiver [User id] as seen.
*
* @param int $user_id
* @return bool
*/
public function makeSeen($user_id)
{
Message::Where('from_id', $user_id)
->where('to_id', Auth::user()->id)
->where('seen', 0)
->update(['seen' => 1]);
return 1;
}
/**
* Get last message for a specific user
*
* @param int $user_id
* @return Message|Collection|\Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Model|object|null
*/
public function getLastMessageQuery($user_id)
{
return $this->fetchMessagesQuery($user_id)->latest()->first();
}
/**
* Count Unseen messages
*
* @param int $user_id
* @return Collection
*/
public function countUnseenMessages($user_id)
{
return Message::where('from_id', $user_id)->where('to_id', Auth::user()->id)->where('seen', 0)->count();
}
/**
* Get user list's item data [Contact Itme]
* (e.g. User data, Last message, Unseen Counter...)
*
* @param int $messenger_id
* @param Collection $user
* @return string
*/
public function getContactItem($user)
{
try {
// get last message
$lastMessage = $this->getLastMessageQuery($user->id);
// Get Unseen messages counter
$unseenCounter = $this->countUnseenMessages($user->id);
if ($lastMessage) {
$lastMessage->created_at = $lastMessage->created_at->toIso8601String();
$lastMessage->timeAgo = $lastMessage->created_at->diffForHumans();
}
return view('Chatify::layouts.listItem', [
'get' => 'users',
'user' => $this->getUserWithAvatar($user),
'lastMessage' => $lastMessage,
'unseenCounter' => $unseenCounter,
])->render();
} catch (\Throwable $th) {
throw new Exception($th->getMessage());
}
}
/**
* Get user with avatar (formatted).
*
* @param Collection $user
* @return Collection
*/
public function getUserWithAvatar($user)
{
if ($user->avatar == 'avatar.png' && config('chatify.gravatar.enabled')) {
$imageSize = config('chatify.gravatar.image_size');
$imageset = config('chatify.gravatar.imageset');
$user->avatar = 'https://www.gravatar.com/avatar/' . md5(strtolower(trim($user->email))) . '?s=' . $imageSize . '&d=' . $imageset;
} else {
$user->avatar = self::getUserAvatarUrl($user->avatar);
}
return $user;
}
/**
* Check if a user in the favorite list
*
* @param int $user_id
* @return boolean
*/
public function inFavorite($user_id)
{
return Favorite::where('user_id', Auth::user()->id)
->where('favorite_id', $user_id)->count() > 0
? true : false;
}
/**
* Make user in favorite list
*
* @param int $user_id
* @param int $star
* @return boolean
*/
public function makeInFavorite($user_id, $action)
{
if ($action > 0) {
// Star
$star = new Favorite();
$star->user_id = Auth::user()->id;
$star->favorite_id = $user_id;
$star->save();
return $star ? true : false;
} else {
// UnStar
$star = Favorite::where('user_id', Auth::user()->id)->where('favorite_id', $user_id)->delete();
return $star ? true : false;
}
}
/**
* Get shared photos of the conversation
*
* @param int $user_id
* @return array
*/
public function getSharedPhotos($user_id)
{
$images = array(); // Default
// Get messages
$msgs = $this->fetchMessagesQuery($user_id)->orderBy('created_at', 'DESC');
if ($msgs->count() > 0) {
foreach ($msgs->get() as $msg) {
// If message has attachment
if ($msg->attachment) {
$attachment = json_decode($msg->attachment);
// determine the type of the attachment
in_array(pathinfo($attachment->new_name, PATHINFO_EXTENSION), $this->getAllowedImages())
? array_push($images, $attachment->new_name) : '';
}
}
}
return $images;
}
/**
* Delete Conversation
*
* @param int $user_id
* @return boolean
*/
public function deleteConversation($user_id)
{
try {
foreach ($this->fetchMessagesQuery($user_id)->get() as $msg) {
// delete file attached if exist
if (isset($msg->attachment)) {
$path = config('chatify.attachments.folder').'/'.json_decode($msg->attachment)->new_name;
if (self::storage()->exists($path)) {
self::storage()->delete($path);
}
}
// delete from database
$msg->delete();
}
return 1;
} catch (Exception $e) {
return 0;
}
}
/**
* Delete message by ID
*
* @param int $id
* @return boolean
*/
public function deleteMessage($id)
{
try {
$msg = Message::where('from_id', auth()->id())->where('id', $id)->firstOrFail();
if (isset($msg->attachment)) {
$path = config('chatify.attachments.folder') . '/' . json_decode($msg->attachment)->new_name;
if (self::storage()->exists($path)) {
self::storage()->delete($path);
}
}
$msg->delete();
return 1;
} catch (Exception $e) {
throw new Exception($e->getMessage());
}
}
/**
* Return a storage instance with disk name specified in the config.
*
*/
public function storage()
{
return Storage::disk(config('chatify.storage_disk_name'));
}
/**
* Get user avatar url.
*
* @param string $user_avatar_name
* @return string
*/
public function getUserAvatarUrl($user_avatar_name)
{
return self::storage()->url(config('chatify.user_avatar.folder') . '/' . $user_avatar_name);
}
/**
* Get attachment's url.
*
* @param string $attachment_name
* @return string
*/
public function getAttachmentUrl($attachment_name)
{
return self::storage()->url(config('chatify.attachments.folder') . '/' . $attachment_name);
}
}
+153
View File
@@ -0,0 +1,153 @@
<?php
namespace Chatify;
use Chatify\Console\InstallCommand;
use Chatify\Console\PublishCommand;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\ServiceProvider;
class ChatifyServiceProvider extends ServiceProvider
{
/**
* Register services.
*
* @return void
*/
public function register()
{
app()->bind('ChatifyMessenger', function () {
return new \Chatify\ChatifyMessenger;
});
}
/**
* Bootstrap services.
*
* @return void
*/
public function boot()
{
// Load Views and Routes
$this->loadViewsFrom(__DIR__ . '/views', 'Chatify');
$this->loadRoutes();
if ($this->app->runningInConsole()) {
$this->commands([
InstallCommand::class,
PublishCommand::class,
]);
$this->setPublishes();
}
}
/**
* Publishing the files that the user may override.
*
* @return void
*/
protected function setPublishes()
{
// Load user's avatar folder from package's config
$userAvatarFolder = json_decode(json_encode(include(__DIR__.'/config/chatify.php')))->user_avatar->folder;
// Config
$this->publishes([
__DIR__ . '/config/chatify.php' => config_path('chatify.php')
], 'chatify-config');
// Migrations
$this->publishes([
__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'),
__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'),
__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'),
__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'),
__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'),
__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'),
], 'chatify-migrations');
// Models
$isV8 = explode('.', app()->version())[0] >= 8;
$this->publishes([
__DIR__ . '/Models' => app_path($isV8 ? 'Models' : '')
], 'chatify-models');
// Controllers
$this->publishes([
__DIR__ . '/Http/Controllers' => app_path('Http/Controllers/vendor/Chatify')
], 'chatify-controllers');
// Views
$this->publishes([
__DIR__ . '/views' => resource_path('views/vendor/Chatify')
], 'chatify-views');
// Assets
$this->publishes([
// CSS
__DIR__ . '/assets/css' => public_path('css/chatify'),
// JavaScript
__DIR__ . '/assets/js' => public_path('js/chatify'),
// Images
__DIR__ . '/assets/imgs' => storage_path('app/public/' . $userAvatarFolder),
// CSS
__DIR__ . '/assets/sounds' => public_path('sounds/chatify'),
], 'chatify-assets');
// Routes (API and Web)
$this->publishes([
__DIR__ . '/routes' => base_path('routes/chatify')
], 'chatify-routes');
}
/**
* Group the routes and set up configurations to load them.
*
* @return void
*/
protected function loadRoutes()
{
if (config('chatify.routes.custom')) {
Route::group($this->routesConfigurations(), function () {
$this->loadRoutesFrom(base_path('routes/chatify/web.php'));
});
Route::group($this->apiRoutesConfigurations(), function () {
$this->loadRoutesFrom(base_path('routes/chatify/api.php'));
});
} else {
Route::group($this->routesConfigurations(), function () {
$this->loadRoutesFrom(__DIR__ . '/routes/web.php');
});
Route::group($this->apiRoutesConfigurations(), function () {
$this->loadRoutesFrom(__DIR__ . '/routes/api.php');
});
}
}
/**
* Routes configurations.
*
* @return array
*/
private function routesConfigurations()
{
return [
'prefix' => config('chatify.routes.prefix'),
'namespace' => config('chatify.routes.namespace'),
'middleware' => config('chatify.routes.middleware'),
];
}
/**
* API routes configurations.
*
* @return array
*/
private function apiRoutesConfigurations()
{
return [
'prefix' => config('chatify.api_routes.prefix'),
'namespace' => config('chatify.api_routes.namespace'),
'middleware' => config('chatify.api_routes.middleware'),
];
}
}
+151
View File
@@ -0,0 +1,151 @@
<?php
namespace Chatify\Console;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\File;
class InstallCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'chatify:install';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Install Chatify package';
/**
* Check Laravel version.
*
* @var bool
*/
private $isV8;
/**
* Execute the console command.
*
* @return void
*/
public function handle()
{
$this->isV8 = explode('.',app()->version())[0] >= 8;
$this->info('Installing Chatify...');
$this->line('----------');
$this->line('Configurations...');
$this->modifyModelsPath('/../Http/Controllers/MessagesController.php','User');
$this->modifyModelsPath('/../Http/Controllers/MessagesController.php','ChFavorite');
$this->modifyModelsPath('/../Http/Controllers/MessagesController.php','ChMessage');
$this->modifyModelsPath('/../Http/Controllers/Api/MessagesController.php','User');
$this->modifyModelsPath('/../Http/Controllers/Api/MessagesController.php','ChFavorite');
$this->modifyModelsPath('/../Http/Controllers/Api/MessagesController.php','ChMessage');
$this->modifyModelsPath('/../ChatifyMessenger.php','ChFavorite');
$this->modifyModelsPath('/../ChatifyMessenger.php','ChMessage');
$this->modifyModelsPath('/../Models/ChFavorite.php');
$this->modifyModelsPath('/../Models/ChMessage.php');
$this->info('[✓] done');
$assetsToBePublished = [
'config' => config_path('chatify.php'),
'views' => resource_path('views/vendor/Chatify'),
'assets' => public_path('css/chatify'),
'models' => app_path(($this->isV8 ? 'Models/' : '').'ChMessage.php'),
'migrations' => database_path('migrations/2019_09_22_192348_create_messages_table.php'),
'routes' => base_path('routes/chatify'),
];
foreach ($assetsToBePublished as $target => $path) {
$this->line('----------');
$this->process($target, $path);
}
$this->line('----------');
$this->line('Creating storage symlink...');
Artisan::call('storage:link');
$this->info('[✓] Storage linked.');
$this->line('----------');
$this->info('[✓] Chatify installed successfully');
}
/**
* Modify models imports/namespace path according to Laravel version.
*
* @param string $targetFilePath
* @param string $model
* @return void
*/
private function modifyModelsPath($targetFilePath, $model = null){
$path = realpath(__DIR__.$targetFilePath);
$contents = File::get($path);
$model = !empty($model) ? '\\'.$model : ';';
$contents = str_replace(
(!$this->isV8 ? 'App\Models' : 'App').$model,
($this->isV8 ? 'App\Models' : 'App').$model,
$contents
);
File::put($path, $contents);
}
/**
* Check, publish, or overwrite the assets.
*
* @param string $target
* @param string $path
* @return void
*/
private function process($target, $path)
{
$this->line('Publishing '.$target.'...');
if (!File::exists($path)) {
$this->publish($target);
$this->info('[✓] '.$target.' published.');
return;
}
if ($this->shouldOverwrite($target)) {
$this->line('Overwriting '.$target.'...');
$this->publish($target,true);
$this->info('[✓] '.$target.' published.');
return;
}
$this->line('[-] Ignored, The existing '.$target.' was not overwritten');
}
/**
* Ask to overwrite.
*
* @param string $target
* @return void
*/
private function shouldOverwrite($target)
{
return $this->confirm(
$target.' already exists. Do you want to overwrite it?',
false
);
}
/**
* Call the publish command.
*
* @param string $tag
* @param bool $forcePublish
* @return void
*/
private function publish($tag, $forcePublish = false)
{
$this->call('vendor:publish', [
'--tag' => 'chatify-'.$tag,
'--force' => $forcePublish,
]);
}
}
+57
View File
@@ -0,0 +1,57 @@
<?php
namespace Chatify\Console;
use Illuminate\Console\Command;
class PublishCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'chatify:publish {--force : Overwrite any existing files}';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Publish all of the chatify assets';
/**
* Execute the console command.
*
* @return void
*/
public function handle()
{
if($this->option('force')){
$this->call('vendor:publish', [
'--tag' => 'chatify-config',
'--force' => true,
]);
$this->call('vendor:publish', [
'--tag' => 'chatify-migrations',
'--force' => true,
]);
$this->call('vendor:publish', [
'--tag' => 'chatify-models',
'--force' => true,
]);
}
$this->call('vendor:publish', [
'--tag' => 'chatify-views',
'--force' => true,
]);
$this->call('vendor:publish', [
'--tag' => 'chatify-assets',
'--force' => true,
]);
}
}
+14
View File
@@ -0,0 +1,14 @@
<?php
namespace Chatify\Facades;
use Illuminate\Support\Facades\Facade;
class ChatifyMessenger extends Facade
{
protected static function getFacadeAccessor()
{
return 'ChatifyMessenger';
}
}
@@ -0,0 +1,400 @@
<?php
namespace Chatify\Http\Controllers\Api;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\Response;
use App\Models\ChMessage as Message;
use App\Models\ChFavorite as Favorite;
use Chatify\Facades\ChatifyMessenger as Chatify;
use App\Models\User;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
class MessagesController extends Controller
{
protected $perPage = 30;
/**
* Authinticate the connection for pusher
*
* @param Request $request
* @return void
*/
public function pusherAuth(Request $request)
{
return Chatify::pusherAuth(
$request->user(),
Auth::user(),
$request['channel_name'],
$request['socket_id']
);
}
/**
* Fetch data by id for (user/group)
*
* @param Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function idFetchData(Request $request)
{
return auth()->user();
// Favorite
$favorite = Chatify::inFavorite($request['id']);
// User data
if ($request['type'] == 'user') {
$fetch = User::where('id', $request['id'])->first();
if($fetch){
$userAvatar = Chatify::getUserWithAvatar($fetch)->avatar;
}
}
// send the response
return Response::json([
'favorite' => $favorite,
'fetch' => $fetch ?? null,
'user_avatar' => $userAvatar ?? null,
]);
}
/**
* This method to make a links for the attachments
* to be downloadable.
*
* @param string $fileName
* @return \Illuminate\Http\JsonResponse
*/
public function download($fileName)
{
$path = config('chatify.attachments.folder') . '/' . $fileName;
if (Chatify::storage()->exists($path)) {
return response()->json([
'file_name' => $fileName,
'download_path' => Chatify::storage()->url($path)
], 200);
} else {
return response()->json([
'message'=>"Sorry, File does not exist in our server or may have been deleted!"
], 404);
}
}
/**
* Send a message to database
*
* @param Request $request
* @return JSON response
*/
public function send(Request $request)
{
// default variables
$error = (object)[
'status' => 0,
'message' => null
];
$attachment = null;
$attachment_title = null;
// if there is attachment [file]
if ($request->hasFile('file')) {
// allowed extensions
$allowed_images = Chatify::getAllowedImages();
$allowed_files = Chatify::getAllowedFiles();
$allowed = array_merge($allowed_images, $allowed_files);
$file = $request->file('file');
// check file size
if ($file->getSize() < Chatify::getMaxUploadSize()) {
if (in_array(strtolower($file->extension()), $allowed)) {
// get attachment name
$attachment_title = $file->getClientOriginalName();
// upload attachment and store the new name
$attachment = Str::uuid() . "." . $file->extension();
$file->storeAs(config('chatify.attachments.folder'), $attachment, config('chatify.storage_disk_name'));
} else {
$error->status = 1;
$error->message = "File extension not allowed!";
}
} else {
$error->status = 1;
$error->message = "File size you are trying to upload is too large!";
}
}
if (!$error->status) {
// send to database
$message = Chatify::newMessage([
'type' => $request['type'],
'from_id' => Auth::user()->id,
'to_id' => $request['id'],
'body' => htmlentities(trim($request['message']), ENT_QUOTES, 'UTF-8'),
'attachment' => ($attachment) ? json_encode((object)[
'new_name' => $attachment,
'old_name' => htmlentities(trim($attachment_title), ENT_QUOTES, 'UTF-8'),
]) : null,
]);
// fetch message to send it with the response
$messageData = Chatify::parseMessage($message);
// send to user using pusher
if (Auth::user()->id != $request['id']) {
Chatify::push("private-chatify.".$request['id'], 'messaging', [
'from_id' => Auth::user()->id,
'to_id' => $request['id'],
'message' => $messageData
]);
}
}
// send the response
return Response::json([
'status' => '200',
'error' => $error,
'message' => $messageData ?? [],
'tempID' => $request['temporaryMsgId'],
]);
}
/**
* fetch [user/group] messages from database
*
* @param Request $request
* @return JSON response
*/
public function fetch(Request $request)
{
$query = Chatify::fetchMessagesQuery($request['id'])->latest();
$messages = $query->paginate($request->per_page ?? $this->perPage);
$totalMessages = $messages->total();
$lastPage = $messages->lastPage();
$response = [
'total' => $totalMessages,
'last_page' => $lastPage,
'last_message_id' => collect($messages->items())->last()->id ?? null,
'messages' => $messages->items(),
];
return Response::json($response);
}
/**
* Make messages as seen
*
* @param Request $request
* @return void
*/
public function seen(Request $request)
{
// make as seen
$seen = Chatify::makeSeen($request['id']);
// send the response
return Response::json([
'status' => $seen,
], 200);
}
/**
* Get contacts list
*
* @param Request $request
* @return \Illuminate\Http\JsonResponse response
*/
public function getContacts(Request $request)
{
// get all users that received/sent message from/to [Auth user]
$users = Message::join('users', function ($join) {
$join->on('ch_messages.from_id', '=', 'users.id')
->orOn('ch_messages.to_id', '=', 'users.id');
})
->where(function ($q) {
$q->where('ch_messages.from_id', Auth::user()->id)
->orWhere('ch_messages.to_id', Auth::user()->id);
})
->where('users.id','!=',Auth::user()->id)
->select('users.*',DB::raw('MAX(ch_messages.created_at) max_created_at'))
->orderBy('max_created_at', 'desc')
->groupBy('users.id')
->paginate($request->per_page ?? $this->perPage);
return response()->json([
'contacts' => $users->items(),
'total' => $users->total() ?? 0,
'last_page' => $users->lastPage() ?? 1,
], 200);
}
/**
* Put a user in the favorites list
*
* @param Request $request
* @return void
*/
public function favorite(Request $request)
{
$userId = $request['user_id'];
// check action [star/unstar]
$favoriteStatus = Chatify::inFavorite($userId) ? 0 : 1;
Chatify::makeInFavorite($userId, $favoriteStatus);
// send the response
return Response::json([
'status' => @$favoriteStatus,
], 200);
}
/**
* Get favorites list
*
* @param Request $request
* @return void
*/
public function getFavorites(Request $request)
{
$favorites = Favorite::where('user_id', Auth::user()->id)->get();
foreach ($favorites as $favorite) {
$favorite->user = User::where('id', $favorite->favorite_id)->first();
}
return Response::json([
'total' => count($favorites),
'favorites' => $favorites ?? [],
], 200);
}
/**
* Search in messenger
*
* @param Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function search(Request $request)
{
$input = trim(filter_var($request['input']));
$records = User::where('id','!=',Auth::user()->id)
->where('name', 'LIKE', "%{$input}%")
->paginate($request->per_page ?? $this->perPage);
foreach ($records->items() as $index => $record) {
$records[$index] += Chatify::getUserWithAvatar($record);
}
return Response::json([
'records' => $records->items(),
'total' => $records->total(),
'last_page' => $records->lastPage()
], 200);
}
/**
* Get shared photos
*
* @param Request $request
* @return \Illuminate\Http\JsonResponse
*/
public function sharedPhotos(Request $request)
{
$images = Chatify::getSharedPhotos($request['user_id']);
foreach ($images as $image) {
$image = asset(config('chatify.attachments.folder') . $image);
}
// send the response
return Response::json([
'shared' => $images ?? [],
], 200);
}
/**
* Delete conversation
*
* @param Request $request
* @return void
*/
public function deleteConversation(Request $request)
{
// delete
$delete = Chatify::deleteConversation($request['id']);
// send the response
return Response::json([
'deleted' => $delete ? 1 : 0,
], 200);
}
public function updateSettings(Request $request)
{
$msg = null;
$error = $success = 0;
// dark mode
if ($request['dark_mode']) {
$request['dark_mode'] == "dark"
? User::where('id', Auth::user()->id)->update(['dark_mode' => 1]) // Make Dark
: User::where('id', Auth::user()->id)->update(['dark_mode' => 0]); // Make Light
}
// If messenger color selected
if ($request['messengerColor']) {
$messenger_color = trim(filter_var($request['messengerColor']));
User::where('id', Auth::user()->id)
->update(['messenger_color' => $messenger_color]);
}
// if there is a [file]
if ($request->hasFile('avatar')) {
// allowed extensions
$allowed_images = Chatify::getAllowedImages();
$file = $request->file('avatar');
// check file size
if ($file->getSize() < Chatify::getMaxUploadSize()) {
if (in_array(strtolower($file->extension()), $allowed_images)) {
// delete the older one
if (Auth::user()->avatar != config('chatify.user_avatar.default')) {
$path = Chatify::getUserAvatarUrl(Auth::user()->avatar);
if (Chatify::storage()->exists($path)) {
Chatify::storage()->delete($path);
}
}
// upload
$avatar = Str::uuid() . "." . $file->extension();
$update = User::where('id', Auth::user()->id)->update(['avatar' => $avatar]);
$file->storeAs(config('chatify.user_avatar.folder'), $avatar, config('chatify.storage_disk_name'));
$success = $update ? 1 : 0;
} else {
$msg = "File extension not allowed!";
$error = 1;
}
} else {
$msg = "File size you are trying to upload is too large!";
$error = 1;
}
}
// send the response
return Response::json([
'status' => $success ? 1 : 0,
'error' => $error ? 1 : 0,
'message' => $error ? $msg : 0,
], 200);
}
/**
* Set user's active status
*
* @param Request $request
* @return void
*/
public function setActiveStatus(Request $request)
{
$activeStatus = $request['status'] > 0 ? 1 : 0;
$status = User::where('id', Auth::user()->id)->update(['active_status' => $activeStatus]);
return Response::json([
'status' => $status,
], 200);
}
}
@@ -0,0 +1,483 @@
<?php
namespace Chatify\Http\Controllers;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
use Illuminate\Support\Facades\Response;
use App\Models\User;
use App\Models\ChMessage as Message;
use App\Models\ChFavorite as Favorite;
use Chatify\Facades\ChatifyMessenger as Chatify;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Request as FacadesRequest;
use Illuminate\Support\Str;
class MessagesController extends Controller
{
protected $perPage = 30;
/**
* Authenticate the connection for pusher
*
* @param Request $request
* @return JsonResponse
*/
public function pusherAuth(Request $request)
{
return Chatify::pusherAuth(
$request->user(),
Auth::user(),
$request['channel_name'],
$request['socket_id']
);
}
/**
* Returning the view of the app with the required data.
*
* @param int $id
* @return \Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View
*/
public function index( $id = null)
{
$messenger_color = Auth::user()->messenger_color;
return view('Chatify::pages.app', [
'id' => $id ?? 0,
'messengerColor' => $messenger_color ? $messenger_color : Chatify::getFallbackColor(),
'dark_mode' => Auth::user()->dark_mode < 1 ? 'light' : 'dark',
]);
}
/**
* Fetch data (user, favorite.. etc).
*
* @param Request $request
* @return JsonResponse
*/
public function idFetchData(Request $request)
{
$favorite = Chatify::inFavorite($request['id']);
$fetch = User::where('id', $request['id'])->first();
if($fetch){
$userAvatar = Chatify::getUserWithAvatar($fetch)->avatar;
}
return Response::json([
'favorite' => $favorite,
'fetch' => $fetch ?? null,
'user_avatar' => $userAvatar ?? null,
]);
}
/**
* This method to make a links for the attachments
* to be downloadable.
*
* @param string $fileName
* @return \Symfony\Component\HttpFoundation\StreamedResponse|void
*/
public function download($fileName)
{
$filePath = config('chatify.attachments.folder') . '/' . $fileName;
if (Chatify::storage()->exists($filePath)) {
return Chatify::storage()->download($filePath);
}
return abort(404, "Sorry, File does not exist in our server or may have been deleted!");
}
/**
* Send a message to database
*
* @param Request $request
* @return JsonResponse
*/
public function send(Request $request)
{
// default variables
$error = (object)[
'status' => 0,
'message' => null
];
$attachment = null;
$attachment_title = null;
// if there is attachment [file]
if ($request->hasFile('file')) {
// allowed extensions
$allowed_images = Chatify::getAllowedImages();
$allowed_files = Chatify::getAllowedFiles();
$allowed = array_merge($allowed_images, $allowed_files);
$file = $request->file('file');
// check file size
if ($file->getSize() < Chatify::getMaxUploadSize()) {
if (in_array(strtolower($file->extension()), $allowed)) {
// get attachment name
$attachment_title = $file->getClientOriginalName();
// upload attachment and store the new name
$attachment = Str::uuid() . "." . $file->extension();
$file->storeAs(config('chatify.attachments.folder'), $attachment, config('chatify.storage_disk_name'));
} else {
$error->status = 1;
$error->message = "File extension not allowed!";
}
} else {
$error->status = 1;
$error->message = "File size you are trying to upload is too large!";
}
}
if (!$error->status) {
$message = Chatify::newMessage([
'from_id' => Auth::user()->id,
'to_id' => $request['id'],
'body' => htmlentities(trim($request['message']), ENT_QUOTES, 'UTF-8'),
'attachment' => ($attachment) ? json_encode((object)[
'new_name' => $attachment,
'old_name' => htmlentities(trim($attachment_title), ENT_QUOTES, 'UTF-8'),
]) : null,
]);
$messageData = Chatify::parseMessage($message);
if (Auth::user()->id != $request['id']) {
Chatify::push("private-chatify.".$request['id'], 'messaging', [
'from_id' => Auth::user()->id,
'to_id' => $request['id'],
'message' => Chatify::messageCard($messageData, true)
]);
}
}
// send the response
return Response::json([
'status' => '200',
'error' => $error,
'message' => Chatify::messageCard(@$messageData),
'tempID' => $request['temporaryMsgId'],
]);
}
/**
* fetch [user/group] messages from database
*
* @param Request $request
* @return JsonResponse
*/
public function fetch(Request $request)
{
$query = Chatify::fetchMessagesQuery($request['id'])->latest();
$messages = $query->paginate($request->per_page ?? $this->perPage);
$totalMessages = $messages->total();
$lastPage = $messages->lastPage();
$response = [
'total' => $totalMessages,
'last_page' => $lastPage,
'last_message_id' => collect($messages->items())->last()->id ?? null,
'messages' => '',
];
// if there is no messages yet.
if ($totalMessages < 1) {
$response['messages'] ='<p class="message-hint center-el"><span>Say \'hi\' and start messaging</span></p>';
return Response::json($response);
}
if (count($messages->items()) < 1) {
$response['messages'] = '';
return Response::json($response);
}
$allMessages = null;
foreach ($messages->reverse() as $message) {
$allMessages .= Chatify::messageCard(
Chatify::parseMessage($message)
);
}
$response['messages'] = $allMessages;
return Response::json($response);
}
/**
* Make messages as seen
*
* @param Request $request
* @return JsonResponse|void
*/
public function seen(Request $request)
{
// make as seen
$seen = Chatify::makeSeen($request['id']);
// send the response
return Response::json([
'status' => $seen,
], 200);
}
/**
* Get contacts list
*
* @param Request $request
* @return JsonResponse
*/
public function getContacts(Request $request)
{
// get all users that received/sent message from/to [Auth user]
$users = Message::join('users', function ($join) {
$join->on('ch_messages.from_id', '=', 'users.id')
->orOn('ch_messages.to_id', '=', 'users.id');
})
->where(function ($q) {
$q->where('ch_messages.from_id', Auth::user()->id)
->orWhere('ch_messages.to_id', Auth::user()->id);
})
->where('users.id','!=',Auth::user()->id)
->select('users.*',DB::raw('MAX(ch_messages.created_at) max_created_at'))
->orderBy('max_created_at', 'desc')
->groupBy('users.id')
->paginate($request->per_page ?? $this->perPage);
$usersList = $users->items();
if (count($usersList) > 0) {
$contacts = '';
foreach ($usersList as $user) {
$contacts .= Chatify::getContactItem($user);
}
} else {
$contacts = '<p class="message-hint center-el"><span>Your contact list is empty</span></p>';
}
return Response::json([
'contacts' => $contacts,
'total' => $users->total() ?? 0,
'last_page' => $users->lastPage() ?? 1,
], 200);
}
/**
* Update user's list item data
*
* @param Request $request
* @return JsonResponse
*/
public function updateContactItem(Request $request)
{
// Get user data
$user = User::where('id', $request['user_id'])->first();
if(!$user){
return Response::json([
'message' => 'User not found!',
], 401);
}
$contactItem = Chatify::getContactItem($user);
// send the response
return Response::json([
'contactItem' => $contactItem,
], 200);
}
/**
* Put a user in the favorites list
*
* @param Request $request
* @return JsonResponse|void
*/
public function favorite(Request $request)
{
$userId = $request['user_id'];
// check action [star/unstar]
$favoriteStatus = Chatify::inFavorite($userId) ? 0 : 1;
Chatify::makeInFavorite($userId, $favoriteStatus);
// send the response
return Response::json([
'status' => @$favoriteStatus,
], 200);
}
/**
* Get favorites list
*
* @param Request $request
* @return JsonResponse|void
*/
public function getFavorites(Request $request)
{
$favoritesList = null;
$favorites = Favorite::where('user_id', Auth::user()->id);
foreach ($favorites->get() as $favorite) {
// get user data
$user = User::where('id', $favorite->favorite_id)->first();
$favoritesList .= view('Chatify::layouts.favorite', [
'user' => $user,
]);
}
// send the response
return Response::json([
'count' => $favorites->count(),
'favorites' => $favorites->count() > 0
? $favoritesList
: 0,
], 200);
}
/**
* Search in messenger
*
* @param Request $request
* @return JsonResponse|void
*/
public function search(Request $request)
{
$getRecords = null;
$input = trim(filter_var($request['input']));
$records = User::where('id','!=',Auth::user()->id)
->where('name', 'LIKE', "%{$input}%")
->paginate($request->per_page ?? $this->perPage);
foreach ($records->items() as $record) {
$getRecords .= view('Chatify::layouts.listItem', [
'get' => 'search_item',
'user' => Chatify::getUserWithAvatar($record),
])->render();
}
if($records->total() < 1){
$getRecords = '<p class="message-hint center-el"><span>Nothing to show.</span></p>';
}
// send the response
return Response::json([
'records' => $getRecords,
'total' => $records->total(),
'last_page' => $records->lastPage()
], 200);
}
/**
* Get shared photos
*
* @param Request $request
* @return JsonResponse|void
*/
public function sharedPhotos(Request $request)
{
$shared = Chatify::getSharedPhotos($request['user_id']);
$sharedPhotos = null;
// shared with its template
for ($i = 0; $i < count($shared); $i++) {
$sharedPhotos .= view('Chatify::layouts.listItem', [
'get' => 'sharedPhoto',
'image' => Chatify::getAttachmentUrl($shared[$i]),
])->render();
}
// send the response
return Response::json([
'shared' => count($shared) > 0 ? $sharedPhotos : '<p class="message-hint"><span>Nothing shared yet</span></p>',
], 200);
}
/**
* Delete conversation
*
* @param Request $request
* @return JsonResponse
*/
public function deleteConversation(Request $request)
{
// delete
$delete = Chatify::deleteConversation($request['id']);
// send the response
return Response::json([
'deleted' => $delete ? 1 : 0,
], 200);
}
/**
* Delete message
*
* @param Request $request
* @return JsonResponse
*/
public function deleteMessage(Request $request)
{
// delete
$delete = Chatify::deleteMessage($request['id']);
// send the response
return Response::json([
'deleted' => $delete ? 1 : 0,
], 200);
}
public function updateSettings(Request $request)
{
$msg = null;
$error = $success = 0;
// dark mode
if ($request['dark_mode']) {
$request['dark_mode'] == "dark"
? User::where('id', Auth::user()->id)->update(['dark_mode' => 1]) // Make Dark
: User::where('id', Auth::user()->id)->update(['dark_mode' => 0]); // Make Light
}
// If messenger color selected
if ($request['messengerColor']) {
$messenger_color = trim(filter_var($request['messengerColor']));
User::where('id', Auth::user()->id)
->update(['messenger_color' => $messenger_color]);
}
// if there is a [file]
if ($request->hasFile('avatar')) {
// allowed extensions
$allowed_images = Chatify::getAllowedImages();
$file = $request->file('avatar');
// check file size
if ($file->getSize() < Chatify::getMaxUploadSize()) {
if (in_array(strtolower($file->extension()), $allowed_images)) {
// delete the older one
if (Auth::user()->avatar != config('chatify.user_avatar.default')) {
$avatar = Auth::user()->avatar;
if (Chatify::storage()->exists($avatar)) {
Chatify::storage()->delete($avatar);
}
}
// upload
$avatar = Str::uuid() . "." . $file->extension();
$update = User::where('id', Auth::user()->id)->update(['avatar' => $avatar]);
$file->storeAs(config('chatify.user_avatar.folder'), $avatar, config('chatify.storage_disk_name'));
$success = $update ? 1 : 0;
} else {
$msg = "File extension not allowed!";
$error = 1;
}
} else {
$msg = "File size you are trying to upload is too large!";
$error = 1;
}
}
// send the response
return Response::json([
'status' => $success ? 1 : 0,
'error' => $error ? 1 : 0,
'message' => $error ? $msg : 0,
], 200);
}
/**
* Set user's active status
*
* @param Request $request
* @return JsonResponse
*/
public function setActiveStatus(Request $request)
{
$activeStatus = $request['status'] > 0 ? 1 : 0;
$status = User::where('id', Auth::user()->id)->update(['active_status' => $activeStatus]);
return Response::json([
'status' => $status,
], 200);
}
}
+11
View File
@@ -0,0 +1,11 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Chatify\Traits\UUID;
class ChFavorite extends Model
{
use UUID;
}
+11
View File
@@ -0,0 +1,11 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Chatify\Traits\UUID;
class ChMessage extends Model
{
use UUID;
}
+26
View File
@@ -0,0 +1,26 @@
<?php
namespace Chatify\Traits;
use Illuminate\Support\Str;
trait UUID
{
protected static function boot()
{
parent::boot();
static::creating(function ($model) {
$model->{$model->getKeyName()} = (string) Str::uuid();
});
}
public function getIncrementing ()
{
return false;
}
public function getKeyType ()
{
return 'string';
}
}
+158
View File
@@ -0,0 +1,158 @@
/*app scroll*/
.app-scroll::-webkit-scrollbar-thumb,
.app-scroll-thin::-webkit-scrollbar-thumb {
background: var(--dark-scrollbar-thumb-color);
}
.app-scroll-thin::-webkit-scrollbar {
background: var(--dark-secondary-bg-color);
}
.app-scroll::-webkit-scrollbar:hover,
.app-scroll-thin::-webkit-scrollbar:hover {
background: var(--dark-secondary-bg-color);
}
.messenger {
background: var(--dark-primary-bg-color);
}
.messenger-search[type="text"] {
background: var(--dark-secondary-bg-color);
color: #fff;
}
.messenger-search[type="text"]::placeholder {
color: #fff;
}
.messenger-listView {
background: var(--dark-primary-bg-color);
border: 1px solid var(--dark-border-color);
}
.messenger-listView-tabs {
border-bottom: 1px solid var(--dark-border-color);
}
.messenger-listView-tabs a:hover,
.messenger-listView-tabs a:focus {
background-color: var(--dark-secondary-bg-color);
}
.messenger-favorites div.avatar {
border: 2px solid var(--dark-primary-bg-color);
}
.messenger-list-item:hover {
background: var(--dark-secondary-bg-color);
}
.messenger-messagingView {
border-top: 1px solid var(--dark-secondary-bg-color);
border-bottom: 1px solid var(--dark-secondary-bg-color);
background: var(--dark-messagingView-bg-color);
}
.m-header-messaging {
background: var(--dark-primary-bg-color);
}
.messenger-infoView {
background: var(--dark-primary-bg-color);
border: 1px solid var(--dark-border-color);
}
.messenger-infoView > p {
color: #fff;
}
.divider {
border-top: 1px solid var(--dark-border-color);
}
.messenger-sendCard {
background: var(--dark-primary-bg-color);
border-top: 1px solid var(--dark-border-color);
}
.attachment-preview > p {
color: #fff;
}
.m-send {
color: #fff;
}
.m-send::placeholder {
color: #fff;
}
.message-card .message {
background: var(--dark-message-card-color);
color: #fff;
}
.m-li-divider {
border-bottom: 1px solid var(--dark-border-color);
}
.m-header a,
.m-header a:hover,
.m-header a:focus {
text-decoration: none;
color: #fff;
}
.messenger-list-item td p {
color: #fff;
}
.activeStatus {
border: 2px solid var(--dark-border-color);
}
.messenger-list-item:hover .activeStatus {
border-color: var(--dark-secondary-bg-color);
}
.messenger-favorites > div p {
color: #ffffff;
}
.avatar {
background-color: var(--dark-secondary-bg-color);
border-color: var(--dark-border-color);
}
.messenger-sendCard svg {
color: var(--dark-send-input-icons-color);
}
.messenger-title {
color: #dbdbdb;
}
.messenger-title > span {
background-color: var(--dark-primary-bg-color);
}
.messenger-title::before {
background-color: var(--dark-border-color);
}
.message-hint span {
background: var(--dark-message-hint-bg-color);
color: var(--dark-message-hint-color);
}
.messenger-infoView > nav > p {
color: #fff;
}
/*
***********************************************
* Placeholder loading
***********************************************
*/
.loadingPlaceholder-body div,
.loadingPlaceholder-header tr td div {
background: var(--dark-secondary-bg-color);
background-image: -webkit-linear-gradient(
left,
var(--dark-secondary-bg-color) 0%,
var(--dark-secondary-bg-color) 20%,
var(--dark-secondary-bg-color) 40%,
var(--dark-secondary-bg-color) 100%
);
}
/*
***********************************************
* App Modal
***********************************************
*/
.app-modal-card {
background: var(--dark-modal-bg-color);
}
.app-modal-header {
color: #fff;
}
.app-modal-body {
color: #fff;
}
.messages .message-time {
color: #fff;
}
.message-card .actions .delete-btn {
color: #fff;
}
+166
View File
@@ -0,0 +1,166 @@
/*app scroll*/
.app-scroll::-webkit-scrollbar-thumb,
.app-scroll-thin::-webkit-scrollbar-thumb {
background: var(--scrollbar-thumb-color);
}
.app-scroll-thin::-webkit-scrollbar {
background: var(--secondary-bg-color);
}
.app-scroll::-webkit-scrollbar:hover,
.app-scroll-thin::-webkit-scrollbar:hover {
background: var(--secondary-bg-color);
}
.messenger {
background: var(--primary-bg-color);
}
.messenger-search[type="text"] {
background: var(--secondary-bg-color);
color: #333;
}
.messenger-listView {
background: var(--primary-bg-color);
border: 1px solid var(--border-color);
}
.messenger-listView-tabs {
border-bottom: 1px solid var(--border-color);
}
.messenger-listView-tabs a:hover,
.messenger-listView-tabs a:focus {
background-color: var(--secondary-bg-color);
}
.messenger-favorites div.avatar {
border: 2px solid var(--primary-bg-color);
}
.messenger-list-item:hover {
background: var(--secondary-bg-color);
}
.messenger-messagingView {
border-top: 1px solid var(--secondary-bg-color);
border-bottom: 1px solid var(--secondary-bg-color);
background: var(--messagingView-bg-color);
}
.m-header-messaging {
background: var(--primary-bg-color);
}
.messenger-infoView {
background: var(--primary-bg-color);
border: 1px solid var(--border-color);
}
.messenger-infoView > p {
color: #000;
}
.divider {
border-top: 1px solid var(--border-color);
}
.messenger-sendCard {
background: var(--primary-bg-color);
border-top: 1px solid var(--border-color);
}
.attachment-preview > p {
color: #333;
}
.m-send {
color: #333;
}
.message-card .message {
background: var(--message-card-color);
color: #656b75;
box-shadow: 0px 6px 11px rgba(18, 67, 105, 0.03);
}
.m-li-divider {
border-bottom: 1px solid var(--border-color);
}
.m-header a,
.m-header a:hover,
.m-header a:focus {
text-decoration: none;
color: #202020;
}
.messenger-list-item td p {
color: #3c3c3c;
}
.messenger-list-item td span {
color: #929292;
}
.activeStatus {
border: 2px solid var(--primary-bg-color);
}
.messenger-list-item:hover .activeStatus {
border-color: var(--secondary-bg-color);
}
.messenger-favorites > div p {
color: #4a4a4a;
}
.avatar {
background-color: var(--secondary-bg-color);
border-color: var(--border-color);
}
.messenger-sendCard svg {
color: var(--send-input-icons-color);
}
.messenger-title {
color: #797979;
}
.messenger-title > span {
background-color: var(--primary-bg-color);
}
.messenger-title::before {
background-color: var(--border-color);
}
.message-hint span {
background: var(--message-hint-bg-color);
color: var(--message-hint-color);
}
/*
***********************************************
* Placeholder loading
***********************************************
*/
.loadingPlaceholder-body div,
.loadingPlaceholder-header tr td div {
background: var(--secondary-bg-color);
background-image: -webkit-linear-gradient(
left,
var(--secondary-bg-color) 0%,
var(--secondary-bg-color) 20%,
var(--secondary-bg-color) 40%,
var(--secondary-bg-color) 100%
);
}
.messenger-infoView > nav > p {
color: #333;
}
/*
***********************************************
* App Modal
***********************************************
*/
.app-modal-card {
background: var(--modal-bg-color);
}
.app-modal-header {
color: #000;
}
.app-modal-body {
color: #000;
}
/*
*****************************************
* Responsive Design
*****************************************
*/
@media (max-width: 1060px) {
.messenger-infoView {
box-shadow: 0px 0px 20px rgba(18, 67, 105, 0.06);
}
}
@media (max-width: 980px) {
.messenger-listView {
box-shadow: 0px 0px 20px rgba(18, 67, 105, 0.06);
}
}
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

+288
View File
@@ -0,0 +1,288 @@
/*
****************************************************************************
* Text Area auto resize
****************************************************************************
*/
(function (global, factory) {
if (typeof define === "function" && define.amd) {
define(['module', 'exports'], factory);
} else if (typeof exports !== "undefined") {
factory(module, exports);
} else {
var mod = {
exports: {}
};
factory(mod, mod.exports);
global.autosize = mod.exports;
}
})(this, function (module, exports) {
'use strict';
var map = typeof Map === "function" ? new Map() : function () {
var keys = [];
var values = [];
return {
has: function has(key) {
return keys.indexOf(key) > -1;
},
get: function get(key) {
return values[keys.indexOf(key)];
},
set: function set(key, value) {
if (keys.indexOf(key) === -1) {
keys.push(key);
values.push(value);
}
},
delete: function _delete(key) {
var index = keys.indexOf(key);
if (index > -1) {
keys.splice(index, 1);
values.splice(index, 1);
}
}
};
}();
var createEvent = function createEvent(name) {
return new Event(name, { bubbles: true });
};
try {
new Event('test');
} catch (e) {
// IE does not support `new Event()`
createEvent = function createEvent(name) {
var evt = document.createEvent('Event');
evt.initEvent(name, true, false);
return evt;
};
}
function assign(ta) {
if (!ta || !ta.nodeName || ta.nodeName !== 'TEXTAREA' || map.has(ta)) return;
var heightOffset = null;
var clientWidth = null;
var cachedHeight = null;
function init() {
var style = window.getComputedStyle(ta, null);
if (style.resize === 'vertical') {
ta.style.resize = 'none';
} else if (style.resize === 'both') {
ta.style.resize = 'horizontal';
}
if (style.boxSizing === 'content-box') {
heightOffset = -(parseFloat(style.paddingTop) + parseFloat(style.paddingBottom));
} else {
heightOffset = parseFloat(style.borderTopWidth) + parseFloat(style.borderBottomWidth);
}
// Fix when a textarea is not on document body and heightOffset is Not a Number
if (isNaN(heightOffset)) {
heightOffset = 0;
}
update();
}
function changeOverflow(value) {
{
// Chrome/Safari-specific fix:
// When the textarea y-overflow is hidden, Chrome/Safari do not reflow the text to account for the space
// made available by removing the scrollbar. The following forces the necessary text reflow.
var width = ta.style.width;
ta.style.width = '0px';
// Force reflow:
/* jshint ignore:start */
ta.offsetWidth;
/* jshint ignore:end */
ta.style.width = width;
}
ta.style.overflowY = value;
}
function getParentOverflows(el) {
var arr = [];
while (el && el.parentNode && el.parentNode instanceof Element) {
if (el.parentNode.scrollTop) {
arr.push({
node: el.parentNode,
scrollTop: el.parentNode.scrollTop
});
}
el = el.parentNode;
}
return arr;
}
function resize() {
if (ta.scrollHeight === 0) {
// If the scrollHeight is 0, then the element probably has display:none or is detached from the DOM.
return;
}
var overflows = getParentOverflows(ta);
var docTop = document.documentElement && document.documentElement.scrollTop; // Needed for Mobile IE (ticket #240)
ta.style.height = '';
ta.style.height = ta.scrollHeight + heightOffset + 'px';
// used to check if an update is actually necessary on window.resize
clientWidth = ta.clientWidth;
// prevents scroll-position jumping
overflows.forEach(function (el) {
el.node.scrollTop = el.scrollTop;
});
if (docTop) {
document.documentElement.scrollTop = docTop;
}
}
function update() {
resize();
var styleHeight = Math.round(parseFloat(ta.style.height));
var computed = window.getComputedStyle(ta, null);
// Using offsetHeight as a replacement for computed.height in IE, because IE does not account use of border-box
var actualHeight = computed.boxSizing === 'content-box' ? Math.round(parseFloat(computed.height)) : ta.offsetHeight;
// The actual height not matching the style height (set via the resize method) indicates that
// the max-height has been exceeded, in which case the overflow should be allowed.
if (actualHeight < styleHeight) {
if (computed.overflowY === 'hidden') {
changeOverflow('scroll');
resize();
actualHeight = computed.boxSizing === 'content-box' ? Math.round(parseFloat(window.getComputedStyle(ta, null).height)) : ta.offsetHeight;
}
} else {
// Normally keep overflow set to hidden, to avoid flash of scrollbar as the textarea expands.
if (computed.overflowY !== 'hidden') {
changeOverflow('hidden');
resize();
actualHeight = computed.boxSizing === 'content-box' ? Math.round(parseFloat(window.getComputedStyle(ta, null).height)) : ta.offsetHeight;
}
}
if (cachedHeight !== actualHeight) {
cachedHeight = actualHeight;
var evt = createEvent('autosize:resized');
try {
ta.dispatchEvent(evt);
} catch (err) {
// Firefox will throw an error on dispatchEvent for a detached element
// https://bugzilla.mozilla.org/show_bug.cgi?id=889376
}
}
}
var pageResize = function pageResize() {
if (ta.clientWidth !== clientWidth) {
update();
}
};
var destroy = function (style) {
window.removeEventListener('resize', pageResize, false);
ta.removeEventListener('input', update, false);
ta.removeEventListener('keyup', update, false);
ta.removeEventListener('autosize:destroy', destroy, false);
ta.removeEventListener('autosize:update', update, false);
Object.keys(style).forEach(function (key) {
ta.style[key] = style[key];
});
map.delete(ta);
}.bind(ta, {
height: ta.style.height,
resize: ta.style.resize,
overflowY: ta.style.overflowY,
overflowX: ta.style.overflowX,
wordWrap: ta.style.wordWrap
});
ta.addEventListener('autosize:destroy', destroy, false);
// IE9 does not fire onpropertychange or oninput for deletions,
// so binding to onkeyup to catch most of those events.
// There is no way that I know of to detect something like 'cut' in IE9.
if ('onpropertychange' in ta && 'oninput' in ta) {
ta.addEventListener('keyup', update, false);
}
window.addEventListener('resize', pageResize, false);
ta.addEventListener('input', update, false);
ta.addEventListener('autosize:update', update, false);
ta.style.overflowX = 'hidden';
ta.style.wordWrap = 'break-word';
map.set(ta, {
destroy: destroy,
update: update
});
init();
}
function destroy(ta) {
var methods = map.get(ta);
if (methods) {
methods.destroy();
}
}
function update(ta) {
var methods = map.get(ta);
if (methods) {
methods.update();
}
}
var autosize = null;
// Do nothing in Node.js environment and IE8 (or lower)
if (typeof window === 'undefined' || typeof window.getComputedStyle !== 'function') {
autosize = function autosize(el) {
return el;
};
autosize.destroy = function (el) {
return el;
};
autosize.update = function (el) {
return el;
};
} else {
autosize = function autosize(el, options) {
if (el) {
Array.prototype.forEach.call(el.length ? el : [el], function (x) {
return assign(x, options);
});
}
return el;
};
autosize.destroy = function (el) {
if (el) {
Array.prototype.forEach.call(el.length ? el : [el], destroy);
}
return el;
};
autosize.update = function (el) {
if (el) {
Array.prototype.forEach.call(el.length ? el : [el], update);
}
return el;
};
}
exports.default = autosize;
module.exports = exports['default'];
});
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
+41
View File
@@ -0,0 +1,41 @@
/**
* changes date string to time ago string.
* @param dateString - The date string to convert to a time ago string.
* @returns A string that tells the user how long ago the date was.
*/
function dateStringToTimeAgo(dateString) {
const now = new Date();
const date = new Date(dateString);
const seconds = Math.floor((now - date) / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
const days = Math.floor(hours / 24);
const weeks = Math.floor(days / 7);
if (seconds < 60) {
return "just now";
} else if (minutes < 60) {
return `${minutes}m ago`;
} else if (hours < 24) {
return `${hours}h ago`;
} else if (days < 7) {
return `${days}d ago`;
} else {
return `${weeks}w ago`;
}
}
/**
* It returns a function that, when invoked, will wait for a specified amount of time before executing
* the original function.
* @param callback - The function to be executed after the delay.
* @param delay - The amount of time to wait before calling the callback.
* @returns A function that will call the callback function after a delay.
*/
function debounce(callback, delay) {
let timerId;
return function (...args) {
clearTimeout(timerId);
timerId = setTimeout(() => {
callback.apply(this, args);
}, delay);
};
}
Binary file not shown.
+124
View File
@@ -0,0 +1,124 @@
<?php
return [
/*
|-------------------------------------
| Messenger display name
|-------------------------------------
*/
'name' => env('CHATIFY_NAME', 'Chatify Messenger'),
/*
|-------------------------------------
| The disk on which to store added
| files and derived images by default.
|-------------------------------------
*/
'storage_disk_name' => env('CHATIFY_STORAGE_DISK', 'public'),
/*
|-------------------------------------
| Routes configurations
|-------------------------------------
*/
'routes' => [
'custom' => env('CHATIFY_CUSTOM_ROUTES', false),
'prefix' => env('CHATIFY_ROUTES_PREFIX', 'chatify'),
'middleware' => env('CHATIFY_ROUTES_MIDDLEWARE', ['web','auth']),
'namespace' => env('CHATIFY_ROUTES_NAMESPACE', 'Chatify\Http\Controllers'),
],
'api_routes' => [
'prefix' => env('CHATIFY_API_ROUTES_PREFIX', 'chatify/api'),
'middleware' => env('CHATIFY_API_ROUTES_MIDDLEWARE', ['api']),
'namespace' => env('CHATIFY_API_ROUTES_NAMESPACE', 'Chatify\Http\Controllers\Api'),
],
/*
|-------------------------------------
| Pusher API credentials
|-------------------------------------
*/
'pusher' => [
'debug' => env('APP_DEBUG', false),
'key' => env('PUSHER_APP_KEY'),
'secret' => env('PUSHER_APP_SECRET'),
'app_id' => env('PUSHER_APP_ID'),
'options' => [
'cluster' => env('PUSHER_APP_CLUSTER', 'mt1'),
'host' => env('PUSHER_HOST') ?: 'api-'.env('PUSHER_APP_CLUSTER', 'mt1').'.pusher.com',
'port' => env('PUSHER_PORT', 443),
'scheme' => env('PUSHER_SCHEME', 'https'),
'encrypted' => true,
'useTLS' => env('PUSHER_SCHEME', 'https') === 'https',
],
],
/*
|-------------------------------------
| User Avatar
|-------------------------------------
*/
'user_avatar' => [
'folder' => 'users-avatar',
'default' => 'avatar.png',
],
/*
|-------------------------------------
| Gravatar
|
| imageset property options:
| [ 404 | mp | identicon (default) | monsterid | wavatar ]
|-------------------------------------
*/
'gravatar' => [
'enabled' => true,
'image_size' => 200,
'imageset' => 'identicon'
],
/*
|-------------------------------------
| Attachments
|-------------------------------------
*/
'attachments' => [
'folder' => 'attachments',
'download_route_name' => 'attachments.download',
'allowed_images' => (array) ['png','jpg','jpeg','gif'],
'allowed_files' => (array) ['zip','rar','txt'],
'max_upload_size' => env('CHATIFY_MAX_FILE_SIZE', 150), // MB
],
/*
|-------------------------------------
| Messenger's colors
|-------------------------------------
*/
'colors' => (array) [
'#2180f3',
'#2196F3',
'#00BCD4',
'#3F51B5',
'#673AB7',
'#4CAF50',
'#FFC107',
'#FF9800',
'#ff2522',
'#9C27B0',
],
/*
|-------------------------------------
| Sounds
| You can enable/disable the sounds and
| change sound's name/path placed at
| `public/` directory of your app.
|
|-------------------------------------
*/
'sounds' => [
'enabled' => true,
'public_path' => 'sounds/chatify',
'new_message' => 'new-message-sound.mp3',
]
];
@@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddActiveStatusToUsers extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('users', function (Blueprint $table) {
// if not exist, add the new column
if (!Schema::hasColumn('users', 'active_status')) {
$table->boolean('active_status')->default(0);
}
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('active_status');
});
}
}
@@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddAvatarToUsers extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('users', function (Blueprint $table) {
// if not exist, add the new column
if (!Schema::hasColumn('users', 'avatar')) {
$table->string('avatar')->default(config('chatify.user_avatar.default'));
}
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('avatar');
});
}
}
@@ -0,0 +1,35 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddDarkModeToUsers extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('users', function (Blueprint $table) {
// if not exist, add the new column
if (!Schema::hasColumn('users', 'dark_mode')) {
$table->boolean('dark_mode')->default(0);
}
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('dark_mode');
});
}
}
@@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class AddMessengerColorToUsers extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('users', function (Blueprint $table) {
if (!Schema::hasColumn('users', 'messenger_color')) {
$table->string('messenger_color')->nullable();
}
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('users', function (Blueprint $table) {
$table->dropColumn('messenger_color');
});
}
}
@@ -0,0 +1,33 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateChatifyFavoritesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('ch_favorites', function (Blueprint $table) {
$table->uuid('id')->primary();
$table->bigInteger('user_id');
$table->bigInteger('favorite_id');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('ch_favorites');
}
}
@@ -0,0 +1,36 @@
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class CreateChatifyMessagesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('ch_messages', function (Blueprint $table) {
$table->uuid('id')->primary();
$table->bigInteger('from_id');
$table->bigInteger('to_id');
$table->string('body',5000)->nullable();
$table->string('attachment')->nullable();
$table->boolean('seen')->default(false);
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('ch_messages');
}
}
+75
View File
@@ -0,0 +1,75 @@
<?php
use Illuminate\Support\Facades\Route;
/**
* Authentication for pusher private channels
*/
Route::post('/chat/auth', 'MessagesController@pusherAuth')->name('api.pusher.auth');
/**
* Fetch info for specific id [user/group]
*/
Route::post('/idInfo', 'MessagesController@idFetchData')->name('api.idInfo');
/**
* Send message route
*/
Route::post('/sendMessage', 'MessagesController@send')->name('api.send.message');
/**
* Fetch messages
*/
Route::post('/fetchMessages', 'MessagesController@fetch')->name('api.fetch.messages');
/**
* Download attachments route to create a downloadable links
*/
Route::get('/download/{fileName}', 'MessagesController@download')->name('api.'.config('chatify.attachments.download_route_name'));
/**
* Make messages as seen
*/
Route::post('/makeSeen', 'MessagesController@seen')->name('api.messages.seen');
/**
* Get contacts
*/
Route::get('/getContacts', 'MessagesController@getContacts')->name('api.contacts.get');
/**
* Star in favorite list
*/
Route::post('/star', 'MessagesController@favorite')->name('api.star');
/**
* get favorites list
*/
Route::post('/favorites', 'MessagesController@getFavorites')->name('api.favorites');
/**
* Search in messenger
*/
Route::get('/search', 'MessagesController@search')->name('api.search');
/**
* Get shared photos
*/
Route::post('/shared', 'MessagesController@sharedPhotos')->name('api.shared');
/**
* Delete Conversation
*/
Route::post('/deleteConversation', 'MessagesController@deleteConversation')->name('api.conversation.delete');
/**
* Delete Conversation
*/
Route::post('/updateSettings', 'MessagesController@updateSettings')->name('api.avatar.update');
/**
* Set active status
*/
Route::post('/setActiveStatus', 'MessagesController@setActiveStatus')->name('api.activeStatus.set');
+118
View File
@@ -0,0 +1,118 @@
<?php
/**
* -----------------------------------------------------------------
* NOTE : There is two routes has a name (user & group),
* any change in these two route's name may cause an issue
* if not modified in all places that used in (e.g Chatify class,
* Controllers, chatify javascript file...).
* -----------------------------------------------------------------
*/
use Illuminate\Support\Facades\Route;
/*
* This is the main app route [Chatify Messenger]
*/
Route::get('/', 'MessagesController@index')->name(config('chatify.routes.prefix'));
/**
* Fetch info for specific id [user/group]
*/
Route::post('/idInfo', 'MessagesController@idFetchData');
/**
* Send message route
*/
Route::post('/sendMessage', 'MessagesController@send')->name('send.message');
/**
* Fetch messages
*/
Route::post('/fetchMessages', 'MessagesController@fetch')->name('fetch.messages');
/**
* Download attachments route to create a downloadable links
*/
Route::get('/download/{fileName}', 'MessagesController@download')->name(config('chatify.attachments.download_route_name'));
/**
* Authentication for pusher private channels
*/
Route::post('/chat/auth', 'MessagesController@pusherAuth')->name('pusher.auth');
/**
* Make messages as seen
*/
Route::post('/makeSeen', 'MessagesController@seen')->name('messages.seen');
/**
* Get contacts
*/
Route::get('/getContacts', 'MessagesController@getContacts')->name('contacts.get');
/**
* Update contact item data
*/
Route::post('/updateContacts', 'MessagesController@updateContactItem')->name('contacts.update');
/**
* Star in favorite list
*/
Route::post('/star', 'MessagesController@favorite')->name('star');
/**
* get favorites list
*/
Route::post('/favorites', 'MessagesController@getFavorites')->name('favorites');
/**
* Search in messenger
*/
Route::get('/search', 'MessagesController@search')->name('search');
/**
* Get shared photos
*/
Route::post('/shared', 'MessagesController@sharedPhotos')->name('shared');
/**
* Delete Conversation
*/
Route::post('/deleteConversation', 'MessagesController@deleteConversation')->name('conversation.delete');
/**
* Delete Message
*/
Route::post('/deleteMessage', 'MessagesController@deleteMessage')->name('message.delete');
/**
* Update setting
*/
Route::post('/updateSettings', 'MessagesController@updateSettings')->name('avatar.update');
/**
* Set active status
*/
Route::post('/setActiveStatus', 'MessagesController@setActiveStatus')->name('activeStatus.set');
/*
* [Group] view by id
*/
Route::get('/group/{id}', 'MessagesController@index')->name('group');
/*
* user view by id.
* Note : If you added routes after the [User] which is the below one,
* it will considered as user id.
*
* e.g. - The commented routes below :
*/
// Route::get('/route', function(){ return 'Munaf'; }); // works as a route
Route::get('/{id}', 'MessagesController@index')->name('user');
// Route::get('/route', function(){ return 'Munaf'; }); // works as a user id
@@ -0,0 +1,8 @@
<div class="favorite-list-item">
@if($user)
<div data-id="{{ $user->id }}" data-action="0" class="avatar av-m"
style="background-image: url('{{ Chatify::getUserWithAvatar($user)->avatar }}');">
</div>
<p>{{ strlen($user->name) > 5 ? substr($user->name,0,6).'..' : $user->name }}</p>
@endif
</div>
@@ -0,0 +1,17 @@
<script src="https://js.pusher.com/7.2.0/pusher.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@joeattardi/[email protected]/dist/index.min.js"></script>
<script >
// Gloabl Chatify variables from PHP to JS
window.chatify = {
name: "{{ config('chatify.name') }}",
sounds: {!! json_encode(config('chatify.sounds')) !!},
allowedImages: {!! json_encode(config('chatify.attachments.allowed_images')) !!},
allowedFiles: {!! json_encode(config('chatify.attachments.allowed_files')) !!},
maxUploadSize: {{ Chatify::getMaxUploadSize() }},
pusher: {!! json_encode(config('chatify.pusher')) !!},
pusherAuthEndpoint: '{{route("pusher.auth")}}'
};
window.chatify.allAllowedExtensions = chatify.allowedImages.concat(chatify.allowedFiles);
</script>
<script src="{{ asset('js/chatify/utils.js') }}"></script>
<script src="{{ asset('js/chatify/code.js') }}"></script>
@@ -0,0 +1,30 @@
<title>{{ config('chatify.name') }}</title>
{{-- Meta tags --}}
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="id" content="{{ $id }}">
<meta name="messenger-color" content="{{ $messengerColor }}">
<meta name="messenger-theme" content="{{ $dark_mode }}">
<meta name="csrf-token" content="{{ csrf_token() }}">
<meta name="url" content="{{ url('').'/'.config('chatify.routes.prefix') }}" data-user="{{ Auth::user()->id }}">
{{-- scripts --}}
<script
src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="{{ asset('js/chatify/font.awesome.min.js') }}"></script>
<script src="{{ asset('js/chatify/autosize.js') }}"></script>
<script src="{{ asset('js/app.js') }}"></script>
<script src='https://unpkg.com/[email protected]/nprogress.js'></script>
{{-- styles --}}
<link rel='stylesheet' href='https://unpkg.com/[email protected]/nprogress.css'/>
<link href="{{ asset('css/chatify/style.css') }}" rel="stylesheet" />
<link href="{{ asset('css/chatify/'.$dark_mode.'.mode.css') }}" rel="stylesheet" />
<link href="{{ asset('css/app.css') }}" rel="stylesheet" />
{{-- Setting messenger primary color to css --}}
<style>
:root {
--primary-color: {{ $messengerColor }};
}
</style>
+11
View File
@@ -0,0 +1,11 @@
{{-- user info and avatar --}}
<div class="avatar av-l chatify-d-flex"></div>
<p class="info-name">{{ config('chatify.name') }}</p>
<div class="messenger-infoView-btns">
<a href="#" class="danger delete-conversation">Delete Conversation</a>
</div>
{{-- shared photos --}}
<div class="messenger-infoView-shared">
<p class="messenger-title"><span>Shared Photos</span></p>
<div class="shared-photos-list"></div>
</div>
@@ -0,0 +1,90 @@
{{-- -------------------- Saved Messages -------------------- --}}
@if($get == 'saved')
<table class="messenger-list-item" data-contact="{{ Auth::user()->id }}">
<tr data-action="0">
{{-- Avatar side --}}
<td>
<div class="saved-messages avatar av-m">
<span class="far fa-bookmark"></span>
</div>
</td>
{{-- center side --}}
<td>
<p data-id="{{ Auth::user()->id }}" data-type="user">Saved Messages <span>You</span></p>
<span>Save messages secretly</span>
</td>
</tr>
</table>
@endif
{{-- -------------------- Contact list -------------------- --}}
@if($get == 'users' && !!$lastMessage)
<?php
$lastMessageBody = mb_convert_encoding($lastMessage->body, 'UTF-8', 'UTF-8');
$lastMessageBody = strlen($lastMessageBody) > 30 ? mb_substr($lastMessageBody, 0, 30, 'UTF-8').'..' : $lastMessageBody;
?>
<table class="messenger-list-item" data-contact="{{ $user->id }}">
<tr data-action="0">
{{-- Avatar side --}}
<td style="position: relative">
@if($user->active_status)
<span class="activeStatus"></span>
@endif
<div class="avatar av-m"
style="background-image: url('{{ $user->avatar }}');">
</div>
</td>
{{-- center side --}}
<td>
<p data-id="{{ $user->id }}" data-type="user">
{{ strlen($user->name) > 12 ? trim(substr($user->name,0,12)).'..' : $user->name }}
<span class="contact-item-time" data-time="{{$lastMessage->created_at}}">{{ $lastMessage->timeAgo }}</span></p>
<span>
{{-- Last Message user indicator --}}
{!!
$lastMessage->from_id == Auth::user()->id
? '<span class="lastMessageIndicator">You :</span>'
: ''
!!}
{{-- Last message body --}}
@if($lastMessage->attachment == null)
{!!
$lastMessageBody
!!}
@else
<span class="fas fa-file"></span> Attachment
@endif
</span>
{{-- New messages counter --}}
{!! $unseenCounter > 0 ? "<b>".$unseenCounter."</b>" : '' !!}
</td>
</tr>
</table>
@endif
{{-- -------------------- Search Item -------------------- --}}
@if($get == 'search_item')
<table class="messenger-list-item" data-contact="{{ $user->id }}">
<tr data-action="0">
{{-- Avatar side --}}
<td>
<div class="avatar av-m"
style="background-image: url('{{ $user->avatar }}');">
</div>
</td>
{{-- center side --}}
<td>
<p data-id="{{ $user->id }}" data-type="user">
{{ strlen($user->name) > 12 ? trim(substr($user->name,0,12)).'..' : $user->name }}
</td>
</tr>
</table>
@endif
{{-- -------------------- Shared photos Item -------------------- --}}
@if($get == 'sharedPhoto')
<div class="shared-photo chat-image" style="background-image: url('{{ $image }}')"></div>
@endif
@@ -0,0 +1,39 @@
<?php
$seenIcon = (!!$seen ? 'check-double' : 'check');
$timeAndSeen = "<span data-time='$created_at' class='message-time'>
".($isSender ? "<span class='fas fa-$seenIcon' seen'></span>" : '' )." <span class='time'>$timeAgo</span>
</span>";
?>
<div class="message-card @if($isSender) mc-sender @endif" data-id="{{ $id }}">
{{-- Delete Message Button --}}
@if ($isSender)
<div class="actions">
<i class="fas fa-trash delete-btn" data-id="{{ $id }}"></i>
</div>
@endif
{{-- Card --}}
<div class="message-card-content">
@if (@$attachment->type != 'image' || $message)
<div class="message">
{!! ($message == null && $attachment != null && @$attachment->type != 'file') ? $attachment->title : nl2br($message) !!}
{!! $timeAndSeen !!}
{{-- If attachment is a file --}}
@if(@$attachment->type == 'file')
<a href="{{ route(config('chatify.attachments.download_route_name'), ['fileName'=>$attachment->file]) }}" class="file-download">
<span class="fas fa-file"></span> {{$attachment->title}}</a>
@endif
</div>
@endif
@if(@$attachment->type == 'image')
<div class="image-wrapper" style="text-align: {{$isSender ? 'end' : 'start'}}">
<div class="image-file chat-image" style="background-image: url('{{ Chatify::getAttachmentUrl($attachment->file) }}')">
<div>{{ $attachment->title }}</div>
</div>
<div style="margin-bottom:5px">
{!! $timeAndSeen !!}
</div>
</div>
@endif
</div>
</div>
@@ -0,0 +1,73 @@
{{-- ---------------------- Image modal box ---------------------- --}}
<div id="imageModalBox" class="imageModal">
<span class="imageModal-close">&times;</span>
<img class="imageModal-content" id="imageModalBoxSrc">
</div>
{{-- ---------------------- Delete Modal ---------------------- --}}
<div class="app-modal" data-name="delete">
<div class="app-modal-container">
<div class="app-modal-card" data-name="delete" data-modal='0'>
<div class="app-modal-header">Are you sure you want to delete this?</div>
<div class="app-modal-body">You can not undo this action</div>
<div class="app-modal-footer">
<a href="javascript:void(0)" class="app-btn cancel">Cancel</a>
<a href="javascript:void(0)" class="app-btn a-btn-danger delete">Delete</a>
</div>
</div>
</div>
</div>
{{-- ---------------------- Alert Modal ---------------------- --}}
<div class="app-modal" data-name="alert">
<div class="app-modal-container">
<div class="app-modal-card" data-name="alert" data-modal='0'>
<div class="app-modal-header"></div>
<div class="app-modal-body"></div>
<div class="app-modal-footer">
<a href="javascript:void(0)" class="app-btn cancel">Cancel</a>
</div>
</div>
</div>
</div>
{{-- ---------------------- Settings Modal ---------------------- --}}
<div class="app-modal" data-name="settings">
<div class="app-modal-container">
<div class="app-modal-card" data-name="settings" data-modal='0'>
<form id="update-settings" action="{{ route('avatar.update') }}" enctype="multipart/form-data" method="POST">
@csrf
{{-- <div class="app-modal-header">Update your profile settings</div> --}}
<div class="app-modal-body">
{{-- Udate profile avatar --}}
<div class="avatar av-l upload-avatar-preview chatify-d-flex"
style="background-image: url('{{ Chatify::getUserWithAvatar(Auth::user())->avatar }}');"
></div>
<p class="upload-avatar-details"></p>
<label class="app-btn a-btn-primary update" style="background-color:{{$messengerColor}}">
Upload New
<input class="upload-avatar chatify-d-none" accept="image/*" name="avatar" type="file" />
</label>
{{-- Dark/Light Mode --}}
<p class="divider"></p>
<p class="app-modal-header">Dark Mode <span class="
{{ Auth::user()->dark_mode > 0 ? 'fas' : 'far' }} fa-moon dark-mode-switch"
data-mode="{{ Auth::user()->dark_mode > 0 ? 1 : 0 }}"></span></p>
{{-- change messenger color --}}
<p class="divider"></p>
{{-- <p class="app-modal-header">Change {{ config('chatify.name') }} Color</p> --}}
<div class="update-messengerColor">
@foreach (config('chatify.colors') as $color)
<span style="background-color: {{ $color}}" data-color="{{$color}}" class="color-btn"></span>
@if (($loop->index + 1) % 5 == 0)
<br/>
@endif
@endforeach
</div>
</div>
<div class="app-modal-footer">
<a href="javascript:void(0)" class="app-btn cancel">Cancel</a>
<input type="submit" class="app-btn a-btn-success update" value="Save Changes" />
</div>
</form>
</div>
</div>
</div>
@@ -0,0 +1,9 @@
<div class="messenger-sendCard">
<form id="message-form" method="POST" action="{{ route('send.message') }}" enctype="multipart/form-data">
@csrf
<label><span class="fas fa-plus-circle"></span><input disabled='disabled' type="file" class="upload-attachment" name="file" accept=".{{implode(', .',config('chatify.attachments.allowed_images'))}}, .{{implode(', .',config('chatify.attachments.allowed_files'))}}" /></label>
<button class="emoji-button"></span><span class="fas fa-smile"></button>
<textarea readonly='readonly' name="message" class="m-send app-scroll" placeholder="Type a message.."></textarea>
<button disabled='disabled' class="send-button"><span class="fas fa-paper-plane"></span></button>
</form>
</div>
+112
View File
@@ -0,0 +1,112 @@
@include('Chatify::layouts.headLinks')
<div class="messenger">
{{-- ----------------------Users/Groups lists side---------------------- --}}
<div class="messenger-listView {{ !!$id ? 'conversation-active' : '' }}">
{{-- Header and search bar --}}
<div class="m-header">
<nav>
<a href="#"><i class="fas fa-inbox"></i> <span class="messenger-headTitle">MESSAGES</span> </a>
{{-- header buttons --}}
<nav class="m-header-right">
<a href="#"><i class="fas fa-cog settings-btn"></i></a>
<a href="#" class="listView-x"><i class="fas fa-times"></i></a>
</nav>
</nav>
{{-- Search input --}}
<input type="text" class="messenger-search" placeholder="Search" />
{{-- Tabs --}}
{{-- <div class="messenger-listView-tabs">
<a href="#" class="active-tab" data-view="users">
<span class="far fa-user"></span> Contacts</a>
</div> --}}
</div>
{{-- tabs and lists --}}
<div class="m-body contacts-container">
{{-- Lists [Users/Group] --}}
{{-- ---------------- [ User Tab ] ---------------- --}}
<div class="show messenger-tab users-tab app-scroll" data-view="users">
{{-- Favorites --}}
<div class="favorites-section">
<p class="messenger-title"><span>Favorites</span></p>
<div class="messenger-favorites app-scroll-hidden"></div>
</div>
{{-- Saved Messages --}}
<p class="messenger-title"><span>Your Space</span></p>
{!! view('Chatify::layouts.listItem', ['get' => 'saved']) !!}
{{-- Contact --}}
<p class="messenger-title"><span>All Messages</span></p>
<div class="listOfContacts" style="width: 100%;height: calc(100% - 272px);position: relative;"></div>
</div>
{{-- ---------------- [ Search Tab ] ---------------- --}}
<div class="messenger-tab search-tab app-scroll" data-view="search">
{{-- items --}}
<p class="messenger-title"><span>Search</span></p>
<div class="search-records">
<p class="message-hint center-el"><span>Type to search..</span></p>
</div>
</div>
</div>
</div>
{{-- ----------------------Messaging side---------------------- --}}
<div class="messenger-messagingView">
{{-- header title [conversation name] amd buttons --}}
<div class="m-header m-header-messaging">
<nav class="chatify-d-flex chatify-justify-content-between chatify-align-items-center">
{{-- header back button, avatar and user name --}}
<div class="chatify-d-flex chatify-justify-content-between chatify-align-items-center">
<a href="#" class="show-listView"><i class="fas fa-arrow-left"></i></a>
<div class="avatar av-s header-avatar" style="margin: 0px 10px; margin-top: -5px; margin-bottom: -5px;">
</div>
<a href="#" class="user-name">{{ config('chatify.name') }}</a>
</div>
{{-- header buttons --}}
<nav class="m-header-right">
<a href="#" class="add-to-favorite"><i class="fas fa-star"></i></a>
<a href="/"><i class="fas fa-home"></i></a>
<a href="#" class="show-infoSide"><i class="fas fa-info-circle"></i></a>
</nav>
</nav>
{{-- Internet connection --}}
<div class="internet-connection">
<span class="ic-connected">Connected</span>
<span class="ic-connecting">Connecting...</span>
<span class="ic-noInternet">No internet access</span>
</div>
</div>
{{-- Messaging area --}}
<div class="m-body messages-container app-scroll">
<div class="messages">
<p class="message-hint center-el"><span>Please select a chat to start messaging</span></p>
</div>
{{-- Typing indicator --}}
<div class="typing-indicator">
<div class="message-card typing">
<div class="message">
<span class="typing-dots">
<span class="dot dot-1"></span>
<span class="dot dot-2"></span>
<span class="dot dot-3"></span>
</span>
</div>
</div>
</div>
</div>
{{-- Send Message Form --}}
@include('Chatify::layouts.sendForm')
</div>
{{-- ---------------------- Info side ---------------------- --}}
<div class="messenger-infoView app-scroll">
{{-- nav actions --}}
<nav>
<p>User Details</p>
<a href="#"><i class="fas fa-times"></i></a>
</nav>
{!! view('Chatify::layouts.info')->render() !!}
</div>
</div>
@include('Chatify::layouts.modals')
@include('Chatify::layouts.footerLinks')