diff --git a/admin/post-types/wc_product_tab.php b/admin/post-types/wc_product_tab.php new file mode 100644 index 0000000..a43789c --- /dev/null +++ b/admin/post-types/wc_product_tab.php @@ -0,0 +1,471 @@ +ID ) ) { + wp_dequeue_script( 'autosave' ); + } +} + + +add_filter( 'bulk_actions-edit-wc_product_tab', 'wc_tab_manager_edit_product_tab_bulk_actions' ); + +/** + * Remove the bulk edit action for product tabs, it really isn't useful + * + * @access public + * @param array $actions associative array of action identifier to name. + * + * @return array associative array of action identifier to name + */ +function wc_tab_manager_edit_product_tab_bulk_actions( $actions ) { + + unset( $actions['edit'] ); + + // Remove the date filter dropdown as well. + add_filter( 'disable_months_dropdown', '__return_true' ); + + return $actions; +} + + +add_filter( 'views_edit-wc_product_tab', 'wc_tab_manager_edit_product_tab_views' ); + +/** + * Modify the 'views' links, ie All (3) | Publish (1) | Draft (1) | Private (2) | Trash (3) + * shown above the product tabs list table, to hide the publish/private states, + * which are not important and confusing for product tab objects. + * + * @access public + * @param array $views associative-array of view state name to link. + * + * @return array associative array of view state name to link + */ +function wc_tab_manager_edit_product_tab_views( $views ) { + + // Publish and private are not important distinctions for product tabs. + unset( $views['publish'], $views['private'], $views['future'] ); + + return $views; +} + + +add_filter( 'manage_edit-wc_product_tab_columns', 'wc_tab_manager_edit_product_tab_columns' ); + +/** + * Columns for product tab page + * + * @access public + * @param array $columns associative-array of column identifier to header names. + * + * @return array associative-array of column identifier to header names for the product tabs page + */ +function wc_tab_manager_edit_product_tab_columns( $columns ) { + + $columns = array( + 'cb' => '', + 'name' => esc_html__( 'Name', 'woocommerce-tab-manager' ), + 'type' => esc_html__( 'Tab Type', 'woocommerce-tab-manager' ), + 'parent-product' => esc_html__( 'Parent Product', 'woocommerce-tab-manager' ), + 'product-cat' => esc_html__( 'Categories', 'woocommerce-tab-manager' ), + ); + + if ( 'yes' === get_option( 'wc_tab_manager_enable_search', 'yes' ) ) { + + $is_searchable_text = esc_html__( 'Searchable?', 'woocommerce-tab-manager' ); + $is_searchable_html = ''; + + $columns['is-searchable'] = $is_searchable_html; + } + + return $columns; +} + + +add_action( 'manage_wc_product_tab_posts_custom_column', 'wc_tab_manager_custom_product_tab_columns', 2 ); + + +/** + * Custom Column values for product tabs page + * + * @access public + * @param string $column column identifier. + */ +function wc_tab_manager_custom_product_tab_columns( $column ) { + global $post; + + switch ( $column ) { + + case 'name': + + echo ''; + + // Add the parent product name if any. + if ( $post->post_parent ) { + $parent = wc_get_product( $post->post_parent ); + echo esc_html( $parent->get_title() ) . ' - '; + } + + $edit_link = get_edit_post_link( $post->ID ); + echo '' . esc_html__( _draft_or_post_title(), 'woocommerce-tab-manager' ) . ''; + + // Display post states a little more selectively than `_post_states( $post )`. + if ( 'draft' === $post->post_status ) { + echo ' (' . esc_html__( 'Draft', 'woocommerce-tab-manager' ) . ')'; + } + + echo ''; + + // Get actions. + $actions = array( + 'id' => "ID: {$post->ID}", + 'slug' => "Slug: {$post->post_name}", + ); + + $post_type_object = get_post_type_object( $post->post_type ); + + if ( current_user_can( $post_type_object->cap->delete_post, $post->ID ) ) { + if ( 'trash' === $post->post_status ) { + // Before: `post.php?post=%d` + // After: `post.php?post={$post->ID}&action=untrash`. + $untrash_link = add_query_arg( + array( 'action' => 'untrash' ), + sprintf( $post_type_object->_edit_link, $post->ID ) + ); + + /** + * `check_admin_referer( 'untrash-post_' . $post_id )` is + * used internally to check if the link is valid. The nonce + * action name has to match that or the link won't work. + * + * @link https://core.trac.wordpress.org/browser/trunk/src/wp-admin/post.php#L227 + */ + $untrash_link = wp_nonce_url( + admin_url( $untrash_link ), + "untrash-post_{$post->ID}" + ); + $actions['untrash'] = '' . esc_html__( 'Restore', 'woocommerce-tab-manager' ) . ''; + } else { + if ( defined( 'EMPTY_TRASH_DAYS' ) && EMPTY_TRASH_DAYS ) { + $trash_link = get_delete_post_link( $post->ID ); + $actions['trash'] = '' . esc_html__( 'Trash', 'woocommerce-tab-manager' ) . ''; + } else { + $delete_link = get_delete_post_link( $post->ID, '', true ); + $actions['delete'] = '' . esc_html__( 'Delete Permanently', 'woocommerce-tab-manager' ) . ''; + } + } + } + + $actions = apply_filters( 'post_row_actions', $actions, $post ); + + echo '
'; + + $i = 0; + $action_count = count( $actions ); + + foreach ( $actions as $action => $link ) { + $sep = ( $i === $action_count - 1 ) ? '' : ' | '; + $link .= $sep; + // @codingStandardsIgnoreStart - phpcs complains about `$link` not being escaped. + echo '' . $link . ''; + // @codingStandardsIgnoreEnd + $i ++; + } + echo '
'; + break; + + case 'type': + + if ( $post->post_parent ) { + esc_html_e( 'Product', 'woocommerce-tab-manager' ); + } else { + esc_html_e( 'Global', 'woocommerce-tab-manager' ); + } + + break; + + case 'parent-product': + + if ( $post->post_parent ) { + $parent_product = wc_get_product( $post->post_parent ); + echo '' . esc_html( $parent_product->get_title() ) . ''; + } else { + echo '' . esc_html__( 'N/A', 'woocommerce-tab-manager' ) . ''; + } + + break; + + case 'product-cat': + + $product_cats = get_post_meta( $post->ID, '_wc_tab_categories', true ); + + if ( ! empty( $product_cats ) ) { + + foreach ( $product_cats as $term ) { + + $cat = get_term_by( 'id', $term, 'product_cat' ); + + if ( $cat && isset( $cat->name ) ) { + + isset( $multiple ) ? edit_term_link( $cat->name, ', ', '', $cat ) : edit_term_link( $cat->name, '', '', $cat ); + + $multiple = true; + } + } + + } else { + + echo '' . esc_html__( 'N/A', 'woocommerce-tab-manager' ) . ''; + } + + break; + + case 'is-searchable': + + if ( wc_tab_manager()->get_search_instance()->is_searchable_tab( $post->ID ) ) : ?> + + + + + + set( 'post_parent', 0 ); + } else if ( 'product' === $_GET['product_tab_type'] ) { + $query->set( 'post_parent__not_in', array( 0 ) ); + } + } + + return $query; +} + + +add_action( 'restrict_manage_posts', 'wc_tab_manager_product_tabs_by_type', 20 ); + +/** + * Render the "Show All Types" dropdown filter menu on the Product Tabs + * page so that tabs can be filtered on their type (product/global) + * + * @access public + */ +function wc_tab_manager_product_tabs_by_type() { + global $typenow; + + if ( 'wc_product_tab' === $typenow ) : + $product_tab_type = isset( $_GET['product_tab_type'] ) ? $_GET['product_tab_type'] : ''; + ?> + + posts} AS product_parents ON ( {$wpdb->posts}.post_parent = 0 OR ( {$wpdb->posts}.post_parent = product_parents.ID AND product_parents.post_status != 'trash' ) )"; + } + + return $join; +} + + +add_filter( 'posts_groupby', 'wc_tab_manager_tabs_posts_groupby', 10, 2 ); + +/** + * Group the returned tab posts for the Tabs table by ID. This is to compensate + * for the extra global tabs returend by the join query above. + * + * @since 1.0.6 + * @param string $groupby the group by query. + * @param \WP_Query $query the query object. + * @return string + */ +function wc_tab_manager_tabs_posts_groupby( $groupby, $query ) { + global $wpdb, $typenow; + + if ( 'wc_product_tab' === $typenow ) { + $groupby = "{$wpdb->posts}.ID"; + } + + return $groupby; +} + + +add_action( 'delete_post', 'wc_tab_manager_delete_post' ); + +/** + * Invoked when a WordPress post is deleted. If the post is a product + * with child tabs, delete them as well to avoid leaving any orphans. + * + * @access public + * @param int $post_id post identifier. + */ +function wc_tab_manager_delete_post( $post_id ) { + + if ( ! current_user_can( 'delete_posts' ) || ! $post_id ) { + return; + } + + // Does this post have any attached product tabs? + $posts = get_posts( array( + 'numberposts' => - 1, + 'post_type' => 'wc_product_tab', + 'post_parent' => $post_id, + ) ); + + if ( $posts ) { + + foreach ( $posts as $post ) { + wp_delete_post( $post->ID ); + } + + } +} + + +add_action( 'woocommerce_duplicate_product', 'wc_tab_manager_duplicate_product', 10, 2 ); + +/** + * Invoked when a product is duplicated and duplicates any product tabs + * + * @since 1.0.6 + */ +function wc_tab_manager_duplicate_product( $new_id, $original_post ) { + $tabs = get_post_meta( $new_id, '_product_tabs', true ); + + if ( is_array( $tabs ) ) { + + $is_updated = false; + foreach ( $tabs as $key => $tab ) { + if ( 'product' === $tab['type'] ) { + $tab_post = get_post( $tab['id'] ); + + $new_tab_data = array( + 'post_title' => $tab_post->post_title, + 'post_content' => $tab_post->post_content, + 'post_status' => 'publish', + 'ping_status' => 'closed', + 'post_author' => get_current_user_id(), + 'post_type' => 'wc_product_tab', + 'post_parent' => $new_id, + 'post_password' => uniqid( 'tab_' ), + // Protects the post just in case. + ); + + // Link up the product to its new tab. + $tabs[ $key ]['id'] = wp_insert_post( $new_tab_data ); + $is_updated = true; + } + } + + if ( $is_updated ) { + update_post_meta( $new_id, '_product_tabs', $tabs ); + } + } +} diff --git a/admin/post-types/wc_product_tab_metabox.php b/admin/post-types/wc_product_tab_metabox.php new file mode 100644 index 0000000..0728132 --- /dev/null +++ b/admin/post-types/wc_product_tab_metabox.php @@ -0,0 +1,161 @@ +post_parent ) { + ?>

+

+ + + + +

+ + + + + ' . esc_html__( 'Tabs', 'woocommerce-tab-manager' ) . ''; +} + + +add_action( 'woocommerce_product_data_panels', 'wc_tab_manager_product_tabs_panel_content' ); + +/** + * Adds the "Tabs" tab panel to the Product Data postbox in the product interface. + */ +function wc_tab_manager_product_tabs_panel_content() { + global $post; + + wc_tab_manager_sortable_product_tabs( get_post_meta( $post->ID, '_product_tabs', true ) ); +} + + +add_action( 'woocommerce_process_product_meta', 'wc_tab_manager_process_product_meta_tabs_tab', 10, 2 ); + +/** + * Create/Update/Delete the product tabs + * + * @access public + * @param int $post_id the post identifier + * @param \WP_Post $post the post object + */ +function wc_tab_manager_process_product_meta_tabs_tab( $post_id, $post ) { + + $new_tabs = wc_tab_manager_process_tabs( $post_id, $post ); + + $old_tabs = get_post_meta( $post_id, '_product_tabs', true ); + + if ( ! is_array( $old_tabs ) ) { + $old_tabs = array(); + } + + update_post_meta( $post_id, '_product_tabs', $new_tabs ); + + do_action( 'wc_tab_manager_product_tabs_updated', $new_tabs, $old_tabs ); + + // Whether the tab layout defined at the product level should be used. + $override_tab_layout = isset( $_POST['_override_tab_layout'] ) && $_POST['_override_tab_layout'] ? 'yes' : 'no'; + + update_post_meta( $post_id, '_override_tab_layout', $override_tab_layout ); + + // Update / remove tab content meta. + $args = array( + 'product_id' => $post_id, + ); + + if ( 'yes' === $override_tab_layout ) { + $args['action'] = 'update'; + } else { + $args['action'] = 'remove'; + } + + // Extract product & global tab IDs from tab data array. + $tab_id_list = array(); + foreach ( $new_tabs as $key => $tab ) { + if ( 'product' === $tab['type'] || 'global' === $tab['type'] ) { + $tab_id_list[] = $tab['id']; + } + } + + // Only update meta if we have any tabs to process. + if ( ! empty( $tab_id_list ) ) { + wc_tab_manager()->get_search_instance()->update_products_for_tabs( $tab_id_list, $args ); + } +} diff --git a/admin/post-types/writepanels/writepanels-init.php b/admin/post-types/writepanels/writepanels-init.php new file mode 100644 index 0000000..06d3ccc --- /dev/null +++ b/admin/post-types/writepanels/writepanels-init.php @@ -0,0 +1,160 @@ +get_plugin_path() . '/admin/post-types/writepanels/writepanel-product_data-tabs.php' ); + +/** Product Tab Actions writepanel */ +include_once( wc_tab_manager()->get_plugin_path() . '/admin/post-types/writepanels/writepanel-product-tab_actions.php' ); + + +/** + * Save meta boxes + */ +add_action( 'save_post_wc_product_tab', 'wc_tab_manager_meta_boxes_save', 1, 3 ); + +/** + * Runs when a post is saved and does an action which the write panel save scripts can hook into. + * + * @access public + * @param int $post_id post identifier + * @param object $post post object + */ +function wc_tab_manager_meta_boxes_save( $post_id, $post, $is_update ) { + + $has_post_data = ( ! empty( $post_id ) && ! empty( $post ) && ! empty( $_POST ) ); + $is_revision = is_int( wp_is_post_revision( $post ) ); + $is_autosave = is_int( wp_is_post_autosave( $post ) ); + $doing_autosave = ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ); + $is_tab_update = ( ! $is_revision && ! $is_autosave && ! $doing_autosave ); + $is_nonce_valid = ( ! empty( $_POST['woocommerce_meta_nonce'] ) && wp_verify_nonce( $_POST['woocommerce_meta_nonce'], 'woocommerce_save_data' ) ); + $user_can_edit = current_user_can( 'edit_post', $post_id ); + + if ( ! $has_post_data || ! $is_tab_update || ! $is_nonce_valid || ! $user_can_edit ) { + return; + } + + // {BR 2017-08-15} This whole section is wonky and needs to be re-thought; we were previously updating meta for + // every product on every global tab save, whether included in search or not, so at least we ask the user to use the + // ajax batch process instead, but we could use a direct link for it instead to run the update. + + // determine if the current tab should be included in search results + if ( isset( $_POST['tab_type'] ) && 'global' === $_POST['tab_type'] ) { + + if ( isset( $_POST['_include_in_search'] ) && 'yes' === $_POST['_include_in_search'] ) { + + wc_tab_manager()->get_search_instance()->add_searchable_tab( $post_id ); + + /* translators: Placeholders: %1$s - , %2$s - */ + $message = sprintf( __( 'Please %1$srun the tab search update%2$s to ensure the new tab content is indexed and searchable.', 'woocommerce-tab-manager' ), + '', + '' + ); + + wc_tab_manager()->get_message_handler()->add_message( $message ); + + } else { + wc_tab_manager()->get_search_instance()->remove_searchable_tab( $post_id ); + } + } + + do_action( 'woocommerce_process_wc_product_tab_meta', $post_id, $post ); +} + +// protect tab posts +add_action( 'publish_wc_product_tab', 'wc_tab_manager_protect_tab', 10, 2 ); + +/** + * Automatically protect the product tab posts + * + * @access public + * @param int $post_id the post tab identifier + * @param object $post the post tab object + */ +function wc_tab_manager_protect_tab( $post_id, $post ) { + global $wpdb; + + if ( ! $post->post_password ) { + + $wpdb->update( $wpdb->posts, array( 'post_password' => uniqid( 'tab_' ) ), array( 'ID' => $post_id ) ); + + } +} + + +/** + * Init the meta boxes. + * + * Inits the write panels for Product Tabs. Also removes unused default write panels. + * + * @access public + */ +function wc_tab_manager_meta_boxes() { + + add_meta_box( 'wc-tab-manager-product-tab-actions', __( 'Tab Actions', 'woocommerce-tab-manager' ), 'wc_tab_manager_product_tab_actions_meta_box', 'wc_product_tab', 'side', 'high' ); +} + +add_action( 'add_meta_boxes_wc_product_tab', 'wc_tab_manager_meta_boxes' ); + + +/** + * Remove any meta box that isn't whitelisted. + * + * @since 1.4.0 + * @param string $post_type The current post type. + * @param \WP_Post $post The current post object. + */ +function wc_tab_manager_remove_meta_boxes( $post_type, $post ) { + + if ( 'wc_product_tab' !== $post_type ) { + return; + } + + $screen = get_current_screen(); + + $allowed_meta_box_ids = apply_filters( 'wc_tab_manager_allowed_meta_box_ids', array( + 'et_pb_layout', // Divi Builder + ) ); + + foreach ( $GLOBALS['wp_meta_boxes'][ $screen->id ] as $context => $meta_boxes_by_context ) { + foreach ( $meta_boxes_by_context as $subcontext => $meta_boxes_by_subcontext ) { + foreach ( $meta_boxes_by_subcontext as $meta_box_id => $meta_box ) { + if ( ! in_array( $meta_box_id, $allowed_meta_box_ids ) ) { + remove_meta_box( $meta_box_id, $post_type, $context ); + } + } + } + } +} + +add_action( 'add_meta_boxes', 'wc_tab_manager_remove_meta_boxes', 30, 2 ); diff --git a/admin/woocommerce-tab-manager-admin-functions.php b/admin/woocommerce-tab-manager-admin-functions.php new file mode 100644 index 0000000..856d1e5 --- /dev/null +++ b/admin/woocommerce-tab-manager-admin-functions.php @@ -0,0 +1,509 @@ + + +
+

+ ID, '_override_tab_layout', true ); + ?> + /> + + / +

+ +
+
+ + get_core_tabs(); + + if ( 'product' === $typenow ) { + global $post; + + $product_terms = get_the_terms( $post->ID, 'product_cat' ); + $product_cats = array(); + + if ( is_array( $product_terms ) ) { + $product_cats = wp_list_pluck( $product_terms, 'term_id' ); + } + } + + // get any global tabs + $global_tabs = array(); + $posts = get_posts( array( 'numberposts' => -1, 'post_type' => 'wc_product_tab', 'post_parent' => 0, 'post_status' => 'publish', 'suppress_filters' => false ) ); + + foreach ( $posts as $post_obj ) { + + // compare selected categories for the tab vs the product categories + $tab_cats = get_post_meta( $post_obj->ID, '_wc_tab_categories', true ); + + // don't add global tabs that won't be shown for this product + if ( isset( $product_cats ) && ! empty( $tab_cats ) && ! array_intersect( $product_cats, $tab_cats ) ) { + continue; + } + + $tab = array( 'id' => $post_obj->ID, 'position' => 0, 'type' => 'global', 'title' => $post_obj->post_title ); + list( $tab['content'] ) = explode( "\n", wordwrap( str_replace( "\n", "", strip_shortcodes( strip_tags( $post_obj->post_content ) ) ), 155 ) ); + + // content excerpt + if ( strlen( $post_obj->post_content ) > 155 ) { + $tab['content'] .= '...'; + } + + if ( $tab['content'] ) { + $tab['content'] .= ' - '; + } + + $tab['content'] .= ' ' . __( 'Edit Global Tab Content', 'woocommerce-tab-manager' ) . ''; + + $global_tabs[ 'global_tab_' . $post_obj->ID ] = $tab; + } + + // get any 3rd party tabs + $third_party_tabs = array(); + + foreach ( wc_tab_manager()->get_third_party_tabs() as $id => $tab ) { + if ( ! isset( $tab['ignore'] ) || false === $tab['ignore'] ) { + $third_party_tabs[ $id ] = array( 'id' => $tab['id'], 'position' => 0, 'type' => 'third_party', 'title' => $tab['title'], 'description' => $tab['description'] ); + } + } + + // if no tabs are set (for this product) try defaulting to the default tab layout, if it exists + if ( ! is_array( $tabs ) ) { + $tabs = get_option( 'wc_tab_manager_default_layout', false ); + } + + // if no default tab layout either, default to the core + 3rd party tabs + if ( ! is_array( $tabs ) ) { + $tabs = $core_tabs + $third_party_tabs; + } else { + // otherwise, get the content and title for any product/global tabs, and verify that any global/3rd party tabs still exist + foreach ( $tabs as $id => $tab ) { + + if ( 'global' === $tab['type'] ) { + // global tab: get an excerpt of the content to display if any, or if the tab has been removed or trashed, remove it from view + + if ( isset( $global_tabs[ $id ] ) ) { + $tabs[ $id ]['title'] = $global_tabs[ $id ]['title']; + $tabs[ $id ]['content'] = $global_tabs[ $id ]['content']; + } else { + // global tab is gone + unset( $tabs[ $id ] ); + } + + } elseif ( 'third_party' === $tab['type'] ) { + // 3rd party tab, does the plugin still exist? + + if ( isset( $third_party_tabs[ $id ] ) ) { + $tabs[ $id ]['title'] = $third_party_tabs[ $id ]['title']; + $tabs[ $id ]['description'] = $third_party_tabs[ $id ]['description']; + } else { + unset( $tabs[ $id ] ); + } + + } elseif ( 'product' === $tab['type'] ) { + // get any custom product tab content from the underlying post + + $tab_post = get_post( $tab['id'] ); + + if ( $tab_post && 'publish' === $tab_post->post_status ) { + $tabs[ $id ]['content'] = $tab_post->post_content; + $tabs[ $id ]['title'] = $tab_post->post_title; + } else { + // product tab is gone + unset( $tabs[ $id ] ); + } + + } + } + } + + // markup for all core, global and 3rd party tabs will be rendered, and if not currently added to the product, they will be hidden until added + $combined_tabs = array_merge( $core_tabs, $global_tabs, $third_party_tabs, $tabs ); + + $i = 0; + foreach ( $combined_tabs as $id => $tab ) { + + $position = $tab['position']; + + $active = isset( $tabs[ $id ] ); + + // for the core tabs, even if the title is changed, keep the displayed name the same in the bar so there's less confusion + $name = 'core' === $tab['type'] ? $core_tabs[ $id ]['title'] : $tab['title']; + // handle the Reviews tab specially by cutting off the ' (%d)' which looks like garbage in the sortable tab list + if ( 'core' === $tab['type'] && 'reviews' === $tab['id'] ) { + $name = substr( $name, 0, -4 ); + } + + ?> + + +
+ + +

+ + +

+ +
+
+
+
+ $tab_positions[ $i ], 'type' => $tab_types[ $i ], 'id' => $tab_ids[ $i ] ); + + if ( isset( $tab_titles[ $i ] ) ) $tab['title'] = $tab_titles[ $i ]; + + if ( 'product' === $tab['type'] ) { + + if ( ! $tab['id'] ) { + // new custom product tab + + $new_tab_data = array( + 'post_title' => $tab_titles[ $i ], + 'post_content' => $tab_content[ $i ], + 'post_status' => 'publish', + 'ping_status' => 'closed', + 'post_author' => get_current_user_id(), + 'post_type' => 'wc_product_tab', + 'post_parent' => $post_id, + 'post_password' => uniqid( 'tab_', false ) // Protects the post just in case + ); + + $tab['id'] = wp_insert_post( $new_tab_data ); + } else { + // update existing custom product tab + + $tab_data = array( + 'ID' => $tab['id'], + 'post_title' => $tab_titles[ $i ], + 'post_content' => $tab_content[ $i ], + ); + wp_update_post( $tab_data ); + } + + } + + // only the core description and additional information tabs have a heading + if ( isset( $tab_headings[ $i ] ) ) { + $tab['heading'] = $tab_headings[ $i ]; + } + + $tabs[ $tab['type'] . '_tab_' . $tab['id'] ] = $tab; + } else { + // tab removed + if ( 'product' === $tab_types[ $i ] ) { + // for product custom tabs, remove the tab post record + wp_delete_post( $tab_ids[ $i ] ); + } + } + } + + // sort the tabs according to position + if ( ! function_exists( 'product_tabs_cmp' ) ) { + + function product_tabs_cmp( $a, $b ) { + + if ( $a['position'] == $b['position'] ) { + return 0; + } + + return $a['position'] < $b['position'] ? -1 : 1; + } + } + + uasort( $tabs, 'product_tabs_cmp' ); + + // make sure the position values are 0, 1, 2 ... + $i = 0; + foreach ( $tabs as &$tab ) { + $tab['position'] = $i++; + } + + + // it's important to generate unique names to use for the tab/tab panel css ids, so that + // clicking a tab brings up the correct tab panel (since we can't change their names) + // We'll generate names like 'description', 'description-1', 'description-2', etc + $found_names = array(); + $tab_names = array(); + + // first off, the core tabs get priority on naming (which for them is their id) + foreach ( $tabs as &$tab ) { + + if ( 'core' === $tab['type'] ) { + + $tab_name = $tab['id']; + + if ( ! isset( $found_names[ $tab_name ] ) ) { + $found_names[ $tab_name ] = 0; + } + + $found_names[ $tab_name ]++; + } + } + + // next up: the 3rd party tabs; we don't want to clash with their keys + foreach ( $tabs as &$tab ) { + + if ( 'third_party' === $tab['type'] ) { + + $tab_name = $tab['id']; + + if ( ! isset( $found_names[ $tab_name ] ) ) { + $found_names[ $tab_name ] = 0; + } + + $found_names[ $tab_name ]++; + } + } + + // next up are the global tabs + foreach ( $tabs as &$tab ) { + + if ( 'global' === $tab['type'] ) { + + // see product tab comment below for naming discussion + if ( strlen( $tab['title'] ) !== strlen( utf8_encode( $tab['title'] ) ) ) { + $tab_name = 'global-tab'; + } else { + $tab_name = sanitize_title( $tab['title'] ); + } + + if ( ! isset( $found_names[ $tab_name ] ) ) { + $found_names[ $tab_name ] = 0; + } + + $found_names[ $tab_name ]++; + + if ( $found_names[ $tab_name ] > 1 ) $tab_name .= '-' . ( $found_names[ $tab_name ] - 1 ); + + $tab['name'] = $tab_name; + + // once the title is used to generate the unique name, it is no longer needed as it will be pulled from the tab post + unset( $tab['title'] ); + } + } + + // finally the custom product tabs + foreach ( $tabs as &$tab ) { + + if ( 'product' === $tab['type'] ) { + + // we try to generate a clean unique tab name based off of the tab title, + // however the page javascript (jquery) that controls the tab switching can not + // handle unicode class id's, escaped or otherwise. The compromise is to + // use the "pretty" name for non-unicode strings, and just use a safe "product-tab" + // identifier for tab titles containing unicode + if ( strlen( $tab['title'] ) !== strlen( utf8_encode( $tab['title'] ) ) ) { + $tab_name = 'product-tab'; + } else { + $tab_name = sanitize_title( $tab['title'] ); + } + + if ( ! isset( $found_names[ $tab_name ] ) ) { + $found_names[ $tab_name ] = 0; + } + + $found_names[ $tab_name ]++; + + if ( $found_names[ $tab_name ] > 1 ) { + $tab_name .= '-' . ( $found_names[ $tab_name ] - 1 ); + } + + $tab['name'] = $tab_name; + + // once the title is used to generate the unique name, it is no longer needed as it will be pulled from the tab post + unset( $tab['title'] ); + } + } + + return $tabs; +} diff --git a/admin/woocommerce-tab-manager-admin-global-layout.php b/admin/woocommerce-tab-manager-admin-global-layout.php new file mode 100644 index 0000000..290ca6b --- /dev/null +++ b/admin/woocommerce-tab-manager-admin-global-layout.php @@ -0,0 +1,107 @@ + +
+ +
+

+
+ +

+ +
+ + + +
+
+ + +
+ +
+
+
+ +

+ + +

+ +
+
+ \WC_TAB_MANAGER::PLUGIN_ID, 'result' => 'saved' ), admin_url( 'admin.php' ) ) ); +} diff --git a/admin/woocommerce-tab-manager-admin-init.php b/admin/woocommerce-tab-manager-admin-init.php new file mode 100644 index 0000000..acbbe1c --- /dev/null +++ b/admin/woocommerce-tab-manager-admin-init.php @@ -0,0 +1,357 @@ + 'edit-wc_product_tab', + 'tab-edit' => 'wc_product_tab', + 'defaults' => 'admin_page_tab_manager', + ); + + $is_admin_page = $screen && in_array( $screen->id, $known_screen_ids, true ); + + if ( ! $return_detailed_info ) { + return $is_admin_page; + } + + $data = array( + 'result' => $is_admin_page, + 'screens' => $known_screen_ids, + 'is_page' => array( + 'tab-list' => $known_screen_ids['tab-list'] === $screen->id, + 'tab-edit' => $known_screen_ids['tab-edit'] === $screen->id, + 'defaults' => $known_screen_ids['defaults'] === $screen->id, + ), + ); + + return $data; +} + +add_action( 'admin_init', 'wc_tab_manager_admin_init' ); + +/** + * Initialize the admin, adding actions to properly display and handle + * the Tab Manager admin custom tabs and panels + * @access public + */ +function wc_tab_manager_admin_init() { + global $pagenow; + + // on the product new/edit page + if ( 'post-new.php' === $pagenow || 'post.php' === $pagenow ) { + include_once( wc_tab_manager()->get_plugin_path() . '/admin/post-types/writepanels/writepanels-init.php' ); + } +} + + +add_action( 'admin_head', 'wc_tab_manager_admin_menu_highlight' ); + +/** + * Highlight the correct top level admin menu item for the product tab post type add screen + * @access public + */ +function wc_tab_manager_admin_menu_highlight() { + + global $parent_file, $submenu_file; + + if ( wc_tab_manager_is_tab_admin_page() ) { + $submenu_file = 'edit.php?post_type=wc_product_tab'; + $parent_file = 'woocommerce'; + } +} + +add_action( 'admin_enqueue_scripts', 'wc_tab_manager_admin_enqueue_scripts', 15 ); + +/** + * Add necessary admin scripts + * @access public + */ +function wc_tab_manager_admin_enqueue_scripts() { + + // get screen info + $screen_id = get_current_screen()->id; + // figure out which page we're on + $is_default_layout_page = 'admin_page_tab_manager' === $screen_id; + $is_custom_layout_page = 'product' === $screen_id; + + // need this for the drag-and-drop to work on the Default Tab Layout page + if ( $is_default_layout_page ) { + + wp_enqueue_script( 'jquery-ui-sortable' ); + wp_enqueue_script( 'woocommerce_admin' ); + wp_enqueue_script( 'media-upload' ); + wp_enqueue_script( 'thickbox' ); + wp_enqueue_script( + 'woocommerce_admin_meta_boxes', + WC()->plugin_url() . '/assets/js/admin/meta-boxes.min.js', + array( 'jquery', 'jquery-ui-datepicker', 'jquery-ui-sortable', 'accounting', 'round' ), + WC()->version, + true + ); + + // keep the `woocommerce_admin_meta_boxes` script happy + wp_localize_script( 'woocommerce_admin_meta_boxes', 'woocommerce_admin_meta_boxes', array( + 'ajax_url' => admin_url( 'admin-ajax.php' ) + ) ); + } + + if ( $is_default_layout_page || $is_custom_layout_page ) { + + wp_enqueue_script( + 'wc_tab_manager_admin', + wc_tab_manager()->get_plugin_url() . '/assets/js/admin/wc-tab-manager-admin.min.js', + array( 'jquery' ), + \WC_Tab_Manager::VERSION, + true + ); + + wp_localize_script( 'wc_tab_manager_admin', 'wc_tab_manager_admin_params', array( + 'remove_product_tab' => __( 'Remove this product tab?', 'woocommerce-tab-manager' ), + 'remove_label' => __( 'Remove', 'woocommerce-tab-manager' ), + 'click_to_toggle' => __( 'Click to toggle', 'woocommerce-tab-manager' ), + 'title_label' => __( 'Title', 'woocommerce-tab-manager' ), + 'title_description' => __( 'The tab title, this appears in the tab', 'woocommerce-tab-manager' ), + 'content_label' => __( 'Content', 'woocommerce-tab-manager' ), + 'ajax_url' => admin_url( 'admin-ajax.php' ), + 'get_editor_nonce' => wp_create_nonce( 'get-editor' ), + ) ); + } +} + + +add_action( 'admin_print_styles', 'wc_tab_manager_admin_css' ); + +/** + * Queue required CSS + * @access public + */ +function wc_tab_manager_admin_css() { + + wp_enqueue_style( + 'wc_tab_manager_admin_styles', + wc_tab_manager()->get_plugin_url() . '/assets/css/admin/wc-tab-manager-admin.min.css', + array(), + \WC_Tab_Manager::VERSION + ); + + if ( wc_tab_manager_is_tab_admin_page() ) { + wp_enqueue_style( 'woocommerce_admin_styles', WC()->plugin_url() . '/assets/css/admin.css' ); + } +} + + +add_filter( 'post_updated_messages', 'wc_tab_manager_product_tab_updated_messages' ); + +/** + * Set the product updated messages so they're specific to the Product Tabs + * + * @access public + * @param array $messages array of update messages + * @return array + */ +function wc_tab_manager_product_tab_updated_messages( $messages ) { + global $post, $post_ID; + + $messages['wc_product_tab'] = array( + 0 => '', // Unused. Messages start at index 1. + 1 => __( 'Tab updated.', 'woocommerce-tab-manager' ), + 2 => __( 'Custom field updated.', 'woocommerce-tab-manager' ), + 3 => __( 'Custom field deleted.', 'woocommerce-tab-manager' ), + 4 => __( 'Tab updated.', 'woocommerce-tab-manager' ), + 5 => isset( $_GET['revision'] ) ? sprintf( __( 'Tab restored to revision from %s', 'woocommerce-tab-manager' ), wp_post_revision_title( (int) $_GET['revision'], false ) ) : false, + 6 => __( 'Tab updated.', 'woocommerce-tab-manager' ), + 7 => __( 'Tab saved.', 'woocommerce-tab-manager' ), + 8 => __( 'Tab submitted.', 'woocommerce-tab-manager' ), + 9 => sprintf( + __( 'Tab scheduled for: %1$s.', 'woocommerce-tab-manager' ), + date_i18n( __( 'M j, Y @ G:i', 'woocommerce-tab-manager' ), strtotime( $post->post_date ) ) + ), + 10 => __( 'Tab draft updated.', 'woocommerce-tab-manager' ), + ); + + return $messages; +} + + +add_action( 'all_admin_notices', 'wc_tab_manager_admin_nav', 1 ); + +/** + * Use JavaScript to alter the Product Tab post list/add/edit page header so that they become + * tabs, with a third tab taking us to the Default Tab Layout custom admin + * page. + * @access public + */ +function wc_tab_manager_admin_nav() { + + // first show any framework notices + wc_tab_manager()->get_message_handler()->show_messages(); + + $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null; + + if ( ! $screen ) { + return; + } + + $is_admin_page = false; + $tabs_active = ''; + $default_active = ''; + + switch ( $screen->id ) { + case 'edit-wc_product_tab' : + $is_admin_page = true; + $tabs_active = 'nav-tab-active'; + break; + case 'wc_product_tab' : + $is_admin_page = true; + break; + case 'admin_page_tab_manager' : + $is_admin_page = true; + $default_active = 'nav-tab-active'; + break; + } + + if ( ! $is_admin_page ) { + return; + } + + if ( ! empty( $_REQUEST['s'] ) ) { + $search_query = get_search_query(); + } + + $hide_if_enhanced_navigation = Framework\SV_WC_Helper::is_wc_navigation_enabled() ? 'style="display: none;' : ''; + + ?> +

> + + + + + + + + + + + +

+ 'woocommerce-tab-manager', + 'title' => __( 'Tab Manager', 'woocommerce-tab-manager' ), + ] ); + + $post_type = \Automattic\WooCommerce\Admin\Features\Navigation\Menu::get_post_type_items( + 'wc_product_tab', + [ + 'parent' => 'woocommerce-tab-manager', + 'title' => __( 'All Tabs', 'woocommerce-tab-manager' ), + 'order' => 1, + ] + ); + + if ( isset( $post_type['all'] ) ) { + + \Automattic\WooCommerce\Admin\Features\Navigation\Menu::add_plugin_item( $post_type['all'] ); + + if ( isset( $post_type['new'] ) ) { + + $post_type['new']['order'] = 2; + $post_type['new']['title'] = __( 'Add New Global Tab', 'woocommerce-tab-manager' ); + + \Automattic\WooCommerce\Admin\Features\Navigation\Menu::add_plugin_item( $post_type['new'] ); + } + } + + \Automattic\WooCommerce\Admin\Features\Navigation\Menu::add_plugin_item( [ + 'id' => 'woocommerce-tab-manager-default-layout', + 'title' => __( 'Default Tab Layout', 'woocommerce-tab-manager' ), + 'url' => 'tab_manager', + 'parent' => 'woocommerce-tab-manager', + 'order' => 3, + ] ); +} diff --git a/assets/css/admin/wc-tab-manager-admin.min.css b/assets/css/admin/wc-tab-manager-admin.min.css new file mode 100644 index 0000000..c8aa8ac --- /dev/null +++ b/assets/css/admin/wc-tab-manager-admin.min.css @@ -0,0 +1 @@ +.wp-admin .ajax-spinner{position:relative;display:none;width:1em;height:1em;margin-left:1em;margin-right:1em}.wp-admin .ajax-spinner.active{display:inline-block}.wp-admin.admin_page_tab_manager .woo-nav-tab-wrapper,.wp-admin.post-type-wc_product_tab .woo-nav-tab-wrapper{margin:10px 20px 0 2px!important}.wp-admin.admin_page_tab_manager p.note,.wp-admin.post-type-wc_product_tab p.note{border:1px solid #ddd;float:left;margin-top:0;padding:8px}.wp-admin.admin_page_tab_manager #woocommerce-product-data,.wp-admin.post-type-wc_product_tab #woocommerce-product-data{margin-top:20px}.wp-admin.admin_page_tab_manager #woocommerce-product-data h3.hndle,.wp-admin.post-type-wc_product_tab #woocommerce-product-data h3.hndle{margin-bottom:0}.wp-admin.admin_page_tab_manager #woocommerce-product-data #woocommerce_product_tabs,.wp-admin.post-type-wc_product_tab #woocommerce-product-data #woocommerce_product_tabs{float:none;width:100%}.wp-admin.admin_page_tab_manager #woocommerce-product-data .panel-wrap,.wp-admin.post-type-wc_product_tab #woocommerce-product-data .panel-wrap{padding-left:0}.wp-admin.admin_page_tab_manager .wc_product_tab_actions li,.wp-admin.post-type-wc_product_tab .wc_product_tab_actions li{border-bottom:1px solid #ddd;border-top:1px solid #fff;line-height:1.6em;margin:0;padding:6px 0}.wp-admin.admin_page_tab_manager .wc_product_tab_actions li:last-child,.wp-admin.post-type-wc_product_tab .wc_product_tab_actions li:last-child{border-bottom:0} \ No newline at end of file diff --git a/assets/js/admin/wc-tab-manager-admin-batch-product-update.min.js b/assets/js/admin/wc-tab-manager-admin-batch-product-update.min.js new file mode 100644 index 0000000..dd29843 --- /dev/null +++ b/assets/js/admin/wc-tab-manager-admin-batch-product-update.min.js @@ -0,0 +1 @@ +"use strict";var _createClass=function(){function n(t,e){for(var a=0;a compB) ? 1 : 0; + }); + + // reorder + $wcProductTabItems.each( function(idx, itm) { $('.woocommerce_product_tabs').append(itm); } ); + + // update the product tab indexes, based on the current ordering + function productTabRowIndexes() { + $('.woocommerce_product_tabs .woocommerce_product_tab').each(function(index, el){ + $('.product_tab_position', el).val( parseInt( $(el).index('.woocommerce_product_tabs .woocommerce_product_tab'), 10 ) ); + }); + } + + // Add rows + $('button.add_product_tab').on('click', function() { + + var size = $('.woocommerce_product_tabs .woocommerce_product_tab').size(); + + var productTabId = $('select.product_tab').val(); + + if (!productTabId) { + + var data = { + action: 'wc_tab_manager_get_editor', + size: size, + security: wc_tab_manager_admin_params.get_editor_nonce + }; + + $.post( wc_tab_manager_admin_params.ajax_url, data, function(response) { + + $('.tab_content_editor_' + data.size).append( response ); + $('#producttabcontent' + data.size).val( $('#product_tab_content_' + data.size).val() ); + $('#product_tab_content_' + data.size).remove(); + try { + var qt = quicktags( { id:'producttabcontent' + data.size, buttons:'strong,em,link,block,del,ins,img,ul,ol,li,code,more,spell,close,fullscreen' } ); // jshint ignore:line + // force the quicktag buttons to render by calling the "prive" _buttonsInit() method + QTags._buttonsInit(); + // hook up the "Upload/Insert" links + $('#wp-producttabcontent' + data.size + '-wrap').mousedown(function(){ + wpActiveEditor = this.id.slice(3, -5); + }); + } catch(e) {} + + }); + + // Add custom tab row + $('.woocommerce_product_tabs').append('
\ +

\ + \ +
\ + \ +

\ + \ + \ + \ + \ +
\ +
\ +

\ + \ + ' + wc_tab_manager_admin_params.title_description + '\ +

\ +
\ +
\ + \ +
\ + \ + \ + \ + \ +
\ +
'); + + productTabTitleChange(); + + } else { + + // Reveal core/global row + var $thisrow = $('.woocommerce_product_tabs .woocommerce_product_tab.' + productTabId); + if (!$thisrow.is(':visible')) { + $('.woocommerce_product_tabs').append( $thisrow ); + $thisrow.find('.product_tab_active').val(1); + productTabRowIndexes(); + } + $thisrow.show().find('.woocommerce_product_tab_data').show(); + + } + + $('select.product_tab').val(''); + }); + + // listener to update the product tab name in the h3 when the tab title is changed + // (except for the core tabs, who's displayed names we do not change) + function productTabTitleChange() { + $('.woocommerce_product_tabs').on('blur', 'input.product_tab_title', function() { + var $parent = $(this).closest('.woocommerce_product_tab'); + if (!$parent.hasClass('product_tab_core')) { + $parent.find('strong.product_tab_name').text( $(this).val() ); + } + }); + } + productTabTitleChange(); + + + $('.woocommerce_product_tabs').on('click', 'button.remove_row', function() { + var answer = confirm(wc_tab_manager_admin_params.remove_product_tab); // jshint ignore:line + if (answer) { + var $parent = $(this).parent().parent(); + + $parent.hide(); + $parent.find('.product_tab_active').val(0); + productTabRowIndexes(); + } + return false; + }); + + // Attribute ordering + var tabs = $( '.woocommerce_product_tabs' ); + if ( tabs.length ) { + tabs.sortable({ + items:'.woocommerce_product_tab', + cursor:'move', + axis:'y', + handle: 'h3', + scrollSensitivity:40, + forcePlaceholderSize: true, + helper: 'clone', + opacity: 0.65, + placeholder: 'wc-metabox-sortable-placeholder', + start:function(event,ui){ + ui.item.css('background-color','#f6f6f6'); + }, + stop:function(event,ui){ + ui.item.removeAttr('style'); + productTabRowIndexes(); + } + }); + } + + // Override default layout handler + $('#_override_tab_layout').change(function() { + if ( $(this).is(':checked') ) { + $('#wc_tab_manager_block').hide(); + } else { + $('#wc_tab_manager_block').show(); + } + }).change(); +}); diff --git a/assets/js/admin/wc-tab-manager-admin.min.js b/assets/js/admin/wc-tab-manager-admin.min.js new file mode 100644 index 0000000..75411ef --- /dev/null +++ b/assets/js/admin/wc-tab-manager-admin.min.js @@ -0,0 +1 @@ +"use strict";jQuery(function(a){var t=a(".woocommerce_product_tabs").find(".woocommerce_product_tab");function c(){a(".woocommerce_product_tabs .woocommerce_product_tab").each(function(t,o){a(".product_tab_position",o).val(parseInt(a(o).index(".woocommerce_product_tabs .woocommerce_product_tab"),10))})}function r(){a(".woocommerce_product_tabs").on("blur","input.product_tab_title",function(){var t=a(this).closest(".woocommerce_product_tab");t.hasClass("product_tab_core")||t.find("strong.product_tab_name").text(a(this).val())})}t.sort(function(t,o){t=parseInt(a(t).attr("rel"),10),o=parseInt(a(o).attr("rel"),10);return t\t\t\t\t

\t\t\t\t\t\t\t\t\t\t
\t\t\t\t\t\t\t\t\t

\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t
\t\t\t\t\t\t\t
\t\t\t\t\t\t\t\t

\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t '+wc_tab_manager_admin_params.title_description+'\t\t\t\t\t\t\t\t

\t\t\t\t\t\t\t
\t\t\t\t\t\t\t
\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t
\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t
\t\t\t'),r()),a("select.product_tab").val("")}),r(),a(".woocommerce_product_tabs").on("click","button.remove_row",function(){var t;return confirm(wc_tab_manager_admin_params.remove_product_tab)&&((t=a(this).parent().parent()).hide(),t.find(".product_tab_active").val(0),c()),!1});t=a(".woocommerce_product_tabs");t.length&&t.sortable({items:".woocommerce_product_tab",cursor:"move",axis:"y",handle:"h3",scrollSensitivity:40,forcePlaceholderSize:!0,helper:"clone",opacity:.65,placeholder:"wc-metabox-sortable-placeholder",start:function(t,o){o.item.css("background-color","#f6f6f6")},stop:function(t,o){o.item.removeAttr("style"),c()}}),a("#_override_tab_layout").change(function(){a(this).is(":checked")?a("#wc_tab_manager_block").hide():a("#wc_tab_manager_block").show()}).change()}); \ No newline at end of file diff --git a/changelog.txt b/changelog.txt new file mode 100644 index 0000000..d5944cb --- /dev/null +++ b/changelog.txt @@ -0,0 +1,208 @@ +*** WooCommerce Tab Manager Changelog *** + +2023.06.20 - version 1.16.0 + * Fix - Register admin tab layout page properly to avoid deprecation messages in PHP 8+ + * Misc - Add compatibility for WooCommerce High Performance Order Storage (HPOS) + +2022.11.30 - version 1.15.0 + * Misc - Require PHP 7.4 and WordPress 5.6 + +2022.08.15 - version 1.14.2 + * Fix - Replace deprecated usages of `is_ajax()` in favor of `wp_doing_ajax()` + * Misc - Require WooCommerce 3.9.4 or newer + +2021.03.16 - version 1.14.1 + * Tweak - Simplify the paths in the Tab Manager admin screen to add a global tab by using a button only + * Tweak - Add support for the WooCommerce Navigation admin feature + * Localization - Update Italian translation + +2020.12.07 - version 1.14.0 + * Misc - Add compatibility for WooCommerce 4.7 + * Misc - Require PHP 7.0 or newer + +2020.09.30 - version 1.13.1 + * Misc - Remove the SkyVerge help menu item as part of the plugin assets + +2020.08.17 - version 1.13.0 + * Misc - Add SkyVerge help menu for support on WooCommerce.com connected sites + +2020.05.04 - version 1.12.2 + * Misc - Add support for WooCommerce 4.1 + +2020.03.05 - version 1.12.1 + * Misc - Add support for WooCommerce 4.0 + +2020.01.02 - version 1.12.0 + * Misc - Add support for WooCommerce 3.9 + * Misc - Add support for WooCommerce 3.8 + * Localization - Add Italian translation + +2019.08.07 - version 1.11.0 + * Misc - Add support for WooCommerce 3.7 + * Misc - Remove support for WooCommerce 2.6 + * Misc - Require PHP 5.6+ + +2019.04.03 - version 1.10.1 + * Misc - Add support for WooCommerce 3.6 + * Misc - Require PHP 5.4+ + +2019.02.28 - version 1.10.0 + * Misc - Drop support for PHP 5.2: PHP 5.3 is now the minimum supported version + * Misc - Update SkyVerge Plugin Framework to version 5.3.0 + +2018.10.24 - version 1.9.2 + * Misc - Add support for WooCommerce 3.5 + +2018.05.23 - version 1.9.1 + * Misc - Add support for WooCommerce 3.4 + +2018.01.23 - version 1.9.0 + * Tweak - Add setting to enable or disable if tab content is searchable rather than searching by default + * Fix - Content for custom tabs was omitted from search if there were no searchable global tabs + * Misc - Add support for WooCommerce 3.3 + * Misc - Remove support for WooCommerce 2.5 + +2017.09.13 - version 1.8.4 + * Tweak - Ensure tab search functions are not run when there are no searchable tabs + +2017.08.23 - version 1.8.3 + * Tweak - Global tabs restricted to a category are hidden from product edit if unavailable for the product + * Fix - Saving tabs could time out with very large product catalogs + +2017.06.28 - version 1.8.2 + * Misc - Added support for WooCommerce 3.1 + +2017.05.31 - version 1.8.1 + * Tweak - Support linking to custom tabs directly via anchor link + * Fix - New product-level tabs were not properly saved on some sites running WC > 3.0 + +2017.03.28 - version 1.8.0 + * Misc - Added support for WooCommerce 3.0 + * Misc - Removed support for WooCommerce 2.4 + +2017.01.24 - version 1.7.4 + * Fix - Fix compatibility with WordPress 4.7 + +2016.12.07 - version 1.7.3 + * Fix - Fix a PHP7 compatibility checker notice + * Fix - Fixes an issue with translating the default tab layout using WPML + * Fix - Fixes an issue with tab switching on sites using WooCommerce Multilingual with unicode languages + +2016.08.31 - version 1.7.2 + * Fix - Fix an issue with global tabs not appearing on products in certain environments + +2016.08.10 - version 1.7.1 + * Fix - Tooltip incompatibility with WooCommerce 2.4.13 + +2016.08.02 - version 1.7.0 + * Feature - Global tabs can now be shown on a per-category basis + +2016.07.27 - version 1.6.2 + * Fix - Fix a PHP7 compatibility checker notice + +2016.06.14 - version 1.6.1 + * Fix - When bbPress is active, a PHP Error may be shown when visiting forums if PHP is set to display errors + +2016.06.02 - version 1.6.0 + * Misc - Added support for WooCommerce 2.6 + * Misc - Removed support for WooCommerce 2.3 + +2016.04.27 - version 1.5.3 + * Fix - Fixes an error that made the tabs disappear + +2016.04.26 - version 1.5.2 + * Tweak - The review count in the Reviews tab title can now be filtered + * Fix - Fixed an issue where the "Save Attributes" button in Product page is relocated to another position when Tab Manager is active + * Fix - The reviews count in the Reviews tab title was incorrect in certain themes + * Misc - Improved support for WPML and WCML + +2016.01.20 - version 1.5.1 + * Fix - Fix a parse error in older versions of PHP + +2016.01.14 - version 1.5.0 + * Misc - Added support for WooCommerce 2.5 + * Misc - Removed support for WooCommerce 2.2 + +2015.12.09 - version 1.4.2 + * Fix - Improved performance on search pages + * Tweak - Allow use of Divi Builder on global tabs + +2015.11.25 - version 1.4.1 + * Fix - Fatal error on plugin upgrade when using PHP 5.3 or 5.4 + +2015.11.24 - version 1.4.0 + * Feature - Tab content can now be included in site search results + * Feature - Compatibility with Relevanssi search plugin + * Fix - Bug when filtering tabs by type + +2015.08.20 - version 1.3.1 + * Fix - WordPress 4.3 compatibility + * Fix - Improve third-party plugin compatibility + +2015.07.28 - version 1.3.0 + * Misc - WooCommerce 2.4 Compatibility + +2015.02.09 - version 1.2.0 + * Misc - WooCommerce 2.3 Compatibility + +2014.09.07 - version 1.1.3 + * Misc - WooCommerce 2.2 Compatibility + +2014.07.15 - version 1.1.2 + * Fix - Compatibility fix with Yoast SEO Premium plugin + +2014.02.10 - version 1.1.1 + * Tweak - Tab content template can be overridden by tab name + * Fix - Compatibility with WooCommerce Product Enquiry tab + * Fix - Installation bug + +2014.01.20 - version 1.1 + * Misc - Uses SkyVerge Plugin Framework + * Misc - WooCommerce 2.1 Compatibility + * Localization - Changed text domain to woocommerce-tab-manager and moved langues directory to i18n + +2013.11.09 - version 1.0.10 + * Tweak - Compatibility enhancement with WPML + +2013.08.23 - version 1.0.9 + * Localization - Italian and French translations provided by Ali Razzaq + * Tweak - Update plugin action links + +2013.05.17 - version 1.0.8 + * Fix - Tab content area is no longer sanitized so stringently, allowing for more HTML tags + +2013.05.06 - version 1.0.7 + * Fix - Product tab override supported in product_page shortcode for WC < 2.0 + * Fix - Support for unicode characters in tab titles + +2013.03.21 - version 1.0.6 + * Fix - Product tabs also duplicated when product is duplicated + +2013.02.17 - version 1.0.5 + * WooCommerce 2.0 compatibility + +2013.02.08 - version 1.0.4.3 + * Fix - Compatibility with the WooCommerce Branding plugin + +2013.02.05 - version 1.0.4.2 + * Fix - Migration from free "Custom Product Tabs Lite" plugin + +2013.01.24 - version 1.0.4.1 + * Fix - Core tab title editable only in WC 2.0+ + +2013.01.13 - version 1.0.4 + * Feature - Core tab heading editable + +2012.12.17 - version 1.0.3 + * Fix - product tabs deleted on product deletion + * Fix - save_post filter conflict + * Fix - WP 3.5 compatibility (prepare) + +2012.12.04 - version 1.0.2 + * New updater + +2012.11.08 - version 1.0.1 + * Fix - PHP 5.2.x compatibility fix + +2012.11.08 - version 1.0 + * Initial Release diff --git a/class-wc-tab-manager.php b/class-wc-tab-manager.php new file mode 100644 index 0000000..6445730 --- /dev/null +++ b/class-wc-tab-manager.php @@ -0,0 +1,1164 @@ + 'woocommerce-tab-manager', + 'supports_hpos' => true, + ] + ); + + // allow direct linking to tabs + if ( ! is_admin() ) { + add_action( 'wp', array( $this, 'use_tab_anchor_links' ) ); + } + + add_filter( 'woocommerce_product_tabs', array( $this, 'setup_tabs' ), 98 ); + + // allow the use of shortcodes within the tab content + add_filter( 'woocommerce_tab_manager_tab_panel_content', 'do_shortcode' ); + + add_action( 'wp_print_footer_scripts', array( $this, 'include_js_templates' ) ); + add_action( 'admin_print_footer_scripts', array( $this, 'include_js_templates' ) ); + } + + + /** + * Builds the lifecycle handler instance. + * + * @since 1.10.0 + */ + protected function init_lifecycle_handler() { + + require_once( $this->get_plugin_path() . '/src/Lifecycle.php' ); + + $this->lifecycle_handler = new SkyVerge\WooCommerce\Tab_Manager\Lifecycle( $this ); + } + + + /** + * Initializes the plugin. + * + * @internal + * + * @since 1.10.0 + */ + public function init_plugin() { + + add_action( 'init', array( $this, 'init_post_types' ), 20 ); + + $this->includes(); + } + + + /** + * Includes files required by both the admin and frontend. + * + * @since 1.0 + */ + private function includes() { + + require_once( $this->get_plugin_path() . '/woocommerce-tab-manager-template.php' ); + + if ( is_admin() ) { + $this->admin_includes(); + } + + if ( wp_doing_ajax() ) { + $this->ajax_includes(); + } + + if ( ! class_exists( 'WC_Tab_Manager_Search' ) ) { + $this->search = $this->load_class( '/src/class-wc-tab-manager-search.php', 'WC_Tab_Manager_Search' ); + } + } + + + /** + * Includes required admin files. + * + * @since 1.0 + */ + private function admin_includes() { + + // admin functions + include_once( $this->get_plugin_path() . '/admin/woocommerce-tab-manager-admin-functions.php' ); + + // product tab-specific admin functions + include_once( $this->get_plugin_path() . '/admin/post-types/wc_product_tab.php' ); + + // product tab meta boxes + include_once( $this->get_plugin_path() . '/admin/post-types/wc_product_tab_metabox.php' ); + + // default Tab Layout admin screen and persistence code + include_once( $this->get_plugin_path() . '/admin/woocommerce-tab-manager-admin-global-layout.php' ); + require_once( $this->get_plugin_path() . '/admin/woocommerce-tab-manager-admin-init.php' ); + + // custom WooCommerce settings + if ( ! class_exists( 'WC_Tab_Manager_Settings' ) ) { + $this->wc_settings = $this->load_class( '/src/class-wc-tab-manager-settings.php', 'WC_Tab_Manager_Settings' ); + } + } + + + /** + * Includes required ajax files. + * + * @since 1.0 + */ + private function ajax_includes() { + + if ( ! class_exists( 'WC_Tab_Manager_Ajax_Events' ) ) { + $this->wc_settings = $this->load_class( '/src/class-wc-tab-manager-ajax-events.php', 'WC_Tab_Manager_Ajax_Events' ); + } + } + + + /** + * Returns the tab manager search class instance. + * + * @since 1.6.0 + * + * @return \WC_Tab_Manager_Search + */ + public function get_search_instance() { + + return $this->search; + } + + + /** + * Returns the tab manager settings class instance. + * + * @since 1.6.0 + * + * @return \WC_Tab_Manager_Settings + */ + public function get_settings_instance() { + + return $this->wc_settings; + } + + + /** + * Returns the tab manager ajax class instance. + * + * @since 1.6.0 + * + * @return \WC_Tab_Manager_Ajax_Events + */ + public function get_ajax_instance() { + + return $this->ajax; + } + + + /** + * Includes frontend scripts. + * + * @since 1.8.1 + * + * @internal + */ + public function use_tab_anchor_links() { + global $post; + + if ( is_product() ) { + + $tabs = json_encode( $this->get_product_tabs( $post->ID ) ); + + // adding an entire frontend js file feels sort of heavy for this, perhaps consider it if we add more js in the future {BR 2017-05-30} + wc_enqueue_js( " + (function() { + var hash = window.location.hash; + var tabs = $( this ).find( '.wc-tabs, ul.tabs' ).first(); + var ref = $tabs; + + for ( index in ref ) { + + tab = ref[index]; + + /* global tabs */ + if ( tab.name && ( hash === '#' + tab.name || hash === '#tab-' + tab.name ) ) { + tabs.find( 'li.' + tab.name + '_tab a' ).trigger( 'click' ); + /* third-party tabs */ + } else if ( hash === '#' + tab.id || hash === '#tab-' + tab.id ) { + tabs.find( 'li.' + tab.id + '_tab a' ).trigger( 'click' ); + } + } + })(); + " ); + } + } + + + /** + * Initializes WooCommerce Tab Manager user role. + * + * @since 1.0 + */ + private function init_user_roles() { + global $wp_roles; + + if ( ! isset( $wp_roles ) && class_exists( 'WP_Roles' ) ) { + $wp_roles = new WP_Roles(); + } + + // it's fine if this gets executed more than once + if ( is_object( $wp_roles ) ) { + $wp_roles->add_cap( 'shop_manager', 'manage_woocommerce_tab_manager' ); + $wp_roles->add_cap( 'administrator', 'manage_woocommerce_tab_manager' ); + } + } + + + /** + * Initializes the tab custom post type. + * + * @since 1.10.0 + * + * @internal + */ + public function init_post_types() { + + // init user roles + $this->init_user_roles(); + + // bail if the post type was already registered + if ( post_type_exists( 'wc_product_tab' ) ) { + return; + } + + register_post_type( 'wc_product_tab', + array( + 'labels' => array( + 'name' => __( 'Tabs', 'woocommerce-tab-manager' ), + 'singular_name' => __( 'Tab', 'woocommerce-tab-manager' ), + 'menu_name' => _x( 'Tab Manager', 'Admin menu name', 'woocommerce-tab-manager' ), + 'add_new' => __( 'Add global tab', 'woocommerce-tab-manager' ), + 'add_new_item' => __( 'Add new global tab', 'woocommerce-tab-manager' ), + 'edit' => __( 'Edit', 'woocommerce-tab-manager' ), + 'edit_item' => __( 'Edit tab', 'woocommerce-tab-manager' ), + 'new_item' => __( 'New tab', 'woocommerce-tab-manager' ), + 'view' => __( 'View tabs', 'woocommerce-tab-manager' ), + 'view_item' => __( 'View tab', 'woocommerce-tab-manager' ), + 'search_items' => __( 'Search tabs', 'woocommerce-tab-manager' ), + 'not_found' => __( 'No tabs found', 'woocommerce-tab-manager' ), + 'not_found_in_trash' => __( 'No tabs found in trash', 'woocommerce-tab-manager' ), + ), + 'description' => __( 'This is where you can add new tabs that you can add to products.', 'woocommerce-tab-manager' ), + 'public' => true, + 'show_ui' => true, + 'capability_type' => 'post', + 'capabilities' => array( + 'publish_posts' => 'manage_woocommerce_tab_manager', + 'edit_posts' => 'manage_woocommerce_tab_manager', + 'edit_others_posts' => 'manage_woocommerce_tab_manager', + 'delete_posts' => 'manage_woocommerce_tab_manager', + 'delete_others_posts' => 'manage_woocommerce_tab_manager', + 'read_private_posts' => 'manage_woocommerce_tab_manager', + 'edit_post' => 'manage_woocommerce_tab_manager', + 'delete_post' => 'manage_woocommerce_tab_manager', + 'read_post' => 'manage_woocommerce_tab_manager', + ), + 'publicly_queryable' => true, + 'exclude_from_search' => true, + 'show_in_menu' => current_user_can( 'manage_woocommerce' ) ? 'woocommerce' : true, + 'hierarchical' => false, + 'rewrite' => false, + 'query_var' => false, + 'supports' => array( 'title', 'editor' ), + 'show_in_nav_menus' => false, + ) + ); + } + + + /** + * Organizes the product tabs as configured within the Tab Manager. + * + * $tabs structure: + * Array( + * id => Array( + * 'title' => (string) Tab title, + * 'priority' => (string) Tab priority, + * 'callback' => (mixed) callback function, + * ) + * ) + * + * @since 1.0.5 + * + * @internal + * + * @param array $tabs array representing the product tabs + * @return array representing the product tabs + */ + public function setup_tabs( $tabs ) { + global $product; + + // first off, make sure that we're dealing with an array rather than null + if ( null === $tabs ) { + $tabs = array(); + } + + $new_tabs = $tabs; + $product_tabs = $product instanceof \WC_Product ? $this->get_product_tabs( $product->get_id() ) : null; + + // if product tabs have been configured for this product or globally (otherwise, allow default behavior) + if ( is_array( $product_tabs ) ) { + + // start fresh + $new_tabs = array(); + + // unhook and load any third party tabs that have been added by this point + $third_party_tabs = $this->get_third_party_tabs( $tabs ); + + foreach ( $product_tabs as $key => $tab ) { + + $priority = ( $tab['position'] + 1 ) * 10; + + /** + * Filters the tab ID. + * + * @since 1.5.2 + * + * @param int|string $tab_id note that this could be a string or integer + */ + $tab_id = apply_filters( 'wc_tab_manager_tab_id', $tab['id'] ); + + if ( 'core' === $tab['type'] ) { + + // the core tabs can be suppressed for a variety of reasons: description tab due to no content, attributes tab due to no attributes, etc + if ( ! isset( $tabs[ $tab_id ] ) ) { + continue; + } + + // set the review (comment) count for the reviews tab, if they used the '%d' substitution + if ( 'reviews' === $tab_id && false !== strpos( $tab['title'], '%d' ) ) { + + /** + * Filters the review count in the Reviews (%d) tab title. + * + * @since 1.5.2 + * + * @param int $review_count array representing the product tabs + * @param \WC_Product the product + */ + $tab['title'] = str_replace( '%d', apply_filters( 'wc_tab_manager_reviews_tab_title_review_count', $product->get_review_count(), $product ), $tab['title'] ); + } + + // add the core tab to the new tab set + $new_tabs[ $tab_id ] = array( + 'title' => $tab['title'], // modified title + 'priority' => $priority, // modified priority + 'callback' => $tabs[ $tab_id ]['callback'], // get the core tab callback + ); + + // handle core tab headings (displays just before the tab content) + if ( 'additional_information' === $tab_id ) { + add_filter( 'woocommerce_product_additional_information_heading', array( $this, 'core_tab_heading' ) ); + } elseif ( 'description' === $tab_id ) { + add_filter( 'woocommerce_product_description_heading', array( $this, 'core_tab_heading' ) ); + } + + } elseif ( 'third_party' === $tab['type'] ) { + + // third-party provided tab: ensure it's still available + if ( ! isset( $third_party_tabs[ $key ] ) || ( isset( $third_party_tabs[ $key ]['ignore'] ) && true == $third_party_tabs[ $key ]['ignore'] ) ) { + continue; + } + + // add the 3rd party tab in with the new priority + $new_tabs[ $tab_id ] = $third_party_tabs[ $key ]; + $new_tabs[ $tab_id ]['priority'] = $priority; + + // Product/Global tabs: + } else { + + $post_id = (int) $tab_id; + $tab_post = get_post( $post_id ); + + // skip any global/product tabs that have been deleted + if ( ! $tab_post || 'publish' !== $tab_post->post_status || ! $tab_post->post_title ) { + continue; + } + + // global tabs + if ( ! $tab_post->post_parent ) { + + $product_terms = get_the_terms( $product->get_id(), 'product_cat' ); + + if ( is_wp_error( $product_terms ) ) { + continue; + } + + $tab_cats = get_post_meta( $post_id, '_wc_tab_categories', true ); + $product_cats = wp_list_pluck( (array) $product_terms, 'term_id' ); + + // compare selected categories for the tab vs the product's categories + $cat_check = empty( $tab_cats ) ? array() : array_intersect( $product_cats, $tab_cats ); + + // Hacky fix for WooCommerce Multilingual conflict -- revisit this in the rewrite {TZ 2016-12-06} + // We try to generate a clean unique tab name using the tab title in wc_tab_manager_process_tabs() and handle + // unicode tab titles there as well (tab switching doesn't work with unicode tab titles). However, sites using + // WooCommerce Multilingual don't benefit from this unicode handling as that function is not run for translated + // tabs. Therefore, we must take into account unicode titles later in the process (right before display) + if ( strlen( $tab_post->post_title ) !== strlen( utf8_encode( $tab_post->post_title ) ) ) { + $tab['name'] = 'global-tab-' . uniqid( '', false ); + } + + // add the global tab if there are no categories, or if selected ones match this product + if ( ! empty( $cat_check ) || empty( $tab_cats ) ) { + $new_tabs[ $tab['name'] ] = array( + 'title' => $tab_post->post_title, + 'priority' => $priority, + 'callback' => 'woocommerce_tab_manager_tab_content', + 'id' => $tab_id, + ); + } + + } else { + + // Hacky fix for WooCommerce Multilingual conflict -- revisit this in the rewrite {TZ 2016-12-06} + // We try to generate a clean unique tab name using the tab title in wc_tab_manager_process_tabs() and handle + // unicode tab titles there as well (tab switching doesn't work with unicode tab titles). However, sites using + // WooCommerce Multilingual don't benefit from this unicode handling as that function is not run for translated + // tabs. Therefore, we must take into account unicode titles later in the process (right before display) + if ( strlen( $tab_post->post_title ) !== strlen( utf8_encode( $tab_post->post_title ) ) ) { + $tab['name'] = 'product-tab-' . uniqid( '', false ); + } + + // add any product tabs + $new_tabs[ $tab['name'] ] = array( + 'title' => $tab_post->post_title, + 'priority' => $priority, + 'callback' => 'woocommerce_tab_manager_tab_content', + 'id' => $tab_id, + ); + } + } + } + + // finally add in any non-managed 3rd party tabs with their own priority + foreach ( $third_party_tabs as $key => $tab ) { + if ( isset( $tab['ignore'] ) && true === $tab['ignore'] ) { + $new_tabs[ $key ] = $tab; + } + } + } + + return apply_filters( 'wc_tab_manager_product_tabs', $new_tabs ); + } + + + /** + * Filter to modify the Description and Additional Information core tab headings. + * + * The heading is not what shows up in the "tab" itself, this is the heading for the tab content area. + * + * @since 1.0 + * + * @internal + * + * @param string $heading the tab heading + * @return string the tab heading + */ + public function core_tab_heading( $heading ) { + global $product; + + $tabs = $this->get_product_tabs( $product->get_id() ); + $current_filter = current_filter(); + + if ( 'woocommerce_product_additional_information_heading' === $current_filter ) { + $heading = $tabs['core_tab_additional_information']['heading']; + } elseif ( 'woocommerce_product_description_heading' === $current_filter ) { + $heading = $tabs['core_tab_description']['heading']; + } + + return $heading; + } + + + /** + * Returns the main Tab Manager Instance. + * + * Ensures only one instance is/can be loaded. + * + * @since 1.2.0 + * + * @return \WC_Tab_Manager + */ + public static function instance() { + + if ( null === self::$instance ) { + + self::$instance = new self(); + } + + return self::$instance; + } + + + /** + * Gets any third party tabs which have been added via the `woocommerce_product_tabs action`. + * + * Any third party tabs so found are collected so they can be re-added by the manager in the appropriate order. + * In the admin a human readable title is automatically generated (allowing for automatic integration of 3rd party plugin tabs) + * and three filters are fired to allow for improved integration with customized titles/descriptions: + * + * - woocommerce_tab_manager_integration_tab_allowed: allows plugin to mark tab as not available for management, its priority will not be modified and it will not appear within the Tab Manager Admin UI + * - woocommerce_tab_manager_integration_tab_title: allows plugin to provide a more descriptive tab title to display within the Tab Manager Admin UI + * - woocommerce_tab_manager_integration_tab_description: allows plugin to provide a description to display within the Tab Manager Admin UI + * + * $tabs structure: + * Array( + * key => Array( + * 'title' => (string) Tab title, + * 'priority' => (string) Tab priority, + * 'callback' => (mixed) callback function, + * 'description' => (string) An optional tab description added by this method to the return array, + * 'ignore' => (boolean) Optional marker indicating this tab is not managed by the Tab Manager plugin and added by this method to the return array, + * 'id' => (string) the original tab key, + * ) + * ) + * Where key is: third_party_tab_{id} + * + * @since 1.0.5 + * + * @param array $tabs optional array representing the product tabs + * @return array representing the product tabs + */ + public function get_third_party_tabs( $tabs = null ) { + global $wp_filter; + + if ( null === $this->third_party_tabs ) { + + // gather the tabs if not provided + if ( null === $tabs ) { + + // in WC 2.1+ the woocommerce_default_product_tabs filter (which requires a global $post/$product) is hooked into from the admin so unhook to avoid a fatal error (has no effect in pre WC 2.1) + if ( is_admin() ) { + remove_filter( 'woocommerce_product_tabs', 'woocommerce_default_product_tabs' ); + remove_filter( 'woocommerce_product_tabs', 'woocommerce_sort_product_tabs', 99 ); + } + + $tabs = (array) apply_filters( 'woocommerce_product_tabs', array() ); + } + + $this->third_party_tabs = array(); + + // remove the core tabs (if any) leaving only 3rd party tabs (if any) + unset( $tabs['additional_information'], $tabs['reviews'], $tabs['description'] ); + + foreach ( $tabs as $key => $tab ) { + + // is this tab available for management by the Tab Manager plugin? + if ( apply_filters( 'woocommerce_tab_manager_integration_tab_allowed', true, $tab ) ) { + + if ( is_admin() ) { + + // on the off chance that the 3rd party tab doesn't have a title, provide it a default one based on the callback so it can be identified within the admin + if ( ! isset( $tab['title'] ) || ! $tab['title'] ) { + + // get a title for the tab. Default to humanizing the function name, or class name + if ( is_array( $tab['callback'] ) ) { + $tab_title = ( is_object( $tab['callback'][0] ) ? get_class( $tab['callback'][0] ) : $tab['callback'][0] ); + } else { + $tab_title = (string) $tab['callback']; + } + + $tab_title = ucwords( str_replace( '_', ' ', $tab_title ) ); + $tab_title = str_ireplace( array( 'woocommerce', 'wordpress' ), array( 'WooCommerce', 'WordPress' ), $tab_title ); // fix some common words + + $tab['title'] = $tab_title; + } + + // improved 3rd party integration by allowing plugins to provide a more descriptive title/description for their tabs + $tab['title'] = apply_filters( 'woocommerce_tab_manager_integration_tab_title', $tab['title'], $tab ); + $tab['description'] = apply_filters( 'woocommerce_tab_manager_integration_tab_description', '', $tab ); + } + + $tab['id'] = $key; + + } else { + + // this tab is not managed by the Tab Manager, so mark it as such + $tab['ignore'] = true; + } + + // save the tab + $this->third_party_tabs[ 'third_party_tab_' . $key ] = $tab; + } + } + + return $this->third_party_tabs; + } + + + /** + * Get the default WooCommerce core tabs data structure. + * + * @since 1.0 + * + * @return array the core tabs + */ + public function get_core_tabs() { + + return array( + 'core_tab_description' => array( + 'id' => 'description', + 'position' => 0, + 'type' => 'core', + 'title' => __( 'Description', 'woocommerce-tab-manager' ), + 'description' => __( 'Displays the product content set in the main content editor.', 'woocommerce-tab-manager' ), + 'heading' => __( 'Product Description', 'woocommerce-tab-manager' ), + ), + 'core_tab_additional_information' => array( + 'id' => 'additional_information', + 'position' => 1, + 'type' => 'core', + 'title' => __( 'Additional Information', 'woocommerce-tab-manager' ), + 'description' => __( 'Displays the product attributes and properties configured in the Product Data panel.', 'woocommerce-tab-manager' ), + 'heading' => __( 'Additional Information', 'woocommerce-tab-manager' ), + ), + 'core_tab_reviews' => array( + 'id' => 'reviews', + 'position' => 2, + 'type' => 'core', + 'title' => __( 'Reviews (%d)', 'woocommerce-tab-manager' ), + 'description' => __( 'Displays the product review form and any reviews. Use %d in the Title to substitute the number of reviews for the product.', 'woocommerce-tab-manager' ), + ), + ); + } + + + /** + * Gets the product tabs (if any) for the identified product. + * + * If not configured at the product level, the default layout (if any) will be returned. + * + * returned tabs structure: + * Array( + * key => Array( + * 'position' => (int) 0-indexed ordered position from the Tab Manager Admin UI, + * 'type' => (string) one of 'core', 'global', 'third_party' or 'product', + * 'id' => (string) Tab identifier, ie 'description', 'reviews', 'additional_information' for the core tabs, post id for product/global, and woocommerce_product_tabs key for third party tabs, + * 'title' => (string) The tab title to display on the frontend (not used for 3rd party tabs, though it could be), + * 'heading' => (string) Tab heading (core description/additional_information tabs only), + * 'name' => (string) Product/Global tabs only, this is the sanitized title, and is used to key the tab in the final woocommerce tab data structure, + * ) + * ) + * Where key is: {type}_tab_{id} + * + * @param int $product_id product identifier + * @return array product tabs data + */ + public function get_product_tabs( $product_id ) { + + if ( ! isset( $this->product_tabs[ $product_id ] ) ) { + + $override_tab_layout = get_post_meta( $product_id, '_override_tab_layout', true ); + + if ( 'yes' === $override_tab_layout ) { + // product defines its own tab layout? + $this->product_tabs[ $product_id ] = get_post_meta( $product_id, '_product_tabs', true ); + } else { + // otherwise, get the default layout if any + $this->product_tabs[ $product_id ] = get_option( 'wc_tab_manager_default_layout', false ); + } + } + + return $this->product_tabs[ $product_id ]; + } + + + /** + * Gets the product tab or null if the tab cannot be found. + * + * @since 1.0 + * + * @param int $product_id product identifier + * @param int $tab_id tab identifier + * @param bool $get_the_content whether to get the tab content and title + * @return array|null tab array, or null + */ + public function get_product_tab( $product_id, $tab_id, $get_the_content = false ) { + + $tab = null; + + // load the tabs + $this->get_product_tabs( $product_id ); + + if ( is_array( $this->product_tabs[ $product_id ] ) ) { + + foreach ( $this->product_tabs[ $product_id ] as $id => $tab ) { + + if ( $tab['id'] == $tab_id ) { + + // get the tab content, if needed + if ( $get_the_content && ! isset( $tab['content'] ) ) { + + /** this filter is documented in /woocommerce-tab-manager.php */ + $tab_id = apply_filters( 'wc_tab_manager_tab_id', $tab_id ); + $tab_post = get_post( $tab_id ); + + $content = apply_filters( 'the_content', $tab_post->post_content ); + $content = str_replace( ']]>', ']]>', $content ); + + $this->product_tabs[ $product_id ][ $id ]['content'] = $content; + $this->product_tabs[ $product_id ][ $id ]['title'] = $tab_post->post_title; + } + + $tab = $this->product_tabs[ $product_id ][ $id ]; + break; + } + } + } + + return apply_filters( 'wc_tab_manager_get_product_tab', $tab, $product_id, $tab_id, $get_the_content ); + } + + + /** + * Processes a batch of products. + * + * @since 1.4.0 + * + * @param array $args optional array of arguments + * @return array + */ + public function batch_update_products( array $args = array() ) { + + $args = wp_parse_args( $args, array( + 'step' => 0, + 'limit' => 100, + ) ); + + $step = absint( $args['step'] ); + $limit = absint( $args['limit'] ); + $offset = $step * $limit; + + $product_posts = get_posts( array( + 'post_type' => 'product', + 'posts_per_page' => $limit, + 'offset' => $offset, + ) ); + + $products_count = $offset + count( $product_posts ); + $products_total = absint( wp_count_posts( 'product' )->publish ); + + $response = array( + 'complete' => false, + 'step' => ++$step, + 'offset' => $offset, + 'limit' => $limit, + 'current' => $products_count, + 'total' => $products_total, + ); + + if ( 1 === $step ) { + $this->get_search_instance()->update_relevanssi_searchable_tab_meta(); + $this->get_search_instance()->maybe_build_relevanssi_index(); + } + + // loop through the current set of products + foreach ( $product_posts as $product_post ) { + + // get any tabs associated with the current product. + $tabs = $this->get_product_tabs( $product_post->ID ); + + if ( ! empty( $tabs ) ) { + + // if any tabs were found, update the tab content meta for the current product + $tab_id_list = wp_list_pluck( array_values( $tabs ), 'id' ); + $tab_id_list = array_filter( $tab_id_list, 'is_numeric' ); + $args = array( + 'target' => 'custom', + 'action' => 'update', + 'product_id' => $product_post->ID, + ); + + $this->get_search_instance()->update_products_for_tabs( $tab_id_list, $args ); + + // also update the Relevanssi index for the current product if the plugin is installed + $this->get_search_instance()->update_relevanssi_index_for_product( $product_post->ID ); + } + } + + // we've processed all of the products + if ( $products_count >= $products_total ) { + $response['complete'] = true; + } + + return $response; + } + + + /** + * Includes JavaScript templates to be used in admin context. + * + * @since 1.4.0 + * + * @internal + */ + public function include_js_templates() { + + if ( is_admin() ) { + + include_once( $this->get_plugin_path() . '/templates/js/admin.tmpl.php' ); + } + } + + + /** + * Tries to determine the current post type based on available global values. + * + * Useful for when you need to determine if a specific post type is being edited on an admin page or when you need to know the post type in an action callback but the action is fired before `get_post_type()` has been defined. + * + * @since 1.4.0 + * + * @return string|bool the current post type if one was found or false if not + */ + public function get_current_post_type() { + global $post, $typenow, $current_screen; + + if ( isset( $post->post_type ) ) { + $post_type = $post->post_type; + } elseif ( $typenow ) { + $post_type = $typenow; + } elseif ( $current_screen && $current_screen->post_type ) { + $post_type = $current_screen->post_type; + } elseif ( isset( $_REQUEST['post_type'] ) ) { + $post_type = sanitize_key( $_REQUEST['post_type'] ); + } else { + $post_type = false; + } + + return $post_type; + } + + + /** + * Tries to determine the current product ID based on context. + * + * If a product is being edited, that product's ID is used. + * If a product-level tab is being edited, the parent product's ID is used. + * + * @since 1.4.0 + * + * @return int|false the current product ID if one was found or false if not + */ + public function maybe_get_tab_product_id() { + global $post; + + $product_id = false; + $post_type = $this->get_current_post_type(); + + if ( 'product' === $post_type ) { + + $product_id = $post->ID; + + } elseif ( 'wc_product_tab' === $post_type ) { + + if ( isset( $post->post_parent ) ) { + + $product_id = $post->post_parent; + } + } + + return $product_id; + } + + + /** + * Extracts numeric IDs from an array. + * + * @since 1.4.0 + * + * @param array $id_list an array where each value is either a numeric ID or an associative array where at least one of the following keys has a numeric value: 'tab_ID', 'tab_id', 'post_ID', 'post_id', 'ID', or 'id' + * @return array an array containing the extracted integer IDs. + */ + public function get_numeric_ids( array $id_list ) { + + $valid_ids = array(); + + foreach ( array_values( $id_list ) as $value ) { + + // if the current value is an array, check commonly-used key names to see if we can find a numeric ID somewhere + if ( is_array( $value ) ) { + + $id_keys = array( 'tab_ID', 'tab_id', 'post_ID', 'post_id', 'ID', 'id' ); + + foreach ( $id_keys as $key ) { + $id = isset( $value[ $key ] ) ? $value[ $key ] : null; + + if ( ! $id || ! is_numeric( $id ) ) { + continue; + } + + // Stop once we find a valid ID. + $valid_ids[] = absint( $id ); + break; + } + + continue; + } + + // bail if the current value isn't numeric + if ( ! is_numeric( $value ) ) { + continue; + } + + $valid_ids[] = absint( $value ); + } + + // remove any duplicate or falsy values + $valid_ids = array_filter( array_unique( $valid_ids ) ); + + return $valid_ids; + } + + + /** + * Verifies that the post ID / object / array passed corresponds to an existing post and, if so, returns the WP_Post instance. + * + * @since 1.4.0 + * + * @param int|\WP_Post|array|object $post a \WP_Post instance, numeric post ID, object, or array: if an object or array is passed it should match the structure of a WP_Post and contain all of the same data (or at least the data that you need returned) + * @return false|\WP_Post|bool post object if it exists or false if not found + */ + public function ensure_post( $post = null ) { + + // If no post data was passed return the current global instance. + if ( empty( $post ) && $GLOBALS['post'] ) { + return $GLOBALS['post']; + } + + // If a valid WP_Post instance was passed just return it. + $is_wp_post = ( $post instanceof WP_Post ); + + if ( $is_wp_post && isset( $post->ID ) ) { + return $post; + } + + // Bail if the post data isn't an ID, object, or array. + $is_post_id = is_numeric( $post ); + $is_post_object = ( is_object( $post ) && isset( $post->ID ) ); + $is_post_array = ( is_array( $post ) && isset( $post['ID'] ) ); + + if ( ! $is_post_id && ! $is_post_object && ! $is_post_array ) { + return false; + } + + // If post data is an array, convert it to an object. + if ( $is_post_array ) { + $post = $this->array_to_object( $post ); + } + + // Try to get the post instance. + $post = get_post( $post ); + + if ( ! empty( $post ) ) { + return $post; + } + + return false; + } + + + /** + * Converts an array into an object. + * + * Helper method. Works with associative and multi-dimensional arrays. + * + * @since 1.4.0 + * + * @param array $array the array to convert + * @return stdClass the resulting object + */ + public function array_to_object( array $array ) { + + foreach ( $array as $key => $value ) { + + if ( is_array( $value ) ) { + + $array[ $key ] = $this->array_to_object( $value ); + } + } + + return (object) $array; + } + + + /** + * Returns the plugin name, localized. + * + * @since 1.1 + * + * @return string the plugin name + */ + public function get_plugin_name() { + + return __( 'WooCommerce Tab Manager', 'woocommerce-tab-manager' ); + } + + + /** + * Returns __FILE__. + * + * @since 1.1 + * + * @return string the full path and filename of the plugin file + */ + protected function get_file() { + + return __FILE__; + } + + + /** + * Gets the plugin configuration URL. + * + * @since 1.1 + * + * @param null|string $plugin_id optional plugin identifier + * @return string plugin settings URL + */ + public function get_settings_url( $plugin_id = null ) { + + return admin_url( 'edit.php?post_type=wc_product_tab' ); + } + + + /** + * Gets the plugin documentation URL. + * + * @since 1.3.0 + * + * @return string + */ + public function get_documentation_url() { + + return 'https://docs.woocommerce.com/document/tab-manager/'; + } + + + /** + * Gets the plugin support URL. + * + * @since 1.3.0 + * + * @return string + */ + public function get_support_url() { + + return 'https://woocommerce.com/my-account/marketplace-ticket-form/'; + } + + + /** + * Returns the plugin sales page URL. + * + * @since 1.10.0 + * + * @return string + */ + public function get_sales_page_url() { + + return 'https://woocommerce.com/products/woocommerce-tab-manager/'; + } + + + /** + * Returns true if on the admin tab configuration page + * + * @since 1.0.1 + * + * @return bool + */ + public function is_plugin_settings() { + + return isset( $_GET['post_type'] ) && 'wc_product_tab' === $_GET['post_type']; + } + + +} + + +/** + * Returns the One True Instance of Tab Manager. + * + * @since 1.2.0 + * + * @return \WC_Tab_Manager + */ +function wc_tab_manager() { + + return \WC_Tab_Manager::instance(); +} diff --git a/i18n/languages/woocommerce-tab-manager-fr_FR.mo b/i18n/languages/woocommerce-tab-manager-fr_FR.mo new file mode 100644 index 0000000..2ac4e27 Binary files /dev/null and b/i18n/languages/woocommerce-tab-manager-fr_FR.mo differ diff --git a/i18n/languages/woocommerce-tab-manager-fr_FR.po b/i18n/languages/woocommerce-tab-manager-fr_FR.po new file mode 100644 index 0000000..f6d480c --- /dev/null +++ b/i18n/languages/woocommerce-tab-manager-fr_FR.po @@ -0,0 +1,549 @@ +# This file was generated by WPML +# WPML is a WordPress plugin that can turn any WordPress or WordPressMU site into a full featured multilingual content management system. +# http://wpml.org +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: https://woocommerce.com/my-account/marketplace-ticket-" +"form/\n" +"POT-Creation-Date: 2014-06-15 22:41:30+00:00\n" +"PO-Revision-Date: \n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" + +#: admin/post-types/wc_product_tab.php:122 +msgid "Name" +msgstr "" + +#: admin/post-types/wc_product_tab.php:123 +msgid "Tab Type" +msgstr "" + +#: admin/post-types/wc_product_tab.php:124 +msgid "Parent Product" +msgstr "" + +#: admin/post-types/wc_product_tab.php:125 +msgid "Categories" +msgstr "" + +# +# +# +#: admin/post-types/wc_product_tab.php:130 +#, fuzzy +msgid "Searchable?" +msgstr "Rechercher les'onglets" + +#: admin/post-types/wc_product_tab.php:169 +msgid "Draft" +msgstr "" + +#: admin/post-types/wc_product_tab.php:202 +msgid "Restore this item from the Trash" +msgstr "" + +#: admin/post-types/wc_product_tab.php:202 +msgid "Restore" +msgstr "" + +#: admin/post-types/wc_product_tab.php:206 +msgid "Move this item to the Trash" +msgstr "" + +#: admin/post-types/wc_product_tab.php:206 +msgid "Trash" +msgstr "" + +#: admin/post-types/wc_product_tab.php:209 +msgid "Delete this item permanently" +msgstr "" + +#: admin/post-types/wc_product_tab.php:209 +#: admin/post-types/writepanels/writepanel-product-tab_actions.php:64 +msgid "Delete Permanently" +msgstr "" + +#: admin/post-types/wc_product_tab.php:235 +msgid "Product" +msgstr "" + +#: admin/post-types/wc_product_tab.php:237 +msgid "Global" +msgstr "" + +#: admin/post-types/wc_product_tab.php:248 +#: admin/post-types/wc_product_tab.php:273 +msgid "N/A" +msgstr "" + +#: admin/post-types/wc_product_tab.php:282 +msgid "Yes" +msgstr "" + +#: admin/post-types/wc_product_tab.php:287 +msgid "No" +msgstr "" + +#: admin/post-types/wc_product_tab.php:339 +msgid "Show all Tabs" +msgstr "" + +#: admin/post-types/wc_product_tab.php:342 +msgid "Show product tabs" +msgstr "" + +#: admin/post-types/wc_product_tab.php:345 +msgid "Show global tabs" +msgstr "" + +#: admin/post-types/wc_product_tab_metabox.php:62 +#: admin/post-types/wc_product_tab_metabox.php:134 +msgid "Product Categories" +msgstr "" + +#: admin/post-types/wc_product_tab_metabox.php:125 +msgid "Product-level tabs will always be shown on their assigned product." +msgstr "" + +#: admin/post-types/wc_product_tab_metabox.php:136 +msgid "" +"Select categories to restrict the display of this tab to certain products." +msgstr "" + +#: admin/post-types/wc_product_tab_metabox.php:144 +msgid "Any category" +msgstr "" + +# +# +# +#: admin/post-types/writepanels/writepanel-product-tab_actions.php:54 +#, fuzzy +msgid "Save Tab" +msgstr "New Nouvel Onglet" + +#: admin/post-types/writepanels/writepanel-product-tab_actions.php:54 +msgid "Save/update the tab" +msgstr "" + +#: admin/post-types/writepanels/writepanel-product-tab_actions.php:66 +msgid "Move to Trash" +msgstr "" + +# +# +# +#: admin/post-types/writepanels/writepanel-product_data-tabs.php:41 +#: admin/woocommerce-tab-manager-admin-init.php:260 +#: woocommerce-tab-manager.php:338 +msgid "Tabs" +msgstr "Onglets" + +#. translators: Placeholders: %1$s - , %2$s - +#: admin/post-types/writepanels/writepanels-init.php:78 +msgid "" +"Please %1$srun the tab search update%2$s to ensure the new tab content is " +"indexed and searchable." +msgstr "" + +#: admin/post-types/writepanels/writepanels-init.php:123 +msgid "Tab Actions" +msgstr "" + +#: admin/woocommerce-tab-manager-admin-functions.php:102 +msgid "Override default tab layout:" +msgstr "" + +#: admin/woocommerce-tab-manager-admin-functions.php:104 +msgid "Close all" +msgstr "" + +#: admin/woocommerce-tab-manager-admin-functions.php:104 +msgid "Expand all" +msgstr "" + +#: admin/woocommerce-tab-manager-admin-functions.php:152 +msgid "Edit Global Tab Content" +msgstr "" + +#: admin/woocommerce-tab-manager-admin-functions.php:236 +#: admin/woocommerce-tab-manager-admin-init.php:151 +msgid "Remove" +msgstr "" + +#: admin/woocommerce-tab-manager-admin-functions.php:237 +#: admin/woocommerce-tab-manager-admin-init.php:152 +msgid "Click to toggle" +msgstr "" + +#: admin/woocommerce-tab-manager-admin-functions.php:247 +msgid "The title/content for this tab will be provided by a third party plugin" +msgstr "" + +#: admin/woocommerce-tab-manager-admin-functions.php:252 +#: admin/woocommerce-tab-manager-admin-init.php:153 +msgid "Title" +msgstr "" + +#: admin/woocommerce-tab-manager-admin-functions.php:257 +#: admin/woocommerce-tab-manager-admin-init.php:154 +msgid "The tab title, this appears in the tab" +msgstr "" + +#: admin/woocommerce-tab-manager-admin-functions.php:263 +msgid "Heading" +msgstr "" + +#: admin/woocommerce-tab-manager-admin-functions.php:264 +msgid "The tab heading, this appears just before the tab content" +msgstr "" + +#: admin/woocommerce-tab-manager-admin-functions.php:269 +#: admin/woocommerce-tab-manager-admin-init.php:155 +msgid "Content" +msgstr "" + +# +# +# +#: admin/woocommerce-tab-manager-admin-functions.php:294 +#, fuzzy +msgid "Add" +msgstr "Ajouter onglet" + +#: admin/woocommerce-tab-manager-admin-functions.php:297 +msgid "Custom Tab" +msgstr "" + +#: admin/woocommerce-tab-manager-admin-global-layout.php:51 +msgid "Tabs layout %s" +msgstr "" + +#: admin/woocommerce-tab-manager-admin-global-layout.php:58 +#: admin/woocommerce-tab-manager-admin-init.php:266 +msgid "Default Tab Layout" +msgstr "" + +#: admin/woocommerce-tab-manager-admin-global-layout.php:72 +msgid "Save Changes" +msgstr "" + +#: admin/woocommerce-tab-manager-admin-init.php:150 +msgid "Remove this product tab?" +msgstr "" + +#: admin/woocommerce-tab-manager-admin-init.php:198 +#: admin/woocommerce-tab-manager-admin-init.php:201 +#: admin/woocommerce-tab-manager-admin-init.php:203 +msgid "Tab updated." +msgstr "" + +#: admin/woocommerce-tab-manager-admin-init.php:199 +msgid "Custom field updated." +msgstr "" + +#: admin/woocommerce-tab-manager-admin-init.php:200 +msgid "Custom field deleted." +msgstr "" + +#: admin/woocommerce-tab-manager-admin-init.php:202 +msgid "Tab restored to revision from %s" +msgstr "" + +#: admin/woocommerce-tab-manager-admin-init.php:204 +msgid "Tab saved." +msgstr "" + +#: admin/woocommerce-tab-manager-admin-init.php:205 +msgid "Tab submitted." +msgstr "" + +#: admin/woocommerce-tab-manager-admin-init.php:207 +msgid "Tab scheduled for: %1$s." +msgstr "" + +#: admin/woocommerce-tab-manager-admin-init.php:208 +msgid "M j, Y @ G:i" +msgstr "" + +#: admin/woocommerce-tab-manager-admin-init.php:210 +msgid "Tab draft updated." +msgstr "" + +# +# +# +#: admin/woocommerce-tab-manager-admin-init.php:238 +#, fuzzy +msgid "Add Global Tab" +msgstr "Ajouter onglet" + +# +# +# +#: admin/woocommerce-tab-manager-admin-init.php:251 +#: woocommerce-tab-manager.php:344 +msgid "Edit Tab" +msgstr "Modifier l'onglet" + +#. translators: Placeholders: %s - keyword search query +#: admin/woocommerce-tab-manager-admin-init.php:271 +msgid "Search results for “%s”" +msgstr "" + +# +# +# +#. Plugin Name of the plugin/theme +#, fuzzy +msgid "WooCommerce Tab Manager" +msgstr "Outil de modification de l'onglet" + +#: src/class-wc-tab-manager-search.php:285 +msgid "Include tab content in search results?" +msgstr "" + +#: src/class-wc-tab-manager-search.php:314 +msgid "Any tab layout" +msgstr "" + +#: src/class-wc-tab-manager-search.php:317 +msgid "Default layout" +msgstr "" + +#: src/class-wc-tab-manager-search.php:320 +msgid "Custom layout" +msgstr "" + +#: src/class-wc-tab-manager-search.php:926 +msgid "" +"Hey there, Tab Manager has a nifty new search feature. If you'd like to take " +"advantage of this you'll need to update your products and tabs (you only " +"need to do this once)." +msgstr "" + +#: src/class-wc-tab-manager-search.php:929 +msgid "" +"It looks like you recently installed the Relevanssi plugin. If you'd like to " +"use the new Tab Manager search feature with Relevanssi, you'll need to " +"update your products and tabs so they can be indexed." +msgstr "" + +#: src/class-wc-tab-manager-search.php:936 +msgid "Go to the update page." +msgstr "" + +#: src/class-wc-tab-manager-search.php:938 +msgid "OK, don't remind me again." +msgstr "" + +# +# +# +#: src/class-wc-tab-manager-settings.php:87 +#, fuzzy +msgid "Search Settings" +msgstr "Rechercher les'onglets" + +#: src/class-wc-tab-manager-settings.php:95 +msgid "Include product tab content in search" +msgstr "" + +#: src/class-wc-tab-manager-settings.php:99 +msgid "" +"Enable to include custom tab content (and global tab content if enabled) in " +"site search." +msgstr "" + +#: src/class-wc-tab-manager-settings.php:121 +msgid "Update Products & Tabs" +msgstr "" + +#: src/class-wc-tab-manager-settings.php:128 +msgid "Update" +msgstr "" + +#: src/class-wc-tab-manager-settings.php:134 +msgid "" +"This update will allow your product tab content to show up in your site’s " +"search results, making it easier for customers to find products whose tabs " +"contain the search query." +msgstr "" + +#: templates/js/admin.tmpl.php:3 +msgid "Updating your products and tabs..." +msgstr "" + +#: templates/js/admin.tmpl.php:6 +msgid "All done!" +msgstr "" + +#. translators: {{ data.current }} - current product count, {{ data.total }} - +#. total product count (eg. 2 of 168 products updated) +#: templates/js/admin.tmpl.php:9 +msgid "{{ data.current }} of {{ data.total }} products updated." +msgstr "" + +# +# +# +#: woocommerce-tab-manager.php:339 +msgid "Tab" +msgstr "Onglet" + +# +# +# +#: woocommerce-tab-manager.php:341 +msgid "Add Tab" +msgstr "Ajouter onglet" + +# +# +# +#: woocommerce-tab-manager.php:342 +msgid "Add New Tab" +msgstr "Ajouter nouvel onglet" + +# +# +# +#: woocommerce-tab-manager.php:343 +msgid "Edit" +msgstr "Modifier" + +# +# +# +#: woocommerce-tab-manager.php:345 +msgid "New Tab" +msgstr "New Nouvel Onglet" + +# +# +# +#: woocommerce-tab-manager.php:346 +msgid "View Tabs" +msgstr "Visualiser les onglets" + +# +# +# +#: woocommerce-tab-manager.php:347 +msgid "View Tab" +msgstr "Visualiser les onglets" + +# +# +# +#: woocommerce-tab-manager.php:348 +msgid "Search Tabs" +msgstr "Rechercher les'onglets" + +# +# +# +#: woocommerce-tab-manager.php:349 +msgid "No Tabs found" +msgstr "Pas d'onglets trouvés" + +# +# +# +#: woocommerce-tab-manager.php:350 +msgid "No Tabs found in trash" +msgstr "Pas d'onglet trouvé dans une poubelle" + +# +# +# +#: woocommerce-tab-manager.php:352 +msgid "This is where you can add new tabs that you can add to products." +msgstr "Ajoutez ici de nouveaux onglets que vous pouvez ajouter aux produits." + +#: woocommerce-tab-manager.php:391 +msgid "Configure" +msgstr "" + +#: woocommerce-tab-manager.php:392 +msgid "Docs" +msgstr "" + +#: woocommerce-tab-manager.php:393 +msgid "Support" +msgstr "" + +#: woocommerce-tab-manager.php:394 +msgid "Write a Review" +msgstr "" + +#: woocommerce-tab-manager.php:727 +msgid "Description" +msgstr "" + +#: woocommerce-tab-manager.php:728 +msgid "Displays the product content set in the main content editor." +msgstr "" + +#: woocommerce-tab-manager.php:729 +msgid "Product Description" +msgstr "" + +#: woocommerce-tab-manager.php:735 woocommerce-tab-manager.php:737 +msgid "Additional Information" +msgstr "" + +#: woocommerce-tab-manager.php:736 +msgid "" +"Displays the product attributes and properties configured in the Product " +"Data panel." +msgstr "" + +#: woocommerce-tab-manager.php:743 +msgid "Reviews (%d)" +msgstr "" + +#: woocommerce-tab-manager.php:744 +msgid "" +"Displays the product review form and any reviews. Use %d in the Title to " +"substitute the number of reviews for the product." +msgstr "" + +#. Plugin URI of the plugin/theme +msgid "http://www.woocommerce.com/products/woocommerce-tab-manager/" +msgstr "" + +#. Description of the plugin/theme +msgid "A product tab manager for WooCommerce" +msgstr "" + +#. Author of the plugin/theme +msgid "SkyVerge" +msgstr "" + +#. Author URI of the plugin/theme +msgid "http://www.woocommerce.com" +msgstr "" + +# +# +# +#: src/class-wc-tab-manager-settings.php:62 +#, fuzzy +msgctxt "Custom WooCommerce settings section" +msgid "Tab Manager" +msgstr "Outil de modification de l'onglet" + +# +# +# +#: woocommerce-tab-manager.php:340 +#, fuzzy +msgctxt "Admin menu name" +msgid "Tab Manager" +msgstr "Outil de modification de l'onglet" diff --git a/i18n/languages/woocommerce-tab-manager-it_IT.mo b/i18n/languages/woocommerce-tab-manager-it_IT.mo new file mode 100644 index 0000000..de0866a Binary files /dev/null and b/i18n/languages/woocommerce-tab-manager-it_IT.mo differ diff --git a/i18n/languages/woocommerce-tab-manager-it_IT.po b/i18n/languages/woocommerce-tab-manager-it_IT.po new file mode 100644 index 0000000..04119f7 --- /dev/null +++ b/i18n/languages/woocommerce-tab-manager-it_IT.po @@ -0,0 +1,584 @@ +# This file was generated by WPML +# WPML is a WordPress plugin that can turn any WordPress or WordPressMU site into a full featured multilingual content management system. +# http://wpml.org +msgid "" +msgstr "" +"Project-Id-Version: \n" +"Report-Msgid-Bugs-To: https://woocommerce.com/my-account/marketplace-ticket-" +"form/\n" +"POT-Creation-Date: 2021-03-16 06:44:58+00:00\n" +"PO-Revision-Date: \n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: it_IT\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Poedit 2.4.2\n" + +#: admin/post-types/wc_product_tab.php:123 +msgid "Name" +msgstr "Nome" + +#: admin/post-types/wc_product_tab.php:124 +msgid "Tab Type" +msgstr "Tipo scheda" + +#: admin/post-types/wc_product_tab.php:125 +msgid "Parent Product" +msgstr "Prodotto principale" + +#: admin/post-types/wc_product_tab.php:126 +msgid "Categories" +msgstr "Categorie" + +# +# +# +#: admin/post-types/wc_product_tab.php:131 +msgid "Searchable?" +msgstr "Cercabile?" + +#: admin/post-types/wc_product_tab.php:170 +msgid "Draft" +msgstr "Bozza" + +#: admin/post-types/wc_product_tab.php:203 +msgid "Restore this item from the Trash" +msgstr "Ripristina questo oggetto dal cestino" + +#: admin/post-types/wc_product_tab.php:203 +msgid "Restore" +msgstr "Ripristina" + +#: admin/post-types/wc_product_tab.php:207 +msgid "Move this item to the Trash" +msgstr "Sposta questo oggetto nel cestino" + +#: admin/post-types/wc_product_tab.php:207 +msgid "Trash" +msgstr "Cestino" + +#: admin/post-types/wc_product_tab.php:210 +msgid "Delete this item permanently" +msgstr "Elimina questo oggetto definitivamente" + +#: admin/post-types/wc_product_tab.php:210 +#: admin/post-types/writepanels/writepanel-product-tab_actions.php:65 +msgid "Delete Permanently" +msgstr "Elimina definitivamente" + +#: admin/post-types/wc_product_tab.php:236 +msgid "Product" +msgstr "Prodotto" + +#: admin/post-types/wc_product_tab.php:238 +msgid "Global" +msgstr "Globale" + +#: admin/post-types/wc_product_tab.php:249 +#: admin/post-types/wc_product_tab.php:274 +msgid "N/A" +msgstr "n.d." + +#: admin/post-types/wc_product_tab.php:283 +msgid "Yes" +msgstr "Sì" + +#: admin/post-types/wc_product_tab.php:288 +msgid "No" +msgstr "No" + +#: admin/post-types/wc_product_tab.php:340 +msgid "Show all Tabs" +msgstr "Mostra tutte le schede" + +#: admin/post-types/wc_product_tab.php:343 +msgid "Show product tabs" +msgstr "Mostra le schede del prodotto" + +#: admin/post-types/wc_product_tab.php:346 +msgid "Show global tabs" +msgstr "Mostra le schede globali" + +#: admin/post-types/wc_product_tab_metabox.php:65 +#: admin/post-types/wc_product_tab_metabox.php:137 +msgid "Product Categories" +msgstr "Categorie del prodotto" + +#: admin/post-types/wc_product_tab_metabox.php:128 +msgid "Product-level tabs will always be shown on their assigned product." +msgstr "" +"Le schede correlate ad un prodotto saranno sempre mostrate nella pagina del " +"prodotto corrispettiva." + +#: admin/post-types/wc_product_tab_metabox.php:139 +msgid "" +"Select categories to restrict the display of this tab to certain products." +msgstr "" +"Seleziona categorie per limitare la visibilità di questa scheda ad alcuni " +"prodotti." + +#: admin/post-types/wc_product_tab_metabox.php:147 +msgid "Any category" +msgstr "Qualsiasi categoria" + +# +# +# +#: admin/post-types/writepanels/writepanel-product-tab_actions.php:55 +msgid "Save Tab" +msgstr "Salva scheda" + +#: admin/post-types/writepanels/writepanel-product-tab_actions.php:55 +msgid "Save/update the tab" +msgstr "Salva/aggiorna la scheda" + +#: admin/post-types/writepanels/writepanel-product-tab_actions.php:67 +msgid "Move to Trash" +msgstr "Sposta nel cestino" + +# +# +# +#: admin/post-types/writepanels/writepanel-product_data-tabs.php:42 +#: admin/woocommerce-tab-manager-admin-init.php:268 +#: class-wc-tab-manager.php:300 +msgid "Tabs" +msgstr "Schede" + +#. translators: Placeholders: %1$s - , %2$s - +#: admin/post-types/writepanels/writepanels-init.php:79 +msgid "" +"Please %1$srun the tab search update%2$s to ensure the new tab content is " +"indexed and searchable." +msgstr "" +"Per favore, %1$slancia l'aggiornamento della ricerca schede%2$s per " +"assicurarti che il nuovo contenuto delle schede venga indicizzato e possa " +"essere rinvenuto nelle ricerche." + +#: admin/post-types/writepanels/writepanels-init.php:124 +msgid "Tab Actions" +msgstr "Azioni scheda" + +#: admin/woocommerce-tab-manager-admin-functions.php:103 +msgid "Override default tab layout:" +msgstr "Sovrascrivere il modello scheda predefinito:" + +#: admin/woocommerce-tab-manager-admin-functions.php:105 +msgid "Close all" +msgstr "Chiudi tutti" + +#: admin/woocommerce-tab-manager-admin-functions.php:105 +msgid "Expand all" +msgstr "Espandi tutto" + +#: admin/woocommerce-tab-manager-admin-functions.php:153 +msgid "Edit Global Tab Content" +msgstr "Modifica il contenuto della scheda globale" + +#: admin/woocommerce-tab-manager-admin-functions.php:237 +#: admin/woocommerce-tab-manager-admin-init.php:152 +msgid "Remove" +msgstr "Rimuovi" + +#: admin/woocommerce-tab-manager-admin-functions.php:238 +#: admin/woocommerce-tab-manager-admin-init.php:153 +msgid "Click to toggle" +msgstr "Clicca per attivare" + +#: admin/woocommerce-tab-manager-admin-functions.php:248 +msgid "The title/content for this tab will be provided by a third party plugin" +msgstr "" +"Il titolo/contenuto per questa scheda sarà generato da un plugin di terze " +"parti" + +#: admin/woocommerce-tab-manager-admin-functions.php:253 +#: admin/woocommerce-tab-manager-admin-init.php:154 +msgid "Title" +msgstr "Titolo" + +#: admin/woocommerce-tab-manager-admin-functions.php:258 +#: admin/woocommerce-tab-manager-admin-init.php:155 +msgid "The tab title, this appears in the tab" +msgstr "Il titolo della scheda, questo compare nella scheda" + +#: admin/woocommerce-tab-manager-admin-functions.php:264 +msgid "Heading" +msgstr "Testata" + +#: admin/woocommerce-tab-manager-admin-functions.php:265 +msgid "The tab heading, this appears just before the tab content" +msgstr "" +"La testata della scheda, questo compare prima del contenuto della scheda" + +#: admin/woocommerce-tab-manager-admin-functions.php:270 +#: admin/woocommerce-tab-manager-admin-init.php:156 +msgid "Content" +msgstr "Contenuto" + +# +# +# +#: admin/woocommerce-tab-manager-admin-functions.php:295 +msgid "Add" +msgstr "Aggiungi" + +#: admin/woocommerce-tab-manager-admin-functions.php:298 +msgid "Custom Tab" +msgstr "Scheda personalizzata" + +#. translators: Placeholder: %s - updated notice (e.g. Tab layout updated, Tab +#. layout saved, etc.) +#: admin/woocommerce-tab-manager-admin-global-layout.php:53 +msgid "Tabs layout %s" +msgstr "Modello scheda %s" + +#: admin/woocommerce-tab-manager-admin-global-layout.php:61 +msgid "Default tab layout" +msgstr "Modello scheda predefinito" + +#: admin/woocommerce-tab-manager-admin-global-layout.php:79 +msgid "Save changes" +msgstr "Salva i cambiamenti" + +#: admin/woocommerce-tab-manager-admin-init.php:151 +msgid "Remove this product tab?" +msgstr "Rimuovere questa scheda del prodotto?" + +#: admin/woocommerce-tab-manager-admin-init.php:199 +#: admin/woocommerce-tab-manager-admin-init.php:202 +#: admin/woocommerce-tab-manager-admin-init.php:204 +msgid "Tab updated." +msgstr "Scheda aggiornata." + +#: admin/woocommerce-tab-manager-admin-init.php:200 +msgid "Custom field updated." +msgstr "Campo personalizzato aggiornato." + +#: admin/woocommerce-tab-manager-admin-init.php:201 +msgid "Custom field deleted." +msgstr "Campo personalizzato eliminato." + +#: admin/woocommerce-tab-manager-admin-init.php:203 +msgid "Tab restored to revision from %s" +msgstr "Scheda ripristinata alla versione del %s" + +#: admin/woocommerce-tab-manager-admin-init.php:205 +msgid "Tab saved." +msgstr "Scheda salvata." + +#: admin/woocommerce-tab-manager-admin-init.php:206 +msgid "Tab submitted." +msgstr "Scheda inviata." + +#: admin/woocommerce-tab-manager-admin-init.php:208 +msgid "Tab scheduled for: %1$s." +msgstr "Scheda programmata per: %1$s." + +#: admin/woocommerce-tab-manager-admin-init.php:209 +msgid "M j, Y @ G:i" +msgstr "j M Y @ G:i" + +#: admin/woocommerce-tab-manager-admin-init.php:211 +msgid "Tab draft updated." +msgstr "Bozza scheda aggiornata." + +#: admin/woocommerce-tab-manager-admin-init.php:271 +#: admin/woocommerce-tab-manager-admin-init.php:352 +msgid "Default Tab Layout" +msgstr "Modello scheda predefinito" + +#. translators: Placeholder: %s - search query keyword +#: admin/woocommerce-tab-manager-admin-init.php:276 +msgid "Search results for “%s”" +msgstr "Risultati della ricerca per “%s”" + +# +# +# +#. Plugin Name of the plugin/theme +msgid "WooCommerce Tab Manager" +msgstr "WooCommerce Tab Manager" + +# +# +# +#: admin/woocommerce-tab-manager-admin-init.php:325 +msgid "Tab Manager" +msgstr "Tab Manager" + +#: admin/woocommerce-tab-manager-admin-init.php:332 +msgid "All Tabs" +msgstr "Tutte le schede" + +# +# +# +#: admin/woocommerce-tab-manager-admin-init.php:344 +msgid "Add New Global Tab" +msgstr "Aggiungi nuova tab globale" + +# +# +# +#: class-wc-tab-manager.php:301 +msgid "Tab" +msgstr "Scheda" + +# +# +# +#: class-wc-tab-manager.php:303 +msgid "Add global tab" +msgstr "Aggiungi tab globale" + +# +# +# +#: class-wc-tab-manager.php:304 +msgid "Add new global tab" +msgstr "Aggiungi nuova tab globale" + +# +# +# +#: class-wc-tab-manager.php:305 +msgid "Edit" +msgstr "Modifica" + +# +# +# +#: class-wc-tab-manager.php:306 +msgid "Edit tab" +msgstr "Modifica scheda" + +# +# +# +#: class-wc-tab-manager.php:307 +msgid "New tab" +msgstr "Nuova scheda" + +# +# +# +#: class-wc-tab-manager.php:308 +msgid "View tabs" +msgstr "Visualizza schede" + +# +# +# +#: class-wc-tab-manager.php:309 +msgid "View tab" +msgstr "Visualizza scheda" + +# +# +# +#: class-wc-tab-manager.php:310 +msgid "Search tabs" +msgstr "Cerca schede" + +# +# +# +#: class-wc-tab-manager.php:311 +msgid "No tabs found" +msgstr "Nessuna scheda trovata" + +# +# +# +#: class-wc-tab-manager.php:312 +msgid "No tabs found in trash" +msgstr "Nessuna scheda trovata nel cestino" + +# +# +# +#: class-wc-tab-manager.php:314 +msgid "This is where you can add new tabs that you can add to products." +msgstr "Qui puoi aggiungere nuove schede da aggiungere ai prodotti." + +#: class-wc-tab-manager.php:677 +msgid "Description" +msgstr "Descrizione" + +#: class-wc-tab-manager.php:678 +msgid "Displays the product content set in the main content editor." +msgstr "" +"Mostra il contenuto del prodotto impostato nell'editor del contenuto " +"principale." + +#: class-wc-tab-manager.php:679 +msgid "Product Description" +msgstr "Descrizione del prodotto" + +#: class-wc-tab-manager.php:685 class-wc-tab-manager.php:687 +msgid "Additional Information" +msgstr "Informazioni addizionali" + +#: class-wc-tab-manager.php:686 +msgid "" +"Displays the product attributes and properties configured in the Product " +"Data panel." +msgstr "" +"Mostra gli attributi del prodotto e le proprietà configurate nel pannello " +"Dati del prodotto." + +#: class-wc-tab-manager.php:693 +msgid "Reviews (%d)" +msgstr "Recensioni (%d)" + +#: class-wc-tab-manager.php:694 +msgid "" +"Displays the product review form and any reviews. Use %d in the Title to " +"substitute the number of reviews for the product." +msgstr "" +"Mostra il modulo di recensione del prodotto e le recensioni pubblicate. " +"Utilizza %d nel Titolo per sostituire il numero delle recensioni del " +"prodotto." + +#: src/class-wc-tab-manager-search.php:286 +msgid "Include tab content in search results?" +msgstr "Includere il contenuto della scheda nei risultati della ricerca?" + +#: src/class-wc-tab-manager-search.php:315 +msgid "Any tab layout" +msgstr "Qualsiasi modello di scheda" + +#: src/class-wc-tab-manager-search.php:318 +msgid "Default layout" +msgstr "Modello predefinito" + +#: src/class-wc-tab-manager-search.php:321 +msgid "Custom layout" +msgstr "Modello personalizzato" + +#: src/class-wc-tab-manager-search.php:923 +msgid "" +"Hey there, Tab Manager has a nifty new search feature. If you'd like to take " +"advantage of this you'll need to update your products and tabs (you only " +"need to do this once)." +msgstr "" +"Ehilà! Tab Manager ha una nuova elegante funzione di ricerca. Se ti " +"piacerebbe trarne vantaggio, è necessario aggiornare i prodotti e le schede " +"(occorre fare questo solo una volta)." + +#: src/class-wc-tab-manager-search.php:926 +msgid "" +"It looks like you recently installed the Relevanssi plugin. If you'd like to " +"use the new Tab Manager search feature with Relevanssi, you'll need to " +"update your products and tabs so they can be indexed." +msgstr "" +"Sembra che tu abbia installato di recente il plugin Relevanssi. Se ti " +"piacerebbe utilizzare la nuova funzione di ricerca di Tab Manager con " +"Relevanssi, è necessario aggiornare i tuoi prodotti e le tue schede in modo " +"tale che vengano indicizzate." + +#: src/class-wc-tab-manager-search.php:933 +msgid "Go to the update page." +msgstr "Vai alla pagina dell'aggiornamento." + +#: src/class-wc-tab-manager-search.php:935 +msgid "OK, don't remind me again." +msgstr "OK, non ricordarmelo di nuovo." + +# +# +# +#: src/class-wc-tab-manager-settings.php:88 +msgid "Search Settings" +msgstr "Impostazioni della ricerca" + +#: src/class-wc-tab-manager-settings.php:96 +msgid "Include product tab content in search" +msgstr "Includi il contenuto della scheda del prodotto nella ricerca" + +#: src/class-wc-tab-manager-settings.php:100 +msgid "" +"Enable to include custom tab content (and global tab content if enabled) in " +"site search." +msgstr "" +"Abilita per includere il contenuto delle schede personalizzate (e il " +"contenuto delle schede globali, se abilitate) nella ricerca." + +#: src/class-wc-tab-manager-settings.php:122 +msgid "Update Products & Tabs" +msgstr "Aggiorna prodotti e schede" + +#: src/class-wc-tab-manager-settings.php:129 +msgid "Update" +msgstr "Aggiorna" + +#: src/class-wc-tab-manager-settings.php:135 +msgid "" +"This update will allow your product tab content to show up in your site’s " +"search results, making it easier for customers to find products whose tabs " +"contain the search query." +msgstr "" +"Questo aggiornamento consentirà al contenuto delle tue schede del prodotto " +"di pervenire tra i risultati della ricerca del tuo sito, rendendo più facile " +"ai tuoi clienti di trovare prodotti le cui schede contengono le chiavi di " +"ricerca." + +#: templates/js/admin.tmpl.php:3 +msgid "Updating your products and tabs..." +msgstr "Sto aggiornando i tuoi prodotti e le loro schede..." + +#: templates/js/admin.tmpl.php:6 +msgid "All done!" +msgstr "Tutto fatto!" + +#. translators: {{ data.current }} - current product count, {{ data.total }} - +#. total product count (eg. 2 of 168 products updated) +#: templates/js/admin.tmpl.php:9 +msgid "{{ data.current }} of {{ data.total }} products updated." +msgstr "Prodotti aggiornati: {{ data.current }} di {{ data.total }}." + +#. Plugin URI of the plugin/theme +msgid "http://www.woocommerce.com/products/woocommerce-tab-manager/" +msgstr "http://www.woocommerce.com/products/woocommerce-tab-manager/" + +#. Description of the plugin/theme +msgid "A product tab manager for WooCommerce" +msgstr "Uno strumento per gestire le schede dei prodotti WooCommerce" + +#. Author of the plugin/theme +msgid "SkyVerge" +msgstr "SkyVerge" + +#. Author URI of the plugin/theme +msgid "http://www.woocommerce.com" +msgstr "http://www.woocommerce.com" + +# +# +# +#: class-wc-tab-manager.php:302 +msgctxt "Admin menu name" +msgid "Tab Manager" +msgstr "Tab Manager" + +# +# +# +#: src/class-wc-tab-manager-settings.php:63 +msgctxt "Custom WooCommerce settings section" +msgid "Tab Manager" +msgstr "Tab Manager" + +# +# +# +#~ msgid "Add Tab" +#~ msgstr "Aggiungi tab" + +# +# +# +#~ msgid "Add New Tab" +#~ msgstr "Aggiungi nuovo tab" diff --git a/i18n/languages/woocommerce-tab-manager.pot b/i18n/languages/woocommerce-tab-manager.pot new file mode 100644 index 0000000..be8ed74 --- /dev/null +++ b/i18n/languages/woocommerce-tab-manager.pot @@ -0,0 +1,472 @@ +# Copyright (C) 2023 SkyVerge +# This file is distributed under the GNU General Public License v3.0. +msgid "" +msgstr "" +"Project-Id-Version: WooCommerce Tab Manager 1.16.0\n" +"Report-Msgid-Bugs-To: " +"https://woocommerce.com/my-account/marketplace-ticket-form/\n" +"POT-Creation-Date: 2023-06-20 10:49:37+00:00\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"PO-Revision-Date: 2023-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" + +#: admin/post-types/wc_product_tab.php:123 +msgid "Name" +msgstr "" + +#: admin/post-types/wc_product_tab.php:124 +msgid "Tab Type" +msgstr "" + +#: admin/post-types/wc_product_tab.php:125 +msgid "Parent Product" +msgstr "" + +#: admin/post-types/wc_product_tab.php:126 +msgid "Categories" +msgstr "" + +#: admin/post-types/wc_product_tab.php:131 +msgid "Searchable?" +msgstr "" + +#: admin/post-types/wc_product_tab.php:170 +msgid "Draft" +msgstr "" + +#: admin/post-types/wc_product_tab.php:203 +msgid "Restore this item from the Trash" +msgstr "" + +#: admin/post-types/wc_product_tab.php:203 +msgid "Restore" +msgstr "" + +#: admin/post-types/wc_product_tab.php:207 +msgid "Move this item to the Trash" +msgstr "" + +#: admin/post-types/wc_product_tab.php:207 +msgid "Trash" +msgstr "" + +#: admin/post-types/wc_product_tab.php:210 +msgid "Delete this item permanently" +msgstr "" + +#: admin/post-types/wc_product_tab.php:210 +#: admin/post-types/writepanels/writepanel-product-tab_actions.php:65 +msgid "Delete Permanently" +msgstr "" + +#: admin/post-types/wc_product_tab.php:236 +msgid "Product" +msgstr "" + +#: admin/post-types/wc_product_tab.php:238 +msgid "Global" +msgstr "" + +#: admin/post-types/wc_product_tab.php:249 +#: admin/post-types/wc_product_tab.php:274 +msgid "N/A" +msgstr "" + +#: admin/post-types/wc_product_tab.php:283 +msgid "Yes" +msgstr "" + +#: admin/post-types/wc_product_tab.php:288 +msgid "No" +msgstr "" + +#: admin/post-types/wc_product_tab.php:340 +msgid "Show all Tabs" +msgstr "" + +#: admin/post-types/wc_product_tab.php:343 +msgid "Show product tabs" +msgstr "" + +#: admin/post-types/wc_product_tab.php:346 +msgid "Show global tabs" +msgstr "" + +#: admin/post-types/wc_product_tab_metabox.php:65 +#: admin/post-types/wc_product_tab_metabox.php:137 +msgid "Product Categories" +msgstr "" + +#: admin/post-types/wc_product_tab_metabox.php:128 +msgid "Product-level tabs will always be shown on their assigned product." +msgstr "" + +#: admin/post-types/wc_product_tab_metabox.php:139 +msgid "Select categories to restrict the display of this tab to certain products." +msgstr "" + +#: admin/post-types/wc_product_tab_metabox.php:147 +msgid "Any category" +msgstr "" + +#: admin/post-types/writepanels/writepanel-product-tab_actions.php:55 +msgid "Save Tab" +msgstr "" + +#: admin/post-types/writepanels/writepanel-product-tab_actions.php:55 +msgid "Save/update the tab" +msgstr "" + +#: admin/post-types/writepanels/writepanel-product-tab_actions.php:67 +msgid "Move to Trash" +msgstr "" + +#: admin/post-types/writepanels/writepanel-product_data-tabs.php:42 +#: admin/woocommerce-tab-manager-admin-init.php:268 +#: class-wc-tab-manager.php:301 +msgid "Tabs" +msgstr "" + +#: admin/post-types/writepanels/writepanels-init.php:79 +#. translators: Placeholders: %1$s - , %2$s - +msgid "" +"Please %1$srun the tab search update%2$s to ensure the new tab content is " +"indexed and searchable." +msgstr "" + +#: admin/post-types/writepanels/writepanels-init.php:124 +msgid "Tab Actions" +msgstr "" + +#: admin/woocommerce-tab-manager-admin-functions.php:103 +msgid "Override default tab layout:" +msgstr "" + +#: admin/woocommerce-tab-manager-admin-functions.php:105 +msgid "Close all" +msgstr "" + +#: admin/woocommerce-tab-manager-admin-functions.php:105 +msgid "Expand all" +msgstr "" + +#: admin/woocommerce-tab-manager-admin-functions.php:153 +msgid "Edit Global Tab Content" +msgstr "" + +#: admin/woocommerce-tab-manager-admin-functions.php:237 +#: admin/woocommerce-tab-manager-admin-init.php:152 +msgid "Remove" +msgstr "" + +#: admin/woocommerce-tab-manager-admin-functions.php:238 +#: admin/woocommerce-tab-manager-admin-init.php:153 +msgid "Click to toggle" +msgstr "" + +#: admin/woocommerce-tab-manager-admin-functions.php:248 +msgid "The title/content for this tab will be provided by a third party plugin" +msgstr "" + +#: admin/woocommerce-tab-manager-admin-functions.php:253 +#: admin/woocommerce-tab-manager-admin-init.php:154 +msgid "Title" +msgstr "" + +#: admin/woocommerce-tab-manager-admin-functions.php:258 +#: admin/woocommerce-tab-manager-admin-init.php:155 +msgid "The tab title, this appears in the tab" +msgstr "" + +#: admin/woocommerce-tab-manager-admin-functions.php:264 +msgid "Heading" +msgstr "" + +#: admin/woocommerce-tab-manager-admin-functions.php:265 +msgid "The tab heading, this appears just before the tab content" +msgstr "" + +#: admin/woocommerce-tab-manager-admin-functions.php:270 +#: admin/woocommerce-tab-manager-admin-init.php:156 +msgid "Content" +msgstr "" + +#: admin/woocommerce-tab-manager-admin-functions.php:295 +msgid "Add" +msgstr "" + +#: admin/woocommerce-tab-manager-admin-functions.php:298 +msgid "Custom Tab" +msgstr "" + +#: admin/woocommerce-tab-manager-admin-global-layout.php:53 +#. translators: Placeholder: %s - updated notice (e.g. Tab layout updated, Tab +#. layout saved, etc.) +msgid "Tabs layout %s" +msgstr "" + +#: admin/woocommerce-tab-manager-admin-global-layout.php:61 +msgid "Default tab layout" +msgstr "" + +#: admin/woocommerce-tab-manager-admin-global-layout.php:79 +msgid "Save changes" +msgstr "" + +#: admin/woocommerce-tab-manager-admin-init.php:151 +msgid "Remove this product tab?" +msgstr "" + +#: admin/woocommerce-tab-manager-admin-init.php:199 +#: admin/woocommerce-tab-manager-admin-init.php:202 +#: admin/woocommerce-tab-manager-admin-init.php:204 +msgid "Tab updated." +msgstr "" + +#: admin/woocommerce-tab-manager-admin-init.php:200 +msgid "Custom field updated." +msgstr "" + +#: admin/woocommerce-tab-manager-admin-init.php:201 +msgid "Custom field deleted." +msgstr "" + +#: admin/woocommerce-tab-manager-admin-init.php:203 +msgid "Tab restored to revision from %s" +msgstr "" + +#: admin/woocommerce-tab-manager-admin-init.php:205 +msgid "Tab saved." +msgstr "" + +#: admin/woocommerce-tab-manager-admin-init.php:206 +msgid "Tab submitted." +msgstr "" + +#: admin/woocommerce-tab-manager-admin-init.php:208 +msgid "Tab scheduled for: %1$s." +msgstr "" + +#: admin/woocommerce-tab-manager-admin-init.php:209 +msgid "M j, Y @ G:i" +msgstr "" + +#: admin/woocommerce-tab-manager-admin-init.php:211 +msgid "Tab draft updated." +msgstr "" + +#: admin/woocommerce-tab-manager-admin-init.php:271 +#: admin/woocommerce-tab-manager-admin-init.php:352 +msgid "Default Tab Layout" +msgstr "" + +#: admin/woocommerce-tab-manager-admin-init.php:276 +#. translators: Placeholder: %s - search query keyword +msgid "Search results for “%s”" +msgstr "" + +#. Plugin Name of the plugin/theme +msgid "WooCommerce Tab Manager" +msgstr "" + +#: admin/woocommerce-tab-manager-admin-init.php:325 +msgid "Tab Manager" +msgstr "" + +#: admin/woocommerce-tab-manager-admin-init.php:332 +msgid "All Tabs" +msgstr "" + +#: admin/woocommerce-tab-manager-admin-init.php:344 +msgid "Add New Global Tab" +msgstr "" + +#: class-wc-tab-manager.php:302 +msgid "Tab" +msgstr "" + +#: class-wc-tab-manager.php:304 +msgid "Add global tab" +msgstr "" + +#: class-wc-tab-manager.php:305 +msgid "Add new global tab" +msgstr "" + +#: class-wc-tab-manager.php:306 +msgid "Edit" +msgstr "" + +#: class-wc-tab-manager.php:307 +msgid "Edit tab" +msgstr "" + +#: class-wc-tab-manager.php:308 +msgid "New tab" +msgstr "" + +#: class-wc-tab-manager.php:309 +msgid "View tabs" +msgstr "" + +#: class-wc-tab-manager.php:310 +msgid "View tab" +msgstr "" + +#: class-wc-tab-manager.php:311 +msgid "Search tabs" +msgstr "" + +#: class-wc-tab-manager.php:312 +msgid "No tabs found" +msgstr "" + +#: class-wc-tab-manager.php:313 +msgid "No tabs found in trash" +msgstr "" + +#: class-wc-tab-manager.php:315 +msgid "This is where you can add new tabs that you can add to products." +msgstr "" + +#: class-wc-tab-manager.php:678 +msgid "Description" +msgstr "" + +#: class-wc-tab-manager.php:679 +msgid "Displays the product content set in the main content editor." +msgstr "" + +#: class-wc-tab-manager.php:680 +msgid "Product Description" +msgstr "" + +#: class-wc-tab-manager.php:686 class-wc-tab-manager.php:688 +msgid "Additional Information" +msgstr "" + +#: class-wc-tab-manager.php:687 +msgid "" +"Displays the product attributes and properties configured in the Product " +"Data panel." +msgstr "" + +#: class-wc-tab-manager.php:694 +msgid "Reviews (%d)" +msgstr "" + +#: class-wc-tab-manager.php:695 +msgid "" +"Displays the product review form and any reviews. Use %d in the Title to " +"substitute the number of reviews for the product." +msgstr "" + +#: src/class-wc-tab-manager-search.php:286 +msgid "Include tab content in search results?" +msgstr "" + +#: src/class-wc-tab-manager-search.php:315 +msgid "Any tab layout" +msgstr "" + +#: src/class-wc-tab-manager-search.php:318 +msgid "Default layout" +msgstr "" + +#: src/class-wc-tab-manager-search.php:321 +msgid "Custom layout" +msgstr "" + +#: src/class-wc-tab-manager-search.php:923 +msgid "" +"Hey there, Tab Manager has a nifty new search feature. If you'd like to " +"take advantage of this you'll need to update your products and tabs (you " +"only need to do this once)." +msgstr "" + +#: src/class-wc-tab-manager-search.php:926 +msgid "" +"It looks like you recently installed the Relevanssi plugin. If you'd like " +"to use the new Tab Manager search feature with Relevanssi, you'll need to " +"update your products and tabs so they can be indexed." +msgstr "" + +#: src/class-wc-tab-manager-search.php:933 +msgid "Go to the update page." +msgstr "" + +#: src/class-wc-tab-manager-search.php:935 +msgid "OK, don't remind me again." +msgstr "" + +#: src/class-wc-tab-manager-settings.php:88 +msgid "Search Settings" +msgstr "" + +#: src/class-wc-tab-manager-settings.php:96 +msgid "Include product tab content in search" +msgstr "" + +#: src/class-wc-tab-manager-settings.php:100 +msgid "" +"Enable to include custom tab content (and global tab content if enabled) in " +"site search." +msgstr "" + +#: src/class-wc-tab-manager-settings.php:122 +msgid "Update Products & Tabs" +msgstr "" + +#: src/class-wc-tab-manager-settings.php:129 +msgid "Update" +msgstr "" + +#: src/class-wc-tab-manager-settings.php:135 +msgid "" +"This update will allow your product tab content to show up in your site’s " +"search results, making it easier for customers to find products whose tabs " +"contain the search query." +msgstr "" + +#: templates/js/admin.tmpl.php:3 +msgid "Updating your products and tabs..." +msgstr "" + +#: templates/js/admin.tmpl.php:6 +msgid "All done!" +msgstr "" + +#: templates/js/admin.tmpl.php:9 +#. translators: {{ data.current }} - current product count, {{ data.total }} - +#. total product count (eg. 2 of 168 products updated) +msgid "{{ data.current }} of {{ data.total }} products updated." +msgstr "" + +#. Plugin URI of the plugin/theme +msgid "http://www.woocommerce.com/products/woocommerce-tab-manager/" +msgstr "" + +#. Description of the plugin/theme +msgid "A product tab manager for WooCommerce" +msgstr "" + +#. Author of the plugin/theme +msgid "SkyVerge" +msgstr "" + +#. Author URI of the plugin/theme +msgid "http://www.woocommerce.com" +msgstr "" + +#: class-wc-tab-manager.php:303 +msgctxt "Admin menu name" +msgid "Tab Manager" +msgstr "" + +#: src/class-wc-tab-manager-settings.php:63 +msgctxt "Custom WooCommerce settings section" +msgid "Tab Manager" +msgstr "" \ No newline at end of file diff --git a/readme.txt b/readme.txt new file mode 100644 index 0000000..1a09e4e --- /dev/null +++ b/readme.txt @@ -0,0 +1,15 @@ +=== WooCommerce Tab Manager === +Author: skyverge +Tags: woocommerce +Requires at least: 5.6 +Tested up to: 6.2.2 +Requires PHP: 7.4 + +A product tab manager for WooCommerce + +See https://docs.woocommerce.com/document/tab-manager/ for full documentation. + +== Installation == + +1. Upload the entire 'woocommerce-tab-manager' folder to the '/wp-content/plugins/' directory +2. Activate the plugin through the 'Plugins' menu in WordPress diff --git a/src/Lifecycle.php b/src/Lifecycle.php new file mode 100644 index 0000000..f293b1f --- /dev/null +++ b/src/Lifecycle.php @@ -0,0 +1,293 @@ +upgrade_versions = [ + '1.0.4.1', + '1.0.5', + '1.3.1', + ]; + } + + + /** + * Performs install tasks. + * + * @since 1.10.0 + */ + protected function install() { + global $wpdb; + + // check for a pre 1.1 version + $legacy_version = get_option( 'wc_tab_manager_db_version' ); + + if ( false !== $legacy_version ) { + + // upgrade path from previous version, trash old version option + delete_option( 'wc_tab_manager_db_version' ); + + // upgrade path + $this->upgrade( $legacy_version ); + + // and we're done + return; + } + + // any Custom Product Lite Tabs? + $results = $wpdb->get_results( " + SELECT post_id, meta_value + FROM {$wpdb->postmeta} + WHERE meta_key='frs_woo_product_tabs' + " ); + + // prepare the core tabs + $core_tabs = $this->get_plugin()->get_core_tabs(); + foreach ( $core_tabs as $id => $tab ) { + unset( $core_tabs[ $id ]['description'] ); + } + + // foreach product with a custom lite tab + foreach ( $results as $result ) { + + $old_tabs = maybe_unserialize( $result->meta_value ); + + $new_tabs = [ 'core_tab_description' => $core_tabs['core_tab_description'], 'core_tab_additional_information' => $core_tabs['core_tab_additional_information'] ]; + + // keep track of tab names to avoid clashes + $found_names = [ 'description' => 1, 'additional_information' => 1, 'reviews' => 1 ]; + + foreach ( $old_tabs as $tab ) { + + // create the product tab + if ( $tab['title'] && $tab['content'] ) { + + $new_tab = [ + 'position' => count( $new_tabs ), + 'type' => 'product' + ]; + + $new_tab_data = [ + 'post_title' => $tab['title'], + 'post_content' => $tab['content'], + 'post_status' => 'publish', + 'ping_status' => 'closed', + 'post_author' => get_current_user_id(), + 'post_type' => 'wc_product_tab', + 'post_parent' => $result->post_id, + 'post_password' => uniqid( 'tab_', false ), // Protects the post just in case + ]; + + // create the post and get the id + $id = wp_insert_post( $new_tab_data ); + $new_tab['id'] = $id; + + // determine the unique tab name + $tab_name = sanitize_title( $tab['title'] ); + if ( ! isset( $found_names[ $tab_name ] ) ) { + $found_names[ $tab_name ] = 1; + } else { + $found_names[ $tab_name ]++; + } + if ( $found_names[ $tab_name ] > 1 ) { + $tab_name .= '-' . ( $found_names[ $tab_name ] - 1 ); + } + $new_tab['name'] = $tab_name; + + // tab is complete + $new_tabs[ 'product_tab_' . $id ] = $new_tab; + } + } + + // add the core reviews tab on at the end + $new_tabs['core_tab_reviews'] = $core_tabs['core_tab_reviews']; + $new_tabs['core_tab_reviews']['position'] = count( $new_tabs ) - 1; + + + if ( count( $new_tabs ) > 3 ) { + // if we actually had any product tabs + add_post_meta( $result->post_id, '_product_tabs', $new_tabs, true ); + add_post_meta( $result->post_id, '_override_tab_layout', 'yes', true ); + } + } + } + + + /** + * Updates to version 1.0.4.1 + * + * In this version and before: + * - custom product lite tabs were imported but their status was set to 'future' meaning they appeared in the Tab Manager menu, but not at the product level + * - product tab layout had 'tab_name' rather than 'name' for imported custom product lite tabs + * - imported custom product lite tabs attached to products did not have the '_override_tab_layout' meta set + * + * @since 1.10.1 + */ + protected function upgrade_to_1_0_4_1() { + + $tabs = get_posts( [ + 'numberposts' => '', + 'post_type' => 'wc_product_tab', + 'nopaging' => true, + 'post_status' => 'future' + ] ); + + if ( is_array( $tabs ) ) { + + foreach( $tabs as $tab ) { + + // make the tab post status 'publish' + wp_update_post( [ + 'ID' => $tab->ID, + 'post_status' => 'publish', + ] ); + + // mark the tab as migrated, in case we need to reference them one day + add_post_meta( $tab->ID, '_migrated_future', 'yes' ); + + // fix the product tab layout 'tab_name' field, which should be 'name' + $fixed = false; + $product_tabs = get_post_meta( $tab->post_parent, '_product_tabs', true ); + + foreach ( $product_tabs as $index => $product_tab ) { + + if ( isset( $product_tab['tab_name'] ) && $product_tab['tab_name'] && ! isset( $product_tab['name'] ) ) { + + $product_tabs[ $index ]['name'] = $product_tab['tab_name']; + + unset( $product_tabs[ $index ]['tab_name'] ); + + $fixed = true; + } + } + + if ( $fixed ) { + update_post_meta( $tab->post_parent, '_product_tabs', $product_tabs ); + } + + // It seems that setting the tab layout override in existing stores would be too dangerous, so for now the following is not used enable the product '_override_tab_layout' so the product tab is actually used: + // update_post_meta( $tab->post_parent, '_override_tab_layout', 'yes' ); + } + } + + unset( $tabs ); + } + + + /** + * Updates to version 1.0.5 + * + * In version 1.0.5 the core tab previously referred to as 'attributes' now needs to be referred to as 'additional_information' for consistency with WC 2.0+, so fix the global and any product tab layouts. + * + * @since 1.10.1 + */ + protected function upgrade_to_1_0_5() { + global $wpdb; + + // fix global tab layout + $tab_layout = get_option( 'wc_tab_manager_default_layout' ); + + if ( $tab_layout && isset( $tab_layout['core_tab_attributes'] ) ) { + + $tab_layout['core_tab_additional_information'] = $tab_layout['core_tab_attributes']; + $tab_layout['core_tab_additional_information']['id'] = 'additional_information'; + + unset( $tab_layout['core_tab_attributes'] ); + + update_option( 'wc_tab_manager_default_layout', $tab_layout ); + } + + // fix any product-level tab layouts + $results = $wpdb->get_results( " + SELECT post_id, meta_value FROM {$wpdb->postmeta} + WHERE meta_key='_product_tabs'" + ); + + if ( is_array( $results ) ) { + + foreach ( $results as $row ) { + + $tab_layout = maybe_unserialize( $row->meta_value ); + + if ( $tab_layout && isset( $tab_layout['core_tab_attributes'] ) ) { + + $tab_layout['core_tab_additional_information'] = $tab_layout['core_tab_attributes']; + $tab_layout['core_tab_additional_information']['id'] = 'additional_information'; + + unset( $tab_layout['core_tab_attributes'] ); + + update_post_meta( $row->post_id, '_product_tabs', $tab_layout ); + } + } + } + + unset( $results ); + } + + + /** + * Updates to version 1.3.1 + * + * @since 1.10.1 + */ + protected function upgrade_to_1_3_1() { + + // enable the batch product update nag + $user = wp_get_current_user(); + + if ( $user ) { + update_user_meta( $user->ID, 'wc_tab_manager_show_update_products_nag', 'true' ); + } + + // ensures that tabs are searchable if Relevanssi is installed + $this->get_plugin()->get_search_instance()->update_relevanssi_searchable_tab_meta(); + $this->get_plugin()->get_search_instance()->maybe_build_relevanssi_index(); + } + + +} diff --git a/src/class-wc-tab-manager-ajax-events.php b/src/class-wc-tab-manager-ajax-events.php new file mode 100644 index 0000000..39bea90 --- /dev/null +++ b/src/class-wc-tab-manager-ajax-events.php @@ -0,0 +1,118 @@ + 'product_tab_content[' . $size . ']', 'tinymce' => false, 'textarea_rows' => 10 ) ); + + ob_clean(); + + wp_editor( '', 'producttabcontent' . $size, array( 'textarea_name' => 'product_tab_content[' . $size . ']', 'tinymce' => false, 'textarea_rows' => 10 ) ); + + $content = ob_get_contents(); + + ob_end_clean(); + + echo $content; + + // Quit out. + exit(); + } + + + /** + * Processes a batch of products via AJAX. + * + * @since 1.4.0 + */ + public function ajax_batch_update_products() { + + check_ajax_referer( 'wc_tab_manager_nonce', 'nonce' ); + + ignore_user_abort( true ); + + @set_time_limit( 0 ); + + $step = isset( $_POST['step'] ) ? absint( $_POST['step'] ) : 0; + + $args = array( + 'step' => $step, + ); + + $response = $this->get_plugin()->batch_update_products( $args ); + + wp_send_json_success( $response ); + } + + +} diff --git a/src/class-wc-tab-manager-search.php b/src/class-wc-tab-manager-search.php new file mode 100644 index 0000000..e08d059 --- /dev/null +++ b/src/class-wc-tab-manager-search.php @@ -0,0 +1,1113 @@ + admin_url( 'admin-ajax.php' ), + 'nonce' => wp_create_nonce( 'wc_tab_manager_nonce' ), + ); + + $asset_url = $this->get_plugin()->get_plugin_url() . '/assets/js/admin/wc-tab-manager-admin-batch-product-update.min.js'; + + wp_enqueue_script( 'wc_tab_manager_batch_product_update', $asset_url, array( 'jquery', 'wp-util' ), \WC_Tab_Manager::VERSION, true ); + + wp_localize_script( 'wc_tab_manager_batch_product_update', 'wc_tab_manager_admin_params', $script_settings ); + } + + + /** + * Adds a tab ID to the list of searchable tabs. + * + * @since 1.4.0 + * @param mixed $tab A post ID / object / array that corresponds to the tab you want to add. + * @param bool $update_post_meta true to update the product tab post meta. + * @return bool|null + */ + public function add_searchable_tab( $tab, $update_post_meta = true ) { + + $tab = $this->get_plugin()->ensure_post( $tab ); + + // Bail if invalid post object or product-level tab. + if ( empty( $tab ) || $tab->post_parent ) { + return false; + } + + // Get an array containing each searchable tab's ID. + $tabs = $this->get_searchable_tabs(); + + // Make sure the current tab is in the list. + if ( ! in_array( $tab->ID, $tabs, false ) ) { + $tabs[] = $tab->ID; + } + + // Update the product tab postmeta. + if ( $update_post_meta ) { + update_post_meta( $tab->ID, '_include_in_search', 'yes' ); + } + + // Update the list. + $this->update_searchable_tabs( $tabs ); + } + + + /** + * Removes a tab ID from the list of searchable tabs. + * + * @since 1.4.0 + * @param mixed $tab A post ID / object / array that corresponds to the tab you want to remove. + * @param bool $update_post_meta true to update the product tab post meta. + * @return bool|void + */ + public function remove_searchable_tab( $tab, $update_post_meta = true ) { + + $tab = $this->get_plugin()->ensure_post( $tab ); + + // Bail if invalid post object or product-level tab. + if ( empty( $tab ) || $tab->post_parent ) { + return false; + } + + // Get an array containing each searchable tab's ID. + $tabs = $this->get_searchable_tabs(); + + // See if the specified tab ID is in the list of searchable tabs. + if ( in_array( $tab->ID, $tabs, false ) ) { + + // If so, remove the tab ID from the list. + $tabs = array_diff( $tabs, array( $tab->ID ) ); + + // Re-index the array (not sure if this is necessary). + $tabs = array_merge( $tabs ); + + // Update the product tab postmeta. + if ( $update_post_meta ) { + delete_post_meta( $tab->ID, '_include_in_search' ); + } + + // Update the list. + $this->update_searchable_tabs( $tabs ); + } + } + + + /** + * Gets the saved list of searchable tabs. + * + * @since 1.4.0 + * @return array An array containing the numeric post ID for each tab. + */ + public function get_searchable_tabs() { + return get_option( 'wc_tab_manager_searchable_tabs', array() ); + } + + + /** + * Updates the saved list of searchable tabs. + * + * @since 1.4.0 + * @param array $tabs An array of post IDs / objects / arrays where each array value corresponds to a tab you want to include in the new list. + */ + public function update_searchable_tabs( $tabs = array() ) { + + // Bail out if it is not an array. + if ( ! is_array( $tabs ) ) { + return; + } + + // Remove duplicate entries, convert to integers, and remove 0 values. + $tabs = array_map( 'absint', array_unique( $tabs ) ); + $tabs = array_filter( $tabs ); + + update_option( 'wc_tab_manager_searchable_tabs', $tabs ); + + $this->update_relevanssi_searchable_tab_meta(); + } + + + /** + * Function to determine if a product tab is searchable. + * + * @since 1.4.0 + * @param mixed $tab A post ID / object / array that corresponds to the tab you want to check. + * @return bool Whether the specified product tab is searchable. + */ + public function is_searchable_tab( $tab ) { + + // Make sure we have a valid post object before continuing. + $tab = $this->get_plugin()->ensure_post( $tab ); + if ( empty( $tab ) ) { + return false; + } + + // All product-level tabs are searchable. + if ( $tab->post_parent ) { + return true; + } + + // Check searchable setting in post meta. + if ( 'yes' === get_post_meta( $tab->ID, '_include_in_search', true ) ) { + return true; + } + + return false; + } + + + /** + * Function to determine if a product tab is in the default layout. + * + * @since 1.4.0 + * @param mixed $tab A post ID / object / array that corresponds to the tab you want to check. + * @return bool Whether the specified product tab is in the default layout. + */ + public function is_default_tab( $tab ) { + + // Make sure we have a valid post object before continuing. + $tab = $this->get_plugin()->ensure_post( $tab ); + if ( empty( $tab ) ) { + return false; + } + + $default_tabs = get_option( 'wc_tab_manager_default_layout', array() ); + if ( empty( $default_tabs ) ) { + return false; + } + + $default_tab_ids = $this->get_plugin()->get_numeric_ids( $default_tabs ); + + return in_array( $tab->ID, $default_tab_ids, false ); + } + + + /** + * Outputs search-related form controls on the product tab actions metabox. + * + * @since 1.4.0 + * @param WP_Post $post The post object for the tab currently being edited. + */ + public function tab_actions_meta_box_inputs( $post ) { + + if ( ! $post->post_parent && 'yes' === get_option( 'wc_tab_manager_enable_search', 'yes' ) ) : ?> + +
  • + is_searchable_tab( $post->ID ); + $input_checked_att = $include_in_search ? 'checked="checked"' : ''; + ?> + + +
  • + + + + + + get_plugin()->get_current_post_type() ) { + return; + } + + $current_type = isset( $_GET['product_tab_layout'] ) ? $_GET['product_tab_layout'] : ''; + + ?> + + get( 'meta_query' ); + if ( empty( $meta_query ) ) { + $meta_query = array(); + } + + $tab_meta_query = $this->get_tab_layout_meta_query( $tab_layout ); + $meta_query = array_merge( $meta_query, $tab_meta_query ); + + $query->set( 'meta_query', $meta_query ); + + return $query; + } + + + /** + * Returns an array containing the meta type for each tab type; "combined" + * tabs have a single meta entry that contains the combined content of each + * tab, while "separate" tabs create a new meta entry for each tab. Each + * tab type is filtered by `wc_tab_manager_tab_meta_type_{tab_type}` and + * then the entire list is run through the `wc_tab_manager_tab_meta_types` + * filter. Useful if a customer needs to change the default settings. + * + * @since 1.4.0 + * @return array associative array where each key is a tab type ('product' or 'global') and each value is a meta type ('combined' or 'separate'). + */ + public function get_tab_meta_types() { + + $meta_types = array( + 'product' => 'combined', + 'global' => 'separate', + ); + + $filtered_types = array(); + + foreach ( $meta_types as $tab_type => $meta_type ) { + + /** + * Individual tab meta type filters. + * + * Allows customer to modify the default meta types on an individual + * basis. The filter name incorporates the tab type at the end (e.g. + * wc_tab_manager_tab_meta_type_product is used to filter the meta type + * for product-level tabs). + * + * @since 1.4.0 + * @param string $meta_type The meta type setting for the specified tab type. + */ + $filtered_types[ $tab_type ] = apply_filters( "wc_tab_manager_tab_meta_type_{$tab_type}", $meta_type ); + } + + /** + * All tab meta types filter. + * + * Allows customer to modify the default meta types. For example, if + * you wanted to combine global tab content you would do something like + * this in your filter callback: + * + * `$meta_types['global'] = 'combined'` + * + * @since 1.4.0 + * @param array $filtered_types The entire array of meta types. + */ + return apply_filters( 'wc_tab_manager_tab_meta_types', $filtered_types ); + } + + + /** + * Triggered when a product is saved that overrides the default tab layout + * and has custom product tabs. Checks for any new or deleted tabs and + * updates the tab content meta accordingly for the current product. + * + * @since 1.4.0 + * @param array $new_tabs The current set of tabs (after the update). + * @param array $old_tabs The previous set of tabs (before the update). + */ + public function on_product_tabs_updated( $new_tabs, $old_tabs ) { + $this->on_tabs_added_or_removed( $new_tabs, $old_tabs, 'custom' ); + } + + + /** + * Triggered when the default tab layout is updated. Checks for any new or + * deleted tabs and updates the tab content accordingly for any products + * that use the default layout. + * + * @since 1.4.0 + * @param array $new_tabs The current set of tabs (after the update). + * @param array $old_tabs The previous set of tabs (before the update). + */ + public function on_default_tabs_updated( $new_tabs, $old_tabs ) { + $this->on_tabs_added_or_removed( $new_tabs, $old_tabs, 'default' ); + } + + + /** + * Generic function to check for added or removed tabs whenever a list of + * tabs is saved. Updates tab content meta for any products associated with + * the added or removed tabs. + * + * @since 1.4.0 + * @see WC_Tab_Manager_Search::update_products_for_tabs() + * @param array $new_tabs The current set of tabs (after the update). + * @param array $old_tabs The previous set of tabs (before the update). + * @param string $target Determines products will be updated if a product ID can't be determined automatically from the current context. + */ + public function on_tabs_added_or_removed( $new_tabs, $old_tabs, $target ) { + + if ( ! is_array( $new_tabs ) || ! is_array( $old_tabs ) ) { + return; + } + + // Extract tab IDs excluding non-numeric IDs (e.g. core tabs). + $new_tabs = $this->get_plugin()->get_numeric_ids( $new_tabs ); + $old_tabs = $this->get_plugin()->get_numeric_ids( $old_tabs ); + + // `array_diff` isn't a bi-directional comparison, it returns an array + // containing all elements that are in array 1 but not array 2. + $added = array_diff( $new_tabs, $old_tabs ); + $removed = array_diff( $old_tabs, $new_tabs ); + + // Bail if no tabs have been added or removed. + if ( empty( $added ) && empty( $removed ) ) { + return; + } + + // Update product meta for tabs that were added / removed. + if ( ! empty( $added ) ) { + + $args = array( + 'action' => 'update', + 'target' => $target, + ); + + $this->update_products_for_tabs( $added, $args ); + } + + if ( ! empty( $removed ) ) { + + $args = array( + 'action' => 'remove', + 'target' => $target, + ); + + $this->update_products_for_tabs( $removed, $args ); + } + } + + + /** + * Finds all products associated with a specific tab and updates the tab + * content meta for each product. + * + * @since 1.4.0 + * @see \WC_Tab_Manager_Search::update_products_for_tabs() + * @see \WC_Tab_Manager_Search::get_tab_products() for more details about the `$args` parameter. + * @param int|string $tab_id A numeric post ID for the tab to process. + * @param array $args Optional. An array of arguments. Default empty. + * @return bool + */ + public function update_products_for_tab( $tab_id, $args = array() ) { + if ( ! is_numeric( $tab_id ) ) { + return false; + } + + $this->update_products_for_tabs( $tab_id, $args ); + } + + + /** + * Loops through an array of tab IDs and finds all products associated with + * each tab, then updates the tab content meta for each product. + * + * @since 1.4.0 + * @see \WC_Tab_Manager_Search::get_tab_products() for more details about the `$args` parameter. + * @param mixed $tab_id_list Either a numeric post ID or an array of numeric post IDs for the tab(s) to process. + * @param array $args Optional. An array of args. Default empty. + * @return bool + */ + public function update_products_for_tabs( $tab_id_list, $args = array() ) { + + // The `target` arg determines which tabs are updated; `all` means all + // tabs, `custom` means only tabs that override the default layout, and + // `default` means only tabs that don't override the default layout. + $defaults = array( + 'action' => 'update', // Can be 'update' or 'remove'. + 'target' => 'custom', // Can be 'all', 'custom', or 'default'. + 'product_id' => 0, + ); + + $args = wp_parse_args( $args, $defaults ); + $target = $args['target']; + + if ( ! is_array( $tab_id_list ) && is_numeric( $tab_id_list ) ) { + $tab_id_list = array( $tab_id_list ); + } + + $tab_id_list = array_filter( $tab_id_list, 'absint' ); + + // If a product ID was specified, use that for the product ID list. If + // not and we're on a product page or editing a product-level tab, only + // update the tab meta for that product; otherwise, update all products + // associated with each tab. + $product_id = isset( $args['product_id'] ) ? absint( $args['product_id'] ) : $this->get_plugin()->maybe_get_tab_product_id(); + + if ( $product_id ) { + $product_id_list = array( $product_id ); + } else { + $product_id_list = $this->get_tab_products( $tab_id_list, $args ); + } + + if ( empty( $product_id_list ) || empty( $tab_id_list ) ) { + return false; + } + + $tab_id_list = array_unique( $tab_id_list ); + $product_id_list = array_unique( $product_id_list ); + + $meta_key = ''; + $meta_types = $this->get_tab_meta_types(); + + foreach ( $product_id_list as $product_id ) { + + $override = ''; + + if ( $product = wc_get_product( $product_id ) ) { + $override = $product->get_meta( '_override_tab_layout' ); + } + + // If we're targeting products that use custom layouts and the + // current product doesn't use a custom layout we should remove + // the tab content meta. + if ( 'custom' === $target && 'yes' !== $override ) { + $action = 'remove'; + } else { + $action = $args['action']; + } + + foreach ( $tab_id_list as $tab_id ) { + + // Make sure we have a valid post object before continuing. + $tab = $this->get_plugin()->ensure_post( $tab_id ); + if ( empty( $tab ) ) { + continue; + } + + if ( isset( $tab->post_parent ) && $tab->post_parent ) { + $tab_type = 'product'; + } else { + $tab_type = 'global'; + } + + // Product tabs only apply to custom tab layouts, so if the + // current product isn't using a custom layout we should remove + // the tab content meta. + if ( 'product' === $tab_type && 'yes' !== $override ) { + $action = 'remove'; + } else { + $action = $args['action']; + } + + // `$meta_type` determines whether we should store each tab's + // content in a separate meta field or combine their content + // and store the result in a single meta field. + $meta_type = $meta_types[ $tab_type ]; + if ( 'separate' === $meta_type ) { + $meta_key = "_{$tab_type}_tab_{$tab_id}_content"; + } else if ( 'combined' === $meta_type ) { + $meta_key = "_{$tab_type}_tab_content"; + } + + if ( 'separate' === $meta_type ) { + if ( 'update' === $action ) { + update_post_meta( $product_id, $meta_key, $tab->post_content ); + } else if ( 'remove' === $action ) { + delete_post_meta( $product_id, $meta_key ); + } + } else if ( 'combined' === $meta_type ) { + if ( 'update' === $action ) { + if ( ! isset( $combined_content[ $tab_type ] ) ) { + $combined_content[ $tab_type ] = ''; + } + $combined_content[ $tab_type ] .= $tab->post_content . ' '; + } else { + delete_post_meta( $product_id, $meta_key ); + } + } + } + + if ( ! empty( $combined_content ) && 'update' === $action ) { + + foreach ( $combined_content as $tab_type => $content ) { + + if ( $product = wc_get_product( $product_id ) ) { + + $product->update_meta_data( "_{$tab_type}_tab_content", trim( $content ) ); + $product->save_meta_data(); + } + } + } + + $this->update_relevanssi_index_for_product( $product_id ); + } + } + + + /** + * Gets all products that include the specified tab ID(s). + * + * The type of products that are targeted will vary depending on the tab + * being processed: + * + * Product-level tabs: Returns the parent product + * Global default tabs: Returns all products that use the default layout + * Global non-default tabs: Returns all products that don't use the default layout + * + * @since 1.4.0 + * @param mixed $tab_id_list Optional. Either a numeric tab ID or an array of numeric tab IDs. Default empty. + * @param array $args { + * Optional. An array of arguments. + * + * @type string `action` The action to take on the tab content meta for each product found. + * Default 'update'. Accepts 'update', 'remove'. + * @type string `target` The type of products that should be targeted for each tab. Only used when a product ID is not specified and one can't be automatically determined. + * Default 'custom'. Accepts 'all', 'custom', 'default'. + * Note: 'all' targets all products, 'custom' targets products that use a custom tab layout, and 'default' targets products that use the default tab layout. + * @type int|string `product_id` A numeric ID for the product to update. If an ID isn't specified it is automatically determined. + * When saving a product, the current product ID is used. When updating a product-level tab from the Tab Manager, the tab's parent product ID is used. + * If an ID still hasn't been found, `get_posts()` is used to find products for each tab based on the value of `target`. + * } + * @return array An array of numeric product IDs. + */ + public function get_tab_products( $tab_id_list = array(), $args = array() ) { + + $args = wp_parse_args( $args, array( + 'action' => 'update', + 'target' => 'custom', + 'offset' => 0, + 'limit' => -1, + ) ); + + $target = $args['target']; + $offset = $args['offset']; + $limit = $args['limit']; + + // See if we have any default global tabs that are searchable. + $default_tabs = get_option( 'wc_tab_manager_default_layout', array() ); + + foreach ( $default_tabs as $tab ) { + + if ( 'global' !== $tab['type'] ) { + continue; + } + + // Check tab meta to see if tab is included in search results. + $tab_id = $tab['id']; + + if ( $this->is_searchable_tab( $tab_id ) ) { + + $searchable_tabs[] = $tab_id; + } + } + + // Get all products that use the updated global tab. + $query_args = array( + 'fields' => 'ids', + 'post_type' => 'product', + 'posts_per_page' => $limit, + 'offset' => $offset, + ); + + // Get meta query to target tabs with the specified layout. + $meta_query = $this->get_tab_layout_meta_query( $target ); + + // See if we have any specific custom tabs to check. + if ( 'custom' === $target ) { + + // If so, restrict the query to products that have one of the + // specified tab IDs in `_product_tabs`. + if ( ! empty( $tab_id_list ) ) { + + // Make sure we have an array of tab IDs. + if ( ! is_array( $tab_id_list ) ) { + $tab_id_list = array( $tab_id_list ); + } + + $tab_id_list_serialized = array(); + + // Get the serialized value for each tab ID. + foreach ( $tab_id_list as $tab_id ) { + + if ( ! is_numeric( $tab_id ) ) { + continue; + } + + // `_product_tabs` is a serialized array with data for each + // tab, hence the `sprintf()` for the meta value. + $tab_id_list_serialized[] = sprintf( '("product_tab_%d")', $tab_id ); + } + + // Convert the serialized tab ID list to a RegEx pattern. + $tab_id_list_regex = implode( '|', $tab_id_list_serialized ); + + // Modify the meta query. + $meta_query['relation'] = 'AND'; + $meta_query[] = array( + 'key' => '_product_tabs', + 'value' => $tab_id_list_regex, + 'compare' => 'RLIKE', + ); + } + } + + if ( 'all' !== $target && ! empty( $meta_query ) ) { + $query_args['meta_query'] = $meta_query; + } + + return get_posts( $query_args ); + } + + + /** + * Wrapper function for @see \WP_Query::parse_search_terms() + * + * @since 1.4.0 + * @param string|array $terms Terms to check + * @return array Terms that are not stopwords + */ + public function parse_search_terms( $terms ) { + + if ( empty( $terms ) ) { + $terms = ''; + } + + if ( is_string( $terms ) ) { + $terms = explode( ' ', $terms ); + } + + return parent::parse_search_terms( $terms ); + } + + + /** + * Returns a meta query designed to target products with a specific tab + * layout. + * + * @since 1.4.0 + * + * @link https://core.trac.wordpress.org/ticket/23268 + * + * @param string $layout The tab layout you want to target. Can be 'default' or 'custom'. + * @return string The meta query if a valid `$layout` value was passed; an empty string otherwise. + */ + public function get_tab_layout_meta_query( $layout = 'all' ) { + + if ( 'default' === $layout ) { + + return array( + 'relation' => 'OR', + // Prior to 3.9 the `NOT EXISTS` comparator would only work + // if `value` was set and was not null, empty, or 0. + array( + 'key' => '_override_tab_layout', + 'value' => 'bug #23268', + 'compare' => 'NOT EXISTS', + ), + array( + 'key' => '_override_tab_layout', + 'value' => 'no', + ), + ); + } + + if ( 'custom' === $layout ) { + + return array( + 'relation' => 'AND', + array( + 'key' => '_override_tab_layout', + 'value' => 'yes', + ), + ); + } + + return ''; + } + + + /** + * Adds the meta keys associated with product-level and global-level tab + * content to the main search query so that all product-level tabs will be + * included in the search results, as well as any global tabs that have + * been designated to be included in the search results. + * + * @since 1.4.0 + * @param array $clauses An array of search clauses where the key is the clause type (where, join, etc.) and the value is the SQL for that clause. + * @param WP_Query $_wp_query The WP_Query object for the current search. + * @return array The updated array of clauses. + */ + public function modify_search_clauses( $clauses, $_wp_query ) { + global $wpdb; + + if ( ! $_wp_query instanceof \WP_Query || 'no' === get_option( 'wc_tab_manager_enable_search', 'yes' ) || ! $_wp_query->is_search || is_admin() ) { + return $clauses; + } + + // get the current search terms and list of searchable tabs + $search_query = $_wp_query->get( 's' ); + $search_terms = $this->parse_search_terms( $search_query ); + $searchable_tabs = $this->get_searchable_tabs(); + + // get product-level tabs (which must have a parent value) + $custom_tabs = array_filter( get_posts( array( + 'fields' => 'id=>parent', + 'post_type' => 'wc_product_tab', + 'posts_per_page' => - 1, + ) ) ); + + // bail if either the term list or the tab ID list is empty + if ( empty( $search_terms ) || ( empty( $searchable_tabs ) && empty( $custom_tabs ) ) ) { + return $clauses; + } + + // get list of meta keys to search + $meta_keys = array( '_product_tab_content' ); + if ( ! empty( $searchable_tabs ) ) { + foreach ( $searchable_tabs as $tab_id ) { + $meta_keys[] = "_global_tab_{$tab_id}_content"; + } + } + + // add tab content to WHERE + $where = ''; + foreach ( $search_terms as $term ) { + foreach ( $meta_keys as $meta_key ) { + $meta_like = '%' . $wpdb->esc_like( $term ) . '%'; + $where_sql = " OR ( + {$wpdb->postmeta}.meta_key = %s + AND {$wpdb->postmeta}.meta_value LIKE %s + )"; + $where .= $wpdb->prepare( $where_sql, $meta_key, $meta_like ); + } + } + $clauses['where'] .= $where; + + // JOIN to include postmeta in search + if ( false === strpos( $clauses['join'], "INNER JOIN {$wpdb->postmeta} ON" ) ) { + $clauses['join'] .= " INNER JOIN {$wpdb->postmeta} ON ( {$wpdb->posts}.ID = {$wpdb->postmeta}.post_id )"; + } + + // GROUP BY post ID so we don't get duplicate posts + $clauses['groupby'] = "{$wpdb->posts}.ID"; + + return $clauses; + } + + + /** + * Displays a notice when upgrading the plugin from an older version that + * doesn't support tab content search to one that does. The notice informs + * the user that if they'd like to use these search features, they need to + * manually update their product meta entries. + * + * @since 1.4.0 + */ + public function update_products_after_upgrade_nag() { + $user = wp_get_current_user(); + + if ( isset( $_GET['wc_tab_manager_show_update_products_nag'] ) && 'false' === $_GET['wc_tab_manager_show_update_products_nag'] ) { + delete_user_meta( $user->ID, 'wc_tab_manager_show_update_products_nag' ); + } + + if ( isset( $_GET['wc_tab_manager_show_relevanssi_nag'] ) && 'false' === $_GET['wc_tab_manager_show_relevanssi_nag'] ) { + delete_user_meta( $user->ID, 'wc_tab_manager_show_relevanssi_nag' ); + } + + $show_update_nag = get_user_meta( $user->ID, 'wc_tab_manager_show_update_products_nag', true ); + $show_relevanssi_nag = get_user_meta( $user->ID, 'wc_tab_manager_show_relevanssi_nag', true ); + + if ( ! empty( $show_update_nag ) ) { + $nag_text = __( 'Hey there, Tab Manager has a nifty new search feature. If you\'d like to take advantage of this you\'ll need to update your products and tabs (you only need to do this once).', 'woocommerce-tab-manager' ); + $url_param = 'wc_tab_manager_show_update_products_nag'; + } else if ( ! empty( $show_relevanssi_nag ) ) { + $nag_text = __( 'It looks like you recently installed the Relevanssi plugin. If you\'d like to use the new Tab Manager search feature with Relevanssi, you\'ll need to update your products and tabs so they can be indexed.', 'woocommerce-tab-manager' ); + $url_param = 'wc_tab_manager_show_relevanssi_nag'; + } else { + return; + } + + $update_url = admin_url( "admin.php?page=wc-settings&tab=products§ion=wc_tab_manager&{$url_param}=false" ); + $update_url_text = __( 'Go to the update page.', 'woocommerce-tab-manager' ); + $dismiss_url = add_query_arg( $url_param, 'false' ); + $dismiss_url_text = __( 'OK, don\'t remind me again.', 'woocommerce-tab-manager' ); + + ?> +
    +

    + +

    +

    + + + | + + + +

    +
    + ID, 'wc_tab_manager_show_relevanssi_nag', true ); + + if ( empty( $show_nag ) ) { + + update_user_meta( $user->ID, 'wc_tab_manager_show_relevanssi_nag', true ); + } + } + } + + + /** + * Fires whenever a plugin is deactivated and checks to see if the plugin + * was Relevanssi. If so, the notice that was scheduled on de-activation is + * un-scheduled in case it hasn't been dismissed yet. + * + * @since 1.4.0 + * @param string $plugin The slug of the plugin that was just activated (e.g. `plugin-folder/plugin-file.php`). + * @param bool $network_activation True if the plugin was activated network-wide; false otherwise. + */ + public function detect_relevanssi_deactivation( $plugin, $network_activation ) { + + if ( is_string( $plugin ) && 'relevanssi/relevanssi.php' === $plugin ) { + + $user = wp_get_current_user(); + + delete_user_meta( $user->ID, 'wc_tab_manager_show_relevanssi_nag' ); + } + } + + + /** + * Forces Relevanssi to re-index a specific product. Used to keep the index + * up-to-date whenever a tab or product is updated. + * + * @since 1.4.0 + * @param int|string $product_id The numeric post ID for the product that should be re-indexed. + */ + public function update_relevanssi_index_for_product( $product_id ) { + + if ( is_callable( 'relevanssi_index_doc' ) && is_callable( 'relevanssi_get_custom_fields' ) ) { + + $custom_fields = relevanssi_get_custom_fields(); + + relevanssi_index_doc( $product_id, true, $custom_fields, true ); + } + } + + + /** + * Checks if Relevanssi is installed; if so, builds the index if it hasn't + * been built already. + * + * @since 1.4.0 + */ + public function maybe_build_relevanssi_index() { + + // Check if Relevanssi is installed and active. + if ( is_callable( 'relevanssi_build_index' ) ) { + + // Make sure that products are searchable. + $index_post_types = get_option( 'relevanssi_index_post_types' ); + if ( ! is_array( $index_post_types ) ) { + $index_post_types = array( 'product' ); + } else { + if ( ! in_array( 'product', $index_post_types, true ) ) { + $index_post_types[] = 'product'; + } + } + update_option( 'relevanssi_index_post_types', $index_post_types ); + + // Check if the index has been built yet. + if ( ! get_option( 'relevanssi_indexed' ) ) { + + // If not, build it now. + relevanssi_build_index(); + } + } + } + + + /** + * Checks if the Relevanssi plugin is installed and active and, if so, loops + * updates the `relevanssi_index_fields` option to ensure that all of our + * tab content meta fields are indexed. + * + * @since 1.4.0 + */ + public function update_relevanssi_searchable_tab_meta() { + + // Check if Relevanssi is installed and active. + if ( is_callable( 'relevanssi_get_custom_fields' ) ) { + + // Get the current list of searchable tabs. + $tabs = get_option( 'wc_tab_manager_searchable_tabs', array() ); + + // Get the current list of indexed custom fields. + $custom_fields = get_option( 'relevanssi_index_fields' ); + + // If any indexed fields were returned, split them into an array. + // Otherwise use an empty array. + if ( empty( $custom_fields ) ) { + $custom_fields = array(); + } else { + $custom_fields = explode( ',', $custom_fields ); + $custom_fields = array_map( 'trim', $custom_fields ); + } + + // Make sure our product tab content meta key is in the array. + if ( ! in_array( '_product_tab_content', $custom_fields, true ) ) { + $custom_fields[] = '_product_tab_content'; + } + + // Loop through the searchable tabs and make sure each tabs' meta + // key is in the array. + foreach ( $tabs as $tab_id ) { + + $tab_field = "_global_tab_{$tab_id}_content"; + + if ( ! in_array( $tab_field, $custom_fields, true ) ) { + + $custom_fields[] = $tab_field; + } + } + + // Loop through each field and make sure there aren't any meta keys + // for tabs that aren't searchable. + foreach ( $custom_fields as $index => $value ) { + + $id = str_replace( array( '_global_tab_', '_content' ), '', $value ); + + if ( is_numeric( $id ) && ! in_array( $id, $tabs, false ) ) { + + unset( $custom_fields[ $index ] ); + } + } + + // Convert back to a comma-separated list and save. + $custom_fields = implode( ',', $custom_fields ); + + update_option( 'relevanssi_index_fields', $custom_fields ); + } + } + + +} diff --git a/src/class-wc-tab-manager-settings.php b/src/class-wc-tab-manager-settings.php new file mode 100644 index 0000000..96a8dd2 --- /dev/null +++ b/src/class-wc-tab-manager-settings.php @@ -0,0 +1,147 @@ + _x( 'Tab Manager', 'Custom WooCommerce settings section', 'woocommerce-tab-manager' ), + ); + + return wp_parse_args( $new_sections, $sections ); + } + + + /** + * Adds any inputs that should be displayed in the settings section. + * + * @since 1.4.0 + * @param array $settings The default settings list. + * @param string $current_section The section that's currently being processed. + * @return array An updated settings list that includes our custom settings. + */ + public function add_settings( $settings, $current_section ) { + + // Bail if we're in a different section. + if ( 'wc_tab_manager' !== $current_section ) { + return $settings; + } + + // Add our custom settings. + $_settings = array( + array( + 'title' => __( 'Search Settings', 'woocommerce-tab-manager' ), + 'type' => 'title', + 'id' => 'search_settings', + ), + array( + 'type' => 'batch_update_button', + ), + array( + 'title' => __( 'Include product tab content in search', 'woocommerce-tab-manager' ), + 'type' => 'checkbox', + 'id' => 'wc_tab_manager_enable_search', + 'default' => 'yes', + 'desc' => __( 'Enable to include custom tab content (and global tab content if enabled) in site search.', 'woocommerce-tab-manager' ), + ), + array( + 'type' => 'sectionend', + 'id' => 'search_settings', + ), + ); + + return $_settings; + } + + + /** + * Renders the custom batch product update button. + * + * @since 1.4.0 + */ + public function batch_update_button() { + + ?> + + + + + +
    + + +
    +

    + +

    + + + + <# if ( ! data ) { #> + + <# } else { #> + <# if ( data.complete ) { #> + + <# } else { #> + + <# } #> + <# } #> + diff --git a/templates/single-product/tabs/content.php b/templates/single-product/tabs/content.php new file mode 100644 index 0000000..43f14c5 --- /dev/null +++ b/templates/single-product/tabs/content.php @@ -0,0 +1,36 @@ + + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright © + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright © + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/vendor/skyverge/wc-plugin-framework/woocommerce/Addresses/Address.php b/vendor/skyverge/wc-plugin-framework/woocommerce/Addresses/Address.php new file mode 100644 index 0000000..652534f --- /dev/null +++ b/vendor/skyverge/wc-plugin-framework/woocommerce/Addresses/Address.php @@ -0,0 +1,292 @@ +line_1; + } + + + /** + * Gets line 2 of the street address. + * + * @since 5.3.0 + * + * @return string + */ + public function get_line_2() { + + return $this->line_2; + } + + + /** + * Gets line 3 of the street address. + * + * @since 5.3.0 + * + * @return string + */ + public function get_line_3() { + + return $this->line_3; + } + + + /** + * Gets the locality or city. + * + * @since 5.3.0 + * + * @return string + */ + public function get_locality() { + + return $this->locality; + } + + + /** + * Gets the region or state. + * + * @since 5.3.0 + * + * @return string + */ + public function get_region() { + + return $this->region; + } + + + /** + * Gets the country. + * + * @since 5.3.0 + * + * @return string + */ + public function get_country() { + + return $this->country; + } + + + /** + * Gets the postcode. + * + * @since 5.3.0 + * + * @return string + */ + public function get_postcode() { + + return $this->postcode; + } + + + /** + * Gets the hash representation of this address. + * + * @see Address::get_hash_data() + * + * @since 5.3.0 + * + * @return string + */ + public function get_hash() { + + return md5( json_encode( $this->get_hash_data() ) ); + } + + + /** + * Gets the data used to generate a hash for the address. + * + * @since 5.3.0 + * + * @return string[] + */ + protected function get_hash_data() { + + return [ + $this->get_line_1(), + $this->get_line_2(), + $this->get_line_3(), + $this->get_locality(), + $this->get_region(), + $this->get_country(), + $this->get_postcode(), + ]; + } + + + /** Setter methods ************************************************************************************************/ + + + /** + * Sets line 1 of the street address. + * + * @since 5.3.0 + * + * @param string $value line 1 value + */ + public function set_line_1( $value ) { + + $this->line_1 = $value; + } + + + /** + * Sets line 2 of the street address. + * + * @since 5.3.0 + * + * @param string $value line 2 value + */ + public function set_line_2( $value ) { + + $this->line_2 = $value; + } + + + /** + * Gets line 3 of the street address. + * + * @since 5.3.0 + * + * @param string $value line 3 value + */ + public function set_line_3( $value ) { + + $this->line_3 = $value; + } + + + /** + * Gets the locality or city. + * + * @since 5.3.0 + * + * @param string $value locality value + */ + public function set_locality( $value ) { + + $this->locality = $value; + } + + + /** + * Gets the region or state. + * + * @since 5.3.0 + * + * @param string $value region value + */ + public function set_region( $value ) { + + $this->region = $value; + } + + + /** + * Sets the country. + * + * @since 5.3.0 + * + * @param string $value country value + */ + public function set_country( $value ) { + + $this->country = $value; + } + + + /** + * Sets the postcode. + * + * @since 5.3.0 + * + * @param string $value postcode value + */ + public function set_postcode( $value ) { + + $this->postcode = $value; + } + + +} + + +endif; diff --git a/vendor/skyverge/wc-plugin-framework/woocommerce/Addresses/Customer_Address.php b/vendor/skyverge/wc-plugin-framework/woocommerce/Addresses/Customer_Address.php new file mode 100644 index 0000000..3151d8b --- /dev/null +++ b/vendor/skyverge/wc-plugin-framework/woocommerce/Addresses/Customer_Address.php @@ -0,0 +1,150 @@ +first_name; + } + + + /** + * Gets the customer first name. + * + * @since 5.3.0 + * + * @return string + */ + public function get_last_name() { + + return $this->last_name; + } + + + /** + * Gets the data used to generate a hash for the address. + * + * @see Address::get_hash_data() + * + * @since 5.3.0 + * + * @return string[] + */ + protected function get_hash_data() { + + // add the first & last name to data used to generate the hash + return array_merge( [ + $this->get_first_name(), + $this->get_last_name(), + ], parent::get_hash_data() ); + } + + + /** Setter Methods ************************************************************************************************/ + + + /** + * Sets the customer first name. + * + * @since 5.3.0 + * + * @param string $value first name value + */ + public function set_first_name( $value ) { + + $this->first_name = $value; + } + + + /** + * Sets the customer last name. + * + * @since 5.3.0 + * + * @param string $value first name value + */ + public function set_last_name( $value ) { + + $this->last_name = $value; + } + + + /** + * Sets the full address based on a WooCommerce order. + * + * @since 5.3.0 + * + * @param \WC_Order $order WooCommerce order object + * @param string $type address type, like billing or shipping + */ + public function set_from_order( \WC_Order $order, $type = 'billing' ) { + + $this->set_first_name( $order->{"get_{$type}_first_name"}() ); + $this->set_last_name( $order->{"get_{$type}_last_name"}() ); + $this->set_line_1( $order->{"get_{$type}_address_1"}() ); + $this->set_line_2( $order->{"get_{$type}_address_2"}() ); + $this->set_locality( $order->{"get_{$type}_city"}() ); + $this->set_region( $order->{"get_{$type}_state"}() ); + $this->set_country( $order->{"get_{$type}_country"}() ); + $this->set_postcode( $order->{"get_{$type}_postcode"}() ); + } + + +} + + +endif; diff --git a/vendor/skyverge/wc-plugin-framework/woocommerce/Country_Helper.php b/vendor/skyverge/wc-plugin-framework/woocommerce/Country_Helper.php new file mode 100644 index 0000000..7bd5f3e --- /dev/null +++ b/vendor/skyverge/wc-plugin-framework/woocommerce/Country_Helper.php @@ -0,0 +1,661 @@ + ISO 3166-alpha3 */ + static public $alpha3 = [ + 'AF' => 'AFG', 'AL' => 'ALB', 'DZ' => 'DZA', 'AD' => 'AND', 'AO' => 'AGO', + 'AG' => 'ATG', 'AR' => 'ARG', 'AM' => 'ARM', 'AU' => 'AUS', 'AT' => 'AUT', + 'AZ' => 'AZE', 'BS' => 'BHS', 'BH' => 'BHR', 'BD' => 'BGD', 'BB' => 'BRB', + 'BY' => 'BLR', 'BE' => 'BEL', 'BZ' => 'BLZ', 'BJ' => 'BEN', 'BT' => 'BTN', + 'BO' => 'BOL', 'BA' => 'BIH', 'BW' => 'BWA', 'BR' => 'BRA', 'BN' => 'BRN', + 'BG' => 'BGR', 'BF' => 'BFA', 'BI' => 'BDI', 'KH' => 'KHM', 'CM' => 'CMR', + 'CA' => 'CAN', 'CV' => 'CPV', 'CF' => 'CAF', 'TD' => 'TCD', 'CL' => 'CHL', + 'CN' => 'CHN', 'CO' => 'COL', 'KM' => 'COM', 'CD' => 'COD', 'CG' => 'COG', + 'CR' => 'CRI', 'CI' => 'CIV', 'HR' => 'HRV', 'CU' => 'CUB', 'CY' => 'CYP', + 'CZ' => 'CZE', 'DK' => 'DNK', 'DJ' => 'DJI', 'DM' => 'DMA', 'DO' => 'DOM', + 'EC' => 'ECU', 'EG' => 'EGY', 'SV' => 'SLV', 'GQ' => 'GNQ', 'ER' => 'ERI', + 'EE' => 'EST', 'ET' => 'ETH', 'FJ' => 'FJI', 'FI' => 'FIN', 'FR' => 'FRA', + 'GA' => 'GAB', 'GM' => 'GMB', 'GE' => 'GEO', 'DE' => 'DEU', 'GH' => 'GHA', + 'GR' => 'GRC', 'GD' => 'GRD', 'GT' => 'GTM', 'GN' => 'GIN', 'GW' => 'GNB', + 'GY' => 'GUY', 'HT' => 'HTI', 'HN' => 'HND', 'HU' => 'HUN', 'IS' => 'ISL', + 'IN' => 'IND', 'ID' => 'IDN', 'IR' => 'IRN', 'IQ' => 'IRQ', 'IE' => 'IRL', + 'IL' => 'ISR', 'IT' => 'ITA', 'JM' => 'JAM', 'JP' => 'JPN', 'JO' => 'JOR', + 'KZ' => 'KAZ', 'KE' => 'KEN', 'KI' => 'KIR', 'KP' => 'PRK', 'KR' => 'KOR', + 'KW' => 'KWT', 'KG' => 'KGZ', 'LA' => 'LAO', 'LV' => 'LVA', 'LB' => 'LBN', + 'LS' => 'LSO', 'LR' => 'LBR', 'LY' => 'LBY', 'LI' => 'LIE', 'LT' => 'LTU', + 'LU' => 'LUX', 'MK' => 'MKD', 'MG' => 'MDG', 'MW' => 'MWI', 'MY' => 'MYS', + 'MV' => 'MDV', 'ML' => 'MLI', 'MT' => 'MLT', 'MH' => 'MHL', 'MR' => 'MRT', + 'MU' => 'MUS', 'MX' => 'MEX', 'FM' => 'FSM', 'MD' => 'MDA', 'MC' => 'MCO', + 'MN' => 'MNG', 'ME' => 'MNE', 'MA' => 'MAR', 'MZ' => 'MOZ', 'MM' => 'MMR', + 'NA' => 'NAM', 'NR' => 'NRU', 'NP' => 'NPL', 'NL' => 'NLD', 'NZ' => 'NZL', + 'NI' => 'NIC', 'NE' => 'NER', 'NG' => 'NGA', 'NO' => 'NOR', 'OM' => 'OMN', + 'PK' => 'PAK', 'PW' => 'PLW', 'PA' => 'PAN', 'PG' => 'PNG', 'PY' => 'PRY', + 'PE' => 'PER', 'PH' => 'PHL', 'PL' => 'POL', 'PT' => 'PRT', 'QA' => 'QAT', + 'RO' => 'ROU', 'RU' => 'RUS', 'RW' => 'RWA', 'KN' => 'KNA', 'LC' => 'LCA', + 'VC' => 'VCT', 'WS' => 'WSM', 'SM' => 'SMR', 'ST' => 'STP', 'SA' => 'SAU', + 'SN' => 'SEN', 'RS' => 'SRB', 'SC' => 'SYC', 'SL' => 'SLE', 'SG' => 'SGP', + 'SK' => 'SVK', 'SI' => 'SVN', 'SB' => 'SLB', 'SO' => 'SOM', 'ZA' => 'ZAF', + 'ES' => 'ESP', 'LK' => 'LKA', 'SD' => 'SDN', 'SR' => 'SUR', 'SZ' => 'SWZ', + 'SE' => 'SWE', 'CH' => 'CHE', 'SY' => 'SYR', 'TJ' => 'TJK', 'TZ' => 'TZA', + 'TH' => 'THA', 'TL' => 'TLS', 'TG' => 'TGO', 'TO' => 'TON', 'TT' => 'TTO', + 'TN' => 'TUN', 'TR' => 'TUR', 'TM' => 'TKM', 'TV' => 'TUV', 'UG' => 'UGA', + 'UA' => 'UKR', 'AE' => 'ARE', 'GB' => 'GBR', 'US' => 'USA', 'UY' => 'URY', + 'UZ' => 'UZB', 'VU' => 'VUT', 'VA' => 'VAT', 'VE' => 'VEN', 'VN' => 'VNM', + 'YE' => 'YEM', 'ZM' => 'ZMB', 'ZW' => 'ZWE', 'TW' => 'TWN', 'CX' => 'CXR', + 'CC' => 'CCK', 'HM' => 'HMD', 'NF' => 'NFK', 'NC' => 'NCL', 'PF' => 'PYF', + 'YT' => 'MYT', 'GP' => 'GLP', 'PM' => 'SPM', 'WF' => 'WLF', 'TF' => 'ATF', + 'BV' => 'BVT', 'CK' => 'COK', 'NU' => 'NIU', 'TK' => 'TKL', 'GG' => 'GGY', + 'IM' => 'IMN', 'JE' => 'JEY', 'AI' => 'AIA', 'BM' => 'BMU', 'IO' => 'IOT', + 'VG' => 'VGB', 'KY' => 'CYM', 'FK' => 'FLK', 'GI' => 'GIB', 'MS' => 'MSR', + 'PN' => 'PCN', 'SH' => 'SHN', 'GS' => 'SGS', 'TC' => 'TCA', 'MP' => 'MNP', + 'PR' => 'PRI', 'AS' => 'ASM', 'UM' => 'UMI', 'GU' => 'GUM', 'VI' => 'VIR', + 'HK' => 'HKG', 'MO' => 'MAC', 'FO' => 'FRO', 'GL' => 'GRL', 'GF' => 'GUF', + 'MQ' => 'MTQ', 'RE' => 'REU', 'AX' => 'ALA', 'AW' => 'ABW', 'AN' => 'ANT', + 'SJ' => 'SJM', 'AC' => 'ASC', 'TA' => 'TAA', 'AQ' => 'ATA', 'CW' => 'CUW', + ]; + + /** @var array ISO 3166-alpha2 => ISO 3166-numeric */ + static public $numeric = [ + 'AF' => '004', 'AX' => '248', 'AL' => '008', 'DZ' => '012', 'AS' => '016', + 'AD' => '020', 'AO' => '024', 'AI' => '660', 'AQ' => '010', 'AG' => '028', + 'AR' => '032', 'AM' => '051', 'AW' => '533', 'AU' => '036', 'AT' => '040', + 'AZ' => '031', 'BS' => '044', 'BH' => '048', 'BD' => '050', 'BB' => '052', + 'BY' => '112', 'BE' => '056', 'BZ' => '084', 'BJ' => '204', 'BM' => '060', + 'BT' => '064', 'BO' => '068', 'BQ' => '535', 'BA' => '070', 'BW' => '072', + 'BV' => '074', 'BR' => '076', 'IO' => '086', 'BN' => '096', 'BG' => '100', + 'BF' => '854', 'BI' => '108', 'KH' => '116', 'CM' => '120', 'CA' => '124', + 'CV' => '132', 'KY' => '136', 'CF' => '140', 'TD' => '148', 'CL' => '152', + 'CN' => '156', 'CX' => '162', 'CC' => '166', 'CO' => '170', 'KM' => '174', + 'CG' => '178', 'CD' => '180', 'CK' => '184', 'CR' => '188', 'CI' => '384', + 'HR' => '191', 'CU' => '192', 'CW' => '531', 'CY' => '196', 'CZ' => '203', + 'DK' => '208', 'DJ' => '262', 'DM' => '212', 'DO' => '214', 'EC' => '218', + 'EG' => '818', 'SV' => '222', 'GQ' => '226', 'ER' => '232', 'EE' => '233', + 'ET' => '231', 'FK' => '238', 'FO' => '234', 'FJ' => '242', 'FI' => '246', + 'FR' => '250', 'GF' => '254', 'PF' => '258', 'TF' => '260', 'GA' => '266', + 'GM' => '270', 'GE' => '268', 'DE' => '276', 'GH' => '288', 'GI' => '292', + 'GR' => '300', 'GL' => '304', 'GD' => '308', 'GP' => '312', 'GU' => '316', + 'GT' => '320', 'GG' => '831', 'GN' => '324', 'GW' => '624', 'GY' => '328', + 'HT' => '332', 'HM' => '334', 'VA' => '336', 'HN' => '340', 'HK' => '344', + 'HU' => '348', 'IS' => '352', 'IN' => '356', 'ID' => '360', 'IR' => '364', + 'IQ' => '368', 'IE' => '372', 'IM' => '833', 'IL' => '376', 'IT' => '380', + 'JM' => '388', 'JP' => '392', 'JE' => '832', 'JO' => '400', 'KZ' => '398', + 'KE' => '404', 'KI' => '296', 'KP' => '408', 'KR' => '410', 'KW' => '414', + 'KG' => '417', 'LA' => '418', 'LV' => '428', 'LB' => '422', 'LS' => '426', + 'LR' => '430', 'LY' => '434', 'LI' => '438', 'LT' => '440', 'LU' => '442', + 'MO' => '446', 'MK' => '807', 'MG' => '450', 'MW' => '454', 'MY' => '458', + 'MV' => '462', 'ML' => '466', 'MT' => '470', 'MH' => '584', 'MQ' => '474', + 'MR' => '478', 'MU' => '480', 'YT' => '175', 'MX' => '484', 'FM' => '583', + 'MD' => '498', 'MC' => '492', 'MN' => '496', 'ME' => '499', 'MS' => '500', + 'MA' => '504', 'MZ' => '508', 'MM' => '104', 'NA' => '516', 'NR' => '520', + 'NP' => '524', 'NL' => '528', 'NC' => '540', 'NZ' => '554', 'NI' => '558', + 'NE' => '562', 'NG' => '566', 'NU' => '570', 'NF' => '574', 'MP' => '580', + 'NO' => '578', 'OM' => '512', 'PK' => '586', 'PW' => '585', 'PS' => '275', + 'PA' => '591', 'PG' => '598', 'PY' => '600', 'PE' => '604', 'PH' => '608', + 'PN' => '612', 'PL' => '616', 'PT' => '620', 'PR' => '630', 'QA' => '634', + 'RE' => '638', 'RO' => '642', 'RU' => '643', 'RW' => '646', 'BL' => '652', + 'SH' => '654', 'KN' => '659', 'LC' => '662', 'MF' => '663', 'PM' => '666', + 'VC' => '670', 'WS' => '882', 'SM' => '674', 'ST' => '678', 'SA' => '682', + 'SN' => '686', 'RS' => '688', 'SC' => '690', 'SL' => '694', 'SG' => '702', + 'SX' => '534', 'SK' => '703', 'SI' => '705', 'SB' => '090', 'SO' => '706', + 'ZA' => '710', 'GS' => '239', 'SS' => '728', 'ES' => '724', 'LK' => '144', + 'SD' => '729', 'SR' => '740', 'SJ' => '744', 'SZ' => '748', 'SE' => '752', + 'CH' => '756', 'SY' => '760', 'TW' => '158', 'TJ' => '762', 'TZ' => '834', + 'TH' => '764', 'TL' => '626', 'TG' => '768', 'TK' => '772', 'TO' => '776', + 'TT' => '780', 'TN' => '788', 'TR' => '792', 'TM' => '795', 'TC' => '796', + 'TV' => '798', 'UG' => '800', 'UA' => '804', 'AE' => '784', 'GB' => '826', + 'US' => '840', 'UM' => '581', 'UY' => '858', 'UZ' => '860', 'VU' => '548', + 'VE' => '862', 'VN' => '704', 'VG' => '092', 'VI' => '850', 'WF' => '876', + 'EH' => '732', 'YE' => '887', 'ZM' => '894', 'ZW' => '716', + ]; + + /** @var array ISO 3166-alpha2 => phone calling code(s) */ + static public $calling_codes = [ + 'BD' => '+880', + 'BE' => '+32', + 'BF' => '+226', + 'BG' => '+359', + 'BA' => '+387', + 'BB' => '+1246', + 'WF' => '+681', + 'BL' => '+590', + 'BM' => '+1441', + 'BN' => '+673', + 'BO' => '+591', + 'BH' => '+973', + 'BI' => '+257', + 'BJ' => '+229', + 'BT' => '+975', + 'JM' => '+1876', + 'BV' => '', + 'BW' => '+267', + 'WS' => '+685', + 'BQ' => '+599', + 'BR' => '+55', + 'BS' => '+1242', + 'JE' => '+441534', + 'BY' => '+375', + 'BZ' => '+501', + 'RU' => '+7', + 'RW' => '+250', + 'RS' => '+381', + 'TL' => '+670', + 'RE' => '+262', + 'TM' => '+993', + 'TJ' => '+992', + 'RO' => '+40', + 'TK' => '+690', + 'GW' => '+245', + 'GU' => '+1671', + 'GT' => '+502', + 'GS' => '', + 'GR' => '+30', + 'GQ' => '+240', + 'GP' => '+590', + 'JP' => '+81', + 'GY' => '+592', + 'GG' => '+441481', + 'GF' => '+594', + 'GE' => '+995', + 'GD' => '+1473', + 'GB' => '+44', + 'GA' => '+241', + 'SV' => '+503', + 'GN' => '+224', + 'GM' => '+220', + 'GL' => '+299', + 'GI' => '+350', + 'GH' => '+233', + 'OM' => '+968', + 'TN' => '+216', + 'JO' => '+962', + 'HR' => '+385', + 'HT' => '+509', + 'HU' => '+36', + 'HK' => '+852', + 'HN' => '+504', + 'HM' => '', + 'VE' => '+58', + 'PR' => [ + '+1787', + '+1939', + ], + 'PS' => '+970', + 'PW' => '+680', + 'PT' => '+351', + 'SJ' => '+47', + 'PY' => '+595', + 'IQ' => '+964', + 'PA' => '+507', + 'PF' => '+689', + 'PG' => '+675', + 'PE' => '+51', + 'PK' => '+92', + 'PH' => '+63', + 'PN' => '+870', + 'PL' => '+48', + 'PM' => '+508', + 'ZM' => '+260', + 'EH' => '+212', + 'EE' => '+372', + 'EG' => '+20', + 'ZA' => '+27', + 'EC' => '+593', + 'IT' => '+39', + 'VN' => '+84', + 'SB' => '+677', + 'ET' => '+251', + 'SO' => '+252', + 'ZW' => '+263', + 'SA' => '+966', + 'ES' => '+34', + 'ER' => '+291', + 'ME' => '+382', + 'MD' => '+373', + 'MG' => '+261', + 'MF' => '+590', + 'MA' => '+212', + 'MC' => '+377', + 'UZ' => '+998', + 'MM' => '+95', + 'ML' => '+223', + 'MO' => '+853', + 'MN' => '+976', + 'MH' => '+692', + 'MK' => '+389', + 'MU' => '+230', + 'MT' => '+356', + 'MW' => '+265', + 'MV' => '+960', + 'MQ' => '+596', + 'MP' => '+1670', + 'MS' => '+1664', + 'MR' => '+222', + 'IM' => '+441624', + 'UG' => '+256', + 'TZ' => '+255', + 'MY' => '+60', + 'MX' => '+52', + 'IL' => '+972', + 'FR' => '+33', + 'IO' => '+246', + 'SH' => '+290', + 'FI' => '+358', + 'FJ' => '+679', + 'FK' => '+500', + 'FM' => '+691', + 'FO' => '+298', + 'NI' => '+505', + 'NL' => '+31', + 'NO' => '+47', + 'NA' => '+264', + 'VU' => '+678', + 'NC' => '+687', + 'NE' => '+227', + 'NF' => '+672', + 'NG' => '+234', + 'NZ' => '+64', + 'NP' => '+977', + 'NR' => '+674', + 'NU' => '+683', + 'CK' => '+682', + 'XK' => '', + 'CI' => '+225', + 'CH' => '+41', + 'CO' => '+57', + 'CN' => '+86', + 'CM' => '+237', + 'CL' => '+56', + 'CC' => '+61', + 'CA' => '+1', + 'CG' => '+242', + 'CF' => '+236', + 'CD' => '+243', + 'CZ' => '+420', + 'CY' => '+357', + 'CX' => '+61', + 'CR' => '+506', + 'CW' => '+599', + 'CV' => '+238', + 'CU' => '+53', + 'SZ' => '+268', + 'SY' => '+963', + 'SX' => '+599', + 'KG' => '+996', + 'KE' => '+254', + 'SS' => '+211', + 'SR' => '+597', + 'KI' => '+686', + 'KH' => '+855', + 'KN' => '+1869', + 'KM' => '+269', + 'ST' => '+239', + 'SK' => '+421', + 'KR' => '+82', + 'SI' => '+386', + 'KP' => '+850', + 'KW' => '+965', + 'SN' => '+221', + 'SM' => '+378', + 'SL' => '+232', + 'SC' => '+248', + 'KZ' => '+7', + 'KY' => '+1345', + 'SG' => '+65', + 'SE' => '+46', + 'SD' => '+249', + 'DO' => [ + '+1809', + '+1829', + '+1849', + ], + 'DM' => '+1767', + 'DJ' => '+253', + 'DK' => '+45', + 'VG' => '+1284', + 'DE' => '+49', + 'YE' => '+967', + 'DZ' => '+213', + 'US' => '+1', + 'UY' => '+598', + 'YT' => '+262', + 'UM' => '+1', + 'LB' => '+961', + 'LC' => '+1758', + 'LA' => '+856', + 'TV' => '+688', + 'TW' => '+886', + 'TT' => '+1868', + 'TR' => '+90', + 'LK' => '+94', + 'LI' => '+423', + 'LV' => '+371', + 'TO' => '+676', + 'LT' => '+370', + 'LU' => '+352', + 'LR' => '+231', + 'LS' => '+266', + 'TH' => '+66', + 'TF' => '', + 'TG' => '+228', + 'TD' => '+235', + 'TC' => '+1649', + 'LY' => '+218', + 'VA' => '+379', + 'VC' => '+1784', + 'AE' => '+971', + 'AD' => '+376', + 'AG' => '+1268', + 'AF' => '+93', + 'AI' => '+1264', + 'VI' => '+1340', + 'IS' => '+354', + 'IR' => '+98', + 'AM' => '+374', + 'AL' => '+355', + 'AO' => '+244', + 'AQ' => '', + 'AS' => '+1684', + 'AR' => '+54', + 'AU' => '+61', + 'AT' => '+43', + 'AW' => '+297', + 'IN' => '+91', + 'AX' => '+35818', + 'AZ' => '+994', + 'IE' => '+353', + 'ID' => '+62', + 'UA' => '+380', + 'QA' => '+974', + 'MZ' => '+258', + ]; + + + /** @var array flipped calling codes */ + protected static $flipped_calling_codes; + + + /** + * Convert a 2-character country code into its 3-character equivalent, or + * vice-versa, e.g. + * + * 1) given USA, returns US + * 2) given US, returns USA + * + * @since 5.4.3 + * + * @param string $code ISO-3166-alpha-2 or ISO-3166-alpha-3 country code + * @return string country code + */ + public static function convert_alpha_country_code( $code ) { + + $countries = 3 === strlen( $code ) ? array_flip( self::$alpha3 ) : self::$alpha3; + + return isset( $countries[ $code ] ) ? $countries[ $code ] : $code; + } + + + /** + * Converts an ISO 3166-alpha2 country code to an ISO 3166-alpha3 country code. + * + * @since 5.4.3 + * + * @param string $alpha2_code ISO 3166-alpha2 country code + * @return string ISO 3166-alpha3 country code + */ + public static function alpha2_to_alpha3( $alpha2_code ) { + + return isset( self::$alpha3[ $alpha2_code ] ) ? self::$alpha3[ $alpha2_code ] : ''; + } + + + /** + * Converts an ISO 3166-alpha2 country code to an ISO 3166-numeric country code. + * + * @since 5.4.3 + * + * @param string $alpha2_code ISO 3166-alpha2 country code + * @return string ISO 3166-numeric country code + */ + public static function alpha2_to_numeric( $alpha2_code ) { + + return isset( self::$numeric[ $alpha2_code ] ) ? self::$numeric[ $alpha2_code ] : ''; + } + + + /** + * Converts an ISO 3166-alpha2 country code to a calling code. + * + * This conversion is available in WC 3.6+ so we'll call out to that when available. + * + * @since 5.4.3 + * + * @param string $alpha2_code ISO 3166-alpha2 country code + * @return string calling code + */ + public static function alpha2_to_calling_code( $alpha2_code ) { + + // check not only for the right version, but if the helper is loaded & available + if ( SV_WC_Plugin_Compatibility::is_wc_version_gte( '3.6.0' ) && WC() && isset( WC()->countries ) && is_callable( [ WC()->countries, 'get_country_calling_code' ] ) ) { + + $calling_code = WC()->countries->get_country_calling_code( $alpha2_code ); + + } else { + + $calling_code = isset( self::$calling_codes[ $alpha2_code ] ) ? self::$calling_codes[ $alpha2_code ] : ''; + + // we can't really know _which_ code is to be used, so use the first + $calling_code = is_array( $calling_code ) ? $calling_code[0] : $calling_code; + } + + return $calling_code; + } + + + /** + * Converts an ISO 3166-alpha3 country code to an ISO 3166-alpha2 country code. + * + * @since 5.4.3 + * + * @param string $alpha3_code ISO 3166-alpha3 country code + * @return string ISO 3166-alpha2 country code + */ + public static function alpha3_to_alpha2( $alpha3_code ) { + + $countries = array_flip( self::$alpha3 ); + + return isset( $countries[ $alpha3_code ] ) ? $countries[ $alpha3_code ] : ''; + } + + + /** + * Converts an ISO 3166-alpha3 country code to an ISO 3166-numeric country code. + * + * @since 5.4.3 + * + * @param string $alpha3_code ISO 3166-alpha3 country code + * @return string ISO 3166-numeric country code + */ + public static function alpha3_to_numeric( $alpha3_code ) { + return self::alpha2_to_numeric( self::alpha3_to_alpha2( $alpha3_code ) ); + } + + + /** + * Converts an ISO 3166-alpha3 country code to a calling code. + * + * @since 5.4.3 + * + * @param string $alpha3_code ISO 3166-alpha3 country code + * @return string calling code + */ + public static function alpha3_to_calling_code( $alpha3_code ) { + return self::alpha2_to_calling_code( self::alpha3_to_alpha2( $alpha3_code ) ); + } + + + /** + * Converts an ISO 3166-numeric country code to an ISO 3166-alpha2 code. + * + * @since 5.4.3 + * + * @param string $numeric ISO 3166-numeric country code + * @return string ISO 3166-alpha2 country code + */ + public static function numeric_to_alpha2( $numeric ) { + + $codes = array_flip( self::$numeric ); + + return isset( $codes[ $numeric ] ) ? $codes[ $numeric ] : ''; + } + + + /** + * Converts an ISO 3166-numeric country code to an ISO 3166-alpha3 code. + * + * @since 5.4.3 + * + * @param string $numeric ISO 3166-numeric country code + * @return string ISO 3166-alpha3 country code + */ + public static function numeric_to_alpha3( $numeric ) { + return self::alpha2_to_alpha3( self::numeric_to_alpha2( $numeric ) ); + } + + + /** + * Converts an ISO 3166-numeric country code to a calling code. + * + * @since 5.4.3 + * + * @param string $numeric ISO 3166-numeric country code + * @return string calling code + */ + public static function numeric_to_calling_code( $numeric ) { + return self::alpha2_to_calling_code( self::numeric_to_alpha2( $numeric ) ); + } + + + /** + * Converts a country calling code to an ISO 3166-alpha2 code. + * + * @since 5.4.3 + * + * @param string $calling_code country calling code (includes leading '+') + * @return string ISO 3166-alpha2 code + */ + public static function calling_code_to_alpha2( $calling_code ) { + + $flipped_calling_codes = self::get_flipped_calling_codes(); + + return isset( $flipped_calling_codes[ $calling_code ] ) ? $flipped_calling_codes[ $calling_code ] : ''; + } + + + /** + * Converts a country calling code to an ISO 3166-alpha3 code. + * + * @since 5.4.3 + * + * @param string $calling_code country calling code (includes leading '+') + * @return string ISO 3166-alpha3 code + */ + public static function calling_code_to_alpha3( $calling_code ) { + + return self::alpha2_to_alpha3( self::calling_code_to_alpha2( $calling_code ) ); + } + + + /** + * Converts a country calling code to an ISO 3166-numeric code. + * + * @since 5.4.3 + * + * @param string $calling_code country calling code (includes leading '+') + * @return string ISO 3166-numeric code + */ + public static function calling_code_to_numeric( $calling_code ) { + + return self::alpha2_to_numeric( self::calling_code_to_alpha2( $calling_code ) ); + } + + + /** + * Gets the flipped version of the calling codes array. + * + * Since array_flip will fail on the calling codes array due to + * having some arrays as values, this custom function is necessary. + * + * @since 5.4.3 + * + * @return array + */ + public static function get_flipped_calling_codes() { + + if ( null === self::$flipped_calling_codes ) { + + $flipped_calling_codes = []; + + foreach ( self::$calling_codes as $alpha2 => $calling_code ) { + + if ( is_array( $calling_code ) ) { + + foreach ( $calling_code as $sub_code ) { + + $flipped_calling_codes[ $sub_code ] = $alpha2; + } + } else { + + $flipped_calling_codes[ $calling_code ] = $alpha2; + } + } + + self::$flipped_calling_codes = $flipped_calling_codes; + } + + return self::$flipped_calling_codes; + } + + +} + + +endif; diff --git a/vendor/skyverge/wc-plugin-framework/woocommerce/Handlers/Script_Handler.php b/vendor/skyverge/wc-plugin-framework/woocommerce/Handlers/Script_Handler.php new file mode 100644 index 0000000..679d2cd --- /dev/null +++ b/vendor/skyverge/wc-plugin-framework/woocommerce/Handlers/Script_Handler.php @@ -0,0 +1,343 @@ +add_hooks(); + } + + + /** + * Adds the action and filter hooks. + * + * @since 5.7.0 + */ + protected function add_hooks() { + + add_action( 'wp_ajax_wc_' . $this->get_id() . '_log_script_event', [ $this, 'ajax_log_event' ] ); + add_action( 'wp_ajax_nopriv_wc_' . $this->get_id() . '_log_script_event', [ $this, 'ajax_log_event' ] ); + } + + + /** + * Returns the JS handler class name. + * + * @since 5.7.0 + * + * @return string + */ + protected function get_js_handler_class_name() { + + return sprintf( '%s_v5_11_0', $this->js_handler_base_class_name ); + } + + + /** + * Returns the JS handler object name. + * + * @since 5.7.0 + * + * @return string + */ + protected function get_js_handler_object_name() { + + return 'wc_' . $this->get_id() . '_handler'; + } + + + /** + * Gets the JS event triggered after the JS handler class is loaded. + * + * @since 5.7.0 + * + * @return string + */ + protected function get_js_loaded_event() { + + return sprintf( '%s_loaded', strtolower( $this->get_js_handler_class_name() ) ); + } + + + /** + * Gets the handler instantiation JS wrapped in a safe load technique. + * + * @since 5.7.0 + * + * @param array $additional_args additional handler arguments, if any + * @param string $handler_name handler name, if different from self::get_js_handler_class_name() + * @param string $object_name object name, if different from self::get_js_handler_object_name() + * @return string + */ + protected function get_safe_handler_js( array $additional_args = [], $handler_name = '', $object_name = '' ) { + + if ( ! $handler_name ) { + $handler_name = $this->get_js_handler_class_name(); + } + + $load_function = 'load_' . $this->get_id() . '_handler'; + + ob_start(); + + ?> + function () { + get_handler_js( $additional_args, $handler_name, $object_name ); ?> + } + + try { + + if ( 'undefined' !== typeof ) { + (); + } else { + window.jQuery( document.body ).on( 'get_js_loaded_event() ); ?>', ); + } + + } catch ( err ) { + + get_js_handler_event_debug_log_request(); ?> + } + get_js_handler_args() ); + + /** + * Filters the JavaScript handler arguments. + * + * @since 5.7.0 + * + * @param array $args arguments to pass to the JS handler + * @param Script_Handler $handler script handler instance + */ + $args = apply_filters( 'wc_' . $this->get_id() . '_js_args', $args, $this ); + + if ( ! $handler_name ) { + $handler_name = $this->get_js_handler_class_name(); + } + + if ( ! $object_name ) { + $object_name = $this->get_js_handler_object_name(); + } + + return sprintf( 'window.%1$s = new %2$s( %3$s );', esc_js( $object_name ), esc_js( $handler_name ), json_encode( $args ) ); + } + + + /** + * Gets the JS handler arguments. + * + * @since 5.7.0 + * + * @return array + */ + protected function get_js_handler_args() { + + return []; + } + + + /** + * Gets inline JavaScript code to issue an AJAX request to log a script error event. + * + * @since 5.7.0 + * + * @return string + */ + protected function get_js_handler_event_debug_log_request() { + + ob_start(); + + ?> + + var errorName = '', + errorMessage = ''; + + if ( 'undefined' === typeof err || 0 === err.length || ! err ) { + errorName = ''; + errorMessage = 'get_js_handler_class_name() ) ); ?>'; + } else { + errorName = 'undefined' !== typeof err.name ? err.name : ''; + errorMessage = 'undefined' !== typeof err.message ? err.message : ''; + } + + is_logging_enabled() ) : ?> + + console.log( [ errorName, errorMessage ].filter( Boolean ).join( ' ' ) ); + + + + jQuery.post( '', { + action: 'get_id() . '_log_script_event' ); ?>', + security: 'get_id_dasherized() . '-log-script-event' ) ); ?>', + name: errorName, + message: errorMessage, + } ); + + is_logging_enabled() ) { + return; + } + + try { + + if ( ! wp_verify_nonce( SV_WC_Helper::get_posted_value( 'security' ), 'wc-' . $this->get_id_dasherized() . '-log-script-event' ) ) { + throw new SV_WC_Plugin_Exception( 'Invalid nonce.' ); + } + + $name = isset( $_POST['name'] ) && is_string( $_POST['name'] ) ? trim( $_POST['name'] ) : ''; + $message = isset( $_POST['message'] ) && is_string( $_POST['message'] ) ? trim( $_POST['message'] ) : ''; + + if ( ! $message ) { + throw new SV_WC_Plugin_Exception( 'A message is required.' ); + } + + if ( $name ) { + $message = "{$name} {$message}"; + } + + $this->log_event( $message ); + + wp_send_json_success(); + + } catch ( SV_WC_Plugin_Exception $exception ) { + + wp_send_json_error( $exception->getMessage() ); + } + } + + + /** + * Adds a log entry. + * + * @since 5.7.0 + * + * @param string $message message to log + */ + abstract protected function log_event( $message ); + + + /** Conditional methods *******************************************************************************************/ + + + /** + * Determines whether logging is enabled. + * + * @since 5.7.0 + * + * @return bool + */ + protected function is_logging_enabled() { + + return false; + } + + + /** Getter methods ************************************************************************************************/ + + + /** + * Gets the ID of this script handler. + * + * @since 5.7.0 + * + * @return string + */ + abstract public function get_id(); + + + /** + * Gets the ID, but dasherized. + * + * @since 5.7.0 + * + * @return string + */ + public function get_id_dasherized() { + + return str_replace( '_', '-', $this->get_id() ); + } + + +} + +endif; diff --git a/vendor/skyverge/wc-plugin-framework/woocommerce/Lifecycle.php b/vendor/skyverge/wc-plugin-framework/woocommerce/Lifecycle.php new file mode 100644 index 0000000..435a8d9 --- /dev/null +++ b/vendor/skyverge/wc-plugin-framework/woocommerce/Lifecycle.php @@ -0,0 +1,668 @@ +plugin = $plugin; + + $this->add_hooks(); + } + + + /** + * Adds the action & filter hooks. + * + * @since 5.1.0 + */ + protected function add_hooks() { + + // handle activation + add_action( 'admin_init', array( $this, 'handle_activation' ) ); + + // handle deactivation + add_action( 'deactivate_' . $this->get_plugin()->get_plugin_file(), array( $this, 'handle_deactivation' ) ); + + if ( is_admin() && ! wp_doing_ajax() ) { + + // initialize the plugin lifecycle + add_action( 'wp_loaded', array( $this, 'init' ) ); + + // add the admin notices + add_action( 'init', array( $this, 'add_admin_notices' ) ); + } + + // catch any milestones triggered by action + add_action( 'wc_' . $this->get_plugin()->get_id() . '_milestone_reached', array( $this, 'trigger_milestone' ), 10, 3 ); + } + + + /** + * Initializes the plugin lifecycle. + * + * @since 5.2.0 + */ + public function init() { + + // potentially handle a new activation + $this->handle_activation(); + + $installed_version = $this->get_installed_version(); + $plugin_version = $this->get_plugin()->get_version(); + + // installed version lower than plugin version? + if ( version_compare( $installed_version, $plugin_version, '<' ) ) { + + if ( ! $installed_version ) { + + $this->install(); + + // store the upgrade event regardless if there was a routine for it + $this->store_event( 'install' ); + + /** + * Fires after the plugin has been installed. + * + * @since 5.1.0 + */ + do_action( 'wc_' . $this->get_plugin()->get_id() . '_installed' ); + + } else { + + $this->upgrade( $installed_version ); + + // store the upgrade event regardless if there was a routine for it + $this->add_upgrade_event( $installed_version ); + + // if the plugin never had any previous milestones, consider them all reached so their notices aren't displayed + if ( ! $this->get_milestone_version() ) { + $this->set_milestone_version( $plugin_version ); + } + + /** + * Fires after the plugin has been updated. + * + * @since 5.1.0 + * + * @param string $installed_version previously installed version + */ + do_action( 'wc_' . $this->get_plugin()->get_id() . '_updated', $installed_version ); + } + + // new version number + $this->set_installed_version( $plugin_version ); + } + } + + + /** + * Triggers plugin activation. + * + * We don't use register_activation_hook() as that can't be called inside + * the 'plugins_loaded' action. Instead, we rely on setting to track the + * plugin's activation status. + * + * @internal + * + * @link https://developer.wordpress.org/reference/functions/register_activation_hook/#comment-2100 + * + * @since 5.2.0 + */ + public function handle_activation() { + + if ( ! get_option( 'wc_' . $this->get_plugin()->get_id() . '_is_active', false ) ) { + + $this->activate(); + + /** + * Fires when the plugin is activated. + * + * @since 5.2.0 + */ + do_action( 'wc_' . $this->get_plugin()->get_id() . '_activated' ); + + update_option( 'wc_' . $this->get_plugin()->get_id() . '_is_active', 'yes' ); + } + } + + + /** + * Triggers plugin deactivation. + * + * @internal + * + * @since 5.2.0 + */ + public function handle_deactivation() { + + // if the enhanced admin is available, delete all of this plugin's notes on deactivation + if ( SV_WC_Plugin_Compatibility::is_enhanced_admin_available() ) { + + Notes_Helper::delete_notes_with_source( $this->get_plugin()->get_id_dasherized() ); + + // if this is a gateway plugin, also delete the plugin's individual gateway notes + if ( $this->get_plugin() instanceof SV_WC_Payment_Gateway_Plugin ) { + + foreach ( $this->get_plugin()->get_gateways() as $gateway ) { + Notes_Helper::delete_notes_with_source( $gateway->get_id_dasherized() ); + } + } + } + + $this->deactivate(); + + /** + * Fires when the plugin is deactivated. + * + * @since 5.2.0 + */ + do_action( 'wc_' . $this->get_plugin()->get_id() . '_deactivated' ); + + delete_option( 'wc_' . $this->get_plugin()->get_id() . '_is_active' ); + } + + + /** + * Handles plugin activation. + * + * Plugins can override this to run their own activation tasks. + * + * Important Note: operations here should never be destructive for existing + * data. Since we rely on an option to track activation, it's possible for + * this to run outside of genuine activations. + * + * @since 5.2.0 + */ + public function activate() { + + // stub + } + + + /** + * Handles plugin deactivation. + * + * Plugins can override this to run their own deactivation tasks. + * + * @since 5.2.0 + */ + public function deactivate() { + + // stub + } + + + /** + * Helper method to install default settings for a plugin. + * + * @since 5.2.0 + * + * @param array $settings settings in format required by WC_Admin_Settings + */ + public function install_default_settings( array $settings ) { + + foreach ( $settings as $setting ) { + + if ( isset( $setting['id'], $setting['default'] ) ) { + update_option( $setting['id'], $setting['default'] ); + } + } + } + + + /** + * Performs any install tasks. + * + * @since 5.2.0 + */ + protected function install() { + + // stub + } + + + /** + * Performs any upgrade tasks based on the provided installed version. + * + * @since 5.2.0 + * + * @param string $installed_version installed version + */ + protected function upgrade( $installed_version ) { + + foreach ( $this->upgrade_versions as $upgrade_version ) { + + $upgrade_method = 'upgrade_to_' . str_replace( array( '.', '-' ), '_', $upgrade_version ); + + if ( version_compare( $installed_version, $upgrade_version, '<' ) && is_callable( array( $this, $upgrade_method ) ) ) { + + $this->get_plugin()->log( "Starting upgrade to v{$upgrade_version}" ); + + $this->$upgrade_method( $installed_version ); + + $this->get_plugin()->log( "Upgrade to v{$upgrade_version} complete" ); + } + } + } + + + /** + * Adds any lifecycle admin notices. + * + * @since 5.1.0 + */ + public function add_admin_notices() { + + // display any milestone notices + foreach ( $this->get_milestone_messages() as $id => $message ) { + + // bail if this notice was already dismissed + if ( ! $this->get_plugin()->get_admin_notice_handler()->should_display_notice( $id ) ) { + continue; + } + + /** + * Filters a milestone notice message. + * + * @since 5.1.0 + * + * @param string $message message text to be used for the milestone notice + * @param string $id milestone ID + */ + $message = apply_filters( 'wc_' . $this->get_plugin()->get_id() . '_milestone_message', $this->generate_milestone_notice_message( $message ), $id ); + + if ( $message ) { + + $this->get_plugin()->get_admin_notice_handler()->add_admin_notice( $message, $id, array( + 'always_show_on_settings' => false, + ) ); + + // only display one notice at a time + break; + } + } + } + + + /** Milestone Methods *****************************************************/ + + + /** + * Triggers a milestone. + * + * This will only be triggered if the install's "milestone version" is lower + * than $since. Plugins can specify $since as the version at which a + * milestone's feature was added. This prevents existing installs from + * triggering notices for milestones that have long passed, like a payment + * gateway's first successful payment. Omitting $since will assume the + * milestone has always existed and should only trigger for fresh installs. + * + * @since 5.1.0 + * + * @param string $id milestone ID + * @param string $message message to display to the user + * @param string $since the version since this milestone has existed in the plugin + * @return bool + */ + public function trigger_milestone( $id, $message, $since = '1.0.0' ) { + + // if the plugin was had milestones before this milestone was added, don't trigger it + if ( version_compare( $this->get_milestone_version(), $since, '>' ) ) { + return false; + } + + return $this->register_milestone_message( $id, $message ); + } + + + /** + * Generates a milestone notice message. + * + * @since 5.1.0 + * + * @param string $custom_message custom text that notes what milestone was completed. + * @return string + */ + protected function generate_milestone_notice_message( $custom_message ) { + + $message = ''; + + if ( $this->get_plugin()->get_reviews_url() ) { + + // to be prepended at random to each milestone notice + $exclamations = array( + __( 'Awesome', 'woocommerce-plugin-framework' ), + __( 'Fantastic', 'woocommerce-plugin-framework' ), + __( 'Cowabunga', 'woocommerce-plugin-framework' ), + __( 'Congratulations', 'woocommerce-plugin-framework' ), + __( 'Hot dog', 'woocommerce-plugin-framework' ), + ); + + $message = $exclamations[ array_rand( $exclamations ) ] . ', ' . esc_html( $custom_message ) . ' '; + + $message .= sprintf( + /* translators: Placeholders: %1$s - plugin name, %2$s - tag, %3$s - tag, %4$s - tag, %5$s - tag */ + __( 'Are you having a great experience with %1$s so far? Please consider %2$sleaving a review%3$s! If things aren\'t going quite as expected, we\'re happy to help -- please %4$sreach out to our support team%5$s.', 'woocommerce-plugin-framework' ), + '' . esc_html( $this->get_plugin()->get_plugin_name() ) . '', + '', '', + '', '' + ); + } + + return $message; + } + + + /** + * Registers a milestone message to be displayed in the admin. + * + * @since 5.1.0 + * @see Lifecycle::generate_milestone_notice_message() + * + * @param string $id milestone ID + * @param string $message message to display to the user + * @return bool whether the message was successfully registered + */ + public function register_milestone_message( $id, $message ) { + + $milestone_messages = $this->get_milestone_messages(); + $dismissed_notices = array_keys( $this->get_plugin()->get_admin_notice_handler()->get_dismissed_notices() ); + + // get the total number of dismissed milestone messages + $dismissed_milestone_messages = array_intersect( array_keys( $milestone_messages ), $dismissed_notices ); + + // if the user has dismissed more than three milestone messages already, don't add any more + if ( count( $dismissed_milestone_messages ) > 3 ) { + return false; + } + + $milestone_messages[ $id ] = $message; + + return update_option( 'wc_' . $this->get_plugin()->get_id() . '_milestone_messages', $milestone_messages ); + } + + + /** Event history methods *****************************************************************************************/ + + + /** + * Adds an upgrade lifecycle event. + * + * @since 5.4.0 + * + * @param string $from_version version upgrading from + * @param array $data extra data to add + * @return false|int + */ + public function add_upgrade_event( $from_version, array $data = array() ) { + + $data = array_merge( array( + 'from_version' => $from_version, + ), $data ); + + return $this->store_event( 'upgrade', $data ); + } + + + /** + * Adds a migration lifecycle event. + * + * @since 5.4.0 + * + * @param string $from_plugin plugin migrating from + * @param string $from_version version migrating from + * @param array $data extra data to add + * @return false|int + */ + public function add_migrate_event( $from_plugin, $from_version = '', array $data = array() ) { + + $data = array_merge( array( + 'from_plugin' => $from_plugin, + 'from_version' => $from_version, + ), $data ); + + return $this->store_event( 'migrate', $data ); + } + + + /** + * Stores a lifecycle event. + * + * This can be used to log installs, upgrades, etc... + * + * Uses a direct database query to avoid cache issues. + * + * @since 5.4.0 + * + * @param string $name lifecycle event name + * @param array $data any extra data to store + * @return false|int + */ + public function store_event( $name, array $data = array() ) { + global $wpdb; + + $history = $this->get_event_history(); + + $event = array( + 'name' => wc_clean( $name ), + 'time' => (int) current_time( 'timestamp' ), + 'version' => wc_clean( $this->get_plugin()->get_version() ), + ); + + if ( ! empty( $data ) ) { + $event['data'] = wc_clean( $data ); + } + + array_unshift( $history, $event ); + + // limit to the last 30 events + $history = array_slice( $history, 0, 29 ); + + return $wpdb->replace( + $wpdb->options, + array( + 'option_name' => $this->get_event_history_option_name(), + 'option_value' => json_encode( $history ), + 'autoload' => 'no', + ), + array( + '%s', + '%s', + ) + ); + } + + + /** + * Gets the lifecycle event history. + * + * The last 30 events are stored, with the latest first. + * + * @since 5.4.0 + * + * @return array + */ + public function get_event_history() { + global $wpdb; + + $history = array(); + + $results = $wpdb->get_var( $wpdb->prepare( " + SELECT option_value + FROM {$wpdb->options} + WHERE option_name = %s + ", $this->get_event_history_option_name() ) ); + + if ( $results ) { + $history = json_decode( $results, true ); + } + + return is_array( $history ) ? $history : array(); + } + + + /** + * Gets the event history option name. + * + * @since 5.4.0 + * + * @return string + */ + protected function get_event_history_option_name() { + + return 'wc_' . $this->get_plugin()->get_id() . '_lifecycle_events'; + } + + + /** Utility Methods *******************************************************/ + + + /** + * Gets the registered milestone messages. + * + * @since 5.1.0 + * + * @return array + */ + protected function get_milestone_messages() { + + return get_option( 'wc_' . $this->get_plugin()->get_id() . '_milestone_messages', array() ); + } + + + /** + * Sets the milestone version. + * + * @since 5.1.0 + * + * @param string $version plugin version + * @return bool + */ + public function set_milestone_version( $version ) { + + $this->milestone_version = $version; + + return update_option( 'wc_' . $this->get_plugin()->get_id() . '_milestone_version', $version ); + } + + + /** + * Gets the milestone version. + * + * @since 5.1.0 + * + * @return string + */ + public function get_milestone_version() { + + if ( ! $this->milestone_version ) { + $this->milestone_version = get_option( 'wc_' . $this->get_plugin()->get_id() . '_milestone_version', '' ); + } + + return $this->milestone_version; + } + + + /** + * Gets the currently installed plugin version. + * + * @since 5.2.0 + * + * @return string + */ + protected function get_installed_version() { + + return get_option( $this->get_plugin()->get_plugin_version_name() ); + } + + + /** + * Sets the installed plugin version. + * + * @since 5.2.0 + * + * @param string $version version to set + */ + protected function set_installed_version( $version ) { + + update_option( $this->get_plugin()->get_plugin_version_name(), $version ); + } + + + /** + * Gets the plugin instance. + * + * @since 5.1.0 + * + * @return SV_WC_Plugin|SV_WC_Payment_Gateway_Plugin + */ + protected function get_plugin() { + + return $this->plugin; + } + + +} + + +endif; diff --git a/vendor/skyverge/wc-plugin-framework/woocommerce/Settings_API/Abstract_Settings.php b/vendor/skyverge/wc-plugin-framework/woocommerce/Settings_API/Abstract_Settings.php new file mode 100644 index 0000000..a87ac01 --- /dev/null +++ b/vendor/skyverge/wc-plugin-framework/woocommerce/Settings_API/Abstract_Settings.php @@ -0,0 +1,537 @@ +id = $id; + + $this->register_settings(); + $this->load_settings(); + } + + + /** + * Registers the settings. + * + * Plugins or payment gateways should overwrite this method to register their settings. + * + * @since 5.7.0 + */ + abstract protected function register_settings(); + + + /** + * Loads the values for all registered settings. + * + * @since 5.7.0 + */ + protected function load_settings() { + + foreach ( $this->settings as $setting_id => $setting ) { + + $value = get_option( $this->get_option_name_prefix() . '_' . $setting_id, null ); + $value = $this->get_value_from_database( $value, $setting ); + + $this->settings[ $setting_id ]->set_value( $value ); + } + } + + + /** + * Registers a setting. + * + * @param string $id unique setting ID + * @param string $type setting type + * @param array $args setting arguments + * @return bool + */ + public function register_setting( $id, $type, array $args = [] ) { + + try { + + if ( ! empty( $this->settings[ $id ] ) ) { + throw new Framework\SV_WC_Plugin_Exception( "Setting {$id} is already registered" ); + } + + if ( ! in_array( $type, $this->get_setting_types(), true ) ) { + throw new Framework\SV_WC_Plugin_Exception( "{$type} is not a valid setting type" ); + } + + $setting = new Setting(); + + $setting->set_id( $id ); + $setting->set_type( $type ); + + $args = wp_parse_args( $args, [ + 'name' => '', + 'description' => '', + 'is_multi' => false, + 'options' => [], + 'default' => null, + ] ); + + $setting->set_name( $args['name'] ); + $setting->set_description( $args['description'] ); + $setting->set_default( $args['default'] ); + $setting->set_is_multi( $args['is_multi'] ); + + if ( is_array( $args['options'] ) ) { + $setting->set_options( $args['options'] ); + } + + $this->settings[ $id ] = $setting; + + return true; + + } catch ( \Exception $exception ) { + + wc_doing_it_wrong( __METHOD__, 'Could not register setting: ' . $exception->getMessage(), '5.7.0' ); + + return false; + } + } + + + /** + * Unregisters a setting. + * + * @since 5.7.0 + * + * @param string $id setting ID to unregister + */ + public function unregister_setting( $id ) { + + unset( $this->settings[ $id ] ); + } + + + /** + * Registers a control for a setting. + * + * @since 5.7.0 + * + * @param string $setting_id the setting ID + * @param string $type the control type + * @param array $args optional args for the control + * @return bool + */ + public function register_control( $setting_id, $type, array $args = [] ) { + + try { + + if ( ! in_array( $type, $this->get_control_types(), true ) ) { + throw new \UnexpectedValueException( "{$type} is not a valid control type" ); + } + + $setting = $this->get_setting( $setting_id ); + + if ( ! $setting ) { + throw new \InvalidArgumentException( "Setting {$setting_id} does not exist" ); + } + + $setting_control_types = $this->get_setting_control_types( $setting ); + if ( ! empty( $setting_control_types ) && ! in_array( $type, $setting_control_types, true ) ) { + throw new \UnexpectedValueException( "{$type} is not a valid control type for setting {$setting->get_id()} of type {$setting->get_type()}" ); + } + + $args = wp_parse_args( $args, [ + 'name' => $setting->get_name(), + 'description' => $setting->get_description(), + 'options' => [], + ] ); + + $control = new Control(); + + $control->set_setting_id( $setting_id ); + $control->set_type( $type ); + $control->set_name( $args['name'] ); + $control->set_description( $args['description'] ); + + if ( is_array( $args['options'] ) ) { + $control->set_options( $args['options'], $setting->get_options() ); + } + + $setting->set_control( $control ); + + return true; + + } catch ( \Exception $exception ) { + + wc_doing_it_wrong( __METHOD__, 'Could not register setting control: ' . $exception->getMessage(), '5.7.0' ); + + return false; + } + } + + + /** + * Gets the settings ID. + * + * @since 5.7.0 + * + * @return string + */ + public function get_id() { + + return $this->id; + } + + + /** + * Gets registered settings. + * + * It returns all settings by default, but you can pass an array of IDs to filter the results. + * + * @param string[] $ids setting IDs to get + * @return Setting[] + */ + public function get_settings( array $ids = [] ) { + + $settings = $this->settings; + + if ( ! empty( $ids ) ) { + + foreach ( array_keys( $this->settings ) as $id ) { + + if ( ! in_array( $id, $ids, true ) ) { + unset( $settings[ $id ] ); + } + } + } + + return $settings; + } + + + /** + * Gets a setting object. + * + * @since 5.7.0 + * + * @param string $id setting ID to get + * @return Setting|null + */ + public function get_setting( $id ) { + + return ! empty( $this->settings[ $id ] ) ? $this->settings[ $id ] : null; + } + + + /** + * Gets the stored value for a setting. + * + * Optionally, will return the setting's default value if nothing is stored. + * + * @since 5.7.0 + * + * @param string $setting_id setting ID + * @param bool $with_default whether to return the default value if nothing is stored + * @return array|bool|float|int|string + * @throws Framework\SV_WC_Plugin_Exception + */ + public function get_value( $setting_id, $with_default = true ) { + + $setting = $this->get_setting( $setting_id ); + + if ( ! $setting ) { + throw new Framework\SV_WC_Plugin_Exception( "Setting {$setting_id} does not exist" ); + } + + $value = $setting->get_value(); + + if ( $with_default && null === $value ) { + $value = $setting->get_default(); + } + + return $value; + } + + + /** + * Updates the stored value for a setting. + * + * @since 5.7.0 + * + * @param string $setting_id setting ID + * @param array|bool|float|int|string $value + * @throws Framework\SV_WC_Plugin_Exception + */ + public function update_value( $setting_id, $value ) { + + $setting = $this->get_setting( $setting_id ); + + if ( ! $setting ) { + throw new Framework\SV_WC_Plugin_Exception( "Setting {$setting_id} does not exist", 404 ); + } + + // performs the validations and updates the value + $setting->update_value( $value ); + + $this->save( $setting_id ); + } + + + /** + * Deletes the stored value for a setting. + * + * @since 5.7.0 + * + * @param string $setting_id setting ID + * @return bool + * @throws Framework\SV_WC_Plugin_Exception + */ + public function delete_value( $setting_id ) { + + $setting = $this->get_setting( $setting_id ); + + if ( ! $setting ) { + throw new Framework\SV_WC_Plugin_Exception( "Setting {$setting_id} does not exist" ); + } + + $setting->set_value( null ); + + return delete_option( "{$this->get_option_name_prefix()}_{$setting->get_id()}" ); + } + + + /** + * Saves registered settings in their current state. + * + * It saves all settings by default, but you can pass a setting ID to save a specific setting. + * + * @since 5.7.0 + * + * @param string $setting_id setting ID + */ + public function save( $setting_id = '' ) { + + if ( ! empty( $setting_id ) ) { + $settings = [ $this->get_setting( $setting_id ) ]; + } else { + $settings = $this->settings; + } + + $settings = array_filter( $settings ); + + foreach ( $settings as $setting ) { + + $option_name = "{$this->get_option_name_prefix()}_{$setting->get_id()}"; + $setting_value = $setting->get_value(); + + if ( null === $setting_value ) { + + delete_option( $option_name ); + + } else { + + update_option( $option_name, $this->get_value_for_database( $setting ) ); + } + } + } + + + /** + * Converts the value of a setting to be stored in an option. + * + * @since 5.7.0 + * + * @param Setting $setting + * @return mixed + */ + protected function get_value_for_database( Setting $setting ) { + + $value = $setting->get_value(); + + if ( null !== $value && Setting::TYPE_BOOLEAN === $setting->get_type() ) { + $value = wc_bool_to_string( $value ); + } + + return $value; + } + + + /** + * Converts the stored value of a setting to the proper setting type. + * + * @since 5.7.0 + * + * @param mixed $value the value stored in an option + * @param Setting $setting + * @return mixed + */ + protected function get_value_from_database( $value, Setting $setting ) { + + if ( null !== $value ) { + + switch ( $setting->get_type() ) { + + case Setting::TYPE_BOOLEAN: + $value = wc_string_to_bool( $value ); + break; + + case Setting::TYPE_INTEGER: + $value = is_numeric( $value ) ? (int) $value : null; + break; + + case Setting::TYPE_FLOAT: + $value = is_numeric( $value ) ? (float) $value : null; + break; + } + } + + return $value; + } + + + /** + * Gets the list of valid setting types. + * + * @since 5.7.0 + * + * @return string[] + */ + public function get_setting_types() { + + $setting_types = [ + Setting::TYPE_STRING, + Setting::TYPE_URL, + Setting::TYPE_EMAIL, + Setting::TYPE_INTEGER, + Setting::TYPE_FLOAT, + Setting::TYPE_BOOLEAN, + ]; + + /** + * Filters the list of valid setting types. + * + * @param string[] $setting_types valid setting types + * @param Abstract_Settings $settings the settings handler instance + */ + return apply_filters( "wc_{$this->get_id()}_settings_api_setting_types", $setting_types, $this ); + } + + + /** + * Gets the list of valid control types. + * + * @since 5.7.0 + * + * @return string[] + */ + public function get_control_types() { + + $control_types = [ + Control::TYPE_TEXT, + Control::TYPE_TEXTAREA, + Control::TYPE_NUMBER, + Control::TYPE_EMAIL, + Control::TYPE_PASSWORD, + Control::TYPE_DATE, + Control::TYPE_CHECKBOX, + Control::TYPE_RADIO, + Control::TYPE_SELECT, + Control::TYPE_FILE, + Control::TYPE_COLOR, + Control::TYPE_RANGE, + ]; + + /** + * Filters the list of valid control types. + * + * @param string[] $control_types valid control types + * @param Abstract_Settings $settings the settings handler instance + */ + return apply_filters( "wc_{$this->get_id()}_settings_api_control_types", $control_types, $this ); + } + + + /** + * Returns the valid control types for a setting. + * + * @since 5.7.0 + * + * @param Setting $setting setting object + * @return string[] + */ + public function get_setting_control_types( $setting ) { + + /** + * Filters the list of valid control types for a setting. + * + * @param string[] $control_types valid control types + * @param string $setting_type setting type + * @param Setting $setting setting object + * @param Abstract_Settings $settings the settings handler instance + */ + return apply_filters( "wc_{$this->get_id()}_settings_api_setting_control_types", [], $setting->get_type(), $setting, $this ); + } + + + /** + * Gets the prefix for db option names. + * + * @since 5.7.0 + * + * @return string + */ + public function get_option_name_prefix() { + + return "wc_{$this->id}"; + } + + +} + +endif; diff --git a/vendor/skyverge/wc-plugin-framework/woocommerce/Settings_API/Control.php b/vendor/skyverge/wc-plugin-framework/woocommerce/Settings_API/Control.php new file mode 100644 index 0000000..2d7c89f --- /dev/null +++ b/vendor/skyverge/wc-plugin-framework/woocommerce/Settings_API/Control.php @@ -0,0 +1,271 @@ + $label */ + protected $options = []; + + + /** Getter methods ************************************************************************************************/ + + + /** + * The setting ID to which this control belongs. + * + * @since 5.7.0 + * + * @return null|string + */ + public function get_setting_id() { + + return $this->setting_id; + } + + + /** + * Gets the control type. + * + * @since 5.7.0 + * + * @return null|string + */ + public function get_type() { + + return $this->type; + } + + + /** + * Gets the control name. + * + * @since 5.7.0 + * + * @return string + */ + public function get_name() { + + return $this->name; + } + + + /** + * Gets the control description. + * + * @since 5.7.0 + * + * @return string + */ + public function get_description() { + + return $this->description; + } + + + /** + * Gets the control options. + * + * As $option => $label for display. + * + * @since 5.7.0 + * + * @return array + */ + public function get_options() { + + return $this->options; + } + + + /** Setter methods ************************************************************************************************/ + + + /** + * Sets the setting ID. + * + * @since 5.7.0 + * + * @param string $value setting ID to set + * @throws Framework\SV_WC_Plugin_Exception + */ + public function set_setting_id( $value ) { + + if ( ! is_string( $value ) ) { + throw new Framework\SV_WC_Plugin_Exception( 'Setting ID value must be a string' ); + } + + $this->setting_id = $value; + } + + + /** + * Sets the type. + * + * @since 5.7.0 + * + * @param string $value setting ID to set + * @param string[] $valid_types allowed control types + * @throws Framework\SV_WC_Plugin_Exception + */ + public function set_type( $value, array $valid_types = [] ) { + + if ( ! empty( $valid_types ) && ! in_array( $value, $valid_types, true ) ) { + + throw new Framework\SV_WC_Plugin_Exception( sprintf( + 'Control type must be one of %s', + Framework\SV_WC_Helper::list_array_items( $valid_types, 'or' ) + ) ); + } + + $this->type = $value; + } + + + /** + * Sets the name. + * + * @since 5.7.0 + * + * @param string $value control name to set + * @throws Framework\SV_WC_Plugin_Exception + */ + public function set_name( $value ) { + + if ( ! is_string( $value ) ) { + throw new Framework\SV_WC_Plugin_Exception( 'Control name value must be a string' ); + } + + $this->name = $value; + } + + + /** + * Sets the description. + * + * @since 5.7.0 + * + * @param string $value control description to set + * @throws Framework\SV_WC_Plugin_Exception + */ + public function set_description( $value ) { + + if ( ! is_string( $value ) ) { + throw new Framework\SV_WC_Plugin_Exception( 'Control description value must be a string' ); + } + + $this->description = $value; + } + + + /** + * Sets the options. + * + * @since 5.7.0 + * + * @param array $options options to set + * @param array $valid_options valid option keys to check against + */ + public function set_options( array $options, array $valid_options = [] ) { + + if ( ! empty( $valid_options ) ) { + + foreach ( array_keys( $options ) as $key ) { + + if ( ! in_array( $key, $valid_options, true ) ) { + unset( $options[ $key ] ); + } + } + } + + $this->options = $options; + } + + +} + +endif; diff --git a/vendor/skyverge/wc-plugin-framework/woocommerce/Settings_API/Setting.php b/vendor/skyverge/wc-plugin-framework/woocommerce/Settings_API/Setting.php new file mode 100644 index 0000000..353c91b --- /dev/null +++ b/vendor/skyverge/wc-plugin-framework/woocommerce/Settings_API/Setting.php @@ -0,0 +1,479 @@ +id; + } + + + /** + * Gets the setting type. + * + * @since 5.7.0 + * + * @return string + */ + public function get_type() { + + return $this->type; + } + + + /** + * Gets the setting name. + * + * @since 5.7.0 + * + * @return string + */ + public function get_name() { + + return $this->name; + } + + + /** + * Gets the setting description. + * + * @since 5.7.0 + * + * @return string + */ + public function get_description() { + + return $this->description; + } + + + /** + * Returns whether the setting holds an array of multiple values. + * + * @since 5.7.0 + * + * @return bool + */ + public function is_is_multi() { + + return $this->is_multi; + } + + + /** + * Gets the setting options. + * + * @since 5.7.0 + * + * @return array + */ + public function get_options() { + + return $this->options; + } + + + /** + * Gets the setting default value. + * + * @since 5.7.0 + * + * @return array|bool|float|int|string|null + */ + public function get_default() { + + return $this->default; + } + + + /** + * Gets the setting current value. + * + * @since 5.7.0 + * + * @return array|bool|float|int|string + */ + public function get_value() { + + return $this->value; + } + + + /** + * Gets the setting control. + * + * @since 5.7.0 + * + * @return Control + */ + public function get_control() { + + return $this->control; + } + + + /** Setter Methods ************************************************************************************************/ + + + /** + * Sets the setting ID. + * + * @since 5.7.0 + * + * @param string $id + */ + public function set_id( $id ) { + + $this->id = $id; + } + + + /** + * Sets the setting type. + * + * @since 5.7.0 + * + * @param string $type + */ + public function set_type( $type ) { + + $this->type = $type; + } + + + /** + * Sets the setting name. + * + * @since 5.7.0 + * + * @param string $name + */ + public function set_name( $name ) { + + $this->name = $name; + } + + + /** + * Sets the setting description. + * + * @since 5.7.0 + * + * @param string $description + */ + public function set_description( $description ) { + + $this->description = $description; + } + + + /** + * Sets whether the setting holds an array of multiple values. + * + * @since 5.7.0 + * + * @param bool $is_multi + */ + public function set_is_multi( $is_multi ) { + + $this->is_multi = $is_multi; + } + + + /** + * Sets the setting options. + * + * @since 5.7.0 + * + * @param array $options + */ + public function set_options( $options ) { + + foreach ( $options as $key => $option ) { + + if ( ! $this->validate_value( $option ) ) { + unset( $options[ $key ] ); + } + } + + $this->options = $options; + } + + + /** + * Sets the setting default value. + * + * @since 5.7.0 + * + * @param array|bool|float|int|string|null $value default value to set + */ + public function set_default( $value ) { + + if ( $this->is_is_multi() ) { + + $_value = array_filter( (array) $value, [ $this, 'validate_value' ] ); + + // clear the default if all values were invalid + $value = ! empty( $_value ) ? $_value : null; + + } elseif ( ! $this->validate_value( $value ) ) { + + $value = null; + } + + $this->default = $value; + } + + + /** + * Sets the setting current value. + * + * @since 5.7.0 + * + * @param array|bool|float|int|string $value + */ + public function set_value( $value ) { + + $this->value = $value; + } + + + /** + * Sets the setting control. + * + * @since 5.7.0 + * + * @param Control $control + */ + public function set_control( $control ) { + + $this->control = $control; + } + + + /** + * Sets the setting current value, after validating it against the type and, if set, options. + * + * @since 5.7.0 + * + * @param array|bool|float|int|string $value + * @throws Framework\SV_WC_Plugin_Exception + */ + public function update_value( $value ) { + + if ( ! $this->validate_value( $value ) ) { + + throw new Framework\SV_WC_Plugin_Exception( "Setting value for setting {$this->id} is not valid for the setting type {$this->type}", 400 ); + + } elseif ( ! empty( $this->options ) && ! in_array( $value, $this->options ) ) { + + throw new Framework\SV_WC_Plugin_Exception( sprintf( + 'Setting value for setting %s must be one of %s', + $this->id, + Framework\SV_WC_Helper::list_array_items( $this->options, 'or' ) + ), 400 ); + + } else { + + $this->set_value( $value ); + } + } + + + /** + * Validates the setting value. + * + * @since 5.7.0 + * + * @param array|bool|float|int|string $value + * @return bool + */ + public function validate_value( $value ) { + + $validate_method = "validate_{$this->get_type()}_value"; + + return is_callable( [ $this, $validate_method ] ) ? $this->$validate_method( $value ) : true; + } + + + /** + * Validates a string value. + * + * @since 5.7.0 + * + * @param array|bool|float|int|string $value value to validate + * @return bool + */ + protected function validate_string_value( $value ) { + + return is_string( $value ); + } + + + /** + * Validates a URL value. + * + * @since 5.7.0 + * + * @param array|bool|float|int|string $value value to validate + * @return bool + */ + protected function validate_url_value( $value ) { + + return wc_is_valid_url( $value ); + } + + + /** + * Validates an email value. + * + * @since 5.7.0 + * + * @param mixed $value value to validate + * @return bool + */ + protected function validate_email_value( $value ) { + + return (bool) is_email( $value ); + } + + + /** + * Validates an integer value. + * + * @since 5.7.0 + * + * @param mixed $value value to validate + * @return bool + */ + public function validate_integer_value( $value ) { + + return is_int( $value ); + } + + + /** + * Validates a float value. + * + * @since 5.7.0 + * + * @param mixed $value value to validate + * @return bool + */ + protected function validate_float_value( $value ) { + + return is_int( $value ) || is_float( $value ); + } + + + /** + * Validates a boolean value. + * + * @since 5.7.0 + * + * @param mixed $value value to validate + * @return bool + */ + protected function validate_boolean_value( $value ) { + + return is_bool( $value ); + } + + +} + +endif; diff --git a/vendor/skyverge/wc-plugin-framework/woocommerce/admin/Notes_Helper.php b/vendor/skyverge/wc-plugin-framework/woocommerce/admin/Notes_Helper.php new file mode 100644 index 0000000..ee0b4e8 --- /dev/null +++ b/vendor/skyverge/wc-plugin-framework/woocommerce/admin/Notes_Helper.php @@ -0,0 +1,150 @@ +get_notes_with_name( $name ); + + } catch ( \Exception $exception ) {} + + return $note_ids; + } + + + /** + * Gets all note IDs from the given source. + * + * @since 5.6.1 + * + * @param string $source note source + * @return int[] + */ + public static function get_note_ids_with_source( $source ) { + global $wpdb; + + return $wpdb->get_col( + $wpdb->prepare( + "SELECT note_id FROM {$wpdb->prefix}wc_admin_notes WHERE source = %s ORDER BY note_id ASC", + $source + ) + ); + } + + + /** + * Deletes all notes from the given source. + * + * @since 5.6.1 + * + * @param string $source source name + */ + public static function delete_notes_with_source( $source ) { + + foreach ( self::get_note_ids_with_source( $source ) as $note_id ) { + + if ( $note = WooCommerce_Admin_Notes\WC_Admin_Notes::get_note( $note_id ) ) { + $note->delete(); + } + } + } + + +} + +endif; diff --git a/vendor/skyverge/wc-plugin-framework/woocommerce/admin/abstract-sv-wc-plugin-admin-setup-wizard.php b/vendor/skyverge/wc-plugin-framework/woocommerce/admin/abstract-sv-wc-plugin-admin-setup-wizard.php new file mode 100644 index 0000000..1a3d3ef --- /dev/null +++ b/vendor/skyverge/wc-plugin-framework/woocommerce/admin/abstract-sv-wc-plugin-admin-setup-wizard.php @@ -0,0 +1,1299 @@ +required_capability ) ) { + return; + } + + $this->id = $plugin->get_id(); + $this->plugin = $plugin; + + // register the steps + $this->register_steps(); + + /** + * Filters the registered setup wizard steps. + * + * @since 5.2.2 + * + * @param array $steps registered steps + */ + $this->steps = apply_filters( "wc_{$this->id}_setup_wizard_steps", $this->steps, $this ); + + // only continue if there are registered steps + if ( $this->has_steps() ) { + + // if requesting the wizard + if ( $this->is_setup_page() ) { + + $this->init_setup(); + + // otherwise, add the hooks for customizing the regular admin + } else { + + $this->add_hooks(); + + // mark the wizard as complete if specifically requested + if ( Framework\SV_WC_Helper::get_requested_value( "wc_{$this->id}_setup_wizard_complete" ) ) { + $this->complete_setup(); + } + } + } + } + + + /** + * Registers the setup steps. + * + * Plugins should extend this to register their own steps. + * + * @since 5.2.2 + */ + abstract protected function register_steps(); + + + /** + * Adds the action & filter hooks. + * + * @since 5.2.2 + */ + protected function add_hooks() { + + // add any admin notices + add_action( 'admin_notices', array( $this, 'add_admin_notices' ) ); + + // add a 'Setup' link to the plugin action links if the wizard hasn't been completed + if ( ! $this->is_complete() ) { + add_filter( 'plugin_action_links_' . plugin_basename( $this->get_plugin()->get_plugin_file() ), array( $this, 'add_setup_link' ), 20 ); + } + } + + + /** + * Adds any admin notices. + * + * @since 5.2.2 + */ + public function add_admin_notices() { + + if ( Framework\SV_WC_Helper::is_current_screen( 'plugins' ) || $this->get_plugin()->is_plugin_settings() ) { + + if ( $this->is_complete() && $this->get_documentation_notice_message() ) { + $notice_id = "wc_{$this->id}_docs"; + $message = $this->get_documentation_notice_message(); + } else { + $notice_id = "wc_{$this->id}_setup"; + $message = $this->get_setup_notice_message(); + } + + $this->get_plugin()->get_admin_notice_handler()->add_admin_notice( $message, $notice_id, array( + 'always_show_on_settings' => false, + ) ); + } + } + + + /** + * Gets the new installation documentation notice message. + * + * This prompts users to read the docs and is displayed if the wizard has + * already been completed. + * + * @since 5.2.2 + * + * @return string + */ + protected function get_documentation_notice_message() { + + if ( $this->get_plugin()->get_documentation_url() ) { + + $message = sprintf( + /** translators: Placeholders: %1$s - plugin name, %2$s - tag, %3$s - tag */ + __( 'Thanks for installing %1$s! To get started, take a minute to %2$sread the documentation%3$s :)', 'woocommerce-plugin-framework' ), + esc_html( $this->get_plugin()->get_plugin_name() ), + '', '' + ); + + } else { + + $message = ''; + } + + return $message; + } + + + /** + * Gets the new installation setup notice message. + * + * This prompts users to start the setup wizard and is displayed if the + * wizard has not yet been completed. + * + * @since 5.2.2 + * + * @return string + */ + protected function get_setup_notice_message() { + + return sprintf( + /** translators: Placeholders: %1$s - plugin name, %2$s - tag, %3$s - tag */ + __( 'Thanks for installing %1$s! To get started, take a minute to complete these %2$squick and easy setup steps%3$s :)', 'woocommerce-plugin-framework' ), + esc_html( $this->get_plugin()->get_plugin_name() ), + '', '' + ); + } + + + /** + * Adds a 'Setup' link to the plugin action links if the wizard hasn't been completed. + * + * This will override the plugin's standard "Configure" link with a link to this setup wizard. + * + * @internal + * + * @since 5.2.2 + * + * @param array $action_links plugin action links + * @return array + */ + public function add_setup_link( $action_links ) { + + // remove the standard plugin "Configure" link + unset( $action_links['configure'] ); + + $setup_link = array( + 'setup' => sprintf( '%s', $this->get_setup_url(), esc_html__( 'Setup', 'woocommerce-plugin-framework' ) ), + ); + + return array_merge( $setup_link, $action_links ); + } + + + /** + * Initializes setup. + * + * @since 5.2.2 + */ + protected function init_setup() { + + // get a step ID from $_GET + $current_step = sanitize_key( Framework\SV_WC_Helper::get_requested_value( 'step' ) ); + $current_action = sanitize_key( Framework\SV_WC_Helper::get_requested_value( 'action' ) ); + + if ( ! $current_action ) { + + if ( $this->has_step( $current_step ) ) { + $this->current_step = $current_step; + } elseif ( $first_step_url = $this->get_step_url( key( $this->steps ) ) ) { + wp_safe_redirect( $first_step_url ); + exit; + } else { + wp_safe_redirect( $this->get_dashboard_url() ); + exit; + } + } + + // add the page to WP core + add_action( 'admin_menu', array( $this, 'add_page' ) ); + + // renders the entire setup page markup + add_action( 'admin_init', array( $this, 'render_page' ) ); + } + + + /** + * Adds the page to WordPress core. + * + * While this doesn't output any markup/menu items, it is essential to officially register the page to avoid permissions issues. + * + * @internal + * + * @since 5.2.2 + */ + public function add_page() { + + add_dashboard_page( '', '', $this->required_capability, $this->get_slug(), '' ); + } + + + /** + * Renders the entire setup page markup. + * + * @internal + * + * @since 5.2.2 + */ + public function render_page() { + + // maybe save and move onto the next step + $error_message = Framework\SV_WC_Helper::get_posted_value( 'save_step' ) ? $this->save_step( $this->current_step ) : ''; + + $page_title = sprintf( + /* translators: Placeholders: %s - plugin name */ + __( '%s › Setup', 'woocommerce-plugin-framework' ), + $this->get_plugin()->get_plugin_name() + ); + + // add the step name to the page title + if ( ! empty( $this->steps[ $this->current_step ]['name'] ) ) { + $page_title .= " › {$this->steps[ $this->current_step ]['name']}"; + } + + $this->load_scripts_styles(); + + ob_start(); + + ?> + + > + + + + <?php echo esc_html( $page_title ); ?> + + + + + + render_header(); ?> + render_steps(); ?> + render_content( $error_message ); ?> + render_footer(); ?> + + + id}_setup_wizard_save" ) ) { + throw new Framework\SV_WC_Plugin_Exception( $error_message ); + } + + if ( $this->has_step( $step_id ) ) { + + // if the step has a saving callback defined, save the form fields + if ( is_callable( $this->steps[ $step_id ]['save'] ) ) { + call_user_func( $this->steps[ $step_id ]['save'], $this ); + } + + // move to the next step + wp_safe_redirect( $this->get_next_step_url( $step_id ) ); + exit; + } + + } catch ( Framework\SV_WC_Plugin_Exception $exception ) { + + return $exception->getMessage() ? $exception->getMessage() : $error_message; + } + } + + + /** + * Registers and enqueues the wizard's scripts and styles. + * + * @since 5.2.2 + */ + protected function load_scripts_styles() { + + // block UI + wp_register_script( 'jquery-blockui', WC()->plugin_url() . '/assets/js/jquery-blockui/jquery.blockUI.min.js', array( 'jquery' ), '2.70', true ); + + // enhanced dropdowns + wp_register_script( 'selectWoo', WC()->plugin_url() . '/assets/js/selectWoo/selectWoo.full.min.js', array( 'jquery' ), '1.0.0' ); + wp_register_script( 'wc-enhanced-select', WC()->plugin_url() . '/assets/js/admin/wc-enhanced-select.min.js', array( 'jquery', 'selectWoo' ), $this->get_plugin()->get_version() ); + wp_localize_script( + 'wc-enhanced-select', + 'wc_enhanced_select_params', + array( + 'i18n_no_matches' => _x( 'No matches found', 'enhanced select', 'woocommerce-plugin-framework' ), + 'i18n_ajax_error' => _x( 'Loading failed', 'enhanced select', 'woocommerce-plugin-framework' ), + 'i18n_input_too_short_1' => _x( 'Please enter 1 or more characters', 'enhanced select', 'woocommerce-plugin-framework' ), + 'i18n_input_too_short_n' => _x( 'Please enter %qty% or more characters', 'enhanced select', 'woocommerce-plugin-framework' ), + 'i18n_input_too_long_1' => _x( 'Please delete 1 character', 'enhanced select', 'woocommerce-plugin-framework' ), + 'i18n_input_too_long_n' => _x( 'Please delete %qty% characters', 'enhanced select', 'woocommerce-plugin-framework' ), + 'i18n_selection_too_long_1' => _x( 'You can only select 1 item', 'enhanced select', 'woocommerce-plugin-framework' ), + 'i18n_selection_too_long_n' => _x( 'You can only select %qty% items', 'enhanced select', 'woocommerce-plugin-framework' ), + 'i18n_load_more' => _x( 'Loading more results…', 'enhanced select', 'woocommerce-plugin-framework' ), + 'i18n_searching' => _x( 'Searching…', 'enhanced select', 'woocommerce-plugin-framework' ), + 'ajax_url' => admin_url( 'admin-ajax.php' ), + 'search_products_nonce' => wp_create_nonce( 'search-products' ), + 'search_customers_nonce' => wp_create_nonce( 'search-customers' ), + ) + ); + + // WooCommerce Setup core styles + wp_enqueue_style( 'woocommerce_admin_styles', WC()->plugin_url() . '/assets/css/admin.css', array(), $this->get_plugin()->get_version() ); + wp_enqueue_style( 'wc-setup', WC()->plugin_url() . '/assets/css/wc-setup.css', array( 'dashicons', 'install' ), $this->get_plugin()->get_version() ); + + // framework bundled styles + wp_enqueue_style( 'sv-wc-admin-setup', $this->get_plugin()->get_framework_assets_url() . '/css/admin/sv-wc-plugin-admin-setup-wizard.min.css', array( 'wc-setup' ), $this->get_plugin()->get_version() ); + wp_enqueue_script( 'sv-wc-admin-setup', $this->get_plugin()->get_framework_assets_url() . '/js/admin/sv-wc-plugin-admin-setup-wizard.min.js', array( 'jquery', 'wc-enhanced-select', 'jquery-blockui' ), $this->get_plugin()->get_version() ); + } + + + /** Header Methods ************************************************************************************************/ + + + /** + * Renders the header markup. + * + * @since 5.2.2 + */ + protected function render_header() { + + $title = $this->get_plugin()->get_plugin_name(); + $link_url = $this->get_plugin()->get_sales_page_url(); + $image_url = $this->get_header_image_url(); + + $header_content = $image_url ? '' . esc_attr( $title ) . '' : $title; + + ?> +

    + + + + + +

    + +
      + + steps as $id => $step ) : ?> + + current_step ) : ?> +
    1. + is_step_complete( $id ) ) : ?> +
    2. + +
    3. + + + + +
    4. + +
    + +
    + + is_finished() ) : ?> + + render_finished(); ?> + + complete_setup(); ?> + + + + + is_started() ) : ?> + render_welcome(); ?> + + + + + render_error( $error_message ); ?> + + +
    + render_step( $this->current_step ); ?> + id}_setup_wizard_save", 'nonce' ); ?> +
    + + + +
    + %s

    ', esc_html( $message ) ); + } + } + + + /** + * Renders a default welcome note. + * + * @since 5.2.2 + */ + protected function render_welcome() { + + ?> +

    render_welcome_heading()?>

    +

    render_welcome_text(); ?>

    + get_plugin()->get_plugin_name() + ); + } + + + /** + * Renders the default welcome note text. + * + * @since 5.2.2 + */ + protected function render_welcome_text() { + + esc_html_e( 'This quick setup wizard will help you configure the basic settings and get you started.', 'woocommerce-plugin-framework' ); + } + + + /** + * Renders the finished screen markup. + * + * This is what gets displayed after all of the steps have been completed or skipped. + * + * @since 5.2.2 + */ + protected function render_finished() { + + ?> +

    get_plugin()->get_plugin_name() ) ); ?>

    + render_before_next_steps(); ?> + render_next_steps(); ?> + render_after_next_steps(); ?> + get_next_steps(); + $additional_actions = $this->get_additional_actions(); + + if ( ! empty( $next_steps ) || ! empty( $additional_actions ) ) : + + ?> +
      + + + +
    • +
      + +

      +

      + + +

      + + +
      + +
      +

      + + + + + +

      +
      +
    • + + + + + +
    • +
      +

      +
      +
      + +

      + + $url ) : ?> + + + + + + + +

      +
      +
    • + + + +
    + get_plugin()->get_documentation_url() ) { + + $steps['view-docs'] = array( + 'name' => __( 'View the Docs', 'woocommerce-plugin-framework' ), + 'label' => __( 'See more setup options', 'woocommerce-plugin-framework' ), + 'description' => __( 'Learn more about customizing the plugin', 'woocommerce-plugin-framework' ), + 'url' => $this->get_plugin()->get_documentation_url(), + ); + } + + return $steps; + } + + + /** + * Gets the additional steps. + * + * These are secondary actions. + * + * @since 5.2.2 + * + * @return array + */ + protected function get_additional_actions() { + + $next_steps = $this->get_next_steps(); + $actions = array(); + + if ( $this->get_plugin()->get_settings_url() ) { + $actions[ __( 'Review Your Settings', 'woocommerce-plugin-framework' ) ] = $this->get_plugin()->get_settings_url(); + } + + if ( empty( $next_steps['view-docs'] ) && $this->get_plugin()->get_documentation_url() ) { + $actions[ __( 'View the Docs', 'woocommerce-plugin-framework' ) ] = $this->get_plugin()->get_documentation_url(); + } + + if ( $this->get_plugin()->get_reviews_url() ) { + $actions[ __( 'Leave a Review', 'woocommerce-plugin-framework' ) ] = $this->get_plugin()->get_reviews_url(); + } + + return $actions; + } + + + /** + * Renders a given step's markup. + * + * This will display a title, whatever get's rendered by the step's view + * callback, then the navigation buttons. + * + * @since 5.2.2 + * + * @param string $step_id step ID to render + */ + protected function render_step( $step_id ) { + + call_user_func( $this->steps[ $step_id ]['view'], $this ); + + ?> +

    + + + + steps[ $step_id ]['save'] ) ) : ?> + + + + + + + + +

    + render_toggle_form_field( $key, $args, $value ); + } else { + woocommerce_form_field( $key, $args, $value ); + } + } + + + /** + * Renders the toggle form field. + * + * This requires special markup for the toggle UI. + * + * @since 5.2.2 + * + * @param string $key field key + * @param array $args field args - @see woocommerce_form_field() + * @param string|null $value field value + */ + public function render_toggle_form_field( $key, $args, $value ) { + + $args = wp_parse_args( $args, array( + 'type' => 'text', + 'label' => '', + 'description' => '', + 'required' => false, + 'id' => $key, + 'class' => array(), + 'label_class' => array(), + 'input_class' => array(), + 'custom_attributes' => array(), + 'default' => false, + 'allow_html' => false, + ) ); + + $args['class'][] = 'toggle'; + + if ( $args['required'] ) { + $args['class'][] = 'validate-required'; + } + + if ( null === $value ) { + $value = $args['default']; + } + + $custom_attributes = array(); + $args['custom_attributes'] = array_filter( (array) $args['custom_attributes'], 'strlen' ); + + if ( $args['description'] ) { + $args['custom_attributes']['aria-describedby'] = $args['id'] . '-description'; + } + + if ( ! empty( $args['custom_attributes'] ) && is_array( $args['custom_attributes'] ) ) { + foreach ( $args['custom_attributes'] as $attribute => $attribute_value ) { + $custom_attributes[] = esc_attr( $attribute ) . '="' . esc_attr( $attribute_value ) . '"'; + } + } + + $enabled = $value || $args['default']; + + if ( $enabled ) { + $args['class'][] = 'enabled'; + } + + ?> +
    + +

    + + +

    + +

    + + +
    + + + + /> + +
    + +
    + + is_finished() ) : ?> + + is_started() ) : ?> + + + + + has_step( $id ) ) { + throw new Framework\SV_WC_Plugin_Exception( 'Invalid step ID' ); + } + + // invalid name + if ( ! is_string( $name ) || empty( $name ) ) { + throw new Framework\SV_WC_Plugin_Exception( 'Invalid step name' ); + } + + // invalid view callback + if ( ! is_callable( $view_callback ) ) { + throw new Framework\SV_WC_Plugin_Exception( 'Invalid view callback' ); + } + + // invalid save callback + if ( null !== $save_callback && ! is_callable( $save_callback ) ) { + throw new Framework\SV_WC_Plugin_Exception( 'Invalid save callback' ); + } + + $this->steps[ $id ] = array( + 'name' => $name, + 'view' => $view_callback, + 'save' => $save_callback, + ); + + return true; + + } catch ( Framework\SV_WC_Plugin_Exception $exception ) { + + wc_doing_it_wrong( __METHOD__, $exception->getMessage(), '5.2.2' ); + + return false; + } + } + + + /** + * Marks the setup as complete. + * + * @since 5.2.2 + * + * @return bool + */ + public function complete_setup() { + + return update_option( "wc_{$this->id}_setup_wizard_complete", 'yes' ); + } + + + /** Conditional Methods *******************************************************************************************/ + + + /** + * Determines if the current page is the setup wizard page. + * + * @since 5.2.2 + * + * @return bool + */ + public function is_setup_page() { + + return is_admin() && $this->get_slug() === Framework\SV_WC_Helper::get_requested_value( 'page' ); + } + + + /** + * Determines if a step is the current one displayed. + * + * @since 5.2.2 + * + * @param string $step_id step ID + * @return bool + */ + public function is_current_step( $step_id ) { + + return $this->current_step === $step_id; + } + + + /** + * Determines if setup has started. + * + * @since 5.2.2 + * + * @return bool + */ + public function is_started() { + + $steps = array_keys( $this->steps ); + + return $this->current_step && $this->current_step === reset( $steps ); + } + + + /** + * Determines if setup has completed all of the steps. + * + * @since 5.2.2 + * + * @return bool + */ + public function is_finished() { + + return self::ACTION_FINISH === Framework\SV_WC_Helper::get_requested_value( 'action' ); + } + + + /** + * Determines if the setup wizard has been completed. + * + * This will be true if any user has been redirected back to the regular + * WordPress dashboard, either manually or after finishing the steps. + * + * @since 5.2.2 + * + * @return bool + */ + public function is_complete() { + + return 'yes' === get_option( "wc_{$this->id}_setup_wizard_complete", 'no' ); + } + + + /** + * Determines if the given step has been completed. + * + * @since 5.2.2 + * + * @param string $step_id step ID to check + * @return bool + */ + public function is_step_complete( $step_id ) { + + return array_search( $this->current_step, array_keys( $this->steps ), true ) > array_search( $step_id, array_keys( $this->steps ), true ) || $this->is_finished(); + } + + + /** + * Determines if the wizard has steps to display. + * + * @since 5.2.2 + * + * @return bool + */ + public function has_steps() { + + return is_array( $this->steps ) && ! empty( $this->steps ); + } + + + /** + * Determines if this setup handler has a given step. + * + * @since 5.2.2 + * + * @param string $step_id step ID to check + * @return bool + */ + public function has_step( $step_id ) { + + return ! empty( $this->steps[ $step_id ] ); + } + + + /** Getter Methods ************************************************************************************************/ + + + /** + * Gets a given step's title. + * + * @since 5.2.2 + * + * @param string $step_id step ID (optional: will assume the current step if unspecified) + * @return string + */ + public function get_step_title( $step_id = '' ) { + + $step_title = ''; + + if ( ! $step_id ) { + $step_id = $this->current_step; + } + + if ( isset( $this->steps[ $step_id ]['name'] ) ) { + $step_title = $this->steps[ $step_id ]['name']; + } + + return $step_title; + } + + + /** + * Gets the Setup Wizard URL. + * + * @since 5.2.2 + * + * @return string + */ + public function get_setup_url() { + + return add_query_arg( 'page', $this->get_slug(), admin_url( 'index.php' ) ); + } + + + /** + * Gets the URL for the next step based on a current step. + * + * @since 5.2.2 + * + * @param string $step_id step ID to base "next" off of - defaults to this class's internal pointer + * @return string + */ + public function get_next_step_url( $step_id = '' ) { + + if ( ! $step_id ) { + $step_id = $this->current_step; + } + + $steps = array_keys( $this->steps ); + + // if on the last step, next is the final finish step + if ( end( $steps ) === $step_id ) { + + $url = $this->get_finish_url(); + + } else { + + $step_index = array_search( $step_id, $steps, true ); + + // if the current step is found, use the next in the array. otherwise, the first + $step = false !== $step_index ? $steps[ $step_index + 1 ] : reset( $steps ); + + $url = add_query_arg( 'step', $step ); + } + + return $url; + } + + + /** + * Gets a given step's URL. + * + * @since 5.2.2 + * + * @param string $step_id step ID + * @return string|false + */ + public function get_step_url( $step_id ) { + + $url = false; + + if ( $this->has_step( $step_id ) ) { + $url = add_query_arg( 'step', $step_id, remove_query_arg( 'action' ) ); + } + + return $url; + } + + + /** + * Gets the "finish" action URL. + * + * @since 5.2.2 + * + * @return string + */ + protected function get_finish_url() { + + return add_query_arg( 'action', self::ACTION_FINISH, remove_query_arg( 'step' ) ); + } + + + /** + * Gets the return URL. + * + * Can be used to return the user to the dashboard. The plugin's settings URL + * will be used if it exists, otherwise the general dashboard URL. + * + * @since 5.2.2 + * + * @return string + */ + protected function get_dashboard_url() { + + $settings_url = $this->get_plugin()->get_settings_url(); + $dashboard_url = ! empty( $settings_url ) ? $settings_url : admin_url(); + + return add_query_arg( "wc_{$this->id}_setup_wizard_complete", true, $dashboard_url ); + } + + + /** + * Gets the setup setup handler's slug. + * + * @since 5.2.2 + * + * @return string + */ + protected function get_slug() { + + return 'wc-' . $this->get_plugin()->get_id_dasherized() . '-setup'; + } + + + /** + * Gets the plugin instance. + * + * @since 5.2.2 + * + * @return Framework\SV_WC_Plugin|Framework\SV_WC_Payment_Gateway_Plugin + */ + protected function get_plugin() { + + return $this->plugin; + } + + +} + + +endif; diff --git a/vendor/skyverge/wc-plugin-framework/woocommerce/api/Abstract_Cacheable_API_Base.php b/vendor/skyverge/wc-plugin-framework/woocommerce/api/Abstract_Cacheable_API_Base.php new file mode 100644 index 0000000..22ee924 --- /dev/null +++ b/vendor/skyverge/wc-plugin-framework/woocommerce/api/Abstract_Cacheable_API_Base.php @@ -0,0 +1,273 @@ +is_request_cacheable() && ! $this->get_request()->should_refresh() && $response = $this->load_response_from_cache() ) { + + $this->response_loaded_from_cache = true; + + return $response; + } + + return parent::do_remote_request( $request_uri, $request_args ); + } + + + /** + * Handle and parse the response + * + * @since 5.10.10 + * + * @param array|\WP_Error $response response data + * @throws SV_WC_API_Exception network issues, timeouts, API errors, etc + * @return SV_WC_API_Request|object request class instance that implements SV_WC_API_Request + */ + protected function handle_response( $response ) { + + parent::handle_response( $response ); + + // cache the response + if ( ! $this->is_response_loaded_from_cache() && $this->is_request_cacheable() ) { + + $this->save_response_to_cache( $response ); + } + + return $this->response; // this param is set by the parent method + } + + + /** + * Resets the API response members to their default values. + * + * @since 5.10.10 + */ + protected function reset_response() { + + $this->response_loaded_from_cache = false; + + parent::reset_response(); + } + + + /** + * Gets the request transient key for the current plugin and request data. + * + * Request transients can be disabled by using the filter below. + * + * @since 5.10.10 + * + * @return string transient key + */ + protected function get_request_transient_key() : string { + + // ex: wc__ + return sprintf( 'wc_%s_api_response_%s', $this->get_plugin()->get_id(), md5( implode( '_', [ + $this->get_request_uri(), + $this->get_request_body(), + $this->get_request_cache_lifetime(), + ] ) ) ); + } + + + /** + * Checks whether the current request is cacheable. + * + * @since 5.10.10 + * + * @return bool + */ + protected function is_request_cacheable() : bool { + + if ( ! in_array( Cacheable_Request_Trait::class, class_uses( $this->get_request() ), true ) ) { + return false; + } + + /** + * Filters whether the API request is cacheable. + * + * Allows actors to disable API request caching when a request is normally cacheable. This may be useful + * primarily for debugging situations. + * + * Note: this filter is only applied if the request is originally cacheable, in order to prevent issues when + * a non-cacheable request is accidentally flagged as cacheable. + * + * @since 5.10.10 + * + * @param bool $is_cacheable whether the request is cacheable + * @param SV_WC_API_Request $request the request instance + */ + return (bool) apply_filters( 'wc_plugin_' . $this->get_plugin()->get_id() . '_api_request_is_cacheable', true, $this->get_request() ); + } + + + /** + * Gets the cache lifetime for the current request. + * + * @since 5.10.10 + * + * @return int + */ + protected function get_request_cache_lifetime() : int { + + /** + * Filters API request cache lifetime. + * + * Allows actors to override cache lifetime for cacheable API requests. This may be useful for debugging + * API requests by temporarily setting short cache timeouts. + * + * @since 5.10.10 + * + * @param int $lifetime cache lifetime in seconds, 0 = unlimited + * @param SV_WC_API_Request $request the request instance + */ + return (int) apply_filters( 'wc_plugin_' . $this->get_plugin()->get_id() . '_api_request_cache_lifetime' , $this->get_request()->get_cache_lifetime(), $this->get_request() ); + } + + + + /** + * Determine whether the response was loaded from cache or not. + * + * @since 5.10.10 + * + * @return bool + */ + protected function is_response_loaded_from_cache() : bool { + + return $this->response_loaded_from_cache; + } + + + /** + * Loads the response for the current request from the cache, if available. + * + * @since 5.10.10 + * + * @return array|null + */ + protected function load_response_from_cache() { + + return get_transient( $this->get_request_transient_key() ); + } + + + /** + * Saves the response to cache. + * + * @since 5.10.10 + * + * @param array $response + */ + protected function save_response_to_cache( array $response ) { + + set_transient( $this->get_request_transient_key(), $response, $this->get_request_cache_lifetime() ); + } + + + /** + * Gets the response data for broadcasting the request. + * + * Adds a flag to the response data indicating whether the response was loaded from cache. + * + * @since 5.10.10 + * + * @return array + */ + protected function get_request_data_for_broadcast() : array { + + $request_data = parent::get_request_data_for_broadcast(); + + if ( $this->is_request_cacheable() ) { + $request_data = [ + 'force_refresh' => $this->get_request()->should_refresh(), + 'should_cache' => $this->get_request()->should_cache(), + ] + $request_data; + } + + return $request_data; + } + + + /** + * Gets the response data for broadcasting the request. + * + * Adds a flag to the response data indicating whether the response was loaded from cache. + * + * @since 5.10.10 + * + * @return array + */ + protected function get_response_data_for_broadcast() : array { + + $response_data = parent::get_response_data_for_broadcast(); + + if ( $this->is_request_cacheable() ) { + $response_data = [ 'from_cache' => $this->is_response_loaded_from_cache() ] + $response_data; + } + + return $response_data; + } + +} + + +endif; diff --git a/vendor/skyverge/wc-plugin-framework/woocommerce/api/abstract-sv-wc-api-json-request.php b/vendor/skyverge/wc-plugin-framework/woocommerce/api/abstract-sv-wc-api-json-request.php new file mode 100644 index 0000000..4b440ca --- /dev/null +++ b/vendor/skyverge/wc-plugin-framework/woocommerce/api/abstract-sv-wc-api-json-request.php @@ -0,0 +1,135 @@ +method; + } + + + /** + * Get the request path. + * + * @since 4.3.0 + * @see SV_WC_API_Request::get_path() + * @return string + */ + public function get_path() { + return $this->path; + } + + + /** + * Get the request parameters. + * + * @since 4.3.0 + * @see SV_WC_API_Request::get_params() + * @return array + */ + public function get_params() { + return $this->params; + } + + + /** + * Get the request data. + * + * @since 4.5.0 + * @return array + */ + public function get_data() { + return $this->data; + } + + + /** API Helper Methods ******************************************************/ + + + /** + * Get the string representation of this request. + * + * @since 4.3.0 + * @see SV_WC_API_Request::to_string() + * @return string + */ + public function to_string() { + + $data = $this->get_data(); + + return ! empty( $data ) ? json_encode( $data ) : ''; + } + + + /** + * Get the string representation of this request with any and all sensitive elements masked + * or removed. + * + * @since 4.3.0 + * @see SV_WC_API_Request::to_string_safe() + * @return string + */ + public function to_string_safe() { + + return $this->to_string(); + } + + +} + + +endif; diff --git a/vendor/skyverge/wc-plugin-framework/woocommerce/api/abstract-sv-wc-api-json-response.php b/vendor/skyverge/wc-plugin-framework/woocommerce/api/abstract-sv-wc-api-json-response.php new file mode 100644 index 0000000..105e8f2 --- /dev/null +++ b/vendor/skyverge/wc-plugin-framework/woocommerce/api/abstract-sv-wc-api-json-response.php @@ -0,0 +1,105 @@ +raw_response_json = $raw_response_json; + + $this->response_data = json_decode( $raw_response_json ); + } + + + /** + * Magic accessor for response data attributes + * + * @since 4.3.0 + * @param string $name The attribute name to get. + * @return mixed The attribute value + */ + public function __get( $name ) { + + // accessing the response_data object indirectly via attribute (useful when it's a class) + return isset( $this->response_data->$name ) ? $this->response_data->$name : null; + } + + + /** + * Get the string representation of this response. + * + * @since 4.3.0 + * @see SV_WC_API_Response::to_string() + * @return string + */ + public function to_string() { + + return $this->raw_response_json; + } + + + /** + * Get the string representation of this response with any and all sensitive elements masked + * or removed. + * + * @since 4.3.0 + * @see SV_WC_API_Response::to_string_safe() + * @return string + */ + public function to_string_safe() { + + return $this->to_string(); + } + + +} + + +endif; diff --git a/vendor/skyverge/wc-plugin-framework/woocommerce/api/abstract-sv-wc-api-xml-request.php b/vendor/skyverge/wc-plugin-framework/woocommerce/api/abstract-sv-wc-api-xml-request.php new file mode 100644 index 0000000..a940091 --- /dev/null +++ b/vendor/skyverge/wc-plugin-framework/woocommerce/api/abstract-sv-wc-api-xml-request.php @@ -0,0 +1,200 @@ +method; + } + + + /** + * Get the path for this request. + * + * @since 4.3.0 + * @see SV_WC_API_Request::get_path() + * @return string + */ + public function get_path() { + return $this->path; + } + + + /** + * Get the request parameters. + * + * @since 4.5.0 + * @return array + */ + public function get_params() { + return $this->params; + } + + + /** + * Convert the request data into XML. + * + * @since 4.3.0 + * @return string + */ + protected function to_xml() { + + if ( ! empty( $this->request_xml ) ) { + return $this->request_xml; + } + + $this->xml = new \XMLWriter(); + + // Create XML document in memory + $this->xml->openMemory(); + + // Set XML version & encoding + $this->xml->startDocument( '1.0', 'UTF-8' ); + + $request_data = $this->get_data(); + + SV_WC_Helper::array_to_xml( $this->xml, $this->get_root_element(), $request_data[ $this->get_root_element() ] ); + + $this->xml->endDocument(); + + return $this->request_xml = $this->xml->outputMemory(); + } + + + /** + * Gets the request data to be converted to XML. + * + * @since 5.0.0 + * @return array + */ + public function get_data() { + + return $this->request_data; + } + + + /** + * Get the string representation of this request + * + * @since 4.3.0 + * @see SV_WC_API_Request::to_string() + * @return string + */ + public function to_string() { + + return $this->to_xml(); + } + + + /** + * Get the string representation of this request with any and all sensitive elements masked + * or removed. + * + * @since 4.3.0 + * @see SV_WC_API_Request::to_string_safe() + * @return string + */ + public function to_string_safe() { + + return $this->prettify_xml( $this->to_string() ); + } + + + /** + * Helper method for making XML pretty, suitable for logging or rendering + * + * @since 4.3.0 + * @param string $xml_string ugly XML string + * @return string + */ + public function prettify_xml( $xml_string ) { + + $dom = new \DOMDocument(); + + // suppress errors for invalid XML syntax issues + if ( @$dom->loadXML( $xml_string ) ) { + $dom->formatOutput = true; + $xml_string = $dom->saveXML(); + } + + return $xml_string; + } + + + /** + * Concrete classes must implement this method to return the root element + * for the XML document + * + * @since 4.3.0 + * @return string + */ + abstract protected function get_root_element(); + + +} + + +endif; diff --git a/vendor/skyverge/wc-plugin-framework/woocommerce/api/abstract-sv-wc-api-xml-response.php b/vendor/skyverge/wc-plugin-framework/woocommerce/api/abstract-sv-wc-api-xml-response.php new file mode 100644 index 0000000..ca5e05a --- /dev/null +++ b/vendor/skyverge/wc-plugin-framework/woocommerce/api/abstract-sv-wc-api-xml-response.php @@ -0,0 +1,138 @@ +raw_response_xml = $raw_response_xml; + + // LIBXML_NOCDATA ensures that any XML fields wrapped in [CDATA] will be included as text nodes + $this->response_xml = new \SimpleXMLElement( $raw_response_xml, LIBXML_NOCDATA ); + + /** + * workaround to convert the horrible data structure that SimpleXMLElement returns + * and provide a nice array of stdClass objects. Note there is some fidelity lost + * in the conversion (such as attributes), but implementing classes can access + * the response_xml member directly to retrieve them as needed. + */ + $this->response_data = json_decode( json_encode( $this->response_xml ) ); + } + + + /** + * Magic method for getting XML element data. Note the response data has + * already been casted into simple data types (string,int,array) and does not + * require further casting in order to use. + * + * @since 4.3.0 + * @param string $key + * @return mixed + */ + public function __get( $key ) { + + if ( ! isset( $this->response_data->$key ) ) { + return null; + } + + // array cast & empty check prevents fataling on empty stdClass objects + $array = (array) $this->response_data->$key; + + if ( empty( $array ) ) { + return null; + } + + return $this->response_data->$key; + } + + + /** + * Get the string representation of this response. + * + * @since 4.3.0 + * @return string + */ + public function to_string() { + + $response = $this->raw_response_xml; + + $dom = new \DOMDocument(); + + // suppress errors for invalid XML syntax issues + if ( @$dom->loadXML( $response ) ) { + $dom->formatOutput = true; + $response = $dom->saveXML(); + } + + return $response; + } + + + /** + * Get the string representation of this response with any and all sensitive elements masked + * or removed. + * + * @since 4.3.0 + * @see SV_WC_API_Response::to_string_safe() + * @return string + */ + public function to_string_safe() { + + return $this->to_string(); + } + + +} + + +endif; diff --git a/vendor/skyverge/wc-plugin-framework/woocommerce/api/class-sv-wc-api-base.php b/vendor/skyverge/wc-plugin-framework/woocommerce/api/class-sv-wc-api-base.php new file mode 100644 index 0000000..d8ed557 --- /dev/null +++ b/vendor/skyverge/wc-plugin-framework/woocommerce/api/class-sv-wc-api-base.php @@ -0,0 +1,867 @@ +reset_response(); + + // save the request object + $this->request = $request; + + $start_time = microtime( true ); + + // if this API requires TLS v1.2, force it + if ( $this->get_plugin()->require_tls_1_2() ) { + add_action( 'http_api_curl', array( $this, 'set_tls_1_2_request' ), 10, 3 ); + } + + // perform the request + $response = $this->do_remote_request( $this->get_request_uri(), $this->get_request_args() ); + + // calculate request duration + $this->request_duration = round( microtime( true ) - $start_time, 5 ); + + try { + + // parse & validate response + $response = $this->handle_response( $response ); + + } catch ( SV_WC_Plugin_Exception $e ) { + + // alert other actors that a request has been made + $this->broadcast_request(); + + throw $e; + } + + return $response; + } + + + /** + * Simple wrapper for wp_remote_request() so child classes can override this + * and provide their own transport mechanism if needed, e.g. a custom + * cURL implementation + * + * @since 2.2.0 + * + * @param string $request_uri + * @param string $request_args + * @return array|\WP_Error + */ + protected function do_remote_request( $request_uri, $request_args ) { + + return wp_safe_remote_request( $request_uri, $request_args ); + } + + + /** + * Handle and parse the response + * + * @since 2.2.0 + * @param array|\WP_Error $response response data + * @throws SV_WC_API_Exception network issues, timeouts, API errors, etc + * @return SV_WC_API_Request|object request class instance that implements SV_WC_API_Request + */ + protected function handle_response( $response ) { + + // check for WP HTTP API specific errors (network timeout, etc) + if ( is_wp_error( $response ) ) { + throw new SV_WC_API_Exception( $response->get_error_message(), (int) $response->get_error_code() ); + } + + // set response data + $this->response_code = wp_remote_retrieve_response_code( $response ); + $this->response_message = wp_remote_retrieve_response_message( $response ); + $this->raw_response_body = wp_remote_retrieve_body( $response ); + + $response_headers = wp_remote_retrieve_headers( $response ); + + // WP 4.6+ returns an object + if ( is_object( $response_headers ) ) { + $response_headers = $response_headers->getAll(); + } + + $this->response_headers = $response_headers; + + // allow child classes to validate response prior to parsing -- this is useful + // for checking HTTP status codes, etc. + $this->do_pre_parse_response_validation(); + + // parse the response body and tie it to the request + $this->response = $this->get_parsed_response( $this->raw_response_body ); + + // allow child classes to validate response after parsing -- this is useful + // for checking error codes/messages included in a parsed response + $this->do_post_parse_response_validation(); + + // fire do_action() so other actors can act on request/response data, + // primarily used for logging + $this->broadcast_request(); + + return $this->response; + } + + + /** + * Allow child classes to validate a response prior to instantiating the + * response object. Useful for checking response codes or messages, e.g. + * throw an exception if the response code is not 200. + * + * A child class implementing this method should simply return true if the response + * processing should continue, or throw a \SV_WC_API_Exception with a + * relevant error message & code to stop processing. + * + * Note: Child classes *must* sanitize the raw response body before throwing + * an exception, as it will be included in the broadcast_request() method + * which is typically used to log requests. + * + * @since 2.2.0 + */ + protected function do_pre_parse_response_validation() { + // stub method + } + + + /** + * Allow child classes to validate a response after it has been parsed + * and instantiated. This is useful for check error codes or messages that + * exist in the parsed response. + * + * A child class implementing this method should simply return true if the response + * processing should continue, or throw a \SV_WC_API_Exception with a + * relevant error message & code to stop processing. + * + * Note: Response body sanitization is handled automatically + * + * @since 2.2.0 + */ + protected function do_post_parse_response_validation() { + // stub method + } + + + /** + * Return the parsed response object for the request + * + * @since 2.2.0 + * @param string $raw_response_body + * @return object|SV_WC_API_Request response class instance which implements SV_WC_API_Request + */ + protected function get_parsed_response( $raw_response_body ) { + + $handler_class = $this->get_response_handler(); + + return new $handler_class( $raw_response_body ); + } + + + /** + * Alert other actors that a request has been performed. This is primarily used + * for request logging. + * + * @since 2.2.0 + */ + protected function broadcast_request() { + + $request_data = $this->get_request_data_for_broadcast(); + $response_data = $this->get_response_data_for_broadcast(); + + /** + * API Base Request Performed Action. + * + * Fired when an API request is performed via this base class. Plugins can + * hook into this to log request/response data. + * + * Note: request and response data arrays may contain additional, undocumented keys provided by the implementing plugin. + * + * @since 2.2.0 + * @param array $request_data { + * @type string $method request method, e.g. POST + * @type string $uri request URI + * @type string $user-agent + * @type string $headers request headers + * @type string $body request body + * @type string $duration in seconds + * } + * @param array $response data { + * @type string $code response HTTP code + * @type string $message response message + * @type string $headers response HTTP headers + * @type string $body response body + * } + * @param SV_WC_API_Base $this instance + */ + do_action( 'wc_' . $this->get_api_id() . '_api_request_performed', $request_data, $response_data, $this ); + } + + + /** + * Reset the API response members to their + * + * @since 1.0.0 + */ + protected function reset_response() { + + $this->response_code = null; + $this->response_message = null; + $this->response_headers = null; + $this->raw_response_body = null; + $this->response = null; + $this->request_duration = null; + } + + + /** Request Getters *******************************************************/ + + + /** + * Get the request URI + * + * @since 2.2.0 + * @return string + */ + protected function get_request_uri() { + + $uri = $this->request_uri . $this->get_request_path(); + + // append any query params to the URL when necessary + if ( $query = $this->get_request_query() ) { + + $url_parts = parse_url( $uri ); + + // if the URL already has some query params, add to them + if ( ! empty( $url_parts['query'] ) ) { + $query = '&' . $query; + } else { + $query = '?' . $query; + } + + $uri = untrailingslashit( $uri ) . $query; + } + + /** + * Request URI Filter. + * + * Allow actors to filter the request URI. Note that child classes can override + * this method, which means this filter may be invoked prior to the overridden + * method. + * + * @since 4.1.0 + * + * @param string $uri current request URI + * @param SV_WC_API_Base class instance + */ + return apply_filters( 'wc_' . $this->get_api_id() . '_api_request_uri', $uri, $this ); + } + + + /** + * Gets the request path. + * + * @since 4.5.0 + * @return string + */ + protected function get_request_path() { + + return ( $this->get_request() ) ? $this->get_request()->get_path() : ''; + } + + + /** + * Gets the request URL query. + * + * @since 4.5.0 + * + * @return string + */ + protected function get_request_query() { + + $query = ''; + $request = $this->get_request(); + + if ( $request && in_array( strtoupper( $this->get_request_method() ), array( 'GET', 'HEAD' ), true ) ) { + + $params = $request->get_params(); + + if ( ! empty( $params ) ) { + $query = http_build_query( $params, '', '&' ); + } + } + + return $query; + } + + + /** + * Get the request arguments in the format required by wp_remote_request() + * + * @since 2.2.0 + * + * @return array + */ + protected function get_request_args() { + + $args = array( + 'method' => $this->get_request_method(), + 'timeout' => MINUTE_IN_SECONDS, + 'redirection' => 0, + 'httpversion' => $this->get_request_http_version(), + 'sslverify' => true, + 'blocking' => true, + 'user-agent' => $this->get_request_user_agent(), + 'headers' => $this->get_request_headers(), + 'body' => $this->get_request_body(), + 'cookies' => array(), + ); + + /** + * Request arguments. + * + * Allow other actors to filter the request arguments. Note that + * child classes can override this method, which means this filter may + * not be invoked, or may be invoked prior to the overridden method + * + * @since 2.2.0 + * @param array $args request arguments + * @param SV_WC_API_Base class instance + */ + return apply_filters( 'wc_' . $this->get_api_id() . '_http_request_args', $args, $this ); + } + + + /** + * Get the request method, POST by default + * + * @since 2.2.0 + * @return string + */ + protected function get_request_method() { + // if the request object specifies the method to use, use that, otherwise use the API default + return $this->get_request() && $this->get_request()->get_method() ? $this->get_request()->get_method() : $this->request_method; + } + + + /** + * Gets the request body. + * + * @since 4.5.0 + * @return string + */ + protected function get_request_body() { + + // GET & HEAD requests don't support a body + if ( in_array( strtoupper( $this->get_request_method() ), array( 'GET', 'HEAD' ) ) ) { + return ''; + } + + return ( $this->get_request() && $this->get_request()->to_string() ) ? $this->get_request()->to_string() : ''; + } + + + /** + * Gets the sanitized request body, for logging. + * + * @since 4.5.0 + * @return string + */ + protected function get_sanitized_request_body() { + + // GET & HEAD requests don't support a body + if ( in_array( strtoupper( $this->get_request_method() ), array( 'GET', 'HEAD' ) ) ) { + return ''; + } + + return ( $this->get_request() && $this->get_request()->to_string_safe() ) ? $this->get_request()->to_string_safe() : ''; + } + + + /** + * Get the request HTTP version, 1.1 by default + * + * @since 2.2.0 + * @return string + */ + protected function get_request_http_version() { + + return $this->request_http_version; + } + + + /** + * Get the request headers + * + * @since 2.2.0 + * @return array + */ + protected function get_request_headers() { + return $this->request_headers; + } + + + /** + * Get sanitized request headers suitable for logging, stripped of any + * confidential information + * + * The `Authorization` header is sanitized automatically. + * + * Child classes that implement any custom authorization headers should + * override this method to perform sanitization. + * + * @since 2.2.0 + * @return array + */ + protected function get_sanitized_request_headers() { + + $headers = $this->get_request_headers(); + + if ( ! empty( $headers['Authorization'] ) ) { + $headers['Authorization'] = str_repeat( '*', strlen( $headers['Authorization'] ) ); + } + + return $headers; + } + + + /** + * Get the request user agent, defaults to: + * + * Dasherized-Plugin-Name/Plugin-Version (WooCommerce/WC-Version; WordPress/WP-Version) + * + * @since 2.2.0 + * @return string + */ + protected function get_request_user_agent() { + + return sprintf( '%s/%s (WooCommerce/%s; WordPress/%s)', str_replace( ' ', '-', $this->get_plugin()->get_plugin_name() ), $this->get_plugin()->get_version(), WC_VERSION, $GLOBALS['wp_version'] ); + } + + + /** + * Get the request duration in seconds, rounded to the 5th decimal place + * + * @since 2.2.0 + * @return string + */ + protected function get_request_duration() { + return $this->request_duration; + } + + + /** + * Gets the request data for broadcasting the request. + * + * Overriding this method allows child classes to customize the request data when broadcasting the request. + * + * @since 5.10.10 + * + * @return array + */ + protected function get_request_data_for_broadcast() : array { + + return [ + 'method' => $this->get_request_method(), + 'uri' => $this->get_request_uri(), + 'user-agent' => $this->get_request_user_agent(), + 'headers' => $this->get_sanitized_request_headers(), + 'body' => $this->get_sanitized_request_body(), + 'duration' => $this->get_request_duration() . 's', // seconds + ]; + } + + + /** Response Getters ******************************************************/ + + + /** + * Get the response handler class name + * + * @since 2.2.0 + * @return string + */ + protected function get_response_handler() { + return $this->response_handler; + } + + + /** + * Get the response code + * + * @since 2.2.0 + * @return string + */ + protected function get_response_code() { + return $this->response_code; + } + + + /** + * Get the response message + * + * @since 2.2.0 + * @return string + */ + protected function get_response_message() { + return $this->response_message; + } + + + /** + * Get the response headers + * + * @since 2.2.0 + * @return array + */ + protected function get_response_headers() { + return $this->response_headers; + } + + + /** + * Get the raw response body, prior to any parsing or sanitization + * + * @since 2.2.0 + * @return string + */ + protected function get_raw_response_body() { + return $this->raw_response_body; + } + + + /** + * Get the sanitized response body, provided by the response class + * to_string_safe() method + * + * @since 2.2.0 + * @return string|null + */ + protected function get_sanitized_response_body() { + return is_callable( array( $this->get_response(), 'to_string_safe' ) ) ? $this->get_response()->to_string_safe() : null; + } + + + /** + * Gets the response data for broadcasting the request. + * + * Overriding this method allows child classes to customize the response data when broadcasting the request. + * + * @since 5.10.10 + * + * @return array + * @return array + */ + protected function get_response_data_for_broadcast() : array + { + return [ + 'code' => $this->get_response_code(), + 'message' => $this->get_response_message(), + 'headers' => $this->get_response_headers(), + 'body' => $this->get_sanitized_response_body() ?: $this->get_raw_response_body(), + ]; + } + + + /** Misc Getters ******************************************************/ + + + /** + * Returns the most recent request object. + * + * @since 2.2.0 + * + * @return SV_WC_API_Request|object the most recent request object + */ + public function get_request() { + + return $this->request; + } + + + /** + * Returns the most recent response object. + * + * @since 2.2.0 + * + * @return SV_WC_API_Response|object the most recent response object + */ + public function get_response() { + + return $this->response; + } + + + /** + * Get the ID for the API, used primarily to namespace the action name + * for broadcasting requests + * + * @since 2.2.0 + * @return string + */ + protected function get_api_id() { + + return $this->get_plugin()->get_id(); + } + + + /** + * Return a new request object + * + * Child classes must implement this to return an object that implements + * \SV_WC_API_Request which should be used in the child class API methods + * to build the request. The returned SV_WC_API_Request should be passed + * to self::perform_request() by your concrete API methods + * + * @since 2.2.0 + * + * @param array $args optional request arguments + * @return SV_WC_API_Request|object + */ + abstract protected function get_new_request( $args = array() ); + + + /** + * Return the plugin class instance associated with this API + * + * Child classes must implement this to return their plugin class instance + * + * This is used for defining the plugin ID used in filter names, as well + * as the plugin name used for the default user agent. + * + * @since 2.2.0 + * + * @return SV_WC_Plugin + */ + abstract protected function get_plugin(); + + + /** Setters ***************************************************************/ + + + /** + * Set a request header + * + * @since 2.2.0 + * @param string $name header name + * @param string $value header value + * @return string + */ + protected function set_request_header( $name, $value ) { + + $this->request_headers[ $name ] = $value; + } + + + /** + * Set multiple request headers at once + * + * @since 4.3.0 + * @param array $headers + */ + protected function set_request_headers( array $headers ) { + + foreach ( $headers as $name => $value ) { + + $this->request_headers[ $name ] = $value; + } + } + + + /** + * Set HTTP basic auth for the request + * + * Since 2.2.0 + * @param string $username + * @param string $password + */ + protected function set_http_basic_auth( $username, $password ) { + + $this->request_headers['Authorization'] = sprintf( 'Basic %s', base64_encode( "{$username}:{$password}" ) ); + } + + + /** + * Set the Content-Type request header + * + * @since 2.2.0 + * @param string $content_type + */ + protected function set_request_content_type_header( $content_type ) { + $this->request_headers['content-type'] = $content_type; + } + + + /** + * Set the Accept request header + * + * @since 2.2.0 + * @param string $type the request accept type + */ + protected function set_request_accept_header( $type ) { + $this->request_headers['accept'] = $type; + } + + + /** + * Set the response handler class name. This class will be instantiated + * to parse the response for the request. + * + * Note the class should implement SV_WC_API + * + * @since 2.2.0 + * + * @param string $handler handle class name + */ + protected function set_response_handler( $handler ) { + + $this->response_handler = $handler; + } + + + /** + * Maybe force TLS v1.2 requests. + * + * @since 4.4.0 + * + * @param resource $handle the cURL handle returned by curl_init() (passed by reference) + * @param array $r the HTTP request arguments + * @param $url string the request URL + */ + public function set_tls_1_2_request( $handle, $r, $url ) { + + if ( ! SV_WC_Helper::str_starts_with( $url, 'https://' ) ) { + return; + } + + curl_setopt( $handle, CURLOPT_SSLVERSION, 6 ); + } + + + /** + * Determines if TLS v1.2 is required for API requests. + * + * @since 4.4.0 + * @deprecated 5.5.2 + * + * @return bool + */ + public function require_tls_1_2() { + + wc_deprecated_function( __METHOD__, '5.5.2', 'SV_WC_Plugin::require_tls_1_2()' ); + + return $this->get_plugin()->require_tls_1_2(); + } + + + /** + * Determines if TLS 1.2 is available. + * + * @since 4.6.5 + * + * @return bool + */ + public function is_tls_1_2_available() { + + /** + * Filters whether TLS 1.2 is available. + * + * @since 4.7.1 + * + * @param bool $is_available whether TLS 1.2 is available + * @param SV_WC_API_Base $api API class instance + */ + return (bool) apply_filters( 'wc_' . $this->get_plugin()->get_id() . '_api_is_tls_1_2_available', $this->get_plugin()->is_tls_1_2_available(), $this ); + } + + +} + + +endif; diff --git a/vendor/skyverge/wc-plugin-framework/woocommerce/api/class-sv-wc-api-exception.php b/vendor/skyverge/wc-plugin-framework/woocommerce/api/class-sv-wc-api-exception.php new file mode 100644 index 0000000..c5a3246 --- /dev/null +++ b/vendor/skyverge/wc-plugin-framework/woocommerce/api/class-sv-wc-api-exception.php @@ -0,0 +1,38 @@ +cache_lifetime = $lifetime; + + return $this; + } + + + /** + * Gets the cache lifetime for this request. + * + * @since 5.10.10 + * + * @return int + */ + public function get_cache_lifetime() : int { + + return $this->cache_lifetime; + } + + + /** + * Sets whether a fresh request should be attempted, regardless if a cached response is available. + * + * @since 5.10.10 + * + * @param bool $value whether to force a fresh request, or not + * @return self + */ + public function set_force_refresh( bool $value ) { + + $this->force_refresh = $value; + + return $this; + } + + + /** + * Determines whether a fresh request should be attempted. + * + * @since 5.10.10 + * + * @return bool + */ + public function should_refresh() : bool { + + return $this->force_refresh; + } + + + /** + * Sets whether the request's response should be stored in cache. + * + * @since 5.10.10 + * + * @param bool $value whether to cache the request, or not + * @return self + */ + public function set_should_cache( bool $value ) { + + $this->should_cache = $value; + + return $this; + } + + + /** + * Determines whether the request's response should be stored in cache. + * + * @since 5.10.10 + * + * @return bool + */ + public function should_cache() : bool { + + return $this->should_cache; + } + + + /** + * Bypasses caching for this request completely. + * + * When called, sets the `force_refresh` flag to true and `should_cache` flag to false + * + * @since 5.10.10 + * + * @return self + */ + public function bypass_cache() { + + $this->set_force_refresh( true ); + $this->set_should_cache( false ); + + return $this; + } + +} + + +endif; diff --git a/vendor/skyverge/wc-plugin-framework/woocommerce/assets/css/admin/sv-wc-plugin-admin-setup-wizard.min.css b/vendor/skyverge/wc-plugin-framework/woocommerce/assets/css/admin/sv-wc-plugin-admin-setup-wizard.min.css new file mode 100644 index 0000000..50d350d --- /dev/null +++ b/vendor/skyverge/wc-plugin-framework/woocommerce/assets/css/admin/sv-wc-plugin-admin-setup-wizard.min.css @@ -0,0 +1 @@ +h1#wc-logo.sv-wc-plugin-logo{font-size:28px;font-weight:bold}h1#wc-logo.sv-wc-plugin-logo a{color:#444444;text-decoration:none}h1#wc-logo.sv-wc-plugin-logo a:hover{color:#222222;text-decoration:none}.sv-wc-plugin-admin-setup-content .error{background:#dc3232;border-radius:5px;color:#fff;padding:1em}.sv-wc-plugin-admin-setup-content .error a{color:#fff;text-decoration:none}.sv-wc-plugin-admin-setup-content .error a:hover{text-decoration:underline}.sv-wc-plugin-admin-setup-content .sv-wc-plugin-admin-setup-control{margin-bottom:20px}.sv-wc-plugin-admin-setup-content .sv-wc-plugin-admin-setup-control label{color:#666;display:inline-block;font-size:15px;font-weight:500;margin-top:.85em;margin-bottom:.5em}.sv-wc-plugin-admin-setup-content .sv-wc-plugin-admin-setup-control input[type="text"],.sv-wc-plugin-admin-setup-content .sv-wc-plugin-admin-setup-control input[type="number"],.sv-wc-plugin-admin-setup-content .sv-wc-plugin-admin-setup-control input[type="password"],.sv-wc-plugin-admin-setup-content .sv-wc-plugin-admin-setup-control select{background-color:#fff;border:1px solid #ddd;border-radius:4px;color:#444;display:inline-block;font-size:16px;height:30px;padding:0 24px 0 8px;width:calc(100% - 8px - 24px - 2px)}.sv-wc-plugin-admin-setup-content .sv-wc-plugin-admin-setup-control select{width:100%}.sv-wc-plugin-admin-setup-content .sv-wc-plugin-admin-setup-control textarea{background-color:#fff;border:1px solid #ddd;border-radius:4px;color:#444;font-size:16px;padding:10px}.sv-wc-plugin-admin-setup-content .sv-wc-plugin-admin-setup-control .description{color:#888;font-size:13px;margin:5px 0 0}.sv-wc-plugin-admin-setup-content .sv-wc-plugin-admin-setup-control.toggle{display:flex;flex-wrap:nowrap;justify-content:space-between;padding:0;border-bottom:1px solid #eee;color:#666;align-items:top}.sv-wc-plugin-admin-setup-content .sv-wc-plugin-admin-setup-control.toggle:last-child{border-bottom:0}.sv-wc-plugin-admin-setup-content .sv-wc-plugin-admin-setup-control.toggle .name{flex-basis:0;min-width:160px;text-align:center;font-weight:bold;align-self:stretch;display:flex;align-items:baseline}.sv-wc-plugin-admin-setup-content .sv-wc-plugin-admin-setup-control.toggle .content{flex-grow:1;padding:20px}.sv-wc-plugin-admin-setup-content .sv-wc-plugin-admin-setup-control.toggle .content p{margin-bottom:1em}.sv-wc-plugin-admin-setup-content .sv-wc-plugin-admin-setup-control.toggle .content p:last-child{margin-bottom:0}.sv-wc-plugin-admin-setup-content .sv-wc-plugin-admin-setup-control.toggle .enable{flex-basis:0;min-width:75px;text-align:center;cursor:pointer;padding:2em 0;position:relative;max-height:1.5em;align-self:flex-start}.sv-wc-plugin-admin-setup-content .sv-wc-plugin-admin-setup-control.toggle .toggle{height:16px;width:32px;border:2px solid #935687;background-color:#935687;display:inline-block;text-indent:-9999px;border-radius:10em;position:relative}.sv-wc-plugin-admin-setup-content .sv-wc-plugin-admin-setup-control.toggle .toggle input[type=checkbox]{display:none}.sv-wc-plugin-admin-setup-content .sv-wc-plugin-admin-setup-control.toggle .toggle:before{content:"";display:block;width:16px;height:16px;background:#fff;position:absolute;top:0;right:0;border-radius:100%}.sv-wc-plugin-admin-setup-content .sv-wc-plugin-admin-setup-control.toggle .toggle.disabled{border-color:#999;background-color:#999}.sv-wc-plugin-admin-setup-content .sv-wc-plugin-admin-setup-control.toggle .toggle.disabled:before{right:auto;left:0} \ No newline at end of file diff --git a/vendor/skyverge/wc-plugin-framework/woocommerce/assets/images/ajax-loader.gif b/vendor/skyverge/wc-plugin-framework/woocommerce/assets/images/ajax-loader.gif new file mode 100644 index 0000000..e01a70f Binary files /dev/null and b/vendor/skyverge/wc-plugin-framework/woocommerce/assets/images/ajax-loader.gif differ diff --git a/vendor/skyverge/wc-plugin-framework/woocommerce/assets/js/admin/sv-wc-plugin-admin-setup-wizard.min.js b/vendor/skyverge/wc-plugin-framework/woocommerce/assets/js/admin/sv-wc-plugin-admin-setup-wizard.min.js new file mode 100644 index 0000000..4f87f69 --- /dev/null +++ b/vendor/skyverge/wc-plugin-framework/woocommerce/assets/js/admin/sv-wc-plugin-admin-setup-wizard.min.js @@ -0,0 +1 @@ +(function() { "use strict"; /** * WooCommerce Plugin Framework Setup Wizard scripts. * * @since 5.3.0 */ jQuery(function($) { var blockWizardUI; blockWizardUI = function() { return $('.wc-setup-content').block({ message: null, overlayCSS: { background: '#fff', opacity: 0.6 } }); }; $('.sv-wc-plugin-admin-setup-control').on('change', '.enable input', function() { if ($(this).is(':checked')) { return $(this).closest('.toggle').removeClass('disabled'); } else { return $(this).closest('.toggle').addClass('disabled'); } }); return $('.sv-wc-plugin-admin-setup-control').on('click', '.enable', function(e) { var $checkbox; if ($(e.target).is('input')) { e.stopPropagation(); return; } $checkbox = $(this).find('input[type="checkbox"]'); return $checkbox.prop('checked', !$checkbox.is(':checked')).change(); }); });}).call(this); \ No newline at end of file diff --git a/vendor/skyverge/wc-plugin-framework/woocommerce/assets/js/admin/sv-wp-admin-job-batch-handler.min.js b/vendor/skyverge/wc-plugin-framework/woocommerce/assets/js/admin/sv-wp-admin-job-batch-handler.min.js new file mode 100644 index 0000000..615c5b5 --- /dev/null +++ b/vendor/skyverge/wc-plugin-framework/woocommerce/assets/js/admin/sv-wp-admin-job-batch-handler.min.js @@ -0,0 +1 @@ +/** * WordPress Batch Job Handler * * @since 4.8.0 */(function() { var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; jQuery(function($) { 'use strict'; return window.SV_WP_Job_Batch_Handler = (function() { function SV_WP_Job_Batch_Handler(args) { this.process_job = bind(this.process_job, this); this.id = args.id; this.process_nonce = args.process_nonce; this.cancel_nonce = args.cancel_nonce; this.cancelled = false; } SV_WP_Job_Batch_Handler.prototype.process_job = function(job_id) { return new Promise((function(_this) { return function(resolve, reject) { var data; if (_this.cancelled === job_id) { return _this.cancel_job(job_id); } data = { action: _this.id + "_process_batch", security: _this.process_nonce, job_id: job_id }; return $.post(ajaxurl, data).done(function(response) { if (!(response.success && (response.data != null))) { return reject(response); } if (response.data.status !== 'processing') { return resolve(response); } $(document).trigger(_this.id + "_batch_progress_" + response.data.id, { percentage: response.data.percentage, progress: response.data.progress, total: response.data.total }); return resolve(_this.process_job(response.data.id)); }).fail(function(jqXHR, textStatus, error) { return reject(error); }); }; })(this)); }; SV_WP_Job_Batch_Handler.prototype.cancel_job = function(job_id) { return new Promise((function(_this) { return function(resolve, reject) { var data; _this.cancelled = false; data = { action: _this.id + "_cancel_job", security: _this.cancel_nonce, job_id: job_id }; return $.post(ajaxurl, data).done(function(response) { if (!response.success) { return reject(response); } return resolve(response); }).fail(function(jqXHR, textStatus, error) { return reject(error); }); }; })(this)); }; return SV_WP_Job_Batch_Handler; })(); });}).call(this); \ No newline at end of file diff --git a/vendor/skyverge/wc-plugin-framework/woocommerce/changelog.txt b/vendor/skyverge/wc-plugin-framework/woocommerce/changelog.txt new file mode 100644 index 0000000..ef2446d --- /dev/null +++ b/vendor/skyverge/wc-plugin-framework/woocommerce/changelog.txt @@ -0,0 +1,428 @@ +*** SkyVerge WooCommerce Plugin Framework Changelog *** + +2023.04.20 - version 5.11.0 + * Feature - Add compatibility methods and support for WooCommerce High Performance Order Storage (HPOS) + * Misc - Require PHP 7.4 or higher + * Misc - Remove deprecated compatibility methods + +2023.03.27 - version 5.10.16 + * Fix - Prevent fatal errors when no card types are selected for a payment gateway + +2023.02.06 - version 5.10.15 + * Fix - "My Account" delete payment method compatibility with WC 7.2+ + +2022.12.06 - version 5.10.14 + * Tweak - Improve the performance of the orders admin screen in some sites by optimizing payment gateways bulk action detection - props @ravinderk + * Fix - Allow gateway plugins to override card images by defining an override with the same name in the plugin's `assets/images` folder - props @cadic + +2022.08.16 - version 5.10.13 + * Fix - Improve Background Job Handler compatibility with newer WooCommerce versions while processing a job queue + +2022.01.13 - version 5.10.12 + * Fix - Remove deprecated usages of `is_ajax()` in favor of `wp_doing_ajax()` + +2022.01.11 - version 5.10.11 + * Tweak - Remove automatic notices for unsupported WooCommerce versions + +2021.09.22 - version 5.10.10 + * Feature - Add `Abstract_Cacheable_API_Base` and `Cacheable_Request_Trait` to support caching API request responses + * Tweak - Allow classes extending `SV_WC_API_Base` to tweak request and response data when broadcasting a request + +2021.08.27 - version 5.10.9 + * Feature - Add helper methods to convert arrays to comma separated lists for database IN clauses use + * Feature - Add helper method to format a percentage + +2021.06.17 - version 5.10.8 + * Fix - Address an issue at checkout with payment form JS never loaded when a $0 order has a total updated later + +2021.04.15 - version 5.10.7 + * Fix - Prevent fatal errors by loading External_Checkout extensions in the wp_loaded action rather than on init + +2021.03.12 - version 5.10.6 + * Feature - Add a helper method to determine whether the new WC navigation feature is enabled + +2021.03.04 - version 5.10.5 + * Tweak - Add a class representation for Braintree PayPal tokens + +2021.03.01 - version 5.10.4 + * Fix - Prevent a Fatal error while editing the Checkout page using Elementor + +2020.12.08 - version 5.10.3 + * Fix - Fix enqueuing token editor JS + +2020.12.03 - version 5.10.2 + * Misc - Update deprecated jQuery usages + +2020.11.24 - version 5.10.1 + * Fix - Prevent a deprecated notice for SV_WC_Payment_Gateway_Hosted::do_invalid_transaction_response() in PHP 8 + * Fix - Prevent PHP Notices and fatal errors on wizard steps + +2020.11.06 - version 5.10.0 + * Feature - Add Google Pay support + * Tweak - Improve multiple gateway settings inheritance to allow for more than two gateways + * Tweak - Add admin notices for tax configurations that are incompatible with Apple Pay + * Fix - Ensure blank settings inputs aren't added when a base abstract gateway defines shared settings that a child gateway doesn't support + +2020.10.09 - version 5.9.0 + * Feature - Add helper method to detect whether the current request is a REST API request + * Fix - Fix a Capture Charge button issue when adding items to an order + * Fix - Harden code to avoid a potential PHP warning at checkout if no card logos are configured for a gateway + * Fix - Update Pre-Orders and Subscriptions integrations to use 2 digits expiration years + +2020.07.31 - version 5.8.1 + * Fix - Ensure that some payment gateway scripts used for handling tokens reference the current version of the Framework + +2020.07.29 - version 5.8.0 + * Tweak - Migrate payment tokens to be compatible with WooCommerce core payment tokens + * Fix - Unblock the UI when removing a token from the admin token editor that was just added but not saved yet + * Dev - Deprecate some filter hooks in the payment methods table + +2020.05.15 - version 5.7.1 + * Fix - Prevent JavaScript error triggered when different versions of the framework are used at the same time + * Fix - Fix URL for the Configure link in the admin notes shown for payment gateways that are not configured + +2020.05.07 - version 5.7.0 + * Feature - Add a Settings API for easily registering plugin settings for display and REST API handling + * Feature - Introduce a base script handler for enqueueing and loading JavaScript objects + * Tweak - Ensure payment gateway scripts can be used when certain script optimization plugins are delaying load + * Tweak - Improve the payment form display on mobile devices + * Tweak - Update Apple Pay to allow all currencies by default + +2020.03.09 - version 5.6.1 + * Fix - Delete enhanced admin notes on plugin deactivation + * Fix - Prevent uncaught errors when creating notes when WC Admin is disabled + +2020.03.04 - version 5.6.0 + * Feature - Add support for WooCommerce Admin enhanced notes + * Tweak - Refactor Apple Pay handler classes for greater flexibility + * Fix - Remove gateway payment field validation on initial page load + +2020.01.20 - version 5.5.4 + * Tweak - Add a link to the site's terms and conditions page below Apple Pay buttons when available + * Tweak - Adjust the place order button label for redirect/hosted gateways + * Fix - Fix a JavaScript error triggered trying read the 'length' property of an undefined value in format_credit_card_inputs() + +2020.01.13 - version 5.5.3 + * Fix - Fix a JavaScript error when instantiating a class that hasn't been loaded + +2020.01.09 - version 5.5.2 + * Fix - `SV_WC_Payment_Gateway_Apple_Pay::process_payment()` now throws an exception if the result returned by the processing gateway doesn't indicate whether the transaction was successful or not + * Fix - Update `SV_WC_Payment_Gateway_Direct::process_payment()` to cover for and edge case in which `SV_WC_Payment_Gateway_Direct::do_transaction()` fails without throwing an exception + * Fix - On WooCommerce 3.9: prevent empty credit card fields from being marked as invalid before the user has entered any data + * Dev - TLS 1.2 helper methods moved from `SV_WC_API_Base` to `SV_WC_Plugin` + * Dev - Deprecated `SV_WC_API_Base::require_tls_1_2()` + +2019.11.14 - version 5.5.1 + * Tweak - Refactor Apple Pay order creation to support the same filters and actions that are fired during regular checkout + * Tweak - Allow multiple old hooks to be mapped to a single new one via the hook deprecator + * Fix - Harden integration with WooCommerce Pre-Orders to avoid a PHP error in some circumstances + * Fix - Fix double product stock reduction when an order is held and payment is not completed + +2019.10.15 - version 5.5.0 + * Feature - Add a plugin helper method to retrieve a template part while consistently passing the default template path to `wc_get_template()` + * Misc - Deprecate backwards compatibility methods for unsupported WooCommerce and PHP versions + * Misc - Replace `SV_WC_Helper::get_post()` and `SV_WC_Helper::get_request()` with `SV_WC_Helper::get_posted_value()` and `SV_WC_Helper::get_requested_value()` + +2019.09.05 - version 5.4.3 + * Fix - Do not show the checkbox to save the payment method on the checkout page if not logged in and registration during checkout is disabled + * Misc - Add a Country_Helper class to assist converting country codes to and from various formats + +2019.08.27 - version 5.4.2 + * Tweak - Add a standard set of subscription details to orders payment data set by a gateway + * Tweak - Add replacement helper methods to get the current screen in WordPress and check the screen ID + * Misc - Change SV_WC_Payment_Gateway::is_configured() from protected to public + * Misc - Add admin notice when a gateway is enabled but is not configured and is unable to take payments + +2019.08.06 - version 5.4.1 + * Misc - Add a configurable admin notice for plugins running deprecated WooCommerce versions + +2019.03.13 - version 5.4.0 + * Feature - Add abstract handlers for hosted payment processing + * Feature - Revamp the Lifecycle handler for easier upgrade routines and add event logging for important lifecycle events + * Tweak - Adjust the no-HTTPS notice to point to where the merchant can fix the problem + * Fix - Prevent the Capture button from showing on failed orders or orders without an original transaction ID + * Fix - Use the current order total when determining whether a captured order should change status + +2019.01.09 - version 5.3.1 + * Fix - Fix a JavaScript error in the welcome wizard for missing parameters + * Fix - Correctly handle memory_limit shorthand from php.ini + * Misc - Adjust the Accepted Card Logos setting description to further clarify its purpose + +2018.09.25 - version 5.3.0 + * Feature - Add support for updating payment methods via API on payment + * Tweak - Refactor capture handling and add a dedicated handling class + * Tweak - Add an admin notice for gateways when debug logging is enabled in production mode + * Fix - Ensure orders are automatically captured when the status is changes via the REST API if enabled + * Fix - Ensure the gateway capture UI is only displayed for regular orders + * Fix - Prevent capture JS errors when multiple gateways running the same framework version are activated + * Fix - Strip price HTML from the admin capture alert + * Dev - Add the wc_{gateway_id}_held_order_status filter + +2018.09.04 - version 5.2.2 + * Tweak - Provide an abstract Setup Wizard for plugins to implement for easier onboarding + +2018.08.21 - version 5.2.1 + * Fix - Prevent errors when triggering payment gateway payment and refund milestones + * Fix - Add escaping to some admin notice messages + +2018.07.24 - version 5.2.0 + * Fix - Use the order currency for the gateway capture message currency symbol + * Dev - Introduce dedicated plugin methods for loading after init + * Dev - Move plugin lifecycle methods to the Lifecycle handler + * Dev - Introduce a dependency handler for PHP compatibility notices + * Dev - Introduce a REST API handler base + * Misc - Add default plugin and gateway data to the WooCommerce REST API System Status response + +2018.06.25 - version 5.1.5 + * Fix - Ensure exceptions are caught for Subscriptions "change payment" and Pre-Orders tokenization failures + +2018.05.22 - version 5.1.4 + * Tweak - Add a gateway privacy handler to export or remove order payment data and payment tokens on request + * Tweak - Add a warning for WooCommerce 2.6 installs that 3.0 will soon be required + * Misc - Add support for WooCommerce 3.4 + +2018.04.17 - version 5.1.3 + * Tweak - Add a method for gateways to call during failing captures + +2018.04.02 - version 5.1.2 + * Fix - Prevent warnings in PHP 7.2 when building the gateway settings + * Fix - Fix namespaces in the Apple Pay framework + +2018.03.27 - version 5.1.1 + * Tweak - Disable the Add Payment Method button when editing a method + * Fix - Ensure customers can't delete subscription payment methods from gateways that use integer token IDs + * Fix - Always pass user email to gateways when adding a payment method + +2018.02.27 - version 5.1.0 + * Feature - Add payment method editing support + * Feature - Allow users to set nicknames for their payment methods + * Feature - Add support for auto-capturing orders when changed to a paid status + * Feature - Add a Milestones API for plugins to trigger milestone messages and prompt users for feedback after key plugin events + * Tweak - Improve the My Account Payment Methods table on desktop and mobile + * Tweak - Let gateway handle their own API errors when deleting payment methods + * Tweak - Improve the admin token editor with better error handling and improved display + * Tweak - Let plugins define a "reviews" URL to be displayed with the plugin action links + * Tweak - Adjust the gateway "Accepted Cards" setting wording to clarify that it doesn't affect payment processor card support + * Tweak - Support warning and info message types in the Admin Message Handler + * Fix - Prevent duplicate admin notices when running alongside legacy framework versions + +2018.01.17 - version 5.0.1 + * Misc - Remove support for WooCommerce 2.5 + * Misc - Require WordPress 4.4 or higher + +2018.01.11 - version 5.0.0 + * Feature - Partial capture - add a UI for multiple partial captures in supported gateways + * Feature - Add CSC setting to enable or disable the field for tokenized methods + * Tweak - Improve the My Payment Methods table styling on mobile + * Dev - Add versioned namespaces + * Dev - Add a sample plugin loader class + * Dev - Add action hooks for My Payment Method actions + * Misc - Drop WooCommerce 2.5 support + * Misc - Drop Subscriptions 1.x support + +2017.12.11 - version 4.8.3 + * Fix - Ensure failed order token meta is only copied to the parent subscription when a successful payment has occurred + * Fix - Don't reset the checkout password field if it's already visible + +2017.12.01 - version 4.8.2 + * Fix - Fix a possible race condition when performing background processing health checks + * Fix - Account for possible false negatives when testing loopback connections in certain environments + +2017.11.27 - version 4.8.1 + * Fix - Fix Apple Pay compatibility with WooCommerce 3.2+ + +2017.10.31 - version 4.8.0 + * Feature - Add a framework for batch job handling for when background processing is unavailable + * Feature - Debug tool for testing the site's environment for loopback connection support + +2017.10.05 - version 4.7.3 + * Tweak - Add new methods for checking for specific WooCommerce versions + * Tweak - Adjust the PHP version notice to check for 5.6 by May 2018 and adjust the messaging when that date has passed + * Fix - Conflict with WooCommerce filtering of nonce checks for background jobs + +2017.09.12 - version 4.7.2 + * Fix - Ensure failed Pre-Orders can be paid with a new method by bypassing the failed order's stored token + * Fix - Use the parameters passed to SV_WP_Admin_Message_Handler::show_messages() + +2017.08.14 - version 4.7.1 + * Tweak - Refine the TLS 1.2 notice wording and appearance + +2017.07.25 - version 4.7.0 + * Feature - Introduce the Apple Pay framework for developers + +2017.07.11 - version 4.6.6 + * Fix - Ensure backwards compatibility with gateways that don't extend the SV_WC_API_Base class for their API + +2017.06.26 - version 4.6.5 + * Misc. - Make a TLS 1.2 admin notice available for gateways that require it + * Misc. - Ensure WooCommerce 3.1 compatibility + +2017.05.20 - version 4.6.4 + * Fix - Add dedicated subscriptions Change Payment handling to avoid subscription manipulation + * Fix - Ensure old payment methods can be removed after changing subscription payment to a new method + +2017.05.09 - version 4.6.3 + * Tweak - Add optional notice for plugins that want to require PHP 5.3+ in the future + * Tweak - Improved background process handling for certain server & cache configurations + +2017.05.01 - version 4.6.2 + * Fix - Ensure authorized, but not yet captured, transactions are marked "on hold" for off-site gateways + +2017.04.17 - version 4.6.1 + * Tweak - Load admin translations based on the user's configured language in WordPress 4.7+ + * Tweak - Added the SV_WC_Order_Compatibility::has_shipping_address() method + * Fix - Prevent some deprecated notices when processing subscriptions in WooCommerce 3.0+ + +2017.03.27 - version 4.6.0 + * Tweak - Add Payment Gateway debug mode to the System Status report + * Tweak - Plugin "Docs" links now open in a new tab + * Misc - Add helper method to get normalized WooCommerce screen IDs + * Misc - Added support for WooCommerce 3.0 + +2017.01.06 - version 4.5.2 + * Fix - Include Curaçao when converting country codes + +2016.11.18 - version 4.5.1 + * Fix - Prevent a potential fatal error for plugins not using the latest JSON/XML request classes + +2016.11.07 - version 4.5.0 + * Feature - Mobile-friendly credit card fields using the `tel` input + * Feature - Add setting to enable capture for virtual-only orders + * Feature - Define minimum php.ini requirements an display a notice when they are not met + * Feature - Allow deprecated hooks to be mapped to their replacements + * Tweak - Move capture handling to the base gateway class to make it available to hosted gateways + * Tweak - Add a "card not accepted" icon when a card number format is not accepted or recognized + * Tweak - Add full MasterCard BIN Series 2 support & update the card logo + * Tweak - Improve consistency of card type IDs and abbreviations + * Tweak - Refactor gateway settings inheritance + * Fix - Fix failed renewal payment data not updating for auth-only renewals + * Fix - The `load_translation()` method is no longer required for base plugins + * Fix - Prevent notices when running alongside bbPress or BuddyPress + +2016.09.14 - version 4.4.3 + * Fix - Fix an error when processing guest pre-order payments + +2016.08.02 - version 4.4.2 + * Tweak - Refactor background job data structure and processing + +2016.07.18 - version 4.4.1 + * Misc - Add compatibility for WordPress 4.6 + +2016.06.01 - version 4.4.0 + * Feature - Allow bundled framework and plugin translations to be easily overridden + * Tweak - Allow plugins extending SV_WC_API_Base to declare TLS v1.2 as a requirement for requests + * Misc - Added support for WooCommerce 2.6 + * Misc - Removed support for WooCommerce 2.3 + +2016.04.18 - version 4.3.0 + * Feature - Revamped admin payment token editor + * Feature - Prevent deleting subscription payment methods + * Feature - Add payment gateway environment information to the WooCommerce system status report + * Tweak - Support WordPress core dismissible notices + * Tweak - Misc Payment Gateway framework improvements + * Fix - Properly validate CSC if present for tokenized payment methods + * Fix - Fix double confirm messages when deleting a payment method in certain cases + +2016.02.08 - version 4.2.2 + * Fix - Fix handling guest pre-orders + +2016.01.20 - version 4.2.1 + * Fix - Fix `implode()` warnings in `SV_WC_Helper::get_order_line_items()` + +2016.01.13 - version 4.2.0 + * Feature - Greatly improved compatibility with multi-language/translation plugins + * Misc - Switched to using a separate text domain for the framework strings - 'woocommerce-plugin-framework' + * Misc - Added support for WooCommerce 2.5 + * Misc - Removed support for WooCommerce 2.2 + +2015.11.05 - version 4.1.2 + * Tweak - Misc Payment Gateway framework improvements + +2015.09.09 - version 4.1.1 + * Fix - For Subscriptions 1.5, don't mark the original order as failed when a renewal payment fails + +2015.08.27 - version 4.1.0 + * Feature - WooCommerce Subscriptions 2.0 Support + * Tweak - Add specific width/height styling for payment method icons + * Fix - Fix assert() warnings with certain gateway configurations on the My Account page + +2015.07.29 - version 4.0.1 + * Fix - Fix typo in payment gateway frontend javascript + * Tweak - Add inline style for payment gateway icons + +2015.07.27 - version 4.0.0 + * Feature - Standardized payment gateway form + * Feature - Add new payment method feature + * Feature - Standardized & unified My Payment Methods table + * Feature - New payment method icons in SVG format + * Tweak - Adds is_woocommerce_active() method to bootstrap class, to support non-WooThemes listed frameworked plugins + * Tweak - Payment gateway token support now allows for merging local data with remote data, and caching results in a transient + * Tweak - The order status for voided orders is now "cancelled" rather than "refunded" + * Tweak - Improved support for REST API development + * Tweak - Framework bootstrap now gives the option to easily deactivate backwards incompatible plugins, rather than only an instruction to update older plugins + * Tweak - Framework bootstrap option to declare minimum required WordPress version + * Misc - WooCommerce 2.4 compatibility + +2015.03.17 - version 3.1.2 + * Fix - JS variable `wc_select_params` undefined in WC 2.3.6+ + +2015.03.10 - version 3.1.1 + * Tweak - Add `get_cancel_order_url_raw()` compatibility method + +2015.02.09 - version 3.1.0 + * Feature - Refund/Void support for the payment gateway framework, huzzah! + * Misc - WooCommerce 2.3 compatibility + +2014.12.11 - version 3.0.4 + * Fix - Bug when removing a tokenized credit card from the My Account page + * Tweak - Accept a notice-class parameter when rendering admin notice to avoid always using the "error" notice + +2014.11.20 - version 3.0.3 + * Fix - Payment gateway framework now catches all SV_WC_Plugin_Exception exceptions. Fixes uncaught SV_WC_API_Exception error. + +2014.10.19 - version 3.0.2 + * Fix - Add commonly used notice functions to avoid errors when renewing subscriptions + +2014.10.15 - version 3.0.1 + * Tweak - Method visibility changed from private to protected to allow adjustment via sub-classes + * Fix - Fix "Wrong parameters for Exception" fatal error + +2014.09.07 - version 3.0.0 + * Feature - Edit tokens from Admin Order Edit screen + * Tweak - Improved dismissible admin notices + * Misc - WooCommerce 2.2 compatibility + * Misc - Backwards incompatible + +2014.08.26 - version 2.2.0 + * Feature - Added API base class and automatic request logging + * Feature - Introduced Helper class + * Feature - Optional detailed customer decline messages on checkout + * Tweak - Introduced named exceptions + * Tweak - Updates to support Chase Paymentech certification mode + * Tweak - Updates to the Hosted Gateway class for improved handling of redirect-back gateways + * Tweak - My Payment Methods template now uses Dashicons instead of images + * Tweak - Plugin active method now checks filename only + * Fix - Fixed product page URLs + +2014.05.26 - version 2.1.0 + * Feature - Implemented hosted payment gateway framework + * Feature - Capture charge bulk order action for payment gateways + * Tweak - Authorized charges are no longer automatically capture when changing order status from on-hold to processing/completed + * Feature - Added function dependency checks + +2014.03.05 - version 2.0.3 + * Fix - Fixed WC 2.1 compatibility for payment gateway charge captures + +2014.02.03 - version 2.0.2 + * Fix - Improved WC 2.1 compatibility method to return the order id on the checkout pay page + +2014.01.29 - version 2.0.1 + * Tweak - Additional WC 2.1 compatibility helpers + +2014.01.20 - version 2.0.0 + * Feature - Generalized Plugin Framework + * Feature - Support for "tokenize with sale" gateways + * Tweak - Improved handling for credit card capture request + +2013.11.11 - version 1.0.0 + * First Release diff --git a/vendor/skyverge/wc-plugin-framework/woocommerce/class-sv-wc-admin-notice-handler.php b/vendor/skyverge/wc-plugin-framework/woocommerce/class-sv-wc-admin-notice-handler.php new file mode 100644 index 0000000..1580a02 --- /dev/null +++ b/vendor/skyverge/wc-plugin-framework/woocommerce/class-sv-wc-admin-notice-handler.php @@ -0,0 +1,436 @@ +plugin = $plugin; + + // render any admin notices, delayed notices, and + add_action( 'admin_notices', array( $this, 'render_admin_notices' ), 15 ); + add_action( 'admin_footer', array( $this, 'render_delayed_admin_notices' ), 15 ); + add_action( 'admin_footer', array( $this, 'render_admin_notice_js' ), 20 ); + + // AJAX handler to dismiss any warning/error notices + add_action( 'wp_ajax_wc_plugin_framework_' . $this->get_plugin()->get_id() . '_dismiss_notice', array( $this, 'handle_dismiss_notice' ) ); + } + + + /** + * Adds the given $message as a dismissible notice identified by $message_id, + * unless the notice has been dismissed, or we're on the plugin settings page + * + * @since 3.0.0 + * @param string $message the notice message to display + * @param string $message_id the message id + * @param array $params { + * Optional parameters. + * + * @type bool $dismissible If the notice should be dismissible + * @type bool $always_show_on_settings If the notice should be forced to display on the + * plugin settings page, regardless of `$dismissible`. + * @type string $notice_class Additional classes for the notice. + * } + */ + public function add_admin_notice( $message, $message_id, $params = array() ) { + + $params = wp_parse_args( $params, array( + 'dismissible' => true, + 'always_show_on_settings' => true, + 'notice_class' => 'updated', + ) ); + + if ( $this->should_display_notice( $message_id, $params ) ) { + $this->admin_notices[ $message_id ] = array( + 'message' => $message, + 'rendered' => false, + 'params' => $params, + ); + } + } + + + /** + * Returns true if the identified notice hasn't been cleared, or we're on + * the plugin settings page (where notices are always displayed) + * + * @since 3.0.0 + * @param string $message_id the message id + * @param array $params { + * Optional parameters. + * + * @type bool $dismissible If the notice should be dismissible + * @type bool $always_show_on_settings If the notice should be forced to display on the + * plugin settings page, regardless of `$dismissible`. + * } + * @return bool + */ + public function should_display_notice( $message_id, $params = array() ) { + + // bail out if user is not a shop manager + if ( ! current_user_can( 'manage_woocommerce' ) ) { + return false; + } + + $params = wp_parse_args( $params, array( + 'dismissible' => true, + 'always_show_on_settings' => true, + ) ); + + // if the notice is always shown on the settings page, and we're on the settings page + if ( $params['always_show_on_settings'] && $this->get_plugin()->is_plugin_settings() ) { + return true; + } + + // non-dismissible, always display + if ( ! $params['dismissible'] ) { + return true; + } + + // dismissible: display if notice has not been dismissed + return ! $this->is_notice_dismissed( $message_id ); + } + + + /** + * Render any admin notices, as well as the admin notice placeholder + * + * @since 3.0.0 + * @param boolean $is_visible true if the notices should be immediately visible, false otherwise + */ + public function render_admin_notices( $is_visible = true ) { + + // default for actions + if ( ! is_bool( $is_visible ) ) { + $is_visible = true; + } + + foreach ( $this->admin_notices as $message_id => $message_data ) { + if ( ! $message_data['rendered'] ) { + $message_data['params']['is_visible'] = $is_visible; + $this->render_admin_notice( $message_data['message'], $message_id, $message_data['params'] ); + $this->admin_notices[ $message_id ]['rendered'] = true; + } + } + + if ( $is_visible && ! self::$admin_notice_placeholder_rendered ) { + // placeholder for moving delayed notices up into place + echo '
    '; + self::$admin_notice_placeholder_rendered = true; + } + + } + + + /** + * Render any delayed admin notices, which have not yet already been rendered + * + * @since 3.0.0 + */ + public function render_delayed_admin_notices() { + $this->render_admin_notices( false ); + } + + + /** + * Render a single admin notice + * + * @since 3.0.0 + * @param string $message the notice message to display + * @param string $message_id the message id + * @param array $params { + * Optional parameters. + * + * @type bool $dismissible If the notice should be dismissible + * @type bool $is_visible If the notice should be immediately visible + * @type bool $always_show_on_settings If the notice should be forced to display on the + * plugin settings page, regardless of `$dismissible`. + * @type string $notice_class Additional classes for the notice. + * } + */ + public function render_admin_notice( $message, $message_id, $params = array() ) { + + $params = wp_parse_args( $params, array( + 'dismissible' => true, + 'is_visible' => true, + 'always_show_on_settings' => true, + 'notice_class' => 'updated', + ) ); + + $classes = array( + 'notice', + 'js-wc-plugin-framework-admin-notice', + $params['notice_class'], + ); + + // maybe make this notice dismissible + // uses a WP core class which handles the markup and styling + if ( $params['dismissible'] && ( ! $params['always_show_on_settings'] || ! $this->get_plugin()->is_plugin_settings() ) ) { + $classes[] = 'is-dismissible'; + } + + echo sprintf( + '

    %5$s

    ', + esc_attr( implode( ' ', $classes ) ), + esc_attr( $this->get_plugin()->get_id() ), + esc_attr( $message_id ), + ( ! $params['is_visible'] ) ? 'style="display:none;"' : '', + wp_kses_post( $message ) + ); + } + + + /** + * Render the javascript to handle the notice "dismiss" functionality + * + * @since 3.0.0 + */ + public function render_admin_notice_js() { + + // if there were no notices, or we've already rendered the js, there's nothing to do + if ( empty( $this->admin_notices ) || self::$admin_notice_js_rendered ) { + return; + } + + $plugin_slug = $this->get_plugin()->get_id_dasherized(); + + self::$admin_notice_js_rendered = true; + + ob_start(); + + ?> + ( function( $ ) { + + // log dismissed notices + $( '.js-wc-plugin-framework-admin-notice' ).on( 'click.wp-dismiss-notice', '.notice-dismiss', function( e ) { + + var $notice = $( this ).closest( '.js-wc-plugin-framework-admin-notice' ); + + log_dismissed_notice( + $( $notice ).data( 'plugin-id' ), + $( $notice ).data( 'message-id' ) + ); + + } ); + + // Log and hide legacy notices + $( 'a.js-wc-plugin-framework-notice-dismiss' ).click( function( e ) { + + e.preventDefault(); + + var $notice = $( this ).closest( '.js-wc-plugin-framework-admin-notice' ); + + log_dismissed_notice( + $( $notice ).data( 'plugin-id' ), + $( $notice ).data( 'message-id' ) + ); + + $( $notice ).fadeOut(); + + } ); + + function log_dismissed_notice( pluginID, messageID ) { + + $.get( + ajaxurl, + { + action: 'wc_plugin_framework_' + pluginID + '_dismiss_notice', + messageid: messageID + } + ); + } + + // move any delayed notices up into position .show(); + $( '.js-wc-plugin-framework-admin-notice:hidden' ).insertAfter( '.js-wc--admin-notice-placeholder' ).show(); + + } ) ( jQuery ); + get_dismissed_notices( $user_id ); + + $dismissed_notices[ $message_id ] = true; + + update_user_meta( $user_id, '_wc_plugin_framework_' . $this->get_plugin()->get_id() . '_dismissed_messages', $dismissed_notices ); + + /** + * Admin Notice Dismissed Action. + * + * Fired when a user dismisses an admin notice. + * + * @since 3.0.0 + * @param string $message_id notice identifier + * @param string|int $user_id + */ + do_action( 'wc_' . $this->get_plugin()->get_id(). '_dismiss_notice', $message_id, $user_id ); + } + + + /** + * Marks the identified admin notice as not dismissed for the identified user + * + * @since 3.0.0 + * @param string $message_id the message identifier + * @param int $user_id optional user identifier, defaults to current user + */ + public function undismiss_notice( $message_id, $user_id = null ) { + + if ( is_null( $user_id ) ) { + $user_id = get_current_user_id(); + } + + $dismissed_notices = $this->get_dismissed_notices( $user_id ); + + $dismissed_notices[ $message_id ] = false; + + update_user_meta( $user_id, '_wc_plugin_framework_' . $this->get_plugin()->get_id() . '_dismissed_messages', $dismissed_notices ); + } + + + /** + * Returns true if the identified admin notice has been dismissed for the + * given user + * + * @since 3.0.0 + * @param string $message_id the message identifier + * @param int $user_id optional user identifier, defaults to current user + * @return boolean true if the message has been dismissed by the admin user + */ + public function is_notice_dismissed( $message_id, $user_id = null ) { + + $dismissed_notices = $this->get_dismissed_notices( $user_id ); + + return isset( $dismissed_notices[ $message_id ] ) && $dismissed_notices[ $message_id ]; + } + + + /** + * Returns the full set of dismissed notices for the user identified by + * $user_id, for this plugin + * + * @since 3.0.0 + * @param int $user_id optional user identifier, defaults to current user + * @return array of message id to dismissed status (true or false) + */ + public function get_dismissed_notices( $user_id = null ) { + + if ( is_null( $user_id ) ) { + $user_id = get_current_user_id(); + } + + $dismissed_notices = get_user_meta( $user_id, '_wc_plugin_framework_' . $this->get_plugin()->get_id() . '_dismissed_messages', true ); + + if ( empty( $dismissed_notices ) ) { + return array(); + } else { + return $dismissed_notices; + } + } + + + /** AJAX methods ******************************************************/ + + + /** + * Dismiss the identified notice + * + * @since 3.0.0 + */ + public function handle_dismiss_notice() { + + $this->dismiss_notice( $_REQUEST['messageid'] ); + + } + + + /** Getter methods ******************************************************/ + + + /** + * Get the plugin + * + * @since 3.0.0 + * @return SV_WC_Plugin returns the plugin instance + */ + protected function get_plugin() { + return $this->plugin; + } + + +} + + +endif; diff --git a/vendor/skyverge/wc-plugin-framework/woocommerce/class-sv-wc-framework-bootstrap.php b/vendor/skyverge/wc-plugin-framework/woocommerce/class-sv-wc-framework-bootstrap.php new file mode 100644 index 0000000..fd4c127 --- /dev/null +++ b/vendor/skyverge/wc-plugin-framework/woocommerce/class-sv-wc-framework-bootstrap.php @@ -0,0 +1,407 @@ +registered_plugins[] = array( 'version' => $version, 'plugin_name' => $plugin_name, 'path' => $path, 'callback' => $callback, 'args' => $args ); + } + + + /** + * Loads all registered framework plugins, first initializing the plugin + * framework by loading the highest versioned one. + * + * @since 2.0.0 + */ + public function load_framework_plugins() { + + // first sort the registered plugins by framework version + usort( $this->registered_plugins, array( $this, 'compare_frameworks' ) ); + + $loaded_framework = null; + + foreach ( $this->registered_plugins as $plugin ) { + + // load the first found (highest versioned) plugin framework class + if ( ! class_exists( 'SV_WC_Plugin' ) ) { + require_once( $this->get_plugin_path( $plugin['path'] ) . '/lib/skyverge/woocommerce/class-sv-wc-plugin.php' ); + $loaded_framework = $plugin; + + // the loaded plugin is always considered active (for the + // purposes of handling conflicts between this and other plugins + // with incompatible framework versions) + $this->active_plugins[] = $plugin; + } + + // if the loaded version of the framework has a backwards compatibility requirement + // which is not met by the current plugin add an admin notice and move on without + // loading the plugin + if ( ! empty( $loaded_framework['args']['backwards_compatible'] ) && version_compare( $loaded_framework['args']['backwards_compatible'], $plugin['version'], '>' ) ) { + + $this->incompatible_framework_plugins[] = $plugin; + + // next plugin + continue; + } + + // if a plugin defines a minimum WC version which is not met, render a notice and skip loading the plugin + if ( ! empty( $plugin['args']['minimum_wc_version'] ) && version_compare( $this->get_wc_version(), $plugin['args']['minimum_wc_version'], '<' ) ) { + + $this->incompatible_wc_version_plugins[] = $plugin; + + // next plugin + continue; + } + + // if a plugin defines a minimum WP version which is not met, render a notice and skip loading the plugin + if ( ! empty( $plugin['args']['minimum_wp_version'] ) && version_compare( get_bloginfo( 'version' ), $plugin['args']['minimum_wp_version'], '<' ) ) { + + $this->incompatible_wp_version_plugins[] = $plugin; + + // next plugin + continue; + } + + // add this plugin to the active list + if ( ! in_array( $plugin, $this->active_plugins ) ) { + $this->active_plugins[] = $plugin; + } + + // load the first found (highest versioned) payment gateway framework class, as needed + if ( isset( $plugin['args']['is_payment_gateway'] ) && ! class_exists( 'SV_WC_Payment_Gateway' ) ) { + require_once( $this->get_plugin_path( $plugin['path'] ) . '/lib/skyverge/woocommerce/payment-gateway/class-sv-wc-payment-gateway-plugin.php' ); + } + + // initialize the plugin + $plugin['callback'](); + } + + // render update notices + if ( ( $this->incompatible_framework_plugins || $this->incompatible_wc_version_plugins || $this->incompatible_wp_version_plugins ) && is_admin() && ! defined( 'DOING_AJAX' ) && ! has_action( 'admin_notices', array( $this, 'render_update_notices' ) ) ) { + + add_action( 'admin_notices', array( $this, 'render_update_notices' ) ); + } + + /** + * WC Plugin Framework Plugins Loaded Action. + * + * Fired when all frameworked plugins are loaded. Frameworked plugins can + * hook into this action rather than `plugins_loaded`/`woocommerce_loaded` + * as needed. + * + * @since 2.0.0 + */ + do_action( 'sv_wc_framework_plugins_loaded' ); + } + + + /** Admin methods ******************************************************/ + + + /** + * Deactivate backwards-incompatible framework plugins, which will allow + * plugins with an older version of the framework to be active. Useful when + * the admin isn't ready to upgrade older plugins yet needs them to still + * function (e.g. a payment gateway) + * + * @since 4.0.0 + */ + public function maybe_deactivate_framework_plugins() { + + if ( isset( $_GET['sv_wc_framework_deactivate_newer'] ) ) { + if ( 'yes' == $_GET['sv_wc_framework_deactivate_newer'] ) { + + // don't want to just deactivate all active plugins willy-nilly if there's no incompatible plugins + if ( count( $this->incompatible_framework_plugins ) == 0 ) { + return; + } + + $plugins = array(); + + foreach ( $this->active_plugins as $plugin ) { + $plugins[] = plugin_basename( $plugin['path'] ); + } + + // deactivate all "active" frameworked plugins, these will be the newest, backwards-incompatible ones + deactivate_plugins( $plugins ); + + // redirect to the inactive plugin admin page, with a message indicating the number of plugins deactivated + wp_redirect( admin_url( 'plugins.php?plugin_status=inactive&sv_wc_framework_deactivate_newer=' . count( $plugins ) ) ); + exit; + } else { + // we're on the inactive plugin page and we've deactivated one or more plugins + add_action( 'admin_notices', array( $this, 'render_deactivation_notice' ) ); + } + } + } + + + /** + * Render a notice with a count of the backwards incompatible frameworked + * plugins that were deactivated + * + * @since 4.0.0 + */ + public function render_deactivation_notice() { + echo '

    '; + echo $_GET['sv_wc_framework_deactivate_newer'] > 1 ? + sprintf( 'Deactivated %d plugins', $_GET['sv_wc_framework_deactivate_newer'] ) : + 'Deactivated one plugin'; + echo '

    '; + } + + + /** + * Render a notice to update any plugins with incompatible framework + * versions, or incompatiblities with the current WooCommerce or WordPress + * versions + * + * @since 2.0.0 + */ + public function render_update_notices() { + + // must update plugin notice + if ( ! empty( $this->incompatible_framework_plugins ) ) { + + $plugin_count = count( $this->incompatible_framework_plugins ); + + echo '
    '; + + // describe the problem + echo '

    '; + echo esc_html( _n( 'The following plugin is disabled because it is out of date and incompatible with newer plugins on your site:', 'The following plugins are disabled because they are out of date and incompatible with newer plugins on your site:', $plugin_count, 'woocommerce-plugin-framework' ) ); + echo '

    '; + + // add a incompatible plugin list + echo '
      '; + foreach ( $this->incompatible_framework_plugins as $plugin ) { + printf( '
    • %s
    • ', esc_html( $plugin['plugin_name'] ) ); + } + echo '
    '; + + // describe the way to fix it + echo '

    '; + printf( + /** translators: Placeholders: %1$s - tag, %2$s - tag, %3$s - tag, %4$s - tag, %5$s - tag, %6$s - tag, %7$s - tag, %8$s - tag */ + esc_html( _n( 'To resolve this, please %1$supdate%2$s (recommended) %3$sor%4$s %5$sdeactivate%6$s the above plugin, or %7$sdeactivate the following%8$s:', 'To resolve this, please %1$supdate%2$s (recommended) %3$sor%4$s %5$sdeactivate%6$s the above plugins, or %7$sdeactivate the following%8$s:', $plugin_count, 'woocommerce-plugin-framework' ) ), + '', '', + '', '', + '', '', + '', '' + ); + echo '

    '; + + // add the list of active plugins + echo '
      '; + foreach ( $this->active_plugins as $plugin ) { + printf( '
    • %s
    • ', esc_html( $plugin['plugin_name'] ) ); + } + echo '
    '; + + echo '
    '; + } + + // must update WC notice + if ( ! empty( $this->incompatible_wc_version_plugins ) ) { + + printf( '

    %s

      ', count( $this->incompatible_wc_version_plugins ) > 1 ? esc_html__( 'The following plugins are inactive because they require a newer version of WooCommerce:', 'woocommerce-plugin-framework' ) : esc_html__( 'The following plugin is inactive because it requires a newer version of WooCommerce:', 'woocommerce-plugin-framework' ) ); + + foreach ( $this->incompatible_wc_version_plugins as $plugin ) { + + /* translators: Placeholders: %1$s - plugin name, %2$s - WooCommerce version number */ + echo '
    • ' . sprintf( esc_html__( '%1$s requires WooCommerce %2$s or newer', 'woocommerce-plugin-framework' ), esc_html( $plugin['plugin_name'] ), esc_html( $plugin['args']['minimum_wc_version'] ) ) . '
    • '; + } + + /* translators: Placeholders: %1$s - tag, %2$s - tag */ + echo '

    ' . sprintf( esc_html__( 'Please %1$supdate WooCommerce%2$s', 'woocommerce-plugin-framework' ), '', ' »' ) . '

    '; + } + + // must update WP notice + if ( ! empty( $this->incompatible_wp_version_plugins ) ) { + + printf( '

    %s

      ', count( $this->incompatible_wp_version_plugins ) > 1 ? 'The following plugins are inactive because they require a newer version of WordPress:' : 'The following plugin is inactive because it requires a newer version of WordPress:' ); + + foreach ( $this->incompatible_wp_version_plugins as $plugin ) { + printf( '
    • %s requires WordPress %s or newer
    • ', esc_html( $plugin['plugin_name'] ), esc_html( $plugin['args']['minimum_wp_version'] ) ); + } + + echo '

    Please update WordPress »

    '; + } + } + + + /** Helper methods ******************************************************/ + + + /** + * Is the WooCommerce plugin installed and active? This method is handy for + * frameworked plugins that are listed on wordpress.org and thus don't have + * access to the Woo Helper functions bundled with WooThemes-listed plugins. + * + * Notice: For now you can't rely on this method being available, since the + * bootstrap class is the only piece of the framework which is loaded + * simply according to the lexical order of plugin directories. Therefore + * to use, you should first check that this method exists, or if you really + * need to check for WooCommerce being active, define your own method. + * + * @since 4.0.0 + * @return boolean true if the WooCommerce plugin is installed and active + */ + public static function is_woocommerce_active() { + + $active_plugins = (array) get_option( 'active_plugins', array() ); + + if ( is_multisite() ) { + $active_plugins = array_merge( $active_plugins, get_site_option( 'active_sitewide_plugins', array() ) ); + } + + return in_array( 'woocommerce/woocommerce.php', $active_plugins ) || array_key_exists( 'woocommerce/woocommerce.php', $active_plugins ); + } + + + /** + * Compare the two framework versions. Returns -1 if $a is less than $b, 0 if + * they're equal, and 1 if $a is greater than $b + * + * @since 2.0.0 + * @param array $a first registered plugin to compare + * @param array $b second registered plugin to compare + * @return int -1 if $a is less than $b, 0 if they're equal, and 1 if $a is greater than $b + */ + public function compare_frameworks( $a, $b ) { + // compare versions without the operator argument, so we get a -1, 0 or 1 result + return version_compare( $b['version'], $a['version'] ); + } + + + /** + * Returns the plugin path for the given $file + * + * @since 2.0.0 + * @param string $file the file + * @return string plugin path + */ + public function get_plugin_path( $file ) { + return untrailingslashit( plugin_dir_path( $file ) ); + } + + + /** + * Returns the WooCommerce version number, backwards compatible to + * WC 1.5 + * + * @since 3.0.0 + * @return null|string + */ + protected function get_wc_version() { + + if ( defined( 'WC_VERSION' ) && WC_VERSION ) return WC_VERSION; + if ( defined( 'WOOCOMMERCE_VERSION' ) && WOOCOMMERCE_VERSION ) return WOOCOMMERCE_VERSION; + + return null; + } + +} + + +// instantiate the class +SV_WC_Framework_Bootstrap::instance(); + + +endif; diff --git a/vendor/skyverge/wc-plugin-framework/woocommerce/class-sv-wc-helper.php b/vendor/skyverge/wc-plugin-framework/woocommerce/class-sv-wc-helper.php new file mode 100644 index 0000000..c445b7f --- /dev/null +++ b/vendor/skyverge/wc-plugin-framework/woocommerce/class-sv-wc-helper.php @@ -0,0 +1,1104 @@ + 'foo', 'item_2' => 'bar' ) + * + * array_insert_after( $array, 'item_1', array( 'item_1.5' => 'w00t' ) ) + * + * becomes + * + * array( 'item_1' => 'foo', 'item_1.5' => 'w00t', 'item_2' => 'bar' ) + * + * @since 2.2.0 + * @param array $array array to insert the given element into + * @param string $insert_key key to insert given element after + * @param array $element element to insert into array + * @return array + */ + public static function array_insert_after( Array $array, $insert_key, Array $element ) { + + $new_array = array(); + + foreach ( $array as $key => $value ) { + + $new_array[ $key ] = $value; + + if ( $insert_key == $key ) { + + foreach ( $element as $k => $v ) { + $new_array[ $k ] = $v; + } + } + } + + return $new_array; + } + + + /** + * Convert array into XML by recursively generating child elements + * + * First instantiate a new XML writer object: + * + * $xml = new XMLWriter(); + * + * Open in memory (alternatively you can use a local URI for file output) + * + * $xml->openMemory(); + * + * Then start the document + * + * $xml->startDocument( '1.0', 'UTF-8' ); + * + * Don't forget to end the document and output the memory + * + * $xml->endDocument(); + * + * $your_xml_string = $xml->outputMemory(); + * + * @since 2.2.0 + * + * @param \XMLWriter $xml_writer XML writer instance + * @param string|array $element_key name for element, e.g. + * @param string|array $element_value value for element, e.g. 100 + */ + public static function array_to_xml( $xml_writer, $element_key, $element_value = array() ) { + + if ( is_array( $element_value ) ) { + + // handle attributes + if ( '@attributes' === $element_key ) { + + foreach ( $element_value as $attribute_key => $attribute_value ) { + + $xml_writer->startAttribute( $attribute_key ); + $xml_writer->text( $attribute_value ); + $xml_writer->endAttribute(); + } + + return; + } + + // handle multi-elements (e.g. multiple elements) + if ( is_numeric( key( $element_value ) ) ) { + + // recursively generate child elements + foreach ( $element_value as $child_element_key => $child_element_value ) { + + $xml_writer->startElement( $element_key ); + + foreach ( $child_element_value as $sibling_element_key => $sibling_element_value ) { + self::array_to_xml( $xml_writer, $sibling_element_key, $sibling_element_value ); + } + + $xml_writer->endElement(); + } + + } else { + + // start root element + $xml_writer->startElement( $element_key ); + + // recursively generate child elements + foreach ( $element_value as $child_element_key => $child_element_value ) { + self::array_to_xml( $xml_writer, $child_element_key, $child_element_value ); + } + + // end root element + $xml_writer->endElement(); + } + + } else { + + // handle single elements + if ( '@value' === $element_key ) { + + $xml_writer->text( $element_value ); + + } else { + + // wrap element in CDATA tags if it contains illegal characters + if ( false !== strpos( $element_value, '<' ) || false !== strpos( $element_value, '>' ) ) { + + $xml_writer->startElement( $element_key ); + $xml_writer->writeCdata( $element_value ); + $xml_writer->endElement(); + + } else { + + $xml_writer->writeElement( $element_key, $element_value ); + } + } + } + } + + + /** + * Lists an array as text. + * + * Takes an array and returns a list like "one, two, three, and four" + * with a (mandatory) oxford comma. + * + * @since 5.2.0 + * + * @param array $items items to list + * @param string|null $conjunction coordinating conjunction, like "or" or "and" + * @param string $separator list separator, like a comma + * @return string + */ + public static function list_array_items( array $items, $conjunction = null, $separator = '' ) { + + if ( ! is_string( $conjunction ) ) { + $conjunction = _x( 'and', 'coordinating conjunction for a list of items: a, b, and c', 'woocommerce-plugin-framework' ); + } + + // append the conjunction to the last item + if ( count( $items ) > 1 ) { + + $last_item = array_pop( $items ); + + array_push( $items, trim( "{$conjunction} {$last_item}" ) ); + + // only use a comma if needed and no separator was passed + if ( count( $items ) < 3 ) { + $separator = ' '; + } elseif ( ! is_string( $separator ) || '' === $separator ) { + $separator = ', '; + } + } + + return implode( $separator, $items ); + } + + + /** Number helper functions *******************************************/ + + + /** + * Format a number with 2 decimal points, using a period for the decimal + * separator and no thousands separator. + * + * Commonly used for payment gateways which require amounts in this format. + * + * @since 3.0.0 + * @param float $number + * @return string + */ + public static function number_format( $number ) { + + return number_format( (float) $number, 2, '.', '' ); + } + + + /** WooCommerce helper functions **************************************/ + + + /** + * Gets order line items (products) as an array of objects. + * + * Object properties: + * + * + id - item ID + * + name - item name, usually product title, processed through htmlentities() + * + description - formatted item meta (e.g. Size: Medium, Color: blue), processed through htmlentities() + * + quantity - item quantity + * + item_total - item total (line total divided by quantity, excluding tax & rounded) + * + line_total - line item total (excluding tax & rounded) + * + meta - formatted item meta array + * + product - item product or null if getting product from item failed + * + item - raw item array + * + * @since 3.0.0 + * + * @param \WC_Order $order + * @return \stdClass[] array of line item objects + */ + public static function get_order_line_items( $order ) { + + $line_items = []; + + /** @var \WC_Order_Item_Product $item */ + foreach ( $order->get_items() as $id => $item ) { + + $line_item = new \stdClass(); + $product = $item->get_product(); + $name = $item->get_name(); + $quantity = $item->get_quantity(); + $sku = $product instanceof \WC_Product ? $product->get_sku() : ''; + $item_desc = []; + + // add SKU to description if available + if ( ! empty( $sku ) ) { + $item_desc[] = sprintf( 'SKU: %s', $sku ); + } + + $meta_data = $item->get_formatted_meta_data( '-', true ); + $item_meta = []; + + foreach ( $meta_data as $meta ) { + $item_meta[] = array( + 'label' => $meta->display_key, + 'value' => $meta->value, + ); + } + + if ( ! empty( $item_meta ) ) { + foreach ( $item_meta as $meta ) { + $item_desc[] = sprintf( '%s: %s', $meta['label'], $meta['value'] ); + } + } + + $item_desc = implode( ', ', $item_desc ); + + $line_item->id = $id; + $line_item->name = htmlentities( $name, ENT_QUOTES, 'UTF-8', false ); + $line_item->description = htmlentities( $item_desc, ENT_QUOTES, 'UTF-8', false ); + $line_item->quantity = $quantity; + $line_item->item_total = isset( $item['recurring_line_total'] ) ? $item['recurring_line_total'] : $order->get_item_total( $item ); + $line_item->line_total = $order->get_line_total( $item ); + $line_item->meta = $item_meta; + $line_item->product = is_object( $product ) ? $product : null; + $line_item->item = $item; + + $line_items[] = $line_item; + } + + return $line_items; + } + + + /** + * Determines if an order contains only virtual products. + * + * @since 4.5.0 + * + * @param \WC_Order $order the order object + * @return bool + */ + public static function is_order_virtual( \WC_Order $order ) { + + $is_virtual = true; + + /** @var \WC_Order_Item_Product $item */ + foreach ( $order->get_items() as $item ) { + + $product = $item->get_product(); + + // once we've found one non-virtual product we know we're done, break out of the loop + if ( $product && ! $product->is_virtual() ) { + + $is_virtual = false; + break; + } + } + + return $is_virtual; + } + + + /** + * Determines if a shop has any published virtual products. + * + * @since 5.10.0 + * + * @return bool + */ + public static function shop_has_virtual_products() { + + $virtual_products = wc_get_products( [ + 'virtual' => true, + 'status' => 'publish', + 'limit' => 1, + ] ); + + return sizeof( $virtual_products ) > 0; + } + + + /** + * Safely gets a value from $_POST. + * + * If the expected data is a string also trims it. + * + * @since 5.5.0 + * + * @param string $key posted data key + * @param int|float|array|bool|null|string $default default data type to return (default empty string) + * @return int|float|array|bool|null|string posted data value if key found, or default + */ + public static function get_posted_value( $key, $default = '' ) { + + $value = $default; + + if ( isset( $_POST[ $key ] ) ) { + $value = is_string( $_POST[ $key ] ) ? trim( $_POST[ $key ] ) : $_POST[ $key ]; + } + + return $value; + } + + + /** + * Safely gets a value from $_REQUEST. + * + * If the expected data is a string also trims it. + * + * @since 5.5.0 + * + * @param string $key posted data key + * @param int|float|array|bool|null|string $default default data type to return (default empty string) + * @return int|float|array|bool|null|string posted data value if key found, or default + */ + public static function get_requested_value( $key, $default = '' ) { + + $value = $default; + + if ( isset( $_REQUEST[ $key ] ) ) { + $value = is_string( $_REQUEST[ $key ] ) ? trim( $_REQUEST[ $key ] ) : $_REQUEST[ $key ]; + } + + return $value; + } + + + /** + * Get the count of notices added, either for all notices (default) or for one + * particular notice type specified by $notice_type. + * + * WC notice functions are not available in the admin + * + * @since 3.0.2 + * @param string $notice_type The name of the notice type - either error, success or notice. [optional] + * @return int + */ + public static function wc_notice_count( $notice_type = '' ) { + + if ( function_exists( 'wc_notice_count' ) ) { + return wc_notice_count( $notice_type ); + } + + return 0; + } + + + /** + * Add and store a notice. + * + * WC notice functions are not available in the admin + * + * @since 3.0.2 + * @param string $message The text to display in the notice. + * @param string $notice_type The singular name of the notice type - either error, success or notice. [optional] + */ + public static function wc_add_notice( $message, $notice_type = 'success' ) { + + if ( function_exists( 'wc_add_notice' ) ) { + wc_add_notice( $message, $notice_type ); + } + } + + + /** + * Print a single notice immediately + * + * WC notice functions are not available in the admin + * + * @since 3.0.2 + * @param string $message The text to display in the notice. + * @param string $notice_type The singular name of the notice type - either error, success or notice. [optional] + */ + public static function wc_print_notice( $message, $notice_type = 'success' ) { + + if ( function_exists( 'wc_print_notice' ) ) { + wc_print_notice( $message, $notice_type ); + } + } + + + /** + * Gets the full URL to the log file for a given $handle + * + * @since 4.0.0 + * @param string $handle log handle + * @return string URL to the WC log file identified by $handle + */ + public static function get_wc_log_file_url( $handle ) { + return admin_url( sprintf( 'admin.php?page=wc-status&tab=logs&log_file=%s-%s-log', $handle, sanitize_file_name( wp_hash( $handle ) ) ) ); + } + + + /** + * Gets the current WordPress site name. + * + * This is helpful for retrieving the actual site name instead of the + * network name on multisite installations. + * + * @since 4.6.0 + * @return string + */ + public static function get_site_name() { + + return ( is_multisite() ) ? get_blog_details()->blogname : get_bloginfo( 'name' ); + } + + + /** JavaScript helper functions ***************************************/ + + + /** + * Enhanced search JavaScript (Select2) + * + * Enqueues JavaScript required for AJAX search with Select2. + * + * @codeCoverageIgnore no need to unit test this since it's mostly JavaScript + * + * @since 3.1.0 + */ + public static function render_select2_ajax() { + + if ( ! did_action( 'sv_wc_select2_ajax_rendered' ) ) { + + $javascript = "( function(){ + if ( ! $().select2 ) return; + "; + + // Ensure localized strings are used. + $javascript .= " + + function getEnhancedSelectFormatString() { + + if ( 'undefined' !== typeof wc_select_params ) { + wc_enhanced_select_params = wc_select_params; + } + + if ( 'undefined' === typeof wc_enhanced_select_params ) { + return {}; + } + + var formatString = { + formatMatches: function( matches ) { + if ( 1 === matches ) { + return wc_enhanced_select_params.i18n_matches_1; + } + + return wc_enhanced_select_params.i18n_matches_n.replace( '%qty%', matches ); + }, + formatNoMatches: function() { + return wc_enhanced_select_params.i18n_no_matches; + }, + formatAjaxError: function( jqXHR, textStatus, errorThrown ) { + return wc_enhanced_select_params.i18n_ajax_error; + }, + formatInputTooShort: function( input, min ) { + var number = min - input.length; + + if ( 1 === number ) { + return wc_enhanced_select_params.i18n_input_too_short_1 + } + + return wc_enhanced_select_params.i18n_input_too_short_n.replace( '%qty%', number ); + }, + formatInputTooLong: function( input, max ) { + var number = input.length - max; + + if ( 1 === number ) { + return wc_enhanced_select_params.i18n_input_too_long_1 + } + + return wc_enhanced_select_params.i18n_input_too_long_n.replace( '%qty%', number ); + }, + formatSelectionTooBig: function( limit ) { + if ( 1 === limit ) { + return wc_enhanced_select_params.i18n_selection_too_long_1; + } + + return wc_enhanced_select_params.i18n_selection_too_long_n.replace( '%qty%', number ); + }, + formatLoadMore: function( pageNumber ) { + return wc_enhanced_select_params.i18n_load_more; + }, + formatSearching: function() { + return wc_enhanced_select_params.i18n_searching; + } + }; + + return formatString; + } + "; + + $javascript .= " + + $( 'select.sv-wc-enhanced-search' ).filter( ':not(.enhanced)' ).each( function() { + + var select2_args = { + allowClear: $( this ).data( 'allow_clear' ) ? true : false, + placeholder: $( this ).data( 'placeholder' ), + minimumInputLength: $( this ).data( 'minimum_input_length' ) ? $( this ).data( 'minimum_input_length' ) : '3', + escapeMarkup: function( m ) { + return m; + }, + ajax: { + url: '" . esc_js( admin_url( 'admin-ajax.php' ) ) . "', + dataType: 'json', + cache: true, + delay: 250, + data: function( params ) { + return { + term: params.term, + request_data: $( this ).data( 'request_data' ) ? $( this ).data( 'request_data' ) : {}, + action: $( this ).data( 'action' ) || 'woocommerce_json_search_products_and_variations', + security: $( this ).data( 'nonce' ) + }; + }, + processResults: function( data, params ) { + var terms = []; + if ( data ) { + $.each( data, function( id, text ) { + terms.push( { id: id, text: text } ); + }); + } + return { results: terms }; + } + } + }; + + select2_args = $.extend( select2_args, getEnhancedSelectFormatString() ); + + $( this ).select2( select2_args ).addClass( 'enhanced' ); + } ); + "; + + $javascript .= '} )();'; + + wc_enqueue_js( $javascript ); + + /** + * WC Select2 Ajax Rendered Action. + * + * Fired when an Ajax select2 is rendered. + * + * @since 3.1.0 + */ + do_action( 'sv_wc_select2_ajax_rendered' ); + } + } + + + /** Framework translation functions ***********************************/ + + + /** + * Gettext `__()` wrapper for framework-translated strings + * + * Warning! This function should only be used if an existing + * translation from the framework is to be used. It should + * never be called for plugin-specific or untranslated strings! + * Untranslated = not registered via string literal. + * + * @since 4.1.0 + * @param string $text + * @return string translated text + */ + public static function f__( $text ) { + + return __( $text, 'woocommerce-plugin-framework' ); + } + + + /** + * Gettext `_e()` wrapper for framework-translated strings + * + * Warning! This function should only be used if an existing + * translation from the framework is to be used. It should + * never be called for plugin-specific or untranslated strings! + * Untranslated = not registered via string literal. + * + * @since 4.1.0 + * @param string $text + */ + public static function f_e( $text ) { + + _e( $text, 'woocommerce-plugin-framework' ); + } + + + /** + * Gettext `_x()` wrapper for framework-translated strings + * + * Warning! This function should only be used if an existing + * translation from the framework is to be used. It should + * never be called for plugin-specific or untranslated strings! + * Untranslated = not registered via string literal. + * + * @since 4.1.0 + * + * @param string $text + * @param string $context + * @return string translated text + */ + public static function f_x( $text, $context ) { + + return _x( $text, $context, 'woocommerce-plugin-framework' ); + } + + + /** Misc functions ****************************************************/ + + + /** + * Gets the WordPress current screen. + * + * @see get_current_screen() replacement which is always available, unlike the WordPress core function + * + * @since 5.4.2 + * + * @return \WP_Screen|null + */ + public static function get_current_screen() { + global $current_screen; + + return $current_screen ?: null; + } + + + /** + * Checks if the current screen matches a specified ID. + * + * This helps avoiding using the get_current_screen() function which is not always available, + * or setting the substitute global $current_screen every time a check needs to be performed. + * + * @since 5.4.2 + * + * @param string $id id (or property) to compare + * @param string $prop optional property to compare, defaults to screen id + * @return bool + */ + public static function is_current_screen( $id, $prop = 'id' ) { + global $current_screen; + + return isset( $current_screen->$prop ) && $id === $current_screen->$prop; + } + + + /** + * Determines if viewing an enhanced admin screen. + * + * @since 5.6.0 + * + * @return bool + */ + public static function is_enhanced_admin_screen() { + + return is_admin() && SV_WC_Plugin_Compatibility::is_enhanced_admin_available() && ( \Automattic\WooCommerce\Admin\Loader::is_admin_page() || \Automattic\WooCommerce\Admin\Loader::is_embed_page() ); + } + + + /** + * Determines whether the new WooCommerce enhanced navigation is supported and enabled. + * + * @since 5.10.6 + * + * @return bool + */ + public static function is_wc_navigation_enabled() { + + return + is_callable( [ \Automattic\WooCommerce\Admin\Features\Navigation\Screen::class, 'register_post_type' ] ) && + is_callable( [ \Automattic\WooCommerce\Admin\Features\Navigation\Menu::class, 'add_plugin_item' ] ) && + is_callable( [ \Automattic\WooCommerce\Admin\Features\Navigation\Menu::class, 'add_plugin_category' ] ) && + is_callable( [ \Automattic\WooCommerce\Admin\Features\Features::class, 'is_enabled' ] ) && + \Automattic\WooCommerce\Admin\Features\Features::is_enabled( 'navigation' ); + } + + + /** + * Determines if the current request is for a WC REST API endpoint. + * + * @see \WooCommerce::is_rest_api_request() + * + * @since 5.9.0 + * + * @return bool + */ + public static function is_rest_api_request() { + + if ( is_callable( 'WC' ) && is_callable( [ WC(), 'is_rest_api_request' ] ) ) { + return (bool) WC()->is_rest_api_request(); + } + + if ( empty( $_SERVER['REQUEST_URI'] ) || ! function_exists( 'rest_get_url_prefix' ) ) { + return false; + } + + $rest_prefix = trailingslashit( rest_get_url_prefix() ); + $is_rest_api_request = false !== strpos( $_SERVER['REQUEST_URI'], $rest_prefix ); + + /* applies WooCommerce core filter */ + return (bool) apply_filters( 'woocommerce_is_rest_api_request', $is_rest_api_request ); + } + + + /** + * Displays a notice if the provided hook has not yet run. + * + * @since 5.2.0 + * + * @param string $hook action hook to check + * @param string $method method/function name + * @param string $version version the notice was added + */ + public static function maybe_doing_it_early( $hook, $method, $version ) { + + if ( ! did_action( $hook ) ) { + wc_doing_it_wrong( $method, "This should only be called after '{$hook}'", $version ); + } + } + + + /** + * Triggers a PHP error. + * + * This wrapper method ensures AJAX isn't broken in the process. + * + * @since 4.6.0 + * @param string $message the error message + * @param int $type Optional. The error type. Defaults to E_USER_NOTICE + */ + public static function trigger_error( $message, $type = E_USER_NOTICE ) { + + if ( is_callable( 'wp_doing_ajax' ) && wp_doing_ajax() ) { + + switch ( $type ) { + + case E_USER_NOTICE: + $prefix = 'Notice: '; + break; + + case E_USER_WARNING: + $prefix = 'Warning: '; + break; + + default: + $prefix = ''; + } + + error_log( $prefix . $message ); + + } else { + + trigger_error( $message, $type ); + } + } + + + /** + * Converts an array of strings to a comma separated list of strings, escaped for SQL use. + * + * This can be safely used in SQL IN clauses. + * + * @since 5.10.9 + * + * @param string[] $values + * @return string + */ + public static function get_escaped_string_list( array $values ) { + global $wpdb; + + return (string) $wpdb->prepare( implode( ', ', array_fill( 0, count( $values ), '%s' ) ), $values ); + } + + + /** + * Converts an array of numerical integers into a comma separated list of IDs. + * + * This can be safely used for SQL IN clauses. + * + * @since 5.10.9 + * + * @param int[] $ids + * @return string + */ + public static function get_escaped_id_list( array $ids ) { + + return implode( ',', array_unique( array_map( 'intval', $ids ) ) ); + } + + +} + + +endif; diff --git a/vendor/skyverge/wc-plugin-framework/woocommerce/class-sv-wc-hook-deprecator.php b/vendor/skyverge/wc-plugin-framework/woocommerce/class-sv-wc-hook-deprecator.php new file mode 100644 index 0000000..47a8dd2 --- /dev/null +++ b/vendor/skyverge/wc-plugin-framework/woocommerce/class-sv-wc-hook-deprecator.php @@ -0,0 +1,199 @@ +plugin_name = $plugin_name; + $this->hooks = array_map( array( $this, 'set_hook_defaults' ), $hooks ); + + $this->map_deprecated_hooks(); + + add_action( 'shutdown', array( $this, 'trigger_deprecated_errors' ), 999 ); + } + + + /** + * Sets the deprecated hook defaults. + * + * @since 4.5.0 + * @param array $hook_params the hook parameters + * @return array + */ + protected function set_hook_defaults( $hook_params ) { + + $defaults = array( + 'removed' => false, + 'map' => false, + 'replacement' => '', + ); + + return wp_parse_args( $hook_params, $defaults ); + } + + + /** + * Map each deprecated hook to its replacement. + * + * @since 4.5.0 + */ + protected function map_deprecated_hooks() { + + foreach ( $this->hooks as $old_hook => $hook ) { + + if ( ! empty( $hook['replacement'] ) && $hook['removed'] && $hook['map'] ) { + add_filter( $hook['replacement'], array( $this, 'map_deprecated_hook' ), 10, 10 ); + } + } + } + + + /** + * Map a deprecated/renamed hook to a new one. + * + * This method works by hooking into the new, renamed version of the action/filter + * and checking if any actions/filters are hooked into the old hook. It then runs + * these and applies the data modifications in the new hook. + * + * @since 4.5.0 + * @return mixed + */ + public function map_deprecated_hook() { + + $args = func_get_args(); + $data = $args[0]; + $new_hook = current_filter(); + + $new_hooks = wp_list_pluck( $this->hooks, 'replacement' ); + + // check if there are matching old hooks for the current hook + foreach ( array_keys( $new_hooks, $new_hook ) as $old_hook ) { + + // check if there are any hooks added to the old hook + if ( has_filter( $old_hook ) ) { + + // prepend old hook name to the args + array_unshift( $args, $old_hook ); + + // apply the hooks attached to the old hook to $data + $data = call_user_func_array( 'apply_filters', $args ); + } + } + + return $data; + } + + + /** + * Trigger a notice when other actors have attached callbacks to hooks that + * are either deprecated or removed. This only runs when WP_DEBUG is on. + * + * @since 4.3.0 + */ + public function trigger_deprecated_errors() { + global $wp_filter; + + // follow WP core behavior for showing deprecated notices and only do so when WP_DEBUG is on + if ( defined( 'WP_DEBUG' ) && WP_DEBUG && apply_filters( 'sv_wc_plugin_framework_show_deprecated_hook_notices', true ) ) { + + // sanity check + if ( ! is_array( $wp_filter ) || empty( $wp_filter ) ) { + return; + } + + foreach ( $this->hooks as $old_hook_tag => $hook ) { + + // if other actors have attached a callback to the deprecated/removed hook... + if ( isset( $wp_filter[ $old_hook_tag ] ) ) { + + $this->trigger_error( $old_hook_tag, $hook ); + } + } + } + } + + + /** + * Trigger the deprecated/removed notice + * + * @since 4.3.0 + * @param string $old_hook_name deprecated/removed hook name + * @param array $hook { + * @type string $version version the hook was deprecated/removed in + * @type bool $removed if present and true, the message will indicate the hook was removed instead of deprecated + * @type string|bool $replacement if present and a string, the message will indicate the replacement hook to use, + * otherwise (if bool and false) the message will indicate there is no replacement available. + * } + */ + protected function trigger_error( $old_hook_name, $hook ) { + + // e.g. WooCommerce Memberships: "wc_memberships_some_hook" was deprecated in version 1.2.3. + $message = sprintf( '%1$s: action/filter "%2$s" was %3$s in version %4$s. ', + $this->plugin_name, + $old_hook_name, + $hook['removed'] ? 'removed' : 'deprecated', + $hook['version'] + ); + + // e.g. Use "wc_memberships_some_new_hook" instead. + $message .= ! empty( $hook['replacement'] ) ? sprintf( 'Use %1$s instead.', $hook['replacement'] ) : 'There is no replacement available.'; + + // triggers as E_USER_NOTICE + SV_WC_Helper::trigger_error( $message ); + } + + +} + + +endif; diff --git a/vendor/skyverge/wc-plugin-framework/woocommerce/class-sv-wc-plugin-compatibility.php b/vendor/skyverge/wc-plugin-framework/woocommerce/class-sv-wc-plugin-compatibility.php new file mode 100644 index 0000000..ab66274 --- /dev/null +++ b/vendor/skyverge/wc-plugin-framework/woocommerce/class-sv-wc-plugin-compatibility.php @@ -0,0 +1,367 @@ + 1 ] ); + + if ( is_array( $wp_org_request ) && isset( $wp_org_request['body'] ) ) { + + $plugin_info = json_decode( $wp_org_request['body'], true ); + + if ( is_array( $plugin_info ) && ! empty( $plugin_info['versions'] ) && is_array( $plugin_info['versions'] ) ) { + + $latest_wc_versions = []; + + // reverse array as WordPress supplies oldest version first, newest last + foreach ( array_keys( array_reverse( $plugin_info['versions'] ) ) as $wc_version ) { + + // skip trunk, release candidates, betas and other non-final or irregular versions + if ( + is_string( $wc_version ) + && '' !== $wc_version + && is_numeric( $wc_version[0] ) + && false === strpos( $wc_version, '-' ) + ) { + $latest_wc_versions[] = $wc_version; + } + } + + set_transient( 'sv_wc_plugin_wc_versions', $latest_wc_versions, WEEK_IN_SECONDS ); + } + } + } + + return is_array( $latest_wc_versions ) ? $latest_wc_versions : []; + } + + + /** + * Gets the version of the currently installed WooCommerce. + * + * @since 3.0.0 + * + * @return string|null Woocommerce version number or null if undetermined + */ + public static function get_wc_version() { + + return defined( 'WC_VERSION' ) && WC_VERSION ? WC_VERSION : null; + } + + + /** + * Determines if the installed WooCommerce version matches a specific version. + * + * @since 5.5.0 + * + * @param string $version semver + * @return bool + */ + public static function is_wc_version( $version ) { + + $wc_version = self::get_wc_version(); + + // accounts for semver cases like 3.0 being equal to 3.0.0 + return $wc_version === $version || ( $wc_version && version_compare( $wc_version, $version, '=' ) ); + } + + + /** + * Determines if the installed version of WooCommerce is equal or greater than a given version. + * + * @since 4.7.3 + * + * @param string $version version number to compare + * @return bool + */ + public static function is_wc_version_gte( $version ) { + + $wc_version = self::get_wc_version(); + + return $wc_version && version_compare( $wc_version, $version, '>=' ); + } + + + /** + * Determines if the installed version of WooCommerce is lower than a given version. + * + * @since 4.7.3 + * + * @param string $version version number to compare + * @return bool + */ + public static function is_wc_version_lt( $version ) { + + $wc_version = self::get_wc_version(); + + return $wc_version && version_compare( $wc_version, $version, '<' ); + } + + + /** + * Determines if the installed version of WooCommerce is greater than a given version. + * + * @since 2.0.0 + * + * @param string $version the version to compare + * @return bool + */ + public static function is_wc_version_gt( $version ) { + + $wc_version = self::get_wc_version(); + + return $wc_version && version_compare( $wc_version, $version, '>' ); + } + + + /** + * Determines whether the enhanced admin is available. + * + * This checks both for WooCommerce v4.0+ and the underlying package availability. + * + * @since 5.6.0 + * + * @return bool + */ + public static function is_enhanced_admin_available() { + + return self::is_wc_version_gte( '4.0' ) && function_exists( 'wc_admin_url' ); + } + + + /** WordPress core ******************************************************/ + + + /** + * Normalizes a WooCommerce page screen ID. + * + * Needed because WordPress uses a menu title (which is translatable), not slug, to generate screen ID. + * See details in: https://core.trac.wordpress.org/ticket/21454 + * TODO: Add WP version check when https://core.trac.wordpress.org/ticket/18857 is addressed {BR 2016-12-12} + * + * @since 4.6.0 + * + * @param string $slug slug for the screen ID to normalize (minus `woocommerce_page_`) + * @return string normalized screen ID + */ + public static function normalize_wc_screen_id( $slug = 'wc-settings' ) { + + // The textdomain usage is intentional here, we need to match the menu title. + $prefix = sanitize_title( __( 'WooCommerce', 'woocommerce' ) ); + + return $prefix . '_page_' . $slug; + } + + + /** + * Converts a shorthand byte value to an integer byte value. + * + * Wrapper for wp_convert_hr_to_bytes(), moved to load.php in WordPress 4.6 from media.php + * + * Based on ActionScheduler's compat wrapper for the same function: + * ActionScheduler_Compatibility::convert_hr_to_bytes() + * + * @link https://secure.php.net/manual/en/function.ini-get.php + * @link https://secure.php.net/manual/en/faq.using.php#faq.using.shorthandbytes + * + * @since 5.3.1 + * + * @param string $value A (PHP ini) byte value, either shorthand or ordinary. + * @return int An integer byte value. + */ + public static function convert_hr_to_bytes( $value ) { + + if ( function_exists( 'wp_convert_hr_to_bytes' ) ) { + + return wp_convert_hr_to_bytes( $value ); + } + + $value = strtolower( trim( $value ) ); + $bytes = (int) $value; + + if ( false !== strpos( $value, 'g' ) ) { + + $bytes *= GB_IN_BYTES; + + } elseif ( false !== strpos( $value, 'm' ) ) { + + $bytes *= MB_IN_BYTES; + + } elseif ( false !== strpos( $value, 'k' ) ) { + + $bytes *= KB_IN_BYTES; + } + + // deal with large (float) values which run into the maximum integer size + return min( $bytes, PHP_INT_MAX ); + } + + + /** Subscriptions *********************************************************/ + + + /** + * Determines if the installed version of WooCommerce Subscriptions matches or exceeds a given version. + * + * @since 5.5.0 + * + * @param string $version version number to compare + * @return bool + */ + public static function is_wc_subscriptions_version_gte( $version ) { + + $subscriptions_version = self::get_wc_subscriptions_version(); + + return $subscriptions_version && version_compare( $subscriptions_version, $version, '>=' ); + } + + /** + * Determines if the installed version of WooCommerce Subscriptions exceeds a given version. + * + * @since 5.5.0 + * + * @param string $version version number to compare + * @return bool + */ + public static function is_wc_subscriptions_version_gt( $version ) { + + $subscriptions_version = self::get_wc_subscriptions_version(); + + return $subscriptions_version && version_compare( $subscriptions_version, $version, '>' ); + } + + + /** + * Determines if the installed version of WooCommerce Subscriptions is lower than a given version. + * + * @since 5.5.0 + * + * @param string $version version number to compare + * @return bool + */ + public static function is_wc_subscriptions_version_lt( $version ) { + + $subscriptions_version = self::get_wc_subscriptions_version(); + + return $subscriptions_version && version_compare( $subscriptions_version, $version, '<' ); + } + + + /** + * Gets the version of the currently installed WooCommerce Subscriptions. + * + * @since 4.1.0 + * + * @return string|null WooCommerce Subscriptions version number or null if not found + */ + protected static function get_wc_subscriptions_version() { + + return class_exists( 'WC_Subscriptions' ) && ! empty( \WC_Subscriptions::$version ) ? \WC_Subscriptions::$version : null; + } + + + /** + * Determines if the installed WooCommerce Subscriptions version matches a specific version. + * + * @since 5.5.0 + * + * @param string $version semver + * @return bool + */ + protected static function is_wc_subscriptions_version( $version ) { + + $subscriptions_version = self::get_wc_subscriptions_version(); + + // accounts for semver cases like 2.2 being equal to 2.2.0 + return $version === $subscriptions_version || ( $subscriptions_version && version_compare( $version, $subscriptions_version, '=' ) ); + } + + + /** + * Determines whether HPOS is enabled. + * + * @link https://woocommerce.com/document/high-performance-order-storage/ + * @link https://github.com/woocommerce/woocommerce/wiki/High-Performance-Order-Storage-Upgrade-Recipe-Book#detecting-whether-hpos-tables-are-being-used-in-the-store + * + * @since 5.11.0 + * + * @return bool + */ + public static function is_hpos_enabled() : bool { + + return is_callable( OrderUtil::class . '::' . 'custom_orders_table_usage_is_enabled' ) + && OrderUtil::custom_orders_table_usage_is_enabled(); + } + + +} + + +endif; diff --git a/vendor/skyverge/wc-plugin-framework/woocommerce/class-sv-wc-plugin-dependencies.php b/vendor/skyverge/wc-plugin-framework/woocommerce/class-sv-wc-plugin-dependencies.php new file mode 100644 index 0000000..b8e654b --- /dev/null +++ b/vendor/skyverge/wc-plugin-framework/woocommerce/class-sv-wc-plugin-dependencies.php @@ -0,0 +1,523 @@ +plugin = $plugin; + + $dependencies = $this->parse_dependencies( $args ); + + $this->php_extensions = (array) $dependencies['php_extensions']; + $this->php_functions = (array) $dependencies['php_functions']; + $this->php_settings = (array) $dependencies['php_settings']; + + // add the action & filter hooks + $this->add_hooks(); + } + + + /** + * Parses the dependency arguments and sets defaults. + * + * @since 5.2.0 + * + * @param array $args dependency args + * @return array + */ + private function parse_dependencies( $args ) { + + $dependencies = wp_parse_args( $args, array( + 'php_extensions' => array(), + 'php_functions' => array(), + 'php_settings' => array(), + ) ); + + $default_settings = array( + 'suhosin.post.max_array_index_length' => 256, + 'suhosin.post.max_totalname_length' => 65535, + 'suhosin.post.max_vars' => 1024, + 'suhosin.request.max_array_index_length' => 256, + 'suhosin.request.max_totalname_length' => 65535, + 'suhosin.request.max_vars' => 1024, + ); + + // override any default settings requirements if the plugin specifies them + if ( ! empty( $dependencies['php_settings'] ) ) { + $dependencies['php_settings'] = array_merge( $default_settings, $dependencies['php_settings'] ); + } + + return $dependencies; + } + + + /** + * Adds the action & filter hooks. + * + * @since 5.2.0 + */ + protected function add_hooks() { + + // add the admin dependency notices + add_action( 'admin_init', array( $this, 'add_admin_notices' ) ); + } + + + /** + * Adds the admin dependency notices. + * + * @since 5.2.0 + */ + public function add_admin_notices() { + + $this->add_php_extension_notices(); + $this->add_php_function_notices(); + $this->add_php_settings_notices(); + + $this->add_deprecated_notices(); + } + + + /** + * Adds notices for any missing PHP extensions. + * + * @since 5.2.0 + */ + public function add_php_extension_notices() { + + $missing_extensions = $this->get_missing_php_extensions(); + + if ( count( $missing_extensions ) > 0 ) { + + $message = sprintf( + /* translators: Placeholders: %1$s - plugin name, %2$s - a PHP extension/comma-separated list of PHP extensions */ + _n( + '%1$s requires the %2$s PHP extension to function. Contact your host or server administrator to install and configure the missing extension.', + '%1$s requires the following PHP extensions to function: %2$s. Contact your host or server administrator to install and configure the missing extensions.', + count( $missing_extensions ), + 'woocommerce-plugin-framework' + ), + esc_html( $this->get_plugin()->get_plugin_name() ), + '' . implode( ', ', $missing_extensions ) . '' + ); + + $this->add_admin_notice( 'wc-' . $this->get_plugin()->get_id_dasherized() . '-missing-extensions', $message, 'error' ); + } + } + + + /** + * Adds notices for any missing PHP functions. + * + * @since 5.2.0 + */ + public function add_php_function_notices() { + + $missing_functions = $this->get_missing_php_functions(); + + if ( count( $missing_functions ) > 0 ) { + + $message = sprintf( + /* translators: Placeholders: %1$s - plugin name, %2$s - a PHP function/comma-separated list of PHP functions */ + _n( + '%1$s requires the %2$s PHP function to exist. Contact your host or server administrator to install and configure the missing function.', + '%1$s requires the following PHP functions to exist: %2$s. Contact your host or server administrator to install and configure the missing functions.', + count( $missing_functions ), + 'woocommerce-plugin-framework' + ), + esc_html( $this->get_plugin()->get_plugin_name() ), + '' . implode( ', ', $missing_functions ) . '' + ); + + $this->add_admin_notice( 'wc-' . $this->get_plugin()->get_id_dasherized() . '-missing-functions', $message, 'error' ); + } + } + + + /** + * Adds notices for any incompatible PHP settings. + * + * @since 5.2.0 + */ + public function add_php_settings_notices() { + + if ( isset( $_GET['page'] ) && 'wc-settings' === $_GET['page'] ) { + + $bad_settings = $this->get_incompatible_php_settings(); + + if ( count( $bad_settings ) > 0 ) { + + $message = sprintf( + /* translators: Placeholders: %s - plugin name */ + __( '%s may behave unexpectedly because the following PHP configuration settings are required:' ), + '' . esc_html( $this->get_plugin()->get_plugin_name() ) . '' + ); + + $message .= '
      '; + + foreach ( $bad_settings as $setting => $values ) { + + $setting_message = '' . $setting . ' = ' . $values['expected'] . ''; + + if ( ! empty( $values['type'] ) && 'min' === $values['type'] ) { + + $setting_message = sprintf( + /** translators: Placeholders: %s - a PHP setting value */ + __( '%s or higher', 'woocommerce-plugin-framework' ), + $setting_message + ); + } + + $message .= '
    • ' . $setting_message . '
    • '; + } + + $message .= '
    '; + + $message .= __( 'Please contact your hosting provider or server administrator to configure these settings.', 'woocommerce-plugin-framework' ); + + $this->add_admin_notice( 'wc-' . $this->get_plugin()->get_id_dasherized() . '-incompatibile-php-settings', $message, 'warning' ); + } + } + } + + + /** + * Gets any deprecated warning notices. + * + * @since 5.2.0 + */ + protected function add_deprecated_notices() { + + // add a notice for PHP < 5.6 + if ( version_compare( PHP_VERSION, '5.6.0', '<' ) ) { + + $message = '

    '; + + $message .= sprintf( + /* translators: Placeholders: %1$s - , %2$s - */ + __( 'Hey there! We\'ve noticed that your server is running %1$san outdated version of PHP%2$s, which is the programming language that WooCommerce and its extensions are built on. + The PHP version that is currently used for your site is no longer maintained, nor %1$sreceives security updates%2$s; newer versions are faster and more secure. + As a result, %3$s no longer supports this version and you should upgrade PHP as soon as possible. + Your hosting provider can do this for you. %4$sHere are some resources to help you upgrade%5$s and to explain PHP versions further.', 'woocommerce-plugin-framework' ), + '', '', + esc_html( $this->get_plugin()->get_plugin_name() ), + '', '' + ); + + $message .= '

    '; + + $this->add_admin_notice( 'sv-wc-deprecated-php-version', $message, 'error' ); + } + } + + + /** + * Adds an admin notice. + * + * @since 5.2.0 + * + * @param string $id notice ID + * @param string $message notice message + * @param string $type notice type + */ + protected function add_admin_notice( $id, $message, $type = 'info' ) { + + $notice_class = $type; + + // translate the types into WP notice classes + switch ( $type ) { + + case 'error': + $notice_class = 'notice-error'; + break; + + case 'warning': + $notice_class = 'notice-warning'; + break; + + case 'info': + $notice_class = 'notice-info'; + break; + + case 'success': + $notice_class = 'notice-success'; + break; + } + + $this->get_plugin()->get_admin_notice_handler()->add_admin_notice( $message, $id, array( + 'notice_class' => $notice_class, + ) ); + } + + + /** + * Returns the active scripts optimization plugins. + * + * Returns a key-value array where the key contains the plugin file identifier and the value is the name of the plugin. + * + * @since 5.7.0 + * + * @return array + */ + public function get_active_scripts_optimization_plugins() { + + /** + * Filters script optimization plugins to look for. + * + * @since 5.7.0 + * + * @param array $plugins an array of file identifiers (keys) and plugin names (values) + */ + $plugins = (array) apply_filters( 'wc_' . $this->get_plugin()->get_id() . '_scripts_optimization_plugins', [ + 'async-javascript.php' => 'Async JavaScript', + 'autoptimize.php' => 'Autoptimize', + 'wp-hummingbird.php' => 'Hummingbird', + 'sg-optimizer.php' => 'SG Optimizer', + 'w3-total-cache.php' => 'W3 Total Cache', + 'wpFastestCache.php' => 'WP Fastest Cache', + 'wp-rocket.php' => 'WP Rocket', + ] ); + + $active_plugins = []; + + foreach ( $plugins as $filename => $plugin_name ) { + + if ( $this->get_plugin()->is_plugin_active( $filename ) ) { + + $active_plugins[ $filename ] = $plugin_name; + } + } + + return $active_plugins; + } + + + /** + * Returns true if any of the known scripts optimization plugins is active. + * + * @since 5.7.0 + * + * @return bool + */ + public function is_scripts_optimization_plugin_active() { + + return ! empty( $this->get_active_scripts_optimization_plugins() ); + } + + + /** Getter methods ********************************************************/ + + + /** + * Gets any missing PHP extensions. + * + * @since 5.2.0 + * + * @return array + */ + public function get_missing_php_extensions() { + + $missing_extensions = []; + + foreach ( $this->get_php_extensions() as $extension ) { + + if ( ! extension_loaded( $extension ) ) { + $missing_extensions[] = $extension; + } + } + + return $missing_extensions; + } + + + /** + * Gets the required PHP extensions. + * + * @since 5.2.0 + * + * @return array + */ + public function get_php_extensions() { + + return $this->php_extensions; + } + + + /** + * Gets any missing PHP functions. + * + * @since 5.2.0 + * + * @return array + */ + public function get_missing_php_functions() { + + $missing_functions = []; + + foreach ( $this->get_php_functions() as $function ) { + + if ( ! extension_loaded( $function ) ) { + $missing_functions[] = $function; + } + } + + return $missing_functions; + } + + + /** + * Gets the required PHP functions. + * + * @since 5.2.0 + * + * @return array + */ + public function get_php_functions() { + + return $this->php_functions; + } + + + /** + * Gets any incompatible PHP settings. + * + * @since 5.2.0 + * + * @return array + */ + public function get_incompatible_php_settings() { + + $incompatible_settings = []; + + if ( function_exists( 'ini_get' ) ) { + + foreach ( $this->get_php_settings() as $setting => $expected ) { + + $actual = ini_get( $setting ); + + if ( ! $actual ) { + continue; + } + + if ( is_int( $expected ) ) { + + // determine if this is a size string, like "10MB" + $is_size = ! is_numeric( substr( $actual, -1 ) ); + + $actual_num = $is_size ? wc_let_to_num( $actual ) : $actual; + + if ( $actual_num < $expected ) { + + $incompatible_settings[ $setting ] = [ + 'expected' => $is_size ? size_format( $expected ) : $expected, + 'actual' => $is_size ? size_format( $actual_num ) : $actual, + 'type' => 'min', + ]; + } + + } elseif ( $actual !== $expected ) { + + $incompatible_settings[ $setting ] = [ + 'expected' => $expected, + 'actual' => $actual, + ]; + } + } + } + + return $incompatible_settings; + } + + + /** + * Gets the required PHP settings. + * + * @since 5.2.0 + * + * @return array + */ + public function get_php_settings() { + + return $this->php_settings; + } + + + /** + * Gets the plugin instance. + * + * @since 5.2.0 + * + * @return SV_WC_Plugin + */ + protected function get_plugin() { + + return $this->plugin; + } + + +} + + +endif; diff --git a/vendor/skyverge/wc-plugin-framework/woocommerce/class-sv-wc-plugin-exception.php b/vendor/skyverge/wc-plugin-framework/woocommerce/class-sv-wc-plugin-exception.php new file mode 100644 index 0000000..9429f67 --- /dev/null +++ b/vendor/skyverge/wc-plugin-framework/woocommerce/class-sv-wc-plugin-exception.php @@ -0,0 +1,38 @@ +id = $id; + $this->version = $version; + + $args = wp_parse_args( $args, [ + 'text_domain' => '', + 'supports_hpos' => false, + 'dependencies' => [], + ] ); + + $this->text_domain = $args['text_domain']; + $this->supports_hpos = $args['supports_hpos']; + + // includes that are required to be available at all times + $this->includes(); + + // initialize the dependencies manager + $this->init_dependencies( $args['dependencies'] ); + + // build the admin message handler instance + $this->init_admin_message_handler(); + + // build the admin notice handler instance + $this->init_admin_notice_handler(); + + // build the hook deprecator instance + $this->init_hook_deprecator(); + + // build the lifecycle handler instance + $this->init_lifecycle_handler(); + + // build the REST API handler instance + $this->init_rest_api_handler(); + + // build the setup handler instance + $this->init_setup_wizard_handler(); + + // add the action & filter hooks + $this->add_hooks(); + } + + + /** Init methods **********************************************************/ + + + /** + * Initializes the plugin dependency handler. + * + * @since 5.2.0 + * + * @param array $dependencies { + * PHP extension, function, and settings dependencies + * + * @type array $php_extensions PHP extension dependencies + * @type array $php_functions PHP function dependencies + * @type array $php_settings PHP settings dependencies + * } + */ + protected function init_dependencies( $dependencies ) { + + $this->dependency_handler = new SV_WC_Plugin_Dependencies( $this, $dependencies ); + } + + + /** + * Builds the admin message handler instance. + * + * Plugins can override this with their own handler. + * + * @since 5.2.0 + */ + protected function init_admin_message_handler() { + + $this->message_handler = new SV_WP_Admin_Message_Handler( $this->get_id() ); + } + + + /** + * Builds the admin notice handler instance. + * + * Plugins can override this with their own handler. + * + * @since 5.2.0 + */ + protected function init_admin_notice_handler() { + + $this->admin_notice_handler = new SV_WC_Admin_Notice_Handler( $this ); + } + + + /** + * Builds the hook deprecator instance. + * + * Plugins can override this with their own handler. + * + * @since 5.2.0 + */ + protected function init_hook_deprecator() { + + $this->hook_deprecator = new SV_WC_Hook_Deprecator( $this->get_plugin_name(), array_merge( $this->get_framework_deprecated_hooks(), $this->get_deprecated_hooks() ) ); + } + + + /** + * Builds the lifecycle handler instance. + * + * Plugins can override this with their own handler to perform install and + * upgrade routines. + * + * @since 5.2.0 + */ + protected function init_lifecycle_handler() { + + $this->lifecycle_handler = new Plugin\Lifecycle( $this ); + } + + + /** + * Builds the REST API handler instance. + * + * Plugins can override this to add their own data and/or routes. + * + * @since 5.2.0 + */ + protected function init_rest_api_handler() { + + $this->rest_api_handler = new REST_API( $this ); + } + + + /** + * Builds the Setup Wizard handler instance. + * + * Plugins can override and extend this method to add their own setup wizard. + * + * @since 5.3.0 + */ + protected function init_setup_wizard_handler() { + + require_once( $this->get_framework_path() . '/admin/abstract-sv-wc-plugin-admin-setup-wizard.php' ); + } + + + /** + * Adds the action & filter hooks. + * + * @since 5.2.0 + */ + private function add_hooks() { + + // initialize the plugin + add_action( 'plugins_loaded', array( $this, 'init_plugin' ), 15 ); + + // initialize the plugin admin + add_action( 'admin_init', array( $this, 'init_admin' ), 0 ); + + // hook for translations separately to ensure they're loaded + add_action( 'init', array( $this, 'load_translations' ) ); + + // handle HPOS compatibility + add_action( 'before_woocommerce_init', [ $this, 'handle_hpos_compatibility' ] ); + + // add the admin notices + add_action( 'admin_notices', array( $this, 'add_admin_notices' ) ); + add_action( 'admin_footer', array( $this, 'add_delayed_admin_notices' ) ); + + // add a 'Configure' link to the plugin action links + add_filter( 'plugin_action_links_' . plugin_basename( $this->get_plugin_file() ), array( $this, 'plugin_action_links' ) ); + + // automatically log HTTP requests from SV_WC_API_Base + $this->add_api_request_logging(); + + // add any PHP incompatibilities to the system status report + add_filter( 'woocommerce_system_status_environment_rows', array( $this, 'add_system_status_php_information' ) ); + } + + + /** + * Cloning instances is forbidden due to singleton pattern. + * + * @since 3.1.0 + */ + public function __clone() { + /* translators: Placeholders: %s - plugin name */ + _doing_it_wrong( __FUNCTION__, sprintf( esc_html__( 'You cannot clone instances of %s.', 'woocommerce-plugin-framework' ), esc_html( $this->get_plugin_name() ) ), '3.1.0' ); + } + + + /** + * Unserializing instances is forbidden due to singleton pattern. + * + * @since 3.1.0 + */ + public function __wakeup() { + /* translators: Placeholders: %s - plugin name */ + _doing_it_wrong( __FUNCTION__, sprintf( esc_html__( 'You cannot unserialize instances of %s.', 'woocommerce-plugin-framework' ), esc_html( $this->get_plugin_name() ) ), '3.1.0' ); + } + + + /** + * Load plugin & framework text domains. + * + * @internal + * + * @since 4.2.0 + */ + public function load_translations() { + + $this->load_framework_textdomain(); + + // if this plugin passes along its text domain, load its translation files + if ( $this->text_domain ) { + $this->load_plugin_textdomain(); + } + } + + + /** + * Loads the framework textdomain. + * + * @since 4.5.0 + */ + protected function load_framework_textdomain() { + $this->load_textdomain( 'woocommerce-plugin-framework', dirname( plugin_basename( $this->get_framework_file() ) ) ); + } + + + /** + * Loads the plugin textdomain. + * + * @since 4.5.0 + */ + protected function load_plugin_textdomain() { + $this->load_textdomain( $this->text_domain, dirname( plugin_basename( $this->get_plugin_file() ) ) ); + } + + + /** + * Loads the plugin textdomain. + * + * @since 4.5.0 + * @param string $textdomain the plugin textdomain + * @param string $path the i18n path + */ + protected function load_textdomain( $textdomain, $path ) { + + // user's locale if in the admin for WP 4.7+, or the site locale otherwise + $locale = is_admin() && is_callable( 'get_user_locale' ) ? get_user_locale() : get_locale(); + + $locale = apply_filters( 'plugin_locale', $locale, $textdomain ); + + load_textdomain( $textdomain, WP_LANG_DIR . '/' . $textdomain . '/' . $textdomain . '-' . $locale . '.mo' ); + + load_plugin_textdomain( $textdomain, false, untrailingslashit( $path ) . '/i18n/languages' ); + } + + + /** + * Initializes the plugin. + * + * Plugins can override this to set up any handlers after WordPress is ready. + * + * @since 5.2.0 + */ + public function init_plugin() { + + // stub + } + + + /** + * Initializes the plugin admin. + * + * Plugins can override this to set up any handlers after the WordPress admin is ready. + * + * @since 5.2.0 + */ + public function init_admin() { + + // stub + } + + + /** + * Include any critical files which must be available as early as possible, + * + * @since 2.0.0 + */ + private function includes() { + + $framework_path = $this->get_framework_path(); + + // common exception class + require_once( $framework_path . '/class-sv-wc-plugin-exception.php' ); + + // addresses + require_once( $framework_path . '/Addresses/Address.php' ); + require_once( $framework_path . '/Addresses/Customer_Address.php' ); + + // Settings API + require_once( $framework_path . '/Settings_API/Abstract_Settings.php' ); + require_once( $framework_path . '/Settings_API/Setting.php' ); + require_once( $framework_path . '/Settings_API/Control.php' ); + + // common utility methods + require_once( $framework_path . '/class-sv-wc-helper.php' ); + require_once( $framework_path . '/Country_Helper.php' ); + require_once( $framework_path . '/admin/Notes_Helper.php' ); + + // backwards compatibility for older WC versions + require_once( $framework_path . '/class-sv-wc-plugin-compatibility.php' ); + require_once( $framework_path . '/compatibility/abstract-sv-wc-data-compatibility.php' ); + require_once( $framework_path . '/compatibility/class-sv-wc-order-compatibility.php' ); + + // generic API base + require_once( $framework_path . '/api/class-sv-wc-api-exception.php' ); + require_once( $framework_path . '/api/class-sv-wc-api-base.php' ); + require_once( $framework_path . '/api/interface-sv-wc-api-request.php' ); + require_once( $framework_path . '/api/interface-sv-wc-api-response.php' ); + + // XML API base + require_once( $framework_path . '/api/abstract-sv-wc-api-xml-request.php' ); + require_once( $framework_path . '/api/abstract-sv-wc-api-xml-response.php' ); + + // JSON API base + require_once( $framework_path . '/api/abstract-sv-wc-api-json-request.php' ); + require_once( $framework_path . '/api/abstract-sv-wc-api-json-response.php' ); + + // Cacheable API + require_once( $framework_path . '/api/traits/Cacheable_Request_Trait.php' ); + require_once( $framework_path . '/api/Abstract_Cacheable_API_Base.php' ); + + // REST API Controllers + require_once( $framework_path . '/rest-api/Controllers/Settings.php' ); + + // Handlers + require_once( $framework_path . '/Handlers/Script_Handler.php' ); + require_once( $framework_path . '/class-sv-wc-plugin-dependencies.php' ); + require_once( $framework_path . '/class-sv-wc-hook-deprecator.php' ); + require_once( $framework_path . '/class-sv-wp-admin-message-handler.php' ); + require_once( $framework_path . '/class-sv-wc-admin-notice-handler.php' ); + require_once( $framework_path . '/Lifecycle.php' ); + require_once( $framework_path . '/rest-api/class-sv-wc-plugin-rest-api.php' ); + } + + + /** + * Gets a list of framework deprecated/removed hooks. + * + * @see SV_WC_Plugin::init_hook_deprecator() + * @see SV_WC_Plugin::get_deprecated_hooks() + * + * @since 5.8.0 + * + * @return array associative array + */ + private function get_framework_deprecated_hooks() { + + $plugin_id = $this->get_id(); + $deprecated_hooks = []; + $deprecated_filters = [ + /** @see SV_WC_Payment_Gateway_My_Payment_Methods handler - once migrated to WC core tokens UI, we removed these and have no replacement */ + // TODO: remove deprecated hooks handling by version 6.0.0 or by 2021-02-25 {FN 2020-02-25} + "wc_{$plugin_id}_my_payment_methods_table_html", + "wc_{$plugin_id}_my_payment_methods_table_head_html", + "wc_{$plugin_id}_my_payment_methods_table_title", + "wc_{$plugin_id}_my_payment_methods_table_title_html", + "wc_{$plugin_id}_my_payment_methods_table_row_html", + "wc_{$plugin_id}_my_payment_methods_table_body_html", + "wc_{$plugin_id}_my_payment_methods_table_body_row_data", + "wc_{$plugin_id}_my_payment_methods_table_method_expiry_html", + "wc_{$plugin_id}_my_payment_methods_table_actions_html", + ]; + + foreach ( $deprecated_filters as $deprecated_filter ) { + $deprecated_hooks[ $deprecated_filter ] = [ + 'removed' => true, + 'replacement' => false, + 'version' => '5.8.1' + ]; + } + + return $deprecated_hooks; + } + + + /** + * Gets a list of the plugin's deprecated/removed hooks. + * + * Implementing classes should override this and return an array of deprecated/removed hooks in the following format: + * + * $old_hook_name = array { + * @type string $version version the hook was deprecated/removed in + * @type bool $removed if present and true, the message will indicate the hook was removed instead of deprecated + * @type string|bool $replacement if present and a string, the message will indicate the replacement hook to use, + * otherwise (if bool and false) the message will indicate there is no replacement available. + * } + * + * @since 4.3.0 + * + * @return array + */ + protected function get_deprecated_hooks() { + + // stub method + return []; + } + + + /** Admin methods ******************************************************/ + + + /** + * Returns true if on the admin plugin settings page, if any + * + * @since 2.0.0 + * @return boolean true if on the admin plugin settings page + */ + public function is_plugin_settings() { + // optional method, not all plugins *have* a settings page + return false; + } + + + /** + * Adds admin notices upon initialization. + * + * @internal + * + * @since 3.0.0 + */ + public function add_admin_notices() { + // stub method + } + + + /** + * Convenience method to add delayed admin notices, which may depend upon + * some setting being saved prior to determining whether to render + * + * @since 3.0.0 + */ + public function add_delayed_admin_notices() { + // stub method + } + + + /** + * Return the plugin action links. This will only be called if the plugin + * is active. + * + * @since 2.0.0 + * @param array $actions associative array of action names to anchor tags + * @return array associative array of plugin action links + */ + public function plugin_action_links( $actions ) { + + $custom_actions = array(); + + // settings url(s) + if ( $this->get_settings_link( $this->get_id() ) ) { + $custom_actions['configure'] = $this->get_settings_link( $this->get_id() ); + } + + // documentation url if any + if ( $this->get_documentation_url() ) { + /* translators: Docs as in Documentation */ + $custom_actions['docs'] = sprintf( '%s', $this->get_documentation_url(), esc_html__( 'Docs', 'woocommerce-plugin-framework' ) ); + } + + // support url if any + if ( $this->get_support_url() ) { + $custom_actions['support'] = sprintf( '%s', $this->get_support_url(), esc_html_x( 'Support', 'noun', 'woocommerce-plugin-framework' ) ); + } + + // review url if any + if ( $this->get_reviews_url() ) { + $custom_actions['review'] = sprintf( '%s', $this->get_reviews_url(), esc_html_x( 'Review', 'verb', 'woocommerce-plugin-framework' ) ); + } + + // add the links to the front of the actions list + return array_merge( $custom_actions, $actions ); + } + + + /** + * Declares HPOS compatibility if the plugin is compatible with HPOS. + * + * @internal + * + * @since 5.11.0 + */ + public function handle_hpos_compatibility() { + + if ( class_exists( \Automattic\WooCommerce\Utilities\FeaturesUtil::class ) ) { + \Automattic\WooCommerce\Utilities\FeaturesUtil::declare_compatibility( 'custom_order_tables', $this->get_plugin_file(), $this->is_hpos_compatible() ); + } + } + + + /** Helper methods ******************************************************/ + + + /** + * Automatically log API requests/responses when using SV_WC_API_Base + * + * @since 2.2.0 + * @see SV_WC_API_Base::broadcast_request() + */ + public function add_api_request_logging() { + + if ( ! has_action( 'wc_' . $this->get_id() . '_api_request_performed' ) ) { + add_action( 'wc_' . $this->get_id() . '_api_request_performed', array( $this, 'log_api_request' ), 10, 2 ); + } + } + + + /** + * Log API requests/responses + * + * @since 2.2.0 + * @param array $request request data, see SV_WC_API_Base::broadcast_request() for format + * @param array $response response data + * @param string|null $log_id log to write data to + */ + public function log_api_request( $request, $response, $log_id = null ) { + + $this->log( "Request\n" . $this->get_api_log_message( $request ), $log_id ); + + if ( ! empty( $response ) ) { + $this->log( "Response\n" . $this->get_api_log_message( $response ), $log_id ); + } + } + + + /** + * Transform the API request/response data into a string suitable for logging + * + * @since 2.2.0 + * @param array $data + * @return string + */ + public function get_api_log_message( $data ) { + + $messages = array(); + + $messages[] = isset( $data['uri'] ) && $data['uri'] ? 'Request' : 'Response'; + + foreach ( (array) $data as $key => $value ) { + + if ( is_array( $value ) || ( is_object( $value ) && 'stdClass' === get_class( $value ) ) ) { + $value = print_r( (array) $value, true ); + } elseif ( is_bool( $value ) ) { + $value = wc_bool_to_string( $value ); + } + + $messages[] = trim( sprintf( '%s: %s', $key, $value ) ); + } + + return implode( "\n", $messages ) . "\n"; + } + + + /** + * Adds any PHP incompatibilities to the system status report. + * + * @since 4.5.0 + * + * @param array $rows WooCommerce system status rows + * @return array + */ + public function add_system_status_php_information( $rows ) { + + foreach ( $this->get_dependency_handler()->get_incompatible_php_settings() as $setting => $values ) { + + if ( isset( $values['type'] ) && 'min' === $values['type'] ) { + + // if this setting already has a higher minimum from another plugin, skip it + if ( isset( $rows[ $setting ]['expected'] ) && $values['expected'] < $rows[ $setting ]['expected'] ) { + continue; + } + + $note = __( '%1$s - A minimum of %2$s is required.', 'woocommerce-plugin-framework' ); + + } else { + + // if this requirement is already listed, skip it + if ( isset( $rows[ $setting ] ) ) { + continue; + } + + $note = __( 'Set as %1$s - %2$s is required.', 'woocommerce-plugin-framework' ); + } + + $note = sprintf( $note, $values['actual'], $values['expected'] ); + + $rows[ $setting ] = array( + 'name' => $setting, + 'note' => $note, + 'success' => false, + 'expected' => $values['expected'], // WC doesn't use this, but it's useful for us + ); + } + + return $rows; + } + + + /** + * Saves errors or messages to WooCommerce Log (woocommerce/logs/plugin-id-xxx.txt) + * + * @since 2.0.0 + * @param string $message error or message to save to log + * @param string $log_id optional log id to segment the files by, defaults to plugin id + */ + public function log( $message, $log_id = null ) { + + if ( is_null( $log_id ) ) { + $log_id = $this->get_id(); + } + + if ( ! is_object( $this->logger ) ) { + $this->logger = new \WC_Logger(); + } + + $this->logger->add( $log_id, $message ); + } + + + /** + * Require and instantiate a class + * + * @since 4.2.0 + * @param string $local_path path to class file in plugin, e.g. '/includes/class-wc-foo.php' + * @param string $class_name class to instantiate + * @return object instantiated class instance + */ + public function load_class( $local_path, $class_name ) { + + require_once( $this->get_plugin_path() . $local_path ); + + return new $class_name; + } + + + /** + * Determines if TLS v1.2 is required for API requests. + * + * Subclasses should override this to return true if TLS v1.2 is required. + * + * @since 5.5.2 + * + * @return bool + */ + public function require_tls_1_2() { + + return false; + } + + + /** + * Determines if TLS 1.2 is available. + * + * @since 5.5.2 + * + * @return bool + */ + public function is_tls_1_2_available() { + + // assume availability to avoid notices for unknown SSL types + $is_available = true; + + // check the cURL version if installed + if ( is_callable( 'curl_version' ) ) { + + $versions = curl_version(); + + // cURL 7.34.0 is considered the minimum version that supports TLS 1.2 + if ( version_compare( $versions['version'], '7.34.0', '<' ) ) { + $is_available = false; + } + } + + return $is_available; + } + + + /** + * Determines whether the plugin supports HPOS. + * + * @since 5.11.0 + * + * @return bool + */ + public function is_hpos_compatible() : bool + { + return $this->supports_hpos && SV_WC_Plugin_Compatibility::is_wc_version_gte('7.6'); + } + + + /** Getter methods ******************************************************/ + + + /** + * Gets the main plugin file. + * + * @since 5.0.0 + * + * @return string + */ + public function get_plugin_file() { + + $slug = dirname( plugin_basename( $this->get_file() ) ); + + return trailingslashit( $slug ) . $slug . '.php'; + } + + + /** + * The implementation for this abstract method should simply be: + * + * return __FILE__; + * + * @since 2.0.0 + * @return string the full path and filename of the plugin file + */ + abstract protected function get_file(); + + + /** + * Returns the plugin id + * + * @since 2.0.0 + * @return string plugin id + */ + public function get_id() { + return $this->id; + } + + + /** + * Returns the plugin id with dashes in place of underscores, and + * appropriate for use in frontend element names, classes and ids + * + * @since 2.0.0 + * @return string plugin id with dashes in place of underscores + */ + public function get_id_dasherized() { + return str_replace( '_', '-', $this->get_id() ); + } + + + /** + * Returns the plugin full name including "WooCommerce", ie + * "WooCommerce X". This method is defined abstract for localization purposes + * + * @since 2.0.0 + * @return string plugin name + */ + abstract public function get_plugin_name(); + + + /** Handler methods *******************************************************/ + + + /** + * Gets the dependency handler. + * + * @since 5.2.0.1 + * + * @return SV_WC_Plugin_Dependencies + */ + public function get_dependency_handler() { + + return $this->dependency_handler; + } + + + /** + * Gets the lifecycle handler instance. + * + * @since 5.1.0 + * + * @return Plugin\Lifecycle + */ + public function get_lifecycle_handler() { + + return $this->lifecycle_handler; + } + + + /** + * Gets the Setup Wizard handler instance. + * + * @since 5.3.0 + * + * @return null|Admin\Setup_Wizard + */ + public function get_setup_wizard_handler() { + + return $this->setup_wizard_handler; + } + + + /** + * Gets the admin message handler. + * + * @since 2.0.0 + * + * @return SV_WP_Admin_Message_Handler + */ + public function get_message_handler() { + + return $this->message_handler; + } + + + /** + * Gets the admin notice handler instance. + * + * @since 3.0.0 + * + * @return SV_WC_Admin_Notice_Handler + */ + public function get_admin_notice_handler() { + + return $this->admin_notice_handler; + } + + + /** + * Gets the settings API handler instance. + * + * Plugins can use this to init the settings API handler. + * + * @since 5.7.0 + * + * @return void|Settings_API\Abstract_Settings + */ + public function get_settings_handler() { + + return; + } + + + /** + * Returns the plugin version name. Defaults to wc_{plugin id}_version + * + * @since 2.0.0 + * @return string the plugin version name + */ + public function get_plugin_version_name() { + + return 'wc_' . $this->get_id() . '_version'; + } + + + /** + * Returns the current version of the plugin + * + * @since 2.0.0 + * @return string plugin version + */ + public function get_version() { + return $this->version; + } + + + /** + * Returns the "Configure" plugin action link to go directly to the plugin + * settings page (if any) + * + * @since 2.0.0 + * @see SV_WC_Plugin::get_settings_url() + * @param string $plugin_id optional plugin identifier. Note that this can be a + * sub-identifier for plugins with multiple parallel settings pages + * (ie a gateway that supports both credit cards and echecks) + * @return string plugin configure link + */ + public function get_settings_link( $plugin_id = null ) { + + $settings_url = $this->get_settings_url( $plugin_id ); + + if ( $settings_url ) { + return sprintf( '%s', $settings_url, esc_html__( 'Configure', 'woocommerce-plugin-framework' ) ); + } + + // no settings + return ''; + } + + + /** + * Gets the plugin configuration URL + * + * @since 2.0.0 + * @see SV_WC_Plugin::get_settings_link() + * @param string $plugin_id optional plugin identifier. Note that this can be a + * sub-identifier for plugins with multiple parallel settings pages + * (ie a gateway that supports both credit cards and echecks) + * @return string plugin settings URL + */ + public function get_settings_url( $plugin_id = null ) { + + // stub method + return ''; + } + + + /** + * Returns true if the current page is the admin general configuration page + * + * @since 3.0.0 + * @return boolean true if the current page is the admin general configuration page + */ + public function is_general_configuration_page() { + + return isset( $_GET['page'] ) && 'wc-settings' === $_GET['page'] && ( ! isset( $_GET['tab'] ) || 'general' === $_GET['tab'] ); + } + + + /** + * Returns the admin configuration url for the admin general configuration page + * + * @since 3.0.0 + * @return string admin configuration url for the admin general configuration page + */ + public function get_general_configuration_url() { + + return admin_url( 'admin.php?page=wc-settings&tab=general' ); + } + + + /** + * Gets the plugin documentation url, used for the 'Docs' plugin action + * + * @since 2.0.0 + * @return string documentation URL + */ + public function get_documentation_url() { + + return null; + } + + + /** + * Gets the support URL, used for the 'Support' plugin action link + * + * @since 4.0.0 + * @return string support url + */ + public function get_support_url() { + + return null; + } + + + /** + * Gets the plugin sales page URL. + * + * @since 5.1.0 + * + * @return string + */ + public function get_sales_page_url() { + + return ''; + } + + + /** + * Gets the plugin reviews page URL. + * + * Used for the 'Reviews' plugin action and review prompts. + * + * @since 5.1.0 + * + * @return string + */ + public function get_reviews_url() { + + return $this->get_sales_page_url() ? $this->get_sales_page_url() . '#comments' : ''; + } + + + /** + * Gets the plugin's path without a trailing slash. + * + * e.g. /path/to/wp-content/plugins/plugin-directory + * + * @since 2.0.0 + * + * @return string + */ + public function get_plugin_path() { + + if ( null === $this->plugin_path ) { + $this->plugin_path = untrailingslashit( plugin_dir_path( $this->get_file() ) ); + } + + return $this->plugin_path; + } + + + /** + * Gets the plugin's URL without a trailing slash. + * + * E.g. http://skyverge.com/wp-content/plugins/plugin-directory + * + * @since 2.0.0 + * + * @return string + */ + public function get_plugin_url() { + + if ( null === $this->plugin_url ) { + $this->plugin_url = untrailingslashit( plugins_url( '/', $this->get_file() ) ); + } + + return $this->plugin_url; + } + + + /** + * Gets the woocommerce uploads path, without trailing slash. + * + * Oddly WooCommerce core does not provide a way to get this. + * + * @since 2.0.0 + * + * @return string + */ + public static function get_woocommerce_uploads_path() { + + $upload_dir = wp_upload_dir(); + + return $upload_dir['basedir'] . '/woocommerce_uploads'; + } + + + /** + * Returns the loaded framework __FILE__ + * + * @since 4.0.0 + * @return string + */ + public function get_framework_file() { + + return __FILE__; + } + + + /** + * Gets the loaded framework path, without trailing slash. + * + * This matches the path to the highest version of the framework currently loaded. + * + * @since 4.0.0 + * @return string + */ + public function get_framework_path() { + + return untrailingslashit( plugin_dir_path( $this->get_framework_file() ) ); + } + + + /** + * Gets the absolute path to the loaded framework image directory, without a trailing slash. + * + * @since 4.0.0 + * + * @return string + */ + public function get_framework_assets_path() { + + return $this->get_framework_path() . '/assets'; + } + + + /** + * Gets the loaded framework assets URL without a trailing slash. + * + * @since 4.0.0 + * + * @return string + */ + public function get_framework_assets_url() { + + return untrailingslashit( plugins_url( '/assets', $this->get_framework_file() ) ); + } + + + /** + * Gets the plugin default template path, without a trailing slash. + * + * @since 5.5.0 + * + * @return string + */ + public function get_template_path() { + + if ( null === $this->template_path ) { + $this->template_path = $this->get_plugin_path() . '/templates'; + } + + return $this->template_path; + } + + + /** + * Loads and outputs a template file HTML. + * + * @see \wc_get_template() except we define automatically the default path + * + * @since 5.5.0 + * + * @param string $template template name/part + * @param array $args associative array of optional template arguments + * @param string $path optional template path, can be empty, as themes can override this + * @param string $default_path optional default template path, will normally use the plugin's own template path unless overridden + */ + public function load_template( $template, array $args = [], $path = '', $default_path = '' ) { + + if ( '' === $default_path || ! is_string( $default_path ) ) { + $default_path = trailingslashit( $this->get_template_path() ); + } + + wc_get_template( $template, $args, $path, $default_path ); + } + + + /** + * Determines whether a plugin is active. + * + * @since 2.0.0 + * + * @param string $plugin_name plugin name, as the plugin-filename.php + * @return boolean true if the named plugin is installed and active + */ + public function is_plugin_active( $plugin_name ) { + + $is_active = false; + + if ( is_string( $plugin_name ) ) { + + if ( ! array_key_exists( $plugin_name, $this->active_plugins ) ) { + + $active_plugins = (array) get_option( 'active_plugins', array() ); + + if ( is_multisite() ) { + $active_plugins = array_merge( $active_plugins, array_keys( get_site_option( 'active_sitewide_plugins', array() ) ) ); + } + + $plugin_filenames = array(); + + foreach ( $active_plugins as $plugin ) { + + if ( SV_WC_Helper::str_exists( $plugin, '/' ) ) { + + // normal plugin name (plugin-dir/plugin-filename.php) + list( , $filename ) = explode( '/', $plugin ); + + } else { + + // no directory, just plugin file + $filename = $plugin; + } + + $plugin_filenames[] = $filename; + } + + $this->active_plugins[ $plugin_name ] = in_array( $plugin_name, $plugin_filenames, true ); + } + + $is_active = (bool) $this->active_plugins[ $plugin_name ]; + } + + return $is_active; + } + + +} + + +endif; diff --git a/vendor/skyverge/wc-plugin-framework/woocommerce/class-sv-wp-admin-message-handler.php b/vendor/skyverge/wc-plugin-framework/woocommerce/class-sv-wp-admin-message-handler.php new file mode 100644 index 0000000..5d6a42f --- /dev/null +++ b/vendor/skyverge/wc-plugin-framework/woocommerce/class-sv-wp-admin-message-handler.php @@ -0,0 +1,438 @@ +add_message( 'Hello World!' ); + * ` + * + * Then show the messages wherever you need, either with the built-in method + * or by writing your own: + * + * `$admin_message_handler->show_messages();` + * + * @version 1.0.1 + */ +class SV_WP_Admin_Message_Handler { + + + /** transient message prefix */ + const MESSAGE_TRANSIENT_PREFIX = '_wp_admin_message_'; + + /** the message id GET name */ + const MESSAGE_ID_GET_NAME = 'wpamhid'; + + + /** @var string unique message identifier, defaults to __FILE__ unless otherwise set */ + private $message_id; + + /** @var array array of messages */ + private $messages = array(); + + /** @var array array of error messages */ + private $errors = array(); + + /** @var array array of warning messages */ + private $warnings = array(); + + /** @var array array of info messages */ + private $infos = array(); + + + /** + * Construct and initialize the admin message handler class + * + * @since 1.0.0 + * @param string $message_id optional message id. Best practice is to set + * this to a unique identifier based on the client plugin, such as __FILE__ + */ + public function __construct( $message_id = null ) { + + $this->message_id = $message_id; + + // load any available messages + $this->load_messages(); + + add_filter( 'wp_redirect', array( $this, 'redirect' ), 1, 2 ); + } + + + /** + * Persist messages + * + * @since 1.0.0 + * @return boolean true if any messages were set, false otherwise + */ + public function set_messages() { + + // any messages to persist? + if ( $this->message_count() > 0 || $this->info_count() > 0 || $this->warning_count() > 0 || $this->error_count() > 0 ) { + + set_transient( + self::MESSAGE_TRANSIENT_PREFIX . $this->get_message_id(), + array( + 'errors' => $this->errors, + 'warnings' => $this->warnings, + 'infos' => $this->infos, + 'messages' => $this->messages, + ), + 60 * 60 + ); + + return true; + } + + return false; + } + + + /** + * Loads messages + * + * @since 1.0.0 + */ + public function load_messages() { + + if ( isset( $_GET[ self::MESSAGE_ID_GET_NAME ] ) && $this->get_message_id() == $_GET[ self::MESSAGE_ID_GET_NAME ] ) { + + $memo = get_transient( self::MESSAGE_TRANSIENT_PREFIX . $_GET[ self::MESSAGE_ID_GET_NAME ] ); + + if ( isset( $memo['errors'] ) ) $this->errors = $memo['errors']; + if ( isset( $memo['warnings'] ) ) $this->warnings = $memo['warnings']; + if ( isset( $memo['infos'] ) ) $this->infos = $memo['infos']; + if ( isset( $memo['messages'] ) ) $this->messages = $memo['messages']; + + $this->clear_messages( $_GET[ self::MESSAGE_ID_GET_NAME ] ); + } + } + + + /** + * Clear messages and errors + * + * @since 1.0.0 + * @param string $id the messages identifier + */ + public function clear_messages( $id ) { + delete_transient( self::MESSAGE_TRANSIENT_PREFIX . $id ); + } + + + /** + * Add an error message. + * + * @since 1.0.0 + * @param string $error error message + */ + public function add_error( $error ) { + $this->errors[] = $error; + } + + + /** + * Adds a warning message. + * + * @since 5.1.0 + * + * @param string $message warning message to add + */ + public function add_warning( $message ) { + $this->warnings[] = $message; + } + + + /** + * Adds a info message. + * + * @since 5.1.0 + * + * @param string $message info message to add + */ + public function add_info( $message ) { + $this->infos[] = $message; + } + + + /** + * Add a message. + * + * @since 1.0.0 + * @param string $message the message to add + */ + public function add_message( $message ) { + $this->messages[] = $message; + } + + + /** + * Get error count. + * + * @since 1.0.0 + * @return int error message count + */ + public function error_count() { + return sizeof( $this->errors ); + } + + + /** + * Gets the warning message count. + * + * @since 5.1.0 + * + * @return int warning message count + */ + public function warning_count() { + return sizeof( $this->warnings ); + } + + + /** + * Gets the info message count. + * + * @since 5.1.0 + * + * @return int info message count + */ + public function info_count() { + return sizeof( $this->infos ); + } + + + /** + * Get message count. + * + * @since 1.0.0 + * @return int message count + */ + public function message_count() { + return sizeof( $this->messages ); + } + + + /** + * Get error messages + * + * @since 1.0.0 + * @return array of error message strings + */ + public function get_errors() { + return $this->errors; + } + + + /** + * Get an error message + * + * @since 1.0.0 + * @param int $index the error index + * @return string the error message + */ + public function get_error( $index ) { + return isset( $this->errors[ $index ] ) ? $this->errors[ $index ] : ''; + } + + + /** + * Gets all warning messages. + * + * @since 5.1.0 + * + * @return array + */ + public function get_warnings() { + return $this->warnings; + } + + + /** + * Gets a specific warning message. + * + * @since 5.1.0 + * + * @param int $index warning message index + * @return string + */ + public function get_warning( $index ) { + return isset( $this->warnings[ $index ] ) ? $this->warnings[ $index ] : ''; + } + + + /** + * Gets all info messages. + * + * @since 5.1.0 + * + * @return array + */ + public function get_infos() { + return $this->infos; + } + + + /** + * Gets a specific info message. + * + * @since 5.0.0 + * + * @param int $index info message index + * @return string + */ + public function get_info( $index ) { + return isset( $this->infos[ $index ] ) ? $this->infos[ $index ] : ''; + } + + + /** + * Get messages + * + * @since 1.0.0 + * @return array of message strings + */ + public function get_messages() { + return $this->messages; + } + + + /** + * Get a message + * + * @since 1.0.0 + * @param int $index the message index + * @return string the message + */ + public function get_message( $index ) { + return isset( $this->messages[ $index ] ) ? $this->messages[ $index ] : ''; + } + + + /** + * Render the errors and messages. + * + * @since 1.0.0 + * @param array $params { + * Optional parameters. + * + * @type array $capabilities Any user capabilities to check if the user is allowed to view the messages, + * default: `manage_woocommerce` + * } + */ + public function show_messages( $params = array() ) { + + $params = wp_parse_args( $params, array( + 'capabilities' => array( + 'manage_woocommerce', + ), + ) ); + + $check_user_capabilities = array(); + + // check if user has at least one capability that allows to see messages + foreach ( $params['capabilities'] as $capability ) { + $check_user_capabilities[] = current_user_can( $capability ); + } + + // bail out if user has no minimum capabilities to see messages + if ( ! in_array( true, $check_user_capabilities, true ) ) { + return; + } + + $output = ''; + + if ( $this->error_count() > 0 ) { + $output .= '
    • ' . implode( '
    • ', $this->get_errors() ) . '
    '; + } + + if ( $this->warning_count() > 0 ) { + $output .= '
    • ' . implode( '
    • ', $this->get_warnings() ) . '
    '; + } + + if ( $this->info_count() > 0 ) { + $output .= '
    • ' . implode( '
    • ', $this->get_infos() ) . '
    '; + } + + if ( $this->message_count() > 0 ) { + $output .= '
    • ' . implode( '
    • ', $this->get_messages() ) . '
    '; + } + + echo wp_kses_post( $output ); + } + + + /** + * Redirection hook which persists messages into session data. + * + * @since 1.0.0 + * @param string $location the URL to redirect to + * @param int $status the http status + * @return string the URL to redirect to + */ + public function redirect( $location, $status ) { + + // add the admin message id param to the + if ( $this->set_messages() ) { + $location = add_query_arg( self::MESSAGE_ID_GET_NAME, $this->get_message_id(), $location ); + } + + return $location; + } + + + /** + * Generate a unique id to identify the messages + * + * @since 1.0.0 + * @return string unique identifier + */ + protected function get_message_id() { + + if ( ! isset( $this->message_id ) ) $this->message_id = __FILE__; + + return wp_create_nonce( $this->message_id ); + + } + + +} + + +endif; diff --git a/vendor/skyverge/wc-plugin-framework/woocommerce/compatibility/abstract-sv-wc-data-compatibility.php b/vendor/skyverge/wc-plugin-framework/woocommerce/compatibility/abstract-sv-wc-data-compatibility.php new file mode 100644 index 0000000..bf1641f --- /dev/null +++ b/vendor/skyverge/wc-plugin-framework/woocommerce/compatibility/abstract-sv-wc-data-compatibility.php @@ -0,0 +1,43 @@ +get_formatted_meta_data( $hide_prefix, $include_all ); + $item_meta = []; + + foreach ( $meta_data as $meta ) { + + $item_meta[] = array( + 'label' => $meta->display_key, + 'value' => $meta->value, + ); + } + + } else { + + $item_meta = new \WC_Order_Item_Meta( $item ); + $item_meta = $item_meta->get_formatted( $hide_prefix ); + } + + return $item_meta; + } + + + /** + * Gets the orders screen admin URL according to HPOS availability. + * + * @since 5.11.0 + * + * @return string + */ + public static function get_orders_screen_url() : string { + + if ( SV_WC_Plugin_Compatibility::is_hpos_enabled() ) { + return admin_url( 'admin.php?page=wc-orders' ); + } + + return admin_url( 'edit.php?post_type=shop_order' ); + } + + + /** + * Gets the admin Edit screen URL for an order according to HPOS compatibility. + * + * @see OrderUtil::get_order_admin_edit_url() + * @see PageController::get_edit_url() + * + * @since 5.0.1 + * + * @param \WC_Order|int $order order object or ID + * @return string + */ + public static function get_edit_order_url( $order ) : string { + + $order_id = $order instanceof \WC_Order ? $order->get_id() : $order; + $order_id = max((int) $order_id, 0); + + if ( SV_WC_Plugin_Compatibility::is_wc_version_gte( '3.3' ) ) { + $order_url = OrderUtil::get_order_admin_edit_url( $order_id ); + } else { + $order_url = apply_filters( 'woocommerce_get_edit_order_url', admin_url( 'post.php?post=' . absint( $order_id ) ) . '&action=edit', $order ); + } + + return $order_url; + } + + + /** + * Determines if the current admin screen is for the orders. + * + * @since 5.11.0 + * + * @return bool + */ + public static function is_orders_screen() : bool { + + $current_screen = SV_WC_Helper::get_current_screen(); + + if ( ! $current_screen ) { + return false; + } + + if ( ! SV_WC_Plugin_Compatibility::is_hpos_enabled() ) { + return 'edit-shop_order' === $current_screen->id; + } + + return static::get_order_screen_id() === $current_screen->id + && isset( $_GET['page'] ) + && $_GET['page'] === 'wc-orders' + && ! static::is_order_edit_screen(); + } + + + /** + * Determines if the current orders screen is for orders of a specific status. + * + * @since 5.11.0 + * + * @param string|string[] $status one or more statuses to compare + * @return bool + */ + public static function is_orders_screen_for_status( $status ) : bool { + global $post_type, $post_status; + + if ( ! SV_WC_Plugin_Compatibility::is_hpos_enabled() ) { + + if ( 'shop_order' !== $post_type ) { + return false; + } + + return empty( $status ) || in_array( $post_status, (array) $status, true ); + } + + if ( ! static::is_orders_screen() ) { + return false; + } + + return empty( $status ) || ( isset( $_GET['status'] ) && in_array( $_GET['status'], (array) $status, true ) ); + } + + + /** + * Determines if the current admin screen is for adding or editing an order. + * + * @since 5.11.0 + * + * @return bool + */ + public static function is_order_edit_screen() : bool { + + $current_screen = SV_WC_Helper::get_current_screen(); + + if ( ! $current_screen ) { + return false; + } + + if ( ! SV_WC_Plugin_Compatibility::is_hpos_enabled() ) { + return 'shop_order' === $current_screen->id; + } + + return static::get_order_screen_id() === $current_screen->id + && isset( $_GET['page'], $_GET['action'] ) + && $_GET['page'] === 'wc-orders' + && in_array( $_GET['action'], [ 'new', 'edit' ], true ); + } + + + /** + * Determines if the current admin page is for any kind of order screen. + * + * @since 5.11.0 + * + * @return bool + */ + public static function is_order_screen() : bool { + + return static::is_orders_screen() + || static::is_order_edit_screen(); + } + + + /** + * Gets the ID of the order for the current edit screen. + * + * @since 5.11.0 + * + * @return int|null + */ + public static function get_order_id_for_order_edit_screen() : ?int { + global $post, $theorder; + + if ( SV_WC_Plugin_Compatibility::is_hpos_enabled() ) { + return $theorder instanceof \WC_Abstract_Order && ! $theorder instanceof \WC_Subscription && static::is_order_edit_screen() + ? $theorder->get_id() + : null; + } + + return $post->ID ?? null; + } + + + /** + * Gets the admin screen ID for orders. + * + * This method detects the expected orders screen ID according to HPOS availability. + * `shop_order` as a registered post type as the screen ID is no longer used when HPOS is active. + * + * @see OrderUtil::get_order_admin_screen() + * @see COTMigrationUtil::get_order_admin_screen() + * + * @since 5.11.0 + * + * @return string + */ + public static function get_order_screen_id() : string { + + if ( is_callable( OrderUtil::class . '::get_order_admin_screen' ) ) { + return OrderUtil::get_order_admin_screen(); + } elseif ( SV_WC_Plugin_Compatibility::is_hpos_enabled() ) { + return function_exists( 'wc_get_page_screen_id' ) ? wc_get_page_screen_id( 'shop-order' ) : false; + } + + return 'shop_order'; + } + + + /** + * Gets the orders table. + * + * @return string + */ + public static function get_orders_table() : string + { + global $wpdb; + + return SV_WC_Plugin_Compatibility::is_hpos_enabled() + ? OrdersTableDataStore::get_orders_table_name() + : $wpdb->posts; + } + + + /** + * Gets the orders meta table. + * + * @return string + */ + public static function get_orders_meta_table() : string + { + global $wpdb; + + return SV_WC_Plugin_Compatibility::is_hpos_enabled() + ? OrdersTableDataStore::get_meta_table_name() + : $wpdb->postmeta; + } + + + /** + * Determines whether a given identifier is a WooCommerce order or not, according to HPOS availability. + * + * @see OrderUtil::get_order_type() + * + * @since 5.11.0 + * + * @param int|\WP_Post|\WC_Order|null $post_order_or_id identifier of a possible order + * @param string|string[] $order_type the order type, defaults to shop_order, can specify multiple types + * @return bool + */ + public static function is_order( $post_order_or_id, $order_type = 'shop_order' ) : bool { + + if ( ! $post_order_or_id ) { + return false; + } + + if ( $post_order_or_id instanceof \WC_Subscription ) { + + return false; + + } elseif ( $post_order_or_id instanceof \WC_Abstract_Order ) { + + $found_type = $post_order_or_id->get_type(); + + } elseif ( ! SV_WC_Plugin_Compatibility::is_hpos_enabled() ) { + + $found_type = is_numeric( $post_order_or_id ) || $post_order_or_id instanceof \WP_Post ? get_post_type( $post_order_or_id ) : null; + + } else { + + $found_type = OrderUtil::get_order_type( $post_order_or_id ); + } + + return $found_type && in_array( $found_type, (array) $order_type, true ); + } + + + /** + * Determines whether a given identifier is a WooCommerce refund or not, according to HPOS availability. + * + * @since 5.11.0 + * + * @param int|\WP_Post|\WC_Order|null $order_post_or_id identifier of a possible order + * @return bool + */ + public static function is_refund( $order_post_or_id ) : bool { + + return static::is_order( $order_post_or_id, 'shop_order_refund' ); + } + + + /** + * Gets the order meta according to HPOS availability. + * + * Uses {@see \WC_Order::get_meta()} if HPOS is enabled, otherwise it uses the WordPress {@see get_post_meta()} function. + * + * @since 5.11.0 + * + * @param int|\WC_Order $order order ID or object + * @param string $meta_key meta key + * @param bool $single return the first found meta with key (true), or all meta sharing the same key (default true) + * @return mixed + */ + public static function get_order_meta( $order, string $meta_key, bool $single = true ) { + + if ( SV_WC_Plugin_Compatibility::is_hpos_enabled() ) { + + $value = $single ? '' : []; + $order = is_numeric( $order ) && $order > 0 ? wc_get_order( (int) $order ) : $order; + + if ( $order instanceof \WC_Order ) { + $value = $order->get_meta( $meta_key, $single ); + } + + } else { + + $order_id = $order instanceof \WC_Order ? $order->get_id() : $order; + + $value = is_numeric( $order_id ) && $order_id > 0 ? get_post_meta( (int) $order_id, $meta_key, $single ) : false; + } + + return $value; + } + + + /** + * Updates the order meta according to HPOS availability. + * + * Uses {@see \WC_Order::update_meta_data()} if HPOS is enabled, otherwise it uses the WordPress {@see update_meta_data()} function. + * + * @since 5.11.0 + * + * @param int|\WC_Order $order order ID or object + * @param string $meta_key meta key + * @param mixed $meta_value meta value + */ + public static function update_order_meta( $order, string $meta_key, $meta_value ) { + + if ( SV_WC_Plugin_Compatibility::is_hpos_enabled() ) { + + $order = is_numeric( $order ) && $order > 0 ? wc_get_order( (int) $order ) : $order; + + if ( $order instanceof \WC_Order ) { + $order->update_meta_data( $meta_key, $meta_value ); + $order->save_meta_data(); + } + + } else { + + $order_id = $order instanceof \WC_Order ? $order->get_id() : $order; + + if ( is_numeric( $order_id ) && $order_id > 0 ) { + update_post_meta( (int) $order_id, $meta_key, $meta_value ); + } + } + } + + + /** + * Adds the order meta according to HPOS availability. + * + * Uses {@see \WC_Order::add_meta_data()} if HPOS is enabled, otherwise it uses the WordPress {@see add_meta_data()} function. + * + * @since 5.11.0 + * + * @param int|\WC_Order $order order ID or object + * @param string $meta_key meta key + * @param mixed $meta_value meta value + * @param bool $unique optional - whether the same key should not be added (default false) + */ + public static function add_order_meta( $order, string $meta_key, $meta_value, bool $unique = false ) { + + if ( SV_WC_Plugin_Compatibility::is_hpos_enabled() ) { + + $order = is_numeric( $order ) && $order > 0 ? wc_get_order( (int) $order ) : $order; + + if ( $order instanceof \WC_Order ) { + $order->add_meta_data( $meta_key, $meta_value, $unique ); + $order->save_meta_data(); + } + + } else { + + $order_id = $order instanceof \WC_Order ? $order->get_id() : $order; + + if ( is_numeric( $order_id ) && $order_id > 0 ) { + add_post_meta( (int) $order_id, $meta_key, $meta_value, $unique ); + } + } + } + + + /** + * Deletes the order meta according to HPOS availability. + * + * Uses {@see \WC_Order::delete_meta_data()} if HPOS is enabled, otherwise it uses the WordPress {@see delete_meta_data()} function. + * + * @since 5.11.0 + * + * @param int|\WC_Order $order order ID or object + * @param string $meta_key meta key + * @param mixed $meta_value optional (applicable if HPOS is inactive) + */ + public static function delete_order_meta( $order, string $meta_key, $meta_value = '' ) { + + if ( SV_WC_Plugin_Compatibility::is_hpos_enabled() ) { + + $order = is_numeric( $order ) && $order > 0 ? wc_get_order( (int) $order ) : $order; + + if ( $order instanceof \WC_Order ) { + $order->delete_meta_data( $meta_key); + $order->save_meta_data(); + } + + } else { + + $order_id = $order instanceof \WC_Order ? $order->get_id() : $order; + + if ( is_numeric( $order_id ) && $order_id > 0 ) { + delete_post_meta( (int) $order_id, $meta_key, $meta_value ); + } + } + } + + + /** + * Determines if an order meta exists according to HPOS availability. + * + * Uses {@see \WC_Order::meta_exists()} if HPOS is enabled, otherwise it uses the WordPress {@see metadata_exists()} function. + * + * @since 5.11.0 + * + * @param int|\WC_Order $order order ID or object + * @param string $meta_key meta key + * @return bool + */ + public static function order_meta_exists( $order, string $meta_key ) : bool { + + if ( SV_WC_Plugin_Compatibility::is_hpos_enabled() ) { + + $order = is_numeric( $order ) && $order > 0 ? wc_get_order( (int) $order ) : $order; + + if ( $order instanceof \WC_Order ) { + return $order->meta_exists( $meta_key ); + } + + } else { + + $order_id = $order instanceof \WC_Order ? $order->get_id() : $order; + + if ( is_numeric( $order_id ) && $order_id > 0 ) { + return metadata_exists( 'post', (int) $order_id, $meta_key ); + } + } + + return false; + } + + +} + + +endif; diff --git a/vendor/skyverge/wc-plugin-framework/woocommerce/i18n/languages/woocommerce-plugin-framework-et.mo b/vendor/skyverge/wc-plugin-framework/woocommerce/i18n/languages/woocommerce-plugin-framework-et.mo new file mode 100644 index 0000000..4676611 Binary files /dev/null and b/vendor/skyverge/wc-plugin-framework/woocommerce/i18n/languages/woocommerce-plugin-framework-et.mo differ diff --git a/vendor/skyverge/wc-plugin-framework/woocommerce/i18n/languages/woocommerce-plugin-framework-et.po b/vendor/skyverge/wc-plugin-framework/woocommerce/i18n/languages/woocommerce-plugin-framework-et.po new file mode 100644 index 0000000..95701e8 --- /dev/null +++ b/vendor/skyverge/wc-plugin-framework/woocommerce/i18n/languages/woocommerce-plugin-framework-et.po @@ -0,0 +1,2296 @@ +# Copyright (C) 2015 +# This file is distributed under the same license as the package. +# Translators: +# Illimar Tambek , 2015 +msgid "" +msgstr "" +"Project-Id-Version: WC Plugin Framework\n" +"Report-Msgid-Bugs-To: https://support.woocommerce.com/hc/\n" +"POT-Creation-Date: 2023-03-27 07:41:54+00:00\n" +"PO-Revision-Date: 2017-03-27 11:59-0700\n" +"Last-Translator: Illimar Tambek \n" +"Language-Team: Estonian (http://www.transifex.com/projects/p/wc-plugin-" +"framework/language/et/)\n" +"Language: et\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Poedit 1.8.11\n" + +#: Lifecycle.php:394 +msgid "Awesome" +msgstr "" + +#: Lifecycle.php:395 +msgid "Fantastic" +msgstr "" + +#: Lifecycle.php:396 +msgid "Cowabunga" +msgstr "" + +#: Lifecycle.php:397 +msgid "Congratulations" +msgstr "" + +#: Lifecycle.php:398 +msgid "Hot dog" +msgstr "" + +#. translators: Placeholders: %1$s - plugin name, %2$s - tag, %3$s - +#. tag, %4$s - tag, %5$s - tag +#: Lifecycle.php:405 +msgid "" +"Are you having a great experience with %1$s so far? Please consider " +"%2$sleaving a review%3$s! If things aren't going quite as expected, we're " +"happy to help -- please %4$sreach out to our support team%5$s." +msgstr "" + +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:182 +msgid "" +"Thanks for installing %1$s! To get started, take a minute to %2$sread the " +"documentation%3$s :)" +msgstr "" + +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:210 +msgid "" +"Thanks for installing %1$s! To get started, take a minute to complete these " +"%2$squick and easy setup steps%3$s :)" +msgstr "" + +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:235 +msgid "Setup" +msgstr "" + +#. translators: Placeholders: %s - plugin name +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:303 +msgid "%s › Setup" +msgstr "" + +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:350 +#, fuzzy +msgid "Oops! An error occurred, please try again." +msgstr "Sinu päringuga esines viga, palun proovi uuesti." + +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:488 +msgid "Ready!" +msgstr "" + +#. translators: Placeholder: %s - plugin name +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:581 +msgid "Welcome to %s!" +msgstr "" + +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:594 +msgid "" +"This quick setup wizard will help you configure the basic settings and get " +"you started." +msgstr "" + +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:608 +msgid "%s is ready!" +msgstr "" + +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:660 +msgid "Next step" +msgstr "" + +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:686 +msgid "You can also:" +msgstr "" + +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:730 +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:760 +msgid "View the Docs" +msgstr "" + +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:731 +msgid "See more setup options" +msgstr "" + +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:732 +msgid "Learn more about customizing the plugin" +msgstr "" + +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:756 +msgid "Review Your Settings" +msgstr "" + +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:764 +msgid "Leave a Review" +msgstr "" + +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:788 +msgid "Continue" +msgstr "Jätka" + +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:948 +msgid "Return to the WordPress Dashboard" +msgstr "" + +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:950 +msgid "Not right now" +msgstr "" + +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:952 +msgid "Skip this step" +msgstr "" + +#: class-sv-wc-framework-bootstrap.php:268 +msgid "" +"The following plugin is disabled because it is out of date and incompatible " +"with newer plugins on your site:" +msgid_plural "" +"The following plugins are disabled because they are out of date and " +"incompatible with newer plugins on your site:" +msgstr[0] "" +msgstr[1] "" + +#: class-sv-wc-framework-bootstrap.php:282 +msgid "" +"To resolve this, please %1$supdate%2$s (recommended) %3$sor%4$s " +"%5$sdeactivate%6$s the above plugin, or %7$sdeactivate the following%8$s:" +msgid_plural "" +"To resolve this, please %1$supdate%2$s (recommended) %3$sor%4$s " +"%5$sdeactivate%6$s the above plugins, or %7$sdeactivate the following%8$s:" +msgstr[0] "" +msgstr[1] "" + +#: class-sv-wc-framework-bootstrap.php:303 +msgid "" +"The following plugins are inactive because they require a newer version of " +"WooCommerce:" +msgstr "" +"Järgnevad pluginad ei ole aktiivsed, kuna vajavad korrektselt töötamiseks " +"uuemat WooCommerce'i versiooni:" + +#: class-sv-wc-framework-bootstrap.php:303 +msgid "" +"The following plugin is inactive because it requires a newer version of " +"WooCommerce:" +msgstr "" +"Järgnev plugin ei ole aktiivne, kuna vajab korrektselt töötamiseks uuemat " +"WooCommerce'i versiooni:" + +#. translators: Placeholders: %1$s - plugin name, %2$s - WooCommerce version +#. number +#: class-sv-wc-framework-bootstrap.php:308 +msgid "%1$s requires WooCommerce %2$s or newer" +msgstr "%1$s vajab WooCommerce'i versiooni %2$s või uuemat" + +#. translators: Placeholders: %1$s - tag, %2$s - tag +#: class-sv-wc-framework-bootstrap.php:312 +msgid "Please %1$supdate WooCommerce%2$s" +msgstr "Palun %1$suuenda WooCommerce'i%2$s" + +#: class-sv-wc-plugin-compatibility.php:351 +msgid "WooCommerce" +msgstr "" + +#. translators: Placeholders: %1$s - plugin name, %2$s - a PHP +#. extension/comma-separated list of PHP extensions +#: class-sv-wc-plugin-dependencies.php:156 +msgid "" +"%1$s requires the %2$s PHP extension to function. Contact your host or " +"server administrator to install and configure the missing extension." +msgid_plural "" +"%1$s requires the following PHP extensions to function: %2$s. Contact your " +"host or server administrator to install and configure the missing extensions." +msgstr[0] "" +msgstr[1] "" + +#. translators: Placeholders: %1$s - plugin name, %2$s - a PHP +#. function/comma-separated list of PHP functions +#: class-sv-wc-plugin-dependencies.php:184 +msgid "" +"%1$s requires the %2$s PHP function to exist. Contact your host or server " +"administrator to install and configure the missing function." +msgid_plural "" +"%1$s requires the following PHP functions to exist: %2$s. Contact your host " +"or server administrator to install and configure the missing functions." +msgstr[0] "" +msgstr[1] "" + +#. translators: Placeholders: %s - plugin name +#: class-sv-wc-plugin-dependencies.php:214 +msgid "" +"%s may behave unexpectedly because the following PHP configuration settings " +"are required:" +msgstr "" + +#: class-sv-wc-plugin-dependencies.php:228 +msgid "%s or higher" +msgstr "" + +#: class-sv-wc-plugin-dependencies.php:238 +msgid "" +"Please contact your hosting provider or server administrator to configure " +"these settings." +msgstr "" + +#. translators: Placeholders: %1$s - , %2$s - +#: class-sv-wc-plugin-dependencies.php:260 +msgid "" +"Hey there! We've noticed that your server is running %1$san outdated version " +"of PHP%2$s, which is the programming language that WooCommerce and its " +"extensions are built on.\n" +"\t\t\t\t\tThe PHP version that is currently used for your site is no longer " +"maintained, nor %1$sreceives security updates%2$s; newer versions are faster " +"and more secure.\n" +"\t\t\t\t\tAs a result, %3$s no longer supports this version and you should " +"upgrade PHP as soon as possible.\n" +"\t\t\t\t\tYour hosting provider can do this for you. %4$sHere are some " +"resources to help you upgrade%5$s and to explain PHP versions further." +msgstr "" + +#. translators: Placeholders: %s - plugin name +#: class-sv-wc-plugin.php:310 +msgid "You cannot clone instances of %s." +msgstr "%s eksemplari ei saa kloonida." + +#. translators: Placeholders: %s - plugin name +#: class-sv-wc-plugin.php:321 +msgid "You cannot unserialize instances of %s." +msgstr "%s eksemplari ei saa deserialiseerida (unserialize)." + +#. translators: Docs as in Documentation +#: class-sv-wc-plugin.php:597 +msgid "Docs" +msgstr "Dokumentatsioon" + +#: class-sv-wc-plugin.php:712 +msgid "%1$s - A minimum of %2$s is required." +msgstr "" + +#: class-sv-wc-plugin.php:721 +msgid "Set as %1$s - %2$s is required." +msgstr "" + +#: class-sv-wc-plugin.php:1015 +#: payment-gateway/class-sv-wc-payment-gateway-plugin.php:876 +msgid "Configure" +msgstr "Seadista" + +#: payment-gateway/External_Checkout/Admin.php:137 +#: payment-gateway/External_Checkout/Admin.php:147 +msgid "Processing Gateway" +msgstr "" + +#: payment-gateway/External_Checkout/Admin.php:287 +msgid "Single products" +msgstr "" + +#: payment-gateway/External_Checkout/Admin.php:288 +msgid "Cart" +msgstr "" + +#: payment-gateway/External_Checkout/Admin.php:289 +#, fuzzy +msgid "Checkout" +msgstr "E-tÅ¡ekk" + +#. translators: Placeholder: %s - external checkout label +#: payment-gateway/External_Checkout/Admin.php:330 +msgid "%s is disabled." +msgstr "" + +#. translators: Placeholders: %1$s - plugin name, %2$s - a +#. currency/comma-separated list of currencies, %3$s - tag, %4$s - +#. tag, %5$s - external checkout label +#: payment-gateway/External_Checkout/Admin.php:394 +msgid "" +"Accepts payment in %1$s only. %2$sConfigure%3$s WooCommerce to accept %1$s " +"to enable %4$s." +msgid_plural "" +"Accepts payment in one of %1$s only. %2$sConfigure%3$s WooCommerce to accept " +"one of %1$s to enable %4$s." +msgstr[0] "" +msgstr[1] "" + +#: payment-gateway/External_Checkout/Admin.php:436 +msgid "" +"%4$s%1$s Notice!%5$s Your store %2$scalculates taxes%3$s based on the " +"shipping address, but %1$s %4$sdoes not%5$s share customer shipping " +"information with your store for orders with only virtual products. These " +"orders will have their taxes calculated based on the shop address instead." +msgstr "" + +#: payment-gateway/External_Checkout/Admin.php:470 +msgid "" +"%4$s%1$s Notice!%5$s Your store %2$scalculates taxes%3$s based on the " +"billing address, but %1$s %4$sdoes not%5$s share the customer billing " +"address with your store before payment. These orders will have their taxes " +"calculated based on the shipping address (or shop address, for orders with " +"only virtual products)." +msgstr "" + +#: payment-gateway/External_Checkout/Frontend.php:259 +msgid "or" +msgstr "" + +#: payment-gateway/External_Checkout/Frontend.php:293 +msgid "" +"By submitting your payment, you agree to our %1$sterms and conditions%2$s." +msgstr "" + +#: payment-gateway/External_Checkout/Google_Pay/Admin.php:71 +#: payment-gateway/External_Checkout/Google_Pay/Admin.php:87 +#: payment-gateway/External_Checkout/Google_Pay/Google_Pay.php:66 +msgid "Google Pay" +msgstr "" + +#: payment-gateway/External_Checkout/Google_Pay/Admin.php:93 +#: payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:93 +#: payment-gateway/class-sv-wc-payment-gateway.php:1262 +msgid "Enable / Disable" +msgstr "Luba / Keela" + +#: payment-gateway/External_Checkout/Google_Pay/Admin.php:94 +msgid "Accept Google Pay" +msgstr "" + +#: payment-gateway/External_Checkout/Google_Pay/Admin.php:101 +msgid "Allow Google Pay on" +msgstr "" + +#: payment-gateway/External_Checkout/Google_Pay/Admin.php:111 +#: payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:111 +msgid "Button Style" +msgstr "" + +#: payment-gateway/External_Checkout/Google_Pay/Admin.php:114 +#: payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:114 +msgid "Black" +msgstr "" + +#: payment-gateway/External_Checkout/Google_Pay/Admin.php:115 +#: payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:115 +msgid "White" +msgstr "" + +#: payment-gateway/External_Checkout/Google_Pay/Admin.php:148 +#: payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:150 +#: payment-gateway/class-sv-wc-payment-gateway.php:1440 +msgid "Connection Settings" +msgstr "" + +#: payment-gateway/External_Checkout/Google_Pay/Admin.php:157 +#: payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:188 +#, fuzzy +msgid "Test Mode" +msgstr "Veaotsingu režiim" + +#: payment-gateway/External_Checkout/Google_Pay/Admin.php:158 +msgid "" +"Enable to test Google Pay functionality throughout your sites without " +"processing real payments." +msgstr "" + +#: payment-gateway/External_Checkout/Google_Pay/Frontend.php:130 +#: payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php:141 +#: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:99 +msgid "An error occurred, please try again or try an alternate form of payment" +msgstr "Esines viga, palun proovi uuesti või kasuta teistsugust makseviisi" + +#: payment-gateway/External_Checkout/Google_Pay/Google_Pay.php:255 +#: payment-gateway/External_Checkout/Google_Pay/Google_Pay.php:380 +#: payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay.php:539 +msgid "Subtotal" +msgstr "" + +#: payment-gateway/External_Checkout/Google_Pay/Google_Pay.php:390 +#: payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay.php:549 +#, fuzzy +msgid "Discount" +msgstr "Konto" + +#: payment-gateway/External_Checkout/Google_Pay/Google_Pay.php:400 +#: payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay.php:559 +msgid "Shipping" +msgstr "" + +#: payment-gateway/External_Checkout/Google_Pay/Google_Pay.php:410 +#: payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay.php:569 +msgid "Fees" +msgstr "" + +#: payment-gateway/External_Checkout/Google_Pay/Google_Pay.php:420 +#: payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay.php:579 +msgid "Taxes" +msgstr "" + +#: payment-gateway/External_Checkout/Google_Pay/Google_Pay.php:463 +#, fuzzy +msgid "Google Pay payment authorized." +msgstr "%1$s: makse ebaõnnestus (%2$s)" + +#: payment-gateway/External_Checkout/Google_Pay/Google_Pay.php:538 +#, fuzzy +msgid "Google Pay payment failed. %s" +msgstr "%1$s: makse ebaõnnestus (%2$s)" + +#: payment-gateway/External_Checkout/Orders.php:140 +#: payment-gateway/External_Checkout/Orders.php:153 +#: payment-gateway/External_Checkout/Orders.php:157 +msgid "Error %d: Unable to create order. Please try again." +msgstr "" + +#: payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:71 +#: payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:87 +#: payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay.php:65 +msgid "Apple Pay" +msgstr "" + +#: payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:94 +msgid "Accept Apple Pay" +msgstr "" + +#: payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:101 +msgid "Allow Apple Pay on" +msgstr "" + +#: payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:116 +msgid "White with outline" +msgstr "" + +#: payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:159 +msgid "Apple Merchant ID" +msgstr "" + +#: payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:163 +msgid "This is found in your %1$sApple developer account%2$s" +msgstr "" + +#: payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:173 +msgid "Certificate Path" +msgstr "" + +#. translators: Placeholders: %s - the server's web root path +#: payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:178 +msgid "For reference, your current web root path is: %s" +msgstr "" + +#: payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:189 +msgid "" +"Enable to test Apple Pay functionality throughout your sites without " +"processing real payments." +msgstr "" + +#: payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:228 +msgid "Your site must be served over HTTPS with a valid SSL certificate." +msgstr "" + +#: payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:238 +msgid "" +"Your %1$sMerchant Identity Certificate%2$s cannot be found. Please check " +"your path configuration." +msgstr "" + +#: payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php:176 +msgid "Buy with" +msgstr "" + +#: payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay.php:152 +msgid "Apple Pay payment authorized." +msgstr "" + +#: payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay.php:186 +#, fuzzy +msgid "Apple Pay payment failed. %s" +msgstr "%1$s: makse ebaõnnestus (%2$s)" + +#: payment-gateway/Handlers/Abstract_Hosted_Payment_Handler.php:179 +#, fuzzy +msgid "" +"There was a problem processing your order and it is being placed on hold for " +"review. Please contact us to complete the transaction." +msgstr "" +"Tellimus on pandud ülevaatuseks ootele. Tehingu sooritamiseks võta palun " +"meiega ühendust." + +#: payment-gateway/Handlers/Abstract_Hosted_Payment_Handler.php:217 +#: payment-gateway/class-sv-wc-payment-gateway.php:2889 +#: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-subscriptions.php:521 +msgid "" +"An error occurred, please try again or try an alternate form of payment." +msgstr "Esines viga, palun proovi uuesti või kasuta teistsugust makseviisi." + +#. translators: Placeholders: %s - a WooCommerce order ID +#: payment-gateway/Handlers/Abstract_Hosted_Payment_Handler.php:320 +#: payment-gateway/class-sv-wc-payment-gateway-hosted.php:449 +msgid "Could not find order %s" +msgstr "" + +#. translators: Placeholders: %1$s - status code, %2$s - status message +#. translators: Placeholders: %1$s - payment request response status code, %2$s +#. - payment request response status message +#: payment-gateway/Handlers/Abstract_Payment_Handler.php:152 +#: payment-gateway/class-sv-wc-payment-gateway.php:2483 +#: payment-gateway/payment-tokens/class-sv-wc-payment-gateway-payment-tokens-handler.php:172 +msgid "Status code %1$s: %2$s" +msgstr "Staatuse kood %1$s: %2$s" + +#. translators: Placeholders: %s - status code +#. translators: Placeholders: %s - payment request response status code +#: payment-gateway/Handlers/Abstract_Payment_Handler.php:155 +#: payment-gateway/class-sv-wc-payment-gateway.php:2486 +#: payment-gateway/payment-tokens/class-sv-wc-payment-gateway-payment-tokens-handler.php:175 +msgid "Status code: %s" +msgstr "Staatuse kood: %s" + +#. translators: Placeholders; %s - status message +#. translators: Placeholders: %s - payment request response status message +#: payment-gateway/Handlers/Abstract_Payment_Handler.php:158 +#: payment-gateway/class-sv-wc-payment-gateway.php:2489 +#: payment-gateway/payment-tokens/class-sv-wc-payment-gateway-payment-tokens-handler.php:178 +msgid "Status message: %s" +msgstr "Staatuse teade: %s" + +#: payment-gateway/Handlers/Abstract_Payment_Handler.php:163 +#: payment-gateway/class-sv-wc-payment-gateway.php:2494 +#: payment-gateway/payment-tokens/class-sv-wc-payment-gateway-payment-tokens-handler.php:185 +msgid "Transaction ID %s" +msgstr "Tehingu ID %s" + +#. translators: Placeholders: %s - payment gateway title (such as +#. Authorize.net, Braintree, etc) +#: payment-gateway/Handlers/Abstract_Payment_Handler.php:204 +#: payment-gateway/class-sv-wc-payment-gateway-hosted.php:513 +msgid "%s duplicate transaction received" +msgstr "%s: duplikaattehing" + +#: payment-gateway/Handlers/Abstract_Payment_Handler.php:207 +#: payment-gateway/class-sv-wc-payment-gateway-hosted.php:516 +msgid "Order %s is already paid for." +msgstr "" + +#: payment-gateway/Handlers/Abstract_Payment_Handler.php:267 +#: payment-gateway/class-sv-wc-payment-gateway.php:2825 +msgid "" +"Your order has been received and is being reviewed. Thank you for your " +"business." +msgstr "" +"Sinu tellimus on vastu võetud ja on ülevaatamisel. Täname koostöö eest." + +#. translators: This is a message describing that the transaction in question +#. only performed a credit card authorization and did not capture any funds. +#: payment-gateway/Handlers/Abstract_Payment_Handler.php:274 +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:864 +#: payment-gateway/class-sv-wc-payment-gateway.php:1861 +#: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-pre-orders.php:370 +msgid "Authorization only transaction" +msgstr "Autoriseerimise tehing" + +#. translators: Placeholders: %s - payment gateway title +#: payment-gateway/Handlers/Abstract_Payment_Handler.php:364 +#, fuzzy +msgid "%s Transaction Held for Review" +msgstr "%1$s: tehning pandi ülevaatuseks ootele (%2$s)" + +#. translators: Placeholders: %s - payment gateway title +#: payment-gateway/Handlers/Abstract_Payment_Handler.php:435 +#, fuzzy +msgid "%s Payment Failed" +msgstr "%1$s: makse ebaõnnestus (%2$s)" + +#. translators: Placeholders: %s - payment gateway title +#: payment-gateway/Handlers/Abstract_Payment_Handler.php:462 +#, fuzzy +msgid "%s Transaction Cancelled" +msgstr "%1$s: tehing tühistatud (%2$s)" + +#: payment-gateway/Handlers/Capture.php:158 +msgid "Order cannot be captured" +msgstr "" + +#: payment-gateway/Handlers/Capture.php:163 +msgid "Transaction authorization has expired" +msgstr "" + +#: payment-gateway/Handlers/Capture.php:168 +msgid "Transaction has already been fully captured" +msgstr "" + +#: payment-gateway/Handlers/Capture.php:173 +#, fuzzy +msgid "Transaction cannot be captured" +msgstr "Tehingu tüüp" + +#. translators: Placeholders: %1$s - payment gateway title (such as +#. Authorize.net, Braintree, etc), %2$s - transaction amount. Definitions: +#. Capture, as in capture funds from a credit card. +#: payment-gateway/Handlers/Capture.php:189 +msgid "%1$s Capture of %2$s Approved" +msgstr "%1$s: tasumine summas %2$s kinnitatud" + +#. translators: Placeholders: %s - transaction ID +#: payment-gateway/Handlers/Capture.php:198 +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:683 +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:768 +#: payment-gateway/class-sv-wc-payment-gateway.php:2162 +#: payment-gateway/class-sv-wc-payment-gateway.php:2395 +#: payment-gateway/class-sv-wc-payment-gateway.php:2706 +#: payment-gateway/class-sv-wc-payment-gateway.php:2751 +#: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-pre-orders.php:353 +msgid "(Transaction ID %s)" +msgstr "(Tehingu ID %s)" + +#. translators: Placeholders: %1$s - payment gateway title (such as +#. Authorize.net, Braintree, etc), %2$s - failure message. Definitions: +#. "capture" as in capturing funds from a credit card. +#: payment-gateway/Handlers/Capture.php:229 +msgid "%1$s Capture Failed: %2$s" +msgstr "%1$s: makse teostamine ebaõnnestus: %2$s" + +#: payment-gateway/admin/class-sv-wc-payment-gateway-admin-order.php:130 +#, fuzzy +msgid "" +"Are you sure you wish to process this capture? The action cannot be undone." +msgstr "" +"Oled kindel, et soovid seda teha? Muudatust ei rakendata enne kui klikid " +"\"Uuenda\"" + +#: payment-gateway/admin/class-sv-wc-payment-gateway-admin-order.php:133 +msgid "" +"Something went wrong, and the capture could no be completed. Please try " +"again." +msgstr "" + +#. translators: verb, as in "Capture credit card charge". Used when an +#. amount has been pre-authorized before, but funds have not yet been captured +#. (taken) from the card. Capturing the charge will take the money from the +#. credit card and put it in the merchant's pockets. +#: payment-gateway/admin/class-sv-wc-payment-gateway-admin-order.php:178 +#: payment-gateway/admin/class-sv-wc-payment-gateway-admin-order.php:267 +#: payment-gateway/admin/class-sv-wc-payment-gateway-admin-order.php:330 +msgid "Capture Charge" +msgstr "Teosta makse" + +#: payment-gateway/admin/class-sv-wc-payment-gateway-admin-order.php:320 +msgid "This charge has been fully captured." +msgstr "" + +#: payment-gateway/admin/class-sv-wc-payment-gateway-admin-order.php:322 +msgid "This charge can no longer be captured." +msgstr "" + +#: payment-gateway/admin/class-sv-wc-payment-gateway-admin-order.php:324 +msgid "This charge cannot be captured." +msgstr "" + +#: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:91 +msgid "Are you sure you want to remove this token?" +msgstr "" + +#: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:101 +msgid "Invalid token data" +msgstr "" + +#: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:105 +#, fuzzy +msgid "An error occurred. Please try again." +msgstr "Sinu päringuga esines viga, palun proovi uuesti." + +#: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:491 +#: payment-gateway/admin/class-sv-wc-payment-gateway-admin-user-handler.php:305 +msgid "(%s)" +msgstr "" + +#: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:521 +#: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:900 +msgid "Default" +msgstr "" + +#: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:557 +#: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:590 +msgid "Token ID" +msgstr "" + +#: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:562 +#: payment-gateway/class-sv-wc-payment-gateway-privacy.php:300 +msgid "Card Type" +msgstr "Kaardi tüüp" + +#: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:567 +#: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:603 +#: payment-gateway/class-sv-wc-payment-gateway-privacy.php:192 +#: payment-gateway/class-sv-wc-payment-gateway-privacy.php:298 +msgid "Last Four" +msgstr "Viimased 4 numbrit" + +#: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:574 +#: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:362 +msgid "Expiration (MM/YY)" +msgstr "Aegub (KK/AA)" + +#. translators: e-check account type, HTML form field label +#: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:595 +#: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:470 +#: payment-gateway/class-sv-wc-payment-gateway-privacy.php:299 +msgid "Account Type" +msgstr "Konto tüüp" + +#: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:598 +msgid "Checking" +msgstr "" + +#: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:599 +msgid "Savings" +msgstr "" + +#: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:700 +msgid "Refresh" +msgstr "" + +#: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:702 +msgid "Add New" +msgstr "" + +#: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:705 +#: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:297 +msgid "Save" +msgstr "" + +#: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:728 +msgid "Remove" +msgstr "" + +#: payment-gateway/admin/class-sv-wc-payment-gateway-admin-user-handler.php:224 +#: payment-gateway/class-sv-wc-payment-gateway-privacy.php:209 +msgid "%s Payment Tokens" +msgstr "%s maksevahendid" + +#: payment-gateway/admin/class-sv-wc-payment-gateway-admin-user-handler.php:302 +#: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-subscriptions.php:862 +msgid "Customer ID" +msgstr "Kliendi ID" + +#: payment-gateway/admin/views/html-admin-gateway-status.php:32 +msgid "This section contains configuration settings for this gateway." +msgstr "" + +#. translators: environment as in a software environment (test/production) +#: payment-gateway/admin/views/html-admin-gateway-status.php:53 +#: payment-gateway/class-sv-wc-payment-gateway.php:1389 +msgid "Environment" +msgstr "Keskkond" + +#: payment-gateway/admin/views/html-admin-gateway-status.php:54 +msgid "The transaction environment for this gateway." +msgstr "" + +#: payment-gateway/admin/views/html-admin-gateway-status.php:61 +msgid "Tokenization Enabled" +msgstr "" + +#: payment-gateway/admin/views/html-admin-gateway-status.php:62 +msgid "Displays whether or not tokenization is enabled for this gateway." +msgstr "" + +#: payment-gateway/admin/views/html-admin-gateway-status.php:75 +#: payment-gateway/class-sv-wc-payment-gateway.php:1316 +msgid "Debug Mode" +msgstr "Veaotsingu režiim" + +#: payment-gateway/admin/views/html-admin-gateway-status.php:76 +msgid "Displays whether or not debug logging is enabled for this gateway." +msgstr "" + +#: payment-gateway/admin/views/html-admin-gateway-status.php:79 +msgid "Display at Checkout & Log" +msgstr "" + +#: payment-gateway/admin/views/html-admin-gateway-status.php:81 +msgid "Display at Checkout" +msgstr "" + +#: payment-gateway/admin/views/html-admin-gateway-status.php:83 +#: payment-gateway/class-sv-wc-payment-gateway.php:1324 +msgid "Save to Log" +msgstr "Salvesta logifaili" + +#: payment-gateway/admin/views/html-admin-gateway-status.php:85 +#: payment-gateway/class-sv-wc-payment-gateway.php:1322 +msgid "Off" +msgstr "Välja lülitatud" + +#: payment-gateway/admin/views/html-order-partial-capture.php:30 +#, fuzzy +msgid "Authorization total" +msgstr "Autoriseerimine" + +#: payment-gateway/admin/views/html-order-partial-capture.php:34 +msgid "Amount already captured" +msgstr "" + +#: payment-gateway/admin/views/html-order-partial-capture.php:40 +msgid "Remaining order total" +msgstr "" + +#: payment-gateway/admin/views/html-order-partial-capture.php:46 +#, fuzzy +msgid "Capture amount" +msgstr "Teosta makse" + +#: payment-gateway/admin/views/html-order-partial-capture.php:53 +msgid "Comment (optional):" +msgstr "" + +#: payment-gateway/admin/views/html-order-partial-capture.php:65 +#, fuzzy +msgid "Capture %s" +msgstr "Teosta makse" + +#: payment-gateway/admin/views/html-order-partial-capture.php:66 +#: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:608 +#, fuzzy +msgid "Cancel" +msgstr "Tühista tellimus" + +#: payment-gateway/admin/views/html-user-payment-token-editor-token.php:57 +msgid "-- Select an option --" +msgstr "" + +#: payment-gateway/admin/views/html-user-payment-token-editor.php:59 +msgid "No saved payment tokens" +msgstr "" + +#: payment-gateway/admin/views/html-user-profile-field-customer-id.php:30 +msgid "The gateway customer ID for the user. Only edit this if necessary." +msgstr "" +"Kasutajale makseviisi poolt määratud kliendi tunnus. Muuda seda ainult siis, " +"kui tõesti vajalik." + +#: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:100 +msgid "" +"We cannot process your order with the payment information that you provided. " +"Please use a different payment account or an alternate payment method." +msgstr "" +"Me ei saa sinu tellimust antud makseinfo alusel töödelda. Palun kasuta teist " +"maksekontot või teistsugust makseviisi." + +#: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:101 +msgid "" +"This order is being placed on hold for review. Please contact us to complete " +"the transaction." +msgstr "" +"Tellimus on pandud ülevaatuseks ootele. Tehingu sooritamiseks võta palun " +"meiega ühendust." + +#: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:106 +msgid "" +"This order is being placed on hold for review due to an incorrect card " +"verification number. You may contact the store to complete the transaction." +msgstr "" +"Tellimus pandi ootele, kuna kaardi turvakood oli vale. Tehingu " +"lõpuleviimiseks võid poega ühendust võtta." + +#: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:107 +msgid "The card verification number is invalid, please try again." +msgstr "Kaardi turvakood on vale, palun proovi uuesti." + +#: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:108 +msgid "Please enter your card verification number and try again." +msgstr "Palun sisesta oma kaardi turvakood ja proovi uuesti." + +#: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:111 +msgid "" +"That card type is not accepted, please use an alternate card or other form " +"of payment." +msgstr "" +"Sellist tüüpi kaarti ei võeta vastu, palun proovi mõnda teist kaarti või " +"teistsugust makseviisi." + +#: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:112 +#: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:116 +msgid "" +"The card type is invalid or does not correlate with the credit card number. " +"Please try again or use an alternate card or other form of payment." +msgstr "" +"Kaardi tüüp on vigane või ei vasta kaardi numbrile. Palun proovi uuesti, " +"proovi mõnda teist kaarti või teistsugust makseviisi." + +#: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:113 +msgid "Please select the card type and try again." +msgstr "Palun vali kaardi tüüp ja proovi uuesti." + +#: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:117 +msgid "The card number is invalid, please re-enter and try again." +msgstr "Kaardi number on vigane, palun sisesta uuesti ja proovi veelkord." + +#: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:118 +msgid "Please enter your card number and try again." +msgstr "Palun sisesta oma kaardi number ja proovi uuesti." + +#: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:121 +msgid "The card expiration date is invalid, please re-enter and try again." +msgstr "" +"Kaardi aegumiskuupäev on vale, palun sisesta uuesti ja proovi veelkord." + +#: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:122 +msgid "The card expiration month is invalid, please re-enter and try again." +msgstr "Kaardi aegumise kuu on vale, palun sisesta uuesti ja proovi veelkord." + +#: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:123 +msgid "The card expiration year is invalid, please re-enter and try again." +msgstr "" +"Kaardi aegumise aasta on vale, palun sisesta uuesti ja proovi veelkord." + +#: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:124 +msgid "Please enter your card expiration date and try again." +msgstr "Palun sisesta oma kaardi aegumiskuupäev ja proovi uuesti." + +#: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:127 +msgid "The bank routing number is invalid, please re-enter and try again." +msgstr "" +"Panga suunakood ei ole korrektne, palun sisesta uuesti ja proovi veelkord." + +#: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:128 +msgid "The bank account number is invalid, please re-enter and try again." +msgstr "" +"Pangakonto number ei ole korrektne, palun sisesta uuesti ja proovi veelkord." + +#: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:131 +msgid "" +"The provided card is expired, please use an alternate card or other form of " +"payment." +msgstr "" +"Antud kaart on aegunud, palun kasuta mõnda teist kaarti või teistsugust " +"makseviisi." + +#: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:132 +msgid "" +"The provided card was declined, please use an alternate card or other form " +"of payment." +msgstr "" +"Antud kaart klükati tagasi, palun kasuta mõnda teist kaarti või teistsugust " +"makseviisi." + +#: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:133 +msgid "" +"Insufficient funds in account, please use an alternate card or other form of " +"payment." +msgstr "" +"Kontol pole piisavalt vahendeid, palun kasuta mõnda teist kaarti või " +"teistsugust makseviisi." + +#: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:134 +msgid "" +"The card is inactivate or not authorized for card-not-present transactions, " +"please use an alternate card or other form of payment." +msgstr "" +"Antud kaart ei ole aktiveeritud või ei ole sellega internetimaksed lubatud. " +"Palun kasuta mõnda teist kaarti või teistsugust makseviisi." + +#: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:135 +msgid "" +"The credit limit for the card has been reached, please use an alternate card " +"or other form of payment." +msgstr "" +"Kaardi krediitilimiit on ära kasutatud, palun kasuta mõnda teist kaarti või " +"teistsugust makseviisi." + +#: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:136 +msgid "" +"The card verification number does not match. Please re-enter and try again." +msgstr "Kaardi turvakood ei klapi. Palun sisesta uuesti ja proovi veelkord." + +#: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:137 +msgid "" +"The provided address does not match the billing address for cardholder. " +"Please verify the address and try again." +msgstr "" +"Antud aadress ei kattu kaardi omaniku aadressiga. Palun kontrolli, et " +"sisestaid õige aadressi ning proovi uuesti." + +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:61 +msgid "" +"Payment error, please try another payment method or contact us to complete " +"your transaction." +msgstr "" +"Viga maksega, palun proovi teistsugust makseviisi või võta meiega ühendust." + +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:161 +#: payment-gateway/class-sv-wc-payment-gateway.php:503 +msgid "Card expiration date is invalid" +msgstr "Kaardi aegumiskuupäev ei ole korrektne" + +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:185 +#: payment-gateway/class-sv-wc-payment-gateway.php:496 +msgid "Card number is missing" +msgstr "Kaardi number on puudu" + +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:191 +#: payment-gateway/class-sv-wc-payment-gateway.php:499 +msgid "Card number is invalid (wrong length)" +msgstr "Kaardi number ei ole korrektne (pikkus on vale)" + +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:196 +#: payment-gateway/class-sv-wc-payment-gateway.php:498 +msgid "Card number is invalid (only digits allowed)" +msgstr "Kaardi number ei ole korrektne (lubatud on ainult numbrid)" + +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:201 +#: payment-gateway/class-sv-wc-payment-gateway.php:497 +msgid "Card number is invalid" +msgstr "Kaardi number ei ole korrektne" + +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:228 +#: payment-gateway/class-sv-wc-payment-gateway.php:501 +msgid "Card security code is invalid (only digits are allowed)" +msgstr "Kaardi turvakood ei ole korrektne (lubatud on ainult numbrid)" + +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:234 +#: payment-gateway/class-sv-wc-payment-gateway.php:502 +msgid "Card security code is invalid (must be 3 or 4 digits)" +msgstr "Kaardi turvakood ei ole korrektne (peab olema 3 või 4 numbrit)" + +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:240 +#: payment-gateway/class-sv-wc-payment-gateway.php:500 +msgid "Card security code is missing" +msgstr "Kaardi turvakood on puudu" + +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:267 +#: payment-gateway/class-sv-wc-payment-gateway.php:512 +msgid "Routing Number is missing" +msgstr "Suunakood on puudu" + +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:274 +#: payment-gateway/class-sv-wc-payment-gateway.php:513 +msgid "Routing Number is invalid (only digits are allowed)" +msgstr "Suunakood ei ole korrektne (lubatud on ainult numbrid)" + +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:280 +#: payment-gateway/class-sv-wc-payment-gateway.php:514 +msgid "Routing number is invalid (must be 9 digits)" +msgstr "Suunakood ei ole korrektne (peab olemas 9 numbrit)" + +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:289 +#: payment-gateway/class-sv-wc-payment-gateway.php:509 +msgid "Account Number is missing" +msgstr "Konto number on puudu" + +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:296 +#: payment-gateway/class-sv-wc-payment-gateway.php:510 +msgid "Account Number is invalid (only digits are allowed)" +msgstr "Konto number ei ole korrektne (lubatud on ainult numbrid)" + +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:302 +#: payment-gateway/class-sv-wc-payment-gateway.php:511 +msgid "Account number is invalid (must be between 5 and 17 digits)" +msgstr "Konto number ei ole korrektne (peab olemas 5-17 numbrit)" + +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:309 +#: payment-gateway/class-sv-wc-payment-gateway.php:508 +msgid "Drivers license number is invalid" +msgstr "Juhiloa number ei ole korrektne" + +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:315 +#: payment-gateway/class-sv-wc-payment-gateway.php:504 +msgid "Check Number is invalid (only digits are allowed)" +msgstr "TÅ¡eki number ei ole korrektne (lubatud on ainult numbrid)" + +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:494 +#, fuzzy +msgid "Unknown error" +msgstr "Esines tundmatu viga" + +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:503 +#, fuzzy +msgid "Payment method address could not be updated. %s" +msgstr "Maksevahend kustutatud." + +#. translators: Placeholders: %1$s - payment method title, %2$s - payment +#. account type (savings/checking) (may or may not be available), %3$s - last +#. four digits of the account +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:673 +#: payment-gateway/class-sv-wc-payment-gateway.php:2741 +msgid "%1$s Check Transaction Approved: %2$s account ending in %3$s" +msgstr "%1$s: tÅ¡eki tehing vastu võetud: %2$s konto, lõpeb numbritega %3$s" + +#. translators: Placeholders: %s - check number +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:678 +#: payment-gateway/class-sv-wc-payment-gateway.php:2746 +msgid "Check number %s" +msgstr "TÅ¡eki number %s" + +#. translators: Placeholders: %1$s - payment method title, %2$s - environment +#. ("Test"), %3$s - transaction type (authorization/charge), %4$s - card type +#. (mastercard, visa, ...), %5$s - last four digits of the card +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:747 +#, fuzzy +msgid "%1$s %2$s %3$s Approved: %4$s ending in %5$s" +msgstr "%1$s %2$s: %3$s kinnitatud: %4$s lõpeb numbritega %5$s (aegub %6$s)" + +#. translators: Placeholders: %s - expiry date +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:760 +#: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:718 +#: payment-gateway/class-sv-wc-payment-gateway.php:2698 +msgid "(expires %s)" +msgstr "(aegub %s)" + +#. translators: Placeholders: %s - failure message +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:832 +msgid "Tokenization Request Failed: %s" +msgstr "Maksevahendi salvestamise päring ebaõnnestus: %s" + +#. translators: Placeholders: %1$s - payment method title, %2$s - failure +#. message +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:843 +msgid "%1$s Tokenization Request Failed: %2$s" +msgstr "%1$s: maksevahendi salvestamise päring ebaõnnestus: %2$s" + +#. translators: Placeholders: %s - failure message. Payment method as in a +#. specific credit card, e-check or bank account +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:901 +msgid "Oops, adding your new payment method failed: %s" +msgstr "Oih, sinu maksevahendi lisamine ebaõnnestus: %s" + +#. translators: Payment method as in a specific credit card. Placeholders: %1$s +#. - card type (visa, mastercard, ...), %2$s - last four digits of the card, +#. %3$s - card expiry date +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:946 +msgid "Nice! New payment method added: %1$s ending in %2$s (expires %3$s)" +msgstr "" +"Lahe! Uus maksevahend lisatud: %1$s, lõpeb numbritega %2$s (aegub %3$s)" + +#. translators: Payment method as in a specific e-check account. Placeholders: +#. %1$s - account type (checking/savings), %2$s - last four digits of the +#. account +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:956 +msgid "Nice! New payment method added: %1$s account ending in %2$s" +msgstr "Lahe! Uus maksevahend lisatud: %1$s konto, lõpeb numbritega %2$s" + +#. translators: Payment method as in a specific credit card, e-check or bank +#. account +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:963 +msgid "Nice! New payment method added." +msgstr "Lahe! Uus maksevahend lisatud." + +#. translators: Placeholders: %1$s - site title, %2$s - customer email. Payment +#. method as in a specific credit card, e-check or bank account +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:1086 +msgid "%1$s - Add Payment Method for %2$s" +msgstr "%1$s - Lisa maksevahend kliendile %2$s" + +#: payment-gateway/class-sv-wc-payment-gateway-helper.php:180 +msgid "PayPal" +msgstr "PayPal" + +#: payment-gateway/class-sv-wc-payment-gateway-helper.php:181 +msgid "Checking Account" +msgstr "TÅ¡ekikonto" + +#: payment-gateway/class-sv-wc-payment-gateway-helper.php:182 +msgid "Savings Account" +msgstr "Hoiuarve" + +#: payment-gateway/class-sv-wc-payment-gateway-helper.php:183 +msgid "Credit / Debit Card" +msgstr "Deebet- või krediitkaart" + +#: payment-gateway/class-sv-wc-payment-gateway-helper.php:184 +msgid "Bank Account" +msgstr "Pangakonto" + +#: payment-gateway/class-sv-wc-payment-gateway-hosted.php:301 +msgid "Thank you for your order, please click the button below to pay." +msgstr "Aitäh tellimuse eest. Palun kliki maksmiseks alloleval nupul." + +#: payment-gateway/class-sv-wc-payment-gateway-hosted.php:302 +msgid "" +"Thank you for your order. We are now redirecting you to complete payment." +msgstr "Aitäh tellimuse eest. Makse teostamiseks suunatakse sind nüüd edasi." + +#: payment-gateway/class-sv-wc-payment-gateway-hosted.php:303 +msgid "Pay Now" +msgstr "Maksa" + +#: payment-gateway/class-sv-wc-payment-gateway-hosted.php:304 +msgid "Cancel Order" +msgstr "Tühista tellimus" + +#. translators: Placeholders: %1$s - payment gateway title (such as +#. Authorize.net, Braintree, etc), %2$s - payment method name (mastercard, bank +#. account, etc), %3$s - last four digits of the card/account, %4$s - +#. card/account expiry date +#: payment-gateway/class-sv-wc-payment-gateway-hosted.php:601 +#: payment-gateway/payment-tokens/class-sv-wc-payment-gateway-payment-tokens-handler.php:1114 +msgid "%1$s Payment Method Saved: %2$s ending in %3$s (expires %4$s)" +msgstr "" +"%1$s: maksevahend salvestatud: %2$s lõpeb numbritega in %3$s (aegub %4$s)" + +#. translators: Placeholders: %1$s - payment gateway title (such as CyberSouce, +#. NETbilling, etc), %2$s - account type (checking/savings - may or may not be +#. available), %3$s - last four digits of the account +#: payment-gateway/class-sv-wc-payment-gateway-hosted.php:612 +#: payment-gateway/payment-tokens/class-sv-wc-payment-gateway-payment-tokens-handler.php:1125 +msgid "%1$s eCheck Payment Method Saved: %2$s account ending in %3$s" +msgstr "" +"%1$s: e-tÅ¡eki maksevahend salvestatud: %2$s konto, lõpeb numbritega %3$s" + +#. translators: Placeholders: %s - payment gateway title (such as CyberSouce, +#. NETbilling, etc) +#: payment-gateway/class-sv-wc-payment-gateway-hosted.php:621 +#, fuzzy +msgid "%s Payment Method Saved" +msgstr "Minu maksevahendid." + +#. translators: Placeholders: %s - a failed tokenization API error +#: payment-gateway/class-sv-wc-payment-gateway-hosted.php:630 +#, fuzzy +msgid "Tokenization failed. %s" +msgstr "Maksevahendi salvestamise päring ebaõnnestus: %s" + +#: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:293 +#: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:607 +msgid "Edit" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:337 +#: payment-gateway/class-sv-wc-payment-gateway.php:1269 +msgid "Title" +msgstr "Nimetus" + +#: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:340 +msgid "Details" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:343 +#, fuzzy +msgid "Default?" +msgstr "(vaikimisi)" + +#: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:609 +#, fuzzy +msgid "" +"Oops, there was an error updating your payment method. Please try again." +msgstr "Sinu päringuga esines viga, palun proovi uuesti." + +#: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:610 +msgid "Are you sure you want to delete this payment method?" +msgstr "Oled sa kindel, et soovid selle maksevahendi kustutada?" + +#. translators: Payment method as in a specific credit card, eCheck or bank +#. account +#: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:697 +msgid "You do not have any saved payment methods." +msgstr "Sul ei ole salvestatud maksevahendeid." + +#: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:872 +#: payment-gateway/class-sv-wc-payment-gateway-privacy.php:200 +msgid "Nickname" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:1118 +msgid "Oops, you took too long, please try again." +msgstr "Oih, sul läks liiga kaua aega - palun proovi uuesti." + +#: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:1129 +msgid "There was an error with your request, please try again." +msgstr "Sinu päringuga esines viga, palun proovi uuesti." + +#: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:344 +msgid "Card Number" +msgstr "Kaardi number" + +#: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:365 +msgid "MM / YY" +msgstr "KK / AA" + +#: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:384 +msgid "Card Security Code" +msgstr "Kaardi turvakood" + +#: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:387 +msgid "CSC" +msgstr "Turvakood" + +#: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:427 +msgid "Where do I find this?" +msgstr "Kust ma selle leian?" + +#. translators: e-check routing number, HTML form field label, +#. https:en.wikipedia.org/wiki/Routing_transit_number +#: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:433 +msgid "Routing Number" +msgstr "Suunakood" + +#. translators: e-check account number, HTML form field label +#: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:452 +msgid "Account Number" +msgstr "Kontonumber" + +#. translators: Test mode refers to the current software environment +#: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:518 +msgid "TEST MODE ENABLED" +msgstr "TESTREŽIIM SISSE LÃœLITATUD" + +#: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:545 +#, fuzzy +msgid "Sample Check" +msgstr "E-tÅ¡ekk" + +#. translators: Payment method as in a specific credit card, eCheck or bank +#. account +#: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:620 +msgid "Manage Payment Methods" +msgstr "Halda maksevahendeid" + +#: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:757 +msgid "Use a new card" +msgstr "Kasuta uut kaarti" + +#: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:757 +msgid "Use a new bank account" +msgstr "Kasuta uut pangakontot" + +#. translators: account as in customer's account on the eCommerce site +#: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:820 +msgid "Securely Save to Account" +msgstr "Salvesta turvaliselt oma kontole" + +#: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:954 +#, fuzzy +msgid "Payment Info" +msgstr "Maksevahendid" + +#. translators: Placeholders: %1$s - plugin name, %2$s - tag, %3$s - +#. tag +#: payment-gateway/class-sv-wc-payment-gateway-plugin.php:706 +#, fuzzy +msgid "" +"%1$s: WooCommerce is not being forced over SSL; your customers' payment data " +"may be at risk. %2$sVerify your site URLs here%3$s" +msgstr "" +"%s: WooCommerce'i ei sunnita SSLi kasutama; sinu klientide andmed võivad " +"olla ohus." + +#. translators: Placeholders: %s - payment gateway name +#: payment-gateway/class-sv-wc-payment-gateway-plugin.php:723 +msgid "" +"%s will soon require TLS 1.2 support to process transactions and your server " +"environment may need to be updated. Please contact your hosting provider to " +"confirm that your site can send and receive TLS 1.2 connections and request " +"they make any necessary updates." +msgstr "" + +#. translators: Placeholders: %1$s - plugin name, %2$s - a +#. currency/comma-separated list of currencies, %3$s - tag, %4$s - tag +#: payment-gateway/class-sv-wc-payment-gateway-plugin.php:779 +msgid "" +"%1$s accepts payment in %2$s only. %3$sConfigure%4$s WooCommerce to accept " +"%2$s to enable this gateway for checkout." +msgid_plural "" +"%1$s accepts payment in one of %2$s only. %3$sConfigure%4$s WooCommerce to " +"accept one of %2$s to enable this gateway for checkout." +msgstr[0] "" +msgstr[1] "" + +#. translators: Placeholders: %1$s - payment gateway name, %2$s - opening +#. tag, %3$s - closing tag +#: payment-gateway/class-sv-wc-payment-gateway-plugin.php:814 +msgid "" +"Heads up! %1$s is currently configured to log transaction data for debugging " +"purposes. If you are not experiencing any problems with payment processing, " +"we recommend %2$sturning off Debug Mode%3$s" +msgstr "" + +#. translators: Placeholders: %s - gateway name +#: payment-gateway/class-sv-wc-payment-gateway-plugin.php:865 +msgid "%s is not configured" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-plugin.php:877 +msgid "Dismiss" +msgstr "Loobu" + +#. translators: Placeholders: %1$s - plugin name, %2$s - opening HTML link +#. tag, %3$s - closing HTML link tag +#: payment-gateway/class-sv-wc-payment-gateway-plugin.php:914 +msgid "" +"Heads up! Apple Pay for %1$s requires WooCommerce version 3.2 or greater. " +"Please %2$supdate WooCommerce%3$s." +msgstr "" + +#. translators: Placeholders: %1$s - plugin name, %2$s - opening HTML link +#. tag, %3$s - closing HTML link tag +#: payment-gateway/class-sv-wc-payment-gateway-plugin.php:938 +msgid "" +"Heads up! Google Pay for %1$s requires WooCommerce version 3.2 or greater. " +"Please %2$supdate WooCommerce%3$s." +msgstr "" + +#. translators: Placeholders: %1$s - payment gateway title (such as +#. Authorize.net, Braintree, etc), %2$s - tag, %3$s - tag +#: payment-gateway/class-sv-wc-payment-gateway-plugin.php:974 +msgid "" +"%1$s is inactive for subscription transactions. Please %2$senable " +"tokenization%3$s to activate %1$s for Subscriptions." +msgstr "" +"%1$s ei ole korduvtellimuste jaoks kasutatav. Palun %2$slülita " +"maksevahendite salvestamine%3$s sisse, et aktiveerida %1$s Korduvellimuste " +"(Subscriptions) jaoks." + +#. translators: Placeholders: %1$s - payment gateway title (such as +#. Authorize.net, Braintree, etc), %2$s - tag, %3$s - tag +#: payment-gateway/class-sv-wc-payment-gateway-plugin.php:992 +msgid "" +"%1$s is inactive for pre-order transactions. Please %2$senable " +"tokenization%3$s to activate %1$s for Pre-Orders." +msgstr "" +"%1$s ei ole eeltellimuste maksete jaoks kasutatav. Palun %2$slülita " +"maksevahendite salvestamine%3$s sisse, et aktiveerida %1$s Eeltellimuste " +"(Pre-Orders) jaoks." + +#: payment-gateway/class-sv-wc-payment-gateway-plugin.php:1029 +msgid "" +"You must enable tokenization for this gateway in order to support automatic " +"renewal payments with the WooCommerce Subscriptions extension." +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-plugin.php:1030 +msgid "Inactive" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-privacy.php:115 +#, fuzzy +msgid "%s Customer ID" +msgstr "Kliendi ID" + +#: payment-gateway/class-sv-wc-payment-gateway-privacy.php:184 +#, fuzzy +msgid "Type" +msgstr "Kaardi tüüp" + +#: payment-gateway/class-sv-wc-payment-gateway-privacy.php:254 +msgid "Removed payment token \"%d\"" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-privacy.php:301 +#, fuzzy +msgid "Expiry Date" +msgstr "Aegumiskuupäev (01/%s)" + +#: payment-gateway/class-sv-wc-payment-gateway.php:350 +msgid "you successfully processed a payment!" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:355 +msgid "you successfully processed a refund!" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:505 +msgid "Check Number is missing" +msgstr "TÅ¡eki number on puudu" + +#: payment-gateway/class-sv-wc-payment-gateway.php:506 +msgid "Drivers license state is missing" +msgstr "Juhiloa osariik on puudu" + +#: payment-gateway/class-sv-wc-payment-gateway.php:507 +msgid "Drivers license number is missing" +msgstr "Juhiloa number on puudu" + +#: payment-gateway/class-sv-wc-payment-gateway.php:720 +msgid "Continue to Payment" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:720 +msgid "Place order" +msgstr "Esita tellimus" + +#: payment-gateway/class-sv-wc-payment-gateway.php:752 +msgid "Thank you for your order." +msgstr "Aitäh tellimuse eest." + +#: payment-gateway/class-sv-wc-payment-gateway.php:1221 +msgid "Credit Card" +msgstr "Krediitkaart" + +#: payment-gateway/class-sv-wc-payment-gateway.php:1223 +msgid "eCheck" +msgstr "E-tÅ¡ekk" + +#: payment-gateway/class-sv-wc-payment-gateway.php:1241 +msgid "Pay securely using your credit card." +msgstr "Maksa turvaliselt oma krediitkaardiga." + +#: payment-gateway/class-sv-wc-payment-gateway.php:1243 +msgid "Pay securely using your checking account." +msgstr "Maksa turvaliselt oma tÅ¡ekikontoga." + +#: payment-gateway/class-sv-wc-payment-gateway.php:1263 +msgid "Enable this gateway" +msgstr "Lülita see makseviis sisse" + +#: payment-gateway/class-sv-wc-payment-gateway.php:1271 +msgid "Payment method title that the customer will see during checkout." +msgstr "Kliendile kassas nähtav makseviisi nimetus." + +#: payment-gateway/class-sv-wc-payment-gateway.php:1276 +msgid "Description" +msgstr "Kirjeldus" + +#: payment-gateway/class-sv-wc-payment-gateway.php:1278 +msgid "Payment method description that the customer will see during checkout." +msgstr "Kliendile kassas nähtav makseviisi kirjeldus." + +#: payment-gateway/class-sv-wc-payment-gateway.php:1307 +msgid "Detailed Decline Messages" +msgstr "Täpsemad maksest keeldumise teated" + +#: payment-gateway/class-sv-wc-payment-gateway.php:1309 +msgid "" +"Check to enable detailed decline messages to the customer during checkout " +"when possible, rather than a generic decline message." +msgstr "" +"Lülita see valik sisse, kui soovid klientidele üldise maksest keeldumise " +"teate asemel näidata võimaluse korral täpsemaid põhjusi." + +#. translators: Placeholders: %1$s - tag, %2$s - tag +#: payment-gateway/class-sv-wc-payment-gateway.php:1319 +msgid "" +"Show Detailed Error Messages and API requests/responses on the checkout page " +"and/or save them to the %1$sdebug log%2$s" +msgstr "" +"Näita üksikasjalikke veateateud ja API päringuid/vastuseid kassas ja/või " +"salvesta need %1$slogifaili%2$s" + +#: payment-gateway/class-sv-wc-payment-gateway.php:1323 +msgid "Show on Checkout Page" +msgstr "Näita kassas" + +#. translators: show debugging information on both checkout page and in the log +#: payment-gateway/class-sv-wc-payment-gateway.php:1326 +msgid "Both" +msgstr "Mõlemad" + +#: payment-gateway/class-sv-wc-payment-gateway.php:1392 +msgid "Select the gateway environment to use for transactions." +msgstr "Vali makseviisi tehingute teostamise keskkond." + +#: payment-gateway/class-sv-wc-payment-gateway.php:1446 +msgid "Share connection settings" +msgstr "Jaga ühenduse andmeid" + +#: payment-gateway/class-sv-wc-payment-gateway.php:1448 +msgid "Use connection/authentication settings from other gateway" +msgstr "Kasuta teise makseviisi ühenduse/autentimise seadeid" + +#: payment-gateway/class-sv-wc-payment-gateway.php:1451 +msgid "Disabled because the other gateway is using these settings" +msgstr "Ei saa muuta, kuna teine makseviis kasutab neid seadeid" + +#: payment-gateway/class-sv-wc-payment-gateway.php:1468 +msgid "Card Verification (CSC)" +msgstr "Kaardi turvakood (CSC)" + +#: payment-gateway/class-sv-wc-payment-gateway.php:1469 +msgid "Display the Card Security Code (CV2) field on checkout" +msgstr "Näita kassas kaardi turvakoodi (CV2) välja" + +#: payment-gateway/class-sv-wc-payment-gateway.php:1477 +#, fuzzy +msgid "Saved Card Verification" +msgstr "Kaardi turvakood (CSC)" + +#: payment-gateway/class-sv-wc-payment-gateway.php:1478 +#, fuzzy +msgid "Display the Card Security Code field when paying with a saved card" +msgstr "Näita kassas kaardi turvakoodi (CV2) välja" + +#. translators: Placeholders: %1$s - site title, %2$s - order number +#: payment-gateway/class-sv-wc-payment-gateway.php:1814 +msgid "%1$s - Order %2$s" +msgstr "%1$s - Tellimus %2$s" + +#. translators: Placeholders: %1$s - site title, %2$s - order number. +#. Definitions: Capture as in capture funds from a credit card. +#: payment-gateway/class-sv-wc-payment-gateway.php:1943 +msgid "%1$s - Capture for Order %2$s" +msgstr "%1$s - Tasumine tellimuse %2$s eest" + +#. translators: Placeholders: %1$s - site title, %2$s - order number +#: payment-gateway/class-sv-wc-payment-gateway.php:2086 +msgid "%1$s - Refund for Order %2$s" +msgstr "%1$s - Tagasimakse tellimuse %2$s eest" + +#. translators: Placeholders: %1$s - payment gateway title (such as +#. Authorize.net, Braintree, etc), %2$s - a monetary amount +#: payment-gateway/class-sv-wc-payment-gateway.php:2153 +msgid "%1$s Refund in the amount of %2$s approved." +msgstr "%1$s: tagasimakse summas %2$s kinnitatud." + +#. translators: Placeholders: %1$s - payment gateway title (such as +#. Authorize.net, Braintree, etc), %2$s - error code, %3$s - error message +#: payment-gateway/class-sv-wc-payment-gateway.php:2183 +msgid "%1$s Refund Failed: %2$s - %3$s" +msgstr "%1$s: tagasimakse ebaõnnestus: %2$s - %3$s" + +#. translators: Placeholders: %1$s - payment gateway title (such as +#. Authorize.net, Braintree, etc), %2$s - error message +#: payment-gateway/class-sv-wc-payment-gateway.php:2191 +msgid "%1$s Refund Failed: %2$s" +msgstr "%1$s: tagasimakse ebaõnnestus: %2$s" + +#. translators: Placeholders: %s - payment gateway title (such as +#. Authorize.net, Braintree, etc) +#: payment-gateway/class-sv-wc-payment-gateway.php:2212 +msgid "%s Order completely refunded." +msgstr "%s: tellimus täielikult tagasi makstud." + +#: payment-gateway/class-sv-wc-payment-gateway.php:2267 +msgid "" +"Oops, you cannot partially void this order. Please use the full order amount." +msgstr "" +"Oih, sa ei saa seda tellimust osaliselt tühistada. Palun kasuta tellimuse " +"täissummat." + +#. translators: Placeholders: %1$s - payment gateway title, %2$s - error code, +#. %3$s - error message. Void as in to void an order. +#: payment-gateway/class-sv-wc-payment-gateway.php:2354 +msgid "%1$s Void Failed: %2$s - %3$s" +msgstr "%1$s: tühistamine ebaõnnestus: %2$s - %3$s" + +#. translators: Placeholders: %1$s - payment gateway title, %2$s - error +#. message. Void as in to void an order. +#: payment-gateway/class-sv-wc-payment-gateway.php:2362 +msgid "%1$s Void Failed: %2$s" +msgstr "%1$s: tühistamine ebaõnnestus: %2$s" + +#. translators: Placeholders: %1$s - payment gateway title, %2$s - a monetary +#. amount. Void as in to void an order. +#: payment-gateway/class-sv-wc-payment-gateway.php:2386 +msgid "%1$s Void in the amount of %2$s approved." +msgstr "%1$s: tühistamine summas %2$s kinnitatud." + +#. translators: Placeholders: %1$s - payment method title, %2$s - environment +#. ("Test"), %3$s - transaction type (authorization/charge) +#: payment-gateway/class-sv-wc-payment-gateway.php:2677 +#, fuzzy +msgid "%1$s %2$s %3$s Approved" +msgstr "%1$s: %2$s tehing kinnitatud" + +#. translators: Placeholders: %1$s - credit card type (MasterCard, Visa, +#. etc...), %2$s - last four digits of the card +#: payment-gateway/class-sv-wc-payment-gateway.php:2687 +msgid "%1$s ending in %2$s" +msgstr "" + +#. translators: Placeholders: %1$s - payment gateway title, %2$s - message +#. (probably reason for the transaction being held for review) +#: payment-gateway/class-sv-wc-payment-gateway.php:2783 +msgid "%1$s Transaction Held for Review (%2$s)" +msgstr "%1$s: tehning pandi ülevaatuseks ootele (%2$s)" + +#. translators: Placeholders: %1$s - payment gateway title, %2$s - error +#. message; e.g. Order Note: [Payment method] Payment failed [error] +#: payment-gateway/class-sv-wc-payment-gateway.php:2872 +msgid "%1$s Payment Failed (%2$s)" +msgstr "%1$s: makse ebaõnnestus (%2$s)" + +#. translators: Placeholders: %1$s - payment gateway title, %2$s - +#. message/error +#: payment-gateway/class-sv-wc-payment-gateway.php:2907 +msgid "%1$s Transaction Cancelled (%2$s)" +msgstr "%1$s: tehing tühistatud (%2$s)" + +#: payment-gateway/class-sv-wc-payment-gateway.php:3155 +msgid "Transaction Type" +msgstr "Tehingu tüüp" + +#: payment-gateway/class-sv-wc-payment-gateway.php:3157 +msgid "" +"Select how transactions should be processed. Charge submits all transactions " +"for settlement, Authorization simply authorizes the order total for capture " +"later." +msgstr "" +"Vali, kuidas peaks tehinguid töötlema. \"Makse\" saadab kõik tehingud " +"koheselt tasumisele, \"Autoriseerimine\" lihtsalt autoriseerib tellimuse " +"summa hilisemaks tasumiseks." + +#: payment-gateway/class-sv-wc-payment-gateway.php:3168 +msgid "Charge Virtual-Only Orders" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:3170 +msgid "" +"If the order contains exclusively virtual items, enable this to immediately " +"charge, rather than authorize, the transaction." +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:3178 +#, fuzzy +msgid "Enable Partial Capture" +msgstr "Lülita see makseviis sisse" + +#: payment-gateway/class-sv-wc-payment-gateway.php:3180 +msgid "Allow orders to be partially captured multiple times." +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:3192 +#, fuzzy +msgid "Capture Paid Orders" +msgstr "Teosta makse" + +#: payment-gateway/class-sv-wc-payment-gateway.php:3195 +msgid "Automatically capture orders when they are changed to %s." +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:3196 +msgid "a paid status" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:3386 +#, fuzzy +msgid "Accepted Card Logos" +msgstr "Vastuvõetavad kaardid" + +#: payment-gateway/class-sv-wc-payment-gateway.php:3388 +#, fuzzy +msgid "" +"These are the card logos that are displayed to customers as accepted during " +"checkout." +msgstr "Kliendile kassas nähtav makseviisi nimetus." + +#. translators: Placeholders: %1$s - tag, %2$s - tag +#: payment-gateway/class-sv-wc-payment-gateway.php:3391 +msgid "" +"This setting %1$sdoes not%2$s change which card types the gateway will " +"accept. Accepted cards are configured from your payment processor account." +msgstr "" + +#. translators: +#. http:www.cybersource.com/products/payment_security/payment_tokenization/ and +#. https:en.wikipedia.org/wiki/Tokenization_(data_security) +#: payment-gateway/class-sv-wc-payment-gateway.php:3482 +msgid "Tokenization" +msgstr "Maksevahendite salvestamine" + +#: payment-gateway/class-sv-wc-payment-gateway.php:3483 +msgid "" +"Allow customers to securely save their payment details for future checkout." +msgstr "" +"Võimalda klientidel oma makseandmeid edaspidisteks tehinguteks turvaliselt " +"talletada." + +#. translators: %1$s - gateway name, %2$s - tag, %3$s - tag, %4$s - +#. tag, %5$s - tag +#: payment-gateway/class-sv-wc-payment-gateway.php:4302 +msgid "" +"Heads up! %1$s is not fully configured and cannot accept payments. Please " +"%2$sreview the documentation%3$s and configure the %4$sgateway settings%5$s." +msgstr "" + +#: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-pre-orders.php:261 +msgid "Pre-Order Tokenization attempt failed (%s)" +msgstr "" + +#: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-pre-orders.php:307 +msgid "%s - Pre-Order Release Payment for Order %s" +msgstr "" + +#: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-pre-orders.php:311 +msgid "Payment token missing/invalid." +msgstr "Maksevahend on puudu või vigane." + +#: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-pre-orders.php:336 +msgid "%s %s Pre-Order Release Payment Approved: %s ending in %s (expires %s)" +msgstr "" + +#: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-pre-orders.php:347 +msgid "%s eCheck Pre-Order Release Payment Approved: %s ending in %s" +msgstr "" + +#: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-pre-orders.php:391 +msgid "Pre-Order Release Payment Failed: %s" +msgstr "Eeltellimuse väljastamise makse ebaõnnestus: %s" + +#: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-subscriptions.php:358 +msgid "Subscription Renewal: payment token is missing/invalid." +msgstr "" + +#: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-subscriptions.php:384 +msgid "%1$s - Subscription Renewal Order %2$s" +msgstr "" + +#. translators: Placeholders: %1$s - payment gateway title, %2$s - error +#. message; e.g. Order Note: [Payment method] Payment Change failed [error] +#: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-subscriptions.php:516 +#, fuzzy +msgid "%1$s Payment Change Failed (%2$s)" +msgstr "%1$s: makse ebaõnnestus (%2$s)" + +#: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-subscriptions.php:671 +msgid "Via %s ending in %s" +msgstr "" + +#: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-subscriptions.php:698 +msgid "Subscriptions" +msgstr "" + +#: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-subscriptions.php:759 +msgid "N/A" +msgstr "-" + +#: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-subscriptions.php:858 +msgid "Payment Token" +msgstr "" + +#: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-subscriptions.php:887 +#: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-subscriptions.php:892 +msgid "%s is required." +msgstr "" + +#: payment-gateway/payment-tokens/class-sv-wc-payment-gateway-payment-tokens-handler.php:180 +msgid "Unknown Error" +msgstr "Esines tundmatu viga" + +#: rest-api/Controllers/Settings.php:84 +msgid "Unique identifier for the resource." +msgstr "" + +#: rest-api/Controllers/Settings.php:119 +msgid "Sorry, you cannot list resources." +msgstr "" + +#. translators: Placeholder: %s - setting ID +#: rest-api/Controllers/Settings.php:168 +msgid "Setting %s does not exist" +msgstr "" + +#: rest-api/Controllers/Settings.php:191 +msgid "Sorry, you cannot edit this resource." +msgstr "" + +#: rest-api/Controllers/Settings.php:224 +msgid "Could not update setting: %s" +msgstr "" + +#: rest-api/Controllers/Settings.php:294 +msgid "Unique identifier of the setting." +msgstr "" + +#: rest-api/Controllers/Settings.php:300 +msgid "The type of the setting." +msgstr "" + +#: rest-api/Controllers/Settings.php:307 +msgid "The name of the setting." +msgstr "" + +#: rest-api/Controllers/Settings.php:313 +msgid "The description of the setting. It may or may not be used for display." +msgstr "" + +#: rest-api/Controllers/Settings.php:319 +msgid "Whether the setting stores an array of values or a single value." +msgstr "" + +#: rest-api/Controllers/Settings.php:325 +msgid "A list of valid options, used for validation before storing the value." +msgstr "" + +#: rest-api/Controllers/Settings.php:331 +msgid "Optional default value for the setting." +msgstr "" + +#: rest-api/Controllers/Settings.php:337 +msgid "The value of the setting." +msgstr "" + +#: rest-api/Controllers/Settings.php:342 +msgid "" +"Optional object that defines how the user will interact with and update the " +"setting." +msgstr "" + +#: rest-api/Controllers/Settings.php:346 +msgid "The type of the control." +msgstr "" + +#: rest-api/Controllers/Settings.php:353 +msgid "The name of the control. Inherits the setting's name." +msgstr "" + +#: rest-api/Controllers/Settings.php:359 +msgid "The description of the control. Inherits the setting's description." +msgstr "" + +#: rest-api/Controllers/Settings.php:365 +msgid "" +"A list of key/value pairs defining the display value of each setting option. " +"The keys should match the options defined in the base setting for validation." +msgstr "" + +#: utilities/class-sv-wp-background-job-handler.php:659 +msgid "Job data key \"%s\" not set" +msgstr "" + +#: utilities/class-sv-wp-background-job-handler.php:663 +msgid "Job data key \"%s\" is not an array" +msgstr "" + +#: utilities/class-sv-wp-background-job-handler.php:899 +msgid "Every %d Minutes" +msgstr "" + +#: utilities/class-sv-wp-background-job-handler.php:1063 +msgid "Background Processing Test" +msgstr "" + +#: utilities/class-sv-wp-background-job-handler.php:1064 +#, fuzzy +msgid "Run Test" +msgstr "test" + +#: utilities/class-sv-wp-background-job-handler.php:1065 +msgid "" +"This tool will test whether your server is capable of processing background " +"jobs." +msgstr "" + +#: utilities/class-sv-wp-background-job-handler.php:1083 +msgid "Success! You should be able to process background jobs." +msgstr "" + +#: utilities/class-sv-wp-background-job-handler.php:1086 +msgid "" +"Could not connect. Please ask your hosting company to ensure your server has " +"loopback connections enabled." +msgstr "" + +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:395 +msgctxt "enhanced select" +msgid "No matches found" +msgstr "" + +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:396 +msgctxt "enhanced select" +msgid "Loading failed" +msgstr "" + +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:397 +msgctxt "enhanced select" +msgid "Please enter 1 or more characters" +msgstr "" + +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:398 +msgctxt "enhanced select" +msgid "Please enter %qty% or more characters" +msgstr "" + +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:399 +msgctxt "enhanced select" +msgid "Please delete 1 character" +msgstr "" + +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:400 +msgctxt "enhanced select" +msgid "Please delete %qty% characters" +msgstr "" + +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:401 +msgctxt "enhanced select" +msgid "You can only select 1 item" +msgstr "" + +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:402 +msgctxt "enhanced select" +msgid "You can only select %qty% items" +msgstr "" + +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:403 +msgctxt "enhanced select" +msgid "Loading more results…" +msgstr "" + +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:404 +msgctxt "enhanced select" +msgid "Searching…" +msgstr "" + +#: class-sv-wc-helper.php:426 +msgctxt "coordinating conjunction for a list of items: a, b, and c" +msgid "and" +msgstr "" + +#: class-sv-wc-plugin.php:602 +msgctxt "noun" +msgid "Support" +msgstr "Kasutajatugi" + +#: class-sv-wc-plugin.php:607 +msgctxt "verb" +msgid "Review" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:749 +#: payment-gateway/class-sv-wc-payment-gateway.php:2679 +msgctxt "noun, software environment" +msgid "Test" +msgstr "test" + +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:750 +#: payment-gateway/class-sv-wc-payment-gateway.php:2680 +#: payment-gateway/class-sv-wc-payment-gateway.php:3161 +msgctxt "credit card transaction type" +msgid "Authorization" +msgstr "Autoriseerimine" + +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:750 +#: payment-gateway/class-sv-wc-payment-gateway.php:2680 +#: payment-gateway/class-sv-wc-payment-gateway.php:3160 +msgctxt "noun, credit card transaction type" +msgid "Charge" +msgstr "Makse" + +#: payment-gateway/class-sv-wc-payment-gateway-helper.php:193 +msgctxt "payment method type" +msgid "Account" +msgstr "Konto" + +#: payment-gateway/class-sv-wc-payment-gateway-helper.php:229 +#: payment-gateway/class-sv-wc-payment-gateway.php:3419 +msgctxt "credit card type" +msgid "Visa" +msgstr "Visa" + +#: payment-gateway/class-sv-wc-payment-gateway-helper.php:233 +#: payment-gateway/class-sv-wc-payment-gateway.php:3420 +msgctxt "credit card type" +msgid "MasterCard" +msgstr "MasterCard" + +#: payment-gateway/class-sv-wc-payment-gateway-helper.php:237 +#: payment-gateway/class-sv-wc-payment-gateway.php:3421 +msgctxt "credit card type" +msgid "American Express" +msgstr "American Express" + +#: payment-gateway/class-sv-wc-payment-gateway-helper.php:241 +msgctxt "credit card type" +msgid "Diners Club" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-helper.php:245 +#: payment-gateway/class-sv-wc-payment-gateway.php:3422 +msgctxt "credit card type" +msgid "Discover" +msgstr "Discover" + +#: payment-gateway/class-sv-wc-payment-gateway-helper.php:249 +#: payment-gateway/class-sv-wc-payment-gateway.php:3424 +msgctxt "credit card type" +msgid "JCB" +msgstr "JCB" + +#: payment-gateway/class-sv-wc-payment-gateway-helper.php:253 +msgctxt "credit card type" +msgid "CarteBleue" +msgstr "CarteBleue" + +#: payment-gateway/class-sv-wc-payment-gateway-helper.php:257 +msgctxt "credit card type" +msgid "Maestro" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-helper.php:261 +msgctxt "credit card type" +msgid "Laser" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:3423 +msgctxt "credit card type" +msgid "Diners" +msgstr "Diners" + +#. translators: http:www.investopedia.com/terms/c/checkingaccount.asp +#: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:478 +msgctxt "account type" +msgid "Checking" +msgstr "TÅ¡ekikonto" + +#. translators: http:www.investopedia.com/terms/s/savingsaccount.asp +#: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:480 +msgctxt "account type" +msgid "Savings" +msgstr "Hoiuarve" + +#: payment-gateway/class-sv-wc-payment-gateway.php:2461 +msgctxt "hash before order number" +msgid "#" +msgstr "#" + +#: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-subscriptions.php:753 +msgctxt "hash before order number" +msgid "#%s" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:3189 +msgctxt "" +"coordinating conjunction for a list of order statuses: on-hold, processing, " +"or completed" +msgid "or" +msgstr "" + +#. translators: https:www.skyverge.com/for-translators-environments/ +#: payment-gateway/class-sv-wc-payment-gateway.php:4028 +msgctxt "software environment" +msgid "Production" +msgstr "Töö/avalik" + +#~ msgid "My Payment Methods" +#~ msgstr "Minu maksevahendid." + +#~ msgid "Add New Payment Method" +#~ msgstr "Lisa uus maksevahend" + +#~ msgid "Method" +#~ msgstr "Maksevahend" + +#~ msgid "Expires" +#~ msgstr "Aegub" + +#~ msgid "Credit/Debit Cards" +#~ msgstr "Deebet- ja krediitkaardid" + +#~ msgid "Bank Accounts" +#~ msgstr "Pangakontod" + +#~ msgid "Delete" +#~ msgstr "Kustuta" + +#~ msgid "Error removing payment method" +#~ msgstr "Viga maksevahendi eemaldamisel" + +#~ msgid "Payment method deleted." +#~ msgstr "Maksevahend kustutatud." + +#, fuzzy +#~ msgid "Pay with" +#~ msgstr "Maksa" + +#~ msgid "%1$s Capture Failed: %2$s - %3$s" +#~ msgstr "%1$s: tasumine ebaõnnestus: %2$s - %3$s" + +#~ msgid "(check number %s)" +#~ msgstr "(tÅ¡eki number %s)" + +#~ msgid "Make Default" +#~ msgstr "Määra vaikimisi valikuks" + +#~ msgid "ending in %s" +#~ msgstr "lõpeb numbritega %s" + +#~ msgid "Default payment method updated." +#~ msgstr "Vaikimisi maksevahend muudetud." + +#~ msgid "Select which card types you accept." +#~ msgstr "Vali, millist tüüpi kaarte soovid vastu võtta." + +#~ msgid "" +#~ "The following plugins are inactive because they require a newer version " +#~ "to function properly:" +#~ msgstr "" +#~ "Järgnevad pluginad ei ole aktiivsed ja vajavad toimimiseks uuendamist:" + +#~ msgid "" +#~ "The following plugin is inactive because it requires a newer version to " +#~ "function properly:" +#~ msgstr "Järgnev plugin ei ole aktiivne ja vajab toimimiseks uuendamist:" + +#~ msgid "" +#~ "To reactivate these plugins, please %1$supdate now (recommended)%2$s " +#~ "%3$sor%4$s %5$sdeactivate the following%6$s:" +#~ msgstr "" +#~ "Et neid pluginaid taas sisse lülitada, %1$suuenda palun kohe " +#~ "(soovitatud)%2$s %3$svõi%4$s %5$slülita järgnevad välja%6$s:" + +#~ msgid "" +#~ "To reactivate this plugin, please %1$supdate now (recommended)%2$s " +#~ "%3$sor%4$s %5$sdeactivate the following%6$s:" +#~ msgstr "" +#~ "Et see plugin taas sisse lülitada, %1$suuenda palun kohe (soovitatud)%2$s " +#~ "%3$svõi%4$s %5$slülita järgnevad välja%6$s:" + +#~ msgid "%s Customer Details" +#~ msgstr "%s: kliendi andmed" + +#~ msgid "Customer ID (%s)" +#~ msgstr "Kliendi ID (%s)" + +#~ msgid "" +#~ "The gateway customer ID for the user in the %s environment. Only edit " +#~ "this if necessary." +#~ msgstr "" +#~ "Kasutajale makseviisi poolt määratud kliendi tunnus %s keskkonnas. Muuda " +#~ "seda ainult siis, kui tõesti vajalik." + +#~ msgid "This customer has no saved payment tokens" +#~ msgstr "Sel kliendil ei ole salvestatud maksevahendeid" + +#~ msgid "Default card" +#~ msgstr "Vaikimisi kaart" + +#~ msgid "Add a Payment Token" +#~ msgstr "Lisa maksevahend" + +#~ msgid "Token" +#~ msgstr "Vahend" + +#~ msgid "%s Subscription Renewal Approved" +#~ msgstr "Korduvtellimus summas %s uuendus kinnitatud" + +#~ msgid "Subscription Renewal: Payment Token or User ID is missing/invalid." +#~ msgstr "" +#~ "Korduvtellimuse uuendus: maksevahend või kasutajatunnus on puudu või " +#~ "vigane." + +#~ msgid "" +#~ "%1$s %2$s Subscription Renewal Payment Approved: %3$s ending in %4$s " +#~ "(expires %5$s)" +#~ msgstr "" +#~ "%1$s: %2$s korduvtellimuse uuendamise makse kinnitatud: %3$s lõpeb " +#~ "numbritega %4$s (aegub %5$s)" + +#~ msgid "" +#~ "%1$s Check Subscription Renewal Payment Approved: %2$s account ending in " +#~ "%3$s" +#~ msgstr "" +#~ "%1$s: tÅ¡ekiga tasutav korduvtellimuse uuendamise makse kinnitatud: %2$s " +#~ "konto, lõpeb numbritega %3$s" + +#~ msgid "Via %1$s ending in %2$s" +#~ msgstr "%1$s, lõpeb numbritega %2$s" + +#~ msgid "%1$s Pre-Order Tokenization attempt failed (%2$s)" +#~ msgstr "" +#~ "%1$s: eeltellimuse maksevahendi salvestamise katse ebaõnnestus (%2$s)" + +#~ msgid "%1$s - Pre-Order Release Payment for Order %2$s" +#~ msgstr "%1$s - Eeltellimuse väljastamise makse tellimuse %2$s eest" + +#~ msgid "" +#~ "%1$s %2$s Pre-Order Release Payment Approved: %3$s ending in %4$s " +#~ "(expires %5$s)" +#~ msgstr "" +#~ "%1$s: %2$s eeltellimuse väljastamise makse vastu kinnitatud: %3$s lõpeb " +#~ "numbritega %4$s (aegub %5$s)" + +#~ msgid "%1$s eCheck Pre-Order Release Payment Approved: %2$s ending in %3$s" +#~ msgstr "" +#~ "%1$s: e-tÅ¡eki eeltellimuse väljastamise makse kinnitatud: %2$s lõpeb " +#~ "numbritega %3$s" + +#~ msgid "IPN processing error: %s duplicate transaction received" +#~ msgstr "IPN töötlemise viga: %s duplikaattehing" + +#~ msgid "%1$s %2$s Transaction Approved: %3$s ending in %4$s" +#~ msgstr "%1$s: %2$s tehing kinnitatud: %3$s lõpeb numbritega %4$s" + +#~ msgctxt "Supports capture charge" +#~ msgid "Capture Charge" +#~ msgstr "Makse " diff --git a/vendor/skyverge/wc-plugin-framework/woocommerce/i18n/languages/woocommerce-plugin-framework.pot b/vendor/skyverge/wc-plugin-framework/woocommerce/i18n/languages/woocommerce-plugin-framework.pot new file mode 100644 index 0000000..558f713 --- /dev/null +++ b/vendor/skyverge/wc-plugin-framework/woocommerce/i18n/languages/woocommerce-plugin-framework.pot @@ -0,0 +1,2034 @@ +# Copyright (C) 2023 +# This file is distributed under the same license as the package. +msgid "" +msgstr "" +"Project-Id-Version: WooCommerce Plugin Framework 5.11.0\n" +"Report-Msgid-Bugs-To: https://support.woocommerce.com/hc/\n" +"POT-Creation-Date: 2023-03-27 07:41:54+00:00\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=utf-8\n" +"Content-Transfer-Encoding: 8bit\n" +"PO-Revision-Date: 2023-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" + +#: Lifecycle.php:394 +msgid "Awesome" +msgstr "" + +#: Lifecycle.php:395 +msgid "Fantastic" +msgstr "" + +#: Lifecycle.php:396 +msgid "Cowabunga" +msgstr "" + +#: Lifecycle.php:397 +msgid "Congratulations" +msgstr "" + +#: Lifecycle.php:398 +msgid "Hot dog" +msgstr "" + +#: Lifecycle.php:405 +#. translators: Placeholders: %1$s - plugin name, %2$s - tag, %3$s - +#. tag, %4$s - tag, %5$s - tag +msgid "" +"Are you having a great experience with %1$s so far? Please consider " +"%2$sleaving a review%3$s! If things aren't going quite as expected, we're " +"happy to help -- please %4$sreach out to our support team%5$s." +msgstr "" + +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:182 +msgid "" +"Thanks for installing %1$s! To get started, take a minute to %2$sread the " +"documentation%3$s :)" +msgstr "" + +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:210 +msgid "" +"Thanks for installing %1$s! To get started, take a minute to complete these " +"%2$squick and easy setup steps%3$s :)" +msgstr "" + +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:235 +msgid "Setup" +msgstr "" + +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:303 +#. translators: Placeholders: %s - plugin name +msgid "%s › Setup" +msgstr "" + +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:350 +msgid "Oops! An error occurred, please try again." +msgstr "" + +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:488 +msgid "Ready!" +msgstr "" + +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:581 +#. translators: Placeholder: %s - plugin name +msgid "Welcome to %s!" +msgstr "" + +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:594 +msgid "" +"This quick setup wizard will help you configure the basic settings and get " +"you started." +msgstr "" + +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:608 +msgid "%s is ready!" +msgstr "" + +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:660 +msgid "Next step" +msgstr "" + +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:686 +msgid "You can also:" +msgstr "" + +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:730 +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:760 +msgid "View the Docs" +msgstr "" + +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:731 +msgid "See more setup options" +msgstr "" + +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:732 +msgid "Learn more about customizing the plugin" +msgstr "" + +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:756 +msgid "Review Your Settings" +msgstr "" + +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:764 +msgid "Leave a Review" +msgstr "" + +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:788 +msgid "Continue" +msgstr "" + +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:948 +msgid "Return to the WordPress Dashboard" +msgstr "" + +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:950 +msgid "Not right now" +msgstr "" + +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:952 +msgid "Skip this step" +msgstr "" + +#: class-sv-wc-framework-bootstrap.php:268 +msgid "" +"The following plugin is disabled because it is out of date and incompatible " +"with newer plugins on your site:" +msgid_plural "" +"The following plugins are disabled because they are out of date and " +"incompatible with newer plugins on your site:" +msgstr[0] "" +msgstr[1] "" + +#: class-sv-wc-framework-bootstrap.php:282 +msgid "" +"To resolve this, please %1$supdate%2$s (recommended) %3$sor%4$s " +"%5$sdeactivate%6$s the above plugin, or %7$sdeactivate the following%8$s:" +msgid_plural "" +"To resolve this, please %1$supdate%2$s (recommended) %3$sor%4$s " +"%5$sdeactivate%6$s the above plugins, or %7$sdeactivate the following%8$s:" +msgstr[0] "" +msgstr[1] "" + +#: class-sv-wc-framework-bootstrap.php:303 +msgid "" +"The following plugins are inactive because they require a newer version of " +"WooCommerce:" +msgstr "" + +#: class-sv-wc-framework-bootstrap.php:303 +msgid "" +"The following plugin is inactive because it requires a newer version of " +"WooCommerce:" +msgstr "" + +#: class-sv-wc-framework-bootstrap.php:308 +#. translators: Placeholders: %1$s - plugin name, %2$s - WooCommerce version +#. number +msgid "%1$s requires WooCommerce %2$s or newer" +msgstr "" + +#: class-sv-wc-framework-bootstrap.php:312 +#. translators: Placeholders: %1$s - tag, %2$s - tag +msgid "Please %1$supdate WooCommerce%2$s" +msgstr "" + +#: class-sv-wc-plugin-compatibility.php:351 +msgid "WooCommerce" +msgstr "" + +#: class-sv-wc-plugin-dependencies.php:156 +#. translators: Placeholders: %1$s - plugin name, %2$s - a PHP +#. extension/comma-separated list of PHP extensions +msgid "" +"%1$s requires the %2$s PHP extension to function. Contact your host or " +"server administrator to install and configure the missing extension." +msgid_plural "" +"%1$s requires the following PHP extensions to function: %2$s. Contact your " +"host or server administrator to install and configure the missing " +"extensions." +msgstr[0] "" +msgstr[1] "" + +#: class-sv-wc-plugin-dependencies.php:184 +#. translators: Placeholders: %1$s - plugin name, %2$s - a PHP +#. function/comma-separated list of PHP functions +msgid "" +"%1$s requires the %2$s PHP function to exist. Contact your host or server " +"administrator to install and configure the missing function." +msgid_plural "" +"%1$s requires the following PHP functions to exist: %2$s. Contact your " +"host or server administrator to install and configure the missing functions." +msgstr[0] "" +msgstr[1] "" + +#: class-sv-wc-plugin-dependencies.php:214 +#. translators: Placeholders: %s - plugin name +msgid "" +"%s may behave unexpectedly because the following PHP configuration settings " +"are required:" +msgstr "" + +#: class-sv-wc-plugin-dependencies.php:228 +msgid "%s or higher" +msgstr "" + +#: class-sv-wc-plugin-dependencies.php:238 +msgid "" +"Please contact your hosting provider or server administrator to configure " +"these settings." +msgstr "" + +#: class-sv-wc-plugin-dependencies.php:260 +#. translators: Placeholders: %1$s - , %2$s - +msgid "" +"Hey there! We've noticed that your server is running %1$san outdated " +"version of PHP%2$s, which is the programming language that WooCommerce and " +"its extensions are built on.\n" +"\t\t\t\t\tThe PHP version that is currently used for your site is no longer " +"maintained, nor %1$sreceives security updates%2$s; newer versions are " +"faster and more secure.\n" +"\t\t\t\t\tAs a result, %3$s no longer supports this version and you should " +"upgrade PHP as soon as possible.\n" +"\t\t\t\t\tYour hosting provider can do this for you. %4$sHere are some " +"resources to help you upgrade%5$s and to explain PHP versions further." +msgstr "" + +#: class-sv-wc-plugin.php:310 +#. translators: Placeholders: %s - plugin name +msgid "You cannot clone instances of %s." +msgstr "" + +#: class-sv-wc-plugin.php:321 +#. translators: Placeholders: %s - plugin name +msgid "You cannot unserialize instances of %s." +msgstr "" + +#: class-sv-wc-plugin.php:597 +#. translators: Docs as in Documentation +msgid "Docs" +msgstr "" + +#: class-sv-wc-plugin.php:712 +msgid "%1$s - A minimum of %2$s is required." +msgstr "" + +#: class-sv-wc-plugin.php:721 +msgid "Set as %1$s - %2$s is required." +msgstr "" + +#: class-sv-wc-plugin.php:1015 +#: payment-gateway/class-sv-wc-payment-gateway-plugin.php:876 +msgid "Configure" +msgstr "" + +#: payment-gateway/External_Checkout/Admin.php:137 +#: payment-gateway/External_Checkout/Admin.php:147 +msgid "Processing Gateway" +msgstr "" + +#: payment-gateway/External_Checkout/Admin.php:287 +msgid "Single products" +msgstr "" + +#: payment-gateway/External_Checkout/Admin.php:288 +msgid "Cart" +msgstr "" + +#: payment-gateway/External_Checkout/Admin.php:289 +msgid "Checkout" +msgstr "" + +#: payment-gateway/External_Checkout/Admin.php:330 +#. translators: Placeholder: %s - external checkout label +msgid "%s is disabled." +msgstr "" + +#: payment-gateway/External_Checkout/Admin.php:394 +#. translators: Placeholders: %1$s - plugin name, %2$s - a +#. currency/comma-separated list of currencies, %3$s - tag, %4$s - +#. tag, %5$s - external checkout label +msgid "" +"Accepts payment in %1$s only. %2$sConfigure%3$s WooCommerce to accept %1$s " +"to enable %4$s." +msgid_plural "" +"Accepts payment in one of %1$s only. %2$sConfigure%3$s WooCommerce to " +"accept one of %1$s to enable %4$s." +msgstr[0] "" +msgstr[1] "" + +#: payment-gateway/External_Checkout/Admin.php:436 +msgid "" +"%4$s%1$s Notice!%5$s Your store %2$scalculates taxes%3$s based on the " +"shipping address, but %1$s %4$sdoes not%5$s share customer shipping " +"information with your store for orders with only virtual products. These " +"orders will have their taxes calculated based on the shop address instead." +msgstr "" + +#: payment-gateway/External_Checkout/Admin.php:470 +msgid "" +"%4$s%1$s Notice!%5$s Your store %2$scalculates taxes%3$s based on the " +"billing address, but %1$s %4$sdoes not%5$s share the customer billing " +"address with your store before payment. These orders will have their taxes " +"calculated based on the shipping address (or shop address, for orders with " +"only virtual products)." +msgstr "" + +#: payment-gateway/External_Checkout/Frontend.php:259 +msgid "or" +msgstr "" + +#: payment-gateway/External_Checkout/Frontend.php:293 +msgid "By submitting your payment, you agree to our %1$sterms and conditions%2$s." +msgstr "" + +#: payment-gateway/External_Checkout/Google_Pay/Admin.php:71 +#: payment-gateway/External_Checkout/Google_Pay/Admin.php:87 +#: payment-gateway/External_Checkout/Google_Pay/Google_Pay.php:66 +msgid "Google Pay" +msgstr "" + +#: payment-gateway/External_Checkout/Google_Pay/Admin.php:93 +#: payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:93 +#: payment-gateway/class-sv-wc-payment-gateway.php:1262 +msgid "Enable / Disable" +msgstr "" + +#: payment-gateway/External_Checkout/Google_Pay/Admin.php:94 +msgid "Accept Google Pay" +msgstr "" + +#: payment-gateway/External_Checkout/Google_Pay/Admin.php:101 +msgid "Allow Google Pay on" +msgstr "" + +#: payment-gateway/External_Checkout/Google_Pay/Admin.php:111 +#: payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:111 +msgid "Button Style" +msgstr "" + +#: payment-gateway/External_Checkout/Google_Pay/Admin.php:114 +#: payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:114 +msgid "Black" +msgstr "" + +#: payment-gateway/External_Checkout/Google_Pay/Admin.php:115 +#: payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:115 +msgid "White" +msgstr "" + +#: payment-gateway/External_Checkout/Google_Pay/Admin.php:148 +#: payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:150 +#: payment-gateway/class-sv-wc-payment-gateway.php:1440 +msgid "Connection Settings" +msgstr "" + +#: payment-gateway/External_Checkout/Google_Pay/Admin.php:157 +#: payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:188 +msgid "Test Mode" +msgstr "" + +#: payment-gateway/External_Checkout/Google_Pay/Admin.php:158 +msgid "" +"Enable to test Google Pay functionality throughout your sites without " +"processing real payments." +msgstr "" + +#: payment-gateway/External_Checkout/Google_Pay/Frontend.php:130 +#: payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php:141 +#: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:99 +msgid "An error occurred, please try again or try an alternate form of payment" +msgstr "" + +#: payment-gateway/External_Checkout/Google_Pay/Google_Pay.php:255 +#: payment-gateway/External_Checkout/Google_Pay/Google_Pay.php:380 +#: payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay.php:539 +msgid "Subtotal" +msgstr "" + +#: payment-gateway/External_Checkout/Google_Pay/Google_Pay.php:390 +#: payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay.php:549 +msgid "Discount" +msgstr "" + +#: payment-gateway/External_Checkout/Google_Pay/Google_Pay.php:400 +#: payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay.php:559 +msgid "Shipping" +msgstr "" + +#: payment-gateway/External_Checkout/Google_Pay/Google_Pay.php:410 +#: payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay.php:569 +msgid "Fees" +msgstr "" + +#: payment-gateway/External_Checkout/Google_Pay/Google_Pay.php:420 +#: payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay.php:579 +msgid "Taxes" +msgstr "" + +#: payment-gateway/External_Checkout/Google_Pay/Google_Pay.php:463 +msgid "Google Pay payment authorized." +msgstr "" + +#: payment-gateway/External_Checkout/Google_Pay/Google_Pay.php:538 +msgid "Google Pay payment failed. %s" +msgstr "" + +#: payment-gateway/External_Checkout/Orders.php:140 +#: payment-gateway/External_Checkout/Orders.php:153 +#: payment-gateway/External_Checkout/Orders.php:157 +msgid "Error %d: Unable to create order. Please try again." +msgstr "" + +#: payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:71 +#: payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:87 +#: payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay.php:65 +msgid "Apple Pay" +msgstr "" + +#: payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:94 +msgid "Accept Apple Pay" +msgstr "" + +#: payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:101 +msgid "Allow Apple Pay on" +msgstr "" + +#: payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:116 +msgid "White with outline" +msgstr "" + +#: payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:159 +msgid "Apple Merchant ID" +msgstr "" + +#: payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:163 +msgid "This is found in your %1$sApple developer account%2$s" +msgstr "" + +#: payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:173 +msgid "Certificate Path" +msgstr "" + +#: payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:178 +#. translators: Placeholders: %s - the server's web root path +msgid "For reference, your current web root path is: %s" +msgstr "" + +#: payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:189 +msgid "" +"Enable to test Apple Pay functionality throughout your sites without " +"processing real payments." +msgstr "" + +#: payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:228 +msgid "Your site must be served over HTTPS with a valid SSL certificate." +msgstr "" + +#: payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay-admin.php:238 +msgid "" +"Your %1$sMerchant Identity Certificate%2$s cannot be found. Please check " +"your path configuration." +msgstr "" + +#: payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay-frontend.php:176 +msgid "Buy with" +msgstr "" + +#: payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay.php:152 +msgid "Apple Pay payment authorized." +msgstr "" + +#: payment-gateway/External_Checkout/apple-pay/class-sv-wc-payment-gateway-apple-pay.php:186 +msgid "Apple Pay payment failed. %s" +msgstr "" + +#: payment-gateway/Handlers/Abstract_Hosted_Payment_Handler.php:179 +msgid "" +"There was a problem processing your order and it is being placed on hold " +"for review. Please contact us to complete the transaction." +msgstr "" + +#: payment-gateway/Handlers/Abstract_Hosted_Payment_Handler.php:217 +#: payment-gateway/class-sv-wc-payment-gateway.php:2889 +#: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-subscriptions.php:521 +msgid "An error occurred, please try again or try an alternate form of payment." +msgstr "" + +#: payment-gateway/Handlers/Abstract_Hosted_Payment_Handler.php:320 +#: payment-gateway/class-sv-wc-payment-gateway-hosted.php:449 +#. translators: Placeholders: %s - a WooCommerce order ID +msgid "Could not find order %s" +msgstr "" + +#: payment-gateway/Handlers/Abstract_Payment_Handler.php:152 +#: payment-gateway/class-sv-wc-payment-gateway.php:2483 +#: payment-gateway/payment-tokens/class-sv-wc-payment-gateway-payment-tokens-handler.php:172 +#. translators: Placeholders: %1$s - status code, %2$s - status message +#. translators: Placeholders: %1$s - payment request response status code, %2$s +#. - payment request response status message +msgid "Status code %1$s: %2$s" +msgstr "" + +#: payment-gateway/Handlers/Abstract_Payment_Handler.php:155 +#: payment-gateway/class-sv-wc-payment-gateway.php:2486 +#: payment-gateway/payment-tokens/class-sv-wc-payment-gateway-payment-tokens-handler.php:175 +#. translators: Placeholders: %s - status code +#. translators: Placeholders: %s - payment request response status code +msgid "Status code: %s" +msgstr "" + +#: payment-gateway/Handlers/Abstract_Payment_Handler.php:158 +#: payment-gateway/class-sv-wc-payment-gateway.php:2489 +#: payment-gateway/payment-tokens/class-sv-wc-payment-gateway-payment-tokens-handler.php:178 +#. translators: Placeholders; %s - status message +#. translators: Placeholders: %s - payment request response status message +msgid "Status message: %s" +msgstr "" + +#: payment-gateway/Handlers/Abstract_Payment_Handler.php:163 +#: payment-gateway/class-sv-wc-payment-gateway.php:2494 +#: payment-gateway/payment-tokens/class-sv-wc-payment-gateway-payment-tokens-handler.php:185 +msgid "Transaction ID %s" +msgstr "" + +#: payment-gateway/Handlers/Abstract_Payment_Handler.php:204 +#: payment-gateway/class-sv-wc-payment-gateway-hosted.php:513 +#. translators: Placeholders: %s - payment gateway title (such as +#. Authorize.net, Braintree, etc) +msgid "%s duplicate transaction received" +msgstr "" + +#: payment-gateway/Handlers/Abstract_Payment_Handler.php:207 +#: payment-gateway/class-sv-wc-payment-gateway-hosted.php:516 +msgid "Order %s is already paid for." +msgstr "" + +#: payment-gateway/Handlers/Abstract_Payment_Handler.php:267 +#: payment-gateway/class-sv-wc-payment-gateway.php:2825 +msgid "" +"Your order has been received and is being reviewed. Thank you for your " +"business." +msgstr "" + +#: payment-gateway/Handlers/Abstract_Payment_Handler.php:274 +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:864 +#: payment-gateway/class-sv-wc-payment-gateway.php:1861 +#: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-pre-orders.php:370 +#. translators: This is a message describing that the transaction in question +#. only performed a credit card authorization and did not capture any funds. +msgid "Authorization only transaction" +msgstr "" + +#: payment-gateway/Handlers/Abstract_Payment_Handler.php:364 +#. translators: Placeholders: %s - payment gateway title +msgid "%s Transaction Held for Review" +msgstr "" + +#: payment-gateway/Handlers/Abstract_Payment_Handler.php:435 +#. translators: Placeholders: %s - payment gateway title +msgid "%s Payment Failed" +msgstr "" + +#: payment-gateway/Handlers/Abstract_Payment_Handler.php:462 +#. translators: Placeholders: %s - payment gateway title +msgid "%s Transaction Cancelled" +msgstr "" + +#: payment-gateway/Handlers/Capture.php:158 +msgid "Order cannot be captured" +msgstr "" + +#: payment-gateway/Handlers/Capture.php:163 +msgid "Transaction authorization has expired" +msgstr "" + +#: payment-gateway/Handlers/Capture.php:168 +msgid "Transaction has already been fully captured" +msgstr "" + +#: payment-gateway/Handlers/Capture.php:173 +msgid "Transaction cannot be captured" +msgstr "" + +#: payment-gateway/Handlers/Capture.php:189 +#. translators: Placeholders: %1$s - payment gateway title (such as +#. Authorize.net, Braintree, etc), %2$s - transaction amount. Definitions: +#. Capture, as in capture funds from a credit card. +msgid "%1$s Capture of %2$s Approved" +msgstr "" + +#: payment-gateway/Handlers/Capture.php:198 +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:683 +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:768 +#: payment-gateway/class-sv-wc-payment-gateway.php:2162 +#: payment-gateway/class-sv-wc-payment-gateway.php:2395 +#: payment-gateway/class-sv-wc-payment-gateway.php:2706 +#: payment-gateway/class-sv-wc-payment-gateway.php:2751 +#: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-pre-orders.php:353 +#. translators: Placeholders: %s - transaction ID +msgid "(Transaction ID %s)" +msgstr "" + +#: payment-gateway/Handlers/Capture.php:229 +#. translators: Placeholders: %1$s - payment gateway title (such as +#. Authorize.net, Braintree, etc), %2$s - failure message. Definitions: +#. "capture" as in capturing funds from a credit card. +msgid "%1$s Capture Failed: %2$s" +msgstr "" + +#: payment-gateway/admin/class-sv-wc-payment-gateway-admin-order.php:130 +msgid "Are you sure you wish to process this capture? The action cannot be undone." +msgstr "" + +#: payment-gateway/admin/class-sv-wc-payment-gateway-admin-order.php:133 +msgid "" +"Something went wrong, and the capture could no be completed. Please try " +"again." +msgstr "" + +#: payment-gateway/admin/class-sv-wc-payment-gateway-admin-order.php:178 +#: payment-gateway/admin/class-sv-wc-payment-gateway-admin-order.php:267 +#: payment-gateway/admin/class-sv-wc-payment-gateway-admin-order.php:330 +#. translators: verb, as in "Capture credit card charge". Used when an +#. amount has been pre-authorized before, but funds have not yet been captured +#. (taken) from the card. Capturing the charge will take the money from the +#. credit card and put it in the merchant's pockets. +msgid "Capture Charge" +msgstr "" + +#: payment-gateway/admin/class-sv-wc-payment-gateway-admin-order.php:320 +msgid "This charge has been fully captured." +msgstr "" + +#: payment-gateway/admin/class-sv-wc-payment-gateway-admin-order.php:322 +msgid "This charge can no longer be captured." +msgstr "" + +#: payment-gateway/admin/class-sv-wc-payment-gateway-admin-order.php:324 +msgid "This charge cannot be captured." +msgstr "" + +#: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:91 +msgid "Are you sure you want to remove this token?" +msgstr "" + +#: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:101 +msgid "Invalid token data" +msgstr "" + +#: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:105 +msgid "An error occurred. Please try again." +msgstr "" + +#: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:491 +#: payment-gateway/admin/class-sv-wc-payment-gateway-admin-user-handler.php:305 +msgid "(%s)" +msgstr "" + +#: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:521 +#: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:900 +msgid "Default" +msgstr "" + +#: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:557 +#: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:590 +msgid "Token ID" +msgstr "" + +#: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:562 +#: payment-gateway/class-sv-wc-payment-gateway-privacy.php:300 +msgid "Card Type" +msgstr "" + +#: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:567 +#: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:603 +#: payment-gateway/class-sv-wc-payment-gateway-privacy.php:192 +#: payment-gateway/class-sv-wc-payment-gateway-privacy.php:298 +msgid "Last Four" +msgstr "" + +#: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:574 +#: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:362 +msgid "Expiration (MM/YY)" +msgstr "" + +#: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:595 +#: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:470 +#: payment-gateway/class-sv-wc-payment-gateway-privacy.php:299 +#. translators: e-check account type, HTML form field label +msgid "Account Type" +msgstr "" + +#: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:598 +msgid "Checking" +msgstr "" + +#: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:599 +msgid "Savings" +msgstr "" + +#: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:700 +msgid "Refresh" +msgstr "" + +#: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:702 +msgid "Add New" +msgstr "" + +#: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:705 +#: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:297 +msgid "Save" +msgstr "" + +#: payment-gateway/admin/class-sv-wc-payment-gateway-admin-payment-token-editor.php:728 +msgid "Remove" +msgstr "" + +#: payment-gateway/admin/class-sv-wc-payment-gateway-admin-user-handler.php:224 +#: payment-gateway/class-sv-wc-payment-gateway-privacy.php:209 +msgid "%s Payment Tokens" +msgstr "" + +#: payment-gateway/admin/class-sv-wc-payment-gateway-admin-user-handler.php:302 +#: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-subscriptions.php:862 +msgid "Customer ID" +msgstr "" + +#: payment-gateway/admin/views/html-admin-gateway-status.php:32 +msgid "This section contains configuration settings for this gateway." +msgstr "" + +#: payment-gateway/admin/views/html-admin-gateway-status.php:53 +#: payment-gateway/class-sv-wc-payment-gateway.php:1389 +#. translators: environment as in a software environment (test/production) +msgid "Environment" +msgstr "" + +#: payment-gateway/admin/views/html-admin-gateway-status.php:54 +msgid "The transaction environment for this gateway." +msgstr "" + +#: payment-gateway/admin/views/html-admin-gateway-status.php:61 +msgid "Tokenization Enabled" +msgstr "" + +#: payment-gateway/admin/views/html-admin-gateway-status.php:62 +msgid "Displays whether or not tokenization is enabled for this gateway." +msgstr "" + +#: payment-gateway/admin/views/html-admin-gateway-status.php:75 +#: payment-gateway/class-sv-wc-payment-gateway.php:1316 +msgid "Debug Mode" +msgstr "" + +#: payment-gateway/admin/views/html-admin-gateway-status.php:76 +msgid "Displays whether or not debug logging is enabled for this gateway." +msgstr "" + +#: payment-gateway/admin/views/html-admin-gateway-status.php:79 +msgid "Display at Checkout & Log" +msgstr "" + +#: payment-gateway/admin/views/html-admin-gateway-status.php:81 +msgid "Display at Checkout" +msgstr "" + +#: payment-gateway/admin/views/html-admin-gateway-status.php:83 +#: payment-gateway/class-sv-wc-payment-gateway.php:1324 +msgid "Save to Log" +msgstr "" + +#: payment-gateway/admin/views/html-admin-gateway-status.php:85 +#: payment-gateway/class-sv-wc-payment-gateway.php:1322 +msgid "Off" +msgstr "" + +#: payment-gateway/admin/views/html-order-partial-capture.php:30 +msgid "Authorization total" +msgstr "" + +#: payment-gateway/admin/views/html-order-partial-capture.php:34 +msgid "Amount already captured" +msgstr "" + +#: payment-gateway/admin/views/html-order-partial-capture.php:40 +msgid "Remaining order total" +msgstr "" + +#: payment-gateway/admin/views/html-order-partial-capture.php:46 +msgid "Capture amount" +msgstr "" + +#: payment-gateway/admin/views/html-order-partial-capture.php:53 +msgid "Comment (optional):" +msgstr "" + +#: payment-gateway/admin/views/html-order-partial-capture.php:65 +msgid "Capture %s" +msgstr "" + +#: payment-gateway/admin/views/html-order-partial-capture.php:66 +#: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:608 +msgid "Cancel" +msgstr "" + +#: payment-gateway/admin/views/html-user-payment-token-editor-token.php:57 +msgid "-- Select an option --" +msgstr "" + +#: payment-gateway/admin/views/html-user-payment-token-editor.php:59 +msgid "No saved payment tokens" +msgstr "" + +#: payment-gateway/admin/views/html-user-profile-field-customer-id.php:30 +msgid "The gateway customer ID for the user. Only edit this if necessary." +msgstr "" + +#: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:100 +msgid "" +"We cannot process your order with the payment information that you " +"provided. Please use a different payment account or an alternate payment " +"method." +msgstr "" + +#: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:101 +msgid "" +"This order is being placed on hold for review. Please contact us to " +"complete the transaction." +msgstr "" + +#: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:106 +msgid "" +"This order is being placed on hold for review due to an incorrect card " +"verification number. You may contact the store to complete the transaction." +msgstr "" + +#: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:107 +msgid "The card verification number is invalid, please try again." +msgstr "" + +#: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:108 +msgid "Please enter your card verification number and try again." +msgstr "" + +#: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:111 +msgid "" +"That card type is not accepted, please use an alternate card or other form " +"of payment." +msgstr "" + +#: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:112 +#: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:116 +msgid "" +"The card type is invalid or does not correlate with the credit card number. " +" Please try again or use an alternate card or other form of payment." +msgstr "" + +#: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:113 +msgid "Please select the card type and try again." +msgstr "" + +#: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:117 +msgid "The card number is invalid, please re-enter and try again." +msgstr "" + +#: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:118 +msgid "Please enter your card number and try again." +msgstr "" + +#: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:121 +msgid "The card expiration date is invalid, please re-enter and try again." +msgstr "" + +#: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:122 +msgid "The card expiration month is invalid, please re-enter and try again." +msgstr "" + +#: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:123 +msgid "The card expiration year is invalid, please re-enter and try again." +msgstr "" + +#: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:124 +msgid "Please enter your card expiration date and try again." +msgstr "" + +#: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:127 +msgid "The bank routing number is invalid, please re-enter and try again." +msgstr "" + +#: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:128 +msgid "The bank account number is invalid, please re-enter and try again." +msgstr "" + +#: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:131 +msgid "" +"The provided card is expired, please use an alternate card or other form of " +"payment." +msgstr "" + +#: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:132 +msgid "" +"The provided card was declined, please use an alternate card or other form " +"of payment." +msgstr "" + +#: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:133 +msgid "" +"Insufficient funds in account, please use an alternate card or other form " +"of payment." +msgstr "" + +#: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:134 +msgid "" +"The card is inactivate or not authorized for card-not-present transactions, " +"please use an alternate card or other form of payment." +msgstr "" + +#: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:135 +msgid "" +"The credit limit for the card has been reached, please use an alternate " +"card or other form of payment." +msgstr "" + +#: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:136 +msgid "The card verification number does not match. Please re-enter and try again." +msgstr "" + +#: payment-gateway/api/class-sv-wc-payment-gateway-api-response-message-helper.php:137 +msgid "" +"The provided address does not match the billing address for cardholder. " +"Please verify the address and try again." +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:61 +msgid "" +"Payment error, please try another payment method or contact us to complete " +"your transaction." +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:161 +#: payment-gateway/class-sv-wc-payment-gateway.php:503 +msgid "Card expiration date is invalid" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:185 +#: payment-gateway/class-sv-wc-payment-gateway.php:496 +msgid "Card number is missing" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:191 +#: payment-gateway/class-sv-wc-payment-gateway.php:499 +msgid "Card number is invalid (wrong length)" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:196 +#: payment-gateway/class-sv-wc-payment-gateway.php:498 +msgid "Card number is invalid (only digits allowed)" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:201 +#: payment-gateway/class-sv-wc-payment-gateway.php:497 +msgid "Card number is invalid" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:228 +#: payment-gateway/class-sv-wc-payment-gateway.php:501 +msgid "Card security code is invalid (only digits are allowed)" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:234 +#: payment-gateway/class-sv-wc-payment-gateway.php:502 +msgid "Card security code is invalid (must be 3 or 4 digits)" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:240 +#: payment-gateway/class-sv-wc-payment-gateway.php:500 +msgid "Card security code is missing" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:267 +#: payment-gateway/class-sv-wc-payment-gateway.php:512 +msgid "Routing Number is missing" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:274 +#: payment-gateway/class-sv-wc-payment-gateway.php:513 +msgid "Routing Number is invalid (only digits are allowed)" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:280 +#: payment-gateway/class-sv-wc-payment-gateway.php:514 +msgid "Routing number is invalid (must be 9 digits)" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:289 +#: payment-gateway/class-sv-wc-payment-gateway.php:509 +msgid "Account Number is missing" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:296 +#: payment-gateway/class-sv-wc-payment-gateway.php:510 +msgid "Account Number is invalid (only digits are allowed)" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:302 +#: payment-gateway/class-sv-wc-payment-gateway.php:511 +msgid "Account number is invalid (must be between 5 and 17 digits)" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:309 +#: payment-gateway/class-sv-wc-payment-gateway.php:508 +msgid "Drivers license number is invalid" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:315 +#: payment-gateway/class-sv-wc-payment-gateway.php:504 +msgid "Check Number is invalid (only digits are allowed)" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:494 +msgid "Unknown error" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:503 +msgid "Payment method address could not be updated. %s" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:673 +#: payment-gateway/class-sv-wc-payment-gateway.php:2741 +#. translators: Placeholders: %1$s - payment method title, %2$s - payment +#. account type (savings/checking) (may or may not be available), %3$s - last +#. four digits of the account +msgid "%1$s Check Transaction Approved: %2$s account ending in %3$s" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:678 +#: payment-gateway/class-sv-wc-payment-gateway.php:2746 +#. translators: Placeholders: %s - check number +msgid "Check number %s" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:747 +#. translators: Placeholders: %1$s - payment method title, %2$s - environment +#. ("Test"), %3$s - transaction type (authorization/charge), %4$s - card type +#. (mastercard, visa, ...), %5$s - last four digits of the card +msgid "%1$s %2$s %3$s Approved: %4$s ending in %5$s" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:760 +#: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:718 +#: payment-gateway/class-sv-wc-payment-gateway.php:2698 +#. translators: Placeholders: %s - expiry date +msgid "(expires %s)" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:832 +#. translators: Placeholders: %s - failure message +msgid "Tokenization Request Failed: %s" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:843 +#. translators: Placeholders: %1$s - payment method title, %2$s - failure +#. message +msgid "%1$s Tokenization Request Failed: %2$s" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:901 +#. translators: Placeholders: %s - failure message. Payment method as in a +#. specific credit card, e-check or bank account +msgid "Oops, adding your new payment method failed: %s" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:946 +#. translators: Payment method as in a specific credit card. Placeholders: %1$s +#. - card type (visa, mastercard, ...), %2$s - last four digits of the card, +#. %3$s - card expiry date +msgid "Nice! New payment method added: %1$s ending in %2$s (expires %3$s)" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:956 +#. translators: Payment method as in a specific e-check account. Placeholders: +#. %1$s - account type (checking/savings), %2$s - last four digits of the +#. account +msgid "Nice! New payment method added: %1$s account ending in %2$s" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:963 +#. translators: Payment method as in a specific credit card, e-check or bank +#. account +msgid "Nice! New payment method added." +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:1086 +#. translators: Placeholders: %1$s - site title, %2$s - customer email. Payment +#. method as in a specific credit card, e-check or bank account +msgid "%1$s - Add Payment Method for %2$s" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-helper.php:180 +msgid "PayPal" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-helper.php:181 +msgid "Checking Account" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-helper.php:182 +msgid "Savings Account" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-helper.php:183 +msgid "Credit / Debit Card" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-helper.php:184 +msgid "Bank Account" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-hosted.php:301 +msgid "Thank you for your order, please click the button below to pay." +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-hosted.php:302 +msgid "Thank you for your order. We are now redirecting you to complete payment." +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-hosted.php:303 +msgid "Pay Now" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-hosted.php:304 +msgid "Cancel Order" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-hosted.php:601 +#: payment-gateway/payment-tokens/class-sv-wc-payment-gateway-payment-tokens-handler.php:1114 +#. translators: Placeholders: %1$s - payment gateway title (such as +#. Authorize.net, Braintree, etc), %2$s - payment method name (mastercard, bank +#. account, etc), %3$s - last four digits of the card/account, %4$s - +#. card/account expiry date +msgid "%1$s Payment Method Saved: %2$s ending in %3$s (expires %4$s)" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-hosted.php:612 +#: payment-gateway/payment-tokens/class-sv-wc-payment-gateway-payment-tokens-handler.php:1125 +#. translators: Placeholders: %1$s - payment gateway title (such as CyberSouce, +#. NETbilling, etc), %2$s - account type (checking/savings - may or may not be +#. available), %3$s - last four digits of the account +msgid "%1$s eCheck Payment Method Saved: %2$s account ending in %3$s" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-hosted.php:621 +#. translators: Placeholders: %s - payment gateway title (such as CyberSouce, +#. NETbilling, etc) +msgid "%s Payment Method Saved" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-hosted.php:630 +#. translators: Placeholders: %s - a failed tokenization API error +msgid "Tokenization failed. %s" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:293 +#: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:607 +msgid "Edit" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:337 +#: payment-gateway/class-sv-wc-payment-gateway.php:1269 +msgid "Title" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:340 +msgid "Details" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:343 +msgid "Default?" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:609 +msgid "Oops, there was an error updating your payment method. Please try again." +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:610 +msgid "Are you sure you want to delete this payment method?" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:697 +#. translators: Payment method as in a specific credit card, eCheck or bank +#. account +msgid "You do not have any saved payment methods." +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:872 +#: payment-gateway/class-sv-wc-payment-gateway-privacy.php:200 +msgid "Nickname" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:1118 +msgid "Oops, you took too long, please try again." +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-my-payment-methods.php:1129 +msgid "There was an error with your request, please try again." +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:344 +msgid "Card Number" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:365 +msgid "MM / YY" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:384 +msgid "Card Security Code" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:387 +msgid "CSC" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:427 +msgid "Where do I find this?" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:433 +#. translators: e-check routing number, HTML form field label, +#. https:en.wikipedia.org/wiki/Routing_transit_number +msgid "Routing Number" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:452 +#. translators: e-check account number, HTML form field label +msgid "Account Number" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:518 +#. translators: Test mode refers to the current software environment +msgid "TEST MODE ENABLED" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:545 +msgid "Sample Check" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:620 +#. translators: Payment method as in a specific credit card, eCheck or bank +#. account +msgid "Manage Payment Methods" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:757 +msgid "Use a new card" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:757 +msgid "Use a new bank account" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:820 +#. translators: account as in customer's account on the eCommerce site +msgid "Securely Save to Account" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:954 +msgid "Payment Info" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-plugin.php:706 +#. translators: Placeholders: %1$s - plugin name, %2$s - tag, %3$s - +#. tag +msgid "" +"%1$s: WooCommerce is not being forced over SSL; your customers' payment " +"data may be at risk. %2$sVerify your site URLs here%3$s" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-plugin.php:723 +#. translators: Placeholders: %s - payment gateway name +msgid "" +"%s will soon require TLS 1.2 support to process transactions and your " +"server environment may need to be updated. Please contact your hosting " +"provider to confirm that your site can send and receive TLS 1.2 connections " +"and request they make any necessary updates." +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-plugin.php:779 +#. translators: Placeholders: %1$s - plugin name, %2$s - a +#. currency/comma-separated list of currencies, %3$s - tag, %4$s - tag +msgid "" +"%1$s accepts payment in %2$s only. %3$sConfigure%4$s WooCommerce to accept " +"%2$s to enable this gateway for checkout." +msgid_plural "" +"%1$s accepts payment in one of %2$s only. %3$sConfigure%4$s WooCommerce to " +"accept one of %2$s to enable this gateway for checkout." +msgstr[0] "" +msgstr[1] "" + +#: payment-gateway/class-sv-wc-payment-gateway-plugin.php:814 +#. translators: Placeholders: %1$s - payment gateway name, %2$s - opening +#. tag, %3$s - closing tag +msgid "" +"Heads up! %1$s is currently configured to log transaction data for " +"debugging purposes. If you are not experiencing any problems with payment " +"processing, we recommend %2$sturning off Debug Mode%3$s" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-plugin.php:865 +#. translators: Placeholders: %s - gateway name +msgid "%s is not configured" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-plugin.php:877 +msgid "Dismiss" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-plugin.php:914 +#. translators: Placeholders: %1$s - plugin name, %2$s - opening HTML link +#. tag, %3$s - closing HTML link tag +msgid "" +"Heads up! Apple Pay for %1$s requires WooCommerce version 3.2 or greater. " +"Please %2$supdate WooCommerce%3$s." +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-plugin.php:938 +#. translators: Placeholders: %1$s - plugin name, %2$s - opening HTML link +#. tag, %3$s - closing HTML link tag +msgid "" +"Heads up! Google Pay for %1$s requires WooCommerce version 3.2 or greater. " +"Please %2$supdate WooCommerce%3$s." +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-plugin.php:974 +#. translators: Placeholders: %1$s - payment gateway title (such as +#. Authorize.net, Braintree, etc), %2$s - tag, %3$s - tag +msgid "" +"%1$s is inactive for subscription transactions. Please %2$senable " +"tokenization%3$s to activate %1$s for Subscriptions." +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-plugin.php:992 +#. translators: Placeholders: %1$s - payment gateway title (such as +#. Authorize.net, Braintree, etc), %2$s - tag, %3$s - tag +msgid "" +"%1$s is inactive for pre-order transactions. Please %2$senable " +"tokenization%3$s to activate %1$s for Pre-Orders." +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-plugin.php:1029 +msgid "" +"You must enable tokenization for this gateway in order to support automatic " +"renewal payments with the WooCommerce Subscriptions extension." +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-plugin.php:1030 +msgid "Inactive" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-privacy.php:115 +msgid "%s Customer ID" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-privacy.php:184 +msgid "Type" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-privacy.php:254 +msgid "Removed payment token \"%d\"" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-privacy.php:301 +msgid "Expiry Date" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:350 +msgid "you successfully processed a payment!" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:355 +msgid "you successfully processed a refund!" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:505 +msgid "Check Number is missing" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:506 +msgid "Drivers license state is missing" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:507 +msgid "Drivers license number is missing" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:720 +msgid "Continue to Payment" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:720 +msgid "Place order" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:752 +msgid "Thank you for your order." +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:1221 +msgid "Credit Card" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:1223 +msgid "eCheck" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:1241 +msgid "Pay securely using your credit card." +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:1243 +msgid "Pay securely using your checking account." +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:1263 +msgid "Enable this gateway" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:1271 +msgid "Payment method title that the customer will see during checkout." +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:1276 +msgid "Description" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:1278 +msgid "Payment method description that the customer will see during checkout." +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:1307 +msgid "Detailed Decline Messages" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:1309 +msgid "" +"Check to enable detailed decline messages to the customer during checkout " +"when possible, rather than a generic decline message." +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:1319 +#. translators: Placeholders: %1$s - tag, %2$s - tag +msgid "" +"Show Detailed Error Messages and API requests/responses on the checkout " +"page and/or save them to the %1$sdebug log%2$s" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:1323 +msgid "Show on Checkout Page" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:1326 +#. translators: show debugging information on both checkout page and in the log +msgid "Both" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:1392 +msgid "Select the gateway environment to use for transactions." +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:1446 +msgid "Share connection settings" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:1448 +msgid "Use connection/authentication settings from other gateway" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:1451 +msgid "Disabled because the other gateway is using these settings" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:1468 +msgid "Card Verification (CSC)" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:1469 +msgid "Display the Card Security Code (CV2) field on checkout" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:1477 +msgid "Saved Card Verification" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:1478 +msgid "Display the Card Security Code field when paying with a saved card" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:1814 +#. translators: Placeholders: %1$s - site title, %2$s - order number +msgid "%1$s - Order %2$s" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:1943 +#. translators: Placeholders: %1$s - site title, %2$s - order number. +#. Definitions: Capture as in capture funds from a credit card. +msgid "%1$s - Capture for Order %2$s" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:2086 +#. translators: Placeholders: %1$s - site title, %2$s - order number +msgid "%1$s - Refund for Order %2$s" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:2153 +#. translators: Placeholders: %1$s - payment gateway title (such as +#. Authorize.net, Braintree, etc), %2$s - a monetary amount +msgid "%1$s Refund in the amount of %2$s approved." +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:2183 +#. translators: Placeholders: %1$s - payment gateway title (such as +#. Authorize.net, Braintree, etc), %2$s - error code, %3$s - error message +msgid "%1$s Refund Failed: %2$s - %3$s" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:2191 +#. translators: Placeholders: %1$s - payment gateway title (such as +#. Authorize.net, Braintree, etc), %2$s - error message +msgid "%1$s Refund Failed: %2$s" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:2212 +#. translators: Placeholders: %s - payment gateway title (such as +#. Authorize.net, Braintree, etc) +msgid "%s Order completely refunded." +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:2267 +msgid "" +"Oops, you cannot partially void this order. Please use the full order " +"amount." +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:2354 +#. translators: Placeholders: %1$s - payment gateway title, %2$s - error code, +#. %3$s - error message. Void as in to void an order. +msgid "%1$s Void Failed: %2$s - %3$s" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:2362 +#. translators: Placeholders: %1$s - payment gateway title, %2$s - error +#. message. Void as in to void an order. +msgid "%1$s Void Failed: %2$s" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:2386 +#. translators: Placeholders: %1$s - payment gateway title, %2$s - a monetary +#. amount. Void as in to void an order. +msgid "%1$s Void in the amount of %2$s approved." +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:2677 +#. translators: Placeholders: %1$s - payment method title, %2$s - environment +#. ("Test"), %3$s - transaction type (authorization/charge) +msgid "%1$s %2$s %3$s Approved" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:2687 +#. translators: Placeholders: %1$s - credit card type (MasterCard, Visa, +#. etc...), %2$s - last four digits of the card +msgid "%1$s ending in %2$s" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:2783 +#. translators: Placeholders: %1$s - payment gateway title, %2$s - message +#. (probably reason for the transaction being held for review) +msgid "%1$s Transaction Held for Review (%2$s)" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:2872 +#. translators: Placeholders: %1$s - payment gateway title, %2$s - error +#. message; e.g. Order Note: [Payment method] Payment failed [error] +msgid "%1$s Payment Failed (%2$s)" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:2907 +#. translators: Placeholders: %1$s - payment gateway title, %2$s - +#. message/error +msgid "%1$s Transaction Cancelled (%2$s)" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:3155 +msgid "Transaction Type" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:3157 +msgid "" +"Select how transactions should be processed. Charge submits all " +"transactions for settlement, Authorization simply authorizes the order " +"total for capture later." +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:3168 +msgid "Charge Virtual-Only Orders" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:3170 +msgid "" +"If the order contains exclusively virtual items, enable this to immediately " +"charge, rather than authorize, the transaction." +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:3178 +msgid "Enable Partial Capture" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:3180 +msgid "Allow orders to be partially captured multiple times." +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:3192 +msgid "Capture Paid Orders" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:3195 +msgid "Automatically capture orders when they are changed to %s." +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:3196 +msgid "a paid status" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:3386 +msgid "Accepted Card Logos" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:3388 +msgid "" +"These are the card logos that are displayed to customers as accepted during " +"checkout." +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:3391 +#. translators: Placeholders: %1$s - tag, %2$s - tag +msgid "" +"This setting %1$sdoes not%2$s change which card types the gateway will " +"accept. Accepted cards are configured from your payment processor account." +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:3482 +#. translators: +#. http:www.cybersource.com/products/payment_security/payment_tokenization/ and +#. https:en.wikipedia.org/wiki/Tokenization_(data_security) +msgid "Tokenization" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:3483 +msgid "Allow customers to securely save their payment details for future checkout." +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:4302 +#. translators: %1$s - gateway name, %2$s - tag, %3$s - tag, %4$s - +#. tag, %5$s - tag +msgid "" +"Heads up! %1$s is not fully configured and cannot accept payments. Please " +"%2$sreview the documentation%3$s and configure the %4$sgateway settings%5$s." +msgstr "" + +#: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-pre-orders.php:261 +msgid "Pre-Order Tokenization attempt failed (%s)" +msgstr "" + +#: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-pre-orders.php:307 +msgid "%s - Pre-Order Release Payment for Order %s" +msgstr "" + +#: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-pre-orders.php:311 +msgid "Payment token missing/invalid." +msgstr "" + +#: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-pre-orders.php:336 +msgid "%s %s Pre-Order Release Payment Approved: %s ending in %s (expires %s)" +msgstr "" + +#: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-pre-orders.php:347 +msgid "%s eCheck Pre-Order Release Payment Approved: %s ending in %s" +msgstr "" + +#: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-pre-orders.php:391 +msgid "Pre-Order Release Payment Failed: %s" +msgstr "" + +#: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-subscriptions.php:358 +msgid "Subscription Renewal: payment token is missing/invalid." +msgstr "" + +#: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-subscriptions.php:384 +msgid "%1$s - Subscription Renewal Order %2$s" +msgstr "" + +#: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-subscriptions.php:516 +#. translators: Placeholders: %1$s - payment gateway title, %2$s - error +#. message; e.g. Order Note: [Payment method] Payment Change failed [error] +msgid "%1$s Payment Change Failed (%2$s)" +msgstr "" + +#: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-subscriptions.php:671 +msgid "Via %s ending in %s" +msgstr "" + +#: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-subscriptions.php:698 +msgid "Subscriptions" +msgstr "" + +#: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-subscriptions.php:759 +msgid "N/A" +msgstr "" + +#: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-subscriptions.php:858 +msgid "Payment Token" +msgstr "" + +#: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-subscriptions.php:887 +#: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-subscriptions.php:892 +msgid "%s is required." +msgstr "" + +#: payment-gateway/payment-tokens/class-sv-wc-payment-gateway-payment-tokens-handler.php:180 +msgid "Unknown Error" +msgstr "" + +#: rest-api/Controllers/Settings.php:84 +msgid "Unique identifier for the resource." +msgstr "" + +#: rest-api/Controllers/Settings.php:119 +msgid "Sorry, you cannot list resources." +msgstr "" + +#: rest-api/Controllers/Settings.php:168 +#. translators: Placeholder: %s - setting ID +msgid "Setting %s does not exist" +msgstr "" + +#: rest-api/Controllers/Settings.php:191 +msgid "Sorry, you cannot edit this resource." +msgstr "" + +#: rest-api/Controllers/Settings.php:224 +msgid "Could not update setting: %s" +msgstr "" + +#: rest-api/Controllers/Settings.php:294 +msgid "Unique identifier of the setting." +msgstr "" + +#: rest-api/Controllers/Settings.php:300 +msgid "The type of the setting." +msgstr "" + +#: rest-api/Controllers/Settings.php:307 +msgid "The name of the setting." +msgstr "" + +#: rest-api/Controllers/Settings.php:313 +msgid "The description of the setting. It may or may not be used for display." +msgstr "" + +#: rest-api/Controllers/Settings.php:319 +msgid "Whether the setting stores an array of values or a single value." +msgstr "" + +#: rest-api/Controllers/Settings.php:325 +msgid "A list of valid options, used for validation before storing the value." +msgstr "" + +#: rest-api/Controllers/Settings.php:331 +msgid "Optional default value for the setting." +msgstr "" + +#: rest-api/Controllers/Settings.php:337 +msgid "The value of the setting." +msgstr "" + +#: rest-api/Controllers/Settings.php:342 +msgid "" +"Optional object that defines how the user will interact with and update the " +"setting." +msgstr "" + +#: rest-api/Controllers/Settings.php:346 +msgid "The type of the control." +msgstr "" + +#: rest-api/Controllers/Settings.php:353 +msgid "The name of the control. Inherits the setting's name." +msgstr "" + +#: rest-api/Controllers/Settings.php:359 +msgid "The description of the control. Inherits the setting's description." +msgstr "" + +#: rest-api/Controllers/Settings.php:365 +msgid "" +"A list of key/value pairs defining the display value of each setting " +"option. The keys should match the options defined in the base setting for " +"validation." +msgstr "" + +#: utilities/class-sv-wp-background-job-handler.php:659 +msgid "Job data key \"%s\" not set" +msgstr "" + +#: utilities/class-sv-wp-background-job-handler.php:663 +msgid "Job data key \"%s\" is not an array" +msgstr "" + +#: utilities/class-sv-wp-background-job-handler.php:899 +msgid "Every %d Minutes" +msgstr "" + +#: utilities/class-sv-wp-background-job-handler.php:1063 +msgid "Background Processing Test" +msgstr "" + +#: utilities/class-sv-wp-background-job-handler.php:1064 +msgid "Run Test" +msgstr "" + +#: utilities/class-sv-wp-background-job-handler.php:1065 +msgid "" +"This tool will test whether your server is capable of processing background " +"jobs." +msgstr "" + +#: utilities/class-sv-wp-background-job-handler.php:1083 +msgid "Success! You should be able to process background jobs." +msgstr "" + +#: utilities/class-sv-wp-background-job-handler.php:1086 +msgid "" +"Could not connect. Please ask your hosting company to ensure your server " +"has loopback connections enabled." +msgstr "" + +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:395 +msgctxt "enhanced select" +msgid "No matches found" +msgstr "" + +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:396 +msgctxt "enhanced select" +msgid "Loading failed" +msgstr "" + +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:397 +msgctxt "enhanced select" +msgid "Please enter 1 or more characters" +msgstr "" + +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:398 +msgctxt "enhanced select" +msgid "Please enter %qty% or more characters" +msgstr "" + +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:399 +msgctxt "enhanced select" +msgid "Please delete 1 character" +msgstr "" + +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:400 +msgctxt "enhanced select" +msgid "Please delete %qty% characters" +msgstr "" + +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:401 +msgctxt "enhanced select" +msgid "You can only select 1 item" +msgstr "" + +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:402 +msgctxt "enhanced select" +msgid "You can only select %qty% items" +msgstr "" + +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:403 +msgctxt "enhanced select" +msgid "Loading more results…" +msgstr "" + +#: admin/abstract-sv-wc-plugin-admin-setup-wizard.php:404 +msgctxt "enhanced select" +msgid "Searching…" +msgstr "" + +#: class-sv-wc-helper.php:426 +msgctxt "coordinating conjunction for a list of items: a, b, and c" +msgid "and" +msgstr "" + +#: class-sv-wc-plugin.php:602 +msgctxt "noun" +msgid "Support" +msgstr "" + +#: class-sv-wc-plugin.php:607 +msgctxt "verb" +msgid "Review" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:749 +#: payment-gateway/class-sv-wc-payment-gateway.php:2679 +msgctxt "noun, software environment" +msgid "Test" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:750 +#: payment-gateway/class-sv-wc-payment-gateway.php:2680 +#: payment-gateway/class-sv-wc-payment-gateway.php:3161 +msgctxt "credit card transaction type" +msgid "Authorization" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-direct.php:750 +#: payment-gateway/class-sv-wc-payment-gateway.php:2680 +#: payment-gateway/class-sv-wc-payment-gateway.php:3160 +msgctxt "noun, credit card transaction type" +msgid "Charge" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-helper.php:193 +msgctxt "payment method type" +msgid "Account" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-helper.php:229 +#: payment-gateway/class-sv-wc-payment-gateway.php:3419 +msgctxt "credit card type" +msgid "Visa" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-helper.php:233 +#: payment-gateway/class-sv-wc-payment-gateway.php:3420 +msgctxt "credit card type" +msgid "MasterCard" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-helper.php:237 +#: payment-gateway/class-sv-wc-payment-gateway.php:3421 +msgctxt "credit card type" +msgid "American Express" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-helper.php:241 +msgctxt "credit card type" +msgid "Diners Club" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-helper.php:245 +#: payment-gateway/class-sv-wc-payment-gateway.php:3422 +msgctxt "credit card type" +msgid "Discover" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-helper.php:249 +#: payment-gateway/class-sv-wc-payment-gateway.php:3424 +msgctxt "credit card type" +msgid "JCB" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-helper.php:253 +msgctxt "credit card type" +msgid "CarteBleue" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-helper.php:257 +msgctxt "credit card type" +msgid "Maestro" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-helper.php:261 +msgctxt "credit card type" +msgid "Laser" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:3423 +msgctxt "credit card type" +msgid "Diners" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:478 +#. translators: http:www.investopedia.com/terms/c/checkingaccount.asp +msgctxt "account type" +msgid "Checking" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway-payment-form.php:480 +#. translators: http:www.investopedia.com/terms/s/savingsaccount.asp +msgctxt "account type" +msgid "Savings" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:2461 +msgctxt "hash before order number" +msgid "#" +msgstr "" + +#: payment-gateway/integrations/class-sv-wc-payment-gateway-integration-subscriptions.php:753 +msgctxt "hash before order number" +msgid "#%s" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:3189 +msgctxt "" +"coordinating conjunction for a list of order statuses: on-hold, processing, " +"or completed" +msgid "or" +msgstr "" + +#: payment-gateway/class-sv-wc-payment-gateway.php:4028 +#. translators: https:www.skyverge.com/for-translators-environments/ +msgctxt "software environment" +msgid "Production" +msgstr "" \ No newline at end of file diff --git a/vendor/skyverge/wc-plugin-framework/woocommerce/index.php b/vendor/skyverge/wc-plugin-framework/woocommerce/index.php new file mode 100644 index 0000000..129bbf3 --- /dev/null +++ b/vendor/skyverge/wc-plugin-framework/woocommerce/index.php @@ -0,0 +1,29 @@ +settings = $settings; + $this->namespace = 'wc/v3'; + $this->rest_base = "{$settings->get_id()}/settings"; + } + + + /** + * Registers the API routes. + * + * @since 5.7.0 + */ + public function register_routes() { + + register_rest_route( + $this->namespace, "/{$this->rest_base}", [ + [ + 'methods' => \WP_REST_Server::READABLE, + 'callback' => [ $this, 'get_items' ], + 'permission_callback' => [ $this, 'get_items_permissions_check' ], + ], + 'schema' => [ $this, 'get_public_item_schema' ], + ] + ); + + register_rest_route( + $this->namespace, "/{$this->rest_base}/(?P[\w-]+)", [ + 'args' => [ + 'id' => [ + 'description' => __( 'Unique identifier for the resource.', 'woocommerce' ), + 'type' => 'string', + ], + ], + [ + 'methods' => \WP_REST_Server::READABLE, + 'callback' => [ $this, 'get_item' ], + 'permission_callback' => [ $this, 'get_items_permissions_check' ], + ], + [ + 'methods' => \WP_REST_Server::EDITABLE, + 'callback' => [ $this, 'update_item' ], + 'permission_callback' => [ $this, 'update_item_permissions_check' ], + 'args' => $this->get_endpoint_args_for_item_schema( \WP_REST_Server::EDITABLE ), + ], + 'schema' => [ $this, 'get_public_item_schema' ], + ] + ); + } + + + /** Read methods **************************************************************************************************/ + + + /** + * Checks whether the user has permissions to get settings. + * + * @since 5.7.0 + * + * @param \WP_REST_Request $request request object + * @return bool|\WP_Error + */ + public function get_items_permissions_check( $request ) { + + if ( ! wc_rest_check_manager_permissions( 'settings', 'read' ) ) { + return new \WP_Error( 'wc_rest_cannot_view', __( 'Sorry, you cannot list resources.', 'woocommerce-plugin-framework' ), [ 'status' => rest_authorization_required_code() ] ); + } + + return true; + } + + + /** + * Gets all registered settings. + * + * @since 5.7.0 + * + * @param \WP_REST_Request $request request object + * @return \WP_REST_Response|\WP_Error + */ + public function get_items( $request ) { + + $items = []; + + foreach ( $this->settings->get_settings() as $setting ) { + $items[] = $this->prepare_setting_item( $setting, $request ); + } + + return rest_ensure_response( $items ); + } + + + /** + * Gets a single setting. + * + * @since 5.7.0 + * + * @param \WP_REST_Request $request request object + * @return \WP_REST_Response|\WP_Error + */ + public function get_item( $request ) { + + $setting_id = $request->get_param( 'id' ); + + if ( $setting = $this->settings->get_setting( $setting_id ) ) { + + return rest_ensure_response( $this->prepare_setting_item( $setting, $request ) ); + + } else { + + return new \WP_Error( + 'wc_rest_setting_not_found', + sprintf( + /* translators: Placeholder: %s - setting ID */ + __( 'Setting %s does not exist', 'woocommerce-plugin-framework' ), + $setting_id + ), + [ 'status' => 404 ] + ); + } + } + + + /** Update methods ************************************************************************************************/ + + + /** + * Checks whether the user has permissions to update a setting. + * + * @since 5.7.0 + * + * @param \WP_REST_Request $request request object + * @return bool|\WP_Error + */ + public function update_item_permissions_check( $request ) { + + if ( ! wc_rest_check_manager_permissions( 'settings', 'edit' ) ) { + return new \WP_Error( 'wc_rest_cannot_edit', __( 'Sorry, you cannot edit this resource.', 'woocommerce-plugin-framework' ), [ 'status' => rest_authorization_required_code() ] ); + } + + return true; + } + + + /** + * Updates a single setting. + * + * @since 5.7.0 + * + * @param \WP_REST_Request $request request object + * @return WP_Error|WP_REST_Response + */ + public function update_item( $request ) { + + try { + + $setting_id = $request->get_param( 'id' ); + $value = $request->get_param( 'value' ); + + // throws an exception if the setting doesn't exist or the value is not valid + $this->settings->update_value( $setting_id, $value ); + + return rest_ensure_response( $this->prepare_setting_item( $this->settings->get_setting( $setting_id ), $request ) ); + + } catch ( \Exception $e ) { + + return new \WP_Error( + 'wc_rest_setting_could_not_update', + sprintf( + /* Placeholders: %s - error message */ + __( 'Could not update setting: %s', 'woocommerce-plugin-framework' ), + $e->getMessage() + ), + [ 'status' => $e->getCode() ?: 400 ] + ); + } + } + + + /** Utility methods ***********************************************************************************************/ + + + /** + * Prepares the item for the REST response. + * + * @since 5.7.0 + * + * @param Setting $setting a setting object + * @param \WP_REST_Request $request request object + * @return array + */ + public function prepare_setting_item( $setting, $request ) { + + if ( $setting instanceof Setting ) { + + $item = [ + 'id' => $setting->get_id(), + 'type' => $setting->get_type(), + 'name' => $setting->get_name(), + 'description' => $setting->get_description(), + 'is_multi' => $setting->is_is_multi(), + 'options' => $setting->get_options(), + 'default' => $setting->get_default(), + 'value' => $setting->get_value(), + 'control' => null, + ]; + + if ( $control = $setting->get_control() ) { + $item['control'] = [ + 'type' => $control->get_type(), + 'name' => $control->get_name(), + 'description' => $control->get_description(), + 'options' => $control->geT_options(), + ]; + } + + } else { + + $item = []; + } + + return $item; + } + + + /** + * Retrieves the item's schema, conforming to JSON Schema. + * + * @since 5.7.0 + * + * @return array + */ + public function get_item_schema() { + + $schema = [ + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'title' => "{$this->settings->get_id()}_setting", + 'type' => 'object', + 'properties' => [ + 'id' => [ + 'description' => __( 'Unique identifier of the setting.', 'woocommerce-plugin-framework' ), + 'type' => 'string', + 'context' => [ 'view', 'edit' ], + 'readonly' => true, + ], + 'type' => [ + 'description' => __( 'The type of the setting.', 'woocommerce-plugin-framework' ), + 'type' => 'string', + 'context' => [ 'view', 'edit' ], + 'enum' => $this->settings->get_setting_types(), + 'readonly' => true, + ], + 'name' => [ + 'description' => __( 'The name of the setting.', 'woocommerce-plugin-framework' ), + 'type' => 'string', + 'context' => [ 'view', 'edit' ], + 'readonly' => true, + ], + 'description' => [ + 'description' => __( 'The description of the setting. It may or may not be used for display.', 'woocommerce-plugin-framework' ), + 'type' => 'string', + 'context' => [ 'view', 'edit' ], + 'readonly' => true, + ], + 'is_multi' => [ + 'description' => __( 'Whether the setting stores an array of values or a single value.', 'woocommerce-plugin-framework' ), + 'type' => 'boolean', + 'context' => [ 'view', 'edit' ], + 'readonly' => true, + ], + 'options' => [ + 'description' => __( 'A list of valid options, used for validation before storing the value.', 'woocommerce-plugin-framework' ), + 'type' => 'array', + 'context' => [ 'view', 'edit' ], + 'readonly' => true, + ], + 'default' => [ + 'description' => __( 'Optional default value for the setting.', 'woocommerce-plugin-framework' ), + 'type' => 'string', + 'context' => [ 'view', 'edit' ], + 'readonly' => true, + ], + 'value' => [ + 'description' => __( 'The value of the setting.', 'woocommerce-plugin-framework' ), + 'type' => 'string', + 'context' => [ 'view', 'edit' ], + ], + 'control' => [ + 'description' => __( 'Optional object that defines how the user will interact with and update the setting.', 'woocommerce-memberships' ), + 'type' => 'object', + 'properties' => [ + 'type' => [ + 'description' => __( 'The type of the control.', 'woocommerce-plugin-framework' ), + 'type' => 'string', + 'context' => [ 'view', 'edit' ], + 'enum' => $this->settings->get_control_types(), + 'readonly' => true, + ], + 'name' => [ + 'description' => __( "The name of the control. Inherits the setting's name.", 'woocommerce-plugin-framework' ), + 'type' => 'string', + 'context' => [ 'view', 'edit' ], + 'readonly' => true, + ], + 'description' => [ + 'description' => __( "The description of the control. Inherits the setting's description.", 'woocommerce-plugin-framework' ), + 'type' => 'string', + 'context' => [ 'view', 'edit' ], + 'readonly' => true, + ], + 'options' => [ + 'description' => __( 'A list of key/value pairs defining the display value of each setting option. The keys should match the options defined in the base setting for validation.', 'woocommerce-plugin-framework' ), + 'type' => 'array', + 'context' => [ 'view', 'edit' ], + 'readonly' => true, + ], + ], + 'context' => [ 'view', 'edit' ], + 'readonly' => true, + ], + ], + ]; + + return $this->add_additional_fields_schema( $schema ); + } + + +} + +endif; diff --git a/vendor/skyverge/wc-plugin-framework/woocommerce/rest-api/class-sv-wc-plugin-rest-api.php b/vendor/skyverge/wc-plugin-framework/woocommerce/rest-api/class-sv-wc-plugin-rest-api.php new file mode 100644 index 0000000..5700f37 --- /dev/null +++ b/vendor/skyverge/wc-plugin-framework/woocommerce/rest-api/class-sv-wc-plugin-rest-api.php @@ -0,0 +1,162 @@ +plugin = $plugin; + + $this->add_hooks(); + } + + + /** + * Adds the action and filter hooks. + * + * @since 5.2.0 + */ + protected function add_hooks() { + + // add plugin data to the system status + add_filter( 'woocommerce_rest_prepare_system_status', array( $this, 'add_system_status_data' ), 10, 3 ); + + // registers new WC REST API routes + add_action( 'rest_api_init', array( $this, 'register_routes' ) ); + } + + + /** + * Adds plugin data to the system status. + * + * @internal + * + * @since 5.2.0 + * + * @param \WP_REST_Response $response REST API response object + * @param array $system_status system status data + * @param \WP_REST_Request $request REST API request object + * @return \WP_REST_Response + */ + public function add_system_status_data( $response, $system_status, $request ) { + + $data = array( + 'is_payment_gateway' => $this->get_plugin() instanceof SV_WC_Payment_Gateway_Plugin, + 'lifecycle_events' => $this->get_plugin()->get_lifecycle_handler()->get_event_history(), + ); + + $data = array_merge( $data, $this->get_system_status_data() ); + + /** + * Filters the data added to the WooCommerce REST API System Status response. + * + * @since 5.2.0 + * + * @param array $data system status response data + * @param \WP_REST_Response $response REST API response object + * @param \WP_REST_Request $request REST API request object + */ + $data = apply_filters( 'wc_' . $this->get_plugin()->get_id() . '_rest_api_system_status_data', $data, $response, $request ); + + $response->data[ 'wc_' . $this->get_plugin()->get_id() ] = $data; + + return $response; + } + + + /** + * Gets the data to add to the WooCommerce REST API System Status response. + * + * Plugins can override this to add their own data. + * + * @since 5.2.0 + * + * @return array + */ + protected function get_system_status_data() { + + return array(); + } + + + /** + * Registers new WC REST API routes. + * + * @since 5.2.0 + */ + public function register_routes() { + + if ( $settings = $this->get_plugin()->get_settings_handler() ) { + + $settings_controller = new REST_API\Controllers\Settings( $settings ); + + $settings_controller->register_routes(); + } + } + + + /** + * Gets the plugin instance. + * + * @since 5.2.0 + * + * @return SV_WC_Plugin|SV_WC_Payment_Gateway_Plugin + */ + protected function get_plugin() { + + return $this->plugin; + } + + +} + + +endif; diff --git a/vendor/skyverge/wc-plugin-framework/woocommerce/utilities/class-sv-wp-async-request.php b/vendor/skyverge/wc-plugin-framework/woocommerce/utilities/class-sv-wp-async-request.php new file mode 100644 index 0000000..dfb6a30 --- /dev/null +++ b/vendor/skyverge/wc-plugin-framework/woocommerce/utilities/class-sv-wp-async-request.php @@ -0,0 +1,192 @@ +identifier = $this->prefix . '_' . $this->action; + + add_action( 'wp_ajax_' . $this->identifier, array( $this, 'maybe_handle' ) ); + add_action( 'wp_ajax_nopriv_' . $this->identifier, array( $this, 'maybe_handle' ) ); + } + + + /** + * Set data used during the async request + * + * @since 4.4.0 + * @param array $data + * @return SV_WP_Async_Request + */ + public function set_data( $data ) { + $this->data = $data; + + return $this; + } + + + /** + * Dispatch the async request + * + * @since 4.4.0 + * @return array|\WP_Error + */ + public function dispatch() { + + $url = add_query_arg( $this->get_query_args(), $this->get_query_url() ); + $args = $this->get_request_args(); + + return wp_safe_remote_get( esc_url_raw( $url ), $args ); + } + + + /** + * Get query args + * + * @since 4.4.0 + * @return array + */ + protected function get_query_args() { + + if ( property_exists( $this, 'query_args' ) ) { + return $this->query_args; + } + + return array( + 'action' => $this->identifier, + 'nonce' => wp_create_nonce( $this->identifier ), + ); + } + + + /** + * Get query URL + * + * @since 4.4.0 + * @return string + */ + protected function get_query_url() { + + if ( property_exists( $this, 'query_url' ) ) { + return $this->query_url; + } + + return admin_url( 'admin-ajax.php' ); + } + + + /** + * Get request args + * + * In 4.6.3 renamed from get_post_args to get_request_args + * + * @since 4.4.0 + * @return array + */ + protected function get_request_args() { + + if ( property_exists( $this, 'request_args' ) ) { + return $this->request_args; + } + + return array( + 'timeout' => 0.01, + 'blocking' => false, + 'body' => $this->data, + 'cookies' => $_COOKIE, + 'sslverify' => apply_filters( 'https_local_ssl_verify', false ), + ); + } + + + /** + * Maybe handle + * + * Check for correct nonce and pass to handler. + * @since 4.4.0 + */ + public function maybe_handle() { + check_ajax_referer( $this->identifier, 'nonce' ); + + $this->handle(); + + wp_die(); + } + + + /** + * Handle + * + * Override this method to perform any actions required + * during the async request. + * + * @since 4.4.0 + */ + abstract protected function handle(); + + +} + + +endif; diff --git a/vendor/skyverge/wc-plugin-framework/woocommerce/utilities/class-sv-wp-background-job-handler.php b/vendor/skyverge/wc-plugin-framework/woocommerce/utilities/class-sv-wp-background-job-handler.php new file mode 100644 index 0000000..31725a6 --- /dev/null +++ b/vendor/skyverge/wc-plugin-framework/woocommerce/utilities/class-sv-wp-background-job-handler.php @@ -0,0 +1,1136 @@ +create_job( $attrs ); + * $background_job_handler->dispatch(); + * + * @since 4.4.0 + */ +abstract class SV_WP_Background_Job_Handler extends SV_WP_Async_Request { + + + /** @var string async request prefix */ + protected $prefix = 'sv_wp'; + + /** @var string async request action */ + protected $action = 'background_job'; + + /** @var string data key */ + protected $data_key = 'data'; + + /** @var int start time of current process */ + protected $start_time = 0; + + /** @var string cron hook identifier */ + protected $cron_hook_identifier; + + /** @var string cron interval identifier */ + protected $cron_interval_identifier; + + /** @var string debug message, used by the system status tool */ + protected $debug_message; + + + /** + * Initiate new background job handler + * + * @since 4.4.0 + */ + public function __construct() { + + parent::__construct(); + + $this->cron_hook_identifier = $this->identifier . '_cron'; + $this->cron_interval_identifier = $this->identifier . '_cron_interval'; + + $this->add_hooks(); + } + + + /** + * Adds the necessary action and filter hooks. + * + * @since 4.8.0 + */ + protected function add_hooks() { + + // cron healthcheck + add_action( $this->cron_hook_identifier, array( $this, 'handle_cron_healthcheck' ) ); + add_filter( 'cron_schedules', array( $this, 'schedule_cron_healthcheck' ) ); + + // debugging & testing + add_action( "wp_ajax_nopriv_{$this->identifier}_test", array( $this, 'handle_connection_test_response' ) ); + add_filter( 'woocommerce_debug_tools', array( $this, 'add_debug_tool' ) ); + add_filter( 'gettext', array( $this, 'translate_success_message' ), 10, 3 ); + } + + + /** + * Dispatch + * + * @since 4.4.0 + * @return array|WP_Error + */ + public function dispatch() { + + // schedule the cron healthcheck + $this->schedule_event(); + + // perform remote post + return parent::dispatch(); + } + + + /** + * Maybe processes job queue. + * + * Checks whether data exists within the job queue and that the background process is not already running. + * + * @since 4.4.0 + * + * @throws \Exception upon error + */ + public function maybe_handle() { + + if ( $this->is_process_running() ) { + // background process already running + wp_die(); + } + + if ( $this->is_queue_empty() ) { + // no data to process + wp_die(); + } + + /** + * WC core does 2 things here that can interfere with our nonce check: + * + * 1. WooCommerce starts a session due to our GET request to dispatch a job + * However, this happens *after* we've generated a nonce without a session (in CRON context) + * 2. it then filters nonces for logged-out users indiscriminately without checking the nonce action; if + * there is a session created (and now the server does have one), it tries to filter every.single.nonce + * for logged-out users to use the customer session ID instead of 0 for user ID. We *want* to check + * against a UID of 0 (since that's how the nonce was created), so we temporarily pause the + * logged-out nonce hijacking before standing aside. + * + * @see \WC_Session_Handler::init() when the action is hooked + * @see \WC_Session_Handler::nonce_user_logged_out() WC < 5.3 callback + * @see \WC_Session_Handler::maybe_update_nonce_user_logged_out() WC >= 5.3 callback + */ + if ( SV_WC_Plugin_Compatibility::is_wc_version_gte('5.3') ) { + $callback = [ WC()->session, 'maybe_update_nonce_user_logged_out' ]; + $arguments = 2; + } else { + $callback = [ WC()->session, 'nonce_user_logged_out' ]; + $arguments = 1; + } + + remove_filter( 'nonce_user_logged_out', $callback ); + + check_ajax_referer( $this->identifier, 'nonce' ); + + // sorry, later nonce users! please play again + add_filter( 'nonce_user_logged_out', $callback, 10, $arguments ); + + $this->handle(); + + wp_die(); + } + + + /** + * Check whether job queue is empty or not + * + * @since 4.4.0 + * @return bool True if queue is empty, false otherwise + */ + protected function is_queue_empty() { + global $wpdb; + + $key = $this->identifier . '_job_%'; + + // only queued or processing jobs count + $queued = '%"status":"queued"%'; + $processing = '%"status":"processing"%'; + + $count = $wpdb->get_var( $wpdb->prepare( " + SELECT COUNT(*) + FROM {$wpdb->options} + WHERE option_name LIKE %s + AND ( option_value LIKE %s OR option_value LIKE %s ) + ", $key, $queued, $processing ) ); + + return ( $count > 0 ) ? false : true; + } + + + /** + * Check whether background process is running or not + * + * Check whether the current process is already running + * in a background process. + * + * @since 4.4.0 + * @return bool True if processing is running, false otherwise + */ + protected function is_process_running() { + + // add a random artificial delay to prevent a race condition if 2 or more processes are trying to + // process the job queue at the very same moment in time and neither of them have yet set the lock + // before the others are calling this method + usleep( rand( 100000, 300000 ) ); + + return (bool) get_transient( "{$this->identifier}_process_lock" ); + } + + + /** + * Lock process + * + * Lock the process so that multiple instances can't run simultaneously. + * Override if applicable, but the duration should be greater than that + * defined in the time_exceeded() method. + * + * @since 4.4.0 + */ + protected function lock_process() { + + // set start time of current process + $this->start_time = time(); + + // set lock duration to 1 minute by default + $lock_duration = ( property_exists( $this, 'queue_lock_time' ) ) ? $this->queue_lock_time : 60; + + /** + * Filter the queue lock time + * + * @since 4.4.0 + * @param int $lock_duration Lock duration in seconds + */ + $lock_duration = apply_filters( "{$this->identifier}_queue_lock_time", $lock_duration ); + + set_transient( "{$this->identifier}_process_lock", microtime(), $lock_duration ); + } + + + /** + * Unlock process + * + * Unlock the process so that other instances can spawn. + * + * @since 4.4.0 + * @return SV_WP_Background_Job_Handler + */ + protected function unlock_process() { + + delete_transient( "{$this->identifier}_process_lock" ); + + return $this; + } + + + /** + * Check if memory limit is exceeded + * + * Ensures the background job handler process never exceeds 90% + * of the maximum WordPress memory. + * + * @since 4.4.0 + * + * @return bool True if exceeded memory limit, false otherwise + */ + protected function memory_exceeded() { + + $memory_limit = $this->get_memory_limit() * 0.9; // 90% of max memory + $current_memory = memory_get_usage( true ); + $return = false; + + if ( $current_memory >= $memory_limit ) { + $return = true; + } + + /** + * Filter whether memory limit has been exceeded or not + * + * @since 4.4.0 + * + * @param bool $exceeded + */ + return apply_filters( "{$this->identifier}_memory_exceeded", $return ); + } + + + /** + * Get memory limit + * + * @since 4.4.0 + * + * @return int memory limit in bytes + */ + protected function get_memory_limit() { + + if ( function_exists( 'ini_get' ) ) { + $memory_limit = ini_get( 'memory_limit' ); + } else { + // sensible default + $memory_limit = '128M'; + } + + if ( ! $memory_limit || -1 === (int) $memory_limit ) { + // unlimited, set to 32GB + $memory_limit = '32G'; + } + + return SV_WC_Plugin_Compatibility::convert_hr_to_bytes( $memory_limit ); + } + + + /** + * Check whether request time limit has been exceeded or not + * + * Ensures the background job handler never exceeds a sensible time limit. + * A timeout limit of 30s is common on shared hosting. + * + * @since 4.4.0 + * + * @return bool True, if time limit exceeded, false otherwise + */ + protected function time_exceeded() { + + /** + * Filter default time limit for background job execution, defaults to + * 20 seconds + * + * @since 4.4.0 + * + * @param int $time Time in seconds + */ + $finish = $this->start_time + apply_filters( "{$this->identifier}_default_time_limit", 20 ); + $return = false; + + if ( time() >= $finish ) { + $return = true; + } + + /** + * Filter whether maximum execution time has exceeded or not + * + * @since 4.4.0 + * @param bool $exceeded true if execution time exceeded, false otherwise + */ + return apply_filters( "{$this->identifier}_time_exceeded", $return ); + } + + + /** + * Create a background job + * + * Delicious Brains' versions alternative would be using ->data()->save(). + * Allows passing in any kind of job attributes, which will be available at item data processing time. + * This allows sharing common options between items without the need to repeat + * the same information for every single item in queue. + * + * Instead of returning self, returns the job instance, which gives greater + * control over the job. + * + * @since 4.4.0 + * + * @param array|mixed $attrs Job attributes. + * @return \stdClass|object|null + */ + public function create_job( $attrs ) { + global $wpdb; + + if ( empty( $attrs ) ) { + return null; + } + + // generate a unique ID for the job + $job_id = md5( microtime() . mt_rand() ); + + /** + * Filter new background job attributes + * + * @since 4.4.0 + * + * @param array $attrs Job attributes + * @param string $id Job ID + */ + $attrs = apply_filters( "{$this->identifier}_new_job_attrs", $attrs, $job_id ); + + // ensure a few must-have attributes + $attrs = wp_parse_args( array( + 'id' => $job_id, + 'created_at' => current_time( 'mysql' ), + 'created_by' => get_current_user_id(), + 'status' => 'queued', + ), $attrs ); + + $wpdb->insert( $wpdb->options, array( + 'option_name' => "{$this->identifier}_job_{$job_id}", + 'option_value' => json_encode( $attrs ), + 'autoload' => 'no' + ) ); + + $job = new \stdClass(); + + foreach ( $attrs as $key => $value ) { + $job->{$key} = $value; + } + + /** + * Runs when a job is created. + * + * @since 4.4.0 + * + * @param \stdClass|object $job the created job + */ + do_action( "{$this->identifier}_job_created", $job ); + + return $job; + } + + + /** + * Get a job (by default the first in the queue) + * + * @since 4.4.0 + * + * @param string $id Optional. Job ID. Will return first job in queue if not + * provided. Will not return completed or failed jobs from queue. + * @return \stdClass|object|null The found job object or null + */ + public function get_job( $id = null ) { + global $wpdb; + + if ( ! $id ) { + + $key = $this->identifier . '_job_%'; + $queued = '%"status":"queued"%'; + $processing = '%"status":"processing"%'; + + $results = $wpdb->get_var( $wpdb->prepare( " + SELECT option_value + FROM {$wpdb->options} + WHERE option_name LIKE %s + AND ( option_value LIKE %s OR option_value LIKE %s ) + ORDER BY option_id ASC + LIMIT 1 + ", $key, $queued, $processing ) ); + + } else { + + $results = $wpdb->get_var( $wpdb->prepare( " + SELECT option_value + FROM {$wpdb->options} + WHERE option_name = %s + ", "{$this->identifier}_job_{$id}" ) ); + + } + + if ( ! empty( $results ) ) { + + $job = new \stdClass(); + + foreach ( json_decode( $results, true ) as $key => $value ) { + $job->{$key} = $value; + } + + } else { + return null; + } + + /** + * Filters the job as returned from the database. + * + * @since 4.4.0 + * + * @param \stdClass|object $job + */ + return apply_filters( "{$this->identifier}_returned_job", $job ); + } + + + /** + * Gets jobs. + * + * @since 4.4.2 + * + * @param array $args { + * Optional. An array of arguments + * + * @type string|array $status Job status(es) to include + * @type string $order ASC or DESC. Defaults to DESC + * @type string $orderby Field to order by. Defaults to option_id + * } + * @return \stdClass[]|object[]|null Found jobs or null if none found + */ + public function get_jobs( $args = array() ) { + global $wpdb; + + $args = wp_parse_args( $args, array( + 'order' => 'DESC', + 'orderby' => 'option_id', + ) ); + + $replacements = array( $this->identifier . '_job_%' ); + $status_query = ''; + + // prepare status query + if ( ! empty( $args['status'] ) ) { + + $statuses = (array) $args['status']; + $placeholders = array(); + + foreach ( $statuses as $status ) { + + $placeholders[] = '%s'; + $replacements[] = '%"status":"' . sanitize_key( $status ) . '"%'; + } + + $status_query = 'AND ( option_value LIKE ' . implode( ' OR option_value LIKE ', $placeholders ) . ' )'; + } + + // prepare sorting vars + $order = sanitize_key( $args['order'] ); + $orderby = sanitize_key( $args['orderby'] ); + + // put it all together now + $query = $wpdb->prepare( " + SELECT option_value + FROM {$wpdb->options} + WHERE option_name LIKE %s + {$status_query} + ORDER BY {$orderby} {$order} + ", $replacements ); + + $results = $wpdb->get_col( $query ); + + if ( empty( $results ) ) { + return null; + } + + $jobs = array(); + + foreach ( $results as $result ) { + + $job = new \stdClass(); + + foreach ( json_decode( $result, true ) as $key => $value ) { + $job->{$key} = $value; + } + + /** This filter is documented above */ + $job = apply_filters( "{$this->identifier}_returned_job", $job ); + + $jobs[] = $job; + } + + return $jobs; + } + + + /** + * Handles jobs. + * + * Process jobs while remaining within server memory and time limit constraints. + * + * @since 4.4.0 + * + * @throws \Exception + */ + protected function handle() { + + $this->lock_process(); + + do { + + // Get next job in the queue + $job = $this->get_job(); + + // handle PHP errors from here on out + register_shutdown_function( array( $this, 'handle_shutdown' ), $job ); + + // Start processing + $this->process_job( $job ); + + } while ( ! $this->time_exceeded() && ! $this->memory_exceeded() && ! $this->is_queue_empty() ); + + $this->unlock_process(); + + // Start next job or complete process + if ( ! $this->is_queue_empty() ) { + $this->dispatch(); + } else { + $this->complete(); + } + + wp_die(); + } + + + /** + * Process a job + * + * Default implementation is to loop over job data and passing each item to + * the item processor. Subclasses are, however, welcome to override this method + * to create totally different job processing implementations - see + * WC_CSV_Import_Suite_Background_Import in CSV Import for an example. + * + * If using the default implementation, the job must have a $data_key property set. + * Subclasses can override the data key, but the contents must be an array which + * the job processor can loop over. By default, the data key is `data`. + * + * If no data is set, the job will completed right away. + * + * @since 4.4.0 + * + * @param \stdClass|object $job + * @param int $items_per_batch number of items to process in a single request. Defaults to unlimited. + * @throws \Exception when job data is incorrect + * @return \stdClass $job + */ + public function process_job( $job, $items_per_batch = null ) { + + if ( ! $this->start_time ) { + $this->start_time = time(); + } + + // Indicate that the job has started processing + if ( 'processing' !== $job->status ) { + + $job->status = 'processing'; + $job->started_processing_at = current_time( 'mysql' ); + + $job = $this->update_job( $job ); + } + + $data_key = $this->data_key; + + if ( ! isset( $job->{$data_key} ) ) { + throw new \Exception( sprintf( __( 'Job data key "%s" not set', 'woocommerce-plugin-framework' ), $data_key ) ); + } + + if ( ! is_array( $job->{$data_key} ) ) { + throw new \Exception( sprintf( __( 'Job data key "%s" is not an array', 'woocommerce-plugin-framework' ), $data_key ) ); + } + + $data = $job->{$data_key}; + + $job->total = count( $data ); + + // progress indicates how many items have been processed, it + // does NOT indicate the processed item key in any way + if ( ! isset( $job->progress ) ) { + $job->progress = 0; + } + + // skip already processed items + if ( $job->progress && ! empty( $data ) ) { + $data = array_slice( $data, $job->progress, null, true ); + } + + // loop over unprocessed items and process them + if ( ! empty( $data ) ) { + + $processed = 0; + $items_per_batch = (int) $items_per_batch; + + foreach ( $data as $item ) { + + // process the item + $this->process_item( $item, $job ); + + $processed++; + $job->progress++; + + // update job progress + $job = $this->update_job( $job ); + + // job limits reached + if ( ( $items_per_batch && $processed >= $items_per_batch ) || $this->time_exceeded() || $this->memory_exceeded() ) { + break; + } + } + } + + // complete current job + if ( $job->progress >= count( $job->{$data_key} ) ) { + $job = $this->complete_job( $job ); + } + + return $job; + } + + + /** + * Update job attrs + * + * @since 4.4.0 + * + * @param \stdClass|object|string $job Job instance or ID + * @return \stdClass|object|false on failure + */ + public function update_job( $job ) { + + if ( is_string( $job ) ) { + $job = $this->get_job( $job ); + } + + if ( ! $job ) { + return false; + } + + $job->updated_at = current_time( 'mysql' ); + + $this->update_job_option( $job ); + + /** + * Runs when a job is updated. + * + * @since 4.4.0 + * + * @param \stdClass|object $job the updated job + */ + do_action( "{$this->identifier}_job_updated", $job ); + + return $job; + } + + + /** + * Handles job completion. + * + * @since 4.4.0 + * + * @param \stdClass|object|string $job Job instance or ID + * @return \stdClass|object|false on failure + */ + public function complete_job( $job ) { + + if ( is_string( $job ) ) { + $job = $this->get_job( $job ); + } + + if ( ! $job ) { + return false; + } + + $job->status = 'completed'; + $job->completed_at = current_time( 'mysql' ); + + $this->update_job_option( $job ); + + /** + * Runs when a job is completed. + * + * @since 4.4.0 + * + * @param \stdClass|object $job the completed job + */ + do_action( "{$this->identifier}_job_complete", $job ); + + return $job; + } + + + /** + * Handle job failure + * + * Default implementation does not call this method directly, but it's + * provided as a convenience method for subclasses that may call this to + * indicate that a particular job has failed for some reason. + * + * @since 4.4.0 + * + * @param \stdClass|object|string $job Job instance or ID + * @param string $reason Optional. Reason for failure. + * @return \stdClass|false on failure + */ + public function fail_job( $job, $reason = '' ) { + + if ( is_string( $job ) ) { + $job = $this->get_job( $job ); + } + + if ( ! $job ) { + return false; + } + + $job->status = 'failed'; + $job->failed_at = current_time( 'mysql' ); + + if ( $reason ) { + $job->failure_reason = $reason; + } + + $this->update_job_option( $job ); + + /** + * Runs when a job is failed. + * + * @since 4.4.0 + * + * @param \stdClass|object $job the failed job + */ + do_action( "{$this->identifier}_job_failed", $job ); + + return $job; + } + + + /** + * Delete a job + * + * @since 4.4.2 + * + * @param \stdClass|object|string $job Job instance or ID + * @return false on failure + */ + public function delete_job( $job ) { + global $wpdb; + + if ( is_string( $job ) ) { + $job = $this->get_job( $job ); + } + + if ( ! $job ) { + return false; + } + + $wpdb->delete( $wpdb->options, array( 'option_name' => "{$this->identifier}_job_{$job->id}" ) ); + + /** + * Runs after a job is deleted. + * + * @since 4.4.2 + * + * @param \stdClass|object $job the job that was deleted from database + */ + do_action( "{$this->identifier}_job_deleted", $job ); + } + + + /** + * Handle job queue completion + * + * Override if applicable, but ensure that the below actions are + * performed, or, call parent::complete(). + * + * @since 4.4.0 + */ + protected function complete() { + + // unschedule the cron healthcheck + $this->clear_scheduled_event(); + } + + + /** + * Schedule cron healthcheck + * + * @since 4.4.0 + * @param array $schedules + * @return array + */ + public function schedule_cron_healthcheck( $schedules ) { + + $interval = property_exists( $this, 'cron_interval' ) ? $this->cron_interval : 5; + + /** + * Filter cron health check interval + * + * @since 4.4.0 + * @param int $interval Interval in minutes + */ + $interval = apply_filters( "{$this->identifier}_cron_interval", $interval ); + + // adds every 5 minutes to the existing schedules. + $schedules[ $this->identifier . '_cron_interval' ] = array( + 'interval' => MINUTE_IN_SECONDS * $interval, + 'display' => sprintf( __( 'Every %d Minutes' ), $interval ), + ); + + return $schedules; + } + + + /** + * Handle cron healthcheck + * + * Restart the background process if not already running + * and data exists in the queue. + * + * @since 4.4.0 + */ + public function handle_cron_healthcheck() { + + if ( $this->is_process_running() ) { + // background process already running + exit; + } + + if ( $this->is_queue_empty() ) { + // no data to process + $this->clear_scheduled_event(); + exit; + } + + $this->dispatch(); + } + + + /** + * Schedule cron health check event + * + * @since 4.4.0 + */ + protected function schedule_event() { + + if ( ! wp_next_scheduled( $this->cron_hook_identifier ) ) { + + // schedule the health check to fire after 30 seconds from now, as to not create a race condition + // with job process lock on servers that fire & handle cron events instantly + wp_schedule_event( time() + 30, $this->cron_interval_identifier, $this->cron_hook_identifier ); + } + } + + + /** + * Clear scheduled health check event + * + * @since 4.4.0 + */ + protected function clear_scheduled_event() { + + $timestamp = wp_next_scheduled( $this->cron_hook_identifier ); + + if ( $timestamp ) { + wp_unschedule_event( $timestamp, $this->cron_hook_identifier ); + } + } + + + /** + * Process an item from job data + * + * Implement this method to perform any actions required on each + * item in job data. + * + * @since 4.4.2 + * + * @param mixed $item Job data item to iterate over + * @param \stdClass|object $job Job instance + * @return mixed + */ + abstract protected function process_item( $item, $job ); + + + /** + * Handles PHP shutdown, say after a fatal error. + * + * @since 4.5.0 + * + * @param \stdClass|object $job the job being processed + */ + public function handle_shutdown( $job ) { + + $error = error_get_last(); + + // if shutting down because of a fatal error, fail the job + if ( $error && E_ERROR === $error['type'] ) { + + $this->fail_job( $job, $error['message'] ); + + $this->unlock_process(); + } + } + + + /** + * Update a job option in options database. + * + * @since 4.6.3 + * + * @param \stdClass|object $job the job instance to update in database + * @return int|bool number of rows updated or false on failure, see wpdb::update() + */ + private function update_job_option( $job ) { + global $wpdb; + + return $wpdb->update( + $wpdb->options, + array( 'option_value' => json_encode( $job ) ), + array( 'option_name' => "{$this->identifier}_job_{$job->id}" ) + ); + } + + + /** Debug & Testing Methods ***********************************************/ + + + /** + * Tests the background handler's connection. + * + * @since 4.8.0 + * + * @return bool + */ + public function test_connection() { + + $test_url = add_query_arg( 'action', "{$this->identifier}_test", admin_url( 'admin-ajax.php' ) ); + $result = wp_safe_remote_get( $test_url ); + $body = ! is_wp_error( $result ) ? wp_remote_retrieve_body( $result ) : null; + + // some servers may add a UTF8-BOM at the beginning of the response body, so we check if our test + // string is included in the body, as an equal check would produce a false negative test result + return $body && SV_WC_Helper::str_exists( $body, '[TEST_LOOPBACK]' ); + } + + + /** + * Handles the connection test request. + * + * @since 4.8.0 + */ + public function handle_connection_test_response() { + + echo '[TEST_LOOPBACK]'; + exit; + } + + + /** + * Adds the WooCommerce debug tool. + * + * @since 4.8.0 + * + * @param array $tools WooCommerce core tools + * @return array + */ + public function add_debug_tool( $tools ) { + + // this key is not unique to the plugin to avoid duplicate tools + $tools['sv_wc_background_job_test'] = array( + 'name' => __( 'Background Processing Test', 'woocommerce-plugin-framework' ), + 'button' => __( 'Run Test', 'woocommerce-plugin-framework' ), + 'desc' => __( 'This tool will test whether your server is capable of processing background jobs.', 'woocommerce-plugin-framework' ), + 'callback' => array( $this, 'run_debug_tool' ), + ); + + return $tools; + } + + + /** + * Runs the test connection debug tool. + * + * @since 4.8.0 + * + * @return string + */ + public function run_debug_tool() { + + if ( $this->test_connection() ) { + $this->debug_message = esc_html__( 'Success! You should be able to process background jobs.', 'woocommerce-plugin-framework' ); + $result = true; + } else { + $this->debug_message = esc_html__( 'Could not connect. Please ask your hosting company to ensure your server has loopback connections enabled.', 'woocommerce-plugin-framework' ); + $result = false; + } + + return $result; + } + + + /** + * Translate the tool success message. + * + * This can be removed in favor of returning the message string in `run_debug_tool()` + * when WC 3.1 is required, though that means the message will always be "success" styled. + * + * @since 4.8.0 + * + * @param string $translated the text to output + * @param string $original the original text + * @param string $domain the textdomain + * @return string the updated text + */ + public function translate_success_message( $translated, $original, $domain ) { + + if ( 'woocommerce' === $domain && ( 'Tool ran.' === $original || 'There was an error calling %s' === $original ) ) { + $translated = $this->debug_message; + } + + return $translated; + } + + + /** Helper Methods ********************************************************/ + + + /** + * Gets the job handler identifier. + * + * @since 4.8.0 + * + * @return string + */ + public function get_identifier() { + + return $this->identifier; + } + + +} + + +endif; diff --git a/vendor/skyverge/wc-plugin-framework/woocommerce/utilities/class-sv-wp-job-batch-handler.php b/vendor/skyverge/wc-plugin-framework/woocommerce/utilities/class-sv-wp-job-batch-handler.php new file mode 100644 index 0000000..7fe8420 --- /dev/null +++ b/vendor/skyverge/wc-plugin-framework/woocommerce/utilities/class-sv-wp-job-batch-handler.php @@ -0,0 +1,310 @@ +job_handler = $job_handler; + $this->plugin = $plugin; + + $this->add_hooks(); + + $this->render_js(); + } + + + /** + * Adds the necessary action and filter hooks. + * + * @since 4.8.0 + */ + protected function add_hooks() { + + add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_scripts' ) ); + + add_action( 'wp_ajax_' . $this->get_job_handler()->get_identifier() . '_process_batch', array( $this, 'ajax_process_batch' ) ); + add_action( 'wp_ajax_' . $this->get_job_handler()->get_identifier() . '_cancel_job', array( $this, 'ajax_cancel_job' ) ); + } + + + /** + * Enqueues the scripts. + * + * @since 4.8.0 + */ + public function enqueue_scripts() { + + wp_enqueue_script( $this->get_job_handler()->get_identifier() . '_batch_handler', $this->get_plugin()->get_framework_assets_url() . '/js/admin/sv-wp-admin-job-batch-handler.min.js', array( 'jquery' ), $this->get_plugin()->get_version() ); + } + + + /** + * Renders the inline JavaScript for instantiating the batch handler class. + * + * @since 4.8.0 + */ + protected function render_js() { + + /** + * Filters the JavaScript batch handler arguments. + * + * @since 4.8.0 + * + * @param array $args arguments to pass to the JavaScript batch handler + * @param SV_WP_Job_Batch_Handler $handler handler object + */ + $args = apply_filters( $this->get_job_handler()->get_identifier() . '_batch_handler_js_args', $this->get_js_args(), $this ); + + wc_enqueue_js( sprintf( 'window.%1$s_batch_handler = new %2$s( %3$s );', + esc_js( $this->get_job_handler()->get_identifier() ), + esc_js( $this->get_js_class() ), + json_encode( $args ) + ) ); + } + + + /** + * Gets the JavaScript batch handler arguments. + * + * @since 4.8.0 + * + * @return array + */ + protected function get_js_args() { + + return array( + 'id' => $this->get_job_handler()->get_identifier(), + 'process_nonce' => wp_create_nonce( $this->get_job_handler()->get_identifier() . '_process_batch' ), + 'cancel_nonce' => wp_create_nonce( $this->get_job_handler()->get_identifier() . '_cancel_job' ), + ); + } + + + /** + * Gets the JavaScript batch handler class name. + * + * Plugins can override this with their own handler that extends the base. + * + * @since 4.8.0 + * + * @return string + */ + protected function get_js_class() { + + return 'SV_WP_Job_Batch_Handler'; + } + + + /** + * Processes a job batch via AJAX. + * + * @internal + * + * @since 4.8.0 + * + * @throws \Exception upon error. + */ + public function ajax_process_batch() { + + check_ajax_referer( $this->get_job_handler()->get_identifier() . '_process_batch', 'security' ); + + if ( empty( $_POST['job_id'] ) ) { + return; + } + + try { + + $job = $this->process_batch( $_POST['job_id'] ); + + $job = $this->process_job_status( $job ); + + wp_send_json_success( (array) $job ); + + } catch( SV_WC_Plugin_Exception $e ) { + + $data = ( ! empty( $job ) ) ? (array) $job : array(); + + $data['message'] = $e->getMessage(); + + wp_send_json_error( $data ); + } + } + + + /** + * Cancels a job via AJAX. + * + * @internal + * + * @since 4.8.0 + */ + public function ajax_cancel_job() { + + check_ajax_referer( $this->get_job_handler()->get_identifier() . '_cancel_job', 'security' ); + + if ( empty( $_POST['job_id'] ) ) { + return; + } + + $this->get_job_handler()->delete_job( $_POST['job_id'] ); + + wp_send_json_success(); + } + + + /** + * Handles a job after processing one of its batches. + * + * Allows plugins to add extra job properties and handle certain statuses. + * Implementations may throw a SV_WC_Plugin_Exception. + * + * @since 4.8.0 + * + * @param \stdClass|object $job job object + * @return \stdClass|object $job job object + */ + protected function process_job_status( $job ) { + + $job->percentage = SV_WC_Helper::number_format( (int) $job->progress / (int) $job->total * 100 ); + + return $job; + } + + + /** + * Processes a batch of items for the given job. + * + * A batch consists of the number of items defined by self::get_items_per_batch() + * or the number we're able to process before exceeding time or memory limits. + * + * @since 4.8.0 + * + * @param string $job_id job to process + * @return \stdClass|object $job job after processing the batch + * @throws \Exception + * @throws SV_WC_Plugin_Exception + */ + public function process_batch( $job_id ) { + + $job = $this->get_job_handler()->get_job( $job_id ); + + if ( ! $job ) { + throw new SV_WC_Plugin_Exception( 'Invalid job ID' ); + } + + return $this->get_job_handler()->process_job( $job, $this->get_items_per_batch() ); + } + + + /** + * Gets the number of items to process in a single request when processing job item batches. + * + * @since 4.8.0 + * + * @return int + */ + protected function get_items_per_batch() { + + /** + * Filters the number of items to process in a single request when processing job item batches. + * + * @since 4.8.0 + * + * @param int $items_per_batch + */ + $items_per_batch = absint( apply_filters( $this->get_job_handler()->get_identifier() . '_batch_handler_items_per_batch', $this->items_per_batch ) ); + + return $items_per_batch > 0 ? $items_per_batch : 1; + } + + + /** + * Gets the job handler. + * + * @since 4.8.0 + * + * @return SV_WP_Background_Job_Handler + */ + protected function get_job_handler() { + + return $this->job_handler; + } + + + /** + * Gets the plugin instance. + * + * @since 4.8.0 + * + * @return SV_WC_Plugin + */ + protected function get_plugin() { + + return $this->plugin; + } + + +} + + +endif; diff --git a/woocommerce-tab-manager-template.php b/woocommerce-tab-manager-template.php new file mode 100644 index 0000000..0899327 --- /dev/null +++ b/woocommerce-tab-manager-template.php @@ -0,0 +1,82 @@ + (string) Tab title, + * 'priority' => (string) Tab priority, + * 'callback' => (mixed) callback function, + * 'id' => (int) tab post identifier, + * ) + * + * @since 1.0.5 + * + * @global \WC_Tab_Manager wc_tab_manager() + * @global \WC_Product $product + * + * @param string $key tab key, this is the sanitized tab title with possibly a numerical suffix to avoid key clashes + * @param null|array $tab tab data + */ + function woocommerce_tab_manager_tab_content( $key, $tab ) { + global $product; + + if ( $product && ( $tab = wc_tab_manager()->get_product_tab( $product->get_id(), $tab['id'], true ) ) ) { + + // first look for a template specific for this tab + $template_name = "single-product/tabs/content-{$tab['name']}.php"; + $located = locate_template( [ + trailingslashit( WC()->template_path() ) . $template_name, + $template_name + ] ); + + // if not found, fallback to the general template + if ( ! $located ) { + $template_name = 'single-product/tabs/content.php'; + } + + wc_tab_manager()->load_template( $template_name, [ 'tab' => $tab ] ); + } + } + +} diff --git a/woocommerce-tab-manager.php b/woocommerce-tab-manager.php new file mode 100644 index 0000000..c387ca6 --- /dev/null +++ b/woocommerce-tab-manager.php @@ -0,0 +1,390 @@ +is_environment_compatible() ) { + + add_action( 'plugins_loaded', array( $this, 'init_plugin' ) ); + } + } + + + /** + * Cloning instances is forbidden due to singleton pattern. + * + * @since 1.10.0 + */ + public function __clone() { + + _doing_it_wrong( __FUNCTION__, sprintf( 'You cannot clone instances of %s.', get_class( $this ) ), '1.10.0' ); + } + + + /** + * Unserializing instances is forbidden due to singleton pattern. + * + * @since 1.10.0 + */ + public function __wakeup() { + + _doing_it_wrong( __FUNCTION__, sprintf( 'You cannot unserialize instances of %s.', get_class( $this ) ), '1.10.0' ); + } + + + /** + * Initializes the plugin. + * + * @since 1.10.0 + */ + public function init_plugin() { + + if ( $this->plugins_compatible() ) { + + $this->load_framework(); + + // load the main plugin class + require_once( plugin_dir_path( __FILE__ ) . 'class-wc-tab-manager.php' ); + + // fire it up! + wc_tab_manager(); + } + } + + + /** + * Loads the base framework classes. + * + * @since 1.10.0 + */ + protected function load_framework() { + + if ( ! class_exists( '\\SkyVerge\\WooCommerce\\PluginFramework\\' . $this->get_framework_version_namespace() . '\\SV_WC_Plugin' ) ) { + + require_once( plugin_dir_path( __FILE__ ) . 'vendor/skyverge/wc-plugin-framework/woocommerce/class-sv-wc-plugin.php' ); + } + } + + + /** + * Returns the framework version in namespace form. + * + * @since 1.10.0 + * + * @return string + */ + protected function get_framework_version_namespace() { + + return 'v' . str_replace( '.', '_', $this->get_framework_version() ); + } + + + /** + * Returns the framework version used by this plugin. + * + * @since 1.10.0 + * + * @return string + */ + protected function get_framework_version() { + + return self::FRAMEWORK_VERSION; + } + + + /** + * Checks the server environment and other factors and deactivates plugins as necessary. + * + * Based on http://wptavern.com/how-to-prevent-wordpress-plugins-from-activating-on-sites-with-incompatible-hosting-environments + * + * @since 1.10.0 + */ + public function activation_check() { + + if ( ! $this->is_environment_compatible() ) { + + $this->deactivate_plugin(); + + wp_die( self::PLUGIN_NAME . ' could not be activated. ' . $this->get_environment_message() ); + } + } + + + /** + * Checks the environment on loading WordPress, just in case the environment changes after activation. + * + * @since 1.10.0 + */ + public function check_environment() { + + if ( ! $this->is_environment_compatible() && is_plugin_active( plugin_basename( __FILE__ ) ) ) { + + $this->deactivate_plugin(); + + $this->add_admin_notice( 'bad_environment', 'error', self::PLUGIN_NAME . ' has been deactivated. ' . $this->get_environment_message() ); + } + } + + + /** + * Adds notices for out-of-date WordPress and/or WooCommerce versions. + * + * @since 1.10.0 + */ + public function add_plugin_notices() { + + if ( ! $this->is_wp_compatible() ) { + + $this->add_admin_notice( 'update_wordpress', 'error', sprintf( + '%s requires WordPress version %s or higher. Please %supdate WordPress »%s', + '' . self::PLUGIN_NAME . '', + self::MINIMUM_WP_VERSION, + '', '' + ) ); + } + + if ( ! $this->is_wc_compatible() ) { + + $this->add_admin_notice( 'update_woocommerce', 'error', sprintf( + '%s requires WooCommerce version %s or higher. Please %supdate WooCommerce »%s', + '' . self::PLUGIN_NAME . '', + self::MINIMUM_WC_VERSION, + '', '' + ) ); + } + } + + + /** + * Determines if the required plugins are compatible. + * + * @since 1.10.0 + * + * @return bool + */ + protected function plugins_compatible() { + + return $this->is_wp_compatible() && $this->is_wc_compatible(); + } + + + /** + * Determines if the WordPress compatible. + * + * @since 1.10.0 + * + * @return bool + */ + protected function is_wp_compatible() { + + return version_compare( get_bloginfo( 'version' ), self::MINIMUM_WP_VERSION, '>=' ); + } + + + /** + * Determines if the WooCommerce compatible. + * + * @since 1.10.0 + * + * @return bool + */ + protected function is_wc_compatible() { + + return defined( 'WC_VERSION' ) && version_compare( WC_VERSION, self::MINIMUM_WC_VERSION, '>=' ); + } + + + /** + * Deactivates the plugin. + * + * @since 1.10.0 + */ + protected function deactivate_plugin() { + + deactivate_plugins( plugin_basename( __FILE__ ) ); + + if ( isset( $_GET['activate'] ) ) { + unset( $_GET['activate'] ); + } + } + + + /** + * Adds an admin notice to be displayed. + * + * @since 1.10.0 + * + * @param string $slug the notice slug + * @param string $class the notice class + * @param string $message the notice message body + */ + public function add_admin_notice( $slug, $class, $message ) { + + $this->notices[ $slug ] = array( + 'class' => $class, + 'message' => $message + ); + } + + + /** + * Displays any admin notices added during plugin loading. + * + * @internal + * + * @since 1.10.0 + */ + public function admin_notices() { + + foreach ( (array) $this->notices as $notice_key => $notice ) : + + ?> +
    +

    array( 'href' => array() ) ) ); ?>

    +
    + =' ); + } + + + /** + * Returns the message for display when the environment is incompatible with this plugin. + * + * @since 1.10.0 + * + * @return string + */ + protected function get_environment_message() { + + return sprintf( 'The minimum PHP version required for this plugin is %1$s. You are running %2$s.', self::MINIMUM_PHP_VERSION, PHP_VERSION ); + } + + + /** + * Returns the main plugin loader instance. + * + * Ensures only one instance can be loaded. + * + * @since 1.10.0 + * + * @return \WC_Tab_Manager_Loader + */ + public static function instance() { + + if ( null === self::$instance ) { + + self::$instance = new self(); + } + + return self::$instance; + } + + +} + +// fire it up! +WC_Tab_Manager_Loader::instance(); diff --git a/wpml-config.xml b/wpml-config.xml new file mode 100644 index 0000000..ee3ac3f --- /dev/null +++ b/wpml-config.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + +