...
 
Commits (8)
...@@ -2,6 +2,7 @@ variables: ...@@ -2,6 +2,7 @@ variables:
# Configure mysql service (https://hub.docker.com/_/mysql/) # Configure mysql service (https://hub.docker.com/_/mysql/)
MYSQL_DATABASE: wordpress_tests MYSQL_DATABASE: wordpress_tests
MYSQL_ROOT_PASSWORD: mysql MYSQL_ROOT_PASSWORD: mysql
WP_VERSION: latest
cache: cache:
paths: paths:
...@@ -10,7 +11,7 @@ cache: ...@@ -10,7 +11,7 @@ cache:
before_script: before_script:
# Set up WordPress tests # 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 # PHPUnit
- | - |
...@@ -20,42 +21,68 @@ before_script: ...@@ -20,42 +21,68 @@ before_script:
composer global require "phpunit/phpunit=4.8.*" composer global require "phpunit/phpunit=4.8.*"
fi fi
# Install PHPCS and WPCS PHPunit:PHP5.3:MySQL:
- composer global require automattic/vipwpcs stage: test
- phpcs --config-set installed_paths $HOME/.composer/vendor/wp-coding-standards/wpcs,$HOME/.composer/vendor/automattic/vipwpcs variables:
WP_VERSION: '5.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
test_7.0: 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 image: containers.ethitter.com:443/docker/images/php:7.0
services: services:
- mysql:5.6 - mysql:5.6
script: script:
- find . -type "f" -iname "*.php" | xargs -L "1" php -l - find . -type "f" -iname "*.php" | xargs -L "1" php -l
- phpcs -n
- phpunit - phpunit
artifacts:
reports:
junit: /tmp/wordpress-tests-lib/tests/phpunit/build/logs/junit.xml
test_7.1: PHPunit:PHP7.1:MySQL:
stage: test
image: containers.ethitter.com:443/docker/images/php:7.1 image: containers.ethitter.com:443/docker/images/php:7.1
services: services:
- mysql:5.6 - mysql:5.6
script: script:
- find . -type "f" -iname "*.php" | xargs -L "1" php -l - find . -type "f" -iname "*.php" | xargs -L "1" php -l
- phpcs -n
- phpunit - phpunit
artifacts:
reports:
junit: /tmp/wordpress-tests-lib/tests/phpunit/build/logs/junit.xml
test_7.2: PHPunit:PHP7.2:MySQL:
stage: test
image: containers.ethitter.com:443/docker/images/php:7.2 image: containers.ethitter.com:443/docker/images/php:7.2
services: services:
- mysql:5.6 - mysql:5.6
script: script:
- find . -type "f" -iname "*.php" | xargs -L "1" php -l - find . -type "f" -iname "*.php" | xargs -L "1" php -l
- phpcs -n
- phpunit - phpunit
artifacts:
reports: PHPunit:PHP7.3:MySQL:
junit: /tmp/wordpress-tests-lib/tests/phpunit/build/logs/junit.xml stage: test
image: containers.ethitter.com:443/docker/images/php:7.3
services:
- mysql:5.6
script:
- find . -type "f" -iname "*.php" | xargs -L "1" php -l
- phpunit
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
...@@ -3,7 +3,6 @@ ...@@ -3,7 +3,6 @@
**Donate link:** https://ethitter.com/donate/ **Donate link:** https://ethitter.com/donate/
**Tags:** image, proxy, cdn **Tags:** image, proxy, cdn
**Requires at least:** 4.9 **Requires at least:** 4.9
**Requires PHP:** 7.0
**Tested up to:** 4.9 **Tested up to:** 4.9
**Stable tag:** 0.1.0 **Stable tag:** 0.1.0
**License:** GPLv2 or later **License:** GPLv2 or later
......
...@@ -13,58 +13,3 @@ ...@@ -13,58 +13,3 @@
*/ */
namespace Camo_Image_Proxy; namespace Camo_Image_Proxy;
const PLUGIN_PATH = __DIR__;
/**
* Trait for singletons
*/
require_once PLUGIN_PATH . '/inc/trait-singleton.php';
/**
* Plugin options
*/
require_once PLUGIN_PATH . '/inc/class-options.php';
/**
* Options page
*/
require_once PLUGIN_PATH . '/inc/class-options-page.php';
/**
* URL Building
*/
require_once PLUGIN_PATH . '/inc/class-url.php';
/**
* Rewrite WordPress-generated URLs
*/
require_once PLUGIN_PATH . '/inc/class-rewrite-urls.php';
/**
* Rewrite URLs in post content
*/
require_once PLUGIN_PATH . '/inc/class-rewrite-content.php';
/**
* Assorted functions
*/
require_once PLUGIN_PATH . '/inc/functions.php';
/**
* Load plugin singletons
*/
function init() {
Options::instance();
URL::instance();
if ( is_admin() ) {
Options_Page::instance();
}
if ( URL::instance()->can_rewrite() ) {
Rewrite_URLs::instance();
Rewrite_Content::instance();
}
}
add_action( 'init', __NAMESPACE__ . '\init' );
<?php
/**
* Plugin options page
*
* @package Camo_Image_Proxy
*/
namespace Camo_Image_Proxy;
/**
* Class Options_Page
*/
class Options_Page {
use Singleton;
/**
* Settings screen section
*
* @var string
*/
private $section = 'camp-image-proxy';
/**
* Field labels
*
* @var array
*/
private $labels = [];
/**
* Option name
*
* @var string
*/
private $name;
/**
* Hooks
*/
public function setup() {
$this->name = Options::instance()->name;
$this->labels['host'] = __( 'Host', 'camo-image-proxy' );
$this->labels['key'] = __( 'Shared Key', 'camo-image-proxy' );
add_action( 'admin_init', [ $this, 'action_admin_init' ] );
}
/**
* Add fields to Media settings page
*/
public function action_admin_init() {
register_setting( 'media', $this->name, [ Options::instance(), 'sanitize_all' ] );
add_settings_section( $this->section, __( 'Camo Image Proxy', 'camo-image-proxy' ), '__return_false', 'media' );
foreach ( $this->labels as $key => $label ) {
$args = [
'option' => $key,
'label' => $label,
];
add_settings_field( $key, $label, [ $this, 'screen' ], 'media', $this->section, $args );
}
}
/**
* Render options field
*
* @param array $args Field arguments.
*/
public function screen( $args ) {
$value = Options::instance()->get( $args['option'] );
$input_type = 'host' === $args['option'] ? 'url' : 'text';
$name = sprintf( '%1$s[%2$s]', $this->name, $args['option'] );
$html_id = sprintf( '%1$s-%2$s', str_replace( '_', '-', $this->name ), $args['option'] );
?>
<input
type="<?php echo esc_attr( $input_type ); ?>"
name="<?php echo esc_attr( $name ); ?>"
class="regular-text"
id="<?php echo esc_attr( $html_id ); ?>"
value="<?php echo esc_attr( $value ); ?>"
/>
<?php
}
}
<?php
/**
* Plugin options
*
* @package Camo_Image_Proxy
*/
namespace Camo_Image_Proxy;
/**
* Class Options
*/
class Options {
use Singleton;
/**
* Option name
*
* @var string
*/
private $name = 'camo_image_proxy_opts';
/**
* Allowed options
*
* @var array
*/
private $allowed_options = [
'host' => '',
'key' => '',
];
/**
* Access certain private properties
*
* @param string $name Property name.
* @return mixed
*/
public function __get( string $name ) {
switch ( $name ) {
case 'name':
return $this->name;
default:
return new \WP_Error( 'invalid-property', __( 'Invalid property requested.', 'camo-image-proxy' ), $name );
}
}
/**
* Retrieve full plugin options
*
* @return array
*/
private function get_all() : array {
$options = get_option( $this->name, [] );
$options = wp_parse_args( $options, $this->allowed_options );
return $options;
}
/**
* Get plugin option
*
* @param string $option Plugin option to retrieve.
* @return mixed
*/
public function get( string $option ) {
if ( ! array_key_exists( $option, $this->allowed_options ) ) {
return false;
}
$options = $this->get_all();
return $options[ $option ] ?? false;
}
/**
* Set plugin option
*
* @param string $option Plugin option to set.
* @param mixed $value Option value.
* @return bool
*/
public function set( string $option, $value ) : bool {
$value = $this->sanitize( $option, $value );
$options = $this->get_all();
$options[ $option ] = $value;
return update_option( $this->name, $options );
}
/**
* Sanitize option
*
* @param string $option Plugin option.
* @param mixed $value Option value to sanitize.
* @return mixed
*/
public function sanitize( string $option, $value ) {
switch ( $option ) {
case 'host':
$value = esc_url( $value );
if ( ! empty( $value ) ) {
$value = untrailingslashit( $value );
}
break;
case 'key':
$value = sanitize_text_field( $value );
break;
default:
return false;
}
return $value;
}
/**
* Sanitize array of options
*
* @param array $options Options to sanitize.
* @return array
*/
public function sanitize_all( array $options ) : array {
foreach ( $options as $option => $value ) {
$options[ $option ] = $this->sanitize( $option, $value );
}
return $options;
}
}
<?php
/**
* Rewrite images in content
*
* @package Camo_Image_Proxy
*/
namespace Camo_Image_Proxy;
/**
* Class Rewrite_Content
*/
class Rewrite_Content {
use Singleton;
/**
* Filter priority
*
* @var int
*/
private $priority;
/**
* Hooks
*/
public function setup() {
$priority = apply_filters( 'camo_image_proxy_rewrite_content_priority', PHP_INT_MAX - 1 );
$this->priority = absint( $priority );
add_filter( 'the_content', [ $this, 'filter_the_content' ], $this->priority );
}
/**
* Rewrite image URLs in content
*
* @param string $content Post content.
* @return string
*/
public function filter_the_content( string $content ) : string {
// TODO: only deal with image srcs, use DOM Document.
return $content;
}
}
<?php
/**
* Force Core's image functions to use Camo
*
* @package Camo_Image_Proxy
*/
namespace Camo_Image_Proxy;
/**
* Class Rewrite_URLs
*/
class Rewrite_URLs {
use Singleton;
/**
* Hooks
*/
public function setup() {
add_filter( 'wp_get_attachment_image_src', [ $this, 'encode_image' ] );
}
/**
* Camouflage attachment URL
*
* @param array $image Image data.
* @return array
*/
public function encode_image( array $image ) : array {
$url = URL::instance()->encode( $image[0] );
if ( is_string( $url ) ) {
$image[0] = $url;
}
return $image;
}
}
<?php
/**
* URL Building
*
* @package Camo_Image_Proxy
*/
namespace Camo_Image_Proxy;
/**
* Class URL
*/
class URL {
use Singleton;
/**
* Can URLs be rewritten to use Camo?
*
* @return bool
*/
public function can_rewrite() : bool {
// Never rewrite in admin.
if ( is_admin() ) {
return false;
}
$host = Options::instance()->get( 'host' );
$key = Options::instance()->get( 'key' );
$can_rewrite = true;
// Validate host.
if ( ! $this->is_valid_url( $host ) ) {
$can_rewrite = false;
}
// Validate key.
// TODO: make sure it's an HMAC or something?
if ( empty( $key ) || ! is_string( $key ) ) {
$can_rewrite = false;
}
return apply_filters( 'camo_image_proxy_can_rewrite', $can_rewrite, $host, $key );
}
/**
* Encode image URL
*
* @param string $url Image URL to encode.
* @return string|bool
*/
public function encode( string $url ) : string {
if ( ! $this->can_rewrite() || ! $this->is_valid_url( $url ) ) {
return false;
}
$key = hash_hmac( 'sha1', $url, Options::instance()->get( 'key' ) );
$url_encoded = bin2hex( $url );
$url_encoded = sprintf( '%1$s/%2$s/%3$s', Options::instance()->get( 'host' ), $key, $url_encoded );
$url_encoded = set_url_scheme( $url_encoded, 'https' );
return $url_encoded;
}
/**
* Decode encoded URL
*
* @param string $url Camo URL to decode.
* @return string|bool
*/
public function decode( string $url ) : string {
return false;
}
/**
* Can we encode this URL?
*
* @param string $url URL to validate.
* @return bool
*/
private function is_valid_url( string $url ) : bool {
if ( empty( $url ) ) {
return false;
}
if ( false === filter_var( $url, FILTER_VALIDATE_URL ) && false === filter_var( $url, FILTER_VALIDATE_IP ) ) {
return false;
}
return true;
}
}
<?php
/**
* Assorted helpers
*
* @package Camo_Image_Proxy
*/
namespace Camo_Image_Proxy;
<?php
/**
* Trait file for Singletons.
*
* @package Camo_Image_Proxy
*/
namespace Camo_Image_Proxy;
/**
* Make a class into a singleton.
*/
trait Singleton {
/**
* Existing instance.
*
* @var object
*/
protected static $instance;
/**
* Get class instance.
*
* @return object
*/
public static function instance() {
if ( ! isset( static::$instance ) ) {
static::$instance = new static();
static::$instance->setup();
}
return static::$instance;
}
/**
* Setup the singleton.
*/
public function setup() {
// Silence.
}
}
...@@ -4,7 +4,7 @@ msgid "" ...@@ -4,7 +4,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Camo Image Proxy 0.1.0\n" "Project-Id-Version: Camo Image Proxy 0.1.0\n"
"Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/camo-image-proxy\n" "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/camo-image-proxy\n"
"POT-Creation-Date: 2018-02-19 01:54:23+00:00\n" "POT-Creation-Date: 2018-02-18 19:07:54+00:00\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n" "Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
...@@ -24,22 +24,10 @@ msgstr "" ...@@ -24,22 +24,10 @@ msgstr ""
"X-Poedit-Bookmarks: \n" "X-Poedit-Bookmarks: \n"
"X-Textdomain-Support: yes\n" "X-Textdomain-Support: yes\n"
#: inc/class-options-page.php:43
msgid "Host"
msgstr ""
#: inc/class-options-page.php:44
msgid "Shared Key"
msgstr ""
#. Plugin Name of the plugin/theme #. Plugin Name of the plugin/theme
msgid "Camo Image Proxy" msgid "Camo Image Proxy"
msgstr "" msgstr ""
#: inc/class-options.php:45
msgid "Invalid property requested."
msgstr ""
#. Plugin URI of the plugin/theme #. Plugin URI of the plugin/theme
msgid "https://ethitter.com/plugins/" msgid "https://ethitter.com/plugins/"
msgstr "" msgstr ""
......
...@@ -3,7 +3,6 @@ Contributors: ethitter ...@@ -3,7 +3,6 @@ Contributors: ethitter
Donate link: https://ethitter.com/donate/ Donate link: https://ethitter.com/donate/
Tags: image, proxy, cdn Tags: image, proxy, cdn
Requires at least: 4.9 Requires at least: 4.9
Requires PHP: 7.0
Tested up to: 4.9 Tested up to: 4.9
Stable tag: 0.1.0 Stable tag: 0.1.0
License: GPLv2 or later License: GPLv2 or later
......