Merge branch 'master' into fix/15315

This commit is contained in:
Claudio Sanches 2017-05-31 11:59:49 -03:00 committed by GitHub
commit bc3c7f6bbd
77 changed files with 9116 additions and 544 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -4968,6 +4968,7 @@ table.bar_chart {
font-size: 1.2em;
padding: 0.75em 1.5em;
height: auto;
display: inline-block !important
}
}
@ -5421,3 +5422,343 @@ table.bar_chart {
margin: 1px 6px 4px 1px;
}
}
.woocommerce-progress-form-wrapper,
.woocommerce-exporter-wrapper,
.woocommerce-importer-wrapper {
text-align: center;
max-width: 700px;
margin: 40px auto;
.error {
text-align: left;
}
.wc-progress-steps {
padding: 0 0 24px;
margin: 0;
list-style: none outside;
overflow: hidden;
color: #ccc;
width:100%;
display: -webkit-inline-flex;
display: -ms-inline-flexbox;
display: inline-flex;
li {
width: 25%;
float: left;
padding: 0 0 0.8em;
margin: 0;
text-align: center;
position: relative;
border-bottom: 4px solid #ccc;
line-height: 1.4em;
}
li::before {
content: '';
border: 4px solid #ccc;
border-radius: 100%;
width: 4px;
height: 4px;
position: absolute;
bottom: 0;
left: 50%;
margin-left: -6px;
margin-bottom: -8px;
background: #fff;
}
li.active {
border-color: #a16696;
color: #a16696;
&::before {
border-color: #a16696;
}
}
li.done {
border-color: #a16696;
color: #a16696;
&::before {
border-color: #a16696;
background: #a16696;
}
}
}
.wc-actions {
overflow: hidden;
border-top: 1px solid #eee;
margin: 0;
padding: 23px 24px 24px;
line-height: 3em;
.button {
float: right;
font-size: 1.25em;
padding: 0.5em 1em !important;
line-height: 1.5em !important;
margin-right: 0.5em;
margin-bottom: 2px;
height: auto !important;
border-radius: 4px;
background-color: #bb77ae;
border-color: #a36597;
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 0 #a36597;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 0 #a36597;
text-shadow: 0 -1px 1px #a36597, 1px 0 1px #a36597, 0 1px 1px #a36597, -1px 0 1px #a36597;
margin: 0;
opacity: 1;
&:hover, &:focus, &:active {
background: #a36597;
border-color: #a36597;
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 0 #a36597;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 0 #a36597;
}
}
.woocommerce-importer-toggle-advanced-options {
color: #999;
}
}
.woocommerce-exporter,
.woocommerce-importer,
.wc-progress-form-content {
background: #fff;
overflow: hidden;
padding: 0;
margin: 0 0 16px;
box-shadow: 0 1px 3px rgba(0,0,0,.13);
color: #555;
text-align: left;
header {
border-bottom: 1px solid #eee;
margin: 0;
padding: 24px 24px 0;
}
section {
padding: 24px 24px 0;
}
h2 {
margin: 0 0 24px;
color: #555;
font-size: 24px;
font-weight: normal;
line-height: 1em;
}
p {
font-size: 1em;
line-height: 1.75em;
font-size: 16px;
color: #555;
margin: 0 0 24px;
}
.form-row {
margin-top: 24px;
}
.spinner {
display: none;
}
.woocommerce-importer-options th,
.woocommerce-importer-options td,
.woocommerce-exporter-options th,
.woocommerce-exporter-options td {
vertical-align: top;
line-height: 1.75em;
padding: 0 0 24px 0;
label {
color: #555;
font-weight: normal;
}
input[type="checkbox"] {
margin: 0 4px 0 0;
padding: 7px;
}
input[type="text"],
input[type="number"] {
padding: 7px;
height: auto;
margin: 0;
}
.woocommerce-importer-file-url-field-wrapper {
border: 1px solid #ddd;
-webkit-box-shadow: inset 0 1px 2px rgba(0,0,0,.07);
box-shadow: inset 0 1px 2px rgba(0,0,0,.07);
background-color: #fff;
color: #32373c;
outline: 0;
line-height: 1;
display: block;
code {
background: none;
font-size: smaller;
padding: 0;
margin: 0;
color: #999;
padding: 7px 0 0 7px;
display: inline-block;
}
input {
font-family: Consolas,Monaco,monospace;
border: 0;
margin: 0;
outline: 0;
box-shadow: none;
display: inline-block;
min-width: 25%;
box-sizing: borde
}
}
}
.woocommerce-exporter-options th,
.woocommerce-importer-options th {
width: 35%;
padding-right: 20px;
}
progress {
width: 100%;
height: 42px;
margin: 0 auto 24px;
display: block;
-webkit-appearance: none;
border: none;
display: none;
background: #f5f5f5;
border: 2px solid #eee;
border-radius: 4px;
padding: 0;
box-shadow: 0 1px 0px 0 rgba(255, 255, 255, 0.2);
}
progress::-webkit-progress-bar {
background: transparent none;
border: 0;
border-radius: 4px;
padding: 0;
box-shadow: none;
}
progress::-webkit-progress-value {
border-radius: 3px;
box-shadow: inset 0 1px 1px 0 rgba(255, 255, 255, 0.4);
background: #A46497;
background: linear-gradient( top, #A46497, #66405F ), #A46497;
transition: width 1s ease;
}
progress::-moz-progress-bar {
border-radius: 3px;
box-shadow: inset 0 1px 1px 0 rgba(255, 255, 255, 0.4);
background: #A46497;
background: linear-gradient( top, #A46497, #66405F ), #A46497;
transition: width 1s ease;
}
progress::-ms-fill {
border-radius: 3px;
box-shadow: inset 0 1px 1px 0 rgba(255, 255, 255, 0.4);
background: #A46497;
background: linear-gradient( to bottom, #A46497, #66405F ), #A46497;
transition: width 1s ease;
}
&.woocommerce-exporter__exporting,
&.woocommerce-importer__importing {
.spinner {
display: block;
}
progress {
display: block;
}
.wc-actions,
.woocommerce-exporter-options {
display: none;
}
}
.wc-importer-mapping-table-wrapper,
.wc-importer-error-log {
padding: 0;
}
.wc-importer-mapping-table,
.wc-importer-error-log-table {
margin: 0;
border: 0;
box-shadow: none;
td, th {
border: 0;
padding: 12px;
vertical-align: middle;
select {
width: 100%;
}
}
tbody tr:nth-child(odd) td,
tbody tr:nth-child(odd) th {
background: #fbfbfb;
}
th {
font-weight: bold;
}
td:first-child,
th:first-child {
padding-left: 24px;
}
td:last-child,
th:last-child {
padding-right: 24px;
}
.wc-importer-mapping-table-name {
width: 50%;
.description {
color: #999;
margin-top: 4px;
display: block;
code {
background: none;
padding: 0;
}
}
}
}
.woocommerce-importer-done {
text-align: center;
padding: 48px 24px;
font-size: 1.5em;
line-height: 1.75em;
&::before {
@include icon( '\e015' );
color: #A16696;
position: static;
font-size: 100px;
display: block;
float: none;
margin: 0 0 24px;
}
}
}
}

File diff suppressed because one or more lines are too long

1
assets/css/helper.css Normal file

File diff suppressed because one or more lines are too long

743
assets/css/helper.scss Normal file
View File

@ -0,0 +1,743 @@
/*------------------------------------------------------------------------------
General table styling
------------------------------------------------------------------------------*/
$white: #ffffff;
// Primary Accent (Blues)
$blue-wordpress: #0087be;
$woo_pink2: #78dcfa;
$woo_pink1: #00aadc;
$blue-dark: #005082;
// Jetpack Green
$green-jetpack: #8cc258;
// Grays
$gray: #87a6bc;
$gray-light: lighten( $gray, 33% ); //#f3f6f8
$gray-dark: darken( $gray, 38% ); //#2e4453
// $gray-text: ideal for standard, non placeholder text
// $gray-text-min: minimum contrast needed for WCAG 2.0 AA on white background
$gray-text: $gray-dark;
$gray-text-min: darken( $gray, 18% ); //#537994
$woo_pink1: #955a89;
$woo_pink2: #bb77ae;
$color_text_blue: #0073AA;
$color_button_primary: $woo_pink1;
$color_button_secondary: $woo_pink2;
/*------------------------------------------------------------------------------
Tab navigation
------------------------------------------------------------------------------*/
.wc-helper{
.nav-tab-wrapper{
margin-bottom: 22px;
}
@media only screen and (max-width : 784px) {
.nav-tab{
max-width: 40%;
overflow: hidden;
text-overflow: ellipsis;
}
}
}
/*------------------------------------------------------------------------------
Buttons
------------------------------------------------------------------------------*/
.wc-helper{
.button, .button:hover{
background-color: $color_button_primary;
border-width: 0;
box-shadow: none;
border-radius: 3px;
color: #fff;
height: auto;
padding: 3px 14px;
text-align: center;
white-space: normal !important;
@media only screen and (max-width : 782px) {
line-height: 2;
}
}
.button:hover{
opacity: 0.8;
}
.button.button-secondary{
background-color: #e6e6e6;
color: #3c3c3c;
text-shadow: none;
}
}
.wc-helper .subscription-filter{
color: #2E4453;
font-size: 13px;
line-height: 13px;
margin: 22px 0;
label{
display: none;
position: relative;
.chevron{
color: #E1E1E1;
line-height: 1;
position: absolute;
top: 12px;
right: 12px;
border-bottom-width: 0;
padding: 0;
}
}
span{
font-weight: bold;
padding-right: 4px;
}
a{
position: relative;
color: #0073AA;
display: inline-block;
padding: 0 4px 0 8px;
text-decoration: none;
&:before{
background-color: #979797;
content: " ";
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 1px;
}
}
@media only screen and (max-width : 600px) {
background-color: #fff;
border: 1px solid #E1E1E1;
border-radius: 2px;
font-size: 14px;
label, span, a{
border-bottom: 1px solid #E1E1E1;
line-height: 2;
padding: 8px 16px;
margin: 0;
}
label, span.chevron{
display: block;
}
span, a{
display: none;
}
a{
cursor: pointer;
&::before{
display: none;
}
}
span.chevron{
transform: rotateX(0deg);
}
&:focus,&:hover{
span, a{
display: block;
}
span.chevron{
transform: rotateX(0deg);
}
}
}
}
/*------------------------------------------------------------------------------
Subscripton table
------------------------------------------------------------------------------*/
.wc-helper {
.striped > tbody > :nth-child(odd),
ul.striped > :nth-child(odd),
.alternate {
background-color: #ffffff;
}
table.widefat,
.wp-editor-container,
.stuffbox,
p.popular-tags,
.widgets-holder-wrap,
.popular-tags,
.feature-filter,
.imgedit-group,
.comment-ays {
padding-top: 5px;
}
.widefat thead tr th,
.widefat thead tr td,
.widefat tfoot tr th,
.widefat tfoot tr td {
color: #32373c;
padding-top: 10px;
padding-bottom: 15px;
}
.widefat td {
//border-bottom: 1px solid #e5e5e5;
padding-top: 15px;
padding-bottom: 15px;
}
.wp-list-table{
padding-top: 0 !important;
border: 0;
}
.button{
@media only screen and (max-width : 782px) {
font-size: 11px;
}
}
.wp-list-table__row{
background-color: rgba(0,0,0,0);
td{
background-color: #fff;
border: 0px;
//border-top: 1px solid #e5e5e5;
padding: 16px 22px;
vertical-align: middle;
@media only screen and (max-width : 782px) {
padding: 16px;
}
}
td.color-bar{
border-left: 0;
}
&.is-ext-header{
td{
border-top: 1px solid #E1E1E1;
}
@media only screen and (max-width : 782px) {
display: inline-flex;
flex-flow: row wrap;
width: 100%;
.wp-list-table__ext-details{
flex: 2;
display: block;
}
.wp-list-table__ext-actions{
display: block;
flex: 1;
min-width: 0;
}
}
}
&:last-child td{
border-bottom: 6px solid #F1F1F1;
box-shadow: inset 0 -1px 0 #E1E1E1;
}
}
.wp-list-table__ext-details,
.wp-list-table__ext-status{
border-left: 1px solid #E1E1E1 !important;
padding-right: 22px;
width: 100%;
}
.wp-list-table__ext-details{
display: flex;
@media only screen and (max-width : 782px) {
display: table;
}
}
.wp-list-table__ext-title{
color: $color_text_blue;
font-size: 18px;
font-weight: 784;
width: 60%;
@media only screen and (max-width : 782px) {
margin-bottom: 12px;
width: 100%;
}
}
.wp-list-table__ext-description {
color: #333;
width: 40%;
@media only screen and (max-width : 782px) {
width: 100%;
}
}
.wp-list-table__ext-status {
position: relative;
&.update-available:after{
content: " ";
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 5px;
background-color: #FFC322;
}
&.expired:after{
content: " ";
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 5px;
background-color: #B81C23;
}
.dashicons-update{
color: #FFC322;
}
.dashicons-info{
color: #B81C23;
}
p{
color: #333333;
margin: 0;
}
.dashicons{
margin-right: 5px;
}
}
.wp-list-table__ext-actions{
border-right: 1px solid #E1E1E1 !important;
min-width: 150px;
width: 25%;
text-align: right;
}
.wp-list-table__ext-updates {
td{
position: relative;
&:before {
content: " ";
position: absolute;
top: 0;
left: 0;
right: 0;
height: 1px;
background-color: #E1E1E1;
}
}
td.wp-list-table__ext-status:before {
left: 22px;
}
td.wp-list-table__ext-actions:before {
right: 22px;
}
@media only screen and (max-width : 782px) {
display: flex;
.wp-list-table__ext-status{
flex: 2;
&::before{
left: 0 !important;
width: 100% !important;
}
}
.wp-list-table__ext-actions{
flex: 1;
min-width: 0;
&::before{
right: 0 !important;
left: 0 !important;
width: 100% !important;
}
}
}
}
}
/*------------------------------------------------------------------------------
Expired notification bar
------------------------------------------------------------------------------*/
.wc-helper {
td.color-bar {
border-left: solid 4px transparent;
}
td.color-bar.expired {
border-left-color: #B81C23;
}
td.color-bar.expiring {
border-left-color: orange;
}
td.color-bar.update-available {
border-left-color: #8FAE1B;
}
td.color-bar.expiring.update-available {
border-left-color: #8FAE1B;
}
}
/*------------------------------------------------------------------------------
Connected account table
------------------------------------------------------------------------------*/
.wc-helper {
.connect-wrapper {
background-color: #fff;
margin-bottom: 25px;
border: 1px solid #e5e5e5;
overflow: auto;
}
.connected {
display: flex;
.user-info{
display: flex;
padding: 20px;
width: 100%;
vertical-align: middle;
}
img {
border: 1px solid #e5e5e5;
height: 34px;
width: 34px;
}
.buttons{
padding: 20px;
white-space: nowrap;
}
p {
flex: 2;
margin: 10px 0 0 20px;
}
.chevron{
display: none;
&:hover{
cursor: pointer;
color: $woo_pink1;
}
}
@media only screen and (max-width : 784px) {
display: block;
strong{
display: block;
overflow: hidden;
text-overflow: ellipsis;
}
p{
margin: 0;
overflow: hidden;
text-overflow: ellipsis;
width: 80%;
}
.user-info{
padding-right: 0;
width: auto;
}
.avatar{
margin-right: 12px;
}
.chevron{
display: block;
color: #E1E1E1;
margin: 10px;
transform: rotateX(0deg);
}
.buttons{
border-top: 1px solid #E1E1E1;
padding: 10px 20px;
display: none;
&.active{
display: block;
.chevron{
}
}
}
}
}
}
/*------------------------------------------------------------------------------
Initial connection screen
------------------------------------------------------------------------------*/
.wc-helper {
.start-container {
background-color: #ffffff;
padding: 45px 20px 20px 30px;
position: relative;
overflow: hidden;
border-left: 4px solid #cc99c2;
}
.start-container::before {
content: "\e01C";
font-family: WooCommerce;
text-align: center;
line-height: 1;
color: #eee2ec;
display: block;
width: 1em;
font-size: 192px;
top: 65%;
right: -3%;
position: absolute;
}
.start-container h2 {
font-size: 24px;
line-height: 29px;
position: relative;
}
.start-container p {
font-size: 16px;
margin-bottom: 30px;
position: relative;
}
.wp-core-ui .button-primary {
background: #bb77ae;
border-color: #A36597;
-webkit-box-shadow: inset 0 1px 0 rgba(255,255,255,.25), 0 1px 0 #A36597;
box-shadow: inset 0 1px 0 rgba(255,255,255,.25), 0 1px 0 #A36597;
color: #fff;
text-shadow: 0 -1px 1px #A36597, 1px 0 1px #A36597, 0 1px 1px #A36597, -1px 0 1px #A36597;
}
.wp-core-ui a.button-primary:hover {
background: #A36597;
border-color: #A36597;
-webkit-box-shadow: inset 0 1px 0 rgba(255,255,255,.25), 0 1px 0 #A36597;
box-shadow: inset 0 1px 0 rgba(255,255,255,.25), 0 1px 0 #A36597;
}
.wp-core-ui a.button-primary:focus {
background: #A36597;
border-color: #A36597;
-webkit-box-shadow: inset 0 1px 0 rgba(255,255,255,.25), 0 1px 0 #A36597;
box-shadow: inset 0 1px 0 rgba(255,255,255,.25), 0 1px 0 #A36597;
}
.wp-core-ui a.button-primary:active {
background: #A36597;
border-color: #A36597;
-webkit-box-shadow: inset 0 1px 0 rgba(255,255,255,.25), 0 1px 0 #A36597;
box-shadow: inset 0 1px 0 rgba(255,255,255,.25), 0 1px 0 #A36597;
}
}
// ==========================================================================
// FormToggle
// ==========================================================================
.form-toggle[type="checkbox"] {
display: none;
}
.form-toggle__switch {
position: relative;
display: inline-block;
border-radius: 12px;
box-sizing: border-box;
padding: 2px;
width: 40px;
height: 24px;
vertical-align: middle;
align-self: flex-start;
outline: 0;
cursor: pointer;
transition: all .4s ease, box-shadow 0s;
&:before,
&:after {
position: relative;
display: block;
content: "";
width: 20px;
height: 20px;
}
&:after {
left: 0;
border-radius: 50%;
background: $white;
transition: all .2s ease;
}
&:before {
display: none;
}
.accessible-focus &:focus{
box-shadow: 0 0 0 2px $woo_pink1;
}
}
.form-toggle__label {
cursor: pointer;
.is-disabled & {
cursor: default;
}
.form-toggle__label-content {
flex: 0 1 100%;
margin-left: 12px;
color: #87a6bc;
font-size: 13px;
line-height: 16px;
margin-right: 8px;
vertical-align: top;
text-transform: uppercase;
cursor: pointer;
@media only screen and (max-width : 480px) {
display: none;
}
}
}
.form-toggle {
.accessible-focus &:focus {
+ .form-toggle__label .form-toggle__switch {
box-shadow: 0 0 0 2px $woo_pink1;
}
&:checked + .form-toggle__label .form-toggle__switch {
box-shadow: 0 0 0 2px $woo_pink2;
}
}
& + .form-toggle__label .form-toggle__switch {
background: lighten( $gray, 10% );
}
&:not( :disabled ) {
+ .form-toggle__label:hover .form-toggle__switch {
background: lighten( $gray, 20% );
}
}
&:checked{
+ .form-toggle__label .form-toggle__switch {
background: $woo_pink1;
&:after {
left: 16px;
}
}
}
&:checked:not( :disabled ) {
+ .form-toggle__label:hover .form-toggle__switch {
background: $woo_pink2;
}
}
&:disabled {
+ label.form-toggle__label span.form-toggle__switch {
opacity: 0.25;
cursor: default;
}
}
}
// Classes for toggle state before action is complete (updating plugin or something)
.form-toggle.is-toggling {
+ .form-toggle__label .form-toggle__switch {
background: $woo_pink1;
}
&:checked {
+ .form-toggle__label .form-toggle__switch {
background: lighten( $gray, 20% );
}
}
}
.form-toggle.is-compact {
+ .form-toggle__label .form-toggle__switch {
border-radius: 8px;
width: 24px;
height: 16px;
&:before,
&:after {
width: 12px;
height: 12px;
}
}
&:checked {
+ .form-toggle__label .form-toggle__switch {
&:after{
left: 8px;
}
}
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -16,6 +16,7 @@ body {
.wc-setup-content {
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.13);
padding: 24px 24px 0;
margin: 0 0 20px;
background: #fff;
overflow: hidden;
zoom: 1;
@ -249,6 +250,13 @@ body {
opacity: 1;
padding: 1em;
text-align: center;
}
a.button-primary {
background-color: #bb77ae;
border-color: #a36597;
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 0 #a36597;
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 0 #a36597;
text-shadow: 0 -1px 1px #a36597, 1px 0 1px #a36597, 0 1px 1px #a36597, -1px 0 1px #a36597;
&:hover, &:focus, &:active {
background: #a36597;

View File

@ -0,0 +1,86 @@
/*global ajaxurl, wc_product_export_params */
;(function ( $, window ) {
/**
* productExportForm handles the export process.
*/
var productExportForm = function( $form ) {
this.$form = $form;
this.xhr = false;
// Initial state.
this.$form.find('.woocommerce-exporter-progress').val( 0 );
// Methods.
this.processStep = this.processStep.bind( this );
// Events.
$form.on( 'submit', { productExportForm: this }, this.onSubmit );
};
/**
* Handle export form submission.
*/
productExportForm.prototype.onSubmit = function( event ) {
event.preventDefault();
event.data.productExportForm.$form.addClass( 'woocommerce-exporter__exporting' );
event.data.productExportForm.$form.find('.woocommerce-exporter-progress').val( 0 );
event.data.productExportForm.$form.find('.woocommerce-exporter-button').prop( 'disabled', true );
event.data.productExportForm.processStep( 1, $( this ).serialize(), '' );
};
/**
* Process the current export step.
*/
productExportForm.prototype.processStep = function( step, data, columns ) {
var $this = this,
selected_columns = $( '.woocommerce-exporter-columns' ).val(),
export_meta = $( '#woocommerce-exporter-meta:checked' ).length ? 1 : 0,
export_types = $( '.woocommerce-exporter-types' ).val();
$.ajax( {
type: 'POST',
url: ajaxurl,
data: {
form : data,
action : 'woocommerce_do_ajax_product_export',
step : step,
columns : columns,
selected_columns : selected_columns,
export_meta : export_meta,
export_types : export_types,
security : wc_product_export_params.export_nonce
},
dataType: 'json',
success: function( response ) {
if ( response.success ) {
if ( 'done' === response.data.step ) {
$this.$form.find('.woocommerce-exporter-progress').val( response.data.percentage );
window.location = response.data.url;
setTimeout( function() {
$this.$form.removeClass( 'woocommerce-exporter__exporting' );
$this.$form.find('.woocommerce-exporter-button').prop( 'disabled', false );
}, 2000 );
} else {
$this.$form.find('.woocommerce-exporter-progress').val( response.data.percentage );
$this.processStep( parseInt( response.data.step, 10 ), data, response.data.columns );
}
}
}
} ).fail( function( response ) {
window.console.log( response );
} );
};
/**
* Function to call productExportForm on jquery selector.
*/
$.fn.wc_product_export_form = function() {
new productExportForm( this );
return this;
};
$( '.woocommerce-exporter' ).wc_product_export_form();
})( jQuery, window );

View File

@ -0,0 +1 @@
!function(a,b){var c=function(a){this.$form=a,this.xhr=!1,this.$form.find(".woocommerce-exporter-progress").val(0),this.processStep=this.processStep.bind(this),a.on("submit",{productExportForm:this},this.onSubmit)};c.prototype.onSubmit=function(b){b.preventDefault(),b.data.productExportForm.$form.addClass("woocommerce-exporter__exporting"),b.data.productExportForm.$form.find(".woocommerce-exporter-progress").val(0),b.data.productExportForm.$form.find(".woocommerce-exporter-button").prop("disabled",!0),b.data.productExportForm.processStep(1,a(this).serialize(),"")},c.prototype.processStep=function(c,d,e){var f=this,g=a(".woocommerce-exporter-columns").val(),h=a("#woocommerce-exporter-meta:checked").length?1:0,i=a(".woocommerce-exporter-types").val();a.ajax({type:"POST",url:ajaxurl,data:{form:d,action:"woocommerce_do_ajax_product_export",step:c,columns:e,selected_columns:g,export_meta:h,export_types:i,security:wc_product_export_params.export_nonce},dataType:"json",success:function(a){a.success&&("done"===a.data.step?(f.$form.find(".woocommerce-exporter-progress").val(a.data.percentage),b.location=a.data.url,setTimeout(function(){f.$form.removeClass("woocommerce-exporter__exporting"),f.$form.find(".woocommerce-exporter-button").prop("disabled",!1)},2e3)):(f.$form.find(".woocommerce-exporter-progress").val(a.data.percentage),f.processStep(parseInt(a.data.step,10),d,a.data.columns)))}}).fail(function(a){b.console.log(a)})},a.fn.wc_product_export_form=function(){return new c(this),this},a(".woocommerce-exporter").wc_product_export_form()}(jQuery,window);

View File

@ -0,0 +1,80 @@
/*global ajaxurl, wc_product_import_params */
;(function ( $, window ) {
/**
* productImportForm handles the import process.
*/
var productImportForm = function( $form ) {
this.$form = $form;
this.xhr = false;
this.mapping = wc_product_import_params.mapping;
this.position = 0;
this.file = wc_product_import_params.file;
this.update_existing = wc_product_import_params.update_existing;
this.security = wc_product_import_params.import_nonce;
// Number of import successes/failures.
this.imported = 0;
this.failed = 0;
this.updated = 0;
this.skipped = 0;
// Initial state.
this.$form.find('.woocommerce-importer-progress').val( 0 );
this.run_import = this.run_import.bind( this );
// Start importing.
this.run_import();
};
/**
* Run the import in batches until finished.
*/
productImportForm.prototype.run_import = function() {
var $this = this;
$.ajax( {
type: 'POST',
url: ajaxurl,
data: {
action : 'woocommerce_do_ajax_product_import',
position : $this.position,
mapping : $this.mapping,
file : $this.file,
update_existing : $this.update_existing,
security : $this.security
},
dataType: 'json',
success: function( response ) {
if ( response.success ) {
$this.position = response.data.position;
$this.imported += response.data.imported;
$this.failed += response.data.failed;
$this.updated += response.data.updated;
$this.skipped += response.data.skipped;
$this.$form.find('.woocommerce-importer-progress').val( response.data.percentage );
if ( 'done' === response.data.position ) {
window.location = response.data.url + '&products-imported=' + parseInt( $this.imported, 10 ) + '&products-failed=' + parseInt( $this.failed, 10 ) + '&products-updated=' + parseInt( $this.updated, 10 ) + '&products-skipped=' + parseInt( $this.skipped, 10 );
} else {
$this.run_import();
}
}
}
} ).fail( function( response ) {
window.console.log( response );
} );
};
/**
* Function to call productImportForm on jQuery selector.
*/
$.fn.wc_product_importer = function() {
new productImportForm( this );
return this;
};
$( '.woocommerce-importer' ).wc_product_importer();
})( jQuery, window );

View File

@ -0,0 +1 @@
!function(a,b){var c=function(a){this.$form=a,this.xhr=!1,this.mapping=wc_product_import_params.mapping,this.position=0,this.file=wc_product_import_params.file,this.update_existing=wc_product_import_params.update_existing,this.security=wc_product_import_params.import_nonce,this.imported=0,this.failed=0,this.updated=0,this.skipped=0,this.$form.find(".woocommerce-importer-progress").val(0),this.run_import=this.run_import.bind(this),this.run_import()};c.prototype.run_import=function(){var c=this;a.ajax({type:"POST",url:ajaxurl,data:{action:"woocommerce_do_ajax_product_import",position:c.position,mapping:c.mapping,file:c.file,update_existing:c.update_existing,security:c.security},dataType:"json",success:function(a){a.success&&(c.position=a.data.position,c.imported+=a.data.imported,c.failed+=a.data.failed,c.updated+=a.data.updated,c.skipped+=a.data.skipped,c.$form.find(".woocommerce-importer-progress").val(a.data.percentage),"done"===a.data.position?b.location=a.data.url+"&products-imported="+parseInt(c.imported,10)+"&products-failed="+parseInt(c.failed,10)+"&products-updated="+parseInt(c.updated,10)+"&products-skipped="+parseInt(c.skipped,10):c.run_import())}}).fail(function(a){b.console.log(a)})},a.fn.wc_product_importer=function(){return new c(this),this},a(".woocommerce-importer").wc_product_importer()}(jQuery,window);

View File

@ -1,10 +1,30 @@
/* global woocommerce_admin */
/**
* WooCommerce Admin JS
*/
jQuery( function ( $ ) {
// Add buttons to product screen.
var $product_screen = $( '.edit-php.post-type-product' ),
$title_action = $product_screen.find( '.page-title-action:first' ),
$blankslate = $product_screen.find( '.woocommerce-BlankState' );
if ( 0 === $blankslate.length ) {
$title_action.after( '<a href="' + woocommerce_admin.urls.export_products + '" class="page-title-action">' + woocommerce_admin.strings.export_products + '</a>' );
$title_action.after( '<a href="' + woocommerce_admin.urls.import_products + '" class="page-title-action">' + woocommerce_admin.strings.import_products + '</a>' );
} else {
$title_action.hide();
}
// Progress indicators when showing steps.
$( '.woocommerce-progress-form-wrapper .button-next' ).on( 'click', function() {
$('.wc-progress-form-content').block({
message: null,
overlayCSS: {
background: '#fff',
opacity: 0.6
}
});
return true;
} );
// Field validation error tips
$( document.body )

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -435,7 +435,8 @@ abstract class WC_Data {
}
if ( ! empty( $this->cache_group ) ) {
$cache_key = WC_Cache_Helper::get_cache_prefix( $this->cache_group ) . 'object_meta_' . $this->get_id();
// Prefix by group allows invalidation by group until https://core.trac.wordpress.org/ticket/4476 is implemented.
$cache_key = WC_Cache_Helper::get_cache_prefix( $this->cache_group ) . WC_Cache_Helper::get_cache_prefix( 'object_' . $this->get_id() ) . 'object_meta_' . $this->get_id();
}
if ( ! $force_read ) {
@ -483,9 +484,9 @@ abstract class WC_Data {
$this->data_store->update_meta( $this, $meta );
}
}
if ( ! empty( $this->cache_group ) ) {
WC_Cache_Helper::incr_cache_prefix( $this->cache_group );
$cache_key = WC_Cache_Helper::get_cache_prefix( $this->cache_group ) . WC_Cache_Helper::get_cache_prefix( 'object_' . $this->get_id() ) . 'object_meta_' . $this->get_id();
wp_cache_delete( $cache_key, $this->cache_group );
}
}

View File

@ -782,14 +782,22 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
}
/**
* Get an order item object, based on it's type.
* Get an order item object, based on it's ID. The item must belong to the current
* order. If the item cannot be found or it doens't belong to current order
* FALSE will be returned.
*
* @since 3.0.0
* @param int $item_id
* @return WC_Order_Item
*/
public function get_item( $item_id ) {
return WC_Order_Factory::get_order_item( $item_id );
$type = $this->data_store->get_order_item_type( $this, $item_id );
if ( ! $type ) {
return false;
}
$items = $this->get_items( $type );
return ! empty( $items[ $item_id ] ) ? $items[ $item_id ] : false;
}
/**

View File

@ -1688,7 +1688,7 @@ class WC_Product extends WC_Abstract_Legacy_Product {
* @return int Quantity or -1 if unlimited.
*/
public function get_max_purchase_quantity() {
return $this->is_sold_individually() ? 1 : ( $this->backorders_allowed() || ! $this->get_manage_stock() ? -1 : $this->get_stock_quantity() );
return $this->is_sold_individually() ? 1 : ( $this->backorders_allowed() || ! $this->managing_stock() ? -1 : $this->get_stock_quantity() );
}
/**

View File

@ -362,6 +362,11 @@ class WC_Admin_Addons {
* Handles output of the addons page in admin.
*/
public static function output() {
if ( isset( $_GET['section'] ) && 'helper' === $_GET['section'] ) {
do_action( 'woocommerce_helper_output' );
return;
}
$sections = self::get_sections();
$theme = wp_get_theme();
$section_keys = array_keys( $sections );

View File

@ -72,7 +72,6 @@ class WC_Admin_Assets {
/**
* @deprecated 2.3
*/
if ( has_action( 'woocommerce_admin_css' ) ) {
do_action( 'woocommerce_admin_css' );
wc_deprecated_function( 'The woocommerce_admin_css action', '2.3', 'admin_enqueue_scripts' );
@ -155,6 +154,14 @@ class WC_Admin_Assets {
'i18_sale_less_than_regular_error' => __( 'Please enter in a value less than the regular price.', 'woocommerce' ),
'decimal_point' => $decimal,
'mon_decimal_point' => wc_get_price_decimal_separator(),
'strings' => array(
'import_products' => __( 'Import', 'woocommerce' ),
'export_products' => __( 'Export', 'woocommerce' ),
),
'urls' => array(
'import_products' => esc_url_raw( admin_url( 'edit.php?post_type=product&page=product_importer' ) ),
'export_products' => esc_url_raw( admin_url( 'edit.php?post_type=product&page=product_exporter' ) ),
),
);
wp_localize_script( 'woocommerce_admin', 'woocommerce_admin', $params );

View File

@ -0,0 +1,152 @@
<?php
/**
* Init WooCommerce data exporters.
*
* @author WooThemes
* @category Admin
* @package WooCommerce/Admin
* @version 3.1.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* WC_Admin_Exporters Class.
*/
class WC_Admin_Exporters {
/**
* Array of exporter IDs.
*
* @var string[]
*/
protected $exporters = array();
/**
* Constructor.
*/
public function __construct() {
add_action( 'admin_menu', array( $this, 'add_to_menus' ) );
add_action( 'admin_head', array( $this, 'hide_from_menus' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'admin_scripts' ) );
add_action( 'admin_init', array( $this, 'download_export_file' ) );
add_action( 'wp_ajax_woocommerce_do_ajax_product_export', array( $this, 'do_ajax_product_export' ) );
// Register WooCommerce exporters.
$this->exporters['product_exporter'] = array(
'menu' => 'edit.php?post_type=product',
'name' => __( 'Product Export', 'woocommerce' ),
'capability' => 'edit_products',
'callback' => array( $this, 'product_exporter' ),
);
}
/**
* Add menu items for our custom exporters.
*/
public function add_to_menus() {
foreach ( $this->exporters as $id => $exporter ) {
add_submenu_page( $exporter['menu'], $exporter['name'], $exporter['name'], $exporter['capability'], $id, $exporter['callback'] );
}
}
/**
* Hide menu items from view so the pages exist, but the menu items do not.
*/
public function hide_from_menus() {
global $submenu;
foreach ( $this->exporters as $id => $exporter ) {
if ( isset( $submenu[ $exporter['menu'] ] ) ) {
foreach ( $submenu[ $exporter['menu'] ] as $key => $menu ) {
if ( $id === $menu[2] ) {
unset( $submenu[ $exporter['menu'] ][ $key ] );
}
}
}
}
}
/**
* Enqueue scripts.
*/
public function admin_scripts() {
$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
wp_register_script( 'wc-product-export', WC()->plugin_url() . '/assets/js/admin/wc-product-export' . $suffix . '.js', array( 'jquery' ), WC_VERSION );
wp_localize_script( 'wc-product-export', 'wc_product_export_params', array(
'export_nonce' => wp_create_nonce( 'wc-product-export' ),
) );
}
/**
* Export page UI.
*/
public function product_exporter() {
include_once( WC_ABSPATH . 'includes/export/class-wc-product-csv-exporter.php' );
include_once( dirname( __FILE__ ) . '/views/html-admin-page-product-export.php' );
}
/**
* Serve the generated file.
*/
public function download_export_file() {
if ( isset( $_GET['action'], $_GET['nonce'] ) && wp_verify_nonce( $_GET['nonce'], 'product-csv' ) && 'download_product_csv' === $_GET['action'] ) {
include_once( WC_ABSPATH . 'includes/export/class-wc-product-csv-exporter.php' );
$exporter = new WC_Product_CSV_Exporter();
$exporter->export();
}
}
/**
* AJAX callback for doing the actual export to the CSV file.
*/
public function do_ajax_product_export() {
check_ajax_referer( 'wc-product-export', 'security' );
if ( ! current_user_can( 'edit_products' ) ) {
wp_die( -1 );
}
include_once( WC_ABSPATH . 'includes/export/class-wc-product-csv-exporter.php' );
$step = absint( $_POST['step'] );
$exporter = new WC_Product_CSV_Exporter();
if ( ! empty( $_POST['columns'] ) ) {
$exporter->set_column_names( $_POST['columns'] );
}
if ( ! empty( $_POST['selected_columns'] ) ) {
$exporter->set_columns_to_export( $_POST['selected_columns'] );
}
if ( ! empty( $_POST['export_meta'] ) ) {
$exporter->enable_meta_export( true );
}
if ( ! empty( $_POST['export_types'] ) ) {
$exporter->set_product_types_to_export( $_POST['export_types'] );
}
$exporter->set_page( $step );
$exporter->generate_file();
if ( 100 === $exporter->get_percent_complete() ) {
wp_send_json_success( array(
'step' => 'done',
'percentage' => 100,
'url' => add_query_arg( array( 'nonce' => wp_create_nonce( 'product-csv' ), 'action' => 'download_product_csv' ), admin_url( 'edit.php?post_type=product&page=product_exporter' ) ),
) );
} else {
wp_send_json_success( array(
'step' => ++$step,
'percentage' => $exporter->get_percent_complete(),
'columns' => $exporter->get_column_names(),
) );
}
}
}
new WC_Admin_Exporters();

View File

@ -1,41 +1,114 @@
<?php
/**
* Setup importers for WC data
* Init WooCommerce data importers.
*
* @author WooThemes
* @author Automattic
* @category Admin
* @package WooCommerce/Admin
* @version 2.5.0
* @version 3.1.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
if ( ! class_exists( 'WC_Admin_Importers', false ) ) :
/**
* WC_Admin_Importers Class.
*/
class WC_Admin_Importers {
/**
* Hook in tabs.
* Array of importer IDs.
*
* @var string[]
*/
protected $importers = array();
/**
* Constructor.
*/
public function __construct() {
add_action( 'admin_menu', array( $this, 'add_to_menus' ) );
add_action( 'admin_init', array( $this, 'register_importers' ) );
add_action( 'import_start', array( $this, 'post_importer_compatibility' ) );
add_action( 'admin_head', array( $this, 'hide_from_menus' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'admin_scripts' ) );
add_action( 'wp_ajax_woocommerce_do_ajax_product_import', array( $this, 'do_ajax_product_import' ) );
// Register WooCommerce importers.
$this->importers['product_importer'] = array(
'menu' => 'edit.php?post_type=product',
'name' => __( 'Product Import', 'woocommerce' ),
'capability' => 'edit_products',
'callback' => array( $this, 'product_importer' ),
);
}
/**
* Add menu items.
* Add menu items for our custom importers.
*/
public function add_to_menus() {
foreach ( $this->importers as $id => $importer ) {
add_submenu_page( $importer['menu'], $importer['name'], $importer['name'], $importer['capability'], $id, $importer['callback'] );
}
}
/**
* Hide menu items from view so the pages exist, but the menu items do not.
*/
public function hide_from_menus() {
global $submenu;
foreach ( $this->importers as $id => $importer ) {
if ( isset( $submenu[ $importer['menu'] ] ) ) {
foreach ( $submenu[ $importer['menu'] ] as $key => $menu ) {
if ( $id === $menu[2] ) {
unset( $submenu[ $importer['menu'] ][ $key ] );
}
}
}
}
}
/**
* Register importer scripts.
*/
public function admin_scripts() {
$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
wp_register_script( 'wc-product-import', WC()->plugin_url() . '/assets/js/admin/wc-product-import' . $suffix . '.js', array( 'jquery' ), WC_VERSION );
}
/**
* The product importer.
*
* This has a custom screen - the Tools > Import item is a placeholder.
* If we're on that screen, redirect to the custom one.
*/
public function product_importer() {
if ( defined( 'WP_LOAD_IMPORTERS' ) ) {
wp_safe_redirect( admin_url( 'edit.php?post_type=product&page=product_importer' ) );
exit;
}
include_once( WC_ABSPATH . 'includes/import/class-wc-product-csv-importer.php' );
include_once( WC_ABSPATH . 'includes/admin/importers/class-wc-product-csv-importer-controller.php' );
$importer = new WC_Product_CSV_Importer_Controller();
$importer->dispatch();
}
/**
* Register WordPress based importers.
*/
public function register_importers() {
register_importer( 'woocommerce_tax_rate_csv', __( 'WooCommerce tax rates (CSV)', 'woocommerce' ), __( 'Import <strong>tax rates</strong> to your store via a csv file.', 'woocommerce' ), array( $this, 'tax_rates_importer' ) );
if ( defined( 'WP_LOAD_IMPORTERS' ) ) {
add_action( 'import_start', array( $this, 'post_importer_compatibility' ) );
register_importer( 'woocommerce_product_csv', __( 'WooCommerce products (CSV)', 'woocommerce' ), __( 'Import <strong>products</strong> to your store via a csv file.', 'woocommerce' ), array( $this, 'product_importer' ) );
register_importer( 'woocommerce_tax_rate_csv', __( 'WooCommerce tax rates (CSV)', 'woocommerce' ), __( 'Import <strong>tax rates</strong> to your store via a csv file.', 'woocommerce' ), array( $this, 'tax_rates_importer' ) );
}
}
/**
* Add menu item.
* The tax rate importer which extends WP_Importer.
*/
public function tax_rates_importer() {
// Load Importer API
@ -58,7 +131,7 @@ class WC_Admin_Importers {
}
/**
* When running the WP importer, ensure attributes exist.
* When running the WP XML importer, ensure attributes exist.
*
* WordPress import should work - however, it fails to import custom product attribute taxonomies.
* This code grabs the file before it is imported and ensures the taxonomies are created.
@ -75,44 +148,38 @@ class WC_Admin_Importers {
$parser = new WXR_Parser();
$import_data = $parser->parse( $file );
if ( isset( $import_data['posts'] ) ) {
$posts = $import_data['posts'];
if ( isset( $import_data['posts'] ) && ! empty( $import_data['posts'] ) ) {
foreach ( $import_data['posts'] as $post ) {
if ( 'product' === $post['post_type'] && ! empty( $post['terms'] ) ) {
foreach ( $post['terms'] as $term ) {
if ( strstr( $term['domain'], 'pa_' ) ) {
if ( ! taxonomy_exists( $term['domain'] ) ) {
$attribute_name = wc_sanitize_taxonomy_name( str_replace( 'pa_', '', $term['domain'] ) );
if ( $posts && sizeof( $posts ) > 0 ) {
foreach ( $posts as $post ) {
if ( 'product' === $post['post_type'] ) {
if ( ! empty( $post['terms'] ) ) {
foreach ( $post['terms'] as $term ) {
if ( strstr( $term['domain'], 'pa_' ) ) {
if ( ! taxonomy_exists( $term['domain'] ) ) {
$attribute_name = wc_sanitize_taxonomy_name( str_replace( 'pa_', '', $term['domain'] ) );
// Create the taxonomy
if ( ! in_array( $attribute_name, wc_get_attribute_taxonomies() ) ) {
$attribute = array(
'attribute_label' => $attribute_name,
'attribute_name' => $attribute_name,
'attribute_type' => 'select',
'attribute_orderby' => 'menu_order',
'attribute_public' => 0,
);
$wpdb->insert( $wpdb->prefix . 'woocommerce_attribute_taxonomies', $attribute );
delete_transient( 'wc_attribute_taxonomies' );
}
// Register the taxonomy now so that the import works!
register_taxonomy(
$term['domain'],
apply_filters( 'woocommerce_taxonomy_objects_' . $term['domain'], array( 'product' ) ),
apply_filters( 'woocommerce_taxonomy_args_' . $term['domain'], array(
'hierarchical' => true,
'show_ui' => false,
'query_var' => true,
'rewrite' => false,
) )
);
}
// Create the taxonomy
if ( ! in_array( $attribute_name, wc_get_attribute_taxonomies() ) ) {
$attribute = array(
'attribute_label' => $attribute_name,
'attribute_name' => $attribute_name,
'attribute_type' => 'select',
'attribute_orderby' => 'menu_order',
'attribute_public' => 0,
);
$wpdb->insert( $wpdb->prefix . 'woocommerce_attribute_taxonomies', $attribute );
delete_transient( 'wc_attribute_taxonomies' );
}
// Register the taxonomy now so that the import works!
register_taxonomy(
$term['domain'],
apply_filters( 'woocommerce_taxonomy_objects_' . $term['domain'], array( 'product' ) ),
apply_filters( 'woocommerce_taxonomy_args_' . $term['domain'], array(
'hierarchical' => true,
'show_ui' => false,
'query_var' => true,
'rewrite' => false,
) )
);
}
}
}
@ -120,8 +187,64 @@ class WC_Admin_Importers {
}
}
}
/**
* Ajax callback for importing one batch of products from a CSV.
*/
public function do_ajax_product_import() {
check_ajax_referer( 'wc-product-import', 'security' );
if ( ! current_user_can( 'edit_products' ) || ! isset( $_POST['file'] ) ) {
wp_die( -1 );
}
include_once( WC_ABSPATH . 'includes/admin/importers/class-wc-product-csv-importer-controller.php' );
include_once( WC_ABSPATH . 'includes/import/class-wc-product-csv-importer.php' );
$file = wc_clean( $_POST['file'] );
$params = array(
'start_pos' => isset( $_POST['position'] ) ? absint( $_POST['position'] ) : 0,
'mapping' => isset( $_POST['mapping'] ) ? (array) $_POST['mapping'] : array(),
'update_existing' => isset( $_POST['update_existing'] ) ? (bool) $_POST['update_existing'] : false,
'lines' => apply_filters( 'woocommerce_product_import_batch_size', 10 ),
'parse' => true,
);
// Log failures.
if ( 0 !== $params['start_pos'] ) {
$error_log = array_filter( (array) get_user_option( 'product_import_error_log' ) );
} else {
$error_log = array();
}
$importer = WC_Product_CSV_Importer_Controller::get_importer( $file, $params );
$results = $importer->import();
$percent_complete = $importer->get_percent_complete();
$error_log = array_merge( $error_log, $results['failed'], $results['skipped'] );
update_user_option( get_current_user_id(), 'product_import_error_log', $error_log );
if ( 100 === $percent_complete ) {
wp_send_json_success( array(
'position' => 'done',
'percentage' => 100,
'url' => add_query_arg( array( 'nonce' => wp_create_nonce( 'product-csv' ) ), admin_url( 'edit.php?post_type=product&page=product_importer&step=done' ) ),
'imported' => count( $results['imported'] ),
'failed' => count( $results['failed'] ),
'updated' => count( $results['updated'] ),
'skipped' => count( $results['skipped'] ),
) );
} else {
wp_send_json_success( array(
'position' => $importer->get_file_position(),
'percentage' => $percent_complete,
'imported' => count( $results['imported'] ),
'failed' => count( $results['failed'] ),
'updated' => count( $results['updated'] ),
'skipped' => count( $results['skipped'] ),
) );
}
}
}
endif;
return new WC_Admin_Importers();
new WC_Admin_Importers();

View File

@ -51,7 +51,8 @@ class WC_Admin_Post_Types {
add_filter( 'post_row_actions', array( $this, 'row_actions' ), 2, 100 );
// Views
add_filter( 'views_edit-product', array( $this, 'product_sorting_link' ) );
add_filter( 'views_edit-product', array( $this, 'product_views' ) );
add_filter( 'disable_months_dropdown', array( $this, 'disable_months_dropdown' ), 10, 2 );
// Bulk / quick edit
add_action( 'bulk_edit_custom_box', array( $this, 'bulk_edit' ), 10, 2 );
@ -779,29 +780,43 @@ class WC_Admin_Post_Types {
}
/**
* Product sorting link.
*
* Based on Simple Page Ordering by 10up (https://wordpress.org/plugins/simple-page-ordering/).
* Change views on the edit product screen.
*
* @param array $views
* @return array
*/
public function product_sorting_link( $views ) {
global $post_type, $wp_query;
public function product_views( $views ) {
global $wp_query;
if ( ! current_user_can( 'edit_others_pages' ) ) {
return $views;
// Products do not have authors.
unset( $views['mine'] );
// Add sorting link.
if ( current_user_can( 'edit_others_pages' ) ) {
$class = ( isset( $wp_query->query['orderby'] ) && 'menu_order title' === $wp_query->query['orderby'] ) ? 'current' : '';
$query_string = remove_query_arg( array( 'orderby', 'order' ) );
$query_string = add_query_arg( 'orderby', urlencode( 'menu_order title' ), $query_string );
$query_string = add_query_arg( 'order', urlencode( 'ASC' ), $query_string );
$views['byorder'] = '<a href="' . esc_url( $query_string ) . '" class="' . esc_attr( $class ) . '">' . __( 'Sorting', 'woocommerce' ) . '</a>';
}
$class = ( isset( $wp_query->query['orderby'] ) && 'menu_order title' === $wp_query->query['orderby'] ) ? 'current' : '';
$query_string = remove_query_arg( array( 'orderby', 'order' ) );
$query_string = add_query_arg( 'orderby', urlencode( 'menu_order title' ), $query_string );
$query_string = add_query_arg( 'order', urlencode( 'ASC' ), $query_string );
$views['byorder'] = '<a href="' . esc_url( $query_string ) . '" class="' . esc_attr( $class ) . '">' . __( 'Sort products', 'woocommerce' ) . '</a>';
return $views;
}
/**
* @deprecated 3.1
*/
public function product_sorting_link( $views ) {
$this->product_views( $views );
}
/**
* Disable months dropdown on product screen.
*/
public function disable_months_dropdown( $bool, $post_type ) {
return 'product' === $post_type ? true : $bool;
}
/**
* Custom bulk edit - form.
*
@ -1447,12 +1462,12 @@ class WC_Admin_Post_Types {
global $wp_query;
// Category Filtering
wc_product_dropdown_categories();
wc_product_dropdown_categories( array( 'option_select_text' => __( 'Filter by category', 'woocommerce' ) ) );
// Type filtering
$terms = get_terms( 'product_type' );
$output = '<select name="product_type" id="dropdown_product_type">';
$output .= '<option value="">' . __( 'Show all product types', 'woocommerce' ) . '</option>';
$output .= '<option value="">' . __( 'Filter by product type', 'woocommerce' ) . '</option>';
foreach ( $terms as $term ) {
$output .= '<option value="' . sanitize_title( $term->name ) . '" ';
@ -1938,6 +1953,7 @@ class WC_Admin_Post_Types {
?>
<h2 class="woocommerce-BlankState-message"><?php _e( 'Ready to start selling something awesome?', 'woocommerce' ); ?></h2>
<a class="woocommerce-BlankState-cta button-primary button" href="<?php echo esc_url( admin_url( 'post-new.php?post_type=product&tutorial=true' ) ); ?>"><?php _e( 'Create your first product!', 'woocommerce' ); ?></a>
<a class="woocommerce-BlankState-cta button" href="<?php echo esc_url( admin_url( 'edit.php?post_type=product&page=product_importer' ) ); ?>"><?php _e( 'Import products from a CSV file', 'woocommerce' ); ?></a>
<?php
break;
}

View File

@ -853,6 +853,7 @@ class WC_Admin_Setup_Wizard {
<h2><?php esc_html_e( 'Next steps', 'woocommerce' ); ?></h2>
<ul>
<li class="setup-product"><a class="button button-primary button-large" href="<?php echo esc_url( admin_url( 'post-new.php?post_type=product&tutorial=true' ) ); ?>"><?php esc_html_e( 'Create your first product!', 'woocommerce' ); ?></a></li>
<li class="setup-product"><a class="button button-large" href="<?php echo esc_url( admin_url( 'edit.php?post_type=product&page=product_importer' ) ); ?>"><?php esc_html_e( 'Import products from a CSV file', 'woocommerce' ); ?></a></li>
</ul>
</div>
<div class="wc-setup-next-steps-last">

View File

@ -54,6 +54,8 @@ class WC_Admin {
include_once( dirname( __FILE__ ) . '/class-wc-admin-api-keys.php' );
include_once( dirname( __FILE__ ) . '/class-wc-admin-webhooks.php' );
include_once( dirname( __FILE__ ) . '/class-wc-admin-pointers.php' );
include_once( dirname( __FILE__ ) . '/class-wc-admin-importers.php' );
include_once( dirname( __FILE__ ) . '/class-wc-admin-exporters.php' );
// Help Tabs
if ( apply_filters( 'woocommerce_enable_admin_help_tab', true ) ) {
@ -73,6 +75,13 @@ class WC_Admin {
if ( defined( 'WP_LOAD_IMPORTERS' ) ) {
include_once( dirname( __FILE__ ) . '/class-wc-admin-importers.php' );
}
// Helper
include_once( dirname( __FILE__ ) . '/helper/class-wc-helper-options.php' );
include_once( dirname( __FILE__ ) . '/helper/class-wc-helper-api.php' );
include_once( dirname( __FILE__ ) . '/helper/class-wc-helper-updater.php' );
include_once( dirname( __FILE__ ) . '/helper/class-wc-helper-plugin-info.php' );
include_once( dirname( __FILE__ ) . '/helper/class-wc-helper.php' );
}
/**

View File

@ -0,0 +1,126 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* WC_Helper_API Class
*
* Provides a communication interface with the WooCommerce.com Helper API.
*/
class WC_Helper_API {
public static $api_base;
/**
* Load
*
* Allow devs to point the API base to a local API development or staging server.
* Note that sslverify will be turned off for the woocommerce.dev + WP_DEBUG combination.
* The URL can be changed on plugins_loaded before priority 10.
*/
public static function load() {
self::$api_base = apply_filters( 'woocommerce_helper_api_base', 'https://woocommerce.com/wp-json/helper/1.0' );
}
/**
* Perform an HTTP request to the Helper API.
*
* @param string $endpoint The endpoint to request.
* @param array $args Additional data for the request. Set authenticated to a truthy value to enable auth.
*
* @return array The response from wp_safe_remote_request()
*/
public static function request( $endpoint, $args = array() ) {
$url = self::url( $endpoint );
if ( ! empty( $args['authenticated'] ) ) {
self::_authenticate( $url, $args );
}
/**
* Allow developers to filter the request args passed to wp_safe_remote_request().
* Useful to remove sslverify when working on a local api dev environment.
*/
$args = apply_filters( 'woocommerce_helper_api_request_args', $args, $endpoint );
// TODO: Check response signatures on certain endpoints.
return wp_safe_remote_request( $url, $args );
}
/**
* Adds authentication headers to an HTTP request.
*
* @param string $url The request URI.
* @param array $args By-ref, the args that will be passed to wp_remote_request().
*/
private static function _authenticate( $url, &$args ) {
$auth = WC_Helper_Options::get( 'auth' );
$request_uri = parse_url( $url, PHP_URL_PATH );
$query_string = parse_url( $url, PHP_URL_QUERY );
if ( $query_string ) {
$request_uri .= '?' . $query_string;
}
$data = array(
'host' => parse_url( $url, PHP_URL_HOST ),
'request_uri' => $request_uri,
'method' => ! empty( $args['method'] ) ? $args['method'] : 'GET',
);
if ( ! empty( $args['body'] ) ) {
$data['body'] = $args['body'];
}
$signature = hash_hmac( 'sha256', json_encode( $data ), $auth['access_token_secret'] );
if ( empty( $args['headers'] ) ) {
$args['headers'] = array();
}
$args['headers'] = array(
'Authorization' => 'Bearer ' . $auth['access_token'],
'X-Woo-Signature' => $signature,
);
}
/**
* Wrapper for self::request().
*
* @param string $endpoint The helper API endpoint to request.
* @param array $args Arguments passed to wp_remote_request().
*
* @return array The response object from wp_safe_remote_request().
*/
public static function get( $endpoint, $args = array() ) {
$args['method'] = 'GET';
return self::request( $endpoint, $args );
}
/**
* Wrapper for self::request().
*
* @param string $endpoint The helper API endpoint to request.
* @param array $args Arguments passed to wp_remote_request().
*
* @return array The response object from wp_safe_remote_request().
*/
public static function post( $endpoint, $args = array() ) {
$args['method'] = 'POST';
return self::request( $endpoint, $args );
}
/**
* Using the API base, form a request URL from a given endpoint.
*
* @param string $endpoint The endpoint to request.
*
* @return string The absolute endpoint URL.
*/
public static function url( $endpoint ) {
$endpoint = ltrim( $endpoint, '/' );
$endpoint = sprintf( '%s/%s', self::$api_base, $endpoint );
$endpoint = esc_url_raw( $endpoint );
return $endpoint;
}
}
WC_Helper_API::load();

View File

@ -0,0 +1,54 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* WC_Helper_Options Class
*
* An interface to the woocommerce_helper_data entry in the wp_options table.
*/
class WC_Helper_Options {
/**
* The option name used to store the helper data.
*
* @var string
*/
private static $option_name = 'woocommerce_helper_data';
/**
* Update an option by key
*
* All helper options are grouped in a single options entry. This method
* is not thread-safe, use with caution.
*
* @param string $key The key to update.
* @param mixed $value The new option value.
*
* @return bool True if the option has been updated.
*/
public static function update( $key, $value ) {
$options = get_option( self::$option_name, array() );
$options[ $key ] = $value;
return update_option( self::$option_name, $options, true );
}
/**
* Get an option by key
*
* @see self::update
*
* @param string $key The key to fetch.
* @param mixed $default The default option to return if the key does not exist.
*
* @return mixed An option or the default.
*/
public static function get( $key, $default = false ) {
$options = get_option( self::$option_name, array() );
if ( array_key_exists( $key, $options ) ) {
return $options[ $key ];
}
return $default;
}
}

View File

@ -0,0 +1,70 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* WC_Helper_Plugin_Info Class
*
* Provides the "View Information" core modals with data for WooCommerce.com
* hosted extensions.
*/
class WC_Helper_Plugin_Info {
/**
* Loads the class, runs on init.
*/
public static function load() {
add_filter( 'plugins_api', array( __CLASS__, 'plugins_api' ), 20, 3 );
}
/**
* Plugin information callback for Woo extensions.
*
* @param object $response The response core needs to display the modal.
* @param string $action The requested plugins_api() action.
* @param object $args Arguments passed to plugins_api().
*
* @return object An updated $response.
*/
public static function plugins_api( $response, $action, $args ) {
if ( 'plugin_information' !== $action ) {
return $response;
}
if ( empty( $args->slug ) ) {
return $response;
}
$found_plugin = null;
// Look through local Woo plugins by slugs.
foreach ( WC_Helper::get_local_woo_plugins() as $plugin ) {
$slug = dirname( $plugin['_filename'] );
if ( dirname( $plugin['_filename'] ) === $args->slug ) {
$plugin['_slug'] = $args->slug;
$found_plugin = $plugin;
break;
}
}
if ( ! $found_plugin ) {
return $response;
}
// Fetch the product information from the Helper API.
$request = WC_Helper_API::get( add_query_arg( array(
'product_id' => absint( $plugin['_product_id'] ),
'product_slug' => rawurlencode( $plugin['_slug'] ),
), 'info' ), array( 'authenticated' => true ) );
$results = json_decode( wp_remote_retrieve_body( $request ), true );
if ( ! empty( $results ) ) {
$response = (object) $results;
}
return $response;
}
}
WC_Helper_Plugin_Info::load();

View File

@ -0,0 +1,240 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* WC_Helper_Updater Class
*
* Contains the logic to fetch available updates and hook into Core's update
* routines to serve WooCommerce.com-provided packages.
*/
class WC_Helper_Updater {
/**
* Loads the class, runs on init.
*/
public static function load() {
add_action( 'pre_set_site_transient_update_plugins', array( __CLASS__, 'transient_update_plugins' ), 21, 1 );
add_action( 'pre_set_site_transient_update_themes', array( __CLASS__, 'transient_update_themes' ), 21, 1 );
}
/**
* Runs in a cron thread, or in a visitor thread if triggered
* by _maybe_update_plugins(), or in an auto-update thread.
*
* @param object $transient The update_plugins transient object.
*
* @return object The same or a modified version of the transient.
*/
public static function transient_update_plugins( $transient ) {
$update_data = self::get_update_data();
foreach ( WC_Helper::get_local_woo_plugins() as $plugin ) {
if ( empty( $update_data[ $plugin['_product_id'] ] ) ) {
continue;
}
$data = $update_data[ $plugin['_product_id'] ];
$filename = $plugin['_filename'];
$item = array(
'id' => 'woo-' . $plugin['_product_id'],
'slug' => $data['slug'],
'plugin' => $filename,
'new_version' => $data['version'],
'url' => $data['url'],
'package' => '',
'upgrade_notice' => $data['upgrade_notice'],
);
if ( self::_has_active_subscription( $plugin['_product_id'] ) ) {
$item['package'] = $data['package'];
}
if ( version_compare( $plugin['Version'], $data['version'], '<' ) ) {
$transient->response[ $filename ] = (object) $item;
unset( $transient->no_update[ $filename ] );
} else {
$transient->no_update[ $filename ] = (object) $item;
unset( $transient->response[ $filename ] );
}
}
return $transient;
}
/**
* Runs on pre_set_site_transient_update_themes, provides custom
* packages for WooCommerce.com-hosted extensions.
*
* @param object $transient The update_themes transient object.
*
* @return object The same or a modified version of the transient.
*/
public static function transient_update_themes( $transient ) {
$update_data = self::get_update_data();
foreach ( WC_Helper::get_local_woo_themes() as $theme ) {
if ( empty( $update_data[ $theme['_product_id'] ] ) ) {
continue;
}
$data = $update_data[ $theme['_product_id'] ];
$slug = $theme['_stylesheet'];
$item = array(
'theme' => $slug,
'new_version' => $data['version'],
'url' => $data['url'],
'package' => '',
);
if ( self::_has_active_subscription( $theme['_product_id'] ) ) {
$item['package'] = $data['package'];
}
if ( version_compare( $theme['Version'], $data['version'], '<' ) ) {
$transient->response[ $slug ] = $item;
} else {
unset( $transient->response[ $slug ] );
$transient->checked[ $slug ] = $data['version'];
}
}
return $transient;
}
/**
* Get update data for all extensions.
*
* Scans through all subscriptions for the connected user, as well
* as all Woo extensions without a subscription, and obtains update
* data for each product.
*
* @return array Update data {product_id => data}
*/
public static function get_update_data() {
$payload = array();
// Scan subscriptions.
foreach ( WC_Helper::get_subscriptions() as $subscription ) {
$payload[ $subscription['product_id'] ] = array(
'product_id' => $subscription['product_id'],
'file_id' => '',
);
}
// Scan local plugins which may or may not have a subscription.
foreach ( WC_Helper::get_local_woo_plugins() as $data ) {
if ( ! isset( $payload[ $data['_product_id'] ] ) ) {
$payload[ $data['_product_id'] ] = array(
'product_id' => $data['_product_id'],
);
}
$payload[ $data['_product_id'] ]['file_id'] = $data['_file_id'];
}
// Scan local themes
foreach ( WC_Helper::get_local_woo_themes() as $data ) {
if ( ! isset( $payload[ $data['_product_id'] ] ) ) {
$payload[ $data['_product_id'] ] = array(
'product_id' => $data['_product_id'],
);
}
$payload[ $data['_product_id'] ]['file_id'] = $data['_file_id'];
}
return self::_update_check( $payload );
}
/**
* Run an update check API call.
*
* The call is cached based on the payload (product ids, file ids). If
* the payload changes, the cache is going to miss.
*
* @return array Update data for each requested product.
*/
private static function _update_check( $payload ) {
ksort( $payload );
$hash = md5( json_encode( $payload ) );
$cache_key = '_woocommerce_helper_updates';
if ( false !== ( $data = get_transient( $cache_key ) ) ) {
if ( hash_equals( $hash, $data['hash'] ) ) {
return $data['products'];
}
}
$data = array(
'hash' => $hash,
'updated' => time(),
'products' => array(),
'errors' => array(),
);
$request = WC_Helper_API::post( 'update-check', array(
'body' => json_encode( array( 'products' => $payload ) ),
'authenticated' => true,
) );
if ( wp_remote_retrieve_response_code( $request ) !== 200 ) {
$data['errors'][] = 'http-error';
} else {
$data['products'] = json_decode( wp_remote_retrieve_body( $request ), true );
}
set_transient( $cache_key, $data, 12 * HOUR_IN_SECONDS );
return $data['products'];
}
/**
* Check for an active subscription.
*
* Checks a given product id against all subscriptions on
* the current site. Returns true if at least one active
* subscription is found.
*
* @param int $product_id The product id to look for.
*
* @return bool True if active subscription found.
*/
private static function _has_active_subscription( $product_id ) {
if ( ! isset( $auth ) ) {
$auth = WC_Helper_Options::get( 'auth' );
}
if ( ! isset( $subscriptions ) ) {
$subscriptions = WC_Helper::get_subscriptions();
}
if ( empty( $auth['site_id'] ) || empty( $subscriptions ) ) {
return false;
}
// Check for an active subscription.
foreach ( $subscriptions as $subscription ) {
if ( $subscription['product_id'] != $product_id ) {
continue;
}
if ( in_array( absint( $auth['site_id'] ), $subscription['connections'] ) ) {
return true;
}
}
return false;
}
/**
* Flushes cached update data.
*/
public static function flush_updates_cache() {
delete_transient( '_woocommerce_helper_updates' );
}
}
WC_Helper_Updater::load();

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,238 @@
<?php defined( 'ABSPATH' ) or exit(); ?>
<div class="wrap woocommerce wc_addons_wrap wc-helper">
<?php include( WC_Helper::get_view_filename( 'html-section-nav.php' ) ); ?>
<h1 class="screen-reader-text"><?php _e( 'WooCommerce Extensions', 'woocommerce' ); ?></h1>
<?php include( WC_Helper::get_view_filename( 'html-section-notices.php' ) ); ?>
<?php include( WC_Helper::get_view_filename( 'html-section-account.php' ) ); ?>
<h2><?php _e( 'Subscriptions', 'woocommerce' ); ?></h2>
<p><?php _e( 'Below is a list of products available on your WooCommerce.com account. To receive plugin updates please make sure the product is installed, activated and connected to your WooCommerce.com account.', 'woocommerce' ); ?></p>
<table class="wp-list-table widefat fixed striped">
<?php if ( ! empty( $subscriptions ) ) : ?>
<?php foreach ( $subscriptions as $subscription ) : ?>
<?php
$installed = ! empty( $subscription['local']['installed'] );
$connected = $subscription['active'];
$product_id = $subscription['product_id'];
$update_available = false;
if ( $installed && ! empty( $updates[ $product_id ] ) &&
version_compare( $updates[ $product_id ]['version'], $subscription['local']['version'], '>' ) ) {
$update_available = true;
}
$download_url = $subscription['product_url'];
if ( ! $installed && ! empty( $updates[ $product_id ]['package'] ) ) {
$download_url = $updates[ $product_id ]['package'];
}
$classes = array(
'color-bar' => true,
'expired' => $subscription['expired'],
'expiring' => $subscription['expiring'],
'update-available' => $update_available,
'autorenews' => $subscription['autorenew'],
);
$classes = array_filter( $classes, function( $i ) {
return (bool) $i;
} );
$classes = array_keys( $classes );
?>
<tbody>
<tr class="wp-list-table__row is-ext-header">
<td class="wp-list-table__ext-details <?php echo implode( ' ', $classes ); ?>">
<div class="wp-list-table__ext-title">
<a href="<?php echo esc_url( $subscription['product_url'] ); ?>" target="_blank"><?php
echo esc_html( $subscription['product_name'] ); ?></a>
</div>
<div class="wp-list-table__ext-description">
<?php if ( $subscription['expired'] ) : ?>
<span class="renews">
<strong><?php _e( 'Expired :(', 'woocommerce' ); ?></strong>
<?php echo date_i18n( 'F jS, Y', $subscription['expires'] ); ?>
</span>
<?php elseif ( $subscription['autorenew'] ) : ?>
<span class="renews">
<?php _e( 'Auto renews on:', 'woocommerce' ); ?>
<?php echo date_i18n( 'F jS, Y', $subscription['expires'] ); ?>
</span>
<?php elseif ( $subscription['expiring'] ) : ?>
<span class="renews">
<strong><?php _e( 'Expiring soon!', 'woocommerce' ); ?></strong>
<?php echo date_i18n( 'F jS, Y', $subscription['expires'] ); ?>
</span>
<?php else : ?>
<span class="renews">
<?php _e( 'Expires on:', 'woocommerce' ); ?>
<?php echo date_i18n( 'F jS, Y', $subscription['expires'] ); ?>
</span>
<?php endif; ?>
<br/>
<span class="subscription">
<?php
if ( $subscription['sites_max'] > 0 ) {
/* translators: %1$d: sites active, %2$d max sites active */
printf( __( 'Subscription: Using %1$d of %2$d sites available', 'woocommerce' ), $subscription['sites_active'], $subscription['sites_max'] );
} else {
_e( 'Subscription: Unlimited', 'woocommerce' );
}
?>
</span>
</div>
</td>
<td class="wp-list-table__ext-actions">
<?php if ( ! $installed && ! $subscription['expired'] ) : ?>
<a class="button" href="<?php echo esc_url( $download_url ); ?>" target="_blank"><?php _e( 'Download', 'woocommerce' ); ?></a>
<?php elseif ( $connected ) : ?>
<!-- TODO: Replace with a toggle -->
<a class="button" href="<?php echo esc_url( $subscription['deactivate_url'] ); ?>"><?php _e( 'Deactivate', 'woocommerce' ); ?></a>
<?php elseif ( ! $subscription['expired'] ) : ?>
<a class="button" href="<?php echo esc_url( $subscription['activate_url'] ); ?>"><?php _e( 'Activate', 'woocommerce' ); ?></a>
<?php else : ?>
<a class="button disabled" href="#"><?php _e( 'Activate', 'woocommerce' ); ?></a>
<?php endif; ?>
</td>
</tr>
<?php if ( $update_available && ! $subscription['expired'] ) : ?>
<tr class="wp-list-table__row wp-list-table__ext-updates">
<td class="wp-list-table__ext-status update-available">
<p><span class="dashicons dashicons-update"></span>
<?php /* translators: %s: version number */ ?>
<?php printf( __( 'Version %s is <strong>available</strong>.', 'woocommerce' ), esc_html( $updates[ $product_id ]['version'] ) ); ?>
<?php if ( ! $connected ) : ?>
<?php _e( 'To enable this update you need to <strong>activate</strong> this subscription.', 'woocommerce' ); ?>
<?php endif; ?>
</p>
</td>
<td class="wp-list-table__ext-actions">
<?php if ( $connected ) : ?>
<a class="button" href="<?php echo esc_url( $subscription['update_url'] ); ?>"><?php _e( 'Update', 'woocommerce' ); ?></a>
<!-- TODO: Activate & Update -->
<?php endif; ?>
</td>
</tr>
<?php endif; ?>
<?php if ( $update_available && $subscription['expired'] ) : ?>
<tr class="wp-list-table__row wp-list-table__ext-updates">
<td class="wp-list-table__ext-status expired">
<p><span class="dashicons dashicons-info"></span>
<?php /* translators: %s: version number */ ?>
<?php printf( __( 'Version %s is <strong>available</strong>.', 'woocommerce' ), esc_html( $updates[ $product_id ]['version'] ) ); ?>
<?php _e( 'To enable this update you need to <strong>purchase</strong> a new subscription.', 'woocommerce' ); ?>
</p>
</td>
<td class="wp-list-table__ext-actions">
<a class="button" href="<?php echo esc_url( $subscription['product_url'] ); ?>" target="_blank"><?php _e( 'Purchase', 'woocommerce' ); ?></a>
</td>
</tr>
<?php endif; ?>
<?php if ( $subscription['expiring'] && ! $subscription['autorenew'] ) : ?>
<tr class="wp-list-table__row wp-list-table__ext-updates">
<td class="wp-list-table__ext-status expired">
<p><span class="dashicons dashicons-info"></span>
<?php _e( 'Subscription is <strong>expiring</strong> soon.', 'woocommerce' ); ?>
</p>
</td>
<td class="wp-list-table__ext-actions">
<a class="button" href="https://woocommerce.com/my-account/my-subscriptions/" target="_blank"><?php _e( 'Enable auto-renew', 'woocommerce' ); ?></a>
</td>
</tr>
<?php endif; ?>
<?php if ( ! $connected && $subscription['sites_max'] > 0 && $subscription['sites_active'] >= $subscription['sites_max'] ) : ?>
<tr class="wp-list-table__row wp-list-table__ext-updates">
<td class="wp-list-table__ext-status expired">
<p><span class="dashicons dashicons-info"></span>
<?php _e( 'You are already using the <strong>maximum number of sites available</strong> with your current subscription.', 'woocommerce' ); ?>
</p>
</td>
<td class="wp-list-table__ext-actions">
<a class="button" href="https://woocommerce.com/my-account/my-subscriptions/" target="_blank"><?php _e( 'Upgrade', 'woocommerce' ); ?></a>
</td>
</tr>
<?php endif; ?>
</tbody>
<?php endforeach; ?>
<?php else : ?>
<tr>
<td colspan="3"><em><?php _e( 'Could not find any subscriptions on your WooCommerce.com account', 'woocommerce' ); ?></td>
</tr>
<?php endif; ?>
</tbody>
</table>
<?php if ( ! empty( $no_subscriptions ) ) : ?>
<h2><?php _e( 'Installed Extensions without a Subscription', 'woocommerce' ); ?></h2>
<p>Below is a list of WooCommerce.com products available on your site - but are either out-dated or do not have a valid subscription.</p>
<table class="wp-list-table widefat fixed striped">
<?php /* Extensions without a subscription. */ ?>
<?php foreach ( $no_subscriptions as $filename => $data ) : ?>
<?php
$product_id = $data['_product_id'];
$update_available = false;
if ( ! empty( $updates[ $product_id ] ) &&
version_compare( $updates[ $product_id ]['version'], $data['Version'], '>' ) ) {
$update_available = true;
}
$product_url = '#';
if ( ! empty( $updates[ $product_id ]['url'] ) ) {
$product_url = $updates[ $product_id ]['url'];
} elseif ( ! empty( $data['PluginURI'] ) ) {
$product_url = $data['PluginURI'];
}
?>
<tbody>
<tr class="wp-list-table__row is-ext-header">
<td class="wp-list-table__ext-details color-bar autorenews">
<div class="wp-list-table__ext-title">
<a href="<?php echo esc_url( $product_url ); ?>" target="_blank"><?php echo esc_html( $data['Name'] ); ?></a>
</div>
<div class="wp-list-table__ext-description">
</div>
</td>
<td class="wp-list-table__ext-actions">
<span class="form-toggle__wrapper">
<input type="checkbox" class="form-toggle is-compact" readonly="" value="on">
<label class="form-toggle__label" for="activate-akismet-undefined">
<span class="form-toggle__label-content">
<label class="plugin-action__label" for="activate-akismet-undefined">Inactive</label>
</span>
<span class="form-toggle__switch" id="activate-akismet-undefined" role="checkbox" aria-checked="false" tabindex="0"></span>
</label>
</span>
</td>
</tr>
<?php if ( $update_available ) : ?>
<tr class="wp-list-table__row wp-list-table__ext-updates">
<td class="wp-list-table__ext-status update-available">
<p><span class="dashicons dashicons-update"></span>
<?php printf( __( 'Version %s is available. To enable this update you need to <strong>purchase</strong> a new subscription.', 'woocommerce' ), esc_html( $updates[ $product_id ]['version'] ) ); ?>
</p>
</td>
<td class="wp-list-table__ext-actions">
<a class="button" href="<?php echo esc_url( $product_url ); ?>" target="_blank"><?php _e( 'Purchase', 'woocommerce' ); ?></a>
</td>
</tr>
<?php endif; ?>
</tbody>
<?php endforeach; ?>
</table>
<?php endif; ?>
</div>

View File

@ -0,0 +1,21 @@
<?php defined( 'ABSPATH' ) or exit(); ?>
<div class="wrap woocommerce wc_addons_wrap wc-helper">
<?php include( WC_Helper::get_view_filename( 'html-section-nav.php' ) ); ?>
<h1 class="screen-reader-text"><?php _e( 'WooCommerce Extensions', 'woocommerce' ); ?></h1>
<?php include( WC_Helper::get_view_filename( 'html-section-notices.php' ) ); ?>
<div class="start-container">
<div class="text">
<img src="https://woocommerce.com/wp-content/themes/woomattic/images/logo-woocommerce@2x.png" alt="WooCommerce" style="width:180px;">
<?php if ( ! empty( $_GET['wc-helper-status'] ) && 'helper-disconnected' === $_GET['wc-helper-status'] ) : ?>
<p><?php _e( '<strong>Sorry to see you go</strong>. Feel free to reconnect again using the button below.', 'woocommerce' ); ?></p>
<?php endif; ?>
<h2><?php _e( 'Connect your store to your WooCommerce.com account, and manage your purchases directly from your personal site', 'woocommerce' ); ?></h2>
<p><?php _e( 'When you connect your account, your purchases will automatically be listed on your site. You can disconnect the account at anytime. Once your connected, you will receive the updates &amp; support that come with your subscription. ', 'woocommerce' ); ?></p>
<p><a class="button-primary" href="<?php echo esc_url( $connect_url ); ?>"><?php _e( 'Connect your store', 'woocommerce' ); ?></a></p>
</div>
</div>
</div>

View File

@ -0,0 +1,15 @@
<?php defined( 'ABSPATH' ) or exit(); ?>
<div class="connect-wrapper">
<div class="connected active">
<div class="user-info">
<?php echo get_avatar( $auth_user_data['email'], 100 ); ?>
<p><?php printf( __( 'Connected to: <strong>%s</strong>', 'woocommerce' ), esc_html( $auth_user_data['email'] ) ); ?></p>
<span class="chevron dashicons dashicons-arrow-up-alt2"></span>
</div>
<div class="buttons active">
<a class="button button-secondary" href="<?php echo esc_url( $disconnect_url ); ?>">Disconnect</a>
<a class="button" href="<?php echo esc_url( $refresh_url ); ?>">Refresh</a>
</div>
</div>
</div>

View File

@ -0,0 +1,6 @@
<?php defined( 'ABSPATH' ) or exit(); ?>
<nav class="nav-tab-wrapper woo-nav-tab-wrapper">
<a href="<?php echo esc_url( admin_url( 'admin.php?page=wc-addons' ) ); ?>" class="nav-tab"><?php _e( 'Browse Extensions', 'woocommerce' ); ?></a>
<a href="<?php echo esc_url( admin_url( 'admin.php?page=wc-addons&section=helper' ) ); ?>" class="nav-tab nav-tab-active"><?php _e( 'WooCommerce.com Subscriptions', 'woocommerce' ); ?></a>
</nav>

View File

@ -0,0 +1,7 @@
<?php defined( 'ABSPATH' ) or exit(); ?>
<?php foreach ( $notices as $notice ) : ?>
<div class="notice <?php echo sanitize_html_class( $notice['type'] ); ?>">
<?php echo wpautop( $notice['message'] ); ?>
</div>
<?php endforeach; ?>

View File

@ -0,0 +1,579 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
if ( ! class_exists( 'WP_Importer' ) ) {
return;
}
/**
* Product importer controller - handles file upload and forms in admin.
*
* @author Automattic
* @category Admin
* @package WooCommerce/Admin/Importers
* @version 3.1.0
*/
class WC_Product_CSV_Importer_Controller {
/**
* The path to the current file.
*
* @var string
*/
protected $file = '';
/**
* The current import step.
*
* @var string
*/
protected $step = '';
/**
* Progress steps.
*
* @var array
*/
protected $steps = array();
/**
* Errors.
*
* @var array
*/
protected $errors = array();
/**
* The current delimiter for the file being read.
*
* @var string
*/
protected $delimiter = ',';
/**
* Whether to skip existing products.
*
* @var bool
*/
protected $update_existing = false;
/**
* Get importer instance.
*
* @param string $file File to import.
* @param array $args Importer arguments.
* @return WC_Product_CSV_Importer
*/
public static function get_importer( $file, $args = array() ) {
$importer_class = apply_filters( 'woocommerce_product_csv_importer_class', 'WC_Product_CSV_Importer' );
return new $importer_class( $file, $args );
}
/**
* Constructor.
*/
public function __construct() {
$this->steps = array(
'upload' => array(
'name' => __( 'Upload CSV file', 'woocommerce' ),
'view' => array( $this, 'upload_form' ),
'handler' => array( $this, 'upload_form_handler' ),
),
'mapping' => array(
'name' => __( 'Column mapping', 'woocommerce' ),
'view' => array( $this, 'mapping_form' ),
'handler' => '',
),
'import' => array(
'name' => __( 'Import', 'woocommerce' ),
'view' => array( $this, 'import' ),
'handler' => '',
),
'done' => array(
'name' => __( 'Done!', 'woocommerce' ),
'view' => array( $this, 'done' ),
'handler' => '',
),
);
$this->step = isset( $_REQUEST['step'] ) ? sanitize_key( $_REQUEST['step'] ) : current( array_keys( $this->steps ) );
$this->file = isset( $_REQUEST['file'] ) ? wc_clean( $_REQUEST['file'] ) : '';
$this->update_existing = isset( $_REQUEST['update_existing'] ) ? (bool) $_REQUEST['update_existing'] : false;
}
/**
* Get the URL for the next step's screen.
* @param string step slug (default: current step)
* @return string URL for next step if a next step exists.
* Admin URL if it's the last step.
* Empty string on failure.
*/
public function get_next_step_link( $step = '' ) {
if ( ! $step ) {
$step = $this->step;
}
$keys = array_keys( $this->steps );
if ( end( $keys ) === $step ) {
return admin_url();
}
$step_index = array_search( $step, $keys );
if ( false === $step_index ) {
return '';
}
$params = array(
'step' => $keys[ $step_index + 1 ],
'file' => $this->file,
'delimiter' => $this->delimiter,
'update_existing' => $this->update_existing,
'_wpnonce' => wp_create_nonce( 'woocommerce-csv-importer' ), // wp_nonce_url() escapes & to &amp; breaking redirects.
);
return add_query_arg( $params );
}
/**
* Output header view.
*/
protected function output_header() {
include( dirname( __FILE__ ) . '/views/html-csv-import-header.php' );
}
/**
* Output steps view.
*/
protected function output_steps() {
include( dirname( __FILE__ ) . '/views/html-csv-import-steps.php' );
}
/**
* Output footer view.
*/
protected function output_footer() {
include( dirname( __FILE__ ) . '/views/html-csv-import-footer.php' );
}
/**
* Add error message.
*/
protected function add_error( $error ) {
$this->errors[] = $error;
}
/**
* Add error message.
*/
protected function output_errors() {
if ( $this->errors ) {
foreach ( $this->errors as $error ) {
echo '<div class="error inline"><p>' . esc_html( $error ) . '</p></div>';
}
}
}
/**
* Dispatch current step and show correct view.
*/
public function dispatch() {
if ( ! empty( $_POST['save_step'] ) && ! empty( $this->steps[ $this->step ]['handler'] ) ) {
call_user_func( $this->steps[ $this->step ]['handler'], $this );
}
$this->output_header();
$this->output_steps();
$this->output_errors();
call_user_func( $this->steps[ $this->step ]['view'], $this );
$this->output_footer();
}
/**
* Output information about the uploading process.
*/
protected function upload_form() {
$bytes = apply_filters( 'import_upload_size_limit', wp_max_upload_size() );
$size = size_format( $bytes );
$upload_dir = wp_upload_dir();
include( dirname( __FILE__ ) . '/views/html-product-csv-import-form.php' );
}
/**
* Handle the upload form and store options.
*/
public function upload_form_handler() {
check_admin_referer( 'woocommerce-csv-importer' );
$file = $this->handle_upload();
if ( is_wp_error( $file ) ) {
$this->add_error( $file->get_error_message() );
return;
} else {
$this->file = $file;
}
wp_redirect( esc_url_raw( $this->get_next_step_link() ) );
exit;
}
/**
* Handles the CSV upload and initial parsing of the file to prepare for
* displaying author import options.
*
* @return string|WP_Error
*/
public function handle_upload() {
if ( empty( $_POST['file_url'] ) ) {
if ( ! isset( $_FILES['import'] ) ) {
return new WP_Error( 'woocommerce_product_csv_importer_upload_file_empty', __( 'File is empty. Please upload something more substantial. This error could also be caused by uploads being disabled in your php.ini or by post_max_size being defined as smaller than upload_max_filesize in php.ini.', 'woocommerce' ) );
}
$overrides = array( 'test_form' => false, 'test_type' => false );
$_FILES['import']['name'] .= '.txt';
$upload = wp_handle_upload( $_FILES['import'], $overrides );
if ( isset( $upload['error'] ) ) {
return new WP_Error( 'woocommerce_product_csv_importer_upload_error', $upload['error'] );
}
// Construct the object array.
$object = array(
'post_title' => basename( $upload['file'] ),
'post_content' => $upload['url'],
'post_mime_type' => $upload['type'],
'guid' => $upload['url'],
'context' => 'import',
'post_status' => 'private',
);
// Save the data.
$id = wp_insert_attachment( $object, $upload['file'] );
/*
* Schedule a cleanup for one day from now in case of failed
* import or missing wp_import_cleanup() call.
*/
wp_schedule_single_event( time() + DAY_IN_SECONDS, 'importer_scheduled_cleanup', array( $id ) );
return $upload['file'];
} elseif ( file_exists( ABSPATH . $_POST['file_url'] ) ) {
return ABSPATH . $_POST['file_url'];
}
return new WP_Error( 'woocommerce_product_csv_importer_upload_invalid_file', __( 'Please upload or provide the link to a valid CSV file.', 'woocommerce' ) );
}
/**
* Mapping step.
*/
protected function mapping_form() {
$importer = self::get_importer( $this->file, array( 'lines' => 1 ) );
$headers = $importer->get_raw_keys();
$mapped_items = $this->auto_map_columns( $headers );
$sample = current( $importer->get_raw_data() );
if ( empty( $sample ) ) {
$this->add_error( __( 'The file is empty, please try again with a new file.', 'woocommerce' ) );
return;
}
include_once( dirname( __FILE__ ) . '/views/html-csv-import-mapping.php' );
}
/**
* Import the file if it exists and is valid.
*/
public function import() {
if ( ! is_file( $this->file ) ) {
$this->add_error( __( 'The file does not exist, please try again.', 'woocommerce' ) );
return;
}
if ( ! empty( $_POST['map_to'] ) ) {
$mapping = wp_unslash( $_POST['map_to'] );
} elseif ( ! empty( $_GET['auto_map'] ) ) {
// Auto mapping.
$importer = self::get_importer( $this->file, array( 'lines' => 1 ) );
$mapping = $this->auto_map_columns( $importer->get_raw_keys(), false );
} else {
wp_redirect( esc_url_raw( $this->get_next_step_link( 'upload' ) ) );
exit;
}
wp_localize_script( 'wc-product-import', 'wc_product_import_params', array(
'import_nonce' => wp_create_nonce( 'wc-product-import' ),
'mapping' => $mapping,
'file' => $this->file,
'update_existing' => $this->update_existing,
) );
wp_enqueue_script( 'wc-product-import' );
include_once( dirname( __FILE__ ) . '/views/html-csv-import-progress.php' );
}
/**
* Done step.
*/
protected function done() {
$imported = isset( $_GET['products-imported'] ) ? absint( $_GET['products-imported'] ) : 0;
$updated = isset( $_GET['products-updated'] ) ? absint( $_GET['products-updated'] ) : 0;
$failed = isset( $_GET['products-failed'] ) ? absint( $_GET['products-failed'] ) : 0;
$skipped = isset( $_GET['products-skipped'] ) ? absint( $_GET['products-skipped'] ) : 0;
$errors = array_filter( (array) get_user_option( 'product_import_error_log' ) );
include_once( dirname( __FILE__ ) . '/views/html-csv-import-done.php' );
}
/**
* Get default fields.
*
* @return array
*/
protected function get_default_fields() {
$fields = array(
'id',
'type',
'sku',
'name',
'status',
'featured',
'catalog_visibility',
'short_description',
'description',
'date_on_sale_from',
'date_on_sale_to',
'tax_status',
'tax_class',
'stock_status',
'backorders',
'sold_individually',
'weight',
'length',
'width',
'height',
'reviews_allowed',
'purchase_note',
'price',
'regular_price',
'manage_stock',
'stock_quantity',
'category_ids',
'tag_ids',
'shipping_class_id',
'images',
'downloads',
'download_limit',
'download_expiry',
'parent_id',
'grouped_products',
'upsell_ids',
'cross_sell_ids',
'product_url',
'button_text',
);
return apply_filters( 'woocommerce_csv_product_default_fields', $fields );
}
/**
* Auto map column names.
*
* @param array $raw_headers Raw header columns.
* @param bool $num_indexes If should use numbers or raw header columns as indexes.
* @return array
*/
protected function auto_map_columns( $raw_headers, $num_indexes = true ) {
$weight_unit = get_option( 'woocommerce_weight_unit' );
$dimension_unit = get_option( 'woocommerce_dimension_unit' );
include( dirname( __FILE__ ) . '/mappings/mappings.php' );
/**
* @hooked wc_importer_generic_mappings - 10
* @hooked wc_importer_wordpress_mappings - 10
*/
$default_columns = apply_filters( 'woocommerce_csv_product_import_mapping_default_columns', array(
__( 'ID', 'woocommerce' ) => 'id',
__( 'Type', 'woocommerce' ) => 'type',
__( 'SKU', 'woocommerce' ) => 'sku',
__( 'Name', 'woocommerce' ) => 'name',
__( 'Published', 'woocommerce' ) => 'published',
__( 'Is featured?', 'woocommerce' ) => 'featured',
__( 'Visibility in catalog', 'woocommerce' ) => 'catalog_visibility',
__( 'Short description', 'woocommerce' ) => 'short_description',
__( 'Description', 'woocommerce' ) => 'description',
__( 'Date sale price starts', 'woocommerce' ) => 'date_on_sale_from',
__( 'Date sale price ends', 'woocommerce' ) => 'date_on_sale_to',
__( 'Tax status', 'woocommerce' ) => 'tax_status',
__( 'Tax class', 'woocommerce' ) => 'tax_class',
__( 'In stock?', 'woocommerce' ) => 'stock_status',
__( 'Stock', 'woocommerce' ) => 'stock_quantity',
__( 'Backorders allowed?', 'woocommerce' ) => 'backorders',
__( 'Sold individually?', 'woocommerce' ) => 'sold_individually',
sprintf( __( 'Weight (%s)', 'woocommerce' ), $weight_unit ) => 'weight',
sprintf( __( 'Length (%s)', 'woocommerce' ), $dimension_unit ) => 'length',
sprintf( __( 'Width (%s)', 'woocommerce' ), $dimension_unit ) => 'width',
sprintf( __( 'Height (%s)', 'woocommerce' ), $dimension_unit ) => 'height',
__( 'Allow customer reviews?', 'woocommerce' ) => 'reviews_allowed',
__( 'Purchase note', 'woocommerce' ) => 'purchase_note',
__( 'Sale price', 'woocommerce' ) => 'sale_price',
__( 'Regular price', 'woocommerce' ) => 'regular_price',
__( 'Categories', 'woocommerce' ) => 'category_ids',
__( 'Tags', 'woocommerce' ) => 'tag_ids',
__( 'Shipping class', 'woocommerce' ) => 'shipping_class_id',
__( 'Images', 'woocommerce' ) => 'images',
__( 'Download limit', 'woocommerce' ) => 'download_limit',
__( 'Download expiry days', 'woocommerce' ) => 'download_expiry',
__( 'Parent', 'woocommerce' ) => 'parent_id',
__( 'Upsells', 'woocommerce' ) => 'upsell_ids',
__( 'Cross-sells', 'woocommerce' ) => 'cross_sell_ids',
__( 'Grouped products', 'woocommerce' ) => 'grouped_products',
__( 'External URL', 'woocommerce' ) => 'product_url',
__( 'Button text', 'woocommerce' ) => 'button_text',
) );
$special_columns = array_map(
array( $this, 'sanitize_special_column_name_regex' ),
apply_filters( 'woocommerce_csv_product_import_mapping_special_columns',
array(
'attributes:name' => __( 'Attribute %d name', 'woocommerce' ),
'attributes:value' => __( 'Attribute %d value(s)', 'woocommerce' ),
'attributes:visible' => __( 'Attribute %d visible', 'woocommerce' ),
'attributes:taxonomy' => __( 'Attribute %d global', 'woocommerce' ),
'attributes:default' => __( 'Attribute %d default', 'woocommerce' ),
'downloads:name' => __( 'Download %d name', 'woocommerce' ),
'downloads:url' => __( 'Download %d URL', 'woocommerce' ),
'meta:' => __( 'Meta: %s', 'woocommerce' ),
)
)
);
$headers = array();
foreach ( $raw_headers as $key => $field ) {
$index = $num_indexes ? $key : $field;
$headers[ $index ] = $field;
if ( isset( $default_columns[ $field ] ) ) {
$headers[ $index ] = $default_columns[ $field ];
} else {
foreach ( $special_columns as $special_key => $regex ) {
if ( preg_match( $regex, $field, $matches ) ) {
$headers[ $index ] = $special_key . $matches[1];
break;
}
}
}
}
return apply_filters( 'woocommerce_csv_product_import_mapped_columns', $headers, $raw_headers );
}
/**
* Sanitize special column name regex.
*
* @param string $value Raw special column name.
* @return string
*/
protected function sanitize_special_column_name_regex( $value ) {
return '/' . str_replace( array( '%d', '%s' ), '(.*)', quotemeta( $value ) ) . '/';
}
/**
* Get mapping options.
*
* @param string $item Item name
* @return array
*/
protected function get_mapping_options( $item = '' ) {
// Get index for special column names.
$index = $item;
if ( preg_match( '/\d+$/', $item, $matches ) ) {
$index = $matches[0];
}
// Properly format for meta field.
$meta = str_replace( 'meta:', '', $item );
// Available options.
$weight_unit = get_option( 'woocommerce_weight_unit' );
$dimension_unit = get_option( 'woocommerce_dimension_unit' );
$options = array(
'id' => __( 'ID', 'woocommerce' ),
'type' => __( 'Type', 'woocommerce' ),
'sku' => __( 'SKU', 'woocommerce' ),
'name' => __( 'Name', 'woocommerce' ),
'published' => __( 'Published', 'woocommerce' ),
'featured' => __( 'Is featured?', 'woocommerce' ),
'catalog_visibility' => __( 'Visibility in catalog', 'woocommerce' ),
'short_description' => __( 'Short description', 'woocommerce' ),
'description' => __( 'Description', 'woocommerce' ),
'date_on_sale_from' => __( 'Date sale price starts', 'woocommerce' ),
'date_on_sale_to' => __( 'Date sale price ends', 'woocommerce' ),
'tax_status' => __( 'Tax status', 'woocommerce' ),
'tax_class' => __( 'Tax class', 'woocommerce' ),
'stock_status' => __( 'In stock?', 'woocommerce' ),
'backorders' => __( 'Backorders allowed?', 'woocommerce' ),
'sold_individually' => __( 'Sold individually?', 'woocommerce' ),
/* translators: %s: weight unit */
'weight' => sprintf( __( 'Weight (%s)', 'woocommerce' ), $weight_unit ),
'dimensions' => array(
'name' => __( 'Dimensions', 'woocommerce' ),
'options' => array(
/* translators: %s: dimension unit */
'length' => sprintf( __( 'Length (%s)', 'woocommerce' ), $dimension_unit ),
/* translators: %s: dimension unit */
'width' => sprintf( __( 'Width (%s)', 'woocommerce' ), $dimension_unit ),
/* translators: %s: dimension unit */
'height' => sprintf( __( 'Height (%s)', 'woocommerce' ), $dimension_unit ),
),
),
'reviews_allowed' => __( 'Allow customer reviews?', 'woocommerce' ),
'purchase_note' => __( 'Purchase note', 'woocommerce' ),
'sale_price' => __( 'Sale price', 'woocommerce' ),
'regular_price' => __( 'Regular Price', 'woocommerce' ),
'stock_quantity' => __( 'Stock', 'woocommerce' ),
'category_ids' => __( 'Categories', 'woocommerce' ),
'tag_ids' => __( 'Tags', 'woocommerce' ),
'shipping_class_id' => __( 'Shipping class', 'woocommerce' ),
'images' => __( 'Images', 'woocommerce' ),
'parent_id' => __( 'Parent', 'woocommerce' ),
'upsell_ids' => __( 'Upsells', 'woocommerce' ),
'cross_sell_ids' => __( 'Cross-sells', 'woocommerce' ),
'grouped_products' => __( 'Grouped products', 'woocommerce' ),
'external' => array(
'name' => __( 'External product', 'woocommerce' ),
'options' => array(
'product_url' => __( 'External URL', 'woocommerce' ),
'button_text' => __( 'Button text', 'woocommerce' ),
),
),
'downloads' => array(
'name' => __( 'Downloads', 'woocommerce' ),
'options' => array(
'downloads:name' . $index => __( 'Download name', 'woocommerce' ),
'downloads:url' . $index => __( 'Download URL', 'woocommerce' ),
'download_limit' => __( 'Download limit', 'woocommerce' ),
'download_expiry' => __( 'Download expiry days', 'woocommerce' ),
),
),
'attributes' => array(
'name' => __( 'Attributes', 'woocommerce' ),
'options' => array(
'attributes:name' . $index => __( 'Attribute name', 'woocommerce' ),
'attributes:value' . $index => __( 'Attribute value(s)', 'woocommerce' ),
'attributes:taxonomy' . $index => __( 'Is a global attribute?', 'woocommerce' ),
'attributes:visible' . $index => __( 'Attribute visibility', 'woocommerce' ),
'attributes:default' . $index => __( 'Default attribute', 'woocommerce' ),
),
),
'meta:' . $meta => __( 'Import as meta', 'woocommerce' ),
);
return apply_filters( 'woocommerce_csv_product_import_mapping_options', $options, $item );
}
}

View File

@ -0,0 +1,25 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Add generic mappings.
*
* @since 3.1.0
* @param array $mappings
* @return array
*/
function wc_importer_generic_mappings( $mappings ) {
$generic_mappings = array(
__( 'Title', 'woocommerce' ) => 'name',
__( 'Product Title', 'woocommerce' ) => 'name',
__( 'Price', 'woocommerce' ) => 'regular_price',
__( 'Parent SKU', 'woocommerce' ) => 'parent_id',
__( 'Quantity', 'woocommerce' ) => 'stock_quantity',
);
return array_merge( $mappings, $generic_mappings );
}
add_filter( 'woocommerce_csv_product_import_mapping_default_columns', 'wc_importer_generic_mappings' );

View File

@ -0,0 +1,11 @@
<?php
/**
* Load up extra automatic mappings for the CSV importer.
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
include( dirname( __FILE__ ) . '/generic.php' );
include( dirname( __FILE__ ) . '/wordpress.php' );

View File

@ -0,0 +1,26 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Add mappings for WordPress tables.
*
* @since 3.1.0
* @param array $mappings
* @return array
*/
function wc_importer_wordpress_mappings( $mappings ) {
$wp_mappings = array(
'post_id' => 'id',
'post_title' => 'name',
'post_content' => 'description',
'post_excerpt' => 'short_description',
'post_parent' => 'parent_id',
);
return array_merge( $mappings, $wp_mappings );
}
add_filter( 'woocommerce_csv_product_import_mapping_default_columns', 'wc_importer_wordpress_mappings' );

View File

@ -0,0 +1,93 @@
<?php
/**
* Admin View: Importer - Done!
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
?>
<div class="wc-progress-form-content woocommerce-importer">
<section class="woocommerce-importer-done">
<?php
$results = array();
if ( 0 < $imported ) {
$results[] = sprintf(
/* translators: %d: products count */
_n( '%s product imported', '%s products imported', $imported, 'woocommerce' ),
'<strong>' . number_format_i18n( $imported ) . '</strong>'
);
}
if ( 0 < $updated ) {
$results[] = sprintf(
/* translators: %d: products count */
_n( '%s product updated', '%s products updated', $updated, 'woocommerce' ),
'<strong>' . number_format_i18n( $updated ) . '</strong>'
);
}
if ( 0 < $skipped ) {
$results[] = sprintf(
/* translators: %d: products count */
_n( '%s product was skipped', '%s products were skipped', $skipped, 'woocommerce' ),
'<strong>' . number_format_i18n( $skipped ) . '</strong>'
);
}
if ( 0 < $failed ) {
$results [] = sprintf(
/* translators: %d: products count */
_n( 'Failed to import %s product', 'Failed to import %s products', $failed, 'woocommerce' ),
'<strong>' . number_format_i18n( $failed ) . '</strong>'
);
}
if ( 0 < $failed || 0 < $skipped ) {
$results[] = '<a href="#" class="woocommerce-importer-done-view-errors">' . __( 'View import log', 'woocommerce' ) . '</a>';
}
/* translators: %d: import results */
echo wp_kses_post( __( 'Import complete!', 'woocommerce' ) . ' ' . implode( '. ', $results ) );
?>
</section>
<section class="wc-importer-error-log" style="display:none">
<table class="widefat wc-importer-error-log-table">
<thead>
<tr>
<th><?php esc_html_e( 'Product', 'woocommerce' ); ?></th>
<th><?php esc_html_e( 'Reason for failure', 'woocommerce' ); ?></th>
</tr>
</thead>
<tbody>
<?php
if ( count( $errors ) ) {
foreach ( $errors as $error ) {
if ( ! is_wp_error( $error ) ) {
continue;
}
$error_data = $error->get_error_data();
?>
<tr>
<th><code><?php echo esc_html( $error_data['row'] ); ?></code></th>
<td><?php echo esc_html( $error->get_error_message() ); ?></td>
</tr>
<?php
}
}
?>
</tbody>
</table>
</section>
<script type="text/javascript">
jQuery(function() {
jQuery( '.woocommerce-importer-done-view-errors' ).on( 'click', function() {
jQuery( '.wc-importer-error-log' ).slideToggle();
return false;
} );
} );
</script>
<div class="wc-actions">
<a class="button button-primary" href="<?php echo esc_url( admin_url( 'edit.php?post_type=product' ) ); ?>"><?php esc_html_e( 'View products', 'woocommerce' ); ?></a>
</div>
</div>

View File

@ -0,0 +1,10 @@
<?php
/**
* Admin View: Header
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
?>
</div>
</div>

View File

@ -0,0 +1,12 @@
<?php
/**
* Admin View: Header
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
?>
<div class="wrap woocommerce">
<h1><?php esc_html_e( 'Import Products', 'woocommerce' ); ?></h1>
<div class="woocommerce-progress-form-wrapper">

View File

@ -0,0 +1,61 @@
<?php
/**
* Admin View: Importer - CSV mapping
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
?>
<form class="wc-progress-form-content woocommerce-importer" method="post" action="<?php echo esc_url( $this->get_next_step_link() ) ?>">
<header>
<h2><?php esc_html_e( 'Map CSV fields to products', 'woocommerce' ); ?></h2>
<p><?php esc_html_e( 'Select fields from your CSV file to map against products fields, or to ignore during import.', 'woocommerce' ); ?></p>
</header>
<section class="wc-importer-mapping-table-wrapper">
<table class="widefat wc-importer-mapping-table">
<thead>
<tr>
<th><?php _e( 'Column name', 'woocommerce' ); ?></th>
<th><?php _e( 'Map to field', 'woocommerce' ); ?></th>
</tr>
</thead>
<tbody>
<?php foreach ( $headers as $index => $name ) : ?>
<?php $mapped_value = $mapped_items[ $index ]; ?>
<tr>
<td class="wc-importer-mapping-table-name">
<?php echo esc_html( $name ); ?>
<?php if ( ! empty( $sample[ $index ] ) ) : ?>
<span class="description"><?php _e( 'Sample:', 'woocommerce' ); ?> <code><?php echo esc_html( $sample[ $index ] ); ?></code></span>
<?php endif; ?>
</td>
<td class="wc-importer-mapping-table-field">
<select name="map_to[<?php echo esc_attr( $name ); ?>]">
<option value=""><?php esc_html_e( 'Do not import', 'woocommerce' ); ?></option>
<option value="">--------------</option>
<?php foreach ( $this->get_mapping_options( $mapped_value ) as $key => $value ) : ?>
<?php if ( is_array( $value ) ) : ?>
<optgroup label="<?php echo esc_attr( $value['name'] ); ?>">
<?php foreach ( $value['options'] as $sub_key => $sub_value ) : ?>
<option value="<?php echo esc_attr( $sub_key ); ?>" <?php selected( $mapped_value, $sub_key ); ?>><?php echo esc_html( $sub_value ); ?></option>
<?php endforeach ?>
</optgroup>
<?php else : ?>
<option value="<?php echo esc_attr( $key ); ?>" <?php selected( $mapped_value, $key ); ?>><?php echo esc_html( $value ); ?></option>
<?php endif; ?>
<?php endforeach ?>
</select>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</section>
<div class="wc-actions">
<input type="submit" class="button button-primary button-next" value="<?php esc_attr_e( 'Run the importer', 'woocommerce' ); ?>" name="save_step" />
<input type="hidden" name="file" value="<?php echo esc_attr( $this->file ); ?>" />
<input type="hidden" name="delimiter" value="<?php echo esc_attr( $this->delimiter ); ?>" />
<input type="hidden" name="update_existing" value="<?php echo (int) $this->update_existing; ?>" />
<?php wp_nonce_field( 'woocommerce-csv-importer' ); ?>
</div>
</form>

View File

@ -0,0 +1,18 @@
<?php
/**
* Admin View: Importer - CSV import progress
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
?>
<div class="wc-progress-form-content woocommerce-importer woocommerce-importer__importing">
<header>
<span class="spinner is-active"></span>
<h2><?php esc_html_e( 'Importing', 'woocommerce' ); ?></h2>
<p><?php esc_html_e( 'Your products are now being imported...', 'woocommerce' ); ?></p>
</header>
<section>
<progress class="woocommerce-importer-progress" max="100" value="0"></progress>
</section>
</div>

View File

@ -0,0 +1,19 @@
<?php
/**
* Admin View: Steps
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
?>
<ol class="wc-progress-steps">
<?php foreach ( $this->steps as $step_key => $step ) : ?>
<li class="<?php
if ( $step_key === $this->step ) {
echo 'active';
} elseif ( array_search( $this->step, array_keys( $this->steps ) ) > array_search( $step_key, array_keys( $this->steps ) ) ) {
echo 'done';
}
?>"><?php echo esc_html( $step['name'] ); ?></li>
<?php endforeach; ?>
</ol>

View File

@ -0,0 +1,92 @@
<?php
/**
* Admin View: Product import form
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
?>
<form class="wc-progress-form-content woocommerce-importer" enctype="multipart/form-data" method="post">
<header>
<h2><?php esc_html_e( 'Import products from a CSV file', 'woocommerce' ); ?></h2>
<p><?php esc_html_e( 'This tool allows you to import (or merge) product data to your store from a CSV file.', 'woocommerce' ); ?></p>
</header>
<section>
<table class="form-table woocommerce-importer-options">
<tbody>
<tr>
<th scope="row">
<label for="upload">
<?php _e( 'Choose a CSV file from your computer:', 'woocommerce' ); ?>
</label>
</th>
<td>
<?php
if ( ! empty( $upload_dir['error'] ) ) {
?><div class="inline error">
<p><?php esc_html_e( 'Before you can upload your import file, you will need to fix the following error:', 'woocommerce' ); ?></p>
<p><strong><?php echo esc_html( $upload_dir['error'] ); ?></strong></p>
</div><?php
} else {
?>
<input type="file" id="upload" name="import" size="25" />
<input type="hidden" name="action" value="save" />
<input type="hidden" name="max_file_size" value="<?php echo esc_attr( $bytes ); ?>" />
<br><small><?php
/* translators: %s: maximum upload size */
printf(
__( 'Maximum size: %s', 'woocommerce' ),
$size
);
?></small>
<?php
}
?>
</td>
</tr>
<tr>
<th><label for="woocommerce-importer-update-existing"><?php _e( 'Update existing products', 'woocommerce' ); ?></label><br/></th>
<td>
<input type="hidden" name="update_existing" value="0" />
<input type="checkbox" id="woocommerce-importer-update-existing" name="update_existing" value="1" />
<label for="woocommerce-importer-update-existing"><?php esc_html_e( 'If a product being imported matches an existing product ID or SKU, update the existing product data.', 'woocommerce' ); ?></label>
</td>
</tr>
<tr class="woocommerce-importer-advanced hidden">
<th>
<label for="woocommerce-importer-file-url"><?php _e( '<em>or</em> enter the path to a CSV file on your server:', 'woocommerce' ); ?></label>
</th>
<td>
<label for="woocommerce-importer-file-url" class="woocommerce-importer-file-url-field-wrapper">
<code><?php echo esc_html( ABSPATH ) . ' '; ?></code><input type="text" id="woocommerce-importer-file-url" name="file_url" />
</label>
</td>
</tr>
<tr class="woocommerce-importer-advanced hidden">
<th><label><?php _e( 'CSV Delimiter', 'woocommerce' ); ?></label><br/></th>
<td><input type="text" name="delimiter" placeholder="," size="2" /></td>
</tr>
</tbody>
</table>
</section>
<script type="text/javascript">
jQuery(function() {
jQuery( '.woocommerce-importer-toggle-advanced-options' ).on( 'click', function() {
var elements = jQuery( '.woocommerce-importer-advanced' );
if ( elements.is( '.hidden' ) ) {
elements.removeClass( 'hidden' );
jQuery( this ).text( jQuery( this ).data( 'hidetext' ) );
} else {
elements.addClass( 'hidden' );
jQuery( this ).text( jQuery( this ).data( 'showtext' ) );
}
return false;
} );
});
</script>
<div class="wc-actions">
<a href="#" class="woocommerce-importer-toggle-advanced-options" data-hidetext="<?php esc_html_e( 'Hide advanced options', 'woocommerce' ); ?>" data-showtext="<?php esc_html_e( 'Hide advanced options', 'woocommerce' ); ?>"><?php esc_html_e( 'Show advanced options', 'woocommerce' ); ?></a>
<input type="submit" class="button button-primary button-next" value="<?php esc_attr_e( 'Continue', 'woocommerce' ); ?>" name="save_step" />
<?php wp_nonce_field( 'woocommerce-csv-importer' ); ?>
</div>
</form>

View File

@ -11,7 +11,12 @@ if ( ! defined( 'ABSPATH' ) ) {
?>
<div class="wrap woocommerce wc_addons_wrap">
<h1><?php echo get_admin_page_title(); ?></h1>
<nav class="nav-tab-wrapper woo-nav-tab-wrapper">
<a href="<?php echo esc_url( admin_url( 'admin.php?page=wc-addons' ) ); ?>" class="nav-tab nav-tab-active"><?php _e( 'Browse Extensions', 'woocommerce' ); ?></a>
<a href="<?php echo esc_url( admin_url( 'admin.php?page=wc-addons&section=helper' ) ); ?>" class="nav-tab"><?php _e( 'WooCommerce.com Subscriptions', 'woocommerce' ); ?></a>
</nav>
<h1 class="screen-reader-text"><?php _e( 'WooCommerce Extensions', 'woocommerce' ); ?></h1>
<?php if ( $sections ) : ?>
<ul class="subsubsub">

View File

@ -0,0 +1,78 @@
<?php
/**
* Admin View: Product Export
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
wp_enqueue_script( 'wc-product-export' );
$exporter = new WC_Product_CSV_Exporter();
$product_count = wp_count_posts( 'product' );
$variation_count = wp_count_posts( 'product' );
$total_rows = $product_count->publish + $product_count->private + $variation_count->publish + $variation_count->private;
?>
<div class="wrap woocommerce">
<h1><?php esc_html_e( 'Export Products', 'woocommerce' ); ?></h1>
<div class="woocommerce-exporter-wrapper">
<form class="woocommerce-exporter">
<header>
<span class="spinner is-active"></span>
<h2><?php esc_html_e( 'Export products to a CSV file', 'woocommerce' ); ?></h2>
<p><?php esc_html_e( 'This tool allows you to generate and download a CSV file containing a list of all products.', 'woocommerce' ); ?></p>
</header>
<section>
<table class="form-table woocommerce-exporter-options">
<tbody>
<tr>
<th scope="row">
<label for="woocommerce-exporter-columns"><?php esc_html_e( 'Which columns should be exported?', 'woocommerce' ); ?></label>
</th>
<td>
<select id="woocommerce-exporter-columns" class="woocommerce-exporter-columns wc-enhanced-select" style="width:100%;" multiple data-placeholder="<?php esc_attr_e( 'Export all columns', 'woocommerce' ); ?>">
<?php
foreach ( $exporter->get_default_column_names() as $column_id => $column_name ) {
echo '<option value="' . esc_attr( $column_id ) . '">' . esc_html( $column_name ) . '</option>';
}
?>
<option value="downloads"><?php esc_html_e( 'Downloads', 'woocommerce' ); ?></option>
<option value="attributes"><?php esc_html_e( 'Attributes', 'woocommerce' ); ?></option>
</select>
</td>
</tr>
<tr>
<th scope="row">
<label for="woocommerce-exporter-types"><?php esc_html_e( 'Which product types should be exported?', 'woocommerce' ); ?></label>
</th>
<td>
<select id="woocommerce-exporter-types" class="woocommerce-exporter-types wc-enhanced-select" style="width:100%;" multiple data-placeholder="<?php esc_attr_e( 'Export all products', 'woocommerce' ); ?>">
<?php
foreach ( wc_get_product_types() as $value => $label ) {
echo '<option value="' . esc_attr( $value ) . '">' . esc_html( $label ) . '</option>';
}
?>
<option value="variation"><?php esc_html_e( 'Product variations', 'woocommerce' ); ?></option>
</select>
</td>
</tr>
<tr>
<th scope="row">
<label for="woocommerce-exporter-meta"><?php esc_html_e( 'Export custom meta?', 'woocommerce' ); ?></label>
</th>
<td>
<input type="checkbox" id="woocommerce-exporter-meta" value="1" />
<label for="woocommerce-exporter-meta"><?php esc_html_e( 'Yes, export all custom meta', 'woocommerce' ); ?></label>
</td>
</tr>
</tbody>
</table>
<progress class="woocommerce-exporter-progress" max="100" value="0"></progress>
</section>
<div class="wc-actions">
<input type="submit" class="woocommerce-exporter-button button button-primary" value="<?php esc_attr_e( 'Generate CSV', 'woocommerce' ); ?>" />
</div>
</form>
</div>
</div>

View File

@ -29,6 +29,8 @@ function wc_get_screen_ids() {
$wc_screen_id . '_page_wc-addons',
'toplevel_page_wc-reports',
'product_page_product_attributes',
'product_page_product_exporter',
'product_page_product_importer',
'edit-product',
'product',
'edit-shop_coupon',

View File

@ -576,9 +576,6 @@ class WC_Checkout {
}
$data[ $key ] = apply_filters( 'woocommerce_process_checkout_' . $type . '_field', apply_filters( 'woocommerce_process_checkout_field_' . $key, $value ) );
// BW compatibility.
$this->legacy_posted_data[ $key ] = $data[ $key ];
}
}
@ -588,6 +585,9 @@ class WC_Checkout {
}
}
// BW compatibility.
$this->legacy_posted_data = $data;
return $data;
}

View File

@ -841,7 +841,7 @@ CREATE TABLE {$wpdb->prefix}woocommerce_termmeta (
}
}
echo wp_kses_post( $upgrade_notice );
echo apply_filters( 'woocommerce_in_plugin_update_message', wp_kses_post( $upgrade_notice ) );
}
/**

View File

@ -52,6 +52,10 @@ class WC_Post_Data {
// Download permissions
add_action( 'woocommerce_process_product_file_download_paths', array( __CLASS__, 'process_product_file_download_paths' ), 10, 3 );
// Meta cache flushing.
add_action( 'updated_post_meta', array( __CLASS__, 'flush_object_meta_cache' ), 10, 4 );
add_action( 'updated_order_item_meta', array( __CLASS__, 'flush_object_meta_cache' ), 10, 4 );
}
/**
@ -449,6 +453,17 @@ class WC_Post_Data {
}
}
}
/**
* Flush meta cache for CRUD objects on direct update.
* @param int $meta_id
* @param int $object_id
* @param string $meta_key
* @param string $meta_value
*/
public static function flush_object_meta_cache( $meta_id, $object_id, $meta_key, $meta_value ) {
WC_Cache_Helper::incr_cache_prefix( 'object_' . $object_id );
}
}
WC_Post_Data::init();

View File

@ -164,6 +164,10 @@ abstract class Abstract_WC_Order_Data_Store_CPT extends WC_Data_Store_WP impleme
'force_delete' => false,
) );
if ( ! $id ) {
return;
}
if ( $args['force_delete'] ) {
wp_delete_post( $id );
$order->set_id( 0 );
@ -354,4 +358,22 @@ abstract class Abstract_WC_Order_Data_Store_CPT extends WC_Data_Store_WP impleme
public function update_payment_token_ids( $order, $token_ids ) {
update_post_meta( $order->get_id(), '_payment_tokens', $token_ids );
}
/**
* Return the order type of a given item which belongs to WC_Order
*
* @param WC_Order $order Order Object
* @param int $order_id
*
* @return string Order Item type
*/
public function get_order_item_type( WC_Order $order, $order_item_id ) {
global $wpdb;
$query = $wpdb->prepare(
"SELECT DISTINCT order_item_type FROM {$wpdb->prefix}woocommerce_order_items WHERE order_id = %d and order_item_id = %d",
$order->get_id(),
$order_item_id
);
return $wpdb->get_var( $query );
}
}

View File

@ -179,12 +179,16 @@ class WC_Coupon_Data_Store_CPT extends WC_Data_Store_WP implements WC_Coupon_Dat
$id = $coupon->get_id();
if ( ! $id ) {
return;
}
if ( $args['force_delete'] ) {
wp_delete_post( $coupon->get_id() );
wp_delete_post( $id );
$coupon->set_id( 0 );
do_action( 'woocommerce_delete_coupon', $id );
} else {
wp_trash_post( $coupon->get_id() );
wp_trash_post( $id );
do_action( 'woocommerce_trash_coupon', $id );
}
}

View File

@ -17,6 +17,7 @@ class WC_Customer_Data_Store_Session extends WC_Data_Store_WP implements WC_Cust
* @var array
*/
protected $session_keys = array(
'id',
'billing_postcode',
'billing_city',
'billing_address_1',
@ -81,24 +82,22 @@ class WC_Customer_Data_Store_Session extends WC_Data_Store_WP implements WC_Cust
}
/**
* Read customer data from the session.
* Read customer data from the session unless the user has logged in, in
* which case the stored ID will differ from the actual ID.
*
* @since 3.0.0
* @param WC_Customer $customer
*/
public function read( &$customer ) {
$data = (array) WC()->session->get( 'customer' );
if ( ! empty( $data ) ) {
if ( ! empty( $data ) && isset( $data['id'] ) && $data['id'] === $customer->get_id() ) {
foreach ( $this->session_keys as $session_key ) {
$function_key = $session_key;
if ( 'billing_' === substr( $session_key, 0, 8 ) ) {
$session_key = str_replace( 'billing_', '', $session_key );
}
if ( ! empty( $data[ $session_key ] ) && is_callable( array( $customer, "set_{$function_key}" ) ) ) {
// Only set from session if data is already missing.
if ( ! $customer->{"get_{$function_key}"}() ) {
$customer->{"set_{$function_key}"}( $data[ $session_key ] );
}
$customer->{"set_{$function_key}"}( $data[ $session_key ] );
}
}
}

View File

@ -41,6 +41,10 @@ class WC_Order_Refund_Data_Store_CPT extends Abstract_WC_Order_Data_Store_CPT im
public function delete( &$order, $args = array() ) {
$id = $order->get_id();
if ( ! $id ) {
return;
}
wp_delete_post( $id );
$order->set_id( 0 );
do_action( 'woocommerce_delete_order_refund', $id );

View File

@ -177,6 +177,7 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
'post_status' => $product->get_status( 'edit' ) ? $product->get_status( 'edit' ) : 'publish',
'menu_order' => $product->get_menu_order( 'edit' ),
'post_name' => $product->get_slug( 'edit' ),
'post_type' => 'product',
);
if ( $product->get_date_created( 'edit' ) ) {
$post_data['post_date'] = gmdate( 'Y-m-d H:i:s', $product->get_date_created( 'edit' )->getOffsetTimestamp() );
@ -234,12 +235,16 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
'force_delete' => false,
) );
if ( ! $id ) {
return;
}
if ( $args['force_delete'] ) {
wp_delete_post( $product->get_id() );
wp_delete_post( $id );
$product->set_id( 0 );
do_action( 'woocommerce_delete_' . $post_type, $id );
} else {
wp_trash_post( $product->get_id() );
wp_trash_post( $id );
$product->set_status( 'trash' );
do_action( 'woocommerce_trash_' . $post_type, $id );
}
@ -1166,7 +1171,6 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
* Generate WP_Query args.
*/
$wp_query_args = array(
'post_type' => 'variation' === $args['type'] ? 'product_variation' : 'product',
'post_status' => $args['status'],
'posts_per_page' => $args['limit'],
'meta_query' => array(),
@ -1174,12 +1178,26 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
'order' => $args['order'],
'tax_query' => array(),
);
// Do not load unnecessary post data if the user only wants IDs.
if ( 'ids' === $args['return'] ) {
$wp_query_args['fields'] = 'ids';
}
if ( 'variation' !== $args['type'] ) {
if ( 'variation' === $args['type'] ) {
$wp_query_args['post_type'] = 'product_variation';
} elseif ( is_array( $args['type'] ) && in_array( 'variation', $args['type'] ) ) {
$wp_query_args['post_type'] = array( 'product_variation', 'product' );
$wp_query_args['tax_query'][] = array(
'relation' => 'OR',
array(
'taxonomy' => 'product_type',
'field' => 'slug',
'terms' => $args['type'],
),
array(
'taxonomy' => 'product_type',
'field' => 'id',
'operator' => 'NOT EXISTS',
),
);
} else {
$wp_query_args['post_type'] = 'product';
$wp_query_args['tax_query'][] = array(
'taxonomy' => 'product_type',
'field' => 'slug',
@ -1187,6 +1205,11 @@ class WC_Product_Data_Store_CPT extends WC_Data_Store_WP implements WC_Object_Da
);
}
// Do not load unnecessary post data if the user only wants IDs.
if ( 'ids' === $args['return'] ) {
$wp_query_args['fields'] = 'ids';
}
if ( ! empty( $args['sku'] ) ) {
$wp_query_args['meta_query'][] = array(
'key' => '_sku',

View File

@ -161,7 +161,8 @@ class WC_Product_Variation_Data_Store_CPT extends WC_Product_Data_Store_CPT impl
'post_date' => gmdate( 'Y-m-d H:i:s', $product->get_date_created( 'edit' )->getOffsetTimestamp() ),
'post_date_gmt' => gmdate( 'Y-m-d H:i:s', $product->get_date_created( 'edit' )->getTimestamp() ),
'post_modified' => isset( $changes['date_modified'] ) ? gmdate( 'Y-m-d H:i:s', $product->get_date_modified( 'edit' )->getOffsetTimestamp() ) : current_time( 'mysql' ),
'post_modified_gmt' => isset( $changes['date_modified'] ) ? gmdate( 'Y-m-d H:i:s', $product->get_date_modified( 'edit' )->getTimestamp() ) : current_time( 'mysql', 1 ),
'post_modified_gmt' => isset( $changes['date_modified'] ) ? gmdate( 'Y-m-d H:i:s', $product->get_date_modified( 'edit' )->getTimestamp() ) : current_time( 'mysql', 1 ),
'post_type' => 'product_variation',
);
/**

View File

@ -34,6 +34,7 @@ class WC_Email_Customer_Processing_Order extends WC_Email {
$this->set_email_strings();
// Triggers for this email
add_action( 'woocommerce_order_status_failed_to_processing_notification', array( $this, 'trigger' ), 10, 2 );
add_action( 'woocommerce_order_status_on-hold_to_processing_notification', array( $this, 'trigger' ), 10, 2 );
add_action( 'woocommerce_order_status_pending_to_processing_notification', array( $this, 'trigger' ), 10, 2 );

View File

@ -0,0 +1,150 @@
<?php
/**
* Handles Batch CSV export.
*
* Based on https://pippinsplugins.com/batch-processing-for-big-data/
*
* @author Automattic
* @category Admin
* @package WooCommerce/Export
* @version 3.1.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Include dependencies.
*/
if ( ! class_exists( 'WC_CSV_Exporter', false ) ) {
include_once( WC_ABSPATH . 'includes/export/abstract-wc-csv-exporter.php' );
}
/**
* WC_CSV_Exporter Class.
*/
abstract class WC_CSV_Batch_Exporter extends WC_CSV_Exporter {
/**
* The file being exported to.
*
* @var string
*/
protected $file;
/**
* Page being exported
*
* @var integer
*/
protected $page = 1;
/**
* Constructor.
*/
public function __construct() {
$upload_dir = wp_upload_dir();
$this->file = trailingslashit( $upload_dir['basedir'] ) . $this->get_filename();
$this->column_names = $this->get_default_column_names();
}
/**
* Get the file contents.
*
* @since 3.1.0
* @return string
*/
public function get_file() {
$file = '';
if ( @file_exists( $this->file ) ) {
$file = @file_get_contents( $this->file );
} else {
@file_put_contents( $this->file, '' );
@chmod( $this->file, 0664 );
}
return $file;
}
/**
* Serve the file and remove once sent to the client.
*
* @since 3.1.0
*/
public function export() {
$this->send_headers();
$this->send_content( $this->get_file() );
@unlink( $this->file );
die();
}
/**
* Generate the CSV file.
*
* @since 3.1.0
*/
public function generate_file() {
if ( 1 === $this->get_page() ) {
@unlink( $this->file );
}
$this->prepare_data_to_export();
$this->write_csv_data( $this->get_csv_data() );
}
/**
* Write data to the file.
*
* @since 3.1.0
* @param string $data
*/
protected function write_csv_data( $data ) {
$file = $this->get_file();
// Add columns when finished.
if ( 100 === $this->get_percent_complete() ) {
$file = $this->export_column_headers() . $file;
}
$file .= $data;
@file_put_contents( $this->file, $file );
}
/**
* Get page.
*
* @since 3.1.0
* @return int
*/
public function get_page() {
return $this->page;
}
/**
* Set page.
*
* @since 3.1.0
* @param int $page
*/
public function set_page( $page ) {
$this->page = absint( $page );
}
/**
* Get count of records exported.
*
* @since 3.1.0
* @return int
*/
public function get_total_exported() {
return ( $this->get_page() * $this->get_limit() ) + $this->exported_row_count;
}
/**
* Get total % complete.
*
* @since 3.1.0
* @return int
*/
public function get_percent_complete() {
return $this->total_rows ? floor( ( $this->get_total_exported() / $this->total_rows ) * 100 ) : 100;
}
}

View File

@ -0,0 +1,409 @@
<?php
/**
* Handles CSV export.
*
* @author Automattic
* @category Admin
* @package WooCommerce/Export
* @version 3.1.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* WC_CSV_Exporter Class.
*/
abstract class WC_CSV_Exporter {
/**
* Type of export used in filter names.
* @var string
*/
protected $export_type = '';
/**
* Batch limit.
* @var integer
*/
protected $limit = 30;
/**
* Number exported.
* @var integer
*/
protected $exported_row_count = 0;
/**
* Raw data to export.
* @var array
*/
protected $row_data = array();
/**
* The CSV data - each row is an array item.
* @var string[]
*/
protected $csv_rows = array();
/**
* Total rows to export.
* @var integer
*/
protected $total_rows = 0;
/**
* Columns ids and names.
* @var array
*/
protected $column_names = array();
/**
* List of columns to export, or empty for all.
* @var array
*/
protected $columns_to_export = array();
/**
* Prepare data that will be exported.
*/
abstract function prepare_data_to_export();
/**
* Return an array of supported column names and ids.
*
* @since 3.1.0
* @return array
*/
public function get_column_names() {
return apply_filters( "woocommerce_{$this->export_type}_export_column_names", $this->column_names, $this );
}
/**
* Set column names.
*
* @since 3.1.0
* @param array $column_names
*/
public function set_column_names( $column_names ) {
$this->column_names = array();
foreach ( $column_names as $column_id => $column_name ) {
$this->column_names[ wc_clean( $column_id ) ] = wc_clean( $column_name );
}
}
/**
* Return an array of columns to export.
*
* @since 3.1.0
* @return array
*/
public function get_columns_to_export() {
return $this->columns_to_export;
}
/**
* Set columns to export.
*
* @since 3.1.0
* @param array $column_names
*/
public function set_columns_to_export( $columns ) {
$this->columns_to_export = array_map( 'wc_clean', $columns );
}
/**
* See if a column is to be exported or not.
*
* @since 3.1.0
* @param string $column_id
* @return boolean
*/
public function is_column_exporting( $column_id ) {
$column_id = strstr( $column_id, ':' ) ? current( explode( ':', $column_id ) ) : $column_id;
$columns_to_export = $this->get_columns_to_export();
if ( empty( $columns_to_export ) ) {
return true;
}
if ( in_array( $column_id, $columns_to_export ) ) {
return true;
}
return false;
}
/**
* Return default columns.
*
* @since 3.1.0
* @return array
*/
public function get_default_column_names() {
return array();
}
/**
* Do the export.
*
* @since 3.1.0
*/
public function export() {
$this->prepare_data_to_export();
$this->send_headers();
$this->send_content( $this->export_column_headers() . $this->get_csv_data() );
die();
}
/**
* Set the export headers.
*
* @since 3.1.0
*/
public function send_headers() {
if ( function_exists( 'gc_enable' ) ) {
gc_enable();
}
if ( function_exists( 'apache_setenv' ) ) {
@apache_setenv( 'no-gzip', 1 );
}
@ini_set( 'zlib.output_compression', 'Off' );
@ini_set( 'output_buffering', 'Off' );
@ini_set( 'output_handler', '' );
ignore_user_abort( true );
wc_set_time_limit( 0 );
nocache_headers();
header( 'Content-Type: text/csv; charset=utf-8' );
header( 'Content-Disposition: attachment; filename=' . $this->get_filename() );
header( 'Pragma: no-cache' );
header( 'Expires: 0' );
}
/**
* Generate and return a filename.
*
* @return string
*/
public function get_filename() {
return sanitize_file_name( 'wc-' . $this->export_type . '-export-' . date_i18n( 'Y-m-d', current_time( 'timestamp' ) ) . '.csv' );
}
/**
* Set the export content.
*
* @since 3.1.0
*/
public function send_content( $csv_data ) {
echo $csv_data;
}
/**
* Get CSV data for this export.
*
* @since 3.1.0
* @return string
*/
protected function get_csv_data() {
return "\r\n" . implode( "\r\n", $this->export_rows() );
}
/**
* Export column headers in CSV format.
*
* @since 3.1.0
* @return string
*/
protected function export_column_headers() {
$columns = $this->get_column_names();
$row = '';
foreach ( $columns as $column_id => $column_name ) {
if ( ! $this->is_column_exporting( $column_id ) ) {
continue;
}
$row .= '"' . addslashes( $column_name ) . '",';
}
return rtrim( $row, ',' );
}
/**
* Get data that will be exported.
*
* @since 3.1.0
* @return array
*/
protected function get_data_to_export() {
return $this->row_data;
}
/**
* Export rows in CSV format.
*
* @since 3.1.0
* @return array
*/
protected function export_rows() {
$this->csv_rows = array();
$data = $this->get_data_to_export();
array_walk( $data, array( $this, 'export_row' ) );
return apply_filters( "woocommerce_{$this->export_type}_export_rows", $this->csv_rows, $this );
}
/**
* Export a row in CSV format.
*
* @since 3.1.0
* @param array $row_data
*/
protected function export_row( $row_data ) {
$columns = $this->get_column_names();
$row = '';
foreach ( $columns as $column_id => $column_name ) {
if ( ! $this->is_column_exporting( $column_id ) ) {
continue;
}
if ( isset( $row_data[ $column_id ] ) ) {
$row .= '"' . $this->format_data( $row_data[ $column_id ] ) . '",';
} else {
$row .= '"",';
}
}
$this->csv_rows[] = rtrim( $row, ',' );
++ $this->exported_row_count;
}
/**
* Get batch limit.
*
* @since 3.1.0
* @return int
*/
public function get_limit() {
return $this->limit;
}
/**
* Set batch limit.
*
* @since 3.1.0
* @param int $limit
*/
public function set_limit( $limit ) {
$this->limit = absint( $limit );
}
/**
* Get count of records exported.
*
* @since 3.1.0
* @return int
*/
public function get_total_exported() {
return $this->exported_row_count;
}
/**
* Escape a string to be used in a CSV context
*
* Malicious input can inject formulas into CSV files, opening up the possibility
* for phishing attacks and disclosure of sensitive information.
*
* Additionally, Excel exposes the ability to launch arbitrary commands through
* the DDE protocol.
*
* @see http://www.contextis.com/resources/blog/comma-separated-vulnerabilities/
* @see https://hackerone.com/reports/72785
*
* @since 3.1.0
* @param string $field CSV field to escape
* @return string
*/
public function escape_data( $data ) {
$active_content_triggers = array( '=', '+', '-', '@' );
if ( in_array( mb_substr( $data, 0, 1 ), $active_content_triggers, true ) ) {
$data = "'" . $data;
}
return $data;
}
/**
* Format and escape data ready for the CSV file.
*
* @since 3.1.0
* @param string $data
* @return string
*/
public function format_data( $data ) {
if ( ! is_scalar( $data ) ) {
if ( is_a( $data, 'WC_Datetime' ) ) {
$data = $data->date( 'Y-m-d G:i:s' );
} else {
$data = ''; // Not supported.
}
} elseif ( is_bool( $data ) ) {
$data = $data ? 1 : 0;
}
$data = (string) urldecode( $data );
$encoding = mb_detect_encoding( $data, 'UTF-8, ISO-8859-1', true );
$data = 'UTF-8' === $encoding ? $data : utf8_encode( $data );
return $this->escape_data( addslashes( $data ) );
}
/**
* Format term ids to names.
*
* @since 3.1.0
* @param array $term_ids
* @param string $taxonomy
* @return array
*/
public function format_term_ids( $term_ids, $taxonomy ) {
$term_ids = wp_parse_id_list( $term_ids );
if ( ! count( $term_ids ) ) {
return '';
}
$formatted_terms = array();
if ( is_taxonomy_hierarchical( $taxonomy ) ) {
foreach ( $term_ids as $term_id ) {
$formatted_term = array();
$ancestor_ids = array_reverse( get_ancestors( $term_id, $taxonomy ) );
foreach ( $ancestor_ids as $ancestor_id ) {
$term = get_term( $ancestor_id, $taxonomy );
if ( $term && ! is_wp_error( $term ) ) {
$formatted_term[] = $term->name;
}
}
$term = get_term( $term_id, $taxonomy );
if ( $term && ! is_wp_error( $term ) ) {
$formatted_term[] = $term->name;
}
$formatted_terms[] = implode( ' > ', $formatted_term );
}
} else {
foreach ( $term_ids as $term_id ) {
$term = get_term( $term_id, $taxonomy );
if ( $term && ! is_wp_error( $term ) ) {
$formatted_terms[] = $term->name;
}
}
}
return implode( ', ', $formatted_terms );
}
}

View File

@ -0,0 +1,546 @@
<?php
/**
* Handles product CSV export.
*
* @author Automattic
* @category Admin
* @package WooCommerce/Export
* @version 3.1.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Include dependencies.
*/
if ( ! class_exists( 'WC_CSV_Batch_Exporter', false ) ) {
include_once( WC_ABSPATH . 'includes/export/abstract-wc-csv-batch-exporter.php' );
}
/**
* WC_Product_CSV_Exporter Class.
*/
class WC_Product_CSV_Exporter extends WC_CSV_Batch_Exporter {
/**
* Type of export used in filter names.
* @var string
*/
protected $export_type = 'product';
/**
* Should meta be exported?
* @var boolean
*/
protected $enable_meta_export = false;
/**
* Which product types are beign exported.
* @var array
*/
protected $product_types_to_export = array();
/**
* Constructor.
*/
public function __construct() {
parent::__construct();
$this->set_product_types_to_export( array_merge( array_keys( wc_get_product_types() ), array( 'variation' ) ) );
}
/**
* Should meta be exported?
*
* @since 3.1.0
* @param bool $enable_meta_export
*/
public function enable_meta_export( $enable_meta_export ) {
$this->enable_meta_export = (bool) $enable_meta_export;
}
/**
* Product types to export.
*
* @since 3.1.0
* @param array $product_types_to_export
*/
public function set_product_types_to_export( $product_types_to_export ) {
$this->product_types_to_export = array_map( 'wc_clean', $product_types_to_export );
}
/**
* Return an array of columns to export.
*
* @since 3.1.0
* @return array
*/
public function get_default_column_names() {
return array(
'id' => __( 'ID', 'woocommerce' ),
'type' => __( 'Type', 'woocommerce' ),
'sku' => __( 'SKU', 'woocommerce' ),
'name' => __( 'Name', 'woocommerce' ),
'published' => __( 'Published', 'woocommerce' ),
'featured' => __( 'Is featured?', 'woocommerce' ),
'catalog_visibility' => __( 'Visibility in catalog', 'woocommerce' ),
'short_description' => __( 'Short description', 'woocommerce' ),
'description' => __( 'Description', 'woocommerce' ),
'date_on_sale_from' => __( 'Date sale price starts', 'woocommerce' ),
'date_on_sale_to' => __( 'Date sale price ends', 'woocommerce' ),
'tax_status' => __( 'Tax class', 'woocommerce' ),
'stock_status' => __( 'In stock?', 'woocommerce' ),
'stock' => __( 'Stock', 'woocommerce' ),
'backorders' => __( 'Backorders allowed?', 'woocommerce' ),
'sold_individually' => __( 'Sold individually?', 'woocommerce' ),
'weight' => sprintf( __( 'Weight (%s)', 'woocommerce' ), get_option( 'woocommerce_weight_unit' ) ),
'length' => sprintf( __( 'Length (%s)', 'woocommerce' ), get_option( 'woocommerce_dimension_unit' ) ),
'width' => sprintf( __( 'Width (%s)', 'woocommerce' ), get_option( 'woocommerce_dimension_unit' ) ),
'height' => sprintf( __( 'Height (%s)', 'woocommerce' ), get_option( 'woocommerce_dimension_unit' ) ),
'reviews_allowed' => __( 'Allow customer reviews?', 'woocommerce' ),
'purchase_note' => __( 'Purchase note', 'woocommerce' ),
'sale_price' => __( 'Sale price', 'woocommerce' ),
'regular_price' => __( 'Regular price', 'woocommerce' ),
'category_ids' => __( 'Categories', 'woocommerce' ),
'tag_ids' => __( 'Tags', 'woocommerce' ),
'shipping_class_id' => __( 'Shipping class', 'woocommerce' ),
'images' => __( 'Images', 'woocommerce' ),
'download_limit' => __( 'Download limit', 'woocommerce' ),
'download_expiry' => __( 'Download expiry days', 'woocommerce' ),
'parent_id' => __( 'Parent', 'woocommerce' ),
'grouped_products' => __( 'Grouped products', 'woocommerce' ),
'upsell_ids' => __( 'Upsells', 'woocommerce' ),
'cross_sell_ids' => __( 'Cross-sells', 'woocommerce' ),
'product_url' => __( 'External URL', 'woocommerce' ),
'button_text' => __( 'Button text', 'woocommerce' ),
);
}
/**
* Prepare data for export.
*
* @since 3.1.0
*/
public function prepare_data_to_export() {
$columns = $this->get_column_names();
$products = wc_get_products( array(
'status' => array( 'private', 'publish' ),
'type' => $this->product_types_to_export,
'limit' => $this->get_limit(),
'page' => $this->get_page(),
'orderby' => array(
'ID' => 'DESC',
),
'return' => 'objects',
'paginate' => true,
) );
$this->total_rows = $products->total;
$this->row_data = array();
foreach ( $products->products as $product ) {
$row = array();
foreach ( $columns as $column_id => $column_name ) {
$column_id = strstr( $column_id, ':' ) ? current( explode( ':', $column_id ) ) : $column_id;
$value = '';
// Skip some columns if dynamically handled later or if we're being selective.
if ( in_array( $column_id, array( 'downloads', 'attributes', 'meta' ) ) || ! $this->is_column_exporting( $column_id ) ) {
continue;
}
// Filter for 3rd parties.
if ( has_filter( "woocommerce_product_export_{$this->export_type}_column_{$column_id}" ) ) {
$value = apply_filters( "woocommerce_product_export_{$this->export_type}_column_{$column_id}", '', $product );
// Handle special columns which don't map 1:1 to product data.
} elseif ( is_callable( array( $this, "get_column_value_{$column_id}" ) ) ) {
$value = $this->{"get_column_value_{$column_id}"}( $product );
// Default and custom handling.
} elseif ( is_callable( array( $product, "get_{$column_id}" ) ) ) {
$value = $product->{"get_{$column_id}"}( 'edit' );
}
$row[ $column_id ] = $value;
}
$this->prepare_downloads_for_export( $product, $row );
$this->prepare_attributes_for_export( $product, $row );
$this->prepare_meta_for_export( $product, $row );
$this->row_data[] = apply_filters( 'woocommerce_product_export_row_data', $row, $product );
}
}
/**
* Get published value.
*
* @since 3.1.0
* @param WC_Product $product
* @return int
*/
protected function get_column_value_published( $product ) {
return 'publish' === $product->get_status( 'edit' ) ? 1 : 0;
}
/**
* Get product_cat value.
*
* @since 3.1.0
* @param WC_Product $product
* @return string
*/
protected function get_column_value_category_ids( $product ) {
$term_ids = $product->get_category_ids( 'edit' );
return $this->format_term_ids( $term_ids, 'product_cat' );
}
/**
* Get product_tag value.
*
* @since 3.1.0
* @param WC_Product $product
* @return string
*/
protected function get_column_value_tag_ids( $product ) {
$term_ids = $product->get_tag_ids( 'edit' );
return $this->format_term_ids( $term_ids, 'product_tag' );
}
/**
* Get product_shipping_class value.
*
* @since 3.1.0
* @param WC_Product $product
* @return string
*/
protected function get_column_value_shipping_class_id( $product ) {
$term_ids = $product->get_shipping_class_id( 'edit' );
return $this->format_term_ids( $term_ids, 'product_shipping_class' );
}
/**
* Get images value.
*
* @since 3.1.0
* @param WC_Product $product
* @return string
*/
protected function get_column_value_images( $product ) {
$image_ids = array_merge( array( $product->get_image_id( 'edit' ) ), $product->get_gallery_image_ids( 'edit' ) );
$images = array();
foreach ( $image_ids as $image_id ) {
$image = wp_get_attachment_image_src( $image_id, 'full' );
if ( $image ) {
$images[] = $image[0];
}
}
return implode( ', ', $images );
}
/**
* Prepare linked products for export.
*
* @since 3.1.0
* @param int[] $linked_products
* @return string
*/
protected function prepare_linked_products_for_export( $linked_products ) {
$product_list = array();
foreach ( $linked_products as $linked_product ) {
if ( $linked_product->get_sku() ) {
$product_list[] = $linked_product->get_sku();
} else {
$product_list[] = 'id:' . $linked_product->get_id();
}
}
return implode( ',', $product_list );
}
/**
* Get cross_sell_ids value.
*
* @since 3.1.0
* @param WC_Product $product
* @return string
*/
protected function get_column_value_cross_sell_ids( $product ) {
return $this->prepare_linked_products_for_export( array_filter( array_map( 'wc_get_product', (array) $product->get_cross_sell_ids( 'edit' ) ) ) );
}
/**
* Get upsell_ids value.
*
* @since 3.1.0
* @param WC_Product $product
* @return string
*/
protected function get_column_value_upsell_ids( $product ) {
return $this->prepare_linked_products_for_export( array_filter( array_map( 'wc_get_product', (array) $product->get_upsell_ids( 'edit' ) ) ) );
}
/**
* Get parent_id value.
*
* @since 3.1.0
* @param WC_Product $product
* @return string
*/
protected function get_column_value_parent_id( $product ) {
if ( $product->get_parent_id( 'edit' ) ) {
$parent = wc_get_product( $product->get_parent_id( 'edit' ) );
if ( ! $parent ) {
return '';
}
return $parent->get_sku( 'edit' ) ? $parent->get_sku( 'edit' ) : 'id:' . $parent->get_id();
}
return '';
}
/**
* Get grouped_products value.
*
* @since 3.1.0
* @param WC_Product $product
* @return string
*/
protected function get_column_value_grouped_products( $product ) {
if ( 'grouped' !== $product->get_type() ) {
return '';
}
$grouped_products = array();
$child_ids = $product->get_children( 'edit' );
foreach ( $child_ids as $child_id ) {
$child = wc_get_product( $child_id );
if ( ! $child ) {
continue;
}
$grouped_products[] = $child->get_sku( 'edit' ) ? $child->get_sku( 'edit' ) : 'id:' . $child_id;
}
return implode( ',', $grouped_products );
}
/**
* Get download_limit value.
*
* @since 3.1.0
* @param WC_Product $product
* @return string
*/
protected function get_column_value_download_limit( $product ) {
return $product->is_downloadable() && $product->get_download_limit( 'edit' ) ? $product->get_download_limit( 'edit' ) : '';
}
/**
* Get download_expiry value.
*
* @since 3.1.0
* @param WC_Product $product
* @return string
*/
protected function get_column_value_download_expiry( $product ) {
return $product->is_downloadable() && $product->get_download_expiry( 'edit' ) ? $product->get_download_expiry( 'edit' ) : '';
}
/**
* Get stock value.
*
* @since 3.1.0
* @param WC_Product $product
* @return string
*/
protected function get_column_value_stock( $product ) {
$manage_stock = $product->get_manage_stock( 'edit' );
$stock_quantity = $product->get_stock_quantity( 'edit' );
if ( $product->is_type( 'variation' && 'parent' === $manage_stock ) ) {
return 'parent';
} elseif ( $manage_stock ) {
return $stock_quantity;
} else {
return '';
}
}
/**
* Get stock status value.
*
* @since 3.1.0
* @param WC_Product $product
* @return string
*/
protected function get_column_value_stock_status( $product ) {
$status = $product->get_stock_status( 'edit' );
return 'instock' === $status ? 1 : 0;
}
/**
* Get backorders.
*
* @since 3.1.0
* @param WC_Product $product
* @return string
*/
protected function get_column_value_backorders( $product ) {
$backorders = $product->get_backorders( 'edit' );
switch ( $backorders ) {
case 'notify' :
return 'notify';
default :
return wc_string_to_bool( $backorders ) ? 1 : 0;
}
}
/**
* Get type value.
*
* @since 3.1.0
* @param WC_Product $product
* @return string
*/
protected function get_column_value_type( $product ) {
$types = array();
$types[] = $product->get_type();
if ( $product->is_downloadable() ) {
$types[] = 'downloadable';
}
if ( $product->is_virtual() ) {
$types[] = 'virtual';
}
return implode( ', ', $types );
}
/**
* Export downloads.
*
* @since 3.1.0
* @param WC_Product $product
* @param array $row
*/
protected function prepare_downloads_for_export( $product, &$row ) {
if ( $product->is_downloadable() && $this->is_column_exporting( 'downloads' ) ) {
$downloads = $product->get_downloads( 'edit' );
if ( $downloads ) {
$i = 1;
foreach ( $downloads as $download ) {
$this->column_names[ 'downloads:name' . $i ] = sprintf( __( 'Download %d name', 'woocommerce' ), $i );
$this->column_names[ 'downloads:url' . $i ] = sprintf( __( 'Download %d URL', 'woocommerce' ), $i );
$row[ 'downloads:name' . $i ] = $download->get_name();
$row[ 'downloads:url' . $i ] = $download->get_file();
$i++;
}
}
}
}
/**
* Export attributes data.
*
* @since 3.1.0
* @param WC_Product $product
* @param array $row
*/
protected function prepare_attributes_for_export( $product, &$row ) {
if ( $this->is_column_exporting( 'attributes' ) ) {
$attributes = $product->get_attributes();
$default_attributes = $product->get_default_attributes();
if ( count( $attributes ) ) {
$i = 1;
foreach ( $attributes as $attribute_name => $attribute ) {
$this->column_names[ 'attributes:name' . $i ] = sprintf( __( 'Attribute %d name', 'woocommerce' ), $i );
$this->column_names[ 'attributes:value' . $i ] = sprintf( __( 'Attribute %d value(s)', 'woocommerce' ), $i );
$this->column_names[ 'attributes:visible' . $i ] = sprintf( __( 'Attribute %d visible', 'woocommerce' ), $i );
$this->column_names[ 'attributes:taxonomy' . $i ] = sprintf( __( 'Attribute %d global', 'woocommerce' ), $i );
if ( is_a( $attribute, 'WC_Product_Attribute' ) ) {
$row[ 'attributes:name' . $i ] = wc_attribute_label( $attribute->get_name(), $product );
if ( $attribute->is_taxonomy() ) {
$terms = $attribute->get_terms();
$values = array();
foreach ( $terms as $term ) {
$values[] = $term->name;
}
$row[ 'attributes:value' . $i ] = implode( ', ', $values );
$row[ 'attributes:taxonomy' . $i ] = 1;
} else {
$row[ 'attributes:value' . $i ] = implode( ', ', $attribute->get_options() );
$row[ 'attributes:taxonomy' . $i ] = 0;
}
$row[ 'attributes:visible' . $i ] = $attribute->get_visible();
} else {
$row[ 'attributes:name' . $i ] = wc_attribute_label( $attribute_name, $product );
if ( 0 === strpos( $attribute_name, 'pa_' ) ) {
$option_term = get_term_by( 'slug', $attribute, $attribute_name );
$row[ 'attributes:value' . $i ] = $option_term && ! is_wp_error( $option_term ) ? $option_term->name : $attribute;
$row[ 'attributes:taxonomy' . $i ] = 1;
} else {
$row[ 'attributes:value' . $i ] = $attribute;
$row[ 'attributes:taxonomy' . $i ] = 0;
}
$row[ 'attributes:visible' . $i ] = '';
}
if ( $product->is_type( 'variable' ) && isset( $default_attributes[ sanitize_title( $attribute_name ) ] ) ) {
$this->column_names[ 'attributes:default' . $i ] = sprintf( __( 'Attribute %d default', 'woocommerce' ), $i );
$default_value = $default_attributes[ sanitize_title( $attribute_name ) ];
if ( 0 === strpos( $attribute_name, 'pa_' ) ) {
$option_term = get_term_by( 'slug', $default_value, $attribute_name );
$row[ 'attributes:default' . $i ] = $option_term && ! is_wp_error( $option_term ) ? $option_term->name : $default_value;
} else {
$row[ 'attributes:default' . $i ] = $default_value;
}
}
$i++;
}
}
}
}
/**
* Export meta data.
*
* @since 3.1.0
* @param WC_Product $product
* @param array $row
*/
protected function prepare_meta_for_export( $product, &$row ) {
if ( $this->enable_meta_export ) {
$meta_data = $product->get_meta_data();
if ( count( $meta_data ) ) {
$i = 1;
foreach ( $meta_data as $meta ) {
if ( ! is_scalar( $meta->value ) ) {
continue;
}
$column_key = 'meta:' . esc_attr( $meta->key );
$this->column_names[ $column_key ] = sprintf( __( 'Meta: %s', 'woocommerce' ), $meta->key );
$row[ $column_key ] = $meta->value;
$i++;
}
}
}
}
}

View File

@ -0,0 +1,611 @@
<?php
/**
* Abstract Product importer
*
* @author Automattic
* @category Admin
* @package WooCommerce/Import
* @version 3.1.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Include dependencies.
*/
if ( ! class_exists( 'WC_Importer_Interface', false ) ) {
include_once( WC_ABSPATH . 'includes/interfaces/class-wc-importer-interface.php' );
}
/**
* WC_Product_Importer Class.
*/
abstract class WC_Product_Importer implements WC_Importer_Interface {
/**
* CSV file.
*
* @var string
*/
protected $file = '';
/**
* The file position after the last read.
*
* @var int
*/
protected $file_position = 0;
/**
* Importer parameters.
*
* @var array
*/
protected $params = array();
/**
* Raw keys - CSV raw headers.
*
* @var array
*/
protected $raw_keys = array();
/**
* Mapped keys - CSV headers.
*
* @var array
*/
protected $mapped_keys = array();
/**
* Raw data.
*
* @var array
*/
protected $raw_data = array();
/**
* Parsed data.
*
* @var array
*/
protected $parsed_data = array();
/**
* Get file raw headers.
*
* @return array
*/
public function get_raw_keys() {
return $this->raw_keys;
}
/**
* Get file mapped headers.
*
* @return array
*/
public function get_mapped_keys() {
return ! empty( $this->mapped_keys ) ? $this->mapped_keys : $this->raw_keys;
}
/**
* Get raw data.
*
* @return array
*/
public function get_raw_data() {
return $this->raw_data;
}
/**
* Get parsed data.
*
* @return array
*/
public function get_parsed_data() {
return apply_filters( 'woocommerce_product_importer_parsed_data', $this->parsed_data, $this->get_raw_data() );
}
/**
* Get file pointer position from the last read.
*
* @return int
*/
public function get_file_position() {
return $this->file_position;
}
/**
* Get file pointer position as a percentage of file size.
*
* @return int
*/
public function get_percent_complete() {
$size = filesize( $this->file );
if ( ! $size ) {
return 0;
}
return absint( min( round( ( $this->file_position / $size ) * 100 ), 100 ) );
}
/**
* Prepare a single product for create or update.
*
* @param array $data Item data.
* @return WC_Product|WP_Error
*/
protected function get_product_object( $data ) {
$id = isset( $data['id'] ) ? absint( $data['id'] ) : 0;
// Type is the most important part here because we need to be using the correct class and methods.
if ( isset( $data['type'] ) ) {
$types = array_keys( wc_get_product_types() );
$types[] = 'variation';
if ( ! in_array( $data['type'], $types, true ) ) {
return new WP_Error( 'woocommerce_product_importer_invalid_type', __( 'Invalid product type.', 'woocommerce' ), array( 'status' => 401 ) );
}
$classname = WC_Product_Factory::get_classname_from_product_type( $data['type'] );
if ( ! class_exists( $classname ) ) {
$classname = 'WC_Product_Simple';
}
$product = new $classname( $id );
} elseif ( isset( $data['id'] ) ) {
$product = wc_get_product( $id );
if ( ! $product ) {
return new WP_Error( 'woocommerce_product_csv_importer_invalid_id', sprintf( __( 'Invalid product ID %d.', 'woocommerce' ), $id ), array( 'id' => $id, 'status' => 401 ) );
}
} else {
$product = new WC_Product_Simple( $id );
}
return apply_filters( 'woocommerce_product_import_get_product_object', $product, $data );
}
/**
* Process a single item and save.
*
* @param array $data Raw CSV data.
* @return array|WC_Error
*/
protected function process_item( $data ) {
try {
// Get product ID from SKU if created during the importation.
if ( empty( $data['id'] ) && ! empty( $data['sku'] ) && ( $product_id = wc_get_product_id_by_sku( $data['sku'] ) ) ) {
$data['id'] = $product_id;
}
$object = $this->get_product_object( $data );
$updating = false;
if ( is_wp_error( $object ) ) {
return $object;
}
if ( $object->get_id() && 'importing' !== $object->get_status() ) {
$updating = true;
}
if ( 'external' === $object->get_type() ) {
unset( $data['manage_stock'], $data['stock_status'], $data['backorders'] );
}
$result = $object->set_props( array_diff_key( $data, array_flip( array( 'meta_data', 'raw_image_id', 'raw_gallery_image_ids', 'raw_attributes' ) ) ) );
if ( is_wp_error( $result ) ) {
throw new Exception( $result->get_error_message() );
}
if ( 'variation' === $object->get_type() ) {
$this->set_variation_data( $object, $data );
} else {
$this->set_product_data( $object, $data );
}
$this->set_image_data( $object, $data );
$this->set_meta_data( $object, $data );
if ( 'importing' === $object->get_status() ) {
$object->set_status( 'publish' );
}
$object = apply_filters( 'woocommerce_product_import_pre_insert_product_object', $object, $data );
$object->save();
return array(
'id' => $object->get_id(),
'updated' => $updating,
);
} catch ( Exception $e ) {
return new WP_Error( 'woocommerce_product_importer_error', $e->getMessage(), array( 'status' => $e->getCode() ) );
}
}
/**
* Convert raw image URLs to IDs and set.
*
* @param WC_Product $product Product instance.
* @param array $data Item data.
*/
protected function set_image_data( &$product, $data ) {
// Image URLs need converting to IDs before inserting.
if ( isset( $data['raw_image_id'] ) ) {
$product->set_image_id( $this->get_attachment_id_from_url( $data['raw_image_id'], $product->get_id() ) );
}
// Gallery image URLs need converting to IDs before inserting.
if ( isset( $data['raw_gallery_image_ids'] ) ) {
$gallery_image_ids = array();
foreach ( $data['raw_gallery_image_ids'] as $image_id ) {
$gallery_image_ids[] = $this->get_attachment_id_from_url( $image_id, $product->get_id() );
}
$product->set_gallery_image_ids( $gallery_image_ids );
}
}
/**
* Append meta data.
*
* @param WC_Product $product Product instance.
* @param array $data Item data.
*/
protected function set_meta_data( &$product, $data ) {
if ( isset( $data['meta_data'] ) ) {
foreach ( $data['meta_data'] as $meta ) {
$product->update_meta_data( $meta['key'], $meta['value'] );
}
}
}
/**
* Set product data.
*
* @param WC_Product $product Product instance.
* @param array $data Item data.
*
* @return WC_Product|WP_Error
* @throws Exception
*/
protected function set_product_data( &$product, $data ) {
if ( isset( $data['raw_attributes'] ) ) {
$attributes = array();
$default_attributes = array();
foreach ( $data['raw_attributes'] as $position => $attribute ) {
$attribute_id = 0;
// Get ID if is a global attribute.
if ( ! empty( $attribute['taxonomy'] ) ) {
$attribute_id = $this->get_attribute_taxonomy_id_by_name( $attribute['name'] );
}
// Set attribute visibility.
if ( isset( $attribute['visible'] ) ) {
$is_visible = $attribute['visible'];
} else {
$is_visible = 1;
}
// Set if is a variation attribute.
$is_variation = 0;
if ( $attribute_id ) {
$attribute_name = wc_attribute_taxonomy_name_by_id( $attribute_id );
if ( isset( $attribute['value'] ) ) {
$options = array_map( 'wc_sanitize_term_text_based', $attribute['value'] );
$options = array_filter( $options, 'strlen' );
} else {
$options = array();
}
// Check for default attributes and set "is_variation".
if ( ! empty( $attribute['default'] ) && in_array( $attribute['default'], $options ) ) {
$default_term = get_term_by( 'name', $attribute['default'], $attribute_name );
if ( $default_term && ! is_wp_error( $default_term ) ) {
$default = $default_term->slug;
} else {
$default = sanitize_title( $attribute['default'] );
}
$default_attributes[ $attribute_name ] = $default;
$is_variation = 1;
}
if ( ! empty( $options ) ) {
$attribute_object = new WC_Product_Attribute();
$attribute_object->set_id( $attribute_id );
$attribute_object->set_name( $attribute_name );
$attribute_object->set_options( $options );
$attribute_object->set_position( $position );
$attribute_object->set_visible( $is_visible );
$attribute_object->set_variation( $is_variation );
$attributes[] = $attribute_object;
}
} elseif ( isset( $attribute['value'] ) ) {
// Check for default attributes and set "is_variation".
if ( ! empty( $attribute['default'] ) && in_array( $attribute['default'], $attribute['value'] ) ) {
$default_attributes[ sanitize_title( $attribute['name'] ) ] = $attribute['default'];
$is_variation = 1;
}
$attribute_object = new WC_Product_Attribute();
$attribute_object->set_name( $attribute['name'] );
$attribute_object->set_options( $attribute['value'] );
$attribute_object->set_position( $position );
$attribute_object->set_visible( $is_visible );
$attribute_object->set_variation( $is_variation );
$attributes[] = $attribute_object;
}
}
$product->set_attributes( $attributes );
// Set variable default attributes.
if ( $product->is_type( 'variable' ) ) {
$product->set_default_attributes( $default_attributes );
}
}
}
/**
* Set variation data.
*
* @param WC_Product $variation Product instance.
* @param array $data Item data.
*
* @return WC_Product|WP_Error
* @throws Exception
*/
protected function set_variation_data( &$variation, $data ) {
$parent = false;
// Check if parent exist.
if ( isset( $data['parent_id'] ) ) {
$parent = wc_get_product( $data['parent_id'] );
if ( $parent ) {
$variation->set_parent_id( $parent->get_id() );
}
}
// Stop if parent does not exists.
if ( ! $parent ) {
return new WP_Error( 'woocommerce_product_importer_missing_variation_parent_id', __( 'Variation cannot be imported: Missing parent ID or parent does not exist yet.', 'woocommerce' ), array( 'status' => 401 ) );
}
if ( isset( $data['raw_attributes'] ) ) {
$attributes = array();
$parent_attributes = $this->get_variation_parent_attributes( $data['raw_attributes'], $parent );
foreach ( $data['raw_attributes'] as $attribute ) {
// Get ID if is a global attribute.
$attribute_id = wc_attribute_taxonomy_id_by_name( $attribute['name'] );
if ( $attribute_id ) {
$attribute_name = wc_attribute_taxonomy_name_by_id( $attribute_id );
} else {
$attribute_name = sanitize_title( $attribute['name'] );
}
if ( ! isset( $parent_attributes[ $attribute_name ] ) || ! $parent_attributes[ $attribute_name ]->get_variation() ) {
continue;
}
$attribute_key = sanitize_title( $parent_attributes[ $attribute_name ]->get_name() );
$attribute_value = isset( $attribute['value'] ) ? current( $attribute['value'] ) : '';
if ( $parent_attributes[ $attribute_name ]->is_taxonomy() ) {
// If dealing with a taxonomy, we need to get the slug from the name posted to the API.
$term = get_term_by( 'name', $attribute_value, $attribute_name );
if ( $term && ! is_wp_error( $term ) ) {
$attribute_value = $term->slug;
} else {
$attribute_value = sanitize_title( $attribute_value );
}
}
$attributes[ $attribute_key ] = $attribute_value;
}
$variation->set_attributes( $attributes );
}
}
/**
* Get variation parent attributes and set "is_variation".
*
* @param array $attributes Attributes list.
* @param WC_Product $parent Parent product data.
* @return array
*/
protected function get_variation_parent_attributes( $attributes, $parent ) {
$parent_attributes = $parent->get_attributes();
$require_save = false;
foreach ( $attributes as $attribute ) {
$attribute_id = 0;
// Get ID if is a global attribute.
if ( ! empty( $attribute['taxonomy'] ) ) {
$attribute_id = $this->get_attribute_taxonomy_id_by_name( $attribute['name'] );
}
if ( $attribute_id ) {
$attribute_name = wc_attribute_taxonomy_name_by_id( $attribute_id );
} else {
$attribute_name = sanitize_title( $attribute['name'] );
}
// Check if attribute handle variations.
if ( isset( $parent_attributes[ $attribute_name ] ) && ! $parent_attributes[ $attribute_name ]->get_variation() ) {
// Re-create the attribute to CRUD save and genarate again.
$parent_attributes[ $attribute_name ] = clone $parent_attributes[ $attribute_name ];
$parent_attributes[ $attribute_name ]->set_variation( 1 );
$require_save = true;
}
}
// Save variation attributes.
if ( $require_save ) {
$parent->set_attributes( array_values( $parent_attributes ) );
$parent->save();
}
return $parent_attributes;
}
/**
* Get attachment ID.
*
* @param string $url Attachment URL.
* @param int $product_id Product ID.
* @return int
*/
protected function get_attachment_id_from_url( $url, $product_id ) {
if ( empty( $url ) ) {
return 0;
}
$id = 0;
$upload_dir = wp_upload_dir();
$base_url = $upload_dir['baseurl'] . '/';
// Check first if attachment is on WordPress uploads directory.
if ( false !== strpos( $url, $base_url ) ) {
// Search for yyyy/mm/slug.extension
$file = str_replace( $base_url, '', $url );
$args = array(
'post_type' => 'attachment',
'post_status' => 'any',
'fields' => 'ids',
'meta_query' => array(
array(
'value' => $file,
'compare' => 'LIKE',
'key' => '_wp_attachment_metadata',
),
),
);
if ( $ids = get_posts( $args ) ) {
$id = current( $ids );
}
} else {
$args = array(
'post_type' => 'attachment',
'post_status' => 'any',
'fields' => 'ids',
'meta_query' => array(
array(
'value' => $url,
'key' => '_wc_attachment_source',
),
),
);
if ( $ids = get_posts( $args ) ) {
$id = current( $ids );
}
}
// Upload if attachment does not exists.
if ( ! $id ) {
$upload = wc_rest_upload_image_from_url( $url );
if ( is_wp_error( $upload ) ) {
throw new Exception( $upload->get_error_message(), 400 );
}
$id = wc_rest_set_uploaded_image_as_attachment( $upload, $product_id );
if ( ! wp_attachment_is_image( $id ) ) {
throw new Exception( sprintf( __( 'Not able to attach "%s".', 'woocommerce' ), $url ), 400 );
}
// Save attachment source for future reference.
update_post_meta( $id, '_wc_attachment_source', $url );
}
return $id;
}
/**
* Get attribute taxonomy ID by name.
* If does not exists register a new attribute.
*
* @param string $name Attribute name.
* @return int
*/
protected function get_attribute_taxonomy_id_by_name( $name ) {
global $wpdb;
// Check if exists.
if ( $attribute_id = wc_attribute_taxonomy_id_by_name( $name ) ) {
return $attribute_id;
}
// Register new attribute.
$slug = wc_sanitize_taxonomy_name( $name );
$args = array(
'attribute_label' => $name,
'attribute_name' => wc_sanitize_taxonomy_name( $name ),
'attribute_type' => 'select',
'attribute_orderby' => 'menu_order',
'attribute_public' => 0,
);
// Validate attribute.
if ( strlen( $slug ) >= 28 ) {
throw new Exception( sprintf( __( 'Slug "%s" is too long (28 characters max). Shorten it, please.', 'woocommerce' ), $slug ), 400 );
} elseif ( wc_check_if_attribute_name_is_reserved( $slug ) ) {
throw new Exception( sprintf( __( 'Slug "%s" is not allowed because it is a reserved term. Change it, please.', 'woocommerce' ), $slug ), 400 );
} elseif ( $new_data && taxonomy_exists( wc_attribute_taxonomy_name( $slug ) ) ) {
throw new Exception( sprintf( __( 'Slug "%s" is already in use. Change it, please.', 'woocommerce' ), $slug ), 400 );
}
$result = $wpdb->insert(
$wpdb->prefix . 'woocommerce_attribute_taxonomies',
$args,
array( '%s', '%s', '%s', '%s', '%d' )
);
// Pass errors.
if ( is_wp_error( $result ) ) {
throw new Exception( $result->get_error_message(), 400 );
}
// Delete transient.
delete_transient( 'wc_attribute_taxonomies' );
// Register as taxonomy while importing.
$taxonomy_data = array(
'labels' => array(
'name' => $name,
),
);
register_taxonomy( wc_attribute_taxonomy_name( $slug ), array( 'product' ), $taxonomy_data );
// Set product attributes global.
global $wc_product_attributes;
$wc_product_attributes = array();
foreach ( wc_get_attribute_taxonomies() as $tax ) {
if ( $name = wc_attribute_taxonomy_name( $tax->attribute_name ) ) {
$wc_product_attributes[ $name ] = $tax;
}
}
return $wpdb->insert_id;
}
}

View File

@ -0,0 +1,684 @@
<?php
/**
* WooCommerce Product CSV importer
*
* @author Automattic
* @category Admin
* @package WooCommerce/Import
* @version 3.1.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Include dependencies.
*/
if ( ! class_exists( 'WC_Product_Importer', false ) ) {
include_once( dirname( __FILE__ ) . '/abstract-wc-product-importer.php' );
}
/**
* WC_Product_CSV_Importer Class.
*/
class WC_Product_CSV_Importer extends WC_Product_Importer {
/**
* Initialize importer.
*
* @param string $file File to read.
* @param array $args Arguments for the parser.
*/
public function __construct( $file, $params = array() ) {
$default_args = array(
'start_pos' => 0, // File pointer start.
'end_pos' => -1, // File pointer end.
'lines' => -1, // Max lines to read.
'mapping' => array(), // Column mapping. csv_heading => schema_heading.
'parse' => false, // Whether to sanitize and format data.
'update_existing' => false, // Whether to update existing items.
'delimiter' => ',', // CSV delimiter.
);
$this->params = wp_parse_args( $params, $default_args );
$this->file = $file;
$this->read_file();
}
/**
* Read file.
*
* @return array
*/
protected function read_file() {
if ( false !== ( $handle = fopen( $this->file, 'r' ) ) ) {
$this->raw_keys = fgetcsv( $handle, 0, $this->params['delimiter'] );
if ( 0 !== $this->params['start_pos'] ) {
fseek( $handle, (int) $this->params['start_pos'] );
}
while ( false !== ( $row = fgetcsv( $handle, 0, $this->params['delimiter'] ) ) ) {
$this->raw_data[] = $row;
if ( ( $this->params['end_pos'] > 0 && ftell( $handle ) >= $this->params['end_pos'] ) || 0 === --$this->params['lines'] ) {
break;
}
}
$this->file_position = ftell( $handle );
}
if ( ! empty( $this->params['mapping'] ) ) {
$this->set_mapped_keys();
}
if ( $this->params['parse'] ) {
$this->set_parsed_data();
}
}
/**
* Set file mapped keys.
*
* @return array
*/
protected function set_mapped_keys() {
$mapping = $this->params['mapping'];
foreach ( $this->raw_keys as $key ) {
$this->mapped_keys[] = isset( $mapping[ $key ] ) ? $mapping[ $key ] : $key;
}
}
/**
* Parse relative field and return product ID.
*
* Handles `id:xx` and SKUs.
*
* If mapping to an id: and the product ID does not exist, this link is not
* valid.
*
* If mapping to a SKU and the product ID does not exist, a temporary object
* will be created so it can be updated later.
*
* @param string $field Field value.
* @return int|string
*/
protected function parse_relative_field( $field ) {
if ( empty( $field ) ) {
return '';
}
if ( preg_match( '/^id:(\d+)$/', $field, $matches ) ) {
return intval( $matches[1] );
}
if ( $id = wc_get_product_id_by_sku( $field ) ) {
return $id;
}
try {
$product = new WC_Product_Simple();
$product->set_name( 'Import placeholder for ' . $field );
$product->set_status( 'importing' );
$product->set_sku( $field );
$id = $product->save();
if ( $id && ! is_wp_error( $id ) ) {
return $id;
}
} catch ( Exception $e ) {
return '';
}
return '';
}
/**
* Parse reletive comma-delineated field and return product ID.
*
* @param string $field Field value.
* @return array
*/
protected function parse_relative_comma_field( $field ) {
if ( empty( $field ) ) {
return array();
}
return array_filter( array_map( array( $this, 'parse_relative_field' ), array_map( 'trim', explode( ',', $field ) ) ) );
}
/**
* Parse a comma-delineated field from a CSV.
*
* @param string $field Field value.
* @return array
*/
protected function parse_comma_field( $field ) {
if ( empty( $field ) ) {
return array();
}
return array_map( 'wc_clean', array_map( 'trim', explode( ',', $field ) ) );
}
/**
* Parse a field that is generally '1' or '0' but can be something else.
*
* @param string $field Field value.
* @return bool|string
*/
protected function parse_bool_field( $field ) {
if ( '0' === $field ) {
return false;
}
if ( '1' === $field ) {
return true;
}
// Don't return explicit true or false for empty fields or values like 'notify'.
return wc_clean( $field );
}
/**
* Parse a float value field.
*
* @param string $field Field value.
* @return float|string
*/
protected function parse_float_field( $field ) {
if ( '' === $field ) {
return $field;
}
return floatval( $field );
}
/**
* Parse a category field from a CSV.
* Categories are separated by commas and subcategories are "parent > subcategory".
*
* @param string $field Field value.
* @return array of arrays with "parent" and "name" keys.
*/
protected function parse_categories_field( $field ) {
if ( empty( $field ) ) {
return array();
}
$row_terms = array_map( 'trim', explode( ',', $field ) );
$categories = array();
foreach ( $row_terms as $row_term ) {
$parent = null;
$_terms = array_map( 'trim', explode( '>', $row_term ) );
$total = count( $_terms );
foreach ( $_terms as $index => $_term ) {
// Check if category exists. Parent must be empty string or null if doesn't exists.
// @codingStandardsIgnoreStart
$term = term_exists( $_term, 'product_cat', $parent );
// @codingStandardsIgnoreEnd
if ( is_array( $term ) ) {
$term_id = $term['term_id'];
} else {
$term = wp_insert_term( $_term, 'product_cat', array( 'parent' => intval( $parent ) ) );
$term_id = $term['term_id'];
}
// Only requires assign the last category.
if ( ( 1 + $index ) === $total ) {
$categories[] = $term_id;
} else {
// Store parent to be able to insert or query categories based in parent ID.
$parent = $term_id;
}
}
}
return $categories;
}
/**
* Parse a tag field from a CSV.
*
* @param string $field Field value.
* @return array
*/
protected function parse_tags_field( $field ) {
if ( empty( $field ) ) {
return array();
}
$names = array_map( 'trim', explode( ',', $field ) );
$tags = array();
foreach ( $names as $name ) {
$term = get_term_by( 'name', $name, 'product_tag' );
if ( ! $term || is_wp_error( $term ) ) {
$term = (object) wp_insert_term( $name, 'product_tag' );
}
$tags[] = $term->term_id;
}
return $tags;
}
/**
* Parse a shipping class field from a CSV.
*
* @param string $field Field value.
* @return int
*/
protected function parse_shipping_class_field( $field ) {
if ( empty( $field ) ) {
return 0;
}
$term = get_term_by( 'name', $field, 'product_shipping_class' );
if ( ! $term || is_wp_error( $term ) ) {
$term = (object) wp_insert_term( $field, 'product_shipping_class' );
}
return $term->term_id;
}
/**
* Parse images list from a CSV.
*
* @param string $field Field value.
* @return array
*/
protected function parse_images_field( $field ) {
if ( empty( $field ) ) {
return array();
}
return array_map( 'esc_url_raw', array_map( 'trim', explode( ',', $field ) ) );
}
/**
* Parse dates from a CSV.
* Dates requires the format YYYY-MM-DD.
*
* @param string $field Field value.
* @return string|null
*/
protected function parse_date_field( $field ) {
if ( empty( $field ) ) {
return null;
}
if ( preg_match( '/^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])$/', $field ) ) {
return $field;
}
return null;
}
/**
* Parse backorders from a CSV.
*
* @param string $field Field value.
* @return string
*/
protected function parse_backorders_field( $field ) {
if ( empty( $field ) ) {
return '';
}
$field = $this->parse_bool_field( $field );
if ( 'notify' === $field ) {
return 'notify';
} elseif ( is_bool( $field ) ) {
return $field ? 'yes' : 'no';
}
return '';
}
/**
* Get formatting callback.
*
* @return array
*/
protected function get_formating_callback() {
/**
* Columns not mentioned here will get parsed with 'wc_clean'.
* column_name => callback.
*/
$data_formatting = array(
'id' => 'absint',
'type' => array( $this, 'parse_comma_field' ),
'published' => array( $this, 'parse_bool_field' ),
'featured' => array( $this, 'parse_bool_field' ),
'date_on_sale_from' => array( $this, 'parse_date_field' ),
'date_on_sale_to' => array( $this, 'parse_date_field' ),
'name' => 'wp_filter_post_kses',
'short_description' => 'wp_filter_post_kses',
'description' => 'wp_filter_post_kses',
'manage_stock' => array( $this, 'parse_bool_field' ),
'backorders' => array( $this, 'parse_backorders_field' ),
'stock_status' => array( $this, 'parse_bool_field' ),
'sold_individually' => array( $this, 'parse_bool_field' ),
'width' => array( $this, 'parse_float_field' ),
'length' => array( $this, 'parse_float_field' ),
'height' => array( $this, 'parse_float_field' ),
'weight' => array( $this, 'parse_float_field' ),
'reviews_allowed' => array( $this, 'parse_bool_field' ),
'purchase_note' => 'wp_filter_post_kses',
'price' => 'wc_format_decimal',
'regular_price' => 'wc_format_decimal',
'stock_quantity' => 'wc_stock_amount',
'category_ids' => array( $this, 'parse_categories_field' ),
'tag_ids' => array( $this, 'parse_tags_field' ),
'shipping_class_id' => array( $this, 'parse_shipping_class_field' ),
'images' => array( $this, 'parse_images_field' ),
'parent_id' => array( $this, 'parse_relative_field' ),
'grouped_products' => array( $this, 'parse_relative_comma_field' ),
'upsell_ids' => array( $this, 'parse_relative_comma_field' ),
'cross_sell_ids' => array( $this, 'parse_relative_comma_field' ),
'download_limit' => 'absint',
'download_expiry' => 'absint',
'product_url' => 'esc_url_raw',
);
/**
* Match special column names.
*/
$regex_match_data_formatting = array(
'/attributes:value*/' => array( $this, 'parse_comma_field' ),
'/attributes:visible*/' => array( $this, 'parse_bool_field' ),
'/attributes:taxonomy*/' => array( $this, 'parse_bool_field' ),
'/downloads:url*/' => 'esc_url',
'/meta:*/' => 'wp_kses_post', // Allow some HTML in meta fields.
);
$callbacks = array();
// Figure out the parse function for each column.
foreach ( $this->get_mapped_keys() as $index => $heading ) {
$callback = 'wc_clean';
if ( isset( $data_formatting[ $heading ] ) ) {
$callback = $data_formatting[ $heading ];
} else {
foreach ( $regex_match_data_formatting as $regex => $callback ) {
if ( preg_match( $regex, $heading ) ) {
$callback = $callback;
break;
}
}
}
$callbacks[] = $callback;
}
return $callbacks;
}
/**
* Check if strings starts with determined word.
*
* @param string $haystack Complete sentence.
* @param string $needle Excerpt.
* @return bool
*/
protected function starts_with( $haystack, $needle ) {
return substr( $haystack, 0, strlen( $needle ) ) === $needle;
}
/**
* Expand special and internal data into the correct formats for the product CRUD.
*
* @param array $data Data to import.
* @return array
*/
protected function expand_data( $data ) {
$data = apply_filters( 'woocommerce_product_importer_pre_expand_data', $data );
// Status is mapped from a special published field.
if ( isset( $data['published'] ) ) {
$data['status'] = ( $data['published'] ? 'publish' : 'draft' );
unset( $data['published'] );
}
// Images field maps to image and gallery id fields.
if ( isset( $data['images'] ) ) {
$images = $data['images'];
$data['raw_image_id'] = array_shift( $images );
if ( ! empty( $images ) ) {
$data['raw_gallery_image_ids'] = $images;
}
unset( $data['images'] );
}
// Type, virtual and downloadable are all stored in the same column.
if ( isset( $data['type'] ) ) {
$data['type'] = array_map( 'strtolower', $data['type'] );
$data['virtual'] = in_array( 'virtual', $data['type'], true );
$data['downloadable'] = in_array( 'downloadable', $data['type'], true );
// Convert type to string.
$data['type'] = current( array_diff( $data['type'], array( 'virtual', 'downloadable' ) ) );
}
if ( isset( $data['stock_quantity'] ) ) {
$data['manage_stock'] = 0 < $data['stock_quantity'];
}
// Stock is bool.
if ( isset( $data['stock_status'] ) ) {
$data['stock_status'] = $data['stock_status'] ? 'instock' : 'outofstock';
}
// Prepare grouped products.
if ( isset( $data['grouped_products'] ) ) {
$data['children'] = $data['grouped_products'];
unset( $data['grouped_products'] );
}
// Handle special column names which span multiple columns.
$attributes = array();
$downloads = array();
$meta_data = array();
foreach ( $data as $key => $value ) {
// Attributes.
if ( $this->starts_with( $key, 'attributes:name' ) ) {
if ( ! empty( $value ) ) {
$attributes[ str_replace( 'attributes:name', '', $key ) ]['name'] = $value;
}
unset( $data[ $key ] );
} elseif ( $this->starts_with( $key, 'attributes:value' ) ) {
$attributes[ str_replace( 'attributes:value', '', $key ) ]['value'] = $value;
unset( $data[ $key ] );
} elseif ( $this->starts_with( $key, 'attributes:taxonomy' ) ) {
$attributes[ str_replace( 'attributes:taxonomy', '', $key ) ]['taxonomy'] = wc_string_to_bool( $value );
unset( $data[ $key ] );
} elseif ( $this->starts_with( $key, 'attributes:visible' ) ) {
$attributes[ str_replace( 'attributes:visible', '', $key ) ]['visible'] = wc_string_to_bool( $value );
unset( $data[ $key ] );
} elseif ( $this->starts_with( $key, 'attributes:default' ) ) {
if ( ! empty( $value ) ) {
$attributes[ str_replace( 'attributes:default', '', $key ) ]['default'] = $value;
}
unset( $data[ $key ] );
// Downloads.
} elseif ( $this->starts_with( $key, 'downloads:name' ) ) {
if ( ! empty( $value ) ) {
$downloads[ str_replace( 'downloads:name', '', $key ) ]['name'] = $value;
}
unset( $data[ $key ] );
} elseif ( $this->starts_with( $key, 'downloads:url' ) ) {
if ( ! empty( $value ) ) {
$downloads[ str_replace( 'downloads:url', '', $key ) ]['url'] = $value;
}
unset( $data[ $key ] );
// Meta data.
} elseif ( $this->starts_with( $key, 'meta:' ) ) {
$meta_data[] = array(
'key' => str_replace( 'meta:', '', $key ),
'value' => $value,
);
unset( $data[ $key ] );
}
}
if ( ! empty( $attributes ) ) {
// Remove empty attributes and clear indexes.
foreach ( $attributes as $attribute ) {
if ( empty( $attribute['name'] ) ) {
continue;
}
$data['raw_attributes'][] = $attribute;
}
}
if ( ! empty( $downloads ) ) {
$data['downloads'] = array();
foreach ( $downloads as $key => $file ) {
if ( empty( $file['url'] ) ) {
continue;
}
$data['downloads'][] = array(
'name' => $file['name'] ? $file['name'] : wc_get_filename_from_url( $file['url'] ),
'file' => $file['url'],
);
}
}
if ( ! empty( $meta_data ) ) {
$data['meta_data'] = $meta_data;
}
return $data;
}
/**
* Map and format raw data to known fields.
*
* @return array
*/
protected function set_parsed_data() {
$parse_functions = $this->get_formating_callback();
$mapped_keys = $this->get_mapped_keys();
// Parse the data.
foreach ( $this->raw_data as $row ) {
// Skip empty rows.
if ( ! count( array_filter( $row ) ) ) {
continue;
}
$data = array();
foreach ( $row as $id => $value ) {
// Skip ignored columns.
if ( empty( $mapped_keys[ $id ] ) ) {
continue;
}
$data[ $mapped_keys[ $id ] ] = call_user_func( $parse_functions[ $id ], $value );
}
$this->parsed_data[] = apply_filters( 'woocommerce_product_importer_parsed_data', $this->expand_data( $data ) );
}
}
/**
* Get a string to identify the row from parsed data.
*
* @param array $parsed_data
* @return string
*/
protected function get_row_id( $parsed_data ) {
$id = isset( $parsed_data['id'] ) ? absint( $parsed_data['id'] ) : 0;
$sku = isset( $parsed_data['sku'] ) ? esc_attr( $parsed_data['sku'] ) : '';
$name = isset( $parsed_data['name'] ) ? esc_attr( $parsed_data['name'] ) : '';
$row_data = array();
if ( $name ) {
$row_data[] = $name;
}
if ( $id ) {
$row_data[] = sprintf( __( 'ID %d', 'woocommerce' ), $id );
}
if ( $sku ) {
$row_data[] = sprintf( __( 'SKU %s', 'woocommerce' ), $sku );
}
return implode( ', ', $row_data );
}
/**
* Process importer.
*
* @return array
*/
public function import() {
$data = array(
'imported' => array(),
'failed' => array(),
'updated' => array(),
'skipped' => array(),
);
foreach ( $this->parsed_data as $parsed_data_key => $parsed_data ) {
// Do not import products with IDs or SKUs that already exist if option
// is true UNLESS this is a dummy product created during this import.
if ( ! $this->params['update_existing'] ) {
$id = isset( $parsed_data['id'] ) ? absint( $parsed_data['id'] ) : 0;
$sku = isset( $parsed_data['sku'] ) ? esc_attr( $parsed_data['sku'] ) : '';
if ( $id ) {
$product = wc_get_product( $id );
if ( $product && 'importing' !== $product->get_status() ) {
$data['skipped'][] = new WP_Error( 'woocommerce_product_importer_error', __( 'A product with this ID already exists.', 'woocommerce' ), array( 'id' => $id, 'row' => $this->get_row_id( $parsed_data ) ) );
continue;
}
} elseif ( $sku && ( $id_from_sku = wc_get_product_id_by_sku( $sku ) ) ) {
$product = wc_get_product( $id_from_sku );
if ( $product && 'importing' !== $product->get_status() ) {
$data['skipped'][] = new WP_Error( 'woocommerce_product_importer_error', __( 'A product with this SKU already exists.', 'woocommerce' ), array( 'sku' => $sku, 'row' => $this->get_row_id( $parsed_data ) ) );
continue;
}
}
}
$result = $this->process_item( $parsed_data );
if ( is_wp_error( $result ) ) {
$result->add_data( array( 'row' => $this->get_row_id( $parsed_data ) ) );
$data['failed'][] = $result;
} elseif ( $result['updated'] ) {
$data['updated'][] = $result['id'];
} else {
$data['imported'][] = $result['id'];
}
}
return $data;
}
}

View File

@ -0,0 +1,77 @@
<?php
/**
* WooCommerce Importer Interface
*
* @author Automattic
* @category Admin
* @package WooCommerce/Import
* @version 3.1.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* WC_Importer_Interface class.
*/
interface WC_Importer_Interface {
/**
* Process importation.
* Returns an array with the imported and failed items.
* 'imported' contains a list of IDs.
* 'failed' contains a list of WP_Error objects.
*
* Example:
* ['imported' => [], 'failed' => []]
*
* @return array
*/
public function import();
/**
* Get file raw keys.
*
* CSV - Headers.
* XML - Element names.
* JSON - Keys
*
* @return array
*/
public function get_raw_keys();
/**
* Get file mapped headers.
*
* @return array
*/
public function get_mapped_keys();
/**
* Get raw data.
*
* @return array
*/
public function get_raw_data();
/**
* Get parsed data.
*
* @return array
*/
public function get_parsed_data();
/**
* Get file pointer position from the last read.
*
* @return int
*/
public function get_file_position();
/**
* Get file pointer position as a percentage of file size.
*
* @return int
*/
public function get_percent_complete();
}

View File

@ -99,8 +99,7 @@ function wc_delete_order_item( $item_id ) {
function wc_update_order_item_meta( $item_id, $meta_key, $meta_value, $prev_value = '' ) {
$data_store = WC_Data_Store::load( 'order-item' );
if ( $data_store->update_metadata( $item_id, $meta_key, $meta_value, $prev_value ) ) {
$cache_key = WC_Cache_Helper::get_cache_prefix( 'order-items' ) . 'object_meta_' . $item_id;
wp_cache_delete( $cache_key, 'order-items' );
WC_Cache_Helper::incr_cache_prefix( 'object_' . $item_id ); // Invalidate cache.
return true;
}
return false;
@ -119,8 +118,7 @@ function wc_update_order_item_meta( $item_id, $meta_key, $meta_value, $prev_valu
function wc_add_order_item_meta( $item_id, $meta_key, $meta_value, $unique = false ) {
$data_store = WC_Data_Store::load( 'order-item' );
if ( $meta_id = $data_store->add_metadata( $item_id, $meta_key, $meta_value, $unique ) ) {
$cache_key = WC_Cache_Helper::get_cache_prefix( 'order-items' ) . 'object_meta_' . $item_id;
wp_cache_delete( $cache_key, 'order-items' );
WC_Cache_Helper::incr_cache_prefix( 'object_' . $item_id ); // Invalidate cache.
return $meta_id;
}
return 0;
@ -139,8 +137,7 @@ function wc_add_order_item_meta( $item_id, $meta_key, $meta_value, $unique = fal
function wc_delete_order_item_meta( $item_id, $meta_key, $meta_value = '', $delete_all = false ) {
$data_store = WC_Data_Store::load( 'order-item' );
if ( $data_store->delete_metadata( $item_id, $meta_key, $meta_value, $delete_all ) ) {
$cache_key = WC_Cache_Helper::get_cache_prefix( 'order-items' ) . 'object_meta_' . $item_id;
wp_cache_delete( $cache_key, 'order-items' );
WC_Cache_Helper::incr_cache_prefix( 'object_' . $item_id ); // Invalidate cache.
return true;
}
return false;

View File

@ -209,6 +209,7 @@ function wc_product_dropdown_categories( $args = array(), $deprecated_hierarchic
'orderby' => 'name',
'selected' => $current_product_cat,
'menu_order' => false,
'option_select_text' => __( 'Select a category', 'woocommerce' ),
);
$args = wp_parse_args( $args, $defaults );
@ -225,7 +226,7 @@ function wc_product_dropdown_categories( $args = array(), $deprecated_hierarchic
}
$output = "<select name='product_cat' class='dropdown_product_cat'>";
$output .= '<option value="" ' . selected( $current_product_cat, '', false ) . '>' . esc_html__( 'Select a category', 'woocommerce' ) . '</option>';
$output .= '<option value="" ' . selected( $current_product_cat, '', false ) . '>' . esc_html( $args['option_select_text'] ) . '</option>';
$output .= wc_walk_category_dropdown_tree( $terms, 0, $args );
if ( $args['show_uncategorized'] ) {
$output .= '<option value="0" ' . selected( $current_product_cat, '0', false ) . '>' . esc_html__( 'Uncategorized', 'woocommerce' ) . '</option>';

View File

@ -185,6 +185,10 @@ Yes you can! Join in on our [GitHub repository](http://github.com/woocommerce/wo
* Tweak - When searching, disable WC sort order so results are sorted by relevance.
* Tweak - Update price sorting code to use min or max for variable products depending on sorting direction.
* Tweak - Utilize $product method to get thumbnail in loops.
* Tweak - Check for an existing display name before updating a user on checkout. Adds display_name prop to the CRUD.
* Tweak - Adapt variable product price used in sorting based on direction of sort.
* Tweak - Made state validation less strict for keys.
* Tweak - For COD orders, force payment complete status to be completed.
* Fix - Added log_id as the secondary sorting column to log list so log entries sort correctly.
* Fix - Fix shop page when using shop base and UTF8 shop page slug.
* Fix - Added handles so drag and drop does not break edit on mobile when sorting categories.
@ -193,9 +197,11 @@ Yes you can! Join in on our [GitHub repository](http://github.com/woocommerce/wo
* Fix - Emails sent via admin should switch to global locale.
* Fix - Set and restore wp_query so product page functions think it's a real product page.
* Fix - Variation default value of '0' fails to save on product.
* Fix - Prevent locations being added to the "Rest Of The World" shipping zone via the API.
* Dev - Allow date created to be set in wc_create_refund.
* Dev - Introduced a [WC_Order_Query class](https://github.com/woocommerce/woocommerce/wiki/wc_get_orders-and-WC_Order_Query) for finding/searching orders.
* Dev - Added "restored" webhook.
* Dev - Support floats for the custom attribute name sorting function.
* Dev - Updated Emogrifier to version 1.2.
* Dev - Sort product data tabs by priority in admin screen.
* Dev - Added new hooks for: dashboard reviews widget, product and category sorting events, woocommerce_add_to_cart_sold_individually_found_in_cart, cart empty messages.
@ -203,12 +209,14 @@ Yes you can! Join in on our [GitHub repository](http://github.com/woocommerce/wo
* Dev - Added filter for cookie name.
* Dev - Added ability to filter Photoswipe lightbox options.
* Dev - Added new filter for product thumbnail size.
* Dev - Added action for displaying custom data for fees in admin.
* Dev - Changed build_payload from private to public in webhook system.
* Dev - Added deprecated notice to WC_Order_Item_Meta (deprecated in 3.0).
* Dev - Added namespace to jQuery events that are removed in VariationForm.
* Dev - Made WC_Checkout::get_posted_data() public.
* Dev - Add custom message for custom system status tools.
* Dev - Added filters to change which order items are created and loaded to support custom item types.
* Dev - Updated jQuery payment and serializejson libraries.
* Localization - Added Bolivian states.
* Localization - Use VAT for Norway instead of Tax.

View File

@ -13,7 +13,7 @@
* @see https://docs.woocommerce.com/document/template-structure/
* @author WooThemes
* @package WooCommerce/Templates
* @version 3.0.3
* @version 3.1.0
*/
if ( ! defined( 'ABSPATH' ) ) {
@ -107,7 +107,7 @@ do_action( 'woocommerce_before_cart' ); ?>
$product_quantity = woocommerce_quantity_input( array(
'input_name' => "cart[{$cart_item_key}][qty]",
'input_value' => $cart_item['quantity'],
'max_value' => $_product->backorders_allowed() ? '' : $_product->get_stock_quantity(),
'max_value' => $_product->get_max_purchase_quantity(),
'min_value' => '0',
), $_product, false );
}

View File

@ -15,20 +15,21 @@ class WC_Helper_Customer {
public static function create_mock_customer() {
$customer_data = array(
'country' => 'US',
'state' => 'PA',
'postcode' => '19123',
'city' => 'Philadelphia',
'address' => '123 South Street',
'address_2' => 'Apt 1',
'shipping_country' => 'US',
'shipping_state' => 'PA',
'shipping_postcode' => '19123',
'shipping_city' => 'Philadelphia',
'shipping_address' => '123 South Street',
'shipping_address_2' => 'Apt 1',
'is_vat_exempt' => false,
'calculated_shipping' => false,
'id' => 0,
'country' => 'US',
'state' => 'PA',
'postcode' => '19123',
'city' => 'Philadelphia',
'address' => '123 South Street',
'address_2' => 'Apt 1',
'shipping_country' => 'US',
'shipping_state' => 'PA',
'shipping_postcode' => '19123',
'shipping_city' => 'Philadelphia',
'shipping_address' => '123 South Street',
'shipping_address_2' => 'Apt 1',
'is_vat_exempt' => false,
'calculated_shipping' => false,
);
WC_Helper_Customer::set_customer_details( $customer_data );

View File

@ -112,4 +112,24 @@ class WC_Tests_CRUD_Meta_Data extends WC_Unit_Test_Case {
$this->assertTrue( in_array( 'random_other', wp_list_pluck( $new_order->get_meta_data(), 'key' ) ) );
$this->assertTrue( in_array( 'random_other_pre_crud', wp_list_pluck( $new_order->get_meta_data(), 'key' ) ) );
}
/**
* Tests that the meta data cache gets flushed when update_post_meta updates the object's meta.
* @see https://github.com/woocommerce/woocommerce/issues/15274
*/
function test_get_meta_data_after_update_post_meta() {
// Create an object.
$object = new WC_Product;
$object->save();
// Update a meta value.
update_post_meta( $object->get_id(), '_some_meta_key', 'val1' );
$product = wc_get_product( $object->get_id() );
$this->assertEquals( 'val1', $product->get_meta( '_some_meta_key', true ) );
// Update meta to diff value.
update_post_meta( $object->get_id(), '_some_meta_key', 'val2' );
$product = wc_get_product( $object->get_id() );
$this->assertEquals( 'val2', $product->get_meta( '_some_meta_key', true ) );
}
}

View File

@ -0,0 +1,147 @@
<?php
/**
* Meta
* @package WooCommerce\Tests\Exporter
*/
class WC_Tests_Product_CSV_Exporter extends WC_Unit_Test_Case {
/**
* Load up the exporter classes since they aren't loaded by default.
*/
public function setUp() {
$bootstrap = WC_Unit_Tests_Bootstrap::instance();
require_once $bootstrap->plugin_dir . '/includes/export/class-wc-product-csv-exporter.php';
}
/**
* Test escape_data to prevent regressions that could open security holes.
* @since 3.1.0
*/
public function test_escape_data() {
$exporter = new WC_Product_CSV_Exporter();
$data = "=cmd|' /C calc'!A0";
$this->assertEquals( "'=cmd|' /C calc'!A0", $exporter->escape_data( $data ) );
$data = "+cmd|' /C calc'!A0";
$this->assertEquals( "'+cmd|' /C calc'!A0", $exporter->escape_data( $data ) );
$data = "-cmd|' /C calc'!A0";
$this->assertEquals( "'-cmd|' /C calc'!A0", $exporter->escape_data( $data ) );
$data = "@cmd|' /C calc'!A0";
$this->assertEquals( "'@cmd|' /C calc'!A0", $exporter->escape_data( $data ) );
}
/**
* Test format_data.
* @since 3.1.0
*/
public function test_format_data() {
$exporter = new WC_Product_CSV_Exporter();
$data = "test";
$this->assertEquals( "test", $exporter->format_data( $data ) );
$time = time();
$data = new WC_DateTime( "@{$time}", new DateTimeZone( 'UTC' ) );
$this->assertEquals( date( 'Y-m-d G:i:s', $time ), $exporter->format_data( $data ) );
$data = true;
$this->assertEquals( 1, $exporter->format_data( $data ) );
$data = false;
$this->assertEquals( 0, $exporter->format_data( $data ) );
}
/**
* Test escape_data to prevent regressions that could open security holes.
* @since 3.1.0
*/
public function test_format_term_ids() {
$exporter = new WC_Product_CSV_Exporter();
$term1 = wp_insert_category( array( 'cat_name' => 'cat1' ) );
$term2 = wp_insert_category( array( 'cat_name' => 'cat2' ) );
$term3 = wp_insert_category( array( 'cat_name' => 'cat3' ) );
$expected = "cat1, cat2, cat3";
$this->assertEquals( $expected, $exporter->format_term_ids( array( $term1, $term2, $term3 ), 'category' ) );
wp_insert_category( array( 'cat_ID' => $term2, 'cat_name' => 'cat2', 'category_parent' => $term1 ) );
$expected = "cat1, cat1 > cat2, cat3";
$this->assertEquals( $expected, $exporter->format_term_ids( array( $term1, $term2, $term3 ), 'category' ) );
wp_insert_category( array( 'cat_ID' => $term3, 'cat_name' => 'cat3', 'category_parent' => $term2 ) );
$expected = "cat1, cat1 > cat2, cat1 > cat2 > cat3";
$this->assertEquals( $expected, $exporter->format_term_ids( array( $term1, $term2, $term3 ), 'category' ) );
}
/**
* Test prepare_data_to_export.
* @since 3.1.0
*/
public function test_prepare_data_to_export() {
add_filter( 'woocommerce_product_export_row_data', array( $this, 'verify_exported_data' ), 10, 2 );
$exporter = new WC_Product_CSV_Exporter();
$product = WC_Helper_Product::create_simple_product();
$product->set_description( 'Test description' );
$product->set_short_description( 'Test short description' );
$product->set_weight( 12.5 );
$product->set_height( 10 );
$product->set_length( 20 );
$product->set_width( 1 );
$sale_start = time();
$sale_end = $sale_start + DAY_IN_SECONDS;
$product->set_date_on_sale_from( $sale_start );
$product->set_date_on_sale_to( $sale_end );
$product->save();
WC_Helper_Product::create_external_product();
WC_Helper_Product::create_grouped_product();
WC_Helper_Product::create_variation_product();
$exporter->prepare_data_to_export();
}
/**
* Verify one product for test_perpare_data_to_export.
* @since 3.1.0
*/
public function verify_exported_data( $row, $product ) {
$this->assertEquals( $product->get_id(), $row['id'] );
$this->assertEquals( $product->get_type(), $row['type'] );
$this->assertEquals( $product->get_sku(), $row['sku'] );
$this->assertEquals( $product->get_name(), $row['name'] );
$this->assertEquals( $product->get_short_description(), $row['short_description'] );
$this->assertEquals( $product->get_description(), $row['description'] );
$this->assertEquals( $product->get_tax_status(), $row['tax_status'] );
$this->assertEquals( $product->get_width(), $row['width'] );
$this->assertEquals( $product->get_height(), $row['height'] );
$this->assertEquals( $product->get_length(), $row['length'] );
$this->assertEquals( $product->get_weight(), $row['weight'] );
$this->assertEquals( $product->get_featured(), $row['featured'] );
$this->assertEquals( $product->get_sold_individually(), $row['sold_individually'] );
$this->assertEquals( $product->get_date_on_sale_from(), $row['date_on_sale_from'] );
$this->assertEquals( $product->get_date_on_sale_to(), $row['date_on_sale_to'] );
$this->assertEquals( 'publish' === $product->get_status(), $row['published'] );
$this->assertEquals( 'instock' === $product->get_stock_status(), $row['stock_status'] );
$this->assertContains( $row['catalog_visibility'], array( 'visible', 'catalog', 'search', 'hidden' ) );
$this->assertContains( $row['backorders'], array( 1, 0, 'notify' ) );
$expected_parent = '';
$parent_id = $product->get_parent_id();
if ( $parent_id ) {
$parent = wc_get_product( $parent_id );
$expected_parent = $parent->get_sku() ? $parent->get_sku() : 'id:' . $parent->get_id();
}
$this->assertEquals( $expected_parent, $row['parent_id'] );
return $row;
}
}

View File

@ -0,0 +1,572 @@
<?php
/**
* Meta
* @package WooCommerce\Tests\Importer
*/
class WC_Tests_Product_CSV_Importer extends WC_Unit_Test_Case {
/**
* Test CSV file path.
*
* @var string
*/
protected $csv_file = string;
/**
* Load up the importer classes since they aren't loaded by default.
*/
public function setUp() {
$this->csv_file = dirname( __FILE__ ) . '/sample.csv';
$bootstrap = WC_Unit_Tests_Bootstrap::instance();
require_once $bootstrap->plugin_dir . '/includes/import/class-wc-product-csv-importer.php';
}
/**
* Get CSV mapped items.
*
* @since 3.1.0
* @return array
*/
private function get_csv_mapped_items() {
return array(
'Type' => 'type',
'SKU' => 'sku',
'Name' => 'name',
'Published' => 'published',
'Is featured?' => 'featured',
'Visibility in catalog' => 'catalog_visibility',
'Short description' => 'short_description',
'Description' => 'description',
'Date sale price starts' => 'date_on_sale_from',
'Date sale price ends' => 'date_on_sale_to',
'Tax status' => 'tax_status',
'Tax class' => 'tax_class',
'In stock?' => 'stock_status',
'Stock' => 'stock_quantity',
'Backorders allowed?' => 'backorders',
'Sold individually?' => 'sold_individually',
'Weight (kg)' => 'weight',
'Length (cm)' => 'length',
'Width (cm)' => 'width',
'Height (cm)' => 'height',
'Allow customer reviews?' => 'reviews_allowed',
'Purchase note' => 'purchase_note',
'Sale price' => 'sale_price',
'Regular price' => 'regular_price',
'Categories' => 'category_ids',
'Tags' => 'tag_ids',
'Shipping class' => 'shipping_class_id',
'Images' => 'images',
'Download limit' => 'download_limit',
'Download expiry days' => 'download_expiry',
'Parent' => 'parent_id',
'Upsells' => 'upsell_ids',
'Cross-sells' => 'cross_sell_ids',
'Grouped products' => 'grouped_products',
'External URL' => 'product_url',
'Button text' => 'button_text',
'Attribute 1 name' => 'attributes:name1',
'Attribute 1 value(s)' => 'attributes:value2',
'Attribute 2 name' => 'attributes:name2',
'Attribute 2 value(s)' => 'attributes:value2',
'Attribute 1 default' => 'attributes:default1',
'Attribute 2 default' => 'attributes:default2',
'Download 1 name' => 'downloads:name1',
'Download 1 URL' => 'downloads:url1',
);
}
/**
* Test import.
* @since 3.1.0
*/
public function test_import() {
$args = array(
'mapping' => $this->get_csv_mapped_items(),
'parse' => true,
);
$importer = new WC_Product_CSV_Importer( $this->csv_file, $args );
$results = $importer->import();
$this->assertEquals( 7, count( $results['imported'] ) );
$this->assertEquals( 0, count( $results['failed'] ) );
$this->assertEquals( 0, count( $results['updated'] ) );
$this->assertEquals( 0, count( $results['skipped'] ) );
// Exclude imported products.
foreach ( $results['imported'] as $id ) {
wp_delete_post( $id, true );
}
}
/**
* Test get_raw_keys.
* @since 3.1.0
*/
public function test_get_raw_keys() {
$importer = new WC_Product_CSV_Importer( $this->csv_file, array( 'lines' => 1 ) );
$raw_keys = array_keys( $this->get_csv_mapped_items() );
$this->assertEquals( $raw_keys, $importer->get_raw_keys() );
}
/**
* Test get_mapped_keys.
* @since 3.1.0
*/
public function test_get_mapped_keys() {
$args = array(
'mapping' => $this->get_csv_mapped_items(),
'lines' => 1,
);
$importer = new WC_Product_CSV_Importer( $this->csv_file, $args );
$this->assertEquals( array_values( $args['mapping'] ), $importer->get_mapped_keys() );
}
/**
* Test get_raw_data.
* @since 3.1.0
*/
public function test_get_raw_data() {
$importer = new WC_Product_CSV_Importer( $this->csv_file, array( 'parse' => false, 'lines' => 2 ) );
$items = array(
array(
'simple',
'WOOLOGO',
'Woo Logo',
'1',
'',
'visible',
'Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an.',
'Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an.',
'2017-01-01',
'2030-01-01',
'taxable',
'standard',
'1',
'5',
'notify',
'1',
'1',
'1',
'20',
'40',
'1',
'Lorem ipsum dolor sit amet.',
'18',
'20',
'Clothing, Clothing > T-shirts',
'',
'',
'http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_1_front.jpg, http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_1_back.jpg',
'',
'',
'',
'WOOALBUM',
'WOOALBUM',
'',
'',
'',
'Color',
'Red',
'',
'',
'',
'',
'',
'',
),
array(
'simple, downloadable, virtual',
'WOOALBUM',
'Woo Album #1',
'1',
'1',
'visible',
'Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an.',
'Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an.',
'',
'',
'taxable',
'standard',
'1',
'',
'',
'',
'',
'',
'',
'',
'1',
'Lorem ipsum dolor sit amet.',
'',
'5',
'Music > Albums, Music',
'Woo',
'',
'http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/cd_1_angle.jpg, http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/cd_1_flat.jpg',
'10',
'90',
'',
'WOOLOGO',
'WOOLOGO',
'',
'',
'',
'Label',
'WooCommerce',
'Vinyl',
'180-Gram',
'',
'',
'Album flac',
'http://woo.dev/albums/album.flac',
),
);
$this->assertEquals( $items, $importer->get_raw_data() );
}
/**
* Test get_parsed_data.
* @since 3.1.0
*/
public function test_get_parsed_data() {
$args = array(
'mapping' => $this->get_csv_mapped_items(),
'parse' => true
);
$importer = new WC_Product_CSV_Importer( $this->csv_file, $args );
$items = array(
array(
'type' => 'simple',
'sku' => 'WOOLOGO',
'name' => 'Woo Logo',
'featured' => '',
'catalog_visibility' => 'visible',
'short_description' => 'Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an.',
'description' => 'Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an.',
'date_on_sale_from' => '2017-01-01',
'date_on_sale_to' => '2030-01-01',
'tax_status' => 'taxable',
'tax_class' => 'standard',
'stock_status' => 'instock',
'stock_quantity' => 5,
'backorders' => 'notify',
'sold_individually' => true,
'weight' => 1.0,
'length' => 1.0,
'width' => 20.0,
'height' => 40.0,
'reviews_allowed' => true,
'purchase_note' => 'Lorem ipsum dolor sit amet.',
'sale_price' => '18',
'regular_price' => '20',
'shipping_class_id' => 0,
'download_limit' => 0,
'download_expiry' => 0,
'product_url' => '',
'button_text' => '',
'status' => 'publish',
'raw_image_id' => 'http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_1_front.jpg',
'raw_gallery_image_ids' => array( 'http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_1_back.jpg' ),
'virtual' => '',
'downloadable' => '',
'manage_stock' => true,
'virtual' => false,
'downloadable' => false,
'raw_attributes' => array(
array(
'name' => 'Color',
),
),
),
array(
'type' => 'simple',
'sku' => 'WOOALBUM',
'name' => 'Woo Album #1',
'featured' => true,
'catalog_visibility' => 'visible',
'short_description' => 'Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an.',
'description' => 'Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an.',
'date_on_sale_from' => null,
'date_on_sale_to' => null,
'tax_status' => 'taxable',
'tax_class' => 'standard',
'stock_status' => 'instock',
'stock_quantity' => 0,
'backorders' => '',
'sold_individually' => '',
'weight' => '',
'length' => '',
'width' => '',
'height' => '',
'reviews_allowed' => true,
'purchase_note' => 'Lorem ipsum dolor sit amet.',
'sale_price' => '',
'regular_price' => '5',
'shipping_class_id' => 0,
'download_limit' => 10,
'download_expiry' => 90,
'product_url' => '',
'button_text' => '',
'status' => 'publish',
'raw_image_id' => 'http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/cd_1_angle.jpg',
'raw_gallery_image_ids' => array( 'http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/cd_1_flat.jpg' ),
'virtual' => true,
'downloadable' => true,
'manage_stock' => false,
'raw_attributes' => array(
array(
'name' => 'Label',
),
array(
'value' => array( '180-Gram' ),
'name' => 'Vinyl',
),
),
'downloads' => array(
array(
'name' => 'Album flac',
'file' => 'http://woo.dev/albums/album.flac',
),
),
),
array(
'type' => 'external',
'sku' => '',
'name' => 'WooCommerce Product CSV Suite',
'featured' => '',
'catalog_visibility' => 'visible',
'short_description' => 'Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an.',
'description' => 'Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an.',
'date_on_sale_from' => null,
'date_on_sale_to' => null,
'tax_status' => 'taxable',
'tax_class' => 'standard',
'stock_status' => 'instock',
'stock_quantity' => 0,
'backorders' => '',
'sold_individually' => '',
'weight' => '',
'length' => '',
'width' => '',
'height' => '',
'reviews_allowed' => false,
'purchase_note' => 'Lorem ipsum dolor sit amet.',
'sale_price' => '',
'regular_price' => '199',
'shipping_class_id' => 0,
'download_limit' => 0,
'download_expiry' => 0,
'product_url' => 'https://woocommerce.com/products/product-csv-import-suite/',
'button_text' => 'Buy on WooCommerce.com',
'status' => 'publish',
'raw_image_id' => null,
'virtual' => false,
'downloadable' => false,
'manage_stock' => false,
),
array(
'type' => 'variable',
'sku' => 'WOOIDEA',
'name' => 'Ship Your Idea',
'featured' => '',
'catalog_visibility' => 'visible',
'short_description' => 'Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an.',
'description' => 'Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an.',
'date_on_sale_from' => null,
'date_on_sale_to' => null,
'tax_status' => '',
'tax_class' => '',
'stock_status' => 'outofstock',
'stock_quantity' => 0,
'backorders' => '',
'sold_individually' => '',
'weight' => '',
'length' => '',
'width' => '',
'height' => '',
'reviews_allowed' => true,
'purchase_note' => 'Lorem ipsum dolor sit amet.',
'sale_price' => '',
'regular_price' => '',
'shipping_class_id' => 0,
'download_limit' => 0,
'download_expiry' => 0,
'product_url' => '',
'button_text' => '',
'status' => 'publish',
'raw_image_id' => 'http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_4_front.jpg',
'raw_gallery_image_ids' => array(
'http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_4_back.jpg',
'http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_3_front.jpg',
'http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_3_back.jpg',
),
'virtual' => false,
'downloadable' => false,
'manage_stock' => false,
'raw_attributes' => array(
array(
'name' => 'Color',
'default' => 'Green',
),
array(
'value' => array( 'M', 'L' ),
'name' => 'Size',
'default' => 'L'
),
),
),
array(
'type' => 'variation',
'sku' => '',
'name' => '',
'featured' => '',
'catalog_visibility' => 'visible',
'short_description' => '',
'description' => 'Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an.',
'date_on_sale_from' => null,
'date_on_sale_to' => null,
'tax_status' => 'taxable',
'tax_class' => 'standard',
'stock_status' => 'instock',
'stock_quantity' => 6,
'backorders' => '',
'sold_individually' => '',
'weight' => 1.0,
'length' => 2.0,
'width' => 25.0,
'height' => 55.0,
'reviews_allowed' => '',
'purchase_note' => '',
'sale_price' => '',
'regular_price' => '20',
'shipping_class_id' => 0,
'download_limit' => 0,
'download_expiry' => 0,
'product_url' => '',
'button_text' => '',
'status' => 'publish',
'raw_image_id' => 'http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_4_front.jpg',
'virtual' => false,
'downloadable' => false,
'manage_stock' => true,
'raw_attributes' => array(
array(
'name' => 'Color',
),
array(
'value' => array( 'M' ),
'name' => 'Size',
),
),
),
array(
'type' => 'variation',
'sku' => '',
'name' => '',
'featured' => '',
'catalog_visibility' => 'visible',
'short_description' => '',
'description' => 'Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an.',
'date_on_sale_from' => null,
'date_on_sale_to' => null,
'tax_status' => 'taxable',
'tax_class' => 'standard',
'stock_status' => 'instock',
'stock_quantity' => 10,
'backorders' => 'yes',
'sold_individually' => '',
'weight' => 1.0,
'length' => 2.0,
'width' => 25.0,
'height' => 55.0,
'reviews_allowed' => '',
'purchase_note' => '',
'sale_price' => '17.99',
'regular_price' => '20',
'shipping_class_id' => 0,
'download_limit' => 0,
'download_expiry' => 0,
'product_url' => '',
'button_text' => '',
'status' => 'publish',
'raw_image_id' => 'http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_3_front.jpg',
'virtual' => false,
'downloadable' => false,
'manage_stock' => true,
'raw_attributes' => array(
array(
'name' => 'Color',
),
array(
'value' => array( 'L' ),
'name' => 'Size'
)
),
),
array(
'type' => 'grouped',
'sku' => '',
'name' => 'Best Woo Products',
'featured' => true,
'catalog_visibility' => 'visible',
'short_description' => 'Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an.',
'description' => 'Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an.',
'date_on_sale_from' => null,
'date_on_sale_to' => null,
'tax_status' => '',
'tax_class' => '',
'stock_status' => 'instock',
'stock_quantity' => 0,
'backorders' => '',
'sold_individually' => '',
'weight' => '',
'length' => '',
'width' => '',
'height' => '',
'reviews_allowed' => '',
'purchase_note' => '',
'sale_price' => '',
'regular_price' => '',
'shipping_class_id' => 0,
'download_limit' => 0,
'download_expiry' => 0,
'product_url' => '',
'button_text' => '',
'status' => 'publish',
'raw_image_id' => 'http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_1_front.jpg',
'raw_gallery_image_ids' => array( 'http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/cd_1_angle.jpg' ),
'virtual' => false,
'downloadable' => false,
'manage_stock' => false,
),
);
$parsed_data = $importer->get_parsed_data();
// Remove fields that depends on product ID or term ID.
foreach ( $parsed_data as &$data ) {
unset( $data['parent_id'], $data['upsell_ids'], $data['cross_sell_ids'], $data['children'], $data['category_ids'], $data['tag_ids'] );
}
$this->assertEquals( $items, $parsed_data );
// Remove temporary products.
$temp_products = get_posts( array(
'post_status' => 'importing',
'post_type' => 'product',
'fields' => 'ids',
) );
foreach ( $temp_products as $id ) {
wp_delete_post( $id, true );
}
}
}

View File

@ -0,0 +1,8 @@
Type,SKU,Name,Published,Is featured?,Visibility in catalog,Short description,Description,Date sale price starts,Date sale price ends,Tax status,Tax class,In stock?,Stock,Backorders allowed?,Sold individually?,Weight (kg),Length (cm),Width (cm),Height (cm),Allow customer reviews?,Purchase note,Sale price,Regular price,Categories,Tags,Shipping class,Images,Download limit,Download expiry days,Parent,Upsells,Cross-sells,Grouped products,External URL,Button text,Attribute 1 name,Attribute 1 value(s),Attribute 2 name,Attribute 2 value(s),Attribute 1 default,Attribute 2 default,Download 1 name,Download 1 URL
simple,WOOLOGO,Woo Logo,1,,visible,"Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an.","Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an.",2017-01-01,2030-01-01,taxable,standard,1,5,notify,1,1,1,20,40,1,Lorem ipsum dolor sit amet.,18,20,"Clothing, Clothing > T-shirts",,,"http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_1_front.jpg, http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_1_back.jpg",,,,WOOALBUM,WOOALBUM,,,,Color,Red,,,,,,
"simple, downloadable, virtual",WOOALBUM,Woo Album #1,1,1,visible,"Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an.","Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an.",,,taxable,standard,1,,,,,,,,1,Lorem ipsum dolor sit amet.,,5,"Music > Albums, Music",Woo,,"http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/cd_1_angle.jpg, http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/cd_1_flat.jpg",10,90,,WOOLOGO,WOOLOGO,,,,Label,WooCommerce,Vinyl,180-Gram,,,Album flac,http://woo.dev/albums/album.flac
external,,WooCommerce Product CSV Suite,1,,visible,"Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an.","Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an.",,,taxable,standard,1,,,,,,,,0,Lorem ipsum dolor sit amet.,,199,Software,WooCommerce,,,,,,,,,https://woocommerce.com/products/product-csv-import-suite/,Buy on WooCommerce.com,,,,,,,,
variable,WOOIDEA,Ship Your Idea,1,,visible,"Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an.","Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an.",,,,,,,,,,,,,1,Lorem ipsum dolor sit amet.,,,"Clothing, Clothing > T-shirts",,,"http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_4_front.jpg, http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_4_back.jpg, http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_3_front.jpg, http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_3_back.jpg",,,,,,,,,Color,"Black, Green",Size,"M, L",Green,L,,
variation,,,1,,visible,,"Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an.",,,taxable,standard,1,6,0,,1,2,25,55,,,,20,,,,http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_4_front.jpg,,,WOOIDEA,,,,,,Color,Black,Size,M,,,,
variation,,,1,,visible,,"Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an.",,,taxable,standard,1,10,1,,1,2,25,55,,,17.99,20,,,,http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_3_front.jpg,,,WOOIDEA,,,,,,Color,Green,Size,L,,,,
grouped,,Best Woo Products,1,1,visible,"Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an.","Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an.",,,,,1,,,,,,,,,,,,"Clothing, Clothing > T-shirts, Music > Albums, Music",,,"http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_1_front.jpg, http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/cd_1_angle.jpg",,,,,,"WOOLOGO, WOOALBUM",,,,,,,,,,
1 Type SKU Name Published Is featured? Visibility in catalog Short description Description Date sale price starts Date sale price ends Tax status Tax class In stock? Stock Backorders allowed? Sold individually? Weight (kg) Length (cm) Width (cm) Height (cm) Allow customer reviews? Purchase note Sale price Regular price Categories Tags Shipping class Images Download limit Download expiry days Parent Upsells Cross-sells Grouped products External URL Button text Attribute 1 name Attribute 1 value(s) Attribute 2 name Attribute 2 value(s) Attribute 1 default Attribute 2 default Download 1 name Download 1 URL
2 simple WOOLOGO Woo Logo 1 visible Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an. Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an. 2017-01-01 2030-01-01 taxable standard 1 5 notify 1 1 1 20 40 1 Lorem ipsum dolor sit amet. 18 20 Clothing, Clothing > T-shirts http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_1_front.jpg, http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_1_back.jpg WOOALBUM WOOALBUM Color Red
3 simple, downloadable, virtual WOOALBUM Woo Album #1 1 1 visible Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an. Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an. taxable standard 1 1 Lorem ipsum dolor sit amet. 5 Music > Albums, Music Woo http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/cd_1_angle.jpg, http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/cd_1_flat.jpg 10 90 WOOLOGO WOOLOGO Label WooCommerce Vinyl 180-Gram Album flac http://woo.dev/albums/album.flac
4 external WooCommerce Product CSV Suite 1 visible Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an. Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an. taxable standard 1 0 Lorem ipsum dolor sit amet. 199 Software WooCommerce https://woocommerce.com/products/product-csv-import-suite/ Buy on WooCommerce.com
5 variable WOOIDEA Ship Your Idea 1 visible Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an. Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an. 1 Lorem ipsum dolor sit amet. Clothing, Clothing > T-shirts http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_4_front.jpg, http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_4_back.jpg, http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_3_front.jpg, http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_3_back.jpg Color Black, Green Size M, L Green L
6 variation 1 visible Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an. taxable standard 1 6 0 1 2 25 55 20 http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_4_front.jpg WOOIDEA Color Black Size M
7 variation 1 visible Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an. taxable standard 1 10 1 1 2 25 55 17.99 20 http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_3_front.jpg WOOIDEA Color Green Size L
8 grouped Best Woo Products 1 1 visible Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an. Lorem ipsum dolor sit amet, at exerci civibus appetere sit, iuvaret hendrerit mea no. Eam integre feugait liberavisse an. 1 Clothing, Clothing > T-shirts, Music > Albums, Music http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/T_1_front.jpg, http://demo.woothemes.com/woocommerce/wp-content/uploads/sites/56/2013/06/cd_1_angle.jpg WOOLOGO, WOOALBUM

View File

@ -479,10 +479,10 @@ class WC_Tests_CRUD_Orders extends WC_Unit_Test_Case {
'product' => WC_Helper_Product::create_simple_product(),
'quantity' => 4,
) );
$item->save();
$object->add_item( $item->get_id() );
$object->add_item( $item );
$object->save();
$this->assertTrue( $object->get_item( $item->get_id() ) instanceOf WC_Order_Item_Product );
$this->assertEquals( spl_object_hash( $item ), spl_object_hash( $object->get_item( $item->get_id() ) ) );
$object = new WC_Order();
$item = new WC_Order_Item_Coupon();
@ -495,6 +495,51 @@ class WC_Tests_CRUD_Orders extends WC_Unit_Test_Case {
$object->add_item( $item );
$object->save();
$this->assertTrue( $object->get_item( $item_id ) instanceOf WC_Order_Item_Coupon );
$object = new WC_Order( $object->get_id() );
$this->assertTrue( $object->get_item( $item_id ) instanceOf WC_Order_Item_Coupon );
}
/**
* Make sure that items returned by get_item is tied to the order,
* and that is saved when the order is saved.
*/
public function test_get_item_object_is_updated_with_order_save() {
$object = new WC_Order();
$item = new WC_Order_Item_Product();
$item->set_props( array(
'product' => WC_Helper_Product::create_simple_product(),
'quantity' => 4,
) );
$object->add_item( $item );
$object->save();
$object = new WC_Order( $object->get_id() );
$item = $object->get_item( $item->get_id() );
$item->set_quantity( 6 );
$object->save();
$object = new WC_Order( $object->get_id() );
$this->assertEquals(6, $object->get_item( $item->get_id() )->get_quantity() );
}
/**
* Makes sure that get_item only returns items related to the order
*/
public function test_get_item_from_another_order() {
$object = new WC_Order();
$item = new WC_Order_Item_Product();
$item->set_props( array(
'product' => WC_Helper_Product::create_simple_product(),
'quantity' => 4,
'order_id' => 999,
) );
$item->save();
$this->assertFalse( $object->get_item( $item->get_id() ) );
$object->save();
$this->assertFalse( $object->get_item( $item->get_id() ) );
}
/**