Advertisement
Guest User

Untitled

a guest
Mar 4th, 2025
31
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 36.53 KB | None | 0 0
  1. <?php
  2. if( ! defined( 'AVIA_FW' ) ) { exit; } // Exit if accessed directly
  3.  
  4. /**
  5. * Provides support for responsive images.
  6. *
  7. * As WP already offers support for this feature this class mainly provides wrappers to be used
  8. * to prepare html code for WP to be able to add needed attributes.
  9. *
  10. * With WP 5.5 loading="lazy" attribute was added. This is also supported in this class.
  11. *
  12. * @since 4.7.5.1
  13. * @added_by Günter
  14. */
  15.  
  16. if( ! class_exists( 'av_responsive_images', false ) )
  17. {
  18.  
  19. class av_responsive_images extends aviaFramework\base\object_properties
  20. {
  21.  
  22. /**
  23. * Holds the instance of this class
  24. *
  25. * @since 4.7.5.1
  26. * @var av_responsive_images
  27. */
  28. static private $_instance = null;
  29.  
  30. /**
  31. *
  32. * @since 4.7.5.1
  33. * @var array
  34. */
  35. protected $config;
  36.  
  37. /**
  38. * Default WP thumbnails, can be changed with filter
  39. *
  40. * @since 4.7.5.1
  41. * @var array
  42. */
  43. protected $wp_default_names;
  44.  
  45. /**
  46. * Stores all WP default image sizes (original sizes, no responsive scaled down)
  47. *
  48. * @since 4.7.5.1
  49. * @var array
  50. */
  51. protected $wp_default_images;
  52.  
  53. /**
  54. * Stores all theme image sizes (original sizes, no responsive scaled down)
  55. *
  56. * @since 4.7.5.1
  57. * @var array
  58. */
  59. protected $theme_images;
  60.  
  61. /**
  62. * Stores all image sizes defined by plugins (original sizes, no responsive scaled down)
  63. *
  64. * @since 4.7.5.1
  65. * @var array
  66. */
  67. protected $plugin_images;
  68.  
  69. /**
  70. * For performance this are the merged basic image sized (WP, theme, plugin)
  71. *
  72. * @since 4.7.5.1
  73. * @var array
  74. */
  75. protected $base_images;
  76.  
  77. /**
  78. * Array of image sizes and their human readable string
  79. *
  80. * @since 4.7.5.1
  81. * @var array
  82. */
  83. protected $readable_img_sizes;
  84.  
  85. /**
  86. * Array of image sizes grouped by aspect ratio
  87. *
  88. * @since 4.7.5.1
  89. * @var array
  90. */
  91. protected $size_groups;
  92.  
  93. /**
  94. * Holds an array of id's of images that must not get the loading="lazy" attribute
  95. *
  96. * @since 4.7.6.2
  97. * @var array
  98. */
  99. protected $no_lazy_loading_ids;
  100.  
  101. /**
  102. * Option key for relationship attachment URL to attachment ID
  103. *
  104. * @since 4.8.4
  105. * @var string
  106. */
  107. protected $opt_key_attachment_urls;
  108.  
  109. /**
  110. * ALB elements can temporary disable usage of responsive images and override theme option setting (only when enabled)
  111. *
  112. * @since 4.8.6.3
  113. * @var boolean
  114. */
  115. protected $temporary_disabled;
  116.  
  117. /**
  118. * Return the instance of this class
  119. *
  120. * @since 4.7.5.1
  121. * @param array|null $config
  122. * @return av_responsive_images
  123. */
  124. static public function instance( $config = array() )
  125. {
  126. if( is_null( av_responsive_images::$_instance ) )
  127. {
  128. av_responsive_images::$_instance = new av_responsive_images( $config );
  129. }
  130.  
  131. return av_responsive_images::$_instance;
  132. }
  133.  
  134. /**
  135. * @since 4.7.5.1
  136. * @param array|null $config
  137. */
  138. protected function __construct( $config = array() )
  139. {
  140. global $avia;
  141.  
  142. $this->config = apply_filters( 'avf_responsive_images_defaults', array(
  143. 'default_jpeg_quality' => 100, // used by WP filter
  144. 'theme_images' => array(),
  145. 'readableImgSizes' => array(),
  146. 'no_lazy_loading_ids' => array()
  147. ) );
  148.  
  149. $this->reinit( $config );
  150.  
  151. /**
  152. * @since 4.7.5.1
  153. * @param array
  154. * @return array
  155. */
  156. $this->wp_default_names = apply_filters( 'avf_wp_default_thumbnail_names', array( 'thumb', 'thumbnail', 'medium', 'medium_large', 'large', 'post-thumbnail', '1536x1536', '2048x2048' ) );
  157.  
  158. $this->wp_default_images = array();
  159. $this->theme_images = array();
  160. $this->plugin_images = array();
  161. $this->base_images = array();
  162. $this->readable_img_sizes = array();
  163. $this->size_groups = array();
  164. $this->no_lazy_loading_ids = array();
  165. $this->opt_key_attachment_urls = avia_backend_safe_string( $avia->base_data['prefix'] ) . '-attachment_urls';
  166. $this->temporary_disabled = false;
  167.  
  168.  
  169. add_action( 'init', array( $this, 'handler_wp_init'), 999999 );
  170. add_filter( 'body_class', array( $this, 'handler_body_class' ), 10, 2 );
  171.  
  172. add_filter( 'jpeg_quality', array( $this, 'handler_wp_jpeg_quality' ), 99, 2 );
  173. add_filter( 'wp_editor_set_quality', array( $this, 'handler_wp_editor_set_quality'), 99, 2 );
  174.  
  175. add_filter( 'post_thumbnail_html', array( $this, 'handler_wp_post_thumbnail_html' ), 10, 5 );
  176. add_filter( 'wp_get_attachment_image_attributes', array( $this, 'handler_wp_get_attachment_image_attributes' ), 99, 3 );
  177.  
  178. add_filter( 'wp_lazy_loading_enabled', array( $this, 'handler_wp_lazy_loading_enabled' ), 99, 3 );
  179. add_filter( 'wp_img_tag_add_loading_attr', array( $this, 'handler_wp_img_tag_add_loading_attr' ), 99, 3 );
  180.  
  181. // reset postmeta and delete all css files
  182. add_action( 'ava_after_theme_update', array( $this, 'handler_ava_reset_db_options' ), 100 );
  183. add_action( 'ava_after_import_demo_settings', array( $this, 'handler_ava_reset_db_options'), 100 );
  184. add_action( 'avia_ajax_after_save_options_page', array( $this, 'handler_ava_reset_db_options'), 100 );
  185. }
  186.  
  187. /**
  188. * @since 4.7.5.1
  189. */
  190. public function __destruct()
  191. {
  192. unset( $this->config );
  193. unset( $this->wp_default_names );
  194. unset( $this->wp_default_images );
  195. unset( $this->theme_images );
  196. unset( $this->plugin_images );
  197. unset( $this->base_images );
  198. unset( $this->readable_img_sizes );
  199. unset( $this->size_groups );
  200. unset( $this->no_lazy_loading_ids );
  201. }
  202.  
  203. /**
  204. * Allows a reinitialisation of config array
  205. *
  206. * @since 4.7.5.1
  207. * @param array $config
  208. */
  209. public function reinit( array $config )
  210. {
  211. if( empty( $config ) )
  212. {
  213. return;
  214. }
  215.  
  216. $default_jpeg_quality = isset( $config['default_jpeg_quality'] ) ? $config['default_jpeg_quality'] : $this->config['default_jpeg_quality'];
  217.  
  218. $this->config = array_merge_recursive( $this->config, $config );
  219.  
  220. // array_merge_recursive creates array !!!
  221. $this->config['default_jpeg_quality'] = $default_jpeg_quality;
  222. }
  223.  
  224. /**
  225. * Loads all defined image sizes and inits local variables
  226. * All plugins must have registered their images before
  227. *
  228. * @since 4.7.5.1
  229. */
  230. public function handler_wp_init()
  231. {
  232. global $_wp_additional_image_sizes;
  233.  
  234. /**
  235. * @since 4.7.5.1
  236. * @param array
  237. * @return array
  238. */
  239. $this->no_lazy_loading_ids = (array) apply_filters( 'avf_init_no_lazy_loading_ids', $this->config['no_lazy_loading_ids'] );
  240.  
  241.  
  242. if( ! is_admin() )
  243. {
  244. return;
  245. }
  246.  
  247. $this->theme_images = $this->config['theme_images'];
  248.  
  249. foreach( get_intermediate_image_sizes() as $_size )
  250. {
  251. $img_size = array();
  252.  
  253. if( in_array( $_size, $this->wp_default_names ) )
  254. {
  255. if( isset( $_wp_additional_image_sizes[ $_size ] ) )
  256. {
  257. $img_size['width'] = $_wp_additional_image_sizes[ $_size ]['width'];
  258. $img_size['height'] = $_wp_additional_image_sizes[ $_size ]['height'];
  259. $img_size['crop'] = (bool) $_wp_additional_image_sizes[ $_size ]['crop'];
  260. }
  261. else
  262. {
  263. $img_size['width'] = get_option( "{$_size}_size_w", 0 );
  264. $img_size['height'] = get_option( "{$_size}_size_h", 0 );
  265. $img_size['crop'] = (bool) get_option( "{$_size}_crop", false );
  266. }
  267.  
  268. $this->wp_default_images[ $_size ] = $img_size;
  269. }
  270. else if( array_key_exists( $_size, $this->theme_images ) )
  271. {
  272. $this->theme_images[ $_size ]['width'] = is_numeric( $this->theme_images[ $_size ]['width'] ) ? (int) $this->theme_images[ $_size ]['width'] : 0;
  273. $this->theme_images[ $_size ]['height'] = is_numeric( $this->theme_images[ $_size ]['height'] ) ? (int) $this->theme_images[ $_size ]['height'] : 0;
  274. $this->theme_images[ $_size ]['crop'] = empty( $this->theme_images[ $_size ]['crop'] ) ? false : true;
  275. }
  276. else if ( isset( $_wp_additional_image_sizes[ $_size ] ) )
  277. {
  278. $img_size['width'] = $_wp_additional_image_sizes[ $_size ]['width'];
  279. $img_size['height'] = $_wp_additional_image_sizes[ $_size ]['height'];
  280. $img_size['crop'] = (bool) $_wp_additional_image_sizes[ $_size ]['crop'];
  281. $this->plugin_images[ $_size ] = $img_size;
  282. }
  283. }
  284.  
  285. $this->base_images = array_merge( $this->wp_default_images, $this->theme_images, $this->plugin_images );
  286.  
  287. /**
  288. * Allows to translate WP and plugin thumbnail names to human readable
  289. *
  290. * @since 4.7.5.1
  291. * @param array $this->config['readableImgSizes']
  292. * @param array $this->base_images
  293. * @param av_responsive_images $this
  294. * @return array
  295. */
  296. $this->readable_img_sizes = apply_filters( 'avf_resp_images_readable_sizes', $this->config['readableImgSizes'], $this->base_images, $this );
  297.  
  298. $this->group_image_sizes();
  299.  
  300. /**
  301. *
  302. * @since 4.7.5.1
  303. * @param array $this->size_groups
  304. * @param av_responsive_images $this
  305. */
  306. do_action( 'ava_responsive_image_sizes_grouped', $this->size_groups, $this );
  307. }
  308.  
  309. /**
  310. * Add extra classes
  311. *
  312. * @since 4.8.2
  313. * @param array $classes
  314. * @param array $class
  315. * @return string
  316. */
  317. public function handler_body_class( array $classes, array $class )
  318. {
  319. if( ! $this->responsive_images_active() )
  320. {
  321. return $classes;
  322. }
  323.  
  324. $classes[] = 'avia-responsive-images-support';
  325.  
  326. if( $this->responsive_images_lightbox_active() )
  327. {
  328. $classes[] = 'responsive-images-lightbox-support';
  329. }
  330.  
  331. return $classes;
  332. }
  333.  
  334. /**
  335. * Prepares images for WP to recognise for scrset and sizes and for lazy loading attr.
  336. *
  337. * @since 4.7.5.1
  338. * @param string $html
  339. * @param int $post_id
  340. * @param int $post_thumbnail_id
  341. * @param string|array $size
  342. * @param string $attr
  343. * @return string
  344. */
  345. public function handler_wp_post_thumbnail_html( $html, $post_id, $post_thumbnail_id, $size, $attr )
  346. {
  347. $lazy_loading = $this->is_attachment_id_not_lazy_loading( $post_thumbnail_id ) ? 'disabled' : 'enabled';
  348.  
  349. return $this->prepare_single_image( $html, $post_thumbnail_id, $lazy_loading );
  350. }
  351.  
  352. /**
  353. * Checks for theme responsive image setting and removes the attributes if necessary.
  354. * Also removes default "loading" attribute for a specific attachment depending
  355. * on theme or global settings.
  356. *
  357. * @since 4.7.5.1
  358. * @param array $attr
  359. * @param WP_Post $attachment
  360. * @param string|array $size
  361. * @return array
  362. */
  363. public function handler_wp_get_attachment_image_attributes( $attr, $attachment, $size )
  364. {
  365. global $avia_config;
  366.  
  367. if( ! $this->responsive_images_active() )
  368. {
  369. unset( $attr['srcset'] );
  370. unset( $attr['sizes'] );
  371. }
  372.  
  373. if( ! array_key_exists( 'loading', $attr ) )
  374. {
  375. return $attr;
  376. }
  377.  
  378. if( isset( $avia_config['alb_html_lazy_loading'] ) && ( $avia_config['alb_html_lazy_loading'] != 'enabled' ) )
  379. {
  380. $this->add_attachment_id_to_not_lazy_loading( $attachment->ID );
  381. }
  382.  
  383. if( $this->is_attachment_id_not_lazy_loading( $attachment->ID ) )
  384. {
  385. unset( $attr['loading'] );
  386.  
  387. $class = 'avia-img-lazy-loading-not-' . $attachment->ID;
  388. }
  389. else
  390. {
  391. $class = 'avia-img-lazy-loading-' . $attachment->ID;
  392. }
  393.  
  394. if( array_key_exists( 'class', $attr ) && false === strpos( $attr['class'], $class ) )
  395. {
  396. $class .= ' ' . $attr['class'];
  397. }
  398.  
  399. $attr['class'] = $class;
  400.  
  401. return $attr;
  402. }
  403.  
  404. /**
  405. * Sets the default image to our default quality (100%) for more beautiful images when used in conjunction with img optimization plugins
  406. *
  407. * @since 4.7.5.1
  408. * @since 4.3 in functions-enfold added_by Kriesi
  409. * @param int $quality
  410. * @param string $mime_type
  411. * @return int
  412. */
  413. public function handler_wp_editor_set_quality( $quality, $mime_type = '' )
  414. {
  415. return apply_filters( 'avf_wp_editor_set_quality', $this->config['default_jpeg_quality'], $quality, $mime_type );
  416. }
  417.  
  418.  
  419. /**
  420. * Sets the default image to our default quality (100%) for more beautiful images when used in conjunction with img optimization plugins
  421. *
  422. * @since 4.7.5.1
  423. * @since 4.3 in functions-enfold added_by Kriesi
  424. * @param int $quality
  425. * @param string $context edit_image | image_resize
  426. * @return int
  427. */
  428. public function handler_wp_jpeg_quality( $quality, $context = '' )
  429. {
  430. return apply_filters( 'avf_jpeg_quality', $this->config['default_jpeg_quality'], $quality, $context );
  431. }
  432.  
  433. /**
  434. * Reset option to store attachment URL -> attachment ID relationship to speed up frontend
  435. *
  436. * @since 4.8.4
  437. */
  438. public function handler_ava_reset_db_options()
  439. {
  440. update_option( $this->opt_key_attachment_urls, array() );
  441. }
  442.  
  443. /**
  444. * General WP handler to disable lazy loading
  445. * WP 5.5 does not give any information about the image - so we can only
  446. * make a global check here. This might change in a future version which might allow
  447. * to change our checks. At the moment we only can remove the loading attribute in
  448. * av_responsive_images::handler_wp_img_tag_add_loading_attr
  449. *
  450. * @since 4.7.6.2
  451. * @param boolean $default
  452. * @param string $tag_name
  453. * @param string $context
  454. * @return boolean
  455. */
  456. public function handler_wp_lazy_loading_enabled( $default, $tag_name = 'img', $context = '' )
  457. {
  458. if( $tag_name != 'img' )
  459. {
  460. return $default;
  461. }
  462.  
  463. /**
  464. * Disable for images loaded in an ajax call
  465. */
  466. if( defined( 'DOING_AJAX' ) && DOING_AJAX )
  467. {
  468. return false;
  469. }
  470.  
  471. if( 'none' == $this->lazy_loading_active() )
  472. {
  473. return false;
  474. }
  475.  
  476. return $default;
  477. }
  478.  
  479. /**
  480. * Checks if an image must not be lazy loaded.
  481. * Individual immage settings overrules global settings
  482. * By default WP has a threshold to force loading images
  483. *
  484. * apply_filters( 'wp_omit_loading_attr_threshold', 1 )
  485. *
  486. * @since 4.7.6.2
  487. * @param string|bool $attr_value false | 'lazy' | 'eager'
  488. * @param string $image_tag
  489. * @param string $context
  490. * @return type
  491. */
  492. public function handler_wp_img_tag_add_loading_attr( $attr_value, $image_tag = '', $context = '' )
  493. {
  494. if( false === $attr_value )
  495. {
  496. return $attr_value;
  497. }
  498.  
  499. if( 'none' == $this->lazy_loading_active() )
  500. {
  501. return false;
  502. }
  503.  
  504. if( false !== strpos( $image_tag, 'avia-img-lazy-loading-not-' ) )
  505. {
  506. return false;
  507. }
  508.  
  509. if( false !== strpos( $image_tag, 'avia-img-lazy-loading-' ) )
  510. {
  511. return 'lazy';
  512. }
  513.  
  514. $attachment_id = $this->get_attachment_id( $image_tag );
  515.  
  516. if( false === $attachment_id )
  517. {
  518. return $attr_value;
  519. }
  520.  
  521. if( $this->is_attachment_id_not_lazy_loading( $attachment_id ) )
  522. {
  523. return false;
  524. }
  525.  
  526. return $attr_value;
  527. }
  528.  
  529. /**
  530. * Allow to disable usage of responsive images for a period of code execution - e.g. during a single ALB element.
  531. * Has to be reset when finished !!!
  532. * Anything different from 'disabled' will reset this flag.
  533. *
  534. * @since 4.8.6.3
  535. * @param string $action 'disabled' | mixed
  536. */
  537. public function force_disable( $action = '' )
  538. {
  539. if( current_theme_supports( 'avia_show_alb_responsive_image_option' ) )
  540. {
  541. $this->temporary_disabled = 'disabled' === $action;
  542. }
  543. else
  544. {
  545. $this->temporary_disabled = false;
  546. }
  547. }
  548.  
  549. /**
  550. * Adds an attachment id or array of ids to be recognized for not lazy loading.
  551. *
  552. * @since 4.7.6.2
  553. * @param int|array|mixed $ids
  554. */
  555. public function add_attachment_id_to_not_lazy_loading( $ids )
  556. {
  557. if( ! is_array( $ids ) )
  558. {
  559. if( ! is_numeric( $ids ) || ( $ids <= 0 ) )
  560. {
  561. return;
  562. }
  563.  
  564. $ids = array( $ids );
  565. }
  566.  
  567. $this->no_lazy_loading_ids = array_unique( array_merge( $this->no_lazy_loading_ids, $ids ) );
  568. }
  569.  
  570. /**
  571. * Checks a given attachment id if lazy loading is prohibited
  572. *
  573. * @since 4.7.6.2
  574. * @param int $id
  575. * @return boolean
  576. */
  577. public function is_attachment_id_not_lazy_loading( $id )
  578. {
  579. return in_array( $id, $this->no_lazy_loading_ids );
  580. }
  581.  
  582. /**
  583. * Return true, if option is selected
  584. *
  585. * @since 4.7.5.1
  586. * @return boolean
  587. */
  588. public function responsive_images_active()
  589. {
  590. $opt_active = 'responsive_images' == avia_get_option( 'responsive_images', '' );
  591.  
  592. // check if temporary disabled
  593. if( $opt_active )
  594. {
  595. $active = ! $this->temporary_disabled;
  596. }
  597. else
  598. {
  599. $active = false;
  600. }
  601.  
  602. /**
  603. * @since 4.8.2
  604. * @since 4.8.6.3 added $opt_active, $this->temporary_disabled
  605. * @param boolean $active
  606. * @param boolean $opt_active
  607. * @param boolean $this->temporary_disabled
  608. * @return boolean
  609. */
  610. return apply_filters( 'avf_responsive_images_active', $active, $opt_active, $this->temporary_disabled );
  611. }
  612.  
  613. /**
  614. * Return true, if option is selected
  615. *
  616. * @since 4.8.2
  617. * @return boolean
  618. */
  619. public function responsive_images_lightbox_active()
  620. {
  621. $opt_active = 'responsive_images_lightbox' == avia_get_option( 'responsive_images_lightbox', '' );
  622.  
  623. // check if temporary disabled
  624. if( $opt_active )
  625. {
  626. $active = ! $this->temporary_disabled;
  627. }
  628. else
  629. {
  630. $active = false;
  631. }
  632.  
  633. /**
  634. * @since 4.8.2
  635. * @since 4.8.6.3 added $opt_active, $this->temporary_disabled
  636. * @param boolean $active
  637. * @param boolean $opt_active
  638. * @param boolean $this->temporary_disabled
  639. * @return boolean
  640. */
  641. return apply_filters( 'avf_responsive_images_lightbox_active', $active, $opt_active, $this->temporary_disabled );
  642. }
  643.  
  644.  
  645. /**
  646. * Return info about theme options setting for Lazy Loading.
  647. * Currently only applies for images, but might be extended for iframe in future.
  648. *
  649. * @since 4.7.6.2
  650. * @param string $what 'image' | .....
  651. * @return string 'none' | 'all'
  652. */
  653. public function lazy_loading_active( $what = 'image' )
  654. {
  655. $option = avia_get_option( 'lazy_loading', '' );
  656.  
  657. switch( $option )
  658. {
  659. case 'no_lazy_loading_all':
  660. $return = 'none';
  661. break;
  662. case '':
  663. default:
  664. $return = 'all';
  665. }
  666.  
  667. return $return;
  668. }
  669.  
  670. /**
  671. * Adds the class "wp-image-{$attachment_ID}" to <img ...> tag.
  672. * Needed by WP to add scrset and sizes attributes.
  673. *
  674. * Adds class "avia-lazy-loading-{$attachment_ID}" or "avia-lazy-loading-not-{$attachment_ID}"
  675. * to identify image as not allowed for lazy loading
  676. * Attachment ID is saved for not lazy loading.
  677. *
  678. * Classes will be added to all img tags in content !!!
  679. *
  680. * @since 4.7.6.2
  681. * @param string $html
  682. * @param int $attachment_id
  683. * @param string $lazy_loading '' | 'enabled' | 'disabled'
  684. * @return string
  685. */
  686. public function prepare_single_image( $html, $attachment_id, $lazy_loading = 'disabled' )
  687. {
  688. $lazy_loading = $this->validate_lazy_loading_alb_option( $lazy_loading );
  689.  
  690. if( ! $this->responsive_images_active() && ( 'none' == $this->lazy_loading_active() ) )
  691. {
  692. return $html;
  693. }
  694.  
  695. $matches = array();
  696. if ( ! preg_match_all( '/<img [^>]+>/', $html, $matches ) )
  697. {
  698. return $html;
  699. }
  700.  
  701. foreach ( $matches[0] as $image )
  702. {
  703. $new_img = $this->add_lazy_loading_to_img( $image, $attachment_id, $lazy_loading );
  704.  
  705. if( is_numeric( $attachment_id ) || 0 != $attachment_id )
  706. {
  707. $new_img = $this->add_attachment_id_to_img( $new_img, $attachment_id );
  708. }
  709.  
  710. $pos = strpos( $html, $image );
  711. if( false !== $pos )
  712. {
  713. $html = substr_replace( $html, $new_img, $pos, strlen( $image ) );
  714. }
  715. }
  716.  
  717. return $html;
  718. }
  719.  
  720. /**
  721. * Returns a "valid" string.
  722. *
  723. * @since 4.7.6.2
  724. * @param string $lazy_loading
  725. * @return string 'enabled' | 'disabled'
  726. */
  727. public function validate_lazy_loading_alb_option( $lazy_loading = '' )
  728. {
  729. return in_array( $lazy_loading, array( 'enabled', 'disabled' ) ) ? $lazy_loading : 'disabled';
  730. }
  731.  
  732.  
  733. /**
  734. * Gets the attachment id from image tag
  735. *
  736. * @since 4.7.5.1
  737. * @param string $image_tag
  738. * @return int|false
  739. */
  740. public function get_attachment_id( $image_tag )
  741. {
  742. $attachment_id = false;
  743. $match = array();
  744. if ( preg_match( '/wp-image-([0-9]+)/i', $image_tag, $match ) )
  745. {
  746. $attachment_id = absint( $match[1] );
  747. }
  748.  
  749. return $attachment_id;
  750. }
  751.  
  752.  
  753. /**
  754. * Gets final image HTML with scrset and sizes added if necessary
  755. * and attribute loading. Is prepared to handle multiple img tags, but
  756. * it only makes sense to call with a single img as we add the same $attachment_id
  757. * to all img
  758. *
  759. * @since 4.7.5.1
  760. * @param string $html
  761. * @param int $attachment_id
  762. * @param string $lazy_loading '' | 'enabled' | 'disabled'
  763. * @return string
  764. */
  765. public function make_image_responsive( $html, $attachment_id, $lazy_loading = '' )
  766. {
  767. $img = $this->prepare_single_image( $html, $attachment_id, $lazy_loading );
  768. return $this->make_content_images_responsive( $img );
  769. }
  770.  
  771. /**
  772. * Wrapper function to prepare HTML attributes that standard WP function
  773. * wp_make_content_images_responsive resp. wp_filter_content_tags can handle
  774. * images properly to add scrset, sizes, and loading attributes
  775. * (callback handler_wp_lazy_loading_enabled and handler_wp_img_tag_add_loading_attr)
  776. *
  777. * Returns final HTML for output
  778. *
  779. * @since 4.7.5.1
  780. * @param string $content
  781. * @return string
  782. */
  783. public function make_content_images_responsive( $content )
  784. {
  785. global $wp_version;
  786.  
  787. if( ! $this->responsive_images_active() )
  788. {
  789. return $content;
  790. }
  791.  
  792. // Stay backwards comp.
  793. if( version_compare( $wp_version, '5.4.99999', '<' ) && ! function_exists( 'wp_make_content_images_responsive' ) )
  794. {
  795. return $content;
  796. }
  797.  
  798. $matches = array();
  799. if ( ! preg_match_all( '/<img [^>]+>/', $content, $matches ) )
  800. {
  801. return $content;
  802. }
  803.  
  804. foreach ( $matches[0] as $image )
  805. {
  806. $new_image = $this->ensure_attr_enclosure( $image );
  807. if( $new_image != $image )
  808. {
  809. $pos = strpos( $content, $image );
  810.  
  811. if( false !== $pos )
  812. {
  813. $content = substr_replace( $content, $new_image, $pos, strlen( $image ) );
  814. }
  815. }
  816. }
  817.  
  818. if( version_compare( $wp_version, '5.4.99999', '<' ) )
  819. {
  820. $return = wp_make_content_images_responsive( $content );
  821. }
  822. else
  823. {
  824. $return = wp_filter_content_tags( $content );
  825. }
  826.  
  827. return $return;
  828. }
  829.  
  830. /**
  831. * Remove the loading attribute from image tags in content.
  832. * This function is a fallback to scan content and remove all loading='lazy' attributes.
  833. *
  834. * @since 4.7.6.2
  835. * @param string $content
  836. * @return string
  837. */
  838. public function remove_loading_lazy_attributes( $content )
  839. {
  840. $matches = array();
  841. if ( ! preg_match_all( '/<img [^>]+>/', $content, $matches ) )
  842. {
  843. return $content;
  844. }
  845.  
  846. foreach ( $matches[0] as $image )
  847. {
  848. $attachment_id = $this->get_attachment_id( $image );
  849. if( false !== $attachment_id )
  850. {
  851. $this->add_attachment_id_to_not_lazy_loading( $attachment_id );
  852. }
  853.  
  854. $count = 0;
  855. $new_image = str_ireplace( array( 'loading="lazy"', "loading='lazy'" ), '', $image, $count );
  856. if( $count != 0 )
  857. {
  858. $pos = strpos( $content, $image );
  859.  
  860. if( false !== $pos )
  861. {
  862. $content = substr_replace( $content, $new_image, $pos, strlen( $image ) );
  863. }
  864. }
  865. }
  866.  
  867. return $content;
  868. }
  869.  
  870. /**
  871. * Wrapper for WP attachment_url_to_postid().
  872. * Tries to convert an URL to an attachment id. If not exist, returns URL
  873. * Uses DB option to store already converted URL's. Option deleted when theme options change.
  874. *
  875. * @since 4.8.4
  876. * @param string|int $attachment
  877. * @return string|int
  878. */
  879. public function attachment_url_to_postid( $attachment )
  880. {
  881. if( is_numeric( $attachment ) )
  882. {
  883. return $attachment;
  884. }
  885.  
  886. $cache = (array) get_option( $this->opt_key_attachment_urls, array() );
  887.  
  888. if( isset( $cache[ $attachment ] ) && is_numeric( $cache[ $attachment ] ) && $cache[ $attachment ] > 0 )
  889. {
  890. return $cache[ $attachment ];
  891. }
  892.  
  893. $id = attachment_url_to_postid ($attachment );
  894.  
  895. if( $id <= 0 )
  896. {
  897. return $attachment;
  898. }
  899.  
  900. $cache[ $attachment ] = $id;
  901.  
  902. update_option( $this->opt_key_attachment_urls, $cache );
  903.  
  904. return $id;
  905. }
  906.  
  907. /**
  908. * Returns wp_get_attachment_image_src() extended by scrset and sizes
  909. *
  910. * @since 4.8.2
  911. * @param int $attachment_id
  912. * @param string $image_size
  913. * @return array|false
  914. */
  915. public function responsive_image_src( $attachment_id, $image_size = 'large' )
  916. {
  917. $img_src = wp_get_attachment_image_src( $attachment_id, $image_size );
  918.  
  919. if( false === $img_src )
  920. {
  921. return $img_src;
  922. }
  923.  
  924. $img_src['srcset'] = '';
  925. $img_src['sizes'] = '';
  926.  
  927. if( ! $this->responsive_images_active() )
  928. {
  929. return $img_src;
  930. }
  931.  
  932. $img = "<img src='{$img_src[0]}' width='{$img_src[1]}' height='{$img_src[2]}' />";
  933.  
  934. $img = $this->make_image_responsive( $img, $attachment_id );
  935.  
  936. $attrs = array( 'srcset', 'sizes' );
  937.  
  938. foreach( $attrs as $attr )
  939. {
  940. $match = array();
  941. if( preg_match( "/{$attr}='([^']+)'/", $img, $match ) )
  942. {
  943. $img_src[ $attr ] = $match[1];
  944. continue;
  945. }
  946. if( preg_match( "/{$attr}=\"([^\"]+)\"/", $img, $match ) )
  947. {
  948. $img_src[ $attr ] = $match[1];
  949. }
  950. }
  951.  
  952. return $img_src;
  953. }
  954.  
  955. /**
  956. * Returns the attribute string created from extended array of wp_get_attachment_image_src and scrset and sizes
  957. * or from AviaHelper::get_url()
  958. *
  959. * Replaces the attribute keys according to <img> or <a> tag
  960. *
  961. * @since 4.8.2
  962. * @param array|string $image_src
  963. * @param boolean $image_link
  964. * @return string
  965. */
  966. public function html_attr_image_src( $image_src, $image_link = true )
  967. {
  968. if( ! is_array( $image_src ) )
  969. {
  970. return $image_link ? 'src="' . esc_attr( $image_src ) . '"' : 'href="' . esc_attr( $image_src ) . '"';
  971. }
  972.  
  973. $atts = array();
  974.  
  975. foreach( $image_src as $key => $value )
  976. {
  977. // PHP fix < 8.0: case does not compare string and int -> force to string and compare strings !!!
  978. $key .= '';
  979.  
  980. switch( $key )
  981. {
  982. case '0': // = url
  983. $a = $image_link ? 'src' : 'href';
  984. break;
  985. case 'srcset':
  986. case 'sizes':
  987. $a = $image_link ? $key : 'data-' . $key;
  988. break;
  989. default:
  990. $a = null;
  991. break;
  992. }
  993.  
  994. if( is_null( $a ) || empty( $value ) )
  995. {
  996. continue;
  997. }
  998.  
  999. $atts[] = $a . '="' . esc_attr( $value ) . '"';
  1000. }
  1001.  
  1002. return implode( ' ', $atts );
  1003. }
  1004.  
  1005. /**
  1006. * @since 4.7.6.2
  1007. * @param string $image
  1008. * @param int $attachment_id
  1009. * @param string $lazy_loading 'enabled' | 'disabled'
  1010. * @return string
  1011. */
  1012. protected function add_lazy_loading_to_img( $image, $attachment_id, $lazy_loading )
  1013. {
  1014. if( 'disabled' == $lazy_loading )
  1015. {
  1016. $this->add_attachment_id_to_not_lazy_loading( $attachment_id );
  1017. }
  1018.  
  1019. $prefix = 'avia-img-lazy-loading-';
  1020.  
  1021. if( false !== strpos( $image, $prefix ) )
  1022. {
  1023. return $image;
  1024. }
  1025.  
  1026. if( 'none' == $this->lazy_loading_active() )
  1027. {
  1028. $lazy_loading = 'disabled';
  1029. }
  1030.  
  1031. $class = ( 'disabled' == $lazy_loading ) ? $prefix . 'not-' : $prefix;
  1032. $class .= $attachment_id;
  1033.  
  1034. $image = $this->add_class_to_img_tag( $image, $class );
  1035. $image = $this->add_lazy_loading_attr_to_img_tag( $image, $lazy_loading );
  1036.  
  1037. return $image;
  1038. }
  1039.  
  1040. /**
  1041. * Adds the WP class "wp-image-{$attachment_ID}" to <img> tag
  1042. *
  1043. * @since 4.7.5.1
  1044. * @param string $image
  1045. * @param int $attachment_id
  1046. * @return string
  1047. */
  1048. protected function add_attachment_id_to_img( $image, $attachment_id )
  1049. {
  1050. $prefix = 'wp-image-';
  1051.  
  1052. if( false !== strpos( $image, $prefix ) )
  1053. {
  1054. return $image;
  1055. }
  1056.  
  1057. $class = $prefix . $attachment_id;
  1058.  
  1059. $image = $this->add_class_to_img_tag( $image, $class );
  1060.  
  1061. return $image;
  1062. }
  1063.  
  1064. /**
  1065. * WP changed logic to handle attribute loading with 6.3.0
  1066. * Use filter 'wp_omit_loading_attr_threshold' (defauts to 3 by WP) to change number of first images skipped from lazy load
  1067. *
  1068. * @since 5.6.7
  1069. * @param string $image
  1070. * @param string $lazy_loading 'enabled' | 'disabled'
  1071. * @return string
  1072. */
  1073. protected function add_lazy_loading_attr_to_img_tag( $image, $lazy_loading )
  1074. {
  1075. global $wp_version;
  1076.  
  1077. /**
  1078. * @see ..\wp-includes\media.php wp_img_tag_add_loading_optimization_attrs()
  1079. * WP checks for loading attribute and there is no way to add it with a filter.
  1080. * Therefore we add the attribute directly into HTML
  1081. *
  1082. * @since 5.6.7
  1083. */
  1084. if( version_compare( $wp_version, '6.3.0', '<' ) )
  1085. {
  1086. return $image;
  1087. }
  1088.  
  1089. $match_loading = [];
  1090. $match_fetchpriority = [];
  1091.  
  1092. $loading_val = preg_match( '/ loading=["\']([A-Za-z]+)["\']/', $image, $match_loading );
  1093. $fetchpriority_val = preg_match( '/ fetchpriority=["\']([A-Za-z]+)["\']/', $image, $match_fetchpriority );
  1094.  
  1095. /**
  1096. * We follow WP way either loading or fetchpriority can be used
  1097. */
  1098. if( 'enabled' == $lazy_loading )
  1099. {
  1100. if( empty( $loading_val ) )
  1101. {
  1102. $image = str_replace( '<img ', '<img loading="lazy" ', $image );
  1103. }
  1104.  
  1105. if( ! empty( $fetchpriority_val ) )
  1106. {
  1107. $image = str_replace( $match_fetchpriority[0], '', $image );
  1108. }
  1109. }
  1110. else
  1111. {
  1112. if( ! empty( $loading_val ) )
  1113. {
  1114. $image = str_replace( $match_loading[0], '', $image );
  1115. }
  1116.  
  1117. if( empty( $fetchpriority_val ) )
  1118. {
  1119. $image = str_replace( '<img ', '<img fetchpriority="high" ', $image );
  1120. }
  1121. }
  1122.  
  1123. return $image;
  1124. }
  1125.  
  1126. /**
  1127. * @since 4.7.6.2
  1128. * @param string $image
  1129. * @param string $class
  1130. * @return string
  1131. */
  1132. protected function add_class_to_img_tag( $image, $class )
  1133. {
  1134. if( false === strpos( $image, 'class=' ) )
  1135. {
  1136. $image = str_replace( '<img', '<img class="' . $class . '" ', $image );
  1137. }
  1138. else if( false !== strpos( $image, 'class="' ) )
  1139. {
  1140. $image = str_replace( 'class="', 'class="' . $class . ' ', $image );
  1141. }
  1142. else if( false !== strpos( $image, "class='" ) )
  1143. {
  1144. $image = str_replace( "class='", "class='" . $class . ' ', $image );
  1145. }
  1146.  
  1147. return $image;
  1148. }
  1149.  
  1150. /**
  1151. * Make sure that HTML is xxx="...." and mot xxx='.....' which is not recognized by WP:
  1152. * - src
  1153. * - height
  1154. * - width
  1155. *
  1156. * @since 4.7.5.1
  1157. * @param string $image
  1158. * @return string
  1159. */
  1160. protected function ensure_attr_enclosure( $image )
  1161. {
  1162. $attrs = array( 'src', 'height', 'width' );
  1163.  
  1164. foreach( $attrs as $attr )
  1165. {
  1166. $match_attr = array();
  1167.  
  1168. if( preg_match( "/{$attr}='([^']+)'/", $image, $match_attr ) )
  1169. {
  1170. $new_attr = $attr . '="' . $match_attr[1] . '"';
  1171.  
  1172. $pos = strpos( $image, $match_attr[0] );
  1173.  
  1174. if( false !== $pos )
  1175. {
  1176. $image = substr_replace( $image, $new_attr, $pos, strlen( $match_attr[0] ) );
  1177. }
  1178. }
  1179. }
  1180.  
  1181. return $image;
  1182. }
  1183.  
  1184. /**
  1185. * Build internal responsive groups
  1186. * Needed to display in backend only for user information
  1187. *
  1188. * @since 4.7.5.1
  1189. */
  1190. protected function group_image_sizes()
  1191. {
  1192. $widths = array();
  1193. $all = $this->base_images;
  1194.  
  1195. foreach( $all as $sizes )
  1196. {
  1197. if( ! in_array( $sizes['width'], $widths ) )
  1198. {
  1199. $widths[] = $sizes['width'];
  1200. }
  1201. }
  1202.  
  1203. sort( $widths );
  1204.  
  1205. $groups = array();
  1206.  
  1207. while( count( $widths ) > 0 )
  1208. {
  1209. $current_width = array_shift( $widths );
  1210.  
  1211. do
  1212. {
  1213. $found = false;
  1214. $height_key = '';
  1215.  
  1216. foreach( $all as $name => $sizes )
  1217. {
  1218. if( $sizes['width'] == $current_width )
  1219. {
  1220. $height_key = $sizes['height'];
  1221. $group_key = $current_width . '*' . $height_key;
  1222. $groups[ $group_key ][ $name ] = $sizes;
  1223.  
  1224. unset( $all[ $name ] );
  1225. $found = true;
  1226. break;
  1227. }
  1228. }
  1229.  
  1230. if( ! $found )
  1231. {
  1232. break;
  1233. }
  1234.  
  1235. /**
  1236. * Check remaining for same aspect ratio
  1237. */
  1238. foreach( $all as $name => $sizes )
  1239. {
  1240. if( wp_image_matches_ratio( $current_width, $height_key, $sizes['width'], $sizes['height'] ) )
  1241. {
  1242. $groups[ $group_key ][ $name ] = $sizes;
  1243. unset( $all[ $name ] );
  1244. }
  1245. }
  1246.  
  1247. } while ( $found );
  1248. }
  1249.  
  1250. $this->size_groups = array();
  1251.  
  1252. foreach( $groups as $group => $images )
  1253. {
  1254. $widths = array();
  1255.  
  1256. foreach( $images as $image )
  1257. {
  1258. if( ! in_array( $image['width'], $widths ) )
  1259. {
  1260. $widths[] = $image['width'];
  1261. }
  1262. }
  1263.  
  1264. sort( $widths );
  1265.  
  1266. while( count( $widths ) > 0 )
  1267. {
  1268. $current_width = array_shift( $widths );
  1269.  
  1270. foreach( $images as $name => $sizes )
  1271. {
  1272. if( $sizes['width'] == $current_width )
  1273. {
  1274. $this->size_groups[ $group ][ $name ] = $sizes;
  1275. unset( $images[ $name ] );
  1276. }
  1277. }
  1278. }
  1279.  
  1280. }
  1281.  
  1282. }
  1283.  
  1284. /**
  1285. * Prepare overview of used image sizes for theme options page
  1286. *
  1287. * @since 4.7.5.1
  1288. * @return string
  1289. */
  1290. public function options_page_overview()
  1291. {
  1292. $html = '';
  1293.  
  1294. foreach( $this->size_groups as $size_group => $sizes )
  1295. {
  1296. $html .= '<h3>' . $this->get_group_headline( $size_group ) . '</h3>';
  1297.  
  1298. $html .= '<ul>';
  1299.  
  1300. foreach( $sizes as $key => $image )
  1301. {
  1302. $info = $image['width'] . '*' . $image['height'];
  1303. if( isset( $image['crop'] ) && true === $image['crop'] )
  1304. {
  1305. $info .= ' ' . __( '(cropped)', 'avia_framework' );
  1306. }
  1307.  
  1308. $info .= ' - ' . $this->get_image_key_info( $key );
  1309.  
  1310. $html .= '<li>' . $info . '</li>';
  1311. }
  1312.  
  1313. $html .= '</ul>';
  1314. }
  1315.  
  1316. return $html;
  1317. }
  1318.  
  1319. /**
  1320. * Returns the string for the group headline.
  1321. * Calculates the aspect ratio or only width/height if one value is 0
  1322. *
  1323. * @since 4.7.5.1
  1324. * @param string $group
  1325. * @return string
  1326. */
  1327. protected function get_group_headline( $group )
  1328. {
  1329. $headline = '';
  1330.  
  1331. $sizes = explode( '*', $group );
  1332.  
  1333. $w = isset( $sizes[0] ) ? (int) $sizes[0] : 0;
  1334. $h = isset( $sizes[1] ) ? (int) $sizes[1] : 0;
  1335.  
  1336. if( 0 == $h )
  1337. {
  1338. $headline .= __( 'Images keeping original aspect ratio', 'avia_framework' );
  1339. }
  1340. else if ( 0 == $w )
  1341. {
  1342. $headline .= __( 'Images keeping original aspect ratio', 'avia_framework' );
  1343. }
  1344. else
  1345. {
  1346. $gcd = $this->greatest_common_divisor( $w, $h );
  1347. $w = (int) ( $w / $gcd );
  1348. $h = (int) ( $h / $gcd );
  1349.  
  1350. $headline .= sprintf( __( 'Images aspect ratio: %d : %d', 'avia_framework' ), $w, $h );
  1351. }
  1352.  
  1353. /**
  1354. *
  1355. * @since 4.7.5.1
  1356. * @param string $headline
  1357. * @param string $group
  1358. * @return string
  1359. */
  1360. return apply_filters( 'avf_admin_image_group_headline', $headline, $group );
  1361. }
  1362.  
  1363. /**
  1364. * Return readable info for an image size key for options page
  1365. *
  1366. * @since 4.7.5.1
  1367. * @param string $image_key
  1368. * @return string
  1369. */
  1370. protected function get_image_key_info( $image_key )
  1371. {
  1372. $info = '';
  1373.  
  1374. $info .= ( array_key_exists( $image_key, $this->readable_img_sizes ) ) ? $this->readable_img_sizes[ $image_key ] : $image_key;
  1375.  
  1376. $info .= ' (';
  1377.  
  1378. if( array_key_exists( $image_key, $this->theme_images ) )
  1379. {
  1380. $info .= __( 'added by theme', 'avia_framework' );
  1381. }
  1382. else if( array_key_exists( $image_key, $this->plugin_images ) )
  1383. {
  1384. $info .= __( 'added by a plugin', 'avia_framework' );
  1385. }
  1386. else if( array_key_exists( $image_key, $this->wp_default_images ) )
  1387. {
  1388. $info .= __( 'WP default size', 'avia_framework' );
  1389. }
  1390. else
  1391. {
  1392. $info .= __( 'unknown', 'avia_framework' );
  1393. }
  1394.  
  1395. $info .= ')';
  1396.  
  1397. /**
  1398. *
  1399. * @since 4.7.5.1
  1400. * @param string $info
  1401. * @param string $image_key
  1402. * @return string
  1403. */
  1404. return apply_filters( 'avf_admin_image_key_info', $info, $image_key );
  1405. }
  1406.  
  1407.  
  1408. /**
  1409. * Calculates the value based on https://en.wikipedia.org/wiki/Greatest_common_divisor - euclid's algorithm
  1410. *
  1411. * @since 4.7.5.1
  1412. * @param int $a
  1413. * @param int $b
  1414. * @return int
  1415. */
  1416. protected function greatest_common_divisor( $a, $b )
  1417. {
  1418. if( 0 == $a )
  1419. {
  1420. return abs( $b );
  1421. }
  1422.  
  1423. if( 0 == $b )
  1424. {
  1425. return abs( $a );
  1426. }
  1427.  
  1428. if( $a < $b )
  1429. {
  1430. $h = $a;
  1431. $a = $b;
  1432. $b = $h;
  1433. }
  1434.  
  1435. do
  1436. {
  1437. $h = $a % $b;
  1438. $a = $b;
  1439. $b = $h;
  1440. } while ( $b != 0 );
  1441.  
  1442. return abs( $a );
  1443. }
  1444. }
  1445.  
  1446. /**
  1447. * Returns the main instance of av_responsive_images to prevent the need to use globals
  1448. *
  1449. * @since 4.7.5.1
  1450. * @param array|null $config
  1451. * @return av_responsive_images
  1452. */
  1453. function Av_Responsive_Images( $config = array() )
  1454. {
  1455. return av_responsive_images::instance( $config );
  1456. }
  1457.  
  1458. }
  1459.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement