Commit 2df78223 authored by Sean O'Shaughnessy's avatar Sean O'Shaughnessy
Browse files

Merge branch 'develop'

parents fab51bf0 d4722347
<?php
/*
Plugin Name: CDN Rewrite
Plugin URI: http://voceconnect.com/
Description: Rewrites asset URLs to CDN
Version: 0.1
Author: Chris Scott, Michael Pretty
Author URI: http://voceconnect.com/
*/
require_once('voce-settings.php');
class CDN_Rewrite {
const OPTION_GENERAL = 'cdn_general';
private $submenu_general;
private $file_extensions;
private $blog_details;
private $cdn_root_url;
public function __construct() {
$this->file_extensions = $this->get_setting('file_extensions');
$this->cdn_root_url = untrailingslashit($this->get_setting('root_url'));
}
public function initialize() {
if (!class_exists('Voce_Settings')) {
return;
}
add_action('admin_menu', array($this, 'add_options_page'));
if ('' == $this->file_extensions || '' == $this->cdn_root_url) {
add_action('admin_notices', array($this, 'settings_warning'));
return;
}
if('/' != $this->cdn_root_url) {
add_action('template_redirect', array($this, 'start_buffer'), 1);
}
}
/**
* get general setting
*
* @param string $setting setting name
* @return mixed setting value or false if not set
*/
private function get_setting($setting) {
$settings = get_option(self::OPTION_GENERAL);
if(!$settings || !is_array($settings)) {
$settings = array(
'file_extensions' => 'bmp|bz2|css|gif|ico|gz|jpg|jpeg|js|mp3|pdf|png|rar|rtf|swf|tar|tgz|txt|wav|zip'
);
update_option(self::OPTION_GENERAL, $settings);
}
return (isset($settings[$setting])) ? $settings[$setting] : false;
}
public function settings_warning() {
echo "<div class='update-nag'>The CDN Rewrite plugin is missing some required settings.</div>";
}
/**
* adds the options page
*
* @return void
*/
public function add_options_page() {
$this->submenu_general = add_options_page('CDN Rewrite', 'CDN Rewrite', 'manage_options', self::OPTION_GENERAL, array($this, 'submenu_general'));
$settings = new Voce_Settings(self::OPTION_GENERAL, self::OPTION_GENERAL);
$section = $settings->add_section('api', 'CDN Rewrite Settings', $this->submenu_general);
$section->add_field('root_url', 'CDN Root URL (required)', 'field_input', array('description' => 'The base URL of the CDN.'));
$section->add_field('file_extensions', 'File Extensions (required)', 'field_input');
}
/**
* callback to display submenu_external
*
* @return void
*/
function submenu_general() {
?>
<div class="wrap">
<h2>CDN Rewrite Settings</h2>
<form method="post" action="options.php">
<?php settings_fields(self::OPTION_GENERAL); ?>
<?php do_settings_sections($this->submenu_general); ?>
<p class="submit">
<input name="Submit" type="submit" class="button-primary" value="<?php esc_attr_e('Save Changes'); ?>" />
</p>
</form>
</div>
<?php
}
/**
* start output buffering.
*
*/
public function start_buffer() {
ob_start(array($this, 'filter_urls'));
}
/**
* Callback for output buffering. Search content for urls to replace
*
* @param string $content
* @return string
*/
public function filter_urls($content) {
$root_url = $this->get_site_root_url();
$regex = '#(?<=[(\"\'])'.quotemeta($root_url).'(?:(/[^\"\')]+\.('.$this->file_extensions.')))#';
$content = preg_replace_callback($regex, array($this, 'url_rewrite'), $content);
return $content;
}
/**
* Returns the root url of the current site
*
* @return string
*/
public function get_site_root_url() {
if(is_multisite() && !is_subdomain_install()) {
$root_blog = get_blog_details(1);
$root_url = $root_blog->siteurl;
} else {
$root_url = site_url();
}
return $root_url;
}
/**
* Returns the details for the current blog
*
* @return object
*/
public function get_this_blog_details() {
if(!isset($this->blog_details)) {
global $blog_id;
$this->blog_details = get_blog_details($blog_id);
}
return $this->blog_details;
}
/**
* Callback for url preg_replace_callback. Returns corrected URL
*
* @param array $match
* @return string
*/
public function url_rewrite($match) {
global $blog_id;
$path = $match[1];
//if is subfolder install and isn't root blog and path starts with site_url and isnt uploads dir
if(is_multisite() && !is_subdomain_install() && $blog_id !== 1) {
$bloginfo = $this->get_this_blog_details();
if((0 === strpos($path, $bloginfo->path)) && (0 !== strpos($path, $bloginfo->path.'files/'))) {
$path = '/'.substr($path, strlen($bloginfo->path));
}
}
return $this->cdn_root_url . $path;
}
}
add_action('init', array(new CDN_Rewrite(), 'initialize'));
class CDN_VersionAssets {
private $default_version = '';
private $root_url;
public function __construct() {
$this->root_url = site_url();
}
public function initialize() {
add_filter('style_loader_src', array($this, 'replace_version'), 10);
add_filter('script_loader_src', array($this, 'replace_version'), 10);
add_filter('stylesheet_uri', array($this, 'replace_version'), 10);
}
public function on_template_redirect() {
$this->default_version = @filemtime(get_stylesheet_directory().'/style.css');
}
private function get_version($url) {
if(0 === strpos($url, $this->root_url)) {
$parts = parse_url($url);
$file_path = str_replace(site_url('/'), ABSPATH, $parts['scheme'].'://'.$parts['host'].$parts['path']);
if( !($version = @filemtime($file_path)) ) {
$version = $this->default_version;
}
return $version;
}
return false;
}
public function replace_version($src) {
if( $new_version = $this->get_version($src) ) {
return add_query_arg('ver', $new_version, $src);
}
return $src;
}
}
add_action('init', array(new CDN_VersionAssets(), 'initialize'));
\ No newline at end of file
<?php
/*
Plugin Name: Cloudfiles CDN
Plugin URI: http://voceconnect.com/
Description: Adds/Deletes uploaded images on CDN and rewrites asset URLs to CDN
Version: 0.1
Author: Chris Scott, Michael Pretty
Author URI: http://voceconnect.com/
*/
class CloudfilesCdn {
function __construct() {
if (!defined('CF_USERNAME') || !defined('CF_API_KEY') || !defined('CF_CONTAINER') || !defined('CF_CONTAINER_URL')) {
// these should be defined in wp-config. if not, don't do anything here.
return;
}
add_filter('wp_handle_upload', array('CloudfilesCdn', 'catch_wp_handle_upload'));
add_filter('wp_delete_file', array('CloudfilesCdn', 'catch_wp_delete_file'));
add_filter('wp_generate_attachment_metadata', array('CloudfilesCdn', 'catch_wp_generate_attachment_metadata'));
add_filter('bp_core_avatar_cropstore', array('CloudfilesCdn', 'catch_bp_core_avatar_cropstore'));
add_action('bp_core_avatar_save', array('CloudfilesCdn', 'catch_bp_core_avatar_save'), 10, 2);
}
/**
* delete old BP avatars on save
*
* @param string $user_id not used
* @param string $old the path to the file being deleted
* @return void
*/
public static function catch_bp_core_avatar_save($user_id, $old) {
$files = array();
// this will be -avatar2
$files[] = str_replace(ABSPATH, '', $old);
// get -avatar1 also
$files[] = str_replace('-avatar2', '-avatar1', $files[0]);
foreach ($files as $file) {
if (defined('CF_DEBUG_ENABLED') && CF_DEBUG_ENABLED)
error_log("DELETING OLD BP AVATAR: $file");
self::delete_file($file);
}
}
/**
* when BP avatars are cropped, catch the cropped sizes and upload
*
* @param string $files
* @return array the original file array
*/
public static function catch_bp_core_avatar_cropstore($files) {
foreach ((array) $files as $file) {
$relative_file_path = str_replace(ABSPATH, '', $file);
$file_type = wp_check_filetype($file);
if (defined('CF_DEBUG_ENABLED') && CF_DEBUG_ENABLED)
error_log("UPLOADING BP AVATAR: $relative_file_path");
self::upload_file($file, $file_type['type'], $relative_file_path);
}
return $files;
}
/**
* go grab the already generated intermediate sizes and upload
*
* @param string $metadata
* @return array updated metadata
*/
public static function catch_wp_generate_attachment_metadata($metadata) {
//error_log("WP_GENERATE_ATTACHMENT_METADATA: " . var_export($metadata, true));
$upload_dir = wp_upload_dir();
$upload_path = trailingslashit($upload_dir['path']);
$sizes = $metadata['sizes'];
foreach ($sizes as $size => $size_data ) {
$file = $size_data['file'];
$relative_file_path = self::get_blog_path() . 'files' . trailingslashit($upload_dir['subdir']) . $file;
$file_type = wp_check_filetype($file);
if (defined('CF_DEBUG_ENABLED') && CF_DEBUG_ENABLED)
error_log("UPLOADING INTERMEDIATE SIZE: $relative_file_path");
self::upload_file($upload_path . $file, $file_type['type'], $relative_file_path);
}
return $metadata;
}
/**
* get a local filename from a CDN URL
*
* @param string $url
* @return string filename
*/
private static function get_local_filename($url) {
return ABSPATH . str_replace(trailingslashit(CF_CONTAINER_URL), '', $url);
}
/**
* Filter to handle wp_handle_upload for uploaded files. Logs any errors.
*
* @param string $upload
* @return void
*/
public static function catch_wp_handle_upload($upload) {
// check for buddypress avatar upload and don't upload since it resizes and deletes this one
if (function_exists('bp_core_setup_globals') && strpos($upload['file'], '/avatars/') !== false) {
return $upload;
}
$blog_path = self::get_blog_path();
$relative_url = $blog_path . self::remove_site_url($upload['url']);
if (defined('CF_DEBUG_ENABLED') && CF_DEBUG_ENABLED)
error_log("UPLOADING: $relative_url");
// upload file
if (!self::upload_file($upload['file'], $upload['type'], $relative_url)) {
error_log("[CloudfilesCdn] Error uploading file: $relative_url");
return $upload;
}
return $upload;
}
/**
* Filter to handle wp_delete_file for deleted files. Deletes the file
* from the CDN.
*
* @param string $file
* @return string the original file path
*/
public static function catch_wp_delete_file($file) {
//error_log("WP_DELETE_FILE: " . var_export($file, true));
// handle 'wp-content/blogs.dir/1/files/2010/07/653106995_338e53fb1412.jpg' files
if (strpos($file, 'blogs.dir')) {
$parts = explode('files/', $file);
$file = $parts[1];
}
$upload_dir = wp_upload_dir();
$relative_path = self::get_blog_path() . 'files/' . $file;
// delete the file from the CDN if it is on there
if (defined('CF_DEBUG_ENABLED') && CF_DEBUG_ENABLED)
error_log("DELETING FILE: $relative_path");
self::delete_file(str_replace(ABSPATH, '', $relative_path));
return $file;
}
/**
* get the blog's path without the leading slash
*
* @return string
*/
private static function get_blog_path() {
global $blog_id;
$blog_path = '';
if ((int) $blog_id !== 1) {
$blog_details = get_blog_details($blog_id);
$blog_path = $blog_details->path;
}
return ltrim($blog_path, '/');
}
/**
* Get the relative filename. The filename from the wp_delete_file filter
* will either have the file path to the WP_UPLOAD_DIR prepended to the cdn'd file
* url or it will be the path relative to the WP_UPLOAD_DIR
*
* @param string $file
* @return void
*/
private static function get_relative_file($file) {
$cont_url = CF_CONTAINER_URL;
if (strpos($file, $cont_url) !== false) {
if ($file_parts = explode(trailingslashit(CF_CONTAINER_URL), $file)) {
// prepended w/bad upload path (e.g. http://c0002127.cdn1.cloudfiles.rackspacecloud.com/wp-content/uploads/2010/07/http://c0002127.cdn1.cloudfiles.rackspacecloud.com/wp-content/uploads/2010/07/653106995_338e53fb1416-150x150.jpg)
if (isset($file_parts[2])) {
return $file_parts[2];
} elseif (isset($file_parts[1])) {
// just regular CDN url
return $file_parts[1];
}
}
}
return false;
}
/**
* delete a file from the CDN
*
* @param string $file relative filename
* @return bool true on success, false on failure
*/
private static function delete_file($file) {
require_once(trailingslashit(dirname(__FILE__)) . 'cloudfiles/cloudfiles.php');
$auth = new CF_Authentication(CF_USERNAME, CF_API_KEY);
try {
$auth->authenticate();
} catch (Exception $e) {
error_log(sprintf("[CloudfilesCdn] Error authenticating to Cloudfiles: %s", $e->getMessage()));
return false;
}
$conn = new CF_Connection($auth);
$container = $conn->get_container(CF_CONTAINER);
try {
$obj = $container->get_object($file);
$container->delete_object($obj);
} catch (Exception $e) {
error_log(sprintf("[CloudfilesCdn] Error deleting file '%s' from Cloudfiles: %s", $file, $e->getMessage()));
}
return true;
}
/**
* Update the attachement URL if the attached file is on the CDN
*
* @param string $url
* @param string $post_id
* @return string original or updated URL
*/
public static function cdn_attachment_url($url, $post_id) {
if ($file = get_post_meta($post_id, '_wp_attached_file', true)) {
if (strpos($file, CF_CONTAINER_URL) !== false) {
return $file;
}
}
return $url;
}
/**
* Upload a file to cloudfiles
*
* @param string $file the file's path
* @param string $file_type the file's mime type
* @param string $file_url the file's site-relative URL
* @return bool true on succes, false on fail
*/
private static function upload_file($file, $file_type, $file_url) {
require_once(trailingslashit(dirname(__FILE__)) . 'cloudfiles/cloudfiles.php');
$auth = new CF_Authentication(CF_USERNAME, CF_API_KEY);
try {
$auth->authenticate();
} catch (Exception $e) {
error_log(sprintf("[CloudfilesCdn] Error authenticating to Cloudfiles: %s", $e->getMessage()));
return false;
}
$conn = new CF_Connection($auth);
$container = $conn->get_container(CF_CONTAINER);
$obj = $container->create_object($file_url);
$obj->content_type = $file_type;
try {
$obj->load_from_filename($file);
} catch (Exception $e) {
error_log(sprintf("[CloudfilesCdn] Error uploading '%s' to Cloudfiles: %s", $file, $e->getMessage()));
return false;
}
if (defined('CF_DEBUG_ENABLED') && CF_DEBUG_ENABLED)
error_log("UPLOADED: $file, $file_type, $file_url");
return true;
}
/**
* Get a site-relative url without a leading / from an absolute URL containing the siteurl
*
* @param string $absolute_url
* @return string relative url
*/
private static function remove_site_url($absolute_url) {
return str_replace(trailingslashit(get_option('siteurl')), '', $absolute_url);
}
private static function remove_cdn_url($url) {
return str_replace(trailingslashit(CF_CONTAINER_URL), '', $url);
}
}
$cdn = new CloudfilesCdn();
\ No newline at end of file
This diff is collapsed.
<?php
/**
* Custom Exceptions for the CloudFiles API
*
* Requres PHP 5.x (for Exceptions and OO syntax)
*
* See COPYING for license information.
*
* @author Eric "EJ" Johnson <ej@racklabs.com>
* @copyright Copyright (c) 2008, Rackspace US, Inc.
* @package php-cloudfiles-exceptions
*/
/**
* Custom Exceptions for the CloudFiles API
* @package php-cloudfiles-exceptions
*/
class SyntaxException extends Exception { }
class AuthenticationException extends Exception { }
class InvalidResponseException extends Exception { }
class NonEmptyContainerException extends Exception { }
class NoSuchObjectException extends Exception { }
class NoSuchContainerException extends Exception { }
class NoSuchAccountException extends Exception { }
class MisMatchedChecksumException extends Exception { }
class IOException extends Exception { }
class CDNNotEnabledException extends Exception { }
class BadContentTypeException extends Exception { }
class InvalidUTF8Exception extends Exception { }
class ConnectionNotOpenException extends Exception { }
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
/*
* Local variables:
* tab-width: 4
* c-basic-offset: 4
* c-hanging-comment-ender-p: nil
* End:
*/
?>
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
<?php
/* Copyright 2010 Chris Scott (cscott@voceconnect.com)
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License, version 2, as
published by the Free Software Foundation.
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
*/
if (!class_exists('Voce_Settings')) {
class Voce_Settings {
private $page_name;
private $option_name;
private $page;
private $section;
public function __construct($option_group, $option_name, $sanitize_callback = '') {
register_setting($option_group, $option_name, $sanitize_callback);
$this->option_name = $option_name;
}
public function add_section($name, $title, $page, $callback = null) {
// use blank output to allow not setting a callback if no output is desired between heading and fields
$callback = (null === $callback) ? create_function('', 'echo \'\';') : $callback;
add_settings_section($name, $title, $callback, $page);
$this->section = $name;
$this->page = $page;
return $this;
}
public function add_field($id, $label, $type, $args = null, $page = null, $section = null) {
$section = (null === $section) ? $this->section : $section;
$page = (null === $page) ? $this->page : $page;
$this->add_settings_field(
$id,
$label,
$type,
$page,
$section,
$args
);
}
/**
* helper to use the settings API to add a field
*
* @param string $id the HTML ID for the field
* @param string $label the label tag text
* @param string $display_callback the function to use for the field display
* @param string $extra_args extra args to pass to the callback
* @return void
*/
public function add_settings_field($id, $label, $display_callback, $page, $section, $extra_