diff --git a/.travis.yml b/.travis.yml index a9652ccc33e4ac..076ccfd0249468 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,65 @@ -language: node_js -node_js: - - "node" +sudo: false + +language: php + +notifications: + email: + on_success: never + on_failure: change + +cache: + directories: + - vendor + - $HOME/.composer/cache + +before_install: + - nvm install 7 && nvm use 7 + +matrix: + include: + - php: 7.1 + env: WP_VERSION=latest + - php: 5.6 + env: WP_VERSION=latest + - php: 5.2 + env: WP_VERSION=latest + - php: 5.6 + env: TRAVISCI=phpcs + - php: 7.1 + env: TRAVISCI=js + +before_script: + - export PATH="$HOME/.composer/vendor/bin:$PATH" + - | + if [[ ! -z "$WP_VERSION" ]] ; then + bash bin/install-wp-tests.sh wordpress_test root '' localhost $WP_VERSION + if [[ ${TRAVIS_PHP_VERSION:0:2} == "5." ]]; then + mkdir -p $HOME/phpunit-bin + wget -O $HOME/phpunit-bin/phpunit https://phar.phpunit.de/phpunit-4.8.phar + chmod +x $HOME/phpunit-bin/phpunit + export PATH=$PATH:$HOME/phpunit-bin/ + else + composer global require "phpunit/phpunit=5.7.*" + fi + fi + - | + if [[ "$TRAVISCI" == "phpcs" ]] ; then + composer global require wp-coding-standards/wpcs + phpcs --config-set installed_paths $HOME/.composer/vendor/wp-coding-standards/wpcs + fi + script: - - "npm run ci" + - | + if [[ ! -z "$WP_VERSION" ]] ; then + phpunit + WP_MULTISITE=1 phpunit + fi + - | + if [[ "$TRAVISCI" == "phpcs" ]] ; then + phpcs --standard=phpcs.ruleset.xml $(find . -name '*.php') + fi + - | + if [[ "$TRAVISCI" == "js" ]] ; then + npm install + npm run ci + fi diff --git a/bin/install-wp-tests.sh b/bin/install-wp-tests.sh new file mode 100755 index 00000000000000..73bb4c787eb2cc --- /dev/null +++ b/bin/install-wp-tests.sh @@ -0,0 +1,127 @@ +#!/usr/bin/env bash + +if [ $# -lt 3 ]; then + echo "usage: $0 [db-host] [wp-version] [skip-database-creation]" + exit 1 +fi + +DB_NAME=$1 +DB_USER=$2 +DB_PASS=$3 +DB_HOST=${4-localhost} +WP_VERSION=${5-latest} +SKIP_DB_CREATE=${6-false} + +WP_TESTS_DIR=${WP_TESTS_DIR-/tmp/wordpress-tests-lib} +WP_CORE_DIR=${WP_CORE_DIR-/tmp/wordpress/} + +download() { + if [ `which curl` ]; then + curl -s "$1" > "$2"; + elif [ `which wget` ]; then + wget -nv -O "$2" "$1" + fi +} + +if [[ $WP_VERSION =~ [0-9]+\.[0-9]+(\.[0-9]+)? ]]; then + WP_TESTS_TAG="tags/$WP_VERSION" +elif [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then + WP_TESTS_TAG="trunk" +else + # http serves a single offer, whereas https serves multiple. we only want one + download http://api.wordpress.org/core/version-check/1.7/ /tmp/wp-latest.json + grep '[0-9]+\.[0-9]+(\.[0-9]+)?' /tmp/wp-latest.json + LATEST_VERSION=$(grep -o '"version":"[^"]*' /tmp/wp-latest.json | sed 's/"version":"//') + if [[ -z "$LATEST_VERSION" ]]; then + echo "Latest WordPress version could not be found" + exit 1 + fi + WP_TESTS_TAG="tags/$LATEST_VERSION" +fi + +set -ex + +install_wp() { + + if [ -d $WP_CORE_DIR ]; then + return; + fi + + mkdir -p $WP_CORE_DIR + + if [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then + mkdir -p /tmp/wordpress-nightly + download https://wordpress.org/nightly-builds/wordpress-latest.zip /tmp/wordpress-nightly/wordpress-nightly.zip + unzip -q /tmp/wordpress-nightly/wordpress-nightly.zip -d /tmp/wordpress-nightly/ + mv /tmp/wordpress-nightly/wordpress/* $WP_CORE_DIR + else + if [ $WP_VERSION == 'latest' ]; then + local ARCHIVE_NAME='latest' + else + local ARCHIVE_NAME="wordpress-$WP_VERSION" + fi + download https://wordpress.org/${ARCHIVE_NAME}.tar.gz /tmp/wordpress.tar.gz + tar --strip-components=1 -zxmf /tmp/wordpress.tar.gz -C $WP_CORE_DIR + fi + + download https://raw.github.com/markoheijnen/wp-mysqli/master/db.php $WP_CORE_DIR/wp-content/db.php +} + +install_test_suite() { + # portable in-place argument for both GNU sed and Mac OSX sed + if [[ $(uname -s) == 'Darwin' ]]; then + local ioption='-i .bak' + else + local ioption='-i' + fi + + # set up testing suite if it doesn't yet exist + if [ ! -d $WP_TESTS_DIR ]; then + # set up testing suite + mkdir -p $WP_TESTS_DIR + svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/includes/ $WP_TESTS_DIR/includes + svn co --quiet https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/data/ $WP_TESTS_DIR/data + fi + + if [ ! -f wp-tests-config.php ]; then + download https://develop.svn.wordpress.org/${WP_TESTS_TAG}/wp-tests-config-sample.php "$WP_TESTS_DIR"/wp-tests-config.php + # remove all forward slashes in the end + WP_CORE_DIR=$(echo $WP_CORE_DIR | sed "s:/\+$::") + sed $ioption "s:dirname( __FILE__ ) . '/src/':'$WP_CORE_DIR/':" "$WP_TESTS_DIR"/wp-tests-config.php + sed $ioption "s/youremptytestdbnamehere/$DB_NAME/" "$WP_TESTS_DIR"/wp-tests-config.php + sed $ioption "s/yourusernamehere/$DB_USER/" "$WP_TESTS_DIR"/wp-tests-config.php + sed $ioption "s/yourpasswordhere/$DB_PASS/" "$WP_TESTS_DIR"/wp-tests-config.php + sed $ioption "s|localhost|${DB_HOST}|" "$WP_TESTS_DIR"/wp-tests-config.php + fi + +} + +install_db() { + + if [ ${SKIP_DB_CREATE} = "true" ]; then + return 0 + fi + + # parse DB_HOST for port or socket references + local PARTS=(${DB_HOST//\:/ }) + local DB_HOSTNAME=${PARTS[0]}; + local DB_SOCK_OR_PORT=${PARTS[1]}; + local EXTRA="" + + if ! [ -z $DB_HOSTNAME ] ; then + if [ $(echo $DB_SOCK_OR_PORT | grep -e '^[0-9]\{1,\}$') ]; then + EXTRA=" --host=$DB_HOSTNAME --port=$DB_SOCK_OR_PORT --protocol=tcp" + elif ! [ -z $DB_SOCK_OR_PORT ] ; then + EXTRA=" --socket=$DB_SOCK_OR_PORT" + elif ! [ -z $DB_HOSTNAME ] ; then + EXTRA=" --host=$DB_HOSTNAME --protocol=tcp" + fi + fi + + # create database + mysqladmin create $DB_NAME --user="$DB_USER" --password="$DB_PASS"$EXTRA +} + +install_wp +install_test_suite +install_db diff --git a/index.php b/index.php index 633db0559ddce9..7f7a6746c55a4b 100644 --- a/index.php +++ b/index.php @@ -33,10 +33,10 @@ function gutenberg_menu() { /** * Registers a block. * - * @param string $slug Block slug including namespace. - * @param array $settings Block settings + * @param string $slug Block slug including namespace. + * @param array $settings Block settings. - * @return array The block, if it has been successfully registered. + * @return array The block, if it has been successfully registered. */ function register_block( $slug, $settings ) { global $registered_blocks; @@ -55,23 +55,39 @@ function register_block( $slug, $settings ) { } if ( isset( $registered_blocks[ $slug ] ) ) { - $message = sprintf( __( 'Block "%s" is already registered.' ), $slug ); - _doing_it_wrong( __FUNCTION__, $message, '0.1.0' ); return; } - $settings[ 'slug' ] = $slug; + $settings['slug'] = $slug; $registered_blocks[ $slug ] = $settings; return $settings; } +/** + * Unregisters a block. + * + * @param string $slug Block slug. + * @return array The previous block value, if it has been + * successfully unregistered; otherwise `null`. + */ +function unregister_block( $slug ) { + global $registered_blocks; + if ( ! isset( $registered_blocks[ $slug ] ) ) { + /* translators: 1: block slug */ + $message = sprintf( __( 'Block "%s" is not registered.' ), $slug ); + _doing_it_wrong( __FUNCTION__, $message, '0.1.0' ); + return; + } + unset( $registered_blocks[ $slug ] ); +} + /** * Extract the block attributes from the block's attributes string * * @since 0.1.0 * - * @param string $attr_string Attributes string + * @param string $attr_string Attributes string. * @return array */ @@ -79,8 +95,8 @@ function parse_block_attributes( $attr_string ) { $attributes_matcher = '/([^\s]+):([^\s]+)\s*/'; preg_match_all( $attributes_matcher, $attr_string, $matches ); $attributes = array(); - foreach ( $matches[ 1 ] as $index => $attribute_match ) { - $attributes[ $attribute_match ] = $matches[ 2 ][ $index ]; + foreach ( $matches[1] as $index => $attribute_match ) { + $attributes[ $attribute_match ] = $matches[2][ $index ]; } return $attributes; @@ -91,40 +107,40 @@ function parse_block_attributes( $attr_string ) { * * @since 0.1.0 * - * @param string $content Post content - - * @return string Updated post content + * @param string $content Post content. + * + * @return string Updated post content. */ function do_blocks( $content ) { global $registered_blocks; - // Extract the blocks from the post content - $open_matcher = '/).)*)-->.*/'; + // Extract the blocks from the post content. + $open_matcher = '/).)*)-->.*?/'; preg_match_all( $open_matcher, $content, $matches, PREG_OFFSET_CAPTURE ); $new_content = $content; - foreach ( $matches[ 0 ] as $index => $block_match ) { - $block_name = $matches[ 1 ][ $index ][ 0 ]; - // do nothing if the block is not registered + foreach ( $matches[0] as $index => $block_match ) { + $block_name = $matches[1][ $index ][0]; + // do nothing if the block is not registered. if ( ! isset( $registered_blocks[ $block_name ] ) ) { continue; } - $block_markup = $block_match[ 0 ]; - $block_position = $block_match[ 1 ]; - $block_attributes_string = $matches[ 2 ][ $index ][ 0 ]; + $block_markup = $block_match[0]; + $block_position = $block_match[1]; + $block_attributes_string = $matches[2][ $index ][0]; $block_attributes = parse_block_attributes( $block_attributes_string ); - // Call the block's render function to generate the dynamic output - $output = call_user_func( $registered_blocks[ $block_name ][ 'render' ], $block_attributes ); + // Call the block's render function to generate the dynamic output. + $output = call_user_func( $registered_blocks[ $block_name ]['render'], $block_attributes ); - // Replace the matched block with the dynamic output + // Replace the matched block with the dynamic output. $new_content = str_replace( $block_markup, $output, $new_content ); } return $new_content; } -add_filter( 'the_content', 'do_blocks', 10 ); // BEFORE do_shortcode() +add_filter( 'the_content', 'do_blocks', 10 ); // BEFORE do_shortcode(). /** * Registers common scripts to be used as dependencies of the editor and plugins. @@ -158,9 +174,9 @@ function gutenberg_register_scripts() { * @since 0.1.0 */ function gutenberg_add_edit_links_filters() { - // For hierarchical post types + // For hierarchical post types. add_filter( 'page_row_actions', 'gutenberg_add_edit_links', 10, 2 ); - // For non-hierarchical post types + // For non-hierarchical post types. add_filter( 'post_row_actions', 'gutenberg_add_edit_links', 10, 2 ); } add_action( 'admin_init', 'gutenberg_add_edit_links_filters' ); @@ -170,14 +186,18 @@ function gutenberg_add_edit_links_filters() { * the Gutenberg editor. * * @since 0.1.0 + * + * @param array $actions Post actions. + * @param array $post Edited post. + * + * @return array Updated post actions. */ function gutenberg_add_edit_links( $actions, $post ) { $can_edit_post = current_user_can( 'edit_post', $post->ID ); $title = _draft_or_post_title( $post->ID ); if ( $can_edit_post && 'trash' !== $post->post_status ) { - // Build the Gutenberg edit action. See also: - // WP_Posts_List_Table::handle_row_actions() + // Build the Gutenberg edit action. See also: WP_Posts_List_Table::handle_row_actions(). $gutenberg_url = menu_page_url( 'gutenberg', false ); $gutenberg_action = sprintf( '%s', @@ -193,7 +213,9 @@ function gutenberg_add_edit_links( $actions, $post ) { $edit_offset = array_search( 'edit', array_keys( $actions ), true ); $actions = array_merge( array_slice( $actions, 0, $edit_offset + 1 ), - array( 'gutenberg hide-if-no-js' => $gutenberg_action ), + array( + 'gutenberg hide-if-no-js' => $gutenberg_action, + ), array_slice( $actions, $edit_offset + 1 ) ); } @@ -206,6 +228,8 @@ function gutenberg_add_edit_links( $actions, $post ) { * * @since 0.1.0 * + * @param string $domain Translation domain. + * * @return array */ function gutenberg_get_jed_locale_data( $domain ) { @@ -217,10 +241,10 @@ function gutenberg_get_jed_locale_data( $domain ) { $domain => array( '' => array( 'domain' => $domain, - 'lang' => is_admin() ? get_user_locale() : get_locale() - ) - ) - ) + 'lang' => is_admin() ? get_user_locale() : get_locale(), + ), + ), + ), ); if ( ! empty( $translations->headers['Plural-Forms'] ) ) { @@ -253,16 +277,16 @@ function gutenberg_scripts_and_styles( $hook ) { * Scripts */ - // The editor code itself + // The editor code itself. wp_enqueue_script( 'wp-editor', plugins_url( 'editor/build/index.js', __FILE__ ), array( 'wp-i18n', 'wp-blocks', 'wp-element' ), filemtime( plugin_dir_path( __FILE__ ) . 'editor/build/index.js' ), - true // $in_footer + true // enqueue in the footer. ); - // Load an actual post if an ID is specified + // Load an actual post if an ID is specified. $post_to_edit = null; if ( isset( $_GET['post_id'] ) && (int) $_GET['post_id'] > 0 ) { $request = new WP_REST_Request( @@ -289,7 +313,7 @@ function gutenberg_scripts_and_styles( $hook ) { ); } - // Prepare Jed locale data + // Prepare Jed locale data. $locale_data = gutenberg_get_jed_locale_data( 'gutenberg' ); wp_add_inline_script( 'wp-editor', @@ -297,7 +321,7 @@ function gutenberg_scripts_and_styles( $hook ) { 'before' ); - // Initialize the editor + // Initialize the editor. wp_add_inline_script( 'wp-editor', 'wp.editor.createEditorInstance( \'editor\', _wpGutenbergPost );' ); /** @@ -346,11 +370,3 @@ function the_gutenberg_project() { 'render_html_block' -) ); diff --git a/phpcs.ruleset.xml b/phpcs.ruleset.xml new file mode 100644 index 00000000000000..210c25a14e74ac --- /dev/null +++ b/phpcs.ruleset.xml @@ -0,0 +1,10 @@ + + + Generally-applicable sniffs for WordPress plugins + + + + + */node_modules/* + */vendor/* + diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 00000000000000..2eb5f4eae3c4b8 --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,14 @@ + + + + ./phpunit/ + + + diff --git a/phpunit/bootstrap.php b/phpunit/bootstrap.php new file mode 100644 index 00000000000000..a398feb4fa5fea --- /dev/null +++ b/phpunit/bootstrap.php @@ -0,0 +1,25 @@ + array( + $this, + 'render_dummy_block', + ), + ); + register_block( 'core/dummy', $settings ); + $post_content = + 'before' . + '' . + 'between' . + '' . + 'after'; + + $updated_post_content = do_blocks( $post_content ); + unregister_block( 'core/dummy' ); + $this->assertEquals( $updated_post_content, + 'before' . + 'b1' . + 'between' . + 'b2' . + 'after' + ); + } +} diff --git a/phpunit/class-registration-test.php b/phpunit/class-registration-test.php new file mode 100644 index 00000000000000..08b86e58fe394f --- /dev/null +++ b/phpunit/class-registration-test.php @@ -0,0 +1,44 @@ + 'text', + ); + $updated_settings = register_block( 'core/text', $settings ); + $this->assertEquals( $updated_settings, array( + 'icon' => 'text', + 'slug' => 'core/text', + ) ); + unregister_block( 'core/text' ); + } +}