Commit 986f4923 by Pham Huy

add woocommerce

parent 2e326036

Too many changes to show.

To preserve performance only 1000 of 1000+ files are displayed.

# Code of Conduct
Facebook has adopted a Code of Conduct that we expect project participants to adhere to.
Please read the [full text](https://code.fb.com/codeofconduct/)
so that you can understand what actions will and will not be tolerated.
# Contributing to Facebook for WooCommerce
We want to make contributing to this project as easy and transparent as
possible.
## Pull Requests
We actively welcome your pull requests.
1. Fork the repo and create your branch from `master`.
2. If you've added code that should be tested, add tests.
3. If you've changed APIs, update the documentation.
4. Make sure your code lints.
5. If you haven't already, complete the Contributor License Agreement ("CLA").
## Contributor License Agreement ("CLA")
In order to accept your pull request, we need you to submit a CLA. You only need
to do this once to work on any of Facebook's open source projects.
Complete your CLA here: <https://code.facebook.com/cla>
## Issues
We use GitHub issues to track public bugs. Please ensure your description is
clear and has sufficient instructions to be able to reproduce the issue.
Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe
disclosure of security bugs. In those cases, please go through the process
outlined on that page and do not file a public issue.
## Coding Style
* 4 spaces for indentation rather than tabs
* 80 character line length
## License
By contributing to Facebook for WooCommerce, you agree that your contributions
will be licensed under the LICENSE file in the root directory of
this source tree.
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
*
* This source code is licensed under the license found in the
* LICENSE file in the root directory of this source tree.
*
* @package FacebookCommerce
*/
#fbinfobanner .btn {
/* container: */
background-color: rgb(86,120,227);
border: 1px solid;
border-color: #435a8b #3c5488 #334c83;
border-radius: 2px;
padding: 5px 10px 5px 10px;
/* Get started: */
font-family: helvetica, arial, sans-serif;
font-size: 12px;
font-weight: 600;
color: #FFFFFF;
text-align: center;
text-decoration: none;
display: inline-block;
margin: 5px 10px 1px 1px;
}
#fbinfobanner .btn.dismiss {
margin: 5px 10px 5px 1px;
}
#fbinfobanner .btn.grey{
background: #f6f7f8;
border-color: #e0e1e2;
color: #4e5665;
}
#fbinfobanner .iconDetails {
margin: 5px 10px 5px 3px;
float:left;
height:50px;
width:50px;
}
#fbinfobanner .tipTitle {
padding: 1px 12px 1px 20px;
font-size: 14px;
}
#fbinfobanner .tipContent {
padding: 1px 12px 1px 20px;
font-size: 14px;
}
#fbinfobanner .tipButton {
margin-bottom: 0px;
}
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
*
* This source code is licensed under the license found in the
* LICENSE file in the root directory of this source tree.
*
* @package FacebookCommerce
*/
#fbsetup .wrapper {
box-shadow: inset 0 0 0 1px rgba(0,0,0,0.2);
display: block;
width: 600px;
background: white url(../image/small-store.png) no-repeat;
background-position-x: 92%;
background-position-y: 172px;
background-size: 180px;
}
#fbsetup .wrapper .help-center {
float: right;
margin-top: -23px;
}
#fbsetup .wrapper .help-center a {
color: white;
}
#fbsetup .wrapper .help-center-icon {
display: inline-block;
background-image: url(../image/help-center.png);
background-size: 38px 38px;
background-repeat: no-repeat;
width: 38px;
height: 38px;
zoom: 0.7;
top: 12px;
position: relative;
}
#fbsetup .content {
padding: 10px 30px 20px 30px;
}
#fbsetup .loader {
border: 4px solid #f3f3f3; /* Light grey */
border-top: 4px solid #3498db; /* Blue */
border-radius: 50%;
width: 20px;
height: 20px;
animation: spin 2s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
#fbsetup .btn {
/* container: */
background-color: rgb(86,120,227);
border: 1px solid;
border-color: #435a8b #3c5488 #334c83;
border-radius: 2px;
padding: 10px 20px;
/* Get started: */
font-family: helvetica, arial, sans-serif;
font-size: 12px;
font-weight: 550;
color: #FFFFFF;
text-align: center;
text-decoration: none;
display: inline-block;
letter-spacing: 0.2px;
margin: 0px 12px 10px 0px;
width: 60px;
}
#fbsetup .btn.grey{
background: #f6f7f8;
border-color: #e0e1e2;
color: #4e5665;
width: 75px;
}
#fbsetup .btn.pre-setup{
font-size: 13px;
width: 80px;
}
#fbsetup header {
display: block;
background: url(../image/facebook.png) no-repeat #4267B2;
background-position: 20px center;
background-size: 100px 19px;
padding: 20px;
}
#fbsetup h1 {
/* title: */
font-family: arial;
font-size: 18px;
font-weight: bold;
color: #141823;
margin-top: 12px;
width: 400px;
}
#fbsetup p {
/* copy: */
font-family: Helvetica;
font-size: 12px;
color: #1D2129;
margin: 10px 0px 14px 0px;
}
#fbsetup h2 {
color: rgb(0,0,0);
font-size: 14px;
font-weight: normal;
line-height: 18px;
margin-bottom: 6px;
margin-top: 12px;
width: 520px;
}
#fbsetup ul {
list-style-type: disc;
margin: 10px 10px 15px 19px;
}
#fbsetup li {
margin: 2px 0px;
padding-left: 8px;
}
#fbsetup .nux-message {
box-sizing: border-box;
color: #fff;
overflow: hidden;
padding: 9px;
position: absolute;
text-align: left;
transform: translateZ(0);
transition: all 200ms cubic-bezier(.08,.52,.52,1);
z-index: 5;
}
#fbsetup .nux-message .nux-message-text {
background: #3578e5;
border-radius: 3px;
box-shadow: 0 1px 3px rgba(0, 0, 0, .3);
box-sizing: border-box;
padding: 10px 34px 12px 10px;
white-space: normal;
min-width: 252px;
}
#fbsetup .nux-message .nux-message-close-btn {
cursor: pointer;
position: absolute;
right: 19px;
top: 19px;
height: 12px;
width: 12px;
font-style: normal;
color: #ccc;
}
#fbsetup .nux-message .nux-message-close-btn:hover {
color: #fff;
}
#fbsetup .nux-message .nux-message-arrow {
border-right: 9px solid #3578e5;
left: 0;
position: absolute;
border-bottom: 9px solid transparent;
border-top: 9px solid transparent;
margin-top: -9px;
top: 50%;
}
#fbsetup .settings-section {
display: inline-block;
min-width: 45%;
max-width: 45%;
margin-right: 8px;
vertical-align: top;
}
#fbsetup .settings-section h1 {
color: rgb(126,129,136);
font-size: 14px;
margin-top: 0px;
width: auto;
}
#fbsetup .settings-section h2 {
color: rgb(126,129,136);
font-size: 14px;
margin-top: 2px;
width: auto;
}
#fbsetup .settings-section .btn.small {
background: #f6f7f8;
border-color: #e0e1e2;
color: #4e5665;
height: 20px;
margin-top: 10px;
padding: 2px 6px;
width: auto;
}
.tooltip {
position: relative;
border-bottom: 1px dotted black;
/* container: */
border: 1px solid;
border-radius: 2px;
/* Launch Test: */
font-size: 12px;
font-weight: 550;
color: #FFFFFF;
text-align: center;
text-decoration: none;
display: inline-block;
letter-spacing: 0.2px;
margin: 0px 12px 10px 0px;
background: #f6f7f8;
border-color: #e0e1e2;
color: #4e5665;
height: 20px;
margin-top: 10px;
padding: 2px 6px;
width: auto;
}
.tooltip .tooltiptext {
color: black;
font-size: 13px;
padding: 5px 0;
text-align: justify;
visibility: hidden;
width: 400px;
/* Position the tooltip */
position: absolute;
z-index: 1;
top: 100%;
left: 50%;
margin-left: -60px;
}
.tooltip:hover .tooltiptext {
visibility: visible;
opacity: 1;
}
#fbsetup a {
text-decoration: none;
}
#fbAdvancedOptionsText {
color: rgb(69, 114, 169);
font-size: 14px;
font-family: Helvetica;
padding: 8px 0px 8px 0px;
}
#fbAdvancedOptions {
color: rgb(126,129,136);
font-size: 12px;
font-family: Helvetica;
display: none;
padding: 8px 4px 8px 4px;
}
#fbAdvancedOptions .autosyncSavedNotice {
color: green;
font-size: 10px;
display: none;
}
#fbAdvancedOptions div {
padding: 4px 4px 4px 4px;
}
.autosyncTime {
padding: 0px;
font-family: Helvetica;
font-size: 12px;
height: 18px;
}
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
*
* This source code is licensed under the license found in the
* LICENSE file in the root directory of this source tree.
*
* @package FacebookCommerce
*/
/*
* Ajax helper function.
* Takes optional payload for POST and optional callback.
*/
function ajax(action, payload = null, callback = null, failcallback = null) {
var data = Object.assign( {}, {
'action': action,
}, payload );
// Since Wordpress 2.8 ajaxurl is always defined in admin header and
// points to admin-ajax.php
jQuery.post(
ajaxurl,
data,
function(response) {
if (callback) {
callback( response );
}
}
).fail(
function(errorResponse){
if (failcallback) {
failcallback( errorResponse );
}
}
);
}
function fb_woo_infobanner_post_click(){
console.log( "Woo infobanner post tip click!" );
return ajax(
'ajax_woo_infobanner_post_click',
{
"_ajax_nonce": wc_facebook_infobanner_jsx.nonce
},
);
}
function fb_woo_infobanner_post_xout() {
console.log( "Woo infobanner post tip xout!" );
return ajax(
'ajax_woo_infobanner_post_xout',
{
"_ajax_nonce": wc_facebook_infobanner_jsx.nonce
},
);
}
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
*
* This source code is licensed under the license found in the
* LICENSE file in the root directory of this source tree.
*
* @package FacebookCommerce
*/
/*
* Ajax helper function.
* Takes optional payload for POST and optional callback.
*/
function ajax(action, payload = null, cb = null, failcb = null) {
var data = Object.assign( {}, {
'action': action,
}, payload);
// Since Wordpress 2.8 ajaxurl is always defined in admin header and
// points to admin-ajax.php
jQuery.post(
ajaxurl,
data,
function(response) {
if (cb) {
cb( response );
}
}
).fail(
function(errorResponse){
if (failcb) {
failcb( errorResponse );
}
}
);
}
function fb_reset_product(wp_id) {
if (confirm(
'Resetting Facebook metadata will not remove this product from your shop. ' +
'If you have duplicated another product and are trying to publish a new Facebook product, ' +
'click OK to proceed. ' +
'Otherwise, Facebook metadata will be restored when this product is updated again.'
)) {
var metadata = document.querySelector( '#fb_metadata' );
if (metadata) {
metadata.innerHTML =
"<b>This product is not yet synced to Facebook.</b>";
}
return ajax(
'ajax_reset_single_fb_product',
{
'wp_id': wp_id,
"_ajax_nonce": wc_facebook_metabox_jsx.nonce
}
);
}
}
function fb_delete_product(wp_id) {
if (confirm(
'Are you sure you want to delete this product on Facebook? If you only want to "hide" the product, ' +
'uncheck the "Visible" checkbox and hit "Update". If you delete a product on Facebook and hit "Update" after, ' +
'this product will be recreated. To permanently remove this product from Facebook, hit "OK" and close the window.' +
'This will not delete the product from WooCommerce.'
)) {
var metadata = document.querySelector( '#fb_metadata' );
if (metadata) {
metadata.innerHTML =
"<b>This product is not yet synced to Facebook.</b>";
}
return ajax(
'ajax_delete_fb_product',
{
'wp_id': wp_id,
"_ajax_nonce": wc_facebook_metabox_jsx.nonce,
}
);
}
}
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
*
* This source code is licensed under the license found in the
* LICENSE file in the root directory of this source tree.
*
* @package FacebookCommerce
*/
/*
* Ajax helper function.
* Takes optional payload for POST and optional callback.
*/
function ajax(action, payload = null, cb = null, failcb = null) {
var data = Object.assign( {},
{
'action': action,
}, payload
);
// Since Wordpress 2.8 ajaxurl is always defined in admin header and
// points to admin-ajax.php
jQuery.post(
ajaxurl,
data,
function(response) {
if (cb) {
cb( response );
}
}
).fail(
function(errorResponse){
if (failcb) {
failcb( errorResponse );
}
}
);
}
function fb_toggle_visibility(wp_id, published) {
var buttonId = document.querySelector( "#viz_" + wp_id );
var tooltip = document.querySelector( "#tip_" + wp_id );
if (published) {
tooltip.setAttribute(
'data-tip',
'Product is synced and published (visible) on Facebook.'
);
buttonId.setAttribute( 'onclick','fb_toggle_visibility(' + wp_id + ', false)' );
buttonId.innerHTML = 'Hide';
buttonId.setAttribute( 'class', 'button' );
} else {
tooltip.setAttribute(
'data-tip',
'Product is synced but not marked as published (visible) on Facebook.'
);
buttonId.setAttribute( 'onclick','fb_toggle_visibility(' + wp_id + ', true)' );
buttonId.innerHTML = 'Show';
buttonId.setAttribute( 'class', 'button button-primary button-large' );
}
// Reset tooltip
jQuery(
function($) {
$( '.tips' ).tipTip(
{
'attribute': 'data-tip',
'fadeIn': 50,
'fadeOut': 50,
'delay': 200
}
);
}
);
return ajax(
'ajax_fb_toggle_visibility',
{
'wp_id': wp_id,
'published': published,
"_ajax_nonce": wc_facebook_product_jsx.nonce
}
);
}
{
"name": "facebookincubator/facebook-for-woocommerce",
"description": "Grow your business with Facebook for WooCommerce! This plugin will install a Facebook Pixel and optionally create a shop on your Facebook page.",
"type": "wordpress-plugin",
"license": "GPL-2.0+",
"require": {
"composer/installers": "~1.0",
"php": ">=5.6"
},
"repositories": [
{
"type": "git",
"url" : "git@github.com:facebookincubator/facebook-for-woocommerce.git"
}
]
}
<?php
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
*
* This source code is licensed under the license found in the
* LICENSE file in the root directory of this source tree.
*
* @package FacebookCommerce
*/
if ( ! class_exists( 'WC_Facebookcommerce_MessengerChat' ) ) :
if ( ! class_exists( 'WC_Facebookcommerce_Utils' ) ) {
include_once 'includes/fbutils.php';
}
class WC_Facebookcommerce_MessengerChat {
public function __construct( $settings ) {
$this->enabled = isset( $settings['is_messenger_chat_plugin_enabled'] )
? $settings['is_messenger_chat_plugin_enabled']
: 'no';
$this->page_id = isset( $settings['fb_page_id'] )
? $settings['fb_page_id']
: '';
$this->jssdk_version = isset( $settings['facebook_jssdk_version'] )
? $settings['facebook_jssdk_version']
: '';
$this->greeting_text_code = isset( $settings['msger_chat_customization_greeting_text_code'] )
? $settings['msger_chat_customization_greeting_text_code']
: null;
$this->locale = isset( $settings['msger_chat_customization_locale'] )
? $settings['msger_chat_customization_locale']
: null;
$this->theme_color_code = isset( $settings['msger_chat_customization_theme_color_code'] )
? $settings['msger_chat_customization_theme_color_code']
: null;
add_action( 'wp_footer', array( $this, 'inject_messenger_chat_plugin' ) );
}
public function inject_messenger_chat_plugin() {
if ( $this->enabled === 'yes' ) {
echo sprintf(
"<div
attribution=\"fbe_woocommerce\"
class=\"fb-customerchat\"
page_id=\"%s\"
%s
%s
%s /></div>
<!-- Facebook JSSDK -->
<script>
window.fbAsyncInit = function() {
FB.init({
appId : '',
autoLogAppEvents : true,
xfbml : true,
version : '%s'
});
};
(function(d, s, id){
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) {return;}
js = d.createElement(s); js.id = id;
js.src = 'https://connect.facebook.net/%s/sdk/xfbml.customerchat.js';
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));
</script>
<div></div>",
$this->page_id,
$this->theme_color_code ? sprintf( 'theme_color="%s"', $this->theme_color_code ) : '',
$this->greeting_text_code ? sprintf( 'logged_in_greeting="%s"', $this->greeting_text_code ) : '',
$this->greeting_text_code ? sprintf( 'logged_out_greeting="%s"', $this->greeting_text_code ) : '',
$this->jssdk_version,
$this->locale ? $this->locale : 'en_US'
);
}
}
}
endif;
<?php
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
*
* This source code is licensed under the license found in the
* LICENSE file in the root directory of this source tree.
*
* @package FacebookCommerce
*/
if ( ! class_exists( 'WC_Facebookcommerce_Pixel' ) ) :
class WC_Facebookcommerce_Pixel {
const SETTINGS_KEY = 'facebook_config';
const PIXEL_ID_KEY = 'pixel_id';
const USE_PII_KEY = 'use_pii';
const PIXEL_RENDER = 'pixel_render';
const NO_SCRIPT_RENDER = 'no_script_render';
private $user_info;
private $last_event;
static $render_cache = array();
static $default_pixel_basecode = "
<script type='text/javascript'>
!function(f,b,e,v,n,t,s){if(f.fbq)return;n=f.fbq=function(){n.callMethod?
n.callMethod.apply(n,arguments):n.queue.push(arguments)};if(!f._fbq)f._fbq=n;
n.push=n;n.loaded=!0;n.version='2.0';n.queue=[];t=b.createElement(e);t.async=!0;
t.src=v;s=b.getElementsByTagName(e)[0];s.parentNode.insertBefore(t,s)}(window,
document,'script','https://connect.facebook.net/en_US/fbevents.js');
</script>
";
public function __construct( $user_info = array() ) {
$this->user_info = $user_info;
$this->last_event = '';
}
public static function initialize() {
if ( ! is_admin() ) {
return;
}
// Initialize PixelID in storage - this will only need to happen when the
// user is an admin
$pixel_id = self::get_pixel_id();
if ( ! WC_Facebookcommerce_Utils::is_valid_id( $pixel_id ) &&
class_exists( 'WC_Facebookcommerce_WarmConfig' ) ) {
$fb_warm_pixel_id = WC_Facebookcommerce_WarmConfig::$fb_warm_pixel_id;
if ( WC_Facebookcommerce_Utils::is_valid_id( $fb_warm_pixel_id ) &&
(int) $fb_warm_pixel_id == $fb_warm_pixel_id ) {
$fb_warm_pixel_id = (string) $fb_warm_pixel_id;
self::set_pixel_id( $fb_warm_pixel_id );
}
}
$is_advanced_matching_enabled = self::get_use_pii_key();
if ( $is_advanced_matching_enabled == null &&
class_exists( 'WC_Facebookcommerce_WarmConfig' ) ) {
$fb_warm_is_advanced_matching_enabled =
WC_Facebookcommerce_WarmConfig::$fb_warm_is_advanced_matching_enabled;
if ( is_bool( $fb_warm_is_advanced_matching_enabled ) ) {
self::set_use_pii_key( $fb_warm_is_advanced_matching_enabled ? 1 : 0 );
}
}
}
/**
* Returns FB pixel code script part
*/
public function pixel_base_code() {
$pixel_id = self::get_pixel_id();
if (
(
isset( self::$render_cache[ self::PIXEL_RENDER ] ) &&
self::$render_cache[ self::PIXEL_RENDER ] === true
) ||
! isset( $pixel_id ) ||
$pixel_id === 0
) {
return;
}
self::$render_cache[ self::PIXEL_RENDER ] = true;
$params = self::add_version_info();
return sprintf(
"
<!-- %s Facebook Integration Begin -->
%s
<script>
%s
fbq('track', 'PageView', %s);
document.addEventListener('DOMContentLoaded', function() {
jQuery && jQuery(function($){
$('body').on('added_to_cart', function(event) {
// Ajax action.
$.get('?wc-ajax=fb_inject_add_to_cart_event', function(data) {
$('head').append(data);
});
});
});
}, false);
</script>
<!-- DO NOT MODIFY -->
<!-- %s Facebook Integration end -->
",
WC_Facebookcommerce_Utils::getIntegrationName(),
self::get_basecode(),
$this->pixel_init_code(),
json_encode( $params, JSON_PRETTY_PRINT | JSON_FORCE_OBJECT ),
WC_Facebookcommerce_Utils::getIntegrationName()
);
}
/**
* Prevent double-fires by checking the last event
*/
public function check_last_event( $event_name ) {
return $event_name === $this->last_event;
}
/**
* Preferred method to inject events in a page, normally you should use this
* instead of WC_Facebookcommerce_Pixel::build_event()
*/
public function inject_event( $event_name, $params, $method = 'track' ) {
$code = self::build_event( $event_name, $params, $method );
$this->last_event = $event_name;
if ( WC_Facebookcommerce_Utils::isWoocommerceIntegration() ) {
WC_Facebookcommerce_Utils::wc_enqueue_js( $code );
} else {
printf(
'
<!-- Facebook Pixel Event Code -->
<script>
%s
</script>
<!-- End Facebook Pixel Event Code -->
',
$code
);
}
}
public function inject_conditional_event(
$event_name, $params, $listener, $jsonified_pii = '' ) {
$code = self::build_event( $event_name, $params, 'track' );
$this->last_event = $event_name;
// Prepends fbq(...) with pii information to the injected code.
if ( $jsonified_pii && get_option( self::SETTINGS_KEY )[ self::USE_PII_KEY ] ) {
$this->user_info = '%s';
$code =
sprintf( $this->pixel_init_code(), '" || ' . $jsonified_pii . ' || "' ) . $code;
}
printf(
"
<!-- Facebook Pixel Event Code -->
<script>
document.addEventListener('%s', function (event) {
%s
}, false );
</script>
<!-- End Facebook Pixel Event Code -->
",
$listener,
$code
);
}
/**
* Returns FB pixel code noscript part to avoid W3 validation error
*/
public function pixel_base_code_noscript() {
$pixel_id = self::get_pixel_id();
if (
(
isset( self::$render_cache[ self::NO_SCRIPT_RENDER ] ) &&
self::$render_cache[ self::NO_SCRIPT_RENDER ] === true
) ||
! isset( $pixel_id ) ||
$pixel_id === 0
) {
return;
}
self::$render_cache[ self::NO_SCRIPT_RENDER ] = true;
return sprintf(
'
<!-- Facebook Pixel Code -->
<noscript>
<img height="1" width="1" style="display:none" alt="fbpx"
src="https://www.facebook.com/tr?id=%s&ev=PageView&noscript=1"/>
</noscript>
<!-- DO NOT MODIFY -->
<!-- End Facebook Pixel Code -->
',
esc_js( $pixel_id )
);
}
/**
* You probably should use WC_Facebookcommerce_Pixel::inject_event() but
* this method is available if you need to modify the JS code somehow
*/
public static function build_event( $event_name, $params, $method = 'track' ) {
$params = self::add_version_info( $params );
return sprintf(
"/* %s Facebook Integration Event Tracking */\n" .
"fbq('%s', '%s', %s);",
WC_Facebookcommerce_Utils::getIntegrationName(),
$method,
$event_name,
json_encode( $params, JSON_PRETTY_PRINT | JSON_FORCE_OBJECT )
);
}
public static function get_pixel_id() {
$fb_options = self::get_options();
if ( ! $fb_options ) {
return '';
}
return isset( $fb_options[ self::PIXEL_ID_KEY ] ) ?
$fb_options[ self::PIXEL_ID_KEY ] : '';
}
public static function set_pixel_id( $pixel_id ) {
$fb_options = self::get_options();
if ( isset( $fb_options[ self::PIXEL_ID_KEY ] )
&& $fb_options[ self::PIXEL_ID_KEY ] == $pixel_id ) {
return;
}
$fb_options[ self::PIXEL_ID_KEY ] = $pixel_id;
update_option( self::SETTINGS_KEY, $fb_options );
}
public static function get_use_pii_key() {
$fb_options = self::get_options();
if ( ! $fb_options ) {
return null;
}
return isset( $fb_options[ self::USE_PII_KEY ] ) ?
$fb_options[ self::USE_PII_KEY ] : null;
}
public static function set_use_pii_key( $use_pii ) {
$fb_options = self::get_options();
if ( isset( $fb_options[ self::USE_PII_KEY ] )
&& $fb_options[ self::USE_PII_KEY ] == $use_pii ) {
return;
}
$fb_options[ self::USE_PII_KEY ] = $use_pii;
update_option( self::SETTINGS_KEY, $fb_options );
}
public static function get_basecode() {
return self::$default_pixel_basecode;
}
private static function get_version_info() {
global $wp_version;
if ( WC_Facebookcommerce_Utils::isWoocommerceIntegration() ) {
return array(
'source' => 'woocommerce',
'version' => WC()->version,
'pluginVersion' => WC_Facebookcommerce_Utils::PLUGIN_VERSION,
);
}
return array(
'source' => 'wordpress',
'version' => $wp_version,
'pluginVersion' => WC_Facebookcommerce_Utils::PLUGIN_VERSION,
);
}
public static function get_options() {
return get_option(
self::SETTINGS_KEY,
array(
self::PIXEL_ID_KEY => '0',
self::USE_PII_KEY => 0,
)
);
}
/**
* Returns an array with version_info for pixel fires. Parameters provided by
* users should not be overwritten by this function
*/
private static function add_version_info( $params = array() ) {
// if any parameter is passed in the pixel, do not overwrite it
return array_replace( self::get_version_info(), $params );
}
/**
* Init code might contain additional information to help matching website
* users with facebook users. Information is hashed in JS side using SHA256
* before sending to Facebook.
*/
private function pixel_init_code() {
$version_info = self::get_version_info();
$agent_string = sprintf(
'%s-%s-%s',
$version_info['source'],
$version_info['version'],
$version_info['pluginVersion']
);
$params = array(
'agent' => $agent_string,
);
return apply_filters(
'facebook_woocommerce_pixel_init',
sprintf(
"fbq('init', '%s', %s, %s);\n",
esc_js( self::get_pixel_id() ),
json_encode( $this->user_info, JSON_PRETTY_PRINT | JSON_FORCE_OBJECT ),
json_encode( $params, JSON_PRETTY_PRINT | JSON_FORCE_OBJECT )
)
);
}
}
endif;
<?php
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
*
* This source code is licensed under the license found in the
* LICENSE file in the root directory of this source tree.
*
* @package FacebookCommerce
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
if ( ! class_exists( 'WC_Facebookcommerce_WarmConfig' ) ) :
class WC_Facebookcommerce_WarmConfig {
static $fb_warm_pixel_id = null;
static $fb_warm_is_advanced_matching_enabled = null;
}
endif;
<?php
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
*
* This source code is licensed under the license found in the
* LICENSE file in the root directory of this source tree.
*
* Plugin Name: Facebook for WooCommerce
* Plugin URI: https://github.com/facebookincubator/facebook-for-woocommerce/
* Description: Grow your business on Facebook! Use this official plugin to help sell more of your products using Facebook. After completing the setup, you'll be ready to create ads that promote your products and you can also create a shop section on your Page where customers can browse your products on Facebook.
* Author: Facebook
* Author URI: https://www.facebook.com/
* Version: 1.9.15
* Woo: 2127297:0ea4fe4c2d7ca6338f8a322fb3e4e187
* Text Domain: facebook-for-woocommerce
* WC requires at least: 3.0.0
* WC tested up to: 3.3.5
*
* @package FacebookCommerce
*/
if ( ! class_exists( 'WC_Facebookcommerce' ) ) :
include_once 'includes/fbutils.php';
class WC_Facebookcommerce {
// Change it above as well
const PLUGIN_VERSION = WC_Facebookcommerce_Utils::PLUGIN_VERSION;
/**
* Construct the plugin.
*/
public function __construct() {
add_action( 'plugins_loaded', array( $this, 'init' ) );
}
/**
* Initialize the plugin.
*/
public function init() {
if ( is_admin() ) {
add_filter(
'plugin_action_links_' . plugin_basename( __FILE__ ),
array( $this, 'add_settings_link' )
);
}
if ( WC_Facebookcommerce_Utils::isWoocommerceIntegration() ) {
if ( ! defined( 'WOOCOMMERCE_FACEBOOK_PLUGIN_SETTINGS_URL' ) ) {
define(
'WOOCOMMERCE_FACEBOOK_PLUGIN_SETTINGS_URL',
get_admin_url()
. '/admin.php?page=wc-settings&tab=integration'
. '&section=facebookcommerce'
);
}
include_once 'facebook-commerce.php';
// Register WooCommerce integration.
add_filter(
'woocommerce_integrations',
array(
$this,
'add_woocommerce_integration',
)
);
}
}
public function add_settings_link( $links ) {
$settings = array(
'settings' => sprintf(
'<a href="%s">%s</a>',
admin_url( 'admin.php?page=wc-settings&tab=integration&section=facebookcommerce' ),
'Settings'
),
);
return array_merge( $settings, $links );
}
public function wp_debug_display_error() {
?>
<div class="error below-h3">
<p>
<?php
printf(
__(
'To use Facebook for WooCommerce,
please disable WP_DEBUG_DISPLAY in your wp-config.php file.
Contact your server administrator for more assistance.',
'facebook-for-woocommerce'
)
);
?>
</p>
</div>
<?php
}
/**
* Add a new integration to WooCommerce.
*/
public function add_woocommerce_integration( $integrations ) {
$integrations[] = 'WC_Facebookcommerce_Integration';
return $integrations;
}
public function add_wordpress_integration() {
new WP_Facebook_Integration();
}
}
$WC_Facebookcommerce = new WC_Facebookcommerce( __FILE__ );
endif;
<?php
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
*
* This source code is licensed under the license found in the
* LICENSE file in the root directory of this source tree.
*
* @package FacebookCommerce
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
if ( ! class_exists( 'WP_Async_Request', false ) ) {
// Do not attempt to create this class without WP_Async_Request
return;
}
if ( ! class_exists( 'WC_Facebookcommerce_Async_Request' ) ) :
/**
* FB Graph API async request
*/
class WC_Facebookcommerce_Async_Request extends WP_Async_Request {
protected $action = 'wc_facebook_async_request';
/**
* Handle
*
* Override this method to perform any actions required
* during the async request.
*/
protected function handle() {
// Actions to perform
}
}
endif;
<?php
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
*
* This source code is licensed under the license found in the
* LICENSE file in the root directory of this source tree.
*
* @package FacebookCommerce
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
if ( ! class_exists( 'WP_Background_Process', false ) ) {
// Do not attempt to create this class without WP_Background_Process
return;
}
if ( ! class_exists( 'WC_Facebookcommerce_Background_Process' ) ) :
class WC_Facebookcommerce_Background_Process extends WP_Background_Process {
public function __construct( $commerce ) {
$this->commerce = $commerce; // Full WC_Facebookcommerce_Integration obj
}
/**
* @var string
*/
protected $action = 'fb_commerce_background_process';
public function dispatch() {
$commerce = $this->commerce;
$dispatched = parent::dispatch();
if ( is_wp_error( $dispatched ) ) {
WC_Facebookcommerce_Utils::log(
sprintf(
'Unable to dispatch FB Background processor: %s',
$dispatched->get_error_message()
),
array( 'source' => 'wc_facebook_background_process' )
);
}
}
public function get_item_count() {
$commerce = $this->commerce;
return (int) get_transient( $commerce::FB_SYNC_REMAINING );
}
/**
* Handle cron healthcheck
*
* Restart the background process if not already running
* and data exists in the queue.
*/
public function handle_cron_healthcheck() {
$commerce = $this->commerce;
if ( $this->is_process_running() ) {
// Background process already running, no-op
return true; // Return "is running? status"
}
if ( $this->is_queue_empty() ) {
// No data to process.
$this->clear_scheduled_event();
delete_transient( $commerce::FB_SYNC_REMAINING );
return;
}
$this->handle();
return true;
}
/**
* Schedule fallback event.
*/
protected function schedule_event() {
if ( ! wp_next_scheduled( $this->cron_hook_identifier ) ) {
wp_schedule_event(
time() + 10,
$this->cron_interval_identifier,
$this->cron_hook_identifier
);
}
}
/**
* Is the processor updating?
*
* @return boolean
*/
public function is_updating() {
return false === $this->is_queue_empty();
}
/**
* Is the processor running?
*
* @return boolean
*/
public function is_running() {
return $this->is_process_running();
}
/**
* Process individual product
*
* Returns false to remove the item from the queue
* (would return item if it needed additional processing).
*
* @param mixed $item Queue item to iterate over
*
* @return mixed
*/
protected function task( $item ) {
$commerce = $this->commerce; // PHP5 compatibility for static access
$remaining = $this->get_item_count();
$count_message = sprintf(
'Background syncing products to Facebook. Products remaining: %1$d',
$remaining
);
$this->commerce->display_sticky_message( $count_message, true );
$this->commerce->on_product_publish( $item );
$remaining--;
set_transient(
$commerce::FB_SYNC_IN_PROGRESS,
true,
$commerce::FB_SYNC_TIMEOUT
);
set_transient(
$commerce::FB_SYNC_REMAINING,
$remaining
);
return false;
}
/**
* Complete
*
* Override if applicable, but ensure that the below actions are
* performed, or, call parent::complete().
*/
protected function complete() {
$commerce = $this->commerce; // PHP5 compatibility for static access
delete_transient( $commerce::FB_SYNC_IN_PROGRESS );
delete_transient( $commerce::FB_SYNC_REMAINING );
WC_Facebookcommerce_Utils::log( 'Background sync complete!' );
WC_Facebookcommerce_Utils::fblog( 'Background sync complete!' );
$this->commerce->remove_sticky_message();
$this->commerce->display_info_message( 'Facebook product sync complete!' );
parent::complete();
}
}
endif;
<?php
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
*
* This source code is licensed under the license found in the
* LICENSE file in the root directory of this source tree.
*
* @package FacebookCommerce
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
if ( ! class_exists( 'WC_Facebookcommerce_Graph_API' ) ) :
if ( ! class_exists( 'WC_Facebookcommerce_Async_Request' ) ) {
include_once 'fbasync.php';
}
/**
* FB Graph API helper functions
*/
class WC_Facebookcommerce_Graph_API {
const GRAPH_API_URL = 'https://graph.facebook.com/v2.9/';
const CURL_TIMEOUT = 500;
/**
* Cache the api_key
*/
var $api_key;
/**
* Init
*/
public function __construct( $api_key ) {
$this->api_key = $api_key;
}
public function _get( $url, $api_key = '' ) {
$api_key = $api_key ?: $this->api_key;
return wp_remote_get(
$url,
array(
'headers' => array(
'Authorization' => 'Bearer ' . $api_key,
),
'timeout' => self::CURL_TIMEOUT,
)
);
}
public function _post( $url, $data, $api_key = '' ) {
if ( class_exists( 'WC_Facebookcommerce_Async_Request' ) ) {
return self::_post_async( $url, $data );
} else {
return self::_post_sync( $url, $data );
}
}
public function _post_sync( $url, $data, $api_key = '' ) {
$api_key = $api_key ?: $this->api_key;
return wp_remote_post(
$url,
array(
'body' => $data,
'headers' => array(
'Authorization' => 'Bearer ' . $api_key,
),
'timeout' => self::CURL_TIMEOUT,
)
);
}
public function _post_async( $url, $data, $api_key = '' ) {
if ( ! class_exists( 'WC_Facebookcommerce_Async_Request' ) ) {
return;
}
$api_key = $api_key ?: $this->api_key;
$fbasync = new WC_Facebookcommerce_Async_Request();
$fbasync->query_url = $url;
$fbasync->query_args = array();
$fbasync->post_args = array(
'body' => $data,
'headers' => array(
'Authorization' => 'Bearer ' . $api_key,
),
'timeout' => self::CURL_TIMEOUT,
);
return $fbasync->dispatch();
}
public function _delete( $url, $api_key = '' ) {
$api_key = $api_key ?: $this->api_key;
return wp_remote_request(
$url,
array(
'headers' => array(
'Authorization' => 'Bearer ' . $api_key,
),
'timeout' => self::CURL_TIMEOUT,
'method' => 'DELETE',
)
);
}
// GET https://graph.facebook.com/vX.X/{page-id}/?fields=name
public function get_page_name( $page_id, $api_key = '' ) {
$api_key = $api_key ?: $this->api_key;
$url = $this->build_url( $page_id, '/?fields=name' );
$response = self::_get( $url, $api_key );
if ( is_wp_error( $response ) ) {
WC_Facebookcommerce_Utils::log( $response->get_error_message() );
return;
}
if ( $response['response']['code'] != '200' ) {
return;
}
$response_body = wp_remote_retrieve_body( $response );
return json_decode( $response_body )->name;
}
public function validate_product_catalog( $product_catalog_id ) {
$url = $this->build_url( $product_catalog_id );
$response = self::_get( $url );
if ( is_wp_error( $response ) ) {
WC_Facebookcommerce_Utils::log( $response->get_error_message() );
return;
}
return $response['response']['code'] == '200';
}
// POST https://graph.facebook.com/vX.X/{product-catalog-id}/product_groups
public function create_product_group( $product_catalog_id, $data ) {
$url = $this->build_url( $product_catalog_id, '/product_groups' );
return self::_post( $url, $data );
}
// POST https://graph.facebook.com/vX.X/{product-group-id}/products
public function create_product_item( $product_group_id, $data ) {
$url = $this->build_url( $product_group_id, '/products' );
return self::_post( $url, $data );
}
public function update_product_group( $product_catalog_id, $data ) {
$url = $this->build_url( $product_catalog_id );
return self::_post( $url, $data );
}
public function update_product_item( $product_id, $data ) {
$url = $this->build_url( $product_id );
return self::_post( $url, $data );
}
public function delete_product_item( $product_item_id ) {
$product_item_url = $this->build_url( $product_item_id );
return self::_delete( $product_item_url );
}
public function delete_product_group( $product_group_id ) {
$product_group_url = $this->build_url( $product_group_id );
return self::_delete( $product_group_url );
}
public function log( $ems_id, $message, $error ) {
$log_url = $this->build_url( $ems_id, '/log_events' );
$data = array(
'message' => $message,
'error' => $error,
);
self::_post( $log_url, $data );
}
public function log_tip_event( $tip_id, $channel_id, $event ) {
$tip_event_log_url = $this->build_url( '', '/log_tip_events' );
$data = array(
'tip_id' => $tip_id,
'channel_id' => $channel_id,
'event' => $event,
);
self::_post( $tip_event_log_url, $data );
}
public function create_upload( $facebook_feed_id, $path_to_feed_file ) {
$url = $this->build_url(
$facebook_feed_id,
'/uploads?access_token=' . $this->api_key
);
$data = array(
'file' => new CurlFile( $path_to_feed_file, 'text/csv' ),
);
$curl = curl_init();
curl_setopt_array(
$curl,
array(
CURLOPT_URL => $url,
CURLOPT_POST => 1,
CURLOPT_POSTFIELDS => $data,
CURLOPT_RETURNTRANSFER => 1,
)
);
$response = curl_exec( $curl );
if ( curl_errno( $curl ) ) {
WC_Facebookcommerce_Utils::fblog( $response );
return null;
}
return WC_Facebookcommerce_Utils::decode_json( $response, true );
}
public function create_feed( $facebook_catalog_id, $data ) {
$url = $this->build_url( $facebook_catalog_id, '/product_feeds' );
// success API call will return {id: <product feed id>}
// failure API will return {error: <error message>}
return self::_post( $url, $data );
}
public function get_upload_status( $facebook_upload_id ) {
$url = $this->build_url( $facebook_upload_id, '/?fields=end_time' );
// success API call will return
// {id: <upload id>, end_time: <time when upload completes>}
// failure API will return {error: <error message>}
return self::_get( $url );
}
// success API call will return a JSON of tip info
public function get_tip_info( $external_merchant_settings_id ) {
$url = $this->build_url( $external_merchant_settings_id, '/?fields=connect_woo' );
$response = self::_get( $url, $this->api_key );
$data = array(
'response' => $response,
);
if ( is_wp_error( $response ) ) {
$data['error_type'] = 'is_wp_error';
WC_Facebookcommerce_Utils::fblog(
'Failed to get AYMT tip info via API.',
$data,
true
);
return;
}
if ( $response['response']['code'] != '200' ) {
$data['error_type'] = 'Non-200 error code from FB';
WC_Facebookcommerce_Utils::fblog(
'Failed to get AYMT tip info via API.',
$data,
true
);
return;
}
$response_body = wp_remote_retrieve_body( $response );
$connect_woo =
WC_Facebookcommerce_Utils::decode_json( $response_body )->connect_woo;
if ( ! isset( $connect_woo ) ) {
$data['error_type'] = 'Response body not set';
WC_Facebookcommerce_Utils::fblog(
'Failed to get AYMT tip info via API.',
$data,
true
);
}
return $connect_woo;
}
public function get_facebook_id( $facebook_catalog_id, $product_id ) {
$param = 'catalog:' . (string) $facebook_catalog_id . ':' .
base64_encode( $product_id ) . '/?fields=id,product_group{id}';
$url = $this->build_url( '', $param );
// success API call will return
// {id: <fb product id>, product_group{id} <fb product group id>}
// failure API will return {error: <error message>}
return self::_get( $url );
}
public function check_product_info( $facebook_catalog_id, $product_id, $pr_v ) {
$param = 'catalog:' . (string) $facebook_catalog_id . ':' .
base64_encode( $product_id ) . '/?fields=id,name,description,price,' .
'sale_price,sale_price_start_date,sale_price_end_date,image_url,' .
'visibility';
if ( $pr_v ) {
$param = $param . ',additional_variant_attributes{value}';
}
$url = $this->build_url( '', $param );
// success API call will return
// {id: <fb product id>, name,description,price,sale_price,sale_price_start_date
// sale_price_end_date
// failure API will return {error: <error message>}
return self::_get( $url );
}
public function set_default_variant( $product_group_id, $data ) {
$url = $this->build_url( $product_group_id );
return self::_post( $url, $data );
}
private function build_url( $field_id, $param = '' ) {
return self::GRAPH_API_URL . (string) $field_id . $param;
}
}
endif;
<?php
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
*
* This source code is licensed under the license found in the
* LICENSE file in the root directory of this source tree.
*
* @package FacebookCommerce
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
if ( ! class_exists( 'WC_Facebookcommerce_Utils' ) ) {
include_once 'includes/fbutils.php';
}
if ( ! class_exists( 'WC_Facebookcommerce_Info_Banner' ) ) :
/**
* FB Info Banner class
*/
class WC_Facebookcommerce_Info_Banner {
const FB_NO_TIP_EXISTS = 'No Tip Exist!';
const DEFAULT_TIP_IMG_URL_PREFIX = 'https://www.facebook.com';
const CHANNEL_ID = 2087541767986590;
/** @var object Class Instance */
private static $instance;
/** @var string If the banner has been dismissed */
private $external_merchant_settings_id;
private $fbgraph;
private $should_query_tip;
/**
* Get the class instance
*/
public static function get_instance(
$external_merchant_settings_id,
$fbgraph,
$should_query_tip = false ) {
return null === self::$instance
? ( self::$instance = new self(
$external_merchant_settings_id,
$fbgraph,
$should_query_tip
) )
: self::$instance;
}
/**
* Constructor
*/
public function __construct(
$external_merchant_settings_id,
$fbgraph,
$should_query_tip = false ) {
$this->should_query_tip = $should_query_tip;
$this->external_merchant_settings_id = $external_merchant_settings_id;
$this->fbgraph = $fbgraph;
add_action( 'wp_ajax_ajax_woo_infobanner_post_click', array( $this, 'ajax_woo_infobanner_post_click' ) );
add_action( 'wp_ajax_ajax_woo_infobanner_post_xout', array( $this, 'ajax_woo_infobanner_post_xout' ) );
add_action( 'admin_notices', array( $this, 'banner' ) );
add_action( 'admin_init', array( $this, 'dismiss_banner' ) );
}
/**
* Post click event when hit primary button.
*/
function ajax_woo_infobanner_post_click() {
WC_Facebookcommerce_Utils::check_woo_ajax_permissions(
'post tip click event',
true
);
check_ajax_referer( 'wc_facebook_infobanner_jsx' );
$tip_info = WC_Facebookcommerce_Utils::get_cached_best_tip();
$tip_id = isset( $tip_info->tip_id )
? $tip_info->tip_id
: null;
if ( $tip_id == null ) {
WC_Facebookcommerce_Utils::fblog(
'Do not have tip id when click, sth went wrong',
array( 'tip_info' => $tip_info ),
true
);
} else {
WC_Facebookcommerce_Utils::tip_events_log(
$tip_id,
self::CHANNEL_ID,
'click'
);
}
}
/**
* Post xout event when hit dismiss button.
*/
function ajax_woo_infobanner_post_xout() {
WC_Facebookcommerce_Utils::check_woo_ajax_permissions(
'post tip xout event',
true
);
check_ajax_referer( 'wc_facebook_infobanner_jsx' );
$tip_info = WC_Facebookcommerce_Utils::get_cached_best_tip();
$tip_id = isset( $tip_info->tip_id )
? $tip_info->tip_id
: null;
// Delete cached tip if xout.
update_option( 'fb_info_banner_last_best_tip', '' );
if ( $tip_id == null ) {
WC_Facebookcommerce_Utils::fblog(
'Do not have tip id when xout, sth went wrong',
array( 'tip_info' => $tip_info ),
true
);
} else {
WC_Facebookcommerce_Utils::tip_events_log(
$tip_id,
self::CHANNEL_ID,
'xout'
);
}
}
/**
* Display a info banner on Woocommerce pages.
*/
public function banner() {
$screen = get_current_screen();
if ( ! in_array(
$screen->base,
array(
'woocommerce_page_wc-reports',
'woocommerce_page_wc-settings',
'woocommerce_page_wc-status',
)
) ||
$screen->is_network || $screen->action ) {
return;
}
$tip_info = null;
if ( ! $this->should_query_tip ) {
// If last query is less than 1 day, either has last best tip or default
// tip pass time cap.
$tip_info = WC_Facebookcommerce_Utils::get_cached_best_tip();
} else {
$tip_info = $this->fbgraph->get_tip_info(
$this->external_merchant_settings_id
);
update_option( 'fb_info_banner_last_query_time', current_time( 'mysql' ) );
}
// Not render if no cached best tip, or no best tip returned from FB.
if ( ! $tip_info || ( $tip_info === self::FB_NO_TIP_EXISTS ) ) {
// Delete cached tip if should query and get no tip.
delete_option( 'fb_info_banner_last_best_tip' );
return;
} else {
// Get tip creatives via API
if ( is_string( $tip_info ) ) {
$tip_info = WC_Facebookcommerce_Utils::decode_json( $tip_info );
}
$tip_title = isset( $tip_info->tip_title->__html )
? $tip_info->tip_title->__html
: null;
$tip_body = isset( $tip_info->tip_body->__html )
? $tip_info->tip_body->__html
: null;
$tip_action_link = isset( $tip_info->tip_action_link )
? $tip_info->tip_action_link
: null;
$tip_action = isset( $tip_info->tip_action->__html )
? $tip_info->tip_action->__html
: null;
$tip_img_url = isset( $tip_info->tip_img_url )
? self::DEFAULT_TIP_IMG_URL_PREFIX . $tip_info->tip_img_url
: null;
if ( $tip_title == null || $tip_body == null || $tip_action_link == null
|| $tip_action == null || $tip_action == null ) {
WC_Facebookcommerce_Utils::fblog(
'Unexpected response from FB for tip info.',
array( 'tip_info' => $tip_info ),
true
);
return;
}
update_option(
'fb_info_banner_last_best_tip',
is_object( $tip_info ) || is_array( $tip_info )
? json_encode( $tip_info ) : $tip_info
);
}
$dismiss_url = $this->dismiss_url();
echo '<div class="updated fade"><div id="fbinfobanner"><div><img src="' . $tip_img_url .
'" class="iconDetails"></div><p class = "tipTitle">' .
__( '<strong>' . $tip_title . '</strong>', 'facebook-for-woocommerce' ) . "\n";
echo '<p class = "tipContent">' .
__( $tip_body, 'facebook-for-woocommerce' ) . '</p>';
echo '<p class = "tipButton"><a href="' . $tip_action_link . '" class = "btn" onclick="fb_woo_infobanner_post_click(); return true;" title="' .
__( 'Click and redirect.', 'facebook-for-woocommerce' ) .
'"> ' . __( $tip_action, 'facebook-for-woocommerce' ) . '</a>' .
'<a href="' . esc_url( $dismiss_url ) . '" class = "btn dismiss grey" onclick="fb_woo_infobanner_post_xout(); return true;" title="' .
__( 'Dismiss this notice.', 'facebook-for-woocommerce' ) .
'"> ' . __( 'Dismiss', 'facebook-for-woocommerce' ) . '</a></p></div></div>';
}
/**
* Returns the url that the user clicks to remove the info banner
*
* @return (string)
*/
private function dismiss_url() {
$url = admin_url( 'admin.php' );
$url = add_query_arg(
array(
'page' => 'wc-settings',
'tab' => 'integration',
'wc-notice' => 'dismiss-fb-info-banner',
),
$url
);
return wp_nonce_url( $url, 'woocommerce_info_banner_dismiss' );
}
/**
* Handles the dismiss action so that the banner can be permanently hidden
* during time threshold
*/
public function dismiss_banner() {
if ( ! isset( $_GET['wc-notice'] ) ) {
return;
}
if ( 'dismiss-fb-info-banner' !== $_GET['wc-notice'] ) {
return;
}
if ( ! check_admin_referer( 'woocommerce_info_banner_dismiss' ) ) {
return;
}
// Delete cached tip if xout.
delete_option( 'fb_info_banner_last_best_tip' );
if ( wp_get_referer() ) {
wp_safe_redirect( wp_get_referer() );
} else {
wp_safe_redirect( admin_url( 'admin.php?page=wc-settings&tab=integration' ) );
}
}
}
endif;
<?php
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
*
* This source code is licensed under the license found in the
* LICENSE file in the root directory of this source tree.
*
* @package FacebookCommerce
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
if ( ! class_exists( 'WC_Facebook_WPML_Injector' ) ) :
class FB_WPML_Language_Status {
const VISIBLE = 1;
const HIDDEN = 2;
const NOT_SYNCED = 0;
}
class WC_Facebook_WPML_Injector {
public static $settings = null;
public static $default_lang = null;
const OPTION = 'fb_wmpl_language_visibility';
public function __construct() {
add_action( 'icl_menu_footer', array( $this, 'wpml_support' ) );
add_action( 'icl_ajx_custom_call', array( $this, 'wpml_ajax_support' ), 10, 2 );
self::$settings = get_option( self::OPTION );
self::$default_lang = apply_filters( 'wpml_default_language', null );
}
public static function should_hide( $wp_id ) {
$product_lang = apply_filters( 'wpml_post_language_details', null, $wp_id );
$settings = self::$settings;
if ( $product_lang && isset( $product_lang['language_code'] ) ) {
$product_lang = $product_lang['language_code'];
}
// Option doesn't exist : Backwards Compatibility
if ( ! $settings ) {
return ( $product_lang && self::$default_lang !== $product_lang );
}
// Hide products from non-active languages.
if ( ! isset( $settings[ $product_lang ] ) ) {
return true;
}
return $settings[ $product_lang ] !== FB_WPML_Language_Status::VISIBLE;
}
public function wpml_ajax_support( $call, $REQUEST ) {
global $sitepress;
if ( isset( $REQUEST['icl_ajx_action'] ) ) {
$call = $REQUEST['icl_ajx_action'];
}
if ( $call === 'icl_fb_woo' ) {
$active_languages = array_keys( $sitepress->get_active_languages() );
$settings = array();
foreach ( $active_languages as $lang ) {
$settings[ $lang ] = $REQUEST[ $lang ] === 'on' ?
FB_WPML_Language_Status::VISIBLE : FB_WPML_Language_Status::HIDDEN;
}
update_option( 'fb_wmpl_language_visibility', $settings, false );
self::$settings = $settings;
}
}
public function wpml_support() {
global $sitepress;
if ( strpos( $_GET['page'], 'languages.php' ) ) {
$active_languages = $sitepress->get_active_languages();
$settings = get_option( self::OPTION );
// Default setting is only show default lang.
if ( ! $settings ) {
$settings = array_fill_keys(
array_keys( $active_languages ),
FB_WPML_Language_Status::HIDDEN
);
$settings[ self::$default_lang ] = FB_WPML_Language_Status::VISIBLE;
}
$ajax_response = sprintf(
'Saved. You should now ' .
' <a href="%s&fb_force_resync=true">Re-Sync</a>' .
' your products with Facebook. ',
WOOCOMMERCE_FACEBOOK_PLUGIN_SETTINGS_URL
);
?><div id="lang-sec-fb" class="wpml-section wpml-section-languages">
<div class="wpml-section-header">
<h3><?php _e( 'Facebook Visibility', 'sitepress' ); ?></h3>
</div>
<div class="wpml-section-content">
WooCommerce Products with languages that are selected
here will be visible to customers who see your Facebook Shop.
<div class="wpml-section-content-inner">
<form id="icl_fb_woo" name="icl_fb_woo" action="">
<?php
foreach ( $settings as $language => $set ) {
$is_checked = $set === FB_WPML_Language_Status::VISIBLE ?
'checked' : '';
$str = '
<p><label>
<input type="checkbox" id="icl_fb_woo_chk" name="' . $language . '" ' . $is_checked . '>
' . $active_languages[ $language ]['native_name'] . '
</label></p>
';
echo $str;
}
?>
<p class="buttons-wrap">
<span class="icl_ajx_response_fb" id="icl_ajx_response_fb" hidden="true">
<?php echo $ajax_response; ?>
</span>
<input class="button button-primary"
name="save"
value="<?php _e( 'Save', 'sitepress' ); ?>"
type="submit" />
</p>
</form>
<script type="text/javascript">
addLoadEvent(function(){
jQuery('#icl_fb_woo').submit(iclSaveForm);
jQuery('#icl_fb_woo').submit(function(){
jQuery('#icl_ajx_response_fb').show();
});
});
</script>
</div>
</div>
</div>
<?php
}
}
}
endif;
<?php
/**
* Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
*
* This source code is licensed under the license found in the
* LICENSE file in the root directory of this source tree.
*
* @package FacebookCommerce
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
require_once dirname( __DIR__ ) . '/fbproductfeed.php';
require_once dirname( __DIR__ ) . '/fbutils.php';
if ( ! class_exists( 'WC_Facebook_Product_Feed_Test' ) ) :
/**
* Mock for Facebook feed class
*/
class WC_Facebook_Product_Feed_Test_Mock extends WC_Facebook_Product_Feed {
public static $product_post_wpid = null;
// Return test product post id.
// Don't mess up actual products.
public function get_product_wpid() {
return self::$product_post_wpid;
}
// Log progress in local log file for testing.
// Not to overwhelm DB log to track important signals.
public function log_feed_progress( $msg, $object = array() ) {
$msg = empty( $object ) ? $msg : $msg . json_encode( $object );
WC_Facebookcommerce_Utils::log( 'Test - ' . $msg );
}
}
endif;
=== Facebook for WooCommerce ===
Contributors: facebook, automattic, woothemes
Tags: facebook, shop, catalog, advertise, pixel, product
Requires at least: 4.4
Tested up to: 5.2.2
Stable tag: 1.9.15
Requires PHP: 5.6 or greater
MySQL: 5.6 or greater
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Get the Official Facebook for WooCommerce plugin for two powerful ways to help grow your business, including an ads extension and shops tab for your page.
== Description ==
This is the official Facebook for WooCommerce plugin that connects your WooCommerce website to Facebook. With this plugin, you can install the Facebook pixel, upload your online store catalog, and create a shop on your Facebook page, enabling you to easily run dynamic ads.
Marketing on Facebook helps your business build lasting relationships with people, find new customers, and increase sales for your online store. With this Facebook ad extension, reaching the people who matter most to your business is simple. This extension will track the results of your advertising across devices. It will also help you:
* Maximize your campaign performance. By setting up the Facebook pixel and building your audience, you will optimize your ads for people likely to buy your products, and reach people with relevant ads on Facebook after they’ve visited your website.
* Find more customers. Connecting your product catalog automatically creates carousel ads that showcase the products you sell and attract more shoppers to your website.
* Generate sales among your website visitors. When you set up the Facebook pixel and connect your product catalog, you can use dynamic ads to reach shoppers when they’re on Facebook with ads for the products they viewed on your website. This will be included in a future release of Facebook for WooCommerce.
== Installation ==
Visit the Facebook Help Center [here](https://www.facebook.com/business/help/900699293402826).
== Support ==
If you believe you have found a security vulnerability on Facebook, we encourage you to let us know right away. We investigate all legitimate reports and do our best to quickly fix the problem. Before reporting, please review [this page](https://www.facebook.com/whitehat), which includes our responsible disclosure policy and reward guideline. You can submit bugs [here](https://github.com/facebookincubator/facebook-for-woocommerce/issues) or contact advertising support [here](https://www.facebook.com/business/help/900699293402826).
When opening a bug on GitHub, please give us as many details as possible.
* Symptoms of your problem
* Screenshot, if possible
* Your Facebook page URL
* Your website URL
* Current version of Facebook-for-WooCommerce, WooCommerce, Wordpress, PHP
== Changelog ==
= 1.9.15 - 2019-06-27 =
* CSRF handling for Ajax calls like ajax_woo_infobanner_post_click, ajax_woo_infobanner_post_xout, ajax_fb_toggle_visibility
* use phpcs to adhere to WP coding standards
* Minor UI changes on the iFrame
= 1.9.14 - 2019-06-20 =
* Revisit CSRF security issue
* Remove rest controller which is not used
* Tested installation in wordpress 5.2.2, WooCommerce 3.64, php 5.6/7.3 with browser Chrome v75/Safari v12.1/Firefox v67.
= 1.9.13 - 2019-06-18 =
* Fix security issue
* Add more contributors to the plugin
= 1.9.12 - 2019-05-2 =
* Remove dead code which causes exception (Issue 975)
= 1.9.11 - 2019-02-26 =
* changing contributor to facebook from facebook4woocommerce, so that
woo plugin will be shown under
https://profiles.wordpress.org/facebook/#content-plugins
* adding changelog in readme.txt so that notifications will be sent for
updates and changelog will be shown under
https://wordpress.org/plugins/facebook-for-woocommerce/#developers
* removing debug flags notice under facebook-for-woocommerce.php so that
developers will be able to debug with debug logs
.git/
.gitignore
.travis.yml
readme.md
tests/
_inc/lib/icalendar-reader.php
modules/shortcodes/upcoming-events.php
modules/widgets/upcoming-events.php
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
this.wc=this.wc||{},this.wc.csvExport=function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=716)}({14:function(e,t){!function(){e.exports=this.moment}()},443:function(e,t,n){
/*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */
var r=r||function(e){"use strict";if("undefined"==typeof navigator||!/MSIE [1-9]\./.test(navigator.userAgent)){var t=e.document,n=function(){return e.URL||e.webkitURL||e},r=t.createElementNS("http://www.w3.org/1999/xhtml","a"),o="download"in r,i=/Version\/[\d\.]+.*Safari/.test(navigator.userAgent),a=e.webkitRequestFileSystem,u=e.requestFileSystem||a||e.mozRequestFileSystem,c=function(t){(e.setImmediate||e.setTimeout)((function(){throw t}),0)},f=0,l=function(e){setTimeout((function(){"string"==typeof e?n().revokeObjectURL(e):e.remove()}),4e4)},s=function(e,t,n){for(var r=(t=[].concat(t)).length;r--;){var o=e["on"+t[r]];if("function"==typeof o)try{o.call(e,n||e)}catch(e){c(e)}}},d=function(e){return/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(e.type)?new Blob(["\ufeff",e],{type:e.type}):e},p=function(t,c,p){p||(t=d(t));var v,y,w,m=this,b=t.type,g=!1,h=function(){s(m,"writestart progress write writeend".split(" "))},S=function(){if(y&&i&&"undefined"!=typeof FileReader){var r=new FileReader;return r.onloadend=function(){var e=r.result;y.location.href="data:attachment/file"+e.slice(e.search(/[,;]/)),m.readyState=m.DONE,h()},r.readAsDataURL(t),void(m.readyState=m.INIT)}(!g&&v||(v=n().createObjectURL(t)),y)?y.location.href=v:void 0===e.open(v,"_blank")&&i&&(e.location.href=v);m.readyState=m.DONE,h(),l(v)},O=function(e){return function(){if(m.readyState!==m.DONE)return e.apply(this,arguments)}},R={create:!0,exclusive:!1};if(m.readyState=m.INIT,c||(c="download"),o)return v=n().createObjectURL(t),void setTimeout((function(){var e,t;r.href=v,r.download=c,e=r,t=new MouseEvent("click"),e.dispatchEvent(t),h(),l(v),m.readyState=m.DONE}));e.chrome&&b&&"application/octet-stream"!==b&&(w=t.slice||t.webkitSlice,t=w.call(t,0,t.size,"application/octet-stream"),g=!0),a&&"download"!==c&&(c+=".download"),("application/octet-stream"===b||a)&&(y=e),u?(f+=t.size,u(e.TEMPORARY,f,O((function(e){e.root.getDirectory("saved",R,O((function(e){var n=function(){e.getFile(c,R,O((function(e){e.createWriter(O((function(n){n.onwriteend=function(t){y.location.href=e.toURL(),m.readyState=m.DONE,s(m,"writeend",t),l(e)},n.onerror=function(){var e=n.error;e.code!==e.ABORT_ERR&&S()},"writestart progress write abort".split(" ").forEach((function(e){n["on"+e]=m["on"+e]})),n.write(t),m.abort=function(){n.abort(),m.readyState=m.DONE},m.readyState=m.WRITING})),S)})),S)};e.getFile(c,{create:!1},O((function(e){e.remove(),n()})),O((function(e){e.code===e.NOT_FOUND_ERR?n():S()})))})),S)})),S)):S()},v=p.prototype;return"undefined"!=typeof navigator&&navigator.msSaveOrOpenBlob?function(e,t,n){return n||(e=d(e)),navigator.msSaveOrOpenBlob(e,t||"download")}:(v.abort=function(){this.readyState=this.DONE,s(this,"abort")},v.readyState=v.INIT=0,v.WRITING=1,v.DONE=2,v.error=v.onwritestart=v.onprogress=v.onwrite=v.onabort=v.onerror=v.onwriteend=null,function(e,t,n){return new p(e,t,n)})}}("undefined"!=typeof self&&self||"undefined"!=typeof window&&window||this.content);e.exports?e.exports.saveAs=r:"undefined"!=typeof define&&null!==define&&null!==define.amd&&define([],(function(){return r}))},716:function(e,t,n){"use strict";n.r(t),n.d(t,"generateCSVDataFromTable",(function(){return f})),n.d(t,"generateCSVFileName",(function(){return l})),n.d(t,"downloadCSVFile",(function(){return s}));var r=n(14),o=n.n(r),i=n(443);function a(e){var t=e.toString();return["=","+","-","@"].includes(t.charAt(0))?t="'"+t:t.match(/[,"\s]/)&&(t='"'+t.replace(/"/g,'""')+'"'),t}function u(e){return Array.isArray(e)?e.map((function(e){return a(e.label)})).join(","):[]}function c(e){return Array.isArray(e)?e.map((function(e){return e.map((function(e){return void 0===e.value||null===e.value?"":a(e.value)})).join(",")})).join("\n"):[]}function f(e,t){return[u(e),c(t)].filter((function(e){return e.length})).join("\n")}function l(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"",t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};return[e.toLowerCase().replace(/[^a-z0-9]/g,"-"),o()().format("YYYY-MM-DD"),Object.keys(t).map((function(e){return e.toLowerCase().replace(/[^a-z0-9]/g,"-")+"-"+decodeURIComponent(t[e]).toLowerCase().replace(/[^a-z0-9]/g,"-")})).join("_")].filter((function(e){return e.length})).join("_")+".csv"}function s(e,t){var n=new Blob([t],{type:"text/csv;charset=utf-8"});Object(i.saveAs)(n,e)}}});
\ No newline at end of file
this.wc=this.wc||{},this.wc.currency=function(e){var t={};function r(o){if(t[o])return t[o].exports;var n=t[o]={i:o,l:!1,exports:{}};return e[o].call(n.exports,n,n.exports,r),n.l=!0,n.exports}return r.m=e,r.c=t,r.d=function(e,t,o){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:o})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var o=Object.create(null);if(r.r(o),Object.defineProperty(o,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var n in e)r.d(o,n,function(t){return e[t]}.bind(null,n));return o},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=717)}({0:function(e,t){!function(){e.exports=this.wp.element}()},1:function(e,t){!function(){e.exports=this.wp.i18n}()},128:function(e,t){!function(){e.exports=this.wc.number}()},32:function(e,t,r){"use strict";function o(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}r.d(t,"a",(function(){return o}))},7:function(e,t,r){"use strict";function o(e,t){for(var r=0;r<t.length;r++){var o=t[r];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),Object.defineProperty(e,o.key,o)}}function n(e,t,r){return t&&o(e.prototype,t),r&&o(e,r),e}r.d(t,"a",(function(){return n}))},717:function(e,t,r){"use strict";r.r(t),r.d(t,"default",(function(){return l})),r.d(t,"getCurrencyData",(function(){return p}));var o=r(32),n=r(8),i=r(7),a=r(0),s=r(1),c=r(128);function u(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);t&&(o=o.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,o)}return r}var l=function(){function e(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:null;Object(n.a)(this,e),this.code||this.setCurrency(t)}return Object(i.a)(e,[{key:"setCurrency",value:function(e){var t=function(e){for(var t=1;t<arguments.length;t++){var r=null!=arguments[t]?arguments[t]:{};t%2?u(Object(r),!0).forEach((function(t){Object(o.a)(e,t,r[t])})):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(r)):u(Object(r)).forEach((function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(r,t))}))}return e}({},{code:"USD",symbol:"$",symbolPosition:"left",thousandSeparator:",",decimalSeparator:".",precision:2},{},e);this.code=t.code.toString(),this.symbol=t.symbol.toString(),this.symbolPosition=t.symbolPosition.toString(),this.decimalSeparator=t.decimalSeparator.toString(),this.priceFormat=t.priceFormat?t.priceFormat.toString():this.getPriceFormat(t),this.thousandSeparator=t.thousandSeparator.toString();var r=parseInt(t.precision,10);this.precision=r}},{key:"getPriceFormat",value:function(e){switch(e.symbolPosition){case"left":return"%1$s%2$s";case"right":return"%2$s%1$s";case"left_space":return"%1$s&nbsp;%2$s";case"right_space":return"%2$s&nbsp;%1$s"}return"%1$s%2$s"}},{key:"formatCurrency",value:function(e){var t=Object(c.numberFormat)(this,e);return""===t?t:Object(s.sprintf)(this.priceFormat,this.symbol,t)}},{key:"formatDecimal",value:function(e){return"number"!=typeof e&&(e=parseFloat(e)),Number.isNaN(e)?0:Math.round(e*Math.pow(10,this.precision))/Math.pow(10,this.precision)}},{key:"formatDecimalString",value:function(e){return"number"!=typeof e&&(e=parseFloat(e)),Number.isNaN(e)?"":e.toFixed(this.precision)}},{key:"render",value:function(e){return"number"!=typeof e&&(e=parseFloat(e)),e<0?Object(a.createElement)("span",{className:"is-negative"},this.formatCurrency(e)):this.formatCurrency(e)}}]),e}();function p(){return{US:{code:"USD",symbol:"$",symbolPosition:"left",thousandSeparator:",",decimalSeparator:".",precision:2},EU:{code:"EUR",symbol:"€",symbolPosition:"left",thousandSeparator:".",decimalSeparator:",",precision:2},IN:{code:"INR",symbol:"₹",symbolPosition:"left",thousandSeparator:",",decimalSeparator:".",precision:2},GB:{code:"GBP",symbol:"£",symbolPosition:"left",thousandSeparator:",",decimalSeparator:".",precision:2},BR:{code:"BRL",symbol:"R$",symbolPosition:"left",thousandSeparator:".",decimalSeparator:",",precision:2},VN:{code:"VND",symbol:"₫",symbolPosition:"right",thousandSeparator:".",decimalSeparator:",",precision:1},ID:{code:"IDR",symbol:"Rp",symbolPosition:"left",thousandSeparator:".",decimalSeparator:",",precision:0},BD:{code:"BDT",symbol:"৳",symbolPosition:"left",thousandSeparator:",",decimalSeparator:".",precision:0},PK:{code:"PKR",symbol:"₨",symbolPosition:"left",thousandSeparator:",",decimalSeparator:".",precision:2},RU:{code:"RUB",symbol:"₽",symbolPosition:"right",thousandSeparator:" ",decimalSeparator:",",precision:2},TR:{code:"TRY",symbol:"₺",symbolPosition:"left",thousandSeparator:".",decimalSeparator:",",precision:2},MX:{code:"MXN",symbol:"$",symbolPosition:"left",thousandSeparator:",",decimalSeparator:".",precision:2},CA:{code:"CAD",symbol:"$",symbolPosition:"left",thousandSeparator:",",decimalSeparator:".",precision:2}}}},8:function(e,t,r){"use strict";function o(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}r.d(t,"a",(function(){return o}))}});
\ No newline at end of file
.woocommerce-report-table.has-compare .woocommerce-card__action .woocommerce-table__compare,.woocommerce-report-table.has-search .woocommerce-card__action .woocommerce-table__compare{-ms-grid-row-align:center;align-self:center;-ms-grid-column:1;grid-column-start:1;-ms-grid-column-span:1;grid-column-end:2}.woocommerce-report-table.has-compare .woocommerce-card__action .woocommerce-search,.woocommerce-report-table.has-search .woocommerce-card__action .woocommerce-search{-ms-grid-row-align:center;align-self:center;-ms-grid-column:2;grid-column-start:2;-ms-grid-column-span:1;grid-column-end:3}.woocommerce-report-table.has-compare .woocommerce-card__action .woocommerce-table__download-button,.woocommerce-report-table.has-search .woocommerce-card__action .woocommerce-table__download-button{-ms-grid-row-align:center;align-self:center;-ms-grid-column:3;grid-column-start:3;-ms-grid-column-span:1;grid-column-end:4}
\ No newline at end of file
.woocommerce-report-table.has-compare .woocommerce-card__action .woocommerce-table__compare,.woocommerce-report-table.has-search .woocommerce-card__action .woocommerce-table__compare{-ms-grid-row-align:center;align-self:center;-ms-grid-column:1;grid-column-start:1;-ms-grid-column-span:1;grid-column-end:2}.woocommerce-report-table.has-compare .woocommerce-card__action .woocommerce-search,.woocommerce-report-table.has-search .woocommerce-card__action .woocommerce-search{-ms-grid-row-align:center;align-self:center;-ms-grid-column:2;grid-column-start:2;-ms-grid-column-span:1;grid-column-end:3}.woocommerce-report-table.has-compare .woocommerce-card__action .woocommerce-table__download-button,.woocommerce-report-table.has-search .woocommerce-card__action .woocommerce-table__download-button{-ms-grid-row-align:center;align-self:center;-ms-grid-column:3;grid-column-start:3;-ms-grid-column-span:1;grid-column-end:4}
\ No newline at end of file
this.wc=this.wc||{},this.wc.number=function(t){var e={};function r(n){if(e[n])return e[n].exports;var i=e[n]={i:n,l:!1,exports:{}};return t[n].call(i.exports,i,i.exports,r),i.l=!0,i.exports}return r.m=t,r.c=e,r.d=function(t,e,n){r.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:n})},r.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},r.t=function(t,e){if(1&e&&(t=r(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var i in t)r.d(n,i,function(e){return t[e]}.bind(null,i));return n},r.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return r.d(e,"a",e),e},r.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},r.p="",r(r.s=719)}({32:function(t,e,r){"use strict";function n(t,e,r){return e in t?Object.defineProperty(t,e,{value:r,enumerable:!0,configurable:!0,writable:!0}):t[e]=r,t}r.d(e,"a",(function(){return n}))},40:function(t,e,r){"use strict";function n(t,e){return function(t){if(Array.isArray(t))return t}(t)||function(t,e){if(Symbol.iterator in Object(t)||"[object Arguments]"===Object.prototype.toString.call(t)){var r=[],n=!0,i=!1,o=void 0;try{for(var u,c=t[Symbol.iterator]();!(n=(u=c.next()).done)&&(r.push(u.value),!e||r.length!==e);n=!0);}catch(t){i=!0,o=t}finally{try{n||null==c.return||c.return()}finally{if(i)throw o}}return r}}(t,e)||function(){throw new TypeError("Invalid attempt to destructure non-iterable instance")}()}r.d(e,"a",(function(){return n}))},719:function(t,e,r){"use strict";r.r(e),r.d(e,"numberFormat",(function(){return c})),r.d(e,"formatValue",(function(){return a})),r.d(e,"calculateDelta",(function(){return l}));var n=r(32),i=r(40);function o(t,e){var r=Object.keys(t);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(t);e&&(n=n.filter((function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),r.push.apply(r,n)}return r}var u=r(720);function c(t,e){var r=t.precision,n=void 0===r?null:r,o=t.decimalSeparator,c=void 0===o?".":o,a=t.thousandSeparator,l=void 0===a?",":a;if("number"!=typeof e&&(e=parseFloat(e)),isNaN(e))return"";if(n=parseInt(n),isNaN(n)){var f=e.toString().split("."),s=Object(i.a)(f,2)[1];n=s?s.length:0}return u(e,n,c,l)}function a(t,e,r){if(!Number.isFinite(r))return null;switch(e){case"average":return Math.round(r);case"number":return c(function(t){for(var e=1;e<arguments.length;e++){var r=null!=arguments[e]?arguments[e]:{};e%2?o(Object(r),!0).forEach((function(e){Object(n.a)(t,e,r[e])})):Object.getOwnPropertyDescriptors?Object.defineProperties(t,Object.getOwnPropertyDescriptors(r)):o(Object(r)).forEach((function(e){Object.defineProperty(t,e,Object.getOwnPropertyDescriptor(r,e))}))}return t}({},t,{precision:null}),r)}}function l(t,e){return Number.isFinite(t)&&Number.isFinite(e)?0===e?0:Math.round((t-e)/e*100):null}},720:function(t,e,r){"use strict";t.exports=function(t,e,r,n){t=(t+"").replace(/[^0-9+\-Ee.]/g,"");var i=isFinite(+t)?+t:0,o=isFinite(+e)?Math.abs(e):0,u=void 0===n?",":n,c=void 0===r?".":r,a="";return(a=(o?function(t,e){if(-1===(""+t).indexOf("e"))return+(Math.round(t+"e+"+e)+"e-"+e);var r=(""+t).split("e"),n="";return+r[1]+e>0&&(n="+"),(+(Math.round(+r[0]+"e"+n+(+r[1]+e))+"e-"+e)).toFixed(e)}(i,o).toString():""+Math.round(i)).split("."))[0].length>3&&(a[0]=a[0].replace(/\B(?=(?:\d{3})+(?!\d))/g,u)),(a[1]||"").length<o&&(a[1]=a[1]||"",a[1]+=new Array(o-a[1].length+1).join("0")),a.join(c)}}});
\ No newline at end of file
this.wc=this.wc||{},this.wc.onboardingProductNotice=function(t){var e={};function n(r){if(e[r])return e[r].exports;var o=e[r]={i:r,l:!1,exports:{}};return t[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=t,n.c=e,n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:r})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var o in t)n.d(r,o,function(e){return t[e]}.bind(null,o));return r},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=722)}({1:function(t,e){!function(){t.exports=this.wp.i18n}()},16:function(t,e){t.exports=function(t,e,n){return e in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}},22:function(t,e,n){"use strict";var r={};n.r(r),n.d(r,"ADMIN_URL",(function(){return l})),n.d(r,"COUNTRIES",(function(){return p})),n.d(r,"CURRENCY",(function(){return y})),n.d(r,"LOCALE",(function(){return b})),n.d(r,"ORDER_STATUSES",(function(){return S})),n.d(r,"SITE_TITLE",(function(){return m})),n.d(r,"WC_ASSET_URL",(function(){return O})),n.d(r,"DEFAULT_DATE_RANGE",(function(){return g})),n.d(r,"getSetting",(function(){return _})),n.d(r,"setSetting",(function(){return v})),n.d(r,"getAdminLink",(function(){return E}));var o=n(66),c=n(16),u=n.n(c),i=n(61);function f(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(t);e&&(r=r.filter((function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),n.push.apply(n,r)}return n}function a(t){for(var e=1;e<arguments.length;e++){var n=null!=arguments[e]?arguments[e]:{};e%2?f(Object(n),!0).forEach((function(e){u()(t,e,n[e])})):Object.getOwnPropertyDescriptors?Object.defineProperties(t,Object.getOwnPropertyDescriptors(n)):f(Object(n)).forEach((function(e){Object.defineProperty(t,e,Object.getOwnPropertyDescriptor(n,e))}))}return t}var s={adminUrl:"",countries:[],currency:{code:"USD",precision:2,symbol:"$",symbolPosition:"left",decimalSeparator:".",priceFormat:"%1$s%2$s",thousandSeparator:","},defaultDateRange:"period=month&compare=previous_year",locale:{siteLocale:"en_US",userLocale:"en_US",weekdaysShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"]},orderStatuses:[],siteTitle:"",wcAssetUrl:""},d=a({},s,{},"object"===("undefined"==typeof wcSettings?"undefined":n.n(i)()(wcSettings))?wcSettings:{});d.currency=a({},s.currency,{},d.currency),d.locale=a({},s.locale,{},d.locale);var l=d.adminUrl,p=d.countries,y=d.currency,b=d.locale,S=d.orderStatuses,m=d.siteTitle,O=d.wcAssetUrl,g=d.defaultDateRange;function _(t){var e=arguments.length>1&&void 0!==arguments[1]&&arguments[1];return(arguments.length>2&&void 0!==arguments[2]?arguments[2]:function(t){return t})(d.hasOwnProperty(t)?d[t]:e,e)}function v(t,e){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:function(t){return t};d[t]=n(e)}function E(t){return(l||"")+t}n.d(e,"a",(function(){return j})),n.d(e,"b",(function(){return w})),n.d(e,"c",(function(){return h})),n.d(e,"d",(function(){return R})),n.d(e,"e",(function(){return U})),n.d(e,"g",(function(){return A})),n.d(e,"h",(function(){return D})),n.d(e,"f",(function(){return L}));var T=o&&void 0!==o.getSetting?o:r,j=T.ADMIN_URL,w=(T.COUNTRIES,T.CURRENCY),h=T.LOCALE,R=T.ORDER_STATUSES,U=(T.SITE_TITLE,T.WC_ASSET_URL),A=(T.DEFAULT_DATE_RANGE,T.getSetting),D=T.setSetting,L=T.getAdminLink||E},28:function(t,e){!function(){t.exports=this.wp.data}()},61:function(t,e){function n(t){return(n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function r(e){return"function"==typeof Symbol&&"symbol"===n(Symbol.iterator)?t.exports=r=function(t){return n(t)}:t.exports=r=function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":n(t)},r(e)}t.exports=r},66:function(t,e){!function(){t.exports=this.wc.wcSettings}()},722:function(t,e,n){"use strict";n.r(e);var r=n(1),o=n(28),c=n(76),u=n(22);Object(c.a)((function(){Object(o.dispatch)("core/notices").createSuccessNotice(Object(r.__)("You created your first product!","woocommerce-admin"),{id:"WOOCOMMERCE_ONBOARDING_PRODUCT_NOTICE",actions:[{url:Object(u.f)("admin.php?page=wc-admin"),label:Object(r.__)("Continue setup.","woocommerce-admin")}]})}))},76:function(t,e,n){"use strict";e.a=function(t){if("complete"===document.readyState||"interactive"===document.readyState)return t();document.addEventListener("DOMContentLoaded",t)}}});
\ No newline at end of file
this.wc=this.wc||{},this.wc.onboardingTaxNotice=function(t){var e={};function n(r){if(e[r])return e[r].exports;var o=e[r]={i:r,l:!1,exports:{}};return t[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}return n.m=t,n.c=e,n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:r})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var o in t)n.d(r,o,function(e){return t[e]}.bind(null,o));return r},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=723)}({1:function(t,e){!function(){t.exports=this.wp.i18n}()},16:function(t,e){t.exports=function(t,e,n){return e in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n,t}},22:function(t,e,n){"use strict";var r={};n.r(r),n.d(r,"ADMIN_URL",(function(){return d})),n.d(r,"COUNTRIES",(function(){return p})),n.d(r,"CURRENCY",(function(){return y})),n.d(r,"LOCALE",(function(){return b})),n.d(r,"ORDER_STATUSES",(function(){return m})),n.d(r,"SITE_TITLE",(function(){return S})),n.d(r,"WC_ASSET_URL",(function(){return O})),n.d(r,"DEFAULT_DATE_RANGE",(function(){return g})),n.d(r,"getSetting",(function(){return v})),n.d(r,"setSetting",(function(){return _})),n.d(r,"getAdminLink",(function(){return E}));var o=n(66),c=n(16),u=n.n(c),i=n(61);function f(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(t);e&&(r=r.filter((function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable}))),n.push.apply(n,r)}return n}function a(t){for(var e=1;e<arguments.length;e++){var n=null!=arguments[e]?arguments[e]:{};e%2?f(Object(n),!0).forEach((function(e){u()(t,e,n[e])})):Object.getOwnPropertyDescriptors?Object.defineProperties(t,Object.getOwnPropertyDescriptors(n)):f(Object(n)).forEach((function(e){Object.defineProperty(t,e,Object.getOwnPropertyDescriptor(n,e))}))}return t}var s={adminUrl:"",countries:[],currency:{code:"USD",precision:2,symbol:"$",symbolPosition:"left",decimalSeparator:".",priceFormat:"%1$s%2$s",thousandSeparator:","},defaultDateRange:"period=month&compare=previous_year",locale:{siteLocale:"en_US",userLocale:"en_US",weekdaysShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"]},orderStatuses:[],siteTitle:"",wcAssetUrl:""},l=a({},s,{},"object"===("undefined"==typeof wcSettings?"undefined":n.n(i)()(wcSettings))?wcSettings:{});l.currency=a({},s.currency,{},l.currency),l.locale=a({},s.locale,{},l.locale);var d=l.adminUrl,p=l.countries,y=l.currency,b=l.locale,m=l.orderStatuses,S=l.siteTitle,O=l.wcAssetUrl,g=l.defaultDateRange;function v(t){var e=arguments.length>1&&void 0!==arguments[1]&&arguments[1];return(arguments.length>2&&void 0!==arguments[2]?arguments[2]:function(t){return t})(l.hasOwnProperty(t)?l[t]:e,e)}function _(t,e){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:function(t){return t};l[t]=n(e)}function E(t){return(d||"")+t}n.d(e,"a",(function(){return T})),n.d(e,"b",(function(){return j})),n.d(e,"c",(function(){return h})),n.d(e,"d",(function(){return A})),n.d(e,"e",(function(){return L})),n.d(e,"g",(function(){return R})),n.d(e,"h",(function(){return U})),n.d(e,"f",(function(){return P}));var w=o&&void 0!==o.getSetting?o:r,T=w.ADMIN_URL,j=(w.COUNTRIES,w.CURRENCY),h=w.LOCALE,A=w.ORDER_STATUSES,L=(w.SITE_TITLE,w.WC_ASSET_URL),R=(w.DEFAULT_DATE_RANGE,w.getSetting),U=w.setSetting,P=w.getAdminLink||E},28:function(t,e){!function(){t.exports=this.wp.data}()},61:function(t,e){function n(t){return(n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function r(e){return"function"==typeof Symbol&&"symbol"===n(Symbol.iterator)?t.exports=r=function(t){return n(t)}:t.exports=r=function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":n(t)},r(e)}t.exports=r},66:function(t,e){!function(){t.exports=this.wc.wcSettings}()},723:function(t,e,n){"use strict";n.r(e);var r=n(1),o=n(28),c=n(76),u=n(22),i=function(){var t=document.querySelector(".woocommerce-save-button");t.classList.contains("is-clicked")||(t.classList.add("is-clicked"),function t(){return null!==document.querySelector(".blockUI.blockOverlay")?new Promise((function(t){requestAnimationFrame(t)})).then((function(){return t()})):Promise.resolve(!0)}().then((function(){return Object(o.dispatch)("core/notices").createSuccessNotice(Object(r.__)("You've added your first tax rate!","woocommerce-admin"),{id:"WOOCOMMERCE_ONBOARDING_TAX_NOTICE",actions:[{url:Object(u.f)("admin.php?page=wc-admin"),label:Object(r.__)("Continue setup.","woocommerce-admin")}]})})))};Object(c.a)((function(){var t=document.querySelector(".woocommerce-save-button");t&&t.addEventListener("click",i)}))},76:function(t,e,n){"use strict";e.a=function(t){if("complete"===document.readyState||"interactive"===document.readyState)return t();document.addEventListener("DOMContentLoaded",t)}}});
\ No newline at end of file
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400"> <path d="M226.153073,88.3099993 L355.380187,301.446227 C363.970299,315.614028 359.448689,334.062961 345.280888,342.653073 C340.591108,345.496544 335.21158,347 329.727115,347 L71.2728854,347 C54.7043429,347 41.2728854,333.568542 41.2728854,317 C41.2728854,311.515534 42.7763415,306.136007 45.6198127,301.446227 L174.846927,88.3099993 C183.437039,74.1421985 201.885972,69.6205881 216.053773,78.2106999 C220.184157,80.7150022 223.64877,84.1796157 226.153073,88.3099993 Z M184.370159,153 L186.899684,255.024156 L213.459691,255.024156 L215.989216,153 L184.370159,153 Z M200.179688,307.722584 C209.770801,307.722584 217.359375,300.450201 217.359375,291.175278 C217.359375,281.900355 209.770801,274.627972 200.179688,274.627972 C190.588574,274.627972 183,281.900355 183,291.175278 C183,300.450201 190.588574,307.722584 200.179688,307.722584 Z" id="Combined-Shape" stroke="#979797" fill="#95588A" fill-rule="nonzero"></path></svg>
\ No newline at end of file
<?php
/**
* Core Functions
*
* Holds core functions for wc-admin.
*
* @package WC_Admin\Functions
*/
use \Automattic\WooCommerce\Admin\Loader;
/**
* Format a number using the decimal and thousands separator settings in WooCommerce.
*
* @param mixed $number Number to be formatted.
* @return string
*/
function wc_admin_number_format( $number ) {
$currency_settings = Loader::get_currency_settings();
return number_format(
$number,
0,
$currency_settings['decimalSeparator'],
$currency_settings['thousandSeparator']
);
}
/**
* Retrieves a URL to relative path inside WooCommerce admin with
* the provided query parameters.
*
* @param string $path Relative path of the desired page.
* @param array $query Query parameters to append to the path.
*
* @return string Fully qualified URL pointing to the desired path.
*/
function wc_admin_url( $path = null, $query = array() ) {
if ( ! empty( $query ) ) {
$query_string = http_build_query( $query );
$path = $path ? '&path=' . $path . '&' . $query_string : '';
}
return admin_url( 'admin.php?page=wc-admin' . $path, dirname( __FILE__ ) );
}
/**
* Record an event using Tracks.
*
* @internal WooCommerce core only includes Tracks in admin, not the REST API, so we need to include it.
* @param string $event_name Event name for tracks.
* @param array $properties Properties to pass along with event.
*/
function wc_admin_record_tracks_event( $event_name, $properties = array() ) {
if ( ! class_exists( 'WC_Tracks' ) ) {
if ( ! defined( 'WC_ABSPATH' ) || ! file_exists( WC_ABSPATH . 'includes/tracks/class-wc-tracks.php' ) ) {
return;
}
include_once WC_ABSPATH . 'includes/tracks/class-wc-tracks.php';
include_once WC_ABSPATH . 'includes/tracks/class-wc-tracks-event.php';
include_once WC_ABSPATH . 'includes/tracks/class-wc-tracks-client.php';
include_once WC_ABSPATH . 'includes/tracks/class-wc-tracks-footer-pixel.php';
include_once WC_ABSPATH . 'includes/tracks/class-wc-site-tracking.php';
}
WC_Tracks::record_event( $event_name, $properties );
}
<?php
/**
* Admin report export download
*
* @package WooCommerce/Admin/Templates/Emails/HTML
*/
defined( 'ABSPATH' ) || exit;
/*
* @hooked WC_Emails::email_header() Output the email header
*/
do_action( 'woocommerce_email_header', $email_heading, $email );
?>
<a href="<?php echo esc_url( $download_url ); ?>">
<?php echo esc_html( sprintf( __( 'Download your %s Report', 'woocommerce-admin' ), $report_name ) ); ?>
</a>
<?php
/*
* @hooked WC_Emails::email_footer() Output the email footer
*/
do_action( 'woocommerce_email_footer', $email );
<?php
/**
* Admin report export download email (plain text)
*
* @package WooCommerce/Admin/Templates/Emails/HTML
*/
defined( 'ABSPATH' ) || exit;
echo "=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n";
echo esc_html( wp_strip_all_tags( $email_heading ) );
echo "\n=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n\n";
/* translators: %1$s: report name, %2$s: download URL */
echo sprintf( __( 'Download your %1$s Report: %2$s', 'woocommerce-admin' ), $report_name, $download_url );
echo "\n\n----------------------------------------\n\n";
echo wp_kses_post( apply_filters( 'woocommerce_email_footer_text', get_option( 'woocommerce_email_footer_text' ) ) );
<?php
// WARNING: Do not directly edit this file.
// This file is auto-generated as part of the build process and things may break.
if ( ! function_exists( 'wc_admin_get_feature_config' ) ) {
function wc_admin_get_feature_config() {
return array(
'activity-panels' => true,
'analytics' => true,
'analytics-dashboard' => true,
'analytics-dashboard/customizable' => true,
'devdocs' => false,
'onboarding' => true,
'store-alerts' => true,
);
}
}
<?php
/**
* Convenience functions for PageController.
*
* @package Woocommerce Admin
*/
use Automattic\WooCommerce\Admin\PageController;
/**
* Connect an existing page to WooCommerce Admin.
* Passthrough to PageController::connect_page().
*
* @param array $options Options for PageController::connect_page().
*/
function wc_admin_connect_page( $options ) {
$controller = PageController::get_instance();
$controller->connect_page( $options );
}
/**
* Register JS-powered WooCommerce Admin Page.
* Passthrough to PageController::register_page().
*
* @param array $options Options for PageController::register_page().
*/
function wc_admin_register_page( $options ) {
$controller = PageController::get_instance();
$controller->register_page( $options );
}
/**
* Is this page connected to WooCommerce Admin?
* Passthrough to PageController::is_connected_page().
*
* @return boolean True if the page is connected to WooCommerce Admin.
*/
function wc_admin_is_connected_page() {
$controller = PageController::get_instance();
return $controller->is_connected_page();
}
/**
* Is this a WooCommerce Admin Page?
* Passthrough to PageController::is_registered_page().
*
* @return boolean True if the page is a WooCommerce Admin page.
*/
function wc_admin_is_registered_page() {
$controller = PageController::get_instance();
return $controller->is_registered_page();
}
/**
* Get breadcrumbs for WooCommerce Admin Page navigation.
* Passthrough to PageController::get_breadcrumbs().
*
* @return array Navigation pieces (breadcrumbs).
*/
function wc_admin_get_breadcrumbs() {
$controller = PageController::get_instance();
return $controller->get_breadcrumbs();
}
<?php
/**
* WooCommerce Admin Updates
*
* Functions for updating data, used by the background updater.
*
* @package WooCommerce/Admin
*/
use \Automattic\WooCommerce\Admin\Install as Installer;
/**
* Update order stats `status` index length.
* See: https://github.com/woocommerce/woocommerce-admin/issues/2969.
*/
function wc_admin_update_0201_order_status_index() {
global $wpdb;
// Max DB index length. See wp_get_db_schema().
$max_index_length = 191;
$index = $wpdb->get_row( "SHOW INDEX FROM {$wpdb->prefix}wc_order_stats WHERE key_name = 'status'" );
if ( property_exists( $index, 'Sub_part' ) ) {
// The index was created with the right length. Time to bail.
if ( $max_index_length === $index->Sub_part ) { // phpcs:ignore WordPress.NamingConventions.ValidVariableName
return;
}
// We need to drop the index so it can be recreated.
$wpdb->query( "DROP INDEX `status` ON {$wpdb->prefix}wc_order_stats" );
}
// Recreate the status index with a max length.
$wpdb->query( $wpdb->prepare( "ALTER TABLE {$wpdb->prefix}wc_order_stats ADD INDEX status (status(%d))", $max_index_length ) );
}
/**
* Update DB Version.
*/
function wc_admin_update_0201_db_version() {
Installer::update_db_version( '0.20.1' );
}
/**
* Rename "gross_total" to "total_sales".
* See: https://github.com/woocommerce/woocommerce-admin/issues/3175
*/
function wc_admin_update_0230_rename_gross_total() {
global $wpdb;
// We first need to drop the new `total_sales` column, since dbDelta() will have created it.
$wpdb->query( "ALTER TABLE {$wpdb->prefix}wc_order_stats DROP COLUMN `total_sales`" );
// Then we can rename the existing `gross_total` column.
$wpdb->query( "ALTER TABLE {$wpdb->prefix}wc_order_stats CHANGE COLUMN `gross_total` `total_sales` double DEFAULT 0 NOT NULL" );
}
/**
* Update DB Version.
*/
function wc_admin_update_0230_db_version() {
Installer::update_db_version( '0.23.0' );
}
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
<?php
/**
* REST API Coupons Controller
*
* Handles requests to /coupons/*
*
* @package WooCommerce Admin/API
*/
namespace Automattic\WooCommerce\Admin\API;
defined( 'ABSPATH' ) || exit;
/**
* Coupons controller.
*
* @package WooCommerce Admin/API
* @extends WC_REST_Coupons_Controller
*/
class Coupons extends \WC_REST_Coupons_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc-analytics';
/**
* Get the query params for collections.
*
* @return array
*/
public function get_collection_params() {
$params = parent::get_collection_params();
$params['search'] = array(
'description' => __( 'Limit results to coupons with codes matching a given string.', 'woocommerce-admin' ),
'type' => 'string',
'validate_callback' => 'rest_validate_request_arg',
);
return $params;
}
/**
* Add coupon code searching to the WC API.
*
* @param WP_REST_Request $request Request data.
* @return array
*/
protected function prepare_objects_query( $request ) {
$args = parent::prepare_objects_query( $request );
if ( ! empty( $request['search'] ) ) {
$args['search'] = $request['search'];
$args['s'] = false;
}
return $args;
}
/**
* Get a collection of posts and add the code search option to WP_Query.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|WP_REST_Response
*/
public function get_items( $request ) {
add_filter( 'posts_where', array( __CLASS__, 'add_wp_query_search_code_filter' ), 10, 2 );
$response = parent::get_items( $request );
remove_filter( 'posts_where', array( __CLASS__, 'add_wp_query_search_code_filter' ), 10 );
return $response;
}
/**
* Add code searching to the WP Query
*
* @param string $where Where clause used to search posts.
* @param object $wp_query WP_Query object.
* @return string
*/
public static function add_wp_query_search_code_filter( $where, $wp_query ) {
global $wpdb;
$search = $wp_query->get( 'search' );
if ( $search ) {
$search = $wpdb->esc_like( $search );
$search = "'%" . $search . "%'";
$where .= ' AND ' . $wpdb->posts . '.post_title LIKE ' . $search;
}
return $where;
}
}
<?php
/**
* REST API Customers Controller
*
* Handles requests to /customers/*
*
* @package WooCommerce Admin/API
*/
namespace Automattic\WooCommerce\Admin\API;
defined( 'ABSPATH' ) || exit;
/**
* Customers controller.
*
* @package WooCommerce Admin/API
* @extends \Automattic\WooCommerce\Admin\API\Reports\Customers\Controller
*/
class Customers extends \Automattic\WooCommerce\Admin\API\Reports\Customers\Controller {
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'customers';
/**
* Register the routes for customers.
*/
public function register_routes() {
register_rest_route(
$this->namespace,
'/' . $this->rest_base,
array(
array(
'methods' => \WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => $this->get_collection_params(),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/(?P<id>[\d-]+)',
array(
'args' => array(
'id' => array(
'description' => __( 'Unique ID for the resource.', 'woocommerce-admin' ),
'type' => 'integer',
),
),
array(
'methods' => \WP_REST_Server::READABLE,
'callback' => array( $this, 'get_item' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => $this->get_collection_params(),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
}
/**
* Maps query arguments from the REST request.
*
* @param array $request Request array.
* @return array
*/
protected function prepare_reports_query( $request ) {
$args = parent::prepare_reports_query( $request );
$args['customers'] = $request['include'];
return $args;
}
/**
* Get the query params for collections.
*
* @return array
*/
public function get_collection_params() {
$params = parent::get_collection_params();
$params['include'] = $params['customers'];
unset( $params['customers'] );
return $params;
}
}
<?php
/**
* REST API Data Controller
*
* Handles requests to /data
*
* @package WooCommerce Admin/API
*/
namespace Automattic\WooCommerce\Admin\API;
defined( 'ABSPATH' ) || exit;
/**
* Data controller.
*
* @package WooCommerce Admin/API
* @extends WC_REST_Data_Controller
*/
class Data extends \WC_REST_Data_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc-analytics';
/**
* Return the list of data resources.
*
* @param WP_REST_Request $request Request data.
* @return WP_Error|WP_REST_Response
*/
public function get_items( $request ) {
$response = parent::get_items( $request );
$response->data[] = $this->prepare_response_for_collection(
$this->prepare_item_for_response(
(object) array(
'slug' => 'download-ips',
'description' => __( 'An endpoint used for searching download logs for a specific IP address.', 'woocommerce-admin' ),
),
$request
)
);
return $response;
}
}
<?php
/**
* REST API Data countries controller.
*
* Handles requests to the /data/countries endpoint.
*
* @package WooCommerce Admin/API
*/
namespace Automattic\WooCommerce\Admin\API;
defined( 'ABSPATH' ) || exit;
/**
* REST API Data countries controller class.
*
* @package WooCommerce Admin/API
* @extends WC_REST_Data_Countries_Controller
*/
class DataCountries extends \WC_REST_Data_Countries_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc-analytics';
}
<?php
/**
* REST API Data Download IP Controller
*
* Handles requests to /data/download-ips
*
* @package WooCommerce Admin/API
*/
namespace Automattic\WooCommerce\Admin\API;
defined( 'ABSPATH' ) || exit;
/**
* Data Download IP controller.
*
* @package WooCommerce Admin/API
* @extends WC_REST_Data_Controller
*/
class DataDownloadIPs extends \WC_REST_Data_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc-analytics';
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'data/download-ips';
/**
* Register routes.
*
* @since 3.5.0
*/
public function register_routes() {
register_rest_route(
$this->namespace,
'/' . $this->rest_base,
array(
array(
'methods' => \WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
}
/**
* Return the download IPs matching the passed parameters.
*
* @since 3.5.0
* @param WP_REST_Request $request Request data.
* @return WP_Error|WP_REST_Response
*/
public function get_items( $request ) {
global $wpdb;
if ( isset( $request['match'] ) ) {
$downloads = $wpdb->get_results(
$wpdb->prepare(
"SELECT DISTINCT( user_ip_address ) FROM {$wpdb->prefix}wc_download_log
WHERE user_ip_address LIKE %s
LIMIT 10",
$request['match'] . '%'
)
);
} else {
return new \WP_Error( 'woocommerce_rest_data_download_ips_invalid_request', __( 'Invalid request. Please pass the match parameter.', 'woocommerce-admin' ), array( 'status' => 400 ) );
}
$data = array();
if ( ! empty( $downloads ) ) {
foreach ( $downloads as $download ) {
$response = $this->prepare_item_for_response( $download, $request );
$data[] = $this->prepare_response_for_collection( $response );
}
}
return rest_ensure_response( $data );
}
/**
* Prepare the data object for response.
*
* @since 3.5.0
* @param object $item Data object.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response $response Response data.
*/
public function prepare_item_for_response( $item, $request ) {
$data = $this->add_additional_fields_to_object( $item, $request );
$data = $this->filter_response_by_context( $data, 'view' );
$response = rest_ensure_response( $data );
$response->add_links( $this->prepare_links( $item ) );
/**
* Filter the list returned from the API.
*
* @param WP_REST_Response $response The response object.
* @param array $item The original item.
* @param WP_REST_Request $request Request used to generate the response.
*/
return apply_filters( 'woocommerce_rest_prepare_data_download_ip', $response, $item, $request );
}
/**
* Prepare links for the request.
*
* @param object $item Data object.
* @return array Links for the given object.
*/
protected function prepare_links( $item ) {
$links = array(
'collection' => array(
'href' => rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ),
),
);
return $links;
}
/**
* Get the query params for collections.
*
* @return array
*/
public function get_collection_params() {
$params = array();
$params['context'] = $this->get_context_param( array( 'default' => 'view' ) );
$params['match'] = array(
'description' => __( 'A partial IP address can be passed and matching results will be returned.', 'woocommerce-admin' ),
'type' => 'string',
'validate_callback' => 'rest_validate_request_arg',
);
return $params;
}
/**
* Get the schema, conforming to JSON Schema.
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'data_download_ips',
'type' => 'object',
'properties' => array(
'user_ip_address' => array(
'type' => 'string',
'description' => __( 'IP address.', 'woocommerce-admin' ),
'context' => array( 'view' ),
'readonly' => true,
),
),
);
return $this->add_additional_fields_schema( $schema );
}
}
<?php
/**
* REST API bootstrap.
*
* @package WooCommerce Admin/Classes
*/
namespace Automattic\WooCommerce\Admin\API;
defined( 'ABSPATH' ) || exit;
use \Automattic\WooCommerce\Admin\Loader;
/**
* Init class.
*/
class Init {
/**
* Boostrap REST API.
*/
public function __construct() {
// Hook in data stores.
add_filter( 'woocommerce_data_stores', array( __CLASS__, 'add_data_stores' ) );
// REST API extensions init.
add_action( 'rest_api_init', array( $this, 'rest_api_init' ) );
// Add currency symbol to orders endpoint response.
add_filter( 'woocommerce_rest_prepare_shop_order_object', array( __CLASS__, 'add_currency_symbol_to_order_response' ) );
}
/**
* Init REST API.
*/
public function rest_api_init() {
$controllers = array(
'Automattic\WooCommerce\Admin\API\Notes',
'Automattic\WooCommerce\Admin\API\NoteActions',
'Automattic\WooCommerce\Admin\API\Coupons',
'Automattic\WooCommerce\Admin\API\Customers',
'Automattic\WooCommerce\Admin\API\Data',
'Automattic\WooCommerce\Admin\API\DataCountries',
'Automattic\WooCommerce\Admin\API\DataDownloadIPs',
'Automattic\WooCommerce\Admin\API\Leaderboards',
'Automattic\WooCommerce\Admin\API\Options',
'Automattic\WooCommerce\Admin\API\Orders',
'Automattic\WooCommerce\Admin\API\Products',
'Automattic\WooCommerce\Admin\API\ProductCategories',
'Automattic\WooCommerce\Admin\API\ProductVariations',
'Automattic\WooCommerce\Admin\API\ProductReviews',
'Automattic\WooCommerce\Admin\API\ProductVariations',
'Automattic\WooCommerce\Admin\API\Reports\Controller',
'Automattic\WooCommerce\Admin\API\SettingOptions',
'Automattic\WooCommerce\Admin\API\Reports\Import\Controller',
'Automattic\WooCommerce\Admin\API\Reports\Export\Controller',
'Automattic\WooCommerce\Admin\API\Reports\Products\Controller',
'Automattic\WooCommerce\Admin\API\Reports\Variations\Controller',
'Automattic\WooCommerce\Admin\API\Reports\Products\Stats\Controller',
'Automattic\WooCommerce\Admin\API\Reports\Revenue\Stats\Controller',
'Automattic\WooCommerce\Admin\API\Reports\Orders\Controller',
'Automattic\WooCommerce\Admin\API\Reports\Orders\Stats\Controller',
'Automattic\WooCommerce\Admin\API\Reports\Categories\Controller',
'Automattic\WooCommerce\Admin\API\Reports\Taxes\Controller',
'Automattic\WooCommerce\Admin\API\Reports\Taxes\Stats\Controller',
'Automattic\WooCommerce\Admin\API\Reports\Coupons\Controller',
'Automattic\WooCommerce\Admin\API\Reports\Coupons\Stats\Controller',
'Automattic\WooCommerce\Admin\API\Reports\Stock\Controller',
'Automattic\WooCommerce\Admin\API\Reports\Stock\Stats\Controller',
'Automattic\WooCommerce\Admin\API\Reports\Downloads\Controller',
'Automattic\WooCommerce\Admin\API\Reports\Downloads\Stats\Controller',
'Automattic\WooCommerce\Admin\API\Reports\Customers\Controller',
'Automattic\WooCommerce\Admin\API\Reports\Customers\Stats\Controller',
'Automattic\WooCommerce\Admin\API\Taxes',
'Automattic\WooCommerce\Admin\API\Themes',
);
if ( Loader::is_onboarding_enabled() ) {
$controllers = array_merge(
$controllers,
array(
'Automattic\WooCommerce\Admin\API\OnboardingProfile',
'Automattic\WooCommerce\Admin\API\OnboardingPlugins',
'Automattic\WooCommerce\Admin\API\OnboardingTasks',
)
);
}
// The performance indicators controller must be registered last, after other /stats endpoints have been registered.
$controllers[] = 'Automattic\WooCommerce\Admin\API\Reports\PerformanceIndicators\Controller';
$controllers = apply_filters( 'woocommerce_admin_rest_controllers', $controllers );
foreach ( $controllers as $controller ) {
$this->$controller = new $controller();
$this->$controller->register_routes();
}
}
/**
* Adds data stores.
*
* @param array $data_stores List of data stores.
* @return array
*/
public static function add_data_stores( $data_stores ) {
return array_merge(
$data_stores,
array(
'report-revenue-stats' => 'Automattic\WooCommerce\Admin\API\Reports\Orders\Stats\DataStore',
'report-orders' => 'Automattic\WooCommerce\Admin\API\Reports\Orders\DataStore',
'report-orders-stats' => 'Automattic\WooCommerce\Admin\API\Reports\Orders\Stats\DataStore',
'report-products' => 'Automattic\WooCommerce\Admin\API\Reports\Products\DataStore',
'report-variations' => 'Automattic\WooCommerce\Admin\API\Reports\Variations\DataStore',
'report-products-stats' => 'Automattic\WooCommerce\Admin\API\Reports\Products\Stats\DataStore',
'report-categories' => 'Automattic\WooCommerce\Admin\API\Reports\Categories\DataStore',
'report-taxes' => 'Automattic\WooCommerce\Admin\API\Reports\Taxes\DataStore',
'report-taxes-stats' => 'Automattic\WooCommerce\Admin\API\Reports\Taxes\Stats\DataStore',
'report-coupons' => 'Automattic\WooCommerce\Admin\API\Reports\Coupons\DataStore',
'report-coupons-stats' => 'Automattic\WooCommerce\Admin\API\Reports\Coupons\Stats\DataStore',
'report-downloads' => 'Automattic\WooCommerce\Admin\API\Reports\Downloads\DataStore',
'report-downloads-stats' => 'Automattic\WooCommerce\Admin\API\Reports\Downloads\Stats\DataStore',
'admin-note' => 'Automattic\WooCommerce\Admin\Notes\DataStore',
'report-customers' => 'Automattic\WooCommerce\Admin\API\Reports\Customers\DataStore',
'report-customers-stats' => 'Automattic\WooCommerce\Admin\API\Reports\Customers\Stats\DataStore',
'report-stock-stats' => 'Automattic\WooCommerce\Admin\API\Reports\Stock\Stats\DataStore',
)
);
}
/**
* Add the currency symbol (in addition to currency code) to each Order
* object in REST API responses. For use in formatCurrency().
*
* @param {WP_REST_Response} $response REST response object.
* @returns {WP_REST_Response}
*/
public static function add_currency_symbol_to_order_response( $response ) {
$response_data = $response->get_data();
$currency_code = $response_data['currency'];
$currency_symbol = get_woocommerce_currency_symbol( $currency_code );
$response_data['currency_symbol'] = html_entity_decode( $currency_symbol );
$response->set_data( $response_data );
return $response;
}
}
<?php
/**
* REST API Admin Note Action controller
*
* Handles requests to the admin note action endpoint.
*
* @package WooCommerce Admin/API
*/
namespace Automattic\WooCommerce\Admin\API;
defined( 'ABSPATH' ) || exit;
use \Automattic\WooCommerce\Admin\Notes\WC_Admin_Notes;
/**
* REST API Admin Note Action controller class.
*
* @package WooCommerce/API
* @extends WC_REST_CRUD_Controller
*/
class NoteActions extends Notes {
/**
* Register the routes for admin notes.
*/
public function register_routes() {
register_rest_route(
$this->namespace,
'/' . $this->rest_base . '/(?P<note_id>[\d-]+)/action/(?P<action_id>[\d-]+)',
array(
'args' => array(
'note_id' => array(
'description' => __( 'Unique ID for the Note.', 'woocommerce-admin' ),
'type' => 'integer',
),
'action_id' => array(
'description' => __( 'Unique ID for the Note Action.', 'woocommerce-admin' ),
'type' => 'integer',
),
),
array(
'methods' => \WP_REST_Server::EDITABLE,
'callback' => array( $this, 'trigger_note_action' ),
// @todo - double check these permissions for taking note actions.
'permission_callback' => array( $this, 'get_item_permissions_check' ),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
}
/**
* Trigger a note action.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_REST_Request|WP_Error
*/
public function trigger_note_action( $request ) {
$note = WC_Admin_Notes::get_note( $request->get_param( 'note_id' ) );
if ( ! $note ) {
return new \WP_Error(
'woocommerce_admin_notes_invalid_id',
__( 'Sorry, there is no resource with that ID.', 'woocommerce-admin' ),
array( 'status' => 404 )
);
}
// Find note action by ID.
$action_id = $request->get_param( 'action_id' );
$actions = $note->get_actions( 'edit' );
$triggered_action = false;
foreach ( $actions as $action ) {
if ( $action->id === $action_id ) {
$triggered_action = $action;
}
}
if ( ! $triggered_action ) {
return new \WP_Error(
'woocommerce_admin_note_action_invalid_id',
__( 'Sorry, there is no resource with that ID.', 'woocommerce-admin' ),
array( 'status' => 404 )
);
}
/**
* Fires when an admin note action is taken.
*
* @param string $name The triggered action name.
* @param WC_Admin_Note $note The corresponding Note.
*/
do_action( 'woocommerce_note_action', $triggered_action->name, $note );
/**
* Fires when an admin note action is taken.
* For more specific targeting of note actions.
*
* @param WC_Admin_Note $note The corresponding Note.
*/
do_action( 'woocommerce_note_action_' . $triggered_action->name, $note );
// Update the note with the status for this action.
if ( ! empty( $triggered_action->status ) ) {
$note->set_status( $triggered_action->status );
}
$note->save();
if ( in_array( $note->get_type(), array( 'error', 'update' ) ) ) {
$tracks_event = 'wcadmin_store_alert_action';
} else {
$tracks_event = 'wcadmin_inbox_action_click';
}
wc_admin_record_tracks_event(
$tracks_event,
array(
'note_name' => $note->get_name(),
'note_type' => $note->get_type(),
'note_title' => $note->get_title(),
'note_content' => $note->get_content(),
'note_icon' => $note->get_icon(),
'action_name' => $triggered_action->name,
'action_label' => $triggered_action->label,
)
);
$data = $note->get_data();
$data = $this->prepare_item_for_response( $data, $request );
$data = $this->prepare_response_for_collection( $data );
return rest_ensure_response( $data );
}
}
<?php
/**
* REST API Options Controller
*
* Handles requests to get and update options in the wp_options table.
*
* @package WooCommerce Admin/API
*/
namespace Automattic\WooCommerce\Admin\API;
defined( 'ABSPATH' ) || exit;
/**
* Options Controller.
*
* @package WooCommerce Admin/API
* @extends WC_REST_Data_Controller
*/
class Options extends \WC_REST_Data_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc-admin';
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'options';
/**
* Register routes.
*/
public function register_routes() {
register_rest_route(
$this->namespace,
'/' . $this->rest_base,
array(
array(
'methods' => \WP_REST_Server::READABLE,
'callback' => array( $this, 'get_options' ),
'permission_callback' => array( $this, 'get_item_permissions_check' ),
),
'schema' => array( $this, 'get_item_schema' ),
)
);
register_rest_route(
$this->namespace,
'/' . $this->rest_base,
array(
array(
'methods' => \WP_REST_Server::EDITABLE,
'callback' => array( $this, 'update_options' ),
'permission_callback' => array( $this, 'update_item_permissions_check' ),
),
'schema' => array( $this, 'get_item_schema' ),
)
);
}
/**
* Check if a given request has access to get options.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function get_item_permissions_check( $request ) {
$params = explode( ',', $request['options'] );
if ( ! isset( $request['options'] ) || ! is_array( $params ) ) {
return new \WP_Error( 'woocommerce_rest_cannot_view', __( 'You must supply an array of options.', 'woocommerce-admin' ), 500 );
}
foreach ( $params as $option ) {
if ( ! $this->user_has_permission( $option, $request ) ) {
return new \WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot view these options.', 'woocommerce-admin' ), array( 'status' => rest_authorization_required_code() ) );
}
}
return true;
}
/**
* Check if the user has permission given an option name.
*
* @param string $option Option name.
* @param WP_REST_Request $request Full details about the request.
* @return boolean
*/
public function user_has_permission( $option, $request ) {
$permissions = $this->get_option_permissions( $request );
if ( isset( $permissions[ $option ] ) ) {
return $permissions[ $option ];
}
return current_user_can( 'manage_options' );
}
/**
* Check if a given request has access to update options.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function update_item_permissions_check( $request ) {
$params = $request->get_json_params();
if ( ! is_array( $params ) ) {
return new \WP_Error( 'woocommerce_rest_cannot_update', __( 'You must supply an array of options and values.', 'woocommerce-admin' ), 500 );
}
foreach ( $params as $option_name => $option_value ) {
if ( ! $this->user_has_permission( $option_name, $request ) ) {
return new \WP_Error( 'woocommerce_rest_cannot_update', __( 'Sorry, you cannot manage these options.', 'woocommerce-admin' ), array( 'status' => rest_authorization_required_code() ) );
}
}
return true;
}
/**
* Get an array of options and respective permissions for the current user.
*
* @param WP_REST_Request $request Full details about the request.
* @return array
*/
public function get_option_permissions( $request ) {
$permissions = array(
'theme_mods_' . get_stylesheet() => current_user_can( 'edit_theme_options' ),
'woocommerce_setup_jetpack_opted_in' => current_user_can( 'manage_woocommerce' ),
'woocommerce_stripe_settings' => current_user_can( 'manage_woocommerce' ),
'woocommerce_ppec_paypal_settings' => current_user_can( 'manage_woocommerce' ),
'woocommerce_task_list_payments' => current_user_can( 'manage_woocommerce' ),
'woocommerce_demo_store' => current_user_can( 'manage_woocommerce' ),
'woocommerce_demo_store_notice' => current_user_can( 'manage_woocommerce' ),
);
return apply_filters( 'woocommerce_rest_api_option_permissions', $permissions, $request );
}
/**
* Gets an array of options and respective values.
*
* @param WP_REST_Request $request Full details about the request.
* @return array Options object with option values.
*/
public function get_options( $request ) {
$params = explode( ',', $request['options'] );
$options = array();
if ( ! is_array( $params ) ) {
return array();
}
foreach ( $params as $option ) {
$options[ $option ] = get_option( $option );
}
return $options;
}
/**
* Updates an array of objects.
*
* @param WP_REST_Request $request Full details about the request.
* @return array Options object with a boolean if the option was updated.
*/
public function update_options( $request ) {
$params = $request->get_json_params();
$updated = array();
if ( ! is_array( $params ) ) {
return array();
}
foreach ( $params as $key => $value ) {
$updated[ $key ] = update_option( $key, $value );
}
return $updated;
}
/**
* Get the schema, conforming to JSON Schema.
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'options',
'type' => 'object',
'properties' => array(
'options' => array(
'type' => 'array',
'description' => __( 'Array of options with associated values.', 'woocommerce-admin' ),
'context' => array( 'view' ),
'readonly' => true,
),
),
);
return $this->add_additional_fields_schema( $schema );
}
}
<?php
/**
* REST API Orders Controller
*
* Handles requests to /orders/*
*
* @package WooCommerce Admin/API
*/
namespace Automattic\WooCommerce\Admin\API;
defined( 'ABSPATH' ) || exit;
/**
* Orders controller.
*
* @package WooCommerce Admin/API
* @extends WC_REST_Orders_Controller
*/
class Orders extends \WC_REST_Orders_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc-analytics';
/**
* Get the query params for collections.
*
* @return array
*/
public function get_collection_params() {
$params = parent::get_collection_params();
// This needs to remain a string to support extensions that filter Order Number.
$params['number'] = array(
'description' => __( 'Limit result set to orders matching part of an order number.', 'woocommerce-admin' ),
'type' => 'string',
'validate_callback' => 'rest_validate_request_arg',
);
// Fix the default 'status' value until it can be patched in core.
$params['status']['default'] = array( 'any' );
return $params;
}
/**
* Prepare objects query.
*
* @param WP_REST_Request $request Full details about the request.
* @return array
*/
protected function prepare_objects_query( $request ) {
global $wpdb;
$args = parent::prepare_objects_query( $request );
// Search by partial order number.
if ( ! empty( $request['number'] ) ) {
$partial_number = trim( $request['number'] );
$limit = intval( $args['posts_per_page'] );
$order_ids = $wpdb->get_col(
$wpdb->prepare(
"SELECT ID
FROM {$wpdb->prefix}posts
WHERE post_type = 'shop_order'
AND ID LIKE %s
LIMIT %d",
$wpdb->esc_like( absint( $partial_number ) ) . '%',
$limit
)
);
// Force WP_Query return empty if don't found any order.
$order_ids = empty( $order_ids ) ? array( 0 ) : $order_ids;
$args['post__in'] = $order_ids;
}
return $args;
}
}
<?php
/**
* REST API Product Categories Controller
*
* Handles requests to /products/categories.
*
* @package WooCommerce Admin/API
*/
namespace Automattic\WooCommerce\Admin\API;
defined( 'ABSPATH' ) || exit;
/**
* Product categories controller.
*
* @package WooCommerce Admin/API
* @extends WC_REST_Product_Categories_Controller
*/
class ProductCategories extends \WC_REST_Product_Categories_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc-analytics';
}
<?php
/**
* REST API Product Reviews Controller
*
* Handles requests to /products/reviews.
*
* @package WooCommerce Admin/API
*/
namespace Automattic\WooCommerce\Admin\API;
defined( 'ABSPATH' ) || exit;
/**
* Product reviews controller.
*
* @package WooCommerce Admin/API
* @extends WC_REST_Product_Reviews_Controller
*/
class ProductReviews extends \WC_REST_Product_Reviews_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc-analytics';
/**
* Prepare links for the request.
*
* @param WP_Comment $review Product review object.
* @return array Links for the given product review.
*/
protected function prepare_links( $review ) {
$links = array(
'self' => array(
'href' => rest_url( sprintf( '/%s/%s/%d', $this->namespace, $this->rest_base, $review->comment_ID ) ),
),
'collection' => array(
'href' => rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ),
),
);
if ( 0 !== (int) $review->comment_post_ID ) {
$links['up'] = array(
'href' => rest_url( sprintf( '/%s/products/%d', $this->namespace, $review->comment_post_ID ) ),
'embeddable' => true,
);
}
if ( 0 !== (int) $review->user_id ) {
$links['reviewer'] = array(
'href' => rest_url( 'wp/v2/users/' . $review->user_id ),
'embeddable' => true,
);
}
return $links;
}
}
<?php
/**
* REST API Product Variations Controller
*
* Handles requests to /products/variations.
*
* @package WooCommerce Admin/API
*/
namespace Automattic\WooCommerce\Admin\API;
defined( 'ABSPATH' ) || exit;
/**
* Product variations controller.
*
* @package WooCommerce Admin/API
* @extends WC_REST_Product_Variations_Controller
*/
class ProductVariations extends \WC_REST_Product_Variations_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc-analytics';
/**
* Get the query params for collections.
*
* @return array
*/
public function get_collection_params() {
$params = parent::get_collection_params();
$params['search'] = array(
'description' => __( 'Search by similar product name or sku.', 'woocommerce-admin' ),
'type' => 'string',
'validate_callback' => 'rest_validate_request_arg',
);
return $params;
}
/**
* Add product name and sku filtering to the WC API.
*
* @param WP_REST_Request $request Request data.
* @return array
*/
protected function prepare_objects_query( $request ) {
$args = parent::prepare_objects_query( $request );
if ( ! empty( $request['search'] ) ) {
$args['search'] = $request['search'];
unset( $args['s'] );
}
return $args;
}
/**
* Get a collection of posts and add the post title filter option to WP_Query.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|WP_REST_Response
*/
public function get_items( $request ) {
add_filter( 'posts_where', array( 'Automattic\WooCommerce\Admin\API\Products', 'add_wp_query_filter' ), 10, 2 );
add_filter( 'posts_join', array( 'Automattic\WooCommerce\Admin\API\Products', 'add_wp_query_join' ), 10, 2 );
add_filter( 'posts_groupby', array( 'Automattic\WooCommerce\Admin\API\Products', 'add_wp_query_group_by' ), 10, 2 );
$response = parent::get_items( $request );
remove_filter( 'posts_where', array( 'Automattic\WooCommerce\Admin\API\Products', 'add_wp_query_filter' ), 10 );
remove_filter( 'posts_join', array( 'Automattic\WooCommerce\Admin\API\Products', 'add_wp_query_join' ), 10 );
remove_filter( 'posts_groupby', array( 'Automattic\WooCommerce\Admin\API\Products', 'add_wp_query_group_by' ), 10 );
return $response;
}
/**
* Get the Product's schema, conforming to JSON Schema.
*
* @return array
*/
public function get_item_schema() {
$schema = parent::get_item_schema();
$schema['properties']['name'] = array(
'description' => __( 'Product parent name.', 'woocommerce-admin' ),
'type' => 'string',
'context' => array( 'view', 'edit' ),
);
$schema['properties']['type'] = array(
'description' => __( 'Product type.', 'woocommerce-admin' ),
'type' => 'string',
'default' => 'variation',
'enum' => array( 'variation' ),
'context' => array( 'view', 'edit' ),
);
$schema['properties']['parent_id'] = array(
'description' => __( 'Product parent ID.', 'woocommerce-admin' ),
'type' => 'integer',
'context' => array( 'view', 'edit' ),
);
return $schema;
}
/**
* Prepare a single variation output for response.
*
* @param WC_Data $object Object data.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response
*/
public function prepare_object_for_response( $object, $request ) {
$context = empty( $request['context'] ) ? 'view' : $request['context'];
$response = parent::prepare_object_for_response( $object, $request );
$data = $response->get_data();
$data['name'] = $object->get_name( $context );
$data['type'] = $object->get_type();
$data['parent_id'] = $object->get_parent_id( $context );
$response->set_data( $data );
return $response;
}
}
<?php
/**
* REST API Products Controller
*
* Handles requests to /products/*
*
* @package WooCommerce Admin/API
*/
namespace Automattic\WooCommerce\Admin\API;
defined( 'ABSPATH' ) || exit;
/**
* Products controller.
*
* @package WooCommerce Admin/API
* @extends WC_REST_Products_Controller
*/
class Products extends \WC_REST_Products_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc-analytics';
/**
* Adds properties that can be embed via ?_embed=1.
*
* @return array
*/
public function get_item_schema() {
$schema = parent::get_item_schema();
$properties_to_embed = array(
'id',
'name',
'slug',
'permalink',
'images',
'description',
'short_description',
);
foreach ( $properties_to_embed as $property ) {
$schema['properties'][ $property ]['context'][] = 'embed';
}
return $schema;
}
/**
* Get the query params for collections.
*
* @return array
*/
public function get_collection_params() {
$params = parent::get_collection_params();
$params['low_in_stock'] = array(
'description' => __( 'Limit result set to products that are low or out of stock.', 'woocommerce-admin' ),
'type' => 'boolean',
'default' => false,
'sanitize_callback' => 'wc_string_to_bool',
);
$params['search'] = array(
'description' => __( 'Search by similar product name or sku.', 'woocommerce-admin' ),
'type' => 'string',
'validate_callback' => 'rest_validate_request_arg',
);
return $params;
}
/**
* Add product name and sku filtering to the WC API.
*
* @param WP_REST_Request $request Request data.
* @return array
*/
protected function prepare_objects_query( $request ) {
$args = parent::prepare_objects_query( $request );
if ( ! empty( $request['search'] ) ) {
$args['search'] = trim( $request['search'] );
unset( $args['s'] );
}
if ( ! empty( $request['low_in_stock'] ) ) {
$args['low_in_stock'] = $request['low_in_stock'];
$args['post_type'] = array( 'product', 'product_variation' );
}
return $args;
}
/**
* Get a collection of posts and add the post title filter option to WP_Query.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|WP_REST_Response
*/
public function get_items( $request ) {
add_filter( 'posts_fields', array( __CLASS__, 'add_wp_query_fields' ), 10, 2 );
add_filter( 'posts_where', array( __CLASS__, 'add_wp_query_filter' ), 10, 2 );
add_filter( 'posts_join', array( __CLASS__, 'add_wp_query_join' ), 10, 2 );
add_filter( 'posts_groupby', array( __CLASS__, 'add_wp_query_group_by' ), 10, 2 );
$response = parent::get_items( $request );
remove_filter( 'posts_fields', array( __CLASS__, 'add_wp_query_fields' ), 10 );
remove_filter( 'posts_where', array( __CLASS__, 'add_wp_query_filter' ), 10 );
remove_filter( 'posts_join', array( __CLASS__, 'add_wp_query_join' ), 10 );
remove_filter( 'posts_groupby', array( __CLASS__, 'add_wp_query_group_by' ), 10 );
return $response;
}
/**
* Add `low_stock_amount` property to product data
*
* @param WC_Data $object Object data.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response
*/
public function prepare_object_for_response( $object, $request ) {
$data = parent::prepare_object_for_response( $object, $request );
$object_data = $object->get_data();
if ( $request->get_param( 'low_in_stock' ) && is_numeric( $object_data['low_stock_amount'] ) ) {
$data->data['low_stock_amount'] = $object_data['low_stock_amount'];
}
return $data;
}
/**
* Add in conditional select fields to the query.
*
* @param string $select Select clause used to select fields from the query.
* @param object $wp_query WP_Query object.
* @return string
*/
public static function add_wp_query_fields( $select, $wp_query ) {
if ( $wp_query->get( 'low_in_stock' ) ) {
$select .= ', low_stock_amount_meta.meta_value AS low_stock_amount';
}
return $select;
}
/**
* Add in conditional search filters for products.
*
* @param string $where Where clause used to search posts.
* @param object $wp_query WP_Query object.
* @return string
*/
public static function add_wp_query_filter( $where, $wp_query ) {
global $wpdb;
$search = $wp_query->get( 'search' );
if ( $search ) {
$search = $wpdb->esc_like( $search );
$search = "'%" . $search . "%'";
$where .= " AND ({$wpdb->posts}.post_title LIKE {$search}";
$where .= wc_product_sku_enabled() ? ' OR wc_product_meta_lookup.sku LIKE ' . $search . ')' : ')';
}
if ( $wp_query->get( 'low_in_stock' ) ) {
$low_stock_amount = absint( max( get_option( 'woocommerce_notify_low_stock_amount' ), 1 ) );
$where .= "
AND wc_product_meta_lookup.stock_quantity IS NOT NULL
AND wc_product_meta_lookup.stock_status IN('instock','outofstock')
AND (
(
low_stock_amount_meta.meta_value > ''
AND wc_product_meta_lookup.stock_quantity <= CAST(low_stock_amount_meta.meta_value AS SIGNED)
)
OR (
(
low_stock_amount_meta.meta_value IS NULL OR low_stock_amount_meta.meta_value <= ''
)
AND wc_product_meta_lookup.stock_quantity <= {$low_stock_amount}
)
)";
}
return $where;
}
/**
* Join posts meta tables when product search or low stock query is present.
*
* @param string $join Join clause used to search posts.
* @param object $wp_query WP_Query object.
* @return string
*/
public static function add_wp_query_join( $join, $wp_query ) {
global $wpdb;
$search = $wp_query->get( 'search' );
if ( $search && wc_product_sku_enabled() ) {
$join = self::append_product_sorting_table_join( $join );
}
if ( $wp_query->get( 'low_in_stock' ) ) {
$join = self::append_product_sorting_table_join( $join );
$join .= " LEFT JOIN {$wpdb->postmeta} AS low_stock_amount_meta ON {$wpdb->posts}.ID = low_stock_amount_meta.post_id AND low_stock_amount_meta.meta_key = '_low_stock_amount' ";
}
return $join;
}
/**
* Join wc_product_meta_lookup to posts if not already joined.
*
* @param string $sql SQL join.
* @return string
*/
protected static function append_product_sorting_table_join( $sql ) {
global $wpdb;
if ( ! strstr( $sql, 'wc_product_meta_lookup' ) ) {
$sql .= " LEFT JOIN {$wpdb->wc_product_meta_lookup} wc_product_meta_lookup ON $wpdb->posts.ID = wc_product_meta_lookup.product_id ";
}
return $sql;
}
/**
* Group by post ID to prevent duplicates.
*
* @param string $groupby Group by clause used to organize posts.
* @param object $wp_query WP_Query object.
* @return string
*/
public static function add_wp_query_group_by( $groupby, $wp_query ) {
global $wpdb;
$search = $wp_query->get( 'search' );
$low_in_stock = $wp_query->get( 'low_in_stock' );
if ( empty( $groupby ) && ( $search || $low_in_stock ) ) {
$groupby = $wpdb->posts . '.ID';
}
return $groupby;
}
}
<?php
/**
* REST API Reports Cache.
*
* Handles report data object caching.
*
* @package WooCommerce Admin/API
*/
namespace Automattic\WooCommerce\Admin\API\Reports;
defined( 'ABSPATH' ) || exit;
/**
* REST API Reports Cache class.
*
* @package WooCommerce Admin/API
*/
class Cache {
/**
* Cache version. Used to invalidate all cached values.
*/
const VERSION_OPTION = 'woocommerce_reports';
/**
* Invalidate cache.
*/
public static function invalidate() {
\WC_Cache_Helper::get_transient_version( self::VERSION_OPTION, true );
}
/**
* Get cache version number.
*
* @return string
*/
public static function get_version() {
$version = \WC_Cache_Helper::get_transient_version( self::VERSION_OPTION );
return $version;
}
/**
* Get cached value.
*
* @param string $key Cache key.
* @return mixed
*/
public static function get( $key ) {
$transient_version = self::get_version();
$transient_value = get_transient( $key );
if (
isset( $transient_value['value'], $transient_value['version'] ) &&
$transient_value['version'] === $transient_version
) {
return $transient_value['value'];
}
return false;
}
/**
* Update cached value.
*
* @param string $key Cache key.
* @param mixed $value New value.
* @return bool
*/
public static function set( $key, $value ) {
$transient_version = self::get_version();
$transient_value = array(
'version' => $transient_version,
'value' => $value,
);
$result = set_transient( $key, $transient_value, WEEK_IN_SECONDS );
return $result;
}
}
<?php
/**
* Class for parameter-based Categories Report querying
*
* Example usage:
* $args = array(
* 'before' => '2018-07-19 00:00:00',
* 'after' => '2018-07-05 00:00:00',
* 'page' => 2,
* 'order' => 'desc',
* 'orderby' => 'items_sold',
* );
* $report = new \Automattic\WooCommerce\Admin\API\Reports\Query( $args );
* $mydata = $report->get_data();
*
* @package WooCommerce Admin/Classes
*/
namespace Automattic\WooCommerce\Admin\API\Reports\Categories;
defined( 'ABSPATH' ) || exit;
use \Automattic\WooCommerce\Admin\API\Reports\Query as ReportsQuery;
/**
* API\Reports\Query
*/
class Query extends ReportsQuery {
const REPORT_NAME = 'report-categories';
/**
* Valid fields for Categories report.
*
* @return array
*/
protected function get_default_query_vars() {
return array();
}
/**
* Get categories data based on the current query vars.
*
* @return array
*/
public function get_data() {
$args = apply_filters( 'woocommerce_analytics_categories_query_args', $this->get_query_vars() );
$results = \WC_Data_Store::load( self::REPORT_NAME )->get_data( $args );
return apply_filters( 'woocommerce_analytics_categories_select_query', $results, $args );
}
}
<?php
/**
* REST API Reports controller extended by WC Admin plugin.
*
* Handles requests to the reports endpoint.
*
* @package WooCommerce Admin/API
*/
namespace Automattic\WooCommerce\Admin\API\Reports;
defined( 'ABSPATH' ) || exit;
/**
* REST API Reports controller class.
*
* @package WooCommerce Admin/API
* @extends WC_REST_Reports_Controller
*/
class Controller extends \WC_REST_Reports_Controller {
/**
* Endpoint namespace.
*
* @var string
*/
protected $namespace = 'wc-analytics';
/**
* Route base.
*
* @var string
*/
protected $rest_base = 'reports';
/**
* Register the routes for reports.
*/
public function register_routes() {
register_rest_route(
$this->namespace,
'/' . $this->rest_base,
array(
array(
'methods' => \WP_REST_Server::READABLE,
'callback' => array( $this, 'get_items' ),
'permission_callback' => array( $this, 'get_items_permissions_check' ),
'args' => $this->get_collection_params(),
),
'schema' => array( $this, 'get_public_item_schema' ),
)
);
}
/**
* Check whether a given request has permission to read reports.
*
* @param WP_REST_Request $request Full details about the request.
* @return WP_Error|boolean
*/
public function get_items_permissions_check( $request ) {
if ( ! wc_rest_check_manager_permissions( 'reports', 'read' ) ) {
return new \WP_Error( 'woocommerce_rest_cannot_view', __( 'Sorry, you cannot list resources.', 'woocommerce-admin' ), array( 'status' => rest_authorization_required_code() ) );
}
return true;
}
/**
* Get all reports.
*
* @param WP_REST_Request $request Request data.
* @return array|WP_Error
*/
public function get_items( $request ) {
$data = array();
$reports = array(
array(
'slug' => 'performance-indicators',
'description' => __( 'Batch endpoint for getting specific performance indicators from `stats` endpoints.', 'woocommerce-admin' ),
),
array(
'slug' => 'revenue/stats',
'description' => __( 'Stats about revenue.', 'woocommerce-admin' ),
),
array(
'slug' => 'orders/stats',
'description' => __( 'Stats about orders.', 'woocommerce-admin' ),
),
array(
'slug' => 'products',
'description' => __( 'Products detailed reports.', 'woocommerce-admin' ),
),
array(
'slug' => 'products/stats',
'description' => __( 'Stats about products.', 'woocommerce-admin' ),
),
array(
'slug' => 'categories',
'description' => __( 'Product categories detailed reports.', 'woocommerce-admin' ),
),
array(
'slug' => 'categories/stats',
'description' => __( 'Stats about product categories.', 'woocommerce-admin' ),
),
array(
'slug' => 'coupons',
'description' => __( 'Coupons detailed reports.', 'woocommerce-admin' ),
),
array(
'slug' => 'coupons/stats',
'description' => __( 'Stats about coupons.', 'woocommerce-admin' ),
),
array(
'slug' => 'taxes',
'description' => __( 'Taxes detailed reports.', 'woocommerce-admin' ),
),
array(
'slug' => 'taxes/stats',
'description' => __( 'Stats about taxes.', 'woocommerce-admin' ),
),
array(
'slug' => 'downloads',
'description' => __( 'Product downloads detailed reports.', 'woocommerce-admin' ),
),
array(
'slug' => 'downloads/files',
'description' => __( 'Product download files detailed reports.', 'woocommerce-admin' ),
),
array(
'slug' => 'downloads/stats',
'description' => __( 'Stats about product downloads.', 'woocommerce-admin' ),
),
array(
'slug' => 'customers',
'description' => __( 'Customers detailed reports.', 'woocommerce-admin' ),
),
);
/**
* Filter the list of allowed reports, so that data can be loaded from third party extensions in addition to WooCommerce core.
* Array items should be in format of array( 'slug' => 'downloads/stats', 'description' => '',
* 'url' => '', and 'path' => '/wc-ext/v1/...'.
*
* @param array $endpoints The list of allowed reports..
*/
$reports = apply_filters( 'woocommerce_admin_reports', $reports );
foreach ( $reports as $report ) {
if ( empty( $report['slug'] ) ) {
continue;
}
if ( empty( $report['path'] ) ) {
$report['path'] = '/' . $this->namespace . '/reports/' . $report['slug'];
}
// Allows a different admin page to be loaded here,
// or allows an empty url if no report exists for a set of performance indicators.
if ( ! isset( $report['url'] ) ) {
if ( '/stats' === substr( $report['slug'], -6 ) ) {
$url_slug = substr( $report['slug'], 0, -6 );
} else {
$url_slug = $report['slug'];
}
$report['url'] = '/analytics/' . $url_slug;
}
$item = $this->prepare_item_for_response( (object) $report, $request );
$data[] = $this->prepare_response_for_collection( $item );
}
return rest_ensure_response( $data );
}
/**
* Get the order number for an order. If no filter is present for `woocommerce_order_number`, we can just return the ID.
* Returns the parent order number if the order is actually a refund.
*
* @param int $order_id Order ID.
* @return string
*/
public function get_order_number( $order_id ) {
$order = wc_get_order( $order_id );
if ( 'shop_order_refund' === $order->get_type() ) {
$order = wc_get_order( $order->get_parent_id() );
}
if ( ! has_filter( 'woocommerce_order_number' ) ) {
return $order->get_id();
}
return $order->get_order_number();
}
/**
* Prepare a report object for serialization.
*
* @param stdClass $report Report data.
* @param WP_REST_Request $request Request object.
* @return WP_REST_Response
*/
public function prepare_item_for_response( $report, $request ) {
$data = array(
'slug' => $report->slug,
'description' => $report->description,
'path' => $report->path,
);
$context = ! empty( $request['context'] ) ? $request['context'] : 'view';
$data = $this->add_additional_fields_to_object( $data, $request );
$data = $this->filter_response_by_context( $data, $context );
// Wrap the data in a response object.
$response = rest_ensure_response( $data );
$response->add_links(
array(
'self' => array(
'href' => rest_url( $report->path ),
),
'report' => array(
'href' => $report->url,
),
'collection' => array(
'href' => rest_url( sprintf( '%s/%s', $this->namespace, $this->rest_base ) ),
),
)
);
/**
* Filter a report returned from the API.
*
* Allows modification of the report data right before it is returned.
*
* @param WP_REST_Response $response The response object.
* @param object $report The original report object.
* @param WP_REST_Request $request Request used to generate the response.
*/
return apply_filters( 'woocommerce_rest_prepare_report', $response, $report, $request );
}
/**
* Get the Report's schema, conforming to JSON Schema.
*
* @return array
*/
public function get_item_schema() {
$schema = array(
'$schema' => 'http://json-schema.org/draft-04/schema#',
'title' => 'report',
'type' => 'object',
'properties' => array(
'slug' => array(
'description' => __( 'An alphanumeric identifier for the resource.', 'woocommerce-admin' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'description' => array(
'description' => __( 'A human-readable description of the resource.', 'woocommerce-admin' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
'path' => array(
'description' => __( 'API path.', 'woocommerce-admin' ),
'type' => 'string',
'context' => array( 'view' ),
'readonly' => true,
),
),
);
return $this->add_additional_fields_schema( $schema );
}
/**
* Get the query params for collections.
*
* @return array
*/
public function get_collection_params() {
return array(
'context' => $this->get_context_param( array( 'default' => 'view' ) ),
);
}
/**
* Get order statuses without prefixes.
*
* @return array
*/
public function get_order_statuses() {
return array_keys( $this->get_order_status_labels() );
}
/**
* Get order statuses (and labels) without prefixes.
*
* @return array
*/
public function get_order_status_labels() {
$order_statuses = array();
foreach ( wc_get_order_statuses() as $key => $label ) {
$new_key = str_replace( 'wc-', '', $key );
$order_statuses[ $new_key ] = $label;
}
return $order_statuses;
}
}
<?php
/**
* Class for parameter-based Coupons Report querying
*
* Example usage:
* $args = array(
* 'before' => '2018-07-19 00:00:00',
* 'after' => '2018-07-05 00:00:00',
* 'page' => 2,
* 'coupons' => array(5, 120),
* );
* $report = new \Automattic\WooCommerce\Admin\API\Reports\Coupons\Query( $args );
* $mydata = $report->get_data();
*
* @package WooCommerce Admin/Classes
*/
namespace Automattic\WooCommerce\Admin\API\Reports\Coupons;
defined( 'ABSPATH' ) || exit;
use \Automattic\WooCommerce\Admin\API\Reports\Query as ReportsQuery;
/**
* API\Reports\Coupons\Query
*/
class Query extends ReportsQuery {
/**
* Valid fields for Products report.
*
* @return array
*/
protected function get_default_query_vars() {
return array();
}
/**
* Get product data based on the current query vars.
*
* @return array
*/
public function get_data() {
$args = apply_filters( 'woocommerce_analytics_coupons_query_args', $this->get_query_vars() );
$data_store = \WC_Data_Store::load( 'report-coupons' );
$results = $data_store->get_data( $args );
return apply_filters( 'woocommerce_analytics_coupons_select_query', $results, $args );
}
}
<?php
/**
* API\Reports\Coupons\Stats\DataStore class file.
*
* @package WooCommerce Admin/Classes
*/
namespace Automattic\WooCommerce\Admin\API\Reports\Coupons\Stats;
defined( 'ABSPATH' ) || exit;
use \Automattic\WooCommerce\Admin\API\Reports\Coupons\DataStore as CouponsDataStore;
use \Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface;
use \Automattic\WooCommerce\Admin\API\Reports\TimeInterval;
use \Automattic\WooCommerce\Admin\API\Reports\SqlQuery;
/**
* API\Reports\Coupons\Stats\DataStore.
*/
class DataStore extends CouponsDataStore implements DataStoreInterface {
/**
* Mapping columns to data type to return correct response types.
*
* @var array
*/
protected $column_types = array(
'date_start' => 'strval',
'date_end' => 'strval',
'date_start_gmt' => 'strval',
'date_end_gmt' => 'strval',
'amount' => 'floatval',
'coupons_count' => 'intval',
'orders_count' => 'intval',
);
/**
* SQL columns to select in the db query.
*
* @var array
*/
protected $report_columns;
/**
* Data store context used to pass to filters.
*
* @var string
*/
protected $context = 'coupon_stats';
/**
* Cache identifier.
*
* @var string
*/
protected $cache_key = 'coupons_stats';
/**
* Assign report columns once full table name has been assigned.
*/
protected function assign_report_columns() {
$table_name = self::get_db_table_name();
$this->report_columns = array(
'amount' => 'SUM(discount_amount) as amount',
'coupons_count' => 'COUNT(DISTINCT coupon_id) as coupons_count',
'orders_count' => "COUNT(DISTINCT {$table_name}.order_id) as orders_count",
);
}
/**
* Updates the database query with parameters used for Products Stats report: categories and order status.
*
* @param array $query_args Query arguments supplied by the user.
*/
protected function update_sql_query_params( $query_args ) {
global $wpdb;
$clauses = array(
'where' => '',
'join' => '',
);
$order_coupon_lookup_table = self::get_db_table_name();
$included_coupons = $this->get_included_coupons( $query_args, 'coupons' );
if ( $included_coupons ) {
$clauses['where'] .= " AND {$order_coupon_lookup_table}.coupon_id IN ({$included_coupons})";
}
$order_status_filter = $this->get_status_subquery( $query_args );
if ( $order_status_filter ) {
$clauses['join'] .= " JOIN {$wpdb->prefix}wc_order_stats ON {$order_coupon_lookup_table}.order_id = {$wpdb->prefix}wc_order_stats.order_id";
$clauses['where'] .= " AND ( {$order_status_filter} )";
}
$this->add_time_period_sql_params( $query_args, $order_coupon_lookup_table );
$this->add_intervals_sql_params( $query_args, $order_coupon_lookup_table );
$clauses['where_time'] = $this->get_sql_clause( 'where_time' );
$this->interval_query->add_sql_clause( 'limit', $this->get_sql_clause( 'limit' ) );
$this->interval_query->add_sql_clause( 'order_by', $this->get_sql_clause( 'order_by' ) );
$this->interval_query->add_sql_clause( 'select', $this->get_sql_clause( 'select' ) );
$this->interval_query->add_sql_clause( 'select', 'AS time_interval' );
foreach ( array( 'join', 'where_time', 'where' ) as $clause ) {
$this->interval_query->add_sql_clause( $clause, $clauses[ $clause ] );
$this->total_query->add_sql_clause( $clause, $clauses[ $clause ] );
}
}
/**
* Returns the report data based on parameters supplied by the user.
*
* @since 3.5.0
* @param array $query_args Query parameters.
* @return stdClass|WP_Error Data.
*/
public function get_data( $query_args ) {
global $wpdb;
$table_name = self::get_db_table_name();
// These defaults are only partially applied when used via REST API, as that has its own defaults.
$defaults = array(
'per_page' => get_option( 'posts_per_page' ),
'page' => 1,
'order' => 'DESC',
'orderby' => 'date',
'before' => TimeInterval::default_before(),
'after' => TimeInterval::default_after(),
'fields' => '*',
'interval' => 'week',
'coupons' => array(),
);
$query_args = wp_parse_args( $query_args, $defaults );
$this->normalize_timezones( $query_args, $defaults );
/*
* We need to get the cache key here because
* parent::update_intervals_sql_params() modifies $query_args.
*/
$cache_key = $this->get_cache_key( $query_args );
$data = $this->get_cached_data( $cache_key );
if ( false === $data ) {
$this->initialize_queries();
$data = (object) array(
'data' => array(),
'total' => 0,
'pages' => 0,
'page_no' => 0,
);
$selections = $this->selected_columns( $query_args );
$totals_query = array();
$intervals_query = array();
$limit_params = $this->get_limit_sql_params( $query_args );
$this->update_sql_query_params( $query_args, $totals_query, $intervals_query );
$db_intervals = $wpdb->get_col(
$this->interval_query->get_query_statement()
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
$db_interval_count = count( $db_intervals );
$expected_interval_count = TimeInterval::intervals_between( $query_args['after'], $query_args['before'], $query_args['interval'] );
$total_pages = (int) ceil( $expected_interval_count / $limit_params['per_page'] );
if ( $query_args['page'] < 1 || $query_args['page'] > $total_pages ) {
return $data;
}
$this->total_query->add_sql_clause( 'select', $selections );
$totals = $wpdb->get_results(
$this->total_query->get_query_statement(),
ARRAY_A
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
if ( null === $totals ) {
return $data;
}
// @todo remove these assignements when refactoring segmenter classes to use query objects.
$totals_query = array(
'from_clause' => $this->total_query->get_sql_clause( 'join' ),
'where_time_clause' => $this->total_query->get_sql_clause( 'where_time' ),
'where_clause' => $this->total_query->get_sql_clause( 'where' ),
);
$intervals_query = array(
'select_clause' => $this->get_sql_clause( 'select' ),
'from_clause' => $this->interval_query->get_sql_clause( 'join' ),
'where_time_clause' => $this->interval_query->get_sql_clause( 'where_time' ),
'where_clause' => $this->interval_query->get_sql_clause( 'where' ),
'limit' => $this->get_sql_clause( 'limit' ),
);
$segmenter = new Segmenter( $query_args, $this->report_columns );
$totals[0]['segments'] = $segmenter->get_totals_segments( $totals_query, $table_name );
$totals = (object) $this->cast_numbers( $totals[0] );
// Intervals.
$this->update_intervals_sql_params( $query_args, $db_interval_count, $expected_interval_count, $table_name );
$this->interval_query->add_sql_clause( 'select', ", MAX({$table_name}.date_created) AS datetime_anchor" );
if ( '' !== $selections ) {
$this->interval_query->add_sql_clause( 'select', ', ' . $selections );
}
$intervals = $wpdb->get_results(
$this->interval_query->get_query_statement(),
ARRAY_A
); // WPCS: cache ok, DB call ok, unprepared SQL ok.
if ( null === $intervals ) {
return $data;
}
$data = (object) array(
'totals' => $totals,
'intervals' => $intervals,
'total' => $expected_interval_count,
'pages' => $total_pages,
'page_no' => (int) $query_args['page'],
);
if ( TimeInterval::intervals_missing( $expected_interval_count, $db_interval_count, $limit_params['per_page'], $query_args['page'], $query_args['order'], $query_args['orderby'], count( $intervals ) ) ) {
$this->fill_in_missing_intervals( $db_intervals, $query_args['adj_after'], $query_args['adj_before'], $query_args['interval'], $data );
$this->sort_intervals( $data, $query_args['orderby'], $query_args['order'] );
$this->remove_extra_records( $data, $query_args['page'], $limit_params['per_page'], $db_interval_count, $expected_interval_count, $query_args['orderby'], $query_args['order'] );
} else {
$this->update_interval_boundary_dates( $query_args['after'], $query_args['before'], $query_args['interval'], $data->intervals );
}
$segmenter->add_intervals_segments( $data, $intervals_query, $table_name );
$this->create_interval_subtotals( $data->intervals );
$this->set_cached_data( $cache_key, $data );
}
return $data;
}
/**
* Initialize query objects.
*/
protected function initialize_queries() {
$this->clear_all_clauses();
unset( $this->subquery );
$this->total_query = new SqlQuery( $this->context . '_total' );
$this->total_query->add_sql_clause( 'from', self::get_db_table_name() );
$this->interval_query = new SqlQuery( $this->context . '_interval' );
$this->interval_query->add_sql_clause( 'from', self::get_db_table_name() );
$this->interval_query->add_sql_clause( 'group_by', 'time_interval' );
}
}
<?php
/**
* Class for parameter-based Products Report querying
*
* Example usage:
* $args = array(
* 'before' => '2018-07-19 00:00:00',
* 'after' => '2018-07-05 00:00:00',
* 'page' => 2,
* 'coupons' => array(5, 120),
* );
* $report = new \Automattic\WooCommerce\Admin\API\Reports\Coupons\Stats\Query( $args );
* $mydata = $report->get_data();
*
* @package WooCommerce Admin/Classes
*/
namespace Automattic\WooCommerce\Admin\API\Reports\Coupons\Stats;
defined( 'ABSPATH' ) || exit;
use \Automattic\WooCommerce\Admin\API\Reports\Query as ReportsQuery;
/**
* API\Reports\Coupons\Stats\Query
*/
class Query extends ReportsQuery {
/**
* Valid fields for Products report.
*
* @return array
*/
protected function get_default_query_vars() {
return array();
}
/**
* Get product data based on the current query vars.
*
* @return array
*/
public function get_data() {
$args = apply_filters( 'woocommerce_analytics_coupons_stats_query_args', $this->get_query_vars() );
$data_store = \WC_Data_Store::load( 'report-coupons-stats' );
$results = $data_store->get_data( $args );
return apply_filters( 'woocommerce_analytics_coupons_select_query', $results, $args );
}
}
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment