├── .editorconfig ├── .gitignore ├── .scrutinizer.yml ├── .travis.yml ├── README.md ├── bin └── install-wp-tests.sh ├── composer.json ├── lib ├── class-react.php └── class-wp-rest-react-controller.php ├── license.txt ├── phpunit.xml ├── react.php ├── static ├── emoji-raw.json ├── emoji.json ├── react.css └── react.js ├── tests ├── bootstrap.php ├── class-wp-test-rest-controller-testcase.php ├── class-wp-test-rest-testcase.php ├── class-wp-test-spy-rest-server.php ├── test-frontend.php └── test-rest-reactions-controller.php └── tools └── compile_emoji.php /.editorconfig: -------------------------------------------------------------------------------- 1 | # This file is for unifying the coding style for different editors and IDEs 2 | # editorconfig.org 3 | 4 | # WordPress Coding Standards 5 | # http://make.wordpress.org/core/handbook/coding-standards/ 6 | 7 | root = true 8 | 9 | [*] 10 | charset = utf-8 11 | end_of_line = lf 12 | insert_final_newline = true 13 | trim_trailing_whitespace = true 14 | indent_style = tab 15 | 16 | [{.jshintrc,*.json,*.yml}] 17 | indent_style = space 18 | indent_size = 2 19 | 20 | [{*.txt,wp-config-sample.php}] 21 | end_of_line = crlf 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # General 2 | .DS_Store 3 | composer.lock 4 | 5 | # Vendor (e.g. Composer) 6 | vendor/* 7 | !vendor/.gitkeep -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | # Scrutinizer Configuration File 2 | 3 | tools: 4 | external_code_coverage: false 5 | 6 | php_sim: 7 | enabled: true 8 | min_mass: 50 9 | 10 | php_pdepend: 11 | enabled: true 12 | configuration_file: null 13 | suffixes: 14 | - php 15 | excluded_dirs: { } 16 | 17 | php_analyzer: 18 | enabled: true 19 | extensions: 20 | - php 21 | dependency_paths: { } 22 | path_configs: { } 23 | 24 | php_changetracking: 25 | enabled: true 26 | bug_patterns: 27 | - '\bfix(?:es|ed)?\b' 28 | feature_patterns: 29 | - '\badd(?:s|ed)?\b' 30 | - '\bimplement(?:s|ed)?\b' 31 | 32 | php_hhvm: 33 | enabled: true 34 | command: hhvm 35 | extensions: 36 | - php 37 | path_configs: { } 38 | 39 | php_code_sniffer: 40 | config: 41 | standard: 'WordPress' 42 | 43 | before_commands: { } 44 | after_commands: { } 45 | artifacts: { } 46 | build_failure_conditions: { } 47 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | matrix: 4 | include: 5 | - php: 5.2 6 | env: WP_VERSION=nightly WP_MULTISITE=0 7 | - php: 5.2 8 | env: WP_VERSION=nightly WP_MULTISITE=1 9 | - php: 5.3 10 | env: WP_VERSION=nightly WP_MULTISITE=0 11 | - php: 5.3 12 | env: WP_VERSION=nightly WP_MULTISITE=1 13 | - php: 5.4 14 | env: WP_VERSION=nightly WP_MULTISITE=0 15 | - php: 5.4 16 | env: WP_VERSION=nightly WP_MULTISITE=1 17 | - php: 5.5 18 | env: WP_VERSION=nightly WP_MULTISITE=0 19 | - php: 5.5 20 | env: WP_VERSION=nightly WP_MULTISITE=1 21 | - php: 5.6 22 | env: WP_VERSION=nightly WP_MULTISITE=0 23 | - php: 5.6 24 | env: WP_VERSION=nightly WP_MULTISITE=1 25 | - php: 5.6 26 | env: WP_VERSION=nightly WP_MULTISITE=0 DB=MySQL5.1 27 | - php: 7.0 28 | env: WP_VERSION=nightly WP_MULTISITE=0 29 | - php: 7.0 30 | env: WP_VERSION=nightly WP_MULTISITE=1 31 | - php: hhvm 32 | env: WP_VERSION=nightly WP_MULTISITE=0 33 | - php: hhvm 34 | env: WP_VERSION=nightly WP_MULTISITE=1 35 | 36 | allow_failures: 37 | - php: hhvm 38 | 39 | addons: 40 | apt: 41 | packages: 42 | - perl 43 | 44 | # Clones WordPress and configures our testing environment. 45 | before_script: 46 | - phpenv local 5.5 47 | - composer install --no-interaction 48 | - phpenv local --unset 49 | 50 | - > 51 | if [[ "$DB" == "MySQL5.1" ]]; then 52 | bash bin/install-wp-tests.sh wordpress_test travis travis 127.0.0.1:3310 $WP_VERSION 53 | else 54 | bash bin/install-wp-tests.sh wordpress_test root '' 127.0.0.1 $WP_VERSION 55 | fi 56 | 57 | script: 58 | - phpunit --coverage-clover=coverage.xml 59 | 60 | after_success: 61 | - bash <(curl -s https://codecov.io/bash) 62 | 63 | notifications: 64 | slack: 65 | rooms: 66 | secure: m8wmVqRIg/S7oH5GDDmzyavlrzbQv0cX0JG9Yo8LKLNelcHEZj95a7Qvmwf/hIKwCgPfeFdq/Gy8BnQDRmfpZ255J7S/JoYIRCGcJ6CVv/EY0ijP5617+cgmHoj4TgApn3+wzkh2sfckJqI161mmrGnJM2ZQTi/NZXH8fKkcmTjSKa0Bo/GqwJS/o0SLfQuSlbRWIFb7iYmAQPCl4Aj1v/HnVX7AjJPKqmytcvWUdTTSRbEma3MufD7KgTQmdMASbd72pEevTIsaohvRnV1mM8niRD254ny31hKSBvoNW2dg/hD0a0a89i70AsIzvCQ5/d1adLCzbDPwQA0MTw7/3AMp9x3BcKy88MA/LdiIvh7xUXL7zJ+v0pDbRBBzvRT++WXEUSAZfh6XfuvxScNJE0dAjLbylksEFA6VW2A23ojJ0F6wbZqt5W04WZh56wkwWF8xE+441psfC2hDzGuqsZFTdViZt0Z4pYg+sCjt6rKCZs9/qLT3AtrYsuqHm4Tmt0Jc5oagXje25kuq04L6iWSqlOnaV8QBajRKgX2TSLNSrwLf9BxysG/Ef3JuO3LpjQGUMlJiigymhZRV3DbmcvePFu5hVHcIitoQjgUY11eVil69y32WGvLGAu3X1nzbpsm6+43M/ebR2y0OJDOqTJ5aC1gqI5kVvYwmnillkiw= 67 | on_start: never 68 | on_failure: always 69 | on_success: change 70 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Reactions # 2 | Contributors: pento 3 | Tags: emoji, reactions, comments 4 | Requires at least: 4.4 5 | Tested up to: 4.5-trunk 6 | Stable tag: trunk 7 | License: GPLv2 or later 8 | License URI: http://www.gnu.org/licenses/gpl-2.0.html 9 | 10 | 💩 reactions. 11 | 12 | ## Description ## 13 | 14 | 💩 reactions. 15 | 16 | It's what you've always dreamed of. 17 | 18 | Additional information at https://make.wordpress.org/core/2016/03/07/reactions/ 19 | 20 | [![Build Status](https://travis-ci.org/pento/react.svg?branch=master)](https://travis-ci.org/pento/react) 21 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/pento/reactions/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/pento/reactions/?branch=master) 22 | [![codecov.io](https://codecov.io/github/pento/react/coverage.svg?branch=master)](https://codecov.io/github/pento/react?branch=master) 23 | 24 | ## Changelog ## 25 | 26 | ### 0.1.0 ### 27 | 28 | First release. 29 | -------------------------------------------------------------------------------- /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-latest} 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_wp() { 20 | mkdir -p $WP_CORE_DIR 21 | 22 | if [ $WP_VERSION == 'latest' ]; then 23 | local ARCHIVE_NAME='latest' 24 | elif [ $WP_VERSION == 'nightly' ]; then 25 | local ARCHIVE_NAME='nightly-builds/wordpress-latest' 26 | else 27 | local ARCHIVE_NAME="wordpress-$WP_VERSION" 28 | fi 29 | 30 | TMP_EXTRACT=$(mktemp -d /tmp/wp-XXXXX) 31 | 32 | wget -nv -O /tmp/wordpress.zip https://wordpress.org/${ARCHIVE_NAME}.zip 33 | unzip /tmp/wordpress.zip -d $TMP_EXTRACT 34 | DIRS=($TMP_EXTRACT/*) 35 | mv ${DIRS[@]:0:1}/* $WP_CORE_DIR 36 | rm -r $TMP_EXTRACT 37 | 38 | wget -nv -O $WP_CORE_DIR/wp-content/db.php https://raw.github.com/markoheijnen/wp-mysqli/master/db.php 39 | } 40 | 41 | install_test_suite() { 42 | # portable in-place argument for both GNU sed and Mac OSX sed 43 | if [[ $(uname -s) == 'Darwin' ]]; then 44 | local ioption='-i .bak' 45 | else 46 | local ioption='-i' 47 | fi 48 | 49 | # set up testing suite 50 | mkdir -p $WP_TESTS_DIR 51 | cd $WP_TESTS_DIR 52 | svn co --quiet https://develop.svn.wordpress.org/trunk/tests/phpunit/includes/ 53 | 54 | wget -nv -O wp-tests-config.php https://develop.svn.wordpress.org/trunk/wp-tests-config-sample.php 55 | sed $ioption "s:dirname( __FILE__ ) . '/src/':'$WP_CORE_DIR':" wp-tests-config.php 56 | sed $ioption "s/youremptytestdbnamehere/$DB_NAME/" wp-tests-config.php 57 | sed $ioption "s/yourusernamehere/$DB_USER/" wp-tests-config.php 58 | sed $ioption "s/yourpasswordhere/$DB_PASS/" wp-tests-config.php 59 | sed $ioption "s|localhost|${DB_HOST}|" wp-tests-config.php 60 | } 61 | 62 | install_db() { 63 | if [[ "$DB" == "MySQL5.1" ]]; then 64 | curl -L https://cpanmin.us | perl - App::cpanminus 65 | 66 | export PERL5LIB=~/perl5/lib/perl5 67 | export PATH=~/perl5/bin:$PATH 68 | 69 | cpanm MySQL::Sandbox 70 | 71 | local SANDBOX_FILE=mysql-5.1.72-linux-x86_64-glibc23.tar.gz 72 | 73 | wget http://downloads.mysql.com/archives/get/file/"$SANDBOX_FILE" 74 | 75 | make_sandbox "$SANDBOX_FILE" -- -u travis -p travis -P 3310 -d msb --no_confirm 76 | fi 77 | 78 | # parse DB_HOST for port or socket references 79 | local PARTS=(${DB_HOST//\:/ }) 80 | local DB_HOSTNAME=${PARTS[0]}; 81 | local DB_SOCK_OR_PORT=${PARTS[1]}; 82 | local EXTRA="" 83 | 84 | if ! [ -z $DB_HOSTNAME ] ; then 85 | if [[ "$DB_SOCK_OR_PORT" =~ ^[0-9]+$ ]] ; then 86 | EXTRA=" --host=$DB_HOSTNAME --port=$DB_SOCK_OR_PORT --protocol=tcp" 87 | elif ! [ -z $DB_SOCK_OR_PORT ] ; then 88 | EXTRA=" --socket=$DB_SOCK_OR_PORT" 89 | elif ! [ -z $DB_HOSTNAME ] ; then 90 | EXTRA=" --host=$DB_HOSTNAME --protocol=tcp" 91 | fi 92 | fi 93 | 94 | # create database 95 | mysqladmin create $DB_NAME --user="$DB_USER" --password="$DB_PASS"$EXTRA 96 | } 97 | 98 | install_wp 99 | install_test_suite 100 | install_db -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pento/reactions", 3 | "description": "💩 reactions", 4 | "version": "0.1.0", 5 | "type": "wordpress-plugin", 6 | "license": "GPL-2.0+", 7 | "require": { 8 | "composer/installers": "~1.0" 9 | }, 10 | "require-dev": { 11 | "wp-api/wp-api": "dev-develop" 12 | }, 13 | "extra": { 14 | "installer-paths": { 15 | "vendor/{$name}": [ 16 | "wp-api/wp-api" 17 | ] 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /lib/class-react.php: -------------------------------------------------------------------------------- 1 | api = new WP_REST_React_Controller(); 20 | 21 | add_action( 'rest_api_init', array( $this->api, 'register_routes' ) ); 22 | 23 | if ( is_admin() ) { 24 | return; 25 | } 26 | 27 | $this->enqueue(); 28 | 29 | add_action( 'wp_head', array( $this, 'print_settings' ) ); 30 | add_action( 'wp_footer', array( $this, 'print_selector' ) ); 31 | 32 | add_filter( 'the_content', array( $this, 'the_content' ) ); 33 | } 34 | 35 | /** 36 | * Initialises the reactions. 37 | * 38 | * @return React Static instance of the React class. 39 | */ 40 | public static function init() { 41 | static $instance; 42 | 43 | if ( ! $instance ) { 44 | $instance = new React; 45 | } 46 | 47 | return $instance; 48 | } 49 | 50 | /** 51 | * Print the JavaScript settings. 52 | */ 53 | public function print_settings() { 54 | ?> 55 | 63 | $post_id, 88 | 'type' => 'reaction', 89 | ) ); 90 | 91 | $reactions_summary = array(); 92 | foreach ( $reactions as $reaction ) { 93 | if ( ! isset( $reactions_summary[ $reaction->comment_content ] ) ) { 94 | $reactions_summary[ $reaction->comment_content ] = 0; 95 | } 96 | 97 | $reactions_summary[ $reaction->comment_content ]++; 98 | } 99 | 100 | $content .= '
'; 101 | 102 | foreach ( $reactions_summary as $emoji => $count ) { 103 | $content .= "
$emoji
$count
"; 104 | } 105 | 106 | if ( comments_open( $post_id ) ) { 107 | /* translators: This is the emoji used for the "Add new emoji reaction" button */ 108 | $content .= "
" . __( '😃+', 'react' ) . '
'; 109 | } 110 | $content .= '
'; 111 | return $content; 112 | } 113 | 114 | public function print_selector() { 115 | ?> 116 | 136 | namespace = 'wp/v2'; 26 | $this->rest_base = 'react'; 27 | } 28 | 29 | /** 30 | * Register the routes for the objects of the controller. 31 | */ 32 | public function register_routes() { 33 | register_rest_route( $this->namespace, $this->rest_base, array( 34 | array( 35 | 'methods' => WP_Rest_Server::READABLE, 36 | 'callback' => array( $this, 'get_items' ), 37 | 'permission_callback' => array( $this, 'get_items_permissions_check' ), 38 | 'args' => $this->get_collection_params(), 39 | ), 40 | array( 41 | 'methods' => WP_Rest_Server::CREATABLE, 42 | 'callback' => array( $this, 'create_item' ), 43 | 'permission_callback' => array( $this, 'create_item_permissions_check' ), 44 | 'args' => $this->get_creation_params(), 45 | ), 46 | 'schema' => array( $this, 'get_public_item_schema' ), 47 | ) ); 48 | } 49 | 50 | /** 51 | * Check if a given request has access to read reactions. 52 | * 53 | * @param WP_REST_Request $request Full details about the request. 54 | * @return WP_Error|boolean 55 | */ 56 | public function get_items_permissions_check( $request ) { 57 | if ( ! empty( $request['post'] ) ) { 58 | foreach ( (array) $request['post'] as $post_id ) { 59 | $post = get_post( $post_id ); 60 | if ( ! empty( $post_id ) && $post && ! $this->check_read_post_permission( $post ) ) { 61 | return new WP_Error( 'rest_cannot_read_post', __( 'Sorry, you cannot read the post for this reaction.', 'react' ), array( 'status' => rest_authorization_required_code() ) ); 62 | } else if ( 0 === $post_id && ! current_user_can( 'moderate_comments' ) ) { 63 | return new WP_Error( 'rest_cannot_read', __( 'Sorry, you cannot read reactions without a post.', 'react' ), array( 'status' => rest_authorization_required_code() ) ); 64 | } 65 | } 66 | } 67 | 68 | return true; 69 | } 70 | 71 | /** 72 | * Get a list of reactions. 73 | * 74 | * @param WP_REST_Request $request Full details about the request. 75 | * @return WP_Error|WP_REST_Response 76 | */ 77 | public function get_items( $request ) { 78 | $prepared_args = array( 79 | 'post__in' => $request['post'], 80 | 'type' => 'reaction', 81 | ); 82 | 83 | /** 84 | * Filter arguments, before passing to WP_Comment_Query, when querying reactions via the REST API. 85 | * 86 | * @see https://developer.wordpress.org/reference/classes/wp_comment_query/ 87 | * 88 | * @param array $prepared_args Array of arguments for WP_Comment_Query. 89 | * @param WP_REST_Request $request The current request. 90 | */ 91 | $prepared_args = apply_filters( 'rest_reaction_query', $prepared_args, $request ); 92 | 93 | $query = new WP_Comment_Query; 94 | $query_result = $query->query( $prepared_args ); 95 | 96 | $reactions_count = array(); 97 | foreach ( $query_result as $reaction ) { 98 | if ( empty( $reactions_count[ $reaction->comment_content ] ) ) { 99 | $reactions_count[ $reaction->comment_content ] = array( 100 | 'count' => 0, 101 | 'post_id' => $reaction->comment_post_ID, 102 | ); 103 | } 104 | 105 | $reactions_count[ $reaction->comment_content ]++; 106 | } 107 | 108 | $reactions = array(); 109 | foreach ( $reactions_count as $emoji => $data ) { 110 | $reaction = array( 111 | 'emoji' => $emoji, 112 | 'count' => $data['count'], 113 | 'post_id' => $data['post_id'], 114 | ); 115 | 116 | $data = $this->prepare_item_for_response( $reaction, $request ); 117 | $reactions[] = $this->prepare_response_for_collection( $data ); 118 | } 119 | 120 | $total_reactions = (int) $query->found_comments; 121 | $reaction_groups = count( $reactions ); 122 | 123 | $response = rest_ensure_response( $reactions ); 124 | $response->header( 'X-WP-Total', $total_reactions ); 125 | $response->header( 'X-WP-TotalGroups', $reaction_groups ); 126 | 127 | return $response; 128 | } 129 | 130 | /** 131 | * Check if a given request has access to create a reaction 132 | * 133 | * @param WP_REST_Request $request Full details about the request. 134 | * @return WP_Error|boolean 135 | */ 136 | public function create_item_permissions_check( $request ) { 137 | if ( ! empty( $request['post'] ) && $post = get_post( (int) $request['post'] ) ) { 138 | if ( ! $this->check_read_post_permission( $post ) ) { 139 | return new WP_Error( 'rest_cannot_read_post', __( 'Sorry, you cannot read the post for this reaction.', 'react' ), array( 'status' => rest_authorization_required_code() ) ); 140 | } 141 | 142 | if ( ! comments_open( $post->ID ) ) { 143 | return new WP_Error( 'rest_reactions_closed', __( 'Sorry, reactions are closed on this post.', 'react' ), array( 'status' => 403 ) ); 144 | } 145 | } 146 | return true; 147 | } 148 | 149 | /** 150 | * Create a reaction. 151 | * 152 | * @param WP_REST_Request $request Full details about the request. 153 | * @return WP_Error|WP_REST_Response 154 | */ 155 | public function create_item( $request ) { 156 | $comment = array( 157 | 'comment_content' => $request['emoji'], 158 | 'comment_post_ID' => $request['post'], 159 | 'comment_type' => 'reaction', 160 | ); 161 | 162 | wp_insert_comment( $comment ); 163 | 164 | return $this->get_items( $request ); 165 | } 166 | 167 | /** 168 | * Check if we can read a post. 169 | * 170 | * Correctly handles posts with the inherit status. 171 | * 172 | * @param object $post Post object. 173 | * @return boolean Can we read it? 174 | */ 175 | public function check_read_post_permission( $post ) { 176 | $posts_controller = new WP_REST_Posts_Controller( $post->post_type ); 177 | 178 | return $posts_controller->check_read_permission( $post ); 179 | } 180 | 181 | /** 182 | * Prepare a reaction group output for response. 183 | * 184 | * @param array $reaction Reaction data. 185 | * @param WP_REST_Request $request Request object. 186 | * @return WP_REST_Response $response 187 | */ 188 | public function prepare_item_for_response( $reaction, $request ) { 189 | $data = array( 190 | 'emoji' => $reaction['emoji'], 191 | 'count' => (int) $reaction['count'], 192 | 'post_id' => (int) $reaction['post_id'], 193 | ); 194 | 195 | // Wrap the data in a response object 196 | $response = rest_ensure_response( $data ); 197 | 198 | $response->add_links( $this->prepare_links( $reaction ) ); 199 | 200 | /** 201 | * Filter a reaction group returned from the API. 202 | * 203 | * Allows modification of the reaction right before it is returned. 204 | * 205 | * @param WP_REST_Response $response The response object. 206 | * @param array $reaction The original reaction data. 207 | * @param WP_REST_Request $request Request used to generate the response. 208 | */ 209 | return apply_filters( 'rest_prepare_comment', $response, $reaction, $request ); 210 | } 211 | 212 | /** 213 | * Prepare a response for inserting into a collection. 214 | * 215 | * @param WP_REST_Response $response Response object. 216 | * @return array Response data, ready for insertion into collection data. 217 | */ 218 | public function prepare_response_for_collection( $response ) { 219 | if ( ! ( $response instanceof WP_REST_Response ) ) { 220 | return $response; 221 | } 222 | 223 | $data = (array) $response->get_data(); 224 | $links = WP_REST_Server::get_response_links( $response ); 225 | if ( ! empty( $links ) ) { 226 | $data['_links'] = $links; 227 | } 228 | 229 | return $data; 230 | } 231 | 232 | /** 233 | * Prepare links for the request. 234 | * 235 | * @param array $reaction Reaction. 236 | * @return array Links for the given reaction. 237 | */ 238 | protected function prepare_links( $reaction ) { 239 | $links = array( 240 | 'self' => array( 241 | 'href' => rest_url( sprintf( '/%s/%s/%s', $this->namespace, $this->rest_base, $reaction['emoji'] ) ), 242 | ), 243 | 'collection' => array( 244 | 'href' => rest_url( sprintf( '/%s/%s', $this->namespace, $this->rest_base ) ), 245 | ), 246 | ); 247 | 248 | if ( 0 !== (int) $reaction['post_id'] ) { 249 | $post = get_post( $reaction['post_id'] ); 250 | if ( ! empty( $post->ID ) ) { 251 | $obj = get_post_type_object( $post->post_type ); 252 | $base = ! empty( $obj->rest_base ) ? $obj->rest_base : $obj->name; 253 | $links['up'] = array( 254 | 'href' => rest_url( '/wp/v2/' . $base . '/' . $reaction['post_id'] ), 255 | 'embeddable' => true, 256 | 'post_type' => $post->post_type, 257 | ); 258 | } 259 | } 260 | 261 | return $links; 262 | } 263 | 264 | /** 265 | * Get the query params for collections 266 | * 267 | * @return array 268 | */ 269 | public function get_collection_params() { 270 | $query_params = array(); 271 | 272 | $query_params['post'] = array( 273 | 'default' => array(), 274 | 'description' => __( 'Limit result set to resources assigned to specific post ids.', 'react' ), 275 | 'type' => 'array', 276 | 'sanitize_callback' => 'wp_parse_id_list', 277 | 'validate_callback' => 'rest_validate_request_arg', 278 | ); 279 | 280 | return $query_params; 281 | } 282 | /** 283 | * Get the query params for collections 284 | * 285 | * @return array 286 | */ 287 | public function get_creation_params() { 288 | $query_params = array(); 289 | 290 | $query_params['post'] = array( 291 | 'default' => array(), 292 | 'description' => __( 'The post ID to add a reaction to.', 'react' ), 293 | 'type' => 'integer', 294 | 'sanitize_callback' => 'absint', 295 | 'validate_callback' => 'rest_validate_request_arg', 296 | ); 297 | 298 | $query_params['emoji'] = array( 299 | 'default' => array(), 300 | 'description' => __( 'The reaction emoji.', 'react' ), 301 | 'type' => 'string', 302 | 'validate_callback' => 'rest_validate_request_arg', 303 | ); 304 | 305 | return $query_params; 306 | } 307 | } -------------------------------------------------------------------------------- /license.txt: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 9 | 10 | 11 | tests/ 12 | 13 | 14 | 15 | 16 | 17 | . 18 | 19 | 20 | ./react.php 21 | ./lib 22 | 23 | 24 | -------------------------------------------------------------------------------- /react.php: -------------------------------------------------------------------------------- 1 |
'; 122 | html += character; 123 | html += '
'; 124 | } 125 | 126 | tab.innerHTML = html; 127 | } 128 | } 129 | 130 | /** 131 | * Displays the emoji selector 132 | * 133 | * @param HtmlElement el The button that was clicked 134 | */ 135 | var showReactionPopup = function( el ) { 136 | var left = 0, top = 0, 137 | parent; 138 | 139 | populateReactionPopup(); 140 | 141 | popup.dataset.post = el.dataset.post; 142 | 143 | if ( document.documentElement.clientWidth > 768 ) { 144 | parent = el; 145 | 146 | while ( parent ) { 147 | left += parent.offsetLeft; 148 | top += parent.offsetTop; 149 | parent = parent.offsetParent; 150 | } 151 | 152 | top -= 300; 153 | 154 | popup.style.left = left + 'px'; 155 | popup.style.top = top + 'px'; 156 | } 157 | 158 | changeReactionTab( 0 ); 159 | 160 | popup.style.display = 'block'; 161 | }; 162 | 163 | /** 164 | * Hide the reaction popup. 165 | */ 166 | var hideReactionPopup = function() { 167 | if ( popup && 'none' !== popup.style.display ) { 168 | popup.style.display = 'none'; 169 | } 170 | } 171 | 172 | /** 173 | * Switch to a different tab in the reactions popup. 174 | * 175 | * @param int tab_number The tab number to switch to. 176 | */ 177 | var changeReactionTab = function( tab_number ) { 178 | var ii; 179 | for( ii = 0; ii <= 7; ii++ ) { 180 | tab = popup.getElementsByClassName( 'container-' + ii ); 181 | if ( 1 !== tab.length ) { 182 | continue; 183 | } 184 | tab = tab[0]; 185 | 186 | if ( ii === tab_number ) { 187 | tab.style.display = 'block'; 188 | } else { 189 | tab.style.display = 'none'; 190 | } 191 | } 192 | } 193 | 194 | /** 195 | * Send a reaction message back to the server 196 | * 197 | * @param HtmlElement el The button that was clicked 198 | */ 199 | var react = function( el ) { 200 | var post, params, xhr; 201 | 202 | if ( el.dataset.post ) { 203 | post = el.dataset.post; 204 | } else { 205 | post = el.parentElement.parentElement.dataset.post; 206 | } 207 | 208 | params = 'post=' + post + '&emoji=' + el.dataset.emoji; 209 | 210 | xhr = new XMLHttpRequest(); 211 | 212 | xhr.open( 'POST', settings.endpoint, true ); 213 | 214 | xhr.setRequestHeader( 'Content-type', 'application/x-www-form-urlencoded' ); 215 | 216 | xhr.send( params ); 217 | }; 218 | 219 | /** 220 | * Load the emoji definition JSON blob 221 | */ 222 | var loadEmoji = function() { 223 | var xhr; 224 | 225 | if ( loading ) { 226 | return; 227 | } 228 | loading = true; 229 | 230 | xhr = new XMLHttpRequest(); 231 | xhr.onreadystatechange = function() { 232 | if ( xhr.readyState === XMLHttpRequest.DONE ) { 233 | if ( 200 === xhr.status ) { 234 | loaded = true; 235 | emoji = JSON.parse( xhr.responseText ); 236 | } 237 | } 238 | } 239 | 240 | xhr.open( 'GET', settings.emoji_url, true ); 241 | xhr.send(); 242 | } 243 | 244 | if ( 'complete' === document.readyState ) { 245 | loadEmoji(); 246 | } else { 247 | if ( document.addEventListener ) { 248 | document.addEventListener( 'DOMContentLoaded', loadEmoji, false ); 249 | window.addEventListener( 'load', loadEmoji, false ); 250 | } else { 251 | window.attachEvent( 'onload', loadEmoji ); 252 | document.attachEvent( 'onreadystatechange', function() { 253 | if ( 'complete' === document.readyState ) { 254 | loadEmoji(); 255 | } 256 | } ); 257 | } 258 | } 259 | 260 | if ( document.addEventListener ) { 261 | document.addEventListener( 'click', reactionClick ); 262 | } else { 263 | document.attachEvent( 'click', reactionClick ); 264 | } 265 | 266 | } )( window, document, window.wp.react.settings ); 267 | 268 | 269 | /*! http://mths.be/fromcodepoint v0.1.0 by @mathias */ 270 | if (!String.fromCodePoint) { 271 | (function() { 272 | var defineProperty = (function() { 273 | // IE 8 only supports `Object.defineProperty` on DOM elements 274 | try { 275 | var object = {}; 276 | var $defineProperty = Object.defineProperty; 277 | var result = $defineProperty(object, object, object) && $defineProperty; 278 | } catch(error) {} 279 | return result; 280 | }()); 281 | var stringFromCharCode = String.fromCharCode; 282 | var floor = Math.floor; 283 | var fromCodePoint = function() { 284 | var MAX_SIZE = 0x4000; 285 | var codeUnits = []; 286 | var highSurrogate; 287 | var lowSurrogate; 288 | var index = -1; 289 | var length = arguments.length; 290 | if (!length) { 291 | return ''; 292 | } 293 | var result = ''; 294 | while (++index < length) { 295 | var codePoint = Number(arguments[index]); 296 | if ( 297 | !isFinite(codePoint) || // `NaN`, `+Infinity`, or `-Infinity` 298 | codePoint < 0 || // not a valid Unicode code point 299 | codePoint > 0x10FFFF || // not a valid Unicode code point 300 | floor(codePoint) != codePoint // not an integer 301 | ) { 302 | throw RangeError('Invalid code point: ' + codePoint); 303 | } 304 | if (codePoint <= 0xFFFF) { // BMP code point 305 | codeUnits.push(codePoint); 306 | } else { // Astral code point; split in surrogate halves 307 | // http://mathiasbynens.be/notes/javascript-encoding#surrogate-formulae 308 | codePoint -= 0x10000; 309 | highSurrogate = (codePoint >> 10) + 0xD800; 310 | lowSurrogate = (codePoint % 0x400) + 0xDC00; 311 | codeUnits.push(highSurrogate, lowSurrogate); 312 | } 313 | if (index + 1 == length || codeUnits.length > MAX_SIZE) { 314 | result += stringFromCharCode.apply(null, codeUnits); 315 | codeUnits.length = 0; 316 | } 317 | } 318 | return result; 319 | }; 320 | if (defineProperty) { 321 | defineProperty(String, 'fromCodePoint', { 322 | 'value': fromCodePoint, 323 | 'configurable': true, 324 | 'writable': true 325 | }); 326 | } else { 327 | String.fromCodePoint = fromCodePoint; 328 | } 329 | }()); 330 | } 331 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | server = $wp_rest_server = new WP_Test_Spy_REST_Server; 13 | do_action( 'rest_api_init' ); 14 | } 15 | 16 | public function tearDown() { 17 | parent::tearDown(); 18 | 19 | /** @var WP_REST_Server $wp_rest_server */ 20 | global $wp_rest_server; 21 | $wp_rest_server = null; 22 | } 23 | 24 | abstract public function test_register_routes(); 25 | 26 | abstract public function test_context_param(); 27 | 28 | abstract public function test_get_items(); 29 | 30 | abstract public function test_get_item(); 31 | 32 | abstract public function test_create_item(); 33 | 34 | abstract public function test_update_item(); 35 | 36 | abstract public function test_delete_item(); 37 | 38 | abstract public function test_prepare_item(); 39 | 40 | abstract public function test_get_item_schema(); 41 | 42 | } 43 | -------------------------------------------------------------------------------- /tests/class-wp-test-rest-testcase.php: -------------------------------------------------------------------------------- 1 | as_error(); 8 | } 9 | 10 | $this->assertInstanceOf( 'WP_Error', $response ); 11 | $this->assertEquals( $code, $response->get_error_code() ); 12 | 13 | if ( null !== $status ) { 14 | $data = $response->get_error_data(); 15 | $this->assertArrayHasKey( 'status', $data ); 16 | $this->assertEquals( $status, $data['status'] ); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/class-wp-test-spy-rest-server.php: -------------------------------------------------------------------------------- 1 | endpoints; 11 | } 12 | 13 | /** 14 | * Allow calling protected methods from tests 15 | * 16 | * @param string $method Method to call 17 | * @param array $args Arguments to pass to the method 18 | * @return mixed 19 | */ 20 | public function __call( $method, $args ) { 21 | return call_user_func_array( array( $this, $method ), $args ); 22 | } 23 | 24 | /** 25 | * Call dispatch() with the rest_post_dispatch filter 26 | */ 27 | public function dispatch( $request ) { 28 | $result = parent::dispatch( $request ); 29 | $result = rest_ensure_response( $result ); 30 | if ( is_wp_error( $result ) ) { 31 | $result = $this->error_to_response( $result ); 32 | } 33 | return apply_filters( 'rest_post_dispatch', rest_ensure_response( $result ), $this, $request ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/test-frontend.php: -------------------------------------------------------------------------------- 1 | factory->post->create(); 15 | 16 | $this->go_to( get_permalink( $post_id ) ); 17 | 18 | ob_start(); 19 | the_content(); 20 | $content = ob_get_clean(); 21 | 22 | $this->assertGreaterThanOrEqual( 0, strpos( '
factory->post->create(); 30 | 31 | $this->go_to( get_permalink( $post_id ) ); 32 | 33 | ob_start(); 34 | the_content(); 35 | $content = ob_get_clean(); 36 | 37 | $this->assertEquals( 1, preg_match( "/
]*class='emoji-reaction-add'/", $content ) ); 38 | } 39 | 40 | /** 41 | * Test that React::the_content() doesn't change the content when not in the loop. 42 | */ 43 | function test_content_not_changed_outside_loop() { 44 | $react = React::init(); 45 | 46 | $content ='foo'; 47 | 48 | $this->assertEquals( $content, $react->the_content( $content ) ); 49 | } 50 | 51 | /** 52 | * Test that the emoji.json URL is passed. 53 | */ 54 | function test_json_url_is_passed() { 55 | $post_id = $this->factory->post->create(); 56 | 57 | $this->go_to( get_permalink( $post_id ) ); 58 | 59 | ob_start(); 60 | wp_head(); 61 | $head = ob_get_clean(); 62 | 63 | $this->assertEquals( 1, preg_match( "/emoji_url: '[^']*emoji.json'/", $head ) ); 64 | } 65 | 66 | function test_selector_in_footer() { 67 | $post_id = $this->factory->post->create(); 68 | 69 | $this->go_to( get_permalink( $post_id ) ); 70 | 71 | ob_start(); 72 | wp_footer(); 73 | $footer = ob_get_clean(); 74 | 75 | $this->assertGreaterThanOrEqual( 0, strpos( '
has_img_twitter ) ) { 15 | continue; 16 | } 17 | 18 | $category = array_search( $emoji->category, $categories ); 19 | if ( false === $category ) { 20 | if ( 0 === strpos( $emoji->short_name, 'flag-' ) ) { 21 | $category = 7; 22 | } else { 23 | $category = 100; 24 | } 25 | } 26 | $code = "0x" . $emoji->unified; 27 | $code = str_replace( '-', "-0x", $code ); 28 | $code = explode( '-', $code ); 29 | 30 | $map[ $category ][] = array( 31 | 'code' => $code, 32 | 'sort_order' => $emoji->sort_order, 33 | ); 34 | } 35 | 36 | ksort( $map ); 37 | 38 | foreach ( $map as $category => $emoji_list ) { 39 | usort( $map[ $category ], function( $a, $b ) { 40 | if ( $a['sort_order'] == $b['sort_order'] ) { 41 | return 0; 42 | } 43 | 44 | return ( $a['sort_order'] < $b['sort_order'] ) ? -1 : 1; 45 | } ); 46 | 47 | foreach ( $map[ $category ] as $id => $emoji ) { 48 | $map[ $category ][ $id ] = $emoji['code']; 49 | } 50 | } 51 | 52 | file_put_contents( dirname( __DIR__ ) . '/static/emoji.json', json_encode( $map ) ); 53 | --------------------------------------------------------------------------------