I don't have experience in php. I've followed a few tutorials to modify my Drupal forms using the theme method in template.php.
For some reason the [#weight] property for a field does not adhere to its values. I'd like to move the Category field [cid] above Subject field [subject].
These are the lines of code I used:
$form['cid']['#weight'] = 0.003;
$form['subject']['#weight'] = 0.004;
When I print my array to view I see the values have changed, but when I render the form no changes are made. I have already cleared performance cache after every modification.
Here is a snippet of my printed array:
[subject] => Array
(
[#type] => textfield
[#title] => Subject
[#maxlength] => 255
[#required] => 1
[#post] => Array
(
)
[#programmed] =>
[#tree] =>
[#parents] => Array
(
[0] => subject
)
[#array_parents] => Array
(
[0] => subject
)
[#weight] => 0.004
[#processed] => 1
[#description] =>
[#attributes] => Array
(
)
[#input] => 1
[#size] => 60
[#autocomplete_path] =>
[#process] => Array
(
[0] => form_expand_ahah
)
[#name] => subject
[#id] => edit-subject
[#value] =>
[#defaults_loaded] => 1
[#sorted] => 1
)
[cid] => Array
(
[#type] => select
[#title] => Category
[#default_value] => 1
[#options] => Array
(
[1] => General Enquiries
[2] => Support
)
[#required] => 1
[#post] => Array
(
)
[#programmed] =>
[#tree] =>
[#parents] => Array
(
[0] => cid
)
[#array_parents] => Array
(
[0] => cid
)
[#weight] => 0.003
[#processed] => 1
[#description] =>
[#attributes] => Array
(
)
[#input] => 1
[#size] => 0
[#multiple] =>
[#process] => Array
(
[0] => form_expand_ahah
)
[#name] => cid
[#id] => edit-cid
[#value] => 1
[#defaults_loaded] => 1
[#sorted] => 1
)
You generally see that behavior on node forms when CCK is enabled. This is because CCK overrides weight for the default and CCK fields with its own ordering system configurable by going to Content management -> Content types -> [Content Type] -> Manage fields.
Of course, if you disable CCK (probably not an option), your #weight values will be respected.
One thing to note is that while Forms API allows decimals for #weight, it's good practice to use integers.
Edit
If you want to work within the constraints CCK presents, in a custom module you'll need to implement a #pre_render handler that will alter the weights of the form elements after CCK alters them:
function mymodule_form_alter(&$form, &$form_state, $form_id) {
$form['#pre_render'][] = 'mymodule_form_alter_weight';
}
function mymodule_form_alter_weight($elements) {
$elements['cid']['#weight'] = 0.003;
$elements['subject']['#weight'] = 0.004;
return $elements;
}
The problem is, you will have no idea what weight values CCK has used for other form elements, so while you'll have ordered cid before subject, both fields might show up in the middle of all the other fields on the page and not in their original position.
CCK forces you into a corner when it comes to weight. The correct, Drupal/CCK way to handle form ordering is to respect the choices made by an administrator on Content management -> Content types -> [Content type] -> Manage fields.
If you have a custom form element, you can tell CCK about it so it shows up in Manage fields by implementing hook_content_extra_fields(). Continuing with the code above:
function mymodule_content_extra_fields() {
$extras['mymodule'] = array( // Name of field
'label' => t('Mymodule'),
'description' => t('Mymodule field'),
'weight' => 10, // The default weight, can be overriden on Manage Fields
);
return $extras;
}
Related
I have a large JSON array which is the result of querying the API of an Icinga2 monitoring system.
I have used json_decode like this in my code to decode it:
$response = json_decode($array, true);
and I can see the output looks like this:
Array
(
[results] => Array
(
[0] => Array
(
[attrs] => Array
(
[__name] => HOSTNAME0
[acknowledgement] => 0
[acknowledgement_expiry] => 0
...
...
[state] => 0
[state_type] => 1
[meta] => Array
(
)
[name] => HOSTNAME0
[type] => Host
)
[1] => Array
(
[attrs] => Array
(
[__name] => HOSTNAME1
[acknowledgement] => 0
[acknowledgement_expiry] => 0
...
...
[state] => 0
[state_type] => 1
[meta] => Array
(
)
[name] => HOSTNAME1
[type] => Host
)
There are 400 Records in total and it's quite a complex structure but the only bits I am really interested in are the name and state fields.
Basically my script has a list of 150 hostnames from another source and what I want to do is for each hostname, search for it in the array and return the value of the state field for that host.
So far I've been struggling to do this without looping through the entire array for each of the 150 hostnames. There must be a more efficient way to do a lookup in the array based on a hostname and return a single value but I can't figure it out.
Given, the name field has no logical sorting inside the json result, there is no way to look at least once at each element. If they are sorted alphabetical, you could use a simple binary search, which would give you the result in O(log(n)).
The other thing is, if you have to search for multiple names, you could put them inside an name assiciated array. This way, you only have an initial overhead of O(n) building the list and each following search would return you the state on O(1).
// building the array
$states = [];
foreach ($items as $item) {
$states[$item['name']] = $item['state'];
}
looking for HOSTNAME1
$state = $states['HOSTNAME1'];
I'm hoping that I've got the source data array in the correct layout as the format was a bit confusing from the original question. But the main idea is to use array_column to extract the "attrs" and key the result by the "name" element of this array.
$response = Array(
"results" => Array(
0 => Array(
"attrs" => Array(
"__name" => "HOSTNAME0",
"acknowledgement" => 0,
"acknowledgement_expiry" => 0,
"state" => 0,
"state_type" => 1
),
"name" => "HOSTNAME0",
"type" => "Host"
),
1 => Array(
"attrs" => Array(
"__name" => "HOSTNAME1",
"acknowledgement" => 0,
"acknowledgement_expiry" => 0,
"state" => 2,
"state_type" => 1
),
"name" => "HOSTNAME1",
"type" => "Host1"
)
)
);
$extract = array_column($response["results"], "attrs", "name");
print_r($extract);
With the sample data, this gives...
Array
(
[HOSTNAME0] => Array
(
[__name] => HOSTNAME0
[acknowledgement] => 0
[acknowledgement_expiry] => 0
[state] => 0
[state_type] => 1
)
[HOSTNAME1] => Array
(
[__name] => HOSTNAME1
[acknowledgement] => 0
[acknowledgement_expiry] => 0
[state] => 2
[state_type] => 1
)
)
So to find any server by name, you'd use
echo "HOSTNAME1=".$extract["HOSTNAME1"]["state"].PHP_EOL;
If you only wanted the state field (as you asked for) and wanted to simplify the array, you can then use...
array_walk($extract, function(&$data) {$data=$data["state"];});
print_r($extract);
The array_walk() goes through the array and just copies the state field to be the entry, so the result of this is...
Array
(
[HOSTNAME0] => 0
[HOSTNAME1] => 2
)
So now you just do...
echo "HOSTNAME1=".$extract["HOSTNAME1"].PHP_EOL;
I am trying to change the sorting of shipping prices to ASC in opencart "Estimate Shipping & Taxes" section of the cart.
I changed the sort of the array in php using "uasort", and everything looks good, but when the json loop prints it out in the tpl, its in the wrong order again.
/catalog/controller/total/shipping.php
When printing the php array it is in the right order:
Array
(
[free] => Array
(
[title] => Free Shipping
[quote] => Array
(
[free] => Array
(
[code] => free.free
[title] => Free Shipping
[cost] => 0
[tax_class_id] => 0
[text] => $0.00
)
)
[sort_order] => 1
[error] =>
)
[usps] => Array
(
[title] => United States Postal Service
[quote] => Array
(
[00] => Array
(
[code] => usps.00
[title] => First-Class Mail Parcel
[cost] => 4.54
[tax_class_id] => 0
[text] => $4.54
)
[4] => Array
(
[code] => usps.4
[title] => USPS Retail Ground
[cost] => 7.29
[tax_class_id] => 0
[text] => $7.29
)
[1] => Array
(
[code] => usps.1
[title] => Priority Mail 2-Day
[cost] => 7.3
[tax_class_id] => 0
[text] => $7.30
)
)
[sort_order] => 2
[error] =>
)
)
Prints out correctly in json
{"shipping_method":{"free":{"title":"Free Shipping","quote":{"free":{"code":"free.free","title":"Free Shipping","cost":0,"tax_class_id":0,"text":"$0.00"}},"sort_order":"3","error":false},"usps":{"title":"United States Postal Service","quote":{"00":{"code":"usps.00","title":"First-Class Mail Parcel","cost":4.54,"tax_class_id":"0","text":"$4.54"},"4":{"code":"usps.4","title":"USPS Retail Ground","cost":7.29,"tax_class_id":"0","text":"$7.29"},"1":{"code":"usps.1","title":"Priority Mail 2-Day","cost":7.3,"tax_class_id":"0","text":"$7.30"}},"sort_order":"2","error":false}}}
However end result shows them in the original order
/catalog/view/theme/default/template/total/shipping.tpl
Please select the preferred shipping method to use on this order.
Free Shipping
Free Shipping - $0.00
United States Postal Service
Priority Mail 2-Day - $7.30
USPS Retail Ground - $7.29
First-Class Mail Parcel - $4.54
Maybe I am just tired but seems I am missing something obvious.
What am I doing wrong.
The original files can be viewed here...
https://github.com/opencart/opencart/blob/master/upload/catalog/controller/total/shipping.php
https://github.com/opencart/opencart/blob/master/upload/catalog/view/theme/default/template/total/shipping.tpl
The reason it did not work was because it also stores the array in the session and the form field must match the original shipment type key (i.e. "usps.00" with [00]).
<input type="radio" name="shipping_method" value="usps.00">
Im just guessing though; I didnt look very thorough.
Because I do not know know to resort multidimensional arrays in json I duplicated the "$quote_data" array in "/catalog/controller/total/shipping.php", sorting cost with usaort() then using array_values(), so the original keys are stored in the session and json get the modified keys.
If you want to use my hacky method here it is...
REPLACE
<?php
// START EDIT - FUNCTION TO RESORT BY PRICE/COST
function sortbycost ($a, $b) {
return $a['cost'] > $b['cost'];
}
//END EDIT
class ControllerTotalShipping extends Controller {
REPLACE
if ($quote) {
// START EDIT - FUNCTION TO SORT BY QUOTE (PRICE/COST)
uasort($quote['quote'], 'sortbycost');
// ADDED SECOND ARRAY (DUPLICATE ARRAY TO BE STORED IN SESSION)
$quote_data2[$result['code']] = array(
'title' => $quote['title'],
'quote' => $quote['quote'],
'sort_order' => $quote['sort_order'],
'error' => $quote['error']
);
// REPLACE KEYS FOR JS LOOP
$quote['quote'] = array_values($quote['quote']);
// END EDIT
$quote_data[$result['code']] = array(
'title' => $quote['title'],
'quote' => $quote['quote'],
'sort_order' => $quote['sort_order'],
'error' => $quote['error']
);
}
REPLACE
//$this->session->data['shipping_methods'] = $quote_data; // ORIGINAL
$this->session->data['shipping_methods'] = $quote_data2; // EDIT - KEEP DUPLICATE ARRAY WITH THE ORIGINAL KEYS IN SESSION
REPLACE
//$json['shipping_method'] = $this->session->data['shipping_methods']; // ORIGINAL
$json['shipping_method'] = $quote_data; // EDIT - USE THE NEW KEYS
It would probably be better to just re-sort the data in the javascript of the template file, but like I said, I do not know how to do that.
I have a scenario where i have templates which has many themes.
Clear enough that my relation will be template hasmany themes
I wanna show the template with number of themes and i am applying this code:
$this->Template->recursive = 2;
$this->Template->bindModel(
array(
'hasMany' =>array(
'TemplateTheme'=>array(
'className'=>'TemplateTheme',
'fields' => 'count(TemplateTheme.id) AS themes'
)
)
),false
);
$this->paginate = array(
'order' => array('Template.modified DESC'),
'limit' =>$limit
);
$template = $this->paginate('Template');
pr($template);die();
but i am getting is
Array
(
[0] => Array
(
[Template] => Array
(
[id] => 1
[name] => churchDesign
[status] => Active
[created] => 2011-10-24 10:37:23
[modified] => 2011-10-25 15:16:46
)
[TemplateTheme] => Array
(
[0] => Array
(
[template_id] => 1
[TemplateTheme] => Array
(
[0] => Array
(
[themes] => 3
)
)
)
)
)
[1] => Array
(
[Template] => Array
(
[id] => 2
[name] => blossoms
[status] => Active
[created] => 2011-10-19 00:00:00
[modified] => 2011-10-24 14:05:27
)
[TemplateTheme] => Array
(
)
)
)
The problem here is it is counting all the themes in first array(1st template).see [TemplateTheme] => Array
(
[0] => Array
(
[themes] => 3
)
the third theme actually belongs to the 2nd template.The relation is
template_id is in themes table to represent each theme for a template.
Template 1 has 2 themes and template 2nd has one theme and currently its showing all the three themes in the first template.
i want it like this way
templatename1
count of themes 1st template
template name 2
count of themes of 2nd template
Please tell me the right way to do this.
If you wanted to get the count of the Themes per each Template, the most elegant way is to use counterCache.
Another way is to use Containable behavior, which fetches only the related data which you wish, and then count the fetched data array in PHP.
After you attach the containable behavior to the Template model, some code like this should work:
$allTemplates = $this->Templates->find('all', array(
'contain' => 'TemplateTheme.name'
));
foreach($allTemplates as $template){
$currentThemeCount = count($template['TemplateTheme']);
foreach($template['TemplateTheme'] as $theme){
echo $theme['name'];
}
}
Hope this helps someone along the way.
you can count the themes array like
foreach($template as $t){
count($t['TemplateTheme'])
}
hope this helps
You should be using the find('count') method instead of using a virtual 'count' field like you're doing now. For example:
$allTemplates = $this->Template->find('all');
foreach($allTemplates as $data) {
$templateCount = $this->Template->TemplateTheme->find('count', array(
'conditions' => array(
'TemplateTheme.template_id' => $data['Template']['id']
)
));
// $templateCount should now contain the count of this specific template. Foreach will continue recursion for all others.
}
I am playing around with a quotes database relating to a ski trip I run. I am trying to list the quotes, but sort by the person who said the quote, and am struggling to get the paginate helper to let me do this.
I have four relevant tables.
quotes, trips, people and attendances. Attendances is essentially a join table for people and trips.
Relationships are as follows;
Attendance belongsTo Person hasMany Attendance
Attendance belongsTo Trip hasMany Attendance
Attendance hasMany Quote belongs to Attendance
In the QuotesController I use containable to retrieve the fields from Quote, along with the associated Attendance, and the fields from the Trip and Person associated with that Attendance.
function index() {
$this->Quote->recursive = 0;
$this->paginate['Quote'] = array(
'contain' => array('Attendance.Person', 'Attendance.Trip'));
$this->set('quotes', $this->paginate());
}
This seems to work fine, and in the view, I can echo out
foreach ($quotes as $quote) {
echo $quote['Attendance']['Person']['first_name'];
}
without any problem.
What I cannot get to work is accessing/using the same variable as a sort field in paginate
echo $this->Paginator->sort('Name', 'Attendance.Person.first_name');
or
echo $this->Paginator->sort('Location', 'Attendance.Trip.location');
Does not work. It appears to sort by something, but I'm not sure what.
The $quotes array I am passing looks like this;
Array
(
[0] => Array
(
[Quote] => Array
(
[id] => 1
[attendance_id] => 15
[quote_text] => Hello
)
[Attendance] => Array
(
[id] => 15
[person_id] => 2
[trip_id] => 7
[Person] => Array
(
[id] => 2
[first_name] => John
[last_name] => Smith
)
[Trip] => Array
(
[id] => 7
[location] => La Plagne
[year] => 2000
[modified] =>
)
)
)
I would be immensely grateful if someone could suggest how I might be able to sort by the the first_name of the Person associated with the Quote. I suspect my syntax is wrong, but I have not been able to find the answer. Is it not possible to sort by a second level association in this way?
I am pretty much brand new with cakephp so please be gentle.
Thanks very much in advance.
I've had the similar problem awhile back. Not with sort though. Try putting the associated table in another array.
echo $this->Paginator->sort('Name', 'Attendance.Person.first_name');
change to:
echo $this->Paginator->sort('Name', array('Attendance' => 'Person.first_name'));
Hope this helps
i'm also looking for help with this.
so far i've found that you can sort multi level associations in controller's pagination options after using the linkable plugin https://github.com/Terr/linkable.
but it breaks down when you try to sort form the paginator in the view. i'm using a controller for magazine clippings. each clipping belongs to an issue and each issue belongs to a publication.
$this->paginate = array(
"recursive"=>0,
"link"=>array("Issue"=>array("Publication")),
"order"=>array("Publication.name"=>"ASC",
"limit"=>10);
after debugging $this->Paginator->params->paging->Clipping in the view, you can see that the sort is described in two separate places, "defaults" and "options". the sort info needs to be present in both for it to work in the view.
here is after setting order in controller:
[Clipping] => Array
(
[page] => 1
[current] => 10
[count] => 6685
[prevPage] =>
[nextPage] => 1
[pageCount] => 669
[defaults] => Array
(
[limit] => 10
[step] => 1
[recursive] => 0
[link] => Array
(
[Issue] => Array
(
[0] => Publication
)
)
[order] => Array
(
[Publication.name] => ASC
)
[conditions] => Array
(
)
)
[options] => Array
(
[page] => 1
[limit] => 10
[recursive] => 0
[link] => Array
(
[Issue] => Array
(
[0] => Publication
)
)
[order] => Array
(
[Publication.name] => ASC
)
[conditions] => Array
(
)
)
)
and here is after using $this->Paginator->sort("Publication","Publication.name");.
notice the options array is empty.
[Clipping] => Array
(
[page] => 1
[current] => 10
[count] => 6685
[prevPage] =>
[nextPage] => 1
[pageCount] => 669
[defaults] => Array
(
[limit] => 10
[step] => 1
[recursive] => 0
[link] => Array
(
[Issue] => Array
(
[0] => Publication
)
)
[order] => Array
(
[Publication.name] => DESC
)
[conditions] => Array
(
)
)
[options] => Array
(
[page] => 1
[limit] => 10
[recursive] => 0
[link] => Array
(
[Issue] => Array
(
[0] => Publication
)
)
[order] => Array
(
)
[conditions] => Array
(
)
)
does one really need to modify the paginator class to make this work?
UPDATE:
i found out the problem:
in the core cake controller paginator merges default and options to create the find query.
but the options array is empty when using linkable to sort. because options is listed after default it overrides default and the empty array replaces the default array of options.
solution to this is extending the paginate function inside of app_controller.php and unsetting the options array order value if it is empty:
(line 1172 in cake/libs/controller/controller.php)
if(empty($options["order"])){
unset($options["order"]);
}
then the options will not be overwritten by thte blank array.
of course this should not be changed inside of controller.php, but put it in app_controller.php and move it to your app folder.
On CakePHP 3 this problem can be solved by adding 'sortWhitelist' params to $this->paginate on your controller.
$this->paginate = [
// ...
'sortWhitelist' => ['id', 'status', 'Attendance.Person.first_name']
];
And then in your view:
echo $this->Paginator->sort('Name', 'Attendance.Person.first_name');
This is noted in the docs:
This option is required when you want to sort on any associated data, or computed fields that may be part of your pagination query:
However that could be easily missed by tired eyes, so hope this helps someone out there!
I'm wondering what the cleanest way is to implement a cakephp form where 1 control is a multi-select and the rest are text fields or single-selects, and then the data is inserted as multiple rows with a saveall(). So for example a form is selected with these values:
textfield A
value=Foo
mulit-select B
values=US,Mexico,Canada
single=select C
value=10
and so I want to insert these rows into the database with a saveall():
Foo,US,10
Foo,Mexico,10
Foo,Canada,10
Now I know in the add view I can use this format for the input statement:
input('Model.0.field1',...)
but I'm wondering if I can mix that in that same form with inputs formatted like
input('Model.field2',....).
Update:
When I mix and match the single-select and multiple-select controls, the form data gets submitted like this:
Array
(
[Alert] => Array
(
[schedule_id] => 75
[user_id] => 6
[0] => Array
(
[frequency] => Array
(
[0] => WEEKLY
[1] => MONTHLY
)
)
[limit_value] => .03
[limit_adjustment] => 0
[type] => LIMIT
[disabled] => 0
)
)
I tried passing that data into saveall() but it treats it like a single record.
Update2: I think saveAll() requires that the multiple rows of data be formatted like this:
Array
(
[Article] => Array(
[0] => Array
(
[title] => title 1
)
[1] => Array
(
[title] => title 2
)
)
)
So it looks like after the submit I'm going to need some javascript code that will restructure the array.
I have something that works... I'm not sure if it takes full advantage of all of cake's "automagic" capabilities, but I don't think it's too convoluted.
So I just added the following code to my controller's add function:
if (!empty($this->data)) {
//debug($this->data, true);
/* begin custom code */
$multiselect = $this->data['Alert']['entity_id'];
$tmp2 = array();
foreach ($multiselect as $item)
{
$tmp = $this->data['Alert'];
$tmp['entity_id'] = $item;
array_push($tmp2,$tmp);
}
$this->data['Alert'] = $tmp2;
debug($this->data,true);
/* end custom code */
$this->Alert->create();
//restructure data
if ($this->Alert->saveAll($this->data['Alert'])) {
$this->Session->setFlash(__('The alert has been saved', true));
//$this->redirect(array('action' => 'index'));
} else {
$this->Session->setFlash(__('The alert could not be saved. Please, try again.', true));
}
and that converts my data to this:
Array
(
[Alert] => Array
(
[0] => Array
(
[schedule_id] => 74
[entity_id] => 1
[user_id] => 6
[frequency] => HOURLY
[limit_value] => .02
[limit_adjustment] => 0
[type] => LIMIT
[disabled] => 1
)
[1] => Array
(
[schedule_id] => 74
[entity_id] => 2
[user_id] => 6
[frequency] => HOURLY
[limit_value] => .02
[limit_adjustment] => 0
[type] => LIMIT
[disabled] => 1
)
)
)