'1', 'horizontal-pos' => '5', 'horizontal-pos-unit' => 'px', 'horizontal-pos-from' => 'left', 'vertical-pos' => '-5', 'vertical-pos-unit' => 'px', 'vertical-pos-from' => 'bottom', 'size' => 'contain', 'image' => '', 'width' => '200', 'width-unit' => 'px', 'width-max' => '80', 'width-max-unit' => '%', 'width-min' => '10', 'width-min-unit' => '%', 'height' => '100', 'height-unit' => 'px', 'height-max' => '80', 'height-max-unit' => '%', 'height-min' => '10', 'height-min-unit' => '%', 'exclude' => '^wpcf7.*', // Servers supporting this would make the plugin more secure/portable. 'supports_document_root' => false, 'supports_end' => false, // Do not use in production. 'debug' => false ); return $defaults; } function add_watermark_option($name) { $defaults = add_watermark_defaults(); if (!isset($defaults[$name])) { die("Unknown option: $name"); } return get_option("add-watermark-$name", $defaults[$name]); } function add_watermark_header($type) { if (add_watermark_option('debug')) { echo "Would now add header for $type\n"; } else { header("Content-Type: $type"); } } class AddWatermarksSettings { static function register() { $settings = new AddWatermarksSettings(); add_action( 'admin_init', array($settings, 'loadPositionSettings') ); $optionsPage = new AddWatermarkOptionsPage(); add_action( 'admin_menu', array($optionsPage, 'registerMenu')); add_action( 'admin_enqueue_scripts', array($optionsPage, 'scripts') ); add_filter('init', array($settings, 'htaccess')); add_filter('load-settings_page_add-watermark-menu', array($settings, 'onSettingsLoad')); add_filter('manage_media_columns', array($settings, 'addWatermarkColumn')); add_filter('manage_media_custom_column', array($settings, 'outputWatermarkColumn')); add_filter('admin_footer-upload.php', array($settings, 'outputAdminFooter')); add_action('load-upload.php', array($settings, 'doBulkAction')); add_action('plugins_loaded', array($settings, 'loadTextdomain')); // Does not seem to work :-( //add_action('update_attached_file ', array($settings, 'removeAttachmentFromCache')); add_action('delete_attachment', array('AddWatermarksSettings', 'removeAttachmentFromCache')); } function loadTextdomain() { load_plugin_textdomain('add-watermark', false, dirname( plugin_basename( __FILE__ ) ) . '/languages' ); } static function pluginActivate() { $settings = new AddWatermarksSettings(); $settings->writeHtaccessFile(); } static function pluginDeactivate() { $settings = new AddWatermarksSettings(); $settings->removeHtaccessFile(); AddWatermarkRequest::emptyCache(); } static function pluginUninstall() { foreach (add_watermark_defaults() as $name => $value) { delete_option('add-watermark-' . $name); } } static function removeAttachmentFromCache($attachmentId) { $meta = wp_get_attachment_metadata($attachmentId); if ($meta) { // < No meta for non-images. AddWatermarkRequest::removeFileFromCache($meta['file']); foreach ($meta['sizes'] as $name => $size) { AddWatermarkRequest::removeFileFromCache(dirname($meta['file']) . "/" . $size['file']); } } } function addWatermarkColumn($columns) { $columns['add-watermark'] = __("Watermark", 'add-watermark'); return $columns; } function watermarkYes($post_id) { update_metadata('post', $post_id, 'add-watermark', 'yes'); self::removeAttachmentFromCache($post_id); } function watermarkNo($post_id) { update_metadata('post', $post_id, 'add-watermark', 'no'); self::removeAttachmentFromCache($post_id); } function watermarkUnset($post_id) { delete_metadata('post', $post_id, 'add-watermark'); self::removeAttachmentFromCache($post_id); } function doBulkAction() { $wp_list_table = _get_list_table('WP_Media_List_Table'); $doaction = $wp_list_table->current_action(); if ( isset( $_REQUEST['media'] ) ) { $post_ids = $_REQUEST['media']; } elseif ( isset( $_REQUEST['ids'] ) ) { $post_ids = explode( ',', $_REQUEST['ids'] ); } $location = 'upload.php'; if ( $referer = wp_get_referer() ) { if ( false !== strpos( $referer, 'upload.php' ) ) $location = remove_query_arg( array( 'trashed', 'untrashed', 'deleted', 'message', 'ids', 'posted' ), $referer ); } if ($doaction == 'add_watermark_yes') { $action = array($this, 'watermarkYes'); } else if ($doaction == 'add_watermark_no') { $action = array($this, 'watermarkNo'); } else if ($doaction == 'add_watermark_unset') { $action = array($this, 'watermarkUnset'); } if ( isset($action)) { if ( !isset( $post_ids ) ) return; check_admin_referer('bulk-media'); array_map($action, $post_ids); wp_redirect( $location ); } return; } function outputAdminFooter() { ?> __('protect', 'add-watermark'), 'no' => __('unprotect', 'add-watermark'), 'unset' => __('default', 'add-watermark') ); if ($meta == 'yes') { $current = __('Protected', 'add-watermark'); unset($actions['yes']); } else if ($meta == 'no') { $current = __('Unprotected', 'add-watermark'); unset($actions['no']); } else { $current = __('Use default', 'add-watermark'); unset($actions['unset']); } echo '
' . esc_html($current) . '
'; echo '
'; // TODO: echo implode(' | ', array_map(array($this, 'generateActionLink'), array_keys($actions), array_values($actions))); echo '
'; } } function generateActionLink($action, $text) { return sprintf('%s', $action, get_the_ID(), $text); } function htaccess() { // add_rewrite_rule('wp-content/uploads/(.*?\\.(png|jpe?g))', 'wp-admin/admin-ajax.php?action=watermark_image&path=$1'); } function getUploadPath($fileName) { // Gets the uploads direcotry $upload_dir = wp_upload_dir(); return $upload_dir['basedir'] . "/" . $fileName; } /** * Write the htaccess file in the uploads directory. */ function writeHtaccessFile() { $file = $this->getUploadPath('.htaccess'); $content = file_get_contents($file); $content = preg_replace('=^\\n?### WATERMARK START([\w\W]*)### WATERMARK END=m', '', $content); $upload_dir = wp_upload_dir(); $upload_root = parse_url($upload_dir['baseurl']); if ( isset( $upload_root['path'] ) ) $upload_root = trailingslashit($upload_root['path']); else $upload_root = '/'; $cache_root = parse_url( plugins_url('cache', __FILE__) ); if ( !isset( $cache_root['path'] ) ) //TODO: Display error. return; $cache_root = trailingslashit($cache_root['path']); $exclude = add_watermark_option('exclude'); $exclude = preg_replace('/[\\s\\n]/', '\\s', $exclude); if (add_watermark_option('supports_document_root')) { // nice server. $docRoot = '%{DOCUMENT_ROOT}'; } else { // We hope this is the same as our document root. // For bad hosters (like 1und1) $docRoot = $_SERVER['DOCUMENT_ROOT']; } $cacheDir = $docRoot . $cache_root; //$cacheDir = AddWatermarkRequest::getCacheDir(); $content .= "\n### WATERMARK START"; $content .= "\nRewriteEngine On"; $content .= "\nRewriteBase $upload_root"; // Use cached files if they are there $content .= "\n" . 'RewriteCond ' . $docRoot . '%{REQUEST_URI} -f'; $content .= "\n" . 'RewriteCond $0 ^/?(.*\\.(jpe?g|png))$'; $content .= "\n" . 'RewriteCond ' . $cacheDir . '/%1 -f'; $end = add_watermark_option('supports_end') ? ',END' : ''; $content .= "\n" . 'RewriteRule (.*) ' . $cache_root . '/$1 [L' . $end . ']'; // If there is no don't watermark flag and it is an image, handle it. $content .= "\n" . 'RewriteCond $0 ^/?(.*\\.(jpe?g|png))$'; $content .= "\n" . 'RewriteCond ' . $cacheDir . '/%1.nowm !-f'; // User can set a regexp that should not get watermarked. if ($exclude) { $content .= "\n" . 'RewriteCond $0 !'.$exclude; } // Note: The uploads direcotry should not be moved outside wp-content, the admin directory not moved. $content .= "\n" . 'RewriteRule (.*) ../../wp-admin/admin-ajax.php?action=watermark_image&path=$1 [L]'; $content .= "\n### WATERMARK END"; file_put_contents($file, $content); } function removeHtaccessFile() { $file = $this->getUploadPath('.htaccess'); @unlink($file); } function storeSettings() { flush_rewrite_rules(); $this->writeHtaccessFile(); AddWatermarkRequest::emptyCache(); } function onSettingsLoad() { if(isset($_GET['settings-updated']) && $_GET['settings-updated']) { $this->storeSettings(); } wp_enqueue_media(); wp_enqueue_script( 'add-watermark-js', plugin_dir_url( __FILE__ ) . 'js/settings.js'); } function loadPositionSettings() { register_setting( 'add-watermark-settings', 'add-watermark-default-active' ); register_setting( 'add-watermark-settings', 'add-watermark-exclude' ); register_setting( 'add-watermark-settings', 'add-watermark-image' ); register_setting( 'add-watermark-settings', 'add-watermark-size' ); $this->registerUnitSelect('add-watermark-horizontal-pos'); register_setting( 'add-watermark-settings', 'add-watermark-horizontal-pos-from' ); $this->registerUnitSelect('add-watermark-vertical-pos'); register_setting( 'add-watermark-settings', 'add-watermark-vertical-pos-from' ); $this->registerMinMaxSize('add-watermark-width'); $this->registerMinMaxSize('add-watermark-height'); add_settings_section( 'add-watermark-general', __('General settings', 'add-watermark'), null, 'add-watermark-settings'); add_settings_field( 'add-watermark-default-active', __('Watermark images that do not have an explicit setting', 'add-watermark'), array($this, 'outputDefaultSelect'), 'add-watermark-settings', 'add-watermark-general'); add_settings_field( 'add-watermark-exclude', __('Files to exclude (regexp, relative to uploads dir)', 'add-watermark'), array($this, 'outputExclude'), 'add-watermark-settings', 'add-watermark-general'); add_settings_section( 'add-watermark-image', __('Watermark image', 'add-watermark'), null, 'add-watermark-settings'); add_settings_field( 'add-watermark-image', __('Image', 'add-watermark'), array($this, 'outputImageSelect'), 'add-watermark-settings', 'add-watermark-image'); add_settings_section( 'add-watermark-position', __('Watermark position', 'add-watermark'), array($this, 'addPositionDescription'), 'add-watermark-settings'); add_settings_field( 'add-watermark-size', __('Fit the image', 'add-watermark'), array($this, 'addSizeSelect'), 'add-watermark-settings', 'add-watermark-position'); add_settings_field( 'add-watermark-horizontal-pos', __('Horizontal position', 'add-watermark'), array($this, 'outputHorizontalPos'), 'add-watermark-settings', 'add-watermark-position'); add_settings_field( 'add-watermark-width', __('Width', 'add-watermark'), array($this, 'outputWidth'), 'add-watermark-settings', 'add-watermark-position'); add_settings_field( 'add-watermark-vertical-pos', __('Vertical Position', 'add-watermark'), array($this, 'outputVerticalPos'), 'add-watermark-settings', 'add-watermark-position'); add_settings_field( 'add-watermark-height', __('Height', 'add-watermark'), array($this, 'outputHeight'), 'add-watermark-settings', 'add-watermark-position'); } function addPositionDescription() { ?>
outputUnitSelect('horizontal-pos'); ?> outputMinMaxSize('width'); } function outputVerticalPos() { $setting = add_watermark_option("vertical-pos-from"); ?> outputUnitSelect('vertical-pos'); ?> outputMinMaxSize('height'); } function registerUnitSelect($name) { register_setting( 'add-watermark-settings', $name ); register_setting( 'add-watermark-settings', "$name-unit" ); } function registerMinMaxSize($name) { $this->registerUnitSelect($name); $this->registerUnitSelect("$name-min"); $this->registerUnitSelect("$name-max"); } function outputMinMaxSize($name) { echo '' . __('Desired:', 'add-watermark') . ''; $this->outputUnitSelect("$name"); echo '
' . __('Min:', 'add-watermark') . ''; $this->outputUnitSelect("$name-min"); echo '
' . __('Max:', 'add-watermark') . ''; $this->outputUnitSelect("$name-max"); } function outputUnitSelect($name) { $setting = add_watermark_option("$name"); $unit = add_watermark_option("$name-unit"); ?>

type = wp_check_filetype($imagepath); $this->image = $this->createImage($imagepath); if (!$this->image) { throw new RuntimeException("Could not load base image."); } } function createImage($imagepath) { $type = wp_check_filetype($imagepath); @ini_set( 'memory_limit', apply_filters( 'image_memory_limit', WP_MAX_MEMORY_LIMIT ) ); if (add_watermark_option('debug')) { echo "Loading image $imagepath of type {$type['type']} having memory: " . ini_get('memory_limit') . "\n"; } switch ($type['type']) { case 'image/jpeg': return @imagecreatefromjpeg($imagepath); break; case 'image/png': return @imagecreatefrompng($imagepath); break; default: throw new RuntimeException("Could not convert type {$type['type']}."); } } function addWatermark() { $watermarkId = add_watermark_option("image"); if (!$watermarkId) { $watermarkFile = plugin_dir_path( __FILE__ ) . "images/watermark.png"; } else { $watermarkFile = get_attached_file($watermarkId); } $watermark = $this->createImage($watermarkFile); if (!$watermark) { throw new RuntimeException("Could not load watermark image."); } $width = 1.0 * $this->getUnitValueClamped("width", imagesx($this->image), 100); $height = 1.0 * $this->getUnitValueClamped("height", imagesy($this->image), 100); $setting = add_watermark_option("size"); if ($setting == 'contain') { $originalAspect = 1.0 * imagesx($watermark) / imagesy($watermark); $currentAspect = $width / $height; if ($currentAspect < $originalAspect) { // Make currentAspect bigger by decreasing height $height = $width / $originalAspect; } else if ($currentAspect < $originalAspect) { // Make currentAspect smaller by decreasing width $width = $height * $originalAspect; } } switch (add_watermark_option("horizontal-pos-from")) { case 'right': $horizontal = 1.0; break; case 'center': $horizontal = 0.5; break; default: $horizontal = 0; break; } switch (add_watermark_option("vertical-pos-from")) { case 'bottom': $vertical = 1.0; break; case 'center': $vertical = 0.5; break; default: $vertical = 0; break; } $x = (imagesx($this->image) - $width) * $horizontal; $y = (imagesy($this->image) - $height) * $vertical; $x += $this->getUnitOption("horizontal-pos", imagesx($this->image), 0); $y += $this->getUnitOption("vertical-pos", imagesy($this->image), 0); if (add_watermark_option('debug')) { echo "Adding watermark at ($x, $y) with size ($width, $height).\n"; } imagecopyresampled($this->image, $watermark, (int) $x, (int) $y, 0, 0, (int) $width, (int) $height, imagesx($watermark), imagesy($watermark)); } /** * Get a pixel or percent value. */ function getUnitOption($name, $relativeTo, $default=10) { $value = add_watermark_option("$name") * 1; $unit = add_watermark_option("$name-unit"); if ($unit == '%') { $value = $value / 100.0 * $relativeTo; } return $value; } // Respects min/max flags. function getUnitValueClamped($name, $relativeTo, $default = 50) { $min = $this->getUnitOption("$name-min", $relativeTo, 0); $max = $this->getUnitOption("$name-max", $relativeTo, $relativeTo); $default = $this->getUnitOption("$name", $relativeTo, $default); if ($min > $default) { return $min; } else if ($max < $default) { return $max; } else { return $default; } } function sendFile($filename = null) { add_watermark_header($this->type['type']); switch ($this->type['type']) { case 'image/jpeg': @imagejpeg($this->image, $filename); break; case 'image/png': @imagepng($this->image, $filename); break; } } } class AddWatermarkRequest { static function runAjax() { $m = new AddWatermarkRequest(); $m->searchAttachments(); if ($m->shouldAddWatermark()) { if (add_watermark_option('debug')) { echo "Should add watermark: yes.\n"; } $m->addWatermarkCached(); } else { if (add_watermark_option('debug')) { echo "Should add watermark: no.\n"; } $m->linkOriginalInCache(); $m->outputOriginal(); } } function getPaths() { $request = $_REQUEST['path']; $upload_dir = wp_upload_dir(); if (preg_match('=^(.*/)((?[^/]+?)(-(?\d+)x(?\d+))?(?.\w+))$=', $request, $matches) === false) { $this->error404(); } $paths = array(); // thumb: The file we want. Attachment: The wordpress DB entry. $paths['thumbfile'] = $matches[2]; $paths['attachmentfile'] = $matches['name'] . $matches['ext']; $paths['thumbupload'] = $matches[1] . $paths['thumbfile']; $paths['attachmentupload'] = $matches[1] . $paths['attachmentfile']; // Get the absolute path $paths['thumbabs'] = realpath($upload_dir['basedir'] . "/" . $paths['thumbupload']); if (!$paths['thumbabs']) { if (add_watermark_option('debug')) { echo "No such file: " . $upload_dir['basedir'] . "/" . $paths['thumbupload']; } $this->error404(); } // We don't really need this since we do a DB query first. //$path = realpath($upload_dir['basedir'] . "/" . $request); //$realUpload = realpath($upload_dir['basedir']) . "/"; //if (strpos($path, $realUpload) !== 0) { // Someone tried to trick us. // $this->error404(); //} // guid = $upload_dir['baseurl'] . substr($path, strlen($realUpload)); //$path = substr($path, strlen($realUpload)); return $paths; } function searchAttachments() { $this->paths = $this->getPaths(); if (add_watermark_option('debug')) { echo "Paths found for the image:\n"; print_r($this->paths); } $myquery = new WP_Query(array( 'post_type' => 'attachment', 'post_status' => 'any', 'meta_key' => '_wp_attached_file', 'meta_value' => $this->paths['attachmentupload'])); $attachments = $myquery->get_posts(); if (count($attachments) != 1) { //print_r($attachments); // Second attempt: scan for a post that has that thumbnail. $paths = $this->paths; $myquery = new WP_Query(array( 'post_type' => 'attachment', 'post_status' => 'any', 'meta_query' => array(array( 'key' => '_wp_attachment_metadata', 'value' => '' . preg_quote($this->paths['thumbfile']) . '', 'compare' => 'REGEXP' ), array( 'key' => '_wp_attached_file', 'value' => preg_quote(dirname($this->paths['thumbupload'])) . '/.*', 'compare' => 'REGEXP' )) )); $attachments = array_filter($myquery->get_posts(), function($attachment) use($paths) { $meta = wp_get_attachment_metadata($attachment->ID); $file = get_metadata('post', $attachment->ID, '_wp_attached_file', true); $sizefound = $file == $paths['thumbupload']; foreach ($meta['sizes'] as $name => $size) { if ($size['file'] == $paths['thumbfile']) { $sizefound = true; } } $good = $sizefound && strpos($file, dirname($paths['thumbupload'])) === 0; if (add_watermark_option('debug') && !$good) { echo "Possible mat seems not to be our image (has file: $file, searching for: ${paths['thumbfile']}, ${paths['thumbupload']}):\n"; print_r($attachment); print_r($meta); } return $good; }); } if (count($attachments) != 1) { if (add_watermark_option('debug')) { echo "Wrong number of attachments:\n"; print_r($attachments); } $this->error404(); } $this->attachment = array_values($attachments)[0]; if (add_watermark_option('debug')) { echo "Found this attachment:\n"; print_r($this->attachment); } // handle the size if ($this->paths['attachmentfile'] != $this->paths['thumbfile']) { $meta = wp_get_attachment_metadata($this->attachment->ID); if (add_watermark_option('debug')) { echo "This is no full size image. Scanning meta sizes:\n"; print_r($meta); } foreach ($meta['sizes'] as $name => $size) { if ($size['file'] == $this->paths['thumbfile']) { $this->size = $name; } } if (!$this->size) { if (add_watermark_option('debug')) { echo "Could not find attachment size (" . $this->paths['thumbfile'] . "):\n"; print_r($meta); } $this->error404(); } } } function shouldAddWatermark() { $meta = get_metadata('post', $this->attachment->ID, 'add-watermark', true); if ($meta == 'yes') { return true; } else if ($meta == 'no') { return false; } else { return add_watermark_option("default-active", "1") * 1; } } function addWatermark() { try { $wm = new AddWatermarkMarker($this->paths['thumbabs']); $wm->addWatermark(); $wm->sendFile(); return $wm; } catch (Exception $e) { if (add_watermark_option('debug')) { echo "There was a problem generating the watermark: $e"; } $this->error404(); } } function outputOriginal($path = null) { if ($path == null) { $path = $this->paths['thumbabs']; } $type = wp_check_filetype($this->paths['thumbabs']); add_watermark_header($type['type']); readfile($path); exit; } function linkOriginalInCache() { $cacheFile = $this->getCachePath(true); $this->removeFromCache(); @touch("$cacheFile.nowm"); } function error404() { if (add_watermark_option('debug')) { debug_print_backtrace(); } status_header( 404 ); nocache_headers(); include( get_query_template( '404' ) ); die(); } function removeFromCache() { self::removeFileFromCache($this->paths['thumbupload']); } /** * Removes a file from the cache * @param relativeFile The relative filename inside the uploads dir. */ static function removeFileFromCache($relativeFile) { $cacheFile = self::getCacheDir() . "/" . $relativeFile; @unlink("$cacheFile"); @unlink("$cacheFile.nowm"); } function getCachePath($createDir = false) { $path = self::getCacheDir() . "/" . $this->paths['thumbupload']; if ($createDir) { if (!is_dir(dirname($path))) { @mkdir(dirname($path), 0777, true); // Servers not supporting END cannot support chache dir protection. if (!is_file(self::getCacheDir() . '/.htaccess') && add_watermark_option('supports_end')) { // We could also use deny from all // This might cause an internal server error if order is not allowed on that server. // So we use mod_rewrite instead. file_put_contents(self::getCacheDir() . '/.htaccess', "RewriteEngine ON\nRewriteRule .* / [F]"); } } } return $path; } function addWatermarkCached() { $file = $this->getCachePath(true); if (!is_file($file)) { if (add_watermark_option('debug')) { echo "Generating watermark\n"; } $wm = $this->addWatermark(); if (add_watermark_option('debug')) { echo "Updating cache\n"; } $wm->sendFile("$file-temp"); $this->removeFromCache(); @rename("$file-temp", $file); } else { if (add_watermark_option('debug')) { echo "There is a cached version of this file. Send it.\n"; } // Some race condition htaccess did not catch. $this->outputOriginal($file); } } static function getCacheDir() { //alternative: trailingslashit( WP_CONTENT_DIR ) . 'watermark-cache'; return plugin_dir_path( __FILE__ ) . "cache"; } static function emptyCache() { $dir = self::getCacheDir (); self::deleteContents($dir); } static function deleteContents($dir) { $files = array_diff(scandir($dir), array('.', '..')); foreach ($files as $file) { if (is_dir("$dir/$file")) { self::deleteContents("$dir/$file"); rmdir("$dir/$file"); } else { unlink("$dir/$file"); } } } } if ( is_admin() ){ AddWatermarksSettings::register(); } add_action( 'wp_ajax_watermark_image', array('AddWatermarkRequest', 'runAjax') ); add_action( 'wp_ajax_nopriv_watermark_image', array('AddWatermarkRequest', 'runAjax') ); register_activation_hook(__FILE__, array('AddWatermarksSettings', 'pluginActivate')); register_deactivation_hook(__FILE__, array('AddWatermarksSettings', 'pluginDeactivate')); register_uninstall_hook(__FILE__, array('AddWatermarksSettings', 'pluginUninstall'));