├── .distignore ├── .editorconfig ├── .gitignore ├── .travis.yml ├── Gruntfile.js ├── LICENSE ├── README.md ├── bin └── install-wp-tests.sh ├── composer.json ├── composer.lock ├── img └── ChromieQL.png ├── multisite.xml ├── package.json ├── phpunit.xml.dist ├── readme.txt ├── src ├── AppContext.php ├── README.md ├── Schema │ └── WPQuery.php ├── Type │ ├── AvatarType.php │ ├── BaseType.php │ ├── CommentType.php │ ├── Enum │ │ └── README.md │ ├── MenuItemType.php │ ├── MenuLocationType.php │ ├── MenuType.php │ ├── NodeType.php │ ├── PluginType.php │ ├── PostInterfaceType.php │ ├── PostObjectType.php │ ├── PostType.php │ ├── PostTypeType.php │ ├── QueryType.php │ ├── README.md │ ├── Scalar │ │ └── README.md │ ├── TaxonomyType.php │ ├── TermType.php │ ├── ThemeType.php │ └── UserType.php └── TypeSystem.php ├── tests ├── bootstrap.php ├── test-query.php └── test-schema.php ├── travis.yml └── wp-graphql.php /.distignore: -------------------------------------------------------------------------------- 1 | # A set of files you probably don't want in your WordPress.org distribution 2 | .distignore 3 | .editorconfig 4 | .git 5 | .gitignore 6 | .travis.yml 7 | .DS_Store 8 | Thumbs.db 9 | bin 10 | composer.json 11 | composer.lock 12 | Gruntfile.js 13 | package.json 14 | phpunit.xml 15 | multisite.xml 16 | phpunit.xml.dist 17 | phpcs.ruleset.xml 18 | README.md 19 | wp-cli.local.yml 20 | tests 21 | vendor 22 | node_modules 23 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs 2 | # editorconfig.org 3 | 4 | # WordPress Coding Standards 5 | # https://make.wordpress.org/core/handbook/coding-standards/ 6 | 7 | root = true 8 | 9 | [*] 10 | charset = utf-8 11 | end_of_line = lf 12 | insert_final_newline = true 13 | trim_trailing_whitespace = true 14 | indent_style = tab 15 | indent_size = 4 16 | 17 | [{.jshintrc,*.json,*.yml}] 18 | indent_style = space 19 | indent_size = 2 20 | 21 | [{*.txt,wp-config-sample.php}] 22 | end_of_line = crlf 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | Thumbs.db 3 | wp-cli.local.yml 4 | node_modules/ 5 | vendor/ 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | sudo: false 4 | 5 | notifications: 6 | email: 7 | on_success: never 8 | on_failure: never 9 | 10 | branches: 11 | only: 12 | - master 13 | - develop 14 | 15 | matrix: 16 | include: 17 | - php: 5.4 18 | env: WP_VERSION=latest WP_MULTISITE=0 19 | - php: 5.5 20 | env: WP_VERSION=latest WP_MULTISITE=0 21 | - php: 5.6 22 | env: WP_VERSION=latest WP_MULTISITE=0 23 | - php: 7.0 24 | env: WP_VERSION=latest WP_MULTISITE=0 25 | - php: 5.4 26 | env: WP_VERSION=latest WP_MULTISITE=1 27 | - php: 5.5 28 | env: WP_VERSION=latest WP_MULTISITE=1 29 | - php: 5.6 30 | env: WP_VERSION=latest WP_MULTISITE=1 31 | - php: 7.0 32 | env: WP_VERSION=latest WP_MULTISITE=1 33 | - php: 7.0 34 | env: WP_VERSION=nightly WP_MULTISITE=0 35 | fast_finish: true 36 | 37 | cache: 38 | directories: 39 | - vendor 40 | - $HOME/.composer/cache 41 | 42 | before_script: 43 | - bash bin/install-wp-tests.sh wordpress_test root '' localhost $WP_VERSION 44 | - composer self-update 45 | - composer install --no-interaction 46 | 47 | script: phpunit 48 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function( grunt ) { 2 | 3 | 'use strict'; 4 | var banner = '/**\n * <%= pkg.homepage %>\n * Copyright (c) <%= grunt.template.today("yyyy") %>\n * This file is generated automatically. Do not edit.\n */\n'; 5 | // Project configuration 6 | grunt.initConfig( { 7 | 8 | pkg: grunt.file.readJSON( 'package.json' ), 9 | 10 | addtextdomain: { 11 | options: { 12 | textdomain: 'wp-graphql', 13 | }, 14 | target: { 15 | files: { 16 | src: [ '*.php', '**/*.php', '!node_modules/**', '!php-tests/**', '!bin/**' ] 17 | } 18 | } 19 | }, 20 | 21 | wp_readme_to_markdown: { 22 | your_target: { 23 | files: { 24 | 'README.md': 'readme.txt' 25 | } 26 | }, 27 | }, 28 | 29 | makepot: { 30 | target: { 31 | options: { 32 | domainPath: '/languages', 33 | mainFile: 'wp-graphql.php', 34 | potFilename: 'wp-graphql.pot', 35 | potHeaders: { 36 | poedit: true, 37 | 'x-poedit-keywordslist': true 38 | }, 39 | type: 'wp-plugin', 40 | updateTimestamp: true 41 | } 42 | } 43 | }, 44 | } ); 45 | 46 | grunt.loadNpmTasks( 'grunt-wp-i18n' ); 47 | grunt.loadNpmTasks( 'grunt-wp-readme-to-markdown' ); 48 | grunt.registerTask( 'i18n', ['addtextdomain', 'makepot'] ); 49 | grunt.registerTask( 'readme', ['wp_readme_to_markdown'] ); 50 | 51 | grunt.util.linefeed = '\n'; 52 | 53 | }; 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hello and Welcome to WP GraphQL! 2 | 3 | This version of WP GraphQL has been deprecated. Check out https://github.com/wp-graphql/wp-graphql instead. 4 | -------------------------------------------------------------------------------- /bin/install-wp-tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ $# -lt 3 ]; then 4 | echo "usage: $0 [db-host] [wp-version] [skip-database-creation]" 5 | exit 1 6 | fi 7 | 8 | DB_NAME=$1 9 | DB_USER=$2 10 | DB_PASS=$3 11 | DB_HOST=${4-localhost} 12 | WP_VERSION=${5-latest} 13 | SKIP_DB_CREATE=${6-false} 14 | 15 | WP_TESTS_DIR=${WP_TESTS_DIR-/tmp/wordpress-tests-lib} 16 | WP_CORE_DIR=${WP_CORE_DIR-/tmp/wordpress/} 17 | 18 | download() { 19 | if [ `which curl` ]; then 20 | curl -s "$1" > "$2"; 21 | elif [ `which wget` ]; then 22 | wget -nv -O "$2" "$1" 23 | fi 24 | } 25 | 26 | if [[ $WP_VERSION =~ [0-9]+\.[0-9]+(\.[0-9]+)? ]]; then 27 | WP_TESTS_TAG="tags/$WP_VERSION" 28 | elif [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then 29 | WP_TESTS_TAG="trunk" 30 | else 31 | # http serves a single offer, whereas https serves multiple. we only want one 32 | download http://api.wordpress.org/core/version-check/1.7/ /tmp/wp-latest.json 33 | grep '[0-9]+\.[0-9]+(\.[0-9]+)?' /tmp/wp-latest.json 34 | LATEST_VERSION=$(grep -o '"version":"[^"]*' /tmp/wp-latest.json | sed 's/"version":"//') 35 | if [[ -z "$LATEST_VERSION" ]]; then 36 | echo "Latest WordPress version could not be found" 37 | exit 1 38 | fi 39 | WP_TESTS_TAG="tags/$LATEST_VERSION" 40 | fi 41 | 42 | set -ex 43 | 44 | install_wp() { 45 | 46 | if [ -d $WP_CORE_DIR ]; then 47 | return; 48 | fi 49 | 50 | mkdir -p $WP_CORE_DIR 51 | 52 | if [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then 53 | mkdir -p /tmp/wordpress-nightly 54 | download https://wordpress.org/nightly-builds/wordpress-latest.zip /tmp/wordpress-nightly/wordpress-nightly.zip 55 | unzip -q /tmp/wordpress-nightly/wordpress-nightly.zip -d /tmp/wordpress-nightly/ 56 | mv /tmp/wordpress-nightly/wordpress/* $WP_CORE_DIR 57 | else 58 | if [ $WP_VERSION == 'latest' ]; then 59 | local ARCHIVE_NAME='latest' 60 | else 61 | local ARCHIVE_NAME="wordpress-$WP_VERSION" 62 | fi 63 | download https://wordpress.org/${ARCHIVE_NAME}.tar.gz /tmp/wordpress.tar.gz 64 | tar --strip-components=1 -zxmf /tmp/wordpress.tar.gz -C $WP_CORE_DIR 65 | fi 66 | 67 | download https://raw.github.com/markoheijnen/wp-mysqli/master/db.php $WP_CORE_DIR/wp-content/db.php 68 | } 69 | 70 | install_test_suite() { 71 | # portable in-place argument for both GNU sed and Mac OSX sed 72 | if [[ $(uname -s) == 'Darwin' ]]; then 73 | local ioption='-i .bak' 74 | else 75 | local ioption='-i' 76 | fi 77 | 78 | # set up testing suite if it doesn't yet exist 79 | if [ ! -d $WP_TESTS_DIR ]; then 80 | # set up testing suite 81 | mkdir -p $WP_TESTS_DIR 82 | svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/includes/ $WP_TESTS_DIR/includes 83 | fi 84 | 85 | if [ ! -f wp-tests-config.php ]; then 86 | download https://develop.svn.wordpress.org/${WP_TESTS_TAG}/wp-tests-config-sample.php "$WP_TESTS_DIR"/wp-tests-config.php 87 | # remove all forward slashes in the end 88 | WP_CORE_DIR=$(echo $WP_CORE_DIR | sed "s:/\+$::") 89 | sed $ioption "s:dirname( __FILE__ ) . '/src/':'$WP_CORE_DIR/':" "$WP_TESTS_DIR"/wp-tests-config.php 90 | sed $ioption "s/youremptytestdbnamehere/$DB_NAME/" "$WP_TESTS_DIR"/wp-tests-config.php 91 | sed $ioption "s/yourusernamehere/$DB_USER/" "$WP_TESTS_DIR"/wp-tests-config.php 92 | sed $ioption "s/yourpasswordhere/$DB_PASS/" "$WP_TESTS_DIR"/wp-tests-config.php 93 | sed $ioption "s|localhost|${DB_HOST}|" "$WP_TESTS_DIR"/wp-tests-config.php 94 | fi 95 | 96 | } 97 | 98 | install_db() { 99 | 100 | if [ ${SKIP_DB_CREATE} = "true" ]; then 101 | return 0 102 | fi 103 | 104 | # parse DB_HOST for port or socket references 105 | local PARTS=(${DB_HOST//\:/ }) 106 | local DB_HOSTNAME=${PARTS[0]}; 107 | local DB_SOCK_OR_PORT=${PARTS[1]}; 108 | local EXTRA="" 109 | 110 | if ! [ -z $DB_HOSTNAME ] ; then 111 | if [ $(echo $DB_SOCK_OR_PORT | grep -e '^[0-9]\{1,\}$') ]; then 112 | EXTRA=" --host=$DB_HOSTNAME --port=$DB_SOCK_OR_PORT --protocol=tcp" 113 | elif ! [ -z $DB_SOCK_OR_PORT ] ; then 114 | EXTRA=" --socket=$DB_SOCK_OR_PORT" 115 | elif ! [ -z $DB_HOSTNAME ] ; then 116 | EXTRA=" --host=$DB_HOSTNAME --protocol=tcp" 117 | fi 118 | fi 119 | 120 | # create database 121 | mysqladmin create $DB_NAME --user="$DB_USER" --password="$DB_PASS"$EXTRA 122 | } 123 | 124 | install_wp 125 | install_test_suite 126 | install_db 127 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "BEForever/graphql-wp", 3 | "description": "GraphQL API for WordPress", 4 | "license": "GPL-3.0", 5 | "keywords": ["GraphQL"], 6 | "authors": [ 7 | { 8 | "name": "Edwin Cromley", 9 | "email": "edwincromleydev@gmail.com" 10 | } 11 | ], 12 | "require": { 13 | "webonyx/graphql-php": "dev-master" 14 | }, 15 | "require-dev": { 16 | "phpunit/phpunit": "4.0.*" 17 | }, 18 | "autoload": { 19 | "psr-4": { 20 | "BEForever\\WPGraphQL\\": "src/" 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", 5 | "This file is @generated automatically" 6 | ], 7 | "hash": "3c3f4db9961e2fdac12ac14de09085cc", 8 | "content-hash": "61511439a6eab541b476deb3007d8c35", 9 | "packages": [ 10 | { 11 | "name": "webonyx/graphql-php", 12 | "version": "dev-master", 13 | "source": { 14 | "type": "git", 15 | "url": "https://github.com/webonyx/graphql-php.git", 16 | "reference": "29670b378bda350a417e08a1509b5df322bfb353" 17 | }, 18 | "dist": { 19 | "type": "zip", 20 | "url": "https://api.github.com/repos/webonyx/graphql-php/zipball/29670b378bda350a417e08a1509b5df322bfb353", 21 | "reference": "29670b378bda350a417e08a1509b5df322bfb353", 22 | "shasum": "" 23 | }, 24 | "require": { 25 | "php": ">=5.4,<8.0-DEV" 26 | }, 27 | "require-dev": { 28 | "phpunit/phpunit": "^4.8" 29 | }, 30 | "type": "library", 31 | "autoload": { 32 | "psr-4": { 33 | "GraphQL\\": "src/" 34 | } 35 | }, 36 | "notification-url": "https://packagist.org/downloads/", 37 | "license": [ 38 | "BSD" 39 | ], 40 | "description": "A PHP port of GraphQL reference implementation", 41 | "homepage": "https://github.com/webonyx/graphql-php", 42 | "keywords": [ 43 | "api", 44 | "graphql" 45 | ], 46 | "time": "2016-10-24 10:07:52" 47 | } 48 | ], 49 | "packages-dev": [ 50 | { 51 | "name": "phpunit/php-code-coverage", 52 | "version": "2.0.17", 53 | "source": { 54 | "type": "git", 55 | "url": "https://github.com/sebastianbergmann/php-code-coverage.git", 56 | "reference": "c4e8e7725e351184a76544634855b8a9c405a6e3" 57 | }, 58 | "dist": { 59 | "type": "zip", 60 | "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/c4e8e7725e351184a76544634855b8a9c405a6e3", 61 | "reference": "c4e8e7725e351184a76544634855b8a9c405a6e3", 62 | "shasum": "" 63 | }, 64 | "require": { 65 | "php": ">=5.3.3", 66 | "phpunit/php-file-iterator": "~1.3", 67 | "phpunit/php-text-template": "~1.2", 68 | "phpunit/php-token-stream": "~1.3", 69 | "sebastian/environment": "~1.0", 70 | "sebastian/version": "~1.0" 71 | }, 72 | "require-dev": { 73 | "ext-xdebug": ">=2.1.4", 74 | "phpunit/phpunit": "~4" 75 | }, 76 | "suggest": { 77 | "ext-dom": "*", 78 | "ext-xdebug": ">=2.2.1", 79 | "ext-xmlwriter": "*" 80 | }, 81 | "type": "library", 82 | "extra": { 83 | "branch-alias": { 84 | "dev-master": "2.0.x-dev" 85 | } 86 | }, 87 | "autoload": { 88 | "classmap": [ 89 | "src/" 90 | ] 91 | }, 92 | "notification-url": "https://packagist.org/downloads/", 93 | "license": [ 94 | "BSD-3-Clause" 95 | ], 96 | "authors": [ 97 | { 98 | "name": "Sebastian Bergmann", 99 | "email": "sb@sebastian-bergmann.de", 100 | "role": "lead" 101 | } 102 | ], 103 | "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", 104 | "homepage": "https://github.com/sebastianbergmann/php-code-coverage", 105 | "keywords": [ 106 | "coverage", 107 | "testing", 108 | "xunit" 109 | ], 110 | "time": "2015-05-25 05:11:59" 111 | }, 112 | { 113 | "name": "phpunit/php-file-iterator", 114 | "version": "1.3.4", 115 | "source": { 116 | "type": "git", 117 | "url": "https://github.com/sebastianbergmann/php-file-iterator.git", 118 | "reference": "acd690379117b042d1c8af1fafd61bde001bf6bb" 119 | }, 120 | "dist": { 121 | "type": "zip", 122 | "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/acd690379117b042d1c8af1fafd61bde001bf6bb", 123 | "reference": "acd690379117b042d1c8af1fafd61bde001bf6bb", 124 | "shasum": "" 125 | }, 126 | "require": { 127 | "php": ">=5.3.3" 128 | }, 129 | "type": "library", 130 | "autoload": { 131 | "classmap": [ 132 | "File/" 133 | ] 134 | }, 135 | "notification-url": "https://packagist.org/downloads/", 136 | "include-path": [ 137 | "" 138 | ], 139 | "license": [ 140 | "BSD-3-Clause" 141 | ], 142 | "authors": [ 143 | { 144 | "name": "Sebastian Bergmann", 145 | "email": "sb@sebastian-bergmann.de", 146 | "role": "lead" 147 | } 148 | ], 149 | "description": "FilterIterator implementation that filters files based on a list of suffixes.", 150 | "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", 151 | "keywords": [ 152 | "filesystem", 153 | "iterator" 154 | ], 155 | "time": "2013-10-10 15:34:57" 156 | }, 157 | { 158 | "name": "phpunit/php-text-template", 159 | "version": "1.2.1", 160 | "source": { 161 | "type": "git", 162 | "url": "https://github.com/sebastianbergmann/php-text-template.git", 163 | "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" 164 | }, 165 | "dist": { 166 | "type": "zip", 167 | "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", 168 | "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", 169 | "shasum": "" 170 | }, 171 | "require": { 172 | "php": ">=5.3.3" 173 | }, 174 | "type": "library", 175 | "autoload": { 176 | "classmap": [ 177 | "src/" 178 | ] 179 | }, 180 | "notification-url": "https://packagist.org/downloads/", 181 | "license": [ 182 | "BSD-3-Clause" 183 | ], 184 | "authors": [ 185 | { 186 | "name": "Sebastian Bergmann", 187 | "email": "sebastian@phpunit.de", 188 | "role": "lead" 189 | } 190 | ], 191 | "description": "Simple template engine.", 192 | "homepage": "https://github.com/sebastianbergmann/php-text-template/", 193 | "keywords": [ 194 | "template" 195 | ], 196 | "time": "2015-06-21 13:50:34" 197 | }, 198 | { 199 | "name": "phpunit/php-timer", 200 | "version": "1.0.8", 201 | "source": { 202 | "type": "git", 203 | "url": "https://github.com/sebastianbergmann/php-timer.git", 204 | "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260" 205 | }, 206 | "dist": { 207 | "type": "zip", 208 | "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/38e9124049cf1a164f1e4537caf19c99bf1eb260", 209 | "reference": "38e9124049cf1a164f1e4537caf19c99bf1eb260", 210 | "shasum": "" 211 | }, 212 | "require": { 213 | "php": ">=5.3.3" 214 | }, 215 | "require-dev": { 216 | "phpunit/phpunit": "~4|~5" 217 | }, 218 | "type": "library", 219 | "autoload": { 220 | "classmap": [ 221 | "src/" 222 | ] 223 | }, 224 | "notification-url": "https://packagist.org/downloads/", 225 | "license": [ 226 | "BSD-3-Clause" 227 | ], 228 | "authors": [ 229 | { 230 | "name": "Sebastian Bergmann", 231 | "email": "sb@sebastian-bergmann.de", 232 | "role": "lead" 233 | } 234 | ], 235 | "description": "Utility class for timing", 236 | "homepage": "https://github.com/sebastianbergmann/php-timer/", 237 | "keywords": [ 238 | "timer" 239 | ], 240 | "time": "2016-05-12 18:03:57" 241 | }, 242 | { 243 | "name": "phpunit/php-token-stream", 244 | "version": "1.4.8", 245 | "source": { 246 | "type": "git", 247 | "url": "https://github.com/sebastianbergmann/php-token-stream.git", 248 | "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da" 249 | }, 250 | "dist": { 251 | "type": "zip", 252 | "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da", 253 | "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da", 254 | "shasum": "" 255 | }, 256 | "require": { 257 | "ext-tokenizer": "*", 258 | "php": ">=5.3.3" 259 | }, 260 | "require-dev": { 261 | "phpunit/phpunit": "~4.2" 262 | }, 263 | "type": "library", 264 | "extra": { 265 | "branch-alias": { 266 | "dev-master": "1.4-dev" 267 | } 268 | }, 269 | "autoload": { 270 | "classmap": [ 271 | "src/" 272 | ] 273 | }, 274 | "notification-url": "https://packagist.org/downloads/", 275 | "license": [ 276 | "BSD-3-Clause" 277 | ], 278 | "authors": [ 279 | { 280 | "name": "Sebastian Bergmann", 281 | "email": "sebastian@phpunit.de" 282 | } 283 | ], 284 | "description": "Wrapper around PHP's tokenizer extension.", 285 | "homepage": "https://github.com/sebastianbergmann/php-token-stream/", 286 | "keywords": [ 287 | "tokenizer" 288 | ], 289 | "time": "2015-09-15 10:49:45" 290 | }, 291 | { 292 | "name": "phpunit/phpunit", 293 | "version": "4.0.20", 294 | "source": { 295 | "type": "git", 296 | "url": "https://github.com/sebastianbergmann/phpunit.git", 297 | "reference": "de121ce8708b7ac7f628603d7682d0d57f528345" 298 | }, 299 | "dist": { 300 | "type": "zip", 301 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/de121ce8708b7ac7f628603d7682d0d57f528345", 302 | "reference": "de121ce8708b7ac7f628603d7682d0d57f528345", 303 | "shasum": "" 304 | }, 305 | "require": { 306 | "ext-dom": "*", 307 | "ext-json": "*", 308 | "ext-pcre": "*", 309 | "ext-reflection": "*", 310 | "ext-spl": "*", 311 | "php": ">=5.3.3", 312 | "phpunit/php-code-coverage": ">=2.0.0,<2.1.0", 313 | "phpunit/php-file-iterator": "~1.3.1", 314 | "phpunit/php-text-template": "~1.2", 315 | "phpunit/php-timer": "~1.0.2", 316 | "phpunit/phpunit-mock-objects": ">=2.0.0,<2.1.0", 317 | "sebastian/diff": "~1.1", 318 | "sebastian/environment": "~1.0", 319 | "sebastian/exporter": "~1.0.1", 320 | "sebastian/version": "~1.0.3", 321 | "symfony/yaml": "~2.0" 322 | }, 323 | "suggest": { 324 | "phpunit/php-invoker": "~1.1" 325 | }, 326 | "bin": [ 327 | "phpunit" 328 | ], 329 | "type": "library", 330 | "extra": { 331 | "branch-alias": { 332 | "dev-master": "4.0.x-dev" 333 | } 334 | }, 335 | "autoload": { 336 | "classmap": [ 337 | "src/" 338 | ] 339 | }, 340 | "notification-url": "https://packagist.org/downloads/", 341 | "include-path": [ 342 | "", 343 | "../../symfony/yaml/" 344 | ], 345 | "license": [ 346 | "BSD-3-Clause" 347 | ], 348 | "authors": [ 349 | { 350 | "name": "Sebastian Bergmann", 351 | "email": "sebastian@phpunit.de", 352 | "role": "lead" 353 | } 354 | ], 355 | "description": "The PHP Unit Testing framework.", 356 | "homepage": "http://www.phpunit.de/", 357 | "keywords": [ 358 | "phpunit", 359 | "testing", 360 | "xunit" 361 | ], 362 | "time": "2014-05-02 07:19:37" 363 | }, 364 | { 365 | "name": "phpunit/phpunit-mock-objects", 366 | "version": "2.0.10", 367 | "source": { 368 | "type": "git", 369 | "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", 370 | "reference": "e60bb929c50ae4237aaf680a4f6773f4ee17f0a2" 371 | }, 372 | "dist": { 373 | "type": "zip", 374 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/e60bb929c50ae4237aaf680a4f6773f4ee17f0a2", 375 | "reference": "e60bb929c50ae4237aaf680a4f6773f4ee17f0a2", 376 | "shasum": "" 377 | }, 378 | "require": { 379 | "php": ">=5.3.3", 380 | "phpunit/php-text-template": "~1.2" 381 | }, 382 | "require-dev": { 383 | "phpunit/phpunit": ">=4.0.0,<4.1.0" 384 | }, 385 | "suggest": { 386 | "ext-soap": "*" 387 | }, 388 | "type": "library", 389 | "extra": { 390 | "branch-alias": { 391 | "dev-master": "2.0.x-dev" 392 | } 393 | }, 394 | "autoload": { 395 | "classmap": [ 396 | "src/" 397 | ] 398 | }, 399 | "notification-url": "https://packagist.org/downloads/", 400 | "include-path": [ 401 | "" 402 | ], 403 | "license": [ 404 | "BSD-3-Clause" 405 | ], 406 | "authors": [ 407 | { 408 | "name": "Sebastian Bergmann", 409 | "email": "sb@sebastian-bergmann.de", 410 | "role": "lead" 411 | } 412 | ], 413 | "description": "Mock Object library for PHPUnit", 414 | "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", 415 | "keywords": [ 416 | "mock", 417 | "xunit" 418 | ], 419 | "time": "2014-06-12 07:19:48" 420 | }, 421 | { 422 | "name": "sebastian/diff", 423 | "version": "1.4.1", 424 | "source": { 425 | "type": "git", 426 | "url": "https://github.com/sebastianbergmann/diff.git", 427 | "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e" 428 | }, 429 | "dist": { 430 | "type": "zip", 431 | "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/13edfd8706462032c2f52b4b862974dd46b71c9e", 432 | "reference": "13edfd8706462032c2f52b4b862974dd46b71c9e", 433 | "shasum": "" 434 | }, 435 | "require": { 436 | "php": ">=5.3.3" 437 | }, 438 | "require-dev": { 439 | "phpunit/phpunit": "~4.8" 440 | }, 441 | "type": "library", 442 | "extra": { 443 | "branch-alias": { 444 | "dev-master": "1.4-dev" 445 | } 446 | }, 447 | "autoload": { 448 | "classmap": [ 449 | "src/" 450 | ] 451 | }, 452 | "notification-url": "https://packagist.org/downloads/", 453 | "license": [ 454 | "BSD-3-Clause" 455 | ], 456 | "authors": [ 457 | { 458 | "name": "Kore Nordmann", 459 | "email": "mail@kore-nordmann.de" 460 | }, 461 | { 462 | "name": "Sebastian Bergmann", 463 | "email": "sebastian@phpunit.de" 464 | } 465 | ], 466 | "description": "Diff implementation", 467 | "homepage": "https://github.com/sebastianbergmann/diff", 468 | "keywords": [ 469 | "diff" 470 | ], 471 | "time": "2015-12-08 07:14:41" 472 | }, 473 | { 474 | "name": "sebastian/environment", 475 | "version": "1.3.8", 476 | "source": { 477 | "type": "git", 478 | "url": "https://github.com/sebastianbergmann/environment.git", 479 | "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea" 480 | }, 481 | "dist": { 482 | "type": "zip", 483 | "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/be2c607e43ce4c89ecd60e75c6a85c126e754aea", 484 | "reference": "be2c607e43ce4c89ecd60e75c6a85c126e754aea", 485 | "shasum": "" 486 | }, 487 | "require": { 488 | "php": "^5.3.3 || ^7.0" 489 | }, 490 | "require-dev": { 491 | "phpunit/phpunit": "^4.8 || ^5.0" 492 | }, 493 | "type": "library", 494 | "extra": { 495 | "branch-alias": { 496 | "dev-master": "1.3.x-dev" 497 | } 498 | }, 499 | "autoload": { 500 | "classmap": [ 501 | "src/" 502 | ] 503 | }, 504 | "notification-url": "https://packagist.org/downloads/", 505 | "license": [ 506 | "BSD-3-Clause" 507 | ], 508 | "authors": [ 509 | { 510 | "name": "Sebastian Bergmann", 511 | "email": "sebastian@phpunit.de" 512 | } 513 | ], 514 | "description": "Provides functionality to handle HHVM/PHP environments", 515 | "homepage": "http://www.github.com/sebastianbergmann/environment", 516 | "keywords": [ 517 | "Xdebug", 518 | "environment", 519 | "hhvm" 520 | ], 521 | "time": "2016-08-18 05:49:44" 522 | }, 523 | { 524 | "name": "sebastian/exporter", 525 | "version": "1.0.2", 526 | "source": { 527 | "type": "git", 528 | "url": "https://github.com/sebastianbergmann/exporter.git", 529 | "reference": "c7d59948d6e82818e1bdff7cadb6c34710eb7dc0" 530 | }, 531 | "dist": { 532 | "type": "zip", 533 | "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/c7d59948d6e82818e1bdff7cadb6c34710eb7dc0", 534 | "reference": "c7d59948d6e82818e1bdff7cadb6c34710eb7dc0", 535 | "shasum": "" 536 | }, 537 | "require": { 538 | "php": ">=5.3.3" 539 | }, 540 | "require-dev": { 541 | "phpunit/phpunit": "~4.0" 542 | }, 543 | "type": "library", 544 | "extra": { 545 | "branch-alias": { 546 | "dev-master": "1.0.x-dev" 547 | } 548 | }, 549 | "autoload": { 550 | "classmap": [ 551 | "src/" 552 | ] 553 | }, 554 | "notification-url": "https://packagist.org/downloads/", 555 | "license": [ 556 | "BSD-3-Clause" 557 | ], 558 | "authors": [ 559 | { 560 | "name": "Jeff Welch", 561 | "email": "whatthejeff@gmail.com" 562 | }, 563 | { 564 | "name": "Volker Dusch", 565 | "email": "github@wallbash.com" 566 | }, 567 | { 568 | "name": "Bernhard Schussek", 569 | "email": "bschussek@2bepublished.at" 570 | }, 571 | { 572 | "name": "Sebastian Bergmann", 573 | "email": "sebastian@phpunit.de" 574 | }, 575 | { 576 | "name": "Adam Harvey", 577 | "email": "aharvey@php.net" 578 | } 579 | ], 580 | "description": "Provides the functionality to export PHP variables for visualization", 581 | "homepage": "http://www.github.com/sebastianbergmann/exporter", 582 | "keywords": [ 583 | "export", 584 | "exporter" 585 | ], 586 | "time": "2014-09-10 00:51:36" 587 | }, 588 | { 589 | "name": "sebastian/version", 590 | "version": "1.0.6", 591 | "source": { 592 | "type": "git", 593 | "url": "https://github.com/sebastianbergmann/version.git", 594 | "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6" 595 | }, 596 | "dist": { 597 | "type": "zip", 598 | "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", 599 | "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", 600 | "shasum": "" 601 | }, 602 | "type": "library", 603 | "autoload": { 604 | "classmap": [ 605 | "src/" 606 | ] 607 | }, 608 | "notification-url": "https://packagist.org/downloads/", 609 | "license": [ 610 | "BSD-3-Clause" 611 | ], 612 | "authors": [ 613 | { 614 | "name": "Sebastian Bergmann", 615 | "email": "sebastian@phpunit.de", 616 | "role": "lead" 617 | } 618 | ], 619 | "description": "Library that helps with managing the version number of Git-hosted PHP projects", 620 | "homepage": "https://github.com/sebastianbergmann/version", 621 | "time": "2015-06-21 13:59:46" 622 | }, 623 | { 624 | "name": "symfony/yaml", 625 | "version": "v2.8.13", 626 | "source": { 627 | "type": "git", 628 | "url": "https://github.com/symfony/yaml.git", 629 | "reference": "396784cd06b91f3db576f248f2402d547a077787" 630 | }, 631 | "dist": { 632 | "type": "zip", 633 | "url": "https://api.github.com/repos/symfony/yaml/zipball/396784cd06b91f3db576f248f2402d547a077787", 634 | "reference": "396784cd06b91f3db576f248f2402d547a077787", 635 | "shasum": "" 636 | }, 637 | "require": { 638 | "php": ">=5.3.9" 639 | }, 640 | "type": "library", 641 | "extra": { 642 | "branch-alias": { 643 | "dev-master": "2.8-dev" 644 | } 645 | }, 646 | "autoload": { 647 | "psr-4": { 648 | "Symfony\\Component\\Yaml\\": "" 649 | }, 650 | "exclude-from-classmap": [ 651 | "/Tests/" 652 | ] 653 | }, 654 | "notification-url": "https://packagist.org/downloads/", 655 | "license": [ 656 | "MIT" 657 | ], 658 | "authors": [ 659 | { 660 | "name": "Fabien Potencier", 661 | "email": "fabien@symfony.com" 662 | }, 663 | { 664 | "name": "Symfony Community", 665 | "homepage": "https://symfony.com/contributors" 666 | } 667 | ], 668 | "description": "Symfony Yaml Component", 669 | "homepage": "https://symfony.com", 670 | "time": "2016-10-21 20:59:10" 671 | } 672 | ], 673 | "aliases": [], 674 | "minimum-stability": "stable", 675 | "stability-flags": { 676 | "webonyx/graphql-php": 20 677 | }, 678 | "prefer-stable": false, 679 | "prefer-lowest": false, 680 | "platform": [], 681 | "platform-dev": [] 682 | } 683 | -------------------------------------------------------------------------------- /img/ChromieQL.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BE-Webdesign/wp-graphql/d0bded46c16a85e25bf989c65c8b6639d01093de/img/ChromieQL.png -------------------------------------------------------------------------------- /multisite.xml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | tests 16 | 17 | 18 | 19 | 20 | 21 | . 22 | 23 | 24 | ./lib 25 | ./plugin.php 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "name": "wp-graphql", 4 | "version": "0.1.0", 5 | "main": "Gruntfile.js", 6 | "author": "Edwin Cromley", 7 | "devDependencies": { 8 | "grunt": "~0.4.5", 9 | "grunt-wp-i18n": "~0.5.0", 10 | "grunt-wp-readme-to-markdown": "~1.0.0" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | ./tests/ 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | === WP GraphQL === 2 | Contributors: ChopinBach 3 | Donate link: N/A 4 | Tags: GraphQL 5 | Requires at least: 4.6.1 6 | Tested up to: 4.6.1 7 | Stable tag: N/A 8 | License: GPLv3 or later 9 | License URI: http://www.gnu.org/licenses/gpl-3.0.html 10 | 11 | Here is a short description of the plugin. This should be no more than 150 characters. No markup here. 12 | 13 | == Description == 14 | 15 | This is the long description. No limit, and you can use Markdown (as well as in the following sections). 16 | 17 | For backwards compatibility, if this section is missing, the full length of the short description will be used, and 18 | Markdown parsed. 19 | 20 | A few notes about the sections above: 21 | 22 | * "Contributors" is a comma separated list of wp.org/wp-plugins.org usernames 23 | * "Tags" is a comma separated list of tags that apply to the plugin 24 | * "Requires at least" is the lowest version that the plugin will work on 25 | * "Tested up to" is the highest version that you've *successfully used to test the plugin*. Note that it might work on 26 | higher versions... this is just the highest one you've verified. 27 | * Stable tag should indicate the Subversion "tag" of the latest stable version, or "trunk," if you use `/trunk/` for 28 | stable. 29 | 30 | Note that the `readme.txt` of the stable tag is the one that is considered the defining one for the plugin, so 31 | if the `/trunk/readme.txt` file says that the stable tag is `4.3`, then it is `/tags/4.3/readme.txt` that'll be used 32 | for displaying information about the plugin. In this situation, the only thing considered from the trunk `readme.txt` 33 | is the stable tag pointer. Thus, if you develop in trunk, you can update the trunk `readme.txt` to reflect changes in 34 | your in-development version, without having that information incorrectly disclosed about the current stable version 35 | that lacks those changes -- as long as the trunk's `readme.txt` points to the correct stable tag. 36 | 37 | If no stable tag is provided, it is assumed that trunk is stable, but you should specify "trunk" if that's where 38 | you put the stable version, in order to eliminate any doubt. 39 | 40 | == Installation == 41 | 42 | This section describes how to install the plugin and get it working. 43 | 44 | e.g. 45 | 46 | 1. Upload `plugin-name.php` to the `/wp-content/plugins/` directory 47 | 1. Activate the plugin through the 'Plugins' menu in WordPress 48 | 1. Place `` in your templates 49 | 50 | == Frequently Asked Questions == 51 | 52 | = A question that someone might have = 53 | 54 | An answer to that question. 55 | 56 | = What about foo bar? = 57 | 58 | Answer to foo bar dilemma. 59 | 60 | == Screenshots == 61 | 62 | 1. This screen shot description corresponds to screenshot-1.(png|jpg|jpeg|gif). Note that the screenshot is taken from 63 | the /assets directory or the directory that contains the stable readme.txt (tags or trunk). Screenshots in the /assets 64 | directory take precedence. For example, `/assets/screenshot-1.png` would win over `/tags/4.3/screenshot-1.png` 65 | (or jpg, jpeg, gif). 66 | 2. This is the second screen shot 67 | 68 | == Changelog == 69 | 70 | = 1.0 = 71 | * A change since the previous version. 72 | * Another change. 73 | 74 | = 0.5 = 75 | * List versions from most recent at top to oldest at bottom. 76 | 77 | == Upgrade Notice == 78 | 79 | = 1.0 = 80 | Upgrade notices describe the reason a user should upgrade. No more than 300 characters. 81 | 82 | = 0.5 = 83 | This version fixes a security related bug. Upgrade immediately. 84 | 85 | == Arbitrary section == 86 | 87 | You may provide arbitrary sections, in the same format as the ones above. This may be of use for extremely complicated 88 | plugins where more information needs to be conveyed that doesn't fit into the categories of "description" or 89 | "installation." Arbitrary sections will be shown below the built-in sections outlined above. 90 | 91 | == A brief Markdown Example == 92 | 93 | Ordered list: 94 | 95 | 1. Some feature 96 | 1. Another feature 97 | 1. Something else about the plugin 98 | 99 | Unordered list: 100 | 101 | * something 102 | * something else 103 | * third thing 104 | 105 | Here's a link to [WordPress](http://wordpress.org/ "Your favorite software") and one to [Markdown's Syntax Documentation][markdown syntax]. 106 | Titles are optional, naturally. 107 | 108 | [markdown syntax]: http://daringfireball.net/projects/markdown/syntax 109 | "Markdown is what the parser uses to process much of the readme file" 110 | 111 | Markdown uses email style notation for blockquotes and I've been told: 112 | > Asterisks for *emphasis*. Double it up for **strong**. 113 | 114 | `` 115 | -------------------------------------------------------------------------------- /src/AppContext.php: -------------------------------------------------------------------------------- 1 | types = $types; 35 | } 36 | 37 | /** 38 | * Utility for returning WP_Query args as GraphQL argument schema. 39 | * 40 | * @return array $args Array of valid arguments for posts collections. 41 | */ 42 | public function args() { 43 | $args = array( 44 | // Author parameters. 45 | 'author' => array( 46 | 'type' => $this->types->int(), 47 | 'description' => esc_html__( 'Restricts a collection based on author ID.', 'wp-graphql' ), 48 | ), 49 | 'author_name' => array( 50 | 'type' => $this->types->string(), 51 | 'description' => esc_html__( 'Restricts a collection based on author slug. This field matches against the WP_User->user_nicename property.', 'wp-graphql' ), 52 | ), 53 | 'author__in' => array( 54 | 'type' => $this->types->listOf( $this->types->int() ), 55 | 'description' => esc_html__( 'Restricts a collection based on a list of author IDs', 'wp-graphql' ), 56 | ), 57 | 'author__not_in' => array( 58 | 'type' => $this->types->listOf( $this->types->int() ), 59 | 'description' => esc_html__( 'Removes items from a collection based on a list of author IDs', 'wp-graphql' ), 60 | ), 61 | // Category parameters. 62 | 'category' => array( 63 | 'type' => $this->types->int(), 64 | 'description' => esc_html__( 'Restricts a collection based on category ID.', 'wp-graphql' ), 65 | ), 66 | 'category_name' => array( 67 | 'type' => $this->types->string(), 68 | 'description' => esc_html__( 'Restricts a collection based on category slug.', 'wp-graphql' ), 69 | ), 70 | 'category_and' => array( 71 | 'type' => $this->types->listOf( $this->types->int() ), 72 | 'description' => esc_html__( 'Restricts a collection based on posts that have each category ID.', 'wp-graphql' ), 73 | ), 74 | 'category__in' => array( 75 | 'type' => $this->types->listOf( $this->types->int() ), 76 | 'description' => esc_html__( 'Restricts a collection based on a list of category IDs', 'wp-graphql' ), 77 | ), 78 | 'category__not_in' => array( 79 | 'type' => $this->types->listOf( $this->types->int() ), 80 | 'description' => esc_html__( 'Removes items from a collection based on a list of category IDs', 'wp-graphql' ), 81 | ), 82 | // Tag parameters. 83 | 'tag' => array( 84 | 'type' => $this->types->int(), 85 | 'description' => esc_html__( 'Restricts a collection based on tag ID.', 'wp-graphql' ), 86 | ), 87 | 'tag_and' => array( 88 | 'type' => $this->types->listOf( $this->types->int() ), 89 | 'description' => esc_html__( 'Restricts a collection based on tag slug.', 'wp-graphql' ), 90 | ), 91 | 'tag__in' => array( 92 | 'type' => $this->types->listOf( $this->types->int() ), 93 | 'description' => esc_html__( 'Restricts a collection based on a list of category IDs', 'wp-graphql' ), 94 | ), 95 | 'tag__not_in' => array( 96 | 'type' => $this->types->listOf( $this->types->int() ), 97 | 'description' => esc_html__( 'Removes items from a collection based on a list of tag IDs', 'wp-graphql' ), 98 | ), 99 | 'tag_slug__and' => array( 100 | 'type' => $this->types->listOf( $this->types->string() ), 101 | 'description' => esc_html__( 'Restricts a collection based on posts that have each tag ID.', 'wp-graphql' ), 102 | ), 103 | 'tag_slug__in' => array( 104 | 'type' => $this->types->listOf( $this->types->string() ), 105 | 'description' => esc_html__( 'Restricts a collection based on tag slugs.', 'wp-graphql' ), 106 | ), 107 | // Taxonomy query parameters. This must be composed of another type. 108 | // Search parameters. 109 | 's' => array( 110 | 'type' => $this->types->string(), 111 | 'description' => esc_html__( 'Search phrase to use.', 'wp-graphql' ), 112 | ), 113 | // Post parameters. 114 | 'p' => array( 115 | 'type' => $this->types->int(), 116 | 'description' => esc_html__( 'Restricts a collection based on post ID.', 'wp-graphql' ), 117 | ), 118 | 'name' => array( 119 | 'type' => $this->types->string(), 120 | 'description' => esc_html__( 'Restricts a collection based on post slug.', 'wp-graphql' ), 121 | ), 122 | 'page_id' => array( 123 | 'type' => $this->types->int(), 124 | 'description' => esc_html__( 'Restricts a collection based on a page ID', 'wp-graphql' ), 125 | ), 126 | 'page_slug' => array( 127 | 'type' => $this->types->string(), 128 | 'description' => esc_html__( 'Restricts a collection based on a page slug', 'wp-graphql' ), 129 | ), 130 | 'post_parent' => array( 131 | 'type' => $this->types->int(), 132 | 'description' => esc_html__( 'Restricts items from a collection based on post parent ID', 'wp-graphql' ), 133 | ), 134 | 'post_parent__in' => array( 135 | 'type' => $this->types->listOf( $this->types->int() ), 136 | 'description' => esc_html__( 'Restricts items from a collection based on a list of post parent IDs', 'wp-graphql' ), 137 | ), 138 | 'post_parent__not_in' => array( 139 | 'type' => $this->types->listOf( $this->types->int() ), 140 | 'description' => esc_html__( 'Removes items from a collection based on a list of post parent IDs', 'wp-graphql' ), 141 | ), 142 | 'post__in' => array( 143 | 'type' => $this->types->listOf( $this->types->int() ), 144 | 'description' => esc_html__( 'Restrics items from a collection based on a list of post IDs', 'wp-graphql' ), 145 | ), 146 | 'post__not_in' => array( 147 | 'type' => $this->types->listOf( $this->types->int() ), 148 | 'description' => esc_html__( 'Removes items from a collection based on a list of post IDs', 'wp-graphql' ), 149 | ), 150 | 'post_name__in' => array( 151 | 'type' => $this->types->listOf( $this->types->string() ), 152 | 'description' => esc_html__( 'Restricts items from a collection based on a list of post slugs', 'wp-graphql' ), 153 | ), 154 | // Post type parameters. This should be changed to a union type at some point for consistency with WP_Query. 155 | 'post_type' => array( 156 | 'type' => $this->types->listOf( $this->types->string() ), 157 | 'description' => esc_html__( 'Restricts items from a collection based on a list of post types', 'wp-graphql' ), 158 | ), 159 | // Post type parameters. This should be changed to a union type at some point for consistency with WP_Query. 160 | 'post_status' => array( 161 | 'type' => $this->types->listOf( $this->types->string() ), 162 | 'description' => esc_html__( 'Restricts items from a collection based on a list of post stati', 'wp-graphql' ), 163 | ), 164 | // Pagination parameters will be handled by WP GraphQL differently to make collections Relay compliant. 165 | 'first' => array( 166 | 'type' => $this->types->int(), 167 | 'description' => esc_html__( 'The pagination limit. This is equivalent to posts_per_page for WP_Query.', 'wp-graphql' ), 168 | 'defaultValue' => 10, 169 | ), 170 | 'after' => array( 171 | 'type' => $this->types->int(), 172 | 'description' => esc_html__( 'The pagination offset. This is equivalent to offset for WP_Query.', 'wp-graphql' ), 173 | 'defaultValue' => 0, 174 | ), 175 | 'ignore_sticky_posts' => array( 176 | 'type' => $this->types->boolean(), 177 | 'description' => esc_html__( 'A boolean flag for whether to ignore sticky posts.', 'wp-graphql' ), 178 | 'defaultValue' => false, 179 | ), 180 | // Order/orderby params. Currently WP GraphQL will not support multiple orderby params as it will be difficult to support arbitrary key value multiple orderby params. 181 | 'order' => array( 182 | 'type' => $this->types->string(), 183 | 'description' => esc_html__( 'Orders a collection either ascending or descending based on specified orderby.', 'wp-graphql' ), 184 | 'defaultValue' => 'DESC', 185 | ), 186 | 'orderby' => array( 187 | 'type' => $this->types->string(), 188 | 'description' => esc_html__( 'Restricts a collection based on a page slug', 'wp-graphql' ), 189 | 'defaultValue' => 'date', 190 | ), 191 | // Date parameters not supported yet. 192 | // Meta query parameters not supported yet. 193 | // Permissions paramaters not supported yet. 194 | // Mime type params. 195 | 'post_mime_type' => array( 196 | 'type' => $this->types->string(), 197 | 'description' => esc_html__( 'Restricts a collection of attachments based on mime_type', 'wp-graphql' ), 198 | ), 199 | // Caching parameters are not supported, and probably will not be. 200 | ); 201 | 202 | // Add a filter here for extensibility. 203 | return $args; 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /src/Type/AvatarType.php: -------------------------------------------------------------------------------- 1 | definition = new ObjectType([ 21 | 'name' => 'Avatar', 22 | 'fields' => function() use ( $types ) { 23 | return [ 24 | 'size' => array( 25 | 'type' => $types->int(), 26 | 'description' => esc_html__( 'The size of the avatar in pixels. A value of 96 will match a 96px x 96px gravatar image.', 'wp-graphql' ), 27 | ), 28 | 'height' => array( 29 | 'type' => $types->int(), 30 | 'description' => esc_html__( 'Height of the avatar image.', 'wp-graphql' ), 31 | ), 32 | 'width' => array( 33 | 'type' => $types->int(), 34 | 'description' => esc_html__( 'Width of the avatar image.', 'wp-graphql' ), 35 | ), 36 | 'default' => array( 37 | 'type' => $types->string(), 38 | 'description' => esc_html__( "URL for the default image or a default type. Accepts '404' (return a 404 instead of a default image), 'retro' (8bit), 'monsterid' (monster), 'wavatar' (cartoon face), 'indenticon' (the 'quilt'), 'mystery', 'mm', or 'mysteryman' (The Oyster Man), 'blank' (transparent GIF), or 'gravatar_default' (the Gravatar logo).", 'wp-graphql' ), 39 | ), 40 | 'force_default' => array( 41 | 'type' => $types->boolean(), 42 | 'description' => esc_html__( 'Whether to always show the default image, never the Gravatar.', 'wp-graphql' ), 43 | ), 44 | 'rating' => array( 45 | 'type' => $types->string(), 46 | 'description' => esc_html__( "What rating to display avatars up to. Accepts 'G', 'PG', 'R', 'X', and are judged in that order.", 'wp-graphql' ), 47 | ), 48 | 'scheme' => array( 49 | 'type' => $types->string(), 50 | 'description' => esc_html__( 'Type of url scheme to use. Typically HTTP vs. HTTPS.', 'wp-graphql' ), 51 | ), 52 | //'processed_args' => $types->string(), 53 | 'extra_attr' => array( 54 | 'type' => $types->string(), 55 | 'description' => esc_html__( 'HTML attributes to insert in the IMG element. Is not sanitized.', 'wp-graphql' ), 56 | ), 57 | 'found_avatar' => array( 58 | 'type' => $types->boolean(), 59 | 'description' => esc_html__( 'Whether the avatar was successfully found.', 'wp-graphql' ), 60 | ), 61 | 'url' => array( 62 | 'type' => $types->string(), 63 | 'description' => esc_html__( 'URL for the gravatar image source.', 'wp-graphql' ), 64 | ), 65 | ]; 66 | }, 67 | 'description' => esc_html__( 'Avatars are profile images for users. WordPress by default uses the Gravatar service to host and fetch avatars from.', 'wp-graphql' ), 68 | 'resolveField' => function( $value, $args, $context, ResolveInfo $info ) { 69 | if ( method_exists( $this, $info->fieldName ) ) { 70 | return $this->{$info->fieldName}( $value, $args, $context, $info ); 71 | } else { 72 | if ( isset( $value[ $info->fieldName ] ) ) { 73 | return $value[ $info->fieldName ]; 74 | } 75 | 76 | return $value->{$info->fieldName}; 77 | } 78 | }, 79 | ]); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/Type/BaseType.php: -------------------------------------------------------------------------------- 1 | definition; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Type/CommentType.php: -------------------------------------------------------------------------------- 1 | definition = new ObjectType([ 13 | 'name' => 'Comment', 14 | 'fields' => function() use ( $types ) { 15 | return array( 16 | 'id' => array( 17 | 'type' => $types->id(), 18 | 'description' => esc_html__( 'The id field for comments matches the comment id. This field is equivalent to WP_Comment->comment_ID and the value matching the `comment_ID` column in SQL.', 'wp-graphql' ), 19 | ), 20 | 'post' => array( 21 | 'type' => $types->post(), 22 | 'description' => esc_html__( 'The post field for comments matches the post comment is assigned to. This field is equivalent to \WP_Post::get_instance( WP_Comment->comment_post_ID ) and the post matching the `comment_post_ID` column in SQL.', 'wp-graphql' ), 23 | ), 24 | 'author' => array( 25 | 'type' => $types->user(), 26 | 'description' => esc_html__( 'The post field for comments matches the post id the comment is assigned to. This field is equivalent to WP_Comment->comment_post_ID and the value matching the `comment_post_ID` column in SQL.', 'wp-graphql' ), 27 | ), 28 | 'author_ip' => array( 29 | 'type' => $types->string(), 30 | 'description' => esc_html__( 'IP address for the author. This field is equivalent to WP_Comment->comment_author_IP and the value matching the `comment_author_IP` column in SQL.', 'wp-graphql' ), 31 | ), 32 | 'date' => array( 33 | 'type' => $types->string(), 34 | 'description' => esc_html__( 'Date the comment was posted in local time. This field is equivalent to WP_Comment->date and the value matching the `date` column in SQL.', 'wp-graphql' ), 35 | ), 36 | 'date_gmt' => array( 37 | 'type' => $types->string(), 38 | 'description' => esc_html__( 'Date the comment was posted in GMT. This field is equivalent to WP_Comment->date_gmt and the value matching the `date_gmt` column in SQL.', 'wp-graphql' ), 39 | ), 40 | 'content' => array( 41 | 'type' => $types->string(), 42 | 'description' => esc_html__( 'Content of the comment. This field is equivalent to WP_Comment->comment_content and the value matching the `comment_content` column in SQL.', 'wp-graphql' ), 43 | ), 44 | 'karma' => array( 45 | 'type' => $types->int(), 46 | 'description' => esc_html__( 'Karma value for the comment. This field is equivalent to WP_Comment->comment_karma and the value matching the `comment_karma` column in SQL.', 'wp-graphql' ), 47 | ), 48 | 'approved' => array( 49 | 'type' => $types->string(), 50 | 'description' => esc_html__( 'The approval status of the comment. This field is equivalent to WP_Comment->comment_approved and the value matching the `comment_approved` column in SQL.', 'wp-graphql' ), 51 | ), 52 | 'agent' => array( 53 | 'type' => $types->string(), 54 | 'description' => esc_html__( 'User agent used to post the comment. This field is equivalent to WP_Comment->comment_agent and the value matching the `comment_agent` column in SQL.', 'wp-graphql' ), 55 | ), 56 | 'type' => array( 57 | 'type' => $types->string(), 58 | 'description' => esc_html__( 'Type of comment. This field is equivalent to WP_Comment->comment_type and the value matching the `comment_type` column in SQL.', 'wp-graphql' ), 59 | ), 60 | 'parent' => array( 61 | 'type' => $types->comment(), 62 | 'description' => esc_html__( 'Parent comment of current comment. This field is equivalent to the WP_Comment instance matching the WP_Comment->comment_parent ID.', 'wp-graphql' ), 63 | ), 64 | 'children' => [ 65 | 'type' => $types->listOf( $types->comment() ), 66 | 'description' => 'Returns comments based on collection args', 67 | 'args' => [ 68 | // Limit and after are equivalent to per_page and offset. 69 | 'first' => array( 70 | 'type' => $types->int(), 71 | 'description' => esc_html__( 'The number of comment children to query for. First is pretty much the same as LIMIT in SQL, or a `per_page` parameter in pagination.', 'wp-graphql' ), 72 | 'defaultValue' => 10, 73 | ), 74 | 'after' => array( 75 | 'type' => $types->int(), 76 | 'description' => esc_html__( 'The offset for the query.', 'wp-graphql' ), 77 | 'defaultValue' => 0, 78 | ), 79 | ], 80 | ], 81 | ); 82 | }, 83 | 'interfaces' => [ 84 | $types->node(), 85 | ], 86 | 'description' => esc_html__( 'Comments are used to provide user created context to a post or other comments. The comment type internally matches a WP_Comment.', 'wp-graphql' ), 87 | 'resolveField' => function( $value, $args, $context, ResolveInfo $info ) { 88 | if ( method_exists( $this, $info->fieldName ) ) { 89 | return $this->{$info->fieldName}( $value, $args, $context, $info ); 90 | } else { 91 | return $value->{$info->fieldName}; 92 | } 93 | }, 94 | ]); 95 | } 96 | 97 | // Testing to see if it will resolve based on interface. 98 | public function id( \WP_Comment $comment, $args, AppContext $context ) { 99 | return $comment->comment_ID; 100 | } 101 | 102 | public function post( \WP_Comment $comment, $args, AppContext $context ) { 103 | return \WP_Post::get_instance( $comment->comment_post_ID ); 104 | } 105 | 106 | /** 107 | * $comment->user_id for whatever reason is the author id for the author. 108 | * Do not confuse this with $comment->author 109 | */ 110 | public function author( \WP_Comment $comment, $args, AppContext $context ) { 111 | return new \WP_User( $comment->user_id ); 112 | } 113 | 114 | public function author_ip( \WP_Comment $comment, $args, AppContext $context ) { 115 | return $comment->comment_author_IP; 116 | } 117 | 118 | public function content( \WP_Comment $comment, $args, AppContext $context ) { 119 | return $comment->comment_content; 120 | } 121 | 122 | public function karma( \WP_Comment $comment, $args, AppContext $context ) { 123 | return $comment->comment_karma; 124 | } 125 | 126 | public function approved( \WP_Comment $comment, $args, AppContext $context ) { 127 | return $comment->comment_approved; 128 | } 129 | 130 | public function agent( \WP_Comment $comment, $args, AppContext $context ) { 131 | return $comment->comment_agent; 132 | } 133 | 134 | public function type( \WP_Comment $comment, $args, AppContext $context ) { 135 | return $comment->comment_type; 136 | } 137 | 138 | public function parent( \WP_Comment $comment, $args, AppContext $context ) { 139 | return get_comment( $comment->comment_parent ); 140 | } 141 | 142 | /** 143 | * ID of comment author. 144 | */ 145 | public function user_id( \WP_Comment $comment, $args, AppContext $context ) { 146 | return $comment->user_id; 147 | } 148 | 149 | /** 150 | * Comments field resolver. 151 | * 152 | * @param \WP_Comment $comment Value for the resolver. 153 | * @param array $args List of arguments for this resolver. 154 | * @param AppContext $context Context object for the Application. 155 | * @return array List of WP_Comment objects. 156 | */ 157 | public function children( \WP_Comment $comment, $args, AppContext $context ) { 158 | $query_args = array( 159 | 'parent' => $comment->comment_ID, 160 | ); 161 | 162 | if ( isset( $args['first'] ) ) { 163 | $query_args['number'] = $args['first']; 164 | } 165 | 166 | if ( isset( $args['after'] ) ) { 167 | $query_args['offset'] = $args['after']; 168 | } 169 | 170 | $comments_query = new \WP_Comment_Query( $query_args ); 171 | $comments = $comments_query->get_comments(); 172 | 173 | return ! empty( $comments ) ? $comments : null; 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /src/Type/Enum/README.md: -------------------------------------------------------------------------------- 1 | # Enumerable Types 2 | 3 | This section is reserved for enumerable types aka EnumTypes. 4 | 5 | ## Description 6 | 7 | Enumerable types are used to specify types whose value can only match a limited 8 | set of predefined values. For instance you might create an enumerable type for 9 | periods in "Classical Music". Our EnumType could have Baroque, Classical, and 10 | Romantic all as values. Then our Composer ObjectType could have a field named 11 | era. That era field would be an instance of EraEnumType, meaning its value could 12 | only match Baroque, Classical, or Romantic. You can see that is kind of limiting 13 | but that is the whole point of EnumTypes. If you need to limit values to a 14 | certain set, create an EnumType. 15 | -------------------------------------------------------------------------------- /src/Type/MenuItemType.php: -------------------------------------------------------------------------------- 1 | definition = new ObjectType([ 13 | 'name' => 'MenuItem', 14 | 'fields' => function() use ( $types ) { 15 | return array( 16 | 'id' => array( 17 | 'type' => $types->id(), 18 | 'description' => esc_html__( 'ID of the nav menu item.', 'wp-graphql' ), 19 | ), 20 | 'title' => array( 21 | 'type' => $types->string(), 22 | 'description' => esc_html__( 'Title of the nav menu item. This is what is displayed visually in a menu as text.', 'wp-graphql' ), 23 | ), 24 | 'type' => array( 25 | 'type' => $types->string(), 26 | 'description' => esc_html__( 'The type relating the object being displayed in the type.', 'wp-graphql' ), 27 | ), 28 | 'object_id' => array( 29 | 'type' => $types->id(), 30 | 'description' => esc_html__( 'The ID of the object the menu item relates to.', 'wp-graphql' ), 31 | ), 32 | 'object' => array( 33 | 'type' => $types->string(), 34 | 'description' => esc_html__( 'The serialized object that the menu item represents.', 'wp-graphql' ), 35 | ), 36 | 'target' => array( 37 | 'type' => $types->string(), 38 | 'description' => esc_html__( 'Target attribute for the link.', 'wp-graphql' ), 39 | ), 40 | 'xfn' => array( 41 | 'type' => $types->string(), 42 | 'description' => esc_html__( 'Link relationship.', 'wp-graphql' ), 43 | ), 44 | 'url' => array( 45 | 'type' => $types->string(), 46 | 'description' => esc_html__( 'URL for the nav menu item.', 'wp-graphql' ), 47 | ), 48 | ); 49 | }, 50 | 'interfaces' => [ 51 | $types->node(), 52 | ], 53 | 'description' => esc_html__( 'Navigation menu items are the individual items assigned to a menu. These are rendered as the links in a navigation menu.', 'wp-graphql' ), 54 | 'resolveField' => function( $value, $args, $context, ResolveInfo $info ) { 55 | if ( method_exists( $this, $info->fieldName ) ) { 56 | return $this->{$info->fieldName}( $value, $args, $context, $info ); 57 | } else { 58 | return $value->{$info->fieldName}; 59 | } 60 | }, 61 | ]); 62 | } 63 | 64 | public function id( \WP_Post $menu_item, $args, AppContext $context) { 65 | return $menu_item->ID; 66 | } 67 | 68 | public function title( \WP_Post $menu_item, $args, AppContext $context) { 69 | $title = $menu_item->post_title; 70 | 71 | if ( empty( $title ) ) { 72 | $post_id = get_post_meta( $menu_item->ID, '_menu_item_object_id', true ); 73 | $title = get_the_title( $post_id ); 74 | } 75 | 76 | return $title; 77 | } 78 | 79 | public function type( \WP_Post $menu_item, $args, AppContext $context) { 80 | return get_post_meta( $menu_item->ID, '_menu_item_type', true ); 81 | } 82 | 83 | public function object_id( \WP_Post $menu_item, $args, AppContext $context) { 84 | return get_post_meta( $menu_item->ID, '_menu_item_object_id', true ); 85 | } 86 | 87 | public function object( \WP_Post $menu_item, $args, AppContext $context) { 88 | return get_post_meta( $menu_item->ID, '_menu_item_object', true ); 89 | } 90 | 91 | public function target( \WP_Post $menu_item, $args, AppContext $context) { 92 | return get_post_meta( $menu_item->ID, '_menu_item_target', true ); 93 | } 94 | 95 | public function xfn( \WP_Post $menu_item, $args, AppContext $context) { 96 | return get_post_meta( $menu_item->ID, '_menu_item_xfn', true ); 97 | } 98 | 99 | public function url( \WP_Post $menu_item, $args, AppContext $context) { 100 | $url = get_post_meta( $menu_item->ID, '_menu_item_url', true ); 101 | 102 | if ( empty( $url ) ) { 103 | $post_id = get_post_meta( $menu_item->ID, '_menu_item_object_id', true ); 104 | $url = get_permalink( $post_id ); 105 | } 106 | 107 | return $url; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/Type/MenuLocationType.php: -------------------------------------------------------------------------------- 1 | definition = new ObjectType([ 13 | 'name' => 'MenuLocation', 14 | 'fields' => function() use ( $types ) { 15 | return array( 16 | 'name' => array( 17 | 'type' => $types->string(), 18 | 'description' => esc_html__( 'Display name of the registered menu location.', 'wp-graphql' ), 19 | ), 20 | 'slug' => array( 21 | 'type' => $types->string(), 22 | 'description' => esc_html__( 'The URL friendly identified for the registered menu location.', 'wp-graphql' ), 23 | ), 24 | 'active_menu' => array( 25 | 'type' => $types->menu(), 26 | 'description' => esc_html__( 'The active menu assigned to this registered menu locations.', 'wp-graphql' ), 27 | ), 28 | ); 29 | }, 30 | 'description' => esc_html__( 'Menu locations are typically registered by the active theme. They may include social menus, or the primary menu, or custom menu widgets.', 'wp-graphql' ), 31 | 'resolveField' => function( $value, $args, $context, ResolveInfo $info ) { 32 | if ( method_exists( $this, $info->fieldName ) ) { 33 | return $this->{$info->fieldName}( $value, $args, $context, $info ); 34 | } else { 35 | return $value->{$info->fieldName}; 36 | } 37 | }, 38 | ]); 39 | } 40 | 41 | public function slug( array $menu_location, $args, AppContext $context ) { 42 | return $menu_location['slug']; 43 | } 44 | 45 | public function name( array $menu_location, $args, AppContext $context ) { 46 | return $menu_location['name']; 47 | } 48 | 49 | public function active_menu( array $menu_location, $args, AppContext $context ) { 50 | $locations = get_nav_menu_locations(); 51 | 52 | $menu = null; 53 | 54 | // Returns the ID of the menu item currently active in this location. 55 | if ( isset( $locations[ $menu_location['slug'] ] ) ) { 56 | $menu = get_term( $locations[ $menu_location['slug'] ] ); 57 | } 58 | 59 | return $menu; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/Type/MenuType.php: -------------------------------------------------------------------------------- 1 | definition = new ObjectType([ 13 | 'name' => 'Menu', 14 | 'fields' => function() use ( $types ) { 15 | return array( 16 | 'id' => array( 17 | 'type' => $types->id(), 18 | 'description' => esc_html__( 'ID of the menu. Equivalent to WP_Term->term_id.', 'wp-graphql' ), 19 | ), 20 | 'name' => array( 21 | 'type' => $types->string(), 22 | 'description' => esc_html__( 'Display name of the menu. Equivalent to WP_Term->name.', 'wp-graphql' ), 23 | ), 24 | 'slug' => array( 25 | 'type' => $types->string(), 26 | 'description' => esc_html__( 'The url friendly name of the menu. Equivalent to WP_Term->slug', 'wp-graphql' ), 27 | ), 28 | 'group' => array( 29 | 'type' => $types->string(), 30 | 'description' => esc_html__( 'Group of the menu. Groups are useful as secondary indexes for SQL. Equivalent to WP_Term->term_group.', 'wp-graphql' ), 31 | ), 32 | 'items' => array( 33 | 'type' => $types->listOf( $types->menu_item() ), 34 | 'description' => esc_html__( 'The nav menu items assigned to the menu.', 'wp-graphql' ), 35 | ), 36 | ); 37 | }, 38 | 'interfaces' => [ 39 | $types->node(), 40 | ], 41 | 'description' => esc_html__( 'Menus are the containers for navigation items. Menus can be assigned to menu locations, which are typically registered by the active theme.', 'wp-graphql' ), 42 | 'resolveField' => function( $value, $args, $context, ResolveInfo $info ) { 43 | if ( method_exists( $this, $info->fieldName ) ) { 44 | return $this->{$info->fieldName}( $value, $args, $context, $info ); 45 | } else { 46 | return $value->{$info->fieldName}; 47 | } 48 | }, 49 | ]); 50 | } 51 | 52 | public function id( \WP_Term $menu, $args, AppContext $context) { 53 | return $menu->term_id; 54 | } 55 | 56 | public function group( \WP_Term $menu, $args, AppContext $context) { 57 | return $menu->term_group; 58 | } 59 | 60 | public function items( \WP_Term $menu, $args, AppContext $context) { 61 | return wp_get_nav_menu_items( $menu->name ); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Type/NodeType.php: -------------------------------------------------------------------------------- 1 | definition = new InterfaceType([ 29 | 'name' => 'Node', 30 | 'fields' => [ 31 | 'id' => $types->id() 32 | ], 33 | 'resolveType' => function ( $object ) use ( $types ) { 34 | return $this->resolveType( $object, $types ); 35 | }, 36 | ]); 37 | } 38 | 39 | /** 40 | * Type resolver used for introspection. 41 | * 42 | * @param mixed $object What type the interface is using. 43 | * @param TypeSystem $types Current TypeSystem. 44 | */ 45 | public function resolveType( $object, TypeSystem $types ) { 46 | if ( $object instanceof User ) { 47 | return $types->user(); 48 | } elseif ( $object instanceof Post ) { 49 | return $types->post(); 50 | } elseif ( $object instanceof Comment ) { 51 | return $types->comment(); 52 | } elseif ( $object instanceof Term ) { 53 | return $types->term(); 54 | } elseif ( $object instanceof MenuItem ) { 55 | return $types->menu_item(); 56 | } elseif ( $object instanceof Menu ) { 57 | return $types->menu(); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Type/PluginType.php: -------------------------------------------------------------------------------- 1 | definition = new ObjectType([ 13 | 'name' => 'Plugin', 14 | 'fields' => function() use ( $types ) { 15 | return array( 16 | 'name' => array( 17 | 'type' => $types->string(), 18 | 'description' => esc_html__( 'Display name of the plugin.', 'wp-graphql' ), 19 | ), 20 | 'plugin_uri' => array( 21 | 'type' => $types->string(), 22 | 'description' => esc_html__( 'URI for the plugin website. This is useful for directing users for support requests etc.', 'wp-graphql' ), 23 | ), 24 | 'description' => array( 25 | 'type' => $types->string(), 26 | 'description' => esc_html__( 'Description of the plugin.', 'wp-graphql' ), 27 | ), 28 | 'author' => array( 29 | 'type' => $types->string(), 30 | 'description' => esc_html__( 'Name of the plugin author(s), may also be a company name.', 'wp-graphql' ), 31 | ), 32 | 'author_uri' => array( 33 | 'type' => $types->string(), 34 | 'description' => esc_html__( 'URI for the related author(s)/company website.', 'wp-graphql' ), 35 | ), 36 | 'version' => array( 37 | 'type' => $types->string(), 38 | 'description' => esc_html__( 'Current version of the plugin.', 'wp-graphql' ), 39 | ), 40 | ); 41 | }, 42 | 'description' => esc_html__( 'Plugins are pieces of software used to extend the functionlity of WordPress.', 'wp-graphql' ), 43 | 'resolveField' => function( $value, $args, $context, ResolveInfo $info ) { 44 | if ( method_exists( $this, $info->fieldName ) ) { 45 | return $this->{$info->fieldName}( $value, $args, $context, $info ); 46 | } else { 47 | return $value->{$info->fieldName}; 48 | } 49 | }, 50 | ]); 51 | } 52 | 53 | public function name( array $plugin, $args, AppContext $context) { 54 | return $plugin['Name']; 55 | } 56 | 57 | public function plugin_uri( array $plugin, $args, AppContext $context) { 58 | return $plugin['PluginURI']; 59 | } 60 | 61 | public function description( array $plugin, $args, AppContext $context) { 62 | return $plugin['Description']; 63 | } 64 | 65 | public function author( array $plugin, $args, AppContext $context) { 66 | return $plugin['Author']; 67 | } 68 | 69 | public function author_uri( array $plugin, $args, AppContext $context) { 70 | return $plugin['AuthorURI']; 71 | } 72 | 73 | public function version( array $plugin, $args, AppContext $context) { 74 | return $plugin['Version']; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Type/PostInterfaceType.php: -------------------------------------------------------------------------------- 1 | definition = new InterfaceType( array( 29 | 'name' => 'PostInterface', 30 | 'fields' => array( 31 | 'id' => array( 32 | 'type' => $types->id(), 33 | 'description' => esc_html__( 'The id field matches the WP_Post->ID field.', 'wp-graphql' ), 34 | ), 35 | 'author' => array( 36 | 'type' => $types->user(), 37 | 'description' => esc_html__( "The author field will return a queryable User type matching the post's author.", 'wp-graphql' ), 38 | ), 39 | 'date' => array( 40 | 'type' => $types->string(), 41 | 'description' => esc_html__( 'Post publishing date.', 'wp-graphql' ), 42 | ), 43 | 'date_gmt' => array( 44 | 'type' => $types->string(), 45 | 'description' => esc_html__( 'The publishing date set in GMT.', 'wp-graphql' ), 46 | ), 47 | 'content' => array( 48 | 'type' => $types->string(), 49 | 'description' => esc_html__( 'The content of the post. This is currently just the raw content. An amendment to support rendered content needs to be made.', 'wp-graphql' ), 50 | ), 51 | 'title' => array( 52 | 'type' => $types->string(), 53 | 'description' => esc_html__( 'The title of the post. This is currently just the raw title. An amendment to support rendered title needs to be made.', 'wp-graphql' ), 54 | ), 55 | 'excerpt' => array( 56 | 'type' => $types->string(), 57 | 'description' => esc_html__( 'The excerpt of the post. This is currently just the raw excerpt. An amendment to support rendered excerpts needs to be made.', 'wp-graphql' ), 58 | ), 59 | 'post_status' => array( 60 | 'type' => $types->string(), 61 | 'description' => esc_html__( 'The current status of the post. ( published, draft, etc. ) This should be changed to an enum type supporting valid stati.', 'wp-graphql' ), 62 | ), 63 | 'comment_status' => array( 64 | 'type' => $types->string(), 65 | 'description' => esc_html__( 'Whether the comments are open or closed for this particular post. Needs investigating.', 'wp-graphql' ), 66 | ), 67 | 'ping_status' => array( 68 | 'type' => $types->string(), 69 | 'description' => esc_html__( 'Whether the pings are open or closed for this particular post. Needs investigating.', 'wp-graphql' ), 70 | ), 71 | 'slug' => array( 72 | 'type' => $types->string(), 73 | 'description' => esc_html__( 'The uri slug for the post. This is equivalent to the WP_Post->post_name field and the post_name column in the database for the `wp_posts` table.', 'wp-graphql' ), 74 | ), 75 | 'to_ping' => array( 76 | 'type' => $types->string(), 77 | 'description' => esc_html__( 'URLs queued to be pinged.', 'wp-graphql' ), 78 | ), 79 | 'pinged' => array( 80 | 'type' => $types->string(), 81 | 'description' => esc_html__( 'URLs that have been pinged.', 'wp-graphql' ), 82 | ), 83 | 'modified' => array( 84 | 'type' => $types->string(), 85 | 'description' => esc_html__( 'The local modified time for a post. If a post was recently updated the modified field will change to match the corresponding time.', 'wp-graphql' ), 86 | ), 87 | 'modified_gmt' => array( 88 | 'type' => $types->string(), 89 | 'description' => esc_html__( 'The GMT modified time for a post. If a post was recently updated the modified field will change to match the corresponding time in GMT.', 'wp-graphql' ), 90 | ), 91 | 'parent' => array( 92 | 'type' => $types->int(), 93 | 'description' => esc_html__( 'The ID of the corresponding post parent. This is only typically used with hierarchical content types. This field is equivalent to the value of WP_Post->post_parent and the post_parent column in the `wp_posts` database table.', 'wp-graphql' ), 94 | ), 95 | 'guid' => array( 96 | 'type' => $types->string(), 97 | 'description' => esc_html__( 'The global unique identifier for this post. This currently matches the value stored in WP_Post->guid and the guid column in the `wp_posts` database table.', 'wp-graphql' ), 98 | ), 99 | 'menu_order' => array( 100 | 'type' => $types->int(), 101 | 'description' => esc_html__( 'A field used for ordering posts. This is typically used with nav menu items or for special ordering of hierarchical content types.', 'wp-graphql' ), 102 | ), 103 | 'type' => array( 104 | 'type' => $types->string(), 105 | 'description' => esc_html__( 'This field tells what kind of content type the object is. In WordPress different post types are used to denote different types of content. This field is equivalent to the value of WP_Post->post_type and the post_type column in the `wp_posts` database table.', 'wp-graphql' ), 106 | ), 107 | 'mime_type' => array( 108 | 'type' => $types->string(), 109 | 'description' => esc_html__( 'If the post is an attachment or a media file, this field will carry the corresponding MIME type. This field is equivalent to the value of WP_Post->post_mime_type and the post_mime_type column in the `wp_posts` database table.', 'wp-graphql' ), 110 | ), 111 | 'comment_count' => array( 112 | 'type' => $types->int(), 113 | 'description' => esc_html__( 'The number of comments. Even though WP GraphQL denotes this field as an integer, in WordPress this field should be saved as a numeric string for compatability.', 'wp-graphql' ), 114 | ), 115 | 'comments' => [ 116 | 'type' => $types->listOf( $types->comment() ), 117 | 'description' => 'Returns comments for post based on collection args', 118 | 'args' => [ 119 | // Limit and after are equivalent to per_page and offset. 120 | 'first' => $types->int(), 121 | 'after' => $types->int(), 122 | ], 123 | ], 124 | ), 125 | 'resolveType' => function ( $object ) use ( $types ) { 126 | return $this->resolveType( $object, $types ); 127 | }, 128 | ) ); 129 | } 130 | 131 | /** 132 | * Type resolver used for introspection. 133 | * 134 | * @param mixed $object What type the interface is using. 135 | * @param TypeSystem $types Current TypeSystem. 136 | */ 137 | public function resolveType( $object, TypeSystem $types ) { 138 | return $types->post_object( $object->post_type ); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/Type/PostObjectType.php: -------------------------------------------------------------------------------- 1 | post_type = $post_type; 14 | 15 | $this->definition = new ObjectType([ 16 | 'name' => $post_type, 17 | 'fields' => function() use ( $types ) { 18 | return array( 19 | 'id' => array( 20 | 'type' => $types->id(), 21 | 'description' => esc_html__( 'The id field matches the WP_Post->ID field.', 'wp-graphql' ), 22 | ), 23 | 'author' => array( 24 | 'type' => $types->user(), 25 | 'description' => esc_html__( "The author field will return a queryable User type matching the post's author.", 'wp-graphql' ), 26 | ), 27 | 'date' => array( 28 | 'type' => $types->string(), 29 | 'description' => esc_html__( 'Post publishing date.', 'wp-graphql' ), 30 | ), 31 | 'date_gmt' => array( 32 | 'type' => $types->string(), 33 | 'description' => esc_html__( 'The publishing date set in GMT.', 'wp-graphql' ), 34 | ), 35 | 'content' => array( 36 | 'type' => $types->string(), 37 | 'description' => esc_html__( 'The content of the post. This is currently just the raw content. An amendment to support rendered content needs to be made.', 'wp-graphql' ), 38 | ), 39 | 'title' => array( 40 | 'type' => $types->string(), 41 | 'description' => esc_html__( 'The title of the post. This is currently just the raw title. An amendment to support rendered title needs to be made.', 'wp-graphql' ), 42 | ), 43 | 'excerpt' => array( 44 | 'type' => $types->string(), 45 | 'description' => esc_html__( 'The excerpt of the post. This is currently just the raw excerpt. An amendment to support rendered excerpts needs to be made.', 'wp-graphql' ), 46 | ), 47 | 'post_status' => array( 48 | 'type' => $types->string(), 49 | 'description' => esc_html__( 'The current status of the post. ( published, draft, etc. ) This should be changed to an enum type supporting valid stati.', 'wp-graphql' ), 50 | ), 51 | 'comment_status' => array( 52 | 'type' => $types->string(), 53 | 'description' => esc_html__( 'Whether the comments are open or closed for this particular post. Needs investigating.', 'wp-graphql' ), 54 | ), 55 | 'ping_status' => array( 56 | 'type' => $types->string(), 57 | 'description' => esc_html__( 'Whether the pings are open or closed for this particular post. Needs investigating.', 'wp-graphql' ), 58 | ), 59 | 'slug' => array( 60 | 'type' => $types->string(), 61 | 'description' => esc_html__( 'The uri slug for the post. This is equivalent to the WP_Post->post_name field and the post_name column in the database for the `wp_posts` table.', 'wp-graphql' ), 62 | ), 63 | 'to_ping' => array( 64 | 'type' => $types->string(), 65 | 'description' => esc_html__( 'URLs queued to be pinged.', 'wp-graphql' ), 66 | ), 67 | 'pinged' => array( 68 | 'type' => $types->string(), 69 | 'description' => esc_html__( 'URLs that have been pinged.', 'wp-graphql' ), 70 | ), 71 | 'modified' => array( 72 | 'type' => $types->string(), 73 | 'description' => esc_html__( 'The local modified time for a post. If a post was recently updated the modified field will change to match the corresponding time.', 'wp-graphql' ), 74 | ), 75 | 'modified_gmt' => array( 76 | 'type' => $types->string(), 77 | 'description' => esc_html__( 'The GMT modified time for a post. If a post was recently updated the modified field will change to match the corresponding time in GMT.', 'wp-graphql' ), 78 | ), 79 | 'parent' => array( 80 | 'type' => $types->int(), 81 | 'description' => esc_html__( 'The ID of the corresponding post parent. This is only typically used with hierarchical content types. This field is equivalent to the value of WP_Post->post_parent and the post_parent column in the `wp_posts` database table.', 'wp-graphql' ), 82 | ), 83 | 'guid' => array( 84 | 'type' => $types->string(), 85 | 'description' => esc_html__( 'The global unique identifier for this post. This currently matches the value stored in WP_Post->guid and the guid column in the `wp_posts` database table.', 'wp-graphql' ), 86 | ), 87 | 'menu_order' => array( 88 | 'type' => $types->int(), 89 | 'description' => esc_html__( 'A field used for ordering posts. This is typically used with nav menu items or for special ordering of hierarchical content types.', 'wp-graphql' ), 90 | ), 91 | 'type' => array( 92 | 'type' => $types->string(), 93 | 'description' => esc_html__( 'This field tells what kind of content type the object is. In WordPress different post types are used to denote different types of content. This field is equivalent to the value of WP_Post->post_type and the post_type column in the `wp_posts` database table.', 'wp-graphql' ), 94 | ), 95 | 'mime_type' => array( 96 | 'type' => $types->string(), 97 | 'description' => esc_html__( 'If the post is an attachment or a media file, this field will carry the corresponding MIME type. This field is equivalent to the value of WP_Post->post_mime_type and the post_mime_type column in the `wp_posts` database table.', 'wp-graphql' ), 98 | ), 99 | 'comment_count' => array( 100 | 'type' => $types->int(), 101 | 'description' => esc_html__( 'The number of comments. Even though WP GraphQL denotes this field as an integer, in WordPress this field should be saved as a numeric string for compatability.', 'wp-graphql' ), 102 | ), 103 | 'comments' => [ 104 | 'type' => $types->listOf( $types->comment() ), 105 | 'description' => 'Returns comments for post based on collection args', 106 | 'args' => [ 107 | // Limit and after are equivalent to per_page and offset. 108 | 'first' => $types->int(), 109 | 'after' => $types->int(), 110 | ], 111 | ], 112 | ); 113 | }, 114 | 'description' => esc_html__( 'The post object for WordPress. Internally this is an instance of WP_Post. Posts are the main default content type of WordPress. They are the heart and soul of a blog.', 'wp-graphql' ), 115 | 'interfaces' => [ 116 | $types->post_interface(), 117 | ], 118 | 'resolveField' => function( $value, $args, $context, ResolveInfo $info ) { 119 | if ( method_exists( $this, $info->fieldName ) ) { 120 | return $this->{$info->fieldName}( $value, $args, $context, $info ); 121 | } else { 122 | return $value->{$info->fieldName}; 123 | } 124 | }, 125 | ]); 126 | } 127 | 128 | public function id( \WP_Post $post, $args, AppContext $context ) { 129 | return $post->ID; 130 | } 131 | 132 | public function parent( \WP_Post $post, $args, AppContext $context ) { 133 | return $post->post_parent; 134 | } 135 | 136 | public function author( \WP_Post $post, $args, AppContext $context ) { 137 | return new \WP_User( $post->post_author ); 138 | } 139 | 140 | public function guid( \WP_Post $post, $args, AppContext $context ) { 141 | return $post->guid; 142 | } 143 | 144 | public function title( \WP_Post $post, $args, AppContext $context ) { 145 | return $post->post_title; 146 | } 147 | 148 | public function content( \WP_Post $post, $args, AppContext $context ) { 149 | return $post->post_content; 150 | } 151 | 152 | public function excerpt( \WP_Post $post, $args, AppContext $context ) { 153 | return $post->post_excerpt; 154 | } 155 | 156 | public function date( \WP_Post $post, $args, AppContext $context ) { 157 | return $post->post_date; 158 | } 159 | 160 | public function date_gmt( \WP_Post $post, $args, AppContext $context ) { 161 | return $post->post_date_gmt; 162 | } 163 | 164 | public function modified( \WP_Post $post, $args, AppContext $context ) { 165 | return $post->post_modified; 166 | } 167 | 168 | public function modified_gmt( \WP_Post $post, $args, AppContext $context ) { 169 | return $post->post_modified_gmt; 170 | } 171 | 172 | public function slug( \WP_Post $post, $args, AppContext $context ) { 173 | return $post->post_name; 174 | } 175 | 176 | public function mime_type( \WP_Post $post, $args, AppContext $context ) { 177 | return $post->post_mime_type; 178 | } 179 | 180 | public function type( \WP_Post $post, $args, AppContext $context ) { 181 | return $post->post_type; 182 | } 183 | 184 | /** 185 | * Comments field resolver. 186 | * 187 | * @param mixed $value Value for the resolver. 188 | * @param array $args List of arguments for this resolver. 189 | * @param AppContext $context Context object for the Application. 190 | * @return array List of WP_Comment objects. 191 | */ 192 | public function comments( \WP_Post $post, $args, AppContext $context ) { 193 | $query_args = array( 194 | 'post_id' => $post->ID, 195 | ); 196 | 197 | if ( isset( $args['first'] ) ) { 198 | $query_args['number'] = $args['first']; 199 | } 200 | 201 | if ( isset( $args['after'] ) ) { 202 | $query_args['offset'] = $args['after']; 203 | } 204 | 205 | $comments_query = new \WP_Comment_Query( $query_args ); 206 | $comments = $comments_query->get_comments(); 207 | 208 | return ! empty( $comments ) ? $comments : null; 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /src/Type/PostType.php: -------------------------------------------------------------------------------- 1 | definition = new ObjectType([ 19 | 'name' => 'Post', 20 | 'fields' => function() use ( $types ) { 21 | return array( 22 | 'id' => array( 23 | 'type' => $types->id(), 24 | 'description' => esc_html__( 'The id field matches the WP_Post->ID field.', 'wp-graphql' ), 25 | ), 26 | 'author' => array( 27 | 'type' => $types->user(), 28 | 'description' => esc_html__( "The author field will return a queryable User type matching the post's author.", 'wp-graphql' ), 29 | ), 30 | 'date' => array( 31 | 'type' => $types->string(), 32 | 'description' => esc_html__( 'Post publishing date.', 'wp-graphql' ), 33 | ), 34 | 'date_gmt' => array( 35 | 'type' => $types->string(), 36 | 'description' => esc_html__( 'The publishing date set in GMT.', 'wp-graphql' ), 37 | ), 38 | 'content' => array( 39 | 'type' => $types->string(), 40 | 'description' => esc_html__( 'The content of the post. This is currently just the raw content. An amendment to support rendered content needs to be made.', 'wp-graphql' ), 41 | ), 42 | 'title' => array( 43 | 'type' => $types->string(), 44 | 'description' => esc_html__( 'The title of the post. This is currently just the raw title. An amendment to support rendered title needs to be made.', 'wp-graphql' ), 45 | ), 46 | 'excerpt' => array( 47 | 'type' => $types->string(), 48 | 'description' => esc_html__( 'The excerpt of the post. This is currently just the raw excerpt. An amendment to support rendered excerpts needs to be made.', 'wp-graphql' ), 49 | ), 50 | 'post_status' => array( 51 | 'type' => $types->string(), 52 | 'description' => esc_html__( 'The current status of the post. ( published, draft, etc. ) This should be changed to an enum type supporting valid stati.', 'wp-graphql' ), 53 | ), 54 | 'comment_status' => array( 55 | 'type' => $types->string(), 56 | 'description' => esc_html__( 'Whether the comments are open or closed for this particular post. Needs investigating.', 'wp-graphql' ), 57 | ), 58 | 'ping_status' => array( 59 | 'type' => $types->string(), 60 | 'description' => esc_html__( 'Whether the pings are open or closed for this particular post. Needs investigating.', 'wp-graphql' ), 61 | ), 62 | 'slug' => array( 63 | 'type' => $types->string(), 64 | 'description' => esc_html__( 'The uri slug for the post. This is equivalent to the WP_Post->post_name field and the post_name column in the database for the `wp_posts` table.', 'wp-graphql' ), 65 | ), 66 | 'to_ping' => array( 67 | 'type' => $types->string(), 68 | 'description' => esc_html__( 'URLs queued to be pinged.', 'wp-graphql' ), 69 | ), 70 | 'pinged' => array( 71 | 'type' => $types->string(), 72 | 'description' => esc_html__( 'URLs that have been pinged.', 'wp-graphql' ), 73 | ), 74 | 'modified' => array( 75 | 'type' => $types->string(), 76 | 'description' => esc_html__( 'The local modified time for a post. If a post was recently updated the modified field will change to match the corresponding time.', 'wp-graphql' ), 77 | ), 78 | 'modified_gmt' => array( 79 | 'type' => $types->string(), 80 | 'description' => esc_html__( 'The GMT modified time for a post. If a post was recently updated the modified field will change to match the corresponding time in GMT.', 'wp-graphql' ), 81 | ), 82 | 'parent' => array( 83 | 'type' => $types->int(), 84 | 'description' => esc_html__( 'The ID of the corresponding post parent. This is only typically used with hierarchical content types. This field is equivalent to the value of WP_Post->post_parent and the post_parent column in the `wp_posts` database table.', 'wp-graphql' ), 85 | ), 86 | 'guid' => array( 87 | 'type' => $types->string(), 88 | 'description' => esc_html__( 'The global unique identifier for this post. This currently matches the value stored in WP_Post->guid and the guid column in the `wp_posts` database table.', 'wp-graphql' ), 89 | ), 90 | 'menu_order' => array( 91 | 'type' => $types->int(), 92 | 'description' => esc_html__( 'A field used for ordering posts. This is typically used with nav menu items or for special ordering of hierarchical content types.', 'wp-graphql' ), 93 | ), 94 | 'type' => array( 95 | 'type' => $types->string(), 96 | 'description' => esc_html__( 'This field tells what kind of content type the object is. In WordPress different post types are used to denote different types of content. This field is equivalent to the value of WP_Post->post_type and the post_type column in the `wp_posts` database table.', 'wp-graphql' ), 97 | ), 98 | 'mime_type' => array( 99 | 'type' => $types->string(), 100 | 'description' => esc_html__( 'If the post is an attachment or a media file, this field will carry the corresponding MIME type. This field is equivalent to the value of WP_Post->post_mime_type and the post_mime_type column in the `wp_posts` database table.', 'wp-graphql' ), 101 | ), 102 | 'comment_count' => array( 103 | 'type' => $types->int(), 104 | 'description' => esc_html__( 'The number of comments. Even though WP GraphQL denotes this field as an integer, in WordPress this field should be saved as a numeric string for compatability.', 'wp-graphql' ), 105 | ), 106 | 'comments' => [ 107 | 'type' => $types->listOf( $types->comment() ), 108 | 'description' => 'Returns comments for post based on collection args', 109 | 'args' => [ 110 | // Limit and after are equivalent to per_page and offset. 111 | 'first' => $types->int(), 112 | 'after' => $types->int(), 113 | ], 114 | ], 115 | ); 116 | }, 117 | 'description' => esc_html__( 'The post object for WordPress. Internally this is an instance of WP_Post. Posts are the main default content type of WordPress. They are the heart and soul of a blog.', 'wp-graphql' ), 118 | 'interfaces' => [ 119 | $types->post_interface(), 120 | ], 121 | 'resolveField' => function( $value, $args, $context, ResolveInfo $info ) { 122 | if ( method_exists( $this, $info->fieldName ) ) { 123 | return $this->{$info->fieldName}( $value, $args, $context, $info ); 124 | } else { 125 | return $value->{$info->fieldName}; 126 | } 127 | }, 128 | ]); 129 | } 130 | 131 | public function id( \WP_Post $post, $args, AppContext $context ) { 132 | return $post->ID; 133 | } 134 | 135 | public function parent( \WP_Post $post, $args, AppContext $context ) { 136 | return $post->post_parent; 137 | } 138 | 139 | public function author( \WP_Post $post, $args, AppContext $context ) { 140 | return new \WP_User( $post->post_author ); 141 | } 142 | 143 | public function guid( \WP_Post $post, $args, AppContext $context ) { 144 | return $post->guid; 145 | } 146 | 147 | public function title( \WP_Post $post, $args, AppContext $context ) { 148 | return $post->post_title; 149 | } 150 | 151 | public function content( \WP_Post $post, $args, AppContext $context ) { 152 | return $post->post_content; 153 | } 154 | 155 | public function excerpt( \WP_Post $post, $args, AppContext $context ) { 156 | return $post->post_excerpt; 157 | } 158 | 159 | public function date( \WP_Post $post, $args, AppContext $context ) { 160 | return $post->post_date; 161 | } 162 | 163 | public function date_gmt( \WP_Post $post, $args, AppContext $context ) { 164 | return $post->post_date_gmt; 165 | } 166 | 167 | public function modified( \WP_Post $post, $args, AppContext $context ) { 168 | return $post->post_modified; 169 | } 170 | 171 | public function modified_gmt( \WP_Post $post, $args, AppContext $context ) { 172 | return $post->post_modified_gmt; 173 | } 174 | 175 | public function slug( \WP_Post $post, $args, AppContext $context ) { 176 | return $post->post_name; 177 | } 178 | 179 | public function mime_type( \WP_Post $post, $args, AppContext $context ) { 180 | return $post->post_mime_type; 181 | } 182 | 183 | public function type( \WP_Post $post, $args, AppContext $context ) { 184 | return $post->post_type; 185 | } 186 | 187 | /** 188 | * Comments field resolver. 189 | * 190 | * @param mixed $value Value for the resolver. 191 | * @param array $args List of arguments for this resolver. 192 | * @param AppContext $context Context object for the Application. 193 | * @return array List of WP_Comment objects. 194 | */ 195 | public function comments( \WP_Post $post, $args, AppContext $context ) { 196 | $query_args = array( 197 | 'post_id' => $post->ID, 198 | ); 199 | 200 | if ( isset( $args['first'] ) ) { 201 | $query_args['number'] = $args['first']; 202 | } 203 | 204 | if ( isset( $args['after'] ) ) { 205 | $query_args['offset'] = $args['after']; 206 | } 207 | 208 | $comments_query = new \WP_Comment_Query( $query_args ); 209 | $comments = $comments_query->get_comments(); 210 | 211 | return ! empty( $comments ) ? $comments : null; 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /src/Type/PostTypeType.php: -------------------------------------------------------------------------------- 1 | definition = new ObjectType([ 13 | 'name' => 'PostType', 14 | 'fields' => function() use ( $types ) { 15 | return array( 16 | 'name' => array( 17 | 'type' => $types->string(), 18 | 'description' => esc_html__( 'The internal name of the post type. This should not be used for display purposes.', 'wp-graphql' ), 19 | ), 20 | 'label' => array( 21 | 'type' => $types->string(), 22 | 'description' => esc_html__( 'Display name of the content type.', 'wp-graphql' ), 23 | ), 24 | //'labels' => $types->post_type_labels(), 25 | 'description' => array( 26 | 'type' => $types->string(), 27 | 'description' => esc_html__( 'Description of the content type.', 'wp-graphql' ), 28 | ), 29 | 'public' => array( 30 | 'type' => $types->boolean(), 31 | 'description' => esc_html__( 'Whether a post type is intended for use publicly either via the admin interface or by front-end users. While the default settings of exclude_from_search, publicly_queryable, show_ui, and show_in_nav_menus are inherited from public, each does not rely on this relationship and controls a very specific intention.', 'wp-graphql' ), 32 | ), 33 | 'hierarchical' => array( 34 | 'type' => $types->boolean(), 35 | 'description' => esc_html__( 'Whether the post type is hierarchical, for example pages.', 'wp-graphql' ), 36 | ), 37 | 'exclude_from_search' => array( 38 | 'type' => $types->boolean(), 39 | 'description' => esc_html__( 'Whether to exclude posts with this post type from front end search results.', 'wp-graphql' ), 40 | ), 41 | 'publicly_queryable' => array( 42 | 'type' => $types->boolean(), 43 | 'description' => esc_html__( 'Whether queries can be performed on the front end for the post type as part of parse_request(). Endpoints would include: \n* ?post_type={post_type_key}\n* ?{post_type_key}={single_post_slug}\n* ?{post_type_query_var}={single_post_slug}', 'wp-graphql' ), 44 | ), 45 | 'show_ui' => array( 46 | 'type' => $types->boolean(), 47 | 'description' => esc_html__( 'Whether to generate and allow a UI for managing this post type in the admin.', 'wp-graphql' ), 48 | ), 49 | 'show_in_menu' => array( 50 | 'type' => $types->boolean(), 51 | 'description' => esc_html__( 'Where to show the post type in the admin menu. To work, $show_ui must be true. If true, the post type is shown in its own top level menu. If false, no menu is shown. If a string of an existing top level menu (eg. "tools.php" or "edit.php?post_type=page"), the post type will be placed as a sub-menu of that.', 'wp-graphql' ), 52 | ), 53 | 'show_in_nav_menus' => array( 54 | 'type' => $types->boolean(), 55 | 'description' => esc_html__( 'Makes this post type available for selection in navigation menus.', 'wp-graphql' ), 56 | ), 57 | 'show_in_admin_bar' => array( 58 | 'type' => $types->boolean(), 59 | 'description' => esc_html__( 'Makes this post type available via the admin bar.', 'wp-graphql' ), 60 | ), 61 | 'menu_position' => array( 62 | 'type' => $types->int(), 63 | 'description' => esc_html__( 'The position of this post type in the menu. Only applies if show_in_menu is true.', 'wp-graphql' ), 64 | ), 65 | 'menu_icon' => array( 66 | 'type' => $types->string(), 67 | 'description' => esc_html__( 'The name of the icon file to display as a menu icon.', 'wp-graphql' ), 68 | ), 69 | 'taxonomies' => array( 70 | 'type' => $types->listOf( $types->string() ), 71 | 'description' => esc_html__( 'List of taxonomies available to the post type.', 'wp-graphql' ), 72 | ), 73 | 'has_archive' => array( 74 | 'type' => $types->boolean(), 75 | 'description' => esc_html__( 'Whether this content type should have archives. Content archives are generated by type and by date.', 'wp-graphql' ), 76 | ), 77 | 'can_export' => array( 78 | 'type' => $types->boolean(), 79 | 'description' => esc_html__( 'Whether this content type should can be exported.', 'wp-graphql' ), 80 | ), 81 | 'delete_with_user' => array( 82 | 'type' => $types->boolean(), 83 | 'description' => esc_html__( 'Whether delete this type of content when the author of it is deleted from the system.', 'wp-graphql' ), 84 | ), 85 | 'show_in_rest' => array( 86 | 'type' => $types->boolean(), 87 | 'description' => esc_html__( 'Whether to add the post type route in the REST API `wp/v2` namespace.', 'wp-graphql' ), 88 | ), 89 | 'rest_base' => array( 90 | 'type' => $types->string(), 91 | 'description' => esc_html__( 'Name of content type to diplay in REST API `wp/v2` namespace.', 'wp-graphql' ), 92 | ), 93 | 'rest_controller_class' => array( 94 | 'type' => $types->string(), 95 | 'description' => esc_html__( 'The REST Controller class assigned to handling this content type.', 'wp-graphql' ), 96 | ), 97 | ); 98 | }, 99 | 'description' => esc_html__( 'Post types are used to define different types of content in WordPress. Pages are a post type as well as posts themselves. Custom post types are often used in WordPress to present different types of content. It is advised to potentially store custom content types in custom database tables if they do not map well to `wp_posts`.', 'wp-graphql' ), 100 | 'resolveField' => function( $value, $args, $context, ResolveInfo $info ) { 101 | if ( method_exists( $this, $info->fieldName ) ) { 102 | return $this->{$info->fieldName}( $value, $args, $context, $info ); 103 | } else { 104 | return $value->{$info->fieldName}; 105 | } 106 | }, 107 | ]); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/Type/QueryType.php: -------------------------------------------------------------------------------- 1 | wp_query_schema = new WPQuery( $types ); 28 | 29 | $fields = array( 30 | 'user' => [ 31 | 'type' => $types->user(), 32 | 'description' => 'Returns user by id', 33 | 'args' => [ 34 | 'id' => $types->nonNull( $types->id() ), 35 | ], 36 | ], 37 | 'users' => [ 38 | 'type' => $types->listOf( $types->user() ), 39 | 'description' => 'Returns users based on collection args', 40 | 'args' => [ 41 | // Limit and after are equivalent to per_page and offset. 42 | 'first' => $types->int(), 43 | 'after' => $types->int(), 44 | ], 45 | ], 46 | 'comment' => [ 47 | 'type' => $types->comment(), 48 | 'description' => 'Returns comment by id', 49 | 'args' => [ 50 | 'id' => $types->nonNull( $types->id() ), 51 | ], 52 | ], 53 | 'comments' => [ 54 | 'type' => $types->listOf( $types->comment() ), 55 | 'description' => 'Returns comments based on collection args', 56 | 'args' => [ 57 | // Limit and after are equivalent to per_page and offset. 58 | 'first' => $types->int(), 59 | 'after' => $types->int(), 60 | ], 61 | ], 62 | 'term' => array( 63 | 'type' => $types->term(), 64 | 'description' => 'Returns term by id', 65 | 'args' => [ 66 | 'id' => $types->nonNull( $types->id() ), 67 | ], 68 | ), 69 | 'terms' => [ 70 | 'type' => $types->listOf( $types->term() ), 71 | 'description' => 'Returns terms based on collection args', 72 | 'args' => [ 73 | // Limit and after are equivalent to per_page and offset. 74 | 'first' => $types->int(), 75 | 'after' => $types->int(), 76 | ], 77 | ], 78 | 'taxonomy' => array( 79 | 'type' => $types->taxonomy(), 80 | 'description' => 'Returns taxonomy by name', 81 | 'args' => [ 82 | 'name' => $types->nonNull( $types->string() ), 83 | ], 84 | ), 85 | 'taxonomies' => [ 86 | 'type' => $types->listOf( $types->taxonomy() ), 87 | 'description' => 'Returns taxonomies based on collection args', 88 | 'args' => [ 89 | // Limit and after are equivalent to per_page and offset. 90 | 'first' => $types->int(), 91 | 'after' => $types->int(), 92 | ], 93 | ], 94 | 'menu_item' => array( 95 | 'type' => $types->menu_item(), 96 | 'description' => 'Returns menu_item by id', 97 | 'args' => [ 98 | 'id' => $types->nonNull( $types->id() ), 99 | ], 100 | ), 101 | 'menu' => array( 102 | 'type' => $types->menu(), 103 | 'description' => 'Returns menu by id', 104 | 'args' => [ 105 | 'id' => $types->id(), 106 | 'name' => $types->string(), 107 | 'slug' => $types->string(), 108 | ], 109 | ), 110 | 'menu_location' => array( 111 | 'type' => $types->menu_location(), 112 | 'description' => 'Returns menu location by name', 113 | 'args' => [ 114 | 'slug' => $types->nonNull( $types->string() ), 115 | ], 116 | ), 117 | 'menu_locations' => array( 118 | 'type' => $types->listOf( $types->menu_location() ), 119 | 'description' => 'Returns menu locations registered in global', 120 | ), 121 | 'theme' => array( 122 | 'type' => $types->theme(), 123 | 'description' => 'Returns theme by name', 124 | 'args' => [ 125 | 'slug' => $types->nonNull( $types->string() ), 126 | ], 127 | ), 128 | 'themes' => [ 129 | 'type' => $types->listOf( $types->theme() ), 130 | 'description' => 'Returns themes based on collection args', 131 | 'args' => [ 132 | // Limit and after are equivalent to per_page and offset. 133 | 'first' => $types->int(), 134 | 'after' => $types->int(), 135 | ], 136 | ], 137 | 'plugin' => array( 138 | 'type' => $types->plugin(), 139 | 'description' => 'Returns plugin by name', 140 | 'args' => [ 141 | 'slug' => $types->nonNull( $types->string() ), 142 | ], 143 | ), 144 | 'plugins' => [ 145 | 'type' => $types->listOf( $types->plugin() ), 146 | 'description' => 'Returns plugins based on collection args', 147 | 'args' => [ 148 | // Limit and after are equivalent to per_page and offset. 149 | 'first' => $types->int(), 150 | 'after' => $types->int(), 151 | ], 152 | ], 153 | 'post_type' => [ 154 | 'type' => $types->post_type(), 155 | 'description' => 'Returns registered post type', 156 | 'args' => [ 157 | 'name' => $types->string(), 158 | ], 159 | ], 160 | 'post_types' => [ 161 | 'type' => $types->listOf( $types->post_type() ), 162 | 'description' => 'Returns registered post types', 163 | ], 164 | 'hello' => Type::string(), 165 | ); 166 | 167 | /** 168 | * This is the dynamic creation of custom post types. 169 | */ 170 | if ( isset( $types->wp_config['post_types'] ) && is_array( $types->wp_config['post_types'] ) ) { 171 | foreach ( $types->wp_config['post_types'] as $name => $post_type ) { 172 | // Add singular post types. 173 | $fields[ $post_type['name'] ] = $this->add_singular_object_type( $post_type['name'], $post_type, $types ); 174 | $fields[ $post_type['plural_name'] ] = $this->add_plural_object_type( $post_type['name'], $post_type, $types ); 175 | } 176 | } 177 | 178 | $this->definition = new ObjectType([ 179 | 'name' => 'Query', 180 | 'fields' => $fields, 181 | 'resolveField' => function( $value, $args, $context, ResolveInfo $info ) { 182 | return $this->{$info->fieldName}( $value, $args, $context, $info ); 183 | }, 184 | ]); 185 | } 186 | 187 | /** 188 | * Generate individual object type field for main Query Type. 189 | * 190 | * This is used in dynamically rendering single posts, pages etc. as fields 191 | * for the main query. 192 | * See graphql_build_wp_config() for the style of the configuration data. 193 | * 194 | * @param string $name The registered name of the post type. 195 | * @param array $post_type Array of formatted post_type data. 196 | * @param TypeSystem $types The generated type system. 197 | * @return array Array of field data dynamically generated for the post type. 198 | */ 199 | private function add_singular_object_type( $name, $post_type, $types ) { 200 | return array( 201 | 'type' => $types->post_object( $name ), 202 | /* translators: %s Is the registered name of the post type. */ 203 | 'description' => sprintf( esc_html__( 'WordPress features many different object types known as post types. This field resolves to the %s type.', 'wp-graphql' ), $post_type['name'] ), 204 | 'args' => array( 205 | 'id' => $types->nonNull( $types->id() ), 206 | ), 207 | 'resolve' => function( $value, $args, $context ) use ( $post_type ) { 208 | $post = get_post( $args['id'] ); 209 | 210 | if ( isset( $post->post_type ) && $post_type['name'] === $post->post_type ) { 211 | return $post; 212 | } 213 | 214 | return null; 215 | }, 216 | ); 217 | } 218 | 219 | /** 220 | * Generate collection object type field for main Query Type. 221 | * 222 | * This is used in dynamically rendering single posts, pages etc. as fields 223 | * for the main query. 224 | * See graphql_build_wp_config() for the style of the configuration data. 225 | * 226 | * These fields will make use of WP_Query! 227 | * 228 | * @param string $name The registered name of the post type. 229 | * @param array $post_type Array of formatted post_type data. 230 | * @param TypeSystem $types The generated type system. 231 | * @return array Array of field data dynamically generated for the post type. 232 | */ 233 | private function add_plural_object_type( $name, $post_type, $types ) { 234 | return array( 235 | 'type' => $types->listOf( $types->post_object( $name ) ), 236 | /* translators: %s Is the registered name of the post type. */ 237 | 'description' => sprintf( esc_html__( 'WordPress features many different object types known as post types. This field resolves to a collection of the %s type.', 'wp-graphql' ), $post_type['name'] ), 238 | 'args' => $this->wp_query_schema->args(), 239 | 'resolve' => function( $value, $args, $context ) use ( $post_type ) { 240 | $query_args = array( 241 | 'post_type' => $post_type['registered_name'], 242 | ); 243 | 244 | if ( isset( $args['first'] ) ) { 245 | $query_args['posts_per_page'] = $args['first']; 246 | } 247 | 248 | if ( isset( $args['after'] ) ) { 249 | $query_args['offset'] = $args['after']; 250 | } 251 | 252 | $posts_query = new \WP_Query( $query_args ); 253 | $posts = $posts_query->get_posts(); 254 | return ! empty( $posts ) ? $posts : null; 255 | }, 256 | ); 257 | } 258 | 259 | /** 260 | * Post field resolver. 261 | * 262 | * Note that post is a field within the query type. 263 | * 264 | * @param mixed $value Value for the resolver. 265 | * @param array $args List of arguments for this resolver. 266 | * @param AppContext $context Context object for the Application. 267 | * @return WP_Post Post object. 268 | */ 269 | public function post( $value, $args, AppContext $context ) { 270 | return get_post( $args['id'] ); 271 | } 272 | 273 | /** 274 | * Posts field resolver. 275 | * 276 | * @param mixed $value Value for the resolver. 277 | * @param array $args List of arguments for this resolver. 278 | * @param AppContext $context Context object for the Application. 279 | * @return array List of WP_Post objects. 280 | */ 281 | public function posts( $value, $args, AppContext $context ) { 282 | $query_args = array(); 283 | 284 | if ( isset( $args['first'] ) ) { 285 | $query_args['posts_per_page'] = $args['first']; 286 | } 287 | 288 | if ( isset( $args['after'] ) ) { 289 | $query_args['offset'] = $args['after']; 290 | } 291 | 292 | $query_args = array_merge( $query_args, $args ); 293 | 294 | $posts_query = new \WP_Query( $query_args ); 295 | $posts = $posts_query->get_posts(); 296 | return ! empty( $posts ) ? $posts : null; 297 | } 298 | 299 | /** 300 | * Comment field resolver. 301 | * 302 | * Note that comment is a field within the query type. 303 | * 304 | * @param mixed $value Value for the resolver. 305 | * @param array $args List of arguments for this resolver. 306 | * @param AppContext $context Context object for the Application. 307 | * @return WP_Comment Comment object. 308 | */ 309 | public function comment( $value, $args, AppContext $context ) { 310 | return get_comment( $args['id'] ); 311 | } 312 | 313 | /** 314 | * Comments field resolver. 315 | * 316 | * @param mixed $value Value for the resolver. 317 | * @param array $args List of arguments for this resolver. 318 | * @param AppContext $context Context object for the Application. 319 | * @return array List of WP_Comment objects. 320 | */ 321 | public function comments( $value, $args, AppContext $context ) { 322 | $query_args = array(); 323 | 324 | if ( isset( $args['first'] ) ) { 325 | $query_args['number'] = $args['first']; 326 | } 327 | 328 | if ( isset( $args['after'] ) ) { 329 | $query_args['offset'] = $args['after']; 330 | } 331 | 332 | $comments_query = new \WP_Comment_Query( $query_args ); 333 | $comments = $comments_query->get_comments(); 334 | return ! empty( $comments ) ? $comments : null; 335 | } 336 | 337 | /** 338 | * User field resolver. 339 | * 340 | * Note that user is a field within the user type. 341 | * 342 | * @param mixed $value Value for the resolver. 343 | * @param array $args List of arguments for this resolver. 344 | * @param AppContext $context Context object for the Application. 345 | * @return WP_User User object. 346 | */ 347 | public function user( $value, $args, AppContext $context ) { 348 | return get_user_by( 'id', $args['id'] ); 349 | } 350 | 351 | /** 352 | * Users field resolver. 353 | * 354 | * @param mixed $value Value for the resolver. 355 | * @param array $args List of arguments for this resolver. 356 | * @param AppContext $context Context object for the Application. 357 | * @return array List of WP_User objects. 358 | */ 359 | public function users( $value, $args, AppContext $context ) { 360 | $query_args = array( 361 | 'orderby' => 'ID', 362 | ); 363 | 364 | if ( isset( $args['first'] ) ) { 365 | $query_args['number'] = $args['first']; 366 | } 367 | 368 | if ( isset( $args['after'] ) ) { 369 | $query_args['offset'] = $args['after']; 370 | } 371 | 372 | $users_query = new \WP_User_Query( $query_args ); 373 | $users_query->query(); 374 | return $users_query->get_results(); 375 | } 376 | 377 | /** 378 | * Term field resolver. 379 | * 380 | * @param mixed $value Value for the resolver. 381 | * @param array $args List of arguments for this resolver. 382 | * @param AppContext $context Context object for the Application. 383 | * @return WP_Term Term object. 384 | */ 385 | public function term( $value, $args, AppContext $context ) { 386 | return get_term( $args['id'] ); 387 | } 388 | 389 | /** 390 | * Terms field resolver. 391 | * 392 | * @param mixed $value Value for the resolver. 393 | * @param array $args List of arguments for this resolver. 394 | * @param AppContext $context Context object for the Application. 395 | * @return array List of WP_User objects. 396 | */ 397 | public function terms( $value, $args, AppContext $context ) { 398 | $query_args = array( 399 | 'hide_empty' => false, 400 | ); 401 | 402 | if ( isset( $args['first'] ) ) { 403 | $query_args['number'] = $args['first']; 404 | } 405 | 406 | if ( isset( $args['after'] ) ) { 407 | $query_args['offset'] = $args['after']; 408 | } 409 | 410 | $terms_query = new \WP_Term_Query(); 411 | $terms = $terms_query->query( $query_args ); 412 | 413 | return ! empty( $terms ) ? $terms : null; 414 | } 415 | 416 | /** 417 | * Term field resolver. 418 | * 419 | * @param mixed $value Value for the resolver. 420 | * @param array $args List of arguments for this resolver. 421 | * @param AppContext $context Context object for the Application. 422 | * @return WP_Taxonomy Taxonomy object. 423 | */ 424 | public function taxonomy( $value, $args, AppContext $context ) { 425 | $taxonomy = get_taxonomy( $args['name'] ); 426 | 427 | return false !== $taxonomy ? $taxonomy : null; 428 | } 429 | 430 | /** 431 | * Term field resolver. 432 | * 433 | * @param mixed $value Value for the resolver. 434 | * @param array $args List of arguments for this resolver. 435 | * @param AppContext $context Context object for the Application. 436 | * @return WP_Taxonomy Taxonomy object. 437 | */ 438 | public function taxonomies( $value, $args, AppContext $context ) { 439 | $taxonomies = get_taxonomies( '', 'objects' ); 440 | 441 | if ( isset( $args['first'] ) || isset( $args['after'] ) ) { 442 | $limit = isset( $args['first'] ) ? $args['first'] : count( $taxonomies ); 443 | $offset = isset( $args['after'] ) ? $args['after'] : 0; 444 | 445 | $taxonomies = array_splice( $taxonomies, $offset, $limit ); 446 | } 447 | 448 | return ! empty( $taxonomies ) ? $taxonomies : null; 449 | } 450 | 451 | /** 452 | * Menu item field resolver. 453 | * 454 | * @param mixed $value Value for the resolver. 455 | * @param array $args List of arguments for this resolver. 456 | * @param AppContext $context Context object for the Application. 457 | * @return WP_Post Post object. Nav menu items are posts. 458 | */ 459 | public function menu_item( $value, $args, AppContext $context ) { 460 | $menu_item = get_post( $args['id'] ); 461 | 462 | // If it is a nav menu item return it otherwise null. 463 | return 'nav_menu_item' === $menu_item->post_type ? $menu_item : null; 464 | } 465 | 466 | /** 467 | * Menu field resolver. 468 | * 469 | * @param mixed $value Value for the resolver. 470 | * @param array $args List of arguments for this resolver. 471 | * @param AppContext $context Context object for the Application. 472 | * @return WP_Term Term object. Menus are terms. 473 | */ 474 | public function menu( $value, $args, AppContext $context ) { 475 | $menu = null; 476 | 477 | // Check if the request ID arg is set. 478 | if ( isset( $args['id'] ) ) { 479 | $menu = get_term( $args['id'] ); 480 | } elseif ( isset( $args['name'] ) ) { 481 | $menu = wp_get_nav_menu_object( $args['name'] ); 482 | } elseif ( isset( $args['slug'] ) ) { 483 | $menu = wp_get_nav_menu_object( $args['slug'] ); 484 | } 485 | 486 | if ( isset( $menu->taxonomy ) ) { 487 | // If it is a nav menu item return it otherwise null. 488 | return 'nav_menu' === $menu->taxonomy ? $menu : null; 489 | } 490 | 491 | // If there was some sort of error return null. 492 | return null; 493 | } 494 | 495 | /** 496 | * Menu location field resolver. 497 | * 498 | * @param mixed $value Value for the resolver. 499 | * @param array $args List of arguments for this resolver. 500 | * @param AppContext $context Context object for the Application. 501 | * @return array Array of register nav menu data. 502 | */ 503 | public function menu_location( $value, $args, AppContext $context ) { 504 | $menus = get_registered_nav_menus(); 505 | $name = $args['slug']; 506 | 507 | // If it is a nav menu item return it otherwise null. 508 | if ( isset( $menus[ $name ] ) ) { 509 | return array( 510 | 'slug' => $name, 511 | 'name' => $menus[ $name ], 512 | ); 513 | } 514 | } 515 | 516 | /** 517 | * Menu locations field resolver. 518 | * 519 | * @param mixed $value Value for the resolver. 520 | * @param array $args List of arguments for this resolver. 521 | * @param AppContext $context Context object for the Application. 522 | * @return array Array of register nav menus data. 523 | */ 524 | public function menu_locations( $value, $args, AppContext $context ) { 525 | $menus = array(); 526 | $registered_menus = get_registered_nav_menus(); 527 | 528 | foreach ( $registered_menus as $slug => $name ) { 529 | $menus[] = array( 530 | 'slug' => $slug, 531 | 'name' => $name, 532 | ); 533 | } 534 | 535 | return ! empty( $menus ) ? $menus : null; 536 | } 537 | 538 | /** 539 | * Theme field resolver. 540 | * 541 | * @param mixed $value Value for the resolver. 542 | * @param array $args List of arguments for this resolver. 543 | * @param AppContext $context Context object for the Application. 544 | * @return \WP_Theme Theme object. 545 | */ 546 | public function theme( $value, $args, AppContext $context ) { 547 | $theme = wp_get_theme( $args['slug'] ); 548 | 549 | return $theme->exists() ? $theme : null; 550 | } 551 | 552 | /** 553 | * Themes field resolver. 554 | * 555 | * @param mixed $value Value for the resolver. 556 | * @param array $args List of arguments for this resolver. 557 | * @param AppContext $context Context object for the Application. 558 | * @return \WP_Theme Theme object. 559 | */ 560 | public function themes( $value, $args, AppContext $context ) { 561 | $themes = wp_get_themes(); 562 | 563 | if ( isset( $args['first'] ) || isset( $args['after'] ) ) { 564 | $limit = isset( $args['first'] ) ? $args['first'] : count( $themes ); 565 | $offset = isset( $args['after'] ) ? $args['after'] : 0; 566 | 567 | $themes = array_splice( $themes, $offset, $limit ); 568 | } 569 | 570 | return ! empty( $themes ) ? $themes : null; 571 | } 572 | 573 | /** 574 | * Plugin field resolver. 575 | * 576 | * @param mixed $value Value for the resolver. 577 | * @param array $args List of arguments for this resolver. 578 | * @param AppContext $context Context object for the Application. 579 | * @return array Array of plugin data. 580 | */ 581 | public function plugin( $value, $args, AppContext $context ) { 582 | return $this->get_plugin( $args['slug'] ); 583 | } 584 | 585 | /** 586 | * Plugins field resolver. 587 | * 588 | * @param mixed $value Value for the resolver. 589 | * @param array $args List of arguments for this resolver. 590 | * @param AppContext $context Context object for the Application. 591 | * @return array Array of plugin data. 592 | */ 593 | public function plugins( $value, $args, AppContext $context ) { 594 | $plugins = $this->get_plugins(); 595 | 596 | if ( isset( $args['first'] ) || isset( $args['after'] ) ) { 597 | $limit = isset( $args['first'] ) ? $args['first'] : count( $plugins ); 598 | $offset = isset( $args['after'] ) ? $args['after'] : 0; 599 | 600 | $plugins = array_splice( $plugins, $offset, $limit ); 601 | } 602 | 603 | return ! empty( $plugins ) ? $plugins : null; 604 | } 605 | 606 | /** 607 | * Post types field resolver. 608 | * 609 | * @param mixed $value Value for the resolver. 610 | * @param array $args List of arguments for this resolver. 611 | * @param AppContext $context Context object for the Application. 612 | * @return array Array of plugin data. 613 | */ 614 | public function post_type( $value, $args, AppContext $context ) { 615 | $post_types = get_post_types( '', 'objects' ); 616 | $post_type = array(); 617 | 618 | foreach ( $post_types as $type ) { 619 | if ( $args['name'] === $type->name ) { 620 | $post_type = $type; 621 | } 622 | } 623 | 624 | return ! empty( $post_type ) ? $post_type : null; 625 | } 626 | 627 | /** 628 | * Post types field resolver. 629 | * 630 | * @param mixed $value Value for the resolver. 631 | * @param array $args List of arguments for this resolver. 632 | * @param AppContext $context Context object for the Application. 633 | * @return array Array of plugin data. 634 | */ 635 | public function post_types( $value, $args, AppContext $context ) { 636 | return get_post_types( array(), 'objects' ); 637 | } 638 | 639 | /** 640 | * Hello resolver. 641 | * 642 | * @return String Welcoming message. 643 | */ 644 | public function hello() { 645 | return 'Welcome to WP GraphQL, I hope that you will enjoy this adventure!'; 646 | } 647 | 648 | /** 649 | * Displays a single plugin. 650 | * 651 | * This function is currently not ideal, as the best way to grab plugin data 652 | * currently requires require a file from wp-admin, which hasn't loaded yet. 653 | * 654 | * @param string $name Name of the plugin. 655 | * @return array Array of plugin data. 656 | */ 657 | private function get_plugin( $name ) { 658 | // Puts input into a url friendly slug format. 659 | $slug = sanitize_title( $name ); 660 | $plugin = null; 661 | 662 | // File has not loaded. 663 | require_once ABSPATH . 'wp-admin/includes/plugin.php'; 664 | 665 | // This is missing must use and drop in plugins. 666 | $plugins = apply_filters( 'all_plugins', get_plugins() ); 667 | 668 | foreach ( $plugins as $path => $plugin_data ) { 669 | if ( sanitize_title( $plugin_data['Name'] ) === $slug ) { 670 | $plugin = $plugin_data; 671 | $plugin['path'] = $path; 672 | // Exit early when plugin is found. 673 | break; 674 | } 675 | } 676 | 677 | return $plugin; 678 | } 679 | 680 | /** 681 | * Returns a list of plugins. 682 | * 683 | * @return array Array of an array of plugin data. 684 | */ 685 | private function get_plugins() { 686 | $plugins = array(); 687 | // File has not loaded. 688 | require_once ABSPATH . 'wp-admin/includes/plugin.php'; 689 | 690 | // This is missing must use and drop in plugins. 691 | $plugins = apply_filters( 'all_plugins', get_plugins() ); 692 | 693 | return $plugins; 694 | } 695 | } 696 | -------------------------------------------------------------------------------- /src/Type/README.md: -------------------------------------------------------------------------------- 1 | # Types 2 | 3 | Types are fundamental to GraphQL, they form the building blocks of the schema. 4 | A schema is data that tells us the shape our data will take. For instance the 5 | PostType may have a bunch of fields, each field will have its own type, maybe a 6 | string maybe something else. The PostType will itself be a type and the queries 7 | we use to find our posts are also types. 8 | 9 | Almost everything becomes a type and the relations of our data are defined 10 | through these different types. Types can wrap around other types in a 11 | compositional fashion. Let's breakdown some of the types in GraphQL. 12 | 13 | ## Scalars 14 | 15 | Scalars are types that do not contain additional fields and resolve to only one 16 | value. Due to the text based nature of a GraphQL query any scalar value could 17 | potentially be written as a string. It is wise to leverage the internal types 18 | that a language affords. This is a PHP implementation of GraphQL for WordPress, 19 | so in this plugin we will make heavy use of the PHP types like string, int, and 20 | boolean. 21 | 22 | It is important to note that more sophisticated scalar types can be created as 23 | well. For instance, WordPress has a lot of HTML content. This content could be 24 | represented simply by a string but we could create a HTML type that would handle 25 | the validation and sanitization of any field set to the HTML type. This enables 26 | us to have more fine grained control than just to consider the html content 27 | another string. 28 | 29 | ## Objects 30 | 31 | Object types are used to group fields. An object type must have at least one 32 | field. Any field within an object will have its own type defined as well. Object 33 | types are where things get interesting and where the overall schema/structure of 34 | our application starts to take shape. In the context of WordPress the schema is 35 | pretty well laid out for us already. The goal of this project is to find an 36 | elegant way to expose that schema as a GraphQL type system. 37 | 38 | Object types can also take arguments in the GraphQL query. This enables for a 39 | wide range of possibilities and flexibility when querying. 40 | 41 | Let's think of some things in WordPress that fit the Object Type bill. The first 42 | that comes to my mind is `WP_Post`. `WP_Post` is a PHP object that describes the 43 | various attributes of a WordPress post. In WordPress, posts are kind of 44 | confusing as there are many post types. To match WordPress's schema more 45 | precisely we will want a `PostInterfaceType` that will cover some of the shared 46 | fields among each post type, then for every post type an individual object type 47 | should go along side that. Let's talk more about interface types. 48 | 49 | ## Interfaces 50 | 51 | Interface types are used to define a set of fields that can be implemented by 52 | an object type. If you are familiar with the interface keyword in PHP, Java, or 53 | other languages you will have a general idea of what interface types bring to 54 | GraphQL. 55 | 56 | If you are not familiar with the interface keyword, think of an 57 | interface as a description or tool describing how to interact with an object. 58 | The interface will enable you to interact with the object without knowing the 59 | magic that goes on behind the scenes, similar to how a keyboard describes how we 60 | can enter text into a computer without us having to worry about the technical 61 | details. 62 | 63 | Interfaces in Object Oriented Programming languages provide a hacky way to get 64 | multiple inheritance. What does that mean? With classical inheritance every 65 | object inherits from one parent object and then extends that functionality. You 66 | can't combine two classes into one like Mammal and Egg Layer to get a Platypus 67 | or can you? With interfaces you can define what an object should do or how it 68 | interacts with things. There could be a Mammal interface and an Egg Layer 69 | interface that say "I do mammal things" and "I do egg laying things". Then a 70 | Platypus type could come along and easily implement both of these qualities. 71 | Nature, despite the grand hierarchical classification that we often view it 72 | through, favors composition over inheritance. Each one of us is composed of 73 | roughly **37.2 TRILLION CELLS** and about **50 TRILLION** microbial organisms. 74 | Each cell of ours is composed of roughly **100 TRILLION ATOMS**; WOW! Imagine, 75 | trying to represent that with some sort of classical hierarchy; it ain't gonna 76 | happen. This is why object composition and multiple inheritance are such 77 | important concepts to understand when dealing with software architecture. 78 | 79 | GraphQL allows us to bake multiple inheritance and object composition directly 80 | into our type schema. Interfaces allow for multiple inheritance among types and 81 | object composition is created through the building upon of basic types. In 82 | WordPress there are many opportunities to leverage Interface types to build our 83 | GraphQL schema. A PostInterfaceType for common post type fields would be great. 84 | This means that instead of redefining these fields over and over and over again 85 | in every post type object we could instead implement the PostInterfaceType for 86 | each post. 87 | 88 | ## Unions 89 | 90 | Union Types are somewhat tricky. Union types are used to represent a list of 91 | possible object types, without guaranteeing that fields will be consistent 92 | across types. Because there is no guarantee of what fields are present when 93 | querying for a union type you cannot define fields within the union type like 94 | you normally would for other queries. Instead you are forced to use fragments 95 | to determine which fields you want to return for the various object types being 96 | joined by the union type. 97 | 98 | Confusing!? I think it is. For WordPress, I only see this being applicable for 99 | situations where you would want to be able to do advanced searching queries. 100 | As we dive deeper into the project, more applications for leveraging union types 101 | will most likely arise. 102 | 103 | ## Enums 104 | 105 | Enums or enumerable types are a special form of Scalar types. Enumerable types 106 | are pretty straightforward; they provide a list of possible scalar values. Each 107 | value should be unique. For WordPress there are a huge number of possible 108 | enumerable types. Post statuses, post types, taxonomies, image sizes, and many 109 | many more. 110 | 111 | ## Lists 112 | 113 | List types are types that define what a collection is made up of. Lists can be 114 | used to define lists of scalars and enums, but they can also be used to define 115 | lists of object types. Let's look at a WordPress example. Say we have a Posts 116 | Type it will most likely be a ListType of individual Post ObjectTypes. Pretty 117 | neat! You can probably now see how all of these things come together. 118 | 119 | ## NonNull 120 | 121 | NonNull Types are also pretty straightforward; they define whether a field can 122 | hold an null value (empty value). This type must be implemented by wrapping 123 | around another base type. 124 | 125 | In WordPress a perfect non null example would be an ID field for some resource 126 | like a post. So a Post Types id field would have a nonnull type of a string. 127 | 128 | ## Input Objects 129 | 130 | Fields can define various arguments to pass to them while querying. Most of the 131 | time these arguments will be a simple scalar value like a string or integer. 132 | Sometimes the need for a more complex structure of input is needed; enter Input 133 | Objects! 134 | 135 | With WordPress a great use case of an input object would be query parameters for 136 | WP_Query. By using an Input Object we could set default values, validation, and 137 | create a near replica of WP_Query. Input objects are really great and I think 138 | WordPress will have a lot of need for these kind of types. 139 | 140 | ## That's a wrap! 141 | 142 | That's a basic overview of Types. Understanding types and how to combine them is 143 | essential to creating a strong and usable GraphQL schema for powerful querying. 144 | -------------------------------------------------------------------------------- /src/Type/Scalar/README.md: -------------------------------------------------------------------------------- 1 | # Scalar Types 2 | 3 | Scalar types are used to represent scalar values. 4 | 5 | ## Description 6 | 7 | A scalar type will have no subsequent fields. Enumerable types are just a 8 | special kind of scalar type. A great way to think about scalar types would be 9 | language primitives. If we had an ObjectType of Burrito, maybe it would contain 10 | the fields: hasBeans, hasRice, hasGreens, hasTomatoes. Each of these fields 11 | would resolve to a boolean value of True or False. Strings, integers, floats, 12 | numbers, and any other scalar values fit into this category of types. If a field 13 | is of a scalar type then it will resolve to only one basic value. Most if not 14 | all complex types are built out of individual scalars, in many ways they are the 15 | building blocks for a high level type system. 16 | -------------------------------------------------------------------------------- /src/Type/TaxonomyType.php: -------------------------------------------------------------------------------- 1 | definition = new ObjectType([ 13 | 'name' => 'Taxonomy', 14 | 'fields' => function() use ( $types ) { 15 | return array( 16 | 'name' => array( 17 | 'type' => $types->string(), 18 | 'description' => esc_html__( 'The display name of the taxonomy. This field is equivalent to WP_Taxonomy->label', 'wp-graphql' ), 19 | ), 20 | 'slug' => array( 21 | 'type' => $types->string(), 22 | 'description' => esc_html__( 'The url friendly name of the taxonomy. This field is equivalent to WP_Taxonomy->name', 'wp-graphql' ), 23 | ), 24 | 'description' => array( 25 | 'type' => $types->string(), 26 | 'description' => esc_html__( 'Description of the taxonomy. This field is equivalent to WP_Taxonomy->description', 'wp-graphql' ), 27 | ), 28 | 'show_cloud' => array( 29 | 'type' => $types->boolean(), 30 | 'description' => esc_html__( 'Whether to show the taxonomy as part of a tag cloud widget. This field is equivalent to WP_Taxonomy->show_tagcloud', 'wp-graphql' ), 31 | ), 32 | 'hierarchical' => array( 33 | 'type' => $types->string(), 34 | 'description' => esc_html__( 'Whether the taxonomy is hierarchical. This field is equivalent to WP_Taxonomy->hierarchical', 'wp-graphql' ), 35 | ), 36 | ); 37 | }, 38 | 'description' => esc_html__( 'Taxonomies are groups for which content types can be grouped under. Taxonomies are comprised of terms. Content is assigned to terms which belong to a specific taxonomy. Internally this field maps to WP_Taxonomy.', 'wp-graphql' ), 39 | 'resolveField' => function( $value, $args, $context, ResolveInfo $info ) { 40 | if ( method_exists( $this, $info->fieldName ) ) { 41 | return $this->{$info->fieldName}( $value, $args, $context, $info ); 42 | } else { 43 | return $value->{$info->fieldName}; 44 | } 45 | }, 46 | ]); 47 | } 48 | 49 | public function name( $taxonomy, $args, AppContext $context) { 50 | if ( ! is_null( $taxonomy ) ) { 51 | return $taxonomy->label; 52 | } else { 53 | return null; 54 | } 55 | } 56 | 57 | public function slug( $taxonomy, $args, AppContext $context) { 58 | if ( ! is_null( $taxonomy ) ) { 59 | // Name property of the taxonomy is closer to the slug. 60 | return $taxonomy->name; 61 | } else { 62 | return null; 63 | } 64 | } 65 | 66 | public function description( $taxonomy, $args, AppContext $context) { 67 | if ( ! is_null( $taxonomy ) ) { 68 | return $taxonomy->description; 69 | } else { 70 | return null; 71 | } 72 | } 73 | 74 | public function show_cloud( $taxonomy, $args, AppContext $context) { 75 | if ( ! is_null( $taxonomy ) ) { 76 | return $taxonomy->show_tagcloud; 77 | } else { 78 | return null; 79 | } 80 | } 81 | 82 | public function hierarchical( $taxonomy, $args, AppContext $context) { 83 | if ( ! is_null( $taxonomy ) ) { 84 | return $taxonomy->hierarchical; 85 | } else { 86 | return null; 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/Type/TermType.php: -------------------------------------------------------------------------------- 1 | definition = new ObjectType([ 13 | 'name' => 'Term', 14 | 'fields' => function() use ( $types ) { 15 | return array( 16 | 'id' => array( 17 | 'type' => $types->id(), 18 | 'description' => esc_html__( 'ID for the term. The id field is equivalent to WP_Term->term_id or the `term_id` field in SQL.', 'wp-graphql' ), 19 | ), 20 | 'name' => array( 21 | 'type' => $types->string(), 22 | 'description' => esc_html__( 'Name of the term. Name is essentially the display name for the term, usually capitalized and plural. The name field is equivalent to WP_Term->name or the `term_name` field in SQL.', 'wp-graphql' ), 23 | ), 24 | 'slug' => array( 25 | 'type' => $types->string(), 26 | 'description' => esc_html__( 'Name of the term. Slug is the more url friendly name of the term. The name field is equivalent to WP_Term->slug or the `term_slug` field in SQL.', 'wp-graphql' ), 27 | ), 28 | 'group' => array( 29 | 'type' => $types->string(), 30 | 'description' => esc_html__( 'Group for the term. Groups of terms can be used for performance enhancements while querying they act like a secondary index in SQL. The group field is equivalent to WP_Term->term_group or the `term_group` field in SQL.', 'wp-graphql' ), 31 | ), 32 | 'taxonomy' => array( 33 | 'type' => $types->taxonomy(), 34 | 'description' => esc_html__( 'The taxonomy the term belongs to.', 'wp-graphql' ), 35 | ), 36 | 'description' => array( 37 | 'type' => $types->string(), 38 | 'description' => esc_html__( 'The description for the term. This field is equivalent to WP_Term->description', 'wp-graphql' ), 39 | ), 40 | 'parent' => array( 41 | 'type' => $types->term(), 42 | 'description' => esc_html__( 'The parent term. Terms can be hierarchically organized in one to many relationships. This field is equivalent to new WP_Term( WP_Term->parent )', 'wp-graphql' ), 43 | ), 44 | 'count' => array( 45 | 'type' => $types->int(), 46 | 'description' => esc_html__( 'The count of items assigned to the term. This field is equivalent to WP_Term->count', 'wp-graphql' ), 47 | ), 48 | 'children' => array( 49 | 'type' => $types->listOf( $types->term() ), 50 | 'description' => esc_html__( 'The count of items assigned to the term. This field is equivalent to WP_Term->count', 'wp-graphql' ), 51 | 'args' => array( 52 | 'first' => array( 53 | 'type' => $types->int(), 54 | 'description' => esc_html__( 'The first number of items to fetch for the collection.', 'wp-graphql' ), 55 | // WordPress internally uses 0 to fetch all; this is a bad idea. 56 | 'defaultValue' => 0, 57 | ), 58 | 'after' => array( 59 | 'type' => $types->int(), 60 | 'description' => esc_html__( 'The offset for fetching the collection.', 'wp-graphql' ), 61 | 'defaultValue' => 0, 62 | ), 63 | ), 64 | ), 65 | 'objects' => array( 66 | 'type' => $types->listOf( $types->post() ), 67 | 'description' => esc_html__( 'The post objects associated with the selected term.', 'wp-graphql' ), 68 | 'args' => array( 69 | 'first' => array( 70 | 'type' => $types->int(), 71 | 'description' => esc_html__( 'The first number of items to fetch for the collection.', 'wp-graphql' ), 72 | 'defaultValue' => 10, 73 | ), 74 | 'after' => array( 75 | 'type' => $types->int(), 76 | 'description' => esc_html__( 'The offset for fetching the collection.', 'wp-graphql' ), 77 | 'defaultValue' => 0, 78 | ), 79 | ), 80 | ), 81 | ); 82 | }, 83 | 'interfaces' => [ 84 | $types->node(), 85 | ], 86 | 'description' => esc_html__( 'Terms are used to name particular groups within a taxonomy. WordPress ships with categories and tags as default taxonomies. Any individual category in the category taxonomy will be a term. This type internally mirrors the WP_Term object.', 'wp-graphql' ), 87 | 'resolveField' => function( $value, $args, $context, ResolveInfo $info ) { 88 | if ( method_exists( $this, $info->fieldName ) ) { 89 | return $this->{$info->fieldName}( $value, $args, $context, $info ); 90 | } else { 91 | return $value->{$info->fieldName}; 92 | } 93 | }, 94 | ]); 95 | } 96 | 97 | public function id( \WP_Term $term, $args, AppContext $context ) { 98 | return $term->term_id; 99 | } 100 | 101 | public function parent( \WP_Term $term, $args, AppContext $context ) { 102 | return get_term( $term->parent ); 103 | } 104 | 105 | public function group( \WP_Term $term, $args, AppContext $context ) { 106 | return $term->term_group; 107 | } 108 | 109 | public function taxonomy( \WP_Term $term, $args, AppContext $context ) { 110 | return get_taxonomy( $term->taxonomy ); 111 | } 112 | 113 | public function children( \WP_Term $term, $args, AppContext $context ) { 114 | $terms = get_terms( array( 115 | 'taxonomy' => $term->taxonomy, 116 | 'parent' => $term->term_id, 117 | 'number' => $args['first'], 118 | 'offset' => $args['after'], 119 | 'hide_empty' => false, 120 | ) ); 121 | 122 | if ( is_wp_error( $terms ) ) { 123 | return null; 124 | } 125 | 126 | return $terms; 127 | } 128 | 129 | public function objects( \WP_Term $term, $args, AppContext $context ) { 130 | $tax_query = array( 131 | array( 132 | 'taxonomy' => $term->taxonomy, 133 | 'field' => 'term_id', 134 | 'terms' => $term->term_id, 135 | ), 136 | ); 137 | 138 | $query_args = array( 139 | 'no_found_rows' => true, 140 | 'tax_query' => $tax_query, 141 | 'posts_per_page' => $args['first'], 142 | 'offest' => $args['after'], 143 | ); 144 | 145 | $query = new \WP_Query(); 146 | 147 | $objects = $query->query( $query_args ); 148 | 149 | return $objects; 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/Type/ThemeType.php: -------------------------------------------------------------------------------- 1 | definition = new ObjectType([ 13 | 'name' => 'Theme', 14 | 'fields' => function() use ( $types ) { 15 | return array( 16 | 'slug' => array( 17 | 'type' => $types->string(), 18 | 'description' => esc_html__( 'The theme slug is used to internally match themes. Theme slugs can have subdirectories like: my-theme/sub-theme. This field is equivalent to WP_Theme->get_stylesheet().', 'wp-graphql' ), 19 | ), 20 | 'name' => array( 21 | 'type' => $types->string(), 22 | 'description' => esc_html__( 'Display name of the theme. This field is equivalent to WP_Theme->get( "Name" ).', 'wp-graphql' ), 23 | ), 24 | 'screenshot' => array( 25 | 'type' => $types->string(), 26 | 'description' => esc_html__( 'The URL of the screenshot for the theme. The screenshot is intended to give an overview of what the theme looks like. This field is equivalent to WP_Theme->get_screenshot().', 'wp-graphql' ), 27 | ), 28 | 'theme_uri' => array( 29 | 'type' => $types->string(), 30 | 'description' => esc_html__( 'A URI if the theme has a website associated with it. The Theme URI is handy for directing users to a theme site for support etc. This field is equivalent to WP_Theme->get( "ThemeURI" ).', 'wp-graphql' ), 31 | ), 32 | 'description' => array( 33 | 'type' => $types->string(), 34 | 'description' => esc_html__( 'The description of the theme. This field is equivalent to WP_Theme->get( "Description" ).', 'wp-graphql' ), 35 | ), 36 | 'author' => array( 37 | 'type' => $types->string(), 38 | 'description' => esc_html__( 'Name of the theme author(s), could also be a company name. This field is equivalent to WP_Theme->get( "Author" ).', 'wp-graphql' ), 39 | ), 40 | 'author_uri' => array( 41 | 'type' => $types->string(), 42 | 'description' => esc_html__( 'URI for the author/company website. This field is equivalent to WP_Theme->get( "AuthorURI" ).', 'wp-graphql' ), 43 | ), 44 | 'tags' => array( 45 | 'type' => $types->listOf( $types->string() ), 46 | 'description' => esc_html__( 'URI for the author/company website. This field is equivalent to WP_Theme->get( "Tags" ).', 'wp-graphql' ), 47 | ), 48 | 'version' => array( 49 | 'type' => $types->string(), 50 | 'description' => esc_html__( 'The current version of the theme. This field is equivalent to WP_Theme->get( "Version" ).', 'wp-graphql' ), 51 | ), 52 | ); 53 | }, 54 | 'description' => esc_html__( 'Themes are responsible for the presentational aspect of a WordPress installation. Internally this type resolves to a WP_Theme object.', 'wp-graphql' ), 55 | 'resolveField' => function( $value, $args, $context, ResolveInfo $info ) { 56 | if ( method_exists( $this, $info->fieldName ) ) { 57 | return $this->{$info->fieldName}( $value, $args, $context, $info ); 58 | } else { 59 | return $value->{$info->fieldName}; 60 | } 61 | }, 62 | ]); 63 | } 64 | 65 | public function slug( \WP_Theme $theme, $args, AppContext $context) { 66 | return $theme->get_stylesheet(); 67 | } 68 | 69 | public function name( \WP_Theme $theme, $args, AppContext $context) { 70 | return $theme->get( 'Name' ); 71 | } 72 | 73 | public function screenshot( \WP_Theme $theme, $args, AppContext $context) { 74 | return $theme->get_screenshot(); 75 | } 76 | 77 | public function theme_uri( \WP_Theme $theme, $args, AppContext $context) { 78 | return $theme->get( 'ThemeURI' ); 79 | } 80 | 81 | public function description( \WP_Theme $theme, $args, AppContext $context) { 82 | return $theme->get( 'Description' ); 83 | } 84 | 85 | public function author( \WP_Theme $theme, $args, AppContext $context) { 86 | return $theme->get( 'Author' ); 87 | } 88 | 89 | public function author_uri( \WP_Theme $theme, $args, AppContext $context) { 90 | return $theme->get( 'AuthorURI' ); 91 | } 92 | 93 | public function tags( \WP_Theme $theme, $args, AppContext $context) { 94 | return $theme->get( 'Tags' ); 95 | } 96 | 97 | public function version( \WP_Theme $theme, $args, AppContext $context) { 98 | return $theme->get( 'Version' ); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/Type/UserType.php: -------------------------------------------------------------------------------- 1 | definition = new ObjectType([ 21 | 'name' => 'User', 22 | 'fields' => function() use ( $types ) { 23 | return [ 24 | 'id' => array( 25 | 'type' => $types->id(), 26 | 'description' => esc_html__( 'This field is the id of the user. The id of the user matches WP_User->ID field and the value in the ID column for the `wp_users` table in SQL.', 'wp-graphql' ), 27 | ), 28 | 'capabilities' => array( 29 | 'type' => $types->listOf( $types->string() ), 30 | 'description' => esc_html__( 'Returns the list of individually assigned capabilities a user has. This is equivalent to the array keys of WP_User->allcaps, where the capability is set to true.', 'wp-graphql' ), 31 | ), 32 | 'cap_key' => array( 33 | 'type' => $types->string(), 34 | 'description' => esc_html__( 'User metadata option name. Usually it will be `wp_capabilities`.', 'wp-graphql' ), 35 | ), 36 | 'roles' => array( 37 | 'type' => $types->listOf( $types->string() ), 38 | 'description' => esc_html__( 'A list of roles that the user has. Roles can be used for querying for certain types of users, but should not be used in permissions checks.', 'wp-graphql' ), 39 | ), 40 | 'extra_capabilities' => array( 41 | 'type' => $types->listOf( $types->string() ), 42 | 'description' => esc_html__( 'A complete list of capabilities including capabilities inherited from a role. This is equivalent to the array keys of WP_User->allcaps.', 'wp-graphql' ), 43 | ), 44 | 'email' => array( 45 | 'type' => $types->string(), 46 | 'description' => esc_html__( 'Email of the user. This is equivalent to the WP_User->user_email property.', 'wp-graphql' ), 47 | ), 48 | 'first_name' => array( 49 | 'type' => $types->string(), 50 | 'description' => esc_html__( 'First name of the user. This is equivalent to the WP_User->user_first_name property.', 'wp-graphql' ), 51 | ), 52 | 'last_name' => array( 53 | 'type' => $types->string(), 54 | 'description' => esc_html__( 'Last name of the user. This is equivalent to the WP_User->user_last_name property.', 'wp-graphql' ), 55 | ), 56 | 'description' => array( 57 | 'type' => $types->string(), 58 | 'description' => esc_html__( 'Description of the user.', 'wp-graphql' ), 59 | ), 60 | 'username' => array( 61 | 'type' => $types->string(), 62 | 'description' => esc_html__( 'Username for the user. This field is equivalent to WP_User->user_login.', 'wp-graphql' ), 63 | ), 64 | 'name' => array( 65 | 'type' => $types->string(), 66 | 'description' => esc_html__( 'Display name of the user. This is equivalent to the WP_User->dispaly_name property.', 'wp-graphql' ), 67 | ), 68 | 'registered_date' => array( 69 | 'type' => $types->string(), 70 | 'description' => esc_html__( 'The date the user registered or was created. The field follows a full ISO8601 date string format.', 'wp-graphql' ), 71 | ), 72 | 'nickname' => array( 73 | 'type' => $types->string(), 74 | 'description' => esc_html__( 'Nickname of the user.', 'wp-graphql' ), 75 | ), 76 | 'url' => array( 77 | 'type' => $types->string(), 78 | 'description' => esc_html__( 'A website url that is associated with the user.', 'wp-graphql' ), 79 | ), 80 | 'slug' => array( 81 | 'type' => $types->string(), 82 | 'description' => esc_html__( 'The slug for the user. This field is equivalent to WP_User->user_nicename', 'wp-graphql' ), 83 | ), 84 | 'locale' => array( 85 | 'type' => $types->string(), 86 | 'description' => esc_html__( 'The preferred language locale set for the user. Value derived from get_user_locale().', 'wp-graphql' ), 87 | ), 88 | 'avatar' => array( 89 | 'type' => $types->avatar(), 90 | 'description' => esc_html__( 'Avatar object for user. The avatar object can be retrieved in different sizes by specifying the size argument.', 'wp-graphql' ), 91 | 'args' => array( 92 | 'size' => array( 93 | 'type' => $types->int(), 94 | 'description' => esc_html__( 'The size attribute of the avatar field can be used to fetch avatars of different sizes. The value corresponds to the dimension in pixels to fetch. The default is 96 pixels.', 'wp-graphql' ), 95 | 'defaultValue' => 96, 96 | ), 97 | ), 98 | ), 99 | 'posts' => array( 100 | 'type' => $types->listOf( $types->post() ), 101 | 'description' => esc_html__( 'A collection of posts assigned to the user.', 'wp-graphql' ), 102 | 'args' => [ 103 | // Limit and after are equivalent to per_page and offset. 104 | 'first' => array( 105 | 'type' => $types->int(), 106 | 'description' => esc_html__( 'The number of posts by this user to query for. First is pretty much the same as LIMIT in SQL, or a `per_page` parameter in pagination.', 'wp-graphql' ), 107 | 'defaultValue' => 10, 108 | ), 109 | 'after' => array( 110 | 'type' => $types->int(), 111 | 'description' => esc_html__( 'The offset for the query.', 'wp-graphql' ), 112 | 'defaultValue' => 0, 113 | ), 114 | ], 115 | ), 116 | 'comments' => array( 117 | 'type' => $types->listOf( $types->comment() ), 118 | 'description' => esc_html__( 'A collection of comments assigned to the user.', 'wp-graphql' ), 119 | 'args' => [ 120 | // Limit and after are equivalent to per_page and offset. 121 | 'first' => array( 122 | 'type' => $types->int(), 123 | 'description' => esc_html__( 'The number of comments by this user to query for. First is pretty much the same as LIMIT in SQL, or a `per_page` parameter in pagination.', 'wp-graphql' ), 124 | 'defaultValue' => 10, 125 | ), 126 | 'after' => array( 127 | 'type' => $types->int(), 128 | 'description' => esc_html__( 'The offset for the query.', 'wp-graphql' ), 129 | 'defaultValue' => 0, 130 | ), 131 | ], 132 | ), 133 | ]; 134 | }, 135 | 'interfaces' => [ 136 | $types->node(), 137 | ], 138 | 'description' => esc_html__( 'The User type is internally represented by a WP_User object. Some of the fields are aliases for properties of the WP_User object.', 'wp-graphql' ), 139 | 'resolveField' => function( $value, $args, $context, ResolveInfo $info ) { 140 | if ( method_exists( $this, $info->fieldName ) ) { 141 | return $this->{$info->fieldName}( $value, $args, $context, $info ); 142 | } else { 143 | return $value->{$info->fieldName}; 144 | } 145 | }, 146 | ]); 147 | } 148 | 149 | /** 150 | * User field resolver. 151 | * 152 | * Note that user is a field within the user type. 153 | * 154 | * @param \WP_User $user User for the resolver. 155 | * @param array $args List of arguments for this resolver. 156 | * @param AppContext $context Context object for the Application. 157 | * @return string 158 | */ 159 | public function id( \WP_User $user, $args, AppContext $context ) { 160 | return $user->ID; 161 | } 162 | 163 | /** 164 | * User field resolver. 165 | * 166 | * Note that user is a field within the user type. 167 | * 168 | * @param \WP_User $user User for the resolver. 169 | * @param array $args List of arguments for this resolver. 170 | * @param AppContext $context Context object for the Application. 171 | * @return string 172 | */ 173 | public function capabilities( \WP_User $user, $args, AppContext $context ) { 174 | // Filters list for capabilities the user has. 175 | return array_keys( array_filter( $user->allcaps, function( $cap ) { 176 | return true === $cap; 177 | } ) ); 178 | } 179 | 180 | /** 181 | * User field resolver. 182 | * 183 | * Note that user is a field within the user type. 184 | * 185 | * @param \WP_User $user User for the resolver. 186 | * @param array $args List of arguments for this resolver. 187 | * @param AppContext $context Context object for the Application. 188 | * @return string 189 | */ 190 | public function extra_capabilities( \WP_User $user, $args, AppContext $context ) { 191 | return array_keys( $user->allcaps ); 192 | } 193 | 194 | /** 195 | * User field resolver. 196 | * 197 | * Note that user is a field within the user type. 198 | * 199 | * @param \WP_User $user User for the resolver. 200 | * @param array $args List of arguments for this resolver. 201 | * @param AppContext $context Context object for the Application. 202 | * @return string 203 | */ 204 | public function email( \WP_User $user, $args, AppContext $context ) { 205 | return $user->user_email; 206 | } 207 | 208 | /** 209 | * User field resolver. 210 | * 211 | * Note that user is a field within the user type. 212 | * 213 | * @param \WP_User $user User for the resolver. 214 | * @param array $args List of arguments for this resolver. 215 | * @param AppContext $context Context object for the Application. 216 | * @return string 217 | */ 218 | public function username( \WP_User $user, $args, AppContext $context ) { 219 | return $user->user_login; 220 | } 221 | 222 | /** 223 | * User field resolver. 224 | * 225 | * Note that user is a field within the user type. 226 | * 227 | * @param \WP_User $user User for the resolver. 228 | * @param array $args List of arguments for this resolver. 229 | * @param AppContext $context Context object for the Application. 230 | * @return string 231 | */ 232 | public function name( \WP_User $user, $args, AppContext $context ) { 233 | return $user->display_name; 234 | } 235 | 236 | /** 237 | * User field resolver. 238 | * 239 | * Note that user is a field within the user type. 240 | * 241 | * @param \WP_User $user User for the resolver. 242 | * @param array $args List of arguments for this resolver. 243 | * @param AppContext $context Context object for the Application. 244 | * @return string 245 | */ 246 | public function url( \WP_User $user, $args, AppContext $context ) { 247 | return $user->user_url; 248 | } 249 | 250 | /** 251 | * User field resolver. 252 | * 253 | * Note that user is a field within the user type. 254 | * 255 | * @param \WP_User $user User for the resolver. 256 | * @param array $args List of arguments for this resolver. 257 | * @param AppContext $context Context object for the Application. 258 | * @return string 259 | */ 260 | public function slug( \WP_User $user, $args, AppContext $context ) { 261 | return $user->user_nicename; 262 | } 263 | 264 | /** 265 | * User field resolver. 266 | * 267 | * Note that user is a field within the user type. 268 | * 269 | * @param \WP_User $user User for the resolver. 270 | * @param array $args List of arguments for this resolver. 271 | * @param AppContext $context Context object for the Application. 272 | * @return string 273 | */ 274 | public function locale( \WP_User $user, $args, AppContext $context ) { 275 | return get_user_locale( $user ); 276 | } 277 | 278 | /** 279 | * User field resolver. 280 | * 281 | * Note that user is a field within the user type. 282 | * 283 | * @param \WP_User $user User for the resolver. 284 | * @param array $args List of arguments for this resolver. 285 | * @param AppContext $context Context object for the Application. 286 | * @return string 287 | */ 288 | public function registered_date( \WP_User $user, $args, AppContext $context ) { 289 | return date( 'c', strtotime( $user->user_registered ) ); 290 | } 291 | 292 | /** 293 | * User field resolver. 294 | * 295 | * Note that user is a field within the user type. 296 | * 297 | * @param \WP_User $user User for the resolver. 298 | * @param array $args List of arguments for this resolver. 299 | * @param AppContext $context Context object for the Application. 300 | * @return string 301 | */ 302 | public function avatar( \WP_User $user, $args, AppContext $context ) { 303 | return get_avatar_data( $user->ID, array( 'size', $args['size'] ) ); 304 | } 305 | 306 | /** 307 | * Posts field resolver. 308 | * 309 | * Returns a collection of posts by the author. 310 | * 311 | * @param \WP_User $user User for the resolver. 312 | * @param array $args List of arguments for this resolver. 313 | * @param AppContext $context Context object for the Application. 314 | * @return string 315 | */ 316 | public function posts( \WP_User $user, $args, AppContext $context ) { 317 | $query_args = array( 318 | 'author' => $user->ID, 319 | 'no_found_rows' => true, 320 | ); 321 | 322 | if ( isset( $args['first'] ) ) { 323 | $query_args['posts_per_page'] = $args['first']; 324 | } 325 | 326 | if ( isset( $args['after'] ) ) { 327 | $query_args['offset'] = $args['after']; 328 | } 329 | 330 | $posts_query = new \WP_Query(); 331 | $posts = $posts_query->query( $query_args ); 332 | return ! empty( $posts ) ? $posts : null; 333 | } 334 | 335 | /** 336 | * Comments field resolver. 337 | * 338 | * @param mixed $value Value for the resolver. 339 | * @param array $args List of arguments for this resolver. 340 | * @param AppContext $context Context object for the Application. 341 | * @return array List of WP_Comment objects. 342 | */ 343 | public function comments( \WP_User $user, $args, AppContext $context ) { 344 | $query_args = array( 345 | 'user_id' => $user->ID, 346 | ); 347 | 348 | if ( isset( $args['first'] ) ) { 349 | $query_args['number'] = $args['first']; 350 | } 351 | 352 | if ( isset( $args['after'] ) ) { 353 | $query_args['offset'] = $args['after']; 354 | } 355 | 356 | $comments_query = new \WP_Comment_Query(); 357 | $comments = $comments_query->query( $query_args ); 358 | return ! empty( $comments ) ? $comments : null; 359 | } 360 | } 361 | -------------------------------------------------------------------------------- /src/TypeSystem.php: -------------------------------------------------------------------------------- 1 | wp_config = $wp_config; 112 | 113 | if ( isset( $wp_config['post_types'] ) && is_array( $wp_config['post_types'] ) ) { 114 | foreach ( $wp_config['post_types'] as $type => $post_type ) { 115 | $single = $post_type['name']; 116 | $single_type = $post_type['singular_type']; 117 | 118 | $this->{$single} = new PostObjectType( $this, $single_type ); 119 | } 120 | } 121 | } 122 | 123 | /** 124 | * @return PostType 125 | */ 126 | public function post() { 127 | return $this->post ?: ( $this->post = new PostType( $this ) ); 128 | } 129 | 130 | /** 131 | * @return UserType 132 | */ 133 | public function user() { 134 | return $this->user ?: ( $this->user = new UserType( $this ) ); 135 | } 136 | 137 | /** 138 | * @return CommentType 139 | */ 140 | public function comment() { 141 | return $this->comment ?: ( $this->comment = new CommentType( $this ) ); 142 | } 143 | 144 | /** 145 | * @return TermType 146 | */ 147 | public function term() { 148 | return $this->term ?: ( $this->term = new TermType( $this ) ); 149 | } 150 | 151 | /** 152 | * @return TaxonomyType 153 | */ 154 | public function taxonomy() { 155 | return $this->taxonomy ?: ( $this->taxonomy = new TaxonomyType( $this ) ); 156 | } 157 | 158 | /** 159 | * @return MenuItemType 160 | */ 161 | public function menu_item() { 162 | return $this->menu_item ?: ( $this->menu_item = new MenuItemType( $this ) ); 163 | } 164 | 165 | /** 166 | * @return MenuType 167 | */ 168 | public function menu() { 169 | return $this->menu ?: ( $this->menu = new MenuType( $this ) ); 170 | } 171 | 172 | /** 173 | * @return MenuLocationType 174 | */ 175 | public function menu_location() { 176 | return $this->menu_location ?: ( $this->menu_location = new MenuLocationType( $this ) ); 177 | } 178 | 179 | /** 180 | * @return ThemeType 181 | */ 182 | public function theme() { 183 | return $this->theme ?: ( $this->theme = new ThemeType( $this ) ); 184 | } 185 | 186 | /** 187 | * @return PluginType 188 | */ 189 | public function plugin() { 190 | return $this->plugin ?: ( $this->plugin = new PluginType( $this ) ); 191 | } 192 | 193 | /** 194 | * @return PostTypeType 195 | */ 196 | public function post_type() { 197 | return $this->post_type ?: ( $this->post_type = new PostTypeType( $this ) ); 198 | } 199 | 200 | /** 201 | * @return AvatarType 202 | */ 203 | public function avatar() { 204 | return $this->avatar ?: ( $this->avatar = new AvatarType( $this ) ); 205 | } 206 | 207 | /** 208 | * @return QueryType 209 | */ 210 | public function query() { 211 | return $this->query ?: ( $this->query = new QueryType( $this ) ); 212 | } 213 | 214 | /** 215 | * @return PostObjectType 216 | */ 217 | public function post_object( $post_type ) { 218 | return $this->{$post_type} ?: ( $this->{$post_type} = new PostObjectType( $this, $post_type ) ); 219 | } 220 | 221 | 222 | // Interface types 223 | private $node; 224 | private $post_interface; 225 | 226 | /** 227 | * @return NodeType 228 | */ 229 | public function node() { 230 | return $this->node ?: ( $this->node = new NodeType( $this ) ); 231 | } 232 | 233 | public function post_interface() { 234 | return $this->post_interface ?: ( $this->post_interface = new PostInterfaceType( $this ) ); 235 | } 236 | 237 | // Add basic scalar types. 238 | public function boolean() { 239 | return Type::boolean(); 240 | } 241 | 242 | /** 243 | * @return \GraphQL\Type\Definition\FloatType 244 | */ 245 | public function float() { 246 | return Type::float(); 247 | } 248 | 249 | /** 250 | * @return \GraphQL\Type\Definition\IDType 251 | */ 252 | public function id() { 253 | return Type::id(); 254 | } 255 | 256 | /** 257 | * @return \GraphQL\Type\Definition\IntType 258 | */ 259 | public function int() { 260 | return Type::int(); 261 | } 262 | 263 | /** 264 | * @return \GraphQL\Type\Definition\StringType 265 | */ 266 | public function string() { 267 | return Type::string(); 268 | } 269 | 270 | /** 271 | * @param Type|DefinitionContainer $type 272 | * @return ListOfType 273 | */ 274 | public function listOf( $type ) { 275 | return new ListOfType( $type ); 276 | } 277 | 278 | /** 279 | * @param Type|DefinitionContainer $type 280 | * @return NonNull 281 | */ 282 | public function nonNull( $type ) { 283 | return new NonNull( $type ); 284 | } 285 | } 286 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | admin = $this->factory->user->create( array( 26 | 'role' => 'admin', 27 | ) ); 28 | 29 | $wp_config = \BEForever\WPGraphQL\graphql_build_wp_config(); 30 | 31 | // Build the complete type system. 32 | $this->types = new TypeSystem( $wp_config ); 33 | $this->wp_query_schema = new WPQuery( $this->types ); 34 | } 35 | 36 | /** 37 | * Runs after each method. 38 | */ 39 | public function tearDown() { 40 | parent::tearDown(); 41 | } 42 | 43 | /** 44 | * Tests the query for hello. 45 | */ 46 | public function test_wp_query_args_schema() { 47 | $expected = array( 48 | // Author parameters. 49 | 'author' => array( 50 | 'type' => $this->types->int(), 51 | 'description' => esc_html__( 'Restricts a collection based on author ID.', 'wp-graphql' ), 52 | ), 53 | 'author_name' => array( 54 | 'type' => $this->types->string(), 55 | 'description' => esc_html__( 'Restricts a collection based on author slug. This field matches against the WP_User->user_nicename property.', 'wp-graphql' ), 56 | ), 57 | 'author__in' => array( 58 | 'type' => $this->types->listOf( $this->types->int() ), 59 | 'description' => esc_html__( 'Restricts a collection based on a list of author IDs', 'wp-graphql' ), 60 | ), 61 | 'author__not_in' => array( 62 | 'type' => $this->types->listOf( $this->types->int() ), 63 | 'description' => esc_html__( 'Removes items from a collection based on a list of author IDs', 'wp-graphql' ), 64 | ), 65 | // Category parameters. 66 | 'category' => array( 67 | 'type' => $this->types->int(), 68 | 'description' => esc_html__( 'Restricts a collection based on category ID.', 'wp-graphql' ), 69 | ), 70 | 'category_name' => array( 71 | 'type' => $this->types->string(), 72 | 'description' => esc_html__( 'Restricts a collection based on category slug.', 'wp-graphql' ), 73 | ), 74 | 'category_and' => array( 75 | 'type' => $this->types->listOf( $this->types->int() ), 76 | 'description' => esc_html__( 'Restricts a collection based on posts that have each category ID.', 'wp-graphql' ), 77 | ), 78 | 'category__in' => array( 79 | 'type' => $this->types->listOf( $this->types->int() ), 80 | 'description' => esc_html__( 'Restricts a collection based on a list of category IDs', 'wp-graphql' ), 81 | ), 82 | 'category__not_in' => array( 83 | 'type' => $this->types->listOf( $this->types->int() ), 84 | 'description' => esc_html__( 'Removes items from a collection based on a list of category IDs', 'wp-graphql' ), 85 | ), 86 | // Tag parameters. 87 | 'tag' => array( 88 | 'type' => $this->types->int(), 89 | 'description' => esc_html__( 'Restricts a collection based on tag ID.', 'wp-graphql' ), 90 | ), 91 | 'tag_and' => array( 92 | 'type' => $this->types->listOf( $this->types->int() ), 93 | 'description' => esc_html__( 'Restricts a collection based on tag slug.', 'wp-graphql' ), 94 | ), 95 | 'tag__in' => array( 96 | 'type' => $this->types->listOf( $this->types->int() ), 97 | 'description' => esc_html__( 'Restricts a collection based on a list of category IDs', 'wp-graphql' ), 98 | ), 99 | 'tag__not_in' => array( 100 | 'type' => $this->types->listOf( $this->types->int() ), 101 | 'description' => esc_html__( 'Removes items from a collection based on a list of tag IDs', 'wp-graphql' ), 102 | ), 103 | 'tag_slug__and' => array( 104 | 'type' => $this->types->listOf( $this->types->string() ), 105 | 'description' => esc_html__( 'Restricts a collection based on posts that have each tag ID.', 'wp-graphql' ), 106 | ), 107 | 'tag_slug__in' => array( 108 | 'type' => $this->types->listOf( $this->types->string() ), 109 | 'description' => esc_html__( 'Restricts a collection based on tag slugs.', 'wp-graphql' ), 110 | ), 111 | // Taxonomy query parameters. This must be composed of another type. 112 | // Search parameters. 113 | 's' => array( 114 | 'type' => $this->types->string(), 115 | 'description' => esc_html__( 'Search phrase to use.', 'wp-graphql' ), 116 | ), 117 | // Post parameters. 118 | 'p' => array( 119 | 'type' => $this->types->int(), 120 | 'description' => esc_html__( 'Restricts a collection based on post ID.', 'wp-graphql' ), 121 | ), 122 | 'name' => array( 123 | 'type' => $this->types->string(), 124 | 'description' => esc_html__( 'Restricts a collection based on post slug.', 'wp-graphql' ), 125 | ), 126 | 'page_id' => array( 127 | 'type' => $this->types->int(), 128 | 'description' => esc_html__( 'Restricts a collection based on a page ID', 'wp-graphql' ), 129 | ), 130 | 'page_slug' => array( 131 | 'type' => $this->types->string(), 132 | 'description' => esc_html__( 'Restricts a collection based on a page slug', 'wp-graphql' ), 133 | ), 134 | 'post_parent' => array( 135 | 'type' => $this->types->int(), 136 | 'description' => esc_html__( 'Restricts items from a collection based on post parent ID', 'wp-graphql' ), 137 | ), 138 | 'post_parent__in' => array( 139 | 'type' => $this->types->listOf( $this->types->int() ), 140 | 'description' => esc_html__( 'Restricts items from a collection based on a list of post parent IDs', 'wp-graphql' ), 141 | ), 142 | 'post_parent__not_in' => array( 143 | 'type' => $this->types->listOf( $this->types->int() ), 144 | 'description' => esc_html__( 'Removes items from a collection based on a list of post parent IDs', 'wp-graphql' ), 145 | ), 146 | 'post__in' => array( 147 | 'type' => $this->types->listOf( $this->types->int() ), 148 | 'description' => esc_html__( 'Restrics items from a collection based on a list of post IDs', 'wp-graphql' ), 149 | ), 150 | 'post__not_in' => array( 151 | 'type' => $this->types->listOf( $this->types->int() ), 152 | 'description' => esc_html__( 'Removes items from a collection based on a list of post IDs', 'wp-graphql' ), 153 | ), 154 | 'post_name__in' => array( 155 | 'type' => $this->types->listOf( $this->types->string() ), 156 | 'description' => esc_html__( 'Restricts items from a collection based on a list of post slugs', 'wp-graphql' ), 157 | ), 158 | // Post type parameters. This should be changed to a union type at some point for consistency with WP_Query. 159 | 'post_type' => array( 160 | 'type' => $this->types->listOf( $this->types->string() ), 161 | 'description' => esc_html__( 'Restricts items from a collection based on a list of post types', 'wp-graphql' ), 162 | ), 163 | // Post type parameters. This should be changed to a union type at some point for consistency with WP_Query. 164 | 'post_status' => array( 165 | 'type' => $this->types->listOf( $this->types->string() ), 166 | 'description' => esc_html__( 'Restricts items from a collection based on a list of post stati', 'wp-graphql' ), 167 | ), 168 | // Pagination parameters will be handled by WP GraphQL differently to make collections Relay compliant. 169 | 'first' => array( 170 | 'type' => $this->types->int(), 171 | 'description' => esc_html__( 'The pagination limit. This is equivalent to posts_per_page for WP_Query.', 'wp-graphql' ), 172 | 'defaultValue' => 10, 173 | ), 174 | 'after' => array( 175 | 'type' => $this->types->int(), 176 | 'description' => esc_html__( 'The pagination offset. This is equivalent to offset for WP_Query.', 'wp-graphql' ), 177 | 'defaultValue' => 0, 178 | ), 179 | 'ignore_sticky_posts' => array( 180 | 'type' => $this->types->boolean(), 181 | 'description' => esc_html__( 'A boolean flag for whether to ignore sticky posts.', 'wp-graphql' ), 182 | 'defaultValue' => false, 183 | ), 184 | // Order/orderby params. Currently WP GraphQL will not support multiple orderby params as it will be difficult to support arbitrary key value multiple orderby params. 185 | 'order' => array( 186 | 'type' => $this->types->string(), 187 | 'description' => esc_html__( 'Orders a collection either ascending or descending based on specified orderby.', 'wp-graphql' ), 188 | 'defaultValue' => 'DESC', 189 | ), 190 | 'orderby' => array( 191 | 'type' => $this->types->string(), 192 | 'description' => esc_html__( 'Restricts a collection based on a page slug', 'wp-graphql' ), 193 | 'defaultValue' => 'date', 194 | ), 195 | // Date parameters not supported yet. 196 | // Meta query parameters not supported yet. 197 | // Permissions paramaters not supported yet. 198 | // Mime type params. 199 | 'post_mime_type' => array( 200 | 'type' => $this->types->string(), 201 | 'description' => esc_html__( 'Restricts a collection of attachments based on mime_type', 'wp-graphql' ), 202 | ), 203 | // Caching parameters are not supported, and probably will not be. 204 | ); 205 | $actual = $this->wp_query_schema->args(); 206 | 207 | $this->assertEquals( $expected, $actual ); 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.4 5 | - 5.5 6 | - 5.6 7 | - 7.0 8 | - hhvm 9 | 10 | before_script: 11 | - composer self-update 12 | - composer install --prefer-source --no-interaction --dev 13 | 14 | script: phpunit 15 | -------------------------------------------------------------------------------- /wp-graphql.php: -------------------------------------------------------------------------------- 1 | add_query_var( 'graphql_path' ); 63 | } 64 | 65 | /** 66 | * Adds REST rewrite rules. 67 | * 68 | * @since 4.4.0 69 | * 70 | * @see add_rewrite_rule() 71 | */ 72 | function graphql_api_register_rewrites() { 73 | add_rewrite_rule( '^' . get_graphql_url_path() . '/?$', 'index.php?graphql_path=/', 'top' ); 74 | } 75 | 76 | /** 77 | * Fires when a request is parsed by WordPress and matches the GraphQL endpoint. 78 | * 79 | * @global WP $wp Current WordPress environment instance. 80 | */ 81 | function graphql_api_loaded() { 82 | if ( empty( $GLOBALS['wp']->query_vars['graphql_path'] ) ) { 83 | return; 84 | } 85 | 86 | /** 87 | * Whether this is a GRAPHQL Request. 88 | * 89 | * @var bool 90 | */ 91 | define( 'GRAPHQL_REQUEST', true ); 92 | 93 | /** 94 | * Serve request and echo response. 95 | */ 96 | $response = serve_graphql_request(); 97 | header( sprintf( 'Content-Type: application/json; charset=%s', get_option( 'blog_charset' ) ) ); 98 | echo wp_json_encode( $response ); 99 | 100 | // We're done. 101 | die(); 102 | } 103 | 104 | /** 105 | * Retrieves the URL path for the GraphQL endpoint. 106 | * 107 | * @return string Prefix. 108 | */ 109 | function get_graphql_url_path() { 110 | /** 111 | * Makes the GraphQL endpoint changeable. 112 | * 113 | * @param string $path Pathname for graphql. DO NOT use leading slash. 114 | */ 115 | return apply_filters( 'graphql_url', 'graphql' ); 116 | } 117 | 118 | /** 119 | * Sets up necessary checks for plugin activation. 120 | */ 121 | function graphql_setup() { 122 | // Define constants. 123 | define_graphql_constants(); 124 | 125 | // Used to check version of WP to make sure it is greater than 4.4! 126 | add_action( 'admin_init', '\BEForever\WPGraphQL\graphql_check_compatibilty' ); 127 | 128 | // If you are using an unsupported version of wordpress then don't do anything. 129 | if ( ! graphql_is_wp_compatible( WP_GRAPHQL_MINIMUM_WP_VERSION ) || ! graphql_is_php_compatible( WP_GRAPHQL_MINIMUM_PHP_VERSION ) ) { 130 | return; 131 | } 132 | } 133 | 134 | /** 135 | * Checks whether the installation is compatible with required PHP and WP versions. 136 | */ 137 | function graphql_is_compatible() { 138 | return ( graphql_is_wp_compatible() && graphql_is_php_compatible() ); 139 | } 140 | 141 | /** 142 | * This function runs an activation check to make sure plugin runs correctly. 143 | * 144 | * This is typically triggered via WP-CLI activation. 145 | */ 146 | function graphql_activation_check() { 147 | if ( ! graphql_is_compatible() ) { 148 | deactivate_plugins( plugin_basename( __FILE__ ) ); 149 | wp_die( esc_html__( 'WP GraphQL requires WordPress 4.6 or higher and PHP 5.4 or higher!', 'wp-graphql' ) ); 150 | } 151 | 152 | flush_rewrite_rules(); 153 | } 154 | 155 | /** 156 | * Checks whether the WordPress install meets the plugin requirements. 157 | * 158 | * This runs on plugin activation, which can happen via the admin or via CLI. 159 | */ 160 | function graphql_check_compatibilty() { 161 | if ( ! graphql_is_compatible() ) { 162 | if ( is_plugin_active( plugin_basename( __FILE__ ) ) ) { 163 | deactivate_plugins( plugin_basename( __FILE__ ) ); 164 | add_action( 'admin_notices', '\BEForever\WPGraphQL\graphql_disabled_wp_notice' ); 165 | 166 | if ( isset( $_GET['activate'] ) ) { 167 | unset( $_GET['activate'] ); 168 | } 169 | } 170 | } 171 | } 172 | 173 | /** 174 | * Echos an error notification. 175 | */ 176 | function graphql_disabled_wp_notice() { 177 | echo '

', esc_html__( 'WP GraphQL requires WordPress 4.6 or higher!', 'wp-graphql' ), '

'; 178 | } 179 | 180 | /** 181 | * Echos an error notification. 182 | */ 183 | function graphql_disabled_php_notice() { 184 | echo '

', esc_html__( 'WP GraphQL requires PHP 5.4 or higher!', 'wp-graphql' ), '

'; 185 | } 186 | 187 | /** 188 | * Checks whether the current WP Version is compatible. 189 | * 190 | * @return boolean True if compatible false if not. 191 | */ 192 | function graphql_is_wp_compatible() { 193 | if ( version_compare( $GLOBALS['wp_version'], WP_GRAPHQL_MINIMUM_WP_VERSION, '<' ) ) { 194 | return false; 195 | } 196 | 197 | return true; 198 | } 199 | 200 | /** 201 | * Checks whether the current PHP Version is compatible. 202 | * 203 | * @return boolean True if compatible false if not. 204 | */ 205 | function graphql_is_php_compatible() { 206 | if ( version_compare( phpversion(), WP_GRAPHQL_MINIMUM_PHP_VERSION, '<' ) ) { 207 | return false; 208 | } 209 | 210 | return true; 211 | } 212 | 213 | /** 214 | * Define Constants 215 | */ 216 | function define_graphql_constants() { 217 | graphql_define( 'WP_GRAPHQL_VERSION', '0.0.0' ); 218 | graphql_define( 'WP_GRAPHQL_MINIMUM_WP_VERSION', '4.6' ); 219 | graphql_define( 'WP_GRAPHQL_MINIMUM_PHP_VERSION', '5.4' ); 220 | graphql_define( 'WP_GRAPHQL__PLUGIN_URL', plugin_dir_url( __FILE__ ) ); 221 | graphql_define( 'WP_GRAPHQL__PLUGIN_DIR', plugin_dir_path( __FILE__ ) ); 222 | } 223 | 224 | /** 225 | * Defines a constant that has not already been defined. 226 | * 227 | * Consider changing this as it might be better to have a fatal error incase 228 | * there are conflicting constants. 229 | * 230 | * @param string $constant_name Name of the constant to be defined. 231 | * @param mixed $value Value for the constant. 232 | */ 233 | function graphql_define( $constant_name, $value ) { 234 | if ( ! defined( $constant_name ) ) { 235 | define( $constant_name, $value ); 236 | } 237 | } 238 | 239 | add_filter( 'init', '\BEForever\WPGraphQL\graphql_add_extra_api_post_type_arguments', 11 ); 240 | 241 | /** 242 | * Adds extra post type registration arguments. 243 | * 244 | * These attributes will eventually be committed to core. 245 | * 246 | * @global array $wp_post_types Registered post types. 247 | */ 248 | function graphql_add_extra_api_post_type_arguments() { 249 | global $wp_post_types; 250 | 251 | if ( isset( $wp_post_types['post'] ) ) { 252 | $wp_post_types['post']->show_in_graphql = true; 253 | $wp_post_types['post']->graphql_name = 'post'; 254 | $wp_post_types['post']->graphql_plural_name = 'posts'; 255 | $wp_post_types['post']->graphql_singular_type = 'Post'; 256 | $wp_post_types['post']->graphql_plural_type = 'Posts'; 257 | } 258 | 259 | if ( isset( $wp_post_types['page'] ) ) { 260 | $wp_post_types['page']->show_in_graphql = true; 261 | $wp_post_types['page']->graphql_name = 'page'; 262 | $wp_post_types['page']->graphql_plural_name = 'pages'; 263 | $wp_post_types['page']->graphql_singular_type = 'Page'; 264 | $wp_post_types['page']->graphql_plural_type = 'Pages'; 265 | } 266 | } 267 | 268 | /** 269 | * Get the post types nameing data. 270 | * 271 | * This is set in $wp_config for the TypeSystem. 272 | * 273 | * @global array $wp_post_types Registered post types. 274 | * 275 | * @return array Array of post type data. 276 | */ 277 | function graphql_get_post_types() { 278 | global $wp_post_types; 279 | 280 | if ( is_callable( 'graphql_filter_post_types' ) ) { 281 | return array_filter( $wp_post_types, 'graphql_filter_post_types' ); 282 | } 283 | 284 | // Testing will call this out of namespace. 285 | if ( is_callable( '\BEForever\WPGraphQL\graphql_filter_post_types' ) ) { 286 | return array_filter( $wp_post_types, '\BEForever\WPGraphQL\graphql_filter_post_types' ); 287 | } 288 | } 289 | 290 | /** 291 | * Filter post types that are only set to show_in_graphql. 292 | * 293 | * @param WP_Post_Type $post_type Post type object to check against. 294 | * 295 | * @return array Array of post type data. 296 | */ 297 | function graphql_filter_post_types( $post_type ) { 298 | if ( isset( $post_type->show_in_graphql ) ) { 299 | return true === $post_type->show_in_graphql; 300 | } 301 | 302 | return false; 303 | } 304 | 305 | /** 306 | * Builds the necessary structure for the post types. 307 | * 308 | * @param WP_Post_Type $post_type The post_type object. 309 | */ 310 | function graphql_build_post_type( $post_type ) { 311 | $names = array(); 312 | 313 | if ( isset( $post_type->name ) ) { 314 | $names['registered_name'] = $post_type->name; 315 | } 316 | 317 | if ( isset( $post_type->graphql_name ) ) { 318 | $names['name'] = $post_type->graphql_name; 319 | } else { 320 | $names['name'] = $post_type->name; 321 | } 322 | 323 | if ( isset( $post_type->graphql_plural_name ) ) { 324 | $names['plural_name'] = $post_type->graphql_plural_name; 325 | } else { 326 | // Yes I know, terrible code. 327 | $names['plural_name'] = $post_type->name . 's'; 328 | } 329 | 330 | if ( isset( $post_type->graphql_singular_type ) ) { 331 | $names['singular_type'] = $post_type->graphql_singular_type; 332 | } else { 333 | // Yup some more. 334 | $names['singular_type'] = ucfirst( $post_type->name ); 335 | } 336 | 337 | if ( isset( $post_type->graphql_plural_type ) ) { 338 | $names['plural_type'] = $post_type->graphql_plural_type; 339 | } else { 340 | // Yup some more bad code. 341 | $names['plural_type'] = ucfirst( $post_type->name . 's' ); 342 | } 343 | 344 | return $names; 345 | } 346 | 347 | /** 348 | * Returns a formatted set of names for the post types. 349 | * 350 | * This data is passed into the $wp_config. 351 | * 352 | * @param array $post_types List of post type objects. 353 | */ 354 | function graphql_build_post_types( $post_types = array() ) { 355 | if ( empty( $post_types ) ) { 356 | $post_types = graphql_get_post_types(); 357 | } 358 | 359 | if ( ! empty( $post_types ) ) { 360 | if ( is_callable( 'graphql_build_post_type' ) ) { 361 | return array_map( 'graphql_build_post_type', $post_types ); 362 | } 363 | 364 | if ( is_callable( '\BEForever\WPGraphQL\graphql_build_post_type' ) ) { 365 | return array_map( '\BEForever\WPGraphQL\graphql_build_post_type', $post_types ); 366 | } 367 | } 368 | 369 | return array(); 370 | } 371 | 372 | /** 373 | * Returns configuration data for the type system. 374 | * 375 | * This should be used as a way to pass state based information about WordPress 376 | * into WP GraphQL. This is useful for programmatically generating types. 377 | * 378 | * @param array $wp_config Configuration data for the WordPress type system. 379 | */ 380 | function graphql_build_wp_config() { 381 | $wp_config = array(); 382 | $post_types = graphql_get_post_types(); 383 | 384 | $wp_config['post_types'] = graphql_build_post_types( $post_types ); 385 | 386 | return $wp_config; 387 | } 388 | 389 | /** 390 | * Does a GraphQL request. 391 | * 392 | * @return mixed The response data. 393 | */ 394 | function serve_graphql_request() { 395 | if ( ! empty( $_GET['debug'] ) ) { 396 | /** 397 | * Enable additional validation of type configs 398 | * (disabled by default because it is costly) 399 | */ 400 | Config::enableValidation(); 401 | 402 | /** 403 | * Catch custom errors ( to report them in query results if debugging is enabled ) 404 | */ 405 | $php_errors = []; 406 | set_error_handler( function( $severity, $message, $file, $line ) use ( &$php_errors ) { 407 | $php_errors[] = new ErrorException( $message, 0, $severity, $file, $line ); 408 | } ); 409 | } 410 | 411 | try { 412 | $wp_config = graphql_build_wp_config(); 413 | 414 | // Build the complete type system. 415 | $type_system = new TypeSystem( $wp_config ); 416 | 417 | // Build request context that will be available in all field resolvers (as 3rd argument). 418 | $app_context = new AppContext(); 419 | 420 | // Set currently authenticated user to be the viewer in our context. 421 | $app_context->viewer = wp_get_current_user(); 422 | 423 | $app_context->root_url = 'http://local.wordpress.dev/graphql'; 424 | $app_context->request = $_REQUEST; 425 | 426 | // Parse incoming query and variables. 427 | if ( isset( $_SERVER['CONTENT_TYPE'] ) && false !== strpos( $_SERVER['CONTENT_TYPE'], 'application/json' ) ) { 428 | $raw = file_get_contents( 'php://input' ) ?: ''; 429 | $data = json_decode( $raw, true ); 430 | } else { 431 | $data = $_REQUEST; 432 | } 433 | 434 | // Add query data and variable defaults. 435 | $data += [ 'query' => null, 'variables' => null ]; 436 | 437 | // If an empty query is present for now display the hello message. 438 | if ( null === $data['query'] ) { 439 | $data['query'] = ' 440 | {hello} 441 | '; 442 | } 443 | 444 | // Build GraphQL schema out of the query object type. 445 | $schema = new Schema([ 446 | 'query' => $type_system->query(), 447 | ]); 448 | 449 | // Execute the query. 450 | $result = GraphQL::execute( 451 | $schema, 452 | $data['query'], 453 | null, 454 | $app_context, 455 | (array) $data['variables'], 456 | null 457 | ); 458 | 459 | // Add any reported PHP errors to result. 460 | if ( ! empty( $_GET['debug'] ) && ! empty( $php_errors ) ) { 461 | $result['extensions']['phpErrors'] = array_map( 462 | [ 'GraphQL\Error\FormattedError', 'createFromPHPError' ], 463 | $php_errors 464 | ); 465 | } 466 | 467 | $http_status = 200; 468 | } catch ( \Exception $error ) { 469 | $http_status = 500; 470 | if ( ! empty( $_GET['debug'] ) ) { 471 | $result['extensions']['exception'] = FormattedError::createFromException( $error ); 472 | } else { 473 | $result['errors'] = [ FormattedError::create( 'Unexpected Error' ) ]; 474 | } 475 | } 476 | 477 | return $result; 478 | } 479 | --------------------------------------------------------------------------------