/**
  * Helper method used in the System Status > Tools menu to delete
  * any existing webhooks and re-add the webhook. This is helpful to use
  * if the webhook couldn't be created on install or somehow has gotten
  * lost.
  *
  * @since 3.2.0
  */
 public function recreate_webhook()
 {
     try {
         $webhooks = wc_freshbooks()->get_api()->get_webhooks();
         // webhooks exist, remove them
         if (!empty($webhooks)) {
             foreach ($webhooks as $webhook) {
                 wc_freshbooks()->get_api()->delete_webhook($webhook['id']);
             }
         }
         // create a new webhook
         $webhook_id = wc_freshbooks()->get_api()->create_webhook();
         update_option('wc_freshbooks_webhook_id', $webhook_id);
         return true;
     } catch (SV_WC_API_Exception $e) {
         wc_freshbooks()->log($e->getMessage());
         return false;
     }
 }
 /**
  * Helper method to improve the readibility of methods calling the API
  *
  * @since 3.0
  * @return \WC_FreshBooks_API instance
  */
 private function api()
 {
     return wc_freshbooks()->get_api();
 }
 /**
  * Get available invoice languages via FreshBooks API
  *
  * Languages are cached for 5 minutes
  *
  * @since 3.0
  * @return array
  */
 private function get_available_languages()
 {
     $invoice_languages = array();
     // during the initial install, this is sometimes not a proper object
     // which throws fatal errors when trying to call get_api()
     if (!is_object(wc_freshbooks())) {
         return $invoice_languages;
     }
     try {
         $invoice_languages = wc_freshbooks()->get_api()->get_languages();
     } catch (SV_WC_API_Exception $e) {
         wc_freshbooks()->log($e->getMessage());
     }
     return $invoice_languages;
 }
function init_woocommerce_freshbooks()
{
    /**
     * # WooCommerce FreshBooks Main Plugin Class
     *
     * ## Plugin Overview
     *
     * This plugin integrates FreshBooks with WooCommerce by exporting customers/orders
     * to FreshBooks as clients/invoices
     *
     * ## Features
     *
     * + Create invoices & clients for orders (either automatically or manually)
     * + Apply invoice payments after an order is paid (automatically or manually)
     * + Link products to FreshBooks items
     * + Keep invoices & payments synced between FreshBooks and WooCommerce
     *
     * ## Admin Considerations
     *
     * + 'FreshBooks' setting tab added to WooCommerce > Settings
     * + 'FreshBooks Item' select on Simple/Variable product data tab
     * + 'Invoice Status' column on Orders screen
     * + 'Create & Send Invoice' order action on Orders screen
     * + Custom bulk order actions for creating invoices on Orders screen
     * + Custom bulk filter for invoice status on Order screen
     * + Order actions for creating invoices on Order Edit screen
     * + Custom metabox for invoice info on Order Edit screen
     *
     * ## Frontend Considerations
     *
     * + None
     *
     * ## Database
     *
     * ### Global Options
     *
     * + `wc_freshbooks_api_url` - URL for the FreshBooks API
     * + `wc_freshbooks_authentication_token` - auth token for the API
     * + `wc_freshbooks_debug_mode` - yes to enable debug mode
     * + `wc_freshbooks_default_client` - `none` to create a new client for each customer, or the client ID of a client to create all invoices under
     * + `wc_freshbooks_invoice_language` - the language for created invoices
     * + `wc_freshbooks_invoice_sending_method` - the method to send invoices by, either `email` or `snail_mail`
     * + `wc_freshbooks_use_order_number` - yes to to use WC order number as invoice number, false to let FreshBooks generate it
     * + `wc_freshbooks_invoice_number_prefix` - a prefix for the invoice order number
     * + `wc_freshbooks_auto_create_invoices` - yes to automatically create invoices for all new orders
     * + `wc_freshbooks_auto_send_invoices` - yes to automatically send invoices upon creation
     * + `wc_freshbooks_auto_apply_payments` - yes to automatically apply order payment to the invoice
     *
     * ### Options table
     *
     * + `wc_freshbooks_version` - the current plugin version, set on install/upgrade
     * + `wc_freshbooks_upgraded_from_v2` - bool, set to true when upgraded from a version prior to v3.0
     *
     * ### Order Meta
     *
     * + `_wc_freshbooks_invoice_id` - FreshBooks ID for invoice
     * + `_wc_freshbooks_client_id` - FreshBooks client ID for associated invoice
     * + `_wc_freshbooks_payment_id` - FreshBooks payment ID for the invoice, if one exists
     * + `_wc_freshbooks_invoice_status` - invoice status
     * + `_wc_freshbooks_invoice` - invoice data as serialized array, see WC_FreshBooks_API_Request::parse_get_invoice() for format
     *
     * ### Product Meta
     *
     * + `_wc_freshbooks_item_name` - FreshBooks item name associated with the product
     *
     * ### Customer Meta
     *
     * + `_wc_freshbooks_client_id` - FreshBooks client ID for customer/user
     *
     * @since 3.0
     */
    class WC_FreshBooks extends SV_WC_Plugin
    {
        /** string version number */
        const VERSION = '3.5.1';
        /** @var WC_FreshBooks single instance of this plugin */
        protected static $instance;
        /** string the plugin id */
        const PLUGIN_ID = 'freshbooks';
        /** string plugin text domain */
        const TEXT_DOMAIN = 'woocommerce-freshbooks';
        /** @var \WC_FreshBooks_Admin instance */
        public $admin;
        /** @var \WC_FreshBooks_Settings instance */
        public $settings;
        /** @var \WC_FreshBooks_Orders_Admin instance */
        public $orders_admin;
        /** @var \WC_FreshBooks_Products_Admin instance */
        public $products_admin;
        /** @var \WC_FreshBooks_Webhooks instance */
        public $webhooks;
        /** @var \WC_FreshBooks_Handler instance */
        public $handler;
        /** @var \WC_FreshBooks_API instance */
        private $api;
        /**
         * Setup main plugin class
         *
         * @since 3.0
         * @see SV_WC_Plugin::__construct()
         */
        public function __construct()
        {
            parent::__construct(self::PLUGIN_ID, self::VERSION, self::TEXT_DOMAIN);
            // load includes after WC is loaded
            add_action('sv_wc_framework_plugins_loaded', array($this, 'includes'), 11);
            // Subscriptions support
            if ($this->is_plugin_active('woocommerce-subscriptions.php')) {
                if (SV_WC_Plugin_Compatibility::is_wc_subscriptions_version_gte_2_0()) {
                    // don't copy over FreshBooks invoice meta from the original order to the subscription (subscription objects should not have an invoice)
                    add_filter('wcs_subscription_meta', array($this, 'subscriptions_remove_subscription_order_meta'), 10, 3);
                    // don't copy over FreshBooks invoice meta to subscription object during upgrade from 1.5.x to 2.0
                    add_filter('wcs_upgrade_subscription_meta_to_copy', array($this, 'subscriptions_remove_subscription_order_meta_during_upgrade'));
                    // don't copy over FreshBooks invoice meta from the subscription to the renewal order
                    add_filter('wcs_renewal_order_meta', array($this, 'subscriptions_remove_renewal_order_meta'));
                } else {
                    // remove the invoice data from new renewal orders for subscription renewals so a new invoice is created for each renewal order
                    add_filter('woocommerce_subscriptions_renewal_order_meta_query', array($this, 'subscriptions_remove_renewal_order_meta_1_5'), 10, 4);
                }
            }
            // maybe disable API logging
            if ('on' !== get_option('wc_freshbooks_debug_mode')) {
                remove_action('wc_' . $this->get_id() . '_api_request_performed', array($this, 'log_api_request'), 10);
            }
        }
        /**
         * Load plugin text domain.
         *
         * @since 3.0
         * @see SV_WC_Payment_Gateway_Plugin::load_translation()
         */
        public function load_translation()
        {
            load_plugin_textdomain('woocommerce-freshbooks', false, dirname(plugin_basename($this->get_file())) . '/i18n/languages');
        }
        /**
         * Loads any required files
         *
         * @since 3.0
         */
        public function includes()
        {
            require_once $this->get_plugin_path() . '/includes/class-wc-freshbooks-order.php';
            require_once $this->get_plugin_path() . '/includes/class-wc-freshbooks-handler.php';
            $this->handler = new WC_FreshBooks_Handler();
            require_once $this->get_plugin_path() . '/includes/class-wc-freshbooks-webhooks.php';
            $this->webhooks = new WC_FreshBooks_Webhooks();
            if (is_admin()) {
                $this->admin_includes();
            }
        }
        /**
         * Loads required admin files
         *
         * @since 3.0
         */
        private function admin_includes()
        {
            // add settings page
            add_filter('woocommerce_get_settings_pages', array($this, 'add_settings_page'));
            require_once $this->get_plugin_path() . '/includes/admin/class-wc-freshbooks-admin.php';
            $this->admin = new WC_FreshBooks_Admin();
            require_once $this->get_plugin_path() . '/includes/admin/class-wc-freshbooks-orders-admin.php';
            $this->orders_admin = new WC_FreshBooks_Orders_Admin();
            require_once $this->get_plugin_path() . '/includes/admin/class-wc-freshbooks-products-admin.php';
            $this->products_admin = new WC_FreshBooks_Products_Admin();
        }
        /** Admin methods ******************************************************/
        /**
         * Add settings page
         *
         * @since 3.2.0
         * @param array $settings
         * @return array
         */
        public function add_settings_page($settings)
        {
            $settings[] = (require_once $this->get_plugin_path() . '/includes/admin/class-wc-freshbooks-settings.php');
            return $settings;
        }
        /**
         * Renders any admin notices
         *
         * @since 3.2.0
         * @see SV_WC_Plugin::add_admin_notices()
         */
        public function add_delayed_admin_notices()
        {
            parent::add_delayed_admin_notices();
            // onboarding!
            if (!get_option('wc_freshbooks_api_url')) {
                if (get_option('wc_freshbooks_upgraded_from_v2')) {
                    $message = __('Thanks for upgrading to the latest WooCommerce FreshBooks plugin! Please double-check your %sinvoice settings%s.', self::TEXT_DOMAIN);
                } else {
                    $message = __('Thanks for installing the WooCommerce FreshBooks plugin! To get started, please %sadd your FreshBooks API credentials%s. ', self::TEXT_DOMAIN);
                }
                $this->get_admin_notice_handler()->add_admin_notice(sprintf($message, '<a href="' . $this->get_settings_url() . '">', '</a>'), 'welcome-notice', array('notice_class' => 'updated'));
            }
        }
        /** Subscriptions compatibility *******************************************/
        /**
         * Don't copy invoice meta to renewal orders from the WC_Subscription
         * object. Generally the subscription object should not have any order-specific
         * meta. This allows an invoice to be created for each renewal order.
         *
         * @since 3.5.1
         * @param array $order_meta order meta to copy
         * @return array
         */
        public function subscriptions_remove_renewal_order_meta($order_meta)
        {
            $meta_keys = $this->subscriptions_get_order_meta_keys();
            foreach ($order_meta as $index => $meta) {
                if (in_array($meta['meta_key'], $meta_keys)) {
                    unset($order_meta[$index]);
                }
            }
            return $order_meta;
        }
        /**
         * Remove FreshBooks meta when creating a subscription object from an order at checkout.
         * Subscriptions aren't true orders so they shouldn't have a FreshBooks invoice
         *
         * @since 3.5.1
         * @param array $order_meta meta on order
         * @param \WC_Subscription $to_order order meta is being copied to
         * @param \WC_Order $from_order order meta is being copied from
         * @return array
         */
        public function subscriptions_remove_subscription_order_meta($order_meta, $to_order, $from_order)
        {
            // only when copying from an order to a subscription
            if ($to_order instanceof WC_Subscription && $from_order instanceof WC_Order) {
                $meta_keys = $this->subscriptions_get_order_meta_keys();
                foreach ($order_meta as $index => $meta) {
                    if (in_array($meta['meta_key'], $meta_keys)) {
                        unset($order_meta[$index]);
                    }
                }
            }
            return $order_meta;
        }
        /**
         * Don't copy over FreshBooks meta during the upgrade from Subscription 1.5.x to 2.0
         *
         * @since 3.5.1
         * @param array $order_meta meta to copy
         * @return array
         */
        public function subscriptions_remove_subscription_order_meta_during_upgrade($order_meta)
        {
            foreach ($this->subscriptions_get_order_meta_keys() as $meta_key) {
                if (isset($order_meta[$meta_key])) {
                    unset($order_meta[$meta_key]);
                }
            }
            return $order_meta;
        }
        /**
         * Returns an array of meta keys used by FreshBooks
         *
         * @since 3.5.1
         * @return array
         */
        protected function subscriptions_get_order_meta_keys()
        {
            return array('_wc_freshbooks_invoice_id', '_wc_freshbooks_client_id', '_wc_freshbooks_payment_id', '_wc_freshbooks_invoice_status', '_wc_freshbooks_invoice');
        }
        /**
         * Don't copy over the invoice meta when creating a renewal order
         *
         * @since 3.5.1
         * @param array $order_meta_query MySQL query for pulling the metadata
         * @return string
         */
        public function subscriptions_remove_renewal_order_meta_1_5($order_meta_query)
        {
            $order_meta_query .= " AND `meta_key` NOT IN (" . "'_wc_freshbooks_invoice_id', " . "'_wc_freshbooks_client_id', " . "'_wc_freshbooks_payment_id', " . "'_wc_freshbooks_invoice_status', " . "'_wc_freshbooks_invoice' )";
            return $order_meta_query;
        }
        /** Helper methods ******************************************************/
        /**
         * Main FreshBooks Instance, ensures only one instance is/can be loaded
         *
         * @since 3.3.0
         * @see wc_freshbooks()
         * @return WC_FreshBooks
         */
        public static function instance()
        {
            if (is_null(self::$instance)) {
                self::$instance = new self();
            }
            return self::$instance;
        }
        /**
         * Lazy load the FreshBooks API wrapper
         *
         * @since 3.0
         * @return \WC_FreshBooks_API instance
         * @throws Exception missing API URL or authentication token settings
         */
        public function get_api()
        {
            if (is_object($this->api)) {
                return $this->api;
            }
            $api_url = get_option('wc_freshbooks_api_url');
            $authentication_token = get_option('wc_freshbooks_authentication_token');
            // bail if required info is not available
            if (!$api_url || !$authentication_token) {
                throw new SV_WC_API_Exception(__('Missing API URL or Authentication Token', self::TEXT_DOMAIN));
            }
            // bail if API URL does not appear to be valid
            if (false === strpos($api_url, 'freshbooks.com/api/2.1/xml-in')) {
                throw new SV_WC_API_Exception(__('Incorrect API URL', self::TEXT_DOMAIN));
            }
            // load API files
            require_once $this->get_plugin_path() . '/includes/api/class-wc-freshbooks-api-request.php';
            require_once $this->get_plugin_path() . '/includes/api/class-wc-freshbooks-api-response.php';
            require_once $this->get_plugin_path() . '/includes/api/class-wc-freshbooks-api.php';
            return $this->api = new WC_FreshBooks_API($api_url, $authentication_token);
        }
        /** Getter methods ******************************************************/
        /**
         * Returns the plugin name, localized
         *
         * @since 3.0
         * @see SV_WC_Plugin::get_plugin_name()
         * @return string the plugin name
         */
        public function get_plugin_name()
        {
            return __('WooCommerce FreshBooks', self::TEXT_DOMAIN);
        }
        /**
         * Returns __FILE__
         *
         * @since 3.0
         * @see SV_WC_Plugin::get_file()
         * @return string the full path and filename of the plugin file
         */
        protected function get_file()
        {
            return __FILE__;
        }
        /**
         * Gets the plugin configuration URL
         *
         * @since 3.0
         * @see SV_WC_Plugin::get_settings_url()
         * @param string $_ unused
         * @return string plugin settings URL
         */
        public function get_settings_url($_ = null)
        {
            return admin_url('admin.php?page=wc-settings&tab=freshbooks');
        }
        /**
         * Returns conditional dependencies based on the FTP security selected
         *
         * @since 1.1
         * @see SV_WC_Plugin::get_dependencies()
         * @return array of dependencies
         */
        protected function get_dependencies()
        {
            return array('xmlwriter');
        }
        /**
         * Gets the plugin documentation url
         *
         * @since 3.5.0
         * @see SV_WC_Plugin::get_documentation_url()
         * @return string documentation URL
         */
        public function get_documentation_url()
        {
            return 'http://docs.woothemes.com/document/woocommerce-freshbooks/';
        }
        /**
         * Gets the plugin support URL
         *
         * @since 3.5.0
         * @see SV_WC_Plugin::get_support_url()
         * @return string
         */
        public function get_support_url()
        {
            return 'http://support.woothemes.com/';
        }
        /** Lifecycle methods ******************************************************/
        /**
         * Run install
         *
         * @since 3.0
         * @see SV_WC_Plugin::install()
         */
        protected function install()
        {
            // include settings so we can install defaults
            require_once WC()->plugin_path() . '/includes/admin/settings/class-wc-settings-page.php';
            require_once $this->get_plugin_path() . '/includes/admin/class-wc-freshbooks-settings.php';
            foreach ($this->settings->get_settings() as $setting) {
                if (isset($setting['default'])) {
                    update_option($setting['id'], $setting['default']);
                }
            }
            // versions prior to 3.0 did not set a version option, so the upgrade
            // method needs to be called manually
            if (get_option('wc_fb_api_url')) {
                $this->upgrade('2.1.3');
            }
        }
        /**
         * Perform any version-related changes.
         *
         * @since 3.0
         * @see SV_WC_Plugin::upgrade()
         * @param int $installed_version the currently installed version of the plugin
         */
        protected function upgrade($installed_version)
        {
            // upgrade to 3.0
            if (version_compare($installed_version, '3.0', '<')) {
                // API URL / token
                update_option('wc_freshbooks_api_url', get_option('wc_fb_api_url'));
                update_option('wc_freshbooks_authentication_token', get_option('wc_fb_api_token'));
                // invoice send method
                update_option('wc_freshbooks_invoice_sending_method', 'SnailMail' == get_option('wc_fb_send_method') ? 'snail_mail' : 'email');
                // use order number as invoice number
                update_option('wc_freshbooks_use_order_number', get_option('wc_fb_use_order_number') ? 'yes' : 'no');
                // invoice number prefix
                update_option('wc_freshbooks_invoice_number_prefix', get_option('wc_fb_inv_num_prefix'));
                // auto-send invoice
                update_option('wc_freshbooks_auto_send_invoices', get_option('wc_fb_send_invoice') ? 'yes' : 'no');
                // auto-apply payments
                update_option('wc_freshbooks_auto_apply_payments', get_option('wc_fb_add_payments') ? 'yes' : 'no');
                // mark as migrated
                update_option('wc_freshbooks_upgraded_from_v2', 1);
                // remove old options
                $old_options = array('wc_fb_api_url', 'wc_fb_api_token', 'wc_fb_create_client', 'wc_fb_generic_client', 'wc_fb_add_payments', 'wc_fb_send_invoice', 'wc_fb_send_method', 'wc_fb_use_order_number', 'wc_fb_inv_num_prefix');
                foreach ($old_options as $option) {
                    delete_option($option);
                }
            }
        }
    }
    // end \WC_FreshBooks class
    /**
     * Returns the One True Instance of FreshBooks
     *
     * @since 3.3.0
     * @return WC_FreshBooks
     */
    function wc_freshbooks()
    {
        return WC_FreshBooks::instance();
    }
    /**
     * The WC_FreshBooks global object
     *
     * @deprecated 3.3.0
     * @name $wc_freshbooks
     * @global WC_FreshBooks $GLOBALS['wc_freshbooks']
     */
    $GLOBALS['wc_freshbooks'] = wc_freshbooks();
}
 /**
  * Verify the webhook once it's been added. After creating a webhook through
  * the FreshBooks API, it sends a POST to the URL specified, along with
  * a verifier that must then be POST'ed back to verify ownership of
  * the webhook URL
  *
  * @link http://developers.freshbooks.com/webhooks/
  *
  * @since 3.0
  */
 public function verify_callback($callback_id, $data)
 {
     try {
         wc_freshbooks()->get_api()->verify_webhook($callback_id, $data['verifier']);
     } catch (SV_WC_API_Exception $e) {
         wc_freshbooks()->log(sprintf('Callback verification failed: %s', $e->getMessage()));
     }
 }
 /**
  * Return an array of active invoice items
  *
  * @since 3.0
  * @return array
  */
 private function get_active_items()
 {
     $active_items = array();
     try {
         foreach (wc_freshbooks()->get_api()->get_active_items() as $item) {
             $active_items[$item['name']] = $item['name'];
         }
     } catch (SV_WC_API_Exception $e) {
         wc_freshbooks()->log($e->getMessage());
     }
     return $active_items;
 }
 /**
  * Returns the main plugin class
  *
  * @since 3.2.0
  * @see \SV_WC_API_Base::get_plugin()
  * @return object
  */
 protected function get_plugin()
 {
     return wc_freshbooks();
 }
 /**
  * Handle payment webhook events
  *
  * @link http://developers.freshbooks.com/docs/callbacks/#events
  *
  * @param string $action the event type
  * @param string $payment_id the associated payment ID
  * @since 3.0
  */
 public function handle_payment($action, $payment_id)
 {
     try {
         // try to find matching order by payment ID
         $order_id = $this->get_object_by_freshbooks_id('order', 'payment', $payment_id);
         // if a payment was created in FreshBooks manually, look up the associated invoice first
         if (!$order_id) {
             $payment = wc_freshbooks()->get_api()->get_payment($payment_id);
             // no payment found
             if (!$payment) {
                 return;
             }
             // invoice IDs included as part of the payment response contain
             // leading zeroes for some odd reason, so strip them first
             $order_id = $this->get_object_by_freshbooks_id('order', 'invoice', ltrim($payment['invoice_id'], '0'));
             // no matching invoice found, likely deleted
             if (!$order_id) {
                 return;
             }
         }
         $order = new WC_FreshBooks_Order($order_id);
         if ('delete' == $action) {
             delete_post_meta($order->id, '_wc_freshbooks_payment_id', $payment_id);
         } else {
             update_post_meta($order->id, '_wc_freshbooks_payment_id', $payment_id);
         }
         $order->refresh_invoice();
     } catch (SV_WC_API_Exception $e) {
         wc_freshbooks()->log(sprintf('Payment handling failed: %s', $e->getMessage()));
     }
 }