I have a back office developed in laravel that allow to insert data that then android and ios app get.
Now i need to implement onsignal notification for example for when a new product was inserted in back office the app user receives a notification.
I setup my onesignal notification and install in my laravel project this package: https://github.com/laravel-notification-channels/onesignal.
I set OneSignal App ID and REST API Key too.
After that I create a new controller and put the example code that is in package link.
Controller:
use Illuminate\Http\Request;
use NotificationChannels\OneSignal\OneSignalChannel;
use NotificationChannels\OneSignal\OneSignalMessage;
use NotificationChannels\OneSignal\OneSignalWebButton;
use Illuminate\Notifications\Notification;
class NotificationsController extends Controller
{
public function via($notifiable)
{
return [OneSignalChannel::class];
}
public function toOneSignal($notifiable)
{
return OneSignalMessage::create()
->subject("Your {$notifiable->service} account was approved!")
->body("Click here to see details.")
->url('http://onesignal.com')
->webButton(
OneSignalWebButton::create('link-1')
->text('Click here')
->icon('https://upload.wikimedia.org/wikipedia/commons/4/4f/Laravel_logo.png')
->url('http://laravel.com')
);
}
}
But now I don't konw how to use it.
For example, how can I send a notification when a new product was added?
I need to setup routes?
Let me know if don't explain well my problem.
Thank you
Hi you have to create a Custom Channel OneSignal Notification, so you don't have to do that on your Controller.
1.- First, you need to create a OneSignal Notification for each "event" that you want to notify
For instance when a Product was added
php artisan make:notification ProductAdded
This will generate a Notification File inside of App\Notifications\ProductAdded
2.- On the new File ProductAdded you will need to add that logic of the notification to OneSignal
<?php
// App\Notifications\ProductAdded.php
class OneSignal extends Notification
{
use Queueable;
private $data; //this is the "model" data that will be passed through the notify method
/**
* Create a new notification instance.
*
* #return void
*/
public function __construct($data)
{
$this->data = $data;
}
/**
* Get the notification's delivery channels.
*
* #param mixed $notifiable
* #return array
*/
public function via($notifiable)
{
return [OneSignalChannel::class];
}
public function toOneSignal($notifiable)
{
//now you can build your message with the $this->data information
return OneSignalMessage::create()
->subject("Your {$notifiable->service} account was approved!")
->body("Click here to see details.")
->url('http://onesignal.com')
->webButton(
OneSignalWebButton::create('link-1')
->text('Click here')
->icon('https://upload.wikimedia.org/wikipedia/commons/4/4f/Laravel_logo.png')
->url('http://laravel.com')
);
}
}
3.- Use the Notifiable trait in your Model
<?php
class YourModel extends Model
{
use Notifiable;
public function sendNotification()
{
$this->notify(new ProductAdded($this)); //Pass the model data to the OneSignal Notificator
}
public function routeNotificationForOneSignal()
{
/*
* you have to return the one signal player id tat will
* receive the message of if you want you can return
* an array of players id
*/
return $this->data->user_one_signal_id;
}
}
Additionally you can use the Notification Facade wherever you want
$users it's a collection of player id's
$data it's the data to build the notification
Notification::send($users, new ProductAdded($dataToNotify));
I hope this helps :)
Also you can read more of this stuff on Laravel Docs
https://laravel.com/docs/5.4/notifications
PD.
If you feel overwhelmed with the notification system, you can also use this package https://github.com/berkayk/laravel-onesignal it's a simple wrapper that you can use with ease and that has a lot of useful handy methods.
Related
I want to create a media storage apps, that allows user to upload and view their media on my platform.
I use laravel to create it.
I was successfully upload it to google cloud storage with private file attribute, and save the file name to my database.
My question is, how to access it directly from the laravel, and show it as image or video, currently I have cide.json file as a key, but I never get detail explaination how to use it.
Okay, If I imagine your table structure, it might be like
id user_id file_location
You can build an api like
Route::get('users/{id}/files', FolderName\ControllerName#ControllerFunction)
Now you can fetch your user media files by passing the id or you can modify your controller to fetchAll the user media files depending upon your idea.
Brief explanation:
Add this route in Routes.php files in your laravel project
Route::get('users/{id}/files', User\UserApiController#fetchUserMediaFiles);
Create a new controller which manages the flow the request to service function
class UserApiController {
public $userService;
public function __construct(UserService $userService) {
$this->userService = $userService;
}
public function fetchUserMediaFiles($id) {
$userMediaFiles = $this->userService->fetchUserMediaFiles($id);
return $this->done(null, [
'userMediafiles' => $userMediaFiles
]);
}
}
Create a new service which handles the incoming request from Controller and sends it to repository
class UserService {
private $userRepository;
public function __construct(UserRepository $userRepository) {
$this->userRepository= $userRepository;
}
public function fetchUserMediaFiles($id) {
return $this->userRepository->fetchUserMediaFiles($id);
}
}
Create new repository which would take the request from userService class and returns you the data by fetching from your db
class UserRepository {
public function fetchUserMediaFiles($id) {
$userMediaFilesTable = UserMediaFiles::$TABLE_NAME;
$model = UserMediaFiles::with([])
->where("$userMediaFilesTable.user_id", $id);
$model = $model->select(["$userMediaFilesTable.user_id", "$userMediaFilesTable.file_location"])
->get();
return $model;
}
}
Now let's try to hit the api with a dummy get request
http://xxxxxxxx/api/users/1/files
Your response would be like
{
"userMediafiles": [
{
"user_id": 1,
"file_location": "https://xxxxx.com/file",
}
]
}
This is how you do in the laravel. Hope this explanation helps.
On the Laravel docs, it states:
Using The Notification Facade
Alternatively, you may send
notifications via the Notification facade. This is useful primarily
when you need to send a notification to multiple notifiable entities
such as a collection of users. To send notifications using the facade,
pass all of the notifiable entities and the notification instance to
the send method:
Notification::send($users, new InvoicePaid($invoice));
So I am doing this within my controller:
public function index()
{
$subscribers = Subscribers::all();
Notification::send($subscribers, new NewVacancy($subscribers));
}
And here is my Notification class
class NewVacancy extends Notification implements ShouldQueue
{
use Queueable;
public $subscriber;
public function __construct( $subscribers)
{
$this->subscriber = $subscribers;
}
public function toMail($notifiable)
{
return (new MailMessage)->view(
'mail.new-vacancy',
['uuid' => $this->subscriber->uuid]// This fails as $subscriber is a collection
);
}
....
The problem is that within the NewVacancy class, the $subscriber that is passed in is a full collection of all subscribers and not the individual notification being sent.
Now I know I could do a loop over $subscribers and fire the Notification::send() each time but that defeats the point of using facade to begin with.
The general goal is to send emails to all $subscribers with the ability to pass in unique subscriber data using a blade template.
I found out you can access the current user via the $notifiable entity thats passed into the toMail() method.
public function toMail($notifiable)
{
return (new MailMessage)->view(
'mail.new-vacancy',
['uuid' => $notifiable->uuid]
);
}
Please note that $notifiable represents the user object which is getting notified.
$user_id = $notifiable->id;
I am working in laravel v4.2 project and I have different queus tasks that I want to perform. But now I want to perform this task by using two or more drivers fot this. For example I have an queue to send registration email and now I want to send email using redis server.
Second queue I have is to send push notifications to users for this I want to use database drive. So is it possible that use two or more queue drivers on one project.
Please educate me.
Thank you
Just write another QueueManager extend it from the Base QueueManager
Extend Your Base Queue Driver
use Illuminate\Queue\Connectors\RedisConnector;
use Illuminate\Queue\QueueManager;
class RedisQueueManager extends QueueManager
{
public function __construct(\Illuminate\Foundation\Application $app)
{
parent::__construct($app);
$this->registerRedisConnector();
}
/**
* Get the name of the default queue connection.
*
* #return string
*/
public function getDefaultDriver()
{
return 'redis';
}
protected function registerRedisConnector()
{
$app = $this->app;
$this->addConnector('redis', function () use ($app) {
return new RedisConnector($app['redis']);
});
}
}
Now create a service provider to access it through app
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\Extensions\RedisQueueManager;
class RedisQueueServiceProvider extends ServiceProvider
{
/**
* Register the service provider.
*
* #return void
*/
public function register()
{
$this->app->bindShared('redis-queue', function ($app) {
return new RedisQueueManager($app);
});
}
}
Finally Register Your Service provider in config/app.php providers array.
And use it.
Route::get('/', function () {
$default = app('queue');
$redis = app('redis-queue');
});
I have the following Notification entity:
As you can see, there is a field called "objectId" where I want to store the related object id depending on the notification type. Then I add the notification to an email queue. When the queue gets processed I have a problem to fetch the object from the specific service class. For example:
Notification Type 1: UserService::getUser($objectId)
Notification Type 2: CompanyService::getCompany($objectId)
So how could I define that relations without having troubles to add more and more notification types. It feels bad to inject all the needed services and handle it through thousands of "if this than that" :)
If you injected the object instead of the id, you wouldn't need to call an additional service inside the notification to get the appropriate instance.
If the Notification doesn't need to know about what kind of object its using, just depend on an interface that both User and Company implement, and inject those objects directly into Notification.
E.g.:
interface EmailNotifiableEntity {
function getLabel()
function getEmailAddress()
}
class User implements EmailNotifiableEntity {
public function getLabel() {
return $this->getName() . " " . $this->getFullName();
}
public function getEmailAddress() {
return this->getEmailAddress();
}
}
class Company implements EmailNotifiableEntity {
public function getLabel() {
return $this->getCompanyName();
}
public function getEmailAddress() {
return this->getNotificationsEmail();
}
}
class Notification {
public function __construct(EmailNotifiableEntity $entity) {
$this->entity = $entity;
}
public function send() {
$address = $entity->getEmailAddress();
$label = $entity->getLabel();
// do your thing to send your notification
}
(Implementation is a bit bare-bones, so take what you need and build upon it). This way, when you instantiate your Notification you inject the depended upon entity without knowing its specific kind.
Using latest Symfony and FOSUserbundle, after successfully registering a new user, the user is automatically logged in. I want to prevent this. My reason is that only a special user should be able to register new users.
I guess I have to override the registerAction in the RegisterController of the bundle, but I don't know how.
I tried: http://symfony.com/doc/current/bundles/FOSUserBundle/overriding_controllers.html, but it seems to be outdated, no user is created with this method.
Any hints are appreciated.
Edit:
I found out that I did not create the child bundle correctly. I also had to create my own EventListener. It works now when I overwrite the FOSUserEvents::REGISTRATION_SUCCESS event.
Strange thing is that when I use the FOSUserEvents::REGISTRATION_COMPLETEDevent, both events are dispatched, my bundle's and the FOSUserbundle's, so that the user is redirected to the correct site, but logged in as the new user.
Edit 2:
So this is in my listener:
public static function getSubscribedEvents()
{
return array(
FOSUserEvents::REGISTRATION_SUCCESS => 'onRegistrationSuccess',
FOSUserEvents::REGISTRATION_COMPLETED => 'onRegistrationCompleted',
);
}
public function onRegistrationSuccess(FormEvent $event)
{
$url = $this->router->generate('admin');
$event->setResponse(new RedirectResponse($url));
}
public function onRegistrationCompleted(FilterUserResponseEvent $event)
{
}
I set the redirection in the REGISTRATION_SUCCESSevent and the REGISTRATION_COMPLETEDis empty. With the debugger I can verify that my own listener's event is called, but the original event is also called.
Actually, there is no need to do any of these. The fos_user.listener.authentication service is removed from the container if use_authentication_listener is set to false.
See line 74-76 in FOS\UserBundle\DependencyInjection\FOSUserExtension.
This information is also included in document FOS UserBundle Configuration.
You can solve this problem with a Listener, In fos user bundle, it authenticates user with after registration.
file :friendsofsymfony/user-bundle/EventListener/AuthenticationListener.php
class : FOS\UserBundle\EventListener\AuthenticationListener
If you check this class you would see it tracks REGISTRATION_COMPLETED Event.
In Authenticatiton Listener It dispatches Event after triggering logInUser function. Therefore you have to logout user in your listener which subscribes `REGISTRATION COMPLETED.
you can check https://github.com/FriendsOfSymfony/FOSUserBundle/blob/master/Resources/doc/controller_events.rst for writing your listener to logout user.
Note : It may not be a good way log-in log-out user in every registration process, but if you use fosuserbundle easiest way and minimum footprint would be this, if there is already a yml configuration doesn't exists, actually in code there is no direction of yml conf. So this approach would be min. footprint.
try {
$this->loginManager->logInUser($this->firewallName, $event->getUser(), $event->getResponse());
$eventDispatcher->dispatch(FOSUserEvents::SECURITY_IMPLICIT_LOGIN, new UserEvent($event->getUser(), $event->getRequest()));
} catch (AccountStatusException $ex) {
// We simply do not authenticate users which do not pass the user
// checker (not enabled, expired, etc.).
}
EDIT: This technique works on Symfony 3.3, I'm unaware if this works on lower versions.
The correct way of doing this is by creating a Compiler Pass.
You can also: Override the service by adding a new service using the same name: fos_user.listener.authentication on your app/config.yml file or on your bundle config file and adding your new class to it as I've done below and add this
Here is how to override the automatic logging when registering a new user using the compiler pass technique.
The Compiler Pass
namespace arpa3\UserBundle\DependencyInjection;
use arpa3\UserBundle\EventListener\AuthenticationListener;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class OverrideServiceCompilerPass implements CompilerPassInterface {
public function process(ContainerBuilder $container)
{
$definition = $container->getDefinition('fos_user.listener.authentication');
$definition->setClass(AuthenticationListener::class);
}
}
The Service Override
namespace arpa3\UserBundle\EventListener;
use FOS\UserBundle\Event\FilterUserResponseEvent;
use FOS\UserBundle\Event\UserEvent;
use FOS\UserBundle\FOSUserEvents;
use FOS\UserBundle\Security\LoginManagerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Core\Exception\AccountStatusException;
class AuthenticationListener implements EventSubscriberInterface
{
/**
* #var LoginManagerInterface
*/
private $loginManager;
/**
* #var string
*/
private $firewallName;
/**
* AuthenticationListener constructor.
*
* #param LoginManagerInterface $loginManager
* #param string $firewallName
*/
public function __construct(LoginManagerInterface $loginManager, $firewallName)
{
$this->loginManager = $loginManager;
$this->firewallName = $firewallName;
}
/**
* {#inheritdoc}
*/
public static function getSubscribedEvents()
{
return array(
// You can disable any of them or all of them as you want
//FOSUserEvents::REGISTRATION_COMPLETED => 'authenticate',
//FOSUserEvents::REGISTRATION_CONFIRMED => 'authenticate',
//FOSUserEvents::RESETTING_RESET_COMPLETED => 'authenticate',
);
}
/**
* #param FilterUserResponseEvent $event
* #param string $eventName
* #param EventDispatcherInterface $eventDispatcher
*/
public function authenticate(FilterUserResponseEvent $event, $eventName, EventDispatcherInterface $eventDispatcher)
{
try {
$this->loginManager->logInUser($this->firewallName, $event->getUser(), $event->getResponse());
$eventDispatcher->dispatch(FOSUserEvents::SECURITY_IMPLICIT_LOGIN, new UserEvent($event->getUser(), $event->getRequest()));
} catch (AccountStatusException $ex) {
// We simply do not authenticate users which do not pass the user
// checker (not enabled, expired, etc.).
}
}
}
Register your Compiler Pass on your main bundle file
namespace arpa3\UserBundle;
use arpa3\UserBundle\DependencyInjection\OverrideServiceCompilerPass;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;
class arpa3UserBundle extends Bundle {
public function getParent () {
return 'FOSUserBundle';
}
/**
*
* This injects a Compiler Pass that is used to override the automatic login after registration of a new user
* We have done this in order to disable the "by default" behaviour given that only admins can register users
* and logging in into the newly created account automatically is just not a desired behaviour
*
* #param ContainerBuilder $container
*/
public function build ( ContainerBuilder $container ) {
parent ::build( $container );
$container -> addCompilerPass( new OverrideServiceCompilerPass() );
}
}
There are other ways such as overriding the authentication service on your config.yml but the solution above is the cleanest and most maintainable solution I have found.
You are almost there, as you said your listeners are called but the order is not correct, so you need to make your listener be executed before the default one
In order to do that change
FOSUserEvents::REGISTRATION_SUCCESS =>
'onRegistrationSuccess'
to
FOSUserEvents::REGISTRATION_SUCCESS =>
['onRegistrationSuccess',-10],
Notice the -10 there, this changes the priority of the listener.
class RegistrationSuccessEventListener implements EventSubscriberInterface{
private $router;
public function __construct(UrlGeneratorInterface $router){
$this->router = $router;
}
public static function getSubscribedEvents()
{
//this will be called before
return array(
FOSUserEvents::REGISTRATION_SUCCESS => ['onUserRegistrationSuccess', -30],
);
}
/**
* #param FormEvent $event
* When the user registration is completed redirect
* to the employee list page and avoid the automatic
* mail sending and user authentication that happends
*
*/
public function onUserRegistrationSuccess(FormEvent $event){
$url = $this->router->generate('employees_list');
$event->setResponse(new RedirectResponse($url));
}
}
I am using symfony 2.8 with the FOSBundle version
friendsofsymfony/user-bundle dev-master 1f97ccf Symfony FOSUserBundle
according to the output of composer info