diff --git a/.travis.yml b/.travis.yml index 1627d88a8..0c32487f2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -75,6 +75,7 @@ before_script: docker-compose build \ --build-arg DESIRED_PHP_VERSION=${PHP_VERSION} \ --build-arg DESIRED_WP_VERSION=${WP_VERSION} \ + --build-arg USE_XDEBUG=${USE_XDEBUG} \ testing fi # Install PHP CodeSniffer and WPCS. diff --git a/Dockerfile b/Dockerfile index 9318ecae9..332ef179b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,14 +17,16 @@ SHELL [ "/bin/bash", "-c" ] # Redeclare ARGs and set as environmental variables for reuse. ARG DESIRED_WP_VERSION ARG DESIRED_PHP_VERSION +ARG USE_XDEBUG ENV WP_VERSION=${DESIRED_WP_VERSION} ENV PHP_VERSION=${DESIRED_PHP_VERSION} +ENV USING_XDEBUG=${USE_XDEBUG} # Install php extensions RUN docker-php-ext-install pdo_mysql # Install PCOV and XDebug -RUN if [ "$PHP_VERSION" != "5.6" ] && [ "$PHP_VERSION" != "7.0" ]; then \ +RUN if [ "$PHP_VERSION" != "5.6" ] && [ "$PHP_VERSION" != "7.0" ] && [[ -z "$USING_XDEBUG" ]]; then \ apt-get install zip unzip -y && \ pecl install pcov && \ docker-php-ext-enable pcov && \ @@ -35,6 +37,11 @@ RUN if [ "$PHP_VERSION" != "5.6" ] && [ "$PHP_VERSION" != "7.0" ]; then \ && echo "zend_extension=$(find /usr/local/lib/php/extensions/ -name xdebug.so)" > /usr/local/etc/php/conf.d/xdebug.ini \ && echo "xdebug.remote_enable=on" >> /usr/local/etc/php/conf.d/xdebug.ini \ && echo "xdebug.remote_autostart=off" >> /usr/local/etc/php/conf.d/xdebug.ini; \ + elif [ "$PHP_VERSION" == "7.0" ]; then \ + yes | pecl install xdebug-2.6.1 \ + && echo "zend_extension=$(find /usr/local/lib/php/extensions/ -name xdebug.so)" > /usr/local/etc/php/conf.d/xdebug.ini \ + && echo "xdebug.remote_enable=on" >> /usr/local/etc/php/conf.d/xdebug.ini \ + && echo "xdebug.remote_autostart=off" >> /usr/local/etc/php/conf.d/xdebug.ini; \ else \ yes | pecl install xdebug \ && echo "zend_extension=$(find /usr/local/lib/php/extensions/ -name xdebug.so)" > /usr/local/etc/php/conf.d/xdebug.ini \ diff --git a/bin/testing-entrypoint.sh b/bin/testing-entrypoint.sh index 8a3fbccae..73b3edbf2 100644 --- a/bin/testing-entrypoint.sh +++ b/bin/testing-entrypoint.sh @@ -53,12 +53,12 @@ fi COMPOSER_MEMORY_LIMIT=-1 composer install --prefer-source --no-interaction # Install pcov/clobber if PHP7.1+ -if version_gt $PHP_VERSION 7.0 && [[ "$COVERAGE" == "1" ]]; then +if version_gt $PHP_VERSION 7.0 && [[ "$COVERAGE" == "1" ]] && [[ -z "$USING_XDEBUG" ]]; then echo "Installing pcov/clobber" COMPOSER_MEMORY_LIMIT=-1 composer require --dev pcov/clobber vendor/bin/pcov clobber elif [ "$COVERAGE" == "1" ]; then - echo "Sorry, there is no PCOV support for this PHP ${PHP_VERSION} at this time" + echo "Using XDebug for codecoverage" fi # Set output permission @@ -81,11 +81,22 @@ if [ -f "${TESTS_OUTPUT}/coverage.xml" ] && [[ "$COVERAGE" == "1" ]]; then sed -i "s~$pattern~~g" "$TESTS_OUTPUT"/coverage.xml # Remove pcov/clobber - if version_gt $PHP_VERSION 7.0 && [ "$SKIP_TESTS_CLEANUP" != "1" ]; then + if version_gt $PHP_VERSION 7.0 && [ "$SKIP_TESTS_CLEANUP" != "1" ] && [[ -z "$USING_XDEBUG" ]]; then echo 'Removing pcov/clobber.' vendor/bin/pcov unclobber COMPOSER_MEMORY_LIMIT=-1 composer remove --dev pcov/clobber fi + + if [ "$SKIP_TESTS_CLEANUP" != "1" ]; then + echo 'Changing composer configuration in container.' + composer config --global discard-changes true + + echo 'Removing devDependencies.' + composer install --no-dev -n + + echo 'Removing composer.lock' + rm composer.lock + fi fi # Set public test result files permissions. diff --git a/docker-compose.yml b/docker-compose.yml index 312f6242a..c96956927 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -41,8 +41,9 @@ services: context: . dockerfile: Dockerfile args: - DESIRED_PHP_VERSION: "${PHP_VERSION:-7.3}" - DESIRED_WP_VERSION: "${WP_VERSION:-5.2}" + - DESIRED_PHP_VERSION="${PHP_VERSION:-7.3}" + - DESIRED_WP_VERSION="${WP_VERSION:-5.2}" + - USE_XDEBUG image: woographql-testing volumes: - '.:/var/www/html/wp-content/plugins/wp-graphql-woocommerce' diff --git a/includes/class-type-registry.php b/includes/class-type-registry.php index f3efb24b1..060bf0e26 100644 --- a/includes/class-type-registry.php +++ b/includes/class-type-registry.php @@ -64,6 +64,7 @@ public function init( \WPGraphQL\Registry\TypeRegistry $type_registry ) { \WPGraphQL\WooCommerce\Type\WPObject\Product_Variation_Type::register(); \WPGraphQL\WooCommerce\Type\WPObject\Order_Type::register(); \WPGraphQL\WooCommerce\Type\WPObject\Order_Item_Type::register(); + \WPGraphQL\WooCommerce\Type\WPObject\Downloadable_Item_Type::register(); \WPGraphQL\WooCommerce\Type\WPObject\Refund_Type::register(); \WPGraphQL\WooCommerce\Type\WPObject\Product_Download_Type::register(); \WPGraphQL\WooCommerce\Type\WPObject\Customer_Type::register(); @@ -88,6 +89,7 @@ public function init( \WPGraphQL\Registry\TypeRegistry $type_registry ) { \WPGraphQL\WooCommerce\Connection\Products::register_connections(); \WPGraphQL\WooCommerce\Connection\Orders::register_connections(); \WPGraphQL\WooCommerce\Connection\Order_Items::register_connections(); + \WPGraphQL\WooCommerce\Connection\Downloadable_Items::register_connections(); \WPGraphQL\WooCommerce\Connection\Refunds::register_connections(); \WPGraphQL\WooCommerce\Connection\Product_Attributes::register_connections(); \WPGraphQL\WooCommerce\Connection\Variation_Attributes::register_connections(); diff --git a/includes/connection/class-downloadable-items.php b/includes/connection/class-downloadable-items.php new file mode 100644 index 000000000..32016b037 --- /dev/null +++ b/includes/connection/class-downloadable-items.php @@ -0,0 +1,70 @@ + 'Order', + 'toType' => 'DownloadableItem', + 'fromFieldName' => 'downloadableItems', + 'connectionArgs' => self::get_connection_args(), + 'resolve' => function ( $source, $args, $context, $info ) { + return Factory::resolve_downloadable_item_connection( $source, $args, $context, $info ); + }, + ); + return array_merge( $defaults, $args ); + } + + /** + * Returns array of where args + * + * @return array + */ + public static function get_connection_args() { + return array( + 'active' => array( + 'type' => 'Boolean', + 'description' => __( 'Limit results to downloadable items that can be downloaded now.', 'wp-graphql-woocommerce' ), + ), + 'expired' => array( + 'type' => 'Boolean', + 'description' => __( 'Limit results to downloadable items that are expired.', 'wp-graphql-woocommerce' ), + ), + 'hasDownloadsRemaining' => array( + 'type' => 'Boolean', + 'description' => __( 'Limit results to downloadable items that have downloads remaining.', 'wp-graphql-woocommerce' ), + ), + ); + } +} diff --git a/includes/data/class-factory.php b/includes/data/class-factory.php index f9aae14ad..636aac8db 100644 --- a/includes/data/class-factory.php +++ b/includes/data/class-factory.php @@ -27,6 +27,7 @@ use WPGraphQL\WooCommerce\Data\Connection\Shipping_Method_Connection_Resolver; use WPGraphQL\WooCommerce\Data\Connection\Cart_Item_Connection_Resolver; use WPGraphQL\WooCommerce\Data\Connection\Payment_Gateway_Connection_Resolver; +use WPGraphQL\WooCommerce\Data\Connection\Downloadable_Item_Connection_Resolver; use WPGraphQL\WooCommerce\Model\Order_Item; use WPGraphQL\WooCommerce\Model\Product; use WPGraphQL\WooCommerce\Model\Customer; @@ -491,6 +492,22 @@ public static function resolve_cart_item_connection( $source, array $args, AppCo return $resolver->get_connection(); } + /** + * Resolves DownloadableItem connections + * + * @param mixed $source - Data resolver for connection source. + * @param array $args - Connection arguments. + * @param AppContext $context - AppContext object. + * @param ResolveInfo $info - ResolveInfo object. + * + * @return array + * @access public + */ + public static function resolve_downloadable_item_connection( $source, array $args, AppContext $context, ResolveInfo $info ) { + $resolver = new Downloadable_Item_Connection_Resolver( $source, $args, $context, $info ); + return $resolver->get_connection(); + } + /** * Resolves PaymentGateway connections * diff --git a/includes/data/connection/class-downloadable-item-connection-resolver.php b/includes/data/connection/class-downloadable-item-connection-resolver.php new file mode 100644 index 000000000..242e125c8 --- /dev/null +++ b/includes/data/connection/class-downloadable-item-connection-resolver.php @@ -0,0 +1,183 @@ + array() ); + if ( ! empty( $this->args['where'] ) ) { + $where_args = $this->args['where']; + if ( isset( $where_args['active'] ) ) { + $active = $where_args['active']; + + $query_args['filters'][] = function( $downloadable_item ) use ( $active ) { + $is_expired = isset( $downloadable_item['access_expires'] ) + ? time() > $downloadable_item['access_expires']->getTimestamp() + : false; + $downloads_remaining = ( 'integer' === gettype( $downloadable_item['downloads_remaining'] ) ) + ? 0 < $downloadable_item['downloads_remaining'] + : true; + + return $active ? ( ! $is_expired && $downloads_remaining ) : ( $is_expired || ! $downloads_remaining ); + }; + } + + if ( isset( $where_args['expired'] ) ) { + $expired = $where_args['expired']; + + $query_args['filters'][] = function( $downloadable_item ) use ( $expired ) { + $is_expired = isset( $downloadable_item['access_expires'] ) + ? time() < $downloadable_item['access_expires']->getTimestamp() + : false; + + return $expired === $is_expired; + }; + } + + if ( isset( $where_args['hasDownloadsRemaining'] ) ) { + $has_downloads_remaining = $where_args['hasDownloadsRemaining']; + + $query_args['filters'][] = function( $downloadable_item ) use ( $has_downloads_remaining ) { + $downloads_remaining = ( 'integer' === gettype( $downloadable_item['downloads_remaining'] ) ) + ? 0 < $downloadable_item['downloads_remaining'] + : true; + + return $has_downloads_remaining === $downloads_remaining; + }; + } + } + + /** + * Filter the $query_args to allow folks to customize queries programmatically. + * + * @param array $query_args The args that will be passed to the WP_Query. + * @param mixed $source The source that's passed down the GraphQL queries. + * @param array $args The inputArgs on the field. + * @param AppContext $context The AppContext passed down the GraphQL tree. + * @param ResolveInfo $info The ResolveInfo passed down the GraphQL tree. + */ + $query_args = apply_filters( 'graphql_downloadable_item_connection_query_args', $query_args, $this->source, $this->args, $this->context, $this->info ); + + return $query_args; + } + + /** + * Executes query + * + * @return \WP_Query + */ + public function get_query() { + $items = $this->source->downloadable_items; + + if ( empty( $items ) ) { + return array(); + } + + if ( ! empty( $this->query_args['filters'] ) && is_array( $this->query_args['filters'] ) ) { + foreach ( $this->query_args['filters'] as $filter ) { + $items = array_filter( $items, $filter ); + } + } + + $cursor_key = $this->get_offset(); + $cursor_offset = array_search( $cursor_key, \array_column( $items, 'download_id' ), true ); + + if ( ! empty( $this->args['after'] ) ) { + $items = array_splice( $items, $cursor_offset + 1 ); + } elseif ( $cursor_offset ) { + $items = array_splice( $items, 0, $cursor_offset ); + } + + return $items; + } + + /** + * This returns the offset to be used in the $query_args based on the $args passed to the + * GraphQL query. + * + * @return int|mixed + */ + public function get_offset() { + $offset = null; + + // Get the offset. + if ( ! empty( $this->args['after'] ) ) { + $offset = $this->args['after']; + } elseif ( ! empty( $this->args['before'] ) ) { + $offset = $this->args['before']; + } + + /** + * Return the higher of the two values + */ + return $offset; + } + + /** + * Create cursor for downloadable item node. + * + * @param array $node Cart item. + * @param string $key Cart item key. + * + * @return string + */ + protected function get_cursor_for_node( $node, $key = null ) { + return $node['download_id']; + } + + /** + * Return an array of items from the query + * + * @return array + */ + public function get_items() { + return ! empty( $this->query ) ? $this->query : array(); + } + + /** + * Validates offset. + * + * @param integer $offset Post ID. + * + * @return bool + */ + public function is_valid_offset( $offset ) { + return 'string' === gettype( $offset ); + } +} diff --git a/includes/model/class-order.php b/includes/model/class-order.php index 2e3b41ee2..c52c96539 100644 --- a/includes/model/class-order.php +++ b/includes/model/class-order.php @@ -247,9 +247,6 @@ protected function init() { 'needsProcessing' => function() { return ! is_null( $this->data->needs_processing() ) ? $this->data->needs_processing() : null; }, - 'downloadableItems' => function() { - return ! empty( $this->data->get_downloadable_items() ) ? $this->data->get_downloadable_items() : null; - }, /** * Connection resolvers fields * @@ -262,6 +259,9 @@ protected function init() { 'parent_id' => function() { return ! empty( $this->data ) ? $this->data->get_parent_id() : null; }, + 'downloadable_items' => function() { + return ! empty( $this->data->get_downloadable_items() ) ? $this->data->get_downloadable_items() : null; + }, ); } diff --git a/includes/type/object/class-downloadable-item-type.php b/includes/type/object/class-downloadable-item-type.php new file mode 100644 index 000000000..0c722858b --- /dev/null +++ b/includes/type/object/class-downloadable-item-type.php @@ -0,0 +1,112 @@ + __( 'A downloadable item', 'wp-graphql-woocommerce' ), + 'fields' => array( + 'downloadId' => array( + 'type' => array( 'non_null' => 'String' ), + 'description' => __( 'Downloadable item unique identifier', 'wp-graphql-woocommerce' ), + 'resolve' => function ( $source ) { + return ! empty( $source['download_id'] ) ? $source['download_id'] : null; + }, + ), + 'url' => array( + 'type' => 'String', + 'description' => __( 'Download URL of the downloadable item.', 'wp-graphql-woocommerce' ), + 'resolve' => function ( $source ) { + return ! empty( $source['download_url'] ) ? $source['download_url'] : null; + }, + ), + 'name' => array( + 'type' => 'String', + 'description' => __( 'Name of the downloadable item.', 'wp-graphql-woocommerce' ), + 'resolve' => function ( $source ) { + return ! empty( $source['download_name'] ) ? $source['download_name'] : null; + }, + ), + 'downloadsRemaining' => array( + 'type' => 'Int', + 'description' => __( 'Number of times the item can be downloaded.', 'wp-graphql-woocommerce' ), + 'resolve' => function ( $source ) { + return isset( $source['downloads_remaining'] ) && 'integer' === gettype( $source['downloads_remaining'] ) + ? $source['downloads_remaining'] + : null; + }, + ), + 'accessExpires' => array( + 'type' => 'String', + 'description' => __( 'The date the downloadable item expires', 'wp-graphql-woocommerce' ), + 'resolve' => function ( $source ) { + return ! empty( $source['access_expires'] ) ? $source['access_expires'] : null; + }, + ), + 'product' => array( + 'type' => 'Product', + 'description' => __( 'Product of downloadable item.', 'wp-graphql-woocommerce' ), + 'resolve' => function ( $source, array $args, AppContext $context ) { + return Factory::resolve_crud_object( $source['product_id'], $context ); + }, + ), + 'download' => array( + 'type' => 'ProductDownload', + 'description' => __( 'ProductDownload of the downloadable item', 'wp-graphql-woocommerce' ), + 'resolve' => function ( $source ) { + $download_id = $source['download_id']; + $product_id = $source['product_id']; + $files = array_filter( (array) get_post_meta( $product_id, '_downloadable_files', true ) ); + + if ( empty( $download_id ) + || empty( $product_id ) + || empty( $files ) + || ! in_array( $download_id, array_keys( $files ), true ) ) { + return null; + } + + $download_data = $files[ $download_id ]; + $download = new \WC_Product_Download(); + $download->set_id( $download_id ); + $download->set_name( + $download_data['name'] + ? $download_data['name'] + : wc_get_filename_from_url( $download_data['file'] ) + ); + $download->set_file( + apply_filters( + 'woocommerce_file_download_path', + $download_data['file'], + \WC()->product_factory->get_product( $product_id ), + $download_id + ) + ); + + return $download; + }, + ), + ), + ) + ); + } +} diff --git a/includes/type/object/class-order-type.php b/includes/type/object/class-order-type.php index 0515876b3..3504a4eda 100644 --- a/includes/type/object/class-order-type.php +++ b/includes/type/object/class-order-type.php @@ -316,10 +316,6 @@ public static function register() { 'type' => 'Boolean', 'description' => __( 'If order needs processing before it can be completed', 'wp-graphql-woocommerce' ), ), - 'downloadableItems' => array( - 'type' => array( 'list_of' => 'ProductDownload' ), - 'description' => __( 'Product downloads', 'wp-graphql-woocommerce' ), - ), ), ) ); diff --git a/tests/_support/Helper/GraphQLE2E.php b/tests/_support/Helper/GraphQLE2E.php index 07765ef85..3f70fb601 100644 --- a/tests/_support/Helper/GraphQLE2E.php +++ b/tests/_support/Helper/GraphQLE2E.php @@ -485,7 +485,19 @@ public function checkout( $input, $request_headers = array() ) { needsShippingAddress hasDownloadableItem downloadableItems { - downloadId + nodes { + url + accessExpires + downloadId + downloadsRemaining + name + product { + productId + } + download { + downloadId + } + } } needsPayment needsProcessing diff --git a/tests/_support/Helper/crud-helpers/coupon.php b/tests/_support/Helper/crud-helpers/coupon.php index 841c5b307..55329ebca 100644 --- a/tests/_support/Helper/crud-helpers/coupon.php +++ b/tests/_support/Helper/crud-helpers/coupon.php @@ -32,8 +32,6 @@ public function create( $args = array(), $save = true ) { ) ); - codecept_debug( $coupon ); - // Set meta data. if ( ! empty( $args['meta_data'] ) ) { $coupon->set_meta_data( $args['meta_data'] ); diff --git a/tests/_support/Helper/crud-helpers/order.php b/tests/_support/Helper/crud-helpers/order.php index f97ce0baf..b85993ab9 100644 --- a/tests/_support/Helper/crud-helpers/order.php +++ b/tests/_support/Helper/crud-helpers/order.php @@ -305,14 +305,11 @@ public function print_query( $id ) { 'isDownloadPermitted' => $data->is_download_permitted(), 'needsShippingAddress' => $data->needs_shipping_address(), 'hasDownloadableItem' => $data->has_downloadable_item(), - 'downloadableItems' => ! empty( $data->get_downloadable_items() ) - ? array_map( - function( $download ) { - return array( 'downloadId' => $download->get_id() ); - }, - $data->get_downloadable_items() - ) - : null, + 'downloadableItems' => array( + 'nodes' => ! empty( $data->get_downloadable_items() ) + ? $this->print_downloadables( $data->get_id() ) + : array(), + ), 'needsPayment' => $data->needs_payment(), 'needsProcessing' => $data->needs_processing(), ); @@ -435,16 +432,38 @@ public function print_restricted_query( $id ) { 'isDownloadPermitted' => $data->is_download_permitted(), 'needsShippingAddress' => $data->needs_shipping_address(), 'hasDownloadableItem' => $data->has_downloadable_item(), - 'downloadableItems' => ! empty( $data->get_downloadable_items() ) - ? array_map( - function( $download ) { - return array( 'downloadId' => $download->get_id() ); - }, - $data->get_downloadable_items() - ) - : null, + 'downloadableItems' => array( + 'nodes' => ! empty( $data->get_downloadable_items() ) + ? $this->print_downloadables( $data->get_id() ) + : array(), + ), 'needsPayment' => $data->needs_payment(), 'needsProcessing' => $data->needs_processing(), ); } + + public function print_downloadables( $id ) { + $data = new WC_Order( $id ); + + if ( ! $data->get_id() ) { + return null; + } + + $nodes = array(); + foreach ( $data->get_downloadable_items() as $item ) { + $nodes[] = array( + 'url' => $item['download_url'], + 'accessExpires' => $item['access_expires'], + 'downloadId' => $item['download_id'], + 'downloadsRemaining' => isset( $item['downloads_remaining'] ) && 'integer' === gettype( $item['downloads_remaining'] ) + ? $item['downloads_remaining'] + : null, + 'name' => $item['download_name'], + 'product' => array( 'productId' => $item['product_id'] ), + 'download' => array( 'downloadId' => $item['download_id'] ), + ); + } + + return $nodes; + } } \ No newline at end of file diff --git a/tests/_support/Helper/crud-helpers/product.php b/tests/_support/Helper/crud-helpers/product.php index 31c5f2d3d..79272b866 100644 --- a/tests/_support/Helper/crud-helpers/product.php +++ b/tests/_support/Helper/crud-helpers/product.php @@ -210,7 +210,7 @@ public function create_attribute( $raw_name = 'size', $terms = array( 'small' ) // Make sure caches are clean. delete_transient( 'wc_attribute_taxonomies' ); - WC_Cache_Helper::incr_cache_prefix( 'woocommerce-attributes' ); + WC_Cache_Helper::invalidate_cache_group( 'woocommerce-attributes' ); // These are exported as labels, so convert the label to a name if possible first. $attribute_labels = wp_list_pluck( wc_get_attribute_taxonomies(), 'attribute_label', 'attribute_name' ); @@ -285,7 +285,7 @@ public function create_attribute( $raw_name = 'size', $terms = array( 'small' ) public function create_download( $id = 0 ) { $download = new WC_Product_Download(); - $download->set_id( 'testid' ); + $download->set_id( wp_generate_uuid4() ); $download->set_name( 'Test Name' ); $download->set_file( 'http://example.com/file.jpg' ); diff --git a/tests/wpunit/CheckoutMutationsTest.php b/tests/wpunit/CheckoutMutationsTest.php index 05e7b605a..1ef799797 100644 --- a/tests/wpunit/CheckoutMutationsTest.php +++ b/tests/wpunit/CheckoutMutationsTest.php @@ -147,7 +147,19 @@ private function checkout( $input ) { needsShippingAddress hasDownloadableItem downloadableItems { - downloadId + nodes { + url + accessExpires + downloadId + downloadsRemaining + name + product { + productId + } + download { + downloadId + } + } } needsPayment needsProcessing diff --git a/tests/wpunit/ConnectionPaginationTest.php b/tests/wpunit/ConnectionPaginationTest.php index fe2054597..fdcc710f5 100644 --- a/tests/wpunit/ConnectionPaginationTest.php +++ b/tests/wpunit/ConnectionPaginationTest.php @@ -14,7 +14,7 @@ public function setUp() { parent::setUp(); // Create users. - $this->shop_manager = $this->factory->user->create( array( 'role' => 'shop_manager' ) ); + $this->shop_manager = $this->factory->user->create( array( 'role' => 'shop_manager' ) ); // Setup helpers. $this->coupons = $this->getModule('\Helper\Wpunit')->coupon(); @@ -500,4 +500,165 @@ function( $key_a, $key_b ) { $this->assertEquals( $expected, $actual ); } + + public function testDownloadableItemsPagination() { + $customer_id = $this->customers->create(); + $downloable_products = array( + $this->products->create_simple( + array( + 'downloadable' => true, + 'downloads' => array( $this->products->create_download() ) + ) + ), + $this->products->create_simple( + array( + 'downloadable' => true, + 'downloads' => array( $this->products->create_download() ) + ) + ), + $this->products->create_simple( + array( + 'downloadable' => true, + 'downloads' => array( $this->products->create_download() ) + ) + ), + $this->products->create_simple( + array( + 'downloadable' => true, + 'downloads' => array( $this->products->create_download() ) + ) + ), + $this->products->create_simple( + array( + 'downloadable' => true, + 'downloads' => array( $this->products->create_download() ) + ) + ), + ); + + $order_id = $this->orders->create( + array( + 'status' => 'completed', + 'customer_id' => $customer_id, + ), + array( + 'line_items' => array_map( + function( $product_id ) { + return array( + 'product' => $product_id, + 'qty' => 1, + ); + }, + $downloable_products + ), + ) + ); + + // Force download permission updated. + wc_downloadable_product_permissions( $order_id, true ); + + $query = ' + query ($first: Int, $last: Int, $after: String, $before: String) { + customer { + orders { + nodes { + downloadableItems(first: $first, last: $last, after: $after, before: $before) { + nodes { + product { + productId + } + } + pageInfo { + hasPreviousPage + hasNextPage + startCursor + endCursor + } + } + } + + } + } + } + '; + + wp_set_current_user( $customer_id ); + + /** + * Assertion One + * + * Test "first" parameter. + */ + $variables = array( 'first' => 2 ); + $results = graphql( + array( + 'query' => $query, + 'variables' => $variables, + ) + ); + + // use --debug flag to view. + codecept_debug( $results ); + + // Check pageInfo. + $this->assertNotEmpty( $results['data'] ); + $this->assertNotEmpty( $results['data']['customer'] ); + $this->assertNotEmpty( $results['data']['customer']['orders'] ); + $this->assertNotEmpty( $results['data']['customer']['orders']['nodes'] ); + $this->assertNotEmpty( $results['data']['customer']['orders']['nodes'][0] ); + $this->assertNotEmpty( $results['data']['customer']['orders']['nodes'][0]['downloadableItems'] ); + $this->assertNotEmpty( $results['data']['customer']['orders']['nodes'][0]['downloadableItems']['pageInfo'] ); + $this->assertTrue( $results['data']['customer']['orders']['nodes'][0]['downloadableItems']['pageInfo']['hasNextPage'] ); + $this->assertNotEmpty( $results['data']['customer']['orders']['nodes'][0]['downloadableItems']['pageInfo']['endCursor'] ); + $end_cursor = $results['data']['customer']['orders']['nodes'][0]['downloadableItems']['pageInfo']['endCursor']; + + // Check downloadable items. + $actual = $results['data']['customer']['orders']['nodes'][0]['downloadableItems']['nodes']; + $expected = array_map( + function( $product_id ) { + return array( 'product' => array( 'productId' => $product_id ) ); + }, + array_slice( $downloable_products, 0, 2 ) + ); + + $this->assertEquals( $expected, $actual ); + + /** + * Assertion Two + * + * Test "after" parameter. + */ + $variables = array( 'first' => 3, 'after' => $end_cursor ); + $results = graphql( + array( + 'query' => $query, + 'variables' => $variables, + ) + ); + + // use --debug flag to view. + codecept_debug( $results ); + + // Check pageInfo. + $this->assertNotEmpty( $results['data'] ); + $this->assertNotEmpty( $results['data']['customer'] ); + $this->assertNotEmpty( $results['data']['customer']['orders'] ); + $this->assertNotEmpty( $results['data']['customer']['orders']['nodes'] ); + $this->assertNotEmpty( $results['data']['customer']['orders']['nodes'][0] ); + $this->assertNotEmpty( $results['data']['customer']['orders']['nodes'][0]['downloadableItems'] ); + $this->assertNotEmpty( $results['data']['customer']['orders']['nodes'][0]['downloadableItems']['pageInfo'] ); + $this->assertFalse( $results['data']['customer']['orders']['nodes'][0]['downloadableItems']['pageInfo']['hasNextPage'] ); + $this->assertNotEmpty( $results['data']['customer']['orders']['nodes'][0]['downloadableItems']['pageInfo']['endCursor'] ); + + // Check downloadable items. + $actual = $results['data']['customer']['orders']['nodes'][0]['downloadableItems']['nodes']; + $expected = array_map( + function( $product_id ) { + return array( 'product' => array( 'productId' => $product_id ) ); + }, + array_slice( $downloable_products, 2, 3 ) + ); + + $this->assertEquals( $expected, $actual ); + } } \ No newline at end of file diff --git a/tests/wpunit/DownloadableItemQueriesTest.php b/tests/wpunit/DownloadableItemQueriesTest.php new file mode 100644 index 000000000..3000cb8af --- /dev/null +++ b/tests/wpunit/DownloadableItemQueriesTest.php @@ -0,0 +1,323 @@ +customers = $this->getModule('\Helper\Wpunit')->customer(); + $this->orders = $this->getModule('\Helper\Wpunit')->order(); + $this->products = $this->getModule('\Helper\Wpunit')->product(); + + update_option( 'woocommerce_downloads_grant_access_after_payment', 'yes' ); + $this->customer = $this->customers->create(); + } + + public function tearDown() { + // your tear down methods here + + // then + parent::tearDown(); + } + + // tests + public function testOrderToDownloadableItemsQuery() { + $downloadable_product = $this->products->create_simple( + array( + 'downloadable' => true, + 'downloads' => array( $this->products->create_download() ) + ) + ); + $order_id = $this->orders->create( + array( + 'status' => 'completed', + 'customer_id' => $this->customer, + ), + array( + 'line_items' => array( + array( + 'product' => $downloadable_product, + 'qty' => 1, + ), + ), + ) + ); + + // Force download permission updated. + wc_downloadable_product_permissions( $order_id, true ); + + $query = ' + query { + customer { + orders { + nodes { + downloadableItems(first: 1) { + nodes { + url + accessExpires + downloadId + downloadsRemaining + name + product { + productId + } + download { + downloadId + } + } + } + } + } + } + } + '; + + /** + * Assertion One + * + * tests query results + */ + wp_set_current_user( $this->customer ); + $actual = graphql( array( 'query' => $query ) ); + $expected = array( + 'data' => array( + 'customer' => array( + 'orders' => array( + 'nodes' => array( + array( + 'downloadableItems' => array( 'nodes' => $this->orders->print_downloadables( $order_id ) ), + ), + ), + ), + ), + ), + ); + + // use --debug flag to view. + codecept_debug( $actual ); + + $this->assertEquals( $expected, $actual ); + } + + + public function testOrderToDownloadableItemsQueryArgs() { + $valid_product = $this->products->create_simple( + array( + 'downloadable' => true, + 'downloads' => array( $this->products->create_download() ) + ) + ); + $downloadable_product = $this->products->create_simple( + array( + 'download_expiry' => 5, + 'download_limit' => 3, + 'downloadable' => true, + 'downloads' => array( $this->products->create_download() ) + ) + ); + $downloaded_product = $this->products->create_simple( + array( + 'download_limit' => 0, + 'downloadable' => true, + 'downloads' => array( $this->products->create_download() ) + ) + ); + + $order_id = $this->orders->create( + array( + 'status' => 'completed', + 'customer_id' => $this->customer, + ), + array( + 'line_items' => array( + array( + 'product' => $valid_product, + 'qty' => 1, + ), + array( + 'product' => $downloadable_product, + 'qty' => 1, + ), + array( + 'product' => $downloaded_product, + 'qty' => 1, + ), + ), + ) + ); + + // Force download permission updated. + wc_downloadable_product_permissions( $order_id, true ); + + $query = ' + query($input: OrderToDownloadableItemConnectionWhereArgs) { + customer { + orders { + nodes { + downloadableItems(where: $input) { + nodes { + product { + productId + } + } + } + } + } + } + } + '; + + /** + * Assertion One + * + * tests "active" whereArg + */ + wp_set_current_user( $this->customer ); + $actual = graphql( + array( + 'query' => $query, + 'variables' => array( 'input' => array( 'active' => true ) ), + ) + ); + $expected = array( + 'data' => array( + 'customer' => array( + 'orders' => array( + 'nodes' => array( + array( + 'downloadableItems' => array( + 'nodes' => array_map( + function( $product_id ) { + return array( 'product' => array( 'productId' => $product_id ) ); + }, + array( $valid_product, $downloadable_product ) + ), + ), + ), + ), + ), + ), + ), + ); + + // use --debug flag to view. + codecept_debug( $actual ); + + $this->assertEquals( $expected, $actual ); + + /** + * Assertion Two + * + * tests "active" whereArg reversed + */ + $actual = graphql( + array( + 'query' => $query, + 'variables' => array( 'input' => array( 'active' => false ) ), + ) + ); + $expected = array( + 'data' => array( + 'customer' => array( + 'orders' => array( + 'nodes' => array( + array( + 'downloadableItems' => array( + 'nodes' => array_map( + function( $product_id ) { + return array( 'product' => array( 'productId' => $product_id ) ); + }, + array( $downloaded_product ) + ), + ), + ), + ), + ), + ), + ), + ); + + // use --debug flag to view. + codecept_debug( $actual ); + + $this->assertEquals( $expected, $actual ); + + /** + * Assertion Three + * + * tests "hasDownloadsRemaining" whereArg + */ + $actual = graphql( + array( + 'query' => $query, + 'variables' => array( 'input' => array( 'hasDownloadsRemaining' => true ) ) + ) + ); + $expected = array( + 'data' => array( + 'customer' => array( + 'orders' => array( + 'nodes' => array( + array( + 'downloadableItems' => array( + 'nodes' => array_map( + function( $product_id ) { + return array( 'product' => array( 'productId' => $product_id ) ); + }, + array( $valid_product, $downloadable_product ) + ), + ), + ), + ), + ), + ), + ), + ); + + // use --debug flag to view. + codecept_debug( $actual ); + + $this->assertEquals( $expected, $actual ); + + /** + * Assertion Four + * + * tests "hasDownloadsRemaining" whereArg reversed + */ + $actual = graphql( + array( + 'query' => $query, + 'variables' => array( 'input' => array( 'hasDownloadsRemaining' => false ) ) + ) + ); + $expected = array( + 'data' => array( + 'customer' => array( + 'orders' => array( + 'nodes' => array( + array( + 'downloadableItems' => array( + 'nodes' => array_map( + function( $product_id ) { + return array( 'product' => array( 'productId' => $product_id ) ); + }, + array( $downloaded_product ) + ), + ), + ), + ), + ), + ), + ), + ); + + // use --debug flag to view. + codecept_debug( $actual ); + + $this->assertEquals( $expected, $actual ); + } + +} \ No newline at end of file diff --git a/tests/wpunit/OrderMutationsTest.php b/tests/wpunit/OrderMutationsTest.php index ba2923930..aaef243e1 100644 --- a/tests/wpunit/OrderMutationsTest.php +++ b/tests/wpunit/OrderMutationsTest.php @@ -120,7 +120,19 @@ private function orderMutation( $input, $operation_name = 'createOrder', $input_ needsShippingAddress hasDownloadableItem downloadableItems { - downloadId + nodes { + url + accessExpires + downloadId + downloadsRemaining + name + product { + productId + } + download { + downloadId + } + } } needsPayment needsProcessing diff --git a/tests/wpunit/OrderQueriesTest.php b/tests/wpunit/OrderQueriesTest.php index 1f569a89a..e0269b6e0 100644 --- a/tests/wpunit/OrderQueriesTest.php +++ b/tests/wpunit/OrderQueriesTest.php @@ -99,7 +99,19 @@ public function testOrderQuery() { needsShippingAddress hasDownloadableItem downloadableItems { - downloadId + nodes { + url + accessExpires + downloadId + downloadsRemaining + name + product { + productId + } + download { + downloadId + } + } } needsPayment needsProcessing diff --git a/tests/wpunit/ProductQueriesTest.php b/tests/wpunit/ProductQueriesTest.php index 75d918950..eadc910cc 100644 --- a/tests/wpunit/ProductQueriesTest.php +++ b/tests/wpunit/ProductQueriesTest.php @@ -36,7 +36,8 @@ public function setUp() { 'category_ids' => array( $category_id ), 'image_id' => $this->image_id, 'gallery_image_ids' => array( $this->image_id ), - 'downloads' => array( ProductHelper::create_download() ), + 'downloadable' => true, + 'downloads' => array( $this->helper->create_download() ), 'slug' => 'product-slug', 'sku' => 'product-sku', ) diff --git a/vendor/autoload.php b/vendor/autoload.php index 521165a12..31710106c 100644 --- a/vendor/autoload.php +++ b/vendor/autoload.php @@ -4,4 +4,4 @@ require_once __DIR__ . '/composer/autoload_real.php'; -return ComposerAutoloaderInit8ad1f343d0cd8163c5fbabd591f04ce0::getLoader(); +return ComposerAutoloaderInit4127ab9804a522d85e48188e585a812c::getLoader(); diff --git a/vendor/composer/autoload_classmap.php b/vendor/composer/autoload_classmap.php index fbfaf487f..08ec3aec2 100644 --- a/vendor/composer/autoload_classmap.php +++ b/vendor/composer/autoload_classmap.php @@ -14,6 +14,7 @@ 'WPGraphQL\\WooCommerce\\Connection\\Cart_Items' => $baseDir . '/includes/connection/class-cart-items.php', 'WPGraphQL\\WooCommerce\\Connection\\Coupons' => $baseDir . '/includes/connection/class-coupons.php', 'WPGraphQL\\WooCommerce\\Connection\\Customers' => $baseDir . '/includes/connection/class-customers.php', + 'WPGraphQL\\WooCommerce\\Connection\\Downloadable_Items' => $baseDir . '/includes/connection/class-downloadable-items.php', 'WPGraphQL\\WooCommerce\\Connection\\Order_Items' => $baseDir . '/includes/connection/class-order-items.php', 'WPGraphQL\\WooCommerce\\Connection\\Orders' => $baseDir . '/includes/connection/class-orders.php', 'WPGraphQL\\WooCommerce\\Connection\\Payment_Gateways' => $baseDir . '/includes/connection/class-payment-gateways.php', @@ -30,6 +31,7 @@ 'WPGraphQL\\WooCommerce\\Data\\Connection\\Cart_Item_Connection_Resolver' => $baseDir . '/includes/data/connection/class-cart-item-connection-resolver.php', 'WPGraphQL\\WooCommerce\\Data\\Connection\\Coupon_Connection_Resolver' => $baseDir . '/includes/data/connection/class-coupon-connection-resolver.php', 'WPGraphQL\\WooCommerce\\Data\\Connection\\Customer_Connection_Resolver' => $baseDir . '/includes/data/connection/class-customer-connection-resolver.php', + 'WPGraphQL\\WooCommerce\\Data\\Connection\\Downloadable_Item_Connection_Resolver' => $baseDir . '/includes/data/connection/class-downloadable-item-connection-resolver.php', 'WPGraphQL\\WooCommerce\\Data\\Connection\\Order_Connection_Resolver' => $baseDir . '/includes/data/connection/class-order-connection-resolver.php', 'WPGraphQL\\WooCommerce\\Data\\Connection\\Order_Item_Connection_Resolver' => $baseDir . '/includes/data/connection/class-order-item-connection-resolver.php', 'WPGraphQL\\WooCommerce\\Data\\Connection\\Payment_Gateway_Connection_Resolver' => $baseDir . '/includes/data/connection/class-payment-gateway-connection-resolver.php', @@ -115,6 +117,7 @@ 'WPGraphQL\\WooCommerce\\Type\\WPObject\\Coupon_Type' => $baseDir . '/includes/type/object/class-coupon-type.php', 'WPGraphQL\\WooCommerce\\Type\\WPObject\\Customer_Address_Type' => $baseDir . '/includes/type/object/class-customer-address-type.php', 'WPGraphQL\\WooCommerce\\Type\\WPObject\\Customer_Type' => $baseDir . '/includes/type/object/class-customer-type.php', + 'WPGraphQL\\WooCommerce\\Type\\WPObject\\Downloadable_Item_Type' => $baseDir . '/includes/type/object/class-downloadable-item-type.php', 'WPGraphQL\\WooCommerce\\Type\\WPObject\\Meta_Data_Type' => $baseDir . '/includes/type/object/class-meta-data-type.php', 'WPGraphQL\\WooCommerce\\Type\\WPObject\\Order_Item_Type' => $baseDir . '/includes/type/object/class-order-item-type.php', 'WPGraphQL\\WooCommerce\\Type\\WPObject\\Order_Type' => $baseDir . '/includes/type/object/class-order-type.php', diff --git a/vendor/composer/autoload_real.php b/vendor/composer/autoload_real.php index 10550c9d6..375bfad9b 100644 --- a/vendor/composer/autoload_real.php +++ b/vendor/composer/autoload_real.php @@ -2,7 +2,7 @@ // autoload_real.php @generated by Composer -class ComposerAutoloaderInit8ad1f343d0cd8163c5fbabd591f04ce0 +class ComposerAutoloaderInit4127ab9804a522d85e48188e585a812c { private static $loader; @@ -19,15 +19,15 @@ public static function getLoader() return self::$loader; } - spl_autoload_register(array('ComposerAutoloaderInit8ad1f343d0cd8163c5fbabd591f04ce0', 'loadClassLoader'), true, true); + spl_autoload_register(array('ComposerAutoloaderInit4127ab9804a522d85e48188e585a812c', 'loadClassLoader'), true, true); self::$loader = $loader = new \Composer\Autoload\ClassLoader(); - spl_autoload_unregister(array('ComposerAutoloaderInit8ad1f343d0cd8163c5fbabd591f04ce0', 'loadClassLoader')); + spl_autoload_unregister(array('ComposerAutoloaderInit4127ab9804a522d85e48188e585a812c', 'loadClassLoader')); $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); if ($useStaticLoader) { require_once __DIR__ . '/autoload_static.php'; - call_user_func(\Composer\Autoload\ComposerStaticInit8ad1f343d0cd8163c5fbabd591f04ce0::getInitializer($loader)); + call_user_func(\Composer\Autoload\ComposerStaticInit4127ab9804a522d85e48188e585a812c::getInitializer($loader)); } else { $map = require __DIR__ . '/autoload_namespaces.php'; foreach ($map as $namespace => $path) { @@ -48,19 +48,19 @@ public static function getLoader() $loader->register(true); if ($useStaticLoader) { - $includeFiles = Composer\Autoload\ComposerStaticInit8ad1f343d0cd8163c5fbabd591f04ce0::$files; + $includeFiles = Composer\Autoload\ComposerStaticInit4127ab9804a522d85e48188e585a812c::$files; } else { $includeFiles = require __DIR__ . '/autoload_files.php'; } foreach ($includeFiles as $fileIdentifier => $file) { - composerRequire8ad1f343d0cd8163c5fbabd591f04ce0($fileIdentifier, $file); + composerRequire4127ab9804a522d85e48188e585a812c($fileIdentifier, $file); } return $loader; } } -function composerRequire8ad1f343d0cd8163c5fbabd591f04ce0($fileIdentifier, $file) +function composerRequire4127ab9804a522d85e48188e585a812c($fileIdentifier, $file) { if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { require $file; diff --git a/vendor/composer/autoload_static.php b/vendor/composer/autoload_static.php index 3a8f92546..ded3b99e7 100644 --- a/vendor/composer/autoload_static.php +++ b/vendor/composer/autoload_static.php @@ -4,7 +4,7 @@ namespace Composer\Autoload; -class ComposerStaticInit8ad1f343d0cd8163c5fbabd591f04ce0 +class ComposerStaticInit4127ab9804a522d85e48188e585a812c { public static $files = array ( '914b07b8cf678ed0b81bfdb5d23b4f2b' => __DIR__ . '/../..' . '/includes/connection/common-post-type-args.php', @@ -42,6 +42,7 @@ class ComposerStaticInit8ad1f343d0cd8163c5fbabd591f04ce0 'WPGraphQL\\WooCommerce\\Connection\\Cart_Items' => __DIR__ . '/../..' . '/includes/connection/class-cart-items.php', 'WPGraphQL\\WooCommerce\\Connection\\Coupons' => __DIR__ . '/../..' . '/includes/connection/class-coupons.php', 'WPGraphQL\\WooCommerce\\Connection\\Customers' => __DIR__ . '/../..' . '/includes/connection/class-customers.php', + 'WPGraphQL\\WooCommerce\\Connection\\Downloadable_Items' => __DIR__ . '/../..' . '/includes/connection/class-downloadable-items.php', 'WPGraphQL\\WooCommerce\\Connection\\Order_Items' => __DIR__ . '/../..' . '/includes/connection/class-order-items.php', 'WPGraphQL\\WooCommerce\\Connection\\Orders' => __DIR__ . '/../..' . '/includes/connection/class-orders.php', 'WPGraphQL\\WooCommerce\\Connection\\Payment_Gateways' => __DIR__ . '/../..' . '/includes/connection/class-payment-gateways.php', @@ -58,6 +59,7 @@ class ComposerStaticInit8ad1f343d0cd8163c5fbabd591f04ce0 'WPGraphQL\\WooCommerce\\Data\\Connection\\Cart_Item_Connection_Resolver' => __DIR__ . '/../..' . '/includes/data/connection/class-cart-item-connection-resolver.php', 'WPGraphQL\\WooCommerce\\Data\\Connection\\Coupon_Connection_Resolver' => __DIR__ . '/../..' . '/includes/data/connection/class-coupon-connection-resolver.php', 'WPGraphQL\\WooCommerce\\Data\\Connection\\Customer_Connection_Resolver' => __DIR__ . '/../..' . '/includes/data/connection/class-customer-connection-resolver.php', + 'WPGraphQL\\WooCommerce\\Data\\Connection\\Downloadable_Item_Connection_Resolver' => __DIR__ . '/../..' . '/includes/data/connection/class-downloadable-item-connection-resolver.php', 'WPGraphQL\\WooCommerce\\Data\\Connection\\Order_Connection_Resolver' => __DIR__ . '/../..' . '/includes/data/connection/class-order-connection-resolver.php', 'WPGraphQL\\WooCommerce\\Data\\Connection\\Order_Item_Connection_Resolver' => __DIR__ . '/../..' . '/includes/data/connection/class-order-item-connection-resolver.php', 'WPGraphQL\\WooCommerce\\Data\\Connection\\Payment_Gateway_Connection_Resolver' => __DIR__ . '/../..' . '/includes/data/connection/class-payment-gateway-connection-resolver.php', @@ -143,6 +145,7 @@ class ComposerStaticInit8ad1f343d0cd8163c5fbabd591f04ce0 'WPGraphQL\\WooCommerce\\Type\\WPObject\\Coupon_Type' => __DIR__ . '/../..' . '/includes/type/object/class-coupon-type.php', 'WPGraphQL\\WooCommerce\\Type\\WPObject\\Customer_Address_Type' => __DIR__ . '/../..' . '/includes/type/object/class-customer-address-type.php', 'WPGraphQL\\WooCommerce\\Type\\WPObject\\Customer_Type' => __DIR__ . '/../..' . '/includes/type/object/class-customer-type.php', + 'WPGraphQL\\WooCommerce\\Type\\WPObject\\Downloadable_Item_Type' => __DIR__ . '/../..' . '/includes/type/object/class-downloadable-item-type.php', 'WPGraphQL\\WooCommerce\\Type\\WPObject\\Meta_Data_Type' => __DIR__ . '/../..' . '/includes/type/object/class-meta-data-type.php', 'WPGraphQL\\WooCommerce\\Type\\WPObject\\Order_Item_Type' => __DIR__ . '/../..' . '/includes/type/object/class-order-item-type.php', 'WPGraphQL\\WooCommerce\\Type\\WPObject\\Order_Type' => __DIR__ . '/../..' . '/includes/type/object/class-order-type.php', @@ -167,9 +170,9 @@ class ComposerStaticInit8ad1f343d0cd8163c5fbabd591f04ce0 public static function getInitializer(ClassLoader $loader) { return \Closure::bind(function () use ($loader) { - $loader->prefixLengthsPsr4 = ComposerStaticInit8ad1f343d0cd8163c5fbabd591f04ce0::$prefixLengthsPsr4; - $loader->prefixDirsPsr4 = ComposerStaticInit8ad1f343d0cd8163c5fbabd591f04ce0::$prefixDirsPsr4; - $loader->classMap = ComposerStaticInit8ad1f343d0cd8163c5fbabd591f04ce0::$classMap; + $loader->prefixLengthsPsr4 = ComposerStaticInit4127ab9804a522d85e48188e585a812c::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInit4127ab9804a522d85e48188e585a812c::$prefixDirsPsr4; + $loader->classMap = ComposerStaticInit4127ab9804a522d85e48188e585a812c::$classMap; }, null, ClassLoader::class); }