├── .github └── pull_request_template.md ├── .gitignore ├── CONTRIBUTING.md ├── README.md ├── SUPPORT.md ├── css └── editor.css ├── docs ├── img │ ├── block-in-editor.png │ ├── block.png │ ├── pym-example-desktop.png │ ├── pym-example-phone.png │ ├── pym-shortcode-in-post.png │ └── responsive-iframe-npr.png ├── maintainer-notes.md ├── readme.md ├── release-checklist.md └── upgrade-testing.md ├── inc ├── amp.php ├── block.php ├── class-pymsrc-output.php ├── info-page.php ├── settings-page.php └── shortcode.php ├── js ├── block.js ├── pym.js └── pym.v1.min.js ├── license.txt ├── pym-shortcode.php ├── readme.txt └── release.sh /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Changes 2 | 3 | This pull request makes the following changes: 4 | 5 | - 6 | 7 | ## Why 8 | 9 | 10 | 11 | For # 12 | 13 | ## Testing/Questions 14 | 15 | Features that this PR affects: 16 | 17 | - 18 | 19 | 20 | Questions that need to be answered before merging: 21 | 22 | - [ ] Is this PR targeting the correct branch in this repository? 23 | - [ ] Does this work for the test cases provided in https://github.com/INN/pym-shortcode/blob/master/docs/maintainer-notes.md#testing-before-release ? 24 | - [ ] 25 | 26 | Steps to test this PR: 27 | 28 | 1. 29 | 30 | ## Additional information 31 | 32 | INN Member/Labs Client requesting: (if applicable) 33 | 34 | - [ ] Contributor has read INN's [GitHub code of conduct](https://github.com/INN/.github/blob/master/CODE_OF_CONDUCT.md) 35 | - [ ] Contributor would like to be mentioned in the release notes as: (fill in this blank) 36 | - [ ] Contributor agrees to the license terms of this repository. 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | release/ 2 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | We welcome contributions and suggestions to help us improve this project. Please start by [reading our contribution guidelines](https://github.com/INN/docs/blob/master/how-to-work-with-us/contributing.md). 4 | 5 | ### Workflow: 6 | 7 | 1. [Fork this repo](https://help.github.com/articles/fork-a-repo) 8 | 2. Create a branch (git checkout -b my-branch) 9 | 3. Stage and commit your changes (git commit -am 'description of my changes') 10 | 4. Push the changes to your fork (git push origin my-branch) 11 | 5. [Submit a pull request to the parent repo](https://help.github.com/articles/creating-a-pull-request). Please read our [guide to submitting pull requests](https://github.com/inn/docs/blob/master/how-to-work-with-us/pull-requests.md) to see what we expect in a good pull request message. 12 | 6. Pull request should be assigned to: 13 | - [@benlk](http://github.com/benlk) (primary) 14 | 15 | We have [a helpful how-to](https://github.com/INN/docs/blob/master/how-to-work-with-us/via-github.md) that walks through this process in more detail if you're new to using Git. 16 | 17 | Additionally, you can [create issues](https://github.com/INN/pym-shortcode/issues) on this repo to suggest changes or improvements. 18 | 19 | And of course you can always email us: [support@inn.org](mailto:support@inn.org). 20 | 21 | ### Standards 22 | 23 | - Follow all standards from the INN Labs [coding style guide](https://github.com/INN/docs/tree/master/style-guides/code). 24 | - Use [markdown syntax](http://daringfireball.net/projects/markdown/syntax) for all text documents. 25 | - Pull requests for new functionality should be accompanied by tests wherever possible. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Pym.js Embeds 2 | 3 | This plugin allows the use of NPR's [Pym.js](http://blog.apps.npr.org/pym.js/) responsive iframe script on WordPress sites, through the use of shortcodes and Gutenberg blocks. 4 | 5 | For detailed examples, ➡️ [read the docs!](./docs/) ⬅️ 6 | 7 | For a detailed changelog, [read readme.txt](./readme.txt)! 8 | 9 | For the most-recent release, [see the list of releases](https://github.com/INN/pym-shortcode/releases). 10 | -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | # Support resources 2 | 3 | This plugin's [documentation is on GitHub](https://github.com/INN/pym-shortcode/tree/master/docs). 4 | 5 | If you have questions about about this plugin, please contact us at support@inn.org. 6 | -------------------------------------------------------------------------------- /css/editor.css: -------------------------------------------------------------------------------- 1 | /** 2 | * The following styles get applied inside the editor only. 3 | * 4 | * Replace them with your own styles or remove the file completely. 5 | */ 6 | 7 | .wp-block-pym-shortcode-pym { 8 | background-color: #f8f9f9; 9 | padding: 14px; 10 | } 11 | 12 | .wp-block-pym-shortcode-pym .components-base-control__field { 13 | display: flex; 14 | flex-direction: row; 15 | } 16 | .wp-block-pym-shortcode-pym .components-base-control__field label { 17 | display: flex; 18 | align-items: center; 19 | margin-right: 10px; 20 | white-space: nowrap; 21 | font-weight: 600; 22 | flex-shrink: 0; 23 | 24 | margin-bottom: 0; 25 | 26 | font-size: 13px; 27 | font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif; 28 | } 29 | .wp-block-pym-shortcode-pym .dashicon { 30 | margin-right: 10px; 31 | } 32 | .wp-block-pym-shortcode-pym .components-base-control__field input { 33 | flex-grow: 1; 34 | } 35 | -------------------------------------------------------------------------------- /docs/img/block-in-editor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/pym-shortcode/8121d61e66ed6253cfa03d0672aeff6f46167bff/docs/img/block-in-editor.png -------------------------------------------------------------------------------- /docs/img/block.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/pym-shortcode/8121d61e66ed6253cfa03d0672aeff6f46167bff/docs/img/block.png -------------------------------------------------------------------------------- /docs/img/pym-example-desktop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/pym-shortcode/8121d61e66ed6253cfa03d0672aeff6f46167bff/docs/img/pym-example-desktop.png -------------------------------------------------------------------------------- /docs/img/pym-example-phone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/pym-shortcode/8121d61e66ed6253cfa03d0672aeff6f46167bff/docs/img/pym-example-phone.png -------------------------------------------------------------------------------- /docs/img/pym-shortcode-in-post.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/pym-shortcode/8121d61e66ed6253cfa03d0672aeff6f46167bff/docs/img/pym-shortcode-in-post.png -------------------------------------------------------------------------------- /docs/img/responsive-iframe-npr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Automattic/pym-shortcode/8121d61e66ed6253cfa03d0672aeff6f46167bff/docs/img/responsive-iframe-npr.png -------------------------------------------------------------------------------- /docs/maintainer-notes.md: -------------------------------------------------------------------------------- 1 | # Notes for Plugin maintainers 2 | 3 | ## Updating Pym.js 4 | 5 | `js/pym.v1.min.js` in this plugin should be kept up-to-date with the current version of https://pym.nprapps.org/pym.v1.min.js. 6 | 7 | `js/pym.js` is specifically `Pym.js` version 1.1.0, which this plugin was introduced with, before we had solid plans for an update strategy. It will not be updated. 8 | 9 | To update: 10 | 11 | - save https://pym.nprapps.org/pym.v1.min.js as `js/pym.v1.min.js` in this plugin, or download https://github.com/nprapps/pym.js/blob/master/dist/pym.v1.min.js from the relevant most-recent tagged release. 12 | - update this plugin's version number to the `Pym.js` version number 13 | 14 | NPR Visuals Team's [stated intention](https://github.com/nprapps/pym.js/tree/master#versioning) is that versions of `Pym.js` will be backwards-compatible for `0.x` and `0.0.x` releases, so we can copy those in directly. When a `x.0.0` release comes around, we'll need to figure out a plan for that. See discussion in https://github.com/INN/pym-shortcode/issues/12 15 | 16 | ## Updating the plugin 17 | 18 | The plugin's `A.B.C` version number should match the version number of the bundled copy of `Pym.js`. We started doing this in plugin release 1.1.2. 19 | 20 | The plugin's [version history](https://github.com/INN/pym-shortcode/releases) looks like this: 21 | 22 | - 0.1: initial release 23 | - 1.1.2 24 | - 1.2.0 25 | - 1.2.0.1: a fix in this plugin 26 | - 1.2.0.2: a fix in this plugin 27 | - 1.3.1 28 | - 1.3.2 29 | - 1.3.2.1: Gutenberg and settings page 30 | - 1.3.2.2: WordPress 5.0 support 31 | - 1.3.2.3: AMP support 32 | - 1.3.2.4: minor bugfix for WP 5.4 33 | 34 | ## Release checklist 35 | 36 | See [release-checklist.md](./release-checklist.md) for the full list. 37 | 38 | ## Testing before release 39 | 40 | You should make a copy of this document to keep track of checking off the checkboxes. A Github comment is a fine place to do that, as in https://github.com/INN/pym-shortcode/issues/68#issuecomment-593634311. 41 | 42 | See also https://github.com/INN/docs/blob/master/projects/wordpress-plugins/release.sh.md 43 | 44 | Run the following tests both with and without [the AMP plugin](https://wordpress.org/plugins/amp/) activated: 45 | - [ ] with 46 | - [ ] without 47 | 48 | Plugin settings: 49 | 50 | - [ ] Does the plugin settings page work? `/wp-admin/options-general.php?page=pym-embed-settings` 51 | - [ ] Does the plugin info page work? `/wp-admin/tools.php?page=pym-embeds-info` 52 | 53 | Shortcode tests: 54 | 55 | - [ ] `[pym src="https://blog.apps.npr.org/pym.js/examples/table/child.html"]` 56 | - [ ] `[pym src="https://blog.apps.npr.org/pym.js/examples/table/child.html" pymsrc="https://pym.nprapps.org/pym.v1.min.js"]` 57 | - [ ] `[pym src="https://blog.apps.npr.org/pym.js/examples/table/child.html" pymoptions=" xdomain: '\\*\.npr\.org' "]` 58 | - [ ] `[pym src="https://blog.apps.npr.org/pym.js/examples/table/child.html" class="one two three four float-left mw_50"]` 59 | - [ ] `[pym src="https://blog.apps.npr.org/pym.js/examples/table/child.html" align=""]` 60 | - [ ] `[pym src="https://blog.apps.npr.org/pym.js/examples/table/child.html" align="none"]` 61 | - [ ] `[pym src="https://blog.apps.npr.org/pym.js/examples/table/child.html" align="left"]` 62 | - [ ] `[pym src="https://blog.apps.npr.org/pym.js/examples/table/child.html" align="center"]` 63 | - [ ] `[pym src="https://blog.apps.npr.org/pym.js/examples/table/child.html" align="right"]` 64 | - [ ] `[pym src="https://blog.apps.npr.org/pym.js/examples/table/child.html" align="wide"]` 65 | - [ ] `[pym src="https://blog.apps.npr.org/pym.js/examples/table/child.html" align="full"]` 66 | - [ ] `[pym src="https://blog.apps.npr.org/pym.js/examples/table/child.html" id="extremely_specific_id"]` 67 | - [ ] `[pym src="https://blog.apps.npr.org/pym.js/examples/table/child.html" pymsrc="https://pym.nprapps.org/pym.v1.min.js" pymoptions=""]` 68 | - [ ] `[pym src="https://blog.apps.npr.org/pym.js/examples/table/child.html" pymsrc="https://pym.nprapps.org/pym.v1.min.js" pymoptions=""]` 69 | 70 | Gutenberg tests: 71 | 72 | - [ ] the block, when inserted, prompts users for a URL 73 | - [ ] the block's alignment, custom classes, custom ID, and other options are respected. 74 | - [ ] the block uses the default pymsrc URL if the pymsrc attribute is not set 75 | - [ ] on a site with Gutenberg not installed, the plugin functions 76 | - [ ] on a 4.9 site with Gutenberg installed, the plugin functions 77 | - [ ] on a 5.0 site, the plugin functions 78 | -------------------------------------------------------------------------------- /docs/readme.md: -------------------------------------------------------------------------------- 1 | # Pym.js Embeds for WordPress 2 | 3 | Pym.js Embeds provides shortcode and Gutenberg block wrappers for embedding responsive iframes using [Pym.js](http://blog.apps.npr.org/pym.js/), developed by the NPR Visuals Team. Embedded content resizes vertically to match its container's width. 4 | 5 | Contents: 6 | 7 | 1. [Plugin Installation](#plugin-installation) 8 | 2. [The Pym.js Shortcode](#the-pymjs-shortcode) 9 | 3. [The Pym.js Block](#the-pym-block) 10 | 4. [Embed Options](#embed-options) 11 | 1. [src: the only required argument](#src-the-child-url-required-argument) 12 | 2. [pymsrc](#pymsrc-the-url-for-pymjs) 13 | 3. [pymoptions](#pymoptions-settings-for-pymjs) 14 | 4. [class](#class-to-add-html-classes-to-the-pymjs-parent-element) 15 | 5. [align](#align-for-wordpress-alignment-support) 16 | 6. [id](#id-to-set-the-pym-parent-elements-id) 17 | 5. [Plugin Options](#plugin-options) 18 | 5. [Frequently Asked Questions](#frequently-asked-questions) 19 | 1. [Why would I want to use Pym.js in the first place?](#why-would-i-want-to-use-pym-in-the-first-place) 20 | 2. [Why is a WordPress plugin needed to use Pym.js?](#why-is-a-wordpress-plugin-needed-to-use-pymjs) 21 | 3. [When would I use a Pym.js solution versus embed code without using Pym.js?](#when-would-i-use-a-pymjs-solution-versus-embed-code-without-using-pymjs) 22 | 4. [Is Pym.js or this plugin dependent on jQuery or any other library?](#is-pymjs-or-this-plugin-dependent-on-jquery-or-any-other-library) 23 | 5. [Where should I put the graphic files I want to embed using `Pym.js`?](#where-should-i-put-the-graphic-files-i-want-to-embed-using-pymjs) 24 | 6. [What is the URL for pym.v1.min.js?](#what-is-the-url-for-pymv1minjs) 25 | 7. [What is the difference between `Pym.js` and `pym.v1.min.js`?](what-is-the-difference-between-pymjs-and-pymv1minjs) 26 | 8. [Why would I want to change the Pym.js source URL?](#why-would-i-want-to-change-the-pymjs-source-url) 27 | 9. [I've set a different pymsrc option, but now I'm seeing a message in the console](#ive-set-a-different-pymsrc-option-but-now-im-seeing-a-message-in-the-console) 28 | 10. [How do I serve Pym.js if the embedded page uses HTTPS and my site does not?](#how-do-i-serve-pymjs-if-the-embedded-page-uses-https-and-my-site-does-not) 29 | 11. [How do I know if there's an HTTPS problem with a given embedded iframe?](#how-do-i-know-if-theres-an-https-problem-with-a-given-embedded-iframe) 30 | 12. [What license is this plugin licensed under?](#what-license-is-this-plugin-licensed-under) 31 | 13. [How do I contribute to this plugin?](#how-do-i-contribute-to-this-plugin) 32 | 14. [How do I get support for this plugin?](#how-do-i-get-support-for-this-plugin) 33 | 6. [Other Pym.js Resources](#other-pymjs-resources) 34 | 35 | ## Plugin Installation 36 | 37 | 1. In the WordPress Dashboard go to **Plugins**, then click the **Add Plugins** button and search the WordPress Plugins Directory for Pym.js Embeds. Alternatively, you can download the zip file from this Github repo and upload it manually to your WordPress site. 38 | 2. Activate the plugin through the 'Plugins' screen in WordPress. 39 | 3. Nothing to configure, just begin using Pym.js Embeds! 40 | 41 | ## The Pym.js Shortcode 42 | 43 | In a WordPress post or page, use the Pym.js Shortcode like this: 44 | 45 | `[pym src="https://blog.apps.npr.org/pym.js/examples/table/child.html"]` 46 | 47 | Example in a post: 48 | 49 | ![Pym.js Shortcode in a WordPress post](img/pym-shortcode-in-post.png) 50 | 51 | ## The Pym.js Block 52 | 53 | Example in a post: 54 | 55 | ![Screenshot of the Pym.js Embed block in a post, with the block settings pane opened to show the block's options and advanced options](img/block-in-editor.png) 56 | 57 | For the block, all options available via shortcode arguments are available through the block's Advanced Options panel. 58 | 59 | ## Embed Options 60 | 61 | ``` 62 | [pym src="" pymsrc="" pymoptions="" class="" align="" id="" ] 63 | ``` 64 | 65 | ### `src`, the child URL (required argument) 66 | 67 | `src` is the URL of the page that is to be embedded. In these examples, we use https://blog.apps.npr.org/pym.js/examples/table/child.html, the source code for which can be found at https://github.com/nprapps/pym.js/tree/master/examples/table . 68 | 69 | For the shortcode, `src` is the only required parameter. 70 | 71 | ``` 72 | [pym src="https://blog.apps.npr.org/pym.js/examples/table/child.html"] 73 | ``` 74 | 75 | Here's what the setting looks like in a block: 76 | 77 | ![A Pym.js embed block in use in a post, showing its alignment controls](img/block.png) 78 | 79 | ### `pymsrc`, the URL for Pym.js 80 | 81 | `pymsrc` is optional; only set this if you need to specify a different source for `Pym.js` than the default. The default `Pym.js` source URL is `js/pym.v1.min.js` in this plugin's directory on your server. [NPR recommends](http://blog.apps.npr.org/pym.js/#get-pym-cdn) that you use the CDN version of `Pym.js` in most cases, which is available at `https://pym.nprapps.org/pym.v1.min.js`. An example shortcode using this option is as follows: 82 | 83 | ``` 84 | [pym src="https://blog.apps.npr.org/pym.js/examples/table/child.html" pymsrc="https://pym.nprapps.org/pym.v1.min.js"] 85 | ``` 86 | 87 | ### `pymoptions`, settings for Pym.js 88 | 89 | `pymoptions` is optional; this should be a javascript object without the surrounding `{}`, and is given in the event that options need to be passed to the `pymParent`. NPR gives [this example](http://blog.apps.npr.org/pym.js/#examples) javascript: 90 | 91 | ```js 92 | pym.Parent('example', 'https://blog.apps.npr.org/pym.js/examples/table/child.html', { xdomain: '*\.npr\.org' }); 93 | ``` 94 | 95 | To do the same thing with this Pym.js Shortcode, you would write: 96 | 97 | ``` 98 | [pym src="https://blog.apps.npr.org/pym.js/examples/table/child.html" pymoptions=" xdomain: '\\*\.npr\.org' "] 99 | ``` 100 | 101 | For a full list of options, see the Pym.js [API documentation for the `config` parameter](http://blog.apps.npr.org/pym.js/api/pym.js/1.3.2/module-pym.Parent.html). 102 | 103 | ### `class`, to add HTML classes to the Pym.js parent element 104 | 105 | `class` is optional; this should be a valid HTML class name. It will be added to the element's default class, `'pym'`. You would want to use this if, for example, you wanted to [use a size-based class name to determine the size of the embed on your site](https://github.com/INN/pym-shortcode/issues/23). The class `'pym'` will always be output on container elements created by the Pym.js Shortcode. This class was introduced in version 1.2.2. 106 | 107 | For example, the shortcode `[pym src="https://blog.apps.npr.org/pym.js/examples/table/child.html" class="one two three four float-left mw_50"]` results in the following output: 108 | 109 | ```html 110 |
111 | ... 112 | 113 | ``` 114 | 115 | If you do not want the class `'pym'` output on container elements, [add a filter](https://codex.wordpress.org/Plugin_API/Filter_Reference) to the hook `pym_shortcode_default_class` that returns an empty string. 116 | 117 | ### `align`, for WordPress alignment support 118 | 119 | `align` is optional; this should be one of the [WordPress-provided generated alignment types](https://codex.wordpress.org/CSS#WordPress_Generated_Classes): `left`, `right`, `center`, `none`. If your theme supports the `wide` or `full` values, you can use those too, as the value provided here will be prefixed with `align` and output as a CSS class on the Pym.js parent, so that the shortcode `[pym align="foo"]` results in the output `
...` 120 | 121 | In the Gutenberg editor, the alignment options are provided by the alignment controls that appear when the block is selected. The default choice is "none", with no option selected, and the other options are to align it left, center, or right. If your theme [declares support for the "wide" alignment](https://wordpress.org/gutenberg/handbook/extensibility/theme-support/#wide-alignment), you'll also see options for "wide" and "full" widths. The appearance of these alignment options on the page will depend on your site's theme. 122 | 123 | ![A Pym.js embed block in use in a post, showing its alignment controls](img/block.png) 124 | 125 | ### `id`, to set the Pym.js parent element's ID 126 | 127 | `id` is optional; this should be a valid HTML element ID name. It will be used as the ID of your `pymParent` iframe on the parent page. You would want to use this if, for example, [your embedded page contained navigation to another page, requiring the second page to know the pymParent element ID](https://github.com/INN/pym-shortcode/issues/20). 128 | 129 | For example, the shortcode `[pym src="https://blog.apps.npr.org/pym.js/examples/table/child.html" id="extremely_specific_id"]` results in the following output: 130 | 131 | ```html 132 |
133 | ... 134 | 135 | ``` 136 | 137 | ## Plugin Options 138 | 139 | The Pym.js Embed Settings page can be found under WordPress' "Settings" menu. 140 | 141 | The settings page provides two options: the default pymsrc and the option to override all pymsrc arguments. 142 | 143 | We **strongly recommend** that you set the default pymsrc to `https://pym.nprapps.org/pym.v1.min.js` and check the box to enable the pymsrc override. 144 | 145 | ### Default pymsrc 146 | 147 | As explained in the documentation for [the pymsrc embed option](#pymsrc-the-url-for-pymjs), the default copy of `Pym.js` used by this plugin is the copy bundled with this plugin. NPR recommends, and we recommend, that you use the copy of `Pym.js` provided by NPR's CDN. However, this plugin cannot force you to do so; the WordPress.org plugin guidelines generally [prohibit plugin use of third-party scripts without user consent](https://developer.wordpress.org/plugins/wordpress-org/detailed-plugin-guidelines/#7-plugins-may-not-track-users-without-their-consent), and frown upon plugins that use CDNs by default. Therefore, we give you the option to use NPR's CDN, or your newsroom's CDN, and ship the plugin in a state that defaults to no CDN. 148 | 149 | Shortcodes and blocks have the the option to specify an alternate source for `Pym.js` at a per-embed level, which allows you to opt into using the CDN version of the script on a per-embed level. This is less than optimal; every time you create a `Pym.js` embed on your site, you will need to check that the pymsrc option is set. 150 | 151 | To save time and effort, set the "Default pymsrc" option in the plugin settings to NPR's CDN copy of `Pym.js`: `https://pym.nprapps.org/pym.v1.min.js` 152 | 153 | ### Override pymsrc 154 | 155 | Anyone who can edit a post can set the pymsrc URL on a shortcode or a block, and the pymsrc URL can be *any* resource. Browsers will try to load it as JavaScript, even if the set URL is for an image, a 404 page, or a non-`Pym.js` library. This is not good. 156 | 157 | We **strongly recommend** that you check this box, to force all `Pym.js` Embed shortcodes and blocks to use the plugin's default pymsrc. 158 | 159 | This box is not checked by default, because defaulting to overriding the pymsrc URL would potentially break existing shortcodes on sites that used this plugin before release version 1.3.2.1. If you used this plugin to create `[pym]` shortcodes before release 1.3.2.1, and wish to test your embeds before enabling the override, read [this testing advice](./upgrade-testing.md). 160 | 161 | ## Frequently Asked Questions 162 | 163 | ### Why would I want to use Pym.js in the first place? 164 | 165 | Using iframes in a responsive page can be frustrating. It’s easy enough to make an iframe’s width span 100% of its container, but sizing its height is tricky — especially if the content of the iframe changes height depending on page width (for example, because of text wrapping or media queries) or events within the iframe. For more information, see [NPR's documentation for `Pym.js`](http://blog.apps.npr.org/pym.js). 166 | 167 | ### Why is a WordPress plugin needed to use `Pym.js`? 168 | 169 | Normally WordPress strips out JavaScript inserted in posts and pages, so the usual HTML `Pym.js` embed code's `', 138 | wp_json_encode( __( 'Hi Pym.js user! It looks like your post has multiple values for pymsrc for the blocks and shortcodes in use on this page. This may be causing problems for your Pym.js embeds. For more details, see https://github.com/INN/pym-shortcode/tree/master/docs#ive-set-a-different-pymsrc-option-but-now-im-seeing-a-message-in-the-console', 'pym_shortcode' ) ), 139 | wp_json_encode( $this->sources ) 140 | ); 141 | } 142 | 143 | } 144 | -------------------------------------------------------------------------------- /inc/info-page.php: -------------------------------------------------------------------------------- 1 | %1$s', 38 | esc_html( get_admin_page_title() ) 39 | ); 40 | 41 | printf( 42 | '

%1$s

', 43 | wp_kses_post( __( 'For information on how to use the block and shortcode provided by the Pym.js Embeds plugin, read the plugin\'s documentation on GitHub.', 'pym-embeds' ) ) 44 | ); 45 | 46 | printf( 47 | '', 48 | esc_html__( 'The URL for the copy of Pym.js hosted on this site is:', 'pym-embeds' ) 49 | ); 50 | 51 | // copying how qz.com does their share links 52 | printf( 53 | '', 54 | esc_attr( pym_pymsrc_local_url() ) 55 | ); 56 | } 57 | -------------------------------------------------------------------------------- /inc/settings-page.php: -------------------------------------------------------------------------------- 1 | %1$s', 85 | esc_html( get_admin_page_title() ) 86 | ); 87 | 88 | ?> 89 |
90 | 95 |
96 | __NAMESPACE__ . '\sanitize_callback', 110 | ) 111 | ); 112 | 113 | add_settings_section( 114 | settings_section(), 115 | __( 'Pym.js Source Settings', 'pym-embeds' ), 116 | __NAMESPACE__ . '\pym_settings_section_callback', 117 | settings_page() 118 | ); 119 | 120 | add_settings_field( 121 | 'default_pymsrc', 122 | __( 'Default pymsrc', 'pym-embeds' ), 123 | __NAMESPACE__ . '\field_default_pymsrc', 124 | settings_page(), // menu slug of this page. 125 | settings_section(), // settings section slug. 126 | array( 127 | 'label_for' => 'default_pymsrc', 128 | ) 129 | ); 130 | 131 | add_settings_field( 132 | 'override_pymsrc', 133 | __( 'Override pymsrc', 'pym-embeds' ), 134 | __NAMESPACE__ . '\field_override_pymsrc', 135 | settings_page(), // menu slug of this page. 136 | settings_section(), // settings section slug. 137 | array( 138 | 'label_for' => 'override_pymsrc', 139 | ) 140 | ); 141 | } 142 | add_action( 'admin_init', __NAMESPACE__ . '\admin_init' ); 143 | 144 | /** 145 | * Settings section callback 146 | * 147 | * Does nothing. 148 | * 149 | * @param Array $args Arguments passed to the section callback. 150 | */ 151 | function pym_settings_section_callback( $args ) { 152 | printf( 153 | '

%1$s

', 154 | __( 'The Pym.js JavaScript library can be provided from many sources. By default, shortcodes and blocks will use a copy of Pym.js hosted on your website to power embeds. For more information about changing the Pym.js source URL, referred to as \'pymsrc\', please read this plugin\'s documentation.', 'pym-embeds' ) 155 | ); 156 | } 157 | 158 | /** 159 | * Sanitization callback for the option_value. 160 | * 161 | * If the default_pymsrc isn't a valid URL or is the pym_pymsrc_local_url, blank it. 162 | * If the override_pymsrc isn't 'on' indicating it's checked, blank it. 163 | * 164 | * @since 1.3.2.1 165 | * @param Mixed $value The setting. 166 | * @return Array The sanitized setting 167 | * @uses pym_pymsrc_local_url 168 | * @uses pym_plugin_version 169 | */ 170 | function sanitize_callback( $value ) { 171 | $new_value = array(); 172 | 173 | $proposed_pymsrc = wp_http_validate_url( $value['default_pymsrc'] ); 174 | if ( pym_pymsrc_local_url() === $proposed_pymsrc ) { 175 | $proposed_pymsrc = null; 176 | } 177 | $new_value['default_pymsrc'] = ! empty( $proposed_pymsrc ) ? $proposed_pymsrc : null; 178 | 179 | $new_value['override_pymsrc'] = ( isset( $value['override_pymsrc'] ) && 'on' === $value['override_pymsrc'] ) ? 'on': null; 180 | 181 | $new_value['version'] = pym_plugin_version(); 182 | 183 | return $new_value; 184 | } 185 | 186 | /** 187 | * The field for the default Pymsrc 188 | * 189 | * @param Array $args The arguments passed to the settings field callback. 190 | */ 191 | function field_default_pymsrc( $args ) { 192 | $settings = get_option( option_key() ); 193 | $id = isset( $args['label_for'] ) ? $args['label_for'] : 'default_pymsrc'; 194 | $id = option_key() . '[' . $id . ']'; 195 | 196 | $url = isset( $settings['default_pymsrc'] ) ? $settings['default_pymsrc'] : ''; 197 | printf( 198 | '', 199 | esc_attr( $id ), 200 | esc_attr( $url ) 201 | ); 202 | 203 | printf( 204 | '', 205 | esc_attr( $id ), 206 | wp_kses_post( __( 'This URL is where Pym.js will be loaded from for all embeds that do not set a pymsrc in the shortcode attributes or block settings. NPR and the Pym.js Embed plugin maintainers recommend that you use the NPR-provided CDN for this purpose: https://pym.nprapps.org/pym.v1.min.js', 'pym-embeds' ) ) 207 | ); 208 | printf( 209 | '', 210 | esc_attr( $id ), 211 | sprintf( 212 | // translators: %1$s is a bare URL. 213 | wp_kses_post( __( 'If no pymsrc URL is set here, then the plugin-provided copy of Pym.js will be used as the default: %1$s', 'pym-embeds' ) ), 214 | esc_html( pym_pymsrc_local_url() ) 215 | ) 216 | ); 217 | } 218 | 219 | /** 220 | * The checkbox to force all embeds to use the default 221 | * 222 | * @param Array $args The arguments passed to the settings field callback. 223 | */ 224 | function field_override_pymsrc( $args ) { 225 | $settings = get_option( option_key() ); 226 | $id = isset( $args['label_for'] ) ? $args['label_for'] : 'override_pymsrc'; 227 | $id = option_key() . '[' . $id . ']'; 228 | 229 | $value = isset( $settings['override_pymsrc'] ) ? $settings['override_pymsrc'] : ''; 230 | printf( 231 | '', 232 | esc_attr( $id ), 233 | checked( $value, 'on', false ) 234 | ); 235 | 236 | printf( 237 | '', 238 | esc_attr( $id ), 239 | esc_html__( 'Checking this box means that every Pym.js embed will use the default pymsrc URL, ignoring the pymsrc URL set in the embed\'s shortcode attributes or block settings. We recommend that you check this box after setting the default pymsrc URL to the CDN-provided copy of the library.', 'pym-embeds' ) 240 | ); 241 | } 242 | -------------------------------------------------------------------------------- /inc/shortcode.php: -------------------------------------------------------------------------------- 1 |
', 64 | esc_attr( $actual_id ), 65 | esc_attr( $actual_classes ) 66 | ); 67 | 68 | // What's the pymsrc for this shortcode? 69 | if ( 70 | pym_maybe_override_pymsrc() // if the box to override has been checked... 71 | || empty( $atts['pymsrc'] ) // if there is no specified pymsrc... 72 | || ! wp_http_validate_url( $atts['pymsrc'] ) // if the specified pymsrc is not a safe URL... 73 | ) { 74 | $pymsrc = pym_pymsrc_default_url(); // use the default URL. 75 | } else { 76 | $pymsrc = $atts['pymsrc']; 77 | } 78 | 79 | // If this is the first Pym.js element on the page, 80 | // register the default pymsrc script tag for output. 81 | // 82 | // Or, if the pymsrc is set, register that specific pymsrc for output. 83 | if ( 0 === $pym_id || ! empty( $atts['pymsrc'] ) ) { 84 | $pymsrc_output = Pymsrc_Output::get_instance(); 85 | $pymsrc_output->add( $pymsrc ); 86 | } 87 | 88 | // Output the parent's scripts. 89 | pym_shortcode_script_footer_enqueue( array( 90 | 'pym_id' => $pym_id, 91 | 'actual_id' => $actual_id, 92 | 'src' => $src, 93 | 'pymoptions' => $pymoptions, 94 | // The following options are not necessary for the default 95 | // function pym_shortcode_script_footer_enqueue, but are provided 96 | // in case someone wants to do other things with their own version 97 | // of this function. 98 | // @link https://github.com/INN/pym-shortcode/issues/19 99 | 'actual_classes' => $actual_classes, 100 | 'pymsrc' => $pymsrc, 101 | ) ); 102 | 103 | // What is output to the page: 104 | $ret = ob_get_clean(); 105 | return $ret; 106 | } 107 | add_shortcode( 'pym', 'pym_shortcode' ); 108 | 109 | /** 110 | * Given the necessary arguments for creating an embed's activation javascript, enqueue that script in the footer 111 | * 112 | * This function is pluggable. https://codex.wordpress.org/Pluggable_Functions 113 | * If your site requires a different activation script than the one provided 114 | * by this function, create a function in your site's theme or in a plugin 115 | * with this plugin's name, accepting the arguments passed to this function. 116 | * 117 | * @link https://github.com/INN/pym-shortcode/issues/19 118 | * 119 | * @param Array $args Has the following indices: 120 | * - 'pym_id' Which Pym.js embed instance this is on the page, provided for 121 | * informational purposes. In this function, the pym_id value is 122 | * used as the variable name in `var pym_id = new pym.Parent(...);` 123 | * - 'actual_id' the element ID used for the Pym.js container element, 124 | * which is at this point set on the page and not changeable from 125 | * this function. This is the first argument for `new pym.Parent()`. 126 | * - 'src' the URL for the Pym.js child page. This is the second argument 127 | * for `new pym.Parent()`. 128 | * - 'pymoptions' The third argument for `pym.Parent()` See the xdomain 129 | * argument in http://blog.apps.npr.org/pym.js/#example-block 130 | * - 'actual_classes' The classes used on the Pym.js container element, 131 | * provided to this function for informational purposes. 132 | * - 'pymsrc' The URL from which Pym.js is to be loaded for this emebed, 133 | * based on the shortcode/block options and the plugin settings. 134 | * 135 | * @since 1.3.2.1 136 | */ 137 | if ( ! function_exists( 'pym_shortcode_script_footer_enqueue' ) ) { 138 | function pym_shortcode_script_footer_enqueue( $args = array() ) { 139 | add_action( 140 | 'wp_footer', 141 | function() use ( $args ) { 142 | // Output the parent's scripts. 143 | echo ''; 152 | echo PHP_EOL; // for pretty printing of scripts in the footer. 153 | }, 154 | 20 // So that this comes after the pymsrc tag is output at priority 10. 155 | ); 156 | } 157 | } 158 | 159 | /** 160 | * Whether to force use of the default pymsrc URL. 161 | * 162 | * @since 1.3.2.1 163 | * @uses \INN\PymEmbeds\Settings\option_key() 164 | * @return bool Whether to force use of the default URL. 165 | */ 166 | function pym_maybe_override_pymsrc() { 167 | $settings = get_option( \INN\PymEmbeds\Settings\option_key() ); 168 | if ( isset( $settings['override_pymsrc'] ) && 'on' === $settings['override_pymsrc'] ) { 169 | return true; 170 | } 171 | return false; 172 | } 173 | 174 | /** 175 | * The default URL for pymsrc, as defined in plugin settings 176 | * 177 | * @since 1.3.2.1 178 | * @uses pym_pymsrc_local_url 179 | */ 180 | function pym_pymsrc_default_url() { 181 | $settings = get_option( \INN\PymEmbeds\Settings\option_key() ); 182 | if ( isset ( $settings['default_pymsrc'] ) ) { 183 | if ( wp_http_validate_url( $settings['default_pymsrc'] ) ) { 184 | return $settings['default_pymsrc']; 185 | } 186 | } 187 | return pym_pymsrc_local_url(); 188 | } 189 | 190 | /** 191 | * The plugin-provided pymsrc url 192 | * 193 | * @since 1.3.2.1 194 | * @return string The URL for /wp-content/plugins/pym-shortcode/js/pym.v1.min.js 195 | */ 196 | function pym_pymsrc_local_url() { 197 | return plugins_url( '/js/pym.v1.min.js', dirname( __FILE__ ) ); 198 | } 199 | -------------------------------------------------------------------------------- /js/block.js: -------------------------------------------------------------------------------- 1 | ( function( wp ) { 2 | /** 3 | * Registers a new block provided a unique name and an object defining its behavior. 4 | * @see https://github.com/WordPress/gutenberg/tree/master/blocks#api 5 | */ 6 | var registerBlockType = wp.blocks.registerBlockType; 7 | 8 | /** 9 | * Returns a new element of given type. Element is an abstraction layer atop React. 10 | * @see https://github.com/WordPress/gutenberg/tree/master/element#element 11 | */ 12 | var el = wp.element.createElement; 13 | 14 | /** 15 | * Rendering 16 | */ 17 | var ServerSideRender = wp.components.ServerSideRender; 18 | 19 | /** 20 | * Text tools 21 | */ 22 | var TextControl = wp.components.TextControl; 23 | var PanelBody = wp.components.PanelBody; 24 | 25 | /** 26 | * The sidebar controls I think? 27 | * @todo check definition 28 | */ 29 | var InspectorControls = wp.editor.InspectorControls; 30 | var InspectorAdvancedControls = wp.editor.InspectorAdvancedControls; 31 | 32 | /** 33 | * Retrieves the translation of text. 34 | * @see https://github.com/WordPress/gutenberg/tree/master/i18n#api 35 | */ 36 | var __ = wp.i18n.__; 37 | 38 | /** 39 | * Literally just for a fancy dashicon 40 | * @see https://github.com/WordPress/gutenberg/blob/master/packages/components/src/dashicon/README.md 41 | */ 42 | var dashicon = wp.components.Dashicon; 43 | 44 | /** 45 | * Every block starts by registering a new block type definition. 46 | * @see https://wordpress.org/gutenberg/handbook/block-api/ 47 | */ 48 | registerBlockType( 'pym-shortcode/pym', { 49 | /** 50 | * This is the display title for your block, which can be translated with `i18n` functions. 51 | * The block inserter will show this name. 52 | */ 53 | title: __( 'Pym.js Embed' ), 54 | 55 | /** 56 | * An icon property should be specified to make it easier to identify a block. 57 | * These can be any of WordPress’ Dashicons, or a custom svg element. 58 | */ 59 | icon: 'analytics', 60 | 61 | /** 62 | * Blocks are grouped into categories to help users browse and discover them. 63 | * The categories provided by core are `common`, `embed`, `formatting`, `layout` and `widgets`. 64 | */ 65 | category: 'embed', 66 | 67 | /** 68 | * Gutenberg features supported by this block 69 | * @link https://wordpress.org/gutenberg/handbook/block-api/#supports-optional 70 | */ 71 | supports: { 72 | html: false, // Removes support for an HTML mode. 73 | align: true, // supports alignment 74 | alignWide: true, // supports the extra slignment 75 | anchor: false, // see https://github.com/INN/pym-shortcode/issues/36 76 | customClassName: true, 77 | className: true, 78 | inserter: true, 79 | multiple: true, 80 | }, 81 | 82 | /** 83 | * Describe the block for the block inspector 84 | */ 85 | description: __( 'Embed a webpage using NPR\'s Pym.js' ), 86 | 87 | /** 88 | * Make the block easier to find by including keywords 89 | */ 90 | keywords: [ __( 'NPR' ) ], 91 | 92 | /** 93 | * The edit function describes the structure of your block in the context of the editor. 94 | * This represents what the editor will render when the block is used. 95 | * @see https://wordpress.org/gutenberg/handbook/block-edit-save/#edit 96 | * 97 | * @param {Object} [props] Properties passed from the editor. 98 | * @return {Element} Element to render. 99 | */ 100 | edit: function( props ) { 101 | return [ 102 | // https://gist.github.com/pento/cf38fd73ce0f13fcf0f0ae7d6c4b685d#file-php-block-js-L59 103 | el( 104 | 'div', 105 | { 106 | // attributes 107 | className: props.className, 108 | }, 109 | // children follow 110 | el( TextControl, { 111 | label: [ 112 | el( 113 | dashicon, 114 | { 115 | icon: 'analytics' 116 | }, 117 | ), 118 | __( 'Pym.js Child URL' ) 119 | ], 120 | value: props.attributes.src, 121 | placeholder: __( 'What is the URL of your Pym.js child page?' ), 122 | onChange: ( value ) => { props.setAttributes( { src: value } ); }, 123 | } ) 124 | ), 125 | el( InspectorControls, {}, 126 | el( 127 | PanelBody, // inserting a PanelBody here for the presentational classes, so that it matches the display of InspectorAdvancedControls children 128 | { 129 | initialOpen: true, 130 | }, 131 | el( TextControl, { 132 | label: __( 'Pym.js Child URL' ), 133 | value: props.attributes.src, 134 | placeholder: __( 'What is the URL of your Pym.js child page?' ), 135 | onChange: ( value ) => { props.setAttributes( { src: value } ); }, 136 | } ) 137 | ) 138 | ), 139 | el( InspectorAdvancedControls, {}, 140 | el( TextControl, { 141 | label: __( 'Parent Element ID (optional)' ), 142 | value: props.attributes.id, 143 | onChange: ( value ) => { props.setAttributes( { id: value } ); }, 144 | help: __( 'The Pym.js block will automatically generate an ID for the parent element and use that to initiate the Pym.js embed. If your child page\'s code requires its parent to have a specific element ID, set that here.' ), 145 | } ), 146 | el( TextControl, { 147 | label: __( 'Pym.js Source URL (optional)' ), 148 | value: props.attributes.pymsrc, 149 | onChange: ( value ) => { props.setAttributes( { pymsrc: value } ); }, 150 | } ), 151 | el( TextControl, { 152 | label: __( 'Pym.js Options' ), 153 | value: props.attributes.pymoptions, 154 | onChange: ( value ) => { props.setAttributes( { pymoptions: value } ); }, 155 | // @todo make this translatable https://github.com/WordPress/gutenberg/blob/master/packages/i18n/README.md 156 | help: [ 157 | 'For more about this control, see ', 158 | el( 159 | 'a', 160 | { 161 | href: 'http://blog.apps.npr.org/pym.js/', 162 | }, 163 | 'the Usage section of the Pym.js docs', 164 | ), 165 | '.' 166 | ], 167 | } ) 168 | ), 169 | ]; 170 | }, 171 | 172 | /** 173 | * The save function defines the way in which the different attributes should be combined 174 | * into the final markup, which is then serialized by Gutenberg into `post_content`. 175 | * @see https://wordpress.org/gutenberg/handbook/block-edit-save/#save 176 | * 177 | * Though this block has a render callback, we save the URL of the embed in the post_content 178 | * just in case this plugin is ever deactivated. 179 | * 180 | * @return {Element} Element to render. 181 | */ 182 | save: function( props ) { 183 | return wp.element.createElement( 184 | 'a', 185 | { 186 | href: props.attributes.src, 187 | }, 188 | props.attributes.src 189 | ); 190 | }, 191 | 192 | /** 193 | * @todo provide transformation from shortcode 194 | * @todo provide transformation to plain embed 195 | * 196 | * @link https://wordpress.org/gutenberg/handbook/block-api/#transforms-optional 197 | */ 198 | transforms: { 199 | from: [ 200 | { 201 | type: 'shortcode', 202 | tag: 'pym', 203 | attributes: { 204 | src: { 205 | type: 'string', 206 | shortcode: function( named ) { 207 | return named.src ? named.src : ''; 208 | }, 209 | }, 210 | pymsrc: { 211 | type: 'string', 212 | shortcode: function( named ) { 213 | return named.pymsrc ? named.pymsrc : ''; 214 | }, 215 | }, 216 | pymoptions: { 217 | type: 'string', 218 | shortcode: function( named ) { 219 | return named.pymoptions ? named.pymoptions : ''; 220 | }, 221 | }, 222 | id: { 223 | type: 'string', 224 | shortcode: function( named ) { 225 | return named.id ? named.id : ''; 226 | }, 227 | }, 228 | className: { 229 | type: 'string', 230 | shortcode: function( named ) { 231 | return named.class ? named.class : ''; 232 | }, 233 | }, 234 | align: { 235 | type: 'string', 236 | shortcode: function( named ) { 237 | var align = named.align ? named.align : 'alignnone'; 238 | return align.replace( 'align', '' ); 239 | }, 240 | }, 241 | }, 242 | }, 243 | ] 244 | // @todo provide "to" transformations for embed, plain HTML, etc 245 | }, 246 | } ); 247 | } )( 248 | window.wp 249 | ); 250 | -------------------------------------------------------------------------------- /js/pym.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Pym.js is library that resizes an iframe based on the width of the parent and the resulting height of the child. 3 | * Check out the docs at http://blog.apps.npr.org/pym.js/ or the readme at README.md for usage. 4 | */ 5 | 6 | /* global module */ 7 | 8 | (function(factory) { 9 | if (typeof define === 'function' && define.amd) { 10 | define(factory); 11 | } else if (typeof module !== 'undefined' && module.exports) { 12 | module.exports = factory(); 13 | } else { 14 | window.pym = factory.call(this); 15 | } 16 | })(function() { 17 | var MESSAGE_DELIMITER = 'xPYMx'; 18 | 19 | var lib = {}; 20 | 21 | /** 22 | * Generic function for parsing URL params. 23 | * Via http://stackoverflow.com/questions/901115/how-can-i-get-query-string-values-in-javascript 24 | * 25 | * @method _getParameterByName 26 | * @param {String} name The name of the paramter to get from the URL. 27 | */ 28 | var _getParameterByName = function(name) { 29 | var regex = new RegExp("[\\?&]" + name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]') + '=([^&#]*)'); 30 | var results = regex.exec(location.search); 31 | 32 | if (results === null) { 33 | return ''; 34 | } 35 | 36 | return decodeURIComponent(results[1].replace(/\+/g, " ")); 37 | }; 38 | 39 | /** 40 | * Check the message to make sure it comes from an acceptable xdomain. 41 | * Defaults to '*' but can be overriden in config. 42 | * 43 | * @method _isSafeMessage 44 | * @param {Event} e The message event. 45 | * @param {Object} settings Configuration. 46 | */ 47 | var _isSafeMessage = function(e, settings) { 48 | if (settings.xdomain !== '*') { 49 | // If origin doesn't match our xdomain, return. 50 | if (!e.origin.match(new RegExp(settings.xdomain + '$'))) { return; } 51 | } 52 | 53 | return true; 54 | }; 55 | 56 | /** 57 | * Construct a message to send between frames. 58 | * 59 | * NB: We use string-building here because JSON message passing is 60 | * not supported in all browsers. 61 | * 62 | * @method _makeMessage 63 | * @param {String} id The unique id of the message recipient. 64 | * @param {String} messageType The type of message to send. 65 | * @param {String} message The message to send. 66 | */ 67 | var _makeMessage = function(id, messageType, message) { 68 | var bits = ['pym', id, messageType, message]; 69 | 70 | return bits.join(MESSAGE_DELIMITER); 71 | }; 72 | 73 | /** 74 | * Construct a regex to validate and parse messages. 75 | * 76 | * @method _makeMessageRegex 77 | * @param {String} id The unique id of the message recipient. 78 | */ 79 | var _makeMessageRegex = function(id) { 80 | var bits = ['pym', id, '(\\S+)', '(.+)']; 81 | 82 | return new RegExp('^' + bits.join(MESSAGE_DELIMITER) + '$'); 83 | }; 84 | 85 | /** 86 | * Initialize Pym for elements on page that have data-pym attributes. 87 | * 88 | * @method _autoInit 89 | */ 90 | var _autoInit = function() { 91 | var elements = document.querySelectorAll( 92 | '[data-pym-src]:not([data-pym-auto-initialized])' 93 | ); 94 | 95 | var length = elements.length; 96 | 97 | for (var idx = 0; idx < length; ++idx) { 98 | var element = elements[idx]; 99 | 100 | /* 101 | * Mark automatically-initialized elements so they are not 102 | * re-initialized if the user includes pym.js more than once in the 103 | * same document. 104 | */ 105 | element.setAttribute('data-pym-auto-initialized', ''); 106 | 107 | // Ensure elements have an id 108 | if (element.id === '') { 109 | element.id = 'pym-' + idx; 110 | } 111 | 112 | var src = element.getAttribute('data-pym-src'); 113 | var xdomain = element.getAttribute('data-pym-xdomain'); 114 | var config = {}; 115 | 116 | if (xdomain) { 117 | config.xdomain = xdomain; 118 | } 119 | 120 | new lib.Parent(element.id, src, config); 121 | } 122 | }; 123 | 124 | /** 125 | * The Parent half of a response iframe. 126 | * 127 | * @class Parent 128 | * @param {String} id The id of the div into which the iframe will be rendered. 129 | * @param {String} url The url of the iframe source. 130 | * @param {Object} config Configuration to override the default settings. 131 | */ 132 | lib.Parent = function(id, url, config) { 133 | this.id = id; 134 | this.url = url; 135 | this.el = document.getElementById(id); 136 | this.iframe = null; 137 | 138 | this.settings = { 139 | xdomain: '*' 140 | }; 141 | 142 | this.messageRegex = _makeMessageRegex(this.id); 143 | this.messageHandlers = {}; 144 | 145 | // ensure a config object 146 | config = (config || {}); 147 | 148 | /** 149 | * Construct the iframe. 150 | * 151 | * @memberof Parent.prototype 152 | * @method _constructIframe 153 | */ 154 | this._constructIframe = function() { 155 | // Calculate the width of this element. 156 | var width = this.el.offsetWidth.toString(); 157 | 158 | // Create an iframe element attached to the document. 159 | this.iframe = document.createElement('iframe'); 160 | 161 | // Save fragment id 162 | var hash = ''; 163 | var hashIndex = this.url.indexOf('#'); 164 | 165 | if (hashIndex > -1) { 166 | hash = this.url.substring(hashIndex, this.url.length); 167 | this.url = this.url.substring(0, hashIndex); 168 | } 169 | 170 | // If the URL contains querystring bits, use them. 171 | // Otherwise, just create a set of valid params. 172 | if (this.url.indexOf('?') < 0) { 173 | this.url += '?'; 174 | } else { 175 | this.url += '&'; 176 | } 177 | 178 | // Append the initial width as a querystring parameter, and the fragment id 179 | this.iframe.src = this.url + 180 | 'initialWidth=' + width + 181 | '&childId=' + this.id + 182 | '&parentUrl=' + encodeURIComponent(window.location.href) + 183 | hash; 184 | 185 | // Set some attributes to this proto-iframe. 186 | this.iframe.setAttribute('width', '100%'); 187 | this.iframe.setAttribute('scrolling', 'no'); 188 | this.iframe.setAttribute('marginheight', '0'); 189 | this.iframe.setAttribute('frameborder', '0'); 190 | 191 | if (this.settings.title) { 192 | this.iframe.setAttribute('title', this.settings.title); 193 | } 194 | 195 | // Append the iframe to our element. 196 | this.el.appendChild(this.iframe); 197 | 198 | // Add an event listener that will handle redrawing the child on resize. 199 | window.addEventListener('resize', this._onResize); 200 | }; 201 | 202 | /** 203 | * Send width on resize. 204 | * 205 | * @memberof Parent.prototype 206 | * @method _onResize 207 | */ 208 | this._onResize = function() { 209 | this.sendWidth(); 210 | }.bind(this); 211 | 212 | /** 213 | * Fire all event handlers for a given message type. 214 | * 215 | * @memberof Parent.prototype 216 | * @method _fire 217 | * @param {String} messageType The type of message. 218 | * @param {String} message The message data. 219 | */ 220 | this._fire = function(messageType, message) { 221 | if (messageType in this.messageHandlers) { 222 | for (var i = 0; i < this.messageHandlers[messageType].length; i++) { 223 | this.messageHandlers[messageType][i].call(this, message); 224 | } 225 | } 226 | }; 227 | 228 | /** 229 | * Remove this parent from the page and unbind it's event handlers. 230 | * 231 | * @memberof Parent.prototype 232 | * @method remove 233 | */ 234 | this.remove = function() { 235 | window.removeEventListener('message', this._processMessage); 236 | window.removeEventListener('resize', this._onResize); 237 | 238 | this.el.removeChild(this.iframe); 239 | }; 240 | 241 | /** 242 | * @callback Parent~onMessageCallback 243 | * @param {String} message The message data. 244 | */ 245 | 246 | /** 247 | * Process a new message from the child. 248 | * 249 | * @memberof Parent.prototype 250 | * @method _processMessage 251 | * @param {Event} e A message event. 252 | */ 253 | this._processMessage = function(e) { 254 | // First, punt if this isn't from an acceptable xdomain. 255 | if (!_isSafeMessage(e, this.settings)) { 256 | return; 257 | } 258 | 259 | // Discard object messages, we only care about strings 260 | if (typeof e.data !== 'string') { 261 | return; 262 | } 263 | 264 | // Grab the message from the child and parse it. 265 | var match = e.data.match(this.messageRegex); 266 | 267 | // If there's no match or too many matches in the message, punt. 268 | if (!match || match.length !== 3) { 269 | return false; 270 | } 271 | 272 | var messageType = match[1]; 273 | var message = match[2]; 274 | 275 | this._fire(messageType, message); 276 | }.bind(this); 277 | 278 | /** 279 | * Resize iframe in response to new height message from child. 280 | * 281 | * @memberof Parent.prototype 282 | * @method _onHeightMessage 283 | * @param {String} message The new height. 284 | */ 285 | this._onHeightMessage = function(message) { 286 | /* 287 | * Handle parent height message from child. 288 | */ 289 | var height = parseInt(message); 290 | 291 | this.iframe.setAttribute('height', height + 'px'); 292 | }; 293 | 294 | /** 295 | * Navigate parent to a new url. 296 | * 297 | * @memberof Parent.prototype 298 | * @method _onNavigateToMessage 299 | * @param {String} message The url to navigate to. 300 | */ 301 | this._onNavigateToMessage = function(message) { 302 | /* 303 | * Handle parent scroll message from child. 304 | */ 305 | document.location.href = message; 306 | }; 307 | 308 | /** 309 | * Bind a callback to a given messageType from the child. 310 | * 311 | * Reserved message names are: "height", "scrollTo" and "navigateTo". 312 | * 313 | * @memberof Parent.prototype 314 | * @method onMessage 315 | * @param {String} messageType The type of message being listened for. 316 | * @param {Parent~onMessageCallback} callback The callback to invoke when a message of the given type is received. 317 | */ 318 | this.onMessage = function(messageType, callback) { 319 | if (!(messageType in this.messageHandlers)) { 320 | this.messageHandlers[messageType] = []; 321 | } 322 | 323 | this.messageHandlers[messageType].push(callback); 324 | }; 325 | 326 | /** 327 | * Send a message to the the child. 328 | * 329 | * @memberof Parent.prototype 330 | * @method sendMessage 331 | * @param {String} messageType The type of message to send. 332 | * @param {String} message The message data to send. 333 | */ 334 | this.sendMessage = function(messageType, message) { 335 | this.el.getElementsByTagName('iframe')[0].contentWindow.postMessage(_makeMessage(this.id, messageType, message), '*'); 336 | }; 337 | 338 | /** 339 | * Transmit the current iframe width to the child. 340 | * 341 | * You shouldn't need to call this directly. 342 | * 343 | * @memberof Parent.prototype 344 | * @method sendWidth 345 | */ 346 | this.sendWidth = function() { 347 | var width = this.el.offsetWidth.toString(); 348 | 349 | this.sendMessage('width', width); 350 | }; 351 | 352 | // Add any overrides to settings coming from config. 353 | for (var key in config) { 354 | this.settings[key] = config[key]; 355 | } 356 | 357 | // Bind required message handlers 358 | this.onMessage('height', this._onHeightMessage); 359 | this.onMessage('navigateTo', this._onNavigateToMessage); 360 | 361 | // Add a listener for processing messages from the child. 362 | window.addEventListener('message', this._processMessage, false); 363 | 364 | // Construct the iframe in the container element. 365 | this._constructIframe(); 366 | 367 | return this; 368 | }; 369 | 370 | /** 371 | * The Child half of a responsive iframe. 372 | * 373 | * @class Child 374 | * @param {Object} config Configuration to override the default settings. 375 | */ 376 | lib.Child = function(config) { 377 | this.parentWidth = null; 378 | this.id = null; 379 | this.parentUrl = null; 380 | 381 | this.settings = { 382 | renderCallback: null, 383 | xdomain: '*', 384 | polling: 0 385 | }; 386 | 387 | this.messageRegex = null; 388 | this.messageHandlers = {}; 389 | 390 | // Ensure a config object 391 | config = (config || {}); 392 | 393 | /** 394 | * Bind a callback to a given messageType from the child. 395 | * 396 | * Reserved message names are: "width". 397 | * 398 | * @memberof Child.prototype 399 | * @method onMessage 400 | * @param {String} messageType The type of message being listened for. 401 | * @param {Child~onMessageCallback} callback The callback to invoke when a message of the given type is received. 402 | */ 403 | this.onMessage = function(messageType, callback) { 404 | if (!(messageType in this.messageHandlers)) { 405 | this.messageHandlers[messageType] = []; 406 | } 407 | 408 | this.messageHandlers[messageType].push(callback); 409 | }; 410 | 411 | /** 412 | * @callback Child~onMessageCallback 413 | * @param {String} message The message data. 414 | */ 415 | 416 | /** 417 | * Fire all event handlers for a given message type. 418 | * 419 | * @memberof Parent.prototype 420 | * @method _fire 421 | * @param {String} messageType The type of message. 422 | * @param {String} message The message data. 423 | */ 424 | this._fire = function(messageType, message) { 425 | /* 426 | * Fire all event handlers for a given message type. 427 | */ 428 | if (messageType in this.messageHandlers) { 429 | for (var i = 0; i < this.messageHandlers[messageType].length; i++) { 430 | this.messageHandlers[messageType][i].call(this, message); 431 | } 432 | } 433 | }; 434 | 435 | /** 436 | * Process a new message from the parent. 437 | * 438 | * @memberof Child.prototype 439 | * @method _processMessage 440 | * @param {Event} e A message event. 441 | */ 442 | this._processMessage = function(e) { 443 | /* 444 | * Process a new message from parent frame. 445 | */ 446 | // First, punt if this isn't from an acceptable xdomain. 447 | if (!_isSafeMessage(e, this.settings)) { 448 | return; 449 | } 450 | 451 | // Discard object messages, we only care about strings 452 | if (typeof e.data !== 'string') { 453 | return; 454 | } 455 | 456 | // Get the message from the parent. 457 | var match = e.data.match(this.messageRegex); 458 | 459 | // If there's no match or it's a bad format, punt. 460 | if (!match || match.length !== 3) { return; } 461 | 462 | var messageType = match[1]; 463 | var message = match[2]; 464 | 465 | this._fire(messageType, message); 466 | }.bind(this); 467 | 468 | /** 469 | * Resize iframe in response to new width message from parent. 470 | * 471 | * @memberof Child.prototype 472 | * @method _onWidthMessage 473 | * @param {String} message The new width. 474 | */ 475 | this._onWidthMessage = function(message) { 476 | /* 477 | * Handle width message from the child. 478 | */ 479 | var width = parseInt(message); 480 | 481 | // Change the width if it's different. 482 | if (width !== this.parentWidth) { 483 | this.parentWidth = width; 484 | 485 | // Call the callback function if it exists. 486 | if (this.settings.renderCallback) { 487 | this.settings.renderCallback(width); 488 | } 489 | 490 | // Send the height back to the parent. 491 | this.sendHeight(); 492 | } 493 | }; 494 | 495 | /** 496 | * Send a message to the the Parent. 497 | * 498 | * @memberof Child.prototype 499 | * @method sendMessage 500 | * @param {String} messageType The type of message to send. 501 | * @param {String} message The message data to send. 502 | */ 503 | this.sendMessage = function(messageType, message) { 504 | /* 505 | * Send a message to the parent. 506 | */ 507 | window.parent.postMessage(_makeMessage(this.id, messageType, message), '*'); 508 | }; 509 | 510 | /** 511 | * Transmit the current iframe height to the parent. 512 | * 513 | * Call this directly in cases where you manually alter the height of the iframe contents. 514 | * 515 | * @memberof Child.prototype 516 | * @method sendHeight 517 | */ 518 | this.sendHeight = function() { 519 | // Get the child's height. 520 | var height = document.getElementsByTagName('body')[0].offsetHeight.toString(); 521 | 522 | // Send the height to the parent. 523 | this.sendMessage('height', height); 524 | }.bind(this); 525 | this.sendHeightToParent = function() { 526 | // Get the child's height. 527 | var height = document.getElementsByTagName('body')[0].offsetHeight.toString(); 528 | 529 | // Send the height to the parent. 530 | this.sendMessage('height', height); 531 | }.bind(this); 532 | 533 | /** 534 | * Scroll parent to a given element id. 535 | * 536 | * @memberof Child.prototype 537 | * @method scrollParentTo 538 | * @param {String} hash The id of the element to scroll to. 539 | */ 540 | this.scrollParentTo = function(hash) { 541 | this.sendMessage('navigateTo', '#' + hash); 542 | }; 543 | 544 | /** 545 | * Navigate parent to a given url. 546 | * 547 | * @memberof Parent.prototype 548 | * @method navigateParentTo 549 | * @param {String} url The url to navigate to. 550 | */ 551 | this.navigateParentTo = function(url) { 552 | this.sendMessage('navigateTo', url); 553 | }; 554 | 555 | // Identify what ID the parent knows this child as. 556 | this.id = _getParameterByName('childId') || config.id; 557 | this.messageRegex = new RegExp('^pym' + MESSAGE_DELIMITER + this.id + MESSAGE_DELIMITER + '(\\S+)' + MESSAGE_DELIMITER + '(.+)$'); 558 | 559 | // Get the initial width from a URL parameter. 560 | var width = parseInt(_getParameterByName('initialWidth')); 561 | 562 | // Get the url of the parent frame 563 | this.parentUrl = _getParameterByName('parentUrl'); 564 | 565 | // Bind the required message handlers 566 | this.onMessage('width', this._onWidthMessage); 567 | 568 | // Initialize settings with overrides. 569 | for (var key in config) { 570 | this.settings[key] = config[key]; 571 | } 572 | 573 | // Set up a listener to handle any incoming messages. 574 | window.addEventListener('message', this._processMessage, false); 575 | 576 | // If there's a callback function, call it. 577 | if (this.settings.renderCallback) { 578 | this.settings.renderCallback(width); 579 | } 580 | 581 | // Send the initial height to the parent. 582 | this.sendHeight(); 583 | 584 | // If we're configured to poll, create a setInterval to handle that. 585 | if (this.settings.polling) { 586 | window.setInterval(this.sendHeight, this.settings.polling); 587 | } 588 | 589 | return this; 590 | }; 591 | 592 | // Initialize elements with pym data attributes 593 | _autoInit(); 594 | 595 | return lib; 596 | }); 597 | -------------------------------------------------------------------------------- /js/pym.v1.min.js: -------------------------------------------------------------------------------- 1 | /*! pym.js - v1.3.2 - 2018-02-13 */ 2 | 3 | !function(a){"function"==typeof define&&define.amd?define(a):"undefined"!=typeof module&&module.exports?module.exports=a():window.pym=a.call(this)}(function(){var a={},b=function(a){var b=document.createEvent("Event");b.initEvent("pym:"+a,!0,!0),document.dispatchEvent(b)},c=function(a){var b=new RegExp("[\\?&]"+a.replace(/[\[]/,"\\[").replace(/[\]]/,"\\]")+"=([^&#]*)"),c=b.exec(location.search);return null===c?"":decodeURIComponent(c[1].replace(/\+/g," "))},d=function(a,b){if(("*"===b.xdomain||a.origin.match(new RegExp(b.xdomain+"$")))&&"string"==typeof a.data)return!0},e=function(a){var b=/^(?:(?:https?|mailto|ftp):|[^&:\/?#]*(?:[\/?#]|$))/gi;if(a.match(b))return!0},f=function(a,b,c){return["pym",a,b,c].join("xPYMx")},g=function(a){var b=["pym",a,"(\\S+)","(.*)"];return new RegExp("^"+b.join("xPYMx")+"$")},h=Date.now||function(){return(new Date).getTime()},i=function(a,b,c){var d,e,f,g=null,i=0;c||(c={});var j=function(){i=!1===c.leading?0:h(),g=null,f=a.apply(d,e),g||(d=e=null)};return function(){var k=h();i||!1!==c.leading||(i=k);var l=b-(k-i);return d=this,e=arguments,l<=0||l>b?(g&&(clearTimeout(g),g=null),i=k,f=a.apply(d,e),g||(d=e=null)):g||!1===c.trailing||(g=setTimeout(j,l)),f}},j=function(){for(var b=a.autoInitInstances.length,c=b-1;c>=0;c--){var d=a.autoInitInstances[c];d.el.getElementsByTagName("iframe").length&&d.el.getElementsByTagName("iframe")[0].contentWindow||a.autoInitInstances.splice(c,1)}};return a.autoInitInstances=[],a.autoInit=function(c){var d=document.querySelectorAll("[data-pym-src]:not([data-pym-auto-initialized])"),e=d.length;j();for(var f=0;f-1&&(b=this.url.substring(c,this.url.length),this.url=this.url.substring(0,c)),this.url.indexOf("?")<0?this.url+="?":this.url+="&",this.iframe.src=this.url+"initialWidth="+a+"&childId="+this.id,this.settings.optionalparams&&(this.iframe.src+="&parentTitle="+encodeURIComponent(document.title),this.iframe.src+="&"+this.settings.parenturlparam+"="+encodeURIComponent(this.settings.parenturlvalue)),this.iframe.src+=b,this.iframe.setAttribute("width","100%"),this.iframe.setAttribute("scrolling","no"),this.iframe.setAttribute("marginheight","0"),this.iframe.setAttribute("frameborder","0"),this.settings.title&&this.iframe.setAttribute("title",this.settings.title),void 0!==this.settings.allowfullscreen&&!1!==this.settings.allowfullscreen&&this.iframe.setAttribute("allowfullscreen",""),void 0!==this.settings.sandbox&&"string"==typeof this.settings.sandbox&&this.iframe.setAttribute("sandbox",this.settings.sandbox),this.settings.id&&(document.getElementById(this.settings.id)||this.iframe.setAttribute("id",this.settings.id)),this.settings.name&&this.iframe.setAttribute("name",this.settings.name);this.el.firstChild;)this.el.removeChild(this.el.firstChild);this.el.appendChild(this.iframe),window.addEventListener("resize",this._onResize),this.settings.trackscroll&&window.addEventListener("scroll",this._throttleOnScroll)},this._onResize=function(){this.sendWidth(),this.settings.trackscroll&&this.sendViewportAndIFramePosition()}.bind(this),this._onScroll=function(){this.sendViewportAndIFramePosition()}.bind(this),this._fire=function(a,b){if(a in this.messageHandlers)for(var c=0;c 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /pym-shortcode.php: -------------------------------------------------------------------------------- 1 | Pym.js Embed Settings**, decide whether you'd like to change the plugin's behavior to use a non-default source URL for `Pym.js`, and whether you'd like to prevent post authors from setting embed-specific URLs for `Pym.js` 25 | 4. Begin embedding content! 26 | 27 | == Frequently Asked Questions == 28 | 29 | For answers to frequently asked questions, [see this plugin's documentation on GitHub](https://github.com/INN/pym-shortcode/tree/master/docs). 30 | 31 | For support resources, [see this plugin's documentation on GitHub](https://github.com/INN/pym-shortcode/tree/master/docs). 32 | 33 | == Screenshots == 34 | 35 | Embeddable table from NPR: 36 | 37 | ![an embeddable table from NPR](img/responsive-iframe-npr.png) 38 | 39 | Pym.js Shortcode in a WordPress post: 40 | 41 | ![Pym.js Shortcode in a WordPress post](img/pym-shortcode-in-post.png) 42 | 43 | Desktop view of the WordPress post with the NPR embed using Pym.js Shortcode: 44 | 45 | ![Desktop view of the WordPress post with the NPR embed using Pym.js Shortcode](img/pym-example-desktop.png) 46 | 47 | Mobile view of the WordPress post with the NPR embed using Pym.js Shortcode: 48 | 49 | ![Mobile view of the WordPress post with the NPR embed using Pym.js Shortcode](img/pym-example-phone.png) 50 | 51 | == Changelog == 52 | 53 | = 1.3.2.4 = 54 | 55 | - Now tested up to WordPress 5.4 and Gutenberg 7.8. 56 | - Fixes a presentational error in the Pym.js Embeds Block's block inspector control within the editor. PR [#74](https://github.com/INN/pym-shortcode/pull/74) for issue [#72](https://github.com/INN/pym-shortcode/issues/72). 57 | 58 | = 1.3.2.3 = 59 | 60 | New features: 61 | 62 | - Adds compatibility with [the official WordPress AMP Plugin](https://wordpress.org/plugins/amp/). On AMP endpoints, markup for Pym.js-based embeds is converted to `amp-iframe` tags. If you're not using the AMP Plugin, your site won't be affected. And if you're not viewing a page on an AMP endpoint, the page won't be affected. PR [#62](https://github.com/INN/pym-shortcode/pull/62) by Claudiu Lodromanean, [originally for Automattic's Newspack](https://github.com/Automattic/newspack-plugin/pull/276). 63 | 64 | Other updates: 65 | 66 | - Adds credit to GitHub user [eidietrich](https://github.com/eidietrich) for [PR #55](https://github.com/INN/pym-shortcode/pull/55) in the 1.3.2.2 release notes. 67 | - Fixes a 'nwesroom' typo. [PR #66](https://github.com/INN/pym-shortcode/pull/66) for issue [#65](https://github.com/INN/pym-shortcode/issues/65). 68 | 69 | = 1.3.2.2 = 70 | 71 | - Plugin is now tested against WordPress 5.0 beta 3. 72 | - Adds support for WordPress 5.0. 73 | - Fixes bug where the Pym.js Embeds block did not work in WordPress 5.0. [PR #58](https://github.com/INN/pym-shortcode/pulls/58) for [issue #57](https://github.com/INN/pym-shortcode/issues/57). 74 | - Adds advice for where to host graphics files. [PR #55](https://github.com/INN/pym-shortcode/pull/55) from Github user [eidietrich](https://github.com/eidietrich). 75 | 76 | = 1.3.2.1 = 77 | 78 | **This is a major update! Please read the release notes.** 79 | 80 | Following the practice begun at plugin version 1.1.2 of [having the plugin version number match the version number of the bundled copy of `Pym.js`](https://github.com/INN/pym-shortcode/blob/master/docs/maintainer-notes.md), the first three numbers in this plugin's version do not change with this release because the `Pym.js` version has not changed. We've tacked a `.1` on to the end to denote this release. Please read the release notes and test your site as appropriate before upgrading in production. 81 | 82 | We wish to thank all who helped us test [the release candidate](https://github.com/INN/pym-shortcode/releases/tag/v1.3.2.1-rc1) for this version, including Mike Janssen at [Current.org](https://current.org/) and Alyson Hurt at the NPR Visuals Team. 83 | 84 | New features: 85 | 86 | * Plugin renamed from "Pym Shortcode" to "Pym.js Embeds". 87 | * Adds a "Pym.js Embed" block for use in Gutenberg. [PR #34](https://github.com/INN/pym-shortcode/pull/34) for issue [#28](https://github.com/INN/pym-shortcode/issues/28). 88 | * If a block is created using this plugin and Gutenberg, and Gutenberg is then disabled, the block will show a link to the embedded graphic. 89 | * Through the settings page, you can now serve Pym.js using your newsroom's CDN or NPR's CDN! [PR #45]() for [issue #31](https://github.com/INN/pym-shortcode/issues/31). 90 | * Adds a settings page, available to [those users with the `manage_options` capability](https://codex.wordpress.org/Roles_and_Capabilities#Capability_vs._Role_Table), with the following options: 91 | * Change the default pymsrc URL. [PR #45](https://github.com/INN/pym-shortcode/pull/45) for [issue #8](https://github.com/INN/pym-shortcode/issues/8). 92 | * Override block and shortcode pymsrc URLs with the default pymsrc URL. [PR #45](https://github.com/INN/pym-shortcode/pull/45) for [issue #8](https://github.com/INN/pym-shortcode/issues/8). 93 | * Adds an informational page, available to all who can make posts, that lists the plugin's default source URL for `Pym.js`. This is to make the process of building new interactives easier. 94 | * Shortcode now gains an explicit `align=""` parameter, so that WordPress's generated [alignment CSS classes](https://codex.wordpress.org/CSS#WordPress_Generated_Classes) can be used on embeds. By enabling this in the shortcode, the Gutenberg Block also gains support for alignment. [PR #34](https://github.com/INN/pym-shortcode/pull/34). Prior to this release, the alignment classes could be added via the `class=""` parameter. 95 | * Script tags for embeds are no longer output by `the_content()`, instead being output during `wp_footer()` by [closures](https://secure.php.net/manual/en/functions.anonymous.php) hooked on the `'wp_footer'` action. [PR #34](https://github.com/INN/pym-shortcode/pull/34) for issues [#33](https://github.com/INN/pym-shortcode/issues/33) and [#35](https://github.com/INN/pym-shortcode/issues/35). 96 | * The script tag used to run `new pym.Parent` is now configurable. By replacing the [pluggable function](https://codex.wordpress.org/Pluggable_Functions) `pym_shortcode_script_footer_enqueue()` with your own function, you can now use alternate forms of embed code that may be required for PJAX sites or custom versions of Pym.js. This resolves issue [#19](https://github.com/INN/pym-shortcode/issues/19). 97 | * Adds "Requires PHP: 5.3" metadata to the plugin's `readme.txt`, since we're now using PHP namespaces for some code. 98 | * Adds documentation for how to test the plugin: 99 | * tests to run before enabling the "override pymsrc" option in production 100 | * tests to run for site compatibility with Gutenberg 101 | 102 | Changes: 103 | 104 | * The source URL for `pymjs`, known as the pymsrc URL, is now passed through [wp_http_validate_url](https://developer.wordpress.org/reference/functions/wp_http_validate_url/). [PR #45](https://github.com/INN/pym-shortcode/pull/45) for [issue #8](https://github.com/INN/pym-shortcode/issues/8). 105 | * The source URL for `pym.js` is no longer output by `the_content()`, instead being output during `wp_footer` by an action dedicated to the task. If different shortcodes and/or blocks on the page specify different source URLs for Pym.js, all are output (after removing duplicates), but a message is logged in the browser console. If `WP_DEBUG` is set, this message is also logged to the server log, with the post ID specified. [PR #34](https://github.com/INN/pym-shortcode/pull/34) for issues [#33](https://github.com/INN/pym-shortcode/issues/33) and [#35](https://github.com/INN/pym-shortcode/issues/35). See https://github.com/INN/pym-shortcode/tree/master/docs#ive-set-a-different-pymsrc-option-but-now-im-seeing-a-message-in-the-console 106 | * `docs/updating-pym.md` becomes `docs/maintainer-notes.md` 107 | * Script tags for embeds are no longer output by `the_content()`, instead being output during `wp_footer()` by [closures](https://secure.php.net/manual/en/functions.anonymous.php) hooked on the `'wp_footer'` action. [PR #34](https://github.com/INN/pym-shortcode/pull/34) for issues [#33](https://github.com/INN/pym-shortcode/issues/33) and [#35](https://github.com/INN/pym-shortcode/issues/35). 108 | 109 | Removed: 110 | 111 | * Script tags for embeds are no longer output by `the_content()`, instead being output during `wp_footer()` by [closures](https://secure.php.net/manual/en/functions.anonymous.php) hooked on the `'wp_footer'` action. [PR #34](https://github.com/INN/pym-shortcode/pull/34) for issues [#33](https://github.com/INN/pym-shortcode/issues/33) and [#35](https://github.com/INN/pym-shortcode/issues/35). 112 | 113 | = 1.3.2 = 114 | 115 | * *RECOMMENDED UPDATE* : Pym.js users, NPR has released an update that closes a potential security hole. We recommend everyone update to 1.3.2. 116 | * Update to Pym.js version 1.3.2: https://github.com/nprapps/pym.js/releases/tag/v1.3.2 (Changelog at https://github.com/nprapps/pym.js/blob/v1.3.2/CHANGELOG) 117 | 118 | = 1.3.1 = 119 | 120 | * Update to Pym.js version 1.3.1: https://github.com/nprapps/pym.js/releases/tag/v1.3.1 (Changelog at https://github.com/nprapps/pym.js/blob/v1.3.1/CHANGELOG) 121 | * (we skipped pym.js version 1.3.0: https://github.com/nprapps/pym.js/releases/tag/v1.3.0) 122 | 123 | = 1.2.2 = 124 | 125 | * Update to Pym.js version 1.2.2: https://github.com/nprapps/pym.js/releases/tag/v1.2.2 (Changelog at https://github.com/nprapps/pym.js/blob/master/CHANGELOG ) 126 | * (we skipped Pym.js version 1.2.1: https://github.com/nprapps/pym.js/releases/tag/v1.2.1 ) 127 | * Add `id=""` attribute to allow setting custom IDs on embeds. [#21](https://github.com/INN/pym-shortcode/issues/21) 128 | * Add `class=""` attribute to allow setting custom classes on embeds. [#22](https://github.com/INN/pym-shortcode/issues/22) and [#23](https://github.com/INN/pym-shortcode/issues/23). 129 | * Add a default class name `pym` to all embed-containing div elements output by this plugin, and a filter 'pym_shortcode_default_class' to allow changing it. 130 | 131 | = 1.2.0.2 = 132 | 133 | * Fix encoding error on pym.v1.min.js, [thanks to lchheng](https://github.com/INN/pym-shortcode/pull/18) 134 | 135 | = 1.2.0.1 = 136 | 137 | * Add attribution for lchheng's [pymsrc fix](https://github.com/INN/pym-shortcode/pull/17). 138 | 139 | = 1.2.0 = 140 | 141 | * Update to Pym.js version 1.2.0: https://github.com/nprapps/pym.js/releases/tag/v1.2.0 (Changelog at https://github.com/nprapps/pym.js/blob/v1.2.0/CHANGELOG ) 142 | * Fixes a bug where the `pymsrc` attribute might have been ignored, for real this time. [Thanks, lchheng!](https://github.com/INN/pym-shortcode/pull/17) 143 | 144 | = 1.1.2 = 145 | 146 | * Update to Pym.js version 1.1.2: https://github.com/nprapps/pym.js/releases/tag/v1.1.2 147 | * Switch the new default url of `Pym.js` in this plugin to `js/pym.v1.min.js`, leaving the existing `js/pym.js` where it is. 148 | * Provide additional notes in [the documentation](https://github.com/INN/pym-shortcode/tree/master/docs) for maintainers on updating `Pym.js` in this plugin 149 | * Fixes a bug where the `pymsrc` attribute might have been ignored 150 | * Fixes and corrections to documentation. 151 | 152 | = 1.0 = 153 | 154 | * First release of the plugin 155 | 156 | == Pym.js Resources from NPR == 157 | 158 | You may also want to look at NPR's Pym.js resources: 159 | 160 | * [Pym.js homepage](http://blog.apps.npr.org/pym.js/) 161 | * [Pym.js repo on GutHub/nprapps](https://github.com/nprapps/pym.js/) 162 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # 3 | # This file is a WordPress.org plugin release script. 4 | # For more about how it works, see the documentation at 5 | # https://github.com/INN/docs/blob/master/projects/wordpress-plugins/release.sh.md 6 | # 7 | RELEASE_DIR=release; 8 | SVN_PATH=$RELEASE_DIR/svn; 9 | SVN_REPO="https://plugins.svn.wordpress.org/pym-shortcode/"; 10 | BLACKLIST=( 11 | .\* 12 | release.sh 13 | mkdocs.yml 14 | phpunit.xml 15 | requirements.txt 16 | composer.lock 17 | composer.json 18 | package.json 19 | bower.json 20 | Gruntfile.js 21 | release/\* 22 | tests/\* 23 | node_modules/\* 24 | ./\*\*/.\* 25 | ); 26 | 27 | function ensure_release_dir() { 28 | local release_dir="$1"; 29 | if [[ ! -d $release_dir ]] 30 | then 31 | echo "Creating $release_dir directory"; 32 | mkdir -p $release_dir; 33 | fi 34 | } 35 | 36 | function is_master_branch() { 37 | local remotes=`git ls-remote --quiet`; 38 | local current=`get_current_git_commit`; 39 | local is_master=`echo "$remotes" | grep "refs/heads/master" | grep $current | awk '{print $2;}'`; 40 | echo "$is_master"; 41 | } 42 | 43 | function is_git_tag() { 44 | local remotes=`git ls-remote --quiet`; 45 | local current=`get_current_git_commit`; 46 | local is_tag=`echo "$remotes" | grep "refs/tags" | grep $current | awk '{print $2;}' | sed -e 's/^refs\/tags\///;s/\^{}$//'`; 47 | echo "$is_tag"; 48 | } 49 | 50 | function get_current_git_commit() { 51 | echo `git rev-parse HEAD`; 52 | } 53 | 54 | function check_repo_state() { 55 | # 56 | # Check the state of this git repo. If no tag is checked out 57 | # and we're not on the master branch, bail. 58 | # 59 | local is_master=`is_master_branch`; 60 | local is_tag=`is_git_tag`; 61 | 62 | if [[ "$is_master" == "" && "$is_tag" == "" ]] 63 | then 64 | echo "Bad release state for git repo!"; 65 | echo "Make sure you've checked out a tag or the master branch before releasing."; 66 | exit 1; 67 | else 68 | echo "Repository is ready for deployment..."; 69 | fi 70 | } 71 | 72 | function confirm_deployment() { 73 | # make sure we know what we're doing 74 | local is_master=`is_master_branch`; 75 | local is_tag=`is_git_tag`; 76 | local which_text="[master] and [$is_tag]"; 77 | 78 | if [[ $is_master == "" ]] 79 | then 80 | which_text="[$is_tag]"; 81 | fi 82 | 83 | if [[ $is_tag == "" ]] 84 | then 85 | which_text="[master]"; 86 | fi 87 | 88 | read -p "Really release plugin from $which_text? [y/N] " -r; 89 | if [[ ! $REPLY =~ ^[Yy]$ ]] 90 | then 91 | echo "No changes made. Exiting..."; 92 | exit 0; 93 | fi 94 | } 95 | 96 | function init_update_svn_repo() { 97 | local svn_path="$1"; 98 | local svn_repo="$2"; 99 | 100 | if [[ "$svn_path" == "" || "$svn_repo" == "" ]] 101 | then 102 | echo "The svn_path and svn_repo arguments are required."; 103 | exit 1; 104 | fi 105 | 106 | # init and update svn repo 107 | if [[ ! -d $svn_path ]] 108 | then 109 | echo " - checking out svn repo"; 110 | OUT=`mkdir -p $svn_path && svn checkout $svn_repo $svn_path` 111 | if [[ $? -ne 0 ]] 112 | then 113 | echo "$OUT"; 114 | exit 1; 115 | fi 116 | else 117 | echo " - updating svn repo"; 118 | OUT=`cd $svn_path && svn update`; 119 | if [[ $? -ne 0 ]] 120 | then 121 | echo "$OUT"; 122 | exit 1; 123 | fi 124 | fi 125 | } 126 | 127 | function create_release_zip() { 128 | # Build a release zip file 129 | echo "Creating release/wp-release.zip"; 130 | 131 | OUT=`rm -f release/wp-release.zip`; 132 | OUT=`zip -x "${BLACKLIST[@]}" -q -r release/wp-release.zip .`; 133 | 134 | if [[ $? -ne 0 ]] 135 | then 136 | echo "$OUT"; 137 | exit 1; 138 | fi 139 | } 140 | 141 | function install_update_dependencies() { 142 | echo "Checking for third-party/vendor dependencies..."; 143 | # If composer.json exists, run composer install 144 | if [[ -f composer.json ]] 145 | then 146 | echo " - installing composer dependencies"; 147 | composer install --no-dev; 148 | fi 149 | 150 | # If package.json exists, run npm install 151 | if [[ -f package.json ]] 152 | then 153 | echo " - installing npm dependencies"; 154 | npm install; 155 | fi 156 | 157 | # If bower.json exists, run bower install 158 | if [[ -f bower.json ]] 159 | then 160 | echo " - installing bower dependencies"; 161 | bower install; 162 | fi 163 | } 164 | 165 | function write_trunk() { 166 | local svn_path="$1"; 167 | local is_master=`is_master_branch`; 168 | 169 | if [[ $is_master != "" ]] 170 | then 171 | trunk_path=$svn_path/trunk; 172 | echo "Writing to $trunk_path"; 173 | 174 | # overwrite with unzip 175 | OUT=`rm -rf $trunk_path && unzip -o release/wp-release.zip -d $trunk_path`; 176 | if [[ $? -ne 0 ]] 177 | then 178 | echo "$OUT"; 179 | exit 1; 180 | fi; 181 | 182 | # stage all changes (adds and removes) 183 | OUT=`cd $trunk_path && svn st | grep '^\?' | awk '{print \$2}' | xargs svn add` # add all 184 | if [[ $? -ne 0 ]] 185 | then 186 | echo "$OUT"; 187 | exit 1; 188 | fi 189 | 190 | OUT=`cd $trunk_path && svn st | grep '^\!' | awk '{print \$2}' | xargs svn rm` # remove all 191 | if [[ $? -ne 0 ]] 192 | then 193 | echo "$OUT"; 194 | exit 1; 195 | fi 196 | 197 | # make sure something has changed besides the autoloader hashes 198 | CHANGES=`cd $trunk_path && svn st | grep -v 'autoload\(_real\)\?\.php'` 199 | if [[ $CHANGES == "" ]] 200 | then 201 | echo "There are no changes to commit for trunk."; 202 | OUT=`cd $trunk_path && svn revert --recursive .`; 203 | if [[ $? -ne 0 ]] 204 | then 205 | echo "$OUT"; 206 | exit 1; 207 | fi 208 | else 209 | echo "Committing $trunk_path (slow) ..."; 210 | CURRENT=`get_current_git_commit`; 211 | OUT=`cd $trunk_path && svn commit -m "update trunk to git $CURRENT"`; 212 | if [[ $? -ne 0 ]] 213 | then 214 | echo "$OUT"; 215 | exit 1; 216 | fi 217 | fi 218 | fi 219 | } 220 | 221 | function write_tag() { 222 | local svn_path="$1"; 223 | local is_tag=`is_git_tag`; 224 | 225 | if [[ $is_tag != "" ]] 226 | then 227 | WP_TAG=`echo $is_tag | sed -e 's/^v//'`; 228 | TAG_PATH=$svn_path/tags/$WP_TAG; 229 | echo "Writing to $TAG_PATH"; 230 | 231 | # overwrite with unzip 232 | OUT=`rm -rf $TAG_PATH && unzip -o release/wp-release.zip -d $TAG_PATH`; 233 | if [[ $? -ne 0 ]] 234 | then 235 | echo "$OUT"; 236 | exit 1; 237 | fi 238 | 239 | # TODO: set version numbers and/or ensure version numbers in plugin files are correct 240 | 241 | # stage all changes (adds and removes) 242 | OUT=`cd $svn_path/tags && svn st | grep '^\?' | awk '{print \$2}' | xargs svn add`; # add all 243 | if [[ $? -ne 0 ]] 244 | then 245 | echo "$OUT"; 246 | exit 1; 247 | fi 248 | OUT=`cd $svn_path/tags && svn st | grep '^\!' | awk '{print \$2}' | xargs svn rm`; # remove all 249 | if [[ $? -ne 0 ]] 250 | then 251 | echo "$OUT"; 252 | exit 1; 253 | fi 254 | 255 | # make sure something has changed besides the autoloader hashes 256 | CHANGES=`cd $svn_path/tags && svn st | grep -v 'autoload\(_real\)\?\.php'`; 257 | if [[ $CHANGES == "" ]] 258 | then 259 | echo "There are no changes to commit for $TAG_PATH"; 260 | OUT=`cd $svn_path/tags && svn revert --recursive .`; 261 | if [[ $? -ne 0 ]] 262 | then 263 | echo "$OUT"; 264 | exit 1; 265 | fi 266 | else 267 | echo "Committing $TAG_PATH (slow) ..."; 268 | CURRENT=`get_current_git_commit`; 269 | OUT=`cd $svn_path/tags && svn commit -m "update $WP_TAG to git $CURRENT"`; 270 | if [[ $? -ne 0 ]] 271 | then 272 | echo "$OUT"; 273 | exit 1; 274 | fi 275 | fi 276 | fi 277 | } 278 | 279 | function help_text() { 280 | echo "Usage: ./release.sh [--dry_run, --help]"; 281 | echo ""; 282 | echo "--dry_run: Create the release directory and release zip, 283 | but bon't actually commit to the SVN repository." 284 | echo ""; 285 | echo "--help: Display this help screen and exit."; 286 | echo ""; 287 | echo "For more information about this script, see: 288 | https://github.com/INN/docs/blob/master/projects/wordpress-plugins/release.sh.md"; 289 | echo ""; 290 | exit 0; 291 | } 292 | 293 | # Parse args 294 | if [[ $@ =~ "help" || $@ =~ "--help" ]] 295 | then 296 | help_text; 297 | fi 298 | 299 | if [[ $@ =~ "dry_run" || $@ =~ "--dry_run" ]] 300 | then 301 | dry_run=1; 302 | else 303 | dry_run=0; 304 | fi 305 | 306 | # Start the release process 307 | ensure_release_dir "$RELEASE_DIR"; 308 | check_repo_state; 309 | confirm_deployment; 310 | 311 | if [[ $dry_run == 0 ]] 312 | then 313 | init_update_svn_repo "$SVN_PATH" "$SVN_REPO"; 314 | fi 315 | 316 | install_update_dependencies; 317 | create_release_zip; 318 | 319 | if [[ $dry_run == 0 ]] 320 | then 321 | write_trunk "$SVN_PATH"; 322 | write_tag "$SVN_PATH"; 323 | fi 324 | 325 | echo "Release process finished." 326 | --------------------------------------------------------------------------------