CakeDC Search and Tag plugin - search for multiple tags - cakephp

Hopefully I'm missing something simple here. I'm using the CakeDC Search and Tags plugin for my cake (2.3.4) app.
Along with generic search by field functionality I want the user to be able to search by tags. I've almost got this working but the search will only display results if you search for a single tag not multiples. For example, if I add an article with the following tags - black, white, red. The article will only show in the search results if I search for a single tag (say, black) and not all 3, or even 2... What am I missing?
Heres my code:
Article.php model
class Article extends AppModel {
public $actsAs = array(
'Upload.Upload' => array(
'screenshot1' => array (
'fields' => array (
'dir' => 'dir'
),
'thumbnailMethod' => 'php',
'thumbnailSizes' => array(
'xvga' => '1024x768',
'vga' => '640x480',
'thumb' => '80x80'
),
),
),
'Search.Searchable',
'Tags.Taggable'
);
// Search plugin filters
public $filterArgs = array(
'title' => array('type' => 'like'),
'environment' => array('type' => 'like'),
'description' => array('type' => 'like'),
'error' => array('type' => 'like'),
'cause' => array('type' => 'like'),
'resolution' => array('type' => 'like'),
'live' => array('type' => 'value'),
'synced' => array('type' => 'value'),
'tags' => array('type' => 'subquery', 'method' => 'findByTags', 'field' => 'Article.id'),
array('name' => 'search', 'type' => 'query', 'method' => 'filterQuery'),
);
// This is the OR query that runs when the user uses the client side signle field search
public function filterQuery($data = array()) {
if(empty($data['search'])) { // search is the name of my search field
return array();
}
$query = '%'.$data['search'].'%';
return array(
'OR' => array(
'Article.title LIKE' => $query,
'Article.description LIKE' => $query,
'Article.error LIKE' => $query,
)
);
}
// Find by tags method
public function findByTags($data = array()) {
$this->Tagged->Behaviors->attach('Containable', array('autoFields' => false));
$this->Tagged->Behaviors->attach('Search.Searchable');
$query = $this->Tagged->getQuery('all', array(
'conditions' => array('Tag.name' => $data['tags']),
'fields' => array('foreign_key'),
'contain' => array('Tag')
));
return $query;
}
public $hasAndBelongsToMany = array(
'Category' => array(
'className' => 'Category',
'joinTable' => 'articles_categories',
'foreignKey' => 'article_id',
'associationForeignKey' => 'category_id',
'unique' => 'keepExisting',
'conditions' => '',
'fields' => '',
'order' => '',
'limit' => '',
'offset' => '',
'finderQuery' => '',
'deleteQuery' => '',
'insertQuery' => ''
),
'Tag' => array('with' => 'Tagged')
);
Controller method
public function admin_advancedSearch() {
// Disable validation for this action
$this->Article->validate = array();
// For search plugin
$this->Prg->commonProcess();
// Set searched for details
$this->set('searchedFor', $this->passedArgs);
// If passed args are all empty
if ($this->passedArgs) {
if (empty($this->passedArgs['title']) AND
empty($this->passedArgs['environment']) AND
empty($this->passedArgs['description']) AND
empty($this->passedArgs['error']) AND
empty($this->passedArgs['cause']) AND
empty($this->passedArgs['resolution']) AND
empty($this->passedArgs['live']) AND
empty($this->passedArgs['synced']) AND
empty($this->passedArgs['tags'])) {
$this->Session->setFlash('Please enter at least one search criteria', 'flash_failure');
// Set this var for checks in view
$this->set('emptySeach', true);
} else {
$paginateConditions = $this->Article->parseCriteria($this->passedArgs);
$this->paginate = array('conditions' => array(
$paginateConditions),
'limit' => 10,
'order' => array('Article.modified' => 'DESC'));
$this->set('articles', $this->paginate());
// Count number of results
$count = 0;
foreach ($this->paginate() as $result) {
$count++;
}
$this->set('resultsCount', $count);
// Search was not empty - set flag for view
$this->set('emptySeach', false);
}
}
// Set layout
$this->layout = 'admin';
//debug($this->passedArgs);
}
All plugins are loaded successfully from bootstrap, searches work fine without tags. Searches with tags only work if one tag is entered....
*EDIT *
If I debug $query from the findByTags method I get this:
'SELECT `Tagged`.`foreign_key` FROM `knowledgebase`.`tagged` AS `Tagged` LEFT JOIN `knowledgebase`.`tags` AS `Tag` ON (`Tagged`.`tag_id` = `Tag`.`id`) WHERE `Tag`.`name` = 'kia, rio, red''
So it looks like its trying to find a single tag with all the searched for tags in its name. How can I make the WHERE part an IN?
For example:
WHERE `Tag`.`name` IN ('kia', 'rio', 'red')
Thanks

Use the "method" https://github.com/cakedc/search#behavior-and-model-configuration key her to pass the search args to a customized method.
The example here shows you exactly how this works https://github.com/cakedc/search#full-example-for-modelcontroller-configuration-with-overriding
public $filterArgs = array(
'some_related_table_id' => array('type' => 'value'),
'search'=> array('type' => 'like', 'encode' => true, 'before' => false, 'after' => false, 'field' => array('ThisModel.name', 'OtherModel.name')),
'name'=> array('type' => 'query', 'method' => 'searchNameCondition')
);
public function searchNameCondition($data = array()) {
$filter = $data['name'];
$cond = array(
'OR' => array(
$this->alias . '.name LIKE' => '' . $this->formatLike($filter) . '',
$this->alias . '.invoice_number LIKE' => '' . $this->formatLike($filter) . '',
));
return $cond;
}
Inside your custom method explode() the tags and make them an array so that CakePHP is using them as IN() or better (in can become slow) make it a chain of AND or OR.

Related

cakephp find all and afterFind method

I have a model that has a belongsTo variable, find and a afterFind all in play. The problem is, when the afterFind is commented out find works fine, but once its in play the find only returns "true", not an array of objects. If anyone has ran into this issue please inform me thx!
Model:
public $belongsTo = array(
'User' => array(
'className' => 'User',
'foreignKey' => 'user_id',
'conditions' => '',
'fields' => '',
'order' => ''
),
'ContactCategory' => array(
'className' => 'ContactCategory',
'foreignKey' => 'contact_category_id',
'conditions' => '',
'fields' => '',
'order' => ''
),
'State' => array(
'className' => 'State',
'foreignKey' => 'state_id',
'conditions' => '',
'fields' => '',
'order' => ''
)
);
public function getContact($id)
{
if ($id) {
$contact = $this->find('first', array('conditions' => array('Contact.id' => $id), 'fields' => array('id', 'image')));
return $contact;
} else {
return false;
}
}
public function afterFind($results = array(), $primary = false)
{
foreach ($results as $key => &$val) {
if (isset($val['Contact'])) {
$this->decryptFields($val['Contact'], $this->fieldsToEncrypt());
}
}
}

CakeDc User $hasMany not returning Associated model

I am using the CakeDc Users plugin 2.0 and on the plugins admin_index view I would like to see the associated Books for the users.
I have added $hasMany to User.php Model
public $hasMany = array(
'Book' => array(
'className' => 'Book',
'foreignKey' => 'user_id',
'dependent' => true,
'conditions' => '',
'fields' => '',
'order' => 'order',
'limit' => '',
'offset' => '',
'exclusive' => '',
'finderQuery' => '',
'counterQuery' => ''
)
);
And in the UserController.php I added 'contain'
protected function _setupAdminPagination() {
$this->Paginator->settings = array(
'limit' => 20,
'order' => array(
$this->modelClass . '.created' => 'desc'),
'contain' => array('Book')
);
}
public function admin_index() {
$this->Prg->commonProcess();
unset($this->{$this->modelClass}->validate['username']);
unset($this->{$this->modelClass}->validate['email']);
$this->{$this->modelClass}->data[$this->modelClass] = $this->passedArgs;
if ($this->{$this->modelClass}->Behaviors->loaded('Searchable')) {
$parsedConditions = $this->{$this->modelClass}->parseCriteria($this->passedArgs);
} else {
$parsedConditions = array();
}
$this->_setupAdminPagination();
$this->Paginator->settings[$this->modelClass]['conditions'] = $parsedConditions;
$this->set('usersList', $this->Paginator->paginate());
$this->layout = 'btst';
}
However I do not get the associated books in the view, I still just the array of users. This should work?
UPDATE:
debug ($this->Paginator->settings);
Output
array(
'limit' => (int) 20,
'order' => array(
'User.created' => 'desc'
),
'contain' => array(
(int) 0 => 'Book'
),
'User' => array(
'conditions' => array()
)
)
Debug $this->Paginator->settings before calling $this->Paginator and try
'contain' => array('Book')
However it is bad practice to modify plugins directly, you won't be able to update them anymore without trouble (merge conflicts, API changes...) in the worst case. The plugins documentation comes with a lot documentation that explains how to extend the plugin instead of modinfy it directly.
Edit: This was actually a bug in the plugin, fixed it in https://github.com/cakedc/users/commit/73aa350

Why cakephp image uploader plugin isnot working?

I am trying to upload image during ajax call. For this, I used a plugin for thumb nail creation.But Its not inserting any data and thumbnail also.
I am giving my plugin link and also controller code and model :
https://github.com/josegonzalez/cakephp-upload
$club_id = $this->request->data['Photo']['club_id'];
$id = $this->Auth->user();
$this->request->data['Photo']['user_id'] = $id["id"];
$this->request->data['Photo']['club_id'] = $club_id;
$this->request->data['Photo']['photo_type'] = "club";
$this->request->data['Photo']['profile_picture'] = "no";
$this->Photo->save($this->request->data);
AND MODEL CODE :
public $actsAs = array(
'Upload.Upload' => array(
'picture' => array(
'path' => '{ROOT}webroot{DS}img{DS}{model}{DS}{field}{DS}',
'fields' => array(
'dir' => 'picture_dir'
),
'thumbnailSizes' => array(
'thumb' => '100x100'
), 'thumbnailMethod' => 'php'
)
)
);
My view file is :
$this->Form->create($id, array('type' => 'file', "id" => "multiform"));
$this->Form->input('picture', array('type' => 'file'));
$this->Form->button('Upload', array('type' => 'submit', "id" => "club_upload"));
$this->Form->end();
Its not creating thumbnail and data in database. I am stuck on it, any idea would help me.
This should work:
public $actsAs = array(
'Upload.Upload' => array(
'path' => '{ROOT}webroot{DS}img{DS}{model}{DS}{field}{DS}',
'fields' => array(
'dir' => 'picture_dir'
),
'thumbnailSizes' => array(
'thumb' => '100x100'
),
'thumbnailMethod' => 'php',
'deleteOnUpdate' => true
)
);
EDIT:
In view file:
$this->Form->create('Photo', array('type' => 'file', "id" => "multiform"));
Passing id as form name is bad idea.
Now you can access your data like this:
$this->request->data['Photo']['picture']
You may have add form for club_id field.

pagination weird behavior with search filter in cakephp

i wish to have a search form with one textbox, 2 dropdowns and a radio button (in cakephp). But this is to be done with pagination. Search and pagination work fine separately but not together. when i select the filter criteria and click on search button, the results are displyed fine on 1st page, but if i click on any of the pagination links, the filter criteria are lost and pagination data is displayed without any filter. How do i solve this?
controller code:
private function _index() {
$this->genre_list();
$this->language_list();
$conditions = array();
debug($this->postConditions($this->data)); die;
if (!empty($this->request->data['Artist'])) {
foreach ($this->request->data['Artist'] as $name => $record) {
if (isset($record) && !empty($record)) {
if ($name == 'name') {
$conditions = array(
$this->modelClass->User. 'User.name LIKE' => '%' . $record . '%',
);
} else {
$conditions[$this->modelClass . '.' . $name] = $record;
}
}
}
};
$this->paginate = array(
'contain' => array (
'User' => array(
'City',// => array('name'),
'State',// => array('name'),
'Country',// => array('name'),
),
'Genre',// => array('name'),
'Language',// => array('language'),
),
'limit' => 3,
'conditions' => $conditions
);
$data = $this->paginate($this->modelClass);
//~ debug($data);
$this->set(compact('data'));
}
view:
<?php
echo $this->Form->create('Search', array(
'type' => 'file',
'inputDefaults' => array(
'format' => array(
'label', 'between', 'input', 'error', 'after'
)
),
'class' => 'form-horizontal'
));
echo $this->Form->input('Artist.name', array(
'div' => 'control-group',
'label' => array(
'class' => 'control-label',
'text' => 'Artist Name'
),
'between' => '<div class="controls">',
'after' => '</div>',
'placeholder' => 'Name',
'error' => array(
'attributes' => array(
'wrap' => 'div',
'style' => 'color:#B94A48'
)
)
));
echo $this->Form->input('Artist.genre_id', array(
'div' => 'control-group',
'empty' => 'All',
'label' => array(
'class' => 'control-label',
'text' => 'Genre'
),
'between' => '<div class="controls">',
'after' => '</div>',
'error' => array(
'attributes' => array(
'wrap' => 'div',
'style' => 'color:#B94A48'
)
)
));
echo $this->Form->input('Artist.language_id', array(
'div' => 'control-group',
'empty' => 'All',
'label' => array(
'class' => 'control-label',
'text' => 'Select Lanuage'
),
'between' => '<div class="controls">',
'after' => '</div>',
'error' => array(
'attributes' => array(
'wrap' => 'div',
'style' => 'color:#B94A48'
)
)
));
?>
<?php echo $this->element('pagination');
edited code with session
private function _index() {
$this->genre_list();
$this->language_list();
$conditions = array();
//~ foreach($this->request->params['named'] as $key => $record) {
//~ debug($this->request->params['named']);
//~ $this->request->data['Search'][$key] = $record;
//~ }
if (!empty($this->request->params['named']['page'])) {
// use session data for conditions
$conditions = (array)$this->Session->read('_indexConditions');
} else {
// delete session data
$this->Session->delete('_indexConditions');
}
$conditions = array();
if (!empty($this->request->data)) {
// new search! use the data to make conditions,
// like you did, and save the conditions
//~ if (!empty($this->request->data['Artist'])) {
foreach ($this->request->data['Search'] as $name => $record) {
if (isset($record) && !empty($record)) {
if ($name == 'name') {
$conditions = array(
$this->modelClass->User. 'User.name LIKE' => '%' . $record . '%',
);
} else {
$conditions[$this->modelClass . '.' . $name] = $record;
}
}
}
//~ }
$this->Session->write('_indexConditions', $conditions);
}
$this->paginate = array(
'contain' => array (
'User' => array(
'City',
'State',
'Country',
),
'Genre',
'Language',
),
'limit' => 3,
'conditions' => $conditions
);
$data = $this->paginate('Artist');
$this->set(compact('data'));
}
The answer is pretty simple: save the search conditions in the session or cookie and use those conditions if new ones aren't sent.
For simplicity's sake, I've omitted much of your code. Something like this should work.
private function _index() {
// check if this is a pagination request without data
if (!empty($this->request->params['named']['page']) {
// use session data for conditions
$conditions = (array)$this->Session->read('_indexConditions');
} else {
// delete session data
$this->Session->delete('_indexConditions');
}
$conditions = array();
if (!empty($this->request->data)) {
// new search! use the data to make conditions,
// like you did, and save the conditions
$this->Session->write('_indexConditions', $conditions);
}
$this->paginate = array(
'conditions' => $conditions
);
$data = $this->paginate($this->modelClass);
$this->set(compact('data'));
}
It should be wrapped in a component. On one of my projects I have a component that automatically does this and replaces the controller data for a more automated process.
got the solution without using session
in controller:
if (!empty($this->request->data) || $this->request->params['named']) {
foreach($this->request->params['named'] as $key => $record) {
if($key!= 'page')
$this->request->data['Search'][$key] = $record;
}
foreach ($this->request->data['Search'] as $name => $record) {
if (isset($record) && !empty($record)) {
$this->request->params['named'][$name] = $record;
if ($name == 'name') {
$conditions[$this->{$this->modelClass}->User. 'User.name LIKE'] = '%' . $record . '%';
} else {
$conditions[$this->modelClass . '.' . $name] = $record;
}
}
}
}
and in view provide the controller and action explicitly:
echo $this->Form->create('Search', array(
'type' => 'file',
'inputDefaults' => array(
'format' => array(
'label', 'between', 'input', 'error', 'after'
)
),
'url' => array(
'controller' => 'artists',
'action' => 'index',
),
'class' => 'form-horizontal'
));
solves the problem.
In 2.x, you can set your form to use GET instead of POST.
Change the type of your form from file to GET (BTW, why you use file??)
echo $this->Form->create('Search', array(
'type' => 'get',
And of course, instead of using the data array from Request
$this->request->data['Search']['name']
you will use the query array like this:
if (!empty($this->request->query['name'])) {

Cake php default bahaviour for Translate behaviour not working

Can anyone suggest me why when there are no translation for a field it is returned empty, and not with the default translation?
I will appreciate all suggestions...
Can`t tell what part of my code you want to see, because it is all straight forward from www.book.cake.org. So i pasting some code:
Model definition:
<?php
class Article extends AppModel
{
var $useTable = 'nc_articles';
var $name = 'Article';
var $actsAs = array(
'Translate' => array(
'title', 'content', 'meta_key_words','meta_description'
)
);
// Use a different model
var $translateModel = 'ArticlesI18n';
// Use a different table for translateModel
var $translateTable = 'nc_articles_i18ns';
var $belongsTo = array(
'Author' => array('className' => 'User',
'foreignKey' => 'author_id',
'conditions' => '',
'fields' => array(),
'order' => '',
'counterCache' => ''),
'Modifier' => array('className' => 'User',
'foreignKey' => 'modifier_id',
'conditions' => '',
'fields' => array(),
'order' => '',
'counterCache' => ''),
'Category' => array('className' => 'ArticlesCategory',
'foreignKey' => 'category_id',
'conditions' => '',
'fields' => array(),
'order' => '',
'counterCache' => ''),
'Layout' => array('className' => 'Layout',
'foreignKey' => 'layout_id',
'conditions' => '',
'fields' => array(),
'order' => '',
'counterCache' => ''),
);
var $hasMany = array(
'Comments' => array(
'className' => 'ArticlesComment',
'foreignKey' => 'article_id',
'conditions' => array(),
'order' => '',
'limit' => '',
//'dependent'=> true When dependent is set to true, recursive model deletion is possible. In this example, Comment records will be deleted when their associated User record has been deleted.
)
);
}
?>
And then the function where I am changing Language:
function setLanguage($languageCode='pol')
{
$this->Session->write('Config.language', $languageCode);
$this->redirect($this->referer());
}
Would you like to see something more?
Let me answer my own question. There were few more things I had to write more. First of all I had to declare my Config.language in core.php then for Every model which is translated I had to declare a local variable which is an array of language codes
$this->Article->array('en','pol');
And that's how I managed the problem. But now queries for translation are very long and I am facing an optimization problem.

Resources