FVP?FV`?FVp?JVPGV@+pFJVP>.KV`GV`/.KVpGV4GV+pFV =JVPPuGV<GV@+pFJVP>GV DP GV<0 KV.KV`GV+@JV pGV PtpFV (=GV PAGV <`IV `2P GV <.KVp@GV@+@JV@ 0 pGV@ P tpFV0 @ =GV P AGVP <`IVP` 2P GV <pFJV >pFV H=JV`PuGVp<GVpGV@+pFJVP>FV P;GV Pt!KV``BGV<0 KVP@GJVPGV +GV0 XDGV0 PtpFV `=GV PAGV<`IV`2P GV<[HV>pFV@ h=GVp PAJVP`uJVppuGV<0 KVPpFVp=JVPPuGV<GV@+pFJVP >FVPx#;!KVpP#BJV#d0JVP #^eIV `#JV#d0JVP0#^eIV0p#GV@#<0 KV@#@GJV`%GV` %+GVp&DGVP'tpFVp(=pFV*=GVP*AGV*<&IVP*uGV`+ GV+`GV`+AGV+ GV0-FV -;GV-<`GV- GV-pFV.=GVP.AGV.<`GV. GVp /`SHV `/tP GV)/<[HVp/>FV3;JV3d0JVP@3^eIV@P3JV3d0JVPP3^eIVP`3GV`3<GV`3+FV 4;!KVpP4BJV4d0JVPp4^eIVp`4P GV.4<pFJVP7>FVp8>`@@@ح@@@@`O@@S@his->paramProps = []; $this->paramPropsByType = []; if(isset($this->toolInfo['settings']['params'])) { foreach($this->toolInfo['settings']['params'] as $paramCategory => $paramCategoryInfo) { foreach($paramCategoryInfo as $paramGroup => $paramGroupInfo) { foreach($paramGroupInfo as $paramKey => $paramInfo) { $this->paramProps[$paramKey] = $paramInfo; if(!isset($this->paramPropsByType[$paramInfo['type']])) { $paramType = []; } else { $paramType = $this->paramPropsByType[$paramInfo['type']]; } $paramType[$paramKey] = $paramInfo; $this->paramPropsByType[$paramInfo['type']] = $paramType; } } } } $this->hookupUI(); $this->setupHooks(); add_filter('wp_prepare_attachment_for_js', array($this, 'prepareAttachmentForJS'), 1000, 3); add_filter('image_get_intermediate_size', [$this, 'imageGetIntermediateSize'], 0, 3); add_action('admin_enqueue_scripts', [$this, 'enqueueTheGoods']); add_action('wp_ajax_ilab_dynamic_images_edit_page', [$this, 'displayEditUI']); add_action('wp_ajax_ilab_dynamic_images_save', [$this, 'saveAdjustments']); add_action('wp_ajax_ilab_dynamic_images_preview', [$this, 'previewAdjustments']); add_action('wp_ajax_ilab_dynamic_images_new_preset', [$this, 'newPreset']); add_action('wp_ajax_ilab_dynamic_images_save_preset', [$this, 'savePreset']); add_action('wp_ajax_ilab_dynamic_images_delete_preset', [$this, 'deletePreset']); add_filter('clean_url', [$this, 'fixCleanedUrls'], 1000, 3); if (empty($this->settings->keepThumbnails)) { add_filter('wp_image_editors', function($editors) { array_unshift($editors, '\MediaCloud\Plugin\Tools\DynamicImages\DynamicImageEditor'); return $editors; }); } else { add_filter('wp_image_editors', function($editors) { array_unshift($editors, '\MediaCloud\Plugin\Tools\Storage\StorageImageEditor'); return $editors; }, PHP_INT_MAX); } add_filter('wp_get_attachment_metadata', function($metadata, $attachmentId) { if (!isset($metadata['s3'])) { return $metadata; } $hasVersion = isset($metadata['s3']['v']) && ($metadata['s3']['v'] == MEDIA_CLOUD_INFO_VERSION); if ($hasVersion && in_array($attachmentId, $this->processedAttachments)) { return $metadata; } if (!isset($metadata['s3']['mime-type'])) { $mime = get_post_mime_type($attachmentId); $metadata['s3']['mime-type'] = $mime; } else { $mime = $metadata['s3']['mime-type']; } if (strpos($mime, 'image/') !== 0) { return $metadata; } $metadataFile = arrayPath($metadata, 'file'); $width = intval(arrayPath($metadata, 'width', 0)); $height = intval(arrayPath($metadata, 'height', 0)); if (empty($metadataFile) || empty($width) || empty($height)) { Logger::warning("Metadata for image $attachmentId with mimetype $mime missing info. File: $metadataFile, width: $width, height: $height", [], __METHOD__, __LINE__); return $metadata; } if (!isset($metadata['sizes'])) { Logger::warning("Metadata for image $attachmentId with mimetype $mime missing sizes.", [], __METHOD__, __LINE__); return $metadata; } if ($this->allSizes == null) { $this->allSizes = ilab_get_image_sizes(); } $filename = pathinfo($metadataFile, PATHINFO_BASENAME); $didChange = false; foreach($this->allSizes as $sizeKey => $sizeData) { if (isset($metadata['sizes'][$sizeKey]) && $hasVersion) { continue; } $sizeFilename = arrayPath($metadata, "sizes/$sizeKey/file", $filename); $sizeWidth = intval($sizeData['width']); $sizeHeight = intval($sizeData['height']); $sizeS3 = arrayPath($metadata, "sizes/$sizeKey/s3", $metadata['s3']); $cropData = arrayPath($metadata, "sizes/$sizeKey/crop", []); if (!empty($sizeData['crop'])) { $newWidth = $sizeWidth; $newHeight = $sizeHeight; } else { list($newWidth, $newHeight) = sizeToFitSize($width, $height, $sizeWidth, $sizeHeight); } $metadata['sizes'][$sizeKey] = [ 'file' => $sizeFilename, 'width' => intval($newWidth), 'height' => intval($newHeight), 'mime-type' => $mime, 's3' => $sizeS3 ]; if (!empty($cropData)) { $metadata['sizes'][$sizeKey]['crop'] = $cropData; } $didChange = true; } if ($didChange) { $metadata['s3']['v'] = MEDIA_CLOUD_INFO_VERSION; update_post_meta($attachmentId, '_wp_attachment_metadata', $metadata); } $this->processedAttachments[] = $attachmentId; return $metadata; }, PHP_INT_MAX, 2); // Fix for Foo Gallery add_filter('foogallery_thumbnail_resize_args', function($args, $original_image_src, $thumbnail_object) { $this->shouldCrop = true; $args['force_use_original_thumb'] = true; return $args; }, 100000, 3); } protected function setupHooks() { add_filter('wp_get_attachment_url', [$this, 'getAttachmentURL'], 10000, 2); add_filter('image_downsize', [$this, 'imageDownsize'], 1000, 3); } protected function tearDownHooks() { remove_filter('wp_get_attachment_url', [$this, 'getAttachmentURL'], 10000); remove_filter('image_downsize', [$this, 'imageDownsize'], 1000); } public function registerSettings() { parent::registerSettings(); register_setting('ilab-imgix-preset', 'ilab-imgix-presets'); register_setting('ilab-imgix-preset', 'ilab-imgix-size-presets'); } public function forceEnable($enabled) { $this->forcedEnabled = $enabled; if ($this->forcedEnabled) { $this->setupHooks(); } else { $this->tearDownHooks(); } } //endregion //region URL Generation abstract public function buildSizedImage($id, $size); abstract public function buildImage($id, $size, $params = null, $skipParams = false, $mergeParams = null, $newSize = null, $newMeta=null); abstract public function urlForStorageMedia($key); public function fixCleanedUrls($good_protocol_url, $original_url, $context) { return $good_protocol_url; } //endregion //region WordPress Hooks & Filters /** * Filters the attachment data prepared for JavaScript. (https://core.trac.wordpress.org/browser/tags/4.8/src/wp-includes/media.php#L3279) * * @param array $response * @param int|object $attachment * @param array $meta * * @return array */ function prepareAttachmentForJS($response, $attachment, $meta) { if(!$response || empty($response) || !isset($response['sizes'])) { return $response; } foreach($response['sizes'] as $key => $sizeInfo) { $res = $this->buildImage($response['id'], $key); if(is_array($res)) { $response['sizes'][$key]['url'] = $res[0]; } } return $response; } /** * Filters the attachment's url. (https://core.trac.wordpress.org/browser/tags/4.8/src/wp-includes/post.php#L5077) * @param $url * @param $post_id * @return mixed|string */ public function getAttachmentURL($url, $post_id) { if (!empty(apply_filters('media-cloud/dynamic-images/skip-url-generation', false))) { /** @var StorageTool $storageTool */ $storageTool = ToolsManager::instance()->tools['storage']; $result = $storageTool->getAttachmentURL($url, $post_id); return $result; } $res = $this->buildImage($post_id, 'full'); if(!$res || !is_array($res)) { return $url; } $new_url = $res[0]; if(!$new_url) { return $url; } return $new_url; } /** * Filters whether to preempt the output of image_downsize(). (https://core.trac.wordpress.org/browser/tags/4.8/src/wp-includes/media.php#L201) * @param $fail * @param $id * @param $size * @return array|bool */ public function imageDownsize($fail, $id, $size) { if (!empty(apply_filters('media-cloud/dynamic-images/skip-url-generation', false))) { /** @var StorageTool $storageTool */ $storageTool = ToolsManager::instance()->tools['storage']; $result = $storageTool->forcedImageDownsize($fail, $id, $size); return $result; } $this->shouldCrop = apply_filters('media-cloud/dynamic-images/should-crop', $this->shouldCrop); $result = $this->buildImage($id, $size); return $result; } /** * Filters the image data for intermediate sizes. * * @param array $data * @param int $post_id * @param array|string $size * * @return array */ public function imageGetIntermediateSize($data, $post_id, $size) { $result = $this->buildImage($post_id, $size); if (is_array($result) && !empty($result)) { $data['url'] = $result[0]; } else if (!empty($data['width']) && !empty($data['height'])) { $result = $this->buildSizedImage($post_id, [ $data['width'], $data['height'] ]); if (is_array($result) && !empty($result)) { $data['file'] = wp_basename($result[0]); } } return $data; } //endregion //region Imgix Image Editor UI /** * Enqueue the CSS and JS needed to make the magic happen * * @param $hook */ public function enqueueTheGoods($hook) { add_thickbox(); if($hook == 'post.php') { wp_enqueue_media(); } else if($hook == 'upload.php') { $mode = get_user_option('media_library_mode', get_current_user_id()) ? get_user_option('media_library_mode', get_current_user_id()) : 'grid'; if(isset($_GET['mode']) && in_array($_GET ['mode'], ['grid', 'list'])) { $mode = $_GET['mode']; update_user_option(get_current_user_id(), 'media_library_mode', $mode); } if($mode == 'list') { $version = get_bloginfo('version'); if(version_compare($version, '4.2.2') < 0) { wp_dequeue_script('media'); } wp_enqueue_media(); } } else { wp_enqueue_style('media-views'); } wp_enqueue_style('wp-pointer'); wp_enqueue_style('wp-color-picker'); wp_enqueue_script('wp-pointer'); wp_enqueue_script('wp-color-picker'); wp_enqueue_script('ilab-modal-js', ILAB_PUB_JS_URL.'/ilab-modal.js', ['jquery'], MEDIA_CLOUD_VERSION, true); wp_enqueue_script('ilab-media-tools-js', ILAB_PUB_JS_URL.'/ilab-media-tools.js', ['ilab-modal-js'], MEDIA_CLOUD_VERSION, true); } /** * Hook up the "Edit Image" links/buttons in the admin ui */ private function hookupUI() { add_filter('media_row_actions', function($actions, $post) { if (strpos($post->post_mime_type, 'image') === 0) { $meta = wp_get_attachment_metadata($post->ID); if (empty(arrayPath($meta, 's3', null))) { return $actions; } $newaction['ilab_edit_image'] = '' . __('Edit Image') . ''; return array_merge($actions, $newaction); } return $actions; }, 10, 2); add_filter('mediacloud/ui/media-detail-buttons', function($buttons) { $buttons[] = [ 'type' => 'image', 'cloudonly' => true, 'label' => __('Edit Image'), 'url' => $this->editPageURL('__ID__'), ]; return $buttons; }, 2); add_filter('mediacloud/ui/media-detail-links', function($links) { $links[] = [ 'type' => 'image', 'cloudonly' => true, 'label' => __('Edit Image'), 'url' => $this->editPageURL('__ID__'), ]; return $links; }, 2); add_filter('mediacloud/ui/media-detail-remove', function($toRemove) { $toRemove[] = "''"; $toRemove[] = '/(]+[^<]+<\/a>)/'; return $toRemove; }, 2); add_action('mediacloud/ui/media-detail-buttons-extra', function() { $adminUrl = get_admin_url(null, 'admin-ajax.php'); echo <<buildPresetsUI($image_id, $size); if($current_preset && $presets && isset($presets[$current_preset])) { $imgix_settings = $presets[$current_preset]['settings']; $full_src = $this->buildImage($image_id, $size, $imgix_settings)[0]; } else if($size == 'full') { if(!$imgix_settings) { if(isset($meta['imgix-params'])) { $imgix_settings = $meta['imgix-params']; } } } else { if(isset($meta['imgix-size-params'][$size])) { $imgix_settings = $meta['imgix-size-params'][$size]; } else { if($presets && $sizePresets && isset($sizePresets[$size]) && isset($presets[$sizePresets[$size]])) { $imgix_settings = $presets[$sizePresets[$size]]['settings']; if(!$current_preset) { $current_preset = $sizePresets[$size]; } } } if((!$imgix_settings) && (isset($meta['imgix-params']))) { $imgix_settings = $meta['imgix-params']; } } foreach($this->paramPropsByType['media-chooser'] as $key => $info) { if(isset($imgix_settings[$key]) && !empty($imgix_settings[$key])) { $media_id = $imgix_settings[$key]; $imgix_settings[$key.'_url'] = wp_get_attachment_url($media_id); } } if(current_user_can('edit_post', $image_id)) { if(!$partial) { Tracker::trackView("Image Editor", "/image/editor"); echo View::render_view('imgix/ilab-imgix-ui', [ 'partial' => $partial, 'image_id' => $image_id, 'modal_id' => gen_uuid(8), 'size' => $size, 'sizes' => ilab_get_image_sizes(), 'meta' => $meta, 'full_width' => $full_width, 'full_height' => $full_height, 'tool' => $this, 'settings' => $imgix_settings, 'src' => $full_src, 'mode' => $mode, 'presets' => $presetsUI, 'currentPreset' => $current_preset, 'params' => $this->toolInfo['settings']['params'], 'paramProps' => $this->paramProps ]); } else { json_response([ 'status' => 'ok', 'mode' => $mode, 'image_id' => $image_id, 'size' => $size, 'settings' => $imgix_settings, 'src' => $full_src, 'presets' => $presetsUI, 'currentPreset' => $current_preset, 'paramProps' => $this->paramProps ]); } } die; } /** * Builds the presets UI * * @param int $image_id * @param string $size * * @return array */ private function buildPresetsUI($image_id, $size) { $presets = get_option('ilab-imgix-presets'); if(!$presets) { $presets = []; } $sizePresets = get_option('ilab-imgix-size-presets'); if(!$sizePresets) { $sizePresets = []; } $presetsUI = []; foreach($presets as $pkey => $pinfo) { $default_for = ''; foreach($sizePresets as $psize => $psizePreset) { if($psizePreset == $pkey) { $default_for = $psize; break; } } $psettings = $pinfo['settings']; foreach($this->paramPropsByType['media-chooser'] as $mkey => $minfo) { if(isset($psettings[$mkey])) { if(!empty($psettings[$mkey])) { $psettings[$mkey.'_url'] = wp_get_attachment_url($psettings[$mkey]); } } } $presetsUI[$pkey] = [ 'title' => $pinfo['title'], 'default_for' => $default_for, 'settings' => $psettings ]; } return $presetsUI; } //endregion //region Imgix Image Editor Ajax /** * Save The Parameters */ public function saveAdjustments() { $image_id = esc_html($_POST['image_id']); $size = esc_html($_POST['size']); $params = (isset($_POST['settings'])) ? $_POST['settings'] : []; $mode = esc_html($_POST['mode']); if(!current_user_can('edit_post', $image_id)) { json_response([ 'status' => 'error', 'message' => 'You are not strong enough, smart enough or fast enough.' ]); } if ($mode == 'size') { $name = ucwords(preg_replace('/[-_]/',' ', $size)). ' Default'; $key = sanitize_title($name); if (empty($params) || ((count($params) == 1) && isset($params['markalign']) && empty($params['markalign']))) { $this->doDeletePreset($key); } else { $this->doUpdatePresets($key, $name, $params, $size, true, false); } json_response([ 'status' => 'ok' ]); } else { $meta = wp_get_attachment_metadata($image_id); if(!$meta) { json_response([ 'status' => 'error', 'message' => 'Invalid image id.' ]); } if($size == 'full') { $meta['imgix-params'] = $params; } else { $meta['imgix-size-params'][$size] = $params; } wp_update_attachment_metadata($image_id, $meta); Tracker::trackView("Image Editor - Save Adjustments", '/image/editor/save'); json_response([ 'status' => 'ok' ]); } } /** * Preview the adjustment */ public function previewAdjustments() { $image_id = esc_html($_POST['image_id']); $size = esc_html($_POST['size']); if(!current_user_can('edit_post', $image_id)) { json_response([ 'status' => 'error', 'message' => 'You are not strong enough, smart enough or fast enough.' ]); } $this->skipSizeParams = !empty($_POST['forceClean']); $params = (isset($_POST['settings'])) ? $_POST['settings'] : []; $result = $this->buildImage($image_id, $size, $params); json_response(['status' => 'ok', 'src' => $result[0]]); } /** * Update the presets * * @param string $key * @param string $name * @param array $settings * @param string $size * @param bool $makeDefault */ private function doUpdatePresets($key, $name, $settings, $size, $makeDefault, $sendResponse = true) { $image_id = esc_html($_POST['image_id']); $presets = get_option('ilab-imgix-presets'); if(!$presets) { $presets = []; } $presets[$key] = [ 'title' => $name, 'settings' => $settings ]; update_option('ilab-imgix-presets', $presets); $sizePresets = get_option('ilab-imgix-size-presets'); if(!$sizePresets) { $sizePresets = []; } if($size && $makeDefault) { $sizePresets[$size] = $key; } else if($size && !$makeDefault) { foreach($sizePresets as $s => $k) { if($k == $key) { unset($sizePresets[$s]); } } } update_option('ilab-imgix-size-presets', $sizePresets); if ($sendResponse) { json_response([ 'status' => 'ok', 'currentPreset' => $key, 'presets' => $this->buildPresetsUI($image_id, $size) ]); } } /** * Create a new preset */ public function newPreset() { $name = esc_html($_POST['name']); if(empty($name)) { json_response([ 'status' => 'error', 'error' => 'Seems that you may have forgotten something.' ]); } $key = sanitize_title($name); $newKey = $key; $presets = get_option('ilab-imgix-presets'); if($presets) { $keyIndex = 1; while(isset($presets[$newKey])) { $keyIndex ++; $newKey = $key.$keyIndex; } } $settings = $_POST['settings']; $size = (isset($_POST['size'])) ? esc_html($_POST['size']) : null; $makeDefault = (isset($_POST['make_default'])) ? ($_POST['make_default'] == 1) : false; Tracker::trackView("Image Editor - New Preset", '/image/editor/preset/new'); $this->doUpdatePresets($newKey, $name, $settings, $size, $makeDefault); } /** * Save an existing preset */ public function savePreset() { $key = esc_html($_POST['key']); if(empty($key)) { json_response([ 'status' => 'error', 'error' => 'Seems that you may have forgotten something.' ]); } $presets = get_option('ilab-imgix-presets'); if(!isset($presets[$key])) { json_response([ 'status' => 'error', 'error' => 'Seems that you may have forgotten something.' ]); } $name = $presets[$key]['title']; $settings = $_POST['settings']; $size = (isset($_POST['size'])) ? esc_html($_POST['size']) : null; $makeDefault = (isset($_POST['make_default'])) ? ($_POST['make_default'] == 1) : false; if ($makeDefault) { Tracker::trackView("Image Editor - Save Default Preset", '/image/editor/preset/save-default'); } else { Tracker::trackView("Image Editor - Save Preset", '/image/editor/preset/save'); } $this->doUpdatePresets($key, $name, $settings, $size, $makeDefault); } /** * Delete an existing preset */ public function deletePreset() { $key = esc_html($_POST['key']); if(empty($key)) { json_response([ 'status' => 'error', 'error' => 'Seems that you may have forgotten something.' ]); } Tracker::trackView("Image Editor - Delete Preset", '/image/editor/preset/delete'); $this->doDeletePreset($key); return $this->displayEditUI(1); } private function doDeletePreset($key) { $presets = get_option('ilab-imgix-presets'); if($presets) { unset($presets[$key]); update_option('ilab-imgix-presets', $presets); } $sizePresets = get_option('ilab-imgix-size-presets'); if(!$sizePresets) { $sizePresets = []; } foreach($sizePresets as $size => $preset) { if($preset == $key) { unset($sizePresets[$size]); break; } } update_option('ilab-imgix-size-presets', $sizePresets); } //endregion //region Static Methods /** * The current dynamic images tool * @return DynamicImagesTool|null */ public static function currentDynamicImagesTool() { if (ToolsManager::instance()->toolEnabled('imgix')) { return ToolsManager::instance()->tools['imgix']; } return null; } }