photo.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574
  1. <?php
  2. /**
  3. * @class FLPhotoModule
  4. */
  5. class FLPhotoModule extends FLBuilderModule {
  6. /**
  7. * @property $data
  8. */
  9. public $data = null;
  10. /**
  11. * @property $_editor
  12. * @protected
  13. */
  14. protected $_editor = null;
  15. /**
  16. * @method __construct
  17. */
  18. public function __construct() {
  19. parent::__construct(array(
  20. 'name' => __( 'Photo', 'fl-builder' ),
  21. 'description' => __( 'Upload a photo or display one from the media library.', 'fl-builder' ),
  22. 'category' => __( 'Basic', 'fl-builder' ),
  23. 'icon' => 'format-image.svg',
  24. 'partial_refresh' => true,
  25. ));
  26. }
  27. /**
  28. * @method enqueue_scripts
  29. */
  30. public function enqueue_scripts() {
  31. $override_lightbox = apply_filters( 'fl_builder_override_lightbox', false );
  32. if ( $this->settings && 'lightbox' == $this->settings->link_type ) {
  33. if ( ! $override_lightbox ) {
  34. $this->add_js( 'jquery-magnificpopup' );
  35. $this->add_css( 'font-awesome-5' );
  36. $this->add_css( 'jquery-magnificpopup' );
  37. } else {
  38. wp_dequeue_script( 'jquery-magnificpopup' );
  39. wp_dequeue_style( 'jquery-magnificpopup' );
  40. }
  41. }
  42. }
  43. /**
  44. * @method update
  45. * @param $settings {object}
  46. */
  47. public function update( $settings ) {
  48. // Make sure we have a photo_src property.
  49. if ( ! isset( $settings->photo_src ) ) {
  50. $settings->photo_src = '';
  51. }
  52. // Cache the attachment data.
  53. $settings->data = FLBuilderPhoto::get_attachment_data( $settings->photo );
  54. // Save a crop if necessary.
  55. $this->crop();
  56. return $settings;
  57. }
  58. /**
  59. * @method delete
  60. */
  61. public function delete() {
  62. $cropped_path = $this->_get_cropped_path();
  63. if ( fl_builder_filesystem()->file_exists( $cropped_path['path'] ) ) {
  64. fl_builder_filesystem()->unlink( $cropped_path['path'] );
  65. }
  66. }
  67. /**
  68. * @method crop
  69. */
  70. public function crop() {
  71. // Delete an existing crop if it exists.
  72. $this->delete();
  73. // Do a crop.
  74. if ( ! empty( $this->settings->crop ) ) {
  75. $editor = $this->_get_editor();
  76. if ( ! $editor || is_wp_error( $editor ) ) {
  77. return false;
  78. }
  79. $cropped_path = $this->_get_cropped_path();
  80. $size = $editor->get_size();
  81. $new_width = $size['width'];
  82. $new_height = $size['height'];
  83. // Get the crop ratios.
  84. if ( 'landscape' == $this->settings->crop ) {
  85. $ratio_1 = 1.43;
  86. $ratio_2 = .7;
  87. } elseif ( 'panorama' == $this->settings->crop ) {
  88. $ratio_1 = 2;
  89. $ratio_2 = .5;
  90. } elseif ( 'portrait' == $this->settings->crop ) {
  91. $ratio_1 = .7;
  92. $ratio_2 = 1.43;
  93. } elseif ( 'square' == $this->settings->crop ) {
  94. $ratio_1 = 1;
  95. $ratio_2 = 1;
  96. } elseif ( 'circle' == $this->settings->crop ) {
  97. $ratio_1 = 1;
  98. $ratio_2 = 1;
  99. }
  100. // Get the new width or height.
  101. if ( $size['width'] / $size['height'] < $ratio_1 ) {
  102. $new_height = $size['width'] * $ratio_2;
  103. } else {
  104. $new_width = $size['height'] * $ratio_1;
  105. }
  106. // Make sure we have enough memory to crop.
  107. try {
  108. ini_set( 'memory_limit', '300M' );
  109. } catch ( Exception $e ) {
  110. //
  111. }
  112. // Crop the photo.
  113. $editor->resize( $new_width, $new_height, true );
  114. // Save the photo.
  115. $editor->save( $cropped_path['path'] );
  116. /**
  117. * Let third party media plugins hook in.
  118. * @see fl_builder_photo_cropped
  119. */
  120. do_action( 'fl_builder_photo_cropped', $cropped_path, $editor );
  121. // Return the new url.
  122. return $cropped_path['url'];
  123. }
  124. return false;
  125. }
  126. /**
  127. * @method get_data
  128. */
  129. public function get_data() {
  130. if ( ! $this->data ) {
  131. // Photo source is set to "url".
  132. if ( 'url' == $this->settings->photo_source ) {
  133. $this->data = new stdClass();
  134. $this->data->alt = $this->settings->caption;
  135. $this->data->caption = $this->settings->caption;
  136. $this->data->link = $this->settings->photo_url;
  137. $this->data->url = $this->settings->photo_url;
  138. $this->settings->photo_src = $this->settings->photo_url;
  139. } elseif ( is_object( $this->settings->photo ) ) {
  140. $this->data = $this->settings->photo;
  141. } else {
  142. $this->data = FLBuilderPhoto::get_attachment_data( $this->settings->photo );
  143. }
  144. // Data object is empty, use the settings cache.
  145. if ( ! $this->data && isset( $this->settings->data ) ) {
  146. $this->data = $this->settings->data;
  147. }
  148. }
  149. return $this->data;
  150. }
  151. /**
  152. * @method get_classes
  153. */
  154. public function get_classes() {
  155. $classes = array( 'fl-photo-img' );
  156. if ( 'library' == $this->settings->photo_source && ! empty( $this->settings->photo ) ) {
  157. $data = self::get_data();
  158. if ( is_object( $data ) ) {
  159. if ( isset( $data->id ) ) {
  160. $classes[] = 'wp-image-' . $data->id;
  161. }
  162. if ( isset( $data->sizes ) ) {
  163. foreach ( $data->sizes as $key => $size ) {
  164. if ( $size->url == $this->settings->photo_src ) {
  165. $classes[] = 'size-' . $key;
  166. break;
  167. }
  168. }
  169. }
  170. }
  171. }
  172. return implode( ' ', $classes );
  173. }
  174. /**
  175. * @method get_src
  176. */
  177. public function get_src() {
  178. $src = $this->_get_uncropped_url();
  179. // Return a cropped photo.
  180. if ( $this->_has_source() && ! empty( $this->settings->crop ) ) {
  181. $cropped_path = $this->_get_cropped_path();
  182. // See if the cropped photo already exists.
  183. if ( fl_builder_filesystem()->file_exists( $cropped_path['path'] ) ) {
  184. $src = $cropped_path['url'];
  185. } elseif ( stristr( $src, FL_BUILDER_DEMO_URL ) && ! stristr( FL_BUILDER_DEMO_URL, $_SERVER['HTTP_HOST'] ) ) {
  186. $src = $this->_get_cropped_demo_url();
  187. } // It doesn't, check if this is a OLD demo image.
  188. elseif ( stristr( $src, FL_BUILDER_OLD_DEMO_URL ) ) {
  189. $src = $this->_get_cropped_demo_url();
  190. } // A cropped photo doesn't exist, try to create one.
  191. else {
  192. $url = $this->crop();
  193. if ( $url ) {
  194. $src = $url;
  195. }
  196. }
  197. }
  198. return $src;
  199. }
  200. /**
  201. * @method get_link
  202. */
  203. public function get_link() {
  204. $photo = $this->get_data();
  205. if ( 'url' == $this->settings->link_type ) {
  206. $link = $this->settings->link_url;
  207. } elseif ( 'lightbox' == $this->settings->link_type ) {
  208. $link = $photo->url;
  209. } elseif ( 'file' == $this->settings->link_type ) {
  210. $link = $photo->url;
  211. } elseif ( 'page' == $this->settings->link_type ) {
  212. $link = $photo->link;
  213. } else {
  214. $link = '';
  215. }
  216. return $link;
  217. }
  218. /**
  219. * @method get_alt
  220. */
  221. public function get_alt() {
  222. $photo = $this->get_data();
  223. if ( ! empty( $photo->alt ) ) {
  224. return htmlspecialchars( $photo->alt );
  225. } elseif ( ! empty( $photo->description ) ) {
  226. return htmlspecialchars( $photo->description );
  227. } elseif ( ! empty( $photo->caption ) ) {
  228. return htmlspecialchars( $photo->caption );
  229. } elseif ( ! empty( $photo->title ) ) {
  230. return htmlspecialchars( $photo->title );
  231. }
  232. }
  233. /**
  234. * @method get_attributes
  235. */
  236. public function get_attributes() {
  237. $photo = $this->get_data();
  238. $attrs = '';
  239. if ( isset( $this->settings->attributes ) ) {
  240. foreach ( $this->settings->attributes as $key => $val ) {
  241. $attrs .= $key . '="' . $val . '" ';
  242. }
  243. }
  244. if ( is_object( $photo ) && isset( $photo->sizes ) ) {
  245. foreach ( $photo->sizes as $size ) {
  246. if ( $size->url == $this->settings->photo_src ) {
  247. $attrs .= 'height="' . $size->height . '" width="' . $size->width . '" ';
  248. }
  249. }
  250. }
  251. if ( ! empty( $photo->title ) ) {
  252. $attrs .= 'title="' . htmlspecialchars( $photo->title ) . '" ';
  253. }
  254. return $attrs;
  255. }
  256. /**
  257. * @method _has_source
  258. * @protected
  259. */
  260. protected function _has_source() {
  261. if ( 'url' == $this->settings->photo_source && ! empty( $this->settings->photo_url ) ) {
  262. return true;
  263. } elseif ( 'library' == $this->settings->photo_source && ! empty( $this->settings->photo_src ) ) {
  264. return true;
  265. }
  266. return false;
  267. }
  268. /**
  269. * @method _get_editor
  270. * @protected
  271. */
  272. protected function _get_editor() {
  273. if ( $this->_has_source() && null === $this->_editor ) {
  274. $url_path = $this->_get_uncropped_url();
  275. $file_path = str_ireplace( home_url(), ABSPATH, $url_path );
  276. if ( fl_builder_filesystem()->file_exists( $file_path ) ) {
  277. $this->_editor = wp_get_image_editor( $file_path );
  278. } else {
  279. $this->_editor = wp_get_image_editor( $url_path );
  280. }
  281. }
  282. return $this->_editor;
  283. }
  284. /**
  285. * @method _get_cropped_path
  286. * @protected
  287. */
  288. protected function _get_cropped_path() {
  289. $crop = empty( $this->settings->crop ) ? 'none' : $this->settings->crop;
  290. $url = $this->_get_uncropped_url();
  291. $cache_dir = FLBuilderModel::get_cache_dir();
  292. if ( empty( $url ) ) {
  293. $filename = uniqid(); // Return a file that doesn't exist.
  294. } else {
  295. if ( stristr( $url, '?' ) ) {
  296. $parts = explode( '?', $url );
  297. $url = $parts[0];
  298. }
  299. $pathinfo = pathinfo( $url );
  300. if ( isset( $pathinfo['extension'] ) ) {
  301. $dir = $pathinfo['dirname'];
  302. $ext = $pathinfo['extension'];
  303. $name = wp_basename( $url, ".$ext" );
  304. $new_ext = strtolower( $ext );
  305. $filename = "{$name}-{$crop}.{$new_ext}";
  306. } else {
  307. $filename = $pathinfo['filename'] . "-{$crop}.png";
  308. }
  309. }
  310. return array(
  311. 'filename' => $filename,
  312. 'path' => $cache_dir['path'] . $filename,
  313. 'url' => $cache_dir['url'] . $filename,
  314. );
  315. }
  316. /**
  317. * @method _get_uncropped_url
  318. * @protected
  319. */
  320. protected function _get_uncropped_url() {
  321. if ( 'url' == $this->settings->photo_source ) {
  322. $url = $this->settings->photo_url;
  323. } elseif ( ! empty( $this->settings->photo_src ) ) {
  324. $url = $this->settings->photo_src;
  325. } else {
  326. $url = FL_BUILDER_URL . 'img/pixel.png';
  327. }
  328. return $url;
  329. }
  330. /**
  331. * @method _get_cropped_demo_url
  332. * @protected
  333. */
  334. protected function _get_cropped_demo_url() {
  335. $info = $this->_get_cropped_path();
  336. return FL_BUILDER_DEMO_CACHE_URL . $info['filename'];
  337. }
  338. /**
  339. * Returns link rel
  340. * @since 2.0.6
  341. */
  342. public function get_rel() {
  343. $rel = array();
  344. if ( '_blank' == $this->settings->link_target ) {
  345. $rel[] = 'noopener';
  346. }
  347. if ( isset( $this->settings->link_nofollow ) && 'yes' == $this->settings->link_nofollow ) {
  348. $rel[] = 'nofollow';
  349. }
  350. $rel = implode( ' ', $rel );
  351. if ( $rel ) {
  352. $rel = ' rel="' . $rel . '" ';
  353. }
  354. return $rel;
  355. }
  356. }
  357. /**
  358. * Register the module and its form settings.
  359. */
  360. FLBuilder::register_module('FLPhotoModule', array(
  361. 'general' => array( // Tab
  362. 'title' => __( 'General', 'fl-builder' ), // Tab title
  363. 'sections' => array( // Tab Sections
  364. 'general' => array( // Section
  365. 'title' => '', // Section Title
  366. 'fields' => array( // Section Fields
  367. 'photo_source' => array(
  368. 'type' => 'select',
  369. 'label' => __( 'Photo Source', 'fl-builder' ),
  370. 'default' => 'library',
  371. 'options' => array(
  372. 'library' => __( 'Media Library', 'fl-builder' ),
  373. 'url' => __( 'URL', 'fl-builder' ),
  374. ),
  375. 'toggle' => array(
  376. 'library' => array(
  377. 'fields' => array( 'photo' ),
  378. ),
  379. 'url' => array(
  380. 'fields' => array( 'photo_url', 'caption' ),
  381. ),
  382. ),
  383. ),
  384. 'photo' => array(
  385. 'type' => 'photo',
  386. 'label' => __( 'Photo', 'fl-builder' ),
  387. 'connections' => array( 'photo' ),
  388. 'show_remove' => true,
  389. ),
  390. 'photo_url' => array(
  391. 'type' => 'text',
  392. 'label' => __( 'Photo URL', 'fl-builder' ),
  393. 'placeholder' => __( 'http://www.example.com/my-photo.jpg', 'fl-builder' ),
  394. ),
  395. 'crop' => array(
  396. 'type' => 'select',
  397. 'label' => __( 'Crop', 'fl-builder' ),
  398. 'default' => '',
  399. 'options' => array(
  400. '' => _x( 'None', 'Photo Crop.', 'fl-builder' ),
  401. 'landscape' => __( 'Landscape', 'fl-builder' ),
  402. 'panorama' => __( 'Panorama', 'fl-builder' ),
  403. 'portrait' => __( 'Portrait', 'fl-builder' ),
  404. 'square' => __( 'Square', 'fl-builder' ),
  405. 'circle' => __( 'Circle', 'fl-builder' ),
  406. ),
  407. ),
  408. 'align' => array(
  409. 'type' => 'select',
  410. 'label' => __( 'Alignment', 'fl-builder' ),
  411. 'default' => 'center',
  412. 'options' => array(
  413. 'left' => __( 'Left', 'fl-builder' ),
  414. 'center' => __( 'Center', 'fl-builder' ),
  415. 'right' => __( 'Right', 'fl-builder' ),
  416. ),
  417. ),
  418. ),
  419. ),
  420. 'caption' => array(
  421. 'title' => __( 'Caption', 'fl-builder' ),
  422. 'fields' => array(
  423. 'show_caption' => array(
  424. 'type' => 'select',
  425. 'label' => __( 'Show Caption', 'fl-builder' ),
  426. 'default' => '0',
  427. 'options' => array(
  428. '0' => __( 'Never', 'fl-builder' ),
  429. 'hover' => __( 'On Hover', 'fl-builder' ),
  430. 'below' => __( 'Below Photo', 'fl-builder' ),
  431. ),
  432. ),
  433. 'caption' => array(
  434. 'type' => 'text',
  435. 'label' => __( 'Caption', 'fl-builder' ),
  436. ),
  437. ),
  438. ),
  439. 'link' => array(
  440. 'title' => __( 'Link', 'fl-builder' ),
  441. 'fields' => array(
  442. 'link_type' => array(
  443. 'type' => 'select',
  444. 'label' => __( 'Link Type', 'fl-builder' ),
  445. 'options' => array(
  446. '' => _x( 'None', 'Link type.', 'fl-builder' ),
  447. 'url' => __( 'URL', 'fl-builder' ),
  448. 'lightbox' => __( 'Lightbox', 'fl-builder' ),
  449. 'file' => __( 'Photo File', 'fl-builder' ),
  450. 'page' => __( 'Photo Page', 'fl-builder' ),
  451. ),
  452. 'toggle' => array(
  453. '' => array(),
  454. 'url' => array(
  455. 'fields' => array( 'link_url', 'link_target' ),
  456. ),
  457. 'file' => array(),
  458. 'page' => array(),
  459. ),
  460. 'help' => __( 'Link type applies to how the image should be linked on click. You can choose a specific URL, the individual photo or a separate page with the photo.', 'fl-builder' ),
  461. 'preview' => array(
  462. 'type' => 'none',
  463. ),
  464. ),
  465. 'link_url' => array(
  466. 'type' => 'link',
  467. 'label' => __( 'Link URL', 'fl-builder' ),
  468. 'preview' => array(
  469. 'type' => 'none',
  470. ),
  471. 'connections' => array( 'url' ),
  472. ),
  473. 'link_target' => array(
  474. 'type' => 'select',
  475. 'label' => __( 'Link Target', 'fl-builder' ),
  476. 'default' => '_self',
  477. 'options' => array(
  478. '_self' => __( 'Same Window', 'fl-builder' ),
  479. '_blank' => __( 'New Window', 'fl-builder' ),
  480. ),
  481. 'preview' => array(
  482. 'type' => 'none',
  483. ),
  484. ),
  485. 'link_nofollow' => array(
  486. 'type' => 'select',
  487. 'label' => __( 'Link No Follow', 'fl-builder' ),
  488. 'default' => 'no',
  489. 'options' => array(
  490. 'yes' => __( 'Yes', 'fl-builder' ),
  491. 'no' => __( 'No', 'fl-builder' ),
  492. ),
  493. 'preview' => array(
  494. 'type' => 'none',
  495. ),
  496. ),
  497. ),
  498. ),
  499. ),
  500. ),
  501. ));