I have worked on a custom shipping module to match my commerce module shipping needs. I have noticed that the module is slowing drupal a lot on a lot the pages even-though I am basically my module should only be hooking to drupal commerce checkout.
Here is my module code:
function myips_commerce_shipping_method_info() {
$shipping_methods = array();
$shipping_methods['IPS'] = array(
'title' => t('IPS'),
'description' => t('Quote rates from IPS'),
);
return array($shipping_methods);
}
function myips_commerce_shipping_service_info() {
$shipping_services = array();
$shipping_services['myips_shipping_service'] = array( //arbitrary name w/ 'service' in there
'title' => t('Custom Shipping Service'), //title for the interface
'description' => t('Variable rates based on the book origin '),
'display_title' => t('IPS Shipping'),
'shipping_method' => 'IPS',
'price_component' => 'shipping', //from commerce_shipping
'callbacks' => array(
'rate' => 'myips_service_rate_order'),
);
return $shipping_services;
}
function myips_service_rate_order($shipping_service, $order) {
$order_number=$order->order_number;
$currency_code='USD';
$shipping_total=getordershipmentvalue($order_number);
$rates['myips_shipping_service']=array(
'amount' => commerce_currency_decimal_to_amount($shipping_total, $currency_code),
'currency_code' =>$currency_code,
'data' => array(),
);
return $rates[$shipping_service['name']];
}
function getcustomershippingcountry($order_id) {
$order=commerce_order_load($order_id);
$profile_id=$order->commerce_customer_shipping['und']['0']['profile_id'];
$profile=commerce_customer_profile_load($profile_id);
$country= $profile->commerce_customer_address[LANGUAGE_NONE]['0']['country'];
return $country;
}
function getordershipmentvalue($order_id) {
$order=commerce_order_load($order_id);
$total=0;
foreach($order->commerce_line_items[LANGUAGE_NONE] as $line) {
$line_item_id=$line['line_item_id'];
$total=$total+getitemshipmentvalue($line_item_id);
}
return $total;
}
function getitemshipmentvalue($line_item_id){
$line_item=commerce_line_item_load($line_item_id);
$line_quantity=$line_item->quantity;
$order_id= $line_item->order_id;
$destination= getcustomershippingcountry($order_id);
$product_id=$line_item->commerce_product[LANGUAGE_NONE]['0']['product_id'];
$product=commerce_product_load($product_id);
$product_weight=$product->field_book_weight[LANGUAGE_NONE]['0']['weight'];
$product_origin=$product->field_book_origin[LANGUAGE_NONE]['0']['value'];
$line_total=getshippingrates($destination, $product_origin, $product_weight*$line_quantity);
return $line_total;
}
function calculateshippment($shipping_type, $zone, $product_weight) {
$query=new EntityFieldQuery();
$query->entityCondition('entity_type', 'node');
$query->entityCondition('bundle', $shipping_type);
$query->fieldCondition('field_up_to_weight', 'value',$product_weight,'>=');
$query->fieldCondition('field_zone_number', 'value',$zone);
$query->fieldorderby('field_up_to_weight','value','ASC');
$query->range(0,1);
$result=$query->execute();
if(!empty($result)){
$nodes=node_load_multiple(array_keys($result['node']));
foreach($nodes as $node) {
$price=$node->field_shipment_price[LANGUAGE_NONE][0]['value'];
}
}
return $price;
}
function getdestinationzone($destination, $shipping_type) {
$query=new EntityFieldQuery();
$query->entityCondition('entity_type', 'node');
$query->entityCondition('bundle', 'countries_zones');
$query->fieldCondition('field_country_abbreviation', 'value',$destination);
$result=$query->execute();
if(!empty($result)) {
$nodes=node_load_multiple(array_keys($result['node']));
foreach($nodes as $node) {
if($shipping_type=='out_us_zone_prices') {
$zone=$node->field_us_zone[LANGUAGE_NONE]['0']['value'];
}elseif($shipping_type=='mail_zone_prices') {
$zone=$node->field_mail_zone[LANGUAGE_NONE]['0']['value'];
}elseif($shipping_type=='dhl_zone_prices') {
$zone=$node->field_dhl_zone[LANGUAGE_NONE]['0']['value'];
}
}
}
return $zone;
}
function getshippingrates($destination, $product_origin, $product_weight) {
if($product_origin=='US') {
if($destination==$product_origin) {
$shipping_type='us_zone_prices';
}else {
$shipping_type='out_us_zone_prices';
}
/* End of Product Origin US */
}elseif($product_origin=='LB'){
if($destination==$product_origin) {
$shipping_type='mail_zone_prices';
}else {
$shipping_type='dhl_zone_prices';
}
}
$zone=getdestinationzone($destination,$shipping_type);
return calculateshippment($shipping_type, $zone, $product_weight);
}
Why would my module slowing drupal performance?
I think the main problem of your module is that your database queries are in a foreach-loop (in getordershipmentvalue())
Ok you maybe can't see it immediately, but if you look behind the getitemshipmentvalue()-call, you can see, that there are many other function calls there.
And over multiple corners the functions calculateshippment() and getdestinationzone() will be called. In these functions there are database-queries.
Thats the reason, why your module is so slow: Because over multiple corners you query the database in a loop. This is similar to a DOS-Attack on the database-server (A bigger problem with great arrays of "commerce_line_items").
The solution: Think about your code-design. Try to put your database-query-code outside of the foreach-loop.
I would also prefer to cache the data after the first request. (For example with variable_set())
Related
Is it possible to change default comments in wordpress admin?
I`d like to rename comments into testimonials everywhere in admin.
If you want to change all WordPress uses of 'Comment', then you need to hook into the translation filter
is_admin() && add_filter('gettext', function ($translation, $text, $domain) {
if (strpos($translation, 'comment') !== FALSE) {
return str_replace('comment', 'testimonial', $translation);
}
if (strpos($translation, 'Comment') !== FALSE) {
return str_replace('Comment', 'Testimonial', $translation);
}
return $translation;
}, 10, 3);
But that changes EVERYTHING that runs through gettext.
If you just wanted to change the admin section title you would do this:
add_action('admin_head', function () {
global $wp_meta_boxes;
if (!empty($wp_meta_boxes)) {
foreach ($wp_meta_boxes as $page => &$positions) {
foreach ($positions as $context => &$priorities) {
foreach ($priorities as $priority => &$boxes) {
foreach ($boxes as $id => &$box) {
if ($id === 'commentsdiv') {
$box['title'] = 'Testimonials';
break;
}
}
}
}
}
}
});
Beyond that, you'd have to find every instance of 'Comment' and see if there was an associated hook for that situation. I'm not sure if that is feasible.
I'm working on a Wordpress/Buddypress project and I would like to make the members list of group always visible whether or not the group is private and the logged member belong to the group.
I was thinking I had to change access and visibility of the nav items so I did that:
function change_access_group_nav_tabs() {
if(bp_is_group()) {
buddypress()->groups->nav->edit_nav( array('visibility' => 'public'), 'members', bp_current_item() );
buddypress()->groups->nav->edit_nav( array('access' => 'anyone'), 'members', bp_current_item() );
}
}
add_action( 'bp_actions', 'change_access_group_nav_tabs' );
But it didn't work…
Any suggestion how to proceed?
I found a workaround, I'am quite not completely satisfied but...
First of all, disable members list on group:
function change_access_group_nav_tabs() {
if(bp_is_group()) {
buddypress()->groups->nav->edit_nav( array( 'user_has_access' => false ), 'members', bp_current_item() );
}
}
add_action( 'bp_actions', 'change_access_group_nav_tabs' );`
(btw, setting the value to true actually make the the nav items always here, but we still can't access the group list on click)
And then I simply add a custom BP Group Extension to make my own members list:
class Group_Extension_List_Members extends BP_Group_Extension {
function __construct() {
$args = array(
'slug' => 'members-list',
'name' => 'Membres',
'access' => array( 'anyone'),
'show_tab' => array( 'anyone'),
'nav_item_position' => 12,
'screens' => array(
'create' => array(
'enabled' => false
),
'edit' => array(
'enabled' => false
),
)
);
parent::init( $args );
}
function display( $group_id = NULL ) {
//Remove user who do not belong to the group on members loop
function filter_for_groups( $members_template_has_members, $members_template, $r ) {
for ($i=sizeof($members_template->members)-1; $i >= 0 ; $i--) {
$user_id = $members_template->members[$i]->id;
if(!groups_is_user_member( $user_id, bp_get_group_id() )){
$members_template->member_count = $members_template->member_count-1;
array_splice($members_template->members, $i, 1);
}
}
if ($members_template->member_count <= 0) {
return '';
} else {
return $members_template_has_members;
}
};
add_filter( 'bp_has_members', 'filter_for_groups', 10, 3 );
require('/Your/theme/custom/members/loop/members-loop.php');
}
}
bp_register_group_extension( 'Group_Extension_List_Members' );
Hope it will help other in the future, And I'm still open to know the good way to proceed.
I wrote a custom views handler, that mark message private to 0 or 1 or 2; any is value of hers label [MARK_READ,ARK_NEW,...] :
function mydevel_views_data() {
$data['mydevel']['table']['group'] = t('mydevel');
$data['mydevel']['table']['join'] = array(
// Exist in all views.
'#global' => array(),
);
$data['mydevel']['mydevel_isnewmessage'] = array(
'title' => t('is new message field'),
'help' => t('is new message field.'),
'field' => array(
'handler' => 'mydevel_handler_field_isnewmessage',
'click sortable' => TRUE,
),
'filter' => array(
'handler' => 'mydevel_handler_filter_isnewmessage',
),
);
and wrote a filed handler that work properly; message_mark function is wrote on mydevel module file and work currectly; if the message is new that filed label row by "now":
class mydevel_handler_field_isnewmessage extends views_handler_field_numeric {
var $field_alias = 'mydevel_field_isnewmessage';
function query() {
}
function option_definition() {
$options = parent::option_definition();
//dsm($this);
return $options;
}
function options_form(&$form, &$form_state) {
parent::options_form($form, $form_state);
}
function get_value($values, $field = NULL) {
return intval(message_mark($values->mid, $values->message_timestamp));
}
function render($values) {
$value = $this->get_value($values);
$value_theme = theme('mark', array('type' => $value));
return $value_theme;
}
}
Now, i want to write a views filter handler that filter on that filed on numeric mode [0 or 1 or 2] or on check list mode [all, read, new, updated]. but I don't want to overwite query function on filter handler and want to use from value that returned by this common handler filed (mydevel_handler_filter_isnewmessage) that added to views filed. can wrote this idea by extend the standard views handler? what i do my dears? I wrote this but not work: this is return error
class mydevel_handler_filter_isnewmessage extends views_handler_filter_numeric {
var $always_multiple = TRUE;
function option_definition() {
$options = parent::option_definition();
return $options;
}
function operators() {
$operators = parent::operators();
return $operators;
}
function query() {
}
}
tank you a lot.
The code below is a custom report I'm putting together, using SilverStripe 3.1.
The Title and ClassName values are working fine, but though I can get the Status for each Page I'm not sure how to set the Status value against each Page in the DataList. How can I do that?
Once that's done, the Status column should be populated.
class PageListByType extends SS_Report {
function title() {
return 'Page List by Type';
}
function description() {
return 'List all the pages in the site, along with their page type';
}
public function sourceRecords($params = array(), $sort = null, $limit = null) {
$pages = Page::get()->sort($sort);
foreach ($pages as $pagenum=>$page) {
$flags = $page->getStatusFlags();
if ($flags) {
foreach ($flags as $status) {
// if (isset($pages[$pagenum]->Status)) die(array($pages[$pagenum]->Status, $status)); #detect multiple statuses; not sure if this will happen
/////////////////////////
// The following line needs fixing:
/////////////////////////
$pages[$pagenum]->Status = "{$status['text']} ({$status['title']})";
}
}
}
// die($pages->debug());
return $pages;
}
public function columns() {
return array(
'Title' => _t('PageListByTypeReport.PageName', 'Page name'),
'ClassName' => _t('PageListByTypeReport.ClassName', 'Page type'),
'Status' => _t('PageListByTypeReport.Status', 'Status')
);
}
}
Edit: Thanks #Turnerj for your answer! My final working code is as follows:
class PageListByType extends SS_Report {
function title() {
return 'Page List by Type';
}
function description() {
return 'List all the pages in the site, along with their page type';
}
public function sourceRecords($params = array(), $sort = null, $limit = null) {
$pages = DataObject::get("SiteTree", "", "");
return $pages;
}
public function columns() {
return array(
'Title' => _t('PageListByTypeReport.PageName', 'Page name'),
'ClassName' => _t('PageListByTypeReport.ClassName', 'Page type'),
'Status' => _t('PageListByTypeReport.Status', 'Status')
);
}
}
and in Page.php:
public function getStatus() {
$flags = $this->getStatusFlags();
$result = array();
if ($flags) {
foreach ($flags as $status) {
$result[] = "{$status['text']} ({$status['title']})";
}
} else {
$result[] = 'Published';
}
return implode(', ', $result);
}
After further investigation, I recreated the issue and found the solution.
Overall, my solution involves what I suggested in the comments about bringing the status fetching to the actual Page by adding a getStatus function.
I described essentially the following:
public function getStatus()
{
return $this->getStatusFlags();
}
This technically is correct, it will get the status flags but you are right, it doesn't display them in the report. This is due to this function returning an array which the report does not understand to render.
My solution is to change this function to return a string so with a few simple edits combining what you wrote with what I wrote, we get the following:
public function getStatus()
{
$flags = $this->getStatusFlags();
$result = array();
if ($flags)
{
foreach ($flags as $status)
{
$result[] = "{$status['text']} ({$status['title']})";
}
}
return implode(', ', $result);
}
I've got one unique twist on combining our code, I add each status to an array and implode it back to a single string. This can seem a little excessive, by default getStatusFlag will return one key in the array. If however you have a DataExtension that has the updateStatusFlags method, you could add additional keys to the result.
Basically, I would leave the implode handling in if in the future you do have code that messes with the status flags.
Now, you might be able to do something similar using the $casting property on the Page but given you were essentially adding this function just for the report, it was cleaner to just update it directly.
I did notice that the status flags array is actually empty if the page is published which means your report won't have anything next to published pages. If that is your intention, great!
If not, you could do another little alteration:
public function getStatus()
{
$flags = $this->getStatusFlags();
$result = array();
if ($flags)
{
foreach ($flags as $status)
{
$result[] = "{$status['text']} ({$status['title']})";
}
}
else
{
$result[] = 'Published (The page has been published)';
}
return implode(', ', $result);
}
The if ($flags) will evaluate to false when there are no current statuses (aka. the page is published) due to automatic casting.
Have you tried with an anonymous function inside columns()?
class PageListByType extends SS_Report {
function title() {
return 'Page List by Type';
}
function description() {
return 'List all the pages in the site, along with their page type';
}
public function sourceRecords($params = array(), $sort = null, $limit = null) {
return Page::get()->sort($sort);
}
public function columns() {
return array(
'Title' => _t('PageListByTypeReport.PageName', 'Page name'),
'ClassName' => _t('PageListByTypeReport.ClassName', 'Page type'),
'Status' => array(
'title'=>_t('PageListByTypeReport.Status', 'Status'),
'formatting' => function($value, $item) {
$flags = $item->getStatusFlags();
$status = '';
if ($flags) {
foreach ($flags as $status) {
$status = "{$status['text']} ({$status['title']})";
}
}
return $status ? : _t('PageListByTypeReport.PagePublished', 'Page published');
}
)
);
}
}
I'm trying to setup a batch page for processing and I need an example. The example that's given in the Example module is within a form and I need a page that I can run independently of a form that will process batch requests.
For instance:
function mymodule_batch_2() {
$operations[] = array('mymodule_onefunction','mymodule_anotherfunction')
$batch = array(
'operations' => $operations,
'finished' => 'mymodule_finished',
// We can define custom messages instead of the default ones.
'title' => t('Processing batch 2'),
'init_message' => t('Batch 2 is starting.'),
'progress_message' => t('Processed #current out of #total.'),
'error_message' => t('Batch 2 has encountered an error.'),
);
batch_set($batch);
batch_process('');
}
Where the batch function would call other functions in the form of $operations.
You need to give batch process an id to work from. batch_process('mybatch')otherwise yourmexample is correct. are you having a particular problem with this strategy?
Here you can see my sample of batch relization with form that calls batch:
function my_module_menu() {
$items['admin/commerce/import'] = array(
'title' => t('Import'),
'page callback' => 'drupal_get_form',
'page arguments' => array('my_module_settings_form'),
'access arguments' => array('administer site settings'),
'type' => MENU_NORMAL_ITEM,
);
return $items;
}
/**
* Import form
*/
function my_module_settings_form() {
$form = array();
$form['import'] = array(
'#type' => 'fieldset',
'#title' => t('Import'),
'#collapsible' => TRUE,
'#collapsed' => FALSE,
);
$form['import']['submit'] = array(
'#type' => 'submit',
'#value' => t('Import'),
);
return $form;
}
function my_module_settings_form_submit($form, &$form_state) {
batch_my_module_import_start();
}
/**
* Batch start function
*/
function batch_my_module_import_start() {
$batch = array(
'title' => t('Import products'),
'operations' => array(
array('_batch_my_module_import', array()),
),
'progress_message' => t('Import. Operation #current out of #total.'),
'error_message' => t('Error!'),
'finished' => 'my_module_batch_finished',
);
batch_set($batch);
}
/**
* Import from 1C operation. Deletes Products
*/
function _batch_my_module_import(&$context) {
// Your iterms. In my case select all products
$pids = db_select('commerce_product', 'p')
->fields('p', array('sku', 'product_id', 'title'))
->condition('p.type', 'product')
->execute()
->fetchAll();
// Get Count of products
if (empty($context['sandbox']['progress'])) {
$context['sandbox']['progress'] = 0;
$context['sandbox']['max'] = count($pids);
watchdog('import', 'import products');
}
// Create Iteration variable
if (empty($context['sandbox']['iteration'])) {
$context['sandbox']['iteration'] = 0;
}
// Check for the end of cycle
if ($context['sandbox']['iteration'] < $context['sandbox']['max']) {
// Count of operation in one batch step
$limit = 10;
// Counts completed operations in one batch step
$counter = 0;
if ($context['sandbox']['progress'] != 0) {
$context['sandbox']['iteration'] = $context['sandbox']['iteration'] + $limit;
}
// Loop all Product items in xml
for ($i = $context['sandbox']['iteration']; $i < $context['sandbox']['max'] && $counter < $limit; $i++) {
/* Loop items here */
/* ... */
/* ... */
$context['results']['added']['products'][] = $product_item->title;
// Update Progress
$context['sandbox']['progress']++;
$counter++;
// Messages
$context['message'] = t('Now processing product %name. Product %progress of %count', array('%name' => $product_item->title, '%progress' => $context['sandbox']['progress'], '%count' => $context['sandbox']['max']));
$context['results']['processed'] = $context['sandbox']['progress'];
}
}
if ($context['sandbox']['progress'] != $context['sandbox']['max']) {
$context['finished'] = $context['sandbox']['progress'] / $context['sandbox']['max'];
}
}
/**
* Finish of batch. Messagess
*/
function my_module_batch_finished($success, $results, $operations) {
if ($success) {
drupal_set_message(t('#count products added.', array('#count' => isset($results['added']) ? count($results['added']) : 0)));
}
else {
$error_operation = reset($operations);
drupal_set_message(t('An error occurred while processing #operation with arguments : #args', array('#operation' => $error_operation[0], '#args' => print_r($error_operation[0], TRUE))));
}
watchdog('import', 'import finished');
}
function module_name_import_form_submit($form, $form_state) {
// Check to make sure that the file was uploaded to the server properly
$uri = db_query("SELECT uri FROM {file_managed} WHERE fid = :fid", array(
':fid' => $form_state['input']['import']['fid'],
))->fetchField();
if(!empty($uri)) {
if(file_exists(drupal_realpath($uri))) {
// Open the csv
$handle = fopen(drupal_realpath($uri), "r");
// Go through each row in the csv and run a function on it. In this case we are parsing by '|' (pipe) characters.
// If you want commas are any other character, replace the pipe with it.
while (($data = fgetcsv($handle, 0, '|', '"')) !== FALSE) {
$operations[] = array(
'module_name_import_batch_processing', // The function to run on each row
array($data), // The row in the csv
);
}
// Once everything is gathered and ready to be processed... well... process it!
$batch = array(
'title' => t('Importing CSV...'),
'operations' => $operations, // Runs all of the queued processes from the while loop above.
'finished' => 'module_name_import_finished', // Function to run when the import is successful
'error_message' => t('The installation has encountered an error.'),
'progress_message' => t('Imported #current of #total products.'),
);
batch_set($batch);
fclose($handle);
}
}
else {
drupal_set_message(t('There was an error uploading your file. Please contact a System administator.'), 'error');
}
}
/**
* This function runs the batch processing and creates nodes with then given information
* #see
* module_name_import_form_submit()
*/
function module_name_import_batch_processing($data) {
// Lets make the variables more readable.
$title = $data[0];
$body = $data[1];
$serial_num = $data[2];
// Find out if the node already exists by looking up its serial number. Each serial number should be unique. You can use whatever you want.
$nid = db_query("SELECT DISTINCT n.nid FROM {node} n " .
"INNER JOIN {field_data_field_serial_number} s ON s.revision_id = n.vid AND s.entity_id = n.nid " .
"WHERE field_serial_number_value = :serial", array(
':serial' => $serial_num,
))->fetchField();
if(!empty($nid)) {
// The node exists! Load it.
$node = node_load($nid);
// Change the values. No need to update the serial number though.
$node->title = $title;
$node->body['und'][0]['value'] = $body;
$node->body['und'][0]['safe_value'] = check_plain($body);
node_save($node);
}
else {
// The node does not exist! Create it.
global $user;
$node = new StdClass();
$node->type = 'page'; // Choose your type
$node->status = 1; // Sets to published automatically, 0 will be unpublished
$node->title = $title;
$node->uid = $user->uid;
$node->body['und'][0]['value'] = $body;
$node->body['und'][0]['safe_value'] = check_plain($body);
$node->language = 'und';
$node->field_serial_number['und'][0]['value'] = $serial_num;
$node->field_serial_number['und'][0]['safe_value'] = check_plain($serial_num);
node_save($node);
}
}
/**
* This function runs when the batch processing is complete
*
* #see
* module_name_import_form_submit()
*/
function module_name_import_finished() {
drupal_set_message(t('Import Completed Successfully'));
}