Commit 66094556 authored by Erick Hitter's avatar Erick Hitter

Merge branch 'fix/phpcs' into 'master'

v0.3

See merge request !2
parents 3481b43a 6df14680
Pipeline #1009 passed with stages
in 3 minutes and 48 seconds
......@@ -17,6 +17,3 @@ indent_size = 4
[{.jshintrc,*.json,*.yml}]
indent_style = space
indent_size = 2
[{*.txt,wp-config-sample.php}]
end_of_line = crlf
......@@ -2,6 +2,7 @@ variables:
# Configure mysql service (https://hub.docker.com/_/mysql/)
MYSQL_DATABASE: wordpress_tests
MYSQL_ROOT_PASSWORD: mysql
WP_VERSION: latest
cache:
paths:
......@@ -10,7 +11,7 @@ cache:
before_script:
# Set up WordPress tests
- bash bin/install-wp-tests.sh $MYSQL_DATABASE root $MYSQL_ROOT_PASSWORD mysql latest true
- bash bin/install-wp-tests.sh $MYSQL_DATABASE root $MYSQL_ROOT_PASSWORD mysql $WP_VERSION true
# PHPUnit
- |
......@@ -20,10 +21,43 @@ before_script:
composer global require "phpunit/phpunit=4.8.*"
fi
# Install PHPCS and WPCS
- composer global require automattic/vipwpcs
- composer global require phpcompatibility/phpcompatibility-wp
- phpcs --config-set installed_paths $HOME/.composer/vendor/wp-coding-standards/wpcs,$HOME/.composer/vendor/automattic/vipwpcs,$HOME/.composer/vendor/phpcompatibility/php-compatibility,$HOME/.composer/vendor/phpcompatibility/phpcompatibility-paragonie,$HOME/.composer/vendor/phpcompatibility/phpcompatibility-wp
PHPunit:PHP5.3:MySQL:
stage: test
variables:
WP_VERSION: 5.1.1
image: containers.ethitter.com:443/docker/images/php:5.3
services:
- mysql:5.6
script:
- find . -type "f" -iname "*.php" | xargs -L "1" php -l
- phpunit
PHPunit:PHP5.6:MySQL:
stage: test
image: containers.ethitter.com:443/docker/images/php:5.6
services:
- mysql:5.6
script:
- find . -type "f" -iname "*.php" | xargs -L "1" php -l
- phpunit
PHPunit:PHP7.0:MySQL:
stage: test
image: containers.ethitter.com:443/docker/images/php:7.0
services:
- mysql:5.6
script:
- find . -type "f" -iname "*.php" | xargs -L "1" php -l
- phpunit
PHPunit:PHP7.1:MySQL:
stage: test
image: containers.ethitter.com:443/docker/images/php:7.1
services:
- mysql:5.6
script:
- find . -type "f" -iname "*.php" | xargs -L "1" php -l
- phpunit
PHPunit:PHP7.2:MySQL:
stage: test
......@@ -32,9 +66,7 @@ PHPunit:PHP7.2:MySQL:
- mysql:5.6
script:
- find . -type "f" -iname "*.php" | xargs -L "1" php -l
- phpcs -n
- phpunit
allow_failure: true
PHPunit:PHP7.3:MySQL:
stage: test
......@@ -43,13 +75,21 @@ PHPunit:PHP7.3:MySQL:
- mysql:5.6
script:
- find . -type "f" -iname "*.php" | xargs -L "1" php -l
- phpcs -n
- phpunit
allow_failure: true
PHPCS:
stage: test
image: containers.ethitter.com:443/docker/images/php:7.3
before_script:
- composer global require automattic/vipwpcs
- composer global require phpcompatibility/phpcompatibility-wp
- phpcs --config-set installed_paths $HOME/.composer/vendor/wp-coding-standards/wpcs,$HOME/.composer/vendor/automattic/vipwpcs,$HOME/.composer/vendor/phpcompatibility/php-compatibility,$HOME/.composer/vendor/phpcompatibility/phpcompatibility-paragonie,$HOME/.composer/vendor/phpcompatibility/phpcompatibility-wp
script:
- phpcs -n
PluginSVN:
stage: deploy
image: containers.ethitter.com:443/docker/images/php:7.3
image: containers.ethitter.com:443/docker/wp-org-plugin-deploy:latest
before_script:
- curl -o ./bin/deploy.sh https://git-cdn.e15r.co/open-source/wp-org-plugin-deploy/raw/master/scripts/deploy.sh
- chmod +x ./bin/deploy.sh
......
......@@ -4,15 +4,15 @@
**Tags:** latest, latest post, redirect, redirect latest, redirect post
**Requires at least:** 4.5
**Tested up to:** 5.2
**Stable tag:** 0.2.2
**Stable tag:** 0.3
**License:** GPLv2 or later
**License URI:** http://www.gnu.org/licenses/gpl-2.0.html
Redirect a chosen slug, "latest" by default, to, well, the most-recently published post.
Redirect a chosen slug, "latest" by default, to, well, the most-recently-published post.
## Description ##
Once activated, a given slug will redirect to whatever is the most recently-published post on the site. By default, the slug is `latest`, but it can be changed from the Permalinks settings screen.
Once activated, a given slug will redirect to whatever is the most-recently-published post on the site. By default, the slug is `latest`, but it can be changed from the Permalinks settings screen.
## Installation ##
......@@ -20,8 +20,18 @@ Once activated, a given slug will redirect to whatever is the most recently-publ
1. Activate the plugin through the 'Plugins' screen in WordPress.
1. If desired, change the slug from the 'Permalinks' screen in WordPress.
## Frequently Asked Questions ##
### Can I redirect to something other than a post? ###
Yes, using the `eth_redirect_to_latest_post_query_args` or `eth_redirect_to_latest_post_redirection` filters introduced in v0.3.
## Changelog ##
### 0.3 ###
* Introduce filters to make redirection more flexible.
* Add unit tests and conform to coding standards.
### 0.2.2 ###
* Handle sites using slug-only permalinks
......
<?php
/*
Plugin Name: ETH Redirect to Latest Post
Plugin URI: https://ethitter.com/plugins/
Description: Redirect a chosen slug to the whatever is currently the latest post
Author: Erick Hitter
Version: 0.2.2
Author URI: https://ethitter.com/
Text Domain: eth_redirect_to_latest_post
Domain Path: /languages/
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
class ETH_Redirect_To_Latest_Post {
/**
* PLUGIN SETUP
*/
/**
* Singleton
*/
private static $instance = null;
/**
* Instantiate singleton
*/
public static function get_instance() {
if ( ! is_a( self::$instance, __CLASS__ ) ) {
self::$instance = new self;
}
return self::$instance;
}
/**
* Dummy magic methods
*/
public function __clone() { _doing_it_wrong( __FUNCTION__, __( 'Cheatin&#8217; uh?', 'eth_redirect_to_latest_post' ), '0.1' ); }
public function __wakeup() { _doing_it_wrong( __FUNCTION__, __( 'Cheatin&#8217; uh?', 'eth_redirect_to_latest_post' ), '0.1' ); }
public function __call( $name = '', $args = array() ) { unset( $name, $args ); return null; }
/**
* Class properties
*/
private $plugin_option_name = 'eth-redirect-to-latest';
private $slug = '';
private $default_slug = '';
/**
* Register plugin's setup action
*/
private function __construct() {
add_action( 'init', array( $this, 'action_init' ) );
add_action( 'parse_request', array( $this, 'action_parse_request' ) );
add_action( 'admin_init', array( $this, 'action_admin_init' ) );
}
/**
* Translate plugin slug
*/
public function action_init() {
$this->default_slug = __( 'latest', 'eth_redirect_to_latest_post' );
$_slug = get_option( $this->plugin_option_name, $this->default_slug );
if ( is_string( $_slug ) && ! empty( $_slug ) ) {
$this->slug = $_slug;
}
if ( empty( $this->slug ) ) {
$this->slug = $this->default_slug;
}
}
/**
* Redirect to the latest post any requests made to plugin's slug
*/
public function action_parse_request( $r ) {
// Nothing to do if permalinks aren't enabled
if ( ! $r->did_permalink ) {
return;
}
// By default, there's also nothing to do
$should_intercept = false;
// Check if request is for our slug
// The first condition catches permastructs that are more than just post slug, whereas the second catches for slug-only permalinks
if ( isset( $r->query_vars['pagename'] ) && $this->slug === $r->query_vars['pagename'] ) {
$should_intercept = true;
} elseif ( isset( $r->query_vars['name'] ) && $this->slug === $r->query_vars['name'] ) {
$should_intercept = true;
}
// Handle redirection
if ( $should_intercept ) {
$latest = get_posts( array(
'posts_per_page' => 1,
'post_type' => 'post',
'orderby' => 'date',
'order' => 'desc',
'suppress_filters' => false,
'no_found_rows' => true,
) );
if ( is_array( $latest ) && ! empty( $latest ) ) {
$latest = array_shift( $latest );
$dest = get_permalink( $latest->ID );
if ( ! $dest ) {
$dest = user_trailingslashit( home_url() );
}
wp_redirect( $dest, 302 ); // Not validating in case other plugins redirect elsewhere
exit;
}
}
}
/**
* ADMIN OPTIONS
*/
/**
* Save plugin settings and register settings field
*
* Permalinks screen is a snowflake, hence the custom saving handler
*/
public function action_admin_init() {
// Make sure user has necessary permissions first
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
// Save custom option, permalinks screen is a snowflake and doesn't fully use the Settings API
global $pagenow;
if ( 'options-permalink.php' === $pagenow && isset( $_POST[ $this->plugin_option_name ] ) ) {
check_admin_referer( 'update-permalink' );
$_slug = sanitize_text_field( $_POST[ $this->plugin_option_name ] );
if ( empty( $_slug ) ) {
$_slug = $this->default_slug;
}
update_option( $this->plugin_option_name, $_slug );
}
// Add custom input field to permalinks screen
add_settings_field( $this->plugin_option_name, __( '&quot;Latest post&quot; slug', 'eth_redirect_to_latest_post' ), array( $this, 'settings_field' ), 'permalink', 'optional' );
}
/**
* Render settings field
*/
public function settings_field() {
?>
<input type="text" name="<?php echo esc_attr( $this->plugin_option_name ); ?>" value="<?php echo esc_attr( $this->slug ); ?>" class="regular-text" />
<p class="description"><?php printf( __( 'Set the slug that will redirect to the latest published post. The default value is %s.', 'eth_redirect_to_latest_post' ), '<code style="font-style: normal;">' . $this->default_slug . '</code>' ); ?></p>
<?php
}
}
/**
* Load plugin.
*
* @package ETH_Redirect_To_Latest_Post.
*/
/**
* One instance to rule them all
* Plugin Name: ETH Redirect to Latest Post
* Plugin URI: https://ethitter.com/plugins/
* Description: Redirect a chosen slug to the whatever is currently the latest post
* Author: Erick Hitter
* Version: 0.3
* Author URI: https://ethitter.com/
* Text Domain: eth_redirect_to_latest_post
* Domain Path: /languages/
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
ETH_Redirect_To_Latest_Post::get_instance();
require_once __DIR__ . '/inc/class-eth-redirect-to-latest-post.php';
<?php
/**
* Plugin functionality.
*
* @package ETH_Redirect_To_Latest_Post
*/
/**
* Class ETH_Redirect_To_Latest_Post.
*/
class ETH_Redirect_To_Latest_Post {
/**
* PLUGIN SETUP
*/
/**
* Singleton.
*
* @var self
*/
private static $instance;
/**
* Instantiate singleton.
*/
public static function get_instance() {
if ( ! is_a( self::$instance, __CLASS__ ) ) {
self::$instance = new self();
}
return self::$instance;
}
/**
* Dummy magic method.
*/
public function __clone() {
_doing_it_wrong( __FUNCTION__, esc_html__( 'Cheatin&#8217; uh?', 'eth_redirect_to_latest_post' ), '0.1' );
}
/**
* Dummy magic method.
*/
public function __wakeup() {
_doing_it_wrong( __FUNCTION__, esc_html__( 'Cheatin&#8217; uh?', 'eth_redirect_to_latest_post' ), '0.1' );
}
/**
* Dummy magic method.
*
* @param string $name Method name.
* @param array $args Method arguments.
* @return null
*/
public function __call( $name = '', $args = array() ) {
return null;
}
/**
* Plugin's option name.
*
* @var string
*/
private $plugin_option_name = 'eth-redirect-to-latest';
/**
* Plugin's slug.
*
* @var string
*/
private $slug = '';
/**
* Plugin's fallback slug.
*
* @var string
*/
private $default_slug = '';
/**
* Register plugin's setup action.
*/
private function __construct() {
add_action( 'init', array( $this, 'action_init' ) );
add_action( 'parse_request', array( $this, 'action_parse_request' ) );
add_action( 'admin_init', array( $this, 'action_admin_init' ) );
}
/**
* Translate plugin slug.
*/
public function action_init() {
$this->default_slug = __( 'latest', 'eth_redirect_to_latest_post' );
$_slug = get_option( $this->plugin_option_name, $this->default_slug );
if ( is_string( $_slug ) && ! empty( $_slug ) ) {
$this->slug = $_slug;
}
if ( empty( $this->slug ) ) {
$this->slug = $this->default_slug;
}
}
/**
* Redirect to the latest post any requests made to plugin's slug.
*
* @param WP $r WP object.
*/
public function action_parse_request( $r ) {
// Nothing to do if permalinks aren't enabled.
if ( ! $r->did_permalink ) {
return;
}
$redirect = $this->get_redirect_for_request( $r );
if ( null === $redirect ) {
return;
}
header( 'x-redirect: eth-redirect-to-latest-post' );
// Not validating in case other plugins redirect elsewhere.
// phpcs:ignore WordPress.Security.SafeRedirect.wp_redirect_wp_redirect
wp_redirect( $redirect->destination, $redirect->status_code );
exit;
}
/**
* Parse the request to determine its redirect, if any.
*
* @param WP $r WP object.
* @return \stdClass|null
*/
public function get_redirect_for_request( $r ) {
/**
* Check if request is for our slug.
*
* The first condition catches hierarchical permastructs, while
* the second catches non-hierarchical permastructs.
*/
if ( isset( $r->query_vars['pagename'] ) && $this->slug === $r->query_vars['pagename'] ) {
$should_intercept = true;
} elseif ( isset( $r->query_vars['name'] ) && $this->slug === $r->query_vars['name'] ) {
$should_intercept = true;
} else {
$should_intercept = false;
}
if ( ! $should_intercept ) {
return null;
}
$redirect = array(
'destination' => '',
'status_code' => 302,
);
$query_args = array(
'posts_per_page' => 1,
'post_type' => 'post',
'orderby' => 'date',
'order' => 'desc',
'suppress_filters' => false,
'no_found_rows' => true,
);
/**
* Filters the query arguments that determine
* the latest post.
*
* @param array $query_args WP_Query arguments.
* @param WP $r WP Object.
* @return array
*/
$query_args = apply_filters( 'eth_redirect_to_latest_post_query_args', $query_args, $r );
$latest = get_posts( $query_args );
if ( is_array( $latest ) && ! empty( $latest ) ) {
$latest = array_shift( $latest );
$redirect['destination'] = get_permalink( $latest->ID );
}
if ( empty( $redirect['destination'] ) ) {
$redirect['destination'] = user_trailingslashit( home_url() );
}
/**
* Filters the redirection data.
*
* @param array $redirect Array of redirect destination and status code.
* @param array|WP_Post $latest Post object or empty array if no posts found.
* @param WP $r WP object.
* @return array|null
*/
$redirect = apply_filters( 'eth_redirect_to_latest_post_redirection', $redirect, $latest, $r );
if ( null === $redirect ) {
return null;
}
return (object) $redirect;
}
/**
* ADMIN OPTIONS
*/
/**
* Save plugin settings and register settings field
*
* Permalinks screen is a snowflake, hence the custom saving handler.
*/
public function action_admin_init() {
// Make sure user has necessary permissions first.
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
// Save custom option, permalinks screen is a snowflake and doesn't fully use the Settings API.
global $pagenow;
if ( 'options-permalink.php' === $pagenow && isset( $_POST[ $this->plugin_option_name ] ) ) {
check_admin_referer( 'update-permalink' );
$_slug = sanitize_text_field( $_POST[ $this->plugin_option_name ] );
if ( empty( $_slug ) ) {
$_slug = $this->default_slug;
}
update_option( $this->plugin_option_name, $_slug );
}
// Add custom input field to permalinks screen.
add_settings_field( $this->plugin_option_name, __( '&quot;Latest post&quot; slug', 'eth_redirect_to_latest_post' ), array( $this, 'settings_field' ), 'permalink', 'optional' );
}
/**
* Render settings field.
*/
public function settings_field() {
?>
<input type="text" name="<?php echo esc_attr( $this->plugin_option_name ); ?>" value="<?php echo esc_attr( $this->slug ); ?>" class="regular-text" />
<p class="description">
<?php
printf(
/* translators: 1. Default slug, wrapped in a <code> tag. */
esc_html__(
'Set the slug that will redirect to the latest published post. The default value is %s.',
'eth_redirect_to_latest_post'
),
'<code style="font-style: normal;">' . esc_html( $this->default_slug ) . '</code>'
);
?>
</p>
<?php
}
}
/**
* One instance to rule them all.
*/
ETH_Redirect_To_Latest_Post::get_instance();
......@@ -2,10 +2,10 @@
# This file is distributed under the same license as the ETH Redirect to Latest Post package.
msgid ""
msgstr ""
"Project-Id-Version: ETH Redirect to Latest Post 0.2.2\n"
"Project-Id-Version: ETH Redirect to Latest Post 0.3\n"
"Report-Msgid-Bugs-To: "
"https://wordpress.org/support/plugin/eth-redirect-to-latest\n"
"POT-Creation-Date: 2019-04-14 03:43:53+00:00\n"
"POT-Creation-Date: 2019-05-13 01:45:54+00:00\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
......@@ -25,19 +25,21 @@ msgstr ""
"X-Poedit-Bookmarks: \n"
"X-Textdomain-Support: yes\n"
#: eth-redirect-to-latest.php:51 eth-redirect-to-latest.php:52
#: inc/class-eth-redirect-to-latest-post.php:38
#: inc/class-eth-redirect-to-latest-post.php:45
msgid "Cheatin&#8217; uh?"
msgstr ""
#: eth-redirect-to-latest.php:76
#: inc/class-eth-redirect-to-latest-post.php:94
msgid "latest"
msgstr ""
#: eth-redirect-to-latest.php:166
#: inc/class-eth-redirect-to-latest-post.php:239
msgid "&quot;Latest post&quot; slug"
msgstr ""
#: eth-redirect-to-latest.php:176
#: inc/class-eth-redirect-to-latest-post.php:253
#. translators: 1. Default slug, wrapped in a <code> tag.
msgid ""
"Set the slug that will redirect to the latest published post. The default "
"value is %s."
......
......@@ -4,15 +4,15 @@ Donate link: https://ethitter.com/donate/
Tags: latest, latest post, redirect, redirect latest, redirect post
Requires at least: 4.5
Tested up to: 5.2
Stable tag: 0.2.2
Stable tag: 0.3
License: GPLv2 or later
License URI: http://www.gnu.org/licenses/gpl-2.0.html
Redirect a chosen slug, "latest" by default, to, well, the most-recently published post.
Redirect a chosen slug, "latest" by default, to, well, the most-recently-published post.
== Description ==
Once activated, a given slug will redirect to whatever is the most recently-published post on the site. By default, the slug is `latest`, but it can be changed from the Permalinks settings screen.
Once activated, a given slug will redirect to whatever is the most-recently-published post on the site. By default, the slug is `latest`, but it can be changed from the Permalinks settings screen.
== Installation ==
......@@ -20,8 +20,18 @@ Once activated, a given slug will redirect to whatever is the most recently-publ
1. Activate the plugin through the 'Plugins' screen in WordPress.
1. If desired, change the slug from the 'Permalinks' screen in WordPress.
== Frequently Asked Questions ==
= Can I redirect to something other than a post? =
Yes, using the `eth_redirect_to_latest_post_query_args` or `eth_redirect_to_latest_post_redirection` filters introduced in v0.3.
== Changelog ==
= 0.3 =
* Introduce filters to make redirection more flexible.
* Add unit tests and conform to coding standards.
= 0.2.2 =
* Handle sites using slug-only permalinks
......
......@@ -23,6 +23,9 @@ require_once $_tests_dir . '/includes/functions.php';
* Manually load the plugin being tested.
*/
function _manually_load_plugin() {
// Plugin requires pretty permalinks to function.
_set_default_permalink_structure_for_tests();
require dirname( dirname( __FILE__ ) ) . '/eth-redirect-to-latest.php';
}
tests_add_filter( 'muplugins_loaded', '_manually_load_plugin' );
......
<?php
/**
* Class PluginTest.
*
* @package ETH_Redirect_To_Latest_Post
*/
/**
* Plugin test case.
*/
class PluginTest extends WP_UnitTestCase {
/**
* ID of latest post.
*
* @var int
*/
protected static $latest_id;
/**
* Create some objects with various dates, so something
* can be considered "latest."
*
* We create an assortment of posts and pages with the
* slug used for redirection so that we can confirm that
* redirection takes priority.
*/
public function setUp() {
parent::setUp();
$this->factory->post->create_many(
10,
array(
'post_date' => date( 'Y-m-d H:i:s', strtotime( '-1 minute' ) ),
'post_name' => 'latest',
)
);