Changeset 3709161
- Timestamp:
- 09/23/2026 11:12:53 AM (4 weeks ago)
- Location:
- aitoel-html-importer
- Files:
-
- 6 edited
- 1 copied
-
tags/1.3.48 (copied) (copied from aitoel-html-importer/trunk )
-
tags/1.3.48/aitoel-html-importer.php (modified) (2 diffs)
-
tags/1.3.48/includes/class-converter.php (modified) (8 diffs)
-
tags/1.3.48/readme.txt (modified) (3 diffs)
-
trunk/aitoel-html-importer.php (modified) (2 diffs)
-
trunk/includes/class-converter.php (modified) (8 diffs)
-
trunk/readme.txt (modified) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
-
aitoel-html-importer/tags/1.3.48/aitoel-html-importer.php
r3703741 r3709161 3 3 * Plugin Name: AI to Elementor — HTML Importer for Elementor 4 4 * Description: Convert any HTML page into a fully editable Elementor template with one click. Works with AI page generators (Lovable, v0, Stitch, Cursor, bolt.new) and hand-written HTML. 5 * Version: 1.3.4 75 * Version: 1.3.48 6 6 * Author: AI to Elementor 7 7 * Author URI: https://aitoelementor.com … … 24 24 defined('ABSPATH') || exit; 25 25 26 define('HTEL_VERSION', '1.3.4 7');26 define('HTEL_VERSION', '1.3.48'); 27 27 define('HTEL_PLUGIN_FILE', __FILE__); 28 28 define('HTEL_PLUGIN_DIR', plugin_dir_path(__FILE__)); -
aitoel-html-importer/tags/1.3.48/includes/class-converter.php
r3703741 r3709161 1649 1649 * @return array Content, rewritten where a download succeeded. 1650 1650 */ 1651 /** 1652 * The absolute URLs held in the image fields the engine writes: an Image widget's `image`, a 1653 * container's `background_image` and `background_overlay_image`, and a video's poster 1654 * `image_overlay`. Links and video URLs are not among them. 1655 */ 1656 private static function image_field_urls($elements) { 1657 $urls = array(); 1658 foreach ((array) $elements as $el) { 1659 foreach (array('image', 'background_image', 'background_overlay_image', 'image_overlay') as $key) { 1660 $url = isset($el['settings'][$key]['url']) ? $el['settings'][$key]['url'] : null; 1661 if (is_string($url) && preg_match('#^https?://#i', $url)) { 1662 $urls[] = $url; 1663 } 1664 } 1665 if (isset($el['elements'])) { 1666 $urls = array_merge($urls, self::image_field_urls($el['elements'])); 1667 } 1668 } 1669 return $urls; 1670 } 1671 1672 /** 1673 * Each URL replaced where it stands whole in the text. It ends where a URL ends in CSS or 1674 * markup — a quote, a bracket, a comma, white space, an escaped quote (`"`) — and not where 1675 * a URL goes on: a path, a query, an `&`. Nor is it replaced inside another URL. 1676 * 1677 * @param string $text 1678 * @param array $urls from => to 1679 * @return string 1680 */ 1681 private static function replace_whole_urls($text, $urls) { 1682 $url_char = 'A-Za-z0-9._\~:\/?#\[\]@!$*+=%\x80-\xFF-'; 1683 foreach ($urls as $from => $to) { 1684 if (strpos($text, $from) === false) { 1685 continue; 1686 } 1687 $text = preg_replace_callback( 1688 '~(?<![' . $url_char . '])' . preg_quote($from, '~') 1689 . '(?![' . $url_char . ']|&(?!quot;|apos;|#0*39;|#x0*27;|lt;|gt;))~', 1690 function () use ($to) { 1691 return $to; 1692 }, 1693 $text 1694 ); 1695 } 1696 return $text; 1697 } 1698 1651 1699 private static function import_remote_images($content, $post_id) { 1652 1700 if (!apply_filters('htel_import_remote_images', true, $post_id)) { … … 1672 1720 // the escaped form that is actually present. 1673 1721 $scan = str_replace('\/', '/', $json); 1674 if (!preg_match_all('#https?://[^"\s\x5C]+?\.(?:jpe?g|png|gif|webp|avif|svg)(?:\?[^"\s\x5C]*)?#i', $scan, $m)) { 1675 return $content; 1722 preg_match_all('#https?://[^"\s\x5C]+?\.(?:jpe?g|png|gif|webp|avif|svg)(?:\?[^"\s\x5C]*)?#i', $scan, $m); 1723 // An image field holds an image whatever its URL looks like (image CDNs such as Unsplash serve 1724 // without an extension), so its URL is fetched too; a file found only that way is named from its bytes. 1725 $by_extension = array(); 1726 foreach ($m[0] as $url) { 1727 $by_extension[html_entity_decode($url, ENT_QUOTES)] = true; 1676 1728 } 1677 1729 $home_host = wp_parse_url(home_url(), PHP_URL_HOST); 1678 1730 $urls = array(); 1679 foreach ( $m[0]as $url) {1731 foreach (array_merge($m[0], self::image_field_urls($content)) as $url) { 1680 1732 $url = html_entity_decode($url, ENT_QUOTES); 1681 1733 if (isset($urls[$url])) { … … 1699 1751 $started = microtime(true); 1700 1752 $map = array(); 1753 $whole = array(); 1701 1754 $failed = 0; 1702 1755 $count = 0; … … 1730 1783 } 1731 1784 $count++; 1785 $by_bytes = !isset($by_extension[$url]); 1732 1786 $tmp = download_url($url, $file_timeout); 1733 1787 if (is_wp_error($tmp)) { … … 1738 1792 if ($name === '' || strpos($name, '.') === false) { 1739 1793 $name = 'image-' . $count . '.jpg'; 1794 } 1795 if ($by_bytes) { 1796 // Refused here when the bytes are not an image, even for a user WordPress would let 1797 // upload a file of any type (unfiltered_upload). 1798 $ext = self::image_extension_from_signature((string) file_get_contents($tmp, false, null, 0, 64)); 1799 if ($ext === '') { 1800 wp_delete_file($tmp); 1801 $failed++; 1802 continue; 1803 } 1804 $base = wp_basename(wp_parse_url($url, PHP_URL_PATH)); 1805 $name = ($base !== '' ? sanitize_file_name($base) : 'image-' . $count) . '.' . $ext; 1740 1806 } 1741 1807 $file_array = array('name' => $name, 'tmp_name' => $tmp); … … 1755 1821 continue; 1756 1822 } 1823 if ($by_bytes) { 1824 // Repointed wherever it stands whole — the field, and the page's own CSS carried 1825 // beside it (`url(…)` in the styles block) — but never where it begins or sits 1826 // inside a longer URL: the same photo at another size, a gallery page. 1827 $whole[$url] = $wp_url; 1828 continue; 1829 } 1757 1830 // Rewrite the JSON-encoded form of the URL, which is what actually appears in 1758 1831 // $json — a raw string replace would miss any escaped solidus. 1759 $from = trim(wp_json_encode($url), '"'); 1760 $to = trim(wp_json_encode($wp_url), '"'); 1761 $map[$from] = $to; 1832 $map[trim(wp_json_encode($url), '"')] = trim(wp_json_encode($wp_url), '"'); 1762 1833 } 1763 1834 1764 1835 self::$last_remote_images = array( 1765 1836 'found' => count($urls), 1766 'imported' => count($map) ,1837 'imported' => count($map) + count($whole), 1767 1838 'failed' => $failed, 1768 1839 ); … … 1783 1854 self::report_drops(array('remotefail' => $failed, 'remotecap' => $capped)); 1784 1855 1785 if (empty($map) ) {1856 if (empty($map) && empty($whole)) { 1786 1857 return $content; 1787 1858 } … … 1804 1875 return $content; 1805 1876 } 1877 array_walk_recursive($decoded, function (&$value) use ($whole) { 1878 if (is_string($value)) { 1879 $value = self::replace_whole_urls($value, $whole); 1880 } 1881 }); 1806 1882 return $decoded; 1807 1883 } -
aitoel-html-importer/tags/1.3.48/readme.txt
r3703741 r3709161 6 6 Tested up to: 7.1 7 7 Requires PHP: 7.4 8 Stable tag: 1.3.4 78 Stable tag: 1.3.48 9 9 License: GPLv2 or later 10 10 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 202 202 203 203 == Changelog == 204 205 = 1.3.48 = 206 * Fixed: an image whose web address has no file extension (Unsplash's, for example) stayed linked 207 to the site it came from after import. When such an image arrives as an Image widget or a 208 background, it is now copied into your Media Library like any other image, and your page's own 209 styles are pointed at the copy too. A file that turns out not to be an image is left where it was. 210 * Applies to pages you convert from now on. Templates you have already imported are not changed. 204 211 205 212 = 1.3.47 = … … 452 459 == Upgrade Notice == 453 460 461 = 1.3.48 = 462 Images whose web address has no file extension, such as Unsplash's, are now copied into your Media Library when they arrive as Image widgets or backgrounds. 463 454 464 = 1.3.47 = 455 465 Less CSS in converted headings' Title fields: where the page looks exactly the same, their styling moves into the page's stylesheet. -
aitoel-html-importer/trunk/aitoel-html-importer.php
r3703741 r3709161 3 3 * Plugin Name: AI to Elementor — HTML Importer for Elementor 4 4 * Description: Convert any HTML page into a fully editable Elementor template with one click. Works with AI page generators (Lovable, v0, Stitch, Cursor, bolt.new) and hand-written HTML. 5 * Version: 1.3.4 75 * Version: 1.3.48 6 6 * Author: AI to Elementor 7 7 * Author URI: https://aitoelementor.com … … 24 24 defined('ABSPATH') || exit; 25 25 26 define('HTEL_VERSION', '1.3.4 7');26 define('HTEL_VERSION', '1.3.48'); 27 27 define('HTEL_PLUGIN_FILE', __FILE__); 28 28 define('HTEL_PLUGIN_DIR', plugin_dir_path(__FILE__)); -
aitoel-html-importer/trunk/includes/class-converter.php
r3703741 r3709161 1649 1649 * @return array Content, rewritten where a download succeeded. 1650 1650 */ 1651 /** 1652 * The absolute URLs held in the image fields the engine writes: an Image widget's `image`, a 1653 * container's `background_image` and `background_overlay_image`, and a video's poster 1654 * `image_overlay`. Links and video URLs are not among them. 1655 */ 1656 private static function image_field_urls($elements) { 1657 $urls = array(); 1658 foreach ((array) $elements as $el) { 1659 foreach (array('image', 'background_image', 'background_overlay_image', 'image_overlay') as $key) { 1660 $url = isset($el['settings'][$key]['url']) ? $el['settings'][$key]['url'] : null; 1661 if (is_string($url) && preg_match('#^https?://#i', $url)) { 1662 $urls[] = $url; 1663 } 1664 } 1665 if (isset($el['elements'])) { 1666 $urls = array_merge($urls, self::image_field_urls($el['elements'])); 1667 } 1668 } 1669 return $urls; 1670 } 1671 1672 /** 1673 * Each URL replaced where it stands whole in the text. It ends where a URL ends in CSS or 1674 * markup — a quote, a bracket, a comma, white space, an escaped quote (`"`) — and not where 1675 * a URL goes on: a path, a query, an `&`. Nor is it replaced inside another URL. 1676 * 1677 * @param string $text 1678 * @param array $urls from => to 1679 * @return string 1680 */ 1681 private static function replace_whole_urls($text, $urls) { 1682 $url_char = 'A-Za-z0-9._\~:\/?#\[\]@!$*+=%\x80-\xFF-'; 1683 foreach ($urls as $from => $to) { 1684 if (strpos($text, $from) === false) { 1685 continue; 1686 } 1687 $text = preg_replace_callback( 1688 '~(?<![' . $url_char . '])' . preg_quote($from, '~') 1689 . '(?![' . $url_char . ']|&(?!quot;|apos;|#0*39;|#x0*27;|lt;|gt;))~', 1690 function () use ($to) { 1691 return $to; 1692 }, 1693 $text 1694 ); 1695 } 1696 return $text; 1697 } 1698 1651 1699 private static function import_remote_images($content, $post_id) { 1652 1700 if (!apply_filters('htel_import_remote_images', true, $post_id)) { … … 1672 1720 // the escaped form that is actually present. 1673 1721 $scan = str_replace('\/', '/', $json); 1674 if (!preg_match_all('#https?://[^"\s\x5C]+?\.(?:jpe?g|png|gif|webp|avif|svg)(?:\?[^"\s\x5C]*)?#i', $scan, $m)) { 1675 return $content; 1722 preg_match_all('#https?://[^"\s\x5C]+?\.(?:jpe?g|png|gif|webp|avif|svg)(?:\?[^"\s\x5C]*)?#i', $scan, $m); 1723 // An image field holds an image whatever its URL looks like (image CDNs such as Unsplash serve 1724 // without an extension), so its URL is fetched too; a file found only that way is named from its bytes. 1725 $by_extension = array(); 1726 foreach ($m[0] as $url) { 1727 $by_extension[html_entity_decode($url, ENT_QUOTES)] = true; 1676 1728 } 1677 1729 $home_host = wp_parse_url(home_url(), PHP_URL_HOST); 1678 1730 $urls = array(); 1679 foreach ( $m[0]as $url) {1731 foreach (array_merge($m[0], self::image_field_urls($content)) as $url) { 1680 1732 $url = html_entity_decode($url, ENT_QUOTES); 1681 1733 if (isset($urls[$url])) { … … 1699 1751 $started = microtime(true); 1700 1752 $map = array(); 1753 $whole = array(); 1701 1754 $failed = 0; 1702 1755 $count = 0; … … 1730 1783 } 1731 1784 $count++; 1785 $by_bytes = !isset($by_extension[$url]); 1732 1786 $tmp = download_url($url, $file_timeout); 1733 1787 if (is_wp_error($tmp)) { … … 1738 1792 if ($name === '' || strpos($name, '.') === false) { 1739 1793 $name = 'image-' . $count . '.jpg'; 1794 } 1795 if ($by_bytes) { 1796 // Refused here when the bytes are not an image, even for a user WordPress would let 1797 // upload a file of any type (unfiltered_upload). 1798 $ext = self::image_extension_from_signature((string) file_get_contents($tmp, false, null, 0, 64)); 1799 if ($ext === '') { 1800 wp_delete_file($tmp); 1801 $failed++; 1802 continue; 1803 } 1804 $base = wp_basename(wp_parse_url($url, PHP_URL_PATH)); 1805 $name = ($base !== '' ? sanitize_file_name($base) : 'image-' . $count) . '.' . $ext; 1740 1806 } 1741 1807 $file_array = array('name' => $name, 'tmp_name' => $tmp); … … 1755 1821 continue; 1756 1822 } 1823 if ($by_bytes) { 1824 // Repointed wherever it stands whole — the field, and the page's own CSS carried 1825 // beside it (`url(…)` in the styles block) — but never where it begins or sits 1826 // inside a longer URL: the same photo at another size, a gallery page. 1827 $whole[$url] = $wp_url; 1828 continue; 1829 } 1757 1830 // Rewrite the JSON-encoded form of the URL, which is what actually appears in 1758 1831 // $json — a raw string replace would miss any escaped solidus. 1759 $from = trim(wp_json_encode($url), '"'); 1760 $to = trim(wp_json_encode($wp_url), '"'); 1761 $map[$from] = $to; 1832 $map[trim(wp_json_encode($url), '"')] = trim(wp_json_encode($wp_url), '"'); 1762 1833 } 1763 1834 1764 1835 self::$last_remote_images = array( 1765 1836 'found' => count($urls), 1766 'imported' => count($map) ,1837 'imported' => count($map) + count($whole), 1767 1838 'failed' => $failed, 1768 1839 ); … … 1783 1854 self::report_drops(array('remotefail' => $failed, 'remotecap' => $capped)); 1784 1855 1785 if (empty($map) ) {1856 if (empty($map) && empty($whole)) { 1786 1857 return $content; 1787 1858 } … … 1804 1875 return $content; 1805 1876 } 1877 array_walk_recursive($decoded, function (&$value) use ($whole) { 1878 if (is_string($value)) { 1879 $value = self::replace_whole_urls($value, $whole); 1880 } 1881 }); 1806 1882 return $decoded; 1807 1883 } -
aitoel-html-importer/trunk/readme.txt
r3703741 r3709161 6 6 Tested up to: 7.1 7 7 Requires PHP: 7.4 8 Stable tag: 1.3.4 78 Stable tag: 1.3.48 9 9 License: GPLv2 or later 10 10 License URI: https://www.gnu.org/licenses/gpl-2.0.html … … 202 202 203 203 == Changelog == 204 205 = 1.3.48 = 206 * Fixed: an image whose web address has no file extension (Unsplash's, for example) stayed linked 207 to the site it came from after import. When such an image arrives as an Image widget or a 208 background, it is now copied into your Media Library like any other image, and your page's own 209 styles are pointed at the copy too. A file that turns out not to be an image is left where it was. 210 * Applies to pages you convert from now on. Templates you have already imported are not changed. 204 211 205 212 = 1.3.47 = … … 452 459 == Upgrade Notice == 453 460 461 = 1.3.48 = 462 Images whose web address has no file extension, such as Unsplash's, are now copied into your Media Library when they arrive as Image widgets or backgrounds. 463 454 464 = 1.3.47 = 455 465 Less CSS in converted headings' Title fields: where the page looks exactly the same, their styling moves into the page's stylesheet.
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)