Ok, the case is the following:
There is a form that has a some drop downs and multiple selection fields.
All fine by now, except that I have to do the search by property type to be and/or and not only or as for the rest of the form (or at least I believe it is or for the rest).
i.e. property type 1 && property type 2 && (subtype 1 || sybtype 2 || sybtype 3)
Another thing is that when selecting all subtypes, the results shown should be the same as if none are selected.
I really have no idea where to read or look at for more detailed tutorial on the search functionality provided by this plugin in regards to "and" searches.
What I have in my model is the following:
public $filterArgs = array(
//array('name' => 'price', 'type' => 'query', 'method' => 'filterTitle'),
array('name' => 'province_id', 'type' => 'value'),
array('name' => 'city_id', 'type' => 'value'),
array('name' => 'quarter_id', 'type' => 'value'),
array('name' => 'refid', 'type' => 'value'),
array('name' => 'to', 'type' => 'value'),
array('name' => 'price1', 'name2' => 'price2', 'type' => 'expression', 'method' => 'priceRange', 'field' => 'Offer.price BETWEEN ? AND ?'),
array('name' => 'area1', 'name2' => 'area2', 'type' => 'expression', 'method' => 'areaRange', 'field' => 'Offer.area BETWEEN ? AND ?'),
array('name' => 'type_id', 'type' => 'query', 'method' => 'ManyOrConditions'),
array('name' => 'subtype_id', 'type' => 'query', 'method' => 'ManyOrConditions'),
array('name' => 'feature_id', 'type' => 'subquery', 'method' => 'findByTags1', 'field' => 'Offer.id'),
);
public function ManyOrConditions($data = array()) {
//debug($data);
$filter = $data['type_id'];
//not sure if this works
//$subtype = $data['subtype_id'];
$cond = array(
'OR' => array(
$this->alias . '.type_id ' => $filter,
//not sure if the below will work?
//$this->alias . '.subtype_id ' => $subtype
));
//debug($cond);
return $cond;
}
public function orConditions($data = array()) {
//debug($data);
$filter = $data['type_id'];
$cond = array(
'OR' => array(
$this->alias . '.type_id LIKE' => '%' . $filter . '%',
//$this->alias . '.body LIKE' => '%' . $filter . '%',
));
return $cond;
}
Where it as I understand it is creating only "or" searches for the type_id ... sorry guys really am lost in this.
I am new to cakephp and so far have been able to read through thousands of lines of code and do a lot of changes to this system but here I just have no clue what to do due to lack of documentation on this plugin :(
I will appreciate any guidance to tutorials and/or proper documentation - not this one https://github.com/CakeDC/search.
If this is the right one though, please let me know what I am missing.
P.S. If you need any other code here, just let me know and I will provide it.
AND conditions or any other customized query/condition would work exactly the same as orConditions. It is just a callback method in which you can do anything you want to build any kind of query you need.
See how "complex" conditions work: http://book.cakephp.org/2.0/en/models/retrieving-your-data.html#complex-find-conditions
array(
'OR' => array(
array('Company.name' => 'Future Holdings'),
array('Company.city' => 'CA')
),
'AND' => array(
array(
'OR' => array(
array('Company.status' => 'active'),
'NOT' => array(
array('Company.status' => array('inactive', 'suspended'))
)
)
)
)
)
Just create a custom method "myCustomAnd" and put whatever query you need in there using the $data that is passed to the method.
The plugin comes along with a complete example I don't think it is bad documented. It is pretty obvious what you need to do IMHO.
Thank you very much for your directions burzum. I finally understood it. Here is how I did it:
public function andTypeConditions($data = array()){
//The below two variables contain the data received by the submission of the form
$typeID = $data['type_id'];
$subTypeID = $data['subtype_id'];
//Define a variable $conditions to return. The conditions you can build according CakePHP manual for complex find conditions
if (isset($data['subtype_id'])) {
$conditions = array(
'Offer.type_id' => $typeID,
'Offer.subtype_id' => $subTypeID
);
} else {
$conditions = array(
'Offer.type_id' => $typeID
);
}
return $conditions;
}
For anyone that struggles with the same issue to clarify:
In order to do an "AND" search in cakeDC search plugin you need to create a method that you will later on assign to both (in my case two but if you have more , to all) fields like so:
public $filterArgs = array(
array('name' => 'type_id', 'type' => 'query', 'method' => 'andTypeConditions'),
array('name' => 'subtype_id', 'type' => 'query', 'method' => 'andTypeConditions'),
);
... the method itself contains the comments you need to be able to adjust it for your needs.
Here's what I ended up doing for a more complex search. Using the CakeDC Search plugin...this uses 'LIKE OR' instead of 'LIKE AND', but hopefully it sheds some light on the how to do more complex searches using the Search plugin.
User.search is the input element name in my submission form.
class User extends AppModel {
public $filterArgs = array(
'search' => array('type' => 'like', 'field' => array('User.name', 'User.email'))
}
}
class UsersController extends AppController {
public $components = array('Search.Prg');
public function index() {
$this->Prg->commonProcess();
$this->User->data['User'] = $this->passedArgs;
if ($this->User->Behaviors->loaded('Searchable')) {
$query = $this->passedArgs;
$parsedConditions = $this->User->parseCriteria($query);
} else {
$parsedConditions = array();
}
$this->Paginator->settings['User']['conditions'] = $parsedConditions;
$this->set('users', $this->Paginator->paginate());
}
}
Related
i'm using cakeDC's search plugin in my app. can;t seem to figure out why the results are empty if i pass no value in the username field i'm searching but have the account type filter set to either admin or user For the record, having the filter set to all with the username field empty will output all the users in the system trying to replicate the behavior of searches having the account type filter set to all with an empty username search field for having the account type filter set to either 2 user types
here's the relevant code if needed:
controller
public $components = array('Paginator', 'Session','Search.Prg');
public $presetVars = array(
array('field' => 'username', 'type' => 'value'),
array('field' => 'account_type', 'type' => 'value'));
public function admin_index() {
$this->Prg->commonProcess();
$this->paginate = array(
'conditions' => $this->User->parseCriteria($this->passedArgs));
$this->set('users', $this->Paginator->paginate(),'session',$this->Session);
model
public $actsAs = array('Search.Searchable');
public $filterArgs = array( array('name' => 'username', 'type' => 'query', 'method' => 'filterName'),
array('name' => 'account_type', 'type' => 'value'),
);
public function filterName($data, $field = null) {
$nameField = '%' . $data['username'] . '%';
return array(
'OR' => array(
$this->alias . '.username LIKE' => $nameField,
));
}
view search form
<div><?php echo $this->Form->create('User', array(
'novalidate' =>true,'url' => array_merge(array('action' => 'index','admin'=> true), $this->params['pass'])
)); ?>
<?php
$account_types = array(
'' => __('All', true),
0 => __('admin', true),
1 => __('user', true),);
echo $this->Form->input('username', array('div' => false, 'empty' => true)); // empty creates blank option.
echo $this->Form->input('account_type', array('label' => 'account_type', 'options' => $account_types));
echo $this->Form->submit(__('Search', true), array('div' => false));
echo $this->Form->end();
?></div>
Try to put 'empty' and allowEmpty in username and account_type like this:
public $filterArgs = array( array('name' => 'username', 'type' => 'query', 'method' => 'filterName', 'empty'=>true, 'allowEmpty'=>true),
array('name' => 'account_type', 'type' => 'value','empty'=>true, 'allowEmpty'=>true),
);
I had this kind of behavior as well from CakeDC/search and this two configs saved me, hope it will do for you as well.
It's a good idea to check the queries being executed as well using DebugKit.Toolbar.
I am trying to implement a search with CakeDC's Search plugin. In this search I have a field which has 'multiple' => 'checkbox' is set. This field allows user to select multiple cities so that they can filter results according to city/cities. What I did in favour to this, I simply specified the 'type' => 'IN' for that field in Searchable Model's $filterArgs. But noting happened it just responded with all result no searching/filtration happened. To get the clear picture of what I have implemented here are the code snippets:
Model.php
public $actsAs = array(
'Search.Searchable'
);
public $filterArgs = array(
'city' => array(
'type' => 'in',
'field' => 'Model.city'
));
search_form.ctp
echo $this->Form->create('Model', array('url' => array('controller' => 'models', 'action' => 'search')));
echo $this->Form->input('city', array(
'multiple' => 'checkbox',
'options' => array(
'city1' => 'city1',
'city2' => 'city2',
'cityn' => 'cityn')
));
echo $this->Form->end('search');
ModelsController.php
public function search() {
$this->layout = 'front_common';
$this->Prg->commonProcess();
$this->Paginator->settings = array(
'conditions' => $this->Model->parseCriteria($this->Prg->parsedParams()),
'limit' => 10
);
$this->set('Data', $this->Paginator->paginate());
}
also once I tried to use a beforeFilter() in ModelsController to implode the city array() with (,) to be used with IN but same all results. I want to ask if there is any other plugin to do this or any hack to do this with cakeDC's search plugin. Please help.
This should work fine, assuming you are passing in the arg to parseCriteria() as a simple array of values.
public $filterArgs = array(
array(
'name' => 'city',
'type' => 'value',
'field' => 'Model.city'
)
);
And you should be able to pass city:houston|dallas|austin in the URL
It should parse that into an array of args => [city => [houston, dallas, austin]]
And parseCriteria() will translate that into the following conditions fragment: [Model.city => [houston, dallas, austin]]
And CakePHP natively translates that into a SQL where fragment: IN(houston, dallas, austin)
Use DebugKit and monitor your SQL... you can debug() at any step of the process, you should get these values.
Here's the full docs on the Search plugin:
https://github.com/CakeDC/search/tree/master/Docs/Documentation
(I use it heavily, and I love how it lets me organize all search filters)
I am currently trying to figure out a way to implement cakeDC's search plugin within my application, but I am finding it quite difficult to understand the plumbing that needs to be done before I can get it to work(nicely) with my app.
Things to consider:
the search needs to be a 'live search'
Records retrieved need to be paginated
The search will be done using a selected criteria (id,name,etc the actual key not value)
and will require a user entry which we will call 'query' for now..
here is my code so far.
Model Code :
public $filterArgs = array(
'query' => array('type' => 'query', 'method' => 'filterQuery'),
);
public function filterQuery($data = array()) {
$filter = $data['query'];
$criteria = $data['criteria'];
if(empty($filter)){
return array();
}
$cond = array(
'OR' => array(
$this->alias . $criteria. 'LIKE' => '%' . $filter . '%',
//ie. criteria represents a field $ filter is the data to search/match
));
return $cond;
}
So what I am having trouble with is, how will my filterQuery method receive the $data argument.. Is it a normal request data ? I want to access both values submitted.
Here is the relevant code for the view:
<div id="search-container">
<?php
//echo $this->Form->create(false,array('type'=>'post','default'=>false));
echo $this->Form->input('criteria',array(
'label'=>'Search Criteria',
'options' => array(
'id'=> 'By ID',
'name' => 'By Name',
'blood_group_id' => 'By Blood Type',
'type' => 'By Donor Type',
'age' => 'By Age',
'gender' => 'By Gender'
)
));
?>
<?php echo $this->Form->input('query', array('type' => 'text', 'id' => 'query', 'name' => 'query', 'label' => false, 'placeholder' => 'Search')); ?>
[EDIT]
ofcourse in my controller I also have this setup
Search.Prg Component is loaded
public $presetVars = array(
'query' => array('type' => 'value'),
'criteria' => array('type' => 'value'),
);
Any help is appreciated, even if its just a link to a tutorial. Thanks
When I wrote the plugin a lot of useful examples I put directly into test cases of the plugin. So take a look into behavior test file to see how to use query type method.
As I understood from the cakephp-documentation, one of the advantages of the 'containable'-Behavior is being able to fetch fewer data if you need fewer data...
But that doesn't seem to work in my case of a connection between users and usergroups.
My associations look like:
Group
hasMany: Membership
User
hasMany: Membership
Membership
belongsTo: User, Group
(I'm not using HABTM, instead use the Model 'Membership' in between to join users and groups).
All Models implement the 'Containable'-Behavior.
Now I want to get all the members of a group with a certain id, only their ids and mail-addresses. My query is built like that:
$members = $this->Membership->find('all', array(
'conditions' => array(
'group_id' => $id
),
'contain' => array(
'User' => array(
'fields' => array('id', 'fullName')
),
)
));
But the resulting array looks like:
array(
(int) 0 => array(
'Membership' => array(
'id' => '1',
'group_id' => '1',
'user_id' => '1',
'role' => 'member'
),
'Group' => array(
'id' => '1',
'name' => 'Foo Group'
),
'User' => array(
'password' => '*****',
'id' => '1',
'name' => 'Dr. Foo'
'mail' => 'foo#bar.baz',
'role' => 'admin'
)
)
)
So there are definietely more fields fetched than I wanted to... (it's the same thing btw wenn I set the 'contain'-key to:
'contain' => array(
'User.fullName', 'User.id'
)
Am I using the containable-behavior wrong?
Your models don't seem to be acting containabl-y at all. Have you set your models to act as containable?
class Post extends AppModel {
public $actsAs = array('Containable');
}
If so, maybe the problem is with the recursion (to avoid getting the Group array with the query). Containable behavior should handle the recursion level on its own, but try setting it on the AppModel just to be sure
class AppModel extends Model {
public $actsAs = array('Containable');
public $recursive = -1;
Your first attempt
'contain' => array(
'User' => array(
'fields' => array('id', 'fullName')
),
)
looks good in terms of syntax, so it probably the actAs thing.
Also, for debugging also, try
$this->Membership->contain('User');
$this->Membership->find('all', array(
'conditions' => array(
'group_id' => $id
));
and see if you get the expected results that way.
I have been working with Yii for the past few months, but I hit a bit of a block:
In my base controller I have a property public $currentUserOrganisations = NULL; which on load populates it with all the logged in users organizations.
Now I have a page where I get all of the organizations and the user can connect to them, but the ones I already have must say "Connected" rather then have the ability to add. I am using the bootstrap TbGridView widget and in my Organisation model created a function getConnectionAction which either returns the anchor OR a label depending if the organization is already connected to the user.
Here is my problem: I can't find a way to access the already loaded user organizations in the Organisation model, because it is a property on my model class.
See below for code:
Action in controller
public function actionNew()
{
$connectionModel = Connection::model();
$organizationModel = Organisation::model();
$this->selectedSubnav = "Add";
$this->render('new', array("connectionModel" => $connectionModel, "organizationModel" => $organizationModel));
}
Here is the TbGridView in the view:
<?php
$this->widget(
'bootstrap.widgets.TbGridView',
array(
'type'=>'striped',
'enableSorting'=>true,
'id' => 'connection-rest-data',
'dataProvider' => $organizationModel->getConnectionsByOrganization($this->currentOrganisation->id),
'ajaxUpdate' => true,
'template'=>"{pager}<br>\n{items}\n{pager}",
'rowHtmlOptionsExpression' => '',
'emptyText' => Yii::t("site", "no_restults_found") . '.',
"itemsCssClass" => "table table-first-column-number data-table display full dataTable transaction_tbl",
'columns' => array(
array(
'header' => Yii::t("site", "id"),
'value' => '$data->id',
'type' => 'raw',
'name' => 'id'
),
array(
'header' => Yii::t("site", "from_unit"),
'type' => 'raw',
'value' => '$data->fromUnit["name"]',
'name' => 'fromUnit'
),
array(
'header' => Yii::t("site", "to_unit"),
'type' => 'raw',
'value' => '$data->toUnit["name"]',
'name' => 'type'
),
array(
'header' => Yii::t("site", "connection_type"),
'type' => 'raw',
'value' => '$data->type["name"]',
'name' => 'type'
),
array(
'header' => Yii::t("site", "ended"),
'type' => 'raw',
'value' => '$data->ended',
'name' => 'ended'
),
array(
'header' => Yii::t("site", "fees"),
'type' => 'raw',
'value' => 'empty($data->fees) ? "none" : $data->fees["fee"]',
'name' => 'fees'
),
array(
'header' => Yii::t("site", "actions"),
'type' => 'raw',
'value' => 'Organisation::model()->getConnectionAction($data->id, \'' . serialize($this->currentUserOrganisations) . '\'")',
'name' => 'fees'
)
),
)
);
?>
And here is the function in the Organisations Model:
public function getConnectionAction ($connectionId)
{
$currentOrganizations = null; //This I need to get the public $currentUserOrganisations = NULL; value
foreach ($currentOrganizations as $org)
{
if($org->id == $connectionId)
{
return CHtml::label(Yii::t("site", "connected"), "", array("style" => "color:green;"));
}
}
return CHtml::link("Add", "/connection/manage/addConnection?id=$connectionId");
}
Much appreciated guys and give me a shout if anything is unclear!
Ok so I managed a work around which is very close to what Martin said.
In the view for the specific field I do:
'value' => 'Organisation::model()->getConnectionAction($data->id, ' . $this->currentOrganisation->id . ')',
The id is the id of the listed organization and the currentOrganization->id is the id of the users current organization.
Then in my model I get all the organizations for the user:
$currentOrganizations = $this->findAllForUserByOrganizationId($currentOrgId);
Foreach through them and check if they match, full function below.
public function getConnectionAction ($organizationId, $currentOrgId)
{
$currentOrganizations = $this->findAllForUserByOrganizationId($currentOrgId);
foreach ($currentOrganizations as $org)
{
if(in_array($organizationId, array($org->fromUnit['id'], $org->toUnit['id'])))
{
return CHtml::label(Yii::t("site", "connected"), "", array("style" => "color:green;"));
}
}
return CHtml::link(Yii::t("site", "add_connection"), "/connection/manage/addConnection?id=$connectionId", array("class" => "createConnection"));
}
I think you are going to want to avoid Active Record for this one. You can use a CSqlDataProvider to load your TbGridView. Use SQL that looks something like this
select
org.organization_name,
user.name
from organization org
left outer join connection x on org.id = x.organization_id
join user on user.id = x.user_id
where user.id = ? or user.id is null
That ? is a parameter marker to allow you to pass in a user_id in a parameter array rather than just concatenating it. Concatenation provides an opening for SQL injection.
Then in your column list for the gid view test the user.name for null and if null display the add button. If not null, display connected.