diff --git a/.eslintrc b/.eslintrc index 9cb1554..d46cb4d 100644 --- a/.eslintrc +++ b/.eslintrc @@ -15,7 +15,7 @@ "Vue": false }, "extends": [ - "plugin:@wordpress/eslint-plugin/recommended" + "plugin:@wordpress/eslint-plugin/recommended-with-formatting" ], "rules": { "no-alert": "off", diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..8de0ddb --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,15 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "composer" + directory: "/" + schedule: + interval: "monthly" + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "monthly" diff --git a/.github/workflows/wordpress.yml b/.github/workflows/wordpress.yml index 2440daa..2f27164 100644 --- a/.github/workflows/wordpress.yml +++ b/.github/workflows/wordpress.yml @@ -12,54 +12,20 @@ on: jobs: test: - runs-on: ${{ matrix.operating-system }} - strategy: - matrix: - operating-system: [ ubuntu-18.04 ] # OS. ubuntu-18.04 is also available. - php: [ '5.6', '7.2', '7.4' ] # PHP versions to check. - wp: [ 'latest', '5.4' ] # WordPress version to check. - services: - mysql: - image: mysql:5.7 - options: --health-cmd "mysqladmin ping --host 127.0.0.1 --port 3306" --health-interval 20s --health-timeout 10s --health-retries 10 - ports: - - 3306/tcp - env: - MYSQL_ROOT_PASSWORD: root - name: WordPress ${{ matrix.wp }} in PHP ${{ matrix.php }} UnitTest - steps: - - uses: actions/checkout@master - - - name: Setup PHP - uses: nanasess/setup-php@master - with: - php-version: ${{ matrix.php }} - - - name: Validate composer.json and composer.lock - run: composer validate - - - name: Install dependencies - run: composer install --prefer-dist --no-progress --no-suggest - - - name: Start MySQL - run: sudo systemctl start mysql - - - name: Install WordPress - run: bash bin/install-wp-tests.sh wordpress root root 127.0.0.1:3306 ${{ matrix.wp }} - - - name: Check PHP syntax - run: composer test + uses: tarosky/workflows/.github/workflows/phpcs.yml@main + with: + version: 7.2 assets: name: Assets Test - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest steps: - - uses: actions/checkout@master + - uses: actions/checkout@main - name: Install Node - uses: actions/setup-node@v1 + uses: actions/setup-node@v3 with: - node-version: '12' + node-version: '16' - name: Install NPM Packages run: npm install @@ -67,27 +33,45 @@ jobs: - name: Check JS & CSS syntax run: npm run lint + phplint: + uses: tarosky/workflows/.github/workflows/phplint.yml@main + release: name: Deploy WordPress.org - needs: [ test, assets ] + needs: [ test, assets, phplint ] if: contains(github.ref, 'tags/') - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@main - name: Setup PHP - uses: nanasess/setup-php@master + uses: shivammathur/setup-php@v2 with: - php-version: 5.6 + php-version: 7.2 + tools: composer + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Install Node - uses: actions/setup-node@v1 + uses: actions/setup-node@v3 with: - node-version: '12' + node-version: '16' - name: Build Plugin - run: bash bin/build.sh ${{ github.ref }} + run: | + composer install --prefer-dist --no-dev + npm install + npm run package + + - name: Generate readme.txt + uses: tarosky/workflows/actions/wp-readme@main + + - name: Versioning + uses: tarosky/workflows/actions/versioning@main + with: + version: ${{ github.ref }} + files: readme.txt,taro-series.php - name: Deploy to WordPress Directory id: deploy diff --git a/.github/workflows/wp-outdated.yml b/.github/workflows/wp-outdated.yml new file mode 100644 index 0000000..f90ae61 --- /dev/null +++ b/.github/workflows/wp-outdated.yml @@ -0,0 +1,39 @@ +name: Latest WP Support + +on: + schedule: + - cron: "0 2 5 * *" # Every month on the 5th at 2am UTC + +jobs: + is-outdated: + name: Check if WP version is outdated + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@main + + - name: Install Node + uses: actions/setup-node@master + with: + node-version: '18' + + - name: Check wp version + uses: tarosky/farmhand-wp-aciton@v1 + id: wp_version + + - name: Update Issue if needed + if: steps.wp_version.outputs.should_update + uses: actions-ecosystem/action-create-issue@v1 + with: + github_token: ${{ secrets.github_token }} + title: ${{ steps.wp_version.outputs.version }} + body: | + ## TODO + + - [ ] Check if plugin works with the latest WP version + - [ ] Bump "Tested up to" version in README.md + + labels: | + update + assignees: | + user1 diff --git a/.wordpress_org/icon.svg b/.wordpress-org/icon.svg similarity index 100% rename from .wordpress_org/icon.svg rename to .wordpress-org/icon.svg diff --git a/README.md b/README.md index 98f00bb..3e94cd8 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ Tags: series, posts, news Contributors: tarosky, Takahashi_Fumiki -Tested up to: 5.8 -Requires at least: 5.4 -Requires PHP: 5.6 +Tested up to: 6.5 +Requires at least: 5.9 +Requires PHP: 7.2 Stable Tag: nightly License: GPLv3 or later License URI: http://www.gnu.org/licenses/gpl-3.0.txt @@ -75,6 +75,12 @@ Create a new [issue](https://github.com/tarosky/taro-series/issues) or send [pul ## Changelog +### 2.0.0 + +* Add WP_Query orderby parameter `series-updated`. +* Bump minimum PHP requiremtns to PHP 7.2 and over. +* Bump minimum WordPress version to 5.9. + ### 1.1.2 * Fix bug in articles count. diff --git a/assets/js/post-editor.js b/assets/js/post-editor.js index 1e1bd13..f7f5f71 100644 --- a/assets/js/post-editor.js +++ b/assets/js/post-editor.js @@ -12,9 +12,8 @@ const { __ } = wp.i18n; const { apiFetch } = wp; class SeriesRender extends Component { - - constructor(prop) { - super(prop); + constructor( prop ) { + super( prop ); this.state = { loading: false, post: null, @@ -43,11 +42,11 @@ class SeriesRender extends Component { loading: true, }, () => { apiFetch( { - path: `taro-series/v1/available/${this.props.postType}?p=${postId}`, + path: `taro-series/v1/available/${ this.props.postType }?p=${ postId }`, } ).then( ( res ) => { this.setState( { loading: false, - post: res[0], + post: res[ 0 ], }, () => { this.fetching = false; } ); @@ -59,7 +58,7 @@ class SeriesRender extends Component { this.fetching = false; } ); } ); - }); + } ); } else { this.setState( { post: null, @@ -76,7 +75,7 @@ class SeriesRender extends Component { let link = false; let title = ''; if ( post ) { - link = post.edit_link; + link = post.edit_link; title = post.title; } else if ( 0 < this.props.postId ) { title = __( 'Loading…', 'taro-series' ); @@ -100,7 +99,7 @@ class SeriesRender extends Component { { title }
+ } }>{ __( 'Leave Out', 'taro-series' ) } ) : ( { title } @@ -112,9 +111,8 @@ class SeriesRender extends Component { } class SeriesChooser extends Component { - - constructor(props) { - super(props); + constructor( props ) { + super( props ); this.state = { loading: false, posts: [], @@ -138,7 +136,7 @@ class SeriesChooser extends Component { fetch() { this.setState( { loading: true }, () => { apiFetch( { - path: `taro-series/v1/available/${this.props.postType}?s=${this.state.s}`, + path: `taro-series/v1/available/${ this.props.postType }?s=${ this.state.s }`, } ).then( ( res ) => { this.setState( { loading: false, @@ -174,7 +172,7 @@ class SeriesChooser extends Component { value: parseInt( p.id, 10 ), label: p.title, } ); - } ) + } ); result.push( ); @@ -201,9 +199,8 @@ class SeriesChooser extends Component { } class SeriesSelector extends Component { - - constructor(props) { - super(props); + constructor( props ) { + super( props ); this.state = { postId: parseInt( props.postId, 10 ), }; @@ -230,5 +227,5 @@ class SeriesSelector extends Component { // If meta box exists. const metaBox = document.getElementById( 'taro-series-selector' ); if ( metaBox ) { - render( , metaBox ); + render( , metaBox ); } diff --git a/assets/js/series-editor.js b/assets/js/series-editor.js index 397778f..41f9876 100644 --- a/assets/js/series-editor.js +++ b/assets/js/series-editor.js @@ -18,7 +18,7 @@ const message = ( content, status ) => { isDismissible: true, explicitDismiss: false, } ); -} +}; const listStyle = { borderBottom: '1px solid #ddd', @@ -36,7 +36,6 @@ const labelStyle = { const linkStyle = { marginRight: '10px' }; class SearchItem extends Component { - constructor( props ) { super( props ); this.state = { @@ -47,11 +46,11 @@ class SearchItem extends Component { add( post ) { this.setState( { loading: true }, () => { apiFetch( { - path: `taro-series/v1/series/${this.props.seriesId}`, + path: `taro-series/v1/series/${ this.props.seriesId }`, method: 'post', data: { - post_id: post.id - } + post_id: post.id, + }, } ).then( () => { this.props.onAdd( post ); } ).catch( ( res ) => { @@ -83,7 +82,6 @@ class SearchItem extends Component { } class Articles extends Component { - constructor( props ) { super( props ); this.state = { @@ -101,7 +99,7 @@ class Articles extends Component { loading: true, }, () => { apiFetch( { - path: `taro-series/v1/series/${this.props.seriesId}`, + path: `taro-series/v1/series/${ this.props.seriesId }`, method: 'get', } ).then( ( res ) => { this.setState( { @@ -122,7 +120,7 @@ class Articles extends Component { loading: true, }, () => { apiFetch( { - path: `taro-series/v1/series/${this.props.seriesId}?s=${this.state.term}`, + path: `taro-series/v1/series/${ this.props.seriesId }?s=${ this.state.term }`, method: 'get', } ).then( ( res ) => { this.setState( { @@ -150,16 +148,16 @@ class Articles extends Component { return 0; } return a.date < b.date ? -1 : 1; - } ) + } ); this.setState( { - posts + posts, } ); } remove( postId ) { this.setState( { loading: false }, () => { apiFetch( { - path: `taro-series/v1/series/${this.props.seriesId}?post_id=${postId}`, + path: `taro-series/v1/series/${ this.props.seriesId }?post_id=${ postId }`, method: 'delete', } ).then( () => { // Successfully removed. @@ -167,7 +165,7 @@ class Articles extends Component { loading: false, posts: this.state.posts.filter( ( post ) => { return post.id !== postId; - } ) + } ), }, () => { // translators: %d is post id. message( sprintf( __( '#%d is removed from the articles of this series.', 'taro-series' ), postId ), 'success' ); @@ -188,7 +186,7 @@ class Articles extends Component { results: [], resultCount: 0, term: '', - } ) + } ); } render() { @@ -206,12 +204,12 @@ class Articles extends Component {
    { posts.map( ( post ) => { return ( -
  1. +
  2. { post.title } - {post.statusLabel} + { post.statusLabel } { post.postTypeLabel } - {post.dateFormatted} + { post.dateFormatted }

    { __( 'Edit', 'taro-series' ) } @@ -248,7 +246,7 @@ class Articles extends Component {

      { results.map( ( post ) => { return ( - this.add( p ) } /> + this.add( p ) } /> ); } ) }
    diff --git a/bin/build.sh b/bin/build.sh deleted file mode 100755 index d817f1f..0000000 --- a/bin/build.sh +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env bash - -set -e - -# Set variables. -PREFIX="refs/tags/" -VERSION=${1#"$PREFIX"} - -echo "Building Taro Series v${VERSION}..." - -# Install composer. -composer install --no-dev --prefer-dist - -# Install NPM. -npm install -npm run package - -# Create README.txt -curl -L https://raw.githubusercontent.com/fumikito/wp-readme/master/wp-readme.php | php - -# Change version string. -sed -i.bak "s/^Version: .*/Version: ${VERSION}/g" ./taro-series.php -sed -i.bak "s/^Stable Tag: .*/Stable Tag: ${VERSION}/g" ./readme.txt diff --git a/bin/clean.sh b/bin/clean.sh deleted file mode 100644 index 932bd4f..0000000 --- a/bin/clean.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env bash - -set -e - -# Remove unwanted files in distignore. -files=(`cat ".distignore"`) - -for item in "${files[@]}"; do - if [ -e $item ]; then - rm -frv $item - fi -done diff --git a/composer.json b/composer.json index e7af456..c040e96 100644 --- a/composer.json +++ b/composer.json @@ -4,12 +4,10 @@ "minimum-stability": "stable", "license": "GPL-3.0-or-later", "scripts": { - "test": [ - "phpcs --config-set installed_paths $(pwd)/vendor/wp-coding-standards/wpcs", + "lint": [ "phpcs --standard=phpcs.ruleset.xml $(find ./ -name '*.php')" ], "fix": [ - "phpcs --config-set installed_paths $(pwd)/vendor/wp-coding-standards/wpcs", "phpcbf --standard=phpcs.ruleset.xml $(find ./ -name '*.php')" ] }, @@ -20,15 +18,22 @@ } ], "require": { - "php": "^5.6|^7.0" + "php": ">=7.2" }, "require-dev": { "squizlabs/php_codesniffer": "^3.0", - "wp-coding-standards/wpcs": "^2.0" + "wp-coding-standards/wpcs": "^2.0", + "phpcompatibility/php-compatibility": "^9.3", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0" }, "autoload": { "psr-0": { "Tarosky\\Series": "src" } + }, + "config": { + "allow-plugins": { + "dealerdirect/phpcodesniffer-composer-installer": true + } } } diff --git a/gulpfile.js b/gulpfile.js index 0a2682f..4d77d6b 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -5,6 +5,7 @@ const webpack = require( 'webpack-stream' ); const webpackBundle = require( 'webpack' ); const named = require( 'vinyl-named' ); const { dumpSetting } = require('@kunoichi/grab-deps'); +const sass = require( 'gulp-sass' )( require( 'sass' ) ); let plumber = true; @@ -17,7 +18,7 @@ gulp.task( 'sass', function () { } ) ) .pipe( $.sassGlob() ) .pipe( $.sourcemaps.init() ) - .pipe( $.sass( { + .pipe( sass( { errLogToConsole: true, outputStyle: 'compressed', sourceComments: false, diff --git a/package.json b/package.json index a9ffd9a..f70fb24 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "@babel/plugin-transform-react-jsx": "^7.0.0", "@babel/preset-env": "^7.1.0", "@kunoichi/grab-deps": "^1.2.2", - "@wordpress/env": "^4.0", + "@wordpress/env": "^9.7.0", "@wordpress/eslint-plugin": "^9.0", "babel-eslint": "^10.0.1", "babel-loader": "^8.0.5", @@ -35,15 +35,18 @@ "gulp-notify": "^3.2.0", "gulp-plumber": "^1.2.0", "gulp-rename": "^1.4.0", - "gulp-sass": "^4.0.2", + "gulp-sass": "5.0", "gulp-sass-glob": "^1.0.9", "gulp-sourcemaps": "^3.0", "gulp-stylelint": "^13.0.0", + "sass": "^1.74.1", "stylelint": "^13.13.1", "stylelint-config-wordpress": "^17.0.0", "vinyl-named": "^1.1.0", "webpack": "^5.3", "webpack-stream": "^6.1" }, - "dependencies": {} + "volta": { + "node": "16.20.2" + } } diff --git a/phpcs.ruleset.xml b/phpcs.ruleset.xml index 2a3841f..f6925a3 100644 --- a/phpcs.ruleset.xml +++ b/phpcs.ruleset.xml @@ -26,6 +26,10 @@ + + + + */node_modules/* *.js */vendor/* diff --git a/src/Tarosky/Series/Bootstrap.php b/src/Tarosky/Series/Bootstrap.php index c573cc8..29e5a06 100644 --- a/src/Tarosky/Series/Bootstrap.php +++ b/src/Tarosky/Series/Bootstrap.php @@ -56,6 +56,8 @@ protected function init() { ArchiveLink::get_instance(); // Block TocBlock::get_instance(); + // Shortcode + add_shortcode( 'taro_series', [ $this, 'do_shortcode' ] ); } /** @@ -103,4 +105,39 @@ public function register_script() { } } } + + /** + * Render shortcode for debugging. + * + * @param array $attrs Shortcode attributes. + * @param string $contents Shortcode contents. + * + * @return string + */ + public function do_shortcode( $attrs = [], $contents = '' ) { + $attrs = shortcode_atts( [ + 'order' => 'DESC', + 'posts_per_page' => 10, + ], $attrs, 'taro_series' ); + $query_args = array_merge( [ + 'post_type' => taro_series_parent_post_type(), + 'post_status' => 'publish', + 'orderby' => 'series-updated', + ], $attrs ); + $query = new \WP_Query( $query_args ); + if ( ! $query->have_posts() ) { + return ''; + } + ob_start(); + echo '
      '; + foreach ( $query->posts as $post ) { + printf( + '
    • %s
    • ', + get_permalink( $post ), + get_the_title( $post ) + ); + } + echo '
    '; + return ob_get_clean(); + } } diff --git a/src/Tarosky/Series/Controller/Rewrite.php b/src/Tarosky/Series/Controller/Rewrite.php index dcf2fe9..fe3cee3 100644 --- a/src/Tarosky/Series/Controller/Rewrite.php +++ b/src/Tarosky/Series/Controller/Rewrite.php @@ -18,7 +18,10 @@ class Rewrite extends Singleton { protected function init() { add_filter( 'query_vars', [ $this, 'query_vars' ] ); add_filter( 'rewrite_rules_array', [ $this, 'rewrite_rules' ] ); - add_action( 'pre_get_posts', [ $this, 'pre_get_posts' ] ); + add_action( 'pre_get_posts', [ $this, 'query_in_series' ] ); + add_action( 'pre_get_posts', [ $this, 'query_series_top' ] ); + add_filter( 'posts_join', [ $this, 'posts_join' ], 10, 2 ); + add_filter( 'posts_orderby', [ $this, 'posts_orderby' ], 10, 2 ); add_filter( 'get_the_archive_title', [ $this, 'archive_title' ] ); add_filter( 'index_template_hierarchy', [ $this, 'template_hierarchy' ] ); add_filter( 'archive_template_hierarchy', [ $this, 'template_hierarchy' ] ); @@ -32,7 +35,7 @@ protected function init() { * @return string[] */ public function query_vars( $vars ) { - $vars[] = 'series_in'; + $vars[] = 'series_in'; // Posts in specific series. return $vars; } @@ -54,7 +57,7 @@ public function rewrite_rules( $rules ) { * * @param \WP_Query $wp_query Query object. */ - public function pre_get_posts( $wp_query ) { + public function query_in_series( $wp_query ) { $series_in = $wp_query->get( 'series_in' ); if ( ! $series_in ) { return; @@ -153,4 +156,82 @@ public function get_current_series( $slug = '' ) { } return get_page_by_path( $slug, OBJECT, taro_series_parent_post_type() ); } + + /** + * If this is series list, change query. + * + * @param \WP_Query $wp_query + * @return void + */ + public function query_series_top( $wp_query ) { + if ( ! $this->is_series_update( $wp_query ) ) { + return; + } + // Force post type to be series. + $wp_query->set( 'post_type', taro_series_parent_post_type() ); + // Order should be asc or desc. + if ( 'ASC' !== strtoupper( $wp_query->get( 'order' ) ) ) { + $wp_query->set( 'order', 'DESC' ); + } + } + + /** + * @param $join + * @param \WP_Query $wp_query + * + * @return mixed|string + */ + public function posts_join( $join, $wp_query ) { + if ( $this->is_series_update( $wp_query ) ) { + /* @var \wpdb $wpdb */ + global $wpdb; + $post_types = implode( ', ', array_map( function( $post_type ) use ( $wpdb ) { + return $wpdb->prepare( '%s', $post_type ); + }, taro_series_post_types() ) ); + $func = ( 'ASC' === strtoupper( $wp_query->get( 'order' ) ) ) ? 'MIN' : 'MAX'; + $sql = <<posts} AS p + LEFT JOIN {$wpdb->postmeta} AS pm + ON pm.meta_key = %s AND pm.post_id = p.ID + WHERE p.post_type IN ({$post_types}) + AND p.post_status = 'publish' + AND pm.meta_value IS NOT NULL + GROUP BY pm.meta_value + ) AS taro_series ON taro_series.series_id = {$wpdb->posts}.ID +SQL; + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared + $sql = $wpdb->prepare( $sql, taro_series_meta_key() ); + $join .= $sql; + } + return $join; + } + + /** + * Customize order by query. + * + * @param string $orderby + * @param \WP_Query $wp_query + * + * @return mixed + */ + public function posts_orderby( $orderby, $wp_query ) { + if ( $this->is_series_update( $wp_query ) ) { + /* @var \wpdb $wpdb */ + global $wpdb; + $orderby = sprintf( 'taro_series.last_updated %s', ( 'ASC' === strtoupper( $wp_query->get( 'order' ) ) ? 'ASC' : 'DESC' ) ); + } + return $orderby; + } + + /** + * Detect if query is series update list. + * + * @param \WP_Query $wp_query + * @return bool + */ + public function is_series_update( $wp_query ) { + return 'series-updated' === $wp_query->get( 'orderby' ); + } }