暫無描述

presentations.php 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493
  1. <?php //phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
  2. use Automattic\Jetpack\Assets;
  3. /**
  4. * Presentations
  5. * Presentations plugin based on the work done by <a href="http://darylkoop.com/">Daryl Koopersmith</a>. Powered by jmpress.js
  6. *
  7. * HOW TO: How the plugin settings are organized and which features are supported.
  8. *
  9. * The entire presentation should be wrapped with a [presentation] shortcode, and every
  10. * individual slide should be wrapped with a [slide] shortcode. Any settings supported
  11. * by [slide] can be set into [presentation], which will apply that setting for the entire
  12. * presentation unless overridden by individual slides.
  13. *
  14. * - [presentation] only settings:
  15. * - duration: transition durations, default is one second.
  16. * - height: content height, default is 400px
  17. * - width: content width, default is 550px
  18. * - autoplay: delay between transitions in seconds, default 3s
  19. * when set the presentation will automatically transition between slides
  20. * as long as the presentation remains in focus
  21. *
  22. * - [slide] settings:
  23. * - transition: specifies where the next slide will be placed relative
  24. * to the last one before it. Supported values are "up", "down"
  25. * "left", "right", or "none". Default value is "down".
  26. *
  27. * - scale: scales the content relative to other slides, default value is one
  28. *
  29. * - rotate: rotates the content by the specified degrees, default is zero
  30. *
  31. * - fade: slides will fade in and out during transition. Values of "on" or
  32. * "true" will enable fading, while values of "no" or "false" will
  33. * disable it. Default value is "on"
  34. *
  35. * - bgcolor: specifies a background color for the slides. Any CSS valid value
  36. * is permitted. Default color is transparent.
  37. *
  38. * - bgimg: specifies an image url which will fill the background. Image is
  39. * set to fill the background 100% width and height
  40. *
  41. * - fadebullets: any html <li> tags will start out with an opacity of 0 and any
  42. * subsequent slide transitions will show the bullets one by one
  43. *
  44. * Known issues:
  45. *
  46. * - IE 7/8 are not supported by jmpress and presentations will not work
  47. * - IE 9 will not animate transitions at all, though it's possible to at least
  48. * switch between slides.
  49. * - Infinite Scroll themes will not load presentations properly unless the post
  50. * happens to be on the first loaded page. The permalink page will function
  51. * properly, however.
  52. * - Exiting fullscreen mode will not properly reset the scroll locations in Safari
  53. *
  54. * @package automattic/jetpack
  55. */
  56. if ( ! class_exists( 'Presentations' ) ) :
  57. /**
  58. * Create a shortcode to display Presentations and slides.
  59. */
  60. class Presentations {
  61. /**
  62. * Presentation settings.
  63. *
  64. * @var array
  65. */
  66. private $presentation_settings;
  67. /**
  68. * Do we have a Presentation shortcode to be displayed.
  69. *
  70. * @var bool
  71. */
  72. private $presentation_initialized;
  73. /**
  74. * Were scripts and styles enqueued already.
  75. *
  76. * @var bool
  77. */
  78. private $scripts_and_style_included;
  79. /**
  80. * Constructor
  81. */
  82. public function __construct() {
  83. $this->presentation_initialized = false;
  84. $this->scripts_and_style_included = false;
  85. // Registers shortcodes.
  86. add_action( 'wp_head', array( $this, 'add_scripts' ), 1 );
  87. add_shortcode( 'presentation', array( $this, 'presentation_shortcode' ) );
  88. add_shortcode( 'slide', array( $this, 'slide_shortcode' ) );
  89. }
  90. /**
  91. * Enqueue all scripts and styles.
  92. */
  93. public function add_scripts() {
  94. $this->scripts_and_style_included = false;
  95. if ( empty( $GLOBALS['posts'] ) || ! is_array( $GLOBALS['posts'] ) ) {
  96. return;
  97. }
  98. foreach ( $GLOBALS['posts'] as $p ) {
  99. if ( has_shortcode( $p->post_content, 'presentation' ) ) {
  100. $this->scripts_and_style_included = true;
  101. break;
  102. }
  103. }
  104. if ( ! $this->scripts_and_style_included ) {
  105. return;
  106. }
  107. $plugin = plugin_dir_url( __FILE__ );
  108. // Add CSS.
  109. wp_enqueue_style( 'presentations', $plugin . 'css/style.css', array(), JETPACK__VERSION );
  110. // Add JavaScript.
  111. wp_enqueue_script( 'jquery' );
  112. wp_enqueue_script(
  113. 'jmpress',
  114. Assets::get_file_url_for_environment( '_inc/build/shortcodes/js/jmpress.min.js', 'modules/shortcodes/js/jmpress.js' ),
  115. array( 'jquery' ),
  116. JETPACK__VERSION,
  117. true
  118. );
  119. wp_enqueue_script(
  120. 'presentations',
  121. Assets::get_file_url_for_environment( '_inc/build/shortcodes/js/main.min.js', 'modules/shortcodes/js/main.js' ),
  122. array( 'jquery', 'jmpress' ),
  123. JETPACK__VERSION,
  124. true
  125. );
  126. }
  127. /**
  128. * Main Presentation shortcode.
  129. *
  130. * @param array $atts Shortcode attributes.
  131. * @param string $content Post content.
  132. */
  133. public function presentation_shortcode( $atts, $content = '' ) {
  134. // Mark that we've found a valid [presentation] shortcode.
  135. $this->presentation_initialized = true;
  136. $atts = shortcode_atts(
  137. array(
  138. 'duration' => '',
  139. 'height' => '',
  140. 'width' => '',
  141. 'bgcolor' => '',
  142. 'bgimg' => '',
  143. 'autoplay' => '',
  144. // Settings.
  145. 'transition' => '',
  146. 'scale' => '',
  147. 'rotate' => '',
  148. 'fade' => '',
  149. 'fadebullets' => '',
  150. ),
  151. $atts,
  152. 'presentation'
  153. );
  154. $this->presentation_settings = array(
  155. 'transition' => 'down',
  156. 'scale' => 1,
  157. 'rotate' => 0,
  158. 'fade' => 'on',
  159. 'fadebullets' => 0,
  160. 'last' => array(
  161. 'x' => 0,
  162. 'y' => 0,
  163. 'scale' => 1,
  164. 'rotate' => 0,
  165. ),
  166. );
  167. // Set the presentation-wide settings.
  168. if ( '' !== trim( $atts['transition'] ) ) {
  169. $this->presentation_settings['transition'] = $atts['transition'];
  170. }
  171. if ( '' !== trim( $atts['scale'] ) ) {
  172. $this->presentation_settings['scale'] = (float) $atts['scale'];
  173. }
  174. if ( '' !== trim( $atts['rotate'] ) ) {
  175. $this->presentation_settings['rotate'] = (float) $atts['rotate'];
  176. }
  177. if ( '' !== trim( $atts['fade'] ) ) {
  178. $this->presentation_settings['fade'] = $atts['fade'];
  179. }
  180. if ( '' !== trim( $atts['fadebullets'] ) ) {
  181. $this->presentation_settings['fadebullets'] = $atts['fadebullets'];
  182. }
  183. // Set any settings the slides don't care about.
  184. if ( '' !== trim( $atts['duration'] ) ) {
  185. $duration = (float) $atts['duration'] . 's';
  186. } else {
  187. $duration = '1s';
  188. }
  189. // Autoplay durations are set in milliseconds.
  190. if ( '' !== trim( $atts['autoplay'] ) ) {
  191. $autoplay = (float) $atts['autoplay'] * 1000;
  192. } else {
  193. $autoplay = 0;
  194. } // No autoplay
  195. // Set the presentation size as specified or with some nicely sized dimensions.
  196. if ( '' !== trim( $atts['width'] ) ) {
  197. $this->presentation_settings['width'] = (int) $atts['width'];
  198. } else {
  199. $this->presentation_settings['width'] = 480;
  200. }
  201. if ( '' !== trim( $atts['height'] ) ) {
  202. $this->presentation_settings['height'] = (int) $atts['height'];
  203. } else {
  204. $this->presentation_settings['height'] = 370;
  205. }
  206. // Hide the content by default in case the scripts fail.
  207. $style = 'display: none; width: ' . $this->presentation_settings['width'] . 'px; height: ' . $this->presentation_settings['height'] . 'px;';
  208. /*
  209. * Check for background color XOR background image
  210. * Use a white background if nothing specified
  211. */
  212. if ( preg_match( '/https?\:\/\/[^\'"\s]*/', $atts['bgimg'], $matches ) ) {
  213. $style .= ' background-image: url("' . esc_url( $matches[0] ) . '");';
  214. } elseif ( '' !== trim( $atts['bgcolor'] ) ) {
  215. $style .= ' background-color: ' . esc_attr( $atts['bgcolor'] ) . ';';
  216. } else {
  217. $style .= ' background-color: #fff;';
  218. }
  219. // Not supported message style is inlined incase the style sheet doesn't get included.
  220. $out = "<section class='presentation-wrapper'>";
  221. $out .= "<p class='not-supported-msg' style='display: inherit; padding: 25%; text-align: center;'>";
  222. $out .= __( 'This slideshow could not be started. Try refreshing the page or viewing it in another browser.', 'jetpack' ) . '</p>';
  223. // Bail out unless the scripts were added.
  224. if ( $this->scripts_and_style_included ) {
  225. $out .= sprintf(
  226. '<div class="presentation" duration="%s" data-autoplay="%s" style="%s">',
  227. esc_attr( $duration ),
  228. esc_attr( $autoplay ),
  229. esc_attr( $style )
  230. );
  231. $out .= "<div class='nav-arrow-left'></div>";
  232. $out .= "<div class='nav-arrow-right'></div>";
  233. $out .= "<div class='nav-fullscreen-button'></div>";
  234. if ( $autoplay ) {
  235. $out .= '<div class="autoplay-overlay" style="display: none;"><p class="overlay-msg">';
  236. $out .= __( 'Click to autoplay the presentation!', 'jetpack' );
  237. $out .= '</p></div>';
  238. }
  239. $out .= do_shortcode( $content );
  240. }
  241. $out .= '</section>';
  242. $this->presentation_initialized = false;
  243. return $out;
  244. }
  245. /**
  246. * Slide shortcode.
  247. *
  248. * @param array $atts Shortcode attributes.
  249. * @param string $content Post content.
  250. */
  251. public function slide_shortcode( $atts, $content = '' ) {
  252. // Bail out unless wrapped by a [presentation] shortcode.
  253. if ( ! $this->presentation_initialized ) {
  254. return $content;
  255. }
  256. $atts = shortcode_atts(
  257. array(
  258. 'transition' => '',
  259. 'scale' => '',
  260. 'rotate' => '',
  261. 'fade' => '',
  262. 'fadebullets' => '',
  263. 'bgcolor' => '',
  264. 'bgimg' => '',
  265. ),
  266. $atts,
  267. 'slide'
  268. );
  269. // Determine positioning based on transition.
  270. if ( '' === trim( $atts['transition'] ) ) {
  271. $atts['transition'] = $this->presentation_settings['transition'];
  272. }
  273. // Setting the content scale.
  274. if ( '' === trim( $atts['scale'] ) ) {
  275. $atts['scale'] = $this->presentation_settings['scale'];
  276. }
  277. if ( '' === trim( $atts['scale'] ) ) {
  278. $scale = 1;
  279. } else {
  280. $scale = (float) $atts['scale'];
  281. }
  282. if ( $scale < 0 ) {
  283. $scale *= -1;
  284. }
  285. // Setting the content rotation.
  286. if ( '' === trim( $atts['rotate'] ) ) {
  287. $atts['rotate'] = $this->presentation_settings['rotate'];
  288. }
  289. if ( '' === trim( $atts['rotate'] ) ) {
  290. $rotate = 0;
  291. } else {
  292. $rotate = (float) $atts['rotate'];
  293. }
  294. // Setting if the content should fade.
  295. if ( '' === trim( $atts['fade'] ) ) {
  296. $atts['fade'] = $this->presentation_settings['fade'];
  297. }
  298. if ( 'on' === $atts['fade'] || 'true' === $atts['fade'] ) {
  299. $fade = 'fade';
  300. } else {
  301. $fade = '';
  302. }
  303. // Setting if bullets should fade on step changes.
  304. if ( '' === trim( $atts['fadebullets'] ) ) {
  305. $atts['fadebullets'] = $this->presentation_settings['fadebullets'];
  306. }
  307. if ( 'on' === $atts['fadebullets'] || 'true' === $atts['fadebullets'] ) {
  308. $fadebullets = 'fadebullets';
  309. } else {
  310. $fadebullets = '';
  311. }
  312. $coords = $this->get_coords(
  313. array(
  314. 'transition' => $atts['transition'],
  315. 'scale' => $scale,
  316. 'rotate' => $rotate,
  317. )
  318. );
  319. $x = $coords['x'];
  320. $y = $coords['y'];
  321. /*
  322. * Check for background color XOR background image
  323. * Use a white background if nothing specified
  324. */
  325. if ( preg_match( '/https?\:\/\/[^\'"\s]*/', $atts['bgimg'], $matches ) ) {
  326. $style = 'background-image: url("' . esc_url( $matches[0] ) . '");';
  327. } elseif ( '' !== trim( $atts['bgcolor'] ) ) {
  328. $style = 'background-color: ' . esc_attr( $atts['bgcolor'] ) . ';';
  329. } else {
  330. $style = '';
  331. }
  332. // Put everything together and let jmpress do the magic!
  333. $out = sprintf(
  334. '<div class="step %s %s" data-x="%s" data-y="%s" data-scale="%s" data-rotate="%s" style="%s">',
  335. esc_attr( $fade ),
  336. esc_attr( $fadebullets ),
  337. esc_attr( $x ),
  338. esc_attr( $y ),
  339. esc_attr( $scale ),
  340. esc_attr( $rotate ),
  341. esc_attr( $style )
  342. );
  343. $out .= '<div class="slide-content">';
  344. $out .= do_shortcode( $content );
  345. $out .= '</div></div>';
  346. return $out;
  347. }
  348. /**
  349. * Determines the position of the next slide based on the position and scaling of the previous slide.
  350. *
  351. * @param array $args {
  352. * Array of key-value pairs.
  353. *
  354. * @type string $transition: the transition name, "up", "down", "left", or "right".
  355. * @type float $scale: the scale of the next slide (used to determine the position of the slide after that).
  356. * }
  357. *
  358. * @return array with the 'x' and 'y' coordinates of the slide.
  359. */
  360. private function get_coords( $args ) {
  361. if ( 0 === $args['scale'] ) {
  362. $args['scale'] = 1;
  363. }
  364. $width = $this->presentation_settings['width'];
  365. $height = $this->presentation_settings['height'];
  366. $last = $this->presentation_settings['last'];
  367. $scale = $last['scale'];
  368. $next = array(
  369. 'x' => $last['x'],
  370. 'y' => $last['y'],
  371. 'scale' => $args['scale'],
  372. 'rotate' => $args['rotate'],
  373. );
  374. // All angles are measured from the vertical axis, so everything is backwards!
  375. $diag_angle = atan2( $width, $height );
  376. $diagonal = sqrt( pow( $width, 2 ) + pow( $height, 2 ) );
  377. /*
  378. * We offset the angles by the angle formed by the diagonal so that
  379. * we can multiply the sines directly against the diagonal length
  380. */
  381. $theta = deg2rad( $last['rotate'] ) - $diag_angle;
  382. $phi = deg2rad( $next['rotate'] ) - $diag_angle;
  383. // We start by displacing by the slide dimensions.
  384. $total_horiz_disp = $width * $scale;
  385. $total_vert_disp = $height * $scale;
  386. /*
  387. * If the previous slide was rotated, we add the incremental offset from the rotation
  388. * Namely the difference between the regular dimension (no rotation) and the component
  389. * of the diagonal for that angle
  390. */
  391. $total_horiz_disp += ( ( ( abs( sin( $theta ) ) * $diagonal ) - $width ) / 2 ) * $scale;
  392. $total_vert_disp += ( ( ( abs( cos( $theta ) ) * $diagonal ) - $height ) / 2 ) * $scale;
  393. /*
  394. * Similarly, we check if the current slide has been rotated and add whatever additional
  395. * offset has been added. This is so that two rotated corners don't clash with each other.
  396. * Note: we are checking the raw angle relative to the vertical axis, NOT the diagonal angle.
  397. */
  398. if ( 0 !== $next['rotate'] % 180 ) {
  399. $total_horiz_disp += ( abs( ( sin( $phi ) * $diagonal ) - $width ) / 2 ) * $next['scale'];
  400. $total_vert_disp += ( abs( ( cos( $phi ) * $diagonal ) - $height ) / 2 ) * $next['scale'];
  401. }
  402. switch ( trim( $args['transition'] ) ) {
  403. case 'none':
  404. break;
  405. case 'left':
  406. $next['x'] -= $total_horiz_disp;
  407. break;
  408. case 'right':
  409. $next['x'] += $total_horiz_disp;
  410. break;
  411. case 'up':
  412. $next['y'] -= $total_vert_disp;
  413. break;
  414. case 'down':
  415. default:
  416. $next['y'] += $total_vert_disp;
  417. break;
  418. }
  419. $this->presentation_settings['last'] = $next;
  420. return $next;
  421. }
  422. }
  423. $GLOBALS['presentations'] = new Presentations();
  424. endif;