├── tests ├── data │ ├── 100x200.png │ ├── 100x300.png │ ├── 100x400.png │ ├── 100x500.png │ └── 250x250.png ├── stubs │ ├── API_Test_v2.php │ └── API_Test_v1.php ├── v1 │ ├── test-APIv1.php │ └── controllers │ │ ├── test-RewriteRules.php │ │ ├── test-Taxonomies.php │ │ ├── test-Users.php │ │ ├── test-Terms.php │ │ ├── test-Comments.php │ │ └── test-Posts.php ├── APITestCase.php ├── bootstrap.php ├── README.md └── test-API_Base.php ├── .gitignore ├── Capfile ├── Gruntfile.js ├── api ├── v1 │ ├── models │ │ ├── Users.php │ │ ├── Taxonomies.php │ │ ├── Terms.php │ │ ├── Posts.php │ │ └── Comments.php │ ├── controllers │ │ ├── RewriteRules.php │ │ ├── Taxonomies.php │ │ ├── Terms.php │ │ ├── Users.php │ │ ├── Comments.php │ │ └── Posts.php │ └── API.php └── API_Base.php ├── package.json ├── config └── deploy │ └── production.rb ├── .travis.yml ├── phpunit.xml ├── composer.json ├── thermal-api.php ├── dispatcher.php ├── lib └── jsonp │ ├── jsonp.php │ └── jsonpTest.php ├── bin └── install-wp-tests.sh ├── readme.txt ├── composer.lock └── README.md /tests/data/100x200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voceconnect/thermal-api/HEAD/tests/data/100x200.png -------------------------------------------------------------------------------- /tests/data/100x300.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voceconnect/thermal-api/HEAD/tests/data/100x300.png -------------------------------------------------------------------------------- /tests/data/100x400.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voceconnect/thermal-api/HEAD/tests/data/100x400.png -------------------------------------------------------------------------------- /tests/data/100x500.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voceconnect/thermal-api/HEAD/tests/data/100x500.png -------------------------------------------------------------------------------- /tests/data/250x250.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/voceconnect/thermal-api/HEAD/tests/data/250x250.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # config files 2 | .DS_Store 3 | tests/wp-tests-config.php 4 | vendor/ 5 | DOCS.md 6 | node_modules/ -------------------------------------------------------------------------------- /tests/stubs/API_Test_v2.php: -------------------------------------------------------------------------------- 1 | assertTrue( true ); 7 | } 8 | 9 | } -------------------------------------------------------------------------------- /tests/stubs/API_Test_v1.php: -------------------------------------------------------------------------------- 1 | results) { 9 | $found = $wp_users->total_users; 10 | return $wp_users->results; 11 | } 12 | 13 | return array(); 14 | 15 | } 16 | 17 | public function findById($id) { 18 | return get_user_by( 'id', $id ); 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "thermal-api", 3 | "description": "Thermal is the WordPress plugin that gives you the power of WP_Query in a RESTful API.", 4 | "version": "0.13.4", 5 | "repository": { 6 | "type": "git", 7 | "url": "git@github.com:voceconnect/thermal-api.git" 8 | }, 9 | "bugs": { 10 | "url": "https://github.com/voceconnect/thermal-api/issues" 11 | }, 12 | "devDependencies": { 13 | "grunt-voce-plugins": "latest" 14 | } 15 | } -------------------------------------------------------------------------------- /tests/APITestCase.php: -------------------------------------------------------------------------------- 1 | call(); 14 | return $app->response()->finalize(); 15 | } 16 | 17 | public function setUp() { 18 | parent::setUp(); 19 | 20 | \Slim\Slim::registerAutoloader(); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | tests 12 | tests/v1/controllers 13 | 14 | 15 | 16 | 17 | ./api 18 | 19 | 20 | ./vendor 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "voceconnect/thermal-api", 3 | "type": "wordpress-plugin", 4 | "description": "A JSON API that provides access to the data within WordPress", 5 | "license": "GPL-2.0+", 6 | "authors": [ 7 | { 8 | "name" : "Michael Pretty", 9 | "email" : "mpretty@voceconnect.com", 10 | "role" : "lead" 11 | }, 12 | { 13 | "name" : "Kevin Langley" 14 | }, 15 | { 16 | "name" : "Jeff Stieler" 17 | }, 18 | { 19 | "name" : "Mark Parolisi" 20 | }, 21 | { 22 | "name" : "John Ciacia" 23 | }, 24 | { 25 | "name" : "Gary Smirny" 26 | } 27 | ], 28 | "require": { 29 | "php": ">=5.3.0", 30 | "slim/slim": "2.2.0" 31 | }, 32 | "require-dev": { 33 | "phpunit/phpunit": "~3.7.0" 34 | } 35 | } -------------------------------------------------------------------------------- /api/v1/controllers/RewriteRules.php: -------------------------------------------------------------------------------- 1 | wp_rewrite_rules(); 13 | if( is_array( $rules )) { 14 | foreach ( $rules as $regex => $query ) { 15 | $patterns = array( '|index\.php\?&?|', '|\$matches\[(\d+)\]|' ); 16 | $replacements = array( '', '\$$1' ); 17 | 18 | $rewrite_rules[] = array( 19 | 'regex' => $regex, 20 | 'query_expression' => preg_replace( $patterns, $replacements, $query ), 21 | ); 22 | } 23 | } else { 24 | $app->halt('404', 'Rewrite Rules are not setup on this site.'); 25 | } 26 | 27 | return compact( 'base_url', 'rewrite_rules' ); 28 | } 29 | 30 | } 31 | 32 | ?> 33 | -------------------------------------------------------------------------------- /thermal-api.php: -------------------------------------------------------------------------------- 1 | =' ) ) { 23 | @include(__DIR__ . '/vendor/autoload.php'); 24 | require(__DIR__ . '/dispatcher.php'); 25 | new Voce\Thermal\API_Dispatcher(); 26 | } 27 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | # Thermal API Unit Tests 2 | 3 | ## Overview 4 | 5 | The test suite for Thermal API is built using the `WP_UnitTestCase` class created for testing core WordPress functionality. 6 | 7 | You can read more about unit testing WordPress here: [http://make.wordpress.org/core/handbook/automated-testing/](http://make.wordpress.org/core/handbook/automated-testing/) 8 | 9 | ## Configuration 10 | 11 | Since we need to spin up a temporary WordPress installation to test against, configuring the test suite is much like configuring a new WordPress site. 12 | 13 | Set up a **fresh database** just for running these tests. **All data in the test suite database will be destroyed.** 14 | 15 | Rename the `wp-tests-config-sample.php` file to `wp-tests-config.php` and enter the appropriate database information. 16 | 17 | ## Running the Tests 18 | 19 | From the plugin root: 20 | 21 | $ phpunit tests -------------------------------------------------------------------------------- /api/v1/models/Taxonomies.php: -------------------------------------------------------------------------------- 1 | name, $in ); 14 | } ); 15 | } 16 | 17 | if ( !empty( $args['post_type'] ) ) { 18 | $post_types = $args['post_type']; 19 | $taxonomies = array_filter( $taxonomies, function($taxonomy) use ($post_types) { 20 | foreach ( $post_types as $post_type ) { 21 | foreach ( $taxonomy->object_type as $object_type ) { 22 | if ( $object_type == $post_type || 0 === strpos( $object_type, $post_type . ':' ) ) { 23 | return true; 24 | } 25 | } 26 | } 27 | return false; 28 | } ); 29 | } 30 | 31 | return array_values($taxonomies); 32 | } 33 | 34 | public function findById( $name ) { 35 | return get_taxonomy( $name ); 36 | } 37 | 38 | 39 | } -------------------------------------------------------------------------------- /api/v1/models/Terms.php: -------------------------------------------------------------------------------- 1 | \Voce\Thermal\v1\MAX_TERMS_PER_PAGE ) { 11 | $number = \Voce\Thermal\v1\MAX_TERMS_PER_PAGE; 12 | } else { 13 | $number = absint( $args['per_page'] ); 14 | } 15 | if ( isset( $args['offset'] ) ) { 16 | $offset = $args['offset']; 17 | } elseif ( isset( $args['paged'] ) ) { 18 | $offset = ( absint( $args['paged'] ) - 1) * $number; 19 | } else { 20 | $offset = 0; 21 | } 22 | 23 | $term_args = array_merge( $args, array( 24 | 'offset' => $offset, 25 | 'number' => $number 26 | ) ); 27 | 28 | $terms = get_terms( $taxonomy, $term_args ); 29 | 30 | if ( !empty( $args['include_found'] ) ) { 31 | $found = get_terms( $taxonomy, array_merge( $args, array( 32 | 'fields' => 'count' 33 | ) ) ); 34 | } 35 | return array_values( $terms ); 36 | } 37 | 38 | public function findById( $taxonomy, $id ) { 39 | return get_term( $id, $taxonomy ); 40 | } 41 | 42 | } -------------------------------------------------------------------------------- /tests/v1/controllers/test-RewriteRules.php: -------------------------------------------------------------------------------- 1 | set_permalink_structure( '/%year%/%monthnum%/%day%/%postname%/' ); 8 | $wp_rewrite->flush_rules(); 9 | 10 | list($status, $headers, $body) = $this->_getResponse( array( 11 | 'REQUEST_METHOD' => 'GET', 12 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/rewrite_rules/', 13 | 'QUERY_STRING' => '', 14 | ) ); 15 | 16 | $data = json_decode( $body ); 17 | 18 | $this->assertEquals( '200', $status ); 19 | $this->assertObjectHasAttribute( 'base_url', $data ); 20 | $this->assertEquals( 'http://' . trim( WP_TESTS_DOMAIN, '/' ) . '/', $data->base_url ); 21 | $this->assertObjectHasAttribute( 'rewrite_rules', $data ); 22 | $this->assertInternalType( 'array', $data->rewrite_rules ); 23 | } 24 | 25 | public function testGetRewriteRulesNotSet() { 26 | global $wp_rewrite; 27 | $wp_rewrite->set_permalink_structure( '' ); 28 | $wp_rewrite->flush_rules(); 29 | 30 | list($status, $headers, $body) = $this->_getResponse( array( 31 | 'REQUEST_METHOD' => 'GET', 32 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/rewrite_rules/', 33 | 'QUERY_STRING' => '', 34 | ) ); 35 | 36 | $data = json_decode( $body ); 37 | $this->assertEquals( '404', $status ); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /dispatcher.php: -------------------------------------------------------------------------------- 1 | run(); 58 | 59 | exit; 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /lib/jsonp/jsonp.php: -------------------------------------------------------------------------------- 1 | found_posts; 33 | 34 | if ( $wp_posts->have_posts() ) { 35 | return $wp_posts->posts; 36 | } 37 | return array(); 38 | 39 | } 40 | 41 | public function findById($id) { 42 | return get_post($id); 43 | } 44 | 45 | public function _filter_posts_where_handleDateRange( $where, $wp_query ) { 46 | if ( ($before = $wp_query->get( 'before' ) ) && $beforets = strtotime( $before ) ) { 47 | if ( preg_match( '$:[0-9]{2}\s[+-][0-9]{2}$', $before ) || strpos( $before, 'GMT' ) !== false ) { 48 | //adjust to site time if a timezone was set in the timestamp 49 | $beforets += ( get_option( 'gmt_offset' ) * HOUR_IN_SECONDS ); 50 | } 51 | 52 | $where .= sprintf( " AND post_date < '%s'", gmdate( 'Y-m-d H:i:s', $beforets ) ); 53 | } 54 | if ( ($after = $wp_query->get( 'after' ) ) && $afterts = strtotime( $after ) ) { 55 | if ( preg_match( '$:[0-9]{2}\s[+-][0-9]{2}$', $after ) || strpos( $after, 'GMT' ) !== false ) { 56 | //adjust to site time if a timezone was set in the timestamp 57 | $afterts += ( get_option( 'gmt_offset' ) * HOUR_IN_SECONDS ); 58 | } 59 | 60 | $where .= sprintf( " AND post_date > '%s'", gmdate( 'Y-m-d H:i:s', $afterts ) ); 61 | } 62 | remove_filter('posts_search', array($this, __METHOD__)); 63 | return $where; 64 | } 65 | 66 | } -------------------------------------------------------------------------------- /lib/jsonp/jsonpTest.php: -------------------------------------------------------------------------------- 1 | good_callback = 'jQuery19103165583838708699_1367588311424'; 14 | $this->bad_callback = 'alert%28document.cookie%29%3Bfoo'; 15 | $this->reserved_callback = 'switch'; 16 | } 17 | 18 | public function testIsUTF8() { 19 | $this->assertTrue( \Voce\JSONP::is_utf8($this->good_callback)); 20 | } 21 | 22 | 23 | public function testHasReservedWord() { 24 | $this->assertTrue( \Voce\JSONP::has_reserved_word($this->reserved_callback)); 25 | } 26 | 27 | public function testHasReservedWordFalse() { 28 | $this->assertFalse( \Voce\JSONP::has_reserved_word($this->good_callback)); 29 | } 30 | 31 | public function testHasValidSyntax() { 32 | $this->assertTrue( \Voce\JSONP::has_valid_syntax($this->good_callback)); 33 | } 34 | 35 | public function testHasValidSyntaxDotNotation() { 36 | $this->assertTrue( \Voce\JSONP::has_valid_syntax('Foo.bar')); 37 | } 38 | 39 | public function testHasValidSyntaxjQuery(){ 40 | $this->assertTrue( \Voce\JSONP::has_valid_syntax('$.ajaxHandler')); 41 | } 42 | 43 | public function testHasValidSyntaxSquareBrackets(){ 44 | $this->assertTrue( \Voce\JSONP::has_valid_syntax('someClass["callback"]')); 45 | } 46 | 47 | public function testHasValidSyntaxSquareBracketsWithoutParens(){ 48 | $this->assertTrue( \Voce\JSONP::has_valid_syntax('someClass[callback]')); 49 | } 50 | 51 | public function testHasValidSyntaxDotNotationSquareBrackets(){ 52 | $this->assertTrue( \Voce\JSONP::has_valid_syntax('someClass.callbackList["callback"]')); 53 | } 54 | 55 | public function testHasValidSyntaxFalse() { 56 | $this->assertFalse( \Voce\JSONP::has_valid_syntax('foo()')); 57 | } 58 | 59 | public function testIsValidCallback(){ 60 | $this->assertTrue( \Voce\JSONP::is_valid_callback($this->good_callback)); 61 | } 62 | 63 | public function testIsValidCallbackFalse(){ 64 | $this->assertFalse( \Voce\JSONP::is_valid_callback($this->bad_callback)); 65 | } 66 | 67 | public function testIsValidCallbackKeywordFalse(){ 68 | $this->assertFalse( \Voce\JSONP::is_valid_callback('abstract')); 69 | } 70 | 71 | public function testIsValidCallbackSyntaxFalse(){ 72 | $this->assertFalse( \Voce\JSONP::is_valid_callback('foo()')); 73 | } 74 | } -------------------------------------------------------------------------------- /api/API_Base.php: -------------------------------------------------------------------------------- 1 | app = $app; 27 | 28 | $this->app->notFound( function () use ( $app ) { 29 | $data = array( 30 | 'error' => array( 31 | 'message' => 'Invalid route' 32 | ), 33 | ); 34 | $app->contentType( 'application/json' ); 35 | $app->halt( 400, json_encode( $data ) ); 36 | } ); 37 | } 38 | 39 | /** 40 | * Registers route 41 | * 42 | * @param string $method HTTP method 43 | * @param string $pattern The path pattern to match 44 | * @param callback $callback Callback function for route 45 | * @return \Slim\Route 46 | */ 47 | public function registerRoute( $method, $pattern, $callback ) { 48 | $method = strtolower( $method ); 49 | 50 | $valid_methods = array( 51 | 'get', 52 | 'post', 53 | 'delete', 54 | 'put', 55 | 'head', 56 | 'options', 57 | ); 58 | 59 | if ( !in_array( $method, $valid_methods ) ) { 60 | return false; 61 | } 62 | 63 | $match = get_api_base() . trailingslashit( 'v' . $this->version ) . $pattern; 64 | 65 | $app = $this->app; 66 | 67 | return $this->app->$method( $match, function() use ( $app, $callback ) { 68 | $args = func_get_args(); 69 | array_unshift( $args, $app ); 70 | 71 | $res = $app->response(); 72 | 73 | $res->header( 'Access-Control-Allow-Origin', '*' ); 74 | if ( isset( $_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'] ) ) { 75 | $res->header( 'Access-Control-Allow-Headers', $_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'] ); 76 | } 77 | 78 | if ( ( $json_p = $app->request()->get( 'callback' ) ) && ( \Voce\JSONP::is_valid_callback( $json_p ) ) ) { 79 | $app->contentType( 'application/javascript; charset=utf-8;' ); 80 | 81 | $res->write( $json_p . '(' ); 82 | } else { 83 | $app->contentType( 'application/json; charset=utf-8;' ); 84 | $json_p = false; 85 | } 86 | 87 | $data = call_user_func_array( $callback, $args ); 88 | 89 | if ( !is_null( $data ) ) { 90 | $json = json_encode( $data ); 91 | $res->write( $json ); 92 | } 93 | 94 | if ( $json_p ) { 95 | $res->write( ')' ); 96 | } 97 | 98 | do_action_ref_array( 'thermal_response', array( &$res, &$app, &$data ) ); 99 | } ); 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /bin/install-wp-tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ $# -lt 3 ]; then 4 | echo "usage: $0 [db-host] [wp-version]" 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-master} 13 | 14 | WP_TESTS_DIR=${WP_TESTS_DIR-/tmp/wordpress-tests-lib} 15 | WP_CORE_DIR=/tmp/wordpress/ 16 | 17 | set -ex 18 | 19 | install_dependencies() { 20 | composer install 21 | } 22 | 23 | install_wp() { 24 | mkdir -p $WP_CORE_DIR 25 | 26 | if [ $WP_VERSION == 'latest' ]; then 27 | local ARCHIVE_NAME='latest' 28 | else 29 | local ARCHIVE_NAME="wordpress-$WP_VERSION" 30 | fi 31 | 32 | wget -nv -O /tmp/wordpress.tar.gz http://wordpress.org/${ARCHIVE_NAME}.tar.gz 33 | tar --strip-components=1 -zxmf /tmp/wordpress.tar.gz -C $WP_CORE_DIR 34 | 35 | wget -nv -O $WP_CORE_DIR/wp-content/db.php https://raw.github.com/markoheijnen/wp-mysqli/master/db.php 36 | } 37 | 38 | install_test_suite() { 39 | # portable in-place argument for both GNU sed and Mac OSX sed 40 | if [[ $(uname -s) == 'Darwin' ]]; then 41 | local ioption='-i ""' 42 | else 43 | local ioption='-i' 44 | fi 45 | 46 | # set up testing suite 47 | mkdir -p $WP_TESTS_DIR 48 | cd $WP_TESTS_DIR 49 | svn co --quiet http://develop.svn.wordpress.org/trunk/tests/phpunit/includes/ 50 | 51 | wget -nv -O wp-tests-config.php http://develop.svn.wordpress.org/trunk/wp-tests-config-sample.php 52 | sed $ioption "s:dirname( __FILE__ ) . '/src/':'$WP_CORE_DIR':" wp-tests-config.php 53 | sed $ioption "s/youremptytestdbnamehere/$DB_NAME/" wp-tests-config.php 54 | sed $ioption "s/yourusernamehere/$DB_USER/" wp-tests-config.php 55 | sed $ioption "s/yourpasswordhere/$DB_PASS/" wp-tests-config.php 56 | sed $ioption "s|localhost|${DB_HOST}|" wp-tests-config.php 57 | } 58 | 59 | install_db() { 60 | # parse DB_HOST for port or socket references 61 | local PARTS=(${DB_HOST//\:/ }) 62 | local DB_HOSTNAME=${PARTS[0]}; 63 | local DB_SOCK_OR_PORT=${PARTS[1]}; 64 | local EXTRA="" 65 | 66 | if ! [ -z $DB_HOSTNAME ] ; then 67 | if [[ "$DB_SOCK_OR_PORT" =~ ^[0-9]+$ ]] ; then 68 | EXTRA=" --host=$DB_HOSTNAME --port=$DB_SOCK_OR_PORT --protocol=tcp" 69 | elif ! [ -z $DB_SOCK_OR_PORT ] ; then 70 | EXTRA=" --socket=$DB_SOCK_OR_PORT" 71 | elif ! [ -z $DB_HOSTNAME ] ; then 72 | EXTRA=" --host=$DB_HOSTNAME --protocol=tcp" 73 | fi 74 | fi 75 | 76 | # create database 77 | mysqladmin create $DB_NAME --user="$DB_USER" --password="$DB_PASS"$EXTRA 78 | } 79 | 80 | install_dependencies 81 | install_wp 82 | install_test_suite 83 | install_db 84 | -------------------------------------------------------------------------------- /api/v1/controllers/Taxonomies.php: -------------------------------------------------------------------------------- 1 | request()->get(); 18 | 19 | $args = self::convert_request( $request_args ); 20 | 21 | $model = self::model(); 22 | 23 | $taxonomies = $model->find( $args ); 24 | 25 | $taxonomies = array_filter( $taxonomies, function($taxonomy) { 26 | if ( !$taxonomy->public ) { 27 | if ( is_user_logged_in() || !current_user_can( $taxonomy->cap->manage_terms ) ) { 28 | return false; 29 | } 30 | } 31 | return true; 32 | } ); 33 | 34 | array_walk( $taxonomies, array( __CLASS__, 'format' ), 'read' ); 35 | $taxonomies = array_values($taxonomies); 36 | return compact( 'taxonomies' ); 37 | } 38 | 39 | public static function findById( $app, $id ) { 40 | $taxonomy = self::model()->findById( $id ); 41 | if ( !$taxonomy ) { 42 | $app->halt( '404', get_status_header_desc( '404' ) ); 43 | } 44 | 45 | if ( !$taxonomy->public ) { 46 | if ( is_user_logged_in() ) { 47 | if ( !current_user_can( $taxonomy->cap->manage_terms, $taxonomy->ID ) ) { 48 | $app->halt( '403', get_status_header_desc( '403' ) ); 49 | } 50 | } else { 51 | $app->halt( '401', get_status_header_desc( '401' ) ); 52 | } 53 | } 54 | 55 | self::format( $taxonomy, 'read' ); 56 | return $taxonomy; 57 | } 58 | 59 | /** 60 | * Filter and validate the parameters that will be passed to the model. 61 | * @param array $request_args 62 | * @return array 63 | */ 64 | protected static function convert_request( $request_args ) { 65 | // Remove any args that are not allowed by the API 66 | $request_filters = array( 67 | 'in' => array( '\\Voce\\Thermal\\v1\\toArray' ), 68 | 'post_type' => array( '\\Voce\\Thermal\\v1\\toArray' ), 69 | ); 70 | //strip any nonsafe args 71 | $request_args = array_intersect_key( $request_args, $request_filters ); 72 | 73 | //run through basic sanitation 74 | foreach ( $request_args as $key => $value ) { 75 | foreach ( $request_filters[$key] as $callback ) { 76 | $value = call_user_func( $callback, $value ); 77 | } 78 | $request_args[$key] = $value; 79 | } 80 | 81 | return $request_args; 82 | } 83 | 84 | /** 85 | * 86 | * @param \WP_Post $taxonomy 87 | */ 88 | public static function format( &$taxonomy, $state = 'read' ) { 89 | if ( !$taxonomy ) { 90 | return $taxonomy = null; 91 | } 92 | 93 | $data = array( 94 | 'name' => $taxonomy->name, 95 | 'post_types' => $taxonomy->object_type, 96 | 'hierarchical' => $taxonomy->hierarchical, 97 | 'queryVar' => $taxonomy->query_var, 98 | 'labels' => $taxonomy->labels, 99 | 'meta' => new \stdClass() 100 | ); 101 | 102 | $taxonomy = apply_filters_ref_array( 'thermal_taxonomy_entity', array( ( object ) $data, &$taxonomy, $state ) ); 103 | } 104 | 105 | } 106 | 107 | ?> 108 | -------------------------------------------------------------------------------- /api/v1/models/Comments.php: -------------------------------------------------------------------------------- 1 | query( $args ); 51 | 52 | if ( !empty( $args['include_found'] ) ) { 53 | $args['count'] = true; 54 | //@todo - counts don't cache in core 55 | $found = $wp_comments->query( $args ); 56 | } 57 | 58 | return $comments; 59 | } 60 | 61 | public function findById( $id ) { 62 | return get_comment( $id ); 63 | } 64 | 65 | public static function _filter_comments_clauses_handleDateRange( $clauses, &$comment_query ) { 66 | $query_vars = $comment_query->query_vars; 67 | 68 | if ( isset( $query_vars['before'] ) && ($before = $query_vars['before']) && ($beforets = strtotime( $before ) ) ) { 69 | if ( preg_match( '$:[0-9]{2}\s[+-][0-9]{2}$', $before ) || strpos( $before, 'GMT' ) !== false ) { 70 | //adjust to site time if a timezone was set in the timestamp 71 | $beforets += ( get_option( 'gmt_offset' ) * HOUR_IN_SECONDS ); 72 | } 73 | $clauses['where'] .= sprintf( " AND comment_date < '%s'", gmdate( 'Y-m-d H:i:s', $beforets ) ); 74 | } 75 | if ( isset( $query_vars['after'] ) && ($after = $query_vars['after']) && ($afterts = strtotime( $after )) ) { 76 | if ( preg_match( '$:[0-9]{2}\s[+-][0-9]{2}$', $after ) || strpos( $after, 'GMT' ) !== false ) { 77 | //adjust to site time if a timezone was set in the timestamp 78 | $afterts += ( get_option( 'gmt_offset' ) * HOUR_IN_SECONDS ); 79 | } 80 | 81 | $clauses['where'] .= sprintf( " AND comment_date > '%s'", gmdate( 'Y-m-d H:i:s', $afterts ) ); 82 | } 83 | 84 | return $clauses; 85 | } 86 | 87 | public static function _filter_comments_clauses_handleInArg( $clauses, &$comment_query ) { 88 | $query_vars = $comment_query->query_vars; 89 | if ( !empty( $query_vars['in'] ) ) { 90 | $ids = ( array ) $query_vars['in']; 91 | $ids = array_map( '\\intval', $ids ); 92 | $clauses['where'] .= ' AND comment_ID in (' . implode( ', ', $ids ) . ') '; 93 | } 94 | return $clauses; 95 | } 96 | 97 | } -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | === Thermal API === 2 | Contributors: voceplatforms 3 | Tags: thermal, JSON, API 4 | Requires at least: 3.6 5 | Tested up to: 4.1 6 | Stable tag: 0.13.4 7 | License: GPLv2 or later 8 | License URI: http://www.gnu.org/licenses/gpl-2.0.html 9 | 10 | Thermal is the WordPress plugin that gives you the power of WP_Query in a RESTful API. 11 | 12 | == Description == 13 | Thermal is the WordPress plugin that gives you the power of WP_Query in a RESTful API. Thermal supports client-based decisions that when combined with a responsive design framework, allow for a truly responsive application leveraging a WordPress content source. 14 | 15 | **Minimum Requirements** 16 | 17 | * PHP >= 5.3.0 18 | 19 | == Installation == 20 | 21 | 1. Upload the `thermal-api` directory to your `/wp-content/plugins/` directory 22 | 1. Activate the plugin through the 'Plugins' menu in WordPress 23 | 1. Optional: Define `Voce\Thermal\API_BASE` in `wp-config.php` to change the API root url (defaults to `[SITE URL]/wp_api/[API VERSION]`). 24 | 25 | == Frequently Asked Questions == 26 | 27 | = Is there a github repo? I love me some submodules! = 28 | 29 | Yes. https://github.com/voceconnect/thermal-api 30 | 31 | == Changelog == 32 | = 0.13.4 = 33 | * Testing with WordPress 4.1 34 | * Fixing issue with null avatar 35 | 36 | = 0.13.3 = 37 | * Fixing bug with terms per_page arg 38 | 39 | = 0.13.2 = 40 | * Fixing issue with post galleries and adding tests to prevent issues with them again 41 | 42 | = 0.13.1 = 43 | * Adding bin and test directories as build directories to ignore on production 44 | 45 | = 0.13.0 = 46 | * Adding Capistrano deploy files 47 | 48 | = 0.12.0 = 49 | * Modified post_status handling for attachments post_type 50 | 51 | = 0.11.0 = 52 | * Added built in handling for last-modified header. 53 | 54 | = 0.10.1 = 55 | * Added nickname, last_name, first_name to user object. 56 | 57 | = 0.10.0 = 58 | * Converted phpunit structure to match suggested testing structure for WP plugins. 59 | * Added Travis support 60 | * Converted Slim inclusion from submodule to composer. 61 | * Switched from require/include inclusions to custom autoloader. 62 | 63 | = 0.9.0 = 64 | * Added support for 'post_status' query argument 65 | 66 | = 0.8.0 = 67 | * Made users publicly accessible 68 | * Added description to user's default meta 69 | * Added filter 'thermal_list_users_cap' to allow required cap to be set for viewing user listing 70 | * Fixed bug with how users were returned within index 71 | 72 | = 0.7.7 = 73 | * Added 'thermal_response' filter to allow modification of response object. 74 | 75 | = 0.7.6 = 76 | * Fixed undefined var within TaxonomiesController and changing how images are retreived, now using wp_get_attachment_image_src so the return value is filterable by other. 77 | 78 | = 0.7.5 = 79 | * Fixed missing media content for unattached featured images or images in content. 80 | 81 | = 0.7.4 = 82 | * Added entity filters for posts, users, taxonomies, comments, and terms. 83 | 84 | = 0.7.3 = 85 | * Fixed conversion of per_page argument for posts. 86 | 87 | = 0.7.2 = 88 | * Fixed references to MAX_X_PER_PAGE constants 89 | 90 | = 0.7.1 = 91 | * Fixed tests and implementation of media attachments on posts response. 92 | 93 | = 0.7.0 = 94 | * Added endpoints for comments. 95 | * Misc bug fixes. 96 | * Updated documentation to include JSONP handling. 97 | * Split endpoint handling into separate controllers/models. 98 | * Changed 'include' argumetn to 'in' for terms and users. 99 | 100 | = 0.6.1 = 101 | * Fixed error with missing namespace around dispatcher initialization. 102 | 103 | = 0.6.0 = 104 | * Improved handling of missing slashes around API_BASE constant. 105 | * Applying VOCE\Thermal namespace to API_BASE matching. 106 | * Introduced PHP version check before allowing activation. 107 | 108 | = 0.5.1 = 109 | * Initial public version of Thermal API. -------------------------------------------------------------------------------- /tests/test-API_Base.php: -------------------------------------------------------------------------------- 1 | registerRoute( 'get', 'abc', function() { 21 | 22 | } ); 23 | $this->assertInstanceOf( '\Slim\Route', $test ); 24 | $this->assertContains( 'GET', $test->getHttpMethods() ); 25 | 26 | $test2 = $apitest1->registerRoute( 'yum', 'abc', function() { 27 | 28 | } ); 29 | $this->assertFalse( $test2 ); 30 | } 31 | 32 | public function testAPIVersion() { 33 | $slim = new \Slim\Slim(); 34 | 35 | $apiTest = new API_Test_v1( $slim ); 36 | 37 | $test = $apiTest->registerRoute( 'get', 'abc', function() { 38 | 39 | } ); 40 | $this->assertEquals( '/' . Voce\Thermal\API_BASE . '/v1/abc', $test->getPattern() ); 41 | 42 | $apiTest = new API_Test_v2( $slim ); 43 | 44 | $test = $apiTest->registerRoute( 'get', 'abc', function() { 45 | 46 | } ); 47 | $this->assertEquals( '/' . Voce\Thermal\API_BASE . '/v2/abc', $test->getPattern() ); 48 | } 49 | 50 | public function testAccessControlHeader() { 51 | 52 | \Slim\Environment::mock( array( 53 | 'REQUEST_METHOD' => 'GET', 54 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/test', 55 | ) ); 56 | 57 | $slim = new \Slim\Slim(); 58 | $apiTest = new API_Test_v1( $slim ); 59 | 60 | $_SERVER['HTTP_ACCESS_CONTROL_REQUEST_HEADERS'] = 'test_header'; 61 | 62 | $apiTest->registerRoute( 'GET', 'test', function() { 63 | return ''; 64 | } ); 65 | 66 | ob_start(); 67 | $apiTest->app->run(); 68 | ob_end_clean(); 69 | 70 | $res = $apiTest->app->response(); 71 | $this->assertEquals( 'test_header', $res->header( 'access-control-allow-headers' ) ); 72 | } 73 | 74 | public function testAPIOutput() { 75 | 76 | \Slim\Environment::mock( array( 77 | 'REQUEST_METHOD' => 'GET', 78 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/test', 79 | ) ); 80 | $slim = new \Slim\Slim(); 81 | 82 | $apiTest = new API_Test_v1( $slim ); 83 | 84 | $testData = array( 'testKey' => Voce\Thermal\API_BASE . '/test' ); 85 | 86 | $apiTest->registerRoute( 'GET', 'test', function () use ( $testData ) { 87 | return $testData; 88 | } ); 89 | 90 | ob_start(); 91 | $apiTest->app->run(); 92 | ob_end_clean(); 93 | 94 | $res = $apiTest->app->response(); 95 | $this->assertEquals( json_encode( $testData ), $res->body() ); 96 | $this->assertEquals( 'application/json; charset=utf-8;', $res->header( 'Content-Type' ) ); 97 | } 98 | 99 | public function testJSONP() { 100 | 101 | \Slim\Environment::mock( array( 102 | 'REQUEST_METHOD' => 'GET', 103 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/test', 104 | 'QUERY_STRING' => 'callback=doStuff', 105 | ) ); 106 | 107 | $slim = new \Slim\Slim(); 108 | 109 | $apiTest = new API_Test_v1( $slim ); 110 | 111 | $apiTest->registerRoute( 'GET', 'test', function () { 112 | return 'test'; 113 | } ); 114 | 115 | ob_start(); 116 | $apiTest->app->run(); 117 | ob_end_clean(); 118 | 119 | $res = $apiTest->app->response(); 120 | $this->assertEquals( 'doStuff("test")', $res->body() ); 121 | $this->assertEquals( 'application/javascript; charset=utf-8;', $res->header( 'Content-Type' ) ); 122 | } 123 | 124 | public function testBadRoute() { 125 | \Slim\Environment::mock( array( 126 | 'REQUEST_METHOD' => 'GET', 127 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/foobar', 128 | ) ); 129 | $app = new \Slim\Slim(); 130 | 131 | $apiTest = new API_Test_v1( $app ); 132 | 133 | ob_start(); 134 | $apiTest->app->run(); 135 | ob_end_clean(); 136 | 137 | $response = $apiTest->app->response(); 138 | $this->assertObjectHasAttribute( 'error', json_decode( $response->body() ) ); 139 | } 140 | 141 | } -------------------------------------------------------------------------------- /api/v1/API.php: -------------------------------------------------------------------------------- 1 | registerRoute( 'GET', 'posts/?', array( __NAMESPACE__ . '\\controllers\\Posts', 'find' ) ); 35 | $this->registerRoute( 'GET', 'posts/:id/?', array( __NAMESPACE__ . '\\controllers\\Posts', 'findById' ) ); 36 | $this->registerRoute( 'GET', 'posts/:id/comments/?', array( __NAMESPACE__ . '\\controllers\\Comments', 'findByPost' ) ); 37 | $this->registerRoute( 'GET', 'users/?', array( __NAMESPACE__ . '\\controllers\\Users', 'find' ) ); 38 | $this->registerRoute( 'GET', 'users/:id/?', array( __NAMESPACE__ . '\\controllers\\Users', 'findById' ) ); 39 | $this->registerRoute( 'GET', 'taxonomies/?', array( __NAMESPACE__ . '\\controllers\\Taxonomies', 'find' ) ); 40 | $this->registerRoute( 'GET', 'taxonomies/:name/?', array( __NAMESPACE__ . '\\controllers\\Taxonomies', 'findById' ) ); 41 | $this->registerRoute( 'GET', 'taxonomies/:taxonomy_name/terms/?', array( __NAMESPACE__ . '\\controllers\\Terms', 'find' ) ); 42 | $this->registerRoute( 'GET', 'taxonomies/:taxonomy_name/terms/:term_id/?', array( __NAMESPACE__ . '\\controllers\\Terms', 'findById' ) ); 43 | $this->registerRoute( 'GET', 'rewrite_rules/?', array( __NAMESPACE__ . '\\controllers\\RewriteRules', 'find' ) ); 44 | $this->registerRoute( 'GET', 'comments/?', array( __NAMESPACE__ . '\\controllers\\Comments', 'find' ) ); 45 | $this->registerRoute( 'GET', 'comments/:id?', array( __NAMESPACE__ . '\\controllers\\Comments', 'findById' ) ); 46 | 47 | if ( function_exists( 'wp_using_ext_object_cache' ) && wp_using_ext_object_cache() ) { 48 | //add filters for last-modified based off of the 'last_changed' caching in core 49 | $filter_last_post_mod = function($last_modified) { 50 | if( $last_changed = wp_cache_get( 'last_changed', 'posts' ) ) { 51 | list( $micro, $time ) = explode( ' ', $last_changed ); 52 | $last_modified = gmdate( 'Y-m-d H:i:s', $time ); 53 | 54 | } 55 | return $last_modified; 56 | }; 57 | add_filter('thermal_get_lastpostmodified', $filter_last_post_mod); 58 | 59 | $filter_last_comment_mod = function( $last_modified ) { 60 | if( $last_changed = wp_cache_get( 'last_changed', 'comments' ) ) { 61 | list( $micro, $time ) = explode( ' ', $last_changed ); 62 | $last_modified = gmdate( 'Y-m-d H:i:s', $time ); 63 | 64 | } 65 | return $last_modified; 66 | }; 67 | 68 | add_filter('thermal_get_lastcommentmodified', $filter_last_comment_mod); 69 | add_filter('thermal_comment_last_modified', $filter_last_comment_mod); 70 | 71 | $filter_last_term_mod = function( $last_modified ) { 72 | if( $last_changed = wp_cache_get( 'last_changed', 'terms' ) ) { 73 | list( $micro, $time ) = explode( ' ', $last_changed ); 74 | $last_modified = gmdate( 'Y-m-d H:i:s', $time ); 75 | } 76 | return $last_modified; 77 | }; 78 | 79 | add_filter('thermal_get_lasttermmodified', $filter_last_term_mod); 80 | add_filter('thermal_term_last_modified', $filter_last_term_mod); 81 | 82 | } 83 | } 84 | 85 | } 86 | 87 | function toBool( $value ) { 88 | return ( bool ) $value; 89 | } 90 | 91 | function toArray( $value ) { 92 | return ( array ) $value; 93 | } 94 | 95 | function applyInt( $value ) { 96 | return array_map( 'intval', $value ); 97 | } 98 | 99 | function toCommaSeparated( $value ) { 100 | return implode( ',', $value ); 101 | } 102 | -------------------------------------------------------------------------------- /tests/v1/controllers/test-Taxonomies.php: -------------------------------------------------------------------------------- 1 | true, 8 | ) ); 9 | 10 | register_taxonomy( 'public_taxonomy_b', array( 'page' ), array( 11 | 'public' => true, 12 | ) ); 13 | 14 | register_taxonomy( 'private_taxonomy_a', array( 'post' ), array( 15 | 'public' => false, 16 | ) ); 17 | } 18 | 19 | public function testGetTaxonomies() { 20 | $this->_registerTaxonomies(); 21 | 22 | list($status, $headers, $body) = $this->_getResponse( array( 23 | 'REQUEST_METHOD' => 'GET', 24 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/taxonomies/', 25 | 'QUERY_STRING' => '', 26 | ) ); 27 | 28 | $data = json_decode( $body ); 29 | $this->assertEquals( '200', $status ); 30 | $this->assertObjectHasAttribute( 'taxonomies', $data ); 31 | $this->assertInternalType( 'array', $data->taxonomies ); 32 | } 33 | 34 | public function testGetTaxonomiesByIds() { 35 | $this->_registerTaxonomies(); 36 | 37 | list($status, $headers, $body) = $this->_getResponse( array( 38 | 'REQUEST_METHOD' => 'GET', 39 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/taxonomies/', 40 | 'QUERY_STRING' => http_build_query( array( 'in' => array( 'public_taxonomy_a', 'public_taxonomy_b' ) ) ), 41 | ) ); 42 | 43 | $data = json_decode( $body ); 44 | 45 | $this->assertEquals( '200', $status ); 46 | $this->assertObjectHasAttribute( 'taxonomies', $data ); 47 | $this->assertCount( 2, $data->taxonomies ); 48 | } 49 | 50 | public function testGetTaxonomiesExcludePrivate() { 51 | $this->_registerTaxonomies(); 52 | 53 | list($status, $headers, $body) = $this->_getResponse( array( 54 | 'REQUEST_METHOD' => 'GET', 55 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/taxonomies/', 56 | 'QUERY_STRING' => http_build_query( array( 'in' => array( 'public_taxonomy_a', 'public_taxonomy_b', 'private_taxonomy_a' ) ) ), 57 | ) ); 58 | 59 | $data = json_decode( $body ); 60 | 61 | $this->assertEquals( '200', $status ); 62 | $this->assertObjectHasAttribute( 'taxonomies', $data ); 63 | $this->assertCount( 2, $data->taxonomies ); 64 | } 65 | 66 | public function testGetTaxonomiesByType() { 67 | $this->_registerTaxonomies(); 68 | 69 | list($status, $headers, $body) = $this->_getResponse( array( 70 | 'REQUEST_METHOD' => 'GET', 71 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/taxonomies/', 72 | 'QUERY_STRING' => http_build_query( array( 'post_type' => array( 'post' ), 'in' => array( 'public_taxonomy_a', 'public_taxonomy_b', 'private_taxonomy_a' ) ) ), 73 | ) ); 74 | 75 | $data = json_decode( $body ); 76 | 77 | $this->assertEquals( '200', $status ); 78 | $this->assertObjectHasAttribute( 'taxonomies', $data ); 79 | $this->assertCount( 1, $data->taxonomies ); 80 | } 81 | 82 | public function testGetTaxonomy() { 83 | list($status, $headers, $body) = $this->_getResponse( array( 84 | 'REQUEST_METHOD' => 'GET', 85 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/taxonomies/public_taxonomy_a', 86 | 'QUERY_STRING' => '', 87 | ) ); 88 | 89 | $data = json_decode( $body ); 90 | 91 | $this->assertEquals( '200', $status ); 92 | $this->assertObjectHasAttribute( 'name', $data ); 93 | $this->assertEquals( 'public_taxonomy_a', $data->name ); 94 | } 95 | 96 | public function testGetTaxonomyEntityFilter() { 97 | add_filter( 'thermal_taxonomy_entity', function($data, &$taxonomy, $state) { 98 | $data->test_value = $taxonomy->name; 99 | return $data; 100 | }, 10, 3 ); 101 | 102 | list($status, $headers, $body) = $this->_getResponse( array( 103 | 'REQUEST_METHOD' => 'GET', 104 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/taxonomies/public_taxonomy_a', 105 | 'QUERY_STRING' => '', 106 | ) ); 107 | 108 | $data = json_decode( $body ); 109 | 110 | $this->assertEquals( '200', $status ); 111 | $this->assertObjectHasAttribute( 'name', $data ); 112 | $this->assertEquals( 'public_taxonomy_a', $data->name ); 113 | $this->assertObjectHasAttribute( 'test_value', $data ); 114 | $this->assertEquals( 'public_taxonomy_a', $data->test_value ); 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /api/v1/controllers/Terms.php: -------------------------------------------------------------------------------- 1 | request()->get(); 26 | 27 | $args = self::convert_request( $request_args ); 28 | 29 | $model = self::model(); 30 | 31 | if($lastModified = apply_filters('thermal_get_lasttermmodified', false ) ) { 32 | $app->lastModified( strtotime( $lastModified . ' GMT' ) ); 33 | } 34 | $terms = $model->find( $taxonomy->name, $args, $found ); 35 | 36 | array_walk( $terms, array( __CLASS__, 'format' ), 'read' ); 37 | $terms = array_values( $terms ); 38 | return empty( $args['include_found'] ) ? compact( 'terms' ) : compact( 'terms', 'found' ); 39 | } 40 | 41 | public static function findById( $app, $taxonomy_name, $id ) { 42 | $taxonomy = Taxonomies::findById( $app, $taxonomy_name ); 43 | 44 | $term = self::model()->findById( $taxonomy_name, $id ); 45 | if ( !$term ) { 46 | $app->halt( '404', get_status_header_desc( '404' ) ); 47 | } 48 | 49 | if( $lastModified = apply_filters('thermal_term_last_modified', false ) ) { 50 | $app->lastModified( strtotime( $lastModified . ' GMT' ) ); 51 | } 52 | 53 | self::format( $term, 'read' ); 54 | return $term; 55 | } 56 | 57 | /** 58 | * Filter and validate the parameters that will be passed to the model. 59 | * @param array $request_args 60 | * @return array 61 | */ 62 | protected static function convert_request( $request_args ) { 63 | // Remove any args that are not allowed by the API 64 | $request_filters = array( 65 | 'paged' => array( ), 66 | 'per_page' => array( '\\intval' ), 67 | 'offset' => array( '\\intval' ), 68 | 'orderby' => array( ), 69 | 'order' => array( ), 70 | 'in' => array( '\\Voce\\Thermal\\v1\\toArray', '\\Voce\\Thermal\\v1\\applyInt', '\\Voce\\Thermal\\v1\\toCommaSeparated' ), 71 | 'slug' => array( ), 72 | 'parent' => array( '\\intval' ), 73 | 'hide_empty' => array( '\\Voce\\Thermal\\v1\\toBool' ), 74 | 'pad_counts' => array( '\\Voce\\Thermal\\v1\\toBool' ), 75 | 'include_found' => array( '\\Voce\\Thermal\\v1\\toBool' ) 76 | ); 77 | //strip any nonsafe args 78 | $request_args = array_intersect_key( $request_args, $request_filters ); 79 | 80 | //run through basic sanitation 81 | foreach ( $request_args as $key => $value ) { 82 | foreach ( $request_filters[$key] as $callback ) { 83 | $value = call_user_func( $callback, $value ); 84 | } 85 | $request_args[$key] = $value; 86 | } 87 | 88 | //convert 'in' to 'include' 89 | if ( !empty( $request_args['in'] ) ) { 90 | $request_args['include'] = $request_args['in']; 91 | unset( $request_args['in'] ); 92 | } 93 | 94 | if ( !empty( $request_args['paged'] ) && empty( $request_args['include_found'] ) ) { 95 | $request_args['include_found'] = true; 96 | } 97 | 98 | return $request_args; 99 | } 100 | 101 | /** 102 | * 103 | * @param \WP_Post $term 104 | */ 105 | public static function format( &$term, $state = 'read' ) { 106 | if ( !$term ) { 107 | return $term = null; 108 | } 109 | 110 | //allow for use with array_walk 111 | if ( func_num_args() > 2 ) { 112 | $state = func_get_arg( func_num_args() - 1 ); 113 | } 114 | if ( !in_array( $state, array( 'read', 'new', 'edit' ) ) ) { 115 | $state = 'read'; 116 | } 117 | 118 | $data = array( 119 | 'name' => $term->name, 120 | 'slug' => $term->slug, 121 | 'parent' => intval( $term->parent ), 122 | 'parent_str' => ( string ) $term->parent, 123 | 'description' => $term->description, 124 | 'post_count' => intval( $term->count ), 125 | ); 126 | 127 | if ( $state == 'read' ) { 128 | $data = array_merge( $data, array( 129 | 'id' => intval( $term->term_id ), 130 | 'term_id_str' => ( string ) $term->term_id, 131 | 'term_taxonomy_id' => intval( $term->term_taxonomy_id ), 132 | 'term_taxonomy_id_str' => ( string ) $term->term_taxonomy_id, 133 | 'taxonomy' => $term->taxonomy, 134 | 'post_count' => intval( $term->count ), 135 | 'meta' => new \stdClass() 136 | ) ); 137 | } 138 | 139 | $term = apply_filters_ref_array( 'thermal_term_entity', array( ( object ) $data, &$term, $state ) ); 140 | } 141 | 142 | } 143 | 144 | ?> 145 | -------------------------------------------------------------------------------- /api/v1/controllers/Users.php: -------------------------------------------------------------------------------- 1 | halt( '403', get_status_header_desc( '403' ) ); 23 | } 24 | 25 | $found = 0; 26 | $users = array( ); 27 | $request_args = $app->request()->get(); 28 | 29 | $args = self::convert_request( $request_args ); 30 | 31 | $model = self::model(); 32 | 33 | $users = $model->find( $args, $found ); 34 | array_walk( $users, array( __CLASS__, 'format' ), 'read' ); 35 | 36 | return ! empty( $request_args['count_total'] ) ? compact( 'users', 'found' ) : compact( 'users' ); 37 | } 38 | 39 | public static function findById( $app, $id ) { 40 | if ( ( $list_users_cap = self::get_list_users_cap() ) && !current_user_can( $list_users_cap ) && $id !== get_current_user_id() ) { 41 | $app->halt( '403', get_status_header_desc( '403' ) ); 42 | } 43 | 44 | $model = self::model(); 45 | $user = $model->findById($id); 46 | if ( !$user ) { 47 | $user->halt( '404', get_status_header_desc('404') ); 48 | } 49 | self::format($user, 'read'); 50 | return $user; 51 | } 52 | 53 | /** 54 | * 55 | * @param \WP_User $user 56 | * @param string $state State of CRUD to render for, options 57 | * include 'read', new', 'edit' 58 | */ 59 | public static function format( &$user, $state = 'read' ) { 60 | if ( !$user ) { 61 | return $user = null; 62 | } 63 | 64 | //allow for use with array_walk 65 | if ( func_num_args() > 2 ) { 66 | $state = func_get_arg( func_num_args() - 1 ); 67 | } 68 | if ( !in_array( $state, array( 'read', 'new', 'edit' ) ) ) { 69 | $state = 'read'; 70 | } 71 | 72 | $data = array( 73 | 'id' => $user->ID, 74 | 'id_str' => ( string ) $user->ID, 75 | 'nicename' => $user->data->user_nicename, 76 | 'display_name' => $user->data->display_name, 77 | 'user_url' => $user->data->user_url, 78 | ); 79 | 80 | if ( $state === 'read' ) { 81 | 82 | $avatar = get_avatar( $user->ID ); 83 | $a_match = preg_match( "/src='([^']*)'/i", $avatar, $matches ); 84 | 85 | if ( $avatar and $a_match ) { 86 | $data = array_merge( $data, array( 87 | 'avatar' => array( 88 | array( 89 | 'url' => array_pop( $matches ), 90 | 'width' => 96, 91 | 'height' => 96, 92 | ) 93 | ) 94 | ) ); 95 | } 96 | 97 | $data = array_merge( $data, array( 98 | 'posts_url' => get_author_posts_url( $user->ID ), 99 | 'meta' => ( object ) array( 100 | 'description' => get_user_meta($user->ID, 'description', true), 101 | 'first_name' => get_user_meta( $user->ID, 'first_name', true), 102 | 'last_name' => get_user_meta( $user->ID, 'last_name', true), 103 | 'nickname' => get_user_meta( $user->ID, 'nickname', true), 104 | ) 105 | ) 106 | ); 107 | } 108 | 109 | $user = apply_filters_ref_array( 'thermal_user_entity', array( ( object ) $data, &$user, $state ) ); 110 | } 111 | 112 | /** 113 | * Filter and validate the parameters that will be passed to the model. 114 | * @param array $request_args 115 | * @return array 116 | */ 117 | public static function convert_request( $request_args ) { 118 | $request_filters = array( 119 | 'paged' => array( ), 120 | 'per_page' => array( '\\intval' ), 121 | 'offset' => array( '\\intval' ), 122 | 'orderby' => array( ), 123 | 'order' => array( ), 124 | 'in' => array( '\\Voce\\Thermal\\v1\\toArray', '\\Voce\\Thermal\\v1\\applyInt' ), 125 | 'include_found' => array( '\\Voce\\Thermal\\v1\\toBool' ), 126 | 'who' => array( ) 127 | ); 128 | 129 | //strip any nonsafe args 130 | $request_args = array_intersect_key( $request_args, $request_filters ); 131 | 132 | //run through basic sanitation 133 | foreach ( $request_args as $key => $value ) { 134 | foreach ( $request_filters[$key] as $callback ) { 135 | $value = call_user_func( $callback, $value ); 136 | } 137 | $request_args[$key] = $value; 138 | } 139 | 140 | if(!empty($request_args['in'])) { 141 | $request_args['include'] = $request_args['in']; 142 | unset($request_args['in']); 143 | } 144 | 145 | if ( !empty( $request_args['per_page'] ) && $request_args['per_page'] > \Voce\Thermal\v1\MAX_USERS_PER_PAGE ) { 146 | $request_args['per_page'] = \Voce\Thermal\v1\MAX_USERS_PER_PAGE; 147 | } 148 | 149 | $request_args['count_total'] = ! ( empty( $request_args['paged'] ) && empty( $request_args['include_found'] ) ); 150 | 151 | if ( !empty( $request_args['who']) && !in_array( $request_args['who'], array( 'authors' ) ) ) { 152 | unset( $request_args['who'] ); 153 | } 154 | return $request_args; 155 | } 156 | 157 | } -------------------------------------------------------------------------------- /api/v1/controllers/Comments.php: -------------------------------------------------------------------------------- 1 | lastModified( strtotime( $lastModified . ' GMT' ) ); 26 | } 27 | 28 | $comments = $model->find( $args, $found ); 29 | 30 | array_walk( $comments, array( __CLASS__, 'format' ), 'read' ); 31 | 32 | return empty( $args['include_found'] ) ? compact( 'comments' ) : compact( 'comments', 'found' ); 33 | } 34 | 35 | public static function find( $app ) { 36 | $args = $app->request()->get(); 37 | return self::_find( $app, $args ); 38 | } 39 | 40 | public static function findByPost( $app, $post_id ) { 41 | $post = Posts::findById( $app, $post_id ); 42 | $args = $app->request()->get(); 43 | $args['post_id'] = $post->id; 44 | return self::_find( $app, $args ); 45 | } 46 | 47 | public static function findById( $app, $id ) { 48 | $comment = self::model()->findById( $id ); 49 | if ( !$comment ) { 50 | $app->halt( '404', get_status_header_desc( '404' ) ); 51 | } 52 | 53 | if( $lastModified = apply_filters('thermal_comment_last_modified', $comment->comment_date_gmt ) ) { 54 | $app->lastModified( strtotime( $lastModified . ' GMT' ) ); 55 | } 56 | 57 | self::format( $comment, 'read' ); 58 | return $comment; 59 | } 60 | 61 | /** 62 | * Filter and validate the parameters that will be passed to the model. 63 | * @param array $request_args 64 | * @return array 65 | */ 66 | protected static function convert_request( $request_args ) { 67 | // Remove any args that are not allowed by the API 68 | $request_filters = array( 69 | 'before' => array( ), 70 | 'after' => array( ), 71 | 's' => array( ), 72 | 'paged' => array( ), 73 | 'per_page' => array( '\\intval' ), 74 | 'offset' => array( '\\intval' ), 75 | 'orderby' => array( ), 76 | 'order' => array( ), 77 | 'in' => array( '\\Voce\\Thermal\\v1\\toArray', '\\Voce\\Thermal\\v1\\applyInt' ), 78 | 'parent' => array( '\\intval' ), 79 | 'post_id' => array( '\\intval' ), 80 | 'post_name' => array( ), 81 | 'type' => array( ), 82 | 'status' => array( ), 83 | 'user_id' => array( '\\intval' ), 84 | 'include_found' => array( '\\Voce\\Thermal\\v1\\toBool' ), 85 | ); 86 | //strip any nonsafe args 87 | $request_args = array_intersect_key( $request_args, $request_filters ); 88 | 89 | //run through basic sanitation 90 | foreach ( $request_args as $key => $value ) { 91 | foreach ( $request_filters[$key] as $callback ) { 92 | $value = call_user_func( $callback, $value ); 93 | } 94 | $request_args[$key] = $value; 95 | } 96 | 97 | //make sure per_page is below MAX 98 | if ( !empty( $request_args['per_page'] ) ) { 99 | if ( absint( $request_args['per_page'] ) > \Voce\Thermal\v1\MAX_TERMS_PER_PAGE ) { 100 | $request_args['per_page'] = \Voce\Thermal\v1\MAX_COMMENTS_PER_PAGE; 101 | } else { 102 | $request_args['per_page'] = absint( $request_args['per_page'] ); 103 | } 104 | } 105 | 106 | //filter status by user privelages 107 | if ( isset( $request_args['status'] ) && $request_args['status'] !== 'approve' ) { 108 | if ( is_user_logged_in() ) { 109 | if ( !current_user_can( 'moderate_comments' ) ) { 110 | $app->halt( '403', get_status_header_desc( '403' ) ); 111 | } 112 | } else { 113 | $app->halt( '401', get_status_header_desc( '401' ) ); 114 | } 115 | } 116 | 117 | if ( !empty( $request_args['per_page'] ) && $request_args['per_page'] > \Voce\Thermal\v1\MAX_POSTS_PER_PAGE ) { 118 | $request_args['per_page'] = \Voce\Thermal\v1\MAX_POSTS_PER_PAGE; 119 | } 120 | 121 | if ( !empty( $request_args['paged'] ) && !isset( $request_args['include_found'] ) ) { 122 | $request_args['include_found'] = true; 123 | } 124 | 125 | return $request_args; 126 | } 127 | 128 | /** 129 | * 130 | * @param \WP_Comment $comment 131 | * @param string $state State of CRUD to render for, options 132 | * include 'read', new', 'edit' 133 | */ 134 | public static function format( &$comment, $state = 'read' ) { 135 | if ( !$comment ) { 136 | return $comment = null; 137 | } 138 | 139 | //allow for use with array_walk 140 | if ( func_num_args() > 2 ) { 141 | $state = func_get_arg( func_num_args() - 1 ); 142 | } 143 | if ( !in_array( $state, array( 'read', 'new', 'edit' ) ) ) { 144 | $state = 'read'; 145 | } 146 | 147 | //edit provides a slimmed down response containing only editable fields 148 | $GLOBALS['comment'] = $comment; 149 | 150 | 151 | if ( $comment->comment_approved === '1' ) { 152 | $status = 'approve'; 153 | } elseif ( $comment->comment_approved === '0' ) { 154 | $status = 'pending'; 155 | } else { 156 | $status = ( string ) $comment->comment_approved; 157 | } 158 | 159 | $data = array( 160 | 'id' => intval( $comment->comment_ID ), 161 | 'id_str' => ( string ) $comment->comment_ID, 162 | 'type' => empty( $comment->comment_type ) ? 'comment' : $comment->comment_type, 163 | 'author' => $comment->comment_author, 164 | 'author_url' => $comment->comment_author_url, 165 | 'parent' => intval( $comment->comment_parent ), 166 | 'parent_str' => ( string ) $comment->comment_parent, 167 | 'date' => ( string ) get_comment_time( 'c', true, $comment ), 168 | 'content' => $comment->comment_content, 169 | 'status' => $status, 170 | 'user' => intval( $comment->user_id ), 171 | 'user_id_str' => ( string ) $comment->user_id 172 | ); 173 | 174 | //add extended data for 'read' 175 | if ( $state == 'read' ) { 176 | $avatar = get_avatar( $comment ); 177 | preg_match( "/src='([^']*)'/i", $avatar, $matches ); 178 | 179 | $data = array_merge( $data, array( 180 | 'content_display' => apply_filters( 'comment_text', get_comment_text( $comment->comment_ID ), $comment ), 181 | 'avatar' => array( 182 | array( 183 | 'url' => array_pop( $matches ), 184 | 'width' => 96, 185 | 'height' => 96, 186 | ) 187 | ) 188 | ) ); 189 | } 190 | 191 | $comment = apply_filters_ref_array( 'thermal_comment_entity', array( ( object ) $data, &$comment, $state ) ); 192 | } 193 | 194 | } 195 | 196 | ?> 197 | -------------------------------------------------------------------------------- /tests/v1/controllers/test-Users.php: -------------------------------------------------------------------------------- 1 | 'test1_login', 9 | 'user_pass' => wp_generate_password(), 10 | 'user_email' => 'test1@example.org', 11 | 'user_nicename' => 'test1', 12 | 'user_url' => 'http://example.org', 13 | 'display_name' => 'Test User1', 14 | 'description' => 'Test Description', 15 | 'first_name' => 'Test', 16 | 'last_name' => 'Last', 17 | 'nickname' => 'test_nick', 18 | 'role' => 'administrator' 19 | ), 20 | array( 21 | 'user_login' => 'test2_login', 22 | 'user_pass' => wp_generate_password(), 23 | 'user_email' => 'test2@example.org', 24 | 'user_nicename' => 'test2', 25 | 'user_url' => 'http://example.org', 26 | 'display_name' => 'Test User2', 27 | 'description' => 'Test Description', 28 | 'role' => 'editor' 29 | ), 30 | array( 31 | 'user_login' => 'test3_login', 32 | 'user_pass' => wp_generate_password(), 33 | 'user_email' => 'test3@example.org', 34 | 'user_nicename' => 'test3', 35 | 'user_url' => 'http://example.org', 36 | 'display_name' => 'Test User3', 37 | 'description' => 'Test Description', 38 | 'role' => 'author' 39 | ), 40 | array( 41 | 'user_login' => 'test4_login', 42 | 'user_pass' => wp_generate_password(), 43 | 'user_email' => 'test4@example.org', 44 | 'user_nicename' => 'test4', 45 | 'user_url' => 'http://example.org', 46 | 'display_name' => 'Test User4', 47 | 'description' => 'Test Description', 48 | 'role' => 'subscriber' 49 | ), 50 | ); 51 | } 52 | 53 | public function testGetUsers() { 54 | 55 | $users = $this->getTestUserData(); 56 | $user = $users[0]; 57 | $user['role'] = 'subscriber'; 58 | $user['id'] = wp_insert_user($user); 59 | 60 | list($status, $headers, $body) = $this->_getResponse( array( 61 | 'REQUEST_METHOD' => 'GET', 62 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/users/', 63 | 'QUERY_STRING' => 'who=authors', 64 | ) ); 65 | 66 | $data = json_decode( $body ); 67 | $this->assertEquals( '200', $status ); 68 | $this->assertInternalType( 'object', $data ); 69 | $this->assertObjectHasAttribute( 'users', $data ); 70 | $this->assertInternalType( 'array', $data->users ); 71 | $this->assertGreaterThanOrEqual( 1, count( $data->users ) ); 72 | $this->assertObjectNotHasAttribute( 'found', $data ); 73 | 74 | //clean up 75 | wp_delete_user($user['id']); 76 | } 77 | 78 | public function testGetUser() { 79 | $users = $this->getTestUserData(); 80 | $user = $users[0]; 81 | 82 | $user['role'] = 'editor'; 83 | $user['id'] = wp_insert_user($user); 84 | 85 | list($status, $headers, $body) = $this->_getResponse( array( 86 | 'REQUEST_METHOD' => 'GET', 87 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/users/' . $user['id'], 88 | 'QUERY_STRING' => '', 89 | ) ); 90 | 91 | $data = json_decode( $body ); 92 | 93 | $this->assertEquals( '200', $status ); 94 | $this->assertInternalType( 'object', $data ); 95 | 96 | $this->assertObjectHasAttribute('id', $data ); 97 | $this->assertInternalType( 'int', $data->id ); 98 | $this->assertEquals( $user['id'], $data->id ); 99 | 100 | $this->assertObjectHasAttribute('id_str', $data ); 101 | $this->assertInternalType( 'string', $data->id_str ); 102 | $this->assertEquals( (string) $user['id'], $data->id_str ); 103 | 104 | $this->assertObjectHasAttribute('nicename', $data ); 105 | $this->assertInternalType( 'string', $data->nicename); 106 | $this->assertEquals( $user['user_nicename'], $data->nicename ); 107 | 108 | $this->assertObjectHasAttribute('display_name', $data ); 109 | $this->assertInternalType( 'string', $data->display_name ); 110 | $this->assertEquals( $user['display_name'], $data->display_name ); 111 | 112 | $this->assertObjectHasAttribute('user_url', $data ); 113 | $this->assertInternalType( 'string', $data->user_url ); 114 | $this->assertEquals( $user['user_url'], $data->user_url ); 115 | 116 | $this->assertObjectHasAttribute('posts_url', $data ); 117 | $this->assertInternalType( 'string', $data->posts_url ); 118 | 119 | $this->assertObjectHasAttribute('meta', $data ); 120 | $this->assertInternalType( 'object', $data->meta ); 121 | 122 | $this->assertObjectHasAttribute('first_name', $data->meta ); 123 | $this->assertEquals( $user['first_name'], $data->meta->first_name ); 124 | 125 | $this->assertObjectHasAttribute('last_name', $data->meta ); 126 | $this->assertEquals( $user['last_name'], $data->meta->last_name ); 127 | 128 | $this->assertObjectHasAttribute('nickname', $data->meta ); 129 | $this->assertEquals( $user['nickname'], $data->meta->nickname ); 130 | 131 | $this->assertObjectHasAttribute('description', $data->meta ); 132 | $this->assertEquals( $user['description'], $data->meta->description ); 133 | 134 | $data = json_decode( $body ); 135 | 136 | //clean up 137 | wp_delete_user($user['id']); 138 | } 139 | 140 | public function testUserAvatarEnabled() { 141 | $users = $this->getTestUserData(); 142 | $user = $users[0]; 143 | 144 | $user['role'] = 'editor'; 145 | $user['id'] = wp_insert_user($user); 146 | 147 | update_option( 'show_avatars', true ); 148 | 149 | list($status, $headers, $body) = $this->_getResponse( array( 150 | 'REQUEST_METHOD' => 'GET', 151 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/users/' . $user['id'], 152 | 'QUERY_STRING' => '', 153 | ) ); 154 | 155 | $data = json_decode( $body ); 156 | 157 | $this->assertObjectHasAttribute( 'avatar', $data ); 158 | $this->assertInternalType( 'array', $data->avatar ); 159 | 160 | $obj_avatar = $data->avatar[0]; 161 | 162 | $this->assertObjectHasAttribute( 'url', $obj_avatar ); 163 | $this->assertInternalType( 'string', $obj_avatar->url ); 164 | 165 | $this->assertObjectHasAttribute( 'width', $obj_avatar ); 166 | $this->assertInternalType( 'int', $obj_avatar->width ); 167 | 168 | $this->assertObjectHasAttribute( 'height', $obj_avatar ); 169 | $this->assertInternalType( 'int', $obj_avatar->height ); 170 | } 171 | 172 | public function testUserAvatarDisabled() { 173 | $users = $this->getTestUserData(); 174 | $user = $users[0]; 175 | 176 | $user['role'] = 'editor'; 177 | $user['id'] = wp_insert_user($user); 178 | 179 | update_option( 'show_avatars', false ); 180 | 181 | list($status, $headers, $body) = $this->_getResponse( array( 182 | 'REQUEST_METHOD' => 'GET', 183 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/users/' . $user['id'], 184 | 'QUERY_STRING' => '', 185 | ) ); 186 | 187 | $data = json_decode( $body ); 188 | 189 | $this->assertObjectNotHasAttribute('avatar', $data ); 190 | } 191 | } 192 | 193 | 194 | -------------------------------------------------------------------------------- /tests/v1/controllers/test-Terms.php: -------------------------------------------------------------------------------- 1 | true, 9 | ) ); 10 | 11 | register_taxonomy( 'private_taxonomy_a', array( 'post' ), array( 12 | 'public' => false, 13 | ) ); 14 | 15 | $post_id_a = wp_insert_post( array( 16 | 'post_status' => 'publish', 17 | 'post_type' => 'post', 18 | 'post_author' => 1, 19 | 'post_parent' => 0, 20 | 'menu_order' => 0, 21 | 'post_content_filtered' => '', 22 | 'post_excerpt' => 'This is the excerpt.', 23 | 'post_content' => 'This is the content.', 24 | 'post_title' => 'Hello World A!', 25 | 'post_date' => '2013-04-30 20:33:36', 26 | 'post_date_gmt' => '2013-04-30 20:33:36', 27 | 'comment_status' => 'open', 28 | ) ); 29 | 30 | $post_id_b = wp_insert_post( array( 31 | 'post_status' => 'publish', 32 | 'post_type' => 'post', 33 | 'post_author' => 1, 34 | 'post_parent' => 0, 35 | 'menu_order' => 0, 36 | 'post_content_filtered' => '', 37 | 'post_excerpt' => 'This is the excerpt.', 38 | 'post_content' => 'This is the content.', 39 | 'post_title' => 'Hello World B!', 40 | 'post_date' => '2013-04-30 20:33:36', 41 | 'post_date_gmt' => '2013-04-30 20:33:36', 42 | 'comment_status' => 'open', 43 | ) ); 44 | 45 | $term_a = wp_create_term( 'Term In Both', 'public_taxonomy_a' ); 46 | $term_b = wp_create_term( 'Term In A', 'public_taxonomy_a' ); 47 | $term_c = wp_create_term( 'Term In B', 'public_taxonomy_a' ); 48 | $term_d = wp_create_term( 'Term In None', 'public_taxonomy_a' ); 49 | $term_e = wp_create_term( 'Term In Private', 'private_taxonomy_a' ); 50 | 51 | wp_set_object_terms( $post_id_a, array( intval( $term_a['term_id'] ), intval( $term_b['term_id'] ) ), 'public_taxonomy_a' ); 52 | wp_set_object_terms( $post_id_b, array( intval( $term_c['term_id'] ) ), 'public_taxonomy_a' ); 53 | wp_set_object_terms( $post_id_a, array( intval( $term_e['term_id'] ) ), 'private_taxonomy_a' ); 54 | 55 | return compact( 'post_id_a', 'post_id_b', 'term_a', 'term_b', 'term_c', 'term_d', 'term_e' ); 56 | } 57 | 58 | public function testGetTerms() { 59 | $testdata = $this->_insertTestData(); 60 | 61 | list($status, $headers, $body) = $this->_getResponse( array( 62 | 'REQUEST_METHOD' => 'GET', 63 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/taxonomies/public_taxonomy_a/terms/', 64 | 'QUERY_STRING' => '', 65 | ) ); 66 | 67 | $data = json_decode( $body ); 68 | 69 | $this->assertEquals( '200', $status ); 70 | $this->assertInternalType( 'object', $data ); 71 | $this->assertObjectHasAttribute( 'terms', $data ); 72 | $this->assertInternalType( 'array', $data->terms ); 73 | $this->assertObjectNotHasAttribute( 'found', $data ); 74 | $this->assertEquals( 3, count( $data->terms ) ); 75 | } 76 | 77 | public function testGetTermsByParent() { 78 | register_taxonomy( 'hierarchical_taxonomy', array( 'post' ), array( 79 | 'public' => true, 80 | 'hierarchical' => true 81 | ) ); 82 | 83 | $parent_term = (object) wp_insert_term( 'parent_term', 'hierarchical_taxonomy' ); 84 | $child_term_1 = (object) wp_insert_term( 'child_term_1', 'hierarchical_taxonomy', array( 'parent' => ( int ) $parent_term->term_id ) ); 85 | $child_term_2 = (object) wp_insert_term( 'child_term_2', 'hierarchical_taxonomy', array( 'parent' => ( int ) $parent_term->term_id ) ); 86 | 87 | delete_option("hierarchical_taxonomy_children"); //work around for https://core.trac.wordpress.org/ticket/14485 88 | 89 | list($status, $headers, $body) = $this->_getResponse( array( 90 | 'REQUEST_METHOD' => 'GET', 91 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/taxonomies/hierarchical_taxonomy/terms/', 92 | 'QUERY_STRING' => 'hide_empty=0&parent=' . $parent_term->term_id, 93 | ) ); 94 | 95 | $data = json_decode( $body ); 96 | 97 | $this->assertEquals( 2, count( $data->terms ) ); 98 | } 99 | 100 | public function testGetTermsLastModified() { 101 | if ( !function_exists( 'wp_using_ext_object_cache' ) ) { 102 | return; 103 | } 104 | $tmp = wp_using_ext_object_cache(); 105 | wp_using_ext_object_cache( true ); 106 | $testdata = $this->_insertTestData(); 107 | 108 | list($status, $headers, $body) = $this->_getResponse( array( 109 | 'REQUEST_METHOD' => 'GET', 110 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/taxonomies/public_taxonomy_a/terms/', 111 | 'QUERY_STRING' => '', 112 | ) ); 113 | 114 | $data = json_decode( $body ); 115 | 116 | $this->assertEquals( '200', $status ); 117 | $this->assertNotEmpty( $headers['last-modified'] ); 118 | $last_modified = $headers['last-modified']; 119 | 120 | list($status, $headers, $body) = $this->_getResponse( array( 121 | 'REQUEST_METHOD' => 'GET', 122 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/taxonomies/public_taxonomy_a/terms/', 123 | 'QUERY_STRING' => '', 124 | 'IF_MODIFIED_SINCE' => $last_modified 125 | ) ); 126 | 127 | $this->assertEquals( '304', $status ); 128 | $this->assertEmpty( $body ); 129 | wp_using_ext_object_cache( $tmp ); 130 | } 131 | 132 | public function testGetTermsCount() { 133 | $testdata = $this->_insertTestData(); 134 | 135 | list($status, $headers, $body) = $this->_getResponse( array( 136 | 'REQUEST_METHOD' => 'GET', 137 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/taxonomies/public_taxonomy_a/terms/', 138 | 'QUERY_STRING' => http_build_query( array( 'include_found' => true ) ), 139 | ) ); 140 | 141 | $data = json_decode( $body ); 142 | 143 | $this->assertEquals( '200', $status ); 144 | $this->assertObjectHasAttribute( 'found', $data ); 145 | $this->assertEquals( 3, $data->found ); 146 | 147 | 148 | 149 | list($status, $headers, $body) = $this->_getResponse( array( 150 | 'REQUEST_METHOD' => 'GET', 151 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/taxonomies/public_taxonomy_a/terms/', 152 | 'QUERY_STRING' => 'paged=1', 153 | ) ); 154 | 155 | $data = json_decode( $body ); 156 | 157 | $this->assertEquals( '200', $status ); 158 | $this->assertObjectHasAttribute( 'found', $data ); 159 | $this->assertEquals( 3, $data->found ); 160 | } 161 | 162 | public function testGetTerm() { 163 | $testdata = $this->_insertTestData(); 164 | 165 | list($status, $headers, $body) = $this->_getResponse( array( 166 | 'REQUEST_METHOD' => 'GET', 167 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/taxonomies/public_taxonomy_a/terms/' . $testdata['term_a']['term_id'], 168 | 'QUERY_STRING' => '' 169 | ) ); 170 | 171 | $data = json_decode( $body ); 172 | 173 | $this->assertEquals( '200', $status ); 174 | $this->assertObjectHasAttribute( 'name', $data ); 175 | $this->assertEquals( 'Term In Both', $data->name ); 176 | 177 | $id = 9999999; 178 | 179 | list($status, $headers, $body) = $this->_getResponse( array( 180 | 'REQUEST_METHOD' => 'GET', 181 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/taxonomies/public_taxonomy_a/terms/' . $id, 182 | 'QUERY_STRING' => '', 183 | ) ); 184 | 185 | $data = json_decode( $body ); 186 | 187 | $this->assertEquals( '404', $status ); 188 | } 189 | 190 | public function testGetTermLastModified() { 191 | if ( !function_exists( 'wp_using_ext_object_cache' ) ) { 192 | return; 193 | } 194 | //temporarily turn on object cache since no core methods provide last modified for terms without caching enabled 195 | $tmp = wp_using_ext_object_cache(); 196 | wp_using_ext_object_cache( true ); 197 | $testdata = $this->_insertTestData(); 198 | 199 | list($status, $headers, $body) = $this->_getResponse( array( 200 | 'REQUEST_METHOD' => 'GET', 201 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/taxonomies/public_taxonomy_a/terms/' . $testdata['term_a']['term_id'], 202 | 'QUERY_STRING' => '' 203 | ) ); 204 | 205 | $data = json_decode( $body ); 206 | 207 | $this->assertEquals( '200', $status ); 208 | $this->assertNotEmpty( $headers['last-modified'] ); 209 | $last_modified = $headers['last-modified']; 210 | 211 | list($status, $headers, $body) = $this->_getResponse( array( 212 | 'REQUEST_METHOD' => 'GET', 213 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/taxonomies/public_taxonomy_a/terms/' . $testdata['term_a']['term_id'], 214 | 'QUERY_STRING' => '', 215 | 'IF_MODIFIED_SINCE' => $last_modified 216 | ) ); 217 | 218 | $this->assertEquals( '304', $status ); 219 | $this->assertEmpty( $body ); 220 | wp_using_ext_object_cache( $tmp ); 221 | } 222 | 223 | public function testGetTermEntityFilter() { 224 | $testdata = $this->_insertTestData(); 225 | 226 | add_filter( 'thermal_term_entity', function($data, $term, $state) { 227 | $data->test_value = $term->term_id; 228 | return $data; 229 | }, 10, 3 ); 230 | 231 | list($status, $headers, $body) = $this->_getResponse( array( 232 | 'REQUEST_METHOD' => 'GET', 233 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/taxonomies/public_taxonomy_a/terms/' . $testdata['term_a']['term_id'], 234 | 'QUERY_STRING' => '' 235 | ) ); 236 | 237 | $data = json_decode( $body ); 238 | 239 | $this->assertEquals( '200', $status ); 240 | $this->assertObjectHasAttribute( 'name', $data ); 241 | $this->assertEquals( 'Term In Both', $data->name ); 242 | $this->assertObjectHasAttribute( 'test_value', $data ); 243 | $this->assertEquals( $testdata['term_a']['term_id'], $data->test_value ); 244 | 245 | $id = 9999999; 246 | 247 | list($status, $headers, $body) = $this->_getResponse( array( 248 | 'REQUEST_METHOD' => 'GET', 249 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/taxonomies/public_taxonomy_a/terms/' . $id, 250 | 'QUERY_STRING' => '', 251 | ) ); 252 | 253 | $data = json_decode( $body ); 254 | 255 | $this->assertEquals( '404', $status ); 256 | } 257 | 258 | } 259 | -------------------------------------------------------------------------------- /tests/v1/controllers/test-Comments.php: -------------------------------------------------------------------------------- 1 | 'publish', 9 | 'post_type' => 'post', 10 | 'post_author' => 1, 11 | 'post_parent' => 0, 12 | 'menu_order' => 0, 13 | 'post_content_filtered' => '', 14 | 'post_excerpt' => 'This is the excerpt.', 15 | 'post_content' => 'This is the content.', 16 | 'post_title' => 'Hello World A!', 17 | 'post_date' => '2013-04-30 20:33:36', 18 | 'post_date_gmt' => '2013-04-30 20:33:36', 19 | 'comment_status' => 'open', 20 | ) ); 21 | 22 | $post_id_b = wp_insert_post( array( 23 | 'post_status' => 'publish', 24 | 'post_type' => 'post', 25 | 'post_author' => 1, 26 | 'post_parent' => 0, 27 | 'menu_order' => 0, 28 | 'post_content_filtered' => '', 29 | 'post_excerpt' => 'This is the excerpt 2.', 30 | 'post_content' => 'This is the content 2.', 31 | 'post_title' => 'Hello World A!', 32 | 'post_date' => '2013-04-30 20:33:36', 33 | 'post_date_gmt' => '2013-04-30 20:33:36', 34 | 'comment_status' => 'open', 35 | ) ); 36 | 37 | $comment_approved_minus_10 = wp_insert_comment( array( 38 | 'comment_post_ID' => $post_id_a, 39 | 'comment_author' => 'Some Guy', 40 | 'comment_author_email' => 'bob@example.org', 41 | 'comment_content' => 'This is my comment text', 42 | 'user_id' => 1, 43 | 'comment_date' => gmdate( 'Y-m-d H:i:s', ( time() - (10 * MINUTE_IN_SECONDS) + ( get_option( 'gmt_offset' ) * HOUR_IN_SECONDS ) ) ), 44 | 'comment_date_gmt' => gmdate( 'Y-m-d H:i:s', ( time() - (10 * MINUTE_IN_SECONDS) ) ), 45 | 'comment_approved' => 1, 46 | ) ); 47 | 48 | $comment_approved_minus_20 = wp_insert_comment( array( 49 | 'comment_post_ID' => $post_id_a, 50 | 'comment_author' => 'Some Guy', 51 | 'comment_author_email' => 'bob@example.org', 52 | 'comment_content' => 'This is my earlier comment text', 53 | 'user_id' => 1, 54 | 'comment_date' => gmdate( 'Y-m-d H:i:s', ( time() - (20 * MINUTE_IN_SECONDS) + ( get_option( 'gmt_offset' ) * HOUR_IN_SECONDS ) ) ), 55 | 'comment_date_gmt' => gmdate( 'Y-m-d H:i:s', ( time() - (20 * MINUTE_IN_SECONDS) ) ), 56 | 'comment_approved' => 1, 57 | ) ); 58 | 59 | $comment_approved_no_user = wp_insert_comment( array( 60 | 'comment_post_ID' => $post_id_a, 61 | 'comment_author' => 'Some Guy 3', 62 | 'comment_author_email' => 'bobbob@example.org', 63 | 'comment_content' => 'This is another comment text', 64 | 'user_id' => 1, 65 | 'comment_date' => gmdate( 'Y-m-d H:i:s', ( time() - (20 * MINUTE_IN_SECONDS) + ( get_option( 'gmt_offset' ) * HOUR_IN_SECONDS ) ) ), 66 | 'comment_date_gmt' => gmdate( 'Y-m-d H:i:s', ( time() - (20 * MINUTE_IN_SECONDS) ) ), 67 | 'comment_approved' => 1, 68 | ) ); 69 | 70 | $comment_pending_minus_20 = wp_insert_comment( array( 71 | 'comment_post_ID' => $post_id_a, 72 | 'comment_author' => 'Some Guy 2', 73 | 'comment_author_email' => 'bob2@example.org', 74 | 'comment_content' => 'This is my pending comment text', 75 | 'user_id' => null, 76 | 'comment_date' => gmdate( 'Y-m-d H:i:s', ( time() - (20 * MINUTE_IN_SECONDS) + ( get_option( 'gmt_offset' ) * HOUR_IN_SECONDS ) ) ), 77 | 'comment_date_gmt' => gmdate( 'Y-m-d H:i:s', ( time() - (20 * MINUTE_IN_SECONDS) ) ), 78 | 'comment_approved' => 0, 79 | ) ); 80 | 81 | $comment_approved_post_b = wp_insert_comment( array( 82 | 'comment_post_ID' => $post_id_b, 83 | 'comment_author' => 'Some Guy 2', 84 | 'comment_author_email' => 'bob2@example.org', 85 | 'comment_content' => 'This is my pending comment text', 86 | 'user_id' => null, 87 | 'comment_date' => gmdate( 'Y-m-d H:i:s', ( time() - (20 * MINUTE_IN_SECONDS) + ( get_option( 'gmt_offset' ) * HOUR_IN_SECONDS ) ) ), 88 | 'comment_date_gmt' => gmdate( 'Y-m-d H:i:s', ( time() - (20 * MINUTE_IN_SECONDS) ) ), 89 | 'comment_approved' => 1, 90 | ) ); 91 | 92 | return compact( 'post_id_a', 'post_id_b', 'comment_approved_minus_10', 'comment_approved_minus_20', 'comment_pending_minus_20', 'comment_approved_post_b' ); 93 | } 94 | 95 | public function testGetComments() { 96 | $testdata = $this->_insertTestData(); 97 | 98 | list($status, $headers, $body) = $this->_getResponse( array( 99 | 'REQUEST_METHOD' => 'GET', 100 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/comments/', 101 | 'QUERY_STRING' => '', 102 | ) ); 103 | 104 | $data = json_decode( $body ); 105 | 106 | $this->assertEquals( '200', $status ); 107 | $this->assertInternalType( 'object', $data ); 108 | $this->assertObjectHasAttribute( 'comments', $data ); 109 | $this->assertInternalType( 'array', $data->comments ); 110 | $this->assertObjectNotHasAttribute( 'found', $data ); 111 | } 112 | 113 | public function testGetCommentsLastModified() { 114 | $this->_insertTestData(); 115 | 116 | list($status, $headers, $body) = $this->_getResponse( array( 117 | 'REQUEST_METHOD' => 'GET', 118 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/comments/', 119 | 'QUERY_STRING' => '', 120 | ) ); 121 | 122 | $data = json_decode( $body ); 123 | 124 | $this->assertEquals( '200', $status ); 125 | $this->assertNotEmpty( $headers['last-modified']); 126 | $last_modified = $headers['last-modified']; 127 | 128 | list($status, $headers, $body) = $this->_getResponse( array( 129 | 'REQUEST_METHOD' => 'GET', 130 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/comments/', 131 | 'QUERY_STRING' => '', 132 | 'IF_MODIFIED_SINCE' => $last_modified 133 | ) ); 134 | 135 | $this->assertEquals( '304', $status ); 136 | $this->assertEmpty( $body ); 137 | } 138 | 139 | public function testGetCommentsCount() { 140 | $testdata = $this->_insertTestData(); 141 | 142 | list($status, $headers, $body) = $this->_getResponse( array( 143 | 'REQUEST_METHOD' => 'GET', 144 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/comments/', 145 | 'QUERY_STRING' => http_build_query( array( 'include_found' => true ) ), 146 | ) ); 147 | 148 | $data = json_decode( $body ); 149 | 150 | $this->assertEquals( '200', $status ); 151 | $this->assertObjectHasAttribute( 'found', $data ); 152 | 153 | list($status, $headers, $body) = $this->_getResponse( array( 154 | 'REQUEST_METHOD' => 'GET', 155 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/comments/', 156 | 'QUERY_STRING' => 'paged=1', 157 | ) ); 158 | 159 | $data = json_decode( $body ); 160 | 161 | $this->assertEquals( '200', $status ); 162 | $this->assertObjectHasAttribute( 'found', $data ); 163 | } 164 | 165 | public function testGetCommentsBefore() { 166 | $testdata = $this->_insertTestData(); 167 | 168 | list($status, $headers, $body) = $this->_getResponse( array( 169 | 'REQUEST_METHOD' => 'GET', 170 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/comments/', 171 | 'QUERY_STRING' => http_build_query( array( 'before' => gmdate( 'Y-m-d H:i:s', ( time() - (15 * MINUTE_IN_SECONDS) ) + ( get_option( 'gmt_offset' ) * HOUR_IN_SECONDS ) ) ) ) 172 | ) ); 173 | 174 | $data = json_decode( $body ); 175 | 176 | $this->assertEquals( '200', $status ); 177 | 178 | $found_comment_minus_10 = false; 179 | $found_comment_minus_20 = false; 180 | foreach ( $data->comments as $comment ) { 181 | if ( $comment->id == $testdata['comment_approved_minus_10'] ) { 182 | $found_comment_minus_10 = true; 183 | } 184 | if ( $comment->id == $testdata['comment_approved_minus_20'] ) { 185 | $found_comment_minus_20 = true; 186 | } 187 | } 188 | 189 | $this->assertFalse( $found_comment_minus_10 ); 190 | $this->assertTrue( $found_comment_minus_20 ); 191 | } 192 | 193 | public function testGetCommentsAfter() { 194 | $testdata = $this->_insertTestData(); 195 | 196 | list($status, $headers, $body) = $this->_getResponse( array( 197 | 'REQUEST_METHOD' => 'GET', 198 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/comments/', 199 | 'QUERY_STRING' => http_build_query( array( 'after' => gmdate( 'Y-m-d H:i:s', ( time() - (15 * MINUTE_IN_SECONDS) ) + ( get_option( 'gmt_offset' ) * HOUR_IN_SECONDS ) ) ) ) 200 | ) ); 201 | 202 | $data = json_decode( $body ); 203 | 204 | $this->assertEquals( '200', $status ); 205 | 206 | $found_comment_minus_10 = false; 207 | $found_comment_minus_20 = false; 208 | foreach ( $data->comments as $comment ) { 209 | if ( $comment->id == $testdata['comment_approved_minus_10'] ) { 210 | $found_comment_minus_10 = true; 211 | } 212 | if ( $comment->id == $testdata['comment_approved_minus_20'] ) { 213 | $found_comment_minus_20 = true; 214 | } 215 | } 216 | 217 | $this->assertTrue( $found_comment_minus_10 ); 218 | $this->assertFalse( $found_comment_minus_20 ); 219 | } 220 | 221 | public function testGetCommentsSearch() { 222 | $testdata = $this->_insertTestData(); 223 | 224 | list($status, $headers, $body) = $this->_getResponse( array( 225 | 'REQUEST_METHOD' => 'GET', 226 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/comments/', 227 | 'QUERY_STRING' => http_build_query( array( 's' => 'my comment text' ) ) 228 | ) ); 229 | 230 | $data = json_decode( $body ); 231 | 232 | $this->assertEquals( '200', $status ); 233 | 234 | $found_comment_minus_10 = false; 235 | $found_comment_minus_20 = false; 236 | foreach ( $data->comments as $comment ) { 237 | if ( $comment->id == $testdata['comment_approved_minus_10'] ) { 238 | $found_comment_minus_10 = true; 239 | } 240 | if ( $comment->id == $testdata['comment_approved_minus_20'] ) { 241 | $found_comment_minus_20 = true; 242 | } 243 | } 244 | 245 | $this->assertTrue( $found_comment_minus_10 ); 246 | $this->assertFalse( $found_comment_minus_20 ); 247 | } 248 | 249 | public function testGetCommentsByPost() { 250 | $testdata = $this->_insertTestData(); 251 | 252 | list($status, $headers, $body) = $this->_getResponse( array( 253 | 'REQUEST_METHOD' => 'GET', 254 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/posts/' . $testdata['post_id_a'] . '/comments/', 255 | 'QUERY_STRING' => '', 256 | ) ); 257 | 258 | $data = json_decode( $body ); 259 | $this->assertEquals( '200', $status ); 260 | 261 | $found_comment_minus_10 = false; 262 | $found_comment_approved_post_b = false; 263 | foreach ( $data->comments as $comment ) { 264 | if ( $comment->id == $testdata['comment_approved_minus_10'] ) { 265 | $found_comment_minus_10 = true; 266 | } 267 | if ( $comment->id == $testdata['comment_approved_post_b'] ) { 268 | $found_comment_approved_post_b = true; 269 | } 270 | } 271 | 272 | $this->assertTrue( $found_comment_minus_10 ); 273 | $this->assertFalse( $found_comment_approved_post_b ); 274 | } 275 | 276 | public function testGetCommentsIn() { 277 | $testdata = $this->_insertTestData(); 278 | 279 | list($status, $headers, $body) = $this->_getResponse( array( 280 | 'REQUEST_METHOD' => 'GET', 281 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/comments/', 282 | 'QUERY_STRING' => http_build_query( array( 'in' => array( $testdata['comment_approved_minus_10'] ) ) ) 283 | ) ); 284 | 285 | $data = json_decode( $body ); 286 | 287 | $this->assertEquals( '200', $status ); 288 | 289 | $found_comment_minus_10 = false; 290 | $found_comment_minus_20 = false; 291 | foreach ( $data->comments as $comment ) { 292 | if ( $comment->id == $testdata['comment_approved_minus_10'] ) { 293 | $found_comment_minus_10 = true; 294 | } 295 | if ( $comment->id == $testdata['comment_approved_minus_20'] ) { 296 | $found_comment_minus_20 = true; 297 | } 298 | } 299 | 300 | $this->assertTrue( $found_comment_minus_10 ); 301 | $this->assertFalse( $found_comment_minus_20 ); 302 | } 303 | 304 | public function testGetComment() { 305 | $testdata = $this->_insertTestData(); 306 | 307 | list($status, $headers, $body) = $this->_getResponse( array( 308 | 'REQUEST_METHOD' => 'GET', 309 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/comments/' . $testdata['comment_approved_minus_10'], 310 | 'QUERY_STRING' => '' 311 | ) ); 312 | 313 | $data = json_decode( $body ); 314 | 315 | $this->assertEquals( '200', $status ); 316 | 317 | $checks = array( 318 | 'id' => array( 'type' => 'int', 'value' => $testdata['comment_approved_minus_10'] ), 319 | 'id_str' => array( 'type' => 'string', 'value' => $testdata['comment_approved_minus_10'] ), 320 | 'type' => array( 'type' => 'string', 'value' => 'comment' ), 321 | 'author' => array( 'type' => 'string', 'value' => 'Some Guy' ), 322 | 'author_url' => array( 'type' => 'string' ), 323 | 'parent' => array( 'type' => 'int', 'value' => 0 ), 324 | 'parent_str' => array( 'type' => 'string', 'value' => '0' ), 325 | 'date' => array( 'type' => 'string' ), 326 | 'content' => array( 'type' => 'string', 'value' => 'This is my comment text' ), 327 | 'status' => array( 'type' => 'string', 'value' => 'approve' ), 328 | 'user' => array( 'type' => 'int', 'value' => '1' ), 329 | 'content_display' => array( 'type' => 'string', 'value' => "

This is my comment text

\n" ), 330 | 'avatar' => array( 'type' => 'array' ), 331 | ); 332 | 333 | foreach ( $checks as $attrib => $check ) { 334 | $this->assertObjectHasAttribute( $attrib, $data ); 335 | $this->assertInternalType( $check['type'], $data->$attrib ); 336 | if ( isset( $check['value'] ) ) { 337 | $this->assertEquals( $check['value'], $data->$attrib ); 338 | } 339 | } 340 | 341 | $id = 9999999; 342 | 343 | list($status, $headers, $body) = $this->_getResponse( array( 344 | 'REQUEST_METHOD' => 'GET', 345 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/comments/' . $id, 346 | 'QUERY_STRING' => '', 347 | ) ); 348 | 349 | $data = json_decode( $body ); 350 | 351 | $this->assertEquals( '404', $status ); 352 | } 353 | 354 | public function testGetCommentLastModified() { 355 | $testdata = $this->_insertTestData(); 356 | 357 | list($status, $headers, $body) = $this->_getResponse( array( 358 | 'REQUEST_METHOD' => 'GET', 359 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/comments/' . $testdata['comment_approved_minus_10'], 360 | 'QUERY_STRING' => '' 361 | ) ); 362 | 363 | $data = json_decode( $body ); 364 | 365 | $this->assertEquals( '200', $status ); 366 | $this->assertNotEmpty( $headers['last-modified']); 367 | $last_modified = $headers['last-modified']; 368 | 369 | list($status, $headers, $body) = $this->_getResponse( array( 370 | 'REQUEST_METHOD' => 'GET', 371 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/comments/' . $testdata['comment_approved_minus_10'], 372 | 'QUERY_STRING' => '', 373 | 'IF_MODIFIED_SINCE' => $last_modified 374 | ) ); 375 | 376 | $this->assertEquals( '304', $status ); 377 | $this->assertEmpty( $body ); 378 | } 379 | 380 | public function testGetCommentEntityFilter() { 381 | $testdata = $this->_insertTestData(); 382 | add_filter( 'thermal_comment_entity', function($data, &$comment, $state) { 383 | $data->test_value = $comment->comment_ID; 384 | return $data; 385 | }, 10, 3 ); 386 | list($status, $headers, $body) = $this->_getResponse( array( 387 | 'REQUEST_METHOD' => 'GET', 388 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/comments/' . $testdata['comment_approved_minus_10'], 389 | 'QUERY_STRING' => '' 390 | ) ); 391 | 392 | $data = json_decode( $body ); 393 | 394 | $this->assertEquals( '200', $status ); 395 | $this->assertObjectHasAttribute( 'test_value', $data ); 396 | $this->assertEquals( $testdata['comment_approved_minus_10'], $data->test_value ); 397 | } 398 | 399 | } 400 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file" 5 | ], 6 | "hash": "4c43ceb27fa47ee7f5a61183fbcf3a5b", 7 | "packages": [ 8 | { 9 | "name": "slim/slim", 10 | "version": "2.2.0", 11 | "source": { 12 | "type": "git", 13 | "url": "https://github.com/codeguy/Slim.git", 14 | "reference": "b8181de1112a1e2f565b40158b621c34ded38053" 15 | }, 16 | "dist": { 17 | "type": "zip", 18 | "url": "https://api.github.com/repos/codeguy/Slim/zipball/b8181de1112a1e2f565b40158b621c34ded38053", 19 | "reference": "b8181de1112a1e2f565b40158b621c34ded38053", 20 | "shasum": "" 21 | }, 22 | "require": { 23 | "php": ">=5.3.0" 24 | }, 25 | "type": "library", 26 | "autoload": { 27 | "psr-0": { 28 | "Slim": "." 29 | } 30 | }, 31 | "notification-url": "https://packagist.org/downloads/", 32 | "license": [ 33 | "MIT" 34 | ], 35 | "authors": [ 36 | { 37 | "name": "Josh Lockhart", 38 | "email": "info@joshlockhart.com", 39 | "homepage": "http://www.joshlockhart.com/" 40 | } 41 | ], 42 | "description": "Slim Framework, a PHP micro framework", 43 | "homepage": "http://github.com/codeguy/Slim", 44 | "keywords": [ 45 | "microframework", 46 | "rest", 47 | "router" 48 | ], 49 | "time": "2012-12-13 02:15:50" 50 | } 51 | ], 52 | "packages-dev": [ 53 | { 54 | "name": "phpunit/php-code-coverage", 55 | "version": "1.2.13", 56 | "source": { 57 | "type": "git", 58 | "url": "https://github.com/sebastianbergmann/php-code-coverage.git", 59 | "reference": "466e7cd2554b4e264c9e3f31216d25ac0e5f3d94" 60 | }, 61 | "dist": { 62 | "type": "zip", 63 | "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/466e7cd2554b4e264c9e3f31216d25ac0e5f3d94", 64 | "reference": "466e7cd2554b4e264c9e3f31216d25ac0e5f3d94", 65 | "shasum": "" 66 | }, 67 | "require": { 68 | "php": ">=5.3.3", 69 | "phpunit/php-file-iterator": ">=1.3.0@stable", 70 | "phpunit/php-text-template": ">=1.1.1@stable", 71 | "phpunit/php-token-stream": ">=1.1.3@stable" 72 | }, 73 | "require-dev": { 74 | "phpunit/phpunit": "3.7.*@dev" 75 | }, 76 | "suggest": { 77 | "ext-dom": "*", 78 | "ext-xdebug": ">=2.0.5" 79 | }, 80 | "type": "library", 81 | "extra": { 82 | "branch-alias": { 83 | "dev-master": "1.2.x-dev" 84 | } 85 | }, 86 | "autoload": { 87 | "classmap": [ 88 | "PHP/" 89 | ] 90 | }, 91 | "notification-url": "https://packagist.org/downloads/", 92 | "include-path": [ 93 | "" 94 | ], 95 | "license": [ 96 | "BSD-3-Clause" 97 | ], 98 | "authors": [ 99 | { 100 | "name": "Sebastian Bergmann", 101 | "email": "sb@sebastian-bergmann.de", 102 | "role": "lead" 103 | } 104 | ], 105 | "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", 106 | "homepage": "https://github.com/sebastianbergmann/php-code-coverage", 107 | "keywords": [ 108 | "coverage", 109 | "testing", 110 | "xunit" 111 | ], 112 | "time": "2013-09-10 08:14:32" 113 | }, 114 | { 115 | "name": "phpunit/php-file-iterator", 116 | "version": "1.3.4", 117 | "source": { 118 | "type": "git", 119 | "url": "https://github.com/sebastianbergmann/php-file-iterator.git", 120 | "reference": "acd690379117b042d1c8af1fafd61bde001bf6bb" 121 | }, 122 | "dist": { 123 | "type": "zip", 124 | "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/acd690379117b042d1c8af1fafd61bde001bf6bb", 125 | "reference": "acd690379117b042d1c8af1fafd61bde001bf6bb", 126 | "shasum": "" 127 | }, 128 | "require": { 129 | "php": ">=5.3.3" 130 | }, 131 | "type": "library", 132 | "autoload": { 133 | "classmap": [ 134 | "File/" 135 | ] 136 | }, 137 | "notification-url": "https://packagist.org/downloads/", 138 | "include-path": [ 139 | "" 140 | ], 141 | "license": [ 142 | "BSD-3-Clause" 143 | ], 144 | "authors": [ 145 | { 146 | "name": "Sebastian Bergmann", 147 | "email": "sb@sebastian-bergmann.de", 148 | "role": "lead" 149 | } 150 | ], 151 | "description": "FilterIterator implementation that filters files based on a list of suffixes.", 152 | "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", 153 | "keywords": [ 154 | "filesystem", 155 | "iterator" 156 | ], 157 | "time": "2013-10-10 15:34:57" 158 | }, 159 | { 160 | "name": "phpunit/php-text-template", 161 | "version": "1.1.4", 162 | "source": { 163 | "type": "git", 164 | "url": "https://github.com/sebastianbergmann/php-text-template.git", 165 | "reference": "5180896f51c5b3648ac946b05f9ec02be78a0b23" 166 | }, 167 | "dist": { 168 | "type": "zip", 169 | "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/5180896f51c5b3648ac946b05f9ec02be78a0b23", 170 | "reference": "5180896f51c5b3648ac946b05f9ec02be78a0b23", 171 | "shasum": "" 172 | }, 173 | "require": { 174 | "php": ">=5.3.3" 175 | }, 176 | "type": "library", 177 | "autoload": { 178 | "classmap": [ 179 | "Text/" 180 | ] 181 | }, 182 | "notification-url": "https://packagist.org/downloads/", 183 | "include-path": [ 184 | "" 185 | ], 186 | "license": [ 187 | "BSD-3-Clause" 188 | ], 189 | "authors": [ 190 | { 191 | "name": "Sebastian Bergmann", 192 | "email": "sb@sebastian-bergmann.de", 193 | "role": "lead" 194 | } 195 | ], 196 | "description": "Simple template engine.", 197 | "homepage": "https://github.com/sebastianbergmann/php-text-template/", 198 | "keywords": [ 199 | "template" 200 | ], 201 | "time": "2012-10-31 18:15:28" 202 | }, 203 | { 204 | "name": "phpunit/php-timer", 205 | "version": "1.0.5", 206 | "source": { 207 | "type": "git", 208 | "url": "https://github.com/sebastianbergmann/php-timer.git", 209 | "reference": "19689d4354b295ee3d8c54b4f42c3efb69cbc17c" 210 | }, 211 | "dist": { 212 | "type": "zip", 213 | "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/19689d4354b295ee3d8c54b4f42c3efb69cbc17c", 214 | "reference": "19689d4354b295ee3d8c54b4f42c3efb69cbc17c", 215 | "shasum": "" 216 | }, 217 | "require": { 218 | "php": ">=5.3.3" 219 | }, 220 | "type": "library", 221 | "autoload": { 222 | "classmap": [ 223 | "PHP/" 224 | ] 225 | }, 226 | "notification-url": "https://packagist.org/downloads/", 227 | "include-path": [ 228 | "" 229 | ], 230 | "license": [ 231 | "BSD-3-Clause" 232 | ], 233 | "authors": [ 234 | { 235 | "name": "Sebastian Bergmann", 236 | "email": "sb@sebastian-bergmann.de", 237 | "role": "lead" 238 | } 239 | ], 240 | "description": "Utility class for timing", 241 | "homepage": "https://github.com/sebastianbergmann/php-timer/", 242 | "keywords": [ 243 | "timer" 244 | ], 245 | "time": "2013-08-02 07:42:54" 246 | }, 247 | { 248 | "name": "phpunit/php-token-stream", 249 | "version": "1.2.1", 250 | "source": { 251 | "type": "git", 252 | "url": "https://github.com/sebastianbergmann/php-token-stream.git", 253 | "reference": "5220af2a7929aa35cf663d97c89ad3d50cf5fa3e" 254 | }, 255 | "dist": { 256 | "type": "zip", 257 | "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/5220af2a7929aa35cf663d97c89ad3d50cf5fa3e", 258 | "reference": "5220af2a7929aa35cf663d97c89ad3d50cf5fa3e", 259 | "shasum": "" 260 | }, 261 | "require": { 262 | "ext-tokenizer": "*", 263 | "php": ">=5.3.3" 264 | }, 265 | "type": "library", 266 | "extra": { 267 | "branch-alias": { 268 | "dev-master": "1.2-dev" 269 | } 270 | }, 271 | "autoload": { 272 | "classmap": [ 273 | "PHP/" 274 | ] 275 | }, 276 | "notification-url": "https://packagist.org/downloads/", 277 | "include-path": [ 278 | "" 279 | ], 280 | "license": [ 281 | "BSD-3-Clause" 282 | ], 283 | "authors": [ 284 | { 285 | "name": "Sebastian Bergmann", 286 | "email": "sb@sebastian-bergmann.de", 287 | "role": "lead" 288 | } 289 | ], 290 | "description": "Wrapper around PHP's tokenizer extension.", 291 | "homepage": "https://github.com/sebastianbergmann/php-token-stream/", 292 | "keywords": [ 293 | "tokenizer" 294 | ], 295 | "time": "2013-09-13 04:58:23" 296 | }, 297 | { 298 | "name": "phpunit/phpunit", 299 | "version": "3.7.28", 300 | "source": { 301 | "type": "git", 302 | "url": "https://github.com/sebastianbergmann/phpunit.git", 303 | "reference": "3b97c8492bcafbabe6b6fbd2ab35f2f04d932a8d" 304 | }, 305 | "dist": { 306 | "type": "zip", 307 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3b97c8492bcafbabe6b6fbd2ab35f2f04d932a8d", 308 | "reference": "3b97c8492bcafbabe6b6fbd2ab35f2f04d932a8d", 309 | "shasum": "" 310 | }, 311 | "require": { 312 | "ext-dom": "*", 313 | "ext-pcre": "*", 314 | "ext-reflection": "*", 315 | "ext-spl": "*", 316 | "php": ">=5.3.3", 317 | "phpunit/php-code-coverage": "~1.2.1", 318 | "phpunit/php-file-iterator": ">=1.3.1", 319 | "phpunit/php-text-template": ">=1.1.1", 320 | "phpunit/php-timer": ">=1.0.4", 321 | "phpunit/phpunit-mock-objects": "~1.2.0", 322 | "symfony/yaml": "~2.0" 323 | }, 324 | "require-dev": { 325 | "pear-pear/pear": "1.9.4" 326 | }, 327 | "suggest": { 328 | "ext-json": "*", 329 | "ext-simplexml": "*", 330 | "ext-tokenizer": "*", 331 | "phpunit/php-invoker": ">=1.1.0,<1.2.0" 332 | }, 333 | "bin": [ 334 | "composer/bin/phpunit" 335 | ], 336 | "type": "library", 337 | "extra": { 338 | "branch-alias": { 339 | "dev-master": "3.7.x-dev" 340 | } 341 | }, 342 | "autoload": { 343 | "classmap": [ 344 | "PHPUnit/" 345 | ] 346 | }, 347 | "notification-url": "https://packagist.org/downloads/", 348 | "include-path": [ 349 | "", 350 | "../../symfony/yaml/" 351 | ], 352 | "license": [ 353 | "BSD-3-Clause" 354 | ], 355 | "authors": [ 356 | { 357 | "name": "Sebastian Bergmann", 358 | "email": "sebastian@phpunit.de", 359 | "role": "lead" 360 | } 361 | ], 362 | "description": "The PHP Unit Testing framework.", 363 | "homepage": "http://www.phpunit.de/", 364 | "keywords": [ 365 | "phpunit", 366 | "testing", 367 | "xunit" 368 | ], 369 | "time": "2013-10-17 07:27:40" 370 | }, 371 | { 372 | "name": "phpunit/phpunit-mock-objects", 373 | "version": "1.2.3", 374 | "source": { 375 | "type": "git", 376 | "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", 377 | "reference": "5794e3c5c5ba0fb037b11d8151add2a07fa82875" 378 | }, 379 | "dist": { 380 | "type": "zip", 381 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/5794e3c5c5ba0fb037b11d8151add2a07fa82875", 382 | "reference": "5794e3c5c5ba0fb037b11d8151add2a07fa82875", 383 | "shasum": "" 384 | }, 385 | "require": { 386 | "php": ">=5.3.3", 387 | "phpunit/php-text-template": ">=1.1.1@stable" 388 | }, 389 | "suggest": { 390 | "ext-soap": "*" 391 | }, 392 | "type": "library", 393 | "autoload": { 394 | "classmap": [ 395 | "PHPUnit/" 396 | ] 397 | }, 398 | "notification-url": "https://packagist.org/downloads/", 399 | "include-path": [ 400 | "" 401 | ], 402 | "license": [ 403 | "BSD-3-Clause" 404 | ], 405 | "authors": [ 406 | { 407 | "name": "Sebastian Bergmann", 408 | "email": "sb@sebastian-bergmann.de", 409 | "role": "lead" 410 | } 411 | ], 412 | "description": "Mock Object library for PHPUnit", 413 | "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", 414 | "keywords": [ 415 | "mock", 416 | "xunit" 417 | ], 418 | "time": "2013-01-13 10:24:48" 419 | }, 420 | { 421 | "name": "symfony/yaml", 422 | "version": "v2.4.1", 423 | "target-dir": "Symfony/Component/Yaml", 424 | "source": { 425 | "type": "git", 426 | "url": "https://github.com/symfony/Yaml.git", 427 | "reference": "4e1a237fc48145fae114b96458d799746ad89aa0" 428 | }, 429 | "dist": { 430 | "type": "zip", 431 | "url": "https://api.github.com/repos/symfony/Yaml/zipball/4e1a237fc48145fae114b96458d799746ad89aa0", 432 | "reference": "4e1a237fc48145fae114b96458d799746ad89aa0", 433 | "shasum": "" 434 | }, 435 | "require": { 436 | "php": ">=5.3.3" 437 | }, 438 | "type": "library", 439 | "extra": { 440 | "branch-alias": { 441 | "dev-master": "2.4-dev" 442 | } 443 | }, 444 | "autoload": { 445 | "psr-0": { 446 | "Symfony\\Component\\Yaml\\": "" 447 | } 448 | }, 449 | "notification-url": "https://packagist.org/downloads/", 450 | "license": [ 451 | "MIT" 452 | ], 453 | "authors": [ 454 | { 455 | "name": "Fabien Potencier", 456 | "email": "fabien@symfony.com" 457 | }, 458 | { 459 | "name": "Symfony Community", 460 | "homepage": "http://symfony.com/contributors" 461 | } 462 | ], 463 | "description": "Symfony Yaml Component", 464 | "homepage": "http://symfony.com", 465 | "time": "2013-12-28 08:12:03" 466 | } 467 | ], 468 | "aliases": [ 469 | 470 | ], 471 | "minimum-stability": "stable", 472 | "stability-flags": [ 473 | 474 | ], 475 | "platform": { 476 | "php": ">=5.3.0" 477 | }, 478 | "platform-dev": [ 479 | 480 | ] 481 | } 482 | -------------------------------------------------------------------------------- /api/v1/controllers/Posts.php: -------------------------------------------------------------------------------- 1 | request()->get(); 24 | 25 | $args = self::convert_request( $request_args ); 26 | 27 | if ( $lastModified = apply_filters( 'thermal_get_lastpostmodified', get_lastpostmodified( 'gmt' ) ) ) { 28 | $app->lastModified( strtotime( $lastModified . ' GMT' ) ); 29 | } 30 | 31 | $model = self::model(); 32 | 33 | $posts = $model->find( $args, $found ); 34 | 35 | array_walk( $posts, array( __CLASS__, 'format' ), 'read' ); 36 | 37 | return empty( $args['no_found_rows'] ) ? compact( 'posts', 'found' ) : compact( 'posts' ); 38 | } 39 | 40 | public static function findById( $app, $id ) { 41 | $post = self::model()->findById( $id ); 42 | if ( !$post ) { 43 | $app->halt( '404', get_status_header_desc( '404' ) ); 44 | } 45 | $post_type_obj = get_post_type_object( get_post_type( $post ) ); 46 | $post_status_obj = get_post_status_object( get_post_status( $post ) ); 47 | 48 | if ( is_user_logged_in() ) { 49 | if ( !current_user_can( $post_type_obj->cap->read, $post->ID ) ) { 50 | $app->halt( '403', get_status_header_desc( '403' ) ); 51 | } 52 | } elseif ( !($post_type_obj->public && $post_status_obj->public) ) { 53 | $app->halt( '401', get_status_header_desc( '401' ) ); 54 | } 55 | 56 | if ( $lastModified = apply_filters( 'thermal_post_last_modified', $post->post_modified_gmt ) ) { 57 | $app->lastModified( strtotime( $lastModified . ' GMT' ) ); 58 | } 59 | 60 | self::format( $post, 'read' ); 61 | return $post; 62 | } 63 | 64 | /** 65 | * Filter and validate the parameters that will be passed to the model. 66 | * @param array $request_args 67 | * @return array 68 | */ 69 | protected static function convert_request( $request_args ) { 70 | // Remove any args that are not allowed by the API 71 | $request_filters = array( 72 | 'm' => array( ), 73 | 'year' => array( ), 74 | 'monthnum' => array( ), 75 | 'w' => array( ), 76 | 'day' => array( ), 77 | 'hour' => array( ), 78 | 'minute' => array( ), 79 | 'second' => array( ), 80 | 'before' => array( ), 81 | 'after' => array( ), 82 | 's' => array( ), 83 | 'exact' => array( '\\Voce\\Thermal\\v1\\toBool' ), 84 | 'sentence' => array( '\\Voce\\Thermal\\v1\\toBool' ), 85 | 'cat' => array( '\\Voce\\Thermal\\v1\\toArray', '\\Voce\\Thermal\\v1\\applyInt', '\\Voce\\Thermal\\v1\\toCommaSeparated' ), 86 | 'category_name' => array( ), 87 | 'tag' => array( ), 88 | 'taxonomy' => array( '\\Voce\\Thermal\\v1\\toArray' ), 89 | 'paged' => array( ), 90 | 'per_page' => array( '\\intval' ), 91 | 'offset' => array( '\\intval' ), 92 | 'orderby' => array( ), 93 | 'order' => array( ), 94 | 'author_name' => array( ), 95 | 'author' => array( ), 96 | 'post__in' => array( '\\Voce\\Thermal\\v1\\toArray', '\\Voce\\Thermal\\v1\\applyInt' ), 97 | 'p' => array( ), 98 | 'name' => array( ), 99 | 'pagename' => array( ), 100 | 'attachment' => array( ), 101 | 'attachment_id' => array( ), 102 | 'subpost' => array( ), 103 | 'subpost_id' => array( ), 104 | 'post_type' => array( '\\Voce\\Thermal\\v1\\toArray' ), 105 | 'post_status' => array( '\\Voce\\Thermal\\v1\\toArray' ), 106 | 'post_parent__in' => array( '\\Voce\\Thermal\\v1\\toArray', '\\Voce\\Thermal\\v1\\applyInt' ), 107 | 'include_found' => array( '\\Voce\\Thermal\\v1\\toBool' ), 108 | ); 109 | //strip any nonsafe args 110 | $request_args = array_intersect_key( $request_args, $request_filters ); 111 | 112 | //run through basic sanitation 113 | foreach ( $request_args as $key => $value ) { 114 | if ( isset( $request_filters[$key] ) ) { 115 | foreach ( $request_filters[$key] as $callback ) { 116 | $value = call_user_func( $callback, $value ); 117 | } 118 | $request_args[$key] = $value; 119 | } 120 | } 121 | 122 | //taxonomy 123 | if ( isset( $request_args['taxonomy'] ) && is_array( $request_args['taxonomy'] ) ) { 124 | $tax_query = array( ); 125 | $public_taxonomies = get_taxonomies( array( 'public' => true ) ); 126 | foreach ( $request_args['taxonomy'] as $key => $value ) { 127 | if ( in_array( $key, $public_taxonomies ) ) { 128 | $tax_query[] = array( 129 | 'taxonomy' => $key, 130 | 'terms' => is_array( $value ) ? $value : array( ), 131 | 'field' => 'term_id', 132 | ); 133 | } 134 | } 135 | unset( $request_args['taxonomy'] ); 136 | if ( count( $tax_query ) ) { 137 | $request_args['tax_query'] = $tax_query; 138 | } 139 | } 140 | 141 | //post_type filtering 142 | if ( isset( $request_args['post_type'] ) ) { 143 | //filter to only ones with read capability 144 | $post_types = array( ); 145 | foreach ( $request_args['post_type'] as $post_type ) { 146 | if ( $post_type_obj = get_post_type_object( $post_type ) ) { 147 | if ( $post_type_obj->public || current_user_can( $post_type_obj->cap->read ) ) { 148 | $post_types[] = $post_type; 149 | } 150 | } 151 | } 152 | $request_args['post_type'] = $post_types; 153 | } else { 154 | if ( empty( $request_args['s'] ) ) { 155 | $request_args['post_type'] = get_post_types( array( 'public' => true ) ); 156 | } else { 157 | $request_args['post_type'] = get_post_types( array( 'exclude_from_search' => false ) ); 158 | } 159 | } 160 | 161 | if ( empty( $request_args['post_status'] ) ) { 162 | //default to publish status 163 | $request_args['post_status'] = 'publish'; 164 | } else { 165 | $request_args['post_status'] = array_filter( $request_args['post_status'], function( $status ) use ( $request_args ) { 166 | if ( $status == 'inherit' ) { 167 | return true; 168 | } 169 | 170 | $status_obj = get_post_status_object( $status ); 171 | if ( !$status_obj ) { 172 | return false; 173 | }; 174 | 175 | if ( $status_obj->public ) { 176 | return true; 177 | } 178 | 179 | //below makes an assumption that a post status is one of public, protected, or private 180 | //because WP Query doesn't currently handle proper mapping of status to type, if a the 181 | //current user doesn't have the capability to view a for that status, the status gets kicked out 182 | 183 | if ( $status_obj->protected ) { 184 | foreach ( $request_args['post_type'] as $post_type ) { 185 | $post_type_obj = get_post_type_object( $post_type ); 186 | if ( $post_type_obj ) { 187 | $edit_protected_cap = $post_type_obj->cap->edit_others_posts; 188 | } else { 189 | $edit_protected_cap = 'edit_others_' . $post_type; 190 | } 191 | if ( !current_user_can( $edit_protected_cap ) ) { 192 | return false; 193 | } 194 | } 195 | } else if ( $status_obj->private ) { 196 | $post_type_obj = get_post_type_object( $post_type ); 197 | if ( $post_type_obj ) { 198 | $read_private_cap = $post_type_obj->cap->read_rivate_posts; 199 | } else { 200 | $read_private_cap = 'read_private_' . $post_type; 201 | } 202 | if ( !current_user_can( $read_private_cap ) ) { 203 | return false; 204 | } 205 | } else { 206 | return false; 207 | } 208 | return true; 209 | } ); 210 | if ( empty( $request_args['post_status'] ) ) { 211 | unset( $request_args['post_status'] ); 212 | } 213 | } 214 | 215 | if ( isset( $request_args['author'] ) ) { 216 | // WordPress only allows a single author to be excluded. We are not 217 | // allowing any author exclusions to be accepted. 218 | $request_args['author'] = array_filter( ( array ) $request_args['author'], function( $author ) { 219 | return $author > 0; 220 | } ); 221 | $request_args['author'] = implode( ',', $request_args['author'] ); 222 | } 223 | 224 | if ( isset( $request_args['orderby'] ) && is_array( $request_args['orderby'] ) ) { 225 | $request_args['orderby'] = implode( ' ', $request_args['orderby'] ); 226 | } 227 | 228 | if ( !empty( $request_args['per_page'] ) && $request_args['per_page'] > \Voce\Thermal\v1\MAX_POSTS_PER_PAGE ) { 229 | $request_args['per_page'] = \Voce\Thermal\v1\MAX_POSTS_PER_PAGE; 230 | } 231 | 232 | if ( empty( $request_args['paged'] ) && empty( $request_args['include_found'] ) ) { 233 | $request_args['no_found_rows'] = true; 234 | } 235 | 236 | return $request_args; 237 | } 238 | 239 | /** 240 | * 241 | * @param \WP_Post $post 242 | * @param string $state State of CRUD to render for, options 243 | * include 'read', new', 'edit' 244 | */ 245 | public static function format( &$post, $state = 'read' ) { 246 | if ( !$post ) { 247 | return $post = null; 248 | } 249 | 250 | //allow for use with array_walk 251 | if ( func_num_args() > 2 ) { 252 | $state = func_get_arg( func_num_args() - 1 ); 253 | } 254 | if ( !in_array( $state, array( 'read', 'new', 'edit' ) ) ) { 255 | $state = 'read'; 256 | } 257 | 258 | //edit provides a slimmed down response containing only editable fields 259 | $GLOBALS['post'] = $post; 260 | setup_postdata( $post ); 261 | 262 | $data = array( 263 | 'type' => $post->post_type, 264 | 'parent' => $post->post_parent, 265 | 'parent_str' => ( string ) $post->post_parent, 266 | 'date' => ( string ) get_post_time( 'c', true, $post ), 267 | 'status' => $post->post_status, 268 | 'comment_status' => $post->comment_status, 269 | 'menu_order' => $post->menu_order, 270 | 'title' => $post->post_title, 271 | 'name' => $post->post_name, 272 | 'excerpt' => $post->post_excerpt, 273 | 'content' => $post->post_content, 274 | 'author' => $post->post_author, 275 | ); 276 | 277 | //add extended data for 'read' 278 | if ( $state == 'read' ) { 279 | $media = array( ); 280 | $meta = array( ); 281 | 282 | // get direct post attachments 283 | $media_image_ids = get_posts( array( 284 | 'post_parent' => $post->ID, 285 | 'post_mime_type' => 'image', 286 | 'post_type' => 'attachment', 287 | 'fields' => 'ids', 288 | 'posts_per_page' => \Voce\Thermal\v1\MAX_POSTS_PER_PAGE 289 | ) ); 290 | //get media in content 291 | if ( preg_match_all( '||i', $post->post_content, $matches ) ) { 292 | $media_image_ids = array_merge( $media_image_ids, $matches[2] ); 293 | } 294 | 295 | //get media from gallery 296 | $gallery_meta = self::_get_gallery_meta( $post, $media_image_ids ); 297 | if ( !empty( $gallery_meta ) ) { 298 | $meta['gallery'] = $gallery_meta; 299 | } 300 | 301 | if ( $thumbnail_id = get_post_thumbnail_id( $post->ID ) ) { 302 | $media_image_ids[] = $meta['featured_image'] = ( int ) $thumbnail_id; 303 | } 304 | 305 | $media_image_ids = apply_filters('thermal_media_image_ids', $media_image_ids, $post); 306 | 307 | $media_image_ids = array_unique( $media_image_ids ); 308 | foreach ( $media_image_ids as $media_image_id ) { 309 | if ( $image_item = self::_format_image_media_item( $media_image_id ) ) { 310 | $media[$media_image_id] = $image_item; 311 | } 312 | } 313 | 314 | // get taxonomy data 315 | $post_taxonomies = array( ); 316 | $taxonomies = get_object_taxonomies( $post->post_type ); 317 | foreach ( $taxonomies as $taxonomy ) { 318 | // get the terms related to post 319 | $terms = get_the_terms( $post->ID, $taxonomy ); 320 | if ( !empty( $terms ) ) { 321 | array_walk( $terms, array( __NAMESPACE__ . '\Terms', 'format' ), $state ); 322 | $post_taxonomies[$taxonomy] = $terms; 323 | } 324 | } 325 | 326 | remove_filter( 'the_content', 'do_shortcode', 11 ); 327 | remove_filter( 'the_content', 'convert_smilies' ); 328 | remove_filter( 'the_content', 'shortcode_unautop' ); 329 | 330 | // remove "" teaser text for display content 331 | $post_more = get_extended( $post->post_content ); 332 | $content_display = $post_more['extended'] ? $post_more['extended'] : $post_more['main']; 333 | 334 | $userModel = Users::model(); 335 | $author = $userModel->findById( $post->post_author ); 336 | 337 | Users::format( $author, 'read' ); 338 | 339 | $data = array_merge( $data, array( 340 | 'id' => $post->ID, 341 | 'id_str' => ( string ) $post->ID, 342 | 'permalink' => get_permalink( $post ), 343 | 'modified' => get_post_modified_time( 'c', true, $post ), 344 | 'comment_status' => $post->comment_status, 345 | 'comment_count' => ( int ) $post->comment_count, 346 | 'excerpt_display' => apply_filters( 'the_excerpt', get_the_excerpt() ), 347 | 'content_display' => apply_filters( 'the_content', $content_display ), 348 | 'mime_type' => $post->post_mime_type, 349 | 'meta' => ( object ) $meta, 350 | 'taxonomies' => ( object ) $post_taxonomies, 351 | 'media' => array_values( $media ), 352 | 'author' => $author 353 | ) ); 354 | } 355 | 356 | $data = apply_filters_ref_array( 'thermal_post_entity', array( ( object ) $data, &$post, $state ) ); 357 | 358 | wp_reset_postdata(); 359 | 360 | $post = ( object ) $data; 361 | } 362 | 363 | protected static function _get_post_galleries( \WP_Post $post ) { 364 | global $shortcode_tags; 365 | 366 | if ( !isset( $shortcode_tags['gallery'] ) ) 367 | return array( ); 368 | 369 | // setting shortcode tags to 'gallery' only 370 | $backup_shortcode_tags = $shortcode_tags; 371 | $shortcode_tags = array( 'gallery' => $shortcode_tags['gallery'] ); 372 | $pattern = get_shortcode_regex(); 373 | $shortcode_tags = $backup_shortcode_tags; 374 | 375 | $matches = array( ); 376 | preg_match_all( "/$pattern/s", $post->post_content, $matches ); 377 | 378 | $gallery_data = array( ); 379 | foreach ( $matches[3] as $gallery_args ) { 380 | $attrs = shortcode_parse_atts( $gallery_args ); 381 | $gallery_data[] = self::_parse_gallery_attrs( $attrs ); 382 | } 383 | 384 | return $gallery_data; 385 | } 386 | 387 | protected static function _parse_gallery_attrs( $gallery_attrs ) { 388 | 389 | $clean_val = function( $val ) { 390 | $trimmed = trim( $val ); 391 | return ( is_numeric( $trimmed ) ? ( int ) $trimmed : $trimmed ); 392 | }; 393 | 394 | $params = array( 395 | 'id', 396 | 'ids', 397 | 'orderby', 398 | 'order', 399 | 'include', 400 | 'exclude', 401 | ); 402 | $array_params = array( 403 | 'ids', 404 | 'orderby', 405 | 'include', 406 | 'exclude', 407 | ); 408 | 409 | if ( empty( $gallery_attrs['order'] ) ) { 410 | $gallery_attrs['order'] = 'ASC'; 411 | } 412 | if ( !empty( $gallery_attrs['ids'] ) ) { 413 | // 'ids' is explicitly ordered, unless you specify otherwise. 414 | if ( empty( $gallery_attrs['orderby'] ) ) { 415 | $gallery_attrs['orderby'] = 'post__in'; 416 | } 417 | $gallery_attrs['include'] = $gallery_attrs['ids']; 418 | } 419 | if ( empty( $gallery_attrs['orderby'] ) ) { 420 | $gallery_attrs['orderby'] = 'menu_order, ID'; 421 | } 422 | 423 | $gallery = array( ); 424 | foreach ( $params as $param ) { 425 | if ( !empty( $gallery_attrs[$param] ) ) { 426 | if ( in_array( $param, $array_params ) ) { 427 | $gallery_param_array = explode( ',', $gallery_attrs[$param] ); 428 | $gallery_param_array = array_map( $clean_val, $gallery_param_array ); 429 | $gallery[$param] = $gallery_param_array; 430 | } else { 431 | $gallery[$param] = $clean_val( $gallery_attrs[$param] ); 432 | } 433 | } 434 | } 435 | 436 | return $gallery; 437 | } 438 | 439 | /** 440 | * @param $post 441 | * @param $media 442 | * @return array 443 | */ 444 | protected static function _get_gallery_meta( $post, &$attachment_ids = array( ) ) { 445 | $gallery_meta = array( ); 446 | 447 | // check post content for gallery shortcode 448 | if ( $gallery_data = self::_get_post_galleries( $post ) ) { 449 | 450 | foreach ( $gallery_data as $gallery ) { 451 | 452 | $gallery_id = empty( $gallery['id'] ) ? $post->ID : intval( $gallery['id'] ); 453 | $order = strtoupper( $gallery['order'] ); 454 | $orderby = implode( ' ', $gallery['orderby'] ); 455 | $include = empty( $gallery['include'] ) ? array( ) : $gallery['include']; 456 | $exclude = empty( $gallery['exclude'] ) ? array( ) : $gallery['exclude']; 457 | 458 | if ( !empty( $order ) && ( 'RAND' == $order ) ) { 459 | $orderby = 'none'; 460 | } 461 | 462 | $ids = self::_get_gallery_attachments( $gallery_id, $order, $orderby, $include, $exclude ); 463 | 464 | $attachment_ids = array_unique( array_merge( $attachment_ids, $ids ) ); 465 | 466 | $gallery_meta[] = array( 467 | 'ids' => $ids, 468 | 'orderby' => $gallery['orderby'], 469 | 'order' => $order, 470 | ); 471 | } 472 | } 473 | 474 | return $gallery_meta; 475 | } 476 | 477 | /** 478 | * @param $gallery_id 479 | * @param $order 480 | * @param $orderby 481 | * @param $include 482 | * @param $exclude 483 | * @return array|bool 484 | */ 485 | protected static function _get_gallery_attachments( $gallery_id, $order, $orderby, $include, $exclude ) { 486 | 487 | $args = array( 488 | 'post_type' => 'attachment', 489 | 'post_mime_type' => 'image', 490 | 'order' => $order, 491 | 'fields' => 'ids', 492 | 'orderby' => $orderby, 493 | ); 494 | 495 | $attachments = array( ); 496 | 497 | if ( !empty( $include ) ) { 498 | 499 | $args['include'] = $include; 500 | $attachments = get_posts( $args ); 501 | } else if ( !empty( $exclude ) ) { 502 | 503 | $args = array_merge( $args, array( 504 | 'post_parent' => $gallery_id, 505 | 'exclude' => $exclude, 506 | ) ); 507 | $attachments = get_posts( $args ); 508 | } else { 509 | 510 | $args['post_parent'] = $gallery_id; 511 | $attachments = get_posts( $args ); 512 | } 513 | 514 | return $attachments; 515 | } 516 | 517 | /** 518 | * Format the output of a media item. 519 | * @param \WP_Post $post 520 | * @return array 521 | */ 522 | protected static function _format_image_media_item( $post ) { 523 | if ( !is_a( $post, "\WP_Post" ) ) { 524 | $post = get_post( $post ); 525 | if ( !$post ) { 526 | return false; 527 | } 528 | } 529 | $meta = wp_get_attachment_metadata( $post->ID ); 530 | $src = wp_get_attachment_image_src( $post->ID, 'full' ); 531 | 532 | if ( isset( $meta['sizes'] ) and is_array( $meta['sizes'] ) ) { 533 | $upload_dir = wp_upload_dir(); 534 | 535 | $sizes = array( 536 | array( 537 | 'height' => $meta['height'], 538 | 'name' => 'full', 539 | 'url' => $src[0], 540 | 'width' => $meta['width'], 541 | ), 542 | ); 543 | 544 | foreach ( $meta['sizes'] as $size => $data ) { 545 | $src = wp_get_attachment_image_src( $post->ID, $size ); 546 | 547 | $sizes[] = array( 548 | 'height' => $data['height'], 549 | 'name' => $size, 550 | 'url' => $src[0], 551 | 'width' => $data['width'], 552 | ); 553 | } 554 | } 555 | 556 | return array( 557 | 'id' => $post->ID, 558 | 'id_str' => ( string ) $post->ID, 559 | 'mime_type' => $post->post_mime_type, 560 | 'alt_text' => get_post_meta( $post->ID, '_wp_attachment_image_alt', true ), 561 | 'sizes' => $sizes, 562 | ); 563 | } 564 | 565 | } 566 | 567 | ?> 568 | -------------------------------------------------------------------------------- /tests/v1/controllers/test-Posts.php: -------------------------------------------------------------------------------- 1 | 'publish', 9 | 'post_type' => 'post', 10 | 'post_author' => 1, 11 | 'post_parent' => 0, 12 | 'menu_order' => 0, 13 | 'post_content_filtered' => '', 14 | 'post_excerpt' => 'This is the excerpt.', 15 | 'post_content' => 'This is the content.', 16 | 'post_title' => 'Hello World!', 17 | 'post_date' => '2013-04-30 20:33:36', 18 | 'post_date_gmt' => '2013-04-30 20:33:36', 19 | 'comment_status' => 'open', 20 | ) ) ); 21 | 22 | if ( empty( $imgs ) ) { 23 | return $test_post_id; 24 | } 25 | 26 | $attachment_ids = array( ); 27 | foreach ( $imgs as $filename ) { 28 | $upload = $this->_upload_file( dirname( dirname( __DIR__ ) ) . '/data/' . $filename ); 29 | $attachment_ids[] = $this->_make_attachment( $upload, $test_post_id ); 30 | } 31 | 32 | return array( 33 | 'post_id' => $test_post_id, 34 | 'attachment_ids' => $attachment_ids, 35 | ); 36 | } 37 | 38 | protected function _upload_file( $filename ) { 39 | 40 | $contents = file_get_contents( $filename ); 41 | 42 | $upload = wp_upload_bits( basename( $filename ), null, $contents ); 43 | 44 | return $upload; 45 | } 46 | 47 | protected function _delete_attachment( $attachment ) { 48 | foreach ( ( array ) $attachment as $id ) { 49 | wp_delete_attachment( $id, true ); 50 | } 51 | } 52 | 53 | protected function _make_attachment( $upload, $parent_post_id = -1 ) { 54 | $type = ''; 55 | if ( !empty( $upload['type'] ) ) { 56 | $type = $upload['type']; 57 | } else { 58 | $mime = wp_check_filetype( $upload['file'] ); 59 | if ( $mime ) { 60 | $type = $mime['type']; 61 | } 62 | } 63 | 64 | $attachment = array( 65 | 'post_title' => basename( $upload['file'] ), 66 | 'post_content' => '', 67 | 'post_type' => 'attachment', 68 | 'post_parent' => $parent_post_id, 69 | 'post_mime_type' => $type, 70 | 'guid' => $upload['url'], 71 | ); 72 | 73 | // Save the data 74 | $id = wp_insert_attachment( $attachment, $upload['file'], $parent_post_id ); 75 | wp_update_attachment_metadata( $id, wp_generate_attachment_metadata( $id, $upload['file'] ) ); 76 | 77 | return $id; 78 | } 79 | 80 | public function testGetPostsStatus() { 81 | list($status, $headers, $body) = $this->_getResponse( array( 82 | 'REQUEST_METHOD' => 'GET', 83 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/posts', 84 | 'QUERY_STRING' => '', 85 | ) ); 86 | 87 | $this->assertEquals( '200', $status ); 88 | } 89 | 90 | public function testGetPostsContentTypeHeaderJSON() { 91 | list($status, $headers, $body) = $this->_getResponse( array( 92 | 'REQUEST_METHOD' => 'GET', 93 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/posts', 94 | 'QUERY_STRING' => '', 95 | ) ); 96 | 97 | $this->assertTrue( $headers->offsetExists( 'Content-Type' ) ); 98 | $this->assertStringStartsWith( 'application/json', $headers['Content-Type'] ); 99 | } 100 | 101 | public function testGetPostsContentTypeHeaderJSONP() { 102 | list($status, $headers, $body) = $this->_getResponse( array( 103 | 'REQUEST_METHOD' => 'GET', 104 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/posts', 105 | 'QUERY_STRING' => 'callback=test', 106 | ) ); 107 | 108 | $this->assertTrue( $headers->offsetExists( 'Content-Type' ) ); 109 | $this->assertStringStartsWith( 'application/javascript', $headers['Content-Type'] ); 110 | } 111 | 112 | public function testLastGetPostsIfModifiedResponse() { 113 | $this->_insert_post(); 114 | 115 | list($status, $headers, $body) = $this->_getResponse( array( 116 | 'REQUEST_METHOD' => 'GET', 117 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/posts', 118 | 'QUERY_STRING' => '', 119 | ) ); 120 | 121 | $last_modified = $headers['last-modified']; 122 | 123 | list($status, $headers, $body) = $this->_getResponse( array( 124 | 'REQUEST_METHOD' => 'GET', 125 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/posts', 126 | 'QUERY_STRING' => '', 127 | 'IF_MODIFIED_SINCE' => $last_modified 128 | ) ); 129 | 130 | $this->assertEquals( '304', $status ); 131 | $this->assertEmpty( $body ); 132 | } 133 | 134 | public function testLastGetPostsLastModifiedHeader() { 135 | $this->_insert_post(); 136 | 137 | list($status, $headers, $body) = $this->_getResponse( array( 138 | 'REQUEST_METHOD' => 'GET', 139 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/posts', 140 | 'QUERY_STRING' => '', 141 | ) ); 142 | 143 | $this->assertTrue( $headers->offsetExists( 'Last-Modified' ) ); 144 | $this->assertNotEmpty( $headers['last-modified'] ); 145 | } 146 | 147 | public function testGetPostsValidJSON() { 148 | $this->_insert_post(); 149 | list($status, $headers, $body) = $this->_getResponse( array( 150 | 'REQUEST_METHOD' => 'GET', 151 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/posts', 152 | 'QUERY_STRING' => '', 153 | ) ); 154 | 155 | $data = json_decode( $body ); 156 | $this->assertInternalType( 'object', $data ); 157 | } 158 | 159 | /** 160 | * @depends testGetPostsValidJSON 161 | */ 162 | public function testGetPostsNotHasFound() { 163 | list($status, $headers, $body) = $this->_getResponse( array( 164 | 'REQUEST_METHOD' => 'GET', 165 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/posts', 166 | 'QUERY_STRING' => '', 167 | ) ); 168 | 169 | $data = json_decode( $body ); 170 | 171 | $this->assertObjectNotHasAttribute( 'found', $data ); 172 | } 173 | 174 | /** 175 | * @depends testGetPostsValidJSON 176 | */ 177 | public function testGetPostsHasFoundTrue() { 178 | $this->_insert_post(); 179 | list($status, $headers, $body) = $this->_getResponse( array( 180 | 'REQUEST_METHOD' => 'GET', 181 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/posts', 182 | 'QUERY_STRING' => 'include_found=1', 183 | ) ); 184 | 185 | $data = json_decode( $body ); 186 | 187 | $this->assertObjectHasAttribute( 'found', $data ); 188 | $this->assertEquals( 1, $data->found ); 189 | } 190 | 191 | /** 192 | * @depends testGetPostsValidJSON 193 | */ 194 | public function testGetPostsHasFoundForPaging() { 195 | $this->_insert_post(); 196 | $this->_insert_post(); 197 | list($status, $headers, $body) = $this->_getResponse( array( 198 | 'REQUEST_METHOD' => 'GET', 199 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/posts', 200 | 'QUERY_STRING' => 'paged=2&per_page=1', 201 | ) ); 202 | 203 | $data = json_decode( $body ); 204 | $this->assertObjectHasAttribute( 'found', $data ); 205 | $this->assertEquals( 2, $data->found ); 206 | } 207 | 208 | /** 209 | * @depends testGetPostsValidJSON 210 | */ 211 | public function testGetPostsHasPostsArray() { 212 | list($status, $headers, $body) = $this->_getResponse( array( 213 | 'REQUEST_METHOD' => 'GET', 214 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/posts', 215 | 'QUERY_STRING' => '', 216 | ) ); 217 | 218 | $data = json_decode( $body ); 219 | 220 | $this->assertObjectHasAttribute( 'posts', $data ); 221 | $this->assertInternalType( 'array', $data->posts ); 222 | $this->assertCount(0, $data->posts); 223 | } 224 | 225 | /** 226 | * @depends testGetPostsValidJSON 227 | */ 228 | public function testGetPostsHasPostsContent() { 229 | $this->_insert_post(); 230 | 231 | list($status, $headers, $body) = $this->_getResponse( array( 232 | 'REQUEST_METHOD' => 'GET', 233 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/posts', 234 | 'QUERY_STRING' => '', 235 | ) ); 236 | 237 | $data = json_decode( $body ); 238 | 239 | $this->assertObjectHasAttribute( 'posts', $data ); 240 | $this->assertInternalType( 'array', $data->posts ); 241 | $this->assertCount(1, $data->posts); 242 | } 243 | 244 | public function testGetAttachments() { 245 | wp_set_current_user( 1 ); 246 | $upload = $this->_upload_file( dirname( dirname( __DIR__ ) ) . '/data/250x250.png' ); 247 | $attachment_id = $this->_make_attachment( $upload ); 248 | 249 | list($status, $headers, $body) = $this->_getResponse( array( 250 | 'REQUEST_METHOD' => 'GET', 251 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/posts', 252 | 'QUERY_STRING' => 'post_type=attachment', 253 | ) ); 254 | 255 | $data = json_decode( $body ); 256 | 257 | $this->assertEquals( '200', $status ); 258 | $this->assertInternalType( 'object', $data ); 259 | $this->assertObjectHasAttribute( 'posts', $data ); 260 | $this->assertInternalType( 'array', $data->posts ); 261 | $this->assertNotEmpty( $data->posts ); 262 | $this->assertObjectNotHasAttribute( 'found', $data ); 263 | wp_set_current_user( 0 ); 264 | } 265 | 266 | public function testGetPostsByPostStatusFutureUnprivelaged() { 267 | $post_id = $this->_insert_post( array( 268 | 'post_status' => 'future', 269 | 'post_date' => date( 'Y-m-d H:i:s', time() + 604800 ), 270 | 'post_date_gmt' => '', 271 | ) ); 272 | 273 | list($status, $headers, $body) = $this->_getResponse( array( 274 | 'REQUEST_METHOD' => 'GET', 275 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/posts', 276 | 'QUERY_STRING' => 'post_status=future&post__in=' . $post_id, 277 | ) ); 278 | 279 | $data = json_decode( $body ); 280 | 281 | $this->assertEquals( '200', $status ); 282 | $this->assertInternalType( 'object', $data ); 283 | $this->assertObjectHasAttribute( 'posts', $data ); 284 | $this->assertInternalType( 'array', $data->posts ); 285 | $this->assertCount( 0, $data->posts ); 286 | $this->assertObjectNotHasAttribute( 'found', $data ); 287 | } 288 | 289 | public function testGetPostsByPostStatusFuturePrivelaged() { 290 | wp_set_current_user( 1 ); 291 | 292 | $post_id = $this->_insert_post( array( 293 | 'post_status' => 'future', 294 | 'post_date' => date( 'Y-m-d H:i:s', time() + 604800 ), 295 | 'post_date_gmt' => '', 296 | ) ); 297 | 298 | list($status, $headers, $body) = $this->_getResponse( array( 299 | 'REQUEST_METHOD' => 'GET', 300 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/posts', 301 | 'QUERY_STRING' => 'post_status=future&post__in=' . $post_id, 302 | ) ); 303 | 304 | $data = json_decode( $body ); 305 | 306 | $this->assertEquals( '200', $status ); 307 | $this->assertInternalType( 'object', $data ); 308 | $this->assertObjectHasAttribute( 'posts', $data ); 309 | $this->assertInternalType( 'array', $data->posts ); 310 | $this->assertCount( 1, $data->posts ); 311 | $this->assertObjectNotHasAttribute( 'found', $data ); 312 | wp_set_current_user( 0 ); //log back out for other tests. 313 | 314 | } 315 | 316 | public function testGetPostsPerPage() { 317 | $this->_insert_post(); 318 | $this->_insert_post(); 319 | $this->_insert_post(); 320 | $this->_insert_post(); 321 | $this->_insert_post(); 322 | $this->_insert_post(); 323 | $this->_insert_post(); 324 | 325 | list($status, $headers, $body) = $this->_getResponse( array( 326 | 'REQUEST_METHOD' => 'GET', 327 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/posts', 328 | 'QUERY_STRING' => 'per_page=2&include_found=1', 329 | ) ); 330 | 331 | $data = json_decode( $body ); 332 | 333 | $this->assertEquals( '200', $status ); 334 | $this->assertInternalType( 'object', $data ); 335 | $this->assertObjectHasAttribute( 'posts', $data ); 336 | $this->assertInternalType( 'array', $data->posts ); 337 | $this->assertObjectHasAttribute( 'found', $data ); 338 | $this->assertCount( 2, $data->posts ); 339 | } 340 | 341 | public function testGetPostsByPostTypeDefaultPublicPostTypes() { 342 | $this->_insert_post(array('post_type'=> 'page')); 343 | $this->_insert_post(null, array('250x250.png')); 344 | 345 | list($status, $headers, $body) = $this->_getResponse( array( 346 | 'REQUEST_METHOD' => 'GET', 347 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/posts', 348 | 'QUERY_STRING' => '', 349 | ) ); 350 | 351 | $data = json_decode( $body ); 352 | //should have a post for each post, page, attachment as all are publicly_queryable 353 | $this->assertCount( 3, $data->posts ); 354 | } 355 | 356 | public function testGetPostsByPostTypePost() { 357 | $this->_insert_post(array('post_type'=> 'page')); 358 | $this->_insert_post(null, array('250x250.png')); 359 | 360 | list($status, $headers, $body) = $this->_getResponse( array( 361 | 'REQUEST_METHOD' => 'GET', 362 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/posts', 363 | 'QUERY_STRING' => 'post_type=post&include_found=1', 364 | ) ); 365 | 366 | $data = json_decode( $body ); 367 | 368 | $this->assertObjectHasAttribute( 'found', $data ); 369 | $this->assertCount( 1, $data->posts ); 370 | } 371 | 372 | 373 | public function testGetPost() { 374 | 375 | //add media item to test unattached images in content 376 | $upload = $this->_upload_file( dirname( dirname( __DIR__ ) ) . '/data/250x250.png' ); 377 | $att_id = $this->_make_attachment( $upload ); 378 | $imgHtml = get_image_send_to_editor( $att_id, 'Test Caption', 'Test Title', 'left' ); 379 | 380 | $content = "Proin nec risus a metus mattis eleifend. Quisque ullamcorper porttitor aliquam. " . 381 | "Donec ut vulputate diam. Etiam eu dui pretium, condimentum nisi eu, tincidunt elit. \n\n" . 382 | $imgHtml . " \n\n Morbi ipsum dolor, tristique quis lorem sit amet, blandit ornare arcu. " . 383 | "Phasellus facilisis varius porttitor. Nam gravida neque eros, id pellentesque nibh aliquam a."; 384 | //get_image_send_to_editor 385 | $test_data = $this->_insert_post( array( 'post_content' => $content ), array( '100x200.png', '100x300.png' ) ); 386 | 387 | $upload = $this->_upload_file( dirname( dirname( __DIR__ ) ) . '/data/100x500.png' ); 388 | $att_id = $this->_make_attachment( $upload ); 389 | set_post_thumbnail( $test_data['post_id'], $att_id ); 390 | 391 | list($status, $headers, $body) = $this->_getResponse( array( 392 | 'REQUEST_METHOD' => 'GET', 393 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/posts/' . $test_data['post_id'], 394 | 'QUERY_STRING' => '', 395 | ) ); 396 | 397 | $data = json_decode( $body ); 398 | $this->assertEquals( '200', $status ); 399 | $this->assertInternalType( 'object', $data ); 400 | 401 | $post = get_post( $test_data['post_id'] ); 402 | 403 | $checks = array( 404 | 'id' => array( 'type' => 'int', 'value' => $post->ID ), 405 | 'id_str' => array( 'type' => 'string', 'value' => $post->ID ), 406 | 'type' => array( 'type' => 'string', 'value' => 'post' ), 407 | 'permalink' => array( 'type' => 'string', 'value' => get_permalink( $post->ID ) ), 408 | 'parent' => array( 'type' => 'int', 'value' => $post->post_parent ), 409 | 'parent_str' => array( 'type' => 'string', 'value' => $post->post_parent ), 410 | 'date' => array( 'type' => 'string', 'value' => get_post_time( 'c', true, $post ) ), 411 | 'modified' => array( 'type' => 'string', 'value' => get_post_modified_time( 'c', true, $post ) ), 412 | 'status' => array( 'type' => 'string', 'value' => $post->post_status ), 413 | 'comment_status' => array( 'type' => 'string', 'value' => $post->comment_status ), 414 | 'comment_count' => array( 'type' => 'int', 'value' => 0 ), 415 | 'menu_order' => array( 'type' => 'int', 'value' => 0 ), 416 | 'title' => array( 'type' => 'string', 'value' => $post->post_title ), 417 | 'name' => array( 'type' => 'string', 'value' => $post->post_name ), 418 | 'excerpt' => array( 'type' => 'string' ), 419 | 'excerpt_display' => array( 'type' => 'string' ), 420 | 'content' => array( 'type' => 'string' ), 421 | 'content_display' => array( 'type' => 'string' ), 422 | 'author' => array( 'type' => 'object' ), 423 | 'mime_type' => array( 'type' => 'string', 'value' => '' ), 424 | 'meta' => array( 'type' => 'object' ), 425 | 'taxonomies' => array( 'type' => 'object' ), 426 | 'media' => array( 'type' => 'array' ) 427 | ); 428 | 429 | foreach ( $checks as $attrib => $check ) { 430 | $this->assertObjectHasAttribute( $attrib, $data ); 431 | $this->assertInternalType( $check['type'], $data->$attrib ); 432 | if ( isset( $check['value'] ) ) { 433 | $this->assertEquals( $check['value'], $data->$attrib ); 434 | } 435 | } 436 | 437 | $this->assertEquals( 4, count( $data->media ) ); 438 | } 439 | 440 | public function testGetPostNotExist() { 441 | $id = 9999999; 442 | 443 | list($status, $headers, $body) = $this->_getResponse( array( 444 | 'REQUEST_METHOD' => 'GET', 445 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/posts/' . $id, 446 | 'QUERY_STRING' => '', 447 | ) ); 448 | 449 | $data = json_decode( $body ); 450 | $this->assertEquals( '404', $status ); 451 | } 452 | 453 | public function testGetPostHasPermissionForDraftStatus() { 454 | wp_set_current_user(1); 455 | $test_post_id = wp_insert_post( array( 456 | 'post_status' => 'draft', 457 | 'post_title' => 'testGetPostDraft', 458 | ) ); 459 | 460 | list($status, $headers, $body) = $this->_getResponse( array( 461 | 'REQUEST_METHOD' => 'GET', 462 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/posts/' . $test_post_id, 463 | 'QUERY_STRING' => '', 464 | ) ); 465 | 466 | $data = json_decode( $body ); 467 | $this->assertEquals( '200', $status ); 468 | $this->assertInternalType( 'object', $data ); 469 | wp_set_current_user(0); 470 | } 471 | 472 | public function testGetPostNotHasPermissionForDraftStatus() { 473 | $test_post_id = wp_insert_post( array( 474 | 'post_status' => 'draft', 475 | 'post_title' => 'testGetPostDraft', 476 | ) ); 477 | 478 | list($status, $headers, $body) = $this->_getResponse( array( 479 | 'REQUEST_METHOD' => 'GET', 480 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/posts/' . $test_post_id, 481 | 'QUERY_STRING' => '', 482 | ) ); 483 | 484 | $data = json_decode( $body ); 485 | 486 | $this->assertEquals( '401', $status ); 487 | } 488 | 489 | public function testGetPostLastModified() { 490 | $test_data = $this->_insert_post(); 491 | 492 | list($status, $headers, $body) = $this->_getResponse( array( 493 | 'REQUEST_METHOD' => 'GET', 494 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/posts/' . $test_data['post_id'], 495 | 'QUERY_STRING' => '', 496 | ) ); 497 | 498 | $this->assertEquals( '200', $status ); 499 | $this->assertNotEmpty( $headers['last-modified'] ); 500 | $last_modified = $headers['last-modified']; 501 | 502 | list($status, $headers, $body) = $this->_getResponse( array( 503 | 'REQUEST_METHOD' => 'GET', 504 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/posts/' . $test_data['post_id'], 505 | 'QUERY_STRING' => '', 506 | 'IF_MODIFIED_SINCE' => $last_modified 507 | ) ); 508 | 509 | $this->assertEquals( '304', $status ); 510 | $this->assertEmpty( $body ); 511 | } 512 | 513 | public function testGetPostEntityFilter() { 514 | $test_data = $this->_insert_post( null, array( '100x200.png', '100x300.png' ) ); 515 | 516 | add_filter( 'thermal_post_entity', function($data, &$post, $state) { 517 | $data->test_value = $post->ID; 518 | return $data; 519 | }, 10, 3 ); 520 | 521 | 522 | list($status, $headers, $body) = $this->_getResponse( array( 523 | 'REQUEST_METHOD' => 'GET', 524 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/posts/' . $test_data['post_id'], 525 | 'QUERY_STRING' => '', 526 | ) ); 527 | 528 | $data = json_decode( $body ); 529 | $this->assertEquals( '200', $status ); 530 | $this->assertInternalType( 'object', $data ); 531 | $this->assertObjectHasAttribute( 'test_value', $data ); 532 | $this->assertEquals( $test_data['post_id'], $data->test_value ); 533 | } 534 | 535 | public function testGetPostsByIdParameterNotRoute() { 536 | 537 | $test_post_id = $this->_insert_post( array( 538 | 'post_status' => 'publish', 539 | 'post_title' => 'testGetPostsByIdParameterNotRoute', 540 | 'post_author' => 1, 541 | ) ); 542 | 543 | $test_args = array( 544 | 'p' => $test_post_id 545 | ); 546 | 547 | list($status, $headers, $body) = $this->_getResponse( array( 548 | 'REQUEST_METHOD' => 'GET', 549 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/posts', 550 | 'QUERY_STRING' => http_build_query( $test_args ), 551 | ) ); 552 | 553 | $data = json_decode( $body ); 554 | 555 | $this->assertObjectHasAttribute( 'posts', $data ); 556 | $this->assertEquals( 1, count( $data->posts ) ); 557 | } 558 | 559 | public function testGetPostsCustomTaxonomy() { 560 | register_taxonomy( 'test_tax', 'post', array( 'public' => true ) ); 561 | $post_id = $this->_insert_post(); 562 | 563 | if ( $term_obj = get_term_by( 'name', 'Test Term', 'test_tax' ) ) { 564 | $term_id = $term_obj->term_id; 565 | } else { 566 | $term_data = wp_insert_term( 'Test Term', 'test_tax' ); 567 | $term_id = $term_data['term_id']; 568 | } 569 | wp_set_object_terms( $post_id, 'Test Term', 'test_tax' ); 570 | 571 | $args = array( 572 | 'taxonomy' => array( 'test_tax' => array( $term_id ) ) 573 | ); 574 | list($status, $headers, $body) = $this->_getResponse( array( 575 | 'REQUEST_METHOD' => 'GET', 576 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/posts', 577 | 'QUERY_STRING' => http_build_query( $args ) 578 | ) ); 579 | 580 | $data = json_decode( $body ); 581 | 582 | $this->assertEquals( '200', $status ); 583 | $this->assertInternalType( 'object', $data ); 584 | $this->assertObjectHasAttribute( 'posts', $data ); 585 | $this->assertInternalType( 'array', $data->posts ); 586 | $this->assertObjectNotHasAttribute( 'found', $data ); 587 | } 588 | 589 | public function testPostMetaGallery() { 590 | $post_content = 'Lorem Ipsum [gallery]'; 591 | $post_args = array( 'post_content' => $post_content ); 592 | $post_images = array( '100x200.png', '100x300.png', '100x400.png' ); 593 | $post_data = $this->_insert_post( $post_args, $post_images ); 594 | $post_id = $post_data['post_id']; 595 | $attachment_ids = $post_data['attachment_ids']; 596 | 597 | list($status, $headers, $body) = $this->_getResponse( array( 598 | 'REQUEST_METHOD' => 'GET', 599 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/posts/' . $post_id, 600 | 'QUERY_STRING' => '', 601 | ) ); 602 | 603 | $data = json_decode( $body ); 604 | 605 | $this->assertEquals( $attachment_ids, $data->meta->gallery[0]->ids ); 606 | } 607 | 608 | public function testGetPostGalleryExclude() { 609 | $post_content = 'Lorem Ipsum'; 610 | $post_args = array( 'post_content' => $post_content ); 611 | $post_images = array( '100x200.png', '100x300.png', '100x400.png' ); 612 | $post_data = $this->_insert_post( $post_args, $post_images ); 613 | $post_id = $post_data['post_id']; 614 | $attachment_ids = $post_data['attachment_ids']; 615 | 616 | $this->_insert_post( 617 | array( 618 | 'ID' => $post_id, 619 | 'post_content' => sprintf( '[gallery exclude="%d"]', array_shift($attachment_ids) ), 620 | ) 621 | ); 622 | 623 | list($status, $headers, $body) = $this->_getResponse( array( 624 | 'REQUEST_METHOD' => 'GET', 625 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/posts/' . $post_id, 626 | 'QUERY_STRING' => '', 627 | ) ); 628 | 629 | $data = json_decode( $body ); 630 | 631 | $this->assertEquals( $attachment_ids, $data->meta->gallery[0]->ids ); 632 | } 633 | 634 | public function testGetPostGallerySort() { 635 | $post_content = 'Lorem Ipsum [gallery order="DESC" orderby="ID"]'; 636 | $post_args = array( 'post_content' => $post_content ); 637 | $post_images = array( '100x200.png', '100x300.png', '100x400.png' ); 638 | $post_data = $this->_insert_post( $post_args, $post_images ); 639 | $post_id = $post_data['post_id']; 640 | $attachment_ids = $post_data['attachment_ids']; 641 | 642 | rsort($attachment_ids); 643 | 644 | list($status, $headers, $body) = $this->_getResponse( array( 645 | 'REQUEST_METHOD' => 'GET', 646 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/posts/' . $post_id, 647 | 'QUERY_STRING' => '', 648 | ) ); 649 | 650 | $data = json_decode( $body ); 651 | 652 | $this->assertEquals( 'ID', $data->meta->gallery[0]->orderby[0] ); 653 | $this->assertEquals( 'DESC', $data->meta->gallery[0]->order ); 654 | $this->assertEquals( $attachment_ids, $data->meta->gallery[0]->ids ); 655 | } 656 | 657 | public function testGetPostGalleryID() { 658 | $post_content = 'Lorem Ipsum'; 659 | $post_args = array( 'post_content' => $post_content ); 660 | $post_images = array( '100x200.png', '100x300.png', '100x400.png' ); 661 | $post_data = $this->_insert_post( $post_args, $post_images ); 662 | $post_id = $post_data['post_id']; 663 | $attachment_ids = $post_data['attachment_ids']; 664 | 665 | $post_2_content = sprintf( 'Lorem Ipsum [gallery id="%d"]', $post_id ); 666 | $post_2_args = array( 'post_content' => $post_2_content ); 667 | $post_2_id = $this->_insert_post( $post_2_args ); 668 | 669 | list($status, $headers, $body) = $this->_getResponse( array( 670 | 'REQUEST_METHOD' => 'GET', 671 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/posts/' . $post_2_id, 672 | 'QUERY_STRING' => '', 673 | ) ); 674 | 675 | $data = json_decode( $body ); 676 | 677 | $this->assertEquals( $attachment_ids, $data->meta->gallery[0]->ids ); 678 | } 679 | 680 | public function testGetPostGalleryIDs() { 681 | $post_content = 'Lorem Ipsum'; 682 | $post_args = array( 'post_content' => $post_content ); 683 | $post_images = array( '100x200.png', '100x300.png', '100x400.png' ); 684 | $post_data = $this->_insert_post( $post_args, $post_images ); 685 | $post_id = $post_data['post_id']; 686 | $attachment_ids = $post_data['attachment_ids']; 687 | 688 | array_shift($attachment_ids); 689 | 690 | $this->_insert_post( 691 | array( 692 | 'ID' => $post_id, 693 | 'post_content' => sprintf( '[gallery ids="%s"]', implode(',', $attachment_ids) ) 694 | ) 695 | ); 696 | 697 | list($status, $headers, $body) = $this->_getResponse( array( 698 | 'REQUEST_METHOD' => 'GET', 699 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/posts/' . $post_id, 700 | 'QUERY_STRING' => '', 701 | ) ); 702 | 703 | $data = json_decode( $body ); 704 | 705 | $this->assertEquals( $attachment_ids, $data->meta->gallery[0]->ids ); 706 | } 707 | 708 | public function testGetPostGalleryInclude() { 709 | $post_content = 'Lorem Ipsum'; 710 | $post_args = array( 'post_content' => $post_content ); 711 | $post_images = array( '100x200.png', '100x300.png', '100x400.png' ); 712 | $post_data = $this->_insert_post( $post_args, $post_images ); 713 | $post_id = $post_data['post_id']; 714 | $attachment_ids = $post_data['attachment_ids']; 715 | 716 | array_shift($attachment_ids); 717 | 718 | $this->_insert_post( 719 | array( 720 | 'ID' => $post_id, 721 | 'post_content' => sprintf( '[gallery include="%s"]', implode(',', $attachment_ids) ) 722 | ) 723 | ); 724 | 725 | list($status, $headers, $body) = $this->_getResponse( array( 726 | 'REQUEST_METHOD' => 'GET', 727 | 'PATH_INFO' => Voce\Thermal\get_api_base() . 'v1/posts/' . $post_id, 728 | 'QUERY_STRING' => '', 729 | ) ); 730 | 731 | $data = json_decode( $body ); 732 | 733 | $this->assertEquals( $attachment_ids, $data->meta->gallery[0]->ids ); 734 | } 735 | 736 | } 737 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Please note: This plugin is no longer being actively maintained or supported. 2 | 3 | # Thermal API 4 | 5 | [![Build Status](https://travis-ci.org/voceconnect/thermal-api.png?branch=master)](https://travis-ci.org/voceconnect/thermal-api) 6 | 7 | Current API version: v1 8 | 9 | ## Overview 10 | Thermal is the WordPress plugin that gives you the access and control to your content 11 | from outside of the WordPress admin. Thermal supports client-based decisions that, 12 | when combined with a responsive design framework, allows for a truly responsive 13 | application leveraging a WordPress content source. 14 | 15 | ### Versions 16 | In order to support migration, the API plugin will support up to 2 versions of the API. Once a 17 | version is more than 1 cycle old, it will no longer respond at it's API root unless configured 18 | to do so. 19 | 20 | ### API Root 21 | The URL root of the API will be the version number of the API prefixed by your 22 | WordPress site URL and the `Voce\Thermal\API_ROOT` constant. By default this 23 | is set to `wp_api` but can be overridden by setting it in `wp-config.php`. 24 | 25 | The current API version is v1 so the default URL root is: 26 | 27 | http://example.com/wp_api/v1/ 28 | 29 | ## Resource Types 30 | The following resources are available 31 | 32 | * [Posts](#posts) 33 | * [Users](#users) 34 | * [Taxonomies](#taxonomies) 35 | * [Terms](#terms) 36 | * [Rewrite Rules](#rewrite_rules) 37 | * [Media Items](#media_items) 38 | * [Comments](#comments) 39 | 40 | 41 | ## Posts 42 | A post represents a single item of content. 43 | 44 | ### Methods 45 | #### List 46 | 47 | ##### Request 48 | GET {api root}/posts 49 | ##### Parameters 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 64 | 65 | 66 | 67 | 68 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 120 | 121 | 122 | 123 | 124 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 137 | 138 | 139 | 140 | 141 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 258 | 259 | 260 | 261 | 262 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 300 | 301 | 302 | 303 | 304 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 |
ParameterData TypeDescription
62 | Date Filters 63 |
mstring 69 | A compressed datetime string in the format of 'YmdGis' that represents date/time range to filter posts to (.e.g 2012-01-01 13:59:59 is expressed as 20120101135959). As most right most parts of the string are left off, the filter becomes less exact.
70 | 71 | Examples: 72 | 73 |
    74 |
  • 'm=2012' Only posts from 2012 will be returned; Equivalent to 'year=2012'
  • 75 |
  • 'm=201206' Only posts from June 2012 will be returned; Equivalent to 'year=2012&monthnum=6'
  • 76 |
  • 'm=20120609' Only posts from June 9th, 2012 will be returned; Equivalent to 'year=2012&monthnum=6&day=9'
  • 77 |
78 |
yearinteger4 digit year (e.g. 2012)
monthumintegerMonth number (from 1 to 12)
wintegerWeek of the year (from 0 to 53)
dayintegerDay of the month (from 0 to 31)
hourintegerHour of the day in 24 hour format (from 0 to 23)
minuteintegerMinute (from 0 to 59)
secondintegerSecond (from 0 to 59)
beforestringA parsable formatted date string. Unless specified in the format used, 119 | the result will be relative to the timezone of the site.
afterstringA parsable formatted date string. Unless specified in the format used, 125 | the result will be relative to the timezone of the site.
Search Filtering
sstring 134 | Search keyword or string, by default this searches against the title and post_content 135 | By default, the search expression is split into individual terms. 136 |
exactbooleanDefault false. If true, the search will omit the wildcard '%' wrapper, making it so 142 | that at least one searched fields be an exact match.
sentencebooleanDefault false. If true, the search string will not be split up into individual tokens and the expression will be matched in its entirety.
Taxonomy Filters
catarray|integerThe term_id of the category to include. An array of IDs will also be accepted.
category_namestringThe slug of a single category.
tagstringThe slug of a single tag
taxonomyassociative arrayAn associative array where the key is the name of the taxonomy and the value is 171 | an array of term IDs. Post that exist in any of the terms will be included in the 172 | results. Only public taxonomies will be recognized.
Pagination Filters
pagedintegerA positive integer specifiying the page (or subset of results) to return. This filter will automatically determine the offset to use based on the per_page 181 | and paged. Using this filter will cause include_found to be true. 182 |
per_pageintegerThe maximum number of posts to return. The value must range from 1 to MAX_POSTS_PER_PAGE.
offsetintegerThe number of posts to skip over before returning the result set.
Ordering Parameters
orderbyarray|stringSort the results by the given identifier. Defaults to 'date'. Supported values are: 201 |
    202 |
  • 'none' - No ordering will be applied.
  • 203 |
  • 'ID' - The ID of the post.
  • 204 |
  • 'author' - The value of the author ID.
  • 205 |
  • 'title' - The title of the post.
  • 206 |
  • 'name' - The slug/name of the post.
  • 207 |
  • 'date' - (Default) Publish date of the post.
  • 208 |
  • 'modified' - Last modified date of the post.
  • 209 |
  • 'parent'- The ID of the post's parent
  • 210 |
  • 'rand' - A random order, Note: due to caching, the order may not change on every request.
  • 211 |
  • 'comment_count' - The number of comments the post has.
  • 212 |
  • 'menu_order' - The set menu order for the post.
  • 213 |
  • 'post__in' - Preserves the order supplied in the post__in filter. This is ignored unless the post__in filter is supplied.
  • 214 |
215 | 216 | Orderby will also accept an array of multiple identifiers. 217 |
orderstringThe order direction. Options are 'ASC' and 'DESC'. Default is 'DESC'
General Filters
author_namestringThe user_nicename of the author.
authorintegerThe ID of the authors to include. An array of IDs will also be accepted. Negative 236 | ID's can be used to denote exclusion.
post__inarray|integerAn array of post ID's to include.
pintegerA single post ID
namestringThe post_name or slug of the post
pagenamestringThe post_name or slug of the post. Will cause the post_type filter to default 257 | to 'page'
attachmentstringThe post_name or slug of the post. Will cause the post_type filter to default 263 | to 'attachment'.
attachment_idintegerSynonym to 'p' filter.
subpoststringSynonym for 'attachment' filter.
subpost_idintegerSynonym for 'attachment_id' filter.
post_typearray|stringThe post types to be included in the result set.
post_statusarray|stringDefault to 'publish'. The post statii to include in the result set. Note that the statii 289 | passed in are run through capability checks for the current user.
post_parent__inarray|integerArray or single Post ID to pull child posts from.
298 | Response Altering Parameters 299 |
include_foundbooleanDefaut to false. When true, the response will include a found rows count. There is some 305 | overhead in generating the total count so this should only be turned on when needed. This is 306 | automatically turned on if the 'paged' filter is used.
callbackstringWhen set, the response will be wrapped in a JSONP callback.
315 | 316 | 317 | 318 | ##### Response 319 | { 320 | 'found': 40, //only provided if include_found == true 321 | "posts": [ 322 | [Post Object], 323 | …. 324 | ] 325 | } 326 | 327 | 328 | 329 | #### Single Entity 330 | 331 | ##### Request 332 | GET {api root}/posts/{id} 333 | 334 | ##### Parameters 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 |
ParameterData TypeDescription
346 | Response Altering Parameters 347 |
callbackstringWhen set, the response will be wrapped in a JSONP callback.
356 | 357 | ##### Post JSON Schema 358 | { 359 | "title": "Post Object", 360 | "description": "A representation of a single post object", 361 | "type": "object", 362 | "id": "#post", 363 | "properties": { 364 | "author": { 365 | "description": "The user set as the author of the post.", 366 | "type": { 367 | "$ref": "#user" 368 | }, 369 | "required": true 370 | }, 371 | "comment_count": { 372 | "description": "The number of comments for this post.", 373 | "type": "integer", 374 | "minimum": 0, 375 | "required": true 376 | }, 377 | "comment_status": { 378 | "description": "The current status determining whether the post is accepting comments.", 379 | "enum": ["open", "closed"], 380 | "required": true 381 | }, 382 | "content_display": { 383 | "description": "The content of the post after it has been run through the set 'the_content' filters. Shortcodes are not expanded.", 384 | "type": "string", 385 | "required": true 386 | }, 387 | "content": { 388 | "description": "The raw content of the post as it's stored in the database.", 389 | "type": "string", 390 | "required": true 391 | }, 392 | "date": { 393 | "description": "The post's creation time in iso 8601 format.", 394 | "type": "string", 395 | "format": "date-time", 396 | "required": true 397 | }, 398 | "excerpt_display": { 399 | "description": "The excerpt of the post after it has been run through the 'the_excerpt' filters.", 400 | "type": "string", 401 | "required": true 402 | }, 403 | "excerpt": { 404 | "description": "The raw excerpt as it is stored in the database.", 405 | "type": "string", 406 | "required": true 407 | }, 408 | "id_str": { 409 | "description": "The ID of the post represented as a string.", 410 | "type": "string", 411 | "required": true 412 | }, 413 | "id": { 414 | "description": "The ID of the post", 415 | "type": "integer", 416 | "minimum": 1, 417 | "required": true 418 | }, 419 | "type": { 420 | "description": "The post_type of the post", 421 | "type": "string", 422 | "required": true 423 | }, 424 | "media": { 425 | "type": "array", 426 | "required": false, 427 | "items": { 428 | "type": { 429 | "$ref": "#mediaItem" 430 | } 431 | } 432 | }, 433 | "meta": { 434 | "description": "Additional data for the Post object. Handling must be provided by other plugins to expand the provided meta beyond core properties.", 435 | "type": "object", 436 | "required": false, 437 | "default": {}, 438 | "additionalProperties": { 439 | "featuredImage": { 440 | "description": "The ID of the image being referenced as the featured image. The referenced image should be present in the media property.", 441 | "type": "integer", 442 | "minimum": 1 443 | }, 444 | "gallery": { 445 | "description": "An array of objects that represent the galleries in the post content.", 446 | "type": "array", 447 | "required": false, 448 | "items": { 449 | "ids": { 450 | "description": "The IDs of the attachments to be used in the gallery.", 451 | "type": "array", 452 | "required": false 453 | }, 454 | "orderby": { 455 | "description": "Specifies how to sort the display thumbnails.", 456 | "type": "array", 457 | "required": false 458 | }, 459 | "order": { 460 | "description": "Specifies the sort order used to display thumbnails." 461 | "type": "string", 462 | "required": false 463 | }, 464 | "in": { 465 | "description": "An array of IDs to only show the images from these attachments." 466 | "type": "array", 467 | "required": false 468 | }, 469 | "exclude": { 470 | "description": "An array of IDs to not show the images from these attachments." 471 | "type": "array", 472 | "required": false 473 | }, 474 | "id": { 475 | "description": "The ID of the post to be used in the gallery. Used for specifying other posts.", 476 | "type": "integer", 477 | "required": false 478 | } 479 | } 480 | } 481 | } 482 | }, 483 | "mime_type": { 484 | "description": "The mime type of the represented object", 485 | "type": "string", 486 | "required": true, 487 | "default": "text/html" 488 | }, 489 | "modified": { 490 | "type": "string", 491 | "format": "date-time", 492 | "required": true 493 | }, 494 | "name": { 495 | "description": "The name (slug) for the post, used in URLs.", 496 | "type": "string", 497 | "required": true 498 | }, 499 | "parent_str": { 500 | "description": "The ID of the post's parent as a string, if it has one.", 501 | "type": "string", 502 | "required": false 503 | }, 504 | "parent": { 505 | "description": "The ID of the post's parent as a string, if it has one.", 506 | "type": "integer", 507 | "required": false 508 | }, 509 | "permalink": { 510 | "description": "The full permalink URL to the post.", 511 | "type": "string", 512 | "formate": "uri", 513 | "required": true 514 | }, 515 | "status": { 516 | "description": "The status of the post.", 517 | "type": { 518 | "enum": ["publish", "draft", "pending", "future", "trash"] 519 | }, 520 | "required": true 521 | }, 522 | "taxonomies": { 523 | "description": "Key/Value pairs of taxonomies that exist for the given post where the Key is the name of the taxonomy.", 524 | "type": "object", 525 | "required": false, 526 | "default": {}, 527 | "additionalProperties": { 528 | "category": { 529 | "type": "array", 530 | "items": { 531 | "type": { 532 | "$ref": "#term" 533 | } 534 | }, 535 | "required": false 536 | }, 537 | "post_tag": { 538 | "type": "array", 539 | "items": { 540 | "type": { 541 | "$ref": "#term" 542 | } 543 | }, 544 | "required": false 545 | } 546 | } 547 | }, 548 | "title": { 549 | "description": "The title of the Post.", 550 | "type": "string", 551 | "required": true 552 | } 553 | } 554 | } 555 | 556 | ##### Example Post Response 557 | { 558 | "id" : 1234567, 559 | "id_str" : "1234567", 560 | "type" : "post", 561 | "permalink": "http://example.com/posts/foobar/", 562 | "parent": 12345, 563 | "parent_str": "12345", 564 | "date": "2012-01-01T12:59:59+00:00", 565 | "modified": "2012-01-01T12:59:59+00:00", 566 | "status": "publish", 567 | "comment_status":"open", 568 | "comment_count": 99, 569 | "menu_order": 99, 570 | "title": "Lorem Ipsum Dolor!", 571 | "name": "loerm-ipsum-dolor" , 572 | "excerpt": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec sed lacus eros. Integer elementum urna.", 573 | "excerpt_display": "

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec sed lacus eros. Integer elementum urna.

", 574 | "content": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed nec consequatnibh. Quisque in consectetur ligula. Praesent pretium massa vitae neque adipiscing vita cursus nulla congue.\n\"Lorem\n Cras aliquet ipsum non nisi accumsan tempor sollicitudin lacus interdum Donec in enim ut ligula dignissim tempor. Vivamus semper cursus mi, at molestie erat lobortiut. Pellentesque non mi vitae augue egestas vulputate et eu massa. Integer et sem orci. Suspendisse at augue in ipsum convallis semper.\n\n[gallery ids=\"1,2,3,4\"]\n\nNullam vitae libero eros, a fringilla erat. Suspendisse potenti. In dictum bibendum liberoquis facilisis risus malesuada ac. Nulla ullamcorper est ac lectus feugiat scelerisque. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas", 575 | "content_display": "

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed nec consequatnibh. Quisque in consectetur ligula. Praesent pretium massa vitae neque adipiscing vita cursus nulla congue.

\n\"Lorem\n

Cras aliquet ipsum non nisi accumsan tempor sollicitudin lacus interdum Donec in enim ut ligula dignissim tempor. Vivamus semper cursus mi, at molestie erat lobortiut. Pellentesque non mi vitae augue egestas vulputate et eu massa. Integer et sem orci. Suspendisse at augue in ipsum convallis semper.

\n\n
\n\n

Nullam vitae libero eros, a fringilla erat. Suspendisse potenti. In dictum bibendum liberoquis facilisis risus malesuada ac. Nulla ullamcorper est ac lectus feugiat scelerisque. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas

", 576 | "author": [User Object], 577 | "mime_type": "", 578 | "meta": { 579 | "featuredImage": 123456, 580 | "gallery" : [ 581 | { 582 | "ids": [23], 583 | "orderby": [ 584 | "menu_order", 585 | "ID" 586 | ], 587 | "order": "ASC", 588 | …. 589 | }, 590 | …. 591 | ] 592 | }, 593 | "taxonomies": { 594 | "category": [ 595 | [Term Object], 596 | …. 597 | ], 598 | "post_tag": [ 599 | [Term Object], 600 | …. 601 | ], 602 | …. 603 | }, 604 | "media": [ 605 | { 606 | "type": 607 | "id": 123456, 608 | "id_str": ""123445", 609 | "altText": "Lorem ipsum doler set amut.", 610 | "mime_type": "image/jpg", 611 | "sizes": [ 612 | { 613 | "name": "thumbnail", 614 | "width": 100, 615 | "height": 80, 616 | "url": "http://example.com/wp-content/uploads/2012/02/foobar-100x80.jpg" 617 | }, 618 | …. 619 | ] 620 | }, 621 | …. 622 | ] 623 | } 624 | 625 | 626 | 627 | 628 | ##Users 629 | A User represents a single author or user on the site. 630 | ### Methods 631 | #### List 632 | 633 | ##### Request 634 | GET {api root}/users 635 | ##### Parameters 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 679 | 680 | 681 | 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | 692 | 693 | 694 | 697 | 698 | 699 | 700 | 701 | 704 | 705 | 706 | 707 | 708 | 709 | 710 | 711 | 712 | 713 | 714 | 715 | 716 |
ParameterData TypeDescription
Pagination Filters
pagedintegerA positive integer specifiying the page (or subset of results) to return. This filter will automatically determine the offset to use based on the per_page 653 | and paged. Using this filter will cause include_found to be true. 654 |
per_pageintegerThe maximum number of posts to return. The value must range from 1 to MAX_USERS_PER_PAGE.
offsetintegerThe number of posts to skip over before returning the result set.
Ordering Parameters
orderbystringSort the results by the given identifier. Defaults to 'display_name'. Supported values are: 673 |
    674 |
  • 'display_name' - Ordered by the display name of the user.
  • 675 |
  • 'nicename' - The slug/nicename of the user.
  • 676 |
  • 'post_count' - The number of posts the user has.
  • 677 |
678 |
orderstringThe order direction. Options are 'ASC' and 'DESC'. Default is 'DESC'
General Filters
inarray|integerAn array of user ID's to include.
695 | Response Altering Parameters 696 |
include_foundbooleanDefaut to false. When true, the response will include a found rows count. There is some 702 | overhead in generating the total count so this should only be turned on when needed. This is 703 | automatically turned on if the 'paged' filter is used.
whostringFilters to users based on a subset of roles. Currently, only 'authors' is supported.
callbackstringWhen set, the response will be wrapped in a JSONP callback.
717 | 718 | ##### Response 719 | { 720 | 'found': 40, //only provided if include_found == true 721 | "users": [ 722 | [User Object], 723 | …. 724 | ] 725 | } 726 | 727 | 728 | 729 | #### Single Entity 730 | 731 | ##### Request 732 | GET {api root}/users/{id} 733 | 734 | ##### Parameters 735 | 736 | 737 | 738 | 739 | 740 | 741 | 742 | 743 | 744 | 745 | 748 | 749 | 750 | 751 | 752 | 753 | 754 | 755 |
ParameterData TypeDescription
746 | Response Altering Parameters 747 |
callbackstringWhen set, the response will be wrapped in a JSONP callback.
756 | 757 | ##### User JSON Schema 758 | { 759 | "description": "Representation of a single sytem user or author.", 760 | "id": "#user", 761 | "type": "object", 762 | "properties": { 763 | "id_str": { 764 | "description": "The ID of the User object as a string.", 765 | "type": "integer", 766 | "required": true 767 | }, 768 | "id": { 769 | "description": "The ID of the User object.", 770 | "type": "integer", 771 | "required": true 772 | }, 773 | "nicename": { 774 | "description": "The user's slug, or url safe name.", 775 | "type": "string", 776 | "required": false 777 | }, 778 | "display_name": { 779 | "description": "The user's name as shown publicly.", 780 | "type": "string", 781 | "required": false 782 | }, 783 | "posts_url": { 784 | "description": "The URL to the user's posts.", 785 | "type": "string", 786 | "format": "uri", 787 | "required": false 788 | }, 789 | "user_url": { 790 | "description": "The User's personal URL.", 791 | "type": "string", 792 | "required": false 793 | }, 794 | "avatar": { 795 | "description": "An array of images/sizes available for the User's avatar.", 796 | "type": "array", 797 | "items": { 798 | "description": "Image information for a User's Avatar.", 799 | "type": "object", 800 | "properties": { 801 | "height": { 802 | "description": "Height of the image in pixels.", 803 | "type": "integer", 804 | "required": true 805 | }, 806 | "url": { 807 | "description": "Full URL to the image resource.", 808 | "type": "string", 809 | "format": "uri", 810 | "required": true 811 | }, 812 | "width": { 813 | "description": "Width of the image in pixels.", 814 | "type": "integer", 815 | "required": true 816 | } 817 | } 818 | } 819 | }, 820 | "meta": { 821 | "description": "Extended User data.", 822 | "type": "object" 823 | } 824 | } 825 | 826 | } 827 | 828 | 829 | ##### Example User Response 830 | { 831 | "id" : 1234567, 832 | "id_str" : "1234567", 833 | "nicename": "john-doe", 834 | "display_name":"John Doe", 835 | "posts_url": "http://example.com/author/john-doe/", 836 | "user_url": "http://vocecomm.com", 837 | "avatar": [ 838 | { 839 | "url":"http://1.gravatar.com/avatar/7a10459e7210f3bbaf2a75351255d9a3?s=64", 840 | "width":64, 841 | "height":64 842 | }, 843 | …. 844 | ], 845 | "meta":{ 846 | "nickname": "Johnny", 847 | "first_name": "John", 848 | "last_name": "Doe", 849 | "description": "Lorem ipsum dolar set amet." 850 | } 851 | } 852 | 853 | ##Taxonomies 854 | Taxonomies represent the different types of classifications of content. Only public taxonomies can be returned via the API. 855 | ### Methods 856 | #### List 857 | 858 | ##### Request 859 | GET {api root}/taxonomies 860 | ##### Parameters 861 | 862 | 863 | 864 | 865 | 866 | 867 | 868 | 869 | 870 | 871 | 872 | 873 | 874 | 875 | 876 | 877 | 878 | 879 | 880 | 881 | 882 | 883 | 884 | 885 | 886 | 887 | 888 | 889 | 890 | 891 | 892 |
ParameterData TypeDescription
inarray|stringAn array of taxonomy names to include.
post_typearray|stringAn array of post_types to include taxonomies from. Results will include any taxonomies with at least 1 of the given post_types included.
callbackstringWhen set, the response will be wrapped in a JSONP callback.
callbackstringWhen set, the response will be wrapped in a JSONP callback.
893 | 894 | ##### Response 895 | { 896 | "taxonomies": [ 897 | [Taxonomy Object] 898 | …. 899 | ] 900 | } 901 | 902 | 903 | 904 | #### Single Entity 905 | 906 | ##### Request 907 | GET {api root}/taxonomies/{name} 908 | 909 | ##### Parameters 910 | 911 | 912 | 913 | 914 | 915 | 916 | 917 | 918 | 919 | 920 | 923 | 924 | 925 | 926 | 927 | 928 | 929 | 930 |
ParameterData TypeDescription
921 | Response Altering Parameters 922 |
callbackstringWhen set, the response will be wrapped in a JSONP callback.
931 | 932 | ##### Taxonomy JSON Schema 933 | { 934 | "id": "#taxonomy", 935 | "descrption": "A representation of a taxonomy.", 936 | "type": "object", 937 | "properties": { 938 | "name": { 939 | "description": "The name/unique identifier for the taxonomy.", 940 | "type": "string", 941 | "required": true 942 | }, 943 | "post_type": { 944 | "description": "An array of post types the taxony is tied to.", 945 | "type": "array", 946 | "items": { 947 | "description": "The post_type string.", 948 | "type": "string" 949 | } 950 | }, 951 | "hierarchical": { 952 | "description": "Indicates whether the taxonomy is hierarchical or allows parent/child relationships.", 953 | "type": "boolean", 954 | "default": false 955 | }, 956 | "query_var": { 957 | "description": "The query_var tied to this taxonomy. Useful when processing rewrite rules to determine the proper API query.", 958 | "type": "string", 959 | "required": false 960 | }, 961 | "labels": { 962 | "description": "The user displayed name representing the taxonomy.", 963 | "type": "object", 964 | "properties": { 965 | "name": { 966 | "description": "The plural name of the taxonomy.", 967 | "type": "string" 968 | }, 969 | "singularName": { 970 | "description": "The singular name of the taxonomy.", 971 | "type": "string" 972 | } 973 | } 974 | }, 975 | "meta": { 976 | "description": "Extended Taxonomy data.", 977 | "type": "object" 978 | } 979 | } 980 | } 981 | 982 | ##### Example Taxonomy Response 983 | { 984 | "name": "category", 985 | "post_types": [ 986 | "post", 987 | "attachment", 988 | …. 989 | ], 990 | "hierarchical": true, 991 | "queryVar":"category", 992 | "labels": { 993 | "name": "Categories", 994 | "singularName": "Category" 995 | }, 996 | "meta":{ 997 | } 998 | } 999 | 1000 | 1001 | ##Terms 1002 | Terms are individual classifications within a taxonomy. 1003 | ### Methods 1004 | #### List 1005 | 1006 | ##### Request 1007 | GET {api root}/taxonomies/{name}/terms 1008 | 1009 | ##### Parameters 1010 | 1011 | 1012 | 1013 | 1014 | 1015 | 1016 | 1017 | 1018 | 1019 | 1020 | 1021 | 1022 | 1023 | 1024 | 1025 | 1026 | 1029 | 1030 | 1031 | 1032 | 1033 | 1034 | 1035 | 1036 | 1037 | 1038 | 1039 | 1040 | 1041 | 1042 | 1043 | 1044 | 1045 | 1046 | 1053 | 1054 | 1055 | 1056 | 1057 | 1058 | 1059 | 1060 | 1061 | 1062 | 1063 | 1064 | 1065 | 1066 | 1067 | 1068 | 1069 | 1070 | 1071 | 1072 | 1073 | 1074 | 1075 | 1076 | 1077 | 1078 | 1079 | 1080 | 1081 | 1082 | 1083 | 1084 | 1085 | 1086 | 1087 | 1088 | 1091 | 1092 | 1093 | 1094 | 1095 | 1098 | 1099 | 1100 | 1101 | 1102 | 1103 | 1104 | 1105 |
ParameterData TypeDescription
Pagination Filters
pagedintegerA positive integer specifiying the page (or subset of results) to return. This filter will automatically determine the offset to use based on the per_page 1027 | and paged. Using this filter will cause include_found to be true. 1028 |
per_pageintegerThe maximum number of posts to return. The value must range from 1 to MAX_TERMS_PER_PAGE.
offsetintegerThe number of posts to skip over before returning the result set.
Ordering Parameters
orderbystringSort the results by the given identifier. Defaults to 'name'. Supported values are: 1047 |
    1048 |
  • 'name' - The user readable name of the term.
  • 1049 |
  • 'slug' - The slug of the term.
  • 1050 |
  • 'count' - The number of posts the term is connected to.
  • 1051 |
1052 |
orderstringThe order direction. Options are 'ASC' and 'DESC'. Default is 'DESC'
General Filters
inarray|integerAn array of term ID's to include.
slugstringA term slug to include.
parentidInclude the children of the provided term ID.
hide_emptybooleanIf true, only terms with attached posts will be returned. Default is true.
pad_countsbooleanIf true, count all of the children along with the term. Default is false.
1089 | Response Altering Parameters 1090 |
include_foundbooleanDefaut to false. When true, the response will include a found rows count. There is some 1096 | overhead in generating the total count so this should only be turned on when needed. This is 1097 | automatically turned on if the 'paged' filter is used.
callbackstringWhen set, the response will be wrapped in a JSONP callback.
1106 | 1107 | 1108 | ##### Response 1109 | { 1110 | "found": 25, //only provided if include_found == true 1111 | "terms": [ 1112 | [Term Object] 1113 | …. 1114 | ] 1115 | } 1116 | 1117 | 1118 | 1119 | #### Single Entity 1120 | 1121 | ##### Request 1122 | GET {api root}/taxonomies/{name}/terms/{term_id} 1123 | 1124 | ##### Parameters 1125 | 1126 | 1127 | 1128 | 1129 | 1130 | 1131 | 1132 | 1133 | 1134 | 1135 | 1138 | 1139 | 1140 | 1141 | 1142 | 1143 | 1144 | 1145 |
ParameterData TypeDescription
1136 | Response Altering Parameters 1137 |
callbackstringWhen set, the response will be wrapped in a JSONP callback.
1146 | 1147 | #### Term JSON Schema 1148 | { 1149 | "type": "object", 1150 | "required": false, 1151 | "properties": { 1152 | "description": { 1153 | "description": "A long text describing the term.", 1154 | "type": "string", 1155 | "required": false 1156 | }, 1157 | "meta": { 1158 | "description": "Extended Term data.", 1159 | "type": "object", 1160 | }, 1161 | "name": { 1162 | "description": "The title/name of the term as displayed to users.", 1163 | "type": "string", 1164 | "required": false 1165 | }, 1166 | "parent_str": { 1167 | "description": "The ID of the parent term as a string, if exists.", 1168 | "type": "string", 1169 | "required": false 1170 | }, 1171 | "parent": { 1172 | "description": "The ID of the parent term, if exists.", 1173 | "type": "number", 1174 | "required": false 1175 | }, 1176 | "post_count": { 1177 | "description": "The distinct count of posts attached to this term. If 'pad_count' is set to true, this will also include all posts attached to child terms. This only includes posts of type 'post'.", 1178 | "type": "number", 1179 | "required": false 1180 | }, 1181 | "slug": { 1182 | "description": "The name (slug) of the term as used in URLs.", 1183 | "type": "string", 1184 | "required": false 1185 | }, 1186 | "taxonomy": { 1187 | "type": "string", 1188 | "required": false 1189 | }, 1190 | "id_str": { 1191 | "description": "The ID of the term as a string.", 1192 | "type": "string", 1193 | "id": "http://jsonschema.net/term_id_str", 1194 | "required": false 1195 | }, 1196 | "id": { 1197 | "description": "The ID of the term.", 1198 | "type": "number", 1199 | "id": "http://jsonschema.net/term_id", 1200 | "required": false 1201 | }, 1202 | "term_taxonomy_id_str": { 1203 | "description": "The ID that uniquely represents this term/taxonomy as ing asterms are shared across multiple taxonomies.", 1204 | "type": "string", 1205 | "id": "http://jsonschema.net/term_taxonomy_id_str", 1206 | "required": false 1207 | }, 1208 | "term_taxonomy_id": { 1209 | "description": "The ID that uniquely represents this term/taxonomy as terms are shared across multiple taxonomies.", 1210 | "type": "number", 1211 | "id": "http://jsonschema.net/term_taxonomy_id", 1212 | "required": false 1213 | } 1214 | } 1215 | } 1216 | 1217 | ##### Example Term Response 1218 | { 1219 | "id": 123456, 1220 | "term_id_str": "123456", 1221 | "term_taxonomy_id": 123456789, 1222 | "term_taxonomy_id_str": "123456789", 1223 | "parent": 1234567, 1224 | "parent_str": "1234567", 1225 | "name": "Local News", 1226 | "slug": "local-news", 1227 | "taxonomy": "category", 1228 | "description": "News reports from around Polk County", 1229 | "post_count": 25, 1230 | "meta":{ 1231 | } 1232 | } 1233 | 1234 | 1235 | ##Rewrite Rules 1236 | Rewrite Rules can be used to convert internal links in content into API requests. 1237 | ### Methods 1238 | #### List 1239 | 1240 | ##### Request 1241 | GET {api root}/rewrite_rules 1242 | 1243 | #### Rewrite Rules JSON Schema 1244 | { 1245 | "id": "#rewrite_rules", 1246 | "description": "Rewrite Rules represent the URL structure on the hosting API site. Providing these to the client, allows the client to override internal links with sequential API requests.", 1247 | "type": "object", 1248 | "properties": { 1249 | "base_url": { 1250 | "description": "The root URL which all rewrite rules are based.", 1251 | "type": "string", 1252 | "format": "uri", 1253 | "required": true 1254 | }, 1255 | "rewrite_rules": { 1256 | "type": "array", 1257 | "items": { 1258 | "type": "object", 1259 | "properties": { 1260 | "query_expression": { 1261 | "description": "The format string used to build the resulting query parameters for the rewrite rules from the components matched from the regex.", 1262 | "type": "string", 1263 | "required": true 1264 | }, 1265 | "regex": { 1266 | "description": "The regular expression used to break down the requested URL into components.", 1267 | "type": "string", 1268 | "format": "regex", 1269 | "required": true 1270 | } 1271 | } 1272 | } 1273 | 1274 | 1275 | } 1276 | } 1277 | } 1278 | 1279 | ##### Example Rewrite Rules Response 1280 | { 1281 | "base_url": "http://example.com/", 1282 | "rewrite_rules": [ 1283 | { 1284 | "regex": "category/(.+?)/?$", 1285 | "query_expression": "category_name=$1" 1286 | } 1287 | …. 1288 | ] 1289 | } 1290 | 1291 | ## Media Items 1292 | Below are the schemas for media items. 1293 | 1294 | ### Base Media Items JSON Schema 1295 | { 1296 | "description": "Base media object", 1297 | "id": "mediaItem", 1298 | "type": "object", 1299 | "properties": { 1300 | "type": { 1301 | "description": "The subclass of media object being represented.", 1302 | "type": "string", 1303 | "required": true 1304 | } 1305 | } 1306 | 1307 | } 1308 | 1309 | ### Internal Media Item Base JSON Schema 1310 | { 1311 | "description": "An internal media item hosted by this WP instance that is backed by a Post Object", 1312 | "type": "object", 1313 | "id": "#internalMediaItem", 1314 | "extends": { 1315 | "$ref": "#mediaItem" 1316 | }, 1317 | "properties": { 1318 | "idStr": { 1319 | "description": "The ID of the Post object representing this media attachment as a string.", 1320 | "type": "integer", 1321 | "required": true 1322 | }, 1323 | "id": { 1324 | "description": "The ID of the Post object representing this media attachment.", 1325 | "type": "integer", 1326 | "required": true 1327 | }, 1328 | "mimeType": { 1329 | "description": "The mime type of the attched media object", 1330 | "type": "string", 1331 | "required": true 1332 | }, 1333 | } 1334 | } 1335 | 1336 | ### Internal Image Media Item JSON Schema 1337 | { 1338 | "description": "An internal image item hosted by this WP instance", 1339 | "type": "object", 1340 | "id": "#internalMediaItem", 1341 | "extends": { 1342 | "$ref": "#mediaItem" 1343 | }, 1344 | "properties": { 1345 | "altText": { 1346 | "description": "The alternate text for the image. Maps to post_meta key '_wp_attachment_image_alt'.", 1347 | "type": "string", 1348 | "required": false 1349 | }, 1350 | "sizes": { 1351 | "description": "Listing of available sizes of the image allowing the proper size to be used for the client device.", 1352 | "type": "array", 1353 | "required": true, 1354 | "items": { 1355 | "type": "object", 1356 | "required": true, 1357 | "properties": { 1358 | "height": { 1359 | "description": "Height of the image in pixels.", 1360 | "type": "integer", 1361 | "required": true 1362 | }, 1363 | "name": { 1364 | "description": "The identifier for the size that generated this size of the image.", 1365 | "type": "string", 1366 | "required": true 1367 | }, 1368 | "url": { 1369 | "description": "Full URL to the image resource.", 1370 | "type": "string", 1371 | "format": "uri", 1372 | "required": true 1373 | }, 1374 | "width": { 1375 | "description": "Width of the image in pixels.", 1376 | "type": "integer", 1377 | "required": true 1378 | } 1379 | } 1380 | } 1381 | 1382 | 1383 | } 1384 | } 1385 | } 1386 | 1387 | 1388 | ## Comments 1389 | A comment represents a user response to a post 1390 | 1391 | ### Methods 1392 | #### List 1393 | 1394 | ##### Request 1395 | GET {api root}/comments 1396 | 1397 | GET {api root}/posts/{#post_id}/comments 1398 | ##### Parameters 1399 | 1400 | 1401 | 1402 | 1403 | 1404 | 1405 | 1406 | 1407 | 1408 | 1409 | 1410 | 1413 | 1414 | 1415 | 1416 | 1417 | 1419 | 1420 | 1421 | 1422 | 1423 | 1425 | 1426 | 1427 | 1428 | 1429 | 1430 | 1431 | 1432 | 1436 | 1437 | 1438 | 1439 | 1440 | 1441 | 1442 | 1443 | 1446 | 1447 | 1448 | 1449 | 1450 | 1451 | 1452 | 1453 | 1454 | 1455 | 1456 | 1457 | 1458 | 1459 | 1460 | 1461 | 1462 | 1463 | 1477 | 1478 | 1479 | 1480 | 1481 | 1482 | 1483 | 1484 | 1485 | 1486 | 1487 | 1488 | 1489 | 1490 | 1491 | 1492 | 1493 | 1494 | 1495 | 1496 | 1497 | 1498 | 1499 | 1500 | 1501 | 1502 | 1503 | 1504 | 1505 | 1506 | 1507 | 1508 | 1509 | 1510 | 1511 | 1512 | 1513 | 1514 | 1515 | 1516 | 1517 | 1518 | 1519 | 1520 | 1521 | 1522 | 1525 | 1526 | 1527 | 1528 | 1529 | 1532 | 1533 | 1534 | 1535 | 1536 | 1537 | 1538 | 1539 |
ParameterData TypeDescription
1411 | Date Filters 1412 |
before stringA parsable formatted date string. Unless specified in the format used, 1418 | the result will be relative to the timezone of the site.
after stringA parsable formatted date string. Unless specified in the format used, 1424 | the result will be relative to the timezone of the site.
Search Filtering
sstring 1433 | Search keyword or string, by default this searches against the author, author email, author url, author ip, and content. 1434 | The search looks for a match to the entire search expression. 1435 |
Pagination Filters
pagedintegerA positive integer specifiying the page (or subset of results) to return. This filter will automatically determine the offset to use based on the per_page 1444 | and paged arguments. Using this filter will cause include_found to be true. 1445 |
per_pageintegerThe maximum number of posts to return. The value must range from 1 to MAX_COMMENTS_PER_PAGE.
offsetintegerThe number of posts to skip over before returning the result set.
Ordering Parameters
orderbyarray|stringSort the results by the given identifier. Defaults to 'date'. Supported values are: 1464 |
    1465 |
  • 'comment_date_gmt' - (Default) The GMT date of the post.
  • 1466 |
  • 'comment_ID' - The ID of the post.
  • 1467 |
  • 'comment_author' - The value of the author ID.
  • 1468 |
  • 'comment_date' - The date of the comment..
  • 1469 |
  • 'comment_type' - The type of comment.
  • 1470 |
  • 'comment parent'- The ID of the comment's parent
  • 1471 |
  • 'comment_post_ID' - The ID of the post which the comment belongs.
  • 1472 |
  • 'user_id' - The ID of the user making the comments.
  • 1473 |
1474 | 1475 | Orderby will also accept an array of multiple identifiers. 1476 |
orderstringThe order direction. Options are 'ASC' and 'DESC'. Default is 'DESC'
General Filters
ininteger|arrayArray of Ids of comments to include.
parentintegerID of the parent comment to pull from.
post_idintegerID of post from which to pull comments.
post_namestringSlug/Name of the post from which to pull comments.
typestringThe type of comments to return. Default options: 'comment', 'pingback', 'trackback', 'pings' (returns trackbacks and pingbacks').
statusstringThe status of comments to return. Default: 'approved'.
user_idintUser ID of commentor making the comments.
1523 | Response Altering Parameters 1524 |
include_foundbooleanDefaut to false. When true, the response will include a found rows count. There is some 1530 | overhead in generating the total count so this should only be turned on when needed. This is 1531 | automatically turned on if the 'paged' filter is used.
callbackstringWhen set, the response will be wrapped in a JSONP callback.
1540 | 1541 | 1542 | 1543 | ##### Response 1544 | { 1545 | 'found': 40, //only provided if include_found == true 1546 | "comments": [ 1547 | [Comment Object], 1548 | …. 1549 | ] 1550 | } 1551 | 1552 | 1553 | 1554 | #### Single Entity 1555 | 1556 | ##### Request 1557 | GET {api root}/comments/{id} 1558 | 1559 | ##### Parameters 1560 | 1561 | 1562 | 1563 | 1564 | 1565 | 1566 | 1567 | 1568 | 1569 | 1570 | 1573 | 1574 | 1575 | 1576 | 1577 | 1578 | 1579 | 1580 |
ParameterData TypeDescription
1571 | Response Altering Parameters 1572 |
callbackstringWhen set, the response will be wrapped in a JSONP callback.
1581 | 1582 | ##### Comment JSON Schema 1583 | { 1584 | "title": "Comment Object", 1585 | "description": "A representation of a single post object", 1586 | "type": "object", 1587 | "id": "#comment", 1588 | "properties": { 1589 | "author": { 1590 | "description": "Display name of the author of the comment.", 1591 | "type": "string", 1592 | "required": true 1593 | } 1594 | "author_url": { 1595 | "description": "URL set for the author of the comment.", 1596 | "type": "string", 1597 | "required": false 1598 | } 1599 | "date": { 1600 | "description": "The comment's creation time in iso 8601 format.", 1601 | "type": "string", 1602 | "format": "date-time", 1603 | "required": true 1604 | }, 1605 | "content": { 1606 | "description": "The raw comment content.", 1607 | "type": "string", 1608 | "required": true 1609 | }, 1610 | "content_display": { 1611 | "description": "Display formatted content of the comment.", 1612 | "type": "string", 1613 | "required": true 1614 | }, 1615 | "user": { 1616 | "description": "ID of the user making the comment", 1617 | "type": "integer", 1618 | "required": false 1619 | }, 1620 | "user_id_str": { 1621 | "description": "String version of the ID of the user making the comment", 1622 | "type": "string", 1623 | "required": false 1624 | }, 1625 | "id_str": { 1626 | "description": "The ID of the post represented as a string.", 1627 | "type": "string", 1628 | "required": true 1629 | }, 1630 | "id": { 1631 | "description": "The ID of the post", 1632 | "type": "integer", 1633 | "minimum": 1, 1634 | "required": true 1635 | }, 1636 | "type": { 1637 | "description": "The type of comment. Deafult enum: 'comment', 'pingback', 'trackback'", 1638 | "type": "string", 1639 | "required": true 1640 | }, 1641 | "media": { 1642 | "type": "array", 1643 | "required": false, 1644 | "items": { 1645 | "type": { 1646 | "$ref": "#mediaItem" 1647 | } 1648 | } 1649 | }, 1650 | "parent_str": { 1651 | "description": "The ID of the comment's parent as a string, if it has one.", 1652 | "type": "string", 1653 | "required": false 1654 | }, 1655 | "parent": { 1656 | "description": "The ID of the comment's parent as a string, if it has one.", 1657 | "type": "integer", 1658 | "required": false 1659 | }, 1660 | "status": { 1661 | "description": "The status of the comment.", 1662 | "type": { 1663 | "enum": ["approve", "pending", "spam", "trash"] 1664 | }, 1665 | "required": true 1666 | } 1667 | } 1668 | } 1669 | 1670 | ##### Example Comment Response 1671 | { 1672 | "id": 597, 1673 | "id_str": "597", 1674 | "type": "comment", 1675 | "author": "John Doe", 1676 | "author_url": "http://example.org", 1677 | "parent": 0, 1678 | "parent_str": "0", 1679 | "date": "2013-06-11T18:39:46+00:00", 1680 | "content": "This is my comment text", 1681 | "status": "approve", 1682 | "user": 1, 1683 | "user_id_str": "1", 1684 | "content_display": "

This is my comment text<\/p>\n", 1685 | "avatar": [ 1686 | { 1687 | "url": "http:\/\/1.gravatar.com\/avatar\/96614ec98aa0c0d2ee75796dced6df54?s=96&d=http%3A%2F%2F1.gravatar.com%2Favatar%2Fad516503a11cd5ca435acc9bb6523536%3Fs%3D96&r=G", 1688 | "width": 96, 1689 | "height": 96 1690 | } 1691 | ] 1692 | } 1693 | --------------------------------------------------------------------------------