I have a model called PageMetaData that contains a title and a description. This is to be tied to any other model and to be used as the title tag and meta description for the page.
So I have a model called Brand. Brand has a field called page_meta_data_id and Brand belongsTo PageMetaData
Now on the view for Brand I can run this code:
if(!empty($data['PageMetaData']['title']))
{
$this->set('title_for_layout', $data['PageMetaData']['title']);
}
else if(!empty($data['Brand']['name']))
{
$this->set('title_for_layout', $data['Brand']['name']);
}
if(!empty($data['PageMetaData']['description']))
{
echo $this->Html->meta('description', $data['PageMetaData']['description'],array('inline'=>false));
}
else if(!empty($data['Brand']['description']))
{
echo $this->Html->meta('description', $data['Brand']['description'],array('inline'=>false));
}
And if a PageMetaData has been associated to the current Brand and has a value for title, it will set that as the page title, otherwise if the brand has a field called name it will us that. Same for description.
The problem is I don't want to have to diplicate this code in every view for every model that uses PageMetaData.
I cannot figure out where I can abstract the code to, to avoid duplication.
I cannot put it in a Behavior or a Helper because you cannot set the title from either. I cannot put it in a Component because it cannot access the data found from the model.
Is there somewhere I can put this code for reuse?
You can possibly use elements for this. have a look at the cookbook link:
http://book.cakephp.org/view/1081/Elements
Place the method in your AppModel. I assume the method accepts an id for it to return the appropriate data.
Place another method in your AppController's beforeRender method. Pass the id to this method; which in turn will call the method in AppModel; setting title_for_layout, meta_description and keywords.
You should also not echo out these values, but rather pass them to the view and output them there (or in the layout).
AppController and AppModels are application-wide; so any controller/model may access the methods.
I'm sure there's other methods; and this might not work as I haven't tested it.
Related
I want to accomplish a NOT IN clause in CakePHP, but I am not able to achieve it. I've been reading the CakePHP Cookbook and some answers here in StackOverflow but it's not working for me.
I have a Table named 'Hotel' with all its model, controller and views. In the view template I built a CakePHP Cell where I want to show other Hotels, except for the hotel that is currently being viewed. If for example, I am showing the view of a hotel with id #5, I want to show other hotels options, except for that hotel view id number.
I have the following query in the Cell Controller:
$hotels = $this->Hotels->find('all')
->where(['Hotels.id NOT IN' => $current_hotel_id])
->limit(4)
->order('rand()')
->toArray();
I want $current_hotel_id to have the value of current hotel that it's being viewed. That's the approach I am taking.
Any useful information would be appreciated.
If you need some value that is available in your controller in cell, you must pass that value to it.
First, declare an argument in cell action:
class HotelsCell extends Cell{
public function hotels($current_hotel_id){
//your code here
}
}
Then, in your view, pass argument to cell:
$this->cell("Hotels::hotels",[$hotel->id])
More info in docs: Passing Arguments to a Cell
I am confused about the distinction and treatment of the index() and view() functions inside CakePHP 2.4.
Specifically, I am trying to modify a 3rd party API authored in CakePHP 2.4
Consider this Controller:
class EventsController extends AppController {
<snip>
public function index() {
if ($this->request->params['named']) {
$conditions = $this->request->params['named'];
} else {
$conditions = array();
}
}
This allows me to construct a URL like http://myserver/api/events/index/StartTime >=:12/EndTime <=15.json and the StartTime and EndTime get passed into conditions when I do a find.
That's great.
Now that same Controller has this additional function:
public function view($id) {
}
This function seems to be called when I invoke a url like http://myserver/api/events/1234.json
I am trying to extend view with more parameters, just like index. For example, I'd like to invoke:
http://myserver/api/events/1234/MonitorId =:3.json and MonitorId =:3 gets passed as a parameter to handle in view. I don't want to add these into the function definitions - it can be anything.
If I try and do this, I get a "Controller" not found, but this same approach works in index()
Can someone help me achieve my goal and help explain what is going on?
(IF it helps, the full code of the controller is here)
As I can see, you are using CakePHP RESTful routing routes.php
Here,
index is used for listing all the resources e.g. all blog posts. URL /events/index is getting mapped to index method in controller automagically.
view is used for showing a specific resource which can be identified by some unique identifier like id or uuid e.g. specific blog post. URL /events/<some_identifier> gets mapped to view method
What you need to do is modify view definition to read all query string parameters that you pass in URL and call Model accordingly. By default. routes parses the first string after events in URL to resource ID and passes it as first argument to view method in controller. This is scaffolded code.
You can use named parameters $this->request->params['named'] in your view definition and construct your queries. No hard-coding.
It turns out that I need to explicitly call the view action to pass it more parameters.
So api/events/view/218245/AlarmFrames >=: 80.json works as intended and then I can get the parameters with $conditions = $this->request->params['named'];
In my controller, I would like to set some values and have them exist (or live) throughout the different views in my app.
I read somewhere that I need to use beforeFilter function, but I am not sure if that is correct and how I go about doing that.
So in my controller I want to have
public function page1() {
$this->Model->setId('123');
}
public function page2() {
$this->Model->getId(); // would able to get the Id that was set from page1 function
$this->Model->setName('Bob');
}
public function page3() {
$this->Model->getId();
$this->Model->getName();
}
Let me know if you have questions.
To have this kind of "persistence" throughout the views, I guess the most simple approach is sessions.
Note that with the code you provided (I know is not a working example), you want the variable to be persistent inside a model, but that same model wont be maintained between actions of the same controller (or other controller for that matter).
You have to set that variable in sessions and retrieve it when you want to use it, or in the database and create methods in the model to get the last inserted id, for example.
So your code would be like
public function page1() {
$this->Session->write('id', '123');
}
public function page2() {
$this->Session->read('id');
$this->Session->write('name', 'Bob');
}
public function page3() {
$this->Session->read('id');
$this->Session->read('name');
}
Have a single model method that returns all your data in an array like
return array('varName' => $value, 'varName2' => $value2);
You can then call this model method from the controllers before filter and simply do
$this->set(ClassRegistry::init('MyModel')->commonViewVars());
If you would describe for what you think you need to do that I could give you a better advice like using requestAction() for example, this might be another option depending on what you're trying to do.
Depending on what data you want to pass to every page you should really consider to cache it. If its a menu for example cache it "forever" and just update the cache when the menu changes.
When I save data in one model, I'd like to create some data in another model and save that too. Because I can't do this using beforeSave(), I eventually decided to use afterSave() to create new data items in my second model. I'm not writing a blog application, but to use the blog analogy it's equivalent to automatically creating a series of comments for every blog post that is added and, when a post is edited, deleting all comments and re-adding new comments:
class Post extends AppModel {
function afterSave() {
ClassRegistry::init('Comments')->deleteAll(array('Post.id' => $this->id));
ClassRegistry::init('Comments')->saveAll($comments); // comments contains the comments to be added
}
}
This works fine, apart from the fact that the afterSave() function causes the redirection from my controller's add/edit actions (to /posts/index) to be overruled, and I get redirected back to the add/edit form instead (if I comment out the entire afterSave() method, the redirection works as intended).
If you're wondering why I didn't put the logic in the controller, I did originally, but I want it to work for both add and edit actions, and also for a "batch" add action I use to add multiple "posts" at once.
I guess I have two questions:
Is there a better way to achieve this
kind of result?
How can I make the redirection work?
Thanks for reading this far and if I haven't explained it clearly I hope you can use your imagination to see what I'm trying to do.
I think you need to include return true at the end of your afterSave() function. This seems like a decent approach if you don't want to put it in the controller. Although I would think about whether you will always want to add these comments (or whatever they are) after every save, even single-field updates.
In the (somewhat unlikely) event that someone else has the same problem, I eventually found that setting
'atomic' => false
in the saveAll() options solves the problem with the redirect. I have no idea why.
You can't do?:
<?php
function edit_post($id = null) {
if (!$id && empty($this->data)) {
// error...
}
if (!empty($this->data)) {
if ($this->Post->save($this->data)) {
ClassRegistry::init('Comments')->deleteAll(array('Post.id' => $this->Post->id));
ClassRegistry::init('Comments')->saveAll($comments); // comments contains the comments to be added
} else {
// error...
}
}
if (empty($this->data)) {
$this->data = $this->Post->read(null, $id);
}
}
?>
How can I get ID of current record, on Edit view layout?
You can load the Model in question and access it that way from within the view (which, in my opinion, is an awful idea) or you can 'set' it within your controller action:
// In the controller action that renders the view
$this->set('current_id',$this->ModelName->id);
// Access it this way in the view/layout:
<?php echo $current_id; ?>
You could also grab the current record ID using ajax, but that doesn't sound like something that would benefit you here.
Good luck
Use $this->data['ModelName']['id'] in the view
Since 2.3, as long as you have $this->Form->hidden('fieldname'); you can reliably use $this->Form->value('ModelName.fieldname') for the value of a field in the form. When the view is rendered, just as html input tags get a value based on the model used in the controller and referenced in the $this->Form-create('ModelName'), when rendering the $this->Form->value('ModelName.fieldname') has a value.
In the controller, $this->request->data['ModelName'] is set to an array of the Model's data in CakePhp, where ['id'] points to the value of the id field.
$this->Form->create('ModelName');
$this->Form->input('id');
$this->Form->end();
Corresponds to controller logic where $this->request->data['ModelName'] gets an associative array with at least "id" => some id value depending on the data read from the database.
Note that when you use $this->Form->value() to retrieve form data, it will work when the screen is initially rendered by the controller, but may fail when the screen is rendered again because a model validation rule that used a function in the model definition failed.
To make sure that the $this->Form->value() is reliable with more rigorous model validation logic, use $this->Form->hidden('fieldName'); to hold the data in $this->request->data['ModelName'] throughout multiple model validation iterations.