510 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			510 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
<?php
 | 
						|
namespace ShortPixel\Controller;
 | 
						|
 | 
						|
if ( ! defined( 'ABSPATH' ) ) {
 | 
						|
 exit; // Exit if accessed directly.
 | 
						|
}
 | 
						|
 | 
						|
use ShortPixel\ShortPixelLogger\ShortPixelLogger as Log;
 | 
						|
use ShortPixel\Notices\NoticeController as Notices;
 | 
						|
use ShortPixel\Controller\Queue\Queue as Queue;
 | 
						|
 | 
						|
use ShortPixel\Model\Converter\Converter as Converter;
 | 
						|
use ShortPixel\Model\Converter\ApiConverter as ApiConverter;
 | 
						|
 | 
						|
use ShortPixel\Model\Image\MediaLibraryModel as MediaLibraryModel;
 | 
						|
use ShortPixel\Model\Image\ImageModel as ImageModel;
 | 
						|
 | 
						|
use ShortPixel\Model\AccessModel as AccessModel;
 | 
						|
use ShortPixel\Helper\UtilHelper as UtilHelper;
 | 
						|
 | 
						|
 | 
						|
/* AdminController is meant for handling events, hooks, filters in WordPress where there is *NO* specific or more precise  ShortPixel Page active.
 | 
						|
*
 | 
						|
* This should be a delegation class connection global hooks and such to the best shortpixel handler.
 | 
						|
*/
 | 
						|
class AdminController extends \ShortPixel\Controller
 | 
						|
{
 | 
						|
    protected static $instance;
 | 
						|
 | 
						|
		private static $preventUploadHook = array();
 | 
						|
 | 
						|
    public static function getInstance()
 | 
						|
    {
 | 
						|
      if (is_null(self::$instance))
 | 
						|
          self::$instance = new AdminController();
 | 
						|
 | 
						|
      return self::$instance;
 | 
						|
    }
 | 
						|
 | 
						|
    /** Handling upload actions
 | 
						|
    * @hook wp_generate_attachment_metadata
 | 
						|
    */
 | 
						|
    public function handleImageUploadHook($meta, $id)
 | 
						|
    {
 | 
						|
        // Media only hook
 | 
						|
				if ( in_array($id, self::$preventUploadHook))
 | 
						|
				{
 | 
						|
					 return $meta;
 | 
						|
				}
 | 
						|
 | 
						|
        // todo add check here for mediaitem
 | 
						|
			  $fs = \wpSPIO()->filesystem();
 | 
						|
				$fs->flushImageCache(); // it's possible file just changed by external plugin.
 | 
						|
        $mediaItem = $fs->getImage($id, 'media');
 | 
						|
 | 
						|
				if ($mediaItem === false)
 | 
						|
				{
 | 
						|
					 Log::addError('Handle Image Upload Hook triggered, by error in image :' . $id );
 | 
						|
					 return $meta;
 | 
						|
				}
 | 
						|
 | 
						|
				if ($mediaItem->getExtension()  == 'pdf')
 | 
						|
				{
 | 
						|
					$settings = \wpSPIO()->settings();
 | 
						|
					if (! $settings->optimizePdfs)
 | 
						|
					{
 | 
						|
						 Log::addDebug('Image Upload Hook detected PDF, which is turned off - not optimizing');
 | 
						|
						 return $meta;
 | 
						|
					}
 | 
						|
				}
 | 
						|
 | 
						|
				if ($mediaItem->isProcessable())
 | 
						|
				{
 | 
						|
					$converter = Converter::getConverter($mediaItem, true);
 | 
						|
					if (is_object($converter) && $converter->isConvertable())
 | 
						|
					{
 | 
						|
							$args = array('runReplacer' => false);
 | 
						|
 | 
						|
						 	$converter->convert($args);
 | 
						|
							$mediaItem = $fs->getImage($id, 'media');
 | 
						|
							$meta = $converter->getUpdatedMeta();
 | 
						|
					}
 | 
						|
 | 
						|
        	$control = new OptimizeController();
 | 
						|
        	$control->addItemToQueue($mediaItem);
 | 
						|
				}
 | 
						|
				else {
 | 
						|
					Log::addWarn('Passed mediaItem is not processable', $mediaItem);
 | 
						|
				}
 | 
						|
        return $meta; // It's a filter, otherwise no thumbs
 | 
						|
    }
 | 
						|
 | 
						|
 | 
						|
		public function preventImageHook($id)
 | 
						|
		{
 | 
						|
			  self::$preventUploadHook[] = $id;
 | 
						|
		}
 | 
						|
 | 
						|
		// Placeholder function for heic and such, return placeholder URL in image to help w/ database replacements after conversion.
 | 
						|
		public function checkPlaceHolder($url, $post_id)
 | 
						|
		{
 | 
						|
			 if (false === strpos($url, 'heic'))
 | 
						|
			 	 return $url;
 | 
						|
 | 
						|
			$extension = pathinfo($url,  PATHINFO_EXTENSION);
 | 
						|
			if (false === in_array($extension, ApiConverter::CONVERTABLE_EXTENSIONS))
 | 
						|
			{
 | 
						|
				 return $url;
 | 
						|
			}
 | 
						|
 | 
						|
			$fs = \wpSPIO()->filesystem();
 | 
						|
			$mediaImage = $fs->getImage($post_id, 'media');
 | 
						|
 | 
						|
			if (false === $mediaImage)
 | 
						|
			{
 | 
						|
				 return $url;
 | 
						|
			}
 | 
						|
 | 
						|
			if (false === $mediaImage->getMeta()->convertMeta()->hasPlaceholder())
 | 
						|
			{
 | 
						|
				return $url;
 | 
						|
			}
 | 
						|
 | 
						|
			$url = str_replace($extension, 'jpg', $url);
 | 
						|
 | 
						|
			return $url;
 | 
						|
		}
 | 
						|
 | 
						|
		public function processQueueHook($args = array())
 | 
						|
		{
 | 
						|
				$defaults = array(
 | 
						|
					'wait' => 3, // amount of time to wait for next round. Prevents high loads
 | 
						|
					'run_once' => false, //  If true queue must be run at least every few minutes. If false, it tries to complete all.
 | 
						|
					'queues' => array('media','custom'),
 | 
						|
					'bulk' => false,
 | 
						|
				);
 | 
						|
 | 
						|
				if (wp_doing_cron())
 | 
						|
				{
 | 
						|
					 $this->loadCronCompat();
 | 
						|
				}
 | 
						|
 | 
						|
				$args = wp_parse_args($args, $defaults);
 | 
						|
 | 
						|
			  $control = new OptimizeController();
 | 
						|
				if ($args['bulk'] === true)
 | 
						|
				{
 | 
						|
					 $control->setBulk(true);
 | 
						|
				}
 | 
						|
 | 
						|
			 	if ($args['run_once'] === true)
 | 
						|
				{
 | 
						|
					 return	$control->processQueue($args['queues']);
 | 
						|
				}
 | 
						|
 | 
						|
				$running = true;
 | 
						|
				$i = 0;
 | 
						|
 | 
						|
				while($running)
 | 
						|
				{
 | 
						|
							 	$results = $control->processQueue($args['queues']);
 | 
						|
								$running = false;
 | 
						|
 | 
						|
								foreach($args['queues'] as $qname)
 | 
						|
								{
 | 
						|
									  if (property_exists($results, $qname))
 | 
						|
										{
 | 
						|
											  $result = $results->$qname;
 | 
						|
												// If Queue is not completely empty, there should be something to do.
 | 
						|
												if ($result->qstatus != QUEUE::RESULT_QUEUE_EMPTY)
 | 
						|
												{
 | 
						|
													 $running = true;
 | 
						|
													 continue;
 | 
						|
												}
 | 
						|
										}
 | 
						|
								}
 | 
						|
 | 
						|
							sleep($args['wait']);
 | 
						|
				}
 | 
						|
		}
 | 
						|
 | 
						|
    public function scanCustomFoldersHook($args = array() )
 | 
						|
    {
 | 
						|
      $defaults = array(
 | 
						|
        'force' => false,
 | 
						|
        'wait' => 3,
 | 
						|
      );
 | 
						|
 | 
						|
      $args = wp_parse_args($args, $defaults);
 | 
						|
 | 
						|
      $otherMediaController = OtherMediaController::getInstance();
 | 
						|
 | 
						|
      $running = true;
 | 
						|
 | 
						|
      while (true === $running)
 | 
						|
      {
 | 
						|
        $result = $otherMediaController->doNextRefreshableFolder($args);
 | 
						|
        if (false === $result) // stop on false return.
 | 
						|
        {
 | 
						|
           $running = false;
 | 
						|
        }
 | 
						|
        sleep($args['wait']);
 | 
						|
 | 
						|
      }
 | 
						|
 | 
						|
    }
 | 
						|
 | 
						|
		// WP functions that are not loaded during Cron Time.
 | 
						|
		protected function loadCronCompat()
 | 
						|
		{
 | 
						|
			  if (! function_exists('download_url'))
 | 
						|
				{
 | 
						|
					 include(ABSPATH . "wp-admin/includes/admin.php");
 | 
						|
				}
 | 
						|
		}
 | 
						|
 | 
						|
    /** Filter for Medialibrary items in list and grid view. Because grid uses ajax needs to be caught more general.
 | 
						|
    * @handles pre_get_posts
 | 
						|
    * @param WP_Query $query
 | 
						|
    *
 | 
						|
    * @return WP_Query
 | 
						|
    */
 | 
						|
    public function filter_listener($query)
 | 
						|
    {
 | 
						|
      global $pagenow;
 | 
						|
 | 
						|
      if ( empty( $query->query_vars["post_type"] ) || 'attachment' !== $query->query_vars["post_type"] ) {
 | 
						|
        return $query;
 | 
						|
      }
 | 
						|
 | 
						|
      if ( ! in_array( $pagenow, array( 'upload.php', 'admin-ajax.php' ) ) ) {
 | 
						|
        return $query;
 | 
						|
      }
 | 
						|
 | 
						|
      $filter = $this->selected_filter_value( 'shortpixel_status', 'all' );
 | 
						|
 | 
						|
      // No filter
 | 
						|
      if ($filter == 'all')
 | 
						|
      {
 | 
						|
         return $query;
 | 
						|
      }
 | 
						|
 | 
						|
//      add_filter( 'posts_join', array( $this, 'filter_join' ), 10, 2 );
 | 
						|
  		add_filter( 'posts_where', array( $this, 'filter_add_where' ), 10, 2 );
 | 
						|
//  		add_filter( 'posts_orderby', array( $this, 'query_add_orderby' ), 10, 2 );
 | 
						|
 | 
						|
      return $query;
 | 
						|
    }
 | 
						|
 | 
						|
    public function filter_add_where ($where, $query)
 | 
						|
    {
 | 
						|
        global $wpdb;
 | 
						|
        $filter = $this->selected_filter_value( 'shortpixel_status', 'all' );
 | 
						|
        $tableName = UtilHelper::getPostMetaTable();
 | 
						|
 | 
						|
        switch($filter)
 | 
						|
        {
 | 
						|
             case 'all':
 | 
						|
 | 
						|
             break;
 | 
						|
             case 'unoptimized':
 | 
						|
              // The parent <> %d exclusion is meant to also deselect duplicate items ( translations ) since they don't have a status, but shouldn't be in a list like this.
 | 
						|
                $sql = " AND " . $wpdb->posts . '.ID not in ( SELECT attach_id FROM ' . $tableName . " WHERE (parent = %d and status = %d) OR parent <> %d ) ";
 | 
						|
  					    $where .= $wpdb->prepare($sql, MediaLibraryModel::IMAGE_TYPE_MAIN, ImageModel::FILE_STATUS_SUCCESS, MediaLibraryModel::IMAGE_TYPE_MAIN);
 | 
						|
             break;
 | 
						|
             case 'optimized':
 | 
						|
                $sql = ' AND ' . $wpdb->posts . '.ID in ( SELECT attach_id FROM ' . $tableName . ' WHERE parent = %d and status = %d) ';
 | 
						|
   					    $where .= $wpdb->prepare($sql, MediaLibraryModel::IMAGE_TYPE_MAIN, ImageModel::FILE_STATUS_SUCCESS);
 | 
						|
             break;
 | 
						|
             case 'prevented':
 | 
						|
 | 
						|
                $sql = sprintf('AND %s.ID in (SELECT post_id FROM %s WHERE meta_key = %%s)', $wpdb->posts, $wpdb->postmeta);
 | 
						|
 | 
						|
                $sql .= sprintf(' AND %s.ID not in ( SELECT attach_id FROM %s WHERE parent = 0 and status = %s)', $wpdb->posts, $tableName, ImageModel::FILE_STATUS_MARKED_DONE);
 | 
						|
 | 
						|
                $where = $wpdb->prepare($sql, '_shortpixel_prevent_optimize');
 | 
						|
            break;
 | 
						|
        }
 | 
						|
 | 
						|
 | 
						|
        return $where;
 | 
						|
    }
 | 
						|
 | 
						|
 | 
						|
    /**
 | 
						|
  	 * Safely retrieve the selected filter value from a dropdown.
 | 
						|
  	 *
 | 
						|
  	 * @param string $key
 | 
						|
  	 * @param string $default
 | 
						|
  	 *
 | 
						|
  	 * @return string
 | 
						|
  	 */
 | 
						|
  	private function selected_filter_value( $key, $default ) {
 | 
						|
  		if ( wp_doing_ajax() ) {
 | 
						|
  			if ( isset( $_REQUEST['query'][ $key ] ) ) {
 | 
						|
  				$value = sanitize_text_field( $_REQUEST['query'][ $key ] );
 | 
						|
  			}
 | 
						|
  		} else {
 | 
						|
  			if ( ! isset( $_REQUEST['filter_action'] )  ) {
 | 
						|
  				return $default;
 | 
						|
  			}
 | 
						|
 | 
						|
  			if ( ! isset( $_REQUEST[ $key ] ) ) {
 | 
						|
  				return $default;
 | 
						|
  			}
 | 
						|
 | 
						|
  			$value = sanitize_text_field( $_REQUEST[ $key ] );
 | 
						|
  		}
 | 
						|
 | 
						|
  		return ! empty( $value ) ? $value : $default;
 | 
						|
  	}
 | 
						|
 | 
						|
    /**
 | 
						|
		* When replacing happens.
 | 
						|
    * @hook wp_handle_replace
 | 
						|
		* @integration Enable Media Replace
 | 
						|
    */
 | 
						|
    public function handleReplaceHook($params)
 | 
						|
    {
 | 
						|
      if(isset($params['post_id'])) { //integration with EnableMediaReplace - that's an upload for replacing an existing ID
 | 
						|
 | 
						|
          $post_id = intval($params['post_id']);
 | 
						|
          $fs = \wpSPIO()->filesystem();
 | 
						|
 | 
						|
          $imageObj = $fs->getImage($post_id, 'media');
 | 
						|
          // In case entry is corrupted data, this might fail.
 | 
						|
          if (is_object($imageObj))
 | 
						|
          {
 | 
						|
            $imageObj->onDelete();
 | 
						|
          }
 | 
						|
      }
 | 
						|
 | 
						|
    }
 | 
						|
 | 
						|
		/** This function is bound to enable-media-replace hook and fire when a file was replaced
 | 
						|
		*
 | 
						|
		*
 | 
						|
		*/
 | 
						|
		public function handleReplaceEnqueue($target, $source, $post_id)
 | 
						|
		{
 | 
						|
				// Delegate this to the hook, so all checks are done there.
 | 
						|
				$this->handleImageUploadHook(array(), $post_id);
 | 
						|
 | 
						|
		}
 | 
						|
 | 
						|
    public function generatePluginLinks($links) {
 | 
						|
        $in = '<a href="options-general.php?page=wp-shortpixel-settings">Settings</a>';
 | 
						|
        array_unshift($links, $in);
 | 
						|
        return $links;
 | 
						|
    }
 | 
						|
 | 
						|
    /** Allow certain mime-types if we will be using those.
 | 
						|
    *
 | 
						|
    */
 | 
						|
    public function addMimes($mimes)
 | 
						|
    {
 | 
						|
        $settings = \wpSPIO()->settings();
 | 
						|
        if ($settings->createWebp)
 | 
						|
        {
 | 
						|
            if (! isset($mimes['webp']))
 | 
						|
              $mimes['webp'] = 'image/webp';
 | 
						|
        }
 | 
						|
        if ($settings->createAvif)
 | 
						|
        {
 | 
						|
            if (! isset($mimes['avif']))
 | 
						|
              $mimes['avif'] = 'image/avif';
 | 
						|
        }
 | 
						|
 | 
						|
				if (! isset($mimes['heic']))
 | 
						|
				{
 | 
						|
					$mimes['heic'] = 'image/heic';
 | 
						|
				}
 | 
						|
 | 
						|
				if (! isset($mimes['heif']))
 | 
						|
				{
 | 
						|
					$mimes['heif'] = 'image/heif';
 | 
						|
				}
 | 
						|
 | 
						|
        return $mimes;
 | 
						|
    }
 | 
						|
 | 
						|
		/** Media library gallery view, attempt to add fields that looks like the SPIO status */
 | 
						|
		public function editAttachmentScreen($fields, $post)
 | 
						|
		{
 | 
						|
      return;
 | 
						|
				// Prevent this thing running on edit media screen. The media library grid is before the screen is set, so just check if we are not on the attachment window.
 | 
						|
				$screen_id = \wpSPIO()->env()->screen_id;
 | 
						|
				if ($screen_id == 'attachment')
 | 
						|
				{
 | 
						|
					return $fields;
 | 
						|
				}
 | 
						|
 | 
						|
				$fields["shortpixel-image-optimiser"] = array(
 | 
						|
							"label" => esc_html__("ShortPixel", "shortpixel-image-optimiser"),
 | 
						|
							"input" => "html",
 | 
						|
							"html" => '<div id="sp-msg-' . $post->ID . '">--</div>',
 | 
						|
						);
 | 
						|
 | 
						|
				return $fields;
 | 
						|
		}
 | 
						|
 | 
						|
		public function printComparer()
 | 
						|
		{
 | 
						|
 | 
						|
				$screen_id = \wpSPIO()->env()->screen_id;
 | 
						|
				if ($screen_id !== 'upload')
 | 
						|
				{
 | 
						|
					return false;
 | 
						|
				}
 | 
						|
 | 
						|
				$view = \ShortPixel\Controller\View\ListMediaViewController::getInstance();
 | 
						|
				$view->loadComparer();
 | 
						|
		}
 | 
						|
 | 
						|
    /** When an image is deleted
 | 
						|
    * @hook delete_attachment
 | 
						|
    * @param int $post_id  ID of Post
 | 
						|
    * @return itemHandler ItemHandler object.
 | 
						|
    */
 | 
						|
    public function onDeleteAttachment($post_id) {
 | 
						|
        Log::addDebug('onDeleteImage - Image Removal Detected ' . $post_id);
 | 
						|
        $result = null;
 | 
						|
        $fs = \wpSPIO()->filesystem();
 | 
						|
 | 
						|
        try
 | 
						|
        {
 | 
						|
          $imageObj = $fs->getImage($post_id, 'media');
 | 
						|
					//Log::addDebug('OnDelete ImageObj', $imageObj);
 | 
						|
          if ($imageObj !== false)
 | 
						|
            $result = $imageObj->onDelete();
 | 
						|
        }
 | 
						|
        catch(\Exception $e)
 | 
						|
        {
 | 
						|
          Log::addError('OndeleteImage triggered an error. ' . $e->getMessage(), $e);
 | 
						|
        }
 | 
						|
        return $result;
 | 
						|
    }
 | 
						|
 | 
						|
 | 
						|
 | 
						|
    /** Displays an icon in the toolbar when processing images
 | 
						|
    *   hook - admin_bar_menu
 | 
						|
    *  @param Obj $wp_admin_bar
 | 
						|
    */
 | 
						|
    public function toolbar_shortpixel_processing( $wp_admin_bar ) {
 | 
						|
 | 
						|
        if (! \wpSPIO()->env()->is_screen_to_use )
 | 
						|
          return; // not ours, don't load JS and such.
 | 
						|
 | 
						|
        $settings = \wpSPIO()->settings();
 | 
						|
        $access = AccessModel::getInstance();
 | 
						|
				$quotaController = QuotaController::getInstance();
 | 
						|
 | 
						|
        $extraClasses = " shortpixel-hide";
 | 
						|
        /*translators: toolbar icon tooltip*/
 | 
						|
        $id = 'short-pixel-notice-toolbar';
 | 
						|
        $tooltip = __('ShortPixel optimizing...','shortpixel-image-optimiser');
 | 
						|
        $icon = "shortpixel.png";
 | 
						|
        $successLink = $link = admin_url(current_user_can( 'edit_others_posts')? 'upload.php?page=wp-short-pixel-bulk' : 'upload.php');
 | 
						|
        $blank = "";
 | 
						|
 | 
						|
        if($quotaController->hasQuota() === false)
 | 
						|
				{
 | 
						|
            $extraClasses = " shortpixel-alert shortpixel-quota-exceeded";
 | 
						|
            /*translators: toolbar icon tooltip*/
 | 
						|
            $id = 'short-pixel-notice-exceed';
 | 
						|
            $tooltip = '';
 | 
						|
 | 
						|
            if ($access->userIsAllowed('quota-warning'))
 | 
						|
            {
 | 
						|
              $exceedTooltip = __('ShortPixel quota exceeded. Click for details.','shortpixel-image-optimiser');
 | 
						|
              //$link = "http://shortpixel.com/login/" . $this->_settings->apiKey;
 | 
						|
              $link = "options-general.php?page=wp-shortpixel-settings";
 | 
						|
            }
 | 
						|
            else {
 | 
						|
              $exceedTooltip = __('ShortPixel quota exceeded. Click for details.','shortpixel-image-optimiser');
 | 
						|
              //$link = "http://shortpixel.com/login/" . $this->_settings->apiKey;
 | 
						|
              $link = false;
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        $args = array(
 | 
						|
                'id'    => 'shortpixel_processing',
 | 
						|
                'title' => '<div id="' . $id . '" title="' . $tooltip . '"><span class="stats hidden">0</span><img alt="' . __('ShortPixel icon','shortpixel-image-optimiser') . '" src="'
 | 
						|
                         . plugins_url( 'res/img/'.$icon, SHORTPIXEL_PLUGIN_FILE ) . '" success-url="' . $successLink . '"><span class="shp-alert">!</span>'
 | 
						|
                         . '<div class="controls">
 | 
						|
                              <span class="dashicons dashicons-controls-pause pause" title="' . __('Pause', 'shortpixel-image-optimiser') . '"> </span>
 | 
						|
                              <span class="dashicons dashicons-controls-play play" title="' . __('Resume', 'shortpixel-image-optimiser') . '"> </span>
 | 
						|
                            </div>'
 | 
						|
 | 
						|
                         .'<div class="cssload-container"><div class="cssload-speeding-wheel"></div></div></div>',
 | 
						|
    //            'href'  => 'javascript:void(0)', // $link,
 | 
						|
                'meta'  => array('target'=> $blank, 'class' => 'shortpixel-toolbar-processing' . $extraClasses)
 | 
						|
        );
 | 
						|
        $wp_admin_bar->add_node( $args );
 | 
						|
 | 
						|
        if($quotaController->hasQuota() === false)
 | 
						|
				{
 | 
						|
            $wp_admin_bar->add_node( array(
 | 
						|
                'id'    => 'shortpixel_processing-title',
 | 
						|
                'parent' => 'shortpixel_processing',
 | 
						|
                'title' => $exceedTooltip,
 | 
						|
                'href'  => $link
 | 
						|
            ));
 | 
						|
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
} // class
 |