I have build a notification widget in Yii framework which is called every page and gives the user notifications. Now I would like to update those notifications automatically every 10 seconds with Ajax. The structure is as follows:
<?php
class NotificationsWidget extends CWidget {
public function init(){
}
public function run() {
}
What is the best way to do this? I have searched everywhere, but I cannot seem to find the answer. Maybe I'm just looking with the wrong keywords.. If anyone has another (better) way to do this, please! Only restriction is that it has to be loaded into the interface layout and update at least every 10 seconds.
Thanks a lot:)
You setup an action in your controller and poll it every 10 seconds, if there is update it will return the notification from a partial view, if there is no update nothing is returned, this is a skeleton implementation to give you an idea, note it will not work as is.
In your layout file
...
// Your normal layout content
<?php Yii::app()->clientScript->registerScript("poll_ajax_notifications",
'function getNotification(){'.
CHtml::ajax(array(
'url'=>array("//notifications/update"),
'dataType'=>'html',
'type'=>'GET',
'update'=>'#divcontainingNotificationWidget',
)
) . '. }
timer = setTimeout("getNotification()", 10000);
', CClientScript::POS_END);
In your Notifications controller
class NotificationsController extends CController {
....
public function actionUpdate(){
$user = Yii::app()->user->id;
// Your logic logic for finding notifications
if($notificationPresent){ // or any validation to check whether to push data or not
$this->renderPartial('_notificationWidget',array('widgetData'=>$widgetData)); // pass data required by widget here
}
Yii::app()->end();
}
...
}
Finally create a partial view in views/notifications folder called _notificationsWidget.php
In your view place your widget call
<?php
$this->widget('path.to.my.widget',array(
//widget parameters
));
Related
Trying to learn events in Yii 2. I found a few resources. The link I got more attention is here.
How to use events in yii2?
In the first comment itself he explains with an example. Say for an instance we have 10 things to do after registration - events comes handy in that situation.
Calling that function is a big deal? The same thing is happening inside the model init method:
$this->on(self::EVENT_NEW_USER, [$this, 'sendMail']);
$this->on(self::EVENT_NEW_USER, [$this, 'notification']);
My question is what is the point of using events? How should I get full benefit of using them. Please note this question is purely a part of learning Yii 2. Please explain with an example. Thanks in advance.
I use triggering events for written (by default) events like before validation or before deletion. Here's an example why such things are good.
Imagine that you have some users. And some users (administrators, for example) can edit other users. But you want to make sure that specific rules are being followed (let's take this: Only main administrator can create new users and main administrator cannot be deleted). Then what you can do is use these written default events.
In User model (assuming User models holds all users) you can write init() and all additional methods you have defined in init():
public function init()
{
$this->on(self::EVENT_BEFORE_DELETE, [$this, 'deletionProcess']);
$this->on(self::EVENT_BEFORE_INSERT, [$this, 'insertionProcess']);
parent::init();
}
public function deletionProcess()
{
// Operations that are handled before deleting user, for example:
if ($this->id == 1) {
throw new HttpException('You cannot delete main administrator!');
}
}
public function insertionProcess()
{
// Operations that are handled before inserting new row, for example:
if (Yii::$app->user->identity->id != 1) {
throw new HttpException('Only the main administrator can create new users!');
}
}
Constants like self::EVENT_BEFORE_DELETE are already defined and, as the name suggests, this one is triggered before deleting a row.
Now in any controller we can write an example that triggers both events:
public function actionIndex()
{
$model = new User();
$model->scenario = User::SCENARIO_INSERT;
$model->name = "Paul";
$model->save(); // `EVENT_BEFORE_INSERT` will be triggered
$model2 = User::findOne(2);
$model2->delete(); // `EVENT_BEFORE_DELETE` will be trigerred
// Something else
}
I have function in ProductsController productsCount(). It give me amount of records in table.
public function productsCount() {
$productsAmount = $this->Products->find('all')->count();
$this->set(compact('productsAmount'));
$this->set('_serialize', ['productsAmount']);
}
I want to call this function in view of PageController. I want to simply show number of products in ctp file.
How can i do this?
You can use a view cell. These act as mini controllers that can be called into any view, regardless of controller.
Create src/View/Cell/productsCountCell.php and a template in src/Template/Cell/ProductsCount/display.ctp
In your src/View/Cell/productsCountCell.php
namespace App\View\Cell;
use Cake\View\Cell;
class productsCountCell extends Cell
{
public function display()
{
$this->loadModel('Products');
$productsAmount = $this->Products->find('all')->count();
$this->set(compact('productsAmount'));
$this->set('_serialize', ['productsAmount']);
}
}
In src/Template/Cell/ProductsCount/display.ctp lay it out how you want:
<div class="notification-icon">
There are <?= $productsAmount ?> products.
</div>
Now you can call the cell into any view like so:
$cell = $this->cell('productsCount');
I think it would make more sense to just find the product count in the PageController. So add something like $productsAmount = $this->Page->Products->find('all')->count(); in the view action of PageController, and set $productsAmount. If Page and Products aren't related, then you can keep the find call as is as long as you include a use for Products.
Also check this out for model naming conventions: http://book.cakephp.org/3.0/en/intro/conventions.html#model-and-database-conventions
Model names should be singular, so change Products to Product.
you can not call controller method from view page. you can create helper, which you can call from view page.
here you will get a proper documentation to creating helpers-
http://book.cakephp.org/3.0/en/views/helpers.html#creating-helpers
It just depend on the kind of call you're making because there are 3 cases for your issue..
1- If you're calling by a link to click you simply do:
<?= $this->Html->link(_('Product number'),['controller' =>'ProductsController', 'action' => 'productsCount']) ?>
The 2 other cases are whether you want to render the result straight in that same view, then there are some workaround to do.
1- first you will need to check what are the associations between the Page table and the product table and use BelongTo or hasMany option to bind them togheter for proper use.
2- If no association between the tables then you will nedd TableRegistry::get('Produts'); to pass data from a model to another, just like this way in the Pages controller:
public function initialize()
{
parent::initialize();
$this->Products = TableRegistry::get('Produts');
}
But i quite believe that the first option is more likely what you described.
Also you can define static method as below
public static function productsCount() {
return = $this->Products->find('all')->count();
}
And use self::productsCount() in other action.
This is useful only if you need to get count multiple time in controller. otherwise you can use it directly in action as below:
$this->Products->find('all')->count();
I'm a newbie to codeigniter and I'm attempting to write a function that would basically save a name and url to session data whenever you visited a certain page, then report it back in a small widget on the screen.
It's supposed to work as a kind of history function for what pages have been accessed and in what order. So far when working with test data it works great! however I'm trying to figure out how I can call the "add" function on each page that is visited, so we can supply the name and url of that page that was visited. Is there any way to do this? Or is there any way to report back a set of variables such as a name and url for a page after you visit it?
For example: say I visit page1, page2, page3, and page6 and I want each of those to show up in my history function. On each of those pages I would load the history view, and I would want to call the history's controller function "add(name,url)" and fill it in something like this:
add('page1','page1.php')
But I know that you're not supposed to access the controller from the history because that's not the way it's supposed to be done, however I cannot think of any better way to do this. Any help would be appreciated.
I don't know why dont you call this on every controller.
but if you want to call a function of the current controller, you have to get the instance of the current controller this way:
<?php
$CI =& get_instance();
$CI->method($param);
?>
the easiest way to do this would be to put a method in the constructor of your class. that way it will always run first thing, no matter what you are doing. remember that anything you can do in the controller -- sessions, validation, etc -- you can do in a model.
function __construct() {
parent::__construct();
// load model that has page tracker methods
$this->load->model( 'pagetracker_m' );
// call the method to track the pages, and have it return the results
if ( ! $this->history = $this->pagetracker_m->_trackpage(); ) {
echo 'Error getting history ' ; }
} // end construct
function something() {
// pass history to $data, then use echo $history->firstpage, etc in view
$data['history'] = $this->history ;
// call your view etc
}
I have built a simple Notification system in my Cake app that I want to have a function that will create a new notification when I call a certain method. Because this is not something the user would actually access directly and is only database logic I have put it in the Notification model like so:
class Notification extends AppModel
{
public $name = 'Notification';
public function createNotification($userId, $content, $url)
{
$this->create();
$this->request->data['Notification']['user_id'] = $userId;
$this->request->data['Notification']['content'] = $content;
$this->request->data['Notification']['url'] = $url;
$result = $this->save($this->request->data);
if ($result)
{
$this->saveField('datetime', date('Y-m-d H:i:s'));
$this->saveField('status', 0);
}
}
}
And then whenever I want to create a notification within my app I just do:
$this->Notification->createNotification($userId,'Test','Test');
However this doesn't work! The controller is talking to the model fine, but it doesn't create the row in the database... I'm not sure why... but it would seem I'm doing this wrong by just doing all the code in the model and then calling it across the app.
Edit: Based on answers and comments below, I have tried the following the code to create a protected method in my notifications controller:
protected function _createNotification($userId, $content, $url)
{
$this->Notification->create();
$this->request->data['Notification']['user_id'] = $userId;
$this->request->data['Notification']['content'] = $content;
$this->request->data['Notification']['url'] = $url;
$result = $this->save($this->request->data);
if ($result)
{
$this->saveField('datetime', date('Y-m-d H:i:s'));
$this->saveField('status', 0);
}
}
Now the thing that is stumping me still (apologies if this is quite simple to others, but I have not used protected methods in CakePHP before) is how do I then call this from another controller? So for example If have a method in my PostsController and want to create a notification on successful save, how would I do this?
I thought about in my PostsController add method:
if($this->save($this->request-data){
$this->Notification->_createNotification($userId,'Test','Test');
}
But being protected I wouldn't be able to access the method from outside of the NotificationsController. Also I'm using the same syntax as if I was calling a function from a model so again it doesn't feel right.
Hopefully someone can help me out and get me back on track as this is a new area to me.
the controller should pass all data to the model
$this->createNotification($this->request->data);
the model then can use the data:
public function createNotification(array $data) {
$key = $data[$this->alias]['key'];
$data[...] = ...;
$this->create();
return $this->save($data);
}
you never ever try to access the controller (and/or its request object) from within a model.
you can also invoke the method from other models, of course:
public function otherModelsMethod() {
$this->Notification = ClassRegistry::init('Notification');
$data = array(
'Notification' => array(...)
);
$this->Notification->createNotification($data);
}
and you can make your methods verbose, but that usually makes it harder to read/understand/maintain with more and more arguments:
public function createNotification($userId, $content, $url) {
$data = array();
// assign the vars to $data
$data['user_id'] = $userId;
...
$this->create();
return $this->save($data);
}
so this is often not the cake way..
Methods in a model are not "publicly accessible" by definition. A user cannot call or invoke a method in a model. A user can only cause a controller action to be initiated, never anything in the model. If you don't call your model method from any controller, it's never going to be invoked. So forget about the "non-public" part of the question.
Your problem is that you're working in the model as if you were in a controller. There is no request object in a model. You just pass a data array into the model method and save that array. No need for $this->request. Just make a regular array(), put the data that was passed by the controller in there and save it.
The whole approach is totally wrong in the MVC context IMO and screams for the use of the CakePHP event system. Because what you want is in fact trigger some kind of event. Read http://book.cakephp.org/2.0/en/core-libraries/events.html
Trigger an Event and attach a global event listener that will listen for this kind of events and execute whatever it should do (save something to db) when an event happens. It's clean, flexible and extendible.
If you did a proper MVC stack for your app most, if not all, events aka notifications should be fired from within a model like when a post was saved successfully for example.
This is what I have ended up doing. While it certainly isn't glamorous. It works for what I want it to do and is a nice quick win as the notifications are only used in a few methods so I'm not creating a large amount of code that needs improving in the future.
First to create a notification I do the following:
$notificationContent = '<strong>'.$user['User']['username'].'</strong> has requested to be friends with you.';
$notificationUrl = Router::url(array('controller'=>'friends','action'=>'requests'));
$this->Notification->createNotification($friendId,$notificationContent,$notificationUrl);
Here I pass the content I want and the URL where the user can do something, in this case see the friend request they have been notified about. The url can be null if it's an information only notification.
The createNotification function is in the model only and looks like:
public function createNotification($userId, $content, $url = null)
{
$this->saveField('user_id',$userId);
$this->saveField('content',$content);
$this->saveField('url',$url);
$this->saveField('datetime', date('Y-m-d H:i:s'));
$this->saveField('status', 0);
}
This creates a new record in the table with the passed content, sets its status to 0 (which means unread) and the date it was created. The notification is then set as read when a user visits the notifications page.
Again this is most probably not an ideal solution to the problem outlined in this question... but it works and is easy to work with And may prove useful to others who are learning CakePHP who want to run functions from models when building prototype apps.
Remember nothing to stop you improving things in the future!
First of all, you can improve your last solution to do one save() (instead of 5) the following way:
public function createNotification($userId, $content, $url = null){
$data = array(
'user_id' => $userId,
'content' => $content,
'url' => $url,
'datetime' => date('Y-m-d H:i:s'),
'status' => 0
);
$this->create();
$this->save($data);
}
When I began programming CakePHP(1.3) more than a year ago I also had this problem.
(I wanted to use a function of a controller in any other controller.)
Because I didn't know/researched where to place code like this I've done it wrong for over a year in a very big project. Because the project is really really big I decided to leave it that way. This is what i do:
I add a function (without a view, underscored) to the app_controller.php:
class AppController extends Controller {
//........begin of controller..... skipped here
function _doSomething(){
//don't forget to load the used model
$this->loadModel('Notification');
//do ur magic (save or delete or find ;) )
$tadaaa = $this->Notification->find('first');
//return something
return $tadaaa;
}
}
This way you can access the function from your Notification controller and your Posts controller with:
$this->_doSomething();
I use this kind of functions to do things that have nothing to do with data submittance or reading, so i decided to keep them in the app_controller. In my project these functions are used to submit e-mails to users for example.. or post user actions to facebook from different controllers.
Hope I could make someone happy with this ;) but if you're planning to make a lot of these functions, it would be much better to place them in the model!
Hi
I'm trying to figure the best way to execute a PHP script inside Joomla!
I want to redirect users if they are not from X country (already have a database and the script)
I found this extension but I want to make my own stuff http://bit.ly/daenzU
1) How could I execute the script once for each visitor? Working with Cookies?
2) As I'll put the script inside Joomla! template, I'll modify templates/XXX/index.php
3) Based on 2), Joomla! loads templates/XXX/index.php each time a page loads, so I have to avoid redirection script to be executed twice for an user
Thanks in advance for your ideas and suggestions
Just remember that, in Joomla 3.x, (according to the docs) in order to check an information about the user before the 'Login' event, you need to create you plugin in the 'authentication' context. That is, you need to have your plugin at 'root_to_joomla/plugins/authentication/myplugin/myplugin.php'.
Also, your plugin should be a class named PlgAuthenticationMyplugin, it shold extend the base plugin class 'JPlugin' and should have a public method named 'onUserAuthenticate'.
<?php
...
class PlgAuthenticationMyplugin extends JPlugin {
...
public function onUserAuthenticate($credentials, $options, &$response)
{
//your code here (check the users location or whatever)
}
....
If you want to do that after the Login event, your plugin should be at the user context, at root_to_joomla/plugins/user/myplugin/myplugin.php. And should have a public method 'onUserLogin'.
<?php
class PlgUserMyplugin extends JPLugin {
...
public function onUserLogin($user, $options)
{
//your test goes here
}
...
You can see all other User related events here.
DO NOT modify the template, this will do the trick but is not the right place.
I advise you to create a plug-in, see Joomla docs on plug-ins. Here is event execution order of events.
Create a system plugin and implement onAfterInitialise method. In that method add all of your code.
To prevent the execution of script twice for each user set the user state, see Joomla documentation for states. You can also use session $session = JFactory::getSession(), see documentation.
Here is code... for your plug-in.
// no direct access
defined( '_JEXEC' ) or die( 'Restricted access' );
jimport( 'joomla.plugin.plugin' );
class plgMyPlugin extends JPlugin {
//
public function __construct(){
// your code here
}
//
public function onAfterInitialise(){
$app = JFactory::getApplication();
//
$state = $app->getUserStateFromRequest( "plgMyPlugin.is_processed", 'is_processed', null );
if (!$state){
// your code here
// ....
// Set the Steate to prevent from execution
$app->setUserState( "plgMyPlugin.is_processed", 1 );
}
}
}