diff --git a/app/Themes/Apiary/Providers/AssetServiceProvider.php b/app/Themes/Apiary/Providers/AssetServiceProvider.php new file mode 100644 index 00000000000..4d46d88bdc5 --- /dev/null +++ b/app/Themes/Apiary/Providers/AssetServiceProvider.php @@ -0,0 +1,32 @@ +toFrontend() + ->useVite(); + + Filter::add('woocommerce_enqueue_styles', function (array $enqueueStyles) { + // Remove the default WooCommerce styles, layout ands the smallscreen optimisation + unset($enqueueStyles['woocommerce-general'], $enqueueStyles['woocommerce-layout'], $enqueueStyles['woocommerce-smallscreen']); + return $enqueueStyles; + }); + } +} diff --git a/app/Themes/Apiary/Providers/ThemeComponentProvider.php b/app/Themes/Apiary/Providers/ThemeComponentProvider.php new file mode 100644 index 00000000000..74dfdca7bfc --- /dev/null +++ b/app/Themes/Apiary/Providers/ThemeComponentProvider.php @@ -0,0 +1,23 @@ +bootBladeComponents(); + } + + protected function bootBladeComponents(): self + { + if (version_compare($this->app->version(), '8.0.0', '>=')) { + Blade::componentNamespace('App\Themes\Apiary\\View\\Components', 'theme'); + } + + return $this; + } +} diff --git a/app/Themes/Apiary/Providers/WordPress/Formatting.php b/app/Themes/Apiary/Providers/WordPress/Formatting.php new file mode 100644 index 00000000000..7ccff1a863f --- /dev/null +++ b/app/Themes/Apiary/Providers/WordPress/Formatting.php @@ -0,0 +1,47 @@ +currentItem = $item; + } + + if (! is_array($item->classes)) { + $item->classes = [$item->classes]; + } + + $hasChildren = in_array('menu-item-has-children', $item->classes); + + if ($hasChildren && $depth === 0) { + $output .= $this->createDropdownParent($item, $args); + } else { + $output .= $this->createMenuItem($item, $depth, $args); + } + } + + /** + * Ends the element output. + */ + public function end_el(&$output, $item, $depth = 0, $args = [], $id = 0): void + { + if (in_array('menu-item-has-children', $item->classes) && $depth === 0) { + $output .= ''; + } + } + + /** + * Starts the list wrapper. + */ + public function start_lvl(&$output, $depth = 0, $args = []): void + { + if ($depth === 0) { + $output .= '
+
'; + } + } + + /** + * Ends the list wrapper. + */ + public function end_lvl(&$output, $depth = 0, $args = []): void + { + if ($depth === 0) { + $parent_item = $this->currentItem; + $output .= '
'; + $output .= sprintf( + ' + %s %s + ', + esc_url($parent_item->url), + __('Access to'), + $parent_item->title + ); + $output .= '
'; + } + } + + /** + * Creates a dropdown parent element. + */ + protected function createDropdownParent($item, $args): string + { + $classes = empty($item->classes) ? [] : (array) $item->classes; + $classes[] = 'relative'; + + return sprintf( + '
+ ', + implode(' ', $classes), + $item->title + ); + } + + /** + * Creates a regular menu item. + */ + protected function createMenuItem($item, $depth, $args): string + { + $classes = empty($item->classes) ? [] : (array) $item->classes; + + // Classes de base pour les liens + if ($depth === 0) { + $classes[] = 'px-4 py-2 mt-2 text-sm font-semibold text-gray-900 bg-transparent rounded-lg md:mt-0 md:ml-4 hover:text-gray-900 focus:text-gray-900 hover:bg-gray-200 focus:bg-gray-200 focus:outline-hidden focus:shadow-outline'; + } else { + $classes[] = 'block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 dark:hover:bg-gray-600 dark:text-gray-200 dark:hover:text-white'; + } + + $atts = [ + 'href' => !empty($item->url) ? $item->url : '#', + 'title' => !empty($item->attr_title) ? $item->attr_title : '', + 'target' => !empty($item->target) ? $item->target : '', + 'rel' => !empty($item->xfn) ? $item->xfn : '', + 'class' => implode(' ', $classes) + ]; + + $attributes = ''; + foreach ($atts as $attr => $value) { + if (!empty($value)) { + $value = ('href' === $attr) ? esc_url($value) : esc_attr($value); + $attributes .= ' ' . $attr . '="' . $value . '"'; + } + } + + return sprintf('%s', $attributes, $item->title); + } +} diff --git a/app/Themes/Apiary/inc/helpers/image-helpers.php b/app/Themes/Apiary/inc/helpers/image-helpers.php new file mode 100644 index 00000000000..a9562b3f7b3 --- /dev/null +++ b/app/Themes/Apiary/inc/helpers/image-helpers.php @@ -0,0 +1,30 @@ + get_the_title($post_id), + 'link' => get_permalink($post_id), + 'excerpt' => get_the_excerpt($post_id), + 'time' => get_post_time('c', true, $post_id), + 'date' => get_the_date('', $post_id), + 'author_name' => $author_name, + 'author_url' => $author_url, + 'img' => [ + 'url' => get_resized_image_url($img_id, $img_size), + 'alt' => get_image_alt($img_id), + ], + ]; +} + +/** + * Get single post data in nice array format. + * + * @param int $post_id + * @return array + */ +function get_single_post_data($post_id = 0) +{ + if (empty($post_id)) { + $post_id = get_the_id(); + } + + // We can get and use all teaser data + $data = get_post_teaser_data($post_id, 'medium'); + + // And add more data + return array_merge($data, [ + 'content' => apply_filters('the_content', get_post_field('post_content', $post_id, 'raw')), + ]); +} diff --git a/app/Themes/Apiary/inc/helpers/wrapper.php b/app/Themes/Apiary/inc/helpers/wrapper.php new file mode 100644 index 00000000000..c67afbb6418 --- /dev/null +++ b/app/Themes/Apiary/inc/helpers/wrapper.php @@ -0,0 +1,22 @@ +"; + if ($echo) { + echo $html; + } else { + return $html; + } +} + +function wrapper_close(string $tag = 'div', bool $echo = true) +{ + $tag = $tag === '' ? 'div' : $tag; + $html = ""; + if ($echo) { + echo $html; + } else { + return $html; + } +} diff --git a/app/Themes/Apiary/inc/image-customizations.php b/app/Themes/Apiary/inc/image-customizations.php new file mode 100644 index 00000000000..9b78a5328a7 --- /dev/null +++ b/app/Themes/Apiary/inc/image-customizations.php @@ -0,0 +1,71 @@ +\\s*?(<\\/a>|)?\\s*<\\/p>/s', '
$1
', $pee); +}, 50); + +/** + * Customized the output of caption, you can remove the filter to restore back to the WP default output. + * Courtesy of DevPress. http://devpress.com/blog/captions-in-wordpress/ + */ +add_filter('img_caption_shortcode', function ($output, $attr, $content) { + /* We're not worried abut captions in feeds, so just return the output here. */ + if (is_feed()) { + return $output; + } + + /* Set up the default arguments. */ + $defaults = [ + 'id' => '', + 'align' => 'alignnone', + 'width' => '', + 'caption' => '', + ]; + /* Merge the defaults with user input. */ + $attr = shortcode_atts($defaults, $attr); + + /* If the width is less than 1 or there is no caption, return the content wrapped between the [caption]< tags. */ + if (1 > $attr['width'] || empty($attr['caption'])) { + return $content; + } + + /* Set up the attributes for the caption
. */ + $attributes = ' class="media '.esc_attr($attr['align']).'"'; + /* Open the caption
. */ + $output = ''; + /* Allow shortcodes for the content the caption was created for. */ + $output .= do_shortcode($content); + /* Append the caption text. */ + $output .= '
'.$attr['caption'].'
'; + /* Close the caption
. */ + $output .= ''; + + /* Return the formatted, clean caption. */ + return $output; +}, 10, 3); + +/** + * @param string $img_classes Image classes (default : w-8 h-8) + * @param string $link_classes Link image (default: custom-logo-link) + * @param bool $enableLink Enable or disable the logo link (default : true) + * @return string Logo HTML code + */ +function custom_logo($img_classes = 'w-8 h-8', $link_classes = 'custom-logo-link', $enableLink = true) +{ + $custom_logo_id = get_theme_mod('custom_logo'); + + return $enableLink ? sprintf('
', + home_url(), + wp_get_attachment_image($custom_logo_id, 'full', false, [ + 'class' => $img_classes, + ]) + ) : wp_get_attachment_image($custom_logo_id, 'full', false, [ + 'class' => $img_classes, + ]); +} diff --git a/app/Themes/Apiary/inc/template-functions.php b/app/Themes/Apiary/inc/template-functions.php new file mode 100644 index 00000000000..80392d31e03 --- /dev/null +++ b/app/Themes/Apiary/inc/template-functions.php @@ -0,0 +1,72 @@ +base !== 'nav-menus') { + return $match; + } + + if ($rule['operator'] == '==') { + $match = isset($options['nav_menu_item_depth']) && (string) $options['nav_menu_item_depth'] === $rule['value']; + } + + return $match; +}, 10, 3); + +/** + * Add a pingback url auto-discovery header for single posts, pages, or attachments. + */ +Action::add('wp_head', function () { + if (is_singular() && pings_open()) { + echo ''; + } +}); + +/** + * Set the content width in pixels, based on the theme's design and stylesheet. + */ +Action::add('after_setup_theme', function () { + $GLOBALS['content_width'] = 640; +}, 0); diff --git a/app/Themes/Apiary/inc/template-tags.php b/app/Themes/Apiary/inc/template-tags.php new file mode 100644 index 00000000000..33e668c823c --- /dev/null +++ b/app/Themes/Apiary/inc/template-tags.php @@ -0,0 +1,185 @@ +%2$s'; + + if (get_the_time('U') !== get_the_modified_time('U')) { + $time_string = ''; + } + + $time_string = sprintf( + $time_string, + esc_attr(get_the_date(DATE_W3C)), + esc_html(get_the_date()), + esc_attr(get_the_modified_date(DATE_W3C)), + esc_html(get_the_modified_date()) + ); + + /* translators: %s: post date. */ + $posted_on = sprintf( + esc_html_x('Posted on %s', 'post date', 'apiary'), + ''.$time_string.'' + ); + + return ''.$posted_on.''; + } +} + +if (! function_exists('posted_by')) { + /** + * Prints HTML with meta information for the current author. + */ + function posted_by() + { + /* translators: %s: post author. */ + $byline = sprintf( + esc_html_x('by %s', 'post author', 'apiary'), + ''.esc_html(get_the_author()).'' + ); + + return ''; + } +} + +if (! function_exists('post_thumbnail')) { + /** + * Displays an optional post thumbnail. + * + * Wraps the post thumbnail in an anchor element on index views, or a div + * element when on single views. + */ + function post_thumbnail() + { + if (post_password_required() || is_attachment() || ! has_post_thumbnail()) { + return; + } + + if (is_singular()) { + return sprintf( + '
%s
', + get_the_post_thumbnail() + ); + } else { + return sprintf( + '', + get_permalink(), + get_the_post_thumbnail(null, 'post-thumbnail', [ + 'alt' => the_title_attribute(['echo' => false]), + ]) + ); + } + } +} + +if (! function_exists('entry_footer')) { + /** + * Prints HTML with meta information for the categories, tags and comments. + */ + function entry_footer() + { + // Hide category and tag text for pages. + if ('post' === get_post_type()) { + /* translators: used between list items, there is a space after the comma */ + $categories_list = get_the_category_list(esc_html__(', ', 'apiary')); + + if ($categories_list) { + /* translators: 1: list of categories. */ + printf( + ''.esc_html__('Posted in %1$s', 'apiary').'', + $categories_list + ); + } + + /* translators: used between list items, there is a space after the comma */ + $tags_list = get_the_tag_list('', esc_html_x(', ', 'list item separator', 'apiary')); + + if ($tags_list) { + /* translators: 1: list of tags. */ + printf( + ''.esc_html__('Tagged %1$s', 'apiary').'', + $tags_list + ); + } + } + + if (! is_single() && ! post_password_required() && (comments_open() || get_comments_number())) { + echo ''; + comments_popup_link( + sprintf( + wp_kses( + /* translators: %s: post title */ + __('Leave a Comment on %s', 'apiary'), + [ + 'span' => [ + 'class' => [], + ], + ] + ), + get_the_title() + ) + ); + echo ''; + } + + edit_post_link( + sprintf( + wp_kses( + /* translators: %s: Name of current post. Only visible to screen readers */ + __('Edit %s', 'apiary'), + [ + 'span' => [ + 'class' => [], + ], + ] + ), + get_the_title() + ), + '', + '' + ); + } +} + +if (! function_exists('comments_title')) { + /** + * Return the comments title. + * + * @param int $count The number of comments. + * @return string + */ + function comments_title($count) + { + if (1 === $count) { + return sprintf( + esc_html__('One thought on “%1$s”', 'apiary'), + ''.get_the_title().'' + ); + } + + return sprintf( + esc_html(_nx('%1$s thought on “%2$s”', '%1$s thoughts on “%2$s”', $count, 'comments title', 'apiary')), + number_format_i18n($count), + ''.get_the_title().'' + ); + } +} + +if (! function_exists('archive_content_message')) { + /** + * Return an archive content message. + * + * @return string + */ + function archive_content_message() + { + return sprintf( + '

'.esc_html__('Try looking in the monthly archives. %1$s', 'apiary').'

', + convert_smilies(':)') + ); + } +} diff --git a/app/Themes/Apiary/inc/woocommerce/archive-product.php b/app/Themes/Apiary/inc/woocommerce/archive-product.php new file mode 100644 index 00000000000..f8cf1c97a65 --- /dev/null +++ b/app/Themes/Apiary/inc/woocommerce/archive-product.php @@ -0,0 +1,103 @@ +description)) { + $description = wc_format_content(wp_kses_post($term->description)); + echo view('woocommerce.archive.archive-description', ['description' => $description]); + } + } +} + +/** + * Override the woocommerce_product_archive_description function + */ +function woocommerce_product_archive_description() +{ + // Don't display the description on search results page. + if (is_search()) { + return; + } + + if (is_post_type_archive('product') && in_array(absint(get_query_var('paged')), [0, 1], true)) { + $shop_page = get_post(wc_get_page_id('shop')); + if ($shop_page) { + + $allowed_html = wp_kses_allowed_html('post'); + + // This is needed for the search product block to work. + $allowed_html = array_merge( + $allowed_html, + [ + 'form' => [ + 'action' => true, + 'accept' => true, + 'accept-charset' => true, + 'enctype' => true, + 'method' => true, + 'name' => true, + 'target' => true, + ], + + 'input' => [ + 'type' => true, + 'id' => true, + 'class' => true, + 'placeholder' => true, + 'name' => true, + 'value' => true, + ], + + 'button' => [ + 'type' => true, + 'class' => true, + 'label' => true, + ], + + 'svg' => [ + 'hidden' => true, + 'role' => true, + 'focusable' => true, + 'xmlns' => true, + 'width' => true, + 'height' => true, + 'viewbox' => true, + ], + 'path' => [ + 'd' => true, + ], + ] + ); + + $description = wc_format_content(wp_kses($shop_page->post_content, $allowed_html)); + if ($description) { + echo view('woocommerce.archive.archive-description', ['description' => $description]); + } + } + } +} + +// Product title classes +Filter::add('woocommerce_product_loop_title_classes', function (string $classes) { + $product_title_class = config('woocommerce.archive-product.product_title.class', 'text-sm font-medium text-gray-900'); + + return $product_title_class.' '.$classes; +}, 90); diff --git a/app/Themes/Apiary/inc/woocommerce/cart.php b/app/Themes/Apiary/inc/woocommerce/cart.php new file mode 100644 index 00000000000..41e8021271b --- /dev/null +++ b/app/Themes/Apiary/inc/woocommerce/cart.php @@ -0,0 +1,62 @@ +cart->cart_contents_count; + } +} + +// Cart count ajax fragment +Filter::add('woocommerce_add_to_cart_fragments', function ($fragments) { + $fragments['.cart-count'] = ''.wc_get_cart_item_count().''; + + return $fragments; +}); + +// Echo a hidden field with csrf token as value +Action::add('woocommerce_before_add_to_cart_button', function (): void { + echo ''; +}); + +// Enquey the cart variation script +Action::add('wp_enqueue_scripts', function (): void { + wp_enqueue_script('wc-add-to-cart-variation'); +}); + +// Remove attribute from the variation title +Filter::add('woocommerce_product_variation_title_include_attributes', '__return_false'); + +if (! function_exists('wc_cart_item_data_format')) { + /** + * Cart item data formatting + */ + function wc_cart_item_data_format(array $item_data): string + { + $formatted_metas = []; + foreach ($item_data as $key => $meta) { + $output = sprintf('%s: %s', $meta['key'], $meta['display']); + + $formatted_metas[] = $output; + } + + return implode(', ', $formatted_metas); + } +} + +/** + * Override the woocommerce_widget_shopping_cart_subtotal function + */ +function woocommerce_widget_shopping_cart_subtotal(): string +{ + return ''; +} + +Filter::add('wc_add_to_cart_message_html', function ($message) { + return str_replace('button wc-forward wp-element-button', 'button wc-forward inline-block w-auto bg-indigo-600 border border-transparent rounded-md py-3 px-8 text-base font-medium text-white hover:bg-indigo-700 focus:outline-hidden focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 mr-4', $message); +}); diff --git a/app/Themes/Apiary/inc/woocommerce/checkout.php b/app/Themes/Apiary/inc/woocommerce/checkout.php new file mode 100644 index 00000000000..9aa0cf1530d --- /dev/null +++ b/app/Themes/Apiary/inc/woocommerce/checkout.php @@ -0,0 +1,78 @@ +query->get_current_endpoint() === ''; + } +} + +if (! function_exists('wc_get_shipping_method_index')) { + /** + * Get the shipping method index + */ + function wc_get_shipping_method_index(string $chosen_method, array $available_methods): int + { + return (int) array_search($chosen_method, array_keys($available_methods)); + } +} + +// Create shippping methods specific ajax fragment +Filter::add('woocommerce_update_order_review_fragments', function ($fragments) { + // Get checkout payment fragment. + ob_start(); + ?> +
+ cart->needs_shipping() && WC()->cart->show_shipping()) { ?> + + + + +
+ 696, + 'height' => '', + 'crop' => 0, + ]; +}); + +/** + * Override the woocommerce_get_product_thumbnail function + * + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\Factory|\Illuminate\View\View|string|\Pollora\Core\Application + */ +function woocommerce_get_product_thumbnail(string $size = 'woocommerce_thumbnail', int $deprecated1 = 0, int $deprecated2 = 0) +{ + global $product; + + $image_size = apply_filters('single_product_archive_thumbnail_size', $size); + $args['class'] = config('woocommerce.archive-product.product_thumbnail.class', 'w-full h-full object-center object-cover sm:w-full sm:h-full'); + $thumbnail = $product->get_image($image_size, $args); + + return $product ? view('woocommerce.archive.product-image', ['thumbnail' => $thumbnail]) : ''; +} + +// Product thumbnail sizing +Filter::add('woocommerce_checkout_item_thumbnail', fn ($html) => str_replace('attachment-woocommerce_thumbnail', 'attachment-woocommerce_thumbnail '.config('woocommerce.checkout.product_thumbnail.class', 'w-20 h-20 rounded-md object-center object-cover'), $html)); +Filter::add('woocommerce_thank_you_item_thumbnail', fn ($html) => str_replace('attachment-woocommerce_thumbnail', 'attachment-woocommerce_thumbnail '.config('woocommerce.thank_you.product_thumbnail.class', 'flex-none w-20 h-20 object-center object-cover bg-gray-100 rounded-lg sm:w-40 sm:h-40'), $html)); +Filter::add('woocommerce_cart_item_thumbnail', fn ($html) => str_replace('attachment-woocommerce_thumbnail', 'attachment-woocommerce_thumbnail'.config('woocommerce.cart.product_thumbnail.class', 'w-24 h-24 rounded-md object-center object-cover sm:w-48 sm:h-48'), $html)); diff --git a/app/Themes/Apiary/inc/woocommerce/order.php b/app/Themes/Apiary/inc/woocommerce/order.php new file mode 100644 index 00000000000..0e582cc3ffb --- /dev/null +++ b/app/Themes/Apiary/inc/woocommerce/order.php @@ -0,0 +1,44 @@ +get_status(); + $final_statuses = ['completed', 'cancelled', 'refunded', 'failed']; + + if ($current_status !== 'on-hold') { + unset($order_statuses['wc-on-hold']); + } + + foreach ($final_statuses as $final_status) { + if ($current_status !== $final_status) { + if (! in_array($current_status, $final_statuses) && $final_status === 'completed') { + continue; + } + unset($order_statuses["wc-{$final_status}"]); + } + } + + return $order_statuses; + } +} + +if (! function_exists('wc_get_order_status_progress')) { + /** + * Get the percentage of the status progress of a WooCommerce Order + */ + function wc_get_order_status_progress(WC_Order $order): int + { + $statuses = wc_get_current_order_statuses($order); + $current_status = $order->get_status(); + $status_index = array_search("wc-{$current_status}", array_keys($statuses)); + $status_count = count($statuses); + $status_quotient = $status_index / ($status_count - 1); + + return min(100, ($status_quotient * 100 + 5)); + } +} diff --git a/app/Themes/Apiary/inc/woocommerce/related.php b/app/Themes/Apiary/inc/woocommerce/related.php new file mode 100644 index 00000000000..956243321f8 --- /dev/null +++ b/app/Themes/Apiary/inc/woocommerce/related.php @@ -0,0 +1,25 @@ + Filter::add('woocommerce_product_loop_start', 'th_inject_related_product_classes')); +Action::add('woocommerce_after_related_products', fn () => Filter::remove('woocommerce_product_loop_start', 'th_inject_related_product_classes')); diff --git a/app/Themes/Apiary/inc/woocommerce/review.php b/app/Themes/Apiary/inc/woocommerce/review.php new file mode 100644 index 00000000000..dbc7e2844db --- /dev/null +++ b/app/Themes/Apiary/inc/woocommerce/review.php @@ -0,0 +1,105 @@ +
'; + + return $args; + } +} + +Filter::add('woocommerce_product_review_comment_form_args', 'wc_review_comment_form_args', 50); + +Filter::add('comment_form_field_cookies', function ($output) { + $label_class = config('woocommerce.review.form.cookie.label.class', 'text-sm font-medium text-gray-700 mb-1 ml-3'); + $wrapper_class = config('woocommerce.review.form.cookie.wrapper.class', 'py-3 flex items-start'); + $checkbox_class = config('woocommerce.review.form.cookie.checkbox.class', 'focus:ring-indigo-500 h-4 w-4 text-indigo-600 border-gray-300 rounded-sm'); + + $search = [ + '