I made a custom shipping method for Woocommerce. It shows in shipping zones, and it's been added to all the zones, but during checkout, the option for selecting this method doesn't show up.
Here's my code:
add_action('woocommerce_shipping_init', 'UPS_number_shipping_method');
function UPS_number_shipping_method()
{
if (!class_exists('UPS_Number_Shipping_Method')) {
class UPS_Number_Shipping_Method extends WC_Shipping_Method
{
public function __construct($instance_id = 0)
{
$this->id = 'UPS_number_shipping';
$this->instance_id = absint($instance_id);
$this->domain = 'UPS_number_shipping';
$this->method_title = __('UPS Number', $this->domain);
$this->method_description = __('Shipping method to be used when dealer has a UPS number', $this->domain);
$this->title = __('UPS Number', $this->domain);
$this->supports = array(
'shipping-zones',
'instance-settings',
'instance-settings-modal',
);
$this->init();
}
## Load the settings API
function init()
{
$this->init_form_fields();
$this->init_settings();
$this->enabled = 'yes';
$this->title = __('UPS Number', $this->domain);
add_action('woocommerce_update_options_shipping_' . $this->id, array($this, 'process_admin_options'));
}
public function calculate_shipping($packages = array())
{
$this->add_rate(0);
}
}
}
}
add_filter('woocommerce_shipping_methods', 'add_UPS_number_shipping');
function add_UPS_number_shipping($methods)
{
$methods['UPS_number_shipping'] = 'UPS_Number_Shipping_Method';
return $methods;
}
Related
I have created woocommerce plugin which gets shipping rates from third party URL(my web site) and displays it on the checkout page. when I click on one of shipping method it also calculates correct shipping rates, but when I again refresh checkout page my selected shipping option gets lost and I have to select again. also, my custom shipping option does not reach to order after a successful order.
In below Screenshot, I pass Suburb and Postcode, and I get all rates i desire, when i click on one of shipping method it also calculates correct a shipping rates
After Click on Place Order, it takes first shipping method which is a flat rate: $5, please see below screenshot
Here is my Plugin please have a look and guide me where I am going wrong.
<?php
/**
* Plugin Name: Iconsignit Shipping
* Plugin URI: http://Iconsignit.com.au
* Description: Iconsignit Shipping Method for WooCommerce
* Version: 1.0.0
* Author: Jaimin prajapati
* Author URI: http://www.webbrainstechnologies.com
* License: GPL-3.0+
* License URI: http://www.gnu.org/licenses/gpl-3.0.html
* Domain Path: /
* Text Domain: Iconsignit
*/
if (!defined('WPINC')) {
die;
}
/*
* Check if WooCommerce is active
*/
if (in_array('woocommerce/woocommerce.php', apply_filters('active_plugins', get_option('active_plugins')))) {
function iconsignit_shipping_method()
{
if (!class_exists('Iconsignit_Shipping_Method')) {
class Iconsignit_Shipping_Method extends WC_Shipping_Method
{
/**
* Constructor for your shipping class
*
* #access public
* #return void
*/
public function __construct($instance_id = 0)
{
$this->id = 'iconsignit';
$this->instance_id = absint($instance_id);
$this->method_title = __('Iconsignit Shipping', 'iconsignit');
$this->method_description = __('Custom Shipping Method for iconsignit', 'iconsignit');
$this->init();
$this->enabled = isset($this->settings['enabled']) ? $this->settings['enabled'] : 'yes';
$this->title = isset($this->settings['title']) ? $this->settings['title'] : __('Iconsignit Shipping', 'iconsignit');
}
/**
* Init your settings
*
* #access public
* #return void
*/
function init()
{
// Load the settings API
$this->init_form_fields();
$this->init_settings();
// Save settings in admin if you have any defined
add_action('woocommerce_update_options_shipping_' . $this->id, array($this, 'process_admin_options'));
}
/**
* Define settings field for this shipping
* #return void
*/
function init_form_fields()
{
// We will add our settings here
$this->form_fields = array(
'ApiToken' => array(
'title' => __('Api Token', 'iconsignit-integration-demo'),
'type' => 'text',
'description' => __('Enter with your API Key. You can find this in "User Profile" drop-down (top right corner) > API Keys.', 'iconsignit-integration-demo'),
'desc_tip' => true,
'default' => '',
),
'ApiUrl' => array(
'title' => __('Api Url', 'iconsignit-integration-demo'),
'type' => 'text',
'default' => '',
'desc_tip' => true,
'description' => __('Website URL', 'iconsignit-integration-demo'),
),
'ConnectIconsignUrl' => array(
'title' => __('Iconsignit Url', 'iconsignit-integration-demo'),
'type' => 'text',
'default' => '',
'desc_tip' => true,
'description' => __('Url from where all shipping rates will come', 'iconsignit-integration-demo'),
),
);
}
/**
* This function is used to calculate the shipping cost. Within this function we can check for weights, dimensions and other parameters.
*
* #access public
* #param mixed $package
* #return void
*/
public function calculate_shipping($package = array())
{
// We will add the cost, rate and logics in here
$item = array();
$count = 0;
foreach ($package['contents'] as $item_id => $values) {
$item[$count]['item_qty'] = $values['quantity'];
$item[$count]['item_length'] = $values['data']->get_length();
$item[$count]['item_width'] = $values['data']->get_width();
$item[$count]['item_height'] = $values['data']->get_height();
$item[$count]['item_weight'] = $values['data']->get_weight();
$item[$count]['item_palletised'] = 0;
$count++;
}
$country = $_POST['s_country'];
$state = $_POST['s_state'];
$postcode = $_POST['s_postcode'];
$city = $_POST['s_city'];
$address = $_POST['s_address'];
$address_2 = $_POST['s_address_2'];
$isCredentials = get_option('woocommerce_iconsignit_settings');
$data = array('ApiUrl' => $isCredentials['ApiUrl'], 'ApiToken' => $isCredentials['ApiToken'], 'DeliveryTown' => $city, 'DeliveryPostcode' => $postcode, 'Items' => $item);
$isResponse = Requests::post($isCredentials['ConnectIconsignUrl'].'/api/getconsignrate', array(), $data);
$resp = json_decode($isResponse->body, true);
$counter = 1;
foreach ($resp['result'] as $key => $res) {
$rate = array(
'id' => $res['QuoteRateID'],//$this->id,
'label' => $res['carrier_nm'] . "-(" . $res['service_nm'] . ")",
'cost' => $res['total_charge'],
'calc_tax' => 'per_item',
);
$this->add_rate($rate);
$counter++;
}
}
}
}
}
add_action('woocommerce_shipping_init', 'iconsignit_shipping_method');
function add_iconsignit_shipping_method($methods)
{
$methods[] = 'Iconsignit_Shipping_Method';
return $methods;
}
add_filter('woocommerce_shipping_methods', 'add_iconsignit_shipping_method');
}
problem is solved the problem was in $this->id, it should have to be unique.
I created a CUSTOM product type variation, everything works good in backend and front end, the only problem is, in the shopping cart / Checkout details, the variable product name not showing, also after purchase item the stock is not reducing, 1 item available for the variable, after purchase, keep the 1 item available instead of out of stock.
This is the code used.
* Step 1. Add a custom product type "term" to other hardcoded ones
*/
add_filter( 'product_type_selector', 'misha_ticket_product_type' );
function misha_ticket_product_type( $product_types ){
$product_types[ 'ticket' ] = 'Ticket';
return $product_types;
}
/**
* Step 2. Each product type has a PHP class WC_Product_{type}
*/
add_action( 'init', 'misha_create_ticket_product_class' );
add_filter( 'woocommerce_product_class', 'misha_load_ticket_product_class',10,2);
function misha_create_ticket_product_class(){
class WC_Product_Ticket extends WC_Product_Variable {
public function __construct( $product ) {
$this->product_type = 'ticket';
$this->supports[] = 'ajax_add_to_cart';
parent::__construct( $product );
}
public function get_type() {
return 'ticket'; // so you can use $product = wc_get_product(); $product->get_type()
}
}
}
add_filter('woocommerce_product_data_tabs','ticket_showtabs',10,1);
function ticket_showtabs($tabs) {
array_push($tabs['attribute']['class'], 'show_if_variable', 'show_if_ticket');
array_push($tabs['variations']['class'], 'show_if_ticket');
return $tabs;
}
function producttype_custom_js() {
if ( 'product' != get_post_type() ) :
return;
endif;
?><script type='text/javascript'>
jQuery("body").bind("DOMNodeInserted", function() {
jQuery(this).find('.enable_variation').addClass('show_if_ticket').show();
});
</script><?php
}
add_action( 'admin_footer', 'producttype_custom_js' ,99);
function misha_load_ticket_product_class( $php_classname, $product_type ) {
if ( $product_type == 'ticket' ) {
$php_classname = 'WC_Product_Ticket';
}
return $php_classname;
}
add_filter( 'woocommerce_data_stores', function( $stores ){
$stores['product-ticket'] = 'WC_Product_Variable_Data_Store_CPT';
return $stores;
} );
add_action( 'woocommerce_ticket_add_to_cart', 'woocommerce_variable_add_to_cart' );
//**************************************END*********************************//```
I ran into the exact same problem, and it occurs because woocommerce_add_to_cart_handler expects get_type to return 'variable' in order to trigger the add_to_cart_handler_variable method.
function misha_custom_add_to_cart_handler( $handler, $adding_to_cart ){
if( $handler == 'ticket' ){
$handler = 'variable';
}
return $handler;
}
add_filter( 'woocommerce_add_to_cart_handler', 'misha_custom_add_to_cart_handler', 10, 2 );
You would need something like the above as per discussion here How to add a custom WooCommerce product type that extends WC_Product_Variable to the cart
Recently updated Woocommerce to 3.0 and after that i having problem to save my custom product type that i have created.
This is what the code look like now.
function register_xxxxxx_product_type() {
class WC_Product_package extends WC_Product {
public function __construct( $product ) {
$this->product_type = 'xxxxxx';
parent::__construct( $product );
}
}
}
add_action( 'plugins_loaded', 'register_xxxxxxx_product_type' );
function add_xxxxxx_package_product( $types ){
// Key should be exactly the same as in the class
$types[ 'xxxxxx' ] = __( 'xxxxxx' );
$types[ 'xxxxxx' ] = __( 'xxxxxx' );
$types[ 'xxxxxx' ] = __( 'xxxxxx' );
return $types;
}
add_filter( 'product_type_selector', 'add_xxxx_package_product' );
are there anyone who have solved this?
Thanks!
UPDATE
Now my code look like this
function register_xxxxxx_product_type() {
class WC_Product_package extends WC_Product {
public $product_type = 'NameOfType';
public function __construct( $product ) {
parent::__construct( $product );
}
}
}
add_action( 'init', 'register_xxxxxx_product_type' );
function add_xxxxxx_package_product( $types ){
// Key should be exactly the same as in the class
$types[ 'xxxxxx_package' ] = __( 'xxxxxx Paket' );
$types[ 'xxxxxx_parts' ] = __( 'xxxxxx Tillbehör' );
$types[ 'xxxxxx_service' ] = __( 'xxxxxx Tillvalstjänster' );
return $types;
}
add_filter( 'product_type_selector', 'add_xxxxxx_package_product' );
function woocommerce_product_class( $classname, $product_type ) {
if ( $product_type == 'NameOfType' ) { // notice the checking here.
$classname = 'WC_Product_package';
}
return $classname;
}
add_filter( 'woocommerce_product_class', 'woocommerce_product_class', 10, 2 );
But is not working. What am i doing wrong?
UPDATE #2
Okey, this is how it looks like now.
function register_daniel_product_type() {
class WC_Product_package extends WC_Product {
public $product_type = 'daniel';
public function __construct( $product ) {
parent::__construct( $product );
}
}
}
add_action( 'init', 'register_daniel_product_type' );
function add_daniel_package_product( $types ){
// Key should be exactly the same as in the class
$types[ 'daniel_package' ] = __( 'Daniel Paket' );
$types[ 'daniel_parts' ] = __( 'Daniel Tillbehör' );
$types[ 'daniel_service' ] = __( 'Daniel Tillvalstjänster' );
return $types;
}
add_filter( 'product_type_selector', 'add_daniel_package_product' );
function woocommerce_product_class( $classname, $product_type ) {
if ( $product_type == 'daniel_package' ) { // notice the checking here.
$classname = 'WC_Product_package';
}
return $classname;
}
add_filter( 'woocommerce_product_class', 'woocommerce_product_class', 10, 2 );
Sorry that i am slow but would you please give it a try one more time to explain it for me.
Thanks!!
this is how to do it.
first make sure your class that extends to WC_Product is hooked on init.
function register_xxxxxx_product_type() {
class WC_Product_package extends WC_Product {
public $product_type = 'xxxxxx';
public function __construct( $product ) {
parent::__construct( $product );
}
}
}
add_action( 'init', 'register_xxxxxx_product_type' );
then add your product type.
function add_xxxx_package_product( $types ){
// Key should be exactly the same as in the class
$types[ 'xxxxxx' ] = __( 'xxxxxx' );
return $types;
}
add_filter( 'product_type_selector', 'add_xxxx_package_product' );
then use your created class for your product type.
If you don't have this, then you'll be stuck on WC_Product_Simple.
function woocommerce_product_class( $classname, $product_type ) {
if ( $product_type == 'xxxxxx' ) { // notice the checking here.
$classname = 'WC_Product_package';
}
return $classname;
}
add_filter( 'woocommerce_product_class', 'woocommerce_product_class', 10, 2 );
Yes, this worked.
class WC_Product_Exam_Training extends WC_Product_Variable {
But i had a small issue.
When i selected the new product_type, and saved it,
WC would somehow turn in back into a 'variable' product,
instead of the custom type "exam_training"
After some 'debugging' i found the issue:
In the WC_Product_Variable class there is a function:
/**
* Get internal type.
*
* #return string
*/
public function get_type(): string {
return 'variable';
}
This function needs to be present in the new class too, and set the new 'type' in there too.
THEN WC will save it as the correct product type
So in the new class i got:
...
public string $product_type = WC_Product_Types::WC_TYPE_EXAM;
/**
* Get internal type.
*
* #return string
*/
public function get_type(): string {
return $this->product_type;
}
...
hope this helps the future devs on this topic
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 working on a WordPress plugin, and part of that plugin requires extending WP_List_Table and storing any of the items which are checked in that table to an option. I've managed to figure out how to properly setup and display the required table, but how do I handle storing the checked options?
Here's what I've got so far...
class TDBar_List_Table extends WP_List_Table {
// Reference parent constructor
function __construct() {
global $status, $page;
// Set defaults
parent::__construct( array(
'singular' => 'theme',
'plural' => 'themes',
'ajax' => false
));
}
// Set table classes
function get_table_classes() {
return array('widefat', 'wp-list-table', 'themes');
}
// Setup default column
function column_default($item, $column_name) {
switch($column_name) {
case 'Title':
case 'URI':
case'Description':
return $item[$column_name];
default:
return print_r($item, true);
}
}
// Displaying checkboxes!
function column_cb($item) {
return sprintf(
'<input type="checkbox" name="%1$s" id="%2$s" value="checked" />',
//$this->_args['singular'],
$item['Stylesheet'] . '_status',
$item['Stylesheet'] . '_status'
);
}
// Display theme title
function column_title($item) {
return sprintf(
'<strong>%1$s</strong>',
$item['Title']
);
}
// Display theme preview
function column_preview($item) {
if (file_exists(get_theme_root() . '/' . $item['Stylesheet'] . '/screenshot.png')) {
$preview = get_theme_root_uri() . '/' . $item['Stylesheet'] . '/screenshot.png';
} else {
$preview = '';
}
return sprintf(
'<img src="%3$s" style="width: 150px;" />',
$preview,
$item['Title'],
$preview
);
}
// Display theme description
function column_description($item) {
if (isset($item['Version'])) {
$version = 'Version ' . $item['Version'];
if (isset($item['Author']) || isset($item['URI']))
$version .= ' | ';
} else {
$version = '';
}
if (isset($item['Author'])) {
$author = 'By ' . $item['Author'];
if (isset($item['URI']))
$author .= ' | ';
} else {
$author = '';
}
if (isset($item['URI'])) {
$uri = $item['URI'];
} else {
$uri = '';
}
return sprintf(
'<div class="theme-description"><p>%1$s</p></div><div class="second theme-version-author-uri">%2$s%3$s%4$s',
$item['Description'],
$version,
$author,
$uri
);
}
// Setup columns
function get_columns() {
$columns = array(
'cb' => '<input type="checkbox" />',
'title' => 'Theme',
'preview' => 'Preview',
'description' => 'Description'
);
return $columns;
}
// Make title column sortable
function get_sortable_columns() {
$sortable_columns = array(
'title' => array('Title', true)
);
return $sortable_columns;
}
// Setup bulk actions
function get_bulk_actions() {
$actions = array(
'update' => 'Update'
);
return $actions;
}
// Handle bulk actions
function process_bulk_action() {
// Define our data source
if (defined('WP_ALLOW_MULTISITE') && WP_ALLOW_MULTISITE == true) {
$themes = get_allowed_themes();
} else {
$themes = get_themes();
}
if ('update' === $this->current_action()) {
foreach ($themes as $theme) {
if ($theme['Stylesheet'] . '_status' == 'checked') {
// Do stuff - here's the problem
}
}
}
}
// Handle data preparation
function prepare_items() {
// How many records per page?
$per_page = 10;
// Define column headers
$columns = $this->get_columns();
$hidden = array();
$sortable = $this->get_sortable_columns();
// Build the array
$this->_column_headers = array($columns, $hidden, $sortable);
// Pass off bulk action
$this->process_bulk_action();
// Define our data source
if (defined('WP_ALLOW_MULTISITE') && WP_ALLOW_MULTISITE == true) {
$themes = get_allowed_themes();
} else {
$themes = get_themes();
}
// Handle sorting
function usort_reorder($a,$b) {
$orderby = (!empty($_REQUEST['orderby'])) ? $_REQUEST['orderby'] : 'Title';
$order = (!empty($_REQUEST['order'])) ? $_REQUEST['order'] : 'asc';
$result = strcmp($a[$orderby], $b[$orderby]);
return ($order === 'asc') ? $result : -$result;
}
usort($themes, 'usort_reorder');
//MAIN STUFF HERE
//for ($i = 0; i < count($themes); $i++) {
//}
// Figure out the current page and how many items there are
$current_page = $this->get_pagenum();
$total_items = count($themes);
// Only show the current page
$themes = array_slice($themes,(($current_page-1)*$per_page),$per_page);
// Display sorted data
$this->items = $themes;
// Register pagination options
$this->set_pagination_args( array(
'total_items' => $total_items,
'per_page' => $per_page,
'total_pages' => ceil($total_items/$per_page)
));
}
}
Problem is, I can't get it to save properly. I select the rows I want, hit save and it just resets.
I assume you are talking about the checkboxes in your table listing, so this will be how to process bulk actions.
All you need to do is add two new methods to your class and initialize it in the prepare_items method. I use the code below in one of my plugins to delete or export, but you can just as easily run an update.
/**
* Define our bulk actions
*
* #since 1.2
* #returns array() $actions Bulk actions
*/
function get_bulk_actions() {
$actions = array(
'delete' => __( 'Delete' , 'visual-form-builder'),
'export-all' => __( 'Export All' , 'visual-form-builder'),
'export-selected' => __( 'Export Selected' , 'visual-form-builder')
);
return $actions;
}
/**
* Process our bulk actions
*
* #since 1.2
*/
function process_bulk_action() {
$entry_id = ( is_array( $_REQUEST['entry'] ) ) ? $_REQUEST['entry'] : array( $_REQUEST['entry'] );
if ( 'delete' === $this->current_action() ) {
global $wpdb;
foreach ( $entry_id as $id ) {
$id = absint( $id );
$wpdb->query( "DELETE FROM $this->entries_table_name WHERE entries_id = $id" );
}
}
}
Now, call this method inside prepare_items() like so:
function prepare_items() {
//Do other stuff in here
/* Handle our bulk actions */
$this->process_bulk_action();
}
There's a fantastic helper plugin called Custom List Table Example that makes figuring out the WP_List_Table class much easier.