[\w-]+)/restore',
200 | [
201 | 'args' => [
202 | 'key' => [
203 | 'description' => __( 'The cart item key is what identifies the item in the cart.', 'headless-cms' ),
204 | 'type' => 'string',
205 | 'validate_callback' => [ $this, 'is_valid_removed_cart_item' ],
206 | ],
207 | ],
208 |
209 | [
210 | 'methods' => WP_REST_Server::EDITABLE,
211 | 'callback' => [ $this, 'restore_item' ],
212 | 'permission_callback' => '__return_true',
213 | ],
214 | ]
215 | );
216 | }
217 |
218 | /**
219 | * Get the Cart schema, conforming to JSON Schema.
220 | *
221 | * @return array
222 | * @since 1.0.0
223 | *
224 | */
225 | public function get_default_item_schema() {
226 | // @todo: Add more properties matches the /cart GET endpoint
227 | $schema = [
228 | '$schema' => 'http://json-schema.org/draft-04/schema#',
229 | 'title' => 'cart',
230 | 'type' => 'object',
231 | 'properties' => [
232 | 'key' => [
233 | 'description' => __( 'Cart item key.', 'headless-cms' ),
234 | 'type' => 'string',
235 | 'context' => [ 'view', 'edit' ],
236 | 'readonly' => true,
237 | ],
238 | 'product_id' => [
239 | 'description' => __( 'ID of the product to add to the cart.', 'headless-cms' ),
240 | 'type' => 'integer',
241 | 'context' => [ 'view', 'edit' ],
242 | 'default' => 0,
243 | 'arg_options' => [
244 | 'validate_callback' => [ $this, 'is_valid_product' ],
245 | ],
246 | ],
247 | 'quantity' => [
248 | 'description' => __( 'Quantity of the item to add.', 'headless-cms' ),
249 | 'type' => 'integer',
250 | 'context' => [ 'view', 'edit' ],
251 | 'default' => 1,
252 | 'arg_options' => [
253 | 'validate_callback' => [ $this, 'is_valid_quantity' ],
254 | ],
255 | ],
256 | 'variation_id' => [
257 | 'description' => __( 'ID of the variation being added to the cart.', 'headless-cms' ),
258 | 'type' => 'integer',
259 | 'context' => [ 'view', 'edit' ],
260 | 'default' => 0,
261 | 'arg_options' => [
262 | 'validate_callback' => [ $this, 'is_valid_product' ],
263 | ],
264 | ],
265 | 'variation' => [
266 | 'description' => __( 'Variation attribute values.', 'headless-cms' ),
267 | 'type' => 'object',
268 | 'context' => [ 'view', 'edit' ],
269 | 'default' => [],
270 | ],
271 | 'cart_item_data' => [
272 | 'description' => __( 'Extra cart item data we want to pass into the item.', 'headless-cms' ),
273 | 'type' => 'object',
274 | 'context' => [ 'view', 'edit' ],
275 | 'default' => [],
276 | ],
277 | ],
278 | ];
279 |
280 | return $this->add_additional_fields_schema( $schema );
281 | }
282 |
283 | /**
284 | * Retrieves the item's schema for display / public consumption purposes.
285 | *
286 | * @return array Public item schema data.
287 | * @since 4.7.0
288 | *
289 | */
290 | public function get_public_item_schema() {
291 |
292 | $schema = $this->get_item_schema();
293 |
294 | if ( ! empty( $schema['properties'] ) ) {
295 | foreach ( $schema['properties'] as &$property ) {
296 | unset( $property['arg_options'] );
297 | }
298 | }
299 |
300 | return $schema;
301 | }
302 |
303 | /**
304 | * Get cart items
305 | *
306 | * @param \WP_REST_Request $request
307 | *
308 | * @return \WP_REST_Response
309 | * @since 1.0.0
310 | *
311 | */
312 | public function get_items( $request ) {
313 | $data = [];
314 | include_once WC_ABSPATH . 'includes/wc-cart-functions.php';
315 | include_once WC_ABSPATH . 'includes/class-wc-cart.php';
316 |
317 | if ( is_null( WC()->cart ) ) {
318 | wc_load_cart();
319 | }
320 | $cart = WC()->cart->get_cart();
321 |
322 | foreach ( $cart as $item_key => &$cart_item ) {
323 | $data[] = $this->prepare_cart_item_for_response( $cart_item, $request );
324 | }
325 |
326 | $response = rest_ensure_response( $data );
327 | $response = $this->add_headers( $response );
328 |
329 | return $response;
330 | }
331 |
332 | /**
333 | * Retrieves an array of endpoint arguments from the item schema for the controller.
334 | *
335 | * @param string $method Optional. HTTP method of the request. The arguments for `CREATABLE` requests are
336 | * checked for required values and may fall-back to a given default, this is not done
337 | * on `EDITABLE` requests. Default WP_REST_Server::CREATABLE.
338 | *
339 | * @return array Endpoint arguments.
340 | * @since 4.7.0
341 | *
342 | */
343 | public function get_endpoint_args_for_item_schema( $method = WP_REST_Server::CREATABLE ) {
344 | return rest_get_endpoint_args_for_schema( $this->get_item_schema(), $method );
345 | }
346 |
347 | /**
348 | * Retrieves the item's schema, conforming to JSON Schema.
349 | *
350 | * @return array Item schema data.
351 | * @since 4.7.0
352 | *
353 | */
354 | public function get_item_schema() {
355 | return $this->add_additional_fields_schema( [] );
356 | }
357 |
358 | /**
359 | * Adds the schema from additional fields to a schema array.
360 | *
361 | * The type of object is inferred from the passed schema.
362 | *
363 | * @param array $schema Schema array.
364 | *
365 | * @return array Modified Schema array.
366 | * @since 4.7.0
367 | *
368 | */
369 | protected function add_additional_fields_schema( $schema ) {
370 | if ( empty( $schema['title'] ) ) {
371 | return $schema;
372 | }
373 |
374 | // Can't use $this->get_object_type otherwise we cause an inf loop.
375 | $object_type = $schema['title'];
376 |
377 | $additional_fields = $this->get_additional_fields( $object_type );
378 |
379 | foreach ( $additional_fields as $field_name => $field_options ) {
380 | if ( ! $field_options['schema'] ) {
381 | continue;
382 | }
383 |
384 | $schema['properties'][ $field_name ] = $field_options['schema'];
385 | }
386 |
387 | return $schema;
388 | }
389 |
390 | /**
391 | * Retrieves all of the registered additional fields for a given object-type.
392 | *
393 | * @param string $object_type Optional. The object type.
394 | *
395 | * @return array Registered additional fields (if any), empty array if none or if the object type could
396 | * not be inferred.
397 | * @since 4.7.0
398 | *
399 | */
400 | protected function get_additional_fields( $object_type = null ) {
401 |
402 | if ( ! $object_type ) {
403 | $object_type = $this->get_object_type();
404 | }
405 |
406 | if ( ! $object_type ) {
407 | return [];
408 | }
409 |
410 | global $wp_rest_additional_fields;
411 |
412 | if ( ! $wp_rest_additional_fields || ! isset( $wp_rest_additional_fields[ $object_type ] ) ) {
413 | return [];
414 | }
415 |
416 | return $wp_rest_additional_fields[ $object_type ];
417 | }
418 |
419 | /**
420 | * Load WC Cart functionalities in REST environment
421 | *
422 | * @return void
423 | * @since 1.0.0
424 | *
425 | */
426 | public function wc_load_cart() {
427 | include_once WC_ABSPATH . 'includes/wc-notice-functions.php';
428 |
429 | if ( ! wc()->cart ) {
430 | include_once WC_ABSPATH . 'includes/wc-cart-functions.php';
431 | wc_load_cart();
432 | wc()->cart->get_cart();
433 | }
434 | }
435 |
436 | /**
437 | * Add item to cart
438 | *
439 | * @param \WP_REST_Request $request
440 | *
441 | * @return \WP_REST_Response
442 | * @since 1.0.0
443 | *
444 | */
445 | public function create_item( $request ) {
446 | $product_id = $request['product_id'];
447 | $quantity = ! empty( $request['quantity'] ) ? $request['quantity'] : 1;
448 | $variation_id = ! empty( $request['variation_id'] ) ? $request['variation_id'] : 0;
449 |
450 | $product = wc_get_product( $variation_id ? $variation_id : $product_id );
451 |
452 | if ( empty( $product ) ) {
453 | return new WP_Error( 'rest_invalid_product', __( 'Product not exists', 'headless-cms' ) );
454 | }
455 |
456 | if ( $product instanceof WC_Product_Variation ) {
457 | $product_id = $product->get_parent_id();
458 | $variation = $product->get_variation_attributes();
459 | }
460 |
461 | // Force quantity to 1 if sold individually and check for existing item in cart.
462 | if ( $product->is_sold_individually() ) {
463 | $quantity = 1;
464 |
465 | $cart_contents = wc()->cart->cart_contents;
466 |
467 | $found_in_cart = apply_filters( 'woocommerce_add_to_cart_sold_individually_found_in_cart', $cart_item_key && $cart_contents[ $cart_item_key ]['quantity'] > 0, $product_id, $variation_id, $cart_item_data, $cart_id );
468 |
469 | if ( $found_in_cart ) {
470 | /* translators: %s: product name */
471 | return new WP_Error( 'wc_next_rest_product_sold_individually', sprintf( __( 'You cannot add another "%s" to your cart.', 'headless-cms' ), $product->get_name() ), [ 'status' => 500 ] );
472 | }
473 | }
474 |
475 | // Product is purchasable check.
476 | if ( ! $product->is_purchasable() ) {
477 | return new WP_Error( 'wc_next_rest_cannot_be_purchased', __( 'Sorry, this product cannot be purchased.', 'headless-cms' ), [ 'status' => 500 ] );
478 | }
479 |
480 | // Stock check - only check if we're managing stock and backorders are not allowed.
481 | if ( ! $product->is_in_stock() ) {
482 | return new WP_Error( 'wc_next_rest_product_out_of_stock', sprintf( __( 'You cannot add "%s" to the cart because the product is out of stock.', 'headless-cms' ), $product->get_name() ), [ 'status' => 500 ] );
483 | }
484 | if ( ! $product->has_enough_stock( $quantity ) ) {
485 | return new WP_Error( 'wc_next_rest_not_enough_in_stock', sprintf( __( 'You cannot add that amount of "%1$s" to the cart because there is not enough stock (%2$s remaining).', 'headless-cms' ), $product->get_name(), wc_format_stock_quantity_for_display( $product->get_stock_quantity(), $product ) ), [ 'status' => 500 ] );
486 | }
487 |
488 | // Stock check - this time accounting for whats already in-cart.
489 | if ( $product->managing_stock() ) {
490 | $products_qty_in_cart = wc()->cart->get_cart_item_quantities();
491 |
492 | if ( isset( $products_qty_in_cart[ $product->get_stock_managed_by_id() ] ) && ! $product->has_enough_stock( $products_qty_in_cart[ $product->get_stock_managed_by_id() ] + $quantity ) ) {
493 | return new WP_Error(
494 | 'wc_next_rest_not_enough_stock_remaining',
495 | sprintf(
496 | __( 'You cannot add that amount to the cart — we have %1$s in stock and you already have %2$s in your cart.', 'headless-cms' ),
497 | wc_format_stock_quantity_for_display( $product->get_stock_quantity(), $product ),
498 | wc_format_stock_quantity_for_display( $products_qty_in_cart[ $product->get_stock_managed_by_id() ], $product )
499 | ),
500 | [ 'status' => 500 ]
501 | );
502 | }
503 | }
504 |
505 | // Add item to cart.
506 | $item_key = wc()->cart->add_to_cart( $product_id, $quantity, $variation_id );
507 |
508 | // Return response to added item to cart or return error.
509 | if ( $item_key ) {
510 | $cart_item = wc()->cart->get_cart_item( $item_key );
511 |
512 | do_action( 'wc_next_rest_add_to_cart', $item_key, $cart_item );
513 |
514 | if ( is_array( $cart_item ) ) {
515 | $data = $this->prepare_cart_item_for_response( $cart_item, $request );
516 | $response = rest_ensure_response( $data );
517 | $response = $this->add_headers( $response );
518 |
519 | return $response;
520 | }
521 | }
522 |
523 | return new WP_Error( 'wc_next_rest_cannot_add_to_cart', sprintf( __( 'You cannot add "%s" to your cart.', 'headless-cms' ), $product->get_name() ), [ 'status' => 500 ] );
524 | }
525 |
526 | /**
527 | * Prepare cart item
528 | *
529 | * @param array $cart_item
530 | * @param \WP_REST_Request $request
531 | *
532 | * @return array
533 | * @since 1.0.0
534 | *
535 | */
536 | protected function prepare_cart_item_for_response( $cart_item, $request ) {
537 | $product = $cart_item['data'];
538 |
539 | $product_id = $product->get_id();
540 |
541 | if ( $product instanceof WC_Product_Variation ) {
542 | $product_id = $product->get_parent_id();
543 | }
544 |
545 | $cart_item['data'] = $product->get_data();
546 | $cart_item['data']['images'] = $this->get_images( $product );
547 | $cart_item['currency'] = html_entity_decode( get_woocommerce_currency_symbol() );
548 |
549 | return $cart_item;
550 | }
551 |
552 | /**
553 | * Get the images for a product or product variation.
554 | *
555 | * @param WC_Product|WC_Product_Variation $product Product instance.
556 | *
557 | * @return array
558 | * @since 1.0.0
559 | *
560 | */
561 | protected function get_images( $product ) {
562 | $images = [];
563 | $attachment_ids = [];
564 |
565 | // Add featured image.
566 | if ( $product->get_image_id() ) {
567 | $attachment_ids[] = $product->get_image_id();
568 | }
569 |
570 | // Add gallery images.
571 | $attachment_ids = array_merge( $attachment_ids, $product->get_gallery_image_ids() );
572 |
573 | // Build image data.
574 | foreach ( $attachment_ids as $position => $attachment_id ) {
575 | $attachment_post = get_post( $attachment_id );
576 | if ( is_null( $attachment_post ) ) {
577 | continue;
578 | }
579 |
580 | $attachment = wp_get_attachment_image_src( $attachment_id, 'full' );
581 | if ( ! is_array( $attachment ) ) {
582 | continue;
583 | }
584 |
585 | $images[] = [
586 | 'id' => (int) $attachment_id,
587 | 'src' => current( $attachment ),
588 | 'name' => get_the_title( $attachment_id ),
589 | 'alt' => get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ),
590 | ];
591 | }
592 |
593 | // Set a placeholder image if the product has no images set.
594 | if ( empty( $images ) ) {
595 | $images[] = [
596 | 'id' => 0,
597 | 'src' => wc_placeholder_img_src(),
598 | 'name' => __( 'Placeholder', 'headless-cms' ),
599 | 'alt' => __( 'Placeholder', 'headless-cms' ),
600 | ];
601 | }
602 |
603 | return $images;
604 | }
605 |
606 | /**
607 | * Add response header
608 | *
609 | * @param \WP_REST_Response $response
610 | *
611 | * @since 1.0.0
612 | *
613 | */
614 | protected function add_headers( $response ) {
615 | wc()->cart->calculate_totals();
616 |
617 | $response->header( 'X-WC-Cart-TotalItems', wc()->cart->get_cart_contents_count() );
618 | $response->header( 'X-WC-Cart-Totals', json_encode( wc()->cart->get_totals() ) );
619 |
620 | $wc_session_id = null;
621 | $headers = headers_list();
622 |
623 | foreach ( $headers as $header ) {
624 | if ( 0 === strpos( $header, 'Set-Cookie: wp_woocommerce_session_' ) ) {
625 | preg_match_all( '/Set-Cookie: wp_woocommerce_session_(.*?)=(.*?);/', $header, $matches );
626 |
627 | if ( ! empty( $matches[2][0] ) ) {
628 | $wc_session_id = $matches[2][0];
629 | }
630 | }
631 | }
632 |
633 | $response->header( 'X-WC-Session', $wc_session_id );
634 |
635 | return $response;
636 | }
637 |
638 | /**
639 | * Validate product
640 | *
641 | * @param mixed $value
642 | * @param \WP_REST_Request $request
643 | * @param string $param
644 | *
645 | * @return bool
646 | * @since 1.0.0
647 | *
648 | */
649 | public function is_valid_product( $value, $request, $param ) {
650 | if ( ! is_integer( $value ) ) {
651 | return new WP_Error( 'rest_invalid_product_id', sprintf( __( 'Invalid %s.', 'headless-cms' ), $param ) );
652 | }
653 |
654 | if ( empty( absint( $request['product_id'] ) ) && empty( absint( $request['variation_id'] ) ) ) {
655 | return new WP_Error( 'rest_invalid_data', __( 'product_id or variation_id is required.', 'headless-cms' ) );
656 | }
657 |
658 | $product_id = absint( $value );
659 |
660 | if ( $product_id <= 0 ) {
661 | return true;
662 | }
663 |
664 | $product = wc_get_product( $product_id );
665 |
666 | if ( $product instanceof WC_Product && 'trash' !== $product->get_status() ) {
667 | return true;
668 | }
669 |
670 | return new WP_Error( 'rest_invalid_product', sprintf( __( '%s does not exist.', 'headless-cms' ), $param ) );
671 | }
672 |
673 | /**
674 | * Validate product quantity
675 | *
676 | * @param mixed $value
677 | * @param \WP_REST_Request $request
678 | * @param string $param
679 | *
680 | * @return bool|\WP_Error
681 | * @since 1.0.0
682 | *
683 | */
684 | public function is_valid_quantity( $value, $request, $param ) {
685 | if ( ! is_integer( $value ) ) {
686 | return new WP_Error( 'rest_invalid_quantity', __( 'quantity is not numeric.', 'headless-cms' ) );
687 | }
688 |
689 | $value = absint( $value );
690 |
691 | if ( $value < 0 ) {
692 | return new WP_Error( 'rest_invalid_quantity', __( 'quntity must be equal or greater than 0.', 'headless-cms' ) );
693 | }
694 |
695 | return true;
696 | }
697 |
698 | /**
699 | * Validate a cart item
700 | *
701 | * @param string $key
702 | * @param \WP_REST_Request $request
703 | * @param string $param
704 | *
705 | * @return bool
706 | * @since 1.0.0
707 | *
708 | */
709 | public function is_valid_cart_item( $key, $request, $param ) {
710 | if ( wc()->cart->is_empty() ) {
711 | return new WP_Error( 'wc_next_rest_empty_cart', __( "You don't have any item in your cart.", 'headless-cms' ) );
712 | }
713 |
714 | if ( wc()->cart->get_cart_item( $key ) ) {
715 | return true;
716 | }
717 |
718 | return new WP_Error( 'wc_next_rest_invalid_cart_item_key', __( 'Invalid cart item key.', 'headless-cms' ) );
719 | }
720 |
721 | /**
722 | * Validate a trashed cart item
723 | *
724 | * @param string $key
725 | * @param \WP_REST_Request $request
726 | * @param string $param
727 | *
728 | * @return bool
729 | * @since 1.0.0
730 | *
731 | */
732 | public function is_valid_removed_cart_item( $key, $request, $param ) {
733 | $removed_items = wc()->cart->get_removed_cart_contents();
734 |
735 | if ( isset( $removed_items[ $key ] ) ) {
736 | return true;
737 | }
738 |
739 | return new WP_Error( 'wc_next_rest_invalid_trashed_item_key', __( 'Cart item not found in removed items.', 'headless-cms' ) );
740 | }
741 |
742 | /**
743 | * Clear cart
744 | *
745 | * @return \WP_REST_Response|\WP_Error
746 | * @since 1.0.0
747 | *
748 | */
749 | public function clear_cart() {
750 | wc()->cart->empty_cart();
751 | wc()->session->set( 'cart', [] );
752 |
753 | if ( ! wc()->cart->is_empty() ) {
754 | return new WP_Error( 'wc_next_rest_clear_cart_failed', __( 'Clearing the cart failed!', 'headless-cms' ), [ 'status' => 500 ] );
755 | } else {
756 | $response = rest_ensure_response( [] );
757 | $response = $this->add_headers( $response );
758 |
759 | return $response;
760 | }
761 | }
762 |
763 | /**
764 | * Update a cart item
765 | *
766 | * @param \WP_REST_Request $request
767 | *
768 | * @return \WP_REST_Response|\WP_Error
769 | * @since 1.0.0
770 | *
771 | */
772 | public function update_item( $request ) {
773 | $cart_item_key = $request['key'];
774 | $quantity = $request['quantity'];
775 |
776 | $current_data = wc()->cart->get_cart_item( $cart_item_key );
777 |
778 | // Checks if the item has enough stock before updating
779 | $has_enough_stock = $this->has_enough_stock( $current_data, $quantity );
780 |
781 | if ( is_wp_error( $has_enough_stock ) ) {
782 | return $has_enough_stock;
783 | }
784 |
785 | if ( wc()->cart->set_quantity( $cart_item_key, $quantity ) ) {
786 | $new_data = wc()->cart->get_cart_item( $cart_item_key );
787 |
788 | $product_id = ! isset( $new_data['product_id'] ) ? 0 : absint( $new_data['product_id'] );
789 | $variation_id = ! isset( $new_data['variation_id'] ) ? 0 : absint( $new_data['variation_id'] );
790 |
791 | $product_data = wc_get_product( $variation_id ? $variation_id : $product_id );
792 |
793 | if ( isset( $new_data['quantity'] ) && $quantity != $new_data['quantity'] ) {
794 | do_action( 'wc_next_rest_item_quantity_changed', $cart_item_key, $new_data );
795 | }
796 |
797 | $data = ! empty( $new_data ) ? $this->prepare_cart_item_for_response( $new_data, $request ) : [];
798 | $response = rest_ensure_response( $data );
799 | $response = $this->add_headers( $response );
800 |
801 | // Return response based on product quantity increment.
802 | if ( $quantity > $current_data['quantity'] ) {
803 | $status = 'increased';
804 | } elseif ( $quantity < $current_data['quantity'] ) {
805 | $status = 'decreased';
806 | } else {
807 | $status = 'unchanged';
808 | }
809 |
810 | $quantity_status = json_encode( [
811 | 'status' => $status,
812 | 'previous_quantity' => $current_data['quantity'],
813 | 'new_quantity' => $quantity,
814 | ] );
815 |
816 | $response->header( 'X-WC-Cart-ItemQuantity', $quantity_status );
817 |
818 | return $response;
819 |
820 | } else {
821 | return new WP_Error( 'wc_next_rest_can_not_update_item', __( 'Unable to update item quantity in cart.', 'headless-cms' ), [ 'status' => 500 ] );
822 | }
823 | }
824 |
825 | /**
826 | * Checks if the product in the cart has enough stock
827 | * before updating the quantity.
828 | *
829 | * @param array $current_data
830 | * @param string $quantity
831 | *
832 | * @return bool|WP_Error
833 | * @since 1.0.0
834 | *
835 | */
836 | protected function has_enough_stock( $current_data = [], $quantity = 1 ) {
837 | $product_id = ! isset( $current_data['product_id'] ) ? 0 : absint( $current_data['product_id'] );
838 | $variation_id = ! isset( $current_data['variation_id'] ) ? 0 : absint( $current_data['variation_id'] );
839 | $current_product = wc_get_product( $variation_id ? $variation_id : $product_id );
840 |
841 | $quantity = absint( $quantity );
842 |
843 | if ( ! $current_product->has_enough_stock( $quantity ) ) {
844 | return new WP_Error(
845 | 'wc_next_rest_not_enough_in_stock',
846 | sprintf(
847 | __( 'You cannot add that amount of "%1$s" to the cart because there is not enough stock (%2$s remaining).', 'headless-cms' ),
848 | $current_product->get_name(),
849 | wc_format_stock_quantity_for_display( $current_product->get_stock_quantity(), $current_product )
850 | ),
851 | [ 'status' => 500 ]
852 | );
853 | }
854 |
855 | return true;
856 | }
857 |
858 | /**
859 | * Delete/Remove a cart item
860 | *
861 | * @param \WP_REST_Request $request
862 | *
863 | * @return \WP_REST_Response
864 | * @since 1.0.0
865 | *
866 | */
867 | public function delete_item( $request ) {
868 | $cart_item = wc()->cart->get_cart_item( $request['key'] );
869 |
870 | if ( ! wc()->cart->remove_cart_item( $request['key'] ) ) {
871 | return new WP_Error( 'wc_cart_rest_can_not_remove_item', __( 'Unable to remove item from cart.', 'headless-cms' ), [ 'status' => 500 ] );
872 | }
873 |
874 | $data = $this->prepare_cart_item_for_response( $cart_item, $request );
875 | $response = rest_ensure_response( $data );
876 | $response = $this->add_headers( $response );
877 |
878 | return $response;
879 | }
880 |
881 | /**
882 | * Restore a cart item
883 | *
884 | * @param \WP_REST_Request $request
885 | *
886 | * @return \WP_REST_Response
887 | * @since 1.0.0
888 | *
889 | */
890 | public function restore_item( $request ) {
891 | if ( ! wc()->cart->restore_cart_item( $request['key'] ) ) {
892 | return new WP_Error( 'wc_cart_rest_can_not_restore_item', __( 'Unable to restore cart item.', 'headless-cms' ), [ 'status' => 500 ] );
893 | }
894 |
895 | $cart_item = wc()->cart->get_cart_item( $request['key'] );
896 | $data = $this->prepare_cart_item_for_response( $cart_item, $request );
897 | $response = rest_ensure_response( $data );
898 | $response = $this->add_headers( $response );
899 |
900 | return $response;
901 | }
902 |
903 | /**
904 | * Get normalized rest base.
905 | *
906 | * @return string
907 | */
908 | protected function get_normalized_rest_base() {
909 | return preg_replace( '/\(.*\)\//i', '', $this->rest_base );
910 | }
911 |
912 | /**
913 | * Check batch limit.
914 | *
915 | * ATTENTION: Intentionally keep original code from WooCommerce
916 | *
917 | * @param array $items Request items.
918 | *
919 | * @return bool|WP_Error
920 | * @since 1.0.0
921 | *
922 | */
923 | protected function check_batch_limit( $items ) {
924 | $limit = apply_filters( 'woocommerce_rest_batch_items_limit', 100, $this->get_normalized_rest_base() );
925 | $total = 0;
926 |
927 | if ( ! empty( $items['create'] ) ) {
928 | $total += count( $items['create'] );
929 | }
930 |
931 | if ( ! empty( $items['update'] ) ) {
932 | $total += count( $items['update'] );
933 | }
934 |
935 | if ( ! empty( $items['delete'] ) ) {
936 | $total += count( $items['delete'] );
937 | }
938 |
939 | if ( $total > $limit ) {
940 | /* translators: %s: items limit */
941 | return new WP_Error( 'woocommerce_rest_request_entity_too_large', sprintf( __( 'Unable to accept more than %s items for this request.', 'headless-cms' ), $limit ), [ 'status' => 413 ] );
942 | }
943 |
944 | return true;
945 | }
946 |
947 | /**
948 | * Bulk create, update and delete items.
949 | *
950 | * @param WP_REST_Request $request Full details about the request.
951 | *
952 | * @return array Of WP_Error or WP_REST_Response.
953 | * @since 1.0.0
954 | *
955 | */
956 | public function batch_items( $request ) {
957 | /**
958 | * REST Server
959 | *
960 | * @var WP_REST_Server $wp_rest_server
961 | */
962 | global $wp_rest_server;
963 |
964 | // Get the request params.
965 | $items = array_filter( $request->get_params() );
966 | $response = [];
967 |
968 | // Check batch limit.
969 | $limit = $this->check_batch_limit( $items );
970 | if ( is_wp_error( $limit ) ) {
971 | return $limit;
972 | }
973 |
974 | if ( ! empty( $items['create'] ) ) {
975 | foreach ( $items['create'] as $item ) {
976 | $_item = new WP_REST_Request( 'POST' );
977 |
978 | // Default parameters.
979 | $defaults = [];
980 | $schema = $this->get_public_item_schema();
981 | foreach ( $schema['properties'] as $arg => $options ) {
982 | if ( isset( $options['default'] ) ) {
983 | $defaults[ $arg ] = $options['default'];
984 | }
985 | }
986 | $_item->set_default_params( $defaults );
987 |
988 | // Set request parameters.
989 | $_item->set_body_params( $item );
990 | $_response = $this->create_item( $_item );
991 |
992 | if ( is_wp_error( $_response ) ) {
993 | $response['create'][] = [
994 | 'id' => 0,
995 | 'error' => [
996 | 'code' => $_response->get_error_code(),
997 | 'message' => $_response->get_error_message(),
998 | 'data' => $_response->get_error_data(),
999 | ],
1000 | ];
1001 | } else {
1002 | $response['create'][] = $wp_rest_server->response_to_data( $_response, '' );
1003 | }
1004 | }
1005 | }
1006 |
1007 | if ( ! empty( $items['update'] ) ) {
1008 | foreach ( $items['update'] as $item ) {
1009 | $_item = new WP_REST_Request( 'PUT' );
1010 | $_item->set_body_params( $item );
1011 | $_response = $this->update_item( $_item );
1012 |
1013 | if ( is_wp_error( $_response ) ) {
1014 | $response['update'][] = [
1015 | 'id' => $item['id'],
1016 | 'error' => [
1017 | 'code' => $_response->get_error_code(),
1018 | 'message' => $_response->get_error_message(),
1019 | 'data' => $_response->get_error_data(),
1020 | ],
1021 | ];
1022 | } else {
1023 | $response['update'][] = $wp_rest_server->response_to_data( $_response, '' );
1024 | }
1025 | }
1026 | }
1027 |
1028 | if ( ! empty( $items['delete'] ) ) {
1029 | foreach ( $items['delete'] as $id ) {
1030 | $id = (int) $id;
1031 |
1032 | if ( 0 === $id ) {
1033 | continue;
1034 | }
1035 |
1036 | $_item = new WP_REST_Request( 'DELETE' );
1037 | $_item->set_query_params(
1038 | [
1039 | 'id' => $id,
1040 | 'force' => true,
1041 | ]
1042 | );
1043 | $_response = $this->delete_item( $_item );
1044 |
1045 | if ( is_wp_error( $_response ) ) {
1046 | $response['delete'][] = [
1047 | 'id' => $id,
1048 | 'error' => [
1049 | 'code' => $_response->get_error_code(),
1050 | 'message' => $_response->get_error_message(),
1051 | 'data' => $_response->get_error_data(),
1052 | ],
1053 | ];
1054 | } else {
1055 | $response['delete'][] = $wp_rest_server->response_to_data( $_response, '' );
1056 | }
1057 | }
1058 | }
1059 |
1060 | return $response;
1061 | }
1062 |
1063 | }
1064 |
--------------------------------------------------------------------------------
/inc/classes/api/class-wc-countries.php:
--------------------------------------------------------------------------------
1 | setup_hooks();
41 | }
42 |
43 | /**
44 | * To setup action/filter.
45 | *
46 | * @return void
47 | */
48 | protected function setup_hooks() {
49 |
50 | /**
51 | * Action
52 | */
53 | add_action( 'rest_api_init', [ $this, 'register_rest_api_endpoints' ] );
54 |
55 | }
56 |
57 | /**
58 | * Register endpoints.
59 | */
60 | public function register_rest_api_endpoints() {
61 |
62 | // e.g. http://example.com/wp-json/rae/v1/wc/countries
63 | register_rest_route(
64 | $this->namespace,
65 | '/' . $this->rest_base,
66 | [
67 | [
68 | 'methods' => WP_REST_Server::READABLE,
69 | 'callback' => [ $this, 'get_countries' ],
70 | 'permission_callback' => '__return_true',
71 | ],
72 | ]
73 | );
74 | }
75 |
76 | /**
77 | * Get countries
78 | *
79 | * @param \WP_REST_Request $request
80 | *
81 | * @return \WP_REST_Response
82 | *
83 | * @since 1.0.0
84 | *
85 | */
86 | public function get_countries( \WP_REST_Request $request ): \WP_REST_Response {
87 |
88 | // All countries for billing.
89 | $all_countries = class_exists( 'WooCommerce' ) ? WC()->countries : [];
90 | $billing_countries = ! empty( $all_countries->countries ) ? $all_countries->countries : [];
91 | $billing_countries = $this->get_formatted_countries( $billing_countries );
92 |
93 | // All countries with states for shipping.
94 | $shipping_countries = class_exists( 'WooCommerce' ) ? WC()->countries->get_shipping_countries() : [];;
95 | $shipping_countries = ! empty( $shipping_countries ) ? $shipping_countries : [];
96 | $shipping_countries = $this->get_formatted_countries( $shipping_countries );
97 |
98 | /**
99 | * Here you need to return data that matches the shape of the "WooCountries" type. You could get
100 | * the data from the WP Database, an external API, or static values.
101 | * For example in this case we are getting it from WordPress database.
102 | */
103 | $data = [
104 | 'billingCountries' => $billing_countries,
105 | 'shippingCountries' => $shipping_countries,
106 | ];
107 |
108 | return rest_ensure_response( $data );
109 | }
110 |
111 | /**
112 | * Get Formatted Countries.
113 | *
114 | * @param $countries
115 | *
116 | * @return array
117 | */
118 | public function get_formatted_countries( $countries ) {
119 |
120 | $formatted_countries = [];
121 |
122 | if ( empty( $countries ) && ! is_array( $countries ) ) {
123 | return $formatted_countries;
124 | }
125 |
126 | foreach ( $countries as $countryCode => $countryName ) {
127 | array_push( $formatted_countries, [
128 | 'countryCode' => $countryCode,
129 | 'countryName' => $countryName,
130 | ] );
131 | }
132 |
133 | return $formatted_countries;
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/inc/classes/api/class-wc-states.php:
--------------------------------------------------------------------------------
1 | setup_hooks();
41 | }
42 |
43 | /**
44 | * To setup action/filter.
45 | *
46 | * @return void
47 | */
48 | protected function setup_hooks() {
49 |
50 | /**
51 | * Action
52 | */
53 | add_action( 'rest_api_init', [ $this, 'register_rest_api_endpoints' ] );
54 |
55 | }
56 |
57 | /**
58 | * Register endpoints.
59 | */
60 | public function register_rest_api_endpoints() {
61 |
62 | // e.g. http://example.com/wp-json/rae/v1/wc/states?countryCode=IN
63 | register_rest_route(
64 | $this->namespace,
65 | '/' . $this->rest_base,
66 | [
67 | [
68 | 'methods' => WP_REST_Server::READABLE,
69 | 'callback' => [ $this, 'get_states' ],
70 | 'permission_callback' => '__return_true',
71 | ],
72 | ]
73 | );
74 | }
75 |
76 | /**
77 | * Get States by
78 | *
79 | * @param \WP_REST_Request $request
80 | *
81 | * @return \WP_REST_Response
82 | */
83 | public function get_states( \WP_REST_Request $request ): \WP_REST_Response {
84 |
85 | if ( ! class_exists( 'WooCommerce' ) ) {
86 | return rest_ensure_response( [] );
87 | }
88 |
89 | $parameters = $request->get_params();
90 | $countryCode = ! empty( $parameters['countryCode'] ) ? sanitize_text_field( $parameters['countryCode'] ) : '';
91 |
92 | $states = ! empty( $countryCode ) ? WC()->countries->get_states( strtoupper( $countryCode ) ) : [];
93 | $states = $this->get_formatted_states( $states );
94 |
95 | /**
96 | * Here you need to return data that matches the shape of the "WooStates" type. You could get
97 | * the data from the WP Database, an external API, or static values.
98 | * For example in this case we are getting it from WordPress database.
99 | */
100 | $data = [
101 | 'states' => $states,
102 | ];
103 |
104 | return rest_ensure_response( $data );
105 | }
106 |
107 | /**
108 | * Get Formatted States.
109 | *
110 | * @param array $states
111 | *
112 | * @return array
113 | */
114 | public function get_formatted_states( array $states = [] ): array {
115 |
116 | $formatted_states = [];
117 |
118 | if ( empty( $states ) && ! is_array( $states ) ) {
119 | return $formatted_states;
120 | }
121 |
122 | foreach ( $states as $stateCode => $stateName ) {
123 | array_push( $formatted_states, [
124 | 'stateCode' => $stateCode,
125 | 'stateName' => $stateName,
126 | ] );
127 | }
128 |
129 | return $formatted_states;
130 | }
131 | }
132 |
--------------------------------------------------------------------------------
/inc/classes/class-assets.php:
--------------------------------------------------------------------------------
1 | setup_hooks();
24 | }
25 |
26 | /**
27 | * To setup action/filter.
28 | *
29 | * @return void
30 | */
31 | protected function setup_hooks() {
32 |
33 | /**
34 | * Action
35 | */
36 | add_action( 'admin_enqueue_scripts', [ $this, 'admin_enqueue_scripts' ] );
37 | add_action( 'enqueue_block_assets', [ $this, 'enqueue_editor_assets' ] );
38 |
39 | }
40 |
41 | /**
42 | * To enqueue scripts and styles. in admin.
43 | *
44 | * @param string $hook_suffix Admin page name.
45 | *
46 | * @return void
47 | */
48 | public function admin_enqueue_scripts( $hook_suffix ) {
49 |
50 | if ( 'toplevel_page_hcms-settings-menu-page' === $hook_suffix ) {
51 | wp_register_script( 'hcms-plugins-settings-js', HEADLESS_CMS_BUILD_URI . '/js/settings.js', [ 'jquery' ], filemtime( HEADLESS_CMS_BUILD_DIR . '/js/settings.js' ), true );
52 | wp_register_style( 'hcms-plugins-settings-css', HEADLESS_CMS_BUILD_URI . '/css/settings.css', [], filemtime( HEADLESS_CMS_BUILD_DIR . '/css/settings.css' ), false );
53 |
54 | wp_enqueue_style( 'hcms-plugins-settings-css' );
55 | wp_enqueue_media();
56 | wp_enqueue_script( 'hcms-plugins-settings-js' );
57 | wp_enqueue_script( 'media-uploader' );
58 | }
59 |
60 | if ( 'term.php' === $hook_suffix ) {
61 | wp_register_script( 'hcms-plugins-category-js', HEADLESS_CMS_BUILD_URI . '/js/category.js', [ 'jquery' ], filemtime( HEADLESS_CMS_BUILD_DIR . '/js/category.js' ), true );
62 | wp_register_style( 'hcms-plugins-category-css', HEADLESS_CMS_BUILD_URI . '/css/category.css', [], filemtime( HEADLESS_CMS_BUILD_DIR . '/css/category.css' ), false );
63 |
64 | wp_enqueue_style( 'hcms-plugins-category-css' );
65 | wp_enqueue_media();
66 | wp_enqueue_script( 'hcms-plugins-category-js' );
67 | wp_enqueue_script( 'media-uploader' );
68 | }
69 |
70 | }
71 |
72 | /**
73 | * Enqueue editor scripts.
74 | */
75 | public function enqueue_editor_assets() {
76 |
77 | $plugin_settings = get_option('hcms_plugin_options');
78 | $is_custom_preview_link_active = is_array($plugin_settings) && !empty($plugin_settings['activate_preview']) ? $plugin_settings['activate_preview'] : false;
79 | $frontend_site_url = is_array($plugin_settings) && !empty($plugin_settings['frontend_site_url']) ? $plugin_settings['frontend_site_url'] : '';
80 |
81 | // Theme Editor JS.
82 | if ( is_admin() ) {
83 | wp_enqueue_script(
84 | 'hcms-editor-js',
85 | HEADLESS_CMS_BUILD_URI . '/js/editor.js',
86 | [],
87 | '1.1',
88 | true
89 | );
90 | wp_localize_script( 'hcms-editor-js', 'frontendConfig', [
91 | 'isPreviewLinkActive' => $is_custom_preview_link_active,
92 | 'frontendSiteUrl' => $frontend_site_url
93 | ] );
94 | }
95 | }
96 |
97 | }
98 |
--------------------------------------------------------------------------------
/inc/classes/class-category.php:
--------------------------------------------------------------------------------
1 | setup_hooks();
26 | }
27 |
28 | /**
29 | * To setup action/filter.
30 | *
31 | * @return void
32 | */
33 | protected function setup_hooks() {
34 |
35 | /**
36 | * Action
37 | */
38 | add_action( 'category_add_form_fields', [ $this, 'add_category_image' ], 10, 2 );
39 | add_action( 'created_category', [ $this, 'save_category_image' ], 10, 2 );
40 | add_action( 'category_edit_form_fields', [ $this, 'update_category_image' ], 10, 2 );
41 | add_action( 'edited_category', [ $this, 'updated_category_image' ], 10, 2 );
42 |
43 | }
44 |
45 | /**
46 | * Add form fields
47 | *
48 | * @param string $taxonomy Taxonomy.
49 | */
50 | public function add_category_image( $taxonomy ) {
51 | include_once HEADLESS_CMS_TEMPLATE_PATH . 'category-img-form.php';
52 | }
53 |
54 | /**
55 | * Save the form fields
56 | *
57 | * @param integer $term_id term ID.
58 | */
59 | public function save_category_image( $term_id ) {
60 | if ( ! empty( $_POST['category-image-id'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.NoNonceVerification
61 | $image = sanitize_text_field( $_POST['category-image-id'] );
62 | $meta_key = 'category-image-id';
63 |
64 | add_term_meta( $term_id, $meta_key, $image, true );
65 | }
66 | }
67 |
68 | /**
69 | * Edit the form fields
70 | *
71 | * @param object $term Term.
72 | */
73 | public function update_category_image( $term ) { ?>
74 |
75 |
76 |
77 | |
78 |
79 | term_id, 'category-image-id', true );
81 | $add_image_class = ! empty( $image_id ) ? 'hcms_hide' : '';
82 | $remove_image_class = empty( $image_id ) ? 'hcms_hide' : '';
83 | ?>
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 | |
95 |
96 | setup_hooks();
24 | }
25 |
26 | /**
27 | * To setup action/filter.
28 | *
29 | * @return void
30 | */
31 | protected function setup_hooks() {
32 |
33 | /**
34 | * Action
35 | */
36 | add_action( 'customize_register', [ $this, 'customize_register' ] );
37 |
38 | }
39 |
40 | /**
41 | * Customize register.
42 | *
43 | * @param \WP_Customize_Manager $wp_customize Theme Customizer object.
44 | *
45 | * @action customize_register
46 | */
47 | public function customize_register( \WP_Customize_Manager $wp_customize ) {
48 |
49 | $this->social_icon_section( $wp_customize );
50 | $this->footer_section( $wp_customize );
51 |
52 | }
53 |
54 | /**
55 | * Add Footer section settings.
56 | *
57 | * @param object $wp_customize WP_Customize.
58 | */
59 | public function footer_section( $wp_customize ) {
60 |
61 | $wp_customize->add_section(
62 | 'rae_footer',
63 | [
64 | 'title' => esc_html__( 'Footer', 'rest-api-endpoints' ),
65 | 'description' => esc_html__( 'Footer', 'rest-api-endpoints' ),
66 | ]
67 | );
68 |
69 | $setting_id = 'rae_footer_text';
70 |
71 | $wp_customize->add_setting(
72 | $setting_id,
73 | [
74 | 'default' => '',
75 | 'capability' => 'edit_theme_options',
76 | 'sanitize_callback' => 'esc_html',
77 | ]
78 | );
79 |
80 | $wp_customize->add_control(
81 | $setting_id,
82 | [
83 | 'label' => esc_html__( 'Copyright text', 'rest-api-endpoints' ),
84 | 'section' => 'rae_footer',
85 | 'settings' => $setting_id,
86 | 'type' => 'text',
87 | ]
88 | );
89 | }
90 |
91 | /**
92 | * Add social icon section.
93 | *
94 | * @param object $wp_customize WP_Customize.
95 | */
96 | public function social_icon_section( $wp_customize ) {
97 |
98 | // Social Icons.
99 | $social_icons = [ 'facebook', 'twitter', 'instagram', 'youtube' ];
100 |
101 | $wp_customize->add_section(
102 | 'rae_social_links',
103 | [
104 | 'title' => esc_html__( 'Social Links', 'rest-api-endpoints' ),
105 | 'description' => esc_html__( 'Social links', 'rest-api-endpoints' ),
106 | ]
107 | );
108 |
109 | foreach ( $social_icons as $social_icon ) {
110 |
111 | $setting_id = sprintf( 'rae_%s_link', $social_icon );
112 |
113 | $wp_customize->add_setting(
114 | $setting_id,
115 | [
116 | 'default' => '',
117 | 'capability' => 'edit_theme_options',
118 | 'sanitize_callback' => 'esc_url',
119 | ]
120 | );
121 |
122 | $wp_customize->add_control(
123 | $setting_id,
124 | [
125 | 'label' => esc_html( $social_icon ),
126 | 'section' => 'rae_social_links',
127 | 'settings' => $setting_id,
128 | 'type' => 'text',
129 | ]
130 | );
131 | }
132 | }
133 |
134 |
135 | }
136 |
--------------------------------------------------------------------------------
/inc/classes/class-plugin.php:
--------------------------------------------------------------------------------
1 | setup_hooks();
26 | }
27 |
28 | /**
29 | * To setup action/filter.
30 | *
31 | * @return void
32 | */
33 | protected function setup_hooks()
34 | {
35 |
36 | /**
37 | * Action
38 | */
39 | add_filter('preview_post_link', [$this, 'set_frontend_post_preview_link'], 1, 2);
40 |
41 | }
42 |
43 | /**
44 | * Sets the customized post preview link for frontend application.
45 | *
46 | * @param string $link The WordPress preview link.
47 | * @param object $post Post Object
48 | */
49 | public function set_frontend_post_preview_link($link, $post)
50 | {
51 | $plugin_settings = get_option('hcms_plugin_options');
52 | $is_custom_preview_link_active = is_array($plugin_settings) && !empty($plugin_settings['activate_preview']) ? $plugin_settings['activate_preview'] : false;
53 | $frontend_site_url = is_array($plugin_settings) && !empty($plugin_settings['frontend_site_url']) ? $plugin_settings['frontend_site_url'] : '';
54 |
55 | if (!$is_custom_preview_link_active) {
56 | return $link;
57 | }
58 |
59 | // Expected frontend preview URL /api/preview/?postType=page&postId=30
60 | $root_url = WP_DEBUG === true ? 'http://localhost:3000' : $frontend_site_url;
61 | return sprintf('%1$sapi/preview/?postType=%2$s&postId=%3$d', esc_url(trailingslashit( $root_url )), get_post_type($post), $post->ID);
62 |
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/inc/classes/class-settings.php:
--------------------------------------------------------------------------------
1 | setup_hooks();
26 | }
27 |
28 | /**
29 | * To setup action/filter.
30 | *
31 | * @return void
32 | */
33 | protected function setup_hooks() {
34 |
35 | /**
36 | * Action
37 | */
38 | add_action( 'admin_menu', [ $this, 'add_settings_page' ] );
39 |
40 | // Get plugin settings.
41 | $plugin_settings = get_option( 'hcms_plugin_options' );
42 | $allow_anonymous_comments = is_array( $plugin_settings ) && ! empty( $plugin_settings['allow_anonymous_comments'] ) ? $plugin_settings['allow_anonymous_comments'] : false;
43 |
44 | // If $allow_anonymous_comments is checked, then allow users to post comments without logging-in via REST API.
45 | if ( ! empty( $allow_anonymous_comments ) ) {
46 | add_filter( 'rest_allow_anonymous_comments', '__return_true' );
47 | }
48 |
49 | }
50 |
51 | /**
52 | * Adds settings page for the plugin in the dashboard.
53 | */
54 | public function add_settings_page() {
55 |
56 | $menu_plugin_title = __( 'HCMS Settings', 'headless-cms' );
57 |
58 | // Create new top-level menu.
59 | add_menu_page(
60 | __(
61 | 'HCMS Plugin Settings',
62 | 'headless-cms'
63 | ),
64 | $menu_plugin_title,
65 | 'administrator',
66 | 'hcms-settings-menu-page',
67 | [ $this, 'plugin_settings_page_content' ],
68 | 'dashicons-admin-generic'
69 | );
70 |
71 | // Call register settings function.
72 | add_action( 'admin_init', [ $this, 'register_plugin_settings' ] );
73 | }
74 |
75 | /**
76 | * Register our settings.
77 | */
78 | public function register_plugin_settings() {
79 | register_setting( 'hcms-plugin-settings-group', 'hcms_plugin_options' );
80 | }
81 |
82 | /**
83 | * Settings Page Content for Orion Plugin.
84 | */
85 | public function plugin_settings_page_content() {
86 |
87 | // Check user capabilities.
88 | if ( ! current_user_can( 'manage_options' ) ) {
89 | return;
90 | }
91 |
92 | /**
93 | * Add error/update messages.
94 | * Check if the user have submitted the settings.
95 | * WordPress will add the "settings-updated" $_GET parameter to the url.
96 | */
97 | if ( isset( $_GET['settings-updated'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.NoNonceVerification
98 |
99 | // Add settings saved message with the class of "updated".
100 | add_settings_error( 'hcms_app_messages', 'hcms_app_message', __( 'Settings Saved', 'headless-cms' ), 'updated' );
101 |
102 | }
103 |
104 | // Show error/update messages.
105 | settings_errors( 'hcms_app_messages' );
106 |
107 | include_once HEADLESS_CMS_TEMPLATE_PATH . 'settings-form-template.php';
108 |
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/inc/classes/mutations/class-add-wishlist.php:
--------------------------------------------------------------------------------
1 | setup_hooks();
24 | }
25 |
26 | /**
27 | * To setup action/filter.
28 | *
29 | * @return void
30 | */
31 | protected function setup_hooks() {
32 |
33 | /**
34 | * Action
35 | */
36 |
37 | // Register Wishlist Mutation Types.
38 | add_action( 'graphql_register_types', [ $this, 'add_wishlist_mutation' ] );
39 |
40 | }
41 |
42 | /**
43 | *
44 | * inputFields: expects an array of Fields to be used for inputting values to the mutation
45 | *
46 | * outputFields: expects an array of fields that can be asked for in response to the mutation
47 | * the resolve function is optional, but can be useful if the mutateAndPayload doesn't return an array
48 | * with the same key(s) as the outputFields
49 | *
50 | * mutateAndGetPayload: expects a function, and the function gets passed the $input, $context, and $info
51 | * the function should return enough info for the outputFields to resolve with
52 | *
53 | * @throws \Exception
54 | */
55 | /**
56 | * Register field.
57 | */
58 | public function add_wishlist_mutation() {
59 | register_graphql_mutation(
60 | 'addToWishlist',
61 | [
62 | 'inputFields' => [
63 | 'productId' => [
64 | 'type' => 'Integer',
65 | 'description' => __( 'Product id', 'headless-cms' ),
66 | ],
67 | ],
68 |
69 | 'outputFields' => [
70 | 'added' => [
71 | 'type' => 'Boolean',
72 | 'description' => __( 'True if the product is removed, false otherwise', 'headless-cms' ),
73 | ],
74 | 'productId' => [
75 | 'type' => 'Integer',
76 | 'description' => __( 'The Product id that was added', 'headless-cms' ),
77 | ],
78 | 'wishlistProductIds' => [
79 | 'type' => [ 'list_of' => 'Integer' ],
80 | 'description' => __( 'The Product ids in the wishlist', 'headless-cms' ),
81 | ],
82 | 'error' => [
83 | 'type' => 'String',
84 | 'description' => __( 'Description of the error', 'headless-cms' ),
85 | ],
86 | ],
87 |
88 | 'mutateAndGetPayload' => function ( $input, $context, $info ) {
89 |
90 | $response = [
91 | 'added' => false,
92 | 'productId' => ! empty( $input['productId'] ) ? $input['productId'] : 0,
93 | 'wishlistProductIds' => [],
94 | 'error' => '',
95 | ];
96 |
97 | $user_id = get_current_user_id();
98 |
99 | if ( ! $user_id ) {
100 | $response['error'] = __( 'Authentication failed', 'headless-cms' );
101 |
102 | return $response;
103 | }
104 |
105 | if ( empty( $input['productId'] ) ) {
106 | $response['error'] = __( 'Please enter a valid product id', 'headless-cms' );
107 |
108 | return $response;
109 | }
110 |
111 | return $this->save_products_to_wishlist( $input['productId'], $user_id, $response );
112 | },
113 | ]
114 | );
115 | }
116 |
117 | /**
118 | * Save products to wishlist
119 | *
120 | * @param int $product_id Product id.
121 | * @param int $user_id User id.
122 | * @param array $response Response.
123 | *
124 | * @return array $response Response.
125 | */
126 | public function save_products_to_wishlist( int $product_id, int $user_id, array $response ) {
127 |
128 | // Check if the product id is valid else return error.
129 | $product = wc_get_product( $product_id );
130 | if ( empty( $product ) ) {
131 | $response['error'] = __( 'Product does not exist', 'headless-cms' );
132 | return $response;
133 | }
134 |
135 | // Get saved products of current user.
136 | $saved_products = (array) get_user_meta( $user_id, 'wishlist_saved_products', true );
137 | $response['wishlistProductIds'] = array_filter( $saved_products );
138 |
139 | // Check if the product already exists.
140 | if ( in_array( $product_id, $saved_products, true ) ) {
141 | if ( array_search( $product_id, $saved_products, true ) ) {
142 | $response['error'] = __( 'Product already exist', 'headless-cms' );
143 | return $response;
144 | }
145 | } else {
146 | $saved_products[] = $product_id;
147 | }
148 |
149 | // Save product to current user.
150 | $save_product_to_user = update_user_meta( $user_id, 'wishlist_saved_products', $saved_products );
151 |
152 | if ( !$save_product_to_user ) {
153 | $response['error'] = __( 'Something went wrong in adding the product to wishlist', 'headless-cms' );
154 |
155 | return $response;
156 | }
157 |
158 | $response['added'] = true;
159 | $response['productId'] = $product_id;
160 | $response['wishlistProductIds'] = array_filter( $saved_products );
161 | return $response;
162 | }
163 | }
164 |
--------------------------------------------------------------------------------
/inc/classes/mutations/class-delete-wishlist.php:
--------------------------------------------------------------------------------
1 | setup_hooks();
24 | }
25 |
26 | /**
27 | * To setup action/filter.
28 | *
29 | * @return void
30 | */
31 | protected function setup_hooks() {
32 |
33 | /**
34 | * Action
35 | */
36 |
37 | // Register Delete wishlist Mutation Types.
38 | add_action( 'graphql_register_types', [ $this, 'delete_wishlist_mutation' ] );
39 |
40 | }
41 |
42 | /**
43 | *
44 | * inputFields: expects an array of Fields to be used for inputting values to the mutation
45 | *
46 | * outputFields: expects an array of fields that can be asked for in response to the mutation
47 | * the resolve function is optional, but can be useful if the mutateAndPayload doesn't return an array
48 | * with the same key(s) as the outputFields
49 | *
50 | * mutateAndGetPayload: expects a function, and the function gets passed the $input, $context, and $info
51 | * the function should return enough info for the outputFields to resolve with
52 | *
53 | * @throws \Exception
54 | */
55 | public function delete_wishlist_mutation() {
56 | register_graphql_mutation( 'removeFromWishlist', [
57 | 'inputFields' => [
58 | 'productId' => [
59 | 'type' => 'Integer',
60 | 'description' => __( 'Product id', 'headless-cms' ),
61 | ],
62 | ],
63 |
64 | 'outputFields' => [
65 | 'removed' => [
66 | 'type' => 'Boolean',
67 | 'description' => __( 'True if the product is removed, false otherwise', 'headless-cms' ),
68 | ],
69 | 'productId' => [
70 | 'type' => 'Integer',
71 | 'description' => __( 'The Product id that was deleted', 'headless-cms' ),
72 | ],
73 | 'wishlistProductIds' => [
74 | 'type' => [ 'list_of' => 'Integer' ],
75 | 'description' => __( 'The Product ids in the wishlist', 'headless-cms' ),
76 | ],
77 | 'error' => [
78 | 'type' => 'String',
79 | 'description' => __( 'Description of the error', 'headless-cms' ),
80 | ],
81 | ],
82 |
83 | 'mutateAndGetPayload' => function ( $input, $context, $info ) {
84 |
85 | $response = [
86 | 'removed' => false,
87 | 'productId' => ! empty( $input['productId'] ) ? intval($input['productId']) : 0,
88 | 'error' => '',
89 | ];
90 |
91 | if ( empty( $input['productId'] ) ) {
92 | $response['error'] = __( 'Please enter a valid product id', 'headless-cms' );
93 |
94 | return $response;
95 | }
96 |
97 | $user_id = get_current_user_id();
98 |
99 | if ( ! $user_id ) {
100 | $response['error'] = __( 'Request is not authenticated', 'headless-cms' );
101 |
102 | return $response;
103 | }
104 |
105 | return $this->remove_item( $input['productId'], $user_id, $response );
106 | },
107 | ] );
108 | }
109 |
110 | /**
111 | * Remove item from wishlist
112 | *
113 | * @since 1.0.0
114 | *
115 | */
116 | public function remove_item( $product_id, $user_id, $response ) {
117 |
118 | $saved_products = (array) get_user_meta( $user_id, 'wishlist_saved_products', true );
119 | $key = array_search( $product_id, $saved_products );
120 |
121 | if ( ! $key ) {
122 | $response['error'] = __( 'Product does not exist in the wishlist', 'headless-cms' );
123 |
124 | return $response;
125 | }
126 |
127 | unset( $saved_products[ $key ] );
128 | $removed_product_from_wishlist = update_user_meta( $user_id, 'wishlist_saved_products', $saved_products );
129 |
130 | if ( !$removed_product_from_wishlist ) {
131 | $response['error'] = __( 'Something went wrong in removing the product', 'headless-cms' );
132 |
133 | return $response;
134 | } else {
135 | $response['removed'] = true;
136 | $response['productId'] = intval( $product_id );
137 | $response['wishlistProductIds'] = array_filter( $saved_products );
138 |
139 | return $response;
140 | }
141 |
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/inc/classes/queries/class-get-wishlist.php:
--------------------------------------------------------------------------------
1 | setup_hooks();
24 | }
25 |
26 | /**
27 | * To setup action/filter.
28 | *
29 | * @return void
30 | */
31 | protected function setup_hooks() {
32 |
33 | /**
34 | * Action
35 | */
36 |
37 | // Register Wishlist Field.
38 | add_action( 'graphql_register_types', [ $this, 'register_get_wishlist_fields' ] );
39 |
40 | }
41 |
42 | /**
43 | * Register field.
44 | */
45 | function register_get_wishlist_fields() {
46 |
47 | if ( !class_exists('WooCommerce') ) {
48 | return;
49 | }
50 |
51 | register_graphql_object_type( 'WishlistProductImage', [
52 | 'fields' => [
53 | 'attachmentId' => [ 'type' => 'Integer' ],
54 | 'src' => [ 'type' => 'String' ],
55 | 'alt' => [ 'type' => 'String' ],
56 | ],
57 | ] );
58 |
59 | register_graphql_object_type( 'WishlistProduct', [
60 | 'fields' => [
61 | 'databaseId' => [ 'type' => 'Integer' ],
62 | 'name' => [ 'type' => 'String' ],
63 | 'slug' => [ 'type' => 'String' ],
64 | 'typename' => [ 'type' => 'String' ],
65 | 'priceHtml' => [ 'type' => 'String' ],
66 | 'image' => [ 'type' => 'WishlistProductImage' ],
67 | 'buttonText' => [ 'type' => 'String' ],
68 | 'productUrl' => [ 'type' => 'String' ],
69 | 'stockStatus' => [ 'type' => 'String' ],
70 | 'stockQuantity' => [ 'type' => 'Integer' ],
71 | ],
72 | ] );
73 |
74 | register_graphql_object_type( 'WishlistProducts', [
75 | 'description' => __( 'States Type', 'headless-cms' ),
76 | 'fields' => [
77 | 'productIds' => [
78 | 'type' => [
79 | 'list_of' => 'Integer',
80 | ],
81 | ],
82 | 'products' => [ 'type' => [ 'list_of' => 'WishlistProduct' ] ],
83 | 'error' => [ 'type' => 'String' ],
84 | ],
85 | ] );
86 |
87 | register_graphql_field(
88 | 'RootQuery',
89 | 'getWishList',
90 | [
91 | 'description' => __( 'States', 'headless-cms' ),
92 | 'type' => 'WishlistProducts',
93 | 'resolve' => function ( $source, $args, $context, $info ) {
94 | $wishlist_products = [
95 | 'productIds' => [],
96 | 'products' => [],
97 | ];
98 |
99 | $user_id = get_current_user_id();
100 |
101 | if ( ! $user_id ) {
102 | $wishlist_products['error'] = __( 'Request is not authenticated', 'headless-cms' );
103 |
104 | return $wishlist_products;
105 | }
106 |
107 | $saved_product_ids = (array) get_user_meta( $user_id, 'wishlist_saved_products', true );
108 | $wish_list_products = $this->prepare_wishlist_items_for_response( $saved_product_ids );
109 |
110 | /**
111 | * Here you need to return data that matches the shape of the "WishlistProduct" type. You could get
112 | * the data from the WP Database, an external API, or static values.
113 | * For example in this case we are getting it from WordPress database.
114 | */
115 | $wishlist_products['productIds'] = array_filter( $saved_product_ids );
116 | $wishlist_products['products'] = $wish_list_products;
117 |
118 | return $wishlist_products;
119 |
120 | },
121 | ]
122 | );
123 | }
124 |
125 | /**
126 | * Get the wishlist products with required data.
127 | *
128 | * @param array $product_ids Product Ids
129 | *
130 | * @return array $wishlist_products Wishlist products.
131 | */
132 | public function prepare_wishlist_items_for_response( array $product_ids ) {
133 |
134 | $type_list = [
135 | 'simple' => 'SimpleProduct',
136 | 'variable' => 'VariableProduct',
137 | 'external' => 'ExternalProduct',
138 | 'group' => 'GroupProduct',
139 | ];
140 |
141 | $wishlist_products = [];
142 | $args = [
143 | 'include' => $product_ids,
144 | ];
145 | $products = wc_get_products( $args );
146 |
147 | if ( empty( $products ) || ! is_array( $products ) ) {
148 | return $wishlist_products;
149 | }
150 |
151 | foreach ( $products as $product ) {
152 | $product_data = [];
153 | $data = $product->get_data();
154 | $stock_status = ! empty( $data['stock_status'] ) ? $data['stock_status'] : '';
155 | $stock_status = 'instock' === $stock_status ? 'IN_STOCK' : $stock_status;
156 | $typename = $product->get_type();
157 | $typename = ! empty( $typename ) ? $type_list[$typename] : '';
158 |
159 | $product_data['databaseId'] = ! empty( $data['id'] ) ? $data['id'] : 0;
160 | $product_data['name'] = ! empty( $data['name'] ) ? $data['name'] : '';
161 | $product_data['slug'] = ! empty( $data['slug'] ) ? $data['slug'] : '';
162 | $product_data['typename'] = $typename;
163 | $product_data['priceHtml'] = $product->get_price_html();
164 | $product_data['image'] = $this->get_image( $product, $data['name'] );
165 | $product_data['buttonText'] = ! empty( $data['button_text'] ) ? $data['button_text'] : '';
166 | $product_data['productUrl'] = ! empty( $data['product_url'] ) ? $data['product_url'] : '';
167 | $product_data['stockStatus'] = $stock_status;
168 | $product_data['stockQuantity'] = intval( $data['stock_quantity'] );
169 |
170 | // Push each product into the wishlist products array.
171 | $wishlist_products[] = $product_data;
172 | }
173 |
174 | return $wishlist_products;
175 | }
176 |
177 |
178 | /**
179 | * Get the featured image for a product
180 | *
181 | * @param object $product Product
182 | * @param string $product_name Product name
183 | *
184 | * @return array Featured image.
185 | */
186 | protected function get_image( object $product, string $product_name ) {
187 | $attachment_id = $product->get_image_id() ? $product->get_image_id() : 0;
188 |
189 | // Set a placeholder image if the product has no images set.
190 | if ( empty( $attachment_id ) ) {
191 | return [
192 | 'attachmentId' => 0,
193 | 'src' => wc_placeholder_img_src(),
194 | 'alt' => $product_name,
195 | ];
196 | }
197 |
198 | $attachment = wp_get_attachment_image_src( $attachment_id, 'full' );
199 | $altText = get_post_meta( $attachment_id, '_wp_attachment_image_alt', true );
200 |
201 | return [
202 | 'attachmentId' => (int) $attachment_id,
203 | 'src' => current( $attachment ),
204 | 'alt' => ! empty( $altText ) ? $altText : $product_name,
205 | ];
206 | }
207 |
208 | }
209 |
--------------------------------------------------------------------------------
/inc/classes/queries/class-header-footer-schema.php:
--------------------------------------------------------------------------------
1 | setup_hooks();
24 | }
25 |
26 | /**
27 | * To setup action/filter.
28 | *
29 | * @return void
30 | */
31 | protected function setup_hooks() {
32 |
33 | /**
34 | * Action
35 | */
36 |
37 | // Register Header Type and Field.
38 | add_action( 'graphql_register_types', [ $this, 'register_header_type' ] );
39 | add_action( 'graphql_register_types', [ $this, 'register_header_field' ] );
40 |
41 | // Register Social Links Type.
42 | add_action( 'graphql_register_types', [ $this, 'register_social_links_type' ] );
43 |
44 | // Register Footer Type and Field.
45 | add_action( 'graphql_register_types', [ $this, 'register_footer_type' ] );
46 | add_action( 'graphql_register_types', [ $this, 'register_footer_field' ] );
47 |
48 | }
49 |
50 | /**
51 | * Register header type.
52 | */
53 | public function register_header_type() {
54 | register_graphql_object_type(
55 | 'HCMSHeader',
56 | [
57 | 'description' => __( 'Header Type', 'headless-cms' ),
58 | 'fields' => [
59 | 'siteLogoUrl' => [
60 | 'type' => 'String',
61 | 'description' => __( 'Site logo URL', 'headless-cms' ),
62 | ],
63 | 'siteTitle' => [
64 | 'type' => 'String',
65 | 'description' => __( 'Site title', 'headless-cms' ),
66 | ],
67 | 'siteTagLine' => [
68 | 'type' => 'String',
69 | 'description' => __( 'Site tagline', 'headless-cms' ),
70 | ],
71 | 'favicon' => [
72 | 'type' => 'String',
73 | 'description' => __( 'favicon', 'headless-cms' ),
74 | ],
75 | ],
76 | ]
77 | );
78 | }
79 |
80 | /**
81 | * Register header field
82 | */
83 | public function register_header_field() {
84 |
85 | register_graphql_field(
86 | 'RootQuery',
87 | 'getHeader',
88 | [
89 | 'description' => __( 'Get header', 'headless-cms' ),
90 | 'type' => 'HCMSHeader',
91 | 'resolve' => function () {
92 |
93 | /**
94 | * Here you need to return data that matches the shape of the "HCMSHeader" type. You could get
95 | * the data from the WP Database, an external API, or static values.
96 | * For example in this case we are getting it from WordPress database.
97 | */
98 | return [
99 | 'siteLogoUrl' => $this->get_logo_url( 'custom_logo' ),
100 | 'siteTitle' => get_bloginfo( 'title' ),
101 | 'siteTagLine' => get_bloginfo( 'description' ),
102 | 'favicon' => get_site_icon_url(),
103 | ];
104 |
105 | },
106 | ]
107 | );
108 |
109 | }
110 |
111 | /**
112 | * Register footer type.
113 | */
114 | public function register_footer_type() {
115 | register_graphql_object_type(
116 | 'HCMSFooter',
117 | [
118 | 'description' => __( 'Header Type', 'headless-cms' ),
119 | 'fields' => [
120 | 'copyrightText' => [
121 | 'type' => 'String',
122 | 'description' => __( 'Copyright text', 'headless-cms' ),
123 | ],
124 | 'socialLinks' => [
125 | 'type' => [ 'list_of' => 'HCMSSocialLinks' ],
126 | 'description' => __( 'Social links', 'headless-cms' ),
127 | ],
128 | 'sidebarOne' => [
129 | 'type' => 'String',
130 | 'description' => __( 'sidebarOne', 'headless-cms' ),
131 | ],
132 | 'sidebarTwo' => [
133 | 'type' => 'String',
134 | 'description' => __( 'sidebarTwo', 'headless-cms' ),
135 | ],
136 | ],
137 | ]
138 | );
139 | }
140 |
141 | /**
142 | * Register footer field
143 | */
144 | public function register_footer_field() {
145 |
146 | register_graphql_field(
147 | 'RootQuery',
148 | 'getFooter',
149 | [
150 | 'description' => __( 'Get footer', 'headless-cms' ),
151 | 'type' => 'HCMSFooter',
152 | 'resolve' => function () {
153 |
154 | /**
155 | * Here you need to return data that matches the shape of the "HCMSFooter" type. You could get
156 | * the data from the WP Database, an external API, or static values.
157 | * For example in this case we are getting it from WordPress database.
158 | */
159 | return [
160 | 'copyrightText' => $this->get_copyright_text(),
161 | 'socialLinks' => $this->get_social_icons(),
162 | 'sidebarOne' => $this->get_sidebar( 'hcms-footer-sidebar-1' ),
163 | 'sidebarTwo' => $this->get_sidebar( 'hcms-footer-sidebar-2' ),
164 | ];
165 |
166 | },
167 | ]
168 | );
169 |
170 | }
171 |
172 | /**
173 | * Register social links field
174 | */
175 | public function register_social_links_type() {
176 | register_graphql_object_type(
177 | 'HCMSSocialLinks',
178 | [
179 | 'description' => __( 'Social Links Type', 'headless-cms' ),
180 | 'fields' => [
181 | 'iconName' => [
182 | 'type' => 'String',
183 | 'description' => __( 'Icon name', 'headless-cms' ),
184 | ],
185 | 'iconUrl' => [
186 | 'type' => 'String',
187 | 'description' => __( 'Icon url', 'headless-cms' ),
188 | ],
189 | ],
190 | ]
191 | );
192 | }
193 |
194 | /**
195 | * Get logo URL.
196 | *
197 | * @param string $key Key.
198 | *
199 | * @return string Image.
200 | */
201 | public function get_logo_url( $key ) {
202 |
203 | $custom_logo_id = get_theme_mod( $key );
204 | $image = wp_get_attachment_image_src( $custom_logo_id, 'full' );
205 |
206 | return $image[0];
207 | }
208 |
209 | /**
210 | * Get social icons
211 | *
212 | * @return array $social_icons
213 | */
214 | public function get_social_icons() {
215 |
216 | $social_icons = [];
217 | $social_icons_name = [ 'facebook', 'twitter', 'instagram', 'youtube' ];
218 |
219 | foreach ( $social_icons_name as $social_icon_name ) {
220 |
221 | $social_link = get_theme_mod( sprintf( 'rae_%s_link', $social_icon_name ) );
222 |
223 | if ( $social_link ) {
224 | array_push(
225 | $social_icons,
226 | [
227 | 'iconName' => esc_attr( $social_icon_name ),
228 | 'iconUrl' => esc_url( $social_link ),
229 | ]
230 | );
231 | }
232 | }
233 |
234 | return $social_icons;
235 |
236 | }
237 |
238 | /**
239 | * Get copyright text
240 | *
241 | * @return mixed
242 | */
243 | public function get_copyright_text() {
244 |
245 | $copy_right_text = get_theme_mod( 'rae_footer_text' );
246 |
247 | return $copy_right_text ? $copy_right_text : '';
248 | }
249 |
250 | /**
251 | * Returns the content of all the sidebars with given sidebar id.
252 | *
253 | * @param string $sidebar_id Sidebar id.
254 | *
255 | * @return false|string
256 | */
257 | public function get_sidebar( $sidebar_id ) {
258 | ob_start();
259 |
260 | dynamic_sidebar( $sidebar_id );
261 | $output = ob_get_contents();
262 |
263 | ob_end_clean();
264 |
265 | return $output;
266 | }
267 |
268 | }
269 |
--------------------------------------------------------------------------------
/inc/classes/queries/class-post-schema.php:
--------------------------------------------------------------------------------
1 | setup_hooks();
26 | }
27 |
28 | /**
29 | * To setup action/filter.
30 | *
31 | * @return void
32 | */
33 | protected function setup_hooks() {
34 |
35 | /**
36 | * Actions
37 | */
38 | add_action( 'graphql_register_types', [ $this, 'register_graphql_fields' ] );
39 |
40 | }
41 |
42 | /**
43 | * Register field.
44 | */
45 | public function register_graphql_fields() {
46 |
47 | // coAuthors
48 | register_graphql_field(
49 | 'Post',
50 | 'coAuthors',
51 | [
52 | 'type' => 'String',
53 | 'description' => __( 'Co Authors', 'headless-cms' ),
54 | 'resolve' => function ($post) {
55 | return function_exists( 'get_coauthors' ) ? wp_json_encode( get_coauthors( $post->ID ) ) : '';
56 | },
57 | ]
58 | );
59 |
60 | // Register bodyClasses type for Posts.
61 | register_graphql_field(
62 | 'Post',
63 | 'bodyClasses',
64 | [
65 | 'type' => 'String',
66 | 'description' => __( 'bodyClasses', 'headless-cms' ),
67 | 'resolve' => function ($post) {
68 | return $this->get_body_classes($post);
69 | },
70 | ]
71 | );
72 |
73 | // Register bodyClasses type for Page.
74 | register_graphql_field(
75 | 'Page',
76 | 'bodyClasses',
77 | [
78 | 'type' => 'String',
79 | 'description' => __( 'bodyClasses', 'headless-cms' ),
80 | 'resolve' => function ($post) {
81 | return $this->get_body_classes($post);
82 | },
83 | ]
84 | );
85 | }
86 |
87 | /**
88 | * Get body classes including elementor classes.
89 | *
90 | * @param Object $post Post.
91 | *
92 | * @return string Body classes.
93 | */
94 | public function get_body_classes( $post ) {
95 | $body_classes = array_merge( [], get_body_class() );
96 | $body_classes = implode( ' ', $body_classes );
97 | $elementor_kit_post = get_page_by_title('Default Kit', OBJECT, 'elementor_library');
98 | $elementor_kit_post_id = ! empty( $elementor_kit_post ) ? $elementor_kit_post->ID : '';
99 | $body_classes = $body_classes . sprintf( ' elementor-default elementor-kit-%1$s elementor-page elementor-page-%2$s', $elementor_kit_post_id, $post->ID );
100 | return $body_classes;
101 | }
102 |
103 | }
104 |
105 |
--------------------------------------------------------------------------------
/inc/classes/queries/class-product.php:
--------------------------------------------------------------------------------
1 | setup_hooks();
24 | }
25 |
26 | /**
27 | * To setup action/filter.
28 | *
29 | * @return void
30 | */
31 | protected function setup_hooks() {
32 |
33 | /**
34 | * Action
35 | */
36 | // Register Product Fields.
37 | add_action( 'graphql_register_types', [ $this, 'register_product_fields' ] );
38 | }
39 |
40 | /**
41 | * Register field.
42 | */
43 | function register_product_fields() {
44 | if ( !class_exists('WooCommerce') ) {
45 | return;
46 | }
47 | register_graphql_field(
48 | 'Product',
49 | 'productCurrency',
50 | [
51 | 'description' => __( 'Product Currency', 'headless-cms' ),
52 | 'type' => 'String',
53 | 'resolve' => function () {
54 | return get_woocommerce_currency();
55 | },
56 | ]
57 | );
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/inc/classes/queries/class-register-countries.php:
--------------------------------------------------------------------------------
1 | setup_hooks();
24 | }
25 |
26 | /**
27 | * To setup action/filter.
28 | *
29 | * @return void
30 | */
31 | protected function setup_hooks() {
32 |
33 | /**
34 | * Action
35 | */
36 |
37 | // Register Countries Field.
38 | add_action( 'graphql_register_types', [ $this, 'register_countries_fields' ] );
39 |
40 | }
41 |
42 | /**
43 | * Register field.
44 | */
45 | public function register_countries_fields() {
46 |
47 | register_graphql_object_type( 'WooCountry', [
48 | 'fields' => [
49 | 'countryCode' => [ 'type' => 'String' ],
50 | 'countryName' => [ 'type' => 'String' ],
51 | ]
52 | ] );
53 |
54 | register_graphql_object_type( 'WooCountries', [
55 | 'description' => __( 'Countries Type', 'headless-cms' ),
56 | 'fields' => [
57 | 'billingCountries' => [
58 | 'type' => [
59 | 'list_of' => 'WooCountry'
60 | ]
61 | ],
62 | 'shippingCountries' => [
63 | 'type' => [
64 | 'list_of' => 'WooCountry'
65 | ]
66 | ],
67 | ],
68 | ] );
69 |
70 | register_graphql_field(
71 | 'RootQuery',
72 | 'wooCountries',
73 | [
74 | 'description' => __( 'Countries', 'headless-cms' ),
75 | 'type' => 'WooCountries',
76 | 'resolve' => function () {
77 |
78 | // All countries for billing.
79 | $all_countries = class_exists( 'WooCommerce' ) ? WC()->countries : [];
80 | $billing_countries = ! empty( $all_countries->countries ) ? $all_countries->countries : [];
81 | $billing_countries = $this->get_formatted_countries( $billing_countries );
82 |
83 | // All countries with states for shipping.
84 | $shipping_countries = class_exists( 'WooCommerce' ) ? WC()->countries->get_shipping_countries() : [];;
85 | $shipping_countries = ! empty( $shipping_countries ) ? $shipping_countries : [];
86 | $shipping_countries = $this->get_formatted_countries( $shipping_countries );
87 |
88 | /**
89 | * Here you need to return data that matches the shape of the "WooCountries" type. You could get
90 | * the data from the WP Database, an external API, or static values.
91 | * For example in this case we are getting it from WordPress database.
92 | */
93 | return [
94 | 'billingCountries' => $billing_countries,
95 | 'shippingCountries' => $shipping_countries,
96 | ];
97 |
98 | },
99 | ]
100 | );
101 | }
102 |
103 | public function get_formatted_countries( $countries ) {
104 |
105 | $formatted_countries = [];
106 |
107 | if ( empty( $countries ) && !is_array( $countries ) ) {
108 | return $formatted_countries;
109 | }
110 |
111 | foreach ( $countries as $countryCode => $countryName ) {
112 | array_push( $formatted_countries, [
113 | 'countryCode' => $countryCode,
114 | 'countryName' => $countryName,
115 | ] );
116 | }
117 |
118 | return $formatted_countries;
119 | }
120 |
121 | }
122 |
--------------------------------------------------------------------------------
/inc/classes/queries/class-register-shipping.php:
--------------------------------------------------------------------------------
1 | setup_hooks();
24 | }
25 |
26 | /**
27 | * To setup action/filter.
28 | *
29 | * @return void
30 | */
31 | protected function setup_hooks() {
32 |
33 | /**
34 | * Action
35 | */
36 |
37 | // Register Shipping Zones fields.
38 | add_action( 'graphql_register_types', [ $this, 'register_shipping_zones_fields' ] );
39 |
40 | }
41 |
42 | /**
43 | * Register field.
44 | */
45 | function register_shipping_zones_fields() {
46 |
47 | register_graphql_object_type( 'ShippingInfo', [
48 | 'description' => __( 'Shipping Zones Type', 'headless-cms' ),
49 | 'fields' => [
50 | 'shippingZones' => [ 'type' => 'String' ],
51 | 'storePostCode' => [ 'type' => 'Integer' ],
52 | ]
53 | ] );
54 |
55 | register_graphql_field(
56 | 'RootQuery',
57 | 'shippingInfo',
58 | [
59 | 'description' => __( 'Shipping Zones', 'headless-cms' ),
60 | 'type' => 'ShippingInfo',
61 | 'resolve' => function () {
62 |
63 | $zone_locations = $this->get_zone_locations();
64 | $store_post_code = class_exists('WooCommerce') ? WC()->countries->get_base_postcode() : 0;
65 |
66 | /**
67 | * Here you need to return data that matches the shape of the "ShippingInfo" type. You could get
68 | * the data from the WP Database, an external API, or static values.
69 | * For example in this case we are getting it from WordPress database.
70 | */
71 | return [
72 | 'shippingZones' => wp_json_encode($zone_locations),
73 | 'storePostCode' => intval( $store_post_code ),
74 | ];
75 |
76 | },
77 | ]
78 | );
79 | }
80 |
81 | public function get_store_address() {
82 | $store_address = '';
83 | if( class_exists( 'WC_Countries' ) ) {
84 | $store_address = get_option( 'woocommerce_store_address' );
85 | }
86 |
87 | return $store_address;
88 | }
89 |
90 | /**
91 | * Get Zone locations
92 | *
93 | * @return array $zone_locations Zone locations.
94 | */
95 | public function get_zone_locations() {
96 | $zone_locations = [];
97 |
98 | if( class_exists( 'WC_Shipping_Zones' ) ) {
99 | $all_zones = \WC_Shipping_Zones::get_zones();
100 | if ( ! empty( $all_zones ) && is_array( $all_zones ) ) {
101 | foreach ((array) $all_zones as $key => $the_zone ) {
102 |
103 | $zone_info = [
104 | 'zone_name' => $the_zone['zone_name'],
105 | 'country_names' => $the_zone['formatted_zone_location'],
106 | 'zone_location_codes' => $the_zone['zone_locations'],
107 | 'shipping_methods' => $this->get_shipping_methods( $the_zone['shipping_methods'] ),
108 | ];
109 | array_push($zone_locations, $zone_info);
110 |
111 | }
112 | }
113 | }
114 |
115 | return $zone_locations;
116 | }
117 |
118 | /**
119 | * Get Shipping methods.
120 | *
121 | * @param array $shipping_methods_data Shipping method data.
122 | *
123 | * @return array Shipping methods.
124 | */
125 | public function get_shipping_methods($shipping_methods_data) {
126 |
127 | $shipping_methods = [];
128 |
129 | if ( empty( $shipping_methods_data ) || !is_array( $shipping_methods_data ) ) {
130 | return $shipping_methods;
131 | }
132 |
133 | foreach ((array) $shipping_methods_data as $key => $shipping_method ) {
134 | array_push($shipping_methods, [
135 | 'method_title' => ! empty($shipping_method->instance_settings['title']) ? $shipping_method->instance_settings['title'] : '',
136 | ]);
137 | }
138 |
139 | return $shipping_methods;
140 | }
141 |
142 | }
143 |
--------------------------------------------------------------------------------
/inc/classes/queries/class-register-states.php:
--------------------------------------------------------------------------------
1 | setup_hooks();
24 | }
25 |
26 | /**
27 | * To setup action/filter.
28 | *
29 | * @return void
30 | */
31 | protected function setup_hooks() {
32 |
33 | /**
34 | * Action
35 | */
36 |
37 | // Register States Field.
38 | add_action( 'graphql_register_types', [ $this, 'register_states_fields' ] );
39 |
40 | }
41 |
42 | /**
43 | * Register field.
44 | */
45 | function register_states_fields() {
46 |
47 | register_graphql_object_type( 'WooState', [
48 | 'fields' => [
49 | 'stateCode' => [ 'type' => 'String' ],
50 | 'stateName' => [ 'type' => 'String' ],
51 | ],
52 | ] );
53 |
54 | register_graphql_object_type( 'WooStates', [
55 | 'description' => __( 'States Type', 'headless-cms' ),
56 | 'fields' => [
57 | 'states' => [
58 | 'type' => [
59 | 'list_of' => 'WooState'
60 | ]
61 | ],
62 | ]
63 | ] );
64 |
65 | register_graphql_field(
66 | 'RootQuery',
67 | 'wooStates',
68 | [
69 | 'description' => __( 'States', 'headless-cms' ),
70 | 'type' => 'WooStates',
71 | 'args' => [
72 | 'countryCode' => [
73 | 'type' => 'String',
74 | ],
75 | ],
76 | 'resolve' => function ( $source, $args, $context, $info ) {
77 | $states = [];
78 |
79 | if ( ! class_exists( 'WooCommerce' ) ) {
80 | return $states;
81 | }
82 |
83 | $states = isset( $args['countryCode'] ) && ! empty( $args['countryCode'] ) ? WC()->countries->get_states( strtoupper($args['countryCode']) ) : [];
84 | $states = $this->get_formatted_states( $states );
85 |
86 | /**
87 | * Here you need to return data that matches the shape of the "WooStates" type. You could get
88 | * the data from the WP Database, an external API, or static values.
89 | * For example in this case we are getting it from WordPress database.
90 | */
91 | return [
92 | 'states' => $states,
93 | ];
94 |
95 | },
96 | ]
97 | );
98 | }
99 |
100 | public function get_formatted_states( $states ) {
101 |
102 | $formatted_states = [];
103 |
104 | if ( empty( $states ) && !is_array( $states ) ) {
105 | return $formatted_states;
106 | }
107 |
108 | foreach ( $states as $stateCode => $stateName ) {
109 | array_push( $formatted_states, [
110 | 'stateCode' => $stateCode,
111 | 'stateName' => $stateName,
112 | ] );
113 | }
114 |
115 | return $formatted_states;
116 | }
117 |
118 | }
119 |
--------------------------------------------------------------------------------
/inc/classes/queries/class-seo.php:
--------------------------------------------------------------------------------
1 | setup_hooks();
26 | }
27 |
28 | /**
29 | * To setup action/filter.
30 | *
31 | * @return void
32 | */
33 | protected function setup_hooks() {
34 |
35 | /**
36 | * Actions
37 | */
38 | add_action( 'graphql_register_types', [ $this, 'register_graphql_fields' ] );
39 |
40 | }
41 |
42 | /**
43 | * Register field.
44 | */
45 | public function register_graphql_fields() {
46 |
47 | // Register type 'schemaDetails'.
48 | register_graphql_field(
49 | 'PostTypeSEO',
50 | 'schemaDetails',
51 | [
52 | 'type' => 'String',
53 | 'description' => esc_html__( 'Yoast SEO Schema', 'headless-cms' ),
54 | 'resolve' => function( $root, $args, $context, $info ) { // phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
55 |
56 | $post_id = get_the_ID(); // Current post or page id.
57 | $post_type = get_post_type( $post_id ); // Current post type.
58 | $yoast_meta = \YoastSEO()->meta;
59 |
60 | if ( is_home() || is_front_page() ) {
61 | // Get schema for home page.
62 | $output = $yoast_meta->for_home_page()->schema;
63 | } elseif ( 'post' === $post_type ) {
64 | // Get schema for post. Only post type 'post'.
65 | $output = $yoast_meta->for_post( $post_id )->schema;
66 | } else {
67 | // Get schema for all other page or post.
68 | $output = $yoast_meta->for_current_page()->schema;
69 | }
70 |
71 | if ( ! empty( $output ) ) {
72 | $output = wp_json_encode( $output, JSON_UNESCAPED_SLASHES );
73 | $output = $this->replace_backend_url( $output );
74 | }
75 |
76 | return $output;
77 | },
78 | ]
79 | );
80 | }
81 |
82 | /**
83 | * Function to replace backend URL with frontend.
84 | *
85 | * @param string $output String to replace backend URL.
86 | *
87 | * @return string
88 | */
89 | public function replace_backend_url( $output ) {
90 |
91 | $plugin_options = get_option('hcms_plugin_options');
92 | $frontend_url = is_array( $plugin_options ) && ! empty( $plugin_options['frontend_site_url'] ) ? esc_url( $plugin_options['frontend_site_url'] ) : '';
93 |
94 | if ( ! empty( $frontend_url ) ) {
95 | $frontend_url = untrailingslashit( $frontend_url );
96 | $output = str_replace( home_url(), $frontend_url, $output );
97 | }
98 |
99 | return $output;
100 |
101 | }
102 |
103 | }
104 |
105 |
--------------------------------------------------------------------------------
/inc/classes/queries/class-sticky-post.php:
--------------------------------------------------------------------------------
1 | setup_hooks();
24 | }
25 |
26 | /**
27 | * To setup action/filter.
28 | *
29 | * @return void
30 | */
31 | protected function setup_hooks() {
32 |
33 | /**
34 | * Actions
35 | */
36 | add_action( 'graphql_register_types', [ $this, 'add_sticky_where_argument' ] );
37 |
38 | /**
39 | * Filters
40 | */
41 | add_filter( 'graphql_post_object_connection_query_args', [ $this, 'add_sticky_argument_condition' ], 10, 5 );
42 |
43 | }
44 |
45 | /**
46 | * Function to register WPGraphql field.
47 | *
48 | * @param TypeRegistry $type_registry Instance of the TypeRegistry
49 | *
50 | * @return void
51 | */
52 | public function add_sticky_where_argument( $type_registry ) {
53 |
54 | register_graphql_field(
55 | 'RootQueryToPostConnectionWhereArgs',
56 | 'onlySticky',
57 | [
58 | 'type' => 'boolean',
59 | 'description' => esc_html__( 'The ID of the post object to filter by', 'headless-cms' ),
60 | ]
61 | );
62 |
63 | }
64 |
65 | /**
66 | * Function to add custom argument condition in WP_Query args.
67 | *
68 | * @param array $query_args Query arguments.
69 | * @param mixed $source The source that's passed down the GraphQL queries
70 | * @param array $args The inputArgs on the field
71 | * @param AppContext $context The AppContext passed down the GraphQL tree
72 | * @param ResolveInfo $info The ResolveInfo passed down the GraphQL tree
73 | *
74 | * @return array
75 | */
76 | public function add_sticky_argument_condition( $query_args, $source, $args, $context, $info ) {
77 |
78 | // Add condition if onlySticky argument is set.
79 | if (
80 | ! empty( $args['where'] ) &&
81 | isset( $args['where']['onlySticky'] )
82 | ) {
83 |
84 | if ( true === $args['where']['onlySticky'] ) {
85 | $query_args['post__in'] = get_option( 'sticky_posts', [] );
86 | } else {
87 | $query_args['post__not_in'] = get_option( 'sticky_posts', [] );
88 | }
89 | }
90 |
91 | return $query_args;
92 |
93 | }
94 |
95 |
96 | }
97 |
--------------------------------------------------------------------------------
/inc/helpers/autoloader.php:
--------------------------------------------------------------------------------
1 | esc_html__( 'HCMS Header Menu', 'headless-cms' ),
44 | 'hcms-menu-footer' => esc_html__( 'HCMS Footer Menu', 'headless-cms' ),
45 | ]
46 | );
47 | }
48 | add_action( 'init', 'hcms_custom_new_menu' );
49 |
50 | /**
51 | * Register Sidebar
52 | */
53 |
54 | /**
55 | * Register widget areas.
56 | *
57 | * @link https://developer.wordpress.org/themes/functionality/sidebars/#registering-a-sidebar
58 | */
59 | function hcms_sidebar_registration() {
60 |
61 | // Arguments used in all register_sidebar() calls.
62 | $shared_args = [
63 | 'before_title' => '',
65 | 'before_widget' => '',
67 | ];
68 |
69 | // Footer #1.
70 | register_sidebar(
71 | array_merge(
72 | $shared_args,
73 | [
74 | 'name' => __( 'HCMS Footer #1', 'headless-cms' ),
75 | 'id' => 'hcms-footer-sidebar-1',
76 | 'description' => __( 'Widgets in this area will be displayed in the first column in the footer.', 'headless-cms' ),
77 | ]
78 | )
79 | );
80 |
81 | // Footer #2.
82 | register_sidebar(
83 | array_merge(
84 | $shared_args,
85 | [
86 | 'name' => __( 'HCMS Footer #2', 'headless-cms' ),
87 | 'id' => 'hcms-footer-sidebar-2',
88 | 'description' => __( 'Widgets in this area will be displayed in the second column in the footer.', 'headless-cms' ),
89 | ]
90 | )
91 | );
92 |
93 | }
94 |
95 | add_action( 'widgets_init', 'hcms_sidebar_registration' );
96 |
97 | /**
98 | * Add theme supports
99 | */
100 | function hcms_theme_support() {
101 |
102 | if ( function_exists( 'add_theme_support' ) ) {
103 | /*
104 | * Enable support for Post Thumbnails on posts and pages.
105 | *
106 | * @link https://developer.wordpress.org/themes/functionality/featured-images-post-thumbnails/
107 | */
108 | add_theme_support( 'post-thumbnails' );
109 |
110 | // Set post thumbnail size.
111 | set_post_thumbnail_size( 1200, 9999 );
112 |
113 | // Add support for full and wide align images.
114 | add_theme_support( 'align-wide' );
115 | }
116 |
117 | }
118 |
119 | add_action( 'after_setup_theme', 'hcms_theme_support' );
120 |
121 | /**
122 | * Back to React Theme's home page.
123 | */
124 | function hcms_back_to_home_button() {
125 |
126 | $frontend_site_url = ! empty( $option_val_array['frontend_site_url'] ) ? $option_val_array['frontend_site_url'] : 'https://gatsby-woocommerce-theme.netlify.app';
127 |
128 | printf(
129 | '%2$s',
130 | esc_url( $frontend_site_url ),
131 | __('Back to Home', 'headless-cms')
132 | );
133 |
134 | }
135 |
136 | add_action( 'woocommerce_order_details_after_order_table', 'hcms_back_to_home_button', 10 );
137 |
138 | add_filter( 'graphql_jwt_auth_secret_key', function() {
139 | $plugin_options = get_option( 'hcms_plugin_options' );
140 | if ( ! is_array($plugin_options) && empty( $plugin_options['jwt_secret'] ) ) {
141 | return '';
142 | }
143 |
144 | return $plugin_options['jwt_secret'];
145 | });
146 |
--------------------------------------------------------------------------------
/inc/traits/trait-singleton.php:
--------------------------------------------------------------------------------
1 | value pair for each `classname => instance` in self::$_instance
70 | * for each sub-class.
71 | */
72 | $called_class = get_called_class();
73 |
74 | if ( ! isset( $instance[ $called_class ] ) ) {
75 |
76 | $instance[ $called_class ] = new $called_class();
77 |
78 | /**
79 | * Dependent items can use the headless_cms_features_singleton_init_{$called_class} hook to execute code
80 | */
81 | do_action( sprintf( 'headless_cms_features_singleton_init_%s', $called_class ) ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
82 |
83 | }
84 |
85 | return $instance[ $called_class ];
86 |
87 | }
88 |
89 | } // End trait
90 |
--------------------------------------------------------------------------------
/phpcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | WordPress and VIP Go Coding Standards
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | */node_modules/*
24 | */vendor/*
25 | .github/
26 |
27 |
28 |
--------------------------------------------------------------------------------
/readme.txt:
--------------------------------------------------------------------------------
1 | === Headless CMS ===
2 | Contributors: gsayed786
3 | Tags: headless-cms, decoupled, graphql
4 | Requires at least: 4.6
5 | Tested up to: 5.4.2
6 | Stable tag: 4.9.2
7 | Requires PHP: 5.2.4
8 | License: GPLv2 or later
9 | License URI: https://www.gnu.org/licenses/gpl-2.0.html
10 |
11 | A WordPress plugin that adds features to use WordPress as a headless CMS with any front-end environment using REST API.
12 |
13 | == Description ==
14 |
15 | A WordPress plugin that adds following features to use WordPress as a headless CMS with any front-end environment using REST API
16 | This plugin provides multiple features and you can use the one's that is relevant to your front-end application. You don't necessarily need to use all.
17 |
18 | == Features ==
19 |
20 | 1. Custom REST API Endpoints.
21 | 2. Social links in customizer.
22 | 3. Image uploads for categories.
23 | 4. Custom header and footer menus.
24 | 5. Custom Widgets.
25 | 6. Custom Header and Footer GraphQL fields when using [wp-graphql](https://github.com/wp-graphql/wp-graphql) plugin
26 |
27 | == Feature Details ==
28 |
29 | ## Features
30 | * Adds option to add social links in customizer
31 | * Registers two custom menus for header ( menu location = hcms-menu-header ) and for footer ( menu location = hcms-menu-footer )
32 | * Registers the following sidebars
33 | 1. HCMS Footer #1 with sidebar id 'hcms-sidebar-1'
34 | 2. HCMS Footer #2 with sidebar id 'hcms-sidebar-2'
35 |
36 | == Available Custom REST API endpoints ==
37 | 1. Get single post ( GET request ): `http://example.com/wp-json/rae/v1/post?post_id=1`
38 |
39 | 2. Get posts by page no: ( GET Request ) : `http://example.com/wp-json/rae/v1/posts?page_no=1`
40 |
41 | 3. Get header and footer date: ( GET Request )
42 | * Get the header data ( site title, site description , site logo URL, menu items ) and footer data ( footer menu items, social icons )
43 | * `http://example.com/wp-json/rae/v1/header-footer?header_location_id=hcms-menu-header&footer_location_id=hcms-menu-footer``
44 |
45 | 4. Get posts by page no: ( GET Request )
46 | * Get the posts by taxonomy
47 | * `http://example.com/wp-json/rae/v1/posts-by-tax?post_type=post&taxonomy=category&slug=xyz`
48 |
49 | == More Features ==
50 | 1. Registers the sections for socials icons in the customizer
51 |
52 | * Social icons urls for 'facebook', 'twitter', 'instagram', 'youtube'
53 |
54 | 2. Image upload features for categories
55 |
56 | * Provides Image upload features for categories.
57 |
58 | 3. Plugin Settings Page
59 |
60 | * Settings for getting data for a custom page like Hero section, Search section, Featured post section, latest posts heading.
61 |
62 | == Installation and Use ==
63 |
64 | This section describes how to install the plugin and get it working.
65 |
66 | 1. Upload the plugin files to the `/wp-content/plugins/plugin-name` directory, or install the plugin through the WordPress plugins screen directly.
67 | 2. Activate the plugin through the 'Plugins' screen in WordPress
68 | 3. Your can add social icons from customizer
69 | 4. You can set up custom header and footer menus.
70 | 5. You can add image to categories.
71 |
72 | == Demo of the Frontend applications that can be used with this plugin ==
73 |
74 | Please check the demo of an example React front-end application, where this plugin can be used.
75 |
76 | [2020-07-02] Demo.
77 |
78 | [youtube https://youtu.be/nYXL1KKjKrc]
79 |
80 | = Its not working.
81 |
82 | Step 1. Check if your Plugin is activated.
83 | Step 2. Deactivate all plugins and reactivate headless-cms.
84 |
85 | == Screenshots ==
86 |
87 | 1-Plugin Settings. screenshot-1.png
88 | 2-GraphQL Fields. screenshot-2.png
89 | 3-Category Image Upload. screenshot-3.png
90 | 4-Custom Header Menu. screenshot-4.png
91 | 5-Custom Footer Menu. screenshot-5.png
92 |
--------------------------------------------------------------------------------
/templates/category-img-form.php:
--------------------------------------------------------------------------------
1 |
9 |
10 |
19 |
--------------------------------------------------------------------------------
/templates/comments-section.php:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/templates/featured-post-section.php:
--------------------------------------------------------------------------------
1 | 'post',
19 | 'post_status' => 'publish',
20 | 'orderby' => 'date',
21 | 'update_post_meta_cache' => false,
22 | 'update_post_term_cache' => false,
23 |
24 | ];
25 |
26 | $latest_posts_data = new WP_Query( $args );
27 | $latest_posts = ! empty( $latest_posts_data->posts ) ? $latest_posts_data->posts : [];
28 | ?>
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
59 |
60 |
61 |
62 |
63 |
79 |
80 |
81 |
82 |
83 |
99 |
100 |
101 |
102 |
103 |
--------------------------------------------------------------------------------
/templates/frontend-site-details-section.php:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
http://localhost:3000
21 |
https://example.com
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/templates/hero-section.php:
--------------------------------------------------------------------------------
1 |
19 |
20 |
21 |
22 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/templates/latest-posts-section.php:
--------------------------------------------------------------------------------
1 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/templates/post-preview-section.php:
--------------------------------------------------------------------------------
1 | k|7iFfGuH+0#oPTLXiG@8r-';
14 | ?>
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | />
25 |
26 |
27 |
28 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/templates/search-section.php:
--------------------------------------------------------------------------------
1 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
; ?>)
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/templates/settings-form-template.php:
--------------------------------------------------------------------------------
1 |
9 |
10 |
78 |
--------------------------------------------------------------------------------