/acf-image-crop-v5.php

  1. <?php 
  2.  
  3. class acf_field_image_crop extends acf_field_image { 
  4.  
  5.  
  6. /** 
  7. * __construct 
  8. * 
  9. * This function will setup the field type data 
  10. * 
  11. * @type function 
  12. * @date 5/03/2014 
  13. * @since 5.0.0 
  14. * 
  15. * @param n/a 
  16. * @return n/a 
  17. */ 
  18.  
  19. function __construct() { 
  20.  
  21. /** 
  22. * name (string) Single word, no spaces. Underscores allowed 
  23. */ 
  24.  
  25. $this->name = 'image_crop'; 
  26.  
  27.  
  28. /** 
  29. * label (string) Multiple words, can include spaces, visible when selecting a field type 
  30. */ 
  31.  
  32. $this->label = __('Image with user-crop', 'acf-image_crop'); 
  33.  
  34.  
  35. /** 
  36. * category (string) basic | content | choice | relational | jquery | layout | CUSTOM GROUP NAME 
  37. */ 
  38.  
  39. $this->category = 'content'; 
  40.  
  41.  
  42. /** 
  43. * defaults (array) Array of default settings which are merged into the field object. These are used later in settings 
  44. */ 
  45.  
  46. $this->defaults = array( 
  47. 'force_crop' => 'no',  
  48. 'crop_type' => 'hard',  
  49. 'preview_size' => 'medium',  
  50. 'save_format' => 'id',  
  51. 'save_in_media_library' => 'yes',  
  52. 'target_size' => 'thumbnail',  
  53. 'library' => 'all',  
  54. 'retina_mode' => 'no' 
  55. ); 
  56.  
  57. $this->options = get_option( 'acf_image_crop_settings' ); 
  58.  
  59. // add ajax action to be able to retrieve full image size via javascript 
  60. add_action( 'wp_ajax_acf_image_crop_get_image_size', array( &$this, 'crop_get_image_size' ) ); 
  61. add_action( 'wp_ajax_acf_image_crop_perform_crop', array( &$this, 'perform_crop' ) ); 
  62.  
  63.  
  64. // add filter to media query function to hide cropped images from media library 
  65. add_filter('ajax_query_attachments_args', array($this, 'filterMediaQuery')); 
  66.  
  67. // Register extra fields on the media settings page on admin_init 
  68. add_action('admin_init', array($this, 'registerSettings')); 
  69.  
  70. /** 
  71. * l10n (array) Array of strings that are used in JavaScript. This allows JS strings to be translated in PHP and loaded via: 
  72. * var message = acf._e('image_crop', 'error'); 
  73. */ 
  74.  
  75. $this->l10n = array( 
  76. 'width_should_be' => __( 'Width should be at least: ', 'acf-image_crop' ),  
  77. 'height_should_be' => __( 'Height should be at least: ', 'acf-image_crop' ),  
  78. 'selected_width' => __( 'Selected image width: ', 'acf-image_crop' ),  
  79. 'selected_height' => __( 'Selected image height: ', 'acf-image_crop' ),  
  80. 'size_warning' => __( 'Warning: The selected image is smaller than the required size!', 'acf-image_crop' ),  
  81. 'crop_error' => __( 'Sorry, an error occurred when trying to crop your image:') 
  82. ); 
  83.  
  84.  
  85. // do not delete! 
  86. acf_field::__construct(); 
  87. //parent::__construct(); 
  88.  
  89.  
  90.  
  91. // AJAX handler for retieving full image dimensions from ID 
  92. public function crop_get_image_size() 
  93. $img = wp_get_attachment_image_src( $_POST['image_id'], 'full'); 
  94. if($img) { 
  95. echo json_encode( array( 
  96. 'url' => $img[0],  
  97. 'width' => $img[1],  
  98. 'height' => $img[2] 
  99. ) ); 
  100. exit; 
  101.  
  102.  
  103. /** 
  104. * render_field_settings() 
  105. * 
  106. * Create extra settings for your field. These are visible when editing a field 
  107. * 
  108. * @type action 
  109. * @since 3.6 
  110. * @date 23/01/13 
  111. * 
  112. * @param $field (array) the $field being edited 
  113. * @return n/a 
  114. */ 
  115.  
  116. function render_field_settings( $field ) { 
  117.  
  118. /** 
  119. * acf_render_field_setting 
  120. * 
  121. * This function will create a setting for your field. Simply pass the $field parameter and an array of field settings. 
  122. * The array of settings does not require a `value` or `prefix`; These settings are found from the $field array. 
  123. * 
  124. * More than one setting can be added by copy/paste the above code. 
  125. * Please note that you must also have a matching $defaults value for the field name (font_size) 
  126. */ 
  127.  
  128. // crop_type 
  129. acf_render_field_setting( $field, array( 
  130. 'label' => __('Crop type', 'acf-image_crop'),  
  131. 'instructions' => __('Select the type of crop the user should perform', 'acf-image_crop'),  
  132. 'type' => 'select',  
  133. 'name' => 'crop_type',  
  134. 'layout' => 'horizontal',  
  135. 'class' => 'crop-type-select',  
  136. 'choices' => array( 
  137. 'hard' => __('Hard crop', 'acf-image_crop'),  
  138. 'min' => __('Minimal dimensions', 'acf-image_crop') 
  139. )); 
  140.  
  141. // target_size 
  142. $sizes = acf_get_image_sizes(); 
  143. // added 
  144. $instructions = __('Select the target size for this field', 'acf-image_crop'); 
  145. if ( $this->getOption('retina_mode') ) { 
  146. $instructions .= '<br><br><em><strong>'.__('Retina mode is enabled - user will be required to select an image twice this size', 'acf-image_crop').'</strong></em>'; 
  147. // added END 
  148. $sizes['custom'] = __('Custom size', 'acf-image_crop'); 
  149. acf_render_field_setting( $field, array( 
  150. 'label' => __('Target size', 'acf-image_crop'),  
  151. 'instructions' => $instructions,  
  152. 'type' => 'select',  
  153. 'name' => 'target_size',  
  154. 'class' => 'target-size-select',  
  155. 'choices' => $sizes 
  156. )); 
  157.  
  158. // width - conditional: target_size == 'custom' 
  159. acf_render_field_setting( $field, array( 
  160. 'label' => __('Custom target width', 'acf-image_crop'),  
  161. 'instructions' => __('Leave blank for no restriction (does not work with hard crop option)', 'acf-image_crop'),  
  162. 'type' => 'number',  
  163. 'name' => 'width',  
  164. 'class' => 'custom-target-width custom-target-dimension',  
  165. 'append' => 'px' 
  166. )); 
  167.  
  168. // height - conditional: target_size == 'custom' 
  169. acf_render_field_setting( $field, array( 
  170. 'label' => __('Custom target height', 'acf-image_crop'),  
  171. 'instructions' => __('Leave blank for no restriction (does not work with hard crop option)', 'acf-image_crop'),  
  172. 'type' => 'number',  
  173. 'name' => 'height',  
  174. 'class' => 'custom-target-height custom-target-dimension',  
  175. 'append' => 'px' 
  176. )); 
  177.  
  178. // preview_size 
  179. acf_render_field_setting( $field, array( 
  180. 'label' => __('Preview Size', 'acf'),  
  181. 'instructions' => __('Shown when entering data', 'acf'),  
  182. 'type' => 'select',  
  183. 'name' => 'preview_size',  
  184. 'choices' => acf_get_image_sizes() 
  185. )); 
  186.  
  187. // force_crop 
  188. acf_render_field_setting( $field, array( 
  189. 'label' => __('Force crop', 'acf-image_crop'),  
  190. 'instructions' => __('Force the user to crop the image as soon at it is selected', 'acf-image_crop'),  
  191. 'type' => 'radio',  
  192. 'layout' => 'horizontal',  
  193. 'name' => 'force_crop',  
  194. 'choices' => array('yes' => __('Yes', 'acf'), 'no' => __('No', 'acf')) 
  195. )); 
  196.  
  197. // save_in_media_library 
  198. acf_render_field_setting( $field, array( 
  199. 'label' => __('Save cropped image to media library', 'acf-image_crop'),  
  200. 'instructions' => __('If the cropped image is not saved in the media library, "Image URL" is the only available return value.', 'acf-image_crop'),  
  201. 'type' => 'radio',  
  202. 'layout' => 'horizontal',  
  203. 'name' => 'save_in_media_library',  
  204. 'class' => 'save-in-media-library-select',  
  205. 'choices' => array('yes' => __('Yes', 'acf'), 'no' => __('No', 'acf')) 
  206. )); 
  207.  
  208. // only show this setting if the global option for retina mode is not activated 
  209. // retina mode 
  210. if ( !$this->getOption('retina_mode') ) { 
  211. acf_render_field_setting( $field, array( 
  212. 'label' => __('Retina/@2x mode ', 'acf-image_crop'),  
  213. 'instructions' => __('Require and crop double the size set for this image. Enable this if you are using plugins like WP Retina 2x.', 'acf-image_crop'),  
  214. 'type' => 'radio',  
  215. 'layout' => 'horizontal',  
  216. 'name' => 'retina_mode',  
  217. 'choices' => array('yes' => __('Yes', 'acf'), 'no' => __('No', 'acf')) 
  218. )); 
  219. // return_format 
  220. acf_render_field_setting( $field, array( 
  221. 'label' => __('Return Value', 'acf'),  
  222. 'instructions' => __('Specify the returned value on front end', 'acf'),  
  223. 'type' => 'radio',  
  224. 'name' => 'save_format',  
  225. 'layout' => 'horizontal',  
  226. 'class' => 'return-value-select',  
  227. 'choices' => array( 
  228. 'object' => __("Image Array", 'acf'),  
  229. 'url' => __("Image URL", 'acf'),  
  230. 'id' => __("Image ID", 'acf') 
  231. )); 
  232.  
  233. // library 
  234. acf_render_field_setting( $field, array( 
  235. 'label' => __('Library', 'acf'),  
  236. 'instructions' => __('Limit the media library choice', 'acf'),  
  237. 'type' => 'radio',  
  238. 'name' => 'library',  
  239. 'layout' => 'horizontal',  
  240. 'choices' => array( 
  241. 'all' => __('All', 'acf'),  
  242. 'uploadedTo' => __('Uploaded to post', 'acf') 
  243. )); 
  244.  
  245.  
  246.  
  247.  
  248. /** 
  249. * render_field() 
  250. * 
  251. * Create the HTML interface for your field 
  252. * 
  253. * @param $field (array) the $field being rendered 
  254. * 
  255. * @type action 
  256. * @since 3.6 
  257. * @date 23/01/13 
  258. * 
  259. * @param $field (array) the $field being edited 
  260. * @return n/a 
  261. */ 
  262.  
  263. function render_field( $field ) { 
  264.  
  265.  
  266. // enqueue 
  267. acf_enqueue_uploader(); 
  268.  
  269. // get data from value 
  270. //$data = json_decode($field['value']); 
  271. $imageData = $this->get_image_data($field); 
  272.  
  273. $url = ''; 
  274. $orignialImage = null; 
  275.  
  276. if($imageData->original_image) { 
  277. $originalImage = wp_get_attachment_image_src($imageData->original_image, 'full'); 
  278. $url = $imageData->preview_image_url; 
  279.  
  280. $width = 0; 
  281. $height = 0; 
  282.  
  283. if($field['target_size'] == 'custom') { 
  284. $width = $field['width']; 
  285. $height = $field['height']; 
  286. else{ 
  287. global $_wp_additional_image_sizes; 
  288. $s = $field['target_size']; 
  289. if (isset($_wp_additional_image_sizes[$s])) { 
  290. $width = intval($_wp_additional_image_sizes[$s]['width']); 
  291. $height = intval($_wp_additional_image_sizes[$s]['height']); 
  292. } else { 
  293. $width = get_option($s.'_size_w'); 
  294. $height = get_option($s.'_size_h'); 
  295.  
  296. // Retina mode 
  297. if($this->getOption('retina_mode') || $field['retina_mode'] == 'yes') { 
  298. $width = $width * 2; 
  299. $height = $height * 2; 
  300.  
  301. // vars 
  302. $div_atts = array( 
  303. 'class' => 'acf-image-uploader acf-cf acf-image-crop',  
  304. 'data-field-settings' => json_encode($field),  
  305. 'data-crop_type' => $field['crop_type'],  
  306. 'data-target_size' => $field['target_size'],  
  307. 'data-width' => $width,  
  308. 'data-height' => $height,  
  309. 'data-force_crop' => $field['force_crop'] == 'yes' ? 'true' : 'false',  
  310. 'data-save_to_media_library' => $field['save_in_media_library'],  
  311. 'data-save_format' => $field['save_format'],  
  312. 'data-preview_size' => $field['preview_size'],  
  313. 'data-library' => $field['library'] 
  314. ); 
  315. $input_atts = array( 
  316. 'type' => 'hidden',  
  317. 'name' => $field['name'],  
  318. 'value' => htmlspecialchars($field['value']),  
  319. 'data-name' => 'id',  
  320. 'data-original-image' => $imageData->original_image,  
  321. 'data-cropped-image' => json_encode($imageData->cropped_image),  
  322. 'class' => 'acf-image-value' 
  323. ); 
  324.  
  325. // has value? 
  326. if($imageData->original_image) { 
  327. $url = $imageData->preview_image_url; 
  328. $div_atts['class'] .= ' has-value'; 
  329.  
  330. ?> 
  331. <div <?php acf_esc_attr_e( $div_atts ); ?>> 
  332. <div class="acf-hidden"> 
  333. <input <?php acf_esc_attr_e( $input_atts ); ?>/> 
  334. </div> 
  335. <div class="view show-if-value acf-soh"> 
  336. <ul class="acf-hl acf-soh-target"> 
  337. <li><a class="acf-icon -pencil dark" data-name="edit" href="#"><i class="acf-sprite-edit"></i></a></li> 
  338. <li><a class="acf-icon -cancel dark" data-name="remove" href="#"><i class="acf-sprite-delete"></i></a></li> 
  339. </ul> 
  340. <img data-name="image" src="<?php echo $url; ?>" alt=""/> 
  341. <div class="crop-section"> 
  342. <div class="crop-stage"> 
  343. <div class="crop-action"> 
  344. <h4><?php _e('Crop the image', 'acf-image_crop'); ?></h4> 
  345. <?php if ($imageData->original_image ): ?> 
  346. <img class="crop-image" src="<?php echo $imageData->original_image_url ?>" data-width="<?php echo $imageData->original_image_width ?>" data-height="<?php echo $imageData->original_image_height ?>" alt=""> 
  347. <?php endif ?> 
  348. </div> 
  349. <div class="crop-preview"> 
  350. <h4><?php _e('Preview', 'acf-image_crop'); ?></h4> 
  351. <div class="preview"></div> 
  352. <div class="crop-controls"> 
  353. <a href="#" class="button button-large cancel-crop-button"><?php _e('Cancel', 'acf-image_crop'); ?></a> <a href="#" class="button button-large button-primary perform-crop-button"><?php _e('Crop!', 'acf-image_crop'); ?></a> 
  354. </div> 
  355. </div> 
  356. </div> 
  357. <a href="#" class="button button-large init-crop-button"><?php _e('Crop', 'acf-image_crop'); ?></a> 
  358. </div> 
  359. </div> 
  360. <div class="view hide-if-value"> 
  361. <p><?php _e('No image selected', 'acf'); ?> <a data-name="add" class="acf-button button" href="#"><?php _e('Add Image', 'acf'); ?></a></p> 
  362. </div> 
  363. </div> 
  364. <?php 
  365.  
  366.  
  367. /*** 
  368. * Parses the field value into a consistent data object 
  369. ****/ 
  370. function get_image_data($field) { 
  371. $imageData = new stdClass(); 
  372. $imageData->original_image = ''; 
  373. $imageData->original_image_width = ''; 
  374. $imageData->original_image_height = ''; 
  375. $imageData->cropped_image = ''; 
  376. $imageData->original_image_url = ''; 
  377. $imageData->preview_image_url = ''; 
  378. $imageData->image_url = ''; 
  379.  
  380. if($field['value'] == '') { 
  381. // Field has not yet been saved or is an empty image field 
  382. return $imageData; 
  383.  
  384. $data = json_decode($field['value']); 
  385.  
  386. if(! is_object($data)) { 
  387. // Field was saved as a regular image field 
  388. $imageAtts = wp_get_attachment_image_src($field['value'], 'full'); 
  389. $imageData->original_image = $field['value']; 
  390. $imageData->original_image_width = $imageAtts[1]; 
  391. $imageData->original_image_height = $imageAtts[2]; 
  392. $imageData->preview_image_url = $this->get_image_src($field['value'], $field['preview_size']); 
  393. $imageData->image_url = $this->get_image_src($field['value'], 'full'); 
  394. $imageData->original_image_url = $imageData->image_url; 
  395. return $imageData; 
  396.  
  397. if( !is_numeric($data->original_image) ) 
  398. // The field has been saved, but has no image 
  399. return $imageData; 
  400.  
  401. // By now, we have at least a saved original image 
  402. $imageAtts = wp_get_attachment_image_src($data->original_image, 'full'); 
  403. $imageData->original_image = $data->original_image; 
  404. $imageData->original_image_width = $imageAtts[1]; 
  405. $imageData->original_image_height = $imageAtts[2]; 
  406. $imageData->original_image_url = $this->get_image_src($data->original_image, 'full'); 
  407.  
  408. // Set defaults to original image 
  409. $imageData->image_url = $this->get_image_src($data->original_image, 'full'); 
  410. $imageData->preview_image_url = $this->get_image_src($data->original_image, $field['preview_size']); 
  411.  
  412. // Check if there is a cropped version and set appropriate attributes 
  413. if(is_numeric($data->cropped_image)) { 
  414. // Cropped image was saved to media library ans has an ID 
  415. $imageData->cropped_image = $data->cropped_image; 
  416. $imageData->image_url = $this->get_image_src($data->cropped_image, 'full'); 
  417. $imageData->preview_image_url = $this->get_image_src($data->cropped_image, $field['preview_size']); 
  418. elseif(is_object($data->cropped_image)) { 
  419. // Cropped image was not saved to media library and is only stored by its URL 
  420. $imageData->cropped_image = $data->cropped_image; 
  421.  
  422. // Generate appropriate URLs 
  423. $mediaDir = wp_upload_dir(); 
  424. $imageData->image_url = $mediaDir['baseurl'] . '/' . $data->cropped_image->image; 
  425. $imageData->preview_image_url = $mediaDir['baseurl'] . '/' . $data->cropped_image->preview; 
  426. return $imageData; 
  427.  
  428.  
  429. /** 
  430. * input_admin_enqueue_scripts() 
  431. * 
  432. * This action is called in the admin_enqueue_scripts action on the edit screen where your field is created. 
  433. * Use this action to add CSS + JavaScript to assist your render_field() action. 
  434. * 
  435. * @type action (admin_enqueue_scripts) 
  436. * @since 3.6 
  437. * @date 23/01/13 
  438. * 
  439. * @param n/a 
  440. * @return n/a 
  441. */ 
  442.  
  443.  
  444.  
  445. function input_admin_enqueue_scripts() { 
  446. $dir = plugin_dir_url( __FILE__ ); 
  447.  
  448.  
  449. // // register & include JS 
  450. // wp_register_script( 'acf-input-image_crop', "{$dir}js/input.js" ); 
  451. // wp_enqueue_script('acf-input-image_crop'); 
  452.  
  453.  
  454. // // register & include CSS 
  455. // wp_register_style( 'acf-input-image_crop', "{$dir}css/input.css" ); 
  456. // wp_enqueue_style('acf-input-image_crop'); 
  457.  
  458. // register acf scripts 
  459. //wp_register_script('acf-input-image', "{$dir}../advanced-custom-fields-pro/js/input/image.js"); 
  460. wp_register_script('acf-input-image_crop', "{$dir}js/input.js", array('acf-input', 'imgareaselect')); 
  461.  
  462. wp_register_style('acf-input-image_crop', "{$dir}css/input.css", array('acf-input')); 
  463.  
  464. // scripts 
  465. wp_enqueue_script(array( 
  466. 'acf-input-image_crop' 
  467. )); 
  468.  
  469. //wp_localize_script( 'acf-input-image_crop', 'ajax', array('nonce' => wp_create_nonce('acf_nonce')) ); 
  470.  
  471. // styles 
  472. wp_enqueue_style(array( 
  473. 'acf-input-image_crop',  
  474. 'imgareaselect' 
  475. )); 
  476.  
  477.  
  478.  
  479. function perform_crop() { 
  480. $targetWidth = $_POST['target_width']; 
  481. $targetHeight = $_POST['target_height']; 
  482.  
  483. $saveToMediaLibrary = isset($_POST['save_to_media_library']) && $_POST['save_to_media_library'] == 'yes'; 
  484. $imageData = $this->generate_cropped_image($_POST['id'], $_POST['x1'], $_POST['x2'], $_POST['y1'], $_POST['y2'], $targetWidth, $targetHeight, $saveToMediaLibrary, $_POST['preview_size']); 
  485. // $previewUrl = wp_get_attachment_image_src( $id, $_POST['preview_size']); 
  486. // $fullUrl = wp_get_attachment_image_src( $id, 'full'); 
  487. echo json_encode($imageData); 
  488. die(); 
  489.  
  490. function generate_cropped_image($id, $x1, $x2, $y1, $y2, $targetW, $targetH, $saveToMediaLibrary, $previewSize) {//$id, $x1, $x2, $y$, $y2, $targetW, $targetH) { 
  491. require_once ABSPATH . "/wp-admin/includes/file.php"; 
  492. require_once ABSPATH . "/wp-admin/includes/image.php"; 
  493.  
  494. // Create the variable that will hold the new image data 
  495. $imageData = array(); 
  496.  
  497. // Fetch media library info 
  498. $mediaDir = wp_upload_dir(); 
  499.  
  500. // Get original image info 
  501. $originalImageData = wp_get_attachment_metadata($id); 
  502.  
  503. // Get image editor from original image path to crop the image 
  504. $image = wp_get_image_editor( $mediaDir['basedir'] . '/' . $originalImageData['file'] ); 
  505.  
  506. // Set quality 
  507. $image->set_quality( apply_filters('acf-image-crop/image-quality', 100) ); 
  508.  
  509. if(! is_wp_error( $image ) ) { 
  510.  
  511. // Crop the image using the provided measurements 
  512. $image->crop($x1, $y1, $x2 - $x1, $y2 - $y1, $targetW, $targetH); 
  513.  
  514. // Retrieve original filename and seperate it from its file extension 
  515. $originalFileName = explode('.', basename($originalImageData['file'])); 
  516.  
  517. // Retrieve and remove file extension from array 
  518. $originalFileExtension = array_pop($originalFileName); 
  519.  
  520. // Generate new base filename 
  521. $targetFileName = implode('.', $originalFileName) . '_' . $targetW . 'x' . $targetH . apply_filters('acf-image-crop/filename_postfix', '_acf_cropped') . '.' . $originalFileExtension; 
  522.  
  523.  
  524. // Generate target path new file using existing media library 
  525. $targetFilePath = $mediaDir['path'] . '/' . wp_unique_filename( $mediaDir['path'], $targetFileName); 
  526.  
  527. // Get the relative path to save as the actual image url 
  528. $targetRelativePath = str_replace($mediaDir['basedir'] . '/', '', $targetFilePath); 
  529.  
  530. // Save the image to the target path 
  531. if(is_wp_error($image->save($targetFilePath))) { 
  532. // There was an error saving the image 
  533. //TODO handle it 
  534.  
  535. // If file should be saved to media library, create an attachment for it at get the new attachment ID 
  536. if($saveToMediaLibrary) { 
  537. // Generate attachment from created file 
  538.  
  539. // Get the filetype 
  540. $wp_filetype = wp_check_filetype(basename($targetFilePath), null ); 
  541. $attachment = array( 
  542. 'guid' => $mediaDir['url'] . '/' . basename($targetFilePath),  
  543. 'post_mime_type' => $wp_filetype['type'],  
  544. 'post_title' => preg_replace('/\.[^.]+$/', '', basename($targetFilePath)),  
  545. 'post_content' => '',  
  546. 'post_status' => 'inherit' 
  547. ); 
  548. $attachmentId = wp_insert_attachment( $attachment, $targetFilePath); 
  549. $attachmentData = wp_generate_attachment_metadata( $attachmentId, $targetFilePath ); 
  550. wp_update_attachment_metadata( $attachmentId, $attachmentData ); 
  551. add_post_meta($attachmentId, 'acf_is_cropped', 'true', true); 
  552.  
  553. // Add the id to the imageData-array 
  554. $imageData['value'] = $attachmentId; 
  555.  
  556. // Add the image url 
  557. $imageUrlObject = wp_get_attachment_image_src( $attachmentId, 'full'); 
  558. $imageData['url'] = $imageUrlObject[0]; 
  559.  
  560. // Add the preview url as well 
  561. $previewUrlObject = wp_get_attachment_image_src( $attachmentId, $previewSize); 
  562. $imageData['preview_url'] = $previewUrlObject[0]; 
  563. // Else we need to return the actual path of the cropped image 
  564. else{ 
  565. // Add the image url to the imageData-array 
  566. $imageData['value'] = array('image' => $targetRelativePath); 
  567. $imageData['url'] = $mediaDir['baseurl'] . '/' . $targetRelativePath; 
  568.  
  569. // Get preview size dimensions 
  570. global $_wp_additional_image_sizes; 
  571. $previewWidth = 0; 
  572. $previewHeight = 0; 
  573. $crop = 0; 
  574. if (isset($_wp_additional_image_sizes[$previewSize])) { 
  575. $previewWidth = intval($_wp_additional_image_sizes[$previewSize]['width']); 
  576. $previewHeight = intval($_wp_additional_image_sizes[$previewSize]['height']); 
  577. $crop = $_wp_additional_image_sizes[$previewSize]['crop']; 
  578. } else { 
  579. $previewWidth = get_option($previewSize.'_size_w'); 
  580. $previewHeight = get_option($previewSize.'_size_h'); 
  581. $crop = get_option($previewSize.'_crop'); 
  582.  
  583. // Generate preview file path 
  584. $previewFilePath = $mediaDir['path'] . '/' . wp_unique_filename( $mediaDir['path'], 'preview_' . $targetFileName); 
  585. $previewRelativePath = str_replace($mediaDir['basedir'] . '/', '', $previewFilePath); 
  586.  
  587. // Get image editor from cropped image 
  588. $croppedImage = wp_get_image_editor( $targetFilePath ); 
  589. $croppedImage->resize($previewWidth, $previewHeight, $crop); 
  590.  
  591. // Save the preview 
  592. $croppedImage->save($previewFilePath); 
  593.  
  594. // Add the preview url 
  595. $imageData['preview_url'] = $mediaDir['baseurl'] . '/' . $previewRelativePath; 
  596. $imageData['value']['preview'] = $previewRelativePath; 
  597. $imageData['success'] = true; 
  598. return $imageData; 
  599. else{ 
  600. // Handle WP_ERROR 
  601. $response = array(); 
  602. $response['success'] = false; 
  603. $response['error_message'] = ''; 
  604. foreach($image->error_data as $code => $message) { 
  605. $response['error_message'] .= '<p><strong>' . $code . '</strong>: ' . $message . '</p>'; 
  606. return $response; 
  607.  
  608. function get_image_src($id, $size = 'thumbnail') { 
  609. $atts = wp_get_attachment_image_src( $id, $size); 
  610. return $atts[0]; 
  611.  
  612. function getAbsoluteImageUrl($relativeUrl) { 
  613. $mediaDir = wp_upload_dir(); 
  614. return $mediaDir['baseurl'] . '/' . $relativeUrl; 
  615.  
  616. function getImagePath($relativePath) { 
  617. $mediaDir = wp_upload_dir(); 
  618. return $mediaDir['basedir'] . '/' . $relativePath; 
  619.  
  620. function filterMediaQuery($args) { 
  621. // get options 
  622. $options = get_option( 'acf_image_crop_settings' ); 
  623. $hide = ( isset($options['hide_cropped']) && $options['hide_cropped'] ); 
  624.  
  625. // If hide option is enabled, do not select items with the acf_is_cropped meta-field 
  626. if($hide) { 
  627. $args['meta_query']= array( 
  628. array( 
  629. 'key' => 'acf_is_cropped',  
  630. 'compare' => 'NOT EXISTS' 
  631. ); 
  632. return $args; 
  633.  
  634.  
  635. function registerSettings() { 
  636. add_settings_section( 
  637. 'acf_image_crop_settings',  
  638. __('ACF Image Crop Settings', 'acf-image_crop'),  
  639. array($this, 'displayImageCropSettingsSection'),  
  640. 'media' 
  641. ); 
  642.  
  643. register_setting( 
  644. 'media', // settings page 
  645. 'acf_image_crop_settings', // option name 
  646. // added 
  647. // a callback to a new function to set options to false and not to an empty string 
  648. // there was an error if you set an option, save and then unset this option and save again 
  649. // because the option value could be empty if non of the options is activated 
  650. array($this, 'validateImageCropSettingsSection') 
  651. // added END 
  652. ); 
  653.  
  654. add_settings_field( 
  655. 'acf_image_crop_hide_cropped', // id 
  656. __('Hide cropped images from media dialog', 'acf-image_crop'), // setting title 
  657. array($this, 'displayHideFromMediaInput'), // display callback 
  658. 'media', // settings page 
  659. 'acf_image_crop_settings' // settings section 
  660. ); 
  661.  
  662. add_settings_field( 
  663. 'acf_image_crop_retina_mode', // id 
  664. __('Enable global retina mode (beta)', 'acf-image_crop'), // setting title 
  665. array($this, 'displayRetinaModeInput'), // display callback 
  666. 'media', // settings page 
  667. 'acf_image_crop_settings' // settings section 
  668. ); 
  669.  
  670. function displayHideFromMediaInput() { 
  671. // Get plugin options 
  672. $options = get_option( 'acf_image_crop_settings' ); 
  673. $value = $options['hide_cropped']; 
  674.  
  675. // echo the field 
  676. ?> 
  677. <input name='acf_image_crop_settings[hide_cropped]' 
  678. type='checkbox' <?php echo $value ? 'checked' : '' ?> value='true' /> 
  679. <?php 
  680.  
  681. function displayRetinaModeInput() { 
  682. // Get plugin options 
  683. $options = get_option( 'acf_image_crop_settings' ); 
  684. $value = $options['retina_mode']; 
  685.  
  686. // echo the field 
  687. ?> 
  688. <input id="acf-image-crop-retina-mode" name='acf_image_crop_settings[retina_mode]' 
  689. type='checkbox' <?php echo $value ? 'checked' : '' ?> value='true' /> 
  690. <?php 
  691.  
  692. function displayImageCropSettingsSection() { 
  693. echo ''; 
  694.  
  695. // added 
  696. // a function that sets theoptions value to false 
  697. function validateImageCropSettingsSection($input) { 
  698. $input['hide_cropped'] = ( isset( $input['hide_cropped'] ) && $input['hide_cropped'] == true ? true : false ); 
  699. $input['retina_mode'] = ( isset( $input['retina_mode'] ) && $input['retina_mode'] == true ? true : false ); 
  700. return $input; 
  701. // added END 
  702.  
  703.  
  704.  
  705.  
  706. /** 
  707. * input_admin_head() 
  708. * 
  709. * This action is called in the admin_head action on the edit screen where your field is created. 
  710. * Use this action to add CSS and JavaScript to assist your render_field() action. 
  711. * 
  712. * @type action (admin_head) 
  713. * @since 3.6 
  714. * @date 23/01/13 
  715. * 
  716. * @param n/a 
  717. * @return n/a 
  718. */ 
  719.  
  720. /** 
  721.   
  722. function input_admin_head() { 
  723.   
  724.   
  725.   
  726. } 
  727.   
  728. */ 
  729.  
  730.  
  731.  
  732.  
  733. /** 
  734. * input_form_data() 
  735. * 
  736. * This function is called once on the 'input' page between the head and footer 
  737. * There are 2 situations where ACF did not load during the 'acf/input_admin_enqueue_scripts' and 
  738. * 'acf/input_admin_head' actions because ACF did not know it was going to be used. These situations are 
  739. * seen on comments / user edit forms on the front end. This function will always be called, and includes 
  740. * $args that related to the current screen such as $args['post_id'] 
  741. * 
  742. * @type function 
  743. * @date 6/03/2014 
  744. * @since 5.0.0 
  745. * 
  746. * @param $args (array) 
  747. * @return n/a 
  748. */ 
  749.  
  750. /** 
  751.   
  752. function input_form_data( $args ) { 
  753.   
  754.   
  755.   
  756. } 
  757.   
  758. */ 
  759.  
  760.  
  761. /** 
  762. * input_admin_footer() 
  763. * 
  764. * This action is called in the admin_footer action on the edit screen where your field is created. 
  765. * Use this action to add CSS and JavaScript to assist your render_field() action. 
  766. * 
  767. * @type action (admin_footer) 
  768. * @since 3.6 
  769. * @date 23/01/13 
  770. * 
  771. * @param n/a 
  772. * @return n/a 
  773. */ 
  774.  
  775. /** 
  776.   
  777. function input_admin_footer() { 
  778.   
  779.   
  780.   
  781. } 
  782.   
  783. */ 
  784.  
  785.  
  786. /** 
  787. * field_group_admin_enqueue_scripts() 
  788. * 
  789. * This action is called in the admin_enqueue_scripts action on the edit screen where your field is edited. 
  790. * Use this action to add CSS + JavaScript to assist your render_field_options() action. 
  791. * 
  792. * @type action (admin_enqueue_scripts) 
  793. * @since 3.6 
  794. * @date 23/01/13 
  795. * 
  796. * @param n/a 
  797. * @return n/a 
  798. */ 
  799.  
  800.  
  801.  
  802. function field_group_admin_enqueue_scripts() { 
  803.  
  804. $dir = plugin_dir_url( __FILE__ ); 
  805.  
  806. wp_register_script('acf-input-image-crop-options', "{$dir}js/options.js", array('jquery')); 
  807. wp_enqueue_script( 'acf-input-image-crop-options'); 
  808.  
  809. wp_register_style('acf-input-image-crop-options', "{$dir}css/options.css"); 
  810. wp_enqueue_style( 'acf-input-image-crop-options'); 
  811.  
  812.  
  813.  
  814.  
  815. /** 
  816. * field_group_admin_head() 
  817. * 
  818. * This action is called in the admin_head action on the edit screen where your field is edited. 
  819. * Use this action to add CSS and JavaScript to assist your render_field_options() action. 
  820. * 
  821. * @type action (admin_head) 
  822. * @since 3.6 
  823. * @date 23/01/13 
  824. * 
  825. * @param n/a 
  826. * @return n/a 
  827. */ 
  828.  
  829. /** 
  830.   
  831. function field_group_admin_head() { 
  832.   
  833. } 
  834.   
  835. */ 
  836.  
  837.  
  838. /** 
  839. * load_value() 
  840. * 
  841. * This filter is applied to the $value after it is loaded from the db 
  842. * 
  843. * @type filter 
  844. * @since 3.6 
  845. * @date 23/01/13 
  846. * 
  847. * @param $value (mixed) the value found in the database 
  848. * @param $post_id (mixed) the $post_id from which the value was loaded 
  849. * @param $field (array) the field array holding all the field options 
  850. * @return $value 
  851. */ 
  852.  
  853. /** 
  854.   
  855. function load_value( $value, $post_id, $field ) { 
  856.   
  857. return $value; 
  858.   
  859. } 
  860.   
  861. */ 
  862.  
  863.  
  864. /** 
  865. * update_value() 
  866. * 
  867. * This filter is applied to the $value before it is saved in the db 
  868. * 
  869. * @type filter 
  870. * @since 3.6 
  871. * @date 23/01/13 
  872. * 
  873. * @param $value (mixed) the value found in the database 
  874. * @param $post_id (mixed) the $post_id from which the value was loaded 
  875. * @param $field (array) the field array holding all the field options 
  876. * @return $value 
  877. */ 
  878.  
  879. /** 
  880.   
  881. function update_value( $value, $post_id, $field ) { 
  882.   
  883. return $value; 
  884.   
  885. } 
  886.   
  887. */ 
  888.  
  889.  
  890. /** 
  891. * format_value() 
  892. * 
  893. * This filter is appied to the $value after it is loaded from the db and before it is returned to the template 
  894. * 
  895. * @type filter 
  896. * @since 3.6 
  897. * @date 23/01/13 
  898. * 
  899. * @param $value (mixed) the value which was loaded from the database 
  900. * @param $post_id (mixed) the $post_id from which the value was loaded 
  901. * @param $field (array) the field array holding all the field options 
  902. * 
  903. * @return $value (mixed) the modified value 
  904. */ 
  905.  
  906.  
  907.  
  908. function format_value( $value, $post_id, $field ) { 
  909.  
  910. // validate 
  911. if( !$value ) { 
  912. return false; 
  913.  
  914. $data = json_decode($value); 
  915.  
  916. if(is_object($data)) { 
  917. $value = $data->cropped_image; 
  918. else{ 
  919. // We are migrating from a standard image field 
  920. $data = new stdClass(); 
  921. $data->cropped_image = $value; 
  922. $data->original_image = $value; 
  923.  
  924. // format 
  925. if( $field['save_format'] == 'url' ) 
  926. if(is_numeric($data->cropped_image)) { 
  927. $value = wp_get_attachment_url( $data->cropped_image ); 
  928. elseif(is_array($data->cropped_image)) { 
  929.  
  930. $value = $this->getAbsoluteImageUrl($data->cropped_image['image']); 
  931. elseif(is_object($data->cropped_image)) { 
  932. $value = $this->getAbsoluteImageUrl($data->cropped_image->image); 
  933.  
  934. elseif( $field['save_format'] == 'object' ) 
  935. if(is_numeric($data->cropped_image )) { 
  936. $value = $this->getImageArray($data->cropped_image); 
  937. $value['original_image'] = $this->getImageArray($data->original_image); 
  938. // $attachment = get_post( $data->cropped_image ); 
  939. // // validate 
  940. // if( !$attachment ) 
  941. // { 
  942. // return false; 
  943. // } 
  944.  
  945.  
  946. // // create array to hold value data 
  947. // $src = wp_get_attachment_image_src( $attachment->ID, 'full' ); 
  948.  
  949. // $value = array( 
  950. // 'id' => $attachment->ID,  
  951. // 'alt' => get_post_meta($attachment->ID, '_wp_attachment_image_alt', true),  
  952. // 'title' => $attachment->post_title,  
  953. // 'caption' => $attachment->post_excerpt,  
  954. // 'description' => $attachment->post_content,  
  955. // 'mime_type' => $attachment->post_mime_type,  
  956. // 'url' => $src[0],  
  957. // 'width' => $src[1],  
  958. // 'height' => $src[2],  
  959. // 'sizes' => array(),  
  960. // ); 
  961.  
  962.  
  963. // // find all image sizes 
  964. // $image_sizes = get_intermediate_image_sizes(); 
  965.  
  966. // if( $image_sizes ) 
  967. // { 
  968. // foreach( $image_sizes as $image_size ) 
  969. // { 
  970. // // find src 
  971. // $src = wp_get_attachment_image_src( $attachment->ID, $image_size ); 
  972.  
  973. // // add src 
  974. // $value[ 'sizes' ][ $image_size ] = $src[0]; 
  975. // $value[ 'sizes' ][ $image_size . '-width' ] = $src[1]; 
  976. // $value[ 'sizes' ][ $image_size . '-height' ] = $src[2]; 
  977. // } 
  978. // // foreach( $image_sizes as $image_size ) 
  979. // } 
  980. elseif(is_array( $data->cropped_image) || is_object($data->cropped_image)) { 
  981. // Cropped image is not saved to media directory. Get data from original image instead 
  982. $value = $this->getImageArray($data->original_image); 
  983.  
  984. // Get the relative url from data 
  985. $relativeUrl = ''; 
  986. if(is_array( $data->cropped_image)) { 
  987. $relativeUrl = $data->cropped_image['image']; 
  988. else{ 
  989. $relativeUrl = $data->cropped_image->image; 
  990.  
  991. // Replace URL with cropped version 
  992. $value['url'] = $this->getAbsoluteImageUrl($relativeUrl); 
  993.  
  994. // Calculate and replace sizes 
  995. $imagePath = $this->getImagePath($relativeUrl); 
  996. $dimensions = getimagesize($imagePath); 
  997. $value['width'] = $dimensions[0]; 
  998. $value['height'] = $dimensions[1]; 
  999.  
  1000. // Add original image info 
  1001. $value['original_image'] = $this->getImageArray($data->original_image); 
  1002. // elseif(is_object($data->cropped_image)) { 
  1003. // $value = $this->getImageArray($data->original_image); 
  1004. // $value['url'] = $this->getAbsoluteImageUrl($data->cropped_image->image); 
  1005.  
  1006. // // Calculate sizes 
  1007. // $imagePath = $this->getImagePath($data->cropped_image->image); 
  1008. // $dimensions = getimagesize($imagePath); 
  1009. // $value['width'] = $dimensions[0]; 
  1010. // $value['height'] = $dimensions[1]; 
  1011.  
  1012. // // Add original image info 
  1013. // $value['original_image'] = $this->getImageArray($data->original_image); 
  1014. // } 
  1015. else{ 
  1016. //echo 'ELSE'; 
  1017.  
  1018. return $value; 
  1019.  
  1020.  
  1021. function getOption($key) { 
  1022. return isset($this->options[$key]) ? $this->options[$key] : null; 
  1023.  
  1024. function getImageArray($id) { 
  1025. $attachment = get_post( $id ); 
  1026. // validate 
  1027. if( !$attachment ) 
  1028. return false; 
  1029.  
  1030.  
  1031. // create array to hold value data 
  1032. $src = wp_get_attachment_image_src( $attachment->ID, 'full' ); 
  1033.  
  1034. $imageArray = array( 
  1035. 'id' => $attachment->ID,  
  1036. 'alt' => get_post_meta($attachment->ID, '_wp_attachment_image_alt', true),  
  1037. 'title' => $attachment->post_title,  
  1038. 'caption' => $attachment->post_excerpt,  
  1039. 'description' => $attachment->post_content,  
  1040. 'mime_type' => $attachment->post_mime_type,  
  1041. 'url' => $src[0],  
  1042. 'width' => $src[1],  
  1043. 'height' => $src[2],  
  1044. 'sizes' => array(),  
  1045. ); 
  1046.  
  1047.  
  1048. // find all image sizes 
  1049. $image_sizes = get_intermediate_image_sizes(); 
  1050.  
  1051. if( $image_sizes ) 
  1052. foreach( $image_sizes as $image_size ) 
  1053. // find src 
  1054. $src = wp_get_attachment_image_src( $attachment->ID, $image_size ); 
  1055.  
  1056. // add src 
  1057. $imageArray[ 'sizes' ][ $image_size ] = $src[0]; 
  1058. $imageArray[ 'sizes' ][ $image_size . '-width' ] = $src[1]; 
  1059. $imageArray[ 'sizes' ][ $image_size . '-height' ] = $src[2]; 
  1060. return $imageArray; 
  1061.  
  1062.  
  1063.  
  1064. /** 
  1065. * validate_value() 
  1066. * 
  1067. * This filter is used to perform validation on the value prior to saving. 
  1068. * All values are validated regardless of the field's required setting. This allows you to validate and return 
  1069. * messages to the user if the value is not correct 
  1070. * 
  1071. * @type filter 
  1072. * @date 11/02/2014 
  1073. * @since 5.0.0 
  1074. * 
  1075. * @param $valid (boolean) validation status based on the value and the field's required setting 
  1076. * @param $value (mixed) the $_POST value 
  1077. * @param $field (array) the field array holding all the field options 
  1078. * @param $input (string) the corresponding input name for $_POST value 
  1079. * @return $valid 
  1080. */ 
  1081.  
  1082. /** 
  1083.   
  1084. function validate_value( $valid, $value, $field, $input ) { 
  1085.   
  1086. // Basic usage 
  1087. if( $value < $field['custom_minimum_setting'] ) 
  1088. { 
  1089. $valid = false; 
  1090. } 
  1091.   
  1092.   
  1093. // Advanced usage 
  1094. if( $value < $field['custom_minimum_setting'] ) 
  1095. { 
  1096. $valid = __('The value is too little!', 'acf-image_crop'),  
  1097. } 
  1098.   
  1099.   
  1100. // return 
  1101. return $valid; 
  1102.   
  1103. } 
  1104.   
  1105. */ 
  1106.  
  1107.  
  1108. /** 
  1109. * delete_value() 
  1110. * 
  1111. * This action is fired after a value has been deleted from the db. 
  1112. * Please note that saving a blank value is treated as an update, not a delete 
  1113. * 
  1114. * @type action 
  1115. * @date 6/03/2014 
  1116. * @since 5.0.0 
  1117. * 
  1118. * @param $post_id (mixed) the $post_id from which the value was deleted 
  1119. * @param $key (string) the $meta_key which the value was deleted 
  1120. * @return n/a 
  1121. */ 
  1122.  
  1123. /** 
  1124.   
  1125. function delete_value( $post_id, $key ) { 
  1126.   
  1127.   
  1128.   
  1129. } 
  1130.   
  1131. */ 
  1132.  
  1133.  
  1134. /** 
  1135. * load_field() 
  1136. * 
  1137. * This filter is applied to the $field after it is loaded from the database 
  1138. * 
  1139. * @type filter 
  1140. * @date 23/01/2013 
  1141. * @since 3.6.0 
  1142. * 
  1143. * @param $field (array) the field array holding all the field options 
  1144. * @return $field 
  1145. */ 
  1146.  
  1147. /** 
  1148.   
  1149. function load_field( $field ) { 
  1150.   
  1151. return $field; 
  1152.   
  1153. } 
  1154.   
  1155. */ 
  1156.  
  1157.  
  1158. /** 
  1159. * update_field() 
  1160. * 
  1161. * This filter is applied to the $field before it is saved to the database 
  1162. * 
  1163. * @type filter 
  1164. * @date 23/01/2013 
  1165. * @since 3.6.0 
  1166. * 
  1167. * @param $field (array) the field array holding all the field options 
  1168. * @return $field 
  1169. */ 
  1170.  
  1171. /** 
  1172.   
  1173. function update_field( $field ) { 
  1174.   
  1175. return $field; 
  1176.   
  1177. } 
  1178.   
  1179. */ 
  1180.  
  1181.  
  1182. /** 
  1183. * delete_field() 
  1184. * 
  1185. * This action is fired after a field is deleted from the database 
  1186. * 
  1187. * @type action 
  1188. * @date 11/02/2014 
  1189. * @since 5.0.0 
  1190. * 
  1191. * @param $field (array) the field array holding all the field options 
  1192. * @return n/a 
  1193. */ 
  1194.  
  1195. /** 
  1196.   
  1197. function delete_field( $field ) { 
  1198.   
  1199.   
  1200.   
  1201. } 
  1202.   
  1203. */ 
  1204.  
  1205.  
  1206.  
  1207.  
  1208. // create field 
  1209. new acf_field_image_crop(); 
  1210.  
  1211. ?> 
.