setup_constants(); self::$instance->includes(); self::$instance->actions(); self::$instance->filters(); } /** * Return the WPGraphQL Instance */ return self::$instance; } /** * Throw error on object clone. * The whole idea of the singleton design pattern is that there is a single object * therefore, we don't want the object to be cloned. * * @since 0.0.1 * @access public * @return void */ public function __clone() { // Cloning instances of the class is forbidden. _doing_it_wrong( __FUNCTION__, esc_html__( 'The WPGraphQL class should not be cloned.', 'wp-graphql' ), '0.0.1' ); } /** * Disable unserializing of the class. * * @since 0.0.1 * @access protected * @return void */ public function __wakeup() { // De-serializing instances of the class is forbidden. _doing_it_wrong( __FUNCTION__, esc_html__( 'De-serializing instances of the WPGraphQL class is not allowed', 'wp-graphql' ), '0.0.1' ); } /** * Setup plugin constants. * * @access private * @since 0.0.1 * @return void */ private function setup_constants() { // Plugin version. if ( ! defined( 'WPGRAPHQL_VERSION' ) ) { define( 'WPGRAPHQL_VERSION', '0.0.28' ); } // Plugin Folder Path. if ( ! defined( 'WPGRAPHQL_PLUGIN_DIR' ) ) { define( 'WPGRAPHQL_PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); } // Plugin Folder URL. if ( ! defined( 'WPGRAPHQL_PLUGIN_URL' ) ) { define( 'WPGRAPHQL_PLUGIN_URL', plugin_dir_url( __FILE__ ) ); } // Plugin Root File. if ( ! defined( 'WPGRAPHQL_PLUGIN_FILE' ) ) { define( 'WPGRAPHQL_PLUGIN_FILE', __FILE__ ); } // Whether to autoload the files or not if ( ! defined( 'WPGRAPHQL_AUTOLOAD' ) ) { define( 'WPGRAPHQL_AUTOLOAD', true ); } // Whether to run the plugin in debug mode. Default is false. if ( ! defined( 'GRAPHQL_DEBUG' ) ) { define( 'GRAPHQL_DEBUG', false ); } } /** * Include required files. * Uses composer's autoload * * @access private * @since 0.0.1 * @return void */ private function includes() { /** * WPGRAPHQL_AUTOLOAD can be set to "false" to prevent the autoloader from running. * In most cases, this is not something that should be disabled, but some environments * may bootstrap their dependencies in a global autoloader that will autoload files * before we get to this point, and requiring the autoloader again can trigger fatal errors. * * The codeception tests are an example of an environment where adding the autoloader again causes issues * so this is set to false for tests. */ if ( defined( 'WPGRAPHQL_AUTOLOAD' ) && true === WPGRAPHQL_AUTOLOAD ) { // Autoload Required Classes require_once( WPGRAPHQL_PLUGIN_DIR . 'vendor/autoload.php' ); } // Required non-autoloaded classes require_once( WPGRAPHQL_PLUGIN_DIR . 'access-functions.php' ); } /** * Sets up actions to run at certain spots throughout WordPress and the WPGraphQL execution cycle */ private function actions() { /** * Init WPGraphQL after themes have been setup, * allowing for both plugins and themes to register * things before graphql_init */ add_action( 'after_setup_theme', function() { new \WPGraphQL\Data\Config(); new \WPGraphQL\Router(); /** * Fire off init action * * @param WPGraphQL $instance The instance of the WPGraphQL class */ do_action( 'graphql_init', self::$instance ); } ); /** * Flush permalinks if the registered GraphQL endpoint has not yet been registered. */ add_action( 'wp_loaded', [ $this, 'maybe_flush_permalinks' ] ); /** * Register default settings available in WordPress so we can use * the get_registered_settings method * * @source https://github.com/WordPress/WordPress/blob/master/wp-includes/default-filters.php#L393 */ add_action( 'do_graphql_request', 'register_initial_settings', 10 ); /** * Hook in before fields resolve to check field permissions */ add_action( 'graphql_before_resolve_field', [ '\WPGraphQL\Utils\InstrumentSchema', 'check_field_permissions' ], 10, 8 ); } /** * Flush permalinks if the GraphQL Endpoint route isn't yet registered */ public function maybe_flush_permalinks() { $rules = get_option( 'rewrite_rules' ); if ( ! isset( $rules[ \WPGraphQL\Router::$route . '/?$' ] ) ) { flush_rewrite_rules(); } } /** * Setup filters */ private function filters() { /** * mediaItems are the attachment postObject, but they have a different schema shape * than postObjects out of the box, so this filter adjusts the core mediaItem * shape of data */ add_filter( 'graphql_mediaItem_fields', [ '\WPGraphQL\Type\MediaItem\MediaItemType', 'fields' ], 10, 1 ); /** * Instrument the Schema to provide Resolve Hooks and sanitize Schema output */ add_filter( 'graphql_schema', [ '\WPGraphQL\Utils\InstrumentSchema', 'instrument_schema' ], 10, 1 ); } /** * Function to execute when the user activates the plugin. * * @since 0.0.17 */ public function activate() { flush_rewrite_rules(); // Save the version of the plugin as an option in order to force actions // on upgrade. update_option( 'wp_graphql_version', WPGRAPHQL_VERSION, 'no' ); } /** * Function to execute when the user deactivates the plugin. * * @since 0.0.17 */ public function deactivate() { flush_rewrite_rules(); delete_option( 'wp_graphql_version' ); } /** * This sets up built-in post_types and taxonomies to show in the GraphQL Schema * * @since 0.0.2 * @access public * @return void */ public static function show_in_graphql() { global $wp_post_types, $wp_taxonomies; // Adds GraphQL support for attachments if ( isset( $wp_post_types['attachment'] ) ) { $wp_post_types['attachment']->show_in_graphql = true; $wp_post_types['attachment']->graphql_single_name = 'mediaItem'; $wp_post_types['attachment']->graphql_plural_name = 'mediaItems'; } // Adds GraphQL support for pages if ( isset( $wp_post_types['page'] ) ) { $wp_post_types['page']->show_in_graphql = true; $wp_post_types['page']->graphql_single_name = 'page'; $wp_post_types['page']->graphql_plural_name = 'pages'; } // Adds GraphQL support for posts if ( isset( $wp_post_types['post'] ) ) { $wp_post_types['post']->show_in_graphql = true; $wp_post_types['post']->graphql_single_name = 'post'; $wp_post_types['post']->graphql_plural_name = 'posts'; } // Adds GraphQL support for categories if ( isset( $wp_taxonomies['category'] ) ) { $wp_taxonomies['category']->show_in_graphql = true; $wp_taxonomies['category']->graphql_single_name = 'category'; $wp_taxonomies['category']->graphql_plural_name = 'categories'; } // Adds GraphQL support for tags if ( isset( $wp_taxonomies['post_tag'] ) ) { $wp_taxonomies['post_tag']->show_in_graphql = true; $wp_taxonomies['post_tag']->graphql_single_name = 'tag'; $wp_taxonomies['post_tag']->graphql_plural_name = 'tags'; } } /** * Get the post types that are allowed to be used in GraphQL. This gets all post_types that * are set to show_in_graphql, but allows for external code (plugins/theme) to filter the * list of allowed_post_types to add/remove additional post_types * * @return array * @since 0.0.4 * @access public */ public static function get_allowed_post_types() { /** * Get all post_types */ $post_types = get_post_types([ 'show_in_graphql' => true, ]); /** * Validate that the post_types have a graphql_single_name and graphql_plural_name */ array_map( function( $post_type ) { $post_type_object = get_post_type_object( $post_type ); if ( empty( $post_type_object->graphql_single_name ) || empty( $post_type_object->graphql_plural_name ) ) { throw new \GraphQL\Error\UserError( sprintf( __( 'The %s post_type isn\'t configured properly to show in GraphQL. It needs a "graphql_single_name" and a "graphql_plural_name"', 'wp-graphql' ), $post_type_object->name ) ); } }, $post_types ); /** * Define the $allowed_post_types to be exposed by GraphQL Queries Pass through a filter * to allow the post_types to be modified (for example if a certain post_type should * not be exposed to the GraphQL API) * * @since 0.0.2 * * @param array $post_types Array of post types * * @return array */ self::$allowed_post_types = apply_filters( 'graphql_post_entities_allowed_post_types', $post_types ); /** * Returns the array of allowed_post_types */ return self::$allowed_post_types; } /** * Get the taxonomies that are allowed to be used in GraphQL/This gets all taxonomies that * are set to "show_in_graphql" but allows for external code (plugins/themes) to filter * the list of allowed_taxonomies to add/remove additional taxonomies * * @since 0.0.4 * @access public * @return array */ public static function get_allowed_taxonomies() { /** * Get all taxonomies */ $taxonomies = get_taxonomies([ 'show_in_graphql' => true, ]); /** * Validate that the taxonomies have a graphql_single_name and graphql_plural_name */ array_map( function( $taxonomy ) { $tax_object = get_taxonomy( $taxonomy ); if ( empty( $tax_object->graphql_single_name ) || empty( $tax_object->graphql_plural_name ) ) { throw new \GraphQL\Error\UserError( sprintf( __( 'The %s taxonomy isn\'t configured properly to show in GraphQL. It needs a "graphql_single_name" and a "graphql_plural_name"', 'wp-graphql' ), $tax_object->name ) ); } }, $taxonomies ); /** * Define the $allowed_taxonomies to be exposed by GraphQL Queries Pass through a filter * to allow the taxonomies to be modified (for example if a certain taxonomy should not * be exposed to the GraphQL API) * * @since 0.0.2 * @return array * * @param array $taxonomies Array of taxonomy objects */ self::$allowed_taxonomies = apply_filters( 'graphql_term_entities_allowed_taxonomies', $taxonomies ); /** * Returns the array of $allowed_taxonomies */ return self::$allowed_taxonomies; } /** * Returns the Schema as defined by static registrations throughout * the WP Load. * * @access protected * @return \WPGraphQL\WPSchema */ public static function get_schema() { /** * Fire an action when the Schema is returned */ do_action( 'graphql_get_schema', self::$schema ); if ( null === self::$schema ) { /** * Create an executable Schema from the registered * root_Query and root_mutation */ $executable_schema = [ 'query' => \WPGraphQL\Types::root_query(), 'mutation' => \WPGraphQL\Types::root_mutation(), ]; /** * Generate the Schema */ $schema = new \WPGraphQL\WPSchema( $executable_schema ); /** * Generate & Filter the schema. * * @since 0.0.5 * * @param array $schema The executable Schema that GraphQL executes against * @param \WPGraphQL\AppContext $app_context Object The AppContext object containing all of the * information about the context we know at this point */ self::$schema = apply_filters( 'graphql_schema', $schema, self::get_app_context() ); } /** * Return the Schema after applying filters */ return ! empty( self::$schema ) ? self::$schema : null; } /** * Return the static schema if there is one * * @return null|string * @access public */ public static function get_static_schema() { $schema = null; if ( file_exists( WPGRAPHQL_PLUGIN_DIR . 'schema.graphql' ) && ! empty( file_get_contents( WPGRAPHQL_PLUGIN_DIR . 'schema.graphql' ) ) ) { $schema = file_get_contents( WPGRAPHQL_PLUGIN_DIR . 'schema.graphql' ); } return $schema; } /** * Get the AppContext for use in passing down the Resolve Tree * @return \WPGraphQL\AppContext * @access public */ public static function get_app_context() { /** * Configure the app_context which gets passed down to all the resolvers. * * @since 0.0.4 */ $app_context = new \WPGraphQL\AppContext(); $app_context->viewer = wp_get_current_user(); $app_context->root_url = get_bloginfo( 'url' ); $app_context->request = ! empty( $_REQUEST ) ? $_REQUEST : null; return $app_context; } /** * This processes a GraphQL request, given a $request and optional $variables * * This function is used to resolve the HTTP requests for the GraphQL API, but can also be * used internally to run GraphQL queries inside WordPress via PHP. * * @since 0.0.5 * * @param string $request The GraphQL request to be run * @param string $operation_name The name of the operation * @param string $variables Variables to be passed to your GraphQL request * * @return array $result The results of your request */ public static function do_graphql_request( $request, $operation_name = '', $variables = '' ) { /** * Whether it's a GraphQL Request (http or internal) * * @since 0.0.5 */ if ( ! defined( 'GRAPHQL_REQUEST' ) ) { define( 'GRAPHQL_REQUEST', true ); } /** * Setup the post_types and taxonomies to show_in_graphql */ \WPGraphQL::show_in_graphql(); \WPGraphQL::get_allowed_post_types(); \WPGraphQL::get_allowed_taxonomies(); /** * Store the global post so it can be reset after GraphQL execution * * This allows for a GraphQL query to be used in the middle of post content, such as in a Shortcode * without disrupting the flow of the post as the global POST before and after GraphQL execution will be * the same. */ $global_post = ! empty( $GLOBALS['post'] ) ? $GLOBALS['post'] : null; /** * Run an action as soon when do_graphql_request begins. * * @param string $request The GraphQL request to be run * @param string $operation_name The name of the operation * @param string $variables Variables to be passed to your GraphQL request */ do_action( 'do_graphql_request', $request, $operation_name, $variables ); /** * Run an action before generating the schema * This is a great spot for plugins/themes to hook in to customize the schema. * * @since 0.0.5 * * @param string $request The request to be executed by GraphQL * @param string $operation_name The name of the operation * @param array $variables Variables to be passed to your GraphQL request * @param AppContext object The AppContext object containing all of the * information about the context we know at this point */ if ( ! is_array( $variables ) ) { $variables = (string) $variables; $variables = (array) json_decode( $variables ); } /** * Executes the request and captures the result */ $result = \GraphQL\GraphQL::executeAndReturnResult( self::get_schema(), $request, null, self::get_app_context(), $variables, $operation_name ); /** * Run an action. This is a good place for debug tools to hook in to log things, etc. * * @since 0.0.4 * * @param array $result The result of your GraphQL request * @param Schema object $schema The schema object for the root request * @param string $operation_name The name of the operation * @param string $request The request that GraphQL executed * @param array|null $variables Variables to passed to your GraphQL query */ do_action( 'graphql_execute', $result, self::get_schema(), $operation_name, $request, $variables ); /** * Filter the $result of the GraphQL execution. This allows for the response to be filtered before * it's returned, allowing granular control over the response at the latest point. * * POSSIBLE USAGE EXAMPLES: * This could be used to ensure that certain fields never make it to the response if they match * certain criteria, etc. For example, this filter could be used to check if a current user is * allowed to see certain things, and if they are not, the $result could be filtered to remove * the data they should not be allowed to see. * * Or, perhaps some systems want the result to always include some additional piece of data in * every response, regardless of the request that was sent to it, this could allow for that * to be hooked in and included in the $result * * @since 0.0.5 * * @param array $result The result of your GraphQL query * @param Schema object $schema The schema object for the root query * @param string $operation_name The name of the operation * @param string $request The request that GraphQL executed * @param array|null $variables Variables to passed to your GraphQL request */ $filtered_result = apply_filters( 'graphql_request_results', $result, self::get_schema(), $operation_name, $request, $variables ); /** * Run an action after the result has been filtered, as the response is being returned. * This is a good place for debug tools to hook in to log things, etc. * * @param array $filtered_result The filtered_result of the GraphQL request * @param array $result The result of your GraphQL request * @param Schema object $schema The schema object for the root request * @param string $operation_name The name of the operation * @param string $request The request that GraphQL executed * @param array|null $variables Variables to passed to your GraphQL query */ do_action( 'graphql_return_response', $filtered_result, $result, self::get_schema(), $operation_name, $request, $variables ); /** * Reset the global post after execution * * This allows for a GraphQL query to be used in the middle of post content, such as in a Shortcode * without disrupting the flow of the post as the global POST before and after GraphQL execution will be * the same. */ if ( ! empty( $global_post ) ) { $GLOBALS['post'] = $global_post; } /** * Return the result of the request */ return $result->toArray( GRAPHQL_DEBUG ); } public static function server( $request = null ) { /** * Whether it's a GraphQL Request (http or internal) * * @since 0.0.5 */ if ( ! defined( 'GRAPHQL_REQUEST' ) ) { define( 'GRAPHQL_REQUEST', true ); } /** * Setup the post_types and taxonomies to show_in_graphql */ \WPGraphQL::show_in_graphql(); \WPGraphQL::get_allowed_post_types(); \WPGraphQL::get_allowed_taxonomies(); /** * Run an action as soon when do_graphql_request begins. */ $helper = new \GraphQL\Server\Helper(); $query = $helper->parseHttpRequest()->query; $operation = $helper->parseHttpRequest()->operation; $variables = $helper->parseHttpRequest()->variables; /** * Run an action as soon when do_graphql_request begins. * * @param string $request The GraphQL request to be run * @param string $operation_name The name of the operation * @param string $variables Variables to be passed to your GraphQL request */ do_action( 'do_graphql_request', $query, $operation, $variables ); $config = new \GraphQL\Server\ServerConfig(); $config ->setDebug( GRAPHQL_DEBUG ) ->setSchema( self::get_schema() ) ->setContext( self::get_app_context() ) ->setQueryBatching(true); $server = new \GraphQL\Server\StandardServer( $config ); return $server; } } endif; /** * Function that instantiates the plugins main class * * @since 0.0.1 */ function graphql_init() { /** * Return an instance of the action */ return \WPGraphQL::instance(); } graphql_init(); if ( defined( 'WP_CLI' ) && WP_CLI ) { require_once( 'cli/wp-cli.php' ); }